You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: SEP-2243 Mcp-Param-* mirroring — client emission + server validation
Protocol crate (turul-mcp-protocol-2026-07-28/headers.rs): pure SEP-2243
logic shared by both sides — scan_x_mcp_headers() (annotation discovery at
any nesting depth; constraints: non-empty, tchar field-name syntax,
case-insensitive uniqueness, string/integer/boolean only),
encode_param_value()/decode_param_value() (string/integer/boolean
conversion, JS safe-integer range, =?base64?...?= sentinel incl. the
self-matching-sentinel re-encode rule). Unit tests reproduce all five spec
encoding examples. base64 workspace dep added.
Server: the tools/call handler validates every annotated parameter's
mirrored header against the body argument (sentinel-decoded; integers
compared numerically). Value-without-header, header-without-value, or a
decoded mismatch -> -32001 HeaderMismatch at HTTP 400. The transport
surfaces request headers to handlers via the rpc session metadata (2026
path); the inline 2026 JSON path maps -32001 to 400 alongside -32003.
Invalid annotations on the server's own tool log a warning and skip
enforcement (a conforming client never lists such a tool).
Client: tools/list excludes tool definitions with invalid x-mcp-header
values (spec MUST; warning logged) and captures per-tool bindings BEFORE the
2025-vocabulary remap (the typed 2025 JsonSchema cannot carry the
annotation). tools/call mirrors annotated arguments into Mcp-Param-{name}
headers via the new Transport::send_request_with_extra_headers (default
delegates to send_request — non-HTTP transports MAY ignore the annotations
per spec). Without a cached schema the call goes out headerless per the spec
note; the schema-stale auto-retry is left to the application.
Tests: mcp_param_2026.rs — 4 real-HTTP server cases (match, Base64-sentinel
match, omitted-header rejection, mismatch rejection). Revert-and-fail:
with the validation block compiled out, both negative cases fail and both
positive controls pass — recorded. Client closed loop in
e2e_2026_real_server.rs: the validating server rejects missing mirrors, so
the green client calls (plain ASCII + Base64 sentinel) prove emission.
CI: mcp_param_2026 gate in ci.yml + ci-gates.sh.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
44
44
45
45
### Added (2026-06-10)
46
46
47
+
- **SEP-2243 `Mcp-Param-*` custom-header mirroring (client emission + server validation).** Completes the deferred remainder of the request-metadata headers work. Protocol crate: pure SEP-2243 logic in `headers.rs` — `scan_x_mcp_headers()` (annotation discovery at any nesting depth with the full constraint set: non-empty, `tchar` syntax, case-insensitive uniqueness, string/integer/boolean only), `encode_param_value()` / `decode_param_value()` (string/integer/boolean conversion, JS safe-integer range, `=?base64?…?=` sentinel incl. the self-matching-sentinel re-encode rule; unit tests reproduce all five spec encoding examples). Server: the tools/call handler validates every annotated parameter's mirrored header against the body argument (sentinel-decoded; integers compared numerically) — value-without-header, header-without-value, or decoded mismatch → `-32001 HeaderMismatch` at HTTP 400 (the transport surfaces request headers to handlers via the rpc session metadata; the inline 2026 JSON path now maps `-32001` to 400 alongside `-32003`). Client: `tools/list` rejects tool definitions with invalid `x-mcp-header` values (excluded + warning, per spec) and captures per-tool bindings BEFORE the 2025-vocabulary remap (which cannot carry the annotation); `tools/call` mirrors annotated arguments into `Mcp-Param-{name}` headers via a new `Transport::send_request_with_extra_headers` (default delegates to `send_request` — non-HTTP transports MAY ignore the annotations). Tests: `mcp_param_2026.rs` (4 real-HTTP server cases; revert-and-fail recorded — both negative cases fail with validation disabled) and a closed-loop client e2e (the validating server rejects missing mirrors, so the green client call proves emission; covers plain ASCII and Base64-sentinel values). Not implemented (acceptable per spec): the client's optional schema-stale auto-retry (`tools/list` + retry on rejection is left to the application).
47
48
-**Schema pin re-vendored (content sha256 `1bf94a60…`, fixture pin `1304c8fe`).** One substantive upstream change: `ElicitationCompleteNotificationParams` extracted into a named interface extending `NotificationParams` (surface 159 → 160). The Rust binding already modeled the optional `_meta`, so the previously recorded deviation resolved upstream. ADR-027 revision log + COMPLIANCE.md/schema README hash records updated.
48
49
-**Docs/ADR reconciliation to implemented behavior**: ADR-029 §CI surface rewritten to the as-built lanes (the prescribed `cargo test --workspace` matrices never compiled — spec-pinned workspace members trip the ADR's own mutex; per-crate matrix is the operative shape); ADR-025 revision entry recording the shim's branch reality (still consumed by four non-frozen crates; manifest carries an unpublishable 0.4.0 from the version sweep; the framework-wide cut remains 0.4.0 release-prep work); `docs/plans/2026-07-28-schema-coverage-matrix.md` marked STALE/superseded (authoritative coverage = COMPLIANCE.md + the compliance harness); COMPLIANCE.md elicitation-union and extension-crate-name notes refreshed.
49
50
- **MRTR (SEP-2322): `InputRequiredResult` production (server) and consumption (client).** Server: a tool returning the new `McpError::InputRequired { input_requests, request_state }` sentinel (2026 protocol crate; NOT a wire error — the only return channel available to tool impls) is converted by the `tools/call` handler into a successful `InputRequiredResult` (`resultType: "input_required"`), after enforcing that every input request targets a capability the client declared in that request's `_meta` `clientCapabilities` — undeclared → `-32003 MissingRequiredClientCapability` at HTTP 400 (the 2026 JSON-framed response path now dispatches inline so the HTTP status can reflect the JSON-RPC outcome; SSE-framed responses inherently stay 200). On the retry leg, `CallToolRequestParams.inputResponses`/`requestState` are surfaced to tools via `SessionContext::input_responses()` / `mrtr_request_state()` (requestState documented as attacker-controlled). Client: `call_tool` surfaces `resultType: "input_required"` as the new `McpClientError::InputRequired` carrying `inputRequests`/`requestState`; the application gathers inputs and retries via `call_tool_with_input_responses(name, args, responses, request_state)` (fresh JSON-RPC id; 2026 connections only). New `ClientConfig.declared_capabilities` (elicitation/sampling/roots, all off by default) feeds both the 2026 per-request `_meta` and the 2025 `initialize` capabilities — previously hardcoded empty. Tests: `mrtr_2026.rs` (real-HTTP two-leg round trip + `-32003`/400 capability rejection; the suite does not compile against the pre-slice tree — the sentinel variant did not exist) and a full client-driven MRTR e2e in `e2e_2026_real_server.rs`. Limitations (tracked): MRTR production is wired for `tools/call` only (`resources/read`/`prompts/get` handlers have no input hooks yet); the framework does not generate or verify `requestState` integrity (HMAC is the tool author's concern).
0 commit comments