diff --git a/rfcs/THV-0081-stateless-transport-proxies.md b/rfcs/THV-0081-stateless-transport-proxies.md
new file mode 100644
index 0000000..4661f4e
--- /dev/null
+++ b/rfcs/THV-0081-stateless-transport-proxies.md
@@ -0,0 +1,500 @@
+# RFC-0081: Stateless Transport Proxy Support (MCP 2026-07-28)
+
+- **Status**: Draft
+- **Author(s)**: Jakub Hrozek (@jhrozek)
+- **Created**: 2026-07-15
+- **Last Updated**: 2026-07-15
+- **Target Repository**: toolhive
+- **Related Issues**: [toolhive#5755](https://github.com/stacklok/toolhive/issues/5755)
+
+## Summary
+
+The upcoming MCP 2026-07-28 revision is stateless: it removes the `initialize`
+handshake, protocol sessions, and the GET SSE stream, replacing them with
+per-request `_meta` metadata and a `server/discover` RPC. This RFC proposes making
+ToolHive's transparent and streamable transport proxies serve both the current
+(2025-11-25) and the stateless revision on the same endpoint, discriminating per
+request rather than at a cutover.
+
+## Problem Statement
+
+The 2026-07-28 revision removes exactly the mechanisms the transport proxies are
+built around:
+
+- the `initialize` handshake (replaced by per-request `_meta` carrying
+ `protocolVersion`, `clientInfo`, `clientCapabilities`, plus a `server/discover` RPC),
+- protocol sessions and the `Mcp-Session-Id` header (traffic becomes stateless), and
+- the standalone GET SSE stream.
+
+ToolHive's proxies currently treat the `initialize` request as a trigger for session
+creation, backend-pod pinning (Kubernetes multi-replica affinity), and health-check
+enablement. Under the new revision that trigger never arrives.
+
+Crucially, the ecosystem will straddle both revisions for a long time. SDKs (go-sdk
+v1.7, TS SDK v2) serve both on one endpoint, and clients, servers, and backends will
+upgrade independently. ToolHive therefore cannot do a flag-day cutover; it must
+discriminate the revision **per request** so a 2025-11-25 peer keeps working unchanged
+while a 2026-07-28 peer is served natively. Everyone running ToolHive as a proxy in
+front of MCP servers is affected once either side of the connection upgrades.
+
+## Goals
+
+- Serve 2025-11-25 (Legacy) and 2026-07-28 (Modern) traffic on the same proxy endpoint,
+ chosen per request.
+- Zero behavior change for existing 2025-11-25 traffic.
+- Add the minimum new machinery: a per-request revision classifier and revision-aware
+ handling in the two proxies.
+- Preserve the existing per-request response-correlation confidentiality invariant.
+- Remove, for Modern traffic, the Kubernetes cross-replica affinity burden that only
+ exists to support session-ful backends.
+
+## Non-Goals
+
+- vMCP stateless support and the cross-generation bridge (a client on either revision
+ reaching a backend on either revision) — tracked separately ([toolhive#5756]). This
+ includes vMCP's per-session backend MCP client connections (`pkg/vmcp/session/`), which
+ would churn per request under stateless unless pooled by a stable key.
+- The go-sdk v1.7 adoption / `mcpcompat` stateless plumbing ([toolhive#5754]). This RFC
+ argues the transport-proxy work is independent of it (see Compatibility / Alternatives).
+- OAuth RFC 9207 `iss` validation ([toolhive#5760]) — genuine but unrelated work in
+ ToolHive's own OAuth client.
+- MRTR, Tasks, the `x-mcp-header` tool-parameter mirroring extension, and caching metadata.
+- Off-diagonal era translation in the transport proxies (see Proposed Solution) — deferred.
+
+## Proposed Solution
+
+### High-Level Design
+
+Each request is classified per-request as **Modern** (2026-07-28, stateless) or
+**Legacy** (≤2025-11-25, `initialize`-based). Legacy traffic follows today's code
+paths unchanged; Modern traffic takes a stateless path. Classification is a pure
+function called at the points where the proxies already decode the JSON-RPC body — the
+proxies parse bodies themselves (`golang.org/x/exp/jsonrpc2`), so this lives in
+ToolHive-owned code rather than in an SDK.
+
+```mermaid
+flowchart TD
+ Req[Incoming request] --> Meta{_meta has
protocolVersion?}
+ Meta -->|yes| Modern[Modern: stateless path
no session, per-request token]
+ Meta -->|no| Init{method ==
initialize?}
+ Init -->|yes| LegacyNew[Legacy: create session]
+ Init -->|no| LegacyExisting[Legacy: existing-session path]
+```
+
+The proxy fronts one backend and serves whatever client connects; the two sides have
+independent revisions. The draft spec defines three client kinds — Legacy, Modern
+(Modern-only, no fallback), and Dual-era (probe-then-fallback) — giving:
+
+| Client | Backend | Proxy behavior |
+|-----------------|---------|----------------|
+| Legacy | Legacy | Today's path, unchanged. |
+| Modern/Dual-era | Modern | Stateless pass-through. |
+| Legacy | Modern | Unbridged — spec marks "Fails" (no client fall-forward). Deferred to vMCP. |
+| Dual-era | Legacy | Client probes, falls back to `initialize`; proxy forwards and classifies Legacy. No new proxy work. |
+| Modern-only | Legacy | Spec marks "Fails" — client has no fallback. Client-side gap, outside proxy scope. |
+
+The era-matched cells are the scope of this RFC. The **Dual-era-client / Legacy-server**
+cell needs no proxy work either: the MCP-normative probe-then-fallback runs client-side, so a
+dual-era client downgrades to an `initialize` handshake that the transparent proxy forwards to
+the Legacy backend and classifies as Legacy. In practice this is the common transition case —
+the go-sdk and TS SDK both act as dual-era clients. A *Modern-only* client against a Legacy
+server has no fallback and the spec marks it "Fails"; that is a client-side gap, outside proxy
+scope. That leaves the **Legacy-client / Modern-server** cell genuinely unbridged on the proxy
+side — the spec marks it "Fails" ("Legacy clients have no fall-forward mechanism"), so only a
+gateway could bridge it, and that work belongs to vMCP. The agentgateway project, as an
+aggregator that cannot rely on transparent pass-through, instead actively synthesizes the
+handshake when fronting a Legacy server for a Modern/dual-era client
+(`stateless_send_and_initialize`); it likewise leaves Legacy-client / Modern-server unbridged.
+
+### Detailed Design
+
+#### Component Changes
+
+**Classifier (`pkg/mcp`).** A pure function, called at the existing decode points
+(the transparent proxy's `detectInitialize` inside `RoundTrip`, the streamable proxy's
+`resolveSessionForRequest`). Neither proxy reads the shared `ParsedMCPRequest` on its
+routing path, so classification is not threaded through a context object. A connection
+does not change revision mid-stream, so the transparent proxy classifies once and
+stores the result in session metadata.
+
+**Streamable proxy (HTTP→stdio).** For Modern requests, unconditionally mint a fresh
+per-request routing token and ignore any client-supplied `Mcp-Session-Id`. The proxy
+already has a sessionless branch, but it is guarded on the absence of a session header;
+Modern is sessionless by definition, so the header must be ignored rather than trusted
+(see Security Considerations). No `Mcp-Session-Id` is minted and the session manager is
+not consulted.
+
+**Transparent proxy (HTTP→HTTP).** Modern requests never mint a session, so the
+sticky-session machinery (session guard, pod-pinning, init-body storage, re-init replay,
+backend-SID rewrite) simply does not engage. Because there is no shared pipe, Go's
+`http.Transport` correlates responses to requests natively and the Modern path collapses
+to a plain reverse proxy. The internal health-monitor gate today flips ready on the `initialize` 200 or a
+`Mcp-Session-Id` response header — neither of which occurs on the Modern path, so the
+gate would never open. The Modern path must therefore acquire an alternate readiness
+trigger: gate off the first successful backend contact (and never default the gate ready,
+which would start the monitor before the backend is warm and risk self-stopping the proxy).
+This gate is the internal monitor's start condition and its shutdown-on-failure trigger — it is
+distinct from the `/health` endpoint served to Kubernetes probes, which pings the backend
+independently and is unaffected.
+Note the existing `StatelessMCPPinger` POSTs a JSON-RPC `ping`, which is **removed in 2026-07-28** —
+a Modern-only backend rejects it, so the Modern readiness signal must use `server/discover` or plain
+successful backend contact, not the current pinger.
+
+**Modern message-shape enforcement.** For Modern-classified traffic (Legacy unchanged), reject
+JSON-RPC batches (accepted today via `resolveSessionForBatch`) and client-sent responses (accepted
+today via `handleNotificationOrClientResponse`); `GET`/`DELETE`→405; ignore `Mcp-Session-Id` and
+`Last-Event-ID` (not errors). Enforcement is driven off the validated `Revision` that
+`ClassifyRevision` returns for the request — **not** a re-read of the client-controlled
+`MCP-Protocol-Version` header — so a client cannot flip enforcement by spoofing the header.
+Because `ClassifyRevision` already rejects a header/`_meta` mismatch with `-32020`, the value
+enforced against is trusted rather than raw.
+
+**Parser vocabulary and authz registration.** Register `server/discover` (currently unlabeled,
+yielding an empty resource ID) and the Modern request headers `Mcp-Method` (required on all
+requests) / `Mcp-Name` (required for `tools/call`, `resources/read`, `prompts/get`); source
+`clientInfo`/protocolVersion from `_meta` (consumed by telemetry — see Middleware and Observability).
+Parser labeling is **not sufficient for authorization**: `server/discover` and `subscriptions/listen`
+are absent from `pkg/authz/middleware.go`'s `MCPMethodToFeatureOperation` and unknown methods are
+default-denied there, so both must also be added to the authz map (`server/discover` allowed;
+`subscriptions/listen` recognized so it is not 403'd, even though its delivery is deferred).
+
+#### API Changes
+
+Internal API only; no user-facing or CRD API changes.
+
+```go
+// pkg/mcp
+type Revision int // Legacy, Modern
+
+// ClassifyRevision decides the MCP era of a single request from its method,
+// decoded _meta, and (on Streamable HTTP) the MCP-Protocol-Version header.
+func ClassifyRevision(method string, meta map[string]any, protoHeader string) (Revision, error)
+```
+
+A committed but unwired starting point exists — `pkg/mcp/revision.go`'s `ClassifyRevision`
+implements the basic decision and the `-32020` header check (unit-tested, called nowhere). This
+RFC wires it in and **deepens it** into validation, not key-sniffing:
+
+- **Modern** iff `_meta` carries `io.modelcontextprotocol/protocolVersion`; **Legacy** otherwise,
+ with `method == "initialize"` marking session start.
+- **Version match, not presence.** An unknown/future version → `-32022 UnsupportedProtocolVersion`
+ (not implemented in `revision.go` today).
+- **Required metadata.** Modern requests must carry `clientInfo` and `clientCapabilities`; a
+ missing required capability → `-32021 MissingRequiredClientCapability`.
+- **No silent downgrade.** A request whose `MCP-Protocol-Version` header claims Modern but whose
+ `_meta` is absent/malformed is an **error**, not a Legacy fallback. "Malformed → Legacy" applies
+ only when there is *no* Modern signal at all; a request that claims Modern then fails validation
+ must not be downgraded (or a client could dodge Modern enforcement with broken metadata).
+- **Header/body: neither wins.** On Streamable HTTP the version is mirrored into the
+ `MCP-Protocol-Version` header; a mismatch is rejected with `400` + `-32020 HeaderMismatch`. Do not
+ treat the body as authoritative and proceed.
+
+#### Configuration Changes
+
+None. The classifier is auto-detected from request content and needs no CRD, operator,
+or CLI configuration. The existing `--stateless` run flag is orthogonal (see
+Compatibility).
+
+#### Data Model Changes
+
+None. The session manager (`pkg/transport/session`) is trigger-agnostic and unchanged;
+only the proxy call sites that create sessions move behind the Legacy branch.
+
+#### Middleware and Observability
+
+The middleware chain is session-agnostic and needs no structural change. Authentication,
+token exchange, tool filtering, authorization, audit, and rate limiting all operate on the
+raw request plus the authenticated identity reconstructed from the token per request; none
+requires a session object or keys state on the MCP protocol session id (`Mcp-Session-Id`):
+
+- **Authorization (Cedar)** reconstructs its principal from the token per request. The
+ tool-annotation cache is keyed by tool name (not session) and full-replaced on each
+ `tools/list`, so the per-request routing token never touches it.
+- **Rate limiting** is keyed on principal and tool, so it does not collapse into per-request
+ buckets when sessions disappear.
+- **Audit** derives its subject client name/version from token claims, not the handshake (the
+ `mcp_initialize` event's target name still reflects the handshake `clientInfo`, but no
+ subject/identity field depends on it).
+
+Note the codebase has two distinct "session" concepts: token exchange keys on the JWT `tsid`
+claim — the embedded auth server's upstream-token session — which is independent of the MCP
+protocol session and unaffected by this change.
+
+The one observability touchpoint is telemetry span attributes, and the new revision supplies
+them per request rather than losing them:
+
+- `mcp.protocol.version` is read from the `MCP-Protocol-Version` header, which is mandatory on
+ every Modern POST — present, and more reliably than under Legacy where the header was optional.
+- `mcp.client.name` is today set only on the `initialize` span. Under Modern, telemetry instead
+ reads `clientInfo` from per-request `_meta` (surfaced by the parser) and sets the attribute on
+ every span — richer per-request attribution than Legacy, where the name appeared only on the
+ `initialize` span.
+- `mcp.session.id` is intentionally absent under Modern; W3C trace context carried in `_meta`
+ (already consumed per request by the telemetry middleware) is the cross-request correlation
+ mechanism.
+
+The only code change is telemetry-side: consume `clientInfo` from the parser's `_meta` fields and
+set `mcp.client.name` on all spans instead of only on `initialize`.
+
+#### Request-scoped streaming (designed, deferred)
+
+In 2026-07-28 the standalone GET SSE stream is gone but streaming is not: one POST request may
+return an SSE stream of N server→client notifications (`notifications/progress`,
+`notifications/message`) then a single terminal response. The in-scope changes forward only the
+terminal response; this deferred work forwards the notifications too. This is **stdio-only** — the transparent proxy
+(HTTP/remote, k8s) already streams N-message POST responses natively (`FlushInterval:-1`). The stdio
+streamable proxy is one-request-one-response and drops notifications at `dispatcher.go:23`; note
+this is a **pre-existing gap**, dropped on 2025-11-25 too, not Modern-specific.
+
+Shape (extends, not replaces, the existing rewrite/waiter seam): replace the buffer-1 waiter with a
+per-request stream registered under two keys — the composite id (terminal) and a proxy-rewritten
+`progressToken` (client-chosen, collides across sessions, so rewritten/restored like the request id);
+turn the one-shot handler into a loop that forwards each notification as an SSE event and closes on
+the terminal response; stop dropping notifications in dispatch; hand off to a per-stream bounded
+buffer so a slow client cannot head-of-line-block others (drop oldest *progress* under pressure,
+never the terminal); propagate a downstream disconnect as upstream cancellation.
+
+Two constraints make this a distinct, deferred piece:
+- **Test inversion (required).** `TestHTTPRequestIgnoresNotifications` and the batch spec tests
+ currently *assert* notifications are dropped; the fix must invert them — the notification-delivery
+ test is the acceptance criterion.
+- **Logging attribution is a protocol-level limit.** `notifications/progress` carries a
+ `progressToken` and routes cleanly; `notifications/message` (logging) carries no correlation
+ token, so over multiplexed stdio it cannot be attributed per-request — broadcast-or-drop only.
+ Progress works; logging is best-effort. This bounds what "streaming works" can mean for stdio.
+
+#### Deferred: subscriptions
+
+`subscriptions/listen` (replaces `resources/subscribe` + the GET SSE stream) is a long-lived
+POST-response stream: an `acknowledged` first frame carrying a `subscriptionId`, then unbounded
+change notifications until disconnect. Additive on the request-scoped streaming work (a third demux key; a generalized close
+predicate; indefinite lifetime), but with two genuinely new and costly pieces — disconnect-driven
+teardown of many concurrent indefinite streams (leak risk), and a **streaming authorization filter**
+(the buffer-then-filter `ResponseFilteringWriter` cannot filter a live stream; per-message policy is
+a new enforcement mode). Deferred until subscription demand is demonstrated.
+
+## Security Considerations
+
+### Threat Model
+
+- **Attacker**: an authenticated client of the proxy (authentication is unaffected —
+ see below), attempting to read another client's responses or ride another client's
+ routing state.
+- **Primary threat**: response cross-delivery in the streamable proxy. Responses arriving
+ over the single shared stdio pipe are demultiplexed to the waiting HTTP handler by a
+ composite key of (routing token, JSON-RPC id). If two concurrent requests share a
+ routing token and reuse a JSON-RPC id (trivial — ids commonly start at `1`), the second
+ overwrites the first's waiter entry and one client's response payload is delivered to
+ the other.
+
+### Authentication and Authorization
+
+- Authentication is **not** on the session/initialize axis. It runs in the middleware
+ chain before request routing, so making Modern traffic sessionless does not move any
+ request off the authenticated path.
+- The per-request `_meta` fields (`protocolVersion`, `clientInfo`, `clientCapabilities`)
+ and the mirrored headers are client-controlled and now appear on every request. They
+ are telemetry and labels, **never a security principal**; authorization must continue
+ to key off the authenticated identity, not client-asserted `_meta`.
+- Registering `server/discover` in the parser method tables is partly a security measure:
+ an unknown method currently yields an empty resource ID, so authorization must
+ default-deny rather than match a broad allow rule.
+
+### Data Security
+
+The routing token is an in-process correlation nonce, not persisted and not exposed to
+clients (the proxy never returns it as a session header on the Modern path). Its
+uniqueness is what prevents the cross-delivery leak described in the threat model.
+
+### Input Validation
+
+- `_meta` is decoded with guarded type assertions; malformed or absent metadata
+ classifies as Legacy, preserving the existing session guard (fail-safe direction).
+- `Mcp-Method` (all requests) / `Mcp-Name` (`tools/call`, `resources/read`, `prompts/get`)
+ and `MCP-Protocol-Version` are validated against the body;
+ mismatch is rejected with `-32020`. As an intermediary that reads bodies for
+ authz/audit, the proxy should verify that the protocol version indicates a
+ header-validation-required revision before trusting any mirrored header value.
+
+### Secrets Management
+
+Not applicable — this change handles no secrets or credentials.
+
+### Audit and Logging
+
+`clientInfo` moves from a once-per-session handshake value to a per-request `_meta`
+field. Audit labeling must source it per request. It remains a structured log field
+(not interpolated into messages), so it introduces no log-forging vector.
+
+### Mitigations
+
+- **Streamable proxy**: for Modern requests, unconditionally mint a fresh UUID routing
+ token and ignore any client-supplied `Mcp-Session-Id`. A unique token guarantees a
+ unique composite key even when clients reuse JSON-RPC ids, closing the cross-delivery
+ leak by construction. Ignoring the header is safe because Modern has no legitimate
+ session header.
+- **Transparent proxy**: gate the backend-SID rewrite on Legacy so a Modern request's
+ session header can never be rewritten onto a pinned backend.
+- **Verification**: a concurrency test asserting that two concurrent Modern requests
+ sharing a JSON-RPC id — one carrying a foreign session header — never cross-deliver.
+
+## Alternatives Considered
+
+### Alternative 1: Extend the existing `--stateless` flag as the axis
+
+- Description: reuse ToolHive's per-server `--stateless` flag ("POST-only, no SSE") as
+ the stateless switch instead of a per-request classifier.
+- Pros: reuses existing mechanism (GET/DELETE method gate, POST-ping health check).
+- Cons: `--stateless` is a per-server, operator-set declaration; it cannot express a
+ dual-era endpoint where a Legacy client (which needs GET SSE) and a Modern request
+ arrive concurrently. Its server-global GET/DELETE gate would reject a Legacy client's
+ SSE stream. It also does not touch the POST session/initialize path at all.
+- Why not chosen: it is orthogonal to protocol era. The classifier composes with it
+ additively; the one interaction is that the global GET/DELETE gate must become
+ revision-aware under dual-era.
+
+### Alternative 2: Do stateless handling in the SDK / gate on go-sdk v1.7
+
+- Description: rely on go-sdk v1.7's stateless mode instead of ToolHive proxy code, and
+ sequence this work behind the SDK bump.
+- Pros: less ToolHive-side code; SDK handles discover/MRTR/caching internally.
+- Cons: the transport proxies' routing/session/forwarding path never constructs an
+ mcpcompat/go-sdk client or server — it is hand-rolled JSON-RPC over
+ `golang.org/x/exp/jsonrpc2`. The SDK's stateless mode governs SDK-built servers/clients,
+ not ToolHive's proxy data path. Gating on a prerelease SDK would also delay work that
+ has no actual dependency on it.
+- Why not chosen: the proxy work is SDK-independent and can proceed in parallel with the
+ bump. (The SDK bump remains relevant for ToolHive's client-role code and for vMCP.)
+
+### Alternative 3: Full bidirectional era bridge in the transport proxies
+
+- Description: implement all four matrix cells, including gateway-side translation for
+ Legacy-client / Modern-server and Modern-client / Legacy-server.
+- Pros: any client reaches any backend regardless of era.
+- Cons: the Legacy-client / Modern-server cell requires the gateway to synthesize and
+ hold session state the spec has no client-side fallback for — the spec's own
+ compatibility matrix marks this cell "Fails" ("Legacy clients have no fall-forward
+ mechanism"). It is also more naturally a gateway-aggregation concern than a
+ transport-proxy one.
+- Why not chosen: deferred; the cross-generation bridge belongs to vMCP ([toolhive#5756]).
+ This RFC scopes to the diagonal cells and relies on the spec-normative client fallback
+ for Modern-client / Legacy-server.
+
+## Compatibility
+
+### Backward Compatibility
+
+Fully backward compatible. All traffic classifies as Legacy unless a request carries
+Modern `_meta`, so 2025-11-25 clients, servers, and backends behave exactly as today.
+No CRD or operator change is required (the classifier is auto-detected), so a rolling
+proxy upgrade is safe: old and new proxy pods can share one Service and session store,
+and Modern traffic adds no session writes.
+
+**Dependency note**: [toolhive#5755] currently lists a dependency on the go-sdk v1.7
+adoption issue ([toolhive#5754]). Per Alternative 2, the transport-proxy work does not
+depend on it and this RFC proposes decoupling that edge.
+
+### Forward Compatibility
+
+The binary Modern/Legacy classification suffices while there is a single Modern revision.
+When a second ships, `DiscoverResult.supportedVersions` and
+`UnsupportedProtocolVersionError.data.supported` are version lists, so the classifier
+would need real version-string negotiation rather than a boolean. `ClassifyRevision` is
+the single extension point for that: `Revision` would grow from an enum into a type that
+carries the negotiated version string — a signature-level change, not a drop-in.
+
+## Implementation Plan
+
+The in-scope changes are grouped by component; each is independently shippable, and everything
+classifies as Legacy by default so existing traffic is unchanged until a Modern request arrives.
+
+### In scope — changes to make
+
+- **Revision classifier, parser, authz.** Wire the committed `ClassifyRevision`
+ (`pkg/mcp/revision.go`) at the decode points and deepen it (`-32022` unsupported version, required
+ `clientInfo`/`clientCapabilities`, no silent downgrade, `-32020` on mismatch); store `Revision` in
+ transparent-proxy session metadata. Register `server/discover`, `Mcp-Method`, `Mcp-Name` in the
+ parser; source `clientInfo` from `_meta`. Add authz-map (`MCPMethodToFeatureOperation`) entries for
+ `server/discover` + `subscriptions/listen` — parser labeling alone leaves them default-denied.
+- **Streamable proxy (Modern stateless + shape enforcement).** For Modern, unconditionally mint a
+ per-request routing token and ignore client `Mcp-Session-Id`; do not mint a session. Behind the
+ Legacy/Modern mode flag, reject Modern batches and client-sent responses; GET/DELETE→405.
+- **Transparent proxy.** Gate the session guard, pod-pinning, re-init replay, and backend-SID rewrite
+ on Legacy; give the health monitor a Modern readiness trigger (first backend contact /
+ `server/discover`), not the `ping`-based `StatelessMCPPinger`.
+- **Telemetry.** Set `mcp.client.name` on all spans from `_meta.clientInfo`.
+
+### Deferred (designed above, built separately)
+
+- **Request-scoped streaming** (stdio streamable proxy): the one-shot→streaming dispatcher rework,
+ `progressToken` demux, backpressure, and the test inversion. Lower-impact, stdio-only.
+- **Subscriptions** (`subscriptions/listen` + streaming authz filter): gated on demand.
+- **Legacy-client/Modern-server bridge**: belongs to vMCP ([toolhive#5756]); needs backend-revision
+ detection (probe `server/discover` on stdio; inspect the `400` body on HTTP).
+
+### Dependencies
+
+None blocking. Independent of [toolhive#5754] (see Compatibility).
+
+## Testing Strategy
+
+- **Unit**: `ClassifyRevision` truth table — Modern `_meta`, absent/malformed `_meta`,
+ `initialize`, header/body mismatch → `-32020`, unsupported version → `-32022`, and
+ Modern-header-with-broken-`_meta` → error (not Legacy downgrade).
+- **Integration**: dual-era matrix — Legacy and Modern requests against the same endpoint;
+ verify Legacy session behavior is unchanged and Modern requests mint no session and emit
+ no `Mcp-Session-Id`; Modern batches / client-sent responses rejected.
+- **Security**: concurrency test asserting no response cross-delivery for concurrent Modern
+ requests sharing a JSON-RPC id, one carrying a foreign session header.
+- **Kubernetes**: Modern request to a multi-replica backend routes without pod-pinning and
+ does not re-initialize on backend `404`; the internal monitor does not self-stop on a
+ cold-starting Modern backend.
+- **Health**: pinger against a Modern backend that rejects `ping` (the removed-method case).
+- **Conformance**: the existing job (`test/conformance/`, #5806) runs the upstream suite against
+ the **HTTP/transparent** path on spec `2025-06-18`, so it does **not** cover the stdio streamable
+ proxy or 2026-07-28 — it is not a safety net for this work. A stdio-backend run and a
+ 2026-07-28-capable upstream version are deferred as a separate testing item.
+- **Streaming (when built)**: the notification-delivery test that **inverts**
+ `TestHTTPRequestIgnoresNotifications` and the batch spec tests, which today assert the drop.
+
+## Documentation
+
+- Architecture doc: `docs/arch/stateless-transport-proxies-design.md` (already drafted,
+ [toolhive#5808]) — this RFC is its decision-level counterpart.
+- Update `docs/arch/03-transport-architecture.md` when the proxy changes land.
+
+## Open Questions
+
+1. Should the revision-aware GET/DELETE gate on the transparent proxy be a refinement of
+ the existing `statelessMethodGate`, or a distinct mechanism keyed on `MCP-Protocol-Version`?
+2. Timing: file the implementation issues on acceptance, or after go-sdk v1.7
+ final (given the spec is still finalizing)?
+3. Do we want the optional future tie-in where `--stateless` implies an "assume Modern"
+ fast-path, or keep the two strictly orthogonal?
+
+## References
+
+- MCP draft specification: `basic/versioning` (era terminology, compatibility matrix,
+ fallback), `server/discover`, `basic/transports/{stdio,streamable-http}`.
+- Prior art — agentgateway: `crates/agentgateway/src/mcp/README.md` and `session.rs`
+ (`stateless_send_and_initialize`); evolution via issue #221 and PRs #262, #2365, #2477.
+- ToolHive architecture doc: `docs/arch/stateless-transport-proxies-design.md`
+ ([toolhive#5808]).
+- Related: [toolhive#5755] (this work), [toolhive#5756] (vMCP stateless), [toolhive#5754]
+ (go-sdk v1.7), [toolhive#5760] (OAuth `iss`).
+
+---
+
+## RFC Lifecycle
+
+### Review History
+
+| Date | Reviewer | Decision | Notes |
+|------|----------|----------|-------|
+| 2026-07-15 | — | Draft | Initial submission |
+
+### Implementation Tracking
+
+| Repository | PR | Status |
+|------------|-----|--------|
+| toolhive | — | Not started |