Using agentveto from MCP-aware editors¶
agentveto ships an MCP server which
exposes the policy engine to any MCP-aware client - Claude Code, Cursor,
Continue, Zed, etc. The LLM in your editor can then ask "would this call be
allowed?" before it makes it.
Install¶
pip install 'agentveto[mcp]'
This pulls in mcp<2 (the FastMCP API). The base install has zero deps; MCP
is opt-in so the package doesn't grow for users who never need it.
Wire it up¶
In your editor's MCP config (Claude Code's ~/.claude/mcp.json, Cursor's
settings, etc.):
{
"mcpServers": {
"agentveto": {
"command": "agentveto-mcp",
"args": ["--policy", "/abs/path/to/your/policy.json"]
}
}
}
That's it. The server speaks over stdio. No ports, no auth.
Tools exposed¶
| Tool | What it does |
|---|---|
evaluate_policy(action, payload) |
Returns {effect, rule, allowed, reason}. Same call the runtime decorator makes internally. |
explain_policy(action, payload) |
Returns every rule's verdict plus the final decision. Use this when writing or debugging a policy. |
validate_policy(policy) |
Structural check on a JSON policy. Returns {valid: bool, error?} plus the normalised rule count. |
load_policy_file(path) |
Hot-swap the active policy without restarting the editor. |
All four tools accept the policy argument as a dict, a JSON-encoded string,
or a file path. Without one, evaluate_policy and explain_policy fall
back to the policy that was loaded at startup (or report "no policy loaded"
if you didn't pass --policy).
Why bother¶
The point is not "LLM calls the policy engine" - the point is "the LLM can ask the policy engine what it would do before it commits to a tool call, and rewrite the call if it would be denied." That changes the failure mode:
- Without MCP: the LLM calls
send_email, the gate raisesVetoError, the LLM has to recover from an error mid-run. - With MCP: the LLM asks
evaluate_policy("send_email", {...}), gets backdeny: Marketing email requires a human, and either rewrites the call (send_in_app_messageinstead) or escalates to a human prompt.
That's the difference between "agent that gets caught" and "agent that doesn't try."
Custom policy at runtime¶
The MCP server keeps the loaded policy in memory. To swap it:
use the load_policy_file tool with path /etc/agentveto/policy.json
The next evaluate_policy call will use the new policy. There is no
restart, no signal, no commit ceremony.
Development loop¶
While iterating on a policy:
# 1. draft it
$EDITOR policy.json
# 2. validate it
agentveto policy validate policy.json --strict
# 3. dry-run a representative call
agentveto policy explain policy.json \
--action send_email \
--payload '{"to":"bob@external.com","body":"hi"}'
# 4. once the trace looks good, hit reload in your editor
This loop runs without the editor running at all. The editor picks up the new policy on the next call.