fix(mcp): align SDK and wire streamable server manually
- align all io.modelcontextprotocol.sdk artifacts to 0.18.1 via dependencyManagement so Spring AI transitives no longer pull mcp 0.10.0 - exclude Spring AI's legacy MCP server/webmvc auto-config, which is binary- incompatible with the 0.18.1 streamable transport APIs - build McpSyncServer directly against WebMvcStreamableServerTransportProvider and adapt Spring AI ToolCallbacks to MCP SyncToolSpecifications manually - keep /mcp as the sole Streamable HTTP endpoint for both initialize/tool calls and optional SSE event streams - update MCP transport documentation to match the new runtime Validated locally with: - POST /mcp initialize -> HTTP 200 + Mcp-Session-Id - POST /mcp tools/list -> returns resolve-library-id + get-library-docs
This commit is contained in:
@@ -57,12 +57,10 @@
|
||||
<dependency>
|
||||
<groupId>io.modelcontextprotocol.sdk</groupId>
|
||||
<artifactId>mcp-spring-webmvc</artifactId>
|
||||
<version>0.18.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.modelcontextprotocol.sdk</groupId>
|
||||
<artifactId>mcp-json-jackson2</artifactId>
|
||||
<version>0.18.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- H2 + Flyway -->
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <p>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.
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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<List<ToolCallback>> toolCallbacks,
|
||||
List<ToolCallbackProvider> 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<McpServerFeatures.SyncToolSpecification> 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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}.
|
||||
*
|
||||
* <p>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.
|
||||
* <p>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;
|
||||
|
||||
Reference in New Issue
Block a user