Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -483,14 +483,34 @@ public Mono<Void> sendMessage(McpSchema.JSONRPCMessage sentMessage) {
transportSession.sessionId().get());
}

// Extract method and params for Mcp-Name header
String method = null;
Object params = null;
if (sentMessage instanceof McpSchema.JSONRPCRequest request) {
method = request.method();
params = request.params();
}
else if (sentMessage instanceof McpSchema.JSONRPCNotification notification) {
method = notification.method();
params = notification.params();
}

var builder = requestBuilder.uri(uri)
.header(HttpHeaders.ACCEPT, APPLICATION_JSON + ", " + TEXT_EVENT_STREAM)
.header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON_UTF8)
.header(HttpHeaders.CACHE_CONTROL, "no-cache")
.header(HttpHeaders.PROTOCOL_VERSION,
ctx.getOrDefault(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION,
this.latestSupportedProtocolVersion))
.POST(HttpRequest.BodyPublishers.ofString(jsonBody));
.header(HttpHeaders.PROTOCOL_VERSION, ctx.getOrDefault(McpAsyncClient.NEGOTIATED_PROTOCOL_VERSION,
this.latestSupportedProtocolVersion));

// Add Mcp-Name header if applicable
if (method != null) {
String name = extractNameFromParams(method, params);
if (name != null) {
builder = builder.header(HttpHeaders.MCP_NAME, name);
}
}

builder = builder.POST(HttpRequest.BodyPublishers.ofString(jsonBody));
var transportContext = ctx.getOrDefault(McpTransportContext.KEY, McpTransportContext.EMPTY);
return Mono
.from(this.httpRequestCustomizer.customize(builder, "POST", uri, jsonBody, transportContext));
Expand Down Expand Up @@ -675,6 +695,48 @@ public <T> T unmarshalFrom(Object data, TypeRef<T> typeRef) {
return this.jsonMapper.convertValue(data, typeRef);
}

/**
* Extracts the name/URI from the request params based on the method type.
* @param method the MCP method name
* @param params the request parameters
* @return the name/URI if applicable for the method, or null otherwise
*/
private String extractNameFromParams(String method, Object params) {
if (params == null) {
return null;
}

try {
switch (method) {
case McpSchema.METHOD_TOOLS_CALL -> {
McpSchema.CallToolRequest request = this.jsonMapper.convertValue(params,
new TypeRef<McpSchema.CallToolRequest>() {
});
return request.name();
}
case McpSchema.METHOD_RESOURCES_READ -> {
McpSchema.ReadResourceRequest request = this.jsonMapper.convertValue(params,
new TypeRef<McpSchema.ReadResourceRequest>() {
});
return request.uri();
}
case McpSchema.METHOD_PROMPT_GET -> {
McpSchema.GetPromptRequest request = this.jsonMapper.convertValue(params,
new TypeRef<McpSchema.GetPromptRequest>() {
});
return request.name();
}
default -> {
return null;
}
}
}
catch (Exception e) {
logger.debug("Failed to extract name from params for method {}: {}", method, e.getMessage());
return null;
}
}

/**
* Builder for {@link HttpClientStreamableHttpTransport}.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,42 @@ public void onStartAsync(jakarta.servlet.AsyncEvent event) throws IOException {
* @throws ServletException If a servlet-specific error occurs
* @throws IOException If an I/O error occurs
*/
private String extractNameFromParams(String method, Object params) {
if (params == null) {
return null;
}

try {
switch (method) {
case McpSchema.METHOD_TOOLS_CALL -> {
McpSchema.CallToolRequest request = jsonMapper.convertValue(params,
new TypeRef<McpSchema.CallToolRequest>() {
});
return request.name();
}
case McpSchema.METHOD_RESOURCES_READ -> {
McpSchema.ReadResourceRequest request = jsonMapper.convertValue(params,
new TypeRef<McpSchema.ReadResourceRequest>() {
});
return request.uri();
}
case McpSchema.METHOD_PROMPT_GET -> {
McpSchema.GetPromptRequest request = jsonMapper.convertValue(params,
new TypeRef<McpSchema.GetPromptRequest>() {
});
return request.name();
}
default -> {
return null;
}
}
}
catch (Exception e) {
logger.debug("Failed to extract name from params for method {}: {}", method, e.getMessage());
return null;
}
}

@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
Expand Down Expand Up @@ -436,6 +472,32 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)

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

if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest) {
String method = jsonrpcRequest.method();
if (McpSchema.METHOD_TOOLS_CALL.equals(method) || McpSchema.METHOD_RESOURCES_READ.equals(method)
|| McpSchema.METHOD_PROMPT_GET.equals(method)) {
String expectedName = extractNameFromParams(method, jsonrpcRequest.params());
String headerName = request.getHeader(HttpHeaders.MCP_NAME);

if (headerName == null || headerName.isBlank()) {
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
.message("Mcp-Name header required for method " + method)
.build());
return;
}

if (expectedName == null || !headerName.equals(expectedName)) {
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
.message("Mcp-Name header mismatch: expected '" + expectedName + "' but was '"
+ headerName + "'")
.build());
return;
}
}
}

// Handle initialization request
if (message instanceof McpSchema.JSONRPCRequest jsonrpcRequest
&& jsonrpcRequest.method().equals(McpSchema.METHOD_INITIALIZE)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ public interface HttpHeaders {
*/
String PROTOCOL_VERSION = "MCP-Protocol-Version";

/**
* The name or URI of the resource/tool/prompt being accessed.
*/
String MCP_NAME = "Mcp-Name";

/**
* The HTTP Content-Length header.
* @see <a href=
Expand Down