feat(node): export toWebRequest(), the IncomingMessage→Request conversion inside toNodeHandler (#2390)
This commit is contained in:
parent
199a28e9e2
commit
6cc7b1cb66
2 changed files with 245 additions and 41 deletions
|
|
@ -23,7 +23,7 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
|
|||
import { createServer } from 'node:http';
|
||||
|
||||
import { parseExampleArgs } from '@mcp-examples/shared';
|
||||
import { NodeStreamableHTTPServerTransport, toNodeHandler } from '@modelcontextprotocol/node';
|
||||
import { NodeStreamableHTTPServerTransport, toNodeHandler, toWebRequest } from '@modelcontextprotocol/node';
|
||||
import type {
|
||||
CallToolResult,
|
||||
ElicitRequestFormParams,
|
||||
|
|
@ -276,23 +276,15 @@ if (transport === 'stdio') {
|
|||
|
||||
createServer((req, res) => {
|
||||
void (async () => {
|
||||
// Read the body once for the predicate and pass it forward.
|
||||
let body: unknown;
|
||||
if (req.method === 'POST') {
|
||||
const chunks: Buffer[] = [];
|
||||
for await (const chunk of req) chunks.push(chunk as Buffer);
|
||||
const raw = Buffer.concat(chunks).toString('utf8');
|
||||
try {
|
||||
body = raw ? JSON.parse(raw) : undefined;
|
||||
} catch {
|
||||
body = undefined;
|
||||
}
|
||||
}
|
||||
const probe = new globalThis.Request(`http://localhost${req.url ?? '/'}`, {
|
||||
method: req.method,
|
||||
headers: req.headers as Record<string, string>
|
||||
});
|
||||
await ((await isLegacyRequest(probe, body)) ? handleLegacy(req, res, body) : modern(req, res, body));
|
||||
// `toWebRequest` reads the Node body into a web-standard `Request`,
|
||||
// so the body now lives in `request`, not `req`. Ask the predicate
|
||||
// first — it classifies an internal clone, leaving `request`
|
||||
// readable for the `.json()` both arms need (reading `.json()`
|
||||
// first would make the predicate's internal clone throw).
|
||||
const request = await toWebRequest(req);
|
||||
const legacy = await isLegacyRequest(request);
|
||||
const body: unknown = req.method === 'POST' ? await request.json().catch(() => {}) : undefined;
|
||||
await (legacy ? handleLegacy(req, res, body) : modern(req, res, body));
|
||||
})().catch(error => {
|
||||
console.error('[server] request error:', error instanceof Error ? error.message : error);
|
||||
if (!res.headersSent) res.writeHead(500).end();
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import { randomUUID } from 'node:crypto';
|
|||
|
||||
import { parseExampleArgs } from '@mcp-examples/shared';
|
||||
import { createMcpExpressApp } from '@modelcontextprotocol/express';
|
||||
import { NodeStreamableHTTPServerTransport, toNodeHandler } from '@modelcontextprotocol/node';
|
||||
import { NodeStreamableHTTPServerTransport, toNodeHandler, toWebRequest } from '@modelcontextprotocol/node';
|
||||
import type { McpRequestContext } from '@modelcontextprotocol/server';
|
||||
import { createMcpHandler, isInitializeRequest, isLegacyRequest, McpServer } from '@modelcontextprotocol/server';
|
||||
import cors from 'cors';
|
||||
|
|
@ -72,14 +72,10 @@ app.use(
|
|||
);
|
||||
|
||||
app.post('/mcp', async (req: Request, res: Response) => {
|
||||
// The predicate inspects the same headers + body the entry does. Express
|
||||
// has parsed the JSON body; pass it as `parsedBody` so the predicate need
|
||||
// not re-read the stream.
|
||||
const probe = new globalThis.Request(`http://localhost${req.url}`, {
|
||||
method: req.method,
|
||||
headers: req.headers as Record<string, string>
|
||||
});
|
||||
await ((await isLegacyRequest(probe, req.body)) ? handleLegacy(req, res) : modernNode(req, res, req.body));
|
||||
// `toWebRequest` builds the web-standard `Request` the predicate takes.
|
||||
// Express has already parsed (and consumed) the JSON body — pass it along.
|
||||
const probe = await toWebRequest(req, req.body);
|
||||
await ((await isLegacyRequest(probe)) ? handleLegacy(req, res) : modernNode(req, res, req.body));
|
||||
});
|
||||
// GET (standalone SSE stream / reconnect with Last-Event-ID) and DELETE
|
||||
// (explicit session termination per the MCP spec) are sessionful-2025-only —
|
||||
|
|
|
|||
Loading…
Reference in a new issue