Skip to main content
You run an MCP server and an agent that calls it. In production the gateway is a process between them, started with one command; here the same gateway object is driven in process against a stand-in upstream, so the recipe runs offline and shows exactly what the agent gets back. The gateway in five minutes has the production commands.

The policy

runnable

The code

runnable file=main.py

What the agent sees

One call reached the server. The other three came back as JSON-RPC errors with codes a client can act on, never as tool results the model would read as text and retry.

The receipt

runnable
Every tools/call that reaches a decision has a receipt named mcp.ops.<tool>, denied ones included — which is why four protected calls above leave three receipts and not four: the one waiting on a human is not decided yet.

When an AMBIGUOUS appears

An upstream that commits and then drops the connection comes back as -41010, ctrlrun.upstream_ambiguous, and the identical call is refused with -41005 until a human runs ctrlrun resolve restart:prod-eu:checkout --committed or --failed. For an upstream whose in-band error means it did nothing, set mcp: {not_executed_on_error: true} on the action and such errors become FAILED rather than unknown.

Next