> Discover all available pages from the documentation index: https://mastra.zisheng.pro/zh-HK/llms.txt # MCPServer `MCPServer` 類別可將你現有的 Mastra Tools 及 Agents 公開為 Model Context Protocol (MCP) 伺服器。任何 MCP 用戶端(例如 Cursor、Windsurf 或 Claude Desktop)均可連接這些功能,並讓 agent 使用。 請注意,如果你只需在 Mastra 應用程式內直接使用 Tools 或 Agents,未必需要建立 MCP 伺服器。此 API 專門用於向\_外部\_ MCP 用戶端公開 Mastra Tools 及 Agents。 它同時支援 [stdio(子程序)及 SSE (HTTP) MCP 傳輸](https://modelcontextprotocol.io/docs/concepts/transports)。 ## 建構函式 要建立新的 `MCPServer`,你需要提供伺服器的基本資料、它所提供的 Tools,以及選擇性提供任何想公開為 Tools 的 Agents。 ```typescript import { Agent } from '@mastra/core/agent' import { createTool } from '@mastra/core/tools' import { MCPServer } from '@mastra/mcp' import { z } from 'zod' import { dataProcessingWorkflow } from '../workflows/dataProcessingWorkflow' const myAgent = new Agent({ id: 'my-example-agent', name: 'MyExampleAgent', description: 'A generalist to help with basic questions.', instructions: 'You are a helpful assistant.', model: 'openai/gpt-5.6-sol', }) const weatherTool = createTool({ id: 'getWeather', description: 'Gets the current weather for a location.', inputSchema: z.object({ location: z.string() }), execute: async inputData => `Weather in ${inputData.location} is sunny.`, }) const server = new MCPServer({ id: 'my-custom-server', name: 'My Custom Server', version: '1.0.0', description: 'A server that provides weather data and agent capabilities', instructions: 'Use the available tools to help users with weather information and data processing tasks.', tools: { weatherTool }, agents: { myAgent }, // this agent will become tool "ask_myAgent" workflows: { dataProcessingWorkflow, // this workflow will become tool "run_dataProcessingWorkflow" }, }) ``` ### 設定屬性 建構函式接受具有以下屬性的 `MCPServerConfig` 物件: **id** (`string`): 伺服器的獨有識別碼。伺服器向 Mastra 註冊時會保留此 ID,並可用於透過 getMCPServerById() 擷取伺服器。 **name** (`string`): 伺服器的描述性名稱(例如 'My Weather and Agent Server')。 **version** (`string`): 伺服器的 semantic version(例如 '1.0.0')。 **tools** (`ToolsInput`): 以 Tool 名稱作為鍵、Mastra Tool 定義(使用 createTool 或 Vercel AI SDK 建立)作為值的物件。這些 Tools 會直接公開。 **agents** (`Record`): 以 agent 識別碼作為鍵、Mastra Agent 實例作為值的物件。每個 agent 都會自動轉換為名為 ask\_\ 的 Tool。Agent 的建構函式設定中\*\*必須\*\*定義非空白的 description 字串屬性,此描述會用作 Tool 描述。如果 agent 的描述缺漏或為空白,MCPServer 初始化期間會拋出錯誤。 **workflows** (`Record`): 以 Workflow 識別碼作為鍵、Mastra Workflow 實例作為值的物件。每個 Workflow 都會轉換為名為 run\_\ 的 Tool。Workflow 的 inputSchema 會成為 Tool 的輸入 schema。Workflow \*\*必須\*\*具有非空白的 description 字串屬性,用作 Tool 描述;如描述缺漏或為空白,系統會拋出錯誤。Tool 會先呼叫 workflow\.createRun(),再呼叫 run.start({ inputData: \ }) 以執行 Workflow。如果由 agent 或 Workflow 衍生的 Tool 名稱(例如 ask\_myAgent 或 run\_myWorkflow)與明確定義的 Tool 名稱或另一衍生名稱衝突,則明確定義的 Tool 優先,並會記錄警告。引致後續衝突的 Agents/Workflows 會被略過。 **description** (`string`): MCP 伺服器功能的選擇性描述。 **instructions** (`string`): 說明如何使用伺服器及其功能的選擇性指示。 **mapAuthInfoToUser** (`({ authInfo, extra, requestContext }) => unknown | null | undefined | Promise`): 將 extra.authInfo 中的 MCP 傳輸驗證資料映射至 Mastra FGA 檢查使用的 user 值。受 OAuth 保護的 MCP 伺服器在具有 FGA Provider 的 Mastra 實例上註冊時,請使用此屬性。 **fga** (`{ resourceMapping?: Partial string | undefined }>>; permissionMapping?: Record }`): 覆寫此 MCP 伺服器 tools/list 及 tools/call FGA 檢查的 Resource 及權限映射。當 MCP 授權範圍應與內部 agent 或 Workflow Tool 執行不同時,請使用此屬性。 **repository** (`Repository`): 伺服器原始碼的選擇性 repository 資料。 **releaseDate** (`string`): 此伺服器版本的選擇性發佈日期(ISO 8601 字串)。如未提供,預設為實例化時間。 **isLatest** (`boolean`): 表示這是否為最新版本的選擇性旗標。如未提供,預設為 true。 **packageCanonical** (`'npm' | 'docker' | 'pypi' | 'crates' | string`): 伺服器以套件形式發佈時的選擇性 canonical 封裝格式(例如 'npm'、'docker')。 **packages** (`PackageInfo[]`): 此伺服器可安裝套件的選擇性清單。 **remotes** (`RemoteInfo[]`): 此伺服器遠端存取點的選擇性清單。 **resources** (`MCPServerResources`): 定義伺服器如何處理 MCP Resources 的物件。詳情請參閱 Resource 處理章節。 **prompts** (`MCPServerPrompts`): 定義伺服器如何處理 MCP Prompts 的物件。詳情請參閱 Prompt 處理章節。 **appResources** (`AppResources`): ui:// URI 至 app Resource 設定的映射。每個項目定義一個透過 MCP Apps extension (SEP-1865) 提供的互動式 HTML UI。詳情請參閱 MCP Apps 章節。 ## 將 Agents 公開為 Tools `MCPServer` 的一項強大功能,是自動將 Mastra Agents 公開為可呼叫的 Tools。當你在設定的 `agents` 屬性中提供 Agents 時: - **Tool 命名**:每個 agent 都會轉換為名為 `ask_` 的 Tool,其中 `` 是你在 `agents` 物件中為該 agent 使用的鍵。例如,若設定 `agents: { myAgentKey: myAgentInstance }`,系統便會建立名為 `ask_myAgentKey` 的 Tool。 - **Tool 功能**: - **描述**:產生的 Tool 描述格式為:「向 agent `` 提問。原始 agent 指示:``」。 - **輸入**:Tool 預期接收一個具有 `message` 屬性(字串)的物件引數:`{ message: "Your question for the agent" }`。 - **執行**:呼叫此 Tool 時,它會使用所提供的 `query` 呼叫相應 agent 的 `generate()` 方法。 - **輸出**:直接將 agent 的 `generate()` 方法結果作為 Tool 輸出傳回。 - **名稱衝突。** 如果在 `tools` 設定中明確定義的 Tool,與由 agent 衍生的 Tool 同名(例如名為 `ask_myAgentKey` 的 Tool 與鍵為 `myAgentKey` 的 agent 並存),則會\_優先採用明確定義的 Tool\_。發生此衝突時,該 agent 不會轉換為 Tool,並會記錄警告。 這讓 MCP 用戶端能像使用其他 Tool 一樣,以自然語言查詢直接與 Agents 互動。 ### 將 Agent 轉換為 Tool 當你在 `agents` 設定屬性中提供 Agents 時,`MCPServer` 會自動為每個 agent 建立相應的 Tool。Tool 名稱為 `ask_`,其中 `` 是你在 `agents` 物件中使用的鍵。 此產生的 Tool 描述為:「向 agent `` 提問。Agent 描述:``」。 要將 agent 轉換為 Tool,實例化時的設定中**必須**包含非空白的 `description` 字串屬性(例如 `new Agent({ id: 'my-agent', name: 'myAgent', description: 'This agent does X.', ... })`)。如果傳入 `MCPServer` 的 agent 缺少 `description` 或其值為空白,實例化 `MCPServer` 時會拋出錯誤,伺服器設定亦會失敗。 這讓你可透過 MCP 快速公開 Agents 的生成能力,讓用戶端直接向 Agents「提問」。 ### 在 Tools 中存取 MCP Context 透過 `MCPServer` 公開的 Tools,可根據 Tool 的呼叫方式,經由兩個不同屬性存取 MCP 請求 context(驗證、session ID 等): | 呼叫模式 | 存取方式 | | ------------- | ------------------------------------------- | | 直接呼叫 Tool | `context?.mcp?.extra` | | Agent Tool 呼叫 | `context?.requestContext?.get("mcp.extra")` | **通用模式**(適用於兩種 context): ```typescript const mcpExtra = context?.mcp?.extra ?? context?.requestContext?.get('mcp.extra') const authInfo = mcpExtra?.authInfo ``` #### 範例:適用於兩種 context 的 Tool ```typescript import { createTool } from '@mastra/core/tools' import { z } from 'zod' const fetchUserData = createTool({ id: 'fetchUserData', description: 'Fetches user data using authentication from MCP context', inputSchema: z.object({ userId: z.string().describe('The ID of the user to fetch'), }), execute: async (inputData, context) => { // Access MCP authentication context // When called directly via MCP: context.mcp.extra // When called via agent: context.requestContext.get('mcp.extra') const mcpExtra = context?.mcp?.extra || context?.requestContext?.get('mcp.extra') const authInfo = mcpExtra?.authInfo if (!authInfo?.token) { throw new Error('Authentication required') } const response = await fetch(`https://api.example.com/users/${inputData.userId}`, { headers: { Authorization: `Bearer ${authInfo.token}`, }, }) return response.json() }, }) ``` ## 方法 你可在 `MCPServer` 實例上呼叫以下函式,以控制其行為及取得資料。 ### `startStdio()` 使用此方法啟動伺服器,讓它透過標準輸入及輸出 (stdio) 通訊。伺服器作為命令列程式執行時,通常會採用此方式。 ```typescript async startStdio(): Promise ``` 以下說明如何使用 stdio 啟動伺服器: ```typescript const server = new MCPServer({ id: 'my-server', name: 'My Server', version: '1.0.0', tools: {/* ... */}, }) await server.startStdio() ``` ### `startSSE()` 此方法協助你將 MCP 伺服器整合至現有網頁伺服器,並使用 Server-Sent Events (SSE) 通訊。網頁伺服器收到 SSE 或訊息路徑的請求時,便會從其程式碼呼叫此方法。 ```typescript async startSSE({ url, ssePath, messagePath, req, res, }: { url: URL; ssePath: string; messagePath: string; req: any; res: any; }): Promise ``` 以下範例說明如何在 HTTP 伺服器請求處理器中使用 `startSSE`。在此範例中,MCP 用戶端可透過 `http://localhost:1234/sse` 連接 MCP 伺服器: ```typescript import http from 'http' const httpServer = http.createServer(async (req, res) => { await server.startSSE({ url: new URL(req.url || '', `http://localhost:1234`), ssePath: '/sse', messagePath: '/message', req, res, }) }) httpServer.listen(PORT, () => { console.log(`HTTP server listening on port ${PORT}`) }) ``` `startSSE` 方法所需值的詳細資料如下: **url** (`URL`): 使用者要求的網址。 **ssePath** (`string`): 用戶端連接 SSE 的指定 URL 部分(例如 '/sse')。 **messagePath** (`string`): 用戶端傳送訊息的指定 URL 部分(例如 '/message')。 **req** (`any`): 來自網頁伺服器的傳入請求物件。 **res** (`any`): 網頁伺服器用作傳回資料的回應物件。 ### `startHonoSSE()` 此方法協助你將 MCP 伺服器整合至現有網頁伺服器,並使用 Server-Sent Events (SSE) 通訊。網頁伺服器收到 SSE 或訊息路徑的請求時,便會從其程式碼呼叫此方法。 ```typescript async startHonoSSE({ url, ssePath, messagePath, req, res, }: { url: URL; ssePath: string; messagePath: string; req: any; res: any; }): Promise ``` 以下範例說明如何在 HTTP 伺服器請求處理器中使用 `startHonoSSE`。在此範例中,MCP 用戶端可透過 `http://localhost:1234/hono-sse` 連接 MCP 伺服器: ```typescript import http from 'http' const httpServer = http.createServer(async (req, res) => { await server.startHonoSSE({ url: new URL(req.url || '', `http://localhost:1234`), ssePath: '/hono-sse', messagePath: '/message', req, res, }) }) httpServer.listen(PORT, () => { console.log(`HTTP server listening on port ${PORT}`) }) ``` `startHonoSSE` 方法所需值的詳細資料如下: **url** (`URL`): 使用者要求的網址。 **ssePath** (`string`): 用戶端連接 SSE 的指定 URL 部分(例如 '/hono-sse')。 **messagePath** (`string`): 用戶端傳送訊息的指定 URL 部分(例如 '/message')。 **req** (`any`): 來自網頁伺服器的傳入請求物件。 **res** (`any`): 網頁伺服器用作傳回資料的回應物件。 ### `startHTTP()` 此方法協助你將 MCP 伺服器整合至現有網頁伺服器,並使用可串流 HTTP 通訊。網頁伺服器收到 HTTP 請求時,便會從其程式碼呼叫此方法。 ```typescript async startHTTP({ url, httpPath, req, res, options = { sessionIdGenerator: () => randomUUID() }, }: { url: URL; httpPath: string; req: http.IncomingMessage; res: http.ServerResponse; options?: StreamableHTTPServerTransportOptions; }): Promise ``` 以下範例說明如何在 HTTP 伺服器請求處理器中使用 `startHTTP`。在此範例中,MCP 用戶端可透過 `http://localhost:1234/http` 連接 MCP 伺服器: ```typescript import http from 'http' const httpServer = http.createServer(async (req, res) => { await server.startHTTP({ url: new URL(req.url || '', 'http://localhost:1234'), httpPath: `/mcp`, req, res, options: { sessionIdGenerator: () => randomUUID(), }, }) }) httpServer.listen(PORT, () => { console.log(`HTTP server listening on port ${PORT}`) }) ``` 在 **serverless 環境**(Supabase Edge Functions、Cloudflare Workers、Vercel Edge 等)中,使用 `serverless: true` 啟用無狀態操作: ```typescript // Supabase Edge Function example import { serve } from 'https://deno.land/std@0.168.0/http/server.ts' import { MCPServer } from '@mastra/mcp' // Note: You will need to convert req/res format from Deno to Node import { toReqRes, toFetchResponse } from 'fetch-to-node' const server = new MCPServer({ id: 'my-serverless-mcp', name: 'My Serverless MCP', version: '1.0.0', tools: {/* your tools */}, }) serve(async req => { const url = new URL(req.url) if (url.pathname === '/mcp') { // Convert Deno Request to Node.js-compatible format const { req: nodeReq, res: nodeRes } = toReqRes(req) await server.startHTTP({ url, httpPath: '/mcp', req: nodeReq, res: nodeRes, options: { serverless: true, // ← Enable stateless mode for serverless }, }) return toFetchResponse(nodeRes) } return new Response('Not found', { status: 404 }) }) ``` > **何時使用 serverless: true:** 部署至每項請求都在全新無狀態執行 context 中運行的環境時,請使用 `serverless: true`: > > - Supabase Edge Functions > - Cloudflare Workers > - Vercel Edge Functions > - Netlify Edge Functions > - AWS Lambda > - Deno Deploy > > 以下情況請使用預設的 session 模式(不設置 `serverless: true`): > > - 長時間運行的 Node.js 伺服器 > - Docker 容器 > - 傳統託管(VPS、專用伺服器) > > serverless 模式會停用 session 管理,並為每項請求建立新的伺服器實例。無狀態環境不會在呼叫之間保留記憶體,因此必須採用此方式。 > > 預設情況下,serverless 模式會將每項請求緩衝為單一 JSON 回應,因此 Tool 傳送的 `notifications/progress` 永遠不會送達用戶端。設置 `serverlessStreaming: true`,即可改用請求範圍的 SSE 串流處理請求,並在最終結果前送達進度通知: > > ```typescript > await server.startHTTP({ > url, > httpPath: '/mcp', > req: nodeReq, > res: nodeRes, > options: { > serverless: true, > serverlessStreaming: true, // ← Stream request-scoped notifications/progress > }, > }) > ``` > > 這仍是無狀態模式:毋須提供或保留 `mcp-session-id`。它只會啟用目前請求範圍內的通知(例如進度);下列依賴 session 的功能仍不可用。 > > 下列 MCP 功能需要 session 狀態或持久連線,因此在 serverless 模式中**無法運作**(包括使用 `serverlessStreaming: true` 時): > > - **Elicitation** — Tool 執行期間的互動式使用者輸入請求,需要 session 管理才能將回應路由至正確的用戶端 > - **Resource 訂閱** — `resources/subscribe` 及 `resources/unsubscribe` 需要持久連線以維持訂閱狀態 > - **Resource 更新通知** — `resources.notifyUpdated()` 需要有效訂閱及持久連線才能通知用戶端 > - **Prompt 清單變更通知** — `prompts.notifyListChanged()` 需要持久連線才能向用戶端推送更新 > - **Tool 清單變更通知** — `toolActions.notifyListChanged()` 需要持久連線才能向用戶端推送更新 > - **伺服器記錄通知** — `sendLoggingMessage()` 需要持久連線才能向用戶端推送記錄訊息 > > 這些功能在長時間運行的伺服器環境(Node.js 伺服器、Docker 容器等)中可正常運作。 `startHTTP` 方法所需值的詳細資料如下: **url** (`URL`): 使用者要求的網址。 **httpPath** (`string`): MCP 伺服器處理 HTTP 請求的指定 URL 部分(例如 '/mcp')。 **req** (`http.IncomingMessage`): 來自網頁伺服器的傳入請求物件。 **res** (`http.ServerResponse`): 網頁伺服器用作傳回資料的回應物件。 **options** (`StreamableHTTPServerTransportOptions`): HTTP 傳輸的選擇性設定。詳情請參閱下方選項表。 `StreamableHTTPServerTransportOptions` 物件讓你自訂 HTTP 傳輸的行為。可用選項如下: **serverless** (`boolean`): 如為 true,則以無狀態模式運行而不進行 session 管理。每項請求均由全新的伺服器實例獨立處理。這對無法在呼叫之間保留 session 的 serverless 環境(Cloudflare Workers、Supabase Edge Functions、Vercel Edge 等)至關重要。預設為 false。 **serverlessStreaming** (`boolean`): 如為 true,serverless 請求會使用請求範圍的 SSE 串流,而非已緩衝的 JSON 回應,讓請求內的 notifications/progress 可在最終結果前送達用戶端。只有與 serverless: true 同時使用才會生效。預設為 false(已緩衝的 JSON 回應),以保持向後兼容。它只啟用進度等請求範圍通知;Elicitation、訂閱及請求外通知仍需要 session 狀態。 **sessionIdGenerator** (`(() => string) | undefined`): 產生獨有 session ID 的函式。它應是密碼學上安全且全域獨有的字串。傳回 undefined 可停用 session 管理。 **onsessioninitialized** (`(sessionId: string) => void`): 初始化新 session 時呼叫的回呼函式,適合用於追蹤有效的 MCP sessions。 **enableJsonResponse** (`boolean`): 如為 true,伺服器會傳回純 JSON 回應,而非使用 Server-Sent Events (SSE) 串流。預設為 false。 **eventStore** (`EventStore`): 用於讓訊息可恢復的 event store。提供此選項後,用戶端可重新連接並恢復訊息串流。 ### `close()` 此方法會關閉伺服器並釋放所有資源。 ```typescript async close(): Promise ``` ### `getServerInfo()` 此方法傳回伺服器的基本資料。 ```typescript getServerInfo(): ServerInfo ``` ### `getServerDetail()` 此方法傳回伺服器資料的詳細內容。 ```typescript getServerDetail(): ServerDetail ``` ### `getToolListInfo()` 此方法傳回建立伺服器時設定的 Tools。這是唯讀清單,適合用於除錯。 ```typescript getToolListInfo(): ToolListInfo ``` ### `getToolInfo()` 此方法傳回指定 Tool 的詳細資料。 ```typescript getToolInfo(toolName: string): ToolInfo ``` ### `executeTool()` 此方法執行指定 Tool 並傳回結果。 ```typescript executeTool(toolName: string, input: any): Promise ``` ### `getStdioTransport()` 如果使用 `startStdio()` 啟動伺服器,可透過此方法取得管理 stdio 通訊的物件。這主要用於內部檢查或測試。 ```typescript getStdioTransport(): StdioServerTransport | undefined ``` ### `getSseTransport()` 如果使用 `startSSE()` 啟動伺服器,可透過此方法取得管理 SSE 通訊的物件。與 `getStdioTransport` 一樣,這主要用於內部檢查或測試。 ```typescript getSseTransport(): SSEServerTransport | undefined ``` ### `getSseHonoTransport()` 如果使用 `startHonoSSE()` 啟動伺服器,可透過此方法取得管理 SSE 通訊的物件。與 `getSseTransport` 一樣,這主要用於內部檢查或測試。 ```typescript getSseHonoTransport(): SSETransport | undefined ``` ### `getStreamableHTTPTransport()` 如果使用 `startHTTP()` 啟動伺服器,可透過此方法取得管理 HTTP 通訊的物件。與 `getSseTransport` 一樣,這主要用於內部檢查或測試。 ```typescript getStreamableHTTPTransport(): StreamableHTTPServerTransport | undefined ``` ### `tools()` 執行此 MCP 伺服器提供的指定 Tool。 ```typescript async executeTool( toolId: string, args: any, executionContext?: { messages?: any[]; toolCallId?: string }, ): Promise ``` **toolId** (`string`): 要執行的 Tool ID/名稱。 **args** (`any`): 要傳給 Tool execute 函式的引數。 **executionContext** (`object`): Tool 執行的選擇性 context,例如訊息或 toolCallId。 ## Resource 處理 ### 甚麼是 MCP Resources? Resources 是 Model Context Protocol (MCP) 的核心基本元素,讓伺服器可公開供用戶端讀取的資料及內容,並用作 LLM 互動的 context。它們可代表 MCP 伺服器希望提供的任何資料,例如: - 檔案內容 - 資料庫記錄 - API 回應 - 即時系統資料 - 螢幕截圖及圖像 - 記錄檔 Resources 以獨有 URI 識別(例如 `file:///home/user/documents/report.pdf`、`postgres://database/customers/schema`),可包含文字(UTF-8 編碼)或二進制資料(base64 編碼)。 用戶端可透過以下方式探索 Resources: 1. **直接 Resources**:伺服器透過 `resources/list` 端點公開具體 Resources 清單。 2. **Resource 範本**:對於執行階段定義的 Resources,伺服器可公開 URI 範本 (RFC 6570),讓用戶端用來建構 Resource URI。 要讀取 Resource,用戶端需使用 URI 發出 `resources/read` 請求。如果用戶端已訂閱該 Resource,伺服器亦可通知它 Resource 清單變更 (`notifications/resources/list_changed`) 或指定 Resource 內容更新 (`notifications/resources/updated`)。 詳情請參閱 [MCP 官方 Resources 文件](https://modelcontextprotocol.io/docs/concepts/resources)。 ### `MCPServerResources` 類型 `resources` 選項接受 `MCPServerResources` 類型的物件。此類型定義伺服器處理 Resource 請求時使用的回呼函式: ```typescript export type MCPServerResources = { // Callback to list available resources listResources: () => Promise // Callback to get the content of a specific resource getResourceContent: ({ uri, }: { uri: string }) => Promise // Optional callback to list available resource templates resourceTemplates?: () => Promise } export type MCPServerResourceContent = { text?: string } | { blob?: string } ``` 範例: ```typescript import { MCPServer } from '@mastra/mcp' import type { MCPServerResourceContent, Resource, ResourceTemplate } from '@mastra/mcp' // Resources/resource templates will generally be dynamically fetched. const myResources: Resource[] = [ { uri: 'file://data/123.txt', name: 'Data File', mimeType: 'text/plain' }, ] const myResourceContents: Record = { 'file://data.txt/123': { text: 'This is the content of the data file.' }, } const myResourceTemplates: ResourceTemplate[] = [ { uriTemplate: 'file://data/{id}', name: 'Data File', description: 'A file containing data.', mimeType: 'text/plain', }, ] const myResourceHandlers: MCPServerResources = { listResources: async () => myResources, getResourceContent: async ({ uri }) => { if (myResourceContents[uri]) { return myResourceContents[uri] } throw new Error(`Resource content not found for ${uri}`) }, resourceTemplates: async () => myResourceTemplates, } const serverWithResources = new MCPServer({ id: 'resourceful-server', name: 'Resourceful Server', version: '1.0.0', tools: {/* ... your tools ... */}, resources: myResourceHandlers, }) ``` ### 通知用戶端 Resource 變更 如果可用 Resources 或其內容有變,伺服器可通知已連接並訂閱指定 Resource 的用戶端。 #### `server.resources.notifyUpdated({ uri: string })` 指定 Resource(由其 `uri` 識別)的內容更新時,請呼叫此方法。已訂閱此 URI 的用戶端會收到 `notifications/resources/updated` 訊息。 ```typescript async server.resources.notifyUpdated({ uri: string }): Promise ``` 範例: ```typescript // After updating the content of 'file://data.txt' await serverWithResources.resources.notifyUpdated({ uri: 'file://data.txt' }) ``` #### `server.resources.notifyListChanged()` 可用 Resources 清單有變(例如新增或移除 Resource)時,請呼叫此方法。這會向用戶端傳送 `notifications/resources/list_changed` 訊息,提示它們重新擷取 Resources 清單。 ```typescript async server.resources.notifyListChanged(): Promise ``` 範例: ```typescript // After adding a new resource to the list managed by 'myResourceHandlers.listResources' await serverWithResources.resources.notifyListChanged() ``` ## Prompt 處理 ### 甚麼是 MCP Prompts? Prompts 是 MCP 伺服器向用戶端公開的可重用範本或 Workflows。它們可接受引數、包含 Resource context,亦支援版本控制並將 LLM 互動標準化。 Prompts 以獨有名稱(及選擇性版本)識別,並可在執行階段定義或設為靜態。 ### `MCPServerPrompts` 類型 `prompts` 選項接受 `MCPServerPrompts` 類型的物件。此類型定義伺服器處理 Prompt 請求時使用的回呼函式: ```typescript export type MCPServerPrompts = { // Callback to list available prompts listPrompts: () => Promise // Callback to get the messages/content for a specific prompt getPromptMessages?: ({ name, version, args, }: { name: string version?: string args?: any }) => Promise<{ prompt: Prompt; messages: PromptMessage[] }> } ``` 範例: ```typescript import { MCPServer } from '@mastra/mcp' import type { Prompt, PromptMessage, MCPServerPrompts } from '@mastra/mcp' const prompts: Prompt[] = [ { name: 'analyze-code', description: 'Analyze code for improvements', version: 'v1', }, { name: 'analyze-code', description: 'Analyze code for improvements (new logic)', version: 'v2', }, ] const myPromptHandlers: MCPServerPrompts = { listPrompts: async () => prompts, getPromptMessages: async ({ name, version, args }) => { if (name === 'analyze-code') { if (version === 'v2') { const prompt = prompts.find(p => p.name === name && p.version === 'v2') if (!prompt) throw new Error('Prompt version not found') return { prompt, messages: [ { role: 'user', content: { type: 'text', text: `Analyze this code with the new logic: ${args.code}`, }, }, ], } } // Default or v1 const prompt = prompts.find(p => p.name === name && p.version === 'v1') if (!prompt) throw new Error('Prompt version not found') return { prompt, messages: [ { role: 'user', content: { type: 'text', text: `Analyze this code: ${args.code}` }, }, ], } } throw new Error('Prompt not found') }, } const serverWithPrompts = new MCPServer({ id: 'promptful-server', name: 'Promptful Server', version: '1.0.0', tools: {/* ... */}, prompts: myPromptHandlers, }) ``` ### 通知用戶端 Prompt 變更 如果可用 Prompts 有變,伺服器可通知已連接的用戶端: #### `server.prompts.notifyListChanged()` 可用 Prompts 清單有變(例如新增或移除 Prompt)時,請呼叫此方法。這會向用戶端傳送 `notifications/prompts/list_changed` 訊息,提示它們重新擷取 Prompts 清單。 ```typescript await serverWithPrompts.prompts.notifyListChanged() ``` ### Prompt 處理最佳做法 - 使用清晰且具描述性的 Prompt 名稱及描述。 - 驗證 `getPromptMessages` 中所有必要引數。 - 如果預期會作出重大變更,請加入 `version` 欄位。 - 使用 `version` 參數選擇正確的 Prompt 邏輯。 - Prompt 清單有變時通知用戶端。 - 使用資訊清楚的訊息處理錯誤。 - 記錄預期引數及可用版本。 ## 動態 Tool 管理 Tools 通常在建構 `MCPServer` 時提供,但你亦可在伺服器運行期間新增或移除 Tools。伺服器透過 `toolActions` 屬性公開這些操作。Tool 清單有變時,已連接的用戶端會收到 `notifications/tools/list_changed` 訊息,提示它們重新擷取 Tool 清單。 此屬性名為 `toolActions`,因為 `tools()` 是傳回已註冊 Tool registry 的方法。 ### `toolActions.add(tools)` 在運行中的伺服器註冊新 Tools,並通知已連接的用戶端。Tools 使用其 record key 作為鍵,與傳給建構函式的 Tools 相同。在現有鍵下新增 Tool 會取代原有 Tool。 ```typescript async server.toolActions.add(tools: ToolsInput): Promise ``` 範例: ```typescript import { createTool } from '@mastra/core/tools' import { z } from 'zod' const searchTool = createTool({ id: 'search', description: 'Searches the knowledge base.', inputSchema: z.object({ query: z.string() }), execute: async ({ query }) => ({ results: [] }), }) await server.toolActions.add({ searchTool }) ``` ### `toolActions.remove(toolIds)` 按 Tool ID 從運行中的伺服器移除 Tools,並通知已連接的用戶端。不明的 Tool ID 會被忽略。只有在至少移除一個 Tool 時才會傳送通知。 ```typescript async server.toolActions.remove(toolIds: string[]): Promise ``` 範例: ```typescript await server.toolActions.remove(['searchTool']) ``` ### `toolActions.notifyListChanged()` 向已連接的用戶端傳送 `notifications/tools/list_changed` 訊息,而不修改 Tool registry。Tool 可用性透過其他方式變更(例如授權變更)時,請呼叫此方法。 ```typescript async server.toolActions.notifyListChanged(): Promise ``` ### Mastra registry 同步 伺服器向 Mastra 實例註冊後,`toolActions.add()` 及 `toolActions.remove()` 亦會更新 Mastra 實例的 Tool registry,與啟動時自動註冊 Tools 的行為一致。新增的 Tools 可透過 `mastra.listTools()` 使用(如有內在 `id`,則以它作為鍵),而移除的 Tools 則會從 registry 刪除。 ## 記錄 MCP 伺服器可使用 `notifications/message` 向用戶端傳送結構化記錄訊息。用戶端透過傳送 `logging/setLevel` 請求控制詳細程度。伺服器會捨棄低於所要求最低級別的訊息(依照 RFC 5424 嚴重程度排序)。級別按 session 追蹤,因此不同用戶端可要求不同的詳細程度。 ### `sendLoggingMessage()` 向所有已連接的用戶端傳送記錄通知,並遵從各用戶端的最低記錄級別。 ```typescript async server.sendLoggingMessage(params: { level: LoggingLevel; data: unknown; logger?: string; }): Promise ``` 範例: ```typescript await server.sendLoggingMessage({ level: 'info', data: { message: 'Sync completed', itemsProcessed: 42 }, }) ``` ### `context.mcp.log()` 在 Tool 的 `execute` 函式內,使用 `context.mcp.log()` 向呼叫該 Tool 的用戶端傳送記錄訊息。 ```typescript async context.mcp.log( level: LoggingLevel, message: string, data?: Record ): Promise ``` 範例: ```typescript execute: async ({ location }, context) => { await context.mcp.log('debug', 'Fetching weather', { location }) const weather = await fetchWeather(location) await context.mcp.log('info', 'Weather fetched') return weather } ``` ## 進度通知 長時間運行的 Tools 可使用 `notifications/progress` 向呼叫用戶端報告進度。只有當呼叫者在請求中加入 `progressToken` 以要求追蹤進度時,才會傳送進度(Mastra `MCPClient` 設置 `enableProgressTracking` 時會這樣做)。如未傳送 token,`context.mcp.progress()` 不會執行任何操作。 ### `context.mcp.progress()` ```typescript async context.mcp.progress(params: { progress: number; total?: number; message?: string; }): Promise ``` 範例: ```typescript execute: async ({ items }, context) => { for (const [index, item] of items.entries()) { await processItem(item) await context.mcp.progress({ progress: index + 1, total: items.length, message: `Processed ${item.name}`, }) } return { done: true } } ``` ## 通知傳送 通知方法(`resources.notifyListChanged()`、`prompts.notifyListChanged()`、`toolActions.notifyListChanged()` 及 `sendLoggingMessage()`)會透過所有傳輸向每個已連接的用戶端廣播:包括 stdio/SSE 連線及每個可串流 HTTP session。`resources.notifyUpdated()` 是例外;它只會通知透過 `resources/subscribe` 訂閱 Resource URI 的用戶端。對於可串流 HTTP 用戶端,訂閱會按 session 追蹤;舊版 SSE 用戶端共用主要伺服器實例,因此亦共用同一組訂閱。使用無狀態 serverless 模式的用戶端無法接收通知,因為每項請求都使用暫時的伺服器實例。 ## 範例 如需設定及部署 MCPServer 的實用範例,請參閱[發佈 MCP Server 指南](https://mastra.zisheng.pro/zh-HK/guides/guide/publishing-mcp-server)。 本頁開首的範例亦示範如何使用 Tools 及 Agents 實例化 `MCPServer`。 ## Elicitation ### 甚麼是 Elicitation? Elicitation 是 Model Context Protocol (MCP) 的一項功能,讓伺服器可向使用者要求結構化資料。它支援伺服器在執行階段收集額外資料的互動式 Workflows。 `MCPServer` 類別自動包含 Elicitation 功能。Tools 會在其 `execute` 函式中收到 `context.mcp` 物件,當中包含用於要求使用者輸入的 `elicitation.sendRequest()` 方法。 ### Tool 執行簽署 Tools 在 MCP 伺服器 context 中執行時,會透過 `context.mcp` 物件接收 MCP 專用功能: ```typescript execute: async (inputData, context) => { // input contains the tool's inputData parameters // context.mcp contains server capabilities like elicitation and authentication info // Access authentication information (when available) if (context.mcp?.extra?.authInfo) { console.log('Authenticated request from:', context.mcp.extra.authInfo.clientId) } // Use elicitation capabilities const result = await context.mcp.elicitation.sendRequest({ message: 'Please provide information', requestedSchema: {/* schema */}, }) return result } ``` ### Elicitation 的運作方式 常見使用情境是在 Tool 執行期間需要使用者輸入時,透過 context 參數所提供的 Elicitation 功能取得輸入: 1. Tool 使用訊息及 schema 呼叫 `context.mcp.elicitation.sendRequest()` 2. 請求傳送至已連接的 MCP 用戶端 3. 用戶端向使用者顯示請求(透過 UI、命令列等) 4. 使用者提供輸入、拒絕或取消請求 5. 用戶端將回應傳回伺服器 6. Tool 收到回應並繼續執行 ### 在 Tools 中使用 Elicitation 以下範例展示使用 Elicitation 收集使用者聯絡資料的 Tool: ```typescript import { MCPServer } from '@mastra/mcp' import { createTool } from '@mastra/core/tools' import { z } from 'zod' const server = new MCPServer({ id: 'interactive-server', name: 'Interactive Server', version: '1.0.0', tools: { collectContactInfo: createTool({ id: 'collectContactInfo', description: 'Collects user contact information through elicitation', inputSchema: z.object({ reason: z.string().optional().describe('Reason for collecting contact info'), }), execute: async (inputData, context) => { const { reason } = inputData // Log session info if available console.log('Request from session:', context.mcp?.extra?.sessionId) try { // Request user input via elicitation const result = await context.mcp.elicitation.sendRequest({ message: reason ? `Please provide your contact information. ${reason}` : 'Please provide your contact information', requestedSchema: { type: 'object', properties: { name: { type: 'string', title: 'Full Name', description: 'Your full name', }, email: { type: 'string', title: 'Email Address', description: 'Your email address', format: 'email', }, phone: { type: 'string', title: 'Phone Number', description: 'Your phone number (optional)', }, }, required: ['name', 'email'], }, }) // Handle the user's response if (result.action === 'accept') { return `Contact information collected: ${JSON.stringify(result.content, null, 2)}` } else if (result.action === 'decline') { return 'Contact information collection was declined by the user.' } else { return 'Contact information collection was cancelled by the user.' } } catch (error) { return `Error collecting contact information: ${error}` } }, }), }, }) ``` ### Elicitation 請求 Schema `requestedSchema` 必須是僅包含基本類型屬性的扁平物件。支援的類型包括: - **字串**:`{ type: 'string', title: 'Display Name', description: 'Help text' }` - **數字**:`{ type: 'number', minimum: 0, maximum: 100 }` - **布林值**:`{ type: 'boolean', default: false }` - **列舉**:`{ type: 'string', enum: ['option1', 'option2'] }` 範例 schema: ```typescript { type: 'object', properties: { name: { type: 'string', title: 'Full Name', description: 'Your complete name', }, age: { type: 'number', title: 'Age', minimum: 18, maximum: 120, }, newsletter: { type: 'boolean', title: 'Subscribe to Newsletter', default: false, }, }, required: ['name'], } ``` ### 回應動作 使用者可用三種方式回應 Elicitation 請求: 1. **接受** (`action: 'accept'`):使用者提供資料並確認提交 - 包含帶有所提交資料的 `content` 欄位 2. **拒絕** (`action: 'decline'`):使用者明確拒絕提供資料 - 不含 content 欄位 3. **取消** (`action: 'cancel'`):使用者未作決定便關閉請求 - 不含 content 欄位 Tools 應妥善處理三種回應類型。 ### 保安考慮 - **切勿要求敏感資料**,例如密碼、社會保障號碼或信用卡號碼 - 根據所提供的 schema 驗證所有使用者輸入 - 妥善處理拒絕及取消 - 清楚說明收集資料的原因 - 尊重使用者私隱及偏好 ### Tool 執行 API Tool 執行時,可透過 `options` 參數使用 Elicitation 功能: ```typescript // Within a tool's execute function execute: async (inputData, context) => { // Use elicitation for user input const result = await context.mcp.elicitation.sendRequest({ message: string, // Message to display to user requestedSchema: object // JSON schema defining expected response structure }): Promise // Access authentication info if needed if (context.mcp?.extra?.authInfo) { // Use context.mcp.extra.authInfo.token, etc. } } ``` 使用 HTTP 傳輸(SSE 或 HTTP)時,Elicitation **可識別 session**。多個用戶端連接至同一伺服器時,Elicitation 請求會路由至發起 Tool 執行的用戶端 session。 `ElicitResult` 類型: ```typescript type ElicitResult = { action: 'accept' | 'decline' | 'cancel' content?: any // Only present when action is 'accept' } ``` ## OAuth 保護 要按照 [MCP Auth Specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) 使用 OAuth 驗證保護 MCP 伺服器,請使用 `createOAuthMiddleware` 函式: ```typescript import http from 'node:http' import { MCPServer, createOAuthMiddleware, createStaticTokenValidator } from '@mastra/mcp' const mcpServer = new MCPServer({ id: 'protected-server', name: 'Protected MCP Server', version: '1.0.0', tools: {/* your tools */}, }) // Create OAuth middleware const oauthMiddleware = createOAuthMiddleware({ oauth: { resource: 'https://mcp.example.com/mcp', authorizationServers: ['https://auth.example.com'], scopesSupported: ['mcp:read', 'mcp:write'], resourceName: 'My Protected MCP Server', validateToken: createStaticTokenValidator(['allowed-token-1']), }, mcpPath: '/mcp', }) // Create HTTP server with OAuth protection const httpServer = http.createServer(async (req, res) => { const url = new URL(req.url || '', 'https://mcp.example.com') // Apply OAuth middleware first const result = await oauthMiddleware(req, res, url) if (!result.proceed) return // Middleware handled response (401, metadata, etc.) // Token is valid, proceed to MCP handler await mcpServer.startHTTP({ url, httpPath: '/mcp', req, res }) }) httpServer.listen(3000) ``` middleware 會自動: - 在 `/.well-known/oauth-protected-resource` 提供 **Protected Resource Metadata** (RFC 9728) - 需要驗證時,傳回帶有適當 `WWW-Authenticate` header 的 `401 Unauthorized` - 使用你提供的 validator 驗證 bearer token ### Token 驗證 在正式環境中,請使用適當的 token 驗證: ```typescript import { createOAuthMiddleware, createIntrospectionValidator } from '@mastra/mcp' // Option 1: Token introspection (RFC 7662) const middleware = createOAuthMiddleware({ oauth: { resource: 'https://mcp.example.com/mcp', authorizationServers: ['https://auth.example.com'], validateToken: createIntrospectionValidator('https://auth.example.com/oauth/introspect', { clientId: 'mcp-server', clientSecret: 'secret', }), }, }) // Option 2: Custom validation (JWT, database lookup, etc.) const customMiddleware = createOAuthMiddleware({ oauth: { resource: 'https://mcp.example.com/mcp', authorizationServers: ['https://auth.example.com'], validateToken: async (token, resource) => { const decoded = await verifyJWT(token) if (!decoded) { return { valid: false, error: 'invalid_token' } } return { valid: true, scopes: decoded.scope?.split(' ') || [], subject: decoded.sub, } }, }, }) ``` ### OAuth Middleware 選項 **oauth.resource** (`string`): MCP 伺服器的 canonical URL。此 URL 會在 Protected Resource Metadata 中傳回。 **oauth.authorizationServers** (`string[]`): 可為此 Resource 發出 token 的授權伺服器 URL。 **oauth.scopesSupported** (`string[]`): 此 MCP 伺服器支援的 scopes。 (Default: `['mcp:read', 'mcp:write']`) **oauth.resourceName** (`string`): 此 Resource 伺服器便於閱讀的名稱。 **oauth.validateToken** (`(token: string, resource: string) => Promise`): 驗證 access token 的函式。如未提供,系統會接受 token 而不作驗證(不建議在正式環境使用)。 **mcpPath** (`string`): 提供 MCP 端點的路徑。只有此路徑的請求需要驗證。 (Default: `'/mcp'`) ## 驗證 context 使用 HTTP 傳輸時,Tools 可透過 `context.mcp.extra` 存取請求 metadata。這讓你可將驗證資料、使用者 context 或任何自訂資料,從 HTTP middleware 傳給 MCP Tools。 ### 運作方式 你在 HTTP middleware 的 `req.auth` 上設置的任何內容,都可在 Tools 中透過 `context.mcp.extra.authInfo` 使用: ```text req.auth = { ... } → context?.mcp?.extra?.authInfo.extra = { ... } ``` ### 為 FGA 映射驗證資料 當 `MCPServer` 在具有 fine-grained authorization (FGA) Provider 的 Mastra 實例上註冊時,Mastra 會在列出或呼叫 Tools 前檢查 `requestContext.get('user')`。HTTP MCP 傳輸會以 `extra.authInfo` 傳遞已驗證資料,因此請使用 `mapAuthInfoToUser` 設置 FGA Provider 預期的使用者結構。 ```typescript const server = new MCPServer({ id: 'my-server', name: 'My Server', version: '1.0.0', tools: { getUserData }, mapAuthInfoToUser: ({ authInfo }) => { const user = authInfo as { extra?: { userId?: string organizationMembershipId?: string } } if (!user.extra?.userId) { return null } return { id: user.extra.userId, organizationMembershipId: user.extra.organizationMembershipId, } }, }) ``` ### 個別設定 MCP Tool 的 FGA 範圍 當 MCP 用戶端所需的授權範圍與內部 agent 或 Workflow Tool 執行不同時,請使用 `fga.resourceMapping` 及 `fga.permissionMapping`。覆寫只套用至此 MCP 伺服器的 `tools/list` 及 `tools/call` 檢查。 ```typescript import { MastraFGAPermissions } from '@mastra/core/auth/ee' const server = new MCPServer({ id: 'my-server', name: 'My Server', version: '1.0.0', tools: { getUserData }, mapAuthInfoToUser: ({ authInfo }) => { const user = authInfo as { extra?: { userId?: string organizationMembershipId?: string } } if (!user.extra?.userId) { return null } return { id: user.extra.userId, organizationMembershipId: user.extra.organizationMembershipId, } }, fga: { resourceMapping: { tool: { fgaResourceType: 'user', deriveId: ({ user }) => (user as { id: string }).id, }, }, permissionMapping: { [MastraFGAPermissions.TOOLS_EXECUTE]: 'read', }, }, }) ``` ### 設定驗證 Middleware 要將資料傳給 Tools,請先在 HTTP 伺服器 middleware 的 Node.js 請求物件上填入 `req.auth`,然後才呼叫 `server.startHTTP()`。 ```typescript import express from 'express' type MCPAuthenticatedRequest = express.Request & { auth?: { token: string clientId: string scopes: string[] expiresAt?: number extra?: Record } } const app = express() // Auth middleware - set req.auth before the MCP handler app.use('/mcp', async (req, res, next) => { const authorization = req.headers.authorization if (!authorization?.startsWith('Bearer ')) { res.status(401).json({ error: 'Missing bearer token' }) return } const token = authorization.slice('Bearer '.length) try { const user = await verifyToken(token) // This entire object becomes context.mcp.extra.authInfo const authenticatedRequest = req as MCPAuthenticatedRequest authenticatedRequest.auth = { token, clientId: user.clientId, scopes: user.scopes, expiresAt: user.expiresAt, extra: { userId: user.userId, email: user.email, }, } next() } catch { res.status(401).json({ error: 'Invalid or expired token' }) } }) app.all('/mcp', async (req, res) => { const url = new URL(req.url, `http://${req.headers.host}`) await server.startHTTP({ url, httpPath: '/mcp', req, res }) }) ``` ### 在 Tools 中存取驗證資料 在 Tool 的 execute 函式中,可透過 `context.mcp.extra.authInfo` 使用 `req.auth` 物件: ```typescript execute: async (inputData, context) => { // Access the auth data you set in middleware const authInfo = context?.mcp?.extra?.authInfo if (!authInfo?.extra?.userId) { return { error: 'Authentication required' } } // Use the auth data console.log('User ID:', authInfo.extra.userId) console.log('Email:', authInfo.extra.email) const response = await fetch('/api/data', { headers: { Authorization: `Bearer ${authInfo.token}` }, signal: context?.mcp?.extra?.signal, }) return response.json() } ``` ### 將 `RequestContext` 傳遞給 agent ```typescript execute: async (inputData, context) => { // Access the auth data you set in middleware const authInfo = context?.mcp?.extra?.authInfo const requestContext = context.requestContext || new RequestContext().set('someKey', authInfo) if (!authInfo?.extra?.userId) { return { error: 'Authentication required' } } // Use the auth data console.log('User ID:', authInfo.extra.userId) console.log('Email:', authInfo.extra.email) const agent = context?.mastra?.getAgentById('some-agent-id') if (!agent) { return { error: "Agent 'some-agent-id' not found" } } const response = await agent.generate(prompt, { requestContext }) return response.text } ``` ### `extra` 物件 完整的 `context.mcp.extra` 物件包含: | 屬性 | 描述 | | ------------------ | --------------------------------- | | `authInfo` | 你在 middleware 的 `req.auth` 上設置的內容 | | `sessionId` | MCP 連線的 session 識別碼 | | `signal` | 用於取消請求的 AbortSignal | | `sendNotification` | 用於傳送通知的 MCP protocol 函式 | | `sendRequest` | 用於傳送請求的 MCP protocol 函式 | ### 完整範例 安裝 [`jose`](https://github.com/panva/jose),以根據身份提供者的 JSON Web Key Set (JWKS) 驗證 JSON Web Tokens (JWTs): **npm**: ```shell npm install jose ``` **pnpm**: ```shell pnpm add jose ``` **Yarn**: ```shell yarn add jose ``` **Bun**: ```shell bun add jose ``` 以下範例會驗證 token 的簽署、issuer、audience、algorithm、到期時間及必要 claims,然後才將其使用者資料傳給 Tool: ```typescript import express from 'express' import { createRemoteJWKSet, jwtVerify } from 'jose' import { MCPServer } from '@mastra/mcp' import { createTool } from '@mastra/core/tools' import { z } from 'zod' type MCPAuthenticatedRequest = express.Request & { auth?: { token: string clientId: string scopes: string[] expiresAt?: number extra?: Record } } const issuer = process.env.JWT_ISSUER const audience = process.env.JWT_AUDIENCE const jwksUri = process.env.JWT_JWKS_URI if (!issuer || !audience || !jwksUri) { throw new Error('JWT_ISSUER, JWT_AUDIENCE, and JWT_JWKS_URI are required') } const jwks = createRemoteJWKSet(new URL(jwksUri)) const verifyToken = async (token: string) => { const { payload } = await jwtVerify(token, jwks, { issuer, audience, algorithms: ['RS256'], requiredClaims: ['exp'], }) const clientId = typeof payload.client_id === 'string' ? payload.client_id : typeof payload.azp === 'string' ? payload.azp : undefined if (!payload.sub || typeof payload.email !== 'string' || !clientId || !payload.exp) { throw new Error('Token must contain sub, email, exp, and client_id or azp claims') } return { userId: payload.sub, clientId, email: payload.email, expiresAt: payload.exp, scopes: typeof payload.scope === 'string' ? payload.scope.split(' ') : [], } } // 1. Define your tool that uses auth context const getUserData = createTool({ id: 'get-user-data', description: 'Fetches data for the authenticated user', inputSchema: z.object({}), execute: async (inputData, context) => { const authInfo = context?.mcp?.extra?.authInfo if (!authInfo?.extra?.userId) { return { error: 'Authentication required' } } // Access the data you set in middleware return { userId: authInfo.extra.userId, email: authInfo.extra.email, } }, }) // 2. Create the MCP server with your tools const server = new MCPServer({ id: 'my-server', name: 'My Server', version: '1.0.0', tools: { getUserData }, }) // 3. Set up Express with auth middleware const app = express() app.use('/mcp', async (req, res, next) => { const authorization = req.headers.authorization if (!authorization?.startsWith('Bearer ')) { res.status(401).json({ error: 'Missing bearer token' }) return } const token = authorization.slice('Bearer '.length) try { const user = await verifyToken(token) // This entire object becomes context.mcp.extra.authInfo const authenticatedRequest = req as MCPAuthenticatedRequest authenticatedRequest.auth = { token, clientId: user.clientId, scopes: user.scopes, expiresAt: user.expiresAt, extra: { userId: user.userId, email: user.email, }, } next() } catch { res.status(401).json({ error: 'Invalid or expired token' }) } }) app.all('/mcp', async (req, res) => { const url = new URL(req.url, `http://${req.headers.host}`) await server.startHTTP({ url, httpPath: '/mcp', req, res }) }) app.listen(3000) ``` ## MCP Apps (`appResources`) `appResources` 選項讓你透過 [MCP Apps extension](https://github.com/modelcontextprotocol/ext-apps),從 MCP 伺服器提供互動式 HTML UI。每個項目會將 `ui://` URI 映射至在 Mastra Studio 沙盒 iframe 中呈現的 HTML app。 ### `AppResources` 類型 **Key (URI)** (`string`): 識別 app Resource 的 ui:// URI(例如 ui://calculator/main)。 每個值都是 `AppResource` 物件: **name** (`string`): UI Resource 的顯示名稱。 **description** (`string`): UI Resource 的選擇性描述。 **html** (`string`): UI 的 inline HTML 內容。請提供 html 或 htmlPath。 **htmlPath** (`string`): HTML 檔案的路徑,在伺服器啟動時解析。請提供 html 或 htmlPath。 **meta** (`McpUiResourceMeta`): 來自官方 ext-apps SDK 的 UI Resource metadata(CSP、權限、呈現偏好)。 ### 範例 ```typescript import { MCPServer } from '@mastra/mcp' import { createTool } from '@mastra/core/tools' import { z } from 'zod' const calculatorTool = createTool({ id: 'calculatorWithUI', description: 'An interactive calculator', inputSchema: z.object({ num1: z.number(), num2: z.number(), operation: z.enum(['add', 'subtract']), }), execute: async ({ num1, num2, operation }) => { const result = operation === 'add' ? num1 + num2 : num1 - num2 return { content: [{ type: 'text', text: 'An interactive calculator is displayed.' }], structuredContent: { result }, } }, }) const server = new MCPServer({ id: 'app-server', name: 'App Server', version: '1.0.0', tools: { calculatorTool }, appResources: { 'ui://calculator/main': { name: 'Interactive Calculator', html: '

Calculator

...', }, }, }) ``` 將 Tool 上的 `_meta.ui.resourceUri` 設置為相符的 `ui://` URI,即可連結 Tool 與其 app Resource。伺服器註冊 Tools 時會自動將此 metadata 標準化。請瀏覽 [MCP Apps](https://mastra.zisheng.pro/zh-HK/docs/mcp/overview),了解完整 app bridge API 及使用模式。 ## 相關資料 - 如要在 Mastra 中連接 MCP 伺服器,請參閱 [MCPClient 文件](https://mastra.zisheng.pro/zh-HK/reference/tools/mcp-client)。 - 如要進一步了解 Model Context Protocol,請參閱 [@modelcontextprotocol/sdk 文件](https://github.com/modelcontextprotocol/typescript-sdk)。