Guide · Agentic Frameworks

MCP server Mastra integration

Mastra is a TypeScript-first agent framework that provides first-class MCP support: agents and workflows can connect to MCP servers directly through MCPConfiguration, and the framework handles connection management, tool discovery, and schema translation automatically. Unlike frameworks where MCP is a bolt-on adapter, Mastra treats MCP as a native tool protocol — you specify server endpoints in configuration, and Mastra's agent runtime calls tools/list at startup, presents discovered tools to the LLM, and dispatches tools/call when the LLM selects a tool. This native integration simplifies the code, but the operational considerations remain: connection health, error propagation in workflows, and monitoring the remote MCP servers that your agents depend on at runtime.

TL;DR

Define MCP servers with MCPConfiguration, pass it to createAgent({ ..., tools: { toolsets: [await mcpConfig.getToolset()] } }), and Mastra auto-discovers all tools. For workflows, call await mcpConfig.getToolset() inside a createStep handler to get tools scoped to that step's MCP connection. Always call await mcpConfig.disconnect() on shutdown to close persistent connections. Monitor your MCP endpoints with AliveMCP — Mastra workflows fail with opaque errors when a dependent MCP server goes down mid-execution.

Mastra architecture for MCP

Mastra structures agent applications around three primitives:

PrimitiveDescriptionMCP role
AgentLLM-backed entity that reasons and calls toolsReceives MCP tools via toolset; calls them during chat turns
WorkflowState machine with typed steps and transitionsIndividual steps can call MCP tools or invoke an Agent with MCP toolsets
MCPConfigurationRegistry of MCP server connection specsCentral config for all MCP endpoints; provides getToolset() for agents and steps
ToolsetA live set of tools from one or more MCP serversDynamically discovered; tool list reflects the live server state at connection time

The separation between MCPConfiguration (static spec) and Toolset (live connection) means you can define server endpoints once at startup and obtain fresh tool lists per-agent-invocation or per-workflow-step.

MCPConfiguration setup

Define all MCP server connections in a single MCPConfiguration object. Mastra supports both stdio (subprocess) and HTTP/SSE transport:

import { MCPConfiguration } from "@mastra/mcp";
import { Agent, createAgent } from "@mastra/core";
import Anthropic from "@anthropic-ai/sdk";

// Define all MCP servers once
const mcpConfig = new MCPConfiguration({
  servers: {
    // HTTP/SSE transport — production remote MCP server
    search: {
      url: new URL(process.env.SEARCH_MCP_URL!),
      requestInit: {
        headers: { Authorization: `Bearer ${process.env.SEARCH_MCP_TOKEN}` },
      },
    },
    // stdio transport — local MCP server (development)
    database: {
      command: "node",
      args: ["./db-server/build/index.js"],
      env: {
        DATABASE_URL: process.env.DATABASE_URL!,
      },
    },
    // HTTP/SSE with custom timeout
    codeExecution: {
      url: new URL(process.env.CODE_MCP_URL!),
      requestInit: {
        headers: { Authorization: `Bearer ${process.env.CODE_MCP_TOKEN}` },
        signal: AbortSignal.timeout(30_000),  // 30 s per tool call
      },
    },
  },
});

The servers keys become the namespace prefix for tools from each server. When the search server exposes a tool named search_web, Mastra makes it available as search_search_web to avoid name collisions across servers. You can override name prefixing in advanced configurations.

Creating an agent with MCP tools

Pass a toolset from MCPConfiguration to createAgent. The toolset is obtained asynchronously — it connects to MCP servers and calls tools/list — so it must be awaited before the agent is constructed:

const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

async function buildResearchAgent(): Promise {
  // getToolset() connects to all configured MCP servers and discovers their tools
  const toolset = await mcpConfig.getToolset();

  const agent = createAgent({
    name: "ResearchAgent",
    instructions: `You are a research agent with access to a live web search and database.
    - Use search tools to find current information and recent publications.
    - Use database tools to query structured data.
    - Cite sources in your responses.
    - Prefer specific queries over broad ones — narrow searches return better results.`,
    model: {
      provider: "ANTHROPIC",
      name: "claude-sonnet-4-6",
      toolChoice: "auto",
    },
    tools: {
      toolsets: [toolset],
    },
  });

  return agent;
}

// Usage
const agent = await buildResearchAgent();
const response = await agent.generate(
  "What MCP servers have the highest uptime according to recent registry scans?",
);
console.log(response.text);

toolChoice: "auto" lets the model decide when to call tools. Use "required" to force at least one tool call per turn (useful for workflows that must retrieve data before answering) or { type: "tool", toolName: "search_search_web" } to force a specific tool on the first turn.

Workflow steps with MCP tools

Mastra workflows define typed step-by-step execution. Each step can obtain its own MCP toolset, use it, and pass results to the next step through the workflow's typed state:

import { createWorkflow, createStep } from "@mastra/core/workflows";
import { z } from "zod";

const researchWorkflow = createWorkflow({
  id: "mcp-research-pipeline",
  inputSchema: z.object({ topic: z.string() }),
  outputSchema: z.object({ report: z.string(), sources: z.array(z.string()) }),
})
  .addStep(
    createStep({
      id: "gather-sources",
      inputSchema: z.object({ topic: z.string() }),
      outputSchema: z.object({ rawResults: z.string(), urls: z.array(z.string()) }),
      async execute({ inputData }) {
        // Get a fresh toolset scoped to this step
        const toolset = await mcpConfig.getToolset();
        const tools = toolset.getTools();

        const searchTool = tools.find((t) => t.name === "search_search_web");
        if (!searchTool) throw new Error("search_web tool not found on MCP server");

        const result = await searchTool.execute({ query: inputData.topic, max_results: 10 });
        const parsed = JSON.parse(result.text ?? "[]");
        return {
          rawResults: result.text ?? "",
          urls: parsed.map((r: { url: string }) => r.url),
        };
      },
    }),
  )
  .addStep(
    createStep({
      id: "synthesize-report",
      inputSchema: z.object({ rawResults: z.string(), urls: z.array(z.string()) }),
      outputSchema: z.object({ report: z.string(), sources: z.array(z.string()) }),
      async execute({ inputData }) {
        const agent = await buildResearchAgent();
        const response = await agent.generate(
          `Synthesize this research into a structured report: ${inputData.rawResults}`,
        );
        return { report: response.text, sources: inputData.urls };
      },
    }),
  );

// Run the workflow
const result = await researchWorkflow
  .createRun()
  .start({ inputData: { topic: "MCP server security vulnerabilities 2026" } });

Each step calls mcpConfig.getToolset() independently, which keeps MCP connections short-lived and scoped to each step's execution. This is safer than sharing a single long-lived toolset across all steps: if a step fails and is retried, it gets a fresh connection rather than inheriting a potentially broken one.

Tool execution hooks for monitoring

Mastra exposes lifecycle hooks on agents for observability. Use these to track MCP tool call latency and error rates without modifying tool implementations:

import { createAgent } from "@mastra/core";

const agent = createAgent({
  name: "MonitoredResearchAgent",
  // ...other config...
  hooks: {
    onToolCall: async ({ toolName, input, startTime }) => {
      console.log(`[MCP] Calling tool: ${toolName}`, { input });
    },
    onToolResult: async ({ toolName, result, startTime }) => {
      const latencyMs = Date.now() - startTime;
      console.log(`[MCP] Tool ${toolName} completed in ${latencyMs}ms`, {
        isError: result?.isError,
        latencyMs,
      });
      // Emit to your metrics system (Datadog, OpenTelemetry, etc.)
      metrics.histogram("mcp.tool.latency", latencyMs, { tool: toolName });
      if (result?.isError) {
        metrics.increment("mcp.tool.error", { tool: toolName });
      }
    },
    onToolError: async ({ toolName, error, startTime }) => {
      const latencyMs = Date.now() - startTime;
      console.error(`[MCP] Tool ${toolName} failed after ${latencyMs}ms:`, error.message);
      metrics.increment("mcp.tool.exception", { tool: toolName });
    },
  },
});

