@ultimat3/schema 23.0.0 → 25.0.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.
@@ -0,0 +1,126 @@
1
+ // Single responsibility: the JSON Schema subset an MCP tool publishes, and the two narrowings onto
2
+ // it — an input's and an output's. One home, tier 0: `@ultimat3/mcp`'s `tools/list` is the one
3
+ // tool projection, and no second one may publish a different document for the same declaration.
4
+
5
+ import type { JsonSchema } from './json-schema';
6
+ import { toMcpInputSchema } from './json-schema';
7
+
8
+ /**
9
+ * The subset a tool's `inputSchema` and `outputSchema` speak, and the one `@ultimat3/mcp`'s
10
+ * `validateArgs` holds a hand-written tool to. Narrow on purpose: a tool schema an agent cannot
11
+ * fully understand is a tool it will call wrong.
12
+ */
13
+ export interface WireJsonSchema {
14
+ readonly type?: 'object' | 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'null';
15
+ readonly title?: string;
16
+ readonly description?: string;
17
+ readonly properties?: Readonly<Record<string, WireJsonSchema>>;
18
+ readonly required?: readonly string[];
19
+ readonly additionalProperties?: boolean;
20
+ readonly items?: WireJsonSchema;
21
+ readonly enum?: readonly (string | number | boolean | null)[];
22
+ readonly const?: string | number | boolean | null;
23
+ readonly default?: unknown;
24
+ /**
25
+ * NOT in the subset: `format` NAMES a rule whose meaning lives in this package (`uuid`, `email`,
26
+ * `iana-time-zone`), and `validateArgs` cannot hold a hand-written tool to it without a second
27
+ * definition of each one. `never`, so a narrowing that starts copying it again fails to compile.
28
+ * `pattern` is the opposite case — the rule travels WITH the schema, so it is kept.
29
+ */
30
+ readonly format?: never;
31
+ readonly minimum?: number;
32
+ readonly maximum?: number;
33
+ readonly minLength?: number;
34
+ readonly maxLength?: number;
35
+ /** A `RegExp.source`, JSON Schema semantics (a partial match unless it anchors itself). */
36
+ readonly pattern?: string;
37
+ /** The flags `pattern` compiles with — JSON Schema's `pattern` carries none. */
38
+ readonly 'x-ultimate-pattern-flags'?: string;
39
+ readonly anyOf?: readonly WireJsonSchema[];
40
+ }
41
+
42
+ /** A Standard Schema → a tool's `inputSchema`. Throws `X_SCHEMA_UNSUPPORTED` when it cannot say. */
43
+ export function toWireSchema(schema: unknown): WireJsonSchema {
44
+ return narrow(toMcpInputSchema(schema));
45
+ }
46
+
47
+ /**
48
+ * A Standard Schema → a tool's `outputSchema`, or `undefined` when its root is not an object (MCP's
49
+ * `structuredContent` is one) or it cannot be introspected. STRUCTURE ONLY: a client refuses a call
50
+ * whose `structuredContent` misses this document, and a bound, a pattern or `additionalProperties:
51
+ * false` on a value the server produced (a query's rows are never re-parsed) is a promise nothing
52
+ * enforces on the way out.
53
+ */
54
+ export function toWireOutputSchema(schema: unknown): WireJsonSchema | undefined {
55
+ let rich: JsonSchema;
56
+ try {
57
+ rich = toMcpInputSchema(schema);
58
+ } catch {
59
+ return undefined;
60
+ }
61
+ return rich.type === 'object' ? shape(rich) : undefined;
62
+ }
63
+
64
+ // exactOptionalPropertyTypes: every field is attached only when present, never as an explicit
65
+ // `undefined` — `tools/list` serialises this object verbatim.
66
+ function narrow(source: JsonSchema): WireJsonSchema {
67
+ return {
68
+ ...(source.type === undefined ? {} : { type: source.type }),
69
+ ...(source.title === undefined ? {} : { title: source.title }),
70
+ ...(source.description === undefined ? {} : { description: source.description }),
71
+ ...(source.properties === undefined ? {} : { properties: mapKeys(source.properties, narrow) }),
72
+ ...(source.required === undefined ? {} : { required: source.required }),
73
+ // The subset says yes or no to extra keys; an unrepresentable value schema becomes `true`
74
+ // (permit) rather than a rejection the agent was never warned about.
75
+ ...(source.additionalProperties === undefined
76
+ ? {}
77
+ : { additionalProperties: source.additionalProperties !== false }),
78
+ ...(source.items === undefined ? {} : { items: narrow(source.items) }),
79
+ ...(source.enum === undefined ? {} : { enum: source.enum }),
80
+ ...(source.const === undefined ? {} : { const: source.const }),
81
+ ...(source.default === undefined ? {} : { default: source.default }),
82
+ ...(source.minimum === undefined ? {} : { minimum: source.minimum }),
83
+ ...(source.maximum === undefined ? {} : { maximum: source.maximum }),
84
+ ...(source.minLength === undefined ? {} : { minLength: source.minLength }),
85
+ ...(source.maxLength === undefined ? {} : { maxLength: source.maxLength }),
86
+ ...(source.pattern === undefined ? {} : { pattern: source.pattern }),
87
+ ...(source['x-ultimate-pattern-flags'] === undefined
88
+ ? {}
89
+ : { 'x-ultimate-pattern-flags': source['x-ultimate-pattern-flags'] }),
90
+ ...(source.anyOf === undefined ? {} : { anyOf: source.anyOf.map(narrow) }),
91
+ };
92
+ }
93
+
94
+ function shape(source: JsonSchema): WireJsonSchema {
95
+ return {
96
+ ...(source.type === undefined ? {} : { type: source.type }),
97
+ ...(source.title === undefined ? {} : { title: source.title }),
98
+ ...(source.description === undefined ? {} : { description: source.description }),
99
+ ...(source.properties === undefined ? {} : { properties: mapKeys(source.properties, shape) }),
100
+ ...(source.required === undefined ? {} : { required: source.required }),
101
+ ...(source.items === undefined ? {} : { items: shape(source.items) }),
102
+ ...(source.enum === undefined ? {} : { enum: source.enum }),
103
+ ...(source.const === undefined ? {} : { const: source.const }),
104
+ ...(source.anyOf === undefined ? {} : { anyOf: source.anyOf.map(shape) }),
105
+ };
106
+ }
107
+
108
+ /**
109
+ * `out[key] = …` is not an assignment for exactly one name: `__proto__` runs `Object.prototype`'s
110
+ * setter and RE-PROTOTYPES the record, so a declared field vanishes from `tools/list`.
111
+ */
112
+ function mapKeys(
113
+ properties: Readonly<Record<string, JsonSchema>>,
114
+ each: (child: JsonSchema) => WireJsonSchema,
115
+ ): Readonly<Record<string, WireJsonSchema>> {
116
+ const out: Record<string, WireJsonSchema> = {};
117
+ for (const [key, child] of Object.entries(properties)) {
118
+ Object.defineProperty(out, key, {
119
+ value: each(child),
120
+ writable: true,
121
+ enumerable: true,
122
+ configurable: true,
123
+ });
124
+ }
125
+ return out;
126
+ }