Skip to content

From OpenAPI

Take any OpenAPI 3.x spec and turn it into a Bridgent MCP server in one call.

Quick start

ts
import { createStdioServer } from '@bridgent/core'
import { fromOpenApi } from '@bridgent/source-openapi'

await createStdioServer({
  name: 'github-readonly',
  version: '0.0.1',
  tools: await fromOpenApi({
    spec: 'https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json',
    namespace: 'gh_',
    auth: { type: 'bearer', token: process.env.GITHUB_TOKEN! },
    pathFilter: /^\/repos\/\{owner\}\/\{repo\}\/(issues|pulls|releases)/,
  }),
})

Spec sources

spec accepts:

  • URL (string starting with http:// or https://): 'https://api.example.com/openapi.json'
  • Local file path (string): './openapi.yaml'
  • Inline object: a parsed OpenAPI document (Record<string, unknown>)
  • Raw string: YAML or JSON text

The full TypeScript type is string | Record<string, unknown>.

OpenAPI 3.0 specs are auto-upgraded to 3.1 by @scalar/openapi-parser before tool generation.

Read-only by default

Bridgent only exposes safe HTTP methods (GET, HEAD) unless you opt in:

ts
await fromOpenApi({
  spec: '…',
  allow: { mutating: true }, // enables POST/PUT/PATCH/DELETE
})

For finer control, prefer an explicit allowlist by operationId:

ts
await fromOpenApi({
  spec: '…',
  allowOperations: ['createIssue', 'addComment'],
})

Filter pipeline

Each operation runs through this gate, in order. The pipeline lives in packages/source-openapi/src/filter.ts.

StepCheckDefault
1Method is in allow.methods['GET','HEAD']
2pathFilter(path) passes(no filter)
3op['x-bridgent-allow']falseenforced
4Not in denyOperationsempty
5If allowOperations non-empty, must be in itempty

Use respectExtensions: false to ignore x-bridgent-allow on the spec.

Auth

Bearer auth accepts a static token or a thunk for dynamic refresh:

ts
await fromOpenApi({
  spec: '…',
  auth: { type: 'bearer', token: () => fetchFreshToken() },
})

API-key auth supports headers, query parameters, and cookies:

ts
await fromOpenApi({
  spec: '…',
  auth: {
    type: 'apiKey',
    in: 'header',
    name: 'X-API-Key',
    value: () => process.env.API_KEY!,
  },
})

OAuth2 / PKCE remains a later feature.

Errors

4xx / 5xx responses are not thrown — they come back as a structured payload your LLM can act on:

json
{
  "ok": false,
  "status": 401,
  "statusText": "Unauthorized",
  "body": { "message": "Bad credentials" }
}

This keeps the MCP server alive across upstream failures and gives the model enough info to course-correct.

Tool name strategy

  • Prefer operationId (slugified, capped at 64 chars to play nicely with all hosts)
  • Fallback: ${method}_${pathWithoutBraces} (e.g. GET /users/{id}get_users_id)
  • A namespace prefix (e.g. gh_) is recommended when stitching multiple sources together
  • Duplicate names trigger a fail-fast error with a hint to set namespace

Released under the MIT License.