MCP server: giải thích và tự build từ đầu
MCP server là gì, local và remote khác nhau ra sao, vì sao cần OAuth, rồi build một server notes bằng FastMCP và thêm đăng nhập, scope theo user.
✓ Đã duyệt độc lập · cập nhật 16/10/2026 · 30 min
1. MCP server thật ra chỉ là một danh sách function
Tim mở đầu bằng một nhận xét: ai cũng đang nói về MCP server. Claude có, Cursor có, ChatGPT có. Và thật lòng mà nói, cách phần lớn mọi người giải thích khiến nó nghe phức tạp hơn nhiều so với thực tế. Theo Tim, một MCP server chỉ là một chương trình đưa cho AI model một danh sách các function mà model được phép gọi. Chỉ vậy thôi.
Trong video này, anh hứa ba việc. Một, phân tích chính xác MCP server hoạt động ra sao. Hai, build một server từ đầu bằng Python. Ba, cho xem phần mà gần như tutorial nào cũng bỏ qua: đưa server lên chạy thật (live) và dựng authentication cùng authorization thật sự. "Stay tuned", rồi anh đi vào phần lý thuyết.
Câu hỏi đầu tiên: MCP server là gì? Tim muốn giải thích đơn giản nhất có thể. Một language model tự nó không làm được gì cả. Nó không đọc được file của bạn, không xem được lịch của bạn, thậm chí không query được database của bạn. Tất cả những gì nó làm được là nhận text vào và trả text ra.
Vậy nếu muốn model làm một việc gì đó, bạn phải cho nó quyền truy cập vào tool. Một tool thực chất chỉ là một function có tên, có mô tả (description) và có vài tham số (parameters), tức là có input và có output. Model đọc danh sách tool này và tự quyết định: "okay, tôi cần gọi tool này", hay "tôi cần gọi tool X".
2. Model không bao giờ tự gọi tool
Tim nhấn mạnh một chi tiết mà nhiều người hiểu nhầm. Khi model muốn gọi một tool, thực ra tất cả những gì nó làm là xuất ra một đoạn text nói rằng nó muốn gọi tool nào. Code của bạn mới là thứ thực thi tool đó. Sau đó bạn gửi kết quả ngược lại cho model, hay cho LLM, để nó xử lý tiếp.

Nói cách khác, model không bao giờ gọi tool, nó chỉ yêu cầu được gọi. Code của bạn gọi tool, đưa kết quả lại cho model, và model lúc nào cũng chỉ làm việc với text.
3. Trước MCP: ai cũng tự viết lại phần plumbing
Trước khi MCP ra đời, có một vấn đề: mọi ứng dụng đều phải tự build phần plumbing cho tool mà Tim vừa mô tả. Và nó khác nhau với từng model, với từng tool. Ví dụ, nếu bạn build một integration GitHub cho Claude, nó không chạy được trong Cursor. Nó cũng không chạy được trong ChatGPT. Thế là ai cũng viết đi viết lại cùng một thứ, trong khi số agent thì ngày càng nhiều thêm.
MCP được tạo ra đơn giản là một tiêu chuẩn (standard) cho chuyện đó. MCP là viết tắt của Model Context Protocol. Ý nghĩa của nó: bạn viết tool một lần dưới dạng một MCP server, rồi bất kỳ ứng dụng nào "nói" được MCP đều dùng được những tool đó. Claude, Cursor, VS Code, ChatGPT, không quan trọng, bất kỳ AI agent nào ngoài kia.
Người ta hay ví MCP như "USB-C cho AI tool", và Tim thấy đó là một phép so sánh khá chuẩn: một cái phích cắm cắm vừa gần như mọi cổng.
4. Hai phía: MCP server và MCP client
Có hai phía bạn cần hiểu. Thứ nhất là MCP server, chính là code của bạn: nó expose các tool. Thứ hai là MCP client, ứng dụng mà model "sống" bên trong, ví dụ Claude Desktop hay Cursor. AI agent của bạn đóng vai client, còn server là nơi bạn thực sự viết và expose tool.
Khi client khởi động, nó chỉ hỏi server một câu: bạn có những tool nào? Server gửi lại một danh sách gồm tên và mô tả của từng tool, và danh sách đó được đưa cho model. Rồi khi model muốn gọi một tool trong số đó, client gửi một request tới server. Server chạy tool, tức chạy function, rồi gửi kết quả về. Toàn bộ trao đổi này diễn ra bằng JSON.
Tim diễn lại cuộc trao đổi qua lại cho dễ hình dung: "Này, tôi muốn gọi tool này", tool được gọi, lấy kết quả, đưa lại, đưa cho model. "À, kết quả là vậy. Có lẽ tôi nên gọi thêm một tool nữa." Bạn hiểu ý rồi đấy.
5. Hai cách chạy: local qua stdio và remote qua HTTP
Có hai cách chính để một MCP server chạy. Cách thứ nhất là local. Nghĩa là client tự khởi chạy server của bạn như một subprocess trên chính máy của bạn, và nói chuyện trực tiếp với nó qua standard input và output, tức stdio. Claude Desktop thật sự sẽ chạy một lệnh kiểu python my_server.py rồi bơm (pipe) JSON vào đó.
Theo Tim, đây là thứ mà có lẽ 90% tutorial ngoài kia chỉ cho bạn. Nó tuyệt cho việc cá nhân, nhưng không dùng được khi người khác cần truy cập server.

