Skip to content

Commit 0d784c8

Browse files
committed
docs(planning): add MCP 2026-07-28 spec-impact analysis, close upstream reconciliation
- New doc assesses SEP-2567/2575/2577 (sessionless MCP, handshake removal, roots deprecation) against mcpmux's architecture; rmcp is pinned at 1.5.0 so none of it is wire-visible yet, but the resolver's ranked-signal design already anticipates the shift. - Upstream client-mapping reconciliation doc was stale ("Planning — not started") despite all 4 phases having shipped Jul 17; updated status and added a Resolution section with the landing commits. Signed-off-by: crimsonsunset <jsangio1@gmail.com>
1 parent 7ac5dc1 commit 0d784c8

2 files changed

Lines changed: 128 additions & 2 deletions

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
# MCP 2026-07-28 Spec — What Changed and What It Means for McpMux
2+
3+
**Last Updated:** Aug 13, 2026
4+
**Status:** Informational — no action required yet, tracked for future rmcp upgrade planning
5+
**Related:** [`deny-by-default-bindable-callers.md`](./deny-by-default-bindable-callers.md) (first to flag SEP-2577), [`rootless-declare-root-gate.md`](./rootless-declare-root-gate.md), [`cursor-workspace-routing-bridge.md`](./cursor-workspace-routing-bridge.md)
6+
7+
## TL;DR
8+
9+
MCP shipped a new spec version, `2026-07-28`, that makes the protocol stateless: it **removes** sessions (`Mcp-Session-Id`, SEP-2567) and the `initialize` handshake (SEP-2575) outright, and **deprecates** (not removes) `roots`, sampling, and logging (SEP-2577). McpMux's gateway is a session-keyed, roots-first router — on paper, two of its central design choices are exactly what this spec revision walked away from.
10+
11+
In practice, none of it is urgent:
12+
13+
- **Roots deprecation is soft.** Annotation-only, no wire change, guaranteed to keep working until at least July 2027. McpMux already treats `roots` as one ranked signal rather than a hard dependency (see [Decisions #2](./deny-by-default-bindable-callers.md#decisions) in the deny-by-default doc), so there's nothing to unwind.
14+
- **Session removal is version-gated, not a cutover.** A client that speaks both versions negotiates the old (session-based) protocol with an unmigrated server and the new one with everyone else. Nothing breaks by standing still.
15+
- **McpMux isn't exposed to any of it yet anyway.** The gateway is pinned to `rmcp 1.5.0`, which tops out at protocol version `2025-11-25` — it doesn't recognize `2026-07-28` at all. Every client connecting today negotiates down to the old, session-based, roots-based protocol regardless of what the client itself supports.
16+
17+
The reason this is still worth writing down: this week's live debugging of the `PendingRoots` / phantom-root bug turned up first-hand evidence of exactly the failure mode the roots deprecation SEP cites as its motivation — Cursor's `roots/list` response mixing in folders from unrelated windows and stale, orphaned worker registrations. The spec authors weren't guessing.
18+
19+
## What actually changed
20+
21+
The `2026-07-28` specification (published final, replacing `2025-11-25`) bundles several Specification Enhancement Proposals (SEPs) under one theme: make the protocol stateless so it can sit behind a plain round-robin load balancer with no sticky routing or shared session store.
22+
23+
| Change | SEP | Status | What to do |
24+
|---|---|---|---|
25+
| Sessions / `Mcp-Session-Id` header | [2567](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2567-sessionless-mcp.md) | **Removed** in the new version | Drop shared session stores; mint explicit handles, pass them as ordinary tool arguments |
26+
| `initialize`/`initialized` handshake | 2575 | **Removed** in the new version | Read protocol version + capabilities from `_meta` on every request; implement `server/discover` |
27+
| Blocking `tasks/result` | 2663 | **Removed**, moved to extension | Poll `tasks/get` instead |
28+
| `tools/list` / `resources/list` / `prompts/list` | 2549 | Compatible, new fields | Lists are no longer per-connection; add `ttlMs` + `cacheScope` to enable caching |
29+
| Roots, Sampling, Logging | [2577](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2577-deprecate-roots-sampling-and-logging.md) | **Deprecated**, not removed | Stays in spec ≥12 months (earliest removal July 28, 2027); avoid new hard dependencies |
30+
| Legacy HTTP+SSE transport | 2596 | Deprecated, not removed | Migrate to Streamable HTTP within the window |
31+
| Feature lifecycle policy (Active → Deprecated → Removed, 12-month floor) | 2596 | New governance | Governs the two rows above |
32+
33+
Sources: [MCP blog — the 2026-07-28 specification](https://blog.modelcontextprotocol.io/posts/2026-07-28/), [release-candidate announcement](https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/), [SEP-2567 full text](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/seps/2567-sessionless-mcp.md), [Stacktree's change-by-change writeup](https://stacktr.ee/blog/mcp-2026-spec-changes).
34+
35+
The distinction that matters most for McpMux: **sessions are a hard removal** (in the new protocol version only — old versions keep working via negotiation), while **roots/sampling/logging are a soft, timed deprecation** (still fully functional, no wire changes, minimum 12-month runway under the new SEP-2596 lifecycle policy).
36+
37+
## Is McpMux actually exposed to this?
38+
39+
Not yet, and not by accident of timing — by version pinning.
40+
41+
```
42+
$ grep -A2 'name = "rmcp"' Cargo.lock
43+
name = "rmcp"
44+
version = "1.5.0"
45+
```
46+
47+
`rmcp 1.5.0`'s protocol version enum stops at `2025-11-25`:
48+
49+
```192:195:/Users/joe/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/rmcp-1.5.0/src/model.rs
50+
"2024-11-05" => return Ok(ProtocolVersion::V_2024_11_05),
51+
"2025-03-26" => return Ok(ProtocolVersion::V_2025_03_26),
52+
"2025-06-18" => return Ok(ProtocolVersion::V_2025_06_18),
53+
"2025-11-25" => return Ok(ProtocolVersion::V_2025_11_25),
54+
```
55+
56+
It has no concept of `2026-07-28`. Because MCP negotiates protocol version per-connection, every client that talks to McpMux today — Cursor, Claude Desktop, VS Code, whatever — negotiates down to `2025-11-25` or earlier, the same session-based, roots-based protocol McpMux was built against. The stateless core doesn't apply to a single one of McpMux's live connections right now.
57+
58+
The upstream Rust SDK does support the new spec — `rmcp 3.0.0` added it (sessionless Streamable HTTP by default, `server/discover`, the `_meta`-carried handshake, `resultType` discriminators, `ttlMs`/`cacheScope` caching hints) — but McpMux hasn't picked that up. That's a deliberate, low-risk gap to leave open for now; closing it is a real migration (see [Recommendations](#recommendations)), not a patch.
59+
60+
## Why it's still worth understanding: two SEPs read like they were written about this app
61+
62+
### Sessions (SEP-2567) — "gateways using session ID for sticky routing"
63+
64+
SEP-2567's own backward-compatibility section runs a survey of 1,000 open-source MCP servers and breaks out exactly the categories that get hit:
65+
66+
| Category | Share | Migration |
67+
|---|---:|---|
68+
| No application-level reference to session ID | 90.0% | None |
69+
| `Map<sessionId, Transport>` routing (SDK boilerplate) | 3.5% | Removed by a sessionless SDK transport |
70+
| Transport setup only, session id never read | 2.8% | Delete one constructor option |
71+
| **Session-keyed application state** | 2.5% | Migrate to explicit handles or auth principal |
72+
| **Proxy / gateway sticky routing** | 0.7% | Needs designed replacement |
73+
| Auth artifacts bound to session ID | 0.5% | Replace with a server-generated nonce |
74+
75+
McpMux's `SessionRootsRegistry` (`crates/mcpmux-gateway/src/services/session_roots.rs`) is exactly the second bolded row: every session's reported roots, pinned header, roots-capability flag, and resolved-feature-set snapshot are held in a `DashMap<session_id, _>`. That's session-keyed application state, textbook.
76+
77+
The good news buried in that same section: **McpMux is the cheap case, not the hard one.** SEP-2567 calls out "gateways that spawn one upstream per session" and "sticky routing across stateful replicas" as the categories needing a *designed* replacement. McpMux doesn't do either — it's a single local process per machine (`127.0.0.1` only, no horizontal scaling), so the actual motivating problem this SEP solves (round-robin load balancing across stateless replicas) doesn't exist here. What McpMux uses the session id for is closer to "which open folder is this specific connection about" — and the SEP's own recommended replacement, **explicit server-minted handles passed as ordinary tool arguments**, is a pattern McpMux already ships: `mcpmux_set_workspace_root` is precisely that. A caller that can't or won't rely on session-scoped root probing declares its workspace explicitly, once, and the resolver treats that declaration as authoritative — no different in spirit from a `create_basket()` → `basket_id` handle.
78+
79+
### Roots (SEP-2577) — deprecated for the reason this week's bugs demonstrated
80+
81+
The roots deprecation's stated rationale (per the [release-candidate post](https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/) and [aaif.io's migration writeup](https://aaif.io/blog/mcp-2026-07-28-whats-changing-and-how-to-migrate)) is that roots "tightly coupled clients and servers around filesystem assumptions that don't generalise to remote or cloud environments," with low real-world adoption relative to the implementation burden every compliant client and server has to carry.
82+
83+
This week's live investigation of the `PendingRoots` bug produced first-hand evidence of exactly that unreliability, independent of any protocol committee's reasoning:
84+
85+
- **Cross-window contamination.** A single session's `roots/list` response returned four folders from four unrelated, separately-opened Cursor windows (`jsg-pr-quality`, `sync2hire-platform`, `generAIt`, `mcp-mux`) — not a multi-root workspace, just several ordinary windows open at once, all mixed into one connection's "roots."
86+
- **Phantom/stale entries.** A separate session reported the same real project twice — once at its live path, once at a path that hadn't existed on disk since a `Repos/` reorg weeks earlier — traced to an orphaned Cursor background-agent-worker registration that kept getting reconciled into `roots/list` output long after the folder was gone.
87+
88+
Neither of those is a McpMux bug in the sense of "the resolver got the logic wrong." They're the client-side signal itself being unreliable in exactly the way the SEP describes — "clients vary wildly in roots support" (as the deny-by-default doc already noted back in June) turned out to understate it; even a client that *does* report roots can report garbage alongside the real answer.
89+
90+
## McpMux's existing posture: already built for this, mostly by luck of prior design
91+
92+
The June [`deny-by-default-bindable-callers.md`](./deny-by-default-bindable-callers.md) doc made the load-bearing decision before this spec even shipped as final:
93+
94+
> **The `WorkspaceBinding` is canonical; reported root, client identity, and machine are ranked match signals, none mandatory.** Absence of all → `Unbound`. Decouples the feature from `roots` without ripping out roots support. The deprecation just removes one signal from the priority list someday — not a rewrite.
95+
96+
That framing held up. `roots` in `FeatureSetResolverService` (`crates/mcpmux-gateway/src/services/feature_set_resolver.rs`) is Tier 1 of a ranked list, not the only path to a resolution — client identity (`ClientGrant`), the `X-Mcpmux-Workspace` pin, and the `mcpmux_set_workspace_root` declare-root gate all exist specifically so a caller that can't or won't report reliable roots still resolves correctly. Today's phantom-root fix (filesystem-existence check before giving up on an ambiguous multi-root session) is the same philosophy applied one layer deeper: even *within* the roots signal, don't trust it more than the evidence supports.
97+
98+
## What's not urgent, and why
99+
100+
- **Roots removal isn't eligible before July 28, 2027**, and removal requires a separate SEP even after that floor — "deprecated" isn't a countdown, someone has to actually propose removing it.
101+
- **Session removal only applies to peers that negotiate `2026-07-28`.** McpMux's rmcp pin means no live connection negotiates it today. Cursor, Claude Desktop, and friends all speak multiple versions and fall back gracefully to whatever McpMux offers.
102+
- **Nothing here is a security or correctness bug in the current app.** It's a forward-looking compatibility question, not a fire.
103+
104+
## Recommendations
105+
106+
None of these are asks for this session — they're what to pick up whenever rmcp 3.x adoption becomes a live topic:
107+
108+
1. **Track rmcp 3.x, don't rush it.** The jump isn't wire-breaking for legacy peers (`legacy_session_mode`, formerly `stateful_mode`, keeps serving pre-2026-07-28 clients exactly as today), but it is an API-breaking Rust upgrade (`ServerResult` widens for `resultType`, tool handlers change shape). Budget it as a real migration, not a patch bump.
109+
2. **When it happens, implement `server/discover`.** The new spec makes it a MUST for servers speaking `2026-07-28`; it's the closest thing to an `initialize` replacement.
110+
3. **Adopt `ttlMs`/`cacheScope` on `tools/list` once available.** McpMux's gateway is itself a `tools/list` aggregator across many backend servers — cache hints are a direct fit for reducing repeated list calls from clients that reconnect often (exactly the churn this week's cold-start investigation was chasing).
111+
4. **Update the stale spec-version references.** `AGENTS.md`/`CLAUDE.md` still say "Default to the latest stable version (`2025-11-25`)" — that's no longer current as of this spec's publication. Also: the vendored spec this repo's docs point to (`../modelcontextprotocol/docs/specification/`) isn't checked out on this machine at all — either clone it or repoint the reference before anyone tries to "read the relevant section before implementing."
112+
5. **No action needed on `roots`.** The existing ranked-signal design already absorbs the deprecation. Keep leaning on `WorkspaceBinding` + explicit declare-root as the source of truth, exactly as already decided.

docs/planning/upstream-client-mapping-reconciliation.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
11
# Upstream Client Mapping Reconciliation (`upstream/main` #201#206`dev-rebased`)
22

3-
**Last Updated:** Jul 16, 2026
4-
**Status:** Planning — not started
3+
**Last Updated:** Aug 14, 2026
4+
**Status:** ✅ Completed — all 4 phases landed Jul 17, 2026
55
**Branch:** N/A — each phase is a separate feature branch off `dev-rebased`
66
**Base branch:** `dev-rebased` (fork lineage, currently at migration `035_inbound_client_machine.sql`)
77
**Depends on:** `dev-to-main-port.md` migration-renumbering precedent (020–031 map); this doc extends that pattern for a second wave
88
**Unblocks:** Headless/remote MCP client support (`mcpk_` API keys) landing on the fork without regressing deny-by-default or machine-scoped routing
99

1010
---
1111

12+
## Resolution (Aug 14, 2026)
13+
14+
Shipped exactly as planned below, all in one day (Jul 17, 2026):
15+
16+
- `eb5ae35` — merge `upstream/main` (`87df4a2`, #203) into `dev-rebased`, conflicts resolved
17+
- `830c2ec` — Phase 1: API-key inbound auth (`036_inbound_client_api_keys.sql`)
18+
- `9af05c0` — Phase 2: id-type bindings + resolver Tier 2 (`037_workspace_binding_type.sql`)
19+
- `3401053` — Phase 3: Space lock as narrowing filter (`038_inbound_client_locked_space.sql`)
20+
- `3eb678c` — Phase 4: gateway fixes + consent polish
21+
22+
`upstream/main` hasn't moved since (`87df4a2` is still its tip as of this check) — `dev-rebased` is fully caught up: 0 commits behind, 147 ahead. The plan below is kept as the historical record of *why* each schema/resolver decision was made, not as an open TODO.
23+
24+
---
25+
1226
## Problem
1327

1428
Upstream shipped a 3-PR stacked series (#201 P1, #202 P2, #203 P3) plus two gateway fixes (#205, #206) on Jul 15, 2026 — all after this fork diverged. The series adds:

0 commit comments

Comments
 (0)