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
| Component | What it adds to the session |
|---|---|
| Skills | Capabilities Claude uses when relevant, or that you run as /plugin-name:skill-name |
| Agents | Specialist subagents |
| Hooks | Handlers for tool and lifecycle events |
| MCP servers | External 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
cwdoption. - Absolute paths are used as given.
- Tilde is not expanded.
~/plugins/xwill not work. Build the path withos.homedir()orPath.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:
| Field | Contents |
|---|---|
plugins | Each loaded plugin's name and absolute path |
skills | Skill names, plugin ones prefixed, for example flag-tools:find-stale |
slash_commands | All commands, including plugin skills and plugin command files with the same prefix |
plugin_errors | Why 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:
- The path points at the plugin root, not at
skills/or the manifest. plugin.json, if present, is valid JSON.- The directory is readable by the process running the SDK.
- The directory exists. Remember relative paths resolve from
cwd, not from your script.
A plugin skill does not run.
- Use the namespaced form,
/plugin-name:skill-name. - Confirm the namespaced name appears in the init
skillslist. - Each skill needs its own subfolder with a
SKILL.md, for exampleskills/find-stale/SKILL.md.