stitchkit 0.55.0 → 0.56.1
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/browser/socket-io.d.ts +14 -6
- package/dist/browser/socket-io.d.ts.map +1 -1
- package/dist/cli.js +4 -4
- package/dist/contract/errors.d.ts +7 -0
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/index.js +1 -1
- package/dist/files/boundary.d.ts +8 -5
- package/dist/files/boundary.d.ts.map +1 -1
- package/dist/files.js +1 -1
- package/dist/{index-rgd18hx3.js → index-28cqssm3.js} +7 -0
- package/dist/index-557by2db.js +34 -0
- package/dist/index-5r13htq1.js +283 -0
- package/dist/index-6y759j86.js +1031 -0
- package/dist/{index-npye5kt7.js → index-75njxz7p.js} +69 -7
- package/dist/{index-1k16zv57.js → index-bfcpjw20.js} +125 -31
- package/dist/{index-0gv7k0ra.js → index-escqg10p.js} +73 -666
- package/dist/{index-v4bbq3p4.js → index-j3dem06f.js} +1 -1
- package/dist/{index-qx84bqzk.js → index-jcc611vh.js} +1 -1
- package/dist/{index-pzc32v75.js → index-sm2tjx06.js} +41 -4
- package/dist/{index-js7yexng.js → index-xy8fmh6w.js} +83 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +27 -6
- package/dist/node.js +5 -4
- package/dist/observability/audit.d.ts +4 -0
- package/dist/observability/audit.d.ts.map +1 -1
- package/dist/observability/event.d.ts +5 -0
- package/dist/observability/event.d.ts.map +1 -1
- package/dist/observability/index.js +17 -11
- package/dist/remote.js +1 -1
- package/dist/server/context-contribution.d.ts +8 -0
- package/dist/server/context-contribution.d.ts.map +1 -1
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/error-hook.d.ts +1 -1
- package/dist/server/error-hook.d.ts.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +10 -6
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/middleware/auth.d.ts +29 -2
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/request-body.d.ts.map +1 -1
- package/dist/testing/surface-conformance.d.ts +143 -10
- package/dist/testing/surface-conformance.d.ts.map +1 -1
- package/dist/testing/surface-manifest.d.ts +132 -33
- package/dist/testing/surface-manifest.d.ts.map +1 -1
- package/dist/testing.d.ts +2 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +470 -122
- package/dist/tools/async-operation.d.ts +210 -18
- package/dist/tools/async-operation.d.ts.map +1 -1
- package/dist/tools/define-download-tool.d.ts.map +1 -1
- package/dist/tools/define-upload-tool.d.ts.map +1 -1
- package/dist/tools/internal/surface-projector.d.ts +101 -0
- package/dist/tools/internal/surface-projector.d.ts.map +1 -0
- package/dist/tools/managed-file-error.d.ts +6 -0
- package/dist/tools/managed-file-error.d.ts.map +1 -0
- package/dist/tools/mcp-prepare.d.ts +3 -21
- package/dist/tools/mcp-prepare.d.ts.map +1 -1
- package/dist/tools/mcp-round-policy.d.ts +9 -0
- package/dist/tools/mcp-round-policy.d.ts.map +1 -0
- package/dist/tools/mcp-round.d.ts +0 -4
- package/dist/tools/mcp-round.d.ts.map +1 -1
- package/dist/tools/mount-download.d.ts.map +1 -1
- package/dist/tools/mount-upload.d.ts.map +1 -1
- package/dist/tools/mount.d.ts +5 -9
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/surface.d.ts +7 -0
- package/dist/tools/surface.d.ts.map +1 -1
- package/dist/tools/view-file.d.ts.map +1 -1
- package/dist/tools.d.ts +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +348 -383
- package/llms-full.txt +425 -66
- package/package.json +1 -1
- package/dist/index-h60df7rj.js +0 -110
- package/dist/index-k4dftwf7.js +0 -66
package/dist/tools.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
signJwt,
|
|
3
3
|
verifyPkce
|
|
4
|
-
} from "./index-
|
|
4
|
+
} from "./index-xy8fmh6w.js";
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_CORS_ALLOW_HEADERS,
|
|
7
7
|
DEFAULT_PROCESS_SIGNALS,
|
|
@@ -9,10 +9,9 @@ import {
|
|
|
9
9
|
defaultSignalSource,
|
|
10
10
|
guardSignalCallback,
|
|
11
11
|
reportSignalError
|
|
12
|
-
} from "./index-
|
|
12
|
+
} from "./index-sm2tjx06.js";
|
|
13
13
|
import {
|
|
14
14
|
WaitTimeoutError,
|
|
15
|
-
buildToolPresentationSchema,
|
|
16
15
|
coerceJsonArgs,
|
|
17
16
|
collectToolSurface,
|
|
18
17
|
collectTools,
|
|
@@ -24,37 +23,42 @@ import {
|
|
|
24
23
|
executeToolMethod,
|
|
25
24
|
fetchGuarded,
|
|
26
25
|
fetchPinnedDocument,
|
|
27
|
-
flattenToolJsonSchema,
|
|
28
26
|
formatToolError,
|
|
29
|
-
isObjectPresentationSchema,
|
|
30
|
-
presentationMetadata,
|
|
31
27
|
readCapped,
|
|
32
28
|
runWaitOperation,
|
|
33
29
|
toolErrorFromResult,
|
|
34
30
|
toolResultFromError
|
|
35
|
-
} from "./index-
|
|
31
|
+
} from "./index-escqg10p.js";
|
|
36
32
|
import {
|
|
37
33
|
ManagedFileError
|
|
38
|
-
} from "./index-
|
|
34
|
+
} from "./index-bfcpjw20.js";
|
|
39
35
|
import"./index-sbdmyz75.js";
|
|
40
36
|
import {
|
|
37
|
+
PORTABLE_JSON_SCHEMA_FORMATS,
|
|
41
38
|
assertToolName,
|
|
42
|
-
assertUniqueToolName
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
assertUniqueToolName,
|
|
40
|
+
findNonPortableFormats,
|
|
41
|
+
findUntypedProperties,
|
|
42
|
+
flattenToolJsonSchema,
|
|
43
|
+
prepareProjectedMcpTools,
|
|
44
|
+
presentationMetadata,
|
|
45
|
+
validateMcpRoundPolicy
|
|
46
|
+
} from "./index-6y759j86.js";
|
|
47
|
+
import"./index-cby4ar3v.js";
|
|
47
48
|
import {
|
|
48
49
|
redact
|
|
49
|
-
} from "./index-
|
|
50
|
+
} from "./index-jcc611vh.js";
|
|
50
51
|
import {
|
|
51
52
|
getRequestContext,
|
|
52
53
|
getTraceId,
|
|
54
|
+
normalizeError,
|
|
53
55
|
runWithRequestContext
|
|
54
|
-
} from "./index-
|
|
56
|
+
} from "./index-j3dem06f.js";
|
|
55
57
|
import {
|
|
56
|
-
AppError
|
|
57
|
-
|
|
58
|
+
AppError,
|
|
59
|
+
STITCH_ERROR_STATUS,
|
|
60
|
+
defineContract
|
|
61
|
+
} from "./index-5r13htq1.js";
|
|
58
62
|
import {
|
|
59
63
|
ManagedFilePathSchema,
|
|
60
64
|
ManagedFileRefSchema
|
|
@@ -328,6 +332,120 @@ function defineAsyncOperation(config) {
|
|
|
328
332
|
}
|
|
329
333
|
};
|
|
330
334
|
}
|
|
335
|
+
function adapterResult(capability, target, schema, value) {
|
|
336
|
+
const parsed = schema.safeParse(value);
|
|
337
|
+
if (!parsed.success) {
|
|
338
|
+
throw new Error(`Async operation adapter for capability "${capability}" returned invalid ${target}`, { cause: parsed.error });
|
|
339
|
+
}
|
|
340
|
+
return parsed.data;
|
|
341
|
+
}
|
|
342
|
+
var NON_WIRE_STABLE_ZOD_TYPES = new Set([
|
|
343
|
+
"catch",
|
|
344
|
+
"default",
|
|
345
|
+
"lazy",
|
|
346
|
+
"pipe",
|
|
347
|
+
"prefault",
|
|
348
|
+
"readonly",
|
|
349
|
+
"success",
|
|
350
|
+
"transform"
|
|
351
|
+
]);
|
|
352
|
+
function hasOverwriteCheck(value) {
|
|
353
|
+
return isRecord(value) && isRecord(value._zod) && isRecord(value._zod.def) && value._zod.def.check === "overwrite";
|
|
354
|
+
}
|
|
355
|
+
function assertWireStableIdSchema(schema, boundary) {
|
|
356
|
+
const visited = new Set;
|
|
357
|
+
const pending = [schema];
|
|
358
|
+
while (pending.length > 0) {
|
|
359
|
+
const value = pending.pop();
|
|
360
|
+
if (typeof value !== "object" || value === null || visited.has(value))
|
|
361
|
+
continue;
|
|
362
|
+
visited.add(value);
|
|
363
|
+
if (value instanceof z.ZodType) {
|
|
364
|
+
const definition = value._zod.def;
|
|
365
|
+
if (NON_WIRE_STABLE_ZOD_TYPES.has(definition.type) || "coerce" in definition && definition.coerce === true || "checks" in definition && Array.isArray(definition.checks) && definition.checks.some(hasOverwriteCheck)) {
|
|
366
|
+
throw new Error(`${boundary} must use a wire-stable ID schema without transforms, coercion, defaults or overwrites; use binding: "adapted" with explicit ID adapters`);
|
|
367
|
+
}
|
|
368
|
+
pending.push(...Object.values(definition));
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
if (Array.isArray(value)) {
|
|
372
|
+
pending.push(...value);
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
if (isRecord(value))
|
|
376
|
+
pending.push(...Object.values(value));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
function defineAsyncOperationContract(config) {
|
|
380
|
+
assertWireStableIdSchema(config.id, "Async operation contract");
|
|
381
|
+
const hasStartOutput = config.startOutput !== undefined;
|
|
382
|
+
const hasIdFromStart = config.idFromStart !== undefined;
|
|
383
|
+
if (hasStartOutput !== hasIdFromStart) {
|
|
384
|
+
throw new Error("Async operation contract startOutput and idFromStart must be configured together");
|
|
385
|
+
}
|
|
386
|
+
const description = (capability) => config.descriptions?.[capability] ?? `${config.description}: ${capability}`;
|
|
387
|
+
const endpoint = (capability, method, path, input, output) => {
|
|
388
|
+
const scope = config.scopes?.[capability];
|
|
389
|
+
return {
|
|
390
|
+
method,
|
|
391
|
+
path,
|
|
392
|
+
desc: description(capability),
|
|
393
|
+
input,
|
|
394
|
+
output,
|
|
395
|
+
...scope !== undefined && { scope }
|
|
396
|
+
};
|
|
397
|
+
};
|
|
398
|
+
const startOutput = config.startOutput ?? config.id;
|
|
399
|
+
const endpoints = {
|
|
400
|
+
start: endpoint("start", "POST", "/", config.startInput, startOutput),
|
|
401
|
+
status: endpoint("status", "POST", "/status", config.id, config.snapshot),
|
|
402
|
+
wait: endpoint("wait", "POST", "/wait", config.id, config.snapshot)
|
|
403
|
+
};
|
|
404
|
+
const capabilities = {
|
|
405
|
+
start: "start",
|
|
406
|
+
status: "status",
|
|
407
|
+
wait: "wait"
|
|
408
|
+
};
|
|
409
|
+
const schemas = {
|
|
410
|
+
startInput: config.startInput,
|
|
411
|
+
startOutput,
|
|
412
|
+
id: config.id,
|
|
413
|
+
snapshot: config.snapshot,
|
|
414
|
+
cancelResult: AsyncOperationCancelResultSchema
|
|
415
|
+
};
|
|
416
|
+
const inputFor = {
|
|
417
|
+
status: (id) => adapterResult("status", "input", config.id, id),
|
|
418
|
+
wait: (id) => adapterResult("wait", "input", config.id, id)
|
|
419
|
+
};
|
|
420
|
+
if (config.cancel) {
|
|
421
|
+
endpoints.cancel = endpoint("cancel", "POST", "/cancel", config.id, AsyncOperationCancelResultSchema);
|
|
422
|
+
capabilities.cancel = "cancel";
|
|
423
|
+
schemas.cancel = AsyncOperationCancelResultSchema;
|
|
424
|
+
inputFor.cancel = (id) => adapterResult("cancel", "input", config.id, id);
|
|
425
|
+
}
|
|
426
|
+
if (config.result) {
|
|
427
|
+
endpoints.result = endpoint("result", "POST", "/result", config.id, config.result);
|
|
428
|
+
capabilities.result = "result";
|
|
429
|
+
schemas.result = config.result;
|
|
430
|
+
inputFor.result = (id) => adapterResult("result", "input", config.id, id);
|
|
431
|
+
}
|
|
432
|
+
if (config.artifacts) {
|
|
433
|
+
endpoints.artifacts = endpoint("artifacts", "POST", "/artifacts", config.id, config.artifacts);
|
|
434
|
+
capabilities.artifacts = "artifacts";
|
|
435
|
+
schemas.artifacts = config.artifacts;
|
|
436
|
+
inputFor.artifacts = (id) => adapterResult("artifacts", "input", config.id, id);
|
|
437
|
+
}
|
|
438
|
+
const contract = config.scope ? defineContract({ prefix: config.prefix, scope: config.scope }, endpoints) : defineContract({ prefix: config.prefix }, endpoints);
|
|
439
|
+
const applicationStartOutput = config.startOutput;
|
|
440
|
+
const applicationIdFromStart = config.idFromStart;
|
|
441
|
+
const idFromStart = applicationStartOutput && applicationIdFromStart ? (output) => adapterResult("start", "id", config.id, applicationIdFromStart(output)) : (output) => adapterResult("start", "id", config.id, output);
|
|
442
|
+
return {
|
|
443
|
+
contract,
|
|
444
|
+
capabilities,
|
|
445
|
+
schemas,
|
|
446
|
+
adapters: { idFromStart, inputFor }
|
|
447
|
+
};
|
|
448
|
+
}
|
|
331
449
|
function bindContractAsyncOperation(config) {
|
|
332
450
|
const endpoint = (key) => {
|
|
333
451
|
const found = config.contract.endpoints[key];
|
|
@@ -341,27 +459,82 @@ function bindContractAsyncOperation(config) {
|
|
|
341
459
|
if (!start.output || !status.output || !wait.output) {
|
|
342
460
|
throw new Error("Contract async operation requires outputs for start, status and wait");
|
|
343
461
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
348
|
-
if (wait.output !== status.output) {
|
|
462
|
+
const startOutput = start.output;
|
|
463
|
+
const statusOutput = status.output;
|
|
464
|
+
if (wait.output !== statusOutput) {
|
|
349
465
|
throw new Error('Contract async operation capability "wait" output must reuse the same schema instance as the status output');
|
|
350
466
|
}
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
467
|
+
const optionalCapabilities = [
|
|
468
|
+
"cancel",
|
|
469
|
+
"result",
|
|
470
|
+
"artifacts"
|
|
471
|
+
];
|
|
472
|
+
if (config.binding !== "adapted") {
|
|
473
|
+
assertWireStableIdSchema(startOutput, "Direct contract async operation");
|
|
474
|
+
if (status.input !== startOutput || wait.input !== startOutput) {
|
|
475
|
+
const capability = status.input !== startOutput ? "status" : "wait";
|
|
354
476
|
throw new Error(`Contract async operation capability "${capability}" input must reuse the same schema instance as the start output`);
|
|
355
477
|
}
|
|
478
|
+
const inputFor2 = {
|
|
479
|
+
status: (id) => adapterResult("status", "input", startOutput, id),
|
|
480
|
+
wait: (id) => adapterResult("wait", "input", startOutput, id)
|
|
481
|
+
};
|
|
482
|
+
for (const capability of optionalCapabilities) {
|
|
483
|
+
const key = config.capabilities[capability];
|
|
484
|
+
if (!key)
|
|
485
|
+
continue;
|
|
486
|
+
if (endpoint(key).input !== startOutput) {
|
|
487
|
+
throw new Error(`Contract async operation capability "${capability}" input must reuse the same schema instance as the start output`);
|
|
488
|
+
}
|
|
489
|
+
inputFor2[capability] = (id) => adapterResult(capability, "input", startOutput, id);
|
|
490
|
+
}
|
|
491
|
+
return {
|
|
492
|
+
contract: config.contract,
|
|
493
|
+
handlers: config.handlers,
|
|
494
|
+
capabilities: config.capabilities,
|
|
495
|
+
schemas: { id: startOutput, snapshot: statusOutput },
|
|
496
|
+
adapters: {
|
|
497
|
+
idFromStart: (output) => adapterResult("start", "id", startOutput, output),
|
|
498
|
+
inputFor: inputFor2
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
const adapters = config.adapters;
|
|
503
|
+
const idSchema = config.id;
|
|
504
|
+
const adaptedInput = (capability, key, builder) => {
|
|
505
|
+
const input = endpoint(key).input;
|
|
506
|
+
if (!input) {
|
|
507
|
+
throw new Error(`Contract async operation capability "${capability}" requires an input schema`);
|
|
508
|
+
}
|
|
509
|
+
return (id) => adapterResult(capability, "input", input, builder(id));
|
|
510
|
+
};
|
|
511
|
+
const inputFor = {
|
|
512
|
+
status: adaptedInput("status", config.capabilities.status, adapters.inputFor.status),
|
|
513
|
+
wait: adaptedInput("wait", config.capabilities.wait, adapters.inputFor.wait)
|
|
514
|
+
};
|
|
515
|
+
const optionalInput = (capability, key, builder) => {
|
|
516
|
+
if (!key)
|
|
517
|
+
return;
|
|
518
|
+
if (!builder) {
|
|
519
|
+
throw new Error(`Contract async operation capability "${capability}" requires an input adapter`);
|
|
520
|
+
}
|
|
521
|
+
inputFor[capability] = adaptedInput(capability, key, builder);
|
|
356
522
|
};
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
523
|
+
optionalInput("cancel", config.capabilities.cancel, "cancel" in adapters.inputFor ? adapters.inputFor.cancel : undefined);
|
|
524
|
+
optionalInput("result", config.capabilities.result, "result" in adapters.inputFor ? adapters.inputFor.result : undefined);
|
|
525
|
+
optionalInput("artifacts", config.capabilities.artifacts, "artifacts" in adapters.inputFor ? adapters.inputFor.artifacts : undefined);
|
|
526
|
+
if (!idSchema) {
|
|
527
|
+
throw new Error("Contract adapted async operation requires an id schema");
|
|
528
|
+
}
|
|
360
529
|
return {
|
|
361
530
|
contract: config.contract,
|
|
362
531
|
handlers: config.handlers,
|
|
363
532
|
capabilities: config.capabilities,
|
|
364
|
-
schemas: { id:
|
|
533
|
+
schemas: { id: idSchema, snapshot: statusOutput },
|
|
534
|
+
adapters: {
|
|
535
|
+
idFromStart: (output) => adapterResult("start", "id", idSchema, adapters.idFromStart(output)),
|
|
536
|
+
inputFor
|
|
537
|
+
}
|
|
365
538
|
};
|
|
366
539
|
}
|
|
367
540
|
// src/tools/download-core.ts
|
|
@@ -430,6 +603,36 @@ async function runDownloadOperation(config) {
|
|
|
430
603
|
});
|
|
431
604
|
}
|
|
432
605
|
|
|
606
|
+
// src/tools/managed-file-error.ts
|
|
607
|
+
var SAFE_MANAGED_FILE_MESSAGES = {
|
|
608
|
+
FILE_INVALID_PATH: "Invalid managed-file path",
|
|
609
|
+
FILE_OUTSIDE_ROOT: "Managed-file path escapes its boundary",
|
|
610
|
+
FILE_NOT_FOUND: "Managed file not found",
|
|
611
|
+
FILE_NOT_REGULAR: "Managed path is not a regular file",
|
|
612
|
+
FILE_INSPECTION_REJECTED: "Managed file rejected by inspection",
|
|
613
|
+
FILE_TOO_LARGE: "Managed file exceeds the configured size limit",
|
|
614
|
+
FILE_EXISTS: "Managed file already exists"
|
|
615
|
+
};
|
|
616
|
+
function managedFileAppError(error) {
|
|
617
|
+
if (!(error instanceof ManagedFileError))
|
|
618
|
+
return null;
|
|
619
|
+
switch (error.code) {
|
|
620
|
+
case "FILE_INVALID_PATH":
|
|
621
|
+
case "FILE_OUTSIDE_ROOT":
|
|
622
|
+
case "FILE_NOT_FOUND":
|
|
623
|
+
case "FILE_NOT_REGULAR":
|
|
624
|
+
case "FILE_INSPECTION_REJECTED":
|
|
625
|
+
case "FILE_TOO_LARGE":
|
|
626
|
+
case "FILE_EXISTS":
|
|
627
|
+
return new AppError(error.code, SAFE_MANAGED_FILE_MESSAGES[error.code], STITCH_ERROR_STATUS[error.code]);
|
|
628
|
+
case "FILE_IO_ERROR":
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function normalizeFileToolError(error) {
|
|
633
|
+
return managedFileAppError(error) ?? normalizeError(error);
|
|
634
|
+
}
|
|
635
|
+
|
|
433
636
|
// src/tools/define-download-tool.ts
|
|
434
637
|
function defineDownloadTool(config) {
|
|
435
638
|
return defineRuntimeTool({
|
|
@@ -460,6 +663,9 @@ function defineDownloadTool(config) {
|
|
|
460
663
|
if (error instanceof DownloadOperationError) {
|
|
461
664
|
throw new AppError(error.code, `Download failed: ${error.message}`, error.status);
|
|
462
665
|
}
|
|
666
|
+
const managedError = managedFileAppError(error);
|
|
667
|
+
if (managedError)
|
|
668
|
+
throw managedError;
|
|
463
669
|
throw error;
|
|
464
670
|
}
|
|
465
671
|
}
|
|
@@ -488,7 +694,16 @@ function defineUploadTool(config) {
|
|
|
488
694
|
transports: config.transports,
|
|
489
695
|
annotations: config.annotations,
|
|
490
696
|
present: config.present,
|
|
491
|
-
handler: async (context) =>
|
|
697
|
+
handler: async (context) => {
|
|
698
|
+
try {
|
|
699
|
+
return await runUploadOperation(config.files, context.input.path, (source) => config.upload(source, context), context.signal);
|
|
700
|
+
} catch (error) {
|
|
701
|
+
const managedError = managedFileAppError(error);
|
|
702
|
+
if (managedError)
|
|
703
|
+
throw managedError;
|
|
704
|
+
throw error;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
492
707
|
});
|
|
493
708
|
}
|
|
494
709
|
// src/tools/view-file.ts
|
|
@@ -546,7 +761,7 @@ async function fetchSource(pathOrUrl, options, maxBytes) {
|
|
|
546
761
|
signal: options.signal
|
|
547
762
|
});
|
|
548
763
|
if (!res.ok)
|
|
549
|
-
throw new
|
|
764
|
+
throw new AppError("VIEW_HTTP_ERROR", `HTTP ${res.status}`, 502);
|
|
550
765
|
const headerMime = (res.headers.get("content-type") ?? "").split(";")[0]?.trim() ?? "";
|
|
551
766
|
const mimeType = headerMime || extMime || "application/octet-stream";
|
|
552
767
|
if (mimeType.startsWith("video/")) {
|
|
@@ -562,11 +777,11 @@ async function fetchSource(pathOrUrl, options, maxBytes) {
|
|
|
562
777
|
return buffer ? { buffer, mimeType, bytesRead: buffer.length } : { tooLarge: true, mimeType, bytesRead: maxBytes };
|
|
563
778
|
}
|
|
564
779
|
if (!options.files) {
|
|
565
|
-
throw new
|
|
780
|
+
throw new AppError("FILE_INVALID_PATH", "local file paths are disabled — set files to allow them", 400);
|
|
566
781
|
}
|
|
567
782
|
options.signal?.throwIfAborted();
|
|
568
783
|
if (!extMime) {
|
|
569
|
-
throw new
|
|
784
|
+
throw new AppError("FILE_INSPECTION_REJECTED", "refusing to read a non-media file", 422);
|
|
570
785
|
}
|
|
571
786
|
try {
|
|
572
787
|
const source = await options.files.read(pathOrUrl, {
|
|
@@ -672,7 +887,7 @@ async function runViewFileOperation(paths, options = {}, signal) {
|
|
|
672
887
|
} catch (error) {
|
|
673
888
|
if (signal?.aborted)
|
|
674
889
|
throw signal.reason ?? error;
|
|
675
|
-
const message = error
|
|
890
|
+
const message = normalizeFileToolError(error).message;
|
|
676
891
|
errors.push({ path: pathOrUrl, message });
|
|
677
892
|
content.push({ type: "text", text: `[${pathOrUrl}] Error: ${message}` });
|
|
678
893
|
}
|
|
@@ -864,6 +1079,95 @@ function inlineMcpAppBundle(html) {
|
|
|
864
1079
|
return html.replace(EXT_APPS_BUNDLE_PLACEHOLDER, () => bundle);
|
|
865
1080
|
}
|
|
866
1081
|
|
|
1082
|
+
// src/tools/mcp-prepare.ts
|
|
1083
|
+
function textBlock(text) {
|
|
1084
|
+
return [{ type: "text", text }];
|
|
1085
|
+
}
|
|
1086
|
+
function formatMcpResult(result, mode, toolName, errorHint) {
|
|
1087
|
+
if (result.ok) {
|
|
1088
|
+
if (mode === "none")
|
|
1089
|
+
return { content: [] };
|
|
1090
|
+
let serialized;
|
|
1091
|
+
try {
|
|
1092
|
+
serialized = JSON.stringify(result.data, null, 2);
|
|
1093
|
+
} catch {
|
|
1094
|
+
serialized = undefined;
|
|
1095
|
+
}
|
|
1096
|
+
if (serialized === undefined) {
|
|
1097
|
+
const safe = redact(result.data, { sensitiveKeys: /(?!)/ });
|
|
1098
|
+
return { content: textBlock(JSON.stringify(safe, null, 2)), structuredContent: safe };
|
|
1099
|
+
}
|
|
1100
|
+
return { content: textBlock(serialized), structuredContent: result.data };
|
|
1101
|
+
}
|
|
1102
|
+
return {
|
|
1103
|
+
content: textBlock(JSON.stringify(formatToolError(result, toolName, errorHint), null, 2)),
|
|
1104
|
+
isError: true
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
function prepareMcpSurface(services, config = {}) {
|
|
1108
|
+
const serviceList = Array.isArray(services) ? services : [services];
|
|
1109
|
+
const tools = serviceList.flatMap((service) => collectTools(service, "MCP", config));
|
|
1110
|
+
return prepareMcpTools(tools, config);
|
|
1111
|
+
}
|
|
1112
|
+
function prepareMcpTools(tools, config = {}) {
|
|
1113
|
+
const prepared = prepareProjectedMcpTools(tools.map((mountable) => ({
|
|
1114
|
+
tool: mountable,
|
|
1115
|
+
name: mountable.name,
|
|
1116
|
+
...mountable.method.paramsSchema !== undefined && {
|
|
1117
|
+
paramsSchema: mountable.method.paramsSchema
|
|
1118
|
+
},
|
|
1119
|
+
...mountable.method.inputSchema !== undefined && {
|
|
1120
|
+
inputSchema: mountable.method.inputSchema
|
|
1121
|
+
},
|
|
1122
|
+
...mountable.method.outputSchema !== undefined && {
|
|
1123
|
+
outputSchema: mountable.method.outputSchema
|
|
1124
|
+
},
|
|
1125
|
+
shouldExtend: mountable.shouldExtend,
|
|
1126
|
+
...mountable.method.mcp !== undefined && { mcp: mountable.method.mcp }
|
|
1127
|
+
})), config).map((entry) => {
|
|
1128
|
+
Object.freeze(entry.tool);
|
|
1129
|
+
return Object.freeze({
|
|
1130
|
+
mountable: entry.tool,
|
|
1131
|
+
inputSchema: entry.inputSchema,
|
|
1132
|
+
...entry.outputSchema !== undefined && { outputSchema: entry.outputSchema },
|
|
1133
|
+
outputMode: entry.outputSchema === undefined ? "none" : "direct"
|
|
1134
|
+
});
|
|
1135
|
+
});
|
|
1136
|
+
return Object.freeze(prepared);
|
|
1137
|
+
}
|
|
1138
|
+
function prepareMcpServerSurface(surface, config = {}) {
|
|
1139
|
+
const contractMountables = [];
|
|
1140
|
+
const definitions = [];
|
|
1141
|
+
const runtimeMountables = [];
|
|
1142
|
+
for (const entry of collectToolSurface({
|
|
1143
|
+
surface,
|
|
1144
|
+
transport: "MCP",
|
|
1145
|
+
extend: config.extend,
|
|
1146
|
+
flattenUnionInput: config.flattenUnionInput
|
|
1147
|
+
})) {
|
|
1148
|
+
if (entry.kind === "contract") {
|
|
1149
|
+
contractMountables.push(entry.mountable);
|
|
1150
|
+
} else {
|
|
1151
|
+
definitions.push(entry.definition);
|
|
1152
|
+
runtimeMountables.push(entry.mountable);
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
const contractTools = prepareMcpTools(contractMountables, config);
|
|
1156
|
+
const runtimeDescriptors = prepareMcpTools(runtimeMountables, {
|
|
1157
|
+
schemaValidation: config.schemaValidation,
|
|
1158
|
+
logger: config.logger,
|
|
1159
|
+
multiRound: config.multiRound
|
|
1160
|
+
});
|
|
1161
|
+
const descriptorsByName = new Map(runtimeDescriptors.map((descriptor) => [descriptor.mountable.name, descriptor]));
|
|
1162
|
+
const runtimeTools = [];
|
|
1163
|
+
for (const definition of definitions) {
|
|
1164
|
+
const descriptor = descriptorsByName.get(definition.name);
|
|
1165
|
+
if (descriptor)
|
|
1166
|
+
runtimeTools.push(Object.freeze({ definition, descriptor }));
|
|
1167
|
+
}
|
|
1168
|
+
return Object.freeze({ contractTools, runtimeTools: Object.freeze(runtimeTools) });
|
|
1169
|
+
}
|
|
1170
|
+
|
|
867
1171
|
// src/tools/mcp-round.ts
|
|
868
1172
|
import {
|
|
869
1173
|
acceptedContent,
|
|
@@ -945,28 +1249,6 @@ async function runRoundFailure(tool2, rawArgs, runTool, context, code, message)
|
|
|
945
1249
|
}
|
|
946
1250
|
}, rawArgs, context);
|
|
947
1251
|
}
|
|
948
|
-
function validateMcpRoundPolicy(tool2, policy, runtime) {
|
|
949
|
-
if (!runtime?.stateConfigured) {
|
|
950
|
-
throw new Error(`[stitchkit] MCP tool "${tool2.name}" declares inputRequired but no multiRound.state key is configured`);
|
|
951
|
-
}
|
|
952
|
-
const { maxRounds } = runtime;
|
|
953
|
-
if (!Number.isInteger(maxRounds) || maxRounds < 1) {
|
|
954
|
-
throw new Error("[stitchkit] multiRound.serving.maxRounds must be a positive integer");
|
|
955
|
-
}
|
|
956
|
-
if (policy.inputRequired.length === 0) {
|
|
957
|
-
throw new Error(`[stitchkit] MCP tool "${tool2.name}" must declare at least one input round`);
|
|
958
|
-
}
|
|
959
|
-
if (policy.inputRequired.length > maxRounds) {
|
|
960
|
-
throw new Error(`[stitchkit] MCP tool "${tool2.name}" declares ${policy.inputRequired.length} input rounds, exceeding maxRounds ${maxRounds}`);
|
|
961
|
-
}
|
|
962
|
-
const keys = new Set;
|
|
963
|
-
for (const request of policy.inputRequired) {
|
|
964
|
-
if (keys.has(request.key)) {
|
|
965
|
-
throw new Error(`[stitchkit] MCP tool "${tool2.name}" declares duplicate input key "${request.key}"`);
|
|
966
|
-
}
|
|
967
|
-
keys.add(request.key);
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
1252
|
async function failedResolution(options) {
|
|
971
1253
|
const result = await runRoundFailure(options.tool, options.rawArgs, options.runTool, options.context, options.code, options.message);
|
|
972
1254
|
return { kind: "response", response: options.formatFailure(result) };
|
|
@@ -1099,329 +1381,6 @@ async function resolveMcpRound(options) {
|
|
|
1099
1381
|
};
|
|
1100
1382
|
}
|
|
1101
1383
|
|
|
1102
|
-
// src/tools/portable-formats.ts
|
|
1103
|
-
var PORTABLE_JSON_SCHEMA_FORMATS = new Set([
|
|
1104
|
-
"date",
|
|
1105
|
-
"time",
|
|
1106
|
-
"date-time",
|
|
1107
|
-
"duration",
|
|
1108
|
-
"uri",
|
|
1109
|
-
"uri-reference",
|
|
1110
|
-
"uri-template",
|
|
1111
|
-
"email",
|
|
1112
|
-
"hostname",
|
|
1113
|
-
"ipv4",
|
|
1114
|
-
"ipv6",
|
|
1115
|
-
"regex",
|
|
1116
|
-
"uuid",
|
|
1117
|
-
"json-pointer",
|
|
1118
|
-
"relative-json-pointer"
|
|
1119
|
-
]);
|
|
1120
|
-
function propertyPath(prefix, property) {
|
|
1121
|
-
return prefix ? `${prefix}.${property}` : property;
|
|
1122
|
-
}
|
|
1123
|
-
function keywordPath(prefix, keyword) {
|
|
1124
|
-
if (prefix && keyword.startsWith("["))
|
|
1125
|
-
return `${prefix}${keyword}`;
|
|
1126
|
-
return prefix ? `${prefix}.${keyword}` : keyword;
|
|
1127
|
-
}
|
|
1128
|
-
function findNonPortableFormats(schema, allowFormats = []) {
|
|
1129
|
-
const allowed = new Set([...PORTABLE_JSON_SCHEMA_FORMATS, ...allowFormats]);
|
|
1130
|
-
const found = [];
|
|
1131
|
-
const pending = [{ value: schema, path: "" }];
|
|
1132
|
-
while (pending.length > 0) {
|
|
1133
|
-
const current = pending.pop();
|
|
1134
|
-
if (!current || !isRecord(current.value))
|
|
1135
|
-
continue;
|
|
1136
|
-
const format = current.value.format;
|
|
1137
|
-
if (typeof format === "string" && !allowed.has(format)) {
|
|
1138
|
-
found.push({ path: current.path || "(root)", format });
|
|
1139
|
-
}
|
|
1140
|
-
const properties = current.value.properties;
|
|
1141
|
-
if (isRecord(properties)) {
|
|
1142
|
-
for (const [key, value] of Object.entries(properties)) {
|
|
1143
|
-
pending.push({ value, path: propertyPath(current.path, key) });
|
|
1144
|
-
}
|
|
1145
|
-
}
|
|
1146
|
-
for (const mapKey of ["$defs", "definitions", "patternProperties", "dependentSchemas"]) {
|
|
1147
|
-
const entries = current.value[mapKey];
|
|
1148
|
-
if (!isRecord(entries))
|
|
1149
|
-
continue;
|
|
1150
|
-
for (const [key, value] of Object.entries(entries)) {
|
|
1151
|
-
pending.push({ value, path: keywordPath(current.path, `${mapKey}.${key}`) });
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
const directChildren = [
|
|
1155
|
-
["items", "[]"],
|
|
1156
|
-
["contains", "contains"],
|
|
1157
|
-
["additionalProperties", "*"],
|
|
1158
|
-
["unevaluatedProperties", "*"],
|
|
1159
|
-
["propertyNames", "propertyNames"],
|
|
1160
|
-
["not", "not"],
|
|
1161
|
-
["if", "if"],
|
|
1162
|
-
["then", "then"],
|
|
1163
|
-
["else", "else"]
|
|
1164
|
-
];
|
|
1165
|
-
for (const [keyword, segment] of directChildren) {
|
|
1166
|
-
const child = current.value[keyword];
|
|
1167
|
-
if (isRecord(child)) {
|
|
1168
|
-
pending.push({ value: child, path: keywordPath(current.path, segment) });
|
|
1169
|
-
}
|
|
1170
|
-
if (Array.isArray(child)) {
|
|
1171
|
-
child.forEach((value, index) => {
|
|
1172
|
-
pending.push({ value, path: keywordPath(current.path, `${segment}[${index}]`) });
|
|
1173
|
-
});
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
for (const keyword of ["prefixItems", "allOf", "anyOf", "oneOf"]) {
|
|
1177
|
-
const branches = current.value[keyword];
|
|
1178
|
-
if (!Array.isArray(branches))
|
|
1179
|
-
continue;
|
|
1180
|
-
branches.forEach((value, index) => {
|
|
1181
|
-
pending.push({ value, path: keywordPath(current.path, `${keyword}[${index}]`) });
|
|
1182
|
-
});
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
return found.sort((left, right) => left.path.localeCompare(right.path) || left.format.localeCompare(right.format));
|
|
1186
|
-
}
|
|
1187
|
-
|
|
1188
|
-
// src/tools/untyped-properties.ts
|
|
1189
|
-
var TYPE_KEYWORDS = ["type", "enum", "const", "anyOf", "oneOf", "allOf", "$ref", "not"];
|
|
1190
|
-
function saysWhatItIs(schema) {
|
|
1191
|
-
return TYPE_KEYWORDS.some((keyword) => schema[keyword] !== undefined);
|
|
1192
|
-
}
|
|
1193
|
-
function findUntypedProperties(schema, prefix = "") {
|
|
1194
|
-
if (!isRecord(schema))
|
|
1195
|
-
return [];
|
|
1196
|
-
const found = [];
|
|
1197
|
-
const properties = schema.properties;
|
|
1198
|
-
if (isRecord(properties)) {
|
|
1199
|
-
for (const [key, value] of Object.entries(properties)) {
|
|
1200
|
-
const path = prefix ? `${prefix}.${key}` : key;
|
|
1201
|
-
if (isRecord(value)) {
|
|
1202
|
-
if (!saysWhatItIs(value)) {
|
|
1203
|
-
const description = value.description;
|
|
1204
|
-
found.push({
|
|
1205
|
-
path,
|
|
1206
|
-
...typeof description === "string" && { description }
|
|
1207
|
-
});
|
|
1208
|
-
}
|
|
1209
|
-
found.push(...findUntypedProperties(value, path));
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
for (const key of ["items", "additionalProperties"]) {
|
|
1214
|
-
const child = schema[key];
|
|
1215
|
-
if (isRecord(child))
|
|
1216
|
-
found.push(...findUntypedProperties(child, prefix));
|
|
1217
|
-
if (Array.isArray(child)) {
|
|
1218
|
-
for (const entry of child) {
|
|
1219
|
-
if (isRecord(entry))
|
|
1220
|
-
found.push(...findUntypedProperties(entry, prefix));
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
for (const key of ["prefixItems"]) {
|
|
1225
|
-
const child = schema[key];
|
|
1226
|
-
if (Array.isArray(child)) {
|
|
1227
|
-
for (const entry of child) {
|
|
1228
|
-
if (isRecord(entry))
|
|
1229
|
-
found.push(...findUntypedProperties(entry, prefix));
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
const patterned = schema.patternProperties;
|
|
1234
|
-
if (isRecord(patterned)) {
|
|
1235
|
-
for (const value of Object.values(patterned)) {
|
|
1236
|
-
if (isRecord(value))
|
|
1237
|
-
found.push(...findUntypedProperties(value, prefix));
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
for (const key of ["$defs", "definitions"]) {
|
|
1241
|
-
const container = schema[key];
|
|
1242
|
-
if (!isRecord(container))
|
|
1243
|
-
continue;
|
|
1244
|
-
for (const definition of Object.values(container)) {
|
|
1245
|
-
if (isRecord(definition))
|
|
1246
|
-
found.push(...findUntypedProperties(definition, prefix));
|
|
1247
|
-
}
|
|
1248
|
-
}
|
|
1249
|
-
for (const key of ["allOf", "anyOf", "oneOf"]) {
|
|
1250
|
-
const branches = schema[key];
|
|
1251
|
-
if (Array.isArray(branches)) {
|
|
1252
|
-
for (const branch of branches)
|
|
1253
|
-
found.push(...findUntypedProperties(branch, prefix));
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
return found;
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
// src/tools/mcp-prepare.ts
|
|
1260
|
-
function textBlock(text) {
|
|
1261
|
-
return [{ type: "text", text }];
|
|
1262
|
-
}
|
|
1263
|
-
function formatMcpResult(result, mode, toolName, errorHint) {
|
|
1264
|
-
if (result.ok) {
|
|
1265
|
-
if (mode === "none")
|
|
1266
|
-
return { content: [] };
|
|
1267
|
-
let serialized;
|
|
1268
|
-
try {
|
|
1269
|
-
serialized = JSON.stringify(result.data, null, 2);
|
|
1270
|
-
} catch {
|
|
1271
|
-
serialized = undefined;
|
|
1272
|
-
}
|
|
1273
|
-
if (serialized === undefined) {
|
|
1274
|
-
const safe = redact(result.data, { sensitiveKeys: /(?!)/ });
|
|
1275
|
-
return { content: textBlock(JSON.stringify(safe, null, 2)), structuredContent: safe };
|
|
1276
|
-
}
|
|
1277
|
-
return { content: textBlock(serialized), structuredContent: result.data };
|
|
1278
|
-
}
|
|
1279
|
-
return {
|
|
1280
|
-
content: textBlock(JSON.stringify(formatToolError(result, toolName, errorHint), null, 2)),
|
|
1281
|
-
isError: true
|
|
1282
|
-
};
|
|
1283
|
-
}
|
|
1284
|
-
function probeSchema(schema, io) {
|
|
1285
|
-
try {
|
|
1286
|
-
toJsonSchema(schema, io);
|
|
1287
|
-
return null;
|
|
1288
|
-
} catch (err) {
|
|
1289
|
-
return err instanceof Error ? err.message : String(err);
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
function resolveOutputSchema(outputSchema) {
|
|
1293
|
-
if (!outputSchema)
|
|
1294
|
-
return null;
|
|
1295
|
-
return { schema: outputSchema, mode: "direct" };
|
|
1296
|
-
}
|
|
1297
|
-
function reportIncompatible(message, policy, logger, failures) {
|
|
1298
|
-
if (policy === "throw") {
|
|
1299
|
-
failures.push(message);
|
|
1300
|
-
} else if (policy === "warn") {
|
|
1301
|
-
if (logger)
|
|
1302
|
-
logger.warn(`[stitchkit] ${message}`);
|
|
1303
|
-
else
|
|
1304
|
-
console.warn(`[stitchkit] ${message}`);
|
|
1305
|
-
}
|
|
1306
|
-
}
|
|
1307
|
-
function throwIfFailures(failures) {
|
|
1308
|
-
if (failures.length > 0) {
|
|
1309
|
-
throw new Error(`[stitchkit] ${failures.length} problem(s) with MCP tool schemas:
|
|
1310
|
-
- ${failures.join(`
|
|
1311
|
-
- `)}`);
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
function prepareMcpTool(mountable, config, validation, logger, failures, seen) {
|
|
1315
|
-
const policy = validation.policy ?? "throw";
|
|
1316
|
-
assertUniqueToolName(mountable.name, seen.has(mountable.name), "MCP tool name");
|
|
1317
|
-
seen.add(mountable.name);
|
|
1318
|
-
if (mountable.method.mcp) {
|
|
1319
|
-
validateMcpRoundPolicy(mountable, mountable.method.mcp, config.multiRound);
|
|
1320
|
-
}
|
|
1321
|
-
let inputJsonSchema;
|
|
1322
|
-
try {
|
|
1323
|
-
inputJsonSchema = buildToolPresentationSchema({
|
|
1324
|
-
paramsSchema: mountable.method.paramsSchema,
|
|
1325
|
-
inputSchema: mountable.method.inputSchema,
|
|
1326
|
-
extendSchema: mountable.shouldExtend && config.extend ? config.extend.schema : undefined,
|
|
1327
|
-
flattenUnionInput: config.flattenUnionInput,
|
|
1328
|
-
unrepresentable: "throw"
|
|
1329
|
-
});
|
|
1330
|
-
} catch (err) {
|
|
1331
|
-
reportIncompatible(`MCP tool "${mountable.name}" — input schema is not JSON Schema-compatible: ${err instanceof Error ? err.message : String(err)}`, policy, logger, failures);
|
|
1332
|
-
return null;
|
|
1333
|
-
}
|
|
1334
|
-
if (!isObjectPresentationSchema(inputJsonSchema)) {
|
|
1335
|
-
reportIncompatible(`MCP tool "${mountable.name}" — input must be an object schema; a union, discriminated union or scalar cannot be an MCP tool input (flatten it in the contract, or drop MCP from \`expose\`)`, policy, logger, failures);
|
|
1336
|
-
return null;
|
|
1337
|
-
}
|
|
1338
|
-
if (validation.requireTypedProperties) {
|
|
1339
|
-
const allowed = new Set(validation.allowUntyped ?? []);
|
|
1340
|
-
for (const untyped of findUntypedProperties(inputJsonSchema)) {
|
|
1341
|
-
const path = `${mountable.name}.${untyped.path}`;
|
|
1342
|
-
if (allowed.has(path))
|
|
1343
|
-
continue;
|
|
1344
|
-
const clue = untyped.description ? ` (only a description: "${untyped.description}")` : "";
|
|
1345
|
-
reportIncompatible(`MCP tool "${mountable.name}" — input property "${untyped.path}" carries no type, enum or $ref${clue}. ` + "A model is given no way to know what to send. Use `z.json()` for an arbitrary JSON value; use `allowUntyped` only when the presentation value is genuinely not representable as JSON Schema.", policy === "skip" ? "warn" : policy, logger, failures);
|
|
1346
|
-
}
|
|
1347
|
-
}
|
|
1348
|
-
if (validation.requirePortableFormats) {
|
|
1349
|
-
for (const finding of findNonPortableFormats(inputJsonSchema, validation.allowFormats)) {
|
|
1350
|
-
reportIncompatible(`MCP tool "${mountable.name}" — input property "${finding.path}" uses non-portable JSON Schema format "${finding.format}". ` + "Use a portable pattern/schema, or list the format in `allowFormats` only when every MCP client supports it.", policy === "skip" ? "warn" : policy, logger, failures);
|
|
1351
|
-
}
|
|
1352
|
-
}
|
|
1353
|
-
const resolved = resolveOutputSchema(mountable.method.outputSchema);
|
|
1354
|
-
if (!resolved)
|
|
1355
|
-
return { mountable, inputSchema: inputJsonSchema, outputMode: "none" };
|
|
1356
|
-
const outputError = probeSchema(resolved.schema, "output");
|
|
1357
|
-
if (outputError) {
|
|
1358
|
-
reportIncompatible(`MCP tool "${mountable.name}" — output schema is not JSON Schema-compatible: ${outputError}`, policy, logger, failures);
|
|
1359
|
-
return { mountable, inputSchema: inputJsonSchema, outputMode: "none" };
|
|
1360
|
-
}
|
|
1361
|
-
if (validation.requirePortableFormats) {
|
|
1362
|
-
for (const finding of findNonPortableFormats(toJsonSchema(resolved.schema, "output"), validation.allowFormats)) {
|
|
1363
|
-
reportIncompatible(`MCP tool "${mountable.name}" — output property "${finding.path}" uses non-portable JSON Schema format "${finding.format}". ` + "Use a portable pattern/schema, or list the format in `allowFormats` only when every MCP client supports it.", policy === "skip" ? "warn" : policy, logger, failures);
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
return {
|
|
1367
|
-
mountable,
|
|
1368
|
-
inputSchema: inputJsonSchema,
|
|
1369
|
-
outputSchema: resolved.schema,
|
|
1370
|
-
outputMode: resolved.mode
|
|
1371
|
-
};
|
|
1372
|
-
}
|
|
1373
|
-
function prepareMcpSurface(services, config = {}) {
|
|
1374
|
-
const serviceList = Array.isArray(services) ? services : [services];
|
|
1375
|
-
const tools = serviceList.flatMap((service) => collectTools(service, "MCP", config));
|
|
1376
|
-
return prepareMcpTools(tools, config);
|
|
1377
|
-
}
|
|
1378
|
-
function prepareMcpTools(tools, config = {}) {
|
|
1379
|
-
const seen = new Set;
|
|
1380
|
-
const failures = [];
|
|
1381
|
-
const prepared = [];
|
|
1382
|
-
for (const mountable of tools) {
|
|
1383
|
-
const tool2 = prepareMcpTool(mountable, config, config.schemaValidation ?? {}, config.logger, failures, seen);
|
|
1384
|
-
if (tool2) {
|
|
1385
|
-
Object.freeze(tool2.mountable);
|
|
1386
|
-
prepared.push(Object.freeze(tool2));
|
|
1387
|
-
}
|
|
1388
|
-
}
|
|
1389
|
-
throwIfFailures(failures);
|
|
1390
|
-
return Object.freeze(prepared);
|
|
1391
|
-
}
|
|
1392
|
-
function prepareMcpServerSurface(surface, config = {}) {
|
|
1393
|
-
const contractMountables = [];
|
|
1394
|
-
const definitions = [];
|
|
1395
|
-
const runtimeMountables = [];
|
|
1396
|
-
for (const entry of collectToolSurface({
|
|
1397
|
-
surface,
|
|
1398
|
-
transport: "MCP",
|
|
1399
|
-
extend: config.extend,
|
|
1400
|
-
flattenUnionInput: config.flattenUnionInput
|
|
1401
|
-
})) {
|
|
1402
|
-
if (entry.kind === "contract") {
|
|
1403
|
-
contractMountables.push(entry.mountable);
|
|
1404
|
-
} else {
|
|
1405
|
-
definitions.push(entry.definition);
|
|
1406
|
-
runtimeMountables.push(entry.mountable);
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
const contractTools = prepareMcpTools(contractMountables, config);
|
|
1410
|
-
const runtimeDescriptors = prepareMcpTools(runtimeMountables, {
|
|
1411
|
-
schemaValidation: config.schemaValidation,
|
|
1412
|
-
logger: config.logger,
|
|
1413
|
-
multiRound: config.multiRound
|
|
1414
|
-
});
|
|
1415
|
-
const descriptorsByName = new Map(runtimeDescriptors.map((descriptor) => [descriptor.mountable.name, descriptor]));
|
|
1416
|
-
const runtimeTools = [];
|
|
1417
|
-
for (const definition of definitions) {
|
|
1418
|
-
const descriptor = descriptorsByName.get(definition.name);
|
|
1419
|
-
if (descriptor)
|
|
1420
|
-
runtimeTools.push(Object.freeze({ definition, descriptor }));
|
|
1421
|
-
}
|
|
1422
|
-
return Object.freeze({ contractTools, runtimeTools: Object.freeze(runtimeTools) });
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
1384
|
// src/tools/mcp-trace.ts
|
|
1426
1385
|
import {
|
|
1427
1386
|
BAGGAGE_META_KEY,
|
|
@@ -2015,8 +1974,12 @@ function mountDownload(server, config) {
|
|
|
2015
1974
|
timeoutMs: config.timeoutMs
|
|
2016
1975
|
});
|
|
2017
1976
|
return textResult(JSON.stringify(result, null, 2));
|
|
2018
|
-
} catch (
|
|
2019
|
-
|
|
1977
|
+
} catch (error) {
|
|
1978
|
+
if (error instanceof DownloadOperationError) {
|
|
1979
|
+
return textResult(`Download failed [${error.code}]: ${error.message}`, true);
|
|
1980
|
+
}
|
|
1981
|
+
const normalized = normalizeFileToolError(error);
|
|
1982
|
+
return textResult(`Download failed [${normalized.code}]: ${normalized.message}`, true);
|
|
2020
1983
|
}
|
|
2021
1984
|
});
|
|
2022
1985
|
}
|
|
@@ -2037,8 +2000,9 @@ function mountUpload(server, config) {
|
|
|
2037
2000
|
try {
|
|
2038
2001
|
const uploaded = await runUploadOperation(config.files, path, config.upload);
|
|
2039
2002
|
return textResult(JSON.stringify(uploaded ?? null, null, 2));
|
|
2040
|
-
} catch (
|
|
2041
|
-
|
|
2003
|
+
} catch (error) {
|
|
2004
|
+
const normalized = normalizeFileToolError(error);
|
|
2005
|
+
return textResult(`Upload failed [${normalized.code}]: ${normalized.message}`, true);
|
|
2042
2006
|
}
|
|
2043
2007
|
});
|
|
2044
2008
|
}
|
|
@@ -2840,6 +2804,7 @@ export {
|
|
|
2840
2804
|
createToolLogger,
|
|
2841
2805
|
createToolkit,
|
|
2842
2806
|
defineAsyncOperation,
|
|
2807
|
+
defineAsyncOperationContract,
|
|
2843
2808
|
defineCliCommand,
|
|
2844
2809
|
defineDownloadTool,
|
|
2845
2810
|
defineRuntimeTool,
|