@haddowg/json-api-mcp 1.3.0 → 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 +8 -4
- package/dist/cli.mjs +1 -1
- package/dist/{generate-BcqHRSxB.mjs → generate-yf5Kc4Yh.mjs} +4 -4
- package/dist/{generate-BcqHRSxB.mjs.map → generate-yf5Kc4Yh.mjs.map} +1 -1
- package/dist/generate.mjs +1 -1
- package/dist/index.cjs +5 -5
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +5 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/contract.ts","../src/openapi.ts","../src/portable.ts","../src/inputs.ts","../src/flatten.ts","../src/call.ts","../src/model.ts","../src/scopes.ts","../src/mcp.ts","../src/names.ts","../src/safety.ts"],"sourcesContent":["import type { OpenApiDocument } from './openapi'\n\n/** The oldest generator contract this package reads: the baseline current servers stamp. */\nexport const MINIMUM_CONTRACT = 13\n\n/** The newest generator contract this package reads. */\nexport const MAXIMUM_CONTRACT = 18\n\n/**\n * Throw unless the document's `info.x-generator.contract` is in the range this package reads. A\n * document outside it would yield tools that miss or misread what the server offers, so there is\n * no warning mode.\n */\nexport function assertContract(doc: OpenApiDocument): number {\n const contract = doc.info?.['x-generator']?.contract\n if (typeof contract !== 'number') {\n throw new Error(\n `The OpenAPI document declares no generator contract (info.x-generator.contract). @haddowg/json-api-mcp reads contracts ${MINIMUM_CONTRACT} to ${MAXIMUM_CONTRACT}; regenerate the document from a newer haddowg/json-api server.`,\n )\n }\n if (contract < MINIMUM_CONTRACT) {\n throw new Error(\n `The OpenAPI document declares generator contract ${contract}, older than this package reads (${MINIMUM_CONTRACT} to ${MAXIMUM_CONTRACT}). Regenerate it from a newer haddowg/json-api server.`,\n )\n }\n if (contract > MAXIMUM_CONTRACT) {\n throw new Error(\n `The OpenAPI document declares generator contract ${contract}, newer than this package reads (${MINIMUM_CONTRACT} to ${MAXIMUM_CONTRACT}). Upgrade @haddowg/json-api-mcp.`,\n )\n }\n return contract\n}\n","/**\n * The parts of an OpenAPI 3.1 document this package reads, typed loosely: every object keeps its\n * other members, vendor extensions included, so hooks see the operation as the server wrote it.\n */\n\n/** A JSON Schema node as the document writes it. */\nexport type JsonSchema = { [key: string]: unknown }\n\n/** One security requirement: scheme name to the scopes it needs. */\nexport type SecurityRequirement = Readonly<Record<string, readonly string[]>>\n\nexport interface ParameterObject {\n name: string\n in: string\n description?: string\n required?: boolean\n schema?: JsonSchema\n [key: string]: unknown\n}\n\nexport interface MediaTypeObject {\n schema?: JsonSchema\n [key: string]: unknown\n}\n\nexport interface RequestBodyObject {\n description?: string\n required?: boolean\n content?: Record<string, MediaTypeObject>\n [key: string]: unknown\n}\n\nexport interface ResponseObject {\n description?: string\n content?: Record<string, MediaTypeObject>\n [key: string]: unknown\n}\n\nexport interface OperationObject {\n operationId?: string\n summary?: string\n description?: string\n parameters?: (ParameterObject | { $ref: string })[]\n requestBody?: RequestBodyObject | { $ref: string }\n responses?: Record<string, ResponseObject | { $ref: string }>\n security?: SecurityRequirement[]\n [key: string]: unknown\n}\n\nexport type HttpMethod = 'get' | 'post' | 'patch' | 'put' | 'delete'\n\nexport const HTTP_METHODS: readonly HttpMethod[] = ['get', 'post', 'patch', 'put', 'delete']\n\nexport type PathItemObject = Partial<Record<HttpMethod, OperationObject>> & {\n parameters?: (ParameterObject | { $ref: string })[]\n [key: string]: unknown\n}\n\nexport interface OpenApiDocument {\n openapi?: string\n info?: { 'x-generator'?: { contract?: unknown; [key: string]: unknown }; [key: string]: unknown }\n servers?: { url: string; [key: string]: unknown }[]\n paths?: Record<string, PathItemObject>\n components?: {\n schemas?: Record<string, JsonSchema>\n parameters?: Record<string, ParameterObject>\n requestBodies?: Record<string, RequestBodyObject>\n responses?: Record<string, ResponseObject>\n [key: string]: unknown\n }\n security?: SecurityRequirement[]\n [key: string]: unknown\n}\n\nexport const isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/** Follow a local `#/...` reference. `undefined` for anything else or a dangling one. */\nexport function resolvePointer(doc: OpenApiDocument, ref: string): unknown {\n if (!ref.startsWith('#/')) {\n return undefined\n }\n let node: unknown = doc\n for (const raw of ref.slice(2).split('/')) {\n const segment = decodeURIComponent(raw).replace(/~1/g, '/').replace(/~0/g, '~')\n if (!isObject(node) || !Object.hasOwn(node, segment)) {\n return undefined\n }\n node = node[segment]\n }\n return node\n}\n\n/** Follow `$ref`s until a node that isn't one. A cycle or dangling ref gives `undefined`. */\nexport function deref<T>(\n doc: OpenApiDocument,\n value: T | { $ref: string } | undefined,\n): T | undefined {\n let node: unknown = value\n const seen = new Set<string>()\n while (isObject(node) && typeof node['$ref'] === 'string') {\n const ref = node['$ref']\n if (seen.has(ref)) {\n return undefined\n }\n seen.add(ref)\n node = resolvePointer(doc, ref)\n }\n return node as T | undefined\n}\n\n/** An operation's parameters, path-level ones included, with refs followed. */\nexport function operationParameters(\n doc: OpenApiDocument,\n item: PathItemObject,\n op: OperationObject,\n): ParameterObject[] {\n const out = new Map<string, ParameterObject>()\n for (const raw of [...(item.parameters ?? []), ...(op.parameters ?? [])]) {\n const param = deref<ParameterObject>(doc, raw)\n if (param !== undefined && typeof param.name === 'string') {\n out.set(`${param.in}:${param.name}`, param)\n }\n }\n return [...out.values()]\n}\n","/**\n * Convert document schemas into tool input schemas every major model API accepts: OpenAI's strict\n * function calling and Anthropic's strict tool use. That means no `oneOf`, `allOf`, `if`, `then`\n * or `$ref`, every object closed with `additionalProperties: false` and listing all its\n * properties as required, an optional property made nullable instead, and no numeric, length or\n * pattern keywords. Constraints that can't stay as keywords move into the description, and a\n * value with no portable shape, such as a free-form object, is taken as JSON text.\n *\n * Each schema comes with a {@link Decoder} that turns the arguments a model sends back into the\n * value the API expects: a `null` the conversion added is dropped, and JSON text is parsed.\n */\nimport { isObject, type JsonSchema, type OpenApiDocument, resolvePointer } from './openapi'\n\n/** The string formats both APIs accept. */\nexport const PORTABLE_FORMATS: ReadonlySet<string> = new Set([\n 'date-time',\n 'time',\n 'date',\n 'duration',\n 'email',\n 'hostname',\n 'ipv4',\n 'ipv6',\n 'uuid',\n])\n\n/** Objects may nest this deep before a value is taken as JSON text instead. */\nconst MAX_DEPTH = 6\n\nexport type Decoder =\n | { kind: 'value' }\n | { kind: 'json' }\n | { kind: 'object'; props: Readonly<Record<string, PropertyDecoder>> }\n | { kind: 'array'; items: Decoder }\n\nexport interface PropertyDecoder {\n decoder: Decoder\n /** The conversion made this property nullable, so `null` means \"not given\". */\n nullMeansOmit: boolean\n}\n\nexport interface Portable {\n schema: JsonSchema\n decoder: Decoder\n}\n\nconst VALUE: Decoder = { kind: 'value' }\n\n/** Thrown when a model's argument can't be decoded, such as JSON text that doesn't parse. */\nexport class ArgumentError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ArgumentError'\n }\n}\n\n/** Turn a model's argument back into the value the API expects. */\nexport function decode(value: unknown, decoder: Decoder, path = 'value'): unknown {\n if (value === null || value === undefined) {\n return value\n }\n switch (decoder.kind) {\n case 'json':\n if (typeof value !== 'string') {\n return value\n }\n try {\n return JSON.parse(value) as unknown\n } catch {\n throw new ArgumentError(`${path} must be JSON text`)\n }\n case 'array':\n return Array.isArray(value)\n ? value.map((item, index) => decode(item, decoder.items, `${path}[${index}]`))\n : value\n case 'object': {\n if (!isObject(value)) {\n return value\n }\n const out: Record<string, unknown> = {}\n for (const [key, member] of Object.entries(value)) {\n const prop = decoder.props[key]\n if (prop === undefined) {\n out[key] = member\n } else if (!(member === null && prop.nullMeansOmit)) {\n out[key] = decode(member, prop.decoder, `${path}.${key}`)\n }\n }\n return out\n }\n default:\n return value\n }\n}\n\n/** Add `null` to a portable schema's allowed values. */\nexport function nullable(schema: JsonSchema): JsonSchema {\n const out = { ...schema }\n if (Array.isArray(out['anyOf'])) {\n const branches = out['anyOf'] as JsonSchema[]\n if (!branches.some((b) => b['type'] === 'null')) {\n out['anyOf'] = [...branches, { type: 'null' }]\n }\n return out\n }\n const type = out['type']\n if (typeof type === 'string' && type !== 'null') {\n out['type'] = [type, 'null']\n } else if (Array.isArray(type) && !type.includes('null')) {\n out['type'] = [...(type as string[]), 'null']\n }\n if (Array.isArray(out['enum']) && !(out['enum'] as unknown[]).includes(null)) {\n out['enum'] = [...(out['enum'] as unknown[]), null]\n }\n return out\n}\n\n/** Whether a portable schema already allows `null`. */\nexport function allowsNull(schema: JsonSchema): boolean {\n const type = schema['type']\n if (type === 'null' || (Array.isArray(type) && type.includes('null'))) {\n return true\n }\n return Array.isArray(schema['anyOf']) && (schema['anyOf'] as JsonSchema[]).some(allowsNull)\n}\n\n/** A closed object schema listing every property as required, as strict mode wants. */\nexport function closedObject(\n properties: Record<string, JsonSchema>,\n description?: string,\n): JsonSchema {\n const schema: JsonSchema = {\n type: 'object',\n properties,\n required: Object.keys(properties),\n additionalProperties: false,\n }\n if (description !== undefined && description !== '') {\n schema['description'] = description\n }\n return schema\n}\n\nconst jsonText = (description: string | undefined): Portable => ({\n schema: {\n type: 'string',\n description: joinText(description, 'Send the value as JSON text.'),\n },\n decoder: { kind: 'json' },\n})\n\nfunction joinText(...parts: (string | undefined)[]): string {\n return parts\n .filter((part): part is string => part !== undefined && part.trim() !== '')\n .map((part) => part.trim())\n .join(' ')\n}\n\n/** The non-null `type` keys a schema names. */\nfunction typeKeys(schema: JsonSchema): { types: string[]; nullable: boolean } {\n const raw = schema['type']\n const list = typeof raw === 'string' ? [raw] : Array.isArray(raw) ? (raw as string[]) : []\n return { types: list.filter((t) => t !== 'null'), nullable: list.includes('null') }\n}\n\nfunction inferType(values: readonly unknown[]): string | undefined {\n const types = new Set(\n values\n .filter((v) => v !== null)\n .map((v) =>\n typeof v === 'number' ? (Number.isInteger(v) ? 'integer' : 'number') : typeof v,\n ),\n )\n if (types.size === 1) {\n return [...types][0]\n }\n if (types.size === 2 && types.has('integer') && types.has('number')) {\n return 'number'\n }\n return undefined\n}\n\n/** A range as a sentence, such as `Between 1 and 200 characters.` */\nfunction bounds(\n min: number | undefined,\n max: number | undefined,\n unit: string,\n): string | undefined {\n const suffix = unit === '' ? '' : ` ${unit}`\n if (min !== undefined && max !== undefined) return `Between ${min} and ${max}${suffix}.`\n if (min !== undefined) return `At least ${min}${suffix}.`\n if (max !== undefined) return `At most ${max}${suffix}.`\n return undefined\n}\n\n/** The constraints a schema states that can't stay as keywords, as sentences. */\nfunction constraintNote(schema: JsonSchema): string | undefined {\n const num = (key: string): number | undefined =>\n typeof schema[key] === 'number' ? schema[key] : undefined\n const notes = [\n bounds(num('minLength'), num('maxLength'), 'characters'),\n bounds(num('minimum'), num('maximum'), ''),\n num('exclusiveMinimum') === undefined ? undefined : `Greater than ${num('exclusiveMinimum')}.`,\n num('exclusiveMaximum') === undefined ? undefined : `Less than ${num('exclusiveMaximum')}.`,\n num('multipleOf') === undefined ? undefined : `A multiple of ${num('multipleOf')}.`,\n bounds(num('minItems'), num('maxItems'), 'items'),\n typeof schema['pattern'] === 'string'\n ? `Matches the pattern \\`${schema['pattern']}\\`.`\n : undefined,\n typeof schema['format'] === 'string' && !PORTABLE_FORMATS.has(schema['format'])\n ? `Format: ${schema['format']}.`\n : undefined,\n schema['default'] === undefined\n ? undefined\n : `Defaults to ${JSON.stringify(schema['default'])}.`,\n ].filter((note): note is string => note !== undefined)\n return notes.length === 0 ? undefined : notes.join(' ')\n}\n\n/** Merge an `allOf` into one schema: the members' keywords, then the outer node's own. */\nfunction mergeAllOf(\n doc: OpenApiDocument,\n node: JsonSchema,\n stack: readonly string[],\n): JsonSchema | undefined {\n const members = node['allOf'] as unknown[]\n const merged: JsonSchema = {}\n const properties: Record<string, unknown> = {}\n const required = new Set<string>()\n for (const member of members) {\n const resolved = follow(doc, member, stack)\n if (resolved === undefined) {\n return undefined\n }\n const inner = Array.isArray(resolved.node['allOf'])\n ? mergeAllOf(doc, resolved.node, resolved.stack)\n : resolved.node\n if (inner === undefined) {\n return undefined\n }\n Object.assign(properties, isObject(inner['properties']) ? inner['properties'] : {})\n for (const key of Array.isArray(inner['required']) ? (inner['required'] as string[]) : []) {\n required.add(key)\n }\n Object.assign(merged, inner)\n }\n const { allOf: _allOf, ...own } = node\n Object.assign(merged, own)\n if (Object.keys(properties).length > 0) {\n merged['properties'] = {\n ...properties,\n ...(isObject(own['properties']) ? own['properties'] : {}),\n }\n }\n if (required.size > 0 || Array.isArray(own['required'])) {\n merged['required'] = [\n ...required,\n ...(Array.isArray(own['required']) ? (own['required'] as string[]) : []),\n ]\n }\n return merged\n}\n\n/**\n * Follow a node's `$ref`s, keeping the outermost description. `undefined` on a cycle, which the\n * caller then takes as JSON text.\n */\nfunction follow(\n doc: OpenApiDocument,\n value: unknown,\n stack: readonly string[],\n): { node: JsonSchema; stack: readonly string[] } | undefined {\n let node: unknown = value\n let description: string | undefined\n let path = stack\n while (isObject(node) && typeof node['$ref'] === 'string') {\n const ref = node['$ref']\n if (description === undefined && typeof node['description'] === 'string') {\n description = node['description']\n }\n if (path.includes(ref)) {\n return undefined\n }\n path = [...path, ref]\n node = resolvePointer(doc, ref)\n }\n if (node === true) {\n return { node: {}, stack: path }\n }\n if (!isObject(node)) {\n return undefined\n }\n return {\n node: description === undefined ? node : { ...node, description },\n stack: path,\n }\n}\n\nconst isNull = (b: unknown): boolean => isObject(b) && b['type'] === 'null'\n\n/** Convert one document schema into a portable input schema and its decoder. */\nexport function toPortable(\n doc: OpenApiDocument,\n raw: unknown,\n stack: readonly string[] = [],\n depth = 0,\n): Portable {\n const outerDescription =\n isObject(raw) && typeof raw['description'] === 'string' ? raw['description'] : undefined\n const followed = follow(doc, raw, stack)\n if (followed === undefined) {\n return jsonText(outerDescription)\n }\n let node = followed.node\n const path = followed.stack\n if (Array.isArray(node['allOf'])) {\n const merged = mergeAllOf(doc, node, path)\n if (merged === undefined) {\n return jsonText(outerDescription)\n }\n node = merged\n }\n const description = typeof node['description'] === 'string' ? node['description'] : undefined\n\n const branches = (node['oneOf'] ?? node['anyOf']) as unknown[] | undefined\n if (Array.isArray(branches)) {\n const { oneOf: _oneOf, anyOf: _anyOf, ...rest } = node\n const others = branches.filter((b) => !isNull(b))\n const withNull = others.length < branches.length\n if (others.length === 1) {\n const resolved = follow(doc, others[0], path)\n if (resolved === undefined) {\n return jsonText(description)\n }\n const single = toPortable(doc, { ...resolved.node, ...rest }, resolved.stack, depth)\n return withNull ? { ...single, schema: nullable(single.schema) } : single\n }\n if (depth >= MAX_DEPTH) {\n return jsonText(description)\n }\n const converted = others.map((b) => toPortable(doc, b, path, depth + 1).schema)\n const schema: JsonSchema = { anyOf: withNull ? [...converted, { type: 'null' }] : converted }\n const text = joinText(description, constraintNote(node))\n if (text !== '') {\n schema['description'] = text\n }\n return { schema, decoder: VALUE }\n }\n\n let { types, nullable: isNullable } = typeKeys(node)\n const enumValues = Array.isArray(node['enum'])\n ? (node['enum'] as unknown[])\n : node['const'] !== undefined\n ? [node['const']]\n : undefined\n if (enumValues !== undefined && enumValues.includes(null)) {\n isNullable = true\n }\n if (types.length === 0) {\n const inferred =\n enumValues !== undefined\n ? inferType(enumValues)\n : isObject(node['properties'])\n ? 'object'\n : node['items'] !== undefined\n ? 'array'\n : undefined\n if (inferred === undefined) {\n return jsonText(description)\n }\n types = [inferred]\n }\n if (types.length > 1) {\n const schema: JsonSchema = {\n anyOf: [...types.map((type) => ({ type })), ...(isNullable ? [{ type: 'null' }] : [])],\n }\n if (description !== undefined) {\n schema['description'] = description\n }\n return { schema, decoder: VALUE }\n }\n\n const type = types[0]!\n const text = joinText(description, constraintNote(node))\n let result: Portable\n\n if (type === 'object') {\n const props = isObject(node['properties']) ? node['properties'] : {}\n const writable = Object.entries(props).filter(\n ([, prop]) => !(isObject(prop) && prop['readOnly'] === true),\n )\n if (writable.length === 0 || depth >= MAX_DEPTH) {\n return jsonText(text)\n }\n const required = new Set(Array.isArray(node['required']) ? (node['required'] as string[]) : [])\n const properties: Record<string, JsonSchema> = {}\n const decoders: Record<string, PropertyDecoder> = {}\n for (const [key, prop] of writable) {\n const converted = toPortable(doc, prop, path, depth + 1)\n const optional = !required.has(key)\n const addNull = optional && !allowsNull(converted.schema)\n properties[key] = addNull ? nullable(converted.schema) : converted.schema\n decoders[key] = { decoder: converted.decoder, nullMeansOmit: addNull }\n }\n result = {\n schema: closedObject(properties, text),\n decoder: { kind: 'object', props: decoders },\n }\n } else if (type === 'array') {\n if (node['items'] === undefined || node['items'] === true || depth >= MAX_DEPTH) {\n return jsonText(text)\n }\n const items = toPortable(doc, node['items'], path, depth + 1)\n result = {\n schema: { type: 'array', items: items.schema },\n decoder: { kind: 'array', items: items.decoder },\n }\n if (text !== '') {\n result.schema['description'] = text\n }\n } else {\n const schema: JsonSchema = { type }\n if (text !== '') {\n schema['description'] = text\n }\n if (enumValues !== undefined) {\n schema['enum'] = enumValues.filter((v) => v !== null)\n }\n if (\n type === 'string' &&\n typeof node['format'] === 'string' &&\n PORTABLE_FORMATS.has(node['format'])\n ) {\n schema['format'] = node['format']\n }\n result = { schema, decoder: VALUE }\n }\n\n return isNullable ? { ...result, schema: nullable(result.schema) } : result\n}\n","/**\n * Each tool's input schema, and the plan the call handler follows to turn a model's arguments\n * back into a client call. Every schema is portable (see ./portable.ts): a parameter the API\n * doesn't require is nullable, and `null` at the top level means \"not given\".\n */\nimport {\n type ActionDescriptor,\n type ApiDescriptor,\n type CountableDescriptor,\n isPolymorphic,\n type PaginatorDescriptor,\n type RelationDescriptor,\n resolvePaginator,\n} from '@haddowg/json-api-client'\nimport {\n deref,\n isObject,\n type JsonSchema,\n type OpenApiDocument,\n type OperationObject,\n type ParameterObject,\n type RequestBodyObject,\n} from './openapi'\nimport { capIncludePaths, includePaths } from './outputs'\nimport {\n allowsNull,\n closedObject,\n type Decoder,\n nullable,\n type PropertyDecoder,\n toPortable,\n} from './portable'\n\n/** The include description, for a tool capped at `depth` relationships or uncapped. */\nexport function includeDescription(depth: number | null): string {\n const reach =\n depth === null\n ? ''\n : depth === 1\n ? ' Only direct relationships can be included, so relations of an included resource come back as ids.'\n : ` Paths reach at most ${depth} relationships deep, and relations below that come back as ids.`\n return `Relationship paths to include in the result (JSON:API compound documents). An included relation comes back as the related resource in place of its id.${reach} Leave null to include nothing.`\n}\n\nexport const FIELDS_DESCRIPTION =\n 'Sparse fieldsets (JSON:API): per resource type, the attribute and relationship names to return. A relationship only has a value in the result when it is also included. Leave a type null for its default fields.'\n\n/** The Countable profile's token for counting the collection itself. */\nexport const SELF_COUNT = '_self_'\n\nexport const WITH_COUNT_DESCRIPTION =\n 'Set true to have the total number of results counted and returned in meta. Counting can be slow on a large collection, so leave it null unless the total is needed.'\n\nexport const PAGE_LINK_DESCRIPTION =\n 'A pagination link (first, prev, next or last) copied from the links of an earlier result of this same tool, to fetch that page. When given, every other argument is ignored.'\n\n/** How the handler reads a read tool's query arguments. */\nexport interface QueryPlan {\n filter?: Decoder\n sort: boolean\n include: boolean\n /**\n * The include paths the tool accepts, the ones its input lists. A model before format 3\n * doesn't carry them and its tools accept any path.\n */\n includePaths?: readonly string[]\n fields: readonly string[]\n /** The wire key `pageSize` maps to, when the endpoint paginates by a size. */\n sizeKey?: string\n maxPageSize?: number\n defaultPageSize?: number\n /** Whether `pageLink` is offered: the endpoint paginates at all. */\n paginated: boolean\n /**\n * Whether `withCount` is offered: the endpoint counts its own collection under the Countable\n * profile, sent as `withCount=_self_` with this profile negotiated.\n */\n countProfile?: string\n}\n\n/** One top-level write field: an attribute, or a relation sent as ids. */\nexport interface WriteField {\n relation?: { cardinality: 'one' | 'many'; types: readonly string[]; polymorphic?: boolean }\n property: PropertyDecoder\n}\n\n/** How the handler reads a create, update or flat document action's arguments. */\nexport interface WritePlan {\n fields: Readonly<Record<string, WriteField>>\n /** The client id argument: whether the body takes one. */\n clientId: 'none' | 'optional' | 'required'\n /** Fields `clear` may name, for an update. */\n clearable: readonly string[]\n}\n\n/** How the handler reads a custom action's body. */\nexport type ActionBodyPlan =\n | { kind: 'none' }\n | { kind: 'flat'; write: WritePlan }\n | {\n kind: 'body'\n decoder: Decoder\n required: boolean\n /** The request media type the body goes out as. */\n mediaType: string\n mediaKind: 'document' | 'json' | 'text' | 'form'\n }\n\nexport interface ToolInput {\n schema: JsonSchema & { type: 'object' }\n query?: QueryPlan\n write?: WritePlan\n action?: ActionBodyPlan\n}\n\nconst nullableString = (description: string): JsonSchema =>\n nullable({ type: 'string', description })\n\nconst idSchema = (description: string): JsonSchema => ({ type: 'string', description })\n\n/** The `filter[...]` parameters as one closed object, each member nullable. */\nfunction filterInput(\n doc: OpenApiDocument,\n params: readonly ParameterObject[],\n): { schema: JsonSchema; decoder: Decoder } | undefined {\n const properties: Record<string, JsonSchema> = {}\n const props: Record<string, PropertyDecoder> = {}\n for (const param of params) {\n const match = /^filter\\[(.+)\\]$/.exec(param.name)\n if (param.in !== 'query' || match === null) {\n continue\n }\n const converted = toPortable(doc, param.schema ?? {}, [], 1)\n const schema = { ...converted.schema }\n if (typeof param.description === 'string' && schema['description'] === undefined) {\n schema['description'] = param.description\n } else if (typeof param.description === 'string') {\n schema['description'] = `${param.description} ${String(schema['description'])}`\n }\n const addNull = !allowsNull(schema)\n properties[match[1]!] = addNull ? nullable(schema) : schema\n props[match[1]!] = { decoder: converted.decoder, nullMeansOmit: true }\n }\n if (Object.keys(properties).length === 0) {\n return undefined\n }\n return {\n schema: nullable(\n closedObject(\n properties,\n 'Filters to apply. Leave a filter null to not apply it, or the whole object null for none.',\n ),\n ),\n decoder: { kind: 'object', props },\n }\n}\n\n/** The page parameter's size bound and default for the strategy that answers by default. */\nfunction pageBounds(\n doc: OpenApiDocument,\n params: readonly ParameterObject[],\n kind: string,\n sizeKey: string,\n): { max?: number; default?: number } {\n const param = params.find((p) => p.in === 'query' && p.name === 'page')\n let schema = deref<JsonSchema>(doc, param?.schema)\n const branches = schema?.['oneOf'] ?? schema?.['anyOf']\n if (Array.isArray(branches)) {\n schema = branches\n .map((b) => deref<JsonSchema>(doc, b as JsonSchema))\n .find((b) => isObject(b?.['x-paginator']) && b['x-paginator']['strategy'] === kind)\n }\n const size = isObject(schema?.['properties']) ? schema['properties'][sizeKey] : undefined\n if (!isObject(size)) {\n return {}\n }\n const out: { max?: number; default?: number } = {}\n if (typeof size['maximum'] === 'number') out.max = size['maximum']\n if (typeof size['default'] === 'number') out.default = size['default']\n return out\n}\n\n/** A read tool's query arguments, read from the operation's own parameters. */\nfunction queryInput(\n ctx: InputContext,\n paginator: PaginatorDescriptor | undefined,\n collection: boolean,\n): { properties: Record<string, JsonSchema>; plan: QueryPlan } {\n const { doc, params, defaultPageSize, includeDepth } = ctx\n const properties: Record<string, JsonSchema> = {}\n const plan: QueryPlan = { sort: false, include: false, fields: [], paginated: false }\n\n if (collection) {\n const filter = filterInput(doc, params)\n if (filter !== undefined) {\n properties['filter'] = filter.schema\n plan.filter = filter.decoder\n }\n const sort = params.find((p) => p.in === 'query' && p.name === 'sort')\n const sortItems = deref<JsonSchema>(doc, sort?.schema?.['items'] as JsonSchema | undefined)\n const tokens = Array.isArray(sortItems?.['enum']) ? (sortItems['enum'] as string[]) : []\n if (tokens.length > 0) {\n properties['sort'] = nullable({\n type: 'array',\n items: { type: 'string', enum: tokens },\n description:\n 'Fields to sort by, in order. A leading `-` sorts that field descending. Leave null for the default order.',\n })\n plan.sort = true\n }\n }\n\n const includable = params.some((p) => p.in === 'query' && p.name === 'include')\n const paths = includable ? capIncludePaths(includePaths(doc, ctx.operation), includeDepth) : []\n if (paths.length > 0) {\n properties['include'] = nullable({\n type: 'array',\n items: { type: 'string', enum: paths },\n description: includeDescription(includeDepth),\n })\n plan.include = true\n plan.includePaths = paths\n }\n\n const fieldTypes = params\n .map((p) => (p.in === 'query' ? /^fields\\[(.+)\\]$/.exec(p.name)?.[1] : undefined))\n .filter((t): t is string => t !== undefined)\n if (fieldTypes.length > 0) {\n const members: Record<string, JsonSchema> = {}\n for (const type of fieldTypes) {\n members[type] = nullable({ type: 'array', items: { type: 'string' } })\n }\n properties['fields'] = nullable(closedObject(members, FIELDS_DESCRIPTION))\n plan.fields = fieldTypes\n }\n\n if (collection && paginator !== undefined) {\n const resolved = resolvePaginator(paginator)\n if (resolved.kind !== 'none') {\n plan.paginated = true\n const sizeKey = resolved.keys.size ?? resolved.keys.limit\n if (sizeKey !== undefined) {\n const bounds = pageBounds(doc, params, resolved.kind, sizeKey)\n const fallback = Math.min(defaultPageSize, bounds.max ?? defaultPageSize)\n plan.sizeKey = sizeKey\n plan.defaultPageSize = fallback\n if (bounds.max !== undefined) plan.maxPageSize = bounds.max\n const range = bounds.max === undefined ? 'at least 1' : `1 to ${bounds.max}`\n properties['pageSize'] = nullable({\n type: 'integer',\n description: `How many results a page holds, ${range}. Leave null for ${fallback}.`,\n })\n }\n properties['pageLink'] = nullableString(PAGE_LINK_DESCRIPTION)\n }\n }\n\n return { properties, plan }\n}\n\n/** The `data` member's schema of a write request body. */\nfunction requestData(doc: OpenApiDocument, schema: unknown): JsonSchema | undefined {\n const body = deref<JsonSchema>(doc, schema as JsonSchema)\n const data = isObject(body?.['properties']) ? body['properties']['data'] : undefined\n return deref<JsonSchema>(doc, data as JsonSchema)\n}\n\n/** A relation's write argument: an id, ids, or `{ type, id }` objects for a polymorphic one. */\nfunction relationArgument(\n name: string,\n relation: RelationDescriptor,\n allowNull: boolean,\n): JsonSchema {\n const polymorphic = isPolymorphic(relation)\n const one: JsonSchema = polymorphic\n ? closedObject({\n type: { type: 'string', enum: [...relation.types] },\n id: { type: 'string' },\n })\n : { type: 'string' }\n const target = polymorphic ? relation.types.join(' or ') : relation.types[0]\n if (relation.cardinality === 'many') {\n return {\n type: 'array',\n items: one,\n description: polymorphic\n ? `The ${name} relationship: every related resource, as its type (${target}) and id.`\n : `The ${name} relationship: the ids of the related \\`${target}\\` resources.`,\n }\n }\n const schema: JsonSchema = {\n ...one,\n description: polymorphic\n ? `The ${name} relationship: the related resource's type (${target}) and id.`\n : `The ${name} relationship: the id of the related \\`${target}\\` resource.`,\n }\n return allowNull ? nullable(schema) : schema\n}\n\n/**\n * A flat write input from a JSON:API write request body: the attributes and relationships as\n * one object, the API's required members required, the rest nullable.\n */\nfunction writeInput(\n doc: OpenApiDocument,\n descriptor: ApiDescriptor,\n type: string,\n bodySchema: unknown,\n mode: 'create' | 'update' | 'action',\n): { properties: Record<string, JsonSchema>; required: string[]; plan: WritePlan } {\n const properties: Record<string, JsonSchema> = {}\n const required: string[] = []\n const fields: Record<string, WriteField> = {}\n const clearable: string[] = []\n const data = requestData(doc, bodySchema)\n const dataProps = isObject(data?.['properties']) ? data['properties'] : {}\n\n const attributes = deref<JsonSchema>(doc, dataProps['attributes'] as JsonSchema)\n const converted = attributes === undefined ? undefined : toPortable(doc, attributes, [], 0)\n if (converted !== undefined && converted.decoder.kind === 'object') {\n const attrRequired = new Set(\n Array.isArray(attributes!['required']) ? (attributes!['required'] as string[]) : [],\n )\n const attrProps = converted.schema['properties'] as Record<string, JsonSchema>\n for (const [name, prop] of Object.entries(converted.decoder.props)) {\n const schema = attrProps[name]!\n // The converted object already made every optional member nullable.\n properties[name] = schema\n const isRequired = mode !== 'update' && attrRequired.has(name)\n // At the top level null means \"not given\", except for a required member the API lets be null.\n fields[name] = { property: { decoder: prop.decoder, nullMeansOmit: !isRequired } }\n if (isRequired) {\n required.push(name)\n }\n if (!prop.nullMeansOmit && allowsNull(schema)) {\n clearable.push(name)\n }\n }\n }\n\n const relationships = deref<JsonSchema>(doc, dataProps['relationships'] as JsonSchema)\n const relProps = isObject(relationships?.['properties']) ? relationships['properties'] : {}\n const relRequired = new Set(\n Array.isArray(relationships?.['required']) ? (relationships['required'] as string[]) : [],\n )\n for (const [name, raw] of Object.entries(relProps)) {\n const relation = descriptor[type]?.relations[name]\n if (relation === undefined || name in properties) {\n continue\n }\n const relSchema = deref<JsonSchema>(doc, raw as JsonSchema)\n const linkage = isObject(relSchema?.['properties'])\n ? relSchema['properties']['data']\n : undefined\n const linkageAllowsNull =\n relation.cardinality === 'one' && JSON.stringify(deref(doc, linkage) ?? {}).includes('\"null\"')\n const isRequired = mode !== 'update' && relRequired.has(name)\n const schema = relationArgument(name, relation, linkageAllowsNull)\n properties[name] = isRequired || allowsNull(schema) ? schema : nullable(schema)\n fields[name] = {\n relation: isPolymorphic(relation)\n ? { cardinality: relation.cardinality, types: relation.types, polymorphic: true }\n : { cardinality: relation.cardinality, types: relation.types },\n property: { decoder: { kind: 'value' }, nullMeansOmit: !isRequired },\n }\n if (isRequired) {\n required.push(name)\n }\n if (linkageAllowsNull) {\n clearable.push(name)\n }\n }\n\n let clientId: WritePlan['clientId'] = 'none'\n if (mode === 'create') {\n const idProp = dataProps['id']\n const dataRequired = Array.isArray(data?.['required']) ? (data['required'] as string[]) : []\n if (idProp !== undefined && idProp !== false) {\n clientId = dataRequired.includes('id') ? 'required' : 'optional'\n }\n }\n\n return {\n properties,\n required,\n plan: { fields, clientId, clearable: mode === 'update' ? clearable : [] },\n }\n}\n\n/** Close a root object: every property required, the optional ones already nullable. */\nfunction root(\n properties: Record<string, JsonSchema>,\n required: readonly string[],\n): JsonSchema & { type: 'object' } {\n const out: Record<string, JsonSchema> = {}\n for (const [name, schema] of Object.entries(properties)) {\n out[name] = required.includes(name) || allowsNull(schema) ? schema : nullable(schema)\n }\n return closedObject(out) as JsonSchema & { type: 'object' }\n}\n\nexport interface InputContext {\n doc: OpenApiDocument\n descriptor: ApiDescriptor\n params: readonly ParameterObject[]\n operation: OperationObject\n defaultPageSize: number\n /** How many relationships deep `include` may reach; null for every declared path. */\n includeDepth: number | null\n}\n\n/** A list or to-many related read. */\nexport function collectionInput(\n ctx: InputContext,\n paginator: PaginatorDescriptor | undefined,\n parent: { type: string; relation: string } | undefined,\n countable: CountableDescriptor | undefined,\n): ToolInput {\n const { properties, plan } = queryInput(ctx, paginator, true)\n if (countable !== undefined && countable.tokens.includes(SELF_COUNT)) {\n properties['withCount'] = nullable({ type: 'boolean', description: WITH_COUNT_DESCRIPTION })\n plan.countProfile = countable.profile\n }\n const props: Record<string, JsonSchema> = {}\n const required: string[] = []\n if (parent !== undefined) {\n props['id'] = idSchema(\n `The id of the \\`${parent.type}\\` resource whose ${parent.relation} to list.`,\n )\n required.push('id')\n }\n return { schema: root({ ...props, ...properties }, required), query: plan }\n}\n\n/** A get or to-one related read. */\nexport function singleInput(ctx: InputContext, description: string): ToolInput {\n const { properties, plan } = queryInput(ctx, undefined, false)\n return { schema: root({ id: idSchema(description), ...properties }, ['id']), query: plan }\n}\n\n/** A create, from the type's create request body. */\nexport function createInput(ctx: InputContext, type: string): ToolInput {\n const body = deref<RequestBodyObject>(ctx.doc, ctx.operation.requestBody)\n const schema = body?.content?.['application/vnd.api+json']?.schema\n const write = writeInput(ctx.doc, ctx.descriptor, type, schema, 'create')\n const properties: Record<string, JsonSchema> = {}\n const required = [...write.required]\n if (write.plan.clientId !== 'none') {\n properties['id'] = idSchema(`The id to give the new \\`${type}\\` resource.`)\n if (write.plan.clientId === 'required') {\n required.push('id')\n }\n }\n return { schema: root({ ...properties, ...write.properties }, required), write: write.plan }\n}\n\n/** An update, from the type's update request body. */\nexport function updateInput(ctx: InputContext, type: string): ToolInput {\n const body = deref<RequestBodyObject>(ctx.doc, ctx.operation.requestBody)\n const schema = body?.content?.['application/vnd.api+json']?.schema\n const write = writeInput(ctx.doc, ctx.descriptor, type, schema, 'update')\n const properties: Record<string, JsonSchema> = {\n id: idSchema(`The id of the \\`${type}\\` resource to update.`),\n }\n const clearable = 'clear' in write.properties ? [] : write.plan.clearable\n const extra: Record<string, JsonSchema> = {}\n if (clearable.length > 0) {\n extra['clear'] = nullable({\n type: 'array',\n items: { type: 'string', enum: [...clearable] },\n description: 'Fields to set to null. Every other argument left null keeps its current value.',\n })\n }\n return {\n schema: root({ ...properties, ...write.properties, ...extra }, ['id']),\n write: { ...write.plan, clearable },\n }\n}\n\n/** A delete. */\nexport function deleteInput(type: string): ToolInput {\n return { schema: root({ id: idSchema(`The id of the \\`${type}\\` resource to delete.`) }, ['id']) }\n}\n\n/** The request media type a model can send to an action, in order of preference. */\nfunction pickBody(\n ctx: InputContext,\n):\n | { mediaType: string; schema: unknown; kind: 'document' | 'json' | 'text' | 'form' }\n | 'none'\n | undefined {\n const body = deref<RequestBodyObject>(ctx.doc, ctx.operation.requestBody)\n const content = Object.entries(body?.content ?? {})\n if (content.length === 0) {\n return 'none'\n }\n const find = (test: (mediaType: string) => boolean) =>\n content.find(([mediaType]) => test(mediaType.toLowerCase()))\n const document = find((m) => m === 'application/vnd.api+json')\n if (document !== undefined) {\n return { mediaType: document[0], schema: document[1].schema, kind: 'document' }\n }\n const json = find((m) => m === 'application/json' || m.endsWith('+json'))\n if (json !== undefined) {\n return { mediaType: json[0], schema: json[1].schema, kind: 'json' }\n }\n const text = find((m) => m.startsWith('text/'))\n if (text !== undefined) {\n return { mediaType: text[0], schema: { type: 'string' }, kind: 'text' }\n }\n const form = content.find(([mediaType, media]) => {\n const m = mediaType.toLowerCase()\n const schema = deref<JsonSchema>(ctx.doc, media.schema)\n return (\n (m === 'application/x-www-form-urlencoded' || m.startsWith('multipart/')) &&\n isObject(schema?.['properties']) &&\n Object.keys(schema['properties']).length > 0\n )\n })\n if (form !== undefined) {\n return { mediaType: form[0], schema: form[1].schema, kind: 'form' }\n }\n return undefined\n}\n\n/**\n * A custom action's input: the resource id at resource scope, then the body. A JSON:API body\n * whose type the descriptor knows is flat, like a create; any other body is one `body` argument.\n * `undefined` when every media type the action takes is one a model can't send, such as binary.\n */\nexport function actionInput(\n ctx: InputContext,\n type: string,\n name: string,\n action: ActionDescriptor,\n): ToolInput | undefined {\n const properties: Record<string, JsonSchema> = {}\n const required: string[] = []\n if (action.scope === 'resource') {\n properties['id'] = idSchema(`The id of the \\`${type}\\` resource to run ${name} on.`)\n required.push('id')\n }\n const picked = pickBody(ctx)\n if (picked === undefined) {\n return undefined\n }\n if (picked === 'none') {\n return { schema: root(properties, required), action: { kind: 'none' } }\n }\n const body = deref<RequestBodyObject>(ctx.doc, ctx.operation.requestBody)\n const bodyRequired = body?.required === true\n if (picked.kind === 'document' && action.inputType !== undefined) {\n const write = writeInput(ctx.doc, ctx.descriptor, action.inputType, picked.schema, 'action')\n for (const [key, schema] of Object.entries(write.properties)) {\n if (!(key in properties)) {\n properties[key] = schema\n }\n }\n return {\n schema: root(properties, [...required, ...write.required.filter((r) => r !== 'id')]),\n action: { kind: 'flat', write: write.plan },\n }\n }\n\n const converted = toPortable(ctx.doc, picked.schema ?? {}, [], 1)\n const description =\n typeof body?.description === 'string'\n ? body.description\n : `The request body, as ${picked.mediaType}.`\n properties['body'] = {\n ...converted.schema,\n description:\n converted.schema['description'] === undefined\n ? description\n : `${description} ${String(converted.schema['description'])}`,\n }\n if (bodyRequired) {\n required.push('body')\n }\n return {\n schema: root(properties, required),\n action: {\n kind: 'body',\n decoder: converted.decoder,\n required: bodyRequired,\n mediaType: picked.mediaType,\n mediaKind: picked.kind,\n },\n }\n}\n","/**\n * Flatten the client's materialised results into plain tool output. A resource becomes\n * `{ id, type, ...attributes, relation, links? }`, where a relation is an id (ids for a to-many,\n * or type and id for a polymorphic relation), or the flattened related resource when it was\n * included, and `links` holds only the links the type names. Generic links such as `self` and\n * resource meta are dropped. Results go back in a JSON:API-like envelope of `data`, `links` and\n * `meta`, where `links` only carries a collection's pagination.\n */\nimport { type ApiDescriptor, isPolymorphic } from '@haddowg/json-api-client'\nimport { isObject } from './openapi'\n\n/** What flattening needs from the model. */\nexport interface FlattenContext {\n descriptor: ApiDescriptor\n /** How many relationships deep a result nests included resources; null for no cap. */\n includeDepth: number | null\n /** The links each type names. A type missing from it keeps no links. */\n resourceLinks: Readonly<Record<string, readonly string[]>>\n}\n\n/** A materialised related value carries `$raw` when it was included, not when it's only linkage. */\nconst isIncluded = (value: object): boolean => '$raw' in value\n\nfunction identifier(value: Record<string, unknown>, polymorphic: boolean): unknown {\n return polymorphic ? { type: value['type'], id: value['id'] } : value['id']\n}\n\n/** A link as a flattened resource carries it: `{ href, type?, title?, meta? }`. */\nfunction flatLink(link: unknown): Record<string, unknown> | undefined {\n if (typeof link === 'string') {\n return { href: link }\n }\n if (!isObject(link) || typeof link['href'] !== 'string') {\n return undefined\n }\n const out: Record<string, unknown> = { href: link['href'] }\n if (typeof link['type'] === 'string') out['type'] = link['type']\n if (typeof link['title'] === 'string') out['title'] = link['title']\n if (isObject(link['meta'])) out['meta'] = link['meta']\n return out\n}\n\n/** The links of a resource its type names, or undefined when it carries none of them. */\nfunction namedLinks(\n value: Record<string, unknown>,\n names: readonly string[] | undefined,\n): Record<string, unknown> | undefined {\n const links = (value as { $links?: unknown }).$links\n if (names === undefined || !isObject(links)) {\n return undefined\n }\n const out: Record<string, unknown> = {}\n for (const name of names) {\n const link = flatLink(links[name])\n if (link !== undefined) {\n out[name] = link\n }\n }\n return Object.keys(out).length > 0 ? out : undefined\n}\n\n/**\n * Flatten one materialised resource. A resource already on the path, or one more than\n * `includeDepth` relationships from the root, is cut back to its id.\n */\nexport function flattenResource(\n value: Record<string, unknown>,\n ctx: FlattenContext,\n path: readonly string[] = [],\n): Record<string, unknown> {\n const type = String(value['type'])\n const key = `${type}:${String(value['id'])}`\n const relations = ctx.descriptor[type]?.relations ?? {}\n const maxDepth = ctx.includeDepth\n const out: Record<string, unknown> = { id: value['id'], type }\n for (const [name, member] of Object.entries(value)) {\n if (name === 'id' || name === 'type') {\n continue\n }\n const relation = relations[name]\n if (relation === undefined) {\n out[name] = member\n continue\n }\n const polymorphic = isPolymorphic(relation)\n const one = (item: unknown): unknown => {\n if (!isObject(item)) {\n return item\n }\n const itemKey = `${String(item['type'])}:${String(item['id'])}`\n return isIncluded(item) &&\n itemKey !== key &&\n !path.includes(itemKey) &&\n (maxDepth === null || path.length < maxDepth)\n ? flattenResource(item, ctx, [...path, key])\n : identifier(item, polymorphic)\n }\n out[name] = Array.isArray(member) ? member.map(one) : one(member)\n }\n const links = namedLinks(value, ctx.resourceLinks[type])\n if (links !== undefined) {\n out['links'] = links\n }\n return out\n}\n\nconst PAGE_LINKS = ['first', 'prev', 'next', 'last'] as const\n\nfunction linkHref(link: unknown): string | undefined {\n if (typeof link === 'string') return link\n if (isObject(link) && typeof link['href'] === 'string') return link['href']\n return undefined\n}\n\n/** The document `meta` a materialised resource came with. */\nfunction resourceDocumentMeta(value: unknown): Record<string, unknown> | undefined {\n const document = isObject(value)\n ? (value as { $document?: { meta?: unknown } }).$document\n : undefined\n return isObject(document?.meta) ? document.meta : undefined\n}\n\n/**\n * One resource in its envelope: `{ data, meta? }`, with `data` null when there is none.\n * `fallbackMeta` is the document meta when the value can't carry it, such as a null.\n */\nexport function resourceEnvelope(\n value: unknown,\n ctx: FlattenContext,\n fallbackMeta?: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {\n data: isObject(value) ? flattenResource(value, ctx) : null,\n }\n const meta = resourceDocumentMeta(value) ?? fallbackMeta\n if (meta !== undefined) {\n out['meta'] = meta\n }\n return out\n}\n\n/** A materialised collection in its envelope: `{ data, links, meta? }`. */\nexport function collectionEnvelope(\n value: readonly unknown[] & { $links?: Record<string, unknown>; $meta?: Record<string, unknown> },\n ctx: FlattenContext,\n): Record<string, unknown> {\n const links: Record<string, string> = {}\n for (const name of PAGE_LINKS) {\n const href = linkHref(value.$links?.[name])\n if (href !== undefined) {\n links[name] = href\n }\n }\n const out: Record<string, unknown> = {\n data: value.map((item) => (isObject(item) ? flattenResource(item, ctx) : item)),\n links,\n }\n if (value.$meta !== undefined) {\n out['meta'] = value.$meta\n }\n return out\n}\n","import {\n type ActionDescriptor,\n type ApiDescriptor,\n createClient,\n execute,\n fetchTransport,\n type HeadersInit,\n isActionLocation,\n isPolymorphic,\n JsonApiError,\n type JsonApiTransport,\n materialise,\n type ReadQuery,\n type TransportResponse,\n} from '@haddowg/json-api-client'\nimport type { ResultShape } from './catalogue'\nimport type { McpModelPlan } from './model'\nimport { type QueryPlan, SELF_COUNT, type WritePlan } from './inputs'\nimport { isObject } from './openapi'\nimport { ArgumentError, decode } from './portable'\nimport { collectionEnvelope, type FlattenContext, resourceEnvelope } from './flatten'\nimport type { McpCallToolResult, McpContent, McpOperation, McpTool, McpToolError } from './types'\n\n/** One tool the runtime serves: its operation, definition, call plan and result shape. */\nexport interface ToolEntry {\n op: McpOperation\n tool: McpTool\n input: McpModelPlan\n result: ResultShape\n actionKey?: string\n}\n\n/** Thrown when a `pageLink` isn't a link this tool may follow. */\nexport class PageLinkError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'PageLinkError'\n }\n}\n\ntype Call = (...args: unknown[]) => Promise<unknown>\n\ninterface LooseRelation {\n related(query?: ReadQuery): Promise<unknown>\n}\n\ninterface LooseHandle {\n update(patch: Record<string, unknown>): Promise<unknown>\n delete(): Promise<unknown>\n rel(name: string): LooseRelation\n actions: Record<string, Call | undefined>\n}\n\ninterface LooseAccessor {\n list(query?: ReadQuery): Promise<unknown>\n get(id: string, query?: ReadQuery): Promise<unknown>\n create(input: Record<string, unknown>): Promise<unknown>\n id(id: string): LooseHandle\n actions: Record<string, Call | undefined>\n}\n\nexport interface CallEnvironment {\n descriptor: ApiDescriptor\n baseUrl: string | undefined\n transport: JsonApiTransport | undefined\n headers: HeadersInit | undefined\n signal: AbortSignal | undefined\n /** How many relationships deep a result nests included resources; null for no cap. */\n includeDepth: number | null\n /** The links each type names, which a flattened resource keeps. */\n resourceLinks: Readonly<Record<string, readonly string[]>>\n /**\n * Shape results as models before format 4 describe them: `{ item }`, `{ items, links, meta }`,\n * a bare resource or meta object, and a delete as text.\n */\n legacyResults: boolean\n}\n\nconst text = (value: string): McpContent => ({ type: 'text', text: value })\n\nfunction structured(value: Record<string, unknown>): McpCallToolResult {\n return { content: [text(JSON.stringify(value))], structuredContent: value }\n}\n\n/** The `meta` of a JSON:API response body, when it has one. */\nfunction responseMeta(\n response: TransportResponse | undefined,\n): Record<string, unknown> | undefined {\n if (response === undefined || response.body === '') {\n return undefined\n }\n try {\n const body: unknown = JSON.parse(response.body)\n return isObject(body) && isObject(body['meta']) ? body['meta'] : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * A result in the shape a model before format 4 describes: `{ item }` for a single read,\n * `{ items, links, meta }` for a collection, the bare resource, meta object or URL otherwise,\n * and a delete as text.\n */\nfunction legacyResult(\n entry: ToolEntry,\n envelope: Record<string, unknown>,\n location: boolean,\n id: string | undefined,\n): McpCallToolResult {\n const data = envelope['data']\n if (location || entry.result === 'resource' || entry.result === 'loose') {\n if (Array.isArray(data)) {\n const { data: items, ...rest } = envelope\n return structured({ items, ...rest })\n }\n return isObject(data)\n ? structured(data)\n : { content: [text('The action returned no resource.')] }\n }\n switch (entry.result) {\n case 'item':\n return structured({ item: data ?? null })\n case 'collection': {\n const { data: items, ...rest } = envelope\n return structured({ items, ...rest })\n }\n case 'deleted':\n return { content: [text(`Deleted ${entry.op.type} ${String(id)}.`)] }\n case 'meta':\n return structured(isObject(envelope['meta']) ? envelope['meta'] : {})\n default:\n return structured(envelope)\n }\n}\n\nfunction requireId(args: Record<string, unknown>): string {\n const id = args['id']\n if (typeof id === 'string' && id !== '') {\n return id\n }\n if (typeof id === 'number') {\n return String(id)\n }\n throw new ArgumentError('id is required')\n}\n\nfunction stringList(value: unknown, name: string): string[] | undefined {\n if (value === null || value === undefined) {\n return undefined\n }\n if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {\n throw new ArgumentError(`${name} must be a list of strings`)\n }\n return value as string[]\n}\n\n/** The read query a model's arguments describe. */\nexport function readQuery(args: Record<string, unknown>, plan: QueryPlan): ReadQuery {\n const query: ReadQuery = {}\n if (plan.filter !== undefined && isObject(args['filter'])) {\n const filter = decode(args['filter'], plan.filter, 'filter') as Record<string, unknown>\n for (const [key, value] of Object.entries(filter)) {\n if (isObject(value)) {\n // A range filter with every bound null applies nothing.\n const bounds = Object.fromEntries(Object.entries(value).filter(([, v]) => v !== null))\n if (Object.keys(bounds).length === 0) {\n delete filter[key]\n } else {\n filter[key] = bounds\n }\n }\n }\n if (Object.keys(filter).length > 0) {\n query.filter = filter\n }\n }\n const sort = plan.sort ? stringList(args['sort'], 'sort') : undefined\n if (sort !== undefined && sort.length > 0) {\n query.sort = sort\n }\n const include = plan.include ? stringList(args['include'], 'include') : undefined\n if (plan.includePaths !== undefined) {\n const allowed = plan.includePaths\n const refused = (include ?? []).filter((path) => !allowed.includes(path))\n if (refused.length > 0) {\n throw new ArgumentError(\n `include can't name ${refused.map((p) => `\"${p}\"`).join(', ')}; this tool includes only ${allowed.join(', ')}`,\n )\n }\n }\n if (include !== undefined && include.length > 0) {\n query.include = include\n }\n if (plan.fields.length > 0 && isObject(args['fields'])) {\n const fields: Record<string, string[]> = {}\n for (const [type, names] of Object.entries(args['fields'])) {\n const list = stringList(names, `fields.${type}`)\n if (list !== undefined) {\n fields[type] = list\n }\n }\n if (Object.keys(fields).length > 0) {\n query.fields = fields\n }\n }\n if (plan.countProfile !== undefined && args['withCount'] === true) {\n query.withCount = [SELF_COUNT]\n }\n if (plan.sizeKey !== undefined) {\n const asked = args['pageSize']\n let size =\n typeof asked === 'number' && Number.isFinite(asked)\n ? Math.trunc(asked)\n : plan.defaultPageSize!\n size = Math.max(1, plan.maxPageSize === undefined ? size : Math.min(size, plan.maxPageSize))\n query.page = { [plan.sizeKey]: size }\n }\n return query\n}\n\n/** The flat write input a model's arguments describe: relations as identifiers, nulls resolved. */\nexport function writeInput(\n args: Record<string, unknown>,\n plan: WritePlan,\n): Record<string, unknown> {\n const input: Record<string, unknown> = {}\n for (const [name, field] of Object.entries(plan.fields)) {\n const value = args[name]\n if (value === undefined || (value === null && field.property.nullMeansOmit)) {\n continue\n }\n if (value === null) {\n input[name] = null\n continue\n }\n const relation = field.relation\n if (relation === undefined) {\n input[name] = decode(value, field.property.decoder, name)\n continue\n }\n const identify = (item: unknown): unknown => {\n if (isPolymorphic(relation)) {\n if (!isObject(item) || typeof item['type'] !== 'string' || typeof item['id'] !== 'string') {\n throw new ArgumentError(`${name} takes the related resource's type and id`)\n }\n return { type: item['type'], id: item['id'] }\n }\n if (typeof item !== 'string' && typeof item !== 'number') {\n throw new ArgumentError(`${name} takes the related resource's id`)\n }\n return { type: relation.types[0], id: String(item) }\n }\n if (relation.cardinality === 'many') {\n if (!Array.isArray(value)) {\n throw new ArgumentError(`${name} takes a list`)\n }\n input[name] = value.map(identify)\n } else {\n input[name] = identify(value)\n }\n }\n for (const name of stringList(args['clear'], 'clear') ?? []) {\n if (!plan.clearable.includes(name)) {\n throw new ArgumentError(`clear can't name \"${name}\"`)\n }\n input[name] = null\n }\n if (plan.clientId !== 'none' && args['id'] !== null && args['id'] !== undefined) {\n input['id'] = String(args['id'])\n }\n return input\n}\n\n/** Escape a string for a regular expression. */\nconst escape = (value: string): string => value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/**\n * Check a `pageLink` against the tool's own endpoint: the base URL's origin and the endpoint's\n * path, with any id in it. Returns the absolute URL to fetch.\n */\nexport function checkPageLink(link: unknown, baseUrl: string, template: string): string {\n if (typeof link !== 'string' || link === '') {\n throw new PageLinkError('pageLink must be a link string')\n }\n let base: URL\n try {\n base = new URL(baseUrl)\n } catch {\n throw new PageLinkError('pageLink needs an absolute base URL to be checked against')\n }\n let url: URL\n try {\n url = new URL(link, base)\n } catch {\n throw new PageLinkError('pageLink is not a valid URL')\n }\n const prefix = base.pathname.replace(/\\/$/, '')\n const pattern = template\n .split(/(\\{[^}]+\\})/)\n .map((part) => (/^\\{[^}]+\\}$/.test(part) ? '[^/]+' : escape(part)))\n .join('')\n if (\n (url.protocol !== 'http:' && url.protocol !== 'https:') ||\n url.origin !== base.origin ||\n !new RegExp(`^${escape(prefix)}${pattern}$`).test(url.pathname)\n ) {\n throw new PageLinkError(\n 'pageLink must be one of the links this tool returned; it points somewhere else',\n )\n }\n return url.href\n}\n\n/** The descriptor entry a call sends this action with: the operation's method and the body plan picked. */\nfunction effectiveAction(entry: ToolEntry): ActionDescriptor {\n const {\n inputMedia: _inputMedia,\n inputType,\n contentType: _contentType,\n ...action\n } = entry.op.actionDescriptor!\n const plan = entry.input.action!\n const base = { ...action, method: entry.op.method }\n switch (plan.kind) {\n case 'none':\n return { ...base, input: 'none' }\n case 'flat':\n return inputType === undefined\n ? { ...base, input: 'document' }\n : { ...base, input: 'document', inputType }\n case 'body':\n return plan.mediaKind === 'document'\n ? { ...base, input: 'document' }\n : { ...base, input: 'raw', contentType: plan.mediaType }\n }\n}\n\nfunction withAction(descriptor: ApiDescriptor, entry: ToolEntry): ApiDescriptor {\n const resource = descriptor[entry.op.type]!\n return {\n ...descriptor,\n [entry.op.type]: {\n ...resource,\n actions: { ...resource.actions, [entry.actionKey!]: effectiveAction(entry) },\n },\n }\n}\n\nasync function binaryContent(\n value: Blob | ArrayBuffer,\n uri: string,\n mimeType: string,\n): Promise<McpContent> {\n const bytes = value instanceof ArrayBuffer ? value : await value.arrayBuffer()\n const data = Buffer.from(bytes).toString('base64')\n return mimeType.startsWith('image/')\n ? { type: 'image', data, mimeType }\n : { type: 'resource', resource: { uri, mimeType, blob: data } }\n}\n\n/** Run one tool call against the API. Throws what the call failed with. */\nexport async function runCall(\n entry: ToolEntry,\n rawArgs: unknown,\n env: CallEnvironment,\n): Promise<McpCallToolResult> {\n if (rawArgs !== undefined && rawArgs !== null && !isObject(rawArgs)) {\n throw new ArgumentError('the arguments must be an object')\n }\n const args = (rawArgs ?? {}) as Record<string, unknown>\n if (env.baseUrl === undefined) {\n throw new Error(\n 'No base URL: pass baseUrl, return one from requestInit, or give the document a server',\n )\n }\n // The last response, for a document's meta when the client returns no resource to carry it.\n let last: TransportResponse | undefined\n const inner = env.transport ?? fetchTransport\n const transport: JsonApiTransport = async (request) => {\n last = await inner(request)\n return last\n }\n const outcome = await callApi(entry, args, { ...env, baseUrl: env.baseUrl, transport })\n const id = entry.op.kind === 'delete' ? requireId(args) : undefined\n if (outcome.kind === 'content') {\n return outcome.result\n }\n const location = outcome.kind === 'envelope' && outcome.location === true\n const envelope =\n outcome.kind === 'envelope'\n ? outcome.envelope\n : withFallbackMeta(outcome.envelope, responseMeta(last))\n return env.legacyResults ? legacyResult(entry, envelope, location, id) : structured(envelope)\n}\n\ntype Outcome =\n | { kind: 'content'; result: McpCallToolResult }\n | { kind: 'envelope'; envelope: Record<string, unknown>; location?: boolean }\n /** An envelope that takes the response's meta when it has none of its own. */\n | { kind: 'bare'; envelope: Record<string, unknown> }\n\nfunction withFallbackMeta(\n envelope: Record<string, unknown>,\n meta: Record<string, unknown> | undefined,\n): Record<string, unknown> {\n return meta === undefined || envelope['meta'] !== undefined ? envelope : { ...envelope, meta }\n}\n\nconst envelope = (value: Record<string, unknown>): Outcome => ({\n kind: 'envelope',\n envelope: value,\n})\n\nasync function callApi(\n entry: ToolEntry,\n args: Record<string, unknown>,\n env: CallEnvironment & { transport: JsonApiTransport; baseUrl: string },\n): Promise<Outcome> {\n const { op } = entry\n const descriptor = op.kind === 'action' ? withAction(env.descriptor, entry) : env.descriptor\n const headers = env.headers\n const client = createClient(descriptor, {\n baseUrl: env.baseUrl,\n transport: env.transport,\n ...(headers !== undefined ? { headers: () => headers } : {}),\n })\n const accessor = (client as unknown as Record<string, LooseAccessor>)[op.type]!\n const query = entry.input.query === undefined ? undefined : readQuery(args, entry.input.query)\n if (query !== undefined && env.signal !== undefined) {\n query.signal = env.signal\n }\n const flat: FlattenContext = {\n descriptor: env.descriptor,\n includeDepth: env.includeDepth,\n resourceLinks: env.resourceLinks,\n }\n\n switch (op.kind) {\n case 'list':\n case 'related': {\n if (entry.result === 'item') {\n const related = await accessor.id(requireId(args)).rel(op.relation!).related(query)\n return { kind: 'bare', envelope: resourceEnvelope(related, flat) }\n }\n let collection: unknown\n if (\n args['pageLink'] !== undefined &&\n args['pageLink'] !== null &&\n entry.input.query?.paginated\n ) {\n const url = checkPageLink(args['pageLink'], env.baseUrl, op.path)\n const doc = await execute(\n {\n baseUrl: env.baseUrl,\n transport: env.transport,\n ...(headers !== undefined ? { headers: () => headers } : {}),\n },\n {\n method: 'GET',\n path: url,\n link: true,\n ...(entry.input.query?.countProfile !== undefined &&\n new URL(url).searchParams.has('withCount')\n ? { profiles: [entry.input.query.countProfile] }\n : {}),\n ...(env.signal !== undefined ? { signal: env.signal } : {}),\n },\n )\n collection =\n doc === undefined\n ? []\n : materialise(doc, {\n descriptor: env.descriptor,\n navigate: () => Promise.reject(new Error('not navigable')),\n })\n } else if (op.kind === 'list') {\n collection = await accessor.list(query)\n } else {\n collection = await accessor.id(requireId(args)).rel(op.relation!).related(query)\n }\n return envelope(collectionEnvelope(Array.isArray(collection) ? collection : [], flat))\n }\n case 'get':\n return {\n kind: 'bare',\n envelope: resourceEnvelope(await accessor.get(requireId(args), query), flat),\n }\n case 'create': {\n const input = writeInput(args, entry.input.write!)\n const created = await accessor.create(input)\n // A 204 to a client-generated id sends nothing back but the id the caller chose.\n return {\n kind: 'bare',\n envelope: resourceEnvelope(\n isObject(created) ? created : { id: String(input['id'] ?? ''), type: op.type },\n flat,\n ),\n }\n }\n case 'update': {\n const id = requireId(args)\n const updated = await accessor.id(id).update(writeInput(args, entry.input.write!))\n return {\n kind: 'bare',\n envelope: resourceEnvelope(isObject(updated) ? updated : { id, type: op.type }, flat),\n }\n }\n case 'delete':\n await accessor.id(requireId(args)).delete()\n return { kind: 'bare', envelope: { data: null } }\n case 'action':\n return runAction(entry, args, accessor, env, flat)\n }\n}\n\nasync function runAction(\n entry: ToolEntry,\n args: Record<string, unknown>,\n accessor: LooseAccessor,\n env: CallEnvironment & { baseUrl: string },\n flat: FlattenContext,\n): Promise<Outcome> {\n const { op } = entry\n const action = op.actionDescriptor!\n const actions =\n action.scope === 'resource' ? accessor.id(requireId(args)).actions : accessor.actions\n const call = actions[entry.actionKey!]!\n const options = env.signal === undefined ? {} : { signal: env.signal }\n const plan = entry.input.action!\n let result: unknown\n if (plan.kind === 'none') {\n result = await call(options)\n } else {\n const body =\n plan.kind === 'flat'\n ? writeInput(args, plan.write)\n : args['body'] === null\n ? undefined\n : decode(args['body'], plan.decoder, 'body')\n if (plan.kind === 'body' && plan.required && body === undefined) {\n throw new ArgumentError('body is required')\n }\n result = await call(body, options)\n }\n\n // A URL the action produced goes back as data; the tool never fetches it.\n if (action.location !== undefined && isActionLocation(result)) {\n return { kind: 'envelope', envelope: { data: { ...result } }, location: true }\n }\n\n const content = (value: string): Outcome => ({\n kind: 'content',\n result: { content: [text(value)] },\n })\n switch (entry.result) {\n case 'collection':\n return envelope(collectionEnvelope(Array.isArray(result) ? result : [], flat))\n case 'resource':\n case 'loose':\n if (Array.isArray(result)) {\n return envelope(collectionEnvelope(result, flat))\n }\n return { kind: 'bare', envelope: resourceEnvelope(isObject(result) ? result : null, flat) }\n case 'meta':\n return envelope({ meta: isObject(result) ? result : {} })\n case 'raw': {\n const mimeType = action.rawOutput?.[0]?.mediaType ?? 'application/octet-stream'\n if (result === undefined) {\n return content('Done.')\n }\n if (typeof result === 'string') {\n return content(result)\n }\n if (\n result instanceof ArrayBuffer ||\n (typeof Blob !== 'undefined' && result instanceof Blob)\n ) {\n return {\n kind: 'content',\n result: {\n content: [await binaryContent(result, `${env.baseUrl}${action.path}`, mimeType)],\n },\n }\n }\n return content(JSON.stringify(result))\n }\n default:\n return content('Done.')\n }\n}\n\n/** The error a failed call carries, before the host's `mapError`. */\nexport function toToolError(error: unknown): McpToolError {\n if (error instanceof JsonApiError) {\n return {\n status: error.status,\n message: error.message,\n errors: error.errors.map((e) => {\n const out: McpToolError['errors'][number] = {}\n if (e.status !== undefined) out.status = e.status\n if (e.code !== undefined) out.code = e.code\n if (e.title !== undefined) out.title = e.title\n if (e.detail !== undefined) out.detail = e.detail\n if (e.source !== undefined) out.source = e.source\n if (e.path !== undefined) out.path = e.path\n return out\n }),\n cause: error,\n }\n }\n return {\n message: error instanceof Error ? error.message : String(error),\n errors: [],\n cause: error,\n }\n}\n\n/** A tool error result: the message, the status and each error object, as JSON text. */\nexport function errorResult(error: McpToolError): McpCallToolResult {\n const body: Record<string, unknown> = { error: error.message }\n if (error.status !== undefined) body['status'] = error.status\n if (error.errors.length > 0) body['errors'] = error.errors\n return { content: [text(JSON.stringify(body))], isError: true }\n}\n","/**\n * The generated artefact the runtime reads: everything a host needs to serve the tools, built\n * from the OpenAPI document at build time. It is plain JSON, so any host can load it, and it\n * carries the client's descriptor because the client can't make a call without one.\n */\nimport type { ApiDescriptor } from '@haddowg/json-api-client'\nimport type { ResultShape } from './catalogue'\nimport { MAXIMUM_CONTRACT, MINIMUM_CONTRACT } from './contract'\nimport type { ActionBodyPlan, QueryPlan, WritePlan } from './inputs'\nimport { isObject, type OperationObject, type SecurityRequirement } from './openapi'\nimport type { McpOperationKind, McpTool, SkippedOperation } from './types'\n\n/** The artefact's own format. A runtime refuses a format it doesn't know. */\nexport const MODEL_FORMAT = 4\n\n/**\n * Every format this runtime reads. Format 1 has no `credentialSchemes`. Formats 1 and 2 have no\n * `includeDepth`, and store each operation's `scopes` and the `security` of its operation object.\n * Formats 1 to 3 have no `resourceLinks`, and their output schemas describe the results before\n * the envelope, which the runtime still returns for them.\n */\nconst READABLE_FORMATS: readonly number[] = [1, 2, 3, MODEL_FORMAT]\n\n/** The package that writes the artefact. */\nexport const MODEL_GENERATOR = '@haddowg/json-api-mcp'\n\n/** One operation, as hooks see it, without the descriptor entries the runtime adds back. */\nexport interface McpModelOperation {\n operationId: string\n toolName: string\n kind: McpOperationKind\n type: string\n relation?: string\n action?: string\n /** The action's key in the type's descriptor `actions`. */\n actionKey?: string\n method: string\n path: string\n /**\n * The operation object, extensions included, without its `parameters`, `requestBody` and\n * `responses`: the tool already holds what those say, and they can refer to components the\n * artefact doesn't carry. From format 3 it has no `security` either: the runtime puts back\n * the `security` below.\n */\n operation: OperationObject\n /**\n * The operation's own `security`, else the document's. From format 3 it is absent when the\n * operation declares none, and the runtime takes the model's `security`.\n */\n security?: readonly SecurityRequirement[]\n /**\n * Formats 1 and 2: the OAuth scopes the descriptor says the operation needs. From format 3\n * the runtime reads them from the descriptor.\n */\n scopes?: readonly string[]\n}\n\n/** How the handler turns one tool's arguments into a client call. */\nexport interface McpModelPlan {\n query?: QueryPlan\n write?: WritePlan\n action?: ActionBodyPlan\n}\n\nexport interface McpModelTool {\n operation: McpModelOperation\n tool: McpTool\n plan: McpModelPlan\n result: ResultShape\n}\n\n/** The generated artefact. */\nexport interface McpModel {\n format: 1 | 2 | 3 | typeof MODEL_FORMAT\n generator: typeof MODEL_GENERATOR\n /** The generator contract the source document declared. */\n contract: number\n /** Which document it was built from. */\n source: { title: string; hash: string }\n /** The document's first server URL, the default base URL. */\n baseUrl?: string\n /**\n * The security schemes an MCP caller authenticates with: the document's `oauth2` schemes.\n * Empty when it has none. A format 1 model doesn't carry it.\n */\n credentialSchemes?: string[]\n /**\n * Format 3: the document's `security`, which an operation declaring none of its own takes.\n * Absent when the document declares none.\n */\n security?: readonly SecurityRequirement[]\n /**\n * How many relationships deep a tool's `include` may reach; null for every path the document\n * declares. Formats 1 and 2 don't carry it and set no cap.\n */\n includeDepth?: number | null\n /**\n * Format 4: the links each type names, its declared links and action links, for the types that\n * name any. A flattened resource carries these under `links`.\n */\n resourceLinks?: Record<string, string[]>\n /** The json-api-ts descriptor the client calls through. */\n descriptor: ApiDescriptor\n tools: McpModelTool[]\n skipped: SkippedOperation[]\n}\n\n/** Thrown when an artefact can't be read: another format, or a contract out of range. */\nexport class McpModelError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'McpModelError'\n }\n}\n\n/** Check a parsed artefact before the runtime trusts it. */\nexport function assertModel(value: unknown): McpModel {\n if (!isObject(value) || value['generator'] !== MODEL_GENERATOR) {\n throw new McpModelError(\n `This isn't a model written by ${MODEL_GENERATOR}. Generate one with the json-api-mcp CLI.`,\n )\n }\n if (typeof value['format'] !== 'number' || !READABLE_FORMATS.includes(value['format'])) {\n throw new McpModelError(\n `The model is format ${String(value['format'])} and this runtime reads formats ${READABLE_FORMATS.slice(0, -1).join(', ')} and ${MODEL_FORMAT}. Regenerate it with the same version of ${MODEL_GENERATOR} the server runs.`,\n )\n }\n const contract = value['contract']\n if (typeof contract !== 'number' || contract < MINIMUM_CONTRACT || contract > MAXIMUM_CONTRACT) {\n throw new McpModelError(\n `The model was built from a document with generator contract ${String(contract)}, and this runtime reads contracts ${MINIMUM_CONTRACT} to ${MAXIMUM_CONTRACT}.`,\n )\n }\n if (!isObject(value['descriptor']) || !Array.isArray(value['tools'])) {\n throw new McpModelError('The model has no descriptor or tools; regenerate it.')\n }\n const credentialSchemes = value['credentialSchemes']\n if (\n value['format'] !== 1 &&\n (!Array.isArray(credentialSchemes) || !credentialSchemes.every((s) => typeof s === 'string'))\n ) {\n throw new McpModelError('The model has no credentialSchemes list; regenerate it.')\n }\n const includeDepth = value['includeDepth']\n if (\n (value['format'] as number) >= 3 &&\n includeDepth !== null &&\n !(typeof includeDepth === 'number' && Number.isInteger(includeDepth) && includeDepth >= 0)\n ) {\n throw new McpModelError('The model has no includeDepth; regenerate it.')\n }\n const resourceLinks = value['resourceLinks']\n if (\n value['format'] === MODEL_FORMAT &&\n !(\n isObject(resourceLinks) &&\n Object.values(resourceLinks).every(\n (names) => Array.isArray(names) && names.every((name) => typeof name === 'string'),\n )\n )\n ) {\n throw new McpModelError('The model has no resourceLinks; regenerate it.')\n }\n return value as unknown as McpModel\n}\n\n/**\n * The schemes a format 1 model's caller is taken to authenticate with, since it doesn't record\n * them: every scheme some operation lists scopes under.\n */\nexport function inferCredentialSchemes(model: McpModel): string[] {\n const schemes = new Set<string>()\n for (const tool of model.tools) {\n for (const requirement of tool.operation.security ?? []) {\n for (const [scheme, scopes] of Object.entries(requirement)) {\n if (scopes.length > 0) schemes.add(scheme)\n }\n }\n }\n return [...schemes]\n}\n","import type { McpOperation, McpOperationKind } from './types'\n\n/** The descriptor's key for each kind's operation in a type's `scopes`. */\nconst SCOPE_KEYS: Readonly<Record<Exclude<McpOperationKind, 'related' | 'action'>, string>> = {\n list: 'fetchCollection',\n get: 'fetchOne',\n create: 'create',\n update: 'update',\n delete: 'delete',\n}\n\n/** The scopes the descriptor says an operation needs, or `undefined` when it names none. */\nexport function descriptorScopes(\n op: Pick<McpOperation, 'kind' | 'relation' | 'resource' | 'actionDescriptor'>,\n): readonly string[] | undefined {\n let scopes: readonly string[] | undefined\n if (op.kind === 'action') {\n scopes = op.actionDescriptor?.scopes\n } else if (op.kind === 'related') {\n scopes = op.resource.scopes?.[`fetchRelated.${op.relation ?? ''}`]\n } else {\n scopes = op.resource.scopes?.[SCOPE_KEYS[op.kind]]\n }\n return scopes === undefined || scopes.length === 0 ? undefined : scopes\n}\n","import { errorResult, runCall, type ToolEntry, toToolError } from './call'\nimport {\n assertModel,\n inferCredentialSchemes,\n McpModelError,\n type McpModel,\n type McpModelTool,\n} from './model'\nimport type { SecurityRequirement } from './openapi'\nimport { descriptorScopes } from './scopes'\nimport type {\n JsonApiMcp,\n JsonApiMcpOptions,\n McpCallToolResult,\n McpCapability,\n McpOperation,\n McpRequest,\n McpTool,\n McpToolSession,\n} from './types'\n\n/** Every required scope appears among the granted ones. */\nexport const exactScopeMatcher = (\n granted: readonly string[],\n required: readonly string[],\n): boolean => required.every((scope) => granted.includes(scope))\n\n/**\n * Whether granted scopes satisfy an operation's security. No requirement, or an empty one, needs\n * nothing. Given `credentialSchemes`, the schemes the caller authenticates with, a requirement\n * counts only when it names nothing else, so an operation whose every requirement needs another\n * credential fails. A counted requirement passes when its scopes, across its schemes, all match,\n * or when it lists none. Without `credentialSchemes` every requirement counts.\n */\nexport function satisfiesSecurity(\n security: readonly SecurityRequirement[],\n granted: readonly string[],\n matcher: (granted: readonly string[], required: readonly string[]) => boolean = exactScopeMatcher,\n credentialSchemes?: readonly string[],\n): boolean {\n if (security.length === 0) {\n return true\n }\n return security.some((requirement) => {\n const schemes = Object.keys(requirement)\n if (schemes.length === 0) {\n return true\n }\n if (\n credentialSchemes !== undefined &&\n !schemes.every((scheme) => credentialSchemes.includes(scheme))\n ) {\n return false\n }\n const scopes = Object.values(requirement).flat()\n return scopes.length === 0 || matcher(granted, scopes)\n })\n}\n\n/**\n * The schemes the scope filter counts: the host's, else the model's, else for a format 1 model\n * the schemes it lists scopes under. `undefined`, so every requirement counts, when that's empty.\n */\nfunction resolveCredentialSchemes(\n model: McpModel,\n override: readonly string[] | undefined,\n): readonly string[] | undefined {\n if (override !== undefined) {\n return override\n }\n const schemes = model.credentialSchemes ?? inferCredentialSchemes(model)\n return schemes.length > 0 ? schemes : undefined\n}\n\n/** Rebuild an operation for the hooks, adding back the descriptor entries it names. */\nfunction hydrate(model: McpModel, tool: McpModelTool): ToolEntry {\n const source = tool.operation\n const resource = model.descriptor[source.type]\n if (resource === undefined) {\n throw new McpModelError(`The model's descriptor has no type \"${source.type}\"; regenerate it.`)\n }\n // From format 3 security is stored once: the operation's own, else the document's.\n const security = source.security ?? model.security ?? []\n const operation =\n model.format < 3 || source.security === undefined\n ? source.operation\n : { ...source.operation, security: source.security as SecurityRequirement[] }\n const op: McpOperation = {\n operationId: source.operationId,\n toolName: source.toolName,\n kind: source.kind,\n type: source.type,\n method: source.method,\n path: source.path,\n resource,\n operation,\n security,\n }\n if (source.relation !== undefined) {\n op.relation = source.relation\n const relation = resource.relations[source.relation]\n if (relation !== undefined) op.relationDescriptor = relation\n }\n if (source.action !== undefined) {\n op.action = source.action\n const action = resource.actions?.[source.actionKey ?? source.action]\n if (action !== undefined) op.actionDescriptor = action\n }\n const scopes = source.scopes ?? descriptorScopes(op)\n if (scopes !== undefined) op.scopes = scopes\n const entry: ToolEntry = { op, tool: tool.tool, input: tool.plan, result: tool.result }\n if (source.actionKey !== undefined) entry.actionKey = source.actionKey\n return entry\n}\n\n/**\n * Serve MCP tools from a model the generator wrote: `createJsonApiMcp({ model })` with the parsed\n * artefact. Throws when the artefact isn't one this runtime can read.\n */\nexport function createJsonApiMcp<Ctx = unknown>(options: JsonApiMcpOptions<Ctx>): JsonApiMcp<Ctx> {\n const { hooks = {} } = options\n const model = assertModel(options.model)\n const descriptor = model.descriptor\n const catalogue = { entries: model.tools.map((tool) => hydrate(model, tool)) }\n const defaultBaseUrl = options.baseUrl ?? model.baseUrl\n const credentialSchemes = resolveCredentialSchemes(model, options.credentialSchemes)\n const includeDepth = model.includeDepth ?? null\n const resourceLinks = model.resourceLinks ?? {}\n // A model before format 4 describes the results before the envelope, so it gets those.\n const legacyResults = model.format < 4\n\n async function forRequest(request: McpRequest<Ctx>): Promise<McpToolSession> {\n const ctx = request.context\n let entries: ToolEntry[] = catalogue.entries\n if (request.grantedScopes !== undefined) {\n // null grants nothing, so the filter fails closed.\n const granted = request.grantedScopes ?? []\n entries = entries.filter((e) =>\n satisfiesSecurity(e.op.security, granted, hooks.scopeMatcher, credentialSchemes),\n )\n }\n if (hooks.capabilities !== undefined) {\n const capabilities = await hooks.capabilities(ctx)\n const lookup = (id: string): McpCapability | undefined =>\n capabilities instanceof Map\n ? (capabilities as ReadonlyMap<string, McpCapability>).get(id)\n : (capabilities as Readonly<Record<string, McpCapability>>)[id]\n entries = entries.filter((e) => lookup(e.op.operationId) !== 'denied')\n }\n if (hooks.includeOperation !== undefined) {\n const include = hooks.includeOperation\n const keep = await Promise.all(entries.map((e) => include(e.op, ctx)))\n entries = entries.filter((_, index) => keep[index] === true)\n }\n\n const byName = new Map<string, ToolEntry>()\n const tools: McpTool[] = []\n for (const entry of entries) {\n const tool =\n hooks.decorateTool === undefined\n ? entry.tool\n : await hooks.decorateTool(structuredClone(entry.tool), entry.op, ctx)\n tools.push(tool)\n byName.set(tool.name, entry)\n }\n\n async function callTool(\n name: string,\n args: unknown,\n callOptions: { signal?: AbortSignal } = {},\n ): Promise<McpCallToolResult> {\n const entry = byName.get(name)\n if (entry === undefined) {\n return errorResult({ message: `Unknown tool \"${name}\"`, errors: [], cause: undefined })\n }\n try {\n const init = hooks.requestInit === undefined ? {} : await hooks.requestInit(entry.op, ctx)\n return await runCall(entry, args, {\n descriptor,\n baseUrl: init.baseUrl ?? defaultBaseUrl,\n transport: options.transport,\n headers: init.headers,\n signal: callOptions.signal,\n includeDepth,\n resourceLinks,\n legacyResults,\n })\n } catch (error) {\n const toolError = toToolError(error)\n return errorResult(\n hooks.mapError === undefined ? toolError : await hooks.mapError(toolError, entry.op, ctx),\n )\n }\n }\n\n return { tools, callTool }\n }\n\n return {\n operations: catalogue.entries.map((e) => e.op),\n tools: catalogue.entries.map((e) => e.tool),\n skipped: model.skipped,\n contract: model.contract,\n source: model.source,\n credentialSchemes,\n forRequest,\n }\n}\n","import type { ApiDescriptor } from '@haddowg/json-api-client'\nimport { HTTP_METHODS, type HttpMethod } from './openapi'\nimport type { McpOperationKind } from './types'\n\n/** The name pattern OpenAI's and Anthropic's tool names share. */\nexport const TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/\n\n/** What an `operationId` names, for an operation that becomes a tool. */\nexport interface ParsedOperationId {\n kind: McpOperationKind\n type: string\n relation?: string\n action?: string\n /** The method suffix of a multi-method action's id. */\n method?: HttpMethod\n}\n\nconst CRUD: Readonly<Record<string, McpOperationKind>> = {\n fetchCollection: 'list',\n fetchOne: 'get',\n create: 'create',\n update: 'update',\n delete: 'delete',\n}\n\n/**\n * Parse an `operationId` into what a tool needs. `undefined` for an operation that isn't a tool:\n * relationship endpoints, Atomic Operations, or an id this package doesn't recognise.\n */\nexport function parseOperationId(operationId: string): ParsedOperationId | undefined {\n const parts = operationId.split('.')\n const [verb, type] = parts\n if (verb === undefined || type === undefined || type === '') {\n return undefined\n }\n const crud = CRUD[verb]\n if (crud !== undefined) {\n return parts.length === 2 ? { kind: crud, type } : undefined\n }\n if (verb === 'fetchRelated' && parts.length === 3 && parts[2] !== '') {\n return { kind: 'related', type, relation: parts[2]! }\n }\n if (verb === 'action' && parts[2] !== undefined && parts[2] !== '') {\n if (parts.length === 3) {\n return { kind: 'action', type, action: parts[2] }\n }\n const method = parts[3] as HttpMethod\n if (parts.length === 4 && HTTP_METHODS.includes(method)) {\n return { kind: 'action', type, action: parts[2], method }\n }\n }\n return undefined\n}\n\nconst snake = (value: string): string => value.replace(/-/g, '_')\n\n/**\n * The tool name for a parsed `operationId`: noun first, the type as the document spells it with\n * kebab-case turned into snake_case. A related read ends in `list` or `get` by the relation's\n * cardinality; an action is named after its path, plus the method for a multi-method action.\n */\nexport function toolName(parsed: ParsedOperationId, descriptor: ApiDescriptor): string {\n const type = snake(parsed.type)\n switch (parsed.kind) {\n case 'related': {\n const relation = descriptor[parsed.type]?.relations[parsed.relation!]\n const verb = relation?.cardinality === 'one' ? 'get' : 'list'\n return `${type}_${snake(parsed.relation!)}_${verb}`\n }\n case 'action':\n return parsed.method === undefined\n ? `${type}_${snake(parsed.action!)}`\n : `${type}_${snake(parsed.action!)}_${parsed.method}`\n default:\n return `${type}_${parsed.kind}`\n }\n}\n\n/** Thrown when the tools can't be named: a name too long, with a stray character, or a clash. */\nexport class ToolNameError extends Error {\n readonly problems: readonly string[]\n\n constructor(problems: readonly string[]) {\n super(`Cannot build MCP tools from this document:\\n- ${problems.join('\\n- ')}`)\n this.name = 'ToolNameError'\n this.problems = problems\n }\n}\n\n/** Collect every naming problem across the tools, so one build reports them all. */\nexport function checkToolNames(tools: readonly { name: string; operationId: string }[]): void {\n const problems: string[] = []\n const byName = new Map<string, string[]>()\n for (const { name, operationId } of tools) {\n if (name.length > 64) {\n problems.push(`\"${name}\" (${operationId}) is ${name.length} characters; tool names allow 64`)\n } else if (!TOOL_NAME_PATTERN.test(name)) {\n problems.push(`\"${name}\" (${operationId}) has characters outside [a-zA-Z0-9_-]`)\n }\n byName.set(name, [...(byName.get(name) ?? []), operationId])\n }\n for (const [name, ids] of byName) {\n if (ids.length > 1) {\n problems.push(`\"${name}\" is the tool name of ${ids.join(' and ')}`)\n }\n }\n if (problems.length > 0) {\n throw new ToolNameError(problems)\n }\n}\n","import { isObject, type OperationObject } from './openapi'\n\n/** The vendor extension an action's operation declares its safety in. */\nexport const SAFETY_EXTENSION = 'x-safety'\n\n/** What an action declares about itself. A flag it doesn't declare is absent. */\nexport interface ActionSafety {\n readOnly?: boolean\n destructive?: boolean\n idempotent?: boolean\n}\n\n/**\n * Read an action's declared safety from its operation. An operation without the extension, or\n * with one this package can't read, declares nothing.\n */\nexport function readActionSafety(operation: OperationObject): ActionSafety {\n const raw = operation[SAFETY_EXTENSION]\n const safety: ActionSafety = {}\n if (!isObject(raw)) {\n return safety\n }\n for (const key of ['readOnly', 'destructive', 'idempotent'] as const) {\n if (typeof raw[key] === 'boolean') {\n safety[key] = raw[key]\n }\n }\n return safety\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/contract.ts","../src/openapi.ts","../src/portable.ts","../src/inputs.ts","../src/flatten.ts","../src/call.ts","../src/model.ts","../src/scopes.ts","../src/mcp.ts","../src/names.ts","../src/safety.ts"],"sourcesContent":["import type { OpenApiDocument } from './openapi'\n\n/** The oldest generator contract this package reads: the baseline current servers stamp. */\nexport const MINIMUM_CONTRACT = 13\n\n/** The newest generator contract this package reads. */\nexport const MAXIMUM_CONTRACT = 19\n\n/**\n * Throw unless the document's `info.x-generator.contract` is in the range this package reads. A\n * document outside it would yield tools that miss or misread what the server offers, so there is\n * no warning mode.\n */\nexport function assertContract(doc: OpenApiDocument): number {\n const contract = doc.info?.['x-generator']?.contract\n if (typeof contract !== 'number') {\n throw new Error(\n `The OpenAPI document declares no generator contract (info.x-generator.contract). @haddowg/json-api-mcp reads contracts ${MINIMUM_CONTRACT} to ${MAXIMUM_CONTRACT}; regenerate the document from a newer haddowg/json-api server.`,\n )\n }\n if (contract < MINIMUM_CONTRACT) {\n throw new Error(\n `The OpenAPI document declares generator contract ${contract}, older than this package reads (${MINIMUM_CONTRACT} to ${MAXIMUM_CONTRACT}). Regenerate it from a newer haddowg/json-api server.`,\n )\n }\n if (contract > MAXIMUM_CONTRACT) {\n throw new Error(\n `The OpenAPI document declares generator contract ${contract}, newer than this package reads (${MINIMUM_CONTRACT} to ${MAXIMUM_CONTRACT}). Upgrade @haddowg/json-api-mcp.`,\n )\n }\n return contract\n}\n","/**\n * The parts of an OpenAPI 3.1 document this package reads, typed loosely: every object keeps its\n * other members, vendor extensions included, so hooks see the operation as the server wrote it.\n */\n\n/** A JSON Schema node as the document writes it. */\nexport type JsonSchema = { [key: string]: unknown }\n\n/** One security requirement: scheme name to the scopes it needs. */\nexport type SecurityRequirement = Readonly<Record<string, readonly string[]>>\n\nexport interface ParameterObject {\n name: string\n in: string\n description?: string\n required?: boolean\n schema?: JsonSchema\n [key: string]: unknown\n}\n\nexport interface MediaTypeObject {\n schema?: JsonSchema\n [key: string]: unknown\n}\n\nexport interface RequestBodyObject {\n description?: string\n required?: boolean\n content?: Record<string, MediaTypeObject>\n [key: string]: unknown\n}\n\nexport interface ResponseObject {\n description?: string\n content?: Record<string, MediaTypeObject>\n [key: string]: unknown\n}\n\nexport interface OperationObject {\n operationId?: string\n summary?: string\n description?: string\n parameters?: (ParameterObject | { $ref: string })[]\n requestBody?: RequestBodyObject | { $ref: string }\n responses?: Record<string, ResponseObject | { $ref: string }>\n security?: SecurityRequirement[]\n [key: string]: unknown\n}\n\nexport type HttpMethod = 'get' | 'post' | 'patch' | 'put' | 'delete'\n\nexport const HTTP_METHODS: readonly HttpMethod[] = ['get', 'post', 'patch', 'put', 'delete']\n\nexport type PathItemObject = Partial<Record<HttpMethod, OperationObject>> & {\n parameters?: (ParameterObject | { $ref: string })[]\n [key: string]: unknown\n}\n\nexport interface OpenApiDocument {\n openapi?: string\n info?: { 'x-generator'?: { contract?: unknown; [key: string]: unknown }; [key: string]: unknown }\n servers?: { url: string; [key: string]: unknown }[]\n paths?: Record<string, PathItemObject>\n components?: {\n schemas?: Record<string, JsonSchema>\n parameters?: Record<string, ParameterObject>\n requestBodies?: Record<string, RequestBodyObject>\n responses?: Record<string, ResponseObject>\n [key: string]: unknown\n }\n security?: SecurityRequirement[]\n [key: string]: unknown\n}\n\nexport const isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\n/** Follow a local `#/...` reference. `undefined` for anything else or a dangling one. */\nexport function resolvePointer(doc: OpenApiDocument, ref: string): unknown {\n if (!ref.startsWith('#/')) {\n return undefined\n }\n let node: unknown = doc\n for (const raw of ref.slice(2).split('/')) {\n const segment = decodeURIComponent(raw).replace(/~1/g, '/').replace(/~0/g, '~')\n if (!isObject(node) || !Object.hasOwn(node, segment)) {\n return undefined\n }\n node = node[segment]\n }\n return node\n}\n\n/** Follow `$ref`s until a node that isn't one. A cycle or dangling ref gives `undefined`. */\nexport function deref<T>(\n doc: OpenApiDocument,\n value: T | { $ref: string } | undefined,\n): T | undefined {\n let node: unknown = value\n const seen = new Set<string>()\n while (isObject(node) && typeof node['$ref'] === 'string') {\n const ref = node['$ref']\n if (seen.has(ref)) {\n return undefined\n }\n seen.add(ref)\n node = resolvePointer(doc, ref)\n }\n return node as T | undefined\n}\n\n/** An operation's parameters, path-level ones included, with refs followed. */\nexport function operationParameters(\n doc: OpenApiDocument,\n item: PathItemObject,\n op: OperationObject,\n): ParameterObject[] {\n const out = new Map<string, ParameterObject>()\n for (const raw of [...(item.parameters ?? []), ...(op.parameters ?? [])]) {\n const param = deref<ParameterObject>(doc, raw)\n if (param !== undefined && typeof param.name === 'string') {\n out.set(`${param.in}:${param.name}`, param)\n }\n }\n return [...out.values()]\n}\n","/**\n * Convert document schemas into tool input schemas every major model API accepts: OpenAI's strict\n * function calling and Anthropic's strict tool use. That means no `oneOf`, `allOf`, `if`, `then`\n * or `$ref`, every object closed with `additionalProperties: false` and listing all its\n * properties as required, an optional property made nullable instead, and no numeric, length or\n * pattern keywords. Constraints that can't stay as keywords move into the description, and a\n * value with no portable shape, such as a free-form object, is taken as JSON text.\n *\n * Each schema comes with a {@link Decoder} that turns the arguments a model sends back into the\n * value the API expects: a `null` the conversion added is dropped, and JSON text is parsed.\n */\nimport { isObject, type JsonSchema, type OpenApiDocument, resolvePointer } from './openapi'\n\n/** The string formats both APIs accept. */\nexport const PORTABLE_FORMATS: ReadonlySet<string> = new Set([\n 'date-time',\n 'time',\n 'date',\n 'duration',\n 'email',\n 'hostname',\n 'ipv4',\n 'ipv6',\n 'uuid',\n])\n\n/** Objects may nest this deep before a value is taken as JSON text instead. */\nconst MAX_DEPTH = 6\n\nexport type Decoder =\n | { kind: 'value' }\n | { kind: 'json' }\n | { kind: 'object'; props: Readonly<Record<string, PropertyDecoder>> }\n | { kind: 'array'; items: Decoder }\n\nexport interface PropertyDecoder {\n decoder: Decoder\n /** The conversion made this property nullable, so `null` means \"not given\". */\n nullMeansOmit: boolean\n}\n\nexport interface Portable {\n schema: JsonSchema\n decoder: Decoder\n}\n\nconst VALUE: Decoder = { kind: 'value' }\n\n/** Thrown when a model's argument can't be decoded, such as JSON text that doesn't parse. */\nexport class ArgumentError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'ArgumentError'\n }\n}\n\n/** Turn a model's argument back into the value the API expects. */\nexport function decode(value: unknown, decoder: Decoder, path = 'value'): unknown {\n if (value === null || value === undefined) {\n return value\n }\n switch (decoder.kind) {\n case 'json':\n if (typeof value !== 'string') {\n return value\n }\n try {\n return JSON.parse(value) as unknown\n } catch {\n throw new ArgumentError(`${path} must be JSON text`)\n }\n case 'array':\n return Array.isArray(value)\n ? value.map((item, index) => decode(item, decoder.items, `${path}[${index}]`))\n : value\n case 'object': {\n if (!isObject(value)) {\n return value\n }\n const out: Record<string, unknown> = {}\n for (const [key, member] of Object.entries(value)) {\n const prop = decoder.props[key]\n if (prop === undefined) {\n out[key] = member\n } else if (!(member === null && prop.nullMeansOmit)) {\n out[key] = decode(member, prop.decoder, `${path}.${key}`)\n }\n }\n return out\n }\n default:\n return value\n }\n}\n\n/** Add `null` to a portable schema's allowed values. */\nexport function nullable(schema: JsonSchema): JsonSchema {\n const out = { ...schema }\n if (Array.isArray(out['anyOf'])) {\n const branches = out['anyOf'] as JsonSchema[]\n if (!branches.some((b) => b['type'] === 'null')) {\n out['anyOf'] = [...branches, { type: 'null' }]\n }\n return out\n }\n const type = out['type']\n if (typeof type === 'string' && type !== 'null') {\n out['type'] = [type, 'null']\n } else if (Array.isArray(type) && !type.includes('null')) {\n out['type'] = [...(type as string[]), 'null']\n }\n if (Array.isArray(out['enum']) && !(out['enum'] as unknown[]).includes(null)) {\n out['enum'] = [...(out['enum'] as unknown[]), null]\n }\n return out\n}\n\n/** Whether a portable schema already allows `null`. */\nexport function allowsNull(schema: JsonSchema): boolean {\n const type = schema['type']\n if (type === 'null' || (Array.isArray(type) && type.includes('null'))) {\n return true\n }\n return Array.isArray(schema['anyOf']) && (schema['anyOf'] as JsonSchema[]).some(allowsNull)\n}\n\n/** A closed object schema listing every property as required, as strict mode wants. */\nexport function closedObject(\n properties: Record<string, JsonSchema>,\n description?: string,\n): JsonSchema {\n const schema: JsonSchema = {\n type: 'object',\n properties,\n required: Object.keys(properties),\n additionalProperties: false,\n }\n if (description !== undefined && description !== '') {\n schema['description'] = description\n }\n return schema\n}\n\nconst jsonText = (description: string | undefined): Portable => ({\n schema: {\n type: 'string',\n description: joinText(description, 'Send the value as JSON text.'),\n },\n decoder: { kind: 'json' },\n})\n\nfunction joinText(...parts: (string | undefined)[]): string {\n return parts\n .filter((part): part is string => part !== undefined && part.trim() !== '')\n .map((part) => part.trim())\n .join(' ')\n}\n\n/** The non-null `type` keys a schema names. */\nfunction typeKeys(schema: JsonSchema): { types: string[]; nullable: boolean } {\n const raw = schema['type']\n const list = typeof raw === 'string' ? [raw] : Array.isArray(raw) ? (raw as string[]) : []\n return { types: list.filter((t) => t !== 'null'), nullable: list.includes('null') }\n}\n\nfunction inferType(values: readonly unknown[]): string | undefined {\n const types = new Set(\n values\n .filter((v) => v !== null)\n .map((v) =>\n typeof v === 'number' ? (Number.isInteger(v) ? 'integer' : 'number') : typeof v,\n ),\n )\n if (types.size === 1) {\n return [...types][0]\n }\n if (types.size === 2 && types.has('integer') && types.has('number')) {\n return 'number'\n }\n return undefined\n}\n\n/** A range as a sentence, such as `Between 1 and 200 characters.` */\nfunction bounds(\n min: number | undefined,\n max: number | undefined,\n unit: string,\n): string | undefined {\n const suffix = unit === '' ? '' : ` ${unit}`\n if (min !== undefined && max !== undefined) return `Between ${min} and ${max}${suffix}.`\n if (min !== undefined) return `At least ${min}${suffix}.`\n if (max !== undefined) return `At most ${max}${suffix}.`\n return undefined\n}\n\n/** The constraints a schema states that can't stay as keywords, as sentences. */\nfunction constraintNote(schema: JsonSchema): string | undefined {\n const num = (key: string): number | undefined =>\n typeof schema[key] === 'number' ? schema[key] : undefined\n const notes = [\n bounds(num('minLength'), num('maxLength'), 'characters'),\n bounds(num('minimum'), num('maximum'), ''),\n num('exclusiveMinimum') === undefined ? undefined : `Greater than ${num('exclusiveMinimum')}.`,\n num('exclusiveMaximum') === undefined ? undefined : `Less than ${num('exclusiveMaximum')}.`,\n num('multipleOf') === undefined ? undefined : `A multiple of ${num('multipleOf')}.`,\n bounds(num('minItems'), num('maxItems'), 'items'),\n typeof schema['pattern'] === 'string'\n ? `Matches the pattern \\`${schema['pattern']}\\`.`\n : undefined,\n typeof schema['format'] === 'string' && !PORTABLE_FORMATS.has(schema['format'])\n ? `Format: ${schema['format']}.`\n : undefined,\n schema['default'] === undefined\n ? undefined\n : `Defaults to ${JSON.stringify(schema['default'])}.`,\n ].filter((note): note is string => note !== undefined)\n return notes.length === 0 ? undefined : notes.join(' ')\n}\n\n/** Merge an `allOf` into one schema: the members' keywords, then the outer node's own. */\nfunction mergeAllOf(\n doc: OpenApiDocument,\n node: JsonSchema,\n stack: readonly string[],\n): JsonSchema | undefined {\n const members = node['allOf'] as unknown[]\n const merged: JsonSchema = {}\n const properties: Record<string, unknown> = {}\n const required = new Set<string>()\n for (const member of members) {\n const resolved = follow(doc, member, stack)\n if (resolved === undefined) {\n return undefined\n }\n const inner = Array.isArray(resolved.node['allOf'])\n ? mergeAllOf(doc, resolved.node, resolved.stack)\n : resolved.node\n if (inner === undefined) {\n return undefined\n }\n Object.assign(properties, isObject(inner['properties']) ? inner['properties'] : {})\n for (const key of Array.isArray(inner['required']) ? (inner['required'] as string[]) : []) {\n required.add(key)\n }\n Object.assign(merged, inner)\n }\n const { allOf: _allOf, ...own } = node\n Object.assign(merged, own)\n if (Object.keys(properties).length > 0) {\n merged['properties'] = {\n ...properties,\n ...(isObject(own['properties']) ? own['properties'] : {}),\n }\n }\n if (required.size > 0 || Array.isArray(own['required'])) {\n merged['required'] = [\n ...required,\n ...(Array.isArray(own['required']) ? (own['required'] as string[]) : []),\n ]\n }\n return merged\n}\n\n/**\n * Follow a node's `$ref`s, keeping the outermost description. `undefined` on a cycle, which the\n * caller then takes as JSON text.\n */\nfunction follow(\n doc: OpenApiDocument,\n value: unknown,\n stack: readonly string[],\n): { node: JsonSchema; stack: readonly string[] } | undefined {\n let node: unknown = value\n let description: string | undefined\n let path = stack\n while (isObject(node) && typeof node['$ref'] === 'string') {\n const ref = node['$ref']\n if (description === undefined && typeof node['description'] === 'string') {\n description = node['description']\n }\n if (path.includes(ref)) {\n return undefined\n }\n path = [...path, ref]\n node = resolvePointer(doc, ref)\n }\n if (node === true) {\n return { node: {}, stack: path }\n }\n if (!isObject(node)) {\n return undefined\n }\n return {\n node: description === undefined ? node : { ...node, description },\n stack: path,\n }\n}\n\nconst isNull = (b: unknown): boolean => isObject(b) && b['type'] === 'null'\n\n/** Convert one document schema into a portable input schema and its decoder. */\nexport function toPortable(\n doc: OpenApiDocument,\n raw: unknown,\n stack: readonly string[] = [],\n depth = 0,\n): Portable {\n const outerDescription =\n isObject(raw) && typeof raw['description'] === 'string' ? raw['description'] : undefined\n const followed = follow(doc, raw, stack)\n if (followed === undefined) {\n return jsonText(outerDescription)\n }\n let node = followed.node\n const path = followed.stack\n if (Array.isArray(node['allOf'])) {\n const merged = mergeAllOf(doc, node, path)\n if (merged === undefined) {\n return jsonText(outerDescription)\n }\n node = merged\n }\n const description = typeof node['description'] === 'string' ? node['description'] : undefined\n\n const branches = (node['oneOf'] ?? node['anyOf']) as unknown[] | undefined\n if (Array.isArray(branches)) {\n const { oneOf: _oneOf, anyOf: _anyOf, ...rest } = node\n const others = branches.filter((b) => !isNull(b))\n const withNull = others.length < branches.length\n if (others.length === 1) {\n const resolved = follow(doc, others[0], path)\n if (resolved === undefined) {\n return jsonText(description)\n }\n const single = toPortable(doc, { ...resolved.node, ...rest }, resolved.stack, depth)\n return withNull ? { ...single, schema: nullable(single.schema) } : single\n }\n if (depth >= MAX_DEPTH) {\n return jsonText(description)\n }\n const converted = others.map((b) => toPortable(doc, b, path, depth + 1).schema)\n const schema: JsonSchema = { anyOf: withNull ? [...converted, { type: 'null' }] : converted }\n const text = joinText(description, constraintNote(node))\n if (text !== '') {\n schema['description'] = text\n }\n return { schema, decoder: VALUE }\n }\n\n let { types, nullable: isNullable } = typeKeys(node)\n const enumValues = Array.isArray(node['enum'])\n ? (node['enum'] as unknown[])\n : node['const'] !== undefined\n ? [node['const']]\n : undefined\n if (enumValues !== undefined && enumValues.includes(null)) {\n isNullable = true\n }\n if (types.length === 0) {\n const inferred =\n enumValues !== undefined\n ? inferType(enumValues)\n : isObject(node['properties'])\n ? 'object'\n : node['items'] !== undefined\n ? 'array'\n : undefined\n if (inferred === undefined) {\n return jsonText(description)\n }\n types = [inferred]\n }\n if (types.length > 1) {\n const schema: JsonSchema = {\n anyOf: [...types.map((type) => ({ type })), ...(isNullable ? [{ type: 'null' }] : [])],\n }\n if (description !== undefined) {\n schema['description'] = description\n }\n return { schema, decoder: VALUE }\n }\n\n const type = types[0]!\n const text = joinText(description, constraintNote(node))\n let result: Portable\n\n if (type === 'object') {\n const props = isObject(node['properties']) ? node['properties'] : {}\n const writable = Object.entries(props).filter(\n ([, prop]) => !(isObject(prop) && prop['readOnly'] === true),\n )\n if (writable.length === 0 || depth >= MAX_DEPTH) {\n return jsonText(text)\n }\n const required = new Set(Array.isArray(node['required']) ? (node['required'] as string[]) : [])\n const properties: Record<string, JsonSchema> = {}\n const decoders: Record<string, PropertyDecoder> = {}\n for (const [key, prop] of writable) {\n const converted = toPortable(doc, prop, path, depth + 1)\n const optional = !required.has(key)\n const addNull = optional && !allowsNull(converted.schema)\n properties[key] = addNull ? nullable(converted.schema) : converted.schema\n decoders[key] = { decoder: converted.decoder, nullMeansOmit: addNull }\n }\n result = {\n schema: closedObject(properties, text),\n decoder: { kind: 'object', props: decoders },\n }\n } else if (type === 'array') {\n if (node['items'] === undefined || node['items'] === true || depth >= MAX_DEPTH) {\n return jsonText(text)\n }\n const items = toPortable(doc, node['items'], path, depth + 1)\n result = {\n schema: { type: 'array', items: items.schema },\n decoder: { kind: 'array', items: items.decoder },\n }\n if (text !== '') {\n result.schema['description'] = text\n }\n } else {\n const schema: JsonSchema = { type }\n if (text !== '') {\n schema['description'] = text\n }\n if (enumValues !== undefined) {\n schema['enum'] = enumValues.filter((v) => v !== null)\n }\n if (\n type === 'string' &&\n typeof node['format'] === 'string' &&\n PORTABLE_FORMATS.has(node['format'])\n ) {\n schema['format'] = node['format']\n }\n result = { schema, decoder: VALUE }\n }\n\n return isNullable ? { ...result, schema: nullable(result.schema) } : result\n}\n","/**\n * Each tool's input schema, and the plan the call handler follows to turn a model's arguments\n * back into a client call. Every schema is portable (see ./portable.ts): a parameter the API\n * doesn't require is nullable, and `null` at the top level means \"not given\".\n */\nimport {\n type ActionDescriptor,\n type ApiDescriptor,\n type CountableDescriptor,\n isPolymorphic,\n type PaginatorDescriptor,\n type RelationDescriptor,\n resolvePaginator,\n} from '@haddowg/json-api-client'\nimport {\n deref,\n isObject,\n type JsonSchema,\n type OpenApiDocument,\n type OperationObject,\n type ParameterObject,\n type RequestBodyObject,\n} from './openapi'\nimport { capIncludePaths, includePaths } from './outputs'\nimport {\n allowsNull,\n closedObject,\n type Decoder,\n nullable,\n type PropertyDecoder,\n toPortable,\n} from './portable'\n\n/** The include description, for a tool capped at `depth` relationships or uncapped. */\nexport function includeDescription(depth: number | null): string {\n const reach =\n depth === null\n ? ''\n : depth === 1\n ? ' Only direct relationships can be included, so relations of an included resource come back as ids.'\n : ` Paths reach at most ${depth} relationships deep, and relations below that come back as ids.`\n return `Relationship paths to include in the result (JSON:API compound documents). An included relation comes back as the related resource in place of its id.${reach} Leave null to include nothing.`\n}\n\nexport const FIELDS_DESCRIPTION =\n 'Sparse fieldsets (JSON:API): per resource type, the attribute and relationship names to return. A relationship only has a value in the result when it is also included. Leave a type null for its default fields.'\n\n/** The Countable profile's token for counting the collection itself. */\nexport const SELF_COUNT = '_self_'\n\nexport const WITH_COUNT_DESCRIPTION =\n 'Set true to have the total number of results counted and returned in meta. Counting can be slow on a large collection, so leave it null unless the total is needed.'\n\nexport const PAGE_LINK_DESCRIPTION =\n 'A pagination link (first, prev, next or last) copied from the links of an earlier result of this same tool, to fetch that page. When given, every other argument is ignored.'\n\n/** How the handler reads a read tool's query arguments. */\nexport interface QueryPlan {\n filter?: Decoder\n sort: boolean\n include: boolean\n /**\n * The include paths the tool accepts, the ones its input lists. A model before format 3\n * doesn't carry them and its tools accept any path.\n */\n includePaths?: readonly string[]\n fields: readonly string[]\n /** The wire key `pageSize` maps to, when the endpoint paginates by a size. */\n sizeKey?: string\n maxPageSize?: number\n defaultPageSize?: number\n /** Whether `pageLink` is offered: the endpoint paginates at all. */\n paginated: boolean\n /**\n * Whether `withCount` is offered: the endpoint counts its own collection under the Countable\n * profile, sent as `withCount=_self_` with this profile negotiated.\n */\n countProfile?: string\n}\n\n/** One top-level write field: an attribute, or a relation sent as ids. */\nexport interface WriteField {\n relation?: { cardinality: 'one' | 'many'; types: readonly string[]; polymorphic?: boolean }\n property: PropertyDecoder\n}\n\n/** How the handler reads a create, update or flat document action's arguments. */\nexport interface WritePlan {\n fields: Readonly<Record<string, WriteField>>\n /** The client id argument: whether the body takes one. */\n clientId: 'none' | 'optional' | 'required'\n /** Fields `clear` may name, for an update. */\n clearable: readonly string[]\n}\n\n/** How the handler reads a custom action's body. */\nexport type ActionBodyPlan =\n | { kind: 'none' }\n | { kind: 'flat'; write: WritePlan }\n | {\n kind: 'body'\n decoder: Decoder\n required: boolean\n /** The request media type the body goes out as. */\n mediaType: string\n mediaKind: 'document' | 'json' | 'text' | 'form'\n }\n\nexport interface ToolInput {\n schema: JsonSchema & { type: 'object' }\n query?: QueryPlan\n write?: WritePlan\n action?: ActionBodyPlan\n}\n\nconst nullableString = (description: string): JsonSchema =>\n nullable({ type: 'string', description })\n\nconst idSchema = (description: string): JsonSchema => ({ type: 'string', description })\n\n/** The `filter[...]` parameters as one closed object, each member nullable. */\nfunction filterInput(\n doc: OpenApiDocument,\n params: readonly ParameterObject[],\n): { schema: JsonSchema; decoder: Decoder } | undefined {\n const properties: Record<string, JsonSchema> = {}\n const props: Record<string, PropertyDecoder> = {}\n for (const param of params) {\n const match = /^filter\\[(.+)\\]$/.exec(param.name)\n if (param.in !== 'query' || match === null) {\n continue\n }\n const converted = toPortable(doc, param.schema ?? {}, [], 1)\n const schema = { ...converted.schema }\n if (typeof param.description === 'string' && schema['description'] === undefined) {\n schema['description'] = param.description\n } else if (typeof param.description === 'string') {\n schema['description'] = `${param.description} ${String(schema['description'])}`\n }\n const addNull = !allowsNull(schema)\n properties[match[1]!] = addNull ? nullable(schema) : schema\n props[match[1]!] = { decoder: converted.decoder, nullMeansOmit: true }\n }\n if (Object.keys(properties).length === 0) {\n return undefined\n }\n return {\n schema: nullable(\n closedObject(\n properties,\n 'Filters to apply. Leave a filter null to not apply it, or the whole object null for none.',\n ),\n ),\n decoder: { kind: 'object', props },\n }\n}\n\n/** The page parameter's size bound and default for the strategy that answers by default. */\nfunction pageBounds(\n doc: OpenApiDocument,\n params: readonly ParameterObject[],\n kind: string,\n sizeKey: string,\n): { max?: number; default?: number } {\n const param = params.find((p) => p.in === 'query' && p.name === 'page')\n let schema = deref<JsonSchema>(doc, param?.schema)\n const branches = schema?.['oneOf'] ?? schema?.['anyOf']\n if (Array.isArray(branches)) {\n schema = branches\n .map((b) => deref<JsonSchema>(doc, b as JsonSchema))\n .find((b) => isObject(b?.['x-paginator']) && b['x-paginator']['strategy'] === kind)\n }\n const size = isObject(schema?.['properties']) ? schema['properties'][sizeKey] : undefined\n if (!isObject(size)) {\n return {}\n }\n const out: { max?: number; default?: number } = {}\n if (typeof size['maximum'] === 'number') out.max = size['maximum']\n if (typeof size['default'] === 'number') out.default = size['default']\n return out\n}\n\n/** A read tool's query arguments, read from the operation's own parameters. */\nfunction queryInput(\n ctx: InputContext,\n paginator: PaginatorDescriptor | undefined,\n collection: boolean,\n): { properties: Record<string, JsonSchema>; plan: QueryPlan } {\n const { doc, params, defaultPageSize, includeDepth } = ctx\n const properties: Record<string, JsonSchema> = {}\n const plan: QueryPlan = { sort: false, include: false, fields: [], paginated: false }\n\n if (collection) {\n const filter = filterInput(doc, params)\n if (filter !== undefined) {\n properties['filter'] = filter.schema\n plan.filter = filter.decoder\n }\n const sort = params.find((p) => p.in === 'query' && p.name === 'sort')\n const sortItems = deref<JsonSchema>(doc, sort?.schema?.['items'] as JsonSchema | undefined)\n const tokens = Array.isArray(sortItems?.['enum']) ? (sortItems['enum'] as string[]) : []\n if (tokens.length > 0) {\n properties['sort'] = nullable({\n type: 'array',\n items: { type: 'string', enum: tokens },\n description:\n 'Fields to sort by, in order. A leading `-` sorts that field descending. Leave null for the default order.',\n })\n plan.sort = true\n }\n }\n\n const includable = params.some((p) => p.in === 'query' && p.name === 'include')\n const paths = includable ? capIncludePaths(includePaths(doc, ctx.operation), includeDepth) : []\n if (paths.length > 0) {\n properties['include'] = nullable({\n type: 'array',\n items: { type: 'string', enum: paths },\n description: includeDescription(includeDepth),\n })\n plan.include = true\n plan.includePaths = paths\n }\n\n const fieldTypes = params\n .map((p) => (p.in === 'query' ? /^fields\\[(.+)\\]$/.exec(p.name)?.[1] : undefined))\n .filter((t): t is string => t !== undefined)\n if (fieldTypes.length > 0) {\n const members: Record<string, JsonSchema> = {}\n for (const type of fieldTypes) {\n members[type] = nullable({ type: 'array', items: { type: 'string' } })\n }\n properties['fields'] = nullable(closedObject(members, FIELDS_DESCRIPTION))\n plan.fields = fieldTypes\n }\n\n if (collection && paginator !== undefined) {\n const resolved = resolvePaginator(paginator)\n if (resolved.kind !== 'none') {\n plan.paginated = true\n const sizeKey = resolved.keys.size ?? resolved.keys.limit\n if (sizeKey !== undefined) {\n const bounds = pageBounds(doc, params, resolved.kind, sizeKey)\n const fallback = Math.min(defaultPageSize, bounds.max ?? defaultPageSize)\n plan.sizeKey = sizeKey\n plan.defaultPageSize = fallback\n if (bounds.max !== undefined) plan.maxPageSize = bounds.max\n const range = bounds.max === undefined ? 'at least 1' : `1 to ${bounds.max}`\n properties['pageSize'] = nullable({\n type: 'integer',\n description: `How many results a page holds, ${range}. Leave null for ${fallback}.`,\n })\n }\n properties['pageLink'] = nullableString(PAGE_LINK_DESCRIPTION)\n }\n }\n\n return { properties, plan }\n}\n\n/** The `data` member's schema of a write request body. */\nfunction requestData(doc: OpenApiDocument, schema: unknown): JsonSchema | undefined {\n const body = deref<JsonSchema>(doc, schema as JsonSchema)\n const data = isObject(body?.['properties']) ? body['properties']['data'] : undefined\n return deref<JsonSchema>(doc, data as JsonSchema)\n}\n\n/** A relation's write argument: an id, ids, or `{ type, id }` objects for a polymorphic one. */\nfunction relationArgument(\n name: string,\n relation: RelationDescriptor,\n allowNull: boolean,\n): JsonSchema {\n const polymorphic = isPolymorphic(relation)\n const one: JsonSchema = polymorphic\n ? closedObject({\n type: { type: 'string', enum: [...relation.types] },\n id: { type: 'string' },\n })\n : { type: 'string' }\n const target = polymorphic ? relation.types.join(' or ') : relation.types[0]\n if (relation.cardinality === 'many') {\n return {\n type: 'array',\n items: one,\n description: polymorphic\n ? `The ${name} relationship: every related resource, as its type (${target}) and id.`\n : `The ${name} relationship: the ids of the related \\`${target}\\` resources.`,\n }\n }\n const schema: JsonSchema = {\n ...one,\n description: polymorphic\n ? `The ${name} relationship: the related resource's type (${target}) and id.`\n : `The ${name} relationship: the id of the related \\`${target}\\` resource.`,\n }\n return allowNull ? nullable(schema) : schema\n}\n\n/**\n * A flat write input from a JSON:API write request body: the attributes and relationships as\n * one object, the API's required members required, the rest nullable.\n */\nfunction writeInput(\n doc: OpenApiDocument,\n descriptor: ApiDescriptor,\n type: string,\n bodySchema: unknown,\n mode: 'create' | 'update' | 'action',\n): { properties: Record<string, JsonSchema>; required: string[]; plan: WritePlan } {\n const properties: Record<string, JsonSchema> = {}\n const required: string[] = []\n const fields: Record<string, WriteField> = {}\n const clearable: string[] = []\n const data = requestData(doc, bodySchema)\n const dataProps = isObject(data?.['properties']) ? data['properties'] : {}\n\n const attributes = deref<JsonSchema>(doc, dataProps['attributes'] as JsonSchema)\n const converted = attributes === undefined ? undefined : toPortable(doc, attributes, [], 0)\n if (converted !== undefined && converted.decoder.kind === 'object') {\n const attrRequired = new Set(\n Array.isArray(attributes!['required']) ? (attributes!['required'] as string[]) : [],\n )\n const attrProps = converted.schema['properties'] as Record<string, JsonSchema>\n for (const [name, prop] of Object.entries(converted.decoder.props)) {\n const schema = attrProps[name]!\n // The converted object already made every optional member nullable.\n properties[name] = schema\n const isRequired = mode !== 'update' && attrRequired.has(name)\n // At the top level null means \"not given\", except for a required member the API lets be null.\n fields[name] = { property: { decoder: prop.decoder, nullMeansOmit: !isRequired } }\n if (isRequired) {\n required.push(name)\n }\n if (!prop.nullMeansOmit && allowsNull(schema)) {\n clearable.push(name)\n }\n }\n }\n\n const relationships = deref<JsonSchema>(doc, dataProps['relationships'] as JsonSchema)\n const relProps = isObject(relationships?.['properties']) ? relationships['properties'] : {}\n const relRequired = new Set(\n Array.isArray(relationships?.['required']) ? (relationships['required'] as string[]) : [],\n )\n for (const [name, raw] of Object.entries(relProps)) {\n const relation = descriptor[type]?.relations[name]\n if (relation === undefined || name in properties) {\n continue\n }\n const relSchema = deref<JsonSchema>(doc, raw as JsonSchema)\n const linkage = isObject(relSchema?.['properties'])\n ? relSchema['properties']['data']\n : undefined\n const linkageAllowsNull =\n relation.cardinality === 'one' && JSON.stringify(deref(doc, linkage) ?? {}).includes('\"null\"')\n const isRequired = mode !== 'update' && relRequired.has(name)\n const schema = relationArgument(name, relation, linkageAllowsNull)\n properties[name] = isRequired || allowsNull(schema) ? schema : nullable(schema)\n fields[name] = {\n relation: isPolymorphic(relation)\n ? { cardinality: relation.cardinality, types: relation.types, polymorphic: true }\n : { cardinality: relation.cardinality, types: relation.types },\n property: { decoder: { kind: 'value' }, nullMeansOmit: !isRequired },\n }\n if (isRequired) {\n required.push(name)\n }\n if (linkageAllowsNull) {\n clearable.push(name)\n }\n }\n\n let clientId: WritePlan['clientId'] = 'none'\n if (mode === 'create') {\n const idProp = dataProps['id']\n const dataRequired = Array.isArray(data?.['required']) ? (data['required'] as string[]) : []\n if (idProp !== undefined && idProp !== false) {\n clientId = dataRequired.includes('id') ? 'required' : 'optional'\n }\n }\n\n return {\n properties,\n required,\n plan: { fields, clientId, clearable: mode === 'update' ? clearable : [] },\n }\n}\n\n/** Close a root object: every property required, the optional ones already nullable. */\nfunction root(\n properties: Record<string, JsonSchema>,\n required: readonly string[],\n): JsonSchema & { type: 'object' } {\n const out: Record<string, JsonSchema> = {}\n for (const [name, schema] of Object.entries(properties)) {\n out[name] = required.includes(name) || allowsNull(schema) ? schema : nullable(schema)\n }\n return closedObject(out) as JsonSchema & { type: 'object' }\n}\n\nexport interface InputContext {\n doc: OpenApiDocument\n descriptor: ApiDescriptor\n params: readonly ParameterObject[]\n operation: OperationObject\n defaultPageSize: number\n /** How many relationships deep `include` may reach; null for every declared path. */\n includeDepth: number | null\n}\n\n/** A list or to-many related read. */\nexport function collectionInput(\n ctx: InputContext,\n paginator: PaginatorDescriptor | undefined,\n parent: { type: string; relation: string } | undefined,\n countable: CountableDescriptor | undefined,\n): ToolInput {\n const { properties, plan } = queryInput(ctx, paginator, true)\n if (countable !== undefined && countable.tokens.includes(SELF_COUNT)) {\n properties['withCount'] = nullable({ type: 'boolean', description: WITH_COUNT_DESCRIPTION })\n plan.countProfile = countable.profile\n }\n const props: Record<string, JsonSchema> = {}\n const required: string[] = []\n if (parent !== undefined) {\n props['id'] = idSchema(\n `The id of the \\`${parent.type}\\` resource whose ${parent.relation} to list.`,\n )\n required.push('id')\n }\n return { schema: root({ ...props, ...properties }, required), query: plan }\n}\n\n/** A get or to-one related read. */\nexport function singleInput(ctx: InputContext, description: string): ToolInput {\n const { properties, plan } = queryInput(ctx, undefined, false)\n return { schema: root({ id: idSchema(description), ...properties }, ['id']), query: plan }\n}\n\n/** A create, from the type's create request body. */\nexport function createInput(ctx: InputContext, type: string): ToolInput {\n const body = deref<RequestBodyObject>(ctx.doc, ctx.operation.requestBody)\n const schema = body?.content?.['application/vnd.api+json']?.schema\n const write = writeInput(ctx.doc, ctx.descriptor, type, schema, 'create')\n const properties: Record<string, JsonSchema> = {}\n const required = [...write.required]\n if (write.plan.clientId !== 'none') {\n properties['id'] = idSchema(`The id to give the new \\`${type}\\` resource.`)\n if (write.plan.clientId === 'required') {\n required.push('id')\n }\n }\n return { schema: root({ ...properties, ...write.properties }, required), write: write.plan }\n}\n\n/** An update, from the type's update request body. */\nexport function updateInput(ctx: InputContext, type: string): ToolInput {\n const body = deref<RequestBodyObject>(ctx.doc, ctx.operation.requestBody)\n const schema = body?.content?.['application/vnd.api+json']?.schema\n const write = writeInput(ctx.doc, ctx.descriptor, type, schema, 'update')\n const properties: Record<string, JsonSchema> = {\n id: idSchema(`The id of the \\`${type}\\` resource to update.`),\n }\n const clearable = 'clear' in write.properties ? [] : write.plan.clearable\n const extra: Record<string, JsonSchema> = {}\n if (clearable.length > 0) {\n extra['clear'] = nullable({\n type: 'array',\n items: { type: 'string', enum: [...clearable] },\n description: 'Fields to set to null. Every other argument left null keeps its current value.',\n })\n }\n return {\n schema: root({ ...properties, ...write.properties, ...extra }, ['id']),\n write: { ...write.plan, clearable },\n }\n}\n\n/** A delete. */\nexport function deleteInput(type: string): ToolInput {\n return { schema: root({ id: idSchema(`The id of the \\`${type}\\` resource to delete.`) }, ['id']) }\n}\n\n/** The request media type a model can send to an action, in order of preference. */\nfunction pickBody(\n ctx: InputContext,\n):\n | { mediaType: string; schema: unknown; kind: 'document' | 'json' | 'text' | 'form' }\n | 'none'\n | undefined {\n const body = deref<RequestBodyObject>(ctx.doc, ctx.operation.requestBody)\n const content = Object.entries(body?.content ?? {})\n if (content.length === 0) {\n return 'none'\n }\n const find = (test: (mediaType: string) => boolean) =>\n content.find(([mediaType]) => test(mediaType.toLowerCase()))\n const document = find((m) => m === 'application/vnd.api+json')\n if (document !== undefined) {\n return { mediaType: document[0], schema: document[1].schema, kind: 'document' }\n }\n const json = find((m) => m === 'application/json' || m.endsWith('+json'))\n if (json !== undefined) {\n return { mediaType: json[0], schema: json[1].schema, kind: 'json' }\n }\n const text = find((m) => m.startsWith('text/'))\n if (text !== undefined) {\n return { mediaType: text[0], schema: { type: 'string' }, kind: 'text' }\n }\n const form = content.find(([mediaType, media]) => {\n const m = mediaType.toLowerCase()\n const schema = deref<JsonSchema>(ctx.doc, media.schema)\n return (\n (m === 'application/x-www-form-urlencoded' || m.startsWith('multipart/')) &&\n isObject(schema?.['properties']) &&\n Object.keys(schema['properties']).length > 0\n )\n })\n if (form !== undefined) {\n return { mediaType: form[0], schema: form[1].schema, kind: 'form' }\n }\n return undefined\n}\n\n/**\n * A custom action's input: the resource id at resource scope, then the body. A JSON:API body\n * whose type the descriptor knows is flat, like a create; any other body is one `body` argument.\n * `undefined` when every media type the action takes is one a model can't send, such as binary.\n */\nexport function actionInput(\n ctx: InputContext,\n type: string,\n name: string,\n action: ActionDescriptor,\n): ToolInput | undefined {\n const properties: Record<string, JsonSchema> = {}\n const required: string[] = []\n if (action.scope === 'resource') {\n properties['id'] = idSchema(`The id of the \\`${type}\\` resource to run ${name} on.`)\n required.push('id')\n }\n const picked = pickBody(ctx)\n if (picked === undefined) {\n return undefined\n }\n if (picked === 'none') {\n return { schema: root(properties, required), action: { kind: 'none' } }\n }\n const body = deref<RequestBodyObject>(ctx.doc, ctx.operation.requestBody)\n const bodyRequired = body?.required === true\n if (picked.kind === 'document' && action.inputType !== undefined) {\n const write = writeInput(ctx.doc, ctx.descriptor, action.inputType, picked.schema, 'action')\n for (const [key, schema] of Object.entries(write.properties)) {\n if (!(key in properties)) {\n properties[key] = schema\n }\n }\n return {\n schema: root(properties, [...required, ...write.required.filter((r) => r !== 'id')]),\n action: { kind: 'flat', write: write.plan },\n }\n }\n\n const converted = toPortable(ctx.doc, picked.schema ?? {}, [], 1)\n const description =\n typeof body?.description === 'string'\n ? body.description\n : `The request body, as ${picked.mediaType}.`\n properties['body'] = {\n ...converted.schema,\n description:\n converted.schema['description'] === undefined\n ? description\n : `${description} ${String(converted.schema['description'])}`,\n }\n if (bodyRequired) {\n required.push('body')\n }\n return {\n schema: root(properties, required),\n action: {\n kind: 'body',\n decoder: converted.decoder,\n required: bodyRequired,\n mediaType: picked.mediaType,\n mediaKind: picked.kind,\n },\n }\n}\n","/**\n * Flatten the client's materialised results into plain tool output. A resource becomes\n * `{ id, type, ...attributes, relation, links? }`, where a relation is an id (ids for a to-many,\n * or type and id for a polymorphic relation), or the flattened related resource when it was\n * included, and `links` holds only the links the type names. Generic links such as `self` and\n * resource meta are dropped. Results go back in a JSON:API-like envelope of `data`, `links` and\n * `meta`, where `links` only carries a collection's pagination.\n */\nimport { type ApiDescriptor, isPolymorphic } from '@haddowg/json-api-client'\nimport { isObject } from './openapi'\n\n/** What flattening needs from the model. */\nexport interface FlattenContext {\n descriptor: ApiDescriptor\n /** How many relationships deep a result nests included resources; null for no cap. */\n includeDepth: number | null\n /** The links each type names. A type missing from it keeps no links. */\n resourceLinks: Readonly<Record<string, readonly string[]>>\n}\n\n/** A materialised related value carries `$raw` when it was included, not when it's only linkage. */\nconst isIncluded = (value: object): boolean => '$raw' in value\n\nfunction identifier(value: Record<string, unknown>, polymorphic: boolean): unknown {\n return polymorphic ? { type: value['type'], id: value['id'] } : value['id']\n}\n\n/** A link as a flattened resource carries it: `{ href, type?, title?, meta? }`. */\nfunction flatLink(link: unknown): Record<string, unknown> | undefined {\n if (typeof link === 'string') {\n return { href: link }\n }\n if (!isObject(link) || typeof link['href'] !== 'string') {\n return undefined\n }\n const out: Record<string, unknown> = { href: link['href'] }\n if (typeof link['type'] === 'string') out['type'] = link['type']\n if (typeof link['title'] === 'string') out['title'] = link['title']\n if (isObject(link['meta'])) out['meta'] = link['meta']\n return out\n}\n\n/** The links of a resource its type names, or undefined when it carries none of them. */\nfunction namedLinks(\n value: Record<string, unknown>,\n names: readonly string[] | undefined,\n): Record<string, unknown> | undefined {\n const links = (value as { $links?: unknown }).$links\n if (names === undefined || !isObject(links)) {\n return undefined\n }\n const out: Record<string, unknown> = {}\n for (const name of names) {\n const link = flatLink(links[name])\n if (link !== undefined) {\n out[name] = link\n }\n }\n return Object.keys(out).length > 0 ? out : undefined\n}\n\n/**\n * Flatten one materialised resource. A resource already on the path, or one more than\n * `includeDepth` relationships from the root, is cut back to its id.\n */\nexport function flattenResource(\n value: Record<string, unknown>,\n ctx: FlattenContext,\n path: readonly string[] = [],\n): Record<string, unknown> {\n const type = String(value['type'])\n const key = `${type}:${String(value['id'])}`\n const relations = ctx.descriptor[type]?.relations ?? {}\n const maxDepth = ctx.includeDepth\n const out: Record<string, unknown> = { id: value['id'], type }\n for (const [name, member] of Object.entries(value)) {\n if (name === 'id' || name === 'type') {\n continue\n }\n const relation = relations[name]\n if (relation === undefined) {\n out[name] = member\n continue\n }\n const polymorphic = isPolymorphic(relation)\n const one = (item: unknown): unknown => {\n if (!isObject(item)) {\n return item\n }\n const itemKey = `${String(item['type'])}:${String(item['id'])}`\n return isIncluded(item) &&\n itemKey !== key &&\n !path.includes(itemKey) &&\n (maxDepth === null || path.length < maxDepth)\n ? flattenResource(item, ctx, [...path, key])\n : identifier(item, polymorphic)\n }\n out[name] = Array.isArray(member) ? member.map(one) : one(member)\n }\n const links = namedLinks(value, ctx.resourceLinks[type])\n if (links !== undefined) {\n out['links'] = links\n }\n return out\n}\n\nconst PAGE_LINKS = ['first', 'prev', 'next', 'last'] as const\n\nfunction linkHref(link: unknown): string | undefined {\n if (typeof link === 'string') return link\n if (isObject(link) && typeof link['href'] === 'string') return link['href']\n return undefined\n}\n\n/** The document `meta` a materialised resource came with. */\nfunction resourceDocumentMeta(value: unknown): Record<string, unknown> | undefined {\n const document = isObject(value)\n ? (value as { $document?: { meta?: unknown } }).$document\n : undefined\n return isObject(document?.meta) ? document.meta : undefined\n}\n\n/**\n * One resource in its envelope: `{ data, meta? }`, with `data` null when there is none.\n * `fallbackMeta` is the document meta when the value can't carry it, such as a null.\n */\nexport function resourceEnvelope(\n value: unknown,\n ctx: FlattenContext,\n fallbackMeta?: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = {\n data: isObject(value) ? flattenResource(value, ctx) : null,\n }\n const meta = resourceDocumentMeta(value) ?? fallbackMeta\n if (meta !== undefined) {\n out['meta'] = meta\n }\n return out\n}\n\n/** A materialised collection in its envelope: `{ data, links, meta? }`. */\nexport function collectionEnvelope(\n value: readonly unknown[] & { $links?: Record<string, unknown>; $meta?: Record<string, unknown> },\n ctx: FlattenContext,\n): Record<string, unknown> {\n const links: Record<string, string> = {}\n for (const name of PAGE_LINKS) {\n const href = linkHref(value.$links?.[name])\n if (href !== undefined) {\n links[name] = href\n }\n }\n const out: Record<string, unknown> = {\n data: value.map((item) => (isObject(item) ? flattenResource(item, ctx) : item)),\n links,\n }\n if (value.$meta !== undefined) {\n out['meta'] = value.$meta\n }\n return out\n}\n","import {\n type ActionDescriptor,\n type ApiDescriptor,\n createClient,\n execute,\n fetchTransport,\n type HeadersInit,\n isActionLocation,\n isPolymorphic,\n JsonApiError,\n type JsonApiTransport,\n materialise,\n type ReadQuery,\n type TransportResponse,\n} from '@haddowg/json-api-client'\nimport type { ResultShape } from './catalogue'\nimport type { McpModelPlan } from './model'\nimport { type QueryPlan, SELF_COUNT, type WritePlan } from './inputs'\nimport { isObject } from './openapi'\nimport { ArgumentError, decode } from './portable'\nimport { collectionEnvelope, type FlattenContext, resourceEnvelope } from './flatten'\nimport type { McpCallToolResult, McpContent, McpOperation, McpTool, McpToolError } from './types'\n\n/** One tool the runtime serves: its operation, definition, call plan and result shape. */\nexport interface ToolEntry {\n op: McpOperation\n tool: McpTool\n input: McpModelPlan\n result: ResultShape\n actionKey?: string\n}\n\n/** Thrown when a `pageLink` isn't a link this tool may follow. */\nexport class PageLinkError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'PageLinkError'\n }\n}\n\ntype Call = (...args: unknown[]) => Promise<unknown>\n\ninterface LooseRelation {\n related(query?: ReadQuery): Promise<unknown>\n}\n\ninterface LooseHandle {\n update(patch: Record<string, unknown>): Promise<unknown>\n delete(): Promise<unknown>\n rel(name: string): LooseRelation\n actions: Record<string, Call | undefined>\n}\n\ninterface LooseAccessor {\n list(query?: ReadQuery): Promise<unknown>\n get(id: string, query?: ReadQuery): Promise<unknown>\n create(input: Record<string, unknown>): Promise<unknown>\n id(id: string): LooseHandle\n actions: Record<string, Call | undefined>\n}\n\nexport interface CallEnvironment {\n descriptor: ApiDescriptor\n baseUrl: string | undefined\n transport: JsonApiTransport | undefined\n headers: HeadersInit | undefined\n signal: AbortSignal | undefined\n /** How many relationships deep a result nests included resources; null for no cap. */\n includeDepth: number | null\n /** The links each type names, which a flattened resource keeps. */\n resourceLinks: Readonly<Record<string, readonly string[]>>\n /**\n * Shape results as models before format 4 describe them: `{ item }`, `{ items, links, meta }`,\n * a bare resource or meta object, and a delete as text.\n */\n legacyResults: boolean\n}\n\nconst text = (value: string): McpContent => ({ type: 'text', text: value })\n\nfunction structured(value: Record<string, unknown>): McpCallToolResult {\n return { content: [text(JSON.stringify(value))], structuredContent: value }\n}\n\n/** The `meta` of a JSON:API response body, when it has one. */\nfunction responseMeta(\n response: TransportResponse | undefined,\n): Record<string, unknown> | undefined {\n if (response === undefined || response.body === '') {\n return undefined\n }\n try {\n const body: unknown = JSON.parse(response.body)\n return isObject(body) && isObject(body['meta']) ? body['meta'] : undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * A result in the shape a model before format 4 describes: `{ item }` for a single read,\n * `{ items, links, meta }` for a collection, the bare resource, meta object or URL otherwise,\n * and a delete as text.\n */\nfunction legacyResult(\n entry: ToolEntry,\n envelope: Record<string, unknown>,\n location: boolean,\n id: string | undefined,\n): McpCallToolResult {\n const data = envelope['data']\n if (location || entry.result === 'resource' || entry.result === 'loose') {\n if (Array.isArray(data)) {\n const { data: items, ...rest } = envelope\n return structured({ items, ...rest })\n }\n return isObject(data)\n ? structured(data)\n : { content: [text('The action returned no resource.')] }\n }\n switch (entry.result) {\n case 'item':\n return structured({ item: data ?? null })\n case 'collection': {\n const { data: items, ...rest } = envelope\n return structured({ items, ...rest })\n }\n case 'deleted':\n return { content: [text(`Deleted ${entry.op.type} ${String(id)}.`)] }\n case 'meta':\n return structured(isObject(envelope['meta']) ? envelope['meta'] : {})\n default:\n return structured(envelope)\n }\n}\n\nfunction requireId(args: Record<string, unknown>): string {\n const id = args['id']\n if (typeof id === 'string' && id !== '') {\n return id\n }\n if (typeof id === 'number') {\n return String(id)\n }\n throw new ArgumentError('id is required')\n}\n\nfunction stringList(value: unknown, name: string): string[] | undefined {\n if (value === null || value === undefined) {\n return undefined\n }\n if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {\n throw new ArgumentError(`${name} must be a list of strings`)\n }\n return value as string[]\n}\n\n/** The read query a model's arguments describe. */\nexport function readQuery(args: Record<string, unknown>, plan: QueryPlan): ReadQuery {\n const query: ReadQuery = {}\n if (plan.filter !== undefined && isObject(args['filter'])) {\n const filter = decode(args['filter'], plan.filter, 'filter') as Record<string, unknown>\n for (const [key, value] of Object.entries(filter)) {\n if (isObject(value)) {\n // A range filter with every bound null applies nothing.\n const bounds = Object.fromEntries(Object.entries(value).filter(([, v]) => v !== null))\n if (Object.keys(bounds).length === 0) {\n delete filter[key]\n } else {\n filter[key] = bounds\n }\n }\n }\n if (Object.keys(filter).length > 0) {\n query.filter = filter\n }\n }\n const sort = plan.sort ? stringList(args['sort'], 'sort') : undefined\n if (sort !== undefined && sort.length > 0) {\n query.sort = sort\n }\n const include = plan.include ? stringList(args['include'], 'include') : undefined\n if (plan.includePaths !== undefined) {\n const allowed = plan.includePaths\n const refused = (include ?? []).filter((path) => !allowed.includes(path))\n if (refused.length > 0) {\n throw new ArgumentError(\n `include can't name ${refused.map((p) => `\"${p}\"`).join(', ')}; this tool includes only ${allowed.join(', ')}`,\n )\n }\n }\n if (include !== undefined && include.length > 0) {\n query.include = include\n }\n if (plan.fields.length > 0 && isObject(args['fields'])) {\n const fields: Record<string, string[]> = {}\n for (const [type, names] of Object.entries(args['fields'])) {\n const list = stringList(names, `fields.${type}`)\n if (list !== undefined) {\n fields[type] = list\n }\n }\n if (Object.keys(fields).length > 0) {\n query.fields = fields\n }\n }\n if (plan.countProfile !== undefined && args['withCount'] === true) {\n query.withCount = [SELF_COUNT]\n }\n if (plan.sizeKey !== undefined) {\n const asked = args['pageSize']\n let size =\n typeof asked === 'number' && Number.isFinite(asked)\n ? Math.trunc(asked)\n : plan.defaultPageSize!\n size = Math.max(1, plan.maxPageSize === undefined ? size : Math.min(size, plan.maxPageSize))\n query.page = { [plan.sizeKey]: size }\n }\n return query\n}\n\n/** The flat write input a model's arguments describe: relations as identifiers, nulls resolved. */\nexport function writeInput(\n args: Record<string, unknown>,\n plan: WritePlan,\n): Record<string, unknown> {\n const input: Record<string, unknown> = {}\n for (const [name, field] of Object.entries(plan.fields)) {\n const value = args[name]\n if (value === undefined || (value === null && field.property.nullMeansOmit)) {\n continue\n }\n if (value === null) {\n input[name] = null\n continue\n }\n const relation = field.relation\n if (relation === undefined) {\n input[name] = decode(value, field.property.decoder, name)\n continue\n }\n const identify = (item: unknown): unknown => {\n if (isPolymorphic(relation)) {\n if (!isObject(item) || typeof item['type'] !== 'string' || typeof item['id'] !== 'string') {\n throw new ArgumentError(`${name} takes the related resource's type and id`)\n }\n return { type: item['type'], id: item['id'] }\n }\n if (typeof item !== 'string' && typeof item !== 'number') {\n throw new ArgumentError(`${name} takes the related resource's id`)\n }\n return { type: relation.types[0], id: String(item) }\n }\n if (relation.cardinality === 'many') {\n if (!Array.isArray(value)) {\n throw new ArgumentError(`${name} takes a list`)\n }\n input[name] = value.map(identify)\n } else {\n input[name] = identify(value)\n }\n }\n for (const name of stringList(args['clear'], 'clear') ?? []) {\n if (!plan.clearable.includes(name)) {\n throw new ArgumentError(`clear can't name \"${name}\"`)\n }\n input[name] = null\n }\n if (plan.clientId !== 'none' && args['id'] !== null && args['id'] !== undefined) {\n input['id'] = String(args['id'])\n }\n return input\n}\n\n/** Escape a string for a regular expression. */\nconst escape = (value: string): string => value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/**\n * Check a `pageLink` against the tool's own endpoint: the base URL's origin and the endpoint's\n * path, with any id in it. Returns the absolute URL to fetch.\n */\nexport function checkPageLink(link: unknown, baseUrl: string, template: string): string {\n if (typeof link !== 'string' || link === '') {\n throw new PageLinkError('pageLink must be a link string')\n }\n let base: URL\n try {\n base = new URL(baseUrl)\n } catch {\n throw new PageLinkError('pageLink needs an absolute base URL to be checked against')\n }\n let url: URL\n try {\n url = new URL(link, base)\n } catch {\n throw new PageLinkError('pageLink is not a valid URL')\n }\n const prefix = base.pathname.replace(/\\/$/, '')\n const pattern = template\n .split(/(\\{[^}]+\\})/)\n .map((part) => (/^\\{[^}]+\\}$/.test(part) ? '[^/]+' : escape(part)))\n .join('')\n if (\n (url.protocol !== 'http:' && url.protocol !== 'https:') ||\n url.origin !== base.origin ||\n !new RegExp(`^${escape(prefix)}${pattern}$`).test(url.pathname)\n ) {\n throw new PageLinkError(\n 'pageLink must be one of the links this tool returned; it points somewhere else',\n )\n }\n return url.href\n}\n\n/** The descriptor entry a call sends this action with: the operation's method and the body plan picked. */\nfunction effectiveAction(entry: ToolEntry): ActionDescriptor {\n const {\n inputMedia: _inputMedia,\n inputType,\n contentType: _contentType,\n ...action\n } = entry.op.actionDescriptor!\n const plan = entry.input.action!\n const base = { ...action, method: entry.op.method }\n switch (plan.kind) {\n case 'none':\n return { ...base, input: 'none' }\n case 'flat':\n return inputType === undefined\n ? { ...base, input: 'document' }\n : { ...base, input: 'document', inputType }\n case 'body':\n return plan.mediaKind === 'document'\n ? { ...base, input: 'document' }\n : { ...base, input: 'raw', contentType: plan.mediaType }\n }\n}\n\nfunction withAction(descriptor: ApiDescriptor, entry: ToolEntry): ApiDescriptor {\n const resource = descriptor[entry.op.type]!\n return {\n ...descriptor,\n [entry.op.type]: {\n ...resource,\n actions: { ...resource.actions, [entry.actionKey!]: effectiveAction(entry) },\n },\n }\n}\n\nasync function binaryContent(\n value: Blob | ArrayBuffer,\n uri: string,\n mimeType: string,\n): Promise<McpContent> {\n const bytes = value instanceof ArrayBuffer ? value : await value.arrayBuffer()\n const data = Buffer.from(bytes).toString('base64')\n return mimeType.startsWith('image/')\n ? { type: 'image', data, mimeType }\n : { type: 'resource', resource: { uri, mimeType, blob: data } }\n}\n\n/** Run one tool call against the API. Throws what the call failed with. */\nexport async function runCall(\n entry: ToolEntry,\n rawArgs: unknown,\n env: CallEnvironment,\n): Promise<McpCallToolResult> {\n if (rawArgs !== undefined && rawArgs !== null && !isObject(rawArgs)) {\n throw new ArgumentError('the arguments must be an object')\n }\n const args = (rawArgs ?? {}) as Record<string, unknown>\n if (env.baseUrl === undefined) {\n throw new Error(\n 'No base URL: pass baseUrl, return one from requestInit, or give the document a server',\n )\n }\n // The last response, for a document's meta when the client returns no resource to carry it.\n let last: TransportResponse | undefined\n const inner = env.transport ?? fetchTransport\n const transport: JsonApiTransport = async (request) => {\n last = await inner(request)\n return last\n }\n const outcome = await callApi(entry, args, { ...env, baseUrl: env.baseUrl, transport })\n const id = entry.op.kind === 'delete' ? requireId(args) : undefined\n if (outcome.kind === 'content') {\n return outcome.result\n }\n const location = outcome.kind === 'envelope' && outcome.location === true\n const envelope =\n outcome.kind === 'envelope'\n ? outcome.envelope\n : withFallbackMeta(outcome.envelope, responseMeta(last))\n return env.legacyResults ? legacyResult(entry, envelope, location, id) : structured(envelope)\n}\n\ntype Outcome =\n | { kind: 'content'; result: McpCallToolResult }\n | { kind: 'envelope'; envelope: Record<string, unknown>; location?: boolean }\n /** An envelope that takes the response's meta when it has none of its own. */\n | { kind: 'bare'; envelope: Record<string, unknown> }\n\nfunction withFallbackMeta(\n envelope: Record<string, unknown>,\n meta: Record<string, unknown> | undefined,\n): Record<string, unknown> {\n return meta === undefined || envelope['meta'] !== undefined ? envelope : { ...envelope, meta }\n}\n\nconst envelope = (value: Record<string, unknown>): Outcome => ({\n kind: 'envelope',\n envelope: value,\n})\n\nasync function callApi(\n entry: ToolEntry,\n args: Record<string, unknown>,\n env: CallEnvironment & { transport: JsonApiTransport; baseUrl: string },\n): Promise<Outcome> {\n const { op } = entry\n const descriptor = op.kind === 'action' ? withAction(env.descriptor, entry) : env.descriptor\n const headers = env.headers\n const client = createClient(descriptor, {\n baseUrl: env.baseUrl,\n transport: env.transport,\n ...(headers !== undefined ? { headers: () => headers } : {}),\n })\n const accessor = (client as unknown as Record<string, LooseAccessor>)[op.type]!\n const query = entry.input.query === undefined ? undefined : readQuery(args, entry.input.query)\n if (query !== undefined && env.signal !== undefined) {\n query.signal = env.signal\n }\n const flat: FlattenContext = {\n descriptor: env.descriptor,\n includeDepth: env.includeDepth,\n resourceLinks: env.resourceLinks,\n }\n\n switch (op.kind) {\n case 'list':\n case 'related': {\n if (entry.result === 'item') {\n const related = await accessor.id(requireId(args)).rel(op.relation!).related(query)\n return { kind: 'bare', envelope: resourceEnvelope(related, flat) }\n }\n let collection: unknown\n if (\n args['pageLink'] !== undefined &&\n args['pageLink'] !== null &&\n entry.input.query?.paginated\n ) {\n const url = checkPageLink(args['pageLink'], env.baseUrl, op.path)\n const doc = await execute(\n {\n baseUrl: env.baseUrl,\n transport: env.transport,\n ...(headers !== undefined ? { headers: () => headers } : {}),\n },\n {\n method: 'GET',\n path: url,\n link: true,\n ...(entry.input.query?.countProfile !== undefined &&\n new URL(url).searchParams.has('withCount')\n ? { profiles: [entry.input.query.countProfile] }\n : {}),\n ...(env.signal !== undefined ? { signal: env.signal } : {}),\n },\n )\n collection =\n doc === undefined\n ? []\n : materialise(doc, {\n descriptor: env.descriptor,\n navigate: () => Promise.reject(new Error('not navigable')),\n })\n } else if (op.kind === 'list') {\n collection = await accessor.list(query)\n } else {\n collection = await accessor.id(requireId(args)).rel(op.relation!).related(query)\n }\n return envelope(collectionEnvelope(Array.isArray(collection) ? collection : [], flat))\n }\n case 'get':\n return {\n kind: 'bare',\n envelope: resourceEnvelope(await accessor.get(requireId(args), query), flat),\n }\n case 'create': {\n const input = writeInput(args, entry.input.write!)\n const created = await accessor.create(input)\n // A 204 to a client-generated id sends nothing back but the id the caller chose.\n return {\n kind: 'bare',\n envelope: resourceEnvelope(\n isObject(created) ? created : { id: String(input['id'] ?? ''), type: op.type },\n flat,\n ),\n }\n }\n case 'update': {\n const id = requireId(args)\n const updated = await accessor.id(id).update(writeInput(args, entry.input.write!))\n return {\n kind: 'bare',\n envelope: resourceEnvelope(isObject(updated) ? updated : { id, type: op.type }, flat),\n }\n }\n case 'delete':\n await accessor.id(requireId(args)).delete()\n return { kind: 'bare', envelope: { data: null } }\n case 'action':\n return runAction(entry, args, accessor, env, flat)\n }\n}\n\nasync function runAction(\n entry: ToolEntry,\n args: Record<string, unknown>,\n accessor: LooseAccessor,\n env: CallEnvironment & { baseUrl: string },\n flat: FlattenContext,\n): Promise<Outcome> {\n const { op } = entry\n const action = op.actionDescriptor!\n const actions =\n action.scope === 'resource' ? accessor.id(requireId(args)).actions : accessor.actions\n const call = actions[entry.actionKey!]!\n const options = env.signal === undefined ? {} : { signal: env.signal }\n const plan = entry.input.action!\n let result: unknown\n if (plan.kind === 'none') {\n result = await call(options)\n } else {\n const body =\n plan.kind === 'flat'\n ? writeInput(args, plan.write)\n : args['body'] === null\n ? undefined\n : decode(args['body'], plan.decoder, 'body')\n if (plan.kind === 'body' && plan.required && body === undefined) {\n throw new ArgumentError('body is required')\n }\n result = await call(body, options)\n }\n\n // A URL the action produced goes back as data; the tool never fetches it.\n if (action.location !== undefined && isActionLocation(result)) {\n return { kind: 'envelope', envelope: { data: { ...result } }, location: true }\n }\n\n const content = (value: string): Outcome => ({\n kind: 'content',\n result: { content: [text(value)] },\n })\n switch (entry.result) {\n case 'collection':\n return envelope(collectionEnvelope(Array.isArray(result) ? result : [], flat))\n case 'resource':\n case 'loose':\n if (Array.isArray(result)) {\n return envelope(collectionEnvelope(result, flat))\n }\n return { kind: 'bare', envelope: resourceEnvelope(isObject(result) ? result : null, flat) }\n case 'meta':\n return envelope({ meta: isObject(result) ? result : {} })\n case 'raw': {\n const mimeType = action.rawOutput?.[0]?.mediaType ?? 'application/octet-stream'\n if (result === undefined) {\n return content('Done.')\n }\n if (typeof result === 'string') {\n return content(result)\n }\n if (\n result instanceof ArrayBuffer ||\n (typeof Blob !== 'undefined' && result instanceof Blob)\n ) {\n return {\n kind: 'content',\n result: {\n content: [await binaryContent(result, `${env.baseUrl}${action.path}`, mimeType)],\n },\n }\n }\n return content(JSON.stringify(result))\n }\n default:\n return content('Done.')\n }\n}\n\n/** The error a failed call carries, before the host's `mapError`. */\nexport function toToolError(error: unknown): McpToolError {\n if (error instanceof JsonApiError) {\n return {\n status: error.status,\n message: error.message,\n errors: error.errors.map((e) => {\n const out: McpToolError['errors'][number] = {}\n if (e.status !== undefined) out.status = e.status\n if (e.code !== undefined) out.code = e.code\n if (e.title !== undefined) out.title = e.title\n if (e.detail !== undefined) out.detail = e.detail\n if (e.source !== undefined) out.source = e.source\n if (e.path !== undefined) out.path = e.path\n return out\n }),\n cause: error,\n }\n }\n return {\n message: error instanceof Error ? error.message : String(error),\n errors: [],\n cause: error,\n }\n}\n\n/** A tool error result: the message, the status and each error object, as JSON text. */\nexport function errorResult(error: McpToolError): McpCallToolResult {\n const body: Record<string, unknown> = { error: error.message }\n if (error.status !== undefined) body['status'] = error.status\n if (error.errors.length > 0) body['errors'] = error.errors\n return { content: [text(JSON.stringify(body))], isError: true }\n}\n","/**\n * The generated artefact the runtime reads: everything a host needs to serve the tools, built\n * from the OpenAPI document at build time. It is plain JSON, so any host can load it, and it\n * carries the client's descriptor because the client can't make a call without one.\n */\nimport type { ApiDescriptor } from '@haddowg/json-api-client'\nimport type { ResultShape } from './catalogue'\nimport { MAXIMUM_CONTRACT, MINIMUM_CONTRACT } from './contract'\nimport type { ActionBodyPlan, QueryPlan, WritePlan } from './inputs'\nimport { isObject, type OperationObject, type SecurityRequirement } from './openapi'\nimport type { McpOperationKind, McpTool, SkippedOperation } from './types'\n\n/** The artefact's own format. A runtime refuses a format it doesn't know. */\nexport const MODEL_FORMAT = 4\n\n/**\n * Every format this runtime reads. Format 1 has no `credentialSchemes`. Formats 1 and 2 have no\n * `includeDepth`, and store each operation's `scopes` and the `security` of its operation object.\n * Formats 1 to 3 have no `resourceLinks`, and their output schemas describe the results before\n * the envelope, which the runtime still returns for them.\n */\nconst READABLE_FORMATS: readonly number[] = [1, 2, 3, MODEL_FORMAT]\n\n/** The package that writes the artefact. */\nexport const MODEL_GENERATOR = '@haddowg/json-api-mcp'\n\n/** One operation, as hooks see it, without the descriptor entries the runtime adds back. */\nexport interface McpModelOperation {\n operationId: string\n toolName: string\n kind: McpOperationKind\n type: string\n relation?: string\n action?: string\n /** The action's key in the type's descriptor `actions`. */\n actionKey?: string\n method: string\n path: string\n /**\n * The operation object, extensions included, without its `parameters`, `requestBody` and\n * `responses`: the tool already holds what those say, and they can refer to components the\n * artefact doesn't carry. From format 3 it has no `security` either: the runtime puts back\n * the `security` below.\n */\n operation: OperationObject\n /**\n * The operation's own `security`, else the document's. From format 3 it is absent when the\n * operation declares none, and the runtime takes the model's `security`.\n */\n security?: readonly SecurityRequirement[]\n /**\n * Formats 1 and 2: the OAuth scopes the descriptor says the operation needs. From format 3\n * the runtime reads them from the descriptor.\n */\n scopes?: readonly string[]\n}\n\n/** How the handler turns one tool's arguments into a client call. */\nexport interface McpModelPlan {\n query?: QueryPlan\n write?: WritePlan\n action?: ActionBodyPlan\n}\n\nexport interface McpModelTool {\n operation: McpModelOperation\n tool: McpTool\n plan: McpModelPlan\n result: ResultShape\n}\n\n/** The generated artefact. */\nexport interface McpModel {\n format: 1 | 2 | 3 | typeof MODEL_FORMAT\n generator: typeof MODEL_GENERATOR\n /** The generator contract the source document declared. */\n contract: number\n /** Which document it was built from. */\n source: { title: string; hash: string }\n /** The document's first server URL, the default base URL. */\n baseUrl?: string\n /**\n * The security schemes an MCP caller authenticates with: the document's `oauth2` schemes.\n * Empty when it has none. A format 1 model doesn't carry it.\n */\n credentialSchemes?: string[]\n /**\n * Format 3: the document's `security`, which an operation declaring none of its own takes.\n * Absent when the document declares none.\n */\n security?: readonly SecurityRequirement[]\n /**\n * How many relationships deep a tool's `include` may reach; null for every path the document\n * declares. Formats 1 and 2 don't carry it and set no cap.\n */\n includeDepth?: number | null\n /**\n * Format 4: the links each type names, its declared links and action links, for the types that\n * name any. A flattened resource carries these under `links`.\n */\n resourceLinks?: Record<string, string[]>\n /** The json-api-ts descriptor the client calls through. */\n descriptor: ApiDescriptor\n tools: McpModelTool[]\n skipped: SkippedOperation[]\n}\n\n/** Thrown when an artefact can't be read: another format, or a contract out of range. */\nexport class McpModelError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'McpModelError'\n }\n}\n\n/** Check a parsed artefact before the runtime trusts it. */\nexport function assertModel(value: unknown): McpModel {\n if (!isObject(value) || value['generator'] !== MODEL_GENERATOR) {\n throw new McpModelError(\n `This isn't a model written by ${MODEL_GENERATOR}. Generate one with the json-api-mcp CLI.`,\n )\n }\n if (typeof value['format'] !== 'number' || !READABLE_FORMATS.includes(value['format'])) {\n throw new McpModelError(\n `The model is format ${String(value['format'])} and this runtime reads formats ${READABLE_FORMATS.slice(0, -1).join(', ')} and ${MODEL_FORMAT}. Regenerate it with the same version of ${MODEL_GENERATOR} the server runs.`,\n )\n }\n const contract = value['contract']\n if (typeof contract !== 'number' || contract < MINIMUM_CONTRACT || contract > MAXIMUM_CONTRACT) {\n throw new McpModelError(\n `The model was built from a document with generator contract ${String(contract)}, and this runtime reads contracts ${MINIMUM_CONTRACT} to ${MAXIMUM_CONTRACT}.`,\n )\n }\n if (!isObject(value['descriptor']) || !Array.isArray(value['tools'])) {\n throw new McpModelError('The model has no descriptor or tools; regenerate it.')\n }\n const credentialSchemes = value['credentialSchemes']\n if (\n value['format'] !== 1 &&\n (!Array.isArray(credentialSchemes) || !credentialSchemes.every((s) => typeof s === 'string'))\n ) {\n throw new McpModelError('The model has no credentialSchemes list; regenerate it.')\n }\n const includeDepth = value['includeDepth']\n if (\n (value['format'] as number) >= 3 &&\n includeDepth !== null &&\n !(typeof includeDepth === 'number' && Number.isInteger(includeDepth) && includeDepth >= 0)\n ) {\n throw new McpModelError('The model has no includeDepth; regenerate it.')\n }\n const resourceLinks = value['resourceLinks']\n if (\n value['format'] === MODEL_FORMAT &&\n !(\n isObject(resourceLinks) &&\n Object.values(resourceLinks).every(\n (names) => Array.isArray(names) && names.every((name) => typeof name === 'string'),\n )\n )\n ) {\n throw new McpModelError('The model has no resourceLinks; regenerate it.')\n }\n return value as unknown as McpModel\n}\n\n/**\n * The schemes a format 1 model's caller is taken to authenticate with, since it doesn't record\n * them: every scheme some operation lists scopes under.\n */\nexport function inferCredentialSchemes(model: McpModel): string[] {\n const schemes = new Set<string>()\n for (const tool of model.tools) {\n for (const requirement of tool.operation.security ?? []) {\n for (const [scheme, scopes] of Object.entries(requirement)) {\n if (scopes.length > 0) schemes.add(scheme)\n }\n }\n }\n return [...schemes]\n}\n","import type { McpOperation, McpOperationKind } from './types'\n\n/** The descriptor's key for each kind's operation in a type's `scopes`. */\nconst SCOPE_KEYS: Readonly<Record<Exclude<McpOperationKind, 'related' | 'action'>, string>> = {\n list: 'fetchCollection',\n get: 'fetchOne',\n create: 'create',\n update: 'update',\n delete: 'delete',\n}\n\n/** The scopes the descriptor says an operation needs, or `undefined` when it names none. */\nexport function descriptorScopes(\n op: Pick<McpOperation, 'kind' | 'relation' | 'resource' | 'actionDescriptor'>,\n): readonly string[] | undefined {\n let scopes: readonly string[] | undefined\n if (op.kind === 'action') {\n scopes = op.actionDescriptor?.scopes\n } else if (op.kind === 'related') {\n scopes = op.resource.scopes?.[`fetchRelated.${op.relation ?? ''}`]\n } else {\n scopes = op.resource.scopes?.[SCOPE_KEYS[op.kind]]\n }\n return scopes === undefined || scopes.length === 0 ? undefined : scopes\n}\n","import { errorResult, runCall, type ToolEntry, toToolError } from './call'\nimport {\n assertModel,\n inferCredentialSchemes,\n McpModelError,\n type McpModel,\n type McpModelTool,\n} from './model'\nimport type { SecurityRequirement } from './openapi'\nimport { descriptorScopes } from './scopes'\nimport type {\n JsonApiMcp,\n JsonApiMcpOptions,\n McpCallToolResult,\n McpCapability,\n McpOperation,\n McpRequest,\n McpTool,\n McpToolSession,\n} from './types'\n\n/** Every required scope appears among the granted ones. */\nexport const exactScopeMatcher = (\n granted: readonly string[],\n required: readonly string[],\n): boolean => required.every((scope) => granted.includes(scope))\n\n/**\n * Whether granted scopes satisfy an operation's security. No requirement, or an empty one, needs\n * nothing. Given `credentialSchemes`, the schemes the caller authenticates with, a requirement\n * counts only when it names nothing else, so an operation whose every requirement needs another\n * credential fails. A counted requirement passes when its scopes, across its schemes, all match,\n * or when it lists none. Without `credentialSchemes` every requirement counts.\n */\nexport function satisfiesSecurity(\n security: readonly SecurityRequirement[],\n granted: readonly string[],\n matcher: (granted: readonly string[], required: readonly string[]) => boolean = exactScopeMatcher,\n credentialSchemes?: readonly string[],\n): boolean {\n if (security.length === 0) {\n return true\n }\n return security.some((requirement) => {\n const schemes = Object.keys(requirement)\n if (schemes.length === 0) {\n return true\n }\n if (\n credentialSchemes !== undefined &&\n !schemes.every((scheme) => credentialSchemes.includes(scheme))\n ) {\n return false\n }\n const scopes = Object.values(requirement).flat()\n return scopes.length === 0 || matcher(granted, scopes)\n })\n}\n\n/**\n * The schemes the scope filter counts: the host's, else the model's, else for a format 1 model\n * the schemes it lists scopes under. `undefined`, so every requirement counts, when that's empty.\n */\nfunction resolveCredentialSchemes(\n model: McpModel,\n override: readonly string[] | undefined,\n): readonly string[] | undefined {\n if (override !== undefined) {\n return override\n }\n const schemes = model.credentialSchemes ?? inferCredentialSchemes(model)\n return schemes.length > 0 ? schemes : undefined\n}\n\n/** Rebuild an operation for the hooks, adding back the descriptor entries it names. */\nfunction hydrate(model: McpModel, tool: McpModelTool): ToolEntry {\n const source = tool.operation\n const resource = model.descriptor[source.type]\n if (resource === undefined) {\n throw new McpModelError(`The model's descriptor has no type \"${source.type}\"; regenerate it.`)\n }\n // From format 3 security is stored once: the operation's own, else the document's.\n const security = source.security ?? model.security ?? []\n const operation =\n model.format < 3 || source.security === undefined\n ? source.operation\n : { ...source.operation, security: source.security as SecurityRequirement[] }\n const op: McpOperation = {\n operationId: source.operationId,\n toolName: source.toolName,\n kind: source.kind,\n type: source.type,\n method: source.method,\n path: source.path,\n resource,\n operation,\n security,\n }\n if (source.relation !== undefined) {\n op.relation = source.relation\n const relation = resource.relations[source.relation]\n if (relation !== undefined) op.relationDescriptor = relation\n }\n if (source.action !== undefined) {\n op.action = source.action\n const action = resource.actions?.[source.actionKey ?? source.action]\n if (action !== undefined) op.actionDescriptor = action\n }\n const scopes = source.scopes ?? descriptorScopes(op)\n if (scopes !== undefined) op.scopes = scopes\n const entry: ToolEntry = { op, tool: tool.tool, input: tool.plan, result: tool.result }\n if (source.actionKey !== undefined) entry.actionKey = source.actionKey\n return entry\n}\n\n/**\n * Serve MCP tools from a model the generator wrote: `createJsonApiMcp({ model })` with the parsed\n * artefact. Throws when the artefact isn't one this runtime can read.\n */\nexport function createJsonApiMcp<Ctx = unknown>(options: JsonApiMcpOptions<Ctx>): JsonApiMcp<Ctx> {\n const { hooks = {} } = options\n const model = assertModel(options.model)\n const descriptor = model.descriptor\n const catalogue = { entries: model.tools.map((tool) => hydrate(model, tool)) }\n const defaultBaseUrl = options.baseUrl ?? model.baseUrl\n const credentialSchemes = resolveCredentialSchemes(model, options.credentialSchemes)\n const includeDepth = model.includeDepth ?? null\n const resourceLinks = model.resourceLinks ?? {}\n // A model before format 4 describes the results before the envelope, so it gets those.\n const legacyResults = model.format < 4\n\n async function forRequest(request: McpRequest<Ctx>): Promise<McpToolSession> {\n const ctx = request.context\n let entries: ToolEntry[] = catalogue.entries\n if (request.grantedScopes !== undefined) {\n // null grants nothing, so the filter fails closed.\n const granted = request.grantedScopes ?? []\n entries = entries.filter((e) =>\n satisfiesSecurity(e.op.security, granted, hooks.scopeMatcher, credentialSchemes),\n )\n }\n if (hooks.capabilities !== undefined) {\n const capabilities = await hooks.capabilities(ctx)\n const lookup = (id: string): McpCapability | undefined =>\n capabilities instanceof Map\n ? (capabilities as ReadonlyMap<string, McpCapability>).get(id)\n : (capabilities as Readonly<Record<string, McpCapability>>)[id]\n entries = entries.filter((e) => lookup(e.op.operationId) !== 'denied')\n }\n if (hooks.includeOperation !== undefined) {\n const include = hooks.includeOperation\n const keep = await Promise.all(entries.map((e) => include(e.op, ctx)))\n entries = entries.filter((_, index) => keep[index] === true)\n }\n\n const byName = new Map<string, ToolEntry>()\n const tools: McpTool[] = []\n for (const entry of entries) {\n const tool =\n hooks.decorateTool === undefined\n ? entry.tool\n : await hooks.decorateTool(structuredClone(entry.tool), entry.op, ctx)\n tools.push(tool)\n byName.set(tool.name, entry)\n }\n\n async function callTool(\n name: string,\n args: unknown,\n callOptions: { signal?: AbortSignal } = {},\n ): Promise<McpCallToolResult> {\n const entry = byName.get(name)\n if (entry === undefined) {\n return errorResult({ message: `Unknown tool \"${name}\"`, errors: [], cause: undefined })\n }\n try {\n const init = hooks.requestInit === undefined ? {} : await hooks.requestInit(entry.op, ctx)\n return await runCall(entry, args, {\n descriptor,\n baseUrl: init.baseUrl ?? defaultBaseUrl,\n transport: options.transport,\n headers: init.headers,\n signal: callOptions.signal,\n includeDepth,\n resourceLinks,\n legacyResults,\n })\n } catch (error) {\n const toolError = toToolError(error)\n return errorResult(\n hooks.mapError === undefined ? toolError : await hooks.mapError(toolError, entry.op, ctx),\n )\n }\n }\n\n return { tools, callTool }\n }\n\n return {\n operations: catalogue.entries.map((e) => e.op),\n tools: catalogue.entries.map((e) => e.tool),\n skipped: model.skipped,\n contract: model.contract,\n source: model.source,\n credentialSchemes,\n forRequest,\n }\n}\n","import type { ApiDescriptor } from '@haddowg/json-api-client'\nimport { HTTP_METHODS, type HttpMethod } from './openapi'\nimport type { McpOperationKind } from './types'\n\n/** The name pattern OpenAI's and Anthropic's tool names share. */\nexport const TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]{1,64}$/\n\n/** What an `operationId` names, for an operation that becomes a tool. */\nexport interface ParsedOperationId {\n kind: McpOperationKind\n type: string\n relation?: string\n action?: string\n /** The method suffix of a multi-method action's id. */\n method?: HttpMethod\n}\n\nconst CRUD: Readonly<Record<string, McpOperationKind>> = {\n fetchCollection: 'list',\n fetchOne: 'get',\n create: 'create',\n update: 'update',\n delete: 'delete',\n}\n\n/**\n * Parse an `operationId` into what a tool needs. `undefined` for an operation that isn't a tool:\n * relationship endpoints, Atomic Operations, or an id this package doesn't recognise.\n */\nexport function parseOperationId(operationId: string): ParsedOperationId | undefined {\n const parts = operationId.split('.')\n const [verb, type] = parts\n if (verb === undefined || type === undefined || type === '') {\n return undefined\n }\n const crud = CRUD[verb]\n if (crud !== undefined) {\n return parts.length === 2 ? { kind: crud, type } : undefined\n }\n if (verb === 'fetchRelated' && parts.length === 3 && parts[2] !== '') {\n return { kind: 'related', type, relation: parts[2]! }\n }\n if (verb === 'action' && parts[2] !== undefined && parts[2] !== '') {\n if (parts.length === 3) {\n return { kind: 'action', type, action: parts[2] }\n }\n const method = parts[3] as HttpMethod\n if (parts.length === 4 && HTTP_METHODS.includes(method)) {\n return { kind: 'action', type, action: parts[2], method }\n }\n }\n return undefined\n}\n\nconst snake = (value: string): string => value.replace(/-/g, '_')\n\n/**\n * The tool name for a parsed `operationId`: noun first, the type as the document spells it with\n * kebab-case turned into snake_case. A related read ends in `list` or `get` by the relation's\n * cardinality; an action is named after its path, plus the method for a multi-method action.\n */\nexport function toolName(parsed: ParsedOperationId, descriptor: ApiDescriptor): string {\n const type = snake(parsed.type)\n switch (parsed.kind) {\n case 'related': {\n const relation = descriptor[parsed.type]?.relations[parsed.relation!]\n const verb = relation?.cardinality === 'one' ? 'get' : 'list'\n return `${type}_${snake(parsed.relation!)}_${verb}`\n }\n case 'action':\n return parsed.method === undefined\n ? `${type}_${snake(parsed.action!)}`\n : `${type}_${snake(parsed.action!)}_${parsed.method}`\n default:\n return `${type}_${parsed.kind}`\n }\n}\n\n/** Thrown when the tools can't be named: a name too long, with a stray character, or a clash. */\nexport class ToolNameError extends Error {\n readonly problems: readonly string[]\n\n constructor(problems: readonly string[]) {\n super(`Cannot build MCP tools from this document:\\n- ${problems.join('\\n- ')}`)\n this.name = 'ToolNameError'\n this.problems = problems\n }\n}\n\n/** Collect every naming problem across the tools, so one build reports them all. */\nexport function checkToolNames(tools: readonly { name: string; operationId: string }[]): void {\n const problems: string[] = []\n const byName = new Map<string, string[]>()\n for (const { name, operationId } of tools) {\n if (name.length > 64) {\n problems.push(`\"${name}\" (${operationId}) is ${name.length} characters; tool names allow 64`)\n } else if (!TOOL_NAME_PATTERN.test(name)) {\n problems.push(`\"${name}\" (${operationId}) has characters outside [a-zA-Z0-9_-]`)\n }\n byName.set(name, [...(byName.get(name) ?? []), operationId])\n }\n for (const [name, ids] of byName) {\n if (ids.length > 1) {\n problems.push(`\"${name}\" is the tool name of ${ids.join(' and ')}`)\n }\n }\n if (problems.length > 0) {\n throw new ToolNameError(problems)\n }\n}\n","import { isObject, type OperationObject } from './openapi'\n\n/** The vendor extension an action's operation declares its safety in. */\nexport const SAFETY_EXTENSION = 'x-safety'\n\n/** What an action declares about itself. A flag it doesn't declare is absent. */\nexport interface ActionSafety {\n readOnly?: boolean\n destructive?: boolean\n idempotent?: boolean\n}\n\n/**\n * Read an action's declared safety from its operation. An operation without the extension, or\n * with one this package can't read, declares nothing.\n */\nexport function readActionSafety(operation: OperationObject): ActionSafety {\n const raw = operation[SAFETY_EXTENSION]\n const safety: ActionSafety = {}\n if (!isObject(raw)) {\n return safety\n }\n for (const key of ['readOnly', 'destructive', 'idempotent'] as const) {\n if (typeof raw[key] === 'boolean') {\n safety[key] = raw[key]\n }\n }\n return safety\n}\n"],"mappings":"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"}
|