Skip to content

Commit 4f1052a

Browse files
committed
feat(mcp-core): support Mcp-Name header for Streamable HTTP (SEP-2243)
Add MCP_NAME header constant and inject it in the streamable HTTP client transport (HttpClientStreamableHttpTransport)
1 parent c8ab341 commit 4f1052a

5 files changed

Lines changed: 137 additions & 4 deletions

File tree

mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -483,14 +483,34 @@ public Mono<Void> sendMessage(McpSchema.JSONRPCMessage sentMessage) {
483483
transportSession.sessionId().get());
484484
}
485485

486+
// Extract method and params for Mcp-Name header
487+
String method = null;
488+
Object params = null;
489+
if (sentMessage instanceof McpSchema.JSONRPCRequest request) {
490+
method = request.method();
491+
params = request.params();
492+
}
493+
else if (sentMessage instanceof McpSchema.JSONRPCNotification notification) {
494+
method = notification.method();
495+
params = notification.params();
496+
}
497+
486498
var builder = requestBuilder.uri(uri)
487499
.header(HttpHeaders.ACCEPT, APPLICATION_JSON + ", " + TEXT_EVENT_STREAM)
488500
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON_UTF8)
489501
.header(HttpHeaders.CACHE_CONTROL, "no-cache")
490-
.header(HttpHeaders.PROTOCOL_VERSION,
491-
ctx.getOrDefault(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION,
492-
this.latestSupportedProtocolVersion))
493-
.POST(HttpRequest.BodyPublishers.ofString(jsonBody));
502+
.header(HttpHeaders.PROTOCOL_VERSION, ctx.getOrDefault(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION,
503+
this.latestSupportedProtocolVersion));
504+
505+
// Add Mcp-Name header if applicable
506+
if (method != null) {
507+
String name = extractNameFromParams(method, params);
508+
if (name != null) {
509+
builder = builder.header(HttpHeaders.MCP_NAME, name);
510+
}
511+
}
512+
513+
builder = builder.POST(HttpRequest.BodyPublishers.ofString(jsonBody));
494514
var transportContext = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY);
495515
return Mono
496516
.from(this.httpRequestCustomizer.customize(builder, "POST", uri, jsonBody, transportContext));
@@ -675,6 +695,48 @@ public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
675695
return this.jsonMapper.convertValue(data, typeRef);
676696
}
677697

