# Multi-Tenancy Architecture

ContextForge implements a comprehensive multi-tenant architecture that provides secure isolation, flexible resource sharing, and granular access control. This document describes the complete multi-tenancy design, user lifecycle, team management, and resource scoping mechanisms.

## Overview

The multi-tenancy system is built around **teams as the primary organizational unit**, with users belonging to one or more teams, and all resources scoped to teams with configurable visibility levels.

### Core Principles

1. **Team-Centric**: Teams are the fundamental organizational unit for resource ownership and access control
2. **User Flexibility**: Users can belong to multiple teams with different roles in each team
3. **Resource Isolation**: Resources are scoped to teams with explicit sharing controls
4. **Invitation-Based**: Team membership is controlled through invitation workflows
5. **Role-Based Access**: Users have roles (Owner, Member) within teams that determine their capabilities
6. **Platform Administration**: Separate platform-level administration for system management

---

## User Lifecycle & Authentication

### User Authentication Flow

```mermaid
sequenceDiagram
    participant U as User
    participant G as Gateway
    participant SSO as SSO Provider
    participant DB as Database
    participant E as Email Service

    alt Email Authentication
        U->>G: POST /auth/email/login
        G->>DB: Validate email/password
        DB-->>G: User record
        G-->>U: JWT token + session
    else SSO Authentication
        U->>G: GET /auth/sso/login/github
        G->>SSO: OAuth redirect
        U->>SSO: Authorize application
        SSO->>G: OAuth callback with code
        G->>SSO: Exchange code for token
        SSO-->>G: User profile data
        G->>DB: Create/update user
        G->>DB: Create personal team
        G-->>U: JWT token + session
    end

    Note over G,DB: Personal team auto-created for new users
```

### User Creation & Personal Teams

When personal teams are enabled (default), each user gets an automatically created **Personal Team** upon registration:

```mermaid
flowchart TD
    A[New User Registration] --> B{Authentication Method}

    B -->|Email| C[Email Registration]
    B -->|SSO| D[SSO Registration]

    C --> E[Create EmailUser Record]
    D --> F[Create SSO User Record]

    E --> G[Create Personal Team<br/>if enabled]
    F --> G

    G --> H[Set User as Team Owner]
    H --> I[User Can Access System]

    subgraph "Personal Team Properties"
        J[Name: Display Name's Team<br/>full name or email prefix]
        K[is_personal: true]
        L[Owner: User]
        M[Members: User only]
        N[Visibility: private]
    end

    G --> J
    G --> K
    G --> L
    G --> M
    G --> N

    style G fill:#e1f5fe
    style J fill:#f3e5f5
    style K fill:#f3e5f5
    style L fill:#f3e5f5
    style M fill:#f3e5f5
    style N fill:#f3e5f5
```

---

## Team Architecture & Management

### Team Structure & Roles

```mermaid
erDiagram
    EmailTeam ||--o{ EmailTeamMember : has
    EmailUser ||--o{ EmailTeamMember : belongs_to
    EmailTeam ||--o{ EmailTeamInvitation : has_pending
    EmailUser ||--o{ EmailTeamInvitation : invited_by
    EmailTeam ||--o{ EmailTeamJoinRequest : join_requests
    EmailUser ||--o{ EmailTeamJoinRequest : requested_by

    EmailTeam {
        string id PK
        string name
        string slug
        string description
        string created_by FK
        boolean is_personal
        string visibility
        int max_members
        boolean is_active
        timestamp created_at
        timestamp updated_at
    }

    EmailUser {
        string email PK
        string full_name
        boolean is_admin
        boolean is_active
        string auth_provider
        timestamp created_at
        timestamp updated_at
    }

    EmailTeamMember {
        string id PK
        string team_id FK
        string user_email FK
        string role
        string invited_by FK
        timestamp joined_at
        boolean is_active
    }

    EmailTeamInvitation {
        string id PK
        string team_id FK
        string email
        string role
        string invited_by FK
        timestamp invited_at
        timestamp expires_at
        string token
        boolean is_active
    }

    EmailTeamJoinRequest {
        string id PK
        string team_id FK
        string user_email FK
        string status
        timestamp requested_at
        timestamp expires_at
        timestamp reviewed_at
        string reviewed_by FK
    }
```

### Team Visibility & Access Model

```mermaid
flowchart TB
    subgraph "Team Visibility Types"
        T1["Private Team
Not discoverable; invite-only"]
        T2["Public Team
Discoverable; membership by invite/request"]
    end

    subgraph "Team Roles"
        R1["Owner

- Full team control
- Invite/remove members
- Manage resources
- Delete team"]
        R2["Member

- Access team resources
- Create resources
- No member management"]
    end

    subgraph "Team Membership Flow"
        A[User Exists] --> B{Team Type}
        B -->|Private| C[Requires Invitation]
        B -->|Public| D[Discover and Request Join]

        C --> E[Owner Sends Invite]
        E --> F[Pending Invitation]
        F --> G[User Accepts/Declines]

        D --> J[Join Request Pending]
        J --> K[Owner Approves]
        K --> H[User Joins Team]
        G -->|Accept| H
        H --> I[Team Member]
    end

    style T1 fill:#ffebee
    style T2 fill:#e8f5e8
    style R1 fill:#fff3e0
    style R2 fill:#f3e5f5
```

#### Team Membership Levels (Design)

**Note**: These are team membership levels, separate from RBAC roles. A user can have both a membership level and RBAC role assignments within the same team.

- **Owner** (Team Membership Level):

  - Manage team settings (name, description, visibility) and lifecycle (cannot delete personal teams).
  - Manage membership (invite, accept, change roles, remove members).
  - Full control over team resources (create/update/delete), subject to platform policies.

- **Member** (Team Membership Level):

  - Access and use team resources; can create resources by default unless policies restrict it.
  - Cannot manage team membership or team‑level settings.

**Platform Admin** is a global RBAC role (not a team membership level) with system‑wide oversight.

### Team Invitation Workflow

```mermaid
sequenceDiagram
    participant O as Team Owner
    participant G as Gateway
    participant DB as Database
    participant I as Invited User

    Note over O,I: Invitation Process
    O->>G: POST /teams/{team_id}/invitations
    Note right of O: {email, role, expires_in}

    G->>DB: Check team ownership
    DB-->>G: Owner confirmed

    G->>DB: Create invitation record
    DB-->>G: Invitation token generated

    G-->>O: Invitation created (token)
    O-->>I: Share invite link (out of band)

    Note over I,G: Acceptance Process
    I->>G: POST /teams/invitations/{token}/accept
    G->>DB: Validate token + user
    G->>DB: Create team membership
    G->>DB: Deactivate invitation (is_active=false)
    G-->>I: Welcome to team
```

---

## Visibility Semantics

This section clarifies what Private and Public mean for teams, and what Private/Team/Public mean for resources across the system.

### Team Visibility (Design)

- Private:

  - Discoverability: Not listed to non‑members; only visible to members/owner.
  - Membership: By invitation from a team owner (request‑to‑join is not exposed to non‑members).
  - API/UI: Team shows up only in the current user's teams list; direct deep links require membership.

- Public:

  - Discoverability: Listed in public team discovery views for all authenticated users.
  - Membership: Still requires an invitation or explicit approval of a join request.
  - API/UI: Limited metadata may be visible without membership; all management and resource operations still require membership.

Note: Platform Admin is a global role and is not a team role. Admins can view/manage teams for operational purposes irrespective of team visibility.

### Resource Visibility (Design)

Applies to Tools, Servers, Resources, Prompts, and A2A Agents. All resources are owned by a team (team_id) and created by a user (owner_email).

- Private:

  - Who sees it: Only the resource owner (owner_email), and only when their token is not a public-only token (token_teams must be non-empty).
  - Team members cannot see or use it unless they are the owner.
  - Public-only tokens (token_teams=[]) cannot access private resources even as the owner.
  - Platform Admin bypass (token teams=null, is_admin=true) does NOT grant visibility or mutation access to another user's private resources (see [#4341](https://github.com/IBM/mcp-context-forge/pull/4341)). Since direct-ID reads return 404 under bypass, update/delete of another user's private resource also fails because the read-before-write gate hides the resource.
  - Mutations: Only the owner. If cross-user administrative access is required, prefer changing visibility to `team` or using a properly scoped token; admin bypass alone is insufficient.

- Team:

  - Who sees it: All members of the owning team (owners and members).
  - Mutations: Owner can update/delete; team owners can administratively manage; Platform Admin can override.

- Public:

  - Who sees it: All authenticated users across the platform (cross‑team visibility).
  - Mutations: Only the resource owner, team owners, or Platform Admins can modify/delete.

Enforcement summary:

- Listing queries include resources where (a) visibility == public, (b) team_id ∈ token_teams with visibility ∈ {team, public}, or (c) owner_email == user.email with visibility == private.
- Owner-based access applies only to private resources. Owners cannot use ownership to bypass team scoping for team-visibility resources outside their token scope.
- Public-only tokens (token_teams=[]) see only public resources — owner and team access are both suppressed.
- Read follows the same rules as list; write operations require ownership or delegated/team administrative rights.

---

## Token Scoping Model

### How Token Scoping Works

Token scoping is the mechanism that determines which resources a user can **see** based on the `teams` claim in their JWT token.

**Key functions** in `mcpgateway/auth.py`:

- `normalize_token_teams()` — The **single source of truth** for interpreting JWT team claims into a canonical form. Used directly by API tokens and legacy tokens.
- `resolve_session_teams()` — The **single policy point** for session-token team resolution. Teams are always resolved from the database first so that revoked memberships take effect immediately. If the JWT carries a non-empty `teams` claim, the result is narrowed to the intersection of DB teams and JWT teams — letting callers scope a session to a subset of their memberships without risking stale grants. If the intersection is empty (e.g. all JWT-claimed teams have been revoked), an empty list is returned, which demotes the session to public-only scope — the user can still access public resources and their own private resources, but loses access to all team-scoped resources. An explicit `teams: []` (empty list) in the JWT is treated as "no restriction requested" and returns the full DB membership — this intentionally differs from `normalize_token_teams` where `[] → public-only`. If `email` is `None` or empty, returns `[]` (public-only) — an identity-less session token never receives admin bypass.
- `_narrow_by_jwt_teams()` — Shared intersection logic used by `resolve_session_teams`.
- API tokens and legacy tokens always use `normalize_token_teams()` directly.

### Secure-First Defaults

The token scoping system follows a **secure-first design principle**: when in doubt, access is restricted.

#### API Tokens and Legacy Tokens

These use `normalize_token_teams()` directly — the JWT `teams` claim is the sole authority:

| JWT `teams` State | `is_admin` | Result | Access Level |
|-------------------|------------|--------|--------------|
| Key MISSING | any | `[]` | PUBLIC-ONLY (secure default) |
| Key `null` | `true` | `None` | ADMIN BYPASS (unrestricted) |
| Key `null` | `false` | `[]` | PUBLIC-ONLY (no bypass) |
| Key `[]` (empty) | any | `[]` | PUBLIC-ONLY |
| Key `["t1", "t2"]` | any | `["t1", "t2"]` | TEAM-SCOPED |

#### Session Tokens

Session tokens use `resolve_session_teams()` — the **database** is the authority, and the JWT `teams` claim only narrows (never broadens) the result:

| JWT `teams` State | DB Teams | Result | Access Level |
|-------------------|----------|--------|--------------|
| Key MISSING | `["t1", "t2"]` | `["t1", "t2"]` | Full DB membership |
| Key `null` | `["t1", "t2"]` | `["t1", "t2"]` | Full DB membership |
| Key `[]` (empty) | `["t1", "t2"]` | `["t1", "t2"]` | Full DB membership (no restriction requested) |
| Key `["t1"]` | `["t1", "t2"]` | `["t1"]` | Narrowed to intersection |
| Key `["revoked"]` | `["t1", "t2"]` | `[]` | Empty intersection — public-only (fail-closed) |
| any | `None` (admin) | `None` | ADMIN BYPASS (DB authority) |

!!! warning "Critical Security Behavior"
    A **missing** `teams` key always results in public-only access for API/legacy tokens, even for admin users. Admin bypass requires **explicit** `teams: null` combined with `is_admin: true`.

!!! note "Session Token Membership Staleness"
    Session tokens skip the `_check_team_membership` re-validation in the token scoping middleware because `resolve_session_teams()` already resolved membership from the database. Membership staleness is bounded by the `auth_cache` TTL. The cache stores the full DB membership (not the per-session narrowed intersection) so that multiple sessions for the same user narrow independently.

