Deus Witness

Quickstart | Concepts | API | Verify | Coverage | Teams | Anchors | Recovery | Human approval in CI | Verifiable audit log | Unattended runs | Approval when unattended | Hooks vs deny | Skip permissions | PreToolUse hook | Deny not working | Headless permissions | MCP human-in-the-loop

Human-in-the-loop for MCP tool calls: approve before the tool runs

The MCP specification wants a person able to stop tool calls, but it does not hold a call itself: "there SHOULD always be a human in the loop with the ability to deny tool invocations", and the same page says "the protocol itself does not mandate any specific user interaction model" (Tools, revision 2026-07-28). So approval has to live somewhere you choose: in the client's permission prompt, inside the server, in a proxy between the two, or in your agent framework. The client prompt is enough when a person sits at the terminal. When nobody does (CI, cron, a long autonomous run), the call has to wait somewhere that can reach a person, and fail closed when it cannot. Claude Code behaviour below is checked against 2.1.312 and its MCP and Permissions pages.

What MCP itself gives you

What Claude Code gives you

All of these work when someone answers the terminal. In claude -p, a call that would prompt is refused; --permission-prompt-tool can send it to an MCP tool of your own, but "an allow result from the prompt tool for a flagged tool is converted to a deny". More on headless runs: Claude Code headless mode: permissions and CI.

Where client prompts are not enough

A proxy in front of the server

A proxy is an MCP server that starts the real server as a child and relays the protocol. It takes tools/call, decides, and only then forwards it; tools/list, resources and notifications pass through untouched. Because it sits on the path, it applies to every client that launches the server through it, and the server needs no change.

Four things decide whether a proxy is safe:

  1. Fail closed. If the decision service is unreachable or answers something unexpected, the call is not forwarded.
  2. Bind the approval to the exact call. Approve a hash of the tool name and arguments, so a different call cannot reuse it.
  3. Respect the client's timeout. Claude Code's per-server timeout "is a hard wall-clock limit per tool call, and progress notifications from the server don't extend it". Other clients are reported to give up after about 60 seconds. A long hold should end with a normal tool result such as "not executed, waiting for approval; retry the same call", not a hang.
  4. Cover the bypass. A proxy sees only the servers behind it. Built-in tools (shell, file edits) and other configured servers go around it; deny them or route them through a hook.

In LangGraph and LangChain

If your MCP tools run inside a LangChain agent, the pause belongs in the agent. LangChain's HumanInTheLoopMiddleware checks each tool call against interrupt_on and "issues an interrupt that halts execution"; a person then chooses approve, edit, reject or respond (Human-in-the-loop). It needs a checkpointer: "You must configure a checkpointer to persist the graph state across interrupts." In plain LangGraph the same comes from calling interrupt() in a node and resuming with Command (Interrupts). This works well when your application owns the user interface; it does not help a client you do not control, such as Claude Code or Cursor.

A proxy you can install: Deus Witness

Deus Witness ships the proxy above as deus-witness-mcp. Put the real server after --:

{"mcpServers": {"files": {"command": "deus-witness-mcp",
  "args": ["--", "npx", "-y", "@modelcontextprotocol/server-filesystem", "/srv/data"],
  "timeout": 700000,
  "env": {"DEUS_WITNESS_KEY": "dwk_YOUR_KEY", "DEUS_WITNESS_HOLD_MAX_S": "600"}}}}

Each tools/call goes to the witness service and is judged against a mandate you signed with a passkey. Calls the mandate allows run, calls it refuses are blocked, and a call it does not cover waits for you to approve it with a passkey (phone or laptop). If the wait ends first, the agent gets Action NOT executed: waiting for the owner's approval. with an approval link, and the same call retried after approval runs exactly once. Before forwarding, the proxy checks that the signed receipt is for this exact call; if the witness is unreachable, the call is blocked. The default hold is 50 seconds, which suits clients with short timeouts; raise it only where the client waits longer. Limits: the proxy covers only the servers behind it, and deus-witness doctor lists the ones that are not. For Claude Code's built-in tools use the hook instead: PreToolUse hook examples. Decisions made against your mandate get a signed receipt you can check offline: Audit log for AI agent actions you can verify offline.