Skip to content

Instantly share code, notes, and snippets.

@jmanhype
Created October 13, 2025 02:47
Show Gist options
  • Save jmanhype/f01840b00ad61e424bc182acbfe49315 to your computer and use it in GitHub Desktop.
Save jmanhype/f01840b00ad61e424bc182acbfe49315 to your computer and use it in GitHub Desktop.
πŸš€ Multi-Tier MCP Federation Architecture: apollo-mcp-server + IBM mcp-context-forge = Ultimate AI Gateway (Complete Deployment Guide for ZimaBoard Proxmox)

πŸš€ Multi-Tier MCP Federation Architecture

apollo-mcp-server + IBM mcp-context-forge = Ultimate API Gateway


🎯 Executive Summary

This guide shows how to build a production-grade, federated MCP gateway that exposes both REST and GraphQL APIs as unified MCP tools for AI agents.

Architecture:

  • Tier 1: Protocol-specific converters (apollo-mcp-server for GraphQL)
  • Tier 2: Central gateway (mcp-context-forge for REST + MCP federation)
  • Result: Single public endpoint for all your APIs with enterprise security

πŸ—οΈ The Complete Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  TIER 1: Protocol-Specific Converters                        β”‚
β”‚                                                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚
β”‚  β”‚  apollo-mcp-server (port 4000)                  β”‚        β”‚
β”‚  β”‚  Rust-based GraphQL β†’ MCP converter             β”‚        β”‚
β”‚  β”‚                                                  β”‚        β”‚
β”‚  β”‚  Exposes:                                        β”‚        β”‚
β”‚  β”‚  β”œβ”€ github_list_repos()                         β”‚        β”‚
β”‚  β”‚  β”œβ”€ github_create_issue()                       β”‚        β”‚
β”‚  β”‚  β”œβ”€ shopify_get_products()                      β”‚        β”‚
β”‚  β”‚  β”œβ”€ hasura_query_database()                     β”‚        β”‚
β”‚  β”‚  └─ Any GraphQL API as MCP tools                β”‚        β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚
β”‚                                                               β”‚
β”‚  (Future: Add more specialized converters)                   β”‚
β”‚  β”œβ”€ gRPC β†’ MCP converter                                     β”‚
β”‚  β”œβ”€ SOAP β†’ MCP converter                                     β”‚
β”‚  └─ Custom protocol converters                               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        ↓ (MCP protocol)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  TIER 2: Central Gateway & Aggregator                        β”‚
β”‚                                                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”        β”‚
β”‚  β”‚  IBM mcp-context-forge (port 3000)              β”‚        β”‚
β”‚  β”‚  Python-based MCP Gateway & Registry            β”‚        β”‚
β”‚  β”‚                                                  β”‚        β”‚
β”‚  β”‚  Functions:                                      β”‚        β”‚
β”‚  β”‚  β”œβ”€ Direct REST β†’ MCP conversion:               β”‚        β”‚
β”‚  β”‚  β”‚  β”œβ”€ Radarr (localhost:7878)                  β”‚        β”‚
β”‚  β”‚  β”‚  β”œβ”€ Sonarr (localhost:8989)                  β”‚        β”‚
β”‚  β”‚  β”‚  β”œβ”€ Plex (192.168.1.219:32400)               β”‚        β”‚
β”‚  β”‚  β”‚  └─ Prowlarr (localhost:9696)                β”‚        β”‚
β”‚  β”‚  β”‚                                               β”‚        β”‚
β”‚  β”‚  β”œβ”€ MCP Server Federation:                      β”‚        β”‚
β”‚  β”‚  β”‚  └─ apollo-mcp-server (localhost:4000)       β”‚        β”‚
β”‚  β”‚  β”‚                                               β”‚        β”‚
β”‚  β”‚  β”œβ”€ Unified Security Layer:                     β”‚        β”‚
β”‚  β”‚  β”‚  β”œβ”€ JWT authentication                        β”‚        β”‚
β”‚  β”‚  β”‚  β”œβ”€ OAuth support                             β”‚        β”‚
β”‚  β”‚  β”‚  β”œβ”€ Rate limiting                             β”‚        β”‚
β”‚  β”‚  β”‚  └─ API key management                        β”‚        β”‚
β”‚  β”‚  β”‚                                               β”‚        β”‚
β”‚  β”‚  β”œβ”€ Admin Web UI:                                β”‚        β”‚
β”‚  β”‚  β”‚  β”œβ”€ Real-time monitoring                      β”‚        β”‚
β”‚  β”‚  β”‚  β”œβ”€ Tool discovery                            β”‚        β”‚
β”‚  β”‚  β”‚  └─ Configuration management                  β”‚        β”‚
β”‚  β”‚  β”‚                                               β”‚        β”‚
β”‚  β”‚  └─ Observability:                               β”‚        β”‚
β”‚  β”‚     β”œβ”€ OpenTelemetry metrics                     β”‚        β”‚
β”‚  β”‚     β”œβ”€ Request logging                           β”‚        β”‚
β”‚  β”‚     └─ Performance analytics                     β”‚        β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜        β”‚
β”‚                                                               β”‚
β”‚  Unified Endpoint: http://localhost:3000                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                        ↓
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚   ngrok HTTPS Tunnel      β”‚
        β”‚   https://abc123.ngrok.io β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    ↓
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚   Public Internet         β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                    ↓
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  AI Clients                       β”‚
        β”‚  β”œβ”€ OpenAI Agent Builder          β”‚
        β”‚  β”œβ”€ Claude Desktop                β”‚
        β”‚  β”œβ”€ Custom AI applications        β”‚
        β”‚  └─ Any MCP-compatible client     β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ’‘ Why Use Both Together?

The Problem with Single-Tier Approach

Option A: Only mcp-context-forge

  • ❌ Limited to REST APIs only
  • ❌ No GraphQL support
  • ❌ Would need custom code for each GraphQL API

Option B: Only apollo-mcp-server

  • ❌ No REST API conversion
  • ❌ No unified security layer
  • ❌ No admin UI
  • ❌ No federation of multiple servers

The Solution: Multi-Tier Federation

Tier 1 (apollo-mcp-server):

  • βœ… Specialized GraphQL β†’ MCP conversion
  • βœ… Handles complex GraphQL schemas
  • βœ… Built by Apollo (GraphQL experts)
  • βœ… High-performance Rust implementation

Tier 2 (mcp-context-forge):

  • βœ… Direct REST β†’ MCP conversion
  • βœ… Federates multiple MCP servers (including apollo-mcp-server)
  • βœ… Unified authentication across all tools
  • βœ… Single admin UI for all services
  • βœ… Single public endpoint (one ngrok tunnel)
  • βœ… Enterprise security & observability

🎬 Real-World Use Cases

Use Case 1: Media Automation + GitHub Management

AI Agent: "Add The Matrix to Radarr and create a GitHub issue to track it"

Flow:
1. radarr_search_movie("The Matrix") β†’ mcp-context-forge β†’ Radarr REST API
2. radarr_add_movie(...) β†’ mcp-context-forge β†’ Radarr REST API
3. github_create_issue(...) β†’ mcp-context-forge β†’ apollo-mcp-server β†’ GitHub GraphQL

Use Case 2: E-commerce Inventory Sync

AI Agent: "Check Shopify inventory and download missing product data"

Flow:
1. shopify_get_products() β†’ mcp-context-forge β†’ apollo-mcp-server β†’ Shopify GraphQL
2. sabnzbd_add_nzb(...) β†’ mcp-context-forge β†’ SABnzbd REST API

Use Case 3: Multi-Platform Content Discovery

AI Agent: "Search Plex for sci-fi movies and check GitHub for related projects"

Flow:
1. plex_search("sci-fi") β†’ mcp-context-forge β†’ Plex REST API
2. github_search_repos("sci-fi movies") β†’ mcp-context-forge β†’ apollo-mcp-server β†’ GitHub GraphQL

πŸ“¦ Complete Deployment Guide

Prerequisites

Hardware: ZimaBoard with Proxmox (or any Docker host)

  • LXC 103 (192.168.1.175) with Docker installed
  • 2GB+ RAM available
  • 10GB+ storage

Software:

  • Docker & Docker Compose
  • ngrok account (free tier works)
  • API keys for services you want to expose

Network:

  • LAN access to services (Radarr, Sonarr, Plex, etc.)
  • Internet access for ngrok

πŸ› οΈ Step-by-Step Setup

