Skip to content

Commit 6348e0a

Browse files
committed
Reject Mcp-* headers with more than one value
Fixes #1321 Signed-off-by: Daniel Garnier-Moiroux <git@garnier.wf>
1 parent 99fefaf commit 6348e0a

2 files changed

Lines changed: 57 additions & 6 deletions

File tree

‎mcp-core/src/main/java/io/modelcontextprotocol/modern/server/transport/HttpServletMcpTransport.java‎

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -391,14 +391,23 @@ private void writeJson(HttpServletResponse response, int status, JSONRPCResponse
391391
/**
392392
* Validates {@code Mcp-Method}, {@code MCP-Protocol-Version} and, for the three named
393393
* methods, {@code Mcp-Name} against the request body.
394+
* <p>
395+
* Each of these headers must carry exactly one value. Intermediaries disagree on
396+
* which copy of a repeated header counts (the first, the last, or all of them joined
397+
* with {@code ", "}), so accepting a repeated header would let a request be routed or
398+
* authorized on one value while the server validates another.
394399
* @return a human-readable mismatch description, or {@code null} if the headers are
395400
* consistent with the body
396401
*/
397402
private String validateHeaders(HttpServletRequest request, JSONRPCRequest jsonRpcRequest) {
398-
String methodHeader = request.getHeader("Mcp-Method");
399-
if (methodHeader == null) {
403+
List<String> methodHeaders = Collections.list(request.getHeaders("Mcp-Method"));
404+
if (methodHeaders.isEmpty()) {
400405
return "Missing required header: Mcp-Method";
401406
}
407+
if (methodHeaders.size() > 1) {
408+
return "Mcp-Method header has more than one value";
409+
}
410+
String methodHeader = methodHeaders.get(0);
402411
if (!methodHeader.equals(jsonRpcRequest.method())) {
403412
return "Mcp-Method header does not match request method";
404413
}
@@ -407,10 +416,14 @@ private String validateHeaders(HttpServletRequest request, JSONRPCRequest jsonRp
407416
// which answers it with -32602.
408417
String bodyVersion = protocolVersionFromMeta(jsonRpcRequest.params());
409418
if (bodyVersion != null) {
410-
String versionHeader = request.getHeader("MCP-Protocol-Version");
411-
if (versionHeader == null) {
419+
List<String> versionHeaders = Collections.list(request.getHeaders("MCP-Protocol-Version"));
420+
if (versionHeaders.isEmpty()) {
412421
return "Missing required header: MCP-Protocol-Version";
413422
}
423+
if (versionHeaders.size() > 1) {
424+
return "MCP-Protocol-Version header has more than one value";
425+
}
426+
String versionHeader = versionHeaders.get(0);
414427
if (!versionHeader.equals(bodyVersion)) {
415428
return "MCP-Protocol-Version header does not match _meta protocol version";
416429
}
@@ -427,10 +440,14 @@ private String validateHeaders(HttpServletRequest request, JSONRPCRequest jsonRp
427440
if (!(paramsMap.get(nameKey) instanceof String expectedName)) {
428441
return null;
429442
}
430-
String nameHeader = request.getHeader("Mcp-Name");
431-
if (nameHeader == null) {
443+
List<String> nameHeaders = Collections.list(request.getHeaders("Mcp-Name"));
444+
if (nameHeaders.isEmpty()) {
432445
return "Missing required header: Mcp-Name";
433446
}
447+
if (nameHeaders.size() > 1) {
448+
return "Mcp-Name header has more than one value";
449+
}
450+
String nameHeader = nameHeaders.get(0);
434451
String decoded = McpHeaderDecoder.decodeValue(nameHeader);
435452
if (decoded == null) {
436453
return "Mcp-Name header has a malformed value";

‎mcp-test/src/test/java/io/modelcontextprotocol/modern/server/transport/HttpServletMcpTransportIntegrationTests.java‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.junit.jupiter.api.BeforeAll;
5656
import org.junit.jupiter.api.Test;
5757
import org.junit.jupiter.params.ParameterizedTest;
58+
import org.junit.jupiter.params.provider.CsvSource;
5859
import org.junit.jupiter.params.provider.NullAndEmptySource;
5960
import org.junit.jupiter.params.provider.ValueSource;
6061
import reactor.core.publisher.Flux;
@@ -403,6 +404,39 @@ void headerMismatchIsRejected() throws Exception {
403404
assertThat(((Number) error.get("code")).intValue()).isEqualTo(ErrorCodes.HEADER_MISMATCH);
404405
}
405406

407+
@ParameterizedTest
408+
@CsvSource({ "Mcp-Method, tools/call", "Mcp-Method, tools/list", "Mcp-Name, echo", "Mcp-Name, other",
409+
"MCP-Protocol-Version, " + McpSchema.LATEST_PROTOCOL_VERSION })
410+
void repeatedStandardHeaderIsRejected(String name, String extraValue) throws Exception {
411+
Map<String, Object> params = new HashMap<>();
412+
params.put("_meta", meta());
413+
params.put("name", "echo");
414+
// The request already carries a valid value for every standard header; this adds
415+
// a second line for one of them.
416+
HttpRequest request = post("tools/call", params).header("Mcp-Name", "echo").header(name, extraValue).build();
417+
HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
418+
419+
assertThat(response.statusCode()).isEqualTo(400);
420+
assertThatJson(response.body()).inPath("$.error.code").isEqualTo(ErrorCodes.HEADER_MISMATCH);
421+
assertThatJson(response.body()).inPath("$.error.message").isEqualTo(name + " header has more than one value");
422+
}
423+
424+
@Test
425+
void commaJoinedMcpMethodIsRejected() throws Exception {
426+
Map<String, Object> params = new HashMap<>();
427+
params.put("_meta", meta());
428+
params.put("name", "echo");
429+
HttpRequest request = post("tools/call", params).header("Mcp-Name", "echo")
430+
.setHeader("Mcp-Method", "tools/call, tools/list")
431+
.build();
432+
HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
433+
434+
assertThat(response.statusCode()).isEqualTo(400);
435+
assertThatJson(response.body()).inPath("$.error.code").isEqualTo(ErrorCodes.HEADER_MISMATCH);
436+
assertThatJson(response.body()).inPath("$.error.message")
437+
.isEqualTo("Mcp-Method header does not match request method");
438+
}
439+
406440
@Test
407441
void protocolVersionHeaderMismatchIsRejected() throws Exception {
408442
Map<String, Object> meta = meta();

0 commit comments

Comments
 (0)