Skip to content

fix: eight protocol/transport defects in MCP 2.0 negotiation, replay, and bridging - #259

Merged
steipete merged 8 commits into
mainfrom
steipete/mcp2-hardening-protocol
Aug 2, 2026
Merged

fix: eight protocol/transport defects in MCP 2.0 negotiation, replay, and bridging#259
steipete merged 8 commits into
mainfrom
steipete/mcp2-hardening-protocol

Conversation

@steipete

@steipete steipete commented Aug 2, 2026

Copy link
Copy Markdown
Collaborator

Fixes eight protocol/transport defects found by an adversarial review of the MCP 2.0 series (#254#258) and by testing against 30+ public MCP servers in the wild. Every fix ships with a regression test that fails before and passes after.

Negotiation

  • SSE fallback no longer inherits modern negotiation. SSE is a legacy-era transport, but the fallback reused the modern-negotiating client. Consequences: auto mode re-sent server/discover on the SSE attempt (breaking legacy SSE-only servers), and pin mode masked the SDK's accurate error behind a meaningless SSE error: Non-200 status code (405). Verified in the wild: pinning 2026-07-28 at Context7 now reports "the server did not offer pinned protocol version 2026-07-28 via server/discover (no fallback in pin mode)". The fallback site now builds a legacy client via a small HttpClientFactory seam rather than threading flags.
  • stdio auto-negotiation can now recover from strict legacy servers. Because McporterStdioTransport subclasses the SDK transport, the probe runs in-place on the only process; a legacy server that exits on any pre-initialize request left nothing to fall back to. Auto mode now retries once with a fresh legacy process. Modern servers keep the single-spawn fast path.
  • stdio probe timeout raised from 3s to the SDK default (60s), configurable via MCPORTER_STDIO_PROBE_TIMEOUT_MS. The old 3s budget included process cold-start and misclassified slow modern servers as legacy.

Record/replay

  • RecordTransport now mirrors the wrapped transport's full capability surface. It was dropping hasPerRequestStream, so the v2 client never attached requestSignal and on timeout emitted the removed notifications/cancelled while the HTTP request kept running.
  • Replay honors the caller's elicitation handler instead of force-installing a decline responder, so recordings whose MRTR round was accepted can actually replay.
  • Recorded inbound notifications (progress, logging, list-changed) are re-emitted in order instead of being silently dropped.

Bridge & pagination

  • The dual-era bridge preserves non-object output schemas and projects results per era via the SDK's own projectCallToolResult. Previously outputSchema: {type:"number"} was dropped on both eras and a v1 client's call failed -32602 expected record, received number.
  • Resource pagination detects a repeated/non-advancing cursor instead of issuing 100 requests and appending the same page 99 times.

Proof: pnpm check clean; 955 passed / 3 skipped; autoreview clean (0.96). Re-verified against live servers across all four eras (2025-03-26 → 2026-07-28) with no regressions.

🤖 Generated with Claude Code

@clawsweeper

clawsweeper Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

ClawSweeper status: review started.

I am starting a fresh review of this pull request: fix: eight protocol/transport defects in MCP 2.0 negotiation, replay, and bridging This is item 1/1 in the current shard. Shard 0/1.

This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking.

Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted.

@steipete
steipete merged commit 4d38b88 into main Aug 2, 2026
5 checks passed

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7c4d44b981

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

return {
context: await connectSseFallbackTransport(
client,
clientFactory.createLegacy(activeDefinition),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Don't fall back to legacy when modern is pinned

When a server is configured with protocolVersion: '2026-07-28', any primary Streamable HTTP error that is not tagged EraNegotiationFailed still reaches this fallback and creates a forced-legacy SSE client. For pinned-modern definitions, a 404/405 transport mismatch or similar primary failure can therefore silently connect and run calls in the 2025-era protocol even though the config says 2026-07-28 is required; skip the SSE fallback for pinned definitions rather than only for one error code.

Useful? React with 👍 / 👎.

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