Blog • AI / Agent

Claude Managed Agents 2026: How Does an AI Agent Decide Whether a Tool Call Is Allowed? JSON Permission Policy and MCP Security

On 10 September 2026, Anthropic added a new type to Claude Managed Agents permission policy: auto. The same day, agent.tool_use and agent.mcp_tool_use events started carrying an evaluation field, alongside the existing evaluated_permission.

A model proposing a Tool Call is not the same as that call being executed. The decision sits in server-side policy evaluation: it reads the JSON policy you put on the Agent, then returns allow, deny, or ask for this call’s tool name, input, and session.

This article covers:

Keep this in mind: auto is not a blanket allow, and it is not human approval. When the server judges a call safe, it finishes before you see it. When it judges the call high-risk, the client has no way to override the deny. Below we split the three policies → per-call evaluation → event JSON → MCP → the confirmation flow.

The three policies: always_allow, always_ask, auto

Permission policy only covers tools the server runs for you: built-in agent_toolset_20260401, plus any MCP toolset you attach. Custom tools run in your app and skip this policy. Requests need the beta header managed-agents-2026-04-01. Official docs:

Claude Managed Agents Permission policies.

The policy decides when this call runs, not whether the tool is in the catalog. To take a tool off an Agent, use disable. Do not expect the policy to hide it.

Policy What the server does What the client still does
always_allow Runs immediately, no one waits Read the event; there is no confirm step
always_ask Emits a tool_use event, then stops For each blocked event, send user.tool_confirmation
auto Evaluates this call: run, refuse, or stop and ask you Handle only events that result in ask; a deny cannot be overridden

The defaults differ. The Agent toolset defaults to always_allow. The MCP toolset defaults to always_ask. No toolset turns on auto by default; you write it into the JSON yourself.

A session already running keeps the toolset config from when it was created. After you change the Agent, only new sessions pick up the new policy. Release notes:

Claude Platform release notes (2026-09-10).

What auto actually looks at

Before, a tool was either always allowed or always asked. auto moves the decision from the tool to the call.

The official write-up is specific: evaluation looks at the tool itself, this call’s input, and the session so far. So the same bash with two different command values can come back allow and deny.

  1. 1
    The call is allowed

    The server judges this call safe. The tool runs as it would under always_allow. There is no pause and no human window.

  2. 2
    The call is denied

    The server rates it high-risk. The tool does not run. The Agent gets an error tool result: Permission to use {tool_name} has been denied., with is_error: true. The session continues. Your client cannot change this deny.

  3. 3
    The call stops and waits for you

    The server cannot reach a firm conclusion. The session pauses as it would under always_ask. You then allow or refuse with a confirmation event.

Intent is read only from the user.message you send. Tool results, fetched pages, MCP Server returns, and messages between threads are evaluated as content, not as instructions. Some calls are rated high-risk no matter who asked.

If you paste untrusted end-user text straight into user.message, the server treats it as your intent and may allow a call because of it. Tools on that entry path should get their own always_ask.

Where the policy sits in the JSON

The policy appears in two places: the toolset’s default_config.permission_policy, and a per-tool override inside configs. The config below sets both the Agent toolset and GitHub MCP to auto, then pins bash to always_ask.

tools config when creating an Agent (excerpt from the official docs)
{
  "name": "Ops Agent",
  "model": "claude-opus-5",
  "mcp_servers": [
    {
      "type": "url",
      "name": "github",
      "url": "https://mcp.example.com/github"
    }
  ],
  "tools": [
    {
      "type": "agent_toolset_20260401",
      "default_config": {
        "permission_policy": { "type": "auto" }
      },
      "configs": [
        {
          "name": "bash",
          "permission_policy": { "type": "always_ask" }
        }
      ]
    },
    {
      "type": "mcp_toolset",
      "mcp_server_name": "github",
      "default_config": {
        "permission_policy": { "type": "auto" }
      }
    }
  ]
}

mcp_server_name must match the mcp_servers entry’s name. When you override by tool name on the MCP side, name is the tool name the Server reports, not an alias you invented.

If you omit default_config, the Agent toolset comes up as always_allow. That is a convenience default, not a security default.

