← All posts

Agent Tool Authorization: Discovery Is Not Permission


Design agent tool authorization around each effect, not a cached tool list. Bound delegated scopes, enforce revocation, and recheck long-running sessions.

Agent tool authorization cannot stop at deciding which tools appear in the model’s context. A worker can retain an old definition, resume a queued operation, or invoke a tool by name after its authority changes.

The takeaway: discovery describes available capabilities; execution must authorize the concrete action against current authority. Bind that decision to the caller, resource, operation, arguments, and effect boundary. A tool list is not a durable permission grant.

This is a design guide, not a security benchmark. The examples and acceptance cases below are proposed engineering checks, not results from an experiment.

Separate transport authorization from tool permission

A valid access token answers part of the question: may this client access this protected service under this grant? It does not automatically answer whether a particular invoice can be refunded, a particular repository can be edited, or a particular recipient can receive exported records.

The MCP authorization specification dated 2025-06-18 defines authorization for HTTP-based transports. It requires authorization on every HTTP request, including requests within the same logical session, and requires servers to validate that access tokens were issued for their intended audience. It distinguishes this flow from STDIO, where implementations should obtain credentials from the environment instead.

Those are protocol requirements for that version, not a complete application policy. In either transport, a process possessing a credential still needs a policy governing what it can do with it. Environment credentials do not make local tools harmless.

Consider an illustrative invoice tool. The caller has access to the billing service and a refund-related scope. The application must still resolve the invoice’s tenant, determine whether the caller may act on that invoice, enforce any amount limit, and check whether the intended recipient or payment route is allowed. A caller-supplied tenant identifier is a claim to validate, not the authority for the lookup.

Keep the layers explicit:

Layer Question Enforcement location
Service access Is this credential valid for this service and request? Protected service boundary
Tool operation May this principal request this operation? Tool dispatcher or service policy
Resource access May this principal act on this tenant and object? Trusted resource lookup and authorization
Effect constraints Are the amount, destination, and other limits permitted? Canonical action validation
Current validity Is this authority still usable at the commitment boundary? Execution or resource boundary

A successful check in one row is not evidence that all the others passed.

Filter discovery, then authorize execution independently

Filtering discovery is useful. It keeps irrelevant operations out of context and can avoid revealing privileged tool descriptions. But the executable dispatcher must reject unauthorized calls even when the model already knows their names.

Treat discovery output as an interface snapshot. Cache it under the correct principal and tenant, and invalidate or refresh it when appropriate. Do not let a cache entry for an administrator become the tool catalog for a restricted worker. Conversely, do not rely on cache invalidation as the only barrier to a forbidden action.

At invocation, authenticate the request, resolve the requested executable contract, validate and canonicalize arguments, resolve resources through trusted state, and evaluate the applicable policy. Only then dispatch the authorized action.

Canonicalization matters. If policy checks an approved directory but a later adapter resolves a different filesystem path, the check and the effect concern different resources. If the dispatcher approves a recipient alias and execution expands it into a distribution list, the expanded destination needs to remain within the grant. Reject ambiguity or bind the decision to the resolved target.

Likewise, approving a tool name is insufficient when its implementation changes. A harmless preview operation must not become a mutating operation under an unchanged cached authorization. Bind the decision to a meaningful contract identity; agent tool schema versioning explains why compatible JSON does not guarantee compatible effects.

Delegated scopes must narrow actual authority

Delegation should transfer enough authority for a child to perform its task, not the parent’s entire credential collection. A research worker that needs one project’s documents does not need the parent’s ability to export every tenant’s records.

A useful design rule is that the child can exercise only the intersection of the parent’s delegable authority, the child’s identity constraints, the task grant, and current service policy. This is a design invariant, not a claim that every OAuth deployment supplies a standard field for each restriction.

Represent the restrictions where the executor can enforce them. Depending on the system, that might mean a narrowly scoped credential, a capability checked by a gateway, or a server-side grant referenced by the request. Writing “read-only” into a child prompt does not constrain a broadly privileged backend credential.

Record the delegator, subject, allowed actions, resource boundary, expiry, and whether further delegation is permitted. For consumable limits such as a total spending budget, reserve or decrement capacity atomically: two children must not independently spend the same remaining allowance. Merely copying the parent’s limit into each child multiplies authority.

Do not treat human approval as an unbounded exception. Bind an approval to the actual action and material inputs. If the recipient, amount, tenant, or tool contract changes, determine whether the approval still covers the revised action. A decision to approve one export is not permission for all future exports in the conversation.

Preserve authority across downstream API boundaries

