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 (0)