How the Claude Code Plugin System Architecture Works

Claude Code discovers and activates plugins at startup by scanning the plugins/ directory for .claude-plugin/plugin.json manifests, then registers commands, agents, skills, hooks, and MCP servers without modifying core code.

The Claude Code plugin system architecture provides a declarative, file-based extension mechanism for the anthropics/claude-code repository. By placing a structured directory with a JSON manifest and optional component folders, third-party developers can inject new slash commands, validation hooks, and autonomous agents into the runtime. The system relies on a small Python loader that walks the plugin tree at initialization and wires components into the internal Agent SDK.

Plugin Directory Structure and Manifest

Each plugin lives in its own folder under plugins/ and exposes capabilities through a .claude-plugin/plugin.json manifest file. This JSON descriptor tells Claude Code what components the plugin provides and where to find them.

Component Purpose Location
Commands Slash-command definitions (e.g., /my-cmd) invoked by users plugins/<plugin-name>/commands/ (markdown files with front-matter)
Agents Autonomous Claude agents for long-running or multi-step work plugins/<plugin-name>/agents/
Skills Re-usable skill files (SKILL.md) that agents import plugins/<plugin-name>/skills/
Hooks Event-driven scripts that run before/after tools, on stop, etc. plugins/<plugin-name>/hooks/ (bash, python, or JSON schemas)
MCP Server Optional Model-Context-Protocol server exposing tools to Claude .mcp.json at the plugin root
Metadata Name, version, author, and description for discovery .claude-plugin/plugin.json

Component Registration and Loading Flow

When Claude Code starts, it executes a four-stage loading process to integrate plugins into the runtime.

Discovery

The runtime walks the plugins/*/.claude-plugin/plugin.json tree to locate every installed plugin.

Manifest Parsing

Each discovered JSON file is loaded into a Python Plugin object. The manifest declares which component types are present and their relative paths within the plugin folder.

Component Registration

  • Commands are parsed from markdown front-matter in commands/*.md files.
  • Hooks are collected and wrapped in JSON schemas that declare which events they handle (e.g., PreToolUse, Stop).
  • Agents and skills are registered with the internal Agent SDK for dynamic invocation.
  • MCP servers defined in .mcp.json are surfaced to Claude as native tools.

Runtime Execution

When a user triggers an event—such as typing a slash command or invoking a tool—Claude Code looks up the matching component and executes it. Hook scripts communicate back to the runtime via stdout using a JSON protocol; specific exit codes signal whether to allow, block, or modify the operation.

Hook System Deep Dive: The Hookify Example

The hookify plugin demonstrates how event-driven hooks intercept tool calls using a RuleEngine (plugins/hookify/core/rule_engine.py). This architecture allows users to block dangerous operations without modifying Claude Code's core.

Rule Definition and Loading

Users write rules in markdown files stored in .claude/ directories (e.g., .claude/hookify.dangerous-rm.local.md). These files contain YAML front-matter defining match conditions and actions.

The config_loader module parses these files into Rule objects:


# plugins/hookify/hooks/pretooluse.py

from hookify.core.config_loader import load_rules

# Load all rules tagged for the 'bash' event

rules = load_rules(event='bash')

Runtime Evaluation

For every incoming tool call (Bash, Write, etc.), the RuleEngine evaluates each rule. If conditions match, the engine can block the operation or emit a warning.


# plugins/hookify/hooks/pretooluse.py

import json, sys
from hookify.core.config_loader import load_rules
from hookify.core.rule_engine import RuleEngine

def main():
    input_data = json.load(sys.stdin)  # Tool input from Claude

    rules = load_rules(event='bash')
    engine = RuleEngine()
    result = engine.evaluate_rules(rules, input_data)

    if result.get("decision") == "block":
        # Protocol: JSON stdout with message, exit code 2

        print(json.dumps({"systemMessage": result["reason"]}))
        sys.exit(2)

    # Exit 0 allows the tool to proceed

    sys.exit(0)

if __name__ == "__main__":
    main()

Example Rule Configuration

Rules are defined in markdown with front-matter:

---
name: dangerous-rm
enabled: true
event: bash
tool_matcher: Bash
conditions:
  - field: command
    operator: regex_match
    pattern: "rm\\s+-rf"
---
⚠️ **Dangerous command**`rm -rf` can delete important data. Review before running.

Extending Claude Code: Implementation Patterns

Extension Point Implementation Method Key Source Reference
Add a new command Create commands/my-cmd.md with front-matter (name, description, tools) and a markdown body sent to Claude when invoked plugins/plugin-dev/README.md
Add a hook Drop a script in hooks/pretooluse.py that reads JSON from stdin and writes a JSON response to stdout; use RuleEngine or custom logic plugins/hookify/hooks/pretooluse.py
Expose a tool via MCP Add .mcp.json describing the tool schema (name, description, input_schema) plugins/plugin-dev/README.md
Add a skill Write skills/my-skill/SKILL.md following the progressive-disclosure pattern; agents reference it with skill: my-skill plugins/plugin-dev/skills/skill-development/SKILL.md

Minimal Plugin Manifest

Every plugin requires a plugin.json manifest at .claude-plugin/plugin.json:

{
  "name": "my-example-plugin",
  "description": "Demo plugin showing the manifest format",
  "version": "0.1.0",
  "author": { "name": "Your Name", "email": "[email protected]" }
}

Simple Slash Command Definition

Create commands/hello.md to register /hello:

---
name: hello
description: Say hello to Claude
tools: []
---
Hello, Claude! 👋

When the user types /hello, Claude renders the markdown body as the response.

Summary

  • Discovery mechanism: Claude Code scans plugins/*/.claude-plugin/plugin.json at startup to find extensions.
  • Component types: Plugins can provide commands (slash commands), hooks (event interceptors), agents (autonomous workers), skills (reusable prompts), and MCP servers (tool schemas).
  • Hook protocol: Hook scripts communicate via JSON over stdin/stdout, using exit code 2 to block operations and code 0 to allow them.
  • Rule engine: The hookify plugin demonstrates pluggable validation using config_loader.py and rule_engine.py to evaluate markdown-defined rules against tool inputs.
  • Zero-core changes: The architecture supports new functionality through file placement alone, as implemented in anthropics/claude-code.

Frequently Asked Questions

What file triggers plugin discovery in Claude Code?

Claude Code looks for .claude-plugin/plugin.json inside each immediate subdirectory of plugins/. This JSON manifest declares the plugin's metadata and available components, enabling the runtime to register the extension without additional configuration.

How do hooks communicate blocking decisions to the runtime?

Hooks write a JSON object to stdout and exit with a specific code. Exit code 0 allows the operation to proceed, while exit code 2 blocks it and displays the systemMessage field from the JSON response to the user. This protocol is implemented in files like plugins/hookify/hooks/pretooluse.py.

Can plugins expose new tools to Claude via MCP?

Yes. By adding a .mcp.json file at the plugin root that defines the tool name, description, and input schema, Claude Code automatically surfaces the tool to the user through the Model-Context-Protocol integration.

Where should skill files be placed in a plugin directory?

Skills belong in plugins/<plugin-name>/skills/ as SKILL.md files following the progressive-disclosure pattern. Agents can then reference these skills using the syntax skill: my-skill to import the capabilities defined in the markdown.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →