Generate a Model Context Protocol (MCP) server in Go from any OpenAPI 3.x or Swagger 2.0 specification. Each operation in the spec becomes an MCP tool; tool calls are forwarded to an oapi-codegen HTTP client.
openapi-go-mcp is the OpenAPI counterpart to redpanda-data/protoc-gen-go-mcp.
- OpenAPI 3.0, 3.1, and Swagger 2.0 input — Swagger 2.0 is auto-converted via kin-openapi.
- Companion to oapi-codegen — the generated
*.mcp.goimports the user'soapi-codegenpackage and delegates HTTP work to its typedClientWithResponsesInterface. Zero glue code. - MCP-library-agnostic — runtime targets a thin
MCPServerinterface; ship adapters for the officialmodelcontextprotocol/go-sdkandmark3labs/mcp-go. Switch backends by changing one import. - Tool-call schema built from the spec — path / query / header / body grouped into a single JSON Schema with
$defsfor shared components; recursion-safe. - OpenAI compatibility mode —
-openai-compatflag emits a flattened,$ref-free schema suitable for OpenAI's strict tool-call schema validator. - Multi-spec batch generation —
-specaccepts a directory (recursive walk), glob pattern, or comma-separated list of any of those. Each matched spec is rendered into its own<slug>mcp/subdirectory under-out, withPackageNameandClientImportauto-derived from the filename stem. - Curated exposure with
x-mcp— spec authors can opt operations, path-items, or the whole document in or out of MCP tool generation; pair with-exclude-by-defaultfor opt-in-only specs. - Runtime options —
WithNamePrefix(namespace tools when the same API is registered multiple times),WithExtraProperties(inject per-call context such as base URL or auth token).
Pick whichever fits your environment — every channel ships the same binary.
Homebrew (macOS, Linux)
brew install dipjyotimetia/tap/openapi-go-mcpPre-built binaries — download the archive matching your OS / arch from the latest release, unpack it, and put openapi-go-mcp on your PATH.
Container image (linux/amd64 + linux/arm64)
docker pull ghcr.io/dipjyotimetia/openapi-go-mcp:latest
docker run --rm -v "$PWD":/workspace ghcr.io/dipjyotimetia/openapi-go-mcp:latest \
-spec /workspace/petstore.yaml -out /workspace/mcp -package petmcp \
-client-import github.com/example/petstoreFrom source (Go 1.26+)
go install github.com/dipjyotimetia/openapi-go-mcp/cmd/openapi-go-mcp@latest
# or pin to a specific release
go install github.com/dipjyotimetia/openapi-go-mcp/cmd/openapi-go-mcp@v0.1.0Verify with openapi-go-mcp -version. Generated companion and proxy code
requires Go 1.26+, matching the runtime module it imports.
Two emission modes. Pick proxy for a turnkey server you can go build && ./server, or companion to embed the MCP layer in an existing module alongside your own oapi-codegen clients.
# One command. Produces a complete Go module: main.go + go.mod + <pkg>/<pkg>.mcp.go + README.md.
openapi-go-mcp \
-mode=proxy \
-spec petstore.yaml \
-out gen/petstore-mcp \
-module github.com/me/petstore-mcp
cd gen/petstore-mcp
go mod tidy
go build
# Auth credentials come from environment variables derived from the spec's
# securitySchemes. The generated README.md lists the env var per scheme.
BEARER_TOKEN_BEARERAUTH=xxx ./petstore-mcp # serves MCP on stdioThe spec's servers[0].url is the default upstream base URL; override with API_BASE_URL. The generated server validates inputs against the JSON Schema derived from the spec, signs the upstream request with credentials from env vars, and surfaces HTTP responses (including non-2xx status + headers) as MCP tool results.
# 1. Generate the oapi-codegen HTTP client for your OpenAPI spec.
oapi-codegen -generate types,client -package pet -o gen/pet/pet.gen.go petstore.yaml
# 2. Generate the MCP companion.
openapi-go-mcp \
-spec petstore.yaml \
-out gen/petmcp \
-package petmcp \
-client-import github.com/me/myrepo/gen/petpackage main
import (
"context"
"github.com/modelcontextprotocol/go-sdk/mcp"
"github.com/me/myrepo/gen/pet"
"github.com/me/myrepo/gen/petmcp"
"github.com/dipjyotimetia/openapi-go-mcp/pkg/runtime/gosdk"
)
func main() {
client, _ := pet.NewClientWithResponses("https://api.example.com")
raw, s := gosdk.NewServer("petstore-mcp", "1.0.0")
petmcp.RegisterSwaggerPetstoreClient(s, client)
_ = raw.Run(context.Background(), &mcp.StdioTransport{})
}You write main.go, you own the HTTP transport (custom retries, tracing, mTLS, etc. flow naturally through your oapi-codegen client), and auth is your code's responsibility. Use this mode when the MCP layer is one feature of a larger service binary.
openapi-go-mcp [flags]
-spec PATH OpenAPI 3.x / Swagger 2.0 source. Accepts:
• a single file path
• an http(s):// URL
• a directory (recursively walked, .yaml/.yml/.json)
• a glob pattern (filepath.Glob: *, ?, [...])
• a comma-separated list of any of the above
When the value matches multiple specs, batch mode
is activated: each spec gets its own <slug>mcp/
subdirectory under -out. (required)
-out DIR output directory (default ./mcp). In batch mode this
is the base directory; each spec lands in <out>/<slug>mcp/
-package NAME Go package name (default derived from spec title).
Rejected in batch mode — packages are auto-derived
from filename stems instead.
-client-import PATH import path of the oapi-codegen output package
(required, except with -list / -emit-v3). In batch
mode this is treated as a base path and the slug
is appended (forward-slash join).
-client-type NAME client interface name (default ClientWithResponsesInterface)
-mode MODE emission mode: companion (default) generates a
*.mcp.go file that delegates to your oapi-codegen
client; proxy generates a runnable Go module that
calls the upstream API directly (no oapi-codegen)
-module PATH module path for the generated go.mod. Required iff
-mode=proxy; rejected otherwise. In batch mode it is
a base path and each spec's slug is appended.
-sdk NAME MCP SDK the proxy scaffold's main.go imports:
gosdk (default, modelcontextprotocol/go-sdk) or
mark3labs (mark3labs/mcp-go). Ignored in companion mode.
-runtime-version VERSION version of openapi-go-mcp to pin in a proxy scaffold.
Released binaries infer it; unversioned development
builds require this flag (or a local runtime replace).
-name-prefix PREFIX static prefix added to every tool name
-openai-compat emit OpenAI-tool-compatible JSON Schema
-prefer-content-type CT pick this content type for the request body when an
operation declares multiple (overrides the default
JSON → form → multipart → octet → text → xml priority)
-exclude-by-default invert x-mcp filtering: only operations explicitly
opted in with `x-mcp: true` are generated (default
is to generate every operation unless excluded with
`x-mcp: false`)
-force overwrite the generated *.mcp.go file if it exists;
without this, an existing file is a fatal error
-list print the operations found in the spec and exit
-emit-v3 PATH write the spec as OpenAPI 3 YAML to PATH (Swagger 2.0 conversion helper)
-warnings-as-errors exit non-zero when any warning-level diagnostic fires
-version print version information and exit
Tag any operation, path-item, or the document root with x-mcp: false to keep
it out of the generated tool list; x-mcp: true opts it back in. The most
specific level wins (operation > path > document > CLI default). Excluded
operations show up as info diagnostics; typos like x-mcp: maybe become
warnings so they don't slip past review.
paths:
/admin:
x-mcp: false # exclude every operation under /admin …
delete:
operationId: purgeAll
get:
operationId: listAdmins
x-mcp: true # … except this onePair with -exclude-by-default when only a small curated subset of a large
spec should be exposed: nothing is generated unless x-mcp: true appears
explicitly.
Point -spec at a directory, glob, or comma-separated list — every match is
rendered into its own subdirectory under -out:
# Recursive directory: every spec under apis/ becomes its own tool set
openapi-go-mcp \
-spec apis/ \
-out gen \
-client-import github.com/acme/apis/gen \
-force
# Glob (filepath.Glob syntax — no ** in v1; use a directory for recursion)
openapi-go-mcp -spec 'apis/*.yaml' -out gen -client-import github.com/acme/apis/gen
# Multiple folders / mixed inputs, comma-separated
openapi-go-mcp -spec 'core/,extras/audit.yaml' -out gen -client-import example.com/gFor each matched spec the generator derives a slug from the filename stem
(billing-api.yaml → billingapi) and writes
<out>/<slug>mcp/<slug>mcp.mcp.go. The -client-import value is treated as
a base path; the slug is appended with forward slashes, so
github.com/acme/apis/gen on billing.yaml becomes
github.com/acme/apis/gen/billing in the generated import line.
Failures don't stop the run: each spec is processed independently, every
error is reported at end, and the process exits with code 3 if any spec
failed. Slug collisions (e.g. v1/api.yaml and v2/api.yaml both → api)
are caught up front before any file is written.
See docs/usage-patterns.md Pattern 12 for the
full walkthrough.
oapi-codegen does not accept Swagger 2.0 input. Convert first:
openapi-go-mcp -spec petstore-v2.json -emit-v3 petstore-v3.yaml
oapi-codegen -generate types,client -package pet -o gen/pet/pet.gen.go petstore-v3.yaml
openapi-go-mcp -spec petstore-v3.yaml -out gen/petmcp -package petmcp -client-import ...-emit-v3 also prunes non-JSON content types from responses, which works around a known issue in oapi-codegen v2.7.0 with responses exposed under multiple content types.
The same generated *.mcp.go works with either backend — change only the runtime adapter import:
| Backend | Adapter | Server construction |
|---|---|---|
modelcontextprotocol/go-sdk (official) |
pkg/runtime/gosdk |
raw, s := gosdk.NewServer(name, version) |
mark3labs/mcp-go |
pkg/runtime/mark3labs |
raw, s := mark3labs.NewServer(name, version) |
See examples/petstore and examples/petstore-mark3labs.
| Directory | What it demonstrates |
|---|---|
examples/todos |
Canonical end-to-end demo — two real binaries: todos-server (standalone HTTP backend with graceful shutdown + request logging + /healthz) and todos-mcp (MCP proxy that forwards every tool call over HTTP). Ships a README with MCP client configs for Claude Desktop, Claude Code, Cursor, VS Code, and Inspector. |
examples/petstore |
OpenAPI 3.0, JSON bodies, go-sdk backend |
examples/petstore-mark3labs |
Same spec on the mark3labs/mcp-go backend |
examples/swagger2-petstore |
Swagger 2.0 input via -emit-v3 conversion |
examples/users-api |
UUID path params, required headers, PUT / PATCH / DELETE |
examples/library |
Swagger 2.0 end-to-end (load → convert → generate) |
examples/complex |
Recursive $ref, oneOf / allOf, enums, date-time / uuid formats |
examples/non-json-bodies |
Form-urlencoded, multipart (with base64 file fields), octet-stream, text/plain, XML |
Each operation's MCP tool input schema groups parameters by location:
{
"type": "object",
"properties": {
"path": { "type": "object", "properties": { "petId": { "type": "integer" } }, "required": ["petId"] },
"query": { "type": "object", "properties": { "limit": { "type": "integer" } } },
"header": { "type": "object", "properties": { "X-Trace-Id": { "type": "string" } } },
"body": { "$ref": "#/$defs/NewPet" }
},
"required": ["path", "body"],
"$defs": { "NewPet": { ... } }
}Empty groups are omitted. Shared schemas referenced through $ref are hoisted into a per-operation $defs to keep each tool's schema self-contained.
In -openai-compat mode the schema is inlined (no $ref), composition keywords (oneOf/anyOf/allOf) are flattened, and every object carries additionalProperties: false.
import "github.com/dipjyotimetia/openapi-go-mcp/pkg/runtime"
// Prefix every tool name — useful when registering the same API twice.
petmcp.RegisterSwaggerPetstoreClient(s, client, runtime.WithNamePrefix("staging"))
// Inject per-call context properties (e.g. a tenant token) into every tool.
petmcp.RegisterSwaggerPetstoreClient(s, client, runtime.WithExtraProperties(
runtime.ExtraProperty{Name: "tenant", Description: "Tenant ID", Required: true},
))Pre-1.0. APIs may change between minor versions. Apache 2.0 licensed.
All docs live in docs/. Start there:
- Usage patterns — deployment recipes (stdio, remote, multi-tenant, auth, aggregation, ...)
- Design decisions — the non-obvious choices and why they exist
- Architecture — packages, pipeline, extension points
- Contributing — dev setup, testing, code style
- Changelog
- Security policy
- Code of conduct
Apache License 2.0 — see LICENSE.
Portions of pkg/runtime and pkg/generator/naming.go are adapted from redpanda-data/protoc-gen-go-mcp under the same license.