diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java b/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java index 48462c0db..bd98cd910 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/client/transport/HttpClientStreamableHttpTransport.java @@ -503,6 +503,13 @@ public Mono sendMessage(McpSchema.JSONRPCMessage sentMessage) { transportSession.sessionId().get()); } + if (sentMessage instanceof McpSchema.JSONRPCRequest request) { + requestBuilder = requestBuilder.header(HttpHeaders.MCP_METHOD, request.method()); + } + else if (sentMessage instanceof McpSchema.JSONRPCNotification notification) { + requestBuilder = requestBuilder.header(HttpHeaders.MCP_METHOD, notification.method()); + } + var builder = requestBuilder.uri(uri) .header(HttpHeaders.ACCEPT, APPLICATION_JSON + ", " + TEXT_EVENT_STREAM) .header(HttpHeaders.CONTENT_TYPE, APPLICATION_JSON_UTF8) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/HttpHeaders.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/HttpHeaders.java index 6afc2c119..d62f90915 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/HttpHeaders.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/HttpHeaders.java @@ -26,6 +26,13 @@ public interface HttpHeaders { */ String PROTOCOL_VERSION = "MCP-Protocol-Version"; + /** + * Mirrors the JSON-RPC method of the request or notification carried in the body. + * @see SEP-2243 + */ + String MCP_METHOD = "Mcp-Method"; + /** * The HTTP Content-Length header. * @see { + var initializeRequest = McpSchema.InitializeRequest + .builder(ProtocolVersions.MCP_2025_11_25, McpSchema.ClientCapabilities.builder().roots(true).build(), + McpSchema.Implementation.builder("MCP Client", "0.3.1").build()) + .build(); + var testMessage = new McpSchema.JSONRPCRequest(McpSchema.METHOD_INITIALIZE, "test-id", initializeRequest); + + StepVerifier + .create(t.sendMessage(testMessage).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context))) + .verifyComplete(); + + var requestCaptor = ArgumentCaptor.forClass(HttpRequest.Builder.class); + verify(mockRequestCustomizer, atLeastOnce()).customize(requestCaptor.capture(), eq("POST"), eq(uri), any(), + eq(context)); + assertThat(requestCaptor.getValue().build().headers().firstValue(HttpHeaders.MCP_METHOD)) + .hasValue(McpSchema.METHOD_INITIALIZE); + }); + } + + @Test + void testMcpMethodHeaderOnNotification() throws URISyntaxException { + var uri = new URI(host + "/mcp"); + var mockRequestCustomizer = mock(McpSyncHttpClientRequestCustomizer.class); + + var transport = HttpClientStreamableHttpTransport.builder(host) + .httpRequestCustomizer(mockRequestCustomizer) + .build(); + + withTransport(transport, (t) -> { + var initializeRequest = McpSchema.InitializeRequest + .builder(ProtocolVersions.MCP_2025_11_25, McpSchema.ClientCapabilities.builder().roots(true).build(), + McpSchema.Implementation.builder("MCP Client", "0.3.1").build()) + .build(); + var initializeMessage = new McpSchema.JSONRPCRequest(McpSchema.METHOD_INITIALIZE, "test-id", + initializeRequest); + StepVerifier + .create(t.sendMessage(initializeMessage).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context))) + .verifyComplete(); + + var testMessage = new McpSchema.JSONRPCNotification(McpSchema.METHOD_NOTIFICATION_INITIALIZED); + + StepVerifier + .create(t.sendMessage(testMessage).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context))) + .verifyComplete(); + + var requestCaptor = ArgumentCaptor.forClass(HttpRequest.Builder.class); + verify(mockRequestCustomizer, atLeastOnce()).customize(requestCaptor.capture(), eq("POST"), eq(uri), any(), + eq(context)); + assertThat(requestCaptor.getValue().build().headers().firstValue(HttpHeaders.MCP_METHOD)) + .hasValue(McpSchema.METHOD_NOTIFICATION_INITIALIZED); + }); + } + + @Test + void testNoMcpMethodHeaderOnResponse() throws URISyntaxException { + var uri = new URI(host + "/mcp"); + var mockRequestCustomizer = mock(McpSyncHttpClientRequestCustomizer.class); + + var transport = HttpClientStreamableHttpTransport.builder(host) + .httpRequestCustomizer(mockRequestCustomizer) + .build(); + + withTransport(transport, (t) -> { + var initializeRequest = McpSchema.InitializeRequest + .builder(ProtocolVersions.MCP_2025_11_25, McpSchema.ClientCapabilities.builder().roots(true).build(), + McpSchema.Implementation.builder("MCP Client", "0.3.1").build()) + .build(); + var initializeMessage = new McpSchema.JSONRPCRequest(McpSchema.METHOD_INITIALIZE, "test-id", + initializeRequest); + StepVerifier + .create(t.sendMessage(initializeMessage).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context))) + .verifyComplete(); + + var testMessage = McpSchema.JSONRPCResponse.result("test-id-2", Map.of()); + + StepVerifier + .create(t.sendMessage(testMessage).contextWrite(ctx -> ctx.put(McpTransportContext.KEY, context))) + .verifyComplete(); + + var requestCaptor = ArgumentCaptor.forClass(HttpRequest.Builder.class); + verify(mockRequestCustomizer, atLeastOnce()).customize(requestCaptor.capture(), eq("POST"), eq(uri), any(), + eq(context)); + assertThat(requestCaptor.getValue().build().headers().firstValue(HttpHeaders.MCP_METHOD)).isEmpty(); + }); + } + }