A centralized API gateway for Model Context Protocol (MCP) servers. MCP Gateway enables LLM clients to connect to multiple MCP servers through a single endpoint with unified authentication, session management, and rate limiting.
- Namespace-based routing: Route requests to different MCP servers based on namespace
- API key authentication: Secure access with configurable API keys and namespace restrictions
- Tool filtering: Allow or deny specific tools per namespace
- Request logging: Track all MCP requests with DuckDB for analytics
- Analytics API: Query usage statistics, tool popularity, and time series data
- SSE streaming support: Full support for Server-Sent Events for streaming responses
- Go 1.21 or later
- DuckDB (embedded, no separate installation required)
# Clone the repository
git clone https://github.com/sekoudoumbouya/mcp-gateway.git
cd mcp-gateway
# Install dependencies
go mod download
# Build the gateway
go build -o mcp-gateway ./cmd/gateway# Start with default config
./mcp-gateway
# Start with custom config file
./mcp-gateway -config /path/to/config.yamlThe gateway will start on http://localhost:8080 by default.
Create a config.yaml file:
server:
host: "0.0.0.0"
port: 8080
database:
path: "gateway.duckdb"
# Namespace to MCP server mappings
namespaces:
filesystem:
url: "http://localhost:3001/mcp"
description: "Filesystem operations"
# Optional: restrict which tools are allowed
allowed_tools:
- "read_file"
- "list_directory"
search:
url: "http://localhost:3002/mcp"
description: "Web search and browsing"
database:
url: "http://localhost:3003/mcp"
description: "Database operations"
# Authentication
auth:
enabled: true
api_keys:
"dev-key-12345":
user_id: "developer"
description: "Development key"
# Empty namespaces = access to all
"prod-key-67890":
user_id: "production"
namespaces:
- "filesystem"
- "database"
"search-only":
user_id: "search-user"
namespaces:
- "search"
# Rate limiting
rate_limit:
enabled: true
requests_per_minute: 100
burst_size: 10Override configuration with environment variables:
| Variable | Description |
|---|---|
GATEWAY_HOST |
Server listen host |
GATEWAY_PORT |
Server listen port |
GATEWAY_DB_PATH |
DuckDB database path |
Environment variable expansion is also supported in config values:
database:
path: "${DATABASE_PATH}"GET /health
Returns gateway health status.
Response:
{
"status": "healthy",
"time": "2024-01-15T10:30:00Z"
}GET /namespaces
Returns all configured namespaces.
Response:
{
"namespaces": [
{
"name": "filesystem",
"description": "Filesystem operations"
},
{
"name": "search",
"description": "Web search and browsing"
}
]
}POST /mcp/:namespace
Authorization: Bearer <api-key>
Content-Type: application/json
Forward JSON-RPC 2.0 requests to the specified namespace's MCP server.
Request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}Response:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [...]
}
}For streaming responses, add Accept: text/event-stream header.
GET /analytics/usage?period=7d&group_by=user
Authorization: Bearer <api-key>
Parameters:
period: Time period (e.g.,7d,24h,1h,30m)group_by: Group byuser(default) ortool
Response:
{
"period": "168h0m0s",
"results": [
{
"user_id": "developer",
"namespace": "filesystem",
"request_count": 150,
"error_count": 2,
"avg_latency_ms": 45.5,
"total_request_bytes": 15000,
"total_response_bytes": 75000
}
]
}GET /analytics/usage/:user_id?period=24h
Authorization: Bearer <api-key>
Returns detailed statistics for a specific user.
GET /analytics/tools?period=7d&limit=20
Authorization: Bearer <api-key>
Response:
{
"period": "168h0m0s",
"tools": [
{
"tool_name": "read_file",
"namespace": "filesystem",
"invocations": 500,
"unique_users": 10,
"avg_latency_ms": 25.3,
"error_rate": 0.02
}
]
}GET /analytics/timeseries?period=24h&bucket=1h
Authorization: Bearer <api-key>
Response:
{
"period": "24h0m0s",
"bucket": "1h0m0s",
"data": [
{
"timestamp": "2024-01-15T09:00:00Z",
"value": 45
},
{
"timestamp": "2024-01-15T10:00:00Z",
"value": 62
}
]
}┌─────────────────────────────────────────────────────────────┐
│ LLM Clients │
│ (Claude, GPT, etc. with MCP config) │
└─────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ MCP Gateway │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Auth │ │ Router │ │ Event Logger │ │
│ │ Middleware │ │ (Namespace) │ │ (DuckDB) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ MCP Handler ││
│ │ - HTTP/JSON-RPC proxy ││
│ │ - SSE streaming support ││
│ │ - Tool filtering ││
│ └─────────────────────────────────────────────────────────┘│
└─────────────────────────┬───────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ MCP │ │ MCP │ │ MCP │
│ Server 1 │ │ Server 2 │ │ Server N │
└──────────┘ └──────────┘ └──────────┘
# Run all unit tests
go test ./...
# Run with verbose output
go test -v ./...
# Run integration tests
go test -tags=integration ./...
# Run with coverage
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.outmcp-gateway/
├── cmd/
│ └── gateway/
│ └── main.go # Application entry point
├── internal/
│ ├── config/
│ │ ├── config.go # Configuration loading and validation
│ │ └── config_test.go
│ ├── auth/
│ │ ├── apikey.go # API key authentication middleware
│ │ └── apikey_test.go
│ ├── gateway/
│ │ ├── router.go # Namespace routing
│ │ ├── router_test.go
│ │ ├── handler.go # MCP request handler
│ │ └── handler_test.go
│ ├── db/
│ │ ├── duckdb.go # Database layer
│ │ ├── events.go # Event logging
│ │ └── db_test.go
│ ├── analytics/
│ │ ├── queries.go # Analytics query service
│ │ └── queries_test.go
│ └── api/
│ ├── analytics.go # Analytics REST API
│ └── analytics_test.go
├── testdata/ # Test fixtures
│ ├── config_valid.yaml
│ ├── config_minimal.yaml
│ └── config_invalid_*.yaml
├── integration_test.go # Integration tests
├── config.yaml # Default configuration
├── go.mod
└── README.md
The gateway uses JSON-RPC 2.0 error codes:
| Code | Message |
|---|---|
| -32700 | Parse error - Invalid JSON |
| -32600 | Invalid Request - Missing authorization, access denied |
| -32601 | Method not found |
| -32603 | Internal error - Failed to connect to MCP server |
MIT License