An MCP server may act as a client to another API. These are separate trust relationships. The token presented to the MCP server is not automatically a valid credential for the downstream service, and the downstream service’s broad service-account permissions are not automatically the requesting user’s permissions.

The MCP security best practices describe token passthrough and confused-deputy risks. They prohibit accepting tokens not issued for the MCP server and describe per-client consent protections for proxy authorization flows. The relevant principle is to preserve who authorized which client to use which resource, rather than treating successful upstream authentication as universal consent.

Keep downstream credentials in the trusted execution layer. Use credentials legitimately issued for that downstream relationship, and enforce the originating caller’s restrictions before using them. Where the downstream API supports a narrower delegated identity, use it; where it does not, the proxy owns the responsibility for restricting its broader account.

A document or tool response that says “use the admin connection” cannot change that authority. Untrusted content may suggest an action; it cannot select a more privileged principal or establish user consent. The identity and grant must come from trusted request context.

Make revocation semantics explicit

Revocation is not a property you obtain merely by deleting a tool from discovery. Existing workers may have cached schemas, queued work, valid tokens, or already accepted requests.

Define what revocation means for each state. Before dispatch, the executor can reject the operation under current policy. After a remote service has accepted an effect, local revocation cannot promise to undo it. Between those points, your guarantee depends on where the authoritative check occurs and how it is ordered with the commitment.

A policy check followed by an unrelated write leaves a race. If immediate rejection after a revocation boundary is required, the component that commits the effect must enforce a compatible ordering: for example, checking the current grant revision in the same transaction as a local mutation. A policy revision included in a request helps only if the receiving component compares and enforces it.

Across an external API boundary, that atomic mechanism may not exist. State the weaker guarantee honestly: the gateway blocks newly admitted dispatches after its revocation cutoff, while previously admitted requests may still complete. Track and reconcile them. Agent cancellation covers why stopping local execution and proving the absence of an external effect are different operations.

Short token lifetimes can limit how long some stolen or stale credentials remain usable, but expiry is not immediate revocation. A service validating a self-contained token without consulting changing authority may accept it until expiry. If your product promises a shorter revocation window, provide and test the necessary online check, invalidation, or resource-side enforcement. Do not claim a window that exists only in the interface.

Reauthorize long-running sessions and retries

A long-running session is continuity of work, not continuity of permission. A user may change teams, revoke an integration, or narrow a grant while an agent waits for a review. A refresh token does not freeze the application’s resource policy at the moment the task began.

Store resumable intent separately from credentials. When a worker resumes, obtain valid credentials through the trusted credential mechanism, resolve current resource state, and authorize the next action. Preserve the original scope of the task; refreshing access must not silently widen the requested work.

Do the same for retries. An idempotency key identifies a logical effect; it is not an access token. Returning a stored result may itself disclose protected information, so result retrieval requires appropriate authorization too. If the original effect’s outcome is unknown, reconcile it through an authorized status path instead of acquiring broader privileges and trying again.

Distinguish denial from policy-service unavailability. Neither establishes permission, but they need different operational responses. For protected mutations, an unavailable authorization dependency should leave the action undispatched or explicitly unresolved under your declared policy, not fall back to a cached administrator grant.

Log decision identifiers, principal and grant references, resolved resource identifiers, policy and contract versions, and the resulting effect reference. Keep bearer tokens and refresh tokens out of prompts and traces. Logs should explain why an action was admitted without becoming a second credential store.

Test the authority boundary, not just the error message

Turn these rules into proposed acceptance cases before calling a tool layer production-ready:

  • Discover a tool while authorized, revoke access, then invoke its cached name. Verify rejection and no new effect.
  • Substitute a resource from another tenant while retaining a valid service token. Verify object-level denial and no data disclosure.
  • Delegate a read-only task to a child with access to a broadly capable dispatcher. Verify that a direct mutation request still fails.
  • Change a material argument after human approval. Verify that the prior approval cannot authorize the changed action unless its recorded scope explicitly covers it.
  • Resume a queued operation after grant expiry or revocation. Verify fresh authorization before any dispatch.
  • Race revocation against effect commitment. Verify the declared ordering and separately account for requests admitted before the cutoff.
  • Attempt to retrieve an idempotent result as another principal. Verify that replay does not leak the original result.
  • Make the policy dependency unavailable. Verify the documented unavailable state and absence of unauthorized dispatch.

Use benign allowed cases alongside the denials so a system that refuses everything cannot pass. Inspect resource state, downstream dispatch records, and protected output, not just the assistant’s refusal sentence. These fixtures belong in agent regression gates with explicit expected outcomes.

The useful security claim is narrow and testable: this principal, under this grant and policy, could cause only this permitted effect at this boundary. A remembered tool name should never be enough.