Skip to content

Commit b29953c

Browse files
committed
chore: Cleanup
1 parent c9ca956 commit b29953c

File tree

7 files changed

+150
-91
lines changed

7 files changed

+150
-91
lines changed

mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import io.modelcontextprotocol.spec.McpServerTransport;
1313
import io.modelcontextprotocol.spec.McpServerTransportProvider;
1414
import io.modelcontextprotocol.util.Assert;
15+
import io.modelcontextprotocol.util.Utils;
1516
import org.slf4j.Logger;
1617
import org.slf4j.LoggerFactory;
1718
import reactor.core.Exceptions;
@@ -145,6 +146,7 @@ public WebFluxSseServerTransportProvider(ObjectMapper objectMapper, String messa
145146
* Constructs a new WebFlux SSE server transport provider instance.
146147
* @param objectMapper The ObjectMapper to use for JSON serialization/deserialization
147148
* of MCP messages. Must not be null.
149+
* @param contextPath The context path of the server.
148150
* @param baseUrl webflux message base path
149151
* @param messageEndpoint The endpoint URI where clients should send their JSON-RPC
150152
* messages. This endpoint will be communicated to clients during SSE connection
@@ -159,16 +161,9 @@ public WebFluxSseServerTransportProvider(ObjectMapper objectMapper, String conte
159161
Assert.notNull(messageEndpoint, "Message endpoint must not be null");
160162
Assert.notNull(sseEndpoint, "SSE endpoint must not be null");
161163

162-
if (baseUrl.endsWith("/")) {
163-
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
164-
}
165-
if (contextPath.endsWith("/")) {
166-
contextPath = contextPath.substring(0, contextPath.length() - 1);
167-
}
168-
169164
this.objectMapper = objectMapper;
170-
this.contextPath = contextPath;
171-
this.baseUrl = baseUrl;
165+
this.contextPath = Utils.removeTrailingSlash(contextPath);
166+
this.baseUrl = Utils.removeTrailingSlash(baseUrl);
172167
this.messageEndpoint = messageEndpoint;
173168
this.sseEndpoint = sseEndpoint;
174169
this.routerFunction = RouterFunctions.route()
@@ -438,6 +433,18 @@ public Builder basePath(String baseUrl) {
438433
return this;
439434
}
440435

436+
/**
437+
* Sets the context path under which the server is running.
438+
* @param contextPath the context path.
439+
* @return this builder instance.
440+
* @throws IllegalArgumentException if contextPath is null
441+
*/
442+
public Builder contextPath(String contextPath) {
443+
Assert.notNull(contextPath, "contextPath must not be null");
444+
this.contextPath = contextPath;
445+
return this;
446+
}
447+
441448
/**
442449
* Sets the endpoint URI where clients should send their JSON-RPC messages.
443450
* @param messageEndpoint The message endpoint URI. Must not be null.

mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseCustomPathIntegrationTests.java

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import org.springframework.http.server.reactive.HttpHandler;
1313
import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
1414
import org.springframework.web.reactive.function.client.WebClient;
15-
import org.springframework.web.reactive.function.server.RequestPredicates;
1615
import org.springframework.web.reactive.function.server.RouterFunction;
1716
import org.springframework.web.reactive.function.server.RouterFunctions;
1817
import org.springframework.web.reactive.function.server.ServerResponse;
@@ -22,14 +21,16 @@
2221

2322
import java.util.List;
2423
import java.util.Map;
25-
import java.util.function.Supplier;
2624
import java.util.stream.Stream;
2725

2826
import static org.assertj.core.api.Assertions.assertThat;
2927
import static org.springframework.web.reactive.function.server.RequestPredicates.path;
3028
import static org.springframework.web.reactive.function.server.RouterFunctions.nest;
31-
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
3229

30+
/**
31+
* Tests the {@link WebFluxSseServerTransportProvider} with different values for the
32+
* endpoint.
33+
*/
3334
public class WebFluxSseCustomPathIntegrationTests {
3435

3536
private static final int PORT = TestUtil.findAvailablePort();
@@ -56,25 +57,18 @@ public void testCustomizedEndpoints(String baseUrl, String messageEndpoint, Stri
5657
baseUrl, messageEndpoint, sseEndpoint);
5758

5859
RouterFunction<?> router = this.mcpServerTransportProvider.getRouterFunction();
60+
// wrap the context path around the router function
5961
RouterFunction<ServerResponse> nestedRouter = (RouterFunction<ServerResponse>) nest(path(contextPath), router);
6062
HttpHandler httpHandler = RouterFunctions.toHttpHandler(nestedRouter);
6163
ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler);
6264

6365
this.httpServer = HttpServer.create().port(PORT).handle(adapter).bindNow();
6466

65-
var c = contextPath;
66-
var b = baseUrl;
67-
var s = sseEndpoint;
68-
if (baseUrl.endsWith("/")) {
69-
b = b.substring(0, b.length() - 1);
70-
}
71-
if (contextPath.endsWith("/")) {
72-
c = c.substring(0, c.length() - 1);
73-
}
67+
var endpoint = buildSseEndpoint(contextPath, baseUrl, sseEndpoint);
7468

7569
var clientBuilder = McpClient
7670
.sync(WebFluxSseClientTransport.builder(WebClient.builder().baseUrl("http://localhost:" + PORT))
77-
.sseEndpoint(c + b + s)
71+
.sseEndpoint(endpoint)
7872
.build());
7973

8074
McpSchema.CallToolResult callResponse = new McpSchema.CallToolResult(
@@ -102,24 +96,63 @@ public void testCustomizedEndpoints(String baseUrl, String messageEndpoint, Stri
10296

10397
}
10498

105-
private static Stream<Arguments> provideCustomEndpoints() {
106-
String[] baseUrls = { "", "/v1", "/api/v1", "/", "/v1/", "/api/v1/" };
107-
String[] messageEndpoints = { "/message", "/another/sse", "/" };
108-
String[] sseEndpoints = { "/sse", "/another/sse", "/" };
109-
String[] contextPaths = { "", "/mcp", "/root/mcp", "/", "/mcp/", "/root/mcp/" };
99+
/**
100+
* This is a helper function for the tests which builds the SSE endpoint to pass to the client transport.
101+
*
102+
* @param contextPath context path of the server.
103+
* @param baseUrl base url of the sse endpoint.
104+
* @param sseEndpoint the sse endpoint.
105+
* @return the created sse endpoint.
106+
*/
107+
private String buildSseEndpoint(String contextPath, String baseUrl, String sseEndpoint) {
108+
if (baseUrl.endsWith("/")) {
109+
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
110+
}
111+
if (contextPath.endsWith("/")) {
112+
contextPath = contextPath.substring(0, contextPath.length() - 1);
113+
}
110114

111-
return Stream.of(baseUrls)
112-
.flatMap(baseUrl -> Stream.of(messageEndpoints)
113-
.flatMap(messageEndpoint -> Stream.of(sseEndpoints)
114-
.flatMap(sseEndpoint -> Stream.of(contextPaths)
115-
.map(contextPath -> Arguments.of(baseUrl, messageEndpoint, sseEndpoint, contextPath)))));
115+
return contextPath + baseUrl + sseEndpoint;
116116
}
117117

118118
@AfterEach
119119
public void after() {
120+
if (mcpServerTransportProvider != null) {
121+
mcpServerTransportProvider.closeGracefully().block();
122+
}
120123
if (httpServer != null) {
121124
httpServer.disposeNow();
122125
}
123126
}
124127

128+
/**
129+
* Provides a stream of custom endpoints. This generates all possible combinations for
130+
* allowed endpoint values.
131+
*
132+
* <p>
133+
* Each combination is returned as an {@link Arguments} object containing four
134+
* parameters in the following order:
135+
* </p>
136+
* <ol>
137+
* <li>Base URL (String)</li>
138+
* <li>Message endpoint (String)</li>
139+
* <li>SSE endpoint (String)</li>
140+
* <li>Context path (String)</li>
141+
* </ol>
142+
* @return a {@link Stream} of {@link Arguments} objects, each containing four String
143+
* parameters representing different endpoint combinations for parameterized testing
144+
*/
145+
private static Stream<Arguments> provideCustomEndpoints() {
146+
String[] baseUrls = { "", "/", "/v1", "/v1/" };
147+
String[] messageEndpoints = { "/", "/message", "/message/" };
148+
String[] sseEndpoints = { "/", "/sse", "/sse/" };
149+
String[] contextPaths = { "", "/", "/mcp", "/mcp/" };
150+
151+
return Stream.of(baseUrls)
152+
.flatMap(baseUrl -> Stream.of(messageEndpoints)
153+
.flatMap(messageEndpoint -> Stream.of(sseEndpoints)
154+
.flatMap(sseEndpoint -> Stream.of(contextPaths)
155+
.map(contextPath -> Arguments.of(baseUrl, messageEndpoint, sseEndpoint, contextPath)))));
156+
}
157+
125158
}

mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import io.modelcontextprotocol.spec.McpServerTransportProvider;
2020
import io.modelcontextprotocol.spec.McpServerSession;
2121
import io.modelcontextprotocol.util.Assert;
22+
import io.modelcontextprotocol.util.Utils;
2223
import org.slf4j.Logger;
2324
import org.slf4j.LoggerFactory;
2425
import reactor.core.publisher.Flux;
@@ -161,17 +162,9 @@ public WebMvcSseServerTransportProvider(ObjectMapper objectMapper, String contex
161162
Assert.notNull(sseEndpoint, "SSE endpoint must not be null");
162163
Assert.hasText(sseEndpoint, "SSE endpoint must not be empty");
163164

164-
if (baseUrl.endsWith("/")) {
165-
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
166-
}
167-
168-
if (contextPath.endsWith("/")) {
169-
contextPath = contextPath.substring(0, contextPath.length() - 1);
170-
}
171-
172165
this.objectMapper = objectMapper;
173-
this.baseUrl = baseUrl;
174-
this.contextPath = contextPath;
166+
this.contextPath = Utils.removeTrailingSlash(contextPath);
167+
this.baseUrl = Utils.removeTrailingSlash(baseUrl);
175168
this.messageEndpoint = messageEndpoint;
176169
this.sseEndpoint = sseEndpoint;
177170
this.routerFunction = RouterFunctions.route()

mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseCustomContextPathTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ static class TestConfig {
9191
@Bean
9292
public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider() {
9393

94-
return new WebMvcSseServerTransportProvider(new ObjectMapper(), "", CUSTOM_CONTEXT_PATH, MESSAGE_ENDPOINT,
94+
return new WebMvcSseServerTransportProvider(new ObjectMapper(), CUSTOM_CONTEXT_PATH, "", MESSAGE_ENDPOINT,
9595
WebMvcSseServerTransportProvider.DEFAULT_SSE_ENDPOINT);
9696
}
9797

mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseCustomPathIntegrationTests.java

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport;
55
import io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider;
66
import io.modelcontextprotocol.spec.McpSchema;
7+
78
import java.util.List;
89
import java.util.Map;
910
import java.util.stream.Stream;
11+
12+
import org.springframework.core.env.Environment;
1013
import reactor.core.publisher.Mono;
14+
1115
import static org.assertj.core.api.Assertions.assertThat;
1216

1317
import org.apache.catalina.LifecycleException;
@@ -24,14 +28,16 @@
2428

2529
import com.fasterxml.jackson.databind.ObjectMapper;
2630

31+
/**
32+
* Tests the {@link WebMvcSseServerTransportProvider} with different values for the
33+
* endpoint.
34+
*/
2735
public class WebMvcSseCustomPathIntegrationTests {
2836

2937
private static final int PORT = TestUtil.findAvailablePort();
3038

3139
private WebMvcSseServerTransportProvider mcpServerTransportProvider;
3240

33-
McpClient.SyncSpec clientBuilder;
34-
3541
private TomcatTestUtil.TomcatServer tomcatServer;
3642

3743
String emptyJsonSchema = """
@@ -47,7 +53,7 @@ public class WebMvcSseCustomPathIntegrationTests {
4753
static class TestConfig {
4854

4955
@Bean
50-
public WebMvcSseServerTransportProvider transportProvider(org.springframework.core.env.Environment env) {
56+
public WebMvcSseServerTransportProvider transportProvider(Environment env) {
5157
String baseUrl = env.getProperty("test.baseUrl");
5258
String messageEndpoint = env.getProperty("test.messageEndpoint");
5359
String sseEndpoint = env.getProperty("test.sseEndpoint");
@@ -84,18 +90,10 @@ public void testCustomizedEndpoints(String baseUrl, String messageEndpoint, Stri
8490
throw new RuntimeException("Failed to start Tomcat", e);
8591
}
8692

87-
var c = contextPath;
88-
var b = baseUrl;
89-
var s = sseEndpoint;
90-
if (baseUrl.endsWith("/")) {
91-
b = b.substring(0, b.length() - 1);
92-
}
93-
if (contextPath.endsWith("/")) {
94-
c = c.substring(0, c.length() - 1);
95-
}
93+
var endpoint = buildSseEndpoint(contextPath, baseUrl, sseEndpoint);
9694

97-
clientBuilder = McpClient
98-
.sync(HttpClientSseClientTransport.builder("http://localhost:" + PORT).sseEndpoint(c + b + s).build());
95+
var clientBuilder = McpClient
96+
.sync(HttpClientSseClientTransport.builder("http://localhost:" + PORT).sseEndpoint(endpoint).build());
9997

10098
McpSchema.CallToolResult callResponse = new McpSchema.CallToolResult(
10199
List.of(new McpSchema.TextContent("CALL RESPONSE")), null);
@@ -123,17 +121,23 @@ public void testCustomizedEndpoints(String baseUrl, String messageEndpoint, Stri
123121
server.close();
124122
}
125123

126-
private static Stream<Arguments> provideCustomEndpoints() {
127-
String[] baseUrls = { "", "/v1", "/api/v1", "/", "/v1/", "/api/v1/" };
128-
String[] messageEndpoints = { "/message", "/another/sse", "/" };
129-
String[] sseEndpoints = { "/sse", "/another/sse", "/" };
130-
String[] contextPaths = { "", "/mcp", "/root/mcp", "/", "/mcp/", "/root/mcp/" };
124+
/**
125+
* This is a helper function for the tests which builds the SSE endpoint to pass to the client transport.
126+
*
127+
* @param contextPath context path of the server.
128+
* @param baseUrl base url of the sse endpoint.
129+
* @param sseEndpoint the sse endpoint.
130+
* @return the created sse endpoint.
131+
*/
132+
private String buildSseEndpoint(String contextPath, String baseUrl, String sseEndpoint) {
133+
if (baseUrl.endsWith("/")) {
134+
baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
135+
}
136+
if (contextPath.endsWith("/")) {
137+
contextPath = contextPath.substring(0, contextPath.length() - 1);
138+
}
131139

132-
return Stream.of(baseUrls)
133-
.flatMap(baseUrl -> Stream.of(messageEndpoints)
134-
.flatMap(messageEndpoint -> Stream.of(sseEndpoints)
135-
.flatMap(sseEndpoint -> Stream.of(contextPaths)
136-
.map(contextPath -> Arguments.of(baseUrl, messageEndpoint, sseEndpoint, contextPath)))));
140+
return contextPath + baseUrl + sseEndpoint;
137141
}
138142

139143
@AfterEach
@@ -155,4 +159,34 @@ public void after() {
155159
}
156160
}
157161

162+
/**
163+
* Provides a stream of custom endpoints. This generates all possible combinations for
164+
* allowed endpoint values.
165+
*
166+
* <p>
167+
* Each combination is returned as an {@link Arguments} object containing four
168+
* parameters in the following order:
169+
* </p>
170+
* <ol>
171+
* <li>Base URL (String)</li>
172+
* <li>Message endpoint (String)</li>
173+
* <li>SSE endpoint (String)</li>
174+
* <li>Context path (String)</li>
175+
* </ol>
176+
* @return a {@link Stream} of {@link Arguments} objects, each containing four String
177+
* parameters representing different endpoint combinations for parameterized testing
178+
*/
179+
private static Stream<Arguments> provideCustomEndpoints() {
180+
String[] baseUrls = { "", "/", "/v1", "/v1/" };
181+
String[] messageEndpoints = { "/", "/message", "/message/" };
182+
String[] sseEndpoints = { "/", "/sse", "/sse/" };
183+
String[] contextPaths = { "", "/", "/mcp", "/mcp/" };
184+
185+
return Stream.of(baseUrls)
186+
.flatMap(baseUrl -> Stream.of(messageEndpoints)
187+
.flatMap(messageEndpoint -> Stream.of(sseEndpoints)
188+
.flatMap(sseEndpoint -> Stream.of(contextPaths)
189+
.map(contextPath -> Arguments.of(baseUrl, messageEndpoint, sseEndpoint, contextPath)))));
190+
}
191+
158192
}

mcp/src/main/java/io/modelcontextprotocol/client/transport/HttpClientSseClientTransport.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ public Mono<Void> connect(Function<Mono<JSONRPCMessage>, Mono<JSONRPCMessage>> h
341341
CompletableFuture<Void> future = new CompletableFuture<>();
342342
connectionFuture.set(future);
343343

344-
URI clientUri = Utils.resolveSseUri(this.baseUri, this.sseEndpoint);
344+
URI clientUri = Utils.resolveUri(this.baseUri, this.sseEndpoint);
345345
logger.debug("Subscribing to {}", clientUri);
346346
sseClient.subscribe(clientUri.toString(), new FlowSseClient.SseEventHandler() {
347347
@Override

0 commit comments

Comments
 (0)