DEV Community

Cover image for Why Your MCP Setup Keeps Timing Out in 60 Seconds (And How I Fixed It on Windows)
Jonathan Melton
Jonathan Melton

Posted on

Why Your MCP Setup Keeps Timing Out in 60 Seconds (And How I Fixed It on Windows)

Every developer hits this wall: add more than 8 MCP servers to Claude Desktop (or Cursor, or VSCode) → it spins for exactly 60 seconds → red X, "timed out." It happens on every machine with limited resources. My i5-7300HQ laptop? Poster child for the problem.

Tutorials skip it. AWS/Finch articles ignore Windows entirely. Docker's own docs don't cover it. But the problem is real, and I spent weeks diagnosing every failure mode until I fixed them all.

I documented 6 Windows-specific MCP failure modes that nobody else has written about, then built a single Docker gateway that loads 150+ tools without hitting the timeout. Every single time.


The 6 Real Killers

1. BOM in claude_desktop_config.json

What you see: You edit the config in PowerShell, save it, restart Claude Desktop. Nothing changes. No error message. Claude just ignores your config entirely.

Why it happens: PowerShell's Out-File -Encoding UTF8 adds an invisible 3-byte BOM (byte order mark) at the start of the file. Claude's JSON parser chokes on those bytes without telling you.

How to check:

$bytes = [System.IO.File]::ReadAllBytes("$env:APPDATA\Claude\claude_desktop_config.json")
if ($bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) {
    Write-Host "BOM detected — this is your problem"
}
Enter fullscreen mode Exit fullscreen mode

The fix: Always write config with BOM-free UTF-8:

[System.IO.File]::WriteAllText(
    "$env:APPDATA\Claude\claude_desktop_config.json",
    $content,
    (New-Object System.Text.UTF8Encoding($false))  # $false = no BOM
)
Enter fullscreen mode Exit fullscreen mode

2. %USERPROFILE% Doesn't Expand

What you see: Your config references %USERPROFILE%\some\path and the server never starts. No error — just silence.

Why it happens: Claude Desktop doesn't expand Windows environment variables in the JSON config. It reads %USERPROFILE% as a literal string and tries to find a directory with percent signs in the name.

The fix: Hardcode absolute paths everywhere. No exceptions.

"command": "C:\\Users\\puddi\\AppData\\Local\\Programs\\node.exe"
Enter fullscreen mode Exit fullscreen mode

Not:

"command": "%USERPROFILE%\\AppData\\Local\\Programs\\node.exe"
Enter fullscreen mode Exit fullscreen mode

3. docker.exe Needs the Full Absolute Path

What you see: Config looks correct, Docker is running, but the MCP server won't start.

Why it happens: Claude Desktop launches child processes without inheriting the system PATH. So "command": "docker" or even "command": "docker.exe" fails — it can't find the binary.

The fix: Use the full path to docker.exe:

"command": "C:\\Program Files\\Docker\\Docker\\resources\\bin\\docker.exe"
Enter fullscreen mode Exit fullscreen mode

Every time. Don't trust PATH.


4. Docker Named Pipe vs Unix Socket Mismatch

What you see: Docker commands work fine in your terminal, but MCP servers that need Docker access inside containers get connection errors.

