Fix JSON parse error on SSE events with empty data (#1184)

This commit is contained in:
Felix Weinberger 2025-11-27 21:22:00 +00:00 • committed by GitHub
commit 9203091219
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 56 additions and 0 deletions

View file

@ -865,6 +865,57 @@ describe('StreamableHTTPClientTransport', () => {
const reconnectHeaders = fetchMock.mock.calls[1][1]?.headers as Headers;
expect(reconnectHeaders.get('last-event-id')).toBe('event-123');
});
it('should not throw JSON parse error on priming events with empty data', async () => {
transport = new StreamableHTTPClientTransport(new URL('http://localhost:1234/mcp'));
const errorSpy = vi.fn();
transport.onerror = errorSpy;
const resumptionTokenSpy = vi.fn();
// Create a stream that sends a priming event (ID only, empty data) then a real message
const streamWithPrimingEvent = new ReadableStream({
start(controller) {
// Send a priming event with ID but empty data - this should NOT cause a JSON parse error
controller.enqueue(new TextEncoder().encode('id: priming-123\ndata: \n\n'));
// Send a real message
controller.enqueue(
new TextEncoder().encode('id: msg-456\ndata: {"jsonrpc":"2.0","result":{"tools":[]},"id":"req-1"}\n\n')
);
controller.close();
}
});
const fetchMock = global.fetch as Mock;
fetchMock.mockResolvedValueOnce({
ok: true,
status: 200,
headers: new Headers({ 'content-type': 'text/event-stream' }),
body: streamWithPrimingEvent
});
await transport.start();
transport.send(
{
jsonrpc: '2.0',
method: 'tools/list',
id: 'req-1',
params: {}
},
{ resumptionToken: undefined, onresumptiontoken: resumptionTokenSpy }
);
await vi.advanceTimersByTimeAsync(50);
// No JSON parse errors should have occurred
expect(errorSpy).not.toHaveBeenCalledWith(
expect.objectContaining({ message: expect.stringContaining('Unexpected end of JSON') })
);
// Resumption token callback should have been called for both events with IDs
expect(resumptionTokenSpy).toHaveBeenCalledWith('priming-123');
expect(resumptionTokenSpy).toHaveBeenCalledWith('msg-456');
});
});
it('invalidates all credentials on InvalidClientError during auth', async () => {

View file

@ -338,6 +338,11 @@ export class StreamableHTTPClientTransport implements Transport {
onresumptiontoken?.(event.id);
}
// Skip events with no data (priming events, keep-alives)
if (!event.data) {
continue;
}
if (!event.event || event.event === 'message') {
try {
const message = JSONRPCMessageSchema.parse(JSON.parse(event.data));