All articles

Agent access

MCP authentication, authorization, and human approval explained

Tier Two11 min read

Suppose Dana asks an agent to file a bug in the Billing project in Linear. The host connected fine this morning. The token is valid, the tool is in the list, an admin looks at the request and clicks approve. Linear refuses the write anyway, and no issue exists.

That can be the correct outcome, and it's a confusing one, because four different checks all look like 'permission' from the outside. Each answers a narrower question than people assume, each fails with a different signal, and fixing the wrong one is how teams end up handing out broader scopes than they meant to. This article follows Dana's single call through all four, with the status codes and spec language you'd see at each step.

The short answer

Authentication settles who is calling and finishes before any tool runs. Authorization is what the token's scopes and the destination's own permissions let that identity reach, enforced with 401 and 403. Tool policy decides whether a given tool runs, waits, or stays hidden. Human approval releases one held call. None of the four stands in for another.

Four decisions that look like one permission check

Every MCP host that talks to a protected server passes through the same sequence, whether or not anyone designed it that way. The order matters, because a failure early in the chain hides the later checks entirely: an agent that can't get a token never learns which tools it would have been offered.

QuestionWho answers itHow it failsWhat the agent sees
Who is calling?The OAuth authorization server, during sign-in401 with a WWW-Authenticate challengeA sign-in prompt, or nothing at all
What may this token reach?The MCP server (scopes) and the destination system (its own access rules)403 with insufficient_scope, or an error from the destination after the callA refused call with a reason that names the credential, not the tool
Should this tool run at all, for anyone?Tool policy: rules in the host, or a shared control pointThe tool is refused, or never appears in tools/listA blocked result, or a shorter tool list
Should this one call run now?A person, reviewing the actual argumentsDenied, or nobody answered in timeA pending result, then a decision

Dana's failure lives in the second row. Everything above it passed, the approval in the fourth row passed, and the destination still said no. To see why that's consistent rather than a bug, walk the rows in order.

Authentication: what the spec actually pins down

MCP's authorization specification makes a protected MCP server an OAuth 2.1 resource server. The client never invents an identity; it obtains an access token from an authorization server the MCP server points to, and presents that bearer token on every HTTP request. Discovery is standardized. A server publishes Protected Resource Metadata (RFC 9728), either through a WWW-Authenticate header on its 401 or at a well-known path, and the metadata names the authorization server, whose own metadata (RFC 8414 or OpenID Connect discovery) tells the client where to register and where to send the user.

Two client requirements shape what 'who is calling' means. PKCE is mandatory, with S256 when the server can do it, so a stolen authorization code is useless on its own. And the client must send a resource parameter (RFC 8707) naming the MCP server it wants the token for, which stops a token minted for one server from being replayed against another. Registration is the part that has moved most between revisions: the 2025-06-18 revision said clients and servers should support dynamic client registration, the 2025-11-25 revision made it optional and introduced client ID metadata documents, and the 2026-07-28 revision marks dynamic registration deprecated. Hosts in the field still register dynamically today, so an endpoint has to speak both for a while.

For Tier Two's hosted endpoint at trytiertwo.com/mcp, this layer is Clerk. The endpoint publishes its RFC 9728 metadata, the host registers itself and runs the PKCE flow, and the person signs in with their normal workspace account. The scopes on that token are openid, profile and email; they identify the person and the agent, and that is all they do. What the agent may touch is decided later, by Tier Two's own policy, not by OAuth scopes. So after this step, the system knows two things about Dana's request: it comes from Dana, and it comes from a specific agent acting as Dana. Those two facts follow the call through every later decision and into the activity record.

Authorization: two layers, and neither one is about tools

Authorization in the OAuth sense answers 'what may this token reach', and it happens twice. First at the MCP server: an expired or malformed token gets 401, a valid token without the needed scope gets 403. Since the 2025-11-25 revision a server can also answer 403 with a WWW-Authenticate header carrying error="insufficient_scope" and the scope it wanted, and the client can go back for a bigger token. That step-up flow is a request for more delegated access. It does not involve anyone looking at the tool call that triggered it.

Second, at the destination. When the MCP server calls Linear, GitHub or a database on the agent's behalf, that system applies its own rules with its own credential. Here the spec draws a hard line that explains Dana's outcome: an MCP server must not pass through the token it received from the client to an upstream API. The credential used downstream has to be the server's own, or one the person delegated separately to that server. In Tier Two a connection holds that downstream credential in one of three shapes: the person signs in to the system through the connection, so calls run as them; a shared workspace key that Tier Two decrypts only for the call that needs it; or a personal API key. Dana's Linear permissions therefore come from Dana's own Linear login, and no amount of Tier Two approval changes what that login can do.

The point people miss is duration. OAuth consent happens once, when the token is issued, and the token then covers every request within its scopes for as long as it lives. The spec does not ask for another user interaction on each tools/call. That's the right design for a transport credential and the wrong tool for deciding whether this particular write should happen now, which is why the next two layers exist.

Tool policy: the decision the protocol leaves to you

The tools page of the spec is explicit that tools are model-controlled and that there should always be a human able to deny an invocation, with confirmation prompts for sensitive operations and tool inputs shown to the user before the call. Every one of those is a SHOULD aimed at the client, and the spec names no enforcement location. It also warns that tool annotations such as readOnlyHint are untrusted unless the server is trusted, so 'the tool says it's read-only' is a claim, not a control.

