Repository navigation
POST /mcp with a missing or unparseable Content-Type returns HTTP 500 instead of a 4xx #1966
Copy link
Copy link
Open
Labels
bugSomething isn't workingSomething isn't working
Description
Describe the bug
A Streamable HTTP POST whose Content-Type header is missing, empty or cannot be parsed as JSON throws an unhandled InvalidOperationException and the server returns HTTP 500. A well-formed but non-JSON type such as text/plain gets a 415.
To Reproduce
Save this as app.cs and run dotnet run app.cs. It hosts a minimal stateless MCP server in-process and POSTs an initialize request with different Content-Type values.
#:sdk Microsoft.NET.Sdk.Web
#:package ModelContextProtocol.AspNetCore@2.3.0
// Reproduction: a Streamable HTTP POST with a missing or unparseable Content-Type returns HTTP 500
// (unhandled InvalidOperationException) instead of a 4xx.
//
// Run with: dotnet run app.cs (.NET 10 SDK)
using System.ComponentModel;
using System.Text;
using ModelContextProtocol.Server;
var builder = WebApplication.CreateBuilder(args);
builder.Logging.SetMinimumLevel(LogLevel.None);
builder.WebHost.UseUrls("http://127.0.0.1:0");
builder.Services
.AddMcpServer()
.WithHttpTransport(options => options.Stateless = true)
.WithTools<EchoTool>();
var app = builder.Build();
// Observe only: print the exception that escapes the SDK handler, then rethrow so the host
// still produces its normal response.
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (Exception exception)
{
Console.WriteLine($" unhandled {exception.GetType().Name}: {exception.Message}");
throw;
}
});
app.MapMcp("/mcp");
await app.StartAsync();
const string initialize =
"""{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"repro","version":"1.0"}}}""";
string?[] contentTypes =
[
"application/json",
"text/plain",
null,
"",
"/json"
];
using var client = new HttpClient { BaseAddress = new Uri(app.Urls.First()) };
Console.WriteLine($"{"Content-Type",-30} Status");
foreach (var contentType in contentTypes)
{
using var request = new HttpRequestMessage(HttpMethod.Post, "/mcp");
request.Headers.Accept.ParseAdd("application/json");
request.Headers.Accept.ParseAdd("text/event-stream");
request.Content = new StringContent(initialize, Encoding.UTF8);
request.Content.Headers.Remove("Content-Type");
if (contentType is not null)
request.Content.Headers.TryAddWithoutValidation("Content-Type", contentType);
using var response = await client.SendAsync(request);
Console.WriteLine($"{contentType switch { null => "<absent>", "" => "<empty>", _ => contentType },-30} {(int)response.StatusCode}");
}
await app.StopAsync();
[McpServerToolType]
public sealed class EchoTool
{
[McpServerTool, Description("Echoes the message.")]
public static string Echo(string message) => message;
}Expected behavior
A 4xx response (for example 415, as text/plain already gets) instead of a 500.
Logs
Content-Type Status
application/json 200
text/plain 415
unhandled InvalidOperationException: Unable to read the request as JSON because the request content type '' is not a known JSON content type.
<absent> 500
unhandled InvalidOperationException: Unable to read the request as JSON because the request content type '' is not a known JSON content type.
<empty> 500
unhandled InvalidOperationException: Unable to read the request as JSON because the request content type '/json' is not a known JSON content type.
/json 500
Additional context
- Reproduced on
ModelContextProtocol.AspNetCore2.3.0
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working
Activity
as992949791 commentedon Oct 28, 2026
Hi, I can take this and add an ASP.NET Core integration regression for missing, empty, and malformed Content-Type values while preserving the existing
text/plain415 response. GitHub rejected self-assignment for my account (ReplaceActorsForAssignablepermission), so I cannot complete the repository's coordination step. Could a maintainer assign me or confirm that I may proceed? I will include a visible AI-assistance disclosure and validate the full test matrix before proposing a PR.AI assistance was used to prepare this comment.