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。
Constructor「Constructor」的直接連結
若要建立新的 MCPServer,需要提供 server 的基本資訊、server 將提供的 Tool,以及選填的要公開為 Tool 的 Agent。
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:
name:
version:
tools:
createTool 或 Vercel AI SDK 建立)的物件。這些 Tool 會直接公開。agents?:
ask_<agentIdentifier> 的 Tool。Agent constructor 設定中**必須**定義非空白的 description 字串屬性,此內容會用於 Tool 說明。若 Agent 的 description 缺少或為空白,MCPServer 初始化期間會擲回錯誤。workflows?:
run_<workflowKey> 的 Tool。Workflow 的 inputSchema 會成為 Tool 的輸入 schema。Workflow **必須**具有非空白的 description 字串屬性,供 Tool 說明使用。若 Workflow 的 description 缺少或為空白,則會擲回錯誤。Tool 會先呼叫 workflow.createRun(),再呼叫 run.start({ inputData: <tool_input> }) 以執行 Workflow。若衍生自 Agent 或 Workflow 的 Tool 名稱(例如 ask_myAgent 或 run_myWorkflow)與明確定義的 Tool 名稱或其他衍生名稱衝突,明確定義的 Tool 優先,且系統會記錄警告。造成後續衝突的 Agent/Workflow 會略過。description?:
instructions?:
mapAuthInfoToUser?:
extra.authInfo 中的 MCP transport 驗證資料對應至 Mastra FGA 檢查使用的 user 值。當受 OAuth 保護的 MCP server 註冊至具有 FGA Provider 的 Mastra instance 時,請使用此屬性。fga?:
tools/list 與 tools/call FGA 檢查的資源和權限對應。當 MCP authorization 的 scope 應不同於內部 Agent 或 Workflow Tool 執行時,請使用此屬性。repository?:
releaseDate?:
isLatest?:
packageCanonical?:
packages?:
remotes?:
resources?:
prompts?:
appResources?:
ui:// URI 對應至 app resource 設定的 map。每個項目都定義透過 MCP Apps extension(SEP-1865)提供的互動式 HTML UI。詳情請參閱 MCP Apps 一節。將 Agent 公開為 Tool「將 Agent 公開為 Tool」的直接連結
MCPServer 的強大功能之一,是能自動將 Mastra Agent 公開為可呼叫的 Tool。在設定的 agents 屬性中提供 Agent 時:
-
Tool 命名:每個 Agent 都會轉換為名為
ask_<agentKey>的 Tool,其中<agentKey>是該 Agent 在agents物件中使用的 key。例如,若設定agents: { myAgentKey: myAgentInstance },系統會建立名為ask_myAgentKey的 Tool。 -
Tool 功能:
- 說明:產生的 Tool 說明格式為:"Ask agent
<AgentName>a question. Original agent instructions:<agent description>"。 - 輸入:Tool 預期收到具有
message屬性(字串)的單一物件引數:{ message: "Your question for the agent" }。 - 執行:呼叫此 Tool 時,會使用提供的
query叫用對應 Agent 的generate()方法。 - 輸出:Agent
generate()方法的直接結果會作為 Tool 輸出回傳。
- 說明:產生的 Tool 說明格式為:"Ask agent
-
名稱衝突。 若
tools設定中明確定義的 Tool 與 Agent 衍生 Tool 同名(例如名為ask_myAgentKey的 Tool 與 key 為myAgentKey的 Agent 並存),則_明確定義的 Tool 優先_。發生衝突時,不會將 Agent 轉換為 Tool,且系統會記錄警告。
如此一來,MCP client 便能像使用其他 Tool 一樣,以自然語言查詢與 Agent 互動。
將 Agent 轉換為 Tool「將 Agent 轉換為 Tool」的直接連結
在 agents 設定屬性中提供 Agent 時,MCPServer 會自動為每個 Agent 建立對應 Tool。Tool 名稱為 ask_<agentIdentifier>,其中 <agentIdentifier> 是 agents 物件中使用的 key。
產生的 Tool 說明為:"Ask agent <agent.name> a question. Agent description: <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「在 Tool 中存取 MCP context」的直接連結
透過 MCPServer 公開的 Tool 可根據呼叫方式,使用兩種不同屬性存取 MCP request context(驗證、session ID 等):
| 呼叫模式 | 存取方式 |
|---|---|
| 直接呼叫 Tool | context?.mcp?.extra |
| Agent Tool 呼叫 | context?.requestContext?.get("mcp.extra") |
通用模式(適用於兩種 context):
const mcpExtra = context?.mcp?.extra ?? context?.requestContext?.get('mcp.extra')
const authInfo = mcpExtra?.authInfo
範例:適用於兩種 context 的 Tool「範例:適用於兩種 context 的 Tool」的直接連結
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()「startstdio」的直接連結
使用此方法啟動 server,讓它透過標準輸入與輸出(stdio)通訊。以命令列程式執行 server 時通常會使用此方式。
async startStdio(): Promise<void>
以下示範如何使用 stdio 啟動 server:
const server = new MCPServer({
id: 'my-server',
name: 'My Server',
version: '1.0.0',
tools: {/* ... */},
})
await server.startStdio()
startSSE()「startsse」的直接連結
此方法可將 MCP server 與現有 Web server 整合,並使用 Server-Sent Events(SSE)通訊。Web server 收到 SSE 或 message 路徑請求時,請從 Web server 程式碼呼叫此方法。
async startSSE({
url,
ssePath,
messagePath,
req,
res,
}: {
url: URL;
ssePath: string;
messagePath: string;
req: any;
res: any;
}): Promise<void>
下列範例示範如何在 HTTP server request handler 中使用 startSSE。在此範例中,MCP client 可透過 http://localhost:1234/sse 連線至 MCP server:
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:
ssePath:
messagePath:
req:
res:
startHonoSSE()「starthonosse」的直接連結
此方法可將 MCP server 與現有 Web server 整合,並使用 Server-Sent Events(SSE)通訊。Web server 收到 SSE 或 message 路徑請求時,請從 Web server 程式碼呼叫此方法。
async startHonoSSE({
url,
ssePath,
messagePath,
req,
res,
}: {
url: URL;
ssePath: string;
messagePath: string;
req: any;
res: any;
}): Promise<void>
下列範例示範如何在 HTTP server request handler 中使用 startHonoSSE。在此範例中,MCP client 可透過 http://localhost:1234/hono-sse 連線至 MCP server:
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:
ssePath:
messagePath:
req:
res:
startHTTP()「starthttp」的直接連結
此方法可將 MCP server 與現有 Web server 整合,並使用 streamable HTTP 通訊。Web server 收到 HTTP 請求時,請從 Web server 程式碼呼叫此方法。
async startHTTP({
url,
httpPath,
req,
res,
options = { sessionIdGenerator: () => randomUUID() },
}: {
url: URL;
httpPath: string;
req: http.IncomingMessage;
res: http.ServerResponse<http.IncomingMessage>;
options?: StreamableHTTPServerTransportOptions;
}): Promise<void>
下列範例示範如何在 HTTP server request handler 中使用 startHTTP。在此範例中,MCP client 可透過 http://localhost:1234/http 連線至 MCP server:
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 啟用無狀態操作:
// 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 })
})
部署至每個請求都在全新無狀態 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 串流處理請求,在最終結果前傳遞進度通知:
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:
httpPath:
req:
res:
options:
StreamableHTTPServerTransportOptions 物件可用來自訂 HTTP transport 行為。可用選項如下:
serverless:
true,則在不使用 session 管理的無狀態模式下執行。每個請求都由全新的 server instance 獨立處理。這對 invocation 之間無法保留 session 的 serverless 環境(Cloudflare Workers、Supabase Edge Functions、Vercel Edge 等)至關重要。預設為 false。serverlessStreaming:
true,serverless 請求會使用限於請求範圍的 SSE 串流,而不是緩衝的 JSON response,讓請求內的 notifications/progress 能在最終結果前抵達 client。只有搭配 serverless: true 才會生效。預設為 false(緩衝的 JSON response),以保留向後相容行為。它只會啟用進度等限於請求範圍的通知;elicitation、訂閱與請求外通知仍需要 session state。sessionIdGenerator:
undefined 可停用 session 管理。onsessioninitialized:
enableJsonResponse:
true,server 會回傳一般 JSON response,而不使用 Server-Sent Events(SSE)進行串流。預設為 false。eventStore:
close()「close」的直接連結
此方法會關閉 server 並釋放所有資源。
async close(): Promise<void>
getServerInfo()「getserverinfo」的直接連結
此方法會回傳 server 的基本資訊。
getServerInfo(): ServerInfo
getServerDetail()「getserverdetail」的直接連結
此方法會回傳 server 資訊的詳細內容。
getServerDetail(): ServerDetail
getToolListInfo()「gettoollistinfo」的直接連結
此方法會回傳建立 server 時設定的 Tool。這是唯讀清單,適合用於除錯。
getToolListInfo(): ToolListInfo
getToolInfo()「gettoolinfo」的直接連結
此方法會回傳特定 Tool 的詳細資訊。
getToolInfo(toolName: string): ToolInfo
executeTool()「executetool」的直接連結
此方法會執行特定 Tool 並回傳結果。
executeTool(toolName: string, input: any): Promise<any>
getStdioTransport()「getstdiotransport」的直接連結
若使用 startStdio() 啟動 server,可使用此方法取得管理 stdio 通訊的物件,主要用於內部檢查或測試。
getStdioTransport(): StdioServerTransport | undefined
getSseTransport()「getssetransport」的直接連結
若使用 startSSE() 啟動 server,可使用此方法取得管理 SSE 通訊的物件。與 getStdioTransport 相同,主要用於內部檢查或測試。
getSseTransport(): SSEServerTransport | undefined
getSseHonoTransport()「getssehonotransport」的直接連結
若使用 startHonoSSE() 啟動 server,可使用此方法取得管理 SSE 通訊的物件。與 getSseTransport 相同,主要用於內部檢查或測試。
getSseHonoTransport(): SSETransport | undefined
getStreamableHTTPTransport()「getstreamablehttptransport」的直接連結
若使用 startHTTP() 啟動 server,可使用此方法取得管理 HTTP 通訊的物件。與 getSseTransport 相同,主要用於內部檢查或測試。
getStreamableHTTPTransport(): StreamableHTTPServerTransport | undefined
tools()「tools」的直接連結
執行此 MCP server 提供的特定 Tool。
async executeTool(
toolId: string,
args: any,
executionContext?: { messages?: any[]; toolCallId?: string },
): Promise<any>
toolId:
args:
executionContext?:
資源處理「資源處理」的直接連結
什麼是 MCP Resource?「什麼是 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:
- 直接 resource:Server 透過
resources/listendpoint 公開具體 resource 清單。 - 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 官方文件。
MCPServerResources 型別「mcpserverresources-type」的直接連結
resources 選項接受 MCPServerResources 型別的物件。此型別定義 server 用來處理 resource 請求的 callback:
export type MCPServerResources = {
// Callback to list available resources
listResources: () => Promise<Resource[]>
// Callback to get the content of a specific resource
getResourceContent: ({
uri,
}: {
uri: string
}) => Promise<MCPServerResourceContent | MCPServerResourceContent[]>
// Optional callback to list available resource templates
resourceTemplates?: () => Promise<ResourceTemplate[]>
}
export type MCPServerResourceContent = { text?: string } | { blob?: string }
範例:
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<string, MCPServerResourceContent> = {
'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 變更「通知 client resource 變更」的直接連結
若可用 resource 或其內容變更,server 可通知已連線且訂閱特定 resource 的 client。
server.resources.notifyUpdated({ uri: string })「serverresourcesnotifyupdated-uri-string-」的直接連結
當以 uri 識別的特定 resource 內容更新時,請呼叫此方法。若有 client 訂閱此 URI,便會收到 notifications/resources/updated 訊息。
async server.resources.notifyUpdated({ uri: string }): Promise<void>
範例:
// After updating the content of 'file://data.txt'
await serverWithResources.resources.notifyUpdated({ uri: 'file://data.txt' })
server.resources.notifyListChanged()「serverresourcesnotifylistchanged」的直接連結
可用 resource 清單變更時(例如新增或移除 resource),請呼叫此方法。這會向 client 傳送 notifications/resources/list_changed 訊息,提示 client 重新擷取 resource 清單。
async server.resources.notifyListChanged(): Promise<void>
範例:
// After adding a new resource to the list managed by 'myResourceHandlers.listResources'
await serverWithResources.resources.notifyListChanged()
Prompt 處理「Prompt 處理」的直接連結
什麼是 MCP Prompt?「什麼是 MCP Prompt?」的直接連結
Prompt 是 MCP server 向 client 公開的可重複使用 template 或 Workflow。它們可以接受引數並包含 resource context,也支援版本管理,並將 LLM 互動標準化。
Prompt 以唯一名稱(及選填版本)識別,可以在執行階段定義,也可以是靜態內容。
MCPServerPrompts 型別「mcpserverprompts-type」的直接連結
prompts 選項接受 MCPServerPrompts 型別的物件。此型別定義 server 用來處理 prompt 請求的 callback:
export type MCPServerPrompts = {
// Callback to list available prompts
listPrompts: () => Promise<Prompt[]>
// 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[] }>
}
範例:
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 變更「通知 client Prompt 變更」的直接連結
若可用 prompt 變更,server 可通知已連線的 client:
server.prompts.notifyListChanged()「serverpromptsnotifylistchanged」的直接連結
可用 prompt 清單變更時(例如新增或移除 prompt),請呼叫此方法。這會向 client 傳送 notifications/prompts/list_changed 訊息,提示 client 重新擷取 prompt 清單。
await serverWithPrompts.prompts.notifyListChanged()
Prompt 處理最佳做法「Prompt 處理最佳做法」的直接連結
- 使用清楚且具描述性的 prompt 名稱與說明。
- 驗證
getPromptMessages中的所有必要引數。 - 若預期進行破壞性變更,請包含
version欄位。 - 使用
version參數選取正確的 prompt 邏輯。 - Prompt 清單變更時通知 client。
- 使用資訊完整的訊息處理錯誤。
- 記錄預期引數與可用版本。
動態 Tool 管理「動態 Tool 管理」的直接連結
Tool 通常在建立 MCPServer 時提供,但也可在 server 執行期間新增或移除。Server 會透過 toolActions 屬性公開這些操作。Tool 清單變更時,已連線的 client 會收到 notifications/tools/list_changed 訊息,提示它們重新擷取 Tool 清單。
此屬性命名為 toolActions,因為 tools() 是回傳已註冊 Tool registry 的方法。
toolActions.add(tools)「toolactionsaddtools」的直接連結
在執行中的 server 上註冊新 Tool,並通知已連線的 client。Tool 以 record key 作為 key,與傳給 constructor 的 Tool 相同。在現有 key 下新增 Tool 會取代原有 Tool。
async server.toolActions.add(tools: ToolsInput): Promise<void>
範例:
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)「toolactionsremovetoolids」的直接連結
依 Tool ID 從執行中的 server 移除 Tool,並通知已連線的 client。系統會忽略未知的 Tool ID。只有至少移除一個 Tool 時才會傳送通知。
async server.toolActions.remove(toolIds: string[]): Promise<void>
範例:
await server.toolActions.remove(['searchTool'])
toolActions.notifyListChanged()「toolactionsnotifylistchanged」的直接連結
在不修改 Tool registry 的情況下,向已連線的 client 傳送 notifications/tools/list_changed 訊息。當 Tool 可用性因其他方式變更時(例如 authorization 變更),請呼叫此方法。
async server.toolActions.notifyListChanged(): Promise<void>
Mastra registry 同步「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()「sendloggingmessage」的直接連結
向所有已連線的 client 傳送 log 通知,並遵守各 client 的最低 logging 層級。
async server.sendLoggingMessage(params: {
level: LoggingLevel;
data: unknown;
logger?: string;
}): Promise<void>
範例:
await server.sendLoggingMessage({
level: 'info',
data: { message: 'Sync completed', itemsProcessed: 42 },
})
context.mcp.log()「contextmcplog」的直接連結
在 Tool 的 execute 函式內,使用 context.mcp.log() 向呼叫該 Tool 的 client 傳送 log 訊息。
async context.mcp.log(
level: LoggingLevel,
message: string,
data?: Record<string, unknown>
): Promise<void>
範例:
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()「contextmcpprogress」的直接連結
async context.mcp.progress(params: {
progress: number;
total?: number;
message?: string;
}): Promise<void>
範例:
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 指南。
本頁開頭的範例也示範如何使用 Tool 與 Agent 建立 MCPServer instance。
Elicitation「Elicitation」的直接連結
什麼是 Elicitation?「什麼是 Elicitation?」的直接連結
Elicitation 是 Model Context Protocol(MCP)的一項功能,可讓 server 向使用者要求結構化資訊。它支援 server 在執行階段收集額外資料的互動式 Workflow。
MCPServer 類別會自動包含 elicitation 功能。Tool 會在 execute 函式中收到 context.mcp 物件,其中包含用來要求使用者輸入的 elicitation.sendRequest() 方法。
Tool 執行 signature「Tool 執行 signature」的直接連結
Tool 在 MCP server context 中執行時,會透過 context.mcp 物件接收 MCP 特定功能:
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 的運作方式「Elicitation 的運作方式」的直接連結
常見使用情境是在 Tool 執行期間。Tool 需要使用者輸入時,可使用 context 參數提供的 elicitation 功能:
- Tool 使用訊息與 schema 呼叫
context.mcp.elicitation.sendRequest() - Request 會傳送至已連線的 MCP client
- Client 向使用者呈現 request(透過 UI、命令列等)
- 使用者提供輸入、拒絕或取消 request
- Client 將 response 傳回 server
- Tool 收到 response 並繼續執行
在 Tool 中使用 Elicitation「在 Tool 中使用 Elicitation」的直接連結
下列範例示範使用 elicitation 收集使用者聯絡資訊的 Tool:
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「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 範例:
{
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:
- Accept(
action: 'accept'):使用者已提供資料並確認提交- 包含具有提交資料的
content欄位
- 包含具有提交資料的
- Decline(
action: 'decline'):使用者明確拒絕提供資訊- 沒有 content 欄位
- Cancel(
action: 'cancel'):使用者未做決定便關閉 request- 沒有 content 欄位
Tool 應妥善處理這三種 response 型別。
安全性考量「安全性考量」的直接連結
- 絕不要求敏感資訊,例如密碼、社會安全號碼或信用卡號碼
- 根據提供的 schema 驗證所有使用者輸入
- 妥善處理拒絕與取消
- 提供清楚的資料收集原因
- 尊重使用者隱私與偏好
Tool 執行 API「Tool 執行 API」的直接連結
Elicitation 功能可透過 Tool 執行中的 options 參數使用:
// 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<ElicitResult>
// 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:
type ElicitResult = {
action: 'accept' | 'decline' | 'cancel'
content?: any // Only present when action is 'accept'
}
OAuth 保護「OAuth 保護」的直接連結
若要依 MCP Auth 規範使用 OAuth 驗證保護 MCP server,請使用 createOAuthMiddleware 函式:
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-Authenticateheader 的401 Unauthorized - 使用你提供的 validator 驗證 bearer token
Token 驗證「Token 驗證」的直接連結
在正式環境中,請使用適當的 token 驗證:
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 middleware 選項」的直接連結
oauth.resource:
oauth.scopesSupported?:
oauth.resourceName?:
oauth.validateToken?:
mcpPath?:
驗證 context「驗證 context」的直接連結
使用 HTTP transport 時,Tool 可透過 context.mcp.extra 存取 request 中繼資料。如此便能將驗證資訊、使用者 context 或任何自訂資料從 HTTP middleware 傳給 MCP Tool。
運作方式「運作方式」的直接連結
HTTP middleware 中在 req.auth 設定的任何內容,都可在 Tool 中透過 context.mcp.extra.authInfo 使用:
req.auth = { ... } → context?.mcp?.extra?.authInfo.extra = { ... }
為 FGA 對應驗證資料「為 FGA 對應驗證資料」的直接連結
將 MCPServer 註冊至具有細粒度 authorization(FGA)Provider 的 Mastra instance 時,Mastra 會在列出或呼叫 Tool 前檢查 requestContext.get('user')。HTTP MCP transport 會將已驗證資料作為 extra.authInfo 傳遞,因此請使用 mapAuthInfoToUser 設定 FGA Provider 預期的 user 結構。
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 Tool FGA scope」的直接連結
若 MCP client 所需的 authorization scope 與內部 Agent 或 Workflow Tool 執行不同,請使用 fga.resourceMapping 與 fga.permissionMapping。此覆寫只適用於該 MCP server 的 tools/list 與 tools/call 檢查。
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「設定驗證 middleware」的直接連結
若要將資料傳給 Tool,請先在 HTTP server middleware 的 Node.js request 物件上填入 req.auth,再呼叫 server.startHTTP()。
import express from 'express'
type MCPAuthenticatedRequest = express.Request & {
auth?: {
token: string
clientId: string
scopes: string[]
expiresAt?: number
extra?: Record<string, unknown>
}
}
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 中存取驗證資料」的直接連結
在 Tool 的 execute 函式中,req.auth 物件可作為 context.mcp.extra.authInfo 使用:
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「passing-requestcontext-through-to-agent」的直接連結
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 物件「the-extra-object」的直接連結
完整的 context.mcp.extra 物件包含:
| 屬性 | 說明 |
|---|---|
authInfo | Middleware 中在 req.auth 設定的任何內容 |
sessionId | MCP 連線的 session 識別碼 |
signal | 用於取消 request 的 AbortSignal |
sendNotification | 用於傳送通知的 MCP protocol 函式 |
sendRequest | 用於傳送 request 的 MCP protocol 函式 |
完整範例「完整範例」的直接連結
安裝 jose,使用身分 Provider 的 JSON Web Key Set(JWKS)驗證 JSON Web Token(JWT):
- npm
- pnpm
- Yarn
- Bun
npm install jose
pnpm add jose
yarn add jose
bun add jose
下列範例會先驗證 token 的 signature、issuer、audience、algorithm、expiration 與必要 claim,再將使用者資料傳給 Tool:
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<string, unknown>
}
}
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)「mcp-apps-appresources」的直接連結
appResources 選項可讓 MCP server 透過 MCP Apps extension提供互動式 HTML UI。每個項目都會將 ui:// URI 對應至在 Mastra Studio 的 Sandbox iframe 中呈現的 HTML app。
AppResources 型別「appresources-type」的直接連結
Key (URI):
ui:// URI(例如 ui://calculator/main)。每個值都是 AppResource 物件:
name:
description?:
html?:
html 或 htmlPath 其中之一。htmlPath?:
html 或 htmlPath 其中之一。meta?:
範例「範例」的直接連結
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: '<html><body><h2>Calculator</h2>...</body></html>',
},
},
})
在 Tool 上將 _meta.ui.resourceUri 設為相符的 ui:// URI,即可將 Tool 連結至 app resource。Server 註冊 Tool 時會自動正規化此中繼資料。完整的 app bridge API 與使用模式請參閱 MCP Apps。
相關資訊「相關資訊」的直接連結
- 如需在 Mastra 中連線至 MCP server,請參閱 MCPClient 文件。
- 如需 Model Context Protocol 的更多資訊,請參閱 @modelcontextprotocol/sdk 文件。