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
19 changes: 16 additions & 3 deletions src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1040,6 +1040,14 @@ internal static bool ValidateMcpHeaders(HttpContext context, JsonRpcMessage mess
_ => null,
};
var mcpNameInBody = GetJsonNodeStringProperty(bodyParams, routingNameParameter);
if (mcpNameInBody is null &&
bodyParams is System.Text.Json.Nodes.JsonObject paramsObject &&
paramsObject.TryGetPropertyValue(routingNameParameter, out var routingNameNode) &&
routingNameNode is not null)
{
errorMessage = $"Header mismatch: body '{routingNameParameter}' value must be a string.";
return false;
}

// Check that the header value matches the body value if the body value is present.
if (!string.Equals(decodedMcpNameInHeader, mcpNameInBody, StringComparison.Ordinal))
Expand Down Expand Up @@ -1246,9 +1254,14 @@ argForMissing is not null &&

private static string? GetJsonNodeStringProperty(System.Text.Json.Nodes.JsonNode? node, string propertyName)
{
if (node is System.Text.Json.Nodes.JsonObject obj && obj.TryGetPropertyValue(propertyName, out var value))
{
return value?.GetValue<string>();
// A non-string value (number, bool, object, array) is a malformed request, not a server error,
// so report it as absent rather than letting GetValue<string>() throw.
if (node is System.Text.Json.Nodes.JsonObject obj &&
obj.TryGetPropertyValue(propertyName, out var value) &&
value is System.Text.Json.Nodes.JsonValue jsonValue &&
jsonValue.TryGetValue<string>(out var stringValue))
{
return stringValue;
}

return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,35 @@ public async Task Server_RejectsHeaderMismatch_WhenEmptyHeaderDoesNotMatchBody()
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}

[Theory]
[InlineData("tools/call", "name", "42")]
[InlineData("tools/call", "name", "true")]
[InlineData("tools/call", "name", "{}")]
[InlineData("tools/call", "name", "[]")]
[InlineData("prompts/get", "name", "42")]
[InlineData("resources/read", "uri", "{}")]
public async Task Server_RejectsNonStringRoutingNameInBody_WithJsonRpcError(string method, string routingParameter, string bodyValue)
{
await StartAsync();
await ProbeWithJuly2026ProtocolVersionAsync();

var callJson = "{\"jsonrpc\":\"2.0\",\"id\":77,\"method\":\"" + method + "\",\"params\":{\"" + routingParameter + "\":" + bodyValue +
@",""_meta"":{""io.modelcontextprotocol/protocolVersion"":""2026-07-28"",""io.modelcontextprotocol/clientInfo"":{""name"":""TestClient"",""version"":""1.0""},""io.modelcontextprotocol/clientCapabilities"":{}}}}";

using var request = new HttpRequestMessage(HttpMethod.Post, "");
request.Content = new StringContent(callJson, Encoding.UTF8, "application/json");
request.Headers.Add("MCP-Protocol-Version", "2026-07-28");
request.Headers.Add("Mcp-Method", method);
request.Headers.Add("Mcp-Name", "42");

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(77, json["id"]!.GetValue<int>());
Assert.Equal((int)McpErrorCode.HeaderMismatch, json["error"]!["code"]!.GetValue<int>());
}

[Fact]
public async Task Server_AcceptsBase64EncodedHeaderWithControlChars()
{
Expand Down