Classify before debugging. Local stdio servers are child processes started by the client—no network involved. Remote HTTP/SSE servers meet proxies, idle timeouts, certificates, and corporate gateways. The causes barely overlap, so mixing them produces the classic “change one variable, try again” loop.
Agent usage: Claude Code, Codex CLI. Multiple upstreams: 9Router.
Two transports, two diseases
| Local stdio | Remote HTTP / SSE | |
|---|---|---|
| How it runs | Client spawns a command over stdin/stdout | Client calls a URL; server streams back |
| Typical failure | Command not found, missing deps, permissions, wrong path | Timeouts, handshake with no data, sessions dropping |
| Proxy relevant? | Essentially no | Very |
| Look at | Client logs for spawn errors | Whether egress works and data arrives incrementally |
Where the symptom points
| Symptom | Most likely layer | First move |
|---|---|---|
| “command not found” at startup | Local stdio | Fix path / install the server binary |
| Connects, then drops every few minutes | Remote idle / read timeout | Raise read timeout, switch to Streamable HTTP |
| Handshake OK, tool list empty | Buffering, not routing | Disable proxy buffering on the endpoint |
| Works on LAN, fails over proxy | Egress / certificates | Enable Tun or set HTTPS_PROXY in the launch shell |
Remote debugging order
- Egress. For overseas endpoints, make sure the client process can get out: enable Tun or export
HTTPS_PROXYin the launching shell, then restart the terminal or client. - Loopback. A locally hosted MCP server reached on
127.0.0.1while proxy variables capture loopback looks exactly like a dead service. Add it toNO_PROXY:
export HTTPS_PROXY="http://127.0.0.1:7890"
export NO_PROXY="127.0.0.1,localhost"
export ALL_PROXY="http://127.0.0.1:7890"
- Handshake but no data. Connection established, tool list empty—almost always buffering somewhere, not a blocked route.
- Regular disconnects. Dies after a few minutes, at suspiciously even intervals? Some layer’s read or idle timeout is doing it.
- Certificates and gateways. TLS middleboxes make clients refuse the connection; either get it allowlisted or verify on another network.
Self-hosting: the config people miss
If the remote MCP server is yours on a VPS behind Nginx, “connects but returns nothing” is nearly always buffering: the reverse proxy batches the response while SSE needs to flush continuously. Disable buffering and compression on that location, keep HTTP/1.1 alive, and raise the read timeout:
location /mcp {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffering off;
proxy_cache off;
gzip off;
proxy_read_timeout 86400s;
}
The lower-maintenance path is changing transport. Streamable HTTP splits the long connection into independent requests, so a network hiccup fails one call instead of the whole session, and the reverse-proxy config returns to normal. Prefer it for new deployments; keep SSE for local or LAN use.
Also, do not leave MCP servers naked on the public internet. They frequently hold filesystem or internal-API capabilities—require TLS and authentication, and scope tools to read-only where you can.
Flaky networks are the norm
Sleep, hotspot switching, node changes—every one of them interrupts a long-lived connection. The right expectation is “reconnect,” not “re-audit the config.” If you use remote MCP on the move, switching to Streamable HTTP pays off more than tuning timeouts. See also offline after sleep and proxy on but sites will not open.
Minimal repro
- Disable every MCP server but one.
- Curl the endpoint and watch whether events arrive incrementally or all at once at the end—the latter is buffering.
- Confirm exactly one proxy style is active.
- Re-enable servers one at a time, running a minimal request after each.
Client: download center. Reading logs: log troubleshooting.