Skip to content

Commit d4c74a9

Browse files
committed
refactor(mcp-server): use fill for method wrapping for transport handlers
1 parent 9776402 commit d4c74a9

File tree

2 files changed

+27
-50
lines changed

2 files changed

+27
-50
lines changed

packages/core/src/mcp-server.ts

Lines changed: 23 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
isJsonRpcRequest,
1212
validateMcpServerInstance,
1313
} from './utils/mcp-server/utils';
14+
import { fill } from './utils/object';
1415

1516
const wrappedMcpServerInstances = new WeakSet();
1617

@@ -31,75 +32,50 @@ export function wrapMcpServerWithSentry<S extends object>(mcpServerInstance: S):
3132

3233
const serverInstance = mcpServerInstance as MCPServerInstance;
3334

34-
// Wrap connect() to intercept AFTER Protocol sets up transport handlers
35-
const originalConnect = serverInstance.connect.bind(serverInstance);
36-
serverInstance.connect = new Proxy(originalConnect, {
37-
async apply(target, thisArg, argArray) {
38-
const [transport, ...restArgs] = argArray as [MCPTransport, ...unknown[]];
35+
fill(serverInstance, 'connect', (originalConnect) => {
36+
return async function(this: MCPServerInstance, transport: MCPTransport, ...restArgs: unknown[]) {
37+
const result = await originalConnect.call(this, transport, ...restArgs);
3938

40-
// Call the original connect first to let Protocol set up its handlers
41-
const result = await Reflect.apply(target, thisArg, [transport, ...restArgs]);
42-
43-
// Intercept incoming messages via onmessage
4439
if (transport.onmessage) {
45-
const protocolOnMessage = transport.onmessage.bind(transport);
46-
47-
transport.onmessage = new Proxy(protocolOnMessage, {
48-
apply(onMessageTarget, onMessageThisArg, onMessageArgs) {
49-
const [jsonRpcMessage, extra] = onMessageArgs;
50-
51-
// Instrument both requests and notifications
40+
fill(transport, 'onmessage', (originalOnMessage) => {
41+
return function(this: MCPTransport, jsonRpcMessage: unknown, extra?: unknown) {
5242
if (isJsonRpcRequest(jsonRpcMessage)) {
53-
return createMcpServerSpan(jsonRpcMessage, transport, extra as ExtraHandlerData, () => {
54-
return Reflect.apply(onMessageTarget, onMessageThisArg, onMessageArgs);
43+
return createMcpServerSpan(jsonRpcMessage, this, extra as ExtraHandlerData, () => {
44+
return originalOnMessage.call(this, jsonRpcMessage, extra);
5545
});
5646
}
5747
if (isJsonRpcNotification(jsonRpcMessage)) {
58-
return createMcpNotificationSpan(jsonRpcMessage, transport, extra as ExtraHandlerData, () => {
59-
return Reflect.apply(onMessageTarget, onMessageThisArg, onMessageArgs);
48+
return createMcpNotificationSpan(jsonRpcMessage, this, extra as ExtraHandlerData, () => {
49+
return originalOnMessage.call(this, jsonRpcMessage, extra);
6050
});
6151
}
62-
63-
return Reflect.apply(onMessageTarget, onMessageThisArg, onMessageArgs);
64-
},
52+
return originalOnMessage.call(this, jsonRpcMessage, extra);
53+
};
6554
});
6655
}
6756

68-
// Intercept outgoing messages via send
6957
if (transport.send) {
70-
const originalSend = transport.send.bind(transport);
71-
72-
transport.send = new Proxy(originalSend, {
73-
async apply(sendTarget, sendThisArg, sendArgs) {
74-
const [message] = sendArgs;
75-
76-
// Instrument outgoing notifications (but not requests/responses)
58+
fill(transport, 'send', (originalSend) => {
59+
return async function(this: MCPTransport, message: unknown) {
7760
if (isJsonRpcNotification(message)) {
78-
return createMcpOutgoingNotificationSpan(message, transport, () => {
79-
return Reflect.apply(sendTarget, sendThisArg, sendArgs);
61+
return createMcpOutgoingNotificationSpan(message, this, () => {
62+
return originalSend.call(this, message);
8063
});
8164
}
82-
83-
return Reflect.apply(sendTarget, sendThisArg, sendArgs);
84-
},
65+
return originalSend.call(this, message);
66+
};
8567
});
8668
}
8769

88-
// Handle transport lifecycle events
8970
if (transport.onclose) {
90-
const originalOnClose = transport.onclose.bind(transport);
91-
transport.onclose = new Proxy(originalOnClose, {
92-
apply(onCloseTarget, onCloseThisArg, onCloseArgs) {
93-
// TODO(bete): session and request correlation (methods at the bottom of this file)
94-
// if (transport.sessionId) {
95-
// handleTransportOnClose(transport.sessionId);
96-
// }
97-
return Reflect.apply(onCloseTarget, onCloseThisArg, onCloseArgs);
98-
},
71+
fill(transport, 'onclose', (originalOnClose) => {
72+
return function(this: MCPTransport, ...args: unknown[]) {
73+
return originalOnClose.call(this, ...args);
74+
};
9975
});
10076
}
10177
return result;
102-
},
78+
};
10379
});
10480

10581
wrappedMcpServerInstances.add(mcpServerInstance);

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,13 @@ describe('wrapMcpServerWithSentry', () => {
4747
let mockMcpServer: ReturnType<typeof createMockMcpServer>;
4848
let wrappedMcpServer: ReturnType<typeof createMockMcpServer>;
4949
let mockTransport: ReturnType<typeof createMockTransport>;
50+
let originalConnect: any;
5051

5152
beforeEach(() => {
5253
mockMcpServer = createMockMcpServer();
54+
originalConnect = mockMcpServer.connect;
5355
wrappedMcpServer = wrapMcpServerWithSentry(mockMcpServer);
5456
mockTransport = createMockTransport();
55-
// Don't connect transport here. let individual tests control when connection happens
5657
});
5758

5859
it('should proxy the connect method', () => {
@@ -95,8 +96,8 @@ describe('wrapMcpServerWithSentry', () => {
9596
it('should call original connect and preserve functionality', async () => {
9697
await wrappedMcpServer.connect(mockTransport);
9798

98-
// Original connect should have been called
99-
expect(mockMcpServer.connect).toHaveBeenCalledWith(mockTransport);
99+
// Check the original spy was called
100+
expect(originalConnect).toHaveBeenCalledWith(mockTransport);
100101
});
101102

102103
it('should create spans for incoming JSON-RPC requests', async () => {

0 commit comments

Comments
 (0)