Authorize a tool call
Do this when an agent must not call a particular tool, or must be restricted to a named set of tools. Do it as well when a tool call failed and you need to know which of the several enforcement layers stopped it. Do not do this to remove a tool from an agent entirely — that is composition, and un-equipping the agent is cheaper than denying it. And do not expect a rule to affect a task that is already running: the policy snapshot is frozen when the task is created. Start from the posture: tool policy is default-allow. An agent with no matching rules may call every tool it is composed with. Restriction is something you add.Prerequisites
- You can create policy rules through
/v1/policies. - You know the tool’s name exactly. Matching is
fnmatchglob against the name, and two MCP instances exposing the same tool name are indistinguishable. - Read tool authorization for the layers this guide manipulates.
API=http://localhost:8000 and a bearer token in $TOKEN.
Steps
1. Choose the scope
A rule binds to one subject. The resolver merges the layers workspace → agent → user → task, and a lower layer can only tighten.
The
user layer resolves from whoever created the task, which is how the same
agent produces different verdicts for different callers.
2. Choose deny or allowlist
Deny a named tool when the default set is right and one capability is not. This is the option to reach for most of the time.allow rule at a layer contributes one entry to that layer’s allowlist.
Once an allowlist is non-empty, anything outside it is denied. An absent or empty
allowlist means “no allowlist in use”, not “deny everything”.
Both effects require a named tool. tool:* is parsed as a valid target and
then skipped by the compiler, so a wildcard allow or deny rule is stored and does
nothing.
Response is HTTP 201 with the created rule, including its id.
3. Preview the merged result
Before running anything, resolve the layers without creating a task:task_policy in the same body to see what a per-task document would do on top.
4. Tighten one task only
A per-task restriction rides on task creation rather than the rule table:Verify
Check the snapshot the task actually carries. This is the authoritative answer, because it is what every enforcement point reads:source_policy_ids, so you can confirm your rule is one of
the inputs, and resolver_version (policy-resolver-v1).
Confirm the denial at runtime. A denied call appears in the task event stream
as a tool.result event whose data carries denied_by_policy: true:
Troubleshooting
The rule exists and nothing changed. Check the target kind. The compiler handlesallow and deny only on a named tool: target. Rules targeting
mcp:<id>, model:<id>, skill:<id>, collection:<id> or tool:* are
accepted by the API, stored, returned by GET /v1/policies, and then skipped
when the layer is compiled. They have no runtime effect. Confirm with the preview
endpoint: if tools is absent or unchanged, the rule did not compile.
422 when creating or previewing. The resolver rejects a lower layer that
would loosen a higher one. An agent-level allowlist has to be a subset of the
workspace allowlist patterns; a task policy cannot re-enable a denied tool. The
error names the field that tried to widen.
The task still calls the tool. The snapshot is taken at task creation. A rule
written after the task started does not reach it, and there is no revocation path
into a running workflow. Cancel and restart the task.
It works over the API but not through the MCP proxy, or the reverse. The
proxy has no task snapshot, so it resolves only the workspace and calling user’s
layers at request time. An agent-scoped rule therefore does not apply to a
proxy call. Move the rule to the workspace or user layer if it must cover both
paths.
A tool the model should not see is still in its context. Disclosure keeps
tools that require approval, and always keeps the control-flow tools
(completion, task_complete, request_user_input, recall_history,
read_tool_output, activate_tool_source, load_tools) regardless of policy, so
a restrictive rule cannot strand a run.
The call failed but not with a policy reason. Several other layers can stop a
tool call independently of your rules: the budget gates deny when the run or
service budget is exhausted, and a pattern gate denies destructive shell and SQL
strings such as DROP TABLE, rm -rf / or TRUNCATE TABLE. Those surface as an
activity failure rather than a denied_by_policy event. Read the worker log for
the interceptor name in the raised GovernanceDenied.
Related
- Tool authorization — every layer a call clears and why they are separate.
- Require human approval — the third verdict a tool decision can return.
- Set a budget — the other gates on the pre-tool phase.