Skip to content

Issue 3938 Improve MCP SSE URL configuration documentation and examples #3953

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,27 @@
* Each connection is configured with a URL endpoint for SSE communication.
*
* <p>
* Example configuration: <pre>
* Example configurations: <pre>
* # Simple configuration with default SSE endpoint (/sse)
* spring.ai.mcp.client.sse:
* connections:
* server1:
* url: http://localhost:8080/events
* server2:
* url: http://otherserver:8081/events
* url: http://localhost:8080
*
* # Custom SSE endpoints - split complex URLs correctly
* spring.ai.mcp.client.sse:
* connections:
* mcp-hub:
* url: http://localhost:3000
* sse-endpoint: /mcp-hub/sse/cf9ec4527e3c4a2cbb149a85ea45ab01
* custom-server:
* url: http://api.example.com
* sse-endpoint: /v1/mcp/events?token=abc123&format=json
*
* # How to split a full URL:
* # Full URL: http://localhost:3000/mcp-hub/sse/token123
* # Split as: url: http://localhost:3000
* # sse-endpoint: /mcp-hub/sse/token123
* </pre>
*
* @author Christian Tzolov
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,20 @@ void specialCharactersInSseEndpoint() {
});
}

@Test
void mcpHubStyleUrlWithTokenPath() {
this.contextRunner.withPropertyValues("spring.ai.mcp.client.sse.connections.mcp-hub.url=http://localhost:3000",
"spring.ai.mcp.client.sse.connections.mcp-hub.sse-endpoint=/mcp-hub/sse/cf9ec4527e3c4a2cbb149a85ea45ab01")
.run(context -> {
McpSseClientProperties properties = context.getBean(McpSseClientProperties.class);
assertThat(properties.getConnections()).hasSize(1);
assertThat(properties.getConnections()).containsKey("mcp-hub");
assertThat(properties.getConnections().get("mcp-hub").url()).isEqualTo("http://localhost:3000");
assertThat(properties.getConnections().get("mcp-hub").sseEndpoint())
.isEqualTo("/mcp-hub/sse/cf9ec4527e3c4a2cbb149a85ea45ab01");
});
}

@Configuration
@EnableConfigurationProperties(McpSseClientProperties.class)
static class TestConfiguration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,30 @@ public List<NamedClientMcpTransport> sseHttpClientTransports(McpSseClientPropert
List<NamedClientMcpTransport> sseTransports = new ArrayList<>();

for (Map.Entry<String, SseParameters> serverParameters : sseProperties.getConnections().entrySet()) {
String connectionName = serverParameters.getKey();
SseParameters params = serverParameters.getValue();

String baseUrl = serverParameters.getValue().url();
String sseEndpoint = serverParameters.getValue().sseEndpoint() != null
? serverParameters.getValue().sseEndpoint() : "/sse";
var transport = HttpClientSseClientTransport.builder(baseUrl)
.sseEndpoint(sseEndpoint)
.clientBuilder(HttpClient.newBuilder())
.objectMapper(objectMapper)
.build();
sseTransports.add(new NamedClientMcpTransport(serverParameters.getKey(), transport));
String baseUrl = params.url();
String sseEndpoint = params.sseEndpoint() != null ? params.sseEndpoint() : "/sse";

if (baseUrl == null || baseUrl.trim().isEmpty()) {
throw new IllegalArgumentException("SSE connection '" + connectionName
+ "' requires a 'url' property. Example: url: http://localhost:3000");
}

try {
var transport = HttpClientSseClientTransport.builder(baseUrl)
.sseEndpoint(sseEndpoint)
.clientBuilder(HttpClient.newBuilder())
.objectMapper(objectMapper)
.build();
sseTransports.add(new NamedClientMcpTransport(connectionName, transport));
}
catch (Exception e) {
throw new IllegalArgumentException("Failed to create SSE transport for connection '" + connectionName
+ "'. Check URL splitting: url='" + baseUrl + "', sse-endpoint='" + sseEndpoint
+ "'. Full URL should be split as: url=http://host:port, sse-endpoint=/path/to/endpoint", e);
}
}

return sseTransports;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ Properties for Server-Sent Events (SSE) transport are prefixed with `spring.ai.m
|`/sse`
|===

Example configuration:
Example configurations:
[source,yaml]
----
spring:
Expand All @@ -197,13 +197,55 @@ spring:
client:
sse:
connections:
# Simple configuration using default /sse endpoint
server1:
url: http://localhost:8080

# Custom SSE endpoint
server2:
url: http://otherserver:8081
sse-endpoint: /custom-sse

# Complex URL with path and token (like MCP Hub)
mcp-hub:
url: http://localhost:3000
sse-endpoint: /mcp-hub/sse/cf9ec4527e3c4a2cbb149a85ea45ab01

# SSE endpoint with query parameters
api-server:
url: https://api.example.com
sse-endpoint: /v1/mcp/events?token=abc123&format=json
----

==== URL Splitting Guidelines

When you have a full SSE URL, split it into base URL and endpoint path:

[cols="2,2"]
|===
|Full URL |Configuration

|`\http://localhost:3000/mcp-hub/sse/token123`
|`url: http://localhost:3000` +
`sse-endpoint: /mcp-hub/sse/token123`

|`\https://api.service.com/v2/events?key=secret`
|`url: https://api.service.com` +
`sse-endpoint: /v2/events?key=secret`

|`\http://localhost:8080/sse`
|`url: http://localhost:8080` +
`sse-endpoint: /sse` (or omit for default)
|===

==== Troubleshooting SSE Connections

*404 Not Found Errors:*

* Verify URL splitting: ensure the base `url` contains only the scheme, host, and port
* Check the `sse-endpoint` starts with `/` and includes the full path and query parameters
* Test the full URL directly in a browser or curl to confirm it's accessible

=== Streamable Http Transport Properties

Properties for Streamable Http transport are prefixed with `spring.ai.mcp.client.streamable-http`:
Expand Down