Skip to content

2026 Streamable HTTP rejects whitespace-padded Mcp-Name values #3763

Open

Description

@DMomplaisir

Description

The 2026 Streamable HTTP header validation rejects a valid Mcp-Name when its value has leading or trailing HTTP optional whitespace. Header field values are subject to HTTP whitespace normalization; a client or intermediary that sends Mcp-Name: tools/call is rejected with JSON-RPC error -32020, although the trimmed value names the registered method.

This also fails the official conformance case sep-2243 (header validation).

Reproduction

Send a valid 2026 request with:

  • MCP-Protocol-Version: 2026-07-28
  • Mcp-Method: tools/call
  • Mcp-Name: tools/call
  • a valid tools/call JSON-RPC body

On mcp 2.3.0, the SDK returns -32020 rather than accepting the header value after HTTP whitespace handling.

Expected

Normalize permitted surrounding HTTP whitespace before comparing the header value, while continuing to reject values that differ after normalization.

Environment

Python SDK 2.3.0; reproduced by the MCP conformance suite's sep-2243 header-validation case.

Activity

  1. added
    bugSomething isn't working
    v2Affects the v2 line (2.x on main)
    spec-2026-07-28Concerns the SDK's implementation of the 2026-07-28 MCP spec revision
    on Oct 25, 2026
  2. aritejhg commented on Oct 26, 2026

    @aritejhg

    Thanks for the focused reproduction. I’d normalize only permitted HTTP optional whitespace around Mcp-Name before comparing it with the registered method, then add regression coverage for leading and trailing whitespace while keeping mismatched values rejected. I used Codex to help review the issue and will review and own any change. Does that match the intended spec handling, and would it be okay for me to take this once assigned?

  3. duongynhi000005-oss commented on Oct 27, 2026

    @duongynhi000005-oss

    Technical Root Cause & Proposal

    Root Cause Analysis

    Per RFC 9110 §5.6.3 (and RFC 7230 §3.2.4), HTTP header field values permit surrounding Optional Whitespace (OWS = *( SP / HTAB )). HTTP intermediaries, reverse proxies, and client libraries routinely normalize or retain leading/trailing spaces and tabs around field values.

    In src/mcp/shared/inbound.py:

    def decode_header_value(value: str | None) -> str | None:
        if value is None:
            return None
        m = _B64_SENTINEL.fullmatch(value)
        if m is None:
            return value

    When headers.get(MCP_NAME_HEADER) carries permitted OWS (e.g. Mcp-Name: tools/call or Mcp-Name: my_tool ), decode_header_value returns the unstripped literal. In classify_inbound_request (line 467):

    if body_value is not None and decode_header_value(headers.get(MCP_NAME_HEADER)) != body_value:
        return InboundLadderRejection(
            code=HEADER_MISMATCH,
            message=f"{MCP_NAME_HEADER} header does not match the request body's {name_key!r} parameter",
        )

    The comparison " tools/call " != "tools/call" fails, rejecting valid requests with HEADER_MISMATCH (-32020) and causing conformance scenario sep-2243 to fail.

    Proposed Fix

    In src/mcp/shared/inbound.py, strip permitted HTTP OWS (" \t") in decode_header_value before sentinel matching:

    def decode_header_value(value: str | None) -> str | None:
        if value is None:
            return None
        trimmed = value.strip(" \t")
        m = _B64_SENTINEL.fullmatch(trimmed)
        if m is None:
            return trimmed
        payload = m.group("payload")
        try:
            decoded = base64.b64decode(payload, validate=True)
        except binascii.Error:
            return None
        if base64.b64encode(decoded).decode("ascii") != payload:
            return None
        try:
            return decoded.decode("utf-8")
        except UnicodeDecodeError:
            return None
    • Trimming only " \t" conforms strictly to RFC 9110 OWS definitions.
    • Works consistently for plain text and sentinel-wrapped values (if an intermediary placed whitespace around =?base64?...?=).
    • Preserves exact round-tripping with encode_header_value (values with edge whitespace are already base64-wrapped by encode_header_value).
    • Protects both Mcp-Name and Mcp-Param-* headers.

    Verification & Test Coverage

    Added regression tests to tests/shared/test_inbound.py:

    • test_decode_header_value_normalizes_http_optional_whitespace: verifies spaces, tabs, and sentinel wrapping with OWS.
    • test_header_rung_accepts_whitespace_padded_name_header: verifies classify_inbound_request successfully accepts whitespace-padded Mcp-Name.
    • Full test suites passing: 240/240 tests pass (pytest tests/shared/test_inbound.py tests/server/test_streamable_http_modern.py). Ruff lint and formatting clean.

    Tested fix is ready on fork branch duongynhi000005-oss:fix/3763-whitespace-padded-mcp-name. Per CONTRIBUTING.md, keeping code on the fork — happy to open the PR if maintainers assign the issue!

  4. DMomplaisir commented on Oct 27, 2026

    @DMomplaisir
    Author

    Yep, that matches what I meant. I'd keep it to HTTP OWS (SP / HTAB) at the header boundary, then still reject anything that doesn't match after normalization.

    @aritejhg thanks for taking a look, and @duongynhi000005-oss appreciate the repro + tests. The existing patch looks like the right direction, so no need for folks to duplicate the same fix. Happy to review or help verify sep-2243 once a maintainer picks an owner.

  5. DMomplaisir commented on Oct 27, 2026

    @DMomplaisir
    Author

    Linking the existing implementation so it doesn’t get missed: #3764 already proposes trimming HTTP SP/HTAB before sentinel decoding and comparison. It was auto-closed by the assignment gate, not rejected on technical grounds. There is also the tested fork proposal linked above; I won’t create another implementation.

    The behavior I want as the reporter is to remove surrounding wire-level OWS before decoding, while preserving whitespace inside a decoded Base64 value and continuing to reject actual header/body mismatches. That keeps a body name containing significant edge whitespace distinct from padding added around its encoded header.

    AI disclosure: Codex reviewed the source and existing proposals and drafted this reply; I have not independently run either contributor’s tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingspec-2026-07-28Concerns the SDK's implementation of the 2026-07-28 MCP spec revisionv2Affects the v2 line (2.x on main)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions