Claude Code Hooks vs MCP Servers: When to Use Each
Understand the difference between Claude Code hooks and MCP servers. When to use each, how they work together, and real-world examples of both approaches.
The Two Extension Mechanisms
Claude Code has two ways to extend its capabilities: hooks and MCP servers. They serve different purposes and understanding when to use each is key to building an efficient AI coding workflow.
| Aspect | Hooks | MCP Servers | |--------|-------|-------------| | Type | Local shell scripts | External processes | | When they run | Before/after messages and tools | On-demand (when Claude needs them) | | What they do | Side effects (save, notify, check) | Provide new capabilities (tools, data) | | Complexity | Simple shell scripts | Full programs | | Persistence | Session-scoped | Persistent daemon processes | | Use case | Workflow automation | Tool integration |
---
Hooks: Local Automation Scripts
Hooks are shell scripts that run at specific points during a Claude Code session. They're side-effect scripts — they do things but don't change what Claude Code does.
What Hooks Can Do
{
"hooks": {
"preMessage": "validate-context.sh",
"postMessage": "save-session.sh",
"preToolUse": {
"Edit": "lint-before-edit.sh"
},
"postToolUse": {
"Edit": "auto-commit.sh"
}
}
}
Good for: - ✅ Logging and saving session state - ✅ Running pre-commit hooks - ✅ Sending notifications (Slack, email) - ✅ Running lint checks before edits - ✅ Auto-saving checkpoint files - ✅ Enforcing safety rules
Not good for: - ❌ Adding new capabilities to Claude - ❌ Reading external databases - ❌ Complex tool integrations
Hook Example: Safety Check
#!/bin/bash
# .claude/hooks/pre-bash.sh
# Block dangerous commandsif echo "$*" | grep -q "rm -rf --no-preserve-root"; then
echo "⛛ Blocked: Dangerous command detected"
exit 1
fi
External Links: - Claude Code Hooks Automation Guide — full hook recipes - Claude Code Hooks Documentation
---
MCP Servers: External Tool Providers
MCP (Model Context Protocol) servers are separate processes that provide Claude with new tools and data sources. They're like plugins for your AI agent.
What MCP Servers Can Do
{
"mcpServers": {
"sqlite": {
"command": "uvx",
"args": ["mcp-server-sqlite", "--db-path", "/tmp/test.db"]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "ghp_xxxxxxxx"
}
},
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/allowed/path"]
}
}
}
Good for: - ✅ Database access (SQLite, PostgreSQL) - ✅ API integration (GitHub, Slack, Jira) - ✅ File system access with restrictions - ✅ Custom tool creation - ✅ External data sources
Not good for: - ❌ Simple side effects (use hooks instead) - ❌ Session management - ❌ One-time operations
MCP Example: Database Query
# Claude Code can now query a database on demand:
> Query the users table for inactive subscriptions
# ↓
# MCP SQLite server executes the query
# Returns: Found 23 users with past_due/canceled subscriptions
External Links: - MCP Specification — official protocol docs - MCP Servers Beginner's Guide — getting started with MCP
---
When to Use Each
Is it a side effect? (save, notify, check)
├── YES → Use a HOOK
└── NO → Is it a new capability? (tool, data, API)
├── YES → Use an MCP SERVER
└── NO → Re-evaluate if you need it
Decision Matrix
| Task | Best Approach | Why | |------|--------------|-----| | Auto-save session state | Hook | Simple script, runs after every message | | Send Slack notification | Hook | Side effect, doesn't change Claude's behavior | | Run linter before edits | Hook | Pre-tool validation | | Query database | MCP Server | Claude needs to request data dynamically | | Access GitHub API | MCP Server | Provides new tool (search repos, create issues) | | Block dangerous commands | Hook | Pre-execution validation | | Auto-commit changes | Hook | Side effect after edits | | Read/write files with permission control | MCP Server | Restricted capability | | Save conversation log | Hook | Post-message side effect | | Deploy after commit | Hook | Side effect, not a questonable capability |
---
Using Both Together
The real power comes from combining hooks and MCP servers:
Example: Smart Code Review Pipeline
Hook (pre-edit): Run linter on file before Claude edits it
MCP Server: GitHub API to fetch PR context, linked issues
Hook (post-edit): Auto-save checkpoint + send notification
MCP Server: SQLite to store code review history
Example: Database-First Development
Step 1: MCP Server for Database
{
"mcpServers": {
"db": {
"command": "npx",
"args": ["-y", "@anthropic/mcp-postgres", "postgresql://localhost:5432/mydb"]
}
}
}
Step 2: Hook for Safety
{
"hooks": {
"preToolUse": {
"Bash": "validate-db-commands.sh"
}
}
}
Step 3: Hook for Audit Trail
{
"hooks": {
"postToolUse": {
"Edit": "log-db-changes.sh"
}
}
}
---
Performance Considerations
Hook Performance
Hooks run synchronously — Claude waits for them to complete before continuing:
| Hook Type | Impact | Budget | |-----------|--------|--------| | preMessage | Delays response start | <500ms | | postMessage | Runs after response | <2s | | preToolUse | Delays tool execution | <300ms | | postToolUse | Runs after tool use | <2s |
MCP Performance
MCP servers run as background processes: - Startup time: 1-5s (first request) - Subsequent requests: 200ms-500ms - Always running once started (until Claude exits)
Which is Faster?
| Operation | Hook | MCP Server | |-----------|------|------------| | First call | Instant (script file) | 1-5s (process startup) | | Subsequent calls | Instant | 200-500ms | | Complex operation | Limited (shell only) | Full language capabilities |
---
Migration: When to Convert a Hook to an MCP Server
A hook becomes unwieldy when:
1. It's more than 50 lines of shell script — too complex for a side effect 2. It needs real programming logic — arrays, objects, error handling 3. It's used across multiple projects — should be a reusable tool 4. Claude needs to interact with it — hooks run silently, MCP servers can talk back
Migration Path
# Step 1: Start as a hook
# .claude/hooks/notify.sh
curl -X POST https://hooks.slack.com/... -d '{"text": "Claude did something"}'# Step 2: When you need more (Claude should choose what to notify about)
# → Convert to MCP server
MCP Server version:
// mcp-server-notify/index.js
const { Server } = require('@modelcontextprotocol/sdk/server')const server = new Server({
name: 'notify',
tools: [{
name: 'send_notification',
handler: async (args) => {
await fetch(args.webhook, { method: 'POST', body: JSON.stringify(args.message) })
return { content: [{ type: 'text', text: 'Notification sent' }] }
}
}]
})
---
Quick Reference
HOOKS → "Do this" MCP SERVERS → "Can you?"
│ │
├─ Save session state ├─ Query database
├─ Send notification ├─ Access GitHub
├─ Run linter ├─ Manage files
├─ Log activity ├─ Send email
├─ Enforce safety rules ├─ Deploy to cloud
└─ Auto-commit └─ Any API...
Rule of thumb: > If it's a side effect that runs automatically → Hook > If it's a capability that Claude can use on demand → MCP Server
Related Articles: - Claude Code Hooks Automation Guide — all hooks recipes - MCP Servers Beginner's Guide — MCP server basics - How to Debug AI-Generated Code — fix issues in your setup
Related Articles
Claude Code Hooks Automation Guide: Automate Every Step of Your Workflow
Complete guide to Claude Code hooks — automate testing, linting, deployments, and notifications. With real-world hook recipes for pre-command, post-command, and pipeline integration.
Crush vs Claude Code: Open Source vs Pro AI Coding Agent (2026)
Comprehensive comparison of Crush (successor to OpenCode) vs Claude Code — the two most talked-about terminal AI coding agents. Covers features, setup, cost, multi-model support, and which one to choose for your workflow.
Claude Code Memory Systems Explained: How Context, Sessions, and Persistence Work
A deep dive into Claude Code's memory architecture — understanding context windows, session memory, persistent files, and how to optimize memory usage for large projects.