-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathindex.ts
More file actions
85 lines (75 loc) · 3.21 KB
/
index.ts
File metadata and controls
85 lines (75 loc) · 3.21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import { getClient } from '../../currentScopes';
import { fill } from '../../utils/object';
import { wrapAllMCPHandlers } from './handlers';
import { wrapTransportError, wrapTransportOnClose, wrapTransportOnMessage, wrapTransportSend } from './transport';
import type { MCPServerInstance, McpServerWrapperOptions, MCPTransport, ResolvedMcpOptions } from './types';
import { validateMcpServerInstance } from './validation';
/**
* Tracks wrapped MCP server instances to prevent double-wrapping
* @internal
*/
const wrappedMcpServerInstances = new WeakSet();
/**
* Wraps a MCP Server instance from the `@modelcontextprotocol/sdk` package with Sentry instrumentation.
*
* Compatible with versions `^1.9.0` of the `@modelcontextprotocol/sdk` package (legacy `tool`/`resource`/`prompt` API)
* and versions that expose the newer `registerTool`/`registerResource`/`registerPrompt` API (introduced in 1.x, sole API in 2.x).
* Automatically instruments transport methods and handler functions for comprehensive monitoring.
*
* @example
* ```typescript
* import * as Sentry from '@sentry/core';
* import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
* import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
*
* // Default: inputs/outputs captured based on sendDefaultPii option
* const server = Sentry.wrapMcpServerWithSentry(
* new McpServer({ name: "my-server", version: "1.0.0" })
* );
*
* // Explicitly control input/output capture
* const server = Sentry.wrapMcpServerWithSentry(
* new McpServer({ name: "my-server", version: "1.0.0" }),
* { recordInputs: true, recordOutputs: false }
* );
*
* const transport = new StreamableHTTPServerTransport();
* await server.connect(transport);
* ```
*
* @param mcpServerInstance - MCP server instance to instrument
* @param options - Optional configuration for recording inputs and outputs
* @returns Instrumented server instance (same reference)
*/
export function wrapMcpServerWithSentry<S extends object>(mcpServerInstance: S, options?: McpServerWrapperOptions): S {
if (wrappedMcpServerInstances.has(mcpServerInstance)) {
return mcpServerInstance;
}
if (!validateMcpServerInstance(mcpServerInstance)) {
return mcpServerInstance;
}
const serverInstance = mcpServerInstance as MCPServerInstance;
const client = getClient();
const sendDefaultPii = Boolean(client?.getOptions().sendDefaultPii);
const resolvedOptions: ResolvedMcpOptions = {
recordInputs: options?.recordInputs ?? sendDefaultPii,
recordOutputs: options?.recordOutputs ?? sendDefaultPii,
};
fill(serverInstance, 'connect', originalConnect => {
return async function (this: MCPServerInstance, transport: MCPTransport, ...restArgs: unknown[]) {
const result = await (originalConnect as (...args: unknown[]) => Promise<unknown>).call(
this,
transport,
...restArgs,
);
wrapTransportOnMessage(transport, resolvedOptions);
wrapTransportSend(transport, resolvedOptions);
wrapTransportOnClose(transport);
wrapTransportError(transport);
return result;
};
});
wrapAllMCPHandlers(serverInstance);
wrappedMcpServerInstances.add(mcpServerInstance);
return mcpServerInstance;
}