-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
/
Copy pathmcp-server.ts
129 lines (113 loc) · 4.53 KB
/
mcp-server.ts
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import { DEBUG_BUILD } from './debug-build';
import {
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
} from './semanticAttributes';
import { startSpan } from './tracing';
import { logger } from './utils-hoist';
interface MCPServerInstance {
// The first arg is always a name, the last arg should always be a callback function (ie a handler).
// TODO: We could also make use of the resource uri argument somehow.
resource: (name: string, ...args: unknown[]) => void;
// The first arg is always a name, the last arg should always be a callback function (ie a handler).
tool: (name: string, ...args: unknown[]) => void;
// The first arg is always a name, the last arg should always be a callback function (ie a handler).
prompt: (name: string, ...args: unknown[]) => void;
}
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.
*/
// We are exposing this API for non-node runtimes that cannot rely on auto-instrumentation.
export function wrapMcpServerWithSentry<S extends object>(mcpServerInstance: S): S {
if (wrappedMcpServerInstances.has(mcpServerInstance)) {
return mcpServerInstance;
}
if (!isMcpServerInstance(mcpServerInstance)) {
DEBUG_BUILD && logger.warn('Did not patch MCP server. Interface is incompatible.');
return mcpServerInstance;
}
mcpServerInstance.resource = new Proxy(mcpServerInstance.resource, {
apply(target, thisArg, argArray) {
const resourceName: unknown = argArray[0];
const resourceHandler: unknown = argArray[argArray.length - 1];
if (typeof resourceName !== 'string' || typeof resourceHandler !== 'function') {
return target.apply(thisArg, argArray);
}
return startSpan(
{
name: `mcp-server/resource:${resourceName}`,
forceTransaction: true,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'auto.function.mcp-server',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.mcp-server',
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
'mcp_server.resource': resourceName,
},
},
() => target.apply(thisArg, argArray),
);
},
});
mcpServerInstance.tool = new Proxy(mcpServerInstance.tool, {
apply(target, thisArg, argArray) {
const toolName: unknown = argArray[0];
const toolHandler: unknown = argArray[argArray.length - 1];
if (typeof toolName !== 'string' || typeof toolHandler !== 'function') {
return target.apply(thisArg, argArray);
}
return startSpan(
{
name: `mcp-server/tool:${toolName}`,
forceTransaction: true,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'auto.function.mcp-server',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.mcp-server',
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
'mcp_server.tool': toolName,
},
},
() => target.apply(thisArg, argArray),
);
},
});
mcpServerInstance.prompt = new Proxy(mcpServerInstance.prompt, {
apply(target, thisArg, argArray) {
const promptName: unknown = argArray[0];
const promptHandler: unknown = argArray[argArray.length - 1];
if (typeof promptName !== 'string' || typeof promptHandler !== 'function') {
return target.apply(thisArg, argArray);
}
return startSpan(
{
name: `mcp-server/resource:${promptName}`,
forceTransaction: true,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'auto.function.mcp-server',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.mcp-server',
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route',
'mcp_server.prompt': promptName,
},
},
() => target.apply(thisArg, argArray),
);
},
});
wrappedMcpServerInstances.add(mcpServerInstance);
return mcpServerInstance as S;
}
function isMcpServerInstance(mcpServerInstance: unknown): mcpServerInstance is MCPServerInstance {
return (
typeof mcpServerInstance === 'object' &&
mcpServerInstance !== null &&
'resource' in mcpServerInstance &&
typeof mcpServerInstance.resource === 'function' &&
'tool' in mcpServerInstance &&
typeof mcpServerInstance.tool === 'function' &&
'prompt' in mcpServerInstance &&
typeof mcpServerInstance.prompt === 'function'
);
}