Skip to content

[SECURITY FEATURE]: Role-Based Access Control (RBAC) - User/Team/Global Scopes for full multi-tenancy supportΒ #283

@crivetimihai

Description

@crivetimihai

🧭 Epic

WARNING: MAJOR FEATURE - NEEDS DISCUSSION / ARCHITECTURAL DECISION / WORKSHOPS BEFORE IMPLEMENTING

Title: Role-Based Access Control (RBAC) - User/Team/Global Scopes
Goal: Implement full multi-tenant architecture with private, team, and global catalogs for all MCP Gateway resources
Why now: Current implementation shares all resources globally. Multi-tenancy enables secure isolation for enterprises, teams, and individual users while maintaining collaboration through controlled sharing.

Depends on:


🧭 Type of Feature

  • Enhancement to existing functionality
  • New feature or capability
  • Security Related (requires review)
  • Major Architecture Change (multi-tenancy)

πŸ™‹β€β™‚οΈ User Story 1 β€” Private Catalog Management

As a: registered user
I want: my own private catalog of tools, resources, prompts, and virtual servers
So that: I can develop and test configurations without affecting others

βœ… Acceptance Criteria

Scenario: Create private virtual server
  Given I am logged in as user "alice"
  When I create a virtual server in my private catalog
  Then only I can see and access this server
  And it does not appear in team or global catalogs
  And my JWT tokens can access my private resources

πŸ™‹β€β™‚οΈ User Story 2 β€” Team Collaboration

As a: team owner
I want: to create shared team catalogs and manage member access
So that: my team can collaborate on MCP configurations while maintaining isolation from other teams

βœ… Acceptance Criteria

Scenario: Team owner shares private resource to team
  Given I am team owner of "data-science-team"
  And I have a private tool "custom-analyzer"
  When I share this tool to my team catalog
  Then all team members can see and use "custom-analyzer"
  But users outside my team cannot access it

Scenario: Team member management
  Given I am team owner of "data-science-team"
  When I invite "[email protected]" as a member
  Then Bob receives access to team catalog resources
  And when I promote Bob to owner
  Then Bob can invite new members and share resources

πŸ™‹β€β™‚οΈ User Story 3 β€” Global Resource Publishing

As a: platform admin or team owner
I want: to publish resources from team catalogs to the global catalog
So that: proven tools and configurations can be shared across the entire organization

βœ… Acceptance Criteria

Scenario: Publish team resource globally
  Given I am owner of team "platform-team"
  And we have a team tool "company-auth-server"
  When I publish this tool to the global catalog
  Then all users across all teams can discover and use it
  And the original team retains ownership for updates

πŸ™‹β€β™‚οΈ User Story 4 β€” Scope-Aware API Access

As a: API client
I want: to access resources based on my user/team context and permissions
So that: I only see resources I'm authorized to use

βœ… Acceptance Criteria

Scenario: Scoped resource listing
  Given I authenticate as user "alice" in team "data-science"
  When I call GET /tools
  Then I see tools from:
    - My private catalog
    - My team "data-science" catalog  
    - The global catalog
  But I do not see resources from other teams

Scenario: Cross-scope access denial
  Given I have a token scoped to team "marketing"
  When I try to access a private tool owned by user "alice"
  Then I receive 403 Forbidden with scope violation error

πŸ“ Design Sketch

Catalog Hierarchy:

Global Catalog (visible to all users)
β”œβ”€β”€ Team: data-science
β”‚   β”œβ”€β”€ Member: alice (can read team resources)
β”‚   └── Owner: bob (can manage team, share resources)
β”‚       └── Private: bob's personal catalog
└── Team: platform
    β”œβ”€β”€ Owner: charlie
    └── Private: charlie's personal catalog

Database Schema Extensions:

-- User management
CREATE TABLE users (
  id UUID PRIMARY KEY,
  username VARCHAR(255) UNIQUE NOT NULL,
  email VARCHAR(255) UNIQUE NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);

-- Team management  
CREATE TABLE teams (
  id UUID PRIMARY KEY,
  name VARCHAR(255) NOT NULL,
  description TEXT,
  created_by UUID REFERENCES users(id),
  created_at TIMESTAMP DEFAULT NOW()
);

-- Team membership with roles
CREATE TABLE team_members (
  id UUID PRIMARY KEY,
  team_id UUID REFERENCES teams(id) ON DELETE CASCADE,
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  role ENUM('owner', 'member') DEFAULT 'member',
  joined_at TIMESTAMP DEFAULT NOW(),
  UNIQUE(team_id, user_id)
);

-- Extend ALL existing tables with scope fields
ALTER TABLE tools ADD COLUMN scope_type ENUM('private', 'team', 'global') DEFAULT 'global';
ALTER TABLE tools ADD COLUMN scope_owner UUID REFERENCES users(id);
ALTER TABLE tools ADD COLUMN scope_team UUID REFERENCES teams(id);

-- Apply same pattern to: servers, resources, prompts, gateways, roots

JWT Scope Claims Structure:

{
  "sub": "[email protected]",
  "jti": "uuid-here", 
  "exp": 1234567890,
  "user_id": "user-uuid",
  "teams": [
    {"id": "team-uuid", "name": "data-science", "role": "member"},
    {"id": "team-uuid-2", "name": "platform", "role": "owner"}
  ],
  "scope": {
    "type": "user", // or "team", "global"
    "target": "user-uuid" // or team-uuid
  }
}

Resource Sharing Workflow:

def share_to_team(resource_id: str, from_scope: str, to_team_id: str, user: User):
    # Verify user owns resource in private scope
    # Or user is team owner if sharing from team to global
    # Create new resource entry with team scope
    # Maintain ownership chain for updates

πŸ”— MCP Standards Check

  • Does not affect MCP spec
  • No protocol impact
  • If deviations exist, please describe them below:

Multi-tenancy operates at the Gateway authorization layer, transparent to MCP protocol. Clients see filtered resource catalogs based on their scope permissions.


πŸ–ΌοΈ Architecture Diagrams

1 β€” Scope & Catalog Hierarchy

graph TD
    %% Global level
    G["🌐 Global Catalog"]:::global

    %% Team level
    subgraph "Team Catalogs"
        direction TB
        T1["πŸ§‘β€πŸ€β€πŸ§‘ Team Β«data-scienceΒ» Catalog"]:::team
        T2["πŸ§‘β€πŸ€β€πŸ§‘ Team Β«platformΒ» Catalog"]:::team

        %% Private beneath each team
        subgraph "Private Catalogs – data-science"
            direction TB
            A1["πŸ™‹β€β™€οΈ Alice Private"]:::private
            B1["πŸ™‹β€β™‚οΈ Bob Private"]:::private
        end

        subgraph "Private Catalogs – platform"
            direction TB
            C1["πŸ™‹β€β™‚οΈ Charlie Private"]:::private
        end
    end

    %% Sharing flows
    A1 -->|share| T1
    B1 -->|share| T1
    C1 -->|share| T2
    T1 -->|publish| G
    T2 -->|publish| G

    classDef global  fill:#0057b7,color:#fff,font-weight:bold;
    classDef team    fill:#39c0ed,color:#000;
    classDef private fill:#d3f4ff,color:#000;
Loading

2 β€” Request Flow (sequence)

sequenceDiagram
    participant Client as API Client  
    participant Gateway as MCP Gateway  
    participant Auth as Auth Middleware  
    participant Scope as ScopeResolver  
    participant Catalog as Catalog Service  
    participant DB as DB (RLS/filters)  

    Client->>Gateway: HTTP request + JWT  
    Gateway->>Auth: extract   / verify token  
    Auth-->>Gateway: user_id, teams, scope  
    Gateway->>Scope: resolve(request, ctx)  
    Scope-->>Catalog: list_tools(scope_ctx)  
    Catalog->>DB: SELECT … WHERE scope_type IN (…)  
    DB-->>Catalog: scoped resultset  
    Catalog-->>Gateway: filtered resources  
    Gateway-->>Client: response (200/403)  
Loading

3 β€” Database (Core RBAC Entities)

