Skip to main content

SEP-835 Scopes: Least-Privilege MCP Tokens for Agents That Hold Deploy Keys

9 min readDora NodaDora Noda
Share
On this page

Authorization in MCP is optional. That is not a criticism from a competitor — it is a direct quote of how the NSA's first security assessment of the Model Context Protocol, published in May 2026, describes the protocol's own stance. The spec leaves authorization up to whoever builds on it, which means the one control that matters most for production access is exactly the one most likely to be skipped when a deployment moves fast.

We already know what skipping it looks like. The NSA sheet documents the GitHub MCP server case: blanket user consent at connection time gave tools unrestricted read and write access across private and public repositories, letting compromised tools read private content and publish it publicly without the user's awareness. Now shrink that blast radius down to the deploy button. If your agent-ops story includes an MCP server that can check status, tail logs, deploy, and roll back, a single over-scoped token turns a routine status check into a potential unauthorized deploy. The fix is not a bigger prompt or a stricter system instruction. It is scopes — real, protocol-level, least-privilege scopes.

The before and after: one token vs per-tool scopes

Here is the concrete change, applied to a realistic deploy/rollback MCP surface with four tools. This is the whole argument in one table; everything after it is mechanism.

ToolBefore: one Bearer [REDACTED]After: scoped tokens
get_status (read deploys)Allowed — same token as everythingAllowed — status-check agent holds deploy:status
get_logs (read logs)Allowed — same token as everythingAllowed — holds deploy:logs
deploy (ship a build)Allowed — same token, nothing stops itDenied — token lacks deploy:write; server returns 403 with a scope challenge
rollback (revert prod)Allowed — same token, nothing stops itDenied — same challenge; write scope granted only via explicit step-up

Before, authorization is a connection-time event: authenticate once, reach everything the connection exposes. After, authorization resolves per tool: the agent that only ever checks status and reads logs holds read scopes and nothing else. Removing the write scopes it never uses is, as the Coalition for Secure AI puts it, the cheapest defense available — a compromised agent cannot escalate to a deploy it was never authorized to perform, because the token itself does not carry the permission. No prompt guardrail can offer that guarantee; a guardrail written into a system prompt will not stop a tool call from executing once the token allows it.

What SEP-835 actually added to the spec

For most of MCP's life, scopes were a handshake convention, not a protocol feature. Servers requested broad OAuth scopes to maximize flexibility, clients granted them, and anything finer had to be bolted on outside the spec. SEP-835 changed that by adding native scope definitions to MCP in the 2025-11-25 specification revision.

Three pieces matter for a deploy surface. First, servers can now define scopes at the tool level and advertise the minimal set a client needs to kickstart authorization via scopes_supported in protected-resource metadata. A deploy server can declare that get_status needs deploy:status while deploy needs deploy:write, and a client can start with only the former. Second, scope consent is incremental: when a token without the right scope touches a gated tool, the server answers 403 with a WWW-Authenticate challenge naming the missing scope, and the client can request just that scope — a step-up, not an all-or-nothing re-auth.

Third, the current 2026-07-28 revision carries all of this forward and requires clients to send the RFC 8707 resource parameter, binding each token to the exact server it was issued for. A token minted for your log reader cannot be replayed against your deploy endpoint, even by a holder with full read access to it.

That last point closes a hole the ecosystem is still patching in practice: as recently as July 2026, an MCP SDK was fixed for omitting the resource parameter on refresh-token requests, which silently broke audience binding the moment an access token expired. The spec can mandate binding; implementations still have to send the parameter on every token request, including refreshes.

The worked flow: status check that cannot become a deploy

Walk through the after-column with a concrete agent. A status-check agent polls get_status every minute and tails get_logs when a deploy looks stuck. Its token carries deploy:status and deploy:logs, audience-bound to the deploy MCP server's canonical URI. One day the agent is compromised — a poisoned tool description, an injected instruction in a log line it just read — and it issues a deploy call.

What happens is unremarkable, which is the point. The server checks the token's scopes against the tool's requirement, finds no deploy:write, and returns 403 with a WWW-Authenticate challenge advertising the missing scope. The client may then request step-up consent from the user, or the request simply fails closed. Either way, the unauthorized deploy does not happen — not because the agent chose well, but because the credential could not express the action. Contrast this with the before-column, where the identical compromised call succeeds silently because the token never distinguished reading from writing.

