Claude Code Plugins: The Complete Guide to Building and Sharing Extensions

Listen to this article
Click ▶ to start
0%

Skills, agents, and hooks you add to .claude/ are powerful — but they are locked to one project. Every time you start a new repo you copy the same files, maintain them in multiple places, and drift out of sync. Claude Code plugins solve this: a plugin is a shareable, versioned package that carries all your customisations and can be installed in any project with one command.

This post covers what plugins are, when to use them, and how to build a real one from scratch — a DevOps helper that ships a deployment skill, a pre-deploy safety hook, and an MCP server connection to your Kubernetes cluster.


Plugins vs Standalone Configuration

Claude Code supports two ways to extend it:

ApproachSkill namesBest for
Standalone (.claude/ directory)/deployOne project, personal workflows, quick experiments
Plugins (separate directory with manifest)/devops-helper:deploySharing with team, multiple projects, versioned releases, marketplace distribution

Start with standalone when you are iterating fast on a single project. Convert to a plugin when you want to share the same setup across projects or with teammates — or publish to the Anthropic marketplace.

The main trade-off is skill namespacing. Plugin skills are always prefixed with the plugin name (/devops-helper:deploy) to prevent conflicts when multiple plugins are installed. Standalone skills have no prefix (/deploy).


Plugin Structure

Every plugin is a directory with this layout:

devops-helper/
├── .claude-plugin/
│   └── plugin.json          ← manifest (only file that goes in .claude-plugin/)
├── skills/
│   └── deploy/
│       └── SKILL.md         ← skill definition
├── agents/
│   └── incident-responder.md
├── hooks/
│   └── hooks.json           ← event hooks
├── .mcp.json                ← MCP server config
├── monitors/
│   └── monitors.json        ← background monitors
├── bin/                     ← executables added to PATH
├── settings.json            ← default settings applied when plugin is enabled
└── README.md

The critical rule: only plugin.json goes inside .claude-plugin/. Everything else — skills, agents, hooks — sits at the plugin root. This is the most common structural mistake.

What each component does

ComponentWhat it is
skills/Skills Claude invokes automatically or you call with /plugin:skill-name
agents/Subagent definitions for specialised parallel tasks
hooks/Shell scripts that fire on lifecycle events (PreToolUse, PostToolUse, etc.)
.mcp.jsonMCP server config connecting Claude to external APIs and databases
monitors/Background watchers (log tails, file watchers) that notify Claude as events arrive
bin/Executables added to the Bash tool’s PATH during the session
settings.jsonDefault settings applied when the plugin is enabled

The Plugin Manifest

The manifest at .claude-plugin/plugin.json is the only required file:

{
  "name": "devops-helper",
  "description": "Deployment, Kubernetes, and incident-response tools for DevOps teams",
  "version": "1.0.0",
  "author": {
    "name": "Your Name",
    "email": "you@company.com"
  },
  "homepage": "https://github.com/your-org/devops-helper",
  "repository": "https://github.com/your-org/devops-helper",
  "license": "MIT"
}
FieldPurpose
namePlugin identifier and skill namespace prefix
descriptionShown in the plugin manager when browsing
versionBump to trigger updates for installers. Omit to use git SHA
authorAttribution — useful for marketplace listings
homepage / repositoryLinks shown in the plugin manager

Building a Real Plugin: DevOps Helper

Let’s build a plugin that DevOps teams can install across all their projects. It will include:

  1. A deploy skill — runs deployment with pre-flight checks
  2. A pre-deploy hook — blocks deploys to production without explicit confirmation
  3. A Kubernetes MCP server — gives Claude live cluster access
  4. A background log monitor — streams app errors to Claude automatically

Step 1: Create the structure

mkdir -p devops-helper/.claude-plugin
mkdir -p devops-helper/skills/deploy
mkdir -p devops-helper/skills/rollback
mkdir -p devops-helper/skills/incident-summary
mkdir -p devops-helper/hooks
mkdir -p devops-helper/agents
mkdir -p devops-helper/monitors

Step 2: Write the manifest

{
  "name": "devops-helper",
  "description": "Deployment safety, Kubernetes access, and incident response for DevOps teams",
  "version": "1.0.0",
  "author": {
    "name": "DevOps Team"
  }
}

Step 3: Create the deploy skill

