If you've spent any real time in Claude Code, you've hit the moment where you want it to do more: reach a database it can't see, stop polluting your conversation with 40 file reads, remember a workflow you keep retyping, or guarantee it never runs rm -rf on the wrong directory. There are four mechanisms for that — MCP servers, subagents, skills, and hooks — and they're easy to confuse because the docs introduce them all at once.
The confusion is costly. Reach for an MCP server when you needed a skill and you've bolted on an external dependency for something that was just knowledge. Put a safety rule in CLAUDE.md when you needed a hook and your "rule" is a suggestion the model can ignore. So before we get into each one, here's the mental model that actually disambiguates them:
A skill changes behavior, a subagent protects context, and an MCP server adds capability — and a hook guarantees an action runs deterministically on an event, regardless of what the model decides to do.
These four are a subset of a larger taxonomy. Claude Code's "Extend Claude Code" docs frame seven layers that plug into different parts of the agentic loop: CLAUDE.md (persistent context), skills, code intelligence/LSP, MCP, subagents, agent teams, and hooks — with plugins as the packaging layer on top. We're focused on the four in the title, but I'll touch CLAUDE.md, agent teams, and plugins where they matter, because picking the right tool often means knowing what it isn't.
MCP servers — when Claude needs external capability
MCP (Model Context Protocol) connects Claude to external services and tools it can't reach on its own: querying a database, posting to Slack, controlling a browser, hitting a third-party API, or touching a filesystem over SSH. If the thing you want lives behind a connection or an auth boundary, that's MCP.
The key distinction from a skill: MCP gives Claude purpose-built tools for an external system, with connection and authentication handled by the server. It is capability, not knowledge.
People worry MCP bloats the context window. It used to be a real concern — third-party guides report a typical multi-server setup consuming tens of thousands of tokens before you type anything (one cites roughly 55,000 for a 5-server, ~58-tool setup, though treat that as illustrative). But the default behavior now is friendlier: at session start only tool names load, full JSON schemas stay deferred until a specific tool is needed, and tool search is on by default so idle MCP tools cost minimal context. Run /mcp to see connection status and per-server token cost. Scope precedence is local > project > user.
Reach for MCP when: you're copying something from a browser tab Claude can't see, or you need live data or actions from an external system.
Subagents — when a side task would flood your context
A subagent is a specialized assistant that runs its own agentic loop in an isolated context window, with a custom system prompt, specific tool access, and independent permissions. It does its work separately and returns only a summary to the main conversation.
The job a subagent does best is context protection. When a side task would dump output you'll never reference again — reading dozens of files, running extensive searches, an exhaustive code review — route it to a subagent so your main window stays clean. Beyond that, subagents let you enforce constraints (limit which tools they can touch), specialize behavior with a focused prompt, reuse configurations across projects, and control cost by routing to a faster, cheaper model like Haiku.
Subagents are Markdown files with YAML frontmatter (or created via /agents). A minimal one:
---
name: code-reviewer
description: Reviews diffs for correctness and style issues
tools: Read, Glob, Grep
model: sonnet
---
You are a focused code reviewer. Read the diff, flag real bugs
and convention violations, and return a concise summary.
Frontmatter supports a lot more — disallowedTools, permissionMode, mcpServers, hooks, skills, maxTurns, memory, model, and others. Files resolve by precedence on name conflict: managed (highest) > CLI flag > project (.claude/agents/) > user (~/.claude/agents/) > plugin (lowest). Identity comes only from the name field. One security note: plugin subagents do not support hooks, mcpServers, or permissionMode — those fields are ignored.
Reach for a subagent when: a side task floods the conversation, you want a worker locked to a small tool set, or you want to run something on a cheaper model.
Skills — packaged, reusable expertise
A skill is a SKILL.md file (YAML frontmatter plus a Markdown body) that adds reusable knowledge, workflows, or instructions to Claude's toolkit. Claude loads it automatically when the task matches its description, or you invoke it directly with /skill-name. The body loads only when used, so long reference material costs almost nothing until needed.
There are two flavors:
- Reference skills — knowledge Claude uses throughout a session, like an API style guide or your schema docs.
- Action skills — tell Claude to do something specific, like
/deployrunning a deployment workflow.
If you've used custom slash commands, note they've been merged into skills: .claude/commands/*.md and skills both create a /name command and work the same way. Skills follow the open Agent Skills standard (agentskills.io) that works across multiple AI tools, and Claude Code extends it with a few useful knobs:
disable-model-invocation: truemakes a skill user-only — zero context cost until you invoke it.context: forkruns the skill in an isolated context (subagent-style execution).
Locations and precedence: personal (~/.claude/skills/<name>/SKILL.md), project (.claude/skills/<name>/SKILL.md), and plugin (<plugin>/skills/<name>/SKILL.md). On a name conflict, enterprise/managed wins over personal, which wins over project. Plugin skills are namespaced plugin-name:skill-name, so they can't collide. The directory name becomes the command; frontmatter requires name and description.
Reach for a skill when: you keep pasting the same playbook, you have reference material Claude should consult, or you've retyped the same prompt enough times to make it a command.
Hooks — deterministic lifecycle automation
Hooks are the odd one out, and the most important to understand. They're user-defined handlers that fire automatically on lifecycle events. The critical property: Claude has no awareness of hooks. The system fires them because an event occurred, not because the model decided to. That's what makes them deterministic — a hook always fires on its event; the trigger is guaranteed. A skill, by contrast, depends on Claude interpreting instructions, and the outcome can vary.
This is why the official guidance is blunt: put guardrails in hooks. An instruction like "never edit .env" in CLAUDE.md or a skill is a request, not a guarantee. A PreToolUse hook that blocks the edit is enforcement. If a rule must hold every time, make it a hook.
Hooks aren't only shell scripts. Handler types include: command (shell), http (call an endpoint), mcp_tool (invoke an MCP tool), prompt (a single-turn LLM evaluation like "is this command safe?"), and agent (a subagent for verification; experimental). So yes — a hook can run an LLM prompt or a subagent, not just a script.
Events span session, per-turn, agentic-loop, agent/task, and environment phases. The ones you'll touch most: PreToolUse (can block a call), PostToolUse, UserPromptSubmit, Stop, SubagentStop, SessionStart, SessionEnd. There are many more (PermissionRequest, PreCompact/PostCompact, FileChanged, Notification, and others). Reported total counts vary by source — you'll see "12," "18," "30" depending on whether it's CLI-only or shared with the Agent SDK, so don't anchor on a single number.
Config is JSON with three nesting levels — event, matcher, handlers:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{ "type": "command", "command": "./scripts/block-rm-rf.sh" }
]
}
]
}
}
Matchers can be a tool name (Bash), an alternation (Edit|Write), *, or a regex (mcp__memory__.*). Config lives in ~/.claude/settings.json (all projects), .claude/settings.json (project, shareable), .claude/settings.local.json (gitignored), plugin hooks/hooks.json, skill/agent frontmatter, or managed policy. Exit codes matter: 0 = success, 2 = blocking error (stderr is shown to Claude), any other non-zero = non-blocking.
Reach for a hook when: you want something to happen on every matching event without asking — linting after edits, blocking unsafe commands, logging, notifications, format-on-save — or you need to enforce a hard rule.
The decision guide
The fastest way to choose is to match the trigger you're feeling to the mechanism. The official "build your setup over time" framing maps cleanly:
| You notice... | Reach for |
|---|---|
| A convention Claude gets wrong twice | CLAUDE.md rule |
| Retyping the same prompt | A user-invocable skill |
| Pasting the same playbook a third time | A skill |
| Copying from a browser tab Claude can't see | An MCP server |
| A side task flooding the conversation | A subagent |
| Wanting something to happen every time, no asking | A hook |
| A second repo needing the same setup | A plugin |
And the context-cost cheat sheet, since that drives a lot of real decisions:
| Mechanism | Context cost |
|---|---|
| CLAUDE.md | Full content, every request |
| Skill | Description at start; body when used (low) |
| MCP | Tool names at start; schemas on demand (low until used) |
| Subagent | Isolated — never touches main window |
| Hook | Zero unless it returns output |
Combine, don't choose
The most common mistake is treating these as either/or. They're complementary. The combinations worth knowing:
- MCP + skill — MCP connects Claude to your database; a skill documents the schema and your common query patterns. Capability plus the knowledge to use it well.
- Subagent + skill — an isolated worker that preloads your conventions via its
skills:field, optionally on a cheaper model with a scoped MCP server for live data. - Skill in a fork — a skill with
context: forkruns subagent-style, getting isolation without a separate agent file.
When parallel subagents stop being enough — you hit context limits or they need to talk to each other — that's the signal to graduate to agent teams (independent sessions that self-coordinate; experimental, off by default). And when you want to ship any combination of these across repos or to your team, that's what plugins are for: one installable unit bundling skills, hooks, subagents, and MCP servers, distributed via marketplaces.
Bottom line
Don't overthink which "category" a need falls into — name the trigger. Need external data or actions? MCP. Drowning your context in throwaway output? Subagent. Repeating knowledge or a workflow? Skill. Need a rule enforced every single time, no matter what the model thinks? Hook — because in CLAUDE.md it's a polite request, and in a PreToolUse hook it's a guarantee.
A sane starting setup looks like one short CLAUDE.md, a scoped MCP config with only the servers you actually use, one safety hook, one reusable skill for repeated work, and subagents only when research or review would pollute your main session. Build from there as the triggers show up — and resist the giant plugin pile.