From 7a3b20d245b3b5957a9149077ab00a9385b48d72 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 24 Sep 2025 12:42:55 +0200 Subject: [PATCH 01/10] feature Add methods to McpTransportContext This change adds methods to McpTransportContext: `protocolVersion`, `lastEventId`, `sessionId`, and `principal`. It also provides an implementation of Context extract for Servlet requests, which uses the HTTP Headers defined in the specification. --- .../common/DefaultMcpTransportContext.java | 17 ++++ .../common/McpTransportContext.java | 30 +++++++ .../server/McpTransportContextExtractor.java | 2 + ...etRequestMcpTransportContextExtractor.java | 42 ++++++++++ .../server/servlet/package-info.java | 7 ++ ...HttpServletSseServerTransportProvider.java | 7 +- .../HttpServletStatelessServerTransport.java | 7 +- ...vletStreamableServerTransportProvider.java | 8 +- ...erMcpTransportContextIntegrationTests.java | 15 +++- .../DefaultMcpTransportContextTest.java | 53 ++++++++++++ ...erMcpTransportContextIntegrationTests.java | 15 +++- .../HttpServletSseIntegrationTests.java | 11 ++- ...HttpServletStreamableIntegrationTests.java | 12 ++- ...uestMcpTransportContextExtractorTests.java | 81 +++++++++++++++++++ ...erRequestMcpTransportContextExtractor.java | 5 ++ 15 files changed, 289 insertions(+), 23 deletions(-) create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java create mode 100644 mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/package-info.java create mode 100644 mcp-core/src/test/java/io/modelcontextprotocol/common/DefaultMcpTransportContextTest.java create mode 100644 mcp-core/src/test/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractorTests.java create mode 100644 mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/ServerRequestMcpTransportContextExtractor.java diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/common/DefaultMcpTransportContext.java b/mcp-core/src/main/java/io/modelcontextprotocol/common/DefaultMcpTransportContext.java index cde637b15..a46458189 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/common/DefaultMcpTransportContext.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/common/DefaultMcpTransportContext.java @@ -5,7 +5,9 @@ package io.modelcontextprotocol.common; import java.util.Map; +import java.util.Optional; +import io.modelcontextprotocol.spec.HttpHeaders; import io.modelcontextprotocol.util.Assert; /** @@ -28,6 +30,21 @@ public Object get(String key) { return this.metadata.get(key); } + @Override + public Optional lastEventId() { + return Optional.ofNullable(metadata.get(HttpHeaders.LAST_EVENT_ID)).map(Object::toString); + } + + @Override + public Optional sessionId() { + return Optional.ofNullable(metadata.get(HttpHeaders.MCP_SESSION_ID)).map(Object::toString); + } + + @Override + public Optional protocolVersion() { + return Optional.ofNullable(metadata.get(HttpHeaders.PROTOCOL_VERSION)).map(Object::toString); + } + @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/common/McpTransportContext.java b/mcp-core/src/main/java/io/modelcontextprotocol/common/McpTransportContext.java index 46a2ccf84..e8e29da42 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/common/McpTransportContext.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/common/McpTransportContext.java @@ -4,8 +4,10 @@ package io.modelcontextprotocol.common; +import java.security.Principal; import java.util.Collections; import java.util.Map; +import java.util.Optional; /** * Context associated with the transport layer. It allows to add transport-level metadata @@ -43,4 +45,32 @@ static McpTransportContext create(Map metadata) { */ Object get(String key); + /** + * @return The MCP Protocl Version + */ + default Optional protocolVersion() { + return Optional.empty(); + } + + /** + * @return The Session ID + */ + default Optional sessionId() { + return Optional.empty(); + } + + /** + * @return The Last Event ID + */ + default Optional lastEventId() { + return Optional.empty(); + } + + /** + * @return The Principal. it may represent the authenticated user. + */ + default Optional principal() { + return Optional.empty(); + } + } diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java index ea9f05a4f..fe63755c2 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java @@ -6,6 +6,8 @@ import io.modelcontextprotocol.common.McpTransportContext; +import java.util.Optional; + /** * The contract for extracting metadata from a generic transport request of type * {@link T}. diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java new file mode 100644 index 000000000..d8129646c --- /dev/null +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java @@ -0,0 +1,42 @@ +/* + * Copyright 2024-2024 the original author or authors. + */ +package io.modelcontextprotocol.server.servlet; + +import io.modelcontextprotocol.common.McpTransportContext; +import io.modelcontextprotocol.server.McpTransportContextExtractor; +import io.modelcontextprotocol.spec.ProtocolVersions; +import jakarta.servlet.http.HttpServletRequest; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * {@link McpTransportContextExtractor} implementation for {@link HttpServletRequest}. + */ +public class HttpServletRequestMcpTransportContextExtractor + implements McpTransportContextExtractor { + + @Override + public McpTransportContext extract(HttpServletRequest request) { + return McpTransportContext.create(metadata(request)); + } + + /** + * @param request Servlet Request + * @return Extracts Map for MCP Transport Context + */ + protected Map metadata(HttpServletRequest request) { + Map metadata = new HashMap<>(); + metadata.put(io.modelcontextprotocol.spec.HttpHeaders.PROTOCOL_VERSION, + Optional.ofNullable(request.getHeader(io.modelcontextprotocol.spec.HttpHeaders.PROTOCOL_VERSION)) + .orElse(ProtocolVersions.MCP_2025_03_26)); + Optional.ofNullable(request.getHeader(io.modelcontextprotocol.spec.HttpHeaders.MCP_SESSION_ID)) + .ifPresent(v -> metadata.put(io.modelcontextprotocol.spec.HttpHeaders.MCP_SESSION_ID, v)); + Optional.ofNullable(request.getHeader(io.modelcontextprotocol.spec.HttpHeaders.LAST_EVENT_ID)) + .ifPresent(v -> metadata.put(io.modelcontextprotocol.spec.HttpHeaders.LAST_EVENT_ID, v)); + return metadata; + } + +} diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/package-info.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/package-info.java new file mode 100644 index 000000000..5088da87c --- /dev/null +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/package-info.java @@ -0,0 +1,7 @@ +/* + * Copyright 2024-2024 the original author or authors. + */ +/** + * Classes related with servlet support. + */ +package io.modelcontextprotocol.server.servlet; \ No newline at end of file diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java index 4739e231a..2914f0b1b 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java @@ -18,6 +18,7 @@ import io.modelcontextprotocol.json.TypeRef; import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpTransportContextExtractor; +import io.modelcontextprotocol.server.servlet.HttpServletRequestMcpTransportContextExtractor; import io.modelcontextprotocol.spec.McpError; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpServerSession; @@ -503,8 +504,7 @@ public static class Builder { private String sseEndpoint = DEFAULT_SSE_ENDPOINT; - private McpTransportContextExtractor contextExtractor = ( - serverRequest) -> McpTransportContext.EMPTY; + private McpTransportContextExtractor contextExtractor; private Duration keepAliveInterval; @@ -594,7 +594,8 @@ public HttpServletSseServerTransportProvider build() { } return new HttpServletSseServerTransportProvider( jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, baseUrl, messageEndpoint, sseEndpoint, - keepAliveInterval, contextExtractor); + keepAliveInterval, + contextExtractor == null ? new HttpServletRequestMcpTransportContextExtractor() : contextExtractor); } } 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..595b7f6c4 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 @@ -8,6 +8,7 @@ import java.io.IOException; import java.io.PrintWriter; +import io.modelcontextprotocol.server.servlet.HttpServletRequestMcpTransportContextExtractor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -240,8 +241,7 @@ public static class Builder { private String mcpEndpoint = "/mcp"; - private McpTransportContextExtractor contextExtractor = ( - serverRequest) -> McpTransportContext.EMPTY; + private McpTransportContextExtractor contextExtractor; private Builder() { // used by a static method @@ -297,7 +297,8 @@ public Builder contextExtractor(McpTransportContextExtractor public HttpServletStatelessServerTransport build() { Assert.notNull(mcpEndpoint, "Message endpoint must be set"); return new HttpServletStatelessServerTransport(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, - mcpEndpoint, contextExtractor); + mcpEndpoint, + contextExtractor == null ? new HttpServletRequestMcpTransportContextExtractor() : contextExtractor); } } diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java index 34671c105..58da9cae8 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java @@ -1,7 +1,6 @@ /* * Copyright 2024-2024 the original author or authors. */ - package io.modelcontextprotocol.server.transport; import java.io.BufferedReader; @@ -13,6 +12,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; +import io.modelcontextprotocol.server.servlet.HttpServletRequestMcpTransportContextExtractor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -769,8 +769,7 @@ public static class Builder { private boolean disallowDelete = false; - private McpTransportContextExtractor contextExtractor = ( - serverRequest) -> McpTransportContext.EMPTY; + private McpTransportContextExtractor contextExtractor; private Duration keepAliveInterval; @@ -843,7 +842,8 @@ public HttpServletStreamableServerTransportProvider build() { Assert.notNull(this.mcpEndpoint, "MCP endpoint must be set"); return new HttpServletStreamableServerTransportProvider( jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, mcpEndpoint, disallowDelete, - contextExtractor, keepAliveInterval); + contextExtractor == null ? new HttpServletRequestMcpTransportContextExtractor() : contextExtractor, + keepAliveInterval); } } diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java index 8b2dea462..1faaaa32e 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java @@ -18,6 +18,7 @@ import io.modelcontextprotocol.server.McpServerFeatures; import io.modelcontextprotocol.server.McpStatelessServerFeatures; import io.modelcontextprotocol.server.McpTransportContextExtractor; +import io.modelcontextprotocol.server.servlet.HttpServletRequestMcpTransportContextExtractor; import io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider; import io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport; import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; @@ -91,10 +92,16 @@ public class AsyncServerMcpTransportContextIntegrationTests { return Mono.just(builder); }; - private final McpTransportContextExtractor serverContextExtractor = (HttpServletRequest r) -> { - var headerValue = r.getHeader(HEADER_NAME); - return headerValue != null ? McpTransportContext.create(Map.of("server-side-header-value", headerValue)) - : McpTransportContext.EMPTY; + private final McpTransportContextExtractor serverContextExtractor = new HttpServletRequestMcpTransportContextExtractor() { + @Override + protected Map metadata(HttpServletRequest r) { + Map m = super.metadata(r); + var headerValue = r.getHeader(HEADER_NAME); + if (headerValue != null) { + m.put("server-side-header-value", headerValue); + } + return m; + } }; private final HttpServletStatelessServerTransport statelessServerTransport = HttpServletStatelessServerTransport diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/common/DefaultMcpTransportContextTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/common/DefaultMcpTransportContextTest.java new file mode 100644 index 000000000..7920603c4 --- /dev/null +++ b/mcp-core/src/test/java/io/modelcontextprotocol/common/DefaultMcpTransportContextTest.java @@ -0,0 +1,53 @@ +package io.modelcontextprotocol.common; + +import io.modelcontextprotocol.spec.HttpHeaders; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +class DefaultMcpTransportContextTest { + + @Test + void protocolVersionNotPresent() { + var ctx = new DefaultMcpTransportContext(Collections.emptyMap()); + assertFalse(ctx.protocolVersion().isPresent()); + } + + @Test + void sessionIdNotPresent() { + var ctx = new DefaultMcpTransportContext(Collections.emptyMap()); + assertFalse(ctx.sessionId().isPresent()); + } + + @Test + void lastEventIdNotPresent() { + var ctx = new DefaultMcpTransportContext(Collections.emptyMap()); + assertFalse(ctx.lastEventId().isPresent()); + } + + @Test + void protocolVersion_returnsProvidedValue() { + var ctx = new DefaultMcpTransportContext(Map.of(HttpHeaders.PROTOCOL_VERSION, "2025-01-01", + HttpHeaders.MCP_SESSION_ID, "session-123", HttpHeaders.LAST_EVENT_ID, "evt-456")); + assertEquals("2025-01-01", ctx.protocolVersion().orElseThrow()); + } + + @Test + void sessionId_returnsProvidedValue() { + var ctx = new DefaultMcpTransportContext(Map.of(HttpHeaders.PROTOCOL_VERSION, "2025-01-01", + HttpHeaders.MCP_SESSION_ID, "session-abc", HttpHeaders.LAST_EVENT_ID, "evt-456")); + assertEquals("session-abc", ctx.sessionId().orElseThrow()); + } + + @Test + void lastEventId_returnsProvidedValue() { + var ctx = new DefaultMcpTransportContext(Map.of(HttpHeaders.PROTOCOL_VERSION, "2025-01-01", + HttpHeaders.MCP_SESSION_ID, "session-abc", HttpHeaders.LAST_EVENT_ID, "evt-999")); + assertEquals("evt-999", ctx.lastEventId().orElseThrow()); + } + +} diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java index cc8f4c4be..a4d0b646a 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java @@ -15,6 +15,7 @@ import io.modelcontextprotocol.server.McpStatelessServerFeatures; import io.modelcontextprotocol.server.McpSyncServerExchange; import io.modelcontextprotocol.server.McpTransportContextExtractor; +import io.modelcontextprotocol.server.servlet.HttpServletRequestMcpTransportContextExtractor; import io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider; import io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport; import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; @@ -71,10 +72,16 @@ public class SyncServerMcpTransportContextIntegrationTests { } }; - private final McpTransportContextExtractor serverContextExtractor = (HttpServletRequest r) -> { - var headerValue = r.getHeader(HEADER_NAME); - return headerValue != null ? McpTransportContext.create(Map.of("server-side-header-value", headerValue)) - : McpTransportContext.EMPTY; + private final McpTransportContextExtractor serverContextExtractor = new HttpServletRequestMcpTransportContextExtractor() { + @Override + protected Map metadata(HttpServletRequest r) { + Map m = super.metadata(r); + var headerValue = r.getHeader(HEADER_NAME); + if (headerValue != null) { + m.put("server-side-header-value", headerValue); + } + return m; + } }; private final BiFunction statelessHandler = ( diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java index d2b9d14d0..757fe544c 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java @@ -13,6 +13,7 @@ import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpServer.AsyncSpecification; import io.modelcontextprotocol.server.McpServer.SyncSpecification; +import io.modelcontextprotocol.server.servlet.HttpServletRequestMcpTransportContextExtractor; import io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider; import io.modelcontextprotocol.server.transport.TomcatTestUtil; import jakarta.servlet.http.HttpServletRequest; @@ -98,7 +99,13 @@ public void after() { protected void prepareClients(int port, String mcpEndpoint) { } - static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = (r) -> McpTransportContext - .create(Map.of("important", "value")); + static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = new HttpServletRequestMcpTransportContextExtractor() { + @Override + protected Map metadata(HttpServletRequest r) { + Map m = super.metadata(r); + m.put("important", "value"); + return m; + } + }; } diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java index 81423e0c5..1166aca0b 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java @@ -10,9 +10,9 @@ import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; -import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpServer.AsyncSpecification; import io.modelcontextprotocol.server.McpServer.SyncSpecification; +import io.modelcontextprotocol.server.servlet.HttpServletRequestMcpTransportContextExtractor; import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider; import io.modelcontextprotocol.server.transport.TomcatTestUtil; import jakarta.servlet.http.HttpServletRequest; @@ -96,7 +96,13 @@ public void after() { protected void prepareClients(int port, String mcpEndpoint) { } - static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = (r) -> McpTransportContext - .create(Map.of("important", "value")); + static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = new HttpServletRequestMcpTransportContextExtractor() { + @Override + protected Map metadata(HttpServletRequest r) { + Map m = super.metadata(r); + m.put("important", "value"); + return m; + } + }; } diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractorTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractorTests.java new file mode 100644 index 000000000..a248c1000 --- /dev/null +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractorTests.java @@ -0,0 +1,81 @@ +package io.modelcontextprotocol.server.servlet; + +import io.modelcontextprotocol.common.McpTransportContext; +import io.modelcontextprotocol.spec.HttpHeaders; +import io.modelcontextprotocol.spec.ProtocolVersions; +import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +class HttpServletRequestMcpTransportContextExtractorTests { + + @Test + @DisplayName("extract() includes all provided headers in metadata") + void extractIncludesAllHeaders() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getHeader(HttpHeaders.PROTOCOL_VERSION)).thenReturn("2025-03-26"); + when(request.getHeader(HttpHeaders.MCP_SESSION_ID)).thenReturn("session-abc"); + when(request.getHeader(HttpHeaders.LAST_EVENT_ID)).thenReturn("evt-42"); + + HttpServletRequestMcpTransportContextExtractor extractor = new HttpServletRequestMcpTransportContextExtractor(); + + McpTransportContext ctx = extractor.extract(request); + + assertEquals("2025-03-26", ctx.get(HttpHeaders.PROTOCOL_VERSION)); + assertEquals("session-abc", ctx.get(HttpHeaders.MCP_SESSION_ID)); + assertEquals("evt-42", ctx.get(HttpHeaders.LAST_EVENT_ID)); + + verify(request, times(1)).getHeader(HttpHeaders.PROTOCOL_VERSION); + verify(request, times(1)).getHeader(HttpHeaders.MCP_SESSION_ID); + verify(request, times(1)).getHeader(HttpHeaders.LAST_EVENT_ID); + verifyNoMoreInteractions(request); + } + + @Test + @DisplayName("extract() defaults protocol version when header missing") + void extractDefaultsProtocolVersion() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getHeader(HttpHeaders.PROTOCOL_VERSION)).thenReturn(null); + when(request.getHeader(HttpHeaders.MCP_SESSION_ID)).thenReturn(null); + when(request.getHeader(HttpHeaders.LAST_EVENT_ID)).thenReturn(null); + + HttpServletRequestMcpTransportContextExtractor extractor = new HttpServletRequestMcpTransportContextExtractor(); + + McpTransportContext ctx = extractor.extract(request); + + assertEquals(ProtocolVersions.MCP_2025_03_26, ctx.get(HttpHeaders.PROTOCOL_VERSION)); + assertNull(ctx.get(HttpHeaders.MCP_SESSION_ID)); + assertNull(ctx.get(HttpHeaders.LAST_EVENT_ID)); + + verify(request, times(1)).getHeader(HttpHeaders.PROTOCOL_VERSION); + verify(request, times(1)).getHeader(HttpHeaders.MCP_SESSION_ID); + verify(request, times(1)).getHeader(HttpHeaders.LAST_EVENT_ID); + verifyNoMoreInteractions(request); + } + + @Test + @DisplayName("extract() omits optional headers when not present") + void extractOmitsOptionalHeaders() { + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getHeader(HttpHeaders.PROTOCOL_VERSION)).thenReturn(ProtocolVersions.MCP_2025_03_26); + when(request.getHeader(HttpHeaders.MCP_SESSION_ID)).thenReturn(null); + when(request.getHeader(HttpHeaders.LAST_EVENT_ID)).thenReturn(null); + + HttpServletRequestMcpTransportContextExtractor extractor = new HttpServletRequestMcpTransportContextExtractor(); + + McpTransportContext ctx = extractor.extract(request); + + assertEquals(ProtocolVersions.MCP_2025_03_26, ctx.get(HttpHeaders.PROTOCOL_VERSION)); + assertNull(ctx.get(HttpHeaders.MCP_SESSION_ID)); + assertNull(ctx.get(HttpHeaders.LAST_EVENT_ID)); + + verify(request, times(1)).getHeader(HttpHeaders.PROTOCOL_VERSION); + verify(request, times(1)).getHeader(HttpHeaders.MCP_SESSION_ID); + verify(request, times(1)).getHeader(HttpHeaders.LAST_EVENT_ID); + verifyNoMoreInteractions(request); + } + +} diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/ServerRequestMcpTransportContextExtractor.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/ServerRequestMcpTransportContextExtractor.java new file mode 100644 index 000000000..8943323b6 --- /dev/null +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/ServerRequestMcpTransportContextExtractor.java @@ -0,0 +1,5 @@ +package io.modelcontextprotocol.server.transport; + +public class ServerRequestMcpTransportContextExtractor { + +} From 1a2b0a501cd3fc7f8a897aaf1e397c829dbc6a6e Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 24 Sep 2025 13:15:08 +0200 Subject: [PATCH 02/10] Update mcp-core/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java --- .../server/McpTransportContextExtractor.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java index fe63755c2..ea9f05a4f 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java @@ -6,8 +6,6 @@ import io.modelcontextprotocol.common.McpTransportContext; -import java.util.Optional; - /** * The contract for extracting metadata from a generic transport request of type * {@link T}. From f02ec6f2674c8bd1c3f3b0f51552ad2cc164ff4f Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 24 Sep 2025 14:09:46 +0200 Subject: [PATCH 03/10] adding size --- .../servlet/HttpServletRequestMcpTransportContextExtractor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java index d8129646c..ce3446238 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java @@ -28,7 +28,7 @@ public McpTransportContext extract(HttpServletRequest request) { * @return Extracts Map for MCP Transport Context */ protected Map metadata(HttpServletRequest request) { - Map metadata = new HashMap<>(); + Map metadata = new HashMap<>(3); metadata.put(io.modelcontextprotocol.spec.HttpHeaders.PROTOCOL_VERSION, Optional.ofNullable(request.getHeader(io.modelcontextprotocol.spec.HttpHeaders.PROTOCOL_VERSION)) .orElse(ProtocolVersions.MCP_2025_03_26)); From 926755810beaae72a9931106e23ccdf63a19836e Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 24 Sep 2025 14:30:34 +0200 Subject: [PATCH 04/10] Delete mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/ServerRequestMcpTransportContextExtractor.java --- .../transport/ServerRequestMcpTransportContextExtractor.java | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/ServerRequestMcpTransportContextExtractor.java diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/ServerRequestMcpTransportContextExtractor.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/ServerRequestMcpTransportContextExtractor.java deleted file mode 100644 index 8943323b6..000000000 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/ServerRequestMcpTransportContextExtractor.java +++ /dev/null @@ -1,5 +0,0 @@ -package io.modelcontextprotocol.server.transport; - -public class ServerRequestMcpTransportContextExtractor { - -} From 74c995b6c8ab731b821a087e0330e9cd0ce97c4b Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 24 Sep 2025 15:09:00 +0200 Subject: [PATCH 05/10] Update mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/package-info.java Co-authored-by: Daniel Garnier-Moiroux --- .../io/modelcontextprotocol/server/servlet/package-info.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/package-info.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/package-info.java index 5088da87c..0eaabc23d 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/package-info.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright 2024-2024 the original author or authors. + * Copyright 2025-2025 the original author or authors. */ /** * Classes related with servlet support. From f0632fad7de96ee3b59f58a938b48707be0b6bae Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 24 Sep 2025 15:09:10 +0200 Subject: [PATCH 06/10] Update mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java Co-authored-by: Daniel Garnier-Moiroux --- .../servlet/HttpServletRequestMcpTransportContextExtractor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java index ce3446238..04fd9b891 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2024-2024 the original author or authors. + * Copyright 2025-2025 the original author or authors. */ package io.modelcontextprotocol.server.servlet; From df0c149b21432a681b9db711cf5f5634faf10a68 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 24 Sep 2025 15:12:46 +0200 Subject: [PATCH 07/10] add licenese --- .../HttpServletRequestMcpTransportContextExtractorTests.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractorTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractorTests.java index a248c1000..86ea3436e 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractorTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractorTests.java @@ -1,3 +1,7 @@ +/* + * Copyright 2024-2025 the original author or authors. + */ + package io.modelcontextprotocol.server.servlet; import io.modelcontextprotocol.common.McpTransportContext; From 45ec4b85bfdbde8a02ed6bc0f84a9830aa2fe617 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 24 Sep 2025 15:13:49 +0200 Subject: [PATCH 08/10] add license --- .../common/DefaultMcpTransportContextTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/common/DefaultMcpTransportContextTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/common/DefaultMcpTransportContextTest.java index 7920603c4..56515bc05 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/common/DefaultMcpTransportContextTest.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/common/DefaultMcpTransportContextTest.java @@ -1,3 +1,7 @@ +/* + * Copyright 2024-2025 the original author or authors. + */ + package io.modelcontextprotocol.common; import io.modelcontextprotocol.spec.HttpHeaders; From 24098f2e7026cc8eef3abcedcf158a29e94fd791 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 24 Sep 2025 15:52:38 +0200 Subject: [PATCH 09/10] implementations for McpTransportContextExtractor --- .../common/McpTransportContext.java | 23 +++++++++++++++++++ ...etRequestMcpTransportContextExtractor.java | 19 ++------------- ...erMcpTransportContextIntegrationTests.java | 9 ++++++-- ...erMcpTransportContextIntegrationTests.java | 10 ++++++-- .../HttpServletSseIntegrationTests.java | 9 ++++++-- ...HttpServletStreamableIntegrationTests.java | 10 ++++++-- ...ransportContextExtractorServerRequest.java | 21 +++++++++++++++++ .../WebFluxSseServerTransportProvider.java | 6 ++--- .../WebFluxStatelessServerTransport.java | 6 ++--- ...FluxStreamableServerTransportProvider.java | 6 ++--- ...ransportContextExtractorServerRequest.java | 21 +++++++++++++++++ .../WebMvcSseServerTransportProvider.java | 6 ++--- .../WebMvcStatelessServerTransport.java | 6 ++--- ...bMvcStreamableServerTransportProvider.java | 6 ++--- 14 files changed, 115 insertions(+), 43 deletions(-) create mode 100644 mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/McpTransportContextExtractorServerRequest.java create mode 100644 mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/McpTransportContextExtractorServerRequest.java diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/common/McpTransportContext.java b/mcp-core/src/main/java/io/modelcontextprotocol/common/McpTransportContext.java index e8e29da42..58a8386ab 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/common/McpTransportContext.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/common/McpTransportContext.java @@ -4,10 +4,14 @@ package io.modelcontextprotocol.common; +import io.modelcontextprotocol.spec.ProtocolVersions; + import java.security.Principal; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.function.Function; /** * Context associated with the transport layer. It allows to add transport-level metadata @@ -38,6 +42,25 @@ static McpTransportContext create(Map metadata) { return new DefaultMcpTransportContext(metadata); } + /** + * Returns a Map with entries for MCP transport concepts such as Protocol version, + * session ID and Last Event ID. + * @param headers Function typically backed by an HTTP Request Headers implementation. + * @return Map with entries for MCP transport concepts such as Protocol version, + * session ID and Last Event ID. + */ + static Map createMetadata(Function headers) { + Map metadata = new HashMap<>(3); + metadata.put(io.modelcontextprotocol.spec.HttpHeaders.PROTOCOL_VERSION, + Optional.ofNullable(headers.apply(io.modelcontextprotocol.spec.HttpHeaders.PROTOCOL_VERSION)) + .orElse(ProtocolVersions.MCP_2025_03_26)); + Optional.ofNullable(headers.apply(io.modelcontextprotocol.spec.HttpHeaders.MCP_SESSION_ID)) + .ifPresent(v -> metadata.put(io.modelcontextprotocol.spec.HttpHeaders.MCP_SESSION_ID, v)); + Optional.ofNullable(headers.apply(io.modelcontextprotocol.spec.HttpHeaders.LAST_EVENT_ID)) + .ifPresent(v -> metadata.put(io.modelcontextprotocol.spec.HttpHeaders.LAST_EVENT_ID, v)); + return metadata; + } + /** * Extract a value from the context. * @param key the key under the data is expected diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java index 04fd9b891..3954cfe2f 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java @@ -11,6 +11,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.function.Function; /** * {@link McpTransportContextExtractor} implementation for {@link HttpServletRequest}. @@ -20,23 +21,7 @@ public class HttpServletRequestMcpTransportContextExtractor @Override public McpTransportContext extract(HttpServletRequest request) { - return McpTransportContext.create(metadata(request)); - } - - /** - * @param request Servlet Request - * @return Extracts Map for MCP Transport Context - */ - protected Map metadata(HttpServletRequest request) { - Map metadata = new HashMap<>(3); - metadata.put(io.modelcontextprotocol.spec.HttpHeaders.PROTOCOL_VERSION, - Optional.ofNullable(request.getHeader(io.modelcontextprotocol.spec.HttpHeaders.PROTOCOL_VERSION)) - .orElse(ProtocolVersions.MCP_2025_03_26)); - Optional.ofNullable(request.getHeader(io.modelcontextprotocol.spec.HttpHeaders.MCP_SESSION_ID)) - .ifPresent(v -> metadata.put(io.modelcontextprotocol.spec.HttpHeaders.MCP_SESSION_ID, v)); - Optional.ofNullable(request.getHeader(io.modelcontextprotocol.spec.HttpHeaders.LAST_EVENT_ID)) - .ifPresent(v -> metadata.put(io.modelcontextprotocol.spec.HttpHeaders.LAST_EVENT_ID, v)); - return metadata; + return McpTransportContext.create(McpTransportContext.createMetadata(request::getHeader)); } } diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java index 1faaaa32e..e61f0d0b6 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java @@ -4,6 +4,7 @@ package io.modelcontextprotocol.common; +import java.util.HashMap; import java.util.Map; import java.util.function.BiFunction; @@ -94,8 +95,12 @@ public class AsyncServerMcpTransportContextIntegrationTests { private final McpTransportContextExtractor serverContextExtractor = new HttpServletRequestMcpTransportContextExtractor() { @Override - protected Map metadata(HttpServletRequest r) { - Map m = super.metadata(r); + public McpTransportContext extract(HttpServletRequest request) { + return McpTransportContext.create(metadata(request)); + } + + private Map metadata(HttpServletRequest r) { + Map m = new HashMap<>(McpTransportContext.createMetadata(r::getHeader)); var headerValue = r.getHeader(HEADER_NAME); if (headerValue != null) { m.put("server-side-header-value", headerValue); diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java index a4d0b646a..9901e23b8 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/common/SyncServerMcpTransportContextIntegrationTests.java @@ -23,6 +23,8 @@ import io.modelcontextprotocol.spec.McpSchema; import jakarta.servlet.Servlet; import jakarta.servlet.http.HttpServletRequest; + +import java.util.HashMap; import java.util.Map; import java.util.function.BiFunction; import java.util.function.Supplier; @@ -74,8 +76,12 @@ public class SyncServerMcpTransportContextIntegrationTests { private final McpTransportContextExtractor serverContextExtractor = new HttpServletRequestMcpTransportContextExtractor() { @Override - protected Map metadata(HttpServletRequest r) { - Map m = super.metadata(r); + public McpTransportContext extract(HttpServletRequest request) { + return McpTransportContext.create(metadata(request)); + } + + private Map metadata(HttpServletRequest r) { + Map m = new HashMap<>(McpTransportContext.createMetadata(r::getHeader)); var headerValue = r.getHeader(HEADER_NAME); if (headerValue != null) { m.put("server-side-header-value", headerValue); diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java index 757fe544c..212f16029 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletSseIntegrationTests.java @@ -5,6 +5,7 @@ package io.modelcontextprotocol.server; import java.time.Duration; +import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; @@ -101,8 +102,12 @@ protected void prepareClients(int port, String mcpEndpoint) { static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = new HttpServletRequestMcpTransportContextExtractor() { @Override - protected Map metadata(HttpServletRequest r) { - Map m = super.metadata(r); + public McpTransportContext extract(HttpServletRequest request) { + return McpTransportContext.create(metadata(request)); + } + + Map metadata(HttpServletRequest r) { + Map m = new HashMap<>(McpTransportContext.createMetadata(r::getHeader)); m.put("important", "value"); return m; } diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java index 1166aca0b..f1429235c 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletStreamableIntegrationTests.java @@ -5,11 +5,13 @@ package io.modelcontextprotocol.server; import java.time.Duration; +import java.util.HashMap; import java.util.Map; import java.util.stream.Stream; import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.client.transport.HttpClientStreamableHttpTransport; +import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpServer.AsyncSpecification; import io.modelcontextprotocol.server.McpServer.SyncSpecification; import io.modelcontextprotocol.server.servlet.HttpServletRequestMcpTransportContextExtractor; @@ -98,8 +100,12 @@ protected void prepareClients(int port, String mcpEndpoint) { static McpTransportContextExtractor TEST_CONTEXT_EXTRACTOR = new HttpServletRequestMcpTransportContextExtractor() { @Override - protected Map metadata(HttpServletRequest r) { - Map m = super.metadata(r); + public McpTransportContext extract(HttpServletRequest request) { + return McpTransportContext.create(metadata(request)); + } + + private Map metadata(HttpServletRequest r) { + Map m = new HashMap<>(McpTransportContext.createMetadata(r::getHeader)); m.put("important", "value"); return m; } diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/McpTransportContextExtractorServerRequest.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/McpTransportContextExtractorServerRequest.java new file mode 100644 index 000000000..520919412 --- /dev/null +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/McpTransportContextExtractorServerRequest.java @@ -0,0 +1,21 @@ +/* + * Copyright 2024-2024 the original author or authors. + */ +package io.modelcontextprotocol.server.transport; + +import io.modelcontextprotocol.common.McpTransportContext; +import io.modelcontextprotocol.server.McpTransportContextExtractor; +import org.springframework.web.reactive.function.server.ServerRequest; + +/** + * {@link McpTransportContextExtractor} implementation for {@link ServerRequest}. + */ +public class McpTransportContextExtractorServerRequest implements McpTransportContextExtractor { + + @Override + public McpTransportContext extract(ServerRequest request) { + return McpTransportContext + .create(McpTransportContext.createMetadata(headerName -> request.headers().firstHeader(headerName))); + } + +} diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java index 95355c0f2..a90fb35ef 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxSseServerTransportProvider.java @@ -418,8 +418,7 @@ public static class Builder { private Duration keepAliveInterval; - private McpTransportContextExtractor contextExtractor = ( - serverRequest) -> McpTransportContext.EMPTY; + private McpTransportContextExtractor contextExtractor; /** * Sets the McpJsonMapper to use for JSON serialization/deserialization of MCP @@ -507,7 +506,8 @@ public Builder contextExtractor(McpTransportContextExtractor cont public WebFluxSseServerTransportProvider build() { Assert.notNull(messageEndpoint, "Message endpoint must be set"); return new WebFluxSseServerTransportProvider(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, - baseUrl, messageEndpoint, sseEndpoint, keepAliveInterval, contextExtractor); + baseUrl, messageEndpoint, sseEndpoint, keepAliveInterval, + contextExtractor == null ? new McpTransportContextExtractorServerRequest() : contextExtractor); } } 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..3ed68aa5d 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 @@ -157,8 +157,7 @@ public static class Builder { private String mcpEndpoint = "/mcp"; - private McpTransportContextExtractor contextExtractor = ( - serverRequest) -> McpTransportContext.EMPTY; + private McpTransportContextExtractor contextExtractor; private Builder() { // used by a static method @@ -214,7 +213,8 @@ public Builder contextExtractor(McpTransportContextExtractor cont public WebFluxStatelessServerTransport build() { Assert.notNull(mcpEndpoint, "Message endpoint must be set"); return new WebFluxStatelessServerTransport(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, - mcpEndpoint, contextExtractor); + mcpEndpoint, + contextExtractor == null ? new McpTransportContextExtractorServerRequest() : contextExtractor); } } diff --git a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java index 144a3ce02..d483198e7 100644 --- a/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webflux/src/main/java/io/modelcontextprotocol/server/transport/WebFluxStreamableServerTransportProvider.java @@ -403,8 +403,7 @@ public static class Builder { private String mcpEndpoint = "/mcp"; - private McpTransportContextExtractor contextExtractor = ( - serverRequest) -> McpTransportContext.EMPTY; + private McpTransportContextExtractor contextExtractor; private boolean disallowDelete; @@ -486,7 +485,8 @@ public Builder keepAliveInterval(Duration keepAliveInterval) { public WebFluxStreamableServerTransportProvider build() { Assert.notNull(mcpEndpoint, "Message endpoint must be set"); return new WebFluxStreamableServerTransportProvider( - jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, mcpEndpoint, contextExtractor, + jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, mcpEndpoint, + contextExtractor == null ? new McpTransportContextExtractorServerRequest() : contextExtractor, disallowDelete, keepAliveInterval); } diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/McpTransportContextExtractorServerRequest.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/McpTransportContextExtractorServerRequest.java new file mode 100644 index 000000000..a8b704f2f --- /dev/null +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/McpTransportContextExtractorServerRequest.java @@ -0,0 +1,21 @@ +/* + * Copyright 2024-2024 the original author or authors. + */ +package io.modelcontextprotocol.server.transport; + +import io.modelcontextprotocol.common.McpTransportContext; +import io.modelcontextprotocol.server.McpTransportContextExtractor; +import org.springframework.web.servlet.function.ServerRequest; + +/** + * {@link McpTransportContextExtractor} implementation for {@link ServerRequest}. + */ +public class McpTransportContextExtractorServerRequest implements McpTransportContextExtractor { + + @Override + public McpTransportContext extract(ServerRequest request) { + return McpTransportContext + .create(McpTransportContext.createMetadata(headerName -> request.headers().firstHeader(headerName))); + } + +} diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java index 0b71ddc1f..1223fc6f5 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcSseServerTransportProvider.java @@ -475,8 +475,7 @@ public static class Builder { private Duration keepAliveInterval; - private McpTransportContextExtractor contextExtractor = ( - serverRequest) -> McpTransportContext.EMPTY; + private McpTransportContextExtractor contextExtractor; /** * Sets the JSON object mapper to use for message serialization/deserialization. @@ -564,7 +563,8 @@ public WebMvcSseServerTransportProvider build() { throw new IllegalStateException("MessageEndpoint must be set"); } return new WebMvcSseServerTransportProvider(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, - baseUrl, messageEndpoint, sseEndpoint, keepAliveInterval, contextExtractor); + baseUrl, messageEndpoint, sseEndpoint, keepAliveInterval, + contextExtractor == null ? new McpTransportContextExtractorServerRequest() : contextExtractor); } } 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..025b3fc21 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 @@ -175,8 +175,7 @@ public static class Builder { private String mcpEndpoint = "/mcp"; - private McpTransportContextExtractor contextExtractor = ( - serverRequest) -> McpTransportContext.EMPTY; + private McpTransportContextExtractor contextExtractor; private Builder() { // used by a static method @@ -232,7 +231,8 @@ public Builder contextExtractor(McpTransportContextExtractor cont public WebMvcStatelessServerTransport build() { Assert.notNull(mcpEndpoint, "Message endpoint must be set"); return new WebMvcStatelessServerTransport(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, - mcpEndpoint, contextExtractor); + mcpEndpoint, + contextExtractor == null ? new McpTransportContextExtractorServerRequest() : contextExtractor); } } diff --git a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java index d85046a67..22167b714 100644 --- a/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java +++ b/mcp-spring/mcp-spring-webmvc/src/main/java/io/modelcontextprotocol/server/transport/WebMvcStreamableServerTransportProvider.java @@ -604,8 +604,7 @@ public static class Builder { private boolean disallowDelete = false; - private McpTransportContextExtractor contextExtractor = ( - serverRequest) -> McpTransportContext.EMPTY; + private McpTransportContextExtractor contextExtractor; private Duration keepAliveInterval; @@ -682,7 +681,8 @@ public WebMvcStreamableServerTransportProvider build() { Assert.notNull(this.mcpEndpoint, "MCP endpoint must be set"); return new WebMvcStreamableServerTransportProvider( jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, mcpEndpoint, disallowDelete, - contextExtractor, keepAliveInterval); + contextExtractor == null ? new McpTransportContextExtractorServerRequest() : contextExtractor, + keepAliveInterval); } } From e8cdb3185e3dc20ae9353cc68dde4591ed7137b4 Mon Sep 17 00:00:00 2001 From: Sergio del Amo Date: Wed, 24 Sep 2025 15:57:59 +0200 Subject: [PATCH 10/10] remove usued imports --- .../HttpServletRequestMcpTransportContextExtractor.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java b/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java index 3954cfe2f..18132030d 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/server/servlet/HttpServletRequestMcpTransportContextExtractor.java @@ -5,14 +5,8 @@ import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.server.McpTransportContextExtractor; -import io.modelcontextprotocol.spec.ProtocolVersions; import jakarta.servlet.http.HttpServletRequest; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; - /** * {@link McpTransportContextExtractor} implementation for {@link HttpServletRequest}. */