# MCP, Subagents, and Hooks in Claude Code: The Guide I Wish I'd Had

> Source: <https://dev.to/alvarito1983/mcp-subagents-and-hooks-in-claude-code-the-guide-i-wish-id-had-4gg>
> Published: 2026-08-19 08:36:15+00:00

*Originally published on El Rack — Spanish tech reviews from a sysadmin/homelab perspective.*

Claude Code is capable right out of the box, but it starts to feel thin the moment your project needs to touch external systems: a ticket tracker, a database, your own VPS. This guide covers the four pieces that turn it into a real production tool:

`/mcp`

): connecting external services as toolsYou'll need Claude Code installed and authenticated, and a terminal open in any project folder.

MCP (Model Context Protocol) lets Claude Code use tools it doesn't ship with by default. Those tools live in MCP servers: local processes or hosted services reachable over a URL. You add them with `claude mcp add`

, no hand-editing JSON required. Start with the official docs server — it needs no account and no config:

```
claude mcp add --transport http claude-code-docs https://code.claude.com/docs/mcp
```

Confirm it connected:

```
claude mcp list
```

You should see `✔ Connected`

. From inside a session, manage servers any time with `/mcp`

.

A stdio server is a program Claude Code launches as a subprocess — useful when it needs access to your filesystem or a browser. Example with Playwright, which requires no account:

```
claude mcp add playwright -- npx -y @playwright/mcp@latest
```

The `--`

separates Claude Code's own flags from the command that starts the server. For services that require sign-in (Sentry, Linear, Notion, GitHub), you add them the same way and authenticate from inside the session:

```
claude mcp add --transport http sentry https://mcp.sentry.dev/mcp
```

After adding, you'll see `! Needs authentication`

. Start a session, run `/mcp`

, select the server, and choose "Authenticate" — your browser opens for sign-in.

If the service uses a static token instead of OAuth (common with self-hosted instances), pass it directly:

```
claude mcp add --transport http my-server http://my-host:3001/api/mcp --header "Authorization: Bearer YOUR_TOKEN"
```

By default, every server is registered at "local" scope: private to you, active only in the current project. Two alternatives depending on how widely you want to share it:

```
# Available in all your projects, still private
claude mcp add --scope user --transport http claude-code-docs https://code.claude.com/docs/mcp

# Shared with the team via the repo (writes .mcp.json)
claude mcp add --scope project --transport http claude-code-docs https://code.claude.com/docs/mcp
```

| Scope | File | Available to |
|---|---|---|
| local |
`~/.claude.json` (project entry) |
Only you, only this project |
| project |
`.mcp.json` at the repo root |
Everyone who clones the repo |
| user |
`~/.claude.json` (top-level `mcpServers` ) |
Only you, all your projects |

If you'd rather write `.mcp.json`

by hand to keep it version-controlled with the team:

```
{
  "mcpServers": {
    "claude-code-docs": {
      "type": "http",
      "url": "https://code.claude.com/docs/mcp"
    },
    "playwright": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@playwright/mcp@latest"]
    }
  }
}
```

A subagent is an instance with its own context, its own system prompt, and its own tools, working in isolation and returning only a summary. It's the fix for two problems: flooding your main conversation with logs or results you won't reuse, and re-spawning the same kind of worker with the same instructions over and over.

Claude Code ships with three built-in subagents: Explore (read-only code search), Plan (research during plan mode), and general-purpose (complex tasks with access to everything). For a custom one, just ask Claude to write it:

```
Create a personal subagent in ~/.claude/agents/ called "code-reviewer" that
reviews code for quality, security, and best practices. Make it read-only
and have it use Sonnet.
```

Claude writes the file with YAML frontmatter plus the system prompt:

```
---
name: code-reviewer
description: Reviews code for quality, security, and best practices. Use after writing or modifying code.
tools: Read, Grep, Glob
model: sonnet
---

You are a senior code reviewer. For each issue you find, explain the
problem, show the current code, and provide an improved version.
```

The most useful frontmatter fields: `tools`

(allowlist), `disallowedTools`

(denylist), `model`

(sonnet/opus/haiku/inherit), and `mcpServers`

(gives an MCP server to that subagent alone, without loading its context into the main conversation).

Save it in `.claude/agents/`

to scope it to this project, or `~/.claude/agents/`

to make it available everywhere.

There are three ways to use a subagent, from least to most explicit:

```
# Natural language: Claude decides whether to delegate
Use the code-reviewer subagent to review my recent changes

# @-mention: forces that specific subagent to run
@"code-reviewer (agent)" review the authentication logic

# Run the whole session as that subagent (its system prompt and tools)
claude --agent code-reviewer
```

For independent investigations, you can request several subagents in parallel:

```
Research the authentication, database, and API modules in parallel using
separate subagents
```

Each one explores its own area in isolation; you only get the synthesized summary back in your main conversation.

If you type the same instruction over and over, turn it into a command. Classic commands (`.claude/commands/*.md`

) still work, but the recommended approach now is Skills (`.claude/skills/<name>/SKILL.md`

) — if a command and a skill share a name, the skill wins.

Classic command, saved as `.claude/commands/audit-disk.md`

:

```
---
description: Disk space audit with a configurable threshold
allowed-tools: Read, Bash, Grep
argument-hint: [threshold-percentage]
---

Audit disk space across the servers. Alert threshold: $ARGUMENTS%.
```

Invoke it with `/audit-disk 85`

. `$ARGUMENTS`

captures everything typed after the command; `!` command``

injects live shell output (e.g. `!` git diff --cached``

).

Same idea as a skill, at `.claude/skills/audit-disk/SKILL.md`

:

```
---
name: audit-disk
description: Disk space audit with a configurable threshold. Use when the user asks to check disk space on the servers.
allowed-tools: Read, Bash, Grep
---

Audit disk space across the servers...
```

The advantage of skills: they can bundle several reference files in the same folder, and Claude can invoke them on its own, without you typing the slash.

If you run in an autonomous mode (`--dangerously-skip-permissions`

), hooks are how you block dangerous operations without relying on the AI remembering a rule. They fire every time, at the exact lifecycle point you define.

The two most-used events: `PreToolUse`

(before a tool runs — good for blocking) and `PostToolUse`

(after — good for formatting, linting, or logging). Configure them in `settings.json`

:

```
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "./scripts/validate-command.sh" }
        ]
      }
    ]
  }
}
```

The script receives the command as JSON via stdin, and exit code 2 blocks the operation. Example that stops an overly broad `pkill`

on a server where multiple processes share the same name:

``` bash
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

if echo "$COMMAND" | grep -qE 'pkill.*node|rm -rf /'; then
  echo "Blocked: command too broad or destructive. Use an exact PID." >&2
  exit 2
fi

exit 0
```

Make it executable:

```
chmod +x ./scripts/validate-command.sh
```

Another common one: auto-format every file Claude touches, without having to ask:

```
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          { "type": "command", "command": "npx prettier --write \"$CLAUDE_TOOL_INPUT_FILE_PATH\"" }
        ]
      }
    ]
  }
}
```

Put these four pieces together and Claude Code stops being a terminal assistant and becomes a real automation layer: it connects to whatever it needs (MCP), delegates whatever would clutter its context (subagents), packages whatever repeats (skills), and respects hard limits that don't depend on its memory (hooks). Suggested adoption order: start with one low-risk MCP server, add one read-only subagent, migrate your commands to skills whenever you have time, and — the highest-leverage one if you run in autonomous mode — add at least one hook that blocks the operation you're most afraid of running by accident.

*Tutorial by Álvaro Fraguas Bravo for El Rack.*
