Skip to content

Commit 7a3b20d

Browse files
committed
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.
1 parent 7f16cd0 commit 7a3b20d

15 files changed

+289
-23
lines changed

mcp-core/src/main/java/io/modelcontextprotocol/common/DefaultMcpTransportContext.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
package io.modelcontextprotocol.common;
66

77
import java.util.Map;
8+
import java.util.Optional;
89

10+
import io.modelcontextprotocol.spec.HttpHeaders;
911
import io.modelcontextprotocol.util.Assert;
1012

1113
/**
@@ -28,6 +30,21 @@ public Object get(String key) {
2830
return this.metadata.get(key);
2931
}
3032

33+
@Override
34+
public Optional<String> lastEventId() {
35+
return Optional.ofNullable(metadata.get(HttpHeaders.LAST_EVENT_ID)).map(Object::toString);
36+
}
37+
38+
@Override
39+
public Optional<String> sessionId() {
40+
return Optional.ofNullable(metadata.get(HttpHeaders.MCP_SESSION_ID)).map(Object::toString);
41+
}
42+
43+
@Override
44+
public Optional<String> protocolVersion() {
45+
return Optional.ofNullable(metadata.get(HttpHeaders.PROTOCOL_VERSION)).map(Object::toString);
46+
}
47+
3148
@Override
3249
public boolean equals(Object o) {
3350
if (o == null || getClass() != o.getClass())

mcp-core/src/main/java/io/modelcontextprotocol/common/McpTransportContext.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
package io.modelcontextprotocol.common;
66

7+
import java.security.Principal;
78
import java.util.Collections;
89
import java.util.Map;
10+
import java.util.Optional;
911

1012
/**
1113
* Context associated with the transport layer. It allows to add transport-level metadata
@@ -43,4 +45,32 @@ static McpTransportContext create(Map<String, Object> metadata) {
4345
*/
4446
Object get(String key);
4547

48+
/**
49+
* @return The MCP Protocl Version
50+
*/
51+
default Optional<String> protocolVersion() {
52+
return Optional.empty();
53+
}
54+
55+
/**
56+
* @return The Session ID
57+
*/
58+
default Optional<String> sessionId() {
59+
return Optional.empty();
60+
}
61+
62+
/**
63+
* @return The Last Event ID
64+
*/
65+
default Optional<String> lastEventId() {
66+
return Optional.empty();
67+
}
68+
69+
/**
70+
* @return The Principal. it may represent the authenticated user.
71+
*/
72+
default Optional<Principal> principal() {
73+
return Optional.empty();
74+
}
75+
4676
}

mcp-core/src/main/java/io/modelcontextprotocol/server/McpTransportContextExtractor.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
import io.modelcontextprotocol.common.McpTransportContext;
88

