org.springdoc
diff --git a/trueref-adapters/pom.xml b/trueref-adapters/pom.xml
index 1a69af1..a1bb72b 100644
--- a/trueref-adapters/pom.xml
+++ b/trueref-adapters/pom.xml
@@ -57,12 +57,10 @@
io.modelcontextprotocol.sdk
mcp-spring-webmvc
- 0.18.1
io.modelcontextprotocol.sdk
mcp-json-jackson2
- 0.18.1
diff --git a/trueref-adapters/src/main/java/com/trueref/adapter/in/mcp/McpConfig.java b/trueref-adapters/src/main/java/com/trueref/adapter/in/mcp/McpConfig.java
index e936600..04cf349 100644
--- a/trueref-adapters/src/main/java/com/trueref/adapter/in/mcp/McpConfig.java
+++ b/trueref-adapters/src/main/java/com/trueref/adapter/in/mcp/McpConfig.java
@@ -1,10 +1,20 @@
package com.trueref.adapter.in.mcp;
import com.fasterxml.jackson.databind.ObjectMapper;
+import io.modelcontextprotocol.server.McpServer;
+import io.modelcontextprotocol.server.McpServerFeatures;
+import io.modelcontextprotocol.server.McpSyncServer;
import io.modelcontextprotocol.json.jackson2.JacksonMcpJsonMapper;
+import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.server.transport.WebMvcStreamableServerTransportProvider;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.springframework.ai.mcp.server.autoconfigure.McpServerProperties;
+import org.springframework.ai.tool.ToolCallback;
import org.springframework.ai.tool.ToolCallbackProvider;
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
+import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -14,16 +24,16 @@ import org.springframework.web.servlet.function.ServerResponse;
/**
* Wires the MCP Streamable HTTP transport (2025-03-26 spec) and registers trueref tool callbacks.
*
- * Spring AI 1.0.0 only ships {@link io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider}
- * (legacy SSE). We exclude {@code McpWebMvcServerAutoConfiguration} in {@code application.yml} and
- * register {@link WebMvcStreamableServerTransportProvider} directly — Spring AI's
- * {@code McpServerAutoConfiguration} accepts any {@code McpServerTransportProvider} bean.
+ *
Spring AI 1.0.0 only auto-configures the legacy SSE/stdIO MCP server path. The current
+ * Streamable HTTP provider in MCP SDK 0.18.1 uses the newer
+ * {@code McpStreamableServerTransportProvider} interface, so we exclude Spring AI's MCP server
+ * auto-configuration in {@code application.yml} and build the {@link McpSyncServer} manually.
*
*
Clients connect with {@code type: http} at {@code POST /mcp} (JSON-RPC) and optionally open
* a long-poll GET {@code /mcp} stream for server-initiated notifications.
*/
@Configuration
-@EnableConfigurationProperties(McpProperties.class)
+@EnableConfigurationProperties({McpProperties.class, McpServerProperties.class})
public class McpConfig {
/** Streamable HTTP transport — handles both POST (JSON-RPC) and GET (SSE stream) on /mcp. */
@@ -42,8 +52,85 @@ public class McpConfig {
return transport.getRouterFunction();
}
+ @Bean
+ public McpSyncServer mcpSyncServer(
+ WebMvcStreamableServerTransportProvider transport,
+ ObjectMapper objectMapper,
+ McpServerProperties serverProperties,
+ ObjectProvider> toolCallbacks,
+ List toolCallbackProviders) {
+
+ var jsonMapper = new JacksonMcpJsonMapper(objectMapper);
+ var capabilitiesBuilder = McpSchema.ServerCapabilities.builder();
+ var serverBuilder = McpServer.sync(transport)
+ .serverInfo(serverProperties.getName(), serverProperties.getVersion());
+
+ if (serverProperties.getCapabilities().isTool()) {
+ capabilitiesBuilder.tools(serverProperties.isToolChangeNotification());
+
+ List toolSpecifications = new ArrayList<>();
+ toolCallbacks.stream()
+ .flatMap(List::stream)
+ .map(toolCallback -> toSyncToolSpecification(toolCallback, objectMapper, jsonMapper))
+ .forEach(toolSpecifications::add);
+
+ toolCallbackProviders.stream()
+ .flatMap(provider -> Arrays.stream(provider.getToolCallbacks()))
+ .map(toolCallback -> toSyncToolSpecification(toolCallback, objectMapper, jsonMapper))
+ .forEach(toolSpecifications::add);
+
+ if (!toolSpecifications.isEmpty()) {
+ serverBuilder.tools(toolSpecifications);
+ }
+ }
+
+ if (serverProperties.getCapabilities().isResource()) {
+ capabilitiesBuilder.resources(false, serverProperties.isResourceChangeNotification());
+ }
+ if (serverProperties.getCapabilities().isPrompt()) {
+ capabilitiesBuilder.prompts(serverProperties.isPromptChangeNotification());
+ }
+ if (serverProperties.getCapabilities().isCompletion()) {
+ capabilitiesBuilder.completions();
+ }
+
+ if (serverProperties.getInstructions() != null) {
+ serverBuilder.instructions(serverProperties.getInstructions());
+ }
+ if (serverProperties.getRequestTimeout() != null) {
+ serverBuilder.requestTimeout(serverProperties.getRequestTimeout());
+ }
+
+ return serverBuilder.capabilities(capabilitiesBuilder.build()).build();
+ }
+
@Bean
public ToolCallbackProvider trueRefMcpToolCallbacks(TrueRefMcpTools tools) {
return MethodToolCallbackProvider.builder().toolObjects(tools).build();
}
+
+ private McpServerFeatures.SyncToolSpecification toSyncToolSpecification(
+ ToolCallback toolCallback,
+ ObjectMapper objectMapper,
+ JacksonMcpJsonMapper jsonMapper) {
+ var tool = McpSchema.Tool.builder()
+ .name(toolCallback.getToolDefinition().name())
+ .description(toolCallback.getToolDefinition().description())
+ .inputSchema(jsonMapper, toolCallback.getToolDefinition().inputSchema())
+ .build();
+
+ return new McpServerFeatures.SyncToolSpecification(tool, (exchange, arguments) -> {
+ try {
+ String result = toolCallback.call(objectMapper.writeValueAsString(arguments));
+ return new McpSchema.CallToolResult(result, false);
+ }
+ catch (Exception ex) {
+ String message = ex.getMessage();
+ if (message == null || message.isBlank()) {
+ message = ex.getClass().getSimpleName();
+ }
+ return new McpSchema.CallToolResult(message, true);
+ }
+ });
+ }
}
diff --git a/trueref-adapters/src/main/java/com/trueref/adapter/in/mcp/package-info.java b/trueref-adapters/src/main/java/com/trueref/adapter/in/mcp/package-info.java
index 1dfd957..bfa5331 100644
--- a/trueref-adapters/src/main/java/com/trueref/adapter/in/mcp/package-info.java
+++ b/trueref-adapters/src/main/java/com/trueref/adapter/in/mcp/package-info.java
@@ -1,16 +1,12 @@
/**
* Driving adapter: Model Context Protocol (MCP) server exposing the two Context7-compatible
* tools ({@code resolve-library-id}, {@code get-library-docs}) over Spring AI's MCP WebMVC
- * transport. The HTTP message endpoint is wired to {@code POST /mcp} via
- * {@code spring.ai.mcp.server.sse-message-endpoint}.
+ * transport. The adapter publishes the current Streamable HTTP transport on {@code /mcp}.
*
- * Spring AI 1.0.0 ships the SSE-based WebMVC transport
- * ({@code WebMvcSseServerTransportProvider}); the 2025-03-26 "Streamable HTTP" transport is
- * not a separate selectable property in this version. Clients that POST JSON-RPC to the
- * configured message endpoint receive JSON-RPC responses; the server additionally opens an
- * SSE stream on the configured {@code sse-endpoint} for server-initiated notifications. This
- * is the closest equivalent Spring AI 1.0.0 provides to Streamable HTTP and is the
- * intended/only transport of this adapter.
+ *
Spring AI 1.0.0 still auto-configures only the legacy SSE WebMVC transport, so this adapter
+ * wires {@code WebMvcStreamableServerTransportProvider} manually. Clients send JSON-RPC requests
+ * to {@code POST /mcp}; server-initiated notifications may be streamed from {@code GET /mcp}
+ * using the MCP Streamable HTTP transport.
*/
@org.jspecify.annotations.NullMarked
package com.trueref.adapter.in.mcp;
diff --git a/trueref-bootstrap/src/main/resources/application.yml b/trueref-bootstrap/src/main/resources/application.yml
index c6f8bee..81caef7 100644
--- a/trueref-bootstrap/src/main/resources/application.yml
+++ b/trueref-bootstrap/src/main/resources/application.yml
@@ -21,11 +21,14 @@ spring:
async:
request-timeout: 0 # MCP GET streams must not time out
# Spring AI MCP server — Streamable HTTP transport (MCP spec 2025-03-26).
- # McpWebMvcServerAutoConfiguration (SSE transport) is excluded below;
- # WebMvcStreamableServerTransportProvider is wired manually in McpConfig.
+ # Spring AI 1.0.0's MCP auto-config only knows the legacy SSE/stdIO transport interfaces,
+ # so both MCP auto-config classes are excluded below and the Streamable HTTP transport plus
+ # McpSyncServer are wired manually in McpConfig.
# Clients connect with type: http at POST /mcp.
autoconfigure:
- exclude: org.springframework.ai.mcp.server.autoconfigure.McpWebMvcServerAutoConfiguration
+ exclude:
+ - org.springframework.ai.mcp.server.autoconfigure.McpWebMvcServerAutoConfiguration
+ - org.springframework.ai.mcp.server.autoconfigure.McpServerAutoConfiguration
ai:
mcp:
server: