Authorization
The Registry server provides a claims-based authorization model that controls who can manage sources, registries, and entries. Authorization builds on top of authentication — you need OAuth authentication enabled before configuring authorization.
How authorization works
Authorization in the Registry server operates at two levels:
- Role-based access control (RBAC): Determines which admin operations a caller can perform (manage sources, manage registries, publish/delete entries).
- Claims-based scoping: Limits visibility and access to specific sources, registries, and entries based on key-value claims attached to both resources and callers.
When a caller makes an API request, the server:
- Extracts the caller's claims from their JWT token
- Resolves the caller's roles based on those claims and the
authzconfiguration - Checks whether the caller's role permits the operation
- Checks whether the caller's claims satisfy the resource's claims
Configure roles
Define roles in the auth.authz.roles section of your configuration file. Each
role maps to a list of claim rules — if a caller's JWT claims match any rule in
the list, they are granted that role.
auth:
mode: oauth
oauth:
resourceUrl: https://registry.example.com
providers:
- name: keycloak
issuerUrl: https://keycloak.example.com/realms/mcp
audience: registry-api
authz:
roles:
superAdmin:
- role: 'super-admin'
manageSources:
- org: 'acme'
role: 'admin'
manageRegistries:
- org: 'acme'
role: 'admin'
manageEntries:
- role: 'writer'
Available roles
| Role | Grants access to |
|---|---|
superAdmin | All operations; bypasses all claim checks |
manageSources | Create, update, delete, and list sources via the admin API |
manageRegistries | Create, update, delete, and list registries via the admin API |
manageEntries | Publish and delete MCP server versions and skills |
Role rule matching
Each role is defined as a list of claim maps. A caller is granted the role if their JWT claims match any map in the list (OR logic). Within a single map, all key-value pairs must match (AND logic).
authz:
roles:
manageSources:
# Rule 1: any admin in the acme org
- org: 'acme'
role: 'admin'
# Rule 2: anyone with the platform-lead role
- role: 'platform-lead'
Claim values can be strings or arrays. When a JWT claim is an array (for
example, roles: ["admin", "writer"]), the server checks whether any element
matches the required value. A rule with role: "admin" would match this JWT
because "admin" is one of the array elements.
Super-admin role
The superAdmin role bypasses all claim checks across the entire server. A
super-admin can:
- Access any registry regardless of its claims
- See all entries regardless of source or entry claims
- Manage any source or registry, even those with claims outside their JWT
- Publish and delete entries without claim validation
Use this role sparingly and only for platform operators who need unrestricted access.
Configure claims on sources and registries
Claims are key-value pairs attached to sources and registries in your configuration file. They act as access boundaries — only callers whose JWT claims satisfy the resource's claims can access it.
Source claims
Claims on a source are inherited by all entries during sync. This means every MCP server or skill ingested from that source carries the source's claims.
sources:
- name: platform-tools
format: toolhive
git:
repository: https://github.com/acme/platform-tools.git
branch: main
path: registry.json
syncPolicy:
interval: '30m'
claims:
org: 'acme'
team: 'platform'
- name: data-tools
format: toolhive
git:
repository: https://github.com/acme/data-tools.git
branch: main
path: registry.json
syncPolicy:
interval: '30m'
claims:
org: 'acme'
team: 'data'
With this configuration:
- Entries from
platform-toolsare visible only to callers withorg: "acme"andteam: "platform"in their JWT - Entries from
data-toolsare visible only to callers withorg: "acme"andteam: "data"in their JWT - A super-admin sees all entries regardless of claims
Registry claims
Claims on a registry act as an access gate for the consumer API. Before returning any data from a registry's endpoints, the server checks that the caller's JWT claims satisfy the registry's claims.
registries:
- name: platform
sources: ['platform-tools']
claims:
org: 'acme'
team: 'platform'
- name: public
sources: ['community-tools']
# No claims — accessible to all authenticated users
A caller who requests GET /platform/v0.1/servers must have JWT claims that
include org: "acme" and team: "platform". Otherwise, the server returns
403 Forbidden.
Claim containment
The server uses containment (superset check) for claim validation: the caller's claims must be a superset of the resource's claims. For example:
| Resource claims | Caller JWT claims | Result |
|---|---|---|
{org: "acme"} | {org: "acme", team: "platform"} | Allowed |
{org: "acme", team: "platform"} | {org: "acme"} | Denied |
{} (no claims) | {org: "acme"} | Allowed |
{org: "acme"} | {org: "contoso"} | Denied |
Resources with no claims are accessible to all authenticated callers.
Claims on published entries
When you publish an MCP server version or skill to a managed source, you can attach claims to the entry. The server enforces two rules:
-
Publish claims must be a subset of the publisher's JWT claims. You cannot publish entries with broader visibility than your own identity allows. For example, if your JWT has
{org: "acme", team: "platform"}, you can publish entries with{org: "acme", team: "platform"}but not with{org: "acme"}alone (which would be visible to all teams). -
Subsequent versions must have the same claims as the first. Once you publish the first version of an entry with specific claims, all future versions must carry the exact same claims. This prevents accidentally narrowing or broadening visibility across versions.
curl -X POST \
https://registry.example.com/default/v0.1/publish \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "my-server",
"version": "1.0.0",
"url": "https://mcp.example.com/my-server",
"description": "Team-scoped MCP server",
"claims": {
"org": "acme",
"team": "platform"
}
}'
Admin API claim scoping
When authorization is enabled, the admin API endpoints for managing sources and registries are also scoped by claims:
- List sources/registries: Only returns resources whose claims the caller's JWT satisfies.
- Get source/registry by name: Returns
404 Not Found(not403) when the caller's claims don't match — this prevents information disclosure about resources the caller cannot access. - Create source/registry: The request claims must be a subset of the caller's JWT claims.
- Update/delete source/registry: The caller's JWT claims must satisfy the existing resource's claims.
Anonymous mode
When authentication is set to anonymous, all authorization checks are
bypassed. There are no JWT claims to validate, so all sources, registries, and
entries are accessible without restriction. This is suitable for development and
testing environments only.
Complete example
This example shows a multi-team setup with full RBAC and claims-based scoping:
sources:
- name: platform-tools
format: toolhive
git:
repository: https://github.com/acme/platform-tools.git
branch: main
path: registry.json
syncPolicy:
interval: '30m'
claims:
org: 'acme'
team: 'platform'
- name: data-tools
format: toolhive
git:
repository: https://github.com/acme/data-tools.git
branch: main
path: registry.json
syncPolicy:
interval: '30m'
claims:
org: 'acme'
team: 'data'
- name: shared
managed: {}
registries:
- name: platform
sources: ['platform-tools', 'shared']
claims:
org: 'acme'
team: 'platform'
- name: data
sources: ['data-tools', 'shared']
claims:
org: 'acme'
team: 'data'
auth:
mode: oauth
oauth:
resourceUrl: https://registry.example.com
providers:
- name: keycloak
issuerUrl: https://keycloak.example.com/realms/mcp
audience: registry-api
authz:
roles:
superAdmin:
- role: 'super-admin'
manageSources:
- org: 'acme'
role: 'admin'
manageRegistries:
- org: 'acme'
role: 'admin'
manageEntries:
- role: 'writer'
With this configuration:
- Platform team members (JWT with
org: "acme",team: "platform") can access theplatformregistry and see entries fromplatform-toolsandshared. - Data team members (JWT with
org: "acme",team: "data") can access thedataregistry and see entries fromdata-toolsandshared. - Writers (JWT with
role: "writer") can publish to thesharedmanaged source. - Admins (JWT with
org: "acme",role: "admin") can manage sources and registries within theacmeorg. - Super-admins (JWT with
role: "super-admin") can access and manage everything.
Entries published to the shared source without claims are visible through any
registry that includes it, subject only to the registry-level claims gate. To
restrict visibility further, attach claims when
publishing entries.
Next steps
- Configure authentication to set up OAuth providers
- Configure sources and registries to set up your data sources
- Manage skills to publish and discover reusable skills
Related information
- Registry server introduction - architecture and features overview