Skip to content

Commit 357190d

Browse files
wilocuilayaperumalg
authored andcommitted
Improve MCP SSE URL configuration documentation and examples
Fixes spring-projects#3938 Signed-off-by: Mattia Pasetto <[email protected]>
1 parent 6593762 commit 357190d

File tree

4 files changed

+138
-23
lines changed

4 files changed

+138
-23
lines changed

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/properties/McpSseClientProperties.java

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,27 @@
2929
* Each connection is configured with a URL endpoint for SSE communication.
3030
*
3131
* <p>
32-
* Example configuration: <pre>
32+
* Example configurations: <pre>
33+
* # Simple configuration with default SSE endpoint (/sse)
3334
* spring.ai.mcp.client.sse:
3435
* connections:
3536
* server1:
36-
* url: http://localhost:8080/events
37-
* server2:
38-
* url: http://otherserver:8081/events
37+
* url: http://localhost:8080
38+
*
39+
* # Custom SSE endpoints - split complex URLs correctly
40+
* spring.ai.mcp.client.sse:
41+
* connections:
42+
* mcp-hub:
43+
* url: http://localhost:3000
44+
* sse-endpoint: /mcp-hub/sse/cf9ec4527e3c4a2cbb149a85ea45ab01
45+
* custom-server:
46+
* url: http://api.example.com
47+
* sse-endpoint: /v1/mcp/events?token=abc123&format=json
48+
*
49+
* # How to split a full URL:
50+
* # Full URL: http://localhost:3000/mcp-hub/sse/token123
51+
* # Split as: url: http://localhost:3000
52+
* # sse-endpoint: /mcp-hub/sse/token123
3953
* </pre>
4054
*
4155
* @author Christian Tzolov

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/test/java/org/springframework/ai/mcp/client/common/autoconfigure/properties/McpSseClientPropertiesTests.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,20 @@ void specialCharactersInSseEndpoint() {
283283
});
284284
}
285285

