OpenAPI Specs as Tools
The OpenAPI Specs as Tools use case describes deriving MCP tools directly from an OpenAPI document instead of hand-authoring each one. This tutorial registers a two-operation OpenAPI document in a catalog and compiles it into a tool with mcp-openapi · client, no mcp-http tool config written by hand.
Prerequisites
docker-compose.yaml
services:
gateway:
image: ghcr.io/aklivity/zilla:latest
pull_policy: always
depends_on:
- api
ports:
- 7114:7114
volumes:
- ./zilla.yaml:/etc/zilla/zilla.yaml
command: start -v -e
api:
image: kennethreitz/httpbin
ports:
- 8000:80zilla.yaml
name: openapi-specs-as-tools
stores:
cache:
type: memory
#region spec
catalogs:
spec_catalog:
type: inline
options:
subjects:
billing_openapi:
schema: |
openapi: 3.0.3
info:
title: Billing API
version: "1.0.0"
servers:
- url: http://placeholder
paths:
/anything/billing/{id}:
get:
operationId: getPaymentStatus
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
#endregion spec
bindings:
north_tcp_server:
type: tcp
kind: server
options:
host: 0.0.0.0
port: 7114
routes:
- when:
- port: 7114
exit: north_http_server
north_http_server:
type: http
kind: server
options:
access-control:
policy: cross-origin
routes:
- when:
- headers:
":path": /mcp
exit: north_mcp_server
north_mcp_server:
type: mcp
kind: server
exit: north_mcp_proxy
north_mcp_proxy:
type: mcp
kind: proxy
options:
cache:
store: cache
ttl: PT5M
routes:
- exit: billing_openapi_client
when:
- toolkit: billing
#region client
billing_openapi_client:
type: mcp-openapi
kind: client
options:
specs:
billing:
server: http://api:80
catalog:
spec_catalog:
subject: billing_openapi
version: latest
routes:
- when:
- tool: get_payment_status
with:
spec: billing
operation: getPaymentStatus
#endregion clientThe OpenAPI document lives in a catalog subject, the same catalog mechanism HTTP APIs as Tools uses for JSON schemas:
catalogs:
spec_catalog:
type: inline
options:
subjects:
billing_openapi:
schema: |
openapi: 3.0.3
info:
title: Billing API
version: "1.0.0"
servers:
- url: http://placeholder
paths:
/anything/billing/{id}:
get:
operationId: getPaymentStatus
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'200':
description: OKmcp-openapi · client references that subject by specs.<name>.catalog, overrides the document's own server to point at the running upstream, and routes one operation to a tool:
billing_openapi_client:
type: mcp-openapi
kind: client
options:
specs:
billing:
server: http://api:80
catalog:
spec_catalog:
subject: billing_openapi
version: latest
routes:
- when:
- tool: get_payment_status
with:
spec: billing
operation: getPaymentStatusStart the Stack
docker compose up -dCall the Derived Tool
Point an MCP client at http://localhost:7114/mcp. tools/list shows billing__get_payment_status with an inputSchema derived entirely from the OpenAPI operation's id path parameter, no schemas.input written by hand. Calling it with {"id": "pay_1"} resolves id into the upstream path exactly like a hand-authored mcp-http tool would:
{
"structuredContent": { "result": { "url": "http://api:80/anything/billing/pay_1", "method": "GET" } },
"content": [{ "type": "text", "text": "Call getPaymentStatus" }]
}Neither the tool's description nor its result text was configured, both are fallbacks: the description falls back to the operation id, and the result text falls back to a generic literal naming the operation. Add tools.get_payment_status.description and tools.get_payment_status.summary under options on billing_openapi_client to override either one.
Stop the Stack
docker compose downNext Steps
- See HTTP APIs as Tools for hand-authoring a tool when no OpenAPI document exists.
- See the mcp-openapi client reference for bulk-selecting every operation in a spec by
tagor glob, and for mapping an operation's ownsecurityrequirement to a guard.

