Skip to main content
Draft / RFC — 2026-04-14 · TrueFoundry Engineering. Internal design reference; endpoints and behavior may change before general availability.

1. Executive Summary

AI coding assistants (Cursor, Claude Code, and others emerging) now support hooks — lifecycle callbacks that let external systems observe, control, and extend the agent loop. TrueFoundry’s AI Gateway already has a working Cursor hooks endpoint (/cursor/hooks/validate). This document maps the exact API surface of each client, identifies gaps, and proposes a unified architecture to support all clients through the gateway’s guardrail engine.

2. Current State (What We Have)

Existing Endpoint

POST /cursor/hooks/validate
POST /api/llm/cursor/hooks/validate
Implementation Files:
  • Router: src/routers/cursorHooks.ts
  • Handler: src/handlers/cursorHooksHandler.ts
Current Request Body:
{
  "hook_event_name": "beforeSubmitPrompt | beforeShellExecution | beforeReadFile | afterFileEdit | afterAgentResponse | beforeMCPExecution | afterMCPExecution",
  "text": "content to validate",
  "conversation_id": "string",
  "generation_id": "string",
  "workspace_roots": ["string"],
  "metadata": { "server": "mcp-server-name", "tool_name": "tool-name" }
}
Current Response Body:
{
  "continue": true | false,
  "permission": "allow" | "deny",
  "userMessage": "string",
  "agentMessage": "string",
  "guardrail_checks": [
    { "id": "string", "verdict": true|false, "error": "string", "data": {}, "execution_time": 123 }
  ]
}
What it does: Maps hook events to guardrail types (input/output/mcp-pre/mcp-post), resolves applicable guardrails for the user/tenant, executes them, and returns allow/deny.

3. Cursor Hooks API — Complete Reference

3.1 Protocol

AspectDetail
TransportHooks are local child processes communicating via stdin/stdout JSON. No HTTP involved natively.
RegistrationFile-based: .cursor/hooks.json (project), ~/.cursor/hooks.json (user), MDM-managed (enterprise)
Exit codes0 = success (parse stdout), 2 = block/deny, other = fail-open
Fail behaviorDefault: fail-open. Set failClosed: true to block on hook errors

3.2 Manifest Format (hooks.json)

{
  "version": 1,
  "hooks": {
    "<eventName>": [
      {
        "command": "./path/to/script.sh",
        "type": "command" | "prompt",
        "timeout": 30,
        "loop_limit": 5,
        "failClosed": false,
        "matcher": "regex-pattern"
      }
    ]
  }
}

3.3 All Hook Events

EventCategoryMatcher Target
sessionStartSession
sessionEndSession
beforeSubmitPromptPre-input
preToolUsePre-toolTool name (Shell|Read|Write|Task|MCP)
postToolUsePost-toolTool name
postToolUseFailurePost-toolTool name
beforeShellExecutionPre-tool (shell)
afterShellExecutionPost-tool (shell)
beforeMCPExecutionPre-MCP
afterMCPExecutionPost-MCP
beforeReadFilePre-file
afterFileEditPost-file
afterAgentResponsePost-response
afterAgentThoughtPost-thought
subagentStartSubagent
subagentStopSubagent
stopLifecycle
preCompactCompaction
beforeTabFileReadTab (inline)
afterTabFileEditTab (inline)

3.4 Key Input/Output Schemas

Common base (all events):
{
  "conversation_id": "string",
  "generation_id": "string",
  "model": "string",
  "hook_event_name": "string",
  "cursor_version": "string",
  "workspace_roots": ["string"],
  "user_email": "string | null",
  "transcript_path": "string | null"
}
preToolUse input:
{
  "tool_name": "Shell|Read|Write|Task|MCP",
  "tool_input": { "command": "..." },
  "tool_use_id": "string",
  "cwd": "string",
  "agent_message": "string"
}
preToolUse output:
{
  "permission": "allow|deny",
  "user_message": "string",
  "agent_message": "string",
  "updated_input": {}
}
beforeShellExecution input:
{ "command": "string", "cwd": "string", "sandbox": false }
beforeMCPExecution input:
{ "tool_name": "string", "tool_input": "string", "url": "string" | "command": "string" }
beforeSubmitPrompt input:
{ "prompt": "string", "attachments": [{ "type": "file|rule", "file_path": "string" }] }
Permission output (shared across pre-hooks):
{ "permission": "allow|deny|ask", "user_message": "string", "agent_message": "string" }

