AccessToken. Authorization is the next decision: given that verified identity, what is this caller allowed to do? FastMCP expresses authorization as small checks that read the AccessToken and either pass silently or throw AuthorizationError. You attach those checks where the decision belongs — to an individual tool, resource, or prompt — and FastMCP runs them at the right moments without you wiring anything into the request path.
The recommended starting point is scopes. OAuth tokens already carry scopes describing what was granted, so gating a component on a required scope is the most direct expression of “this caller may use this.” Reach for richer logic only when a scope check cannot say what you mean.
Requiring scopes
requireScopes builds an authorization check from one or more scope names. It passes when the caller’s token carries every named scope and throws AuthorizationError naming the first one it lacks. You set it as the auth field on a component’s config, and it gates that component alone.
tool.ts
Per-component auth
requireScopes is one ready-made check, but the auth field accepts any AuthCheck — a function that takes the AccessToken and throws to deny. That escape hatch is for decisions scopes cannot express: ownership (“this caller may only touch their own records”), tenancy (“this caller’s organization must match”), or anything that depends on the token’s claims rather than its scopes.
tool.ts
AuthorizationError is what marks a denial; the message you pass surfaces to the caller. Resources and prompts take the same auth field on their config and behave identically, which keeps one authorization model across all three primitives.
Multiple schemes
A server sometimes has to accept tokens of different kinds at once — JWTs from your identity provider beside long-lived service tokens, or two providers during a migration.multiAuth composes several verifiers into a single one that tries each in turn and returns the first identity any of them produces. The point for authorization is that every verifier yields the same AccessToken shape, so your scope checks and per-component rules are written once and apply no matter which scheme verified the caller.
server.ts
Caching and identity
Authorization makes responses caller-specific: two callers asking the same question can legitimately get different answers, because one is allowed something the other is not. That collides with response caching, where a result computed for one caller could be replayed to another. FastMCP’sCachingMiddleware is built for this — its default cache key already partitions by identity, so an entry written for one caller is never served to another, with no configuration.
The default key is the request method, plus an auth partition, plus the serialized request params. The auth partition is the piece that keeps identities apart: an unauthenticated request uses a single anon partition, and an authenticated request is partitioned by the SHA-256 hash of its bearer token — the hash, never the raw token. A privileged caller’s filtered tools/list therefore stays out of an anonymous caller’s cache in both directions. The caching page covers the full model.
You take over that partitioning only when you pass a custom CacheKeyFn. A custom key replaces the default entirely — the auth partition is not merged back in — so when your cached values depend on who is calling, you must fold identity into the key yourself. Use the same dimension the default uses: the bearer token, hashed. The first argument is the TTL in milliseconds.
server.ts
ctx.mcpContext.auth?.clientId is a coarser dimension: two tokens can share a clientId yet differ in scope, so a clientId key can merge identities that must stay apart — prefer the hashed token. The general rule follows from the verification-versus-authorization split: identity established once at the edge has to be carried into every decision downstream that depends on it, and a cache is one of those decisions.