In July I wrote a 56-line shell script called mcp-optional whose entire job was to remove two MCP servers from my config.
Not fix them. Remove them.
Both were stdio servers, so each one spawns its own Node process per session. With around 13 Claude Code sessions open on a 16GB M4, that was roughly 1.4GB of duplicated RAM, which meant swap thrash, which meant a hot laptop. Disabling them by default and re-enabling on demand fixed it.
I filed that under "memory problem, solved" and stopped thinking about it.
This morning, chasing something unrelated, I started reading the MCP connection logs Claude Code leaves on disk. It turns out one of those two servers wasn't just the RAM tax. It's also the slowest thing I connect to, by a wide margin β and the reason isn't its code at all.
Here's what 35 days of logs actually say.
Claude Code writes a JSONL log per MCP server, per session, under ~/Library/Caches/claude-cli-nodejs/<project>/mcp-logs-<server>/. Two lines matter:
Starting connection with timeout of 30000ms
Connection established with capabilities: {"hasTools":true,...}
Pair them inside one file and you get a real connection latency. I did that across every project directory on this machine:
This is one developer's machine, not a lab. That's the point β it's the distribution you actually live in.
| percentile | connect time |
|---|---|
| median | 582 ms |
| p75 | 1,650 ms |
| p90 | 3,678 ms |
| p95 | 6,428 ms |
| p99 | 14,049 ms |
The median looks great. Under six tenths of a second β nobody would ever file a bug.
But 39% of connections take longer than a second, and the p99 is fourteen seconds. Against a client timeout of 30,000ms, the tail is not a rounding error. It's most of the way to the wall.
This is the finding that reframed the whole thing for me. I split every connection by whether a token refresh happened inside that same connection window:
| n | median | p90 | |
|---|---|---|---|
| token refresh in window | 748 | 3,170 ms | 9,619 ms |
| no refresh | 26,509 | 550 ms | 3,400 ms |
5.8Γ slower. Same servers, same network, same machine. The only difference is whether the client had to go get a new access token first.
Your MCP server didn't take three seconds to start. Your MCP server took 550ms to start, and an OAuth round-trip took the other 2.6 seconds while the server sat there doing nothing.
That's why "why is this server slow" is usually the wrong question. Only 2.7% of my connections hit a refresh β but that 2.7% is where a disproportionate share of the visible pain lives, because it's the path that turns a fast connection into a slow one non-deterministically. You can't reproduce it on demand, so you blame the server.
| transport | n | median | p90 |
|---|---|---|---|
| HTTP | 19,894 | 676 ms | 3,395 ms |
| stdio | 7,363 | 222 ms | 4,693 ms |
stdio is 3Γ faster at the median β no surprise, there's no network. But look at p90: stdio is worse in the tail. Spawning a process has a floor of roughly nothing and a ceiling of "npm decided to do something." An HTTP server that's already running is slower on average and far more predictable.
If you're choosing a transport, that trade is the actual decision. Not "stdio is faster."
Per-connection numbers hide the thing that actually costs you time, which is fan-out. Per session on this machine:
That p90 is the number that made me stop and re-read my own script. A third of the time I open a session, I'm waiting a meaningful fraction of a minute before the first token β not because any single server is broken, but because eight of them each drew from that fat tail and nothing amortizes.
Worst offender by median, unsurprisingly: one of the two servers I'd already disabled for RAM reasons, at 2,261 ms median and 10,413 ms at p90. I removed it in July for the wrong reason and got the right outcome.
Of 38,876 connection attempts, 11,556 never logged an established line at all β 29.7%.
Some of that is log rotation cutting a file mid-handshake, so treat it as an upper bound rather than a failure rate. But it isn't evenly spread: a single remote connector accounts for 7,422 of them on its own. That's not noise, that's one integration failing over and over while everything upstream stays quiet about it.
My first pass had a max connect time of 119 seconds and a p99 of 14.5s, and I nearly wrote a paragraph about it.
Then I re-read the client's own log line: timeout of 30000ms. A connection cannot establish at 119 seconds if the client gives up at 30. Those samples weren't slow connections β they were a closed laptop. The wall clock kept running through sleep; the connection didn't.
So I capped every measurement at the client's declared 30s timeout and threw the rest out. That removed 24 of 27,281 samples β 0.09%. Every number above is post-cap.
I mention it because the uncapped version would have been a better story and a false one. If you run this on your own machine, cap it.
mcp-optional does β claude mcp remove by default, claude mcp add when a task needs it. It's the highest-leverage 56 lines I have written this year, and I wrote it for the wrong reason.
The reason I care about any of this: at Achiya Automation I run agent tooling against real client infrastructure all day, and a 35-second session start that lands on one session in ten β with a third of them over 10 seconds β is a tax I pay dozens of times a day without ever seeing it on a bill.
If you've got MCP servers configured right now β how many, and when did you last check whether you still use all of them?
I'd genuinely like to know whether median-8 is normal or whether I'm the outlier. And if you've measured a refresh-vs-no-refresh split on a different client, I want to see that number, because I only have one machine to look at.