Claude Certification Guides
CCAR-F/Domain 29 min read

Lesson 2.5

Tool Permissions, Error Handling and Idempotency

How to make tool execution safe when the caller is a language model: permission layers, error surfaces, retries and operations that can be repeated without harm.

When a model calls a tool, three things can go wrong: it calls a tool it should not, the tool fails, or the tool succeeds twice when it should have run once. Each failure has a distinct mitigation. Permissions address the first, error handling the second, idempotency the third. The exam presents scenarios and expects you to pick the mitigation that matches the failure.

Permissions: deciding what may run

Permissions operate at several layers and the architect chooses where each rule lives.

At the tool surface. Do not expose a tool the agent never needs. A reporting agent gets query_readonly, not execute_sql. Narrow tools make permission rules legible.

In the host. Claude Code and the Agent SDK evaluate allow, ask and deny rules before any tool runs, with deny taking precedence. Rules match tool names and, for some tools, argument patterns such as command prefixes or file paths. MCP tools are addressed as mcp__<server>__<tool>, or mcp__<server> for every tool on a server.

In hooks. A PreToolUse hook can inspect the full arguments and deny, allow or escalate to a human with a reason. This is where context-dependent policy lives ("deny writes outside the current ticket's directory").

Inside the tool. The implementation itself validates arguments, enforces an allowlist of permitted operations and uses credentials with least privilege. This layer holds even if the host is misconfigured. For a shell tool, an allowlist of executables and rejection of shell operators (;, &&, |, backticks) is the baseline; a blocklist of dangerous commands is not sufficient.

Least privilege applies to credentials as much as to tools: a server that only reads should hold a read-only token, so that a mistaken or injected write is impossible rather than merely discouraged.

Tool results are untrusted input

Anything a tool returns, especially from the web, email, documents or other users, may contain text that looks like instructions. The model should treat it as data. Mitigations: wrap results in clear delimiters, state in the system prompt that tool output never carries authority, keep high-risk actions behind human approval, and never let a tool result alone trigger an irreversible action. Hosts help by requiring explicit permission for new MCP servers and by keeping project-scoped .mcp.json servers disabled until approved.

Error handling: making failure legible

A failed tool should produce a tool_result with is_error: true and a message that helps the model recover: what failed, why, and what a valid retry would look like.

try:
    out = run_tool(block.name, block.input)
    result = {"type": "tool_result", "tool_use_id": block.id, "content": out}
except ValidationError as e:
    result = {"type": "tool_result", "tool_use_id": block.id,
              "content": f"Invalid input: {e}. Expected {SCHEMA_HINT}", "is_error": True}
except TransientError as e:
    result = {"type": "tool_result", "tool_use_id": block.id,
              "content": f"Temporary failure: {e}. Safe to retry.", "is_error": True}

Distinguish error classes in the message. A validation error invites the model to fix its arguments; a transient error invites a retry; a permission error invites a different approach or escalation. Raising an exception out of the loop instead of returning an error result abandons the conversation and discards the model's ability to adapt.

Retries belong in your code for transient infrastructure failures (a database timeout) and in the model's hands for semantic failures (wrong arguments). Do not let the model retry an expensive or side-effecting operation indefinitely; count attempts and return a final error after a limit.

Idempotency: surviving repeats

An operation is idempotent if performing it twice has the same effect as performing it once. Agents repeat operations for ordinary reasons: a network retry, a resumed session replaying a tool_use whose result was lost, or the model simply calling again because the first result looked ambiguous. Tools with side effects must therefore be designed for repeats:

  • Natural idempotency. "Set status to shipped" is idempotent; "increment counter" is not. Prefer absolute operations over relative ones in tool design.
  • Idempotency keys. Accept or generate a request key (the tool_use_id is a convenient one) and have the backend ignore duplicates with the same key.
  • Check-then-act. Query current state before mutating, and make the tool a no-op if the desired state already holds.
  • Read-only by default. Separate reads from writes so that the common, repeatable case is harmless.

MCP tool annotations (readOnlyHint, destructiveHint, idempotentHint) let a server declare these properties so that hosts can relax or tighten approval prompts accordingly; they are hints, not enforcement.

Putting the three together

A payment tool illustrates all three. Permission: it exists only in the checkout agent, is behind an ask rule, and uses a token scoped to a single merchant account. Error handling: declined cards return is_error: true with the decline reason so the model can tell the user, while gateway timeouts return a "safe to retry" message. Idempotency: every charge carries a key derived from the order ID, so a retry after a timeout cannot double-charge.

Exam focus

  • deny rules override allow; MCP tools are permissioned as mcp__server__tool or mcp__server for a whole server.
  • Shell tools need an executable allowlist and rejection of shell operators; a blocklist is insufficient.
  • Tool results are untrusted data and must never on their own authorise irreversible actions.
  • Failed tools return tool_result with is_error: true and a recovery hint; exceptions should not escape the loop.
  • Idempotency protects against retries and replays: prefer absolute operations, idempotency keys and check-then-act.
  • MCP annotations such as readOnlyHint and destructiveHint inform host permission prompts but do not enforce anything.

Check yourself

5 questions

  1. 1.Why should a payment tool used by an agent be idempotent, or accept an idempotency key?

  2. 2.MCP tools may carry annotations such as readOnlyHint, destructiveHint and idempotentHint. How should a host application treat them?

  3. 3.A text-editor tool implementation receives a path argument of ../../etc/passwd from the model. What is the appropriate defence?

  4. 4.A Claude Code team connects a GitHub MCP server. They want its read tools (get_*, list_*) to run without prompting while write tools still ask for approval. Which configuration achieves this?

  5. 5.Which principle applies to credentials used by an agent's tools?