Skip to content

Commit a4e8d96

Browse files
its-mashclaude
andcommitted
fix: handle SSE responses in TC-SH-014 and TC-SH-015 E2E tests
Per the MCP Streamable HTTP spec (2025-03-26), when a POST contains JSON-RPC requests, the server may respond with either Content-Type: application/json or text/event-stream. The client MUST support both. Add parseMcpResponse() helper that checks the Content-Type header and extracts JSON from SSE data: lines when the gateway returns an SSE stream instead of plain JSON. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
1 parent 7a6f8cc commit a4e8d96

1 file changed

Lines changed: 42 additions & 4 deletions

File tree

tests/e2e/specs/streamable-http.wdio.ts

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,35 @@ import {
4040
const CLOUDFLARE_SERVER_ID = 'cloudflare-server';
4141
const STUB_HTTP_PORT = 3457;
4242

43+
/**
44+
* Parse an MCP Streamable HTTP response that may be either JSON or SSE format.
45+
*
46+
* Per the MCP spec (2025-03-26), when a POST contains JSON-RPC requests, the
47+
* server MUST respond with either `Content-Type: application/json` (single JSON
48+
* object) or `Content-Type: text/event-stream` (SSE stream). The client MUST
49+
* support both cases.
50+
*
51+
* SSE responses contain `data:` lines with JSON-RPC messages. We extract the
52+
* first JSON-RPC response message from the stream.
53+
*/
54+
function parseMcpResponse<T>(contentType: string | null, responseText: string): T {
55+
if (contentType?.includes('text/event-stream')) {
56+
// Parse SSE: extract JSON from `data:` lines
57+
const lines = responseText.split('\n');
58+
for (const line of lines) {
59+
if (line.startsWith('data:')) {
60+
const data = line.slice('data:'.length).trim();
61+
if (data) {
62+
return JSON.parse(data) as T;
63+
}
64+
}
65+
}
66+
throw new Error(`No data events found in SSE response: ${responseText.substring(0, 500)}`);
67+
}
68+
// Default: parse as plain JSON
69+
return JSON.parse(responseText) as T;
70+
}
71+
4372
// ============================================================================
4473
// Test Suite: Streamable HTTP Transport & Notifications
4574
// ============================================================================
@@ -372,6 +401,7 @@ describe('Streamable HTTP: OAuth MCP Client Flow', function () {
372401
});
373402

374403
console.log('[test] Initialize response status:', res.status, res.statusText);
404+
console.log('[test] Initialize response content-type:', res.headers.get('content-type'));
375405
const responseText = await res.text();
376406
console.log('[test] Initialize response body:', responseText.substring(0, 1000));
377407

@@ -381,7 +411,8 @@ describe('Streamable HTTP: OAuth MCP Client Flow', function () {
381411
}
382412
expect(res.status).toBeLessThan(400);
383413

384-
const body = JSON.parse(responseText) as {
414+
// The server may respond with JSON or SSE (per MCP Streamable HTTP spec)
415+
const body = parseMcpResponse<{
385416
jsonrpc: string;
386417
id: number;
387418
result?: {
@@ -393,7 +424,7 @@ describe('Streamable HTTP: OAuth MCP Client Flow', function () {
393424
};
394425
serverInfo: { name: string; version: string };
395426
};
396-
};
427+
}>(res.headers.get('content-type'), responseText);
397428

398429
console.log('[test] Initialize result:', JSON.stringify(body));
399430

@@ -447,13 +478,17 @@ describe('Streamable HTTP: OAuth MCP Client Flow', function () {
447478
});
448479

449480
console.log('[test] Session init status:', initRes.status, initRes.statusText);
481+
console.log('[test] Session init content-type:', initRes.headers.get('content-type'));
450482
const initText = await initRes.text();
451483
console.log('[test] Session init body:', initText.substring(0, 1000));
452484
if (!initRes.ok) {
453485
console.log('[test] FAILURE: /mcp returned', initRes.status, '- body:', initText);
454486
}
455487
expect(initRes.status).toBeLessThan(400);
456488

489+
// Parse the response (may be JSON or SSE per spec)
490+
parseMcpResponse<{ jsonrpc: string; id: number }>(initRes.headers.get('content-type'), initText);
491+
457492
// Check for Mcp-Session-Id in response headers
458493
const sessionId = initRes.headers.get('mcp-session-id');
459494
console.log('[test] Session ID:', sessionId);
@@ -496,9 +531,12 @@ describe('Streamable HTTP: OAuth MCP Client Flow', function () {
496531
});
497532

498533
expect(toolsRes.ok).toBe(true);
499-
const toolsBody = await toolsRes.json() as {
534+
console.log('[test] Tools response content-type:', toolsRes.headers.get('content-type'));
535+
const toolsText = await toolsRes.text();
536+
console.log('[test] Tools response body:', toolsText.substring(0, 1000));
537+
const toolsBody = parseMcpResponse<{
500538
result?: { tools: Array<{ name: string; description?: string }> };
501-
};
539+
}>(toolsRes.headers.get('content-type'), toolsText);
502540

503541
console.log('[test] Tools count:', toolsBody.result?.tools?.length ?? 0);
504542
if (toolsBody.result?.tools && toolsBody.result.tools.length > 0) {

0 commit comments

Comments
 (0)