MCP gateway
Put MCP tool servers behind gateway keys, so clients reach them at /mcp/<server> and models can use their tools.
4 min readUpdated for v2.4.0Edit on GitHub
/mcp/<server> on the data plane, any methodA registered MCP server is used two ways. Clients with a gateway key call it directly at /mcp/<server>, and the gateway adds the server's own credential on the way. Models you grant it to get its tools, and the gateway runs their tool calls for them.
Add a server
- 1
Register it
On the MCP servers page, choose Add server and fill in Name, Upstream URL and, if the server needs one, Authorization (the full header value, such as
Bearer ghp_…). Add and check saves it and checks it at once.The name is the path clients use (
/mcp/github) and the name models are granted it by: letters, digits,.,_and-, up to 64 characters, starting with a letter or digit. - 2
Confirm it answers
The page checks every server when it opens: whether it answers, its tools, the models that use it, its calls in the last 7 days and its response time. A server that does not answer shows under Not answering. See Verifying a server.
- 3
Grant it to models
Open the server and turn on the models under Models that can use it. Only models with Function calling on are listed. Then turn on the tool loop so the gateway runs the calls (see below).
Or use the Management API:
curl -X POST http://localhost:9180/api/v1/mcp-servers \
-H "Authorization: Bearer $OBLETH_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "github", "upstream_url": "https://mcp.githubcopilot.com/mcp", "auth_header": "Bearer ghp_…"}'
GET, PUT and DELETE /api/v1/mcp-servers/{id} read, change and remove a server. auth_header is write-only: responses carry auth_header_set instead, a PUT without it keeps the stored value, and clear_auth: true removes it. A PUT may also rename the server with name, and every model's grant follows the new name. Removing a server removes it from every model that had it. The upstream URL must pass the gateway's outbound address policy.
Call a server through the gateway
Point the MCP client at the gateway with a gateway key:
POST https://gateway.example.com/mcp/github
Authorization: Bearer sk_...
Content-Type: application/json
{"jsonrpc": "2.0", "method": "tools/list", "id": 1}
The key may also go in x-api-key, and an identity token works as it does for model traffic. For each call the gateway:
- Authenticates the credential and applies the tenant gate: a disabled key, a tenant that is not active, or one outside its activation window, expiry or weekly schedule gets 403.
- Looks up the server; a disabled one gets 403
mcp server is disabled. - Removes the client's credential and sends the server's Authorization header instead. Clients never see it.
- Forwards the request and streams the answer back, JSON or SSE, with the server's headers, including
mcp-session-id.
The gateway does not parse JSON-RPC, so any MCP-over-HTTP server works. /mcp/<server>/<rest> forwards the rest of the path and the query string. Bodies are limited to 16 MiB. MCP calls are not admitted through fairshare and not billed.
Verifying a server
A wrong URL or a rejected credential only shows when something calls the server, so the dashboard runs the real MCP handshake (initialize, then tools/list) through /mcp/<name>. It does this when the MCP servers page opens, when you add a server, and when you choose Check again. It reports whether the server answered, its name and version, its tools and the round-trip time. These checks are not counted in the server's call statistics.
The Playground assistant can check every registered server, or a named few, in one step and returns a pass or fail row per server.
The gateway tool loop
When a model is granted MCP servers and the tool loop is on, a plain chat client gets a grounded answer without ever seeing a tool. The gateway adds the servers' tools to the request, runs the model's tool calls against the servers, feeds back the results and asks again until the model answers. A client that sends its own tools keeps them: the granted tools are merged in, and only the gateway's own calls are run by the gateway.
To use it: register the server, grant it to a model with Function calling on, and turn on Tool loop in Settings › Boons, then save. Turn limits, per-tool timeout, the per-request deadline and billing are covered in Boons.
Errors
| Status | Message | Meaning |
|---|---|---|
| 400 | invalid request path | The sub-path tries to leave the server's path |
| 401 | missing bearer token, invalid api key, invalid token | No credential, or one that does not verify |
| 403 | api key disabled, tenant is not active, or a schedule reason | The key or tenant may not call now |
| 403 | mcp server is disabled | The server is turned off |
| 404 | mcp server '<name>' is not registered | No server by that name |
| 413 | request body too large | Over 16 MiB |
| 502 | mcp upstream request failed | The server could not be reached |
| 503 | authentication backend unavailable, identity provisioning unavailable | The credential lookup failed |
An error status from the server itself, 5xx included, is passed back unchanged.
Metrics, traces and alerts
obleth_mcp_requests_total{server, status} counts calls per server by status class (2xx, 4xx, 5xx). With tracing on, each call is an mcp_request span tagged with the server name. Per-server daily counts (direct calls, tool-loop calls, failures, time) feed the MCP servers page and GET /api/v1/stats/daily?kind=mcp.
These alerts go to your alert channels (see Alerting): mcp_upstream_request_failed (the server could not be reached), mcp_upstream_5xx_response, redis_mcp_lookup_failed and mcp_tool_discovery_failed (the tool loop could not list a granted server's tools).
Changes to servers are recorded in the audit log and reach every gateway replica through Redis.
