Skip to content

MCP Spec 2026-07-28 - #56

Merged
Eilodon merged 3 commits into
mainfrom
claude/dependabot-pr-review-60cfas
Aug 4, 2026
Merged

MCP Spec 2026-07-28#56
Eilodon merged 3 commits into
mainfrom
claude/dependabot-pr-review-60cfas

Conversation

@Eilodon

@Eilodon Eilodon commented Aug 4, 2026

Copy link
Copy Markdown
Owner

No description provided.

claude added 3 commits August 4, 2026 05:06
Deep-dive on adopting the MCP 2026-07-28 spec in CALM. Assesses each SEP
against CALM's real deployment (single-node stdio/daemon + opt-in HTTP,
single SQLite, SINGLE_WRITER) rather than adopting wholesale.

Key findings:
- rmcp is pinned "2" (2.2.0, LATEST=2025-11-25); full 2026-07-28 lives in
  the rmcp 3.x line (3.0.0/3.1.0) which the pin excludes.
- Highest-value + deadline item: migrate the write-path human-veto gate
  from deprecated elicitation (SEP-2577) to MRTR/input_required (SEP-2322).
  Fixes a real cross-transport safety hole (gate silently disables on
  stateless connections) and survives the 12-month deprecation window.
- Most of the spec (stateless core, header routing, OAuth hardening) is
  low-value for a single-node tool; deferred to a hosted/multi-user future.
- Structured output + explicit model-visible handles are already in place.

Proposes a 4-phase roadmap (P0 de-risk spike, P1 rmcp 2->3 behavior-
preserving, P2 MRTR gate migration, P3 cacheable lists, P4 deferred).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiYMb2ZWUn8TzViFV4rJC6
…e 1)

Phase 1 of docs/plans/2026-08-04-mcp-2026-07-28-upgrade-plan.md. Live-
verified in a scratch spike first (rmcp 3.1.0 + axum 0.8 + schemars 1 +
transport-streamable-http-server all resolve to a single dependency tree
each, no OTEL-style split-core), then applied here.

rmcp 3.x wraps ServerHandler::call_tool/get_prompt results in new
CallToolResponse/GetPromptResponse enums (SEP-2322 MRTR support) and adds
required cache-hint fields to ListToolsResult/ListPromptsResult (SEP-2549).
Individual #[tool] methods are unaffected -- the IntoCallToolResult
blanket impl absorbs the wrapping in the macro-generated router; only the
hand-written ServerHandler dispatch in tools.rs needed updating.

Explicitly caps CalmServer::supported_protocol_versions() below
2026-07-28: rmcp defaults that list to every version it knows, but a peer
negotiating 2026-07-28 is served statelessly over Streamable-HTTP
regardless of legacy_session_mode (SEP-2567) -- which would silently drop
the per-connection state this server leans on today (toolset narrowing,
the hub-edit human-veto declined-answer cache) before Phase 2 gives that
gate an MRTR-based path that survives statelessness. Verified end-to-end
with a new daemon_integration test: a client offering 2026-07-28 still
gets a successful initialize, gracefully downgraded to 2025-11-25.

Verified: full workspace build clean (default + --all-features), clippy
clean, calm-core (957) + calm-server (308/310, 2 pre-existing unrelated
failures confirmed via git-stash baseline) + calm-cli (all suites,
including http_guard under --features http) tests green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiYMb2ZWUn8TzViFV4rJC6
…07-28 Phase 2)

Phase 2 of docs/plans/2026-08-04-mcp-2026-07-28-upgrade-plan.md. The
write-safety human-veto gate (edit_lines/edit_symbol) previously had
exactly one mechanism -- a live server-initiated elicitation/create
round-trip over an open back-channel (peer.elicit_with_timeout). That
mechanism cannot work at all over a genuinely stateless connection, which
is what any peer negotiating protocol 2026-07-28 gets served over
(SEP-2567) regardless of session-manager configuration -- so the gate
would have silently had no working mechanism for such a peer.

Adds SEP-2322 Multi Round-Trip Requests as a second mechanism, selected
per-call by elicit_setup() based on the peer's negotiated protocol
version: pre-2026-07-28 peers get the exact same legacy round-trip as
before (byte-identical code path, unchanged); 2026-07-28+ peers get an
InputRequiredResult instead of a held-open await, retry the same
tools/call with inputResponses once a human answers, and hub_mrtr_decide
verifies the answer from a self-contained, HMAC-sealed requestState
(rmcp::model::RequestStateCodec, keyed by the same load_or_create_mac_key
project_memory already uses) bound to (tool, path, fingerprint) -- so a
stale or forged approval fails closed regardless of whether the ask and
the retry land on the same server instance.

Both mechanisms converge on the same fail-closed decision: only an
explicit approve:true ever lets the write proceed. decide_mrtr_answer is
map_elicit_outcome's sibling for the MRTR answer shape, mirroring its
table 1:1.

Mechanics, verified against the real rmcp 3.1.0 source (not release
notes):
- ToolCallContext::new discards input_responses/request_state before any
  #[tool] method sees them, so the dispatcher (CalmServer::call_tool) is
  the only place that can forward a retry's answer -- via
  RequestContext::extensions, rmcp's own typed pass-through.
- The #[tool] macro can only auto-derive output_schema from a literal
  Json<T> return type; edit_lines_tool/edit_symbol_tool now return
  HubEditToolResult<T> (Done(Json<T>) | NeedsApproval(InputRequiredResult))
  instead, so both carry an explicit output_schema = schema_for_output::
  <T>() attribute reproducing exactly what the macro used to derive --
  confirmed unchanged by tool_schemas_match_committed_snapshots.
- request-state feature added to the rmcp dependency (pulls hmac
  0.13/sha2 0.11 as new transitive deps, distinct majors from this
  workspace's own 0.12/0.10 pins -- verified safe, not an OTEL-style
  two-cores problem: RequestStateCodec is self-contained, this code never
  constructs an Hmac/Sha2 value itself).

Lifts the Phase 1 protocol-version cap: supported_protocol_versions() now
returns rmcp's full KNOWN_VERSIONS again. The cap existed for exactly the
gap this closes (the gate's only mechanism not surviving statelessness);
the decision no longer depends on any per-connection state, so it is
correct regardless of instance affinity. set_toolset narrowing and the
declined-answer cache remain per-connection conveniences (not safety
guarantees) -- general stateless-HTTP readiness for those stays Phase 4.

Verified: full workspace build clean (default + --all-features), clippy
clean. 7 new unit tests (approve/decline/tamper/forged-state/already-
declined/full-round-trip) plus all 12 existing elicitation tests green,
proving the legacy path is untouched. New daemon_integration end-to-end
test proves 2026-07-28 now negotiates for real over the actual daemon;
existing 2024-11-05 negotiation (used by every other test in that file)
still holds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CiYMb2ZWUn8TzViFV4rJC6
@Eilodon
Eilodon merged commit 86e1aad into main Aug 4, 2026
8 of 10 checks passed
@Eilodon
Eilodon deleted the claude/dependabot-pr-review-60cfas branch August 4, 2026 06:06
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.

2 participants