Course · Claude Code · Extending Claude Code
Bundle your setup into a plugin
A working agent setup for one repository ends up scattered across four places. Take the small TypeScript payments service this course uses as its example, after the earlier lessons have equipped it. Two MCP servers - connections that let the agent query external systems, db for the team’s staging Postgres and jira for the ticket board, both a constructed scenario standing in for a real team’s systems - sit in your MCP config, the project’s .mcp.json. A PreToolUse hook that refuses any commit touching src/payments/ledger.ts unless a test changed too lives as ledger-needs-test.sh in the repo’s .claude/hooks/ directory, with its wiring in .claude/settings.json. A finish-pr skill (a SKILL.md procedure the agent runs to wrap up a branch) sits in .claude/skills/, and a read-only ledger-auditor subagent (a delegated worker defined in a markdown file) sits in .claude/agents/. A new engineer joining the team gets none of it unless they copy files from several places and get the wiring right by hand. The skills and subagents chapters build the last two pieces, if you want them in full; this page needs only their names and locations.
A plugin solves the scatter. It folds everything into one named, versioned, installable unit - skills, agents, hooks, and MCP servers together. Install the plugin and the whole set arrives at once, already wired. One boundary to know before you start: a plugin does not carry rules. A CLAUDE.md at the plugin root is not loaded as project context - the course’s context principle, what information is available when the agent decides, gives an install no always-in-view channel - so persistent instructions ship as skills.
The manifest and the directory contract
Section titled “The manifest and the directory contract”A plugin is a directory. The manifest at .claude-plugin/plugin.json is technically optional when components sit in their default locations, but you want it: it carries the plugin’s identity and version.
{ "name": "payments-kit", "description": "Database + board reach, the ledger-needs-test gate, and the wrap-up ritual for the payments service", "version": "1.0.0"}The name becomes the plugin’s namespace: its skill installs as /payments-kit:finish-pr, which is why two plugins can ship a skill of the same name without colliding. The version matters for updates - installed users get a new release when that string changes.
Around the manifest, your scattered pieces drop into the directories the plugin format expects. The mapping from your current setup is almost mechanical:
payments-kit/├── .claude-plugin/│ └── plugin.json # the manifest (the ONLY thing inside .claude-plugin/)├── skills/│ └── finish-pr/│ └── SKILL.md # the wrap-up skill, lifted as-is├── hooks/│ ├── hooks.json # the ledger gate's PreToolUse config│ └── ledger-needs-test.sh├── agents/│ └── ledger-auditor.md # the read-only auditor from the subagents chapter└── .mcp.json # the db + jira server definitionsTwo details save real grief. First - and the reference is explicit about this - only plugin.json goes inside .claude-plugin/. The skills/, hooks/, agents/ directories and the .mcp.json file all sit at the plugin root. Second, the hook config moves out of your settings.json into a dedicated hooks/hooks.json, and the JSON inside is the same hooks object the gate lesson writes into settings - the PreToolUse block copies across with one exception: the hook command, changed below. (If you are starting without a hook block of your own, the gate lesson writes the one to copy.) The full directory contract and manifest schema are on the plugins reference.
The packer below shows the same layout for five tools, so you can see which directories each expects; the Claude Code tab is the tree above plus a slash-command piece this plugin doesn’t fold:
The .mcp.json at the plugin root carries the db and jira definitions in the same shape claude mcp add --scope project writes to a project’s .mcp.json. A project’s own .mcp.json triggers an approval prompt before first use, because a cloned repo should never auto-connect your agent to someone else’s servers (the scope lesson covers that rail). A plugin’s .mcp.json does not go through that rail: the plugin is the reviewed unit, and installing it is the approval - the course’s control principle, what guides execution, enforces boundaries, or allows intervention, with the approval moved from connection time to install time. The plugins reference documents the behavior directly: plugin MCP servers start automatically when the plugin is enabled, with no per-server prompt.
Run it from disk before anyone ships it
Section titled “Run it from disk before anyone ships it”You do not have to publish anything to try the plugin. Load it straight from disk:
claude --plugin-dir ./payments-kitThat session runs as if the plugin were installed: the database and board tools appear, the ledger hook arms, and finish-pr shows up under /payments-kit:finish-pr. As you tweak the plugin, /reload-plugins picks up changes without a restart. --plugin-dir loads the plugin for the duration of that session without installing it, so you can develop against the bundle and nothing lands in your installed set until you choose to install. This is the tightening loop: get the folder behaving exactly like your hand-built setup before anyone else touches it.
One attribution caveat: the repo’s originals - the MCP config, the .claude/settings.json hook wiring, the .claude/skills/ and .claude/agents/ copies - stay active alongside the plugin, so the tools appearing and the hook firing prove nothing by themselves; the namespaced /payments-kit:finish-pr is unmistakably the plugin’s, and for the rest the clean check is to temporarily retire the originals and rerun the command - anything that disappears is something you have not folded yet, and retiring them is what the fold is for.
What you have now is the entire payments-service agent configuration as one directory - reach, gate, and ritual, manifested and versioned, running from a single flag. The last step is making it run on your team’s machines from a single command, which is what a marketplace is for.