3.5 How Cursor Calls External Hooks (Gateway Integration Pattern)

Cursor hooks are local processes. To route through the gateway, teams deploy a thin CLI script as the hook command that:
  1. Reads JSON from stdin
  2. POSTs it to https://gateway.example.com/cursor/hooks/validate
  3. Reads the gateway response
  4. Exits with code 0 (allow) or 2 (deny)
This is the bridge pattern our current implementation relies on.

4. Claude Code Hooks API — Complete Reference

4.1 Protocol

AspectDetail
TransportFour handler types: command (stdin/stdout), http (POST), prompt (LLM eval), agent (sub-agent)
Registration~/.claude/settings.json (user), .claude/settings.json (project), .claude/settings.local.json (local), managed policy (enterprise), plugin hooks/hooks.json
Exit codes0 = success, 2 = block, other = non-blocking error
Fail behaviorDefault: fail-open.
Key differenceNative HTTP hook support — can POST directly to gateway without a CLI bridge

4.2 Manifest Format (settings.json)

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|Edit|Write",
        "hooks": [
          {
            "type": "command" | "http" | "prompt" | "agent",
            "command": "./script.sh",
            "url": "https://gateway.example.com/hooks/validate",
            "headers": { "Authorization": "Bearer $TFY_API_KEY" },
            "allowedEnvVars": ["TFY_API_KEY"],
            "timeout": 30,
            "if": "Bash(rm *)",
            "statusMessage": "Checking guardrails...",
            "async": false,
            "once": false
          }
        ]
      }
    ]
  }
}

4.3 All Hook Events

EventCategoryMatcher Target
SessionStartSessionSource: startup|resume|clear|compact
SessionEndSessionReason: clear|resume|logout|…
UserPromptSubmitPre-input
PreToolUsePre-toolTool name (Bash|Edit|Write|Read|Glob|Grep|WebFetch|Agent|mcp__*)
PostToolUsePost-toolTool name
PostToolUseFailurePost-toolTool name
PermissionRequestPermissionTool name
PermissionDeniedPermissionTool name
StopLifecycle
StopFailureLifecycleError type: rate_limit|authentication_failed
SubagentStartSubagentAgent type: Explore|Bash|Plan
SubagentStopSubagentAgent type
PreCompact / PostCompactCompactionmanual|auto
NotificationAsyncnotification_type
WorktreeCreate / WorktreeRemoveAsync
InstructionsLoadedAsyncFile path
ConfigChangeAsyncConfig source
CwdChangedAsync
FileChangedAsyncFilename pattern
TaskCreated / TaskCompletedAsync
TeammateIdleAsync
Elicitation / ElicitationResultMCPServer name

4.4 Key Input/Output Schemas

Common base (all events):
{
  "session_id": "string",
  "transcript_path": "string",
  "cwd": "string",
  "permission_mode": "default|plan|acceptEdits|auto|dontAsk|bypassPermissions",
  "hook_event_name": "string",
  "agent_id": "string",
  "agent_type": "string"
}
PreToolUse input:
{
  "tool_name": "Bash|Edit|Write|Read|Glob|Grep|WebFetch|Agent|mcp__server__tool",
  "tool_input": {
    "command": "npm test",
    "description": "Run tests",
    "timeout": 120000
  },
  "tool_use_id": "toolu_01ABC..."
}
PreToolUse output:
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow|deny|ask|defer",
    "permissionDecisionReason": "string",
    "updatedInput": {},
    "additionalContext": "string"
  }
}
UserPromptSubmit input:
{ "prompt": "user's text" }
UserPromptSubmit output:
{
  "decision": "block|none",
  "reason": "string",
  "hookSpecificOutput": {
    "hookEventName": "UserPromptSubmit",
    "additionalContext": "string"
  }
}
Universal output fields (all events):
{
  "continue": true,
  "stopReason": "string",
  "suppressOutput": false,
  "systemMessage": "Warning shown to user",
  "decision": "block|none",
  "reason": "string",
  "hookSpecificOutput": { "hookEventName": "...", "...": "..." }
}
Permission decision precedence: deny > defer > ask > allow

4.5 HTTP Hook Type (Direct Gateway Integration)

This is the critical difference from Cursor. Claude Code can POST directly to our gateway:
{
  "type": "http",
  "url": "https://llm-gateway.truefoundry.com/api/llm/hooks/validate",
  "headers": {
    "Authorization": "Bearer $TFY_API_KEY"
  },
  "allowedEnvVars": ["TFY_API_KEY"],
  "timeout": 30
}
  • Request body = full event JSON sent as POST body
  • Response = JSON parsed for decision/output
  • Non-2xx = non-blocking error (execution continues)
  • Must return 2xx with JSON body containing deny decision to actually block

5. Side-by-Side Comparison

DimensionCursorClaude Code
Protocolstdin/stdout (child process only)stdin/stdout, HTTP, prompt, agent
Direct HTTP to gatewayNo (needs CLI bridge script)Yes (native)
Config location.cursor/hooks.json.claude/settings.json
Config schema version{ "version": 1, "hooks": {...} }{ "hooks": {...} }
Event namingcamelCase (beforeShellExecution)PascalCase (PreToolUse)
Permission valuesallow|deny|askallow|deny|ask|defer
Output structureFlat { permission, user_message }Nested { hookSpecificOutput: { permissionDecision } }
Input mutationupdated_input in preToolUseupdatedInput in hookSpecificOutput
Fail behaviorfailClosed per-hookDefault fail-open (no per-hook override)
Auth in hooksuser_email env varheaders with env var interpolation
MCP tool namingmetadata.server + metadata.tool_namemcp__<server>__<tool> in tool_name
Total event types~20~30+
Shared eventspreToolUse, postToolUse, beforeShellExecution, afterFileEdit, beforeSubmitPrompt, beforeMCPExecution, afterMCPExecution, sessionStart/End, subagentStart/Stop, preCompact, stopAll of Cursor’s + PermissionRequest, PermissionDenied, StopFailure, Notification, WorktreeCreate/Remove, InstructionsLoaded, ConfigChange, FileChanged, TaskCreated/Completed, TeammateIdle, Elicitation, PostCompact

6. Engineering Design Decisions

Decision 1: Unified vs. Client-Specific Endpoints

Option A: Single unified endpoint /api/llm/hooks/validate
  • Normalize all client formats into a common internal representation
  • Pro: One handler, one guardrail resolution path
  • Con: Must handle divergent schemas, risk of lowest-common-denominator
Option B: Client-specific endpoints /cursor/hooks/validate, /claude-code/hooks/validate, etc.
  • Each client gets a dedicated handler with native schema support
  • Pro: Full fidelity, easier to debug, client-specific features
  • Con: Code duplication, more maintenance
Option C (Recommended): Unified core + client adapters
  • Thin adapter layer per client that normalizes to internal format
  • Shared guardrail resolution and execution engine (already exists)
  • Client-specific response serialization
┌─────────────┐    ┌──────────────┐    ┌─────────────────────┐
│ Cursor       │───>│ Cursor       │───>│                     │
│ CLI bridge   │    │ Adapter      │    │  Guardrail Engine   │
└─────────────┘    └──────────────┘    │  (HooksManager +    │
                                       │   Plugin System)     │
┌─────────────┐    ┌──────────────┐    │                     │
│ Claude Code  │───>│ Claude Code  │───>│                     │
│ HTTP hook    │    │ Adapter      │    └─────────────────────┘
└─────────────┘    └──────────────┘
QUESTION: Do we want to invest in a full adapter pattern now or start with Claude Code endpoint only (since it has native HTTP) and keep Cursor as-is?

Decision 2: Claude Code Response Format

The current Cursor response format is flat:
{ "continue": true, "permission": "allow", "userMessage": "", "agentMessage": "" }
Claude Code expects nested output:
{
  "continue": true,
  "decision": "none",
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "allow",
    "additionalContext": ""
  }
}
QUESTION: Should the Claude Code adapter return the exact Claude Code schema, or can we get away with just the common fields (continue, decision, reason)? Need to test what Claude Code actually requires vs. what it tolerates.

Decision 3: Event Mapping to Guardrail Types

Current mapping (Cursor):
Hook EventGuardrail Type
beforeSubmitPrompt, beforeShellExecution, beforeReadFilellmInputGuardrails
afterFileEdit, afterAgentResponsellmOutputGuardrails
beforeMCPExecutionmcpToolPreInvokeGuardrails
afterMCPExecutionmcpToolPostInvokeGuardrails
New events from Claude Code that need mapping:
Claude Code EventProposed Guardrail TypeNotes
UserPromptSubmitllmInputGuardrailsSame as beforeSubmitPrompt
PreToolUse (Bash)llmInputGuardrailsSame as beforeShellExecution
PreToolUse (Edit/Write)llmInputGuardrailsSame as beforeReadFile
PreToolUse (mcp__*)mcpToolPreInvokeGuardrailsMCP tool call
PostToolUsellmOutputGuardrailsSame as afterFileEdit/afterAgentResponse
PostToolUse (mcp__*)mcpToolPostInvokeGuardrailsMCP tool result
StopNew: agentStopGuardrails?No current equivalent
PermissionRequestNew: permissionGuardrails?Could auto-approve/deny
SessionStartNot a guardrailObservability/analytics only
NotificationNot a guardrailObservability/analytics only
QUESTION: Which Claude Code events do we actually want to run guardrails on? The full list has 30+ events — most are observability. Should we start with just the blocking events (UserPromptSubmit, PreToolUse, PostToolUse) and treat the rest as analytics passthrough?

Decision 4: Input Extraction from Claude Code Events

The current handler extracts text from the request body and wraps it as:
const requestParams = { messages: [{ role: 'user', content: text }], model: cacheIdentifier };
Claude Code sends structured tool input, not just text:
{
  "tool_name": "Bash",
  "tool_input": { "command": "rm -rf /", "description": "Clean up" }
}
QUESTION: How should we extract the “text to validate” from Claude Code’s structured input?
  • Option A: JSON.stringify(tool_input) — simple but loses semantic meaning
  • Option B: Extract the primary field per tool type (command for Bash, content for Write, file_path for Read)
  • Option C: Send the full structured input to guardrails and let each plugin decide what to inspect

Decision 5: Authentication for Claude Code HTTP Hooks

Claude Code supports header interpolation from env vars:
{ "headers": { "Authorization": "Bearer $TFY_API_KEY" }, "allowedEnvVars": ["TFY_API_KEY"] }
QUESTION: Do we use the existing JWT auth flow (user must have a TFY API key)? Or do we create a lighter-weight hook-specific API key? Considerations:
  • JWT flow requires the user to have a TrueFoundry account
  • Hook-specific key could be scoped to just guardrail validation
  • Enterprise deployment: how does the admin distribute credentials?

Decision 6: Input Mutation Support

Both Cursor and Claude Code support mutating the tool input via hook response:
  • Cursor: { "updated_input": { "command": "safe-version" } }
  • Claude Code: { "hookSpecificOutput": { "updatedInput": { "command": "safe-version" } } }
QUESTION: Should our guardrails be able to mutate inputs (e.g., sanitize a shell command, redact PII from a prompt)? This is powerful but risky. Current implementation only does allow/deny.

Decision 7: Streaming / Latency Requirements

Hooks are in the critical path of every tool call. If the gateway is slow:
  • Cursor: User sees a hang (script blocked on HTTP POST)
  • Claude Code: User sees custom statusMessage spinner
QUESTION: What is the acceptable p99 latency for hook validation?
  • Current guardrail execution times?
  • Should we implement a fast-path for common cases (e.g., cache allow decisions for repeated tool types)?
  • Should we support async guardrails that don’t block the agent?

Decision 8: Additional Context Injection

Claude Code hooks can inject additionalContext — text that gets added to the conversation for the LLM to see. This is a powerful feature for:
  • Adding security policies inline
  • Warning about sensitive operations
  • Providing organization-specific instructions
QUESTION: Should our gateway be able to inject context (not just allow/deny)? What would the guardrail plugin interface look like for this?

Decision 9: defer Permission (Claude Code Only)

Claude Code supports a 4th permission level: defer (let Claude decide, but show the user). This is between allow and ask. QUESTION: Should we support defer in our response? This would require guardrails to have a “soft warning” mode in addition to hard block.

Decision 10: Other Clients (Future-Proofing)

Emerging AI coding assistants likely to adopt hooks:
  • Windsurf (Codeium) — likely similar to Cursor’s local-process model
  • GitHub Copilot — may adopt HTTP hooks
  • JetBrains AI — may adopt HTTP hooks
  • Zed AI — early stage
  • Aider, Continue.dev — OSS tools
QUESTION: How much should we invest in a generic adapter pattern now vs. adding clients one at a time?