Why it happens: Windows Docker Desktop runs through a named pipe (//./pipe/docker_engine), but containers expect the Unix socket at /var/run/docker.sock. Named pipes are flaky when passed through to containers via WSL2.

The fix: Mount the Unix socket directly. Docker Desktop's WSL2 backend exposes it:

"-v", "/var/run/docker.sock:/var/run/docker.sock"
Enter fullscreen mode Exit fullscreen mode

Don't use the Windows named pipe. The Unix socket is stable through WSL2 integration.


5. Cold-Start Bloat Kills the 60-Second Budget

What you see: A server works fine after the first load, but on a fresh start (cold boot, after a Docker prune, or first install), it times out.

Why it happens: Some MCP servers download heavy dependencies on first run. Puppeteer downloads Chromium (~180MB). Python servers pull packages. Node servers run npm install. All of this happens inside the 60-second initialization window that Claude Desktop enforces. On slower hardware, you're dead.

The fix: Pre-bake dependencies into Docker images. If a server needs Chromium, include it in the Dockerfile — don't download it at runtime. For the gateway approach, this is handled by using official Docker MCP images that ship ready to run.

I removed Puppeteer from my registry entirely after it blew the timeout three times in a row. If you need browser automation, use a pre-built image like browserless/chrome and connect to it remotely.


6. Registry Bloat (The One That Started Everything)

What you see: You have 8 MCP servers and everything works. You add a 9th. Timeout.

Why it happens: Claude Desktop initializes all registered servers in parallel on startup. Each server needs CPU time, memory allocation, and potentially network connections. On an i5-7300HQ with 16GB RAM, 8 servers is the practical ceiling before the 60-second timeout hits.

This isn't a bug — it's a resource constraint. Every server you add steals initialization time from every other server. The relationship is roughly linear until you hit the wall, then it's catastrophic.

The fix: This is the one that made me build FusionAL. Instead of registering 8 separate servers, you register one gateway that loads tools from a catalog. One initialization, one process, 150+ tools available.


The Fix: One Lean Gateway

FusionAL runs a single docker/mcp-gateway container that manages everything through a lightweight registry.yaml + tool catalogs. Cold start drops from "never finishes" to 12-18 seconds.

Before (8 registry entries, constant timeouts):

# registry.yaml with 8+ entries
servers:
  github: ...
  filesystem: ...
  redis: ...
  postgres: ...
  puppeteer: ...    # ← downloads Chrome, blows timeout
  slack: ...
  notion: ...
  custom-api: ...
  analytics: ...    # ← 9th server, guaranteed timeout
Enter fullscreen mode Exit fullscreen mode

After (1 registry entry, 150+ tools):

{
  "mcpServers": {
    "fusional-gateway": {
      "command": "C:\\Program Files\\Docker\\Docker\\resources\\bin\\docker.exe",
      "args": [
        "run", "-i", "--rm",
        "-v", "/var/run/docker.sock:/var/run/docker.sock",
        "-v", "C:/Users/puddi/.docker/mcp:/mcp",
        "docker/mcp-gateway",
        "--catalog=/mcp/catalogs/docker-mcp.yaml",
        "--catalog=/mcp/catalogs/custom.yaml",
        "--registry=/mcp/registry.yaml",
        "--config=/mcp/config.yaml",
        "--transport=stdio"
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

One process. One initialization. All 150+ tools available through the gateway.

Writing this config safely (no BOM):

$config = Get-Content .\my-config.json -Raw
[System.IO.File]::WriteAllText(
    "$env:APPDATA\Claude\claude_desktop_config.json",
    $config,
    (New-Object System.Text.UTF8Encoding($false))
)
Enter fullscreen mode Exit fullscreen mode

Quick Reference: All 6 Fixes

Failure Mode Symptom Fix
BOM encoding Config silently ignored UTF8Encoding($false)
Env vars in paths Server won't start Hardcode absolute paths
docker.exe not found Silent failure Full absolute path to binary
Named pipe mismatch Container can't reach Docker Mount /var/run/docker.sock
Cold-start downloads Timeout on first run Pre-bake deps in Docker images
Registry bloat (>8 servers) 60-second timeout Single gateway, catalog-based loading

What I'm Building on Top of This

The gateway solves the reliability problem. But managing a fleet of MCP servers — knowing which ones are running, which ones crashed, queuing new builds — that's the next layer.

I'm building FusionAL, an open-source MCP operations gateway that wraps all of this into one Docker command. It handles the 6 failure modes automatically and gives you a clean interface for managing your MCP infrastructure.

If you've hit other Windows-specific MCP issues I didn't cover here, drop them in the comments. I'm actively documenting every edge case I find.


Find me on GitHub or X (@2EfinAwesome).

Top comments (2)

Collapse
 
harjjotsinghh profile image
Harjot Singh

The MCP 60s timeout is one of those papercuts that quietly makes people give up on a tool, so writing the fix down is genuinely useful - half the "MCP is flaky" complaints are really env/transport config (stdio buffering, slow cold-start, Windows path quirks) rather than the protocol. Documenting the Windows-specific gotcha will save a lot of people the same hour.

The broader lesson I keep relearning: the agent ecosystem's reliability problems are mostly plumbing, not intelligence - timeouts, transport, auth, the boring integration layer. That's the same boring-20% that eats build time, which is exactly what I work on with Moonshift (prompt to a shipped SaaS on your own GitHub+Vercel - the infra/wiring generated as defaults). Different layer, same "the unglamorous glue is where it breaks" truth. Solid debugging writeup; was it the stdio handshake or the server cold-start that blew the 60s? (Moonshift's first run's free if useful.)

Collapse
 
jonathanmeltonfusional profile image
Jonathan Melton

Both, honestly, but they compound in a nasty order on Windows.
The stdio handshake is the silent killer. On Unix, stdout flushes naturally at newline boundaries. On Windows, Python's stdout defaults to fully-buffered in non-TTY contexts — so the server's ready-signal sits in a buffer that never gets sent until the process exits or you explicitly call sys.stdout.flush(). The client sees silence and starts its 60s countdown. Cold-start is the accelerant: if your server takes 12-15 seconds to import dependencies (which is common with heavy ML/embedding stacks), you've already burned 25% of your window before the handshake even starts. Stack a buffering stall on top, and you're dead.
The fix is embarrassingly simple — PYTHONUNBUFFERED=1 in the environment, or sys.stdout = io.TextIOWrapper(sys.stdout.buffer, line_buffering=True) in the server entrypoint. But you'd never guess it from the timeout error message alone.
Your "boring glue" framing is exactly right, and it's the thing I keep coming back to: the intelligence layer gets all the attention, but 80% of MCP reliability problems are transport plumbing that nobody has systematically documented. That's essentially what I've been building toward with FusionAL — treating the glue layer as a first-class engineering problem instead of an afterthought. Different layer from Moonshift, but hitting the same root cause from the other direction.