These hooks fire on every tool call including MCP tool calls. The startTime parameter is a timestamp in milliseconds captured before the tool call begins, enabling precise latency measurement even for concurrent tool calls.

Monitoring MCP servers in Mastra deployments

Mastra agents are typically deployed as HTTP API servers (via @mastra/server) or serverless functions. In both cases, MCP server failures appear as tool errors in agent responses — the agent's final text might say "I was unable to retrieve that information" without any indication that the underlying MCP server is down.

Add a startup health check and run AliveMCP continuous monitoring:

import { MCPConfiguration } from "@mastra/mcp";

async function healthCheckMCPServers(config: MCPConfiguration): Promise {
  const toolset = await config.getToolset();
  const tools = toolset.getTools();

  if (tools.length === 0) {
    throw new Error(
      "MCP health check failed: no tools discovered. " +
      "Check that all configured MCP servers are reachable."
    );
  }

  console.log(`MCP health check passed: ${tools.length} tools discovered across all servers`);
  await config.disconnect();  // close the probe connection
}

// At app startup, before accepting requests
await healthCheckMCPServers(mcpConfig).catch((err) => {
  console.error("MCP preflight failed — aborting startup:", err.message);
  process.exit(1);
});

The preflight calls getToolset(), which connects to all configured MCP servers, then checks that at least one tool was discovered. Failing fast at startup is far better than silently serving responses that omit MCP-sourced data. Complement this with continuous monitoring via AliveMCP so you detect servers that go down after your service started.

Frequently asked questions

Does Mastra support MCP resources and prompts, not just tools?

Mastra's primary MCP integration surface is the tools API. MCP resources (files, records) and prompts (reusable message templates) are part of the MCP spec but are not auto-ingested by Mastra's agent runtime as of v0.x. To use MCP resources in Mastra, call toolset.getSession().listResources() and readResource(uri) directly from within a workflow step and incorporate the content into the agent's context manually. Follow Mastra's GitHub for native resource support in upcoming releases.

How do I handle MCP tool failures in a Mastra workflow without failing the entire workflow?

Wrap the tool call in a try/catch inside the step's execute function and return a partial result rather than throwing. Mastra workflows propagate exceptions from steps as workflow failures — if you want a step to continue despite an MCP error, return a fallback value (an empty array, a cached result, or a status flag) and let downstream steps handle the missing data. You can also use Mastra's built-in step retry configuration (retryConfig: { attempts: 3, delay: 1000 }) to retry transient MCP failures automatically.

Can I dynamically add MCP servers to MCPConfiguration after it has been created?

No — MCPConfiguration is immutable after construction. To add a new MCP server at runtime, create a new MCPConfiguration instance that includes the new server spec and obtain a new toolset from it. For applications that need to add MCP servers based on user configuration (e.g., a multi-tenant app where each tenant has their own MCP server), create per-request or per-tenant MCPConfiguration instances rather than a shared global config.

Does Mastra work with MCP servers that require OAuth authentication?

Yes — pass OAuth bearer tokens in the requestInit.headers of the server config. Mastra does not manage token refresh automatically; if your MCP server uses short-lived tokens, implement a token provider that refreshes the token and recreates the MCPConfiguration instance before each agent invocation. For OAuth 2.0 machine-to-machine flows (client credentials grant), fetch the access token at startup and refresh it proactively before expiry using a background interval.

What is the difference between getToolset() and getTools() in Mastra?

mcpConfig.getToolset() is an async method that establishes connections to all configured MCP servers, calls tools/list on each, and returns a Toolset object containing the discovered tools and the live sessions. toolset.getTools() is a synchronous method that returns the array of Tool objects already discovered by the toolset — it does not make network calls. Call getToolset() once per agent construction or per workflow step; call getTools() multiple times on the resulting toolset without additional network overhead.

Further reading

Know when your MCP server is down — before users do

AliveMCP probes your server's MCP endpoint every minute, detects protocol errors and transport failures, and pages you before users notice.

Start monitoring free