> Discover all available pages from the documentation index: https://mastra.zisheng.pro/ko/llms.txt # MCPServer The `MCPServer` class는 기존 Mastra Tool과 Agent를 Model Context Protocol(MCP) 서버로 노출하는 기능을 제공합니다. 이를 통해 모든 MCP 클라이언트(예: Cursor, Windsurf 또는 Claude Desktop)가 이러한 기능에 연결하여 Agent에서 사용할 수 있습니다. Tool 또는 Agent를 Mastra 애플리케이션 내에서 직접 사용하기만 하면 되는 경우에는 반드시 MCP 서버를 생성할 필요가 없습니다. 이 API는 Mastra Tool과 Agent를 _external_ MCP clients. It supports both [stdio (subprocess) and SSE (HTTP) MCP transports](https://modelcontextprotocol.io/docs/concepts/transports). ## Constructor To create a new `MCPServer`에 노출하기 위한 것입니다. 서버에 대한 기본 정보, 서버에서 제공할 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" }, }) ``` ### Configuration Properties The constructor accepts an `MCPServerConfig` object with the following properties: **id** (`string`): Unique identifier for the server. This ID is preserved when the server is registered with Mastra and can be used to retrieve the server via getMCPServerById(). **name** (`string`): A descriptive name for your server (e.g., 'My Weather and Agent Server'). **version** (`string`): The semantic version of your server (e.g., '1.0.0'). **tools** (`ToolsInput`): An object where keys are tool names and values are Mastra tool definitions (created with createTool or Vercel AI SDK). These tools will be directly exposed. **agents** (`Record`): An object where keys are agent identifiers and values are Mastra Agent instances. Each agent will be automatically converted into a tool named ask\_\. The agent \*\*must\*\* have a non-empty description string property defined in its constructor configuration. This description will be used in the tool's description. If an agent's description is missing or empty, an error will be thrown during MCPServer initialization. **workflows** (`Record`): An object where keys are workflow identifiers and values are Mastra Workflow instances. Each workflow is converted into a tool named run\_\. The workflow's inputSchema becomes the tool's input schema. The workflow \*\*must\*\* have a non-empty description string property, which is used for the tool's description. If a workflow's description is missing or empty, an error will be thrown. The tool executes the workflow by calling workflow\.createRun() followed by run.start({ inputData: \ }). If a tool name derived from an agent or workflow (e.g., ask\_myAgent or run\_myWorkflow) collides with an explicitly defined tool name or another derived name, the explicitly defined tool takes precedence, and a warning is logged. Agents/workflows leading to subsequent collisions are skipped. **description** (`string`): Optional description of what the MCP server does. **instructions** (`string`): Optional instructions describing how to use the server and its features. **mapAuthInfoToUser** (`({ authInfo, extra, requestContext }) => unknown | null | undefined | Promise`): Maps MCP transport auth data from extra.authInfo into the user value used by Mastra FGA checks. Use this when an OAuth-protected MCP server is registered on a Mastra instance with an FGA provider. **fga** (`{ resourceMapping?: Partial string | undefined }>>; permissionMapping?: Record }`): Overrides resource and permission mappings for this MCP server's tools/list and tools/call FGA checks. Use this when MCP authorization should be scoped differently from internal agent or workflow tool execution. **repository** (`Repository`): Optional repository information for the server's source code. **releaseDate** (`string`): Optional release date of this server version (ISO 8601 string). Defaults to the time of instantiation if not provided. **isLatest** (`boolean`): Optional flag indicating if this is the latest version. Defaults to true if not provided. **packageCanonical** (`'npm' | 'docker' | 'pypi' | 'crates' | string`): Optional canonical packaging format if the server is distributed as a package (e.g., 'npm', 'docker'). **packages** (`PackageInfo[]`): Optional list of installable packages for this server. **remotes** (`RemoteInfo[]`): Optional list of remote access points for this server. **resources** (`MCPServerResources`): An object defining how the server should handle MCP resources. See Resource Handling section for details. **prompts** (`MCPServerPrompts`): An object defining how the server should handle MCP prompts. See Prompt Handling section for details. **appResources** (`AppResources`): A map of ui:// URIs to app resource configurations. Each entry defines an interactive HTML UI served via the MCP Apps extension (SEP-1865). See the MCP Apps section for details. ## Exposing agents as tools A powerful feature of `MCPServer` 의 기능 중 하나는 Mastra Agent를 호출 가능한 Tool로 자동 노출하는 것입니다. `agents` property of the configuration: - **Tool Naming**: Each agent is converted into a tool named `ask_`, where `` is the key you used for that agent in the `agents` object. For instance, if you configure `agents: { myAgentKey: myAgentInstance }`, a tool named `ask_myAgentKey` will be created. - **Tool Functionality**: - **Description**: 생성된 Tool의 설명은 다음 형식으로 제공됩니다: "Ask agent `` a question. Original agent instructions: ``". - **Input**: 이 Tool은 단일 객체 인수를 필요로 하며, 이 객체에는 `message` property (string): `{ message: "Your question for the agent" }`. - **Execution**: 이 Tool이 호출되면 해당 Agent의 `generate()` method with the provided `query`. - **Output**: The direct result from the agent's `generate()` 메서드가 Tool의 출력으로 반환됩니다. - **Name collisions.** If an explicit tool defined in the `tools` 구성에 Agent에서 파생된 Tool과 이름이 같은 Tool이 있는 경우(예: 이름이 `ask_myAgentKey` alongside an agent keyed as `myAgentKey`), the _explicitly defined tool will take precedence_. 이처럼 충돌하는 경우 Agent는 Tool로 변환되지 않으며 경고가 기록됩니다. 이를 통해 다른 Tool과 마찬가지로 MCP 클라이언트가 자연어 쿼리를 사용해 Agent와 쉽게 상호 작용할 수 있습니다. ### Agent-to-Tool Conversion When you provide agents in the `agents` configuration property, `MCPServer` 는 각 Agent에 대응하는 Tool을 자동으로 생성합니다. 이 Tool은 `ask_`, where `` is the key you used in the `agents` object. 이렇게 생성된 Tool의 설명은 다음과 같습니다: "Ask agent `` a question. Agent description: ``". For an agent to be converted into a tool, it **must** have a non-empty `description` 인스턴스화할 때 구성에 설정된 문자열 속성(예: `new Agent({ id: 'my-agent', name: 'myAgent', description: 'This agent does X.', ... })`). If an agent is passed to `MCPServer` with a missing or empty `description`, an error will be thrown when the `MCPServer` is instantiated, and server setup will fail. 이를 통해 MCP를 경유하여 Agent의 생성 기능을 빠르게 공개할 수 있으므로 클라이언트가 Agent에게 직접 질문할 수 있습니다. ### Accessing MCP Context in Tools Tools exposed through `MCPServer` 는 Tool이 호출되는 방식에 따라 서로 다른 두 속성을 통해 MCP 요청 컨텍스트(인증, 세션 ID 등)에 액세스할 수 있습니다: | Call Pattern | Access Method | | ---------------- | ------------------------------------------- | | Direct tool call | `context?.mcp?.extra` | | Agent tool call | `context?.requestContext?.get("mcp.extra")` | **Universal pattern** (works in both contexts): ```typescript const mcpExtra = context?.mcp?.extra ?? context?.requestContext?.get('mcp.extra') const authInfo = mcpExtra?.authInfo ``` #### Example: Tool that works in both contexts ```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() }, }) ``` ## Methods These are the functions you can call on an `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 ``` Here's an example of how you might use `startSSE` 를 HTTP 서버 요청 핸들러 내에서 사용합니다. 이 예시에서 MCP 클라이언트는 다음 주소로 MCP 서버에 연결할 수 있습니다: `http://localhost:1234/sse`: ```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` method: **url** (`URL`): The web address the user is requesting. **ssePath** (`string`): The specific part of the URL where clients will connect for SSE (e.g., '/sse'). **messagePath** (`string`): The specific part of the URL where clients will send messages (e.g., '/message'). **req** (`any`): The incoming request object from your web server. **res** (`any`): The response object from your web server, used to send data back. ### `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 ``` Here's an example of how you might use `startHonoSSE` 를 HTTP 서버 요청 핸들러 내에서 사용합니다. 이 예시에서 MCP 클라이언트는 다음 주소로 MCP 서버에 연결할 수 있습니다: `http://localhost:1234/hono-sse`: ```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` method: **url** (`URL`): The web address the user is requesting. **ssePath** (`string`): The specific part of the URL where clients will connect for SSE (e.g., '/hono-sse'). **messagePath** (`string`): The specific part of the URL where clients will send messages (e.g., '/message'). **req** (`any`): The incoming request object from your web server. **res** (`any`): The response object from your web server, used to send data back. ### `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 ``` Here's an example of how you might use `startHTTP` 를 HTTP 서버 요청 핸들러 내에서 사용합니다. 이 예시에서 MCP 클라이언트는 다음 주소로 MCP 서버에 연결할 수 있습니다: `http://localhost:1234/http`: ```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}`) }) ``` For **serverless environments** (Supabase Edge Functions, Cloudflare Workers, Vercel Edge 등)에서는 `serverless: true` to enable stateless operation: ```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 }) }) ``` > **When to use serverless: true:** Use `serverless: true` 각 요청이 새로운 무상태 실행 컨텍스트에서 실행되는 환경에 배포할 때는 다음을 사용합니다: > > - Supabase Edge Functions > - Cloudflare Workers > - Vercel Edge Functions > - Netlify Edge Functions > - AWS Lambda > - Deno Deploy > > Use the default session-based mode (without `serverless: true`) for: > > - Long-lived Node.js servers > - Docker containers > - Traditional hosting (VPS, dedicated servers) > > 서버리스 모드는 세션 관리를 비활성화하고 요청마다 새로운 서버 인스턴스를 생성합니다. 이는 호출 사이에 Memory가 유지되지 않는 무상태 환경에 필요합니다. > > 기본적으로 서버리스 모드는 각 요청을 버퍼링하여 단일 JSON 응답으로 반환하므로 `notifications/progress` sent by a tool never reach the client. Set `serverlessStreaming: true` 대신 요청 범위의 SSE 스트리밍으로 요청을 처리하려면 다음을 사용합니다. 이 방식은 최종 결과 전에 진행 상황 알림을 전달합니다: > > ```typescript > await server.startHTTP({ > url, > httpPath: '/mcp', > req: nodeReq, > res: nodeRes, > options: { > serverless: true, > serverlessStreaming: true, // ← Stream request-scoped notifications/progress > }, > }) > ``` > > This is still stateless: no `mcp-session-id` 는 필요하지 않으며 유지되지도 않습니다. 현재 요청 범위의 알림(예: 진행 상황)만 활성화합니다. 아래의 세션 종속 기능은 계속 사용할 수 없습니다. > > 다음 MCP 기능에는 세션 상태 또는 지속적인 연결이 필요하며 **won't work** in serverless mode (including with `serverlessStreaming: true`): > > - **Elicitation** - Tool 실행 중 대화형 사용자 입력 요청의 응답을 올바른 클라이언트로 전달하려면 세션 관리가 필요합니다 > - **Resource subscriptions** - `resources/subscribe` and `resources/unsubscribe` need persistent connections to maintain subscription state > - **Resource update notifications** - `resources.notifyUpdated()` 가 클라이언트에 알림을 보내려면 활성 구독과 지속적인 연결이 필요합니다 > - **Prompt list change notifications** - `prompts.notifyListChanged()` 가 클라이언트에 업데이트를 푸시하려면 지속적인 연결이 필요합니다 > - **Tool list change notifications** - `toolActions.notifyListChanged()` 가 클라이언트에 업데이트를 푸시하려면 지속적인 연결이 필요합니다 > - **Server log notifications** - `sendLoggingMessage()` 가 클라이언트에 로그 메시지를 푸시하려면 지속적인 연결이 필요합니다 > > 이러한 기능은 장기 실행 서버 환경(Node.js 서버, Docker 컨테이너 등)에서 정상적으로 작동합니다. 에 필요한 값의 세부 정보는 다음과 같습니다: `startHTTP` method: **url** (`URL`): The web address the user is requesting. **httpPath** (`string`): The specific part of the URL where the MCP server will handle HTTP requests (e.g., '/mcp'). **req** (`http.IncomingMessage`): The incoming request object from your web server. **res** (`http.ServerResponse`): The response object from your web server, used to send data back. **options** (`StreamableHTTPServerTransportOptions`): Optional configuration for the HTTP transport. See the options table below for more details. The `StreamableHTTPServerTransportOptions` 객체를 사용하면 HTTP 전송 방식의 동작을 사용자 지정할 수 있습니다. 사용 가능한 옵션은 다음과 같습니다: **serverless** (`boolean`): If true, runs in stateless mode without session management. Each request is handled independently with a fresh server instance. Essential for serverless environments (Cloudflare Workers, Supabase Edge Functions, Vercel Edge, etc.) where sessions cannot persist between invocations. Defaults to false. **serverlessStreaming** (`boolean`): If true, serverless requests use request-scoped SSE streaming instead of a buffered JSON response, allowing in-request notifications/progress to reach the client before the final result. Only takes effect together with serverless: true. Defaults to false (buffered JSON responses), which preserves backward-compatible behavior. It enables only request-scoped notifications such as progress; elicitation, subscriptions, and out-of-request notifications still require session state. **sessionIdGenerator** (`(() => string) | undefined`): A function that generates a unique session ID. This should be a cryptographically secure, globally unique string. Return undefined to disable session management. **onsessioninitialized** (`(sessionId: string) => void`): A callback that is invoked when a new session is initialized. This is useful for tracking active MCP sessions. **enableJsonResponse** (`boolean`): If true, the server will return plain JSON responses instead of using Server-Sent Events (SSE) for streaming. Defaults to false. **eventStore** (`EventStore`): An event store for message resumability. Providing this enables clients to reconnect and resume message streams. ### `close()` 이 메서드는 서버를 종료하고 모든 리소스를 해제합니다. ```typescript async close(): Promise ``` ### `getServerInfo()` The method returns the server's basic information. ```typescript getServerInfo(): ServerInfo ``` ### `getServerDetail()` 이 메서드는 서버 정보의 세부 내용을 반환합니다. ```typescript getServerDetail(): ServerDetail ``` ### `getToolListInfo()` 이 메서드는 서버를 생성할 때 설정된 Tool을 반환합니다. 디버깅에 유용한 읽기 전용 목록입니다. ```typescript getToolListInfo(): ToolListInfo ``` ### `getToolInfo()` 이 메서드는 특정 Tool의 세부 정보를 반환합니다. ```typescript getToolInfo(toolName: string): ToolInfo ``` ### `executeTool()` 이 메서드는 특정 Tool을 실행하고 결과를 반환합니다. ```typescript executeTool(toolName: string, input: any): Promise ``` ### `getStdioTransport()` If you started the server with `startStdio()`, 이를 사용하여 stdio 통신을 관리하는 객체를 가져올 수 있습니다. 주로 내부 상태 확인이나 테스트에 사용합니다. ```typescript getStdioTransport(): StdioServerTransport | undefined ``` ### `getSseTransport()` If you started the server with `startSSE()`, 이를 사용하여 SSE 통신을 관리하는 객체를 가져올 수 있습니다. `getStdioTransport`와 마찬가지로 주로 내부 상태 확인이나 테스트에 사용합니다. ```typescript getSseTransport(): SSEServerTransport | undefined ``` ### `getSseHonoTransport()` If you started the server with `startHonoSSE()`, 이를 사용하여 SSE 통신을 관리하는 객체를 가져올 수 있습니다. `getSseTransport`와 마찬가지로 주로 내부 상태 확인이나 테스트에 사용합니다. ```typescript getSseHonoTransport(): SSETransport | undefined ``` ### `getStreamableHTTPTransport()` If you started the server with `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`): The ID/name of the tool to execute. **args** (`any`): The arguments to pass to the tool's execute function. **executionContext** (`object`): Optional context for the tool execution, like messages or a toolCallId. ## Resource handling ### What are MCP Resources? 리소스는 서버가 클라이언트에서 읽고 LLM 상호 작용의 컨텍스트로 사용할 수 있는 데이터와 콘텐츠를 공개할 수 있도록 하는 Model Context Protocol(MCP)의 핵심 기본 요소입니다. 리소스는 MCP 서버가 제공하려는 모든 종류의 데이터를 나타낼 수 있으며, 예시는 다음과 같습니다: - File contents - Database records - API responses - Live system data - Screenshots and images - Log files 리소스는 고유한 URI(예: `file:///home/user/documents/report.pdf`, `postgres://database/customers/schema`)로 식별되며 텍스트(UTF-8 인코딩) 또는 바이너리 데이터(base64 인코딩)를 포함할 수 있습니다. Clients can discover resources through: 1. **Direct resources**: 서버는 다음을 통해 구체적인 리소스 목록을 공개합니다: `resources/list` endpoint. 2. **Resource templates**: 런타임에 정의되는 리소스의 경우 서버는 클라이언트가 리소스 URI를 구성하는 데 사용하는 URI 템플릿(RFC 6570)을 공개할 수 있습니다. To read a resource, clients make a `resources/read` 요청에 URI를 포함합니다. 클라이언트가 해당 리소스를 구독한 경우 서버는 리소스 목록의 변경 사항(`notifications/resources/list_changed`) or updates to specific resource content (`notifications/resources/updated`)도 클라이언트에 알릴 수 있습니다. For more detailed information, refer to the [official MCP documentation on Resources](https://modelcontextprotocol.io/docs/concepts/resources). ### `MCPServerResources` Type The `resources` option takes an object of type `MCPServerResources`. 이 유형은 서버가 리소스 요청을 처리하는 데 사용할 콜백을 정의합니다: ```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 } ``` Example: ```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, }) ``` ### Notifying Clients of Resource Changes 사용 가능한 리소스나 그 콘텐츠가 변경되면 서버는 해당 리소스를 구독 중인 연결된 클라이언트에 알릴 수 있습니다. #### `server.resources.notifyUpdated({ uri: string })` 특정 리소스의 콘텐츠(해당 리소스는 `uri`)가 업데이트되었을 때 이 메서드를 호출하세요. 이 URI를 구독 중인 클라이언트가 있으면 해당 클라이언트는 `notifications/resources/updated` message. ```typescript async server.resources.notifyUpdated({ uri: string }): Promise ``` Example: ```typescript // After updating the content of 'file://data.txt' await serverWithResources.resources.notifyUpdated({ uri: 'file://data.txt' }) ``` #### `server.resources.notifyListChanged()` 사용 가능한 리소스 목록이 변경되었을 때(예: 리소스가 추가되거나 제거된 경우) 이 메서드를 호출하세요. 그러면 `notifications/resources/list_changed` 메시지가 클라이언트에 전송되어 리소스 목록을 다시 가져오도록 요청합니다. ```typescript async server.resources.notifyListChanged(): Promise ``` Example: ```typescript // After adding a new resource to the list managed by 'myResourceHandlers.listResources' await serverWithResources.resources.notifyListChanged() ``` ## Prompt handling ### What are MCP Prompts? Prompt는 MCP 서버가 클라이언트에 제공하는 재사용 가능한 템플릿 또는 Workflow입니다. 인수를 받을 수 있고 리소스 컨텍스트를 포함할 수 있습니다. 또한 버전 관리를 지원하고 LLM 상호작용을 표준화합니다. Prompt는 고유한 이름과 선택적 버전으로 식별되며, 런타임에 정의하거나 정적으로 정의할 수 있습니다. ### `MCPServerPrompts` Type The `prompts` option takes an object of type `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[] }> } ``` Example: ```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, }) ``` ### Notifying Clients of Prompt Changes 사용 가능한 Prompt가 변경되면 서버가 연결된 클라이언트에 알림을 보낼 수 있습니다: #### `server.prompts.notifyListChanged()` 사용 가능한 Prompt 목록이 변경되었을 때(예: Prompt가 추가되거나 제거된 경우) 이 메서드를 호출하세요. 그러면 `notifications/prompts/list_changed` 메시지가 클라이언트에 전송되어 Prompt 목록을 다시 가져오도록 요청합니다. ```typescript await serverWithPrompts.prompts.notifyListChanged() ``` ### Best practices for Prompt Handling - Use clear, descriptive prompt names and descriptions. - Validate all required arguments in `getPromptMessages`. - Include a `version` 필드는 호환성을 깨는 변경이 예상될 때 사용하세요. - Use the `version` parameter to select the correct prompt logic. - Notify clients when prompt lists change. - Handle errors with informative messages. - Document argument expectations and available versions. ## Dynamic tool management Tools are usually provided when constructing the `MCPServer`, 서버가 실행 중인 동안에도 Tool을 추가하거나 제거할 수 있습니다. 서버는 이러한 작업을 `toolActions` 속성을 통해 제공합니다. Tool 목록이 변경되면 연결된 클라이언트는 `notifications/tools/list_changed` 메시지를 받아 Tool 목록을 다시 가져오라는 요청을 받습니다. The property is `toolActions` because `tools()` 는 등록된 Tool 레지스트리를 반환하는 메서드입니다. ### `toolActions.add(tools)` 실행 중인 서버에 새 Tool을 등록하고 연결된 클라이언트에 알립니다. Tool은 생성자에 전달된 Tool과 마찬가지로 레코드 키를 기준으로 관리됩니다. 기존 키에 Tool을 추가하면 해당 Tool이 대체됩니다. ```typescript async server.toolActions.add(tools: ToolsInput): Promise ``` Example: ```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를 기준으로 실행 중인 서버에서 Tool을 제거하고 연결된 클라이언트에 알립니다. 알 수 없는 Tool ID는 무시됩니다. 하나 이상의 Tool이 제거된 경우에만 알림이 전송됩니다. ```typescript async server.toolActions.remove(toolIds: string[]): Promise ``` Example: ```typescript await server.toolActions.remove(['searchTool']) ``` ### `toolActions.notifyListChanged()` Sends a `notifications/tools/list_changed` 메시지를 Tool 레지스트리를 수정하지 않고 연결된 클라이언트에 전송합니다. 다른 방식으로 Tool 가용성이 변경될 때(예: 권한 부여가 변경된 경우) 이를 호출하세요. ```typescript async server.toolActions.notifyListChanged(): Promise ``` ### Mastra registry synchronization 서버가 Mastra 인스턴스에 등록되면 `toolActions.add()` and `toolActions.remove()` 도 Mastra 인스턴스의 Tool 레지스트리를 업데이트하여 시작 시 이루어지는 자동 Tool 등록과 동일하게 동작합니다. 추가된 Tool은 `mastra.listTools()` (keyed by the tool's intrinsic `id` 가 있는 경우 이를 통해 사용할 수 있으며, 제거된 Tool은 레지스트리에서 삭제됩니다. ## Logging MCP 서버는 `notifications/message`. Clients control verbosity by sending a `logging/setLevel` 요청을 사용하여 구조화된 로그 메시지를 클라이언트에 전송할 수 있습니다. 서버는 요청된 최소 레벨보다 낮은 메시지를 삭제합니다(RFC 5424 심각도 순서 준수). 레벨은 세션별로 추적되므로 클라이언트마다 서로 다른 상세 수준을 요청할 수 있습니다. ### `sendLoggingMessage()` 각 클라이언트의 최소 로깅 레벨을 준수하여 연결된 모든 클라이언트에 로그 알림을 전송합니다. ```typescript async server.sendLoggingMessage(params: { level: LoggingLevel; data: unknown; logger?: string; }): Promise ``` Example: ```typescript await server.sendLoggingMessage({ level: 'info', data: { message: 'Sync completed', itemsProcessed: 42 }, }) ``` ### `context.mcp.log()` Inside a tool's `execute` function, use `context.mcp.log()` 를 사용하여 Tool을 호출한 클라이언트에 로그 메시지를 전송합니다. ```typescript async context.mcp.log( level: LoggingLevel, message: string, data?: Record ): Promise ``` Example: ```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 } ``` ## Progress notifications 장시간 실행되는 Tool은 `notifications/progress`를 사용하여 호출한 클라이언트에 진행 상황을 보고할 수 있습니다. 호출자가 `progressToken` in the request (the Mastra `MCPClient` does this when `enableProgressTracking` is set). When no token was sent, `context.mcp.progress()` is a no-op. ### `context.mcp.progress()` ```typescript async context.mcp.progress(params: { progress: number; total?: number; message?: string; }): Promise ``` Example: ```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 } } ``` ## Notification delivery Notification methods (`resources.notifyListChanged()`, `prompts.notifyListChanged()`, `toolActions.notifyListChanged()`, and `sendLoggingMessage()`)는 모든 전송 방식에서 연결된 모든 클라이언트, 즉 stdio/SSE 연결과 각 스트리밍 가능 HTTP 세션에 브로드캐스트됩니다. `resources.notifyUpdated()` 는 예외로, `resources/subscribe`를 통해 리소스 URI를 구독한 클라이언트에만 알림을 보냅니다. 스트리밍 가능 HTTP 클라이언트의 구독은 세션별로 추적됩니다. 레거시 SSE 클라이언트는 기본 서버 인스턴스를 공유하므로 하나의 구독 집합도 공유합니다. 상태 비저장 서버리스 모드를 사용하는 클라이언트는 요청마다 임시 서버 인스턴스를 사용하므로 알림을 받을 수 없습니다. ## Examples MCPServer 설정 및 배포에 관한 실용적인 예시는 [Publishing an MCP Server guide](https://mastra.zisheng.pro/ko/guides/guide/publishing-mcp-server). 이 페이지의 시작 부분에 있는 예제에서는 `MCPServer` with both tools and agents. ## Elicitation ### What's Elicitation? Elicitation은 서버가 사용자에게 구조화된 정보를 요청할 수 있도록 하는 Model Context Protocol(MCP)의 기능입니다. 서버가 런타임에 추가 데이터를 수집할 수 있는 대화형 Workflow를 지원합니다. The `MCPServer` 클래스에는 Elicitation 기능이 자동으로 포함됩니다. Tool은 `context.mcp` object in their `execute` function that includes an `elicitation.sendRequest()` method for requesting user input. ### Tool Execution Signature Tool이 MCP 서버 컨텍스트 내에서 실행되면 `context.mcp` object: ```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 } ``` ### How Elicitation Works 를 통해 MCP 전용 기능을 전달받습니다. 일반적인 사용 사례는 Tool 실행 중에 발생합니다. Tool에 사용자 입력이 필요하면 컨텍스트 매개변수를 통해 제공되는 Elicitation 기능을 사용할 수 있습니다: 1. The tool calls `context.mcp.elicitation.sendRequest()` with a message and schema 2. 요청이 연결된 MCP 클라이언트로 전송됩니다 3. 클라이언트가 사용자에게 요청을 표시합니다(UI, 명령줄 등 사용) 4. 사용자가 입력을 제공하거나 요청을 거절 또는 취소합니다 5. 클라이언트가 응답을 서버로 다시 전송합니다 6. Tool이 응답을 받고 실행을 계속합니다 ### Using Elicitation in Tools 다음은 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 The `requestedSchema` 는 기본 타입 속성만 포함하는 평면 객체여야 합니다. 지원되는 타입은 다음과 같습니다: - **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'] }` Example 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'], } ``` ### Response Actions 사용자는 다음 세 가지 방법으로 Elicitation 요청에 응답할 수 있습니다: 1. **Accept** (`action: 'accept'`): User provided data and confirmed submission - Contains `content` field with the submitted data 2. **Decline** (`action: 'decline'`): User explicitly declined to provide information - No content field 3. **Cancel** (`action: 'cancel'`): User dismissed the request without deciding - No content field Tool은 세 가지 응답 타입을 모두 적절하게 처리해야 합니다. ### Security Considerations - **Never request sensitive information** like passwords, SSNs, or credit card numbers - 제공된 스키마를 기준으로 모든 사용자 입력을 검증하세요 - Handle declining and cancellation gracefully - Provide clear reasons for data collection - Respect user privacy and preferences ### Tool Execution API The elicitation functionality is available through the `options` parameter in tool execution: ```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. } } ``` Elicitation is **session-aware** 는 HTTP 기반 전송 방식(SSE 또는 HTTP)을 사용할 때 적용됩니다. 여러 클라이언트가 동일한 서버에 연결되어 있으면 Elicitation 요청은 Tool 실행을 시작한 클라이언트 세션으로 라우팅됩니다. The `ElicitResult` type: ```typescript type ElicitResult = { action: 'accept' | 'decline' | 'cancel' content?: any // Only present when action is 'accept' } ``` ## OAuth protection 에 따라 OAuth 인증으로 MCP 서버를 보호하려면 [MCP Auth Specification](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization), use the `createOAuthMiddleware` function: ```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) ``` The middleware automatically: - Serves **Protected Resource Metadata** at `/.well-known/oauth-protected-resource` (RFC 9728) - Returns `401 Unauthorized` with proper `WWW-Authenticate` headers when authentication is required - Validates bearer tokens using your provided validator ### Token Validation For production, use proper token validation: ```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 Options **oauth.resource** (`string`): The canonical URL of your MCP server. This is returned in Protected Resource Metadata. **oauth.authorizationServers** (`string[]`): URLs of authorization servers that can issue tokens for this resource. **oauth.scopesSupported** (`string[]`): Scopes supported by this MCP server. (Default: `['mcp:read', 'mcp:write']`) **oauth.resourceName** (`string`): Human-readable name for this resource server. **oauth.validateToken** (`(token: string, resource: string) => Promise`): Function to validate access tokens. If not provided, tokens are accepted without validation (NOT recommended for production). **mcpPath** (`string`): Path where the MCP endpoint is served. Only requests to this path require authentication. (Default: `'/mcp'`) ## Authentication context Tools can access request metadata via `context.mcp.extra` 를 HTTP 기반 전송 방식에서 사용하세요. 이를 통해 HTTP 미들웨어의 인증 정보, 사용자 컨텍스트 또는 사용자 지정 데이터를 MCP Tool에 전달할 수 있습니다. ### How it works Whatever you set on `req.auth` in your HTTP middleware becomes available as `context.mcp.extra.authInfo` in your tools: ```text req.auth = { ... } → context?.mcp?.extra?.authInfo.extra = { ... } ``` ### Map auth data for FGA When an `MCPServer` 가 세분화된 권한 부여(FGA) Provider가 있는 Mastra 인스턴스에 등록되면 Mastra는 Tool을 나열하거나 호출하기 전에 `requestContext.get('user')` 를 확인합니다. HTTP MCP 전송 방식은 인증된 데이터를 `extra.authInfo`, so use `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, } }, }) ``` ### Scope MCP tool FGA separately Use `fga.resourceMapping` and `fga.permissionMapping` 는 MCP 클라이언트에 내부 Agent 또는 Workflow의 Tool 실행과 다른 권한 부여 범위가 필요할 때 사용하세요. 재정의는 `tools/list` and `tools/call` checks for this MCP server. ```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', }, }, }) ``` ### Setting Up Authentication Middleware To pass data to your tools, populate `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 }) }) ``` ### Accessing Auth Data in Tools The `req.auth` object is available as `context.mcp.extra.authInfo` in your tool's execute function: ```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() } ``` ### Passing `RequestContext` through to 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 } ``` ### The `extra` Object The full `context.mcp.extra` object contains: | Property | Description | | ------------------ | ------------------------------------------------- | | `authInfo` | Whatever you set on `req.auth` in your middleware | | `sessionId` | Session identifier for the MCP connection | | `signal` | AbortSignal for request cancellation | | `sendNotification` | MCP protocol function for sending notifications | | `sendRequest` | MCP protocol function for sending requests | ### Complete Example Install [`jose`](https://github.com/panva/jose) 를 호출하기 전에 HTTP 서버 미들웨어의 Node.js 요청 객체에 설정하여 ID 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 ``` 다음 예제는 사용자 데이터를 Tool에 전달하기 전에 Token의 서명, 발급자, 대상, 알고리즘, 만료 및 필수 클레임을 검증합니다: ```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`) The `appResources` 옵션을 사용하면 [MCP Apps extension](https://github.com/modelcontextprotocol/ext-apps). Each entry maps a `ui://` 를 통해 MCP 서버에서 대화형 HTML UI를 제공할 수 있습니다. URI는 Mastra Studio의 Sandbox iframe에서 렌더링되는 HTML 앱을 가리킵니다. ### `AppResources` type **Key (URI)** (`string`): A ui:// URI that identifies the app resource (e.g., ui://calculator/main). Each value is an `AppResource` object: **name** (`string`): Display name for the UI resource. **description** (`string`): Optional description of the UI resource. **html** (`string`): Inline HTML content for the UI. Provide either html or htmlPath. **htmlPath** (`string`): Path to an HTML file. Resolved at server startup. Provide either html or htmlPath. **meta** (`McpUiResourceMeta`): UI resource metadata (CSP, permissions, rendering preferences) from the official ext-apps SDK. ### Example ```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

...', }, }, }) ``` Link a tool to its app resource by setting `_meta.ui.resourceUri` on the tool to the matching `ui://` URI입니다. 서버는 Tool을 등록할 때 이 메타데이터를 자동으로 정규화합니다. [MCP Apps](https://mastra.zisheng.pro/ko/docs/mcp/overview) 에서 전체 앱 브리지 API와 사용 패턴을 확인하세요. ## Related information - Mastra에서 MCP 서버에 연결하는 방법은 [MCPClient documentation](https://mastra.zisheng.pro/ko/reference/tools/mcp-client). - Model Context Protocol에 관한 자세한 내용은 [@modelcontextprotocol/sdk documentation](https://github.com/modelcontextprotocol/typescript-sdk).