https://notes.example.com/mcp, cách mọi sản phẩm thật như GitHub, Notion, Stripe đang làm. Dòng dưới cùng: khi server của bạn là một URL, bạn có một vấn đề mà bản local chưa bao giờ có.Cách thứ hai là chạy remote. Server của bạn chạy ở đâu đó trên internet, nên nó có một URL, và client kết nối tới nó qua HTTP. Gần như mọi sản phẩm thật đều làm theo cách này. Ví dụ MCP server của GitHub, của Notion, của Stripe, tất cả thực chất chỉ là những URL. Đây là thứ bạn muốn nếu bạn build cho người khác dùng, hoặc bạn muốn dùng tool của mình từ nhiều máy khác nhau: điện thoại, laptop, máy ở công ty, bạn hiểu ý rồi.
Tóm lại có local và có remote. Remote sẽ là lựa chọn được ưu tiên, nhất là khi bạn định đưa lên production.
6. Access control: ba câu hỏi cho mỗi request
Tuy nhiên, ngay khi có một MCP server remote, bạn phải cẩn thận, vì bạn vừa tạo ra một vấn đề mà bản local không bao giờ gặp. Vấn đề đó là access control. Nếu MCP server của bạn nằm trên internet, nghĩa là bất kỳ ai có URL đều gọi được tool của bạn.
Mà tool thì làm việc thật, đúng không? Chúng đọc dữ liệu, ghi dữ liệu, có khi còn xoá thứ gì đó. Vì vậy với từng request một, bạn phải trả lời được ba câu hỏi:
- Ai đang gọi?
- Họ được phép làm gì?
- Họ đang làm thay cho ai (on behalf of)?
Đó là những câu hỏi bắt buộc phải có lời đáp nếu bạn chạy một MCP server remote.
Rồi Tim kể phần làm anh thật sự bất ngờ khi tìm hiểu: các đợt kiểm tra bảo mật (security audit) năm nay cho thấy khoảng một phần tư số MCP server công khai hoàn toàn không có authentication. Còn trong số những server có một thứ gì đó, phần lớn dùng một static API key: một secret dài hạn duy nhất nằm trong file config và được dùng chung cho tất cả mọi người. Chỉ một phần rất nhỏ thực sự dùng cơ chế authentication đúng chuẩn, là OAuth.
Có thể bạn đang nghĩ: static API key thì có gì sai? Tim bảo hãy thử nghĩ xem. Nếu năm agent khác nhau cùng gọi vào server của bạn và đều dùng chung một key, bạn không thể phân biệt chúng. Bạn không biết user nào đã bảo agent nào làm gì. Và nếu một trong số đó bị compromise, bạn không thể thu hồi (revoke) riêng cái đó. Bạn phải huỷ luôn key và làm hỏng tất cả agent cùng một lúc.
Chuyện đó ổn với một project ngắn, nhưng không ổn với bất cứ thứ gì thật và đang chạy production.
7. OAuth 2.1 với PKCE: server của bạn không làm login
Bản đặc tả chính thức của MCP (MCP specification) có câu trả lời cho chuyện này, và đó cũng là thứ mọi ứng dụng nghiêm túc đã dùng từ lâu: OAuth. Cụ thể, spec dẫn chiếu tới OAuth 2.1 với PKCE (xem phần Authorization của spec). Nếu nghe đáng sợ, Tim bảo đừng lo, vì bạn sẽ không bao giờ phải tự build thứ này. Nhưng bạn nên hiểu nó là gì, vì lát nữa anh sẽ gắn nó vào chính MCP server được build trong video. Đây là phần quan trọng để giữ MCP server an toàn.
Luồng hoạt động như sau. Điều cốt lõi cần hiểu: MCP server của bạn không bao giờ tự đăng nhập vào thứ gì cả. Khi Claude, hay bất kỳ MCP client nào, gọi server mà không kèm một token hợp lệ, server chỉ trả về 401, not authorized. Và kèm theo: "à, nhân tiện, đây là URL của nơi bạn có thể tới để đăng nhập."