9+
import java.util.Optional;
10+
911
/**
1012
* The contract for extracting metadata from a generic transport request of type
1113
* {@link T}.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2024-2024 the original author or authors.
3+
*/
4+
package io.modelcontextprotocol.server.servlet;
5+
6+
import io.modelcontextprotocol.common.McpTransportContext;
7+
import io.modelcontextprotocol.server.McpTransportContextExtractor;
8+
import io.modelcontextprotocol.spec.ProtocolVersions;
9+
import jakarta.servlet.http.HttpServletRequest;
10+
11+
import java.util.HashMap;
12+
import java.util.Map;
13+
import java.util.Optional;
14+
15+
/**
16+
* {@link McpTransportContextExtractor} implementation for {@link HttpServletRequest}.
17+
*/
18+
public class HttpServletRequestMcpTransportContextExtractor
19+
implements McpTransportContextExtractor<HttpServletRequest> {
20+
21+
@Override
22+
public McpTransportContext extract(HttpServletRequest request) {
23+
return McpTransportContext.create(metadata(request));
24+
}
25+
26+
/**
27+
* @param request Servlet Request
28+
* @return Extracts Map for MCP Transport Context
29+
*/
30+
protected Map<String, Object> metadata(HttpServletRequest request) {
31+
Map<String, Object> metadata = new HashMap<>();
32+
metadata.put(io.modelcontextprotocol.spec.HttpHeaders.PROTOCOL_VERSION,
33+
Optional.ofNullable(request.getHeader(io.modelcontextprotocol.spec.HttpHeaders.PROTOCOL_VERSION))
34+
.orElse(ProtocolVersions.MCP_2025_03_26));
35+
Optional.ofNullable(request.getHeader(io.modelcontextprotocol.spec.HttpHeaders.MCP_SESSION_ID))
36+
.ifPresent(v -> metadata.put(io.modelcontextprotocol.spec.HttpHeaders.MCP_SESSION_ID, v));
37+
Optional.ofNullable(request.getHeader(io.modelcontextprotocol.spec.HttpHeaders.LAST_EVENT_ID))
38+
.ifPresent(v -> metadata.put(io.modelcontextprotocol.spec.HttpHeaders.LAST_EVENT_ID, v));
39+
return metadata;
40+
}
41+
42+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright 2024-2024 the original author or authors.
3+
*/
4+
/**
5+
* Classes related with servlet support.
6+
*/
7+
package io.modelcontextprotocol.server.servlet;

mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProvider.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.modelcontextprotocol.json.TypeRef;
1919
import io.modelcontextprotocol.common.McpTransportContext;
2020
import io.modelcontextprotocol.server.McpTransportContextExtractor;
21+
import io.modelcontextprotocol.server.servlet.HttpServletRequestMcpTransportContextExtractor;
2122
import io.modelcontextprotocol.spec.McpError;
2223
import io.modelcontextprotocol.spec.McpSchema;
2324
import io.modelcontextprotocol.spec.McpServerSession;
@@ -503,8 +504,7 @@ public static class Builder {
503504

504505
private String sseEndpoint = DEFAULT_SSE_ENDPOINT;
505506

506-
private McpTransportContextExtractor<HttpServletRequest> contextExtractor = (
507-
serverRequest) -> McpTransportContext.EMPTY;
507+
private McpTransportContextExtractor<HttpServletRequest> contextExtractor;
508508

509509
private Duration keepAliveInterval;
510510

@@ -594,7 +594,8 @@ public HttpServletSseServerTransportProvider build() {
594594
}
595595
return new HttpServletSseServerTransportProvider(
596596
jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, baseUrl, messageEndpoint, sseEndpoint,
597-
keepAliveInterval, contextExtractor);
597+
keepAliveInterval,
598+
contextExtractor == null ? new HttpServletRequestMcpTransportContextExtractor() : contextExtractor);
598599
}
599600

600601
}

mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStatelessServerTransport.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.io.IOException;
99
import java.io.PrintWriter;
1010

11+
import io.modelcontextprotocol.server.servlet.HttpServletRequestMcpTransportContextExtractor;
1112
import org.slf4j.Logger;
1213
import org.slf4j.LoggerFactory;
1314

@@ -240,8 +241,7 @@ public static class Builder {
240241

241242
private String mcpEndpoint = "/mcp";
242243

243-
private McpTransportContextExtractor<HttpServletRequest> contextExtractor = (
244-
serverRequest) -> McpTransportContext.EMPTY;
244+
private McpTransportContextExtractor<HttpServletRequest> contextExtractor;
245245

246246
private Builder() {
247247
// used by a static method
@@ -297,7 +297,8 @@ public Builder contextExtractor(McpTransportContextExtractor<HttpServletRequest>
297297
public HttpServletStatelessServerTransport build() {
298298
Assert.notNull(mcpEndpoint, "Message endpoint must be set");
299299
return new HttpServletStatelessServerTransport(jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper,
300-
mcpEndpoint, contextExtractor);
300+
mcpEndpoint,
301+
contextExtractor == null ? new HttpServletRequestMcpTransportContextExtractor() : contextExtractor);
301302
}
302303

303304
}

mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/*
22
* Copyright 2024-2024 the original author or authors.
33
*/
4-
54
package io.modelcontextprotocol.server.transport;
65

76
import java.io.BufferedReader;
@@ -13,6 +12,7 @@
1312
import java.util.concurrent.ConcurrentHashMap;
1413
import java.util.concurrent.locks.ReentrantLock;
1514

15+
import io.modelcontextprotocol.server.servlet.HttpServletRequestMcpTransportContextExtractor;
1616
import org.slf4j.Logger;
1717
import org.slf4j.LoggerFactory;
1818

@@ -769,8 +769,7 @@ public static class Builder {
769769

770770
private boolean disallowDelete = false;
771771

772-
private McpTransportContextExtractor<HttpServletRequest> contextExtractor = (
773-
serverRequest) -> McpTransportContext.EMPTY;
772+
private McpTransportContextExtractor<HttpServletRequest> contextExtractor;
774773

775774
private Duration keepAliveInterval;
776775

@@ -843,7 +842,8 @@ public HttpServletStreamableServerTransportProvider build() {
843842
Assert.notNull(this.mcpEndpoint, "MCP endpoint must be set");
844843
return new HttpServletStreamableServerTransportProvider(
845844
jsonMapper == null ? McpJsonMapper.getDefault() : jsonMapper, mcpEndpoint, disallowDelete,
846-
contextExtractor, keepAliveInterval);
845+
contextExtractor == null ? new HttpServletRequestMcpTransportContextExtractor() : contextExtractor,
846+
keepAliveInterval);
847847
}
848848

849849
}

