diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts index 9ae95ca..53d7fb2 100644 --- a/src/lib/utils.test.ts +++ b/src/lib/utils.test.ts @@ -868,6 +868,104 @@ describe('Feature: MCP Proxy', () => { }), ) }) + + it('Scenario: Handle server-initiated requests (without corresponding client request)', async () => { + // Given mock transports for client and server + const mockTransportToClient = { + send: vi.fn().mockResolvedValue(undefined), + close: vi.fn().mockResolvedValue(undefined), + start: vi.fn().mockResolvedValue(undefined), + onmessage: vi.fn(), + onclose: vi.fn(), + onerror: vi.fn(), + } as unknown as Transport + + const mockTransportToServer = { + send: vi.fn().mockResolvedValue(undefined), + close: vi.fn().mockResolvedValue(undefined), + start: vi.fn().mockResolvedValue(undefined), + onmessage: vi.fn(), + onclose: vi.fn(), + onerror: vi.fn(), + } as unknown as Transport + + // When setting up the proxy + mcpProxy({ + transportToClient: mockTransportToClient, + transportToServer: mockTransportToServer, + ignoredTools: [], + }) + + // And when server sends a ping message (server-initiated, no corresponding client request) + const serverPingMessage = { + jsonrpc: '2.0' as const, + method: 'ping', + id: 'server-ping-1', + } + + // Simulate server sending the message + if (mockTransportToServer.onmessage) { + mockTransportToServer.onmessage(serverPingMessage) + } + + // Then the message should be forwarded to the client without errors + expect(mockTransportToClient.send).toHaveBeenCalledWith( + expect.objectContaining({ + jsonrpc: '2.0', + method: 'ping', + id: 'server-ping-1', + }), + ) + }) + + it('Scenario: Handle server-initiated response messages without corresponding request', async () => { + // Given mock transports for client and server + const mockTransportToClient = { + send: vi.fn().mockResolvedValue(undefined), + close: vi.fn().mockResolvedValue(undefined), + start: vi.fn().mockResolvedValue(undefined), + onmessage: vi.fn(), + onclose: vi.fn(), + onerror: vi.fn(), + } as unknown as Transport + + const mockTransportToServer = { + send: vi.fn().mockResolvedValue(undefined), + close: vi.fn().mockResolvedValue(undefined), + start: vi.fn().mockResolvedValue(undefined), + onmessage: vi.fn(), + onclose: vi.fn(), + onerror: vi.fn(), + } as unknown as Transport + + // When setting up the proxy + mcpProxy({ + transportToClient: mockTransportToClient, + transportToServer: mockTransportToServer, + ignoredTools: [], + }) + + // And when server sends a response with an ID that has no corresponding request + const orphanedResponse = { + jsonrpc: '2.0' as const, + id: 'unknown-request-id', + result: {}, + } + + // Simulate server sending a response without a matching request + if (mockTransportToServer.onmessage) { + mockTransportToServer.onmessage(orphanedResponse) + } + + // Then the response should still be forwarded to the client + expect(mockTransportToClient.send).toHaveBeenCalledWith( + expect.objectContaining({ + jsonrpc: '2.0', + id: 'unknown-request-id', + result: {}, + }), + ) + }) }) describe('setupOAuthCallbackServerWithLongPoll', () => { diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 3824903..c9fcd93 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -103,6 +103,7 @@ export function createMessageTransformer({ const messageId = message.id if (!messageId) return message const originalRequest = pendingRequests.get(messageId) + if (!originalRequest) return message pendingRequests.delete(messageId) return transformResponseFunction?.(originalRequest, message) ?? message }