<!-- devops-helper/skills/deploy/SKILL.md -->
---
description: >
  Runs a deployment with pre-flight checks and rollback instructions.
  Auto-invokes when the user asks to deploy, release, or push to production.
user-invocable: true
allowed-tools:
  - Bash
  - Read
---

# Deploy Skill

When the user asks to deploy, follow this sequence:

## 1. Read deployment config
Read `deploy.yaml` or `Makefile` to understand the deployment command. If neither exists, ask the user.

## 2. Pre-flight checks
Run these before any deploy:
```bash
# Check current branch
git branch --show-current

# Check for uncommitted changes
git status --porcelain

# Run tests
npm test --silent 2>&1 | tail -10

If tests fail or there are uncommitted changes, stop and report. Do not deploy.

3. Identify target environment

Parse $ARGUMENTS for the environment name (staging, production, etc.).

  • Default to staging if none specified
  • If production is specified, ask the user to confirm: “Deploying to production. Type YES to confirm.”

4. Deploy

Run the deployment command. Stream output. Capture exit code.

5. Verify

After deploy, verify the service is healthy:

# Check rollout status (Kubernetes)
kubectl rollout status deployment/$SERVICE_NAME -n $NAMESPACE --timeout=120s

# Or check the health endpoint
curl -sf $HEALTH_URL || echo "Health check failed"

6. Report

Summarise: what was deployed, to which environment, any warnings, and the health check result. If deploy failed: run the rollback skill immediately and report what happened.


### Step 4: Create the rollback skill

```markdown
<!-- devops-helper/skills/rollback/SKILL.md -->
---
description: Rolls back the last deployment. Use when a deploy fails or causes issues.
user-invocable: true
allowed-tools:
  - Bash
---

# Rollback Skill

## Steps

1. Identify the last stable deployment:
```bash
kubectl rollout history deployment/$SERVICE_NAME -n $NAMESPACE
  1. Roll back:
kubectl rollout undo deployment/$SERVICE_NAME -n $NAMESPACE
  1. Wait and verify:
kubectl rollout status deployment/$SERVICE_NAME -n $NAMESPACE --timeout=60s
  1. Report: whether rollback succeeded, which revision is now active, and recommend investigating what caused the failed deploy.

### Step 5: Create the incident summary skill

```markdown
<!-- devops-helper/skills/incident-summary/SKILL.md -->
---
description: >
  Generates an incident summary from logs and recent changes.
  Auto-invokes when the user mentions an incident, outage, or asks what went wrong.
user-invocable: true
allowed-tools:
  - Bash
  - Read
---

# Incident Summary Skill

Generate a structured incident summary. Collect:

1. **Timeline**: Run `git log --since="6 hours ago" --oneline` — what changed recently?
2. **Errors**: Read recent error logs from `logs/error.log` or run `kubectl logs --tail=100 -l app=$SERVICE`
3. **Impact**: Ask the user to describe impact if not obvious from logs

Write a summary in this format:

Incident Summary — [DATE]

Status: Ongoing / Resolved

Impact: [What broke, how many users affected]

Timeline:

  • HH:MM — [event]
  • HH:MM — [event]

Root cause: [One sentence]

Fix applied: [What was done]

Action items:

  • [Follow-up task]

Keep it factual, under 300 words, suitable for a post-mortem or Slack channel update.

Step 6: Add a pre-deploy safety hook

The hook blocks deploys to production unless the user explicitly confirms. It intercepts the Bash tool and checks if the command matches a deploy pattern targeting production:

// devops-helper/hooks/hooks.json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "devops-helper/hooks/pre-deploy-check.sh"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "devops-helper/hooks/log-commands.sh"
          }
        ]
      }
    ]
  }
}
#!/bin/bash
# devops-helper/hooks/pre-deploy-check.sh
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // ""')

# Check for production deploy patterns
PROD_PATTERNS="prod|production|--env=prod"

if echo "$cmd" | grep -qiE "(deploy|release|kubectl apply|helm upgrade)" && \
   echo "$cmd" | grep -qiE "$PROD_PATTERNS"; then

  jq -n '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "ask",
      permissionDecisionReason: "Production deployment detected. Confirm this is intentional before proceeding."
    }
  }'
  exit 0
fi

