stitchkit 0.90.7 → 0.91.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/CHANGELOG.md +107 -0
- package/dist/agent-runtime-coding-tools.js +4 -4
- package/dist/agent-runtime-harness.js +6 -6
- package/dist/agent-runtime-sandbox.js +4 -4
- package/dist/agent-runtime.js +18 -17
- package/dist/application-grammy.js +1 -1
- package/dist/application.js +3 -3
- package/dist/cli.js +7 -7
- package/dist/contract/errors-factory.d.ts +12 -0
- package/dist/contract/errors-factory.d.ts.map +1 -1
- package/dist/contract/errors.d.ts +35 -1
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/index.d.ts +1 -1
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +4 -2
- package/dist/{index-nrqg8tks.js → index-0a349c33.js} +3 -3
- package/dist/index-0j3vcp5m.js +28 -0
- package/dist/{index-vy2nxwg8.js → index-137jjs4v.js} +2 -2
- package/dist/{index-2hryh65w.js → index-13cjtmrw.js} +54 -13
- package/dist/{index-7gqx71yd.js → index-13e1k7v9.js} +9 -3
- package/dist/{index-1bnkzq95.js → index-1qvdwnp2.js} +10 -3
- package/dist/{index-scs3f1eg.js → index-2vtcmrj9.js} +8 -2
- package/dist/{index-xbppf54a.js → index-6kyksz9y.js} +12 -7
- package/dist/{index-aj9geeez.js → index-709nh7z7.js} +37 -17
- package/dist/{index-zkcqhxx8.js → index-a11g2kry.js} +1 -1
- package/dist/{index-1txvygak.js → index-b971qjes.js} +1 -1
- package/dist/{index-tgh3ksfh.js → index-bh70rk41.js} +3 -3
- package/dist/{index-bva395we.js → index-bv7mytab.js} +2 -2
- package/dist/{index-s208wab5.js → index-bx6yq2m4.js} +23 -495
- package/dist/{index-83fafqw8.js → index-f4p4b8f6.js} +10 -11
- package/dist/index-kw9t5ayn.js +507 -0
- package/dist/{index-8pjqv3zh.js → index-mqhjmadk.js} +43 -21
- package/dist/{index-3yqgj323.js → index-qjeyx5t2.js} +2 -2
- package/dist/{index-gte38nbm.js → index-qmy6zq75.js} +1 -1
- package/dist/{index-zsdgd1tz.js → index-qrbdq8jk.js} +1 -1
- package/dist/{index-fqg5mfk7.js → index-ya611vx3.js} +4 -4
- package/dist/{index-640tz39q.js → index-zsqy3xfn.js} +5 -5
- package/dist/index.js +8 -6
- package/dist/internal/canonical-json.d.ts +20 -0
- package/dist/internal/canonical-json.d.ts.map +1 -0
- package/dist/internal/errors.d.ts +13 -4
- package/dist/internal/errors.d.ts.map +1 -1
- package/dist/node.js +6 -6
- package/dist/observability/bounded-logger.d.ts +2 -0
- package/dist/observability/bounded-logger.d.ts.map +1 -1
- package/dist/observability/index.js +7 -4
- package/dist/observability/sanitize.d.ts +15 -0
- package/dist/observability/sanitize.d.ts.map +1 -1
- package/dist/primitives.js +2 -2
- package/dist/realtime/rejected-frame.d.ts +6 -0
- package/dist/realtime/rejected-frame.d.ts.map +1 -1
- package/dist/remote.js +5 -5
- package/dist/server/implement.d.ts +33 -7
- package/dist/server/implement.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 +8 -8
- package/dist/testing/surface-manifest.d.ts +2 -1
- package/dist/testing/surface-manifest.d.ts.map +1 -1
- package/dist/testing.js +11 -27
- package/dist/tool-invoker.js +6 -6
- package/dist/tools/agent.d.ts +12 -0
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/cli-format.d.ts +1 -1
- package/dist/tools/connections/index.js +1 -1
- package/dist/tools/durability-context.d.ts +1 -1
- package/dist/tools/durability-context.d.ts.map +1 -1
- package/dist/tools/durability-port.d.ts +55 -0
- package/dist/tools/durability-port.d.ts.map +1 -0
- package/dist/tools/execute.d.ts +59 -1
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/mcp-catalog.d.ts +68 -0
- package/dist/tools/mcp-catalog.d.ts.map +1 -0
- package/dist/tools/mcp.d.ts +3 -0
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/mount.d.ts +2 -2
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/native-mcp.d.ts +3 -0
- package/dist/tools/native-mcp.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts +1 -1
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools.d.ts +5 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +99 -20
- package/dist/tracking.js +2 -2
- package/llms-full.txt +258 -8
- package/package.json +2 -2
- package/dist/index-7rkhw9ec.js +0 -34
package/dist/tools.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compareCodepoints,
|
|
3
|
+
serializeCanonicalJson
|
|
4
|
+
} from "./index-0j3vcp5m.js";
|
|
1
5
|
import {
|
|
2
6
|
ManagedFileError
|
|
3
7
|
} from "./index-actkayzn.js";
|
|
4
8
|
import {
|
|
5
9
|
signJwt,
|
|
6
10
|
verifyPkce
|
|
7
|
-
} from "./index-
|
|
11
|
+
} from "./index-bv7mytab.js";
|
|
8
12
|
import"./index-hr4tzbwq.js";
|
|
9
13
|
import {
|
|
10
14
|
DEFAULT_CORS_ALLOW_HEADERS,
|
|
@@ -14,16 +18,20 @@ import {
|
|
|
14
18
|
guardSignalCallback,
|
|
15
19
|
mediaTypeEssence,
|
|
16
20
|
reportSignalError
|
|
17
|
-
} from "./index-
|
|
21
|
+
} from "./index-709nh7z7.js";
|
|
18
22
|
import {
|
|
19
23
|
redact
|
|
20
|
-
} from "./index-
|
|
24
|
+
} from "./index-6kyksz9y.js";
|
|
21
25
|
import {
|
|
22
26
|
buildToolManifest,
|
|
23
27
|
describeToolCatalog,
|
|
24
28
|
mountAgent
|
|
25
|
-
} from "./index-
|
|
26
|
-
import
|
|
29
|
+
} from "./index-f4p4b8f6.js";
|
|
30
|
+
import {
|
|
31
|
+
AgentToolError,
|
|
32
|
+
createLocalStepDurability,
|
|
33
|
+
isAgentToolError
|
|
34
|
+
} from "./index-kw9t5ayn.js";
|
|
27
35
|
import {
|
|
28
36
|
argumentsDigest
|
|
29
37
|
} from "./index-a916km3r.js";
|
|
@@ -38,7 +46,7 @@ import {
|
|
|
38
46
|
} from "./index-4fpa9dy2.js";
|
|
39
47
|
import {
|
|
40
48
|
createToolInvoker
|
|
41
|
-
} from "./index-
|
|
49
|
+
} from "./index-bh70rk41.js";
|
|
42
50
|
import {
|
|
43
51
|
WaitTimeoutError,
|
|
44
52
|
createCli,
|
|
@@ -47,22 +55,22 @@ import {
|
|
|
47
55
|
fetchPinnedDocument,
|
|
48
56
|
readCapped,
|
|
49
57
|
runWaitOperation
|
|
50
|
-
} from "./index-
|
|
58
|
+
} from "./index-ya611vx3.js";
|
|
51
59
|
import"./index-sbdmyz75.js";
|
|
52
60
|
import {
|
|
53
61
|
collectToolSurface
|
|
54
|
-
} from "./index-
|
|
62
|
+
} from "./index-qrbdq8jk.js";
|
|
55
63
|
import {
|
|
56
64
|
collectTools,
|
|
57
65
|
createToolRunner,
|
|
58
66
|
formatToolError
|
|
59
|
-
} from "./index-
|
|
67
|
+
} from "./index-1qvdwnp2.js";
|
|
60
68
|
import {
|
|
61
69
|
ToolExecutionControlError,
|
|
62
70
|
executeToolMethod,
|
|
63
71
|
isToolExecutionControlError,
|
|
64
72
|
toolResultFromError
|
|
65
|
-
} from "./index-
|
|
73
|
+
} from "./index-mqhjmadk.js";
|
|
66
74
|
import {
|
|
67
75
|
getRequestContext,
|
|
68
76
|
getTraceId,
|
|
@@ -71,10 +79,10 @@ import {
|
|
|
71
79
|
import"./index-f9mb610r.js";
|
|
72
80
|
import {
|
|
73
81
|
normalizeError
|
|
74
|
-
} from "./index-
|
|
82
|
+
} from "./index-13cjtmrw.js";
|
|
75
83
|
import {
|
|
76
84
|
defineContract
|
|
77
|
-
} from "./index-
|
|
85
|
+
} from "./index-0a349c33.js";
|
|
78
86
|
import"./index-rxfy4cq7.js";
|
|
79
87
|
import {
|
|
80
88
|
ManagedFilePathSchema,
|
|
@@ -101,11 +109,13 @@ import {
|
|
|
101
109
|
presentationMetadata,
|
|
102
110
|
validateMcpRoundPolicy
|
|
103
111
|
} from "./index-fenaekmk.js";
|
|
104
|
-
import
|
|
112
|
+
import {
|
|
113
|
+
toJsonSchema
|
|
114
|
+
} from "./index-cby4ar3v.js";
|
|
105
115
|
import {
|
|
106
116
|
AppError,
|
|
107
117
|
STITCH_ERROR_STATUS
|
|
108
|
-
} from "./index-
|
|
118
|
+
} from "./index-2vtcmrj9.js";
|
|
109
119
|
import"./index-kzfs85xp.js";
|
|
110
120
|
import {
|
|
111
121
|
isRecord
|
|
@@ -946,6 +956,63 @@ function inlineMcpAppBundle(html) {
|
|
|
946
956
|
return html.replace(EXT_APPS_BUNDLE_PLACEHOLDER, () => bundle);
|
|
947
957
|
}
|
|
948
958
|
|
|
959
|
+
// src/tools/mcp-catalog.ts
|
|
960
|
+
import { createHash } from "node:crypto";
|
|
961
|
+
var MCP_CATALOG_META_KEY = "stitchkit/catalog";
|
|
962
|
+
var stampCache = new WeakMap;
|
|
963
|
+
function toolFingerprint(descriptor) {
|
|
964
|
+
const { mountable } = descriptor;
|
|
965
|
+
return {
|
|
966
|
+
name: mountable.name,
|
|
967
|
+
description: mountable.method.desc,
|
|
968
|
+
input: descriptor.inputSchema,
|
|
969
|
+
output: descriptor.outputSchema ? toJsonSchema(descriptor.outputSchema, "output", "any") : null,
|
|
970
|
+
annotations: mountable.method.annotations ?? null
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
function mcpCatalogStamp(surface) {
|
|
974
|
+
const cached = stampCache.get(surface);
|
|
975
|
+
if (cached)
|
|
976
|
+
return cached;
|
|
977
|
+
const descriptors = [
|
|
978
|
+
...surface.contractTools,
|
|
979
|
+
...surface.runtimeTools.map((entry) => entry.descriptor)
|
|
980
|
+
].sort((left, right) => compareCodepoints(left.mountable.name, right.mountable.name));
|
|
981
|
+
const bytes = serializeCanonicalJson(descriptors.map(toolFingerprint));
|
|
982
|
+
const stamp = {
|
|
983
|
+
digest: createHash("sha256").update(bytes).digest("hex").slice(0, 16),
|
|
984
|
+
tools: descriptors.length
|
|
985
|
+
};
|
|
986
|
+
stampCache.set(surface, stamp);
|
|
987
|
+
return stamp;
|
|
988
|
+
}
|
|
989
|
+
function mcpCatalogMeta(stamp) {
|
|
990
|
+
return { [MCP_CATALOG_META_KEY]: { ...stamp } };
|
|
991
|
+
}
|
|
992
|
+
function readMcpCatalogStamp(meta) {
|
|
993
|
+
if (!isRecord(meta))
|
|
994
|
+
return null;
|
|
995
|
+
const value = meta[MCP_CATALOG_META_KEY];
|
|
996
|
+
if (!isRecord(value))
|
|
997
|
+
return null;
|
|
998
|
+
const { digest, tools } = value;
|
|
999
|
+
if (typeof digest !== "string" || digest.length === 0)
|
|
1000
|
+
return null;
|
|
1001
|
+
if (typeof tools !== "number" || !Number.isInteger(tools) || tools < 0)
|
|
1002
|
+
return null;
|
|
1003
|
+
return { digest, tools };
|
|
1004
|
+
}
|
|
1005
|
+
function stampToolRegistration(config, stamp) {
|
|
1006
|
+
if (!stamp)
|
|
1007
|
+
return config;
|
|
1008
|
+
return { ...config, _meta: { ...config._meta, ...mcpCatalogMeta(stamp) } };
|
|
1009
|
+
}
|
|
1010
|
+
function stampToolResult(result, stamp) {
|
|
1011
|
+
if (!stamp)
|
|
1012
|
+
return result;
|
|
1013
|
+
return { ...result, _meta: { ...result._meta, ...mcpCatalogMeta(stamp) } };
|
|
1014
|
+
}
|
|
1015
|
+
|
|
949
1016
|
// src/tools/mcp-prepare.ts
|
|
950
1017
|
function textBlock(text) {
|
|
951
1018
|
return [{ type: "text", text }];
|
|
@@ -1283,7 +1350,7 @@ function mountPreparedRuntimeMcp(server, tools, config) {
|
|
|
1283
1350
|
"ui/resourceUri": definition.ui.resourceUri
|
|
1284
1351
|
};
|
|
1285
1352
|
}
|
|
1286
|
-
server.registerTool(definition.name, toolConfig, async (rawArgs, mcpContext) => runInMcpRequestContext(mcpContext, definition.name, async () => {
|
|
1353
|
+
server.registerTool(definition.name, stampToolRegistration(toolConfig, config.catalog), async (rawArgs, mcpContext) => stampToolResult(await runInMcpRequestContext(mcpContext, definition.name, async () => {
|
|
1287
1354
|
const args = isRecord(rawArgs) ? rawArgs : {};
|
|
1288
1355
|
const runTool = (tool, toolArgs, roundContext) => executeToolMethod(tool.method, definition.name, toolArgs, { ...config.context, ...roundContext, source: "mcp" }, config.hooks, config.lifecycle, config.coerceJsonArgs ?? true, config.onOutputStrip ? (paths) => config.onOutputStrip?.(definition.name, paths) : undefined);
|
|
1289
1356
|
const round = await resolveMcpRound({
|
|
@@ -1323,7 +1390,7 @@ function mountPreparedRuntimeMcp(server, tools, config) {
|
|
|
1323
1390
|
throw new Error("[stitchkit] Runtime MCP presenter invariant failed");
|
|
1324
1391
|
}
|
|
1325
1392
|
return presentedResult.data;
|
|
1326
|
-
}));
|
|
1393
|
+
}), config.catalog));
|
|
1327
1394
|
}
|
|
1328
1395
|
}
|
|
1329
1396
|
|
|
@@ -1343,7 +1410,10 @@ function mountMcp(mcpServer, services, config = {}) {
|
|
|
1343
1410
|
schemaValidation: config.schemaValidation,
|
|
1344
1411
|
logger: config.logger
|
|
1345
1412
|
});
|
|
1346
|
-
mountPreparedMcp(mcpServer, prepared,
|
|
1413
|
+
mountPreparedMcp(mcpServer, prepared, {
|
|
1414
|
+
...config,
|
|
1415
|
+
catalog: mcpCatalogStamp({ contractTools: prepared, runtimeTools: [] })
|
|
1416
|
+
});
|
|
1347
1417
|
}
|
|
1348
1418
|
function mountPreparedMcp(mcpServer, prepared, config = {}) {
|
|
1349
1419
|
const runTool = createToolRunner({
|
|
@@ -1373,7 +1443,7 @@ function mountPreparedMcp(mcpServer, prepared, config = {}) {
|
|
|
1373
1443
|
"ui/resourceUri": mountable.method.ui.resourceUri
|
|
1374
1444
|
};
|
|
1375
1445
|
}
|
|
1376
|
-
mcpServer.registerTool(mountable.name, toolConfig, async (rawArgs, mcpContext) => runInMcpRequestContext(mcpContext, mountable.name, async () => {
|
|
1446
|
+
mcpServer.registerTool(mountable.name, stampToolRegistration(toolConfig, config.catalog), async (rawArgs, mcpContext) => stampToolResult(await runInMcpRequestContext(mcpContext, mountable.name, async () => {
|
|
1377
1447
|
const args = isRecord(rawArgs) ? rawArgs : {};
|
|
1378
1448
|
try {
|
|
1379
1449
|
const round = await resolveMcpRound({
|
|
@@ -1392,7 +1462,7 @@ function mountPreparedMcp(mcpServer, prepared, config = {}) {
|
|
|
1392
1462
|
} catch (err) {
|
|
1393
1463
|
return formatMcpResult(toolResultFromError(err), "none", mountable.name, config.errorHint);
|
|
1394
1464
|
}
|
|
1395
|
-
}));
|
|
1465
|
+
}), config.catalog));
|
|
1396
1466
|
}
|
|
1397
1467
|
}
|
|
1398
1468
|
function buildMcpServer(config, auth) {
|
|
@@ -1454,7 +1524,9 @@ function buildMcpServerFromPrepared(config, auth, prepared) {
|
|
|
1454
1524
|
};
|
|
1455
1525
|
const server = new McpServer(config.serverInfo, serverOptions);
|
|
1456
1526
|
const context = config.context?.(auth);
|
|
1527
|
+
const catalog = mcpCatalogStamp(prepared);
|
|
1457
1528
|
mountPreparedMcp(server, prepared.contractTools, {
|
|
1529
|
+
catalog,
|
|
1458
1530
|
context,
|
|
1459
1531
|
hooks: config.hooks,
|
|
1460
1532
|
lifecycle: config.lifecycle,
|
|
@@ -1473,6 +1545,7 @@ function buildMcpServerFromPrepared(config, auth, prepared) {
|
|
|
1473
1545
|
}
|
|
1474
1546
|
});
|
|
1475
1547
|
mountPreparedRuntimeMcp(server, prepared.runtimeTools, {
|
|
1548
|
+
catalog,
|
|
1476
1549
|
context,
|
|
1477
1550
|
hooks: config.hooks,
|
|
1478
1551
|
lifecycle: config.lifecycle,
|
|
@@ -2683,6 +2756,7 @@ export {
|
|
|
2683
2756
|
validateMcpSchemas,
|
|
2684
2757
|
summarizeTransports,
|
|
2685
2758
|
resolveMedia,
|
|
2759
|
+
readMcpCatalogStamp,
|
|
2686
2760
|
protectedResourceMetadataUrl,
|
|
2687
2761
|
oauthProtectedResourceRoute,
|
|
2688
2762
|
mountWait,
|
|
@@ -2693,9 +2767,11 @@ export {
|
|
|
2693
2767
|
mountMcp,
|
|
2694
2768
|
mountDownload,
|
|
2695
2769
|
mountAgent,
|
|
2770
|
+
mcpCatalogMeta,
|
|
2696
2771
|
listToolNames,
|
|
2697
2772
|
listContractToolNames,
|
|
2698
2773
|
isToolExecutionControlError,
|
|
2774
|
+
isAgentToolError,
|
|
2699
2775
|
inlineMcpAppBundle,
|
|
2700
2776
|
flattenToolJsonSchema,
|
|
2701
2777
|
findUntypedProperties,
|
|
@@ -2718,6 +2794,7 @@ export {
|
|
|
2718
2794
|
createRuntimeToolFactory,
|
|
2719
2795
|
createMcpHttpRoute,
|
|
2720
2796
|
createMcpHandler,
|
|
2797
|
+
createLocalStepDurability,
|
|
2721
2798
|
createCli,
|
|
2722
2799
|
createAsyncOperationSnapshotSchema,
|
|
2723
2800
|
composeToolLifecycle,
|
|
@@ -2737,6 +2814,8 @@ export {
|
|
|
2737
2814
|
PORTABLE_JSON_SCHEMA_FORMATS,
|
|
2738
2815
|
McpMediaContentSchema,
|
|
2739
2816
|
McpAnnotationsSchema,
|
|
2817
|
+
MCP_CATALOG_META_KEY,
|
|
2740
2818
|
EXT_APPS_BUNDLE_PLACEHOLDER,
|
|
2741
|
-
AsyncOperationCancelResultSchema
|
|
2819
|
+
AsyncOperationCancelResultSchema,
|
|
2820
|
+
AgentToolError
|
|
2742
2821
|
};
|
package/dist/tracking.js
CHANGED
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
} from "./index-3xwxfj4z.js";
|
|
4
4
|
import {
|
|
5
5
|
defineContract
|
|
6
|
-
} from "./index-
|
|
6
|
+
} from "./index-0a349c33.js";
|
|
7
7
|
import"./index-rxfy4cq7.js";
|
|
8
8
|
import"./index-6k1937bx.js";
|
|
9
9
|
import"./index-zcgf3gqf.js";
|
|
10
|
-
import"./index-
|
|
10
|
+
import"./index-2vtcmrj9.js";
|
|
11
11
|
import"./index-kzfs85xp.js";
|
|
12
12
|
import"./index-77fekveh.js";
|
|
13
13
|
|
package/llms-full.txt
CHANGED
|
@@ -66,11 +66,11 @@ own, recorded as an ADR.
|
|
|
66
66
|
| `stitchkit/geo` | server (Bun or Node) | evolving | managed GeoIP reader generations, last-known-good reload and the optional MaxMind adapter |
|
|
67
67
|
| `stitchkit/oauth` | browser **and** server | evolving | provider-neutral Authorization Code + PKCE request and one-shot callback transaction mechanics |
|
|
68
68
|
| `stitchkit/google` | server (Bun or Node) | evolving | optional Google code exchange and verified OIDC identity adapter |
|
|
69
|
-
| `stitchkit/observability` | server | stable<br>_redefined in 1 of the
|
|
69
|
+
| `stitchkit/observability` | server | stable<br>_redefined in 1 of the 36 minors since 0.56.2, most recently 0.83.0_ | request/tool event projections — `createObservability`, trace context, sanitisation |
|
|
70
70
|
| `stitchkit/testing` | tests on Bun or Node | stable | in-process generated clients over a real Fetch handler, plus the store and managed-resource conformance kits |
|
|
71
71
|
| `stitchkit/declaration` | browser + build and deployment tooling (Bun or Node) | evolving | `ProjectDeclarationSchema` — the one machine-readable statement a repository makes about itself |
|
|
72
72
|
| `stitchkit/react` | browser + server rendering | stable | `createCursorQuery`, `createCacheBridge`, QueryClient and `ApiError` retry policy |
|
|
73
|
-
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 20 of the
|
|
73
|
+
| `stitchkit/agent-runtime` | server | evolving<br>_redefined in 20 of the 36 minors since 0.56.2, most recently 0.89.0_ | optional durable conversation/run loop, history, models, prompts, fencing and events |
|
|
74
74
|
| `stitchkit/agent-runtime/sandbox` | server | evolving | optional Linux Bubblewrap sessions, durable workspaces and a host HTTP credential gateway; see [sandbox guide](sandbox.md) |
|
|
75
75
|
| `stitchkit/agent-runtime/testing` | tests on Bun or Node | evolving | credential-free replay, scripted provider faults and deterministic race controls |
|
|
76
76
|
| `stitchkit/agent-runtime/harness` | server | evolving | resource-aware process-local facade over the canonical Agent runtime; supervision stays outside |
|
|
@@ -80,7 +80,7 @@ own, recorded as an ADR.
|
|
|
80
80
|
| `stitchkit/agent-runtime/sqlite/bun` | server (Bun) | evolving | durable built-in SQLite store for the agent runtime |
|
|
81
81
|
| `stitchkit/agent-runtime/sqlite/node` | server (Node ≥ 22.5) | evolving | durable built-in SQLite store for the agent runtime |
|
|
82
82
|
| `stitchkit-tui` | terminal (Bun) | evolving | optional official OpenTUI host over a caller-composed headless runtime |
|
|
83
|
-
| `stitchkit/application` | browser + server | evolving<br>_redefined in 7 of the
|
|
83
|
+
| `stitchkit/application` | browser + server | evolving<br>_redefined in 7 of the 36 minors since 0.56.2, most recently 0.83.0_ | managed resource graph, readiness, admission, schedules, subtree restart and bounded shutdown |
|
|
84
84
|
| `stitchkit/application/grammy` | server | evolving | isolated grammY polling and webhook lifecycle adapters |
|
|
85
85
|
| `stitchkit/application/opentelemetry` | server | evolving | maps application snapshots onto an injected OpenTelemetry `Meter` |
|
|
86
86
|
| `stitchkit/application/schemas` | browser + server | evolving | the application's snapshot, health and shutdown schemas alone, without the kernel |
|
|
@@ -1252,6 +1252,43 @@ scope** — the same prefix under two different group scopes is legal (their URL
|
|
|
1252
1252
|
are separated by `scopePrefixes`, and a genuine path clash is the router's own
|
|
1253
1253
|
construction error).
|
|
1254
1254
|
|
|
1255
|
+
#### A dev stand that survives half a feature — `onMissingHandler`
|
|
1256
|
+
|
|
1257
|
+
Adding an endpoint is two edits by construction: the contract, then the handler.
|
|
1258
|
+
An editor saves one file at a time and a watcher restarts on the first save, so
|
|
1259
|
+
between the two the application refuses to start — and on a shared dev stand that
|
|
1260
|
+
is everyone's stand, not just yours. A project rule ("make both edits at once")
|
|
1261
|
+
cannot outrun the filesystem.
|
|
1262
|
+
|
|
1263
|
+
```ts
|
|
1264
|
+
implementRegistry(apiContractRegistry, handlers, {
|
|
1265
|
+
onMissingHandler: process.env.NODE_ENV === 'production' ? 'throw' : 'stub',
|
|
1266
|
+
})
|
|
1267
|
+
```
|
|
1268
|
+
|
|
1269
|
+
With `'stub'` the application starts, the unimplemented endpoint answers `501` and
|
|
1270
|
+
names itself in the log at every mount, so it cannot be forgotten. Both save
|
|
1271
|
+
orders are covered: a contract ahead of its handler, and a handler ahead of its
|
|
1272
|
+
contract. The option is accepted by `implement`, `implementRegistry`,
|
|
1273
|
+
`createImplement`, `createScopedImplement`, `createImplementRegistry` and
|
|
1274
|
+
`createScopedImplementRegistry`.
|
|
1275
|
+
|
|
1276
|
+
Three things it deliberately does not do. It stays `'throw'` by default, and
|
|
1277
|
+
`'throw'` is the only right answer in production: a release is atomic, so there is
|
|
1278
|
+
no window to protect, and a surface with a hole must not ship. It does not relax
|
|
1279
|
+
the **compile-time** exactness check — that lives in the generic constraint, your
|
|
1280
|
+
editor still shows the missing handler, and a watcher that only transpiles is
|
|
1281
|
+
precisely why the runtime refusal was the one that bit. And it does not stub a
|
|
1282
|
+
streaming multipart endpoint, which needs `defineMultipartStream()` receivers; the
|
|
1283
|
+
stand still stops on one of those.
|
|
1284
|
+
|
|
1285
|
+
The stub answers `NOT_IMPLEMENTED` with status `501` everywhere — over HTTP and on
|
|
1286
|
+
a tool call that crosses a process boundary alike, because the code is registered
|
|
1287
|
+
in `STITCH_ERROR_STATUS`. One consequence follows from that and is worth knowing:
|
|
1288
|
+
a stubbed endpoint keeps its `expose`, so on a dev stand it is advertised to a
|
|
1289
|
+
model as a real tool that always refuses. The refusal is not retryable, so a model
|
|
1290
|
+
stops rather than loops; it still costs one turn to find out.
|
|
1291
|
+
|
|
1255
1292
|
The registry is intentionally flat: every key must point to one concrete
|
|
1256
1293
|
`defineContract()` result. Composed namespace arrays are mounted explicitly with
|
|
1257
1294
|
`implement()` because they do not have a one-key-to-one-handler-map boundary.
|
|
@@ -3446,6 +3483,138 @@ finite `surfaces` registry when identities share a provably immutable surface.
|
|
|
3446
3483
|
Stitchkit never advertises list-change or subscription capabilities
|
|
3447
3484
|
without an implementation.
|
|
3448
3485
|
|
|
3486
|
+
### Knowing your catalog is stale — the catalog stamp
|
|
3487
|
+
|
|
3488
|
+
A client lists the tools once and keeps that list. If the server's contract
|
|
3489
|
+
moves afterwards, the client's copy is wrong and *nothing tells it so*: calls
|
|
3490
|
+
are refused, and from inside the client a refusal looks like a broken server.
|
|
3491
|
+
This is the most expensive shape a contract disagreement takes, because the
|
|
3492
|
+
party able to fix it is the one with no evidence.
|
|
3493
|
+
|
|
3494
|
+
Every advertised tool and every tool result therefore carries the fingerprint of
|
|
3495
|
+
the catalog it came from:
|
|
3496
|
+
|
|
3497
|
+
```ts
|
|
3498
|
+
import { readMcpCatalogStamp } from 'stitchkit/tools';
|
|
3499
|
+
|
|
3500
|
+
const listed = await client.listTools();
|
|
3501
|
+
let known = readMcpCatalogStamp(listed.tools[0]?._meta); // { digest, tools }
|
|
3502
|
+
|
|
3503
|
+
const result = await client.callTool({ name: 'record_journal', arguments });
|
|
3504
|
+
const live = readMcpCatalogStamp(result._meta);
|
|
3505
|
+
if (live && known && live.digest !== known.digest) {
|
|
3506
|
+
// The catalog moved under us. Re-list before blaming the result.
|
|
3507
|
+
known = readMcpCatalogStamp((await client.listTools()).tools[0]?._meta);
|
|
3508
|
+
}
|
|
3509
|
+
```
|
|
3510
|
+
|
|
3511
|
+
The comparison costs nothing and runs on a response you were already receiving —
|
|
3512
|
+
including a refusal, which is exactly when a stale catalog is the likeliest
|
|
3513
|
+
explanation. `notifications/tools/list_changed` is the push-shaped answer to the
|
|
3514
|
+
same problem and `createMcpHandler` cannot send it: it is stateless by
|
|
3515
|
+
construction, so there is no retained session to push down.
|
|
3516
|
+
|
|
3517
|
+
The digest covers each advertised tool's name, description, input schema, output
|
|
3518
|
+
schema and annotations, and nothing else — it moves when the contract moves and
|
|
3519
|
+
not when a handler does. Raw SDK registrations stay consumer-owned and are
|
|
3520
|
+
outside it.
|
|
3521
|
+
|
|
3522
|
+
### Shaping arguments before validation — `beforeToolCall`
|
|
3523
|
+
|
|
3524
|
+
`beforeToolCall` sees the arguments as they arrived and may **replace** them:
|
|
3525
|
+
|
|
3526
|
+
```ts
|
|
3527
|
+
hooks: {
|
|
3528
|
+
beforeToolCall: async ({ args }) =>
|
|
3529
|
+
typeof args.id === 'string' && args.id.startsWith('@')
|
|
3530
|
+
? { ...args, id: await resolveReference(args.id) }
|
|
3531
|
+
: undefined, // undefined / null / nothing → unchanged
|
|
3532
|
+
}
|
|
3533
|
+
```
|
|
3534
|
+
|
|
3535
|
+
The replacement is validated by the contract schema like any other input, so this
|
|
3536
|
+
shapes a call and never bypasses the schema — a hook returning something the
|
|
3537
|
+
contract refuses produces an ordinary `VALIDATION_ERROR`. It runs before the
|
|
3538
|
+
params/input split, before argument coercion and before both schemas, which is
|
|
3539
|
+
why a field declared `z.string()` can receive a reference and still be a string by
|
|
3540
|
+
the time it is checked. `lifecycle.beforeHandle` is too late for this: the schemas
|
|
3541
|
+
have already answered.
|
|
3542
|
+
|
|
3543
|
+
The audit trail is unaffected. `afterToolCall` reports what the caller sent as
|
|
3544
|
+
`args` and the replacement separately as `effectiveArgs`, so a rewrite is visible
|
|
3545
|
+
as a rewrite rather than by overwriting the evidence.
|
|
3546
|
+
|
|
3547
|
+
### Knowing the outcome of a call — `afterToolCall` and `toolCallId`
|
|
3548
|
+
|
|
3549
|
+
`afterToolCall` receives the whole `ToolResult` — `{ok: true, data}` or
|
|
3550
|
+
`{ok: false, code, …}` — on every exit, including a validation failure that never
|
|
3551
|
+
reached the handler, and on all three transports. It is the answer to "did this
|
|
3552
|
+
call fail"; deriving that from the serialized error text instead is the mistake
|
|
3553
|
+
this section exists to prevent, and it is a mistake that does not announce itself:
|
|
3554
|
+
one consumer had five places asking the question and getting five answers.
|
|
3555
|
+
|
|
3556
|
+
To correlate an outcome with the call the model made, read `context.toolCallId`:
|
|
3557
|
+
|
|
3558
|
+
```ts
|
|
3559
|
+
hooks: {
|
|
3560
|
+
afterToolCall: ({ context, result }) => {
|
|
3561
|
+
loop.record(context.toolCallId, result.ok ? 'ok' : result.code);
|
|
3562
|
+
},
|
|
3563
|
+
}
|
|
3564
|
+
```
|
|
3565
|
+
|
|
3566
|
+
Two exits are deliberately **not** reported: a runtime tool presenter that throws
|
|
3567
|
+
on the agent surface (the AI SDK calls it after `execute` has already returned)
|
|
3568
|
+
and `ToolExecutionControlError`, which is a run being stopped rather than a tool
|
|
3569
|
+
failing — check it with `isToolExecutionControlError`.
|
|
3570
|
+
|
|
3571
|
+
### Restartable tool bodies — `durability`
|
|
3572
|
+
|
|
3573
|
+
A tool that starts slow work and must report exactly once, including after a host
|
|
3574
|
+
restart, can keep that bookkeeping **inside** the tool:
|
|
3575
|
+
|
|
3576
|
+
```ts
|
|
3577
|
+
mountAgent(services, {
|
|
3578
|
+
durability: (toolCallId, signal) => myLedgerFor(toolCallId, signal),
|
|
3579
|
+
});
|
|
3580
|
+
|
|
3581
|
+
// in the handler
|
|
3582
|
+
const asset = await ctx.step('render', () => renderAsset(ctx.input));
|
|
3583
|
+
await ctx.waitFor({ event: 'render-done', id: assetId });
|
|
3584
|
+
```
|
|
3585
|
+
|
|
3586
|
+
`ToolDurability` is the port a tool body sees — `step`, `sleep`, `waitFor`. You
|
|
3587
|
+
do not have to implement it: the engine ships from `stitchkit/tools`, and it needs
|
|
3588
|
+
a ledger of two methods over storage you already have:
|
|
3589
|
+
|
|
3590
|
+
```ts
|
|
3591
|
+
import { createLocalStepDurability, type StepDurabilityLedger } from 'stitchkit/tools';
|
|
3592
|
+
|
|
3593
|
+
const ledger: StepDurabilityLedger = {
|
|
3594
|
+
appendEvent: (event) => db.durabilityEvents.insert(event), // returns the row with `seq`
|
|
3595
|
+
readEvents: ({ conversationId, fromSeq, toSeq }) => db.durabilityEvents.page(…),
|
|
3596
|
+
};
|
|
3597
|
+
|
|
3598
|
+
mountAgent(services, {
|
|
3599
|
+
durability: (toolCallId, signal) =>
|
|
3600
|
+
createLocalStepDurability({ store: ledger, conversationId, runId: toolCallId, signal }),
|
|
3601
|
+
});
|
|
3602
|
+
```
|
|
3603
|
+
|
|
3604
|
+
That gives replay, absolute deadlines and decode refusal without writing them.
|
|
3605
|
+
The port is there for an application that already has its own ledger engine.
|
|
3606
|
+
|
|
3607
|
+
One thing the port deliberately leaves out: **delivery**. `waitFor` parks until
|
|
3608
|
+
`{ event, id }` arrives, and a tool body has no business delivering to itself —
|
|
3609
|
+
so `deliver` lives on the engine object you constructed, not in the handler
|
|
3610
|
+
context. Whatever produces the event (a webhook, a queue consumer, a cron) calls
|
|
3611
|
+
`engine.deliver({ event, id, payload })` against the same ledger; the parked
|
|
3612
|
+
`waitFor` resolves, and a `waitFor` that starts after the delivery returns at
|
|
3613
|
+
once. Taking `agent-runtime` is not required and nothing of the runtime proper
|
|
3614
|
+
enters the `stitchkit/tools` graph. An application that *does* run on
|
|
3615
|
+
`agent-runtime` gets durability through the run context and needs no option;
|
|
3616
|
+
where both exist the runtime's wins, because the run is recorded in its ledger.
|
|
3617
|
+
|
|
3449
3618
|
### Typed MCP call metadata
|
|
3450
3619
|
|
|
3451
3620
|
Contract handlers, runtime-tool handlers/factories, lifecycle and tool hooks all
|
|
@@ -5475,7 +5644,7 @@ or it keeps the failed attempt's partial text on screen.
|
|
|
5475
5644
|
usage, files and sources do not count;
|
|
5476
5645
|
- `tool-status` is transient lifecycle presentation with JSON-safe input on
|
|
5477
5646
|
start and output on completion. A mounted typed failure carries the same safe
|
|
5478
|
-
`{ error, details?, _hint? }` envelope as the durable result; an unknown
|
|
5647
|
+
`{ error, retryable, details?, _hint? }` envelope as the durable result; an unknown
|
|
5479
5648
|
internal cause remains generic and stays in local observability only;
|
|
5480
5649
|
- `terminal` follows the winning terminal CAS.
|
|
5481
5650
|
|
|
@@ -9867,7 +10036,8 @@ core never models them (ADR 0002). But stitchkit itself emits a set of its own:
|
|
|
9867
10036
|
`CONFLICT`, `RATE_LIMITED`, `VALIDATION_ERROR`, `FILE_INVALID_PATH`,
|
|
9868
10037
|
`FILE_OUTSIDE_ROOT`, `FILE_NOT_FOUND`, `FILE_NOT_REGULAR`,
|
|
9869
10038
|
`FILE_INSPECTION_REJECTED`, `FILE_TOO_LARGE`, `FILE_EXISTS`,
|
|
9870
|
-
`REALTIME_CONTRACT_VIOLATION`, `APPLICATION_NOT_ACCEPTING`, `
|
|
10039
|
+
`REALTIME_CONTRACT_VIOLATION`, `APPLICATION_NOT_ACCEPTING`, `NOT_IMPLEMENTED`,
|
|
10040
|
+
`WAIT_TIMEOUT`,
|
|
9871
10041
|
`WAIT_FAILED`, `DOWNLOAD_NOT_FOUND`, `VIEW_HTTP_ERROR`,
|
|
9872
10042
|
`OPERATION_NOT_SUCCEEDED`, `STREAM_ITEM_INVALID`, `STREAM_FRAME_TOO_LARGE`,
|
|
9873
10043
|
`STREAM_TERMINAL_MISSING`, `STREAM_LIFETIME_EXCEEDED`,
|
|
@@ -9995,7 +10165,7 @@ whether the code declares `details`:
|
|
|
9995
10165
|
| code **with** a `details` schema | the declared message | no text at all — only `details` and `hint` |
|
|
9996
10166
|
| code **without** one | the declared message | the same text, delivered as `details.message` |
|
|
9997
10167
|
|
|
9998
|
-
The model-facing envelope is `{ error, details?, _hint? }`; for a code with no
|
|
10168
|
+
The model-facing envelope is `{ error, retryable, details?, _hint? }`; for a code with no
|
|
9999
10169
|
details schema the framework fills `details` with `{ message }`, so declaring a
|
|
10000
10170
|
message changes what the model reads there — it used to be the code itself. Put
|
|
10001
10171
|
anything the model must reliably read in `details` or `hint`, not in `message`.
|
|
@@ -10967,6 +11137,24 @@ applied. Active trace, span, user and dimensions are reserved framework fields;
|
|
|
10967
11137
|
caller data cannot overwrite them. A throwing getter, cyclic value or failing
|
|
10968
11138
|
sink never escapes into application code.
|
|
10969
11139
|
|
|
11140
|
+
One string bound for the whole record cuts an error's stack from the bottom —
|
|
11141
|
+
which is exactly where your own frames are. Raise the ceiling for that key alone,
|
|
11142
|
+
wherever it sits (`stack` at the top of one record and under `errorDetail` in the
|
|
11143
|
+
next is the same field), without touching the bound for everything else:
|
|
11144
|
+
|
|
11145
|
+
```ts
|
|
11146
|
+
createBoundedLogger({
|
|
11147
|
+
sink,
|
|
11148
|
+
bounds: { stringLength: 4_000, stringLengthByKey: { stack: 32_000 } },
|
|
11149
|
+
})
|
|
11150
|
+
// or directly: redact(value, { maxStringLength: 4_000, maxStringLengthByKey: { stack: 32_000 } })
|
|
11151
|
+
```
|
|
11152
|
+
|
|
11153
|
+
Keyed by name, not by path — the question is "what is this field", not "where is
|
|
11154
|
+
it". The entry ceiling (`entryBytes`) keeps the last word: a record that does not
|
|
11155
|
+
fit still collapses, but the decision is made for the record, not by cutting one
|
|
11156
|
+
field short in advance.
|
|
11157
|
+
|
|
10970
11158
|
|
|
10971
11159
|
==============================================================================
|
|
10972
11160
|
# Guide: Testing & deployment (docs/guide/testing-and-deployment.md)
|
|
@@ -11935,6 +12123,23 @@ analytics policy belong to the application.
|
|
|
11935
12123
|
|
|
11936
12124
|
# Upgrading stitchkit
|
|
11937
12125
|
|
|
12126
|
+
## Released migration: 0.91.0
|
|
12127
|
+
|
|
12128
|
+
1. `STITCH_ERROR_STATUS` gained `NOT_IMPLEMENTED` (501). An **exhaustive** error
|
|
12129
|
+
vocabulary must name it — without the row `defineErrors({ mapping: { exhaustive:
|
|
12130
|
+
true } })` stops compiling and then throws at startup. Nothing else in this
|
|
12131
|
+
release requires action.
|
|
12132
|
+
|
|
12133
|
+
```ts
|
|
12134
|
+
// before
|
|
12135
|
+
map: { …, WAIT_TIMEOUT: 'timeout' }
|
|
12136
|
+
// after
|
|
12137
|
+
map: { …, NOT_IMPLEMENTED: 'not_implemented', WAIT_TIMEOUT: 'timeout' }
|
|
12138
|
+
```
|
|
12139
|
+
|
|
12140
|
+
The code is thrown only by the new dev-only `onMissingHandler: 'stub'` policy;
|
|
12141
|
+
a vocabulary that is not exhaustive is unaffected.
|
|
12142
|
+
|
|
11938
12143
|
## Released migration: 0.90.0
|
|
11939
12144
|
|
|
11940
12145
|
1. `stitchkit.watch.value` is a discriminated union on `kind` — `full`, `delta`,
|
|
@@ -15666,6 +15871,7 @@ from the root `stitchkit`.
|
|
|
15666
15871
|
| `STITCH_ERROR_STATUS` | const | `code → HTTP status` map for stitchkit's own error codes — [guide](../guide/auth-and-errors.md#stitch-codes-vs-your-codes) |
|
|
15667
15872
|
| `StitchErrorCode` | _type_ | a code stitchkit itself emits (`keyof STITCH_ERROR_STATUS`) |
|
|
15668
15873
|
| `isStitchErrorCode` | function | type guard — is a code one of stitchkit's own? |
|
|
15874
|
+
| `isRetryableStatus` | function | whether a status class means "the same call could work later" — `408`, `429`, `502`, `503`, `504`, and deliberately not every `5xx` |
|
|
15669
15875
|
|
|
15670
15876
|
### Pagination
|
|
15671
15877
|
|
|
@@ -15777,6 +15983,8 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
15777
15983
|
| `implementRegistry` | function | bind one exact contract registry to one exact backend handler registry |
|
|
15778
15984
|
| `createImplementRegistry` | function | context-typed factory for `implementRegistry` |
|
|
15779
15985
|
| `ImplementationRegistry` | _type_ | flat literal registry of concrete contracts accepted by `implementRegistry` |
|
|
15986
|
+
| `ImplementOptions` | _type_ | `{ onMissingHandler }` — how a binding treats a contract endpoint with no handler |
|
|
15987
|
+
| `MissingHandlerPolicy` | _type_ | `'throw'` (default) or `'stub'`, the dev-stand policy that mounts a `501` instead of refusing to start |
|
|
15780
15988
|
| `KeyedServices` | _type_ | registry result: the mount-ordered `ServiceDef[]` carrying the same services under `.byKey` |
|
|
15781
15989
|
| `RegistryHandlers` | _type_ | exact backend handler registry inferred from a contract registry |
|
|
15782
15990
|
| `ExactRegistryHandlers` | _type_ | fail-first handler shape that rejects extra registry and endpoint keys |
|
|
@@ -16730,7 +16938,7 @@ audit event. See the [Observability guide](../guide/observability.md).
|
|
|
16730
16938
|
| `DimensionCollision` / `SetRequestDimensionsOptions` | _type_ | explicit overwrite, preserve or error policy for dimension keys |
|
|
16731
16939
|
| `createBoundedLogger` | function | decorate a `StitchLogger` with request context, shared sanitisation, redaction and total bounds |
|
|
16732
16940
|
| `DEFAULT_REDACT_PATHS` | constant | baseline credential/token paths added to the sanitizer's sensitive-key policy |
|
|
16733
|
-
| `BoundedLoggerBounds` / `BoundedLoggerOptions` | _type_ | per-value and total record ceilings plus sink/redaction configuration |
|
|
16941
|
+
| `BoundedLoggerBounds` / `BoundedLoggerOptions` | _type_ | per-value and total record ceilings (`stringLengthByKey` raises the string bound for named keys such as `stack`) plus sink/redaction configuration |
|
|
16734
16942
|
| `RequestEvent` | _type_ | the normalised audit event handed to the sink; opt-in HTTP cancellation rows carry `outcome: 'cancelled'` |
|
|
16735
16943
|
| `ObservabilityConfig` | _type_ | independent request and tool sink configuration |
|
|
16736
16944
|
| `Observability` | _type_ | `{ request?, toolCall, getStatus(), flush(bound?): Promise<boolean>, close(bound?) }` with bounded sink lifecycle |
|
|
@@ -16784,7 +16992,7 @@ audit event. See the [Observability guide](../guide/observability.md).
|
|
|
16784
16992
|
| `truncatePreview` | function | cap a value by serialised size |
|
|
16785
16993
|
| `measureSize` | function | item count + byte size of a result |
|
|
16786
16994
|
| `JsonValue` | _type_ | a JSON-serialisable value |
|
|
16787
|
-
| `SanitizeOptions` | _type_ | tuning for `redact` / `sanitizePayload` |
|
|
16995
|
+
| `SanitizeOptions` | _type_ | tuning for `redact` / `sanitizePayload` — `maxStringLengthByKey` bounds a named key wherever it sits without changing the record-wide bound |
|
|
16788
16996
|
| `SizeMeasure` | _type_ | the result of `measureSize` |
|
|
16789
16997
|
|
|
16790
16998
|
---
|
|
@@ -17008,6 +17216,48 @@ A native remote-connector auth surface for MCP — [guide](../guide/mcp-and-agen
|
|
|
17008
17216
|
| `RefreshData` | _type_ | a stored refresh-token record |
|
|
17009
17217
|
| `RegisteredClient` | _type_ | a registered OAuth client |
|
|
17010
17218
|
|
|
17219
|
+
### Durable tool bodies
|
|
17220
|
+
|
|
17221
|
+
A mounted tool body can be made restartable without adopting `agent-runtime`: declare a
|
|
17222
|
+
factory and `step` / `sleep` / `waitFor` appear in the handler context, backed by whatever
|
|
17223
|
+
ledger the application already has.
|
|
17224
|
+
|
|
17225
|
+
```ts
|
|
17226
|
+
mountAgent(services, { durability: (toolCallId) => myLedgerFor(toolCallId) });
|
|
17227
|
+
```
|
|
17228
|
+
|
|
17229
|
+
| Export | Kind | Summary |
|
|
17230
|
+
|--------|------|---------|
|
|
17231
|
+
| `ToolDurability` | _type_ | the port a tool body needs: `step`, `sleep`, `waitFor` |
|
|
17232
|
+
| `ToolDurabilityFactory` | _type_ | `(toolCallId, signal) => ToolDurability`, called once per call |
|
|
17233
|
+
| `DurableJsonValue` | _type_ | what a durable step result may hold — a record is read back in another process, so it must be JSON |
|
|
17234
|
+
| `createLocalStepDurability` | function | the durability engine — replay, absolute deadlines, park/deliver — over a two-method ledger the application owns |
|
|
17235
|
+
| `StepDurabilityLedger` | _type_ | the two methods an engine needs: `appendEvent`, `readEvents` |
|
|
17236
|
+
| `LocalStepDurability` | _type_ | what the engine returns; satisfies `ToolDurability` and adds `deliver` and record introspection for the ledger's owner |
|
|
17237
|
+
| `LocalStepDurabilityOptions` | _type_ | `{ store, conversationId, runId, signal?, subscribe?, clock? }` |
|
|
17238
|
+
| `AppendAgentStoreEvent` / `ReadAgentStoreEvents` / `AgentStoreEventEnvelope` / `AgentStoreEventPage` | _type_ | the ledger's row shapes, re-exported so a ledger is typed from the entrypoint that consumes it |
|
|
17239
|
+
| `AgentToolError` | class | the model-safe envelope a failed agent tool throws |
|
|
17240
|
+
| `isAgentToolError` | function | brand check for it — a consumer running its own loop no longer parses the message |
|
|
17241
|
+
|
|
17242
|
+
### Catalog stamp
|
|
17243
|
+
|
|
17244
|
+
Every advertised tool and every tool result carries the fingerprint of the catalog it came
|
|
17245
|
+
from, so a long-lived consumer can see that its own copy is stale **without making a
|
|
17246
|
+
request for it**. `createMcpHandler` is stateless by construction and therefore has no
|
|
17247
|
+
retained session to push `notifications/tools/list_changed` down; the stamp needs none.
|
|
17248
|
+
|
|
17249
|
+
Store the stamp beside the tools when you list them, compare it against the one on every
|
|
17250
|
+
result, and re-list when they differ — including on a refusal, which is exactly when a
|
|
17251
|
+
stale catalog is the likeliest explanation and the least visible one. The server side
|
|
17252
|
+
needs no wiring: every mount stamps what it advertises.
|
|
17253
|
+
|
|
17254
|
+
| Export | Kind | Summary |
|
|
17255
|
+
|--------|------|---------|
|
|
17256
|
+
| `readMcpCatalogStamp` | function | read a peer's stamp out of `_meta`, or `null` when there is none |
|
|
17257
|
+
| `mcpCatalogMeta` | function | the `_meta` fragment a stamped tool or result carries |
|
|
17258
|
+
| `MCP_CATALOG_META_KEY` | const | the namespaced `_meta` key (`stitchkit/catalog`) |
|
|
17259
|
+
| `McpCatalogStamp` | _type_ | `{ digest, tools }` — the catalog fingerprint and its tool count |
|
|
17260
|
+
|
|
17011
17261
|
### MCP Apps (widgets)
|
|
17012
17262
|
|
|
17013
17263
|
Interactive MCP resources — [ADR 0019](../decisions/0019-generic-native-tools.md).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "stitchkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.91.0",
|
|
4
4
|
"description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -211,7 +211,7 @@
|
|
|
211
211
|
"build:js": "bun run build:entrypoints && bun scripts/preserve-webpack-ignore.mjs",
|
|
212
212
|
"build:types": "bun x tsc -p tsconfig.build.json --emitDeclarationOnly && bun scripts/rewrite-declaration-specifiers.mjs",
|
|
213
213
|
"build": "bun ../../scripts/package-build-lock.ts -- bun run build:unlocked",
|
|
214
|
-
"build:unlocked": "rm -rf dist && cp ../../CHANGELOG.md ./CHANGELOG.md && bun run build:js && chmod +x dist/upgrade-cli.js && bun run build:types && bun scripts/check-browser-clean.mjs && bun scripts/check-browser-executes.mjs && bun scripts/check-env-live.mjs && bun scripts/check-public-types.mjs && bun scripts/check-declarations-strict.mjs",
|
|
214
|
+
"build:unlocked": "rm -rf dist && cp ../../CHANGELOG.md ./CHANGELOG.md && bun run build:js && chmod +x dist/upgrade-cli.js && bun run build:types && bun scripts/check-browser-clean.mjs && bun scripts/check-browser-executes.mjs && bun scripts/check-env-live.mjs && bun scripts/check-public-types.mjs && bun scripts/check-durability-port-standalone.mjs && bun scripts/check-declarations-strict.mjs",
|
|
215
215
|
"dev": "bun run build:entrypoints -- --watch",
|
|
216
216
|
"prepack": "cp ../../README.md ./README.md && bun ../../scripts/gen-llms.ts && bun run build",
|
|
217
217
|
"test": "bun test",
|