You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
MCP: 2026-07-28 core, legacy MCP as a deletable edge (#1671, #1670) - #1672
Registrations, tool filters (new McpToolFilter) and interceptors use the SDK's modern types and run once per request, in either era.
Breaking (3.0 is at M1, no deprecation step): the 2025-11-25 registration lists are removed; getTools(), getResources(), getResourceTemplates(), getPrompts() take modern types; McpModernResource/McpModernResourceTemplate are renamed McpResource/McpResourceTemplate; dispatch() and setServerInfo() take modern types.
Legacy requests go through the McpLegacyEdge seam to internal.legacy.McpLegacyServer: initialize answered from server/discover (tools, resources, prompts only), ping empty result, notifications dropped, logging/setLevel and resources/subscribe return -32601, no session. Legacy clients get the final result of a streaming tool, and an error instead of input_required.
McpLegacyBoundaryTestCase enforces the three boundary rules by scanning sources. One allow-listed exception: the SDK's modern header decoder takes HeaderAccessor from a legacy package.
The SDK's legacy client and both legacy transports move to internal.legacy. Removed: getSdkClient(), StdioClientHelper.getTransport(), LEGACY_INPUT_KEY, the public RestletMcpClientTransport.
Auto mode probes server/discover and falls back to initialize on a non-modern error; the era is cached per endpoint.
listTools, callTool, readResource, getPrompt return modern types in both eras.
With a legacy server, the client declares no capabilities and answers server requests with -32601.
Opt-in legacy sessions: setLegacySessions(boolean), setLegacySessionId(String) (caller-owned, sent but never deleted), getLegacySessionId().
Not done
Over stdio, auto mode speaks 2025-11-25: there's no modern stdio client yet.
The server edge accepts only 2025-11-25 in initialize, as before.
Registrations, tool filters and interceptors now use the SDK's modern
types. MCP 2025-11-25 traffic is translated at a legacy edge in
internal.legacy, behind the McpLegacyEdge seam, which exposes nothing
2026-07-28 deprecates or removes. An architecture test enforces the
boundary.
The client detects the server's era (auto mode), falls back to legacy
servers without capabilities, and keeps their session only on opt-in,
through setLegacySessions() or setLegacySessionId().
Closes#1671, closes#1670.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Legacy interceptor failures lose request IDs and can fail the stdio transport, while reply-handling, shutdown-timeout, and discovery-fallback defects also remain unresolved.
This PR moves the Restlet MCP extension to a modern SDK core, isolating legacy compatibility behind a removable edge. It also adds opt-in HTTP era detection and legacy session support.
Changes:
Unifies server registrations, filters, interceptors and common client results on modern SDK types.
Isolates legacy transports and translation, with restricted capabilities and optional sessions.
Updates integration tests, adds boundary checks and documents the API changes.
…ssion deletion
Address the first Copilot review:
- A legacy request failing past the edge, in an interceptor or at the
deadline, is answered with its id once decoded, not a null id that a
stdio client can't match to its call.
- server/discover failing with a header mismatch or a missing client
capability error comes from a modern server: auto mode keeps the
failure instead of falling back to initialize.
- A request a legacy server sends on a response stream is refused with
-32601 with or without a session, and the refusal's empty 202 is
accepted.
- Notifications and replies, which open no stream, take workers of
their own: a refusal, or notifications/initialized, no longer waits
for the call slot the exchange it follows still holds, so a client
with one call slot can initialize and refuse.
- The session DELETE on close runs on its own thread and is aborted at
the request timeout, so a stalled server no longer blocks stop().
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
A delayed 404 from an expired session can invalidate its valid replacement and disrupt concurrent calls, alongside unresolved protocol-recovery, version-tracking, and test-coverage gaps.
After a server-issued legacy session expires, the SDK initializes again and the transport updates its negotiated version from the new response. If that handshake selects a different supported revision, this getter still reports the initial version: both this field and McpLegacyClient.negotiated are snapshots of the first handshake. Read the legacy client's current negotiated version, keeping it synchronized with the transport, and cover a version change after session expiry.
Cached startup failures trigger unsafe era rediscovery
Any cached startup failure clears the era and retries discovery, including timeouts and HTTP 5xx responses that do not establish a protocol mismatch. This sends extra requests during outages and can repeat a session-creating handshake after an ambiguous timeout. Preserve these failures, re-probe only when the cached era is contradicted, and extend the server-error test to cover cached eras.
Protocol mismatch recovery is missing after startup
Era recovery runs only during startup: after an auto client starts, all four common operations keep using the selected client even if the endpoint changes eras. The client therefore cannot perform the promised one-time re-probe or correct its cached era when a later operation exposes the mismatch. Add coordinated runtime recovery for protocol mismatches without blindly replaying potentially side-effecting tool calls.
The legacy tests negotiate only 2025-11-25, but this now accepts three earlier revisions in auto mode. That behavior also depends on the SDK accepting the negotiated revision and the transport switching subsequent version headers. Add parameterized legacy handshakes for the earlier advertised revisions, assert a subsequent call uses the negotiated header, and cover rejection of an unlisted revision; the current fixtures do not exercise this compatibility expansion.
Legacy import boundary test misses wildcard imports
This pattern does not match wildcard imports such as import io.modelcontextprotocol.spec.*; (or the corresponding server.* and client.* imports). Consequently, a core class can import legacy SDK types through a wildcard and still pass onlyTheLegacyPackageUsesTheLegacySdk(), defeating the boundary this test is meant to enforce. Include wildcard imports in the matches while retaining the exact HeaderAccessor exception.
var expired = new McpTransportSessionNotFoundException(sent);
exceptionHandler.accept(expired);
throw new IOException("MCP session " + sent + " expired", expired);
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1671 and #1670. Design memo: https://claude.ai/artifact/73QEwzKyg79YHVHjAyHdLd
Server (#1671)
McpToolFilter) and interceptors use the SDK's modern types and run once per request, in either era.getTools(),getResources(),getResourceTemplates(),getPrompts()take modern types;McpModernResource/McpModernResourceTemplateare renamedMcpResource/McpResourceTemplate;dispatch()andsetServerInfo()take modern types.McpLegacyEdgeseam tointernal.legacy.McpLegacyServer:initializeanswered fromserver/discover(tools, resources, prompts only),pingempty result, notifications dropped,logging/setLevelandresources/subscribereturn -32601, no session. Legacy clients get the final result of a streaming tool, and an error instead ofinput_required.McpLegacyBoundaryTestCaseenforces the three boundary rules by scanning sources. One allow-listed exception: the SDK's modern header decoder takesHeaderAccessorfrom a legacy package.Client (#1670)
internal.legacy. Removed:getSdkClient(),StdioClientHelper.getTransport(),LEGACY_INPUT_KEY, the publicRestletMcpClientTransport.server/discoverand falls back toinitializeon a non-modern error; the era is cached per endpoint.listTools,callTool,readResource,getPromptreturn modern types in both eras.setLegacySessions(boolean),setLegacySessionId(String)(caller-owned, sent but never deleted),getLegacySessionId().Not done
initialize, as before.Tests
./mvnw -B verify -P default,test -pl org.restlet.ext.mcp: 112 tests pass, coverage and SpotBugs checks met.🤖 Generated with Claude Code