Clarify McpTransportContext javadoc

See #1075

Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
This commit is contained in:
Daniel Garnier-Moiroux 2026-08-26 16:08:10 +02:00
commit bb0ca87da5
3 changed files with 45 additions and 6 deletions

View file

@ -494,9 +494,10 @@ public interface McpClient {
* calling any client operation. This allows to extract thread-locals and hand
* them over to the underlying transport.
* <p>
* There is no direct equivalent in {@link AsyncSpec}. To achieve the same result,
* append {@code contextWrite(McpTransportContext.KEY, context)} to any
* {@link McpAsyncClient} call.
* The supplier is invoked at subscription time, on the calling thread, and the
* resulting context is visible to the transport for every leg of the operation,
* including connections opened on other threads, such as the SSE stream started
* during initialization.
* @param contextProvider A supplier to create a context
* @return This builder for method chaining
*/
@ -580,6 +581,21 @@ public interface McpClient {
* <li>Change notification handlers for tools, resources, and prompts
* <li>Custom message sampling logic
* </ul>
*
* <p>
* Unlike {@link SyncSpec}, this specification has no
* {@code transportContextProvider}. This is deliberate: in a reactive pipeline the
* caller owns the Reactor context, and whatever the transport needs, such as an
* {@link McpTransportContext}, can be written into it directly. Write it once where
* the reactive chain starts, rather than at every call site, and every
* {@link McpAsyncClient} call downstream inherits it, including the connections
* opened during initialization: <pre>{@code
* chain.filter(exchange)
* .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context));
* }</pre> To bridge thread-locals into the chain, use Reactor's context propagation
* support.
*
* @see SyncSpec#transportContextProvider(Supplier)
*/
class AsyncSpec {

View file

@ -7,18 +7,26 @@ package io.modelcontextprotocol.client.transport.customizer;
import java.net.URI;
import java.net.http.HttpRequest;
import io.modelcontextprotocol.client.McpClient.SyncSpec;
import io.modelcontextprotocol.common.McpTransportContext;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.annotation.Nullable;
import io.modelcontextprotocol.common.McpTransportContext;
/**
* Customize {@link HttpRequest.Builder} before executing the request, in either SSE or
* Streamable HTTP transport.
* <p>
* When used in a non-blocking context, implementations MUST be non-blocking.
* <p>
* The {@link McpTransportContext} handed to {@code customize} is read from the Reactor
* context, under {@link McpTransportContext#KEY}, and is
* {@link McpTransportContext#EMPTY} when the caller wrote nothing there. Write it once
* where the reactive chain starts, with
* {@code contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context))}, rather than at
* every call site. With a synchronous client, configure
* {@link SyncSpec#transportContextProvider} instead.
*
* @author Daniel Garnier-Moiroux
*/

View file

@ -11,13 +11,28 @@ import java.util.Map;
* Context associated with the transport layer. It allows to add transport-level metadata
* for use further down the line. Specifically, it can be beneficial to extract HTTP
* request metadata for use in MCP feature implementations.
* <p>
* The context travels in the Reactor context, under {@link #KEY}. On the server side, the
* transports populate it from the incoming request. On the client side, writing it is the
* caller's responsibility:
* <ul>
* <li>with a synchronous client, configure
* {@code McpClient.SyncSpec#transportContextProvider(Supplier)}, which is invoked on the
* calling thread before every operation;
* <li>with an asynchronous client, write it into the Reactor context once, where the
* reactive chain starts, using
* {@code contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context))}. Every client
* call downstream inherits it, so there is no need to repeat it at each call site.
* </ul>
*
* @author Dariusz Jędrzejczyk
*/
public interface McpTransportContext {
/**
* Key for use in Reactor Context to transport the context to user land.
* Key for use in Reactor Context to transport the context to user land. Write the
* context under this key to make it visible to the transport, for example
* {@code contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context))}.
*/
String KEY = "MCP_TRANSPORT_CONTEXT";