Local IntentLedger and MCP stdio proxy: propose a tool action, approve or deny in an append-only hash-chained ledger, then let an identical tools/call reach one upstream MCP server.
This is not a SaaS. The ledger path stays on disk. dry-run and approve record decisions only; they do not execute tools. The MCP proxy is what executes, and only after approve.
pip install actgate
Dev:
pip install -e .[dev]
actgate init
actgate propose --tool shell.exec --args '{"cmd":"ls"}' --blast-tags fs.read
actgate dry-run <intent_id>
actgate approve <intent_id>
actgate verify
actgate list
Point your MCP client at ActGate instead of the upstream server:
actgate init
actgate mcp --upstream python -m some_mcp_server
Use the same --root (or the same working directory) for init, approve/deny, and mcp, so they share one ledger.
Flow:
- Client
tools/listis forwarded to upstream. Onlytools/callis gated; other methods are forwarded. - First
tools/callfor a tool+args writes a propose event and returnsACTGATE_PENDING intent_id=...(upstream is not called). - Human:
actgate approve <intent_id>(oractgate deny <intent_id>). - Identical subsequent
tools/call(same tool and args) runs upstream once and appends anexecuteevent. A third call returns already-executed. - Denied intents never hit upstream.
Bare verify checks hash-chain integrity only. Set ACTGATE_SEAL_KEY for
optional HMAC seals, or pass verify --require-seal.
| Code | Meaning |
|---|---|
| 0 | ok (propose, approve, verify clean, show/list) |
| 1 | deny recorded, or verify found a broken chain / bad seal |
| 2 | setup error (missing ledger, bad path, invalid args) |
{
"tool": "shell.exec",
"args": {"cmd": "ls"},
"args_hash": null,
"blast_tags": ["fs.read"],
"requested_mode": "execute",
"created_at": "2026-09-06T00:00:00+00:00"
}- Not a hosted approval product
- Not a policy DSL
- No network calls in the ledger core path (the MCP proxy talks to a local upstream process)
pip install -e .[dev]
pytest