49b9bb6724
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Has been cancelled
59 lines
2.6 KiB
Markdown
59 lines
2.6 KiB
Markdown
# dual-era
|
|
|
|
One server factory, both protocol eras. A `mode="legacy"` client runs the
|
|
`initialize` handshake; a `mode="auto"` client probes `server/discover` and
|
|
adopts the 2026 stateless era — the same `greet` tool answers both and reports
|
|
which era served it via `ctx.request_context.protocol_version`. **Start here**
|
|
when migrating a v1 server: the entry owns the era decision, the server body
|
|
stays era-agnostic.
|
|
|
|
## Run it
|
|
|
|
```bash
|
|
# over HTTP — the same /mcp endpoint serves both eras; the client self-hosts
|
|
# the server on a free port, runs, then tears it down
|
|
uv run python -m stories.dual_era.client --http
|
|
# same, against the lowlevel-API server variant
|
|
uv run python -m stories.dual_era.client --http --server server_lowlevel
|
|
```
|
|
|
|
The bare stdio invocation (`uv run python -m stories.dual_era.client`) is
|
|
legacy-only until the SDK's stdio entry can negotiate the era, so the modern
|
|
leg fails there today — run over `--http`.
|
|
|
|
## What to look at
|
|
|
|
- `client.py` — both connections are visible, against the same `targets()`
|
|
factory: `Client(targets(), mode=mode)` (default `"auto"`, the
|
|
discover-then-fallback ladder) and `Client(targets(), mode="legacy")` (forces
|
|
the `initialize` handshake). The era decision is one explicit `mode=` argument
|
|
at construction; no date strings appear in the body.
|
|
- `client.py` — `client.protocol_version` / `client.server_info` /
|
|
`client.server_capabilities` are era-neutral: populated by `initialize` *or*
|
|
`server/discover`, whichever ran.
|
|
- `server.py` — `ctx.request_context.protocol_version` is the era branch key
|
|
(lowlevel: `ctx.protocol_version` directly). Compare against
|
|
`MODERN_PROTOCOL_VERSIONS`, never a date literal.
|
|
- **Where to read the negotiated version.** One value, three read paths:
|
|
`client.protocol_version` on the client after connect; `ctx.protocol_version`
|
|
inside a lowlevel handler; `ctx.request_context.protocol_version` inside an
|
|
`MCPServer` handler.
|
|
|
|
## Caveats
|
|
|
|
- `ctx.request_context.protocol_version` is the current way to read the
|
|
negotiated version; a later release will shorten it to `ctx.transport.*`.
|
|
- Over HTTP the built-in era branch is currently header-only — a 2026 client
|
|
that omits the `MCP-Protocol-Version` header is mis-routed to the legacy
|
|
path. The body-primary classifier lands in a later release.
|
|
|
|
## Spec
|
|
|
|
- [Versioning — backward compatibility](https://modelcontextprotocol.io/specification/draft/basic/versioning)
|
|
- [`server/discover`](https://modelcontextprotocol.io/specification/draft/server/discover)
|
|
|
|
## See also
|
|
|
|
`legacy_routing/` (route eras yourself), `reconnect/` (persist `DiscoverResult`
|
|
for zero-RTT reconnect).
|