@webpieces/api-doc-model 0.4.807 → 0.4.808

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/api-doc-model",
3
- "version": "0.4.807",
3
+ "version": "0.4.808",
4
4
  "description": "TypeScript-compiler-API extractor producing one in-memory ApiDocModel from a webpieces API contract. No renderer, no runtime, no app-specific import.",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -23,7 +23,7 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@webpieces/core-util": "0.4.807",
26
+ "@webpieces/core-util": "0.4.808",
27
27
  "typescript": "5.9.3"
28
28
  }
29
29
  }
@@ -60,8 +60,10 @@ export declare class McpSchemaRenderer {
60
60
  *
61
61
  * ## Why an unrenderable tool is REPORTED here rather than throwing
62
62
  *
63
- * Some shapes have no MCP schema at all — a discriminated union, a recursive DTO and that is a
64
- * limit of the PROTOCOL, not a defect in a contract that is otherwise perfectly good HTTP. Such a
63
+ * Some shapes have no MCP schema at all — a recursive DTO, an undocumented tool, a request that
64
+ * is not a named object and the first of those is a limit of the PROTOCOL rather than a defect
65
+ * in a contract that is otherwise perfectly good HTTP. (A DISCRIMINATED UNION used to be on this
66
+ * list and no longer is: #1009 taught `ApiJsonSchema` `oneOf`, so one publishes.) Such a
65
67
  * tool was never servable: the reflect-metadata runtime refused it too, for its own reasons. So
66
68
  * failing the whole document build over one would stop a repo publishing its OpenAPI over a tool
67
69
  * nobody could ever have called.
@@ -82,7 +84,17 @@ export declare class McpSchemaRenderer {
82
84
  * are exhaustive, means falling off the end and publishing a tool with NO hints.
83
85
  */
84
86
  private static operationOf;
85
- /** A tool's input/output schema: always a CLOSED object, because MCP publishes objects. */
87
+ /**
88
+ * A tool's input/output schema: always a CLOSED OBJECT, because MCP publishes objects — and
89
+ * because a union at the ROOT of a tool schema is refused by the function-calling APIs that
90
+ * consume it. Both OpenAI and Anthropic reject a top-level `oneOf`/`anyOf`/`allOf`, and a server
91
+ * sends its WHOLE tool list on every request, so one such tool makes every request 400 and bricks
92
+ * the session — not just that tool. Nested composition, inside a property, is fine.
93
+ *
94
+ * The build-time rule `no-root-union-api-type` refuses the same shape across every `@ApiPath`
95
+ * contract in the workspace, `@ApiType` or not. This is the renderer's own backstop, so a catalog
96
+ * can never carry one even if it is reached some other way.
97
+ */
86
98
  private rootSchema;
87
99
  private namedType;
88
100
  /**
@@ -94,6 +106,29 @@ export declare class McpSchemaRenderer {
94
106
  * server does not accept.
95
107
  */
96
108
  private objectSchema;
109
+ /**
110
+ * A UNION, as `oneOf` with its branches INLINE and its DERIVED discriminator.
111
+ *
112
+ * MCP tool schemas are JSON Schema 2020-12 — the same dialect OpenAPI 3.1 uses — so `oneOf` was
113
+ * never the obstacle; the obstacle was this repo's own subset, which had no spelling for it
114
+ * (#1009). The shape written here MIRRORS `SchemaRenderer.union` in `@webpieces/openapi-generator`
115
+ * branch for branch, because a union has ONE published spelling and that renderer is the
116
+ * reference implementation of it. The only difference is the absence of `$ref`: a tool schema is
117
+ * inline, so each branch is rendered in full and the discriminator maps to branch NAMES.
118
+ *
119
+ * `discriminator` is written ONLY when the model DERIVED one. A union TypeScript itself cannot
120
+ * narrow is published as a bare `oneOf` rather than with an invented discriminator — claiming a
121
+ * narrowing the source does not have is worse than admitting there is none.
122
+ */
123
+ private unionSchema;
124
+ /**
125
+ * A FIELD whose type is a union: the model registers `type X = A | B` as its OWN entry carrying
126
+ * the derived discriminator, while the field holds a bare list of branch names. Rendering that
127
+ * list directly would publish the `oneOf` and silently DROP the discriminator — the one part of a
128
+ * union a client needs to narrow on — so the alias is looked up by its branch list first. This is
129
+ * the same lookup `SchemaRenderer.namedUnion` does to emit its `$ref`.
130
+ */
131
+ private fieldUnion;
97
132
  /**
98
133
  * One FIELD: its type, then the prose and the constraints that hang off the field.
99
134
  *
@@ -84,8 +84,10 @@ class McpSchemaRenderer {
84
84
  *
85
85
  * ## Why an unrenderable tool is REPORTED here rather than throwing
86
86
  *
87
- * Some shapes have no MCP schema at all — a discriminated union, a recursive DTO and that is a
88
- * limit of the PROTOCOL, not a defect in a contract that is otherwise perfectly good HTTP. Such a
87
+ * Some shapes have no MCP schema at all — a recursive DTO, an undocumented tool, a request that
88
+ * is not a named object and the first of those is a limit of the PROTOCOL rather than a defect
89
+ * in a contract that is otherwise perfectly good HTTP. (A DISCRIMINATED UNION used to be on this
90
+ * list and no longer is: #1009 taught `ApiJsonSchema` `oneOf`, so one publishes.) Such a
89
91
  * tool was never servable: the reflect-metadata runtime refused it too, for its own reasons. So
90
92
  * failing the whole document build over one would stop a repo publishing its OpenAPI over a tool
91
93
  * nobody could ever have called.
@@ -152,13 +154,34 @@ class McpSchemaRenderer {
152
154
  throw new McpRenderError_1.McpRenderError(`@Endpoint declares operation '${endpoint.operation}', which has no MCP hints`, where, `Use one of the exported constants: ${core_util_1.READ}, ${core_util_1.WRITE_IDEMPOTENT}, ${core_util_1.WRITE}.`);
153
155
  }
154
156
  }
155
- /** A tool's input/output schema: always a CLOSED object, because MCP publishes objects. */
157
+ /**
158
+ * A tool's input/output schema: always a CLOSED OBJECT, because MCP publishes objects — and
159
+ * because a union at the ROOT of a tool schema is refused by the function-calling APIs that
160
+ * consume it. Both OpenAI and Anthropic reject a top-level `oneOf`/`anyOf`/`allOf`, and a server
161
+ * sends its WHOLE tool list on every request, so one such tool makes every request 400 and bricks
162
+ * the session — not just that tool. Nested composition, inside a property, is fine.
163
+ *
164
+ * The build-time rule `no-root-union-api-type` refuses the same shape across every `@ApiPath`
165
+ * contract in the workspace, `@ApiType` or not. This is the renderer's own backstop, so a catalog
166
+ * can never carry one even if it is reached some other way.
167
+ */
156
168
  rootSchema(ref, where, side) {
169
+ if (ref.kind === 'union') {
170
+ throw new McpRenderError_1.McpRenderError(`an MCP tool's ${side} is itself a union`, where, `Wrap it in a property of an object ${side} — a top-level oneOf is rejected by the ` +
171
+ 'OpenAI and Anthropic function-calling APIs, and one such tool 400s every ' +
172
+ 'request in the session.');
173
+ }
157
174
  if (ref.kind !== 'ref') {
158
175
  throw new McpRenderError_1.McpRenderError(`an MCP tool's ${side} is not a named DTO`, where, 'Give it a named interface or class. An inline or primitive ' +
159
176
  `${side} has no object schema, and MCP publishes objects.`);
160
177
  }
161
- return this.objectSchema(this.namedType(ref.refName, where), new Set());
178
+ const type = this.namedType(ref.refName, where);
179
+ if (type.unionRefNames.length > 0) {
180
+ throw new McpRenderError_1.McpRenderError(`an MCP tool's ${side} '${type.name}' is itself a union`, where, `Wrap it in a property of an object ${side} — a top-level oneOf is rejected by the ` +
181
+ 'OpenAI and Anthropic function-calling APIs, and one such tool 400s every ' +
182
+ 'request in the session.');
183
+ }
184
+ return this.objectSchema(type, new Set());
162
185
  }
163
186
  namedType(name, where) {
164
187
  const type = this.model.types.get(name);
@@ -181,7 +204,7 @@ class McpSchemaRenderer {
181
204
  'inline and has no $ref to close a loop with.');
182
205
  }
183
206
  if (type.unionRefNames.length > 0) {
184
- throw new McpRenderError_1.McpRenderError(`'${type.name}' is a union, which an MCP input schema cannot express`, type.name, 'Publish a single object shape to agents, or keep this contract off MCP.');
207
+ return this.unionSchema(type, parents);
185
208
  }
186
209
  if (type.indexSignatureValue !== undefined && type.fields.length > 0) {
187
210
  throw new McpRenderError_1.McpRenderError(`'${type.name}' has both named fields and an index signature`, type.name, 'Split the open map into a field of its own: a schema is either closed or a typed ' +
@@ -204,6 +227,59 @@ class McpSchemaRenderer {
204
227
  }
205
228
  return schema;
206
229
  }
230
+ /**
231
+ * A UNION, as `oneOf` with its branches INLINE and its DERIVED discriminator.
232
+ *
233
+ * MCP tool schemas are JSON Schema 2020-12 — the same dialect OpenAPI 3.1 uses — so `oneOf` was
234
+ * never the obstacle; the obstacle was this repo's own subset, which had no spelling for it
235
+ * (#1009). The shape written here MIRRORS `SchemaRenderer.union` in `@webpieces/openapi-generator`
236
+ * branch for branch, because a union has ONE published spelling and that renderer is the
237
+ * reference implementation of it. The only difference is the absence of `$ref`: a tool schema is
238
+ * inline, so each branch is rendered in full and the discriminator maps to branch NAMES.
239
+ *
240
+ * `discriminator` is written ONLY when the model DERIVED one. A union TypeScript itself cannot
241
+ * narrow is published as a bare `oneOf` rather than with an invented discriminator — claiming a
242
+ * narrowing the source does not have is worse than admitting there is none.
243
+ */
244
+ unionSchema(type, parents) {
245
+ const nested = new Set(parents);
246
+ nested.add(type.name);
247
+ const schema = new core_util_1.ApiJsonSchema();
248
+ schema.oneOf = type.unionRefNames.map((name) => this.objectSchema(this.namedType(name, type.name), nested));
249
+ if (type.description.trim() !== '') {
250
+ schema.description = type.description;
251
+ }
252
+ if (type.discriminator !== undefined) {
253
+ const mapping = {};
254
+ for (const branch of type.unionRefNames) {
255
+ const value = type.discriminator.branchValues.get(branch);
256
+ if (value !== undefined) {
257
+ mapping[value] = branch;
258
+ }
259
+ }
260
+ schema.discriminator = new core_util_1.ApiJsonSchemaDiscriminator(type.discriminator.propertyName, mapping);
261
+ }
262
+ return schema;
263
+ }
264
+ /**
265
+ * A FIELD whose type is a union: the model registers `type X = A | B` as its OWN entry carrying
266
+ * the derived discriminator, while the field holds a bare list of branch names. Rendering that
267
+ * list directly would publish the `oneOf` and silently DROP the discriminator — the one part of a
268
+ * union a client needs to narrow on — so the alias is looked up by its branch list first. This is
269
+ * the same lookup `SchemaRenderer.namedUnion` does to emit its `$ref`.
270
+ */
271
+ fieldUnion(branchNames, where, parents) {
272
+ const key = branchNames.join(',');
273
+ for (const name of this.model.types.keys()) {
274
+ const candidate = this.model.types.get(name);
275
+ if (candidate.unionRefNames.length > 0 && candidate.unionRefNames.join(',') === key) {
276
+ return this.unionSchema(candidate, parents);
277
+ }
278
+ }
279
+ const schema = new core_util_1.ApiJsonSchema();
280
+ schema.oneOf = branchNames.map((name) => this.objectSchema(this.namedType(name, where), parents));
281
+ return schema;
282
+ }
207
283
  /**
208
284
  * One FIELD: its type, then the prose and the constraints that hang off the field.
209
285
  *
@@ -288,7 +364,7 @@ class McpSchemaRenderer {
288
364
  case 'ref':
289
365
  return this.referencedSchema(ref.refName, where, parents);
290
366
  case 'union':
291
- throw new McpRenderError_1.McpRenderError('a union has no MCP input-schema shape', where, 'Publish one object shape to agents, or keep this method off MCP.');
367
+ return this.fieldUnion(ref.unionRefNames, where, parents);
292
368
  default:
293
369
  throw new McpRenderError_1.McpRenderError(`no MCP schema for the declared type '${ref.unmappedText ?? '<unknown>'}'`, where, 'Give the field a shape the model can represent — a named DTO, an array of one, ' +
294
370
  'a string-literal union, a Record, or a primitive.');
@@ -1 +1 @@
1
- {"version":3,"file":"McpSchemaRenderer.js","sourceRoot":"","sources":["../../../../../../packages/docs/api-doc-model/src/render/McpSchemaRenderer.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ApiJsonSchema,\n ApiJsonSchemaType,\n EndpointOperation,\n McpToolCatalog,\n McpToolDefinition,\n mcpHintsForOperation,\n READ,\n WRITE,\n WRITE_IDEMPOTENT,\n} from '@webpieces/core-util';\nimport {\n ApiDocModel,\n DocumentedEndpoint,\n DocumentedField,\n DocumentedType,\n} from '../model/ApiDocModel';\nimport { TypeRef } from '../model/TypeRef';\nimport { McpRenderError } from './McpRenderError';\n\n/** RFC 9110 token, which is what an `Mcp-Param-{name}` header name has to be. */\nconst HEADER_TOKEN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\n\n/** ONE `@WpMcpTool` the build could not give a schema, and why. See {@link McpSchemaRenderer.catalogOf}. */\nexport class SkippedMcpTool {\n constructor(\n /** The stable protocol name it declared. */\n readonly name: string,\n readonly contractName: string,\n /** The render refusal, verbatim, including the declaration it points at. */\n readonly reason: string,\n ) {}\n\n toString(): string {\n return `${this.contractName}/${this.name}: ${this.reason}`;\n }\n}\n\n/** What one catalog render produced: the tools, and the ones it could not produce. */\nexport class McpCatalogRender {\n constructor(\n readonly catalog: McpToolCatalog,\n readonly skipped: readonly SkippedMcpTool[],\n ) {}\n}\n\n/**\n * `ApiDocModel` -> the MCP tool list an agent is shown, and the server accepts calls against.\n *\n * ## It is the ONLY source of an MCP schema\n *\n * It was written for #983 as a MEASUREMENT: it rendered the same `ApiJsonSchema` the reflect-metadata\n * runtime built, so the two could be compared field by field. They matched for every DTO shape the\n * runtime could build, which is what licensed #984 to delete `@WpDtoField` and its erasure-repair\n * arguments — `required`, `arrayItems`, `integer`, `minimum`, `maximum`, `enumValues`, `mapValues`,\n * `mcpHeader` — along with `DtoSchemaBuilder` itself. There is now one schema, built here, written to\n * `mcp-tools.json` by `wp-openapi`, and read at boot by `McpToolRegistry`.\n *\n * ## Where it now goes FURTHER than the deleted runtime could\n *\n * The three reproductions #983 documented were runtime capability gaps, and two of them are closed:\n *\n * - **NULLABLE is rendered**, as `type: [T, \"null\"]`. `ApiJsonSchema.type` used to hold one string,\n * so the runtime could not write it down even where the compiler could see `externalId: string | null`\n * — and `{}` and `{externalId: null}` are different wire documents. `type` is now\n * `ApiJsonSchemaType | readonly ApiJsonSchemaType[]` and the union is emitted.\n * - **A bound on an ARRAY of numbers** lands on the ITEM, where OpenAPI puts it. `@WpDtoField`\n * rejected numeric constraints on a non-`Number` field, so it had nowhere to go at all.\n * - **A nested DTO is INLINED**, with the FIELD's prose on it — not a gap but the protocol: MCP tool\n * schemas are inline and have no `$ref`.\n */\nexport class McpSchemaRenderer {\n constructor(private readonly model: ApiDocModel) {}\n\n /** Every `@WpMcpTool` method of the contract, in declaration order. */\n render(): readonly McpToolDefinition[] {\n const tools: McpToolDefinition[] = [];\n for (const endpoint of this.model.endpoints) {\n if (endpoint.mcpTool !== undefined) {\n tools.push(this.tool(endpoint));\n }\n }\n return tools;\n }\n\n /**\n * The tools of SEVERAL contracts as one catalog — the artifact a server boots from — plus every\n * tool that could not be rendered and why.\n *\n * A catalog rather than a per-contract list because the protocol namespace is flat: two contracts\n * declaring one tool name is a collision an agent would see, and {@link McpToolCatalog} refuses it\n * here, at build time, rather than at somebody's boot.\n *\n * ## Why an unrenderable tool is REPORTED here rather than throwing\n *\n * Some shapes have no MCP schema at all — a discriminated union, a recursive DTO — and that is a\n * limit of the PROTOCOL, not a defect in a contract that is otherwise perfectly good HTTP. Such a\n * tool was never servable: the reflect-metadata runtime refused it too, for its own reasons. So\n * failing the whole document build over one would stop a repo publishing its OpenAPI over a tool\n * nobody could ever have called.\n *\n * It is not silence either. The build NAMES every skipped tool with its reason, and\n * `McpToolRegistry` REFUSES TO BOOT when a registered `@WpMcpTool` is missing from the catalog —\n * which is the right place for that failure, because that is the process actually claiming to\n * serve it.\n */\n // webpieces-disable no-function-outside-class -- static factory over this class\n static catalogOf(models: readonly ApiDocModel[]): McpCatalogRender {\n const tools: McpToolDefinition[] = [];\n const skipped: SkippedMcpTool[] = [];\n for (const model of models) {\n const renderer = new McpSchemaRenderer(model);\n for (const endpoint of model.endpoints) {\n if (endpoint.mcpTool === undefined) {\n continue;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- a per-tool render refusal is REPORTED, see the docstring\n try {\n tools.push(renderer.tool(endpoint));\n } catch (err: unknown) {\n //const error = toError(err);\n if (!(err instanceof McpRenderError)) throw err;\n skipped.push(\n new SkippedMcpTool(endpoint.mcpTool.name, model.contractName, err.message),\n );\n }\n }\n }\n return new McpCatalogRender(new McpToolCatalog(tools), skipped);\n }\n\n /** ONE tool. Visible to {@link catalogOf}, which renders tool by tool so it can report one. */\n tool(endpoint: DocumentedEndpoint): McpToolDefinition {\n const where = `${this.model.contractName}.${endpoint.methodName}`;\n const description = endpoint.mcpDescription ?? endpoint.description;\n if (description.trim() === '') {\n throw new McpRenderError(\n 'an MCP tool has no documentation',\n where,\n 'Write a JSDoc block on the method, or an @mcp tag for agent-facing wording. It is ' +\n 'published verbatim by tools/list, so an agent has nothing else to go on.',\n );\n }\n if (endpoint.request === undefined || endpoint.response === undefined) {\n throw new McpRenderError(\n 'an MCP tool has no declared request or response type',\n where,\n 'Declare both: one request parameter and a Promise<Response> return type.',\n );\n }\n return new McpToolDefinition(\n endpoint.mcpTool!.name,\n endpoint.methodName,\n description,\n mcpHintsForOperation(\n McpSchemaRenderer.operationOf(endpoint, where),\n endpoint.openWorld,\n ),\n this.rootSchema(endpoint.request, where, 'request'),\n this.rootSchema(endpoint.response, where, 'response'),\n );\n }\n\n /**\n * `operation` verbatim from the model, mapped back onto the REAL constant.\n *\n * A `switch` and not a cast, because the model carries the operation as a string and a cast would\n * hand `mcpHintsForOperation` a value it has no case for — which, for a function whose three arms\n * are exhaustive, means falling off the end and publishing a tool with NO hints.\n */\n // webpieces-disable no-function-outside-class -- private static mapping of this class\n private static operationOf(endpoint: DocumentedEndpoint, where: string): EndpointOperation {\n switch (endpoint.operation) {\n case READ:\n return READ;\n case WRITE_IDEMPOTENT:\n return WRITE_IDEMPOTENT;\n case WRITE:\n return WRITE;\n default:\n throw new McpRenderError(\n `@Endpoint declares operation '${endpoint.operation}', which has no MCP hints`,\n where,\n `Use one of the exported constants: ${READ}, ${WRITE_IDEMPOTENT}, ${WRITE}.`,\n );\n }\n }\n\n /** A tool's input/output schema: always a CLOSED object, because MCP publishes objects. */\n private rootSchema(ref: TypeRef, where: string, side: string): ApiJsonSchema {\n if (ref.kind !== 'ref') {\n throw new McpRenderError(\n `an MCP tool's ${side} is not a named DTO`,\n where,\n 'Give it a named interface or class. An inline or primitive ' +\n `${side} has no object schema, and MCP publishes objects.`,\n );\n }\n return this.objectSchema(this.namedType(ref.refName!, where), new Set<string>());\n }\n\n private namedType(name: string, where: string): DocumentedType {\n const type = this.model.types.get(name);\n if (type === undefined) {\n throw new McpRenderError(\n `no model entry for type '${name}'`,\n where,\n 'Declare the type in a file the extractor reaches from this contract.',\n );\n }\n return type;\n }\n\n /**\n * One object DTO. CLOSED (`additionalProperties: false`), with `required` written only when it is\n * non-empty, so an all-optional DTO carries no empty list.\n *\n * The cycle stop is `parents`, and it THROWS rather than truncating: an inline schema cannot\n * express a recursive DTO, and one that silently stopped a level down would publish a shape the\n * server does not accept.\n */\n private objectSchema(type: DocumentedType, parents: ReadonlySet<string>): ApiJsonSchema {\n if (parents.has(type.name)) {\n throw new McpRenderError(\n `recursive DTO '${type.name}' cannot use an inline MCP schema`,\n type.name,\n 'Break the cycle, or keep this DTO out of the MCP document — a tool schema is ' +\n 'inline and has no $ref to close a loop with.',\n );\n }\n if (type.unionRefNames.length > 0) {\n throw new McpRenderError(\n `'${type.name}' is a union, which an MCP input schema cannot express`,\n type.name,\n 'Publish a single object shape to agents, or keep this contract off MCP.',\n );\n }\n if (type.indexSignatureValue !== undefined && type.fields.length > 0) {\n throw new McpRenderError(\n `'${type.name}' has both named fields and an index signature`,\n type.name,\n 'Split the open map into a field of its own: a schema is either closed or a typed ' +\n 'map, and MCP has no spelling for half of each.',\n );\n }\n const nested = new Set(parents);\n nested.add(type.name);\n\n const schema = new ApiJsonSchema('object');\n schema.properties = {};\n schema.additionalProperties = false;\n const required: string[] = [];\n for (const field of type.fields) {\n schema.properties[field.name] = this.fieldSchema(type, field, nested);\n if (!field.optional) {\n required.push(field.name);\n }\n }\n if (required.length > 0) {\n schema.required = required;\n }\n return schema;\n }\n\n /**\n * One FIELD: its type, then the prose and the constraints that hang off the field.\n *\n * The ORDER matters: the type is built first and `description` is written over whatever the type\n * produced, so a nested DTO carries the FIELD's sentence rather than the DTO's. `@WpMin` /\n * `@WpMax` land on the numeric LEAF — on an array, on the item — for the same reason\n * `SchemaRenderer` puts them there: a `minimum` on an array means nothing.\n *\n * NULLABLE widens the type to `[T, \"null\"]` and is deliberately NOT the same thing as OPTIONAL,\n * which is an absent entry in the object's `required` list. `{}` and `{externalId: null}` are\n * different wire documents, and an agent told only \"optional\" would send the wrong one.\n */\n private fieldSchema(\n owner: DocumentedType,\n field: DocumentedField,\n parents: ReadonlySet<string>,\n ): ApiJsonSchema {\n const where = `${owner.name}.${field.name}`;\n const schema = this.typeSchema(field.type, where, parents);\n const description = field.mcpDescription ?? field.description;\n if (description.trim() === '') {\n throw new McpRenderError(\n 'a published DTO field has no documentation',\n where,\n 'Write a JSDoc sentence on the field. It is the only thing an agent is told about ' +\n 'that parameter.',\n );\n }\n schema.description = description;\n\n const leaf = field.type.kind === 'array' ? schema.items! : schema;\n if (field.min !== undefined) {\n leaf.minimum = field.min;\n }\n if (field.max !== undefined) {\n leaf.maximum = field.max;\n }\n if (field.mcpHeader !== undefined) {\n McpSchemaRenderer.assertHeaderFits(field, schema, where);\n schema['x-mcp-header'] = field.mcpHeader;\n }\n if (field.nullable) {\n schema.type = McpSchemaRenderer.nullable(schema, where);\n }\n return schema;\n }\n\n /** `T` -> `[T, \"null\"]`, refusing a field with no type at all rather than publishing `[\"null\"]`. */\n // webpieces-disable no-function-outside-class -- private static mapping of this class\n private static nullable(schema: ApiJsonSchema, where: string): readonly ApiJsonSchemaType[] {\n const base = ApiJsonSchema.baseTypeOf(schema);\n if (base === undefined) {\n throw new McpRenderError(\n 'a nullable field has no other type',\n where,\n 'A field typed only `null` carries no information — give it a real type beside it.',\n );\n }\n return [base, 'null'];\n }\n\n /**\n * `@mcpHeader` mirrors a PRIMITIVE parameter into `Mcp-Param-{name}` (MCP 2026 SEP-2243). The\n * three conditions are the runtime's, restated against the compiler's view of the field, so a\n * declaration the server would reject at boot fails the document build instead.\n */\n // webpieces-disable no-function-outside-class -- private static validator of this class\n private static assertHeaderFits(\n field: DocumentedField,\n schema: ApiJsonSchema,\n where: string,\n ): void {\n if (!HEADER_TOKEN.test(field.mcpHeader!)) {\n throw new McpRenderError(\n `@mcpHeader '${field.mcpHeader}' is not an RFC 9110 token`,\n where,\n 'Use letters, digits and the token punctuation only — it becomes an HTTP header name.',\n );\n }\n if (schema.type !== 'string' && schema.type !== 'boolean' && schema.type !== 'integer') {\n throw new McpRenderError(\n '@mcpHeader is on a field that is not a primitive an MCP header can carry',\n where,\n 'Mirror a string, a boolean or an Integer. A non-integer number and every object ' +\n 'shape are excluded by MCP 2026-07-28.',\n );\n }\n }\n\n /** One resolved type, with no field-level prose or constraints on it. */\n private typeSchema(ref: TypeRef, where: string, parents: ReadonlySet<string>): ApiJsonSchema {\n switch (ref.kind) {\n case 'primitive':\n return McpSchemaRenderer.primitive(ref, where);\n case 'enum': {\n const schema = new ApiJsonSchema('string');\n schema.enum = ref.enumValues.slice();\n return schema;\n }\n case 'array': {\n const schema = new ApiJsonSchema('array');\n schema.items = this.typeSchema(ref.items!, where, parents);\n return schema;\n }\n case 'openMap': {\n const schema = new ApiJsonSchema('object');\n schema.additionalProperties = this.typeSchema(ref.values!, where, parents);\n return schema;\n }\n case 'ref':\n return this.referencedSchema(ref.refName!, where, parents);\n case 'union':\n throw new McpRenderError(\n 'a union has no MCP input-schema shape',\n where,\n 'Publish one object shape to agents, or keep this method off MCP.',\n );\n default:\n throw new McpRenderError(\n `no MCP schema for the declared type '${ref.unmappedText ?? '<unknown>'}'`,\n where,\n 'Give the field a shape the model can represent — a named DTO, an array of one, ' +\n 'a string-literal union, a Record, or a primitive.',\n );\n }\n }\n\n /** A named type: a string enum becomes `enum`, an object DTO is INLINED (MCP has no `$ref`). */\n private referencedSchema(\n name: string,\n where: string,\n parents: ReadonlySet<string>,\n ): ApiJsonSchema {\n const type = this.namedType(name, where);\n if (type.enumValues.length > 0) {\n const schema = new ApiJsonSchema('string');\n schema.enum = type.enumValues.slice();\n return schema;\n }\n return this.objectSchema(type, parents);\n }\n\n // webpieces-disable no-function-outside-class -- private static mapping of this class\n private static primitive(ref: TypeRef, where: string): ApiJsonSchema {\n if (ref.primitive === 'string' || ref.primitive === 'boolean') {\n return new ApiJsonSchema(ref.primitive);\n }\n if (ref.primitive === 'number') {\n return new ApiJsonSchema(ref.integer ? 'integer' : 'number');\n }\n throw new McpRenderError(\n `'${ref.primitive}' has no MCP schema`,\n where,\n 'Give the field a concrete type. `unknown`, `any`, `void` and a bare `null` publish as ' +\n '\"anything\", which an agent cannot fill in.',\n );\n }\n}\n"]}
1
+ {"version":3,"file":"McpSchemaRenderer.js","sourceRoot":"","sources":["../../../../../../packages/docs/api-doc-model/src/render/McpSchemaRenderer.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ApiJsonSchema,\n ApiJsonSchemaDiscriminator,\n ApiJsonSchemaType,\n EndpointOperation,\n McpToolCatalog,\n McpToolDefinition,\n mcpHintsForOperation,\n READ,\n WRITE,\n WRITE_IDEMPOTENT,\n} from '@webpieces/core-util';\nimport {\n ApiDocModel,\n DocumentedEndpoint,\n DocumentedField,\n DocumentedType,\n} from '../model/ApiDocModel';\nimport { TypeRef } from '../model/TypeRef';\nimport { McpRenderError } from './McpRenderError';\n\n/** RFC 9110 token, which is what an `Mcp-Param-{name}` header name has to be. */\nconst HEADER_TOKEN = /^[!#$%&'*+\\-.^_`|~0-9A-Za-z]+$/;\n\n/** ONE `@WpMcpTool` the build could not give a schema, and why. See {@link McpSchemaRenderer.catalogOf}. */\nexport class SkippedMcpTool {\n constructor(\n /** The stable protocol name it declared. */\n readonly name: string,\n readonly contractName: string,\n /** The render refusal, verbatim, including the declaration it points at. */\n readonly reason: string,\n ) {}\n\n toString(): string {\n return `${this.contractName}/${this.name}: ${this.reason}`;\n }\n}\n\n/** What one catalog render produced: the tools, and the ones it could not produce. */\nexport class McpCatalogRender {\n constructor(\n readonly catalog: McpToolCatalog,\n readonly skipped: readonly SkippedMcpTool[],\n ) {}\n}\n\n/**\n * `ApiDocModel` -> the MCP tool list an agent is shown, and the server accepts calls against.\n *\n * ## It is the ONLY source of an MCP schema\n *\n * It was written for #983 as a MEASUREMENT: it rendered the same `ApiJsonSchema` the reflect-metadata\n * runtime built, so the two could be compared field by field. They matched for every DTO shape the\n * runtime could build, which is what licensed #984 to delete `@WpDtoField` and its erasure-repair\n * arguments — `required`, `arrayItems`, `integer`, `minimum`, `maximum`, `enumValues`, `mapValues`,\n * `mcpHeader` — along with `DtoSchemaBuilder` itself. There is now one schema, built here, written to\n * `mcp-tools.json` by `wp-openapi`, and read at boot by `McpToolRegistry`.\n *\n * ## Where it now goes FURTHER than the deleted runtime could\n *\n * The three reproductions #983 documented were runtime capability gaps, and two of them are closed:\n *\n * - **NULLABLE is rendered**, as `type: [T, \"null\"]`. `ApiJsonSchema.type` used to hold one string,\n * so the runtime could not write it down even where the compiler could see `externalId: string | null`\n * — and `{}` and `{externalId: null}` are different wire documents. `type` is now\n * `ApiJsonSchemaType | readonly ApiJsonSchemaType[]` and the union is emitted.\n * - **A bound on an ARRAY of numbers** lands on the ITEM, where OpenAPI puts it. `@WpDtoField`\n * rejected numeric constraints on a non-`Number` field, so it had nowhere to go at all.\n * - **A nested DTO is INLINED**, with the FIELD's prose on it — not a gap but the protocol: MCP tool\n * schemas are inline and have no `$ref`.\n */\nexport class McpSchemaRenderer {\n constructor(private readonly model: ApiDocModel) {}\n\n /** Every `@WpMcpTool` method of the contract, in declaration order. */\n render(): readonly McpToolDefinition[] {\n const tools: McpToolDefinition[] = [];\n for (const endpoint of this.model.endpoints) {\n if (endpoint.mcpTool !== undefined) {\n tools.push(this.tool(endpoint));\n }\n }\n return tools;\n }\n\n /**\n * The tools of SEVERAL contracts as one catalog — the artifact a server boots from — plus every\n * tool that could not be rendered and why.\n *\n * A catalog rather than a per-contract list because the protocol namespace is flat: two contracts\n * declaring one tool name is a collision an agent would see, and {@link McpToolCatalog} refuses it\n * here, at build time, rather than at somebody's boot.\n *\n * ## Why an unrenderable tool is REPORTED here rather than throwing\n *\n * Some shapes have no MCP schema at all — a recursive DTO, an undocumented tool, a request that\n * is not a named object — and the first of those is a limit of the PROTOCOL rather than a defect\n * in a contract that is otherwise perfectly good HTTP. (A DISCRIMINATED UNION used to be on this\n * list and no longer is: #1009 taught `ApiJsonSchema` `oneOf`, so one publishes.) Such a\n * tool was never servable: the reflect-metadata runtime refused it too, for its own reasons. So\n * failing the whole document build over one would stop a repo publishing its OpenAPI over a tool\n * nobody could ever have called.\n *\n * It is not silence either. The build NAMES every skipped tool with its reason, and\n * `McpToolRegistry` REFUSES TO BOOT when a registered `@WpMcpTool` is missing from the catalog —\n * which is the right place for that failure, because that is the process actually claiming to\n * serve it.\n */\n // webpieces-disable no-function-outside-class -- static factory over this class\n static catalogOf(models: readonly ApiDocModel[]): McpCatalogRender {\n const tools: McpToolDefinition[] = [];\n const skipped: SkippedMcpTool[] = [];\n for (const model of models) {\n const renderer = new McpSchemaRenderer(model);\n for (const endpoint of model.endpoints) {\n if (endpoint.mcpTool === undefined) {\n continue;\n }\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- a per-tool render refusal is REPORTED, see the docstring\n try {\n tools.push(renderer.tool(endpoint));\n } catch (err: unknown) {\n //const error = toError(err);\n if (!(err instanceof McpRenderError)) throw err;\n skipped.push(\n new SkippedMcpTool(endpoint.mcpTool.name, model.contractName, err.message),\n );\n }\n }\n }\n return new McpCatalogRender(new McpToolCatalog(tools), skipped);\n }\n\n /** ONE tool. Visible to {@link catalogOf}, which renders tool by tool so it can report one. */\n tool(endpoint: DocumentedEndpoint): McpToolDefinition {\n const where = `${this.model.contractName}.${endpoint.methodName}`;\n const description = endpoint.mcpDescription ?? endpoint.description;\n if (description.trim() === '') {\n throw new McpRenderError(\n 'an MCP tool has no documentation',\n where,\n 'Write a JSDoc block on the method, or an @mcp tag for agent-facing wording. It is ' +\n 'published verbatim by tools/list, so an agent has nothing else to go on.',\n );\n }\n if (endpoint.request === undefined || endpoint.response === undefined) {\n throw new McpRenderError(\n 'an MCP tool has no declared request or response type',\n where,\n 'Declare both: one request parameter and a Promise<Response> return type.',\n );\n }\n return new McpToolDefinition(\n endpoint.mcpTool!.name,\n endpoint.methodName,\n description,\n mcpHintsForOperation(\n McpSchemaRenderer.operationOf(endpoint, where),\n endpoint.openWorld,\n ),\n this.rootSchema(endpoint.request, where, 'request'),\n this.rootSchema(endpoint.response, where, 'response'),\n );\n }\n\n /**\n * `operation` verbatim from the model, mapped back onto the REAL constant.\n *\n * A `switch` and not a cast, because the model carries the operation as a string and a cast would\n * hand `mcpHintsForOperation` a value it has no case for — which, for a function whose three arms\n * are exhaustive, means falling off the end and publishing a tool with NO hints.\n */\n // webpieces-disable no-function-outside-class -- private static mapping of this class\n private static operationOf(endpoint: DocumentedEndpoint, where: string): EndpointOperation {\n switch (endpoint.operation) {\n case READ:\n return READ;\n case WRITE_IDEMPOTENT:\n return WRITE_IDEMPOTENT;\n case WRITE:\n return WRITE;\n default:\n throw new McpRenderError(\n `@Endpoint declares operation '${endpoint.operation}', which has no MCP hints`,\n where,\n `Use one of the exported constants: ${READ}, ${WRITE_IDEMPOTENT}, ${WRITE}.`,\n );\n }\n }\n\n /**\n * A tool's input/output schema: always a CLOSED OBJECT, because MCP publishes objects — and\n * because a union at the ROOT of a tool schema is refused by the function-calling APIs that\n * consume it. Both OpenAI and Anthropic reject a top-level `oneOf`/`anyOf`/`allOf`, and a server\n * sends its WHOLE tool list on every request, so one such tool makes every request 400 and bricks\n * the session — not just that tool. Nested composition, inside a property, is fine.\n *\n * The build-time rule `no-root-union-api-type` refuses the same shape across every `@ApiPath`\n * contract in the workspace, `@ApiType` or not. This is the renderer's own backstop, so a catalog\n * can never carry one even if it is reached some other way.\n */\n private rootSchema(ref: TypeRef, where: string, side: string): ApiJsonSchema {\n if (ref.kind === 'union') {\n throw new McpRenderError(\n `an MCP tool's ${side} is itself a union`,\n where,\n `Wrap it in a property of an object ${side} — a top-level oneOf is rejected by the ` +\n 'OpenAI and Anthropic function-calling APIs, and one such tool 400s every ' +\n 'request in the session.',\n );\n }\n if (ref.kind !== 'ref') {\n throw new McpRenderError(\n `an MCP tool's ${side} is not a named DTO`,\n where,\n 'Give it a named interface or class. An inline or primitive ' +\n `${side} has no object schema, and MCP publishes objects.`,\n );\n }\n const type = this.namedType(ref.refName!, where);\n if (type.unionRefNames.length > 0) {\n throw new McpRenderError(\n `an MCP tool's ${side} '${type.name}' is itself a union`,\n where,\n `Wrap it in a property of an object ${side} — a top-level oneOf is rejected by the ` +\n 'OpenAI and Anthropic function-calling APIs, and one such tool 400s every ' +\n 'request in the session.',\n );\n }\n return this.objectSchema(type, new Set<string>());\n }\n\n private namedType(name: string, where: string): DocumentedType {\n const type = this.model.types.get(name);\n if (type === undefined) {\n throw new McpRenderError(\n `no model entry for type '${name}'`,\n where,\n 'Declare the type in a file the extractor reaches from this contract.',\n );\n }\n return type;\n }\n\n /**\n * One object DTO. CLOSED (`additionalProperties: false`), with `required` written only when it is\n * non-empty, so an all-optional DTO carries no empty list.\n *\n * The cycle stop is `parents`, and it THROWS rather than truncating: an inline schema cannot\n * express a recursive DTO, and one that silently stopped a level down would publish a shape the\n * server does not accept.\n */\n private objectSchema(type: DocumentedType, parents: ReadonlySet<string>): ApiJsonSchema {\n if (parents.has(type.name)) {\n throw new McpRenderError(\n `recursive DTO '${type.name}' cannot use an inline MCP schema`,\n type.name,\n 'Break the cycle, or keep this DTO out of the MCP document — a tool schema is ' +\n 'inline and has no $ref to close a loop with.',\n );\n }\n if (type.unionRefNames.length > 0) {\n return this.unionSchema(type, parents);\n }\n if (type.indexSignatureValue !== undefined && type.fields.length > 0) {\n throw new McpRenderError(\n `'${type.name}' has both named fields and an index signature`,\n type.name,\n 'Split the open map into a field of its own: a schema is either closed or a typed ' +\n 'map, and MCP has no spelling for half of each.',\n );\n }\n const nested = new Set(parents);\n nested.add(type.name);\n\n const schema = new ApiJsonSchema('object');\n schema.properties = {};\n schema.additionalProperties = false;\n const required: string[] = [];\n for (const field of type.fields) {\n schema.properties[field.name] = this.fieldSchema(type, field, nested);\n if (!field.optional) {\n required.push(field.name);\n }\n }\n if (required.length > 0) {\n schema.required = required;\n }\n return schema;\n }\n\n /**\n * A UNION, as `oneOf` with its branches INLINE and its DERIVED discriminator.\n *\n * MCP tool schemas are JSON Schema 2020-12 — the same dialect OpenAPI 3.1 uses — so `oneOf` was\n * never the obstacle; the obstacle was this repo's own subset, which had no spelling for it\n * (#1009). The shape written here MIRRORS `SchemaRenderer.union` in `@webpieces/openapi-generator`\n * branch for branch, because a union has ONE published spelling and that renderer is the\n * reference implementation of it. The only difference is the absence of `$ref`: a tool schema is\n * inline, so each branch is rendered in full and the discriminator maps to branch NAMES.\n *\n * `discriminator` is written ONLY when the model DERIVED one. A union TypeScript itself cannot\n * narrow is published as a bare `oneOf` rather than with an invented discriminator — claiming a\n * narrowing the source does not have is worse than admitting there is none.\n */\n private unionSchema(type: DocumentedType, parents: ReadonlySet<string>): ApiJsonSchema {\n const nested = new Set(parents);\n nested.add(type.name);\n const schema = new ApiJsonSchema();\n schema.oneOf = type.unionRefNames.map((name: string) =>\n this.objectSchema(this.namedType(name, type.name), nested),\n );\n if (type.description.trim() !== '') {\n schema.description = type.description;\n }\n if (type.discriminator !== undefined) {\n const mapping: Record<string, string> = {};\n for (const branch of type.unionRefNames) {\n const value = type.discriminator.branchValues.get(branch);\n if (value !== undefined) {\n mapping[value] = branch;\n }\n }\n schema.discriminator = new ApiJsonSchemaDiscriminator(\n type.discriminator.propertyName,\n mapping,\n );\n }\n return schema;\n }\n\n /**\n * A FIELD whose type is a union: the model registers `type X = A | B` as its OWN entry carrying\n * the derived discriminator, while the field holds a bare list of branch names. Rendering that\n * list directly would publish the `oneOf` and silently DROP the discriminator — the one part of a\n * union a client needs to narrow on — so the alias is looked up by its branch list first. This is\n * the same lookup `SchemaRenderer.namedUnion` does to emit its `$ref`.\n */\n private fieldUnion(\n branchNames: readonly string[],\n where: string,\n parents: ReadonlySet<string>,\n ): ApiJsonSchema {\n const key = branchNames.join(',');\n for (const name of this.model.types.keys()) {\n const candidate = this.model.types.get(name)!;\n if (candidate.unionRefNames.length > 0 && candidate.unionRefNames.join(',') === key) {\n return this.unionSchema(candidate, parents);\n }\n }\n const schema = new ApiJsonSchema();\n schema.oneOf = branchNames.map((name: string) =>\n this.objectSchema(this.namedType(name, where), parents),\n );\n return schema;\n }\n\n /**\n * One FIELD: its type, then the prose and the constraints that hang off the field.\n *\n * The ORDER matters: the type is built first and `description` is written over whatever the type\n * produced, so a nested DTO carries the FIELD's sentence rather than the DTO's. `@WpMin` /\n * `@WpMax` land on the numeric LEAF — on an array, on the item — for the same reason\n * `SchemaRenderer` puts them there: a `minimum` on an array means nothing.\n *\n * NULLABLE widens the type to `[T, \"null\"]` and is deliberately NOT the same thing as OPTIONAL,\n * which is an absent entry in the object's `required` list. `{}` and `{externalId: null}` are\n * different wire documents, and an agent told only \"optional\" would send the wrong one.\n */\n private fieldSchema(\n owner: DocumentedType,\n field: DocumentedField,\n parents: ReadonlySet<string>,\n ): ApiJsonSchema {\n const where = `${owner.name}.${field.name}`;\n const schema = this.typeSchema(field.type, where, parents);\n const description = field.mcpDescription ?? field.description;\n if (description.trim() === '') {\n throw new McpRenderError(\n 'a published DTO field has no documentation',\n where,\n 'Write a JSDoc sentence on the field. It is the only thing an agent is told about ' +\n 'that parameter.',\n );\n }\n schema.description = description;\n\n const leaf = field.type.kind === 'array' ? schema.items! : schema;\n if (field.min !== undefined) {\n leaf.minimum = field.min;\n }\n if (field.max !== undefined) {\n leaf.maximum = field.max;\n }\n if (field.mcpHeader !== undefined) {\n McpSchemaRenderer.assertHeaderFits(field, schema, where);\n schema['x-mcp-header'] = field.mcpHeader;\n }\n if (field.nullable) {\n schema.type = McpSchemaRenderer.nullable(schema, where);\n }\n return schema;\n }\n\n /** `T` -> `[T, \"null\"]`, refusing a field with no type at all rather than publishing `[\"null\"]`. */\n // webpieces-disable no-function-outside-class -- private static mapping of this class\n private static nullable(schema: ApiJsonSchema, where: string): readonly ApiJsonSchemaType[] {\n const base = ApiJsonSchema.baseTypeOf(schema);\n if (base === undefined) {\n throw new McpRenderError(\n 'a nullable field has no other type',\n where,\n 'A field typed only `null` carries no information — give it a real type beside it.',\n );\n }\n return [base, 'null'];\n }\n\n /**\n * `@mcpHeader` mirrors a PRIMITIVE parameter into `Mcp-Param-{name}` (MCP 2026 SEP-2243). The\n * three conditions are the runtime's, restated against the compiler's view of the field, so a\n * declaration the server would reject at boot fails the document build instead.\n */\n // webpieces-disable no-function-outside-class -- private static validator of this class\n private static assertHeaderFits(\n field: DocumentedField,\n schema: ApiJsonSchema,\n where: string,\n ): void {\n if (!HEADER_TOKEN.test(field.mcpHeader!)) {\n throw new McpRenderError(\n `@mcpHeader '${field.mcpHeader}' is not an RFC 9110 token`,\n where,\n 'Use letters, digits and the token punctuation only — it becomes an HTTP header name.',\n );\n }\n if (schema.type !== 'string' && schema.type !== 'boolean' && schema.type !== 'integer') {\n throw new McpRenderError(\n '@mcpHeader is on a field that is not a primitive an MCP header can carry',\n where,\n 'Mirror a string, a boolean or an Integer. A non-integer number and every object ' +\n 'shape are excluded by MCP 2026-07-28.',\n );\n }\n }\n\n /** One resolved type, with no field-level prose or constraints on it. */\n private typeSchema(ref: TypeRef, where: string, parents: ReadonlySet<string>): ApiJsonSchema {\n switch (ref.kind) {\n case 'primitive':\n return McpSchemaRenderer.primitive(ref, where);\n case 'enum': {\n const schema = new ApiJsonSchema('string');\n schema.enum = ref.enumValues.slice();\n return schema;\n }\n case 'array': {\n const schema = new ApiJsonSchema('array');\n schema.items = this.typeSchema(ref.items!, where, parents);\n return schema;\n }\n case 'openMap': {\n const schema = new ApiJsonSchema('object');\n schema.additionalProperties = this.typeSchema(ref.values!, where, parents);\n return schema;\n }\n case 'ref':\n return this.referencedSchema(ref.refName!, where, parents);\n case 'union':\n return this.fieldUnion(ref.unionRefNames, where, parents);\n default:\n throw new McpRenderError(\n `no MCP schema for the declared type '${ref.unmappedText ?? '<unknown>'}'`,\n where,\n 'Give the field a shape the model can represent — a named DTO, an array of one, ' +\n 'a string-literal union, a Record, or a primitive.',\n );\n }\n }\n\n /** A named type: a string enum becomes `enum`, an object DTO is INLINED (MCP has no `$ref`). */\n private referencedSchema(\n name: string,\n where: string,\n parents: ReadonlySet<string>,\n ): ApiJsonSchema {\n const type = this.namedType(name, where);\n if (type.enumValues.length > 0) {\n const schema = new ApiJsonSchema('string');\n schema.enum = type.enumValues.slice();\n return schema;\n }\n return this.objectSchema(type, parents);\n }\n\n // webpieces-disable no-function-outside-class -- private static mapping of this class\n private static primitive(ref: TypeRef, where: string): ApiJsonSchema {\n if (ref.primitive === 'string' || ref.primitive === 'boolean') {\n return new ApiJsonSchema(ref.primitive);\n }\n if (ref.primitive === 'number') {\n return new ApiJsonSchema(ref.integer ? 'integer' : 'number');\n }\n throw new McpRenderError(\n `'${ref.primitive}' has no MCP schema`,\n where,\n 'Give the field a concrete type. `unknown`, `any`, `void` and a bare `null` publish as ' +\n '\"anything\", which an agent cannot fill in.',\n );\n }\n}\n"]}