DEV Community

Stop spawning an MCP server per agent session (and what it won't fix)

Ten parallel Claude sessions. Ten copies of the same MCP server.

Ten processes, ten sockets to the same upstream, ten holders of the same lock — because that is what stdio means. I moved every server to one shared daemon per machine bound to 127.0.0.1, and the fleet stopped fighting itself.

TL;DR: an MCP server registered as stdio is spawned per client session. Register it as an HTTP/SSE URL instead and every session shares one process. It saves memory, sockets and locks. It does not save tokens. And a naive watchdog on that shared daemon will cause worse outages than the crashes it fixes — that part cost us the most.

What does "one copy per session" actually cost?

Here is what we measured on one laptop, 2026-08-01 to 08-03, with a fleet of Claude Code sessions running against a handful of MCP servers:

server copies summed RSS
telegram ~9 ~2.7 GB
mongodb ~26 ~3.0 GB
n8n ~15 ~2.9 GB
whatsapp ~15 ~1.5 GB
launcher wrappers (npx/cmd) ~60 ~5 GB

Read those numbers honestly, because I nearly published them dishonestly. Summing RSS over-counts. Copies share code pages, so the OS is not holding that many distinct bytes and you will not get that many back by fixing this. What is exact is the copy count — and the fact that each copy is an independent client of the upstream service, with its own socket, its own lock, and its own session.

Twenty-six clients against one database is not a memory problem. It is a concurrency problem wearing a memory problem's clothes.

Measure your own machine before you believe anyone's table, including mine:

python scripts/mcp_diet_measure.py
Enter fullscreen mode Exit fullscreen mode

If it prints copies 1 everywhere, you have nothing to fix. That is also what a converted machine looks like: our hub now reports one telegram and one n8n process serving every open session.

What is the actual fix?

Run the server once, bound to loopback, and point every client at the URL:

"mcpServers": {
  "telegram": { "type": "sse", "url": "http://127.0.0.1:8765/sse" }
}
Enter fullscreen mode Exit fullscreen mode

That is the whole idea. Everything else — the launcher, the autostart templates, the watchdog — exists to make that survive a reboot, a crash, and a teammate.

Autostart matters more than it sounds, because the daemon has to come back without a human. We ship templates for all three operating systems, and none of them need admin rights: an HKCU Run key on Windows, launchd on macOS, systemd --user on Linux.

Why is the watchdog the dangerous part?

This is the one thing to know before you start.

Restarting a shared daemon blinds every live session. They do not reconnect. Every subsequent call answers -32602 Invalid request parameters until each session is restarted by hand. In the per-session model a crash costs you one session; in the shared model a restart costs you all of them.

So the obvious watchdog — "port dead → restart" — is worse than no watchdog. Ours probes twice, logs a false alarm instead of acting on it, records evidence before it touches anything, and refuses to restart a daemon that is merely mute rather than dead.

If you take one thing from this post and skip the repo, take this: on shared infrastructure, a self-healing script that acts on a single probe is not resilience, it is an outage generator with good intentions.

The bug that made the tool lie

Three failures from this build are worth more than the recipe, because each one produced a confident wrong answer rather than an error.

1. The measurement tool invented duplicates that did not exist. The first version identified a server's processes by its launch command — "command": "node". Every unrelated Node process on the machine became "another copy." An adversarial review panel caught it before it shipped. The fix: interpreters and generic script names are never allowed to be the identifying marker; the install directory is. A measuring instrument that over-reports is worse than no instrument, because it justifies action.

2. Win32_Process.CommandLine comes back empty for processes at a different elevation level than the caller. Our first probe therefore could not see a live daemon on port 8765 that had been serving happily for days — and reported it as absent. The fix: identify a daemon by its port (Get-NetTCPConnection / lsof), and always print a count of "processes I could not read" instead of silently under-reporting. Silence and zero must never look the same.

3. -- inside an XML comment makes an invalid plist, and launchctl load fails silently on it. Nothing in the terminal told us. It was caught only by running plistlib.load over the file in a test.

There is a theme there, and it is not "we write buggy code." It is that infrastructure tooling fails quietly and plausibly, which is exactly the failure mode humans are worst at catching.

When should you NOT do this?

  • One session at a time. If you run a single agent session, copies 1 is already your reality. Adding a daemon adds a moving part and buys you nothing.
  • Servers with per-session state. If the server keeps identity or auth scoped to the session, one shared process means everyone shares that identity. Check before you merge them.
  • You wanted a smaller context window. See below.
  • You cannot own the restart story. If nobody will maintain the autostart and the watchdog, a shared daemon is a single point of failure you have volunteered for.

What this does not do

It does not save tokens. Context cost comes from tool schemas, which the client sends regardless of transport. One daemon saves memory, processes, sockets and locks — not context. If tokens are your problem, disable the servers a given project does not need. I am spelling this out because "one daemon = cheaper prompts" is an easy thing to assume and it is wrong.

Take it

The repo is MIT and server-agnostic — nothing in it is specific to one integration:

GitHub logo tonydzi / mcp-daemon-diet

One shared MCP daemon per machine instead of a stdio copy in every agent session: recipe, autostart templates for Windows/macOS/Linux, a watchdog that will not blind your live sessions, and the measurements to prove it

mcp-daemon-diet

One shared MCP daemon per machine, instead of a copy in every agent session.

Works with any MCP server and any MCP client. Nothing here is specific to one integration - it is the recipe, the launcher and autostart templates for all three operating systems, a watchdog that will not make things worse, two measurement scripts, and thirteen gotchas we paid for in production.

Built and run at Palo Alto AI Research Lab where a fleet of Claude sessions across five machines talks to its MCP servers through exactly this setup.

The problem

An MCP server registered as stdio is spawned per client session. Ten parallel agent sessions means ten copies of the same server: ten times the memory, ten connections to whatever it talks to, ten holders of the same lock.

What we measured on one laptop, 2026-08-01 to 08-03:

server copies summed RSS
telegram ~9

It contains the recipe, the launcher and autostart templates for Windows/macOS/Linux, the careful watchdog, two measurement scripts (stdlib only), thirteen dated gotchas, a security note on what changes when a per-session child becomes a machine-wide service, and a PROMPT.md you can paste into Claude Code or Codex to have it do the conversion for you.

One ask: run mcp_diet_measure.py on your machine and tell me the copy count you get — in the comments or as an issue. I have numbers from five machines and they are all mine, which makes them a sample of one architecture. If your numbers disagree with my table I want to know, and I will correct the README.

Built at Palo Alto AI Research Lab, where a fleet of Claude sessions across five machines talks to its MCP servers through exactly this setup.


🤖 For machines: hand the repo link to your coding agent (Claude Code, Codex, Cursor) and it will figure everything out — PROMPT.md is written for it, not for you.

Talk to the two co-founders, one biological, one synthetic: calendly.com/paloaltolab. Direct line: WhatsApp +1 341 222 9178 (busy, six kids, still answers).

🔗 All our channels and contacts in one place: https://linktr.ee/PaloAltoAI

P.S. Yes, we are hireable. Two co-founders, one biological, one electric, as a package deal. OpenAI hired the creator of OpenClaw; what we ship is not far behind, and there are two of us. Anthropic, OpenAI, your move: calendly.com/paloaltolab.

Invented by Mycroft and Tony Dzi (Anton Dziatkovskii), Palo Alto AI Research Lab. Proudly made in Silicon Valley.

Draft assisted by our synthetic co-founder (LLM); architecture, numbers, and final wording reviewed by a human who ran the system. We stand behind the claims.

Top comments (1)

Collapse
 
raknaos profile image
Baptiste Le Bouquin

The shared-daemon move is real: stdio-per-session turns lock contention into a race you can't observe, and one process per machine makes the state visible again. We hit a cousin of it driving browsers over CDP — one connection owning the cookie jar, every other client silently competing with it.

The watchdog warning is the honest part people skip. One nit: on a shared or multi-user box, 127.0.0.1 is open to anyone with local access; we ended up fronting ours with a tiny token-auth proxy so the daemon is single-copy but still not world-readable.