diff --git a/src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs b/src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs index f7db4630e..0d3cf3240 100644 --- a/src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs +++ b/src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs @@ -704,8 +704,8 @@ private static string[] GetConfiguredSupportedProtocolVersions(string? protocolV } /// - /// Validates that HTTP requests using per-request metadata declare the same protocol version in both - /// the MCP-Protocol-Version header and body _meta envelope. + /// Validates that HTTP requests declare matching protocol versions in the + /// MCP-Protocol-Version header and the corresponding body field. /// private static bool ValidateProtocolVersionEnvelope( HttpContext context, @@ -719,6 +719,18 @@ private static bool ValidateProtocolVersionEnvelope( } var protocolVersionHeader = context.Request.Headers[McpProtocolVersionHeaderName].ToString(); + + if (message is JsonRpcRequest { Method: RequestMethods.Initialize, Params: JsonObject initializeParams } && + initializeParams["protocolVersion"] is JsonValue initializeProtocolVersionValue && + initializeProtocolVersionValue.TryGetValue(out string? initializeProtocolVersion) && + !string.IsNullOrEmpty(protocolVersionHeader) && + !string.Equals(protocolVersionHeader, initializeProtocolVersion, StringComparison.Ordinal)) + { + errorDetail = CreateHeaderMismatchError( + $"Bad Request: The {McpProtocolVersionHeaderName} header value '{protocolVersionHeader}' does not match body params.protocolVersion value '{initializeProtocolVersion}'."); + return false; + } + bool hasProtocolVersionMeta = TryGetProtocolVersionMeta(message, out var protocolVersionMeta); if (!McpProtocolVersions.RequiresPerRequestMetadata(protocolVersionHeader) && diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/StreamableHttpServerConformanceTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/StreamableHttpServerConformanceTests.cs index 2bdd9d7e6..3422405a6 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/StreamableHttpServerConformanceTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/StreamableHttpServerConformanceTests.cs @@ -204,6 +204,31 @@ public async Task PostRequest_Succeeds_WithValidProtocolVersionHeader() Assert.Equal(HttpStatusCode.OK, response.StatusCode); } + [Theory] + [InlineData(false, McpProtocolVersions.March2025ProtocolVersion, McpProtocolVersions.November2025ProtocolVersion)] + [InlineData(false, McpProtocolVersions.November2025ProtocolVersion, McpProtocolVersions.March2025ProtocolVersion)] + [InlineData(true, McpProtocolVersions.March2025ProtocolVersion, McpProtocolVersions.November2025ProtocolVersion)] + [InlineData(true, McpProtocolVersions.November2025ProtocolVersion, McpProtocolVersions.March2025ProtocolVersion)] + public async Task InitializeRequest_IsBadRequest_WhenProtocolVersionHeaderDoesNotMatchBody( + bool stateless, + string protocolVersionHeader, + string protocolVersionBody) + { + await StartAsync(stateless); + + var body = $$$$""" + {"jsonrpc":"2.0","id":4242,"method":"initialize","params":{"protocolVersion":"{{{{protocolVersionBody}}}}","capabilities":{},"clientInfo":{"name":"IntegrationTestClient","version":"1.0.0"}}} + """; + using var request = new HttpRequestMessage(HttpMethod.Post, "") { Content = JsonContent(body) }; + request.Headers.Add("MCP-Protocol-Version", protocolVersionHeader); + using var response = await HttpClient.SendAsync(request, TestContext.Current.CancellationToken); + + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + var json = JsonNode.Parse(await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); + Assert.Equal(4242, json!["id"]!.GetValue()); + Assert.Equal((int)McpErrorCode.HeaderMismatch, json["error"]!["code"]!.GetValue()); + } + [Fact] public async Task GetRequest_IsBadRequest_WithInvalidProtocolVersionHeader() {