erDiagram
    USERS      ||--o{ TEAM_MEMBERS : has
    TEAMS      ||--o{ TEAM_MEMBERS : includes
    USERS      ||--o{ TOOLS        : owns_private
    TEAMS      ||--o{ TOOLS        : owns_team
    TOOLS      }o--|| GATEWAYS     : originates
    TOOLS      }o--|| SERVERS      : composed_by

    USERS {
        UUID   id PK
        varchar username
        varchar email
    }
    TEAMS {
        UUID   id PK
        varchar name
        text   description
        UUID   created_by FK
    }
    TEAM_MEMBERS {
        UUID id PK
        UUID user_id FK
        UUID team_id FK
        enum role
    }
    TOOLS {
        int   id PK
        varchar name
        enum  scope_type
        UUID  scope_owner FK
        UUID  scope_team  FK
    }
Loading

4 β€” JWT Scope Claims Mapping

flowchart LR
    subgraph JWT Claims
        direction TB
        sub(("sub"))
        uid(("user_id"))
        teams(("teams[]"))
        scopeClaim(("scope"))
    end
    
    sub -->|identifies| ClientUser[Client User]
    uid --> AuthService[(Auth Service)]
    teams --> ScopeResolver
    scopeClaim --> ScopeResolver
    ScopeResolver --> Catalog[Catalog Filters]
Loading

These diagrams collectively cover scope hierarchy, runtime request flow, schema relationships, and token→scope resolution, providing a complete architectural view for the RBAC multi-tenancy feature.


πŸ”„ Alternatives Considered

Approach Pros Cons Decision
Database-per-tenant Perfect isolation Complex deployment, backup ❌ Too complex
Row-level security Database-enforced PostgreSQL-specific ❌ Limits DB options
Application-level scoping Database agnostic, flexible More complex code βœ… Selected
External IAM integration Enterprise-ready Dependency overhead πŸ”„ Future phase

πŸ““ Additional Context

Scope Resolution Order:

  1. Private: User's personal resources (highest priority)
  2. Team: Resources shared within user's teams
  3. Global: Organization-wide shared resources

Permission Matrix:

Role Private Read Private Write Team Read Team Write Team Manage Global Read Global Publish
User Own only Own only If member ❌ ❌ βœ… ❌
Team Member Own only Own only Team resources ❌ ❌ βœ… ❌
Team Owner Own only Own only Team resources βœ… βœ… βœ… Team resources
Platform Admin All All All All All All All

Migration Strategy:

  • Phase 1: Add scope columns with default 'global' (backward compatible)
  • Phase 2: Implement user/team management
  • Phase 3: Add private catalog functionality
  • Phase 4: Enable team collaboration and sharing

🧭 Tasks

  • Phase 1: Database & Schema

    • Create users, teams, team_members tables with proper relationships
    • Add scope columns (scope_type, scope_owner, scope_team) to all resource tables:
      • tools, servers, resources, prompts, gateways, roots
    • Create migration scripts with default scope_type='global' for existing data
    • Add database indexes for efficient scope-based queries
  • Phase 2: User & Team Management

    • Implement user registration/authentication system
    • Create team management service:
      • create_team(), invite_member(), remove_member(), promote_to_owner()
    • Add team management API endpoints:
      • POST /teams β€” create team
      • GET /teams β€” list user's teams
      • POST /teams/{id}/members β€” invite member
      • PUT /teams/{id}/members/{user_id} β€” change role
      • DELETE /teams/{id}/members/{user_id} β€” remove member
  • Phase 3: Scope-Aware Resource Management

    • Extend all service classes with scope filtering:
      • ToolService.list_tools(user_context) β€” filter by scope permissions
      • ServerService.create_server(data, scope_context) β€” create in appropriate scope
    • Implement resource sharing service:
      • share_to_team(resource, from_scope, to_team)
      • publish_to_global(resource, from_scope)
    • Update all API endpoints with scope context:
      • Add ?scope=private|team:{id}|global query parameter
      • Filter results based on user's scope permissions
  • Phase 4: Authentication & Authorization

    • Extend JWT token creation with user/team context
    • Update require_auth() to extract and validate scope permissions
    • Create scope-checking decorators:
      • @require_scope_access(scope_type, scope_id)
      • @require_team_role(team_id, min_role)
    • Implement comprehensive access control for all endpoints
  • Phase 5: Admin UI Overhaul

    • Add scope selector to top navigation (Private | Team: X | Global)
    • Create team management interface:
      • Team list, create team, member management
      • Role assignment and team settings
    • Add resource sharing workflows:
      • "Share to Team" buttons on private resources
      • "Publish to Global" for team owners
      • Ownership indicators and sharing status
    • Update all resource listing pages with scope filtering
  • Phase 6: Advanced Features

  • Phase 7: Testing & Documentation

    • Comprehensive unit tests for scope resolution logic
    • Integration tests for multi-tenant scenarios
    • Security tests for cross-scope access prevention
    • Performance tests for scope-filtered queries
    • Complete documentation overhaul with multi-tenancy examples
    • Migration guide for existing single-tenant deployments

πŸ“– References


🧩 Additional Notes

  • 🚨 Breaking Change: This is a major architectural shift requiring careful migration planning
  • Performance Impact: Scope filtering on all queries requires optimized database indexes
  • Security Critical: Improper scope isolation could lead to data leakage between tenants
  • Phased Rollout: Implement incrementally to maintain stability and allow testing
  • Backward Compatibility: Existing single-tenant deployments should continue working with global scope
  • Future Extensions: Foundation for enterprise features like SSO, advanced RBAC, compliance auditing

Sub-issues

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestsecurityImproves securitytriageIssues / Features awaiting triage

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions