> Discover all available pages from the documentation index: https://mastra.ai/llms.txt

# MCP

Mastra supports the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction), an open standard for connecting AI agents to external tools and resources.

Use [`MCPClient`](https://mastra.ai/reference/tools/mcp-client) to connect to MCP servers. Use [`MCPServer`](https://mastra.ai/reference/tools/mcp-server) to expose Mastra agents, tools, workflows, prompts, and resources to other MCP-compatible systems.

## Connect to MCP servers

Install the MCP package:

**npm**:

```bash
npm install @mastra/mcp@latest
```

**pnpm**:

```bash
pnpm add @mastra/mcp@latest
```

**Yarn**:

```bash
yarn add @mastra/mcp@latest
```

**Bun**:

```bash
bun add @mastra/mcp@latest
```

Configure each server with a local command or remote URL:

```typescript
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}`,
        },
      },
    },
  },
})
```

> **Authentication:** For OAuth-protected servers, use `authenticate()` to complete the browser-based authorization flow. Visit [OAuth authentication](https://mastra.ai/reference/tools/mcp-client) for configuration details.

Pass tools from the configured servers to an agent:

```typescript
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 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:

```typescript
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()`](https://mastra.ai/reference/tools/mcp-client) and [`listToolsets()`](https://mastra.ai/reference/tools/mcp-client) for their full APIs.

### Tool approval

Set `requireToolApproval` on a server to require approval for all its tools:

```typescript
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:

```typescript
requireToolApproval: ({ toolName }) => toolName.startsWith('delete_')
```

Treat tool annotations from servers you don't control as untrusted hints. Visit [tool approval](https://mastra.ai/reference/tools/mcp-client) for the callback context and security guidance.

### Security

MCP servers run code and return content on your agent's behalf, so configure them with the same care as any other external dependency:

- **Stdio subprocess environment**: subprocesses inherit only the MCP SDK's curated environment whitelist (for example `PATH` and `HOME` on POSIX), not the full parent environment. Set `inheritDefaultEnv: false` on a server to pass only the variables you list in `env`.
- **Outbound host restriction**: when HTTP server URLs come from untrusted configuration, set `allowedHosts` to restrict which hosts the client will contact. On the default fetch path this also blocks redirect hops before they're sent; a custom `fetch` gets its final response URL validated after the request runs, so it must enforce redirect policy itself when preventing outbound contact is required.
- **Tool response trust**: tool results are untrusted model input. Use [input and output processors](https://mastra.ai/docs/agents/processors) to inspect or sanitize content before it reaches the model, and `requireToolApproval` to gate sensitive tools.

Visit the [MCPClient security reference](https://mastra.ai/reference/tools/mcp-client) for enforcement details of each option.

### MCP registries

Registries provide hosted or packaged MCP servers. The client configuration above works with registry endpoints and commands.

| Registry                                        | Connection        | Notes                                         |
| ----------------------------------------------- | ----------------- | --------------------------------------------- |
| [Klavis AI](https://klavis.ai)                  | Hosted HTTP       | Enterprise authentication and managed servers |
| [mcp.run](https://www.mcp.run/)                 | Signed SSE URL    | Treat the profile URL as a secret             |
| [Composio](https://mcp.composio.dev)            | Hosted SSE URL    | URLs are often tied to one user account       |
| [Smithery](https://smithery.ai)                 | CLI or hosted URL | Run local packages through `npx`              |
| [Apify](https://mcp.apify.com)                  | Hosted HTTP       | Authenticate with an Apify API token          |
| [Ampersand](https://docs.withampersand.com/mcp) | 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 server

Create an `MCPServer` to expose Mastra primitives to external MCP clients:

```typescript
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:

```typescript
import { Mastra } from '@mastra/core/mastra'
import { mcpServer } from './mcp/server'

export const mastra = new Mastra({
  mcpServers: { mcpServer },
})
```

> **Authentication:** Protect HTTP MCP servers with OAuth middleware. Visit [OAuth protection](https://mastra.ai/reference/tools/mcp-server) for setup instructions.

Visit the [`MCPServer` reference](https://mastra.ai/reference/tools/mcp-server) for prompts, resources, transports, and other server options.

### Publish a stdio server package

Package a server that uses the standard input/output (stdio) transport when you want clients to run it locally through a command such as `npx`. Create a separate executable entry point that starts the server:

```typescript
#!/usr/bin/env node
import { mcpServer } from './server'

mcpServer.startStdio().catch(error => {
  console.error('Failed to start MCP server:', error)
  process.exit(1)
})
```

Build the entry point as an executable Node.js file, then point the package's `bin` field to that output. Include the built file in the published package and keep the shebang as its first line:

```json
{
  "bin": {
    "my-mcp-server": "dist/stdio.js"
  },
  "files": ["dist"]
}
```

After publishing the package, configure clients to run it by package name:

```typescript
import { MCPClient } from '@mastra/mcp'

const mcpClient = new MCPClient({
  servers: {
    myServer: {
      command: 'npx',
      args: ['-y', '@your-org/my-mcp-server@1.0.0'],
    },
  },
})
```

Visit the [npm package publishing documentation](https://docs.npmjs.com/creating-and-publishing-scoped-public-packages) for package naming, authentication, and publication commands.

## Build MCP Apps

The [MCP Apps extension](https://github.com/modelcontextprotocol/ext-apps) 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 resource

Return a short `content` summary for the model and place UI data in `structuredContent`. Link the tool to its app by setting `mcp._meta.ui.resourceUri` to the same `ui://` URI used in `appResources`:

```typescript
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(),
  }),
  mcp: {
    _meta: { ui: { resourceUri: 'ui://calculator/main' } },
  },
  execute: async ({ num1, num2 }) => ({
    content: [{ type: 'text', text: 'The result is displayed in the calculator app.' }],
    structuredContent: { result: num1 + num2 },
  }),
})

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`](https://mastra.ai/reference/tools/mcp-server) for inline HTML, file paths, metadata, and content security policy options.

### Connect the app to Studio

Use the `App` class from `@modelcontextprotocol/ext-apps` inside the HTML resource. Register event handlers before calling `connect()`:

```html
<!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:

1. The agent calls the tool.
2. The tool returns model-facing `content` and UI-facing `structuredContent`.
3. Studio renders the associated app resource.
4. The app receives tool input and can call server tools or send chat messages.

Visit the external [`App` API reference](https://apps.extensions.modelcontextprotocol.io/api/classes/app.App.html) for all guest-side methods and lifecycle hooks.

### Register MCP Apps

For a local app, pass the tool to an agent and register its MCP server on `Mastra`:

```typescript
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:

```typescript
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()`](https://mastra.ai/reference/tools/mcp-client) for proxy configuration details.

### Sandbox security

Mastra Studio uses [`@mcp-ui/client`](https://www.npmjs.com/package/@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.

## Next steps

- [Use tools with agents](https://mastra.ai/docs/agents/tools)
- [`MCPClient` reference](https://mastra.ai/reference/tools/mcp-client)
- [`MCPServer` reference](https://mastra.ai/reference/tools/mcp-server)
- [MCP Apps extension specification](https://github.com/modelcontextprotocol/ext-apps)