698+
/**
699+
* Extracts the name/URI from the request params based on the method type.
700+
* @param method the MCP method name
701+
* @param params the request parameters
702+
* @return the name/URI if applicable for the method, or null otherwise
703+
*/
704+
private String extractNameFromParams(String method, Object params) {
705+
if (params == null) {
706+
return null;
707+
}
708+
709+
try {
710+
switch (method) {
711+
case McpSchema.METHOD_TOOLS_CALL -> {
712+
McpSchema.CallToolRequest request = this.jsonMapper.convertValue(params,
713+
new TypeRef<McpSchema.CallToolRequest>() {
714+
});
715+
return request.name();
716+
}
717+
case McpSchema.METHOD_RESOURCES_READ -> {
718+
McpSchema.ReadResourceRequest request = this.jsonMapper.convertValue(params,
719+
new TypeRef<McpSchema.ReadResourceRequest>() {
720+
});
721+
return request.uri();
722+
}
723+
case McpSchema.METHOD_PROMPT_GET -> {
724+
McpSchema.GetPromptRequest request = this.jsonMapper.convertValue(params,
725+
new TypeRef<McpSchema.GetPromptRequest>() {
726+
});
727+
return request.name();
728+
}
729+
default -> {
730+
return null;
731+
}
732+
}
733+
}
734+
catch (Exception e) {
735+
logger.debug("Failed to extract name from params for method {}: {}", method, e.getMessage());
736+
return null;
737+
}
738+
}
739+
678740
/**
679741
* Builder for {@link HttpClientStreamableHttpTransport}.
680742
*/

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,42 @@ public void onStartAsync(jakarta.servlet.AsyncEvent event) throws IOException {
390390
* @throws ServletException If a servlet-specific error occurs
391391
* @throws IOException If an I/O error occurs
392392
*/
393+
private String extractNameFromParams(String method, Object params) {
394+
if (params == null) {
395+
return null;
396+
}
397+
398+
try {
399+
switch (method) {
400+
case McpSchema.METHOD_TOOLS_CALL -> {
401+
McpSchema.CallToolRequest request = jsonMapper.convertValue(params,
402+
new TypeRef<McpSchema.CallToolRequest>() {
403+
});
404+
return request.name();
405+
}
406+
case McpSchema.METHOD_RESOURCES_READ -> {
407+
McpSchema.ReadResourceRequest request = jsonMapper.convertValue(params,
408+
new TypeRef<McpSchema.ReadResourceRequest>() {
409+
});
410+
return request.uri();
411+
}
412+
case McpSchema.METHOD_PROMPT_GET -> {
413+
McpSchema.GetPromptRequest request = jsonMapper.convertValue(params,
414+
new TypeRef<McpSchema.GetPromptRequest>() {
415+
});
416+
return request.name();
417+
}
418+
default -> {
419+
return null;
420+
}
421+
}
422+
}
423+
catch (Exception e) {
424+
logger.debug("Failed to extract name from params for method {}: {}", method, e.getMessage());
425+
return null;
426+
}
427+
}
428+
393429
@Override
394430
protected void doPost(HttpServletRequest request, HttpServletResponse response)
395431
throws ServletException, IOException {
@@ -436,6 +472,32 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
436472

437473
McpSchema.JSONRPCMessage message = McpSchema.deserializeJsonRpcMessage(jsonMapper, body.toString());
438474

475+
if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
476+
String method = jsonrpcRequest.method();
477+
if (McpSchema.METHOD_TOOLS_CALL.equals(method) || McpSchema.METHOD_RESOURCES_READ.equals(method)
478+
|| McpSchema.METHOD_PROMPT_GET.equals(method)) {
479+
String expectedName = extractNameFromParams(method, jsonrpcRequest.params());
480+
String headerName = request.getHeader(HttpHeaders.MCP_NAME);
481+
482+
if (headerName == null || headerName.isBlank()) {
483+
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
484+
McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
485+
.message("Mcp-Name header required for method " + method)
486+
.build());
487+
return;
488+
}
489+
490+
if (expectedName == null || !headerName.equals(expectedName)) {
491+
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
492+
McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
493+
.message("Mcp-Name header mismatch: expected '" + expectedName + "' but was '"
494+
+ headerName + "'")
495+
.build());
496+
return;
497+
}
498+
}
499+
}
500+
439501
// Handle initialization request
440502
if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest
441503
&& jsonrpcRequest.method().equals(McpSchema.METHOD_INITIALIZE)) {

mcp-core/src/main/java/io/modelcontextprotocol/spec/HttpHeaders.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ public interface HttpHeaders {
2626
*/
2727
String PROTOCOL_VERSION = "MCP-Protocol-Version";
2828

29+
/**
30+
* The name or URI of the resource/tool/prompt being accessed.
31+
*/
32+
String MCP_NAME = "Mcp-Name";
33+
2934
/**
3035
* The HTTP Content-Length header.
3136
* @see <a href=

mcp-json-jackson2/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
Import-Package: io.modelcontextprotocol.json,io.modelcontextprotocol.json.schema, \
4343
*;
4444
Service-Component: OSGI-INF/io.modelcontextprotocol.json.jackson2.JacksonMcpJsonMapperSupplier.xml,OSGI-INF/io.modelcontextprotocol.json.schema.jackson2.JacksonJsonSchemaValidatorSupplier.xml
45+
Export-Package: io.modelcontextprotocol.json.jackson2;version="${project.version}";-noimport:=true, \
46+
io.modelcontextprotocol.json.schema.jackson2;version="${project.version}";-noimport:=true
4547
-noimportjava: true;
4648
-nouses: true;
4749
-removeheaders: Private-Package

mcp-json-jackson3/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
Import-Package: io.modelcontextprotocol.json,io.modelcontextprotocol.json.schema, \
4343
*;
4444
Service-Component: OSGI-INF/io.modelcontextprotocol.json.jackson3.JacksonMcpJsonMapperSupplier.xml,OSGI-INF/io.modelcontextprotocol.json.schema.jackson3.JacksonJsonSchemaValidatorSupplier.xml
45+
Export-Package: io.modelcontextprotocol.json.jackson3;version="${project.version}";-noimport:=true, \
46+
io.modelcontextprotocol.json.schema.jackson3;version="${project.version}";-noimport:=true
4547
-noimportjava: true;
4648
-nouses: true;
4749
-removeheaders: Private-Package

0 commit comments

Comments
 (0)