DevOps Peach

Claude Code in Practice · The .claude toolbox – Skills, agents, hooks and MCP · Lesson 3 of 3

Hooks and MCP: what must always happen, and what lives outside

A hook runs every time, whether Claude remembers or not. An MCP server connects Claude to tools outside the repository but costs context on every turn. How we choose between a hook, an MCP server, a skill and a plain script.

Practice interactively →

Hooks: rules that cannot be forgotten

A line in CLAUDE.md is advice; Claude follows it most of the time. A hook is a command that Claude Code itself runs on an event, every time. Use hooks for things that must never be skipped.

Events you will use most:

Event When Our use
PostToolUse after Claude edits a file run the content validator when a file under content/ changes
PreToolUse before a tool runs; can block it block edits to tools/preview/drafts.txt (a "never edit" rule)
Notification / Stop Claude is waiting or finished play a sound so you can do something else during long tasks

Hooks are configured in .claude/settings.json (team) or settings.local.json (just you). A blocking hook looks like this (simplified):

{ "hooks": { "PreToolUse": [ { "matcher": "Edit|Write",
  "hooks": [ { "type": "command",
    "command": "python3 tools/hooks/protect_paths.py" } ] } ] } }

The script reads the tool call, and if the path is protected it exits with code 2 and a message; Claude sees the message and picks another way. Start with one hook. The "done" sound alone changes how you work: you stop watching the terminal.

MCP: connecting to the outside

An MCP server gives Claude new tools: a browser, GitHub, a database, your email. In this project the browser pane and the link to the owner's laptop are both exposed as tools this way, which is how Claude could preview the site and edit files on Windows from a cloud session.

The catch: every connected server's tool descriptions sit in the context on every turn, used or not. Ten servers can eat a large slice of the window before you type anything.

Our rule: prototype, then shrink

  1. Prototype with MCP. Find out what you actually need.
  2. Convert to a skill or script once the job is clear. We needed YouTube transcripts; instead of keeping a server connected, we wrote tools/yt_transcript.py (40 lines) and a note on how to call it. Zero context cost until used.
  3. Keep the MCP server only for things that need live interaction (a browser, a ticket system).

Before you install one

Use case: The file nobody may edit

The situation

tools/preview/drafts.txt lists modules that are not reviewed yet; anything listed there stays off the site. Only the owner may change it. The rule was in CLAUDE.md, yet once, while "cleaning up the build", a session removed two lines from it and two unreviewed modules went live on the preview.

Why advice was not enough

CLAUDE.md is read at the start of a session. Deep into a long task, with a lot of other text in the context, a rule like that can lose out to the immediate goal ("make the build pass"). For "never", you want something that does not depend on memory.

The hook

A 15-line script, tools/hooks/protect_paths.py, reads the tool call from standard input. If the target path is in a short protected list (tools/preview/drafts.txt, docs/content-schema.md, .claude/), it prints a reason and exits with code 2. Registered as a PreToolUse hook for Edit|Write.

What Claude sees when it tries:

PreToolUse hook blocked Edit: tools/preview/drafts.txt is owner-only.
Ask the owner to change it.

Claude then tells you that the file needs a change instead of making it.

The second hook: a sound when done

Long tasks (re-recording a video, rebuilding all module data) take five to ten minutes. A Stop hook plays a short sound. The owner now starts a task, makes coffee, and comes back when it chimes.

Results

Before After
Edits to protected files 1 incident blocked every time
Time spent watching the terminal most long tasks none

What we took away

Practice interactively →