mcp-servers/USER_MANAGEMENT_SETUP.md

9.2 KiB

User Management System Setup Guide

This guide explains how to integrate the user management system into your MCP Gateway, enabling per-user API key generation and MCP access control.

Overview

The user management system provides:

  • User Creation & Management: Create and manage users, enable/disable accounts
  • API Key Generation: Generate unique API keys with optional TTL (time-to-live)
  • MCP Access Control: Allow/block specific MCPs for each user (e.g., some users access only ERPNext, others access Wave + TrueNAS)
  • Persistent Storage: User data stored in JSON file, survives container restarts
  • Web Dashboard: Intuitive UI for managing users and API keys

Files Created

  1. user_management.py - Core user/API key management logic
  2. user_routes.py - REST API endpoints for user management
  3. user_dashboard_ui.py - Web dashboard with Vue.js UI
  4. gateway_proxy_user_integration.py - Integration instructions

Installation Steps

Step 1: Copy Files

The files are already created in gateway-proxy/:

  • user_management.py
  • user_routes.py
  • user_dashboard_ui.py

Step 2: Update gateway_proxy.py

Add the import at line 33 (after existing imports):

from user_management import user_manager
from user_routes import (
    create_user, list_users, get_user, delete_user, toggle_user,
    generate_api_key, revoke_api_key, set_mcp_access
)
from user_dashboard_ui import user_management_dashboard

Step 3: Update validate_bearer_token() Function

Replace the validate_bearer_token() function (around line 254) with:

def validate_bearer_token(request: Request) -> dict | None:
    auth_header = request.headers.get("Authorization", "")
    if not auth_header.startswith("Bearer "):
        return None
    token = auth_header[7:]

    # Check static API key first
    if STATIC_API_KEY and token == STATIC_API_KEY:
        return {"client_id": "static", "scope": "mcp:tools", "user": "static"}

    # Check user API key
    user_info = user_manager.validate_api_key(token)
    if user_info:
        return {
            "client_id": "api-key",
            "scope": "mcp:tools",
            "user": user_info['username'],
            "user_id": user_info['user_id'],
            "mcp_allowed": user_info['mcp_allowed'],
            "mcp_blocked": user_info['mcp_blocked']
        }

    # Check OAuth token
    token_hash = _hash(token)
    info = ACCESS_TOKENS.get(token_hash)
    if not info:
        return None
    if info["expires_at"] < time.time():
        del ACCESS_TOKENS[token_hash]
        return None
    return info

Step 4: Add MCP Access Control to handle_mcp()

In the handle_mcp() function, after retrieving token_info, add this check:

# Check MCP access control
if token_info and 'user' in token_info and token_info.get('client_id') == 'api-key':
    mcp_allowed = token_info.get('mcp_allowed', [])
    mcp_blocked = token_info.get('mcp_blocked', [])

    # Check if user can access any backend (basic check)
    if not mcp_allowed and not mcp_blocked:
        # User has no restrictions, allow all
        pass
    elif mcp_blocked and len(mcp_blocked) == len(BACKENDS):
        return JSONResponse(
            {'error': 'access_denied', 'message': 'All MCPs are blocked for your user'},
            status_code=403
        )

Step 5: Update Routes List

Add these routes to the routes list before creating the app (around line 1012):

routes = [
    # ... existing well-known and OAuth routes ...

    # MCP endpoint
    Route("/mcp", handle_mcp, methods=["GET", "HEAD", "POST", "DELETE"]),

    # Monitoring
    Route("/health", health, methods=["GET"]),
    Route("/status", status, methods=["GET"]),

    # Dashboard
    Route("/dashboard", dashboard, methods=["GET"]),
    Route("/dashboard/status", dashboard_status, methods=["GET"]),

    # User Management - Enhanced Dashboard
    Route("/admin", user_management_dashboard, methods=["GET"]),

    # User Management API
    Route("/users", create_user, methods=["POST"]),
    Route("/users", list_users, methods=["GET"]),
    Route("/users/{username}", get_user, methods=["GET"]),
    Route("/users/{username}", delete_user, methods=["DELETE"]),
    Route("/users/{username}/enable", toggle_user, methods=["PATCH"]),
    Route("/users/{username}/keys", generate_api_key, methods=["POST"]),
    Route("/users/{username}/mcp-access", set_mcp_access, methods=["PUT"]),
    Route("/keys/revoke", revoke_api_key, methods=["POST"]),
]

Step 6: Update docker-compose.yml

Ensure the gateway service has a data volume for persistent storage:

