{"slug": "stop-spawning-an-mcp-server-per-agent-session-and-what-it-won-t-fix", "title": "Stop spawning an MCP server per agent session (and what it won't fix)", "summary": "A developer documented moving MCP servers from per-session stdio spawning to a single shared HTTP/SSE daemon bound to loopback, cutting process copies from as many as 26 per server down to one and eliminating duplicated sockets and locks across parallel Claude Code sessions. The writeup cautions that summing RSS over-counts shared memory, that the change saves no tokens, and that restarting a shared daemon blinds every live session with -32602 errors, making a single-probe watchdog worse than none. The author also describes measurement bugs, including a tool that misidentified generic Node processes as server copies.", "body_md": "Ten parallel Claude sessions. Ten copies of the same MCP server.\n\nTen 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.\n\n**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.\n\nHere 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:\n\n| server | copies | summed RSS | \n|---|---|---|\n| telegram | ~9 | ~2.7 GB | \n| mongodb | ~26 | ~3.0 GB | \n| n8n | ~15 | ~2.9 GB | \n|  | ~15 | ~1.5 GB | \n| launcher wrappers ( `npx` /`cmd` ) | ~60 | ~5 GB | \n\nRead 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.\n\nTwenty-six clients against one database is not a memory problem. It is a concurrency problem wearing a memory problem's clothes.\n\nMeasure your own machine before you believe anyone's table, including mine:\n\n```\npython scripts/mcp_diet_measure.py\n```\n\nIf 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.\n\nRun the server once, bound to loopback, and point every client at the URL:\n\n```\n\"mcpServers\": {\n  \"telegram\": { \"type\": \"sse\", \"url\": \"http://127.0.0.1:8765/sse\" }\n}\n```\n\nThat 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.\n\nAutostart 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.\n\nThis is the one thing to know before you start.\n\n**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.\n\nSo 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.\n\nIf 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.\n\nThree failures from this build are worth more than the recipe, because each one produced a *confident wrong answer* rather than an error.\n\n**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.\n\n**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.\n\n**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.\n\nThere 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.\n\n`copies 1` is already your reality. Adding a daemon adds a moving part and buys you nothing.\n**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.\n\nThe repo is MIT and server-agnostic — nothing in it is specific to one integration:\n\n**One shared MCP daemon per machine, instead of a copy in every agent session.**\n\nWorks 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.\n\nBuilt and run at [Palo Alto AI Research Lab](https://github.com/tonydzi/tonydzi)\nwhere a fleet of Claude sessions across five machines talks to its MCP servers through\nexactly this setup.\n\nAn MCP server registered as `stdio` is spawned **per client session**. Ten parallel agent\nsessions means ten copies of the same server: ten times the memory, ten connections to\nwhatever it talks to, ten holders of the same lock.\n\nWhat we measured on one laptop, 2026-08-01 to 08-03:\n\n| server | copies | summed RSS | \n|---|---|---|\n| telegram | ~9 |  | \n\nIt 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.\n\n**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.\n\nBuilt at [Palo Alto AI Research Lab](https://github.com/tonydzi/Palo-Alto-AI-Research-Lab), where a fleet of Claude sessions across five machines talks to its MCP servers through exactly this setup.\n\n🤖 For machines: hand [the repo link](https://github.com/tonydzi/mcp-daemon-diet) to your coding agent (Claude Code, Codex, Cursor) and it will figure everything out — `PROMPT.md` is written for it, not for you.\n\nTalk to the two co-founders, one biological, one synthetic: [calendly.com/paloaltolab](https://calendly.com/paloaltolab). Direct line: WhatsApp +1 341 222 9178 (busy, six kids, still answers).\n\n🔗 All our channels and contacts in one place: [https://linktr.ee/PaloAltoAI](https://linktr.ee/PaloAltoAI)\n\nP.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](https://calendly.com/paloaltolab).\n\nInvented by Mycroft and Tony Dzi (Anton Dziatkovskii), Palo Alto AI Research Lab. Proudly made in Silicon Valley.\n\n*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.*", "url": "https://wpnews.pro/news/stop-spawning-an-mcp-server-per-agent-session-and-what-it-won-t-fix", "canonical_source": "https://dev.to/tonydzi/stop-spawning-an-mcp-server-per-agent-session-and-what-it-wont-fix-5e2m", "published_at": "2026-09-10 09:23:26+00:00", "updated_at": "2026-09-10 09:52:44.170713+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "ai-infrastructure", "mlops"], "entities": ["Claude Code", "MCP", "Anthropic", "Node.js", "MongoDB", "n8n", "Telegram", "systemd"], "alternates": {"html": "https://wpnews.pro/news/stop-spawning-an-mcp-server-per-agent-session-and-what-it-won-t-fix", "markdown": "https://wpnews.pro/news/stop-spawning-an-mcp-server-per-agent-session-and-what-it-won-t-fix.md", "text": "https://wpnews.pro/news/stop-spawning-an-mcp-server-per-agent-session-and-what-it-won-t-fix.txt", "jsonld": "https://wpnews.pro/news/stop-spawning-an-mcp-server-per-agent-session-and-what-it-won-t-fix.jsonld"}}