Step 1: Prepare LXC 103 Environment

# SSH into Proxmox host
ssh [email protected]

# Enter LXC 103 container
pct enter 103

# Create project directory
mkdir -p /opt/mcp-gateway
cd /opt/mcp-gateway

# Verify Docker is running
docker ps

Step 2: Deploy apollo-mcp-server (GraphQL Tier)

Create Directory Structure

cd /opt/mcp-gateway
mkdir -p apollo-mcp-server/{config,operations}
cd apollo-mcp-server

Create Dockerfile

cat > Dockerfile <<'EOF'
FROM rust:1.75-slim as builder

WORKDIR /app

# Install dependencies
RUN apt-get update && apt-get install -y \
    git \
    pkg-config \
    libssl-dev \
    && rm -rf /var/lib/apt/lists/*

# Clone and build apollo-mcp-server
RUN git clone https://github.com/apollographql/apollo-mcp-server.git .
RUN cargo build --release

FROM debian:bookworm-slim

RUN apt-get update && apt-get install -y \
    ca-certificates \
    libssl3 \
    && rm -rf /var/lib/apt/lists/*

COPY --from=builder /app/target/release/apollo-mcp-server /usr/local/bin/

WORKDIR /app
COPY config /app/config
COPY operations /app/operations

EXPOSE 4000

CMD ["apollo-mcp-server", "--config", "/app/config/server.toml"]
EOF

Create Server Configuration

cat > config/server.toml <<'EOF'
[server]
host = "0.0.0.0"
port = 4000
transport = "http"

[logging]
level = "info"
format = "json"

# Add your GraphQL endpoints here
[[graphs]]
name = "github"
url = "https://api.github.com/graphql"
operations_dir = "/app/operations/github"

[graphs.headers]
Authorization = "Bearer ${GITHUB_TOKEN}"

[[graphs]]
name = "shopify"
url = "https://${SHOPIFY_STORE}.myshopify.com/admin/api/2024-01/graphql.json"
operations_dir = "/app/operations/shopify"

[graphs.headers]
X-Shopify-Access-Token = "${SHOPIFY_ACCESS_TOKEN}"
EOF

Create GitHub GraphQL Operations

mkdir -p operations/github

cat > operations/github/repos.graphql <<'EOF'
# List user repositories
query ListRepos($owner: String!, $first: Int = 100) {
  user(login: $owner) {
    repositories(first: $first, orderBy: {field: UPDATED_AT, direction: DESC}) {
      nodes {
        name
        description
        stargazerCount
        forkCount
        url
        primaryLanguage {
          name
        }
        updatedAt
      }
    }
  }
}

# Search repositories
query SearchRepos($query: String!, $first: Int = 50) {
  search(query: $query, type: REPOSITORY, first: $first) {
    nodes {
      ... on Repository {
        name
        description
        stargazerCount
        url
        owner {
          login
        }
      }
    }
  }
}

# Create issue
mutation CreateIssue($repositoryId: ID!, $title: String!, $body: String) {
  createIssue(input: {repositoryId: $repositoryId, title: $title, body: $body}) {
    issue {
      number
      url
      title
    }
  }
}

# List issues
query ListIssues($owner: String!, $repo: String!, $first: Int = 50) {
  repository(owner: $owner, name: $repo) {
    issues(first: $first, orderBy: {field: CREATED_AT, direction: DESC}) {
      nodes {
        number
        title
        state
        url
        createdAt
        author {
          login
        }
      }
    }
  }
}
EOF

Create Shopify GraphQL Operations (Optional)

mkdir -p operations/shopify

cat > operations/shopify/products.graphql <<'EOF'
# List products
query GetProducts($first: Int = 50, $query: String) {
  products(first: $first, query: $query) {
    edges {
      node {
        id
        title
        description
        handle
        productType
        vendor
        tags
        status
        createdAt
        updatedAt
        variants(first: 10) {
          edges {
            node {
              id
              title
              price
              inventoryQuantity
              sku
            }
          }
        }
        images(first: 5) {
          edges {
            node {
              url
              altText
            }
          }
        }
      }
    }
  }
}

# Create product
mutation CreateProduct($title: String!, $descriptionHtml: String, $productType: String) {
  productCreate(input: {
    title: $title
    descriptionHtml: $descriptionHtml
    productType: $productType
  }) {
    product {
      id
      title
    }
    userErrors {
      field
      message
    }
  }
}
EOF

Create Docker Compose for apollo-mcp-server

cat > docker-compose.yml <<'EOF'
version: '3.8'

services:
  apollo-mcp:
    build: .
    container_name: apollo-mcp-server
    ports:
      - "4000:4000"
    environment:
      - GITHUB_TOKEN=${GITHUB_TOKEN}
      - SHOPIFY_STORE=${SHOPIFY_STORE}
      - SHOPIFY_ACCESS_TOKEN=${SHOPIFY_ACCESS_TOKEN}
    volumes:
      - ./config:/app/config:ro
      - ./operations:/app/operations:ro
    restart: unless-stopped
    networks:
      - mcp-network

networks:
  mcp-network:
    driver: bridge
EOF

Create Environment File

cat > .env <<'EOF'
# GitHub Personal Access Token
GITHUB_TOKEN=ghp_your_token_here

# Shopify (optional)
SHOPIFY_STORE=your-store-name
SHOPIFY_ACCESS_TOKEN=shpat_your_token_here
EOF

Build and Start apollo-mcp-server

# Build the Docker image
docker-compose build

# Start the service
docker-compose up -d

# Check logs
docker-compose logs -f

# Test the endpoint
curl http://localhost:4000/health

Step 3: Deploy mcp-context-forge (Central Gateway)

cd /opt/mcp-gateway
mkdir -p mcp-context-forge
cd mcp-context-forge

Create mcp-context-forge Configuration

cat > config.yml <<'EOF'
# mcp-context-forge central gateway configuration

server:
  host: "0.0.0.0"
  port: 3000
  cors:
    enabled: true
    origins: ["*"]

security:
  jwt:
    enabled: true
    secret: "${JWT_SECRET}"
  rate_limiting:
    enabled: true
    requests_per_minute: 100

admin:
  enabled: true
  path: "/admin"
  username: "admin"
  password: "${ADMIN_PASSWORD}"

observability:
  opentelemetry:
    enabled: true
    endpoint: "http://localhost:4318"
  logging:
    level: "info"
    format: "json"

# Service Configurations
services:
  # ============================================
  # TIER 1: Direct REST API Conversions
  # ============================================

  - name: radarr
    type: rest
    enabled: true
    baseUrl: "http://192.168.1.175:7878/api/v3"
    description: "Movie collection management and automation"
    authentication:
      type: apikey
      header: "X-Api-Key"
      value: "${RADARR_API_KEY}"
    timeout: 30s
    retry:
      max_attempts: 3
      backoff: exponential

  - name: sonarr
    type: rest
    enabled: true
    baseUrl: "http://192.168.1.175:8989/api/v3"
    description: "TV series collection management and automation"
    authentication:
      type: apikey
      header: "X-Api-Key"
      value: "${SONARR_API_KEY}"
    timeout: 30s
    retry:
      max_attempts: 3
      backoff: exponential

  - name: plex
    type: rest
    enabled: true
    baseUrl: "http://192.168.1.219:32400"
    description: "Plex Media Server API"
    authentication:
      type: header
      header: "X-Plex-Token"
      value: "NNHuTaV8e1wy78cdWYVX"
    timeout: 30s

  - name: prowlarr
    type: rest
    enabled: true
    baseUrl: "http://192.168.1.175:9696/api/v1"
    description: "Indexer manager and proxy"
    authentication:
      type: apikey
      header: "X-Api-Key"
      value: "${PROWLARR_API_KEY}"
    timeout: 30s

  - name: qbittorrent
    type: rest
    enabled: true
    baseUrl: "http://192.168.1.175:8080/api/v2"
    description: "Torrent client management"
    authentication:
      type: cookie
      login_endpoint: "/auth/login"
      credentials:
        username: "${QBITTORRENT_USER}"
        password: "${QBITTORRENT_PASS}"
    timeout: 30s

  - name: sabnzbd
    type: rest
    enabled: true
    baseUrl: "http://192.168.1.175:8085/api"
    description: "Usenet downloader"
    authentication:
      type: query_param
      param: "apikey"
      value: "${SABNZBD_API_KEY}"
    timeout: 30s

  # ============================================
  # TIER 2: MCP Server Federation (Proxies)
  # ============================================

  - name: graphql-tools
    type: mcp-proxy
    enabled: true
    description: "GraphQL APIs exposed via apollo-mcp-server"
    endpoint: "http://localhost:4000"
    transport: http
    prefix: "graphql"
    health_check:
      enabled: true
      endpoint: "/health"
      interval: 60s
    retry:
      max_attempts: 3
      backoff: exponential

# Tool Discovery and Registration
discovery:
  enabled: true
  auto_register: true
  scan_interval: 300s

# Virtual MCP Server Composition
composition:
  enabled: true
  namespaces:
    - name: "media"
      services: ["radarr", "sonarr", "plex"]
      description: "Media management tools"

    - name: "downloads"
      services: ["prowlarr", "qbittorrent", "sabnzbd"]
      description: "Download management tools"

    - name: "dev"
      services: ["graphql-tools"]
      description: "Development and API tools"
EOF

Create Environment File for mcp-context-forge

cat > .env <<'EOF'
# Security
JWT_SECRET=your-super-secret-jwt-key-change-this
ADMIN_PASSWORD=your-admin-password-here

# Radarr
RADARR_API_KEY=your_radarr_api_key_here

# Sonarr
SONARR_API_KEY=your_sonarr_api_key_here

# Prowlarr
PROWLARR_API_KEY=your_prowlarr_api_key_here

# qBittorrent
QBITTORRENT_USER=admin
QBITTORRENT_PASS=your_qbittorrent_password

# SABnzbd
SABNZBD_API_KEY=your_sabnzbd_api_key_here
EOF

Create Docker Compose for mcp-context-forge

cat > docker-compose.yml <<'EOF'
version: '3.8'

services:
  mcp-context-forge:
    image: ghcr.io/ibm/mcp-context-forge:latest
    container_name: mcp-context-forge
    ports:
      - "3000:3000"
    environment:
      - JWT_SECRET=${JWT_SECRET}
      - ADMIN_PASSWORD=${ADMIN_PASSWORD}
      - RADARR_API_KEY=${RADARR_API_KEY}
      - SONARR_API_KEY=${SONARR_API_KEY}
      - PROWLARR_API_KEY=${PROWLARR_API_KEY}
      - QBITTORRENT_USER=${QBITTORRENT_USER}
      - QBITTORRENT_PASS=${QBITTORRENT_PASS}
      - SABNZBD_API_KEY=${SABNZBD_API_KEY}
    volumes:
      - ./config.yml:/app/config/config.yml:ro
      - ./data:/app/data
      - ./logs:/app/logs
    depends_on:
      - apollo-mcp
    restart: unless-stopped
    networks:
      - mcp-network

networks:
  mcp-network:
    external: true
    name: apollo-mcp-server_mcp-network
EOF

Start mcp-context-forge

# Start the service
docker-compose up -d

# Check logs
docker-compose logs -f

# Test the endpoints
curl http://localhost:3000/health
curl http://localhost:3000/admin -u admin:your-admin-password

# Test tool discovery
curl http://localhost:3000/tools

Step 4: Setup ngrok Tunnel

# Install ngrok (if not already installed)
curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | \
  sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null && \
  echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | \
  sudo tee /etc/apt/sources.list.d/ngrok.list && \
  sudo apt update && sudo apt install ngrok

# Configure ngrok with your auth token
ngrok config add-authtoken YOUR_NGROK_AUTH_TOKEN

# Create ngrok configuration
mkdir -p ~/.ngrok2
cat > ~/.ngrok2/ngrok.yml <<'EOF'
version: "2"
authtoken: YOUR_NGROK_AUTH_TOKEN
tunnels:
  mcp-gateway:
    proto: http
    addr: 3000
    bind_tls: true
    inspect: true
    hostname: your-custom-domain.ngrok.io  # Optional: requires paid plan
EOF

# Start ngrok tunnel
ngrok start mcp-gateway

# Or simple command
ngrok http 3000 --log=stdout

Save your ngrok URL: https://abc123.ngrok.io


Step 5: Create systemd Service (Optional)

To ensure everything starts on boot:

# Create systemd service for ngrok
cat > /etc/systemd/system/ngrok-mcp.service <<'EOF'
[Unit]
Description=ngrok tunnel for MCP Gateway
After=network.target docker.service

[Service]
Type=simple
User=root
WorkingDirectory=/root
ExecStart=/usr/local/bin/ngrok http 3000 --log=stdout
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

# Enable and start the service
systemctl daemon-reload
systemctl enable ngrok-mcp.service
systemctl start ngrok-mcp.service

# Check status
systemctl status ngrok-mcp.service

πŸ”Œ Connect to AI Clients

OpenAI Agent Builder

  1. Go to https://platform.openai.com/playground/assistants
  2. Create new Agent
  3. Click "Add Tools" β†’ "MCP Server"
  4. Enter your ngrok URL: https://abc123.ngrok.io
  5. Add authentication header: Authorization: Bearer YOUR_JWT_TOKEN

Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "media-gateway": {
      "command": "npx",
      "args": ["-y", "@anthropic-ai/mcp-client"],
      "env": {
        "MCP_SERVER_URL": "https://abc123.ngrok.io",
        "MCP_AUTH_TOKEN": "Bearer YOUR_JWT_TOKEN"
      }
    }
  }
}

Custom Application

import requests

MCP_GATEWAY_URL = "https://abc123.ngrok.io"
AUTH_TOKEN = "Bearer YOUR_JWT_TOKEN"

headers = {
    "Authorization": AUTH_TOKEN,
    "Content-Type": "application/json"
}

# Discover available tools
response = requests.get(f"{MCP_GATEWAY_URL}/tools", headers=headers)
tools = response.json()

print(f"Available tools: {len(tools)}")
for tool in tools:
    print(f"  - {tool['name']}: {tool['description']}")

# Call a tool
response = requests.post(
    f"{MCP_GATEWAY_URL}/tools/radarr_search_movie",
    headers=headers,
    json={"query": "The Matrix"}
)

result = response.json()
print(f"Search result: {result}")

πŸ“Š Available MCP Tools After Setup

Media Management (Direct REST)

Radarr:

  • radarr_list_movies() - List all movies
  • radarr_search_movie(title) - Search for movie
  • radarr_add_movie(title, quality_profile) - Add movie to library
  • radarr_get_queue() - Check download queue
  • radarr_delete_movie(id) - Remove movie

Sonarr:

  • sonarr_list_series() - List all TV series
  • sonarr_search_series(title) - Search for series
  • sonarr_add_series(title, quality_profile) - Add series to library
  • sonarr_get_episodes(series_id) - List episodes
  • sonarr_get_queue() - Check download queue

Plex:

  • plex_list_libraries() - List all media libraries
  • plex_search(query) - Search all content
  • plex_get_recently_added(count) - Recently added items
  • plex_get_on_deck() - Continue watching
  • plex_play_status() - Current playback status

Prowlarr:

  • prowlarr_list_indexers() - List configured indexers
  • prowlarr_search(query) - Search across all indexers
  • prowlarr_test_indexer(id) - Test indexer connection

qBittorrent:

  • qbittorrent_list_torrents() - List all torrents
  • qbittorrent_add_torrent(url) - Add torrent
  • qbittorrent_pause_torrent(hash) - Pause torrent
  • qbittorrent_resume_torrent(hash) - Resume torrent
  • qbittorrent_delete_torrent(hash) - Delete torrent

SABnzbd:

  • sabnzbd_get_queue() - Get download queue
  • sabnzbd_add_nzb(url) - Add NZB download
  • sabnzbd_pause_queue() - Pause all downloads
  • sabnzbd_resume_queue() - Resume downloads
  • sabnzbd_get_history() - Download history

Development Tools (GraphQL via apollo-mcp-server)

GitHub:

  • github_list_repos(owner, first) - List repositories
  • github_search_repos(query) - Search repositories
  • github_create_issue(repository_id, title, body) - Create issue
  • github_list_issues(owner, repo) - List issues
  • github_get_repo(owner, name) - Get repository details

Shopify (if configured):

  • shopify_get_products(first, query) - List products
  • shopify_create_product(title, description) - Create product
  • shopify_update_inventory(variant_id, quantity) - Update inventory

πŸ§ͺ Testing Your Setup

Test 1: Health Checks

# Test apollo-mcp-server
curl http://192.168.1.175:4000/health

# Test mcp-context-forge
curl http://192.168.1.175:3000/health

# Test through ngrok
curl https://abc123.ngrok.io/health

Test 2: Tool Discovery

# List all available tools
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  https://abc123.ngrok.io/tools | jq .

# Should return tools from both REST and GraphQL tiers

Test 3: Call a REST Tool (Radarr)

curl -X POST \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query": "The Matrix"}' \
  https://abc123.ngrok.io/tools/radarr_search_movie

Test 4: Call a GraphQL Tool (GitHub)

curl -X POST \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"owner": "jmanhype", "first": 10}' \
  https://abc123.ngrok.io/tools/github_list_repos

Test 5: Access Admin UI

Open browser: http://192.168.1.175:3000/admin

  • Username: admin
  • Password: (from your .env file)

You should see:

  • Active tools count
  • Request metrics
  • Real-time traffic
  • Configuration management

πŸ”’ Security Best Practices

1. Strong Authentication

# In mcp-context-forge config.yml
security:
  jwt:
    enabled: true
    secret: "USE-A-LONG-RANDOM-STRING-HERE"  # 32+ chars
    expiry: "24h"

  api_keys:
    enabled: true
    keys:
      - name: "openai-agent"
        key: "sk-mcp-your-random-key-here"
        permissions: ["read", "write"]

      - name: "claude-desktop"
        key: "sk-mcp-another-random-key"
        permissions: ["read"]

2. Rate Limiting

security:
  rate_limiting:
    enabled: true
    rules:
      - name: "default"
        requests_per_minute: 100
        burst: 20

      - name: "expensive-tools"
        pattern: "^(radarr|sonarr)_add_.*"
        requests_per_minute: 10
        burst: 2

3. HTTPS Only (ngrok)

# In ngrok config, force HTTPS
ngrok http 3000 --bind-tls=true

4. Network Isolation

# docker-compose.yml
networks:
  mcp-network:
    driver: bridge
    internal: false  # Set to true for no external access
    ipam:
      config:
        - subnet: 172.20.0.0/16

5. Secret Management

Never commit secrets! Use environment variables or secret management:

# Option 1: .env file (gitignored)
cat > .env <<EOF
JWT_SECRET=$(openssl rand -base64 32)
ADMIN_PASSWORD=$(openssl rand -base64 16)
EOF

# Option 2: Docker secrets
echo "your-jwt-secret" | docker secret create jwt_secret -

# Option 3: HashiCorp Vault (production)
vault kv put secret/mcp-gateway \
  jwt_secret="..." \
  admin_password="..."

πŸ“ˆ Monitoring & Observability

OpenTelemetry Setup (Optional)

# Add OpenTelemetry collector to docker-compose.yml
cat >> docker-compose.yml <<'EOF'

  otel-collector:
    image: otel/opentelemetry-collector:latest
    container_name: otel-collector
    command: ["--config=/etc/otel-collector-config.yaml"]
    volumes:
      - ./otel-config.yaml:/etc/otel-collector-config.yaml
    ports:
      - "4318:4318"  # OTLP HTTP
      - "4317:4317"  # OTLP gRPC
    networks:
      - mcp-network
EOF

# Create OpenTelemetry config
cat > otel-config.yaml <<'EOF'
receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
      grpc:
        endpoint: 0.0.0.0:4317

processors:
  batch:
    timeout: 10s

exporters:
  logging:
    loglevel: debug

  prometheus:
    endpoint: "0.0.0.0:8889"

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch]
      exporters: [logging]

    metrics:
      receivers: [otlp]
      processors: [batch]
      exporters: [logging, prometheus]
EOF

Prometheus + Grafana (Optional)

# Add to docker-compose.yml
cat >> docker-compose.yml <<'EOF'

  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus-data:/prometheus
    ports:
      - "9090:9090"
    networks:
      - mcp-network

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3001:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana-data:/var/lib/grafana
    networks:
      - mcp-network

volumes:
  prometheus-data:
  grafana-data:
EOF

πŸ› Troubleshooting

Issue 1: apollo-mcp-server Build Fails

Solution: Use pre-built image or increase build timeout

# Option 1: Increase Docker timeout
DOCKER_BUILDKIT=1 docker-compose build --no-cache --build-arg BUILDKIT_INLINE_CACHE=1

# Option 2: Build on host (faster)
cargo build --release
cp target/release/apollo-mcp-server ./apollo-mcp-server/
# Then use COPY in Dockerfile instead of building

Issue 2: mcp-context-forge Can't Connect to apollo-mcp-server

Solution: Check network connectivity

# From mcp-context-forge container
docker exec -it mcp-context-forge curl http://apollo-mcp-server:4000/health

# Check if containers are on same network
docker network inspect apollo-mcp-server_mcp-network

Issue 3: ngrok Tunnel Disconnects

Solution: Use systemd service or paid ngrok plan

# Check ngrok logs
journalctl -u ngrok-mcp.service -f

# Restart service
systemctl restart ngrok-mcp.service

Issue 4: API Keys Not Working

Solution: Verify environment variables are loaded

# Check container environment
docker exec mcp-context-forge env | grep API_KEY

# Re-create container with new env
docker-compose down
docker-compose up -d

πŸš€ Next Steps & Extensions

Add More GraphQL APIs

  1. Create GraphQL operations file
  2. Add to apollo-mcp-server config
  3. Restart apollo-mcp-server
  4. Tools automatically appear in mcp-context-forge

Add Custom MCP Servers

# In mcp-context-forge config.yml
services:
  - name: custom-python-server
    type: mcp-proxy
    endpoint: "http://my-custom-server:5000"
    transport: stdio

Scale Horizontally

# Run multiple mcp-context-forge instances
services:
  mcp-forge-1:
    image: ghcr.io/ibm/mcp-context-forge:latest
    # ... config

  mcp-forge-2:
    image: ghcr.io/ibm/mcp-context-forge:latest
    # ... config

  nginx:
    image: nginx:latest
    # Load balance across instances

Add Authentication Providers

security:
  oauth:
    enabled: true
    providers:
      - name: google
        client_id: "${GOOGLE_CLIENT_ID}"
        client_secret: "${GOOGLE_CLIENT_SECRET}"
        redirect_uri: "https://abc123.ngrok.io/oauth/callback"

πŸ“š Reference Links


πŸ’° Cost Analysis

Free Tier (Fully Functional)

  • Proxmox Host: Already owned (ZimaBoard)
  • LXC Container: Free (part of Proxmox)
  • Docker: Free and open source
  • mcp-context-forge: Free (Apache 2.0)
  • apollo-mcp-server: Free (MIT)
  • ngrok Free Tier:
    • 1 concurrent tunnel
    • Random URL (e.g., abc123.ngrok.io)
    • HTTP/HTTPS traffic
    • Limitation: URL changes on restart

Total: $0/month

Paid Tier (Recommended for Production)

  • ngrok Personal Plan: $8/month

    • 3 concurrent tunnels
    • Custom domain (your-gateway.ngrok.io)
    • Static URL (doesn't change)
    • Priority support
  • ngrok Pro Plan: $20/month

    • Everything in Personal
    • IP whitelisting
    • OAuth integration
    • Advanced features

Total: $8-20/month (only ngrok)

Enterprise Tier

  • ngrok Enterprise: Custom pricing
  • Dedicated Proxmox Hardware: One-time cost
  • Additional API quota costs: Varies by service

βœ… Summary

You now have a production-grade, federated MCP gateway that:

βœ… Exposes REST APIs (Radarr, Sonarr, Plex, etc.) as MCP tools βœ… Exposes GraphQL APIs (GitHub, Shopify, etc.) as MCP tools βœ… Provides unified authentication and security βœ… Offers admin UI for management βœ… Scales horizontally βœ… Costs $0-20/month βœ… Runs on your ZimaBoard Proxmox infrastructure

Architecture Benefits:

  • Tier 1 (apollo-mcp-server): Specialized GraphQL conversion
  • Tier 2 (mcp-context-forge): Central gateway, REST conversion, and federation
  • Result: Single public endpoint with all your APIs as AI-accessible tools

Generated: October 12, 2025 Author: AI Infrastructure Assistant Platform: ZimaBoard + Proxmox VE 8.4.1 LXC: 103 (192.168.1.175) Architecture: Multi-Tier MCP Federation (apollo-mcp-server + mcp-context-forge) Public Access: ngrok HTTPS tunnel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment