Plugin Architecture

Six Extension Points and the Art of Composable Extensibility

plugins
extensibility
architecture

Claude Code has six extension mechanisms: hooks, MCP servers, skills, custom agents, plugins, and slash commands. Each solves a different class of extensibility problem, operates at a different layer, and follows a different design pattern. The plugin system is the unifying layer that bundles the other five into distributable packages.

Why six? Because one binary must serve a solo developer debugging a React app, an enterprise team enforcing SOC 2 compliance across 200 engineers, a CI/CD pipeline running unattended, and an IDE integration embedding Claude as a coding assistant. Each audience needs different customization. No single API handles all four gracefully. Six focused mechanisms that compose are the result.

%%{init: {'theme': 'neutral', 'flowchart': {'useMaxWidth': false, 'htmlLabels': true, 'padding': 20, 'nodeSpacing': 30, 'rankSpacing': 40}, 'themeVariables': {'primaryColor': '#8B9DAF', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#6E7F91', 'secondaryColor': '#9CAF88', 'secondaryTextColor': '#ffffff', 'secondaryBorderColor': '#7A8D68', 'tertiaryColor': '#C2856E', 'tertiaryTextColor': '#ffffff', 'tertiaryBorderColor': '#A06A54', 'lineColor': '#B5A99A', 'textColor': '#4A4A4A', 'mainBkg': '#8B9DAF', 'nodeBorder': '#6E7F91', 'clusterBkg': 'rgba(139,157,175,0.12)', 'clusterBorder': '#B5A99A', 'edgeLabelBackground': 'transparent'}}}%%
flowchart TD
  PA["<b>Plugin Architecture Deep Dive</b>"]
  REG["Plugin Registry<br>and Lifecycle"]
  SIX["Six Extension<br>Points"]
  COMP["Composition Patterns<br>and Configuration"]
  MKT["Marketplace Vision<br>and Security Model"]

  PA --> REG
  PA --> SIX
  PA --> COMP
  REG --> MKT
  SIX --> MKT
  COMP --> MKT
  style PA fill:#8B9DAF,color:#fff,stroke:#6E7F91
  style REG fill:#9CAF88,color:#fff,stroke:#7A8D68
  style SIX fill:#C2856E,color:#fff,stroke:#A06A54
  style COMP fill:#B39EB5,color:#fff,stroke:#8E7A93
  style MKT fill:#C4A882,color:#fff,stroke:#A08562
Figure 1: This post’s coverage of the plugin architecture. Four major topics connect to the central node: the plugin registry and lifecycle (discovery through uninstall), the six extension points (hooks, MCP, skills, custom agents, plugins, slash commands), composition patterns and configuration (how mechanisms combine), and the marketplace vision with its security model. All four feed into the marketplace vision, reflecting plugins as the unifying distribution layer.

How to read this diagram. Start at the central “Plugin Architecture Deep Dive” node at the top. Three branches descend to Plugin Registry and Lifecycle, Six Extension Points, and Composition Patterns and Configuration. All three branches then converge on the Marketplace Vision and Security Model node at the bottom, indicating that the marketplace is the culmination of the registry, extension points, and composition patterns.

Source files covered in this post:

File Purpose Size
src/services/plugins/PluginInstallationManager.ts Plugin discovery, install, and lifecycle ~500 LOC
src/services/plugins/pluginOperations.ts Plugin CRUD operations ~300 LOC
src/services/plugins/pluginCliCommands.ts Plugin CLI command integration ~200 LOC
src/commands/plugin/ /plugin command handlers (install, list, remove) 15 files
src/tools/AgentTool/loadAgentsDir.ts Agent discovery (shared with plugins) ~756 LOC
src/skills/loadSkillsDir.ts Skill discovery (shared with plugins) ~300 LOC
.claude-plugin/plugin.json Plugin manifest format Config file

Why Six Extension Points?

Claude Code serves four radically different audiences, each with different extensibility needs. Six focused mechanisms beat one general-purpose API.

Consider what each audience needs:

Solo developers want behavioral customization. “When I write Python, follow PEP 8. When I write React, use functional components. After every file write, run prettier.” These are skills (behavioral rules) and hooks (automated side effects).

Enterprise teams need enforcement. “Block any shell command that touches production databases. Require code review before committing to main. Log every tool invocation to our audit system.” Only hooks can enforce invariants – exit code 2 blocks the action. Skills can guide, but cannot prevent.

CI/CD pipelines need capabilities. “Connect to our internal JIRA instance. Query our Postgres staging database. Post results to Slack.” These are tool capabilities, and MCP is the protocol for adding them. Any language, any runtime, discoverable at startup.

IDE integrations need specialized personas. “Run a security-focused review with read-only access. Generate API documentation with write access to /docs/ only. Triage bugs with access to the issue tracker.” Custom agents provide isolated, role-specific configurations with their own prompts and tool restrictions.

No single extension point could handle all four. A hooks-only system could enforce but not add capabilities. An MCP-only system could add tools but not enforce policy. A skills-only system could guide behavior but not execute side effects. Six mechanisms, each doing one thing well, compose into a system that handles all four audiences.


The Plugin Architecture – The Unifying Layer

Plugins are bundles. A plugin can provide skills, hooks, MCP servers, custom agents, slash commands, and output styles – all in one distributable package.

Part III.4 covered hooks and lifecycle events as individual extension points. This post examines the plugin system that wraps them into a distribution and lifecycle layer. The key insight: plugins do not add a seventh extension mechanism. They are the packaging format for the other six.

Plugin Identity

Every plugin has an identifier in the format {name}@{marketplace}. Built-in plugins use {name}@builtin. Third-party plugins reference their marketplace: security-tools@acme-marketplace. The @ separator is parsed by parsePluginIdentifier(), which splits on the first @ and ignores subsequent ones – marketplace names should not contain @.

%%{init: {'theme': 'neutral', 'flowchart': {'useMaxWidth': false, 'htmlLabels': true, 'padding': 20, 'nodeSpacing': 30, 'rankSpacing': 40}, 'themeVariables': {'primaryColor': '#8B9DAF', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#6E7F91', 'secondaryColor': '#9CAF88', 'secondaryTextColor': '#ffffff', 'secondaryBorderColor': '#7A8D68', 'tertiaryColor': '#C2856E', 'tertiaryTextColor': '#ffffff', 'tertiaryBorderColor': '#A06A54', 'lineColor': '#B5A99A', 'textColor': '#4A4A4A', 'mainBkg': '#8B9DAF', 'nodeBorder': '#6E7F91', 'clusterBkg': 'rgba(139,157,175,0.12)', 'clusterBorder': '#B5A99A', 'edgeLabelBackground': 'transparent'}}}%%
flowchart LR
  subgraph builtin["Built-in Plugin"]
    BN["github@builtin"]
    BND["name: github<br>marketplace: builtin"]
    BN --> BND
  end

  subgraph market["Marketplace Plugin"]
    MPN["security-tools@acme-market"]
    MPD["name: security-tools<br>marketplace: acme-market"]
    MPN --> MPD
  end

  RES["<b>Resolution order:</b><br>1. Split on first @<br>2. No @ = bare name<br>3. @builtin = built-in"]

  builtin ~~~ RES
  market ~~~ RES
  style BN fill:#8B9DAF,color:#fff,stroke:#6E7F91
  style BND fill:#9CAF88,color:#fff,stroke:#7A8D68
  style MPN fill:#C2856E,color:#fff,stroke:#A06A54
  style MPD fill:#B39EB5,color:#fff,stroke:#8E7A93
  style RES fill:#C4A882,color:#fff,stroke:#A08562
Figure 2: Plugin identity format and resolution order. Two examples illustrate the name@marketplace convention: github@builtin resolves to name=‘github’ with marketplace=‘builtin’, while security-tools@acme-market splits into its respective components. The resolution rules are: split on the first @ symbol, treat bare names (no @) as unscoped, and recognize @builtin as indicating a built-in plugin that ships with the CLI binary.

How to read this diagram. Two example plugin identifiers are shown side by side: github@builtin on the left and security-tools@acme-market on the right. Each decomposes into its name and marketplace components via an arrow. The resolution rules box below summarizes the parsing logic: split on the first @, treat bare names (no @) as unscoped, and recognize @builtin as a built-in plugin shipped with the CLI binary.

What a Plugin Contains

The LoadedPlugin type reveals everything a plugin can provide:

type LoadedPlugin = {
  name: string
  manifest: PluginManifest       // Metadata: version, description, author
  path: string                   // Filesystem path to plugin root
  source: string                 // Repository/marketplace source
  commandsPath?: string          // Slash commands (Markdown files)
  commandsPaths?: string[]       // Additional command paths
  agentsPath?: string            // Custom agent definitions
  agentsPaths?: string[]         // Additional agent paths
  skillsPath?: string            // SKILL.md behavioral instructions
  skillsPaths?: string[]         // Additional skill paths
  outputStylesPath?: string      // Custom output formatting
  hooksConfig?: HooksSettings    // Hook definitions (PreToolUse, etc.)
  mcpServers?: Record<string, McpServerConfig>  // MCP server configs
  lspServers?: Record<string, LspServerConfig>  // LSP server configs
  settings?: Record<string, unknown>  // Plugin-specific configuration
}

A single plugin can bundle slash commands, agents, skills, hooks, MCP servers, and even LSP servers. The plugin directory structure mirrors these components:

my-plugin/
+-- .claude-plugin/
|   +-- plugin.json        # Manifest: name, version, author, deps
+-- commands/              # Slash commands (Markdown files)
|   +-- build.md
|   +-- deploy.md
+-- agents/                # Custom agent definitions
|   +-- security-reviewer.md
+-- skills/                # SKILL.md behavioral instructions
|   +-- SKILL.md
+-- hooks/                 # Hook configurations
|   +-- hooks.json
+-- output-styles/         # Custom output formatting
    +-- my-style.md

The Plugin Manifest

The manifest file (.claude-plugin/plugin.json) carries metadata validated by a comprehensive Zod schema:

{
  "name": "security-tools",
  "version": "1.2.3",
  "description": "Security scanning and enforcement tools",
  "author": {
    "name": "Acme Security",
    "email": "security@acme.com",
    "url": "https://acme.com/security"
  },
  "homepage": "https://github.com/acme/security-tools",
  "license": "MIT",
  "keywords": ["security", "scanning", "compliance"],
  "dependencies": ["base-tools@acme-marketplace"]
}

Dependencies are first-class citizens. A plugin can declare dependencies on other plugins from the same marketplace, and the system resolves them at install time. The verifyAndDemote function checks dependencies at load time – if a dependency is missing or disabled, the dependent plugin is demoted.

Plugin Lifecycle

The lifecycle follows a settings-first philosophy. Installing a plugin writes to settings before caching the code – settings declare intent, caching materializes it.

%%{init: {'theme': 'neutral', 'flowchart': {'useMaxWidth': false, 'htmlLabels': true, 'padding': 20, 'nodeSpacing': 30, 'rankSpacing': 40}, 'themeVariables': {'primaryColor': '#8B9DAF', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#6E7F91', 'secondaryColor': '#9CAF88', 'secondaryTextColor': '#ffffff', 'secondaryBorderColor': '#7A8D68', 'tertiaryColor': '#C2856E', 'tertiaryTextColor': '#ffffff', 'tertiaryBorderColor': '#A06A54', 'lineColor': '#B5A99A', 'textColor': '#4A4A4A', 'mainBkg': '#8B9DAF', 'nodeBorder': '#6E7F91', 'clusterBkg': 'rgba(139,157,175,0.12)', 'clusterBorder': '#B5A99A', 'edgeLabelBackground': 'transparent'}}}%%
flowchart LR
  START(( )) -- discover --> browsed
  browsed -- install --> installed
  installed -- enable --> enabled
  enabled -- load --> active
  active -- failure --> error
  installed -- uninstall --> uninstalled
  enabled -- disable --> disabled
  error -- user disables --> disabled
  disabled -- re-enable --> enabled
  style START fill:#8B9DAF,color:#fff,stroke:#6E7F91
  style browsed fill:#9CAF88,color:#fff,stroke:#7A8D68
  style installed fill:#C2856E,color:#fff,stroke:#A06A54
  style enabled fill:#B39EB5,color:#fff,stroke:#8E7A93
  style active fill:#C4A882,color:#fff,stroke:#A08562
  style error fill:#8E9B7A,color:#fff,stroke:#6E7B5A
  style uninstalled fill:#8B9DAF,color:#fff,stroke:#6E7F91
  style disabled fill:#9CAF88,color:#fff,stroke:#7A8D68
Figure 3: Plugin lifecycle state machine from discovery to uninstall. Plugins progress through five states: browsed (discovered in marketplace), installed (cached locally), enabled (activated in settings), and active (loaded into the running agent). Failure during loading transitions to an error state. Disabled plugins can be re-enabled without reinstalling. The settings-first philosophy means installation writes to settings before caching code.

How to read this diagram. Start at the hollow circle on the left and follow the labeled arrows through the state transitions: discover leads to browsed, install leads to installed, enable leads to enabled, and load leads to active. Failure from the active state transitions to error. The key branching paths are: installed can be uninstalled (terminal), enabled can be disabled, and disabled can be re-enabled back to enabled. The error state also transitions to disabled when the user intervenes.

The update mechanism is non-inplace: a new version is cached in a versioned directory alongside the old one. Only after the new version is cached and the installation record updated is the old version orphaned. This ensures that a failed update never leaves the user without a working plugin.

Plugin Scoping

Plugins operate across four scopes that form a precedence hierarchy:

%%{init: {'theme': 'neutral', 'flowchart': {'useMaxWidth': false, 'htmlLabels': true, 'padding': 20, 'nodeSpacing': 30, 'rankSpacing': 40}, 'themeVariables': {'primaryColor': '#8B9DAF', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#6E7F91', 'secondaryColor': '#9CAF88', 'secondaryTextColor': '#ffffff', 'secondaryBorderColor': '#7A8D68', 'tertiaryColor': '#C2856E', 'tertiaryTextColor': '#ffffff', 'tertiaryBorderColor': '#A06A54', 'lineColor': '#B5A99A', 'textColor': '#4A4A4A', 'mainBkg': '#8B9DAF', 'nodeBorder': '#6E7F91', 'clusterBkg': 'rgba(139,157,175,0.12)', 'clusterBorder': '#B5A99A', 'edgeLabelBackground': 'transparent'}}}%%
flowchart TD
  M["<b>managed</b> (policySettings)<br><i>Org-wide policy, can FORCE-DISABLE</i>"]
  L["<b>local</b> (.claude/settings.local.json)<br><i>Developer override for this machine</i>"]
  P["<b>project</b> (.claude/settings.json)<br><i>Team-shared, version-controlled</i>"]
  U["<b>user</b> (~/.claude/settings.json)<br><i>Personal preferences</i>"]
  NOTE["Resolution: local > project > user > managed<br>But: managed can BLOCK plugins that<br>lower scopes cannot override"]

  M --> L --> P --> U
  U -.- NOTE

  style M fill:#8B9DAF,color:#fff,stroke:#6E7F91
  style L fill:#9CAF88,color:#fff,stroke:#7A8D68
  style P fill:#C2856E,color:#fff,stroke:#A06A54
  style U fill:#B39EB5,color:#fff,stroke:#8E7A93
  style NOTE fill:#C4A882,color:#fff,stroke:#A08562,stroke-dasharray: 5 5
Figure 4: Plugin scope precedence hierarchy showing four levels from highest to lowest authority. Managed (policySettings) is org-wide policy that can force-disable plugins irrevocably. Local (.claude/settings.local.json) provides per-machine developer overrides. Project (.claude/settings.json) holds team-shared, version-controlled preferences. User (~/.claude/settings.json) is the personal defaults baseline. Resolution order for enable/disable decisions: local > project > user > managed, but managed scope can block plugins that no lower scope can override.

How to read this diagram. Read top to bottom as a precedence chain from highest authority to lowest. Managed (policySettings) sits at the top with the power to force-disable plugins irrevocably. Below it, local overrides project, which overrides user defaults. The dashed note at the bottom clarifies the resolution rule: local > project > user > managed for enable/disable decisions, but managed scope can block plugins that no lower scope can override.

This scoping model enables a powerful pattern: an enterprise admin can force-disable a plugin via managed settings, and no user or project setting can override that decision. But a developer can use local scope to disable a project-mandated plugin for their own machine (useful for debugging). The function isPluginBlockedByPolicy() is the single source of truth for policy enforcement – a three-line function with outsized impact.


Custom Agents as Extension Points

Custom agents are YAML or Markdown files that define isolated personas with restricted capabilities – sandboxed expertise for specific tasks.

Part II.3 introduced the agent types from the capability spectrum. Here we focus on custom agents as an extension mechanism – how they are defined, what they can control, and how plugins distribute them.

Agent Definition

A custom agent is a Markdown file with frontmatter metadata and a body that serves as the system prompt:


---
name: security-reviewer
description: Reviews code for security vulnerabilities
model: opus
tools:
  - Read
  - Grep
  - Glob
  - Bash
permissionMode: default
maxTurns: 50
memory: project

---

You are a security-focused code reviewer. Your job is to find
vulnerabilities in code. Focus on:
- SQL injection and parameterized queries
- XSS and output encoding
- CSRF token validation
- Authentication and authorization flaws
- Data exposure in logs and error messages

Be specific. Cite line numbers. Suggest fixes with code examples.
Never modify code directly -- report findings only.

The frontmatter fields parsed by parseAgentFromMarkdown() reveal the full control surface:

Field Type Purpose
name string Agent identifier (used in /agent security-reviewer)
description string When the model should delegate to this agent
tools string[] Allowlist of tools (restricts, does not expand)
disallowedTools string[] Blocklist (inverse of tools)
model string Model override (opus, sonnet, haiku, or “inherit”)
effort string/int Reasoning effort level
permissionMode string Permission strictness
maxTurns int Maximum agentic turns before stopping
mcpServers array MCP servers specific to this agent
hooks object Session-scoped hooks registered when agent starts
skills string[] Skills to preload
memory string Persistent memory scope (user, project, local)
background boolean Always run as background task
isolation string Git worktree isolation
initialPrompt string Prepended to first user turn

The tools field is the key security mechanism. A security reviewer restricted to [Read, Grep, Glob, Bash] cannot call Write or Edit – even if the model tries to, the tool registry rejects the call. This is capability restriction, not behavioral guidance. The model cannot reason its way around a missing tool.

Agent Sources

Agents come from four sources, loaded with clear precedence in getActiveAgentsFromList():

  1. Built-in agents (Explore, Plan, etc.) – always present
  2. Plugin agents – from enabled plugins
  3. User agents (~/.claude/agents/) – personal definitions
  4. Project agents (.claude/agents/) – team-shared definitions
  5. Managed agents (policy) – enterprise-mandated
  6. Flag agents (session-only) – from feature flags

When names collide, later sources override earlier ones. A project agent named “security-reviewer” shadows a user agent of the same name. Managed agents shadow everything – ensuring enterprise policy takes priority.

CautionPattern Spotted

Custom agents implement the Actor Model. Each agent is an isolated actor with its own state (conversation context), mailbox (prompt input), and behavior (system prompt + tool restrictions). Agents communicate through message passing (the Agent tool sends a prompt, receives a result). No shared mutable state – the same pattern that makes Erlang processes safe to run concurrently.


Slash Commands as User-Level Extensions

Over 80 slash command directories in the codebase provide deterministic, instant-response operations that bypass the model entirely.

Slash commands are the extension point that does not involve the LLM at all. When you type /compact, Claude Code does not send your request to an API. It parses the command locally, executes the handler, and returns the result. Zero tokens consumed. Zero latency from model inference. Zero hallucination risk.

The command system spans over 80 subdirectories under src/commands/, covering operations from /add-dir to /voice. But the command system is also an extension surface: plugins can contribute their own slash commands via Markdown files in their commands/ directory.

Command Categories

Commands fall into distinct categories based on what they control:

Category Count Examples
Session Management 12 /clear, /compact, /resume, /session, /status, /share
Agent Control 8 /agent, /tasks, /branch, /teleport, /worktree
Git Workflow 7 /commit, /pr, /review, /diff, /rewind, /tag
Configuration 10 /config, /permissions, /model, /theme, /effort, /sandbox
Mode Switching 5 /plan, /code, /architect, /auto, /fast
MCP & Plugins 6 /mcp, /plugin, /skills, /hooks, /reload-plugins
Developer Tools 8 /init, /install, /onboarding, /upgrade, /add-dir
Diagnostics 6 /doctor, /debug, /cost, /usage, /stats
Skills 5+ /simplify, /loop, /code-review, /feedback

Plugin-Contributed Commands

When a plugin is loaded, its commandsPath and commandsPaths fields point to directories containing Markdown files that define new slash commands. The loader parses these files, extracts frontmatter metadata (name, description, whenToUse), and registers them alongside built-in commands. The command’s body becomes the prompt sent to the model when the user invokes it.

This means plugins can extend the / namespace. A security plugin might add /security-scan, /vulnerability-report, and /compliance-check as new slash commands – each with its own behavioral instructions, tool restrictions, and trigger conditions.


The Six Extension Points – Complete Comparison

Each mechanism solves a different problem at a different layer. The comparison table is the decision framework for choosing the right one.

Dimension Hooks MCP Skills Custom Agents Plugins Slash Commands
What it does Automate side effects, enforce invariants Add external tool capabilities Modify agent behavior and reasoning Create isolated personas Bundle and distribute extension packages Direct user control, bypass model
CS Analogy AOP aspects / Intercepting Filters LSP for AI tools Cognitive plugins / Template Method fork() with new env / Actor Model App store / OSGi bundles CLI commands / DSL
Format JSON config + shell command Any language, JSON-RPC Markdown (SKILL.md) Markdown or YAML Directory + plugin.json TypeScript or Markdown
Can block actions? YES (exit code 2) No No No (restricts, does not gate) Via hooks component N/A (bypasses model)
Adds tools? No YES (dynamic registration) No No (restricts existing) Via MCP component No
Modifies prompt? Injects reminders Server instructions (cache-breaking) YES (primary mechanism) YES (own system prompt) Via skills component No
When it runs During execution (sync) On tool call (async, cross-process) At prompt assembly (compile-time) When spawned (async, isolated) Always loaded (lifecycle-managed) On user invocation (immediate)
Scope Per-project or per-user Cross-agent, universal protocol Claude Code specific Claude Code specific 4-scope hierarchy User interface layer
Security model Shell process, inherits perms Separate process, stdio/SSE/HTTP Prompt injection risk Tool restriction (least privilege) Marketplace trust + policy Deterministic, no model

The table reveals the fundamental architectural truth: each extension point occupies a unique position in the design space. No two overlap on all dimensions. This is not accidental – it is the result of identifying distinct classes of extensibility and building a focused mechanism for each.

The “Can block actions?” row is the most operationally important. Of all six extension points, only hooks can prevent a tool invocation. If you need to enforce an invariant – “never delete production data,” “always run linters before committing” – hooks are the only mechanism. Skills can suggest, MCP can provide alternatives, custom agents can restrict tools, but only a PreToolUse hook returning exit code 2 can physically prevent an action from happening.


Extension Composition Patterns

The six mechanisms become powerful when composed. Real-world workflows combine multiple extension points, each contributing what the others cannot.

Pattern 1: Enterprise Compliance

Consider an enterprise with SOC 2 compliance requirements. The composition layers five mechanisms together:

%%{init: {'theme': 'neutral', 'flowchart': {'useMaxWidth': false, 'htmlLabels': true, 'padding': 20, 'nodeSpacing': 30, 'rankSpacing': 40}, 'themeVariables': {'primaryColor': '#8B9DAF', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#6E7F91', 'secondaryColor': '#9CAF88', 'secondaryTextColor': '#ffffff', 'secondaryBorderColor': '#7A8D68', 'tertiaryColor': '#C2856E', 'tertiaryTextColor': '#ffffff', 'tertiaryBorderColor': '#A06A54', 'lineColor': '#B5A99A', 'textColor': '#4A4A4A', 'mainBkg': '#8B9DAF', 'nodeBorder': '#6E7F91', 'clusterBkg': 'rgba(139,157,175,0.12)', 'clusterBorder': '#B5A99A', 'edgeLabelBackground': 'transparent'}}}%%
flowchart TD
  subgraph SK["SKILLS (behavioral guidance)"]
    S1["Secure coding practices.<br>Never log PII. Parameterized queries."]
  end

  subgraph HK["HOOKS (enforcement)"]
    H1["PreToolUse Bash: block rm -rf /"]
    H2["PreToolUse Write: scan for secrets"]
    H3["PostToolUse Write: run security linter"]
    H4["Stop: generate compliance report"]
  end

  subgraph MC["MCP SERVERS (capabilities)"]
    M1["vault-server: secrets management"]
    M2["jira-server: compliance tickets"]
    M3["splunk-server: audit log shipping"]
  end

  subgraph AG["CUSTOM AGENT (specialized review)"]
    A1["security-reviewer<br><i>tools: Read, Grep, Glob</i><br><i>prompt: Focus on OWASP Top 10</i>"]
  end

  subgraph PL["PLUGIN (distribution)"]
    P1["acme-compliance@acme-marketplace<br><i>Bundles all above, version-pinned, managed-scope</i>"]
  end

  SK -- "shapes reasoning" --> HK
  HK -- "gates execution" --> MC
  MC -- "provides tools" --> AG
  AG -- "isolated persona" --> PL
  style S1 fill:#8B9DAF,color:#fff,stroke:#6E7F91
  style H1 fill:#9CAF88,color:#fff,stroke:#7A8D68
  style H2 fill:#C2856E,color:#fff,stroke:#A06A54
  style H3 fill:#B39EB5,color:#fff,stroke:#8E7A93
  style H4 fill:#C4A882,color:#fff,stroke:#A08562
  style M1 fill:#8E9B7A,color:#fff,stroke:#6E7B5A
  style M2 fill:#8B9DAF,color:#fff,stroke:#6E7F91
  style M3 fill:#9CAF88,color:#fff,stroke:#7A8D68
  style A1 fill:#C2856E,color:#fff,stroke:#A06A54
  style P1 fill:#B39EB5,color:#fff,stroke:#8E7A93
Figure 5: Enterprise compliance composition across five extension layers for a SOC 2 scenario. Skills inject secure-coding behavioral guidance (never log PII, parameterized queries). Hooks enforce invariants via PreToolUse/PostToolUse interceptors (block rm -rf, scan for secrets, run security linter). MCP servers connect to infrastructure (Vault secrets management, JIRA compliance tickets, Splunk audit logging). A custom security-reviewer agent runs OWASP-focused analysis with read-only tool access. The plugin layer bundles everything into a version-pinned, managed-scope distributable package.

How to read this diagram. Five horizontal layers stack from top to bottom, each representing a different extension mechanism in a SOC 2 compliance scenario. Start at the top with Skills (behavioral guidance), then move down through Hooks (enforcement via PreToolUse/PostToolUse), MCP Servers (external capabilities like Vault and Splunk), Custom Agent (an isolated security reviewer), and finally the Plugin layer that bundles everything into a distributable package. Arrows between layers are labeled with the relationship: skills shape reasoning, hooks gate execution, MCP provides tools, and the agent runs isolated review.

Each layer contributes something the others cannot. Skills shape how the model thinks about security. Hooks enforce security rules the model cannot bypass. MCP connects to external security infrastructure. The custom agent provides isolated review with restricted capabilities. The plugin bundles everything into a distributable, version-managed package that the enterprise can deploy via managed settings.

Pattern 2: CI/CD Workflow

A CI/CD workflow composes differently:

  • Skill: “Never deploy on Fridays. Run migrations before schema changes. Follow the team’s deployment checklist.”
  • Hook (PreToolUse): Check day of week before deploy commands; validate migration files exist before schema changes.
  • Hook (PostToolUse): Run prettier --write after every file write; post deployment status to Slack.
  • MCP Server: Wraps the CI/CD API (get_build_status, trigger_deploy, rollback).
  • Custom Agent: A deployment-reviewer with read-only access that validates the deployment plan.
  • Slash Command: /deploy-status for instant build information without model involvement.

The Three Temporal Windows

The composition works because each mechanism operates in a different temporal window:

%%{init: {'theme': 'neutral', 'flowchart': {'useMaxWidth': false, 'htmlLabels': true, 'padding': 20, 'nodeSpacing': 30, 'rankSpacing': 40}, 'themeVariables': {'primaryColor': '#8B9DAF', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#6E7F91', 'secondaryColor': '#9CAF88', 'secondaryTextColor': '#ffffff', 'secondaryBorderColor': '#7A8D68', 'tertiaryColor': '#C2856E', 'tertiaryTextColor': '#ffffff', 'tertiaryBorderColor': '#A06A54', 'lineColor': '#B5A99A', 'textColor': '#4A4A4A', 'mainBkg': '#8B9DAF', 'nodeBorder': '#6E7F91', 'clusterBkg': 'rgba(139,157,175,0.12)', 'clusterBorder': '#B5A99A', 'edgeLabelBackground': 'transparent'}}}%%
flowchart LR
  subgraph PRE["PRE-REASONING"]
    P1["Skills inject instructions"]
    P2["MCP injects server instructions"]
    P3["Agent prompt assembled"]
  end

  subgraph DUR["DURING-EXECUTION"]
    D1["Hooks intercept PreToolUse"]
    D2["Can BLOCK (exit 2)"]
    D3["Tool executes"]
  end

  subgraph POST["POST-EXECUTION"]
    D4["Hooks observe PostToolUse"]
    D5["Hook reminders injected<br>back into conversation"]
  end

  PRE --> DUR --> POST
  style P1 fill:#8B9DAF,color:#fff,stroke:#6E7F91
  style P2 fill:#9CAF88,color:#fff,stroke:#7A8D68
  style P3 fill:#C2856E,color:#fff,stroke:#A06A54
  style D1 fill:#B39EB5,color:#fff,stroke:#8E7A93
  style D2 fill:#C4A882,color:#fff,stroke:#A08562
  style D3 fill:#8E9B7A,color:#fff,stroke:#6E7B5A
  style D4 fill:#8B9DAF,color:#fff,stroke:#6E7F91
  style D5 fill:#9CAF88,color:#fff,stroke:#7A8D68
Figure 6: Three temporal windows of extension in the agent lifecycle. Pre-reasoning: skills inject instructions and MCP injects server instructions into the assembled prompt. During-execution: hooks intercept via PreToolUse (with the unique ability to block actions via exit code 2) and the tool executes. Post-execution: hooks observe via PostToolUse and inject reminders back into the conversation for the model’s next turn. The temporal separation is why the six mechanisms compose without conflict.

How to read this diagram. Read left to right through three temporal phases. PRE-REASONING (left) is where skills and MCP inject instructions into the prompt before the model reasons. DURING-EXECUTION (center) is where hooks intercept tool calls – notably, only this phase can block actions via exit code 2. POST-EXECUTION (right) is where hooks observe outcomes and inject reminders back into the conversation for the model’s next turn. The temporal separation explains why the six extension mechanisms compose without conflict.

Skills and MCP instructions operate in the pre-reasoning window – they shape the model’s understanding before it decides what to do. Hooks operate in the during-execution window – they intercept actions after the model decides but before (or after) execution. Hook reminders operate in the post-execution window – they feed results back into the conversation so the model can adjust its next action.

This temporal separation is why the six mechanisms compose without conflict. They do not compete for the same insertion point; they each own a distinct phase of the agent’s decision-execution-feedback loop.


The Configuration Surface

settings.json is the unifying configuration layer. Every extension point reads its configuration from the same hierarchical settings system.

Configuration Hierarchy

All six extension points are configured through a single settings infrastructure with four-level precedence:

Level File Purpose
1 (highest) managed-settings.json (policySettings) Org-wide, admin-controlled, cannot override
2 .claude/settings.local.json (localSettings) Per-machine overrides, not version-controlled
3 .claude/settings.json (projectSettings) Team-shared, version-controlled
4 (lowest) ~/.claude/settings.json (userSettings) User-wide defaults and preferences

Each extension point reads from this hierarchy:

  • Hooks: Configured in settings.hooks – merged across scopes, policy hooks cannot be overridden
  • MCP Servers: Configured in settings.mcpServers – per-scope server definitions
  • Plugins: Configured in settings.enabledPlugins{pluginId: true/false} per scope
  • Custom Agents: Loaded from agents/ subdirectories at each scope level
  • Skills: Loaded from skills/ subdirectories plus plugin-contributed skills
  • Slash Commands: Built-in plus plugin-contributed commands

The settings merge is not simple concatenation. Policy settings can block lower-scope settings. A policy that sets enabledPlugins["dangerous-tool@marketplace"] = false prevents any user or project setting from enabling that plugin. The isPluginBlockedByPolicy() function – just three lines – is the enforcement gate:

export function isPluginBlockedByPolicy(pluginId: string): boolean {
  const policyEnabled = getSettingsForSource('policySettings')?.enabledPlugins
  return policyEnabled?.[pluginId] === false
}

Three lines of code, but they implement the entire enterprise policy enforcement for plugins.


The Plugin Marketplace Vision

The marketplace system transforms Claude Code from a tool with extensions into a platform with an ecosystem.

Marketplace Architecture

A marketplace is a Git repository containing a marketplace.json file that lists available plugins with their metadata and source locations. Claude Code supports multiple simultaneous marketplaces.

%%{init: {'theme': 'neutral', 'flowchart': {'useMaxWidth': false, 'htmlLabels': true, 'padding': 20, 'nodeSpacing': 30, 'rankSpacing': 40}, 'themeVariables': {'primaryColor': '#8B9DAF', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#6E7F91', 'secondaryColor': '#9CAF88', 'secondaryTextColor': '#ffffff', 'secondaryBorderColor': '#7A8D68', 'tertiaryColor': '#C2856E', 'tertiaryTextColor': '#ffffff', 'tertiaryBorderColor': '#A06A54', 'lineColor': '#B5A99A', 'textColor': '#4A4A4A', 'mainBkg': '#8B9DAF', 'nodeBorder': '#6E7F91', 'clusterBkg': 'rgba(139,157,175,0.12)', 'clusterBorder': '#B5A99A', 'edgeLabelBackground': 'transparent'}}}%%
flowchart TD
  OFF["<b>Official Marketplace</b><br><i>Anthropic</i>"]
  ENT["<b>Enterprise Marketplace</b><br><i>acme-corp</i>"]
  COM["<b>Community Marketplace</b><br><i>open-source</i>"]

  MJ1["marketplace.json<br>plugins: name, source, version"]
  MJ2["marketplace.json<br>plugins: name, source, version"]
  MJ3["marketplace.json<br>plugins: name, source, version"]

  OFF --> MJ1
  ENT --> MJ2
  COM --> MJ3

  PL["<b>Plugin Loader</b><br><i>unified</i>"]
  SET["<b>Settings</b><br><i>scoped</i>"]

  MJ1 --> PL
  MJ2 --> PL
  MJ3 --> PL
  PL --> SET
  style OFF fill:#8B9DAF,color:#fff,stroke:#6E7F91
  style ENT fill:#9CAF88,color:#fff,stroke:#7A8D68
  style COM fill:#C2856E,color:#fff,stroke:#A06A54
  style MJ1 fill:#B39EB5,color:#fff,stroke:#8E7A93
  style MJ2 fill:#C4A882,color:#fff,stroke:#A08562
  style MJ3 fill:#8E9B7A,color:#fff,stroke:#6E7B5A
  style PL fill:#8B9DAF,color:#fff,stroke:#6E7F91
  style SET fill:#9CAF88,color:#fff,stroke:#7A8D68
Figure 7: Marketplace architecture supporting multiple simultaneous marketplaces. Three marketplace types are shown: Official (Anthropic-controlled, 8 reserved names), Enterprise (org-specific, e.g., acme-corp), and Community (open-source). Each marketplace is a Git repository containing a marketplace.json that lists plugin names, source locations, and versions. All three feed into a unified Plugin Loader, which resolves and merges plugin definitions into the four-scope settings hierarchy.

How to read this diagram. Three marketplace types sit at the top: Official (Anthropic), Enterprise (org-specific), and Community (open-source). Each connects to its own marketplace.json file containing plugin listings. All three marketplace JSON files feed into the unified Plugin Loader in the center, which resolves and merges plugin definitions into the scoped Settings system at the bottom. The key insight is that multiple marketplaces coexist and are unified through a single loader.

Eight official marketplace names are reserved for Anthropic: claude-code-marketplace, claude-code-plugins, claude-plugins-official, anthropic-marketplace, anthropic-plugins, agent-skills, life-sciences, and knowledge-work-plugins. These names are protected by impersonation pattern matching and verified against the anthropics GitHub organization.

Built-in Plugins

Built-in plugins ship with the CLI binary and use the @builtin marketplace namespace. They differ from marketplace plugins in that they require no network fetch, no Git clone, no cache management. The BuiltinPluginDefinition type shows what they can provide:

type BuiltinPluginDefinition = {
  name: string
  description: string
  version?: string
  skills?: BundledSkillDefinition[]   // Behavioral instructions
  hooks?: HooksSettings               // Lifecycle hooks
  mcpServers?: Record<string, McpServerConfig>  // Tool servers
  isAvailable?: () => boolean         // System capability check
  defaultEnabled?: boolean            // Default state before user sets preference
}

Built-in plugins appear in the /plugin UI under a “Built-in” section. Users can enable or disable them, and preferences are persisted to user settings. The isAvailable function allows conditional availability – a plugin that wraps a system-specific capability can hide itself on unsupported platforms.

Auto-Update and Versioning

Official marketplaces auto-update by default (except those in the NO_AUTO_UPDATE_OFFICIAL_MARKETPLACES set). Plugin versions are calculated by calculatePluginVersion() which synthesizes version strings from multiple sources: the manifest version field, Git commit SHAs, and marketplace-declared versions.

The update mechanism is atomic. updatePluginOp() downloads the new version to a temporary directory, calculates its version hash, copies it to a versioned cache directory, updates the installation record, and only then marks the old version as orphaned. If any step fails, the old version remains intact. This is the same strategy used by container orchestration systems for rolling deployments.


Security Considerations

Each extension point has a different security model. Understanding the threat surface of each is essential for safe extensibility.

Extension Point Execution Isolation Primary Threat Mitigation Trust Level
Hooks Shell process Inherits agent permissions Shell injection if unvalidated input Policy settings; fixed exit code semantics HIGH (same process)
MCP Servers Separate process Process-level, own memory Malicious server exfiltrating data Tool annotations; permission system gates calls MEDIUM (process boundary)
Skills Text injection into prompt None – direct model context Prompt injection overriding guidelines Trusted sources only; review content LOW (no execution boundary)
Custom Agents Isolated agent context Separate conversation, restricted tools Too-broad tool access Least privilege via tools allowlist; maxTurns MEDIUM (tool restriction)
Plugins Composed: hooks + MCP + skills Inherits component levels Malicious bundle Marketplace trust, policy, homograph detection VARIES (per component)
Slash Commands Direct function call, no model Same process as CLI Minimal – deterministic Static code, not dynamic content HIGH (deterministic)

The security models form a gradient from lowest isolation (skills, which inject text directly into the prompt) to highest isolation (MCP servers, which run in separate processes). This gradient is not accidental – it reflects the inherent trade-off between power and safety. Skills have the most direct influence on model behavior (they literally become part of the system prompt) but the least execution isolation. MCP servers have the strongest isolation (separate process, defined protocol) but influence the model only indirectly through tool availability and server instructions.

Plugin-Specific Security

Plugins inherit the security characteristics of their components, but add marketplace trust as an additional layer. The defenses are layered:

  1. Name impersonation protection: BLOCKED_OFFICIAL_NAME_PATTERN catches attempts to register marketplaces with names like “claude-official-plugins”. Non-ASCII homograph detection catches Unicode-based impersonation.

  2. Source org verification: Reserved marketplace names (the eight official names) can only be used by repositories from the anthropics GitHub organization. validateOfficialNameSource() enforces this.

  3. Policy blocking: Enterprise admins can force-disable specific plugins via policySettings. The isPluginBlockedByPolicy() function is checked at install, enable, and load time.

  4. Dependency verification: verifyAndDemote() checks that all declared dependencies are present and enabled. A plugin with an unsatisfied dependency is demoted to disabled.

WarningTrade-off

Skills have the weakest security boundary because their mechanism is prompt injection – they inject text into the system prompt. A malicious skill could potentially override safety instructions, change behavioral guidelines, or inject harmful patterns. The mitigation is trust at the source level (marketplace verification, policy controls) rather than at the execution level (there is no execution boundary for text injection). This is a deliberate trade-off: skills need direct prompt access to work, and sandboxing prompt content would eliminate their utility.


Design Patterns and CS Connections

Claude Code’s extension architecture instantiates half a dozen classic design patterns, each applied at a different layer.

Decorator Pattern (System Level)

The entire extension architecture is the Decorator pattern applied at the system level. The core agent has fixed behavior: receive prompt, reason, call tools, return response. Each extension point wraps additional behavior around this core:

%%{init: {'theme': 'neutral', 'flowchart': {'useMaxWidth': false, 'htmlLabels': true, 'padding': 20, 'nodeSpacing': 30, 'rankSpacing': 40}, 'themeVariables': {'primaryColor': '#8B9DAF', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#6E7F91', 'secondaryColor': '#9CAF88', 'secondaryTextColor': '#ffffff', 'secondaryBorderColor': '#7A8D68', 'tertiaryColor': '#C2856E', 'tertiaryTextColor': '#ffffff', 'tertiaryBorderColor': '#A06A54', 'lineColor': '#B5A99A', 'textColor': '#4A4A4A', 'mainBkg': '#8B9DAF', 'nodeBorder': '#6E7F91', 'clusterBkg': 'rgba(139,157,175,0.12)', 'clusterBorder': '#B5A99A', 'edgeLabelBackground': 'transparent'}}}%%
flowchart TD
  CMD["Slash Commands<br><i>user interface wrapper</i>"]
  HK["Hooks<br><i>execution pipeline wrapper</i>"]
  PL["Plugins<br><i>composition + distribution wrapper</i>"]
  MCP["MCP<br><i>capability wrapper</i>"]
  SK["Skills<br><i>reasoning wrapper</i>"]
  AG["Custom Agents<br><i>persona wrapper</i>"]
  CORE["<b>Core Agent</b><br>prompt --> reason --> tools"]

  CMD --> HK --> PL --> MCP --> SK --> AG --> CORE

  style CMD fill:#8B9DAF,color:#fff,stroke:#6E7F91
  style HK fill:#9CAF88,color:#fff,stroke:#7A8D68
  style PL fill:#C2856E,color:#fff,stroke:#A06A54
  style MCP fill:#B39EB5,color:#fff,stroke:#8E7A93
  style SK fill:#C4A882,color:#fff,stroke:#A08562
  style AG fill:#8E9B7A,color:#fff,stroke:#6E7B5A
  style CORE fill:#8B9DAF,color:#fff,stroke:#6E7F91,stroke-width:3px
Figure 8: Decorator pattern at system scale, shown as concentric wrappers around a central core. The innermost node is the Core Agent (prompt-reason-tools loop, highlighted with a thick border). Six independent wrapper layers surround it: Custom Agents (persona wrapper), Skills (reasoning wrapper), MCP (capability wrapper), Plugins (composition and distribution wrapper), Hooks (execution pipeline wrapper), and Slash Commands (user interface wrapper). Each layer is independently removable – the core agent executes the same loop regardless of which wrappers are active.

How to read this diagram. Start at the top with Slash Commands (the outermost wrapper) and follow the arrows inward through Hooks, Plugins, MCP, Skills, and Custom Agents, arriving at the Core Agent node at the bottom (highlighted with a thick border). Each node is a decorator layer that wraps the one below it. The core agent executes the same prompt-reason-tools loop regardless of which wrapper layers are active – any layer can be removed independently without affecting the others.

Each wrapper layer is independent. You can remove skills without affecting hooks. You can add MCP servers without changing custom agents. The core agent code is unaware of specific extensions – it executes the same loop regardless of which wrappers are active.

Open/Closed Principle

The extension architecture is a textbook implementation of the Open/Closed Principle: open for extension, closed for modification. Adding a new hook does not require modifying the tool execution code. Adding a new MCP server does not require modifying the tool registry code. Adding a new skill does not require modifying the prompt assembly code. Each extension point has a registration interface (settings configuration, file system conventions) that does not touch the core.

Strategy Pattern (Tools and Agents)

The tool registry uses the Strategy pattern: each tool is a strategy for performing a specific operation, and the model selects which strategy to invoke. MCP extends this by dynamically registering new strategies at runtime. Custom agents apply the same pattern to the agent itself – each agent type is a strategy for handling a class of tasks, and the orchestrator selects the appropriate one.

Intercepting Filter Pattern (Hooks)

Hooks are the Intercepting Filter pattern from enterprise Java. PreToolUse and PostToolUse form a filter chain that wraps every tool execution. Each filter can observe, modify, or reject the request. The chain is configurable (via settings) and extensible (add a new hook without modifying existing ones). Spring’s HandlerInterceptor.preHandle() / postHandle() is the direct analogue.

Actor Model (Custom Agents)

Each custom agent runs in isolation with its own conversation context, tool permissions, and system prompt. Communication happens through message passing (the Agent tool). No shared mutable state between agents. This is the Actor Model – the same concurrency primitive used by Erlang’s processes, Akka’s actors, and Go’s goroutines-with-channels.


Building Your Own Extension – A Decision Tree

Choosing the right extension point depends on what you need to accomplish. The decision tree is simple once you understand what each mechanism can and cannot do.

%%{init: {'theme': 'neutral', 'flowchart': {'useMaxWidth': false, 'htmlLabels': true, 'padding': 20, 'nodeSpacing': 30, 'rankSpacing': 40}, 'themeVariables': {'primaryColor': '#8B9DAF', 'primaryTextColor': '#ffffff', 'primaryBorderColor': '#6E7F91', 'secondaryColor': '#9CAF88', 'secondaryTextColor': '#ffffff', 'secondaryBorderColor': '#7A8D68', 'tertiaryColor': '#C2856E', 'tertiaryTextColor': '#ffffff', 'tertiaryBorderColor': '#A06A54', 'lineColor': '#B5A99A', 'textColor': '#4A4A4A', 'mainBkg': '#8B9DAF', 'nodeBorder': '#6E7F91', 'clusterBkg': 'rgba(139,157,175,0.12)', 'clusterBorder': '#B5A99A', 'edgeLabelBackground': 'transparent'}}}%%
flowchart TD
  Q["<b>What do you need?</b>"]

  DO["Agent needs to DO<br>something new?"]
  THINK["Agent needs to THINK<br>differently?"]
  ENFORCE["Need to ENFORCE<br>rules?"]
  ROLE["Need a SPECIALIZED<br>ROLE?"]
  CTRL["Need DETERMINISTIC<br>CONTROL?"]
  DIST["Need to DISTRIBUTE<br>a combination?"]

  MCP["<b>MCP Server</b><br><i>Any language + JSON-RPC</i>"]
  SKILL["<b>Skill</b> (SKILL.md)<br><i>Markdown with frontmatter</i>"]
  HOOK["<b>Hook</b> (Pre/PostToolUse)<br><i>JSON config + shell cmd</i>"]
  AGENT["<b>Custom Agent</b><br><i>Markdown or YAML</i>"]
  CMD["<b>Slash Command</b><br><i>Markdown file</i>"]
  PLUGIN["<b>Plugin</b><br><i>Directory + plugin.json</i>"]

  Q --> DO --> MCP
  Q --> THINK --> SKILL
  Q --> ENFORCE --> HOOK
  Q --> ROLE --> AGENT
  Q --> CTRL --> CMD
  Q --> DIST --> PLUGIN
  style Q fill:#8B9DAF,color:#fff,stroke:#6E7F91
  style DO fill:#9CAF88,color:#fff,stroke:#7A8D68
  style THINK fill:#C2856E,color:#fff,stroke:#A06A54
  style ENFORCE fill:#B39EB5,color:#fff,stroke:#8E7A93
  style ROLE fill:#C4A882,color:#fff,stroke:#A08562
  style CTRL fill:#8E9B7A,color:#fff,stroke:#6E7B5A
  style DIST fill:#8B9DAF,color:#fff,stroke:#6E7F91
  style MCP fill:#9CAF88,color:#fff,stroke:#7A8D68
  style SKILL fill:#C2856E,color:#fff,stroke:#A06A54
  style HOOK fill:#B39EB5,color:#fff,stroke:#8E7A93
  style AGENT fill:#C4A882,color:#fff,stroke:#A08562
  style CMD fill:#8E9B7A,color:#fff,stroke:#6E7B5A
  style PLUGIN fill:#8B9DAF,color:#fff,stroke:#6E7F91
Figure 9: Extension point decision tree for choosing the right mechanism. The central question ‘What do you need?’ branches into six needs, each mapping to exactly one extension point with its implementation format: new capabilities map to MCP Server (any language + JSON-RPC), behavioral changes to Skill (SKILL.md with Markdown frontmatter), enforcement to Hook (JSON config + shell command), specialized roles to Custom Agent (Markdown or YAML), deterministic control to Slash Command (Markdown file), and distribution to Plugin (directory + plugin.json). The one-to-one mapping ensures no ambiguity in extension point selection.

How to read this diagram. Start at the central question “What do you need?” at the top. Six branches descend to six distinct needs (DO something new, THINK differently, ENFORCE rules, SPECIALIZED ROLE, DETERMINISTIC CONTROL, DISTRIBUTE a combination), and each maps to exactly one extension point at the bottom (MCP Server, Skill, Hook, Custom Agent, Slash Command, Plugin respectively). The one-to-one mapping is the point: there is no ambiguity about which mechanism to use for a given need.

Minimal Examples

Hook – Block Bash commands that reference production:

{
  "hooks": {
    "PreToolUse": [{
      "matcher": { "tool": "Bash" },
      "hooks": [{
        "type": "command",
        "command": "echo $TOOL_INPUT | grep -q 'production' && exit 2 || exit 0"
      }]
    }]
  }
}

Skill – Python coding guidelines:


---
name: python-style
description: Enforce Python coding standards

---
When writing Python code:
- Use type hints on all function signatures
- Follow PEP 8 naming conventions
- Prefer f-strings over .format()
- Use pathlib instead of os.path

Custom Agent – Documentation writer:


---
name: doc-writer
description: Writes API documentation
tools:
  - Read
  - Grep
  - Write
model: sonnet
maxTurns: 30

---
You are a technical writer. Generate clear, accurate API
documentation. Read the source code, identify public APIs,
and write docs in JSDoc/docstring format.

Plugin – Bundle all three:

my-team-plugin/
+-- .claude-plugin/
|   +-- plugin.json          # {"name": "my-team-tools", ...}
+-- agents/
|   +-- doc-writer.md        # Custom agent
+-- skills/
|   +-- SKILL.md             # Python guidelines
+-- hooks/
    +-- hooks.json           # Production guard hook

Summary

Extensibility is a layered architecture, not a single API. Claude Code’s six extension points operate at different layers – prompt assembly (skills), tool registry (MCP), execution pipeline (hooks), persona isolation (custom agents), distribution (plugins), and user interface (slash commands). This layering means extensions compose naturally: a skill shapes reasoning, a hook enforces the decision, an MCP server provides the capability, a custom agent runs the review, a plugin bundles the package, and a slash command gives the user direct control. No single-API design could achieve this composition.

Focused extension points are easier to use correctly and harder to misuse. A hook that can only intercept tool execution and return an exit code is much harder to misuse than a general-purpose callback that receives the full agent state. The constraint is the value. If your hook can block actions (exit 2) but cannot modify the prompt, you know exactly what it does and what it cannot do. If your skill can inject prompt text but cannot block actions, you know its power and its limits. Focused mechanisms are self-documenting.

The composition of focused mechanisms is more powerful than one general-purpose API. A general-purpose plugin API that handles everything – capabilities, enforcement, behavior, personas, distribution – would be complex, error-prone, and hard to reason about. Six focused mechanisms that compose achieve more because each is simple enough to understand completely. The complexity lives in the composition, not in any individual mechanism.

Hooks are the only enforcement mechanism. This is the single most important fact about the extension architecture. Skills suggest. MCP provides. Custom agents restrict. Slash commands control. But only hooks can say “no” – exit code 2 blocks the action, period. If you need a guarantee that something will not happen, hooks are your only option.

Plugins are the packaging format, not a seventh mechanism. The plugin system does not add new capabilities. It bundles the other five into distributable packages with lifecycle management, versioning, dependency resolution, and marketplace distribution. Understanding this distinction prevents the confusion of treating plugins as “super-extensions” rather than “extension bundles.”

Configuration is the composition glue. The four-scope settings hierarchy (managed > local > project > user) determines which extensions are active, how they are configured, and who can override whom. Enterprise policy enforcement happens through configuration scope precedence, not through special-case code. Three lines of isPluginBlockedByPolicy() enforce enterprise compliance because the settings system already handles precedence.


For the foundational treatment of hooks and lifecycle events, see Part III.4: Hooks & Lifecycle. For multi-agent coordination and custom agent types, see Part II.3: Multi-Agent Orchestration. For slash commands and the terminal UI, see Part V.1: CLI, Commands & Terminal UI. For system reminders that feed hook results back to the model, see Part III.2: Context Compaction.