UNPKG

@sentry/core

Version:
270 lines (234 loc) 9.93 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const debugBuild = require('./debug-build.js'); const semanticAttributes = require('./semanticAttributes.js'); require('./tracing/errors.js'); require('./utils-hoist/debug-build.js'); const logger = require('./utils-hoist/logger.js'); require('./utils-hoist/time.js'); const spanUtils = require('./utils/spanUtils.js'); const trace = require('./tracing/trace.js'); 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. function wrapMcpServerWithSentry(mcpServerInstance) { if (wrappedMcpServerInstances.has(mcpServerInstance)) { return mcpServerInstance; } if (!isMcpServerInstance(mcpServerInstance)) { debugBuild.DEBUG_BUILD && logger.logger.warn('Did not patch MCP server. Interface is incompatible.'); return mcpServerInstance; } // eslint-disable-next-line @typescript-eslint/unbound-method mcpServerInstance.connect = new Proxy(mcpServerInstance.connect, { apply(target, thisArg, argArray) { const [transport, ...restArgs] = argArray ; if (!transport.onclose) { transport.onclose = () => { if (transport.sessionId) { handleTransportOnClose(transport.sessionId); } }; } if (!transport.onmessage) { transport.onmessage = jsonRpcMessage => { if (transport.sessionId && isJsonRPCMessageWithRequestId(jsonRpcMessage)) { handleTransportOnMessage(transport.sessionId, jsonRpcMessage.id); } }; } const patchedTransport = new Proxy(transport, { set(target, key, value) { if (key === 'onmessage') { target[key] = new Proxy(value, { apply(onMessageTarget, onMessageThisArg, onMessageArgArray) { const [jsonRpcMessage] = onMessageArgArray; if (transport.sessionId && isJsonRPCMessageWithRequestId(jsonRpcMessage)) { handleTransportOnMessage(transport.sessionId, jsonRpcMessage.id); } return Reflect.apply(onMessageTarget, onMessageThisArg, onMessageArgArray); }, }); } else if (key === 'onclose') { target[key] = new Proxy(value, { apply(onCloseTarget, onCloseThisArg, onCloseArgArray) { if (transport.sessionId) { handleTransportOnClose(transport.sessionId); } return Reflect.apply(onCloseTarget, onCloseThisArg, onCloseArgArray); }, }); } else { target[key ] = value; } return true; }, }); return Reflect.apply(target, thisArg, [patchedTransport, ...restArgs]); }, }); mcpServerInstance.resource = new Proxy(mcpServerInstance.resource, { apply(target, thisArg, argArray) { const resourceName = argArray[0]; const resourceHandler = argArray[argArray.length - 1]; if (typeof resourceName !== 'string' || typeof resourceHandler !== 'function') { return target.apply(thisArg, argArray); } const wrappedResourceHandler = new Proxy(resourceHandler, { apply(resourceHandlerTarget, resourceHandlerThisArg, resourceHandlerArgArray) { const extraHandlerDataWithRequestId = resourceHandlerArgArray.find(isExtraHandlerDataWithRequestId); return associateContextWithRequestSpan(extraHandlerDataWithRequestId, () => { return trace.startSpan( { name: `mcp-server/resource:${resourceName}`, forceTransaction: true, attributes: { [semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'auto.function.mcp-server', [semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.mcp-server', [semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', 'mcp_server.resource': resourceName, }, }, () => resourceHandlerTarget.apply(resourceHandlerThisArg, resourceHandlerArgArray), ); }); }, }); return Reflect.apply(target, thisArg, [...argArray.slice(0, -1), wrappedResourceHandler]); }, }); mcpServerInstance.tool = new Proxy(mcpServerInstance.tool, { apply(target, thisArg, argArray) { const toolName = argArray[0]; const toolHandler = argArray[argArray.length - 1]; if (typeof toolName !== 'string' || typeof toolHandler !== 'function') { return target.apply(thisArg, argArray); } const wrappedToolHandler = new Proxy(toolHandler, { apply(toolHandlerTarget, toolHandlerThisArg, toolHandlerArgArray) { const extraHandlerDataWithRequestId = toolHandlerArgArray.find(isExtraHandlerDataWithRequestId); return associateContextWithRequestSpan(extraHandlerDataWithRequestId, () => { return trace.startSpan( { name: `mcp-server/tool:${toolName}`, forceTransaction: true, attributes: { [semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'auto.function.mcp-server', [semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.mcp-server', [semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', 'mcp_server.tool': toolName, }, }, () => toolHandlerTarget.apply(toolHandlerThisArg, toolHandlerArgArray), ); }); }, }); return Reflect.apply(target, thisArg, [...argArray.slice(0, -1), wrappedToolHandler]); }, }); mcpServerInstance.prompt = new Proxy(mcpServerInstance.prompt, { apply(target, thisArg, argArray) { const promptName = argArray[0]; const promptHandler = argArray[argArray.length - 1]; if (typeof promptName !== 'string' || typeof promptHandler !== 'function') { return target.apply(thisArg, argArray); } const wrappedPromptHandler = new Proxy(promptHandler, { apply(promptHandlerTarget, promptHandlerThisArg, promptHandlerArgArray) { const extraHandlerDataWithRequestId = promptHandlerArgArray.find(isExtraHandlerDataWithRequestId); return associateContextWithRequestSpan(extraHandlerDataWithRequestId, () => { return trace.startSpan( { name: `mcp-server/prompt:${promptName}`, forceTransaction: true, attributes: { [semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'auto.function.mcp-server', [semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.function.mcp-server', [semanticAttributes.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', 'mcp_server.prompt': promptName, }, }, () => promptHandlerTarget.apply(promptHandlerThisArg, promptHandlerArgArray), ); }); }, }); return Reflect.apply(target, thisArg, [...argArray.slice(0, -1), wrappedPromptHandler]); }, }); wrappedMcpServerInstances.add(mcpServerInstance); return mcpServerInstance ; } function isMcpServerInstance(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' && 'connect' in mcpServerInstance && typeof mcpServerInstance.connect === 'function' ); } function isJsonRPCMessageWithRequestId(target) { return ( typeof target === 'object' && target !== null && 'id' in target && (typeof target.id === 'number' || typeof target.id === 'string') ); } // Note that not all versions of the MCP library have `requestId` as a field on the extra data. function isExtraHandlerDataWithRequestId(target) { return ( typeof target === 'object' && target !== null && 'sessionId' in target && typeof target.sessionId === 'string' && 'requestId' in target && (typeof target.requestId === 'number' || typeof target.requestId === 'string') ); } const sessionAndRequestToRequestParentSpanMap = new Map(); function handleTransportOnClose(sessionId) { sessionAndRequestToRequestParentSpanMap.delete(sessionId); } function handleTransportOnMessage(sessionId, requestId) { const activeSpan = spanUtils.getActiveSpan(); if (activeSpan) { const requestIdToSpanMap = sessionAndRequestToRequestParentSpanMap.get(sessionId) ?? new Map(); requestIdToSpanMap.set(requestId, activeSpan); sessionAndRequestToRequestParentSpanMap.set(sessionId, requestIdToSpanMap); } } function associateContextWithRequestSpan( extraHandlerData, cb, ) { if (extraHandlerData) { const { sessionId, requestId } = extraHandlerData; const requestIdSpanMap = sessionAndRequestToRequestParentSpanMap.get(sessionId); if (!requestIdSpanMap) { return cb(); } const span = requestIdSpanMap.get(requestId); if (!span) { return cb(); } // remove the span from the map so it can be garbage collected requestIdSpanMap.delete(requestId); return trace.withActiveSpan(span, () => { return cb(); }); } return cb(); } exports.wrapMcpServerWithSentry = wrapMcpServerWithSentry; //# sourceMappingURL=mcp-server.js.map