Claude Code Plugins + uv: Research Notes

Two systems, often confused

There are two separate extension mechanisms for Claude Code:

  1. Plugins — the newer, unified system (public beta). A plugin is a directory that bundles skills, agents, hooks, MCP servers, LSP servers, and executables. Installed via /plugin install name@marketplace.

  2. MCP servers — the older, standalone mechanism. An MCP server is a single process that exposes tools to Claude over the Model Context Protocol. Configured via claude mcp add or .mcp.json.

Plugins can contain MCP servers (via .mcp.json at the plugin root), but MCP servers can also be used independently without plugins.


Where uv/uvx fits in

Prerequisite: uvx does not ship with Claude Code. You need to install uv separately — it gives you both uv and uvx:

curl -LsSf https://astral.sh/uv/install.sh | sh

Without this, any uvx-based MCP server config will fail with “command not found.”

There are 6 distinct integration points between uv and Claude Code:

1. Running published MCP servers via uvx

Many MCP servers are Python packages on PyPI. You run them without installing:

{
  "command": "uvx",
  "args": ["mcp-server-time", "--local-timezone", "America/New_York"]
}

Examples: mcp-server-time, mcp-server-fetch, mcp-server-git, msmcp-azure

CLI registration:

claude mcp add-json server-time --scope user '{
  "command": "uvx",
  "args": ["mcp-server-time", "--local-timezone", "Australia/Sydney"]
}'

2. Running from git repos via uvx --from

The Serena plugin (in the official marketplace) does this:

{
  "command": "uvx",
  "args": ["--from", "git+https://github.com/oraios/serena", "serena", "start-mcp-server"]
}

3. Running local dev servers via uv run

The official MCP tutorial uses this pattern:

{
  "command": "uv",
  "args": ["--directory", "/path/to/weather", "run", "weather.py"]
}

4. FastMCP integration (uses uv under the hood)

fastmcp install claude-code server.py --with pandas --with requests

Under the hood this registers: uv run --with fastmcp fastmcp run server.py

Manual equivalent:

claude mcp add my-server -- uv run --with fastmcp fastmcp run server.py

5. Plugin dependency management via ${CLAUDE_PLUGIN_DATA}

Plugins get a persistent data directory (~/.claude/plugins/data/{id}/) that survives updates. The recommended pattern is a SessionStart hook that diffs the bundled manifest against a stored copy and reinstalls when changed.

The docs show this pattern for Node.js:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "diff -q \"${CLAUDE_PLUGIN_ROOT}/package.json\" \"${CLAUDE_PLUGIN_DATA}/package.json\" >/dev/null 2>&1 || (cd \"${CLAUDE_PLUGIN_DATA}\" && cp \"${CLAUDE_PLUGIN_ROOT}/package.json\" . && npm install) || rm -f \"${CLAUDE_PLUGIN_DATA}/package.json\""
          }
        ]
      }
    ]
  }
}

The same pattern applies to Python: compare pyproject.toml, run uv sync when changed.

6. Hooks that enforce uv usage

PreToolUse hooks that block pip install commands and suggest uv add instead. Written as Python scripts:

#!/usr/bin/env python3
import json, sys

def main():
    data = json.load(sys.stdin)
    if data.get("tool") not in ["Bash", "Run"]:
        sys.exit(0)
    command = data.get("parameters", {}).get("command", "")
    patterns = {
        "python ": "uv run",
        "pip install": "uv add",
        "pytest": "uv run pytest",
    }
    for old, new in patterns.items():
        if old in command:
            print(f"Detected '{old}'. Use '{new}' instead.", file=sys.stderr)
            sys.exit(1)

if __name__ == "__main__":
    main()

Configured in ~/.claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|Run",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/pre_tool_use_uv.py",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

Plugin structure

my-plugin/
├── .claude-plugin/
│   └── plugin.json          # name, version, description (only manifest goes here)
├── .mcp.json                # MCP server configs (optional)
├── .lsp.json                # LSP server configs (optional)
├── skills/
│   └── my-skill/
│       └── SKILL.md         # model-invoked or user-invoked
├── agents/                  # subagent definitions (optional)
│   └── reviewer.md
├── hooks/
│   └── hooks.json           # event handlers (optional)
├── bin/                     # executables added to PATH (optional)
├── settings.json            # default settings (optional)
├── scripts/                 # utility scripts for hooks
└── README.md

plugin.json minimal example

{
  "name": "my-plugin",
  "description": "What this plugin does",
  "version": "1.0.0",
  "author": { "name": "Your Name" }
}

SKILL.md example (user-invoked slash command)

---
name: my-command
description: Short description for /help
argument-hint: <arg1> [optional-arg]
allowed-tools: [Read, Glob, Grep]
---

Instructions for Claude when this skill is invoked.
Use $ARGUMENTS to capture user input.

.mcp.json example (bundling a Python MCP server)

{
  "mcpServers": {
    "my-tool": {
      "command": "uvx",
      "args": ["my-mcp-package"]
    }
  }
}

Or for a bundled server script:

