Skip to main content

Cursor Hooks API

Reference for how Cursor IDE hooks work and the JSON contracts used by this project.

hooks.json

Cursor reads hook configuration from multiple locations (all execute if present):

ScopePath
Project<workspace>/.cursor/hooks.json
User~/.cursor/hooks.json
Enterprise/Library/Application Support/Cursor/hooks.json (macOS)

Format

{
"version": 1,
"hooks": {
"beforeSubmitPrompt": [
{
"command": "node \"/path/to/dist/hooks/before-submit-prompt.js\"",
"timeout": 5000,
"failClosed": false
}
],
"beforeMCPExecution": [
{
"command": "node \"/path/to/dist/hooks/before-mcp-execution.js\"",
"timeout": 5000,
"failClosed": false
}
],
"postToolUse": [
{
"command": "node \"/path/to/dist/hooks/post-tool-use.js\"",
"timeout": 5000,
"failClosed": false
}
],
"afterAgentResponse": [
{
"command": "node \"/path/to/dist/hooks/after-agent-response.js\"",
"timeout": 5000,
"failClosed": false
}
]
}
}
FieldDescription
commandShell command to execute
timeoutMax execution time in milliseconds
failClosedIf true, block on hook failure. We set false (fail-open).

Hook Input (stdin)

All hooks receive JSON on stdin with common base fields:

{
"conversation_id": "...",
"generation_id": "...",
"model": "...",
"hook_event_name": "beforeSubmitPrompt",
"cursor_version": "...",
"workspace_roots": ["..."],
"user_email": "...",
"transcript_path": "..."
}

beforeSubmitPrompt

Additional fields:

FieldTypeDescription
promptstringThe user's prompt text
attachmentsarrayFile attachments (if any)

beforeMCPExecution

Additional fields:

FieldTypeDescription
tool_namestringMCP tool name (format: MCP:server:tool)
tool_inputobjectTool input parameters

postToolUse

Additional fields:

FieldTypeDescription
tool_namestringTool name (e.g., MCP:server:tool, Bash, Write, Edit)
tool_inputobjectTool input parameters
tool_outputstring|objectTool output/result

afterAgentResponse

Additional fields:

FieldTypeDescription
textstringThe AI agent's response text

Hook Output (stdout)

beforeSubmitPrompt

{
"continue": true
}

To block:

{
"continue": false,
"user_message": "Reason for blocking..."
}

beforeMCPExecution

Same contract as beforeSubmitPrompt:

{
"continue": true
}

To block:

{
"continue": false,
"user_message": "MCP tool call blocked by AIRS: ..."
}

postToolUse (observe-only)

Cursor ignores stdout and exit codes for this hook. We emit JSON for logging consistency, but it has no effect on tool output:

{
"permission": "allow"
}
Cannot block tool output

postToolUse fires after the tool has already executed. Cursor does not support blocking or modifying tool output via this hook.

afterAgentResponse (observe-only)

Cursor ignores stdout and exit codes for this hook. We emit JSON for logging consistency, but it has no effect on what the user sees:

{
"permission": "allow"
}
Cannot deny

{ "permission": "deny" } and exit code 2 are no-ops for afterAgentResponse. Cursor does not support blocking the response after it has been displayed.

Exit Codes

CodeHookMeaning
0AllSuccess
2beforeSubmitPromptDeny/block the prompt
2beforeMCPExecutionDeny/block the MCP tool call
2postToolUseNo effect (observe-only)
2afterAgentResponseNo effect (observe-only)
OtherAllHook error (fail-open if failClosed: false)
Different contracts per hook

beforeSubmitPrompt and beforeMCPExecution use { continue: false } to block. postToolUse and afterAgentResponse are observe-only and cannot block content regardless of output.

Cursor Limitation: No Response Blocking

Cursor has no beforeAgentResponse or equivalent hook. The AI response streams directly to the user, and afterAgentResponse fires only after it is already visible.

The hooks that can block all cover actions, not response text. This project implements all of them — see Optional Hooks:

HookWhat it gatesIn this project
beforeSubmitPromptUser prompt → AICore
beforeMCPExecutionMCP tool callsCore
beforeShellExecutionShell commandsOptional
beforeReadFileFile readsOptional
beforeTabFileReadTab file readsOptional
subagentStartSub-agent spawningOptional

None of these intercept the AI's natural language or code response before display. This is a gap in Cursor's hook API — there is no point in the pipeline where external code can scan the generated text and block it before the user sees it.

Recommendations:

  • Lean on prompt-side blocking — if AIRS catches a DLP pattern going in, the AI never sees it to echo back
  • Use response scanning for audit — violations are logged for compliance evidence and security team alerting
  • Request the feature from Cursor — a beforeAgentResponse hook would close this gap. Track or request it on the Cursor community forum