services:
  mcp-gateway:
    image: mcp-gateway:latest
    build:
      context: ./gateway-proxy
      dockerfile: Dockerfile
    container_name: mcp-gateway
    ports:
      - "4444:4444"
    environment:
      # ... existing environment variables ...
      USERS_DB_PATH: /data/users.json
    volumes:
      - ./data:/data
      - ./gateway-proxy:/app
    depends_on:
      # ... existing dependencies ...

Step 7: Create Data Directory

mkdir -p /sessions/vigilant-elegant-ramanujan/mnt/MCP\ Servers/mcp-gateway/data

Step 8: Restart Gateway

docker-compose down
docker-compose up -d mcp-gateway

Usage

Access the Dashboard

Open your browser to:

http://10.0.0.25:4444/admin

Create a User via API

curl -X POST http://10.0.0.25:4444/users \
  -H "Content-Type: application/json" \
  -d '{
    "username": "alice",
    "email": "alice@example.com",
    "description": "Engineering team"
  }'

Generate an API Key

curl -X POST http://10.0.0.25:4444/users/alice/keys \
  -H "Content-Type: application/json" \
  -d '{
    "key_name": "production-key",
    "ttl_days": 90
  }'

Response:

{
  "status": "created",
  "username": "alice",
  "api_key": "mcpgw_...",
  "note": "Save this key immediately — it will not be shown again"
}

Configure MCP Access

curl -X PUT http://10.0.0.25:4444/users/alice/mcp-access \
  -H "Content-Type: application/json" \
  -d '{
    "allowed_mcps": ["erpnext", "wave"],
    "blocked_mcps": []
  }'

This allows alice to access only ERPNext and Wave Finance MCPs.

Use the API Key

curl -X POST http://10.0.0.25:4444/mcp \
  -H "Authorization: Bearer mcpgw_..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "capabilities": {},
      "clientInfo": {"name": "test-client", "version": "1.0"}
    }
  }'

Access Control Examples

Example 1: Limited Access User

Create a user with access to only ERPNext:

# Create user
curl -X POST http://10.0.0.25:4444/users \
  -H "Content-Type: application/json" \
  -d '{"username": "erp-user", "description": "ERP access only"}'

# Generate key
curl -X POST http://10.0.0.25:4444/users/erp-user/keys \
  -H "Content-Type: application/json" \
  -d '{"key_name": "erp-only"}'

# Restrict to ERPNext
curl -X PUT http://10.0.0.25:4444/users/erp-user/mcp-access \
  -H "Content-Type: application/json" \
  -d '{"allowed_mcps": ["erpnext"]}'

Example 2: Block Specific MCPs

Create a user with access to everything except Home Assistant:

curl -X PUT http://10.0.0.25:4444/users/alice/mcp-access \
  -H "Content-Type: application/json" \
  -d '{"blocked_mcps": ["homeassistant"]}'

Example 3: Revoke an API Key

curl -X POST http://10.0.0.25:4444/keys/revoke \
  -H "Content-Type: application/json" \
  -d '{"api_key": "mcpgw_..."}'

Database Schema

User data is stored in /data/users.json with the following structure:

{
  "users": {
    "alice": {
      "user_id": "abc123...",
      "email": "alice@example.com",
      "description": "Engineering team",
      "created_at": "2026-03-31T12:00:00.000000",
      "enabled": true,
      "api_keys": ["hash1", "hash2"],
      "mcp_allowed": ["erpnext", "wave"],
      "mcp_blocked": [],
      "metadata": {}
    }
  },
  "api_keys": {
    "hash1": {
      "user_id": "abc123...",
      "username": "alice",
      "key_name": "prod-key",
      "created_at": "2026-03-31T12:00:00.000000",
      "expires_at": null,
      "revoked": false
    }
  }
}

Security Notes

  1. API Keys are Hashed: Only SHA256 hashes are stored, never the actual key
  2. One-Time Display: Keys are shown only once during generation
  3. TTL Support: Keys can expire after N days
  4. Revocation: Keys can be revoked at any time
  5. Per-User Access Control: Each user has independent MCP restrictions

Troubleshooting

Users/Keys Not Persisting After Restart

Check that:

  1. /data directory exists and is mounted in docker-compose.yml
  2. /data directory has write permissions
  3. USERS_DB_PATH=/data/users.json is set in environment

API Key Not Working

  1. Verify key hasn't been revoked: Check in dashboard
  2. Verify key hasn't expired: Check expires_at timestamp
  3. Verify user is enabled: Check enabled field in dashboard
  4. Check MCP access: Verify the user has access to the MCP they're trying to use

Next Steps

  • Configure additional users for different teams/roles
  • Set up automated key rotation policies
  • Monitor API key usage via the dashboard
  • Integrate with your CI/CD for automated deployments