Skip to content

Add opt-in strict MCP-Protocol-Version validation - #5957

Merged
JAORMX merged 1 commit into
mainfrom
strict-mcp-protocol-validation-5764
Jul 24, 2026
Merged

JAORMX merged 1 commit into
mainfrom
strict-mcp-protocol-validation-5764

Conversation

@JAORMX

@JAORMX JAORMX commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Why: The streamable HTTP proxy accepted any MCP-Protocol-Version header — isSupportedMCPVersion unconditionally returned true (pkg/transport/proxy/streamable/utils.go). This permissive stance is defensible for a transport-level proxy that doesn't depend on a specific MCP revision, but it was undocumented, and the spec says a server MUST reject an invalid/unsupported version with HTTP 400 — operators had no way to opt into that.

What:

  • Add a --strict-protocol-validation flag (default off, preserving today's version-agnostic behavior byte-for-byte via a p.strictProtocolValidation && … short-circuit).
  • When enabled, handlePost rejects a request whose MCP-Protocol-Version header names an unknown MCP revision with HTTP 400; an absent header is still accepted (the streamable HTTP spec says to assume 2025-03-26).
  • Known-version set {2024-11-05, 2025-03-26, 2025-06-18, 2025-11-25, mcp.MCPVersionModern} — the upcoming stateless revision is sourced from mcp.MCPVersionModern (not a duplicated literal) so the strict gate and the ClassifyRevision routing path can't drift.
  • Threaded end-to-end mirroring TrustProxyHeaders: CLI (run_flags.go) → RunConfig → types.Config → StdioTransport → streamable proxy Option; preserved across thv upgrade via the config applier.
  • Documented the default version-agnostic stance and the opt-in flag in docs/arch/03-transport-architecture.md.

Closes #5764 (items 1 & 2 landed in #5940 and #5944; this completes item 3).

Type of change

  • New feature

Test plan

  • Unit tests — go test -race on the affected packages passes:
    • TestHandlePost_StrictProtocolValidation (real handlePost via httptest): strict+unsupported→400, strict+supported→not-400, strict+absent→not-400, strict-off+unsupported→not-400 (default-behavior regression guard).
    • TestIsSupportedMCPVersion: all five known revisions → true; unknown/garbage/empty → false.
    • TestWithStrictProtocolValidation (runner builder) and the thv upgrade config-preservation guard extended to cover the new field (applier_test.go).
  • Linting — task lint 0 issues; task build succeeds; task docs regenerated (flag appears in docs/cli/thv_run.md).

Does this introduce a user-facing change?

Yes. New thv run --strict-protocol-validation flag (default off). When enabled, the streamable-HTTP proxy returns HTTP 400 for a request carrying an unknown/unsupported MCP-Protocol-Version header; default behavior is unchanged (any version accepted).

Special notes for reviewers

  • Reviewed by an MCP-spec + correctness/wiring + security panel. Spec: confirmed the version set is correct and that including 2026-07-28 is required for consistency with ClassifyRevision; 400-on-present-unsupported and accept-on-absent match the spec. Security: fail-closed, default-off preserves posture exactly, no bypass/race/DoS. Correctness: caught a wiring gap where thv upgrade dropped the persisted flag — fixed in applier.go + regression test.
  • Scope: the opt-in is CLI-only for now (mirrors where this proxy setting naturally lives). Operator-CRD and REST-API exposure — where TrustProxyHeaders also lives — are a deliberate follow-up, not included here.
  • Strict rejection uses a plain-text 400 body (transport-level, emitted before body parse); the separate ClassifyRevision path uses a JSON-RPC error body. Both are spec-legal; unifying them is a possible follow-up.
  • Unrelated: TestStandaloneSSE_ListChangedRefiltersThroughExistingMiddleware fails on main independently of this PR (reproduced with these changes stashed) — not introduced here.

Generated with Claude Code

@github-actions github-actions Bot added the size/M Medium PR: 300-599 lines changed label Jul 24, 2026
The streamable HTTP proxy accepted any MCP-Protocol-Version header
(isSupportedMCPVersion always returned true). This was an undocumented,
deliberately permissive stance: defensible for a transport-level proxy
that does not depend on a specific MCP revision, but the spec says a
server MUST reject an invalid/unsupported version with HTTP 400, and
operators had no way to opt into that behavior.

Add a --strict-protocol-validation flag (default off, preserving the
version-agnostic behavior byte-for-byte). When enabled, the proxy rejects
a request whose MCP-Protocol-Version header names an unknown MCP revision
with HTTP 400; an absent header is still accepted (the spec says to assume
2025-03-26). The known-version set is checked against a small map, with
the upcoming stateless revision sourced from mcp.MCPVersionModern so the
strict gate and the ClassifyRevision routing path cannot drift.

The flag is threaded end-to-end mirroring TrustProxyHeaders: CLI ->
RunConfig -> types.Config -> StdioTransport -> streamable proxy option,
and is preserved across thv upgrade via the config applier. The default
version-agnostic stance and the opt-in flag are documented in
docs/arch/03-transport-architecture.md.

Closes #5764.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JAORMX
JAORMX force-pushed the strict-mcp-protocol-validation-5764 branch from 58faad1 to 402c013 Compare July 24, 2026 07:19
@github-actions github-actions Bot added size/M Medium PR: 300-599 lines changed and removed size/M Medium PR: 300-599 lines changed labels Jul 24, 2026
@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.15385% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 71.83%. Comparing base (00b8159) to head (402c013).

Files with missing lines Patch % Lines
pkg/transport/stdio.go 66.66% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #5957   +/-   ##
=======================================
  Coverage   71.82%   71.83%           
=======================================
  Files         708      708           
  Lines       72798    72811   +13     
=======================================
+ Hits        52287    52302   +15     
  Misses      16772    16772           
+ Partials     3739     3737    -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@JAORMX
JAORMX merged commit 97a9dfa into main Jul 24, 2026
59 of 60 checks passed
@JAORMX
JAORMX deleted the strict-mcp-protocol-validation-5764 branch July 24, 2026 07:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M Medium PR: 300-599 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2025-11-25 compliance polish: probe version, filter errors, version stance

2 participants