286+
@Test
287+
void mcpHubStyleUrlWithTokenPath() {
288+
this.contextRunner.withPropertyValues("spring.ai.mcp.client.sse.connections.mcp-hub.url=http://localhost:3000",
289+
"spring.ai.mcp.client.sse.connections.mcp-hub.sse-endpoint=/mcp-hub/sse/cf9ec4527e3c4a2cbb149a85ea45ab01")
290+
.run(context -> {
291+
McpSseClientProperties properties = context.getBean(McpSseClientProperties.class);
292+
assertThat(properties.getConnections()).hasSize(1);
293+
assertThat(properties.getConnections()).containsKey("mcp-hub");
294+
assertThat(properties.getConnections().get("mcp-hub").url()).isEqualTo("http://localhost:3000");
295+
assertThat(properties.getConnections().get("mcp-hub").sseEndpoint())
296+
.isEqualTo("/mcp-hub/sse/cf9ec4527e3c4a2cbb149a85ea45ab01");
297+
});
298+
}
299+
286300
@Configuration
287301
@EnableConfigurationProperties(McpSseClientProperties.class)
288302
static class TestConfiguration {

auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/main/java/org/springframework/ai/mcp/client/httpclient/autoconfigure/SseHttpClientTransportAutoConfiguration.java

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -109,26 +109,38 @@ public List<NamedClientMcpTransport> sseHttpClientTransports(McpSseClientConnect
109109
List<NamedClientMcpTransport> sseTransports = new ArrayList<>();
110110

111111
for (Map.Entry<String, SseParameters> serverParameters : connectionDetails.getConnections().entrySet()) {
112-
113-
String baseUrl = serverParameters.getValue().url();
114-
String sseEndpoint = serverParameters.getValue().sseEndpoint() != null
115-
? serverParameters.getValue().sseEndpoint() : "/sse";
116-
HttpClientSseClientTransport.Builder transportBuilder = HttpClientSseClientTransport.builder(baseUrl)
117-
.sseEndpoint(sseEndpoint)
118-
.clientBuilder(HttpClient.newBuilder())
119-
.jsonMapper(new JacksonMcpJsonMapper(objectMapper));
120-
121-
asyncHttpRequestCustomizer.ifUnique(transportBuilder::asyncHttpRequestCustomizer);
122-
syncHttpRequestCustomizer.ifUnique(transportBuilder::httpRequestCustomizer);
123-
if (asyncHttpRequestCustomizer.getIfUnique() != null && syncHttpRequestCustomizer.getIfUnique() != null) {
124-
logger.warn("Found beans of type %s and %s. Using %s.".formatted(
125-
McpAsyncHttpClientRequestCustomizer.class.getSimpleName(),
126-
McpSyncHttpClientRequestCustomizer.class.getSimpleName(),
127-
McpSyncHttpClientRequestCustomizer.class.getSimpleName()));
112+
String connectionName = serverParameters.getKey();
113+
SseParameters params = serverParameters.getValue();
114+
115+
String baseUrl = params.url();
116+
String sseEndpoint = params.sseEndpoint() != null ? params.sseEndpoint() : "/sse";
117+
if (baseUrl == null || baseUrl.trim().isEmpty()) {
118+
throw new IllegalArgumentException("SSE connection '" + connectionName
119+
+ "' requires a 'url' property. Example: url: http://localhost:3000");
128120
}
129121

130-
HttpClientSseClientTransport transport = transportBuilder.build();
131-
sseTransports.add(new NamedClientMcpTransport(serverParameters.getKey(), transport));
122+
try {
123+
var transportBuilder = HttpClientSseClientTransport.builder(baseUrl)
124+
.sseEndpoint(sseEndpoint)
125+
.clientBuilder(HttpClient.newBuilder())
126+
.jsonMapper(new JacksonMcpJsonMapper(objectMapper));
127+
128+
asyncHttpRequestCustomizer.ifUnique(transportBuilder::asyncHttpRequestCustomizer);
129+
syncHttpRequestCustomizer.ifUnique(transportBuilder::httpRequestCustomizer);
130+
if (asyncHttpRequestCustomizer.getIfUnique() != null
131+
&& syncHttpRequestCustomizer.getIfUnique() != null) {
132+
logger.warn("Found beans of type %s and %s. Using %s.".formatted(
133+
McpAsyncHttpClientRequestCustomizer.class.getSimpleName(),
134+
McpSyncHttpClientRequestCustomizer.class.getSimpleName(),
135+
McpSyncHttpClientRequestCustomizer.class.getSimpleName()));
136+
}
137+
sseTransports.add(new NamedClientMcpTransport(connectionName, transportBuilder.build()));
138+
}
139+
catch (Exception e) {
140+
throw new IllegalArgumentException("Failed to create SSE transport for connection '" + connectionName
141+
+ "'. Check URL splitting: url='" + baseUrl + "', sse-endpoint='" + sseEndpoint
142+
+ "'. Full URL should be split as: url=http://host:port, sse-endpoint=/path/to/endpoint", e);
143+
}
132144
}
133145

134146
return sseTransports;

spring-ai-docs/src/main/antora/modules/ROOT/pages/api/mcp/mcp-client-boot-starter-docs.adoc

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ Properties for Server-Sent Events (SSE) transport are prefixed with `spring.ai.m
241241
|`/sse`
242242
|===
243243

244-
Example configuration:
244+
Example configurations:
245245
[source,yaml]
246246
----
247247
spring:
@@ -250,13 +250,88 @@ spring:
250250
client:
251251
sse:
252252
connections:
253+
# Simple configuration using default /sse endpoint
253254
server1:
254255
url: http://localhost:8080
256+
# Custom SSE endpoint
255257
server2:
256258
url: http://otherserver:8081
257259
sse-endpoint: /custom-sse
260+
# Complex URL with path and token (like MCP Hub)
261+
mcp-hub:
262+
url: http://localhost:3000
263+
sse-endpoint: /mcp-hub/sse/cf9ec4527e3c4a2cbb149a85ea45ab01
264+
# SSE endpoint with query parameters
265+
api-server:
266+
url: https://api.example.com
267+
sse-endpoint: /v1/mcp/events?token=abc123&format=json
258268
----
259269

270+
==== URL Splitting Guidelines
271+
272+
When you have a full SSE URL, split it into base URL and endpoint path:
273+
274+
[cols="2,2"]
275+
|===
276+
|Full URL |Configuration
277+
278+
|`\http://localhost:3000/mcp-hub/sse/token123`
279+
|`url: http://localhost:3000` +
280+
`sse-endpoint: /mcp-hub/sse/token123`
281+
282+
|`\https://api.service.com/v2/events?key=secret`
283+
|`url: https://api.service.com` +
284+
`sse-endpoint: /v2/events?key=secret`
285+
286+
|`\http://localhost:8080/sse`
287+
|`url: http://localhost:8080` +
288+
`sse-endpoint: /sse` (or omit for default)
289+
|===
290+
291+
==== Troubleshooting SSE Connections
292+
293+
*404 Not Found Errors:*
294+
295+
* Verify URL splitting: ensure the base `url` contains only the scheme, host, and port
296+
* Check the `sse-endpoint` starts with `/` and includes the full path and query parameters
297+
* Test the full URL directly in a browser or curl to confirm it's accessible
298+
299+
=== Streamable Http Transport Properties
300+
301+
Properties for Streamable Http transport are prefixed with `spring.ai.mcp.client.streamable-http`:
302+
303+
[cols="3,4,3"]
304+
|===
305+
|Property |Description | Default Value
306+
307+
|`connections`
308+
|Map of named Streamable Http connection configurations
309+
|-
310+
311+
|`connections.[name].url`
312+
|Base URL endpoint for Streamable-Http communication with the MCP server
313+
|-
314+
315+
|`connections.[name].endpoint`
316+
|the streamable-http endpoint (as url suffix) to use for the connection
317+
|`/mcp`
318+
|===
319+
320+
Example configuration:
321+
[source,yaml]
322+
----
323+
spring:
324+
ai:
325+
mcp:
326+
client:
327+
streamable-http:
328+
connections:
329+
server1:
330+
url: http://localhost:8080
331+
server2:
332+
url: http://otherserver:8081
333+
endpoint: /custom-sse
334+
----
260335

261336
== Features
262337

0 commit comments

Comments
 (0)