feat(install): register the Claude Code MCP server at user scope#369
Draft
gbrlcustodio wants to merge 4 commits into
Draft
feat(install): register the Claude Code MCP server at user scope#369gbrlcustodio wants to merge 4 commits into
gbrlcustodio wants to merge 4 commits into
Conversation
On the Claude Code path, the installer left the plugin's bundled uvx `.mcp.json` entry as the running server while also `uv tool install`ing pipefy-mcp-server, so a plugin user who ran /pipefy:install ended up with two materializations (the uvx-cached env plus an unused tool venv). --client claude-code now registers via `claude mcp add pipefy --scope user -- pipefy-mcp-server` (idempotent: it removes any prior user-scope entry first, and require_claude errors cleanly if the CLI is absent). By Claude Code's name precedence (local > project > user > plugin) the user-scope entry shadows the plugin's bundled server, so only the installed binary spawns, on the system Python the CLI is pinned to. Because shadowing means plugin auto-updates no longer reach the running server, a SessionStart hook (hooks/check-server-version.sh) nudges the user to re-run /pipefy:install when the installed server's --version drifts from the plugin manifest's version. It no-ops for users on the pure plugin/uvx path (no installed binary). The compare is a raw string: --version echoes __version__ (dashed, e.g. 0.3.0-alpha.1) and the manifest tracks that same value in lockstep, so matched versions do not nudge.
The version pipeline ends in tr, so its exit status is tr's, not pipefy-mcp-server's; the || exit 0 could never observe the binary failing. The empty-output case it looked like it guarded is already handled by the [ -n "$installed" ] check on the next line.
adriannoes
reviewed
Jul 4, 2026
Collaborator
There was a problem hiding this comment.
Summary
Solid draft — the installer path is the right shape for this problem. claude_code_register_pipefy() is clear about precedence, idempotent (remove-then-add), and require_claude fails with an actionable message. The hook's cheap-before-expensive guard ordering is also good, and the PR body is unusually honest about stacking (#367/#368) and open design questions.
I'm commenting rather than requesting changes because this is still a draft with known design calls outstanding. Before marking ready, two hook issues are worth fixing (see inline threads).
What worked well
- User-scope registration via
claude mcp addwith documented name-shadowing (local > project > user > plugin) - Idempotent re-runs (
mcp removebeforemcp add) require_claudewith a clear fallback (--client none)- Hook defers the expensive
pipefy-mcp-server --versionspawn until manifest version is parsed
Before marking ready
- Drift nudge points to
/pipefy:install, which cannot resync the user-scope server for users who already havepipefyon PATH — remediation should referenceinstall.sh --client claude-codeor wirecommands/install.mdin the same stack. command -v pipefy-mcp-serverdoes not prove a Claude user-scope override exists (Cursor/Codex installs also place the binary on PATH).
Also noted: merge after #367 (manifest lockstep) and #368; live claude mcp list shadowing check still open per your test plan.
/pipefy:install only runs 'uv tool install' for the CLI and stops once pipefy is on PATH; it never reinstalls pipefy-mcp-server or re-registers the user-scope server. Re-running install.sh --client claude-code is what clears the drift.
command -v pipefy-mcp-server also matches --client cursor installs, where no Claude Code user-scope override exists and the plugin's uvx server still runs. Check ~/.claude.json for a top-level mcpServers.pipefy whose command is the binary, so the nudge only fires when the override actually shadows the plugin. Falls through to no nudge if python3 is missing or the config is unreadable.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
On the Claude Code path,
install.shcurrently leaves the plugin's bundled uvx.mcp.jsonentry as the running server while alsouv tool installingpipefy-mcp-server, so a plugin user who runs/pipefy:installends up with two materializations (the uvx-cached env plus an unused tool venv).This makes the installer the single owner of the Claude Code registration:
install.sh:--client claude-coderegisters viaclaude mcp add pipefy --scope user -- pipefy-mcp-server(idempotent: removes any prior user-scope entry first;require_claudeerrors cleanly if theclaudeCLI is absent). By Claude Code's name precedence (local > project > user > plugin) the user-scope entry shadows the plugin's bundled server, so only the installed binary spawns, on the system Python the CLI is pinned to..claude-plugin/plugin.json+hooks/check-server-version.sh: aSessionStarthook that nudges the user to re-run/pipefy:installwhen the installed server's--versiondrifts from the plugin manifest version. It no-ops for users on the pure plugin/uvx path (no installed binary).Why user scope, not editing the plugin
There is no supported way to disable a single plugin's bundled MCP server. Name-shadowing at a higher scope is the only lever, and it is deterministic per the documented precedence. The committed
.mcp.jsonstays the zero-config default (/plugin install pipefy@pipefystill works with no prior step); the override only takes effect once a user runs the installer with--client claude-code.Why the drift hook
Shadowing means plugin auto-updates no longer reach the running (installed) server. The hook is the mitigation for that staleness. It reads the plugin version (cheap
sedonplugin.json) before spawningpipefy-mcp-server --version, so the Python cold start only happens for users who actually have the override installed.The compare is a raw string, and it holds because
--versionis argparseaction="version"echoing__version__verbatim (the dashed form, e.g.0.3.0-alpha.1), and the manifest now tracks that same value in lockstep (via the plugin.json lockstep added in #367). So a user on the current release is not nudged. This depends on #367 landing (before it, the manifest was stuck at0.2.0-beta.1and every session would nudge).Stacking
Stacked on #368 (
chore/mcp-json-pypi-install); GitHub retargets the base todevwhen #368 merges. The two do not overlap in files, but #368 settles the.mcp.jsonshape this feature coexists with, and #367 provides the manifest-lockstep the hook relies on, so this should land after both.Open questions (why this is a draft)
/pipefy:install(commands/install.md) from--client noneto--client claude-code. On currentdevthat command no longer uses the curl installer at all: it runs a direct git-baseduv tool installand never callsinstall.sh. Wiring the slash command to trigger user-scope registration is a separate decision, tangled with the pending CLI-install PyPI migration, so I leftcommands/install.mdout. Without it, user-scope registration is reachable only by runninginstall.sh --client claude-codedirectly.--versionformat coupling. The hook's raw-string compare is correct only as long as--versionkeeps emitting the dashed__version__and the manifest stays in lockstep with it. If either changes to the PEP 440-normalized form (0.3.0a1), the compare needs canonicalization on both sides./simplifyaltitude pass.) The hook diffs the installed binary's--versionagainstplugin.json's manifest version, but that manifest version is not what the plugin actually runs:.mcp.jsonis an unpinneduvx --prerelease allow pipefy-mcp-server, i.e. latest PyPI pre-release. The truthful "is my shadowing binary stale?" comparison is installed-vs-what-the-plugin-would-run, so the options are: pin the bundled server in.mcp.jsonand read that pin, compare installed-vs-latest-PyPI (adds a network call to a session-start hook), or keep the manifest anchor and accept it as a proxy that is exact only at release time. Note build(release): pin workspace siblings to the lockstep version #367 makes the manifest track the package version in lockstep, which removes today's0.2.0-beta.1vs0.3.0-alpha.1skew but does not by itself make the manifest equal the running server's resolved version./simplifyefficiency pass.) The hook spawnspipefy-mcp-server --versionon every SessionStart for anyone who raninstall.sh --client claude-code; measured at ~0.58s wall (Python cold start). Guards correctly defer the spawn (no binary -> instant exit), but it is the steady-state cost for the target population. If kept, the cheap mitigation is an mtime-keyed cache (stat the resolved binary, cache<mtime> <version>, re-spawn only when it changes) so the cost collapses to roughly once per install. Deferred as a design call rather than optimized in place, since question 2 may drop the hook entirely.Test
/simplifypass ran over this diff: applied one cleanup (removed a dead|| exit 0guard in the hook), and surfaced open questions 4 and 5 above; reuse/altitude otherwise judged the shell + hook at the right altitude.sh -n install.shandsh -n hooks/check-server-version.shpass..claude-plugin/plugin.jsonparses as valid JSON.install.sh --dry-run --client claude-codepreviewsclaude mcp add pipefy --scope user -- pipefy-mcp-serverand the user-scope next-steps message.claude mcp add+ reload +claude mcp listshadowing check).