Skip to content

feat(launch): lazy-MCP loading with opt-in prune (profile + global)#90

Merged
NagyVikt merged 3 commits into
mainfrom
feat/lazy-mcp-prune
Jun 30, 2026
Merged

feat(launch): lazy-MCP loading with opt-in prune (profile + global)#90
NagyVikt merged 3 commits into
mainfrom
feat/lazy-mcp-prune

Conversation

@NagyVikt

Copy link
Copy Markdown
Contributor

What

Prune a profile's MCP servers down to what its skills actually need, so unused tool schemas don't burn always-on context every message. Pairs with the honest MCP token accounting from #88 (which can now prove the saving via cue cost).

Behavior

  • Interactive launches keep the existing opt-out picker (mcp-picker.ts) + remembered per-dir overrides (mcp-overrides.ts).
  • Non-interactive launches stay fail-open (keep all) unless CUE_PRUNE_MCPS opts in:
    • CUE_PRUNE_MCPS=auto — drop unused profile-declared MCPs only (user-global MCPs stay untouched — preserves cue's existing invariant).
    • CUE_PRUNE_MCPS=all — also drop unused global servers present in the runtime .claude.json (heavy ones a coding profile never calls). Louder opt-in: it removes globally-set config.
  • Precedence: --disable-mcp > remembered override > CUE_PRUNE_MCPS > keep-all.
  • pin: true on a profile MCP marks it agent-core (never pruned). disabledMcpIds flow to the materializer, which evicts exactly those keys from .claude.json and never touches an unnamed user MCP.

Merge note

This unions cleanly with main's newer when: conditional-MCP feature: the MCP ref schema/types now carry both when (activation gate) and pin (prune-exempt) — see profiles/schema.json McpRef and profiles/_types.ts.

Test plan

  • bunx tsc --noEmit — clean (the when+pin union compiles)
  • bun test on mcp-overrides / mcp-picker / skill-dependencies.getNeeded / runtime-materializer.mcp-prune / profile-loader — pass (CI checks out submodules: recursive)
  • Lint: 0 new errors vs main baseline

🤖 Generated with Claude Code

NagyVikt and others added 3 commits June 30, 2026 02:45
Prune a profile's MCP servers to what its skills actually need so unused tool
schemas don't burn always-on context. Interactive launches keep the existing
opt-out picker + remembered per-dir overrides; non-interactive launches stay
fail-open (keep all) unless CUE_PRUNE_MCPS opts in:

  CUE_PRUNE_MCPS=auto  drop unused PROFILE-declared MCPs (user-global MCPs
                       stay untouched — the existing tested invariant holds)
  CUE_PRUNE_MCPS=all   also drop unused GLOBAL servers present in the runtime
                       .claude.json (heavy ones a coding profile never calls);
                       a louder opt-in because it removes globally-set config

Precedence: --disable-mcp > remembered override > CUE_PRUNE_MCPS > keep-all.
disabledMcpIds (incl globals under =all) flow to the materializer, which evicts
exactly those keys from .claude.json and never touches an unnamed user MCP.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t hermetic

Two CI failures vs main, both from entanglement — neither is the lazy-MCP
feature:

1. runtime-materializer.ts had an unrelated proxy health-probe rewrite
   (TCP-connect → HTTP /health) swept in from branch WIP, but its matching
   test update lived in a file this PR doesn't carry. Reverted the probe to
   main's TCP-connect version (keeping all lazy-MCP changes: disabledMcpIds
   plumbing + syncMcpsIntoClaudeJson). Restores `import net`, drops the now
   unused http/https imports.

2. skill-dependencies.getNeeded.test.ts was an integration test against the
   live resources/skills submodule, asserting requires_mcps backfills not
   present on main's pinned submodule commit. Added an optional skillsRoot
   param to getNeededMcps/getSkillDependencies/findSkillContent (default
   unchanged) and rewrote the test to use hermetic temp fixtures covering
   every detection path (inline + block-sequence requires_mcps, implicit
   body ref, no-dep, unknown id).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e (review M-1)

Review flagged that `--disable-mcp <id>` set reviewed=true, so the choice was
written as a remembered per-dir override and silently stuck on every later
launch until `--cue-pick-mcps`. The flag's doc reads one-shot, and a CI or
debug run must not pollute a shared checkout with override files. Apply the
disable for this launch only; persist a choice via the interactive picker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@NagyVikt NagyVikt merged commit b6e9376 into main Jun 30, 2026
4 of 5 checks passed
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