Skip to content
Merged
72 changes: 72 additions & 0 deletions docs/docs/using/mcpgateway-translate.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ python3 -m mcpgateway.translate \
| **Bidirectional communication** | Full duplex message flow in all modes |
| **Session management** | Stateful sessions with event replay (streamable HTTP) |
| **Flexible response modes** | Choose between SSE streams or JSON responses |
| **Dynamic environment injection** | Extract HTTP headers and inject as environment variables for multi-tenant support |
| **Keep-alive support** | Automatic keepalive frames prevent connection timeouts |
| **CORS configuration** | Enable cross-origin requests for web applications |
| **Authentication** | OAuth2 Bearer token support for secure connections |
Expand Down Expand Up @@ -185,6 +186,42 @@ Connect to a remote streamable HTTP endpoint.
| `--messagePath <path>` | Message POST endpoint path | /message |
| `--keepAlive <seconds>` | Keepalive interval | 30 |

### Dynamic Environment Variable Injection

| Option | Description | Default |
|--------|-------------|---------|
| `--enable-dynamic-env` | Enable dynamic environment variable injection from HTTP headers | False |
| `--header-to-env <HEADER=ENV_VAR>` | Map HTTP header to environment variable (can be specified multiple times) | None |

**Use case**: Multi-tenant deployments where different users need different credentials passed to the MCP server.

**Example - GitHub Enterprise with per-user tokens**:
```bash
python3 -m mcpgateway.translate \
--stdio "uvx mcp-server-github" \
--expose-sse \
--port 9000 \
--enable-dynamic-env \
--header-to-env "Authorization=GITHUB_TOKEN" \
--header-to-env "X-GitHub-Enterprise-Host=GITHUB_HOST"
```

**Client request with headers**:
```bash
curl -X POST http://localhost:9000/message \
-H "Authorization: Bearer ghp_user123token" \
-H "X-GitHub-Enterprise-Host: github.company.com" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

**Security features**:
- Header names validated (alphanumeric + hyphens only)
- Environment variable names validated (standard naming rules)
- Values sanitized (dangerous characters removed, length limits enforced)
- Case-insensitive header matching
- Headers not provided in mappings are ignored

## API Documentation

### SSE Mode Endpoints
Expand Down Expand Up @@ -320,6 +357,41 @@ curl -X POST http://localhost:9001/message \
curl -N http://localhost:9001/sse
```

### Multi-Tenant GitHub Enterprise

Enable per-user GitHub tokens for enterprise deployments:

```bash
# Start the bridge with dynamic environment injection
python3 -m mcpgateway.translate \
--stdio "uvx mcp-server-github" \
--expose-sse \
--port 9000 \
--enable-dynamic-env \
--header-to-env "Authorization=GITHUB_TOKEN" \
--header-to-env "X-GitHub-Enterprise-Host=GITHUB_HOST"

# User A's request (uses their personal access token)
curl -X POST http://localhost:9000/message \
-H "Authorization: Bearer ghp_userA_token123" \
-H "X-GitHub-Enterprise-Host: github.company.com" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_repositories"}}'

# User B's request (uses their own token)
curl -X POST http://localhost:9000/message \
-H "Authorization: Bearer ghp_userB_token456" \
-H "X-GitHub-Enterprise-Host: github.company.com" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_repositories"}}'
```

**Benefits**:
- Each user's credentials are isolated per request
- No shared token security risks
- Supports different enterprise hosts per user
- MCP server process restarts with new credentials for each request

### Container Deployment

```dockerfile
Expand Down
6 changes: 4 additions & 2 deletions mcpgateway/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1228,19 +1228,20 @@ async def ping(request: Request, user=Depends(get_current_user)) -> JSONResponse
Raises:
HTTPException: If the request method is not "ping".
"""
req_id: Optional[str] = None
try:
body: dict = await request.json()
if body.get("method") != "ping":
raise HTTPException(status_code=400, detail="Invalid method")
req_id: Optional[str] = body.get("id")
req_id = body.get("id")
logger.debug(f"Authenticated user {user} sent ping request.")
# Return an empty result per the MCP ping specification.
response: dict = {"jsonrpc": "2.0", "id": req_id, "result": {}}
return JSONResponse(content=response)
except Exception as e:
error_response: dict = {
"jsonrpc": "2.0",
"id": None, # req_id not available in this scope
"id": req_id, # Now req_id is always defined
"error": {"code": -32603, "message": "Internal error", "data": str(e)},
}
return JSONResponse(status_code=500, content=error_response)
Expand Down Expand Up @@ -3354,6 +3355,7 @@ async def handle_rpc(request: Request, db: Session = Depends(get_db), user=Depen
PluginError: If encounters issue with plugin
PluginViolationError: If plugin violated the request. Example - In case of OPA plugin, if the request is denied by policy.
"""
req_id = None
try:
# Extract user identifier from either RBAC user object or JWT payload
if hasattr(user, "email"):
Expand Down
Loading
Loading