- Notifications
You must be signed in to change notification settings - Fork53
Description
Architectural Proposal: Separate Gateway and Registry Containers
Overview
This document provides a detailed plan for separating the NGINX gateway and Registry application into two separate containers. This will enable organizations to use their own API gateways while still leveraging the MCP Gateway Registry infrastructure.
Problem Statement
Currently, NGINX and the Registry application are bundled in the SAME container (docker/Dockerfile.registry). This tight coupling prevents organizations from:
- Using their own existing API gateway (AWS API Gateway, Kong, Apigee, Traefik, etc.)
- Deploying registry-only without unused NGINX overhead
- Scaling gateway and registry independently
- Integrating with existing enterprise infrastructure
Current Architecture
What Exists Today
ONE container contains BOTH NGINX and Registry:
docker/Dockerfile.registry builds:┌─────────────────────────────────────────┐│ Registry Container (ONE big container) │├─────────────────────────────────────────┤│ ││ 1. NGINX Server (running on 80/443) ││ • nginx binary ││ • nginx.conf file ││ • Routes traffic ││ ││ 2. FastAPI App (running on 7860) ││ • registry/main.py ││ • Serves Web UI ││ • Manages servers ││ ││ 3. Python Code that manages NGINX ││ • registry/core/nginx_service.py ││ • Generates nginx.conf dynamically ││ • Reloads NGINX when servers change││ │└─────────────────────────────────────────┘The Problem: Who Controls NGINX Config?
Currently: The Registry Python code (registry/core/nginx_service.py) generates and controls NGINX configuration:
- When you add/remove/toggle a server in the registry Web UI
- Registry Python code rewrites
nginx.conffile - Registry Python code reloads NGINX
- This violates separation of concerns - the registry shouldn't manage the gateway
Architecture Diagram
CLIENTS Human Developer (Web Browser) AI Agent/Assistant (MCP Protocol) │ │ │ HTTPS (Web UI) MCP (Streamable HTTP/SSE) └──────────────┬───────────────────────┘ │ ▼ ╔═══════════════════════════════════════════════════╗ ║ REGISTRY CONTAINER (ONE CONTAINER - PROBLEM!) ║ ╠═══════════════════════════════════════════════════╣ ║ ║ ║ NGINX (80/443) ║ ║ • SSL/TLS termination ║ ║ • Reverse proxy ║ ║ • Auth validation ║ ║ │ ║ ║ │ Internal communication ║ ║ ▼ ║ ║ FastAPI (7860) ║ ║ • Web UI ║ ║ • Server management ║ ║ • Tool discovery ║ ║ ║ ╚═══════════════════════════════════════════════════╝ │ │ │ │ │ │ ▼ ▼ ▼ Auth Server MCP Servers Metrics Service (8888) (8001, etc.) (8890)Proposed Architecture
The Solution: Two Separate Containers
Split the single container into TWO containers:
┌──────────────────────────────────┐│ Gateway Container (NEW) │├──────────────────────────────────┤│ ││ 1. NGINX Server (80/443) ││ • nginx binary ││ • nginx.conf file ││ • Routes traffic ││ ││ 2. Python Code ││ • gateway/config_generator.py││ • Watches registry API ││ • Generates nginx.conf ││ • Reloads NGINX ││ │└──────────────────────────────────┘┌──────────────────────────────────┐│ Registry Container (CLEANED UP) │├──────────────────────────────────┤│ ││ 1. FastAPI App ONLY (7860) ││ • registry/main.py ││ • Serves Web UI ││ • Manages servers ││ • NO NGINX code! ││ │└──────────────────────────────────┘Who Controls NGINX Config Now?
After separation:
- Gateway container controls its own NGINX configuration
- Registry container just provides API endpoints - doesn't touch NGINX at all
- Gateway watches registry API and updates its own config when it detects changes
How Does Gateway Know About Server Changes?
Gateway usespolling (checking registry API periodically):
# Inside gateway container, gateway/main.py runs:whileTrue:# Poll registry APIservers=requests.get("http://registry:7860/api/servers").json()# Regenerate NGINX config if servers changedgenerate_nginx_config(servers)# Reload NGINXreload_nginx()# Wait 30 seconds before checking againsleep(30)
Why polling? Simple, reliable, and doesn't require registry to know about gateway.
Architecture Diagram
CLIENTS Human Developer (Web Browser) AI Agent/Assistant (MCP Protocol) │ │ │ HTTPS (Web UI) MCP (Streamable HTTP/SSE) └──────────────┬───────────────────────┘ │ ▼ ┌────────────────────────────────────────────────┐ │ GATEWAY CONTAINER (NEW - Separate) │ ├────────────────────────────────────────────────┤ │ │ │ NGINX (80/443) │ │ • SSL/TLS termination │ │ • Reverse proxy │ │ • Auth validation │ │ │ └──────┬────────────────┬───────────────┬────────┘ │ │ │ │ │ │ ▼ ▼ ▼ ┌────────────┐ ┌──────────────┐ ┌─────────────┐ │ Auth Server│ │ Registry │ │ MCP Servers │ │ (8888) │ │ (7860) │ │ (8001...) │ │ │ │ │ └─────────────┘ │ │ │ FastAPI only │ │ │ │ • Web UI │ │ │ │ • REST APIs │ │ │ │ • Discovery │ └────────────┘ └──────────────┘Benefits
Why Separate?
Enable Enterprise Adoption
- Organizations can use their existing AWS API Gateway, Kong, Apigee, etc.
- Deploy only the registry stack (registry + auth + metrics + keycloak)
- Skip our NGINX gateway entirely if they have their own
Independent Scaling
- Scale gateway and registry separately based on load
- Deploy multiple gateway containers in different regions → single centralized registry
Resource Efficiency
- Don't run unused NGINX if customer has their own gateway
- Smaller container images (each container has only what it needs)
Better Architecture
- Single responsibility principle - each container does one thing
- All other services already separated (auth-server, metrics-service, keycloak)
- This completes the microservices architecture
What Stays the Same
- ✅ Default deployment still includes NGINX gateway (no breaking changes)
- ✅ Same reverse proxy pattern (protocol independence preserved)
- ✅ Same auth flow, same routing, same functionality
- ✅ Existing users can continue as before
- ✅ Just split into two containers instead of one
Deployment Modes
Mode 1: Full Mode (Gateway + Registry) - DEFAULT
What starts:
- ✅ Gateway container (NGINX + Python)
- ✅ Registry container (FastAPI)
- ✅ Auth-server
- ✅ Metrics-service
- ✅ Keycloak
- ✅ MCP servers (fininfo, mcpgw, currenttime, etc.)
What the Gateway does:
- Serves as reverse proxy toRegistry Web UI (port 7860)
- Serves as reverse proxy toMCP servers (ports 8001, 8003, etc.)
- Serves as reverse proxy toAuth server (port 8888)
- SSL/TLS termination
- Auth validation for all requests
User Access:
- User goes to
https://your-domain.com→ NGINX gateway → proxies to registry:7860 → Web UI - AI agent calls
https://your-domain.com/fininfo/→ NGINX gateway → proxies to fininfo-server:8001
Who uses this: Most users who want everything to work out of the box
Mode 2: Registry-Only Mode (No Gateway)
What starts:
- ❌ NO Gateway container
- ✅ Registry container (FastAPI) - DIRECTLY exposes port 7860
- ✅ Auth-server
- ✅ Metrics-service
- ✅ Keycloak
- ✅ MCP servers (customer's own gateway routes to these)
What the Registry does:
- FastAPI serves Web UI directly on port 7860 (no NGINX in front)
- Provides REST APIs on port 7860
- Doesn't know about gateway at all
User Access:
- User goes to
http://registry-host:7860→ DIRECTLY to FastAPI Web UI (no NGINX) - OR customer sets up their own gateway:
- Customer's gateway (AWS API GW, Kong, etc.) → proxies to registry:7860
- Customer's gateway → proxies to fininfo-server:8001, mcpgw-server:8003, etc.
Who uses this: Enterprises with existing API gateway infrastructure (AWS API Gateway, Kong, Apigee, etc.)
Mode 3: Standalone Registry (No Gateway, No MCP Servers)
What starts:
- ❌ NO Gateway container
- ✅ Registry container (FastAPI) - port 7860
- ✅ Auth-server
- ✅ Metrics-service
- ✅ Keycloak
- ❌ NO MCP servers (or they run somewhere else)
What the Registry does:
- Just provides Web UI for managing server definitions
- Server definitions point to MCP servers running elsewhere
- Pure management/discovery layer
User Access:
- User goes to
http://registry-host:7860directly to manage servers - MCP servers run on other hosts, registry just stores their URLs
Who uses this: Organizations that want centralized registry but run MCP servers on different infrastructure
Key Question: Does Registry Container Include the Web UI Frontend?
YES! The registry container includes the Web UI:
registry/├── main.py # FastAPI serves the Web UI└── ...frontend/build/ # React Web UI (HTML/CSS/JS)├── index.html├── static/└── ...Inregistry/main.py:
# Serve React static filesFRONTEND_BUILD_PATH=Path(__file__).parent.parent/"frontend"/"build"ifFRONTEND_BUILD_PATH.exists():# Serve static assetsapp.mount("/static",StaticFiles(directory=FRONTEND_BUILD_PATH/"static"),name="static")# Serve React app for all other routes (SPA)@app.get("/{full_path:path}")asyncdefserve_react_app(full_path:str):returnFileResponse(FRONTEND_BUILD_PATH/"index.html")
So the FastAPI app SERVES the Web UI directly - no NGINX needed for the UI!
What Does Gateway NGINX Actually Do?
The gateway NGINX provides:
- SSL/TLS termination - HTTPS instead of HTTP
- Single entry point - One domain for everything
- Auth validation - Validates ALL requests before they reach backends
- Routing - Routes to registry, auth, MCP servers based on path
- Protocol handling - SSE, WebSocket support for MCP protocol
But if you don't need these features:
- You can access registry directly on port 7860 (HTTP, no SSL)
- You can use your own gateway for SSL/auth/routing
Summary
| Mode | Gateway Container | Registry Serves Web UI | MCP Servers | Use Case |
|---|---|---|---|---|
| Full | ✅ Runs (NGINX) | ✅ Via gateway proxy | ✅ Local | Default deployment |
| Registry-Only | ❌ Not started | ✅ Direct (port 7860) | ✅ Customer routes to them | Enterprise with own gateway |
| Standalone Registry | ❌ Not started | ✅ Direct (port 7860) | ❌ Run elsewhere | Centralized management only |
Bottom line: The registry container ALWAYS serves the Web UI (via FastAPI). The gateway is OPTIONAL and just adds SSL/auth/routing on top.
New Project Structure
Directory Layout
mcp-gateway-registry/├── gateway/ # NEW FOLDER: All gateway code│ ├── config_generator.py # Generates nginx.conf (moved from registry/)│ ├── config.py # Gateway-specific settings│ ├── templates/│ │ ├── nginx_http_https.conf # NGINX template files│ │ └── nginx_http_only.conf│ ├── main.py # Python service that watches registry│ └── requirements.txt # Python deps (requests, etc.)│├── registry/ # CLEANED UP: No gateway code│ ├── core/│ │ └── nginx_service.py # DELETE THIS FILE│ ├── main.py # REMOVE nginx operations (lines 115-120)│ └── ... # Everything else stays same│├── docker/│ ├── Dockerfile.gateway # NEW: Builds gateway container│ │ # Contains: NGINX + Python + gateway/ code│ ├── Dockerfile.registry-only # NEW: Builds registry container│ │ # Contains: ONLY Python + registry/ code│ ├── start-gateway.sh # NEW: Starts both NGINX and Python in gateway│ └── Dockerfile.registry # KEEP for backward compatibility (optional)│└── docker-compose.yml # UPDATED: Two separate servicesImplementation Plan
Phase 1: Create Gateway Folder and Move Code
Step 1.1: Create gateway folder structure
mkdir -p gateway/templates
Step 1.2: Move NGINX-related code
# Move nginx_service.py to gateway folder (and rename it)mv registry/core/nginx_service.py gateway/config_generator.py# Move NGINX templates to gateway foldermv docker/nginx_rev_proxy_http_and_https.conf gateway/templates/mv docker/nginx_rev_proxy_http_only.conf gateway/templates/
Step 1.3: Create gateway/main.py
This file polls the registry API and updates NGINX config:
#!/usr/bin/env python3"""Gateway Configuration ServiceWatches registry for server changes and updates NGINX config"""importasyncioimportloggingimporthttpxfrom .config_generatorimportNginxConfigGeneratorlogging.basicConfig(level=logging.INFO,format="%(asctime)s,p%(process)s,{%(filename)s:%(lineno)d},%(levelname)s,%(message)s",)logger=logging.getLogger(__name__)# Registry API URLREGISTRY_API_URL="http://registry:7860/api/servers"# Poll interval in secondsPOLL_INTERVAL=30asyncdefwatch_registry():"""Poll registry API for server changes and regenerate NGINX config"""nginx_generator=NginxConfigGenerator()whileTrue:try:logger.info(f"Polling registry API:{REGISTRY_API_URL}")# Get server list from registryasyncwithhttpx.AsyncClient()asclient:response=awaitclient.get(REGISTRY_API_URL)response.raise_for_status()servers_data=response.json()# Extract enabled serversenabled_servers= {path:infoforpath,infoinservers_data.items()ifinfo.get("enabled",False) }logger.info(f"Found{len(enabled_servers)} enabled servers")# Generate NGINX configsuccess=awaitnginx_generator.generate_config_async(enabled_servers)ifsuccess:logger.info("NGINX configuration updated successfully")else:logger.error("Failed to update NGINX configuration")exceptExceptionase:logger.error(f"Error polling registry:{e}",exc_info=True)# Wait before next pollawaitasyncio.sleep(POLL_INTERVAL)if__name__=="__main__":logger.info("Starting Gateway Configuration Service")logger.info(f"Polling interval:{POLL_INTERVAL} seconds")asyncio.run(watch_registry())
Step 1.4: Create gateway/init.py
Make gateway a proper Python module:
"""Gateway configuration service for MCP Gateway Registry"""Step 1.5: Create gateway/requirements.txt
httpx>=0.24.0Step 1.6: Create gateway/config.py
This file handles gateway settings and reads from environment variables:
importosclassGatewaySettings:"""Gateway configuration settings"""# Registry API endpoint to poll for server listregistry_api_url:str=os.getenv("REGISTRY_API_URL","http://registry:7860/api/servers" )# How often to poll registry (seconds)poll_interval:int=int(os.getenv("POLL_INTERVAL","30"))# NGINX config pathsnginx_config_dir:str="/etc/nginx"nginx_templates_dir:str="/etc/nginx/templates"settings=GatewaySettings()
Step 1.7: Update gateway/main.py to use environment variables
Change the hardcoded values to use settings:
importosfromgateway.configimportsettings# Use settings instead of hardcoded valuesREGISTRY_API_URL=settings.registry_api_urlPOLL_INTERVAL=settings.poll_interval
Step 1.8: Update gateway/config_generator.py
After moving from registry/ to gateway/, update the file:
- Update imports: Change any cross-module imports
# OLD (when in registry/core/):fromregistry.constantsimportNGINX_CONFIG_DIR# NEW (now in gateway/):fromgateway.configimportsettings
- Update config paths: Make sure NGINX config generation points to correct locations
# Use gateway settings for pathsconfig_path=f"{settings.nginx_config_dir}/nginx.conf"template_path=f"{settings.nginx_templates_dir}/nginx_http_https.conf"
- Handle missing templates gracefully: Add error handling
importosfrompathlibimportPathdef_load_template(self,template_name:str)->str:"""Load NGINX template file"""template_path=Path(settings.nginx_templates_dir)/template_nameifnottemplate_path.exists():logger.error(f"Template not found:{template_path}")raiseFileNotFoundError(f"NGINX template not found:{template_name}")withopen(template_path,'r')asf:returnf.read()
Step 1.9: Add error handling and startup logic
Updategateway/main.py to handle errors gracefully:
asyncdefwatch_registry():"""Poll registry API for server changes and regenerate NGINX config"""nginx_generator=NginxConfigGenerator()# Wait for registry to be available before startingawait_wait_for_registry()whileTrue:try:logger.info(f"Polling registry API:{REGISTRY_API_URL}")# Get server list from registryasyncwithhttpx.AsyncClient(timeout=10.0)asclient:response=awaitclient.get(REGISTRY_API_URL)response.raise_for_status()servers_data=response.json()# Extract enabled serversenabled_servers= {path:infoforpath,infoinservers_data.items()ifinfo.get("enabled",False) }logger.info(f"Found{len(enabled_servers)} enabled servers")# Generate NGINX configsuccess=awaitnginx_generator.generate_config_async(enabled_servers)ifsuccess:logger.info("NGINX configuration updated successfully")else:logger.error("Failed to update NGINX configuration")excepthttpx.HTTPStatusErrorase:logger.error(f"HTTP error polling registry:{e.response.status_code}")excepthttpx.RequestErrorase:logger.error(f"Network error polling registry:{e}")exceptExceptionase:logger.error(f"Unexpected error:{e}",exc_info=True)# Wait before next pollawaitasyncio.sleep(POLL_INTERVAL)asyncdef_wait_for_registry():"""Wait for registry to become available before starting main loop"""logger.info("Waiting for registry to become available...")max_retries=30retry_delay=2forattemptinrange(max_retries):try:asyncwithhttpx.AsyncClient(timeout=5.0)asclient:response=awaitclient.get(REGISTRY_API_URL)ifresponse.status_code==200:logger.info("Registry is available!")returnexceptExceptionase:logger.debug(f"Registry not ready (attempt{attempt+1}/{max_retries}):{e}")awaitasyncio.sleep(retry_delay)logger.warning("Registry did not become available, continuing anyway...")
Step 1.10: Update gateway/config_generator.py to reload NGINX
Add method to reload NGINX after config generation:
importsubprocessimportlogginglogger=logging.getLogger(__name__)classNginxConfigGenerator:# ... existing code ...defreload_nginx(self)->bool:"""Reload NGINX to apply new configuration"""try:# Test config firstresult=subprocess.run( ["nginx","-t"],capture_output=True,text=True,check=False )ifresult.returncode!=0:logger.error(f"NGINX config test failed:{result.stderr}")returnFalse# Reload NGINXresult=subprocess.run( ["nginx","-s","reload"],capture_output=True,text=True,check=False )ifresult.returncode==0:logger.info("NGINX reloaded successfully")returnTrueelse:logger.error(f"NGINX reload failed:{result.stderr}")returnFalseexceptExceptionase:logger.error(f"Error reloading NGINX:{e}",exc_info=True)returnFalse
Phase 2: Clean Up Registry Code
Step 2.1: Remove NGINX code from registry/main.py
Find and delete these lines (around line 115-120):
# DELETE THIS BLOCK:logger.info("🌐 Generating initial Nginx configuration...")enabled_servers= {path:server_service.get_server_info(path)forpathinserver_service.get_enabled_services()}awaitnginx_service.generate_config_async(enabled_servers)
Step 2.2: Remove nginx_service import
Find and delete this import fromregistry/main.py:
# DELETE THIS LINE:fromregistry.core.nginx_serviceimportnginx_service
Step 2.3: Delete registry/core/nginx_service.py
This file has been moved to gateway/, so delete the old copy:
rm registry/core/nginx_service.py
Step 2.4: Remove NGINX reload calls from server toggle operations
Find where servers are enabled/disabled (likely inregistry/api/server_routes.py) and remove NGINX reload calls.
Search for calls like:
awaitnginx_service.generate_config_async(...)
And delete them. The gateway will pick up changes automatically via polling.
Phase 3: Create Dockerfiles
Step 3.1: Create docker/Dockerfile.gateway
This builds the gateway container with NGINX + Python:
FROM nginx:alpine# Install Python in the nginx containerRUN apk add --no-cache python3 py3-pip# Copy gateway Python codeCOPY gateway/ /app/gateway/COPY gateway/requirements.txt /app/RUN pip3 install --no-cache-dir -r /app/requirements.txt# Copy NGINX config templatesCOPY gateway/templates/ /etc/nginx/templates/# Copy SSL certificates (if they exist)COPY ssl/ /etc/ssl/ 2>/dev/null || trueEXPOSE 80 443# Start both NGINX and the Python config generatorCOPY docker/start-gateway.sh /start-gateway.shRUN chmod +x /start-gateway.shCMD ["/start-gateway.sh"]
Step 3.2: Create initial NGINX configuration
Creategateway/templates/nginx_initial.conf - used before registry is available:
events{worker_connections1024;}http{ # Basic settingssendfile on;tcp_nopush on;tcp_nodelay on;keepalive_timeout65;types_hash_max_size2048; # Loggingaccess_log /var/log/nginx/access.log;error_log /var/log/nginx/error.log; # Basic server - will be replaced by dynamic configserver{listen80 default_server;server_name _;location /{return503"Gateway initializing, please wait...";add_header Content-Type text/plain;}location /health{return200"OK";add_header Content-Type text/plain;}}}
Step 3.3: Create docker/start-gateway.sh
This script initializes NGINX config then starts both NGINX and Python watcher:
#!/bin/shset -eecho"Initializing Gateway..."# Create necessary directoriesmkdir -p /etc/nginx/conf.dmkdir -p /var/log/nginx# Copy initial NGINX config if main config doesn't existif [!-f /etc/nginx/nginx.conf ];thenecho"Creating initial NGINX configuration..." cp /app/gateway/templates/nginx_initial.conf /etc/nginx/nginx.conffi# Test NGINX configurationecho"Testing NGINX configuration..."nginx -t# Start NGINX in backgroundecho"Starting NGINX..."nginx -g'daemon off;'&NGINX_PID=$!# Give NGINX a moment to startsleep 2# Start Python config generator (watches registry)echo"Starting Gateway Configuration Service..."cd /apppython3 -m gateway.main&PYTHON_PID=$!# Function to handle shutdownshutdown() {echo"Shutting down..."kill$NGINX_PID2>/dev/null||truekill$PYTHON_PID2>/dev/null||trueexit 0}# Trap SIGTERM and SIGINTtrap shutdown SIGTERM SIGINT# Wait for both processeswait
Step 3.3: Create docker/Dockerfile.registry-only
This builds the registry container with ONLY FastAPI (no NGINX):
FROM python:3.11-slim# Copy requirements and installCOPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt# Copy ONLY registry application code (no gateway code)COPY registry/ /app/registry/COPY auth_server/ /app/auth_server/COPY frontend/build/ /app/frontend/build/WORKDIR /app# Only FastAPI - NO NGINX!EXPOSE 7860CMD ["uvicorn","registry.main:app","--host","0.0.0.0","--port","7860"]
Key Differences Between Dockerfiles:
| Dockerfile.gateway | Dockerfile.registry-only |
|---|---|
Based onnginx:alpine | Based onpython:3.11-slim |
| Has NGINX server | NO NGINX |
Has Python +gateway/ code | Has Python +registry/ code |
| Runs 2 processes (NGINX + Python) | Runs 1 process (FastAPI only) |
| Exposes ports 80, 443 | Exposes port 7860 only |
Containsgateway/config_generator.py | NO gateway code at all |
| Watches registry API | Doesn't watch anything |
Phase 4: Update docker-compose.yml
Step 4.1: Split registry into two services
Updatedocker-compose.yml to have separate gateway and registry services:
services:# NEW: Separate gateway servicegateway:build:context:.dockerfile:docker/Dockerfile.gatewaycontainer_name:mcp-gatewayports: -"80:80" -"443:443"environment: -REGISTRY_API_URL=http://registry:7860/api/servers -POLL_INTERVAL=30volumes: -${HOME}/mcp-gateway/ssl:/etc/ssl:ro -gateway-logs:/var/log/nginxdepends_on:registry:condition:service_healthyauth-server:condition:service_startedhealthcheck:test:["CMD", "curl", "-f", "http://localhost/health"]interval:30stimeout:10sretries:3start_period:40srestart:unless-stoppednetworks: -mcp-network# UPDATED: Registry without NGINXregistry:build:context:.dockerfile:docker/Dockerfile.registry-onlycontainer_name:mcp-registryports: -"7860:7860"environment: -AUTH_SERVER_URL=http://auth-server:8888 -METRICS_SERVICE_URL=http://metrics-service:8890volumes: -${HOME}/mcp-gateway/servers:/app/registry/servers -${HOME}/mcp-gateway/logs/registry:/app/logsdepends_on: -auth-server -metrics-service -keycloakhealthcheck:test:["CMD", "curl", "-f", "http://localhost:7860/health"]interval:30stimeout:10sretries:3start_period:10srestart:unless-stoppednetworks: -mcp-network# All other services stay the same (auth-server, metrics-service, keycloak, mcp servers)auth-server:# ... (unchanged)metrics-service:# ... (unchanged)keycloak:# ... (unchanged)# MCP servers...fininfo-server:# ... (unchanged)networks:mcp-network:driver:bridge
Phase 5: Create Registry-Only Deployment
Step 5.1: Create docker-compose-registry-only.yml
For customers who want to use their own gateway:
services:# NO gateway service - customer provides their ownregistry:build:context:.dockerfile:docker/Dockerfile.registry-onlyports: -"7860:7860"environment: -AUTH_SERVER_URL=http://auth-server:8888 -METRICS_SERVICE_URL=http://metrics-service:8890 -TRUST_PROXY_HEADERS=true# Trust X-Forwarded-* headers from customer gatewayvolumes: -${HOME}/mcp-gateway/servers:/app/registry/serversdepends_on: -auth-server -metrics-service -keycloakrestart:unless-stoppednetworks: -mcp-networkauth-server:# Same as main docker-compose.yml# ...metrics-service:# Same as main docker-compose.yml# ...keycloak:# Same as main docker-compose.yml# ...# MCP servers (customer gateway will route to these)fininfo-server:# Same as main docker-compose.yml# ...networks:mcp-network:driver:bridge
Phase 6: Testing
Step 6.1: Test Combined Mode (gateway + registry)
# Build and start both containersdocker-compose builddocker-compose up# Verify:# 1. Gateway container is running with both NGINX and Python# 2. Registry container is running with only FastAPI# 3. Web UI accessible at http://localhost# 4. Add/remove servers and verify NGINX config updates (check logs)
Step 6.2: Test Registry-Only Mode
# Start registry-only stackdocker-compose -f docker-compose-registry-only.yml up# Set up a custom NGINX on host machine pointing to registry:7860# Verify registry works without our gateway
Step 6.3: Verify No Breaking Changes
- Existing users should be able to run
docker-compose upwithout changes - All functionality should work exactly as before
- Check logs for any errors
Phase 7: Documentation
Step 7.1: Update README.md
Add sections explaining both deployment modes:
- Combined deployment (default)
- Registry-only deployment
Step 7.2: Create Custom Gateway Integration Guide
Document requirements for organizations using their own gateway.
Step 7.3: Add deployment examples
Include examples for:
- AWS API Gateway integration
- Kong integration
- Custom NGINX configuration
Container Details
Gateway Container - What's Inside
Contains:
- ✅ NGINX server (binary)
- ✅ NGINX config file (nginx.conf)
- ✅ Python 3
- ✅
gateway/folder code - ✅
gateway/main.py- polls registry API every 30 seconds - ✅
gateway/config_generator.py- generates nginx.conf from server list - ✅ SSL certificates (if provided)
Responsibilities:
- NGINX listens on ports 80/443
- Python script polls
http://registry:7860/api/serversevery 30 seconds - When servers change, regenerates nginx.conf
- Reloads NGINX with new config
- Routes traffic to auth-server, registry, MCP servers
Does NOT contain:
- ❌ No registry code
- ❌ No FastAPI
- ❌ No web UI
- ❌ No server management logic
Registry Container - What's Inside
Contains:
- ✅ Python 3
- ✅ FastAPI application
- ✅
registry/folder code - ✅ Web UI (HTML/CSS/JS)
- ✅ Server management logic
- ✅ FAISS search index
- ✅ Health monitoring
Responsibilities:
- Runs FastAPI on port 7860
- Serves web UI
- Provides API endpoints:
GET /api/servers- list all serversPOST /api/servers- add serverPUT /api/servers/{id}- update serverDELETE /api/servers/{id}- remove server
- Manages server state (enabled/disabled)
- Health checks MCP servers
- Tool discovery with FAISS
Does NOT contain:
- ❌ No NGINX
- ❌ No nginx.conf
- ❌ No NGINX management code
- ❌ Doesn't know about gateway at all
Communication Flow
How the system works after separation:
1. User adds server in Web UI ↓2. POST to registry:7860/api/servers ↓3. Registry saves server to servers.json ↓4. Registry returns 200 OK ↓5. (30 seconds pass) ↓6. Gateway polls registry API ↓7. Gateway sees new server in response ↓8. Gateway regenerates nginx.conf ↓9. Gateway reloads NGINX ↓10. New server is now routable through gatewayKey Point: Registry and Gateway don't directly communicate. Gateway just polls registry's public API. This is a clean separation.
Custom Gateway Integration
For organizations that want to use their own gateway instead of ours, document these requirements:
Requirements for Custom Gateway
Call auth server for validation
For each incoming request: 1. Call GET http://auth-server:8888/auth/validate 2. Pass Authorization header from original request 3. If 200 OK: forward X-User and X-Scopes headers to backend 4. If 401/403: reject the requestRoute traffic to appropriate backends
/ → http://registry:7860 (Web UI and REST APIs)/fininfo/ → http://fininfo-server:8001/mcpgw/ → http://mcpgw-server:8003/currenttime/ → http://currenttime-server:8000/atlassian/ → http://atlassian-server:8005Support required protocols
- HTTP/HTTPS
- Server-Sent Events (SSE) for MCP protocol
- WebSocket for real-time health updates
- Long-running connections
Forward required headers
X-Forwarded-ForX-Forwarded-ProtoX-Real-IPAuthorization(to auth-server)X-UserandX-Scopes(from auth-server to backends)
Implementation Phases
| Phase | Task |
|---|---|
| 1 | Create gateway folder and move code |
| 2 | Clean up registry code |
| 3 | Create Dockerfiles |
| 4 | Update docker-compose.yml |
| 5 | Create registry-only compose file |
| 6 | Testing (combined + registry-only) |
| 7 | Documentation |
Success Criteria
- Gateway container runs independently with NGINX + Python watcher
- Registry container runs independently with only FastAPI
- Gateway polls registry API every 30 seconds
- NGINX config regenerates when servers are added/removed
- Combined deployment works (docker-compose up)
- Registry-only deployment works (docker-compose-registry-only.yml)
- Registry supports HTTPS in standalone mode (with SSL certificates)
- No breaking changes for existing users
- Documentation updated with both deployment modes
- All tests pass
- Security scans pass (bandit)
HTTPS Configuration for Registry-Only Mode
CRITICAL: Registry Must Support HTTPS Without Gateway
When running in registry-only mode (without the gateway), the registry must be able to serve HTTPS directly. This is essential for production deployments.
Implementation: FastAPI with SSL/TLS Support
Step 1: Update registry/main.py to support SSL
Add SSL configuration at the end ofregistry/main.py:
importsslimportosfrompathlibimportPath# SSL ConfigurationSSL_ENABLED=os.getenv("SSL_ENABLED","false").lower()=="true"SSL_CERTFILE=os.getenv("SSL_CERTFILE","/etc/ssl/cert.pem")SSL_KEYFILE=os.getenv("SSL_KEYFILE","/etc/ssl/key.pem")if__name__=="__main__":importuvicornifSSL_ENABLED:# Verify SSL files existcert_path=Path(SSL_CERTFILE)key_path=Path(SSL_KEYFILE)ifnotcert_path.exists():logger.error(f"SSL certificate not found:{SSL_CERTFILE}")raiseFileNotFoundError(f"SSL certificate not found:{SSL_CERTFILE}")ifnotkey_path.exists():logger.error(f"SSL key not found:{SSL_KEYFILE}")raiseFileNotFoundError(f"SSL key not found:{SSL_KEYFILE}")logger.info("Starting registry with HTTPS enabled")logger.info(f"SSL Certificate:{SSL_CERTFILE}")uvicorn.run("registry.main:app",host="0.0.0.0",port=7860,ssl_certfile=SSL_CERTFILE,ssl_keyfile=SSL_KEYFILE,ssl_keyfile_password=os.getenv("SSL_KEYFILE_PASSWORD") )else:logger.info("Starting registry with HTTP only (no SSL)")uvicorn.run("registry.main:app",host="0.0.0.0",port=7860 )
Step 2: Update Dockerfile.registry-only to support SSL certificates
FROM python:3.11-slim# Install dependenciesCOPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt# Copy application codeCOPY registry/ /app/registry/COPY auth_server/ /app/auth_server/COPY frontend/build/ /app/frontend/build/WORKDIR /app# Expose HTTP and HTTPS portsEXPOSE 7860# Set Python pathENV PYTHONPATH=/app# Run registry with SSL supportCMD ["python","-m","registry.main"]
Step 3: Update docker-compose-registry-only.yml with SSL configuration
services:registry:build:context:.dockerfile:docker/Dockerfile.registry-onlycontainer_name:mcp-registryports: -"7860:7860"# HTTPS when SSL_ENABLED=true, HTTP otherwiseenvironment: -AUTH_SERVER_URL=http://auth-server:8888 -METRICS_SERVICE_URL=http://metrics-service:8890 -TRUST_PROXY_HEADERS=true# SSL Configuration for standalone HTTPS -SSL_ENABLED=true -SSL_CERTFILE=/etc/ssl/cert.pem -SSL_KEYFILE=/etc/ssl/key.pem# Optional: SSL key password# - SSL_KEYFILE_PASSWORD=${SSL_KEYFILE_PASSWORD}volumes: -${HOME}/mcp-gateway/servers:/app/registry/servers -${HOME}/mcp-gateway/logs/registry:/app/logs# Mount SSL certificates for HTTPS support -${HOME}/mcp-gateway/ssl/cert.pem:/etc/ssl/cert.pem:ro -${HOME}/mcp-gateway/ssl/key.pem:/etc/ssl/key.pem:rodepends_on: -auth-server -metrics-service -keycloakhealthcheck:# Use HTTPS for health check when SSL is enabledtest:["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('https://localhost:7860/health' if ${SSL_ENABLED:-false} else 'http://localhost:7860/health')"]interval:30stimeout:10sretries:3start_period:10srestart:unless-stoppednetworks: -mcp-network
Step 4: Generate SSL certificates for testing
Create a helper scriptscripts/generate-ssl-certs.sh:
#!/bin/bashset -eSCRIPT_DIR="$(cd"$( dirname"${BASH_SOURCE[0]}")"&&pwd)"SSL_DIR="${HOME}/mcp-gateway/ssl"echo"Creating SSL directory..."mkdir -p"$SSL_DIR"echo"Generating self-signed SSL certificate..."openssl req -x509 -newkey rsa:4096 -nodes \ -keyout"$SSL_DIR/key.pem" \ -out"$SSL_DIR/cert.pem" \ -days 365 \ -subj"/C=US/ST=State/L=City/O=Organization/CN=localhost"echo"Setting secure permissions..."chmod 600"$SSL_DIR/key.pem"chmod 644"$SSL_DIR/cert.pem"echo"SSL certificates generated successfully:"echo" Certificate:$SSL_DIR/cert.pem"echo" Private Key:$SSL_DIR/key.pem"echo""echo"For production, replace these with certificates from a trusted CA."
Step 5: Access Registry with HTTPS
With SSL enabled:
# Generate certs (first time only)bash scripts/generate-ssl-certs.sh# Start registry-only mode with SSLdocker-compose -f docker-compose-registry-only.yml up -d# Access via HTTPScurl -k https://localhost:7860/health# Or in browseropen https://localhost:7860
SSL Configuration Summary
| Mode | SSL/TLS | Port | Access URL | Certificate Location |
|---|---|---|---|---|
| Gateway (default) | ✅ Gateway handles | 443 | https://your-domain.com | Mounted in gateway container |
| Registry-Only (HTTP) | ❌ Disabled | 7860 | http://registry-host:7860 | Not needed |
| Registry-Only (HTTPS) | ✅ Registry handles | 7860 | https://registry-host:7860 | Mounted in registry container |
Build and Deployment Scripts
Create Build Scripts for ECR Deployment
Step 1: Create scripts/build-and-push-gateway.sh
#!/bin/bash# Build and push gateway container to Amazon ECRset -eSCRIPT_DIR="$(cd"$( dirname"${BASH_SOURCE[0]}")"&&pwd)"PARENT_DIR="$(dirname"$SCRIPT_DIR")"# ConfigurationAWS_REGION="${AWS_REGION:-us-east-1}"ECR_REPO_NAME="mcp-gateway"AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)ECR_REPO_URI="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO_NAME"echo"Building and pushing Gateway container..."echo"Region:$AWS_REGION"echo"Repository:$ECR_REPO_NAME"# Login to Amazon ECRecho"Logging in to Amazon ECR..."aws ecr get-login-password --region$AWS_REGION| \ docker login --username AWS --password-stdin \"$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com"# Create repository if it doesn't existecho"Creating ECR repository if it doesn't exist..."aws ecr describe-repositories --repository-names"$ECR_REPO_NAME" --region"$AWS_REGION"2>/dev/null|| \ aws ecr create-repository --repository-name"$ECR_REPO_NAME" --region"$AWS_REGION"# Build the Docker imageecho"Building Gateway Docker image..."docker build -f"$PARENT_DIR/docker/Dockerfile.gateway" -t"$ECR_REPO_NAME""$PARENT_DIR"# Tag the imageecho"Tagging image..."docker tag"$ECR_REPO_NAME":latest"$ECR_REPO_URI":latest# Push the image to ECRecho"Pushing image to ECR..."docker push"$ECR_REPO_URI":latestecho"Successfully built and pushed gateway image:"echo"$ECR_REPO_URI:latest"# Save the container URI to a file for referenceecho"$ECR_REPO_URI:latest">"$SCRIPT_DIR/.gateway_container_uri"echo"Container URI saved to$SCRIPT_DIR/.gateway_container_uri"
Step 2: Create scripts/build-and-push-registry.sh
#!/bin/bash# Build and push registry container to Amazon ECRset -eSCRIPT_DIR="$(cd"$( dirname"${BASH_SOURCE[0]}")"&&pwd)"PARENT_DIR="$(dirname"$SCRIPT_DIR")"# ConfigurationAWS_REGION="${AWS_REGION:-us-east-1}"ECR_REPO_NAME="mcp-registry"AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)ECR_REPO_URI="$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com/$ECR_REPO_NAME"echo"Building and pushing Registry container..."echo"Region:$AWS_REGION"echo"Repository:$ECR_REPO_NAME"# Login to Amazon ECRecho"Logging in to Amazon ECR..."aws ecr get-login-password --region$AWS_REGION| \ docker login --username AWS --password-stdin \"$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com"# Create repository if it doesn't existecho"Creating ECR repository if it doesn't exist..."aws ecr describe-repositories --repository-names"$ECR_REPO_NAME" --region"$AWS_REGION"2>/dev/null|| \ aws ecr create-repository --repository-name"$ECR_REPO_NAME" --region"$AWS_REGION"# Build the Docker imageecho"Building Registry Docker image..."docker build -f"$PARENT_DIR/docker/Dockerfile.registry-only" -t"$ECR_REPO_NAME""$PARENT_DIR"# Tag the imageecho"Tagging image..."docker tag"$ECR_REPO_NAME":latest"$ECR_REPO_URI":latest# Push the image to ECRecho"Pushing image to ECR..."docker push"$ECR_REPO_URI":latestecho"Successfully built and pushed registry image:"echo"$ECR_REPO_URI:latest"# Save the container URI to a file for referenceecho"$ECR_REPO_URI:latest">"$SCRIPT_DIR/.registry_container_uri"echo"Container URI saved to$SCRIPT_DIR/.registry_container_uri"
Step 3: Make scripts executable
chmod +x scripts/build-and-push-gateway.shchmod +x scripts/build-and-push-registry.shchmod +x scripts/generate-ssl-certs.sh
Security and Code Quality
Phase 8: Security Validation
Add security scanning to the implementation plan:
Step 8.1: Run Bandit security scanner on gateway code
# Scan new gateway code for security issuesuv run bandit -r gateway/# Generate reportuv run bandit -r gateway/ -f json -o reports/bandit-gateway.json
Step 8.2: Scan registry code
# Scan registry codeuv run bandit -r registry/# Generate reportuv run bandit -r registry/ -f json -o reports/bandit-registry.json
Step 8.3: Docker image security scanning
# Scan gateway imagedocker scan docker/Dockerfile.gateway# Scan registry imagedocker scan docker/Dockerfile.registry-only
Secrets Management Best Practices
Never commit these files:
- SSL certificates (
*.pem,*.crt,*.key) - Environment files with credentials (
.env) - Database credentials
- API keys
Add to .gitignore:
# SSL Certificates*.pem*.crt*.key*.p12# Environment files.env.env.local.env.production# Container URIs (from build scripts)scripts/.gateway_container_uriscripts/.registry_container_uri# Security reportsreports/
Testing Strategy
Unit Tests
Create tests/gateway/test_config_generator.py
importpytestfromunittest.mockimportMock,patchfromgateway.config_generatorimportNginxConfigGeneratorclassTestNginxConfigGenerator:"""Tests for NGINX config generation"""deftest_generate_config_with_enabled_servers(self):"""Test config generation with enabled servers"""generator=NginxConfigGenerator()servers= {"fininfo": {"enabled":True,"port":8001,"protocol":"http" } }config=generator._generate_config(servers)assert"fininfo"inconfigassert"8001"inconfigdeftest_generate_config_empty_servers(self):"""Test config generation with no servers"""generator=NginxConfigGenerator()config=generator._generate_config({})assertconfigisnotNoneassertlen(config)>0@patch('subprocess.run')deftest_reload_nginx_success(self,mock_run):"""Test successful NGINX reload"""mock_run.return_value=Mock(returncode=0,stderr="")generator=NginxConfigGenerator()result=generator.reload_nginx()assertresultisTrueassertmock_run.call_count==2# test + reload@patch('subprocess.run')deftest_reload_nginx_config_test_fails(self,mock_run):"""Test NGINX reload when config test fails"""mock_run.return_value=Mock(returncode=1,stderr="config error")generator=NginxConfigGenerator()result=generator.reload_nginx()assertresultisFalse
Create tests/registry/test_ssl_config.py
importpytestimportosfrompathlibimportPathclassTestRegistrySSLConfig:"""Tests for registry SSL configuration"""deftest_ssl_enabled_environment_variable(self):"""Test SSL enabled via environment variable"""os.environ["SSL_ENABLED"]="true"# Import after setting env varfromregistry.mainimportSSL_ENABLEDassertSSL_ENABLEDisTruedeftest_ssl_disabled_by_default(self):"""Test SSL disabled by default"""os.environ.pop("SSL_ENABLED",None)fromregistry.mainimportSSL_ENABLEDassertSSL_ENABLEDisFalsedeftest_ssl_cert_paths_configurable(self):"""Test SSL cert paths are configurable"""test_cert="/custom/path/cert.pem"test_key="/custom/path/key.pem"os.environ["SSL_CERTFILE"]=test_certos.environ["SSL_KEYFILE"]=test_keyfromregistry.mainimportSSL_CERTFILE,SSL_KEYFILEassertSSL_CERTFILE==test_certassertSSL_KEYFILE==test_key
Integration Tests
Create tests/integration/test_deployment_modes.py
importpytestimporthttpximportasyncioimportdocker@pytest.mark.asyncioclassTestDeploymentModes:"""Integration tests for different deployment modes"""asyncdeftest_full_mode_gateway_and_registry(self):"""Test full mode with both gateway and registry"""asyncwithhttpx.AsyncClient(verify=False)asclient:# Gateway should be accessiblegateway_response=awaitclient.get("http://localhost/health")assertgateway_response.status_code==200# Registry should be accessible through gatewayregistry_response=awaitclient.get("http://localhost/api/servers")assertregistry_response.status_code==200asyncdeftest_registry_only_mode_http(self):"""Test registry-only mode with HTTP"""asyncwithhttpx.AsyncClient()asclient:response=awaitclient.get("http://localhost:7860/health")assertresponse.status_code==200asyncdeftest_registry_only_mode_https(self):"""Test registry-only mode with HTTPS"""asyncwithhttpx.AsyncClient(verify=False)asclient:response=awaitclient.get("https://localhost:7860/health")assertresponse.status_code==200asyncdeftest_gateway_polls_registry(self):"""Test that gateway successfully polls registry"""# Add a server via registry APIasyncwithhttpx.AsyncClient()asclient:# Add test serverresponse=awaitclient.post("http://localhost:7860/api/servers",json={"name":"test-server","port":9999,"enabled":True } )assertresponse.status_code==200# Wait for gateway to pollawaitasyncio.sleep(35)# Check gateway config updated# (Would need to check NGINX config or test routing)
Environment Variables Reference
Complete Environment Variable Documentation
| Variable | Container | Default | Description |
|---|---|---|---|
| REGISTRY_API_URL | gateway | http://registry:7860/api/servers | Registry API endpoint to poll |
| POLL_INTERVAL | gateway | 30 | How often gateway polls registry (seconds) |
| SSL_ENABLED | gateway, registry | false | Enable HTTPS/SSL |
| SSL_CERTFILE | gateway, registry | /etc/ssl/cert.pem | Path to SSL certificate |
| SSL_KEYFILE | gateway, registry | /etc/ssl/key.pem | Path to SSL private key |
| SSL_KEYFILE_PASSWORD | gateway, registry | (none) | Password for encrypted SSL key |
| AUTH_SERVER_URL | registry | http://auth-server:8888 | Authentication server URL |
| METRICS_SERVICE_URL | registry | http://metrics-service:8890 | Metrics service URL |
| TRUST_PROXY_HEADERS | registry | false | Trust X-Forwarded-* headers from proxy |
| LOG_LEVEL | gateway, registry | INFO | Logging level (DEBUG, INFO, WARNING, ERROR) |
Operational Details
Logging
Gateway logs:
- NGINX access:
/var/log/nginx/access.log - NGINX error:
/var/log/nginx/error.log - Python service:
docker logs mcp-gateway
Registry logs:
- Application:
docker logs mcp-registry - Mounted volume:
~/mcp-gateway/logs/registry/
View live logs:
# Gateway logs (both NGINX and Python)docker logs -f mcp-gateway# Registry logsdocker logs -f mcp-registry# All servicesdocker-compose logs -f# Specific servicedocker-compose logs -f gatewaydocker-compose logs -f registry
Monitoring and Health Checks
Health check endpoints:
# Gateway healthcurl http://localhost/health# Registry health (direct)curl http://localhost:7860/health# Registry health (via gateway)curl http://localhost/api/health
Metrics to monitor:
- Gateway poll frequency (should be ~30s)
- NGINX reload success rate
- Registry API response times
- Container restart count
- SSL certificate expiration dates
Container resource limits (production):
services:gateway:deploy:resources:limits:cpus:'1.0'memory:512Mreservations:cpus:'0.5'memory:256Mregistry:deploy:resources:limits:cpus:'2.0'memory:2Greservations:cpus:'1.0'memory:1G
README.md Updates
Add Deployment Modes Section
Add this section to the main README.md:
##Deployment ModesMCP Gateway Registry supports three deployment modes to fit different infrastructure needs.###Mode 1: Full Stack with Gateway (Default)**What you get:**- NGINX gateway with SSL/TLS termination- Registry with Web UI- Authentication server (Keycloak integration)- Metrics service- All MCP servers**Quick start:**```bash# Clone repositorygit clone https://github.com/your-org/mcp-gateway-registry.gitcd mcp-gateway-registry# Start all servicesdocker-compose up -d# Access Web UIopen https://localhost
Best for: Most users who want a complete, ready-to-use solution
Mode 2: Registry-Only (Bring Your Own Gateway)
What you get:
- Registry with Web UI (HTTP or HTTPS)
- Authentication server
- Metrics service
- MCP servers (you route to them via your gateway)
Quick start:
# Generate SSL certificates (for HTTPS)bash scripts/generate-ssl-certs.sh# Start registry stackdocker-compose -f docker-compose-registry-only.yml up -d# Access via HTTPScurl -k https://localhost:7860/health
Configure your gateway to route traffic:
/→http://registry:7860(orhttps://registry:7860if SSL enabled)/fininfo/→http://fininfo-server:8001/mcpgw/→http://mcpgw-server:8003- etc.
Best for: Enterprises with existing API gateways (AWS API Gateway, Kong, Traefik, etc.)
Mode 3: Standalone Registry
What you get:
- Registry with Web UI only
- MCP servers run elsewhere (you manage them separately)
Quick start:
# Edit docker-compose-registry-only.yml# Remove or comment out MCP server services# Start registry onlydocker-compose -f docker-compose-registry-only.yml up registry auth-server keycloak
Best for: Organizations that want centralized server management but run MCP servers on different infrastructure
SSL/HTTPS Configuration
Full Mode (with Gateway)
Gateway handles all SSL/TLS:
# Place your SSL certificatesmkdir -p~/mcp-gateway/sslcp your-cert.pem~/mcp-gateway/ssl/cert.pemcp your-key.pem~/mcp-gateway/ssl/key.pem# Start servicesdocker-compose up -d# Access via HTTPS (gateway)open https://your-domain.com
Registry-Only Mode with HTTPS
Registry serves HTTPS directly:
# Generate self-signed cert (testing only)bash scripts/generate-ssl-certs.sh# Or use your own certificatesmkdir -p~/mcp-gateway/sslcp your-cert.pem~/mcp-gateway/ssl/cert.pemcp your-key.pem~/mcp-gateway/ssl/key.pem# Ensure SSL_ENABLED=true in docker-compose-registry-only.yml# Start servicesdocker-compose -f docker-compose-registry-only.yml up -d# Access via HTTPS (registry direct)curl -k https://localhost:7860/health
Production SSL certificates:
For production, use certificates from a trusted CA:
- Let's Encrypt (free)
- AWS Certificate Manager
- Your organization's CA
Custom Gateway Integration Examples
AWS API Gateway
# API Gateway HTTP API configurationResources:MCPRegistryIntegration:Type:AWS::ApiGatewayV2::IntegrationProperties:ApiId:!Ref HttpApiIntegrationType:HTTP_PROXYIntegrationUri:https://registry-host:7860IntegrationMethod:ANYPayloadFormatVersion:'1.0'TlsConfig:ServerNameToVerify:registry-host
Kong
# kong.ymlservices: -name:mcp-registryurl:https://registry:7860routes: -name:registry-routepaths: -/plugins: -name:jwt -name:rate-limitingconfig:minute:100
Traefik
# docker-compose.yml with Traefikservices:registry:labels: -"traefik.enable=true" -"traefik.http.routers.registry.rule=Host(`registry.example.com`)" -"traefik.http.routers.registry.tls=true" -"traefik.http.services.registry.loadbalancer.server.port=7860" -"traefik.http.services.registry.loadbalancer.server.scheme=https"
## Migration Guide### Migrating from Monolithic to Separated Architecture**For existing deployments:**#### Step 1: Backup Current State```bash# Stop current containersdocker-compose down# Backup configuration and datacp -r ~/mcp-gateway ~/mcp-gateway-backup-$(date +%Y%m%d)# Backup current git stategit branch backup-before-separationStep 2: Pull Latest Changes
# Update to latest versiongit pull origin main# Review changesgit log --oneline -10
Step 3: Rebuild Containers
# Clean rebuild (removes old images)docker-compose build --no-cache# Or rebuild specific servicesdocker-compose build gatewaydocker-compose build registry
Step 4: Update Configuration
# If using custom SSL certificates, ensure they're in the right locationls -la~/mcp-gateway/ssl/# Verify docker-compose.yml has correct volume mountsgrep -A 5"volumes:" docker-compose.yml
Step 5: Start New Architecture
# Start all servicesdocker-compose up -d# Watch logs for any errorsdocker-compose logs -f
Step 6: Verify Services
# Check containers are runningdocker ps| grep mcp-# Expected output:# mcp-gateway (new - separate container)# mcp-registry (new - separate container)# mcp-auth-server# mcp-metrics-service# mcp-keycloak# mcp-fininfo-server# etc.# Test gateway healthcurl http://localhost/health# Test registry health (direct)curl http://localhost:7860/health# Test Web UIopen https://localhost
Step 7: Verify Functionality
# Add a test server via Web UI# Toggle server on/off# Check gateway logs to confirm config regenerationdocker logs mcp-gateway| grep"NGINX configuration updated"
Rollback Plan
If issues occur:
# Stop new architecturedocker-compose down# Checkout previous versiongit checkout backup-before-separation# Restore backup if neededrm -rf~/mcp-gatewaymv~/mcp-gateway-backup-YYYYMMDD~/mcp-gateway# Start old versiondocker-compose up -d
Expected Changes
What changes:
- Two containers instead of one (gateway + registry)
- Gateway polls registry every 30 seconds (slight delay in config updates)
- Separate health endpoints
What stays the same:
- Web UI functionality
- API endpoints
- MCP server routing
- Authentication flow
- All features
Conclusion
This separation provides:
- Clean architecture - each container has single responsibility
- Enterprise adoption - organizations can use their own gateways
- No breaking changes - existing users continue unchanged
- Better scalability - independent scaling of gateway and registry
The separation completes the microservices architecture (auth, metrics, keycloak already separated) and enables broader enterprise adoption while maintaining all existing benefits.