{
"host": "mcp.example.com",
"path": "/github",
"backend": "https://api.github.com",
"auth": {},
"openapi": { "url": "http://landing/github-openapi.json", "public": false },
"mcp": {
"enabled": true,
"name": "github",
"operations": [
{ "name": "list_repos", "operationId": "list_repos" },
{ "name": "get_repo", "operationId": "get_repo" }
]
}
}Expose REST APIs and MCP servers as MCP tools
Useful if you want to…
give MCP clients (an assistant, an IDE, an agent) a governed tool surface over services that do not speak MCP;
expose a documented REST API as MCP tools without writing a server;
proxy an existing MCP server and restrict which tools it exposes;
aggregate several services under one endpoint with its own audience, scope and identity rules.
One endpoint, three sources
An MCP route (mcp.enabled) builds its tool list one of three ways:
| Source | What you declare |
|---|---|
Derive from REST |
|
Proxy an MCP server |
|
Aggregate |
|
The tools are derived from the route policy and the upstream OpenAPI document, not hard-coded per API: the same binary exposes any documented REST service as MCP.
Note | The JSON below is a partial extract (one route or set), not a
complete file. The runnable file is
examples/mcp-tools/routes.json. |
Derive tools from an OpenAPI document
Gate One resolves each operationId in the OpenAPI document to the
method, path template, parameters and JSON body schema, and turns them
into the tool’s arguments. An unresolved operationId refuses the
configuration at load, so a typo never ships as a silently missing tool.
Proxy an existing MCP server
Instead of deriving local tools, forward to a server that already speaks MCP over Streamable HTTP:
{
"host": "mcp.example.com",
"path": "/metrics",
"backend": "http://metrics-mcp:8080",
"auth": {},
"mcp": {
"enabled": true,
"name": "metrics",
"upstream": {
"url": "http://metrics-mcp:8080/mcp",
"tools": { "include": ["list_*", "get_*"], "exclude": ["*_secret"] }
}
}
}include and exclude are glob patterns over tool names; exclude
always wins, and a hidden tool is refused on both tools/list and
tools/call.
Aggregate services under one endpoint
mcpSets groups named services (mcp.name) under a single endpoint. The
set carries the access rights; each member keeps its own path and tool
filter:
{
"mcpSets": {
"developer-tools": {
"host": "mcp.example.com",
"endpoint": "/developer-tools",
"resource": "https://mcp.example.com/developer-tools",
"scope": "mcp-tools",
"identity": { "roles": ["developer"] },
"members": [
{ "service": "github", "path": "/github",
"tools": ["list_repos", "get_repo"] },
{ "service": "metrics", "path": "/metrics" }
]
}
}
}a member endpoint is
host+endpoint+path;toolsis an allow-list; omit it to expose every tool of the service;enabled: falseskips a member entirely (its service is not even compiled), which is how you stage a service without removing it;set
mcp.standalone: falseon a service that must exist only through a set, never on its own endpoint.
Note |
|
Authenticate the upstream
The tool call reaches the backend with one of:
static headers —
upstream.headers, one shared value;delegated credentials —
upstream.tokenBrokerinjects the caller’s per-user token from the token broker. It requires the top-leveltokenBrokerblock (the same name, at the root of the file); a route that declaresupstream.tokenBrokerwithout it refuses to load.
The upstream never receives the Gate One bearer. Delegation is what lets a tool call GitHub or GitLab as the user; see Delegated credentials.
Metadata, scope and audience
Each endpoint serves /.well-known/oauth-protected-resource with its
resource (audience), scope and tool list, and Gate One checks the bearer
aud and scope before any tool runs. The rules are shared with
protected APIs: see
Protect APIs and MCP tools.
Complete example
MCP tools shows a REST-derived service, a proxied MCP server, an aggregating set and delegated credentials.