stitchkit 0.23.0 → 0.25.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/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  emitResult,
5
5
  parseCliArgs,
6
6
  pollUntilDone
7
- } from "./index-x8m8e7dc.js";
7
+ } from "./index-h9d1fm0p.js";
8
8
  import"./index-0ed3bx43.js";
9
9
  import"./index-4gawbm74.js";
10
10
  import"./index-c7nyw0yt.js";
@@ -76,13 +76,66 @@ function coerceJsonArgs(args, schema) {
76
76
  }
77
77
 
78
78
  // src/tools/flatten.ts
79
+ import { z as z3 } from "zod";
80
+
81
+ // src/tools/schema.ts
79
82
  import { z as z2 } from "zod";
83
+ function objectShapeKeys(schema) {
84
+ return schema instanceof z2.ZodObject ? Object.keys(schema.shape) : [];
85
+ }
86
+ function keyPolicyOf(schema) {
87
+ return schema.def.catchall;
88
+ }
89
+ function rebuildObject(source, shape) {
90
+ return withKeyPolicy(z2.object(shape), keyPolicyOf(source));
91
+ }
92
+ function withKeyPolicy(object, policy) {
93
+ if (policy === undefined)
94
+ return object;
95
+ return object.catchall(representable(policy));
96
+ }
97
+ function representable(policy) {
98
+ if (policy instanceof z2.ZodNever || policy instanceof z2.ZodUnknown)
99
+ return policy;
100
+ if (!(policy instanceof z2.ZodType))
101
+ return z2.unknown();
102
+ try {
103
+ toJsonSchema(policy, "input");
104
+ return policy;
105
+ } catch {
106
+ return z2.unknown();
107
+ }
108
+ }
109
+ function mergeSchemas(paramsSchema, inputSchema) {
110
+ if (paramsSchema && !(paramsSchema instanceof z2.ZodObject)) {
111
+ throw new Error("Tool params schema must be a z.object()");
112
+ }
113
+ const paramsObject = paramsSchema instanceof z2.ZodObject ? paramsSchema : undefined;
114
+ if (!inputSchema) {
115
+ return paramsObject ?? z2.object({});
116
+ }
117
+ if (inputSchema instanceof z2.ZodObject) {
118
+ if (paramsObject) {
119
+ const conflicts = Object.keys(paramsObject.shape).filter((key) => (key in inputSchema.shape));
120
+ if (conflicts.length > 0) {
121
+ throw new Error(`Schema merge conflict: ${conflicts.join(", ")} appear in both params and input`);
122
+ }
123
+ }
124
+ return rebuildObject(inputSchema, {
125
+ ...paramsObject?.shape ?? {},
126
+ ...inputSchema.shape
127
+ });
128
+ }
129
+ return paramsObject ? z2.intersection(paramsObject, inputSchema) : inputSchema;
130
+ }
131
+
132
+ // src/tools/flatten.ts
80
133
  function flattenDiscriminatedUnion(union) {
81
134
  const discriminator = union.def.discriminator;
82
135
  const allDiscValues = [];
83
136
  const perKey = new Map;
84
137
  for (const opt of union.def.options) {
85
- if (!(opt instanceof z2.ZodObject)) {
138
+ if (!(opt instanceof z3.ZodObject)) {
86
139
  throw new Error(`flattenDiscriminatedUnion: variant for discriminator '${discriminator}' is not a ZodObject`);
87
140
  }
88
141
  const discValues = stringLiteralOrEnumValues(opt.shape[discriminator]);
@@ -95,7 +148,7 @@ function flattenDiscriminatedUnion(union) {
95
148
  if (key === discriminator)
96
149
  continue;
97
150
  const entry = perKey.get(key) ?? { schemas: [], variants: [] };
98
- entry.schemas.push(field instanceof z2.ZodOptional ? field.unwrap() : field);
151
+ entry.schemas.push(unwrapField(field));
99
152
  entry.variants.push(label);
100
153
  perKey.set(key, entry);
101
154
  }
@@ -106,23 +159,44 @@ function flattenDiscriminatedUnion(union) {
106
159
  throw new Error("flattenDiscriminatedUnion: union has no options");
107
160
  }
108
161
  const shape = {
109
- [discriminator]: z2.enum([firstLiteral, ...restLiterals])
162
+ [discriminator]: z3.enum([firstLiteral, ...restLiterals])
110
163
  };
111
164
  for (const [key, entry] of perKey) {
112
165
  const advertised = mergeCollidingFields(entry.schemas);
113
166
  const hint = `Required if ${discriminator} = ${[...new Set(entry.variants)].join(" | ")}`;
114
- shape[key] = z2.optional(advertised).describe(hint);
167
+ shape[key] = z3.optional(advertised).describe(hint);
168
+ }
169
+ return withKeyPolicy(z3.object(shape), mergeVariantKeyPolicies(union.def.options));
170
+ }
171
+ function mergeVariantKeyPolicies(options) {
172
+ let strict = 0;
173
+ let counted = 0;
174
+ for (const option of options) {
175
+ const policy = option instanceof z3.ZodObject ? keyPolicyOf(option) : undefined;
176
+ if (policy !== undefined && !(policy instanceof z3.ZodNever))
177
+ return z3.unknown();
178
+ counted++;
179
+ if (policy instanceof z3.ZodNever)
180
+ strict++;
181
+ }
182
+ if (counted === 0 || strict === 0)
183
+ return;
184
+ return strict === counted ? z3.never() : z3.unknown();
185
+ }
186
+ function unwrapField(field) {
187
+ if (field instanceof z3.ZodOptional || field instanceof z3.ZodDefault) {
188
+ return unwrapField(field.unwrap());
115
189
  }
116
- return z2.object(shape);
190
+ return field;
117
191
  }
118
192
  function preserveDescription(from, to) {
119
193
  return from.description === undefined ? to : to.describe(from.description);
120
194
  }
121
195
  function stringLiteralOrEnumValues(field) {
122
196
  let raw = null;
123
- if (field instanceof z2.ZodLiteral)
197
+ if (field instanceof z3.ZodLiteral)
124
198
  raw = field.def.values;
125
- else if (field instanceof z2.ZodEnum)
199
+ else if (field instanceof z3.ZodEnum)
126
200
  raw = field.options;
127
201
  if (raw === null)
128
202
  return null;
@@ -134,7 +208,7 @@ function isFlattenableDU(union) {
134
208
  if (union.def.options.length === 0)
135
209
  return false;
136
210
  for (const opt of union.def.options) {
137
- if (!(opt instanceof z2.ZodObject))
211
+ if (!(opt instanceof z3.ZodObject))
138
212
  return false;
139
213
  if (stringLiteralOrEnumValues(opt.shape[disc]) === null)
140
214
  return false;
@@ -156,31 +230,31 @@ function stripAnnotations(node) {
156
230
  }
157
231
  }
158
232
  function hasChecks(schema) {
159
- if (!(schema instanceof z2.ZodType))
233
+ if (!(schema instanceof z3.ZodType))
160
234
  return false;
161
235
  const def = schema.def;
162
236
  if (isRecord(def) && Array.isArray(def.checks) && def.checks.length > 0)
163
237
  return true;
164
- if (schema instanceof z2.ZodOptional || schema instanceof z2.ZodNullable || schema instanceof z2.ZodDefault) {
238
+ if (schema instanceof z3.ZodOptional || schema instanceof z3.ZodNullable || schema instanceof z3.ZodDefault) {
165
239
  return hasChecks(schema.unwrap());
166
240
  }
167
- if (schema instanceof z2.ZodPipe)
241
+ if (schema instanceof z3.ZodPipe)
168
242
  return hasChecks(schema.def.in) || hasChecks(schema.def.out);
169
- if (schema instanceof z2.ZodObject)
243
+ if (schema instanceof z3.ZodObject)
170
244
  return Object.values(schema.shape).some(hasChecks);
171
- if (schema instanceof z2.ZodArray)
245
+ if (schema instanceof z3.ZodArray)
172
246
  return hasChecks(schema.element);
173
- if (schema instanceof z2.ZodUnion)
247
+ if (schema instanceof z3.ZodUnion)
174
248
  return schema.def.options.some(hasChecks);
175
- if (schema instanceof z2.ZodRecord)
249
+ if (schema instanceof z3.ZodRecord)
176
250
  return hasChecks(schema.valueType);
177
- if (schema instanceof z2.ZodIntersection) {
251
+ if (schema instanceof z3.ZodIntersection) {
178
252
  return hasChecks(schema.def.left) || hasChecks(schema.def.right);
179
253
  }
180
254
  return false;
181
255
  }
182
256
  function normalizedJson(schema) {
183
- if (!(schema instanceof z2.ZodType))
257
+ if (!(schema instanceof z3.ZodType))
184
258
  return "{}";
185
259
  const json = toJsonSchema(schema, "input", "any");
186
260
  stripAnnotations(json);
@@ -197,8 +271,8 @@ function mergeCollidingFields(schemas) {
197
271
  const only = values[0];
198
272
  if (values.length <= 1) {
199
273
  if (only === undefined)
200
- return z2.unknown();
201
- return schemas.length > 1 && hasChecks(only) ? z2.unknown() : only;
274
+ return z3.unknown();
275
+ return schemas.length > 1 && hasChecks(only) ? z3.unknown() : only;
202
276
  }
203
277
  const merged = [];
204
278
  let allEnum = true;
@@ -214,47 +288,47 @@ function mergeCollidingFields(schemas) {
214
288
  const uniq = [...new Set(merged)];
215
289
  const [first, ...rest] = uniq;
216
290
  if (first !== undefined)
217
- return z2.enum([first, ...rest]);
291
+ return z3.enum([first, ...rest]);
218
292
  }
219
- return z2.unknown();
293
+ return z3.unknown();
220
294
  }
221
295
  function flattenUnionsDeep(schema) {
222
- if (!(schema instanceof z2.ZodType))
223
- return z2.unknown();
224
- if (schema instanceof z2.ZodDiscriminatedUnion) {
296
+ if (!(schema instanceof z3.ZodType))
297
+ return z3.unknown();
298
+ if (schema instanceof z3.ZodDiscriminatedUnion) {
225
299
  if (!isFlattenableDU(schema))
226
300
  return schema;
227
301
  return preserveDescription(schema, flattenUnionsDeep(flattenDiscriminatedUnion(schema)));
228
302
  }
229
- if (schema instanceof z2.ZodObject) {
303
+ if (schema instanceof z3.ZodObject) {
230
304
  const shape = {};
231
305
  for (const [key, field] of Object.entries(schema.shape)) {
232
306
  shape[key] = flattenUnionsDeep(field);
233
307
  }
234
- return preserveDescription(schema, z2.object(shape));
308
+ return preserveDescription(schema, rebuildObject(schema, shape));
235
309
  }
236
- if (schema instanceof z2.ZodArray) {
237
- return preserveDescription(schema, z2.array(flattenUnionsDeep(schema.element)));
310
+ if (schema instanceof z3.ZodArray) {
311
+ return preserveDescription(schema, z3.array(flattenUnionsDeep(schema.element)));
238
312
  }
239
- if (schema instanceof z2.ZodUnion) {
313
+ if (schema instanceof z3.ZodUnion) {
240
314
  const opts = schema.def.options.map((option) => flattenUnionsDeep(option));
241
315
  const [a, b, ...rest] = opts;
242
- return a && b ? preserveDescription(schema, z2.union([a, b, ...rest])) : schema;
316
+ return a && b ? preserveDescription(schema, z3.union([a, b, ...rest])) : schema;
243
317
  }
244
- if (schema instanceof z2.ZodRecord) {
245
- return preserveDescription(schema, z2.record(schema.keyType, flattenUnionsDeep(schema.valueType)));
318
+ if (schema instanceof z3.ZodRecord) {
319
+ return preserveDescription(schema, z3.record(schema.keyType, flattenUnionsDeep(schema.valueType)));
246
320
  }
247
- if (schema instanceof z2.ZodOptional) {
248
- return preserveDescription(schema, z2.optional(flattenUnionsDeep(schema.unwrap())));
321
+ if (schema instanceof z3.ZodOptional) {
322
+ return preserveDescription(schema, z3.optional(flattenUnionsDeep(schema.unwrap())));
249
323
  }
250
- if (schema instanceof z2.ZodNullable) {
251
- return preserveDescription(schema, z2.nullable(flattenUnionsDeep(schema.unwrap())));
324
+ if (schema instanceof z3.ZodNullable) {
325
+ return preserveDescription(schema, z3.nullable(flattenUnionsDeep(schema.unwrap())));
252
326
  }
253
- if (schema instanceof z2.ZodDefault) {
327
+ if (schema instanceof z3.ZodDefault) {
254
328
  return preserveDescription(schema, flattenUnionsDeep(schema.unwrap()).default(schema.def.defaultValue));
255
329
  }
256
- if (schema instanceof z2.ZodIntersection) {
257
- return preserveDescription(schema, z2.intersection(flattenUnionsDeep(schema.def.left), flattenUnionsDeep(schema.def.right)));
330
+ if (schema instanceof z3.ZodIntersection) {
331
+ return preserveDescription(schema, z3.intersection(flattenUnionsDeep(schema.def.left), flattenUnionsDeep(schema.def.right)));
258
332
  }
259
333
  return schema;
260
334
  }
@@ -262,31 +336,6 @@ function flattenUnionsDeep(schema) {
262
336
  // src/tools/mount.ts
263
337
  import { z as z4 } from "zod";
264
338
 
265
- // src/tools/schema.ts
266
- import { z as z3 } from "zod";
267
- function objectShapeKeys(schema) {
268
- return schema instanceof z3.ZodObject ? Object.keys(schema.shape) : [];
269
- }
270
- function mergeSchemas(paramsSchema, inputSchema) {
271
- if (paramsSchema && !(paramsSchema instanceof z3.ZodObject)) {
272
- throw new Error("Tool params schema must be a z.object()");
273
- }
274
- const paramsObject = paramsSchema instanceof z3.ZodObject ? paramsSchema : undefined;
275
- if (!inputSchema) {
276
- return paramsObject ?? z3.object({});
277
- }
278
- if (inputSchema instanceof z3.ZodObject) {
279
- if (paramsObject) {
280
- const conflicts = Object.keys(paramsObject.shape).filter((key) => (key in inputSchema.shape));
281
- if (conflicts.length > 0) {
282
- throw new Error(`Schema merge conflict: ${conflicts.join(", ")} appear in both params and input`);
283
- }
284
- }
285
- return z3.object({ ...paramsObject?.shape ?? {}, ...inputSchema.shape });
286
- }
287
- return paramsObject ? z3.intersection(paramsObject, inputSchema) : inputSchema;
288
- }
289
-
290
339
  // src/tools/execute.ts
291
340
  function toolResultFromError(err) {
292
341
  const appErr = normalizeError(err);
@@ -421,7 +470,7 @@ function applyExtend(base, extra) {
421
470
  if (conflicts.length > 0) {
422
471
  throw new Error(`Tool extend conflict: ${conflicts.join(", ")} already declared by the contract`);
423
472
  }
424
- return z4.object({ ...extra, ...base.shape });
473
+ return rebuildObject(base, { ...extra, ...base.shape });
425
474
  }
426
475
  return z4.intersection(z4.object(extra), base);
427
476
  }
@@ -6,7 +6,8 @@ import { z } from 'zod';
6
6
  * Operates recursively against the schema (object fields, array items, and the
7
7
  * matching variant of a discriminated union) so a stringified value at any depth
8
8
  * is repaired, not just a top-level field. The transform touches the **arguments**
9
- * only — the advertised tool schema stays exactly the contract schema.
9
+ * only — it never rebuilds a schema, so it cannot alter what an object does with
10
+ * an undeclared key (→ ADR 0034).
10
11
  */
11
12
  export declare function coerceJsonArgs(args: Record<string, unknown>, schema: z.ZodType | undefined): Record<string, unknown>;
12
13
  //# sourceMappingURL=coerce.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"coerce.d.ts","sourceRoot":"","sources":["../../src/tools/coerce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAoFxB;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,SAAS,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGzB"}
1
+ {"version":3,"file":"coerce.d.ts","sourceRoot":"","sources":["../../src/tools/coerce.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAoFxB;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,CAAC,CAAC,OAAO,GAAG,SAAS,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGzB"}
@@ -18,10 +18,18 @@ import { z } from 'zod';
18
18
  * 0033) **and** a superset of the original union, so a model can always produce a
19
19
  * value that passes both the transport SDK and validation.
20
20
  *
21
- * **Lossy and advertised-only:** the original union remains the validation schema
22
- * in `executeToolMethod`. `.strict()` / `.catchall()` / object-level refinements
23
- * on variants are dropped from the *advertised* hint (validation still enforces
24
- * them — see ADR 0033 on the strict-variant caveat).
21
+ * **Key policy is merged, not dropped.** The flat object stands in for every
22
+ * variant, so it takes the policy that cannot remove what any variant would have
23
+ * kept: every variant `.strict()` → strict (sound, because the flat shape is the
24
+ * union of all variant keys); any variant with a catchall → loose; otherwise
25
+ * plain. A *typed* catchall is never copied onto the flat object — it would
26
+ * reject a sibling variant's differently-typed extra key.
27
+ *
28
+ * **Still lossy:** per-variant strictness (a key legal in variant A and illegal
29
+ * in B) is unrepresentable in one flat object, as are object-level refinements.
30
+ * Those stay enforced only by the original union in `executeToolMethod`. What is
31
+ * *not* lossy any more is deletion — the advertised schema can no longer drop a
32
+ * key the contract would have seen. → ADR 0034.
25
33
  */
26
34
  export declare function flattenDiscriminatedUnion(union: z.ZodDiscriminatedUnion): z.ZodObject<z.ZodRawShape>;
27
35
  /**
@@ -30,8 +38,10 @@ export declare function flattenDiscriminatedUnion(union: z.ZodDiscriminatedUnion
30
38
  * and `optional` / `nullable` / `default` / intersection wrappers — so the
31
39
  * advertised JSON Schema carries no `oneOf` / `anyOf` at any depth. A union that
32
40
  * cannot be flattened (non-string discriminator, non-object variant) is left
33
- * untouched rather than crashing the mount (→ ADR 0033). Advertised-only; the
34
- * original schemas remain the validation schemas.
41
+ * untouched rather than crashing the mount (→ ADR 0033).
42
+ *
43
+ * Every object is rebuilt **with its key policy** (`rebuildObject`) — the walk
44
+ * changes union shape, never what an object does with an undeclared key. → ADR 0034.
35
45
  */
36
46
  export declare function flattenUnionsDeep(schema: z.core.$ZodType): z.ZodType;
37
47
  //# sourceMappingURL=flatten.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"flatten.d.ts","sourceRoot":"","sources":["../../src/tools/flatten.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,CAAC,CAAC,qBAAqB,GAC7B,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CA6C5B;AAmID;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,OAAO,CAkDpE"}
1
+ {"version":3,"file":"flatten.d.ts","sourceRoot":"","sources":["../../src/tools/flatten.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,CAAC,CAAC,qBAAqB,GAC7B,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAiD5B;AAsKD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,OAAO,CAqDpE"}
@@ -1 +1 @@
1
- {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/tools/mount.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,UAAU,EAChB,MAAM,WAAW,CAAC;AAKnB;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU,CACzB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAElE,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;IAClC,qEAAqE;IACrE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC3F,uEAAuE;IACvE,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC;CAC9D;AAED,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,MAAM,EAAE,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7C,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC;IAClB,mDAAmD;IACnD,YAAY,EAAE,OAAO,CAAC;CACvB;AAkBD,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,uFAAuF;IACvF,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,SAAS,EACpB,MAAM,GAAE,kBAAuB,GAC9B,aAAa,EAAE,CAkCjB;AAED,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,MAAM,EAAE,eAAe,CAAC;IACxB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,qCAAqC;IACrC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,yEAAyE;IACzE,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,gEAAgE;IAChE,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,+EAA+E;IAC/E,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,gBAAgB,GACvB,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,UAAU,CAAC,CA4BhF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAC,EAC1C,QAAQ,CAAC,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,WAAW,GACtB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAazB"}
1
+ {"version":3,"file":"mount.d.ts","sourceRoot":"","sources":["../../src/tools/mount.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EACL,KAAK,WAAW,EAEhB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,UAAU,EAChB,MAAM,WAAW,CAAC;AAKnB;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU,CACzB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAElE,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC;IAClC,qEAAqE;IACrE,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,QAAQ,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC3F,uEAAuE;IACvE,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,SAAS,KAAK,OAAO,CAAC;CAC9D;AAED,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,MAAM,EAAE,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;IAC7C,+EAA+E;IAC/E,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC;IAClB,mDAAmD;IACnD,YAAY,EAAE,OAAO,CAAC;CACvB;AAyBD,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,uFAAuF;IACvF,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,UAAU,EACnB,SAAS,EAAE,SAAS,EACpB,MAAM,GAAE,kBAAuB,GAC9B,aAAa,EAAE,CAmCjB;AAED,kDAAkD;AAClD,MAAM,WAAW,gBAAgB;IAC/B,iDAAiD;IACjD,MAAM,EAAE,eAAe,CAAC;IACxB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,gDAAgD;IAChD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,qCAAqC;IACrC,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,yEAAyE;IACzE,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,gEAAgE;IAChE,SAAS,CAAC,EAAE,WAAW,CAAC;IACxB,+EAA+E;IAC/E,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,gBAAgB,GACvB,CAAC,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,UAAU,CAAC,CA4BhF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAC,EAC1C,QAAQ,CAAC,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,WAAW,GACtB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAazB"}
@@ -1,15 +1,25 @@
1
1
  import type { RawRoute } from '../server/types';
2
+ /**
3
+ * OpenID Connect DCR `application_type` (SEP-837). A `native` client (desktop /
4
+ * CLI) may register an `http` loopback redirect; a `web` client may not — the
5
+ * mismatch is the usual cause of a `redirect_uri` rejection for CLI clients.
6
+ */
7
+ export type ApplicationType = 'native' | 'web';
2
8
  /** A client as registered via DCR. Public clients (PKCE) carry no secret. */
3
9
  export interface RegisteredClient {
4
10
  clientId: string;
5
11
  redirectUris: string[];
6
12
  clientName?: string;
13
+ /** The `application_type` the client declared, when it declared one. */
14
+ applicationType?: ApplicationType;
7
15
  }
8
16
  /** Metadata posted to `/register` (RFC 7591) before a client id is assigned. */
9
17
  export interface ClientMetadata {
10
18
  redirectUris: string[];
11
19
  clientName?: string;
12
20
  tokenEndpointAuthMethod?: string;
21
+ /** `native` (desktop / CLI, loopback allowed) or `web` (https only). */
22
+ applicationType?: ApplicationType;
13
23
  }
14
24
  /** State persisted between `/authorize` and `/token`, keyed by the auth code. */
15
25
  export interface AuthCodeData {
@@ -1 +1 @@
1
- {"version":3,"file":"oauth-provider.d.ts","sourceRoot":"","sources":["../../src/tools/oauth-provider.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAIhD,6EAA6E;AAC7E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC7B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,iFAAiF;AACjF,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,wFAAwF;IACxF,mBAAmB,EAAE,MAAM,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,2CAA2C;AAC3C,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,aAAa,EAAE,MAAM,CAAC;IACtB,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,wDAAwD;IACxD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,4CAA4C;IAC5C,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB,6BAA6B;IAC7B,OAAO,EAAE;QACP,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAC9D,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC;KACzD,CAAC;IACF,+EAA+E;IAC/E,KAAK,EAAE;QACL,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACtD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;KAClD,CAAC;IACF,uEAAuE;IACvE,aAAa,CAAC,EAAE;QACd,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QACtD,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;KAClD,CAAC;IAEF;;;;OAIG;IACH,aAAa,CACX,GAAG,EAAE,OAAO,EACZ,WAAW,EAAE,WAAW,GACvB,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,QAAQ,CAAC,CAAC;CAC3C;AAoED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,mBAAmB,GAAG,QAAQ,EAAE,CAkR1E"}
1
+ {"version":3,"file":"oauth-provider.d.ts","sourceRoot":"","sources":["../../src/tools/oauth-provider.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,39 @@
1
1
  import { z } from 'zod';
2
2
  /** Keys of a Zod object schema's shape — `[]` for a non-object or absent schema. */
3
3
  export declare function objectShapeKeys(schema?: z.ZodType): string[];
4
+ /**
5
+ * An object schema's own key policy — what it does with a key its shape does not
6
+ * declare. Zod stores it in `def.catchall`: `ZodNever` for `.strict()` (reject),
7
+ * `ZodUnknown` for `.loose()` / `.passthrough()` (keep), a concrete type for
8
+ * `.catchall(T)` (keep and validate), `undefined` for a plain object (strip).
9
+ */
10
+ export type KeyPolicy = z.core.$ZodType | undefined;
11
+ /** The key policy of an object schema. */
12
+ export declare function keyPolicyOf(schema: z.ZodObject): KeyPolicy;
13
+ /**
14
+ * Rebuild an object schema with a new shape, **carrying the source object's key
15
+ * policy over**.
16
+ *
17
+ * Load-bearing, not cosmetic: the advertised tool schema is not
18
+ * advertised-only. Both transport SDKs parse the caller's arguments *with it* and
19
+ * hand the handler the parsed result (MCP `validateToolInput` →
20
+ * `parseResult.data`; the AI SDK's `doParseToolCall` → `parseResult.value`), so
21
+ * an object rebuilt as a bare `z.object()` silently **deletes** every key the
22
+ * contract schema would have rejected (`.strict()`) or kept (`.loose()` /
23
+ * `.catchall()`) — the caller gets a success and never learns its argument was
24
+ * wrong. → ADR 0034.
25
+ *
26
+ * A plain source (`undefined` policy) needs no special case: the rebuilt object
27
+ * strips exactly as the contract object would have.
28
+ *
29
+ * A policy JSON Schema cannot represent (`.catchall(z.date())`) degrades to
30
+ * `z.unknown()`: copying it verbatim would fail the mount's JSON Schema probe and
31
+ * take every tool down with it, while dropping it would silently delete data.
32
+ * Loose keeps the invariant — nothing the contract would have kept is removed.
33
+ */
34
+ export declare function rebuildObject(source: z.ZodObject, shape: Record<string, z.core.$ZodType>): z.ZodObject;
35
+ /** Apply a key policy to a freshly built object, degrading what JSON Schema cannot carry. */
36
+ export declare function withKeyPolicy(object: z.ZodObject<z.ZodRawShape>, policy: KeyPolicy): z.ZodObject;
4
37
  /**
5
38
  * Merge an endpoint's `params` and `input` schemas into the single schema a
6
39
  * tool advertises.
@@ -10,12 +43,21 @@ export declare function objectShapeKeys(schema?: z.ZodType): string[];
10
43
  * - A non-object `input` (a union, a discriminated union, a refined / piped
11
44
  * schema) is kept intact and intersected with `params` when present — the
12
45
  * transport SDK converts it natively (a discriminated union becomes a clean
13
- * `oneOf`).
46
+ * `oneOf`). ⚠️ Zod drops both sides' key policy when it intersects objects, so
47
+ * this branch still strips (MCP rejects a non-object merged schema at mount;
48
+ * the agent surface does not). → ADR 0034, "Not covered".
14
49
  *
15
50
  * The merged schema is exactly the two source schemas side by side: nothing is
16
51
  * coerced, flattened or transformed, so the schema a tool advertises is the
17
52
  * schema its arguments are validated against (`executeToolMethod` parses each
18
53
  * source schema over its own slice of the args).
54
+ *
55
+ * The merged object takes the **input** schema's key policy, never the params
56
+ * one. `executeToolMethod` slices the flat args by the params shape's keys and
57
+ * routes *everything else* into the input slice, so a params catchall can never
58
+ * fire (its slice is built from its own shape) while an undeclared top-level key
59
+ * is judged by the input schema. A params-only tool returns the params object
60
+ * untouched, policy included.
19
61
  */
20
62
  export declare function mergeSchemas(paramsSchema?: z.ZodType<unknown>, inputSchema?: z.ZodType<unknown>): z.ZodType;
21
63
  //# sourceMappingURL=schema.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/tools/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,GAAG,MAAM,EAAE,CAE5D;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAC1B,YAAY,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EACjC,WAAW,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAC/B,CAAC,CAAC,OAAO,CA2BX"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/tools/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,OAAO,GAAG,MAAM,EAAE,CAE5D;AAED;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;AAEpD,0CAA0C;AAC1C,wBAAgB,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,SAAS,GAAG,SAAS,CAE1D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,CAAC,CAAC,SAAS,EACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,GACrC,CAAC,CAAC,SAAS,CAEb;AAED,6FAA6F;AAC7F,wBAAgB,aAAa,CAC3B,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,EAClC,MAAM,EAAE,SAAS,GAChB,CAAC,CAAC,SAAS,CAGb;AAcD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,YAAY,CAC1B,YAAY,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EACjC,WAAW,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAC/B,CAAC,CAAC,OAAO,CA8BX"}
package/dist/tools.d.ts CHANGED
@@ -17,7 +17,7 @@ export { type DownloadToolConfig, mountDownload } from './tools/mount-download';
17
17
  export { mountUpload, type UploadToolConfig } from './tools/mount-upload';
18
18
  export { mountWait, type WaitToolConfig } from './tools/mount-wait';
19
19
  export { oauthProtectedResourceRoute, PROTECTED_RESOURCE_PATH, type ProtectedResourceConfig, protectedResourceMetadataUrl, wwwAuthenticateHeader, } from './tools/oauth-metadata';
20
- export { type AuthCodeData, type AuthRequest, type ClientMetadata, mountOAuthProvider, type OAuthProviderConfig, type RefreshData, type RegisteredClient, } from './tools/oauth-provider';
20
+ export { type ApplicationType, type AuthCodeData, type AuthRequest, type ClientMetadata, mountOAuthProvider, type OAuthProviderConfig, type RefreshData, type RegisteredClient, } from './tools/oauth-provider';
21
21
  export { type ImplementRemoteOptions, implementRemote } from './tools/remote';
22
22
  export { createToolLogger, type ToolCallRecord, type ToolLoggerConfig, } from './tools/tool-logger';
23
23
  export { createToolkit, type Toolkit } from './tools/toolkit';
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EACL,cAAc,EACd,KAAK,wBAAwB,EAC7B,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,QAAQ,EACR,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,2BAA2B,EAC3B,kBAAkB,EAClB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,KAAK,kBAAkB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,KAAK,uBAAuB,EAC5B,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,sBAAsB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EACL,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,gBAAgB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EACL,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7F,OAAO,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EACL,cAAc,EACd,KAAK,wBAAwB,EAC7B,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,QAAQ,EACR,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,2BAA2B,EAC3B,kBAAkB,EAClB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,KAAK,kBAAkB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,KAAK,uBAAuB,EAC5B,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,sBAAsB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EACL,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,gBAAgB,GACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EACL,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,gBAAgB,GACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,KAAK,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/tools.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  pollUntil,
19
19
  readCapped,
20
20
  toolResultFromError
21
- } from "./index-x8m8e7dc.js";
21
+ } from "./index-h9d1fm0p.js";
22
22
  import {
23
23
  toJsonSchema
24
24
  } from "./index-0ed3bx43.js";
@@ -615,13 +615,15 @@ function json(body, status = 200, extraHeaders) {
615
615
  function oauthError(error, description, status = 400) {
616
616
  return json({ error, error_description: description }, status);
617
617
  }
618
- function isHttpUri(value) {
618
+ function isRegistrableRedirectUri(value, applicationType) {
619
619
  try {
620
620
  const url = new URL(value);
621
621
  if (url.protocol === "https:")
622
622
  return true;
623
623
  if (url.protocol !== "http:")
624
624
  return false;
625
+ if (applicationType === "web")
626
+ return false;
625
627
  const host = url.hostname.replace(/^\[|\]$/g, "");
626
628
  return host === "127.0.0.1" || host === "::1" || host === "localhost";
627
629
  } catch {
@@ -655,6 +657,7 @@ function mountOAuthProvider(config) {
655
657
  const registerPath = `${base}/register`;
656
658
  const authorizePath = `${base}/authorize`;
657
659
  const tokenPath = `${base}/token`;
660
+ const redirectToClient = (uri, params) => redirectWith(uri, { ...params, iss: config.issuer });
658
661
  const metadataRoute = {
659
662
  method: "ALL",
660
663
  path: AS_METADATA_PATH,
@@ -670,6 +673,7 @@ function mountOAuthProvider(config) {
670
673
  grant_types_supported: config.refreshTokens ? ["authorization_code", "refresh_token"] : ["authorization_code"],
671
674
  code_challenge_methods_supported: ["S256"],
672
675
  token_endpoint_auth_methods_supported: ["none"],
676
+ authorization_response_iss_parameter_supported: true,
673
677
  ...config.scopesSupported && { scopes_supported: config.scopesSupported }
674
678
  });
675
679
  }
@@ -686,14 +690,20 @@ function mountOAuthProvider(config) {
686
690
  if (!isRecord(meta)) {
687
691
  return oauthError("invalid_client_metadata", "Body must be a JSON object");
688
692
  }
693
+ const rawAppType = meta.application_type;
694
+ if (rawAppType !== undefined && rawAppType !== "native" && rawAppType !== "web") {
695
+ return oauthError("invalid_client_metadata", "application_type must be 'native' or 'web'");
696
+ }
697
+ const applicationType = rawAppType;
689
698
  const redirectUris = meta.redirect_uris;
690
- if (!Array.isArray(redirectUris) || redirectUris.length === 0 || !redirectUris.every((u) => typeof u === "string" && isHttpUri(u))) {
691
- return oauthError("invalid_redirect_uri", "redirect_uris must be a non-empty array of absolute https URLs (http is allowed only on a loopback host)");
699
+ if (!Array.isArray(redirectUris) || redirectUris.length === 0 || !redirectUris.every((u) => typeof u === "string" && isRegistrableRedirectUri(u, applicationType))) {
700
+ return oauthError("invalid_redirect_uri", applicationType === "web" ? "redirect_uris must be a non-empty array of absolute https URLs (a web client cannot register an http loopback URI)" : "redirect_uris must be a non-empty array of absolute https URLs (http is allowed only on a loopback host)");
692
701
  }
693
702
  const client = await config.clients.register({
694
703
  redirectUris,
695
704
  clientName: typeof meta.client_name === "string" ? meta.client_name : undefined,
696
- tokenEndpointAuthMethod: typeof meta.token_endpoint_auth_method === "string" ? meta.token_endpoint_auth_method : undefined
705
+ tokenEndpointAuthMethod: typeof meta.token_endpoint_auth_method === "string" ? meta.token_endpoint_auth_method : undefined,
706
+ ...applicationType && { applicationType }
697
707
  });
698
708
  return json({
699
709
  client_id: client.clientId,
@@ -701,7 +711,8 @@ function mountOAuthProvider(config) {
701
711
  token_endpoint_auth_method: "none",
702
712
  grant_types: config.refreshTokens ? ["authorization_code", "refresh_token"] : ["authorization_code"],
703
713
  response_types: ["code"],
704
- ...client.clientName && { client_name: client.clientName }
714
+ ...client.clientName && { client_name: client.clientName },
715
+ ...client.applicationType && { application_type: client.applicationType }
705
716
  }, 201);
706
717
  }
707
718
  };
@@ -731,27 +742,27 @@ function mountOAuthProvider(config) {
731
742
  return oauthError("invalid_request", "redirect_uri does not match a registered URI");
732
743
  }
733
744
  if (responseType !== "code") {
734
- return redirectWith(redirectUri, {
745
+ return redirectToClient(redirectUri, {
735
746
  error: "unsupported_response_type",
736
747
  ...state && { state }
737
748
  });
738
749
  }
739
750
  if (!codeChallenge || codeChallengeMethod !== "S256") {
740
- return redirectWith(redirectUri, {
751
+ return redirectToClient(redirectUri, {
741
752
  error: "invalid_request",
742
753
  error_description: "PKCE S256 code_challenge is required",
743
754
  ...state && { state }
744
755
  });
745
756
  }
746
757
  if (!resource) {
747
- return redirectWith(redirectUri, {
758
+ return redirectToClient(redirectUri, {
748
759
  error: "invalid_target",
749
760
  error_description: "resource parameter is required",
750
761
  ...state && { state }
751
762
  });
752
763
  }
753
764
  if (resource !== config.resource) {
754
- return redirectWith(redirectUri, {
765
+ return redirectToClient(redirectUri, {
755
766
  error: "invalid_target",
756
767
  error_description: "resource is not served by this authorization server",
757
768
  ...state && { state }
@@ -772,7 +783,7 @@ function mountOAuthProvider(config) {
772
783
  userId: result.userId,
773
784
  expiresAt: Date.now() + AUTH_CODE_TTL_MS
774
785
  });
775
- return redirectWith(redirectUri, { code, ...state && { state } });
786
+ return redirectToClient(redirectUri, { code, ...state && { state } });
776
787
  }
777
788
  };
778
789
  const issueAccessToken = (userId, audience, clientId, scope) => signJwt({ scope, client_id: clientId }, config.signingSecret, {
package/llms-full.txt CHANGED
@@ -1303,11 +1303,23 @@ each variant's fields become optional with a `Required if <disc> = …` hint.
1303
1303
  It is **deep**: unions are flattened at every depth — top level, object fields,
1304
1304
  array items, and through `optional` / `nullable` / `default` / intersection
1305
1305
  wrappers — so no `oneOf` survives anywhere (e.g. a `content.parts[]` that is an
1306
- array of a discriminated union). It is **advertised-only and lossy**: the original
1307
- schemas stay the validation schemas in `executeToolMethod`, so requests are still
1308
- validated against the real union. Schemas a transform cannot safely rebuild
1306
+ array of a discriminated union). Schemas a transform cannot safely rebuild
1309
1307
  (refined / piped / lazy / plain non-discriminated unions) are left as-is.
1310
1308
 
1309
+ The flattened form is **lossy but never destructive**. Lossy: per-variant
1310
+ strictness and object-level refinements are not advertised — the original schemas
1311
+ enforce them in `executeToolMethod`. Not destructive: every object keeps its own
1312
+ **key policy** (`.strict()` stays strict, `.loose()` / `.catchall()` still keep
1313
+ extra keys), because the advertised schema is not advertised-only — the MCP and
1314
+ AI SDKs parse the caller's arguments *with it* and hand the handler the parsed
1315
+ result. An object advertised without its policy would silently delete keys the
1316
+ contract would have rejected. → [ADR 0034](../decisions/0034-advertised-schema-key-policy.md).
1317
+
1318
+ A consequence worth knowing when you read logs: a `.strict()` violation is caught
1319
+ by the SDK **before** the tool callback runs, so it comes back as an MCP
1320
+ `InvalidParams` protocol error rather than a stitchkit `VALIDATION_ERROR`
1321
+ envelope, and `beforeToolCall` / `afterToolCall` do not fire for it.
1322
+
1311
1323
  ## `mountMcp`
1312
1324
 
1313
1325
  If you already run an `McpServer` from the SDK, `mountMcp` adds contract tools
@@ -1393,14 +1405,36 @@ createServer({
1393
1405
  })
1394
1406
  ```
1395
1407
 
1396
- Access tokens are signed HS256 JWTs (`signJwt`) whose `aud` is the resource —
1397
- validate them in `auth` with `verifyJwt(token, secret, { audience: resource })`.
1398
- `authorizeUser` is where the app authenticates the user (reuse an existing
1399
- session) and records consent; return `{ userId }` to issue a code, or a
1400
- `Response` to redirect the browser to a login page first. The AS and resource
1401
- server can co-locate or live on separate origins. See
1408
+ Access tokens are signed HS256 JWTs (`signJwt`) whose `aud` is the resource and
1409
+ whose `iss` is the issuer — validate both in `auth` with
1410
+ `verifyJwt(token, secret, { audience: resource, issuer })`. `authorizeUser` is
1411
+ where the app authenticates the user (reuse an existing session) and records
1412
+ consent; return `{ userId }` to issue a code, or a `Response` to redirect the
1413
+ browser to a login page first. The AS and resource server can co-locate or live
1414
+ on separate origins. See
1402
1415
  [ADR 0015](../decisions/0015-oauth-resource-server.md).
1403
1416
 
1417
+ ### Authorization hardening (MCP 2026-07-28)
1418
+
1419
+ - **`iss` on every authorization response (RFC 9207, SEP-2468).** Success *and*
1420
+ error redirects carry `iss`, and the AS metadata advertises
1421
+ `authorization_response_iss_parameter_supported: true`. A client that talks to
1422
+ several authorization servers validates `iss` before redeeming the code, which
1423
+ closes the **mix-up attack** — an attacker's server cannot pass its response
1424
+ off as this issuer's. Additive on the wire: a client that ignores `iss` is
1425
+ unaffected.
1426
+ - **`application_type` on registration (SEP-837).** A client may declare
1427
+ `"native"` (desktop / CLI) or `"web"` in its DCR body. A **native** client may
1428
+ register an `http` loopback redirect (`http://127.0.0.1:…`, RFC 8252 §7.3); a
1429
+ **web** client is held to `https` only — that mismatch is the usual cause of
1430
+ the `redirect_uri` rejection CLI clients hit. Omit the field and registration
1431
+ behaves exactly as before (loopback allowed); an unknown value is rejected
1432
+ rather than silently defaulted.
1433
+
1434
+ > Dynamic Client Registration is **deprecated** in the 2026-07-28 spec in favour
1435
+ > of Client ID Metadata Documents (CIMD), with a ≥12-month window. DCR keeps
1436
+ > working and stays supported here; CIMD support is tracked separately.
1437
+
1404
1438
  ## Proxying a remote API — `implementRemote`
1405
1439
 
1406
1440
  `implement` binds a contract to local handlers. `implementRemote` binds it to a
@@ -3470,6 +3504,7 @@ A native remote-connector auth surface for MCP — [guide](../guide/mcp-and-agen
3470
3504
  | `PROTECTED_RESOURCE_PATH` | const | the well-known metadata path |
3471
3505
  | `OAuthProviderConfig` | _type_ | config for `mountOAuthProvider` |
3472
3506
  | `ProtectedResourceConfig` | _type_ | config for `oauthProtectedResourceRoute` |
3507
+ | `ApplicationType` | _type_ | DCR `application_type` — `'native'` (loopback allowed) \| `'web'` (https only) |
3473
3508
  | `AuthCodeData` | _type_ | a stored authorization-code record |
3474
3509
  | `AuthRequest` | _type_ | a parsed authorization request |
3475
3510
  | `ClientMetadata` | _type_ | dynamic-client-registration metadata |
@@ -3506,7 +3541,7 @@ Advanced building blocks — the shared machinery the mounts are built on.
3506
3541
  | `TransportCounts` | _type_ | per-transport counts (`{ HTTP, MCP, AGENT, CLI }`) |
3507
3542
  | `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments |
3508
3543
  | `flattenDiscriminatedUnion` | function | flatten one discriminated union into a single object schema |
3509
- | `flattenUnionsDeep` | function | flatten discriminated unions at every depth (advertised schema only) |
3544
+ | `flattenUnionsDeep` | function | flatten discriminated unions at every depth — union shape only; each object keeps its own key policy (`.strict()` / `.loose()` / `.catchall()`) |
3510
3545
  | `MountableTool` | _type_ | one contract method resolved for mounting |
3511
3546
  | `ToolManifestEntry` | _type_ | one `buildToolManifest` row |
3512
3547
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",