exit 0
#!/bin/bash
# devops-helper/hooks/log-commands.sh
# Append every Bash command to an audit log
input=$(cat)
cmd=$(echo "$input" | jq -r '.tool_input.command // ""')
ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")

echo "[$ts] $cmd" >> "${AUDIT_LOG:-/tmp/claude-commands.log}"
exit 0

Step 7: Add a Kubernetes MCP server

The .mcp.json file at the plugin root connects Claude to your cluster:

{
  "mcpServers": {
    "kubernetes": {
      "command": "npx",
      "args": ["-y", "@anthropic/mcp-server-kubernetes"],
      "env": {
        "KUBECONFIG": "${KUBECONFIG:-~/.kube/config}"
      }
    },
    "pagerduty": {
      "command": "npx",
      "args": ["-y", "@anthropic/mcp-server-pagerduty"],
      "env": {
        "PAGERDUTY_API_KEY": "${PAGERDUTY_API_KEY}"
      }
    }
  }
}

With this in place, Claude can run kubectl commands, read pod logs, and check deployment status via MCP tools — without you manually setting up shell access.

Step 8: Add a background error log monitor

The monitor streams new error log lines to Claude during the session:

// devops-helper/monitors/monitors.json
[
  {
    "name": "app-errors",
    "command": "tail -F logs/error.log 2>/dev/null || echo 'No error.log found'",
    "description": "Application error log — new lines are delivered to Claude automatically"
  }
]

Claude receives each new log line as a notification, letting it flag new errors without you having to ask.

Final plugin structure

devops-helper/
├── .claude-plugin/
│   └── plugin.json
├── skills/
│   ├── deploy/
│   │   └── SKILL.md
│   ├── rollback/
│   │   └── SKILL.md
│   └── incident-summary/
│       └── SKILL.md
├── hooks/
│   ├── hooks.json
│   ├── pre-deploy-check.sh
│   └── log-commands.sh
├── monitors/
│   └── monitors.json
├── .mcp.json
└── README.md

Testing Your Plugin

Test locally without installing using --plugin-dir:

# Load from directory
claude --plugin-dir ./devops-helper

# Load from a zip archive (Claude Code v2.1.128+)
claude --plugin-dir ./devops-helper.zip

# Load multiple plugins at once
claude --plugin-dir ./devops-helper --plugin-dir ./other-plugin

Inside the session, verify everything loaded:

# List skills
/help

# Test the deploy skill
/devops-helper:deploy staging

# Test the rollback skill
/devops-helper:rollback

# Check agents are visible
/agents

After making changes, reload without restarting:

/reload-plugins

If something does not work, check the structure first — the most common issue is placing skills/ or hooks/ inside .claude-plugin/ instead of the plugin root.


Installing Plugins

Once the plugin is in a git repository, anyone can install it:

# From GitHub
claude plugin add github:your-org/devops-helper

# From the official Anthropic marketplace
claude plugin add @anthropic/deploy-helper

# Project-scoped install (only active in this repo)
claude plugin add github:your-org/devops-helper --project

Managing installed plugins:

claude plugin list                              # see what's installed
claude plugin update github:your-org/devops-helper
claude plugin update --all                      # update everything
claude plugin remove github:your-org/devops-helper

Or use the in-app plugin manager:

/plugin

The /plugin command opens a browser with Discover, Installed, and Updates tabs.


Converting Existing Config to a Plugin

If you have skills and hooks in .claude/ already, migration is straightforward:

# 1. Create plugin structure
mkdir -p my-plugin/.claude-plugin

# 2. Write manifest
cat > my-plugin/.claude-plugin/plugin.json << 'EOF'
{
  "name": "my-plugin",
  "description": "Migrated from standalone .claude/ config",
  "version": "1.0.0"
}
EOF

# 3. Copy existing components
cp -r .claude/skills  my-plugin/
cp -r .claude/agents  my-plugin/
cp -r .claude/commands my-plugin/

# 4. Migrate hooks
mkdir my-plugin/hooks
# Copy the hooks object from .claude/settings.json into my-plugin/hooks/hooks.json

What changes after migration:

Before (standalone)After (plugin)
/deploy/my-plugin:deploy
Copy files per projectclaude plugin add github:you/my-plugin
Manual updatesclaude plugin update --all
Only in one projectAvailable everywhere

Publishing to the Anthropic Marketplace

When your plugin is ready to share publicly:

  1. Push to a public GitHub repository
  2. Add a README.md with installation instructions and a usage guide
  3. Submit via the in-app form:
    • Claude.ai: Settings → Plugins → Submit
    • Platform console: platform.claude.com/plugins/submit

Once listed, users can install with /plugin install your-plugin-name or claude plugin add @your-org/your-plugin.

For team-internal distribution, host the plugin in a private GitHub repo and configure the team marketplace URL in project settings — no public listing required.


When to Build a Plugin

flowchart TD
    Q1{Do you need this\nacross multiple projects?} -->|Yes| Q2{Share with\nothers?}
    Q1 -->|No| Standalone[Standalone .claude/ config]
    Q2 -->|Yes| Plugin[Build a plugin]
    Q2 -->|No| Q3{More than one\nproject?}
    Q3 -->|Yes| Plugin
    Q3 -->|No| Standalone

Build a plugin when:

  • You want the same skills and hooks in every project without copying files
  • You are building tools for a team and want them to install with one command
  • You want version control over your tooling (bump version to push updates)
  • You want to publish to the Anthropic marketplace for the community

Stick with standalone when:

  • The configuration is specific to one project and will never be reused
  • You are experimenting and not sure if the skill is worth keeping
  • You want short skill names without namespace prefixes

The practical path: build standalone first, convert to a plugin once the skill proves its value.


Plugin Ideas to Build Next

PluginWhat it does
git-helperPR description writer, commit message generator, branch namer
test-enforcerAuto-run tests on edit, enforce coverage thresholds, generate missing tests
security-scannerIDOR checker, injection auditor, pre-commit security review
doc-writerGenerate README, API docs, changelogs from code
infra-opsTerraform plan reviewer, cost estimator, drift detector
incident-botPost-mortem template filler, on-call summary, PagerDuty integration

Each of these follows the same pattern: a few skill files, an optional hook, and an MCP server connection if external data is needed.


GitHub Actions Bot: Automated @claude Integration

The @claude bot on GitHub allows you to tag Claude directly in pull requests and issues. The bot runs in GitHub Actions, reads full context, and either comments with analysis or opens commits and PRs with fixes.

What @claude Can Do

Tag @claude in any PR comment, issue comment, or review comment:

@claude please review this PR for security issues

The bot:

  1. Reads context — PR diff, issue description, conversation history
  2. Runs Claude — in a GitHub Actions environment with full tool access
  3. Responds — posts findings as a comment, or opens a PR with fixes

Examples of @claude Usage

Code review:

@claude review this PR for bugs and security issues

Bug fix:

@claude this test is failing — can you fix it and push the fix?

Feature implementation:

@claude look at issue #234 and implement the described feature

Documentation:

@claude generate API documentation for this module

Performance analysis:

@claude analyze this code for performance issues and suggest optimizations

Installation

Option 1: One-Command Setup (Easiest)

From within a Claude Code session in your repo:

/install-github-app

This:

  1. Installs the Claude GitHub App to your repository
  2. Creates a PR containing .github/workflows/claude.yml
  3. Prompts you to add ANTHROPIC_API_KEY as a GitHub Secret
  4. Merges the PR — setup is complete

Option 2: Manual Setup

  1. Go to github.com/apps/claude and install the app
  2. Create .github/workflows/claude.yml manually:
name: Claude Bot
on:
  issue_comment:
    types: [created, edited]
  pull_request_review_comment:
    types: [created, edited]

jobs:
  claude:
    if: contains(github.event.comment.body, '@claude')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Claude Bot
        run: |
          claude -p "$(cat <<'PROMPT'
          You are the Claude bot on GitHub. Read the issue/PR context and the user's request.
          
          User request: ${{ github.event.comment.body }}
          
          Respond with:
          - Analysis of the problem
          - Specific recommendations or fixes
          - Any follow-up questions
          PROMPT
          )" \
            --max-budget-usd 5.00          
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
      
      - name: Post Comment
        uses: actions/github-script@v6
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: 'Claude review complete. See output above.'
            })            
  1. Add ANTHROPIC_API_KEY as a repository secret:
    • Go to Settings → Secrets and variables → Actions
    • Click New repository secret
    • Name: ANTHROPIC_API_KEY
    • Value: your API key

How @claude Works in Practice

PR Review Workflow:

Developer: Opens PR #42

@claude please review for security issues

GitHub Actions triggers → Claude runs in isolated environment → Claude reads PR diff → Claude posts findings as comment

Example bot response:

## Claude Security Review

### Findings

**Critical Issues:**
- [src/auth.ts:45] — SQL injection vulnerability in query construction. Use parameterised queries instead of string concatenation.
- [src/api.ts:123] — API key logged to console in error handler. Remove before production.

**Warnings:**
- [src/db.ts:67] — Password hashing algorithm is weak. Upgrade to bcrypt with 12+ rounds.

**Passing checks:**
✓ No hardcoded secrets
✓ CORS properly configured
✓ Input validation on all endpoints

### Next steps
1. Fix the SQL injection immediately
2. Remove the API key logging
3. Update password hashing

Running @claude Automatically on Every PR

Create a workflow that runs without needing to tag @claude:

name: Automatic Claude Review
on:
  pull_request:
    types: [opened, synchronize]

jobs:
  claude-review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Review PR
        run: |
          claude -p "Review this PR diff for:
          1. Bugs and logic errors
          2. Security vulnerabilities
          3. Performance issues
          4. Best practice violations
          
          Be specific with file:line references." \
            --max-budget-usd 5.00 > review-output.txt          
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
      
      - name: Comment Review
        uses: actions/github-script@v6
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const fs = require('fs');
            const review = fs.readFileSync('review-output.txt', 'utf8');
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: review
            })            

Bot with Auto-Fixes

For simple issues, the bot can open a PR with fixes:

name: Claude Auto-Fix
on:
  issue_comment:
    types: [created]

jobs:
  auto-fix:
    if: contains(github.event.comment.body, '@claude fix')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
      
      - name: Create Fix Branch
        run: git checkout -b claude-fix-${{ github.event.issue.number }}
      
      - name: Run Claude
        run: |
          claude -p "Fix the issue described. Make minimal, focused changes." \
            --max-budget-usd 3.00          
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
      
      - name: Commit and Push
        run: |
          git add -A
          git commit -m "Claude auto-fix for issue #${{ github.event.issue.number }}"
          git push origin claude-fix-${{ github.event.issue.number }}          
      
      - name: Open PR
        uses: actions/github-script@v6
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            github.rest.pulls.create({
              owner: context.repo.owner,
              repo: context.repo.repo,
              title: 'Claude auto-fix for issue #${{ github.event.issue.number }}',
              body: 'Automated fix generated by Claude bot',
              head: 'claude-fix-${{ github.event.issue.number }}',
              base: 'main'
            })            

Limiting Bot Costs

Each run can cost $0.50 to $5 depending on PR size. Control costs with:

- name: Claude Review
  run: |
    claude -p "..." \
      --max-budget-usd 2.00  # stops if cost exceeds $2    

Or disable the bot for large PRs:

jobs:
  claude-review:
    if: github.event.pull_request.additions < 500  # only run if < 500 lines added
    runs-on: ubuntu-latest

Bot Permissions and Limits

The bot runs with:

  • GitHub token — reads issues, PRs, opens PRs, posts comments
  • Anthropic API key — makes Claude requests
  • Read access — can read all files in the repo
  • Write access — can create commits and PRs on claude-* branches

The bot cannot:

  • Force-push
  • Delete branches
  • Modify GitHub Actions workflows (security protection)
  • Access secrets (only ANTHROPIC_API_KEY and GITHUB_TOKEN)

Troubleshooting @claude Bot

Bot doesn’t respond:

  • Check that ANTHROPIC_API_KEY is set in Settings → Secrets
  • Verify the app is installed: Settings → Installed GitHub Apps
  • Check workflow logs: Actions tab → Claude Bot workflow

Bot makes unwanted changes:

  • Use branch protection: Settings → Branches → add rule for claude-* to require PR review
  • Limit bot access: disable auto-fix workflows, require manual approval

High costs:

  • Set --max-budget-usd to a reasonable limit ($2–$5 per run)
  • Run on PRs < 500 lines only: add if: condition
  • Schedule runs for critical reviews only, not every PR

Abhay

Abhay Pratap Singh

DevOps Engineer passionate about automation, cloud infrastructure, and self-hosted tools. I write about Kubernetes, Terraform, DNS, and everything in between.