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 +2 -2
- package/dist/{index-enc7t99e.js → index-17rdjw68.js} +146 -1
- package/dist/{index-nmq59nae.js → index-bx49hskg.js} +1 -1
- package/dist/{index-k7ysesv0.js → index-dvrn81q4.js} +9 -11
- package/dist/{index-jr8vf7g8.js → index-kgga1n8d.js} +121 -15
- package/dist/node.js +2 -3
- package/dist/observability/index.js +4 -6
- package/dist/server/index.js +9 -11
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/flatten.d.ts.map +1 -1
- package/dist/tools/mcp.d.ts +9 -0
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/untyped-properties.d.ts +17 -0
- package/dist/tools/untyped-properties.d.ts.map +1 -0
- package/dist/tools.d.ts +1 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +88 -8
- package/llms-full.txt +47 -4
- package/package.json +1 -1
- package/dist/index-wya8bkme.js +0 -151
package/dist/cli.js
CHANGED
|
@@ -4,10 +4,10 @@ import {
|
|
|
4
4
|
emitResult,
|
|
5
5
|
parseCliArgs,
|
|
6
6
|
pollUntilDone
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-kgga1n8d.js";
|
|
8
8
|
import"./index-0ed3bx43.js";
|
|
9
9
|
import"./index-x3fcszf8.js";
|
|
10
|
-
import"./index-
|
|
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
|
-
|
|
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,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-
|
|
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-
|
|
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
|
|
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) &&
|
|
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
|
|
253
|
+
return hasInvisibleConstraint(schema.unwrap());
|
|
240
254
|
}
|
|
241
255
|
if (schema instanceof z3.ZodPipe)
|
|
242
|
-
return
|
|
243
|
-
if (schema instanceof z3.ZodObject)
|
|
244
|
-
return Object.values(schema.shape).some(
|
|
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
|
|
261
|
+
return hasInvisibleConstraint(schema.element);
|
|
247
262
|
if (schema instanceof z3.ZodUnion)
|
|
248
|
-
return schema.def.options.some(
|
|
263
|
+
return schema.def.options.some(hasInvisibleConstraint);
|
|
249
264
|
if (schema instanceof z3.ZodRecord)
|
|
250
|
-
return
|
|
265
|
+
return hasInvisibleConstraint(schema.valueType);
|
|
251
266
|
if (schema instanceof z3.ZodIntersection) {
|
|
252
|
-
return
|
|
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
|
-
|
|
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 [
|
|
290
|
-
if (
|
|
291
|
-
return z3.enum([
|
|
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-
|
|
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-
|
|
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-
|
|
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;
|
package/dist/server/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
socketIoLane,
|
|
11
11
|
staticRoute,
|
|
12
12
|
webSocketLane
|
|
13
|
-
} from "../index-
|
|
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-
|
|
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-
|
|
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;
|
|
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;
|
|
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"}
|
package/dist/tools/mcp.d.ts
CHANGED
|
@@ -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
|
/**
|
package/dist/tools/mcp.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/tools/mcp.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAIpE,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,EAGnB,MAAM,WAAW,CAAC;AAEnB,OAAO,EAAE,KAAK,cAAc,EAAsB,MAAM,WAAW,CAAC;AACpE,OAAO,EAKL,KAAK,UAAU,EAChB,MAAM,SAAS,CAAC;
|
|
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
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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}
|
|
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
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
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
package/dist/index-wya8bkme.js
DELETED
|
@@ -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 };
|