Back to Browse

Openapi Swagger MCP Server

Developer ToolsLow Risk10.0MCP RegistryLocal
Free

Server data from the Official MCP Registry

Discover Swagger/OpenAPI operations and call configured dev/UAT APIs through 11 fixed tools.

About

Discover Swagger/OpenAPI operations and call configured dev/UAT APIs through 11 fixed tools.

Security Report

10.0
Low Risk10.0Low Risk

Valid MCP server (2 strong, 2 medium validity signals). No known CVEs in dependencies. Package registry verified. Imported from the Official MCP Registry.

5 files analyzed · 1 issue found

Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.

Permissions Required

This plugin requests these system permissions. Most are normal for its category.

file_system

Check that this permission is expected for this type of plugin.

HTTP Network Access

Connects to external APIs or services over the internet.

How to Install

Add this to your MCP configuration file:

{
  "mcpServers": {
    "io-github-nhan7777-openapi-swagger-mcp": {
      "args": [
        "-y",
        "@nhan7777/openapi-swagger-mcp"
      ],
      "command": "npx"
    }
  }
}

Documentation

View on GitHub

From the project's GitHub README.

openapi-swagger-mcp

A local MCP server that connects your AI assistant to the dev/UAT APIs you configure. Ask it to find an endpoint, explain its inputs, send a request and inspect the response using your Swagger 2.0 or OpenAPI 3.0/3.1 spec.

For example: “Find the order details endpoint and explain the required parameters before calling it.”

You configure the API addresses and authentication in a YAML or JSON file. Your MCP client starts the server, and your assistant uses its eleven fixed tools regardless of how many endpoints you have.

New here? Follow Quick start, then try Everyday usage. You do not need to write tool calls yourself.

Already connected? Jump to Choose your next step, Troubleshooting or Tool reference.

Before you start

You need:

  • Node.js 22.19 or newer and npm.
  • An API you can reach, and its Swagger/OpenAPI spec (a URL or a file).
  • An MCP client that can launch a local stdio server.

Use this only against dev/UAT services. The tools send real POST, PUT, PATCH and DELETE requests. There is no rollback and no server-side write gate. A confirmation prompt in your MCP client is the client's feature, not something this server enforces.

Think about where your data goes. Tool arguments and results can be stored in your AI provider's conversation history. Responses are not scrubbed of tokens or personal data. Read secrets from environment variables or files instead of pasting them into the conversation.

Quick start

1. Create a config file

Save this as config.dev.yaml:

services:
  orders:
    baseUrl: http://localhost:8080/api/v1
    spec: http://localhost:8080/v3/api-docs
  • orders is the service name the agent uses in tool calls.
  • baseUrl is where requests are really sent, including any prefix such as /api/v1.
  • spec is the Swagger/OpenAPI URL, or a file path such as ./openapi.json.
  • With no auth block, requests are anonymous.

The URLs above are examples. They only work if your own API is running there, so replace them with your dev/UAT addresses.

Want to inspect what the agent sends? Follow the Inspector setup and usage guide to enable the local browser UI and open the correct MCP session.

Before continuing: replace both URLs and save the file. Note its absolute path; you will use that same path in the next two steps.

Shortcut: init writes a minimal config for you and prints the matching client entry. It validates the config before creating the file, never overwrites an existing file and never edits your client's files:

npx -y @nhan7777/openapi-swagger-mcp@0.1.4 init --service orders --base-url http://localhost:8080/api/v1 --spec http://localhost:8080/v3/api-docs --out config.dev.yaml --client claude-code

Add --bearer-env ORDERS_TOKEN to read a bearer token from an environment variable (no flag accepts the token itself). A relative --spec file path is taken from the directory where you run the command. --client is one of generic, claude-desktop, claude-code, cursor, vscode, windsurf, cline, codex, omp, kiro; the snippet it prints uses the config's absolute path.

2. Check the config

npx -y @nhan7777/openapi-swagger-mcp@0.1.4 --config /absolute/path/config.dev.yaml --check

ok: true means the static checks passed. It does not mean the remote spec was fetched or any endpoint was verified: --check makes no network calls, does not log in, and does not create the artifact directory. See Troubleshooting for the report format and exit codes.

