Skip to main content
MCP servers expose tools, resources, and prompts to AI applications. Learn how to create production-ready MCP servers with Mastra.

Basic MCP Server

Create a simple MCP server with stdio transport:
mcp-server.ts
import { MCPServer } from '@mastra/mcp';
import { createTool } from '@mastra/core/tools';
import { z } from 'zod';

const weatherTool = createTool({
  id: 'getWeather',
  description: 'Gets the current weather for a location',
  inputSchema: z.object({ 
    location: z.string() 
  }),
  execute: async (input) => {
    const response = await fetch(
      `https://api.weather.com/v1/weather?q=${input.location}`
    );
    return response.json();
  },
});

const server = new MCPServer({
  name: 'Weather Server',
  version: '1.0.0',
  tools: {
    weatherTool,
  },
});

await server.startStdio();
Run the server:
node mcp-server.js

HTTP Server with Express

Create an HTTP-based MCP server:
import http from 'node:http';
import { MCPServer } from '@mastra/mcp';

const server = new MCPServer({
  name: 'My MCP Server',
  version: '1.0.0',
  tools: { /* tools */ },
});

const httpServer = http.createServer(async (req, res) => {
  const url = new URL(req.url || '', `http://${req.headers.host}`);
  
  await server.startSSE({
    url,
    ssePath: '/sse',
    messagePath: '/message',
    req,
    res,
  });
});

httpServer.listen(3000, () => {
  console.log('MCP server running on http://localhost:3000/sse');
});

HTTP Server with Hono

Use Hono framework for modern HTTP handling:
import { Hono } from 'hono';
import { MCPServer } from '@mastra/mcp';

const app = new Hono();

const server = new MCPServer({
  name: 'Hono MCP Server',
  version: '1.0.0',
  tools: { /* tools */ },
});

app.all('*', async (c) => {
  const url = new URL(c.req.url);
  return await server.startHonoSSE({
    url,
    ssePath: '/hono-sse',
    messagePath: '/message',
    context: c,
  });
});

export default app;

Streamable HTTP Transport

Use modern HTTP transport for better performance:
import http from 'node:http';
import { MCPServer } from '@mastra/mcp';
import { randomUUID } from 'node:crypto';

const server = new MCPServer({
  name: 'Streamable Server',
  version: '1.0.0',
  tools: { /* tools */ },
});

const httpServer = http.createServer(async (req, res) => {
  await server.startHTTP({
    url: new URL(req.url || '', 'http://localhost:3000'),
    httpPath: '/mcp',
    req,
    res,
    options: {
      sessionIdGenerator: () => randomUUID(),
      onsessioninitialized: (sessionId) => {
        console.log(`New session: ${sessionId}`);
      },
    },
  });
});

httpServer.listen(3000);

Serverless Mode

Deploy to serverless environments:
export default {
  async fetch(request: Request) {
    const url = new URL(request.url);
    
    if (url.pathname === '/mcp') {
      await server.startHTTP({
        url,
        httpPath: '/mcp',
        req: request,
        res: response,
        options: { 
          serverless: true  // Stateless mode
        },
      });
    }
    
    return new Response('Not found', { status: 404 });
  },
};

Converting Agents to Tools

Automatically expose agents as MCP tools:
import { Agent } from '@mastra/core/agent';
import { MCPServer } from '@mastra/mcp';

const myAgent = new Agent({
  id: 'helper',
  name: 'Helper Agent',
  description: 'A helpful assistant that answers questions',
  instructions: 'You are a helpful assistant.',
  model: 'openai/gpt-4o-mini',
});

const server = new MCPServer({
  name: 'Agent Server',
  version: '1.0.0',
  tools: { /* regular tools */ },
  agents: { myAgent },  // Becomes 'ask_myAgent' tool
});

Converting Workflows to Tools

Expose workflows as executable tools:
import { Workflow } from '@mastra/core/workflows';
import { MCPServer } from '@mastra/mcp';
import { z } from 'zod';

const dataProcessingWorkflow = new Workflow({
  id: 'process-data',
  description: 'Process and transform data',
  inputSchema: z.object({
    data: z.array(z.any()),
  }),
});

const server = new MCPServer({
  name: 'Workflow Server',
  version: '1.0.0',
  workflows: { 
    dataProcessingWorkflow  // Becomes 'run_dataProcessingWorkflow' tool
  },
});

Exposing Resources

