Control Tool Calls
Enforce a default-deny Cedar policy on every Claude Code tool call, CLI and MCP alike, with in-the-loop approvals and a full audit trail.
Coding agents are only as safe as the actions they’re allowed to take. This guide shows how Keycard enforces Cedar policy on every tool call Claude Code makes, CLI commands and MCP tools alike, so an agent can only do what you’ve explicitly allowed.
By the end you’ll have a session that:
- Enforces a default-deny policy on every tool call (CLI and MCP)
- Blocks a destructive action, then re-allows it with a human checkpoint
- Prompts you in-the-loop for sensitive actions
- Produces a full audit trail in the Keycard Console
Prerequisites
Section titled “Prerequisites”- The Keycard CLI and Claude Code installed
- A project with a
keycard.tomland connected MCP servers, running insidekeycard run claude. Set this up in Connect Claude to Resources
Install the Keycard plugin
Section titled “Install the Keycard plugin”The plugin ties Keycard into Claude Code’s hook system. The important one for this guide is the pre-tool-use hook: every time Claude Code is about to use a tool, whether an MCP tool call or a CLI command, the request is routed through this hook, compared against your policy, and allowed or denied.
claude plugin marketplace add keycardai/pluginsclaude plugin install keycard-cli@keycardaiYou can also ask Claude Code to install the plugin into the project for you.
How policy decisions work
Section titled “How policy decisions work”Policies are written in Cedar and are default-deny: anything not explicitly permitted is denied. Each rule produces one of three outcomes:
| Outcome | What happens |
|---|---|
| Permit | Allowed silently, with no prompt. |
| In-the-loop (itl) | Allowed, but a prompt pops up; you must approve or deny. |
| Deny / no match | Blocked. The agent gets a message saying so. |
forbid always beats permit: if a tool matches both, it stays forbidden.
This lets you keep a “defense in depth” hard-block even while a broad permit
exists.
Watch policy filter tool calls
Section titled “Watch policy filter tool calls”With the plugin installed and policy in place, the agent can only reach what you’ve permitted.
Via MCP. Ask the agent to work with Datadog monitors:
List all the monitors for the
svc-consoleservice.
Because the policy permits the monitor-related MCP tools (e.g. the search-monitor and list-monitor tools), the agent can query them and return results.
Via CLI. The same data can come from a CLI tool instead of MCP. Ask:
Use the Datadog
pupCLI to list the monitors forsvc-console.
Here you’ll typically see a mix of results: the agent may try several commands,
some of which aren’t in the allow-list and get blocked (e.g. a which
probe), while the specifically permitted command (pup monitors) succeeds. That
mix is expected: default-deny blocks everything you didn’t explicitly allow,
and you can widen the policy later if needed.
See a block in action
Section titled “See a block in action”Now try something the policy forbids. Suppose there’s a throwaway “spurious demo dashboard” in Datadog:
Delete the spurious demo dashboard from Datadog.
You’ll see it blocked, for two independent reasons:
- Default-deny: dashboard deletion was never permitted, so it’s denied.
- Explicit forbid: the policy also
forbids it outright as defense in depth.
The agent reports back that it can’t delete the dashboard.
Change the policy to allow with a prompt
Section titled “Change the policy to allow with a prompt”Ask the agent to update the policy so that deletes are allowed in-the-loop:
Add dashboard delete to the policy with itl (in-the-loop), so it prompts me before running.
The agent proposes a change that does two things:
- Removes dashboard-delete from the explicit
forbidblock, which is necessary becauseforbidalways wins overpermit. - Adds a new clause permitting the delete but requiring an in-the-loop
prompt (an
@itl("prompt")annotation).
Review the diff and approve it.
Watch the in-the-loop approval
Section titled “Watch the in-the-loop approval”Policy takes effect immediately: the policy file is re-read on every tool call, so your edit applies to the very next one with no restart. Ask again:
Delete the spurious demo dashboard.
This time the tool is permitted, but because it’s in-the-loop, an approval prompt pops up. It shows:
- The exact command that would run.
- A description drawn from the Cedar policy (the
@descriptionannotation), giving you context on why this action matters and what to check before approving.
Approve it, and the delete proceeds. You’ve now gone from hard-blocked to allowed with a human checkpoint, without ever disabling the safety net.
Review the session in the Console
Section titled “Review the session in the Console”Every keycard run session has a session ID (visible in the CLI as a short
identifier).
- Open the Keycard Console.
- Go to Sessions and find your session by its ID.
The session view is a full rundown of what happened: each token exchange, each credential issuance, and each tool call. Entries that succeeded show green; entries that were blocked by policy show red. You get complete insight and explainability into what the agent did and what it was stopped from doing.
- The plugin’s pre-tool-use hook evaluates every tool call against Cedar policy and supersedes Claude’s own permissioning.
- Policy is default-deny with permit / in-the-loop / forbid outcomes;
forbidbeatspermit. - The policy file is re-read on every tool call, so edits apply immediately, and the Console gives you a green/red audit trail per session.