Skip to main content

Fix MCP Server Connection Issues in C#: Stdio vs HTTP Transport Troubleshooting

Verified Oct 2026 From GitHub Issue .NET 10 ModelContextProtocol 1.0.0 ModelContextProtocol.AspNetCore 1.0.0
By Rajesh Mishra · Published: · 10 min read
In 30 Seconds

Troubleshooting guide for Model Context Protocol (MCP) server connection failures in C#. Covers root cause resolution for stdout stream pollution in stdio transport, container IP binding misconfigurations causing connection refused errors over HTTP, and SSE keep-alive timeout prevention for long-running tool calls in .NET 10.

⚠️
Error Fix Guide

Root cause analysis and verified fix. Code examples use ModelContextProtocol 1.0.0.

✓ SOLVED

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.");

⚠ Production Considerations

  • Writing unbuffered raw debug logs to stdout will crash the Claude Desktop or Cursor MCP client instantly. Always check your logging configuration before publishing local MCP servers.
  • Running MCP servers on Windows behind corporate VPNs or firewall proxies can silently drop SSE streams after 60 seconds without emitting socket close events. Always enable TCP keep-alives.

🧠 Architect’s Note

MCP transport errors usually stem from protocol violation rather than tool logic. Because stdio multiplexes protocol data over standard OS streams, strict hygiene over stdout/stderr separation is essential for stability.

AI-Friendly Summary

Summary

Troubleshooting guide for Model Context Protocol (MCP) server connection failures in C#. Covers root cause resolution for stdout stream pollution in stdio transport, container IP binding misconfigurations causing connection refused errors over HTTP, and SSE keep-alive timeout prevention for long-running tool calls in .NET 10.

Key Takeaways

  • Never write application logs to stdout when using MCP stdio transport — redirect to stderr
  • ASP.NET Core MCP containers must bind to 0.0.0.0 rather than 127.0.0.1 to avoid connection refused
  • Reverse proxies must have response buffering disabled for Streamable HTTP / SSE transports
  • Configure client-side HttpClient.Timeout and retry policies with Polly for transient network drops

Implementation Checklist

  • Redirect console logging to stderr: logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace)
  • Verify Docker container environment variable: ASPNETCORE_URLS=http://+:5050
  • Configure reverse proxy buffering: proxy_buffering off; proxy_read_timeout 300s;
  • Set client timeout: HttpClient.Timeout = TimeSpan.FromMinutes(5)
  • Add Polly retry with exponential backoff on MCP client connection attempts

Frequently Asked Questions

Why does my C# MCP server fail with a JSON parsing error when running on stdio?

In stdio transport, stdout is reserved exclusively for JSON-RPC protocol messages. If your application or a third-party library writes standard logging to stdout via Console.WriteLine or default ILogger console formatting, the non-JSON text corrupts the stream, causing client parse exceptions. The fix is redirecting all application logs to stderr.

Why do I get 'HttpRequestException: Connection refused' when connecting to an ASP.NET Core MCP server in Docker?

By default, ASP.NET Core inside a Docker container may bind to localhost (127.0.0.1) instead of 0.0.0.0 (all network interfaces). Configure the environment variable ASPNETCORE_URLS=http://+:5050 or call builder.WebHost.UseUrls('http://0.0.0.0:5050') so external clients and containers can connect.

How do I prevent HTTP / SSE timeouts during long-running MCP tool executions?

Configure periodic keep-alive pings on your MCP HTTP server and disable response buffering on intermediate reverse proxies (e.g. set proxy_buffering off; in NGINX or configure idle timeouts in Azure Container Apps). In the C# MCP client, increase HttpClient.Timeout to account for extended tool reasoning cycles.

You Might Also Enjoy

#MCP #Model Context Protocol #Error Fix #.NET AI #Hospital #C# SDK

Was this article useful?

Feedback is anonymous and helps us improve content quality.