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.
ConstructorConstructor에 대한 직접 링크
To create a new MCPServer에 노출하기 위한 것입니다. 서버에 대한 기본 정보, 서버에서 제공할 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"
},
})
Configuration PropertiesConfiguration Properties에 대한 직접 링크
The constructor accepts an MCPServerConfig object with the following properties:
id:
name:
version:
tools:
createTool or Vercel AI SDK). These tools will be directly exposed.agents?:
ask_<agentIdentifier>. 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?:
run_<workflowKey>. 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: <tool_input> }). 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?:
instructions?:
mapAuthInfoToUser?:
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?:
tools/list and tools/call FGA checks. Use this when MCP authorization should be scoped differently from internal agent or workflow tool execution.repository?:
releaseDate?:
isLatest?:
packageCanonical?:
packages?:
remotes?:
resources?:
prompts?:
appResources?:
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 toolsExposing 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_<agentKey>, where<agentKey>is the key you used for that agent in theagentsobject. For instance, if you configureagents: { myAgentKey: myAgentInstance }, a tool namedask_myAgentKeywill be created. -
Tool Functionality:
- Description: 생성된 Tool의 설명은 다음 형식으로 제공됩니다: "Ask agent
<AgentName>a question. Original agent instructions:<agent description>". - Input: 이 Tool은 단일 객체 인수를 필요로 하며, 이 객체에는
messageproperty (string):{ message: "Your question for the agent" }. - Execution: 이 Tool이 호출되면 해당 Agent의
generate()method with the providedquery. - Output: The direct result from the agent's
generate()메서드가 Tool의 출력으로 반환됩니다.
- Description: 생성된 Tool의 설명은 다음 형식으로 제공됩니다: "Ask agent
-
Name collisions. If an explicit tool defined in the
tools구성에 Agent에서 파생된 Tool과 이름이 같은 Tool이 있는 경우(예: 이름이ask_myAgentKeyalongside an agent keyed asmyAgentKey), the explicitly defined tool will take precedence. 이처럼 충돌하는 경우 Agent는 Tool로 변환되지 않으며 경고가 기록됩니다.
이를 통해 다른 Tool과 마찬가지로 MCP 클라이언트가 자연어 쿼리를 사용해 Agent와 쉽게 상호 작용할 수 있습니다.
Agent-to-Tool ConversionAgent-to-Tool Conversion에 대한 직접 링크
When you provide agents in the agents configuration property, MCPServer 는 각 Agent에 대응하는 Tool을 자동으로 생성합니다. 이 Tool은 ask_<agentIdentifier>, where <agentIdentifier> is the key you used in the agents object.
이렇게 생성된 Tool의 설명은 다음과 같습니다: "Ask agent <agent.name> a question. Agent description: <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 ToolsAccessing 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):
const mcpExtra = context?.mcp?.extra ?? context?.requestContext?.get('mcp.extra')
const authInfo = mcpExtra?.authInfo
Example: Tool that works in both contextsExample: Tool that works in both contexts에 대한 직접 링크
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()
},
})
MethodsMethods에 대한 직접 링크
These are the functions you can call on an MCPServer 인스턴스를 사용하여 동작을 제어하고 정보를 가져올 수 있습니다.
startStdio()startstdio에 대한 직접 링크
표준 입력 및 출력(stdio)을 사용해 통신하도록 서버를 시작하려면 이 메서드를 사용합니다. 일반적으로 서버를 명령줄 프로그램으로 실행할 때 사용합니다.
async startStdio(): Promise<void>
stdio를 사용하여 서버를 시작하는 방법은 다음과 같습니다:
const server = new MCPServer({
id: 'my-server',
name: 'My Server',
version: '1.0.0',
tools: {/* ... */},
})
await server.startStdio()
startSSE()startsse에 대한 직접 링크
이 메서드를 사용하면 MCP 서버를 기존 웹 서버와 통합하여 Server-Sent Events(SSE)로 통신할 수 있습니다. 웹 서버가 SSE 또는 메시지 경로에 대한 요청을 수신할 때 웹 서버 코드에서 이 메서드를 호출합니다.
async startSSE({
url,
ssePath,
messagePath,
req,
res,
}: {
url: URL;
ssePath: string;
messagePath: string;
req: any;
res: any;
}): Promise<void>
Here's an example of how you might use startSSE 를 HTTP 서버 요청 핸들러 내에서 사용합니다. 이 예시에서 MCP 클라이언트는 다음 주소로 MCP 서버에 연결할 수 있습니다: http://localhost:1234/sse:
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:
ssePath:
messagePath:
req:
res:
startHonoSSE()starthonosse에 대한 직접 링크
이 메서드를 사용하면 MCP 서버를 기존 웹 서버와 통합하여 Server-Sent Events(SSE)로 통신할 수 있습니다. 웹 서버가 SSE 또는 메시지 경로에 대한 요청을 수신할 때 웹 서버 코드에서 이 메서드를 호출합니다.
async startHonoSSE({
url,
ssePath,
messagePath,
req,
res,
}: {
url: URL;
ssePath: string;
messagePath: string;
req: any;
res: any;
}): Promise<void>
Here's an example of how you might use startHonoSSE 를 HTTP 서버 요청 핸들러 내에서 사용합니다. 이 예시에서 MCP 클라이언트는 다음 주소로 MCP 서버에 연결할 수 있습니다: http://localhost:1234/hono-sse:
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:
ssePath:
messagePath:
req:
res:
startHTTP()starthttp에 대한 직접 링크
이 메서드를 사용하면 MCP 서버를 기존 웹 서버와 통합하여 스트리밍 가능한 HTTP로 통신할 수 있습니다. 웹 서버가 HTTP 요청을 수신할 때 웹 서버 코드에서 이 메서드를 호출합니다.
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>
Here's an example of how you might use startHTTP 를 HTTP 서버 요청 핸들러 내에서 사용합니다. 이 예시에서 MCP 클라이언트는 다음 주소로 MCP 서버에 연결할 수 있습니다: http://localhost:1234/http:
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:
// 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 })
})
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 스트리밍으로 요청을 처리하려면 다음을 사용합니다. 이 방식은 최종 결과 전에 진행 상황 알림을 전달합니다:
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/subscribeandresources/unsubscribeneed 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:
httpPath:
req:
res:
options:
The StreamableHTTPServerTransportOptions 객체를 사용하면 HTTP 전송 방식의 동작을 사용자 지정할 수 있습니다. 사용 가능한 옵션은 다음과 같습니다:
serverless:
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:
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:
undefined to disable session management.onsessioninitialized:
enableJsonResponse:
true, the server will return plain JSON responses instead of using Server-Sent Events (SSE) for streaming. Defaults to false.eventStore:
close()close에 대한 직접 링크
이 메서드는 서버를 종료하고 모든 리소스를 해제합니다.
async close(): Promise<void>
getServerInfo()getserverinfo에 대한 직접 링크
The method returns the server's basic information.
getServerInfo(): ServerInfo
getServerDetail()getserverdetail에 대한 직접 링크
이 메서드는 서버 정보의 세부 내용을 반환합니다.
getServerDetail(): ServerDetail
getToolListInfo()gettoollistinfo에 대한 직접 링크
이 메서드는 서버를 생성할 때 설정된 Tool을 반환합니다. 디버깅에 유용한 읽기 전용 목록입니다.
getToolListInfo(): ToolListInfo
getToolInfo()gettoolinfo에 대한 직접 링크
이 메서드는 특정 Tool의 세부 정보를 반환합니다.
getToolInfo(toolName: string): ToolInfo
executeTool()executetool에 대한 직접 링크
이 메서드는 특정 Tool을 실행하고 결과를 반환합니다.
executeTool(toolName: string, input: any): Promise<any>
getStdioTransport()getstdiotransport에 대한 직접 링크
If you started the server with startStdio(), 이를 사용하여 stdio 통신을 관리하는 객체를 가져올 수 있습니다. 주로 내부 상태 확인이나 테스트에 사용합니다.
getStdioTransport(): StdioServerTransport | undefined
getSseTransport()getssetransport에 대한 직접 링크
If you started the server with startSSE(), 이를 사용하여 SSE 통신을 관리하는 객체를 가져올 수 있습니다. getStdioTransport와 마찬가지로 주로 내부 상태 확인이나 테스트에 사용합니다.
getSseTransport(): SSEServerTransport | undefined
getSseHonoTransport()getssehonotransport에 대한 직접 링크
If you started the server with startHonoSSE(), 이를 사용하여 SSE 통신을 관리하는 객체를 가져올 수 있습니다. getSseTransport와 마찬가지로 주로 내부 상태 확인이나 테스트에 사용합니다.
getSseHonoTransport(): SSETransport | undefined
getStreamableHTTPTransport()getstreamablehttptransport에 대한 직접 링크
If you started the server with startHTTP(), 이를 사용하여 HTTP 통신을 관리하는 객체를 가져올 수 있습니다. getSseTransport와 마찬가지로 주로 내부 상태 확인이나 테스트에 사용합니다.
getStreamableHTTPTransport(): StreamableHTTPServerTransport | undefined
tools()tools에 대한 직접 링크
이 MCP 서버에서 제공하는 특정 Tool을 실행합니다.
async executeTool(
toolId: string,
args: any,
executionContext?: { messages?: any[]; toolCallId?: string },
): Promise<any>
toolId:
args:
executionContext?:
Resource handlingResource handling에 대한 직접 링크
What are MCP Resources?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:
- Direct resources: 서버는 다음을 통해 구체적인 리소스 목록을 공개합니다:
resources/listendpoint. - 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.
MCPServerResources Typemcpserverresources-type에 대한 직접 링크
The resources option takes an object of type MCPServerResources. 이 유형은 서버가 리소스 요청을 처리하는 데 사용할 콜백을 정의합니다:
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 }
Example:
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,
})
Notifying Clients of Resource ChangesNotifying Clients of Resource Changes에 대한 직접 링크
사용 가능한 리소스나 그 콘텐츠가 변경되면 서버는 해당 리소스를 구독 중인 연결된 클라이언트에 알릴 수 있습니다.
server.resources.notifyUpdated({ uri: string })serverresourcesnotifyupdated-uri-string-에 대한 직접 링크
특정 리소스의 콘텐츠(해당 리소스는 uri)가 업데이트되었을 때 이 메서드를 호출하세요. 이 URI를 구독 중인 클라이언트가 있으면 해당 클라이언트는 notifications/resources/updated message.
async server.resources.notifyUpdated({ uri: string }): Promise<void>
Example:
// After updating the content of 'file://data.txt'
await serverWithResources.resources.notifyUpdated({ uri: 'file://data.txt' })
server.resources.notifyListChanged()serverresourcesnotifylistchanged에 대한 직접 링크
사용 가능한 리소스 목록이 변경되었을 때(예: 리소스가 추가되거나 제거된 경우) 이 메서드를 호출하세요. 그러면 notifications/resources/list_changed 메시지가 클라이언트에 전송되어 리소스 목록을 다시 가져오도록 요청합니다.
async server.resources.notifyListChanged(): Promise<void>
Example:
// After adding a new resource to the list managed by 'myResourceHandlers.listResources'
await serverWithResources.resources.notifyListChanged()
Prompt handlingPrompt handling에 대한 직접 링크
What are MCP Prompts?What are MCP Prompts?에 대한 직접 링크
Prompt는 MCP 서버가 클라이언트에 제공하는 재사용 가능한 템플릿 또는 Workflow입니다. 인수를 받을 수 있고 리소스 컨텍스트를 포함할 수 있습니다. 또한 버전 관리를 지원하고 LLM 상호작용을 표준화합니다.
Prompt는 고유한 이름과 선택적 버전으로 식별되며, 런타임에 정의하거나 정적으로 정의할 수 있습니다.
MCPServerPrompts Typemcpserverprompts-type에 대한 직접 링크
The prompts option takes an object of type MCPServerPrompts. 이 타입은 서버가 Prompt 요청을 처리할 때 사용할 콜백을 정의합니다:
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[] }>
}
Example:
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 ChangesNotifying Clients of Prompt Changes에 대한 직접 링크
사용 가능한 Prompt가 변경되면 서버가 연결된 클라이언트에 알림을 보낼 수 있습니다:
server.prompts.notifyListChanged()serverpromptsnotifylistchanged에 대한 직접 링크
사용 가능한 Prompt 목록이 변경되었을 때(예: Prompt가 추가되거나 제거된 경우) 이 메서드를 호출하세요. 그러면 notifications/prompts/list_changed 메시지가 클라이언트에 전송되어 Prompt 목록을 다시 가져오도록 요청합니다.
await serverWithPrompts.prompts.notifyListChanged()
Best practices for Prompt HandlingBest practices for Prompt Handling에 대한 직접 링크
- Use clear, descriptive prompt names and descriptions.
- Validate all required arguments in
getPromptMessages. - Include a
version필드는 호환성을 깨는 변경이 예상될 때 사용하세요. - Use the
versionparameter 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 managementDynamic 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)toolactionsaddtools에 대한 직접 링크
실행 중인 서버에 새 Tool을 등록하고 연결된 클라이언트에 알립니다. Tool은 생성자에 전달된 Tool과 마찬가지로 레코드 키를 기준으로 관리됩니다. 기존 키에 Tool을 추가하면 해당 Tool이 대체됩니다.
async server.toolActions.add(tools: ToolsInput): Promise<void>
Example:
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를 기준으로 실행 중인 서버에서 Tool을 제거하고 연결된 클라이언트에 알립니다. 알 수 없는 Tool ID는 무시됩니다. 하나 이상의 Tool이 제거된 경우에만 알림이 전송됩니다.
async server.toolActions.remove(toolIds: string[]): Promise<void>
Example:
await server.toolActions.remove(['searchTool'])
toolActions.notifyListChanged()toolactionsnotifylistchanged에 대한 직접 링크
Sends a notifications/tools/list_changed 메시지를 Tool 레지스트리를 수정하지 않고 연결된 클라이언트에 전송합니다. 다른 방식으로 Tool 가용성이 변경될 때(예: 권한 부여가 변경된 경우) 이를 호출하세요.
async server.toolActions.notifyListChanged(): Promise<void>
Mastra registry synchronizationMastra registry synchronization에 대한 직접 링크
서버가 Mastra 인스턴스에 등록되면 toolActions.add() and toolActions.remove() 도 Mastra 인스턴스의 Tool 레지스트리를 업데이트하여 시작 시 이루어지는 자동 Tool 등록과 동일하게 동작합니다. 추가된 Tool은 mastra.listTools() (keyed by the tool's intrinsic id 가 있는 경우 이를 통해 사용할 수 있으며, 제거된 Tool은 레지스트리에서 삭제됩니다.
LoggingLogging에 대한 직접 링크
MCP 서버는 notifications/message. Clients control verbosity by sending a logging/setLevel 요청을 사용하여 구조화된 로그 메시지를 클라이언트에 전송할 수 있습니다. 서버는 요청된 최소 레벨보다 낮은 메시지를 삭제합니다(RFC 5424 심각도 순서 준수). 레벨은 세션별로 추적되므로 클라이언트마다 서로 다른 상세 수준을 요청할 수 있습니다.
sendLoggingMessage()sendloggingmessage에 대한 직접 링크
각 클라이언트의 최소 로깅 레벨을 준수하여 연결된 모든 클라이언트에 로그 알림을 전송합니다.
async server.sendLoggingMessage(params: {
level: LoggingLevel;
data: unknown;
logger?: string;
}): Promise<void>
Example:
await server.sendLoggingMessage({
level: 'info',
data: { message: 'Sync completed', itemsProcessed: 42 },
})
context.mcp.log()contextmcplog에 대한 직접 링크
Inside a tool's execute function, use context.mcp.log() 를 사용하여 Tool을 호출한 클라이언트에 로그 메시지를 전송합니다.
async context.mcp.log(
level: LoggingLevel,
message: string,
data?: Record<string, unknown>
): Promise<void>
Example:
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 notificationsProgress 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()contextmcpprogress에 대한 직접 링크
async context.mcp.progress(params: {
progress: number;
total?: number;
message?: string;
}): Promise<void>
Example:
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 deliveryNotification delivery에 대한 직접 링크
Notification methods (resources.notifyListChanged(), prompts.notifyListChanged(), toolActions.notifyListChanged(), and sendLoggingMessage())는 모든 전송 방식에서 연결된 모든 클라이언트, 즉 stdio/SSE 연결과 각 스트리밍 가능 HTTP 세션에 브로드캐스트됩니다. resources.notifyUpdated() 는 예외로, resources/subscribe를 통해 리소스 URI를 구독한 클라이언트에만 알림을 보냅니다. 스트리밍 가능 HTTP 클라이언트의 구독은 세션별로 추적됩니다. 레거시 SSE 클라이언트는 기본 서버 인스턴스를 공유하므로 하나의 구독 집합도 공유합니다. 상태 비저장 서버리스 모드를 사용하는 클라이언트는 요청마다 임시 서버 인스턴스를 사용하므로 알림을 받을 수 없습니다.
ExamplesExamples에 대한 직접 링크
MCPServer 설정 및 배포에 관한 실용적인 예시는 Publishing an MCP Server guide.
이 페이지의 시작 부분에 있는 예제에서는 MCPServer with both tools and agents.
ElicitationElicitation에 대한 직접 링크
What's 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 SignatureTool Execution Signature에 대한 직접 링크
Tool이 MCP 서버 컨텍스트 내에서 실행되면 context.mcp object:
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 WorksHow Elicitation Works에 대한 직접 링크
를 통해 MCP 전용 기능을 전달받습니다. 일반적인 사용 사례는 Tool 실행 중에 발생합니다. Tool에 사용자 입력이 필요하면 컨텍스트 매개변수를 통해 제공되는 Elicitation 기능을 사용할 수 있습니다:
- The tool calls
context.mcp.elicitation.sendRequest()with a message and schema - 요청이 연결된 MCP 클라이언트로 전송됩니다
- 클라이언트가 사용자에게 요청을 표시합니다(UI, 명령줄 등 사용)
- 사용자가 입력을 제공하거나 요청을 거절 또는 취소합니다
- 클라이언트가 응답을 서버로 다시 전송합니다
- Tool이 응답을 받고 실행을 계속합니다
Using Elicitation in ToolsUsing Elicitation in Tools에 대한 직접 링크
다음은 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 SchemaElicitation 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:
{
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 ActionsResponse Actions에 대한 직접 링크
사용자는 다음 세 가지 방법으로 Elicitation 요청에 응답할 수 있습니다:
- Accept (
action: 'accept'): User provided data and confirmed submission- Contains
contentfield with the submitted data
- Contains
- Decline (
action: 'decline'): User explicitly declined to provide information- No content field
- Cancel (
action: 'cancel'): User dismissed the request without deciding- No content field
Tool은 세 가지 응답 타입을 모두 적절하게 처리해야 합니다.
Security ConsiderationsSecurity 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 APITool Execution API에 대한 직접 링크
The elicitation functionality is available through the options parameter in tool execution:
// 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.
}
}
Elicitation is session-aware 는 HTTP 기반 전송 방식(SSE 또는 HTTP)을 사용할 때 적용됩니다. 여러 클라이언트가 동일한 서버에 연결되어 있으면 Elicitation 요청은 Tool 실행을 시작한 클라이언트 세션으로 라우팅됩니다.
The ElicitResult type:
type ElicitResult = {
action: 'accept' | 'decline' | 'cancel'
content?: any // Only present when action is 'accept'
}
OAuth protectionOAuth protection에 대한 직접 링크
에 따라 OAuth 인증으로 MCP 서버를 보호하려면 MCP Auth Specification, use the createOAuthMiddleware function:
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 Unauthorizedwith properWWW-Authenticateheaders when authentication is required - Validates bearer tokens using your provided validator
Token ValidationToken Validation에 대한 직접 링크
For production, use proper token validation:
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 OptionsOAuth Middleware Options에 대한 직접 링크
oauth.resource:
oauth.scopesSupported?:
oauth.resourceName?:
oauth.validateToken?:
mcpPath?:
Authentication contextAuthentication context에 대한 직접 링크
Tools can access request metadata via context.mcp.extra 를 HTTP 기반 전송 방식에서 사용하세요. 이를 통해 HTTP 미들웨어의 인증 정보, 사용자 컨텍스트 또는 사용자 지정 데이터를 MCP Tool에 전달할 수 있습니다.
How it worksHow it works에 대한 직접 링크
Whatever you set on req.auth in your HTTP middleware becomes available as context.mcp.extra.authInfo in your tools:
req.auth = { ... } → context?.mcp?.extra?.authInfo.extra = { ... }
Map auth data for FGAMap auth data for FGA에 대한 직접 링크
When an MCPServer 가 세분화된 권한 부여(FGA) Provider가 있는 Mastra 인스턴스에 등록되면 Mastra는 Tool을 나열하거나 호출하기 전에 requestContext.get('user') 를 확인합니다. HTTP MCP 전송 방식은 인증된 데이터를 extra.authInfo, so use mapAuthInfoToUser 로 전달하여 FGA Provider에서 예상하는 사용자 구조를 설정합니다.
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 separatelyScope MCP tool FGA separately에 대한 직접 링크
Use fga.resourceMapping and fga.permissionMapping 는 MCP 클라이언트에 내부 Agent 또는 Workflow의 Tool 실행과 다른 권한 부여 범위가 필요할 때 사용하세요. 재정의는 tools/list and tools/call checks for this MCP server.
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 MiddlewareSetting Up Authentication Middleware에 대한 직접 링크
To pass data to your tools, populate 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 })
})
Accessing Auth Data in ToolsAccessing Auth Data in Tools에 대한 직접 링크
The req.auth object is available as context.mcp.extra.authInfo in your tool's execute function:
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 agentpassing-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
}
The extra Objectthe-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 ExampleComplete Example에 대한 직접 링크
Install jose 를 호출하기 전에 HTTP 서버 미들웨어의 Node.js 요청 객체에 설정하여 ID 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
다음 예제는 사용자 데이터를 Tool에 전달하기 전에 Token의 서명, 발급자, 대상, 알고리즘, 만료 및 필수 클레임을 검증합니다:
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에 대한 직접 링크
The appResources 옵션을 사용하면 MCP Apps extension. Each entry maps a ui:// 를 통해 MCP 서버에서 대화형 HTML UI를 제공할 수 있습니다. URI는 Mastra Studio의 Sandbox iframe에서 렌더링되는 HTML 앱을 가리킵니다.
AppResources typeappresources-type에 대한 직접 링크
Key (URI):
ui:// URI that identifies the app resource (e.g., ui://calculator/main).Each value is an AppResource object:
name:
description?:
html?:
html or htmlPath.htmlPath?:
html or htmlPath.meta?:
ExampleExample에 대한 직접 링크
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>',
},
},
})
Link a tool to its app resource by setting _meta.ui.resourceUri on the tool to the matching ui:// URI입니다. 서버는 Tool을 등록할 때 이 메타데이터를 자동으로 정규화합니다. MCP Apps 에서 전체 앱 브리지 API와 사용 패턴을 확인하세요.
Related informationRelated information에 대한 직접 링크
- Mastra에서 MCP 서버에 연결하는 방법은 MCPClient documentation.
- Model Context Protocol에 관한 자세한 내용은 @modelcontextprotocol/sdk documentation.