MCP Server at 100% CPU: Fixing Runaway Cloudflare Processes
While coding one day, I suddenly noticed the machine's fans spinning wildly. A quick top revealed CPU usage maxed out. Following the trail led to a surprising culprit: not a build task, not the browser, but the Cloudflare MCP server attached to Claude Code.
This article recaps the entire investigation process, the process model issue inherent in stdio-type MCPs, and the final mitigation decision: remove the MCP and switch to the Cloudflare REST API.
Symptoms: Two Processes Maxing Out a Core Each, with a Queue of Zombies Behind Them
First, I checked the processes. Sorted by CPU:
ps aux | grep -i mcp | grep -v grep
Output (redacted):
bjhl 56484 99.8 node .../node_modules/.bin/mcp-server-cloudflare run <account-id>
bjhl 56483 99.1 node .../node_modules/.bin/mcp-server-cloudflare run <account-id>
bjhl 77253 0.0 node .../node_modules/.bin/mcp-server-cloudflare run <account-id>
bjhl 77075 0.0 npm exec @cloudflare/mcp-server-cloudflare run <account-id>
bjhl 56199 0.0 npm exec @cloudflare/mcp-server-cloudflare run <account-id>
bjhl 56197 0.0 npm exec @cloudflare/mcp-server-cloudflare run <account-id>
bjhl 39408 0.0 node .../node_modules/.bin/mcp-server-cloudflare run <account-id>
bjhl 39332 0.0 npm exec @cloudflare/mcp-server-cloudflare run <account-id>
Three highly informative details:
- Two instances are each maxing out ~100% CPU (56483 / 56484) — it wasn't a memory leak, but a busy loop, saturating a single core.
- There were 7-8 processes for the same MCP server running simultaneously. The PID range is wide (39xxx / 56xxx / 77xxx), indicating they originated from multiple sessions started at different times. Old ones hadn't exited cleanly, and new ones had spun up.
- Each instance was actually a pair of processes: an
npm execparent process plus anodechild process — because the configuration used thenpx -y @cloudflare/mcp-server-cloudflarestartup method, requiring a full npm resolution chain each time it was launched.
Why This Happens: The stdio MCP Process Model
Using claude mcp get cloudflare to confirm the configuration:
cloudflare:
Scope: User config (available in all your projects)
Status: ✔ Connected
This is a user-scoped stdio-type MCP — meaning every Claude Code session spawns its own dedicated server process upon startup. Open three terminal windows, and you get three separate process sets, explaining why instances accumulate.
The lifecycle contract for stdio MCP is: client exits -> child process's stdin receives EOF -> server exits on its own. However, this relies on the server correctly handling EOF. Based on the scene (and without further profiling its internal code), the most likely scenario for the two CPU-consuming instances is: the parent session is gone, and the server is stuck in a read loop that doesn't handle stream closure — the read returns immediately, the loop retries immediately, thus saturating a core at 100% and never exiting.
The other residual instances with 0% CPU, though quiet, are also products of the same reclamation flaw — they just happened to be idle and didn't enter a busy loop.
In summary, the pitfall encountered here is a combination of three layered problems:
| Layer | Problem |
|---|---|
| Usage Pattern | An infrequently used operations tool was configured as user-scoped persistent — each session pays the cost of a full process set |
| Startup Chain | The npx startup doubles the process count (npm exec + node), and the first run involves network resolution |
| Server Implementation | The process doesn't exit after the session ends, and can even enter a busy loop |
Mitigation: Remove Configuration + Clean Up Processes
The mitigation was straightforward. First, remove the configuration:
claude mcp remove cloudflare -s user
# Removed MCP server cloudflare from user config
Then, kill all remaining processes (including the quiet zombies):
pkill -f "mcp-server-cloudflare"
Verify the cleanup:
ps aux | grep -i cloudflare | grep -v grep
# (no output)
claude mcp list | grep -i cloudflare
# (no output)
CPU usage dropped immediately, and the fans went quiet.
Why Not Reinstall It: For Low-Frequency Operations, APIs Are More Reasonable Than MCP
After removal, a question arises: what about future Cloudflare operations?
The answer is to directly use the Cloudflare REST API. Its API design is mature, well-documented, and a single curl command can perform the same underlying calls as the MCP tool:
# List zones
curl -s "https://api.cloudflare.com/client/v4/zones" \
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[].name'
# Purge cache for a specific zone
curl -s -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/purge_cache" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}'
# Check DNS records
curl -s "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
-H "Authorization: Bearer $CF_API_TOKEN" | jq '.result[] | {name, type, content}'
AI assistants can already write curl commands — having it call the API directly is no different in capability than having it call through an MCP tool. The difference lies in the cost model:
- MCP (Persistent): Whether used or not, each session must maintain a process; the quality of stdio implementations varies, and failed reclamation leads to incidents like this.
- API (On-Demand): Zero cost when unused; troubleshooting involves a straightforward HTTP request path, which is much more transparent.
My decision-making criterion boils down to one sentence: only high-frequency, highly interactive tools requiring state persistence warrant an MCP connection; for low-frequency operations, just have the AI call the API directly. Cloudflare's case is the latter for me — DNS is changed maybe a few times a month; maintaining a persistent process set for it isn't cost-effective.
Takeaway Checklist
- Regular Health Checks: Use
claude mcp listto see how many servers you have connected. For each, ask if its usage frequency justifies the persistent cost. - Fans Kick On? Check MCP First: Run
ps aux | grep mcp, paying attention to whether multiple instances of the same name are piling up — that's a signal of failed session reclamation. - Use User Scope with Caution: User-scoped stdio MCPs spawn processes for "every session × every project"; for tools used only in a specific project, configure them to project scope.
- MCPs Started via
npxHave Higher Cost: It doubles the process count + requires network access on first launch. For truly high-frequency use, consider a global installation and launch with an absolute path. - Removing ≠ Losing Capability: Most SaaS MCP servers are just thin wrappers over REST APIs; curl + API token is always the fallback path.
FAQ
How do I troubleshoot an MCP server process using 100% CPU?
Start with ps aux | grep mcp, sorted by CPU, to identify the specific process. Check its command line to confirm which MCP server it is. Then count the number of same-named instances — a pile of multiple instances indicates processes from historical sessions weren't retracted. The emergency fix is pkill -f "<server_name>". The root solution is evaluating whether this MCP is worth running persistently; for infrequent tools, simply remove it and switch to the API.
How do I completely remove an MCP server from Claude Code?
First, use claude mcp get <name> to confirm its scope (user / project / local). Then use the corresponding claude mcp remove <name> -s <scope> to remove the configuration. Finally, use pkill -f to clean up any still-running residual processes. Deleting the configuration without killing the processes will let the runaway instances continue consuming CPU.
When should I use MCP vs. directly calling an API?
Tools that are high-frequency, highly interactive, and require maintaining state (like browser sessions or database connections) are suitable for MCP. For low-frequency operational tasks (changing DNS, clearing caches, checking configurations), having the AI write curl commands to call the REST API is more cost-effective — zero persistent cost, and the troubleshooting path is much clearer when issues arise.