feat(mcp): migrate to Streamable HTTP transport (MCP spec 2025-03-26)
- Upgrade mcp-spring-webmvc from 0.10.0 to 0.18.1 (adds WebMvcStreamableServerTransportProvider alongside the legacy SSE provider) - Add mcp-json-jackson2 0.18.1 for JacksonMcpJsonMapper adapter - Exclude McpWebMvcServerAutoConfiguration (SSE transport) via spring.autoconfigure.exclude; register WebMvcStreamableServerTransportProvider and its RouterFunction manually in McpConfig so Spring AI's McpServerAutoConfiguration picks up the correct transport bean - Remove sse-message-endpoint / sse-endpoint from application.yml; all MCP traffic now flows through POST+GET /mcp - Remove McpSseMethodNotAllowed workaround from WebConfig and drop 'sse' from SPA fallback exclusions (no longer needed) Clients should connect with type: http at https://trueref.sal.giize.com/mcp
This commit is contained in:
@@ -44,12 +44,27 @@
|
|||||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Spring AI MCP server (Streamable HTTP) -->
|
<!-- Spring AI MCP server — tool/resource registration and McpSyncServer wiring.
|
||||||
|
McpWebMvcServerAutoConfiguration (SSE transport) is excluded in application.yml;
|
||||||
|
the Streamable HTTP transport is wired manually in McpConfig. -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.ai</groupId>
|
<groupId>org.springframework.ai</groupId>
|
||||||
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
|
<artifactId>spring-ai-starter-mcp-server-webmvc</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- MCP Java SDK 0.18.1: provides WebMvcStreamableServerTransportProvider.
|
||||||
|
Overrides the 0.10.0 version pulled in by spring-ai-starter-mcp-server-webmvc. -->
|
||||||
|
<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 -->
|
<!-- H2 + Flyway -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.h2database</groupId>
|
<groupId>com.h2database</groupId>
|
||||||
|
|||||||
@@ -1,22 +1,47 @@
|
|||||||
package com.trueref.adapter.in.mcp;
|
package com.trueref.adapter.in.mcp;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import io.modelcontextprotocol.json.jackson2.JacksonMcpJsonMapper;
|
||||||
|
import io.modelcontextprotocol.server.transport.WebMvcStreamableServerTransportProvider;
|
||||||
import org.springframework.ai.tool.ToolCallbackProvider;
|
import org.springframework.ai.tool.ToolCallbackProvider;
|
||||||
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
|
import org.springframework.ai.tool.method.MethodToolCallbackProvider;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.web.servlet.function.RouterFunction;
|
||||||
|
import org.springframework.web.servlet.function.ServerResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the trueref MCP tool callbacks with Spring AI's MCP WebMVC auto-configuration. The
|
* Wires the MCP Streamable HTTP transport (2025-03-26 spec) and registers trueref tool callbacks.
|
||||||
* {@link MethodToolCallbackProvider} scans {@link TrueRefMcpTools} for methods annotated with
|
*
|
||||||
* {@link org.springframework.ai.tool.annotation.Tool} and publishes them on the MCP endpoint
|
* <p>Spring AI 1.0.0 only ships {@link io.modelcontextprotocol.server.transport.WebMvcSseServerTransportProvider}
|
||||||
* configured in {@code application.yml} (POST {@code /mcp} via
|
* (legacy SSE). We exclude {@code McpWebMvcServerAutoConfiguration} in {@code application.yml} and
|
||||||
* {@code spring.ai.mcp.server.sse-message-endpoint}).
|
* register {@link WebMvcStreamableServerTransportProvider} directly — Spring AI's
|
||||||
|
* {@code McpServerAutoConfiguration} accepts any {@code McpServerTransportProvider} bean.
|
||||||
|
*
|
||||||
|
* <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
|
@Configuration
|
||||||
@EnableConfigurationProperties(McpProperties.class)
|
@EnableConfigurationProperties(McpProperties.class)
|
||||||
public class McpConfig {
|
public class McpConfig {
|
||||||
|
|
||||||
|
/** Streamable HTTP transport — handles both POST (JSON-RPC) and GET (SSE stream) on /mcp. */
|
||||||
|
@Bean
|
||||||
|
public WebMvcStreamableServerTransportProvider streamableTransportProvider(ObjectMapper objectMapper) {
|
||||||
|
return WebMvcStreamableServerTransportProvider.builder()
|
||||||
|
.jsonMapper(new JacksonMcpJsonMapper(objectMapper))
|
||||||
|
.mcpEndpoint("/mcp")
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Registers the transport's GET+POST routes with Spring MVC. */
|
||||||
|
@Bean
|
||||||
|
public RouterFunction<ServerResponse> mcpStreamableRoutes(
|
||||||
|
WebMvcStreamableServerTransportProvider transport) {
|
||||||
|
return transport.getRouterFunction();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public ToolCallbackProvider trueRefMcpToolCallbacks(TrueRefMcpTools tools) {
|
public ToolCallbackProvider trueRefMcpToolCallbacks(TrueRefMcpTools tools) {
|
||||||
return MethodToolCallbackProvider.builder().toolObjects(tools).build();
|
return MethodToolCallbackProvider.builder().toolObjects(tools).build();
|
||||||
|
|||||||
@@ -5,11 +5,6 @@ import java.util.Set;
|
|||||||
import org.jspecify.annotations.Nullable;
|
import org.jspecify.annotations.Nullable;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||||
import org.springframework.web.servlet.resource.PathResourceResolver;
|
import org.springframework.web.servlet.resource.PathResourceResolver;
|
||||||
@@ -20,26 +15,11 @@ import org.springframework.web.servlet.resource.PathResourceResolver;
|
|||||||
* explicitly excluded — Spring routes those first anyway, but we exclude them defensively so the
|
* explicitly excluded — Spring routes those first anyway, but we exclude them defensively so the
|
||||||
* resource resolver does not attempt a fallback for them.
|
* resource resolver does not attempt a fallback for them.
|
||||||
*/
|
*/
|
||||||
/**
|
|
||||||
* Explicit POST handler for the SSE endpoint. Modern MCP clients (e.g. Claude Code) probe for
|
|
||||||
* Streamable HTTP transport by POSTing to the server URL first. Returning 405 here tells them this
|
|
||||||
* server uses the legacy HTTP+SSE transport, triggering the correct GET-based SSE fallback.
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
class McpSseMethodNotAllowed {
|
|
||||||
@PostMapping("/sse")
|
|
||||||
ResponseEntity<Void> rejectPost() {
|
|
||||||
HttpHeaders headers = new HttpHeaders();
|
|
||||||
headers.add(HttpHeaders.ALLOW, "GET");
|
|
||||||
return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).headers(headers).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public class WebConfig implements WebMvcConfigurer {
|
public class WebConfig implements WebMvcConfigurer {
|
||||||
|
|
||||||
private static final Set<String> EXCLUDED_PREFIXES =
|
private static final Set<String> EXCLUDED_PREFIXES =
|
||||||
Set.of("api/", "mcp", "sse", "swagger-ui/", "v3/api-docs", "actuator/");
|
Set.of("api/", "mcp", "swagger-ui/", "v3/api-docs", "actuator/");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
|||||||
@@ -19,12 +19,13 @@ spring:
|
|||||||
locations: classpath:db/migration
|
locations: classpath:db/migration
|
||||||
mvc:
|
mvc:
|
||||||
async:
|
async:
|
||||||
request-timeout: 0 # SSE streams must not time out
|
request-timeout: 0 # MCP GET streams must not time out
|
||||||
# Spring AI MCP server. In Spring AI 1.0.0 the WebMVC transport is SSE-based
|
# Spring AI MCP server — Streamable HTTP transport (MCP spec 2025-03-26).
|
||||||
# (WebMvcSseServerTransportProvider) — the closest available transport to the 2025-03-26
|
# McpWebMvcServerAutoConfiguration (SSE transport) is excluded below;
|
||||||
# "Streamable HTTP" spec; there is no separate "protocol: streamable" property in this
|
# WebMvcStreamableServerTransportProvider is wired manually in McpConfig.
|
||||||
# starter. JSON-RPC POSTs land on `sse-message-endpoint` (/mcp); server-initiated
|
# Clients connect with type: http at POST /mcp.
|
||||||
# notifications stream over `sse-endpoint` (/sse). See com.trueref.adapter.in.mcp.
|
autoconfigure:
|
||||||
|
exclude: org.springframework.ai.mcp.server.autoconfigure.McpWebMvcServerAutoConfiguration
|
||||||
ai:
|
ai:
|
||||||
mcp:
|
mcp:
|
||||||
server:
|
server:
|
||||||
@@ -32,8 +33,6 @@ spring:
|
|||||||
name: trueref
|
name: trueref
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
type: SYNC
|
type: SYNC
|
||||||
sse-message-endpoint: /mcp
|
|
||||||
sse-endpoint: /sse
|
|
||||||
|
|
||||||
server:
|
server:
|
||||||
port: 8080
|
port: 8080
|
||||||
|
|||||||
Reference in New Issue
Block a user