From 343a4ff3c396345924e3d03fa6c72fe3183c9bec Mon Sep 17 00:00:00 2001 From: moze Date: Wed, 6 May 2026 02:34:27 +0200 Subject: [PATCH] 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 --- trueref-adapters/pom.xml | 17 ++++++++- .../com/trueref/adapter/in/mcp/McpConfig.java | 35 ++++++++++++++++--- .../trueref/adapter/in/rest/WebConfig.java | 22 +----------- .../src/main/resources/application.yml | 15 ++++---- 4 files changed, 54 insertions(+), 35 deletions(-) diff --git a/trueref-adapters/pom.xml b/trueref-adapters/pom.xml index c895095..1a69af1 100644 --- a/trueref-adapters/pom.xml +++ b/trueref-adapters/pom.xml @@ -44,12 +44,27 @@ springdoc-openapi-starter-webmvc-ui - + org.springframework.ai spring-ai-starter-mcp-server-webmvc + + + io.modelcontextprotocol.sdk + mcp-spring-webmvc + 0.18.1 + + + io.modelcontextprotocol.sdk + mcp-json-jackson2 + 0.18.1 + + com.h2database 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 9bb9bbe..e936600 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,22 +1,47 @@ 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.method.MethodToolCallbackProvider; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; 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 - * {@link MethodToolCallbackProvider} scans {@link TrueRefMcpTools} for methods annotated with - * {@link org.springframework.ai.tool.annotation.Tool} and publishes them on the MCP endpoint - * configured in {@code application.yml} (POST {@code /mcp} via - * {@code spring.ai.mcp.server.sse-message-endpoint}). + * 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. + * + *

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) 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 mcpStreamableRoutes( + WebMvcStreamableServerTransportProvider transport) { + return transport.getRouterFunction(); + } + @Bean public ToolCallbackProvider trueRefMcpToolCallbacks(TrueRefMcpTools tools) { return MethodToolCallbackProvider.builder().toolObjects(tools).build(); diff --git a/trueref-adapters/src/main/java/com/trueref/adapter/in/rest/WebConfig.java b/trueref-adapters/src/main/java/com/trueref/adapter/in/rest/WebConfig.java index ea95632..071d9b4 100644 --- a/trueref-adapters/src/main/java/com/trueref/adapter/in/rest/WebConfig.java +++ b/trueref-adapters/src/main/java/com/trueref/adapter/in/rest/WebConfig.java @@ -5,11 +5,6 @@ import java.util.Set; import org.jspecify.annotations.Nullable; import org.springframework.context.annotation.Configuration; 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.WebMvcConfigurer; 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 * 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 rejectPost() { - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ALLOW, "GET"); - return ResponseEntity.status(HttpStatus.METHOD_NOT_ALLOWED).headers(headers).build(); - } -} - @Configuration public class WebConfig implements WebMvcConfigurer { private static final Set EXCLUDED_PREFIXES = - Set.of("api/", "mcp", "sse", "swagger-ui/", "v3/api-docs", "actuator/"); + Set.of("api/", "mcp", "swagger-ui/", "v3/api-docs", "actuator/"); @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { diff --git a/trueref-bootstrap/src/main/resources/application.yml b/trueref-bootstrap/src/main/resources/application.yml index f771192..c6f8bee 100644 --- a/trueref-bootstrap/src/main/resources/application.yml +++ b/trueref-bootstrap/src/main/resources/application.yml @@ -19,12 +19,13 @@ spring: locations: classpath:db/migration mvc: async: - request-timeout: 0 # SSE streams must not time out - # Spring AI MCP server. In Spring AI 1.0.0 the WebMVC transport is SSE-based - # (WebMvcSseServerTransportProvider) — the closest available transport to the 2025-03-26 - # "Streamable HTTP" spec; there is no separate "protocol: streamable" property in this - # starter. JSON-RPC POSTs land on `sse-message-endpoint` (/mcp); server-initiated - # notifications stream over `sse-endpoint` (/sse). See com.trueref.adapter.in.mcp. + 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. + # Clients connect with type: http at POST /mcp. + autoconfigure: + exclude: org.springframework.ai.mcp.server.autoconfigure.McpWebMvcServerAutoConfiguration ai: mcp: server: @@ -32,8 +33,6 @@ spring: name: trueref version: 0.1.0 type: SYNC - sse-message-endpoint: /mcp - sse-endpoint: /sse server: port: 8080