Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions packages/middleware/node/test/streamableHttp.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1682,6 +1682,50 @@ describe('Zod v4', () => {
});
expect(stream2.status).toBe(409); // Conflict - only one stream allowed
});

it('should handle subsequent requests when body is pre-read before handleRequest (body-parser pattern)', async () => {
// Covers the body-parser / middleware pattern where the server drains the
// IncomingMessage body before calling transport.handleRequest(req, res, parsedBody).
// Both the initialize request and subsequent non-initialize requests must succeed
// when the transport is reused across requests in stateless mode.

// Build the server manually so the handler closure can reference transport directly
const preReadTransport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
const preReadMcpServer = new McpServer({ name: 'test-server', version: '1.0.0' }, { capabilities: { logging: {} } });
preReadMcpServer.registerTool(
'greet',
{ description: 'A greeting tool', inputSchema: z.object({ name: z.string() }) },
async ({ name }): Promise<CallToolResult> => ({ content: [{ type: 'text', text: `Hello, ${name}!` }] })
);
await preReadMcpServer.connect(preReadTransport);

const preReadServer = createServer(async (req, res) => {
// Simulate body-parser middleware: drain the stream, then pass parsedBody
const chunks: Buffer[] = [];
for await (const chunk of req) chunks.push(Buffer.from(chunk));
const bodyStr = Buffer.concat(chunks).toString();
const parsedBody = bodyStr ? (JSON.parse(bodyStr) as unknown) : undefined;
try {
await preReadTransport.handleRequest(req, res, parsedBody);
} catch (error) {
console.error('Error handling request:', error);
if (!res.headersSent) res.writeHead(500).end();
}
});
const preReadBaseUrl = await listenOnRandomPort(preReadServer);

try {
// Initialize — must succeed
const initResponse = await sendPostRequest(preReadBaseUrl, TEST_MESSAGES.initialize);
expect(initResponse.status).toBe(200);

// Subsequent request on the same reused transport — must also succeed
const toolsResponse = await sendPostRequest(preReadBaseUrl, TEST_MESSAGES.toolsList);
expect(toolsResponse.status).toBe(200);
} finally {
await stopTestServer({ server: preReadServer, transport: preReadTransport });
}
});
});

// Test SSE priming events for POST streams
Expand Down
31 changes: 16 additions & 15 deletions test/integration/test/server/cloudflareWorkers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,28 +150,29 @@ export default {
it('should handle MCP requests', async () => {
expect(env).not.toBeNull();

// Retry connection — wrangler may report "Ready" before it can handle requests
let client!: Client;
// Retry the full interaction — wrangler may report "Ready" before it can
// reliably handle all requests (initialize succeeds but subsequent calls
// can still get "Network connection lost." on slower runtimes like Node 20).
let lastError: unknown;
for (let attempt = 0; attempt < 5; attempt++) {
const client = new Client({ name: 'test-client', version: '1.0.0' });
const transport = new StreamableHTTPClientTransport(new URL(`http://127.0.0.1:${PORT}/`));
try {
client = new Client({ name: 'test-client', version: '1.0.0' });
const transport = new StreamableHTTPClientTransport(new URL(`http://127.0.0.1:${PORT}/`));
await client.connect(transport);
lastError = undefined;
break;
const result = await client.callTool({ name: 'greet', arguments: { name: 'World' } });
expect(result.content).toEqual([{ type: 'text', text: 'Hello, World!' }]);
await client.close();
return;
} catch (error) {
lastError = error;
await new Promise(resolve => setTimeout(resolve, 1000));
try {
await client.close();
} catch {
/* ignore cleanup errors */
}
if (attempt < 4) await new Promise(resolve => setTimeout(resolve, 1000));
}
}
if (lastError) {
throw lastError;
}

const result = await client.callTool({ name: 'greet', arguments: { name: 'World' } });
expect(result.content).toEqual([{ type: 'text', text: 'Hello, World!' }]);

await client.close();
throw lastError;
}, 30_000);
});
Loading