From f13fef387e4d49f6a59087c5331e41db66018df2 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Wed, 27 Aug 2025 17:13:15 +0200 Subject: [PATCH 1/4] McpSyncClient: introduce McpTransportContext - McpSyncClient should be considered thread-agnostic, and therefore consumers cannot rely on thread locals to propagate "context", e.g. pass down the Servlet request reference in a server context. - This PR introduces a mechanism for populating an McpTransportContext before executing client operations, and reworks the HTTP request customizers to leverage that McpTransportContext. - This introduces a breaking change to the Sync/Async request customizers. Signed-off-by: Daniel Garnier-Moiroux --- .../WebFluxSseServerTransportProvider.java | 4 +- .../WebFluxStatelessServerTransport.java | 2 +- ...FluxStreamableServerTransportProvider.java | 6 +- .../WebMvcSseServerTransportProvider.java | 3 +- .../WebMvcStatelessServerTransport.java | 2 +- ...bMvcStreamableServerTransportProvider.java | 6 +- .../client/McpClient.java | 25 ++++++- .../client/McpSyncClient.java | 72 +++++++++++++------ .../HttpClientSseClientTransport.java | 13 ++-- .../HttpClientStreamableHttpTransport.java | 17 +++-- ...legatingMcpAsyncHttpRequestCustomizer.java | 10 ++- ...elegatingMcpSyncHttpRequestCustomizer.java | 9 ++- .../McpAsyncHttpRequestCustomizer.java | 11 +-- .../McpSyncHttpRequestCustomizer.java | 11 ++- .../server/DefaultMcpTransportContext.java | 42 +++++------ .../server/McpTransportContext.java | 24 +++---- .../server/McpTransportContextExtractor.java | 9 +-- ...HttpServletSseServerTransportProvider.java | 11 ++- .../HttpServletStatelessServerTransport.java | 6 +- ...vletStreamableServerTransportProvider.java | 10 +-- ...tpClientStreamableHttpSyncClientTests.java | 31 +++++++- .../client/HttpSseMcpSyncClientTests.java | 36 +++++++++- .../HttpClientSseClientTransportTests.java | 37 +++++++--- ...bleHttpTransportEmptyJsonResponseTest.java | 3 +- ...HttpClientStreamableHttpTransportTest.java | 23 ++++-- ...tingMcpAsyncHttpRequestCustomizerTest.java | 17 +++-- ...atingMcpSyncHttpRequestCustomizerTest.java | 13 ++-- .../HttpServletSseIntegrationTests.java | 7 +- ...HttpServletStreamableIntegrationTests.java | 7 +- .../server/McpAsyncServerExchangeTests.java | 2 +- 30 files changed, 316 insertions(+), 153 deletions(-) diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java index ead7380f0..252c78dc3 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java @@ -344,7 +344,7 @@ private Mono handleSseConnection(ServerRequest request) { return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).bodyValue("Server is shutting down"); } - McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext()); + McpTransportContext transportContext = this.contextExtractor.extract(request); return ServerResponse.ok() .contentType(MediaType.TEXT_EVENT_STREAM) @@ -401,7 +401,7 @@ private Mono handleMessage(ServerRequest request) { .bodyValue(new McpError("Session not found: " + request.queryParam("sessionId").get())); } - McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext()); + McpTransportContext transportContext = this.contextExtractor.extract(request); return request.bodyToMono(String.class).flatMap(body -> { try { diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java index 23fff25b3..423310f63 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java @@ -97,7 +97,7 @@ private Mono handlePost(ServerRequest request) { return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).bodyValue("Server is shutting down"); } - McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext()); + McpTransportContext transportContext = this.contextExtractor.extract(request); List acceptHeaders = request.headers().asHttpHeaders().getAccept(); if (!(acceptHeaders.contains(MediaType.APPLICATION_JSON) diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java index 963a50249..3bff1fea3 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java @@ -166,7 +166,7 @@ private Mono handleGet(ServerRequest request) { return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).bodyValue("Server is shutting down"); } - McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext()); + McpTransportContext transportContext = this.contextExtractor.extract(request); return Mono.defer(() -> { List acceptHeaders = request.headers().asHttpHeaders().getAccept(); @@ -221,7 +221,7 @@ private Mono handlePost(ServerRequest request) { return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).bodyValue("Server is shutting down"); } - McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext()); + McpTransportContext transportContext = this.contextExtractor.extract(request); List acceptHeaders = request.headers().asHttpHeaders().getAccept(); if (!(acceptHeaders.contains(MediaType.APPLICATION_JSON) @@ -309,7 +309,7 @@ private Mono handleDelete(ServerRequest request) { return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).bodyValue("Server is shutting down"); } - McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext()); + McpTransportContext transportContext = this.contextExtractor.extract(request); return Mono.defer(() -> { if (!request.headers().asHttpHeaders().containsKey(HttpHeaders.MCP_SESSION_ID)) { diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java index 6e92cf10c..c9230d3c5 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java @@ -397,8 +397,7 @@ private ServerResponse handleMessage(ServerRequest request) { } try { - final McpTransportContext transportContext = this.contextExtractor.extract(request, - new DefaultMcpTransportContext()); + final McpTransportContext transportContext = this.contextExtractor.extract(request); String body = request.body(String.class); McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, body); diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java index fef1920fc..400b2d824 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java @@ -101,7 +101,7 @@ private ServerResponse handlePost(ServerRequest request) { return ServerResponse.status(HttpStatus.SERVICE_UNAVAILABLE).body("Server is shutting down"); } - McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext()); + McpTransportContext transportContext = this.contextExtractor.extract(request); List acceptHeaders = request.headers().asHttpHeaders().getAccept(); if (!(acceptHeaders.contains(MediaType.APPLICATION_JSON) diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java index fa51a0130..2a69a10c2 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java @@ -238,7 +238,7 @@ private ServerResponse handleGet(ServerRequest request) { return ServerResponse.badRequest().body("Invalid Accept header. Expected TEXT_EVENT_STREAM"); } - McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext()); + McpTransportContext transportContext = this.contextExtractor.extract(request); if (!request.headers().asHttpHeaders().containsKey(HttpHeaders.MCP_SESSION_ID)) { return ServerResponse.badRequest().body("Session ID required in mcp-session-id header"); @@ -322,7 +322,7 @@ private ServerResponse handlePost(ServerRequest request) { .body(new McpError("Invalid Accept headers. Expected TEXT_EVENT_STREAM and APPLICATION_JSON")); } - McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext()); + McpTransportContext transportContext = this.contextExtractor.extract(request); try { String body = request.body(String.class); @@ -431,7 +431,7 @@ private ServerResponse handleDelete(ServerRequest request) { return ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED).build(); } - McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext()); + McpTransportContext transportContext = this.contextExtractor.extract(request); if (!request.headers().asHttpHeaders().containsKey(HttpHeaders.MCP_SESSION_ID)) { return ServerResponse.badRequest().body("Session ID required in mcp-session-id header"); diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/McpClient.java b/mcp/src/main/java/io/modelcontextprotocol/client/McpClient.java index c8af28ac1..ed7573dc1 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/McpClient.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/McpClient.java @@ -11,10 +11,11 @@ import java.util.Map; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; +import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.spec.McpClientTransport; import io.modelcontextprotocol.spec.McpSchema; -import io.modelcontextprotocol.spec.McpTransport; import io.modelcontextprotocol.spec.McpSchema.ClientCapabilities; import io.modelcontextprotocol.spec.McpSchema.CreateMessageRequest; import io.modelcontextprotocol.spec.McpSchema.CreateMessageResult; @@ -22,6 +23,7 @@ import io.modelcontextprotocol.spec.McpSchema.ElicitResult; import io.modelcontextprotocol.spec.McpSchema.Implementation; import io.modelcontextprotocol.spec.McpSchema.Root; +import io.modelcontextprotocol.spec.McpTransport; import io.modelcontextprotocol.util.Assert; import reactor.core.publisher.Mono; @@ -183,6 +185,8 @@ class SyncSpec { private Function elicitationHandler; + private Supplier contextProvider = () -> McpTransportContext.EMPTY; + private SyncSpec(McpClientTransport transport) { Assert.notNull(transport, "Transport must not be null"); this.transport = transport; @@ -409,6 +413,22 @@ public SyncSpec progressConsumers(List> return this; } + /** + * Add a provider of {@link McpTransportContext}, providing a context before + * calling any client operation. This allows to extract thread-locals and hand + * them over to the underlying transport. + *

+ * There is no direct equivalent in {@link AsyncSpec}. To achieve the same result, + * append {@code contextWrite(McpTransportContext.KEY, context)} to any + * {@link McpAsyncClient} call. + * @param contextProvider A supplier to create a context + * @return This builder for method chaining + */ + public SyncSpec transportContextProvider(Supplier contextProvider) { + this.contextProvider = contextProvider; + return this; + } + /** * Create an instance of {@link McpSyncClient} with the provided configurations or * sensible defaults. @@ -423,7 +443,8 @@ public McpSyncClient build() { McpClientFeatures.Async asyncFeatures = McpClientFeatures.Async.fromSync(syncFeatures); return new McpSyncClient( - new McpAsyncClient(transport, this.requestTimeout, this.initializationTimeout, asyncFeatures)); + new McpAsyncClient(transport, this.requestTimeout, this.initializationTimeout, asyncFeatures), + this.contextProvider); } } diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java b/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java index 33784adcd..e096af347 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java @@ -5,16 +5,19 @@ package io.modelcontextprotocol.client; import java.time.Duration; +import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.ClientCapabilities; import io.modelcontextprotocol.spec.McpSchema.GetPromptRequest; import io.modelcontextprotocol.spec.McpSchema.GetPromptResult; import io.modelcontextprotocol.spec.McpSchema.ListPromptsResult; import io.modelcontextprotocol.util.Assert; +import reactor.core.publisher.Mono; /** * A synchronous client implementation for the Model Context Protocol (MCP) that wraps an @@ -63,14 +66,20 @@ public class McpSyncClient implements AutoCloseable { private final McpAsyncClient delegate; + private final Supplier contextProvider; + /** * Create a new McpSyncClient with the given delegate. * @param delegate the asynchronous kernel on top of which this synchronous client * provides a blocking API. + * @param contextProvider the supplier of context before calling any non-blocking + * operation on underlying delegate */ - McpSyncClient(McpAsyncClient delegate) { + McpSyncClient(McpAsyncClient delegate, Supplier contextProvider) { Assert.notNull(delegate, "The delegate can not be null"); + Assert.notNull(contextProvider, "The contextProvider can not be null"); this.delegate = delegate; + this.contextProvider = contextProvider; } /** @@ -177,14 +186,14 @@ public boolean closeGracefully() { public McpSchema.InitializeResult initialize() { // TODO: block takes no argument here as we assume the async client is // configured with a requestTimeout at all times - return this.delegate.initialize().block(); + return withProvidedContext(this.delegate.initialize()).block(); } /** * Send a roots/list_changed notification. */ public void rootsListChangedNotification() { - this.delegate.rootsListChangedNotification().block(); + withProvidedContext(this.delegate.rootsListChangedNotification()).block(); } /** @@ -206,7 +215,7 @@ public void removeRoot(String rootUri) { * @return */ public Object ping() { - return this.delegate.ping().block(); + return withProvidedContext(this.delegate.ping()).block(); } // -------------------------- @@ -224,7 +233,8 @@ public Object ping() { * Boolean indicating if the execution failed (true) or succeeded (false/absent) */ public McpSchema.CallToolResult callTool(McpSchema.CallToolRequest callToolRequest) { - return this.delegate.callTool(callToolRequest).block(); + return withProvidedContext(this.delegate.callTool(callToolRequest)).block(); + } /** @@ -234,7 +244,7 @@ public McpSchema.CallToolResult callTool(McpSchema.CallToolRequest callToolReque * pagination if more tools are available */ public McpSchema.ListToolsResult listTools() { - return this.delegate.listTools().block(); + return withProvidedContext(this.delegate.listTools()).block(); } /** @@ -245,7 +255,8 @@ public McpSchema.ListToolsResult listTools() { * pagination if more tools are available */ public McpSchema.ListToolsResult listTools(String cursor) { - return this.delegate.listTools(cursor).block(); + return withProvidedContext(this.delegate.listTools(cursor)).block(); + } // -------------------------- @@ -257,7 +268,8 @@ public McpSchema.ListToolsResult listTools(String cursor) { * @return The list of all resources result */ public McpSchema.ListResourcesResult listResources() { - return this.delegate.listResources().block(); + return withProvidedContext(this.delegate.listResources()).block(); + } /** @@ -266,7 +278,8 @@ public McpSchema.ListResourcesResult listResources() { * @return The list of resources result */ public McpSchema.ListResourcesResult listResources(String cursor) { - return this.delegate.listResources(cursor).block(); + return withProvidedContext(this.delegate.listResources(cursor)).block(); + } /** @@ -275,7 +288,8 @@ public McpSchema.ListResourcesResult listResources(String cursor) { * @return the resource content. */ public McpSchema.ReadResourceResult readResource(McpSchema.Resource resource) { - return this.delegate.readResource(resource).block(); + return withProvidedContext(this.delegate.readResource(resource)).block(); + } /** @@ -284,7 +298,8 @@ public McpSchema.ReadResourceResult readResource(McpSchema.Resource resource) { * @return the resource content. */ public McpSchema.ReadResourceResult readResource(McpSchema.ReadResourceRequest readResourceRequest) { - return this.delegate.readResource(readResourceRequest).block(); + return withProvidedContext(this.delegate.readResource(readResourceRequest)).block(); + } /** @@ -292,7 +307,8 @@ public McpSchema.ReadResourceResult readResource(McpSchema.ReadResourceRequest r * @return The list of all resource templates result. */ public McpSchema.ListResourceTemplatesResult listResourceTemplates() { - return this.delegate.listResourceTemplates().block(); + return withProvidedContext(this.delegate.listResourceTemplates()).block(); + } /** @@ -304,7 +320,8 @@ public McpSchema.ListResourceTemplatesResult listResourceTemplates() { * @return The list of resource templates result. */ public McpSchema.ListResourceTemplatesResult listResourceTemplates(String cursor) { - return this.delegate.listResourceTemplates(cursor).block(); + return withProvidedContext(this.delegate.listResourceTemplates(cursor)).block(); + } /** @@ -317,7 +334,8 @@ public McpSchema.ListResourceTemplatesResult listResourceTemplates(String cursor * subscribe to. */ public void subscribeResource(McpSchema.SubscribeRequest subscribeRequest) { - this.delegate.subscribeResource(subscribeRequest).block(); + withProvidedContext(this.delegate.subscribeResource(subscribeRequest)).block(); + } /** @@ -326,7 +344,8 @@ public void subscribeResource(McpSchema.SubscribeRequest subscribeRequest) { * to unsubscribe from. */ public void unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) { - this.delegate.unsubscribeResource(unsubscribeRequest).block(); + withProvidedContext(this.delegate.unsubscribeResource(unsubscribeRequest)).block(); + } // -------------------------- @@ -338,7 +357,7 @@ public void unsubscribeResource(McpSchema.UnsubscribeRequest unsubscribeRequest) * @return The list of all prompts result. */ public ListPromptsResult listPrompts() { - return this.delegate.listPrompts().block(); + return withProvidedContext(this.delegate.listPrompts()).block(); } /** @@ -347,11 +366,12 @@ public ListPromptsResult listPrompts() { * @return The list of prompts result. */ public ListPromptsResult listPrompts(String cursor) { - return this.delegate.listPrompts(cursor).block(); + return withProvidedContext(this.delegate.listPrompts(cursor)).block(); + } public GetPromptResult getPrompt(GetPromptRequest getPromptRequest) { - return this.delegate.getPrompt(getPromptRequest).block(); + return withProvidedContext(this.delegate.getPrompt(getPromptRequest)).block(); } /** @@ -359,7 +379,8 @@ public GetPromptResult getPrompt(GetPromptRequest getPromptRequest) { * @param loggingLevel the min logging level */ public void setLoggingLevel(McpSchema.LoggingLevel loggingLevel) { - this.delegate.setLoggingLevel(loggingLevel).block(); + withProvidedContext(this.delegate.setLoggingLevel(loggingLevel)).block(); + } /** @@ -369,7 +390,18 @@ public void setLoggingLevel(McpSchema.LoggingLevel loggingLevel) { * @return the completion result containing suggested values. */ public McpSchema.CompleteResult completeCompletion(McpSchema.CompleteRequest completeRequest) { - return this.delegate.completeCompletion(completeRequest).block(); + return withProvidedContext(this.delegate.completeCompletion(completeRequest)).block(); + + } + + /** + * For a given action, on assembly, capture the "context" via the + * {@link #contextProvider} and store it in the Reactor context. + * @param action the action to perform + * @return the result of the action + */ + private Mono withProvidedContext(Mono action) { + return action.contextWrite(ctx -> ctx.put(McpTransportContext.KEY, this.contextProvider.get())); } } diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java index 74a3155e0..1fcbadff7 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java @@ -25,11 +25,12 @@ import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer; import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; import io.modelcontextprotocol.client.transport.ResponseSubscribers.ResponseEvent; +import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.spec.McpClientTransport; import io.modelcontextprotocol.spec.McpSchema; -import io.modelcontextprotocol.spec.ProtocolVersions; import io.modelcontextprotocol.spec.McpSchema.JSONRPCMessage; import io.modelcontextprotocol.spec.McpTransportException; +import io.modelcontextprotocol.spec.ProtocolVersions; import io.modelcontextprotocol.util.Assert; import io.modelcontextprotocol.util.Utils; import reactor.core.Disposable; @@ -410,14 +411,15 @@ public HttpClientSseClientTransport build() { public Mono connect(Function, Mono> handler) { var uri = Utils.resolveUri(this.baseUri, this.sseEndpoint); - return Mono.defer(() -> { + return Mono.deferContextual(ctx -> { var builder = requestBuilder.copy() .uri(uri) .header("Accept", "text/event-stream") .header("Cache-Control", "no-cache") .header(MCP_PROTOCOL_VERSION_HEADER_NAME, MCP_PROTOCOL_VERSION) .GET(); - return Mono.from(this.httpRequestCustomizer.customize(builder, "GET", uri, null)); + var transportContext = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY); + return Mono.from(this.httpRequestCustomizer.customize(builder, "GET", uri, null, transportContext)); }).flatMap(requestBuilder -> Mono.create(sink -> { Disposable connection = Flux.create(sseSink -> this.httpClient .sendAsync(requestBuilder.build(), @@ -538,13 +540,14 @@ private Mono serializeMessage(final JSONRPCMessage message) { private Mono> sendHttpPost(final String endpoint, final String body) { final URI requestUri = Utils.resolveUri(baseUri, endpoint); - return Mono.defer(() -> { + return Mono.deferContextual(ctx -> { var builder = this.requestBuilder.copy() .uri(requestUri) .header("Content-Type", "application/json") .header(MCP_PROTOCOL_VERSION_HEADER_NAME, MCP_PROTOCOL_VERSION) .POST(HttpRequest.BodyPublishers.ofString(body)); - return Mono.from(this.httpRequestCustomizer.customize(builder, "POST", requestUri, body)); + var transportContext = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY); + return Mono.from(this.httpRequestCustomizer.customize(builder, "POST", requestUri, body, transportContext)); }).flatMap(customizedBuilder -> { var request = customizedBuilder.build(); return Mono.fromFuture(httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())); diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java index a3815d0cf..63435035d 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java @@ -28,6 +28,7 @@ import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer; import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; import io.modelcontextprotocol.client.transport.ResponseSubscribers.ResponseEvent; +import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.spec.DefaultMcpTransportSession; import io.modelcontextprotocol.spec.DefaultMcpTransportStream; import io.modelcontextprotocol.spec.HttpHeaders; @@ -170,14 +171,15 @@ private DefaultMcpTransportSession createTransportSession() { private Publisher createDelete(String sessionId) { var uri = Utils.resolveUri(this.baseUri, this.endpoint); - return Mono.defer(() -> { + return Mono.deferContextual(ctx -> { var builder = this.requestBuilder.copy() .uri(uri) .header("Cache-Control", "no-cache") .header(HttpHeaders.MCP_SESSION_ID, sessionId) .header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION) .DELETE(); - return Mono.from(this.httpRequestCustomizer.customize(builder, "DELETE", uri, null)); + var transportContext = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY); + return Mono.from(this.httpRequestCustomizer.customize(builder, "DELETE", uri, null, transportContext)); }).flatMap(requestBuilder -> { var request = requestBuilder.build(); return Mono.fromFuture(() -> this.httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())); @@ -230,7 +232,7 @@ private Mono reconnect(McpTransportStream stream) { final McpTransportSession transportSession = this.activeSession.get(); var uri = Utils.resolveUri(this.baseUri, this.endpoint); - Disposable connection = Mono.defer(() -> { + Disposable connection = Mono.deferContextual(connectionCtx -> { HttpRequest.Builder requestBuilder = this.requestBuilder.copy(); if (transportSession != null && transportSession.sessionId().isPresent()) { @@ -247,7 +249,8 @@ private Mono reconnect(McpTransportStream stream) { .header("Cache-Control", "no-cache") .header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION) .GET(); - return Mono.from(this.httpRequestCustomizer.customize(builder, "GET", uri, null)); + var transportContext = connectionCtx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY); + return Mono.from(this.httpRequestCustomizer.customize(builder, "GET", uri, null, transportContext)); }) .flatMapMany( requestBuilder -> Flux.create( @@ -407,7 +410,7 @@ public Mono sendMessage(McpSchema.JSONRPCMessage sentMessage) { var uri = Utils.resolveUri(this.baseUri, this.endpoint); String jsonBody = this.toString(sentMessage); - Disposable connection = Mono.defer(() -> { + Disposable connection = Mono.deferContextual(ctx -> { HttpRequest.Builder requestBuilder = this.requestBuilder.copy(); if (transportSession != null && transportSession.sessionId().isPresent()) { @@ -421,7 +424,9 @@ public Mono sendMessage(McpSchema.JSONRPCMessage sentMessage) { .header("Cache-Control", "no-cache") .header(HttpHeaders.PROTOCOL_VERSION, MCP_PROTOCOL_VERSION) .POST(HttpRequest.BodyPublishers.ofString(jsonBody)); - return Mono.from(this.httpRequestCustomizer.customize(builder, "POST", uri, jsonBody)); + var transportContext = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY); + return Mono + .from(this.httpRequestCustomizer.customize(builder, "POST", uri, jsonBody, transportContext)); }).flatMapMany(requestBuilder -> Flux.create(responseEventSink -> { // Create the async request with proper body subscriber selection diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java index 22ba6a265..cf2bbbb13 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java @@ -3,11 +3,15 @@ */ package io.modelcontextprotocol.client.transport.customizer; -import io.modelcontextprotocol.util.Assert; import java.net.URI; import java.net.http.HttpRequest; import java.util.List; + import org.reactivestreams.Publisher; + +import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.util.Assert; + import reactor.core.publisher.Mono; /** @@ -27,10 +31,10 @@ public DelegatingMcpAsyncHttpRequestCustomizer(List customize(HttpRequest.Builder builder, String method, URI endpoint, - String body) { + String body, McpTransportContext context) { var result = Mono.just(builder); for (var customizer : this.customizers) { - result = result.flatMap(b -> Mono.from(customizer.customize(b, method, endpoint, body))); + result = result.flatMap(b -> Mono.from(customizer.customize(b, method, endpoint, body, context))); } return result; } diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java index 65649d916..df373f3e5 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java @@ -4,11 +4,13 @@ package io.modelcontextprotocol.client.transport.customizer; -import io.modelcontextprotocol.util.Assert; import java.net.URI; import java.net.http.HttpRequest; import java.util.List; +import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.util.Assert; + /** * Composable {@link McpSyncHttpRequestCustomizer} that applies multiple customizers, in * order. @@ -25,8 +27,9 @@ public DelegatingMcpSyncHttpRequestCustomizer(List } @Override - public void customize(HttpRequest.Builder builder, String method, URI endpoint, String body) { - this.delegates.forEach(delegate -> delegate.customize(builder, method, endpoint, body)); + public void customize(HttpRequest.Builder builder, String method, URI endpoint, String body, + McpTransportContext context) { + this.delegates.forEach(delegate -> delegate.customize(builder, method, endpoint, body, context)); } } diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpRequestCustomizer.java index 2f685c350..3b8355850 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpRequestCustomizer.java @@ -6,11 +6,14 @@ import java.net.URI; import java.net.http.HttpRequest; + import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; import reactor.util.annotation.Nullable; +import io.modelcontextprotocol.server.McpTransportContext; + /** * Customize {@link HttpRequest.Builder} before executing the request, in either SSE or * Streamable HTTP transport. @@ -22,7 +25,7 @@ public interface McpAsyncHttpRequestCustomizer { Publisher customize(HttpRequest.Builder builder, String method, URI endpoint, - @Nullable String body); + @Nullable String body, McpTransportContext context); McpAsyncHttpRequestCustomizer NOOP = new Noop(); @@ -33,8 +36,8 @@ Publisher customize(HttpRequest.Builder builder, String met * blocking implementation, consider using {@link Schedulers#boundedElastic()}. */ static McpAsyncHttpRequestCustomizer fromSync(McpSyncHttpRequestCustomizer customizer) { - return (builder, method, uri, body) -> Mono.fromSupplier(() -> { - customizer.customize(builder, method, uri, body); + return (builder, method, uri, body, context) -> Mono.fromSupplier(() -> { + customizer.customize(builder, method, uri, body, context); return builder; }); } @@ -43,7 +46,7 @@ class Noop implements McpAsyncHttpRequestCustomizer { @Override public Publisher customize(HttpRequest.Builder builder, String method, URI endpoint, - String body) { + String body, McpTransportContext context) { return Mono.just(builder); } diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpRequestCustomizer.java index 8d2c4a698..5f1ba60ab 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpRequestCustomizer.java @@ -6,16 +6,23 @@ import java.net.URI; import java.net.http.HttpRequest; + import reactor.util.annotation.Nullable; +import io.modelcontextprotocol.client.McpClient.SyncSpec; +import io.modelcontextprotocol.server.McpTransportContext; + /** * Customize {@link HttpRequest.Builder} before executing the request, either in SSE or - * Streamable HTTP transport. + * Streamable HTTP transport. Do not rely on thread-locals in this implementation, instead + * use {@link SyncSpec#transportContextProvider} to extract context, and then consume it + * through {@link McpTransportContext}. * * @author Daniel Garnier-Moiroux */ public interface McpSyncHttpRequestCustomizer { - void customize(HttpRequest.Builder builder, String method, URI endpoint, @Nullable String body); + void customize(HttpRequest.Builder builder, String method, URI endpoint, @Nullable String body, + McpTransportContext context); } diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/DefaultMcpTransportContext.java b/mcp/src/main/java/io/modelcontextprotocol/server/DefaultMcpTransportContext.java index 9e18e189d..036abc016 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/DefaultMcpTransportContext.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/DefaultMcpTransportContext.java @@ -5,45 +5,41 @@ package io.modelcontextprotocol.server; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; + +import io.modelcontextprotocol.util.Assert; /** - * Default implementation for {@link McpTransportContext} which uses a Thread-safe map. - * Objects of this kind are mutable. + * Default implementation for {@link McpTransportContext} which uses a map as storage. * * @author Dariusz Jędrzejczyk + * @author Daniel Garnier-Moiroux */ -public class DefaultMcpTransportContext implements McpTransportContext { - - private final Map storage; +class DefaultMcpTransportContext implements McpTransportContext { - /** - * Create an empty instance. - */ - public DefaultMcpTransportContext() { - this.storage = new ConcurrentHashMap<>(); - } + private final Map metadata; - DefaultMcpTransportContext(Map storage) { - this.storage = storage; + DefaultMcpTransportContext(Map metadata) { + Assert.notNull(metadata, "The metadata cannot be null"); + this.metadata = metadata; } @Override public Object get(String key) { - return this.storage.get(key); + return this.metadata.get(key); } @Override - public void put(String key, Object value) { - this.storage.put(key, value); + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + + DefaultMcpTransportContext that = (DefaultMcpTransportContext) o; + return this.metadata.equals(that.metadata); } - /** - * Allows copying the contents. - * @return new instance with the copy of the underlying map - */ - public McpTransportContext copy() { - return new DefaultMcpTransportContext(new ConcurrentHashMap<>(this.storage)); + @Override + public int hashCode() { + return this.metadata.hashCode(); } } diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContext.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContext.java index 1cd540f72..12f7b5b4b 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContext.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContext.java @@ -5,6 +5,7 @@ package io.modelcontextprotocol.server; import java.util.Collections; +import java.util.Map; /** * Context associated with the transport layer. It allows to add transport-level metadata @@ -26,6 +27,15 @@ public interface McpTransportContext { @SuppressWarnings("unchecked") McpTransportContext EMPTY = new DefaultMcpTransportContext(Collections.EMPTY_MAP); + /** + * Create an unmodifiable context containing the given metadata. + * @param metadata the transport metadata + * @return the context containing the metadata + */ + static McpTransportContext create(Map metadata) { + return new DefaultMcpTransportContext(metadata); + } + /** * Extract a value from the context. * @param key the key under the data is expected @@ -33,18 +43,4 @@ public interface McpTransportContext { */ Object get(String key); - /** - * Inserts a value for a given key. - * @param key a String representing the key - * @param value the value to store - */ - void put(String key, Object value); - - /** - * Copies the contents of the context to allow further modifications without affecting - * the initial object. - * @return a new instance with the underlying storage copied. - */ - McpTransportContext copy(); - } diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java index 97fcecf0d..b70562dad 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java @@ -15,14 +15,11 @@ public interface McpTransportContextExtractor { /** - * Given an empty context, provides the means to fill it with transport-specific - * metadata extracted from the request. + * Extract transport-specific metadata from the request into an McpTransportContext. * @param request the generic representation for the request in the context of a * specific transport implementation - * @param transportContext the mutable context which can be filled in with metadata - * @return the context filled in with metadata. It can be the same instance as - * provided or a new one. + * @return the context containing the metadata */ - McpTransportContext extract(T request, McpTransportContext transportContext); + McpTransportContext extract(T request); } diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java index 582120e3f..9235f47a0 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java @@ -16,7 +16,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.server.DefaultMcpTransportContext; import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.server.McpTransportContextExtractor; import io.modelcontextprotocol.spec.McpError; @@ -149,7 +148,7 @@ public HttpServletSseServerTransportProvider(ObjectMapper objectMapper, String m @Deprecated public HttpServletSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint, String sseEndpoint) { - this(objectMapper, baseUrl, messageEndpoint, sseEndpoint, null, (serverRequest, context) -> context); + this(objectMapper, baseUrl, messageEndpoint, sseEndpoint, null, (serverRequest) -> McpTransportContext.EMPTY); } /** @@ -169,7 +168,7 @@ public HttpServletSseServerTransportProvider(ObjectMapper objectMapper, String b public HttpServletSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint, String sseEndpoint, Duration keepAliveInterval) { this(objectMapper, baseUrl, messageEndpoint, sseEndpoint, keepAliveInterval, - (serverRequest, context) -> context); + (serverRequest) -> McpTransportContext.EMPTY); } /** @@ -371,8 +370,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) body.append(line); } - final McpTransportContext transportContext = this.contextExtractor.extract(request, - new DefaultMcpTransportContext()); + final McpTransportContext transportContext = this.contextExtractor.extract(request); McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(objectMapper, body.toString()); // Process the message through the session's handle method @@ -569,7 +567,8 @@ public static class Builder { private String sseEndpoint = DEFAULT_SSE_ENDPOINT; - private McpTransportContextExtractor contextExtractor = (serverRequest, context) -> context; + private McpTransportContextExtractor contextExtractor = ( + serverRequest) -> McpTransportContext.EMPTY; private Duration keepAliveInterval; diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java index 25b003564..8dd5da735 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java @@ -13,7 +13,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.server.DefaultMcpTransportContext; import io.modelcontextprotocol.server.McpStatelessServerHandler; import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.server.McpTransportContextExtractor; @@ -123,7 +122,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) return; } - McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext()); + McpTransportContext transportContext = this.contextExtractor.extract(request); String accept = request.getHeader(ACCEPT); if (accept == null || !(accept.contains(APPLICATION_JSON) && accept.contains(TEXT_EVENT_STREAM))) { @@ -241,7 +240,8 @@ public static class Builder { private String mcpEndpoint = "/mcp"; - private McpTransportContextExtractor contextExtractor = (serverRequest, context) -> context; + private McpTransportContextExtractor contextExtractor = ( + serverRequest) -> McpTransportContext.EMPTY; private Builder() { // used by a static method diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java index 8b95ec607..448fd3e4b 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.server.DefaultMcpTransportContext; import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.server.McpTransportContextExtractor; import io.modelcontextprotocol.spec.HttpHeaders; @@ -274,7 +273,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) logger.debug("Handling GET request for session: {}", sessionId); - McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext()); + McpTransportContext transportContext = this.contextExtractor.extract(request); try { response.setContentType(TEXT_EVENT_STREAM); @@ -383,7 +382,7 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) badRequestErrors.add("application/json required in Accept header"); } - McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext()); + McpTransportContext transportContext = this.contextExtractor.extract(request); try { BufferedReader reader = request.getReader(); @@ -541,7 +540,7 @@ protected void doDelete(HttpServletRequest request, HttpServletResponse response return; } - McpTransportContext transportContext = this.contextExtractor.extract(request, new DefaultMcpTransportContext()); + McpTransportContext transportContext = this.contextExtractor.extract(request); if (request.getHeader(HttpHeaders.MCP_SESSION_ID) == null) { this.responseError(response, HttpServletResponse.SC_BAD_REQUEST, @@ -769,7 +768,8 @@ public static class Builder { private boolean disallowDelete = false; - private McpTransportContextExtractor contextExtractor = (serverRequest, context) -> context; + private McpTransportContextExtractor contextExtractor = ( + serverRequest) -> McpTransportContext.EMPTY; private Duration keepAliveInterval; diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/HttpClientStreamableHttpSyncClientTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/HttpClientStreamableHttpSyncClientTests.java index 7f00de60e..c7f8542c3 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/HttpClientStreamableHttpSyncClientTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/HttpClientStreamableHttpSyncClientTests.java @@ -4,13 +4,27 @@ package io.modelcontextprotocol.client; +import java.net.URI; +import java.util.Map; + +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.mockito.ArgumentCaptor; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; +import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; +import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.spec.McpClientTransport; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + @Timeout(15) public class HttpClientStreamableHttpSyncClientTests extends AbstractMcpSyncClientTests { @@ -24,9 +38,11 @@ public class HttpClientStreamableHttpSyncClientTests extends AbstractMcpSyncClie .withExposedPorts(3001) .waitingFor(Wait.forHttp("/").forStatusCode(404)); + private final McpSyncHttpRequestCustomizer requestCustomizer = mock(McpSyncHttpRequestCustomizer.class); + @Override protected McpClientTransport createMcpTransport() { - return HttpClientStreamableHttpTransport.builder(host).build(); + return HttpClientStreamableHttpTransport.builder(host).httpRequestCustomizer(requestCustomizer).build(); } @Override @@ -41,4 +57,17 @@ public void onClose() { container.stop(); } + @Test + void customizesRequests() { + var mcpTransportContext = McpTransportContext.create(Map.of("some-key", "some-value")); + withClient(createMcpTransport(), syncSpec -> syncSpec.transportContextProvider(() -> mcpTransportContext), + mcpSyncClient -> { + mcpSyncClient.initialize(); + + verify(requestCustomizer, atLeastOnce()).customize(any(), eq("POST"), eq(URI.create(host + "/mcp")), + eq("{\"jsonrpc\":\"2.0\",\"method\":\"notifications/initialized\"}"), + eq(mcpTransportContext)); + }); + } + } diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/HttpSseMcpSyncClientTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/HttpSseMcpSyncClientTests.java index 8646c1b4c..eb0ebfd47 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/HttpSseMcpSyncClientTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/HttpSseMcpSyncClientTests.java @@ -4,12 +4,28 @@ package io.modelcontextprotocol.client; -import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; -import io.modelcontextprotocol.spec.McpClientTransport; +import java.net.URI; +import java.util.Map; + +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.mockito.ArgumentCaptor; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; +import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; +import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; +import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.spec.McpClientTransport; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + /** * Tests for the {@link McpSyncClient} with {@link HttpClientSseClientTransport}. * @@ -28,9 +44,11 @@ class HttpSseMcpSyncClientTests extends AbstractMcpSyncClientTests { .withExposedPorts(3001) .waitingFor(Wait.forHttp("/").forStatusCode(404)); + private final McpSyncHttpRequestCustomizer requestCustomizer = mock(McpSyncHttpRequestCustomizer.class); + @Override protected McpClientTransport createMcpTransport() { - return HttpClientSseClientTransport.builder(host).build(); + return HttpClientSseClientTransport.builder(host).httpRequestCustomizer(requestCustomizer).build(); } @Override @@ -45,4 +63,16 @@ protected void onClose() { container.stop(); } + @Test + void customizesRequests() { + var mcpTransportContext = McpTransportContext.create(Map.of("some-key", "some-value")); + withClient(createMcpTransport(), syncSpec -> syncSpec.transportContextProvider(() -> mcpTransportContext), + mcpSyncClient -> { + mcpSyncClient.initialize(); + + verify(requestCustomizer, atLeastOnce()).customize(any(), eq("GET"), eq(URI.create(host + "/sse")), + isNull(), eq(mcpTransportContext)); + }); + } + } diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java index f5a5ecb12..9e4660300 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java @@ -15,10 +15,13 @@ import java.util.function.Function; import com.fasterxml.jackson.databind.ObjectMapper; + import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer; import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; +import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeAll; @@ -64,6 +67,8 @@ class HttpClientSseClientTransportTests { private TestHttpClientSseClientTransport transport; + private final McpTransportContext context = McpTransportContext.create(Map.of("some-key", "some-value")); + // Test class to access protected methods static class TestHttpClientSseClientTransport extends HttpClientSseClientTransport { @@ -399,11 +404,14 @@ void testRequestCustomizer() { .build(); // Connect - StepVerifier.create(customizedTransport.connect(Function.identity())).verifyComplete(); + StepVerifier + .create(customizedTransport.connect(Function.identity()) + .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context))) + .verifyComplete(); // Verify the customizer was called verify(mockCustomizer).customize(any(), eq("GET"), - eq(UriComponentsBuilder.fromUriString(host).path("/sse").build().toUri()), isNull()); + eq(UriComponentsBuilder.fromUriString(host).path("/sse").build().toUri()), isNull(), eq(context)); clearInvocations(mockCustomizer); // Send test message @@ -411,12 +419,16 @@ void testRequestCustomizer() { Map.of("key", "value")); // Subscribe to messages and verify - StepVerifier.create(customizedTransport.sendMessage(testMessage)).verifyComplete(); + StepVerifier + .create(customizedTransport.sendMessage(testMessage) + .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context))) + .verifyComplete(); // Verify the customizer was called var uriArgumentCaptor = ArgumentCaptor.forClass(URI.class); verify(mockCustomizer).customize(any(), eq("POST"), uriArgumentCaptor.capture(), eq( - "{\"jsonrpc\":\"2.0\",\"method\":\"test-method\",\"id\":\"test-id\",\"params\":{\"key\":\"value\"}}")); + "{\"jsonrpc\":\"2.0\",\"method\":\"test-method\",\"id\":\"test-id\",\"params\":{\"key\":\"value\"}}"), + eq(context)); assertThat(uriArgumentCaptor.getValue().toString()).startsWith(host + "/message?sessionId="); // Clean up @@ -426,7 +438,7 @@ void testRequestCustomizer() { @Test void testAsyncRequestCustomizer() { var mockCustomizer = mock(McpAsyncHttpRequestCustomizer.class); - when(mockCustomizer.customize(any(), any(), any(), any())) + when(mockCustomizer.customize(any(), any(), any(), any(), any())) .thenAnswer(invocation -> Mono.just(invocation.getArguments()[0])); // Create a transport with the customizer @@ -435,11 +447,14 @@ void testAsyncRequestCustomizer() { .build(); // Connect - StepVerifier.create(customizedTransport.connect(Function.identity())).verifyComplete(); + StepVerifier + .create(customizedTransport.connect(Function.identity()) + .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context))) + .verifyComplete(); // Verify the customizer was called verify(mockCustomizer).customize(any(), eq("GET"), - eq(UriComponentsBuilder.fromUriString(host).path("/sse").build().toUri()), isNull()); + eq(UriComponentsBuilder.fromUriString(host).path("/sse").build().toUri()), isNull(), eq(context)); clearInvocations(mockCustomizer); // Send test message @@ -447,12 +462,16 @@ void testAsyncRequestCustomizer() { Map.of("key", "value")); // Subscribe to messages and verify - StepVerifier.create(customizedTransport.sendMessage(testMessage)).verifyComplete(); + StepVerifier + .create(customizedTransport.sendMessage(testMessage) + .contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context))) + .verifyComplete(); // Verify the customizer was called var uriArgumentCaptor = ArgumentCaptor.forClass(URI.class); verify(mockCustomizer).customize(any(), eq("POST"), uriArgumentCaptor.capture(), eq( - "{\"jsonrpc\":\"2.0\",\"method\":\"test-method\",\"id\":\"test-id\",\"params\":{\"key\":\"value\"}}")); + "{\"jsonrpc\":\"2.0\",\"method\":\"test-method\",\"id\":\"test-id\",\"params\":{\"key\":\"value\"}}"), + eq(context)); assertThat(uriArgumentCaptor.getValue().toString()).startsWith(host + "/message?sessionId="); // Clean up diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportEmptyJsonResponseTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportEmptyJsonResponseTest.java index e7d048ffe..c245c1461 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportEmptyJsonResponseTest.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportEmptyJsonResponseTest.java @@ -86,7 +86,8 @@ void testNotificationInitialized() throws URISyntaxException { // Verify the customizer was called verify(mockRequestCustomizer, atLeastOnce()).customize(any(), eq("POST"), eq(uri), eq( - "{\"jsonrpc\":\"2.0\",\"method\":\"initialize\",\"id\":\"test-id\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{\"roots\":{\"listChanged\":true}},\"clientInfo\":{\"name\":\"Spring AI MCP Client\",\"version\":\"0.3.1\"}}}")); + "{\"jsonrpc\":\"2.0\",\"method\":\"initialize\",\"id\":\"test-id\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{\"roots\":{\"listChanged\":true}},\"clientInfo\":{\"name\":\"Spring AI MCP Client\",\"version\":\"0.3.1\"}}}"), + any()); } diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java index f08518c93..900473128 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java @@ -6,18 +6,22 @@ import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer; import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; +import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.spec.McpSchema; import java.net.URI; import java.net.URISyntaxException; +import java.util.Map; import java.util.function.Consumer; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; @@ -34,6 +38,9 @@ class HttpClientStreamableHttpTransportTest { static String host = "http://localhost:3001"; + private McpTransportContext context = McpTransportContext + .create(Map.of("test-transport-context-key", "some-value")); + @SuppressWarnings("resource") static GenericContainer container = new GenericContainer<>("docker.io/tzolov/mcp-everything-server:v2") .withCommand("node dist/index.js streamableHttp") @@ -79,11 +86,14 @@ void testRequestCustomizer() throws URISyntaxException { var testMessage = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, McpSchema.METHOD_INITIALIZE, "test-id", initializeRequest); - StepVerifier.create(t.sendMessage(testMessage)).verifyComplete(); + StepVerifier + .create(t.sendMessage(testMessage).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context))) + .verifyComplete(); // Verify the customizer was called verify(mockRequestCustomizer, atLeastOnce()).customize(any(), eq("POST"), eq(uri), eq( - "{\"jsonrpc\":\"2.0\",\"method\":\"initialize\",\"id\":\"test-id\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{\"roots\":{\"listChanged\":true}},\"clientInfo\":{\"name\":\"Spring AI MCP Client\",\"version\":\"0.3.1\"}}}")); + "{\"jsonrpc\":\"2.0\",\"method\":\"initialize\",\"id\":\"test-id\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{\"roots\":{\"listChanged\":true}},\"clientInfo\":{\"name\":\"Spring AI MCP Client\",\"version\":\"0.3.1\"}}}"), + eq(context)); }); } @@ -91,7 +101,7 @@ void testRequestCustomizer() throws URISyntaxException { void testAsyncRequestCustomizer() throws URISyntaxException { var uri = new URI(host + "/mcp"); var mockRequestCustomizer = mock(McpAsyncHttpRequestCustomizer.class); - when(mockRequestCustomizer.customize(any(), any(), any(), any())) + when(mockRequestCustomizer.customize(any(), any(), any(), any(), any())) .thenAnswer(invocation -> Mono.just(invocation.getArguments()[0])); var transport = HttpClientStreamableHttpTransport.builder(host) @@ -106,11 +116,14 @@ void testAsyncRequestCustomizer() throws URISyntaxException { var testMessage = new McpSchema.JSONRPCRequest(McpSchema.JSONRPC_VERSION, McpSchema.METHOD_INITIALIZE, "test-id", initializeRequest); - StepVerifier.create(t.sendMessage(testMessage)).verifyComplete(); + StepVerifier + .create(t.sendMessage(testMessage).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context))) + .verifyComplete(); // Verify the customizer was called verify(mockRequestCustomizer, atLeastOnce()).customize(any(), eq("POST"), eq(uri), eq( - "{\"jsonrpc\":\"2.0\",\"method\":\"initialize\",\"id\":\"test-id\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{\"roots\":{\"listChanged\":true}},\"clientInfo\":{\"name\":\"Spring AI MCP Client\",\"version\":\"0.3.1\"}}}")); + "{\"jsonrpc\":\"2.0\",\"method\":\"initialize\",\"id\":\"test-id\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{\"roots\":{\"listChanged\":true}},\"clientInfo\":{\"name\":\"Spring AI MCP Client\",\"version\":\"0.3.1\"}}}"), + eq(context)); }); } diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java index f136cd65e..9e15de0d4 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java @@ -11,6 +11,8 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; +import io.modelcontextprotocol.server.McpTransportContext; + import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -31,25 +33,28 @@ class DelegatingMcpAsyncHttpRequestCustomizerTest { @Test void delegates() { var mockCustomizer = mock(McpAsyncHttpRequestCustomizer.class); - when(mockCustomizer.customize(any(), any(), any(), any())) + when(mockCustomizer.customize(any(), any(), any(), any(), any())) .thenAnswer(invocation -> Mono.just(invocation.getArguments()[0])); var customizer = new DelegatingMcpAsyncHttpRequestCustomizer(List.of(mockCustomizer)); - StepVerifier.create(customizer.customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}")) + var context = McpTransportContext.EMPTY; + StepVerifier + .create(customizer.customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}", context)) .expectNext(TEST_BUILDER) .verifyComplete(); - verify(mockCustomizer).customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}"); + verify(mockCustomizer).customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}", context); } @Test void delegatesInOrder() { var customizer = new DelegatingMcpAsyncHttpRequestCustomizer( - List.of((builder, method, uri, body) -> Mono.just(builder.copy().header("x-test", "one")), - (builder, method, uri, body) -> Mono.just(builder.copy().header("x-test", "two")))); + List.of((builder, method, uri, body, ctx) -> Mono.just(builder.copy().header("x-test", "one")), + (builder, method, uri, body, ctx) -> Mono.just(builder.copy().header("x-test", "two")))); var headers = Mono - .from(customizer.customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}")) + .from(customizer.customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}", + McpTransportContext.EMPTY)) .map(HttpRequest.Builder::build) .map(HttpRequest::headers) .flatMapIterable(h -> h.allValues("x-test")); diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java index 427472912..18348a663 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java @@ -10,6 +10,8 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import io.modelcontextprotocol.server.McpTransportContext; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.verify; @@ -30,19 +32,20 @@ void delegates() { var mockCustomizer = Mockito.mock(McpSyncHttpRequestCustomizer.class); var customizer = new DelegatingMcpSyncHttpRequestCustomizer(List.of(mockCustomizer)); - customizer.customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}"); + var context = McpTransportContext.EMPTY; + customizer.customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}", context); - verify(mockCustomizer).customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}"); + verify(mockCustomizer).customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}", context); } @Test void delegatesInOrder() { var testHeaderName = "x-test"; var customizer = new DelegatingMcpSyncHttpRequestCustomizer( - List.of((builder, method, uri, body) -> builder.header(testHeaderName, "one"), - (builder, method, uri, body) -> builder.header(testHeaderName, "two"))); + List.of((builder, method, uri, body, ctx) -> builder.header(testHeaderName, "one"), + (builder, method, uri, body, ctx) -> builder.header(testHeaderName, "two"))); - customizer.customize(TEST_BUILDER, "GET", TEST_URI, ""); + customizer.customize(TEST_BUILDER, "GET", TEST_URI, null, McpTransportContext.EMPTY); var request = TEST_BUILDER.build(); assertThat(request.headers().allValues(testHeaderName)).containsExactly("one", "two"); diff --git a/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java b/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java index 0f2991a9f..d58846971 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java @@ -7,6 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.time.Duration; +import java.util.Map; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; @@ -94,9 +95,7 @@ public void after() { protected void prepareClients(int port, String mcpEndpoint) { } - static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = (r, tc) -> { - tc.put("important", "value"); - return tc; - }; + static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = (r) -> McpTransportContext + .create(Map.of("important", "value")); } diff --git a/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java b/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java index 2e9b4cbad..1bc287025 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java @@ -7,6 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.time.Duration; +import java.util.Map; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; @@ -92,9 +93,7 @@ public void after() { protected void prepareClients(int port, String mcpEndpoint) { } - static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = (r, tc) -> { - tc.put("important", "value"); - return tc; - }; + static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = (r) -> McpTransportContext + .create(Map.of("important", "value")); } diff --git a/mcp/src/test/java/io/modelcontextprotocol/server/McpAsyncServerExchangeTests.java b/mcp/src/test/java/io/modelcontextprotocol/server/McpAsyncServerExchangeTests.java index 987c43663..3d61c2d3c 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/McpAsyncServerExchangeTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/McpAsyncServerExchangeTests.java @@ -54,7 +54,7 @@ void setUp() { clientInfo = new McpSchema.Implementation("test-client", "1.0.0"); exchange = new McpAsyncServerExchange("testSessionId", mockSession, clientCapabilities, clientInfo, - new DefaultMcpTransportContext()); + McpTransportContext.EMPTY); } @Test From b7140f7a13f22397b779bf615068f2d13fed8246 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Fri, 29 Aug 2025 16:19:46 +0200 Subject: [PATCH 2/4] Move McpTransportContext to common package, make impl immutable Signed-off-by: Daniel Garnier-Moiroux --- .../transport/WebFluxSseServerTransportProvider.java | 8 ++++---- .../server/transport/WebFluxStatelessServerTransport.java | 6 +++--- .../WebFluxStreamableServerTransportProvider.java | 6 +++--- .../modelcontextprotocol/WebFluxSseIntegrationTests.java | 8 ++++---- .../WebFluxStreamableIntegrationTests.java | 8 ++++---- .../transport/WebMvcSseServerTransportProvider.java | 8 ++++---- .../server/transport/WebMvcStatelessServerTransport.java | 6 +++--- .../WebMvcStreamableServerTransportProvider.java | 6 +++--- .../server/WebMvcSseIntegrationTests.java | 8 ++++---- .../server/WebMvcStreamableIntegrationTests.java | 8 ++++---- .../AbstractMcpClientServerIntegrationTests.java | 2 +- .../java/io/modelcontextprotocol/client/McpClient.java | 2 +- .../io/modelcontextprotocol/client/McpSyncClient.java | 2 +- .../client/transport/HttpClientSseClientTransport.java | 2 +- .../transport/HttpClientStreamableHttpTransport.java | 2 +- .../DelegatingMcpAsyncHttpRequestCustomizer.java | 2 +- .../DelegatingMcpSyncHttpRequestCustomizer.java | 2 +- .../customizer/McpAsyncHttpRequestCustomizer.java | 2 +- .../customizer/McpSyncHttpRequestCustomizer.java | 2 +- .../{server => common}/DefaultMcpTransportContext.java | 2 +- .../{server => common}/McpTransportContext.java | 2 +- .../server/DefaultMcpStatelessServerHandler.java | 1 + .../server/McpAsyncServerExchange.java | 1 + .../java/io/modelcontextprotocol/server/McpServer.java | 1 + .../server/McpStatelessAsyncServer.java | 1 + .../server/McpStatelessNotificationHandler.java | 1 + .../server/McpStatelessRequestHandler.java | 1 + .../server/McpStatelessServerFeatures.java | 1 + .../server/McpStatelessServerHandler.java | 1 + .../server/McpSyncServerExchange.java | 1 + .../server/McpTransportContextExtractor.java | 2 ++ .../transport/HttpServletSseServerTransportProvider.java | 2 +- .../transport/HttpServletStatelessServerTransport.java | 2 +- .../HttpServletStreamableServerTransportProvider.java | 2 +- .../io/modelcontextprotocol/spec/McpServerSession.java | 2 +- .../spec/McpStreamableServerSession.java | 2 +- .../client/HttpClientStreamableHttpSyncClientTests.java | 3 +-- .../client/HttpSseMcpSyncClientTests.java | 3 +-- .../transport/HttpClientSseClientTransportTests.java | 2 +- .../transport/HttpClientStreamableHttpTransportTest.java | 3 +-- .../DelegatingMcpAsyncHttpRequestCustomizerTest.java | 2 +- .../DelegatingMcpSyncHttpRequestCustomizerTest.java | 2 +- .../server/AbstractMcpClientServerIntegrationTests.java | 1 + .../server/HttpServletSseIntegrationTests.java | 1 + .../server/HttpServletStatelessIntegrationTests.java | 1 + .../server/HttpServletStreamableIntegrationTests.java | 1 + .../server/McpAsyncServerExchangeTests.java | 1 + 47 files changed, 74 insertions(+), 61 deletions(-) rename mcp/src/main/java/io/modelcontextprotocol/{server => common}/DefaultMcpTransportContext.java (95%) rename mcp/src/main/java/io/modelcontextprotocol/{server => common}/McpTransportContext.java (96%) diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java index 252c78dc3..f64346265 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java @@ -12,8 +12,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.server.DefaultMcpTransportContext; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpTransportContextExtractor; import io.modelcontextprotocol.spec.McpError; import io.modelcontextprotocol.spec.McpSchema; @@ -201,7 +200,7 @@ public WebFluxSseServerTransportProvider(ObjectMapper objectMapper, String baseU public WebFluxSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint, String sseEndpoint, Duration keepAliveInterval) { this(objectMapper, baseUrl, messageEndpoint, sseEndpoint, keepAliveInterval, - (serverRequest, context) -> context); + (serverRequest) -> McpTransportContext.EMPTY); } /** @@ -491,7 +490,8 @@ public static class Builder { private Duration keepAliveInterval; - private McpTransportContextExtractor contextExtractor = (serverRequest, context) -> context; + private McpTransportContextExtractor contextExtractor = ( + serverRequest) -> McpTransportContext.EMPTY; /** * Sets the ObjectMapper to use for JSON serialization/deserialization of MCP diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java index 423310f63..1f3d4c3bf 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java @@ -5,13 +5,12 @@ package io.modelcontextprotocol.server.transport; import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpStatelessServerHandler; -import io.modelcontextprotocol.server.DefaultMcpTransportContext; import io.modelcontextprotocol.server.McpTransportContextExtractor; import io.modelcontextprotocol.spec.McpError; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpStatelessServerTransport; -import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.util.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -151,7 +150,8 @@ public static class Builder { private String mcpEndpoint = "/mcp"; - private McpTransportContextExtractor contextExtractor = (serverRequest, context) -> context; + private McpTransportContextExtractor contextExtractor = ( + serverRequest) -> McpTransportContext.EMPTY; private Builder() { // used by a static method diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java index 3bff1fea3..44d89eaeb 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.server.DefaultMcpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpTransportContextExtractor; import io.modelcontextprotocol.spec.HttpHeaders; import io.modelcontextprotocol.spec.McpError; @@ -15,7 +15,6 @@ import io.modelcontextprotocol.spec.McpStreamableServerTransport; import io.modelcontextprotocol.spec.McpStreamableServerTransportProvider; import io.modelcontextprotocol.spec.ProtocolVersions; -import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.util.Assert; import io.modelcontextprotocol.util.KeepAliveScheduler; @@ -402,7 +401,8 @@ public static class Builder { private String mcpEndpoint = "/mcp"; - private McpTransportContextExtractor contextExtractor = (serverRequest, context) -> context; + private McpTransportContextExtractor contextExtractor = ( + serverRequest) -> McpTransportContext.EMPTY; private boolean disallowDelete; diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java index c8dc6e90b..f8f0f7a3a 100644 --- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java +++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java @@ -5,6 +5,7 @@ package io.modelcontextprotocol; import java.time.Duration; +import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -20,6 +21,7 @@ import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpServer; import io.modelcontextprotocol.server.McpServer.AsyncSpecification; import io.modelcontextprotocol.server.McpServer.SingleSessionSyncSpecification; @@ -42,10 +44,8 @@ class WebFluxSseIntegrationTests extends AbstractMcpClientServerIntegrationTests private WebFluxSseServerTransportProvider mcpServerTransportProvider; - static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = (r, tc) -> { - tc.put("important", "value"); - return tc; - }; + static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = (r) -> McpTransportContext + .create(Map.of("important", "value")); @Override protected void prepareClients(int port, String mcpEndpoint) { diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStreamableIntegrationTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStreamableIntegrationTests.java index a7aac0f1e..933ddf39d 100644 --- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStreamableIntegrationTests.java +++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxStreamableIntegrationTests.java @@ -5,6 +5,7 @@ package io.modelcontextprotocol; import java.time.Duration; +import java.util.Map; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -20,6 +21,7 @@ import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpServer; import io.modelcontextprotocol.server.McpServer.AsyncSpecification; import io.modelcontextprotocol.server.McpServer.SyncSpecification; @@ -40,10 +42,8 @@ class WebFluxStreamableIntegrationTests extends AbstractMcpClientServerIntegrati private WebFluxStreamableServerTransportProvider mcpStreamableServerTransportProvider; - static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = (r, tc) -> { - tc.put("important", "value"); - return tc; - }; + static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = (r) -> McpTransportContext + .create(Map.of("important", "value")); @Override protected void prepareClients(int port, String mcpEndpoint) { diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java index c9230d3c5..85373b6fe 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java @@ -14,8 +14,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.server.DefaultMcpTransportContext; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpTransportContextExtractor; import io.modelcontextprotocol.spec.McpError; import io.modelcontextprotocol.spec.McpSchema; @@ -192,7 +191,7 @@ public WebMvcSseServerTransportProvider(ObjectMapper objectMapper, String baseUr public WebMvcSseServerTransportProvider(ObjectMapper objectMapper, String baseUrl, String messageEndpoint, String sseEndpoint, Duration keepAliveInterval) { this(objectMapper, baseUrl, messageEndpoint, sseEndpoint, keepAliveInterval, - (serverRequest, context) -> context); + (serverRequest) -> McpTransportContext.EMPTY); } /** @@ -552,7 +551,8 @@ public static class Builder { private Duration keepAliveInterval; - private McpTransportContextExtractor contextExtractor = (serverRequest, context) -> context; + private McpTransportContextExtractor contextExtractor = ( + serverRequest) -> McpTransportContext.EMPTY; /** * Sets the JSON object mapper to use for message serialization/deserialization. diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java index 400b2d824..fc2da0439 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java @@ -5,13 +5,12 @@ package io.modelcontextprotocol.server.transport; import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpStatelessServerHandler; -import io.modelcontextprotocol.server.DefaultMcpTransportContext; import io.modelcontextprotocol.server.McpTransportContextExtractor; import io.modelcontextprotocol.spec.McpError; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpStatelessServerTransport; -import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.util.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -176,7 +175,8 @@ public static class Builder { private String mcpEndpoint = "/mcp"; - private McpTransportContextExtractor contextExtractor = (serverRequest, context) -> context; + private McpTransportContextExtractor contextExtractor = ( + serverRequest) -> McpTransportContext.EMPTY; private Builder() { // used by a static method diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java index 2a69a10c2..3cc104dd4 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java @@ -23,8 +23,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.server.DefaultMcpTransportContext; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpTransportContextExtractor; import io.modelcontextprotocol.spec.HttpHeaders; import io.modelcontextprotocol.spec.McpError; @@ -604,7 +603,8 @@ public static class Builder { private boolean disallowDelete = false; - private McpTransportContextExtractor contextExtractor = (serverRequest, context) -> context; + private McpTransportContextExtractor contextExtractor = ( + serverRequest) -> McpTransportContext.EMPTY; private Duration keepAliveInterval; diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java index 8cb2973ed..18a9d0063 100644 --- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java +++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java @@ -6,6 +6,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.time.Duration; +import java.util.Map; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; @@ -26,6 +27,7 @@ import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpServer.AsyncSpecification; import io.modelcontextprotocol.server.McpServer.SingleSessionSyncSpecification; import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider; @@ -40,10 +42,8 @@ class WebMvcSseIntegrationTests extends AbstractMcpClientServerIntegrationTests private WebMvcSseServerTransportProvider mcpServerTransportProvider; - static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = (r, tc) -> { - tc.put("important", "value"); - return tc; - }; + static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = r -> McpTransportContext + .create(Map.of("important", "value")); @Override protected void prepareClients(int port, String mcpEndpoint) { diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcStreamableIntegrationTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcStreamableIntegrationTests.java index 2f4c651fd..3f1716f89 100644 --- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcStreamableIntegrationTests.java +++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcStreamableIntegrationTests.java @@ -6,6 +6,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.time.Duration; +import java.util.Map; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; @@ -26,6 +27,7 @@ import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; import io.modelcontextprotocol.client.transport.WebClientStreamableHttpTransport; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpServer.AsyncSpecification; import io.modelcontextprotocol.server.McpServer.SyncSpecification; import io.modelcontextprotocol.server.transport.WebMvcStreamableServerTransportProvider; @@ -40,10 +42,8 @@ class WebMvcStreamableIntegrationTests extends AbstractMcpClientServerIntegratio private WebMvcStreamableServerTransportProvider mcpServerTransportProvider; - static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = (r, tc) -> { - tc.put("important", "value"); - return tc; - }; + static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = r -> McpTransportContext + .create(Map.of("important", "value")); @Configuration @EnableWebMvc diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java index 5246c1e2d..300f0b534 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java @@ -33,11 +33,11 @@ import org.junit.jupiter.params.provider.ValueSource; import io.modelcontextprotocol.client.McpClient; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpServer; import io.modelcontextprotocol.server.McpServerFeatures; import io.modelcontextprotocol.server.McpSyncServer; import io.modelcontextprotocol.server.McpSyncServerExchange; -import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.spec.McpError; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.CallToolResult; diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/McpClient.java b/mcp/src/main/java/io/modelcontextprotocol/client/McpClient.java index ed7573dc1..534879f2c 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/McpClient.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/McpClient.java @@ -13,7 +13,7 @@ import java.util.function.Function; import java.util.function.Supplier; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpClientTransport; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.ClientCapabilities; diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java b/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java index e096af347..7fdaa8941 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/McpSyncClient.java @@ -10,7 +10,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.ClientCapabilities; import io.modelcontextprotocol.spec.McpSchema.GetPromptRequest; diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java index 1fcbadff7..64a6d6319 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java @@ -25,7 +25,7 @@ import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer; import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; import io.modelcontextprotocol.client.transport.ResponseSubscribers.ResponseEvent; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpClientTransport; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.JSONRPCMessage; diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java index 63435035d..8d65dd706 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java @@ -28,7 +28,7 @@ import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer; import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; import io.modelcontextprotocol.client.transport.ResponseSubscribers.ResponseEvent; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.DefaultMcpTransportSession; import io.modelcontextprotocol.spec.DefaultMcpTransportStream; import io.modelcontextprotocol.spec.HttpHeaders; diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java index cf2bbbb13..b4476a233 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java @@ -9,7 +9,7 @@ import org.reactivestreams.Publisher; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.util.Assert; import reactor.core.publisher.Mono; diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java index df373f3e5..69b262253 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java @@ -8,7 +8,7 @@ import java.net.http.HttpRequest; import java.util.List; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.util.Assert; /** diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpRequestCustomizer.java index 3b8355850..4a10b490d 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpRequestCustomizer.java @@ -12,7 +12,7 @@ import reactor.core.scheduler.Schedulers; import reactor.util.annotation.Nullable; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; /** * Customize {@link HttpRequest.Builder} before executing the request, in either SSE or diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpRequestCustomizer.java index 5f1ba60ab..87695c8ab 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpRequestCustomizer.java @@ -10,7 +10,7 @@ import reactor.util.annotation.Nullable; import io.modelcontextprotocol.client.McpClient.SyncSpec; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; /** * Customize {@link HttpRequest.Builder} before executing the request, either in SSE or diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/DefaultMcpTransportContext.java b/mcp/src/main/java/io/modelcontextprotocol/common/DefaultMcpTransportContext.java similarity index 95% rename from mcp/src/main/java/io/modelcontextprotocol/server/DefaultMcpTransportContext.java rename to mcp/src/main/java/io/modelcontextprotocol/common/DefaultMcpTransportContext.java index 036abc016..cde637b15 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/DefaultMcpTransportContext.java +++ b/mcp/src/main/java/io/modelcontextprotocol/common/DefaultMcpTransportContext.java @@ -2,7 +2,7 @@ * Copyright 2024-2025 the original author or authors. */ -package io.modelcontextprotocol.server; +package io.modelcontextprotocol.common; import java.util.Map; diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContext.java b/mcp/src/main/java/io/modelcontextprotocol/common/McpTransportContext.java similarity index 96% rename from mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContext.java rename to mcp/src/main/java/io/modelcontextprotocol/common/McpTransportContext.java index 12f7b5b4b..46a2ccf84 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContext.java +++ b/mcp/src/main/java/io/modelcontextprotocol/common/McpTransportContext.java @@ -2,7 +2,7 @@ * Copyright 2024-2025 the original author or authors. */ -package io.modelcontextprotocol.server; +package io.modelcontextprotocol.common; import java.util.Collections; import java.util.Map; diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/DefaultMcpStatelessServerHandler.java b/mcp/src/main/java/io/modelcontextprotocol/server/DefaultMcpStatelessServerHandler.java index 2df3514b6..d1b55f594 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/DefaultMcpStatelessServerHandler.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/DefaultMcpStatelessServerHandler.java @@ -4,6 +4,7 @@ package io.modelcontextprotocol.server; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpError; import io.modelcontextprotocol.spec.McpSchema; import org.slf4j.Logger; diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServerExchange.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServerExchange.java index 61d60bacc..1f0aebf02 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServerExchange.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServerExchange.java @@ -4,6 +4,7 @@ package io.modelcontextprotocol.server; +import io.modelcontextprotocol.common.McpTransportContext; import java.util.ArrayList; import java.util.Collections; diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpServer.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpServer.java index f5dfffffb..76a0de76b 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/McpServer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpServer.java @@ -4,6 +4,7 @@ package io.modelcontextprotocol.server; +import io.modelcontextprotocol.common.McpTransportContext; import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java index 41e0e9588..451771295 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.JsonSchemaValidator; import io.modelcontextprotocol.spec.McpError; import io.modelcontextprotocol.spec.McpSchema; diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessNotificationHandler.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessNotificationHandler.java index 6db79a62c..a2fabb283 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessNotificationHandler.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessNotificationHandler.java @@ -4,6 +4,7 @@ package io.modelcontextprotocol.server; +import io.modelcontextprotocol.common.McpTransportContext; import reactor.core.publisher.Mono; /** diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessRequestHandler.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessRequestHandler.java index e5c9e7c09..37cd3c096 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessRequestHandler.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessRequestHandler.java @@ -4,6 +4,7 @@ package io.modelcontextprotocol.server; +import io.modelcontextprotocol.common.McpTransportContext; import reactor.core.publisher.Mono; /** diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessServerFeatures.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessServerFeatures.java index 60c1dbb65..df44d50c4 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessServerFeatures.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessServerFeatures.java @@ -4,6 +4,7 @@ package io.modelcontextprotocol.server; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.CallToolRequest; import io.modelcontextprotocol.util.Assert; diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessServerHandler.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessServerHandler.java index 7c4e23cfc..cbae58bfd 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessServerHandler.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessServerHandler.java @@ -4,6 +4,7 @@ package io.modelcontextprotocol.server; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpSchema; import reactor.core.publisher.Mono; diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpSyncServerExchange.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpSyncServerExchange.java index 5f22df5e9..0b9115b79 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/McpSyncServerExchange.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpSyncServerExchange.java @@ -4,6 +4,7 @@ package io.modelcontextprotocol.server; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification; diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java index b70562dad..ea9f05a4f 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java @@ -4,6 +4,8 @@ package io.modelcontextprotocol.server; +import io.modelcontextprotocol.common.McpTransportContext; + /** * The contract for extracting metadata from a generic transport request of type * {@link T}. diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java index 9235f47a0..bbc1edf24 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java @@ -16,7 +16,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpTransportContextExtractor; import io.modelcontextprotocol.spec.McpError; import io.modelcontextprotocol.spec.McpSchema; diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java index 8dd5da735..9a8f6cbb9 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java @@ -13,8 +13,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpStatelessServerHandler; -import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.server.McpTransportContextExtractor; import io.modelcontextprotocol.spec.McpError; import io.modelcontextprotocol.spec.McpSchema; diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java index 448fd3e4b..3cb8d7b15 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java +++ b/mcp/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java @@ -19,7 +19,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpTransportContextExtractor; import io.modelcontextprotocol.spec.HttpHeaders; import io.modelcontextprotocol.spec.McpError; diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java index e562ca012..e6a0c8b32 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java @@ -12,11 +12,11 @@ import java.util.concurrent.atomic.AtomicReference; import com.fasterxml.jackson.core.type.TypeReference; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpAsyncServerExchange; import io.modelcontextprotocol.server.McpInitRequestHandler; import io.modelcontextprotocol.server.McpNotificationHandler; import io.modelcontextprotocol.server.McpRequestHandler; -import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.util.Assert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java index ef7967c1e..af29ce0ad 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java +++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java @@ -17,10 +17,10 @@ import com.fasterxml.jackson.core.type.TypeReference; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpAsyncServerExchange; import io.modelcontextprotocol.server.McpNotificationHandler; import io.modelcontextprotocol.server.McpRequestHandler; -import io.modelcontextprotocol.server.McpTransportContext; import io.modelcontextprotocol.util.Assert; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/HttpClientStreamableHttpSyncClientTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/HttpClientStreamableHttpSyncClientTests.java index c7f8542c3..43aa261b2 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/HttpClientStreamableHttpSyncClientTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/HttpClientStreamableHttpSyncClientTests.java @@ -9,13 +9,12 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import org.mockito.ArgumentCaptor; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpClientTransport; import static org.assertj.core.api.Assertions.assertThat; diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/HttpSseMcpSyncClientTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/HttpSseMcpSyncClientTests.java index eb0ebfd47..1ea165f3b 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/HttpSseMcpSyncClientTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/HttpSseMcpSyncClientTests.java @@ -9,13 +9,12 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import org.mockito.ArgumentCaptor; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpClientTransport; import static org.assertj.core.api.Assertions.assertThat; diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java index 9e4660300..7e1cb2ed8 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java @@ -18,7 +18,7 @@ import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer; import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest; diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java index 900473128..b3fcab394 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java @@ -6,7 +6,7 @@ import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer; import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpSchema; import java.net.URI; import java.net.URISyntaxException; @@ -15,7 +15,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; import reactor.core.publisher.Mono; diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java index 9e15de0d4..683a6d662 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java @@ -11,7 +11,7 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java index 18348a663..42c379057 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java @@ -10,7 +10,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; -import io.modelcontextprotocol.server.McpTransportContext; +import io.modelcontextprotocol.common.McpTransportContext; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/mcp/src/test/java/io/modelcontextprotocol/server/AbstractMcpClientServerIntegrationTests.java b/mcp/src/test/java/io/modelcontextprotocol/server/AbstractMcpClientServerIntegrationTests.java index acaf0c8a9..f99edaf95 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/AbstractMcpClientServerIntegrationTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/AbstractMcpClientServerIntegrationTests.java @@ -12,6 +12,7 @@ import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.mock; +import io.modelcontextprotocol.common.McpTransportContext; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; diff --git a/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java b/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java index d58846971..c893acf9a 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java @@ -6,6 +6,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.modelcontextprotocol.common.McpTransportContext; import java.time.Duration; import java.util.Map; diff --git a/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java b/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java index a8951e6dc..6a6f6f8b9 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport; import io.modelcontextprotocol.server.transport.TomcatTestUtil; import io.modelcontextprotocol.spec.HttpHeaders; diff --git a/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java b/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java index 1bc287025..6899ba474 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java @@ -6,6 +6,7 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.modelcontextprotocol.common.McpTransportContext; import java.time.Duration; import java.util.Map; diff --git a/mcp/src/test/java/io/modelcontextprotocol/server/McpAsyncServerExchangeTests.java b/mcp/src/test/java/io/modelcontextprotocol/server/McpAsyncServerExchangeTests.java index 3d61c2d3c..104349116 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/McpAsyncServerExchangeTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/McpAsyncServerExchangeTests.java @@ -4,6 +4,7 @@ package io.modelcontextprotocol.server; +import io.modelcontextprotocol.common.McpTransportContext; import java.util.ArrayList; import java.util.Arrays; import java.util.List; From dee0c1a3d03e1891c19c2a5520b6744aacb1a9a0 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Fri, 29 Aug 2025 20:34:11 +0200 Subject: [PATCH 3/4] Rename *HttpRequestCustomizer -> *HttpClientRequestCustomizer Signed-off-by: Daniel Garnier-Moiroux --- .../HttpClientSseClientTransport.java | 27 ++++++++++--------- .../HttpClientStreamableHttpTransport.java | 25 ++++++++--------- ...gMcpAsyncHttpClientRequestCustomizer.java} | 10 +++---- ...ngMcpSyncHttpClientRequestCustomizer.java} | 10 +++---- ... McpAsyncHttpClientRequestCustomizer.java} | 8 +++--- ...> McpSyncHttpClientRequestCustomizer.java} | 2 +- ...tpClientStreamableHttpSyncClientTests.java | 4 +-- .../client/HttpSseMcpSyncClientTests.java | 4 +-- .../HttpClientSseClientTransportTests.java | 10 +++---- ...bleHttpTransportEmptyJsonResponseTest.java | 4 +-- ...HttpClientStreamableHttpTransportTest.java | 8 +++--- ...AsyncHttpClientRequestCustomizerTest.java} | 12 ++++----- ...pSyncHttpClientRequestCustomizerTest.java} | 12 ++++----- 13 files changed, 69 insertions(+), 67 deletions(-) rename mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/{DelegatingMcpAsyncHttpRequestCustomizer.java => DelegatingMcpAsyncHttpClientRequestCustomizer.java} (69%) rename mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/{DelegatingMcpSyncHttpRequestCustomizer.java => DelegatingMcpSyncHttpClientRequestCustomizer.java} (64%) rename mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/{McpAsyncHttpRequestCustomizer.java => McpAsyncHttpClientRequestCustomizer.java} (83%) rename mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/{McpSyncHttpRequestCustomizer.java => McpSyncHttpClientRequestCustomizer.java} (93%) rename mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/{DelegatingMcpAsyncHttpRequestCustomizerTest.java => DelegatingMcpAsyncHttpClientRequestCustomizerTest.java} (82%) rename mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/{DelegatingMcpSyncHttpRequestCustomizerTest.java => DelegatingMcpSyncHttpClientRequestCustomizerTest.java} (78%) diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java index 64a6d6319..c2c74dcae 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java @@ -22,8 +22,8 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer; -import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; +import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpClientRequestCustomizer; +import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer; import io.modelcontextprotocol.client.transport.ResponseSubscribers.ResponseEvent; import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpClientTransport; @@ -115,7 +115,7 @@ public class HttpClientSseClientTransport implements McpClientTransport { /** * Customizer to modify requests before they are executed. */ - private final McpAsyncHttpRequestCustomizer httpRequestCustomizer; + private final McpAsyncHttpClientRequestCustomizer httpRequestCustomizer; /** * Creates a new transport instance with default HTTP client and object mapper. @@ -189,7 +189,7 @@ public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, HttpReques @Deprecated(forRemoval = true) HttpClientSseClientTransport(HttpClient httpClient, HttpRequest.Builder requestBuilder, String baseUri, String sseEndpoint, ObjectMapper objectMapper) { - this(httpClient, requestBuilder, baseUri, sseEndpoint, objectMapper, McpAsyncHttpRequestCustomizer.NOOP); + this(httpClient, requestBuilder, baseUri, sseEndpoint, objectMapper, McpAsyncHttpClientRequestCustomizer.NOOP); } /** @@ -205,7 +205,7 @@ public HttpClientSseClientTransport(HttpClient.Builder clientBuilder, HttpReques * @throws IllegalArgumentException if objectMapper, clientBuilder, or headers is null */ HttpClientSseClientTransport(HttpClient httpClient, HttpRequest.Builder requestBuilder, String baseUri, - String sseEndpoint, ObjectMapper objectMapper, McpAsyncHttpRequestCustomizer httpRequestCustomizer) { + String sseEndpoint, ObjectMapper objectMapper, McpAsyncHttpClientRequestCustomizer httpRequestCustomizer) { Assert.notNull(objectMapper, "ObjectMapper must not be null"); Assert.hasText(baseUri, "baseUri must not be empty"); Assert.hasText(sseEndpoint, "sseEndpoint must not be empty"); @@ -249,7 +249,7 @@ public static class Builder { private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(); - private McpAsyncHttpRequestCustomizer httpRequestCustomizer = McpAsyncHttpRequestCustomizer.NOOP; + private McpAsyncHttpClientRequestCustomizer httpRequestCustomizer = McpAsyncHttpClientRequestCustomizer.NOOP; private Duration connectTimeout = Duration.ofSeconds(10); @@ -355,16 +355,17 @@ public Builder objectMapper(ObjectMapper objectMapper) { * executing them. *

* This overrides the customizer from - * {@link #asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer)}. + * {@link #asyncHttpRequestCustomizer(McpAsyncHttpClientRequestCustomizer)}. *

- * Do NOT use a blocking {@link McpSyncHttpRequestCustomizer} in a non-blocking - * context. Use {@link #asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer)} + * Do NOT use a blocking {@link McpSyncHttpClientRequestCustomizer} in a + * non-blocking context. Use + * {@link #asyncHttpRequestCustomizer(McpAsyncHttpClientRequestCustomizer)} * instead. * @param syncHttpRequestCustomizer the request customizer * @return this builder */ - public Builder httpRequestCustomizer(McpSyncHttpRequestCustomizer syncHttpRequestCustomizer) { - this.httpRequestCustomizer = McpAsyncHttpRequestCustomizer.fromSync(syncHttpRequestCustomizer); + public Builder httpRequestCustomizer(McpSyncHttpClientRequestCustomizer syncHttpRequestCustomizer) { + this.httpRequestCustomizer = McpAsyncHttpClientRequestCustomizer.fromSync(syncHttpRequestCustomizer); return this; } @@ -373,13 +374,13 @@ public Builder httpRequestCustomizer(McpSyncHttpRequestCustomizer syncHttpReques * executing them. *

* This overrides the customizer from - * {@link #httpRequestCustomizer(McpSyncHttpRequestCustomizer)}. + * {@link #httpRequestCustomizer(McpSyncHttpClientRequestCustomizer)}. *

* Do NOT use a blocking implementation in a non-blocking context. * @param asyncHttpRequestCustomizer the request customizer * @return this builder */ - public Builder asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer asyncHttpRequestCustomizer) { + public Builder asyncHttpRequestCustomizer(McpAsyncHttpClientRequestCustomizer asyncHttpRequestCustomizer) { this.httpRequestCustomizer = asyncHttpRequestCustomizer; return this; } diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java index 8d65dd706..4b1ff0d8b 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java @@ -25,8 +25,8 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer; -import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; +import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpClientRequestCustomizer; +import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer; import io.modelcontextprotocol.client.transport.ResponseSubscribers.ResponseEvent; import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.DefaultMcpTransportSession; @@ -116,7 +116,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport { private final boolean resumableStreams; - private final McpAsyncHttpRequestCustomizer httpRequestCustomizer; + private final McpAsyncHttpClientRequestCustomizer httpRequestCustomizer; private final AtomicReference activeSession = new AtomicReference<>(); @@ -126,7 +126,7 @@ public class HttpClientStreamableHttpTransport implements McpClientTransport { private HttpClientStreamableHttpTransport(ObjectMapper objectMapper, HttpClient httpClient, HttpRequest.Builder requestBuilder, String baseUri, String endpoint, boolean resumableStreams, - boolean openConnectionOnStartup, McpAsyncHttpRequestCustomizer httpRequestCustomizer) { + boolean openConnectionOnStartup, McpAsyncHttpClientRequestCustomizer httpRequestCustomizer) { this.objectMapper = objectMapper; this.httpClient = httpClient; this.requestBuilder = requestBuilder; @@ -605,7 +605,7 @@ public static class Builder { private HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(); - private McpAsyncHttpRequestCustomizer httpRequestCustomizer = McpAsyncHttpRequestCustomizer.NOOP; + private McpAsyncHttpClientRequestCustomizer httpRequestCustomizer = McpAsyncHttpClientRequestCustomizer.NOOP; private Duration connectTimeout = Duration.ofSeconds(10); @@ -716,16 +716,17 @@ public Builder openConnectionOnStartup(boolean openConnectionOnStartup) { * executing them. *

* This overrides the customizer from - * {@link #asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer)}. + * {@link #asyncHttpRequestCustomizer(McpAsyncHttpClientRequestCustomizer)}. *

- * Do NOT use a blocking {@link McpSyncHttpRequestCustomizer} in a non-blocking - * context. Use {@link #asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer)} + * Do NOT use a blocking {@link McpSyncHttpClientRequestCustomizer} in a + * non-blocking context. Use + * {@link #asyncHttpRequestCustomizer(McpAsyncHttpClientRequestCustomizer)} * instead. * @param syncHttpRequestCustomizer the request customizer * @return this builder */ - public Builder httpRequestCustomizer(McpSyncHttpRequestCustomizer syncHttpRequestCustomizer) { - this.httpRequestCustomizer = McpAsyncHttpRequestCustomizer.fromSync(syncHttpRequestCustomizer); + public Builder httpRequestCustomizer(McpSyncHttpClientRequestCustomizer syncHttpRequestCustomizer) { + this.httpRequestCustomizer = McpAsyncHttpClientRequestCustomizer.fromSync(syncHttpRequestCustomizer); return this; } @@ -734,13 +735,13 @@ public Builder httpRequestCustomizer(McpSyncHttpRequestCustomizer syncHttpReques * executing them. *

* This overrides the customizer from - * {@link #httpRequestCustomizer(McpSyncHttpRequestCustomizer)}. + * {@link #httpRequestCustomizer(McpSyncHttpClientRequestCustomizer)}. *

* Do NOT use a blocking implementation in a non-blocking context. * @param asyncHttpRequestCustomizer the request customizer * @return this builder */ - public Builder asyncHttpRequestCustomizer(McpAsyncHttpRequestCustomizer asyncHttpRequestCustomizer) { + public Builder asyncHttpRequestCustomizer(McpAsyncHttpClientRequestCustomizer asyncHttpRequestCustomizer) { this.httpRequestCustomizer = asyncHttpRequestCustomizer; return this; } diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpClientRequestCustomizer.java similarity index 69% rename from mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java rename to mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpClientRequestCustomizer.java index b4476a233..2492efe18 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpClientRequestCustomizer.java @@ -15,16 +15,16 @@ import reactor.core.publisher.Mono; /** - * Composable {@link McpAsyncHttpRequestCustomizer} that applies multiple customizers, in - * order. + * Composable {@link McpAsyncHttpClientRequestCustomizer} that applies multiple + * customizers, in order. * * @author Daniel Garnier-Moiroux */ -public class DelegatingMcpAsyncHttpRequestCustomizer implements McpAsyncHttpRequestCustomizer { +public class DelegatingMcpAsyncHttpClientRequestCustomizer implements McpAsyncHttpClientRequestCustomizer { - private final List customizers; + private final List customizers; - public DelegatingMcpAsyncHttpRequestCustomizer(List customizers) { + public DelegatingMcpAsyncHttpClientRequestCustomizer(List customizers) { Assert.notNull(customizers, "Customizers must not be null"); this.customizers = customizers; } diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpClientRequestCustomizer.java similarity index 64% rename from mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java rename to mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpClientRequestCustomizer.java index 69b262253..e627e7e69 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpClientRequestCustomizer.java @@ -12,16 +12,16 @@ import io.modelcontextprotocol.util.Assert; /** - * Composable {@link McpSyncHttpRequestCustomizer} that applies multiple customizers, in - * order. + * Composable {@link McpSyncHttpClientRequestCustomizer} that applies multiple + * customizers, in order. * * @author Daniel Garnier-Moiroux */ -public class DelegatingMcpSyncHttpRequestCustomizer implements McpSyncHttpRequestCustomizer { +public class DelegatingMcpSyncHttpClientRequestCustomizer implements McpSyncHttpClientRequestCustomizer { - private final List delegates; + private final List delegates; - public DelegatingMcpSyncHttpRequestCustomizer(List customizers) { + public DelegatingMcpSyncHttpClientRequestCustomizer(List customizers) { Assert.notNull(customizers, "Customizers must not be null"); this.delegates = customizers; } diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpClientRequestCustomizer.java similarity index 83% rename from mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpRequestCustomizer.java rename to mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpClientRequestCustomizer.java index 4a10b490d..756b39c35 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpAsyncHttpClientRequestCustomizer.java @@ -22,12 +22,12 @@ * * @author Daniel Garnier-Moiroux */ -public interface McpAsyncHttpRequestCustomizer { +public interface McpAsyncHttpClientRequestCustomizer { Publisher customize(HttpRequest.Builder builder, String method, URI endpoint, @Nullable String body, McpTransportContext context); - McpAsyncHttpRequestCustomizer NOOP = new Noop(); + McpAsyncHttpClientRequestCustomizer NOOP = new Noop(); /** * Wrap a sync implementation in an async wrapper. @@ -35,14 +35,14 @@ Publisher customize(HttpRequest.Builder builder, String met * Do NOT wrap a blocking implementation for use in a non-blocking context. For a * blocking implementation, consider using {@link Schedulers#boundedElastic()}. */ - static McpAsyncHttpRequestCustomizer fromSync(McpSyncHttpRequestCustomizer customizer) { + static McpAsyncHttpClientRequestCustomizer fromSync(McpSyncHttpClientRequestCustomizer customizer) { return (builder, method, uri, body, context) -> Mono.fromSupplier(() -> { customizer.customize(builder, method, uri, body, context); return builder; }); } - class Noop implements McpAsyncHttpRequestCustomizer { + class Noop implements McpAsyncHttpClientRequestCustomizer { @Override public Publisher customize(HttpRequest.Builder builder, String method, URI endpoint, diff --git a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpRequestCustomizer.java b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpClientRequestCustomizer.java similarity index 93% rename from mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpRequestCustomizer.java rename to mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpClientRequestCustomizer.java index 87695c8ab..e22e3aa62 100644 --- a/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpRequestCustomizer.java +++ b/mcp/src/main/java/io/modelcontextprotocol/client/transport/customizer/McpSyncHttpClientRequestCustomizer.java @@ -20,7 +20,7 @@ * * @author Daniel Garnier-Moiroux */ -public interface McpSyncHttpRequestCustomizer { +public interface McpSyncHttpClientRequestCustomizer { void customize(HttpRequest.Builder builder, String method, URI endpoint, @Nullable String body, McpTransportContext context); diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/HttpClientStreamableHttpSyncClientTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/HttpClientStreamableHttpSyncClientTests.java index 43aa261b2..6f3b58e3d 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/HttpClientStreamableHttpSyncClientTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/HttpClientStreamableHttpSyncClientTests.java @@ -13,7 +13,7 @@ import org.testcontainers.containers.wait.strategy.Wait; import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; -import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; +import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer; import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpClientTransport; @@ -37,7 +37,7 @@ public class HttpClientStreamableHttpSyncClientTests extends AbstractMcpSyncClie .withExposedPorts(3001) .waitingFor(Wait.forHttp("/").forStatusCode(404)); - private final McpSyncHttpRequestCustomizer requestCustomizer = mock(McpSyncHttpRequestCustomizer.class); + private final McpSyncHttpClientRequestCustomizer requestCustomizer = mock(McpSyncHttpClientRequestCustomizer.class); @Override protected McpClientTransport createMcpTransport() { diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/HttpSseMcpSyncClientTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/HttpSseMcpSyncClientTests.java index 1ea165f3b..4091d7a5e 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/HttpSseMcpSyncClientTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/HttpSseMcpSyncClientTests.java @@ -13,7 +13,7 @@ import org.testcontainers.containers.wait.strategy.Wait; import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; -import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; +import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer; import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpClientTransport; @@ -43,7 +43,7 @@ class HttpSseMcpSyncClientTests extends AbstractMcpSyncClientTests { .withExposedPorts(3001) .waitingFor(Wait.forHttp("/").forStatusCode(404)); - private final McpSyncHttpRequestCustomizer requestCustomizer = mock(McpSyncHttpRequestCustomizer.class); + private final McpSyncHttpClientRequestCustomizer requestCustomizer = mock(McpSyncHttpClientRequestCustomizer.class); @Override protected McpClientTransport createMcpTransport() { diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java index 7e1cb2ed8..257d65f06 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransportTests.java @@ -16,8 +16,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer; -import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; +import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpClientRequestCustomizer; +import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer; import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.JSONRPCRequest; @@ -79,7 +79,7 @@ static class TestHttpClientSseClientTransport extends HttpClientSseClientTranspo public TestHttpClientSseClientTransport(final String baseUri) { super(HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).build(), HttpRequest.newBuilder().header("Content-Type", "application/json"), baseUri, "/sse", - new ObjectMapper(), McpAsyncHttpRequestCustomizer.NOOP); + new ObjectMapper(), McpAsyncHttpClientRequestCustomizer.NOOP); } public int getInboundMessageCount() { @@ -396,7 +396,7 @@ void testChainedCustomizations() { @Test void testRequestCustomizer() { - var mockCustomizer = mock(McpSyncHttpRequestCustomizer.class); + var mockCustomizer = mock(McpSyncHttpClientRequestCustomizer.class); // Create a transport with the customizer var customizedTransport = HttpClientSseClientTransport.builder(host) @@ -437,7 +437,7 @@ void testRequestCustomizer() { @Test void testAsyncRequestCustomizer() { - var mockCustomizer = mock(McpAsyncHttpRequestCustomizer.class); + var mockCustomizer = mock(McpAsyncHttpClientRequestCustomizer.class); when(mockCustomizer.customize(any(), any(), any(), any(), any())) .thenAnswer(invocation -> Mono.just(invocation.getArguments()[0])); diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportEmptyJsonResponseTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportEmptyJsonResponseTest.java index c245c1461..250c7aa50 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportEmptyJsonResponseTest.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportEmptyJsonResponseTest.java @@ -22,7 +22,7 @@ import com.sun.net.httpserver.HttpServer; -import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; +import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer; import io.modelcontextprotocol.server.transport.TomcatTestUtil; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.ProtocolVersions; @@ -71,7 +71,7 @@ static void stopContainer() { void testNotificationInitialized() throws URISyntaxException { var uri = new URI(host + "/mcp"); - var mockRequestCustomizer = mock(McpSyncHttpRequestCustomizer.class); + var mockRequestCustomizer = mock(McpSyncHttpClientRequestCustomizer.class); var transport = HttpClientStreamableHttpTransport.builder(host) .httpRequestCustomizer(mockRequestCustomizer) .build(); diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java index b3fcab394..670e6c7e6 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransportTest.java @@ -4,8 +4,8 @@ package io.modelcontextprotocol.client.transport; -import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpRequestCustomizer; -import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpRequestCustomizer; +import io.modelcontextprotocol.client.transport.customizer.McpAsyncHttpClientRequestCustomizer; +import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer; import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.spec.McpSchema; import java.net.URI; @@ -71,7 +71,7 @@ void withTransport(HttpClientStreamableHttpTransport transport, Consumer Mono.just(invocation.getArguments()[0])); diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpClientRequestCustomizerTest.java similarity index 82% rename from mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java rename to mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpClientRequestCustomizerTest.java index 683a6d662..a04787aa3 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpRequestCustomizerTest.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpAsyncHttpClientRequestCustomizerTest.java @@ -20,11 +20,11 @@ import static org.mockito.Mockito.when; /** - * Tests for {@link DelegatingMcpAsyncHttpRequestCustomizer}. + * Tests for {@link DelegatingMcpAsyncHttpClientRequestCustomizer}. * * @author Daniel Garnier-Moiroux */ -class DelegatingMcpAsyncHttpRequestCustomizerTest { +class DelegatingMcpAsyncHttpClientRequestCustomizerTest { private static final URI TEST_URI = URI.create("https://example.com"); @@ -32,10 +32,10 @@ class DelegatingMcpAsyncHttpRequestCustomizerTest { @Test void delegates() { - var mockCustomizer = mock(McpAsyncHttpRequestCustomizer.class); + var mockCustomizer = mock(McpAsyncHttpClientRequestCustomizer.class); when(mockCustomizer.customize(any(), any(), any(), any(), any())) .thenAnswer(invocation -> Mono.just(invocation.getArguments()[0])); - var customizer = new DelegatingMcpAsyncHttpRequestCustomizer(List.of(mockCustomizer)); + var customizer = new DelegatingMcpAsyncHttpClientRequestCustomizer(List.of(mockCustomizer)); var context = McpTransportContext.EMPTY; StepVerifier @@ -48,7 +48,7 @@ void delegates() { @Test void delegatesInOrder() { - var customizer = new DelegatingMcpAsyncHttpRequestCustomizer( + var customizer = new DelegatingMcpAsyncHttpClientRequestCustomizer( List.of((builder, method, uri, body, ctx) -> Mono.just(builder.copy().header("x-test", "one")), (builder, method, uri, body, ctx) -> Mono.just(builder.copy().header("x-test", "two")))); @@ -64,7 +64,7 @@ void delegatesInOrder() { @Test void constructorRequiresNonNull() { - assertThatThrownBy(() -> new DelegatingMcpAsyncHttpRequestCustomizer(null)) + assertThatThrownBy(() -> new DelegatingMcpAsyncHttpClientRequestCustomizer(null)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Customizers must not be null"); } diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpClientRequestCustomizerTest.java similarity index 78% rename from mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java rename to mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpClientRequestCustomizerTest.java index 42c379057..6c51a3d12 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpRequestCustomizerTest.java +++ b/mcp/src/test/java/io/modelcontextprotocol/client/transport/customizer/DelegatingMcpSyncHttpClientRequestCustomizerTest.java @@ -17,11 +17,11 @@ import static org.mockito.Mockito.verify; /** - * Tests for {@link DelegatingMcpSyncHttpRequestCustomizer}. + * Tests for {@link DelegatingMcpSyncHttpClientRequestCustomizer}. * * @author Daniel Garnier-Moiroux */ -class DelegatingMcpSyncHttpRequestCustomizerTest { +class DelegatingMcpSyncHttpClientRequestCustomizerTest { private static final URI TEST_URI = URI.create("https://example.com"); @@ -29,8 +29,8 @@ class DelegatingMcpSyncHttpRequestCustomizerTest { @Test void delegates() { - var mockCustomizer = Mockito.mock(McpSyncHttpRequestCustomizer.class); - var customizer = new DelegatingMcpSyncHttpRequestCustomizer(List.of(mockCustomizer)); + var mockCustomizer = Mockito.mock(McpSyncHttpClientRequestCustomizer.class); + var customizer = new DelegatingMcpSyncHttpClientRequestCustomizer(List.of(mockCustomizer)); var context = McpTransportContext.EMPTY; customizer.customize(TEST_BUILDER, "GET", TEST_URI, "{\"everybody\": \"needs somebody\"}", context); @@ -41,7 +41,7 @@ void delegates() { @Test void delegatesInOrder() { var testHeaderName = "x-test"; - var customizer = new DelegatingMcpSyncHttpRequestCustomizer( + var customizer = new DelegatingMcpSyncHttpClientRequestCustomizer( List.of((builder, method, uri, body, ctx) -> builder.header(testHeaderName, "one"), (builder, method, uri, body, ctx) -> builder.header(testHeaderName, "two"))); @@ -53,7 +53,7 @@ void delegatesInOrder() { @Test void constructorRequiresNonNull() { - assertThatThrownBy(() -> new DelegatingMcpAsyncHttpRequestCustomizer(null)) + assertThatThrownBy(() -> new DelegatingMcpAsyncHttpClientRequestCustomizer(null)) .isInstanceOf(IllegalArgumentException.class) .hasMessage("Customizers must not be null"); } From ad7b6fc25b55810fb419057f32c587f96fb6ef2d Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Mon, 1 Sep 2025 14:24:29 +0200 Subject: [PATCH 4/4] Add end-to-end McpTransportContextIntegrationTests Signed-off-by: Daniel Garnier-Moiroux --- .../McpTransportContextIntegrationTests.java | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 mcp/src/test/java/io/modelcontextprotocol/common/McpTransportContextIntegrationTests.java diff --git a/mcp/src/test/java/io/modelcontextprotocol/common/McpTransportContextIntegrationTests.java b/mcp/src/test/java/io/modelcontextprotocol/common/McpTransportContextIntegrationTests.java new file mode 100644 index 000000000..8d75b8479 --- /dev/null +++ b/mcp/src/test/java/io/modelcontextprotocol/common/McpTransportContextIntegrationTests.java @@ -0,0 +1,241 @@ +/* + * Copyright 2024-2025 the original author or authors. + */ + +package io.modelcontextprotocol.common; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.modelcontextprotocol.client.McpClient; +import io.modelcontextprotocol.client.McpClient.SyncSpec; +import io.modelcontextprotocol.client.McpSyncClient; +import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; +import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; +import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer; +import io.modelcontextprotocol.server.McpServer; +import io.modelcontextprotocol.server.McpServerFeatures; +import io.modelcontextprotocol.server.McpStatelessServerFeatures; +import io.modelcontextprotocol.server.McpSyncServerExchange; +import io.modelcontextprotocol.server.McpTransportContextExtractor; +import io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider; +import io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport; +import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; +import io.modelcontextprotocol.server.transport.TomcatTestUtil; +import io.modelcontextprotocol.spec.McpSchema; +import jakarta.servlet.Servlet; +import jakarta.servlet.http.HttpServletRequest; +import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.Supplier; +import org.apache.catalina.LifecycleException; +import org.apache.catalina.LifecycleState; +import org.apache.catalina.startup.Tomcat; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test both Client and Server {@link McpTransportContext} integration, in two steps. + *

+ * First, the client calls a tool and writes data stored in a thread-local to an HTTP + * header using {@link SyncSpec#transportContextProvider(Supplier)} and + * {@link McpSyncHttpClientRequestCustomizer}. + *

+ * Then the server reads the header with a {@link McpTransportContextExtractor} and + * returns the value as the result of the tool call. + * + * @author Daniel Garnier-Moiroux + */ +@Timeout(15) +public class McpTransportContextIntegrationTests { + + private static final int PORT = TomcatTestUtil.findAvailablePort(); + + private Tomcat tomcat; + + private static final ThreadLocal CLIENT_SIDE_HEADER_VALUE_HOLDER = new ThreadLocal<>(); + + private static final String HEADER_NAME = "x-test"; + + private final Supplier clientContextProvider = () -> { + var headerValue = CLIENT_SIDE_HEADER_VALUE_HOLDER.get(); + return headerValue != null ? McpTransportContext.create(Map.of("client-side-header-value", headerValue)) + : McpTransportContext.EMPTY; + }; + + private final McpSyncHttpClientRequestCustomizer clientRequestCustomizer = (builder, method, endpoint, body, + context) -> { + var headerValue = context.get("client-side-header-value"); + if (headerValue != null) { + builder.header(HEADER_NAME, headerValue.toString()); + } + }; + + private final McpTransportContextExtractor serverContextExtractor = (HttpServletRequest r) -> { + var headerValue = r.getHeader(HEADER_NAME); + return headerValue != null ? McpTransportContext.create(Map.of("server-side-header-value", headerValue)) + : McpTransportContext.EMPTY; + }; + + private final BiFunction statelessHandler = ( + transportContext, + request) -> new McpSchema.CallToolResult(transportContext.get("server-side-header-value").toString(), null); + + private final BiFunction statefulHandler = ( + exchange, request) -> statelessHandler.apply(exchange.transportContext(), request); + + private final HttpServletStatelessServerTransport statelessServerTransport = HttpServletStatelessServerTransport + .builder() + .objectMapper(new ObjectMapper()) + .contextExtractor(serverContextExtractor) + .build(); + + private final HttpServletStreamableServerTransportProvider streamableServerTransport = HttpServletStreamableServerTransportProvider + .builder() + .objectMapper(new ObjectMapper()) + .contextExtractor(serverContextExtractor) + .build(); + + private final HttpServletSseServerTransportProvider sseServerTransport = HttpServletSseServerTransportProvider + .builder() + .objectMapper(new ObjectMapper()) + .contextExtractor(serverContextExtractor) + .messageEndpoint("/message") + .build(); + + private final McpSyncClient streamableClient = McpClient + .sync(HttpClientStreamableHttpTransport.builder("http://localhost:" + PORT) + .httpRequestCustomizer(clientRequestCustomizer) + .build()) + .transportContextProvider(clientContextProvider) + .build(); + + private final McpSyncClient sseClient = McpClient + .sync(HttpClientSseClientTransport.builder("http://localhost:" + PORT) + .httpRequestCustomizer(clientRequestCustomizer) + .build()) + .transportContextProvider(clientContextProvider) + .build(); + + private final McpSchema.Tool tool = McpSchema.Tool.builder() + .name("test-tool") + .description("return the value of the x-test header from call tool request") + .build(); + + @AfterEach + public void after() { + CLIENT_SIDE_HEADER_VALUE_HOLDER.remove(); + if (statelessServerTransport != null) { + statelessServerTransport.closeGracefully().block(); + } + if (streamableServerTransport != null) { + streamableServerTransport.closeGracefully().block(); + } + if (sseServerTransport != null) { + sseServerTransport.closeGracefully().block(); + } + stopTomcat(); + } + + @Test + void statelessServer() { + startTomcat(statelessServerTransport); + + var mcpServer = McpServer.sync(statelessServerTransport) + .capabilities(McpSchema.ServerCapabilities.builder().tools(true).build()) + .tools(new McpStatelessServerFeatures.SyncToolSpecification(tool, statelessHandler)) + .build(); + + McpSchema.InitializeResult initResult = streamableClient.initialize(); + assertThat(initResult).isNotNull(); + + CLIENT_SIDE_HEADER_VALUE_HOLDER.set("some important value"); + McpSchema.CallToolResult response = streamableClient + .callTool(new McpSchema.CallToolRequest("test-tool", Map.of())); + + assertThat(response).isNotNull(); + assertThat(response.content()).hasSize(1) + .first() + .extracting(McpSchema.TextContent.class::cast) + .extracting(McpSchema.TextContent::text) + .isEqualTo("some important value"); + + mcpServer.close(); + } + + @Test + void streamableServer() { + startTomcat(streamableServerTransport); + + var mcpServer = McpServer.sync(streamableServerTransport) + .capabilities(McpSchema.ServerCapabilities.builder().tools(true).build()) + .tools(new McpServerFeatures.SyncToolSpecification(tool, null, statefulHandler)) + .build(); + + McpSchema.InitializeResult initResult = streamableClient.initialize(); + assertThat(initResult).isNotNull(); + + CLIENT_SIDE_HEADER_VALUE_HOLDER.set("some important value"); + McpSchema.CallToolResult response = streamableClient + .callTool(new McpSchema.CallToolRequest("test-tool", Map.of())); + + assertThat(response).isNotNull(); + assertThat(response.content()).hasSize(1) + .first() + .extracting(McpSchema.TextContent.class::cast) + .extracting(McpSchema.TextContent::text) + .isEqualTo("some important value"); + + mcpServer.close(); + } + + @Test + void sseServer() { + startTomcat(sseServerTransport); + + var mcpServer = McpServer.sync(sseServerTransport) + .capabilities(McpSchema.ServerCapabilities.builder().tools(true).build()) + .tools(new McpServerFeatures.SyncToolSpecification(tool, null, statefulHandler)) + .build(); + + McpSchema.InitializeResult initResult = sseClient.initialize(); + assertThat(initResult).isNotNull(); + + CLIENT_SIDE_HEADER_VALUE_HOLDER.set("some important value"); + McpSchema.CallToolResult response = sseClient.callTool(new McpSchema.CallToolRequest("test-tool", Map.of())); + + assertThat(response).isNotNull(); + assertThat(response.content()).hasSize(1) + .first() + .extracting(McpSchema.TextContent.class::cast) + .extracting(McpSchema.TextContent::text) + .isEqualTo("some important value"); + + mcpServer.close(); + } + + private void startTomcat(Servlet transport) { + tomcat = TomcatTestUtil.createTomcatServer("", PORT, transport); + try { + tomcat.start(); + assertThat(tomcat.getServer().getState()).isEqualTo(LifecycleState.STARTED); + } + catch (Exception e) { + throw new RuntimeException("Failed to start Tomcat", e); + } + } + + private void stopTomcat() { + if (tomcat != null) { + try { + tomcat.stop(); + tomcat.destroy(); + } + catch (LifecycleException e) { + throw new RuntimeException("Failed to stop Tomcat", e); + } + } + } + +}