The problem
Most MCP configs start life as a paste from someone's README. Then a binary isn't on PATH, an env still says YOUR_API_KEY, or npx pulls a different package version than last week. The agent fails with a vague error, and you spend half an hour chasing it. I wanted a check I could run locally or in CI, without spinning up another dashboard.
What I built
So I built mcp-health, and I still maintain it. v1.4.0 is on npm as @leon-pixel/mcp-health. The same scan is the mcp-health-doctor action on GitHub Marketplace. It finds the config files those tools already use, walks each server entry, and reports ok, warn, or fail. Disabled servers are skipped.
Exit codes stay simple: 0 means ok, 1 means warnings only, and 2 means something failed. JSON and SARIF 2.1.0 are there when a pull request needs annotations.
What it checks
The default scan stays on disk. Ask for --online or --probe when you want more.
- Config shape for Cursor, VS Code, Claude Code, Claude Desktop, Gemini CLI, Copilot, Continue, Junie, Cline, Windsurf, Zed, and Codex, including JSONC and Continue YAML. User-level Gemini, VS Code, and Copilot configs are included unless you pass --no-user.
- Commands on PATH.
- Version pinning, and npm drift when you pass --online.
- VS Code and Cursor variables, including ${workspaceFolder}, expanded before the result is decided.
- Disabled servers, reported and then skipped.
- An optional live probe: a stdio or HTTP/SSE handshake, JSON-RPC ids that match, tools/list to catch a server that exposes no tools, and a 401 with an OAuth challenge treated as a warning.
- JSON output, and SARIF 2.1.0 with GitHub pull request annotations.
How it got better
The first cut gave false results. It left ${workspaceFolder} unexpanded, waited on probes for servers that had already crashed, ignored headers, and compared semver as strings. An independent review caught them. I shipped the fixes across 1.1, 1.2, and 1.2.1, with a test for each.
1.3.0 extends discovery to Continue, Junie, and Cline, and reads Claude Code from ~/.claude.json, including the entry for the scan root. --no-user skips those user-level files.
1.4.0 discovers Gemini CLI settings, the VS Code user mcp.json, and Copilot's user mcp-config.json. It warns when uvx, bunx, pnpm dlx, pnpm exec, or yarn dlx launches an unpinned package. I tightened Claude project-path matching so the same directory still matches, including a symlink and macOS /tmp versus /private/tmp. A relative stdio command resolves from the scan root, and SARIF and GitHub annotation paths stay relative to the workspace when the scan root sits inside it. A scan with no MCP config still exits 1, with a clearer NO_CONFIG hint and --allow-missing when that is intentional. Human-readable rows now say project or user. JSON keeps the file path and adds scope.
There are 48 Vitest tests. CI runs on every pull request. A tag-driven release publishes to npm with signed provenance and moves the v1 tag. Node 22.12 or newer.
Signals
- v1.4.0
- On npm + GitHub Marketplace
- Local first
- Add --online or --probe when needed
Try it
- From the project you're in, run the published package with npx.
- In GitHub Actions, use the Marketplace action. The v1 tag moves when a stable release ships.
1. Terminal
1npx @leon-pixel/mcp-health check2. GitHub Action
1- uses: actions/checkout@v52- uses: leon-pixel/mcp-health@v13 with:4 path: .5 args: --no-userWhat it doesn't do
- It won't rewrite your config or host a dashboard. That's intentional.
- Don't run --probe against configs you don't trust.
- Your IDE PATH might differ from the terminal, so run the check in the same place your agents run.
