> Discover all available pages from the documentation index: https://mastra.zisheng.pro/zh-TW/llms.txt # MCPServer `MCPServer` 類別可將現有 Mastra Tool 與 Agent 公開為 Model Context Protocol(MCP)server,讓任何 MCP client(例如 Cursor、Windsurf 或 Claude Desktop)連線並將這些功能提供給 Agent。 請注意,若只需在 Mastra 應用程式中直接使用 Tool 或 Agent,不一定要建立 MCP server。此 API 專門用於向\_外部\_ MCP client 公開 Mastra Tool 與 Agent。 它同時支援 [stdio(subprocess)與 SSE(HTTP)MCP transport](https://modelcontextprotocol.io/docs/concepts/transports)。 ## Constructor 若要建立新的 `MCPServer`,需要提供 server 的基本資訊、server 將提供的 Tool,以及選填的要公開為 Tool 的 Agent。 ```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" }, }) ``` ### 設定屬性 Constructor 接受具有下列屬性的 `MCPServerConfig` 物件: **id** (`string`): Server 的唯一識別碼。Server 註冊至 Mastra 時會保留此 ID,並可透過 getMCPServerById() 擷取 server。 **name** (`string`): Server 的描述性名稱(例如 'My Weather and Agent Server')。 **version** (`string`): Server 的 semantic version(例如 '1.0.0')。 **tools** (`ToolsInput`): Key 為 Tool 名稱、值為 Mastra Tool 定義(使用 createTool 或 Vercel AI SDK 建立)的物件。這些 Tool 會直接公開。 **agents** (`Record`): Key 為 Agent 識別碼、值為 Mastra Agent instance 的物件。每個 Agent 都會自動轉換為名為 ask\_\ 的 Tool。Agent constructor 設定中\*\*必須\*\*定義非空白的 description 字串屬性,此內容會用於 Tool 說明。若 Agent 的 description 缺少或為空白,MCPServer 初始化期間會擲回錯誤。 **workflows** (`Record`): Key 為 Workflow 識別碼、值為 Mastra Workflow instance 的物件。每個 Workflow 都會轉換為名為 run\_\ 的 Tool。Workflow 的 inputSchema 會成為 Tool 的輸入 schema。Workflow \*\*必須\*\*具有非空白的 description 字串屬性,供 Tool 說明使用。若 Workflow 的 description 缺少或為空白,則會擲回錯誤。Tool 會先呼叫 workflow\.createRun(),再呼叫 run.start({ inputData: \ }) 以執行 Workflow。若衍生自 Agent 或 Workflow 的 Tool 名稱(例如 ask\_myAgent 或 run\_myWorkflow)與明確定義的 Tool 名稱或其他衍生名稱衝突,明確定義的 Tool 優先,且系統會記錄警告。造成後續衝突的 Agent/Workflow 會略過。 **description** (`string`): MCP server 功能的選填說明。 **instructions** (`string`): 描述 server 及其功能使用方式的選填 instructions。 **mapAuthInfoToUser** (`({ authInfo, extra, requestContext }) => unknown | null | undefined | Promise`): 將 extra.authInfo 中的 MCP transport 驗證資料對應至 Mastra FGA 檢查使用的 user 值。當受 OAuth 保護的 MCP server 註冊至具有 FGA Provider 的 Mastra instance 時,請使用此屬性。 **fga** (`{ resourceMapping?: Partial string | undefined }>>; permissionMapping?: Record }`): 覆寫此 MCP server tools/list 與 tools/call FGA 檢查的資源和權限對應。當 MCP authorization 的 scope 應不同於內部 Agent 或 Workflow Tool 執行時,請使用此屬性。 **repository** (`Repository`): Server 原始碼的選填 repository 資訊。 **releaseDate** (`string`): 此 server 版本的選填發布日期(ISO 8601 字串)。若未提供,則預設為建立 instance 的時間。 **isLatest** (`boolean`): 指出這是否為最新版本的選填旗標。若未提供,則預設為 true。 **packageCanonical** (`'npm' | 'docker' | 'pypi' | 'crates' | string`): Server 以套件形式發布時使用的選填標準封裝格式(例如 'npm'、'docker')。 **packages** (`PackageInfo[]`): 此 server 的選填可安裝套件清單。 **remotes** (`RemoteInfo[]`): 此 server 的選填遠端存取點清單。 **resources** (`MCPServerResources`): 定義 server 應如何處理 MCP 資源的物件。詳情請參閱「資源處理」一節。 **prompts** (`MCPServerPrompts`): 定義 server 應如何處理 MCP prompt 的物件。詳情請參閱「Prompt 處理」一節。 **appResources** (`AppResources`): 將 ui:// URI 對應至 app resource 設定的 map。每個項目都定義透過 MCP Apps extension(SEP-1865)提供的互動式 HTML UI。詳情請參閱 MCP Apps 一節。 ## 將 Agent 公開為 Tool `MCPServer` 的強大功能之一,是能自動將 Mastra Agent 公開為可呼叫的 Tool。在設定的 `agents` 屬性中提供 Agent 時: - **Tool 命名**:每個 Agent 都會轉換為名為 `ask_` 的 Tool,其中 `` 是該 Agent 在 `agents` 物件中使用的 key。例如,若設定 `agents: { myAgentKey: myAgentInstance }`,系統會建立名為 `ask_myAgentKey` 的 Tool。 - **Tool 功能**: - **說明**:產生的 Tool 說明格式為:"Ask agent `` a question. Original agent instructions: ``"。 - **輸入**:Tool 預期收到具有 `message` 屬性(字串)的單一物件引數:`{ message: "Your question for the agent" }`。 - **執行**:呼叫此 Tool 時,會使用提供的 `query` 叫用對應 Agent 的 `generate()` 方法。 - **輸出**:Agent `generate()` 方法的直接結果會作為 Tool 輸出回傳。 - **名稱衝突。** 若 `tools` 設定中明確定義的 Tool 與 Agent 衍生 Tool 同名(例如名為 `ask_myAgentKey` 的 Tool 與 key 為 `myAgentKey` 的 Agent 並存),則\_明確定義的 Tool 優先\_。發生衝突時,不會將 Agent 轉換為 Tool,且系統會記錄警告。 如此一來,MCP client 便能像使用其他 Tool 一樣,以自然語言查詢與 Agent 互動。 ### 將 Agent 轉換為 Tool 在 `agents` 設定屬性中提供 Agent 時,`MCPServer` 會自動為每個 Agent 建立對應 Tool。Tool 名稱為 `ask_`,其中 `` 是 `agents` 物件中使用的 key。 產生的 Tool 說明為:"Ask agent `` a question. Agent description: ``"。 若要將 Agent 轉換為 Tool,建立 instance 時的設定中**必須**具有非空白的 `description` 字串屬性(例如 `new Agent({ id: 'my-agent', name: 'myAgent', description: 'This agent does X.', ... })`)。若傳給 `MCPServer` 的 Agent 缺少 `description` 或其值為空白,建立 `MCPServer` instance 時會擲回錯誤,且 server 設定會失敗。 這可讓你快速透過 MCP 公開 Agent 的生成能力,讓 client 能直接向 Agent「提問」。 ### 在 Tool 中存取 MCP context 透過 `MCPServer` 公開的 Tool 可根據呼叫方式,使用兩種不同屬性存取 MCP request 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` instance 上呼叫,以控制其行為並取得資訊。 ### `startStdio()` 使用此方法啟動 server,讓它透過標準輸入與輸出(stdio)通訊。以命令列程式執行 server 時通常會使用此方式。 ```typescript async startStdio(): Promise ``` 以下示範如何使用 stdio 啟動 server: ```typescript const server = new MCPServer({ id: 'my-server', name: 'My Server', version: '1.0.0', tools: {/* ... */}, }) await server.startStdio() ``` ### `startSSE()` 此方法可將 MCP server 與現有 Web server 整合,並使用 Server-Sent Events(SSE)通訊。Web server 收到 SSE 或 message 路徑請求時,請從 Web server 程式碼呼叫此方法。 ```typescript async startSSE({ url, ssePath, messagePath, req, res, }: { url: URL; ssePath: string; messagePath: string; req: any; res: any; }): Promise ``` 下列範例示範如何在 HTTP server request handler 中使用 `startSSE`。在此範例中,MCP client 可透過 `http://localhost:1234/sse` 連線至 MCP server: ```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`): 使用者要求的 Web 位址。 **ssePath** (`string`): Client 連線至 SSE 的特定 URL 部分(例如 '/sse')。 **messagePath** (`string`): Client 傳送訊息的特定 URL 部分(例如 '/message')。 **req** (`any`): 來自 Web server 的傳入 request 物件。 **res** (`any`): 來自 Web server、用於傳回資料的 response 物件。 ### `startHonoSSE()` 此方法可將 MCP server 與現有 Web server 整合,並使用 Server-Sent Events(SSE)通訊。Web server 收到 SSE 或 message 路徑請求時,請從 Web server 程式碼呼叫此方法。 ```typescript async startHonoSSE({ url, ssePath, messagePath, req, res, }: { url: URL; ssePath: string; messagePath: string; req: any; res: any; }): Promise ``` 下列範例示範如何在 HTTP server request handler 中使用 `startHonoSSE`。在此範例中,MCP client 可透過 `http://localhost:1234/hono-sse` 連線至 MCP server: ```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`): 使用者要求的 Web 位址。 **ssePath** (`string`): Client 連線至 SSE 的特定 URL 部分(例如 '/hono-sse')。 **messagePath** (`string`): Client 傳送訊息的特定 URL 部分(例如 '/message')。 **req** (`any`): 來自 Web server 的傳入 request 物件。 **res** (`any`): 來自 Web server、用於傳回資料的 response 物件。 ### `startHTTP()` 此方法可將 MCP server 與現有 Web server 整合,並使用 streamable HTTP 通訊。Web server 收到 HTTP 請求時,請從 Web server 程式碼呼叫此方法。 ```typescript async startHTTP({ url, httpPath, req, res, options = { sessionIdGenerator: () => randomUUID() }, }: { url: URL; httpPath: string; req: http.IncomingMessage; res: http.ServerResponse; options?: StreamableHTTPServerTransportOptions; }): Promise ``` 下列範例示範如何在 HTTP server request handler 中使用 `startHTTP`。在此範例中,MCP client 可透過 `http://localhost:1234/http` 連線至 MCP server: ```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:** 部署至每個請求都在全新無狀態 execution context 中執行的環境時,請使用 `serverless: true`: > > - Supabase Edge Functions > - Cloudflare Workers > - Vercel Edge Functions > - Netlify Edge Functions > - AWS Lambda > - Deno Deploy > > 下列環境請使用預設的 session 模式(不設定 `serverless: true`): > > - 長時間執行的 Node.js server > - Docker container > - 傳統託管環境(VPS、專用 server) > > Serverless 模式會停用 session 管理,並為每個請求建立全新的 server instance。這是 invocation 之間不會保留記憶體的無狀態環境所必需的。 > > 根據預設,serverless 模式會將每個請求緩衝為單一 JSON response,因此 Tool 傳送的 `notifications/progress` 不會抵達 client。請設定 `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 state 或持久連線,因此在 serverless 模式下**無法使用**(包括設定 `serverlessStreaming: true` 時): > > - **Elicitation**:Tool 執行期間的互動式使用者輸入請求,需要 session 管理才能將 response 路由回正確的 client > - **資源訂閱**:`resources/subscribe` 與 `resources/unsubscribe` 需要持久連線以維護訂閱狀態 > - **資源更新通知**:`resources.notifyUpdated()` 需要作用中的訂閱與持久連線,才能通知 client > - **Prompt 清單變更通知**:`prompts.notifyListChanged()` 需要持久連線才能將更新推送至 client > - **Tool 清單變更通知**:`toolActions.notifyListChanged()` 需要持久連線才能將更新推送至 client > - **Server log 通知**:`sendLoggingMessage()` 需要持久連線才能將 log 訊息推送至 client > > 這些功能可在長時間執行的 server 環境(Node.js server、Docker container 等)中正常運作。 以下是 `startHTTP` 方法所需值的詳細資訊: **url** (`URL`): 使用者要求的 Web 位址。 **httpPath** (`string`): MCP server 處理 HTTP 請求的特定 URL 部分(例如 '/mcp')。 **req** (`http.IncomingMessage`): 來自 Web server 的傳入 request 物件。 **res** (`http.ServerResponse`): 來自 Web server、用於傳回資料的 response 物件。 **options** (`StreamableHTTPServerTransportOptions`): HTTP transport 的選填設定。詳情請參閱下方選項表。 `StreamableHTTPServerTransportOptions` 物件可用來自訂 HTTP transport 行為。可用選項如下: **serverless** (`boolean`): 若為 true,則在不使用 session 管理的無狀態模式下執行。每個請求都由全新的 server instance 獨立處理。這對 invocation 之間無法保留 session 的 serverless 環境(Cloudflare Workers、Supabase Edge Functions、Vercel Edge 等)至關重要。預設為 false。 **serverlessStreaming** (`boolean`): 若為 true,serverless 請求會使用限於請求範圍的 SSE 串流,而不是緩衝的 JSON response,讓請求內的 notifications/progress 能在最終結果前抵達 client。只有搭配 serverless: true 才會生效。預設為 false(緩衝的 JSON response),以保留向後相容行為。它只會啟用進度等限於請求範圍的通知;elicitation、訂閱與請求外通知仍需要 session state。 **sessionIdGenerator** (`(() => string) | undefined`): 產生唯一 session ID 的函式。此字串應具備密碼學安全性,並且在全域中唯一。回傳 undefined 可停用 session 管理。 **onsessioninitialized** (`(sessionId: string) => void`): 初始化新 session 時叫用的 callback,適合用來追蹤作用中的 MCP session。 **enableJsonResponse** (`boolean`): 若為 true,server 會回傳一般 JSON response,而不使用 Server-Sent Events(SSE)進行串流。預設為 false。 **eventStore** (`EventStore`): 用於恢復訊息的 event store。提供此值後,client 可重新連線並繼續訊息串流。 ### `close()` 此方法會關閉 server 並釋放所有資源。 ```typescript async close(): Promise ``` ### `getServerInfo()` 此方法會回傳 server 的基本資訊。 ```typescript getServerInfo(): ServerInfo ``` ### `getServerDetail()` 此方法會回傳 server 資訊的詳細內容。 ```typescript getServerDetail(): ServerDetail ``` ### `getToolListInfo()` 此方法會回傳建立 server 時設定的 Tool。這是唯讀清單,適合用於除錯。 ```typescript getToolListInfo(): ToolListInfo ``` ### `getToolInfo()` 此方法會回傳特定 Tool 的詳細資訊。 ```typescript getToolInfo(toolName: string): ToolInfo ``` ### `executeTool()` 此方法會執行特定 Tool 並回傳結果。 ```typescript executeTool(toolName: string, input: any): Promise ``` ### `getStdioTransport()` 若使用 `startStdio()` 啟動 server,可使用此方法取得管理 stdio 通訊的物件,主要用於內部檢查或測試。 ```typescript getStdioTransport(): StdioServerTransport | undefined ``` ### `getSseTransport()` 若使用 `startSSE()` 啟動 server,可使用此方法取得管理 SSE 通訊的物件。與 `getStdioTransport` 相同,主要用於內部檢查或測試。 ```typescript getSseTransport(): SSEServerTransport | undefined ``` ### `getSseHonoTransport()` 若使用 `startHonoSSE()` 啟動 server,可使用此方法取得管理 SSE 通訊的物件。與 `getSseTransport` 相同,主要用於內部檢查或測試。 ```typescript getSseHonoTransport(): SSETransport | undefined ``` ### `getStreamableHTTPTransport()` 若使用 `startHTTP()` 啟動 server,可使用此方法取得管理 HTTP 通訊的物件。與 `getSseTransport` 相同,主要用於內部檢查或測試。 ```typescript getStreamableHTTPTransport(): StreamableHTTPServerTransport | undefined ``` ### `tools()` 執行此 MCP server 提供的特定 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,例如 messages 或 toolCallId。 ## 資源處理 ### 什麼是 MCP Resource? Resource 是 Model Context Protocol(MCP)的核心 primitive,讓 server 能公開可由 client 讀取,並作為 LLM 互動 context 的資料與內容。它可代表 MCP server 想提供的任何資料,例如: - 檔案內容 - 資料庫記錄 - API 回應 - 即時系統資料 - 螢幕截圖與圖片 - Log 檔案 Resource 以唯一 URI 識別(例如 `file:///home/user/documents/report.pdf`、`postgres://database/customers/schema`),並可包含文字(UTF-8 編碼)或 binary 資料(base64 編碼)。 Client 可透過下列方式探索 resource: 1. **直接 resource**:Server 透過 `resources/list` endpoint 公開具體 resource 清單。 2. **Resource template**:對於執行階段定義的 resource,server 可公開 URI template(RFC 6570),讓 client 用來建構 resource URI。 若要讀取 resource,client 會使用 URI 發出 `resources/read` 請求。若 client 已訂閱 resource,server 也可通知 client resource 清單變更(`notifications/resources/list_changed`)或特定 resource 內容更新(`notifications/resources/updated`)。 如需詳細資訊,請參閱 [MCP Resource 官方文件](https://modelcontextprotocol.io/docs/concepts/resources)。 ### `MCPServerResources` 型別 `resources` 選項接受 `MCPServerResources` 型別的物件。此型別定義 server 用來處理 resource 請求的 callback: ```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, }) ``` ### 通知 client resource 變更 若可用 resource 或其內容變更,server 可通知已連線且訂閱特定 resource 的 client。 #### `server.resources.notifyUpdated({ uri: string })` 當以 `uri` 識別的特定 resource 內容更新時,請呼叫此方法。若有 client 訂閱此 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()` 可用 resource 清單變更時(例如新增或移除 resource),請呼叫此方法。這會向 client 傳送 `notifications/resources/list_changed` 訊息,提示 client 重新擷取 resource 清單。 ```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 Prompt? Prompt 是 MCP server 向 client 公開的可重複使用 template 或 Workflow。它們可以接受引數並包含 resource context,也支援版本管理,並將 LLM 互動標準化。 Prompt 以唯一名稱(及選填版本)識別,可以在執行階段定義,也可以是靜態內容。 ### `MCPServerPrompts` 型別 `prompts` 選項接受 `MCPServerPrompts` 型別的物件。此型別定義 server 用來處理 prompt 請求的 callback: ```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, }) ``` ### 通知 client Prompt 變更 若可用 prompt 變更,server 可通知已連線的 client: #### `server.prompts.notifyListChanged()` 可用 prompt 清單變更時(例如新增或移除 prompt),請呼叫此方法。這會向 client 傳送 `notifications/prompts/list_changed` 訊息,提示 client 重新擷取 prompt 清單。 ```typescript await serverWithPrompts.prompts.notifyListChanged() ``` ### Prompt 處理最佳做法 - 使用清楚且具描述性的 prompt 名稱與說明。 - 驗證 `getPromptMessages` 中的所有必要引數。 - 若預期進行破壞性變更,請包含 `version` 欄位。 - 使用 `version` 參數選取正確的 prompt 邏輯。 - Prompt 清單變更時通知 client。 - 使用資訊完整的訊息處理錯誤。 - 記錄預期引數與可用版本。 ## 動態 Tool 管理 Tool 通常在建立 `MCPServer` 時提供,但也可在 server 執行期間新增或移除。Server 會透過 `toolActions` 屬性公開這些操作。Tool 清單變更時,已連線的 client 會收到 `notifications/tools/list_changed` 訊息,提示它們重新擷取 Tool 清單。 此屬性命名為 `toolActions`,因為 `tools()` 是回傳已註冊 Tool registry 的方法。 ### `toolActions.add(tools)` 在執行中的 server 上註冊新 Tool,並通知已連線的 client。Tool 以 record key 作為 key,與傳給 constructor 的 Tool 相同。在現有 key 下新增 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 從執行中的 server 移除 Tool,並通知已連線的 client。系統會忽略未知的 Tool ID。只有至少移除一個 Tool 時才會傳送通知。 ```typescript async server.toolActions.remove(toolIds: string[]): Promise ``` 範例: ```typescript await server.toolActions.remove(['searchTool']) ``` ### `toolActions.notifyListChanged()` 在不修改 Tool registry 的情況下,向已連線的 client 傳送 `notifications/tools/list_changed` 訊息。當 Tool 可用性因其他方式變更時(例如 authorization 變更),請呼叫此方法。 ```typescript async server.toolActions.notifyListChanged(): Promise ``` ### Mastra registry 同步 Server 註冊至 Mastra instance 後,`toolActions.add()` 與 `toolActions.remove()` 也會更新 Mastra instance 的 Tool registry,與啟動時的自動 Tool 註冊一致。新增的 Tool 可透過 `mastra.listTools()` 使用(若有 Tool 本身的 `id`,則以此作為 key),移除的 Tool 則會從 registry 刪除。 ## 記錄 MCP server 可使用 `notifications/message` 向 client 傳送結構化 log 訊息。Client 可傳送 `logging/setLevel` 請求控制詳細程度。Server 會捨棄低於要求最低層級的訊息(遵循 RFC 5424 嚴重性排序)。層級會依 session 追蹤,因此不同 client 可要求不同的詳細程度。 ### `sendLoggingMessage()` 向所有已連線的 client 傳送 log 通知,並遵守各 client 的最低 logging 層級。 ```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 的 client 傳送 log 訊息。 ```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 } ``` ## 進度通知 長時間執行的 Tool 可使用 `notifications/progress` 向呼叫端 client 回報進度。只有呼叫端在請求中包含 `progressToken`,要求追蹤進度時才會傳送進度(設定 `enableProgressTracking` 時,Mastra `MCPClient` 會執行此操作)。未傳送 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()`)會透過所有 transport 向每個已連線的 client 廣播:包括 stdio/SSE 連線與各個 streamable HTTP session。`resources.notifyUpdated()` 例外,只會通知透過 `resources/subscribe` 訂閱該 resource URI 的 client。Streamable HTTP client 的訂閱會依 session 追蹤;舊版 SSE client 共用主要 server instance,因此也共用一組訂閱。使用無狀態 serverless 模式的 client 無法接收通知,因為每個請求都使用暫時性的 server instance。 ## 範例 如需設定與部署 MCPServer 的實際範例,請參閱[發布 MCP Server 指南](https://mastra.zisheng.pro/zh-TW/guides/guide/publishing-mcp-server)。 本頁開頭的範例也示範如何使用 Tool 與 Agent 建立 `MCPServer` instance。 ## Elicitation ### 什麼是 Elicitation? Elicitation 是 Model Context Protocol(MCP)的一項功能,可讓 server 向使用者要求結構化資訊。它支援 server 在執行階段收集額外資料的互動式 Workflow。 `MCPServer` 類別會自動包含 elicitation 功能。Tool 會在 `execute` 函式中收到 `context.mcp` 物件,其中包含用來要求使用者輸入的 `elicitation.sendRequest()` 方法。 ### Tool 執行 signature Tool 在 MCP server 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 執行期間。Tool 需要使用者輸入時,可使用 context 參數提供的 elicitation 功能: 1. Tool 使用訊息與 schema 呼叫 `context.mcp.elicitation.sendRequest()` 2. Request 會傳送至已連線的 MCP client 3. Client 向使用者呈現 request(透過 UI、命令列等) 4. 使用者提供輸入、拒絕或取消 request 5. Client 將 response 傳回 server 6. Tool 收到 response 並繼續執行 ### 在 Tool 中使用 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 request schema `requestedSchema` 必須是只具有 primitive 屬性的扁平物件。支援的型別包括: - **String**:`{ type: 'string', title: 'Display Name', description: 'Help text' }` - **Number**:`{ type: 'number', minimum: 0, maximum: 100 }` - **Boolean**:`{ type: 'boolean', default: false }` - **Enum**:`{ 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 request: 1. **Accept**(`action: 'accept'`):使用者已提供資料並確認提交 - 包含具有提交資料的 `content` 欄位 2. **Decline**(`action: 'decline'`):使用者明確拒絕提供資訊 - 沒有 content 欄位 3. **Cancel**(`action: 'cancel'`):使用者未做決定便關閉 request - 沒有 content 欄位 Tool 應妥善處理這三種 response 型別。 ### 安全性考量 - **絕不要求敏感資訊**,例如密碼、社會安全號碼或信用卡號碼 - 根據提供的 schema 驗證所有使用者輸入 - 妥善處理拒絕與取消 - 提供清楚的資料收集原因 - 尊重使用者隱私與偏好 ### Tool 執行 API Elicitation 功能可透過 Tool 執行中的 `options` 參數使用: ```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 transport(SSE 或 HTTP)時,Elicitation 會**感知 session**。多個 client 連線至同一個 server 時,elicitation request 會路由至啟動 Tool 執行的 client session。 The `ElicitResult` type: ```typescript type ElicitResult = { action: 'accept' | 'decline' | 'cancel' content?: any // Only present when action is 'accept' } ``` ## OAuth 保護 若要依 [MCP Auth 規範](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)使用 OAuth 驗證保護 MCP server,請使用 `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 server 的 canonical URL。此值會在 Protected Resource Metadata 中回傳。 **oauth.authorizationServers** (`string[]`): 可為此 resource 發行 token 的 authorization server URL。 **oauth.scopesSupported** (`string[]`): 此 MCP server 支援的 scope。 (Default: `['mcp:read', 'mcp:write']`) **oauth.resourceName** (`string`): 此 resource server 的易讀名稱。 **oauth.validateToken** (`(token: string, resource: string) => Promise`): 用於驗證 access token 的函式。若未提供,則會接受 token 而不進行驗證(不建議用於正式環境)。 **mcpPath** (`string`): 提供 MCP endpoint 的路徑。只有對此路徑的請求需要驗證。 (Default: `'/mcp'`) ## 驗證 context 使用 HTTP transport 時,Tool 可透過 `context.mcp.extra` 存取 request 中繼資料。如此便能將驗證資訊、使用者 context 或任何自訂資料從 HTTP middleware 傳給 MCP Tool。 ### 運作方式 HTTP middleware 中在 `req.auth` 設定的任何內容,都可在 Tool 中透過 `context.mcp.extra.authInfo` 使用: ```text req.auth = { ... } → context?.mcp?.extra?.authInfo.extra = { ... } ``` ### 為 FGA 對應驗證資料 將 `MCPServer` 註冊至具有細粒度 authorization(FGA)Provider 的 Mastra instance 時,Mastra 會在列出或呼叫 Tool 前檢查 `requestContext.get('user')`。HTTP MCP transport 會將已驗證資料作為 `extra.authInfo` 傳遞,因此請使用 `mapAuthInfoToUser` 設定 FGA Provider 預期的 user 結構。 ```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 scope 若 MCP client 所需的 authorization scope 與內部 Agent 或 Workflow Tool 執行不同,請使用 `fga.resourceMapping` 與 `fga.permissionMapping`。此覆寫只適用於該 MCP server 的 `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 若要將資料傳給 Tool,請先在 HTTP server middleware 的 Node.js request 物件上填入 `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 }) }) ``` ### 在 Tool 中存取驗證資料 在 Tool 的 execute 函式中,`req.auth` 物件可作為 `context.mcp.extra.authInfo` 使用: ```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` | 用於取消 request 的 AbortSignal | | `sendNotification` | 用於傳送通知的 MCP protocol 函式 | | `sendRequest` | 用於傳送 request 的 MCP protocol 函式 | ### 完整範例 安裝 [`jose`](https://github.com/panva/jose),使用身分 Provider 的 JSON Web Key Set(JWKS)驗證 JSON Web Token(JWT): **npm**: ```shell npm install jose ``` **pnpm**: ```shell pnpm add jose ``` **Yarn**: ```shell yarn add jose ``` **Bun**: ```shell bun add jose ``` 下列範例會先驗證 token 的 signature、issuer、audience、algorithm、expiration 與必要 claim,再將使用者資料傳給 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 server 透過 [MCP Apps extension](https://github.com/modelcontextprotocol/ext-apps)提供互動式 HTML UI。每個項目都會將 `ui://` URI 對應至在 Mastra Studio 的 Sandbox 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 檔案的路徑。會在 server 啟動時解析。請提供 html 或 htmlPath 其中之一。 **meta** (`McpUiResourceMeta`): 來自官方 ext-apps SDK 的 UI resource 中繼資料(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。Server 註冊 Tool 時會自動正規化此中繼資料。完整的 app bridge API 與使用模式請參閱 [MCP Apps](https://mastra.zisheng.pro/zh-TW/docs/mcp/overview)。 ## 相關資訊 - 如需在 Mastra 中連線至 MCP server,請參閱 [MCPClient 文件](https://mastra.zisheng.pro/zh-TW/reference/tools/mcp-client)。 - 如需 Model Context Protocol 的更多資訊,請參閱 [@modelcontextprotocol/sdk 文件](https://github.com/modelcontextprotocol/typescript-sdk)。