503 lines
12 KiB
Markdown
503 lines
12 KiB
Markdown
# Plugin Structure Reference
|
|
|
|
Complete directory layout and configuration schemas for Claude Code plugins.
|
|
|
|
## Plugin Structure
|
|
|
|
How you structure plugins depends on how they're distributed.
|
|
|
|
### Single Plugin (Standalone)
|
|
|
|
Standalone plugins need their own `.claude-plugin/plugin.json`:
|
|
|
|
```
|
|
my-plugin/
|
|
├── .claude-plugin/
|
|
│ └── plugin.json # Plugin manifest (required)
|
|
├── commands/
|
|
├── agents/
|
|
├── hooks/
|
|
│ └── hooks.json # Auto-discovered hooks (or inline in plugin.json)
|
|
└── README.md
|
|
```
|
|
|
|
### Marketplace with Local Plugins (Consolidated)
|
|
|
|
When all plugins live in the same repo as the marketplace, use `strict: false` to consolidate metadata in `marketplace.json`. Plugins don't need their own manifests:
|
|
|
|
```
|
|
my-marketplace/
|
|
├── .claude-plugin/
|
|
│ └── marketplace.json # All metadata here (strict: false)
|
|
├── plugin-a/
|
|
│ ├── commands/
|
|
│ ├── agents/
|
|
│ └── README.md
|
|
├── plugin-b/
|
|
│ ├── skills/
|
|
│ └── README.md
|
|
└── README.md
|
|
```
|
|
|
|
**Benefits:** Single source of truth, no version drift, simpler structure.
|
|
|
|
### Marketplace with External Plugins (Distributed)
|
|
|
|
When referencing plugins from external repos (GitHub, GitLab, etc.), let each plugin own its manifest:
|
|
|
|
```
|
|
my-marketplace/
|
|
├── .claude-plugin/
|
|
│ └── marketplace.json # Points to external repos
|
|
└── README.md
|
|
|
|
# External repos each have:
|
|
external-plugin/
|
|
├── .claude-plugin/
|
|
│ └── plugin.json # Plugin owns its manifest
|
|
├── commands/
|
|
└── README.md
|
|
```
|
|
|
|
**Key principle:** External plugins are self-contained. The marketplace.json points to them via `source` but doesn't define their metadata.
|
|
|
|
### Mixed Approach
|
|
|
|
Marketplaces can combine both patterns—consolidated for local plugins, distributed for external:
|
|
|
|
```json
|
|
{
|
|
"strict": false,
|
|
"plugins": [
|
|
{"name": "local-plugin", "source": "./local-plugin", "version": "1.0.0"},
|
|
{"name": "external-plugin", "source": {"source": "github", "repo": "owner/plugin"}}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Directory Structure
|
|
|
|
### Minimal Standalone Plugin
|
|
|
|
```
|
|
my-plugin/
|
|
├── .claude-plugin/
|
|
│ └── plugin.json # Required: metadata
|
|
└── README.md # Required for distribution
|
|
```
|
|
|
|
### Complete Standalone Plugin
|
|
|
|
```
|
|
my-plugin/
|
|
├── .claude-plugin/
|
|
│ └── plugin.json # Plugin metadata
|
|
├── README.md # Documentation
|
|
├── CHANGELOG.md # Version history
|
|
├── LICENSE # License file
|
|
├── .gitignore # Git ignore patterns
|
|
├── commands/ # Slash commands
|
|
│ ├── core/ # Core commands
|
|
│ │ └── help.md
|
|
│ └── advanced/ # Advanced features
|
|
│ └── deploy.md
|
|
├── agents/ # Custom agents
|
|
│ ├── reviewer.md
|
|
│ └── analyzer.md
|
|
├── skills/ # Reusable skills
|
|
│ └── my-skill/
|
|
│ └── SKILL.md
|
|
├── hooks/ # Event hooks
|
|
│ └── hooks.json # Auto-discovered (required format)
|
|
├── servers/ # MCP servers
|
|
│ └── my-server/
|
|
│ ├── server.py
|
|
│ └── pyproject.toml
|
|
└── scripts/ # Utility scripts
|
|
└── setup.sh
|
|
```
|
|
|
|
## plugin.json Schema
|
|
|
|
### Required Fields
|
|
|
|
| Field | Type | Description | Example |
|
|
|-------|------|-------------|---------|
|
|
| `name` | string | Unique identifier (kebab-case, no spaces) | `"deployment-tools"` |
|
|
|
|
### Recommended Metadata Fields
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `version` | string | Semantic version (e.g., "1.0.0") |
|
|
| `description` | string | Brief plugin description |
|
|
|
|
### Optional Standard Fields
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `author` | object | Creator information |
|
|
| `author.name` | string | Author name |
|
|
| `author.email` | string | Author email |
|
|
| `homepage` | string | Documentation URL |
|
|
| `repository` | string | Source code URL |
|
|
| `license` | string | SPDX identifier (MIT, Apache-2.0) |
|
|
| `keywords` | array | Search tags |
|
|
| `category` | string | Plugin category |
|
|
| `tags` | array | Additional searchability tags |
|
|
|
|
### Component Configuration Fields
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `commands` | string\|array | Custom paths to command files or directories |
|
|
| `agents` | string\|array | Custom paths to agent files |
|
|
| `hooks` | string\|object | Hook config path or inline config |
|
|
| `mcpServers` | string\|object | MCP config path or inline config |
|
|
| `lspServers` | string\|object | LSP config path or inline config |
|
|
|
|
### Behavior Control
|
|
|
|
| Field | Type | Default | Description |
|
|
|-------|------|---------|-------------|
|
|
| `strict` | boolean | `true` | Set at marketplace level. When `false`, local plugins don't need `.claude-plugin/plugin.json`—marketplace defines all metadata. Use `false` for consolidated local plugins, `true` (or omit) for external plugins. |
|
|
|
|
> **Note:** Hooks can be defined inline in plugin.json OR in a separate `hooks/hooks.json` file.
|
|
|
|
### Complete Example
|
|
|
|
```json
|
|
{
|
|
"name": "enterprise-tools",
|
|
"version": "2.1.0",
|
|
"description": "Enterprise workflow automation tools",
|
|
"author": {
|
|
"name": "Enterprise Team",
|
|
"email": "team@company.com"
|
|
},
|
|
"homepage": "https://docs.company.com/plugins",
|
|
"repository": "https://github.com/company/enterprise-tools",
|
|
"license": "MIT",
|
|
"keywords": ["enterprise", "workflow", "automation"],
|
|
"category": "productivity",
|
|
"commands": [
|
|
"./commands/core/",
|
|
"./commands/enterprise/"
|
|
],
|
|
"agents": [
|
|
"./agents/security-reviewer.md",
|
|
"./agents/compliance-checker.md"
|
|
],
|
|
"mcpServers": {
|
|
"database": {
|
|
"command": "${CLAUDE_PLUGIN_ROOT}/servers/db-server",
|
|
"args": ["--config", "${CLAUDE_PLUGIN_ROOT}/config.json"],
|
|
"env": {
|
|
"DB_HOST": "${DATABASE_HOST}",
|
|
"DB_PASSWORD": "${DATABASE_PASSWORD}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
> **Note:** Hooks can be inlined in plugin.json (add a `"hooks"` key) or defined in `hooks/hooks.json`. See [Event Hooks](#event-hooks) below.
|
|
|
|
## Slash Commands
|
|
|
|
### Command File Structure
|
|
|
|
Commands are markdown files with YAML frontmatter in `commands/`.
|
|
|
|
```markdown
|
|
---
|
|
description: "Brief description shown in /help"
|
|
---
|
|
|
|
Command instructions here.
|
|
Use {{0}}, {{1}} for parameters.
|
|
```
|
|
|
|
### Parameter Syntax
|
|
|
|
| Syntax | Description | Example |
|
|
|--------|-------------|---------|
|
|
| `{{0}}` | First parameter | `/cmd value` |
|
|
| `{{1}}` | Second parameter | `/cmd val1 val2` |
|
|
| `{{0:name}}` | Named (documentation) | `{{0:environment}}` |
|
|
| `{{...}}` | All remaining | `/cmd arg1 arg2 arg3` |
|
|
|
|
### Example Command
|
|
|
|
```markdown
|
|
---
|
|
description: "Deploy to specified environment"
|
|
---
|
|
|
|
Deploy application to {{0:environment}}.
|
|
|
|
Steps:
|
|
1. Validate environment configuration
|
|
2. Run pre-deployment checks
|
|
3. Deploy application
|
|
4. Verify deployment
|
|
```
|
|
|
|
## Custom Agents
|
|
|
|
### Agent File Structure
|
|
|
|
Agents are markdown files with YAML frontmatter in `agents/`.
|
|
|
|
```markdown
|
|
---
|
|
description: "What this agent specializes in"
|
|
capabilities: ["task1", "task2", "task3"]
|
|
allowed-tools: Read, Grep, Glob
|
|
---
|
|
|
|
# Agent Name
|
|
|
|
Detailed description of the agent's role, expertise, and when Claude should invoke it.
|
|
|
|
## Capabilities
|
|
|
|
- Specific task the agent excels at
|
|
- Another specialized capability
|
|
- When to use this agent vs others
|
|
|
|
## Context and examples
|
|
|
|
Provide examples of when this agent should be used.
|
|
```
|
|
|
|
### Agent Frontmatter Fields
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `description` | string | Brief explanation of what the agent does |
|
|
| `capabilities` | array | List of tasks the agent can perform (aids discovery) |
|
|
| `allowed-tools` | string | Comma-separated list of allowed tools (optional) |
|
|
|
|
### Tool Restrictions
|
|
|
|
| Restriction | Tools | Use Case |
|
|
|-------------|-------|----------|
|
|
| Read-only | `Read, Grep, Glob` | Analysis only |
|
|
| With execution | `Read, Grep, Glob, Bash` | Analysis + commands |
|
|
| No restriction | (omit field) | Full capabilities |
|
|
|
|
## Event Hooks
|
|
|
|
Two ways to define hooks in a plugin:
|
|
|
|
1. **Inline in plugin.json** — Add a `"hooks"` key directly
|
|
2. **File-based** — Auto-discovered from `hooks/hooks.json`
|
|
|
|
### Option 1: Inline in plugin.json
|
|
|
|
```json
|
|
{
|
|
"name": "my-plugin",
|
|
"version": "1.0.0",
|
|
"hooks": {
|
|
"PostToolUse": [
|
|
{
|
|
"matcher": "Write|Edit",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/format.sh"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
### Option 2: Separate hooks.json File
|
|
|
|
```
|
|
my-plugin/
|
|
├── .claude-plugin/
|
|
│ └── plugin.json # Can also have hooks inline here
|
|
└── hooks/
|
|
└── hooks.json # Auto-discovered if present
|
|
```
|
|
|
|
### Hook Types
|
|
|
|
| Type | When | Use Cases |
|
|
|------|------|-----------|
|
|
| `PreToolUse` | Before tool | Validation, permissions |
|
|
| `PostToolUse` | After tool | Logging, formatting |
|
|
| `UserPromptSubmit` | Before prompt | Input validation |
|
|
| `Stop` | After response | Cleanup, notifications |
|
|
| `SessionStart` | Session begins | Context loading |
|
|
| `SessionEnd` | Session ends | Cleanup |
|
|
|
|
### hooks/hooks.json Format
|
|
|
|
When using a separate file, it requires a root-level `"hooks"` wrapper:
|
|
|
|
```json
|
|
{
|
|
"hooks": {
|
|
"PreToolUse": [
|
|
{
|
|
"matcher": "Write|Edit",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/validate.sh",
|
|
"timeout": 10
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"PostToolUse": [
|
|
{
|
|
"matcher": "Write(*.ts)",
|
|
"hooks": [
|
|
{
|
|
"type": "command",
|
|
"command": "biome check --write \"$file\"",
|
|
"timeout": 30
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
### Key Points
|
|
|
|
- Hooks can be inline in plugin.json OR in `hooks/hooks.json`
|
|
- Both formats use a `"hooks"` object containing event types
|
|
- Use `${CLAUDE_PLUGIN_ROOT}` for paths relative to plugin
|
|
- Use `$file` for the affected file path in PostToolUse
|
|
|
|
### Hook Script Interface
|
|
|
|
**Input (stdin):**
|
|
|
|
```json
|
|
{
|
|
"session_id": "abc123",
|
|
"transcript_path": "/path/to/transcript.jsonl",
|
|
"cwd": "/current/working/directory",
|
|
"hook_event_name": "PreToolUse",
|
|
"tool_name": "Write",
|
|
"tool_input": {
|
|
"file_path": "/project/src/file.ts",
|
|
"content": "export const foo = 'bar';"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Output (stdout):**
|
|
|
|
Allow:
|
|
|
|
```json
|
|
{"allowed": true}
|
|
```
|
|
|
|
Block:
|
|
|
|
```json
|
|
{
|
|
"allowed": false,
|
|
"message": "Validation failed: reason"
|
|
}
|
|
```
|
|
|
|
Modify:
|
|
|
|
```json
|
|
{
|
|
"allowed": true,
|
|
"modified_parameters": {
|
|
"content": "modified content"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Example Hook Script
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
input=$(cat)
|
|
|
|
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
|
|
content=$(echo "$input" | jq -r '.tool_input.content')
|
|
|
|
# Check for secrets
|
|
if echo "$content" | grep -qiE 'api[_-]?key.*=.*[a-zA-Z0-9]{16,}'; then
|
|
echo '{"allowed": false, "message": "Potential secret detected"}'
|
|
exit 0
|
|
fi
|
|
|
|
echo '{"allowed": true}'
|
|
```
|
|
|
|
## MCP Servers
|
|
|
|
### Server Configuration
|
|
|
|
```json
|
|
{
|
|
"mcpServers": {
|
|
"server-name": {
|
|
"command": "${CLAUDE_PLUGIN_ROOT}/servers/my-server",
|
|
"args": ["--flag", "value"],
|
|
"env": {
|
|
"API_KEY": "${MY_API_KEY}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Variable Substitution
|
|
|
|
| Variable | Resolves To |
|
|
|----------|-------------|
|
|
| `${CLAUDE_PLUGIN_ROOT}` | Plugin installation directory |
|
|
| `${VAR_NAME}` | Environment variable |
|
|
|
|
### Python MCP Server Example
|
|
|
|
```python
|
|
from mcp.server.fastmcp import FastMCP
|
|
|
|
mcp = FastMCP("my-server")
|
|
|
|
@mcp.tool()
|
|
async def my_tool(param: str) -> str:
|
|
"""Tool description"""
|
|
return f"Result: {param}"
|
|
|
|
if __name__ == "__main__":
|
|
mcp.run(transport='stdio')
|
|
```
|
|
|
|
## Platform Considerations
|
|
|
|
### macOS
|
|
|
|
- Config: `~/Library/Application Support/Claude/`
|
|
- Logs: `~/Library/Logs/Claude/`
|
|
|
|
### Windows
|
|
|
|
- Config: `%APPDATA%\Claude\`
|
|
- Use forward slashes or double backslashes
|
|
|
|
### Linux
|
|
|
|
- Config: `~/.config/claude/`
|
|
- Check shebang and permissions
|