From OpenAPI
Take any OpenAPI 3.x spec and turn it into a Bridgent MCP server in one call.
Quick start
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://orhttps://):'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:
await fromOpenApi({
spec: '…',
allow: { mutating: true }, // enables POST/PUT/PATCH/DELETE
})For finer control, prefer an explicit allowlist by operationId:
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.
| Step | Check | Default |
|---|---|---|
| 1 | Method is in allow.methods | ['GET','HEAD'] |
| 2 | pathFilter(path) passes | (no filter) |
| 3 | op['x-bridgent-allow'] ≠ false | enforced |
| 4 | Not in denyOperations | empty |
| 5 | If allowOperations non-empty, must be in it | empty |
Use respectExtensions: false to ignore x-bridgent-allow on the spec.
Auth
Bearer auth accepts a static token or a thunk for dynamic refresh:
await fromOpenApi({
spec: '…',
auth: { type: 'bearer', token: () => fetchFreshToken() },
})API-key auth supports headers, query parameters, and cookies:
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:
{
"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
namespaceprefix (e.g.gh_) is recommended when stitching multiple sources together - Duplicate names trigger a fail-fast error with a hint to set
namespace