> ## Documentation Index
> Fetch the complete documentation index at: https://bifrost-dev.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenAPI Servers

> Turn any REST API that has an OpenAPI or Swagger document into MCP tools, without writing an MCP server.

## Overview

<Note>
  Generating MCP servers from OpenAPI specs is in **beta**.
</Note>

Most APIs you want an agent to use have an OpenAPI (or Swagger) document but no MCP server. Choose **Generate from an OpenAPI spec** as the server source (`connection_type: "openapi"` in the API and `config.json`) and Bifrost builds the MCP server for you: paste the document, pick the operations, and each one Bifrost can call becomes a tool (operations it cannot express as tool arguments, such as multipart uploads, are listed as unsupported with the reason). Tool calls are sent to the API as ordinary HTTP requests, with the credentials you configure.

The result is a regular MCP client in Bifrost. Everything that applies to other servers applies here too: tool filtering and auto-execute, pricing, virtual-key access, [code mode](/mcp/code-mode), the hosted `/mcp/<slug>` endpoint, and tool sync.

<Info>
  Supported documents: **Swagger 2.0**, **OpenAPI 3.0**, **3.1** and **3.2**, as JSON or YAML, up to 5 MiB. Newer 3.x minor versions are parsed as 3.2 with a warning.
</Info>

## How operations become tools

| Document | Tool |
| - | - |
| `operationId` | Tool name, sanitized to letters, digits and underscores (`list-pets.v2` → `list_pets_v2`). Operations without one are named `<method>_<path>` (`get_pets_by_petId`). Duplicates get a numeric suffix. The gateway exposes the tool as `<server>-<tool>`. |
| `summary` and `description` | Tool description, followed by `METHOD /path`. |
| Path, query, header and cookie parameters | Top-level properties of the tool's input schema, each annotated with its location and encoded in its declared `style` (`form`, `deepObject`, `label`, `matrix`, ...). Header parameters named `Accept`, `Content-Type` or `Authorization` are ignored, as OpenAPI specifies; credentials come from the server's configuration, not from the model. |
| Request body (JSON, including `+json` types, `application/x-www-form-urlencoded`, `text/plain`) | Object bodies with named properties are flattened into top-level properties. An object that allows extra keys (`additionalProperties`), has no named properties, carries a `oneOf`/`anyOf` or may be `null` as a whole, and any array, primitive or `text/plain` body, becomes one `body` property. A parameter that collides with a body property is suffixed `__query`, `__path`, and so on (with `_2`, `_3` if that name is taken too). The body is sent with its declared media type. |
| HTTP method | MCP annotations: `GET`/`HEAD`/`QUERY` are read-only and idempotent, `PUT` idempotent, `DELETE` destructive, `POST`/`PATCH` destructive and non-idempotent. |
| Response | Returned to the model as text (JSON and other textual bodies verbatim, capped at 64 KiB by default). Non-2xx responses are returned as tool errors with the status and body. |

Schemas are normalized to the dialect model providers accept: 3.1 `type: [..., "null"]` becomes `nullable`, `allOf` of objects is merged, `readOnly` properties are dropped from inputs, and keywords such as `xml`, `externalDocs`, `discriminator` and examples are removed.

### What is not exposed

These operations are listed in the preview with a reason instead of becoming tools:

* `multipart/form-data`, `application/octet-stream` and other binary request bodies (file uploads).
* Streaming request bodies (OpenAPI 3.2 `itemSchema`: `text/event-stream`, `application/jsonl`, `application/json-seq`).
* Operations that reference schemas in other files (external `$ref`).
* Deprecated operations, unless `include_deprecated` is set.

Security schemes of type `oauth2`, `openIdConnect` and `mutualTLS` cannot be satisfied from the gateway; operations that only accept them are still exposed and are called with the static headers you configure.

## Base URL

The upstream defaults to the first `servers` entry of the document (server variables take their default value; relative URLs are resolved against the document's URL or 3.2 `$self`). Pick another server or type a custom base URL in the preview. A document with no absolute top-level server needs one, unless every operation (or its path) declares an absolute server of its own; each call then goes to its operation's server.

## Authentication

Two layers reach the API on every call:

1. **Security schemes from the document.** The preview lists every `securitySchemes` entry. For `apiKey` (header, query or cookie) and `http` `bearer` schemes you enter one value; for `http` `basic` a username and password. Values accept `env.VAR` and `vault.path` references. Stored credentials are encrypted only when `encryption_key` (or `BIFROST_ENCRYPTION_KEY`) is configured; without a key they are stored in plain text. The first security requirement of an operation whose schemes all have a credential is applied.
2. **The server's own auth type.** `none`, `headers` (static headers sent with every request) and `per_user_headers` (each caller supplies their own values) work as for any other server. OAuth and token exchange do not apply: there is no MCP server to run that handshake against. For the same reason a caller's per-user headers are saved once every required key has a value, with no verification handshake; the upstream API sees them on the first tool call.

### Per-user credentials for security schemes

With **Auth Scope: Per-user**, each security scheme sent in a header takes a header name instead of a stored value, and each caller supplies that header:

| Scheme | Default header | What the caller supplies |
| - | - | - |
| `apiKey` in a header | the scheme's own header name (for example `api_key`) | the key |
| `http` `bearer` | `Authorization` | `Bearer <token>` |
| `http` `basic` | `Authorization` | `Basic <base64 of user:password>` |

The header is forwarded to the API under that name, unchanged, and is added to the server's `per_user_header_keys` (**Required Headers**) for you. Rename it only if the API also accepts the credential under another header; clear it to keep a stored value for that scheme instead. `apiKey` schemes sent in the query or a cookie cannot come from a header, so they keep a stored value. When an operation requires a header scheme and a query or cookie scheme together, the caller's header counts for its scheme and the stored value of the other is still sent. Over the API, list the header in `per_user_header_keys` and leave that scheme out of `security_credentials`.

## Configuration

<Tabs>
  <Tab title="Web UI">
    1. Navigate to **MCP Gateway** and click **Add MCP Server**.
    2. Under **Server source**, choose **Generate from an OpenAPI spec** (marked **BETA**). The connection type and connection URL fields give way to the document source.
    3. Supply the document: **Paste** it or **Upload** a file and it is parsed as soon as you stop typing, or give its **URL** and click **Fetch spec**. Parse errors show under the editor, with **Parse again** to retry.

    <Frame>
      <img src="https://mintlify.s3.us-west-1.amazonaws.com/bifrost-dev/media/ui-mcp-openapi-source.png" alt="OpenAPI document source in the New MCP Server sheet" />
    </Frame>

    4. Review the preview: the document's title and version, the **Base URL**, credential inputs for its security schemes, and the operations. Untick anything the server should not expose. Unsupported operations are shown disabled with the reason.

    <Frame>
      <img src="https://mintlify.s3.us-west-1.amazonaws.com/bifrost-dev/media/ui-mcp-openapi-preview.png" alt="Parsed operations, base URL and credentials" />
    </Frame>

    5. Set the server's name, auth type, static headers and the other options as for any server, then click **Create**.

    To swap the document later, open the server, and on the **General** tab click **Replace spec**. Each operation is matched by method and path, so a selected operation stays selected, and its auto-execute setting and tool price follow it, even if its tool name changes. Settings of operations the new document drops are removed. The server is rebuilt from the new document when you save.

    <Frame>
      <img src="https://mintlify.s3.us-west-1.amazonaws.com/bifrost-dev/media/ui-mcp-openapi-edit-summary.png" alt="Stored document summary and Replace spec on the edit sheet" />
    </Frame>
  </Tab>

  <Tab title="API">
    Preview first to see the tool names the document yields:

    ```bash theme={null}
    curl -X POST http://localhost:8080/api/mcp/openapi/preview \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer <admin-session-token>" \
      -d '{
        "spec_url": "https://petstore3.swagger.io/api/v3/openapi.json"
      }'
    ```

    The response lists `tools` (name, method, path, input schema), `unsupported` operations with a reason, `security_schemes`, `servers`, the resolved `base_url`, and echoes the fetched document as `spec` together with `spec_url`, the URL it was finally read from after redirects. Send that URL with the document on create, so relative servers resolve the way the preview showed them. Then create the server, naming the tools to expose:

    ```bash theme={null}
    curl -X POST http://localhost:8080/api/mcp/client \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer <admin-session-token>" \
      -d '{
        "name": "petstore",
        "connection_type": "openapi",
        "auth_type": "none",
        "tools_to_execute": ["getInventory", "getPetById"],
        "openapi_config": {
          "spec_url": "https://petstore3.swagger.io/api/v3/openapi.json",
          "base_url": "https://petstore3.swagger.io/api/v3",
          "security_credentials": {
            "api_key": { "value": "env.PETSTORE_API_KEY" }
          }
        }
      }'
    ```

    The `env.PETSTORE_API_KEY` reference needs an authenticated admin session: when dashboard authentication is disabled or unconfigured, `env.` and `vault.` references in `security_credentials` are refused with 403 (literal values are accepted). `spec` (inline text) can be sent instead of `spec_url`. A `spec_url` is fetched once and stored with the server. Unknown names in `tools_to_execute` are rejected with 400, as are `auth_type` values other than `none`, `headers` and `per_user_headers`.

    Replace the document on an existing server with `PUT /api/mcp/client/{id}`. The block is merged onto the stored one: every field you leave out keeps its stored value, including `include_deprecated` and `max_response_bytes`, and a redacted credential keeps the stored secret. An empty `base_url` removes the override, an empty `spec_url` removes the stored URL, and a `security_credentials` map replaces the stored set (schemes left out are removed). Renaming the server re-checks `tools_to_execute`, since long tool names are shortened to fit beside the name. Replace `{id}` with the server's ID, and pass the new document as a JSON-escaped string:

    ```bash theme={null}
    curl -X PUT "http://localhost:8080/api/mcp/client/{id}" \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer <admin-session-token>" \
      -d '{
        "openapi_config": { "spec": "<new document text>" },
        "tools_to_execute": ["*"]
      }'
    ```

    `GET /api/mcp/client/{id}/openapi-spec` returns the stored document; `GET /api/mcp/clients` leaves it out and reports `spec_title`, `openapi_version`, `spec_size`, `spec_hash` and `operation_count` instead.

    <Note>
      When dashboard authentication is disabled or unconfigured, `spec_url` and `base_url` must resolve to public addresses, the same rule that applies to an HTTP server's `connection_string`.
    </Note>
  </Tab>

  <Tab title="config.json">
    ```json theme={null}
    {
      "mcp": {
        "client_configs": [
          {
            "name": "petstore",
            "connection_type": "openapi",
            "auth_type": "headers",
            "headers": { "X-Tenant": "acme" },
            "tools_to_execute": ["getInventory", "getPetById"],
            "openapi_config": {
              "spec_file": "specs/petstore.yaml",
              "base_url": "https://petstore3.swagger.io/api/v3",
              "security_credentials": {
                "api_key": { "value": "env.PETSTORE_API_KEY" }
              },
              "include_deprecated": false
            }
          }
        ]
      }
    }
    ```

    `openapi_config` needs at least one document source, used in this order of precedence: `spec` (inline text), `spec_file` (a path relative to the config directory, read at load), then `spec_url` (fetched at every connect when neither of the others is set). Next to `spec`, `spec_url` records where the document came from and resolves its relative servers, so keep it when the document uses them. Editing the document, base URL or credentials in `config.json` is picked up on the next restart like any other client change.
  </Tab>
</Tabs>

## Go SDK

The parser lives in `framework/openapimcp`; core only knows the hook. Register the factory once and declare clients with `connection_type: "openapi"`:

```go theme={null}
import "github.com/maximhq/bifrost/framework/openapimcp"

cfg.MCPConfig.InProcessServerFactory = openapimcp.Factory(openapimcp.WithConfigDir("/etc/bifrost"))
```

Without a factory, an openapi client fails to connect with an error naming the missing hook.

## Limitations

* One tool per operation; very large documents produce many tools. Above 128 tools the preview warns: restrict `tools_to_execute` to limit what the model sees. Above 1000 tools the document is rejected before any allow-list applies; reduce the document to at most 1000 supported operations.
* File uploads, binary and streaming bodies, OAuth-only APIs and externally referenced schemas are not supported (see above).
* The gateway exposes tools as `<server>-<tool>` within 64 characters, so long `operationId`s are shortened. A server name that leaves fewer than 16 characters for the tool (longer than 47 characters) is rejected for OpenAPI servers.
* A redirect to another host, or from `https` to plain `http`, is not followed when the call carries credentials, custom headers or query parameters; the model receives the redirect response instead, so credentials never reach a host the document did not name or travel unencrypted.
* Tool names depend on the document's `operationId`s. With a partial selection, renamed and newly added tools stay unselected until you tick them. If all previous tools were selected, every supported tool in the replacement document is selected, renamed and newly added ones included.

## Next Steps

* [Filtering](/mcp/filtering) to scope which tools each caller sees.
* [Tool Execution](/mcp/tool-execution) for how calls are run and timed out.
* [Authentication](/mcp/auth/overview) for static and per-user headers.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.