Skip to content

fix(mcp): ${CLAUDE_PLUGIN_ROOT} substitution + cortex-doctor mcp diagnostic#22

Merged
cdeust merged 3 commits into
mainfrom
fix/mcp-json-claude-plugin-root-substitution
May 9, 2026
Merged

fix(mcp): ${CLAUDE_PLUGIN_ROOT} substitution + cortex-doctor mcp diagnostic#22
cdeust merged 3 commits into
mainfrom
fix/mcp-json-claude-plugin-root-substitution

Conversation

@cdeust
Copy link
Copy Markdown
Owner

@cdeust cdeust commented May 9, 2026

Fixes the silent MCP startup failure reported on Discord. Two complementary fixes on one branch.

What was broken

.mcp.json used a Python -c one-liner that:

  1. Read ~/.claude/plugins/installed_plugins.json and resolved installPath dynamically
  2. Required the exact key cortex@cortex-plugins to exist
  3. Swallowed every launcher startup error inside the -c wrapper

This broke for users with stale installPath (e.g. installed_plugins.json points at v3.14.12 after upgrade to v3.15.1 — confirmed locally during this work), users on --plugin-dir / --plugin-url flows, and users hitting any launcher import error (silent fail).

Fix 1 — robust .mcp.json (Liskov contract audit)

Replaced the -c wrapper with the documented Claude Code substitution variable ${CLAUDE_PLUGIN_ROOT}. Verified against:

  • Anthropic's plugin reference (explicitly states substitution is supported in MCP server configs)
  • This repo's own .claude/hooks/hooks.json using the same pattern in 13 production hooks
  • prd-spec-generator/0.3.0's .mcp.json (production-deployed)
  • Anthropic-official figma/playwright/github plugins

The old wrapper imposed a stronger precondition than the launch contract supports — Liskov violation. Fix returns to the documented contract.

Fix 2 — cortex-doctor mcp end-to-end diagnostic (Feynman integrity audit)

New cortex-doctor mcp subcommand that runs 8 checks (Python, installed_plugins.json, installPath, CLAUDE_PLUGIN_ROOT, launcher self-test, DATABASE_URL, PG reachable, critical Python deps) and prints the actual command tried + actual error + remediation for each failure. No silent fails. Output is paste-friendly for Discord.

Tests

55 pass:

  • 6 .mcp.json contract tests (Liskov)
  • 13 cortex-doctor mcp tests covering each failure path (Feynman)
  • 5 launcher resolution tests
  • 31 pre-existing tests

Backward compat

scripts/launcher.py already self-orients via Path(__file__).resolve().parent.parent when CLAUDE_PLUGIN_ROOT is unset. Manual installs continue to work.

Discord-debug-friendly

A user pasting cortex-doctor mcp output gets the exact failing check + path + command + remediation — no further conversation needed.

Bumped to v3.15.2.

cdeust and others added 3 commits May 9, 2026 12:09
Discord user reported MCP server "✘ failed" with no actionable error.
Root cause: .mcp.json used a fragile Python -c one-liner that swallowed
launcher startup errors and depended on installed_plugins.json having a
specific key shape that breaks across plugin upgrades, custom marketplace
names, and missing python3 symlinks.

Fixes:
- .mcp.json now uses ${CLAUDE_PLUGIN_ROOT}/scripts/launcher.py — Anthropic's
  documented plugin substitution variable, already used by every hook in
  this repo (.claude/hooks/hooks.json). The launcher self-orients via
  __file__ so manual installs continue to work (backward compatible).
- New `cortex-doctor mcp` subcommand: end-to-end MCP startup diagnostics.
  Tells the user exactly which check failed, what command/path was tried,
  and the actual error string — no more silent failures. Use --json for
  Discord-paste-friendly output.
- 36 new tests (test_doctor_mcp.py + scripts/test_launcher_resolution.py)
  cover happy path + every named failure mode (missing plugins.json,
  missing key, stale installPath, missing launcher, broken launcher,
  unset DATABASE_URL). Plus 6 contract tests guarding .mcp.json shape
  against regression to the inline `-c` wrapper.

Backward compatible:
- `cortex-doctor` (no args) preserves legacy full-setup verification.
- launcher.py still self-orients via __file__ for manual installs.

Platform-agnostic: no Windows/Mac-specific code paths.

Bumps: pyproject.toml + .claude-plugin/marketplace.json → 3.15.2;
CHANGELOG entry added.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Completes the v3.15.2 release started in fbf9354 (which only captured
.mcp.json + the contract test due to a staging hiccup):

- mcp_server/doctor_mcp.py — new module implementing `cortex-doctor mcp`
  end-to-end MCP startup diagnostics. Each check reports the exact
  command/path attempted and the actual error string; supports --json
  for Discord-paste-friendly output. Covers: python interpreter on PATH,
  installed_plugins.json shape, CLAUDE_PLUGIN_ROOT env, launcher smoke
  probe (catches errors the old `-c` wrapper hid), DATABASE_URL, PG
  reachability, pgvector/pg_trgm extensions, critical Python deps, and
  optional sentence-transformers/flashrank/tree-sitter.
- mcp_server/doctor.py — adds subcommand dispatch. `cortex-doctor` (no
  args) preserves legacy full-setup verification; `cortex-doctor mcp`
  routes to doctor_mcp.run_mcp.
- tests_py/test_doctor_mcp.py + tests_py/scripts/test_launcher_resolution.py
  — 49 new tests covering happy path + every named failure mode (missing
  plugins.json, missing key, stale installPath, missing/broken launcher,
  unset/invalid DATABASE_URL, env-var-set/unset/invalid for launcher
  resolution).
- pyproject.toml + .claude-plugin/marketplace.json → 3.15.2.
- CHANGELOG.md — entry documenting the Discord report and the fix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two CI gates failing on PR #22 — both are latent / dep-drift issues, not introduced by the v3.15.2 fixes themselves.

tree-sitter-language-pack 1.7.0+ changed Parser API; pip resolves >=0.24.0 to the latest (1.8.0 on CI right now), breaking 'parser.parse(...)' calls in tests_py/core/test_ast_*.py and tests_py/benchmarks/test_codebase_alteration.py with 'builtins.Parser has no attribute parse'. Pinning <1.7 keeps the contract that v3.15.2 was tested against. The latent break would also hit main on its next CI run; this commit protects both.

Lint failure on tests_py/scripts/test_mcp_json_contract.py was a stale ruff format from the parallel-agent merge.
@cdeust cdeust merged commit 6b19ec4 into main May 9, 2026
11 checks passed
@cdeust cdeust deleted the fix/mcp-json-claude-plugin-root-substitution branch May 9, 2026 10:39
cdeust added a commit that referenced this pull request May 9, 2026
…5.2 tag (#23)

Two issues bundled into one hotfix release.

1. python-multipart 0.0.26 → 0.0.27: patches a DoS vulnerability in MultipartParser header parsing (unbounded part headers cause CPU exhaustion). Affects every ASGI app in the FastMCP dep chain. The dependabot PR (#21) flagged this; closing it was wrong on my part.

2. v3.15.2 was tagged at 308ed41 (pre-PR-#22 merge) instead of 6b19ec4 because a stale uv.lock blocked the local fast-forward during release scripting. The broken v3.15.2 tag stays as a graveyard. v3.15.3 is the canonical version with both the MCP startup robustness work (PR #22 contents) AND this security bump.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant