stitchkit 0.34.0 → 0.36.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,10 +4,10 @@ import {
4
4
  emitResult,
5
5
  parseCliArgs,
6
6
  pollUntilDone
7
- } from "./index-jr8vf7g8.js";
7
+ } from "./index-kgga1n8d.js";
8
8
  import"./index-0ed3bx43.js";
9
9
  import"./index-x3fcszf8.js";
10
- import"./index-enc7t99e.js";
10
+ import"./index-17rdjw68.js";
11
11
  export {
12
12
  pollUntilDone,
13
13
  parseCliArgs,
@@ -192,4 +192,149 @@ function safeJsonParse(text) {
192
192
  return JSON.parse(text, (key, value) => isUnsafeKey(key) ? undefined : value);
193
193
  }
194
194
 
195
- export { __require, mergeMeta, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, typedEntries, isRecord, bytesToBase64Url, base64UrlToBytes, formatZodError, zodIssues, errorCode, recordedErrorMessage, normalizeError, validateHandlerOutput, isUnsafeKey, safeJsonParse };
195
+ // src/server/request.ts
196
+ function generateTraceId() {
197
+ return `${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 8)}`;
198
+ }
199
+ function resolveTraceId(req) {
200
+ const header = req.headers.get("x-request-id") ?? req.headers.get("x-trace-id");
201
+ const trimmed = header?.trim();
202
+ if (trimmed && trimmed.length <= 128 && /^[\w.-]+$/.test(trimmed)) {
203
+ return trimmed;
204
+ }
205
+ return generateTraceId();
206
+ }
207
+ function resolveSocketIp(req, server) {
208
+ if (typeof server === "object" && server !== null && "requestIP" in server && typeof server.requestIP === "function") {
209
+ const addr = server.requestIP(req);
210
+ if (isRecord(addr) && typeof addr.address === "string" && addr.address) {
211
+ return addr.address;
212
+ }
213
+ }
214
+ if ("ip" in req && typeof req.ip === "string" && req.ip)
215
+ return req.ip;
216
+ return;
217
+ }
218
+ function extractIp(req, options = {}) {
219
+ if (options.trustProxy) {
220
+ const forwarded = req.headers.get("x-forwarded-for");
221
+ if (forwarded)
222
+ return (forwarded.split(",")[0] ?? "").trim().replace(/^::ffff:/, "");
223
+ const realIp = req.headers.get("x-real-ip");
224
+ if (realIp)
225
+ return realIp.trim().replace(/^::ffff:/, "");
226
+ }
227
+ return (options.socketIp ?? "").replace(/^::ffff:/, "");
228
+ }
229
+ function getClientInfo(req, options = {}) {
230
+ return {
231
+ ipAddress: extractIp(req, options) || undefined,
232
+ userAgent: req.headers.get("user-agent") ?? undefined
233
+ };
234
+ }
235
+ function parseQueryParams(url) {
236
+ const query = {};
237
+ for (const key of new Set(url.searchParams.keys())) {
238
+ if (isUnsafeKey(key))
239
+ continue;
240
+ const values = url.searchParams.getAll(key);
241
+ const [first] = values;
242
+ query[key] = values.length === 1 && first !== undefined ? first : values;
243
+ }
244
+ return query;
245
+ }
246
+
247
+ // src/observability/trace.ts
248
+ var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
249
+ function randomHex(bytes) {
250
+ const arr = new Uint8Array(bytes);
251
+ crypto.getRandomValues(arr);
252
+ let hex = "";
253
+ for (const byte of arr)
254
+ hex += byte.toString(16).padStart(2, "0");
255
+ return hex;
256
+ }
257
+ function createTraceContext() {
258
+ return { traceId: randomHex(16), spanId: randomHex(8) };
259
+ }
260
+ function parseTraceparent(header) {
261
+ if (!header)
262
+ return null;
263
+ const match = TRACEPARENT_RE.exec(header.trim());
264
+ if (!match?.[1] || !match[2])
265
+ return null;
266
+ const traceId = match[1].toLowerCase();
267
+ const parentSpanId = match[2].toLowerCase();
268
+ if (/^0+$/.test(traceId) || /^0+$/.test(parentSpanId))
269
+ return null;
270
+ return { traceId, spanId: randomHex(8), parentSpanId };
271
+ }
272
+ function formatTraceparent(ctx) {
273
+ return `00-${ctx.traceId}-${ctx.spanId}-01`;
274
+ }
275
+ function resolveTraceContext(req) {
276
+ return parseTraceparent(req.headers.get("traceparent")) ?? createTraceContext();
277
+ }
278
+ function childSpan(parent) {
279
+ return {
280
+ traceId: parent.traceId,
281
+ spanId: randomHex(8),
282
+ parentSpanId: parent.spanId
283
+ };
284
+ }
285
+
286
+ // src/observability/context.ts
287
+ import { AsyncLocalStorage } from "node:async_hooks";
288
+ var storage = new AsyncLocalStorage;
289
+ function runWithRequestContext(ctx, fn) {
290
+ return storage.run(ctx, fn);
291
+ }
292
+ function getRequestContext() {
293
+ return storage.getStore();
294
+ }
295
+ function getTraceId() {
296
+ return storage.getStore()?.trace.traceId;
297
+ }
298
+ function getUserId() {
299
+ return storage.getStore()?.userId;
300
+ }
301
+ function setRequestUser(userId) {
302
+ const ctx = storage.getStore();
303
+ if (ctx)
304
+ ctx.userId = userId;
305
+ }
306
+ function setRequestEndpoint(serviceName, action) {
307
+ const ctx = storage.getStore();
308
+ if (ctx) {
309
+ ctx.serviceName = serviceName;
310
+ ctx.action = action;
311
+ }
312
+ }
313
+ function setRequestDimensions(dimensions) {
314
+ const ctx = storage.getStore();
315
+ if (ctx)
316
+ ctx.dimensions = { ...ctx.dimensions, ...dimensions };
317
+ }
318
+ function setRequestError(error) {
319
+ const ctx = storage.getStore();
320
+ if (ctx)
321
+ ctx.error = error;
322
+ }
323
+ function wrapInRequestContext(handler, options = {}) {
324
+ return (req, server) => {
325
+ const ctx = {
326
+ trace: resolveTraceContext(req),
327
+ source: "http",
328
+ method: req.method,
329
+ path: new URL(req.url, "http://localhost").pathname,
330
+ startedAt: process.hrtime.bigint(),
331
+ ...getClientInfo(req, {
332
+ trustProxy: options.trustProxy,
333
+ socketIp: resolveSocketIp(req, server)
334
+ })
335
+ };
336
+ return runWithRequestContext(ctx, () => handler(req, server));
337
+ };
338
+ }
339
+
340
+ export { __require, mergeMeta, AppError, notFound, badRequest, unauthorized, forbidden, conflict, rateLimited, STITCH_ERROR_STATUS, isStitchErrorCode, appError, typedEntries, isRecord, bytesToBase64Url, base64UrlToBytes, formatZodError, zodIssues, errorCode, recordedErrorMessage, normalizeError, validateHandlerOutput, isUnsafeKey, safeJsonParse, generateTraceId, resolveTraceId, resolveSocketIp, extractIp, getClientInfo, parseQueryParams, createTraceContext, parseTraceparent, formatTraceparent, resolveTraceContext, childSpan, runWithRequestContext, getRequestContext, getTraceId, getUserId, setRequestUser, setRequestEndpoint, setRequestDimensions, setRequestError, wrapInRequestContext };
@@ -6,7 +6,7 @@ import {
6
6
  isUnsafeKey,
7
7
  safeJsonParse,
8
8
  unauthorized
9
- } from "./index-enc7t99e.js";
9
+ } from "./index-17rdjw68.js";
10
10
 
11
11
  // src/server/middleware/cookies.ts
12
12
  function parseCookies(header) {
@@ -6,30 +6,28 @@ import {
6
6
  import {
7
7
  isWithinDir
8
8
  } from "./index-x3fcszf8.js";
9
- import {
10
- extractIp,
11
- getClientInfo,
12
- getRequestContext,
13
- parseQueryParams,
14
- resolveSocketIp,
15
- resolveTraceId,
16
- setRequestEndpoint,
17
- setRequestError
18
- } from "./index-wya8bkme.js";
19
9
  import {
20
10
  AppError,
21
11
  __require,
22
12
  badRequest,
23
13
  errorCode,
14
+ extractIp,
15
+ getClientInfo,
16
+ getRequestContext,
24
17
  isRecord,
25
18
  isUnsafeKey,
26
19
  mergeMeta,
27
20
  normalizeError,
21
+ parseQueryParams,
28
22
  recordedErrorMessage,
23
+ resolveSocketIp,
24
+ resolveTraceId,
29
25
  safeJsonParse,
26
+ setRequestEndpoint,
27
+ setRequestError,
30
28
  typedEntries,
31
29
  validateHandlerOutput
32
- } from "./index-enc7t99e.js";
30
+ } from "./index-17rdjw68.js";
33
31
 
34
32
  // src/server/multipart.ts
35
33
  var DEFAULT_MAX_UPLOAD_BYTES = 25 * 1024 * 1024;
@@ -7,12 +7,14 @@ import {
7
7
  } from "./index-x3fcszf8.js";
8
8
  import {
9
9
  formatZodError,
10
+ getRequestContext,
10
11
  isRecord,
11
12
  isUnsafeKey,
12
13
  normalizeError,
14
+ runWithRequestContext,
13
15
  safeJsonParse,
14
16
  validateHandlerOutput
15
- } from "./index-enc7t99e.js";
17
+ } from "./index-17rdjw68.js";
16
18
 
17
19
  // src/tools/coerce.ts
18
20
  import { z } from "zod";
@@ -229,30 +231,97 @@ function stripAnnotations(node) {
229
231
  stripAnnotations(value);
230
232
  }
231
233
  }
