Skip to content

Commit b323422

Browse files
author
Luca Forstner
committed
feat(node): Add MCP server auto instrumentation
1 parent e0e9a2b commit b323422

2 files changed

Lines changed: 81 additions & 0 deletions

File tree

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import type { InstrumentationConfig } from '@opentelemetry/instrumentation';
2+
import { isWrapped } from '@opentelemetry/instrumentation';
3+
import { InstrumentationNodeModuleFile } from '@opentelemetry/instrumentation';
4+
import { InstrumentationBase, InstrumentationNodeModuleDefinition } from '@opentelemetry/instrumentation';
5+
import { SDK_VERSION } from '@sentry/core';
6+
import { generateInstrumentOnce } from '../otel/instrument';
7+
import { defineIntegration, wrapMcpServerWithSentry } from '@sentry/core';
8+
9+
const supportedVersions = ['>=1.9.0 <2'];
10+
11+
interface MCPServerInstance {
12+
tool: (toolName: string, toolSchema: unknown, handler: (...args: unknown[]) => unknown) => void;
13+
}
14+
15+
interface MCPSdkModuleDef {
16+
McpServer: new (...args: unknown[]) => MCPServerInstance;
17+
}
18+
19+
/**
20+
* Sentry instrumentation for MCP Servers (`@modelcontextprotocol/sdk` package)
21+
*/
22+
export class McpInstrumentation extends InstrumentationBase {
23+
public constructor(config: InstrumentationConfig = {}) {
24+
super('sentry-modelcontextprotocol-sdk', SDK_VERSION, config);
25+
}
26+
27+
/**
28+
* Initializes the instrumentation by defining the modules to be patched.
29+
*/
30+
public init(): InstrumentationNodeModuleDefinition[] {
31+
const moduleDef = new InstrumentationNodeModuleDefinition('@modelcontextprotocol/sdk', supportedVersions);
32+
33+
moduleDef.files.push(
34+
new InstrumentationNodeModuleFile(
35+
'@modelcontextprotocol/sdk/server/mcp.js',
36+
supportedVersions,
37+
(moduleExports: MCPSdkModuleDef) => {
38+
if (isWrapped(moduleExports.McpServer)) {
39+
this._unwrap(moduleExports, 'McpServer');
40+
}
41+
42+
this._wrap(moduleExports, 'McpServer', originalMcpServerClass => {
43+
return new Proxy(originalMcpServerClass, {
44+
construct(McpServerClass, mcpServerConstructorArgArray) {
45+
const mcpServerInstance = new McpServerClass(...mcpServerConstructorArgArray);
46+
47+
return wrapMcpServerWithSentry(mcpServerInstance);
48+
},
49+
});
50+
});
51+
52+
return moduleExports;
53+
},
54+
(moduleExports: MCPSdkModuleDef) => {
55+
this._unwrap(moduleExports, 'McpServer');
56+
},
57+
),
58+
);
59+
60+
return [moduleDef];
61+
}
62+
}
63+
const INTEGRATION_NAME = 'MCP';
64+
65+
const instrumentMcp = generateInstrumentOnce('MCP', () => {
66+
return new McpInstrumentation();
67+
});
68+
69+
/**
70+
* Integration capturing tracing data for MCP servers (via the `@modelcontextprotocol/sdk` package).
71+
*/
72+
export const mcpIntegration = defineIntegration(() => {
73+
return {
74+
name: INTEGRATION_NAME,
75+
setupOnce() {
76+
instrumentMcp();
77+
},
78+
};
79+
});

packages/node/src/sdk/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { envToBool } from '../utils/envToBool';
3939
import { defaultStackParser, getSentryRelease } from './api';
4040
import { NodeClient } from './client';
4141
import { initOpenTelemetry, maybeInitializeEsmLoader } from './initOtel';
42+
import { mcpIntegration } from '../integrations/mcp-server';
4243

4344
function getCjsOnlyIntegrations(): Integration[] {
4445
return isCjs() ? [modulesIntegration()] : [];
@@ -69,6 +70,7 @@ export function getDefaultIntegrationsWithoutPerformance(): Integration[] {
6970
nodeContextIntegration(),
7071
childProcessIntegration(),
7172
processSessionIntegration(),
73+
mcpIntegration(),
7274
...getCjsOnlyIntegrations(),
7375
];
7476
}

0 commit comments

Comments
 (0)