stitchkit 0.17.0 → 0.18.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.
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/browser/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EAGX,gBAAgB,EAChB,eAAe,EAChB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAEL,KAAK,UAAU,IAAI,WAAW,EAG/B,MAAM,QAAQ,CAAC;AA8ChB,kFAAkF;AAClF,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClE,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACnD;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAC1D,OAAO,GACP;KAAG,CAAC,IAAI,CAAC,GAAG,MAAM;CAAE,CAAC;AAEzB;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,MAAM,GAAG,KAAK;IAC5D,UAAU,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,CAAC;IAClE,eAAe,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACrC,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,KAAK,EAE9B,QAAQ,EAAE,WAAW,CAAC,CAAC,EAAE,MAAM,CAAC,EAChC,cAAc,EAAE,YAAY,GAAG,WAAW,EAC1C,cAAc,CAAC,EAAE,oBAAoB,CAAC,CAAC,CAAC,GACvC,gBAAgB,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAepC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,EAC1E,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;CAAE,CAKzF"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/browser/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,WAAW,EAGX,gBAAgB,EAChB,eAAe,EAChB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAEL,KAAK,UAAU,IAAI,WAAW,EAG/B,MAAM,QAAQ,CAAC;AAkEhB,kFAAkF;AAClF,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClE,WAAW,CAAC,EAAE,kBAAkB,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACnD;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,GAC1D,OAAO,GACP;KAAG,CAAC,IAAI,CAAC,GAAG,MAAM;CAAE,CAAC;AAEzB;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,MAAM,GAAG,KAAK;IAC5D,UAAU,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,CAAC;IAClE,eAAe,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC;CAChC;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EACrC,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,KAAK,EAE9B,QAAQ,EAAE,WAAW,CAAC,CAAC,EAAE,MAAM,CAAC,EAChC,cAAc,EAAE,YAAY,GAAG,WAAW,EAC1C,cAAc,CAAC,EAAE,oBAAoB,CAAC,CAAC,CAAC,GACvC,gBAAgB,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAepC;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC,EAC1E,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,WAAW,GAAG;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;CAAE,CAKzF"}
@@ -50,6 +50,15 @@ export interface HttpClientConfig {
50
50
  * request — use it for runtime tokens (e.g. a short-lived auth token).
51
51
  */
52
52
  headers?: HeaderProvider;
53
+ /**
54
+ * Emit a W3C `traceparent` header on every request — a fresh root trace per
55
+ * request. The stitchkit server continues an inbound `traceparent`
56
+ * (`resolveTraceContext`), so with this on, a browser call, its HTTP handler
57
+ * and every nested tool call share one trace id end-to-end. A `traceparent`
58
+ * already set (via `headers`) wins — the client never overwrites it.
59
+ * Default `false`.
60
+ */
61
+ trace?: boolean;
53
62
  }
54
63
  type ParamValue = string | number | boolean | undefined;
