# Tự build MCP server cho AI agent

[Nicolai Nielsen](https://homus.dev/speakers/nicolai-nielsen) · [Nicolai Nielsen](https://homus.dev/orgs/nicolai-nielsen)

> Bọc Python function và backend có sẵn thành MCP server bằng FastMCP, phân quyền từng tool theo scope cho agent, demo agent đặt lịch khám.

Topics: Agents, Dev Tools, Security

Canonical: https://homus.dev/talks/how-to-build-mcp-servers-for-ai-agents

## 1. Từ travel agent tới một MCP server tự build

Nicolai mở đầu bằng việc nối với video trước: ở đó anh đã tạo một travel agent của riêng mình. Video này đi tiếp một bước: build một MCP server của chính bạn, làm mọi thứ từ đầu (from scratch).

Ý anh muốn nhấn mạnh là điểm xuất phát của bạn không quan trọng. Dù bạn đã có sẵn tool, có backend trong công ty hay trong việc kinh doanh của mình, dù bạn đang làm gì đi nữa, chỉ cần bạn có Python function, có một backend, có bất cứ thứ gì như vậy, bạn đều có thể dựng (spin up) một MCP server bao quanh nó. Rồi anh đi thẳng vào việc.

![Nicolai Nielsen nói trước camera trong phòng làm việc có kệ sách và micro](https://homus.dev/photos/RMc-um37Vxc-0003.jpg)

Nicolai Nielsen giới thiệu bài: lần này không dùng MCP server có sẵn mà tự build một cái, bọc quanh code và backend bạn đang có.

Descope là nhà tài trợ của video, đồng thời cũng là nền tảng xác thực được dùng xuyên suốt bản demo bên dưới.

## 2. Bài toán: MCP server đặt lịch khám cho phòng khám

Ví dụ xuyên suốt là một MCP server cho việc đặt lịch khám chữa bệnh (healthcare scheduling). Mục tiêu là để AI agent có thể làm ba loại việc qua server này: gọi tool, lấy thông tin, và thực hiện hành động (take actions), cụ thể là đặt lịch hẹn khám thật sự.

Anh vạch ra thứ tự sẽ làm trong video:

- Trước hết, dựng các tool.

- Sau đó bọc MCP server quanh các tool đó.

- Tiếp theo, tạo agent identity, tức danh tính riêng cho từng agent.

- Cuối cùng là cho MCP server có lớp bảo mật, để chỉ những agent cụ thể mới được truy cập những tool cụ thể.

![File mcp_server.py trong VS Code với docstring mô tả healthcare scheduling MCP server bảo mật bằng Descope](https://homus.dev/photos/RMc-um37Vxc-0023.jpg)

Đầu file `mcp_server.py` của thư mục video-2-healthcare-mcp: docstring mô tả một healthcare scheduling MCP server dùng FastMCP, bảo mật bằng Descope. Danh sách tool gồm book, reschedule, list appointment (ghi vào `clinic.db`), `get_patient_record` (tool nhạy cảm, khoá sau scope `records.read`) và `get_calendar_events` đọc Google Calendar thật qua một Descope Outbound App. Lệnh chạy là `python mcp_server.py`, server nằm ở `localhost:3000/mcp`.

Kéo xuống thêm một chút, anh chỉ vào cây thư mục bên trái: video số hai nằm ở đó. Trong thư mục đó có MCP server, và có phần xác thực bằng Descope giống như những gì đã đi qua ở video trước.

## 3. Đối xử với AI agent như một đội người thật

Toàn bộ phần xác thực (authentication) có thể để Descope tự động lo. Nhờ vậy bạn kiểm soát được hai thứ: agent đó là ai, tức nó mang identity nào; và nó có quyền gì, nghĩa là được dùng những tool nào.

Nicolai nói thẳng: ngoài kia có vô số agent đang có quyền truy cập quá nhiều thông tin, quá nhiều dữ liệu, và có thể làm quá nhiều hành động. Nếu bạn cứ để AI agent "chạy hoang" (run wild), mọi thứ sẽ kết thúc ở một trạng thái tồi tệ.

Vì thế, điều rất quan trọng là xử lý AI agent đúng y như cách ta xử lý các đội làm việc gồm người thật. Nếu bạn làm trong một hệ thống doanh nghiệp lớn, bạn đâu có quyền làm mọi thứ trong đó. Hãy nghĩ tới AWS, Azure: ở đó có đủ loại phân quyền theo vai trò (role-based access). Đó chính là thứ ta có thể đưa thẳng vào MCP server cho việc agent gọi tool. Như vậy, ta đảm bảo có lớp bảo mật cho AI agent, quy định rõ chúng được truy cập gì, và việc này được xử lý một cách đơn giản.

Anh nói thêm một điểm về dữ liệu: không có dữ liệu nào của bạn đi sang Descope. Descope chỉ lo phần xác thực bằng token được gửi qua gửi lại, và kiểm tra xem agent đó có quyền với tool đó hay không.

Mô hình Nicolai mô tả: agent gọi tool kèm token, MCP server nhờ Descope xác thực và kiểm quyền, còn dữ liệu thật vẫn nằm trong backend của bạn.

Anh cũng hé lộ kế hoạch của loạt video: ở video tiếp theo, anh sẽ kết hợp tất cả những gì đã làm trong mấy video của playlist nhỏ này lại với nhau. Và đó là tất cả những gì cần cho phần giới thiệu.

## 4. FastMCP: bọc backend có sẵn thành MCP server

Để làm MCP server, Nicolai dùng [FastMCP](https://github.com/PrefectHQ/fastmcp). Bất kỳ Python function nào cũng bọc được. Bạn cũng có thể dùng, ví dụ, FastAPI. Nếu bạn đã có backend server, backend system, tất cả API đang chạy, các Python function, thì toàn bộ những thứ đó đều có thể được bọc bởi một MCP server và các tool.

Khi đó chúng được expose ra dưới dạng một MCP server, và cả hệ thống backend của bạn như "sống dậy": AI agent có thể truy cập vào đó. Nói cách khác, nếu bạn đã có một hệ thống phần mềm đang chạy, bạn chỉ cần dựng FastMCP lên trên nó.

Nhưng anh nhắc lại: ta phải đảm bảo một agent không có quyền vào toàn bộ hệ thống backend. Ta cần agent identity và phân quyền theo vai trò (role-based permissions).

Ý tưởng chung: không viết lại backend, chỉ thêm một lớp MCP mỏng bên trên, kèm phân quyền theo từng agent.

Bước đầu tiên vì vậy là dựng phần xác thực bằng Descope provider, với hai giá trị: base URL và config URL.

## 5. Biến môi trường và Agentic Identity Hub

Nicolai mở file `.env.example`. Trong đó cần Descope project ID; anh lưu ý bạn có thể có nhiều project, nhiều agent, nhiều bộ quyền khác nhau. Tiếp theo là Descope management key, Descope base URL và cả config URL. Mọi giá trị này đều tra được trên website và trong API documentation của Descope, nơi ghi chi tiết lấy chúng ở đâu.

Cuối file là server URL và MCP URL.

![File .env.example với các biến DESCOPE_PROJECT_ID, DESCOPE_MANAGEMENT_KEY, DESCOPE_BASE_URL, SERVER_URL, MCP_URL và ANTHROPIC](https://homus.dev/photos/RMc-um37Vxc-0303.jpg)

File `.env.example`: project ID và management key lấy trong phần cài đặt của Descope (management key là bắt buộc vì tool Calendar dùng nó để lấy Google token), config URL lấy trong Agentic Identity Hub, mục MCP Servers. Bên dưới là URL công khai của server (phải khớp với thứ đã đăng ký trên Descope), endpoint MCP mà agent kết nối, và phần Claude với API key và model `claude-haiku-4-5`.

Các dòng có thể dùng lại, chép đúng như trên màn hình (các giá trị đều là chỗ trống để bạn điền):

```
# Project ID: https://app.descope.com → Project Settings
DESCOPE_PROJECT_ID=P2xxxxxxxxxxxxxxxx
# Management Key: Company Settings → Management Keys
# Required - the Calendar tool uses it to fetch the Google token from Descope.
DESCOPE_MANAGEMENT_KEY=K2xxxxxxxxxxxxxxxx
DESCOPE_BASE_URL=https://api.descope.com

DESCOPE_CONFIG_URL=

# Public URL of the MCP server (must match what you registered in Descope)
SERVER_URL=http://localhost:3000
# MCP endpoint the agent connects to
MCP_URL=http://localhost:3000/mcp

ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxx
ANTHROPIC_MODEL=claude-haiku-4-5
```

Sau đó anh chuyển sang trang Descope, vào [Agentic Identity Hub](https://docs.descope.com/agentic-identity-hub) như đã đi qua ở video trước. Tại đây bạn có thể có nhiều agent khác nhau, mỗi agent một bộ quyền. Một trong số đó là agent "Clinic Scheduling". Bạn xem được audit trail: mọi thứ đều được ghi lại (logged) ở đây, kể cả agent đó đang có những quyền gì.

![Trang Agentic Identity trong Descope Console liệt kê ba agent: Clinic Scheduling, OpenAI Agent Demo và MCP Inspector](https://homus.dev/photos/RMc-um37Vxc-0323.jpg)

Trang Agentic Identity trong Descope Console: danh sách agent gồm Clinic Scheduling, một agent demo cho OpenAI và MCP Inspector, cả ba thuộc loại User-delegated, kèm cột Agent ID, Client ID, người dùng gắn với agent và địa chỉ IP. Từ đây có thể lọc, chọn và thu hồi quyền.

Agent sẽ dùng trong demo có bốn quyền: đọc calendar, ghi appointment (appointments write), đọc appointment (appointments read) và đọc hồ sơ (records read).

## 6. Khai báo scope cho MCP server trên Descope

Kéo xuống phần MCP server, Nicolai có sẵn một server tên "demo-mcp" mà anh đã tạo. Bạn chỉ việc định nghĩa mọi thứ ở đây, và sẽ nhận được tất cả những gì cần dùng.

Rồi tới phần MCP server scopes. MCP server mà agent này sẽ truy cập có những scope và quyền này. Ví dụ, nó phải đọc được calendar. Mỗi scope còn có một mô tả để hiển thị trên màn hình xin đồng ý (consent screen) cho từng người dùng.

![Trang Edit demo-mcp MCP Server, phần MCP Server Scopes với calendar.read, appointments.write, appointments.read và records.read](https://homus.dev/photos/RMc-um37Vxc-0403.jpg)

Phần MCP Server Scopes của demo-mcp: `calendar.read` với mô tả "Check your Google Calendar for scheduling conflicts", `appointments.write` ("Book and reschedule clinic appointments on your behalf"), `appointments.read` ("View your scheduled clinic appointments") và `records.read`. Mỗi scope có công tắc "Mark as mandatory scope" và ô chọn Connection scopes bên phải.

Khi người dùng đăng nhập bằng email, toàn bộ phần xác thực được xử lý luôn. Bạn chỉ cần đăng nhập bằng email; không phải vào từng nơi tạo credential hay app riêng cho mỗi tool bên ngoài mà bạn muốn dùng. Các connection scope ở đây là: calendar read, appointments write, appointments read và records read.

Đó là tất cả những gì cần. Tới đây coi như bạn đã dựng xong toàn bộ phần quyền của MCP server. Trang này còn có thông tin kết nối (connection information): bạn chỉ việc lấy và đưa vào code. Bên dưới có vài ví dụ sử dụng (usage samples), và tab FastMCP chính là thứ anh đang dùng.

Đoạn mẫu cho FastMCP giải thích rằng DescopeProvider tự động dò ra các endpoint của Descope và cấu hình việc kiểm tra JWT token. Với mỗi lần gọi, nó sẽ trao đổi JWT token và kiểm tra xem người dùng hay AI agent này có quyền với tool nó đang cố gọi không. Trang [Descope trong tài liệu FastMCP](https://gofastmcp.com/integrations/descope) mô tả cùng cách tích hợp này: Descope lo đăng nhập, còn FastMCP server kiểm tra token.

## 7. DescopeProvider và hàm kiểm tra scope

Quay lại code. Trong MCP server, anh đặt config URL cho Descope provider. Theo Nicolai, với bất kỳ framework hay SDK nào làm MCP server ngoài kia, bạn đều chỉ cần khai báo nó xử lý xác thực thế nào. Ở đây toàn bộ phần xác thực được giao cho Descope, và chỉ cần vậy là đủ.

Sau đó tạo một instance FastMCP và đặt tên cho nó: "Clinic Scheduling MCP Server". Anh nhắc rằng thứ được bọc ở đây hoàn toàn có thể là cả hệ thống phần mềm, cả backend của bạn. Nếu bạn đã là một công ty phần mềm, có lẽ bạn đã thấy vô số video, vô số thông báo các công ty expose MCP server của riêng họ. Về cơ bản, đó chỉ là một lớp wrapper quanh các API endpoint đang có.

Giờ đã có xác thực, đã có MCP server. Việc còn lại là cấu hình các tool, và viết một hàm kiểm tra xem scope cần thiết có thật sự nằm trong token hay không. Docstring của hàm ghi rõ ý đồ: từ chối lời gọi trừ khi JWT đã được kiểm tra có mang scope đó. Hàm lấy access token, đọc danh sách scope, rồi kiểm tra scope yêu cầu có nằm trong đó không, nghĩa là agent có thật sự có quyền với tool cụ thể này không. Nếu không, nó raise một permission error.

![Code mcp_server.py: DescopeProvider, FastMCP, hàm _require_scope và tool book_appointment](https://homus.dev/photos/RMc-um37Vxc-0603.jpg)

Phần lõi của `mcp_server.py`: đọc `DESCOPE_CONFIG_URL` và `SERVER_URL`, tạo `DescopeProvider`, truyền nó vào `FastMCP` qua tham số `auth`, rồi định nghĩa `_require_scope` và tool đầu tiên `book_appointment`.

```
CONFIG_URL = os.environ["DESCOPE_CONFIG_URL"]
SERVER_URL = os.getenv("SERVER_URL", "http://localhost:3000")

auth = DescopeProvider(config_url=CONFIG_URL, base_url=SERVER_URL)
mcp = FastMCP(name="Clinic Scheduling MCP Server", auth=auth)

def _require_scope(scope: str) -> None:
"""Deny the call unless the validated JWT carries `scope`."""
token = get_access_token()
scopes = token.scopes or token.claims.get("permissions", [])
if scope not in scopes:
raise PermissionError(f"DENIED by Descope: missing scope: {scope}")
```

Thông báo lỗi là "DENIED by Descope: missing scope" kèm tên scope bị thiếu. Từ đó bạn có thể thêm scope cho agent, hoặc xử lý theo bất cứ cách nào bạn muốn. Ý nghĩa đơn giản: agent này không có quyền với tool này.

## 8. Bọc từng tool, mỗi tool một scope

Tiếp theo là xem cách bọc một MCP server tool quanh một hàm, để AI agent truy cập được nó từ phía client.

Tool đầu tiên là đặt lịch hẹn (book appointment). Nó cần patient ID, lý do khám và thời điểm hẹn. Phần việc thật được gọi sang một Python script riêng là `tools.py`; anh nói đây đơn giản là cách tổ chức code tốt.

```
@mcp.tool
async def book_appointment(patient_id: str, reason: str, scheduled_for: str) -> str:
"""Book a clinic appointment. Scope: appointments.write."""
_require_scope("appointments.write")
return tools.book_appointment(patient_id, reason, scheduled_for)
```

Trình tự rất rõ: đầu tiên kiểm tra scope bắt buộc. Khi bất kỳ ai gọi MCP server tool này, ta kiểm tra họ có quyền `appointments.write` không. Nếu không, raise lỗi. Nếu có, ta mới thật sự gọi hàm và trả kết quả về.

Làm y như vậy cho mọi tool. Anh nhấn mạnh bạn có thể làm điều này cho toàn bộ hệ thống backend của mình; ở đây chỉ là vài tool, nhưng ý tưởng hoàn toàn giống nhau. Bộ tool trong demo gồm: đặt lịch hẹn, đổi lịch hẹn (reschedule), liệt kê lịch hẹn, và lấy hồ sơ bệnh nhân (patient record).

Mỗi tool gọi `_require_scope` với đúng scope của nó trước khi chạy, theo code và docstring trên màn hình.

## 9. Outbound App: cho agent đọc Google Calendar mà không cầm credential

Tool cuối là lấy sự kiện trên calendar (get calendar events). Nó yêu cầu scope `calendar.read`. Hàm lấy access token, lấy user ID, rồi lấy Google token. Để làm được việc đó, trước hết nó phải được nối với một Descope Outbound App.

Nicolai quay lại Descope để cho xem. Có cả một thư viện Outbound App mà bạn kết nối được, chỉ cần dựng lên là xong. Họ có sẵn rất nhiều: Bitbucket, Canva, GitHub, Gmail, Google Drive, Google Meet, Google Sheets, đủ loại app và kết nối bên ngoài. Nền tảng sẽ dựng phần xác thực cho bạn. Bạn đăng nhập bằng đúng email mà bạn muốn AI agent được truy cập.

![Outbound App Library của Descope với các mẫu Google, Google Analytics, Google Calendar, Google Docs, Google Drive, HubSpot, Instagram, Jira](https://homus.dev/photos/RMc-um37Vxc-0743.jpg)

Outbound App Library trong Descope: các mẫu kết nối có sẵn như Google, Google Analytics, Google Calendar, Google Docs, Google Drive, Google Meet, Google Sheets, HubSpot, Instagram, Intercom, Jenkins, Jira, lọc được theo nhóm Collaboration, Communication, Sales, Marketing, Productivity, Data, Social, Analytics.

Anh nêu một điểm rất quan trọng ở đây: có lẽ bạn chỉ nên cho quyền đọc email, chứ không cho quyền viết email. Như vậy sẽ không có một agent nào "đi quá đà" và trả lời toàn bộ email của bạn bằng đủ thứ nội dung.

Quay lại hàm get calendar events, đó chính là những gì nó làm. Nếu agent có quyền, trước tiên nó dựng phần xác thực và kết nối, rồi lấy (fetch) auth token. Token được lưu lại để dùng sau, nên việc này chỉ phải làm một lần. Docstring trong code cũng ghi: Google token được lấy từ Descope Outbound Apps cho đúng người sở hữu JWT của request, nên MCP server không bao giờ nhìn thấy Google credential.

![Code tool get_calendar_events với _require_scope calendar.read, fetch_google_token và xử lý AuthError](https://homus.dev/photos/RMc-um37Vxc-0803.jpg)

Tool `get_calendar_events`: kiểm scope `calendar.read`, lấy `sub` trong claims của access token làm user ID, gọi `fetch_google_token(user_id)`, và nếu chưa có kết nối thì trả lời rằng người dùng cần nối Google Calendar qua Descope Outbound App trước.

```
_require_scope("calendar.read")
user_id = get_access_token().claims.get("sub")
try:
google_token = fetch_google_token(user_id)
except AuthError as e:
return (
f"No Google Calendar connected for this user ({e}). "
"Connect it via the Descope Outbound App first."
)
return tools.get_calendar_events(google_token, max_results)
```

Nicolai cho rằng điểm này còn quan trọng hơn nữa nếu bạn build hệ thống cho người khác, cho các đồng đội, hoặc làm agency: mỗi agent phải truy cập đúng calendar, đúng hộp thư của người tương ứng, và ta không muốn chúng bị trộn lẫn với nhau.

"Bạn có thể thấy", anh nói, đây là những việc nhỏ nhưng thật sự cần thiết khi build hệ thống AI sẵn sàng cho production (production-ready). Nó cực kỳ quan trọng.

## 10. Chạy server trên cổng 3000 và file tools.py

Bước tiếp theo là dựng MCP server thành một HTTP app, với đường dẫn tới MCP server. Đường dẫn này chính là thứ AI agent sẽ gọi. Sau đó chỉ việc chạy nó trên một cổng: trên cổng 3000 sẽ có `/mcp`.

```
app = mcp.http_app(path="/mcp")

if __name__ == "__main__":
import uvicorn

# Must match the port in SERVER_URL (what you registered in Descope).
port = int(os.getenv("PORT", "3000"))
uvicorn.run(app, host="localhost", port=port)
```

Anh so sánh: việc này giống hệt như khi bạn có một API, chỉ khác là ở đây là MCP mà AI agent gọi như tool. Về bản chất, nó chỉ đang gọi các API của bạn mà thôi. Đó là phần server. Còn cần một client nữa, nhưng trước hết anh mở `tools.py` ra xem.

Trong `tools.py` có danh sách bệnh nhân mẫu (seed patients): mỗi người có ID, tên và ghi chú lý do khám, đó là gần như tất cả những gì cần. Sau đó là hàm tạo kết nối database. Hàm này tạo bảng nếu bảng chưa tồn tại, rồi nạp dữ liệu bệnh nhân mẫu ở trên vào.

![Hàm _db trong tools.py tạo bảng appointments và patient_records bằng SQLite rồi nạp bệnh nhân mẫu](https://homus.dev/photos/RMc-um37Vxc-0923.jpg)

Hàm `_db()` trong `tools.py`: kết nối SQLite, tạo bảng `appointments` (id tự tăng, patient_id, reason, scheduled_for, created_at) và bảng `patient_records` (patient_id, name, notes) nếu chưa có, rồi chèn danh sách bệnh nhân mẫu khi bảng còn trống.

Phần còn lại là các tool:

- **Book appointment** chỉ chạy vài câu lệnh SQL vào database: thêm vào bảng appointments patient ID, lý do, thời điểm hẹn và thời điểm tạo.

- **Reschedule appointment** cập nhật dòng tương ứng trong bảng appointments.

- **List appointments** select tất cả ID, tất cả các cột trong bảng appointments, sắp theo ID.

- **Get calendar** lo phần Google: gửi request kèm Google access token lấy được từ Descope.

Nicolai nhấn mạnh: bạn có thể làm bất cứ thứ gì bên trong các Python function này, rồi bọc MCP server tool quanh chúng. Build một MCP server là như vậy. Thế là đủ cho phía server.

## 11. Phía client: OAuth và token lưu trên đĩa

Giờ tới client, rồi chạy chương trình. Đầu file client, anh khai báo model, URL của MCP server, và danh sách scope. Client sẽ duyệt qua các scope và kiểm tra chúng trước khi agent thực thi các tool.

```
MODEL = os.getenv("ANTHROPIC_MODEL", "claude-haiku-4-5")
MCP_URL = os.getenv("MCP_URL", "http://localhost:3000/mcp")
SCOPES = "appointments.write appointments.read records.read calendar.read"
CALLBACK_PORT = 8765
CALLBACK_URL = f"http://localhost:{CALLBACK_PORT}/callback"
TOKEN_FILE = Path(__file__).parent / ".mcp_tokens.json"
```

Có một redirect handler: nếu chưa có quyền, nó mở một trình duyệt web để bạn đăng nhập qua Descope, và mọi thứ sẽ được lưu lại và xử lý giúp bạn. Client cũng chạy một callback để nhận kết quả trả về.

Ở trên còn có lớp `FileTokenStorage`. Theo docstring của nó, lớp này lưu (persist) OAuth token và thông tin DCR client xuống đĩa, nên bước đồng ý trên trình duyệt chỉ xảy ra một lần. Các lần chạy sau dùng lại token và làm mới (refresh) âm thầm ở chế độ nền. Phần còn lại, kể cả token dài hạn, Descope lo.

Sau đó là dựng OAuth client provider: truyền vào MCP server, client metadata gồm tên client, các redirect URL dùng sau khi xác thực và cấp quyền xong, và refresh token. Anh gọi đây là phần dựng cơ bản (basic setup).

Theo code client: lần chạy đầu mở trình duyệt để người dùng đồng ý, token được lưu xuống file; những lần sau đi thẳng tới MCP server bằng token đã lưu.

Trong hàm main, anh tạo Anthropic client. Rồi tới streamable HTTP client: nó stream kết quả ra trong lúc đang chạy, trong lúc agent thực hiện nhiều lời gọi tool liên tiếp. Anh ví nó với cảnh bạn thấy chữ cứ thế hiện ra dần khi dùng OpenAI chẳng hạn. Bên trong, client dựng phần xác thực, dựng OAuth, và phần này do Descope lo.

## 12. Tool runner của Anthropic: agent tự chọn tool

Bên trong client có một client session. Việc đầu tiên là initialize session, rồi liệt kê tất cả tool có sẵn cho session này, sắp xếp chúng và in ra danh sách MCP server tool.

Sau đó là tool runner của Anthropic. Nó nhận model, số token output tối đa, và system instruction: "You are a clinic scheduling assistant. Use the MCP tools to help the user. Be brief." Tiếp theo là danh sách tool mà agent được dùng. Đây chính là lời gọi API gửi tới Anthropic.

![Code agent_mcp_client.py: ClientSession, list_tools và client.beta.messages.tool_runner với system prompt và message của user](https://homus.dev/photos/RMc-um37Vxc-1203.jpg)

Hàm `main()` của client: mở `ClientSession`, gọi `initialize` và `list_tools`, rồi tạo `tool_runner` với model, `max_tokens=16000`, system prompt, các MCP tool được bọc bằng `async_mcp_tool`, và câu yêu cầu của người dùng.

```
runner = client.beta.messages.tool_runner(
model=MODEL,
max_tokens=16000,
system=(
"You are a clinic scheduling assistant. Use the MCP tools "
"to help the user. Be brief."
),
tools=[async_mcp_tool(t, session) for t in listing.tools],
messages=[{
"role": "user",
"content": (
"Pull the record for patient PT-1002, book them a "
"diabetes follow-up on Tuesday 2026-09-01 at 10:00, "
"and then list all scheduled appointments."
),
}],
)
```

Nicolai lưu ý bạn có thể dùng bất kỳ agentic SDK nào, bất kỳ agent SDK nào, bất kỳ LLM provider nào, miễn là nó làm được tool calling. Rồi ta thêm message vào, đó chính là input prompt bạn đưa cho agent.

Từ đó agent sẽ tự suy luận: mình cần những tool nào để giải bài toán này? Nó có thể gọi nhiều tool, dùng output của tool này làm input cho tool khác, rồi trả kết quả ở cuối. Ở giữa quá trình đó, nó có thể làm hành động thật: đặt lịch hẹn, đọc đủ loại thông tin, đúng với những tool đã dựng.

Cuối cùng, client duyệt qua mọi message nhận về từ runner, kiểm tra có tool call nào, in ra nếu agent đang gọi tool và output của lời gọi đó. Sau đó lấy phần text mà AI agent viết ra và in ở cuối. Đó là tất cả những gì cần cho client.

## 13. Demo: đăng nhập một lần, agent gọi ba tool

Giờ chạy thử. Nicolai vào thư mục của video, xoá màn hình terminal, và trước hết chạy MCP server bằng `mcp_server.py`. Log hiện ra cho thấy process đã khởi động và streamable HTTP session manager đã chạy.

Anh mở một terminal mới. Với MCP server đang chạy, giờ có thể chạy client: cd vào thư mục video 2, rồi chạy agent MCP client. Lần đầu anh gõ nhầm "pyton" nên shell báo không tìm thấy lệnh; sửa lại thành python là chạy.

```
cd video-2-healthcare-mcp
python agent_mcp_client.py
```

Client mở trang Descope trên trình duyệt. Đây là nơi người dùng đồng ý (consent) cho mọi inbound app và kết nối đang dựng. Trang đăng nhập có ô email, nút Continue, và hai lựa chọn đăng nhập bằng Google hoặc Microsoft. Anh chọn đăng nhập bằng Google, chọn tài khoản, rồi cấp quyền cho nó kiểm tra Google Calendar, đặt và đổi lịch hẹn phòng khám thay cho mình. Đó chính là những gì đã khai báo trên nền tảng ở các bước trước. Trang báo đã cấp quyền và bạn có thể đóng tab.

Quay lại code, anh thấy AI agent đã chạy xong. Log ghi đang mở trình duyệt để xin đồng ý trên Descope, rồi liệt kê các tool có sẵn: book appointment, get calendar events và các tool còn lại. Agent chạy ba lời gọi tool:

- `get_patient_record` với patient ID PT-1002.

- `book_appointment` với lý do "diabetes follow-up" và thời điểm đã yêu cầu.

- `list_appointments` để liệt kê các lịch hẹn.

![Terminal hiện ba tool call get_patient_record, book_appointment, list_appointments và bản tóm tắt của agent](https://homus.dev/photos/RMc-um37Vxc-1443.jpg)

Kết quả lần chạy đầu: danh sách MCP tool, ba lời gọi tool theo đúng thứ tự, rồi phần "=== Agent ===" với tóm tắt hồ sơ bệnh nhân PT-1002 (Type 2 diabetes, Metformin 500mg), lịch hẹn #1 "Diabetes follow-up" lúc 10:00 sáng ngày 1/9/2026, và danh sách mọi lịch hẹn.

Cuối cùng là bản tóm tắt của agent: hồ sơ bệnh nhân mẫu (tiểu đường type 2, dùng metformin 500 mg), lịch hẹn đã đặt, và tất cả lịch hẹn đang có. Anh mở database của phòng khám: lịch hẹn diabetes follow-up nằm đó, đúng thời điểm hẹn và thời điểm tạo. Bảng patient records chứa đủ các bệnh nhân, và bảng appointments có đúng lịch hẹn vừa đặt. "Khá là hay", anh nói.

## 14. Lần chạy thứ hai: không cần đăng nhập lại

Anh chạy thêm một lần nữa với một patient ID khác. Theo Nicolai, công cụ này nối được với bất cứ thứ gì: chỗ prompt cứng trong code có thể thay bằng giao diện chat nơi người dùng nói chuyện với AI agent. Lần này anh đổi ID và nói sẽ thay "diabetes" bằng một lần khám sức khoẻ (health checkup); trong prompt trên màn hình, lý do mới được gõ là "health follow-up". Rồi anh chạy lại.

Lần này không cần dựng lại xác thực: việc đó đã xong ở lần đầu và được lưu lại. Kết quả hiện ra ngay. Lịch hẹn mới ghi là "health follow-up".

![Bảng appointments trong clinic.db có hai dòng PT-1002 diabetes follow-up và PT-1001 health follow-up](https://homus.dev/photos/RMc-um37Vxc-1603.jpg)

Sau lần chạy thứ hai, bảng `appointments` trong `clinic.db` có hai dòng: PT-1002 với "diabetes follow-up" và PT-1001 với "health follow-up". Terminal bên dưới là bản tóm tắt mới của agent cho bệnh nhân PT-1001 (asthma follow-up, Salbutamol inhaler) và danh sách hai lịch hẹn.

Anh mở lại database và thấy lịch hẹn mới đã được thêm vào. Đó là cách đưa AI agent vào việc: dựng một MCP server bao quanh backend, quanh các Python function của bạn, đưa MCP server đó cho agent, và đảm bảo chúng có phân quyền theo vai trò.

## 15. Giá trị nằm ở agent hành động được trong hệ thống thật

Mọi quyền đều được khai báo rõ, nên agent không có quyền vào quá nhiều thứ. Theo Nicolai, đây là nơi chứa toàn bộ giá trị: build những hệ thống AI sẵn sàng cho production, có thể làm hành động thật. Không phải chỉ là những giao diện chat với LLM để kéo thông tin về hay trả lời câu hỏi; đó không phải chỗ có giá trị.

Giá trị xuất hiện khi ta có agent tự hành (autonomous agents) và cần đưa chúng vào hệ thống doanh nghiệp, build hệ thống phần mềm thật quanh chúng. Và gần như bất kỳ hệ thống phần mềm nào cũng có thể chuyển thành một MCP server để AI agent truy cập.

Kết luận của video: giá trị nằm ở agent làm việc thật trong hệ thống phần mềm, với quyền được giới hạn rõ ràng.

Anh khuyên mọi người thử, và hẹn video tiếp theo: kết hợp mọi thứ lại để build một hệ thống multi-agent, nơi các agent tương tác và làm việc cùng nhau. Mọi thứ được để ở phần mô tả video. Anh chào bằng câu quen thuộc: "Until then, happy building."

## Nguồn và liên kết

- Tên gốc: How To Build MCP Servers For AI Agents
- Sự kiện: [YouTube](https://homus.dev/events/youtube)
- Video gốc: [https://www.youtube.com/watch?v=RMc-um37Vxc](https://www.youtube.com/watch?v=RMc-um37Vxc)

- Kênh YouTube Nicolai Nielsen (Nicolai Nielsen): [youtube.com/channel/UCpABUkWm8xMt5XmGcFb3EFg](https://www.youtube.com/channel/UCpABUkWm8xMt5XmGcFb3EFg)

- FastMCP, thư viện Python để build MCP server và client: [github.com/PrefectHQ/fastmcp](https://github.com/PrefectHQ/fastmcp)

- Hướng dẫn bảo mật FastMCP server bằng Descope (DescopeProvider): [gofastmcp.com/integrations/descope](https://gofastmcp.com/integrations/descope)

- Tài liệu Agentic Identity Hub của Descope: [docs.descope.com/agentic-identity-hub](https://docs.descope.com/agentic-identity-hub)

- Model Context Protocol, trang chính thức: [modelcontextprotocol.io](https://modelcontextprotocol.io)

- MCP Python SDK (gói `mcp` mà client dùng cho ClientSession, OAuth và streamable HTTP): [github.com/modelcontextprotocol/python-sdk](https://github.com/modelcontextprotocol/python-sdk)

Nguồn: https://homus.dev/talks/how-to-build-mcp-servers-for-ai-agents · cập nhật 2026-10-15
