OpenAPI Specs as Tools
HTTP APIs as Tools covers wrapping a REST API by hand, naming each tool and its schema in configuration. That's the right fit when no OpenAPI document exists. When one already does, hand-authoring a tool per operation just re-describes what the spec already declares.
The Problem
An OpenAPI document already names every operation, its parameters, and its request and response shapes. Re-declaring all of that as mcp-http tool configuration duplicates the spec and drifts from it the moment either one changes.
How Zilla Solves It
The mcp-openapi · client binding reads an OpenAPI document directly and derives MCP tools and resources from it, no hand-authored tool list required. Whether an operation becomes a tool or a resource depends on the operation itself:
- A
GEToperation with no path parameters becomes a static resource, listed inresources/listat a fixed URI. - A
GEToperation with a path parameter becomes a resource template, listed inresources/templates/listand read with a concrete value substituted in. - Every other operation, and any
GETexplicitly markedtool:instead ofresource:, becomes a tool.
petstore_openapi_client:
type: mcp-openapi
kind: client
options:
specs:
petstore:
server: http://petstore:4002
catalog:
petstore_catalog:
subject: petstore-spec
version: latest
tools:
search_pets:
input:
model: json
catalog:
petstore_catalog:
- subject: search_pets_params
version: latest
routes:
- when:
- tool: search_pets
with:
spec: petstore
operation: searchPets
params:
tag: ${args.category}Bulk-Selecting Operations
Naming one operation per route, as search_pets does above, is the explicit case. A route can bulk-select many operations at once instead, with routes[].with.tag selecting every operation carrying an OpenAPI tag, or routes[].with.operation containing a * glob matching many operation ids:
routes:
- with:
spec: petstore
tag: pets
- with:
spec: petstore
operation: "search_*"Bulk-selected operations always become tools, never resources, and are named automatically: after their operationId converted to snake_case, falling back to a method-and-path slug when the id is absent or already taken by an earlier route. Routes are evaluated in order, and the first route to claim an operation removes it from consideration by every later route, so an explicit route ahead of a bulk one can carve out one operation for custom naming or schema overrides while the bulk route sweeps up the rest.
Schemas, Derived or Overridden
Each tool's input and output schema is derived from the OpenAPI document by default, no configuration needed. options.tools.<name>.input/output overrides either one explicitly, backed by a model and catalog, the same shape HTTP APIs as Tools uses. Override when the derived schema needs constraints the document doesn't express, or when an argument should be renamed for the agent, as in search_pets above, whose one argument (category) is renamed from the OpenAPI parameter's own name (tag); routes[].with.params reconciles the two, so the upstream still receives tag.
Titles, Descriptions, and Annotations
OpenAPI has no native concept of an MCP tool title or behavior hint (readOnlyHint, destructiveHint, and so on), so a bulk-selected operation only has these if something supplies them. An operation can carry its own x-zilla-mcp vendor extension for exactly this:
paths:
/pets/{id}:
delete:
operationId: delete_pet
x-zilla-mcp:
title: Delete Pet
description: Remove a pet from the store by id.
annotations:
destructiveHint: true
idempotentHint: trueoptions.tools.<name>.title, description, and annotations override the extension when both are present, and the extension itself is the fallback before an HTTP-method-derived default (a DELETE defaults destructiveHint/idempotentHint to true, for instance). When the document isn't yours to edit, an overlay can add x-zilla-mcp without touching the vendored file.
Patching a Third-Party Spec
options.specs.<name>.overlay applies an OpenAPI Overlay document, resolved from a catalog the same way the base spec is, before the spec is parsed. An overlay action can add a security requirement, a servers entry, or x-zilla-mcp metadata to an operation the base document doesn't have edit access to, without forking it:
specs:
petstore:
server: http://petstore:4002
catalog:
petstore_catalog:
subject: petstore-spec
version: latest
overlay:
petstore_catalog:
subject: petstore-overlay
version: latestRedirecting the Outbound Host
An OpenAPI document's declared server is often a public, external address, not necessarily where Zilla should actually send the request. options.specs.<name>.server overrides the outbound host independent of what the document says; nothing else about routing changes.
Security From the Spec
An OpenAPI operation can declare its own security requirement. options.specs.<name>.security maps that requirement to a guard, so a tool derived from a secured operation is authorized the same way a guarded: route authorizes an mcp-http or mcp · client toolkit, expressed through the OpenAPI document instead of a separate route. An operation with no security of its own still inherits whatever toolkit-level guarded: route gates the toolkit as a whole; toolkit access and operation access are independent checks, both have to pass.
Try It
Try the example
Walk through OpenAPI Specs as Tools for the runnable steps: registering a small OpenAPI document in a catalog and compiling it into a tool with mcp-openapi · client, no hand-authored schema.
The same mechanism, bundled
mcp-kafka-connect and mcp-schema-registry use this same overlay/x-zilla-mcp mechanism internally, applied to a bundled OpenAPI document for their respective REST APIs, to supply real tool titles, descriptions, and annotations without editing the vendored spec. Neither exposes options.specs for you to configure directly; they're documented as their own binding types in Backend Bindings.

