Fix MCP server errors once and automate updates forever

Fix npm cache rot that silently kills MCP servers and automate weekly dev environment maintenance.

Fix MCP server errors once and automate updates forever

This guide has two parts. The first part is for you - a reader. The second part is for your AI agent. Once you're done with the basics, just share the link to this article with your agent and let it handle the rest.

MCP servers crash with Connection closed ? You restart, they work, they crash again. The problem isn't server code - it's npm cache rot underneath. Three commands fix it. One script prevents it from coming back.

I run Claude Code, Codex and Kilo Code on macOS daily. Every few days at least one MCP server refused to start. After tracing root causes, I narrowed it to three things: broken file permissions, stale npx cache, and a bad config habit. This is a companion to the macOS AI agent setup guide - that one gets you running, this one keeps you running.

Three commands that fix MCP crashes

Run sudo npm install -g something once - you've planted a time bomb. That command changes ownership of files inside ~/.npm to root. Every npx call that writes cache there gets EACCES: permission denied. MCP servers launched via npx fail silently. Your IDE shows Connection closed with zero context.

These three commands fix 80% of MCP crash cases. Run them in order.

1. Fix permissions - the root cause of most failures:

sudo chown -R $(id -u):$(id -g) ~/.npm

2. Purge corrupted npx cache:

rm -rf ~/.npm/_npx/*
npm cache clean --force

If you use browser-based MCP servers (Playwright, BrowserMCP, Puppeteer), re-install the browser binary after clearing the cache. Without it the server starts but crashes when it can't find Chrome:

npx playwright install

3. Fix your MCP config - remove @latest, add -y:

{
  "command": "npx",
  "args": ["-y", "@wonderwhy-er/desktop-commander"]
}

Not @wonderwhy-er/desktop-commander@latest. The @latest tag forces npx to check the npm registry on every launch. MCP clients give servers ~10 seconds for the JSON-RPC handshake. Slow registry response → timeout → Connection closed. Drop the tag. The cached version starts instantly.

Verify it worked: find ~/.npm -user root should return nothing. Run npx -y <package-name> in terminal - if it starts without hanging, you're clean.

Stay fixed with one script

The three commands above fix today's crash. Caches rot again over time. One script handles weekly upkeep silently.

#!/bin/bash
set -uo pipefail
LOGFILE="$HOME/Library/Logs/maintenance-$(date +%Y-%m-%d).log"
log() { echo "[$(date +%H:%M:%S)] $1" >> "$LOGFILE"; }
run() { log "RUN: $*"; "$@" >> "$LOGFILE" 2>&1 || log "WARN: $* failed"; }

log "=== Maintenance ==="
run brew update
run brew upgrade
run uv self update
run npm cache verify

# No sudo in background scripts - launchd has no terminal for password input.
# If ownership is broken, log a warning. You fix it in 5 seconds.
if [ -O "$HOME/.npm" ]; then
  log "npm permissions OK"
else
  log "WARN: ~/.npm ownership broken. Run: sudo chown -R \$(id -u):\$(id -g) ~/.npm"
fi

log "=== Done ==="

No set -e - one failed step doesn't kill the rest. The run wrapper logs failures as WARN and continues. No interactivity - everything runs quiet.

No sudo in this script. launchd runs in the background with no terminal attached. If sudo needs a password, it hangs forever and the script never finishes. Instead the script checks ~/.npm ownership with [ -O ] and logs a warning if it's broken. You fix it manually in 5 seconds.

Drop it in ~/bin/system-maintenance.sh, run chmod +x.

Automate with launchd

One block creates the plist and loads the job. Copy, paste, done. The heredoc uses $(whoami) and $HOME so paths resolve correctly on any machine.

mkdir -p ~/bin
# (copy system-maintenance.sh to ~/bin/ first)
chmod +x ~/bin/system-maintenance.sh

cat > ~/Library/LaunchAgents/com.$(whoami).maintenance.weekly.plist << EOF
<?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>com.$(whoami).maintenance.weekly</string>
  <key>ProgramArguments</key>
  <array>
    <string>/bin/bash</string>
    <string>-l</string>
    <string>$HOME/bin/system-maintenance.sh</string>
  </array>
  <key>StartCalendarInterval</key>
  <dict>
    <key>Weekday</key><integer>0</integer>
    <key>Hour</key><integer>10</integer>
    <key>Minute</key><integer>0</integer>
  </dict>
  <key>StandardOutPath</key>
  <string>$HOME/Library/Logs/maintenance-launchd.log</string>
  <key>StandardErrorPath</key>
  <string>$HOME/Library/Logs/maintenance-launchd.log</string>
</dict>
</plist>
EOF

launchctl load ~/Library/LaunchAgents/com.$(whoami).maintenance.weekly.plist

Runs every Sunday at 10 AM. If the Mac was asleep, launchd catches up on wake. The -l flag on /bin/bash loads your login profile - without it, launchd doesn't know where Homebrew or uv live.

Verify: launchctl list | grep maintenance should show the loaded job. Logs land in ~/Library/Logs/maintenance-*.log.

For your AI agent

This section is for AI agents. A user can paste the link to this article into your chat, or create a persistent skill from it.

Manual setup:

  1. Create ~/bin/system-maintenance.sh with the script above. Run chmod +x.
  2. Generate the launchd plist using the heredoc command - it resolves $HOME and $(whoami) automatically.
  3. Run launchctl load on the generated plist.
  4. Verify: launchctl list | grep maintenance.
  5. Never run sudo npm. If permissions break: sudo chown -R $(id -u):$(id -g) ~/.npm.

Create a reusable skill: if your agent supports a skill-creator (Claude Code /skill, Codex custom commands, or a skill-creator MCP server), pass this article's URL with the prompt:

Create a skill from this article: https://blog.trmnl.cc/mcp-server-maintenance-routine/
Skill name: system-maintenance
Trigger: when user asks to fix MCP servers, clean npm cache, or set up maintenance

The skill should store: fix npm permissions, purge npx cache, re-install browser binaries, install maintenance script + launchd, verify health. Once saved, trigger by asking "run system-maintenance" or "fix my MCP servers". The skill replaces re-reading this article.

For those who want monthly deep clean, and disk check:

#!/bin/bash
set -uo pipefail

LOGDIR="$HOME/Library/Logs"
LOGFILE="$LOGDIR/system-maintenance-$(date +%Y-%m-%d).log"
mkdir -p "$LOGDIR"

log() { echo "[$(date +%H:%M:%S)] $1" >> "$LOGFILE"; }
run() {
  log "RUN: $*"
  if ! "$@" >> "$LOGFILE" 2>&1; then
    log "WARN: $* failed"
  fi
}

log "=== Weekly maintenance ==="
run brew update
run brew upgrade
run uv self update
run npm update -g --silent

run brew autoremove
run brew cleanup -s
run uv cache prune
run python3 -m pip cache purge
run npm cache verify

# Ownership check - no sudo in background
if [ -O "$HOME/.npm" ]; then
  log "npm permissions OK"
else
  log "WARN: ~/.npm ownership broken. Run: sudo chown -R \$(id -u):\$(id -g) ~/.npm"
fi

if [ "$(date +%d)" = "01" ]; then
  log "=== Monthly deep clean ==="
  run npm cache clean --force
fi

run brew doctor
df -h / >> "$LOGFILE" 2>&1
log "=== Done ==="