@knitli/openapi-mcp 1.2.0 → 1.2.1
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/README.md +7 -0
- package/dist/cli.js +3 -1
- package/dist/runtime/serialize.js +21 -9
- package/dist/slice.d.ts +9 -1
- package/dist/slice.js +28 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -117,6 +117,13 @@ nothing is an error, not a silently smaller release — fix the typo it names.
|
|
|
117
117
|
OpenAPI 3.1 `webhooks` are not part of `paths` and are left untouched, dangling
|
|
118
118
|
refs and all; Graph's v1.0 document (3.0) has none.
|
|
119
119
|
|
|
120
|
+
`--optional <propertyName>` (repeatable) removes that property from every
|
|
121
|
+
`required` array anywhere in the sliced output — component schemas and inline
|
|
122
|
+
request, parameter and response schemas alike — dropping `required` entirely
|
|
123
|
+
where that empties it. Graph's OpenAPI document marks `@odata.type` `required` on nearly
|
|
124
|
+
every schema, but the Graph service itself never requires it on request
|
|
125
|
+
bodies, so pass `--optional '@odata.type'` when slicing Graph specs.
|
|
126
|
+
|
|
120
127
|
## Configure and start the local MCP server
|
|
121
128
|
|
|
122
129
|
Save this as `operator/config.json`, substituting absolute paths, the public key,
|
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ const USAGE = `openapi-mcp — compile OpenAPI documents into signed MCP artifac
|
|
|
15
15
|
--catalog <id> --release <id> --generation <n> --issuer <id> --key-id <id>
|
|
16
16
|
--policy-id <id> --allowed-origin <https-origin> --out <directory> --sign-key <path>
|
|
17
17
|
[--permissions <path>] [--reference-root <path> --reference-map <path>]
|
|
18
|
-
slice --spec <openapi.yaml|json> --out <sliced.json> [--tag <tag>]... [--operation <operationId>]... [--max-document-keys <n>] [--max-document-nodes <n>]
|
|
18
|
+
slice --spec <openapi.yaml|json> --out <sliced.json> [--tag <tag>]... [--operation <operationId>]... [--optional <propertyName>]... [--max-document-keys <n>] [--max-document-nodes <n>]
|
|
19
19
|
verify --artifact <path> --sig <path> --pub <path> (legacy v3 exact-file signature)
|
|
20
20
|
keygen [--out <dir>]
|
|
21
21
|
serve --config <absolute-config-path>
|
|
@@ -214,6 +214,7 @@ if (command === "slice") {
|
|
|
214
214
|
out: { type: "string" },
|
|
215
215
|
tag: { type: "string", multiple: true },
|
|
216
216
|
operation: { type: "string", multiple: true },
|
|
217
|
+
optional: { type: "string", multiple: true },
|
|
217
218
|
"max-document-keys": { type: "string" },
|
|
218
219
|
"max-document-nodes": { type: "string" },
|
|
219
220
|
},
|
|
@@ -231,6 +232,7 @@ if (command === "slice") {
|
|
|
231
232
|
const sliced = sliceSpec(document, {
|
|
232
233
|
tags: values.tag ?? [],
|
|
233
234
|
operations: values.operation ?? [],
|
|
235
|
+
optional: values.optional ?? [],
|
|
234
236
|
});
|
|
235
237
|
await writeFile(values.out, `${JSON.stringify(sliced, null, 2)}\n`);
|
|
236
238
|
const paths = sliced.paths;
|
|
@@ -308,6 +308,18 @@ function validateDiscriminator(schema, branches, value) {
|
|
|
308
308
|
schemaFailure();
|
|
309
309
|
return selected[0];
|
|
310
310
|
}
|
|
311
|
+
/** Indices of the `anyOf`/`oneOf` branches the value validates against. When the schema also
|
|
312
|
+
* carries a discriminator, the branch it selects must be among them. */
|
|
313
|
+
function matchingBranches(schema, raw, value, closure, depth, context) {
|
|
314
|
+
const branches = schemaArray(raw);
|
|
315
|
+
const selected = validateDiscriminator(schema, branches, value);
|
|
316
|
+
const matches = branches
|
|
317
|
+
.map((branch, index) => validates(branch, value, closure, depth + 1, context) ? index : -1)
|
|
318
|
+
.filter((index) => index >= 0);
|
|
319
|
+
if (selected !== null && !matches.includes(selected))
|
|
320
|
+
schemaFailure();
|
|
321
|
+
return matches;
|
|
322
|
+
}
|
|
311
323
|
function validateReference(id, schema, value, closure, depth, context) {
|
|
312
324
|
const activeValues = context.activeReferences.get(id) ?? new Set();
|
|
313
325
|
if (activeValues.has(value))
|
|
@@ -349,22 +361,22 @@ function validateSchema(schema, value, closure, depth, context) {
|
|
|
349
361
|
validateSchema(branch, value, closure, depth + 1, context);
|
|
350
362
|
}
|
|
351
363
|
if (Object.hasOwn(schema, "anyOf")) {
|
|
352
|
-
const matches =
|
|
364
|
+
const matches = matchingBranches(schema, schema.anyOf, value, closure, depth, context);
|
|
353
365
|
if (matches.length === 0)
|
|
354
366
|
schemaFailure();
|
|
355
367
|
}
|
|
356
368
|
if (Object.hasOwn(schema, "oneOf")) {
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
const matches = branches
|
|
360
|
-
.map((branch, index) => validates(branch, value, closure, depth + 1, context) ? index : -1)
|
|
361
|
-
.filter((index) => index >= 0);
|
|
362
|
-
if (matches.length !== 1 || (selected !== null && matches[0] !== selected))
|
|
369
|
+
const matches = matchingBranches(schema, schema.oneOf, value, closure, depth, context);
|
|
370
|
+
if (matches.length !== 1)
|
|
363
371
|
schemaFailure();
|
|
364
372
|
}
|
|
365
|
-
|
|
373
|
+
// A `discriminator` with no `oneOf`/`anyOf` alongside it is OpenAPI 3.0's inheritance form
|
|
374
|
+
// (OpenAPI 3.0.3 §4.7.24.2, Discriminator Object, "composition and inheritance"): it names the
|
|
375
|
+
// property a *consumer* would branch on, but the schema itself is a plain `type: object`
|
|
376
|
+
// (optionally under `allOf`) with no selection for us to perform. Ignore it rather than reject,
|
|
377
|
+
// but keep the same shape check every other supported key gets.
|
|
378
|
+
else if (Object.hasOwn(schema, "discriminator") && !isObject(schema.discriminator))
|
|
366
379
|
schemaFailure();
|
|
367
|
-
}
|
|
368
380
|
if (Object.hasOwn(schema, "nullable")) {
|
|
369
381
|
if (typeof schema.nullable !== "boolean")
|
|
370
382
|
schemaFailure();
|
package/dist/slice.d.ts
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
export declare const HTTP_METHODS: readonly ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
|
|
2
2
|
type Json = Record<string, unknown>;
|
|
3
|
-
/** Which operations survive: any of these tags, or any of these operation ids.
|
|
3
|
+
/** Which operations survive: any of these tags, or any of these operation ids. `optional` names
|
|
4
|
+
* properties to drop from every `required` array in the sliced output — see `dropRequired`. */
|
|
4
5
|
export interface SliceSelection {
|
|
5
6
|
readonly tags: readonly string[];
|
|
6
7
|
readonly operations: readonly string[];
|
|
8
|
+
readonly optional?: readonly string[];
|
|
7
9
|
}
|
|
10
|
+
/** Returns a new value with every `name` in `names` removed from every `required` array found
|
|
11
|
+
* anywhere in `document` — component schemas and inline schemas (paths, requestBodies,
|
|
12
|
+
* parameters) alike — deleting the `required` key entirely if that empties it. Does not mutate
|
|
13
|
+
* `document`. Graph's OpenAPI document marks `@odata.type` `required` on nearly every schema,
|
|
14
|
+
* but the service itself never requires it on request bodies. */
|
|
15
|
+
export declare function dropRequired(document: Json, names: ReadonlySet<string>): Json;
|
|
8
16
|
/** Number of object keys in a JSON value; compare with the compiler's `maxDocumentKeys` (250,000). */
|
|
9
17
|
export declare function countKeys(value: unknown): number;
|
|
10
18
|
/** Describes a key count against compile-release's default `maxDocumentKeys`, and what to do if it doesn't fit. */
|
package/dist/slice.js
CHANGED
|
@@ -16,6 +16,31 @@ export const HTTP_METHODS = [
|
|
|
16
16
|
"patch",
|
|
17
17
|
"trace",
|
|
18
18
|
];
|
|
19
|
+
/** Returns a new value with every `name` in `names` removed from every `required` array found
|
|
20
|
+
* anywhere in `document` — component schemas and inline schemas (paths, requestBodies,
|
|
21
|
+
* parameters) alike — deleting the `required` key entirely if that empties it. Does not mutate
|
|
22
|
+
* `document`. Graph's OpenAPI document marks `@odata.type` `required` on nearly every schema,
|
|
23
|
+
* but the service itself never requires it on request bodies. */
|
|
24
|
+
export function dropRequired(document, names) {
|
|
25
|
+
function walk(value) {
|
|
26
|
+
if (Array.isArray(value))
|
|
27
|
+
return value.map(walk);
|
|
28
|
+
if (!value || typeof value !== "object")
|
|
29
|
+
return value;
|
|
30
|
+
const result = {};
|
|
31
|
+
for (const [key, item] of Object.entries(value)) {
|
|
32
|
+
if (key === "required" && Array.isArray(item)) {
|
|
33
|
+
const kept = item.filter((name) => typeof name !== "string" || !names.has(name));
|
|
34
|
+
if (kept.length > 0)
|
|
35
|
+
result[key] = kept;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
result[key] = walk(item);
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
return walk(document);
|
|
43
|
+
}
|
|
19
44
|
/** Number of object keys in a JSON value; compare with the compiler's `maxDocumentKeys` (250,000). */
|
|
20
45
|
export function countKeys(value) {
|
|
21
46
|
if (Array.isArray(value))
|
|
@@ -193,12 +218,14 @@ export function sliceSpec(document, selection) {
|
|
|
193
218
|
const server = document.servers?.[0]?.url;
|
|
194
219
|
if (!server)
|
|
195
220
|
throw new Error("The document names no server.");
|
|
196
|
-
|
|
221
|
+
const sliced = {
|
|
197
222
|
...document,
|
|
198
223
|
servers: [{ url: new URL(server).origin }],
|
|
199
224
|
paths,
|
|
200
225
|
components: prunedComponents,
|
|
201
226
|
};
|
|
227
|
+
const optional = selection.optional ?? [];
|
|
228
|
+
return optional.length > 0 ? dropRequired(sliced, new Set(optional)) : sliced;
|
|
202
229
|
}
|
|
203
230
|
/** Drops `discriminator.mapping` entries whose target is not in the kept closure, on a shallow
|
|
204
231
|
* copy — the schema itself is left aliased to the source document when there is nothing to prune. */
|
package/package.json
CHANGED