Skip to content

feat(server)\!: drop McpHttpHandler.node; ship toNodeHandler(handler) in @modelcontextprotocol/node#2331

Open
felixweinberger wants to merge 1 commit into
v2-2026-07-28from
fweinberger/on-tonodehandler
Open

feat(server)\!: drop McpHttpHandler.node; ship toNodeHandler(handler) in @modelcontextprotocol/node#2331
felixweinberger wants to merge 1 commit into
v2-2026-07-28from
fweinberger/on-tonodehandler

Conversation

@felixweinberger

Copy link
Copy Markdown
Contributor

Removes the .node face from McpHttpHandler and ships the Node adapter as a free function toNodeHandler(handler) in @modelcontextprotocol/node. Stacked on fweinberger/on-small-fixes.

Motivation and Context

{ fetch, close, notify, bus } is the web-standards-idiomatic handler shape (Workers / Bun / Deno export default). Carrying a Node-specific .node(req, res, body?) method on the same interface couples the runtime-neutral entry to a Node http adapter and forces every host to take the Node types.

This PR:

  • removes McpHttpHandler.node from the interface and implementation
  • moves NodeIncomingMessageLike / NodeServerResponseLike and the nodeRequestToFetchRequest body verbatim into packages/middleware/node/src/toNodeHandler.ts as toNodeHandler(handler): (req, res, parsedBody?) => Promise<void> over handler.fetch (structurally typed FetchLikeMcpHandler, so hand-wired isLegacyRequest compositions also adapt)
  • reproduces the adapter-level error fallback (request-conversion / closed-handler throw → 500 / −32603 / echoable-id body) locally in the adapter; entry-internal failures continue to be reported via fetch
  • drops the two Node*Like type re-exports from @modelcontextprotocol/server; adds toNodeHandler + 4 types to @modelcontextprotocol/node
  • sweeps every .node(...) call site in examples/ (8 sites across harness.ts, legacy-routing, bearer-auth, oauth, oauth-client-credentials, json-response, stateless-legacy, subscriptions) and the integration/conformance fixtures; six @mcp-examples/* packages gain a @modelcontextprotocol/node workspace dep

How Has This Been Tested?

  • the six .node-face unit tests + nodeRequestResponse fixture re-homed to packages/middleware/node/test/toNodeHandler.test.ts (+1 new closed-handler 500 test); server suite keeps the detach-safe-fetch test only — pure relocation following the implementation, no assertions weakened
  • express/fastify/hono adapters and e2e helpers already use .fetch only — no change needed
  • Full gates: typecheck, lint, build:all; all package suites (core 1207, codemod 350, server-legacy 162, server 314, client 535, hono 12, fastify 22, express 40, node 86, shared 2); integration (348); e2e (2535p/205xf); conformance:all client ✓ / server 121p/2f (both pre-existing unexpected fails: json-schema-2020-12, server-sse-polling)

Breaking Changes

Yes. McpHttpHandler.node is removed. Migration:

// before
createServer((req, res) => void handler.node(req, res))
// after
import { toNodeHandler } from '@modelcontextprotocol/node'
createServer(toNodeHandler(handler))

migration.md + migration-SKILL.md carry the before/after; the existing create-mcp-handler.md changeset is reworded and a new handler-drop-node-face.md changeset is added (server major / node minor).

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

The only remaining handler.node reference is the intentional historical mapping in docs/migration-SKILL.md.

@felixweinberger felixweinberger requested a review from a team as a code owner June 19, 2026 14:18
@pkg-pr-new

pkg-pr-new Bot commented Jun 19, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/client@2331

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/codemod@2331

@modelcontextprotocol/server

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server@2331

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/server-legacy@2331

@modelcontextprotocol/express

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/express@2331

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/fastify@2331

@modelcontextprotocol/hono

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/hono@2331

@modelcontextprotocol/node

npm i https://pkg.pr.new/modelcontextprotocol/typescript-sdk/@modelcontextprotocol/node@2331

commit: f954a2d

Comment thread packages/middleware/node/src/index.ts Outdated
Comment thread packages/middleware/node/src/toNodeHandler.ts
@felixweinberger felixweinberger force-pushed the fweinberger/on-tonodehandler branch from f9069fa to c8779f8 Compare June 19, 2026 14:37
@felixweinberger felixweinberger force-pushed the fweinberger/on-small-fixes branch from 761bd17 to e230a8e Compare June 19, 2026 14:37
@changeset-bot

changeset-bot Bot commented Jun 19, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: daf43d3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@modelcontextprotocol/server Major
@modelcontextprotocol/node Major
@modelcontextprotocol/express Major
@modelcontextprotocol/fastify Major
@modelcontextprotocol/hono Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@changeset-bot

changeset-bot Bot commented Jun 19, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c8779f8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@modelcontextprotocol/server Major
@modelcontextprotocol/node Major
@modelcontextprotocol/express Major
@modelcontextprotocol/fastify Major
@modelcontextprotocol/hono Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@felixweinberger felixweinberger force-pushed the fweinberger/on-small-fixes branch from e230a8e to ee14814 Compare June 22, 2026 16:53
@felixweinberger felixweinberger force-pushed the fweinberger/on-tonodehandler branch from c8779f8 to d6e2be7 Compare June 22, 2026 16:54

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both points from my earlier review (the package README's Exports section and the loss of error reporting in the adapter's catch) are addressed in the latest revision — toNodeHandler now takes an optional onerror with tests, and the README documents the new exports. I didn't find any new issues, but since this is a breaking change to the public handler interface spanning five packages, it should still get a human maintainer's sign-off.

Extended reasoning...

Overview

This PR removes the .node(req, res, parsedBody?) face from McpHttpHandler and ships the Node adapter as a standalone toNodeHandler(handler, { onerror? }) in @modelcontextprotocol/node. The conversion logic (nodeRequestToFetchRequest, backpressure-aware response writing, the 500/-32603 fallback) moves verbatim from createMcpHandler.ts into the new toNodeHandler.ts, the six .node-face unit tests are re-homed to the node package with additional onerror coverage, eight examples plus the integration/conformance fixtures are swept to the new pattern, and the migration docs/changesets are updated. The latest commits also resolve the two nits from my prior review: the node package README now lists toNodeHandler and its types, and the adapter accepts an onerror callback restoring observability parity with the removed face (with tests pinning that the callback fires and that a throwing reporter doesn't alter the 500 response).

Security risks

Low. The adapter performs no auth decisions of its own — req.auth remains pass-through to handler.fetch as authInfo, never derived from headers, matching the prior .node face exactly. HTTP/2 pseudo-header skipping and the body re-serialization behavior are copied unchanged. The error fallback maps adapter failures to -32603/500 (consistent with the repo's error-handling conventions) and the onerror callback is wrapped so it cannot alter the response.

Level of scrutiny

High. This is a breaking change to a public interface (McpHttpHandler), drops two type exports from @modelcontextprotocol/server, and triggers major bumps across five packages per the changeset bot. While the implementation is largely a mechanical relocation with strong test coverage (the moved tests assert the same conversion semantics, plus new closed-handler and onerror cases), the decision to remove a shipped API surface and relocate it to a sibling package is a design/API-ownership call that the maintainers should explicitly endorse — it is exactly the kind of decision the repo's review conventions reserve for human judgment.

Other factors

The bug-hunting pass found no issues this round. Migration docs (migration.md, migration-SKILL.md) and both changesets accurately describe the new behavior including the onerror option, and the only remaining handler.node reference is the intentional historical mapping in the skill doc. Test gates reported in the description (full package suites, integration, e2e, conformance) are consistent with the diff's relocated coverage.

@felixweinberger felixweinberger force-pushed the fweinberger/on-tonodehandler branch from d6e2be7 to 7327fb9 Compare June 22, 2026 17:53

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of my earlier comments (the missing onerror reporting in toNodeHandler and the package README exports) are addressed in the latest revision and I found no further issues — but since this removes McpHttpHandler.node from the public API (major bump across server/express/fastify/hono), it still warrants a maintainer's review of the design before merging.

Extended reasoning...

Overview

This PR removes the .node(req, res, parsedBody?) face from McpHttpHandler and ships the Node adapter as toNodeHandler(handler, { onerror? }) in @modelcontextprotocol/node. The adapter body (request conversion, pre-parsed-body handling, req.auth pass-through, HTTP/2 pseudo-header skipping, write backpressure, 500/-32603 fallback) is moved verbatim from createMcpHandler.ts, the Node*Like types move packages, and every .node(...) call site across examples, integration tests, and the conformance fixture is swept to the new wrapper. The diff also carries a stacked change making ctx.mcpReq.log() emit request-related with a per-request _meta.logLevel filter on 2026-era requests.

Changes since my last review

The latest revision adds a ToNodeHandlerOptions.onerror callback (with tests covering the report-then-500 path and a throwing reporter), restoring the observability parity I flagged, and the @modelcontextprotocol/node README's Exports section and the migration docs now document toNodeHandler. Both prior inline comments are resolved.

Security risks

Low. The adapter forwards req.auth as pass-through authInfo exactly as the removed .node face did and performs no token derivation from headers; no auth, crypto, or validation logic changes. The behavior-relevant surface is the error fallback and header conversion, both relocated unchanged and covered by the relocated unit tests.

Level of scrutiny

High by policy rather than by bug risk: this is a breaking removal of a public API method with major changesets across five packages, plus a logging-semantics change (ctx.mcpReq.log riding the in-flight exchange and the spec-mandated absent-key suppression on 2026-era requests). Those are SDK-surface design decisions a maintainer should sign off on, even though the implementation is largely a mechanical relocation with tests re-homed and one new test added.

Other factors

Test coverage looks thorough — the six .node-face tests moved to the node package plus new onerror tests, integration and conformance fixtures updated, and the e2e log-from-handler scenario adjusted for the new _meta.logLevel opt-in. The migration guide and changesets accurately describe the shipped behavior, including the onerror option.

@felixweinberger felixweinberger force-pushed the fweinberger/on-small-fixes branch from ee14814 to cf76ed1 Compare June 22, 2026 20:12
@felixweinberger felixweinberger force-pushed the fweinberger/on-tonodehandler branch from 7327fb9 to bb7a7ca Compare June 22, 2026 20:12
Comment thread packages/middleware/node/src/toNodeHandler.ts
@felixweinberger felixweinberger force-pushed the fweinberger/on-tonodehandler branch from bb7a7ca to 8e9d981 Compare June 22, 2026 21:16
Base automatically changed from fweinberger/on-small-fixes to v2-2026-07-28 June 22, 2026 21:26
Comment thread packages/middleware/node/src/toNodeHandler.ts
@felixweinberger felixweinberger force-pushed the fweinberger/on-tonodehandler branch from 8e9d981 to daf43d3 Compare June 22, 2026 21:39
…dler) in @modelcontextprotocol/node

createMcpHandler now returns the web-standards-idiomatic { fetch, close, notify, bus } shape — what Workers/Bun/Deno expect from `export default`. The duck-typed .node(req, res, parsedBody?) face is removed; Node frameworks (Express, Fastify, plain node:http) wrap the handler once with toNodeHandler(handler) from @modelcontextprotocol/node, which converts the Node request to a web-standard Request, calls handler.fetch, and writes the Response back honoring write backpressure.

NodeIncomingMessageLike / NodeServerResponseLike move from @modelcontextprotocol/server to @modelcontextprotocol/node. The Node-conversion helpers and the .node-face unit tests move with them (toNodeHandler.test.ts).

Consumers updated: examples/server (dualEraStreamableHttp, multiRoundTrip), test/integration createMcpHandler over real HTTP, test/conformance everythingServer.

BREAKING CHANGE: McpHttpHandler.node is removed. Use toNodeHandler(handler) from @modelcontextprotocol/node.
@felixweinberger felixweinberger force-pushed the fweinberger/on-tonodehandler branch from daf43d3 to f954a2d Compare June 22, 2026 21:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant