Add MCP server health check SessionStart hook #88
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ldraney/claude-custom!88
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "76-check-mcp-servers"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
check-mcp-servers.shSessionStart hook that detects MCP servers configured in~/.mcp.jsonthat failed to initialize silentlyadditionalContextbut never blocks session startupChanges
hooks/check-mcp-servers.sh: New 177-line hook. Snapshotsps aux, builds unique fingerprints per server type (uv--directory, npx package name, node script path), reports missing serverssettings.json: Registers hook as 4th SessionStart entryTest Plan
jqis not installed or~/.mcp.jsonis absentReview Checklist
Related
plan-pal-e-agency— Phase 8 (MCP reliability) related work🤖 Generated with Claude Code
PR #88 Review
BLOCKERS
1. Scoped npm package fingerprint is broken (line 151)
The
npxfingerprint logic strips the@versionsuffix using bash parameter expansion:For a scoped npm package like
@playwright/mcp@latest, the%%@*operator removes the longest suffix matching@*. Since the string starts with@, the entire string matches, yielding an empty string. The empty string is then caught by the[[ -n "$package" ]]guard on the pre-stripped value, but the stripped result is what gets echoed. The[[ -z "$fingerprint" ]]check on line 55 would then catch it andcontinue, so the server is silently skipped rather than flagged as missing.Net effect: all scoped npm packages (
@org/package) silently skip fingerprint matching, so they are never checked and never warned about. For the current config,@playwright/mcp@latestis the only npx server, meaning the npx code path is effectively dead.Fix options:
@version:echo "$package" | sed 's/@[^/]*$//'@:echo "${package%@*}"(shortest suffix match with%instead of%%)2. False positives for intentionally disabled servers
The hook reads all keys from
~/.mcp.jsonbut does not account forenabledMcpjsonServersinsettings.json. In the current config:~/.mcp.jsondefines:playwright,notion,pal-e-docs,forgejo,woodpeckersettings.jsonenables:agent-manager,mcp-nvim,ts-tools,nvim-helper,ableton,playwright,forgejoOnly
playwrightandforgejoappear in both. The hook will checknotion,pal-e-docs, andwoodpecker, find no running processes (because Claude Code never started them), and report them as missing every single session. This makes the warning useless -- users will learn to ignore it.The hook needs to either:
settings.jsonand intersect withenabledMcpjsonServers(preferred -- accurate).mcp.jsonin sync withenabledMcpjsonServersNITS
1.
set -euo pipefailvs fail-open tension (line 17)The script uses
set -euo pipefailbut the intent is fail-open. Themain 2>/dev/null || exit 0wrapper on line 176 handles this, and the comment on line 40-42 explains the SIGPIPE rationale well. However,set -u(nounset) could cause unexpected exits if any variable is unintentionally unset insidebuild_fingerprint. The current code appears safe, but this is a latent fragility. Consider whetherset -eo pipefail(without-u) would be more defensive for a fail-open hook.2. The
cat > /dev/nullstdin drain (line 32)Good practice for SessionStart hooks that don't need stdin. Consistent with other hooks in this repo.
3. Temp file in
/tmpwith predictable prefix (line 42)mktemp /tmp/check-mcp-ps.XXXXXXusesXXXXXXwhich gives sufficient randomness. Thetrap cleanup EXITon line 24 handles removal. No security concern here since the file contains onlyps auxoutput, not secrets.4. The jq
to_entriesindex-offset pattern (lines 117-123, 132-138)The jq logic for extracting
--directoryand-mvalues works correctly: it finds the index of the flag viato_entries, then reads$args[$idx + 1]. Verified against the actual~/.mcp.jsonconfigs -- fornotionwith args["run", "--directory", "/home/ldraney/notion-mcp", "python", "-m", "notion_mcp"], this correctly extracts/home/ldraney/notion-mcp. The|| truefallbacks are appropriate.5. Generic fallback case (lines 164-168)
The
*case inbuild_fingerprintuses the full command path as fingerprint. This is reasonable -- if someone has a custom binary MCP server, the command path itself is a decent fingerprint. No issue here.6.
ps auxsnapshot includes all users (line 43)Using
ps auxmeans the hook checks all system processes, not just the current user's. This is fine -- MCP servers run under the current user anyway, andgrep -qFwith a directory path is specific enough to avoid false positives from other users' processes.SOP COMPLIANCE
76-check-mcp-serversreferences issue #76)plan-pal-e-agencybut as general context, not a specific phase deliverable. Acceptable but could be more precise.Closes #76present in PR body~/.mcp.jsonis only read at runtime)session-start-context.shandcleanup-worktrees.sh)VERDICT: NOT APPROVED
Two blockers must be addressed:
The scoped npm package fingerprint bug renders the npx code path non-functional. This needs a fix and ideally a test against the actual
@playwright/mcp@latestconfig.The false positive issue with
enabledMcpjsonServerswill cause the hook to cry wolf every session, which defeats its purpose. The hook must account for which servers are actually enabled, or it will train users to ignore the warning.${package%%@*} (longest suffix match) destroys scoped package names like @playwright/mcp@latest → empty string. Changed to ${package%@*} (shortest suffix match) which correctly yields @playwright/mcp. Also investigated QA finding re: enabledMcpjsonServers filtering — verified empirically that all ~/.mcp.json servers are started regardless of that setting (all 5 servers running, only 2 listed in enabledMcpjsonServers). No filter needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>Nit fixes —
118f16aFinding 1 (scoped package fingerprint) — FIXED
Changed
${package%%@*}→${package%@*}(shortest suffix match). This correctly preserves@playwright/mcpfrom@playwright/mcp@latest.Finding 2 (enabledMcpjsonServers false positives) — REJECTED
Verified empirically that
enabledMcpjsonServersdoes NOT control which~/.mcp.jsonservers are started. Evidence:~/.mcp.jsonhas 5 servers:forgejo,notion,pal-e-docs,playwright,woodpeckerenabledMcpjsonServersonly listsplaywrightandforgejo(plus 5 entries not in.mcp.json)ps auxconfirms ALL 5 servers are running (multiple instances across sessions)The
enabledMcpjsonServersfield appears to serve a different purpose (possibly UI toggles or a separate config source). No filter is needed.Ready for re-review.
PR #88 Re-Review
Re-review after nit fixes from first review round.
Finding 1: Scoped npm package fingerprint (
%vs%%) -- VERIFIED FIXEDThe fix on line 153 changing
${package%%@*}to${package%@*}is correct.Trace through the cases:
%%@*(old, longest suffix)%@*(new, shortest suffix)@playwright/mcp@latest@playwright/mcp(correct)some-package@1.0.0some-package(correct)some-package(correct)some-package(no version)some-package(correct)some-package(correct)The
%%operator was stripping from the FIRST@in scoped packages, destroying the scope prefix entirely. The%operator correctly strips from the LAST@only. The inline comment on lines 150-152 accurately explains the reasoning.Finding 2:
enabledMcpjsonServersfalse positives -- ACCEPTED REJECTIONI reviewed both config files:
settings.json(line 13-21):enabledMcpjsonServerslistsplaywright,forgejo, plus 5 servers NOT in.mcp.json(agent-manager,mcp-nvim,ts-tools,nvim-helper,ableton).settings.local.json(line 93-99): HasenableAllProjectMcpServers: trueand its ownenabledMcpjsonServerslistingplaywright,notion,pal-e-docs,forgejo.The developer provided
ps auxevidence that all 5.mcp.jsonservers are running. The hook iterates.mcp.jsonkeys (line 36), notenabledMcpjsonServers, so servers absent from the allowlist but present in.mcp.jsonare checked -- and they ARE running. Additionally, the fail-open design means even if Claude Code changesenabledMcpjsonServersbehavior in the future, the worst outcome is a warning, not a block.The original concern was theoretical. The evidence refutes it. Rejection accepted.
BLOCKERS
None.
NITS
Latent edge case with scoped packages lacking a version suffix. If
.mcp.jsonever contained an npx arg like@scope/package(no@version), then${package%@*}would match the scope@and yield an empty string. That empty string would pass throughecho, andgrep -qF "" "$PS_FILE"matches every line -- resulting in a false negative (server never flagged as missing). The current.mcp.jsonuses@playwright/mcp@latest(always has@version), so this does not apply today. If scoped packages without version suffixes are ever added, the fingerprint logic would need a guard (e.g., check if the result still starts with@and contains/). Non-blocking -- documenting for future awareness.SOP COMPLIANCE
76-check-mcp-serversreferences issue #76)plan-pal-e-agency)Closes #76VERDICT: APPROVED