How to Automate Claude Code with Hooks and MCP Servers
Purpose
This post shows how to automate Claude Code using hooks, MCP servers, and the -p flag. The goal is to transform Claude from a reactive tool into a component of repeatable development systems.
The Problem
When I first started using Claude Code, I treated it like a one-shot assistant. I’d open it for individual tasks, manually copy context from various sources, and repeat the same prompts over and over.
Here’s what my workflow looked like:
1. Open Claude Code2. Copy context from GitHub issue3. Copy relevant code snippets4. Paste everything into Claude5. Ask for code review6. Manually run prettier on changed files7. Manually run type checker8. Commit changesThis worked, but it was tedious. I was spending more time on process than actual development.
The insight that changed my approach came from a Reddit discussion: “The people getting the most value from Claude aren’t using it for individual tasks. They’re building systems where Claude is a component.”
The Solution
Claude Code provides three automation layers that work together:
+------------------+ +------------------+ +------------------+| HOOKS | | MCP SERVERS | | -p FLAG || Event-driven | | External service | | Headless mode || automation | | connections | | for scripting |+------------------+ +------------------+ +------------------+ | | | v v v+------------------------------------------------------------------+| AUTOMATED WORKFLOWS || - Auto-format on every edit || - Auto-typecheck after changes || - Fetch context from GitHub/Slack/databases || - Run in CI/CD pipelines |+------------------------------------------------------------------+Layer 1: Hooks
Hooks let you run code automatically before or after Claude makes changes. This was the first thing I set up.
I created a hooks configuration in my settings:
{ "hooks": { "PostToolUse": [ { "matcher": "Write|Edit", "hooks": [ { "type": "command", "command": "prettier --write $FILE_PATH" }, { "type": "command", "command": "npx tsc --noEmit $FILE_PATH" } ] } ] }}Now every time Claude edits a file, Prettier formats it and TypeScript checks it. No more manual formatting. No more “forgot to run tsc” errors.
There are three hook types:
PreToolUse - Runs before tool execution (validation, parameter modification)PostToolUse - Runs after tool execution (linters, formatters, type checkers)Stop - Runs when sessions end (final verification, cleanup)Layer 2: MCP Servers
Hooks handle local automation. MCP servers handle external connections.
I was constantly copying information from GitHub issues into Claude. Every session, same thing. Then I discovered MCP servers.
Model Context Protocol servers let Claude connect to external services: Slack, GitHub, databases, APIs. Instead of manual copy-paste, Claude fetches what it needs automatically.
Here’s how I configured GitHub integration:
{ "mcpServers": { "github": { "command": "mcp-server-github", "env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" } } }}Now I can ask Claude to “review the PR at github.com/user/repo/pull/123” and it fetches the PR directly. No manual context gathering.
Other useful MCP servers I’ve configured:
{ "mcpServers": { "slack": { "command": "mcp-server-slack", "env": { "SLACK_BOT_TOKEN": "${SLACK_BOT_TOKEN}" } }, "postgres": { "command": "mcp-server-postgres", "args": ["postgresql://user:pass@localhost/mydb"] }, "fetch": { "command": "mcp-server-fetch" } }}The rule of thumb: if you’re constantly copying information into Claude, there’s probably an MCP server that can do it automatically.
Layer 3: The -p Flag
This was the missing piece for CI/CD integration.
The -p flag runs Claude non-interactively. It takes a prompt, executes it, outputs the result, and exits. No interactive interface.
I tried it first with a simple security check:
#!/bin/bash
# Get changed files in the PRCHANGED_FILES=$(git diff --name-only origin/main...HEAD)
# Run Claude headless for security reviewclaude -p "Review these files for security vulnerabilities:$CHANGED_FILES
Output JSON with:- file: the file path- issues: array of security issues- severity: critical, high, medium, low- recommendation: how to fix"The output goes to stdout, so I can pipe it to other tools:
# In CI pipelineclaude -p "Review for security issues" | jq '.issues[] | select(.severity == "critical")' | wc -lIf critical issues exist, the CI fails.
Putting It Together
The real power comes from combining all three. Here’s my pre-deploy workflow:
1. Developer pushes code2. Hook triggers: TypeScript check runs automatically3. Hook triggers: Prettier formats all changed files4. CI runs: claude -p "Review for deployment readiness"5. MCP server: Checks database migration safety via postgres connection6. MCP server: Posts results to Slack7. If all checks pass, deployment proceedsThis is what “building systems where Claude is a component” means. Claude isn’t just helping with individual tasks. It’s part of the infrastructure.
Common Mistakes
I made several mistakes while learning this:
1. Over-automating early
I added 12 hooks in my first week. TypeScript broke because Prettier ran before tsc. Then I had circular hook dependencies. Start with one hook, add complexity gradually.
2. Reinventing MCP servers
I spent a day writing a custom GitHub integration before realizing mcp-server-github already existed. Check existing servers first.
3. Forgetting environment variables
My MCP server configs use ${GITHUB_TOKEN} but I forgot to set the environment variable. Claude silently failed. Always test MCP servers with claude mcp list first.
4. Not packaging reusable prompts
I had the same prompt saved in three different places. Then I discovered custom slash commands. Package repeated prompts as commands:
---description: Review code for security issues---
Review the current file for:1. Security vulnerabilities (OWASP Top 10)2. Input validation gaps3. Authentication/authorization issues4. Sensitive data exposure
Provide severity ratings and specific fix recommendations.Now I just type /review instead of pasting the full prompt.
Why This Matters
Automation transforms Claude Code from an assistant into infrastructure:
- Consistency: Every file gets formatted, every edit gets type-checked
- Efficiency: No manual context gathering - MCP servers fetch what’s needed
- Scalability: Scriptable workflows work across teams and projects
- Reliability: Hooks catch issues before they compound
The difference between using Claude Code as a tool versus using it as infrastructure is the difference between a one-time fix and a permanent improvement.
Summary
In this post, I showed how to automate Claude Code using hooks, MCP servers, and the -p flag. Start with a single PostToolUse hook for formatting. Add MCP servers for your most-used external services. Then leverage headless mode for CI/CD integration.
Next steps:
- Add one PostToolUse hook for Prettier or ESLint
- Install an MCP server for a service you use frequently (GitHub, Slack, your database)
- Try the
-pflag with a simple code review prompt - Build a workflow that combines all three
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments