Skip to content

fix(sep-1932): serve the draft (2026-07-28) wire from the DPoP fixtures; name a protocol-version rejection once - #1

Open
figitaki wants to merge 2 commits into
nbarbettini:feat/1932-server-dpop-discoveryfrom
figitaki:fix/sep-1932-draft-wire
Open

figitaki wants to merge 2 commits into
nbarbettini:feat/1932-server-dpop-discoveryfrom
figitaki:fix/sep-1932-draft-wire

Conversation

@figitaki

Copy link
Copy Markdown

Fixes modelcontextprotocol#566. Follow-up to the SEP-1932 thread (modelcontextprotocol/modelcontextprotocol#1932): the sep-1932-compliant-server fixture fails auth/dpop-server-validation at the CLI's default spec version.

Two commits that stand alone. If you would rather fix the fixtures by moving them to the TS SDK 2.x packages, as @nbarbettini suggested, drop the first commit and keep the second: the once-only diagnostic is useful either way.

Problem

The CLI defaults to DRAFT_PROTOCOL_VERSION (2026-07-28), whose probe is server/discover with a per-request _meta envelope. The DPoP fixtures are built on the pinned @modelcontextprotocol/sdk 1.29 StreamableHTTPServerTransport, which only speaks the initialize/session lifecycle and answers that probe with HTTP 400 / JSON-RPC -32000 ("Unsupported protocol version: 2026-07-28 …"). AcceptsValidProof therefore fails before any DPoP check runs, 23 further checks go not-testable, and ReportsDpopErrorCode warns: 2/26 at the default, 27/27 with --spec-version 2025-11-25 --force.

The unit tests did not see it because testContext() defaults to LATEST_SPEC_VERSION (2025-11-25), so only the initialize path was exercised.

Change

Three parts, no SDK bump.

  1. examples/servers/typescript/sep-1932-draft-wire.ts (new): a minimal stateless dispatcher for the fixtures, following the pattern everything-server.ts already uses on the same SDK: _meta/header validation (-32020, -32602, -32022 as in the everything-server), then server/discover, ping, tools/list, and tools/call for the echo tool, with resultType and the SEP-2549 hints stamped on results. Authentication stays in the fixture and runs before dispatch.
  2. sep-1932-compliant-server.ts, sep-1932-broken-server.ts: route draft-wire requests to that dispatcher; everything else still goes through the SDK transport. The reject-all fixture needs no change. The echo tool is defined once per fixture and shared by both paths.
  3. src/scenarios/server/auth/dpop.ts: when the positive probe is refused with a non-401 JSON-RPC error, AcceptsValidProof now says so once, with the server's error and a pointer to --spec-version, instead of a bare "Expected 2xx, got 400" followed by 24 not-testable rows. A 401 is still reported as before: that is a real DPoP result. UNSUPPORTED_PROTOCOL_VERSION is the existing constant from spec-types/draft.

Plus two unit tests at DRAFT_PROTOCOL_VERSION (compliant passes with no failures or warnings; broken gets its 2xx baseline and fails the negatives), so the stateless path is covered.

Verification

  • npm run lint, npm run typecheck, npm run build: clean.
  • npx vitest run: all passing, including the two new cases.
  • CLI against the fixtures (fixture started with DPOP_ISSUER_JWK, DPOP_ISSUER and DPOP_AUDIENCE=http://localhost:3010/mcp; CLI with the matching DPOP_ISSUER_PRIVATE_JWK and DPOP_ISSUER; scenario auth/dpop-server-validation):
Fixture Spec version Before After
compliant 2026-07-28 (default) 2/26, 24 failed, 1 warning 27/27
compliant 2025-11-25 --force 27/27 27/27
broken 2026-07-28 n/a (probe rejected) 1/25 (AcceptsValidProof passes, 24 negatives fail, as intended)
original compliant (pre-change) 2026-07-28 "Expected 2xx, got 400" "… answered the server/discover probe with JSON-RPC error -32000 ("Bad Request: Unsupported protocol version: 2026-07-28 (supported versions: …)") … Rerun with --spec-version …"

Pre-existing and untouched: examples/servers/typescript/no-dns-rebinding-protection.ts has two tsc errors on the base branch; npm run lint does not run tsc on examples, so CI is unaffected.

Notes for review

🤖 Generated with Claude Code

figitaki and others added 2 commits October 17, 2026 16:33
The CLI defaults to DRAFT_PROTOCOL_VERSION, whose probe is `server/discover`
with a per-request `_meta` envelope. The compliant and broken fixtures are
built on the pinned SDK 1.x StreamableHTTPServerTransport, which only speaks
the initialize/session lifecycle and answers that probe with 400 / -32000.
AcceptsValidProof then fails before any DPoP check runs and 23 further
checks go not-testable: 2/26 at the default, 27/27 at 2025-11-25.

Add a minimal stateless dispatcher (sep-1932-draft-wire.ts), following the
pattern everything-server.ts already uses on the same SDK: `_meta`/header
validation, then server/discover, ping, tools/list and tools/call for the
echo tool, with resultType and the SEP-2549 hints on results. Route
draft-wire requests there in both fixtures; authentication is unchanged and
still runs first. The reject-all fixture needs nothing.

Add two unit tests at DRAFT_PROTOCOL_VERSION so the stateless path is
covered: testContext() defaults to LATEST_SPEC_VERSION, which is why this
was only reachable by hand.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…Proof

When the positive probe is refused with a non-401 JSON-RPC error, the
request never reached the proof: SDKs that predate the stateless wire
answer server/discover with -32000, a draft-aware server with -32022. Say
so in the AcceptsValidProof failure, with the server's error and a pointer
to --spec-version, instead of "Expected 2xx, got 400" followed by 24
not-testable rows. A 401 is reported exactly as before; that is a real
DPoP result. UNSUPPORTED_PROTOCOL_VERSION is the existing constant from
spec-types/draft.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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