The step-up path also covers the legitimate case without permanent over-privilege. An on-call agent that normally reads status can be granted deploy:write for a single rollback, scoped to that operation, and go back to read-only when the incident closes. Short-lived, task-scoped access beats standing write permission every time; the Coalition for Secure AI's post-RSAC guidance goes further and recommends combining short lifetimes with DPoP proof-of-possession (RFC 9449), so even an intercepted token cannot be replayed by anyone but the client that requested it.

What the spec still will not do for you

Honest boundary first: SEP-835 gives you scope vocabulary and a challenge mechanism, not a policy engine. The Coalition's MCP security taxonomy labels missing or improper access control as MCP-T2 and states plainly that the protocol lacks native support for fine-grained authorization checks — object-level enforcement is an implementation responsibility, not something the spec handles for you.

That is where the field-level framing from CData's September 2026 writeup earns its keep. In data access, field-level security means restricting an agent to specific tables, columns, and rows rather than a whole connection. Translate that to a deploy surface: per-tool scopes are the coarse layer, but a serious platform also scopes per resource — which app, which environment, which parameters. deploy:write that can ship any app to production is still a blunt instrument; Rich Authorization Requests (RFC 9396) let a token name the specific resource and parameters it covers, so a staging-deploy agent cannot touch production even with a write scope in hand.

Enforcement belongs in policy, not application code. The Coalition points to Open Policy Agent, Cedar, and OpenFGA as languages designed for exactly this: external, auditable, version-controlled authorization logic evaluated per request, rather than if statements scattered through tool handlers that become invisible, untestable, and unreviewable. And one rule from the RSAC Q&A floor deserves repeating verbatim in spirit: never pass OAuth tokens through. When your MCP server calls an upstream API, it acts as an OAuth client and obtains its own token via token exchange (RFC 8693) — forwarding the client's token downstream creates confused-deputy conditions the spec explicitly forbids. Scope must narrow at every hop, never expand.

A checklist for your deploy MCP surface

If you run — or plan to run — an MCP server that touches deploys, this is the minimum viable least-privilege posture, assembled from the spec and the Coalition guidance:

  • Declare per-tool scopes and advertise scopes_supported. Clients should be able to start with the minimal set. If your server SDK cannot easily declare required scopes per tool yet — the TypeScript SDK still lacks ergonomic support for scope challenges on tool calls — implement the 403 + WWW-Authenticate path by hand; it is a small amount of code for the highest-value control.
  • Validate token audience on every request. Reject anything not minted for your server's canonical URI, and verify the resource parameter is sent on refresh flows, not just initial authorization.
  • Remove write scopes when read suffices. The status-check agent is the canonical example: read scopes only, step-up consent for the rare write.
  • Exchange, never forward. Token exchange (RFC 8693) for every downstream hop, with narrowing scopes and actor/subject claims so the chain of custody stays visible.
  • Short-lived tokens, ideally with DPoP. No standing credentials; just-in-time, task-scoped, revocable access.
  • Log every action with its identity. User or agent identity, tool called, parameters, timestamp — per request, immutable. A study of 1,899 open-source MCP servers found 7.2% with general vulnerabilities and 5.5% exhibiting MCP-specific tool poisoning; when something goes wrong, the audit trail is the difference between an incident and a mystery.

Least privilege is a deploy feature, not a compliance checkbox

The throughline of 2026's MCP security conversation — the January Coalition whitepaper, the RSAC Q&A converging on identity, the NSA sheet, CData's field-level guide — is that agents are not users with unusual typing speed. They chain tool calls across systems in a single request, they act on non-human credentials at machine scale, and they inherit whatever the connection exposes unless something narrower intervenes. SEP-835 plus RFC 8707 resource binding finally gives that something a standard shape: scopes the server defines, step-up the client can request, tokens bound to one audience.

For a self-hosted platform, this is roadmap material, not theory. The moment a deploy-from-chat flow or an agent-ops console can trigger a real rollout, the authorization model behind it decides whether a compromised status check is a logged 403 or an unexplained production deploy. Build the scoped version first — per-tool scopes, audience validation, step-up for writes — and the audit trail writes itself.

Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Agent-ops is a first-class surface, which means deploy tooling gets least-privilege scoping from day one, not as a retrofit. Star the repo on GitHub or deploy your first app today.

Related articles

Build on bex.co

Open-source infrastructure for apps and agents — deploy on machines you own, or use the hosted multi-chain API.

See pricing