Skip to content

RBAC ConfigurationΒΆ

Role-based access control (RBAC) defines which actions users or teams can perform in MCP Gateway. This document covers the two-layer security model, token scoping semantics, permission system, and best practices for access control.


OverviewΒΆ

MCP Gateway implements a two-layer security model:

  1. Token Scoping (Layer 1): Controls what resources a user CAN SEE (data filtering)
  2. RBAC (Layer 2): Controls what actions a user CAN DO (action authorization)

Both layers must pass for an operation to succeed.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                      Two-Layer Security Model                               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                                             β”‚
β”‚   Request β†’ Authentication β†’ Token Scoping β†’ RBAC Check β†’ Operation        β”‚
β”‚                              (Can See?)       (Can Do?)                     β”‚
β”‚                                                                             β”‚
β”‚   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚   β”‚   JWT    β”‚   β”‚  User    β”‚   β”‚ Resource β”‚   β”‚Permissionβ”‚   β”‚ Execute  β”‚ β”‚
β”‚   β”‚  Token   │──▢│ Identity │──▢│  Access  │──▢│  Check   │──▢│ Operationβ”‚ β”‚
β”‚   β”‚          β”‚   β”‚          β”‚   β”‚          β”‚   β”‚          β”‚   β”‚          β”‚ β”‚
β”‚   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Authentication MethodsΒΆ

Method Priority Description
JWT Token 1 (Primary) Signature verified, supports teams/scopes claims
Plugin Auth 0 (Before JWT) HTTP_AUTH_RESOLVE_USER hook can provide custom auth
API Token (DB) 2 (Fallback) Legacy database-stored tokens
Proxy Header 3 When MCP_CLIENT_AUTH_ENABLED=false AND TRUST_PROXY_AUTH=true
Anonymous 4 When AUTH_REQUIRED=false (development only)

Core ConceptsΒΆ

SubjectsΒΆ

Users authenticated via:

  • JWT tokens (session or API)
  • SSO providers (OAuth 2.0/OIDC)
  • Basic authentication (development only)

TeamsΒΆ

Logical groups that:

  • Organize users for access boundaries
  • Own resources (tools, prompts, resources)
  • Map from external identity providers (SSO groups)

Built-in RBAC RolesΒΆ

Role Scope Permissions
platform_admin global ["*"] (all permissions)
team_admin team admin.dashboard, gateways.read, gateways.create, gateways.update, gateways.delete, servers.read, servers.create, servers.update, servers.delete, teams.read, teams.update, teams.join, teams.delete, teams.manage_members, tools.read, tools.create, tools.update, tools.delete, tools.execute, resources.read, resources.create, resources.update, resources.delete, prompts.read, prompts.create, prompts.update, prompts.delete, a2a.read, a2a.create, a2a.update, a2a.delete, a2a.invoke
developer team admin.dashboard, gateways.read, gateways.create, gateways.update, gateways.delete, servers.read, servers.create, servers.update, servers.delete, teams.join, tools.read, tools.create, tools.update, tools.delete, tools.execute, resources.read, resources.create, resources.update, resources.delete, prompts.read, prompts.create, prompts.update, prompts.delete, a2a.read, a2a.create, a2a.update, a2a.delete, a2a.invoke
viewer team admin.dashboard, gateways.read, servers.read, teams.join, tools.read, resources.read, prompts.read, a2a.read
platform_viewer global admin.dashboard, gateways.read, servers.read, teams.join, tools.read, resources.read, prompts.read, a2a.read

Default Role Assignment

New users automatically receive up to two roles upon creation:

Admin users (is_admin: true) receive:

  1. platform_admin role with global scope (scope_id = None)
  2. Grants unrestricted access to all platform resources
  3. team_admin role with team scope (scope_id = personal team ID)
  4. Grants full management of their personal team resources
  5. Only assigned if personal team creation succeeds

Non-admin users (is_admin: false) receive:

  1. platform_viewer role with global scope (scope_id = None)
  2. Grants read-only access to all platform resources
  3. team_admin role with team scope (scope_id = personal team ID)
  4. Grants full management of their personal team resources
  5. Only assigned if personal team creation succeeds

This dual-role approach ensures: - Users always have appropriate global visibility (via platform_admin or platform_viewer) - Users can fully manage their personal team resources (via team-scoped team_admin, when available) - Clear separation between team-level and platform-level permissions

The granted_by field tracks which admin created the user for audit purposes.

Existing Users Migration

An Alembic migration (v1a2b3c4d5e6) automatically updates existing users without roles:

Previous behavior (before migration): - Admin users: Only platform_admin with global scope (scope_id = None) - Non-admin users: Only viewer with team scope (scope_id = None)

After migration:

Admin users receive:

  1. team_admin role with team scope
  2. scope_id = user's personal team ID (from email_team_members table)
  3. Enables management of personal team resources
  4. platform_admin role with global scope
  5. scope_id = None
  6. Maintains unrestricted platform access

Non-admin users receive:

  1. team_admin role with team scope
  2. scope_id = user's personal team ID (from email_team_members table)
  3. Enables management of personal team resources
  4. platform_viewer role with global scope
  5. scope_id = None
  6. Provides read-only access to platform resources

Migration behavior: - Only affects users without existing role assignments - Users without a personal team still receive their global role (team_admin is skipped) - The platform admin (configured via PLATFORM_ADMIN_EMAIL) is excluded to preserve bootstrap configuration - Migration is idempotent and safe to run multiple times

ResourcesΒΆ

Protected entities:

  • Servers (MCP gateways and virtual servers)
  • Tools, Prompts, Resources (MCP primitives)
  • System configuration and audit logs

Token Scoping ModelΒΆ

Token scoping controls what resources a token can access based on the teams claim in the JWT payload. The normalize_token_teams() function is the single source of truth for interpreting JWT team claims across all enforcement points.

Token Scoping ContractΒΆ

The teams claim in JWT tokens determines resource visibility. The system follows a secure-first design: when in doubt, access is denied.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Token Teams Claim Handling                           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  JWT Claim State          β”‚  is_admin: true       β”‚  is_admin: false        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  No "teams" key           β”‚  PUBLIC-ONLY []       β”‚  PUBLIC-ONLY []         β”‚
β”‚  teams: null              β”‚  ADMIN BYPASS (None)  β”‚  PUBLIC-ONLY []         β”‚
β”‚  teams: []                β”‚  PUBLIC-ONLY []       β”‚  PUBLIC-ONLY []         β”‚
β”‚  teams: ["team-id"]       β”‚  Team + Public        β”‚  Team + Public          β”‚
β”‚  teams: ["t1", "t2"]      β”‚  Both Teams + Public  β”‚  Both Teams + Public    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Admin Bypass Requirements

Admin bypass (unrestricted access) requires BOTH conditions:

  1. teams: null (explicit null, not missing key)
  2. is_admin: true

A missing teams key always results in public-only access, even for admins. An empty teams: [] also results in public-only access, even for admins.

Return Value SemanticsΒΆ

Return Value Meaning Query Behavior
None Admin bypass Skip ALL team filtering
[] (empty list) Public-only Filter to visibility='public' ONLY
["t1", "t2"] Team-scoped Filter to team resources + public

Security Design PrinciplesΒΆ

  1. Secure-First Defaults

  2. Missing teams key always returns [] (public-only access)

  3. This prevents accidental exposure when tokens are misconfigured

  4. Explicit Admin Bypass

  5. Admin bypass requires explicit teams: null AND is_admin: true

  6. Empty teams [] disables bypass even for admins

  7. Scoped Automation Tokens

  8. Tokens with teams: [] are intentionally restricted to public resources

  9. Use case: CI/CD pipelines, monitoring systems, public API clients

