← Back to Blog

My Coding Agents Were Running 646 MCP Server Processes

Every agent session starts its own copy of each stdio MCP server in its config. I moved three of mine to one shared copy per laptop and dropped a fourth. Two needed an auth fix before sharing was safe.

Skip to the setup →

My laptop was crawling. Load average 37 on 12 cores, and 30.1 GB of 31.7 GB swap in use. I expected Chrome, and Chrome was bad at 22 GB. The surprise was 646 MCP server processes holding 20 GB between them.

I had 57 agent sessions open across Claude Code, Codex and Grok. MCP servers are what give an agent tools like Slack, Jira or Reddit. Each session starts every server configured as a command as its own child process and talks to it over standard input and output. That is a stdio server, and the session keeps it whether it ever calls a tool or not.

$ uptime
11:35  up 8 days, 15 mins, 3 users, load averages: 37.09 29.59 17.88
$ sysctl vm.swapusage
vm.swapusage: total = 31744.00M  used = 30143.19M  free = 1600.81M  (encrypted)

atlassian    launcher  n=  66    1458 MB  (22 MB each)
atlassian    server    n=  64    4725 MB  (74 MB each)
aws-pricing  launcher  n=  61    1978 MB  (32 MB each)
aws-pricing  server    n=  61    2757 MB  (45 MB each)
reddit       launcher  n=  65    1732 MB  (27 MB each)
reddit       server    n= 131    3411 MB  (26 MB each)
slack        launcher  n=  61    1839 MB  (30 MB each)
slack        server    n= 122    1588 MB  (13 MB each)

Memory is top's MEM column, which counts compressed pages. The 646 also includes 15 processes (536 MB) from one plugin's bundled server.

Want the same numbers for your machine? Hand your agent this line. It counts what your sessions are running, says which servers can be shared, and changes no config or running server.

Clone https://github.com/karanb192/mcp-sprawl-audit and follow its SKILL.md to audit my MCP servers.

Where the 646 came from

A stdio server runs once per session, so 57 sessions means at least 57 copies. Slack counts twice, because its npm package runs a Node wrapper around a Go binary. On top of that, npx or uvx leaves a launcher process sitting above each server for the life of the session. 253 of the 646 processes were launchers doing nothing, worth 7 GB.

Reddit had 131 server processes because I had configured my own reddit-mcp-buddy twice, once from npm and once from a local build. That one was on me.

Others have hit the same wall. A request to share MCP processes across Claude Code sessions was closed as a duplicate. A report of 15 sessions with 34 servers panicking a 64 GB Mac was closed as not planned. The latest one was closed as a duplicate on October 11. So the fix is on our side for now.

Before, each Claude Code, Codex and Grok session runs its own stdio copies of four MCP servers, 631 processes and 19.5 GB. After, all sessions connect over HTTP to three shared localhost services, 6 processes and 301 MB.
Schematic. Counts measured with top at 57 sessions before and 55 after. aws-pricing was dropped, not shared.

One copy per laptop

If a server can serve Streamable HTTP, and three of my four could, run it once on 127.0.0.1 and point every agent at the URL. Check its docs for a transport or port flag. launchd starts it at login and restarts it if it dies.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>local.mcp.reddit</string>
  <key>ProgramArguments</key>
  <array>
    <string>/opt/homebrew/bin/npx</string>
    <string>-y</string>
    <string>reddit-mcp-buddy@1.1.14</string>
  </array>
  <key>EnvironmentVariables</key>
  <dict>
    <key>REDDIT_BUDDY_HTTP</key><string>true</string>
    <key>REDDIT_BUDDY_PORT</key><string>18761</string>
    <key>PATH</key><string>/opt/homebrew/bin:/usr/bin:/bin</string>
  </dict>
  <key>StandardErrorPath</key><string>/tmp/mcp-reddit.log</string>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
</dict>
</plist>

Save it as ~/Library/LaunchAgents/local.mcp.reddit.plist. launchd does not search your PATH for the program, so give its full path from which npx. The PATH entry must include the folder that holds node, from dirname $(which node), because npx starts node. On Intel Macs or with nvm that is not /opt/homebrew/bin. Load it and check the port. The first start downloads the package, so the check waits ten seconds and shows the log if the port is still closed.

launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/local.mcp.reddit.plist
sleep 10; lsof -nP -iTCP:18761 -sTCP:LISTEN || tail /tmp/mcp-reddit.log

This server needs no secrets. For one that does, keep tokens out of the plist. My version starts each server through a small script that reads them from a file, so no token sits in the plist or on a command line. Then the clients, for Claude Code and Codex.

claude mcp remove -s user reddit-mcp-buddy
claude mcp add --transport http -s user \
  reddit-mcp-buddy http://127.0.0.1:18761/mcp

In ~/.codex/config.toml, replace the server's command and args with its URL.

[mcp_servers.reddit-mcp-buddy]
url = "http://127.0.0.1:18761/mcp"

Remove the old stdio entry in the scope it lives in, or it can win over the new one. Then restart your sessions. Each one keeps its stdio copies until it exits.

Grok picked the servers up from Claude Code's config with no change. Pin the version, too. A service that restarts on its own should never pull @latest.

The gotchas

A stdio server has no network surface. A port on localhost does. Any process on the laptop can call it, and the MCP spec warns that a web page can reach it through DNS rebinding. So before sharing any server that holds a token, I sent it real requests without credentials.

Slack checks its key on tool calls only

slack-mcp-server 1.3.0 takes SLACK_MCP_API_KEY as a bearer key. Listing tools worked with no key, a wrong key and a forged Host header. The key only guards real tool calls, which the source confirms, and it accepts every call when no key is set.

slack call with [none]: REFUSED
slack call with [Bearer ${SLACK_MCP_API_KEY}]: REFUSED
slack call with [Bearer <real key>]: ALLOWED

The tool list holds no messages, so that is enough. Without the key, my DMs would sit one request away from anything that found the port.

Atlassian refuses its own token over HTTP

mcp-atlassian 0.23.1 answered my first HTTP request with a 401, with the API token right there in its environment.

{"error": "Authentication required: no Atlassian credentials were provided."}

The 401 is deliberate. Over HTTP it ignores the stored token unless you set ALLOW_GLOBAL_CRED_FALLBACK, which would let any local caller act as you in Jira. Leave it off. Each agent sends Authorization: Basic base64(email:token) instead, and a call without it still gets 401.

Reddit checks Host and Origin

My copy of reddit-mcp-buddy runs without Reddit credentials, so it can only read public Reddit. It binds to 127.0.0.1 and answers a forged Host header or an unknown browser Origin with a 403. Those are the checks that stop the DNS rebinding the spec warns about.

reddit initialize with [no extra header]: HTTP 200
reddit initialize with [Origin: https://evil.example]: HTTP 403
reddit initialize with [Host: evil.example:18761]: HTTP 403

It holds no token, so those checks are enough for this one.

Old terminals don't know new keys

My first end-to-end test failed with a 401 too. That shell had started before I added the keys, so the agent sent the placeholder text instead of the value. Open a new tab after adding one.

Some servers only speak stdio

The AWS pricing server calls mcp.run() with no transport option, so there is nothing to share. I had used it in 2 sessions in 30 days, so it left my global config. It can come back per project.

Six processes for three servers

At 13:08 IST, with 55 sessions open, the three services used 6 processes and 301 MB. That morning those servers, plus the AWS one I dropped, had used 19.5 GB across 57 sessions. Real tool calls from fresh Claude Code 2.1.300, Codex 0.162.1 and Grok 1.0.52 sessions all came back with data.

Swap went to zero as well. I also quit Chrome and rebooted, so I don't credit that drop to this change.

One leak is still open. A plugin I use bundles its own MCP server, and 36 copies of it are running as I write this, 1.5 GB in all. Its server ships inside the plugin, so sharing it means changing how the plugin starts it. That is next.

I ran the audit on this laptop again on October 18. Nothing is left to share in my own config. What remains sits inside plugins and apps that start their own servers.

Agent sessions running: 32 claude, 30 codex, 10 grok.
Local MCP server copies: 108, using 141 processes and 2007 MB.
Idle npx/uvx launchers among them: 0 processes, 0 MB.
Saving you can act on: about 0 MB. Locked inside plugins and apps: about 1947 MB.

To see your own numbers, hand your agent the line near the top of this post. It reads and reports, and changes no config or running server.


Sources and code