55
64
  type ParamArrayValue = Array<string | number>;
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/browser/http.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/browser/http.ts"],"names":[],"mappings":"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"}
@@ -13,7 +13,7 @@ import {
13
13
  resolveSocketIp,
14
14
  resolveTraceId,
15
15
  setRequestEndpoint
16
- } from "./index-fwqnkc90.js";
16
+ } from "./index-dz2k8d5h.js";
17
17
  import {
18
18
  __require,
19
19
  isUnsafeKey,
@@ -1,3 +1,6 @@
1
+ import {
2
+ resolveTraceContext
3
+ } from "./index-khwedj16.js";
1
4
  import {
2
5
  isRecord,
3
6
  isUnsafeKey
@@ -55,45 +58,6 @@ function parseQueryParams(url) {
55
58
  return query;
56
59
  }
57
60
 
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
61
  // src/observability/context.ts
98
62
  import { AsyncLocalStorage } from "node:async_hooks";
99
63
  var storage = new AsyncLocalStorage;
@@ -148,4 +112,4 @@ function wrapInRequestContext(handler, options = {}) {
148
112
  };
149
113
  }
150
114
 
151
- export { generateTraceId, resolveTraceId, resolveSocketIp, extractIp, getClientInfo, parseQueryParams, createTraceContext, parseTraceparent, formatTraceparent, resolveTraceContext, childSpan, runWithRequestContext, getRequestContext, getTraceId, getUserId, setRequestUser, setRequestEndpoint, setRequestDimensions, setRequestError, wrapInRequestContext };
115
+ export { generateTraceId, resolveTraceId, resolveSocketIp, extractIp, getClientInfo, parseQueryParams, runWithRequestContext, getRequestContext, getTraceId, getUserId, setRequestUser, setRequestEndpoint, setRequestDimensions, setRequestError, wrapInRequestContext };
@@ -0,0 +1,40 @@
1
+ // src/observability/trace.ts
2
+ var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
3
+ function randomHex(bytes) {
4
+ const arr = new Uint8Array(bytes);
5
+ crypto.getRandomValues(arr);
6
+ let hex = "";
7
+ for (const byte of arr)
8
+ hex += byte.toString(16).padStart(2, "0");
9
+ return hex;
10
+ }
11
+ function createTraceContext() {
12
+ return { traceId: randomHex(16), spanId: randomHex(8) };
13
+ }
14
+ function parseTraceparent(header) {
15
+ if (!header)
16
+ return null;
17
+ const match = TRACEPARENT_RE.exec(header.trim());
18
+ if (!match?.[1] || !match[2])
19
+ return null;
20
+ const traceId = match[1].toLowerCase();
21
+ const parentSpanId = match[2].toLowerCase();
22
+ if (/^0+$/.test(traceId) || /^0+$/.test(parentSpanId))
23
+ return null;
24
+ return { traceId, spanId: randomHex(8), parentSpanId };
25
+ }
26
+ function formatTraceparent(ctx) {
27
+ return `00-${ctx.traceId}-${ctx.spanId}-01`;
28
+ }
29
+ function resolveTraceContext(req) {
30
+ return parseTraceparent(req.headers.get("traceparent")) ?? createTraceContext();
31
+ }
32
+ function childSpan(parent) {
33
+ return {
34
+ traceId: parent.traceId,
35
+ spanId: randomHex(8),
36
+ parentSpanId: parent.spanId
37
+ };
38
+ }
39
+
40
+ export { createTraceContext, parseTraceparent, formatTraceparent, resolveTraceContext, childSpan };
package/dist/index.d.ts CHANGED
@@ -4,5 +4,6 @@ export type { SocketEventMap, SocketIOClient, SocketIOClientConfig, } from './br
4
4
  export { createSocketIOClient } from './browser/socket-io';
5
5
  export { type ParseSSEOptions, parseSSE } from './browser/stream';
6
6
  export * from './contract';
7
+ export { childSpan, createTraceContext, formatTraceparent, parseTraceparent, type TraceContext, } from './observability/trace';
7
8
  export { createRetainedTopics, type RetainedTopics } from './retained';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,YAAY,EACZ,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,cAAc,EACd,cAAc,EACd,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAClE,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,YAAY,EACZ,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACL,QAAQ,EACR,KAAK,QAAQ,EACb,KAAK,gBAAgB,EACrB,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,cAAc,GACpB,MAAM,gBAAgB,CAAC;AACxB,YAAY,EACV,cAAc,EACd,cAAc,EACd,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,KAAK,eAAe,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAClE,cAAc,YAAY,CAAC;AAG3B,OAAO,EACL,SAAS,EACT,kBAAkB,EAClB,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,YAAY,GAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,oBAAoB,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -40,6 +40,44 @@ function mapObject(source, mapper) {
40
40
 
41
41
  // src/browser/http.ts
42
42
  import ky, { isHTTPError } from "ky";
43
+
44
+ // src/observability/trace.ts
45
+ var TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/i;
46
+ function randomHex(bytes) {
47
+ const arr = new Uint8Array(bytes);
48
+ crypto.getRandomValues(arr);
49
+ let hex = "";
50
+ for (const byte of arr)
51
+ hex += byte.toString(16).padStart(2, "0");
52
+ return hex;
53
+ }
54
+ function createTraceContext() {
55
+ return { traceId: randomHex(16), spanId: randomHex(8) };
56
+ }
57
+ function parseTraceparent(header) {
58
+ if (!header)
59
+ return null;
60
+ const match = TRACEPARENT_RE.exec(header.trim());
61
+ if (!match?.[1] || !match[2])
62
+ return null;
63
+ const traceId = match[1].toLowerCase();
64
+ const parentSpanId = match[2].toLowerCase();
65
+ if (/^0+$/.test(traceId) || /^0+$/.test(parentSpanId))
66
+ return null;
67
+ return { traceId, spanId: randomHex(8), parentSpanId };
68
+ }
69
+ function formatTraceparent(ctx) {
70
+ return `00-${ctx.traceId}-${ctx.spanId}-01`;
71
+ }
72
+ function childSpan(parent) {
73
+ return {
74
+ traceId: parent.traceId,
75
+ spanId: randomHex(8),
76
+ parentSpanId: parent.spanId
77
+ };
78
+ }
79
+
80
+ // src/browser/http.ts
43
81
  class ApiError extends Error {
44
82
  code;
45
83
  status;
@@ -104,6 +142,9 @@ function createHttpClient(config) {
104
142
  request2.headers.set(key, value);
105
143
  }
106
144
  }
145
+ if (config.trace && !request2.headers.has("traceparent")) {
146
+ request2.headers.set("traceparent", formatTraceparent(createTraceContext()));
147
+ }
107
148
  }
108
149
  ],
