diff --git a/src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs b/src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs index f7db4630e..f5e0aff48 100644 --- a/src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs +++ b/src/ModelContextProtocol.AspNetCore/StreamableHttpHandler.cs @@ -45,6 +45,7 @@ internal sealed class StreamableHttpHandler( private static readonly JsonTypeInfo s_messageTypeInfo = GetRequiredJsonTypeInfo(); private static readonly JsonTypeInfo s_errorTypeInfo = GetRequiredJsonTypeInfo(); + private static readonly JsonTypeInfo s_initializeRequestParamsTypeInfo = GetRequiredJsonTypeInfo(); private static bool AllowNewSessionForNonInitializeRequests { get; } = AppContext.TryGetSwitch("ModelContextProtocol.AspNetCore.AllowNewSessionForNonInitializeRequests", out var enabled) && enabled; @@ -116,6 +117,21 @@ await WriteJsonRpcErrorAsync(context, return; } + if (message is JsonRpcRequest { Method: RequestMethods.Initialize } initializeRequest) + { + try + { + JsonSerializer.Deserialize(initializeRequest.Params, s_initializeRequestParamsTypeInfo); + } + catch (JsonException ex) + { + await WriteJsonRpcErrorAsync(context, + $"Bad Request: The initialize request parameters were invalid. {ex.Message}", + StatusCodes.Status400BadRequest, (int)McpErrorCode.InvalidParams, requestId); + return; + } + } + var session = await GetOrCreateSessionAsync(context, message, requestId); if (session is null) { diff --git a/tests/ModelContextProtocol.AspNetCore.Tests/StreamableHttpServerConformanceTests.cs b/tests/ModelContextProtocol.AspNetCore.Tests/StreamableHttpServerConformanceTests.cs index 2bdd9d7e6..a02292872 100644 --- a/tests/ModelContextProtocol.AspNetCore.Tests/StreamableHttpServerConformanceTests.cs +++ b/tests/ModelContextProtocol.AspNetCore.Tests/StreamableHttpServerConformanceTests.cs @@ -293,6 +293,25 @@ public async Task PostMalformedJson_Returns400_InvalidRequest_WithNullId() Assert.Equal((int)McpErrorCode.InvalidRequest, doc.RootElement.GetProperty("error").GetProperty("code").GetInt32()); } + [Fact] + public async Task InitializeWithMissingClientVersion_Returns400_InvalidParams_WithRequestId() + { + await StartAsync(); + + const string request = """ + {"jsonrpc":"2.0","id":7,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"IntegrationTestClient"}}} + """; + + using var response = await HttpClient.PostAsync("", JsonContent(request), TestContext.Current.CancellationToken); + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.False(response.Headers.Contains("mcp-session-id")); + + using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync(TestContext.Current.CancellationToken)); + Assert.Equal(7, doc.RootElement.GetProperty("id").GetInt64()); + Assert.Equal((int)McpErrorCode.InvalidParams, doc.RootElement.GetProperty("error").GetProperty("code").GetInt32()); + Assert.Contains("version", doc.RootElement.GetProperty("error").GetProperty("message").GetString(), StringComparison.OrdinalIgnoreCase); + } + [Fact] public async Task PostRequestWithExplicitNullId_Returns400_InvalidRequest_WithNullId() {