MCP Hub
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
Quick Start
1. Build
cd cmd/mcp-hub
go build -o ../../mcp-hub
2. 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.json
Notes:
- 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/tools
Execute 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/servers
Configuration 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-hub
Docker 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-hub
Docker 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}
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
}
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
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.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
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:latest
Or use Docker Compose
docker-compose up -d
Image Sizes:
- Standard (with Docker CLI): ~47MB
- Minimal (stdio/HTTP only): ~6MB
Environment variables are automatically passed through and expanded in your configuration.
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
Related Servers
Fulcra Context
Fulcra Context MCP server for accessing your personal health, workouts, sleep, location, and more, all privately. Built around Context by Fulcra.
MCP Server Market
A centralized repository for discovering and utilizing Model Context Protocol (MCP) servers.
PoshMCP
Expose explicitly whitelisted PowerShell commandlets as a MCP Tool
Weather MCP Service
Provides real-time weather information and forecasts.
Nexus Dashboard
A comprehensive Model Context Protocol (MCP) server for Cisco Nexus Dashboard, enabling AI agents like Claude to interact with Nexus Dashboard APIs for intelligent network automation and management.
Wordle MCP
Fetches daily Wordle solutions for a specific date via the Wordle API.
TestDino
TestDino MCP enhances your AI assistant with advanced tooling and diagnostic insights. It enables your AI assistant perform test-run analysis, see root-cause detection, and identify recurring failure patterns.
渠道洞察服务
Provides sales channel analysis, including distribution, dealer networks, and coverage, to help understand enterprise channel layouts.
Immanuel MCP Server
An MCP server for astrological chart calculations using the immanuel-python library.
AI Endurance
AI coach for running, cycling, triathlon