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.

·9 min read

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.

AspectHooksMCP Servers
TypeLocal shell scriptsExternal processes
When they runBefore/after messages and toolsOn-demand (when Claude needs them)
What they doSide effects (save, notify, check)Provide new capabilities (tools, data)
ComplexitySimple shell scriptsFull programs
PersistenceSession-scopedPersistent daemon processes
Use caseWorkflow automationTool 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 commands

if 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

TaskBest ApproachWhy
Auto-save session stateHookSimple script, runs after every message
Send Slack notificationHookSide effect, doesn't change Claude's behavior
Run linter before editsHookPre-tool validation
Query databaseMCP ServerClaude needs to request data dynamically
Access GitHub APIMCP ServerProvides new tool (search repos, create issues)
Block dangerous commandsHookPre-execution validation
Auto-commit changesHookSide effect after edits
Read/write files with permission controlMCP ServerRestricted capability
Save conversation logHookPost-message side effect
Deploy after commitHookSide 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 TypeImpactBudget
preMessageDelays response start<500ms
postMessageRuns after response<2s
preToolUseDelays tool execution<300ms
postToolUseRuns 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?

OperationHookMCP Server
First callInstant (script file)1-5s (process startup)
Subsequent callsInstant200-500ms
Complex operationLimited (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