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