---
title: Control Tool Calls | Keycard
description: 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

- The Keycard CLI and [Claude Code](https://claude.com/claude-code) installed
- A project with a `keycard.toml` and connected MCP servers, running inside `keycard run claude`. Set this up in [Connect Claude to Resources](/guides/connect-claude-to-resources/index.md)

## 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.

Terminal window

```
claude plugin marketplace add keycardai/plugins
claude plugin install keycard-cli@keycardai
```

You can also ask Claude Code to install the plugin into the project for you.

This supersedes Claude's own permissioning

When a pre-tool-use hook returns a decision (permit / deny / prompt), that decision is final. Claude’s internal permission system is never consulted, so flags like “bypass permissions” become irrelevant. Nothing stops someone from passing them, but they have no effect, because policy makes the decision and Claude simply accepts it. It does not fall back to its own checks.

### How policy decisions work

Policies are written in [Cedar](https://www.cedarpolicy.com/) 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

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-console` service.

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 `pup` CLI to list the monitors for `svc-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

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:

1. **Default-deny**: dashboard deletion was never permitted, so it’s denied.
2. **Explicit forbid**: the policy also `forbid`s 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

Note

Organization policy enforcement is coming soon. Orgs will be able to enforce a default policy that users can only **narrow**, not expand, so an individual or a specific agent can be made *more* restricted, never less.

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:

1. **Removes** dashboard-delete from the explicit `forbid` block, which is necessary because `forbid` always wins over `permit`.
2. **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

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 `@description` annotation), 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

Every `keycard run` session has a session ID (visible in the CLI as a short identifier).

1. Open the [Keycard Console](https://console.keycard.ai/).
2. 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.

## Recap

- **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; `forbid` beats `permit`.
- **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.
