Repository navigation
Conversation
The latest protocol revision introduced a stateless incarnation of the
protocol. It requires no initialization handshake or session
management, and it doesn't allow server-to-client requests.
An instance of the modern client is meant to stand for one user. It
does no I/O when built and can share its transport with other clients,
so an application that handles authorization creates one per user over
a single HTTP transport. Identity isn't passed to client methods; the
caller's McpTransportContext reaches the transport's request customizer
and authorization error handler. The tool catalog is therefore that
user's view of the server. The client also uses it for x-mcp-header
mirroring.
Clients are built with McpClient.async(transport) or
McpClient.sync(transport), and each operation goes through an immutable
request spec, e.g. client.tool("create_issue").arguments(args).call().
Timeouts, progress, _meta and elicitation handlers are set on the
builder and can be overridden per call on the spec.
The client takes care of what the revision requires:
- the _meta block with version and capabilities on every request
- version renegotiation
- the multi round-trip loop for input_required results (with callOnce()
for callers that run it themselves)
- the Mcp-* and Mcp-Param-* headers, with one retry on -32020
- subscriptions/listen streams that report which parts of the filter
the server doesn't honour
A round timeout bounds each exchange but not the time a user spends
answering; a call timeout is optional.
The sync client runs the loop and its handlers on the caller's thread.
Transports: Streamable HTTP on the JDK HttpClient, and stdio. Sampling,
roots, logging and extensions are left out for now.
On the server side, the HTTP and stdio wire constants are shared with
the client, input requests are typed by method and mode, and the listen
filter is open so features can honour extension fields.
InputResponses.get and RequestStateCodec.open return null instead of an
empty Optional.
A conformance client in conformance-tests/client-modern passes the
non-auth 2026-07-28 client scenarios.
Signed-off-by: Dariusz Jedrzejczyk <[email protected]>
- listTools() asked the server for every page on each call. The client now keeps the listing for its ttlMs and returns it while it's fresh. listTools() returns a spec: list() serves the kept listing, and skipCache() always asks the server. A listen stream that reports tool changes drops the kept listing. - The client's own failures (missing input handler, too many input rounds, invalid result, lost response, timeout, lost listen stream, HTTP error status) now extend McpClientException. Errors the server answered remain McpException, so catching the two covers every failure of a call. - McpHttpRequestCustomizer.fromSync ran the customizer on whichever thread sent the request, sometimes the caller's and sometimes an HTTP client thread. It now runs on boundedElastic, as the authorization error handler's fromSync does, so a customizer may block, for example to fetch a token. Hooks get the call's identity only from the McpTransportContext, on every attempt. - McpHttpRequest and McpHttpResponseInfo can now be created outside the SDK, with McpHttpRequest.builder(...) and McpHttpResponseInfo.of(...), so a custom HTTP transport can call the request customizers and the authorization error handler. Signed-off-by: Dariusz Jedrzejczyk <[email protected]>
|
Thanks for the modern client. We maintain a downstream MCP client connector on top of this SDK and plan to adopt 2026-07-28, so we have two questions about scope and timing. 1. Fallback to the 2. Release timing. The PR is in milestone 2.2.0-M3. Will the modern client ship in that milestone? Is there an expected date for a non-milestone release that includes it? The roadmap says the SDK implements new revisions within six months of the spec, which is about the end of January 2027. We need to know whether to plan around that date. |
The latest protocol revision introduced a stateless incarnation of the protocol. It requires no initialization handshake or session management, and it doesn't allow server-to-client requests.
An instance of the modern client is meant to stand for one user. It does no I/O when built and can share its transport with other clients, so an application that handles authorization creates one per user over a single HTTP transport. Identity isn't passed to client methods; the caller's
McpTransportContextreaches the transport's request customizer and authorization error handler. The tool catalog is therefore that user's view of the server. The client also uses it forx-mcp-headermirroring.Clients are built with
McpClient.async(transport)orMcpClient.sync(transport), and each operation goes through an immutable request spec, e.g.client.tool("create_issue").arguments(args).call(). Timeouts, progress, _meta and elicitation handlers are set on the builder and can be overridden per call on the spec.The client takes care of what the revision requires:
input_requiredresults (withcallOnce()for callers that run it themselves)Mcp-*andMcp-Param-*headers, with one retry on-32020subscriptions/listenstreams that report which parts of the filter the server doesn't honourA round timeout bounds each exchange but not the time a user spends answering; a call timeout is optional.
The sync client runs the loop and its handlers on the caller's thread.
Transports: Streamable HTTP on the JDK
HttpClient, and stdio. Sampling, roots, logging and extensions are left out for now.On the server side, the HTTP and stdio wire constants are shared with the client, input requests are typed by method and mode, and the listen filter is open so features can honour extension fields.
InputResponses.getandRequestStateCodec.openreturn null instead of an emptyOptional.A conformance client in conformance-tests/client-modern passes the non-auth 2026-07-28 client scenarios.
Part of #1011