That leaves two places to put the decision. Inside the host: Claude Code, for example, lets you write allow, ask and deny rules against MCP tools by name, in the form mcp__linear__save_issue, and Cursor asks before every MCP tool call by default. Those controls are real and worth using; they also live on one machine, in one host, under the control of whoever edits that host's settings. Or at a shared point in the call path, where one rule applies to every agent in a workspace. Tier Two is the second kind. Each tool on a connection carries exactly one mode: Allow runs the call and records it, Ask holds it for a person, Block refuses it and removes it from tools/list so the model never sees it. A tool with no mode is not offered and not allowed, so an unknown tool is a no by default.

Policy can look at one argument without becoming an argument filter. A tool may declare a scope argument, say the project id on a Linear write, and the mode can differ per project; a call whose declared scope can't be resolved is refused rather than guessed. There is deliberately no general allow-list of arguments. If certain inputs would make a tool dangerous and a resource argument can't separate them, the honest modes are Ask or Block. The permissions article walks through choosing these modes tool by tool.

Human approval: one call, one yes

An Ask does something narrower than any layer above it: it holds one concrete invocation and asks a person whether that invocation, with those arguments, should go. Tier Two records the person, the agent, the connection, the tool and a truncated preview of the arguments (top-level keys, strings cut at 80 characters, the whole preview capped at 2 KB, and the raw arguments never stored). A workspace admin sees who asked, from which agent and host, which tool, and the preview, then chooses: approve once, approve for one hour, eight hours or seven days, always for that person, or deny.

The mechanics are what make approval trustworthy. On approve, the original held call is forwarded and its result returns to the agent. There is no retry, so there is no second invocation whose arguments escaped review. The call waits in place for up to nine minutes while the host shows a pending state; the request itself lives fifteen minutes, and if nobody decides, the call is refused with 'no one answered in time' and nothing is forwarded. On deny, the agent receives a message telling it not to retry and not to reach the same goal through other tools. Identical pending requests from the same person fold into one, so a looping agent can't flood the queue. The approval article covers the full state machine, including revocation and what each host displays while waiting.

What approval does not do is the crux of Dana's case. A yes releases the call; it adds no OAuth scope and grants nothing at the destination. An approver is judging whether the action should happen, on the assumption that the credential behind it is the right one. If that credential can't do the thing, the destination's refusal is the correct answer, and it arrives after the approval, not instead of it.

Why Dana's approved call still failed, and what to change

Trace it. Dana's host obtained a token through the endpoint's discovery flow, so authentication passed. The Linear connection in her workspace uses her own Linear sign-in, and that credential is valid, so the token layer passed too. The workspace's policy puts Linear's write tools on Ask, the call to save_issue with the title 'Fix duplicate invoice emails' was held, and an admin approved it. Tier Two forwarded the held call with Dana's Linear credential. Linear evaluated the write against what Dana's account may do in the Billing project, found she has no write access there, and refused.

Another approval won't help, and neither will changing the tool to Allow; both act on the third and fourth rows of the table while the failure sits in the second. The fix is in Linear: either give Dana's account write access to Billing, or decide she shouldn't have it and let the refusal stand. The mirror case is just as common. If Dana did have write access, the policy would still hold the call on Ask, because 'can' and 'should right now' are different questions, and a shared policy is how a team answers the second one without editing every developer's settings.

SymptomLayer to look atTypical cause
Every call answers 401AuthenticationExpired or revoked token, or a token minted for a different resource
403 with insufficient_scopeToken scopesThe delegated scopes don't cover the operation; a step-up request is the fix
The tool is missing from tools/listTool policy or the connectionBlock, no mode set, a disabled connection, or a read-only toolset upstream
A pending result, then 'the human denied this action'Human approvalAn admin said no; the agent has been told to stop
Approved, then the destination refusesDestination permissionsThe person's own account lacks access there; approval never adds it
'No one answered in time'Human approvalNo admin was available within the request's lifetime

Review one sensitive tool in four questions

Pick a tool that creates, updates or deletes something your team would notice, and answer these in order. The exercise takes ten minutes per tool and usually ends with fewer Allows than you started with.

  • Whose identity reaches this call, and through which agent? If the answer is 'a shared bot account', you've lost the first row of the table before you started.
  • Which credential runs the downstream call, and what can it reach on its own? A personal sign-in inherits that person's limits; a shared key makes the tool's mode the only limit.
  • Should the tool run at all, for anyone, and does the answer change by project, repository or database? That's the mode, plus a scope argument if one exists.
  • For calls that should pause, who may say yes, how long should a yes last, and what happens on deny or silence? Decide this before the first request arrives, not during it.

Notice that only the third question is about tools, and only the fourth is about people. The first two are about credentials, which is where most 'the agent had too much access' stories turn out to have started.

Start with one tool that can change something

Dana's request makes the boundary visible: a person can release a call without making it executable at the destination, and a destination can accept a call that a team would never have approved. Keep the four decisions separate and you can explain both why an agent was stopped and why it was allowed through, which is most of what a security review of an agent integration consists of.

Tier Two puts the third and fourth decisions in one place for every agent in your workspace: a mode on every tool, an Ask that holds the real call for a real person, and a record of each outcome. The Free plan covers three people and three connected systems, so you can trace your own Dana before deciding anything bigger.

Sign up for Tier Two