mcp-core/src/test/java/io/modelcontextprotocol/common/AsyncServerMcpTransportContextIntegrationTests.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import io.modelcontextprotocol.server.McpServerFeatures;
1919
import io.modelcontextprotocol.server.McpStatelessServerFeatures;
2020
import io.modelcontextprotocol.server.McpTransportContextExtractor;
21+
import io.modelcontextprotocol.server.servlet.HttpServletRequestMcpTransportContextExtractor;
2122
import io.modelcontextprotocol.server.transport.HttpServletSseServerTransportProvider;
2223
import io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport;
2324
import io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider;
@@ -91,10 +92,16 @@ public class AsyncServerMcpTransportContextIntegrationTests {
9192
return Mono.just(builder);
9293
};
9394

94-
private final McpTransportContextExtractor<HttpServletRequest> serverContextExtractor = (HttpServletRequest r) -> {
95-
var headerValue = r.getHeader(HEADER_NAME);
96-
return headerValue != null ? McpTransportContext.create(Map.of("server-side-header-value", headerValue))
97-
: McpTransportContext.EMPTY;
95+
private final McpTransportContextExtractor<HttpServletRequest> serverContextExtractor = new HttpServletRequestMcpTransportContextExtractor() {
96+
@Override
97+
protected Map<String, Object> metadata(HttpServletRequest r) {
98+
Map<String, Object> m = super.metadata(r);
99+
var headerValue = r.getHeader(HEADER_NAME);
100+
if (headerValue != null) {
101+
m.put("server-side-header-value", headerValue);
102+
}
103+
return m;
104+
}
98105
};
99106

100107
private final HttpServletStatelessServerTransport statelessServerTransport = HttpServletStatelessServerTransport
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.modelcontextprotocol.common;
2+
3+
import io.modelcontextprotocol.spec.HttpHeaders;
4+
import org.junit.jupiter.api.Test;
5+
6+
import java.util.Collections;
7+
import java.util.Map;
8+
9+
import static org.junit.jupiter.api.Assertions.assertEquals;
10+
import static org.junit.jupiter.api.Assertions.assertFalse;
11+
12+
class DefaultMcpTransportContextTest {
13+
14+
@Test
15+
void protocolVersionNotPresent() {
16+
var ctx = new DefaultMcpTransportContext(Collections.emptyMap());
17+
assertFalse(ctx.protocolVersion().isPresent());
18+
}
19+
20+
@Test
21+
void sessionIdNotPresent() {
22+
var ctx = new DefaultMcpTransportContext(Collections.emptyMap());
23+
assertFalse(ctx.sessionId().isPresent());
24+
}
25+
26+
@Test
27+
void lastEventIdNotPresent() {
28+
var ctx = new DefaultMcpTransportContext(Collections.emptyMap());
29+
assertFalse(ctx.lastEventId().isPresent());
30+
}
31+
32+
@Test
33+
void protocolVersion_returnsProvidedValue() {
34+
var ctx = new DefaultMcpTransportContext(Map.of(HttpHeaders.PROTOCOL_VERSION, "2025-01-01",
35+
HttpHeaders.MCP_SESSION_ID, "session-123", HttpHeaders.LAST_EVENT_ID, "evt-456"));
36+
assertEquals("2025-01-01", ctx.protocolVersion().orElseThrow());
37+
}
38+
39+
@Test
40+
void sessionId_returnsProvidedValue() {
41+
var ctx = new DefaultMcpTransportContext(Map.of(HttpHeaders.PROTOCOL_VERSION, "2025-01-01",
42+
HttpHeaders.MCP_SESSION_ID, "session-abc", HttpHeaders.LAST_EVENT_ID, "evt-456"));
43+
assertEquals("session-abc", ctx.sessionId().orElseThrow());
44+
}
45+
46+
@Test
47+
void lastEventId_returnsProvidedValue() {
48+
var ctx = new DefaultMcpTransportContext(Map.of(HttpHeaders.PROTOCOL_VERSION, "2025-01-01",
49+
HttpHeaders.MCP_SESSION_ID, "session-abc", HttpHeaders.LAST_EVENT_ID, "evt-999"));
50+
assertEquals("evt-999", ctx.lastEventId().orElseThrow());
51+
}
52+
53+
}

0 commit comments

Comments
 (0)