apimcp

Convert an OpenAPI document into an MCP server.
What it does
apimcp adapts an OpenAPI-described HTTP API into an MCP server. MCP clients
can discover the API operations as tools and call the existing service without
requiring a second hand-written MCP implementation.
Status
This is an MVP. It supports OpenAPI JSON/YAML, common HTTP methods, path and
query/header parameters, JSON request bodies, Bearer tokens, API keys, OAuth2
client credentials, stdio and Streamable HTTP transports, and allow/deny tool
filters. Path-level parameters are merged with operation parameters, and
generated tool names are deterministic.
Local $ref references are resolved through kin-openapi; external references
are disabled by default. Tool calls also perform server-side checks for
required parameters and common JSON types before contacting the upstream API.
API key names and locations can be inferred from OpenAPI securitySchemes when
they are not supplied explicitly. Basic Auth credentials can be provided with
--basic-username and --basic-password-env. Upstream requests have a 30
second timeout and responses are limited to 10 MiB by default.
Operation-level security overrides the document-level requirement, and an
explicit security: [] keeps a public operation from receiving global
credentials. Common OpenAPI parameter styles (form, simple,
spaceDelimited, pipeDelimited, and deepObject) are supported for
path/query/header/cookie parameters.
Multipart file uploads accept Base64-encoded file values, and binary downloads
are returned as Base64 data with their content type and filename when present.
Run
go run ./cmd/apimcp --spec openapi.yaml --base-url https://api.example.com
The project uses the official Go MCP SDK and adapts OpenAPI operations into MCP
tools. It does not expose every operation automatically when --allow is set:
go run ./cmd/apimcp --spec openapi.yaml --allow getUser,listUsers --stdio
For an API key:
go run ./cmd/apimcp --spec openapi.yaml `
--api-key-name X-API-Key `
--api-key-value "$env:API_KEY" `
--api-key-in header
For credentials stored in environment variables:
go run ./cmd/apimcp --spec openapi.yaml `
--bearer-token-env API_TOKEN
go run ./cmd/apimcp --spec openapi.yaml `
--api-key-value-env API_KEY `
--api-key-name X-API-Key
When the OpenAPI document defines an apiKey security scheme, the name and
location flags can be omitted:
go run ./cmd/apimcp --spec openapi.yaml `
--api-key-value-env API_KEY
For an OAuth2 clientCredentials security scheme, keep credentials in
environment variables:
components:
securitySchemes:
serviceOAuth:
type: oauth2
flows:
clientCredentials:
tokenUrl: https://auth.example.com/oauth/token
scopes:
users.read: Read users
security:
- serviceOAuth:
- users.read
$env:API_CLIENT_ID = "my-client"
$env:API_CLIENT_SECRET = "my-secret"
go run ./cmd/apimcp --spec openapi.yaml `
--oauth2-client-id-env API_CLIENT_ID `
--oauth2-client-secret-env API_CLIENT_SECRET `
--oauth2-scopes users.read,users.write
The token URL is inferred from the OpenAPI
flows.clientCredentials.tokenUrl. Use --oauth2-token-url to override it.
Access tokens are cached and refreshed automatically when they expire. Only
the client credentials flow is currently supported; OAuth2 authorization code
and device flows are not implemented.
Reliability options:
go run ./cmd/apimcp --spec openapi.yaml `
--timeout 15s `
--max-response-bytes 5242880 `
--max-retries 2 `
--retry-base-delay 250ms
Retries apply to idempotent methods (GET, HEAD, OPTIONS, PUT, and
DELETE) and transient statuses such as 408, 429, 502, 503, and 504.
POST and PATCH are not retried unless explicitly enabled through the Go
API.
The --stdio flag is accepted for compatibility with MCP client
configurations; stdio is the default transport. To run an HTTP MCP server:
go run ./cmd/apimcp `
--transport http `
--listen 127.0.0.1:8080 `
--spec openapi.yaml `
--base-url https://api.example.com
The Streamable HTTP MCP endpoint is http://127.0.0.1:8080/mcp. The HTTP
server is stateless and returns JSON MCP responses, which is convenient for
local development and simple reverse-proxy deployments.
For a multipart/form-data operation with a binary field, pass the field as:
{
"file": {
"data": "SGVsbG8=",
"filename": "hello.txt",
"contentType": "text/plain"
}
}
For application/octet-stream request bodies, use the same object under the
body argument. Binary responses are exposed in this form:
{
"contentType": "application/pdf",
"filename": "report.pdf",
"data": "<Base64 data>"
}
Library usage
spec, err := apimcp.LoadFile("openapi.yaml")
if err != nil {
return err
}
return apimcp.RunStdio(ctx, spec, apimcp.Options{
BaseURL: "https://api.example.com",
OAuth2ClientID: clientID,
OAuth2ClientSecret: clientSecret,
})
To embed the HTTP transport in another Go service:
handler, err := apimcp.NewHTTPHandler(spec, apimcp.Options{
BaseURL: "https://api.example.com",
BearerToken: token,
})
if err != nil {
return err
}
http.Handle("/mcp", handler)
return http.ListenAndServe(":8080", nil)
Development
go test ./...
go test -race ./...
go vet ./...
The test suite includes an in-memory MCP client and an httptest upstream
server to verify tool discovery, argument validation, request conversion, and
error results end to end.
Roadmap
- External OpenAPI references and reusable schemas
- OAuth2 authorization code and device flows
- generated tool documentation
Contributing
See CONTRIBUTING.md for the development workflow and commit
conventions.