HTTP 401 · WWW-Authenticate: Bearer resource_metadata=".../.well-known/oauth-protected-resource".Nơi mà server trả về đó gọi là authorization server. Client sẽ tới đó. User thấy một màn hình đăng nhập bình thường và có thể đăng nhập bằng Google, bằng email, hay bằng bất cứ cách nào bạn đã thiết lập. Họ sẽ thấy một màn hình consent, có thể đồng ý một hành động và đăng nhập, y như với bất kỳ ứng dụng nào khác.
Từ đó, user bấm "allow", hay bất cứ nút nào authorization server đưa ra. Authorization server lấy một token và trao nó cho Claude, hay cho bất kỳ agent nào bạn đang dùng. Sau đó agent gửi token này kèm theo mọi request tới MCP server, và token là thứ xác định bạn là ai. Server sẽ kiểm tra chữ ký (signature) của token, chắc chắn rằng token được cấp cho đúng server này, kiểm tra trong token có những quyền (scopes) nào, rồi mới chạy tool.
8. Scopes, user ID và dynamic client registration
Tim dừng lại một chút cho phần thuật ngữ. Các quyền nằm trong token được gọi là scopes. Ví dụ bạn có thể có một scope notes read hay notes write, hoặc những loại scope khác, tuỳ bạn. Về cơ bản, các tool trong server có thể nói: "Này, tôi cần scope này, hay quyền này, thì mới làm việc đó được." Nhờ vậy bạn không chỉ authenticate được một user, mà còn kiểm soát được user đó được làm gì và kiểm tra họ có đủ quyền cần thiết hay không.
Token cũng cho bạn biết user là ai. Bên trong dữ liệu của token có các scope, có user ID, và thêm một ít thông tin khác. Điều đó có nghĩa server của bạn thực sự có thể hành động thay mặt (on behalf of) một user, nên nó scale ra được chứ không chỉ phục vụ một cá nhân.
Còn một mảnh nữa bạn cần biết: client, tức các AI agent, phải tự đăng ký (register) với authorization server trước khi được phép cho bạn đăng nhập. Spec có một cơ chế gọi là dynamic client registration cho việc này, để chúng tự làm một cách tự động. Bạn không phải thiết lập gì ở đây, chỉ cần biết là nó có.

Tim tóm lại toàn bộ luồng: client đăng ký với authorization server và nói "tôi muốn đăng nhập"; authorization server gửi một URL cho user; user đăng nhập, thứ đó cấp một token; token được đưa cho client; client gửi token tới server với mọi request. Token xác định bạn, tức user, cùng tất cả scope bạn có với MCP server. "Thật ra đây là một luồng rất hay", anh nói.
Bạn hoàn toàn có thể tự build tất cả: trang login, màn hình consent, việc ký token, việc đăng ký client, logic refresh. Hoặc bạn chỉ cần cắm vào một thứ đã làm sẵn những việc đó, và lát nữa Tim sẽ cho xem, vì có rất nhiều tool tốt ngoài kia lo hết phần cấu hình MCP này cho bạn.
Đến đây, Tim chuyển sang máy tính. Anh sẽ cho xem cách build một MCP server đơn giản và cách nối nó với nhiều client khác nhau, rồi cách thêm phần authorization để có một MCP server chạy được ở quy mô lớn cho user thật, mà không phải lo mọi chi tiết lắt léo vừa nói.
9. Đọc protocol trên modelcontextprotocol.io
Trên máy, Tim sẽ build một MCP server đơn giản bằng Python. Trước hết anh muốn mọi người hiểu nhanh việc build này vận hành thế nào. Code đã có sẵn, và bạn sẽ thấy nó rất cơ bản. Bạn có thể dùng một module như FastMCP, nhưng bạn không bắt buộc phải dùng framework. Framework chỉ làm mọi thứ dễ hơn. Tất cả những gì các framework này làm cho bạn là đảm bảo server tuân thủ MCP protocol, tức Model Context Protocol. Rốt cuộc, theo Tim, việc ta làm chỉ là build một API bình thường hay một server đơn giản như trong bất kỳ ngôn ngữ lập trình nào, với một cấu trúc request và response cố định để nó hành xử như một MCP server. Thực chất đó là toàn bộ protocol.
Bạn có thể đọc protocol trên modelcontextprotocol.io. Tim lướt nhanh vài ý high level trước khi vào code. Trang Specification (phiên bản 2026-07-28) viết rằng MCP cung cấp một cách chuẩn hoá để ứng dụng chia sẻ thông tin ngữ cảnh với language model, expose tool và khả năng cho AI system, và build các integration có thể ghép với nhau (composable). Có vài chi tiết quan trọng, chẳng hạn MCP dùng JSON (cụ thể là JSON-RPC), và có một loạt tính năng. Theo Tim, MCP thực ra đi xa hơn nhiều so với những gì phần lớn người ta cho bạn xem; muốn đọc kỹ thì cứ đọc.
Ở tab Architecture có vài sơ đồ giải thích host, client, server, các nguyên tắc thiết kế (design principles). Thứ Tim muốn cho xem nhất là sơ đồ trình tự (sequence diagram) mô tả cách một MCP server giao tiếp với các bên.

