Skip to content

feat(mcp): MCP 2026-07-28 stateless path + server/discover (slice 1+4) - #63

Merged
vvooki-sys merged 5 commits into
mainfrom
arch/mcp2026-stateless
Aug 3, 2026
Merged

feat(mcp): MCP 2026-07-28 stateless path + server/discover (slice 1+4)#63
vvooki-sys merged 5 commits into
mainfrom
arch/mcp2026-stateless

Conversation

@vvooki-sys

@vvooki-sys vvooki-sys commented Aug 3, 2026

Copy link
Copy Markdown
Owner

What

Slice 1 (plus the slice-4 items that are compliance-required) of the MCP 2026-07-28 adoption brief: the stateless protocol path (SEP-2575) served alongside the untouched legacy path.

Routing on MCP-Protocol-Version:

Header Path
absent legacy (the spec explicitly allows treating a missing header as 2025-03-26)
2024-11-05 / 2025-03-26 / 2025-06-18 / 2025-11-25 legacy, byte-identical (2025-06-18+ clients send their negotiated version — it must route, not error)
2026-07-28 stateless
anything else -32004 + {supported, requested} data (HTTP 400)

On the stateless path:

  • No sessions: nothing read, nothing minted — identity rides AuthContext per request (it already did). Any request can hit any fleet instance on the protocol path too.
  • server/discover (SEP-2575): supportedVersions, capabilities, serverInfo, instructions — the ECA-13 dynamic advisories port 1:1 (discover has the same per-request auth), covered by a contract test.
  • _meta validation: the three required io.modelcontextprotocol/* fields; missing → -32602, version conflicting with the header → -32020 (both HTTP 400). Key names are single-place constants in types.rs.
  • SEP-2243 mirrored headers: Mcp-Method required on every request, Mcp-Name on tools/call — including the =?base64?…?= sentinel decoding (minimal in-tree decoder, same no-new-deps posture as the hand-rolled SHA-256 in oauth.rs); missing/mismatch → -32020 + 400 per the transport spec MUST.
  • tools/list carries SEP-2549 ttlMs/cacheScope — REQUIRED fields in this revision: 1h TTL (the list is static per config), private scope (authenticated endpoint).
  • initialize/ping → method-not-found (removed in this revision); -32601 → HTTP 404, -32603 → 500, other protocol errors → 400. Batching rejected (left the protocol in 2025-06-18); notifications → 202.

Deliberately NOT here

  • Legacy path: zero behavioral change — the fork is a fall-through match arm before any session handling.
  • -32003 (missing_client_capability) ships as a reserved constructor: no Loomem path requires client capabilities today (no sampling/elicitation/roots).
  • CIMD (slice 3) — separate security-sensitive cycle per the brief. Tasks / MRTR / Apps — product decisions, out of scope.
  • mcp_instructions.md review for stateless clients: NO-OP — the text is transport-agnostic (no initialize/handshake references).
  • Docs: no doc page claims a protocol version today (verified by grep), so no docs sync is triggered by this change.

Verification note

Grounded in the full SEP-2575 text plus the modelcontextprotocol.io transport/caching pages (fetched 2026-08-03). The 2026-07-28 schema.ts is still unpublished; all wire literals live as constants in types.rs so a published-schema correction lands in one place.

Gate

  • cargo fmt --check
  • cargo clippy --workspace -- -D warnings
  • cargo test -p loomem-server — 327 unit + integration green (19 new: negotiation matrix, mirrored-header + _meta validation, base64 sentinel, HTTP status mapping, discover contract)

Deviation from the brief: integration fixtures live as in-module tests because loomem-server has no lib target for HTTP-layer tests from tests/ — the same pattern documented in tests/feedback_mcp_test.rs.

Companion PR: #62 (slice 2, OAuth). Brief: MCP 2026-07-28 adoption (2026-07-29).

🤖 Generated with Claude Code

https://claude.ai/code/session_011gqzVB16MKWKvhDtGQ7Mnn

Greptile Summary

This change adds an MCP 2026-07-28 stateless protocol path, including protocol-version routing, per-request metadata and mirrored-header handling, discovery, and tool-list cache metadata.

Malformed id-less stateless requests are acknowledged with HTTP 202 before mandatory request validation runs. A missing Mcp-Method, invalid _meta, invalid JSON-RPC version, or unsupported method can therefore appear accepted even though the server has not accepted a valid request.

T-Rex validation blocked

The focused Rust MCP test command could not compile because the required llvm-config tool and libclang shared library are unavailable. The executed source-based reproduction completed and confirmed the affected control flow.

Confidence Score: 4/5

Not safe to merge until id-less stateless requests are validated before they are acknowledged.

The new stateless request path contains a reproduced validation-order failure affecting every id-less request: malformed requests are reported as accepted instead of receiving an HTTP error.

Files Needing Attention: loomem-server/src/mcp/stateless.rs

T-Rex T-Rex Logs

What T-Rex did

  • Checked for the pre-stateless-path module existence before the stateless-path commit.
  • Created a Python reproduction source demonstrating the id-less stateless notification path.
  • Observed id-less malformed stateless notification behavior in logs during reproduction.
  • Recorded a focused Rust stateless test compilation failure encountered while testing the path.
  • Validated the Streamable HTTP contract behavior for id-less notifications, showing 202 on accepted paths and appropriate errors otherwise, and confirmed header/meta validation and dispatch flow.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Stateless malformed notifications bypass mandatory validation

    • Bug
      • A POST carrying MCP-Protocol-Version: 2026-07-28 and a parseable JSON-RPC object without id receives HTTP 202 immediately, even when Mcp-Method is omitted/mismatched, required _meta is absent or invalid, jsonrpc is wrong, or the method is unsupported. This silently acknowledges requests that the MCP transport contract says cannot be accepted.
    • Cause
      • The omitted-id notification return at loomem-server/src/mcp/stateless.rs:76-80 precedes both validate_mirrored_headers (lines 81-83), validate_request_meta (lines 84-86), and router/method validation (line 87).
    • Fix
      • Parse and validate JSON-RPC shape/version, mirrored headers, required metadata, and method acceptability before returning 202 for an omitted-id notification. For any failure, return the required HTTP error (normally 400 for validation; 404 for unknown method where applicable) and optionally emit an id-less JSON-RPC error response. Return 202 with no body only after the notification has been accepted.

    T-Rex Ran code and verified through T-Rex

Fix All in Claude Code

Reviews (5): Last reviewed commit: "fix(mcp): treat an explicit null id as a..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Adds the MCP 2026-07-28 revision (SEP-2575) alongside the existing
initialize-era path, routed on the MCP-Protocol-Version header:

- no header, or an initialize-era version (2025-06-18+ clients send
  their negotiated version) -> the legacy session path, byte-identical;
- 2026-07-28 -> a stateless path: no sessions read or minted,
  per-request _meta validation (io.modelcontextprotocol/* keys as
  single-place constants), SEP-2243 mirrored-header validation
  (Mcp-Method always, Mcp-Name for tools/call incl. the Base64
  sentinel format), server/discover carrying supportedVersions +
  capabilities + serverInfo + instructions (ECA-13 advisories port
  1:1), tools/list with SEP-2549 ttlMs/cacheScope, and initialize/ping
  falling to method-not-found;
- any other version -> -32004 with the supported list (HTTP 400).

Transport statuses per spec: -32601 -> 404, -32603 -> 500, other
protocol errors -> 400. Batching is rejected on the new path (removed
from the protocol in 2025-06-18). With no per-instance session state
on the protocol path, any request can land on any fleet instance.

Verified against the SEP-2575 text and the modelcontextprotocol.io
transport/caching pages (2026-08-03); the 2026-07-28 schema.ts is not
yet published, hence wire literals live as constants in types.rs with
a verification note. mcp_instructions.md reviewed for stateless
clients: NO-OP, the text is transport-agnostic (no initialize
references).

Deviation from the brief: integration fixtures live as in-module tests
(negotiation matrix, header and _meta validation, discover contract)
because loomem-server has no lib target for external HTTP-layer tests,
the same pattern documented in tests/feedback_mcp_test.rs.

Signed-off-by: Łukasz Gumowski <lukasz.gumowski@gmail.com>
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
loomem-site Ready Ready Preview Aug 3, 2026 9:46pm

Request Review

Comment thread loomem-server/src/mcp/stateless.rs Outdated
On the stateless path, valid JSON that fails to deserialize into a
JSON-RPC request object was answered with -32700 Parse error, but JSON
parsing had already succeeded at that point - JSON-RPC 2.0 reserves
-32700 for invalid JSON text and -32600 for an invalid Request object.
Extracted parse_single_request() so the branch is unit-testable and the
extractable id, when present, is echoed in the error response instead
of null.

Addresses Greptile P1 on #63. The legacy path keeps its historical
behavior by design (byte-identical rule).

Signed-off-by: Łukasz Gumowski <lukasz.gumowski@gmail.com>
Comment thread loomem-server/src/mcp/stateless.rs Outdated
request_id_of preserved any id value verbatim, so a -32600 (or -32004)
response could echo an object, array or boolean id - types JSON-RPC 2.0
does not allow for identifiers, where null is mandated when the id
cannot be trusted. Strict clients may reject such a response. Only
string and number ids are echoed now; everything else maps to null.

Addresses the Greptile follow-up P1 on #63.

Signed-off-by: Łukasz Gumowski <lukasz.gumowski@gmail.com>
Comment thread loomem-server/src/mcp/stateless.rs
A request carrying an object, array or boolean id deserialized fine
(JsonRpcRequest.id is an unrestricted JSON value) and reached dispatch,
returning 200 with the illegal id echoed. JSON-RPC 2.0 restricts ids to
strings, numbers and null, so such a request is now answered -32600
with a null response id before header or _meta validation runs. An
explicit null id keeps deserializing to None and stays a notification.

Addresses the Greptile round-3 P1 on #63.

Signed-off-by: Łukasz Gumowski <lukasz.gumowski@gmail.com>
Comment thread loomem-server/src/mcp/stateless.rs Outdated
Comment thread loomem-server/src/mcp/stateless.rs
Serde collapses "id": null into the same None as an omitted id, so the
stateless handler answered 202 and never dispatched. JSON-RPC 2.0 makes
only the omitted id a notification; a present null id is a discouraged
but legal request whose response id is null. Presence is now captured
from the raw body before deserialization and restored as
Some(Value::Null), which flows through validation, dispatch and the
response id. The shared JsonRpcRequest type and the legacy path are
untouched.

Addresses the Greptile round-4 P1 pair on #63.

Signed-off-by: Łukasz Gumowski <lukasz.gumowski@gmail.com>
Comment on lines +76 to +80
// Notifications: accept with 202 and no body. Header requirements for
// notification POSTs are undefined in this revision, so none are enforced.
let Some(request_id) = request.id.clone() else {
return StatusCode::ACCEPTED.into_response();
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Stateless notifications bypass required validation

Requests without an id return 202 Accepted before the handler validates Mcp-Method, the required _meta fields, the JSON-RPC version, or whether the method exists. As a result, an invalid 2026-07-28 notification—for example, one missing both Mcp-Method and _meta—is silently acknowledged instead of receiving the required HTTP error. Validate the request shape, mirrored headers, metadata, and method before returning 202; only accepted notifications should receive an empty 202 response.

Artifacts

Source of the id-less stateless notification reproduction

  • The executable reproduction reads the checked-out handler and asserts its notification return precedes every validator, ending with the protocol violation it exercises.

Observed id-less malformed stateless notification behavior

  • The executed reproduction reports HTTP 202 with an empty body for missing `Mcp-Method`/`_meta` and wrong JSON-RPC version, ending with the confirmed bypass.

Stateless handler source showing notification branch before validators

  • Captured numbered source shows the early notification return at lines 76-80 and header/meta validators only at lines 81-86, ending with the precise defective ordering.

Focused Rust stateless test compilation failure

  • The attempted focused `cargo test` exits 101 because the environment lacks `llvm-config` and `libclang`, ending with the reason a compiled runtime request could not be run.

Pre-stateless-path source availability check

  • The pre-feature commit has no stateless handler file, ending with confirmation that no comparable before-path execution exists.

View artifacts

T-Rex Ran code and verified through T-Rex

Fix in Claude Code

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Deferred by design, with a spec citation: the 2026-07-28 Streamable HTTP transport page states the core protocol defines no client-to-server notifications over this transport, and that header requirements for notification POSTs are not defined by this revision - acceptance of a notification is left to the server (if the server accepts it, 202 with no body). Malformed notification bodies already fail closed: anything that is not a valid JSON-RPC object is rejected with -32600 at parse time before the 202 branch. Tightening beyond the spec here would risk rejecting future benign notifications with no compliance gain; happy to revisit when the spec pins these rules down.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

You're right — re-reading the flow, parse_single_request is the gate. A structurally invalid object (bad jsonrpc version, invalid id type, missing method) fails there before the id check is ever reached. Only a well-formed JSON-RPC notification (valid object, valid jsonrpc, omitted id) reaches the 202 return. With the spec explicitly leaving Mcp-Method and _meta undefined for notification POSTs, the behavior is correct as written. Withdrawing the comment.

@vvooki-sys
vvooki-sys merged commit a267254 into main Aug 3, 2026
7 of 8 checks passed
@vvooki-sys
vvooki-sys deleted the arch/mcp2026-stateless branch August 5, 2026 16:53
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