Skip to content

HttpClientTransport sends Content-Type: application/json; charset=utf-8`, which is rejected by spec-strict MCP servers (e.g. GitHub Copilot returns 415) #1527

@shmed

Description

@shmed

Describe the bug

The Streamable HTTP transport sends POST requests with Content-Type: application/json; charset=utf-8. Per IANA's registration for application/json and RFC 8259 §11, no charset parameter is defined for application/json — JSON is always Unicode and the parameter is meaningless.

GitHub Copilot's MCP endpoint enforces this strictly and rejects every request from the SDK with HTTP 415:

HTTP/1.1 415 Unsupported Media Type
Content-Type must be 'application/json'

This makes it impossible to connect to GitHub's MCP server (and any similarly strict server) without injecting a custom HttpClient that strips the charset parameter.

To Reproduce

Requires a GitHub Copilot–enabled PAT (any scope that can call https://api.githubcopilot.com/mcp — same token used in MCP Inspector).

// dotnet add package ModelContextProtocol --version 1.2.0
using ModelContextProtocol.Client;

var pat = Environment.GetEnvironmentVariable("GITHUB_COPILOT_PAT")
    ?? throw new InvalidOperationException("Set GITHUB_COPILOT_PAT");

var options = new HttpClientTransportOptions
{
    Endpoint = new Uri("https://api.githubcopilot.com/mcp"),
    TransportMode = HttpTransportMode.StreamableHttp,
    AdditionalHeaders = new Dictionary<string, string>
    {
        ["Authorization"] = $"Bearer {pat}",
    },
};

var transport = new HttpClientTransport(options);
await using var client = await McpClient.CreateAsync(transport);
// Throws: 415 Unsupported Media Type — Content-Type must be 'application/json'

The same PAT against the same endpoint works in MCP Inspector and with curl, confirming the issue is the SDK's Content-Type header, not the credential or the server.

curl showing the server accepts a bare application/json:

curl -i -X POST https://api.githubcopilot.com/mcp \
  -H "Authorization: Bearer $GITHUB_COPILOT_PAT" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"repro","version":"1.0"}}}'
# 200 OK with an SSE response

# Same request, but with charset=utf-8:
curl -i -X POST https://api.githubcopilot.com/mcp \
  -H "Authorization: Bearer $GITHUB_COPILOT_PAT" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json; charset=utf-8" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"repro","version":"1.0"}}}'
# HTTP/1.1 415 Unsupported Media Type
# Content-Type must be 'application/json'

Expected behavior

The SDK should send Content-Type: application/json (no charset parameter) for JSON-RPC POST bodies, matching the IANA registration for application/json.

Actual behavior

System.Net.Http.HttpRequestException: Response status code does not indicate success: 415 (Unsupported Media Type). Response body: Content-Type must be 'application/json'
   at ModelContextProtocol.HttpResponseMessageExtensions.EnsureSuccessStatusCodeWithResponseBodyAsync(...)
   at ModelContextProtocol.Client.StreamableHttpClientSessionTransport.SendMessageAsync(...)
   at ModelContextProtocol.McpSessionHandler.SendRequestAsync(...)
   ...

Workaround

Inject a custom HttpClient (via the HttpClientTransport(options, httpClient, loggerFactory, ownsHttpClient) constructor) wrapped with a DelegatingHandler that strips the charset parameter before each request:

internal sealed class StrictJsonContentTypeHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct)
    {
        var contentType = request.Content?.Headers?.ContentType;
        if (contentType is not null &&
            string.Equals(contentType.MediaType, "application/json", StringComparison.OrdinalIgnoreCase) &&
            !string.IsNullOrEmpty(contentType.CharSet))
        {
            contentType.CharSet = null;
        }
        return base.SendAsync(request, ct);
    }
}

This works but every consumer hitting a strict server has to discover and re-implement it.

Environment

  • ModelContextProtocol 1.2.0
  • .NET 8.0
  • Windows 11 (also reproduces on Linux)

Additional context

This likely originates from System.Net.Http.Json / JsonContent.Create, which defaults to application/json; charset=utf-8. The fix is either to set Content.Headers.ContentType.CharSet = null after constructing the JsonContent, or to construct the StringContent/ByteArrayContent manually with the bare media type.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions