Hooks return ALLOW but MCP tool still requires manual approval / gets skipped

Where does the bug appear (feature/product)?

Cursor IDE

Describe the Bug

my hook returns ALLOW for all paths (confirmed by looking at the hooks log) but i’m still being asked to approve the tool call. mcp protection is off.

Steps to Reproduce

Setup: user-level hooks at ~/.cursor/hooks.json with both preToolUse and beforeMCPExecution using the same Python script
Issue:

Hook decisions observed in logs show preToolUse=ALLOW and beforeMCPExecution=ALLOW for MCP call user-google-drive-search
Tool call still requires manual approval
Expected behavior:

MCP call executes without manual prompt when hooks return ALLOW
Actual behavior:

Tool call fails with:
User rejected MCP: user-google-drive-search - User chose to skip
or Aborted
This happens despite hooks returning ALLOW
Repro steps:

Configure hooks to return ALLOW for the call
Invoke MCP tool user-google-drive search with query “trashed = false and viewedByMe = true”
Observe approval prompt still appears and skip/abort blocks execution despite hook ALLOW
Sample config:

{
“version”: 1,
“hooks”: {
“preToolUse”: [{ “command”: “./hooks/approval-policy.py” }],
“beforeMCPExecution”: [{ “command”: “./hooks/approval-policy.py” }]
}
}

Expected Behavior

i expected the tool calls to go through, but the ui still asks me to approve it.

Operating System

MacOS

Version Information

Version: 2.6.19 (Universal)
VSCode Version: 1.105.1
Commit: 224838f96445be37e3db643a163a817c15b36060
Date: 2026-03-12T04:07:27.435Z (1 wk ago)
Build Type: Stable
Release Track: Default
Electron: 39.4.0
Chromium: 142.0.7444.265
Node.js: 22.22.0
V8: 14.2.231.22-electron.0
OS: Darwin arm64 24.6.0

Additional Information

here is the approval-policy.py file:

#!/usr/bin/env python3
import json
import sys
from typing import Any, Dict

ALLOW: Dict[str, str] = {“permission”: “allow”}
ASK: Dict[str, str] = {“permission”: “ask”}

def _handle_before_mcp_execution(payload: Dict[str, Any]) → Dict[str, Any]:
command = payload.get(“command”, “”)

if "google-drive" in command:
    return ALLOW

return ASK

def _handle_pre_tool_use(payload: Dict[str, Any]) → Dict[str, Any]:
tool_name = str(payload.get(“tool_name”, “”))
tool_input = payload.get(“tool_input”) or {}
command = str(tool_input.get(“command”, “”))

if tool_name == "Shell":
    # Auto-allow git commands, except anything that includes push.
    if "git" in command:
        if "push" in command:
            return ASK
        return ALLOW

    return ASK

return ALLOW

def main() → int:
try:
payload = json.load(sys.stdin)
except Exception:
print(json.dumps(ASK))
return 0

event_name = str(payload.get("hook_event_name", ""))
if event_name == "beforeMCPExecution":
    response = _handle_before_mcp_execution(payload)
elif event_name == "preToolUse":
    response = _handle_pre_tool_use(payload)
else:
    response = ALLOW

print(json.dumps(response))
return 0

if name == “main”:
raise SystemExit(main())

Does this stop you from using Cursor

Sometimes - I can sometimes use Cursor

Hey, thanks for the detailed report.

This is a known limitation. Right now, hooks can only deny actions. Returning allow from a hook does not override the MCP approval system. The two permission paths, hooks and the MCP allowlist and approval flow, are independent. So even if your hook correctly returns allow, the MCP permission check still runs and prompts you.

The same root cause has been reported for shell hooks too:

As a workaround for now, to auto-approve specific MCP tools, add them to the MCP allowlist in Cursor Settings instead of relying on hooks to grant allow. Hooks are currently most useful for the deny case, blocking risky operations.

I also noticed your other report about beforeShellExecution plus sandbox beforeShellExecution returns permission: "ask" but sandboxed Agent shell still runs the command (sandbox: true). Same root cause. ask is also not enforced in the shell execution paths.

Let me know if the allowlist workaround helps.