109
150
  afterResponse: [
@@ -206,7 +247,7 @@ function withTimeout(options, timeout) {
206
247
  function isParamArray(value) {
207
248
  return Array.isArray(value) && value.every((v) => typeof v === "string" || typeof v === "number");
208
249
  }
209
- function collectQueryParams(args, skipKeys) {
250
+ function collectQueryParams(args, skipKeys, endpoint) {
210
251
  const params = {};
211
252
  let hasParams = false;
212
253
  for (const [key, value] of Object.entries(args)) {
@@ -215,7 +256,10 @@ function collectQueryParams(args, skipKeys) {
215
256
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || isParamArray(value)) {
216
257
  params[key] = value;
217
258
  hasParams = true;
259
+ continue;
218
260
  }
261
+ const what = Array.isArray(value) ? "an array with non-primitive items" : typeof value === "object" ? "a nested object" : `a ${typeof value}`;
262
+ throw new Error(`${endpoint.method} ${endpoint.path}: input field "${key}" is ${what} — it cannot ` + "travel as a query parameter. GET / DELETE input must be flat (string / number / " + "boolean, or an array of string / number); flatten the field or move the " + "operation to a body verb (POST).");
219
263
  }
220
264
  return hasParams ? params : undefined;
221
265
  }
@@ -272,11 +316,11 @@ function createHttpMethod(endpoint, prefix, client, config) {
272
316
  return client.post(url, formData, withTimeout(undefined, endpoint.timeout));
273
317
  }
274
318
  if (isGet) {
275
- const params = collectQueryParams(firstArg, prefixKeys);
319
+ const params = collectQueryParams(firstArg, prefixKeys, endpoint);
276
320
  return client.get(url, withTimeout(params ? { params } : undefined, endpoint.timeout));
277
321
  }
278
322
  if (httpMethod === "delete") {
279
- const params = collectQueryParams(firstArg, prefixKeys);
323
+ const params = collectQueryParams(firstArg, prefixKeys, endpoint);
280
324
  return client.delete(url, withTimeout(params ? { params } : undefined, endpoint.timeout));
281
325
  }
282
326
  const payload = {};
@@ -306,19 +350,19 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
306
350
  const hasBody = !isQuery && !endpoint.multipart && endpoint.input && args;
307
351
  if (isQuery && args) {
308
352
  const remaining = stripParams(args, endpoint.path, prefixKeys);
309
- const searchParams = new URLSearchParams;
310
- for (const [k, v] of Object.entries(remaining)) {
311
- if (v === undefined || v === null)
312
- continue;
313
- if (isParamArray(v)) {
314
- for (const item of v)
315
- searchParams.append(k, String(item));
316
- } else if (typeof v !== "object") {
317
- searchParams.set(k, String(v));
353
+ const params = collectQueryParams(remaining, new Set, endpoint);
354
+ if (params) {
355
+ const searchParams = new URLSearchParams;
356
+ for (const [k, v] of Object.entries(params)) {
357
+ if (Array.isArray(v)) {
358
+ for (const item of v)
359
+ searchParams.append(k, String(item));
360
+ } else {
361
+ searchParams.set(k, String(v));
362
+ }
318
363
  }
319
- }
320
- if (searchParams.size > 0)
321
364
  url += `?${searchParams}`;
365
+ }
322
366
  }
323
367
  if (hasBody)
324
368
  headers["Content-Type"] = "application/json";
@@ -596,20 +640,24 @@ async function* parseSSE(response, options) {
596
640
  export {
597
641
  unauthorized,
598
642
  rateLimited,
643
+ parseTraceparent,
599
644
  parseSSE,
600
645
  paginatedSchema,
601
646
  notFound,
602
647
  isStitchErrorCode,
648
+ formatTraceparent,
603
649
  forbidden,
604
650
  encodeCursor,
605
651
  defineContract,
606
652
  decodeCursor,
653
+ createTraceContext,
607
654
  createSocketIOClient,
608
655
  createRetainedTopics,
609
656
  createHttpClient,
610
657
  createClients,
611
658
  createClient,
612
659
  conflict,
660
+ childSpan,
613
661
  badRequest,
614
662
  appError,
615
663
  STITCH_ERROR_STATUS,
package/dist/node.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  createImplement,
4
4
  createSocketIOServer,
5
5
  implement
6
- } from "./index-6k6y7wat.js";
6
+ } from "./index-6qv1fxqy.js";
7
7
  import {
8
8
  AppError,
9
9
  appError,
@@ -14,7 +14,8 @@ import {
14
14
  rateLimited,
15
15
  unauthorized
16
16
  } from "./index-ktn76xz0.js";
17
- import"./index-fwqnkc90.js";
17
+ import"./index-dz2k8d5h.js";
18
+ import"./index-khwedj16.js";
18
19
  import"./index-tm7dqzxc.js";
19
20
  // src/server/node.ts
20
21
  import { serve } from "srvx";
@@ -1,19 +1,21 @@
1
1
  import {
2
- childSpan,
3
- createTraceContext,
4
- formatTraceparent,
5
2
  getRequestContext,
6
3
  getTraceId,
7
4
  getUserId,
8
- parseTraceparent,
9
- resolveTraceContext,
10
5
  runWithRequestContext,
11
6
  setRequestDimensions,
12
7
  setRequestEndpoint,
13
8
  setRequestError,
14
9
  setRequestUser,
15
10
  wrapInRequestContext
16
- } from "../index-fwqnkc90.js";
11
+ } from "../index-dz2k8d5h.js";
12
+ import {
13
+ childSpan,
14
+ createTraceContext,
15
+ formatTraceparent,
16
+ parseTraceparent,
17
+ resolveTraceContext
18
+ } from "../index-khwedj16.js";
17
19
  import {
18
20
  isRecord,
19
21
  isUnsafeKey
@@ -12,7 +12,7 @@ import {
12
12
  socketIoLane,
13
13
  staticRoute,
14
14
  webSocketLane
15
- } from "../index-6k6y7wat.js";
15
+ } from "../index-6qv1fxqy.js";
16
16
  import {
17
17
  createAuthHook,
18
18
  createBearerResolver,
@@ -50,7 +50,8 @@ import {
50
50
  getTraceId,
51
51
  resolveSocketIp,
52
52
  resolveTraceId
53
- } from "../index-fwqnkc90.js";
53
+ } from "../index-dz2k8d5h.js";
54
+ import"../index-khwedj16.js";
54
55
  import {
55
56
  isRecord
56
57
  } from "../index-tm7dqzxc.js";
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Tool-name baseline — the final mounted name of every tool across the tool
3
+ * transports, with its `(service, method)` identity. Tool names are partly
4
+ * derived (`toolName` override, else `toToolName(service, method)`), so a
5
+ * consumer pins them with a snapshot test / CI diff: an upgrade that would
6
+ * shift a derived name — and silently break MCP client configs — fails the
7
+ * consumer's build instead. Also the mechanical "what changed" diff when
8
+ * migrating a service between contract shapes.
9
+ *
10
+ * Built on `collectTools`, the exact resolver `mountMcp` / `mountAgent` /
11
+ * `createCli` use — the listing can never drift from what actually mounts.
12
+ */
13
+ import type { Transport } from '../contract';
14
+ import type { ServiceDef } from '../server/types';
15
+ /** One mounted tool name and where it comes from. */
16
+ export interface ToolNameEntry {
17
+ /** Final tool name — the `toolName` override, else derived from service + method. */
18
+ name: string;
19
+ /** Owning service (`ServiceDef.name` — the contract's prefix). */
20
+ service: string;
21
+ /** Endpoint key in the contract (e.g. `updatePartial`). */
22
+ method: string;
23
+ /** Tool transports the name is exposed on (mount order: MCP, AGENT, CLI). */
24
+ transports: Transport[];
25
+ }
26
+ /**
27
+ * Resolve every tool name the given services expose, sorted by name (then
28
+ * service) — a stable shape to snapshot. Multipart endpoints are absent
29
+ * (never mounted as tools) and CLI appears only where `expose` opts in,
30
+ * mirroring the real mounts.
31
+ */
32
+ export declare function listToolNames(services: ServiceDef[]): ToolNameEntry[];
33
+ //# sourceMappingURL=list-names.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-names.d.ts","sourceRoot":"","sources":["../../src/tools/list-names.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAGlD,qDAAqD;AACrD,MAAM,WAAW,aAAa;IAC5B,qFAAqF;IACrF,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,UAAU,EAAE,SAAS,EAAE,CAAC;CACzB;AAID;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,aAAa,EAAE,CAwBrE"}
package/dist/tools.d.ts CHANGED
@@ -6,6 +6,7 @@ export type { CliWaitConfig } from './tools/cli-wait';
6
6
  export { coerceJsonArgs } from './tools/coerce';
7
7
  export type { ToolCallHooks, ToolLifecycle, ToolResult } from './tools/execute';
8
8
  export { flattenDiscriminatedUnion, flattenUnionsDeep } from './tools/flatten';
9
+ export { listToolNames, type ToolNameEntry } from './tools/list-names';
9
10
  export { buildToolManifest, type ToolManifestEntry } from './tools/manifest';
10
11
  export { buildMcpServer, type IncompatibleSchemaPolicy, type McpMountConfig, type McpServerBuildConfig, mountMcp, mountMcpResource, validateMcpSchemas, } from './tools/mcp';
11
12
  export { EXT_APPS_BUNDLE_PLACEHOLDER, inlineMcpAppBundle, type McpAppCsp, type McpAppResourceMeta, type McpResourceDef, RESOURCE_MIME_TYPE, } from './tools/mcp-app';
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EACL,cAAc,EACd,KAAK,wBAAwB,EAC7B,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,QAAQ,EACR,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,2BAA2B,EAC3B,kBAAkB,EAClB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,KAAK,kBAAkB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,KAAK,uBAAuB,EAC5B,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,sBAAsB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,KAAK,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,gBAAgB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AACrF,OAAO,EAAE,KAAK,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxD,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAE,yBAAyB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,EACL,cAAc,EACd,KAAK,wBAAwB,EAC7B,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,QAAQ,EACR,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,2BAA2B,EAC3B,kBAAkB,EAClB,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC9E,OAAO,EAAE,oBAAoB,EAAE,KAAK,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACpF,OAAO,EAAE,YAAY,EAAE,KAAK,aAAa,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAClF,OAAO,EAAE,KAAK,kBAAkB,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAChF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpE,OAAO,EACL,2BAA2B,EAC3B,uBAAuB,EACvB,KAAK,uBAAuB,EAC5B,4BAA4B,EAC5B,qBAAqB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,cAAc,EACnB,kBAAkB,EAClB,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,gBAAgB,GACtB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,sBAAsB,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC9E,OAAO,EAAE,aAAa,EAAE,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,KAAK,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/tools.js CHANGED
@@ -23,6 +23,7 @@ import {
23
23
  AppError,
24
24
  isWithinDir
25
25
  } from "./index-ktn76xz0.js";
26
+ import"./index-khwedj16.js";
26
27
  import {
27
28
  isRecord,
28
29
  typedEntries
@@ -69,6 +70,30 @@ function mountAgent(services, config = {}) {
69
70
  }
70
71
  return tools;
71
72
  }
73
+ // src/tools/list-names.ts
74
+ var TOOL_TRANSPORTS = ["MCP", "AGENT", "CLI"];
75
+ function listToolNames(services) {
76
+ const entries = new Map;
77
+ for (const service of services) {
78
+ for (const transport of TOOL_TRANSPORTS) {
79
+ for (const tool2 of collectTools(service, transport)) {
80
+ const id = `${service.name}\x00${tool2.method.key}`;
81
+ const existing = entries.get(id);
82
+ if (existing) {
83
+ existing.transports.push(transport);
84
+ } else {
85
+ entries.set(id, {
86
+ name: tool2.name,
87
+ service: service.name,
88
+ method: tool2.method.key,
89
+ transports: [transport]
90
+ });
91
+ }
92
+ }
93
+ }
94
+ }
95
+ return [...entries.values()].sort((a, b) => a.name.localeCompare(b.name) || a.service.localeCompare(b.service));
96
+ }
72
97
  // src/tools/manifest.ts
73
98
  function buildToolManifest(tools) {
74
99
  return tools.map((t) => {
@@ -873,7 +898,7 @@ function withTimeout(options, timeout) {
873
898
  function isParamArray(value) {
874
899
  return Array.isArray(value) && value.every((v) => typeof v === "string" || typeof v === "number");
875
900
  }
876
- function collectQueryParams(args, skipKeys) {
901
+ function collectQueryParams(args, skipKeys, endpoint) {
877
902
  const params = {};
878
903
  let hasParams = false;
879
904
  for (const [key, value] of Object.entries(args)) {
@@ -882,7 +907,10 @@ function collectQueryParams(args, skipKeys) {
882
907
  if (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || isParamArray(value)) {
883
908
  params[key] = value;
884
909
  hasParams = true;
910
+ continue;
885
911
  }
912
+ const what = Array.isArray(value) ? "an array with non-primitive items" : typeof value === "object" ? "a nested object" : `a ${typeof value}`;
913
+ throw new Error(`${endpoint.method} ${endpoint.path}: input field "${key}" is ${what} — it cannot ` + "travel as a query parameter. GET / DELETE input must be flat (string / number / " + "boolean, or an array of string / number); flatten the field or move the " + "operation to a body verb (POST).");
886
914
  }
887
915
  return hasParams ? params : undefined;
888
916
  }
@@ -936,11 +964,11 @@ function createHttpMethod(endpoint, prefix, client, config) {
936
964
  return client.post(url, formData, withTimeout(undefined, endpoint.timeout));
937
965
  }
938
966
  if (isGet) {
939
- const params = collectQueryParams(firstArg, prefixKeys);
967
+ const params = collectQueryParams(firstArg, prefixKeys, endpoint);
940
968
  return client.get(url, withTimeout(params ? { params } : undefined, endpoint.timeout));
941
969
  }
942
970
  if (httpMethod === "delete") {
943
- const params = collectQueryParams(firstArg, prefixKeys);
971
+ const params = collectQueryParams(firstArg, prefixKeys, endpoint);
944
972
  return client.delete(url, withTimeout(params ? { params } : undefined, endpoint.timeout));
945
973
  }
946
974
  const payload = {};
@@ -970,19 +998,19 @@ function createFetchMethod(endpoint, prefix, config, contractConfig) {
970
998
  const hasBody = !isQuery && !endpoint.multipart && endpoint.input && args;
971
999
  if (isQuery && args) {
972
1000
  const remaining = stripParams(args, endpoint.path, prefixKeys);
973
- const searchParams = new URLSearchParams;
974
- for (const [k, v] of Object.entries(remaining)) {
975
- if (v === undefined || v === null)
976
- continue;
977
- if (isParamArray(v)) {
978
- for (const item of v)
979
- searchParams.append(k, String(item));
980
- } else if (typeof v !== "object") {
981
- searchParams.set(k, String(v));
1001
+ const params = collectQueryParams(remaining, new Set, endpoint);
1002
+ if (params) {
1003
+ const searchParams = new URLSearchParams;
1004
+ for (const [k, v] of Object.entries(params)) {
1005
+ if (Array.isArray(v)) {
1006
+ for (const item of v)
1007
+ searchParams.append(k, String(item));
1008
+ } else {
1009
+ searchParams.set(k, String(v));
1010
+ }
982
1011
  }
983
- }
984
- if (searchParams.size > 0)
985
1012
  url += `?${searchParams}`;
1013
+ }
986
1014
  }
987
1015
  if (hasBody)
988
1016
  headers["Content-Type"] = "application/json";
@@ -1284,6 +1312,7 @@ export {
1284
1312
  mountMcp,
1285
1313
  mountDownload,
1286
1314
  mountAgent,
1315
+ listToolNames,
1287
1316
  inlineMcpAppBundle,
1288
1317
  implementRemote,
1289
1318
  flattenUnionsDeep,
package/llms-full.txt CHANGED
@@ -268,6 +268,26 @@ For the client, an endpoint's argument type is the schema's **input** type
268
268
  present (required) in the handler's parsed `ctx.input`. The contract handles the
269
269
  two type views; you do not.
270
270
 
271
+ ### Query input (`GET` / `DELETE`)
272
+
273
+ `GET` and `DELETE` carry their input as the **query string**, and a query
274
+ string can only encode flat values. An input field on these verbs must be a
275
+ `string`, `number`, `boolean`, or an array of `string` / `number` (repeated
276
+ query keys). A **nested object has no canonical query encoding** — the typed
277
+ client throws on one instead of sending a silently incomplete request:
278
+
279
+ ```ts
280
+ // input: z.object({ q: z.string(), filter: z.object({ … }) }) on a GET
281
+ await api.search({ q: 'x', filter: { status: 'active' } })
282
+ // ✗ throws: GET /: input field "filter" is a nested object — it cannot travel
283
+ // as a query parameter
284
+ ```
285
+
286
+ Flatten the field (`status: 'active'`) or move the operation to a body verb
287
+ (`POST`). Remember the server parses query values from **strings** — use
288
+ `z.coerce.number()` / `z.coerce.boolean()` in a query-input schema for
289
+ non-string fields.
290
+
271
291
  ## Transports
272
292
 
273
293
  By default an endpoint is exposed on **every** surface — HTTP, MCP and agent
@@ -293,9 +313,9 @@ file upload is not a tool call.
293
313
 
294
314
  ## `toolName`
295
315
 
296
- When an endpoint is exposed as a tool, its name defaults to `prefix_key`
297
- (`users` + `create` ⇒ `users_create`). Set `toolName` for an explicit, stable
298
- name:
316
+ When an endpoint is exposed as a tool, its name defaults to a verb-aware
317
+ derivation from the method key + prefix (`users` + `create` ⇒ `create_user`,
318
+ `users` + `list` ⇒ `list_users`). Set `toolName` for an explicit, stable name:
299
319
 
300
320
  ```ts
301
321
  { method: 'POST', path: '/', desc: 'Create a user', toolName: 'create_user', /* … */ }
@@ -831,10 +851,16 @@ const http = createHttpClient({ baseUrl: '/api' })
831
851
  | `headers` | — | extra headers — an object, or a function re-run per request |
832
852
  | `authEndpoints` | `['auth/']` | paths that should **not** emit `unauthorized` on 401 |
833
853
  | `parseError` | built-in | map an error body to `{ code, message, details, hint }` |
854
+ | `trace` | `false` | emit a W3C `traceparent` header on every request |
834
855
 
835
856
  `headers` as a function is the hook for runtime tokens — a bearer token or any
836
857
  short-lived credential — re-evaluated on every request.
837
858
 
859
+ `trace: true` mints a fresh root trace per request. The stitchkit server
860
+ [continues an inbound `traceparent`](./observability.md#trace-context), so the
861
+ browser call, the HTTP handler and every nested tool call share one trace id
862
+ end-to-end. A `traceparent` you set yourself (via `headers`) always wins.
863
+
838
864
  Retry is deliberately conservative: only a connection that never landed (a
839
865
  network error), only on idempotent `GET`. A server that *responded* with a 5xx
840
866
  is the data layer's call (TanStack Query), not the transport's — retrying in
@@ -1053,6 +1079,27 @@ to a verb-aware name from the method + prefix (`list` → `list_widgets`, `get`
1053
1079
  `get_widget`); set `toolName` for an explicit one. See
1054
1080
  [Contracts → transports](./contracts.md#transports).
1055
1081
 
1082
+ ### Pinning tool names — `listToolNames`
1083
+
1084
+ Derived tool names are part of your public surface — an MCP client config or an
1085
+ agent prompt refers to them by string. `listToolNames(services)` resolves every
1086
+ tool name your services expose (the exact resolver the mounts use), with its
1087
+ `(service, method)` identity and transports, sorted — a stable shape to
1088
+ snapshot:
1089
+
1090
+ ```ts
1091
+ import { listToolNames } from 'stitchkit/tools'
1092
+
1093
+ test('tool names have not drifted', () => {
1094
+ expect(listToolNames(services)).toMatchSnapshot()
1095
+ })
1096
+ ```
1097
+
1098
+ A stitchkit upgrade (or a contract refactor) that would shift a derived name
1099
+ now fails this test instead of silently breaking the clients that call the
1100
+ tool. It is also the mechanical diff when migrating a service: run it before
1101
+ and after, compare.
1102
+
1056
1103
  ## MCP — `createMcpHandler`
1057
1104
 
1058
1105
  `createMcpHandler` builds a complete Streamable-HTTP MCP server as a single
@@ -2361,6 +2408,12 @@ You rarely call the trace functions directly — `wrapInRequestContext` and
2361
2408
  `parseTraceparent`, `formatTraceparent`, `childSpan`) for when you need to
2362
2409
  propagate a `traceparent` onward to another service.
2363
2410
 
2411
+ The browser side starts the trace:
2412
+ [`createHttpClient({ trace: true })`](./client.md#httpclientconfig) emits a
2413
+ fresh root `traceparent` on every request, which the server then continues. The
2414
+ trace helpers themselves are browser-safe and also exported from the root
2415
+ `stitchkit` entry — a custom client can format its own header.
2416
+
2364
2417
  > **Span ids live in the request context, not on `ctx`.** The handler `ctx`
2365
2418
  > carries a single `traceId`; the full `{ traceId, spanId, parentSpanId }` is on
2366
2419
  > the observability request context. To stamp `spanId` / `parentSpanId` into an
@@ -2859,6 +2912,20 @@ The browser-and-server entrypoint. Re-exports everything from
2859
2912
  | `RetainedTopics` | _type_ | the `createRetainedTopics` handle |
2860
2913
  | `ParseSSEOptions` | _type_ | options for `parseSSE` |
2861
2914
 
2915
+ ### Trace (client)
2916
+
2917
+ Browser-safe W3C trace helpers — the same functions as on
2918
+ [`stitchkit/observability`](#stitchkitobservability), re-exported so a client
2919
+ can emit / propagate a `traceparent` (see `HttpClientConfig.trace`).
2920
+
2921
+ | Export | Kind | Summary |
2922
+ |--------|------|---------|
2923
+ | `createTraceContext` | function | a fresh root trace |
2924
+ | `formatTraceparent` | function | render a `traceparent` header value |
2925
+ | `parseTraceparent` | function | parse a `traceparent` header |
2926
+ | `childSpan` | function | a child span of a parent trace |
2927
+ | `TraceContext` | _type_ | `{ traceId, spanId, parentSpanId? }` |
2928
+
2862
2929
  ---
2863
2930
 
2864
2931
  ## `stitchkit/contract`
@@ -3095,6 +3162,7 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
3095
3162
  | `mountViewFile` | function | a native multimodal "view file" MCP tool |
3096
3163
  | `resolveMedia` | function | resolve a media reference for a tool result |
3097
3164
  | `validateMcpSchemas` | function | assert every tool schema is JSON Schema-compatible — [guide](../guide/mcp-and-agents.md#incompatible-schemas--onincompatibleschema) |
3165
+ | `listToolNames` | function | every mounted tool name with its `(service, method)` identity — for name-baseline snapshots — [guide](../guide/mcp-and-agents.md#pinning-tool-names--listtoolnames) |
3098
3166
  | `McpHandlerConfig` | _type_ | config for `createMcpHandler` |
3099
3167
  | `StdioMcpServerConfig` | _type_ | config for `createStdioMcpServer` |
3100
3168
  | `McpServerBuildConfig` | _type_ | shared config for `buildMcpServer` |
@@ -3110,6 +3178,7 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
3110
3178
  | `ToolLifecycle` | _type_ | `beforeHandle` / `afterHandle` gate for tool calls — [guide](../guide/mcp-and-agents.md#guarding-tools--lifecycle) |
3111
3179
  | `ToolCallHooks` | _type_ | `beforeToolCall` / `afterToolCall` observability hooks |
3112
3180
  | `ToolResult` | _type_ | the result of one tool call |
3181
+ | `ToolNameEntry` | _type_ | one `listToolNames` row — `{ name, service, method, transports }` |
3113
3182
  | `IncompatibleSchemaPolicy` | _type_ | `'throw' \| 'skip' \| 'warn'` |
3114
3183
  | `McpMediaContent` | _type_ | a multimodal MCP content item |
3115
3184
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.17.0",
3
+ "version": "0.18.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",