@haddowg/json-api-mcp 1.2.1 → 1.4.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/README.md +16 -2
- package/dist/cli.d.mts +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/{generate-CuWnYJUB.d.mts → generate-D0kx-DHv.d.mts} +8 -3
- package/dist/{generate-CuWnYJUB.d.mts.map → generate-D0kx-DHv.d.mts.map} +1 -1
- package/dist/{generate-CiLuqQJ0.mjs → generate-yf5Kc4Yh.mjs} +169 -46
- package/dist/generate-yf5Kc4Yh.mjs.map +1 -0
- package/dist/generate.d.mts +1 -1
- package/dist/generate.mjs +1 -1
- package/dist/index.cjs +201 -56
- package/dist/index.d.cts +8 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +8 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +201 -56
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -4
- package/dist/generate-CiLuqQJ0.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -24,8 +24,8 @@ npm install --save-dev @haddowg/json-api-codegen
|
|
|
24
24
|
## Generate the model
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
npx json-api-mcp --input openapi/v5.json --output src/mcp/v5.mcp.json
|
|
28
|
-
npx json-api-mcp --input openapi/v5.json --output src/mcp/v5.mcp.json --check # in CI
|
|
27
|
+
npx @haddowg/json-api-mcp --input openapi/v5.json --output src/mcp/v5.mcp.json
|
|
28
|
+
npx @haddowg/json-api-mcp --input openapi/v5.json --output src/mcp/v5.mcp.json --check # in CI
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Tools include one relationship deep by default, so each tool's schema carries only the types its
|
|
@@ -45,6 +45,20 @@ session.tools // answer tools/list with these
|
|
|
45
45
|
await session.callTool('albums_list', { sort: ['-releasedAt'], pageSize: 5 })
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
Every result's `structuredContent` is a JSON:API-like envelope. A single resource comes back as
|
|
49
|
+
`{ data }`, a collection as `{ data, links, meta }` with only the pagination links, a delete as
|
|
50
|
+
`{ data: null }` and a meta-only action as `{ meta }`. A flattened resource keeps the links its
|
|
51
|
+
type names, such as a presigned upload URL, under `links`.
|
|
52
|
+
|
|
53
|
+
Coming from 1.2: a get returns `{ data }` where it returned `{ item }`, a create or update is
|
|
54
|
+
wrapped in `{ data }` instead of coming back bare, and a collection lists its resources under
|
|
55
|
+
`data` instead of `items`. A delete returns `{ data: null }` instead of a line of text, a
|
|
56
|
+
meta-only action `{ meta }` instead of the bare meta object, and a URL action
|
|
57
|
+
`{ data: { url, type, expiresAt? } }` instead of the bare object. Regenerate the model to get the
|
|
58
|
+
new shapes. A model generated by 1.2 still loads and keeps the 1.2 shapes its output schemas
|
|
59
|
+
describe. The [migration notes](https://haddowg.github.io/json-api-ts/mcp/#migrating-from-12)
|
|
60
|
+
cover the rest.
|
|
61
|
+
|
|
48
62
|
Scope filtering counts only the security requirements a caller can meet with its credential
|
|
49
63
|
schemes, the document's `oauth2` schemes unless you pass `credentialSchemes`. Scopes, feature
|
|
50
64
|
gates, headers and error hints come from optional hooks: `scopeMatcher`, `capabilities`,
|
package/dist/cli.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as WriteModelOptions } from "./generate-
|
|
1
|
+
import { n as WriteModelOptions } from "./generate-D0kx-DHv.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/cli.d.ts
|
|
4
4
|
declare const USAGE = "Usage: json-api-mcp --input <url|file> --output <file> [--default-page-size <n>] [--include-depth <n|all>] [--check]";
|
package/dist/cli.mjs
CHANGED
|
@@ -196,7 +196,7 @@ type ResultShape = 'collection' | 'resource' | 'item' | 'deleted' | 'meta' | 'lo
|
|
|
196
196
|
//#endregion
|
|
197
197
|
//#region src/model.d.ts
|
|
198
198
|
/** The artefact's own format. A runtime refuses a format it doesn't know. */
|
|
199
|
-
declare const MODEL_FORMAT =
|
|
199
|
+
declare const MODEL_FORMAT = 4;
|
|
200
200
|
/** The package that writes the artefact. */
|
|
201
201
|
declare const MODEL_GENERATOR = "@haddowg/json-api-mcp";
|
|
202
202
|
/** One operation, as hooks see it, without the descriptor entries the runtime adds back. */
|
|
@@ -243,7 +243,7 @@ interface McpModelTool {
|
|
|
243
243
|
}
|
|
244
244
|
/** The generated artefact. */
|
|
245
245
|
interface McpModel {
|
|
246
|
-
format: 1 | 2 | typeof MODEL_FORMAT;
|
|
246
|
+
format: 1 | 2 | 3 | typeof MODEL_FORMAT;
|
|
247
247
|
generator: typeof MODEL_GENERATOR;
|
|
248
248
|
/** The generator contract the source document declared. */
|
|
249
249
|
contract: number;
|
|
@@ -269,6 +269,11 @@ interface McpModel {
|
|
|
269
269
|
* declares. Formats 1 and 2 don't carry it and set no cap.
|
|
270
270
|
*/
|
|
271
271
|
includeDepth?: number | null;
|
|
272
|
+
/**
|
|
273
|
+
* Format 4: the links each type names, its declared links and action links, for the types that
|
|
274
|
+
* name any. A flattened resource carries these under `links`.
|
|
275
|
+
*/
|
|
276
|
+
resourceLinks?: Record<string, string[]>;
|
|
272
277
|
/** The json-api-ts descriptor the client calls through. */
|
|
273
278
|
descriptor: ApiDescriptor;
|
|
274
279
|
tools: McpModelTool[];
|
|
@@ -334,4 +339,4 @@ interface WriteModelResult {
|
|
|
334
339
|
declare function writeMcpModel(options: WriteModelOptions): Promise<WriteModelResult>;
|
|
335
340
|
//#endregion
|
|
336
341
|
export { serialiseMcpModel as a, buildMcpModel as c, generateMcpModel as i, WriteModelOptions as n, writeMcpModel as o, WriteModelResult as r, BuildModelOptions as s, GenerateModelOptions as t };
|
|
337
|
-
//# sourceMappingURL=generate-
|
|
342
|
+
//# sourceMappingURL=generate-D0kx-DHv.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-
|
|
1
|
+
{"version":3,"file":"generate-D0kx-DHv.d.mts","names":[],"sources":["../src/openapi.ts","../src/portable.ts","../src/inputs.ts","../src/types.ts","../src/catalogue.ts","../src/model.ts","../src/build.ts","../src/generate.ts"],"mappings":"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"}
|
|
@@ -123,7 +123,81 @@ function includePaths(doc, op) {
|
|
|
123
123
|
return [];
|
|
124
124
|
}
|
|
125
125
|
const ref = (type) => ({ $ref: `#/$defs/${type}` });
|
|
126
|
-
/**
|
|
126
|
+
/** Links every resource may carry, which a flattened resource leaves out. */
|
|
127
|
+
const GENERIC_LINKS = /* @__PURE__ */ new Set([
|
|
128
|
+
"self",
|
|
129
|
+
"related",
|
|
130
|
+
"describedby"
|
|
131
|
+
]);
|
|
132
|
+
/** The members a flattened link keeps. */
|
|
133
|
+
const LINK_MEMBERS = [
|
|
134
|
+
"href",
|
|
135
|
+
"type",
|
|
136
|
+
"title",
|
|
137
|
+
"meta"
|
|
138
|
+
];
|
|
139
|
+
/**
|
|
140
|
+
* The links a resource schema names: its declared links and its action links. From generator
|
|
141
|
+
* contract 18 they're the properties of the `<Type>ResourceLinks` component's own `allOf`
|
|
142
|
+
* member; before, they sit beside `self` in its properties.
|
|
143
|
+
*/
|
|
144
|
+
function namedLinkSchemas(doc, resource) {
|
|
145
|
+
const component = deref(doc, (isObject(resource?.["properties"]) ? resource["properties"] : {})["links"]);
|
|
146
|
+
if (!isObject(component)) return [];
|
|
147
|
+
const own = isObject(component["properties"]) ? Object.entries(component["properties"]) : [];
|
|
148
|
+
const extended = (Array.isArray(component["allOf"]) ? component["allOf"] : []).filter((member) => isObject(member) && member["$ref"] === void 0).flatMap((member) => isObject(member["properties"]) ? Object.entries(member["properties"]) : []);
|
|
149
|
+
return [...own, ...extended].filter((entry) => !GENERIC_LINKS.has(entry[0]) && isObject(entry[1]));
|
|
150
|
+
}
|
|
151
|
+
const hasHref = (s) => isObject(s) && isObject(s["properties"]) && s["properties"]["href"] !== void 0;
|
|
152
|
+
/** The link object branch of a link schema: the schema itself, or its `anyOf` branch with `href`. */
|
|
153
|
+
function linkObjectSchema(schema) {
|
|
154
|
+
if (hasHref(schema)) return schema;
|
|
155
|
+
return (Array.isArray(schema["anyOf"]) ? schema["anyOf"] : []).find(hasHref);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* One named link as a flattened resource carries it: `{ href, type?, title?, meta? }`, typed from
|
|
159
|
+
* the document's link schema, meta members included. A link the server renders as a bare URL is
|
|
160
|
+
* normalised to `{ href }`.
|
|
161
|
+
*/
|
|
162
|
+
function linkOutput(doc, schema) {
|
|
163
|
+
const resolved = outputSchema(doc, schema);
|
|
164
|
+
const object = linkObjectSchema(resolved);
|
|
165
|
+
const source = isObject(object?.["properties"]) ? object["properties"] : {};
|
|
166
|
+
const properties = { href: {
|
|
167
|
+
type: "string",
|
|
168
|
+
format: "uri-reference"
|
|
169
|
+
} };
|
|
170
|
+
for (const member of LINK_MEMBERS) {
|
|
171
|
+
const value = source[member];
|
|
172
|
+
if (isObject(value)) properties[member] = member === "href" ? {
|
|
173
|
+
...properties["href"],
|
|
174
|
+
...value
|
|
175
|
+
} : value;
|
|
176
|
+
}
|
|
177
|
+
const out = {
|
|
178
|
+
type: "object",
|
|
179
|
+
properties,
|
|
180
|
+
required: ["href", ...(Array.isArray(object?.["required"]) ? object["required"] : []).filter((name) => name !== "href" && name in properties)]
|
|
181
|
+
};
|
|
182
|
+
if (typeof resolved["description"] === "string") out["description"] = resolved["description"];
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The names of the links each type names, for the types that name any. Throws when such a type
|
|
187
|
+
* also has an attribute or relationship called `links`, which the flattened resource couldn't
|
|
188
|
+
* hold beside them.
|
|
189
|
+
*/
|
|
190
|
+
function resourceLinkNames(doc, descriptor, schemas) {
|
|
191
|
+
const out = {};
|
|
192
|
+
for (const [type, resource] of Object.entries(descriptor)) {
|
|
193
|
+
const names = namedLinkSchemas(doc, schemas.get(type)).map(([name]) => name);
|
|
194
|
+
if (names.length === 0) continue;
|
|
195
|
+
if ("links" in resource.attributes || "links" in resource.relations) throw new Error(`The "${type}" type names links (${names.join(", ")}) and also has a field called "links". A flattened resource carries its links under "links", so rename the field.`);
|
|
196
|
+
out[type] = names;
|
|
197
|
+
}
|
|
198
|
+
return out;
|
|
199
|
+
}
|
|
200
|
+
/** One type's flattened resource schema: id, type, attributes, relations and named links, all optional but id and type. */
|
|
127
201
|
function flatResource(doc, descriptor, schemas, type, expanded) {
|
|
128
202
|
const resource = schemas.get(type);
|
|
129
203
|
const attributes = outputSchema(doc, (isObject(resource?.["properties"]) ? resource["properties"] : {})["attributes"]);
|
|
@@ -161,6 +235,12 @@ function flatResource(doc, descriptor, schemas, type, expanded) {
|
|
|
161
235
|
description: `The related ${target}: ${polymorphic ? "its type and id" : "its id"}, or the resource itself when included.`
|
|
162
236
|
};
|
|
163
237
|
}
|
|
238
|
+
const links = namedLinkSchemas(doc, resource);
|
|
239
|
+
if (links.length > 0) properties["links"] = {
|
|
240
|
+
type: "object",
|
|
241
|
+
description: "The links the resource names, such as an upload or download URL or a custom action. A link that does not apply to this resource is absent.",
|
|
242
|
+
properties: Object.fromEntries(links.map(([name, link]) => [name, linkOutput(doc, link)]))
|
|
243
|
+
};
|
|
164
244
|
const schema = {
|
|
165
245
|
type: "object",
|
|
166
246
|
properties,
|
|
@@ -195,65 +275,96 @@ const LINKS = {
|
|
|
195
275
|
last: { type: "string" }
|
|
196
276
|
}
|
|
197
277
|
};
|
|
198
|
-
/**
|
|
278
|
+
/** The document `meta` member of an envelope: what the response declares, when it sends any. */
|
|
279
|
+
function documentMeta(ctx, description) {
|
|
280
|
+
return {
|
|
281
|
+
...responseMeta(ctx),
|
|
282
|
+
description
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/** A collection: `{ data, links, meta? }`. */
|
|
199
286
|
function collectionOutput(ctx, types) {
|
|
200
287
|
const reachable = reachableTypes(ctx.descriptor, types, ctx.includes);
|
|
201
288
|
return {
|
|
202
289
|
type: "object",
|
|
203
290
|
properties: {
|
|
204
|
-
|
|
291
|
+
data: {
|
|
205
292
|
type: "array",
|
|
206
|
-
items: types.length === 1 ? ref(types[0]) : { anyOf: types.map(ref) }
|
|
293
|
+
items: types.length === 1 ? ref(types[0]) : { anyOf: types.map(ref) },
|
|
294
|
+
description: "The resources on this page."
|
|
207
295
|
},
|
|
208
296
|
links: LINKS,
|
|
209
|
-
meta:
|
|
210
|
-
...responseMeta(ctx),
|
|
211
|
-
description: "The document meta, such as page totals."
|
|
212
|
-
}
|
|
297
|
+
meta: documentMeta(ctx, "The document meta, such as page totals.")
|
|
213
298
|
},
|
|
214
|
-
required: ["
|
|
299
|
+
required: ["data", "links"],
|
|
215
300
|
$defs: defsFor(ctx.doc, ctx.descriptor, ctx.schemas, reachable)
|
|
216
301
|
};
|
|
217
302
|
}
|
|
218
|
-
/**
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
303
|
+
/**
|
|
304
|
+
* One resource: `{ data, meta? }`. `data` is null where the operation may find nothing, such as
|
|
305
|
+
* a to-one related read with nothing related.
|
|
306
|
+
*/
|
|
307
|
+
function resourceOutput(ctx, types, nullable = false) {
|
|
308
|
+
const reachable = reachableTypes(ctx.descriptor, types, ctx.includes);
|
|
222
309
|
return {
|
|
223
|
-
|
|
224
|
-
|
|
310
|
+
type: "object",
|
|
311
|
+
properties: {
|
|
312
|
+
data: nullable !== false ? {
|
|
313
|
+
anyOf: [...types.map(ref), { type: "null" }],
|
|
314
|
+
description: nullable
|
|
315
|
+
} : types.length === 1 ? {
|
|
316
|
+
...ref(types[0]),
|
|
317
|
+
description: "The resource."
|
|
318
|
+
} : {
|
|
319
|
+
anyOf: types.map(ref),
|
|
320
|
+
description: "The resource."
|
|
321
|
+
},
|
|
322
|
+
meta: documentMeta(ctx, "The document meta, when the API sends any.")
|
|
323
|
+
},
|
|
324
|
+
required: ["data"],
|
|
325
|
+
$defs: defsFor(ctx.doc, ctx.descriptor, ctx.schemas, reachable)
|
|
225
326
|
};
|
|
226
327
|
}
|
|
227
|
-
/** A
|
|
228
|
-
function
|
|
229
|
-
const reachable = reachableTypes(ctx.descriptor, types, ctx.includes);
|
|
328
|
+
/** A delete: `{ data: null, meta? }`. */
|
|
329
|
+
function deletedOutput(ctx) {
|
|
230
330
|
return {
|
|
231
331
|
type: "object",
|
|
232
|
-
properties: {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
description: "The resource."
|
|
241
|
-
} },
|
|
242
|
-
required: ["item"],
|
|
243
|
-
$defs: defsFor(ctx.doc, ctx.descriptor, ctx.schemas, reachable)
|
|
332
|
+
properties: {
|
|
333
|
+
data: {
|
|
334
|
+
type: "null",
|
|
335
|
+
description: "Always null: the resource is gone."
|
|
336
|
+
},
|
|
337
|
+
meta: documentMeta(ctx, "The document meta, when the API sends any.")
|
|
338
|
+
},
|
|
339
|
+
required: ["data"]
|
|
244
340
|
};
|
|
245
341
|
}
|
|
246
|
-
/** A meta-only result: the document's `meta`. */
|
|
342
|
+
/** A meta-only result: `{ meta }`, the document's own `meta`, with no `data`. */
|
|
247
343
|
function metaOutput(ctx) {
|
|
248
344
|
return {
|
|
249
|
-
|
|
250
|
-
|
|
345
|
+
type: "object",
|
|
346
|
+
properties: { meta: {
|
|
347
|
+
...responseMeta(ctx),
|
|
348
|
+
type: "object",
|
|
349
|
+
description: "The document meta."
|
|
350
|
+
} },
|
|
351
|
+
required: ["meta"]
|
|
251
352
|
};
|
|
252
353
|
}
|
|
253
354
|
/** A JSON:API result whose primary type the descriptor doesn't name. */
|
|
254
|
-
const LOOSE_OUTPUT = {
|
|
355
|
+
const LOOSE_OUTPUT = {
|
|
356
|
+
type: "object",
|
|
357
|
+
properties: {
|
|
358
|
+
data: { description: "The flattened resource or resources." },
|
|
359
|
+
links: LINKS,
|
|
360
|
+
meta: {
|
|
361
|
+
type: "object",
|
|
362
|
+
description: "The document meta, when the API sends any."
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
};
|
|
255
366
|
/** The URL an action answered with (generator contract 17): `{ url, type, expiresAt? }`. */
|
|
256
|
-
function
|
|
367
|
+
function locationData(mediaType) {
|
|
257
368
|
return {
|
|
258
369
|
type: "object",
|
|
259
370
|
description: "The URL the action produced, such as a short-lived download link. The tool does not fetch it.",
|
|
@@ -277,6 +388,14 @@ function locationOutput(mediaType) {
|
|
|
277
388
|
additionalProperties: false
|
|
278
389
|
};
|
|
279
390
|
}
|
|
391
|
+
/** An action that answered with a URL: `{ data: { url, type, expiresAt? } }`. */
|
|
392
|
+
function locationOutput(mediaType) {
|
|
393
|
+
return {
|
|
394
|
+
type: "object",
|
|
395
|
+
properties: { data: locationData(mediaType) },
|
|
396
|
+
required: ["data"]
|
|
397
|
+
};
|
|
398
|
+
}
|
|
280
399
|
/** An action's usual output or the URL it may answer with instead, sharing the root `$defs`. */
|
|
281
400
|
function orLocation(schema, mediaType) {
|
|
282
401
|
const { $defs, ...rest } = schema;
|
|
@@ -1158,7 +1277,7 @@ function actionOutput(ctx, action) {
|
|
|
1158
1277
|
function documentOutput(ctx, action) {
|
|
1159
1278
|
switch (actionResult(action)) {
|
|
1160
1279
|
case "collection": return collectionOutput(ctx, [action.outputType]);
|
|
1161
|
-
case "resource": return resourceOutput(ctx, action.outputType);
|
|
1280
|
+
case "resource": return resourceOutput(ctx, [action.outputType], "The resource the action returned.");
|
|
1162
1281
|
case "meta": return metaOutput(ctx);
|
|
1163
1282
|
case "loose": return LOOSE_OUTPUT;
|
|
1164
1283
|
default: return;
|
|
@@ -1169,6 +1288,7 @@ function buildCatalogue(doc, descriptor, defaultPageSize, includeDepth = null) {
|
|
|
1169
1288
|
const entries = [];
|
|
1170
1289
|
const skipped = [];
|
|
1171
1290
|
const schemas = resourceSchemas(doc);
|
|
1291
|
+
const resourceLinks = resourceLinkNames(doc, descriptor, schemas);
|
|
1172
1292
|
for (const [path, item] of Object.entries(doc.paths ?? {})) for (const method of HTTP_METHODS) {
|
|
1173
1293
|
const operation = item[method];
|
|
1174
1294
|
const operationId = operation?.operationId;
|
|
@@ -1223,7 +1343,8 @@ function buildCatalogue(doc, descriptor, defaultPageSize, includeDepth = null) {
|
|
|
1223
1343
|
entries.sort((a, b) => a.tool.name.localeCompare(b.tool.name));
|
|
1224
1344
|
return {
|
|
1225
1345
|
entries,
|
|
1226
|
-
skipped
|
|
1346
|
+
skipped,
|
|
1347
|
+
resourceLinks
|
|
1227
1348
|
};
|
|
1228
1349
|
}
|
|
1229
1350
|
function buildEntry(op, parsed, inputCtx, outputCtx, descriptor) {
|
|
@@ -1239,21 +1360,22 @@ function buildEntry(op, parsed, inputCtx, outputCtx, descriptor) {
|
|
|
1239
1360
|
break;
|
|
1240
1361
|
case "get":
|
|
1241
1362
|
input = singleInput(inputCtx, `The id of the \`${op.type}\` resource to get.`);
|
|
1242
|
-
output =
|
|
1363
|
+
output = resourceOutput(outputCtx, [op.type]);
|
|
1243
1364
|
result = "item";
|
|
1244
1365
|
break;
|
|
1245
1366
|
case "create":
|
|
1246
1367
|
input = createInput(inputCtx, op.type);
|
|
1247
|
-
output = resourceOutput(outputCtx, op.type);
|
|
1368
|
+
output = resourceOutput(outputCtx, [op.type]);
|
|
1248
1369
|
result = "resource";
|
|
1249
1370
|
break;
|
|
1250
1371
|
case "update":
|
|
1251
1372
|
input = updateInput(inputCtx, op.type);
|
|
1252
|
-
output = resourceOutput(outputCtx, op.type);
|
|
1373
|
+
output = resourceOutput(outputCtx, [op.type]);
|
|
1253
1374
|
result = "resource";
|
|
1254
1375
|
break;
|
|
1255
1376
|
case "delete":
|
|
1256
1377
|
input = deleteInput(op.type);
|
|
1378
|
+
output = deletedOutput(outputCtx);
|
|
1257
1379
|
result = "deleted";
|
|
1258
1380
|
break;
|
|
1259
1381
|
case "related": {
|
|
@@ -1270,7 +1392,7 @@ function buildEntry(op, parsed, inputCtx, outputCtx, descriptor) {
|
|
|
1270
1392
|
result = "collection";
|
|
1271
1393
|
} else {
|
|
1272
1394
|
input = singleInput(inputCtx, `The id of the \`${op.type}\` resource whose ${parsed.relation} to get.`);
|
|
1273
|
-
output =
|
|
1395
|
+
output = resourceOutput(outputCtx, relation.types, "The related resource, or null when there is none.");
|
|
1274
1396
|
result = "item";
|
|
1275
1397
|
}
|
|
1276
1398
|
break;
|
|
@@ -1323,9 +1445,9 @@ function buildEntry(op, parsed, inputCtx, outputCtx, descriptor) {
|
|
|
1323
1445
|
*/
|
|
1324
1446
|
function assertContract(doc) {
|
|
1325
1447
|
const contract = doc.info?.["x-generator"]?.contract;
|
|
1326
|
-
if (typeof contract !== "number") throw new Error(`The OpenAPI document declares no generator contract (info.x-generator.contract). @haddowg/json-api-mcp reads contracts 13 to
|
|
1327
|
-
if (contract < 13) throw new Error(`The OpenAPI document declares generator contract ${contract}, older than this package reads (13 to
|
|
1328
|
-
if (contract >
|
|
1448
|
+
if (typeof contract !== "number") throw new Error(`The OpenAPI document declares no generator contract (info.x-generator.contract). @haddowg/json-api-mcp reads contracts 13 to 19; regenerate the document from a newer haddowg/json-api server.`);
|
|
1449
|
+
if (contract < 13) throw new Error(`The OpenAPI document declares generator contract ${contract}, older than this package reads (13 to 19). Regenerate it from a newer haddowg/json-api server.`);
|
|
1450
|
+
if (contract > 19) throw new Error(`The OpenAPI document declares generator contract ${contract}, newer than this package reads (13 to 19). Upgrade @haddowg/json-api-mcp.`);
|
|
1329
1451
|
return contract;
|
|
1330
1452
|
}
|
|
1331
1453
|
//#endregion
|
|
@@ -1367,7 +1489,7 @@ function buildMcpModel(document, descriptor, options) {
|
|
|
1367
1489
|
const catalogue = buildCatalogue(document, descriptor, options.defaultPageSize ?? 10, includeDepth);
|
|
1368
1490
|
const info = document.info;
|
|
1369
1491
|
const model = {
|
|
1370
|
-
format:
|
|
1492
|
+
format: 4,
|
|
1371
1493
|
generator: MODEL_GENERATOR,
|
|
1372
1494
|
contract,
|
|
1373
1495
|
source: {
|
|
@@ -1376,6 +1498,7 @@ function buildMcpModel(document, descriptor, options) {
|
|
|
1376
1498
|
},
|
|
1377
1499
|
credentialSchemes: oauthSchemes(document),
|
|
1378
1500
|
includeDepth,
|
|
1501
|
+
resourceLinks: catalogue.resourceLinks,
|
|
1379
1502
|
descriptor,
|
|
1380
1503
|
tools: catalogue.entries.map((entry) => {
|
|
1381
1504
|
const { op } = entry;
|
|
@@ -1471,4 +1594,4 @@ async function writeMcpModel(options) {
|
|
|
1471
1594
|
//#endregion
|
|
1472
1595
|
export { buildMcpModel as i, serialiseMcpModel as n, writeMcpModel as r, generateMcpModel as t };
|
|
1473
1596
|
|
|
1474
|
-
//# sourceMappingURL=generate-
|
|
1597
|
+
//# sourceMappingURL=generate-yf5Kc4Yh.mjs.map
|