Securing the New Attack Surface: Authorization for Tool-Calling Agents and MCP

Emmanuel Gautier Emmanuel Gautier ·
Tool Security

Part of the series The Authentication and Authorization Challenges of Agentic AI.

Handing an agent a tool feels like a small decision — a function definition, a couple of parameters, a description string the model reads to decide when to call it. From an authorization standpoint, it isn’t small at all: you’ve just stood up a new API client, with everything that implies about who’s allowed to call it, what it’s allowed to do, and how you’d know if it was being misused. A striking number of early agent deployments skip that step entirely, on the theory that a tool call is somehow different from an API call. It isn’t. This is the tool-surface gap.

”It’s just a tool call” is the whole problem

The Model Context Protocol (MCP) and similar tool-calling standards made it dramatically easier to connect agents to external data sources and services — that’s the point of them, and it’s genuinely useful. But “easier to connect” and “properly authorized” are different properties, and the gap between them is where the real exposure sits. An MCP server, or any equivalent tool endpoint, that has no authentication in front of it is reachable by any agent that discovers it — no identity check, no permission check, no record of who called it or why. And the actions these tools perform are rarely read-only: agents built on top of them execute transactions, modify records, and trigger workflows, which means an unauthenticated tool endpoint isn’t a curiosity, it’s an open door with real consequences on the other side.

The instinct to treat this as lower-stakes than a “real” API is understandable but backwards. A tool an agent can call autonomously, based on its own reasoning about when to invoke it, deserves more authorization rigor than a human-operated API client, not less — there’s no person in the loop double-checking that the call makes sense before it fires.

The protocol layer: OAuth 2.1 plus discovery and scoping extensions

Modern tool-calling protocols converge on OAuth 2.1 as the baseline — which, notably, mandates PKCE for every authorization code flow and drops the implicit grant entirely, closing off some of the weaker patterns that lingered in OAuth 2.0 deployments for years. On top of that baseline, a handful of extensions solve the specific discovery and scoping problems that come up once you have many tool servers and many agent clients instead of one app and one API:

Authorization Server Metadata (RFC 8414) and its resource-server counterpart, Protected Resource Metadata (RFC 9728), let a tool server publish, at a well-known endpoint, which authorization server protects it and what it expects from incoming tokens — so an agent client can discover the right place to request access without hardcoded, manually maintained configuration per tool.

Dynamic Client Registration (RFC 7591), and lighter-weight alternatives built on the same idea, let an agent client register itself with an authorization server programmatically rather than requiring a human to manually provision credentials for every new agent instance ahead of time. This matters specifically because agent fleets tend to grow faster than manual client-provisioning workflows can keep up with — if registering a new agent instance requires a ticket to a platform team, either the process becomes a bottleneck or, more likely, teams start reusing one shared client registration across agents they shouldn’t be sharing it across.

Resource Indicators (RFC 8707) let a client explicitly declare, at request time, which specific resource server a token is intended for — replacing the ambiguity of a generic audience with an explicit, per-request declaration. Several tool-calling protocols lean on resource identifiers in exactly this way, so a token minted for one tool server can’t be quietly reused against a different one it was never meant to reach.

None of these are exotic; they’re the same discovery, registration, and audience-scoping machinery that OAuth deployments have needed for years, applied to a world where the number of “resource servers” (tools) and “clients” (agents) is an order of magnitude larger and far more dynamic than a typical web application ever had to handle.

Diagram of the tool-surface gap: the agent node and the tool/internal API node from the agentic AI trust model, connected by an arrow highlighting gap 5, tool surface

Authentication is not the same question as authorization

A subtlety worth stating explicitly, because it’s easy to consider the problem solved once a tool server checks for a valid token: proving which agent is calling (authentication) is a different question from proving that this specific call is permitted (authorization), and a tool endpoint needs both. A validly authenticated agent presenting a legitimate token is still not automatically entitled to invoke every action the tool server exposes — the same least-privilege scoping discussed throughout this series applies at the level of individual tool actions, not just at the level of “is this a recognized client.”

This is also where the on-behalf-of pattern from earlier in this series reappears: if a tool server, having authenticated the calling agent, needs to turn around and call a further internal API on the original user’s behalf, that’s a fresh delegation hop, and it deserves a fresh, narrowly scoped token exchange rather than simply forwarding whatever credential the agent presented. Tool servers that skip this step and pass through the agent’s own token to everything downstream reintroduce the token pass-through anti-pattern one layer removed from where it’s usually discussed.

Design guidance

Treat every tool and every tool-calling server as a production API, with no informal exceptions. “It’s internal only” or “it’s just a wrapper around a read-only endpoint” are the two justifications that most often precede an incident. If a system can be called by an autonomous agent, it needs the same authentication and authorization rigor as any system a human might call directly — arguably more, given the absence of a human sanity-checking each call.

Use dynamic registration or an equivalent lightweight onboarding mechanism, not manual credential provisioning, once you have more than a handful of agent instances. The moment registering a new agent requires a person to manually create and hand off a client secret, you’ve built a process that will eventually get shortcut — usually by sharing credentials across agents that should each have their own.

Scope tokens explicitly to the specific tool or resource server being called, not a broad audience that happens to cover it. Resource indicators exist precisely so a token minted for one tool can’t be replayed against a different one. If your tokens don’t carry this kind of explicit binding today, that’s a gap worth closing before it’s found by someone other than you.

Don’t conflate “this agent is who it claims to be” with “this agent is allowed to do this.” Authentication answers the first question. It says nothing about the second. Every tool action that matters needs its own authorization check, informed by the agent’s granted scope for that specific task — not just a pass/fail on whether the token was valid.

When a tool server itself needs to reach further systems, re-delegate rather than forward. A tool server sitting between an agent and an internal API is itself now a link in a delegation chain, and it should behave like one: exchange for a freshly scoped token when calling onward, rather than acting as a transparent pipe for whatever token it received.

Handing an agent a tool is handing it the ability to act — treating the connection casually because it’s framed as “just a tool call” is how the tool-surface gap turns into the incident that makes the next quarter’s security review considerably less pleasant.

Next in the series: Least Privilege at the Data Layer: Relationship-Based Authorization for RAG, which covers the question tool-level scopes can’t answer on their own: not just can this agent call this API, but can it retrieve this specific record, for this specific user, right now.