5.9 KiB
OpenUI OAuth "Client not registered" Fix
Problem
When connecting MCP Gateway to Open-UI, you get:
{"error":"invalid_client","error_description":"Client not registered."}
Even though it works fine in Claude.ai.
Root Cause
Your gateway uses in-memory OAuth client registration (REGISTERED_CLIENTS dict). This means:
- Each client (Claude.ai, Open-UI, etc.) registers itself via
/oauth/register - The registration is stored in RAM only
- When the gateway restarts → all registrations are lost
- Open-UI's client ID is no longer recognized during token exchange
Additionally, there are two separate OAuth flows happening:
- Claude.ai uses one client ID
- Open-UI registers and gets a different client ID
- If the gateway restarts between registration and first use, Open-UI's ID is gone
Solution Options
Option A: Persistent OAuth Client Storage (Recommended)
Store OAuth clients in a file or database instead of RAM.
File: gateway-proxy/oauth_storage.py (to be created)
import json
import os
from typing import dict
OAUTH_STORAGE_FILE = os.environ.get("OAUTH_STORAGE_FILE", "/data/oauth_clients.json")
def load_oauth_clients() -> dict:
"""Load OAuth clients from persistent storage"""
if os.path.exists(OAUTH_STORAGE_FILE):
try:
with open(OAUTH_STORAGE_FILE, 'r') as f:
return json.load(f)
except Exception as e:
logger.error(f"Failed to load OAuth clients: {e}")
return {}
return {}
def save_oauth_clients(clients: dict) -> None:
"""Save OAuth clients to persistent storage"""
try:
os.makedirs(os.path.dirname(OAUTH_STORAGE_FILE), exist_ok=True)
with open(OAUTH_STORAGE_FILE, 'w') as f:
json.dump(clients, f, indent=2)
except Exception as e:
logger.error(f"Failed to save OAuth clients: {e}")
def register_client(client_info: dict) -> None:
"""Register a new OAuth client"""
clients = load_oauth_clients()
client_id = client_info["client_id"]
clients[client_id] = client_info
save_oauth_clients(clients)
Changes to gateway_proxy.py:
# At startup, replace:
# REGISTERED_CLIENTS: dict[str, dict] = {}
# with:
from .oauth_storage import load_oauth_clients, save_oauth_clients
REGISTERED_CLIENTS = load_oauth_clients()
# In oauth_register function, after adding client:
REGISTERED_CLIENTS[client_id] = client_info
save_oauth_clients(REGISTERED_CLIENTS) # Add this line
docker-compose.yml update:
gateway-proxy:
volumes:
- gateway-data:/data # Persist OAuth clients
Option B: Pre-register Known Clients (Quick Fix)
Hardcode known clients (Claude.ai, Open-UI) so they don't need dynamic registration.
In .env:
# Pre-registered OAuth clients (JSON format)
OAUTH_CLIENTS='{"claude-app":{"client_id":"claude-app","client_secret":"YOUR_SECRET","client_name":"Claude.ai"},"openui":{"client_id":"openui","client_secret":"YOUR_SECRET","client_name":"Open-UI"}}'
In gateway_proxy.py:
import json
def load_oauth_clients() -> dict:
"""Load pre-registered clients from env"""
clients_json = os.environ.get("OAUTH_CLIENTS", "{}")
try:
return json.loads(clients_json)
except:
return {}
REGISTERED_CLIENTS = load_oauth_clients()
Option C: Disable Client Validation (Development Only)
For testing, skip client ID validation in token endpoint:
In oauth_token function, around line 520:
# Change this check:
client_info = REGISTERED_CLIENTS.get(client_id)
if not client_info:
return JSONResponse({"error": "invalid_client"}, status_code=400)
# To this (development only):
client_info = REGISTERED_CLIENTS.get(client_id)
if not client_info:
logger.warning(f"Client {client_id} not registered, allowing anyway (DEV MODE)")
# Allow unregistered clients for testing
Recommended Implementation
Use Option A (persistent storage) because:
- ✅ Works across gateway restarts
- ✅ Supports multiple clients (Claude.ai, Open-UI, others)
- ✅ Secure (OAuth flow still requires proper credentials)
- ✅ Production-ready
- ✅ No hardcoded secrets
Implementation Steps
Step 1: Create oauth_storage.py
Save to gateway-proxy/oauth_storage.py (content provided above)
Step 2: Update gateway_proxy.py
Around line 27 (imports), add:
from .oauth_storage import load_oauth_clients, save_oauth_clients
Around line 52, replace:
# REGISTERED_CLIENTS: dict[str, dict] = {}
REGISTERED_CLIENTS = load_oauth_clients()
In oauth_register() function, after line 383:
REGISTERED_CLIENTS[client_id] = client_info
save_oauth_clients(REGISTERED_CLIENTS) # ADD THIS LINE
Step 3: Update docker-compose.yml
Add volume to gateway-proxy service:
gateway-proxy:
volumes:
- gateway-data:/data
volumes:
gateway-data:
Step 4: Set environment variable (optional)
In .env:
OAUTH_STORAGE_FILE=/data/oauth_clients.json
Step 5: Restart
docker-compose down
docker-compose up -d
Testing
- Register with Open-UI (should succeed)
- Restart gateway:
docker-compose restart gateway-proxy - Try to use Open-UI again (should work - client is persisted)
- Check
/data/oauth_clients.jsonfile exists with registration
Debugging
Check if clients are saved:
docker exec mcp-gateway cat /data/oauth_clients.json | jq '.'
Check gateway logs for registration:
docker logs mcp-gateway | grep "DCR:"
If you still get "Client not registered":
- Verify oauth_storage.py is in gateway-proxy/
- Check that save_oauth_clients() is being called
- Check file permissions on /data/ volume
- Check docker logs for save errors
Alternative: Use Redis or Database
For production with multiple gateway instances, consider storing OAuth clients in:
- Redis (fast, distributed)
- PostgreSQL (persistent, queryable)
- MongoDB (flexible schema)
This ensures clients are available across all instances.