Skip to content

Plugins in the SDK

Load local plugin directories into Agent SDK sessions to add skills, agents, hooks and MCP servers, then confirm they loaded and call their skills.

A plugin bundles skills, subagents, hooks and MCP server definitions into one directory you can share between projects. The Agent SDK can load plugins straight from disk, which is the easiest way to give a deployed agent the same toolkit your team uses in the CLI, or to load skills from a path outside the usual .claude/ locations.

What a plugin can contribute

ComponentWhat it adds to the session
SkillsCapabilities Claude uses when relevant, or that you run as /plugin-name:skill-name
AgentsSpecialist subagents
HooksHandlers for tool and lifecycle events
MCP serversExternal tools via the Model Context Protocol

How to build a plugin is covered in creating plugins and the manifest reference. This page is about loading them from code.

Loading plugins

Pass a plugins array. Each entry is { type: "local", path }; local is the only type the SDK accepts. Load as many as you like.

import { query } from "@anthropic-ai/claude-agent-sdk";
import { fileURLToPath } from "node:url";

const housekeeping = fileURLToPath(new URL("./plugins/housekeeping", import.meta.url));

for await (const msg of query({
  prompt: "Tidy up stale feature flags in this repo",
  options: {
    cwd: "/work/storefront",
    plugins: [
      { type: "local", path: housekeeping },
      { type: "local", path: "./tooling/flag-tools" }
    ]
  }
})) {
  if (msg.type === "result") console.log(msg.subtype);
}
from pathlib import Path
from claude_agent_sdk import ClaudeAgentOptions

opts = ClaudeAgentOptions(
    cwd="/work/storefront",
    plugins=[
        {"type": "local", "path": str(Path(__file__).parent / "plugins" / "housekeeping")},
        {"type": "local", "path": "./tooling/flag-tools"},
    ],
)

Path rules

  • Relative paths resolve against the cwd option.
  • Absolute paths are used as given.
  • Tilde is not expanded. ~/plugins/x will not work. Build the path with os.homedir() or Path.home().
  • Point at the plugin root: the folder that contains skills/, agents/, hooks/, commands/ or .claude-plugin/.
  • A path that does not exist is skipped silently and the session carries on. Always check the init message.

Marketplace and CLI-installed plugins

The SDK only loads local directories. For a plugin from a marketplace or a Git repository, download or clone it first and pass the folder. Plugins you installed through the CLI with /plugin install name@marketplace live under ~/.claude/plugins/, so you can point the SDK at the installed copy.

Checking what loaded

The system message with subtype init reports:

FieldContents
pluginsEach loaded plugin's name and absolute path
skillsSkill names, plugin ones prefixed, for example flag-tools:find-stale
slash_commandsAll commands, including plugin skills and plugin command files with the same prefix
plugin_errorsWhy a plugin failed to load
from claude_agent_sdk import query, ClaudeAgentOptions, SystemMessage

async for msg in query(prompt="hello", options=opts):
    if isinstance(msg, SystemMessage) and msg.subtype == "init":
        print("plugins:", msg.data.get("plugins"))
        print("skills:", msg.data.get("skills"))
        print("errors:", msg.data.get("plugin_errors"))

In TypeScript these are direct properties on the init message (message.plugins, message.skills and so on).

I log the plugins list on every production start-up. A silently skipped plugin is otherwise very easy to miss.

Using plugin skills

Plugin skills are namespaced with the plugin name so they cannot clash with yours. Claude can pick them up automatically from their descriptions, or you can run one directly:

for await (const msg of query({
  prompt: "/flag-tools:find-stale --older-than 90d",
  options: { plugins: [{ type: "local", path: "./tooling/flag-tools" }], maxTurns: 10 }
})) {
  if (msg.type === "result" && msg.subtype === "success") console.log(msg.result);
}

If you restrict skills with the skills option, list plugin skills as plugin:skill. The skills page covers that option and command dispatch in full.

Plugin layout

The manifest is optional. Without it, Claude Code discovers components from the folder structure.

flag-tools/
├── .claude-plugin/
│   └── plugin.json        optional manifest
├── skills/
│   └── find-stale/
│       └── SKILL.md       one folder per skill
├── commands/
│   └── flag-report.md     older flat-file form of a skill
├── agents/
│   └── flag-auditor.md    subagent definitions
├── hooks/
│   └── hooks.json         event handlers
└── .mcp.json              MCP server definitions

commands/ holds skills as single Markdown files. Both work, but use skills/ for anything new.

Troubleshooting

The plugin is missing from plugins. Read plugin_errors first, then check:

  1. The path points at the plugin root, not at skills/ or the manifest.
  2. plugin.json, if present, is valid JSON.
  3. The directory is readable by the process running the SDK.
  4. The directory exists. Remember relative paths resolve from cwd, not from your script.

A plugin skill does not run.

  1. Use the namespaced form, /plugin-name:skill-name.
  2. Confirm the namespaced name appears in the init skills list.
  3. Each skill needs its own subfolder with a SKILL.md, for example skills/find-stale/SKILL.md.