holdcall

For teams running agents with MCP tools

The control that stays on your machine.

Holdcall sits between your agent and its MCP servers. It decides every tool call where the call happens, shows you the real arguments before anything dangerous runs, and keeps a record you can verify offline. The arguments never leave the machine. Neither does the decision.

holdcall logone agent, one connector, seven calls, 25 September 2026. Nothing here is a mock-up.
#whenagentconnectortooldecisionwhat happened
620:29:45claude-codegithubaddallowforwarded, answered in 63 ms
820:29:45claude-codegithubechoallowforwarded, answered in 1 ms
1020:29:45claude-codegithubexplodedenya rule denies this tool; the server never saw it
1120:29:45claude-codegithubaddallowforwarded
1320:29:45claude-codegithubaddallowforwarded, third of three allowed
1520:29:45claude-codegithubadddenybudget of 3 calls spent this session
1620:29:47claude-codegithubdangerous_toolrejectedheld for a human, who read the arguments and said no

Decides on the machine

Rules that deny, allow, or hold a tool for a human, scoped to an agent and a connector. Budgets that cap how many calls a session may make. Every way of not getting a decision is a denial: no daemon, no decision, no call.

Approves with the real arguments

A held call waits until someone runs holdcall approve and reads exactly what the server would receive, every byte shown as itself. Never a summary, never text the model wrote about the call.

Records what you can verify

Each call, decision and policy change is one entry in a hash-chained journal on disk. holdcall verify checks it without a network, and the format is documented so a second program can check it too.

The moment that matters

The agent asked to run dangerous_tool on a release branch, with force. The rule for that tool says ask. Holdcall held the call, the operator read the arguments and rejected it, and the agent was told so in words it does not retry.

The daemon wrote the rejection to the journal before the relay was told. The server never received the call.

$ holdcall approve
id         483e0508febce5dcc97fb26becf27180-7
age        2s
agent      claude-code
connector  github
tool       dangerous_tool
arguments:
  {
    "branch": "release/2026-09",
    "force": true
  }
$ holdcall reject 483e0508febce5dcc97fb26becf27180-7 --reason "not that branch"
rejected dangerous_tool for agent claude-code on connector github (recorded in the journal)
reason: not that branch
what the agent received
A human reviewing this call's real arguments rejected it. Do not retry automatically.

How it fits

  1. Your clientClaude Code, Cursor, Claude Desktop. Pointed at Holdcall by holdcall init.
  2. holdcall serveRelays every byte unchanged. A tools/call is decided before it is forwarded.
  3. Your MCP serverReceives exactly what the client sent, or nothing.

Under the relay, one daemon per user decides against rules and budgets in SQLite, keeps credentials in the OS store bound to one command, and writes the journal. It answers only to processes that are Holdcall, and it works with no network. The agent's identity is derived from the executable that spawned the relay, as the kernel reports it; nothing on the wire can claim to be Claude Code.

The local console's Held view: one call to dangerous_tool held for a human, showing the agent, the connector, its real arguments with a bidirectional override made visible, and the approve and reject commands to copy
The local console, read-only, on loopback: a held call with its real arguments. Approving stays on the command line.

What it does not promise

Not tamper-proof
The chain has no key. Anyone who can write the journal file can rewrite it and it will verify. What turns the record into evidence is holdcall verify --expect-head against a head you recorded somewhere else. We do not use the words audit log.
Verified on macOS
Linux and Windows are compiled and vetted, not yet run. The status page says which claims hold on which platform, per test.
Rules match names, not arguments
A rule is keyed on agent, connector and tool. Conditions on what a call carries are not built; a human reading the arguments is how that gap is covered today.
  • 414tests, run with the race detector on every change
  • 56attacks tried against real builds, 23 of them live before they were closed
  • 23guarantees, each citing the tests that hold it
  • 29findings published, 2 still open

All of it is on the status page, which refuses to build on a claim without a test behind it.

Early access

The engine is free for one person and one machine, and always will be. The team console, with the journal of every machine, policy in one place and approvals from your phone, opens to a small group first.

One address, stored until the team console opens, deleted on request.