AgentBrake
SHIPPEDA stdio proxy that intercepts an AI agent's MCP tool calls and enforces policies before they run.
Personal project, one-day build · Feb 2026 · fixes Sep 2026
THE PROBLEM
Agents get tools such as file access and shell. Enforce limits on those calls without modifying the agent.
HOW IT WORKS
- The proxy spawns the MCP server as a child process, parses JSON-RPC on stdin, runs each tool call through a chain of policy classes, then forwards or blocks it.
- Policies are configured in YAML validated with zod: per-tool allow/deny rules on arguments, rate and budget limits.
THE PIPELINE
- AgentSends JSON-RPC over stdio.
- AgentBrakeBuffers and parses each message. Unparseable input gets an error.
- Policy chainAllow and deny rules, argument regexes, rate and budget limits, circuit breaker.
- MCP serverStarted as a child process; receives only calls that passed.
- ResponseTool errors are reported back to the circuit breaker.
ENGINEERING EVIDENCE
- Per-argument regex allow and deny rules, so a tool can be allowed while sensitive paths are blocked. GranularAccessPolicy.ts ↗
- Fails closed: unparseable input, batches, malformed tool calls and policy errors are answered with a JSON-RPC error instead of being forwarded, and an invalid or missing policy file makes the proxy refuse to start. Tests split a tool call at every byte boundary and check the policy still applies. proxy.test.ts ↗
- The circuit breaker is fed real tool errors from the server's responses, so it can trip. interceptor.ts ↗
- 85 tests pass. Published to npm as agentbrake, with rogue-agent attack demos in the repository.
DECISIONS & INVESTIGATIONS
tests/proxy.test.ts splits a tools/call at every byte boundary and asserts the policy still applies, and covers multiple messages per chunk, CRLF, invalid UTF-8, batches, oversize lines and duplicate keys.
- Zod-validated YAML policy file, then a same-day catch-all fallback that disables every policyPARTIAL
Fail-fast became fail-open.
Running the built proxy: six consecutive calls to a tool that always fails all reached the server (error -32603) with a breaker threshold of 3; none was short-circuited, because recordFailure() is called only from...
The policy matches case-sensitive regular expressions against the raw argument text, so it blocks the strings used in the demo but not equivalent variations (different letter case, spacing, path form or recipient...
WHAT ISN'T DONE
- Human approval is not implemented: a call that needs approval is refused with a pending error and nothing can approve it. A sandbox action is enforced as a block.
- Stdio only, and not a sandbox: argument rules are regular expressions, which are bypassable, and a server that ignores the proxy is out of scope.
- The budget policy counts calls at a flat cost; it does not track tokens or real spend.
- The first version failed open: any line the proxy could not parse, including a tool call split across stdin chunks, was forwarded unchecked, and an invalid policy file disabled enforcement. Both were fixed in Sep 2026 (see the decisions page).
NEXT STEPS
Each one comes from a gap listed above. It says what fixing the gap would take; it is not a promise.
- Build human approval: a way to approve or deny a pending call that the agent cannot use on itself.
- Track tokens or real spend in the budget policy instead of a flat cost per call.
- Publish a new npm release with the fail-closed fixes.
STACK
- TypeScript
- Node.js
- zod
- Jest
- Docker