Skip to main content
Glama
rtzll

Effect OpenAPI MCP

by rtzll

Effect OpenAPI MCP

Expose an API as MCP tools using its OpenAPI specification. Supply the spec, API target and credentials; the adapter generates tools, validates arguments and calls the API. No hand-written tool per endpoint is needed.

Use it as a stdio or local HTTP server, or embed its Effect 4 library. It supports a defined subset of OpenAPI 3.0 / 3.1 / 3.2, in JSON or YAML.

Quickstart · Your API · MCP clients · Library · Configuration · Compatibility · Security

Quickstart

From a source checkout, in the project directory containing package.json, install Vite+, open a new terminal, then run:

vp install --frozen-lockfile
vp run demo

Vite+ selects Node from .node-version, pnpm from package.json#packageManager, and the project toolchain from the lockfile. No separate Node or pnpm setup is needed. The package requires Node 24.18+.

The demo builds the package, starts a local mock MCP server, calls its tools, checks invalid input, then shuts down. It needs no .env, credentials or external API. Only tool/dependency installation needs network access or populated caches.

Related MCP server: openapi-to-mcp

Use your own API

Copy .env.example to .env, then replace its values. For example:

MCP_OPENAPI_SPEC=/absolute/path/to/your/openapi.yaml
MCP_API_BASE_URL=https://api.example.com/v1/
MCP_CREDENTIALS='{"ApiKeyAuth":"replace-with-your-api-key"}'
MCP_METHODS='["get"]'
MCP_TRANSPORT=http

ApiKeyAuth must match a security-scheme name in your spec; use {} for an anonymous API. The target override is optional when the spec declares the right server. The specification can also be an HTTPS URL.

Inspect before serving:

vp run start inspect
vp run start check --strict
vp run start

inspect lists tools and exclusions without calling the API. check requires a nonempty, exportable catalog; --strict also rejects unsupported operations. Intentional method/tag filters are allowed. The HTTP endpoint defaults to http://127.0.0.1:3000/mcp. Restart after changing the spec or configuration.

Without filters, all supported operations are exposed, including writes. The example above limits discovery and execution to GET operations. Use credentials with only the permissions you need. The bundled inventory spec is an example contract; it requires your own matching API for real requests.

Add --json to inspect for machine-readable output. To avoid task-runner output, use vp exec node --env-file=.env src/cli.ts inspect --json.

vp run start --help lists CLI options. vp run start check checks your API spec; vp run check checks this project's code.

Connect an MCP client

Try the offline fixture in Claude Code

After the quickstart, print a ready-to-use configuration:

vp exec node scripts/client-config.ts

Merge the printed mcpServers entry into your Claude Code project's .mcp.json. Open Claude Code there, run /mcp, and approve openapi-fixture when prompted. Ask: Use echo_message to echo “Café + 😀” with count 3. The tool should return {"message":"Café + 😀","count":3}. The generator prints configuration only; it does not edit files or start a server.

Connect your own API over stdio

Build the executable and get the selected Node path:

vp run build
vp exec node -p "process.execPath"

Set MCP_TRANSPORT=stdio in .env. In a client using mcpServers configuration, replace these example paths with your actual absolute paths:

{
  "mcpServers": {
    "my-api": {
      "command": "/absolute/path/to/node",
      "args": [
        "--env-file=/absolute/path/to/openapi-mcp/.env",
        "/absolute/path/to/openapi-mcp/dist/cli.js"
      ]
    }
  }
}

Other clients use the same executable and arguments in their own configuration format. The client owns the process. Use absolute paths for the local spec too, keep stdout protocol-only, and build before connecting. Adapter logs go to stderr. Rebuild after source changes; regenerate fixture configuration if paths move.

Troubleshooting: missing dist means run vp run build; missing tools means check inspect, credentials and filters. For oversized catalogs, narrow MCP_METHODS/MCP_TAGS to fit your client's limits. HTTP clients can instead connect to the running loopback endpoint above.

Use the library

Import from effect-openapi-mcp. compileOpenApi(document, options) compiles an assembled document without I/O; loadOperationCatalog(location, options) loads and resolves a file/URL through Effect. Compose HttpApi, generatedTools and HTTP/stdio server layers when embedding the adapter. Imports never start a server.

These complete examples build and run offline:

Every vp run check type-checks all examples and runs their offline tests against the built library.

To use the library in another application, create an installable tarball:

vp pm pack --pack-destination /tmp

Install the printed .tgz file with your application's package manager, keeping its Effect dependencies aligned with package.json. The library and CLI ship compiled JavaScript. Copy TypeScript examples and their imported helpers into your application's source before running them with Node; Node does not strip TypeScript inside node_modules.

vp run test:package /absolute/path/to/the-printed-archive.tgz --online checks the public types, CLI and packaged examples in an independent fresh consumer.

Configuration

vp run start loads .env if present. Direct Node runs need --env-file explicitly. Method/tag filters are JSON arrays; both must match when set, and empty arrays mean unrestricted. Credential values are raw keys/tokens (username:password for Basic); OAuth token acquisition and refresh happen outside this adapter. API targets and remote specs require HTTPS, except loopback HTTP; redirects are rejected.

Variable

Default

Purpose

MCP_OPENAPI_SPEC

Required

OpenAPI JSON/YAML file or HTTPS URL

MCP_API_BASE_URL

Spec's servers

Optional target override

MCP_CREDENTIALS

{}

JSON object of security-scheme names to secrets

MCP_TRANSPORT

http

http or stdio

MCP_METHODS

[]

JSON array of lowercase HTTP methods to expose

MCP_TAGS

[]

JSON array of operation tags to expose

MCP_SPEC_TIMEOUT_MS

10000

Specification read/download deadline

MCP_REFERENCES_MODE

none

External references: none, local, same-origin

MCP_MAX_REFERENCE_DOCUMENTS

128

Maximum root and referenced documents

MCP_MAX_REFERENCE_DEPTH

64

Maximum dereference chain length

MCP_REQUEST_HEADERS

{}

Fixed API request headers as a JSON object

MCP_TOOL_HEADERS

[]

Optional per-call header names exposed in tool inputs

MCP_PARAMETER_PROFILE

brackets-indexed

Use spec for strict portable deepObject encoding

MCP_TIMEOUT_MS

10000

Upstream deadline, including reading the response body

MCP_MAX_SPEC_BYTES

33554432

Total specification graph bytes

MCP_MAX_RESPONSE_BYTES

8388608

Maximum decoded upstream response bytes

MCP_MAX_REQUEST_BYTES

8388608

Maximum MCP HTTP input and encoded upstream request body bytes

PORT

3000

Loopback HTTP port

External references default off. local confines them to the entry file's canonical directory; same-origin confines them to its HTTPS/loopback origin. They share the graph byte/deadline limits, never inherit API credentials and cannot follow redirects. Relative local paths use the process working directory.

The default brackets-indexed profile serializes declared deepObject query/form fields with indexed brackets, including nested arrays and objects. Other parameter styles follow their declarations. This convention extends beyond OpenAPI's portable flat-object rules; use MCP_PARAMETER_PROFILE=spec when an API requires strict portable encoding. That mode excludes operations with unsupported shapes.

Indexed encoding preserves numeric map keys. Empty objects and arrays emit no fields; use an empty string when the API requires an explicit clearing value.

Stripe's published spec works with the default profile. Use credentials named bearerAuth. Leave MCP_API_BASE_URL unset: Stripe's file operations use files.stripe.com, while most operations use api.stripe.com.

To pin Stripe's API version and optionally target a connected account:

MCP_REQUEST_HEADERS='{"Stripe-Version":"2026-09-30.endive","Stripe-Account":"acct_example"}'
MCP_TOOL_HEADERS='["Idempotency-Key"]'

Omit Stripe-Account when using your own account. Each tool then accepts an optional headers object, for example {"headers":{"Idempotency-Key":"order-123"}}. Use a distinct key for each logical write and reuse it only when repeating that same write. Keep idempotency keys per call rather than setting a fixed default. Per-call values override fixed defaults. Declared header schemas retain their constraints; authentication and transport headers cannot be configured this way. Fixed values are redacted in configuration and never included in tool schemas.

Compatibility reference

Area

Supported scope / important limits

Documents

OpenAPI 3.0/3.1/3.2 JSON or one YAML 1.2 document; Swagger 2, duplicate keys, YAML aliases/custom tags and multiple documents are rejected

Schemas

Native Effect validation for supported constraints, compositions and recursive refs; unsupported constructs produce diagnostics rather than approximate validation

HTTP

GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS and OpenAPI 3.2 QUERY; JSON, supported form-urlencoded, multipart and ordinary UTF-8 text; YAML/PDF/octet-stream responses; no streaming

Authentication

Header/query API keys, bearer, Basic and supplied OAuth/OIDC tokens; missing/unsupported protected credentials omit the operation, never retry anonymously

MCP

Tools over HTTP/stdio; 2026-07-28 and tested legacy revisions; no resources/prompts or retired separate SSE transport

Responses

Full JSON/text content; response schemas are not validated; no automatic pagination or retries

Tool arguments group locations as path, query, headers and body. Valid operationId values become tool names; other names are normalized predictably. Objects are returned directly as structured content; other results use { "result": ... }. Defaults/formats are annotations, not value injection or additional format validation. Run inspect and check --strict to see which operations can be exposed and why others are excluded.

Multipart file fields (for example body.file) take this JSON value:

{
  "dataBase64": "AAH/gA==",
  "filename": "example.bin",
  "mediaType": "application/octet-stream"
}

Use canonical padded base64, a simple ASCII filename and a concrete media type. Paths and URLs are not read. Other body fields keep their declared types. Multipart support covers closed object schemas with top-level binary file fields; unsupported encodings and file constraints produce diagnostics. The request byte limit includes multipart headers and boundaries.

Declared application/pdf and application/octet-stream responses return { "mimeType": "application/pdf", "dataBase64": "JVBERi0…" } through both the library and MCP structured content. Decode dataBase64 to recover the original bytes. MCP_MAX_RESPONSE_BYTES limits the received bytes before base64 expansion; oversized or mismatched response types fail without returning partial data.

application/yaml responses return the original UTF-8 text, preserving comments, line endings and YAML syntax. The library returns a string; MCP returns { "result": "..." }. The same response byte limit applies. YAML responses are not parsed into objects.

Uploads to a presigned URL returned by an earlier API call require a separate workflow. Generated tools use the specification's servers or the configured base URL; they do not accept arbitrary destination URLs at runtime.

In a source checkout, vp run compatibility and vp run benchmark generate local reports from the pinned fixture corpus. The benchmark measures the default configuration; vp run benchmark --include-strict compares both profiles. Compatibility checks always cover both. These checks establish the tested subset, not compatibility with every live API or client. Corpus counts measure compilation and schema export, not execution of every valid input. Stripe has pinned-spec request tests covering query/form encoding, metadata, uploads and PDF downloads; representative query/form requests also run through a local HTTP listener. Live Stripe sandbox verification has not been performed.

Security

Load trusted specifications: they select API targets and can contain expensive schemas. HTTP binds to loopback and checks Host/Origin. Upstream credentials do not authenticate incoming MCP clients or isolate tenants; remote hosting needs separate authentication and authorization. Tool annotations do not enforce approval.

Successful API responses may contain sensitive or untrusted content. API error bodies are withheld, but successful responses are returned unchanged. Cancellation aborts local HTTP work and cannot undo an upstream mutation already performed.

License

Project code is MIT. Third-party test fixtures retain their upstream licenses and notices and are excluded from the npm package. The manifests in fixtures/sources.json and fixtures/json-schema/sources.json record pinned sources, license files and verification hashes. Fixture documents are unchanged; larger files are gzip compressed.

The QUERY/tag examples are by the OpenAPI Initiative, under CC BY 4.0.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Converts any OpenAPI 3.x spec into a live MCP server, making every endpoint a validated tool that AI agents can call without writing glue code.
    6 npm
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Serves any OpenAPI 3.x/Swagger 2.x API as a local MCP server over stdio, converting every operation into a tool that proxies requests to the upstream API with configurable headers and fixed parameters.
    4 npm
    1
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Turns any OpenAPI 3.0/3.1 spec into validated MCP tools, letting hosts call allowed API operations locally with strict schemas, secure credential handling, and read-only-by-default filtering.
    1
    MIT