The evaluation left on each call

Whatever the policy, agent.tool_use and agent.mcp_tool_use both carry a top-level evaluated_permission, whose value is allow, ask, or deny. Most events also carry an evaluation object, and type names which policy produced the conclusion.

Under auto, the object also records the server’s verdict. On ask or deny it also carries reason_code. Official example: bash under auto; a high-risk call is refused.

An agent.tool_use event refused by auto
{
  "type": "agent.tool_use",
  "id": "sevt_01pqr...",
  "name": "bash",
  "input": {
    "command": "rm -rf /workspace/reports"
  },
  "evaluated_permission": "deny",
  "evaluation": {
    "type": "auto",
    "evaluated_permission": {
      "type": "deny",
      "reason_code": "high_risk"
    }
  },
  "processed_at": "2026-03-25T14:05:12Z"
}
evaluation Top-level result Meaning
{"type":"always_allow"} allow Resolved policy is always_allow; the call already ran
{"type":"always_ask"} ask Resolved policy is always_ask; the session is waiting for you
{"type":"auto","evaluated_permission":{"type":"allow"}} allow auto judged this call safe and already ran it
{"type":"auto","evaluated_permission":{"type":"ask","reason_code":"indeterminate"}} ask auto could not conclude; it stopped to ask you
{"type":"auto","evaluated_permission":{"type":"deny","reason_code":"high_risk"}} deny auto rated it high-risk and already refused it

reason_code is for your client’s branching and audit logs, not copy for humans. Official docs currently name indeterminate and high_risk. The client must tolerate later extra type or reason_code values.

Two cases have no evaluation. The Agent picked a tool that is not enabled on the session: the event only has evaluated_permission: "deny". Older events recorded before the field existed also lack it: allow was read as always_allow, ask as always_ask.

This is the same layer as “JSON is a security boundary,” but the cut is different. That piece is about validating the arguments the model emits. This piece is about the platform writing its own evaluation, which you cannot override. Previous article:

AI Agents and the JSON Security Boundary.

MCP Tool Calls and the default always_ask

MCP moves the tool catalog out of your repo and onto an outside Server. The Server exposes tools over JSON-RPC; each tool carries an inputSchema. The Host feeds the catalog to the model, then turns the model’s call into a tools/call. Spec:

MCP Tools.

Managed Agents stay conservative here: the MCP toolset defaults to always_ask. When a new tool is added to a Server you already connected, it will not silently run in your app. That is the first MCP Security gate the platform can enforce, and it is more reliable than trusting the Server’s own description.

You can still change it. If you trust a GitHub MCP and want fewer interruptions, set that toolset’s default_config.permission_policy to always_allow or auto. You are changing when it runs, not whether Schema still has to be checked.

Layer What it answers What it does not answer
JSON Schema Whether argument fields, types, required keys, and enums are right Whether this call should happen
Tool Calling Which tool the model picked, and what arguments it filled Whether the Host will execute
permission policy allow / ask / deny for this call Whether the payload matches the schema, or whether the description steered the model

Stacked together they look like a door. Turn on auto alone while the Schema is still {"sql":{"type":"string"}}, and the evaluator sees an input surface too wide to judge. More calls fall into indeterminate, which means ask. Tightening the schema helps auto write a conclusion you can log. Who owns which layer:

MCP Security: Why Schema, Tool Calling, and Permissions Cannot Replace Each Other.

The JSON confirmation flow after ask

A call rated ask can only come from always_ask, or from auto with indeterminate. The event order after that is fixed.

  1. 1
    First, a tool_use event

    The session emits agent.tool_use or agent.mcp_tool_use, already carrying evaluated_permission: "ask".

  2. 2
    Then idle

    The session pauses. session.status_idle has stop_reason.type set to requires_action. The blocked event ids sit in stop_reason.event_ids. It will keep waiting.

  3. 3
    You send a confirmation event

    For each blocked id, send user.tool_confirmation. Set tool_use_id to that event id, and result to allow or deny. On refuse you can explain with deny_message. One request can carry several confirmations.

  4. 4
    The session returns to running

    After every block is cleared, allowed tools run. Refused tools do not, and the Agent gets a tool result that includes your deny_message.

