|
| 1 | +# MCP Reverse Proxy |
| 2 | + |
| 3 | +The MCP Reverse Proxy enables local MCP servers to be accessible through remote gateways without requiring inbound network access. This is similar to SSH reverse tunneling or ngrok, but specifically designed for the MCP protocol. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +The reverse proxy establishes an outbound connection from a local environment to a remote gateway, then tunnels all MCP protocol messages through this persistent connection. This allows: |
| 8 | + |
| 9 | +- **Firewall traversal**: Share MCP servers without opening inbound ports |
| 10 | +- **NAT bypass**: Work seamlessly behind corporate or home NATs |
| 11 | +- **Edge deployments**: Connect edge servers to central management |
| 12 | +- **Development testing**: Test local servers with cloud-hosted gateways |
| 13 | + |
| 14 | +## Architecture |
| 15 | + |
| 16 | +``` |
| 17 | +┌─────────────────────┐ ┌──────────────────┐ ┌─────────────┐ |
| 18 | +│ Local MCP Server │ stdio │ Reverse Proxy │ WS/SSE │ Remote │ |
| 19 | +│ (uvx mcp-server) │ <-----> │ Client │ <-----> │ Gateway │ |
| 20 | +└─────────────────────┘ └──────────────────┘ └─────────────┘ |
| 21 | + ↑ |
| 22 | + │ |
| 23 | + ┌──────┴──────┐ |
| 24 | + │ MCP Clients │ |
| 25 | + └─────────────┘ |
| 26 | +``` |
| 27 | + |
| 28 | +## Quick Start |
| 29 | + |
| 30 | +### 1. Basic Usage |
| 31 | + |
| 32 | +Connect a local MCP server to a remote gateway: |
| 33 | + |
| 34 | +```bash |
| 35 | +# Set gateway URL and authentication |
| 36 | +export REVERSE_PROXY_GATEWAY=https://gateway.example.com |
| 37 | +export REVERSE_PROXY_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token \ |
| 38 | + --username admin --exp 10080 --secret your-secret-key) |
| 39 | + |
| 40 | +# Run the reverse proxy |
| 41 | +python3 -m mcpgateway.reverse_proxy \ |
| 42 | + --local-stdio "uvx mcp-server-git" |
| 43 | +``` |
| 44 | + |
| 45 | +### 2. Command Line Options |
| 46 | + |
| 47 | +```bash |
| 48 | +python3 -m mcpgateway.reverse_proxy \ |
| 49 | + --local-stdio "uvx mcp-server-filesystem --directory /path/to/files" \ |
| 50 | + --gateway https://gateway.example.com \ |
| 51 | + --token your-bearer-token \ |
| 52 | + --reconnect-delay 2 \ |
| 53 | + --max-retries 10 \ |
| 54 | + --keepalive 30 \ |
| 55 | + --log-level DEBUG |
| 56 | +``` |
| 57 | + |
| 58 | +Options: |
| 59 | +- `--local-stdio`: Command to run the local MCP server (required) |
| 60 | +- `--gateway`: Remote gateway URL (or use REVERSE_PROXY_GATEWAY env var) |
| 61 | +- `--token`: Bearer token for authentication (or use REVERSE_PROXY_TOKEN env var) |
| 62 | +- `--reconnect-delay`: Initial reconnection delay in seconds (default: 1) |
| 63 | +- `--max-retries`: Maximum reconnection attempts, 0=infinite (default: 0) |
| 64 | +- `--keepalive`: Heartbeat interval in seconds (default: 30) |
| 65 | +- `--log-level`: Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL) |
| 66 | +- `--verbose`: Enable verbose logging (same as --log-level DEBUG) |
| 67 | +- `--config`: Configuration file (YAML or JSON) |
| 68 | + |
| 69 | +### 3. Configuration File |
| 70 | + |
| 71 | +Create a `reverse-proxy.yaml`: |
| 72 | + |
| 73 | +```yaml |
| 74 | +# reverse-proxy.yaml |
| 75 | +local_stdio: "uvx mcp-server-git" |
| 76 | +gateway: "https://gateway.example.com" |
| 77 | +token: "your-bearer-token" |
| 78 | +reconnect_delay: 2 |
| 79 | +max_retries: 0 |
| 80 | +keepalive: 30 |
| 81 | +log_level: "INFO" |
| 82 | +``` |
| 83 | +
|
| 84 | +Run with configuration: |
| 85 | +
|
| 86 | +```bash |
| 87 | +python3 -m mcpgateway.reverse_proxy --config reverse-proxy.yaml |
| 88 | +``` |
| 89 | + |
| 90 | +## Environment Variables |
| 91 | + |
| 92 | +- `REVERSE_PROXY_GATEWAY`: Remote gateway URL |
| 93 | +- `REVERSE_PROXY_TOKEN`: Bearer token for authentication |
| 94 | +- `REVERSE_PROXY_RECONNECT_DELAY`: Initial reconnection delay (seconds) |
| 95 | +- `REVERSE_PROXY_MAX_RETRIES`: Maximum reconnection attempts (0=infinite) |
| 96 | +- `REVERSE_PROXY_LOG_LEVEL`: Python log level |
| 97 | + |
| 98 | +## Docker Deployment |
| 99 | + |
| 100 | +### Single Container |
| 101 | + |
| 102 | +```dockerfile |
| 103 | +FROM python:3.11-slim |
| 104 | + |
| 105 | +# Install MCP gateway and server |
| 106 | +RUN pip install mcp-gateway mcp-server-git |
| 107 | + |
| 108 | +# Set environment |
| 109 | +ENV REVERSE_PROXY_GATEWAY=https://gateway.example.com |
| 110 | +ENV REVERSE_PROXY_TOKEN=your-token |
| 111 | + |
| 112 | +# Run reverse proxy |
| 113 | +CMD ["python", "-m", "mcpgateway.reverse_proxy", \ |
| 114 | + "--local-stdio", "mcp-server-git"] |
| 115 | +``` |
| 116 | + |
| 117 | +### Docker Compose |
| 118 | + |
| 119 | +```yaml |
| 120 | +version: '3.8' |
| 121 | + |
| 122 | +services: |
| 123 | + reverse-proxy-git: |
| 124 | + image: mcp-gateway:latest |
| 125 | + environment: |
| 126 | + REVERSE_PROXY_GATEWAY: https://gateway.example.com |
| 127 | + REVERSE_PROXY_TOKEN: ${TOKEN} |
| 128 | + command: > |
| 129 | + python -m mcpgateway.reverse_proxy |
| 130 | + --local-stdio "mcp-server-git" |
| 131 | + --keepalive 30 |
| 132 | + --log-level INFO |
| 133 | + restart: unless-stopped |
| 134 | + |
| 135 | + reverse-proxy-filesystem: |
| 136 | + image: mcp-gateway:latest |
| 137 | + environment: |
| 138 | + REVERSE_PROXY_GATEWAY: https://gateway.example.com |
| 139 | + REVERSE_PROXY_TOKEN: ${TOKEN} |
| 140 | + volumes: |
| 141 | + - ./data:/data:ro |
| 142 | + command: > |
| 143 | + python -m mcpgateway.reverse_proxy |
| 144 | + --local-stdio "mcp-server-filesystem --directory /data" |
| 145 | + restart: unless-stopped |
| 146 | +``` |
| 147 | +
|
| 148 | +## Kubernetes Deployment |
| 149 | +
|
| 150 | +```yaml |
| 151 | +apiVersion: apps/v1 |
| 152 | +kind: Deployment |
| 153 | +metadata: |
| 154 | + name: mcp-reverse-proxy |
| 155 | +spec: |
| 156 | + replicas: 1 |
| 157 | + selector: |
| 158 | + matchLabels: |
| 159 | + app: mcp-reverse-proxy |
| 160 | + template: |
| 161 | + metadata: |
| 162 | + labels: |
| 163 | + app: mcp-reverse-proxy |
| 164 | + spec: |
| 165 | + containers: |
| 166 | + - name: reverse-proxy |
| 167 | + image: mcp-gateway:latest |
| 168 | + env: |
| 169 | + - name: REVERSE_PROXY_GATEWAY |
| 170 | + value: "https://gateway.example.com" |
| 171 | + - name: REVERSE_PROXY_TOKEN |
| 172 | + valueFrom: |
| 173 | + secretKeyRef: |
| 174 | + name: mcp-credentials |
| 175 | + key: token |
| 176 | + command: |
| 177 | + - python |
| 178 | + - -m |
| 179 | + - mcpgateway.reverse_proxy |
| 180 | + args: |
| 181 | + - --local-stdio |
| 182 | + - "mcp-server-git" |
| 183 | + - --keepalive |
| 184 | + - "30" |
| 185 | + resources: |
| 186 | + limits: |
| 187 | + memory: "256Mi" |
| 188 | + cpu: "100m" |
| 189 | +``` |
| 190 | +
|
| 191 | +## Gateway-Side Configuration |
| 192 | +
|
| 193 | +The remote gateway must have the reverse proxy endpoints enabled: |
| 194 | +
|
| 195 | +### 1. WebSocket Endpoint |
| 196 | +
|
| 197 | +The gateway exposes `/reverse-proxy/ws` for WebSocket connections: |
| 198 | + |
| 199 | +```python |
| 200 | +# Gateway receives connections at: |
| 201 | +wss://gateway.example.com/reverse-proxy/ws |
| 202 | +``` |
| 203 | + |
| 204 | +### 2. Session Management |
| 205 | + |
| 206 | +View active reverse proxy sessions: |
| 207 | + |
| 208 | +```bash |
| 209 | +# List all sessions |
| 210 | +curl -H "Authorization: Bearer $TOKEN" \ |
| 211 | + https://gateway.example.com/reverse-proxy/sessions |
| 212 | +
|
| 213 | +# Disconnect a session |
| 214 | +curl -X DELETE -H "Authorization: Bearer $TOKEN" \ |
| 215 | + https://gateway.example.com/reverse-proxy/sessions/{session_id} |
| 216 | +``` |
| 217 | + |
| 218 | +### 3. Virtual Server Registration |
| 219 | + |
| 220 | +Reverse-proxied servers automatically appear in the gateway's server catalog and can be accessed like any other MCP server. |
| 221 | + |
| 222 | +## Security Considerations |
| 223 | + |
| 224 | +### Authentication |
| 225 | + |
| 226 | +- Always use authentication tokens in production |
| 227 | +- Tokens should have appropriate expiration times |
| 228 | +- Consider using mutual TLS for additional security |
| 229 | + |
| 230 | +### Network Security |
| 231 | + |
| 232 | +- The reverse proxy only requires outbound HTTPS/WSS |
| 233 | +- No inbound firewall rules needed |
| 234 | +- All traffic is encrypted via TLS |
| 235 | + |
| 236 | +### Best Practices |
| 237 | + |
| 238 | +1. **Use specific tokens per deployment** |
| 239 | + ```bash |
| 240 | + # Generate deployment-specific token |
| 241 | + python3 -m mcpgateway.utils.create_jwt_token \ |
| 242 | + --username edge-server-01 \ |
| 243 | + --exp 10080 \ |
| 244 | + --secret $JWT_SECRET |
| 245 | + ``` |
| 246 | + |
| 247 | +2. **Monitor connection health** |
| 248 | + - Check gateway logs for connection events |
| 249 | + - Monitor reconnection attempts |
| 250 | + - Set up alerts for persistent failures |
| 251 | + |
| 252 | +3. **Resource limits** |
| 253 | + - Set appropriate memory/CPU limits in containers |
| 254 | + - Configure max message sizes |
| 255 | + - Implement rate limiting on the gateway |
| 256 | + |
| 257 | +## Troubleshooting |
| 258 | + |
| 259 | +### Connection Issues |
| 260 | + |
| 261 | +1. **Check connectivity**: |
| 262 | + ```bash |
| 263 | + # Test gateway reachability |
| 264 | + curl -I https://gateway.example.com/healthz |
| 265 | + ``` |
| 266 | + |
| 267 | +2. **Verify authentication**: |
| 268 | + ```bash |
| 269 | + # Test token validity |
| 270 | + curl -H "Authorization: Bearer $TOKEN" \ |
| 271 | + https://gateway.example.com/reverse-proxy/sessions |
| 272 | + ``` |
| 273 | + |
| 274 | +3. **Enable debug logging**: |
| 275 | + ```bash |
| 276 | + python3 -m mcpgateway.reverse_proxy \ |
| 277 | + --local-stdio "uvx mcp-server-git" \ |
| 278 | + --log-level DEBUG |
| 279 | + ``` |
| 280 | + |
| 281 | +### Common Errors |
| 282 | + |
| 283 | +| Error | Cause | Solution | |
| 284 | +|-------|-------|----------| |
| 285 | +| `Connection refused` | Gateway unreachable | Check gateway URL and network | |
| 286 | +| `401 Unauthorized` | Invalid token | Regenerate token with correct secret | |
| 287 | +| `WebSocket connection failed` | Firewall blocking WSS | Check outbound port 443 | |
| 288 | +| `Subprocess not running` | Local server crashed | Check server command and logs | |
| 289 | +| `Max retries exceeded` | Persistent network issue | Check network stability | |
| 290 | + |
| 291 | +### Performance Tuning |
| 292 | + |
| 293 | +1. **Adjust keepalive interval**: |
| 294 | + ```bash |
| 295 | + # Shorter interval for unstable networks |
| 296 | + --keepalive 15 |
| 297 | +
|
| 298 | + # Longer interval for stable networks |
| 299 | + --keepalive 60 |
| 300 | + ``` |
| 301 | + |
| 302 | +2. **Configure reconnection strategy**: |
| 303 | + ```bash |
| 304 | + # Quick reconnect with limited retries |
| 305 | + --reconnect-delay 0.5 --max-retries 20 |
| 306 | +
|
| 307 | + # Slow reconnect with infinite retries |
| 308 | + --reconnect-delay 5 --max-retries 0 |
| 309 | + ``` |
| 310 | + |
| 311 | +## Advanced Usage |
| 312 | + |
| 313 | +### Multiple Local Servers |
| 314 | + |
| 315 | +Run multiple reverse proxies for different servers: |
| 316 | + |
| 317 | +```yaml |
| 318 | +# multi-server.yaml |
| 319 | +servers: |
| 320 | + - name: git-server |
| 321 | + command: "uvx mcp-server-git" |
| 322 | + gateway: "https://gateway1.example.com" |
| 323 | +
|
| 324 | + - name: filesystem-server |
| 325 | + command: "uvx mcp-server-filesystem --directory /data" |
| 326 | + gateway: "https://gateway2.example.com" |
| 327 | +``` |
| 328 | + |
| 329 | +### Load Balancing |
| 330 | + |
| 331 | +Connect the same server to multiple gateways: |
| 332 | + |
| 333 | +```bash |
| 334 | +# Primary gateway |
| 335 | +python3 -m mcpgateway.reverse_proxy \ |
| 336 | + --local-stdio "uvx mcp-server-git" \ |
| 337 | + --gateway https://gateway1.example.com & |
| 338 | +
|
| 339 | +# Backup gateway |
| 340 | +python3 -m mcpgateway.reverse_proxy \ |
| 341 | + --local-stdio "uvx mcp-server-git" \ |
| 342 | + --gateway https://gateway2.example.com & |
| 343 | +``` |
| 344 | + |
| 345 | +### Monitoring Integration |
| 346 | + |
| 347 | +Export metrics for monitoring systems: |
| 348 | + |
| 349 | +```python |
| 350 | +# Custom monitoring wrapper |
| 351 | +import asyncio |
| 352 | +from mcpgateway.reverse_proxy import ReverseProxyClient |
| 353 | +
|
| 354 | +class MonitoredReverseProxy(ReverseProxyClient): |
| 355 | + async def connect(self): |
| 356 | + # Export connection metric |
| 357 | + prometheus_client.Counter('reverse_proxy_connections_total').inc() |
| 358 | + await super().connect() |
| 359 | +``` |
| 360 | + |
| 361 | +## Related Documentation |
| 362 | + |
| 363 | +- [MCP Gateway Documentation](./README.md) |
| 364 | +- [MCP Protocol Specification](https://modelcontextprotocol.io) |
| 365 | +- [Transport Protocols](./transports.md) |
| 366 | +- [Authentication Guide](./auth.md) |
0 commit comments