People keep asking me “should I write a skill or set up an MCP server for this?” as if they’re competing options. They’re not. Skills, MCP servers, and plugins live at three different layers of Claude Code, and once you internalize what each one actually is, the decision becomes almost mechanical. They compose well together; in any non-trivial setup you’ll end up using all three.
This page is the disambiguation I wish I’d had eighteen months ago. We’ll walk through what each surface actually does at runtime, when to reach for which, a short decision tree, three worked scenarios from real projects, and how to migrate between them when something outgrows its original shape.
Skills are instructions Claude follows. They’re Markdown files under ~/.claude/skills/<slug>/SKILL.md that auto-load when your prompt matches their description field. Use them when you want Claude to behave a certain way — follow a checklist, write code in a specific house style, refuse to do certain things, structure output in a particular format.
MCP servers are tools Claude calls. They’re separate processes that speak the Model Context Protocol over stdio or HTTP, exposing callable tools, readable resources, and reusable prompts. Use them when Claude needs to do something it can’t do with just bash and file edits — query your Postgres, search Notion, talk to Linear’s API.
Plugins are a packaging format. They’re directories with a .claude-plugin/plugin.json manifest that bundle skills, hooks, subagents, and MCP server references into one shareable unit. Use them when you want to distribute a coherent toolkit to your team or to the world.
So the typical question “skill or MCP?” is malformed. The real question is “does this need behavior, or does it need capability, or both?” If it’s both, you write a skill that calls an MCP tool, and probably ship them together as a plugin.
A skill is a directory. That’s really it. The simplest possible one looks like:
~/.claude/skills/conventional-commits/
└── SKILL.mdInside SKILL.md you have YAML frontmatter followed by Markdown instructions:
---
name: conventional-commits
description: Write Conventional Commits-compliant git commit messages. Use when the user asks to commit changes, write a commit message, or stage and commit.
---
# Conventional Commits
When the user asks you to commit, follow this format:
`<type>(<scope>): <subject>`
Types: feat, fix, docs, style, refactor, test, chore...The discovery mechanism is straightforward: at session start, Claude Code scans ~/.claude/skills/ (user-scoped) and .claude/skills/ in your project (project-scoped) and reads the frontmatter from each SKILL.md. It doesn’t load the body yet — only the name and description. When you send a prompt, Claude matches your intent against those descriptions and pulls in the body of any matching skill before responding.
This is why the description field is the single most important line in a skill. It’s the trigger. Write it like a librarian indexing a book: “Use when the user wants to X, Y, or Z.” Vague descriptions either never fire or fire on everything — both failure modes ruin the experience.
Optional frontmatter fields worth knowing:
allowed-tools — restrict what built-in tools the skill can call (e.g. [Bash, Read, Edit]). Useful for read-only skills.model — pin the skill to a specific Claude model.user-invokable — explicitly mark whether the skill can be invoked by name as a slash command.allowed-tools can also list MCP tool names — this is the bridge into the MCP layer.Skills don’t execute code on their own. They don’t have a process. They’re text Claude reads. If you want Claude to do something beyond reading files, editing them, and running shell commands, you need tools — and that’s where MCP comes in. For a deeper walkthrough of writing your first one, see What is a Claude Code skill.
The Model Context Protocol is an open spec for connecting language models to data sources and tools. An MCP server is a separate process — written in any language — that speaks this protocol and exposes three primitives to Claude:
search_issues(repo, query), execute_sql(query), create_page(title, content).You wire MCP servers in via a config file. Project-scoped configs live in .claude/mcp.json; global configs live in ~/.claude/mcp.json (or claude_desktop_config.json for the desktop app). A minimal entry:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you/projects"]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_TOKEN": "ghp_..." }
}
}
}At session start, Claude Code launches each configured MCP server as a subprocess (for stdio transport) or connects over HTTP/SSE (for remote servers), handshakes to discover what tools and resources each one exposes, and surfaces them to the model. From the model’s perspective, MCP tools look just like built-in tools — it picks them up automatically when the conversation needs them.
The key mental model: an MCP server is a capability layer. It’s how Claude reaches outside its sandbox to talk to your Postgres, your Linear, your filesystem outside the project root, your internal HTTP APIs. The MCP server handles authentication, rate limiting, schema translation, and error handling — all the boring infrastructure stuff that has no business being repeated in instructions.
Crucially, MCP servers are stateful processes. They can hold a database connection, cache API responses, maintain auth tokens. A skill cannot do any of that — a skill is just text. This is the cleanest separation: MCP for stateful capability, skills for behavior on top.
If you’re trying to decide whether something “could be a skill instead of an MCP,” ask: does it need to make network calls, hold connections, or do anything Claude couldn’t accomplish by running curl or editing a file? If yes — MCP. If no — skill.
A plugin is a packaging unit. It’s what you make when you want to ship a coherent toolkit — multiple skills, maybe an MCP server reference, maybe a hook or two, maybe a subagent definition — as one install.
Structurally, a plugin is a directory with a .claude-plugin/ folder containing a plugin.json manifest:
my-team-toolkit/
├── .claude-plugin/
│ └── plugin.json
├── skills/
│ ├── code-review/
│ │ └── SKILL.md
│ ├── pr-description/
│ │ └── SKILL.md
│ └── release-notes/
│ └── SKILL.md
├── hooks/
│ └── on-session-start.sh
└── mcp/
└── jira-server.jsonThe manifest is the metadata layer:
{
"name": "my-team-toolkit",
"version": "1.4.0",
"description": "Engineering team toolkit: review, PRs, releases.",
"author": "Acme Engineering",
"skills": ["code-review", "pr-description", "release-notes"],
"hooks": { "session-start": "hooks/on-session-start.sh" },
"mcp": ["mcp/jira-server.json"]
}The thing to internalize is that a plugin doesn’t add new capabilities to Claude Code. Everything inside a plugin — the skills, the MCP server config, the hooks — could exist on its own outside a plugin. The plugin layer exists for three reasons:
~/.claude/skills/, then edit mcp.json to add this block, then install this hook script.” With plugins, it’s one install command.If you’re writing one skill for yourself, you don’t need a plugin. Just drop a directory into ~/.claude/skills/ and you’re done. Plugins start earning their keep when you have three or more pieces that should ship together, or when you’re distributing to anyone who isn’t you.
The plugin layer is the youngest of the three surfaces and is still evolving. The manifest schema in particular has gained fields over time; check the manifest of a plugin you’re forking before assuming yours needs to match exactly.
Run this five-question gauntlet on your use case. It’s reductive but it covers ~90% of real decisions.
Question 1: Does Claude need to talk to a system it can’t reach with curl, git, file edits, or shell commands?
If yes — database connections, auth’d third-party APIs, internal services with custom protocols, anything stateful — you need an MCP server. There’s no shortcut.
If no — everything Claude needs is reachable from the existing built-in tools — proceed to Q2.
Question 2: Am I trying to change how Claude behaves, not what it can reach?
House style. Output format. A checklist to follow. Things to refuse. A specific multi-step workflow. These are behavior — they belong in a skill.
If the answer is yes and Q1 was no — you want a skill. Stop here.
If yes to both Q1 and Q2 — you want a skill that calls an MCP tool. Both. Skip to composition.
Question 3: Is this for me alone, or for others?
For you alone, on one machine: a bare skill directory or a couple of bare MCP entries are fine. Don’t wrap them in a plugin — you’re adding ceremony for no benefit.
For your team, your company, or the public: package it as a plugin. Installation, versioning, and updates are not optional concerns when more than one person uses something.
Question 4: Will the thing I’m building need to maintain state between Claude’s tool calls?
Holding a database connection. Caching API responses across the session. Tracking a multi-step transaction. All MCP territory. Skills are stateless — every invocation reads the same SKILL.md, with no memory of prior runs.
Question 5: How often will this change?
Skills are the cheapest to iterate on — edit a Markdown file, restart the session, done. MCP servers are the most expensive — you’re writing real code, testing protocol compliance, handling errors. If your logic is going to evolve weekly, lean toward expressing as much of it as possible in skill form even if you need a thin MCP underneath. The MCP exposes raw capabilities; the skill orchestrates them.
One non-question that comes up a lot: “Which is more powerful?” The framing is wrong. MCP servers give Claude new capabilities. Skills give Claude new habits. A team with great MCPs but no skills has a powerful set of tools and no opinion about how to use them. A team with great skills but no MCPs has strong opinions but can’t reach beyond what Claude could already reach. You want both.
The three layers stack like this:
┌─────────────────────────────────────────────────┐
│ Plugin (distribution + versioning layer) │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ Skills │ │ MCP server refs │ │
│ │ (behavior) │ │ (capability) │ │
│ │ │ │ │ │
│ │ SKILL.md ───────┼──┼──▶ tools │ │
│ │ allowed-tools: │ │ resources │ │
│ │ [mcp__github__]│ │ prompts │ │
│ └──────────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────┘
│ │
▼ ▼
Claude reads Claude calls
these as text these as toolsThe most important composition is skill calling MCP tool. When you declare an MCP server in your config, its tools appear to Claude with names like mcp__github__search_issues or mcp__postgres__query. Your skill can reference them in its allowed-tools frontmatter and instruct Claude to use them in its body:
---
name: issue-triage
description: Triage GitHub issues by labelling, prioritising, and assigning. Use when reviewing the issue backlog.
allowed-tools: [mcp__github__search_issues, mcp__github__update_issue, Read]
---
# Issue Triage
When triaging issues:
1. Call `mcp__github__search_issues` with `is:open no:label` to find untriaged issues.
2. For each issue, read the body and classify it as `bug`, `feature`, or `question`.
3. Apply the appropriate label via `mcp__github__update_issue`.
4. If a bug, assign severity (P0-P3) based on the rubric below.
## Severity rubric
- P0: Data loss, security, production outage. Page on-call.
...The skill carries the policy (how to triage, the rubric, what good looks like). The MCP carries the capability (talking to GitHub’s API). Neither could do the job alone. Together they replace what would otherwise be a 200-line Python script you’d have to maintain.
Plugins compose by bundling. A plugin can ship four skills, list a required MCP server in its manifest, install a session-start hook that warms a cache, and define a subagent for a specialized sub-task. Users install the plugin and get the whole stack; uninstall it and the whole stack goes away cleanly. This is why I write very few standalone skills any more — even when something starts as a single skill, I usually have at least one MCP server I want to ship alongside it, so plugin format becomes the natural home.
One subtle composition point: the same MCP server can be referenced from multiple skills in different plugins. The MCP layer is shared infrastructure. Plugins reference MCP servers by name and config; if the user already has the Notion MCP installed, two different plugins that both want to use it just point at the existing installation. This is what makes the ecosystem tractable — you don’t end up with twelve copies of the same database connector.
This is a pure capability problem. Notion is an external service with a REST API, OAuth, rate limits, and a quirky block schema. There’s no way Claude is going to talk to it from a Markdown skill.
Answer: install the Notion MCP server. Notion ships an official one. You add an entry to your mcp.json, set your integration token in the env vars, and Claude immediately has mcp__notion__search, mcp__notion__get_page, mcp__notion__create_page, etc. No skill needed unless you want to encode a specific workflow (“always tag pages with `triage` when creating them”) — in which case write a thin skill on top.
This is pure behavior. The checklist exists in someone’s head or a Notion doc. You want Claude to apply it consistently to PRs. No new capability is needed — Claude can already read code, run linters, and write comments.
Answer: write a skill. Put the checklist in ~/.claude/skills/code-review/SKILL.md. Set the description to fire on review-intent prompts: “Use when reviewing a pull request, doing a code review, or auditing changes for quality.” Encode your checklist as numbered steps. Include negative examples (“don’t comment on style if the linter would catch it”). If you want this enforced across the team, package it as a plugin and have everyone install it. Browse the engineering category for prior art on review skills you can fork.
This is where all three layers earn their place. Incident response touches PagerDuty (capability), Datadog (capability), the internal runbook repo (capability), and has very specific procedures (behavior) — assign incident commander, page secondary, post a status update every 30 minutes, etc.
Answer: build a plugin. The plugin bundles:
The whole bundle ships as one plugin. New engineers install it on day one. The plugin’s manifest pins versions of the MCP servers it depends on. When the incident-response process evolves, you bump the plugin version, push to your internal registry, and everyone updates.
This is the pattern I’ve seen produce real lift in production teams — not any single skill or MCP server in isolation.
Things outgrow their original shape. Here are the common transitions and how to handle them.
You wrote a skill. It got good. You added a second skill that calls the first. You added a hook to set up some env vars. You’re now distributing it by telling people to copy two directories. This is the moment to package as a plugin.
The migration is mechanical: create .claude-plugin/plugin.json at the root of your distribution, list your skills in the manifest, move skills into a skills/ subdirectory if they aren’t there already, add the hook reference. The skills themselves don’t change. Bump to v1.0.0 and push.
I see this one a lot. Someone writes a 400-line MCP server in TypeScript whose entire purpose is to expose three tools: format_commit_message, parse_jira_ticket, generate_pr_title. None of them touch the network. None of them hold state. They’re just text transformations.
Throw the MCP away. These are skill territory — tell Claude how to format commit messages in a skill, and let it do the formatting itself. MCP servers should earn their existence by reaching outside Claude’s sandbox or holding state. If yours doesn’t, you’ve built a Rube Goldberg machine.
The opposite case. You wrote a skill with a 50-step procedure that’s really “here’s how to talk to our internal deployment API.” Each step has error handling, retries, schema validation. Claude follows the steps but gets them subtly wrong sometimes.
The lesson: deterministic procedural logic with strict error semantics belongs in code. Wrap the deployment API in an MCP server with one or two well-typed tools. Let the MCP handle the “what is the correct API call” problem deterministically. Keep a thin skill that says “to deploy, call the deploy tool with these arguments” — or skip the skill entirely if Claude can figure that out from the tool descriptions.
Sometimes a plugin doesn’t pay rent. You bundled four skills together; turns out two of them are useless and the other two should ship separately. Unbundle. There’s no shame in killing a plugin and republishing its components individually if that’s actually how they’re used. Plugins are infrastructure, not identity.
The general rule when in doubt: let the right shape emerge from how people actually use the thing. If a skill is constantly being installed alongside the same MCP, plugin them together. If a plugin’s skills are being installed à la carte while the rest is ignored, unbundle. The shape should fit the use, not the other way around.
A short list of the recurring errors, in roughly the order I encounter them in code reviews.
Treating skill descriptions as marketing copy. The description field is a trigger string for the matching algorithm, not a tagline. “A comprehensive, AI-powered tool for modern software engineering excellence” matches nothing. “Use when the user asks to write or review a git commit message, or to commit staged changes” matches reliably. Write descriptions in the form “Use when the user wants to X.” The full list of anti-patterns lives in skill-md anti-patterns.
Writing an MCP server when bash would do. If your “MCP server” just shells out to git, jq, or curl, delete it. Claude has Bash. Write a skill that tells Claude to run the right shell commands. You’ll save yourself a thousand lines of subprocess plumbing.
Writing a skill when an MCP is needed. The mirror failure: telling Claude in prose to “authenticate to OAuth, refresh the token if expired, retry on 429, paginate through results” for every call. Claude will mostly do this, sometimes wrong. Put it in an MCP and stop hoping.
Plugins with no manifest invariants. A plugin that bundles skills which never reference each other and could each work standalone isn’t really a plugin — it’s a directory of skills with extra metadata. That’s fine, but ask whether the user benefits from the bundling. If installing four unrelated skills as one unit just means uninstalls are messier, ship them separately.
Conflating allowed-tools with security. The allowed-tools field in skill frontmatter restricts which tools Claude will use while that skill is active. It’s a usability and consistency control, not a permission boundary. Don’t use it as your only line of defense against destructive commands — rely on Claude Code’s built-in permission prompts and project-level .claude/settings.json permissions for that.
Overloading one MCP server with everything. I’ve seen “company-everything-mcp” servers that expose 40+ tools across 8 services. Claude’s tool selection gets noticeably worse past about 20 tools per server. Split by service. One MCP per coherent capability domain.
Forgetting that everything is project-scoped too. Skills, MCP configs, and plugins all support project-scoped versions under .claude/ in your repo. Use this aggressively. Project-scoped skills travel with the codebase, get reviewed in PRs, and don’t pollute every other project on your machine. If the skill is specific to one codebase’s conventions, that’s where it belongs.
None of these are catastrophic on their own, but they compound. A team with messy descriptions, oversized MCP servers, and unnecessary plugin wrappers will have a noticeably worse experience than one with crisp descriptions, focused MCPs, and bundling only where it earns its keep. The good news is each of these is cheap to fix once spotted — refactoring extensions is fast because the surface area is small. To browse what “good” looks like across each layer, the curated picks are a reasonable starting point.
Found a bug or want a topic covered? Email [email protected] or open an issue via GitHub.
SKILL.md files, not affiliated with, endorsed by, or sponsored by Anthropic.