Claude Code Integration
Protect Claude Code with shellfirm hooks and MCP
shellfirm integrates with Claude Code through two complementary layers:
- Hooks — automatic pre-tool-use interception that blocks risky Bash commands (exit code 2 = blocked)
- MCP — on-demand tools that let Claude explain risks, suggest alternatives, and check project policies
Quick Start
1. Install shellfirm
brew tap kaplanelad/tap && brew install shellfirm
Or with Cargo:
cargo install shellfirm
2. Connect to Claude Code
shellfirm connect claude-code
This installs both hooks and MCP into ~/.claude/settings.json. You can also install them separately:
shellfirm connect claude-code --hooks-only # hooks only
shellfirm connect claude-code --mcp-only # MCP only
shellfirm connect claude-code --dry-run # preview changes
shellfirm connect claude-code --uninstall # remove everything
3. Restart Claude Code
After connecting, restart Claude Code so it picks up the new configuration.
How Hooks Work
Claude Code hooks are shell commands that fire automatically before and after tool calls. shellfirm registers a PreToolUse hook on the Bash tool:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "shellfirm check --stdin --format json --exit-code"
}
]
}
]
}
}
When Claude Code is about to run a Bash command:
- The hook receives the command as JSON on stdin
- shellfirm runs the full analysis pipeline (pattern matching, context detection, policy checks)
- If the command is safe → exit code 0, Claude proceeds
- If the command is risky → exit code 2, Claude Code blocks execution
This happens automatically on every Bash command — Claude doesn't need to remember to call a tool first.
How MCP Works
The MCP server gives Claude access to four on-demand tools:
- check_command — checks if a command is risky before executing it
- suggest_alternative — gets safer alternatives for a risky command
- get_policy — retrieves the current shellfirm configuration and project policies
- explain_risk — provides detailed risk explanations
While hooks handle automatic blocking, MCP lets Claude proactively analyze commands and make informed decisions about alternatives.
Manual Configuration
If you prefer to configure manually, add to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "shellfirm check --stdin --format json --exit-code"
}
]
}
]
},
"mcpServers": {
"shellfirm": {
"command": "shellfirm",
"args": ["mcp"]
}
}
}
For per-project MCP configuration, add to .mcp.json in your project root:
{
"mcpServers": {
"shellfirm": {
"command": "shellfirm",
"args": ["mcp"]
}
}
}
Example interaction
You: Deploy the latest changes to production.
Claude Code:
Let me check if the deployment command is safe first.
[hook blocks
git push --force origin mainwith exit code 2]shellfirm blocked the force push:
- git:force_push (High) — "Force push can overwrite remote history."
- Alternative:
git push --force-with-leaseI'll use the safer alternative instead:
git push --force-with-lease origin main
Auto-deny for unattended use
When Claude Code runs autonomously, configure auto-deny to automatically block high-severity commands:
# ~/.shellfirm/settings.yaml
agent:
auto_deny_severity: High
With this setting:
- Commands matching patterns with severity High or Critical are automatically denied
- Commands with Medium, Low, or Info severity are allowed to proceed
- Claude sees the denial reason and can suggest alternatives
Combining with project policies
When you have a .shellfirm.yaml in your project, both hooks and MCP benefit from team-level policies. For example, if your team denies force pushes:
# .shellfirm.yaml
version: 1
deny:
- "git:force_push"
The hook will block force push attempts, and Claude will see that force push is on the deny list via MCP.
The shellfirm check CLI
The hook uses shellfirm check with these flags:
--stdin— reads the command from stdin JSON (supports both{"command": "..."}and Claude Code's{"tool_input": {"command": "..."}}format)--format json— outputs a structuredRiskAssessmentJSON to stdout--exit-code— exit 0 if safe, exit 2 if risky/blocked
You can test it directly:
echo '{"command":"rm -rf /"}' | shellfirm check --stdin --format json --exit-code
Troubleshooting
- shellfirm tools not appearing — verify that
shellfirmis in your PATH by runningwhich shellfirm - Hooks not firing — check that
~/.claude/settings.jsoncontains thehooks.PreToolUseentry; restart Claude Code after changes - Commands not being blocked — verify your
auto_deny_severitythreshold in~/.shellfirm/settings.yaml; the default isHigh - Connection errors — check that the MCP configuration JSON is valid and the
commandpath is correct - JSON parse errors — ensure shellfirm is installed and accessible; run
shellfirm check --helpto verify