Expected result: the command exits with code 0 and the report contains "ok": true. If it does not, fix the reported field before connecting your client.

--check stays offline on purpose. When you also want to know whether the spec can be loaded, run the separate network check, which you must ask for explicitly:

npx -y @nhan7777/openapi-swagger-mcp@0.1.4 --config /absolute/path/config.dev.yaml --check-specs

--check-specs loads each service's spec (and the external $refs the loader is allowed to fetch) through the same network policy as the server, then prints one JSON report. It never logs in, never calls a business endpoint and never creates the artifact directory. Static spec credentials, if you configured spec.auth, are sent to the spec's own origin, as at server start.

3. Add the server to your MCP client

For clients that use an mcpServers configuration, add this to the client's MCP config file (not to the YAML file above):

{
  "mcpServers": {
    "api-dev": {
      "command": "npx",
      "args": [
        "-y",
        "@nhan7777/openapi-swagger-mcp@0.1.4",
        "--config",
        "/absolute/path/config.dev.yaml"
      ]
    }
  }
}

The file name and location depend on your client. If your client has a form for command, arguments and environment variables, enter the same values there. Not every client uses the mcpServers format, so adapt the snippet if yours differs. Use absolute paths so the server does not depend on the directory the client starts in.

This server uses stdio: the client starts the process and talks to it over stdin/stdout. There is no web UI and no HTTP port. stdout is reserved for MCP traffic and logs go to stderr. Do not add --check to the client entry, because that mode exits after checking.

Step-by-step setup, including the exact config file, root key and restart or reconnect steps for each client, is in the MCP client setup guide:

ClientConfig formatGuide
Claude DesktopJSON, mcpServersClaude Desktop
Claude CodeCLI claude mcp add, or JSON mcpServersClaude Code
CursorJSON, mcpServersCursor
VS Code / GitHub CopilotJSON, servers or mcpServers depending on the fileVS Code and GitHub Copilot
WindsurfJSON, mcpServersWindsurf
ClineJSON, mcpServersCline
Codex CLITOML, mcp_serversCodex CLI
OMPJSON, mcpServersOMP (Oh My Pi)
KiroJSON, mcpServersKiro

4. Connect and check API availability

Save the client configuration and restart or reconnect the MCP server in your client. Ask:

List the configured APIs and report whether the orders spec loaded. If it failed, explain the error without making any business API requests.

Expected result: the assistant finds orders, its spec revision and its operation count. Unlike --check, this step loads the spec. If the service is unavailable, follow Troubleshooting.

5. Inspect your first endpoint

Ask:

Find the operation that returns order details in the orders service. Read its contract and explain the required inputs. Do not send the request yet.

Expected result: an operation such as GET /orders/{id} and the inputs declared by your own spec. The example path is not guaranteed to exist in your API.

You are now ready to use the server. If the endpoint requires a token, configure authentication before calling it.

Choose your next step

Read only the section that matches your task:

I want to…Start here
Ask the assistant to inspect or call an APIEveryday usage
Add services or dev/UAT addressesConfigure your APIs
Use an existing token, password, API key or cookieStatic credentials
Keep a token in a local JSON fileSecret sources
Share credentials across services or choose an accountMultiple accounts and shared profiles
Log in or refresh an expired tokenLogin and refresh
Work through OTP or import a sessionOTP, SSO and interactive sessions
Read a large response or save a returned fileLarge responses and files
Fix a connection, spec or auth errorTroubleshooting
Configure protected specs, TLS or limitsAdvanced configuration and limitations
Copy configuration for a supported scenarioConfiguration cookbook

Everyday usage

Use normal language in your MCP client. Replace service names, IDs and paths in these prompts with your own. The assistant handles tool arguments; inspect Request and response reference when you need exact syntax.

Find an endpoint without sending a request

In orders, find the endpoint for order details. Read its contract and explain required parameters, authentication and expected responses. Do not call it.

Send a request and understand the result

After checking the operation and providing a real test ID:

Call the order details GET operation in orders for ID demo-123. Report the HTTP status, relevant response fields and any contract diagnostics. Do not retry automatically.

An HTTP response and a contract diagnostic answer different questions: the status tells you what the API returned; diagnostics tell you how the request or response compares with the spec. A successful status does not prove the response matches the contract.

Preview a request before sending it

Preview the create-order operation in orders with the test data I provide. Show the target service, URL and payload and tell me if anything blocks it. Do not send it.

preview_operation takes the same arguments as invoke_operation (without capture and followRedirects) and runs the same request preparation, but never sends anything: no business request, no login or refresh, no spec fetch. It returns sent: false, ready, the target, the method, URL, headers, cookies, query pairs and body, the request diagnostics and the auth binding in use. Credentials the server would inject are shown by binding name only, never by value. Values you or the assistant typed are shown as typed, and invalid data stays invalid.

If something is missing (a credential, a loaded spec, an allowed destination) ready is false, blockers say why, and no request is shown. A preview is not an approval and is not a guarantee: invoke_operation prepares the request again and can differ if the input, config, spec revision or auth state changed. Only the first request is previewed; redirects, a 401 refresh and cookies set by a response happen only on invoke. A multipart boundary changes on every build, and headers such as host and content-length are added by the HTTP library, so neither is part of the preview.

Whether to send is still your decision and your client's confirmation prompt. This server does not enforce an approval step. POST, PUT, PATCH and DELETE requests can change real data.

Choose a service or account explicitly

Use orders-uat for this task. Inspect its order details contract first. Do not call orders-dev.

If you configured multiple profiles:

Use the qa profile allowed by orders. Check its auth status without showing credentials, then explain whether it is ready for a request.

Inspect more of a response

Read the returned artifact at /data/items. If it is paginated, continue using nextOffset. Report whether the response body is complete.

For files, configure export roots first and give the assistant a new destination path. Export never overwrites an existing file.

Configure your APIs

A fuller example with two services:

# yaml-language-server: $schema=/absolute/path/to/config.schema.json
version: 1                 # optional
services:
  orders:
    baseUrl: https://orders.dev.example.test/api/v1
    spec: https://orders.dev.example.test/v3/api-docs
    auth:
      bearer: { token: { fromEnv: ORDERS_TOKEN } }

  catalog:
    baseUrl: http://localhost:8081
    spec: ./specs/catalog.yaml

YAML (.yaml, .yml) and JSON are both supported. For JSON, add "$schema": "/absolute/path/to/config.schema.json" so your editor can suggest keys. The configuration cookbook starts with an anonymous service and offers opt-in examples by scenario. Copy only the blocks you need; the JSON Schema is the field reference.

Key rules:

  • Relative paths in the config resolve from the directory that contains the config file, not from where you run the command.
  • The URL comes from baseUrl. The operation GET /orders/{id} with baseUrl: https://host/api/v1 calls https://host/api/v1/orders/{id}. The server ignores servers, host and basePath in the spec. baseUrl must not contain a username, password, query or fragment.
  • Environments are just services. The server has no environment concept, no --env flag and no switching tool. Give each environment's service a unique id (for example orders-dev and orders-uat) and tell the agent which one to use. Services that share one identity provider can share a profile, so they share one token pair and one refresh. If environments must stay isolated, use separate config files and MCP entries; each process then has its own credentials, cookies and artifacts.
  • A config change needs a restart of the MCP process. refresh_specs only reloads specs.

Pass a token from the environment

services:
  orders:
    baseUrl: https://orders.dev.example.test/api/v1
    spec: https://orders.dev.example.test/v3/api-docs
    auth:
      bearer: { token: { fromEnv: ORDERS_TOKEN } }

The server reads ORDERS_TOKEN from its own process environment. That is not the same as the environment of the shell where you tested the command: set it in the env field of the client entry, or wherever your client launches the process.

{
  "mcpServers": {
    "api-dev": {
      "command": "npx",
      "args": ["-y", "@nhan7777/openapi-swagger-mcp@0.1.4", "--config", "/absolute/path/config.dev.yaml"],
      "env": { "ORDERS_TOKEN": "replace-with-your-dev-token" }
    }
  }
}

The server does not load .env files. Changing the environment of another process does not change the environment of a running MCP server; restart it with the new values.

Other ways to install

The npx commands above download the pinned version on first use. Alternatives:

# Global install; the command is then `openapi-swagger-mcp`
npm install -g @nhan7777/openapi-swagger-mcp@0.1.4

# From a source checkout (see Development)
npm ci && npm run build
node /absolute/path/to/openapi-swagger-mcp/dist/cli.js --config /absolute/path/config.dev.yaml

Do not install the unscoped npm package openapi-swagger-mcp. It belongs to someone else and is a different project. This package is @nhan7777/openapi-swagger-mcp.

Authentication

Pick the approach that matches your API:

Start with the simplest option your API supports. A ready token needs only static auth; recipes and sessions are optional. The YAML examples in this section are configuration blocks to merge at the stated location, not commands to run. Tool examples are JSON arguments for the assistant, not shell commands.

For authenticate and get_auth_status, provide exactly one target: {"service":"orders"} or {"profile":"qa"}, never both. authenticate also accepts "action":"refresh"; omitting action selects authentication (or reloading static sources when there is no login recipe).

You have...Use
A ready bearer token, basic credentials, API key or cookiesStatic credentials
A login or refresh endpoint to callLogin and refresh
Several accounts, or credentials shared by several servicesMultiple accounts and shared profiles
OTP, SSO, or a token you receive interactivelyOTP, SSO and interactive sessions

Static credentials

Place one of these auth blocks inside a service, alongside baseUrl and spec (for example under services.orders). For a shared identity, put it inside a named profile instead.

Bearer token

auth:
  bearer:
    token: { fromEnv: ORDERS_TOKEN }

The server adds Authorization: Bearer <token>. The source value holds only the token, without the Bearer prefix.

Basic authentication

auth:
  basic:
    username: tester
    password: { fromEnv: API_PASSWORD }

Username and password are read independently and Basic-encoded. Both can also come from a file or fromCredential. Only a Basic literal may be an empty string.

API key in a header, query string or cookie

auth:
  apiKey:
    header: X-API-Key
    value: { fromEnv: API_KEY }

Replace header with query or cookie if your API needs that. Each entry picks one destination. You can also use a list and combine methods:

auth:
  bearer: { token: { fromEnv: ACCESS_TOKEN } }
  apiKey:
    - { header: X-CSRF-Token, value: { fromEnv: CSRF_TOKEN } }
    - { query: tenant, value: demo }
  cookies: true

Bearer, API key and the cookie jar can be combined. Bearer and basic cannot, because both use Authorization. Header names that collide case-insensitively are rejected. Query and cookie names are case-sensitive.

Cookie jar

auth: { cookies: true } enables the auth's own cookie jar. It is not a raw Cookie string. It is normally paired with a login recipe that has capture.cookies: true (see below). A cookie-only auth is not considered logged in while the jar is empty. To import cookies you already have, use a session.

Secret sources

The same syntax works for token, value, username, password and the initial values in credentials:

SyntaxMeaning
{ fromEnv: API_TOKEN }Read an environment variable of the MCP process
{ fromFile: ./secrets.json, pointer: /orders/token }Read a field from a JSON file
{ fromFile: ./token.json }Read the JSON root. The file must contain a JSON value such as "demo-token", not plain text
demo-tokenA literal. Produces a warning based on the field name; the value is never logged
{ fromCredential: accessToken }Use a named value from the current auth, usually captured by a recipe

fromCredential cannot be used in initial credentials, in catalog credentialSources, or in spec-fetch auth. Env and file values for a binding must be strings; objects and numbers are not silently converted. A missing source never silently falls back to anonymous. Secret files are protected from upload and export, but you are responsible for their permissions, and you should not commit them.

Multiple accounts and shared profiles

profiles:
  qa:
    auth: { bearer: { token: { fromEnv: QA_TOKEN } } }
  admin:
    auth: { bearer: { token: { fromEnv: ADMIN_TOKEN } } }

services:
  orders:
    baseUrl: https://orders.dev.example.test
    spec: ./orders.json
    auth:
      profiles: [qa, admin]
      defaultProfile: qa
  reports:
    baseUrl: https://reports.dev.example.test
    spec: ./reports.json
    auth: { profile: qa }
  • A call to orders with no auth choice uses qa. To use admin, pass "auth":{"mode":"profile","profileId":"admin"} to invoke_operation.
  • If a service lists profiles but has no defaultProfile, requests are anonymous by default. The first profile is never picked for you. Auth tools aimed at that service ask you to name a profile.
  • To control a profile directly, call authenticate or get_auth_status with {"profile":"qa"}. Do not pass both service and profile.
  • A profile is usable only on services that reference it. Two services using qa share the same credentials, cookie jar and refresh task. Cookies are still scoped by domain and path.
  • A named profile can have its own credentials, authentication and refresh. For recipes, set request.service explicitly; it may point to a separate identity service. That only lets the recipe call that operation. It does not give the identity service your business credentials.
  • A service that references a profile must not also have its own credentials, authentication or refresh. Put them in the profile.

Login and refresh

Use a recipe when you must call an API to obtain credentials. A username and password are not required: the body can be a grant code, client credentials, or whatever your API's contract needs.

Three parts have different roles:

  • auth: what to send on business requests.
  • credentials: initial named values. They are not sent unless referenced.
  • authentication / refresh: call one operation, then capture the result.

Example: grant code plus refresh token

services:
  accounts:
    baseUrl: https://accounts.dev.example.test
    spec: https://accounts.dev.example.test/v3/api-docs
    auth:
      bearer: { token: { fromCredential: accessToken } }
      apiKey: { header: X-CSRF-Token, value: { fromCredential: csrf } }
      cookies: true
    credentials:
      grantCode: { fromEnv: GRANT_CODE }
    authentication:
      request:
        operation: POST /session/exchange
        body:
          json:
            grantCode: { fromCredential: grantCode }
      capture:
        credentials:
          accessToken: { bodyPointer: /session/accessToken }
          refreshToken: { bodyPointer: /session/refreshToken }
          csrf: { header: X-CSRF-Token }
        cookies: true
    refresh:
      request:
        operation: POST /session/refresh
        body:
          form:
            refresh_token: { fromCredential: refreshToken }
      capture:
        credentials:
          accessToken: { bodyPointer: /access_token }
          refreshToken: { bodyPointer: /refresh_token }

Both operations must exist in the spec. A JSON Pointer such as /session/accessToken reads response.session.accessToken. The refresh token stays in the credential store. In this example it is sent only by the refresh recipe, never on business requests.

How to use it. The tool name comes first, followed by its JSON arguments. These are not shell commands.

  1. authenticate with {"service":"accounts"}.
  2. get_auth_status with {"service":"accounts"} until it stops reporting recovering.
  3. When the state is ready, call the API normally.
  4. To refresh manually: authenticate with {"service":"accounts","action":"refresh"}.

You can also ask the agent: "Authenticate the accounts service with the configured recipe, wait until it finishes, then report the status. Do not show any credentials."

Rules to remember

  • authenticate runs only authentication. Without a recipe it reloads static sources. It never logs in automatically when a business call lacks credentials.
  • action: refresh runs only refresh. Without a refresh recipe it fails locally with AUTH_REFRESH_UNAVAILABLE.
  • A 401 can start a background refresh, but the original request is never replayed automatically. The caller still receives the 401 plus authRecovery. After the refresh finishes, you or the agent decide whether to call again.
  • A 403, an anonymous request, a session, or a request with an Authorization override does not trigger a refresh.
  • While a recipe runs, new business requests fail with AUTH_RECOVERING. Calling the same action again joins the running one; a different action fails with AUTH_BUSY.
  • A failed refresh does not retry in a loop. Request a retry explicitly with authenticate and the right action.
  • Captured credentials, cookies and binding updates are applied together. If a required capture is missing, nothing is applied partially. A credential not named in the capture keeps its old value, so to rotate a refresh token you must capture it again.
  • Header captures in managed recipes keep the header content as-is, including Bearer . A scalar capture accepts a non-empty string or a finite number; numbers become strings.
  • A recipe accepts HTTP 200-299 by default. Set successStatuses: [200, 201] to change that. Recipes do not follow redirects, do not replay, and do not run prerequisite recipes.

Recipe requests. request supports service, operation, path, query, headers, cookies, body and auth.

  • service defaults to the service that owns the recipe. A recipe in a named profile must set it explicitly.
  • request.auth defaults to anonymous and does not inherit the owner's bearer or cookies. To send them, declare the auth methods explicitly. cookies: true uses the current jar.
  • Templates are substituted in payloads: {fromEnv}, {fromFile,pointer?}, {fromCredential}. Wrap a value as {literal: {fromEnv: "text"}} to send that object unchanged.
  • A fromCredential name must be declared in initial credentials or in a capture. Authentication can only use credentials with an initial definition, not its own future captures. Refresh can use what authentication captured.
  • A capture needs non-empty credentials or cookies: true. Capturing cookies requires the owner to have auth.cookies: true.
  • An authentication capture starts a fresh cookie jar. A refresh keeps and updates the existing jar.

A recipe body takes one of these forms. They are alternatives, so do not paste all four into one recipe:

# JSON; you can add contentType next to json
body: { json: { name: demo } }

# application/x-www-form-urlencoded, for example client credentials
body:
  form:
    grant_type: client_credentials
    client_id: { fromEnv: CLIENT_ID }
    client_secret: { fromEnv: CLIENT_SECRET }

# Multipart; files must be under uploadRoots
body:
  multipart:
    fields: { note: demo }
    files: [{ field: document, path: ./fixtures/demo.pdf }]

# Binary
body: { binary: { file: ./fixtures/demo.bin, contentType: application/octet-stream } }

File paths in recipes are fixed configuration. They cannot come from credential substitution.

OTP, SSO and interactive sessions

Use a session when an agent and a person must work through several steps: request an OTP, enter it, receive a token or cookie, then call the API. The server never requests an OTP, polls, opens a browser or solves a CAPTCHA by itself. A session is bound to one service.

Capture from a response

  1. Call the operation that requests the OTP once, on explicit request, with auth: {mode: anonymous}.
  2. The person provides the OTP. Call the verify operation with invoke_operation:
{
  "service": "accounts",
  "operation": "POST /otp/verify",
  "revision": "<revision>",
  "auth": { "mode": "anonymous" },
  "input": { "body": { "json": { "value": { "code": "<OTP-provided-by-the-user>" } } } },
  "capture": {
    "tokens": [
      { "id": "access", "type": "bearer", "from": "body", "pointer": "/token" }
    ],
    "cookies": true
  }
}
  1. Read capture.sessionId when capture.state is captured.
  2. Call business operations with auth: {mode: session, sessionId: ...}.

Enable capture.cookies only if the response really sets cookies. To capture a token from a header, use {"id":"access","type":"bearer","from":"header","header":"Authorization"}. Unlike managed recipes, a session's bearer-header capture strips the Bearer prefix. For an API key, use type: apiKey with in: header|query|cookie and name.

A capture applies only to 200-299 responses by default; set successStatuses to change that. A failed capture never updates a session partially. To update an existing session, add capture.sessionId.

Import credentials you already have

Prefer declaring a catalog in the config:

credentialSources:
  qaToken: { fromEnv: QA_SESSION_TOKEN }

Then call manage_session:

{
  "action": "create",
  "service": "accounts",
  "credentialSourceIds": [{ "sourceId": "qaToken", "type": "bearer" }]
}

Only ids declared in top-level credentialSources can be imported. Inline secrets in a service or profile, and captured refresh tokens, do not appear in the catalog automatically.

If you must import values directly (they may end up in AI conversation history):

{
  "action": "create",
  "service": "accounts",
  "credentials": {
    "components": [{ "id": "access", "type": "bearer", "value": "<token>" }],
    "cookies": [{ "name": "sid", "value": "<cookie-value>" }]
  }
}

Inspect with {"action":"inspect","sessionId":"<id>"}, or omit sessionId to list sessions. Delete with {"action":"delete","sessionId":"<id>"}. Inspect returns metadata only, never secrets.

Sessions are bound to one service, do not refresh themselves, do not change managed profiles, and are never promoted to profiles. They live in memory and disappear when the process ends.

Large responses and files

The server stores responses as artifacts and returns a bounded view, so long files and base64 strings do not flood the conversation.

  • bodyTransformed: true: the view was shortened and is not the full response.
  • omissions: which fields were shortened, why, their JSON Pointer and the artifact id.
  • rawBodyArtifact: the response entity bytes after HTTP content decoding, with metadata and a SHA-256.

Read part of an artifact

Call read_artifact with a pointer:

{ "artifactId": "<id>", "pointer": "/data/status" }

Or page through it:

{ "artifactId": "<id>", "offset": 0, "limit": 4096 }

Use nextOffset from the result to continue. A pointer to a string returns text, and a pointer to an object or array returns JSON. Reading a binary artifact returns metadata only; use export to get the file. Pointers work on the parsed JSON value (number precision is limited); exporting original keeps the exact bytes.

Export a file

The destination must be under files.exportRoots. Call export_artifact:

{
  "artifactId": "<id>",
  "pointer": "/data/imageBase64",
  "destination": "/absolute/path/exports/image.png",
  "representation": "decoded"
}
RepresentationResult
originalThe artifact's original bytes
textThe text of the artifact, or of the field at the pointer
decodedStrict base64 decoding of the string

Export never overwrites an existing file and never follows a symlink out of a root. File names from Content-Disposition are not trusted. By default artifacts exist only for the lifetime of the process and are removed on normal shutdown; exported files stay. See Trace, policies and session lifecycle to keep a session on purpose.

To mark a base64 field explicitly in the config:

encodedFields:
  - service: orders
    pointer: /data/*/thumbnail
    encoding: base64
    mediaType: image/png

* matches one path segment. Add operation to limit a rule to one operation. Schema hints such as format: byte, contentEncoding and data URIs also help confirm an encoding. A string that merely looks like base64 is not asserted to be base64.

If a response exceeds responseBytes, you still get the status and headers, but the body may be incomplete (bodyComplete: false). Total artifact storage is capped by sessionArtifactBytes, and the number of artifacts by maxArtifacts.

Troubleshooting

Start with the config check:

npx -y @nhan7777/openapi-swagger-mcp@0.1.4 --config /absolute/path/config.yaml --check

The report contains ok, services, checks, remoteSpecsChecked: false and operationsChecked: false. Each check has a field, a state (ok, pending or error) and possibly a code.

  • pending means a credential will be captured at runtime. It is not a static error.
  • It checks env and file sources (even when a recipe exists), local spec and CA files, and upload/export roots.
  • It does not fetch remote specs, parse spec content, check operations remotely, run recipes or create the artifact directory.
  • If the config cannot be parsed or fails the schema, stderr has a sanitized error and stdout is empty. If it parses, stdout has one JSON report with no secret values.
Exit codeMeaning
0Static checks passed
1A config, source, file or root problem
2CLI usage error, such as a missing --config or an unknown flag

The separate --check-specs network check prints { ok, services } with each service's state (ready or unavailable), spec revision, visible operationCount and, when it failed, a bounded error. It exits 0 when every service is ready, 1 when one is unavailable, and 2 when combined with --check.

Common problems

Symptom / codeWhat to do
The MCP client cannot connectCheck Node is 22.19 or newer, that the CLI and config paths are absolute, and that the entry does not contain --check. If you run from source, check it is built
CONFIG_INVALIDRun --check and read the failing field. Use the JSON Schema for suggestions, and do not mix in old syntax (see Limitations)
SOURCE_UNAVAILABLE / CREDENTIAL_INVALIDCheck the MCP process's environment, the JSON file, the pointer, and that the value is a string. Do not paste secrets into logs
A service shows unavailableRead the error in list_apis. Check the spec, its refs, the CA file and file permissions, fix them, then run refresh_specs. Minor spec problems only produce warnings and do not make a service unavailable
AUTH_REQUIREDCheck the static sources or call authenticate. For OTP flows, use a session
AUTH_RECOVERINGWait until get_auth_status stops reporting recovering. Do not mass-retry write operations
AUTH_BUSYWait for the running action to finish before requesting a different one
AUTH_REFRESH_UNAVAILABLENo refresh recipe is configured. Authenticate again explicitly or import a session
AUTH_RECIPE_INPUTA credential or template the recipe needs has no value. Recipes do not run prerequisite steps
AUTH_RECIPE_INVALID / AUTH_RECIPE_FAILEDCheck the recipe's operation, request body, status and service
AUTH_CAPTURE_FAILEDCheck the JSON Pointer, header or Set-Cookie. Capture results are never published partially
AUTH_TARGET_REQUIREDA service with several profiles has no default. Pass {profile: ...}
PROFILE_NOT_ALLOWED / SESSION_SERVICE_MISMATCHUse a profile the service lists, or a session created for that service
SPEC_REVISION_MISMATCHGet a fresh revision from list, search or schema, and re-read the contract before calling
UPLOAD_NOT_ALLOWED / export errorsCheck the roots, the real path, protected files, and that the destination does not exist
HTTP 401 after a refresh startedThat is still the original request's response. Wait for the refresh, then decide whether to send a new request

Restart or refresh? Config changes need a restart of the MCP process. refresh_specs only reloads specs. authenticate without a recipe reloads static auth sources, not the whole config.

Request and response reference

The normal flow

StepToolExample arguments
See services and revisionslist_apis{}
Find an operationsearch_operations{"service":"orders","query":"order detail","limit":5}
Read its contractget_operation_schema{"service":"orders","operation":"GET /orders/{id}","revision":"<revision-you-just-received>"}
Send the requestinvoke_operationSee below

A complete invoke_operation call:

{
  "service": "orders",
  "operation": "GET /orders/{id}",
  "revision": "<revision-you-just-received>",
  "input": {
    "path": { "id": "demo-123" },
    "query": { "includeItems": true },
    "headers": { "X-Request-ID": "demo-request" }
  }
}

operation is the key returned by search, usually METHOD /path. It is not the operationId. revision is the spec revision, which stops you calling an operation based on an outdated contract. When the spec changes, call refresh_specs with {"service":"orders"}, then search or read the contract again to get the new revision. A failed refresh keeps the last valid snapshot.

search_operations accepts service, method, tag, includeDeprecated, fields, limit and cursor. See Search for what it matches, how results are ranked and what the extra output fields mean. Pass nextCursor with the same query to get the next page. Changing the query, a spec revision, the search config or the operation filter invalidates an old cursor; the error says which (details.reason).

Request bodies

The snippets below are fragments that go inside input. They are not complete tool calls. A request has at most one body type.

Note the difference from auth recipes: invoke_operation.input.body uses the json.value and form.fields wrappers, while a recipe body uses plain json and form (see Login and refresh).

JSON:

{ "body": { "json": { "value": { "name": "demo", "quantity": 2 } } } }

Form (application/x-www-form-urlencoded):

{ "body": { "form": { "fields": { "name": "demo", "tags": ["a", "b"] } } } }

You can set contentType inside json or form to pick one of the media types the operation declares.

For multipart and binary uploads, first allow directories in the config and create them:

files:
  uploadRoots: [./fixtures]
  exportRoots: [./exports]

Then pass absolute paths in the tool call.

Multipart:

{
  "body": {
    "multipart": {
      "fields": { "description": "demo" },
      "files": [
        { "field": "document", "path": "/absolute/path/fixtures/demo.pdf", "filename": "demo.pdf", "contentType": "application/pdf" }
      ]
    }
  }
}

Binary:

{ "body": { "binary": { "file": "/absolute/path/fixtures/demo.bin", "contentType": "application/octet-stream" } } }

The multipart files array may repeat the same field. Files outside uploadRoots, symlinks that escape a root, and protected files are refused. The media type must match what the operation declares, so read the contract before sending.

Choosing auth, and negative testing

invoke_operation.auth has four modes:

ModeBehavior
{"mode":"default"} or no authUse the service's default auth, or anonymous if it has none
{"mode":"profile","profileId":"qa"}Use a named profile the service allows
{"mode":"session","sessionId":"<id>"}Use an interactive session bound to the same service
{"mode":"anonymous"}Inherit no credentials or cookies. Data you pass explicitly in input is still sent

To test a wrong token, pass your own input.headers.Authorization. It overrides the matching credential for that request only, leaves managed credentials untouched, and does not trigger an automatic refresh.

The server serializes what you give it and does not fix your data. It adds no defaults, does no type coercion, and does not strip unknown fields or null. Omitting the body is different from sending {"json":{"value":null}}, which sends the bytes null. HTTP 4xx/5xx responses are real results and are not turned into "tool failed validation" errors.

Documentation truncated — see the full README on GitHub.

Reviews

No reviews yet

Be the first to review this server!