Skip to content

feat(install): register Claude Code MCP server at user scope - #270

Closed
gbrlcustodio wants to merge 4 commits into
feat/234-release-wheel-urlsfrom
feat/mcp-install-user-scope-override
Closed

feat(install): register Claude Code MCP server at user scope#270
gbrlcustodio wants to merge 4 commits into
feat/234-release-wheel-urlsfrom
feat/mcp-install-user-scope-override

Conversation

@gbrlcustodio

Copy link
Copy Markdown
Member

Summary

Stacked on #260. Removes the double materialization of the Pipefy MCP server on the Claude Code path and makes the installer the single owner of the Claude Code registration.

Previously /pipefy:install installed pipefy_mcp_server as a uv tool that the plugin's bundled .mcp.json (uvx) never referenced, so a plugin user who ran it ended up with two copies (the uvx-cached env plus an unused tool venv).

What changed

  • install.sh: the --client claude-code branch now registers the server via claude mcp add pipefy --scope user -- pipefy-mcp-server (idempotent: removes any prior user-scope entry first). By Claude Code's name precedence (local > project > user > plugin) the user-scope entry shadows the plugin's bundled entry, so only the installed binary spawns, on the same system Python the CLI is pinned to. require_claude errors cleanly if the claude CLI is absent.
  • commands/install.md: /pipefy:install switches from --client none to --client claude-code, and the prose explains the override + required reload.
  • .claude-plugin/plugin.json + hooks/check-server-version.sh: a SessionStart hook that nudges the user to re-run /pipefy:install when the installed server version drifts from the plugin's. No-ops for users on the pure plugin/uvx path (no installed binary).

Design notes

  • The committed .mcp.json (uvx) is unchanged and remains the zero-config default, so /plugin install pipefy@pipefy still works with no prior step. The override only kicks in once a user runs /pipefy:install.
  • Why user scope, not editing the plugin: there's no supported way to disable just one plugin's MCP server; name-shadowing at a higher scope is the only lever, and it's deterministic per the documented precedence.
  • Why the drift hook: shadowing means plugin auto-updates stop reaching the running server. The hook is the accepted mitigation for that staleness. It reads the plugin version (cheap sed on plugin.json) before spawning pipefy-mcp-server --version, so the Python cold start only happens for users who actually have the override installed.
  • The server only reads the keychain (load_session -> keyring.get_password); the documented macOS -25244 issue is a non-deterministic write during pipefy auth login. So this is about consistency with the CLI's system-Python install, not a known server-side failure.

Test plan

  • sh -n install.sh and sh -n hooks/check-server-version.sh
  • .claude-plugin/plugin.json parses as valid JSON
  • install.sh --dry-run --client claude-code previews the claude mcp add registration
  • Hook verified in all three states: no binary (silent), version drift (nudge), version match (silent)
  • uv run pytest (2955 passed, 46 skipped)
  • On a machine with Claude Code: after /pipefy:install + reload, confirm claude mcp list shows the user-scope pipefy and the plugin's uvx entry is suppressed

Release sequencing

Targets #260's branch to keep the stack; retarget to main once #260 merges.

`/pipefy:install` previously installed `pipefy_mcp_server` as a uv tool that
the Claude Code plugin's bundled `.mcp.json` (uvx) never referenced, so a
plugin user who ran it ended up materializing the server twice.

Make the installer the single owner of the Claude Code server registration
instead: `install.sh --client claude-code` now runs `claude mcp add pipefy
--scope user -- pipefy-mcp-server`. By Claude Code's name precedence
(local > project > user > plugin) the user-scope entry shadows the plugin's
bundled entry, so only the installed binary spawns, on the same system Python
the CLI uses. The committed `.mcp.json` (uvx) stays as the zero-config default
for the marketplace one-liner.

Shadowing means plugin auto-updates no longer reach the running server, so add
a SessionStart hook (hooks/check-server-version.sh) that nudges the user to
re-run `/pipefy:install` when the installed server version drifts from the
plugin's. It no-ops for users on the pure plugin/uvx path.

`/pipefy:install` switches from `--client none` to `--client claude-code`.
@gbrlcustodio gbrlcustodio self-assigned this Jun 2, 2026
@gbrlcustodio
gbrlcustodio requested a review from adriannoes June 2, 2026 18:56
@gbrlcustodio gbrlcustodio added the enhancement New capability or intentional behavior change (not a bugfix) label Jun 2, 2026

@adriannoes adriannoes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

Reviewed the install/plugin diff locally (sh -n, install.sh --dry-run --client claude-code). The user-scope registration cleanly fixes double materialization (uv tool + plugin uvx) and the hook’s “cheap checks first” ordering is sensible. One version-sync gap is worth addressing before this ships broadly (inline on the hook).

Also noted

  • F2 — install ordering: main() installs both uv tools before require_claude, so a missing claude CLI leaves pipefy / pipefy-mcp-server on disk but exits before claude mcp add. Since /pipefy:install now passes --client claude-code, consider checking for claude before install_tool, or finishing with a clear warning instead of err after wheels are already installed.

Comment thread hooks/check-server-version.sh
…flicts

Adopt the org rename (pipefy/ai-toolkit) and the install.sh-delegation form
from the updated base, while keeping this branch's additions: the plugin.json
SessionStart version-check hook and the install.sh --client claude-code
(user-scope MCP registration) behavior.
The SessionStart hook (hooks/check-server-version.sh) raw-string compares the
installed server's --version against plugin.json's version, but bump_version.py
never wrote or verified that file. Releases left the manifest behind, so users
on the current release got a drift nudge every session. Add .claude-plugin/
plugin.json to the write and verify paths and sync it to the package version.
…atterns

Use numbered groups and re-emit the closing quote in the replacement, matching
VERSION_ASSIGN_RE and ROOT_PROJECT_VERSION_RE in the same file, instead of the
named-group prefix/value/suffix form. Verify reads the version via group(2).
@gbrlcustodio

Copy link
Copy Markdown
Member Author

Closing this; one piece is salvaged and the rest is deferred rather than dropped.

Salvaged into #367: the bump_version.py change that tracks .claude-plugin/plugin.json's version in lockstep. That addressed a real, live drift (the manifest was stuck at 0.2.0-beta.1 while releases shipped 0.3.0-alpha.1); it now moves with every bump and is asserted by verify. Rewritten for current dev and reconciled with the sibling-pinning work in the same file.

Deferred (a design decision, not stale): the installer's user-scope registration (claude mcp add pipefy --scope user -- pipefy-mcp-server) and the SessionStart drift hook. The double-materialization it targets on the Claude Code path still exists, so this is worth revisiting, but it assumed the git-subdirectory .mcp.json that #368 is changing to a PyPI install, so it's better re-evaluated on top of that. The drift hook also depends on the plugin.json lockstep, which is now in place via #367, so that groundwork exists if we pursue it.

This PR was stacked on #260, which is also being closed as superseded (PyPI publishing landed in #365).

@gbrlcustodio
gbrlcustodio deleted the feat/mcp-install-user-scope-override branch July 4, 2026 05:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New capability or intentional behavior change (not a bugfix)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants