Skip to main content
Glama
README.md
<!-- BEGIN:AVATAR -->
To be populated by generator-node typescript-cli-partials
<!-- END:AVATAR -->

<!-- BEGIN:BADGES -->
To be populated by generator-node typescript-cli-partials
<!-- END:BADGES -->

# OAG MCP

An open-source CLI that turns OpenAPI specifications and OpenAPI Generator SDKs into code-mode MCP servers, exposing searchable SDK capabilities and sandboxed SDK execution through two tools: `search` and `execute`.

```bash
oag-mcp serve --spec ./openapi.yaml --sdk ./generated-sdk --transport stdio
```

## Why oag-mcp

Most OpenAPI-to-MCP bridges expose one MCP tool per endpoint, or a generic
HTTP request tool. oag-mcp takes a different approach — **SDK-backed code
mode**:

- **OpenAPI supplies the contract and documentation**: descriptions, tags,
  parameters, schemas, examples and security requirements.
- **The generated typescript-fetch SDK supplies the callable interface**:
  the exact classes, methods, signatures and model types.
- **`search`** discovers relevant SDK methods with their signatures,
  referenced types, auth requirements and a ready-to-run example.
- **`execute`** runs agent-written TypeScript that composes real SDK calls —
  loops, filtering, aggregation, `Promise.all` — inside an isolated Deno
  sandbox, and returns only the compact result.
- **The SDK stays an ordinary package** that your applications and CLIs keep
  using directly; oag-mcp only reads it.
- **MCP sees two tools**, not one per endpoint, so large APIs don't flood the
  agent's context.

```text
                    oag-mcp serve (one process)
 openapi.yaml ─┐   ┌──────────────── start-up, once ────────────────┐
               ├──▶│ 1 inputs  2 compatibility  3 bundle + .d.ts     │
 SDK dir/.tgz ─┘   │ 4 method index  5 bridge + Deno executor        │
  (read-only)      │ 6 register search/execute  7 start transport    │
                   └───────────────────────┬─────────────────────────┘
                                           ▼  (stdio or Streamable HTTP)
                    search ─▶ in-memory index
                    execute ─▶ type check ─▶ Deno sandbox (real SDK) ──┐
                               host broker: authz, network, credentials ◀┘ ─▶ your API
```

## Installation

```bash
npm install -g oag-mcp
```

| Requirement | Needed for | Notes |
| --- | --- | --- |
| Node.js 22+ | everything | |
| Deno 2.x | `execute`, and checking the SDK bundle at start-up | Installed automatically through the `deno` npm dependency; or set `OAG_MCP_DENO_PATH` / put `deno` on `PATH`. |
| A typescript-fetch SDK | everything | Generated with [OpenAPI Generator](https://openapi-generator.tech)'s `typescript-fetch` generator, version 7.26.0. |

oag-mcp serves the SDK you give it; generating that SDK is done with
OpenAPI Generator itself, so the machine running the server needs neither
Docker nor OpenAPI Generator. No account, licence, cloud service or
telemetry is involved; while running, the server only connects to your API and
to any OAuth endpoints you configure.

## Quick start

### 1. Generate a typescript-fetch SDK

Generate the SDK from your OpenAPI document with OpenAPI Generator's official
Docker image:

```bash
docker run --rm -v "$PWD":/local openapitools/openapi-generator-cli:v7.26.0 \
  generate \
  --input-spec /local/openapi.yaml \
  --generator-name typescript-fetch \
  --output /local/generated-sdk
```

The SDK can be used as that directory or packed into an npm archive. To pack
it, generate it with `--additional-properties npmName=<name>,npmVersion=<version>`
(so it has a `package.json`), then in the SDK directory run:

```bash
npm install && npm pack
```

`npm pack` runs the SDK's own build script, which needs its TypeScript dev
dependency, hence the `npm install`; the archive contains both the sources
and the compiled JavaScript.

The `gen-sdk` target in [`Makefile-extras`](Makefile-extras) is an example of
both steps in a Makefile. It writes the SDK to `stage/generated-sdk/` and the
archive to `stage/<SDK_NAME>-<SDK_VERSION>.tgz`:

```bash
make -f Makefile-extras gen-sdk SPEC=path/to/openapi.yaml \
  SDK_NAME=petstore-sdk SDK_VERSION=1.0.0
```

### 2. Serve it

```bash
# stdio, for MCP clients that launch the server themselves
oag-mcp serve --spec ./openapi.yaml --sdk ./generated-sdk --transport stdio

# Streamable HTTP
oag-mcp serve --spec ./openapi.yaml --sdk ./generated-sdk \
  --transport streamable-http --port 3000

# from a packaged SDK
oag-mcp serve --spec ./openapi.yaml --sdk ./petstore-sdk-1.0.0.tgz --transport stdio
```

On start-up oag-mcp checks that the SDK is compatible, prepares it for the
sandbox and indexes its methods — typically in one to three seconds — and
only then accepts MCP requests. If anything cannot be prepared, it exits with
an error code and a hint explaining what to fix, rather than starting a
partially working server.

Example MCP client entry (stdio):

```json
{
  "mcpServers": {
    "petstore": {
      "command": "oag-mcp",
      "args": [
        "serve", "--spec", "/path/to/openapi.yaml", "--sdk", "/path/to/generated-sdk",
        "--transport", "stdio", "--config", "/path/to/oag-mcp.config.yaml"
      ],
      "env": { "PETSTORE_API_KEY": "…" }
    }
  }
}
```

### 3. Configure credentials and access (optional)

```yaml
# oag-mcp.config.yaml — contains references to secrets, never secret values
upstream:
  baseUrl: https://api.example.com/v1   # default: the spec's first absolute server URL
upstreamAuth:
  schemes:
    apiKeyHeader:            # security scheme name from the OpenAPI document
      apiKey: { env: PETSTORE_API_KEY }
```

```bash
oag-mcp serve --spec ./openapi.yaml --sdk ./generated-sdk --config ./oag-mcp.config.yaml
```

Secrets are resolved at start-up from environment variables or a `0600`
secrets file. They are never accepted as CLI arguments and never written to
the method index, the prepared SDK bundle, logs or anything agent code can
read. See [authentication](docs/authentication.md) for MCP access control
(`mcpAuth`), upstream credentials (`upstreamAuth`) and per-operation
`authorization`, and the [configuration reference](docs/configuration.md)
for every option.

## How serving works

- **Inputs are read once per process.** The method index, SDK bindings and
  sandbox bundle are built at start-up and stay fixed while the server runs.
  After regenerating the SDK or editing the spec, restart oag-mcp.
- **Your SDK is only read.** oag-mcp never writes to the SDK directory, never
  installs packages and never runs package or build scripts. If the SDK
  declares runtime dependencies, install them in it yourself
  (`npm install --omit=dev --ignore-scripts`); otherwise start-up stops with
  `SDK_DEPENDENCIES_MISSING` and tells you which packages are missing.
- **Source and compiled SDKs both work.** Sources (`src/*.ts`) are compiled
  by oag-mcp; compiled packages (`dist/*.js` with `.d.ts`) are used as they
  are. See [SDK compatibility](docs/sdk-compatibility.md) for supported
  generator versions, options and layouts.
- **Temporary files clean themselves up.** Prepared output (declarations, the
  sandbox bundle, an extracted archive) lives in a private `oag-mcp-ws-*`
  directory under the OS temporary directory (`TMPDIR`). It is removed when
  the server stops or fails to start, and anything left by a killed process
  is removed on the next start. There is no persistent cache to manage.

## The two tools

### `search`

```jsonc
// browse when the vocabulary is unknown
{ }
// ranked full-text search
{ "query": "vet visits of a pet" }
// full detail for one method
{ "id": "PetsApi.listPetVisits" }
```

A detail result (abridged):

```json
{
  "method": {
    "id": "PetsApi.listPetVisits",
    "sdkPath": "sdk.PetsApi.listPetVisits",
    "operationId": "list-pet-visits",
    "httpMethod": "GET",
    "path": "/pets/{petId}/visits",
    "signature": "listPetVisits(requestParameters: ListPetVisitsRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<Array<Visit>>",
    "requiredArguments": ["petId"],
    "inputType": "interface ListPetVisitsRequest {\n  petId: number; // Identifier of the pet\n  since?: Date; // Only visits on or after this date\n}",
    "referencedTypes": ["interface Visit {\n  id: number;\n  date: Date;\n  reason: string;\n  costCents?: number;\n}"],
    "auth": "apiKeyHeader(apiKey-header) OR bearerAuth(http-bearer)",
    "example": "const result = await sdk.PetsApi.listPetVisits({ petId: 1 });\nreturn result;"
  }
}
```

The binding is the SDK's own method name (`listPetVisits`), not the
operationId (`list-pet-visits`): oag-mcp reads the mapping from the generated
code rather than assuming they are equal.

### `execute`

The agent writes the body of an async function; `sdk` is provided:

```ts
const pets = await sdk.PetsApi.listPets({});
const unvaccinated = pets.filter((p) => !p.vaccinated);
const visits = await Promise.all(
  unvaccinated.map((p) => sdk.PetsApi.listPetVisits({ petId: p.id })),
);
return unvaccinated.map((p, i) => ({
  pet: p.name,
  spentCents: visits[i].reduce((s, v) => s + (v.costCents ?? 0), 0),
}));
```

Result:

```json
{
  "ok": true,
  "result": [
    { "pet": "Biscuit", "spentCents": 8100 },
    { "pet": "Nori", "spentCents": 8400 },
    { "pet": "Tofu", "spentCents": 8700 },
    { "pet": "Pip", "spentCents": 9000 }
  ],
  "logs": [],
  "stats": { "durationMs": 131, "sdkCalls": 5 }
}
```

TypeScript is type-checked against the SDK declarations before it runs.
Failures are structured: `syntax_error`, `type_error` (with line/column
diagnostics), `api_error` (HTTP status and body), `sdk_error`,
`upstream_auth_error`, `permission_denied`, `timeout`, `output_limit`,
`resource_limit`, `unsupported_result`.

## Documentation

- [Architecture](docs/architecture.md) — start-up preparation, execution model, design choices
- [SDK compatibility](docs/sdk-compatibility.md) — supported versions, options, package layouts, dependencies
- [Configuration reference](docs/configuration.md)
- [Authentication and authorization](docs/authentication.md) — `mcpAuth`, `upstreamAuth`, permissions
- [Execution sandbox and limits](docs/security.md) — what is and is not isolated
- [Logging](docs/logging.md) — operational vs security audit records
- [Deployment](docs/deployment.md)
- [Testing](docs/testing.md)

## Colophon

<!-- BEGIN:DEVELOPERS_GUIDE -->
To be populated by generator-node typescript-cli-partials
<!-- END:DEVELOPERS_GUIDE -->

<!-- BEGIN:BUILD_REPORTS -->
To be populated by generator-node typescript-cli-partials
<!-- END:BUILD_REPORTS -->