7. Proposed Architecture

                    ┌─────────────────────────────────────────────┐
                    │           TFY LLM Gateway                   │
                    │                                             │
  Cursor            │  ┌──────────────┐                           │
  (CLI bridge) ────>│  │ /cursor/     │                           │
                    │  │ hooks/       │──┐                        │
                    │  │ validate     │  │  ┌──────────────────┐  │
                    │  └──────────────┘  │  │                  │  │
                    │                    ├─>│  Hooks Adapter    │  │
  Claude Code       │  ┌──────────────┐  │  │  Layer           │  │
  (HTTP hook) ─────>│  │ /claude-code/│  │  │                  │  │
                    │  │ hooks/       │──┘  │  - Normalize     │  │
                    │  │ validate     │     │    input          │  │
                    │  └──────────────┘     │  - Map events    │  │
                    │                       │  - Extract text   │  │
                    │  ┌──────────────┐     │                  │  │
  Future Client ───>│  │ /hooks/      │────>│                  │  │
                    │  │ validate     │     └────────┬─────────┘  │
                    │  └──────────────┘              │             │
                    │                                v             │
                    │                    ┌──────────────────────┐  │
                    │                    │  Guardrail Engine    │  │
                    │                    │  (HooksManager)      │  │
                    │                    │                      │  │
                    │                    │  - Resolve rules     │  │
                    │                    │  - Execute plugins   │  │
                    │                    │  - Collect verdicts  │  │
                    │                    └────────┬─────────────┘  │
                    │                             │                │
                    │                    ┌────────v─────────────┐  │
                    │                    │  Response Adapter    │  │
                    │                    │  (per-client format) │  │
                    │                    └──────────────────────┘  │
                    └─────────────────────────────────────────────┘

Internal Normalized Event Format

interface NormalizedHookEvent {
  // Source client
  client: 'cursor' | 'claude-code' | 'generic';

  // Normalized event category
  category: 'input' | 'output' | 'mcp-pre' | 'mcp-post' | 'lifecycle' | 'analytics';

  // Original event name (preserved for logging/debugging)
  originalEventName: string;

  // Content to validate
  content: string;

  // Structured input (for guardrails that need it)
  structuredInput?: Record<string, unknown>;

  // Tool context
  toolName?: string;
  toolType?: 'shell' | 'file-read' | 'file-write' | 'file-edit' | 'mcp' | 'web' | 'agent' | 'other';

  // MCP context
  mcpServer?: string;
  mcpTool?: string;

  // Session context
  sessionId?: string;
  conversationId?: string;

  // User context (from auth middleware)
  user: Session;
  metadata: Record<string, unknown>;
}

8. Implementation Plan

Phase 1: Claude Code HTTP Hook Endpoint (Week 1-2)

  1. Create src/routers/claudeCodeHooks.ts and src/handlers/claudeCodeHooksHandler.ts
  2. Mount at /claude-code/hooks/validate and /api/llm/claude-code/hooks/validate
  3. Handle PreToolUse, PostToolUse, UserPromptSubmit events
  4. Parse Claude Code input format, map to guardrail types
  5. Return Claude Code response format (hookSpecificOutput)
  6. Auth via Bearer token header (existing JWT flow)

Phase 2: Adapter Layer Refactor (Week 2-3)

  1. Extract common guardrail resolution logic from cursorHooksHandler.ts
  2. Create src/hooks/adapters/cursor.ts and src/hooks/adapters/claudeCode.ts
  3. Create src/hooks/normalizedEvent.ts for internal format
  4. Refactor both handlers to use shared core

Phase 3: Advanced Features (Week 3-4)

  1. Input mutation support (return modified tool inputs)
  2. Additional context injection
  3. defer permission support
  4. Analytics passthrough for non-blocking events (SessionStart, Stop, etc.)

Phase 4: Distribution & Onboarding (Week 4-5)

  1. CLI bridge script for Cursor (npm package or downloadable script)
  2. Claude Code .claude/settings.json template generator
  3. TrueFoundry dashboard UI for hooks configuration
  4. Documentation and onboarding guide

9. Open Questions (Sorted by Priority)

P0 — Must answer before implementation