Provide access to data and content:
const server = new MCPServer({
  name: 'Data Server',
  version: '1.0.0',
  tools: { /* tools */ },
  resources: {
    // List available resources
    listResources: async ({ extra }) => {
      // Access MCP protocol context
      const userId = extra?.meta?.userId;
      
      return [
        {
          uri: 'file:///data/users.json',
          name: 'User Database',
          description: 'List of all users',
          mimeType: 'application/json',
        },
        {
          uri: 'file:///data/products.json',
          name: 'Product Catalog',
          description: 'Product information',
          mimeType: 'application/json',
        },
      ];
    },
    
    // Get resource content
    getResourceContent: async ({ uri, extra }) => {
      const content = await readFile(uri);
      return {
        text: content,
      };
    },
    
    // Optional: Resource templates
    resourceTemplates: async ({ extra }) => {
      return [
        {
          uriTemplate: 'file:///data/{collection}.json',
          name: 'Data Collection',
          description: 'Access any data collection',
          mimeType: 'application/json',
        },
      ];
    },
  },
});

Resource Notifications

Notify clients about resource changes:
const server = new MCPServer({
  name: 'Live Data Server',
  version: '1.0.0',
  resources: {
    listResources: async () => resources,
    getResourceContent: async ({ uri }) => content,
  },
});

// Notify about updated resource
await server.resources.notifyUpdated({ 
  uri: 'file:///data/users.json' 
});

// Notify that resource list changed
await server.resources.notifyListChanged();

// Example: Watch file changes
fs.watch('/data', async (event, filename) => {
  await server.resources.notifyUpdated({ 
    uri: `file:///data/${filename}` 
  });
});

Exposing Prompts

Share reusable prompt templates:
const server = new MCPServer({
  name: 'Prompt Server',
  version: '1.0.0',
  prompts: {
    // List available prompts
    listPrompts: async ({ extra }) => {
      return [
        {
          name: 'code-review',
          version: 'v1',
          description: 'Review code for best practices',
          arguments: [
            { 
              name: 'code', 
              description: 'Code to review', 
              required: true 
            },
            { 
              name: 'language', 
              description: 'Programming language', 
              required: true 
            },
          ],
        },
        {
          name: 'summarize',
          version: 'v1',
          description: 'Summarize text content',
          arguments: [
            { 
              name: 'text', 
              description: 'Text to summarize', 
              required: true 
            },
          ],
        },
      ];
    },
    
    // Get prompt messages
    getPromptMessages: async ({ name, version, args, extra }) => {
      if (name === 'code-review') {
        return [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Review this ${args.language} code for best practices:\n\n${args.code}`,
            },
          },
        ];
      }
      
      if (name === 'summarize') {
        return [
          {
            role: 'user',
            content: {
              type: 'text',
              text: `Summarize the following text:\n\n${args.text}`,
            },
          },
        ];
      }
      
      throw new Error(`Prompt not found: ${name}`);
    },
  },
});

Elicitation (Interactive Input)

Request user input during tool execution:
const interactiveTool = createTool({
  id: 'configure-service',
  description: 'Configure a service with user input',
  execute: async (input, context) => {
    // Request input from user via MCP client
    const result = await context?.mcp?.elicitation.sendRequest({
      message: 'Please provide your email address',
      requestedSchema: {
        type: 'object',
        properties: {
          email: { 
            type: 'string', 
            format: 'email' 
          },
        },
        required: ['email'],
      },
    });
    
    const email = result?.data?.email;
    return { configured: true, email };
  },
});

Error Handling

Handle errors gracefully in MCP servers:
const resilientTool = createTool({
  id: 'api-call',
  description: 'Call external API',
  execute: async (input) => {
    try {
      const response = await fetch(input.url);
      return await response.json();
    } catch (error) {
      // Return error as MCP result
      return {
        content: [{
          type: 'text',
          text: `Error: ${error.message}`,
        }],
        isError: true,
      };
    }
  },
});

Graceful Shutdown

Handle server shutdown properly:
const server = new MCPServer({
  name: 'My Server',
  version: '1.0.0',
  tools: { /* tools */ },
});

const httpServer = http.createServer(/* ... */);

process.on('SIGINT', async () => {
  console.log('Shutting down server...');
  await server.close();
  httpServer.close(() => {
    console.log('Server shutdown complete');
    process.exit(0);
  });
});

Testing MCP Servers

Test your MCP server:
import { MCPClient } from '@mastra/mcp';

// Start server in test
const server = new MCPServer({ /* ... */ });
await server.startStdio();

// Connect client
const client = new MCPClient({
  name: 'test-client',
  version: '1.0.0',
  server: {
    command: 'node',
    args: ['./mcp-server.js'],
  },
});

await client.connect();

// Test tools
const tools = await client.tools();
const result = await tools.weatherTool?.execute?.({ location: 'NYC' });

expect(result).toHaveProperty('temperature');

await client.disconnect();

Next Steps

MCP Overview

Learn more about Model Context Protocol

Creating Tools

Build tools for your MCP server

Build docs developers (and LLMs) love