Repository navigation
Description
Follow-up to #1640. McpClientAdapter can already speak both eras, but the caller picks one before start(): 2025-11-25 by default through the SDK client, or 2026-07-28 with setProtocolVersion(McpProtocol.MODERN_VERSION). It never detects which one the server speaks.
Most MCP servers in the wild are still legacy. A client that consumes third-party servers (for example, Ikanos' consumes: type: mcp) can't know the era in advance.
The 2026-07-28 Versioning and Compatibility page defines how a dual-era client does this. This issue implements it as the client side of the legacy bridge in #1671: the modern client is the core, and legacy servers are reached through a thin edge in org.restlet.ext.mcp.internal.legacy that exposes nothing 2026-07-28 deprecates or removes.
Delivery:
- Auto mode. A new default on
McpClientAdapter(setProtocolVersionunset, or an explicitauto) that picks the era atstart(). Explicit2025-11-25and2026-07-28keep today's behavior. - Modern first, per the spec's transport bindings:
- HTTP: the first modern request. A result, or a recognized modern JSON-RPC error such as
UnsupportedProtocolVersionError(-32022), means a modern server. A4xxwithout a recognized modern error body means a legacy server: fall back toinitialize. - stdio: probe with
server/discover. ADiscoverResultor a modern error means modern. Any other error, or a timeout, means legacy: fall back toinitialize.
- HTTP: the first modern request. A result, or a recognized modern JSON-RPC error such as
- Version selection. On a modern server, pick a mutually supported version from
DiscoverResult.supportedVersionsor fromUnsupportedProtocolVersionError.data.supported, and retry. On a legacy server, accept the versioninitializenegotiates when it's one the SDK supports (2025-11-25, and earlier revisions if the SDK accepts them), instead of failing unless it's exactlyMcpProtocol.VERSION. - Cache the era for the lifetime of the origin (HTTP) or the server process (stdio), as the spec recommends. Re-probe once if a cached assumption later fails.
- Legacy fallback limits. In fallback the client declares no roots, sampling or elicitation capabilities, never calls
logging/setLevel, and rejects server-to-client requests. It opens no listeningGETstream and never sendsMcp-Session-Idunless legacy sessions are turned on (item 6). - Opt-in legacy sessions. Many legacy servers require
Mcp-Session-Id.McpClientAdapterexposes it through explicitly legacy properties, off by default:setLegacySessions(boolean): the edge keeps the id a legacy server returns oninitialize, sends it on later requests, re-initializes once on a 404 (expired session), and sends a best-effortDELETEonclose().getLegacySessionId()/setLegacySessionId(String): read the id in use, or supply one the caller owns (for example a proxy relaying an upstream session). A caller-supplied id is sent but never deleted.- Ignored on modern servers. Javadoc states that 2026-07-28 removed sessions, that these properties go away with the legacy edge, and that the client still opens no
GETstream, does not resume withLast-Event-ID, and rejects server-to-client requests, so servers depending on those won't work. Not marked@Deprecated, to keep callers who need it free of warnings.
- One facade, modern types. Era-neutral entry points for the common calls (
listTools,callTool,readResource,getPrompt) return modern types. Legacy results are converted at the edge; a missingresultTypemeanscomplete. - Tests: dual-era client against a modern server, a stateless legacy server, a session-holding legacy server and a dual-era server (R6: Modern MCP core, with legacy MCP as a deletable bridge edge #1671). Cover fallback on HTTP and stdio, era caching and re-probe, version retry on
-32022, legacy sessions (kept, sent, expired, deleted; caller-supplied id never deleted; no header when off), and assert on the wire that the fallback never sends a deprecated or removed method.
Out of scope: listening GET streams and Last-Event-ID resumption on legacy sessions, server-initiated elicitation, sampling and roots on legacy servers, and the deprecated HTTP+SSE transport. All are deprecated or removed in 2026-07-28. The sessionful transport spiked in #1659 provides the session handling behind item 6.
Open questions:
- Should auto become the default, or stay opt-in while the SDK's modern API is a milestone?
Design memo: https://claude.ai/artifact/73QEwzKyg79YHVHjAyHdLd
Related: #1671, #1640, #1659, #1558. Ikanos consumer: naftiko/ikanos#1014.
Activity
jlouvel commentedon Nov 13, 2026
Rescoped as the client side of the legacy bridge (#1671): modern first, legacy fallback exposes nothing 2026-07-28 deprecates or removes. Session-holding legacy servers and server-initiated elicitation on legacy sessions (former items 5 and 6) moved out of scope; see the updated description.
jlouvel commentedon Nov 13, 2026
Session-holding legacy servers are back in scope, opt-in only: new item 6 adds explicitly legacy
setLegacySessions/getLegacySessionId/setLegacySessionIdproperties onMcpClientAdapter, off by default.GETstreams, resumption and server-initiated requests stay out.