For auth providers, credential vault, and scope challenges, see the Authentication overview.
Overview
The Authorities system provides declarative, built-in authorization on tools, resources, prompts, and skills. Instead of writing imperative access-control checks inside every handler, you declare who can access what directly in decorator metadata. Authorities supports four authorization paradigms:
These paradigms can be composed using
allOf, anyOf, and not combinators to express complex policies.
The system consists of two packages:
@frontmcp/auth— Core types, evaluation engine, registries, and errors@frontmcp/sdk— Built-in flow stages (checkEntryAuthorities,filterByAuthorities) for enforcement, configured via@FrontMcp({ authorities })metadata
Quick Start
Add theauthorities config to your @FrontMcp() decorator and set authorities on any entry.
delete_user, the checkEntryAuthorities flow stage throws an AuthorityDeniedError with MCP error code -32003 before the handler executes. A tools/call answers it as an error result with _meta.code: "AUTHORITY_DENIED" and the denial message, in production too.
The authenticated profile above admits only signed-in callers: an anonymous caller (public mode, allowAnonymous, or an anon: session) has no user.sub in the evaluation context, even when claimsMapping.userId points at another claim, so exists fails for it. A subject that is not a string counts as anonymous too. A caller whose identity carries no sub at all gets the string claimsMapping.userId resolves to as its user.sub, when there is one, and so passes exists. When claimsMapping.userId resolves to something other than a string, a signed-in caller keeps its own sub.
Boot-time fail-fast. If any entry — a tool, resource, resource template, prompt, agent, a tool of an agent, or skill, hidden ones included — declares
authorities but the server has no authorities engine configured (no @FrontMcp({ authorities: { … } })), startup fails with an AuthConfigurationError. This prevents a silent bypass where an entry looks protected but is actually open to everyone. Either add the authorities config or remove authorities from the entry. Servers with no authorities config and no gated entries are unaffected.A rule must check something. Startup also fails (Invalid authorities rule: …) when a rule on an entry or in a profile checks nothing or isn’t what it looks like: {}, { roles: {} }, { roles: { all: [] } }, allOf: [], a misspelled field such as role:, a profile name no profile has (authorities: 'admn'), a profile name inside allOf/anyOf, an operator other than "AND"/"OR", or an ABAC condition without a value the operator can use (see Operator Reference). The engine denies such a rule if it is evaluated anyway. To leave an entry open, remove its authorities.Guards admit only with true. A guard in guards grants access only by returning true. A string is the denial reason; anything else (false, undefined from a guard that forgot to return, null, 0, an object) denies.@Agent({ authorities }) gates the agent’s invoke_<id> tool like any tool: hidden from tools/list and refused on tools/call for callers the rule doesn’t admit. The tools declared inside an agent are checked against the caller the agent runs for, also with execution.useToolFlow: false.JWT Claims Mapping
Every identity provider stores roles and permissions in different JWT claim paths. TheclaimsMapping option tells the engine where to find them.
- Auth0
- Keycloak
- Okta
- Cognito
- Frontegg
Auth0 uses namespaced custom claims for roles and the standard
permissions claim.claimsMapping supports dot-path traversal (e.g., realm_access.roles) and also direct key lookup for namespaced claims containing dots (e.g., https://myapp.com/roles).
Custom Claims Resolver
For more complex scenarios, provide aclaimsResolver function instead of (or in addition to) claimsMapping. It takes precedence when both are configured.
Authority Profiles
Profiles are named, reusable authorization policies registered at the server or app level. They let you writeauthorities: 'admin' instead of repeating the full policy object on every entry.
Registering Profiles
Using Profiles on Entries
RBAC
Role-based access control checks the user’s roles and permissions against required values.Roles
Permissions
Permission checks follow the sameall/any semantics as roles.
Combining Roles and Permissions
When bothroles and permissions are specified in the same policy, they are combined with AND by default.
ABAC
Attribute-based access control evaluates conditions against a context envelope with four namespaces:Simple Match
Thematch field provides simple equality checks. All pairs must match (AND semantics).
Advanced Conditions
Theconditions field supports a rich set of operators for more complex checks.
Operator Reference
An anonymous caller has no
user.sub, so { path: 'user.sub', op: 'exists', value: true } admits signed-in callers only. An expected value that resolves to nothing (for example a missing fromInput field) never satisfies eq or match, even when the actual value is missing too.
Every condition needs a value the operator can use: exists takes true or false, in/notIn a non-empty list, gt/gte/lt/lte a number, and startsWith/endsWith/matches a string; a { fromInput } / { fromClaims } reference works for all but exists. A condition without one (for example { path: 'user.sub', op: 'exists' }, which would admit every caller without a sub) fails startup with Invalid authorities rule, and the engine denies it.
Dynamic Value References
Condition values can reference runtime data instead of using static literals.fromInput is only meaningful for entries that receive request input at call time — tools and agents. @Resource and @Prompt do not pass tool-style input into the authorities evaluation, so a fromInput reference (in an ABAC condition or a ReBAC resourceId) has nothing to resolve against on those entries. Gate resources and prompts with role / permission / claims (fromClaims) policies instead.ReBAC
Relationship-based access control delegates checks to an external authorization backend (e.g., SpiceDB, OpenFGA, or a custom database query).Configuring a Relationship Resolver
First, implement theRelationshipResolver interface and pass it in the authorities config.
Using ReBAC on Entries
Multiple Relationships (AND)
Pass an array of relationship checks. All must pass.Resource ID Sources
Combinators
For complex authorization requirements, compose policies usingallOf, anyOf, not, and the operator field.
allOf (AND)
All nested policies must pass.anyOf (OR)
At least one nested policy must pass.not (Negation)
Invert a nested policy.operator: ‘OR’
By default, top-level fields in a single policy object are combined with AND. Setoperator: 'OR' to use OR instead.
Nested Composition
Combinators nest freely for arbitrarily complex policies.Custom Evaluators
Extend the authorities system with your own evaluators for domain-specific checks.Defining an Evaluator
Implement theAuthoritiesEvaluator interface.
ctx.user.sub is undefined for anonymous callers, so an evaluator that keys on the caller should decide explicitly what an anonymous caller gets.
Registering Evaluators
Using Custom Evaluators in Policies
Reference evaluators under thecustom field. The key must match the evaluator name.
custom evaluator '<name>' is not registered.
Discovery Filtering
List and discovery flows automatically filter entries based on the caller’s authorities via the built-infilterByAuthorities stage. When a client calls tools/list, resources/list, or prompts/list, entries the user is not authorized to access are silently removed from the results.
Skill Discovery Filtering
Skills are filtered on every discovery surface so a caller never sees a skill they are not authorized to load:HTTP skills discovery. With
skillsConfig.auth: 'inherit' (the default), the skills HTTP endpoints (GET /skills, /llm.txt, /llm_full.txt) run the server’s own auth, and authority-gated skills are evaluated against the caller it verified. The api-key, bearer and public modes surface no claims, so there authority-gated skills are left out of every listing and GET /skills/{id} answers 404 for them. Skills without authorities are served over HTTP exactly as before.Hooking into Authority Checks
Authority enforcement runs as native flow stages, not plugin hooks. This means developers can hook into them withWill, Did, and Around decorators — just like any other flow stage.
Flow Stages Reference
Skills are enforced across multiple serving surfaces rather than a single flow stage (MCP custom-method handlers + SEP-2640
skill:// resources + the HTTP Skills API), but the behaviour mirrors the table above: deny on direct load/read (AuthorityDeniedError, code -32003, over MCP; GET /skills/{id} answers 404) and filter on discovery. See Skill Discovery Filtering.
Will Hook — Run Before the Authority Check
UseWill to add custom pre-checks, logging, or feature-flag gates that run before the built-in authority evaluation.
Did Hook — Run After the Authority Check
UseDid to audit authority decisions or emit metrics after the check completes (whether it passed or threw).
Around Hook — Replace the Entire Authority Check
UseAround to wrap or completely replace the built-in authority evaluation with custom logic. This is useful for integrating external policy engines like OPA or Cedar.
Hooking into List Filtering
Error Handling
When an authorities check fails at execution time (as opposed to list filtering), thecheckEntryAuthorities stage throws an AuthorityDeniedError.
AuthorityDeniedError
JSON-RPC Error Format
The error serializes to a standard JSON-RPC error for MCP transport:Denial Reasons
ThedeniedBy field provides actionable feedback:
Type-Safe Profiles
Use theFrontMcpAuthorityProfiles global interface augmentation to get autocomplete and compile-time checks for profile names.
Declaring Profiles
Create a type declaration file (e.g.,authorities.d.ts) in your project:
Autocomplete in Decorators
Once declared, TypeScript provides autocomplete when using string profile references:Metadata Augmentation
The@frontmcp/auth authorities module automatically augments all entry metadata interfaces to accept the authorities field:
authorities is accepted on @Tool(), @Resource(), @ResourceTemplate(), @Prompt(), and @Skill() decorators without any additional configuration. @Agent() accepts it too, through the tool metadata it extends.