!!! note "External IdP tokens resolve via the session table"
    Access tokens accepted from trusted external SSO providers (`SSO_API_TOKEN_AUTH_ENABLED`) are JIT-provisioned into a `token_use="session"` identity and follow the **Session Tokens** row above: `is_admin` and `teams` come from the persisted local user record via `resolve_session_teams()`, not from claims in the external token. See [SSO: Machine-to-machine API auth with external IdP tokens](../manage/sso.md#machine-to-machine-api-auth-with-external-idp-tokens).

### Multi-Team Token Behavior

When a token contains multiple teams (`teams: ["team-1", "team-2"]`):

- **Listing queries**: Return resources visible to ANY of the user's teams
- **`request.state.team_id`**: Set to the **first** team in the list
- **Resource creation**: Uses `request.state.team_id` as default if not explicitly specified

This means the first team in the token's teams array has special significance for default resource ownership.

### Return Value Semantics

The `normalize_token_teams()` and `resolve_session_teams()` functions return:

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

---

## Two-Layer Security Model

ContextForge implements two distinct security layers that work together:

### Layer 1: Token Scoping (Data Filtering)

**Purpose**: Controls what resources a user CAN SEE

- Enforced by `TokenScopingMiddleware` and service layer queries
- Based on the `teams` claim in the JWT token
- Filters database queries based on visibility rules
- Applied **before** RBAC checks

### Layer 2: RBAC (Action Authorization)

**Purpose**: Controls what actions a user CAN DO

- Enforced by `@require_permission` decorators
- Based on role assignments and permission sets
- Checks if user has permission for the requested action
- Applied **after** token scoping passes

### Request Flow

```
Request: POST /tools/{id}/execute
    │
    ▼
┌─────────────────────────────┐
│ Token Scoping (Layer 1)     │ ← Can user ACCESS this tool?
│ - Check visibility          │   (public/team/private)
│ - Check team membership     │
└─────────────────────────────┘
    │ (allowed)
    ▼
┌─────────────────────────────┐
│ RBAC Permission (Layer 2)   │ ← Can user EXECUTE tools?
│ @require_permission(        │   (tools.execute permission)
│   "tools.execute")          │
└─────────────────────────────┘
    │ (allowed)
    ▼
┌─────────────────────────────┐
│ Execute Operation           │
└─────────────────────────────┘
```

### Enforcement Points Matrix

| Location | Token Scoping | RBAC | Notes |
|----------|--------------|------|-------|
| Token Scoping Middleware | ✅ | N/A | Request-level data filtering |
| REST API Endpoints | ✅ | ✅ | `@require_permission` decorators |
| RPC Handler (`/rpc`) | ✅ | Varies | Method-specific checks |
| Admin UI | ✅ | ✅ | Permission-based rendering |
| Service Layer | ✅ | N/A | Database query filtering |
| WebSocket | ✅ | ✅ | Forwards auth to /rpc |
| MCP Transport | ✅ | N/A | Streamable HTTP filtering + per-server OAuth enforcement |

---

## Resource Scoping & Visibility

### Resource Architecture

All resources in ContextForge are scoped to teams with three visibility levels:

```mermaid
flowchart TD
    subgraph "Resource Types"
        A[MCP Servers]
        B[Virtual Servers]
        C[Tools]
        D[Resources]
        E[Prompts]
        F[A2A Agents]
    end

    subgraph "Team Scoping"
        G[team_id: UUID]
        H[owner_email: string]
        I[visibility: enum]
    end

    subgraph "Visibility Levels"
        J["Private
Owner only"]
        K["Team
Team members"]
        L["Public
All users"]
    end

    A --> G
    B --> G
    C --> G
    D --> G
    E --> G
    F --> G

    G --> I
    H --> I

    I --> J
    I --> K
    I --> L

    style J fill:#ffebee
    style K fill:#e3f2fd
    style L fill:#e8f5e8
```

### Resource Visibility Matrix

```mermaid
flowchart LR
    subgraph "User Access to Resources"
        U1["User A
Team 1 Member
Team 2 Owner"]
        U2["User B
Team 1 Owner
Team 3 Member"]
        U3["User C
No team membership"]
    end

    subgraph "Resource Visibility"
        R1["Resource 1
Team 1, Private
Owner: User B"]
        R2["Resource 2
Team 1, Team
Owner: User A"]
        R3["Resource 3
Team 2, Public
Owner: User A"]
        R4["Resource 4
Team 3, Team
Owner: User B"]
    end

    U1 -.->|❌ No Access| R1
    U1 -->|✅ Team Member| R2
    U1 -->|✅ Owner & Public| R3
    U1 -.->|❌ Not Team Member| R4

    U2 -->|✅ Owner & Private| R1
    U2 -->|✅ Team Member| R2
    U2 -->|✅ Public| R3
    U2 -->|✅ Team Member| R4

    U3 -.->|❌ No Access| R1
    U3 -.->|❌ No Access| R2
    U3 -->|✅ Public| R3
    U3 -.->|❌ No Access| R4

    style U1 fill:#e1f5fe
    style U2 fill:#f3e5f5
    style U3 fill:#fff3e0
```

### Resource Access Control Logic

```mermaid
flowchart TD
    A[User requests resource access] --> B{Resource visibility}

    B -->|Private| C{User owns resource?}
    B -->|Team| D{User in resource team?}
    B -->|Public| E[✅ Allow access]

    C -->|Yes| F[✅ Allow access]
    C -->|No| G[❌ Deny access]

    D -->|Yes| H[✅ Allow access]
    D -->|No| I[❌ Deny access]

    style F fill:#e8f5e8
    style H fill:#e8f5e8
    style E fill:#e8f5e8
    style G fill:#ffebee
    style I fill:#ffebee
```

---

## Platform Administration

## Role-Based Access Control (RBAC)

ContextForge implements a comprehensive RBAC system with four built-in roles that are automatically created during system bootstrap. These roles provide granular permission management across different scopes.

### System Roles

The following roles are created automatically when the system starts:

#### 1. Platform Admin (Global Scope)
- **Permissions**: `*` (all permissions)
- **Scope**: Global
- **Description**: Platform administrator with all system-wide permissions
- **Use Case**: System administrators who manage the entire platform

#### 2. Team Admin (Team Scope)
- **Permissions**:

  - `teams.read` - View team information
  - `teams.update` - Modify team settings
  - `teams.join` - Join public teams
  - `teams.manage_members` - Add/remove team members
  - `tools.read` - View tools
  - `tools.execute` - Execute tools
  - `resources.read` - View resources
  - `prompts.read` - View prompts

- **Scope**: Team
- **Description**: Team administrator with team management permissions
- **Use Case**: Team leaders who manage team membership and resources

#### 3. Developer (Team Scope)
- **Permissions**:

  - `teams.join` - Join public teams
  - `tools.read` - View tools
  - `tools.execute` - Execute tools
  - `resources.read` - View resources
  - `prompts.read` - View prompts

- **Scope**: Team
- **Description**: Developer with tool and resource access
- **Use Case**: Team members who need to use tools and access resources

#### 4. Viewer (Team Scope)
- **Permissions**:

  - `teams.join` - Join public teams
  - `tools.read` - View tools
  - `resources.read` - View resources
  - `prompts.read` - View prompts

- **Scope**: Team
- **Description**: Read-only access to resources
- **Use Case**: Team members who only need to view resources without executing them

### Permission Categories

The RBAC system defines permissions across multiple resource categories:

#### User Management
- `users.create`, `users.read`, `users.update`, `users.delete`, `users.invite`

#### Team Management
- `teams.create`, `teams.read`, `teams.update`, `teams.delete`, `teams.manage_members`

#### Tool Management
- `tools.create`, `tools.read`, `tools.update`, `tools.delete`, `tools.execute`

#### Resource Management
- `resources.create`, `resources.read`, `resources.update`, `resources.delete`, `resources.share`

#### Prompt Management
- `prompts.create`, `prompts.read`, `prompts.update`, `prompts.delete`, `prompts.execute`

#### Server Management
- `servers.create`, `servers.read`, `servers.update`, `servers.delete`, `servers.manage`

#### Token Management
- `tokens.create`, `tokens.read`, `tokens.update`, `tokens.revoke`

#### Admin Functions
- `admin.system_config`, `admin.user_management`, `admin.security_audit`

### Role Assignment and Scope

Roles are assigned to users within specific scopes:

- **Global Scope**: Platform-wide permissions (platform_admin only)
- **Team Scope**: Team-specific permissions (team_admin, developer, viewer)
- **Personal Scope**: Individual user permissions (future use)

### Administrator Hierarchy

```mermaid
flowchart TD
    subgraph "RBAC Roles"
        A["Platform Admin

- All permissions (*)
- Global scope
- System management"]
        B["Team Admin

- Team management
- Member control
- Resource access"]
        C["Developer

- Tool execution
- Resource access
- No team management"]
        D["Viewer

- Read-only access
- No execution
- No management"]
    end

    subgraph "Domain Restrictions"
        E["Admin Domain Whitelist
SSO_AUTO_ADMIN_DOMAINS"]
        F["Trusted Domains
SSO_TRUSTED_DOMAINS"]
        G["Admin Approval Gate
SSO_REQUIRE_ADMIN_APPROVAL"]
    end

    A --> E
    A --> G
    B --> F

    subgraph "Access Hierarchy"
        H[Platform Admin] --> I[All Teams & Resources]
        J[Team Admin] --> K[Team Resources & Members]
        L[Developer] --> M[Team Resources Only]
        N[Viewer] --> O[Read-Only Access]
    end

    style A fill:#ff8a80
    style B fill:#ffb74d
    style C fill:#81c784
    style D fill:#90caf9
```

### Administrator Assignment Flow

```mermaid
sequenceDiagram
    participant U as New User
    participant G as Gateway
    participant SSO as SSO Provider
    participant DB as Database
    participant A as Platform Admin

    Note over U,A: SSO Registration with Domain + Approval Checks
    U->>G: SSO Login (user@company.com)
    G->>SSO: OAuth flow
    SSO-->>G: User profile

    G->>G: Check trusted domains (if configured)
    alt Domain not trusted
        G-->>U: Access denied
    else Domain trusted
        G->>G: Require admin approval?
        alt Approval required (SSO_REQUIRE_ADMIN_APPROVAL=true)
            G->>DB: Create PendingUserApproval
            G-->>A: Approval required
            A->>G: Approve user (admin UI)
            Note over U,G: User logs in again after approval
            U->>G: SSO Login (retry)
        else Approval not required
            Note right of G: Continue auto-create flow
        end

        G->>G: Evaluate admin criteria
        Note right of G: Auto-admin domains / provider admin groups
        G->>DB: Create user (is_admin true/false)
        G-->>U: JWT token issued
    end
```

## Password Management

ContextForge now supports:

- Self-service forgot-password/reset flow:
  - `POST /auth/email/forgot-password`
  - `GET /auth/email/reset-password/{token}`
  - `POST /auth/email/reset-password/{token}`
- Admin unlock flow:
  - `POST /auth/email/admin/users/{email}/unlock`
- Admin password reset via UI and API (`PUT /auth/email/admin/users/{email}`)

Operational guidance, Kubernetes recovery commands, emergency SQL procedures, and
SMTP/reset configuration are documented in:

- [Management: Password Management & Recovery](../manage/password-management.md)

### Password Security Requirements

- Argon2id hashing for stored credentials
- One-time reset tokens (hashed in DB, configurable expiry)
- Password reset request rate limiting
- Failed login tracking and account lockout controls
- Audit events for login/reset/lockout/unlock actions

### Role-Based UI Experience

The user interface adapts based on the user's assigned roles:

#### Platform Admin Experience
- **Full System Access**: Can view and manage all teams, users, and resources across the platform
- **Global Configuration**: Access to system-wide settings, SSO configuration, and platform management
- **Cross-Team Management**: Can manage resources in any team regardless of membership
- **User Management**: Can create, modify, and delete user accounts and role assignments

#### Team Admin Experience
- **Team Management**: Can modify team settings, manage team membership (invite/remove members)
- **Resource Control**: Full access to create, modify, and delete team resources
- **Member Oversight**: Can view and manage all team members and their access
- **Limited to Assigned Teams**: Only sees teams where they have the team_admin role

#### Developer Experience
- **Tool Access**: Can view and execute tools within their teams
- **Resource Usage**: Can access and use team resources and prompts
- **No Management Rights**: Cannot manage team membership or team settings
- **Create Resources**: Can create new tools, resources, and prompts within their teams

#### Viewer Experience
- **Read and Execute Access**: Can view tools, resources, and prompts and execute tools within team scope
- **No Creation Rights**: Cannot create new resources or tools
- **No Management Access**: Cannot manage team membership or settings
- **Limited Mutation**: Can execute tools but cannot create, update, or delete resources

### Default Visibility & Sharing

- Default on create: New resources (including MCP Servers, Tools, Resources, Prompts, and A2A Agents) default to `visibility="private"` unless a different value is explicitly provided by an allowed actor. For servers created via the UI, the visibility is enforced to `private` by default.
- Team assignment: When a user creates a server and does not specify `team_id`, the server is automatically assigned to the user's personal team.
- Sharing workflow:

  - Private → Team: Make the resource visible to the owning team by setting `visibility="team"`.
  - Private/Team → Public: Make the resource visible to all authenticated users by setting `visibility="public"`.
  - Cross-team: To have a resource under a different team, create it in that team or move/clone it per policy; cross-team "share" is by visibility, not multi-team ownership.

---

## Complete Multi-Tenancy Flow

### End-to-End Resource Access

```mermaid
sequenceDiagram
    participant U as User
    participant G as Gateway
    participant Auth as Authentication
    participant Team as Team Service
    participant Res as Resource Service
    participant DB as Database

    Note over U,DB: Complete Access Flow
    U->>G: Request resource list
    G->>Auth: Validate JWT token
    Auth-->>G: User identity confirmed

    G->>Team: Get user teams
    Team->>DB: Query team memberships
    DB-->>Team: User team list
    Team-->>G: Teams with roles

    G->>Res: List resources for user
    Res->>DB: Query with team filtering
    Note right of Res: WHERE owner_email = user OR team_id IN user_teams AND visibility IN team,public OR visibility = public

    DB-->>Res: Filtered resource list
    Res-->>G: User-accessible resources
    G-->>U: Resource list response

    Note over U,DB: Resource Creation
    U->>G: Create new resource
    G->>Auth: Validate permissions
    G->>Team: Verify team membership
    Team-->>G: Team access confirmed

    G->>Res: Create resource
    Res->>DB: INSERT with team_id, owner_email, visibility
    DB-->>Res: Resource created
    Res-->>G: Creation confirmed
    G-->>U: Resource created successfully
```

### Team-Based Resource Filtering

```mermaid
flowchart TD
    A[User Request] --> B[Extract User Identity]
    B --> C[Get User Team Memberships]

    C --> D[Build Filter Criteria]

    D --> E{Resource Query}
    E --> F["Owner-Owned Resources
owner_email = user.email"]
    E --> G["Team Resources
team_id IN user.teams
AND visibility IN team,public"]
    E --> H["Public Resources
visibility = public"]

    F --> I[Combine Results]
    G --> I
    H --> I

    I --> J[Apply Additional Filters]
    J --> K[Return Filtered Resources]

    subgraph "Filter Logic"
        L[Personal: User owns directly]
        M[Team: User is team member]
        N[Public: Available to all]
    end

    style F fill:#e1f5fe
    style G fill:#e3f2fd
    style H fill:#e8f5e8
```

---

## Database Schema Design

### Core Multi-Tenant Schema (Key Fields)

```mermaid
erDiagram
    %% User Management
    EmailUser ||--o{ EmailTeamMember : belongs_to
    EmailUser ||--o{ EmailTeamInvitation : invites
    EmailUser ||--o{ EmailTeamJoinRequest : requests
    EmailUser ||--o{ EmailTeam : creates

    %% Team Management
    EmailTeam ||--o{ EmailTeamMember : has
    EmailTeam ||--o{ EmailTeamInvitation : has_pending
    EmailTeam ||--o{ EmailTeamJoinRequest : has_requests
    EmailTeam ||--o{ Tool : owns
    EmailTeam ||--o{ Server : owns
    EmailTeam ||--o{ Resource : owns
    EmailTeam ||--o{ Prompt : owns
    EmailTeam ||--o{ A2AAgent : owns

    EmailUser {
        string email PK
        string full_name
        boolean is_admin
        boolean is_active
        string auth_provider
        timestamp created_at
        timestamp updated_at
    }

    EmailTeam {
        string id PK
        string name
        string slug
        text description
        string created_by FK
        boolean is_personal
        string visibility
        int max_members
        boolean is_active
        timestamp created_at
        timestamp updated_at
    }

    EmailTeamMember {
        string id PK
        string team_id FK
        string user_email FK
        string role
        string invited_by FK
        timestamp joined_at
        boolean is_active
    }

    EmailTeamInvitation {
        string id PK
        string team_id FK
        string email
        string role
        string invited_by FK
        timestamp invited_at
        string token
        timestamp expires_at
        boolean is_active
    }

    EmailTeamJoinRequest {
        string id PK
        string team_id FK
        string user_email FK
        string status
        timestamp requested_at
        timestamp expires_at
        timestamp reviewed_at
        string reviewed_by FK
        string notes
    }

    Tool {
        string id PK
        string original_name
        string display_name
        string team_id FK
        string owner_email
        string visibility
        timestamp created_at
        timestamp updated_at
    }

    Resource {
        string id PK
        string uri
        string name
        string team_id FK
        string owner_email
        string visibility
        timestamp created_at
        timestamp updated_at
    }

    Prompt {
        string id PK
        string name
        string original_name
        string team_id FK
        string owner_email
        string visibility
        timestamp created_at
        timestamp updated_at
    }

    A2AAgent {
        string id PK
        string name
        string slug
        string team_id FK
        string owner_email
        string visibility
        timestamp created_at
        timestamp updated_at
    }

    Server {
        string id PK
        string name
        string team_id FK
        string owner_email
        string visibility
        timestamp created_at
        timestamp updated_at
    }
```

---

## API Design Patterns

### Team-Scoped Endpoints

All resource endpoints follow consistent team-scoping patterns:

```mermaid
flowchart TD
    subgraph "API Endpoint Patterns"
        A["GET /tools?team_id=uuid&visibility=team"]
        B["POST /tools
name, team_id (optional), visibility"]
        C["GET /tools/{tool_id}"]
        D["PUT /tools/{tool_id}
visibility, name, etc."]
        E["DELETE /tools/{tool_id}"]
    end

    subgraph "Request Processing"
        F[Extract User Identity] --> G[Validate Team Access]
        G --> H[Apply Team Filters]
        H --> I[Execute Query]
        I --> J[Return Results]
    end

    subgraph "Access Control Checks"
        K[User Team Membership]
        L[Resource Ownership]
        M[Visibility Level]
        N[Operation Permissions]
    end

    A --> F
    B --> F
    C --> F
    D --> F
    E --> F

    G --> K
    G --> L
    G --> M
    G --> N

    style A fill:#e1f5fe
    style B fill:#f3e5f5
    style C fill:#fff3e0
    style D fill:#e8f5e8
    style E fill:#ffebee
```

### Resource Creation Flow

```mermaid
sequenceDiagram
    participant C as Client
    participant G as Gateway
    participant A as Auth Middleware
    participant R as Resource Service
    participant DB as Database

    C->>G: POST /tools
    Note right of C: {name, team_id, visibility}

    G->>A: Validate request
    A->>A: Extract user from JWT
    A->>A: Enforce permissions + token scope
    A-->>G: User authorized

    G->>R: Create resource
    R->>R: Validate payload + defaults
    R->>DB: INSERT resource
    Note right of R: team_id, owner_email, visibility
    DB-->>R: Resource created
    R-->>G: Creation response
    G-->>C: 201 Created
```

---

## Configuration & Environment

### Multi-Tenancy Configuration

```bash
#####################################
# Multi-Tenancy Configuration
#####################################

# Team Settings
AUTO_CREATE_PERSONAL_TEAMS=true
# PERSONAL_TEAM_PREFIX=personal  # optional: set to get collision-safe email-based slugs
MAX_TEAMS_PER_USER=50
MAX_MEMBERS_PER_TEAM=100        # default limit resolved at check time; platform admins are exempt

# Team Invitation Settings
INVITATION_EXPIRY_DAYS=7
REQUIRE_EMAIL_VERIFICATION_FOR_INVITES=true

# Team Governance
ALLOW_TEAM_CREATION=true
ALLOW_TEAM_JOIN_REQUESTS=true
ALLOW_TEAM_INVITATIONS=true

# Visibility
# NOTE: Resources default to 'private' (not configurable via env today)
# Allowed visibility values: private | team | public

# Platform Administration
PLATFORM_ADMIN_EMAIL=admin@company.com
PLATFORM_ADMIN_PASSWORD=changeme
PLATFORM_ADMIN_FULL_NAME="Platform Administrator"

# SSO (enable + trust and admin mapping)
SSO_ENABLED=true
SSO_TRUSTED_DOMAINS=["company.com","trusted-partner.com"]
SSO_AUTO_ADMIN_DOMAINS=["company.com"]
SSO_GITHUB_ADMIN_ORGS=["your-org"]
SSO_GOOGLE_ADMIN_DOMAINS=["your-google-workspace-domain.com"]
SSO_REQUIRE_ADMIN_APPROVAL=false

# Public team self-join flows are planned; no env toggles yet
```

---

## Security Considerations

### Multi-Tenant Security Model

```mermaid
flowchart TD
    subgraph "Security Layers"
        A["Authentication Layer

- JWT validation
- Session management"]
        B["Authorization Layer

- Team membership
- Resource ownership
- Visibility checks"]
        C["Data Isolation Layer

- Team-scoped queries
- Owner validation
- Access logging"]
    end

    subgraph "Security Controls"
        D["Input Validation

- Team ID validation
- Email format
- Role validation"]
        E["Rate Limiting

- Per-user limits
- Per-team limits
- API quotas"]
        F["Audit Logging

- Access attempts
- Resource changes
- Team modifications"]
    end

    subgraph "Attack Prevention"
        G["Team Enumeration

- UUID team IDs
- Access validation"]
        H["Resource Access

- Ownership checks
- Visibility enforcement"]
        I["Privilege Escalation

- Role validation
- Permission boundaries"]
    end

    A --> B --> C
    D --> E --> F
    G --> H --> I

    style A fill:#ffcdd2
    style B fill:#f8bbd9
    style C fill:#e1bee7
    style D fill:#c8e6c9
    style E fill:#dcedc8
    style F fill:#f0f4c3
```

### RBAC Access Control Matrix

| RBAC Role | Scope | Team Access | Resource Creation | Member Management | Team Settings | Platform Admin |
|-----------|-------|-------------|-------------------|-------------------|---------------|----------------|
| Platform Admin | Global | All teams | All resources | All teams | All settings | Full access |
| Team Admin | Team | Assigned teams | Team resources | Team members | Team settings | No access |
| Developer | Team | Member teams | Team resources | No access | No access | No access |
| Viewer | Team | Member teams | No access | No access | No access | No access |

**Note**: Team Owner/Member roles from the team management system work alongside RBAC roles. A user can have both team membership status (Owner/Member) and RBAC role assignments (Team Admin/Developer/Viewer) within the same team.

### Token Scoping Invariants

These behaviors are enforced consistently across all access paths:

1. `normalize_token_teams()` is the canonical interpreter of JWT team claims; `resolve_session_teams()` is the single policy point for session tokens (always DB-resolved)
2. For API/legacy tokens: missing `teams` key always returns `[]` (public-only, secure default); empty `teams: []` also returns `[]`. For session tokens: missing, null, or empty `teams` returns the full DB membership (no narrowing requested)
3. Admin bypass for API/legacy tokens requires BOTH `teams: null` AND `is_admin: true`; for session tokens, admin bypass is DB-derived (`is_admin` flag). In both cases the service layer requires `token_teams=None` AND `user_email=None` for unrestricted queries
4. All list endpoints pass `token_teams` to the service layer
5. Service layer applies visibility filtering based on `token_teams` via `BaseService._apply_access_control()`
6. Public-only tokens can ONLY access `visibility='public'` resources — owner and team access are both suppressed
7. Owner-based access (`owner_email`) grants visibility only for `visibility='private'` resources — it does not bypass team scoping for team-visibility resources

### Related Documentation

For detailed information on RBAC configuration, token scoping, and permission management, see [RBAC Configuration](../manage/rbac.md).

---

## Implementation Verification

### Key Requirements Checklist

- [x] **User Authentication**: Email and SSO authentication implemented
- [x] **Personal Teams**: Auto-created for every user
- [x] **Team Roles**: Owner and Member roles (platform Admin is global)
- [x] **Team Visibility**: Private and Public team types
- [x] **Resource Scoping**: All resources scoped to teams with visibility controls
- [x] **Invitation System**: Email-based invitations with token management
- [x] **Platform Administration**: Separate admin role with domain restrictions
- [x] **Access Control**: Team-based filtering for all resources
- [x] **Database Design**: Complete multi-tenant schema
- [x] **API Patterns**: Consistent team-scoped endpoints

### Critical Implementation Points

1. **Team ID Validation**: Every resource operation must validate team membership
2. **Visibility Enforcement**: Resource visibility (private/team/public) strictly enforced; team visibility (private/public) per design
3. **Owner Permissions**: Only team owners can manage members and settings
4. **Personal Team Protection**: Personal teams cannot be deleted or transferred
5. **Invitation Security**: Invitation tokens with expiration and single-use
6. **Platform Admin Isolation**: Platform admin access separate from team access
7. **Cross-Team Access**: Public resources accessible across team boundaries
8. **Audit Trail**: Permission checks and auth events audited; extended operation audit planned

---

## Gaps & Issues

- Team roles: Owner and Member only (platform Admin is global) — consistent across ERD, APIs, and UI.
- Team visibility: Private and Public.
- Resource visibility: `private|team|public` — enforced as designed.
- Public team discovery/join: Join‑request/self‑join flows to be implemented.
- Default resource visibility: Defaults to "private"; not configurable via env.
- SSO admin mapping: Domain/org lists supported; provider‑specific org checks may require provider API calls in production.

---

## Enhancements & Roadmap (Part of the Design)

- Public Team Discovery & Join Requests:

  - Add endpoints and UI to request membership on public teams; owner approval workflow; optional auto‑approve policy.
  - Admin toggles/policies to restrict who can create public teams and who can approve joins.

- Unified Operation Audit:

  - System‑wide audit log for create/update/delete across teams, tools, servers, resources, prompts, agents with export/reporting.

- Role Automation:

  - Auto‑assign default RBAC roles on resource creation (e.g., owner gets manager role in team scope; members get viewer).
  - Optional per‑team policies defining who may create public resources.

- ABAC for Virtual Servers:

  - Attribute‑based conditions layered on top of RBAC (tenant tags, data classifications, environment, time windows, client IP).

- Team/Resource Quotas and Policies:

  - Per‑team limits (tools/servers/resources/agents); per‑team defaults for resource visibility and creation rights.

- Public Resource Access Controls:

  - Fine‑grained cross‑tenant rate limits and opt‑in masking for metadata shown to non‑members.

This architecture provides a robust, secure, and scalable multi-tenant system that supports complex organizational structures while maintaining strict data isolation and flexible resource sharing capabilities.
