dynamic-openapi-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@dynamic-openapi-mcplist all available pets"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
dynamic-openapi-mcp
Any OpenAPI spec. Instant AI tools.
Point it at a spec — your AI agent can call the API. OpenAPI v3 • JSON & YAML • Auto-auth • Zero config Every endpoint becomes a tool. Every schema becomes a resource.
Quick Start · The family · Agent Setup · Auth · Filtering · Programmatic API · CLI
Quick Start
npx dynamic-openapi-mcp -s https://petstore3.swagger.io/api/v3/openapi.jsonThat's it. The MCP server starts, your AI agent discovers all the tools, and can call the Petstore API.
For Claude Code, add it in one command:
claude mcp add petstore -- npx dynamic-openapi-mcp -s https://petstore3.swagger.io/api/v3/openapi.jsonNow ask Claude: "list all available pets" — it will call listPets and return real data.
Related MCP server: mcpify
Table of Contents
What's Inside
Category | What you get |
Tools | One per operation — |
Resources | Full spec as |
Prompts |
|
Auth | Bearer, API Key (header/query/cookie), Basic, OAuth2 client credentials, token exchange |
Bodies | JSON, form-urlencoded, multipart/form-data, and octet-stream request bodies |
Sources | URL, local file (JSON/YAML), inline string, or JavaScript object |
The flow is simple: AI calls a tool → dynamic-openapi-mcp makes the real HTTP request → response comes back as MCP content.
The family
Three complementary projects, one spec, three output surfaces — pick the one that fits the use case:
Sibling | Output | Runs when | Best when |
Live MCP server (stdio) | Every tool call spins the server | You want real-time introspection, auto-refreshed OAuth tokens, typed tool I/O | |
Bash CLI (optionally bundled) | Humans and scripts invoke it | You want a commit-friendly shim humans and CI can run | |
Static | Claude loads it on demand | You want zero runtime, diff-friendly docs, and model-driven calls via |
All three share the same parser and auth layer. Switching between them is a matter of pointing them at the same spec.
Setup with AI Agents
Claude Code
Add to your project's .mcp.json:
{
"mcpServers": {
"my-api": {
"command": "npx",
"args": ["dynamic-openapi-mcp", "-s", "https://api.example.com/openapi.json"],
"env": {
"OPENAPI_AUTH_TOKEN": "your-bearer-token"
}
}
}
}Or add via CLI:
claude mcp add my-api -- npx dynamic-openapi-mcp -s https://api.example.com/openapi.jsonCursor
Go to Settings → MCP and add a new server, or add to .cursor/mcp.json:
{
"mcpServers": {
"my-api": {
"command": "npx",
"args": ["dynamic-openapi-mcp", "-s", "./specs/api.yaml"],
"env": {
"OPENAPI_API_KEY": "your-api-key"
}
}
}
}Windsurf
Add to ~/.codeium/windsurf/mcp_config.json:
{
"mcpServers": {
"my-api": {
"command": "npx",
"args": ["dynamic-openapi-mcp", "-s", "https://api.example.com/openapi.json"],
"env": {
"OPENAPI_AUTH_TOKEN": "sk-..."
}
}
}
}Claude Desktop
Add to your config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"my-api": {
"command": "npx",
"args": ["dynamic-openapi-mcp", "-s", "/absolute/path/to/spec.yaml"],
"env": {
"OPENAPI_AUTH_TOKEN": "your-token"
}
}
}
}Multiple APIs
Connect several APIs at once — each runs as a separate MCP server, and the AI sees all their tools combined:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["dynamic-openapi-mcp", "-s", "https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json"],
"env": { "OPENAPI_AUTH_TOKEN": "ghp_..." }
},
"stripe": {
"command": "npx",
"args": ["dynamic-openapi-mcp", "-s", "./specs/stripe.yaml"],
"env": { "OPENAPI_AUTH_TOKEN": "sk_..." }
},
"internal-api": {
"command": "npx",
"args": ["dynamic-openapi-mcp", "-s", "https://internal.company.com/api/v1/openapi.json"],
"env": { "OPENAPI_API_KEY": "key-..." }
}
}
}Authentication
Choosing an Auth Strategy
If your API uses... | Use this | Auto-refresh | Best for |
Static bearer token |
| No | Personal access tokens, fixed service tokens |
Static API key |
| No | Header/query/cookie API keys declared in the spec |
Basic auth |
| No | Legacy username/password APIs |
OAuth2 client credentials |
| Yes | Machine-to-machine OAuth flows with |
Temporary token exchange |
| Yes | Non-standard |
Fully custom auth logic |
| You implement it | Edge cases not covered by built-in strategies |
Via environment variables
# Bearer token (most common)
OPENAPI_AUTH_TOKEN=sk-123 npx dynamic-openapi-mcp -s ./spec.yaml
# API key
OPENAPI_API_KEY=key-456 npx dynamic-openapi-mcp -s ./spec.yaml
# Per-scheme (matches securitySchemes names in your spec)
OPENAPI_AUTH_BEARERAUTH_TOKEN=sk-123 npx dynamic-openapi-mcp -s ./spec.yamlOr set them in the MCP config env block — same effect, cleaner setup.
Supported schemes
Scheme | Env var | Programmatic config |
Bearer |
|
|
API Key (header/query/cookie) |
|
|
Basic |
|
|
OAuth2 (client credentials) | — |
|
Token exchange | — |
|
Custom | — |
|
Resolution order: programmatic config → per-scheme env var → global env var.
Per-scheme environment variables are derived from the securitySchemes name in your OpenAPI document:
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearerThis scheme name maps to:
OPENAPI_AUTH_BEARERAUTH_TOKEN=sk-123For a basic auth scheme named basicAuth, use:
OPENAPI_AUTH_BASICAUTH_TOKEN=username:passwordProgrammatic Examples
Bearer token:
const mcp = await createOpenApiMcp({
source: './spec.yaml',
auth: { bearerToken: process.env.MY_API_TOKEN! },
})API key:
const mcp = await createOpenApiMcp({
source: './spec.yaml',
auth: { apiKey: process.env.MY_API_KEY! },
})Basic auth:
const mcp = await createOpenApiMcp({
source: './spec.yaml',
auth: {
basicAuth: {
username: process.env.API_USER!,
password: process.env.API_PASSWORD!,
},
},
})OAuth2 client credentials with automatic token caching and refresh:
const mcp = await createOpenApiMcp({
source: './spec.yaml',
auth: {
oauth2: {
clientId: process.env.OAUTH_CLIENT_ID!,
clientSecret: process.env.OAUTH_CLIENT_SECRET!,
tokenUrl: 'https://auth.example.com/oauth/token',
scopes: ['pets:read', 'pets:write'],
},
},
})dynamic-openapi-mcp caches the retrieved access token in memory and refreshes it when it is close to expiration.
Temporary Tokens and Refresh
Many APIs are not true OAuth2, but still issue a short-lived bearer token after exchanging credentials such as credId and credSecret.
For these APIs, use auth.tokenExchange. The built-in strategy:
Exchanges credentials for a temporary token.
Caches the token in memory.
Refreshes slightly before
expires_inorexpires_at.Retries once on
401 Unauthorizedafter forcing a fresh token.Reuses a single in-flight refresh promise so concurrent MCP calls do not stampede the auth server.
Example:
import { createOpenApiMcp } from 'dynamic-openapi-mcp'
const mcp = await createOpenApiMcp({
source: './spec.yaml',
auth: {
tokenExchange: {
tokenUrl: 'https://auth.example.com/session',
request: {
contentType: 'application/json',
fields: {
credId: process.env.CRED_ID!,
credSecret: process.env.CRED_SECRET!,
},
},
response: {
tokenField: 'access_token',
expiresInField: 'expires_in',
},
apply: {
location: 'header',
name: 'Authorization',
prefix: 'Bearer ',
},
},
},
})Notes:
auth.tokenExchangealso supports form-encoded requests viarequest.contentType: 'application/x-www-form-urlencoded'.If the token response is nested, use dot-paths such as
response.tokenField: 'data.accessToken'.If there is no expiry metadata, the token stays cached until the API returns
401, then a new exchange is attempted once.apply.locationcan beheader,query, orcookie.The token cache is in-memory. If the MCP process restarts, it will fetch a new token on the next request.
If your auth flow cannot be described declaratively, fall back to
auth.custom.
Advanced fallback with auth.custom:
const mcp = await createOpenApiMcp({
source: './spec.yaml',
auth: {
custom: async (_url, init) => {
const headers = new Headers(init.headers)
headers.set('Authorization', `Bearer ${await getMyTokenSomehow()}`)
return { ...init, headers }
},
},
})How Auth Is Usually Modeled in OpenAPI
For protected endpoints, OpenAPI usually describes the final auth mechanism used when calling the API, not the full lifecycle of how a client should fetch and refresh credentials.
Standard bearer auth:
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
security:
- bearerAuth: []API key auth:
components:
securitySchemes:
apiKeyAuth:
type: apiKey
name: X-API-Key
in: header
security:
- apiKeyAuth: []OAuth2 client credentials:
components:
securitySchemes:
oauth:
type: oauth2
flows:
clientCredentials:
tokenUrl: https://auth.example.com/oauth/token
scopes:
pets:read: Read pets
security:
- oauth: [pets:read]Custom temporary token flows are usually documented in two separate places:
The protected endpoints declare
bearerAuthorapiKeyAuthinsecuritySchemes.A normal operation in
pathsdocuments the login or token-exchange endpoint.
Example:
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
paths:
/auth/token:
post:
summary: Exchange credId and credSecret for a temporary token
requestBody:
required: true
responses:
'200':
description: Token issuedThis pattern is common, but it does not fully tell a generic client:
which credentials should come from environment variables
which response field contains the token
how long the token is valid
when to refresh it
whether a
401should trigger a new exchange
That is why true OAuth2 is easiest to automate from OpenAPI alone, while custom temporary-token systems usually need either explicit auth.tokenExchange config or a small amount of user-supplied code.
If you want to document these custom flows more explicitly for users of this library, a future vendor extension could look like this:
x-dynamic-openapi-mcp-auth:
type: tokenExchange
tokenUrl: https://auth.example.com/session
request:
contentType: application/json
fields:
credId:
env: CRED_ID
credSecret:
env: CRED_SECRET
response:
tokenField: access_token
expiresInField: expires_in
tokenType: BearerThis is not used by dynamic-openapi-mcp today, but it shows the kind of metadata that would make temporary-token flows much easier to automate.
Troubleshooting Auth
If requests return
401 Unauthorized, first confirm the OpenAPI spec'ssecuritySchemesmatches how the real API expects auth.If you use environment variables, prefer per-scheme variables when the spec defines multiple auth schemes.
If your token expires every few minutes, use programmatic auth instead of a static env var.
If your provider gives you a login endpoint that is not OAuth2, start with
auth.tokenExchange. Useauth.customonly when the exchange is too irregular to describe declaratively.If the provider requires the temporary token in a query string or cookie,
auth.tokenExchangesupportsapply.location: 'query'andapply.location: 'cookie'.
Programmatic Usage
pnpm add dynamic-openapi-mcpimport { createOpenApiMcp } from 'dynamic-openapi-mcp'
const mcp = await createOpenApiMcp({
source: 'https://petstore3.swagger.io/api/v3/openapi.json',
auth: { bearerToken: 'my-token' },
})
// Start as MCP server over stdio
await mcp.serve()Custom base URL
const mcp = await createOpenApiMcp({
source: './spec.yaml',
baseUrl: 'http://localhost:3000',
headers: { 'X-Custom-Header': 'value' },
})From an inline spec
const mcp = await createOpenApiMcp({
source: {
openapi: '3.0.3',
info: { title: 'My API', version: '1.0.0' },
servers: [{ url: 'https://api.example.com' }],
paths: {
'/hello': {
get: {
operationId: 'sayHello',
summary: 'Say hello',
responses: { '200': { description: 'OK' } },
},
},
},
},
})Inspecting the parsed spec
const mcp = await createOpenApiMcp({ source: './spec.yaml' })
console.log(mcp.spec.title) // "My API"
console.log(mcp.spec.operations) // ParsedOperation[]
console.log(mcp.spec.schemas) // { Pet: {...}, User: {...} }Retry Behavior
By default, dynamic-openapi-mcp retries only safe methods: GET, HEAD, OPTIONS, and TRACE.
This keeps reads resilient without risking duplicate writes on POST, PUT, PATCH, or DELETE.
If you want different behavior, set fetchOptions.retryPolicy:
const mcp = await createOpenApiMcp({
source: './spec.yaml',
fetchOptions: {
retries: 2,
retryPolicy: 'all', // 'safe-only' (default) | 'all' | 'none'
},
})Notes:
retryPolicy: 'safe-only'is the default.retryPolicy: 'all'retries mutating requests too.retryPolicy: 'none'disables request retries entirely.Built-in auth token fetches use their own internal retry behavior and are not blocked by the default safe-only policy.
CLI Reference
dynamic-openapi-mcp [options] [source]
Options:
-s, --source <url|file> OpenAPI spec URL or file path
-b, --base-url <url> Override the base URL from the spec
--server-index <n> Select Nth server entry (default: 0)
--include-tag <name> Only expose operations with this tag (repeatable, comma-separated)
--exclude-tag <name> Hide operations with this tag (repeatable, comma-separated)
--include-operation <id> Only expose these operationIds (repeatable, comma-separated)
--exclude-operation <id> Hide these operationIds (repeatable, comma-separated)
-h, --help Show helpEnvironment Variable | Description |
| Spec URL or file path (alternative to |
| Override base URL |
| Bearer token for authentication |
| API key for authentication |
Offline bundles
Package any OpenAPI spec into a single standalone bash binary that behaves as an MCP server — no network call at startup, deterministic md5, portable across machines.
dynamic-openapi-mcp bundle \
-s https://petstore3.swagger.io/api/v3/openapi.json \
--name petstore-mcp \
--out ./bin/petstore-mcpThe generated file embeds the dereferenced spec as base64 JSON and execs dynamic-openapi-mcp with --source <tmpfile> at runtime. Any additional arguments are forwarded to the runner, so it drops straight into .mcp.json:
{
"mcpServers": {
"petstore": {
"command": "/absolute/path/to/bin/petstore-mcp"
}
}
}Bundled binaries also expose:
Subcommand | Purpose |
| Decode and print the embedded spec |
| Print the md5 of the embedded spec |
| Override the embedded spec at runtime |
| Re-fetch the original spec and rewrite the binary in-place |
| Symlink (or |
| Remove a previous |
Run dynamic-openapi-mcp bundle --help for the full list of options.
Filtering operations
Not every endpoint needs to reach the AI. Two ways to cut the surface:
Flags (and programmatic filters)
# only expose the `pets` and `store` tags
dynamic-openapi-mcp -s ./spec.yaml --include-tag pets --include-tag store
# hide admin endpoints and one noisy op
dynamic-openapi-mcp -s ./spec.yaml --exclude-tag admin --exclude-operation debugDump
# allowlist specific operations — tags are ignored for these
dynamic-openapi-mcp -s ./spec.yaml --include-operation listPets,getPetById
# mix-and-match: everything under `pets`, minus one write op
dynamic-openapi-mcp -s ./spec.yaml --include-tag pets --exclude-operation deletePetProgrammatic equivalent:
const mcp = await createOpenApiMcp({
source: './spec.yaml',
filters: {
tags: { include: ['pets'], exclude: ['admin'] },
operations: { include: ['healthCheck'], exclude: ['debugDump'] },
},
})Precedence (first match wins): x-hidden → operations.exclude → operations.include → tags.exclude → includes as allowlist. operations.include escapes a matching tags.exclude, but operations.exclude wins over everything except x-hidden.
x-hidden vendor extension
Let the spec author hide an endpoint from every consumer of this tool — no flags needed:
paths:
/admin/reset:
post:
operationId: adminReset
x-hidden: true # always removed, regardless of filter flagsGood for internal-only endpoints that ship in the public spec but shouldn't be called from AI agents / bundled CLIs / skills.
x-mcp-hidden — MCP-only opt-out
Sometimes you want the operation visible in the CLI or skill consumers, but not exposed as an MCP tool — typically risky operations where you trust a human at the terminal but not an autonomous agent:
paths:
/admin/wipe:
delete:
operationId: wipeEverything
x-mcp-hidden: true # invisible to MCP; still callable from the CLIDistinct from x-hidden, which removes the operation everywhere.
MCP_MAX_TOOLS — tool budget
Specs with hundreds of operations poison the agent context with tool listings. Cap it:
MCP_MAX_TOOLS=50 dynamic-openapi-mcp -s ./huge-spec.yamlWhen the spec exceeds the budget, operations are ranked (non-deprecated + tagged first, then alphabetical) and the surplus is registered as a single list_available_operations tool the agent can call to discover what was trimmed.
Tool safety annotations
Every registered tool gets MCP ToolAnnotations derived from the HTTP method, so hosts (Claude Desktop, Cursor) can prompt for confirmation on the right operations:
Method |
|
|
|
|
| ✓ | ✗ | ✓ | ✓ |
| ✗ | ✗ | ✗ | ✓ |
| ✗ | ✗ | ✓ | ✓ |
| ✗ | ✗ | ✗ | ✓ |
| ✗ | ✓ | ✓ | ✓ |
Override at the spec level with vendor extensions:
paths:
/search:
post:
operationId: searchThings
x-side-effect: read-only # POST that only reads — read-only annotations
/admin/wipe:
get:
operationId: wipeEverything
x-destructive: true # GET that actually destroys — destructive annotationResolution: x-side-effect > x-destructive > HTTP method default.
Tool descriptions
Tool descriptions are synthesised deterministically from the operation's summary/description plus parameter signature plus response shape — much richer than truncate(summary):
Fetch pet by id
Parameters:
- petId(path, integer<int64>, required)
- status(query, enum: "available"|"pending"|"sold", optional)
Returns: 200 — array<Pet> — OKCurated descriptions win via x-description-override:
paths:
/search:
post:
operationId: searchThings
x-description-override: |
Search returns up to 25 pets matching the query, ordered by relevance.
Call get-pet-by-id afterwards for full details.How the Mapping Works
Operations → Tools
Each operation in the spec becomes one MCP tool:
OpenAPI | MCP Tool |
| Tool name: |
| Tool name: |
| Tool description (truncated to 200 chars) |
Path + query + header params | Top-level input properties |
Request body | Input property under |
Request bodies preserve the original media type when possible:
application/jsonis sent as JSON.application/x-www-form-urlencodedis serialized asURLSearchParams.multipart/form-datais serialized asFormData.application/octet-streamand other binary bodies support{ dataBase64, filename?, contentType? }.
Response handling follows the same idea:
JSON is pretty-printed.
Images are returned as MCP image content.
Other binary payloads are returned as binary metadata plus base64 when small enough to inline.
Schemas → Resources
OpenAPI | MCP Resource URI |
Full dereferenced spec |
|
|
|
|
|
Prompts
Prompt | Args | What it returns |
| — | Overview with title, version, all endpoints, auth schemes, schemas |
|
| Full details: parameters, request body schema, responses, security |
License
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/forattini-dev/dynamic-openapi-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server