An MCP tools/call needs a scope the token lacks — what should the server return?
answer
- Authenticated, but not permitted
- A status code, not a JSON-RPC error
- The 4xx that means come back with more
- No session to upgrade in place
- Retry as a brand-new request
basics
~20 sA 403 with error="insufficient_scope", not a JSON-RPC error and not a bare failure. The client then runs a fresh authorization flow asking for the extra scope and re-issues the same call as a brand-new request with a new JSON-RPC id.
solid answer
~50 sThe token is valid for this server, so this is not an authentication failure: the server answers at the HTTP layer with `403` and `error="insufficient_scope"`, which tells the client that a step-up is possible rather than that the call is impossible. The client obtains a new token covering the additional scope and then **re-issues the original request as a new request with a new JSON-RPC id** — in MCP revision 2026-07-28 there is no session to upgrade in place, because protocol-level sessions and the `Mcp-Session-Id` header were removed; every request carries its own credential and is authorized on its own. This also explains a rule that surprises people: a server's tool, prompt and resource sets must not vary per connection, but they may vary by the authorization presented, so the same server can legitimately show a larger `tools/list` once the broader token is in play.
code
http · 3 linesHTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope", scope="files.write"
Content-Length: 0go deeper
Know the difference between not being logged in and not being allowed: a missing or invalid token gets 401, while a valid token without the needed permission gets 403.
Name the insufficient_scope signal on the 403 and explain that the client acquires a broader token and sends the call again as a new request, since authorization travels per request.
Show why the refusal belongs at the HTTP layer rather than inside a tool result, and connect the retry to 2026-07-28 removing sessions — there is nothing to upgrade, only a new request with a new id and a new token.
Own scope granularity as a design decision: coarse scopes make step-up meaningless and tokens over-powerful, per-tool scopes exhaust users. Group by blast radius so a consent prompt is something a person can actually evaluate.
## Distinguishing the two rejections A remote MCP server has two very different things to say about a request it will not serve. - **No usable credential.** Nothing was presented, or what was presented is expired, malformed, or bound to a different audience. That is an authentication-layer failure and the answer is `401`. - **A perfectly good credential that does not cover this call.** The token is for this server, unexpired and genuine; it simply lacks the permission the specific tool or resource requires. That is an authorization-layer failure and the answer is `403` with `error="insufficient_scope"`. Getting this split right matters because the two point the client at different remedies. A `401` says "go authenticate"; the `insufficient_scope` signal says "you are the right caller, come back with more permission". Collapsing both into a generic failure leaves the client unable to do anything useful except give up. ## Why it is an HTTP status and not a JSON-RPC error Authorization in MCP is transport-level. The token rides on the HTTP request, is evaluated before the JSON-RPC body is meaningfully processed, and the answer belongs at the same layer. A JSON-RPC error object inside a `200` would bury a security decision in the payload, where generic clients and infrastructure cannot see it, and would make it indistinguishable from a tool that merely failed. Reserve `isError` and JSON-RPC error objects for what happened *inside* a call the caller was allowed to make. ## What the client does next The step-up loop is: 1. Read the `403` and the `insufficient_scope` signal, including any indication of which scope is required. 2. Run an authorization flow requesting that additional scope, with the same audience binding as before — the token must still be for this MCP server. 3. Re-issue the original call **as a new request with a new JSON-RPC id**, carrying the new token. Step 3 is where the stateless design shows through. Through revision 2025-11-25, MCP had a protocol-level session, and it was natural to imagine "upgrading" it. Revision 2026-07-28 removed sessions and the `Mcp-Session-Id` header entirely, along with the `initialize` handshake. There is no connection-scoped authorization state to change; there is only the next request, which will be authorized on the credential it carries. ## Consent, not just plumbing A step-up should be user-visible. The extra scope is a widening of what the server may do on the user's behalf, and MCP's security model puts the host in charge of consent — the protocol cannot enforce it. A host that silently escalates whenever a `403` appears has automated away exactly the decision the user was supposed to make. The good behaviour is to explain which additional permission the call needs and let the user approve or refuse; a refusal simply means the call does not happen. ## The visible-surface consequence MCP requires that the set of tools, prompts and resources a server exposes must not vary *per connection*, but explicitly allows it to vary by the authorization presented. Both halves of that rule are load-bearing here. It means a server may legitimately hide write tools from a read-only token and reveal them once a broader token arrives — the difference is attributable to the credential, not to which connection you happen to be on or what you did earlier. Clients should therefore treat cached listings as tied to the authorization used to fetch them; the `ttlMs` and `cacheScope` fields on a `tools/list` result exist so a server can mark an authorization-dependent listing as private rather than publicly cacheable. ## Server-side design notes Decide scope granularity deliberately. One coarse scope per server makes step-up meaningless and every token maximally powerful; a scope per individual tool produces consent screens nobody reads. A workable middle ground groups by capability and blast radius — read versus write, or per data domain — so that a step-up prompt has a meaning a user can evaluate. Also make the required scope discoverable in the rejection where you can. A client that knows which permission was missing can ask for exactly that; a client that only knows "more" tends to ask for everything, which is how over-broad tokens spread. ## What weak answers look like Returning `401` for a scope problem, which sends the client back through authentication that will succeed and change nothing. Returning a tool result with `isError` set, which hides the security decision inside the payload. Or describing the recovery as "the session is upgraded" — a mechanism that no longer exists after 2026-07-28.
- Why not answer 401 and let the client re-authenticate?Because authentication is not what failed. The token is genuine, unexpired and bound to this server; re-running the same flow returns an equivalent token and the call fails again. A 403 with `insufficient_scope` tells the client the specific remedy — obtain broader permission for the same audience — instead of sending it around a loop that cannot succeed.
- Should the host prompt the user before requesting the wider scope?Yes. A step-up widens what the server may do on the user's behalf, and MCP's security model makes the host the enforcement point for consent since the protocol cannot enforce it. Automatically escalating on every 403 removes the decision the user was meant to make. Declining is a legitimate outcome: the call simply does not happen.
- Can a server show fewer tools to a narrower token?Yes. The rule is that the tool set must not vary per connection, but it may vary by the authorization presented, so hiding write tools from a read-only token is conformant. Clients should key cached listings to the credential used, and servers should mark such results with cacheScope "private" so an authorization-specific listing is not shared.
saying these in an interview costs you the question
- Returns 401 when the token merely lacks a scope
- Reports the refusal as a tool result with isError
- Says the existing session is upgraded in place
- Escalates scope silently without asking the user
- Reuses the original JSON-RPC id when retrying after step-up