
MCP Hub
from amir-the-h
A lightweight MCP Hub to centralize your MCP servers in one place.
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.mdAdding 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
- Use
-iflag: The hub runs containers withdocker run -ifor interactive stdin/stdout - Unbuffered output: Ensure your server outputs are unbuffered (use
python -uorflush()) - Stdin/Stdout only: The MCP protocol uses stdin for input and stdout for output
- Stderr for logs: Use stderr for logging (visible in hub logs)
- Cleanup: Containers are run with
--rmfor 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
| Transport | Use Case | Pros | Cons |
|---|---|---|---|
| stdio | Local MCP servers with direct access | Fast, low overhead | Requires runtime (Node.js/Python) installed |
| Docker | Isolated, reproducible MCP servers | No runtime dependencies, easy versioning | Slightly higher overhead, requires Docker |
| HTTP | Remote/cloud-hosted MCP servers | Scalable, can be load-balanced | Network 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.jsonThis 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:
- The new config is loaded and validated
- Changes are compared with the previous configuration
- Appropriate actions are taken (start/stop/reload servers)
- The registry is automatically updated
- 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
# Create a config directory
mkdir -p config
# Put your config.json in the config directory
cp config.json config/
# Run the container
docker run -d \
-p 8080:8080 \
-v $(pwd)/config:/config:ro \
-e GITHUB_TOKEN=${GITHUB_TOKEN} \
mcp-hubBefore it works, you'll need: GITHUB_PERSONAL_ACCESS_TOKEN
Quick Start
1. Build
cd cmd/mcp-hub
go build -o ../../mcp-hub2. Create Configuration
Create a config.json file (see config.example.json for a template):
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
}
}
}
}3. Run
# Using default config.json
./mcp-hub
# Or specify a config file
./mcp-hub --config /path/to/config.jsonNotes:
- The HTTP listen address can be overridden with the
MCP_HUB_PORTorPORTenvironment variable. If the value contains a colon it is treated as a full address (e.g.0.0.0.0:8080), otherwise it is treated as a port and is prefixed with a colon. - The binary accepts a
--configflag (default:config.json).
4. Use the API
List all available tools:
curl http://localhost:8080/mcp/toolsExecute a tool:
curl -X POST http://localhost:8080/mcp/execute \
-H 'Content-Type: application/json' \
-d '{
"plugin_id": "filesystem",
"tool_name": "read_file",
"arguments": {"path": "/tmp/test.txt"}
}'List connected servers:
curl http://localhost:8080/mcp/serversConfiguration Format
The configuration file uses the standard mcpServers format compatible with Cursor, VSCode, and Claude Desktop.
Stdio Servers (Local)
For MCP servers that run as local processes:
{
"mcpServers": {
"server-name": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-package"],
"env": {
"API_KEY": "${YOUR_API_KEY}"
},
"timeout": 30
}
}
}Fields:
command: Executable to run (required)args: Command line arguments (optional)env: Environment variables (optional, supports${VAR}expansion)timeout: Request timeout in seconds (optional, default: 30)disabled: Set totrueto disable a server (optional)
HTTP Servers (Remote)
For MCP servers accessible via HTTP:
{
"mcpServers": {
"remote-server": {
"type": "http",
"url": "http://localhost:3000/mcp",
"headers": {
"Authorization": "Bearer ${API_TOKEN}"
},
"timeout": 45
}
}
}Fields:
type: Set to"http"for HTTP transport (or auto-detected fromurl)url: HTTP endpoint URL (required)headers: HTTP headers to include (optional, supports${VAR}expansion)timeout: Request timeout in seconds (optional, default: 30)
Environment Variables
Environment variables in the configuration are expanded using ${VAR_NAME} syntax. For example:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
}
}
}
}Then run:
GITHUB_TOKEN=your_token_here ./mcp-hubDocker Deployment
Image Variants
The repository provides two Docker image variants, optimized for different use cases:
1. Standard Image (Dockerfile / mcp-hub:latest)
- Size: ~47MB
- Includes: Alpine Linux + Docker CLI + mcp-hub binary
- Use when: You need Docker transport for running MCP servers in containers, or mixed transports
- Best for: Production deployments with container-based MCP servers
2. Local MCP Servers Image (Dockerfile.local / mcp-hub:local)
- Size: ~800MB
- Includes: Node.js 20, Python 3, uv, curl, git, and mcp-hub binary
- Use when: You want to run local stdio-based MCP servers (Node.js, Python) in the same container
- Best for: Self-contained deployments where the hub and all MCP servers run together
Build Docker Image
To build a specific image locally:
# Standard image (with Docker CLI)
docker build -t mcp-hub:latest .
# Local MCP servers image (with Node.js, Python, uv)
docker build -f Dockerfile.local -t mcp-hub:local .Run with Docker
# Create a config directory
mkdir -p config
# Put your config.json in the config directory
cp config.json config/
# Run the container
docker run -d \
-p 8080:8080 \
-v $(pwd)/config:/config:ro \
-e GITHUB_TOKEN=${GITHUB_TOKEN} \
mcp-hubDocker Compose
version: '3.8'
services:
mcp-hub:
build: .
ports:
- "8080:8080"
volumes:
- ./config:/config:ro
- ./plugins:/plugins:ro # Optional: mount custom plugins
environment:
- GITHUB_TOKEN=${GITHUB_TOKEN}
- BRAVE_API_KEY=${BRAVE_API_KEY}Containerized Deployment
The mcp-hub itself can run in a Docker container. See DOCKER_DEPLOYMENT.md for complete deployment guide.
Quick Docker Deploy
# Build the image
docker build -t mcp-hub:latest .
# Run with your config
docker run -d \
--name mcp-hub \
-p 8080:8080 \
-v $(pwd)/config.json:/app/config.json:ro \
-v /var/run/docker.sock:/var/run/docker.sock \
-e GITHUB_TOKEN=${GITHUB_TOKEN} \
mcp-hub:latestOr use Docker Compose
docker-compose up -dImage Sizes:
- Standard (with Docker CLI): ~47MB
- Minimal (stdio/HTTP only): ~6MB
Environment variables are automatically passed through and expanded in your configuration.
Troubleshooting
Server fails to start
Check logs for specific error messages. Common issues:
- Missing
npxorpython3in PATH - Invalid MCP server package names
- Missing environment variables
- Incorrect file paths in configuration
Tool execution fails
- Verify the MCP server is properly initialized (check
/mcp/servers) - Ensure tool arguments match the expected schema
- Check server logs (stderr output is visible in hub logs)
Timeout errors
Increase the timeout value in server configuration:
{
"mcpServers": {
"slow-server": {
"command": "...",
"timeout": 120
}
}
}License
MIT
Docker Servers (Containerized)
For MCP servers running in Docker containers:
{
"mcpServers": {
"containerized-server": {
"type": "docker",
"image": "my-mcp-server:latest",
"args": ["--option", "value"],
"env": {
"API_KEY": "${YOUR_API_KEY}"
},
"volumes": {
"/host/path": "/container/path",
"${HOME}/data": "/data"
},
"network": "mcp-network",
"timeout": 60
}
}
}Fields:
type: Set to"docker"for Docker transport (or auto-detected fromimage)image: Docker image name (required)args: Command arguments to pass to container entrypoint (optional)env: Environment variables (optional, supports${VAR}expansion)volumes: Volume mounts ashost:containermappings (optional, supports${VAR}expansion)network: Docker network to connect to (optional)timeout: Request timeout in seconds (optional, default: 30)
Benefits of Docker Transport:
- No need to install Node.js, Python, or other runtimes on the hub host
- Isolated dependencies per MCP server
- Easy version management with Docker tags
- Consistent environment across deployments