Token Scoping FlowΒΆ

                                 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                                 β”‚   JWT Token      β”‚
                                 β”‚   Received       β”‚
                                 β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                          β”‚
                                          β–Ό
                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                              β”‚  Extract "teams"      β”‚
                              β”‚  claim from JWT       β”‚
                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                          β”‚
                          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
                          β”‚                               β”‚
                          β–Ό                               β–Ό
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚ "teams" key EXISTS  β”‚       β”‚ "teams" key MISSING β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚                             β”‚
                          β–Ό                             β–Ό
               β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
               β”‚ Check teams value   β”‚       β”‚ Return []           β”‚
               β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚ PUBLIC-ONLY         β”‚
                          β”‚                  β”‚ (secure default)    β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚               β”‚               β”‚
          β–Ό               β–Ό               β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ teams: null   β”‚ β”‚ teams: [] β”‚ β”‚ teams: [...]  β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
          β”‚               β”‚               β”‚
          β–Ό               β”‚               β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚ Check is_adminβ”‚       β”‚       β”‚ Return [...]  β”‚
  β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚       β”‚ TEAM-SCOPED   β”‚
          β”‚               β”‚       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”Œβ”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”         β”‚
    β”‚           β”‚         β”‚
    β–Ό           β–Ό         β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Admin  β”‚ β”‚Non-Adm β”‚ β”‚ Empty  β”‚
β”‚ true   β”‚ β”‚ false  β”‚ β”‚ list   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Return β”‚ β”‚ Return β”‚ β”‚ Return β”‚
β”‚ None   β”‚ β”‚ []     β”‚ β”‚ []     β”‚
β”‚ BYPASS β”‚ β”‚ PUBLIC β”‚ β”‚ PUBLIC β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key Insight

The difference between teams: null and missing teams key is critical:

  • Missing key: Always [] (public-only) - secure default
  • Explicit null: Admin bypass when is_admin: true, otherwise []

Visibility LevelsΒΆ

Resources in MCP Gateway have three visibility levels:

Visibility Description Who Can See
public Accessible to all authenticated users Everyone with valid token
team Accessible to team members only Team members + admins (with bypass)
private Accessible to owner only Resource owner + admins (with bypass)

Access Matrix by Token TypeΒΆ

Token Type Public Resources Team Resources Private Resources
Admin Bypass (teams=null, is_admin=true) βœ… βœ… (all teams) βœ… (all)
Team-Scoped (teams=["t1"]) βœ… βœ… (own team) βœ… (own only)
Public-Only (teams=[]) βœ… ❌ ❌

Public-Only Token Limitations

Public-only tokens (teams=[]) cannot access private resources, even if the resource is owned by the token's user.

This is intentional security behavior - public-only tokens are designed for limited-scope access to public resources only. To access private resources, users must use a team-scoped token that includes their personal team.

# Public-only token behavior:
# βœ… Can access: visibility='public' resources
# ❌ Cannot access: visibility='team' resources (any team)
# ❌ Cannot access: visibility='private' resources (even if owned by user)

Enforcement PointsΒΆ

Token scoping is enforced consistently across all access paths:

Location Token Scoping RBAC Description
Token Scoping Middleware βœ… N/A Request-level data filtering
REST API Endpoints βœ… βœ… @require_permission decorators
RPC Handler (/rpc) βœ… Varies Method-specific permission checks
Admin UI βœ… βœ… Permission-based UI rendering
Service Layer βœ… N/A Database query filtering
WebSocket βœ… βœ… Forwards auth to /rpc
MCP Transport βœ… N/A Streamable HTTP protocol filtering

Token Types and Use CasesΒΆ

Session Tokens (UI Login)ΒΆ

Generated when users log in via the Admin UI:

{
  "sub": "admin@example.com",
  "is_admin": true,
  "teams": null,
  "iss": "mcpgateway",
  "aud": "mcpgateway-api",
  "exp": 1234567890
}

Behavior: Admin session tokens should set teams: null (explicit null) combined with is_admin: true to enable admin bypass (unrestricted access to all resources).

API Tokens (Programmatic Access)ΒΆ

Generated via the Admin UI or API for automation:

{
  "sub": "service-account@example.com",
  "is_admin": false,
  "teams": ["team-uuid-1", "team-uuid-2"],
  "iss": "mcpgateway",
  "aud": "mcpgateway-api",
  "exp": 1234567890
}

Behavior: Access restricted to public resources plus resources owned by specified teams.

Scoped Automation TokensΒΆ

For CI/CD, monitoring, or public API access:

{
  "sub": "ci-pipeline@example.com",
  "is_admin": true,
  "teams": [],  // Explicitly empty = public-only
  "iss": "mcpgateway",
  "aud": "mcpgateway-api",
  "exp": 1234567890
}

Behavior: Even admin tokens with teams: [] are restricted to public resources only. This enables creating limited-scope tokens for automation that shouldn't access team-internal resources.


Generating Scoped TokensΒΆ

Using the CLI ToolΒΆ

# Unrestricted admin token (no teams key)
python3 -m mcpgateway.utils.create_jwt_token \
  --username admin@example.com \
  --exp 60 \
  --secret $JWT_SECRET_KEY

# Team-scoped token
python3 -m mcpgateway.utils.create_jwt_token \
  --username user@example.com \
  --exp 60 \
  --secret $JWT_SECRET_KEY \
  --teams '["team-uuid-1"]'

# Public-only scoped token (for automation)
python3 -m mcpgateway.utils.create_jwt_token \
  --username ci@example.com \
  --exp 60 \
  --secret $JWT_SECRET_KEY \
  --teams '[]'

Using the Admin UIΒΆ

  1. Navigate to Admin UI β†’ Tokens
  2. Click Create Token
  3. Select team scope:

  4. No team selected: Public resources only (secure default)

  5. Specific team(s): Team + public resources

  6. Configure additional restrictions (IP, permissions, expiry)

Token Scope Warning

Tokens created without selecting a team will have access to public resources only. This is the secure default to prevent accidental exposure of team resources.


Permission SystemΒΆ

Permission CategoriesΒΆ

Permissions are defined in the Permissions class and control what actions users can perform:

Category Permissions
Users users.create, users.read, users.update, users.delete, users.invite
Teams teams.create, teams.read, teams.update, teams.delete, teams.join, teams.manage_members
Tools tools.create, tools.read, tools.update, tools.delete, tools.execute
Resources resources.create, resources.read, resources.update, resources.delete, resources.share
Gateways gateways.create, gateways.read, gateways.update, gateways.delete
Prompts prompts.create, prompts.read, prompts.update, prompts.delete, prompts.execute
Servers servers.create, servers.read, servers.update, servers.delete, servers.manage
Tokens tokens.create, tokens.read, tokens.update, tokens.revoke
Admin admin.system_config, admin.user_management, admin.security_audit, admin.overview, admin.dashboard, admin.events, admin.grpc, admin.plugins
A2A a2a.create, a2a.read, a2a.update, a2a.delete, a2a.invoke
Tags tags.read, tags.create, tags.update, tags.delete
Wildcard * (all permissions)

Permission Checking FlowΒΆ

@require_permission("resource.action")
    β”‚
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Extract user_context        β”‚ ← From request/kwargs
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Plugin Permission Hook      β”‚ ← HTTP_AUTH_CHECK_PERMISSION can override
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚ (no plugin decision)
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Admin Bypass Check          β”‚ ← If allow_admin_bypass=True AND user.is_admin
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚ (not admin or bypass disabled)
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Role Collection             β”‚ ← Get all active roles for user
β”‚ - Global scope roles        β”‚
β”‚ - Personal scope roles      β”‚
β”‚ - Team scope roles          β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Permission Aggregation      β”‚ ← Collect permissions from roles
β”‚ - Include inherited perms   β”‚   (role inheritance supported)
β”‚ - Check for wildcard (*)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚
    β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Fallback Permission Check   β”‚ ← Implicit permissions (see below)
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    β”‚
    β–Ό
  GRANT or DENY

Fallback PermissionsΒΆ

The system grants implicit permissions without explicit role assignment. These are not shown in /rbac/my/permissions but are effective:

User Context Implicit Permissions
Any authenticated user teams.create, teams.read
Team member teams.read (for their teams)
Team owner teams.read, teams.update, teams.delete, teams.manage_members
Any authenticated user tokens.* (for own tokens only)

Why Fallback Permissions Exist

Fallback permissions enable basic functionality without requiring explicit role assignment:

  • Users can always create and view teams they belong to
  • Team owners automatically have management rights
  • Users can always manage their own API tokens

Admin API RBACΒΆ

The Admin API enforces strict RBAC where even users with is_admin: true must have explicit permissions granted. This enables delegated administration - granting specific admin capabilities without full superuser access.

Key behaviors:

Aspect Behavior
Admin bypass allow_admin_bypass=False on all admin routes
is_admin flag Does NOT bypass permission checks
UI entry Requires any admin.* permission via has_admin_permission()
Route protection All 177 admin routes use @require_permission decorators

Example: Delegated Server Management

{
  "role": "server-manager",
  "permissions": [
    "servers.read",
    "servers.create",
    "servers.update",
    "servers.delete"
  ]
}

A user with this role can:

  • βœ… Access /admin/servers/* endpoints
  • βœ… View the Admin UI (has servers.* which satisfies has_admin_permission())
  • ❌ Access /admin/tools/* endpoints (no tools.* permissions)
  • ❌ Access /admin/gateways/* endpoints (no gateways.* permissions)

Platform Admin Role

The built-in platform_admin role has ["*"] (wildcard) permissions, which grants access to all operations. For delegated administration, create custom roles with specific permission sets.


Configuration SafetyΒΆ

Development vs Production SettingsΒΆ

The following configuration combinations require careful consideration:

Setting Value Impact Recommended Use
AUTH_REQUIRED false All requests granted admin access Development only
TRUST_PROXY_AUTH true + MCP_CLIENT_AUTH_ENABLED=false Trust X-Forwarded-User header without verification Behind trusted reverse proxy only

Proxy Authentication ModeΒΆ

When MCP_CLIENT_AUTH_ENABLED=false and TRUST_PROXY_AUTH=true:

  • The gateway trusts the X-Forwarded-User header from upstream proxy
  • No JWT validation or database verification is performed
  • Only use when deployed behind a trusted reverse proxy that handles authentication

Security Warning

Proxy authentication mode should only be used in trusted network environments where the reverse proxy is the only entry point to the gateway. Exposing the gateway directly to untrusted networks with this configuration allows header injection attacks.

Anonymous Mode (AUTH_REQUIRED=false)ΒΆ

When AUTH_REQUIRED=false:

  • All unauthenticated requests receive platform-admin context
  • Never use in production - all users have full admin access
  • Intended only for local development and testing

Production Warning

Setting AUTH_REQUIRED=false in production grants administrative access to all requests. This completely bypasses authentication and authorization.


Best PracticesΒΆ

Token LifecycleΒΆ

  1. Use short expiration times for interactive sessions (hours)
  2. Use longer expiration for service accounts with IP restrictions
  3. Rotate tokens regularly (recommended: 90 days for long-lived tokens)
  4. Revoke tokens immediately when access should be removed

Team OrganizationΒΆ

  1. Create purpose-specific teams:

  2. platform-admins - Full administrative access

  3. developers - Development and testing resources
  4. ci-automation - CI/CD pipeline access
  5. monitoring - Read-only observability access

  6. Map SSO groups to teams for automatic membership management

  7. Use personal teams for individual resource ownership

Scoping StrategyΒΆ

Use Case Recommended Token Scope
Admin UI access Session token (teams: null + is_admin: true)
CI/CD pipeline teams: [] (public-only)
Service integration Specific team(s)
Developer access Personal team + project teams
Monitoring/alerting teams: [] with read permissions

TroubleshootingΒΆ

Token Not Seeing Expected ResourcesΒΆ

  1. Check token claims: Decode the JWT to verify teams claim

    # Decode JWT payload (middle section)
    echo "$TOKEN" | cut -d. -f2 | base64 -d | jq .
    

  2. Verify resource visibility: Check the resource's visibility and team_id

    curl -H "Authorization: Bearer $ADMIN_TOKEN" /tools/{id} | jq '{visibility, teamId}'
    

  3. Check user admin status: Non-admin users without teams get public-only access

Admin Token Being RestrictedΒΆ

If an admin token is unexpectedly restricted:

  1. Check for explicit teams claim: teams: [] restricts even admins
  2. Verify is_admin flag: Must be true in JWT or database user
  3. Check middleware logs: Look for "token_teams" in debug output

Inconsistent Results Between EndpointsΒΆ

If REST and RPC endpoints return different results:

  1. Check for caching: REST list endpoints may have cached data
  2. Wait for cache TTL: Default is 60 seconds for registry cache
  3. Use direct GET: /tools/{id} bypasses list cache

Bootstrap Custom RolesΒΆ

MCP Gateway allows you to define custom roles that are automatically created during database bootstrap. This is useful for organizations that need to pre-configure roles before deployment.

ConfigurationΒΆ

Enable custom role bootstrapping with these environment variables:

Variable Default Description
MCPGATEWAY_BOOTSTRAP_ROLES_IN_DB_ENABLED false Enable loading additional roles from file
MCPGATEWAY_BOOTSTRAP_ROLES_IN_DB_FILE additional_roles_in_db.json Path to the JSON file containing role definitions

Role Definition FormatΒΆ

Create a JSON file containing an array of role definitions:

[
  {
    "name": "data_analyst",
    "description": "Read-only access for data analysis",
    "scope": "team",
    "permissions": ["tools.read", "resources.read", "prompts.read"],
    "is_system_role": true
  },
  {
    "name": "auditor",
    "description": "Compliance audit access",
    "scope": "global",
    "permissions": ["tools.read", "resources.read", "prompts.read", "servers.read", "gateways.read"],
    "is_system_role": true
  }
]

Required fields:

  • name - Unique role name
  • scope - Either team (team-level access) or global (system-wide access)
  • permissions - Array of permission strings (e.g., tools.read, resources.create)

Optional fields:

  • description - Human-readable description
  • is_system_role - Set to true to prevent users from modifying/deleting the role

Available PermissionsΒΆ

Resource Permissions
Tools tools.create, tools.read, tools.update, tools.delete, tools.execute
Resources resources.create, resources.read, resources.update, resources.delete
Prompts prompts.create, prompts.read, prompts.update, prompts.delete
Servers servers.create, servers.read, servers.update, servers.delete
Gateways gateways.create, gateways.read, gateways.update, gateways.delete
Teams teams.create, teams.read, teams.update, teams.delete, teams.join

Docker Compose ExampleΒΆ

services:
  gateway:
    environment:
      - MCPGATEWAY_BOOTSTRAP_ROLES_IN_DB_ENABLED=true
      - MCPGATEWAY_BOOTSTRAP_ROLES_IN_DB_FILE=/app/custom_roles.json
    volumes:
      - ./custom_roles.json:/app/custom_roles.json:ro

Kubernetes/Helm ExampleΒΆ

# values.yaml
mcpContextForge:
  env:
    MCPGATEWAY_BOOTSTRAP_ROLES_IN_DB_ENABLED: "true"
    MCPGATEWAY_BOOTSTRAP_ROLES_IN_DB_FILE: "/config/custom_roles.json"

  # Mount ConfigMap with role definitions
  extraVolumes:
    - name: custom-roles
      configMap:
        name: mcp-gateway-roles
  extraVolumeMounts:
    - name: custom-roles
      mountPath: /config

Error HandlingΒΆ

  • File not found: Bootstrap continues with default roles only; warning logged
  • Invalid JSON: Bootstrap continues with default roles only; error logged
  • Malformed entries: Invalid role entries are skipped with warnings; valid entries are processed

Idempotent Bootstrap

Bootstrap is idempotent - running it multiple times won't duplicate roles. Existing roles are detected and skipped.