#QuestionContext
1Which Claude Code events should we run guardrails on?30+ events exist. Recommend starting with UserPromptSubmit, PreToolUse, PostToolUse only. The rest are analytics.
2What is the exact response format Claude Code requires?Need to test: does Claude Code work with just { "decision": "block", "reason": "..." } or does it need the full hookSpecificOutput nesting?
3How do we extract “text to validate” from structured tool inputs?Bash sends { command }, Write sends { content }, Edit sends { old_string, new_string }. Each tool has different primary content.
4Do we use existing JWT auth or create hook-specific API keys?Affects onboarding friction. JWT requires TFY account.

P1 — Should answer before Phase 2

#QuestionContext
5Should we support input mutation?Powerful but risky. E.g., rewriting a dangerous shell command.
6Should we support additionalContext injection?Can inject security policies into the LLM’s context window.
7What is acceptable p99 latency for hook validation?Hooks are in the critical path. Every ms matters.
8Should we support the defer permission level?Only Claude Code supports it. Adds complexity.
9Unified endpoint or keep client-specific?Affects API surface, routing, and documentation.

P2 — Can defer to Phase 3+

#QuestionContext
10How do we handle analytics-only events?SessionStart/End, Stop, Notification etc. Log them? Forward to OTEL?
11Should we generate .cursor/hooks.json and .claude/settings.json from the dashboard?Better DX but more surface area.
12How do we distribute the Cursor CLI bridge script?npm package? curl-installable? Bundled in TFY CLI?
13Do we need to handle PermissionRequest to auto-configure Claude Code permissions?This would let the gateway auto-allow/deny permission prompts.
14How do we handle failClosed behavior differences?Cursor has per-hook failClosed. Claude Code is always fail-open. Should gateway control this?
15Future clients: invest in generic adapter now or add ad-hoc?Windsurf, GitHub Copilot, JetBrains AI are likely next.

10. Appendix: Client Configuration Examples

A. Cursor Setup (with CLI bridge)

.cursor/hooks.json:
{
  "version": 1,
  "hooks": {
    "beforeSubmitPrompt": [
      {
        "command": "tfy-hook-bridge",
        "timeout": 10,
        "failClosed": true
      }
    ],
    "beforeShellExecution": [
      {
        "command": "tfy-hook-bridge",
        "timeout": 10,
        "failClosed": true
      }
    ],
    "afterFileEdit": [
      {
        "command": "tfy-hook-bridge",
        "timeout": 10
      }
    ],
    "beforeMCPExecution": [
      {
        "command": "tfy-hook-bridge",
        "timeout": 10,
        "failClosed": true,
        "matcher": ".*"
      }
    ]
  }
}
tfy-hook-bridge script (conceptual):
#!/bin/bash
INPUT=$(cat)
RESPONSE=$(echo "$INPUT" | curl -s -X POST \
  -H "Authorization: Bearer $TFY_API_KEY" \
  -H "Content-Type: application/json" \
  -d @- \
  "https://llm-gateway.truefoundry.com/cursor/hooks/validate")

PERMISSION=$(echo "$RESPONSE" | jq -r '.permission')
if [ "$PERMISSION" = "deny" ]; then
  echo "$RESPONSE" >&2
  exit 2
fi
echo "$RESPONSE"
exit 0

B. Claude Code Setup (native HTTP)

.claude/settings.json:
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [
          {
            "type": "http",
            "url": "https://llm-gateway.truefoundry.com/api/llm/claude-code/hooks/validate",
            "headers": {
              "Authorization": "Bearer ${TFY_API_KEY}"
            },
            "allowedEnvVars": ["TFY_API_KEY"],
            "timeout": 10,
            "statusMessage": "Checking guardrails..."
          }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Bash|Edit|Write|mcp__.*",
        "hooks": [
          {
            "type": "http",
            "url": "https://llm-gateway.truefoundry.com/api/llm/claude-code/hooks/validate",
            "headers": {
              "Authorization": "Bearer ${TFY_API_KEY}"
            },
            "allowedEnvVars": ["TFY_API_KEY"],
            "timeout": 10,
            "statusMessage": "Validating tool use..."
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Bash|Edit|Write|mcp__.*",
        "hooks": [
          {
            "type": "http",
            "url": "https://llm-gateway.truefoundry.com/api/llm/claude-code/hooks/validate",
            "headers": {
              "Authorization": "Bearer ${TFY_API_KEY}"
            },
            "allowedEnvVars": ["TFY_API_KEY"],
            "timeout": 10
          }
        ]
      }
    ]
  }
}

11. References