Skip to main content
The MCP Server integration provides comprehensive monitoring for Model Context Protocol servers by automatically instrumenting transport methods and handler functions.
This integration wraps MCP Server instances from the @modelcontextprotocol/sdk package (version ^1.9.0).

Installation

import * as Sentry from '@sentry/node';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';

Sentry.init({
  dsn: 'your-dsn',
});

const server = new McpServer({
  name: 'my-mcp-server',
  version: '1.0.0',
});

// Wrap server with Sentry instrumentation
const instrumentedServer = Sentry.wrapMcpServerWithSentry(server);

const transport = new StreamableHTTPServerTransport();
await instrumentedServer.connect(transport);

Basic Usage

Once wrapped, all MCP operations are automatically tracked:
import * as Sentry from '@sentry/node';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';

const server = Sentry.wrapMcpServerWithSentry(
  new McpServer({ name: 'my-server', version: '1.0.0' })
);

// Register a tool
server.tool('calculate', 'Perform calculations', {
  expression: {
    type: 'string',
    description: 'Math expression to evaluate',
  },
}, async ({ expression }) => {
  const result = eval(expression);
  return { content: [{ type: 'text', text: String(result) }] };
});
// Tool calls automatically tracked

// Register a resource
server.resource('file://*', async ({ uri }) => {
  const content = await fs.readFile(uri.replace('file://', ''), 'utf-8');
  return { contents: [{ uri, text: content }] };
});
// Resource access automatically tracked

// Register a prompt
server.prompt('greeting', 'Generate a greeting', async ({ name }) => {
  return {
    messages: [{
      role: 'user',
      content: { type: 'text', text: `Hello, ${name}!` },
    }],
  };
});
// Prompt generation automatically tracked

Configuration

Default Behavior

By default, inputs and outputs are only captured if sendDefaultPii is enabled:
Sentry.init({
  dsn: 'your-dsn',
  sendDefaultPii: false, // Default: no inputs/outputs
});

const server = Sentry.wrapMcpServerWithSentry(
  new McpServer({ name: 'my-server', version: '1.0.0' })
);

Explicit Control

Override the default behavior:
const server = Sentry.wrapMcpServerWithSentry(
  new McpServer({ name: 'my-server', version: '1.0.0' }),
  {
    recordInputs: true,   // Always capture inputs
    recordOutputs: false, // Never capture outputs
  }
);

Integration Options

recordInputs
boolean
default:"sendDefaultPii"
Capture tool/resource/prompt inputs
recordOutputs
boolean
default:"sendDefaultPii"
Capture handler outputs and responses

What Gets Instrumented

The integration automatically wraps:

Transport Methods

  • transport.send(): Outgoing messages
  • transport.onMessage(): Incoming message handlers
  • transport.onClose(): Connection close handlers
  • transport.onError(): Error handlers

Handler Functions

  • Tool Handlers: Functions registered via server.tool()
  • Resource Handlers: Functions registered via server.resource()
  • Prompt Handlers: Functions registered via server.prompt()

Captured Data

  • Method names and types
  • Request/response payloads (when recording enabled)
  • Execution times
  • Errors and exceptions
  • Transport correlation IDs

Practical Examples

Simple MCP Server

import * as Sentry from '@sentry/node';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';

Sentry.init({
  dsn: 'your-dsn',
  sendDefaultPii: true, // Enable input/output capture
});

const server = Sentry.wrapMcpServerWithSentry(
  new McpServer({
    name: 'calculator-server',
    version: '1.0.0',
  })
);

// Register calculator tool
server.tool(
  'calculate',
  'Perform mathematical calculations',
  {
    expression: {
      type: 'string',
      description: 'Mathematical expression',
    },
  },
  async ({ expression }) => {
    try {
      const result = eval(expression);
      return {
        content: [{
          type: 'text',
          text: `Result: ${result}`,
        }],
      };
    } catch (error) {
      // Error automatically captured by Sentry
      throw new Error(`Invalid expression: ${expression}`);
    }
  }
);

const transport = new StreamableHTTPServerTransport();
await server.connect(transport);

File System Resource Server

import fs from 'fs/promises';
import path from 'path';

const server = Sentry.wrapMcpServerWithSentry(
  new McpServer({ name: 'fs-server', version: '1.0.0' })
);

// Read file resource
server.resource(
  'file://*',
  async ({ uri }) => {
    const filePath = uri.replace('file://', '');
    
    try {
      const content = await fs.readFile(filePath, 'utf-8');
      return {
        contents: [{
          uri,
          mimeType: 'text/plain',
          text: content,
        }],
      };
    } catch (error) {
      // Error captured with file path context
      throw new Error(`Failed to read file: ${filePath}`);
    }
  }
);

// List directory resource
server.resource(
  'dir://*',
  async ({ uri }) => {
    const dirPath = uri.replace('dir://', '');
    const files = await fs.readdir(dirPath);
    
    return {
      contents: files.map(file => ({
        uri: `file://${path.join(dirPath, file)}`,
        name: file,
      })),
    };
  }
);

Prompt Template Server

const server = Sentry.wrapMcpServerWithSentry(
  new McpServer({ name: 'prompts-server', version: '1.0.0' })
);

