MagicTools
Pitfall NotesBy CooconAugust 18, 20268 views6 min read

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:

  1. Two instances are each maxing out ~100% CPU (56483 / 56484) — it wasn't a memory leak, but a busy loop, saturating a single core.
  2. 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.
  3. Each instance was actually a pair of processes: an npm exec parent process plus a node child process — because the configuration used the npx -y @cloudflare/mcp-server-cloudflare startup 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

  1. Regular Health Checks: Use claude mcp list to see how many servers you have connected. For each, ask if its usage frequency justifies the persistent cost.
  2. 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.
  3. 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.
  4. MCPs Started via npx Have 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.
  5. 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.

References

Related Articles

Protobuf LSP Setup Guide: VS Code & Neovim with Buf

Buf ships a production-grade Protobuf LSP inside the buf CLI: go-to-definition, completion, find references, rename, and diagnostics that match buf lint. Setup paths for VS Code, Neovim (0.11 native + lspconfig), and JetBrains, a minimal two-file test project, and the known pitfalls—every command verified with buf v1.72.0.

developerAug 17, 20265 min
19

Zsh History Disappearing? A 10-Year-Old Bug, Fixed in 5.9.2

Commands you ran yesterday vanish from Ctrl+R and ~/.zsh_history only has old entries? It's not your config. A bug introduced in 2015 lets a Ctrl+C during shell exit truncate your history file. Fixed in Zsh 5.9.2 (5.9.1 missed it). Root-cause breakdown, a distro upgrade status table, and why INC_APPEND_HISTORY won't save you.

pitfallsAug 17, 20267 min
19

Stripe's $7B OpenRouter Deal: What It Means for Developers

Bloomberg reports Stripe has agreed to buy OpenRouter for over $7 billion—5x its valuation from three months ago. We cross-checked the facts: the deal's real status, why Stripe is buying its own billing customer, the Bridge→Tempo→Metronome→OpenRouter acquisition arc, BYOK rules, five alternatives, and the one thing to do now: abstract your routing layer.

ai-tutorialsAug 17, 20267 min
18

Stripe's $7B OpenRouter Deal: Buying the Right to Route

Stripe has finalized a deal to buy OpenRouter for more than $7 billion. The money isn't for the code that forwards requests — it's for the power to decide which provider serves them. But Stripe bought Amazon's position without Amazon's lock-in.

developerAug 17, 202610 min
66

Published by MagicTools