Skip to content

POST /mcp with a missing or unparseable Content-Type returns HTTP 500 instead of a 4xx #1966

Open

Description

@ItsVeryWindy

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.AspNetCore 2.3.0

Activity

  1. as992949791 commented on Oct 28, 2026

    @as992949791

    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/plain 415 response. GitHub rejected self-assignment for my account (ReplaceActorsForAssignable permission), 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions