The core MCP client advertises text/event-stream support but parses the whole body only with JSON.parse. A valid SSE response fails parsing, becomes null, and callers receive {}. This loses tool results, isError flags, and JSON-RPC errors.
Tested on commit e497e1a01e171f9559f87e0f998318a9efc06609 with Node.js v24.12.0 on Windows, using only an injected local fetch fixture. No production credentials, API requests, or real phone calls were used. I have not established whether the production endpoint currently selects SSE.
Minimal reproduction
Save this as repro-sse.mjs in the repository root and run node repro-sse.mjs:
import assert from 'node:assert/strict';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { listMcpTools } from './packages/core/lib/mcp-client.js';
import { tokenCachePath, writePrivateJson } from './packages/core/lib/cache.js';
import { MCP_PROTOCOL_VERSION } from './packages/core/lib/constants.js';
const config = {
cacheRoot: fs.mkdtempSync(path.join(os.tmpdir(), 'calle-sse-repro-')),
serverUrl: 'https://example.invalid/mcp',
timeoutSeconds: 2,
minTtlSeconds: 0,
};
writePrivateJson(tokenCachePath(config.cacheRoot, config.serverUrl), {
token: { access_token: 'test-token-not-a-credential' },
});
const expected = { tools: [{ name: 'echo', inputSchema: { type: 'object' } }] };
function fetchFor(useSse) {
return async (_url, options) => {
const req = JSON.parse(options.body);
assert.equal(options.headers.Accept, 'application/json, text/event-stream');
if (req.method === 'notifications/initialized') {
return new Response(null, { status: 202 });
}
const initialize = req.method === 'initialize';
const result = initialize ? {
protocolVersion: MCP_PROTOCOL_VERSION,
capabilities: { tools: {} },
serverInfo: { name: 'fixture', version: '1' },
} : expected;
const json = JSON.stringify({ jsonrpc: '2.0', id: req.id, result });
const sse = useSse && !initialize;
return new Response(sse ? `event: message\ndata: ${json}\n\n` : json, {
headers: { 'content-type': sse ? 'text/event-stream' : 'application/json' },
});
};
}
const json = await listMcpTools({ config, fetchImpl: fetchFor(false) });
const sse = await listMcpTools({ config, fetchImpl: fetchFor(true) });
console.log({ json, sse });
assert.deepEqual(json, expected); // passes
assert.deepEqual(sse, expected); // fails: actual {}
Actual behavior
| Final response |
JSON |
SSE |
| Tool list with one tool |
One tool returned |
{} returned |
Tool result with isError: true |
Error flag/content preserved |
{} returned |
JSON-RPC error -32602 |
Rejects with McpHttpError |
Resolves with {} |
A six-case parity harness reproduced all three comparisons.
Expected behavior
Equivalent JSON and SSE envelopes should produce equivalent results and errors. Malformed or incomplete streams should raise a useful protocol error instead of returning an empty success.
Cause and impact
In packages/core/lib/mcp-client.js, line 46 calls JSON.parse; lines 67–73 swallow the exception. Line 198 advertises SSE. Lines 245 and 277 conceal the missing decoded response with an empty object.
When a server selects SSE, tool discovery can appear empty and downstream code can lose a reported operation failure.
Suggested direction
Decode according to the response media type, parse SSE event framing, match the JSON-RPC response ID, preserve result/error envelopes, and reject malformed or missing expected responses. Keep empty HTTP 202 notification acknowledgements valid. Add JSON/SSE parity tests plus interleaved-notification and truncated-event coverage.
This report and reproduction were prepared with AI coding assistance.
The core MCP client advertises
text/event-streamsupport but parses the whole body only withJSON.parse. A valid SSE response fails parsing, becomesnull, and callers receive{}. This loses tool results,isErrorflags, and JSON-RPC errors.Tested on commit
e497e1a01e171f9559f87e0f998318a9efc06609with Node.js v24.12.0 on Windows, using only an injected local fetch fixture. No production credentials, API requests, or real phone calls were used. I have not established whether the production endpoint currently selects SSE.Minimal reproduction
Save this as
repro-sse.mjsin the repository root and runnode repro-sse.mjs:Actual behavior
{}returnedisError: true{}returned-32602McpHttpError{}A six-case parity harness reproduced all three comparisons.
Expected behavior
Equivalent JSON and SSE envelopes should produce equivalent results and errors. Malformed or incomplete streams should raise a useful protocol error instead of returning an empty success.
Cause and impact
In
packages/core/lib/mcp-client.js, line 46 callsJSON.parse; lines 67–73 swallow the exception. Line 198 advertises SSE. Lines 245 and 277 conceal the missing decoded response with an empty object.When a server selects SSE, tool discovery can appear empty and downstream code can lose a reported operation failure.
Suggested direction
Decode according to the response media type, parse SSE event framing, match the JSON-RPC response ID, preserve result/error envelopes, and reject malformed or missing expected responses. Keep empty HTTP 202 notification acknowledgements valid. Add JSON/SSE parity tests plus interleaved-notification and truncated-event coverage.
This report and reproduction were prepared with AI coding assistance.