diff --git a/.changeset/fix-onerror-nested-catch.md b/.changeset/fix-onerror-nested-catch.md new file mode 100644 index 000000000..300730432 --- /dev/null +++ b/.changeset/fix-onerror-nested-catch.md @@ -0,0 +1,5 @@ +--- +'@modelcontextprotocol/server': patch +--- + +Fix nested try-catch in StreamableHTTP transport onerror handler to prevent unhandled exceptions from bubbling up. diff --git a/packages/server/src/server/streamableHttp.ts b/packages/server/src/server/streamableHttp.ts index 74e689892..a8d5d11dc 100644 --- a/packages/server/src/server/streamableHttp.ts +++ b/packages/server/src/server/streamableHttp.ts @@ -635,7 +635,8 @@ export class WebStandardStreamableHTTPServerTransport implements Transport { if (options?.parsedBody === undefined) { try { rawMessage = await req.json(); - } catch { + } catch (error) { + this.onerror?.(error as Error); return this.createJsonErrorResponse(400, -32_700, 'Parse error: Invalid JSON'); } } else { @@ -649,7 +650,8 @@ export class WebStandardStreamableHTTPServerTransport implements Transport { messages = Array.isArray(rawMessage) ? rawMessage.map(msg => JSONRPCMessageSchema.parse(msg)) : [JSONRPCMessageSchema.parse(rawMessage)]; - } catch { + } catch (error) { + this.onerror?.(error as Error); return this.createJsonErrorResponse(400, -32_700, 'Parse error: Invalid JSON-RPC message'); } diff --git a/packages/server/test/server/streamableHttp.test.ts b/packages/server/test/server/streamableHttp.test.ts index ab6f22342..224c5d1c1 100644 --- a/packages/server/test/server/streamableHttp.test.ts +++ b/packages/server/test/server/streamableHttp.test.ts @@ -333,6 +333,38 @@ describe('Zod v4', () => { expectErrorResponse(errorData, -32_700, /Parse error.*Invalid JSON/); }); + it('should call onerror when receiving invalid JSON', async () => { + const errors: Error[] = []; + transport.onerror = error => errors.push(error); + + const request = new Request('http://localhost/mcp', { + method: 'POST', + headers: { + Accept: 'application/json, text/event-stream', + 'Content-Type': 'application/json' + }, + body: 'not valid json' + }); + const response = await transport.handleRequest(request); + + expect(response.status).toBe(400); + expect(errors.length).toBe(1); + }); + + it('should call onerror when receiving invalid JSON-RPC message', async () => { + sessionId = await initializeServer(); + const errors: Error[] = []; + transport.onerror = error => errors.push(error); + + const request = createRequest('POST', { invalid: 'not a jsonrpc message' } as unknown as JSONRPCMessage, { sessionId }); + const response = await transport.handleRequest(request); + + expect(response.status).toBe(400); + const errorData = await response.json(); + expectErrorResponse(errorData, -32_700, /Parse error.*Invalid JSON-RPC/); + expect(errors.length).toBe(1); + }); + it('should accept notifications without session and return 202', async () => { sessionId = await initializeServer();