Point it at an API's specification and it becomes an MCP server. One tool per operation, no code to write.
api-mcp --spec https://api.exemplo.com/openapi.yamlIt reads OpenAPI 3.x, Swagger 2.0 and GraphQL — as JSON, YAML or SDL — from a file,
a URL or stdin. The dialect is detected from the content; --type forces it when detection gets
it wrong.
Plenty of APIs have no MCP server, and the ones that do are not always auditable: using a server hosted by a third party means handing it the credentials of the people you serve. With the specification in hand the server needs neither to be written nor trusted to anyone — it is generated, it runs wherever you put it, and the credentials never leave.
go install github.com/rosaldo/api-mcp@latestOr download a binary for your platform from the
releases — Linux, macOS and Windows,
amd64 and arm64, with SHA256SUMS to verify them.
# see what the spec yields, without starting anything
api-mcp --spec ./openapi.yaml --list
# stdio (default) — this is how an MCP client starts the server
api-mcp --spec ./openapi.yaml --auth bearer --bearer "$TOKEN"
# GraphQL: a schema does not say where the API lives, so the endpoint is required
api-mcp --spec ./schema.graphql --endpoint https://api.example.com/graphql
# HTTP, if you would rather have a server running
api-mcp --spec ./openapi.yaml --mode http --addr :8080In an MCP client:
{
"mcpServers": {
"my-api": {
"command": "api-mcp",
"args": ["--spec", "https://api.example.com/openapi.yaml", "--auth", "bearer", "--bearer", "env:MY_API_TOKEN"],
"env": { "MY_API_TOKEN": "..." }
}
}
}Keep secrets out of the arguments. Any value can be read from an environment variable with
the env: prefix — a secret passed directly sits in ps output and in /proc/<pid>/cmdline,
where every other process on the machine can read it:
--bearer env:MY_API_TOKEN # reads $MY_API_TOKEN
--auth-field secret=env:MY_SECRET
--header 'X-Key=env:MY_KEY'An unset variable is an error, not an empty string.
Static, when the token is fixed:
--auth bearer --bearer env:TOKEN
--auth basic --basic env:USER_AND_PASSWORD # the variable holds user:password
--auth apikey --api-key header:X-Api-Key=env:KEY # header | query | cookieDynamic, when the API trades credentials for a short-lived token — the case most tools do not cover, and the one that makes a server work for two hours and then return nothing but 401:
api-mcp --spec ./openapi.yaml \
--auth-url https://api.example.com/authenticate \
--auth-field key=env:API_KEY --auth-field secret=env:API_SECRET \
--auth-token-path data.token \
--auth-ttl 2hThe token is fetched on the first call, kept in memory and renewed before it expires.
Every Query and Mutation field becomes a tool. Since GraphQL requires the caller to say what
comes back, the selection is assembled automatically: the scalar fields of the return type,
descending two levels (--graphql-depth changes that). When the default does not fit, the tool
takes a _select argument with a hand-written selection.
Arguments travel as GraphQL variables, never interpolated into the query text.
The schema can be SDL or the JSON of an introspection query — useful when all you have is the endpoint.
A large spec becomes dozens of tools, and each one takes up the model's context:
--include-paths '^/v2/(offers|links)' # regexes, comma-separated
--exclude-paths '^/admin'
--include-methods GET,POST
--exclude-methods DELETE| Flag | What |
|---|---|
--spec |
path, file://, http(s):// or - (stdin) |
--type |
openapi | graphql — forces the dialect |
--base-url |
OpenAPI: beats the spec's servers |
--endpoint |
GraphQL: where queries go |
--header |
fixed header on every call, name=value (repeatable) |
--graphql-depth |
depth of the automatic selection (default 2) |
--mode |
stdio (default) | sse | http |
--addr, --path |
address and path in the network modes |
--list |
list the tools and exit |
./commit.sh feat "what changed" # gate → version bump → CHANGELOG → tag
./push.sh # build the binaries, push, tag and publish the ReleaseBoth are shortcuts to scripts/.
docs/architecture.md— the design and the decisions behind it.
The idea of serving a spec as MCP tools comes from swagger-mcp (MIT), by Danish J Sheikh — the one-tool-per-operation model, the filters and the three transports came from there. Thank you.
MIT.