diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 963ead4a..fe9bb9ac 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -6,5 +6,17 @@ "author": { "name": "Pipefy", "url": "https://github.com/pipefy" }, "homepage": "https://github.com/pipefy/ai-toolkit", "repository": "https://github.com/pipefy/ai-toolkit", - "license": "Apache-2.0" + "license": "Apache-2.0", + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "sh \"${CLAUDE_PLUGIN_ROOT}/hooks/check-server-version.sh\"" + } + ] + } + ] + } } diff --git a/hooks/check-server-version.sh b/hooks/check-server-version.sh new file mode 100755 index 00000000..100914b4 --- /dev/null +++ b/hooks/check-server-version.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# SessionStart nudge: when install.sh has registered a user-scope `pipefy` +# server (the persistent pipefy-mcp-server binary, which shadows the plugin's +# bundled uvx entry), plugin auto-updates no longer reach that running server. +# This compares the installed binary's version against the plugin's declared +# version and, on drift, tells the user to re-run install.sh (the path that +# actually reinstalls the binary and re-registers the user-scope server; +# /pipefy:install only installs the CLI). +# +# No-op unless that user-scope override is registered: users on the pure +# plugin/uvx path have no installed binary, and users who installed the binary +# only for another client (e.g. --client cursor) still run the plugin's uvx +# server here, so there is nothing shadowing the plugin to nudge about. +set -eu + +# Cheap pre-filter: no installed binary means nothing could shadow the plugin. +command -v pipefy-mcp-server >/dev/null 2>&1 || exit 0 + +# The precise trigger is a user-scope `pipefy` server whose command is that +# binary. `command -v` alone is not enough: --client cursor also puts the +# binary on PATH without registering a Claude Code override, so the plugin's +# uvx server still runs here and there is nothing to sync. Fall through to no +# nudge if python3 is missing or the config cannot be read. +python3 - <<'PY' || exit 0 +import json +import os +import sys + +try: + servers = json.load(open(os.path.expanduser("~/.claude.json"))).get("mcpServers", {}) +except (OSError, ValueError): + sys.exit(1) +entry = servers.get("pipefy") if isinstance(servers, dict) else None +command = entry.get("command") if isinstance(entry, dict) else None +sys.exit(0 if command == "pipefy-mcp-server" else 1) +PY + +# Cheap checks first; only spawn the binary (Python cold start) once we have a +# plugin version to compare against. +manifest="${CLAUDE_PLUGIN_ROOT:-}/.claude-plugin/plugin.json" +[ -f "$manifest" ] || exit 0 +plugin=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$manifest" | head -n1) +[ -n "$plugin" ] || exit 0 + +installed=$(pipefy-mcp-server --version 2>/dev/null | tr -d '[:space:]') +[ -n "$installed" ] || exit 0 + +if [ "$installed" != "$plugin" ]; then + echo "Pipefy: the installed MCP server ($installed) differs from this plugin ($plugin). Re-run the installer to sync: curl -fsSL https://raw.githubusercontent.com/pipefy/ai-toolkit/main/install.sh | sh -s -- --client claude-code" +fi +exit 0 diff --git a/install.sh b/install.sh index 7668f030..9723d991 100755 --- a/install.sh +++ b/install.sh @@ -4,7 +4,8 @@ # Resolves the latest GitHub Release tag (or a tag passed via --version), # discovers its wheel assets, installs the CLI and MCP server via # `uv tool install`, optionally installs skills via `npx skills add`, and -# writes the MCP server registration into the chosen client's config. +# registers the MCP server with the chosen client (a config-file write for +# most clients; `claude mcp add` at user scope for Claude Code). set -eu @@ -71,8 +72,11 @@ and register the MCP server with an MCP client. Options: --yes, -y Skip all confirmation prompts. --no-skills Skip the skills installation step. - --client Register MCP server in this client's config. + --client Register the MCP server for this client. One of: claude-code, claude-desktop, cursor, codex, none. + claude-code registers via 'claude mcp add' at user scope + (overrides the plugin's bundled server); the others write + the client's own config file. Defaults to 'none' (prints snippet to paste). --version Install a specific GitHub Release tag (e.g. v0.2.0-beta.2). Defaults to the most recent release (incl. prereleases). @@ -314,6 +318,28 @@ require_python3() { fi } +require_claude() { + if ! command -v claude >/dev/null 2>&1; then + err "the 'claude' CLI is required to register the MCP server in Claude Code, but was not found. Install Claude Code (https://claude.com/claude-code), or re-run with --client none and paste the snippet manually." + fi +} + +claude_code_register_pipefy() { + # Register at USER scope: Claude Code resolves same-named servers + # local > project > user > plugin, so a user-scope `pipefy` shadows the + # plugin's bundled entry and only the binary installed above spawns. The + # bare binary (not a uvx command) keeps the server on the system Python it + # was installed with, without baking an interpreter path into user config. + if [ "$DRY_RUN" -eq 0 ]; then + require_claude + # `claude mcp add` errors on a duplicate name; drop any prior user-scope + # entry first to keep re-runs idempotent. + claude mcp remove pipefy --scope user >/dev/null 2>&1 || true + fi + run claude mcp add pipefy --scope user -- pipefy-mcp-server + [ "$DRY_RUN" -eq 1 ] || say "Registered 'pipefy' at user scope (overrides the plugin's bundled server)." +} + json_merge_pipefy() { path="$1" if [ "$DRY_RUN" -eq 1 ]; then @@ -404,6 +430,8 @@ EOF } write_client_config() { + # cursor/claude-desktop/codex write the client's own config file; claude-code + # instead registers via `claude mcp add` (user scope) to override the plugin. case "$CLIENT" in cursor) json_merge_pipefy "$HOME/.cursor/mcp.json" @@ -415,11 +443,7 @@ write_client_config() { codex_append_pipefy "$HOME/.codex/config.toml" ;; claude-code) - cat < Pipefy MCP server registered at user scope (overrides the plugin's" + say " bundled server). Reload or restart Claude Code for it to take effect." + say " For skills and slash commands, also install the plugin:" + say " /plugin marketplace add $REPO && /plugin install pipefy@pipefy" + fi say "" say "Next: authenticate with Pipefy." say " Default (browser): pipefy auth login"