The Error
When launching an MCP server in C# or connecting an AI client (such as Claude Desktop, Cursor, or a Semantic Kernel client), you encounter one of these common transport errors:
Error Scenario A: Stdio JSON Parsing Failure
System.IO.IOException: The pipe is being closed.
at ModelContextProtocol.Transport.StdioTransport.ReadNextMessageAsync()
Newtonsoft.Json.JsonReaderException: Unexpected character encountered while parsing value: [
Line 1, position 1. Text: [14:22:01 info] Starting DotNetStudio MCP server...
Error Scenario B: HTTP Connection Refused
System.Net.Http.HttpRequestException: Connection refused (localhost:5050)
at System.Net.Http.HttpConnectionPool.ConnectAsync()
at ModelContextProtocol.Http.SseClientTransport.ConnectAsync()
Root Cause Analysis
Cause 1: Logging Corrupting the Stdio Stream
In the Model Context Protocol stdio specification, standard input (stdin) and standard output (stdout) are strictly reserved for JSON-RPC messages. If any component in your application calls Console.WriteLine("Starting...") or uses standard .NET console logging, that formatted string is emitted into stdout. The client expects a JSON-RPC object ({"jsonrpc":"2.0",...}) but receives plain text, crashing the parser.
Cause 2: Localhost Binding Inside Containers
When running an ASP.NET Core MCP server in Docker, binding to localhost restricts traffic to inside the container. Host machines and AI agents connecting to http://localhost:5050 receive Connection Refused.
Cause 3: Reverse Proxy Idle Timeouts
Remote MCP servers use Server-Sent Events (SSE) or Streamable HTTP. Many reverse proxies (NGINX, Azure App Service, Cloudflare) default to a 60-second idle timeout. If a complex tool runs for 75 seconds without sending an event, the proxy terminates the TCP connection.
The Verified Fix
Fix 1: Redirect All Logging to Stderr (Stdio Transport)
In your MCP console application Program.cs, configure your logger to write exclusively to stderr (standard error), leaving stdout pure for JSON-RPC:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
var services = new ServiceCollection();
// Configure console logger to output exclusively to STDERR
services.AddLogging(builder =>
{
builder.AddConsole(options =>
{
// Direct all log levels to stderr
options.LogToStandardErrorThreshold = LogLevel.Trace;
});
});
var serviceProvider = services.BuildServiceProvider();
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
// Safe to log! Sent to stderr, keeping stdout clean for MCP JSON-RPC
logger.LogInformation("MCP Server initialized cleanly on stdio.");
var server = new StdioMcpServer(options =>
{
options.ServerInfo = new Implementation { Name = "CleanStdioServer", Version = "1.0.0" };
});
await server.RunAsync();
Fix 2: Configure Universal IP Binding (HTTP Transport)
In your ASP.NET Core Program.cs, bind to http://+:5050 or http://0.0.0.0:5050 to accept connections across all network interfaces:
using ModelContextProtocol.AspNetCore;
var builder = WebApplication.CreateBuilder(args);
// Ensure the server listens on all network interfaces
builder.WebHost.UseUrls("http://0.0.0.0:5050");
builder.Services.AddMcpServer(options =>
{
options.ServerInfo = new Implementation { Name = "RemoteMcpServer", Version = "1.0.0" };
});
var app = builder.Build();
app.MapMcpServer("/mcp");
app.Run();
In your Dockerfile, ensure ASPNETCORE_URLS is declared:
ENV ASPNETCORE_URLS=http://+:5050
EXPOSE 5050
Fix 3: Disable Reverse Proxy Buffering for Streamable HTTP
If your server runs behind an NGINX reverse proxy, disable response buffering and extend read timeouts:
location /mcp {
proxy_pass http://mcp_backend:5050;
proxy_http_version 1.1;
proxy_set_header Connection '';
# CRITICAL: Disable proxy buffering for real-time streaming
proxy_buffering off;
proxy_cache off;
# Prevent 60-second timeouts during long tool runs
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
Fix 4: Resilient C# Client Connection with Polly
When connecting to remote MCP servers in .NET, wrap the connection handshake in a Polly resilience pipeline to handle transient connection drops:
using ModelContextProtocol.Client;
using ModelContextProtocol.Http;
using Polly;
using Polly.Retry;
var retryPipeline = new ResiliencePipelineBuilder()
.AddRetry(new RetryStrategyOptions
{
MaxRetryAttempts = 3,
Delay = TimeSpan.FromSeconds(2),
BackoffType = DelayBackoffType.Exponential,
ShouldHandle = new PredicateBuilder().Handle<HttpRequestException>()
})
.Build();
var httpClient = new HttpClient { Timeout = TimeSpan.FromMinutes(5) };
var transport = new SseClientTransport(httpClient, new Uri("http://localhost:5050/mcp"));
var client = new McpClient(transport);
await retryPipeline.ExecuteAsync(async ct =>
{
await client.ConnectAsync(ct);
});
Console.WriteLine("Connected to MCP server with automated resilience.");