232
- function hasChecks(schema) {
234
+ function hasInvisibleConstraint(schema) {
233
235
  if (!(schema instanceof z3.ZodType))
234
236
  return false;
235
237
  const def = schema.def;
236
- if (isRecord(def) && Array.isArray(def.checks) && def.checks.length > 0)
238
+ if (isRecord(def) && def.type !== "unknown" && def.type !== "any") {
239
+ if (normalizedJson(schema) === "{}")
240
+ return true;
241
+ }
242
+ if (schema instanceof z3.ZodCatch)
243
+ return true;
244
+ if (isRecord(def) && def.coerce === true)
237
245
  return true;
246
+ if (isRecord(def) && Array.isArray(def.checks)) {
247
+ for (const check of def.checks) {
248
+ if (isInvisibleCheck(check))
249
+ return true;
250
+ }
251
+ }
238
252
  if (schema instanceof z3.ZodOptional || schema instanceof z3.ZodNullable || schema instanceof z3.ZodDefault) {
239
- return hasChecks(schema.unwrap());
253
+ return hasInvisibleConstraint(schema.unwrap());
240
254
  }
241
255
  if (schema instanceof z3.ZodPipe)
242
- return hasChecks(schema.def.in) || hasChecks(schema.def.out);
243
- if (schema instanceof z3.ZodObject)
244
- return Object.values(schema.shape).some(hasChecks);
256
+ return true;
257
+ if (schema instanceof z3.ZodObject) {
258
+ return Object.values(schema.shape).some(hasInvisibleConstraint);
259
+ }
245
260
  if (schema instanceof z3.ZodArray)
246
- return hasChecks(schema.element);
261
+ return hasInvisibleConstraint(schema.element);
247
262
  if (schema instanceof z3.ZodUnion)
248
- return schema.def.options.some(hasChecks);
263
+ return schema.def.options.some(hasInvisibleConstraint);
249
264
  if (schema instanceof z3.ZodRecord)
250
- return hasChecks(schema.valueType);
265
+ return hasInvisibleConstraint(schema.valueType);
251
266
  if (schema instanceof z3.ZodIntersection) {
252
- return hasChecks(schema.def.left) || hasChecks(schema.def.right);
267
+ return hasInvisibleConstraint(schema.def.left) || hasInvisibleConstraint(schema.def.right);
253
268
  }
254
269
  return false;
255
270
  }
271
+ var INVISIBLE_CHECK_KINDS = new Set(["custom", "overwrite"]);
272
+ function acceptsMoreThanItsType(schema) {
273
+ if (!(schema instanceof z3.ZodType))
274
+ return false;
275
+ if (schema instanceof z3.ZodCatch)
276
+ return true;
277
+ if (isRecord(schema.def) && schema.def.coerce === true)
278
+ return true;
279
+ if (schema instanceof z3.ZodOptional || schema instanceof z3.ZodNullable || schema instanceof z3.ZodDefault) {
280
+ return acceptsMoreThanItsType(schema.unwrap());
281
+ }
282
+ if (schema instanceof z3.ZodPipe)
283
+ return acceptsMoreThanItsType(schema.def.in);
284
+ return false;
285
+ }
286
+ function isInvisibleCheck(check) {
287
+ if (!isRecord(check))
288
+ return false;
289
+ const inner = check._zod;
290
+ const def = isRecord(inner) ? inner.def : undefined;
291
+ const kind = isRecord(def) ? def.check : undefined;
292
+ return typeof kind === "string" && INVISIBLE_CHECK_KINDS.has(kind);
293
+ }
294
+ function projectToBaseType(schema) {
295
+ if (!(schema instanceof z3.ZodType))
296
+ return z3.unknown();
297
+ if (schema instanceof z3.ZodNullable) {
298
+ return z3.nullable(projectToBaseType(schema.unwrap()));
299
+ }
300
+ if (schema instanceof z3.ZodOptional || schema instanceof z3.ZodDefault) {
301
+ return projectToBaseType(schema.unwrap());
302
+ }
303
+ if (schema instanceof z3.ZodPipe)
304
+ return projectToBaseType(schema.def.in);
305
+ if (schema instanceof z3.ZodCatch)
306
+ return z3.unknown();
307
+ const coerces = isRecord(schema.def) && schema.def.coerce === true;
308
+ if (schema instanceof z3.ZodNumber)
309
+ return coerces ? z3.coerce.number() : z3.number();
310
+ if (schema instanceof z3.ZodString)
311
+ return coerces ? z3.coerce.string() : z3.string();
312
+ if (schema instanceof z3.ZodBoolean && coerces)
313
+ return z3.coerce.boolean();
314
+ if (schema instanceof z3.ZodEnum || schema instanceof z3.ZodLiteral) {
315
+ return stringLiteralOrEnumValues(schema) === null ? z3.unknown() : z3.string();
316
+ }
317
+ if (schema instanceof z3.ZodBoolean)
318
+ return z3.boolean();
319
+ if (schema instanceof z3.ZodArray)
320
+ return z3.array(z3.unknown());
321
+ if (schema instanceof z3.ZodObject)
322
+ return z3.looseObject({});
323
+ return z3.unknown();
324
+ }
256
325
  function normalizedJson(schema) {
257
326
  if (!(schema instanceof z3.ZodType))
258
327
  return "{}";
@@ -272,7 +341,17 @@ function mergeCollidingFields(schemas) {
272
341
  if (values.length <= 1) {
273
342
  if (only === undefined)
274
343
  return z3.unknown();
275
- return schemas.length > 1 && hasChecks(only) ? z3.unknown() : only;
344
+ if (schemas.length <= 1)
345
+ return only;
346
+ const hazards2 = schemas.map(acceptsMoreThanItsType);
347
+ if (hazards2.some(Boolean) && !hazards2.every(Boolean))
348
+ return z3.unknown();
349
+ if (schemas.some(hasInvisibleConstraint)) {
350
+ if (normalizedJson(only) === "{}")
351
+ return only;
352
+ return projectToBaseType(only);
353
+ }
354
+ return only;
276
355
  }
277
356
  const merged = [];
278
357
  let allEnum = true;
@@ -286,9 +365,19 @@ function mergeCollidingFields(schemas) {
286
365
  }
287
366
  if (allEnum) {
288
367
  const uniq = [...new Set(merged)];
289
- const [first, ...rest] = uniq;
290
- if (first !== undefined)
291
- return z3.enum([first, ...rest]);
368
+ const [first2, ...rest] = uniq;
369
+ if (first2 !== undefined)
370
+ return z3.enum([first2, ...rest]);
371
+ }
372
+ const hazards = schemas.map(acceptsMoreThanItsType);
373
+ if (hazards.some(Boolean) && !hazards.every(Boolean))
374
+ return z3.unknown();
375
+ const projected = values.map(projectToBaseType);
376
+ const first = projected[0];
377
+ if (first !== undefined) {
378
+ const shape = normalizedJson(first);
379
+ if (shape !== "{}" && projected.every((p) => normalizedJson(p) === shape))
380
+ return first;
292
381
  }
293
382
  return z3.unknown();
294
383
  }
@@ -347,6 +436,23 @@ function toolResultFromError(err) {
347
436
  };
348
437
  }
349
438
  async function executeToolMethod(method, toolName, rawArgs, context, hooks, lifecycle, coerceJson = false, onOutputStrip) {
439
+ const parent = getRequestContext();
440
+ if (!parent) {
441
+ return runToolMethod(method, toolName, rawArgs, context, hooks, lifecycle, coerceJson, onOutputStrip);
442
+ }
443
+ return runWithRequestContext({
444
+ ...parent,
445
+ trace: parent.trace,
446
+ dimensions: parent.dimensions ? { ...parent.dimensions } : undefined,
447
+ error: undefined,
448
+ source: context.source,
449
+ method: "TOOL",
450
+ path: `/${context.source}/${toolName}`,
451
+ serviceName: method.serviceName,
452
+ action: method.key
453
+ }, () => runToolMethod(method, toolName, rawArgs, context, hooks, lifecycle, coerceJson, onOutputStrip));
454
+ }
455
+ async function runToolMethod(method, toolName, rawArgs, context, hooks, lifecycle, coerceJson, onOutputStrip) {
350
456
  const startedAt = Date.now();
351
457
  const finish = async (result, thrown) => {
352
458
  await hooks?.afterToolCall?.(toolName, rawArgs, result, Date.now() - startedAt, context, method, thrown);
package/dist/node.js CHANGED
@@ -3,10 +3,9 @@ import {
3
3
  createImplement,
4
4
  createSocketIOServer,
5
5
  implement
6
- } from "./index-k7ysesv0.js";
6
+ } from "./index-dvrn81q4.js";
7
7
  import"./index-czmqks7r.js";
8
8
  import"./index-x3fcszf8.js";
9
- import"./index-wya8bkme.js";
10
9
  import {
11
10
  AppError,
12
11
  appError,
@@ -16,7 +15,7 @@ import {
16
15
  notFound,
17
16
  rateLimited,
18
17
  unauthorized
19
- } from "./index-enc7t99e.js";
18
+ } from "./index-17rdjw68.js";
20
19
  // src/server/node.ts
21
20
  import { serve } from "srvx";
22
21
  async function serveNode(config) {
@@ -5,7 +5,10 @@ import {
5
5
  getRequestContext,
6
6
  getTraceId,
7
7
  getUserId,
8
+ isRecord,
9
+ isUnsafeKey,
8
10
  parseTraceparent,
11
+ recordedErrorMessage,
9
12
  resolveTraceContext,
10
13
  runWithRequestContext,
11
14
  setRequestDimensions,
@@ -13,12 +16,7 @@ import {
13
16
  setRequestError,
14
17
  setRequestUser,
15
18
  wrapInRequestContext
16
- } from "../index-wya8bkme.js";
17
- import {
18
- isRecord,
19
- isUnsafeKey,
20
- recordedErrorMessage
21
- } from "../index-enc7t99e.js";
19
+ } from "../index-17rdjw68.js";
22
20
 
23
21
  // src/observability/sanitize.ts
24
22
  var DEFAULT_SENSITIVE_KEYS = /(password|passwd|pwd|secret|token|apikey|api[-_ ]?key|auth|authorization|bearer|session|cookie|init[-_ ]?data|credential|private[-_ ]?key)/i;
@@ -10,7 +10,7 @@ import {
10
10
  socketIoLane,
11
11
  staticRoute,
12
12
  webSocketLane
13
- } from "../index-k7ysesv0.js";
13
+ } from "../index-dvrn81q4.js";
14
14
  import {
15
15
  createAuthHook,
16
16
  createBearerResolver,
@@ -23,7 +23,7 @@ import {
23
23
  signJwt,
24
24
  verifyJwt,
25
25
  verifyPkce
26
- } from "../index-nmq59nae.js";
26
+ } from "../index-bx49hskg.js";
27
27
  import {
28
28
  DEFAULT_CORS_ALLOW_HEADERS,
29
29
  DEFAULT_CORS_EXPOSE_HEADERS,
@@ -37,14 +37,6 @@ import {
37
37
  import {
38
38
  isWithinDir
39
39
  } from "../index-x3fcszf8.js";
40
- import {
41
- extractIp,
42
- generateTraceId,
43
- getClientInfo,
44
- getTraceId,
45
- resolveSocketIp,
46
- resolveTraceId
47
- } from "../index-wya8bkme.js";
48
40
  import {
49
41
  AppError,
50
42
  STITCH_ERROR_STATUS,
@@ -52,16 +44,22 @@ import {
52
44
  badRequest,
53
45
  conflict,
54
46
  errorCode,
47
+ extractIp,
55
48
  forbidden,
56
49
  formatZodError,
50
+ generateTraceId,
51
+ getClientInfo,
52
+ getTraceId,
57
53
  isRecord,
58
54
  isStitchErrorCode,
59
55
  normalizeError,
60
56
  notFound,
61
57
  rateLimited,
58
+ resolveSocketIp,
59
+ resolveTraceId,
62
60
  unauthorized,
63
61
  zodIssues
64
- } from "../index-enc7t99e.js";
62
+ } from "../index-17rdjw68.js";
65
63
  // src/server/swept-map.ts
66
64
  function createSweptMap(options) {
67
65
  const store = new Map;
@@ -1 +1 @@
1
- {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/tools/execute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,MAAM,UAAU,GAClB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC3B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,eAAe,CAAC;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,cAAc,CAAC,EAAE,CACf,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,SAAS,KAChB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,CACd,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,SAAS,EACnB,KAAK,CAAC,EAAE,OAAO,KACZ,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,WAAW,CAAC,EAAE,CACZ,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,SAAS,KAChB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAEjF;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClF,0DAA0D;IAC1D,WAAW,CAAC,EAAE,CACZ,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,SAAS,KAChB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,EAAE;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAC,CAQpF;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAC5C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,EAAE,eAAe,EACxB,KAAK,CAAC,EAAE,aAAa,EACrB,SAAS,CAAC,EAAE,aAAa,EACzB,UAAU,UAAQ,EAClB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,GACxC,OAAO,CAAC,UAAU,CAAC,CAwIrB"}
1
+ {"version":3,"file":"execute.d.ts","sourceRoot":"","sources":["../../src/tools/execute.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAInE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAIjD,MAAM,MAAM,UAAU,GAClB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC3B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,eAAe,CAAC;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,cAAc,CAAC,EAAE,CACf,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,SAAS,KAChB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,CACd,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,SAAS,EACnB,KAAK,CAAC,EAAE,OAAO,KACZ,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,WAAW,CAAC,EAAE,CACZ,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,SAAS,KAChB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;AAEjF;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,aAAa;IAC5B,oDAAoD;IACpD,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClF,0DAA0D;IAC1D,WAAW,CAAC,EAAE,CACZ,GAAG,EAAE,cAAc,EACnB,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,SAAS,KAChB,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,UAAU,EAAE;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAC,CAQpF;AAED,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAC5C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,OAAO,EAAE,eAAe,EACxB,KAAK,CAAC,EAAE,aAAa,EACrB,SAAS,CAAC,EAAE,aAAa,EACzB,UAAU,UAAQ,EAClB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,IAAI,GACxC,OAAO,CAAC,UAAU,CAAC,CAuDrB"}
@@ -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;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
+ {"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;AA6TD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,GAAG,CAAC,CAAC,OAAO,CAqDpE"}
@@ -48,6 +48,15 @@ export interface McpMountConfig {
48
48
  export declare function validateMcpSchemas(services: ServiceDef[], onIncompatibleSchema?: IncompatibleSchemaPolicy, logger?: StitchLogger, options?: {
49
49
  extend?: ToolExtend;
50
50
  flattenUnionInput?: boolean;
51
+ /**
52
+ * Also fail a tool whose advertised schema has a property with no type
53
+ * information — nothing for a model to obey. Off by default because a
54
+ * contract may legitimately declare `z.unknown()`; `allowUntyped` lists the
55
+ * dotted paths that are deliberate, and anything else is a finding. → ADR 0044.
56
+ */
57
+ requireTypedProperties?: boolean;
58
+ /** Dotted paths (`tool.property`) that are deliberately unconstrained. */
59
+ allowUntyped?: readonly string[];
51
60
  }): void;
52
61
  export declare function mountMcp(mcpServer: McpServer, services: ServiceDef | ServiceDef[], config?: McpMountConfig): void;
53
62
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/tools/mcp.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/tools/mcp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ /** One property the model would be shown with no idea what it is. */
2
+ export interface UntypedProperty {
3
+ /** Dotted path from the tool's argument root, e.g. `operations.partIndex`. */
4
+ path: string;
5
+ /** Its `description`, when it has one — usually the only clue present. */
6
+ description?: string;
7
+ }
8
+ /**
9
+ * Every property in a JSON Schema that carries no type information, deep.
10
+ *
11
+ * Walks `properties`, `items`, `additionalProperties`, `$defs` / `definitions`
12
+ * and the `allOf` / `anyOf` / `oneOf` branches — an intersection root emits
13
+ * `allOf` with no root `properties` at all, so reading the top level alone would
14
+ * inspect nothing and pass.
15
+ */
16
+ export declare function findUntypedProperties(schema: unknown, prefix?: string): UntypedProperty[];
17
+ //# sourceMappingURL=untyped-properties.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"untyped-properties.d.ts","sourceRoot":"","sources":["../../src/tools/untyped-properties.ts"],"names":[],"mappings":"AAkBA,qEAAqE;AACrE,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AASD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,SAAK,GAAG,eAAe,EAAE,CA+DrF"}
package/dist/tools.d.ts CHANGED
@@ -22,5 +22,6 @@ 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';
24
24
  export { summarizeTransports, type TransportCounts, type TransportSummary, } from './tools/transports';
25
+ export { findUntypedProperties, type UntypedProperty } from './tools/untyped-properties';
25
26
  export { type McpAnnotations, type McpMediaContent, mountViewFile, resolveMedia, type ViewFileOptions, } from './tools/view-file';
26
27
  //# sourceMappingURL=tools.d.ts.map
@@ -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,EACV,WAAW,EACX,eAAe,EACf,aAAa,EACb,aAAa,EACb,UAAU,GACX,MAAM,iBAAiB,CAAC;AACzB,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,EACL,KAAK,kBAAkB,EACvB,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,eAAe,CAAC;AACvB,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,EACL,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,aAAa,EACb,YAAY,EACZ,KAAK,eAAe,GACrB,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,EACV,WAAW,EACX,eAAe,EACf,aAAa,EACb,aAAa,EACb,UAAU,GACX,MAAM,iBAAiB,CAAC;AACzB,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,EACL,KAAK,kBAAkB,EACvB,YAAY,EACZ,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,eAAe,CAAC;AACvB,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,qBAAqB,EAAE,KAAK,eAAe,EAAE,MAAM,4BAA4B,CAAC;AACzF,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,aAAa,EACb,YAAY,EACZ,KAAK,eAAe,GACrB,MAAM,mBAAmB,CAAC"}
package/dist/tools.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  inputIsQuery,
3
3
  signJwt,
4
4
  verifyPkce
5
- } from "./index-nmq59nae.js";
5
+ } from "./index-bx49hskg.js";
6
6
  import {
7
7
  DEFAULT_CORS_ALLOW_HEADERS
8
8
  } from "./index-czmqks7r.js";
@@ -21,22 +21,20 @@ import {
21
21
  readCapped,
22
22
  toolResultFromError,
23
23
  writeDownload
24
- } from "./index-jr8vf7g8.js";
24
+ } from "./index-kgga1n8d.js";
25
25
  import {
26
26
  toJsonSchema
27
27
  } from "./index-0ed3bx43.js";
28
28
  import {
29
29
  isWithinDir
30
30
  } from "./index-x3fcszf8.js";
31
- import {
32
- getTraceId
33
- } from "./index-wya8bkme.js";
34
31
  import {
35
32
  AppError,
33
+ getTraceId,
36
34
  isRecord,
37
35
  mergeMeta,
38
36
  typedEntries
39
- } from "./index-enc7t99e.js";
37
+ } from "./index-17rdjw68.js";
40
38
 
41
39
  // src/tools/agent.ts
42
40
  import { tool, zodSchema } from "ai";
@@ -139,6 +137,77 @@ function inlineMcpAppBundle(html) {
139
137
  return html.replace(EXT_APPS_BUNDLE_PLACEHOLDER, () => bundle);
140
138
  }
141
139
 
140
+ // src/tools/untyped-properties.ts
141
+ var TYPE_KEYWORDS = ["type", "enum", "const", "anyOf", "oneOf", "allOf", "$ref", "not"];
142
+ function saysWhatItIs(schema) {
143
+ return TYPE_KEYWORDS.some((keyword) => schema[keyword] !== undefined);
144
+ }
145
+ function findUntypedProperties(schema, prefix = "") {
146
+ if (!isRecord(schema))
147
+ return [];
148
+ const found = [];
149
+ const properties = schema.properties;
150
+ if (isRecord(properties)) {
151
+ for (const [key, value] of Object.entries(properties)) {
152
+ const path = prefix ? `${prefix}.${key}` : key;
153
+ if (isRecord(value)) {
154
+ if (!saysWhatItIs(value)) {
155
+ const description = value.description;
156
+ found.push({
157
+ path,
158
+ ...typeof description === "string" && { description }
159
+ });
160
+ }
161
+ found.push(...findUntypedProperties(value, path));
162
+ }
163
+ }
164
+ }
165
+ for (const key of ["items", "additionalProperties"]) {
166
+ const child = schema[key];
167
+ if (isRecord(child))
168
+ found.push(...findUntypedProperties(child, prefix));
169
+ if (Array.isArray(child)) {
170
+ for (const entry of child) {
171
+ if (isRecord(entry))
172
+ found.push(...findUntypedProperties(entry, prefix));
173
+ }
174
+ }
175
+ }
176
+ for (const key of ["prefixItems"]) {
177
+ const child = schema[key];
178
+ if (Array.isArray(child)) {
179
+ for (const entry of child) {
180
+ if (isRecord(entry))
181
+ found.push(...findUntypedProperties(entry, prefix));
182
+ }
183
+ }
184
+ }
185
+ const patterned = schema.patternProperties;
186
+ if (isRecord(patterned)) {
187
+ for (const value of Object.values(patterned)) {
188
+ if (isRecord(value))
189
+ found.push(...findUntypedProperties(value, prefix));
190
+ }
191
+ }
192
+ for (const key of ["$defs", "definitions"]) {
193
+ const container = schema[key];
194
+ if (!isRecord(container))
195
+ continue;
196
+ for (const definition of Object.values(container)) {
197
+ if (isRecord(definition))
198
+ found.push(...findUntypedProperties(definition, prefix));
199
+ }
200
+ }
201
+ for (const key of ["allOf", "anyOf", "oneOf"]) {
202
+ const branches = schema[key];
203
+ if (Array.isArray(branches)) {
204
+ for (const branch of branches)
205
+ found.push(...findUntypedProperties(branch, prefix));
206
+ }
207
+ }
208
+ return found;
209
+ }
210
+
142
211
  // src/tools/mcp.ts
143
212
  function textBlock(text) {
144
213
  return [{ type: "text", text }];
@@ -187,7 +256,7 @@ function reportIncompatible(message, policy, logger, failures) {
187
256
  }
188
257
  function throwIfFailures(failures) {
189
258
  if (failures.length > 0) {
190
- throw new Error(`[stitchkit] ${failures.length} MCP tool(s) have an incompatible schema:
259
+ throw new Error(`[stitchkit] ${failures.length} problem(s) with MCP tool schemas:
191
260
  - ${failures.join(`
192
261
  - `)}`);
193
262
  }
@@ -217,9 +286,19 @@ function prepareMcpTool(mountable, policy, logger, failures, seen) {
217
286
  function validateMcpSchemas(services, onIncompatibleSchema = "throw", logger, options) {
218
287
  const seen = new Set;
219
288
  const failures = [];
289
+ const allowed = new Set(options?.allowUntyped ?? []);
220
290
  for (const service of services) {
221
291
  for (const mountable of collectTools(service, "MCP", options)) {
222
- prepareMcpTool(mountable, onIncompatibleSchema, logger, failures, seen);
292
+ const prepared = prepareMcpTool(mountable, onIncompatibleSchema, logger, failures, seen);
293
+ if (!prepared || !options?.requireTypedProperties)
294
+ continue;
295
+ for (const untyped of findUntypedProperties(toJsonSchema(mountable.schema, "input"))) {
296
+ const path = `${mountable.name}.${untyped.path}`;
297
+ if (allowed.has(path))
298
+ continue;
299
+ const clue = untyped.description ? ` (only a description: "${untyped.description}")` : "";
300
+ reportIncompatible(`MCP tool "${mountable.name}" — property "${untyped.path}" carries no type, enum or $ref${clue}. ` + "A model is given no way to know what to send. Widen the contract, or list it in `allowUntyped` if it is deliberately free-form.", onIncompatibleSchema === "skip" ? "warn" : onIncompatibleSchema, logger, failures);
301
+ }
223
302
  }
224
303
  }
225
304
  throwIfFailures(failures);
@@ -1410,6 +1489,7 @@ export {
1410
1489
  implementRemote,
1411
1490
  flattenUnionsDeep,
1412
1491
  flattenDiscriminatedUnion,
1492
+ findUntypedProperties,
1413
1493
  createToolkit,
1414
1494
  createToolLogger,
1415
1495
  createStdioMcpServer,
package/llms-full.txt CHANGED
@@ -1524,6 +1524,30 @@ cannot become a tool. `onIncompatibleSchema` decides what happens:
1524
1524
  `validateMcpSchemas(services)` runs the same check on its own — useful in a
1525
1525
  startup assertion or a test.
1526
1526
 
1527
+ #### Is every property actually usable by a model?
1528
+
1529
+ A tool schema is the only instruction a model gets about the shape of its
1530
+ arguments. A property that carries a `description` and no `type` / `enum` /
1531
+ `anyOf` / `$ref` tells it nothing — and nothing fails: the schema converts, the
1532
+ mount succeeds, the tool is advertised, and the model then guesses, retrying the
1533
+ same wrong guess because the error does not say what the right one would be.
1534
+
1535
+ ```ts
1536
+ validateMcpSchemas(services, 'throw', logger, {
1537
+ flattenUnionInput: true, // mirror the live mount
1538
+ requireTypedProperties: true,
1539
+ allowUntyped: ['docs_create.payload'], // deliberately free-form
1540
+ })
1541
+ ```
1542
+
1543
+ Off by default, because a contract may legitimately declare `z.unknown()`.
1544
+ `allowUntyped` takes dotted `tool.property` paths — an entry there is a decision,
1545
+ anything else is a finding. Pass the **same** `extend` / `flattenUnionInput` the
1546
+ mount uses, or the check vets a different document than the one advertised.
1547
+
1548
+ `findUntypedProperties(jsonSchema)` is the same walk, exported on its own if you
1549
+ want to assert on a schema you built elsewhere.
1550
+
1527
1551
  ### Discriminated unions for weaker models — `flattenUnionInput`
1528
1552
 
1529
1553
  A discriminated union in a tool's input becomes a JSON Schema `oneOf` / `anyOf`.
@@ -1533,6 +1557,15 @@ the field or mangle its strings. Set `flattenUnionInput: true` (on
1533
1557
  union as a **single flat object** instead — the discriminator becomes an enum and
1534
1558
  each variant's fields become optional with a `Required if <disc> = …` hint.
1535
1559
 
1560
+ **A field several variants declare keeps its type.** Flattening puts every
1561
+ variant's fields side by side, so a key two variants share has to be advertised
1562
+ as one thing. Where they agree, that is what you get; where they disagree — a
1563
+ `.refine()` only one of them carries, two different bounds on the same number, an
1564
+ enum against a free string — the *constraint* is dropped and the **type** is not.
1565
+ A field that is a number in every variant is advertised as a number, not as a
1566
+ bare description. Only genuinely different kinds (a string in one variant, a
1567
+ number in another) fall back to unconstrained. → ADR 0044
1568
+
1536
1569
  It is **deep**: unions are flattened at every depth — top level, object fields,
1537
1570
  array items, and through `optional` / `nullable` / `default` / intersection
1538
1571
  wrappers — so no `oneOf` survives anywhere (e.g. a `content.parts[]` that is an
@@ -2879,6 +2912,8 @@ import {
2879
2912
  } from 'stitchkit/observability'
2880
2913
 
2881
2914
  createAuthHook({ /* … */ inject: (ctx, user) => user && setRequestUser(user.id) })
2915
+ // ↑ on the HTTP path. Inside a tool call this writes to that call's own context
2916
+ // (→ ADR 0045); a tool row takes its identity from the mount's `context`.
2882
2917
  // only to override what the framework already recorded — see below:
2883
2918
  setRequestError({ code: err.code, message: err.message, details: err.issues })
2884
2919
  ```
@@ -2907,6 +2942,12 @@ meaning to (→ ADR 0021). Resolve it cheaply from `ctx.params` / headers in
2907
2942
  `ctx.req` are available there) and it lands on `event.dimensions` for the request
2908
2943
  either way, so your sink reads it as a column instead of re-parsing the path:
2909
2944
 
2945
+ > **On the tool path it lands on that call's own event, not the request's.** The
2946
+ > same hooks object is assignable to `ToolLifecycle`, so this recipe is the one
2947
+ > people apply to tools — and since ADR 0045 each tool call runs in its own
2948
+ > context. Read the value off the tool row (`event.toolName != null`); both rows
2949
+ > carry the same `traceId`.
2950
+
2910
2951
  ```ts
2911
2952
  // beforeHandle (success) and onError (failure) alike:
2912
2953
  const projectId = ctx.req?.headers.get('x-project') ?? String(ctx.params?.projectId ?? '')
@@ -3103,10 +3144,10 @@ object must stay assignable to `ToolLifecycle`.)
3103
3144
  which `createAuditHook`'s **tool** row does not read: a tool event takes
3104
3145
  `errorCode` / `errorMessage` / `errorDetail` from the `ToolResult`, and only
3105
3146
  identity and `dimensions` from the context. Calling it in `onToolError` would
3106
- leave the tool row exactly as scrubbed as before and for MCP over HTTP it would
3107
- also write the cause into the log line of the enclosing `/mcp` request, turning
3108
- one incident into two records. It is right for the **HTTP** path, where the
3109
- request *is* the record.
3147
+ leave the tool row exactly as scrubbed as before. It is right for the **HTTP**
3148
+ path, where the request *is* the record. (Since ADR 0045 a tool call runs in its
3149
+ own context, so it can no longer write into the enclosing `/mcp` request's row
3150
+ either the call is simply not where that helper belongs.)
3110
3151
 
3111
3152
  ### One row that names the cause
3112
3153
 
@@ -3921,6 +3962,8 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
3921
3962
  | `ViewFileOptions` | _type_ | options for `mountViewFile` |
3922
3963
  | `McpAnnotations` | _type_ | MCP annotations on a media result |
3923
3964
  | `CollectToolsConfig` | _type_ | options for `collectTools` |
3965
+ | `findUntypedProperties` | function | every property in a JSON Schema with no `type`/`enum`/`$ref` — what a model is shown and cannot obey ([guide](../guide/mcp-and-agents.md)) |
3966
+ | `UntypedProperty` | _type_ | one such property — `{ path, description? }` |
3924
3967
  | `ToolNameEntry` | _type_ | one `listToolNames` row — `{ name, service, method, transports }` |
3925
3968
  | `IncompatibleSchemaPolicy` | _type_ | `'throw' \| 'skip' \| 'warn'` |
3926
3969
  | `McpMediaContent` | _type_ | a multimodal MCP content item |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.34.0",
3
+ "version": "0.36.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",
@@ -1,151 +0,0 @@
1
- import {
2
- isRecord,
3
- isUnsafeKey
4
- } from "./index-enc7t99e.js";
5
-
6
- // src/server/request.ts
7
- function generateTraceId() {
8
- return `${Date.now().toString(36)}-${crypto.randomUUID().slice(0, 8)}`;
9
- }
10
- function resolveTraceId(req) {
11
- const header = req.headers.get("x-request-id") ?? req.headers.get("x-trace-id");
12
- const trimmed = header?.trim();
13
- if (trimmed && trimmed.length <= 128 && /^[\w.-]+$/.test(trimmed)) {
14
- return trimmed;
15
- }
16
- return generateTraceId();
17
- }
18
- function resolveSocketIp(req, server) {
19
- if (typeof server === "object" && server !== null && "requestIP" in server && typeof server.requestIP === "function") {
20
- const addr = server.requestIP(req);
21
- if (isRecord(addr) && typeof addr.address === "string" && addr.address) {
22
- return addr.address;
23
- }
24
- }
25
- if ("ip" in req && typeof req.ip === "string" && req.ip)
26
- return req.ip;
27
- return;
28
- }
29
- function extractIp(req, options = {}) {
30
- if (options.trustProxy) {
31
- const forwarded = req.headers.get("x-forwarded-for");
32
- if (forwarded)
33
- return (forwarded.split(",")[0] ?? "").trim().replace(/^::ffff:/, "");
34
- const realIp = req.headers.get("x-real-ip");
35
- if (realIp)
36
- return realIp.trim().replace(/^::ffff:/, "");
37
- }
38
- return (options.socketIp ?? "").replace(/^::ffff:/, "");
39
- }
40
- function getClientInfo(req, options = {}) {
41
- return {
42
- ipAddress: extractIp(req, options) || undefined,
43
- userAgent: req.headers.get("user-agent") ?? undefined
44
- };
45
- }
46
- function parseQueryParams(url) {
47
- const query = {};
48
- for (const key of new Set(url.searchParams.keys())) {
49
- if (isUnsafeKey(key))
50
- continue;
51
- const values = url.searchParams.getAll(key);
52
- const [first] = values;
53
- query[key] = values.length === 1 && first !== undefined ? first : values;
54
- }
55
- return query;
56
- }
57
-
58
- // src/observability/trace.ts
59
- var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
60
- function randomHex(bytes) {
61
- const arr = new Uint8Array(bytes);
62
- crypto.getRandomValues(arr);
63
- let hex = "";
64
- for (const byte of arr)
65
- hex += byte.toString(16).padStart(2, "0");
66
- return hex;
67
- }
68
- function createTraceContext() {
69
- return { traceId: randomHex(16), spanId: randomHex(8) };
70
- }
71
- function parseTraceparent(header) {
72
- if (!header)
73
- return null;
74
- const match = TRACEPARENT_RE.exec(header.trim());
75
- if (!match?.[1] || !match[2])
76
- return null;
77
- const traceId = match[1].toLowerCase();
78
- const parentSpanId = match[2].toLowerCase();
79
- if (/^0+$/.test(traceId) || /^0+$/.test(parentSpanId))
80
- return null;
81
- return { traceId, spanId: randomHex(8), parentSpanId };
82
- }
83
- function formatTraceparent(ctx) {
84
- return `00-${ctx.traceId}-${ctx.spanId}-01`;
85
- }
86
- function resolveTraceContext(req) {
87
- return parseTraceparent(req.headers.get("traceparent")) ?? createTraceContext();
88
- }
89
- function childSpan(parent) {
90
- return {
91
- traceId: parent.traceId,
92
- spanId: randomHex(8),
93
- parentSpanId: parent.spanId
94
- };
95
- }
96
-
97
- // src/observability/context.ts
98
- import { AsyncLocalStorage } from "node:async_hooks";
99
- var storage = new AsyncLocalStorage;
100
- function runWithRequestContext(ctx, fn) {
101
- return storage.run(ctx, fn);
102
- }
103
- function getRequestContext() {
104
- return storage.getStore();
105
- }
106
- function getTraceId() {
107
- return storage.getStore()?.trace.traceId;
108
- }
109
- function getUserId() {
110
- return storage.getStore()?.userId;
111
- }
112
- function setRequestUser(userId) {
113
- const ctx = storage.getStore();
114
- if (ctx)
115
- ctx.userId = userId;
116
- }
117
- function setRequestEndpoint(serviceName, action) {
118
- const ctx = storage.getStore();
119
- if (ctx) {
120
- ctx.serviceName = serviceName;
121
- ctx.action = action;
122
- }
123
- }
124
- function setRequestDimensions(dimensions) {
125
- const ctx = storage.getStore();
126
- if (ctx)
127
- ctx.dimensions = { ...ctx.dimensions, ...dimensions };
128
- }
129
- function setRequestError(error) {
130
- const ctx = storage.getStore();
131
- if (ctx)
132
- ctx.error = error;
133
- }
134
- function wrapInRequestContext(handler, options = {}) {
135
- return (req, server) => {
136
- const ctx = {
137
- trace: resolveTraceContext(req),
138
- source: "http",
139
- method: req.method,
140
- path: new URL(req.url, "http://localhost").pathname,
141
- startedAt: process.hrtime.bigint(),
142
- ...getClientInfo(req, {
143
- trustProxy: options.trustProxy,
144
- socketIp: resolveSocketIp(req, server)
145
- })
146
- };
147
- return runWithRequestContext(ctx, () => handler(req, server));
148
- };
149
- }
150
-
151
- export { generateTraceId, resolveTraceId, resolveSocketIp, extractIp, getClientInfo, parseQueryParams, createTraceContext, parseTraceparent, formatTraceparent, resolveTraceContext, childSpan, runWithRequestContext, getRequestContext, getTraceId, getUserId, setRequestUser, setRequestEndpoint, setRequestDimensions, setRequestError, wrapInRequestContext };