feat(install-mcp): register a local-scope mcp server so the vscode extension connects#523
Conversation
…tension connects following the readme in a fresh project did not work in the claude code vs code extension: `install-mcp` wrote only a project-scope `.mcp.json`, which claude code loads only after a per-user approval — and the extension never surfaces that prompt. so the server sat at "pending approval", the kb_* tools never appeared, and the hooks (which need no approval) kept running, which reads as connected but isn't. reproduced end-to-end on a fresh project with released 1.4.0. install-mcp now also registers vouch at local scope in ~/.claude.json (`projects[<abs>].mcpServers`), exactly what `claude mcp add` does — a local-scope server is trusted on sight, no approval gate. declared by a new manifest `user_mcp:` block so the quirk lives in the claude-code adapter, not the core (host-neutral: only claude-code opts in). the merge is idempotent, preserves every other key in ~/.claude.json, and never clobbers a `vouch` server the user added themselves; a malformed config is reported as failed, never crashes the install. `--no-approve` opts out. the cli prints the registration and a reload-your-window nudge (mcp servers load once, at window start). verified end-to-end through the shipped cli: fresh project -> `vouch install-mcp claude-code` -> `claude mcp list` reports `vouch ... ✔ Connected` (was `⏸ Pending approval`), and an mcp handshake exposes all eight kb_* tools. tests sandbox ~/.claude.json via an autouse home fixture so the suite (and the many existing install() calls) never touch the developer's real config. readme + adapter readme now document the one-command flow and the reload; changelog updated.
Walkthrough
ChangesClaude Code MCP registration
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant vouch_install_mcp
participant install_adapter
participant claude_json
User->>vouch_install_mcp: install-mcp claude-code
vouch_install_mcp->>install_adapter: install(approve)
install_adapter->>claude_json: register project MCP server
claude_json-->>install_adapter: registration result
install_adapter-->>vouch_install_mcp: InstallResult
vouch_install_mcp-->>User: counts and reload guidance
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/vouch/install_adapter.py`:
- Around line 415-419: In src/vouch/install_adapter.py lines 415-419, update the
existing-server handling to compare the registered specification: return the
normal empty result only for an exact match, and report differing entries
through the established conflict or failure mechanism. In src/vouch/cli.py lines
4050-4055, show the registered reminder only for exact matches and surface
conflicts explicitly, using the installer result to distinguish these states.
- Around line 422-427: Update the config replacement flow around cfg_path and
tmp to use an exclusive temporary file created in the same directory via mkstemp
or NamedTemporaryFile(delete=False), rather than the fixed .vouch-tmp name. Set
the temporary file mode before writing config contents, replace cfg_path only
after a successful write, and remove the temporary file in a finally block on
all paths.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: bc5a4cdd-346d-4e5b-9fda-ebd50dc69723
📒 Files selected for processing (7)
CHANGELOG.mdREADME.mdadapters/claude-code/README.mdadapters/claude-code/install.yamlsrc/vouch/cli.pysrc/vouch/install_adapter.pytests/test_install_adapter.py
| if reg.name in servers: | ||
| # already registered — never clobber the user's own edits, and stay | ||
| # out of `result.skipped` (a file-path channel; this is a config | ||
| # entry, not a file). The CLI reminds the user to reload regardless. | ||
| return |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
distinguish an identical registration from a name conflict.
the installer currently emits the same empty result for both states, causing the cli to claim success when a user-owned server blocks vouch.
src/vouch/install_adapter.py#L415-L419: compare the existing specification; preserve exact matches, but report differing entries as conflicts or failures.src/vouch/cli.py#L4050-L4055: show the registered reminder only for an exact existing match, and surface conflicts explicitly.
📍 Affects 2 files
src/vouch/install_adapter.py#L415-L419(this comment)src/vouch/cli.py#L4050-L4055
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/vouch/install_adapter.py` around lines 415 - 419, In
src/vouch/install_adapter.py lines 415-419, update the existing-server handling
to compare the registered specification: return the normal empty result only for
an exact match, and report differing entries through the established conflict or
failure mechanism. In src/vouch/cli.py lines 4050-4055, show the registered
reminder only for exact matches and surface conflicts explicitly, using the
installer result to distinguish these states.
| cfg_path.parent.mkdir(parents=True, exist_ok=True) | ||
| tmp = cfg_path.with_name(cfg_path.name + ".vouch-tmp") | ||
| tmp.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8") | ||
| if cfg_path.exists(): | ||
| shutil.copymode(cfg_path, tmp) | ||
| tmp.replace(cfg_path) |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -n -C3 'NamedTemporaryFile|mkstemp|copymode|\.vouch-tmp' src testsRepository: vouchdev/vouch
Length of output: 1437
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '390,460p' src/vouch/install_adapter.py
printf '\n---\n'
sed -n '1,80p' src/vouch/migrations/rewriter.pyRepository: vouchdev/vouch
Length of output: 5474
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C4 'json\.dumps\(data|servers\[reg\.name\]|cfg_path|install-mcp|registered' src/vouch/install_adapter.pyRepository: vouchdev/vouch
Length of output: 4211
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '420,455p' src/vouch/install_adapter.pyRepository: vouchdev/vouch
Length of output: 1720
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n 'tmp\.unlink|unlink\(|replace\(cfg_path\)|except \(OSError, ValueError\)' src/vouch/install_adapter.pyRepository: vouchdev/vouch
Length of output: 224
use an exclusive tempfile here. src/vouch/install_adapter.py:422-427 writes a fixed .vouch-tmp before copying the mode, so a failed run can leave the full config behind and the temp file can exist with broader permissions than the final config. use mkstemp/NamedTemporaryFile(delete=False) in the same directory, set the mode before writing, and clean it up in finally.
🧰 Tools
🪛 ast-grep (0.44.1)
[info] 423-423: use jsonify instead of json.dumps for JSON output
Context: json.dumps(data, indent=2)
Note: [CWE-116] Improper Encoding or Escaping of Output.
(use-jsonify)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/vouch/install_adapter.py` around lines 422 - 427, Update the config
replacement flow around cfg_path and tmp to use an exclusive temporary file
created in the same directory via mkstemp or NamedTemporaryFile(delete=False),
rather than the fixed .vouch-tmp name. Set the temporary file mode before
writing config contents, replace cfg_path only after a successful write, and
remove the temporary file in a finally block on all paths.
the bug, reproduced
following the readme in a fresh project does not work in the claude code vs code extension.
install-mcpwrites only a project-scope.mcp.json, and claude code loads a project-scope server only after a per-user approval — which the extension never surfaces a prompt for. so the server sits at⏸ pending approval, thekb_*tools never appear, and the capture hooks (which need no approval) keep running — which reads as "connected" but isn't.reproduced end-to-end on a clean
/tmpproject with released 1.4.0:proof this is the whole story: in the vouch repo itself the
.mcp.jsonserver is unapproved (enabledMcpjsonServers: []) yet vouch works in the extension — because it also has a local-scopemcpServersentry in~/.claude.json. that entry, not.mcp.json, is what the extension loads.the fix
install-mcpnow also registers vouch at local scope in~/.claude.json(projects[<abs>].mcpServers) — exactly whatclaude mcp adddoes. a local-scope server is trusted on sight, no approval gate, so a fresh install just connects.user_mcp:block, so the quirk lives in the claude-code adapter, not the core — host-neutral, only claude-code opts in.~/.claude.json; never clobbers avouchserver you added yourself; a malformed config is reported asfailed, never crashes the install.--no-approveopts out (keeps only.mcp.json).verified end-to-end through the shipped cli
and an mcp
initialize+tools/listhandshake against the registered server returns all eightkb_*tools.tests
new
test_install_adapter.pycases pin: fresh registration writes the byte-exact working spec, idempotent (no dup, file untouched),--no-approvewrites nothing, existing~/.claude.jsoncontent preserved, a user's ownvouchserver never clobbered, malformed config reported not raised, and a manifestuser_mcp.configtraversal rejected. an autouse home-sandbox fixture makes the whole module (and the many existinginstall()calls, nowapprove=Trueby default) never touch the developer's real~/.claude.json.make checkgreen (pytest, mypy, ruff). readme + adapter readme document the one-command flow and the reload; changelog updated.note on the older docs PRs
this supersedes the manual-workaround guidance in #521 (which documented a
.claude/settings.local.jsonapproval that i only verified against the terminal cli, not the extension). recommend closing #521 in favour of this; the readme change here is the correct, extension-verified story.Summary by CodeRabbit
New Features
vouch install-mcp claude-codenow automatically registers the MCP server for the current project, helping Claude Code tools appear without manual approval.--no-approveto skip automatic registration.Documentation