…encoding
On modern .NET, `McpHttpClient.CreatePostBodyContent` built the request body
with `JsonContent.Create(...)`. `JsonContent` serializes lazily and cannot
report a length, so `HttpClient` had no `Content-Length` to send and fell back
to `Transfer-Encoding: chunked`. Hosts that reject chunked request bodies then
fail the POST: the local Azure Functions Python worker answers with
`400 "'Transfer-Encoding: chunked' header can not be used when content object
is not specified"`, so an MCP server that works when deployed does not work
when run locally.
The netstandard2.0 branch already buffered the payload into a
`ByteArrayContent`, which does set `Content-Length`, so the wire format also
depended on the target framework. Use the buffered path on every target
framework: serialize to UTF-8 bytes and wrap them in a `ByteArrayContent`.
That removes the TFM split and makes the encoding uniform, at the cost of
holding one serialized message in memory, which the netstandard path already
did.
Add a regression test that drives a real Streamable HTTP POST through
`HttpClientTransport` and asserts, from the message handler, that the request
declares a `Content-Length` matching the bytes actually sent and is not sent
with chunked encoding. The values are captured in the handler because
`McpHttpClient` disposes the content once `SendAsync` returns.
Fixes modelcontextprotocol#932
Summary
Buffer the Streamable HTTP POST body so it goes out with a Content-Length instead of Transfer-Encoding: chunked.
McpHttpClient.CreatePostBodyContent built the body with JsonContent.Create(...) on modern .NET. JsonContent serializes lazily and cannot report a length, so HttpClient had no length to send and fell back to chunked encoding. Hosts that reject chunked request bodies then fail the POST — the local Azure Functions Python worker answers 400 "'Transfer-Encoding: chunked' header can not be used when content object is not specified" — which is the failure tracked in #816 and consolidated as #932.
The netstandard2.0 branch already buffered the payload into a ByteArrayContent, which does set Content-Length, so the wire format also depended on the target framework. This PR uses the buffered path on every target framework, which removes the TFM split entirely.
Changes
Tradeoff: one serialized message is held in memory per request instead of being streamed. The netstandard2.0 path already paid that cost, and request bodies here are single JSON-RPC messages.
Testing
Build — all target frameworks, warnings as errors:
dotnet build --configuration Debug 0 个警告 / 0 个错误 (0 warnings, 0 errors)New test, red on unfixed source and green with the fix. With McpHttpClient.cs reverted to upstream/main but the test kept:
With the fix:
Observed request properties from the message handler, before and after:
The values are captured inside the handler because McpHttpClient.SendAsync disposes the content once it returns; reading them afterwards throws ObjectDisposedException.
Full test project on net10.0:
The 5 skips are the pre-existing Docker/external-service tests.
Other target frameworks (environment limit, please note). Only the .NET 10 runtime is installed here, so dotnet test cannot launch the net8.0/net9.0 test hosts. I ran those TFMs with the xUnit v3 in-process runner instead and both pass 17/17 for HttpClientTransportTests:
The net9.0 run executed on the .NET 10 runtime rather than a real .NET 9 runtime, so treat genuine net9.0 execution as covered by CI. netstandard2.0 compiles clean in the build above but has no test host.
Not verified: no end-to-end run against a locally hosted Azure Functions MCP server; the wire-format property is asserted at the request level instead.
Notes on the issue state
#932 still carries needs confirmation. The change follows the root cause and the preferred default described in the maintainer comment on the issue — send a Content-Length body by default rather than making it configurable. If the outcome should instead be opt-in via a HttpClientTransportOptions flag, say so and I will rework it.
Fixes #932
Note
This pull request description was AI-generated.