Skip to content

Commit 826164b

Browse files
cursoragentclaude
andcommitted
fix(mcp-server): Prevent double-wrapping of handlers when both API sets exist
When both legacy (tool/resource/prompt) and new (registerTool/registerResource/registerPrompt) API methods exist on an SDK instance, and the legacy method delegates to the new method, handlers were being wrapped twice. This caused duplicate error capture calls. Added a __sentry_wrapped__ marker to track already-wrapped handlers and skip re-wrapping them. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 91e1398 commit 826164b

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

packages/core/src/integrations/mcp-server/handlers.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,25 @@ function wrapMethodHandler(serverInstance: MCPServerInstance, methodName: keyof
4141
* @returns Wrapped handler function
4242
*/
4343
function createWrappedHandler(originalHandler: MCPHandler, methodName: keyof MCPServerInstance, handlerName: string) {
44-
return function (this: unknown, ...handlerArgs: unknown[]): unknown {
44+
// Check if handler is already wrapped to prevent double-wrapping
45+
// when both legacy and new API methods exist and one delegates to the other
46+
if ((originalHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__) {
47+
return originalHandler;
48+
}
49+
50+
const wrappedHandler = function (this: unknown, ...handlerArgs: unknown[]): unknown {
4551
try {
4652
return createErrorCapturingHandler.call(this, originalHandler, methodName, handlerName, handlerArgs);
4753
} catch (error) {
4854
DEBUG_BUILD && debug.warn('MCP handler wrapping failed:', error);
4955
return originalHandler.apply(this, handlerArgs);
5056
}
5157
};
58+
59+
// Mark the handler as wrapped
60+
(wrappedHandler as { __sentry_wrapped__?: boolean }).__sentry_wrapped__ = true;
61+
62+
return wrappedHandler;
5263
}
5364

5465
/**

packages/core/test/lib/integrations/mcp-server/mcpServerWrapper.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,45 @@ describe('wrapMcpServerWithSentry', () => {
178178
}).not.toThrow();
179179
});
180180
});
181+
182+
describe('Double-wrapping prevention', () => {
183+
it('should not double-wrap handlers when both tool() and registerTool() exist and one delegates to the other', () => {
184+
// Create a mock server with both APIs where tool() delegates to registerTool()
185+
const registeredHandlers = new Map<string, any>();
186+
const mockServerWithBothApis = {
187+
registerTool: vi.fn((name: string, ...args: any[]) => {
188+
const handler = args[args.length - 1];
189+
registeredHandlers.set(name, handler);
190+
}),
191+
tool: vi.fn(function (this: any, name: string, handler: any) {
192+
// Simulate legacy tool() delegating to registerTool()
193+
return this.registerTool(name, {}, handler);
194+
}),
195+
resource: vi.fn(),
196+
prompt: vi.fn(),
197+
connect: vi.fn().mockResolvedValue(undefined),
198+
server: {
199+
setRequestHandler: vi.fn(),
200+
},
201+
};
202+
203+
const wrapped = wrapMcpServerWithSentry(mockServerWithBothApis);
204+
205+
// Register a handler via the legacy tool() method
206+
const originalHandler = vi.fn();
207+
wrapped.tool('test-tool', originalHandler);
208+
209+
// Get the registered handler
210+
const registeredHandler = registeredHandlers.get('test-tool');
211+
212+
// The handler should be wrapped (have the __sentry_wrapped__ marker)
213+
expect((registeredHandler as any).__sentry_wrapped__).toBe(true);
214+
215+
// Verify that calling tool() only wraps once, not twice
216+
// If double-wrapped, the handler would have nested wrappers
217+
// We can verify this by checking that the registered handler is the wrapped version
218+
// and not a double-wrapped version
219+
expect(registeredHandler).not.toBe(originalHandler);
220+
});
221+
});
181222
});

0 commit comments

Comments
 (0)