MCP overview
Mastra supports the Model Context Protocol (MCP), an open standard for connecting AI agents to external tools and resources.
Use MCPClient to connect to MCP servers. Use MCPServer to expose Mastra agents, tools, workflows, prompts, and resources to other MCP-compatible systems.
Connect to MCP serversDirect link to Connect to MCP servers
Install the MCP package:
- npm
- pnpm
- Yarn
- Bun
npm install @mastra/mcp@latest
pnpm add @mastra/mcp@latest
yarn add @mastra/mcp@latest
bun add @mastra/mcp@latest
Configure each server with a local command or remote URL:
import { MCPClient } from '@mastra/mcp'
export const mcpClient = new MCPClient({
id: 'my-mcp-client',
servers: {
wikipedia: {
command: 'npx',
args: ['-y', 'wikipedia-mcp'],
},
weather: {
url: new URL('https://weather.example.com/mcp'),
requestInit: {
headers: {
Authorization: `Bearer ${process.env.WEATHER_API_KEY}`,
},
},
},
},
})
For OAuth-protected servers, use authenticate() to complete the browser-based authorization flow. Visit OAuth authentication for configuration details.
Pass tools from the configured servers to an agent:
import { Agent } from '@mastra/core/agent'
import { mcpClient } from '../mcp/client'
export const assistant = new Agent({
id: 'assistant',
name: 'Assistant',
instructions: `
Use the available MCP tools to answer questions.
Include the source of any information you retrieve.
`,
model: 'openai/gpt-5.6-sol',
tools: await mcpClient.listTools(),
})
Static and runtime toolsDirect link to Static and runtime tools
Choose how to load tools based on whether the server configuration changes between requests:
| Static tools | Runtime toolsets | |
|---|---|---|
| Method | await mcpClient.listTools() | await mcpClient.listToolsets() |
| Use case | Shared, fixed configuration | Per-user or per-request configuration |
| Credentials | Shared by all requests | Can vary between requests |
| Agent API | tools in the Agent constructor | toolsets in generate() or stream() |
The preceding agent example uses static tools. For runtime credentials, create a client for the request and pass its toolsets when calling the agent:
import { MCPClient } from '@mastra/mcp'
import { mastra } from './mastra'
export async function handleRequest(prompt: string, apiKey: string) {
const userMcpClient = new MCPClient({
servers: {
weather: {
url: new URL('https://weather.example.com/mcp'),
requestInit: {
headers: { Authorization: `Bearer ${apiKey}` },
},
},
},
})
const agent = mastra.getAgent('assistant')
const response = await agent.generate(prompt, {
toolsets: await userMcpClient.listToolsets(),
})
await userMcpClient.disconnect()
return response.text
}
Visit listTools() and listToolsets() for their full APIs.
Tool approvalDirect link to Tool approval
Set requireToolApproval on a server to require approval for all its tools:
const mcpClient = new MCPClient({
servers: {
github: {
url: new URL('https://github.example.com/mcp'),
requireToolApproval: true,
},
},
})
You can also provide a function that decides based on the tool name, arguments, or annotations:
requireToolApproval: ({ toolName }) => toolName.startsWith('delete_')
Treat tool annotations from servers you don't control as untrusted hints. Visit tool approval for the callback context and security guidance.
MCP registriesDirect link to MCP registries
Registries provide hosted or packaged MCP servers. The client configuration above works with registry endpoints and commands.
| Registry | Connection | Notes |
|---|---|---|
| Klavis AI | Hosted HTTP | Enterprise authentication and managed servers |
| mcp.run | Signed SSE URL | Treat the profile URL as a secret |
| Composio | Hosted SSE URL | URLs are often tied to one user account |
| Smithery | CLI or hosted URL | Run local packages through npx |
| Apify | Hosted HTTP | Authenticate with an Apify API token |
| Ampersand | SSE or stdio | Connect to configured SaaS integrations |
Store signed URLs, API keys, and tokens in environment variables. Follow the registry's documentation to obtain the endpoint, command, and credentials for each server.
Expose a Mastra MCP serverDirect link to Expose a Mastra MCP server
Create an MCPServer to expose Mastra primitives to external MCP clients:
import { MCPServer } from '@mastra/mcp'
import { assistant } from '../agents/assistant'
import { weatherTool } from '../tools/weather'
import { weatherWorkflow } from '../workflows/weather'
export const mcpServer = new MCPServer({
id: 'my-mcp-server',
name: 'My MCP Server',
version: '1.0.0',
agents: { assistant },
tools: { weatherTool },
workflows: { weatherWorkflow },
})
Register the server on the main Mastra instance:
import { Mastra } from '@mastra/core/mastra'
import { mcpServer } from './mcp/server'
export const mastra = new Mastra({
mcpServers: { mcpServer },
})
Protect HTTP MCP servers with OAuth middleware. Visit OAuth protection for setup instructions.
Visit the MCPServer reference for prompts, resources, transports, and other server options.
Build MCP AppsDirect link to Build MCP Apps
The MCP Apps extension lets MCP tools serve interactive HTML interfaces through ui:// resources. Mastra Studio renders these apps in sandboxed iframes on tool pages and in agent chat.
Use an MCP App when a tool result benefits from interaction, such as a form, calculator, color picker, or data visualization.
Define an app resourceDirect link to Define an app resource
Return a short content summary for the model and place UI data in structuredContent. Link the tool to its app by setting _meta.ui.resourceUri to the same ui:// URI used in appResources:
import { MCPServer } from '@mastra/mcp'
import { createTool } from '@mastra/core/tools'
import { z } from 'zod'
export const calculatorTool = createTool({
id: 'calculatorWithUI',
description: 'Calculate the sum of two numbers',
inputSchema: z.object({
num1: z.number(),
num2: z.number(),
}),
execute: async ({ num1, num2 }) => ({
content: [{ type: 'text', text: 'The result is displayed in the calculator app.' }],
structuredContent: { result: num1 + num2 },
}),
})
calculatorTool._meta = {
ui: { resourceUri: 'ui://calculator/main' },
}
export const calculatorMcpServer = new MCPServer({
id: 'calculator-app-server',
name: 'Calculator App Server',
version: '1.0.0',
tools: { calculatorTool },
appResources: {
'ui://calculator/main': {
name: 'Calculator',
htmlPath: './src/mastra/mcp/calculator.html',
},
},
})
The model sees content, while the app receives structuredContent. Visit appResources for inline HTML, file paths, metadata, and content security policy options.
Connect the app to StudioDirect link to Connect the app to Studio
Use the App class from @modelcontextprotocol/ext-apps inside the HTML resource. Register event handlers before calling connect():
<!doctype html>
<html>
<body>
<p id="result">Waiting for input</p>
<button id="recalculate">Recalculate</button>
<script type="module">
import { App } from 'https://cdn.jsdelivr.net/npm/@modelcontextprotocol/ext-apps/+esm'
const app = new App({ name: 'Calculator', version: '1.0.0' })
let toolInput
app.ontoolinput = params => {
toolInput = params.arguments
}
document.querySelector('#recalculate').addEventListener('click', async () => {
const result = await app.callServerTool({
name: 'calculatorWithUI',
arguments: toolInput,
})
document.querySelector('#result').textContent = JSON.stringify(result)
await app.sendMessage({
role: 'user',
content: [{ type: 'text', text: 'Explain the recalculated result.' }],
})
})
await app.connect()
</script>
</body>
</html>
The guest-side APIs serve different parts of the interaction:
| API | Purpose |
|---|---|
app.ontoolinput | Receive the arguments from the host tool call |
app.callServerTool() | Call an MCP tool from inside the iframe |
app.sendMessage() | Add a user message to chat and start a new model turn |
app.connect() | Connect to the host after registering event handlers |
The interaction follows this sequence:
- The agent calls the tool.
- The tool returns model-facing
contentand UI-facingstructuredContent. - Studio renders the associated app resource.
- The app receives tool input and can call server tools or send chat messages.
Visit the external App API reference for all guest-side methods and lifecycle hooks.
Register MCP AppsDirect link to Register MCP Apps
For a local app, pass the tool to an agent and register its MCP server on Mastra:
import { Agent } from '@mastra/core/agent'
import { Mastra } from '@mastra/core/mastra'
import { calculatorMcpServer, calculatorTool } from './mcp/calculator'
const calculatorAgent = new Agent({
id: 'calculator-agent',
name: 'Calculator Agent',
instructions: 'Use the calculator tool for arithmetic.',
model: 'openai/gpt-5-mini',
tools: { calculatorTool },
})
export const mastra = new Mastra({
agents: { calculatorAgent },
mcpServers: { calculatorMcpServer },
})
For an external MCP server that implements MCP Apps, load its tools with MCPClient.listTools() and register its proxy so Studio can resolve the remote app resources:
import { Agent } from '@mastra/core/agent'
import { Mastra } from '@mastra/core/mastra'
import { mcpClient } from './mcp/client'
const tools = await mcpClient.listTools()
const mcpServers = mcpClient.toMCPServerProxies()
const agent = new Agent({
id: 'remote-app-agent',
name: 'Remote App Agent',
instructions: 'Use the available remote tools.',
model: 'openai/gpt-5-mini',
tools,
})
export const mastra = new Mastra({
agents: { agent },
mcpServers,
})
Tools loaded through listTools() include a serverId in _meta.ui, allowing Studio to resolve each app resource without scanning every server. Visit toMCPServerProxies() for proxy configuration details.
Sandbox securityDirect link to Sandbox security
Mastra Studio uses @mcp-ui/client to load app HTML through a sandbox proxy and communicate over JSON-RPC with postMessage.
App iframes allow scripts, forms, and popups. They can't access the parent page's DOM, cookies, or storage. The host controls all communication with the guest app.