Labsco
amir-the-h logo

MCP Hub

from amir-the-h

A lightweight MCP Hub to centralize your MCP servers in one place.

๐Ÿ”ฅ๐Ÿ”ฅ๐Ÿ”ฅโœ“ VerifiedFreeAdvanced setup

MCP-Hub

A Go-based hub that aggregates multiple MCP (Model Context Protocol) servers and exposes their tools through a unified HTTP API.

Features

  • Standard MCP Protocol: Full support for MCP 2024-11-05 specification
  • Multiple Transport Types:
    • Stdio: For local MCP servers (Node.js, Python, etc.)
    • HTTP/SSE: For remote MCP servers
  • Configuration-Based: JSON configuration compatible with Cursor/VSCode format
  • Docker-Ready: Easy deployment in containers with volume mounts
  • Tool Aggregation: Combine tools from multiple MCP servers in one place
  • HTTP API: RESTful endpoints for tool discovery and execution

HTTP API Reference

GET /mcp/tools

List all available tools from all connected MCP servers.

Response:

[
  {
    "id": "read_file",
    "name": "read_file",
    "description": "Read contents of a file",
    "plugin_id": "filesystem"
  }
]

POST /mcp/execute

Execute a tool on a specific MCP server.

Request:

{
  "plugin_id": "filesystem",
  "tool_name": "read_file",
  "arguments": {
    "path": "/tmp/test.txt"
  }
}

Response: MCP tool call result (format depends on the tool)

GET /mcp/servers

List all connected MCP servers.

Response:

{
  "servers": ["filesystem", "github", "brave-search"]
}

GET /mcp/stream

Server-Sent Events stream of tool registry updates (for real-time tool discovery).

Examples

Example 1: Using Official MCP Servers

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/documents"]
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
      }
    },
    "brave-search": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-brave-search"],
      "env": {
        "BRAVE_API_KEY": "${BRAVE_API_KEY}"
      }
    }
  }
}

Example 2: Custom Python MCP Server

{
  "mcpServers": {
    "custom-tools": {
      "command": "python3",
      "args": ["/app/plugins/custom_mcp_server.py"]
    }
  }
}

Example 3: Mixed Local and Remote Servers

{
  "mcpServers": {
    "local-filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
    },
    "remote-api": {
      "type": "http",
      "url": "https://api.example.com/mcp",
      "headers": {
        "Authorization": "Bearer ${API_TOKEN}"
      }
    }
  }
}

Development

Project Structure

.
โ”œโ”€โ”€ cmd/
โ”‚   โ””โ”€โ”€ mcp-hub/
โ”‚       โ””โ”€โ”€ main.go           # Entry point
โ”œโ”€โ”€ internal/
โ”‚   โ”œโ”€โ”€ config/
โ”‚   โ”‚   โ””โ”€โ”€ config.go         # Configuration parsing
โ”‚   โ”œโ”€โ”€ mcp/
โ”‚   โ”‚   โ””โ”€โ”€ protocol.go       # MCP protocol structures
โ”‚   โ”œโ”€โ”€ plugin/
โ”‚   โ”‚   โ””โ”€โ”€ manager.go        # Server management
โ”‚   โ”œโ”€โ”€ registry/
โ”‚   โ”‚   โ””โ”€โ”€ registry.go       # Tool registry
โ”‚   โ”œโ”€โ”€ server/
โ”‚   โ”‚   โ””โ”€โ”€ server.go         # HTTP server
โ”‚   โ””โ”€โ”€ transport/
โ”‚       โ”œโ”€โ”€ transport.go      # Transport interface
โ”‚       โ”œโ”€โ”€ stdio.go          # Stdio transport
โ”‚       โ””โ”€โ”€ http.go           # HTTP transport
โ”œโ”€โ”€ config.example.json       # Example configuration
โ””โ”€โ”€ README.md

Adding New Transport Types

Implement the Transport interface in internal/transport/:

type Transport interface {
    Start(ctx context.Context) error
    SendRequest(ctx context.Context, req interface{}) (json.RawMessage, error)
    SendNotification(ctx context.Context, notification interface{}) error
    Close() error
    IsConnected() bool
}

Building Docker Images for MCP Servers

You can containerize any MCP server to avoid installing its dependencies on the hub host.

Example: Dockerizing a Python MCP Server

Dockerfile:

FROM python:3.11-slim

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy MCP server code
COPY mcp_server.py .

# Run as non-root user
RUN useradd -m -u 1000 mcpuser && chown -R mcpuser:mcpuser /app
USER mcpuser

# The server should read from stdin and write to stdout
ENTRYPOINT ["python", "-u", "mcp_server.py"]

Build and use:

# Build the image
docker build -t my-mcp-server:latest .

# Add to config.json
{
  "mcpServers": {
    "my-server": {
      "image": "my-mcp-server:latest"
    }
  }
}

Example: Dockerizing a Node.js MCP Server

Dockerfile:

FROM node:20-alpine

WORKDIR /app

# Install dependencies
COPY package*.json ./
RUN npm ci --production

# Copy server code
COPY . .

# Run as non-root user
RUN addgroup -g 1000 mcpuser && \
    adduser -D -u 1000 -G mcpuser mcpuser && \
    chown -R mcpuser:mcpuser /app
USER mcpuser

ENTRYPOINT ["node", "server.js"]

Important Notes for Docker MCP Servers

  1. Use -i flag: The hub runs containers with docker run -i for interactive stdin/stdout
  2. Unbuffered output: Ensure your server outputs are unbuffered (use python -u or flush())
  3. Stdin/Stdout only: The MCP protocol uses stdin for input and stdout for output
  4. Stderr for logs: Use stderr for logging (visible in hub logs)
  5. Cleanup: Containers are run with --rm for automatic cleanup

Pre-built MCP Server Images

Create a registry of Docker images for common MCP servers:

# Example: Build an echo server image
cd examples/plugins
cat > Dockerfile << 'DOCKERFILE'
FROM python:3.11-slim
COPY mcp_echo.py /app/server.py
WORKDIR /app
RUN chmod +x server.py
ENTRYPOINT ["python", "-u", "server.py"]
DOCKERFILE

docker build -t mcp-echo:latest .

Then use it:

{
  "mcpServers": {
    "echo": {
      "image": "mcp-echo:latest"
    }
  }
}

Transport Comparison

TransportUse CaseProsCons
stdioLocal MCP servers with direct accessFast, low overheadRequires runtime (Node.js/Python) installed
DockerIsolated, reproducible MCP serversNo runtime dependencies, easy versioningSlightly higher overhead, requires Docker
HTTPRemote/cloud-hosted MCP serversScalable, can be load-balancedNetwork latency, requires server infrastructure

When to Use Docker Transport

Choose Docker transport when:

  • โœ… You want to avoid installing Node.js, Python, or other runtimes on your hub host
  • โœ… You need consistent, reproducible environments across deployments
  • โœ… You want easy version management with Docker tags
  • โœ… You're running the hub in a containerized environment (Kubernetes, Docker Compose)
  • โœ… You need to isolate server dependencies
  • โœ… You want to use pre-built MCP server images from a registry

Choose stdio transport when:

  • โœ… You're developing locally and want faster iteration
  • โœ… Runtime dependencies are already installed
  • โœ… You need the absolute lowest latency

Choose HTTP transport when:

  • โœ… MCP servers are hosted remotely
  • โœ… You need to scale servers independently
  • โœ… You want to use managed MCP server services

Example: Complete Docker Setup

Here's a complete example running multiple MCP servers in Docker:

# 1. Build your custom MCP server image
docker build -t my-mcp-server:v1.0 ./my-server

# 2. Create Docker network for inter-container communication
docker network create mcp-network

# 3. Configure servers
cat > config.json << 'JSON'
{
  "mcpServers": {
    "echo": {
      "image": "mcp-echo:latest"
    },
    "custom-tools": {
      "image": "my-mcp-server:v1.0",
      "env": {
        "API_KEY": "${MY_API_KEY}"
      },
      "volumes": {
        "/host/data": "/data"
      }
    }
  }
}
JSON

# 4. Run the hub
MY_API_KEY=secret123 ./mcp-hub --config config.json

This setup gives you:

  • โœจ No runtime dependencies on the hub host
  • โœจ Isolated environments for each MCP server
  • โœจ Easy updates by changing Docker image tags
  • โœจ Reproducible deployments

Config File Watching

The MCP Hub automatically watches the configuration file for changes and updates the registry accordingly. When you modify config.json, the hub will:

  • Add new servers: Automatically start any newly added MCP servers
  • Remove servers: Stop servers that are removed from config or disabled
  • Reload servers: Restart servers whose configuration has changed
  • Update registry: Keep the tool registry in sync with active servers

How It Works

The watcher uses fsnotify to monitor the config file for write events. When changes are detected:

  1. The new config is loaded and validated
  2. Changes are compared with the previous configuration
  3. Appropriate actions are taken (start/stop/reload servers)
  4. The registry is automatically updated
  5. Changes are logged for visibility

Debouncing

To avoid processing rapid successive changes (e.g., when editors write multiple times), the watcher includes a 500ms debounce delay. This ensures the config is only reloaded once after you finish editing.

Example

# Start mcp-hub
./mcp-hub --config=config.json

# In another terminal, edit config.json
vim config.json

# The hub will automatically detect changes and log:
# "config file changed, reloading..."
# "adding server: new-server"
# "loaded MCP server: new-server (stdio transport)"

Error Handling

If the new config contains errors:

  • Invalid JSON: Changes are rejected, hub continues with previous config
  • Missing required fields: Changes are rejected with validation error
  • Server startup failures: Logged as warnings, other servers continue running