# Plugins & marketplaces in Claude Code

Bundle the ticket-service skill, hooks, reviewer subagent and GitHub MCP server as the ticket-service-kit plugin, publish it in a Git marketplace, and install it for the team.

Source: https://ai-sw-factory.mellicci.dev/fundamentals/plugins/claude-code

## At a glance

| | Claude Code |
|---|---|
| Term | Plugin, distributed through a marketplace |
| Configured in | Plugin: directory with optional `.claude-plugin/plugin.json`. Marketplace: `.claude-plugin/marketplace.json`. Enabled in `enabledPlugins`, marketplaces in `extraKnownMarketplaces` (settings files) |
| Loads / runs | Installed files are copied to a cache; the plugin loads at session start or on `/reload-plugins`. Its hooks fire and its MCP servers connect in every session where it is enabled |
| Scope & precedence | User, project or local install scope; local overrides project, project overrides user. Managed settings override all scopes |

## Build the scenario

| Step | What you write or run | Where |
|---|---|---|
| 1 | Lay out the plugin and write the manifest | `acme/agent-marketplace`, `plugins/ticket-service-kit/` |
| 2 | Write `marketplace.json` listing it | `.claude-plugin/marketplace.json` |
| 3 | Validate and test locally | `claude plugin validate`, `claude --plugin-dir` |
| 4 | Declare marketplace and plugin for the team | `ticket-service/.claude/settings.json` |
| 5 | Each developer adds, installs, and later updates | Terminal or `/plugin` |

```text
ticket-service-kit/
├── .claude-plugin/plugin.json
├── skills/add-migration/
│   ├── SKILL.md
│   ├── scripts/check-migration.sh
│   └── references/drizzle-conventions.md
├── agents/reviewer.md
├── hooks/
│   └── hooks.json
├── scripts/
│   ├── format.sh
│   ├── guard.sh
│   └── log.sh
└── .mcp.json
```

Only `.claude-plugin/` holds the manifest; every other folder sits at the plugin root. `.claude-plugin/plugin.json`:

```json
{
  "name": "ticket-service-kit",
  "version": "1.0.0",
  "description": "Migration skill, guard hooks and reviewer for ticket-service",
  "author": { "name": "Acme Platform Team" }
}
```

`hooks/hooks.json` has the same shape as the `hooks` key in `settings.json`; `${CLAUDE_PLUGIN_ROOT}` is the installed plugin's absolute path:

```json
{ "hooks": {
  "PostToolUse": [{ "matcher": "Edit|Write", "hooks": [
    { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/format.sh\"" }] }],
  "PreToolUse": [
    { "matcher": "Bash|Edit|Write", "hooks": [
      { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/guard.sh\"" }] },
    { "hooks": [
      { "type": "command", "command": "\"${CLAUDE_PLUGIN_ROOT}/scripts/log.sh\"" }] }]
} }
```

`.mcp.json` takes the shape of a project `.mcp.json`; it is the GitHub server from the [MCP page](https://ai-sw-factory.mellicci.dev/fundamentals/mcp/claude-code) (`"github": { "type": "http", "url": "https://api.githubcopilot.com/mcp/", "headers": { "Authorization": "Bearer ${GITHUB_MCP_TOKEN}" } }` under `mcpServers`). Its tools are named `mcp__plugin_ticket-service-kit_github__<tool>`, so use that prefix in the `reviewer`'s `tools` line. In `SKILL.md`, call the checker as `${CLAUDE_PLUGIN_ROOT}/skills/add-migration/scripts/check-migration.sh`; Claude Code substitutes the path in skill and agent bodies. The skill runs as `/ticket-service-kit:add-migration`, the subagent is `ticket-service-kit:reviewer`.

`.claude-plugin/marketplace.json` in `acme/agent-marketplace`:

```json
{
  "name": "acme-marketplace",
  "owner": { "name": "Acme Platform Team" },
  "plugins": [
    {
      "name": "ticket-service-kit",
      "source": "./plugins/ticket-service-kit",
      "description": "Factory kit for ticket-service"
    }
  ]
}
```

Developers install it, then update after you bump `version` to `1.1.0` and push:

```bash
claude plugin marketplace add acme/agent-marketplace
claude plugin install ticket-service-kit@acme-marketplace --scope project
claude plugin marketplace update acme-marketplace
claude plugin update ticket-service-kit@acme-marketplace   # then /reload-plugins
```

To make this the team default, commit to `ticket-service/.claude/settings.json`:

```json
{
  "extraKnownMarketplaces": {
    "acme-marketplace": { "source": { "source": "github", "repo": "acme/agent-marketplace" }, "autoUpdate": true }
  },
  "enabledPlugins": { "ticket-service-kit@acme-marketplace": true }
}
```

## Specifics

### Plugin layout and manifest

The manifest is optional; without it, Claude Code loads what it finds in the standard layout and takes the name from the marketplace entry or directory. Only `name` is required.

| Field | Purpose |
|---|---|
| `name` | Required; kebab-case; namespaces every component (`ticket-service-kit:reviewer`) |
| `version` | Optional; if set, users stay on it until you change the string |
| `description`, `author`, `homepage`, `license`, `keywords` | Metadata |
| `displayName`, `defaultEnabled` | UI name; whether it starts enabled (default `true`) |
| `dependencies`, `userConfig` | Plugins required; values Claude Code prompts for (`sensitive: true` goes to secure storage) |
| `skills`, `agents`, `hooks`, `mcpServers`, … | Paths or inline config outside default locations |

Run `claude plugin validate <path>` before publishing; an unknown top-level key is stripped with a warning.

### What a plugin can contain

| Component | Default location | Note |
|---|---|---|
| Skills | `skills/<name>/SKILL.md` | Command is `/<plugin>:<name>` |
| Commands | `commands/*.md` | Flat files; new plugins should prefer skills |
| Agents | `agents/*.md` | Ignore `hooks`, `mcpServers`, `permissionMode`, `initialPrompt` in frontmatter |
| Hooks | `hooks/hooks.json` | Register at plugin load, not when a skill runs |
| MCP servers | `.mcp.json` | Shown as `plugin:<plugin>:<server>` in `/mcp` |
| LSP servers, output styles, workflows, themes, monitors | `.lsp.json`, `output-styles/`, `workflows/`, `themes/`, `monitors/monitors.json` | |
| Executables, default settings | `bin/` (on the Bash tool's `PATH`), `settings.json` (`agent`, `subagentStatusLine` only) | |

A `CLAUDE.md` at the plugin root is not loaded; put instructions in a skill.

### Marketplaces

| Marketplace source | You type |
|---|---|
| GitHub | `owner/repo`, optional `#ref` |
| Git URL | `https://host/x.git#ref` or `git@host:path` |
| Local path | `./dir` or a `marketplace.json` file |
| Hosted file | `https://…/marketplace.json` |

Required fields are `name`, `owner` and `plugins`; an entry needs `name` and `source`. Keep the entry name equal to the manifest `name`.

| Plugin `source` | Use when |
|---|---|
| Relative path (`./plugins/x`) | Plugin lives in the marketplace repo |
| `github`, `url` | Plugin has its own repository; optional `ref`, `sha` |
| `git-subdir` | Plugin is a folder in another repo |
| `npm`, `archive`, `command` | Package, HTTPS zip (`sha256`), or a command's output |

### Install, update and scope

| Scope | Recorded in | Who gets it |
|---|---|---|
| User (default) | `~/.claude/settings.json` | You, all projects |
| Project | `.claude/settings.json` | Everyone in the repo |
| Local | `.claude/settings.local.json` | You, this repo |

In a session, `/plugin install name@marketplace` opens the details pane to choose a scope. Committed `enabledPlugins` entries whose plugin comes from a relative path load from the marketplace copy; for external sources each contributor runs `claude plugin install … --scope project` once. Updates are detected by version: manifest `version`, else entry `version`, else the commit SHA. Auto-update is off for third-party marketplaces unless you set `autoUpdate`; an update applies on the next session or after `/reload-plugins`.

### Trust and management

| Topic | Behavior |
|---|---|
| Trust | Plugins run hooks and MCP servers with your user permissions, outside the sandbox. `/plugin` shows a trust warning and a **Will install** list; read `hooks/hooks.json`, `.mcp.json` and `bin/` first |
| Project settings | `extraKnownMarketplaces` from a repository apply only after the workspace trust dialog is accepted |
| Managed | `strictKnownMarketplaces` (allowlist), `blockedMarketplaces`, managed `enabledPlugins` (force-enable or block) |
| UI and shell | `/plugin` tabs Discover, Installed, Marketplaces, Errors; `claude plugin list\|enable\|disable\|uninstall\|details` |
| Local testing | `claude --plugin-dir ./ticket-service-kit` loads it for one session (shown as `@inline`) |
| Evals | `claude plugin eval` runs eval cases against the plugin and, by default, a no-plugin baseline |

## Gotchas

- A manifest `version` pins users: pushing commits without changing `"1.0.0"` ships nothing. Leave `version` out to track commits instead.
- Committing `enabledPlugins` does not install anything for a plugin with an external source, and the marketplace entry is ignored until the teammate trusts the folder.
- Plugin hooks and MCP servers are active in every session where the plugin is enabled. The GitHub server connects whenever the plugin is on; the documentation doesn't describe marking one server optional, but `userConfig` can collect the token.
- The plugin's MCP tools are named `mcp__plugin_<plugin>_<server>__<tool>`, not `mcp__github__…`. The `reviewer`'s `tools` line and hook matchers must use that form.
- Hooks run with `${CLAUDE_PLUGIN_ROOT}`, which changes on update: never write state there. Use `${CLAUDE_PLUGIN_DATA}`.
