Installing a Python CLI is pip install. Installing a Claude Code [1] plugin with an MCP server involves a marketplace registry, a cached git clone, permission configuration, command deployment, and dependency resolution. Each concern exists for good reasons — user discovery, offline caching, security, tight editor integration, and predictable behavior. But from the user’s perspective, it should feel like one command.
The user-facing answer is simple: one command per use case. Behind that one command, six patterns make installation reliable. Each addresses a different concern.
One Command Per Use Case
A user should install exactly what they need with a single command. Every project that requires multiple steps (install binary, register MCP server, configure plugin) ships an install.sh that collapses them:
# CLI + MCP server
curl -fsSL https://raw.githubusercontent.com/punt-labs/quarry/0e4e6d1/install.sh | sh
# CLI + plugin hybrid
curl -fsSL https://raw.githubusercontent.com/punt-labs/biff/419ac99/install.sh | sh
# Plugin (no CLI)
curl -fsSL https://raw.githubusercontent.com/punt-labs/dungeon/a22fab7/install.sh | sh
# Python library
uv add punt-quarry
The script is the user-facing entry point. It verifies Python 3.13+, installs uv if missing, installs the CLI, runs the install subcommand (which registers the MCP server and/or plugin), and finishes with doctor to verify everything works. Idempotent — safe to re-run.
Two-Phase Install
Under the hood, the install script sequences two distinct phases. The Python package lifecycle and the Claude Code plugin lifecycle are separate concerns. Conflating them causes problems — pip install runs in CI, in Docker, in virtualenvs where Claude Code doesn’t exist.
Phase 1: uv tool install <package> — Installs the Python package and CLI entry point. Works immediately. No Claude Code dependency.
Phase 2: <tool> install — Registers the plugin marketplace, triggers claude plugin install, and configures the host. Runs when Claude Code is present.
For plugins without a CLI, Phase 1 doesn’t exist. For CLI+plugin hybrids like Biff, the CLI must be installed before the plugin because the MCP server config references the CLI binary: biff serve --transport stdio. Phase 1 puts it on PATH; Phase 2 wires it into Claude Code. The install.sh script handles this sequencing automatically.
Distribution Boundary
When Claude Code installs a marketplace plugin, it caches a full git clone of the repository. Everything committed becomes part of the plugin distribution. A .mcp.json file committed for local development becomes a ghost MCP server. A stale config directory becomes interference.
The distribution boundary is your .gitignore. If it’s committed, it ships. We learned this when a .mcp.json defining an unrelated MCP server (from a previous experiment) appeared as plugin:biff:claude-flow after marketplace installation. The fix: gitignore .mcp.json and declare MCP servers explicitly in plugin.json’s mcpServers field.
Rule: Treat the git repository as the distribution artifact. Gitignore everything that isn’t part of the plugin.
SessionStart Hooks
Commands and permissions need to be configured in the host, but the plugin system doesn’t handle this automatically.
A SessionStart hook runs on every Claude Code launch. We use it for two things:
-
Deploy commands — Copy
.mdfiles to~/.claude/commands/so users get short-form/whoalongside the namespaced/biff:who. The hook runs before Claude loads commands, but on first install there’s a restart penalty — the hook hasn’t run yet, so commands appear after the second launch. -
Set permissions — Add MCP tool patterns (e.g.,
mcp__plugin_biff_biff__*) tosettings.jsonpermissions.allowusingjq. Without this, users get permission prompts on every tool call.
Both operations are idempotent. The hook checks before writing.
Pin for Production
uv sync respects the lockfile. uv tool install resolves fresh from PyPI.
If pyproject.toml says fastmcp>=2.0.0 and the lockfile pins 2.14.5, development stays on 2.14.5. But when an end user runs uv tool install punt-biff (or the install.sh script does it for them), they get whatever PyPI resolves — which could be 3.0.1 with breaking API changes.
This caused a production crash for us. FastMCP 3.x removed an internal API we depended on. The lockfile protected development; nothing protected the user.
Rule: Pin major versions in dependencies for CLI tools distributed via uv tool install. The lockfile protects development. Dependency bounds protect production.
Doctor Checks
After installation, the user needs one command to answer: “is everything working?”
<tool> doctor runs diagnostics, each classified as required (must pass) or informational (nice to have):
✓ CLI version 0.8.0
✓ Marketplace plugin installed
✓ MCP server registered
✓ Commands deployed
✓ Permissions configured
✓ NATS relay reachable
○ .biff file (not found — run biff init)
○ Status line (not installed)
Required checks gate the exit code. Informational checks report status without failing. The doctor command resolves configuration the same way the runtime does — same loading, same fallback chain — to catch mismatches.
Clean Uninstall
claude plugin uninstall removes the marketplace registration and cached clone. It does not remove:
- Deployed commands in
~/.claude/commands/ - Permission entries in
settings.json - Status line wrapping
- Orphaned local plugin directories from pre-marketplace installs
The project’s own uninstall command must handle all of these. We also clean three legacy locations that persist after marketplace migration: the old ~/.claude/plugins/<name>/ directory, the old cache at ~/.claude/plugins/cache/local/<name>/, and stale entries in installed_plugins.json.
If your tool has an install path, it needs an equally thorough uninstall path.
The Pattern
For the user, installation is one command: curl -fsSL .../install.sh | sh. Under the hood, the script sequences uv tool install, <tool> install, and <tool> doctor — three phases, each with a clear responsibility, each independently testable. The uninstall path mirrors it: <tool> uninstall && uv tool uninstall <pkg>.
All six patterns are documented in punt-kit as reusable patterns for anyone building MCP [2] tools with a Claude Code plugin component. The platform is evolving fast. Some of these patterns will become unnecessary as Claude Code matures. That would be a good outcome.
References
- Anthropic. “Claude Code.” 2024–present. docs.anthropic.com
- Anthropic. “Model Context Protocol.” 2024–present. modelcontextprotocol.io