# Phần khó của MCP là auth: bảo vệ MCP server multi-tenant bằng OAuth

[Hamed Bahram](https://homus.dev/speakers/hamed-bahram) · [Hamed Bahram](https://homus.dev/orgs/hamed-bahram)

> Thêm MCP server vào một app Next.js multi-tenant, dùng Clerk OAuth (CIMD, PKCE, discovery routes) để mỗi người dùng chỉ chạm dữ liệu của mình qua Claude.

Topics: Security, Dev Tools

Canonical: https://homus.dev/talks/the-hard-part-of-mcp-is-auth-heres-how-to-solve-it

## 1. Ứng dụng sẽ nối với Claude và ChatGPT: một task tracker multi-tenant

Hamed Bahram vào việc ngay. Trước hết anh cho xem ứng dụng sẽ làm việc cùng trong video: một ứng dụng task tracker anh đã build sẵn từ trước. Trong đó bạn tạo task, ví dụ một task tên "Record the MCP video". Theo Hamed (video đăng ngày 29/10/2026), đây về cơ bản là một task tracker kiểu Notion: bạn kéo task qua các giai đoạn khác nhau, và tạo được project mới.

Theo anh (video đăng ngày 29/10/2026), việc tạo thêm project là tính năng của gói trả phí, dùng phần tích hợp Clerk Billing. Ứng dụng còn có tình huống multi-tenant nhờ Clerk Organizations: bạn tạo được nhiều team space khác nhau, và mỗi team hay mỗi organization có những project riêng.

![Bảng Task Tracker với ba cột Planned, In Progress, Done](https://homus.dev/photos/j4EMfNyCYR4-0003.jpg)

Ứng dụng Task Tracker lúc mở đầu (demo trong video đăng ngày 29/10/2026): organization demo "HB Media Inc.", project "My Project", cột Planned có hai task mẫu "Build an MCP server in NextJs" và "Secure your MCP server with Clerk", cột In Progress và Done còn trống.

## 2. Mục tiêu, và vì sao authentication là phần khó

Hamed nói hôm nay anh không giải thích lại ứng dụng hoạt động thế nào. Ý tưởng của video là cho phép người dùng authenticate qua ChatGPT, rồi nói chuyện với ứng dụng: tạo task, chuyển task qua lại, tạo project, đại loại vậy. Đó là việc cả video sẽ làm.

![Hamed Bahram nói trước camera, ngồi sau micro](https://homus.dev/photos/j4EMfNyCYR4-0143.jpg)

Hamed Bahram nói trước camera về lý do authentication là phần khó khi đưa một ứng dụng ra qua MCP (video đăng ngày 29/10/2026).

Theo Hamed (video đăng ngày 29/10/2026), phần quan trọng, cũng là chỗ Clerk xuất hiện, chính là authentication. Bạn có thể expose API và MCP, chắc chắn rồi, nhưng làm sao gắn chúng với authentication? Anh nói bạn cần một application, hay một authorization server, đứng ở giữa, trước giao thức hoặc endpoint MCP đó.

## 3. Bật OAuth applications cho MCP client trong Clerk

Anh quay lại bản ghi chú của mình. Theo Hamed (video đăng ngày 29/10/2026), việc đầu tiên cần làm là bật OAuth applications cho MCP client trong Clerk. Trong Dashboard, bạn vào mục Developers rồi OAuth Applications.

Anh giải thích (video đăng ngày 29/10/2026): khi bạn vào đây lần đầu, sẽ chưa có application nào. Màn hình của anh đã có một cái chỉ vì anh đã thử trước và tạo sẵn. Bình thường tab Scopes sẽ trống, và trong Settings mọi thứ đều đang tắt.

![Trang OAuth applications trong Clerk Dashboard với một client tên Claude](https://homus.dev/photos/j4EMfNyCYR4-0123.jpg)

Trang OAuth applications trong Clerk Dashboard (Configure, Developers) có ba tab Applications, Scopes, Settings (demo trong video đăng ngày 29/10/2026). Danh sách có một client tên "Claude", client type CIMD, scopes email, offline_access, profile, do anh đã kết nối thử từ trước.

## 4. CIMD hay dynamic client registration kiểu cũ

Theo Hamed (video đăng ngày 29/10/2026), trong Settings bạn phải bật "Publish CIMD support", tức công bố hỗ trợ client ID metadata. Anh giải thích có hai cách để việc đăng ký client, hay đăng ký OAuth client, diễn ra.

Cách thứ nhất là Client ID Metadata Document (CIMD), theo anh (video đăng ngày 29/10/2026) là cách mới hơn và an toàn hơn. Cách thứ hai là chế độ legacy, dynamic client registration (DCR), mà anh nhận xét là không tốt lắm và không an toàn bằng. Clerk hỗ trợ cả hai (theo Hamed, video đăng ngày 29/10/2026). DCR đã bị deprecated, nhưng một số MCP client hay LLM cũ hơn có thể vẫn chỉ chạy được, chỉ hỗ trợ được cách này. Vì vậy anh nói chỉ bật nó nếu bạn thật sự muốn hỗ trợ những client kiểu đó; còn không thì cứ để nguyên ở CIMD.

![Phần Client onboarding trong Clerk với Publish CIMD support đang bật](https://homus.dev/photos/j4EMfNyCYR4-0223.jpg)

Phần Client onboarding trong Clerk (demo trong video đăng ngày 29/10/2026): "Publish CIMD support" đang bật, bên dưới là ba lựa chọn Client admission; "Publish DCR support" đang tắt và mang nhãn Deprecated; phía dưới là "Allowed scopes for dynamic clients" và "Default scopes for dynamic clients", mỗi mục "3 selected" (email, profile, offline_access).

Docs của Clerk cũng ghi Clerk hỗ trợ cả Client ID Metadata Documents (CIMD) lẫn Dynamic Client Registration cho việc đăng ký OAuth client, và chỉ nên bật "Publish DCR support" khi client không hỗ trợ CIMD mà cần DCR ([Clerk Docs, Build an MCP server](https://clerk.com/docs/nextjs/guides/ai/mcp/build-mcp-server), đọc ngày 30/10/2026). README của mcp-handler ghi bản spec MCP 2026-07-28 deprecate DCR để chuyển sang CIMD ([vercel-labs/mcp-handler](https://github.com/vercel-labs/mcp-handler), đọc ngày 30/10/2026).

## 5. Client admission, default scopes và PKCE

Lựa chọn tiếp theo là client admission. Theo Hamed (video đăng ngày 29/10/2026), bạn có thể cho phép mọi client mới đăng ký theo cách này, hoặc chỉ cho phép những client bạn đã đăng ký và đã kết nối từ trước được nói chuyện, được tạo loại MCP client hay OAuth client này. Bạn chọn trong các thiết lập đó.

Ở phần scopes sẽ có ba default scope được chọn sẵn. Anh giải thích (video đăng ngày 29/10/2026): một số client khi nói chuyện với MCP của bạn không gửi kèm scope, nên bạn có thể đặt default scopes cho những client đó. Thường đó là các client đời cũ dùng dynamic client registration, tức DCR client, vốn không có scope, nên bạn cứ expose bộ mặc định. Bạn đổi được các scope này ở đây, và cũng có thể định nghĩa những scope riêng mà ứng dụng của bạn expose rồi chọn chúng.

![Clerk settings: Publish DCR support, scopes và Require PKCE](https://homus.dev/photos/j4EMfNyCYR4-0303.jpg)

Cuộn xuống trong Clerk (demo trong video đăng ngày 29/10/2026): con trỏ chỉ vào "Publish DCR support" (Deprecated), hai ô scopes, rồi mục "Authorization security" với "Require PKCE" đang bật, kèm dòng giải thích từ chối các authorization code request không có S256 PKCE code challenge.

Về PKCE, theo Hamed (video đăng ngày 29/10/2026), đây là thứ bắt buộc cho authorization code, để trao đổi challenge. Nó bắt buộc khi bạn dùng CIMD, nên hãy bật nó lên. Anh nói lý do nó chưa bật sẵn là vì nếu bạn ở chế độ kia thì nó không bắt buộc, bạn hiểu ý rồi đấy. Docs của Clerk ghi: client CIMD luôn yêu cầu Proof Key for Code Exchange (PKCE) với phương thức S256 ([Clerk Docs](https://clerk.com/docs/nextjs/guides/ai/mcp/build-mcp-server), đọc ngày 30/10/2026).

## 6. Xác thực access token: JWT cục bộ hay hỏi Clerk

Theo Hamed (video đăng ngày 29/10/2026), có hai cách xác thực access token. Cách thứ nhất là xác thực cục bộ bằng JWT. Cách thứ hai là xác thực với Clerk, cũng là lựa chọn mặc định, và theo ý anh là cách tốt hơn, vì bạn có thể revoke bất kỳ token nào ngay lập tức thay vì để nó nằm đó. Anh so sánh: chuyện này giống session authentication so với JWT, loại token mà bạn không revoke quyền truy cập ngay được.

![Clerk: Access tokens và OAuth endpoints](https://homus.dev/photos/j4EMfNyCYR4-0323.jpg)

Mục "Access tokens" trong Clerk (demo trong video đăng ngày 29/10/2026): "JWT access tokens" (xác thực token cục bộ, token đã cấp còn hiệu lực tới khi hết hạn và không revoke ngay được) và "Opaque access tokens" đang được chọn (xác thực với Clerk, revoke được ngay và lộ ít dữ liệu hơn cho client). Bên dưới là danh sách OAuth endpoints: Discovery, Authorize, Token, User Info, Token Introspection.

Anh nói (video đăng ngày 29/10/2026) đó là tất cả những gì cần làm trong dashboard của Clerk.

## 7. Cài package và định nghĩa tool

Bước tiếp theo, theo Hamed (video đăng ngày 29/10/2026), là cài các package: mcp-handler và Model Context Protocol server, rồi Clerk MCP tools. Bạn dùng chúng để tạo MCP endpoint. Lệnh cài trong ghi chú của anh trên màn hình:

```
npm install mcp-handler @modelcontextprotocol/server zod @clerk/mcp-tools
```

Ghi chú trên màn hình (video đăng ngày 29/10/2026) giải thích: `mcp-handler` biến các tool của bạn thành một Next.js route nói được MCP; `@clerk/mcp-tools` xác thực OAuth token của Clerk và phục vụ OAuth metadata.

![Ghi chú: Install the packages và Define your tools](https://homus.dev/photos/j4EMfNyCYR4-0343.jpg)

Ghi chú của Hamed (video đăng ngày 29/10/2026): mục "2. Install the packages" với lệnh npm install, và mục "3. Define your tools" có một tool mẫu tên `search_notes`.

Anh nói sẽ cho xem trong code ngay sau đây: tạo tool, rồi register các tool đó trên MCP route. Mục "Define your tools" trong ghi chú (video đăng ngày 29/10/2026) ghi rằng một tool gồm tên, mô tả và input schema, đó là thứ model nhìn thấy; ID của người dùng đã đăng nhập nằm trong `ctx.http.authInfo`. Đoạn code mẫu trên màn hình:

```
// lib/mcp/tools.ts
export function registerTools(server: McpServer) {
server.registerTool(
'search_notes',
{
description: "Search the signed-in user's notes",
inputSchema: z.object({ query: z.string() }),
},
async ({ query }, ctx) => {
const userId = ctx.http?.authInfo?.extra?.userId as string
const notes = await searchNotes(userId, query)
return { content: [{ type: 'text', text: JSON.stringify(notes) }] }
}
)
}
```

## 8. Discovery routes và cách một client kết nối

Theo Hamed (video đăng ngày 29/10/2026), sau đó bạn phải thêm hai endpoint: một cho protected resource và một cho authorization server. Anh giải thích đây là cách MCP client kết nối: chúng hiểu rằng phải đăng nhập, chúng có được quyền truy cập vào resource, rồi chúng khám phá các tool call và những thứ khác, và có thể giao tiếp với MCP endpoint của bạn.

Mục "5. Add the discovery routes" trong ghi chú (video đăng ngày 29/10/2026) ghi: hai public route cho MCP client biết Clerk là authorization server của bạn, mỗi route export một handler làm sẵn từ `@clerk/mcp-tools/next`, và phải để chúng public, không chặn chúng trong middleware. Hai route trên màn hình:

```
app/.well-known/oauth-protected-resource/mcp/route.ts → protectedResourceHandlerClerk()
app/.well-known/oauth-authorization-server/route.ts → authServerMetadataHandlerClerk()   (for older clients)
```

![Ghi chú: Add the discovery routes, Connect a client, How it works](https://homus.dev/photos/j4EMfNyCYR4-0403.jpg)

Ghi chú của Hamed (video đăng ngày 29/10/2026): "Add the discovery routes", "Connect a client" (deploy rồi thêm `https://your-app.com/mcp` làm custom connector trong Claude hoặc ChatGPT; lần đầu client yêu cầu người dùng đăng nhập bằng Clerk) và bốn bước "How it works", kèm một ô cảnh báo màu vàng.

Để kết nối client, theo anh (video đăng ngày 29/10/2026), bạn lấy MCP endpoint, kết nối nó như một custom connector trong Claude hoặc ChatGPT, rồi bạn trò chuyện được với MCP của mình. Bốn bước "How it works" trong ghi chú trên màn hình:

- Client gọi `/mcp` mà không có token và nhận về 401 trỏ tới discovery route (ghi chú trong video đăng ngày 29/10/2026).

- Discovery route cho biết Clerk là authorization server (ghi chú trong video đăng ngày 29/10/2026).

- Người dùng đăng nhập bằng Clerk và chấp thuận quyền truy cập; Clerk cấp một access token (ghi chú trong video đăng ngày 29/10/2026).

- Client gọi lại `/mcp` kèm token; `withMcpAuth` xác thực nó, và các tool chạy với tư cách người dùng đó (ghi chú trong video đăng ngày 29/10/2026).

Ô cảnh báo trong ghi chú (video đăng ngày 29/10/2026) viết: OAuth token xác định người dùng, chứ không xác định một Clerk organization đang active; nếu dữ liệu của bạn scope theo org, hãy kiểm tra membership của người dùng bên trong mỗi tool.

## 9. Đi qua code: well-known routes, server actions và MCP route

Trước khi tới phần kết nối, Hamed quay lại code để cho xem những gì đã làm. Nhìn từ trên cao, có ứng dụng, có hai well-known route: authorization server và route kia (protected resource). Theo anh (video đăng ngày 29/10/2026), hai route này chỉ là copy-paste nguyên xi theo hướng dẫn của Clerk.

![Cây thư mục của dự án multi-tenant-saas trong editor](https://homus.dev/photos/j4EMfNyCYR4-0443.jpg)

Cây thư mục dự án `multi-tenant-saas` trong editor (video đăng ngày 29/10/2026): trong `app/.well-known` có `oauth-authorization-server/route.ts` và `oauth-protected-resource/mcp/route.ts`; tiếp theo là route group `(site)` với `(auth)`, `dashboard`, `pricing`, rồi các thư mục `actions`, `api`, `mcp`.

Phần site nằm trong route group `(site)`. Tiếp theo là `actions`: theo Hamed (video đăng ngày 29/10/2026), đây là các server action anh đã định nghĩa và sẽ dùng bên trong các tool call của MCP. Rồi tới lớp API, chủ yếu là webhook để Clerk đồng bộ với database Postgres. Cuối cùng là MCP: đây là MCP route. Anh tạo MCP handler bằng package mcp-handler.

## 10. withMcpAuth, data layer và các tool

Trong MCP route, anh register các tool (sẽ cho xem ngay sau đó), rồi tạo `withMcpAuth`. Theo Hamed (video đăng ngày 29/10/2026), đây là chỗ Clerk vào cuộc: nó authenticate OAuth token được chấp nhận, rồi truyền user ID qua context xuống các tool call bên dưới. Đó là cách bạn biết được người dùng là ai.

Về dữ liệu, anh cho xem data layer anh đã định nghĩa để truy cập dữ liệu bằng Prisma (video đăng ngày 29/10/2026). Rồi tới phần MCP: các tool khác nhau anh đã định nghĩa, và anh dùng hàm `registerTool` để register từng tool vào MCP endpoint. Trong file `lib/mcp/tools.ts` trên màn hình, hàm `registerTools(server: McpServer)` gọi `server.registerTool(...)` tám lần.

Luồng Hamed mô tả (video đăng ngày 29/10/2026): connector gọi MCP route tạo bằng mcp-handler, `withMcpAuth` để Clerk xác thực OAuth token rồi truyền user ID xuống tool call, và tool dùng server actions cùng data layer Prisma.

Theo anh (video đăng ngày 29/10/2026), nhìn từ trên cao, đó là những gì cần có để tạo MCP server. Anh đã nói sơ qua rồi, giờ là lúc xem nó chạy thật, và sau đó anh sẽ nói về vài điểm khác nhau giữa cách anh làm và ví dụ của Clerk trong tài liệu.

## 11. Thêm custom connector trong Claude desktop

Hamed quay lại ứng dụng. Anh đã tạo sẵn một custom connector trong Claude; màn hình là ứng dụng Claude desktop. Theo anh (video đăng ngày 29/10/2026), bạn vào phần custom connectors, từ đó thêm một connector, chọn custom connector, đặt tên, rồi dán URL của MCP server, tức bản deploy của ứng dụng, thêm `/mcp` ở cuối.

![Claude desktop với menu dấu cộng đang mở, có mục Connectors](https://homus.dev/photos/j4EMfNyCYR4-0603.jpg)

Ứng dụng Claude desktop, cuộc trò chuyện "YouTube project tasks" (demo trong video đăng ngày 29/10/2026): menu dấu cộng mở ra các mục Upload or add files, Add folder, Record a skill, Skills, Connectors, Design system, Plugins, Research. Phía trên còn một yêu cầu trước đó xoá task "Record the MCP video" và câu trả lời xác nhận đã xoá.

Theo Hamed (video đăng ngày 29/10/2026), connector sẽ đưa bạn qua luồng đăng nhập bằng chính tài khoản bạn dùng trong ứng dụng.

## 12. Demo: liệt kê project và chuyển task sang Done

Anh bắt đầu bằng câu: "can you get the projects that I have in my account?" (bạn lấy giúp các project trong tài khoản của tôi được không?). Claude gọi tới task tracker, và như anh chỉ trên màn hình, nó liệt kê toàn bộ project và toàn bộ task anh đang có (demo trong video đăng ngày 29/10/2026).

Rồi anh hỏi: "can you move the record the MCP video to done?" (chuyển task "Record the MCP video" sang Done giúp tôi). Quay lại ứng dụng, task đó vừa được chuyển sang cột Done (demo trong video đăng ngày 29/10/2026). Theo anh, bạn cũng tạo được task mới, project mới, đại loại vậy.

## 13. Tính năng trả phí vẫn được giữ khi đi qua MCP

Theo Hamed (video đăng ngày 29/10/2026), ứng dụng được thiết lập để tài khoản free chỉ có một project: mỗi organization có một project, muốn tạo project khác thì phải nâng cấp. Phần này dùng Clerk Billing bên dưới.

Anh hỏi Claude: "can you create a new project called YouTube?" (tạo giúp tôi một project mới tên YouTube). Claude không làm được, vì đó là tính năng Pro và anh đang ở gói free (demo trong video đăng ngày 29/10/2026). Lỗi này cũng được trả về bên trong một error, nên Claude giải thích lại được cho anh rằng tài khoản đang ở gói free, chỉ có được một project, và project thứ hai cần Pro.

![Claude trả lời rằng không tạo được project YouTube vì gói free](https://homus.dev/photos/j4EMfNyCYR4-0743.jpg)

Câu trả lời của Claude trên màn hình (demo trong video đăng ngày 29/10/2026): tool call tạo project "YouTube" mang nhãn thất bại; Claude viết rằng tracker vẫn trả về cùng một lỗi, "HB Media Inc." đang ở gói free chỉ cho một project nên project thứ hai cần Pro, và khi nâng cấp xong thì hỏi lại để nó tạo ngay.

Theo anh (video đăng ngày 29/10/2026), bạn có thể mở rộng các tool call tới mức bạn muốn, chẳng hạn để nó subscribe được vào gói Pro, đại loại vậy. Bạn hiểu ý rồi đấy.

## 14. Tổng kết và những điểm khác với ví dụ trong docs của Clerk

Hamed tóm lại (video đăng ngày 29/10/2026): anh và nhóm đã thêm một MCP endpoint, hay một handler, một route endpoint, vào ứng dụng, expose rất nhiều chức năng cho người dùng. Anh và nhóm bảo vệ nó bằng Clerk OAuth, thứ cấp quyền cho từng người dùng cụ thể chỉ được truy cập dữ liệu của chính mình. Trong trường hợp này, đó là project, organization và task trong một kiến trúc multi-tenant. Phần multi-tenancy, cách tách từng org khỏi nhau và schema dữ liệu nằm trong repo của ứng dụng ([HamedBahram/multi-tenant-saas](https://github.com/HamedBahram/multi-tenant-saas), đọc ngày 30/10/2026).

Trước khi kết thúc, anh nêu vài điểm khác giữa cách anh làm và tài liệu của Clerk. Trong docs có một ví dụ triển khai bằng Next.js và một repo hướng dẫn. Theo Hamed (video đăng ngày 29/10/2026), docs dùng phiên bản cũ hơn của mcp-handler, trong đó tạo một dynamic route cho transport, để hỗ trợ cả SSE lẫn HTTP transport. Trang docs, đọc ngày 30/10/2026, vẫn ghi: tạo thư mục `[transport]` trong `app/`, Streamable HTTP dùng `/mcp` làm base path và SSE dùng `/sse` ([Clerk Docs](https://clerk.com/docs/nextjs/guides/ai/mcp/build-mcp-server)).

Ứng dụng của anh không hỗ trợ SSE cũ nữa, nên anh đã bỏ phần đó (video đăng ngày 29/10/2026); mô tả video ghi là chỉ Streamable HTTP, không SSE. Anh cũng dùng mcp-handler bản mới, cách triển khai khác đi một chút. Bảng so sánh trong ghi chú của anh trên màn hình:

![Bảng How this differs from Clerk's example](https://homus.dev/photos/j4EMfNyCYR4-0423.jpg)

Mục "How this differs from Clerk's example" trong ghi chú (video đăng ngày 29/10/2026): ví dụ của Clerk dùng mcp-handler 1.x, ứng dụng của anh dùng 2.x, xây trên MCP SDK v2. Route: `app/[transport]/route.ts` so với `app/mcp/route.ts`. Packages: `@modelcontextprotocol/sdk` so với `@modelcontextprotocol/server` và zod 4.

- Route (ghi chú trong video đăng ngày 29/10/2026): bản 2.x bỏ transport SSE cũ, nên chỉ còn một path để phục vụ; một dynamic route `[transport]` ở gốc còn bắt luôn mọi URL lạ (như `/foo`) thay vì trả về 404.

- Packages (ghi chú trong video đăng ngày 29/10/2026): bản 2.x cần SDK v2, nói được spec MCP mới nhất và vẫn phục vụ client cũ.

README của mcp-handler, đọc ngày 30/10/2026, ghi rằng mcp-handler 2.x cần các package MCP SDK v2 (`@modelcontextprotocol/server` ^2.0.0), zod ^4.2.0 và Node.js 20+, transport HTTP+SSE bị bỏ trong 2.x, và ai còn dùng `@modelcontextprotocol/sdk` 1.x thì dùng mcp-handler 1.x ([vercel-labs/mcp-handler](https://github.com/vercel-labs/mcp-handler)).

Theo Hamed (video đăng ngày 29/10/2026), phần lớn hai bên rất giống nhau; chỉ là package đã lên phiên bản tiếp theo và bản mới hơi khác, và ứng dụng của anh không dùng dynamic transport route để hỗ trợ thêm SSE. Anh kết luận: đó là việc triển khai MCP, expose MCP hay tạo một MCP server trong ứng dụng dễ cỡ nào, và quan trọng hơn là bảo vệ nó bằng Clerk. Rồi anh chào tạm biệt.

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

- Tên gốc: The Hard Part of MCP Is Auth. Here’s How to Solve It
- Sự kiện: [YouTube](https://homus.dev/events/youtube)
- Video gốc: [https://www.youtube.com/watch?v=j4EMfNyCYR4](https://www.youtube.com/watch?v=j4EMfNyCYR4)

- Kênh YouTube Hamed Bahram (Hamed Bahram): [youtube.com/channel/UC6MkbhaNlxFYjv9sBQRDVow](https://www.youtube.com/channel/UC6MkbhaNlxFYjv9sBQRDVow)

- Clerk Docs, Build an MCP server in your application with Clerk (Next.js): các bước bật OAuth cho MCP client, CIMD và DCR, PKCE S256, thư mục `[transport]`, `withMcpAuth` và hai handler metadata (đọc ngày 30/10/2026): [clerk.com/docs/nextjs/guides/ai/mcp/build-mcp-server](https://clerk.com/docs/nextjs/guides/ai/mcp/build-mcp-server)

- mcp-handler, README về bản 2.x, MCP SDK v2, việc bỏ HTTP+SSE và CIMD (đọc ngày 30/10/2026): [github.com/vercel-labs/mcp-handler](https://github.com/vercel-labs/mcp-handler)

- Đặc tả MCP, phần Authorization: Protected Resource Metadata, Client ID Metadata Documents, Dynamic Client Registration, PKCE (bản 2025-11-25, đọc ngày 30/10/2026): [modelcontextprotocol.io/specification/2025-11-25/basic/authorization](https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization)

- Source code của ứng dụng task tracker multi-tenant trong video (đọc ngày 30/10/2026): [github.com/HamedBahram/multi-tenant-saas](https://github.com/HamedBahram/multi-tenant-saas)

Nguồn: https://homus.dev/talks/the-hard-part-of-mcp-is-auth-heres-how-to-solve-it · cập nhật 2026-10-30
