Sitemap

Claude Code, One Knob at a Time: Skills, Subagents, Commands, Hooks, MCP, LSP, Plugins, and the Spec-Driven Workflow

12 min readSep 3, 2026

--

A follow-up to the PDF → JSON walkthrough. That post used one primitive — a Skill. This one walks the rest of the palette.

Why a second post

If you read the previous one, you saw a single Skill (field-extraction) do most of the heavy lifting — take a Markdown file, return JSON. That’s a good first taste. But Skills are only one of ten or so extension points Claude Code ships with, and it’s easy to reach for “another Skill” when a Subagent, a Slash Command, a Hook, or an MCP server would fit better.

The goal here is to walk each primitive once — one paragraph of what it is, one of when to reach for it, and one minimal file you can copy. By the end you should be able to look at a problem and say “that’s a Hook” or “that’s a Skill inside a Plugin” without guessing.

Roadmap: Skills → Subagents → Slash Commands → Hooks → MCP → LSP → Scripts → Plugins → Marketplace → Spec-Driven Design.

1. Skills — procedural knowledge as Markdown

A Skill is a SKILL.md file. That’s it. Frontmatter declares a name and a description, the body is the prompt Claude reads when the Skill fires. Skills live at .claude/skills/<skill-name>/SKILL.md for a project, or my-plugin/skills/<skill-name>/SKILL.md inside a plugin.

Why does the format exist? Because LLMs absorb three kinds of knowledge very differently:

  • MCP gives an agent capability — new APIs, new data sources.
  • RAG gives it facts — retrieved passages to ground its answer.
  • Fine-tuning gives it style / intuition baked into weights.
  • Skills give it procedural knowledge — when to do a thing, why you’d do it, how to do it step by step.

That last category was the gap. Skills fill it with plain Markdown. They follow what Anthropic calls progressive disclosure — three tiers, loaded only when needed:

  1. Metadata (name, description) — always in context, so the router can decide whether to fire.
  2. Body — loaded when the Skill actually activates.
  3. Resources (scripts, references, assets) — loaded only if the body asks for them.

The SKILL.md format is published under Apache 2.0, so it’s not a lock-in.

Here’s a generic one — a code reviewer — that you can drop into any repo:

name: code-reviewer
description: Review a diff or a file for correctness, style, and common
security issues. Trigger when the user asks to "review my changes",
"look at this diff", "code review", "review this PR", or "check for bugs".

# Code Reviewer
## 1. Read the change
- If the user referenced a branch or a PR, run `git diff main…HEAD`.
- Otherwise, ask which file(s) to review, then `Read` them.
## 2. Categorise findings
Group under: Correctness, Security, Style & clarity, Tests.
Skip any heading with no findings - empty sections are noise.
## 3. Emit the report
For each finding: `path:line`, one-sentence problem, one-sentence fix.
End with a Summary line: count by severity.
## What not to do
- Do not auto-fix. Reviewer finds and reports; fixes are a separate turn.
- Do not repeat rules a linter already enforces.

Gotcha: the description is what the router matches on. Vague descriptions mean the Skill never fires. Be specific about trigger phrases — include the exact words a user is likely to type.

2. Subagents — a fresh context window on demand

A Subagent is a named sub-assistant the main agent can delegate to. It lives at .claude/agents/<name>.md, has its own allowed tools, its own model, its own system prompt. When the main agent hands it a task, the sub-agent runs in a fresh context window and returns a single summary message.

Reach for a Subagent when the task is long-running or context-heavy and you don’t want it polluting the main conversation. Common cases:

  • Broad codebase searches (Claude Code ships Explore as a built-in subagent).
  • Implementation planning (Plan, also built-in).
  • Anything you want to run in parallel — two subagents can crawl different parts of the repo simultaneously.
  • Tasks with a well-defined input and output where the main thread shouldn’t care about the intermediate steps.

Here’s a test-runner subagent — runs the suite, reads the failures, proposes the minimal fix:

name: test-runner
description: Runs the project's test suite, interprets failures, and proposes
the minimal targeted fix. Use when tests are red.
tools: [Bash, Read, Edit, Grep, Glob]
model: sonnet

