From 44f432961a614216c086865ea88a015e4cf50fd4 Mon Sep 17 00:00:00 2001 From: Jordan Zimmerman Date: Thu, 30 Oct 2025 10:06:21 +0000 Subject: [PATCH 1/3] Add method to get the MCP handler This will allow setting custom MCP handlers that defer to the handler that is set by the framework --- .../transport/HttpServletStatelessServerTransport.java | 5 +++++ .../spec/McpStatelessServerTransport.java | 6 ++++-- .../server/transport/WebFluxStatelessServerTransport.java | 5 +++++ .../server/transport/WebMvcStatelessServerTransport.java | 5 +++++ 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java index 40767f416..ab174af88 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java @@ -74,6 +74,11 @@ public void setMcpHandler(McpStatelessServerHandler mcpHandler) { this.mcpHandler = mcpHandler; } + @Override + public McpStatelessServerHandler getMcpHandler() { + return mcpHandler; + } + @Override public Mono closeGracefully() { return Mono.fromRunnable(() -> this.isClosing = true); diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpStatelessServerTransport.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpStatelessServerTransport.java index d1c2e5206..b64a05c2a 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpStatelessServerTransport.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpStatelessServerTransport.java @@ -4,15 +4,17 @@ package io.modelcontextprotocol.spec; -import java.util.List; - import io.modelcontextprotocol.server.McpStatelessServerHandler; import reactor.core.publisher.Mono; +import java.util.List; + public interface McpStatelessServerTransport { void setMcpHandler(McpStatelessServerHandler mcpHandler); + McpStatelessServerHandler getMcpHandler(); + /** * Immediately closes all the transports with connected clients and releases any * associated resources. diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java index 400be341e..b6213c003 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java @@ -66,6 +66,11 @@ public void setMcpHandler(McpStatelessServerHandler mcpHandler) { this.mcpHandler = mcpHandler; } + @Override + public McpStatelessServerHandler getMcpHandler() { + return mcpHandler; + } + @Override public Mono closeGracefully() { return Mono.fromRunnable(() -> this.isClosing = true); diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java index 4223084ff..c9d5c85aa 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java @@ -70,6 +70,11 @@ public void setMcpHandler(McpStatelessServerHandler mcpHandler) { this.mcpHandler = mcpHandler; } + @Override + public McpStatelessServerHandler getMcpHandler() { + return mcpHandler; + } + @Override public Mono closeGracefully() { return Mono.fromRunnable(() -> this.isClosing = true); From dee088d4975a88b1bc32236d431eb4aae732ce8e Mon Sep 17 00:00:00 2001 From: Jordan Zimmerman Date: Thu, 30 Oct 2025 12:55:16 +0000 Subject: [PATCH 2/3] Make mcpHandler volatile and contextExtractor final --- .../transport/HttpServletStatelessServerTransport.java | 4 ++-- .../server/transport/WebFluxStatelessServerTransport.java | 6 +++--- .../server/transport/WebMvcStatelessServerTransport.java | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java index ab174af88..9706d019e 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java @@ -52,9 +52,9 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements private final String mcpEndpoint; - private McpStatelessServerHandler mcpHandler; + private volatile McpStatelessServerHandler mcpHandler; - private McpTransportContextExtractor contextExtractor; + private final McpTransportContextExtractor contextExtractor; private volatile boolean isClosing = false; diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java index b6213c003..f570fbe05 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java @@ -4,8 +4,8 @@ package io.modelcontextprotocol.server.transport; -import io.modelcontextprotocol.json.McpJsonMapper; import io.modelcontextprotocol.common.McpTransportContext; +import io.modelcontextprotocol.json.McpJsonMapper; import io.modelcontextprotocol.server.McpStatelessServerHandler; import io.modelcontextprotocol.server.McpTransportContextExtractor; import io.modelcontextprotocol.spec.McpError; @@ -40,9 +40,9 @@ public class WebFluxStatelessServerTransport implements McpStatelessServerTransp private final RouterFunction routerFunction; - private McpStatelessServerHandler mcpHandler; + private volatile McpStatelessServerHandler mcpHandler; - private McpTransportContextExtractor contextExtractor; + private final McpTransportContextExtractor contextExtractor; private volatile boolean isClosing = false; diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java index c9d5c85aa..267bc50e1 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java @@ -44,9 +44,9 @@ public class WebMvcStatelessServerTransport implements McpStatelessServerTranspo private final RouterFunction routerFunction; - private McpStatelessServerHandler mcpHandler; + private volatile McpStatelessServerHandler mcpHandler; - private McpTransportContextExtractor contextExtractor; + private final McpTransportContextExtractor contextExtractor; private volatile boolean isClosing = false; From 1072617f9917581a71997de92285aa8cd120a507 Mon Sep 17 00:00:00 2001 From: Jordan Zimmerman Date: Thu, 30 Oct 2025 12:57:53 +0000 Subject: [PATCH 3/3] Support an HTTP GET handler for HttpServletStatelessServerTransport Currently, `HttpServletStatelessServerTransport` always returns `SC_METHOD_NOT_ALLOWED` for GET. With this change, consumers of the library can implement their own GET handler so that they can implement the Session semantics of the MCP protocol. --- .../HttpServletStatelessServerTransport.java | 33 +++++++++++++------ .../WebFluxStatelessServerTransport.java | 16 ++++++++- .../WebMvcStatelessServerTransport.java | 16 ++++++++- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java index 9706d019e..20a953dbb 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java @@ -4,16 +4,8 @@ package io.modelcontextprotocol.server.transport; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.PrintWriter; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.modelcontextprotocol.json.McpJsonMapper; - import io.modelcontextprotocol.common.McpTransportContext; +import io.modelcontextprotocol.json.McpJsonMapper; import io.modelcontextprotocol.server.McpStatelessServerHandler; import io.modelcontextprotocol.server.McpTransportContextExtractor; import io.modelcontextprotocol.spec.McpError; @@ -25,8 +17,14 @@ import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import reactor.core.publisher.Mono; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; + /** * Implementation of an HttpServlet based {@link McpStatelessServerTransport}. * @@ -58,6 +56,9 @@ public class HttpServletStatelessServerTransport extends HttpServlet implements private volatile boolean isClosing = false; + private volatile GetHandler getHandler = (request, response) -> response + .sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + private HttpServletStatelessServerTransport(McpJsonMapper jsonMapper, String mcpEndpoint, McpTransportContextExtractor contextExtractor) { Assert.notNull(jsonMapper, "jsonMapper must not be null"); @@ -84,6 +85,18 @@ public Mono closeGracefully() { return Mono.fromRunnable(() -> this.isClosing = true); } + public void setGetHandler(GetHandler getHandler) { + Assert.notNull(getHandler, "getHandler must not be null"); + + this.getHandler = getHandler; + } + + public interface GetHandler { + + void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException; + + } + /** * Handles GET requests - returns 405 METHOD NOT ALLOWED as stateless transport * doesn't support GET requests. @@ -102,7 +115,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) return; } - response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + getHandler.doGet(request, response); } /** diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java index f570fbe05..75fe4950b 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStatelessServerTransport.java @@ -46,6 +46,8 @@ public class WebFluxStatelessServerTransport implements McpStatelessServerTransp private volatile boolean isClosing = false; + private volatile GetHandler getHandler = (request) -> ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED).build(); + private WebFluxStatelessServerTransport(McpJsonMapper jsonMapper, String mcpEndpoint, McpTransportContextExtractor contextExtractor) { Assert.notNull(jsonMapper, "jsonMapper must not be null"); @@ -92,8 +94,20 @@ public RouterFunction getRouterFunction() { return this.routerFunction; } + public interface GetHandler { + + Mono doGet(ServerRequest request); + + } + + public void setGetHandler(GetHandler getHandler) { + Assert.notNull(getHandler, "getHandler must not be null"); + + this.getHandler = getHandler; + } + private Mono handleGet(ServerRequest request) { - return ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED).build(); + return getHandler.doGet(request); } private Mono handlePost(ServerRequest request) { diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java index 267bc50e1..055885bb9 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStatelessServerTransport.java @@ -50,6 +50,8 @@ public class WebMvcStatelessServerTransport implements McpStatelessServerTranspo private volatile boolean isClosing = false; + private volatile GetHandler getHandler = (request) -> ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED).build(); + private WebMvcStatelessServerTransport(McpJsonMapper jsonMapper, String mcpEndpoint, McpTransportContextExtractor contextExtractor) { Assert.notNull(jsonMapper, "jsonMapper must not be null"); @@ -96,8 +98,20 @@ public RouterFunction getRouterFunction() { return this.routerFunction; } + public interface GetHandler { + + ServerResponse doGet(ServerRequest request); + + } + + public void setGetHandler(GetHandler getHandler) { + Assert.notNull(getHandler, "getHandler must not be null"); + + this.getHandler = getHandler; + } + private ServerResponse handleGet(ServerRequest request) { - return ServerResponse.status(HttpStatus.METHOD_NOT_ALLOWED).build(); + return getHandler.doGet(request); } private ServerResponse handlePost(ServerRequest request) {