Skip to content

Commit 7a28ae7

Browse files
committed
Merge remote-tracking branch 'refs/remotes/upstream/main' into bedrock_connection_config
2 parents 6f46278 + 3cf3db1 commit 7a28ae7

File tree

39 files changed

+910
-120
lines changed

39 files changed

+910
-120
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: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,8 @@
4949
*
5050
* <p>
5151
* This configuration class sets up the necessary beans for SSE-based HTTP client
52-
* transport when WebFlux is not available. It provides HTTP client-based SSE transport
53-
* implementation for MCP client communication.
54-
*
55-
* <p>
56-
* The configuration is activated after the WebFlux SSE transport auto-configuration to
57-
* ensure proper fallback behavior when WebFlux is not available.
52+
* transport. It provides HTTP client-based SSE transport implementation for MCP client
53+
* communication.
5854
*
5955
* <p>
6056
* Key features:
@@ -113,26 +109,38 @@ public List<NamedClientMcpTransport> sseHttpClientTransports(McpSseClientConnect
113109
List<NamedClientMcpTransport> sseTransports = new ArrayList<>();
114110

115111
for (Map.Entry<String, SseParameters> serverParameters : connectionDetails.getConnections().entrySet()) {
116-
117-
String baseUrl = serverParameters.getValue().url();
118-
String sseEndpoint = serverParameters.getValue().sseEndpoint() != null
119-
? serverParameters.getValue().sseEndpoint() : "/sse";
120-
HttpClientSseClientTransport.Builder transportBuilder = HttpClientSseClientTransport.builder(baseUrl)
121-
.sseEndpoint(sseEndpoint)
122-
.clientBuilder(HttpClient.newBuilder())
123-
.jsonMapper(new JacksonMcpJsonMapper(objectMapper));
124-
125-
asyncHttpRequestCustomizer.ifUnique(transportBuilder::asyncHttpRequestCustomizer);
126-
syncHttpRequestCustomizer.ifUnique(transportBuilder::httpRequestCustomizer);
127-
if (asyncHttpRequestCustomizer.getIfUnique() != null && syncHttpRequestCustomizer.getIfUnique() != null) {
128-
logger.warn("Found beans of type %s and %s. Using %s.".formatted(
129-
McpAsyncHttpClientRequestCustomizer.class.getSimpleName(),
130-
McpSyncHttpClientRequestCustomizer.class.getSimpleName(),
131-
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");
132120
}
133121

134-
HttpClientSseClientTransport transport = transportBuilder.build();
135-
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+
}
136144
}
137145

138146
return sseTransports;

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,8 @@
4747
*
4848
* <p>
4949
* This configuration class sets up the necessary beans for Streamable HTTP client
50-
* transport when WebFlux is not available. It provides HTTP client-based Streamable HTTP
51-
* transport implementation for MCP client communication.
52-
*
53-
* <p>
54-
* The configuration is activated after the WebFlux Streamable HTTP transport
55-
* auto-configuration to ensure proper fallback behavior when WebFlux is not available.
50+
* transport. It provides HTTP client-based Streamable HTTP transport implementation for
51+
* MCP client communication.
5652
*
5753
* <p>
5854
* Key features:

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebFluxAutoConfiguration.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition;
2626
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties;
2727
import org.springframework.beans.factory.ObjectProvider;
28+
import org.springframework.beans.factory.annotation.Qualifier;
2829
import org.springframework.boot.autoconfigure.AutoConfiguration;
2930
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3031
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -82,7 +83,8 @@ public class McpServerSseWebFluxAutoConfiguration {
8283

8384
@Bean
8485
@ConditionalOnMissingBean
85-
public WebFluxSseServerTransportProvider webFluxTransport(ObjectProvider<ObjectMapper> objectMapperProvider,
86+
public WebFluxSseServerTransportProvider webFluxTransport(
87+
@Qualifier("mcpServerObjectMapper") ObjectProvider<ObjectMapper> objectMapperProvider,
8688
McpServerSseProperties serverProperties) {
8789

8890
ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new);

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebFluxAutoConfiguration.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition;
2626
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties;
2727
import org.springframework.beans.factory.ObjectProvider;
28+
import org.springframework.beans.factory.annotation.Qualifier;
2829
import org.springframework.boot.autoconfigure.AutoConfiguration;
2930
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3031
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -47,7 +48,8 @@ public class McpServerStatelessWebFluxAutoConfiguration {
4748
@Bean
4849
@ConditionalOnMissingBean
4950
public WebFluxStatelessServerTransport webFluxStatelessServerTransport(
50-
ObjectProvider<ObjectMapper> objectMapperProvider, McpServerStreamableHttpProperties serverProperties) {
51+
@Qualifier("mcpServerObjectMapper") ObjectProvider<ObjectMapper> objectMapperProvider,
52+
McpServerStreamableHttpProperties serverProperties) {
5153

5254
ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new);
5355

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webflux/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebFluxAutoConfiguration.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;
2727
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties;
2828
import org.springframework.beans.factory.ObjectProvider;
29+
import org.springframework.beans.factory.annotation.Qualifier;
2930
import org.springframework.boot.autoconfigure.AutoConfiguration;
3031
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3132
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -48,7 +49,8 @@ public class McpServerStreamableHttpWebFluxAutoConfiguration {
4849
@Bean
4950
@ConditionalOnMissingBean
5051
public WebFluxStreamableServerTransportProvider webFluxStreamableServerTransportProvider(
51-
ObjectProvider<ObjectMapper> objectMapperProvider, McpServerStreamableHttpProperties serverProperties) {
52+
@Qualifier("mcpServerObjectMapper") ObjectProvider<ObjectMapper> objectMapperProvider,
53+
McpServerStreamableHttpProperties serverProperties) {
5254

5355
ObjectMapper objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new);
5456

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerSseWebMvcAutoConfiguration.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition;
2727
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerSseProperties;
2828
import org.springframework.beans.factory.ObjectProvider;
29+
import org.springframework.beans.factory.annotation.Qualifier;
2930
import org.springframework.boot.autoconfigure.AutoConfiguration;
3031
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3132
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -77,7 +78,8 @@ public class McpServerSseWebMvcAutoConfiguration {
7778
@Bean
7879
@ConditionalOnMissingBean
7980
public WebMvcSseServerTransportProvider webMvcSseServerTransportProvider(
80-
ObjectProvider<ObjectMapper> objectMapperProvider, McpServerSseProperties serverProperties) {
81+
@Qualifier("mcpServerObjectMapper") ObjectProvider<ObjectMapper> objectMapperProvider,
82+
McpServerSseProperties serverProperties) {
8183

8284
ObjectMapper objectMapper = McpServerObjectMapperFactory.getOrCreateObjectMapper(objectMapperProvider);
8385

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStatelessWebMvcAutoConfiguration.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.springframework.ai.mcp.server.common.autoconfigure.McpServerStdioDisabledCondition;
2727
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties;
2828
import org.springframework.beans.factory.ObjectProvider;
29+
import org.springframework.beans.factory.annotation.Qualifier;
2930
import org.springframework.boot.autoconfigure.AutoConfiguration;
3031
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3132
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -49,7 +50,8 @@ public class McpServerStatelessWebMvcAutoConfiguration {
4950
@Bean
5051
@ConditionalOnMissingBean
5152
public WebMvcStatelessServerTransport webMvcStatelessServerTransport(
52-
ObjectProvider<ObjectMapper> objectMapperProvider, McpServerStreamableHttpProperties serverProperties) {
53+
@Qualifier("mcpServerObjectMapper") ObjectProvider<ObjectMapper> objectMapperProvider,
54+
McpServerStreamableHttpProperties serverProperties) {
5355

5456
ObjectMapper objectMapper = McpServerObjectMapperFactory.getOrCreateObjectMapper(objectMapperProvider);
5557

auto-configurations/mcp/spring-ai-autoconfigure-mcp-server-webmvc/src/main/java/org/springframework/ai/mcp/server/autoconfigure/McpServerStreamableHttpWebMvcAutoConfiguration.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerProperties;
2828
import org.springframework.ai.mcp.server.common.autoconfigure.properties.McpServerStreamableHttpProperties;
2929
import org.springframework.beans.factory.ObjectProvider;
30+
import org.springframework.beans.factory.annotation.Qualifier;
3031
import org.springframework.boot.autoconfigure.AutoConfiguration;
3132
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3233
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@@ -50,7 +51,8 @@ public class McpServerStreamableHttpWebMvcAutoConfiguration {
5051
@Bean
5152
@ConditionalOnMissingBean
5253
public WebMvcStreamableServerTransportProvider webMvcStreamableServerTransportProvider(
53-
ObjectProvider<ObjectMapper> objectMapperProvider, McpServerStreamableHttpProperties serverProperties) {
54+
@Qualifier("mcpServerObjectMapper") ObjectProvider<ObjectMapper> objectMapperProvider,
55+
McpServerStreamableHttpProperties serverProperties) {
5456

5557
ObjectMapper objectMapper = McpServerObjectMapperFactory.getOrCreateObjectMapper(objectMapperProvider);
5658

0 commit comments

Comments
 (0)