# Test Runner
## 1. Detect the test command
Python: pytest if pytest.ini / pyproject.toml / conftest.py exists.
Node: npm test, npx vitest, or npx jest depending on devDependencies.
## 2. Run, interpret, fix
One finding at a time. Never delete a failing test to make the suite green.
Flaky tests get flagged, not "fixed" by loosening assertions.

Gotcha: subagents don’t inherit the parent’s cwd, open files, or in-memory state. You have to brief them like a new hire who just walked in the door — include file paths, not pronouns.

3. Slash Commands — user-invoked shortcuts

A Slash Command is Markdown at .claude/commands/<name>.md. The user types /<name> and the body of the file becomes the prompt. Arguments flow through $ARGUMENTS.

Slash Commands and Skills overlap. The distinction is who fires them:

  • A Skill can be model-invoked — the LLM decides when to fire, based on the description.
  • A Slash Command is always user-invoked — it only runs when someone types /name.

In practice a Skill with disable-model-invocation: true is functionally equivalent to a slash command; pick the one that reads clearer to your team.

Example — a pr-summary command that drafts a PR description for the current branch:

description: Generate a Markdown PR description for the current branch vs.
a base branch (default `main`).

Generate a Markdown PR description for the current branch.
Base branch: $ARGUMENTS (default `main` if empty).
## Steps
1. Run `git diff <base>…HEAD - stat` for the file-level scope.
2. Run `git log <base>..HEAD - pretty=format:'%h %s'` for the commit history.
3. Run `git diff <base>…HEAD` for the full diff.
## Output format
Emit exactly this Markdown, inside a fenced block:
## Summary
## Changes
## Test plan

Invoke it as /pr-summary (uses main) or /pr-summary develop.

4. Hooks — deterministic callbacks around agent actions

A Hook is a shell command Claude Code runs at a lifecycle event, configured in .claude/settings.json under a hooks key. The point is to handle deterministic concerns the LLM shouldn’t handle — formatting, blocking dangerous edits, re-running tests.

Event types you’ll actually use:

  • PreToolUse — fires before a tool runs; can block (return non-zero) if the args look wrong.
  • PostToolUse — fires after a tool ran; handy for formatters, linters, auto-test-runs.
  • UserPromptSubmit — fires when the user sends a message; useful for injecting context.
  • SessionStart / SessionEnd — env setup and teardown.
  • PreCompact — fires before Claude compresses the conversation; useful if you want to persist state.

Here’s a tiny one — auto-format every Python file Claude edits:

// .claude/settings.json
{
"permissions":{
"allow":[
"Read",
"Write",
"Edit",
"Glob",
"Grep",
"Skill"
]
},
"hooks":{
"PostToolUse":[
{
"matcher":"Edit|Write",
"hooks":[
{
"type":"command",
"command":"ruff format \"$CLAUDE_FILE_PATH\" 2>/dev/null || true"
}
]
}
]
}
}

Edit a .py file, ruff format runs automatically. The || true swallows the failure when the edited file wasn’t a Python file, so the hook doesn’t noisily break on every Markdown save.

Gotcha: hooks run with your user-level permissions. Do not let a hook execute agent-generated output — treat the command string as trusted static code. A hook that evals something from the LLM turns the whole agent into a prompt-injection liability.

5. MCP Servers — external tools and data sources

Model Context Protocol is an open standard for exposing tools and resources to an LLM. Claude Code connects to MCP servers over stdio (a subprocess) or HTTP. A server exposes tools (callable functions) and resources (read-only content); the agent gets to use them as if they were native.

Reach for MCP when you want Claude to talk to an external system — GitHub, Jira, Postgres, Sentry, your own service — and you don’t want to hand-build a tool wrapper for each one. Someone has already written the server; you just list it.

Config lives in .mcp.json at the project root:

{
"mcpServers":{
"filesystem":{
"type":"stdio",
"command":"npx",
"args":[
"-y",
"@modelcontextprotocol/server-filesystem",
"./app/examples"
]
}
}
}

That wires in the official filesystem reference server, scoped to ./app/examples. Claude now has a second way to list and read those files in addition to the built-in Read / Glob.

The modelcontextprotocol.io registry lists dozens of servers — GitHub, Slack, Postgres, Puppeteer, etc.

Gotcha: stdio servers need the binary on PATH. npx -y works for Node-based servers because it’ll fetch them on first use; for Python servers (uvx …) install conditions apply. Scopes also cascade — local > project > user — so a local override wins silently. If a server “doesn’t work” on one machine, check the scope chain before debugging the server itself.

6. LSP Servers — first-class language intelligence

The Language Server Protocol is how IDEs talk to compilers. An LSP server exposes typed operations — rename, go-to-definition, find-references, diagnostics — so the client can make edits that are type-aware instead of regex-aware. Claude Code can consume an LSP server the same way VS Code does, and suddenly its edits are a lot less likely to miss a reference or mis-rename a field.

Reach for LSP when you’re doing real refactors on a typed codebase — renames across hundreds of files, changing a method signature, auditing an API surface. Grep gets you 80% of the way; LSP catches the last 20%.

Configured in .lsp.json at the project (or plugin) root:

{
"languages":{
"python":{
"command":"pylsp",
"args":[

],
"extensions":[
".py"
]
},
"typescript":{
"command":"typescript-language-server",
"args":[
" - stdio"
],
"extensions":[
".ts",
".tsx",
".js",
".jsx"
]
}
}
}

The binaries (pylsp, typescript-language-server) are installed separately — pip install python-lsp-server and npm i -g typescript-language-server respectively. The .lsp.json only tells Claude Code how to launch them. Once running, the LSP connection is transparent — you just notice that Claude’s refactors stop missing call sites.

Gotcha: LSP adds startup latency. On a large repo, pylsp may take a few seconds on first open while it indexes. Scope the .lsp.json to the languages you actually use in the repo — don’t pre-load a server you won’t call.

Get Samarth joel ram’s stories in your inbox

Join Medium for free to get updates from this writer.

7. Scripts — the escape hatch for deterministic code

Most Skills are just prose. But a Skill (or a plugin) can ship a scripts/ directory alongside the SKILL.md, and the body of the Skill can instruct Claude to run those scripts via Bash. That’s how a Skill stops being “just a prompt” and becomes an actual automation.

Reach for scripts whenever a step is deterministic and you don’t want the LLM re-deriving it every time — parsing JSON, running a git command, regexing, calling a CLI. The LLM orchestrates; the script does the arithmetic.

Example. The commit-message-writer Skill calls this:

# .claude/skills/commit-message-writer/scripts/get_staged_diff.sh

#!/usr/bin/env bash
set -euo pipefail
echo "## Staged files"
git diff - staged - stat
echo
echo "## Staged diff"
git diff - staged

The Skill body says:

## 1. Inspect the staged changes

Run the helper script via `Bash`:

bash scripts/get_staged_diff.sh

No prompt-engineering around “remember to run git diff — staged and — stat together” — the script encodes that decision once.

8. Plugins — packaging it all up

A Plugin is a self-contained bundle of Skills + Subagents + Slash Commands + Hooks + MCP config + LSP config + scripts, with a manifest at .claude-plugin/plugin.json. It’s how you ship the whole toolkit you just built across repos or to teammates.

Minimal layout:

plugins/dev-toolkit/

├── .claude-plugin/
│ └── plugin.json
├── skills/
│ └── code-reviewer/SKILL.md
├── agents/
│ └── test-runner.md
└── commands/
└── pr-summary.md

The manifest:

{
"name":"dev-toolkit",
"version":"0.1.0",
"description":"Everyday dev ergonomics - code review, commit messages, test running, and PR summaries.",
"author":"@samarthjoelram",
"license":"MIT"
}

Once a Skill lives inside a plugin, it becomes namespaced. The code-reviewer Skill above, installed from the dev-toolkit plugin, is addressable as /dev-toolkit:code-reviewer — which means you can install another plugin that also defines code-reviewer without a collision.

For local development: point Claude Code at the plugin directory with — plugin-dir ./plugins/dev-toolkit and it loads without any install ceremony.

9. Marketplace — distributing plugins

Once you have one plugin, you probably want a few. A Marketplace is a git repo with a .claude-plugin/marketplace.json listing the plugins and where to fetch each one. Users point Claude Code at the marketplace URL once, then /plugin install <name> for anything inside it.

{
"name":"demo-repo-marketplace",
"description":"Example Claude Code plugin marketplace.",
"plugins":[
{
"name":"dev-toolkit",
"version":"0.1.0",
"description":"Everyday dev ergonomics.",
"source":{
"type":"local",
"path":"./plugins/dev-toolkit"
}
}
]
}

Install flow from the user’s side:

/plugin marketplace add <git-url-of-this-repo>
/plugin install dev-toolkit

That’s how teams publish internal toolkits. The marketplace URL is just a git repo — there’s no central registry to register with, no publishing pipeline, no review queue.

10. Spec-Driven Development — closing the loop

Everything above is about tool-building. Spec-driven development is about how you use those tools on day one of a project, before any code is written.

Here’s the flow:

  1. Start with a transcript or notes from the discovery call with the client. (Meeting recording → transcription → Markdown. This is deterministic; no LLM needed.)
  2. Use Claude — with a spec-writer Skill or a Subagent — to turn the transcript into Functional Requirements and Technical Requirements. Ask for one FR per user-visible behaviour and one TR per technical constraint.
  3. From the FRs, derive EPICs and user stories. Claude groups related FRs and names the EPICs; you accept, rename, or split them.
  4. Ask Claude to emit functional and technical diagrams — sequence diagrams for the user flows, component diagrams for the architecture, deployment diagrams for the infra. Mermaid is the right output format; Markdown renders it, and diff tools handle it.
  5. Derive the data model. Entities, relationships, constraints — then SQL DDL. This is where spec-driven pays off; most teams skip straight to coding and then spend a month patching the data model.
  6. Generate a resource-based estimate. “To build this, we need X frontend-hours, Y backend-hours, Z DevOps-hours.” If the team is missing an expertise area — nobody knows Postgres tuning, nobody’s ever set up a K8s ingress — Claude flags the gap and proposes fillers: contractor hours, managed services, or a learning-budget line.

None of those steps is magical. All of them use the same primitives you’ve already seen — a Skill per output format, a Subagent for each heavy research step, a slash command to kick the next stage. A spec-writer Skill that turns transcripts into FRs has the same shape as the code-reviewer Skill in §1.

What changes with spec-driven is timing. You do the thinking before the code exists, while Claude is fresh and there’s nothing in the repo to bias it. Once you start writing code, the spec becomes a living document — every PR description (via /pr-summary from §3) ties its changes back to an FR or a TR.

The goal isn’t to automate the engineer away. It’s to put the boring scaffolding — FRs, diagrams, DB DDL, estimates — on rails, so you spend your time on the hard parts that actually need judgement.

Wrap-up

Think of the primitives as a stack:

  • Bottom layer — deterministic: Hooks, Scripts, MCP, LSP. Non-negotiable behaviour. Either the hook runs or it doesn’t; either the LSP finds the reference or it doesn’t.
  • Middle layer — agentic: Skills, Subagents, Slash Commands. Claude chooses when to fire these, guided by your description fields.
  • Top layer — distribution: Plugins and Marketplaces. How you share the first two layers with someone else.
  • Orthogonal — process: Spec-driven development. How you sequence the use of everything else.

Pick the layer that matches the concern. Deterministic things don’t belong in Skills (the LLM will “helpfully” skip a step); agentic judgement doesn’t belong in Hooks (a bash script can’t reason about what to review).

If you only take one thing from this post: add one Skill to your own repo this week. commit-message-writer is the cheapest good one — three minutes to install, saves you thirty seconds every commit, and teaches you the Skill pattern by osmosis. From there the rest of the palette is obvious.

Thanks for reading.

References

Claude Code — primitives

  • Claude Code docs landing — entry point; the pages below hang off it.
  • Skills — SKILL.md format, frontmatter, progressive disclosure.
  • Subagents — the .claude/agents/ layout and tools/model frontmatter.
  • Slash Commands — .claude/commands/ and the $ARGUMENTS placeholder.
  • Hooks — event types, matchers, the settings.json block.
  • MCP — how to wire .mcp.json, scope cascade.
  • Plugins — .claude-plugin/plugin.json layout.
  • Plugin Marketplaces — /plugin marketplace add flow.

Public galleries — worth a browse

MCP & LSP

--

--