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:
claude mcp add --scope user --transport http claude-code-docs https://code.claude.com/docs/mcp
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 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:
Use the code-reviewer subagent to review my recent changes
@"code-reviewer (agent)" review the authentication logic
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:
#!/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.