Skip to content

Avoid string intermediates in MCP transport read path#1319

Draft
Copilot wants to merge 2 commits intomainfrom
copilot/fix-string-encoding-issue
Draft

Avoid string intermediates in MCP transport read path#1319
Copilot wants to merge 2 commits intomainfrom
copilot/fix-string-encoding-issue

Conversation

Copy link
Contributor

Copilot AI commented Feb 19, 2026

The read side of StreamServerTransport and StreamClientSessionTransport was decoding bytes → string via TextReader.ReadLineAsync(), then re-encoding back to UTF-8 internally during JsonSerializer.Deserialize(string, ...). This round-trip was unnecessary since System.Text.Json can deserialize directly from bytes.

Changes

  • Replace TextReader with PipeReader on the input side of both transports. PipeReader.Create(stream) works across all targets; the System.IO.Pipelines dependency was already present.
  • Deserialize directly from bytes — single-segment buffers use JsonSerializer.Deserialize(ReadOnlySpan<byte>, typeInfo); multi-segment use new Utf8JsonReader(ReadOnlySequence<byte>, ...) + JsonSerializer.Deserialize(ref reader, typeInfo).
  • String conversion only when neededGetString() (for trace logging) only allocates when Logger.IsEnabled(LogLevel.Trace). For single-segment sequences it calls Encoding.UTF8.GetString(span) directly; multi-segment falls back to ToArray().
  • CRLF trimming without slicingEndsWithCarriageReturn() checks the last byte of the last segment directly rather than creating an intermediate slice.
  • Remove encoding constructor parameter from StreamClientSessionTransport — it was only used to configure the now-removed TextReader wrapper. Both call sites already passed null.
  • Add Encoding.GetString(ReadOnlySpan<byte>) polyfill to EncodingExtensions.cs for netstandard2.0 compatibility, matching the existing unsafe pattern used by GetByteCount and GetBytes polyfills.
Original prompt

This section details on the original issue you should resolve

<issue_title>More string intermediates in read side if MCP transport</issue_title>
<issue_description>Similar issue to #1259 which was fixed in #1274.

Read / deserialization is still encoding to a string. We should try to keep as utf8 bytes and deserialize from those:

var line = await _inputReader.ReadLineAsync(shutdownToken).ConfigureAwait(false);
if (string.IsNullOrWhiteSpace(line))
{
if (line is null)
{
LogTransportEndOfStream(Name);
break;
}
continue;
}
LogTransportReceivedMessageSensitive(Name, line);
try
{
if (JsonSerializer.Deserialize(line, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonRpcMessage))) is JsonRpcMessage message)

if (await _serverOutput.ReadLineAsync(cancellationToken).ConfigureAwait(false) is not string line)
{
LogTransportEndOfStream(Name);
break;
}
if (string.IsNullOrWhiteSpace(line))
{
continue;
}
LogTransportReceivedMessageSensitive(Name, line);
await ProcessMessageAsync(line, cancellationToken).ConfigureAwait(false);

private async Task ProcessMessageAsync(string line, CancellationToken cancellationToken)
{
try
{
var message = (JsonRpcMessage?)JsonSerializer.Deserialize(line.AsSpan().Trim(), McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonRpcMessage)));
if (message != null)

</issue_description>

<agent_instructions>Try to use built in BCL types like PipeReader + ReadOnlySpan callback + System.Text.Json. Avoid adding ifdefs. If a dependency is missing, add it. If we need to polyfill, add the polyfill source for downlevel.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Co-authored-by: ericstj <8918108+ericstj@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix more string intermediates in MCP transport Avoid string intermediates in MCP transport read path Feb 19, 2026
Copilot AI requested a review from ericstj February 19, 2026 23:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

More string intermediates in read side if MCP transport

2 participants

Comments