Skip to content

Commit 39164fd

Browse files
Zelys-DFKHclaude
andcommitted
test(@modelcontextprotocol/node): cover pre-read body pattern in stateless mode
Add a test for the handleRequest(req, res, parsedBody) overload in stateless mode: drain the IncomingMessage body upstream (body-parser pattern), pass it as parsedBody, and verify that both an initialize request and a subsequent tools/list request succeed on the same reused transport. Found while investigating #1994. In v2 the _hasHandledRequest reuse restriction was removed, so both requests succeed. This test pins that. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 2c0c481 commit 39164fd

1 file changed

Lines changed: 44 additions & 0 deletions

File tree

packages/middleware/node/test/streamableHttp.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1682,6 +1682,50 @@ describe('Zod v4', () => {
16821682
});
16831683
expect(stream2.status).toBe(409); // Conflict - only one stream allowed
16841684
});
1685+
1686+
it('should handle subsequent requests when body is pre-read before handleRequest (body-parser pattern)', async () => {
1687+
// Covers the body-parser / middleware pattern where the server drains the
1688+
// IncomingMessage body before calling transport.handleRequest(req, res, parsedBody).
1689+
// Both the initialize request and subsequent non-initialize requests must succeed
1690+
// when the transport is reused across requests in stateless mode.
1691+
1692+
// Build the server manually so the handler closure can reference transport directly
1693+
const preReadTransport = new NodeStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
1694+
const preReadMcpServer = new McpServer({ name: 'test-server', version: '1.0.0' }, { capabilities: { logging: {} } });
1695+
preReadMcpServer.registerTool(
1696+
'greet',
1697+
{ description: 'A greeting tool', inputSchema: z.object({ name: z.string() }) },
1698+
async ({ name }): Promise<CallToolResult> => ({ content: [{ type: 'text', text: `Hello, ${name}!` }] })
1699+
);
1700+
await preReadMcpServer.connect(preReadTransport);
1701+
1702+
const preReadServer = createServer(async (req, res) => {
1703+
// Simulate body-parser middleware: drain the stream, then pass parsedBody
1704+
const chunks: Buffer[] = [];
1705+
for await (const chunk of req) chunks.push(Buffer.from(chunk));
1706+
const bodyStr = Buffer.concat(chunks).toString();
1707+
const parsedBody = bodyStr ? (JSON.parse(bodyStr) as unknown) : undefined;
1708+
try {
1709+
await preReadTransport.handleRequest(req, res, parsedBody);
1710+
} catch (error) {
1711+
console.error('Error handling request:', error);
1712+
if (!res.headersSent) res.writeHead(500).end();
1713+
}
1714+
});
1715+
const preReadBaseUrl = await listenOnRandomPort(preReadServer);
1716+
1717+
try {
1718+
// Initialize — must succeed
1719+
const initResponse = await sendPostRequest(preReadBaseUrl, TEST_MESSAGES.initialize);
1720+
expect(initResponse.status).toBe(200);
1721+
1722+
// Subsequent request on the same reused transport — must also succeed
1723+
const toolsResponse = await sendPostRequest(preReadBaseUrl, TEST_MESSAGES.toolsList);
1724+
expect(toolsResponse.status).toBe(200);
1725+
} finally {
1726+
await stopTestServer({ server: preReadServer, transport: preReadTransport });
1727+
}
1728+
});
16851729
});
16861730

16871731
// Test SSE priming events for POST streams

0 commit comments

Comments
 (0)