{
  "mcpServers": {
    "my-tool": {
      "command": "uv",
      "args": ["--directory", "${CLAUDE_PLUGIN_ROOT}", "run", "server.py"],
      "env": {
        "MY_CONFIG": "${CLAUDE_PLUGIN_ROOT}/config.json"
      }
    }
  }
}

Environment variables available in plugins

  • ${CLAUDE_PLUGIN_ROOT} — absolute path to plugin install dir (changes on update)
  • ${CLAUDE_PLUGIN_DATA} — persistent dir for deps/state (survives updates, at ~/.claude/plugins/data/{id}/)

Building an MCP server from scratch (the official tutorial pattern)

# 1. Create project
uv init weather
cd weather
uv venv
source .venv/bin/activate

# 2. Install dependencies
uv add "mcp[cli]" httpx

# 3. Write server (weather.py)
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("weather")

@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
    """Get weather forecast for a location."""
    # ... implementation ...
    return forecast_text

if __name__ == "__main__":
    mcp.run(transport="stdio")
# 4. Register with Claude Code
claude mcp add weather -- uv --directory /path/to/weather run weather.py

# Or in .mcp.json / claude_desktop_config.json:
{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": ["--directory", "/absolute/path/to/weather", "run", "weather.py"]
    }
  }
}

FastMCP shortcut

# One command to create + register
fastmcp install claude-code server.py --with pandas --with requests --python 3.11

MCP server configuration scopes

Scope Stored in Available in Shared with team
local ~/.claude.json Current project only No
project .mcp.json in project root Current project only Yes (via git)
user ~/.claude.json All projects No

Key getting-started guides (read and verified)

Official documentation

Guide URL What it covers
Build an MCP Server https://modelcontextprotocol.io/docs/develop/build-server uv init + uv add "mcp[cli]" + weather server + Claude Desktop config
Create Plugins https://code.claude.com/docs/en/plugins plugin.json, skills, --plugin-dir testing, quickstart
MCP in Claude Code https://code.claude.com/docs/en/mcp claude mcp add, .mcp.json, scopes, plugin MCP servers
Plugins Reference https://code.claude.com/docs/en/plugins-reference Full schema, CLAUDE_PLUGIN_DATA, SessionStart dep pattern, bin/
Discover Plugins https://code.claude.com/docs/en/discover-plugins /plugin install, marketplaces, official marketplace
Anthropic Blog https://claude.com/blog/claude-code-plugins Announcement, vision, community examples

Community guides

Guide URL What it covers
FastMCP + Claude Code https://gofastmcp.com/integrations/claude-code fastmcp install claude-code, --with, --python, env vars
CloudArtisan: MCP Servers https://cloudartisan.com/posts/2025-04-12-adding-mcp-servers-claude-code/ uvx examples for time/fetch/git/filesystem servers
pydevtools: Hooks for uv https://pydevtools.com/blog/claude-code-hooks-for-uv/ PreToolUse hooks blocking pip, Notification hooks for uv
pydevtools: Configure for uv https://pydevtools.com/handbook/how-to/how-to-configure-claude-code-to-use-uv/ CLAUDE.md approach to teach Claude to use uv
Azure MCP Server https://devblogs.microsoft.com/azure-sdk/azure-mcp-server-better-python-support/ uvx --from msmcp-azure azmcp server start pattern
Beginner’s MCP with uv https://mahendranp.medium.com/beginners-guide-to-building-and-testing-your-first-mcp-server-with-uv-and-claude-3bfc6198212a End-to-end beginner walkthrough

Source code references

Resource URL What it shows
Official plugins repo https://github.com/anthropics/claude-plugins-official Curated marketplace, plugin structure examples
Example plugin https://github.com/anthropics/claude-plugins-official/tree/main/plugins/example-plugin Reference implementation with skills + .mcp.json
Serena plugin .mcp.json https://github.com/anthropics/claude-plugins-official/blob/main/external_plugins/serena/.mcp.json Real uvx --from git+... pattern
Demo plugins (claude-code repo) https://github.com/anthropics/claude-code/tree/main/plugins PR review, security, commit commands
MCP Python SDK https://github.com/modelcontextprotocol/python-sdk FastMCP source, SDK reference

Key takeaways for our article

  • uvx is the bridge — it lets you run any PyPI-published MCP server without permanent installation. Python equivalent of npx for Node.js servers.
  • uv run is for local dev — when building your own server, execute it in your project’s venv.
  • FastMCP makes building Python MCP servers trivial — @mcp.tool() decorator, type hints auto-generate schemas.
  • Plugins are the distribution mechanism — wrap your MCP server + skills + hooks in a plugin directory, publish to a marketplace for one-command install.
  • ${CLAUDE_PLUGIN_DATA} is how plugins persist venvs/deps across updates.
  • CLAUDE.md + hooks are the simpler path for teams that just want Claude to use uv instead of pip — no plugin needed, just project configuration.
  • The official marketplace already has Python-based plugins using uvx (Serena, Azure MCP, etc.) — good real-world examples to reference.