PreToolUse hook permissionDecision 'deny' not enforced for MCP server tool calls

Resolved 💬 5 comments Opened Mar 11, 2026 by serrurco Closed Apr 18, 2026

Bug Description

PreToolUse hooks that return permissionDecision: "deny" are not enforced when the target tool is an MCP server tool. The hook fires correctly and returns a valid deny response, but the MCP tool call proceeds anyway.

Reproduction Steps

  1. Register a PreToolUse hook in ~/.claude/settings.json targeting an MCP tool:
{
  "hooks": {
    "PreToolUse": [
      {
        "hooks": [
          {
            "command": "/path/to/hook.sh",
            "type": "command"
          }
        ],
        "matcher": "mcp__my_server__my_tool"
      }
    ]
  }
}
  1. The hook script reads stdin and returns a deny decision:
#!/bin/bash
INPUT=$(cat)
echo "BLOCKED: reason here"
echo '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":"reason here"}}'
exit 0
  1. When Claude calls the MCP tool, the hook fires and returns deny, but the MCP tool call is executed anyway.

Expected Behavior

The MCP tool call should be blocked, and the model should see the deny reason — identical to how PreToolUse deny works for built-in tools (Bash, Read, Write, etc.).

Actual Behavior

The hook fires, returns permissionDecision: "deny", but the MCP tool call proceeds and succeeds. The deny decision is effectively ignored.

Context

This was discovered while running multiple Claude Code instances in tmux panes with an identity enforcement hook. The hook correctly detects when an agent tries to send a message as a different identity via an MCP-based messaging server, returns deny, but the message is sent anyway.

Verified:

  • Hook script works correctly when tested manually (echo '...' | hook.sh)
  • Hook IS registered with the correct matcher pattern
  • Built-in tool PreToolUse deny IS enforced (e.g., Bash hooks work)
  • MCP tool PreToolUse deny is NOT enforced

Environment

  • Claude Code CLI
  • Linux
  • MCP server type: HTTP ("type": "http")

View original on GitHub ↗

This issue has 5 comments on GitHub. Read the full discussion on GitHub ↗