@bridgent/source-openapi 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/index.d.mts +59 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +415 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 The Mark and Bridgent contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @bridgent/source-openapi
|
|
2
|
+
|
|
3
|
+
> Turn any OpenAPI 3.x spec into a set of MCP tools.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { createStdioServer } from '@bridgent/core'
|
|
9
|
+
import { fromOpenApi } from '@bridgent/source-openapi'
|
|
10
|
+
|
|
11
|
+
await createStdioServer({
|
|
12
|
+
name: 'github-readonly',
|
|
13
|
+
version: '0.0.1',
|
|
14
|
+
tools: await fromOpenApi({
|
|
15
|
+
spec: 'https://raw.githubusercontent.com/github/rest-api-description/main/descriptions/api.github.com/api.github.com.json',
|
|
16
|
+
namespace: 'gh_',
|
|
17
|
+
auth: { type: 'bearer', token: process.env.GITHUB_TOKEN! },
|
|
18
|
+
pathFilter: /^\/repos\/\{owner\}\/\{repo\}\/(issues|pulls|releases)/,
|
|
19
|
+
}),
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Default safety posture
|
|
24
|
+
|
|
25
|
+
By default Bridgent only exposes **read-only** operations (`GET`, `HEAD`).
|
|
26
|
+
To allow writes, opt in:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
await fromOpenApi({
|
|
30
|
+
spec: '…',
|
|
31
|
+
allow: { mutating: true },
|
|
32
|
+
// …or whitelist by operationId
|
|
33
|
+
allowOperations: ['createIssue', 'updateLabel'],
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Filter pipeline
|
|
38
|
+
|
|
39
|
+
Each operation in the spec runs through this gate (top to bottom):
|
|
40
|
+
|
|
41
|
+
1. **Method**: must be in `allow.methods` (default `['GET','HEAD']`)
|
|
42
|
+
2. **`pathFilter`**: regex or `(path) => boolean`
|
|
43
|
+
3. **Vendor extension**: drops if `x-bridgent-allow: false` (and `respectExtensions` is true, which is default)
|
|
44
|
+
4. **`denyOperations`**: drops by `operationId`
|
|
45
|
+
5. **`allowOperations`**: when non-empty, **only** these `operationId`s pass
|
|
46
|
+
|
|
47
|
+
## Auth
|
|
48
|
+
|
|
49
|
+
v0.1 supports **Bearer** only. Pass a static string, or a thunk for dynamic refresh:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
await fromOpenApi({
|
|
53
|
+
spec: '…',
|
|
54
|
+
auth: { type: 'bearer', token: () => fetchFreshToken() },
|
|
55
|
+
})
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Errors
|
|
59
|
+
|
|
60
|
+
4xx / 5xx are **not** thrown — they come back as a structured payload that LLMs can act on:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{ "ok": false, "status": 401, "body": { "message": "Bad credentials" } }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
This avoids killing the MCP server when the upstream API rejects a single call.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { BridgentTool } from "@bridgent/core";
|
|
2
|
+
|
|
3
|
+
//#region src/types.d.ts
|
|
4
|
+
type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';
|
|
5
|
+
interface BearerAuth {
|
|
6
|
+
type: 'bearer';
|
|
7
|
+
token: string | (() => string | Promise<string>);
|
|
8
|
+
}
|
|
9
|
+
interface FromOpenApiOptions {
|
|
10
|
+
/** Spec source: file path, URL, JSON object, or YAML/JSON string. */
|
|
11
|
+
spec: string | Record<string, unknown>;
|
|
12
|
+
/** Override `spec.servers[0].url` (e.g. sandbox vs prod). */
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
/** Authentication. v0.1 supports Bearer only. */
|
|
15
|
+
auth?: BearerAuth;
|
|
16
|
+
/** Tool name namespace prefix. e.g. `gh_` to avoid collisions across sources. */
|
|
17
|
+
namespace?: string;
|
|
18
|
+
/** Method allowlist. Default: GET/HEAD only (read-only). */
|
|
19
|
+
allow?: {
|
|
20
|
+
/** When `true`, also exposes POST/PUT/PATCH/DELETE. */mutating?: boolean; /** Explicit method list. Overrides `mutating` when set. */
|
|
21
|
+
methods?: HttpMethod[];
|
|
22
|
+
};
|
|
23
|
+
/** Operation allowlist by `operationId`. When non-empty, ONLY these are exposed. */
|
|
24
|
+
allowOperations?: string[];
|
|
25
|
+
/** Operation denylist by `operationId`. Applied after `allowOperations`. */
|
|
26
|
+
denyOperations?: string[];
|
|
27
|
+
/** Path filter. Cuts down enormous specs (e.g. GitHub). */
|
|
28
|
+
pathFilter?: RegExp | ((path: string) => boolean);
|
|
29
|
+
/** Whether to honor `x-bridgent-allow: false` on operations. Default: true. */
|
|
30
|
+
respectExtensions?: boolean;
|
|
31
|
+
/** Custom fetch (for testing or proxying). Default: `globalThis.fetch`. */
|
|
32
|
+
fetch?: typeof globalThis.fetch;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
//#region src/from-openapi.d.ts
|
|
36
|
+
/**
|
|
37
|
+
* Turn an OpenAPI 3.x spec into a list of BridgentTools, ready to feed into
|
|
38
|
+
* `createStdioServer({ tools })`.
|
|
39
|
+
*
|
|
40
|
+
* Read-only by default — to expose mutating operations, set `allow.mutating: true`
|
|
41
|
+
* (or use `allowOperations` allowlist).
|
|
42
|
+
*/
|
|
43
|
+
declare function fromOpenApi(options: FromOpenApiOptions): Promise<BridgentTool[]>;
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/parse-openapi.d.ts
|
|
46
|
+
declare class OpenApiParseError extends Error {
|
|
47
|
+
readonly errors: ReadonlyArray<{
|
|
48
|
+
message: string;
|
|
49
|
+
path?: string[];
|
|
50
|
+
}>;
|
|
51
|
+
name: string;
|
|
52
|
+
constructor(message: string, errors: ReadonlyArray<{
|
|
53
|
+
message: string;
|
|
54
|
+
path?: string[];
|
|
55
|
+
}>);
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { type BearerAuth, type FromOpenApiOptions, type HttpMethod, OpenApiParseError, fromOpenApi };
|
|
59
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/from-openapi.ts","../src/parse-openapi.ts"],"mappings":";;;KAAY,UAAA;AAAA,UAKK,UAAA;EACf,IAAA;EACA,KAAA,2BAAgC,OAAO;AAAA;AAAA,UAGxB,kBAAA;EALA;EAOf,IAAA,WAAe,MAAA;;EAGf,OAAA;EATA;EAYA,IAAA,GAAO,UAAA;EAXyB;EAchC,SAAA;EAduC;EAiBvC,KAAA;IAdiC,uDAgB/B,QAAA,YAda;IAgBb,OAAA,GAAU,UAAA;EAAA;EAUC;EANb,eAAA;EAY+B;EAT/B,cAAA;EAvBA;EA0BA,UAAA,GAAa,MAAA,KAAW,IAAA;EAvBxB;EA0BA,iBAAA;EAvBO;EA0BP,KAAA,UAAe,UAAA,CAAW,KAAA;AAAA;;;;AA5C5B;;;;AAAsB;AAKtB;iBCWsB,WAAA,CACpB,OAAA,EAAS,kBAAA,GACR,OAAA,CAAQ,YAAA;;;cC+BE,iBAAA,SAA0B,KAAA;EAAA,SAInB,MAAA,EAAQ,aAAA;IAAgB,OAAA;IAAiB,IAAA;EAAA;EAHlD,IAAA;cAEP,OAAA,UACgB,MAAA,EAAQ,aAAA;IAAgB,OAAA;IAAiB,IAAA;EAAA;AAAA"}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { defineTool } from "@bridgent/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { dereference, load } from "@scalar/openapi-parser";
|
|
4
|
+
import { fetchUrls } from "@scalar/openapi-parser/plugins/fetch-urls";
|
|
5
|
+
import { readFiles } from "@scalar/openapi-parser/plugins/read-files";
|
|
6
|
+
//#region src/types.ts
|
|
7
|
+
const SAFE_METHODS = ["GET", "HEAD"];
|
|
8
|
+
const MUTATING_METHODS = [
|
|
9
|
+
"POST",
|
|
10
|
+
"PUT",
|
|
11
|
+
"PATCH",
|
|
12
|
+
"DELETE"
|
|
13
|
+
];
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/filter.ts
|
|
16
|
+
/**
|
|
17
|
+
* Apply the filtering pipeline:
|
|
18
|
+
* 1. method ∈ allow.methods (default GET/HEAD)
|
|
19
|
+
* 2. pathFilter
|
|
20
|
+
* 3. respectExtensions && op['x-bridgent-allow'] === false → drop
|
|
21
|
+
* 4. denyOperations
|
|
22
|
+
* 5. allowOperations (if non-empty, gate)
|
|
23
|
+
*/
|
|
24
|
+
function shouldExposeOperation(ctx, opts) {
|
|
25
|
+
if (!resolveAllowedMethods(opts).includes(ctx.method)) return false;
|
|
26
|
+
if (opts.pathFilter) {
|
|
27
|
+
if (!(typeof opts.pathFilter === "function" ? opts.pathFilter(ctx.path) : opts.pathFilter.test(ctx.path))) return false;
|
|
28
|
+
}
|
|
29
|
+
if ((opts.respectExtensions ?? true) && ctx.operation["x-bridgent-allow"] === false) return false;
|
|
30
|
+
const opId = ctx.operation.operationId;
|
|
31
|
+
if (opId && opts.denyOperations?.includes(opId)) return false;
|
|
32
|
+
if (opts.allowOperations && opts.allowOperations.length > 0) {
|
|
33
|
+
if (!opId || !opts.allowOperations.includes(opId)) return false;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
function resolveAllowedMethods(opts) {
|
|
38
|
+
if (opts.allow?.methods) return opts.allow.methods;
|
|
39
|
+
if (opts.allow?.mutating) return [...SAFE_METHODS, ...MUTATING_METHODS];
|
|
40
|
+
return [...SAFE_METHODS];
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/http.ts
|
|
44
|
+
/** Build a fetch-ready request from flattened tool args + an OpenAPI operation. */
|
|
45
|
+
function buildRequest(op, args, baseUrl) {
|
|
46
|
+
const pathParams = {};
|
|
47
|
+
const queryParams = new URLSearchParams();
|
|
48
|
+
const headers = { Accept: "application/json" };
|
|
49
|
+
for (const param of op.parameters) {
|
|
50
|
+
const raw = args[param.name];
|
|
51
|
+
if (raw === void 0 || raw === null) {
|
|
52
|
+
if (param.required && param.in === "path") throw new Error(`Missing required path param "${param.name}" for ${op.operationId}`);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const value = String(raw);
|
|
56
|
+
switch (param.in) {
|
|
57
|
+
case "path":
|
|
58
|
+
pathParams[param.name] = value;
|
|
59
|
+
break;
|
|
60
|
+
case "query":
|
|
61
|
+
if (Array.isArray(raw)) for (const v of raw) queryParams.append(param.name, String(v));
|
|
62
|
+
else queryParams.set(param.name, value);
|
|
63
|
+
break;
|
|
64
|
+
case "header":
|
|
65
|
+
headers[param.name] = value;
|
|
66
|
+
break;
|
|
67
|
+
case "cookie": break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
let path = op.path;
|
|
71
|
+
for (const [key, value] of Object.entries(pathParams)) path = path.replace(`{${key}}`, encodeURIComponent(value));
|
|
72
|
+
let body;
|
|
73
|
+
if (op.requestBody && args.body !== void 0) {
|
|
74
|
+
body = JSON.stringify(args.body);
|
|
75
|
+
headers["Content-Type"] = headers["Content-Type"] ?? "application/json";
|
|
76
|
+
}
|
|
77
|
+
const qs = queryParams.toString();
|
|
78
|
+
return {
|
|
79
|
+
url: `${trimTrailingSlash(baseUrl)}${path}${qs ? `?${qs}` : ""}`,
|
|
80
|
+
method: op.method,
|
|
81
|
+
headers,
|
|
82
|
+
body
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** Send a built request and package the response as a structured result. */
|
|
86
|
+
async function invokeRequest(built, opts) {
|
|
87
|
+
const headers = { ...built.headers };
|
|
88
|
+
if (opts.auth?.type === "bearer") {
|
|
89
|
+
const token = typeof opts.auth.token === "function" ? await opts.auth.token() : opts.auth.token;
|
|
90
|
+
if (token) headers.Authorization = `Bearer ${token}`;
|
|
91
|
+
}
|
|
92
|
+
const response = await opts.fetch(built.url, {
|
|
93
|
+
method: built.method,
|
|
94
|
+
headers,
|
|
95
|
+
body: built.body
|
|
96
|
+
});
|
|
97
|
+
const responseHeaders = {};
|
|
98
|
+
response.headers.forEach((value, key) => {
|
|
99
|
+
responseHeaders[key] = value;
|
|
100
|
+
});
|
|
101
|
+
const text = await response.text();
|
|
102
|
+
let parsedBody;
|
|
103
|
+
if ((responseHeaders["content-type"] ?? "").includes("application/json")) try {
|
|
104
|
+
parsedBody = text.length > 0 ? JSON.parse(text) : null;
|
|
105
|
+
} catch {
|
|
106
|
+
parsedBody = text;
|
|
107
|
+
}
|
|
108
|
+
else parsedBody = text.length > 65536 ? `${text.slice(0, 65536)}…[truncated]` : text;
|
|
109
|
+
return {
|
|
110
|
+
ok: response.ok,
|
|
111
|
+
status: response.status,
|
|
112
|
+
statusText: response.statusText,
|
|
113
|
+
body: parsedBody,
|
|
114
|
+
headers: responseHeaders
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function trimTrailingSlash(url) {
|
|
118
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
119
|
+
}
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/jsonschema-to-zod.ts
|
|
122
|
+
/**
|
|
123
|
+
* Convert a JSON Schema (OpenAPI 3.0/3.1 subset) into a Zod schema.
|
|
124
|
+
*
|
|
125
|
+
* Coverage: object/array/string/number/integer/boolean/enum/const/oneOf/anyOf/
|
|
126
|
+
* allOf-merge/nullable/format(email|uuid|date-time|url)/min-max/required/
|
|
127
|
+
* additionalProperties.
|
|
128
|
+
*
|
|
129
|
+
* Out of scope (v0.1): discriminator (falls back to z.union).
|
|
130
|
+
*/
|
|
131
|
+
function jsonSchemaToZod(input) {
|
|
132
|
+
if (!input || typeof input !== "object") return z.any();
|
|
133
|
+
if (Array.isArray(input.type) && input.type.includes("null")) {
|
|
134
|
+
const rest = input.type.filter((t) => t !== "null");
|
|
135
|
+
return describe(jsonSchemaToZod({
|
|
136
|
+
...input,
|
|
137
|
+
type: rest.length === 1 ? rest[0] : rest
|
|
138
|
+
}).nullable(), input.description);
|
|
139
|
+
}
|
|
140
|
+
if (input.nullable === true) {
|
|
141
|
+
const { nullable, ...rest } = input;
|
|
142
|
+
return describe(jsonSchemaToZod(rest).nullable(), input.description);
|
|
143
|
+
}
|
|
144
|
+
if ("const" in input && input.const !== void 0) return describe(z.literal(input.const), input.description);
|
|
145
|
+
if (Array.isArray(input.enum) && input.enum.length > 0) {
|
|
146
|
+
if (input.enum.every((v) => typeof v === "string")) {
|
|
147
|
+
const values = input.enum;
|
|
148
|
+
return describe(z.enum(values), input.description);
|
|
149
|
+
}
|
|
150
|
+
const variants = input.enum.map((v) => z.literal(v));
|
|
151
|
+
return describe(z.union(variants), input.description);
|
|
152
|
+
}
|
|
153
|
+
if (Array.isArray(input.oneOf) || Array.isArray(input.anyOf)) {
|
|
154
|
+
const variants = (input.oneOf ?? input.anyOf).map(jsonSchemaToZod);
|
|
155
|
+
if (variants.length === 1) return describe(variants[0], input.description);
|
|
156
|
+
return describe(z.union(variants), input.description);
|
|
157
|
+
}
|
|
158
|
+
if (Array.isArray(input.allOf) && input.allOf.length > 0) {
|
|
159
|
+
const merged = {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {},
|
|
162
|
+
required: []
|
|
163
|
+
};
|
|
164
|
+
for (const part of input.allOf) {
|
|
165
|
+
if (part.properties) merged.properties = {
|
|
166
|
+
...merged.properties,
|
|
167
|
+
...part.properties
|
|
168
|
+
};
|
|
169
|
+
if (Array.isArray(part.required)) merged.required = [...merged.required ?? [], ...part.required];
|
|
170
|
+
}
|
|
171
|
+
if (input.description) merged.description = input.description;
|
|
172
|
+
return jsonSchemaToZod(merged);
|
|
173
|
+
}
|
|
174
|
+
const type = Array.isArray(input.type) ? input.type[0] : input.type;
|
|
175
|
+
switch (type) {
|
|
176
|
+
case "string": return describe(buildString(input), input.description);
|
|
177
|
+
case "integer":
|
|
178
|
+
case "number": return describe(buildNumber(input, type === "integer"), input.description);
|
|
179
|
+
case "boolean": return describe(z.boolean(), input.description);
|
|
180
|
+
case "array": return describe(z.array(jsonSchemaToZod(input.items)), input.description);
|
|
181
|
+
default: return describe(buildObject(input), input.description);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function buildString(s) {
|
|
185
|
+
let zs = z.string();
|
|
186
|
+
switch (s.format) {
|
|
187
|
+
case "email":
|
|
188
|
+
zs = zs.email();
|
|
189
|
+
break;
|
|
190
|
+
case "uuid":
|
|
191
|
+
zs = zs.uuid();
|
|
192
|
+
break;
|
|
193
|
+
case "date-time":
|
|
194
|
+
zs = zs.datetime({ offset: true });
|
|
195
|
+
break;
|
|
196
|
+
case "uri":
|
|
197
|
+
case "url":
|
|
198
|
+
zs = zs.url();
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
if (typeof s.minLength === "number") zs = zs.min(s.minLength);
|
|
202
|
+
if (typeof s.maxLength === "number") zs = zs.max(s.maxLength);
|
|
203
|
+
if (typeof s.pattern === "string") try {
|
|
204
|
+
zs = zs.regex(new RegExp(s.pattern));
|
|
205
|
+
} catch {}
|
|
206
|
+
return zs;
|
|
207
|
+
}
|
|
208
|
+
function buildNumber(s, integer) {
|
|
209
|
+
let zn = z.number();
|
|
210
|
+
if (integer) zn = zn.int();
|
|
211
|
+
if (typeof s.minimum === "number") zn = zn.min(s.minimum);
|
|
212
|
+
if (typeof s.maximum === "number") zn = zn.max(s.maximum);
|
|
213
|
+
if (typeof s.exclusiveMinimum === "number") zn = zn.gt(s.exclusiveMinimum);
|
|
214
|
+
if (typeof s.exclusiveMaximum === "number") zn = zn.lt(s.exclusiveMaximum);
|
|
215
|
+
return zn;
|
|
216
|
+
}
|
|
217
|
+
function buildObject(s) {
|
|
218
|
+
const props = s.properties ?? {};
|
|
219
|
+
const required = new Set(s.required ?? []);
|
|
220
|
+
const shape = {};
|
|
221
|
+
for (const [key, value] of Object.entries(props)) {
|
|
222
|
+
const inner = jsonSchemaToZod(value);
|
|
223
|
+
shape[key] = required.has(key) ? inner : inner.optional();
|
|
224
|
+
}
|
|
225
|
+
let obj = z.object(shape);
|
|
226
|
+
if (s.additionalProperties === true) obj = obj.passthrough();
|
|
227
|
+
else if (typeof s.additionalProperties === "object" && s.additionalProperties !== null) obj = obj.catchall(jsonSchemaToZod(s.additionalProperties));
|
|
228
|
+
return obj;
|
|
229
|
+
}
|
|
230
|
+
function describe(schema, description) {
|
|
231
|
+
return description ? schema.describe(description) : schema;
|
|
232
|
+
}
|
|
233
|
+
//#endregion
|
|
234
|
+
//#region src/operation-to-tool.ts
|
|
235
|
+
/** Convert a normalized OpenAPI operation into a BridgentTool. */
|
|
236
|
+
function operationToTool(op, opts) {
|
|
237
|
+
const inputSchema = buildInputSchema(op);
|
|
238
|
+
const description = op.summary ?? op.description ?? `${op.method} ${op.path}`;
|
|
239
|
+
return defineTool({
|
|
240
|
+
name: opts.toolName,
|
|
241
|
+
description: description.slice(0, 1024),
|
|
242
|
+
inputSchema,
|
|
243
|
+
run: async (args) => {
|
|
244
|
+
return invokeRequest(buildRequest(op, args, opts.baseUrl), {
|
|
245
|
+
baseUrl: opts.baseUrl,
|
|
246
|
+
auth: opts.auth,
|
|
247
|
+
fetch: opts.fetch
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
/** Flatten path/query/header params + body into a single z.object(). */
|
|
253
|
+
function buildInputSchema(op) {
|
|
254
|
+
const shape = {};
|
|
255
|
+
for (const param of op.parameters) {
|
|
256
|
+
if (param.in === "cookie") continue;
|
|
257
|
+
const baseSchema = jsonSchemaToZod(param.schema ?? { type: "string" });
|
|
258
|
+
const described = param.description ? baseSchema.describe(param.description) : baseSchema;
|
|
259
|
+
const key = pickInputKey(shape, param.name, param.in);
|
|
260
|
+
shape[key] = param.required ? described : described.optional();
|
|
261
|
+
}
|
|
262
|
+
if (op.requestBody) {
|
|
263
|
+
const json = op.requestBody.content?.["application/json"]?.schema;
|
|
264
|
+
if (json) {
|
|
265
|
+
const bodySchema = jsonSchemaToZod(json);
|
|
266
|
+
shape.body = op.requestBody.required ? bodySchema : bodySchema.optional();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return z.object(shape);
|
|
270
|
+
}
|
|
271
|
+
function pickInputKey(shape, name, location) {
|
|
272
|
+
if (!(name in shape)) return name;
|
|
273
|
+
return `${location}_${name}`;
|
|
274
|
+
}
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region src/parse-openapi.ts
|
|
277
|
+
/**
|
|
278
|
+
* Load + dereference any OpenAPI 3.x source.
|
|
279
|
+
*
|
|
280
|
+
* Accepts:
|
|
281
|
+
* - URL string (https://…)
|
|
282
|
+
* - File path (./openapi.yaml, /abs/path.json)
|
|
283
|
+
* - Inline JSON object
|
|
284
|
+
* - YAML / JSON string
|
|
285
|
+
*
|
|
286
|
+
* Errors during load/dereference are collected; the function throws if the
|
|
287
|
+
* spec cannot be turned into a usable document.
|
|
288
|
+
*/
|
|
289
|
+
async function parseOpenApi(source) {
|
|
290
|
+
if (typeof source !== "string") {
|
|
291
|
+
const result = dereference(source);
|
|
292
|
+
if (!result.schema) throw new OpenApiParseError("dereference returned no schema", result.errors ?? []);
|
|
293
|
+
return {
|
|
294
|
+
document: result.schema,
|
|
295
|
+
version: result.version
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
const loaded = await load(source, { plugins: [readFiles(), fetchUrls()] });
|
|
299
|
+
if (!loaded.specification) throw new OpenApiParseError(`Failed to load spec from ${source}`, loaded.errors ?? []);
|
|
300
|
+
const result = dereference(loaded.filesystem);
|
|
301
|
+
if (!result.schema) throw new OpenApiParseError("dereference returned no schema", result.errors ?? []);
|
|
302
|
+
return {
|
|
303
|
+
document: result.schema,
|
|
304
|
+
version: result.version
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
var OpenApiParseError = class extends Error {
|
|
308
|
+
constructor(message, errors) {
|
|
309
|
+
super(`${message}${errors.length ? `: ${errors.map((e) => e.message).join("; ")}` : ""}`);
|
|
310
|
+
this.errors = errors;
|
|
311
|
+
this.name = "OpenApiParseError";
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region src/slug.ts
|
|
316
|
+
/**
|
|
317
|
+
* Generate an MCP-tool-name from an OpenAPI operation.
|
|
318
|
+
*
|
|
319
|
+
* Strategy:
|
|
320
|
+
* 1. Prefer `operationId` (slugified, capped at 64 chars).
|
|
321
|
+
* 2. Fallback: `${method}_${path-with-braces-stripped-and-slashes-as-underscores}`.
|
|
322
|
+
*
|
|
323
|
+
* The result must match `^[a-zA-Z_][a-zA-Z0-9_-]{0,63}$` to play nicely with
|
|
324
|
+
* Anthropic / Claude Code / Cursor / Gemini CLI tool registries.
|
|
325
|
+
*/
|
|
326
|
+
const TOOL_NAME_RE = /^[a-z_][\w-]{0,63}$/i;
|
|
327
|
+
function operationToToolName(opts) {
|
|
328
|
+
return capName(`${opts.namespace ?? ""}${opts.operationId ? slug(opts.operationId) : `${opts.method.toLowerCase()}_${slugPath(opts.path)}`}`);
|
|
329
|
+
}
|
|
330
|
+
function slug(input) {
|
|
331
|
+
return input.replace(/[^\w-]+/g, "_").replace(/^_+|_+$/g, "").replace(/_{2,}/g, "_");
|
|
332
|
+
}
|
|
333
|
+
function slugPath(path) {
|
|
334
|
+
return slug(path.replace(/^\//, "").replace(/\{([^}]+)\}/g, "$1").replace(/\//g, "_"));
|
|
335
|
+
}
|
|
336
|
+
function capName(name) {
|
|
337
|
+
let n = name.slice(0, 64);
|
|
338
|
+
if (!/^[a-z_]/i.test(n)) n = `_${n}`.slice(0, 64);
|
|
339
|
+
if (!TOOL_NAME_RE.test(n)) return slug(n).slice(0, 64) || "_tool";
|
|
340
|
+
return n;
|
|
341
|
+
}
|
|
342
|
+
//#endregion
|
|
343
|
+
//#region src/from-openapi.ts
|
|
344
|
+
const HTTP_METHODS = [
|
|
345
|
+
"GET",
|
|
346
|
+
"HEAD",
|
|
347
|
+
"POST",
|
|
348
|
+
"PUT",
|
|
349
|
+
"PATCH",
|
|
350
|
+
"DELETE"
|
|
351
|
+
];
|
|
352
|
+
/**
|
|
353
|
+
* Turn an OpenAPI 3.x spec into a list of BridgentTools, ready to feed into
|
|
354
|
+
* `createStdioServer({ tools })`.
|
|
355
|
+
*
|
|
356
|
+
* Read-only by default — to expose mutating operations, set `allow.mutating: true`
|
|
357
|
+
* (or use `allowOperations` allowlist).
|
|
358
|
+
*/
|
|
359
|
+
async function fromOpenApi(options) {
|
|
360
|
+
const { document } = await parseOpenApi(options.spec);
|
|
361
|
+
const baseUrl = options.baseUrl ?? extractBaseUrl(document);
|
|
362
|
+
if (!baseUrl) throw new Error("No baseUrl could be determined from the spec; pass `baseUrl` explicitly.");
|
|
363
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
364
|
+
if (!fetchImpl) throw new Error("No fetch implementation available. Pass `options.fetch` or run on Node ≥ 22.18.");
|
|
365
|
+
const tools = [];
|
|
366
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
367
|
+
const paths = document.paths ?? {};
|
|
368
|
+
for (const [path, pathItem] of Object.entries(paths)) {
|
|
369
|
+
if (!pathItem || typeof pathItem !== "object") continue;
|
|
370
|
+
for (const method of HTTP_METHODS) {
|
|
371
|
+
const operation = pathItem[method.toLowerCase()];
|
|
372
|
+
if (!operation) continue;
|
|
373
|
+
if (!shouldExposeOperation({
|
|
374
|
+
path,
|
|
375
|
+
method,
|
|
376
|
+
operation
|
|
377
|
+
}, options)) continue;
|
|
378
|
+
const normalized = normalizeOperation(path, method, operation);
|
|
379
|
+
const toolName = operationToToolName({
|
|
380
|
+
operationId: normalized.operationId,
|
|
381
|
+
method: normalized.method,
|
|
382
|
+
path: normalized.path,
|
|
383
|
+
namespace: options.namespace
|
|
384
|
+
});
|
|
385
|
+
if (seenNames.has(toolName)) throw new Error(`Duplicate tool name "${toolName}" generated. Provide a \`namespace\` option, or rename the operationId in the spec.`);
|
|
386
|
+
seenNames.add(toolName);
|
|
387
|
+
tools.push(operationToTool(normalized, {
|
|
388
|
+
baseUrl,
|
|
389
|
+
auth: options.auth,
|
|
390
|
+
fetch: fetchImpl,
|
|
391
|
+
toolName
|
|
392
|
+
}));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
return tools;
|
|
396
|
+
}
|
|
397
|
+
function normalizeOperation(path, method, raw) {
|
|
398
|
+
return {
|
|
399
|
+
operationId: raw.operationId ?? `${method.toLowerCase()}_${path}`,
|
|
400
|
+
method,
|
|
401
|
+
path,
|
|
402
|
+
summary: raw.summary,
|
|
403
|
+
description: raw.description,
|
|
404
|
+
parameters: raw.parameters ?? [],
|
|
405
|
+
requestBody: raw.requestBody,
|
|
406
|
+
raw
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
function extractBaseUrl(document) {
|
|
410
|
+
return document.servers?.[0]?.url;
|
|
411
|
+
}
|
|
412
|
+
//#endregion
|
|
413
|
+
export { OpenApiParseError, fromOpenApi };
|
|
414
|
+
|
|
415
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/types.ts","../src/filter.ts","../src/http.ts","../src/jsonschema-to-zod.ts","../src/operation-to-tool.ts","../src/parse-openapi.ts","../src/slug.ts","../src/from-openapi.ts"],"sourcesContent":["export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS'\n\nexport const SAFE_METHODS: HttpMethod[] = ['GET', 'HEAD']\nexport const MUTATING_METHODS: HttpMethod[] = ['POST', 'PUT', 'PATCH', 'DELETE']\n\nexport interface BearerAuth {\n type: 'bearer'\n token: string | (() => string | Promise<string>)\n}\n\nexport interface FromOpenApiOptions {\n /** Spec source: file path, URL, JSON object, or YAML/JSON string. */\n spec: string | Record<string, unknown>\n\n /** Override `spec.servers[0].url` (e.g. sandbox vs prod). */\n baseUrl?: string\n\n /** Authentication. v0.1 supports Bearer only. */\n auth?: BearerAuth\n\n /** Tool name namespace prefix. e.g. `gh_` to avoid collisions across sources. */\n namespace?: string\n\n /** Method allowlist. Default: GET/HEAD only (read-only). */\n allow?: {\n /** When `true`, also exposes POST/PUT/PATCH/DELETE. */\n mutating?: boolean\n /** Explicit method list. Overrides `mutating` when set. */\n methods?: HttpMethod[]\n }\n\n /** Operation allowlist by `operationId`. When non-empty, ONLY these are exposed. */\n allowOperations?: string[]\n\n /** Operation denylist by `operationId`. Applied after `allowOperations`. */\n denyOperations?: string[]\n\n /** Path filter. Cuts down enormous specs (e.g. GitHub). */\n pathFilter?: RegExp | ((path: string) => boolean)\n\n /** Whether to honor `x-bridgent-allow: false` on operations. Default: true. */\n respectExtensions?: boolean\n\n /** Custom fetch (for testing or proxying). Default: `globalThis.fetch`. */\n fetch?: typeof globalThis.fetch\n}\n\n/** Internal: a normalized operation ready to be transformed into a BridgentTool. */\nexport interface NormalizedOperation {\n operationId: string\n method: HttpMethod\n path: string\n summary?: string\n description?: string\n parameters: ParameterObject[]\n requestBody?: RequestBodyObject\n raw: OperationObject\n}\n\nexport interface ParameterObject {\n name: string\n in: 'path' | 'query' | 'header' | 'cookie'\n required?: boolean\n description?: string\n schema?: Record<string, unknown>\n}\n\nexport interface RequestBodyObject {\n description?: string\n required?: boolean\n content?: Record<string, { schema?: Record<string, unknown> }>\n}\n\nexport interface OperationObject {\n operationId?: string\n summary?: string\n description?: string\n parameters?: ParameterObject[]\n requestBody?: RequestBodyObject\n responses?: Record<string, unknown>\n [extension: `x-${string}`]: unknown\n}\n","import type { FromOpenApiOptions, HttpMethod, OperationObject } from './types'\nimport { MUTATING_METHODS, SAFE_METHODS } from './types'\n\nexport interface FilterContext {\n path: string\n method: HttpMethod\n operation: OperationObject\n}\n\n/**\n * Apply the filtering pipeline:\n * 1. method ∈ allow.methods (default GET/HEAD)\n * 2. pathFilter\n * 3. respectExtensions && op['x-bridgent-allow'] === false → drop\n * 4. denyOperations\n * 5. allowOperations (if non-empty, gate)\n */\nexport function shouldExposeOperation(\n ctx: FilterContext,\n opts: FromOpenApiOptions,\n): boolean {\n const allowedMethods = resolveAllowedMethods(opts)\n if (!allowedMethods.includes(ctx.method))\n return false\n\n if (opts.pathFilter) {\n const passes = typeof opts.pathFilter === 'function'\n ? opts.pathFilter(ctx.path)\n : opts.pathFilter.test(ctx.path)\n if (!passes)\n return false\n }\n\n const respectExt = opts.respectExtensions ?? true\n if (respectExt && ctx.operation['x-bridgent-allow'] === false)\n return false\n\n const opId = ctx.operation.operationId\n if (opId && opts.denyOperations?.includes(opId))\n return false\n\n if (opts.allowOperations && opts.allowOperations.length > 0) {\n if (!opId || !opts.allowOperations.includes(opId))\n return false\n }\n\n return true\n}\n\nexport function resolveAllowedMethods(opts: FromOpenApiOptions): HttpMethod[] {\n if (opts.allow?.methods)\n return opts.allow.methods\n\n if (opts.allow?.mutating)\n return [...SAFE_METHODS, ...MUTATING_METHODS]\n\n return [...SAFE_METHODS]\n}\n","import type { BearerAuth, NormalizedOperation } from './types'\n\nexport interface BuiltRequest {\n url: string\n method: string\n headers: Record<string, string>\n body?: string\n}\n\nexport interface InvokeOptions {\n baseUrl: string\n auth?: BearerAuth\n fetch: typeof globalThis.fetch\n}\n\n/** Build a fetch-ready request from flattened tool args + an OpenAPI operation. */\nexport function buildRequest(\n op: NormalizedOperation,\n args: Record<string, unknown>,\n baseUrl: string,\n): BuiltRequest {\n const pathParams: Record<string, string> = {}\n const queryParams = new URLSearchParams()\n const headers: Record<string, string> = { Accept: 'application/json' }\n\n for (const param of op.parameters) {\n const raw = args[param.name]\n if (raw === undefined || raw === null) {\n if (param.required && param.in === 'path')\n throw new Error(`Missing required path param \"${param.name}\" for ${op.operationId}`)\n continue\n }\n const value = String(raw)\n switch (param.in) {\n case 'path':\n pathParams[param.name] = value\n break\n case 'query':\n if (Array.isArray(raw)) {\n for (const v of raw) queryParams.append(param.name, String(v))\n }\n else {\n queryParams.set(param.name, value)\n }\n break\n case 'header':\n headers[param.name] = value\n break\n case 'cookie':\n // Cookies aren't first-class for v0.1; skip silently.\n break\n }\n }\n\n // Path interpolation\n let path = op.path\n for (const [key, value] of Object.entries(pathParams)) {\n path = path.replace(`{${key}}`, encodeURIComponent(value))\n }\n\n // Body (only when requestBody declared and `body` arg present)\n let body: string | undefined\n if (op.requestBody && args.body !== undefined) {\n body = JSON.stringify(args.body)\n headers['Content-Type'] = headers['Content-Type'] ?? 'application/json'\n }\n\n const qs = queryParams.toString()\n const url = `${trimTrailingSlash(baseUrl)}${path}${qs ? `?${qs}` : ''}`\n\n return { url, method: op.method, headers, body }\n}\n\n/** Send a built request and package the response as a structured result. */\nexport async function invokeRequest(\n built: BuiltRequest,\n opts: InvokeOptions,\n): Promise<{\n ok: boolean\n status: number\n statusText: string\n body: unknown\n headers: Record<string, string>\n}> {\n const headers = { ...built.headers }\n if (opts.auth?.type === 'bearer') {\n const token = typeof opts.auth.token === 'function'\n ? await opts.auth.token()\n : opts.auth.token\n if (token)\n headers.Authorization = `Bearer ${token}`\n }\n\n const response = await opts.fetch(built.url, {\n method: built.method,\n headers,\n body: built.body,\n })\n\n const responseHeaders: Record<string, string> = {}\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value\n })\n\n const text = await response.text()\n let parsedBody: unknown\n const contentType = responseHeaders['content-type'] ?? ''\n if (contentType.includes('application/json')) {\n try {\n parsedBody = text.length > 0 ? JSON.parse(text) : null\n }\n catch {\n parsedBody = text\n }\n }\n else {\n // Non-JSON: truncate to ~64KB to avoid blowing up tool responses.\n parsedBody = text.length > 65_536 ? `${text.slice(0, 65_536)}…[truncated]` : text\n }\n\n return {\n ok: response.ok,\n status: response.status,\n statusText: response.statusText,\n body: parsedBody,\n headers: responseHeaders,\n }\n}\n\nfunction trimTrailingSlash(url: string): string {\n return url.endsWith('/') ? url.slice(0, -1) : url\n}\n","import { z } from 'zod'\n\ntype JsonSchema = Record<string, unknown> & {\n type?: string | string[]\n enum?: unknown[]\n const?: unknown\n oneOf?: JsonSchema[]\n anyOf?: JsonSchema[]\n allOf?: JsonSchema[]\n nullable?: boolean\n description?: string\n default?: unknown\n format?: string\n pattern?: string\n minLength?: number\n maxLength?: number\n minimum?: number\n maximum?: number\n exclusiveMinimum?: number | boolean\n exclusiveMaximum?: number | boolean\n items?: JsonSchema\n properties?: Record<string, JsonSchema>\n required?: string[]\n additionalProperties?: boolean | JsonSchema\n}\n\n/**\n * Convert a JSON Schema (OpenAPI 3.0/3.1 subset) into a Zod schema.\n *\n * Coverage: object/array/string/number/integer/boolean/enum/const/oneOf/anyOf/\n * allOf-merge/nullable/format(email|uuid|date-time|url)/min-max/required/\n * additionalProperties.\n *\n * Out of scope (v0.1): discriminator (falls back to z.union).\n */\nexport function jsonSchemaToZod(input: JsonSchema | undefined | null): z.ZodTypeAny {\n if (!input || typeof input !== 'object')\n return z.any()\n\n // 3.1: type can be array including 'null'\n if (Array.isArray(input.type) && input.type.includes('null')) {\n const rest = input.type.filter(t => t !== 'null')\n const next: JsonSchema = {\n ...input,\n type: rest.length === 1 ? rest[0] : rest,\n }\n return describe(jsonSchemaToZod(next).nullable(), input.description)\n }\n\n // 3.0: nullable: true\n if (input.nullable === true) {\n const { nullable, ...rest } = input\n return describe(jsonSchemaToZod(rest).nullable(), input.description)\n }\n\n // const literal (3.1)\n if ('const' in input && input.const !== undefined)\n return describe(z.literal(input.const as never), input.description)\n\n // enum\n if (Array.isArray(input.enum) && input.enum.length > 0) {\n const allStrings = input.enum.every(v => typeof v === 'string')\n if (allStrings) {\n const values = input.enum as [string, ...string[]]\n return describe(z.enum(values), input.description)\n }\n // mixed-type enum → union of literals\n const variants = input.enum.map(v => z.literal(v as never)) as unknown as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]]\n return describe(z.union(variants), input.description)\n }\n\n // oneOf / anyOf\n if (Array.isArray(input.oneOf) || Array.isArray(input.anyOf)) {\n const variants = (input.oneOf ?? input.anyOf!).map(jsonSchemaToZod)\n if (variants.length === 1)\n return describe(variants[0]!, input.description)\n return describe(\n z.union(variants as [z.ZodTypeAny, z.ZodTypeAny, ...z.ZodTypeAny[]]),\n input.description,\n )\n }\n\n // allOf — shallow object merge (sufficient for v0.1 OpenAPI usage)\n if (Array.isArray(input.allOf) && input.allOf.length > 0) {\n const merged: JsonSchema = { type: 'object', properties: {}, required: [] }\n for (const part of input.allOf) {\n if (part.properties)\n merged.properties = { ...merged.properties, ...part.properties }\n if (Array.isArray(part.required))\n merged.required = [...(merged.required ?? []), ...part.required]\n }\n if (input.description)\n merged.description = input.description\n return jsonSchemaToZod(merged)\n }\n\n const type = Array.isArray(input.type) ? input.type[0] : input.type\n\n switch (type) {\n case 'string':\n return describe(buildString(input), input.description)\n case 'integer':\n case 'number':\n return describe(buildNumber(input, type === 'integer'), input.description)\n case 'boolean':\n return describe(z.boolean(), input.description)\n case 'array':\n return describe(z.array(jsonSchemaToZod(input.items)), input.description)\n case 'object':\n default:\n return describe(buildObject(input), input.description)\n }\n}\n\nfunction buildString(s: JsonSchema): z.ZodTypeAny {\n let zs: z.ZodString = z.string()\n switch (s.format) {\n case 'email':\n zs = zs.email()\n break\n case 'uuid':\n zs = zs.uuid()\n break\n case 'date-time':\n zs = zs.datetime({ offset: true })\n break\n case 'uri':\n case 'url':\n zs = zs.url()\n break\n }\n if (typeof s.minLength === 'number')\n zs = zs.min(s.minLength)\n if (typeof s.maxLength === 'number')\n zs = zs.max(s.maxLength)\n if (typeof s.pattern === 'string') {\n try {\n zs = zs.regex(new RegExp(s.pattern))\n }\n catch {\n // ignore invalid regex; tolerate malformed specs\n }\n }\n return zs\n}\n\nfunction buildNumber(s: JsonSchema, integer: boolean): z.ZodTypeAny {\n let zn: z.ZodNumber = z.number()\n if (integer)\n zn = zn.int()\n if (typeof s.minimum === 'number')\n zn = zn.min(s.minimum)\n if (typeof s.maximum === 'number')\n zn = zn.max(s.maximum)\n if (typeof s.exclusiveMinimum === 'number')\n zn = zn.gt(s.exclusiveMinimum)\n if (typeof s.exclusiveMaximum === 'number')\n zn = zn.lt(s.exclusiveMaximum)\n return zn\n}\n\nfunction buildObject(s: JsonSchema): z.ZodTypeAny {\n const props = s.properties ?? {}\n const required = new Set<string>(s.required ?? [])\n const shape: Record<string, z.ZodTypeAny> = {}\n\n for (const [key, value] of Object.entries(props)) {\n const inner = jsonSchemaToZod(value)\n shape[key] = required.has(key) ? inner : inner.optional()\n }\n\n let obj: z.ZodTypeAny = z.object(shape)\n\n // additionalProperties\n if (s.additionalProperties === true) {\n // passthrough lets unknown keys through verbatim\n obj = (obj as z.ZodObject<z.ZodRawShape>).passthrough()\n }\n else if (typeof s.additionalProperties === 'object' && s.additionalProperties !== null) {\n // allow specific shape for unknown keys; still keep declared props strict\n obj = (obj as z.ZodObject<z.ZodRawShape>).catchall(jsonSchemaToZod(s.additionalProperties))\n }\n\n return obj\n}\n\nfunction describe(schema: z.ZodTypeAny, description: string | undefined): z.ZodTypeAny {\n return description ? schema.describe(description) : schema\n}\n","import type { BridgentTool } from '@bridgent/core'\nimport type { BearerAuth, NormalizedOperation } from './types'\nimport { defineTool } from '@bridgent/core'\nimport { z } from 'zod'\nimport { buildRequest, invokeRequest } from './http'\nimport { jsonSchemaToZod } from './jsonschema-to-zod'\n\nexport interface BuildToolOptions {\n baseUrl: string\n auth?: BearerAuth\n fetch: typeof globalThis.fetch\n toolName: string\n}\n\n/** Convert a normalized OpenAPI operation into a BridgentTool. */\nexport function operationToTool(\n op: NormalizedOperation,\n opts: BuildToolOptions,\n): BridgentTool {\n const inputSchema = buildInputSchema(op)\n const description = op.summary ?? op.description ?? `${op.method} ${op.path}`\n\n return defineTool({\n name: opts.toolName,\n description: description.slice(0, 1024),\n inputSchema,\n run: async (args) => {\n const request = buildRequest(op, args as Record<string, unknown>, opts.baseUrl)\n return invokeRequest(request, {\n baseUrl: opts.baseUrl,\n auth: opts.auth,\n fetch: opts.fetch,\n })\n },\n })\n}\n\n/** Flatten path/query/header params + body into a single z.object(). */\nfunction buildInputSchema(op: NormalizedOperation): z.ZodObject<z.ZodRawShape> {\n const shape: Record<string, z.ZodTypeAny> = {}\n\n for (const param of op.parameters) {\n if (param.in === 'cookie')\n continue\n const baseSchema = jsonSchemaToZod(param.schema ?? { type: 'string' })\n const described = param.description\n ? baseSchema.describe(param.description)\n : baseSchema\n // Avoid name collisions across `in` types: prefix non-path duplicates.\n const key = pickInputKey(shape, param.name, param.in)\n shape[key] = param.required ? described : described.optional()\n }\n\n if (op.requestBody) {\n const json = op.requestBody.content?.['application/json']?.schema\n if (json) {\n const bodySchema = jsonSchemaToZod(json)\n shape.body = op.requestBody.required ? bodySchema : bodySchema.optional()\n }\n }\n\n return z.object(shape)\n}\n\nfunction pickInputKey(\n shape: Record<string, z.ZodTypeAny>,\n name: string,\n location: 'path' | 'query' | 'header' | 'cookie',\n): string {\n if (!(name in shape))\n return name\n // path-collisions are unusual; for query/header collisions, prefix to stay unique.\n return `${location}_${name}`\n}\n","import { dereference, load } from '@scalar/openapi-parser'\nimport { fetchUrls } from '@scalar/openapi-parser/plugins/fetch-urls'\nimport { readFiles } from '@scalar/openapi-parser/plugins/read-files'\n\nexport interface DereferencedSpec {\n /** Top-level OpenAPI document with all `$ref`s resolved. */\n document: Record<string, any>\n /** Detected version, e.g. '3.0' / '3.1'. */\n version?: string\n}\n\n/**\n * Load + dereference any OpenAPI 3.x source.\n *\n * Accepts:\n * - URL string (https://…)\n * - File path (./openapi.yaml, /abs/path.json)\n * - Inline JSON object\n * - YAML / JSON string\n *\n * Errors during load/dereference are collected; the function throws if the\n * spec cannot be turned into a usable document.\n */\nexport async function parseOpenApi(\n source: string | Record<string, unknown>,\n): Promise<DereferencedSpec> {\n // For object input, dereference accepts it directly.\n if (typeof source !== 'string') {\n const result = dereference(source as Record<string, unknown>)\n if (!result.schema)\n throw new OpenApiParseError('dereference returned no schema', result.errors ?? [])\n return { document: result.schema as Record<string, any>, version: result.version }\n }\n\n // For string input (URL / file path / raw text), let `load` figure it out.\n const loaded = await load(source, {\n plugins: [readFiles(), fetchUrls()],\n })\n\n if (!loaded.specification)\n throw new OpenApiParseError(`Failed to load spec from ${source}`, loaded.errors ?? [])\n\n const result = dereference(loaded.filesystem)\n if (!result.schema)\n throw new OpenApiParseError('dereference returned no schema', result.errors ?? [])\n\n return { document: result.schema as Record<string, any>, version: result.version }\n}\n\nexport class OpenApiParseError extends Error {\n override name = 'OpenApiParseError'\n constructor(\n message: string,\n public readonly errors: ReadonlyArray<{ message: string, path?: string[] }>,\n ) {\n super(`${message}${errors.length ? `: ${errors.map(e => e.message).join('; ')}` : ''}`)\n }\n}\n","/**\n * Generate an MCP-tool-name from an OpenAPI operation.\n *\n * Strategy:\n * 1. Prefer `operationId` (slugified, capped at 64 chars).\n * 2. Fallback: `${method}_${path-with-braces-stripped-and-slashes-as-underscores}`.\n *\n * The result must match `^[a-zA-Z_][a-zA-Z0-9_-]{0,63}$` to play nicely with\n * Anthropic / Claude Code / Cursor / Gemini CLI tool registries.\n */\n\nconst TOOL_NAME_RE = /^[a-z_][\\w-]{0,63}$/i\n\nexport function operationToToolName(opts: {\n operationId?: string\n method: string\n path: string\n namespace?: string\n}): string {\n const namespace = opts.namespace ?? ''\n const base = opts.operationId\n ? slug(opts.operationId)\n : `${opts.method.toLowerCase()}_${slugPath(opts.path)}`\n\n return capName(`${namespace}${base}`)\n}\n\nfunction slug(input: string): string {\n // Replace any disallowed character with `_`, collapse repeats, trim.\n return input\n .replace(/[^\\w-]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .replace(/_{2,}/g, '_')\n}\n\nfunction slugPath(path: string): string {\n return slug(\n path\n .replace(/^\\//, '')\n .replace(/\\{([^}]+)\\}/g, '$1') // {id} -> id\n .replace(/\\//g, '_'),\n )\n}\n\nfunction capName(name: string): string {\n let n = name.slice(0, 64)\n // Ensure it starts with a letter or underscore.\n if (!/^[a-z_]/i.test(n))\n n = `_${n}`.slice(0, 64)\n if (!TOOL_NAME_RE.test(n))\n return slug(n).slice(0, 64) || '_tool'\n return n\n}\n\nexport function isValidToolName(name: string): boolean {\n return TOOL_NAME_RE.test(name)\n}\n","import type { BridgentTool } from '@bridgent/core'\nimport type { FromOpenApiOptions, HttpMethod, NormalizedOperation, OperationObject, ParameterObject, RequestBodyObject } from './types'\nimport { shouldExposeOperation } from './filter'\nimport { operationToTool } from './operation-to-tool'\nimport { parseOpenApi } from './parse-openapi'\nimport { operationToToolName } from './slug'\n\nconst HTTP_METHODS: readonly HttpMethod[] = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'] as const\n\n/**\n * Turn an OpenAPI 3.x spec into a list of BridgentTools, ready to feed into\n * `createStdioServer({ tools })`.\n *\n * Read-only by default — to expose mutating operations, set `allow.mutating: true`\n * (or use `allowOperations` allowlist).\n */\nexport async function fromOpenApi(\n options: FromOpenApiOptions,\n): Promise<BridgentTool[]> {\n const { document } = await parseOpenApi(options.spec)\n\n const baseUrl = options.baseUrl ?? extractBaseUrl(document)\n if (!baseUrl)\n throw new Error('No baseUrl could be determined from the spec; pass `baseUrl` explicitly.')\n\n const fetchImpl = options.fetch ?? globalThis.fetch\n if (!fetchImpl)\n throw new Error('No fetch implementation available. Pass `options.fetch` or run on Node ≥ 22.18.')\n\n const tools: BridgentTool[] = []\n const seenNames = new Set<string>()\n\n const paths = (document.paths ?? {}) as Record<string, Record<string, OperationObject>>\n\n for (const [path, pathItem] of Object.entries(paths)) {\n if (!pathItem || typeof pathItem !== 'object')\n continue\n\n for (const method of HTTP_METHODS) {\n const operation = pathItem[method.toLowerCase()] as OperationObject | undefined\n if (!operation)\n continue\n\n const ctx = { path, method, operation }\n if (!shouldExposeOperation(ctx, options))\n continue\n\n const normalized = normalizeOperation(path, method, operation)\n const toolName = operationToToolName({\n operationId: normalized.operationId,\n method: normalized.method,\n path: normalized.path,\n namespace: options.namespace,\n })\n\n if (seenNames.has(toolName)) {\n throw new Error(\n `Duplicate tool name \"${toolName}\" generated. `\n + `Provide a \\`namespace\\` option, or rename the operationId in the spec.`,\n )\n }\n seenNames.add(toolName)\n\n tools.push(operationToTool(normalized, {\n baseUrl,\n auth: options.auth,\n fetch: fetchImpl,\n toolName,\n }))\n }\n }\n\n return tools\n}\n\nfunction normalizeOperation(\n path: string,\n method: HttpMethod,\n raw: OperationObject,\n): NormalizedOperation {\n return {\n operationId: raw.operationId ?? `${method.toLowerCase()}_${path}`,\n method,\n path,\n summary: raw.summary,\n description: raw.description,\n parameters: (raw.parameters ?? []) as ParameterObject[],\n requestBody: raw.requestBody as RequestBodyObject | undefined,\n raw,\n }\n}\n\nfunction extractBaseUrl(document: Record<string, any>): string | undefined {\n const servers = document.servers as Array<{ url?: string }> | undefined\n return servers?.[0]?.url\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bridgent/source-openapi",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Bridgent AI — expose any OpenAPI 3.x spec as MCP tools. Read-only by default, with Bearer auth, path filtering, and a Zod-typed bridge.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://bridgent.ai",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/js-mark/bridgent.git",
|
|
11
|
+
"directory": "packages/source-openapi"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/js-mark/bridgent/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"bridgent",
|
|
18
|
+
"bridgent-ai",
|
|
19
|
+
"mcp",
|
|
20
|
+
"openapi",
|
|
21
|
+
"openapi-3.1",
|
|
22
|
+
"ai-agent",
|
|
23
|
+
"claude-code",
|
|
24
|
+
"swagger"
|
|
25
|
+
],
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.mts",
|
|
29
|
+
"import": "./dist/index.mjs"
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
"main": "./dist/index.mjs",
|
|
33
|
+
"types": "./dist/index.d.mts",
|
|
34
|
+
"files": [
|
|
35
|
+
"README.md",
|
|
36
|
+
"dist"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=22.18.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"zod": "^4.4.3"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@scalar/openapi-parser": "^0.28.5",
|
|
46
|
+
"@bridgent/core": "0.1.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^25.9.1",
|
|
50
|
+
"tsdown": "^0.22.1",
|
|
51
|
+
"typescript": "^6.0.3",
|
|
52
|
+
"vitest": "^3.2.6",
|
|
53
|
+
"zod": "^4.4.3"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsdown",
|
|
57
|
+
"dev": "tsdown --watch",
|
|
58
|
+
"test": "vitest run",
|
|
59
|
+
"typecheck": "tsc --noEmit",
|
|
60
|
+
"lint": "eslint ."
|
|
61
|
+
}
|
|
62
|
+
}
|