Skip to main content
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
No plugin required — authorities is a first-class framework feature.

Quick Start

Add the authorities config to your @FrontMcp() decorator and set authorities on any entry.
If a user without the required role calls 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. The claimsMapping option tells the engine where to find them.
Auth0 uses namespaced custom claims for roles and the standard permissions claim.
The 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 a claimsResolver 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 write authorities: 'admin' instead of repeating the full policy object on every entry.

Registering Profiles

Using Profiles on Entries

When an array of profiles is provided, they are evaluated with AND semantics — every profile must pass for access to be granted.

RBAC

Role-based access control checks the user’s roles and permissions against required values.

Roles

Permissions

Permission checks follow the same all/any semantics as roles.

Combining Roles and Permissions

When both roles 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

The match field provides simple equality checks. All pairs must match (AND semantics).

Advanced Conditions

The conditions 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 the RelationshipResolver 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 using allOf, 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. Set operator: '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 the AuthoritiesEvaluator 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 the custom field. The key must match the evaluator name.
If a referenced custom evaluator is not registered, the policy is denied with the message custom evaluator '<name>' is not registered.

Discovery Filtering

List and discovery flows automatically filter entries based on the caller’s authorities via the built-in filterByAuthorities 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.
This ensures AI agents only see tools they can actually call, preventing wasted context and failed invocations.
Discovery filtering runs without request input. filterByAuthorities evaluates each entry’s policy against the caller’s identity (roles / permissions / claims) only — there is no tool input at list time. As a result, a policy that depends on fromInput (an ABAC condition or a ReBAC resourceId resolved from input) cannot be satisfied at discovery time and is treated as denied for everyone, so the entry is filtered out of tools/list (and the skill/resource/prompt discovery surfaces) for all callers — even users who would pass the check at call time. Gate discovery visibility with role / permission / claims-only policies; reserve fromInput/ReBAC-by-input policies for call-time enforcement (checkEntryAuthorities), where the input is available.

Skill Discovery Filtering

Skills are filtered on every discovery surface so a caller never sees a skill they are not authorized to load:
List-time filtering is role/permission/claims-only. Discovery runs without request input, so input-dependent policies — ABAC conditions using { fromInput: '…' } or ReBAC resourceId: { fromInput: '…' } — cannot be evaluated when filtering a list and will exclude the skill from discovery. Use role-, permission-, or claims-based authorities (e.g. authorities: 'admin' or { roles: { any: ['admin'] } }) for skills that must stay discoverable. Input-dependent policies are still enforced correctly at load time (see below), where the request input is available. The same limitation applies to tools, resources, and prompts.
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 with Will, 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

Use Will 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

Use Did to audit authority decisions or emit metrics after the check completes (whether it passed or threw).

Around Hook — Replace the Entire Authority Check

Use Around 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), the checkEntryAuthorities stage throws an AuthorityDeniedError.

AuthorityDeniedError

JSON-RPC Error Format

The error serializes to a standard JSON-RPC error for MCP transport:

Denial Reasons

The deniedBy field provides actionable feedback:

Type-Safe Profiles

Use the FrontMcpAuthorityProfiles 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:
This means 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.