// Code review prompt
server.prompt(
  'code_review',
  'Generate code review prompt',
  {
    code: { type: 'string', description: 'Code to review' },
    language: { type: 'string', description: 'Programming language' },
  },
  async ({ code, language }) => {
    return {
      messages: [
        {
          role: 'system',
          content: {
            type: 'text',
            text: `You are an expert ${language} developer. Review the following code for best practices, bugs, and improvements.`,
          },
        },
        {
          role: 'user',
          content: {
            type: 'text',
            text: `\`\`\`${language}\n${code}\n\`\`\``,
          },
        },
      ],
    };
  }
);

// Bug analysis prompt
server.prompt(
  'analyze_bug',
  'Generate bug analysis prompt',
  {
    error: { type: 'string', description: 'Error message' },
    stacktrace: { type: 'string', description: 'Stack trace' },
  },
  async ({ error, stacktrace }) => {
    return {
      messages: [
        {
          role: 'user',
          content: {
            type: 'text',
            text: `Analyze this error and suggest fixes:\n\nError: ${error}\n\nStack trace:\n${stacktrace}`,
          },
        },
      ],
    };
  }
);

Database Query Tool

import { Client } from 'pg';

const dbClient = new Client({
  connectionString: process.env.DATABASE_URL,
});
await dbClient.connect();

const server = Sentry.wrapMcpServerWithSentry(
  new McpServer({ name: 'db-server', version: '1.0.0' }),
  {
    recordInputs: false,  // Don't capture queries (may contain sensitive data)
    recordOutputs: false, // Don't capture results
  }
);

server.tool(
  'query_database',
  'Execute database query',
  {
    sql: { type: 'string', description: 'SQL query' },
  },
  async ({ sql }) => {
    try {
      const result = await dbClient.query(sql);
      return {
        content: [{
          type: 'text',
          text: JSON.stringify(result.rows, null, 2),
        }],
      };
    } catch (error) {
      // Database errors captured with context
      Sentry.captureException(error, {
        contexts: {
          database: {
            query_length: sql.length,
          },
        },
      });
      throw error;
    }
  }
);

Error Handling

Errors in handlers are automatically captured:
server.tool('risky_operation', 'May fail', {}, async () => {
  throw new Error('Operation failed');
  // Error automatically captured with:
  // - Handler name: 'risky_operation'
  // - Method type: 'tool'
  // - Stack trace
  // - MCP context
});

Custom Error Context

server.tool('api_call', 'Call external API', {
  endpoint: { type: 'string' },
}, async ({ endpoint }) => {
  try {
    const response = await fetch(endpoint);
    return { content: [{ type: 'text', text: await response.text() }] };
  } catch (error) {
    Sentry.captureException(error, {
      contexts: {
        api: {
          endpoint,
          timestamp: new Date().toISOString(),
        },
      },
    });
    throw error;
  }
});

Performance Monitoring

View MCP operations in Sentry:
Transaction: MCP Request
├─ mcp.tool.calculate
│  └─ Duration: 15ms
├─ mcp.resource.file://data.txt
│  └─ Duration: 45ms
├─ mcp.prompt.code_review
│  └─ Duration: 8ms
└─ Total: 150ms

Span Attributes

Captured span attributes include:
{
  'mcp.handler.type': 'tool',
  'mcp.handler.name': 'calculate',
  'mcp.input': { expression: '2+2' },    // If recordInputs: true
  'mcp.output': { result: '4' },          // If recordOutputs: true
  'mcp.transport.type': 'http',
  'mcp.server.name': 'my-server',
  'mcp.server.version': '1.0.0',
}

Source Code

The MCP Server integration is implemented in: packages/core/src/integrations/mcp-server/index.ts:45

Best Practices

Be careful about capturing inputs/outputs that may contain sensitive user data.

1. Secure Sensitive Data

const server = Sentry.wrapMcpServerWithSentry(
  new McpServer({ name: 'secure-server', version: '1.0.0' }),
  {
    recordInputs: false,  // Don't capture potentially sensitive inputs
    recordOutputs: false, // Don't capture outputs
  }
);

2. Add Custom Context

server.tool('process_data', 'Process data', {}, async (args) => {
  return await Sentry.startSpan(
    {
      name: 'Process Data',
      attributes: {
        'data.size': JSON.stringify(args).length,
        'data.type': typeof args,
      },
    },
    async () => {
      // Processing logic
      return { content: [] };
    }
  );
});

3. Handle Errors Gracefully

server.tool('safe_operation', 'Safe operation', {}, async (args) => {
  try {
    // Operation logic
    return { content: [{ type: 'text', text: 'Success' }] };
  } catch (error) {
    Sentry.captureException(error);
    return {
      content: [{
        type: 'text',
        text: 'Operation failed, but error was logged',
      }],
      isError: true,
    };
  }
});

Troubleshooting

Server Not Instrumented

Ensure you’re wrapping the server before calling connect():
// Correct order:
const server = new McpServer({...});
const wrapped = Sentry.wrapMcpServerWithSentry(server);
await wrapped.connect(transport);

// Wrong:
await server.connect(transport);
const wrapped = Sentry.wrapMcpServerWithSentry(server); // Too late

Double Wrapping

The integration prevents double-wrapping automatically:
const wrapped1 = Sentry.wrapMcpServerWithSentry(server);
const wrapped2 = Sentry.wrapMcpServerWithSentry(wrapped1);
// wrapped2 === wrapped1 (same instance, not wrapped again)

Missing Spans

Ensure tracing is enabled:
Sentry.init({
  dsn: 'your-dsn',
  tracesSampleRate: 1.0,
});

Build docs developers (and LLMs) love