diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/McpSseClientConnectionDetails.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/McpSseClientConnectionDetails.java new file mode 100644 index 00000000000..dcc00bc458a --- /dev/null +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/McpSseClientConnectionDetails.java @@ -0,0 +1,33 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.mcp.client.common.autoconfigure; + +import java.util.Map; + +import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpSseClientProperties; +import org.springframework.boot.autoconfigure.service.connection.ConnectionDetails; + +/** + * Connection details for an MCP client. + * + * @author Eddú Meléndez + */ +public interface McpSseClientConnectionDetails extends ConnectionDetails { + + Map getConnections(); + +} diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/PropertiesMcpSseClientConnectionDetails.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/PropertiesMcpSseClientConnectionDetails.java new file mode 100644 index 00000000000..5660aa0e70b --- /dev/null +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-common/src/main/java/org/springframework/ai/mcp/client/common/autoconfigure/PropertiesMcpSseClientConnectionDetails.java @@ -0,0 +1,36 @@ +/* + * Copyright 2025-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.mcp.client.common.autoconfigure; + +import java.util.Map; + +import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpSseClientProperties; + +public class PropertiesMcpSseClientConnectionDetails implements McpSseClientConnectionDetails { + + private final McpSseClientProperties properties; + + public PropertiesMcpSseClientConnectionDetails(McpSseClientProperties properties) { + this.properties = properties; + } + + @Override + public Map getConnections() { + return this.properties.getConnections(); + } + +} diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/main/java/org/springframework/ai/mcp/client/httpclient/autoconfigure/SseHttpClientTransportAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/main/java/org/springframework/ai/mcp/client/httpclient/autoconfigure/SseHttpClientTransportAutoConfiguration.java index a821cc494c6..d4e4a6c75d1 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/main/java/org/springframework/ai/mcp/client/httpclient/autoconfigure/SseHttpClientTransportAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-httpclient/src/main/java/org/springframework/ai/mcp/client/httpclient/autoconfigure/SseHttpClientTransportAutoConfiguration.java @@ -28,7 +28,9 @@ import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequestCustomizer; import io.modelcontextprotocol.spec.McpSchema; +import org.springframework.ai.mcp.client.common.autoconfigure.McpSseClientConnectionDetails; import org.springframework.ai.mcp.client.common.autoconfigure.NamedClientMcpTransport; +import org.springframework.ai.mcp.client.common.autoconfigure.PropertiesMcpSseClientConnectionDetails; import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpClientCommonProperties; import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpSseClientProperties; import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpSseClientProperties.SseParameters; @@ -73,6 +75,11 @@ public class SseHttpClientTransportAutoConfiguration { private static final LogAccessor logger = new LogAccessor(SseHttpClientTransportAutoConfiguration.class); + @Bean + PropertiesMcpSseClientConnectionDetails mcpSseClientConnectionDetails(McpSseClientProperties sseProperties) { + return new PropertiesMcpSseClientConnectionDetails(sseProperties); + } + /** * Creates a list of HTTP client-based SSE transports for MCP communication. * @@ -84,7 +91,8 @@ public class SseHttpClientTransportAutoConfiguration { *
  • ObjectMapper for JSON processing *
  • A sync or async HTTP request customizer. Sync takes precedence. * - * @param sseProperties the SSE client properties containing server configurations + * @param connectionDetails the SSE client connection details containing server + * configurations * @param objectMapperProvider the provider for ObjectMapper or a new instance if not * available * @param syncHttpRequestCustomizer provider for @@ -94,7 +102,7 @@ public class SseHttpClientTransportAutoConfiguration { * @return list of named MCP transports */ @Bean - public List sseHttpClientTransports(McpSseClientProperties sseProperties, + public List sseHttpClientTransports(McpSseClientConnectionDetails connectionDetails, ObjectProvider objectMapperProvider, ObjectProvider syncHttpRequestCustomizer, ObjectProvider asyncHttpRequestCustomizer) { @@ -103,7 +111,7 @@ public List sseHttpClientTransports(McpSseClientPropert List sseTransports = new ArrayList<>(); - for (Map.Entry serverParameters : sseProperties.getConnections().entrySet()) { + for (Map.Entry serverParameters : connectionDetails.getConnections().entrySet()) { String baseUrl = serverParameters.getValue().url(); String sseEndpoint = serverParameters.getValue().sseEndpoint() != null diff --git a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/main/java/org/springframework/ai/mcp/client/webflux/autoconfigure/SseWebFluxTransportAutoConfiguration.java b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/main/java/org/springframework/ai/mcp/client/webflux/autoconfigure/SseWebFluxTransportAutoConfiguration.java index 595cd97dfa6..992ec86a302 100644 --- a/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/main/java/org/springframework/ai/mcp/client/webflux/autoconfigure/SseWebFluxTransportAutoConfiguration.java +++ b/auto-configurations/mcp/spring-ai-autoconfigure-mcp-client-webflux/src/main/java/org/springframework/ai/mcp/client/webflux/autoconfigure/SseWebFluxTransportAutoConfiguration.java @@ -23,7 +23,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport; +import org.springframework.ai.mcp.client.common.autoconfigure.McpSseClientConnectionDetails; import org.springframework.ai.mcp.client.common.autoconfigure.NamedClientMcpTransport; +import org.springframework.ai.mcp.client.common.autoconfigure.PropertiesMcpSseClientConnectionDetails; import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpClientCommonProperties; import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpSseClientProperties; import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpSseClientProperties.SseParameters; @@ -63,6 +65,11 @@ matchIfMissing = true) public class SseWebFluxTransportAutoConfiguration { + @Bean + PropertiesMcpSseClientConnectionDetails mcpSseClientConnectionDetails(McpSseClientProperties sseProperties) { + return new PropertiesMcpSseClientConnectionDetails(sseProperties); + } + /** * Creates a list of WebFlux-based SSE transports for MCP communication. * @@ -73,14 +80,14 @@ public class SseWebFluxTransportAutoConfiguration { *
  • ObjectMapper for JSON processing *
  • Server connection parameters from properties * - * @param sseProperties the SSE client properties containing server configurations + * @param connectionDetails the SSE client properties containing server configurations * @param webClientBuilderProvider the provider for WebClient.Builder * @param objectMapperProvider the provider for ObjectMapper or a new instance if not * available * @return list of named MCP transports */ @Bean - public List sseWebFluxClientTransports(McpSseClientProperties sseProperties, + public List sseWebFluxClientTransports(McpSseClientConnectionDetails connectionDetails, ObjectProvider webClientBuilderProvider, ObjectProvider objectMapperProvider) { @@ -89,7 +96,7 @@ public List sseWebFluxClientTransports(McpSseClientProp var webClientBuilderTemplate = webClientBuilderProvider.getIfAvailable(WebClient::builder); var objectMapper = objectMapperProvider.getIfAvailable(ObjectMapper::new); - for (Map.Entry serverParameters : sseProperties.getConnections().entrySet()) { + for (Map.Entry serverParameters : connectionDetails.getConnections().entrySet()) { var webClientBuilder = webClientBuilderTemplate.clone().baseUrl(serverParameters.getValue().url()); String sseEndpoint = serverParameters.getValue().sseEndpoint() != null ? serverParameters.getValue().sseEndpoint() : "/sse"; diff --git a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/docker-compose.adoc b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/docker-compose.adoc index b729ab31dcf..f71b38f1c7a 100644 --- a/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/docker-compose.adoc +++ b/spring-ai-docs/src/main/antora/modules/ROOT/pages/api/docker-compose.adoc @@ -54,6 +54,9 @@ The following service connection factories are provided in the `spring-ai-spring | `WeaviateConnectionDetails` | Containers named `semitechnologies/weaviate`, `cr.weaviate.io/semitechnologies/weaviate` + +| `McpSseClientConnectionDetails` +| Containers named `docker/mcp-gateway` |==== More service connections are provided by the spring boot module `spring-boot-docker-compose`. Refer to the https://docs.spring.io/spring-boot/reference/features/dev-services.html#features.dev-services.docker-compose[Docker Compose Support] documentation page for the full list. diff --git a/spring-ai-spring-boot-docker-compose/pom.xml b/spring-ai-spring-boot-docker-compose/pom.xml index b9416c30cec..642db247343 100644 --- a/spring-ai-spring-boot-docker-compose/pom.xml +++ b/spring-ai-spring-boot-docker-compose/pom.xml @@ -81,6 +81,20 @@ true + + org.springframework.ai + spring-ai-autoconfigure-mcp-client-httpclient + ${project.version} + true + + + + org.springframework.ai + spring-ai-autoconfigure-mcp-client-webflux + ${project.version} + true + + diff --git a/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/docker/DockerMcpGatewayDockerComposeConnectionDetailsFactory.java b/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/docker/DockerMcpGatewayDockerComposeConnectionDetailsFactory.java new file mode 100644 index 00000000000..3e27914d328 --- /dev/null +++ b/spring-ai-spring-boot-docker-compose/src/main/java/org/springframework/ai/docker/compose/service/connection/docker/DockerMcpGatewayDockerComposeConnectionDetailsFactory.java @@ -0,0 +1,69 @@ +/* + * Copyright 2023-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.docker.compose.service.connection.docker; + +import java.util.Map; + +import org.springframework.ai.mcp.client.common.autoconfigure.McpSseClientConnectionDetails; +import org.springframework.ai.mcp.client.common.autoconfigure.properties.McpSseClientProperties; +import org.springframework.boot.docker.compose.core.RunningService; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionDetailsFactory; +import org.springframework.boot.docker.compose.service.connection.DockerComposeConnectionSource; + +/** + * A {@link DockerComposeConnectionDetailsFactory} implementation that creates + * {@link McpSseClientConnectionDetails} for a Docker MCP Gateway instance running in a + * Docker container. + * + * @author Eddú Meléndez + */ +class DockerMcpGatewayDockerComposeConnectionDetailsFactory + extends DockerComposeConnectionDetailsFactory { + + private static final int GATEWAY_PORT = 8811; + + protected DockerMcpGatewayDockerComposeConnectionDetailsFactory() { + super("docker/mcp-gateway"); + } + + @Override + protected McpSseClientConnectionDetails getDockerComposeConnectionDetails(DockerComposeConnectionSource source) { + return new DockerAgentsGatewayContainerConnectionDetails(source.getRunningService()); + } + + /** + * {@link McpSseClientConnectionDetails} backed by a {@code Docker MCP Gateway} + * {@link RunningService}. + */ + static class DockerAgentsGatewayContainerConnectionDetails extends DockerComposeConnectionDetails + implements McpSseClientConnectionDetails { + + private final String url; + + DockerAgentsGatewayContainerConnectionDetails(RunningService service) { + super(service); + this.url = String.format("http://%s:%d", service.host(), service.ports().get(GATEWAY_PORT)); + } + + @Override + public Map getConnections() { + return Map.of("gateway", new McpSseClientProperties.SseParameters(this.url, "/sse")); + } + + } + +} diff --git a/spring-ai-spring-boot-docker-compose/src/main/resources/META-INF/spring.factories b/spring-ai-spring-boot-docker-compose/src/main/resources/META-INF/spring.factories index d6949d5de09..c32faeaff29 100644 --- a/spring-ai-spring-boot-docker-compose/src/main/resources/META-INF/spring.factories +++ b/spring-ai-spring-boot-docker-compose/src/main/resources/META-INF/spring.factories @@ -16,6 +16,7 @@ org.springframework.boot.autoconfigure.service.connection.ConnectionDetailsFactory=\ org.springframework.ai.docker.compose.service.connection.chroma.ChromaDockerComposeConnectionDetailsFactory,\ +org.springframework.ai.docker.compose.service.connection.docker.DockerMcpGatewayDockerComposeConnectionDetailsFactory,\ org.springframework.ai.docker.compose.service.connection.mongo.MongoDbAtlasLocalDockerComposeConnectionDetailsFactory,\ org.springframework.ai.docker.compose.service.connection.ollama.OllamaDockerComposeConnectionDetailsFactory,\ org.springframework.ai.docker.compose.service.connection.opensearch.AwsOpenSearchDockerComposeConnectionDetailsFactory,\ diff --git a/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/docker/DockerMcpGatewayDockerComposeConnectionDetailsFactoryIT.java b/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/docker/DockerMcpGatewayDockerComposeConnectionDetailsFactoryIT.java new file mode 100644 index 00000000000..c1e899dd061 --- /dev/null +++ b/spring-ai-spring-boot-docker-compose/src/test/java/org/springframework/ai/docker/compose/service/connection/docker/DockerMcpGatewayDockerComposeConnectionDetailsFactoryIT.java @@ -0,0 +1,41 @@ +/* + * Copyright 2023-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.ai.docker.compose.service.connection.docker; + +import org.junit.jupiter.api.Test; +import org.testcontainers.utility.DockerImageName; + +import org.springframework.ai.mcp.client.common.autoconfigure.McpSseClientConnectionDetails; +import org.springframework.boot.docker.compose.service.connection.test.AbstractDockerComposeIT; + +import static org.assertj.core.api.Assertions.assertThat; + +class DockerMcpGatewayDockerComposeConnectionDetailsFactoryIT extends AbstractDockerComposeIT { + + protected DockerMcpGatewayDockerComposeConnectionDetailsFactoryIT() { + super("docker-agents-gateway-compose.yaml", DockerImageName.parse("docker/mcp-gateway")); + } + + @Test + void runCreatesConnectionDetails() { + McpSseClientConnectionDetails connectionDetails = run(McpSseClientConnectionDetails.class); + assertThat(connectionDetails.getConnections()).hasSize(1); + assertThat(connectionDetails.getConnections().get("gateway").url()).startsWith("http://"); + assertThat(connectionDetails.getConnections().get("gateway").sseEndpoint()).contains("/sse"); + } + +} diff --git a/spring-ai-spring-boot-docker-compose/src/test/resources/org/springframework/ai/docker/compose/service/connection/docker/docker-agents-gateway-compose.yaml b/spring-ai-spring-boot-docker-compose/src/test/resources/org/springframework/ai/docker/compose/service/connection/docker/docker-agents-gateway-compose.yaml new file mode 100644 index 00000000000..15b2afe3ca9 --- /dev/null +++ b/spring-ai-spring-boot-docker-compose/src/test/resources/org/springframework/ai/docker/compose/service/connection/docker/docker-agents-gateway-compose.yaml @@ -0,0 +1,9 @@ +services: + mcp-gateway: + image: '{imageName}' + ports: + - 8811 + volumes: + - "/var/run/docker.sock:/var/run/docker.sock" + command: + - --transport=sse