Allow or refuse a waiting call
{
  "events": [
    {
      "type": "user.tool_confirmation",
      "tool_use_id": "sevt_01ask...",
      "result": "allow"
    },
    {
      "type": "user.tool_confirmation",
      "tool_use_id": "sevt_01mcp...",
      "result": "deny",
      "deny_message": "Do not push to main from this session."
    }
  ]
}

If the event’s evaluated_permission is not ask, a confirmation returns 400. That includes calls auto already denied: the client has no appeal path. For interactive handling you can use ant beta:sessions connect and allow or refuse waiting calls from the terminal.

Building your own approval queue, writing deny back to the Agent, and landing a replayable policy in the Host are left to a later implementation piece. This article only makes the JSON the platform already evaluated for you clear.

auto is not a checkpoint

The official docs have a sentence you should not skip: auto is not a human checkpoint. When the server judges a call safe, it runs before anyone sees it, and the effect may be irreversible. Tools that a person must see get always_ask.

Custom tools are also outside this door. agent.custom_tool_use has neither evaluated_permission nor evaluation. Your app receives the event, decides to run or refuse, then replies with user.custom_tool_result. The platform will not evaluate this hop for you.

So “how an AI Agent decides whether a Tool Call is allowed” is a narrow claim: it only covers Agent tools and MCP tools the server runs for you, and only after you explicitly set the policy to auto. Everything else is always allowed, always asked, or not the platform’s job at all.

Check the policy and the events in JSONNote

Policy JSON, tool_use events, and confirmation requests look alike. Their fields are not the same layer. Do not debug them by eye.

  1. 1
    Format the policy first

    Drop the Agent’s tools config into JSON Formatter. Confirm that default_config and the configs entries for permission_policy.type are not mistyped or eaten by an override.

  2. 2
    Then validate the event shape

    Use JSON Schema to write a minimal schema for evaluation. Let type enumerate always_allow / always_ask / auto; under auto require a nested evaluated_permission. Unknown reason_code values should be tolerated via additionalProperties; do not reject them outright.

  3. 3
    Diff two calls

    When the same tool is allowed once and denied once, use JSON Diff to align the two input values and the two evaluation objects. The difference is almost always in command, path, or the step that just happened in the session — not the tool name.

  4. 4
    Keep a shareable recap

    The policy and redacted events can go to a colleague via Hash share. The data never hits a server. Do not put keys or production tokens in it.

FAQ

Will auto allow every Tool Call?

No. auto is per-call: two calls to the same tool can differ if the input or the session differs. A high_risk verdict is deny, and the client cannot override it with user.tool_confirmation.

Does a valid JSON Schema mean the call may run?

No. Schema only answers whether the arguments look right. permission policy answers whether this call may run. A well-shaped bash command can still come back as deny under auto.

Which policy do MCP Tool Calls use by default?

The MCP toolset defaults to always_ask. A new tool added to a Server will not run in your app on its own. Trust a Server first, then change default_config.permission_policy to always_allow or auto.

Do custom tools go through permission policy?

No. Custom tools run in your app. The event is agent.custom_tool_use, with no evaluated_permission and no evaluation. You decide whether to run it, then reply with user.custom_tool_result.

Can auto replace human approval?

No. Official docs say auto is not a human checkpoint. When the server judges a call safe, it runs before anyone sees it, and the side effects may be irreversible. Tools that must be seen by a person get always_ask on their own.

Summary

The auto added on 10 September turns “may this tool be used” into “may this call run.” The judgment uses the JSON policy you put on the Agent, plus this call’s tool, input, and session. The result is written back on the event as evaluated_permission and evaluation.

The model proposes the call. The policy engine allows, refuses, or stops to ask a person. MCP asks first by default. Custom tools are yours. The client cannot override a deny.

Read the policy JSON and the event JSON as two contracts. Do not mistake Schema-valid for authorized. Tools a person must watch stay on always_ask. Tools that need fewer interruptions and still leave audit fields can turn on auto.

← Back to blog