Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 2 additions & 2 deletions src/ModelContextProtocol.Core/Client/McpHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace ModelContextProtocol.Client;

internal class McpHttpClient(HttpClient httpClient)
{
internal static readonly MediaTypeHeaderValue s_applicationJsonContentType = new("application/json") { CharSet = "utf-8" };
internal static readonly MediaTypeHeaderValue s_applicationJsonContentType = new("application/json");

internal virtual async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, JsonRpcMessage? message, CancellationToken cancellationToken)
{
Expand All @@ -32,7 +32,7 @@ internal virtual async Task<HttpResponseMessage> SendAsync(HttpRequestMessage re
}

#if NET
return JsonContent.Create(message, McpJsonUtilities.JsonContext.Default.JsonRpcMessage);
return JsonContent.Create(message, McpJsonUtilities.JsonContext.Default.JsonRpcMessage, s_applicationJsonContentType);
#else
var bytes = JsonSerializer.SerializeToUtf8Bytes(message, McpJsonUtilities.JsonContext.Default.JsonRpcMessage);
var content = new ByteArrayContent(bytes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,57 @@ public async Task DisposeAsync_Should_Dispose_Resources()
Assert.False(transportBase.IsConnected);
}

// Strict server mock used in Content-Type tests below.
// Returns 200 only for bare "application/json", otherwise 415.
private static Func<HttpRequestMessage, Task<HttpResponseMessage>> StrictJsonContentTypeHandler =>
(request) =>
{
if (request.Method == HttpMethod.Post)
{
var contentType = request.Content?.Headers.ContentType;
if (contentType?.CharSet is not null)
{
return Task.FromResult(new HttpResponseMessage
{
StatusCode = HttpStatusCode.UnsupportedMediaType,
Content = new StringContent("Content-Type must be 'application/json'"),
});
}

return Task.FromResult(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(
"""{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":"2025-03-26","capabilities":{},"serverInfo":{"name":"Test","version":"1.0"}}}""",
Encoding.UTF8,
"application/json"),
});
}

throw new IOException("Abort");
};

[Fact]
public async Task SendMessageAsync_StrictServer_Returns200_WhenContentTypeIsApplicationJson()
{
// Regression test for https://github.com/modelcontextprotocol/csharp-sdk/issues/1527
// SDK must send bare "application/json" — no charset parameter.
var options = new HttpClientTransportOptions
{
Endpoint = new Uri("http://localhost:8080"),
TransportMode = HttpTransportMode.StreamableHttp,
};

using var mockHttpHandler = new MockHttpHandler();
using var httpClient = new HttpClient(mockHttpHandler);
await using var transport = new HttpClientTransport(options, httpClient, LoggerFactory);
mockHttpHandler.RequestHandler = StrictJsonContentTypeHandler;

// Succeeds only if the SDK sends Content-Type: application/json (no charset)
await using var session = await transport.ConnectAsync(TestContext.Current.CancellationToken);
Assert.NotNull(session);
}

[Fact]
public async Task StreamableHttp_InitialGetSseConnection_DoesNotCountAgainstMaxReconnectionAttempts()
{
Expand Down
Loading