server/discover trả về phiên bản và capability được hỗ trợ), vòng Client Requests (hành động do user hoặc model khởi xướng, request, chuyển cho AI, response) và phần Subscriptions.Trên sơ đồ có client. Client có thể discover server và nhận lại thông tin về server. Sau đó user hoặc model khởi xướng một hành động, client gửi request tới server, server gửi response về, ta chuyển nó cho AI, và cứ thế qua lại. Mỗi request và response đều có một cấu trúc cố định mà server phải tuân thủ để làm việc được với bất kỳ thứ gì tương thích MCP. Tất cả được mô tả chi tiết nếu bạn đọc hết protocol, như phần message patterns: request được gửi thế nào, response được gửi thế nào, và nhiều thứ khác.
Ý chính là ta không cần tự build những thứ này. Ta chỉ cần dùng framework và tool đã xử lý sẵn. Việc ta thực sự chịu trách nhiệm là thiết lập authorization, thiết lập tool, và phần cung cấp danh tính (identity), như sẽ thấy ngay sau đây. Muốn hiểu protocol ở mức thấp hơn thì cứ vào trang này, nó mô tả mọi thứ rất chi tiết, và Tim bảo thật ra nó không phức tạp đến thế.
10. Demo 1: MCP server local với FastMCP
Giờ tới code và vài demo. Demo đầu tiên là một MCP server local, chỉ client trên chính máy này mới dùng được. Nó hữu ích khi ta tự build một thứ nhanh để vọc, nhưng tất nhiên không dùng được cho client nào khác vì nó chỉ nằm trên máy mình.
Cách dựng một MCP server như sau. Ta mang vào một module như FastMCP, cài bằng pip là được. Ví dụ trong file pyproject.toml của anh chỉ có fastmcp như vậy, rồi nó tự cài các dependency. Tiếp theo là phần code cung cấp tool. Tim có một "notes database" (file notes_db.py): vài function đơn giản kết nối tới database để thêm, xoá, liệt kê vài ghi chú, và tất nhiên là kết nối. Lúc nói, anh gọi đó là database PostgreSQL; còn docstring trên màn hình và README của repo ghi đây là một database SQLite "notes" nhỏ (repo: "Tiny SQLite store used by every version of the server").

v1_local.py: import FastMCP, import ba hàm của notes_db, tạo mcp = FastMCP("notes"), rồi các function list_my_notes, add_a_note, delete_a_note, mỗi function có decorator @mcp.tool(), docstring và type hint.Rồi tất cả những gì anh làm là viết vài function Python và gắn decorator @mcp.tool lên chúng. Khi làm vậy, FastMCP tự động thêm chúng vào schema, tức danh sách tool khả dụng, và thông báo rằng các tool này tồn tại mỗi khi một MCP client kết nối tới MCP server này. "Chỉ vậy thôi", anh nói: gắn decorator cho vài function là chạy được MCP server. Lát nữa anh sẽ cho xem cách thêm cấu hình. Và đột nhiên mọi thứ kết nối vào đều bắt đầu dùng được các tool này để tạo note, xem note, bạn hiểu ý rồi.
Nội dung file như trong repo của video (bản trên màn hình giống hệt):
from fastmcp import FastMCP
from notes_db import add_note, delete_note, list_notes
mcp = FastMCP("notes")
@mcp.tool()
def list_my_notes() -> list[dict]:
"""List all saved notes."""
return list_notes(owner="local")
@mcp.tool()
def add_a_note(text: str) -> dict:
"""Save a new note."""
return add_note(owner="local", text=text)
@mcp.tool()
def delete_a_note(note_id: int) -> str:
"""Delete a note by id."""
return "deleted" if delete_note(owner="local", note_id=note_id) else "not found"
if __name__ == "__main__":
mcp.run() # stdio by defaultTuy nhiên, Tim lưu ý: ở đây không có authentication hay authorization gì cả. Ai có quyền truy cập server này đều gọi được mọi tool và làm mọi thứ mà không bị kiểm tra gì. Trong trường hợp này thì ổn, nhưng lát nữa ta sẽ muốn sửa.
Còn nhiều thứ khác có thể làm. Ví dụ với mcp.tool, bạn có thể thêm một description để mô tả function làm gì. Mặc định, FastMCP lấy description từ docstring của function. Như trên màn hình, có docstring, và đó là thứ được dùng để nói với AI model tool này làm gì. Ngoài ra, các function còn được annotate, tức đã thêm type hint của Python. Điều này cực kỳ hữu ích vì nó cho AI client thêm ngữ cảnh khi function được mô tả cho nó. Nhờ vậy model biết: "Này, tôi nên gọi function này với một string, và tôi sẽ nhận lại một dictionary. Okay, function này gọi với một int, tôi sẽ nhận lại một string."
Lời khuyên cụ thể của Tim cho Python: khi dùng một framework như FastMCP, hãy viết docstring và annotate tham số cùng kiểu trả về của function, vì nó sẽ cho bạn câu trả lời chất lượng hơn từ AI model.
11. Gắn server local vào Cursor qua stdio
Nếu đang ở trong một thứ như Cursor, một AI client mà ta có thể thêm MCP server vào, thì việc cần làm là vào Settings, chọn mục Tools & MCP, rồi bấm "Add Custom MCP". Ở đó ta thêm một cấu hình để chạy server local. Vì ta sẽ chạy server qua standard input output, tức stdio local, nên phải đưa cho Cursor chỉ dẫn để chạy nó như một subprocess trên chính máy mình.
Cấu hình trông như sau. Có khoá mcpServers, rồi tên MCP server là notes-local. Có lệnh muốn chạy (command) là uv, vì anh muốn chạy bằng Python; nếu đây là server JavaScript, Node.js, thì lệnh sẽ là node. Sau đó là các đối số (args) truyền cho lệnh: "run", "--directory", đường dẫn thư mục project trên máy anh, rồi file muốn chạy là "v1_local.py". Tức như gõ uv run --directory <thư mục> v1_local.py. README của repo đưa đúng khối này, với một đường dẫn mẫu và tên server là notes, đặt trong ~/.cursor/mcp.json:
{
"mcpServers": {
"notes": {
"command": "uv",
"args": ["run", "--directory", "C:/Users/User/Desktop/descope-mcp-video", "v1_local.py"]
}
}
}Một lần nữa, vì chạy local nên Cursor chỉ giao tiếp với server qua terminal, qua standard input output. Không có HTTP request nào được gửi. Đây là cách chạy server thứ nhất.
Anh quay lại mục MCP để xem notes-local đã kết nối chưa: thấy ba tool được bật, gồm list notes, add a note và delete a note. Rồi anh mở một cửa sổ agent mới và hỏi: "hey what can u do in my notes serveer". Agent kéo được các tool đang kết nối ra và sẵn sàng dùng ngay: nó trả lời server notes có ba tool là list notes, add a note và delete a note.
Tiếp theo anh thử: "Create a note that says my name five times." Anh vừa gõ vừa đùa: không biết nó có biết tên mình không, có khi phải hỏi lại. Kết quả là vì lý do nào đó nó biết thật: agent gọi tool và thêm tên anh vào note.

notes-local chạy bằng lệnh uv run --directory ... v1_local.py với ba tool list_my_notes, add_a_note, delete_a_note. Bên phải, agent chạy "Add A Note in notes-local" với text "Tim Tim Tim Tim Tim", nhận về {"id":2,...} và giải thích nó chọn "Tim" dựa trên username.Rồi anh hỏi: "okay, can you list all the notes that are available", để xem có những note nào và nó liệt kê được không. "Tôi biết là rất cơ bản", anh nói, "nhưng tôi chỉ muốn cho bạn thấy cách thiết lập." Agent chạy lệnh, báo có note số hai, liệt kê các note ra; và tất nhiên anh có thể xoá note nếu muốn.
Tóm lại, việc duy nhất anh làm là nói: đây là lệnh để chạy server. Vì dùng FastMCP, mọi thứ còn lại liên quan tới protocol đã được lo hết. Giờ anh có ba tool, và có thể biến chúng thành bất cứ thứ gì, làm bất cứ việc gì, đã thêm vào Cursor và có thể thêm vào gần như mọi AI agent khác.
12. Demo 2: chuyển sang remote qua HTTP
Giờ Tim cho xem cách chuyển server sang remote để truy cập được qua một URL từ bất kỳ đâu trên internet. Trong script v1_local có dòng mcp.run(), và mặc định khi chạy, MCP server chạy với stdio, tức standard input output.
Muốn chuyển sang chạy remote, tất cả những gì cần làm là đổi đúng dòng đó để khai báo transport, host và port muốn chạy. Thay đổi này làm server chạy qua HTTP, cụ thể là streamable HTTP, cho phép gửi request thẳng tới một URL thay vì phải chạy như một subprocess local.

mcp.run(transport="http", host="0.0.0.0", port=8000).if __name__ == "__main__":
mcp.run(transport="http", host="0.0.0.0", port=8000)Trong ví dụ này anh chỉ chạy trên máy mình, nhưng nếu chạy trên một VPS hay một server nào đó, việc duy nhất phải làm là đưa URL cho người khác, và họ cũng kết nối được tới MCP server này.
Anh chạy server. Lưu ý là lần này phải tự chạy và để nó chạy liên tục, vì nó chạy qua, "gọi là gì nhỉ", HTTP. Anh gõ uv run v2_remote.py. Nó chạy giống một server FastAPI bình thường: terminal báo Uvicorn đang chạy ở một URL (http://0.0.0.0:8000).
Sang Cursor, vào mục MCP, phóng to lên một chút. Lần này khi thêm custom MCP server, chỉ cần đặt tên, anh gọi là notes, rồi với URL thì viết url, dấu hai chấm, và URL của server. Anh tìm lại trong terminal: 0.0.0.0, hay chỉ là localhost port 8000. Anh dán vào, đổi thành localhost rồi lưu. Ngay lập tức một request được gửi tới MCP server. Nhưng còn phải thêm /mcp vào cuối URL vì URL này bắt buộc có phần đó. Sau khi thêm, các request đi qua và terminal hiện "OK, OK, OK", thay vì những lỗi xuất hiện lúc trước (trên màn hình là các dòng 404 Not Found).
{
"mcpServers": {
"notes": {
"url": "http://localhost:8000/mcp"
}
}
}Tóm lại, anh chỉ nói: đây là tên server, notes, đây là URL, localhost:8000/mcp. "Nhớ thêm phần đó", anh nhắc lại. Và giờ có thể giao tiếp với server. Anh hỏi agent: "Are you connected to any tools MCP servers?", và nó hiện ra server notes. Trong Settings cũng vậy: ba tool, list, add và delete a note.
Khác biệt là lần này Cursor không chạy subprocess cho ta. Server chạy trong terminal, dù hiện có hơi lộn xộn một chút. Bất kỳ client nào truy cập được URL này đều dùng được server: anh có thể dùng nó từ Claude Code, từ ChatGPT, thêm vào bất cứ chỗ nào chấp nhận MCP server, và nó chạy được ngay vì đang chạy remote qua HTTP.
13. Server mở toang và nhà tài trợ Descope
Đến đây Tim muốn chỉ rõ một điều (anh tắt server đi): không có access control nào cả. Server giờ đang mở. Ai có URL này đều vào được. Họ có thể xoá note, thêm note, liệt kê note, và các note không gắn với từng user riêng.
Nếu thật sự muốn build tiếp và cho nhiều client dùng, ta sẽ muốn nhận diện từng client, có scope hay quyền khác nhau cho từng client, biết ai là ai, và tất nhiên chỉ cho phép một số người truy cập máy hay server của mình. Còn nếu muốn cho tất cả mọi người dùng, thì vẫn cần họ có danh tính để biết note nào của ai, và các client khác nhau có quyền kiểm soát khác nhau. Ví dụ, anh có thể đăng nhập tài khoản mình từ nhiều client khác nhau và thấy tất cả note của mình, hoặc tạo một tài khoản thứ hai. Bạn hiểu ý rồi.
Để làm việc đó, anh dùng một tool tên là Descope. Video có một đoạn quảng cáo cho Descope, nhà tài trợ của video; phần còn lại của demo dùng Descope làm authorization server. Theo Tim, sản phẩm "agentic identity hub" của họ được làm đúng cho việc này: một identity provider cho AI agent và MCP server. Nó đóng vai authorization server vừa nói ở phần lý thuyết, lo màn hình login, màn hình consent, dynamic client registration và mọi mảnh khác, để bạn không phải tự build. Nó còn có tính năng Connections, một kho (vault) lưu token Google, GitHub, Slack... của user, để agent của bạn gọi các dịch vụ đó thay mặt user mà bạn không bao giờ phải lưu một key dài hạn. Anh sẽ cho xem phần này ở cuối.
Trên trang chủ Descope, anh nói họ làm khá nhiều thứ. Một trong những thứ chính là agentic identity hub, cho phép xử lý danh tính agent giữa các client và những người đăng nhập. Bạn có thể build MCP server, build AI agent với họ, đủ loại thứ, nhưng ở đây ta chỉ quan tâm tới các thành phần MCP.
14. Tạo project và MCP server trong Descope console
Tim đăng ký tài khoản mới và vào Descope console. Từ đó có thể tạo một project mới, thường console sẽ đưa bạn vào một project ngay. Anh tạo một project mới tên tutorial. Tạm thời để môi trường là non-production, sau này có thể chuyển sang production, rồi bấm Create.
Khi project đã tạo xong (chờ vài giây), anh tìm tới mục Agentic Identity Hub. Trong đó, anh vào MCP Servers để tạo một MCP server mới. Thứ này về cơ bản sẽ cung cấp authorization server cho MCP server của ta, để user đăng nhập được bằng Google, GitHub hay gì đó và đưa danh tính đó cho MCP server.
Anh bấm dấu cộng để thêm MCP server. Tên: notes. Description có thể điền hoặc không. MCP Server URL: http://localhost:8000/mcp. Anh nhấn mạnh: khi điền, nhớ có phần /mcp, thiếu nó thì sẽ không chạy.

http://localhost:8000/mcp; ở phần "MCP Server Scopes", scope đầu tiên là notes:read với mô tả trên màn hình consent "Allow the user to read notes.", bật "Allow user to decline scope", và anh đang gõ tên scope thứ hai. Mỗi scope có thể nối với "Connection scopes" ở bên phải.Kéo xuống là phần scopes. Anh thêm scope "notes read" với mô tả "Allow the user to read notes" (cho phép user đọc note), rồi thêm một scope nữa là "notes write", cũng kèm mô tả "allow the user to modify, edit and create notes" (cho phép user sửa, chỉnh và tạo note). Trên màn hình, tên scope được gõ dạng notes:read. Đây là các scope tuỳ biến (custom scopes), bạn có thể đặt bất kỳ cái nào mình muốn. Đây cũng là thứ ta sẽ đọc lại sau để xem user thực sự có những quyền gì, và user có thể cho phép hoặc từ chối từng scope khi đăng nhập.
Tiếp theo anh bật CIMD rồi bấm Create. (Trong spec của MCP, CIMD là Client ID Metadata Documents, một cách để client tự giới thiệu với authorization server, song song với dynamic client registration.) Sau khi tạo, anh ghi lại URL ở đây: URL "well-known".
15. Code v3_auth.py: provider, scopes và user hiện tại
Anh chép URL đó vào file biến môi trường (environment variable). Code có thể tải hết từ link trong phần mô tả video. Anh tạo hai biến. Biến thứ nhất là DESCOPE_CONFIG_URL, chứa URL well-known vừa thấy, về cơ bản là URL tới authorization server của ta. Kéo xuống trong console còn có các ví dụ code: với FastMCP như anh đang dùng, với Vercel, với Skybridge, chỉ chính xác cách kết nối. Biến thứ hai là MCP_SERVER_URL, anh đặt là URL của MCP server local.
Rồi anh mở file v3_auth.py, code đã viết sẵn hết. Anh dùng Descope MCP SDK; muốn có nó trong Python thì pip install descope-mcp, hoặc cài bằng uv. Anh đã import vài thứ cần thiết, load file biến môi trường để đọc các giá trị, lấy config URL và server URL, rồi bỏ đuôi /mcp để chắc chắn có base URL, phòng khi lỡ điền thừa.

v3_auth.py trên màn hình: import từ descope_mcp (InsufficientScopeError, init_descope_mcp, require_scopes), DescopeProvider của FastMCP và get_access_token; đọc DESCOPE_CONFIG_URL và MCP_SERVER_URL; tạo auth = DescopeProvider(...) với hai scope "notes:read", "notes:write"; rồi mcp = FastMCP("notes", auth=auth).Từ đó, anh kết nối FastMCP. Anh tạo một auth provider với Descope, truyền config URL, base URL và các scope sẽ hỗ trợ. Đây là các custom scope, bạn có thể viết, sửa, điều chỉnh tuỳ ý: ở đây là notes read và notes write. Rồi tạo FastMCP, đặt tên, và auth=auth, tức cung cấp Descope auth provider. Sau đó khởi tạo Descope với URL well-known để chắc chắn nó đã được nạp và sẵn sàng. Phần này trên màn hình như sau:
CONFIG_URL = os.environ["DESCOPE_CONFIG_URL"]
SERVER_URL = os.environ.get("MCP_SERVER_URL", "http://localhost:8000/mcp")
BASE_URL = SERVER_URL.removesuffix("/mcp")
# --- 1. The bouncer: FastMCP + Descope -------------------------------------------------
auth = DescopeProvider(
config_url=CONFIG_URL,
base_url=BASE_URL,
scopes_supported=["notes:read", "notes:write"], # advertised to clients during discovery
)
mcp = FastMCP("notes", auth=auth)
# --- 2. Descope's MCP SDK: scope checks + connection (third-party) tokens ---------------
# Populates the SDK's global context so get_connection_token() knows our server/audience.
init_descope_mcp(well_known_url=CONFIG_URL, mcp_server_url=SERVER_URL)Phía dưới là đúng MCP server lúc trước, chỉ thêm một chút "dây nối" để xử lý các scope. Có một function lấy user hiện tại: nó lấy access token từ Descope, xem qua các claim để tìm sub, tức user ID, và đó là user hiện đang đăng nhập bằng Descope vào ứng dụng của ta. Rồi có một MCP tool chỉ làm đúng một việc là cho biết user hiện tại là ai (whoami), để test và cho thấy trong video.
Sau đó là các function list notes, add a note và delete a note, y hệt như trước. Khác biệt duy nhất là lần này ta nhìn vào user hiện tại và kiểm tra họ có scope được phép thực hiện thao tác đó hay không. Nhờ vậy có thể chặn từng tool, kiểm soát quyền truy cập, và kiểm soát user nào được xem note nào. Chủ (owner) của note giờ là user ID, còn note ID vẫn là note ID. Trước đây, nếu nhìn lại bản cũ, ta luôn dùng một ID local cố định (owner="local") cho note; giờ note gắn với user đã đăng nhập. Trong repo, tool thêm note trông như sau:
@mcp.tool()
def add_a_note(text: str) -> dict:
"""Save a note for the caller. Requires notes:write."""
try:
user_id, _ = current_user("notes:write")
except InsufficientScopeError as e:
return e.to_json()
return add_note(owner=user_id, text=text)16. Đăng nhập, consent và tool whoami
Để cho thấy nó chạy thế nào, anh chạy server bằng uv run v3_auth.py. MCP server khởi động. Quay lại Cursor, anh kiểm tra kết nối MCP. Anh đăng xuất trước, vì phiên đăng nhập đã được cache từ một lần chạy trước. Server notes vẫn giữ nguyên vì chạy trên cùng một URL.
Điều xảy ra là Cursor đã gửi một request tới .well-known/oauth-protected-resource/mcp. Request này trả cho Cursor URL authorization cần để đăng nhập. Và Cursor báo "needs authentication".
Anh bấm Connect. Trình duyệt mở ra một màn hình đăng nhập. Anh chọn đăng nhập bằng Google. Bạn có thể đổi cách đăng nhập: trong chế độ build của console có thể thiết lập magic link, embedded link, social login, tất cả kiểu đó, làm ngay trong console. Anh không đi sâu phần đó.

Đăng nhập xong, anh được đưa tới màn hình consent, nơi có thể cho phép hoặc không cho phép từng scope. Anh bấm Authorize. "Tất nhiên là giờ mình chưa có logo", anh nói. Quay lại Cursor, anh đã đăng nhập: có các tool whoami, list notes, add a note và delete a note, và anh đã được authenticate với server này qua đúng client này.
Anh yêu cầu agent: "Can you create a new note for me that says, "Tim is the best," and then also tell me who I am by calling the notes MCP server?" Agent giao tiếp được với server. Chờ một chút, anh thu nhỏ cửa sổ để thấy log trên server: request được gửi đi, rồi "boom, xong". Nó đã gọi MCP rất nhiều lần.

notes có thêm tool whoami. Log terminal cho thấy đúng luồng ở phần lý thuyết: POST /mcp bị 401 Unauthorized, client lấy /.well-known/oauth-protected-resource/mcp, rồi các request sau là 200 OK. Bên phải, agent báo note "Tim is the best" đã lưu (note id 3) và server nhận diện user với scope notes:read, notes:write cùng thời điểm hết hạn.Note "Tim is the best" đã có. Và kết quả của whoami cho ra user ID, client ID, rồi các scope, notes:read và notes:write, cùng thời điểm hết hạn (expiry). "Boom."
Quay lại Descope, vào mục Users, sẽ thấy user của anh với email đăng nhập bằng Google lúc nãy. Anh có thể xoá user, vô hiệu hoá họ, có toàn quyền kiểm soát ngay từ console. Theo anh, nó lo hết những việc vốn rất mệt nếu tự làm.
17. User thứ hai, Connections và lời kết
Giờ nếu đăng nhập bằng một user khác, người đó sẽ không truy cập được các note này. Anh cho xem trước: hỏi agent xem hiện mình có những note nào. Agent liệt kê note từ server: "bạn có một note: Tim is the best".
Rồi anh vào lại Cursor Settings, mục MCP, bấm Logout. Giờ đã ở trạng thái chưa xác thực (unauthenticated), và nếu thử dùng server notes thì sẽ không được. Anh bấm Connect lại, một URL mới mở ra, và đăng nhập bằng một tài khoản Google khác. Authorize, rồi quay lại Cursor.
Anh mở một chat mới để tránh lỗi và hỏi: "Using the notes MCP server, tell me what notes I have available to me." Câu trả lời: user hiện tại không có note nào dưới danh tính đã xác thực. "Đúng như mong đợi. Perfect."
Tất nhiên còn có thể đi xa hơn nhiều. Với Descope, ta còn nối được tới Connections, thứ Tim thấy cực kỳ hữu ích. Ví dụ nếu muốn có một MCP server làm việc với các nhà cung cấp khác như tài khoản GitHub, Slack hay Notion của bạn, có thể nối chúng bằng cách vào mục Connections. Ở đó anh thêm được connection: Airtable, Asana, đủ các connection họ có. Rồi nối các connection đó với các scope bên trong MCP server.
Ví dụ, thêm một connection mới và nối nó với một scope. Khi đó nếu muốn ai đó cho mình quyền truy cập GitHub của họ, anh làm được, và để Descope xử lý toàn bộ phần auth đó. Luồng sẽ là: anh đăng nhập bằng Google chẳng hạn, rồi cho phép MCP server truy cập GitHub của mình, xác thực với GitHub, và Descope tự lưu tất cả lại. Anh không làm hết demo này, nhưng nói rằng thiết lập rất dễ: nối các connection scope và tạo connection từ dashboard. Repo của video có thêm một tool mẫu my_github_repos làm đúng việc này: nó xin Descope một GitHub token mới cho đúng user đang gọi rồi liệt kê repo cập nhật gần nhất.
Tim kết thúc video ở đó. Anh hy vọng video đã giúp mọi người hiểu về MCP server, và mời ai thấy hay thì like và subscribe.
Nguồn và liên kết 9 nguồn
- Tên gốc: MCP Servers Explained & Built
- Sự kiện: YouTube
- Video gốc: https://www.youtube.com/watch?v=He8tUwLzLnU youtube.com
- Kênh YouTube Tech With Tim (Tim Ruscica): youtube.com/channel/UC4JX40jDee_tINbkjycV4Sg youtube.com
- Code trong video, gồm
v1_local.py,v2_remote.py,v3_auth.pyvà README hướng dẫn từng bước: github.com/techwithtim/descope-mcp-video github.com - MCP specification 2026-07-28, phần Message Patterns: modelcontextprotocol.io/specification/2026-07-28/basic/patterns modelcontextprotocol.io
- MCP specification, phần Architecture (host, client, server và sequence diagram): modelcontextprotocol.io/specification/2026-07-28/architecture modelcontextprotocol.io
- MCP specification, phần Authorization (OAuth 2.1, PKCE, dynamic client registration, Client ID Metadata Documents): modelcontextprotocol.io/specification/2026-07-28/basic/authorization modelcontextprotocol.io
- FastMCP, framework Python dùng trong video: gofastmcp.com gofastmcp.com