---
title: Hook Overview
description: How the agent-receipts-hook binary captures native tool calls that bypass the MCP proxy.
---

`mcp-proxy` covers MCP tool calls. `agent-receipts-hook` covers everything else: the native host tools that agent runtimes execute directly — `Bash`, `Write`, `Edit`, `Read`, `WebFetch`, and `WebSearch` in Claude Code, for example. Together they give the daemon a complete picture of agent activity across both channels.

```mermaid
graph LR
  subgraph agent["Agent runtime (Claude Code)"]
    T1[Native tools — Bash, Write, Edit…] -- "PostToolUse\nhook stdin" --> H[agent-receipts-hook]
    T2[MCP tools — GitHub, Atlassian…] -- "stdio / JSON-RPC" --> P[mcp-proxy]
  end
  H -- "Unix socket" --> D[agent-receipts-daemon]
  P -- "Unix socket" --> D
  D --> DB[(receipts.db)]
```

## How it works

When an agent runtime invokes a native tool, it fires any configured `PostToolUse` hooks after the tool completes. `agent-receipts-hook` is wired up as one of those hooks. The runtime:

1. Serialises the tool name, input, and response as JSON
2. Spawns `agent-receipts-hook` with that JSON on stdin
3. Continues immediately — it does not wait for the hook to finish

The hook binary:

1. Reads and parses the stdin frame
2. Creates an `emitter.Event` with `channel: "claude-code"`, the tool name, input, output, and `decision: "allowed"` (PostToolUse fires only after the tool ran)
3. Forwards the event to the daemon over a Unix socket
4. Exits 0 — always, regardless of success or failure

The hook never blocks the agent. Emit failures (daemon not running, socket missing, oversized payload) are silently dropped, consistent with ADR-0010 §"Failure model".

## Difference from mcp-proxy

| | mcp-proxy | agent-receipts-hook |
|---|---|---|
| **What it covers** | MCP tool calls (`tools/call` JSON-RPC) | Native host tool calls |
| **Deployment** | Long-running subprocess per MCP server | Short-lived process per tool call |
| **Supported clients** | Any MCP client (Claude Code, Claude Desktop, Codex…) | Clients that support post-tool-use hooks |
| **Policy enforcement** | Yes — pass, flag, pause, block | No — hook fires after the tool ran |
| **Local receipt signing** | Yes — in-proxy Ed25519 | No — daemon signs |
| **Taxonomy** | MCP server tool names | Native tool names (`Bash`, `Write`, …) |

Both channels write to the same `agent-receipts-daemon` receipt chain, identified by a shared session ID.

## MCP tool overlap

Claude Code tools with the `mcp__*` prefix are already captured by `mcp-proxy`. By default, `agent-receipts-hook` is registered with an empty matcher, which means it also receives `mcp__*` PostToolUse events. This produces a second receipt for each MCP tool call — one from the proxy, one from the hook. The two receipts are distinguishable: the proxy receipt has `channel: "mcp"` and the hook receipt has `channel: "claude-code"`.

Whether this overlap is desirable depends on your use case. A future release will add a matcher-based option to exclude `mcp__*` tools from the hook. For now, the duplicate provides a cross-check: if the proxy and hook disagree on a tool call result, it is a signal worth investigating.

## Session correlation

Claude Code passes the current session ID in the `session_id` field of every hook frame. The hook forwards it to the daemon via `WithSessionID`, so all receipts within a session — from both `mcp-proxy` and `agent-receipts-hook` — carry the same session identifier and are grouped together when querying.
