11import { fill } from '../../utils/object' ;
2- import type { ExtraHandlerData , MCPServerInstance , MCPTransport } from './types' ;
2+ import { logger } from '../../utils/logger' ;
3+ import { DEBUG_BUILD } from '../../debug-build' ;
4+ import type { ExtraHandlerData , MCPServerInstance , MCPTransport , McpHandlerExtra } from './types' ;
35import {
46 createMcpNotificationSpan ,
57 createMcpOutgoingNotificationSpan ,
@@ -10,6 +12,18 @@ import {
1012} from './utils' ;
1113
1214const wrappedMcpServerInstances = new WeakSet ( ) ;
15+ const wrappedHandlerMethods = new WeakSet ( ) ;
16+
17+ // Map to track handler completion promises by request ID
18+ const requestToHandlerPromiseMap = new Map < string | number , {
19+ resolve : ( value : unknown ) => void ;
20+ reject : ( reason : unknown ) => void ;
21+ } > ( ) ;
22+
23+ /**
24+ * Type for MCP handler callbacks
25+ */
26+ type McpHandlerCallback = ( ...args : unknown [ ] ) => unknown | Promise < unknown > ;
1327
1428/**
1529 * Wraps a MCP Server instance from the `@modelcontextprotocol/sdk` package with Sentry instrumentation.
@@ -28,24 +42,44 @@ export function wrapMcpServerWithSentry<S extends object>(mcpServerInstance: S):
2842
2943 const serverInstance = mcpServerInstance as MCPServerInstance ;
3044
45+ // Wrap tool, resource, and prompt methods to ensure proper async context
46+ wrapHandlerMethods ( serverInstance ) ;
47+
3148 fill ( serverInstance , 'connect' , ( originalConnect ) => {
3249 return async function ( this : MCPServerInstance , transport : MCPTransport , ...restArgs : unknown [ ] ) {
3350 const result = await originalConnect . call ( this , transport , ...restArgs ) ;
3451
3552 if ( transport . onmessage ) {
3653 fill ( transport , 'onmessage' , ( originalOnMessage ) => {
37- return function ( this : MCPTransport , jsonRpcMessage : unknown , extra ?: unknown ) {
54+ return async function ( this : MCPTransport , jsonRpcMessage : unknown , extra ?: unknown ) {
3855 if ( isJsonRpcRequest ( jsonRpcMessage ) ) {
39- return createMcpServerSpan ( jsonRpcMessage , this , extra as ExtraHandlerData , ( ) => {
40- return originalOnMessage . call ( this , jsonRpcMessage , extra ) ;
56+ return await createMcpServerSpan ( jsonRpcMessage , this , extra as ExtraHandlerData , async ( ) => {
57+ const request = jsonRpcMessage as { id : string | number ; method : string } ;
58+
59+ const handlerPromise = new Promise < unknown > ( ( resolve , reject ) => {
60+ requestToHandlerPromiseMap . set ( request . id , { resolve, reject } ) ;
61+
62+ setTimeout ( ( ) => {
63+ const entry = requestToHandlerPromiseMap . get ( request . id ) ;
64+ if ( entry ) {
65+ requestToHandlerPromiseMap . delete ( request . id ) ;
66+ resolve ( undefined ) ;
67+ }
68+ } , 30000 ) ;
69+ } ) ;
70+
71+ const originalResult = originalOnMessage . call ( this , jsonRpcMessage , extra ) ;
72+ await handlerPromise ;
73+ return originalResult ;
4174 } ) ;
4275 }
76+
4377 if ( isJsonRpcNotification ( jsonRpcMessage ) ) {
44- return createMcpNotificationSpan ( jsonRpcMessage , this , extra as ExtraHandlerData , ( ) => {
45- return originalOnMessage . call ( this , jsonRpcMessage , extra ) ;
78+ return await createMcpNotificationSpan ( jsonRpcMessage , this , extra as ExtraHandlerData , async ( ) => {
79+ return await originalOnMessage . call ( this , jsonRpcMessage , extra ) ;
4680 } ) ;
4781 }
48- return originalOnMessage . call ( this , jsonRpcMessage , extra ) ;
82+ return await originalOnMessage . call ( this , jsonRpcMessage , extra ) ;
4983 } ;
5084 } ) ;
5185 }
@@ -54,18 +88,23 @@ export function wrapMcpServerWithSentry<S extends object>(mcpServerInstance: S):
5488 fill ( transport , 'send' , ( originalSend ) => {
5589 return async function ( this : MCPTransport , message : unknown ) {
5690 if ( isJsonRpcNotification ( message ) ) {
57- return createMcpOutgoingNotificationSpan ( message , this , ( ) => {
58- return originalSend . call ( this , message ) ;
91+ return await createMcpOutgoingNotificationSpan ( message , this , async ( ) => {
92+ return await originalSend . call ( this , message ) ;
5993 } ) ;
6094 }
61- return originalSend . call ( this , message ) ;
95+ return await originalSend . call ( this , message ) ;
6296 } ;
6397 } ) ;
6498 }
6599
66100 if ( transport . onclose ) {
67101 fill ( transport , 'onclose' , ( originalOnClose ) => {
68102 return function ( this : MCPTransport , ...args : unknown [ ] ) {
103+ for ( const [ , promiseEntry ] of requestToHandlerPromiseMap . entries ( ) ) {
104+ promiseEntry . resolve ( undefined ) ;
105+ }
106+ requestToHandlerPromiseMap . clear ( ) ;
107+
69108 return originalOnClose . call ( this , ...args ) ;
70109 } ;
71110 } ) ;
@@ -78,6 +117,98 @@ export function wrapMcpServerWithSentry<S extends object>(mcpServerInstance: S):
78117 return mcpServerInstance as S ;
79118}
80119
120+ /**
121+ * Wraps the tool, resource, and prompt registration methods to ensure
122+ * handlers execute within the correct span context
123+ */
124+ function wrapHandlerMethods ( serverInstance : MCPServerInstance ) : void {
125+ if ( wrappedHandlerMethods . has ( serverInstance ) ) {
126+ return ;
127+ }
128+
129+ fill ( serverInstance , 'tool' , ( originalTool ) => {
130+ return function ( this : MCPServerInstance , ...args : unknown [ ] ) {
131+ const toolName = args [ 0 ] as string ;
132+ const lastArg = args [ args . length - 1 ] ;
133+
134+ if ( typeof lastArg !== 'function' ) {
135+ return originalTool . apply ( this , args ) ;
136+ }
137+
138+ const wrappedCallback = wrapHandlerCallback ( lastArg as McpHandlerCallback , 'tool' , toolName ) ;
139+ const newArgs = [ ...args . slice ( 0 , - 1 ) , wrappedCallback ] ;
140+
141+ return originalTool . apply ( this , newArgs ) ;
142+ } ;
143+ } ) ;
144+
145+ fill ( serverInstance , 'resource' , ( originalResource ) => {
146+ return function ( this : MCPServerInstance , ...args : unknown [ ] ) {
147+ const resourceName = args [ 0 ] as string ;
148+ const lastArg = args [ args . length - 1 ] ;
149+
150+ if ( typeof lastArg !== 'function' ) {
151+ return originalResource . apply ( this , args ) ;
152+ }
153+
154+ const wrappedCallback = wrapHandlerCallback ( lastArg as McpHandlerCallback , 'resource' , resourceName ) ;
155+ const newArgs = [ ...args . slice ( 0 , - 1 ) , wrappedCallback ] ;
156+
157+ return originalResource . apply ( this , newArgs ) ;
158+ } ;
159+ } ) ;
160+
161+ fill ( serverInstance , 'prompt' , ( originalPrompt ) => {
162+ return function ( this : MCPServerInstance , ...args : unknown [ ] ) {
163+ const promptName = args [ 0 ] as string ;
164+ const lastArg = args [ args . length - 1 ] ;
165+
166+ if ( typeof lastArg !== 'function' ) {
167+ return originalPrompt . apply ( this , args ) ;
168+ }
169+
170+ const wrappedCallback = wrapHandlerCallback ( lastArg as McpHandlerCallback , 'prompt' , promptName ) ;
171+ const newArgs = [ ...args . slice ( 0 , - 1 ) , wrappedCallback ] ;
172+
173+ return originalPrompt . apply ( this , newArgs ) ;
174+ } ;
175+ } ) ;
176+
177+ wrappedHandlerMethods . add ( serverInstance ) ;
178+ }
179+
180+ /**
181+ * Wraps a handler callback to ensure it executes within the correct span context
182+ */
183+ function wrapHandlerCallback ( callback : McpHandlerCallback , handlerType : string , handlerName : string ) : McpHandlerCallback {
184+ return async function ( this : unknown , ...args : unknown [ ] ) {
185+ const extra = args . find ( ( arg ) : arg is McpHandlerExtra =>
186+ typeof arg === 'object' &&
187+ arg !== null &&
188+ 'requestId' in arg
189+ ) ;
190+
191+ if ( extra ?. requestId ) {
192+ const promiseEntry = requestToHandlerPromiseMap . get ( extra . requestId ) ;
193+
194+ if ( promiseEntry ) {
195+ try {
196+ const result = await callback . apply ( this , args ) ;
197+ requestToHandlerPromiseMap . delete ( extra . requestId ) ;
198+ promiseEntry . resolve ( result ) ;
199+ return result ;
200+ } catch ( error ) {
201+ requestToHandlerPromiseMap . delete ( extra . requestId ) ;
202+ promiseEntry . reject ( error ) ;
203+ throw error ;
204+ }
205+ }
206+ }
207+
208+ return await callback . apply ( this , args ) ;
209+ } ;
210+ }
211+
81212// =============================================================================
82213// SESSION AND REQUEST CORRELATION (Legacy support)
83214// =============================================================================
0 commit comments