Auth and identity for an MCP server
Make every call to your MCP server carry a person or automation, check a scope per tool, reach upstream as that caller, and log one entry per call.
On this page
Every call to a company MCP server has to answer two questions before it does anything: who is calling, and may they do this? A server that answers with one shared token answers neither, and the help desk behind it logs every action under a name that belongs to nobody. The protocol's authorization flow is in the Model Context Protocol "Authorization" (opens in a new tab) page; this lesson decides what the support desk server from designing the tool surface does with it. The desk is still the online retailer's from module 4, not the agency.
The shared token, and the week it cost
The desk server's callers are 12 agents, 2 team leads, you, and the triage automation, which reads orders to enrich tickets: 16 identities. The contractor's first version had one. Every host connected with the same bearer token, and the server called the help desk with an admin API key from its environment.
In week three, 31 tickets carried an internal note reading "refund approved by lead, process today". The help desk showed the author as svc-mcp on all 31, and on the other 84,000 calls that week, about 12,000 a day: 6,300 from the agents' assistant (2,100 tickets × 3 tool calls) and the rest from triage. It took two days to trace the notes to the triage automation, which had acted on a forwarded email it was only meant to read. It could write notes because the token could do anything the admin key could.
A shared token costs four things:
- Accountability. The log says what happened and never who did it. The "Security Best Practices" (opens in a new tab) page lists the lost audit trail among the reasons the specification forbids passing a client's token through to an upstream API.
- The help desk's own rules. An agent can credit $20 and cannot see card details. The admin key can do both, so through the server every caller can too, and the server becomes the deputy that does for a caller what the caller could not do by hand.
- Offboarding. Revoking one leaver means rotating the token for 16 callers, so in practice nobody does.
- Reach. The automation that reads customer email holds the same power as the founder.
Who is calling
The desk server is shared, so it runs over Streamable HTTP as an OAuth resource server, which is what the specification's authorization framework expects of an HTTP server. Each request carries a bearer token that your identity provider issued for this server and for one person or one automation. The server checks three things before it reads the tool name:
- The signature and expiry, as for any token; an invalid or expired one gets a 401.
- The audience. The specification requires the server to accept only tokens issued for it. A token minted for the wiki or the billing system is refused, even when it is valid, because accepting it would let any service holding a company token call the desk.
- The subject.
priya@ortriage-bot, taken from the verified token and never from an argument the model wrote.
Priya signs in once through the help desk's sidebar, the host, in the authorization-code flow from how systems talk. The triage automation gets a client-credentials identity of its own. It never borrows a person's token and never holds the admin key.
The other half is the call the server makes upstream. The specification's "Authorization Security Considerations" (opens in a new tab) page is explicit: a server that calls an upstream API acts as an OAuth client with a separate token, and must not pass through the token it received. So on first use Priya also authorises the desk server at the help desk; the server stores her refresh token encrypted, keyed by the subject from her verified token, and makes every help desk call as her. The help desk then applies her own rules: the $20 credit limit, the hidden card numbers, the queues she can see. That is the deeper reason for per-user credentials: the row-level checks already live in the system that owns the rows, where authentication and authorisation puts them, and the server inherits them. Where an upstream system has no per-user grant, the second-best design uses a narrow service key and enforces the caller's limits in the server, whose own audit log then has to carry the person.
A local server over stdio is a different case. The specification says it takes its credentials from the environment, so it runs as whoever launched it. That is acceptable for one developer's laptop and wrong for a desk of 14, and an MCP server, end to end covers keeping that environment clean.
Scopes per tool
Identity says who is calling. Scope says which tools they may call. The surface from lesson 7.1 needs three scopes:
| Scope | Tools | Agents and leads | Triage automation | You |
|---|---|---|---|---|
desk:read | find_customer, get_ticket, get_order, get_refund_policy | yes | yes | yes |
desk:note | add_internal_note | yes | no | no |
desk:draft | save_draft_reply | yes | no | no |
With this table the week-three notes could not have been written: triage holds desk:read and nothing else. Two mechanisms enforce it. When the server answers tools/list it returns only the tools the caller's scopes permit, which the "Tools" (opens in a new tab) page allows, so triage's model never sees a write. The current revision requires ttlMs and cacheScope on every list result, and a list that depends on the token is marked cacheScope: "private": the "Caching" (opens in a new tab) page warns that a "public" list may be served to a caller holding a different token. And when a call arrives anyway, the server refuses it with a 403 and insufficient_scope, naming the scope it would need.
The tempting shortcut is one scope, desk:*, to spare a consent prompt. The "Security Best Practices" (opens in a new tab) page names wildcard and omnibus scopes as a common mistake: a stolen token then reaches everything, and the log can no longer tell a lookup from a write. Three scopes cost each agent one consent screen at first sign-in.
One audit entry per call
The server writes one line for every call it receives, before the result goes back, including calls it refused and calls that ended in isError:
2026-10-14T09:42:17Z call c-7f2e sub priya@ via help-desk-sidebar
tool add_internal_note scope desk:note ticket T-58113
args sha256:9c1e… 212 chars outcome ok upstream 201 38 ms trace tr-55a0
sub and via say who, and through which host: Priya in the sidebar and Priya in a coding agent on her laptop are different risks. tool, scope and ticket say what, on which record. args is a hash and a length, because the note already sits on the ticket and customer text copied into a log is a second copy to protect. trace joins the entry to the model call that asked for it. The token never appears; the specification's authorization security considerations name tokens logged on the server as a theft risk.
The cost is small enough not to argue about. 12,000 entries a day at about 500 bytes is 6 MB a day, about 2.2 GB a year. With the log in place, the week-three question becomes one query: tool = add_internal_note, those 31 tickets, grouped by sub, answered in a minute instead of two days.
In practice
Ask the contractor two questions and see the answers. Which identity does the help desk see when Priya's sidebar calls add_internal_note? Show me the audit entry for that call, and the one for a call the server refused. If the first answer is "the service account", the server is a shared admin key with extra steps.
The mistake that costs money is one shared service token. Every caller gets the admin key's reach, the help desk's own limits stop applying, and the first incident costs days of tracing because the log names nobody.
Check yourself
1. An agent leaves on Friday at 17:00. What do you do under the shared token, what under per-user tokens, and how long is she exposed?
Shared: rotate the token and reconfigure 16 callers, or, more likely, leave her access open indefinitely. Per-user: disable her in the identity provider; her access token dies at expiry, so 10-minute tokens bound the exposure, and her stored help desk grant is deleted with the account. A five-minute task actually gets done.
2. A lead asks for desk:credit so the assistant can issue credits up to $20. Decide the scope design.
A fourth scope for agents and leads, never triage, on a tool whose schema caps
amountat 20, the by-hand limit. Log the amount in clear: whoever reverses a wrong credit needs it. Whether the model issues it or proposes it for a click is a separate decision, priced in read-only or read-write.
3. Estimate the audit log at twice today's volume and decide whether to keep it for 13 months.
24,000 entries a day × 500 bytes = 12 MB a day, about 4.7 GB over 13 months. Keep it: the cost is negligible, and "who credited this customer last December?" needs more than 12 months.