stitchkit 0.17.0 → 0.19.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.
Files changed (75) hide show
  1. package/dist/browser/client.d.ts.map +1 -1
  2. package/dist/browser/http.d.ts +9 -0
  3. package/dist/browser/http.d.ts.map +1 -1
  4. package/dist/browser/socket-io.d.ts.map +1 -1
  5. package/dist/cli.js +3 -3
  6. package/dist/contract/errors-factory.d.ts +18 -0
  7. package/dist/contract/errors-factory.d.ts.map +1 -0
  8. package/dist/contract/factory.d.ts +36 -0
  9. package/dist/contract/factory.d.ts.map +1 -0
  10. package/dist/contract/index.d.ts +2 -0
  11. package/dist/contract/index.d.ts.map +1 -1
  12. package/dist/contract/index.js +6 -1
  13. package/dist/contract/pagination.d.ts.map +1 -1
  14. package/dist/index-809wc1tt.js +18 -0
  15. package/dist/{index-tm7dqzxc.js → index-c7nyw0yt.js} +9 -9
  16. package/dist/{index-fwqnkc90.js → index-dzx781tm.js} +5 -41
  17. package/dist/index-khwedj16.js +40 -0
  18. package/dist/{index-ktn76xz0.js → index-ng2v2ts4.js} +17 -1
  19. package/dist/index-tje0q6gp.js +44 -0
  20. package/dist/{index-6k6y7wat.js → index-wpbn133x.js} +13 -50
  21. package/dist/{index-yzac2zbh.js → index-x62gnfsk.js} +50 -9
  22. package/dist/{index-akv8rk4w.js → index-xmncnw8z.js} +2 -2
  23. package/dist/{index-yv93m4yr.js → index-ynnh9x6h.js} +11 -29
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +141 -73
  27. package/dist/internal/base64url.d.ts +16 -0
  28. package/dist/internal/base64url.d.ts.map +1 -0
  29. package/dist/node.js +6 -4
  30. package/dist/observability/index.js +9 -7
  31. package/dist/react/entity-cache.d.ts +54 -0
  32. package/dist/react/entity-cache.d.ts.map +1 -0
  33. package/dist/react.d.ts +1 -0
  34. package/dist/react.d.ts.map +1 -1
  35. package/dist/react.js +74 -0
  36. package/dist/server/create.d.ts.map +1 -1
  37. package/dist/server/error-hook.d.ts +56 -0
  38. package/dist/server/error-hook.d.ts.map +1 -0
  39. package/dist/server/index.d.ts +2 -1
  40. package/dist/server/index.d.ts.map +1 -1
  41. package/dist/server/index.js +33 -7
  42. package/dist/server/middleware/auth.d.ts.map +1 -1
  43. package/dist/server/middleware/cors.d.ts +13 -0
  44. package/dist/server/middleware/cors.d.ts.map +1 -1
  45. package/dist/server/middleware/pkce.d.ts +0 -6
  46. package/dist/server/middleware/pkce.d.ts.map +1 -1
  47. package/dist/server/router.d.ts +0 -2
  48. package/dist/server/router.d.ts.map +1 -1
  49. package/dist/server/types.d.ts +0 -8
  50. package/dist/server/types.d.ts.map +1 -1
  51. package/dist/tools/agent.d.ts +2 -2
  52. package/dist/tools/agent.d.ts.map +1 -1
  53. package/dist/tools/cli-format.d.ts +2 -2
  54. package/dist/tools/cli-format.d.ts.map +1 -1
  55. package/dist/tools/cli.d.ts +2 -2
  56. package/dist/tools/cli.d.ts.map +1 -1
  57. package/dist/tools/execute.d.ts +7 -0
  58. package/dist/tools/execute.d.ts.map +1 -1
  59. package/dist/tools/list-names.d.ts +33 -0
  60. package/dist/tools/list-names.d.ts.map +1 -0
  61. package/dist/tools/mcp.d.ts +3 -3
  62. package/dist/tools/mcp.d.ts.map +1 -1
  63. package/dist/tools/mount.d.ts +3 -3
  64. package/dist/tools/mount.d.ts.map +1 -1
  65. package/dist/tools/oauth-metadata.d.ts.map +1 -1
  66. package/dist/tools/oauth-provider.d.ts.map +1 -1
  67. package/dist/tools/tool-logger.d.ts +31 -0
  68. package/dist/tools/tool-logger.d.ts.map +1 -0
  69. package/dist/tools/transports.d.ts +28 -0
  70. package/dist/tools/transports.d.ts.map +1 -0
  71. package/dist/tools.d.ts +4 -1
  72. package/dist/tools.d.ts.map +1 -1
  73. package/dist/tools.js +114 -25
  74. package/llms-full.txt +307 -7
  75. package/package.json +1 -1
package/llms-full.txt CHANGED
@@ -33,19 +33,23 @@ realtime, `@tanstack/react-query` + `react-query-kit` for React). See
33
33
 
34
34
  ## Entrypoints
35
35
 
36
- stitchkit ships five entrypoints. Each is import-safe for one environment —
36
+ stitchkit ships eight entrypoints. Each is import-safe for one environment —
37
37
  keeping server-only code (`Bun.serve`, the MCP SDK) out of browser bundles.
38
38
 
39
39
  | Import | Use in | Holds |
40
40
  |--------|--------|-------|
41
41
  | `stitchkit` | browser **and** server | `defineContract`, `createClient`, `createHttpClient`, `createSocketIOClient`, `parseSSE`, the error model |
42
42
  | `stitchkit/contract` | browser **and** server | the contract layer alone — `defineContract`, errors, pagination |
43
- | `stitchkit/server` | server | `createServer`, `implement`, hooks, auth, Socket.IO server, server primitives |
44
- | `stitchkit/tools` | server | `createMcpHandler`, `mountMcp`, `mountAgent` |
43
+ | `stitchkit/server` | server (Bun) | `createServer`, `implement`, hooks, auth, Socket.IO server, server primitives |
44
+ | `stitchkit/node` | server (Node ≥ 22) | `serveNode` + the runtime-agnostic core — the Node mirror of `/server` |
45
+ | `stitchkit/tools` | server | `createMcpHandler`, `mountMcp`, `mountAgent`, the OAuth provider, native tools |
46
+ | `stitchkit/cli` | server | `createCli` — the CLI transport, light (no MCP SDK / `ai`) |
47
+ | `stitchkit/observability` | server | the audit layer — `createAuditHook`, trace context, sanitisation |
45
48
  | `stitchkit/react` | browser | `createCursorQuery`, `createCacheBridge` |
46
49
 
47
50
  Rule of thumb: browser code imports `stitchkit` and `stitchkit/react`; server
48
- code adds `stitchkit/server` and `stitchkit/tools`.
51
+ code adds `stitchkit/server` (or `stitchkit/node` on Node) and `stitchkit/tools`.
52
+ The full export list of each is in the [API reference](../api/reference.md).
49
53
 
50
54
  ## Project layout
51
55
 
@@ -268,6 +272,26 @@ For the client, an endpoint's argument type is the schema's **input** type
268
272
  present (required) in the handler's parsed `ctx.input`. The contract handles the
269
273
  two type views; you do not.
270
274
 
275
+ ### Query input (`GET` / `DELETE`)
276
+
277
+ `GET` and `DELETE` carry their input as the **query string**, and a query
278
+ string can only encode flat values. An input field on these verbs must be a
279
+ `string`, `number`, `boolean`, or an array of `string` / `number` (repeated
280
+ query keys). A **nested object has no canonical query encoding** — the typed
281
+ client throws on one instead of sending a silently incomplete request:
282
+
283
+ ```ts
284
+ // input: z.object({ q: z.string(), filter: z.object({ … }) }) on a GET
285
+ await api.search({ q: 'x', filter: { status: 'active' } })
286
+ // ✗ throws: GET /: input field "filter" is a nested object — it cannot travel
287
+ // as a query parameter
288
+ ```
289
+
290
+ Flatten the field (`status: 'active'`) or move the operation to a body verb
291
+ (`POST`). Remember the server parses query values from **strings** — use
292
+ `z.coerce.number()` / `z.coerce.boolean()` in a query-input schema for
293
+ non-string fields.
294
+
271
295
  ## Transports
272
296
 
273
297
  By default an endpoint is exposed on **every** surface — HTTP, MCP and agent
@@ -293,9 +317,9 @@ file upload is not a tool call.
293
317
 
294
318
  ## `toolName`
295
319
 
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:
320
+ When an endpoint is exposed as a tool, its name defaults to a verb-aware
321
+ derivation from the method key + prefix (`users` + `create` ⇒ `create_user`,
322
+ `users` + `list` ⇒ `list_users`). Set `toolName` for an explicit, stable name:
299
323
 
300
324
  ```ts
301
325
  { method: 'POST', path: '/', desc: 'Create a user', toolName: 'create_user', /* … */ }
@@ -407,6 +431,21 @@ for every endpoint that declares none. Scopes are free strings — the framework
407
431
  attaches no meaning, your auth hook does. See
408
432
  [Auth & errors](./auth-and-errors.md).
409
433
 
434
+ A plain `defineContract` defaults a missing `scope` to `'public'` — forget it and
435
+ the endpoint is public (fail-open). If every contract in your app must be scoped,
436
+ `createContractFactory` binds your scope vocabulary once and makes `scope`
437
+ **required and typed** — a missing or mistyped scope is a compile error:
438
+
439
+ ```ts
440
+ // app: one line, once
441
+ export const { defineContract } = createContractFactory<'public' | 'user' | 'admin'>()
442
+
443
+ // scope is now mandatory and checked against the union
444
+ export const users = defineContract({ prefix: 'users', scope: 'user' }, { … })
445
+ ```
446
+
447
+ The vocabulary is yours; the returned contracts are ordinary `ContractDef`s.
448
+
410
449
  ## One source of truth
411
450
 
412
451
  A contract is plain data — no classes, no decorators, no codegen. It is imported
@@ -831,10 +870,16 @@ const http = createHttpClient({ baseUrl: '/api' })
831
870
  | `headers` | — | extra headers — an object, or a function re-run per request |
832
871
  | `authEndpoints` | `['auth/']` | paths that should **not** emit `unauthorized` on 401 |
833
872
  | `parseError` | built-in | map an error body to `{ code, message, details, hint }` |
873
+ | `trace` | `false` | emit a W3C `traceparent` header on every request |
834
874
 
835
875
  `headers` as a function is the hook for runtime tokens — a bearer token or any
836
876
  short-lived credential — re-evaluated on every request.
837
877
 
878
+ `trace: true` mints a fresh root trace per request. The stitchkit server
879
+ [continues an inbound `traceparent`](./observability.md#trace-context), so the
880
+ browser call, the HTTP handler and every nested tool call share one trace id
881
+ end-to-end. A `traceparent` you set yourself (via `headers`) always wins.
882
+
838
883
  Retry is deliberately conservative: only a connection that never landed (a
839
884
  network error), only on idempotent `GET`. A server that *responded* with a 5xx
840
885
  is the data layer's call (TanStack Query), not the transport's — retrying in
@@ -1053,6 +1098,27 @@ to a verb-aware name from the method + prefix (`list` → `list_widgets`, `get`
1053
1098
  `get_widget`); set `toolName` for an explicit one. See
1054
1099
  [Contracts → transports](./contracts.md#transports).
1055
1100
 
1101
+ ### Pinning tool names — `listToolNames`
1102
+
1103
+ Derived tool names are part of your public surface — an MCP client config or an
1104
+ agent prompt refers to them by string. `listToolNames(services)` resolves every
1105
+ tool name your services expose (the exact resolver the mounts use), with its
1106
+ `(service, method)` identity and transports, sorted — a stable shape to
1107
+ snapshot:
1108
+
1109
+ ```ts
1110
+ import { listToolNames } from 'stitchkit/tools'
1111
+
1112
+ test('tool names have not drifted', () => {
1113
+ expect(listToolNames(services)).toMatchSnapshot()
1114
+ })
1115
+ ```
1116
+
1117
+ A stitchkit upgrade (or a contract refactor) that would shift a derived name
1118
+ now fails this test instead of silently breaking the clients that call the
1119
+ tool. It is also the mechanical diff when migrating a service: run it before
1120
+ and after, compare.
1121
+
1056
1122
  ## MCP — `createMcpHandler`
1057
1123
 
1058
1124
  `createMcpHandler` builds a complete Streamable-HTTP MCP server as a single
@@ -1393,6 +1459,25 @@ const handleMcp = createMcpHandler({
1393
1459
  })
1394
1460
  ```
1395
1461
 
1462
+ ## Logging tool calls — `createToolLogger`
1463
+
1464
+ Every tool mount fires an `afterToolCall` hook. `createToolLogger` is a ready
1465
+ preset for it — one line logs each call (ok / failed, duration, which endpoint,
1466
+ keyed by the endpoint's stable `serviceName` / `key` identity):
1467
+
1468
+ ```ts
1469
+ import { createToolLogger } from 'stitchkit/tools'
1470
+
1471
+ mountMcp(server, services, { hooks: createToolLogger() })
1472
+ // [tool] ok list_widgets (widgets.list) 12ms
1473
+ // [tool] warn get_widget (widgets.get) NOT_FOUND 4ms
1474
+ ```
1475
+
1476
+ Pass `log` to redirect the line, or `onRecord` to feed a metrics sink the
1477
+ structured `ToolCallRecord`. For a boot-time picture of what is exposed where,
1478
+ `summarizeTransports(services)` returns per-transport operation counts (HTTP /
1479
+ MCP / AGENT / CLI) for you to log.
1480
+
1396
1481
  ## One handler, three callers
1397
1482
 
1398
1483
  A contract handler runs the same for an HTTP request, an MCP tool call and an
@@ -1811,6 +1896,35 @@ onSuccess: () => bridge.markFresh(['notes'])
1811
1896
  to the socket and call `queryClient` directly. The bridge just centralises the
1812
1897
  event-to-cache mapping and the echo guard.
1813
1898
 
1899
+ ### Entity cache handlers
1900
+
1901
+ The created / updated / deleted events of one entity almost always patch the
1902
+ cache the same way: prepend to the list, replace by id, remove by id — plus the
1903
+ detail query. `createEntityCacheHandlers` builds those three handlers from a
1904
+ small config, so you wire them onto the bridge instead of hand-rolling the
1905
+ updater per entity:
1906
+
1907
+ ```ts
1908
+ import { createEntityCacheHandlers } from 'stitchkit/react'
1909
+
1910
+ const widgetCache = createEntityCacheHandlers<Widget>({
1911
+ getId: (w) => w.id,
1912
+ listKey: ['widgets'],
1913
+ detailKey: (id) => ['widgets', id],
1914
+ })
1915
+
1916
+ createCacheBridge({ socket, queryClient, handlers: {
1917
+ widgetCreated: widgetCache.created,
1918
+ widgetUpdated: widgetCache.updated,
1919
+ widgetDeleted: widgetCache.deleted,
1920
+ }})
1921
+ ```
1922
+
1923
+ It patches stitchkit's `Paginated<T>` list envelope (plain or an infinite list
1924
+ of pages) and honours the same `isFresh` echo guard. It deliberately does **not**
1925
+ flatten pages or add a `useAllX` surface — flattening stays in the component;
1926
+ this only keeps the cache correct.
1927
+
1814
1928
  ## Raw binary lane (Bun)
1815
1929
 
1816
1930
  Socket.IO carries binary fine — for most streams a binary event (`pcm(frame)`)
@@ -2195,6 +2309,54 @@ onError: (ctx, err) => {
2195
2309
  }
2196
2310
  ```
2197
2311
 
2312
+ ## Domain errors — `defineErrors`
2313
+
2314
+ Declaring your app's error codes once gives you typed throwers on the server and
2315
+ a code table the client matches with autocomplete — instead of reading the raw
2316
+ `message` string (which breaks the moment a code expects a string but gets an
2317
+ object):
2318
+
2319
+ ```ts
2320
+ export const { errors, codes, isCode } = defineErrors({
2321
+ SESSION_NOT_FOUND: 404,
2322
+ QUOTA_EXCEEDED: 429,
2323
+ })
2324
+
2325
+ // server — a typed thrower, the right HTTP status baked in
2326
+ throw errors.SESSION_NOT_FOUND('no such session')
2327
+
2328
+ // client — match the code, never a magic string
2329
+ if (err instanceof ApiError && err.code === codes.SESSION_NOT_FOUND) { … }
2330
+ ```
2331
+
2332
+ The `code` rides through unchanged in both the HTTP envelope and the MCP tool
2333
+ result, so one vocabulary covers every transport. The codes are yours; the core
2334
+ stays domain-free.
2335
+
2336
+ ## `createErrorHook`
2337
+
2338
+ `createErrorHook` is the code-map above, packaged — you supply the exhaustive
2339
+ `codeMap` and the envelope shape, it does the normalisation (including the
2340
+ never-leak-an-internal-message rule for a raw throw):
2341
+
2342
+ ```ts
2343
+ const onError = createErrorHook({
2344
+ codeMap: {
2345
+ BAD_REQUEST: 'bad_request', VALIDATION_ERROR: 'bad_request',
2346
+ UNAUTHORIZED: 'unauthenticated', FORBIDDEN: 'forbidden',
2347
+ NOT_FOUND: 'not_found', METHOD_NOT_ALLOWED: 'not_found',
2348
+ CONFLICT: 'conflict', RATE_LIMITED: 'rate_limited',
2349
+ INTERNAL_SERVER_ERROR: 'internal',
2350
+ } satisfies Record<StitchErrorCode, string>,
2351
+ render: (info) => ({ ok: false, error: { code: info.code, message: info.message } }),
2352
+ })
2353
+
2354
+ createServer({ services, hooks: { onError } })
2355
+ ```
2356
+
2357
+ Codes you threw yourself (not stitchkit's) pass through `codeMap` unchanged; the
2358
+ `satisfies Record<StitchErrorCode, …>` keeps the map exhaustive across upgrades.
2359
+
2198
2360
 
2199
2361
  ==============================================================================
2200
2362
  # Guide: Observability (docs/guide/observability.md)
@@ -2361,6 +2523,17 @@ You rarely call the trace functions directly — `wrapInRequestContext` and
2361
2523
  `parseTraceparent`, `formatTraceparent`, `childSpan`) for when you need to
2362
2524
  propagate a `traceparent` onward to another service.
2363
2525
 
2526
+ The browser side starts the trace:
2527
+ [`createHttpClient({ trace: true })`](./client.md#httpclientconfig) emits a
2528
+ fresh root `traceparent` on every request, which the server then continues. The
2529
+ trace helpers themselves are browser-safe and also exported from the root
2530
+ `stitchkit` entry — a custom client can format its own header.
2531
+
2532
+ The default CORS allow-list already permits `traceparent` / `tracestate`, so a
2533
+ cross-origin `trace: true` client works out of the box. If you set a custom
2534
+ `cors.headers`, extend `DEFAULT_CORS_ALLOW_HEADERS` rather than replacing it, or
2535
+ the preflight will reject the trace header.
2536
+
2364
2537
  > **Span ids live in the request context, not on `ctx`.** The handler `ctx`
2365
2538
  > carries a single `traceId`; the full `{ traceId, spanId, parentSpanId }` is on
2366
2539
  > the observability request context. To stamp `spanId` / `parentSpanId` into an
@@ -2859,6 +3032,20 @@ The browser-and-server entrypoint. Re-exports everything from
2859
3032
  | `RetainedTopics` | _type_ | the `createRetainedTopics` handle |
2860
3033
  | `ParseSSEOptions` | _type_ | options for `parseSSE` |
2861
3034
 
3035
+ ### Trace (client)
3036
+
3037
+ Browser-safe W3C trace helpers — the same functions as on
3038
+ [`stitchkit/observability`](#stitchkitobservability), re-exported so a client
3039
+ can emit / propagate a `traceparent` (see `HttpClientConfig.trace`).
3040
+
3041
+ | Export | Kind | Summary |
3042
+ |--------|------|---------|
3043
+ | `createTraceContext` | function | a fresh root trace |
3044
+ | `formatTraceparent` | function | render a `traceparent` header value |
3045
+ | `parseTraceparent` | function | parse a `traceparent` header |
3046
+ | `childSpan` | function | a child span of a parent trace |
3047
+ | `TraceContext` | _type_ | `{ traceId, spanId, parentSpanId? }` |
3048
+
2862
3049
  ---
2863
3050
 
2864
3051
  ## `stitchkit/contract`
@@ -2871,6 +3058,8 @@ from the root `stitchkit`.
2871
3058
  | Export | Kind | Summary |
2872
3059
  |--------|------|---------|
2873
3060
  | `defineContract` | function | declare a contract — [guide](../guide/contracts.md#definecontract) |
3061
+ | `createContractFactory` | function | a `defineContract` with a required, typed `scope` — [guide](../guide/contracts.md#scope) |
3062
+ | `ScopedDefineContract` | _type_ | the `defineContract` `createContractFactory` returns |
2874
3063
  | `ALL_TRANSPORTS` | constant | `['HTTP', 'MCP', 'AGENT', 'CLI']` |
2875
3064
  | `ContractDef` | _type_ | a defined contract |
2876
3065
  | `ContractMeta` | _type_ | a contract's `prefix` + optional `scope` |
@@ -2887,6 +3076,8 @@ from the root `stitchkit`.
2887
3076
  | `ScopedEndpointFn` | _type_ | one method's signature with the consumed keys folded in |
2888
3077
  | `MultipartFile` | _type_ | a `multipart` file field — `Blob \| FileDescriptor` |
2889
3078
  | `FileDescriptor` | _type_ | a React Native / Expo file — `{ uri, name, type }` |
3079
+ | `EndpointToolAnnotations` | _type_ | MCP behavioural hints on an endpoint (`readOnlyHint` / `destructiveHint` / `title`) |
3080
+ | `EndpointUiMeta` | _type_ | MCP Apps widget metadata on an endpoint |
2890
3081
 
2891
3082
  ### Errors
2892
3083
 
@@ -2901,6 +3092,9 @@ from the root `stitchkit`.
2901
3092
  | `conflict` | function | throw `409 CONFLICT` |
2902
3093
  | `rateLimited` | function | throw `429 RATE_LIMITED` |
2903
3094
  | `appError` | function | throw an `AppError` for any code |
3095
+ | `defineErrors` | function | declare domain error codes → typed throwers + a code table — [guide](../guide/auth-and-errors.md#domain-errors--defineerrors) |
3096
+ | `DefinedErrors` | _type_ | the `{ errors, codes, isCode }` handle `defineErrors` returns |
3097
+ | `ErrorThrower` | _type_ | one `defineErrors` thrower — `(message?, details?, hint?) => never` |
2904
3098
  | `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) |
2905
3099
  | `StitchErrorCode` | _type_ | a code stitchkit itself emits (`keyof STITCH_ERROR_STATUS`) |
2906
3100
  | `isStitchErrorCode` | function | type guard — is a code one of stitchkit's own? |
@@ -2956,14 +3150,22 @@ Also re-exports the error helpers from `stitchkit/contract`.
2956
3150
  | Export | Kind | Summary |
2957
3151
  |--------|------|---------|
2958
3152
  | `createAuthHook` | function | a scope-enforcing `beforeHandle` hook — [guide](../guide/auth-and-errors.md#createauthhook) |
3153
+ | `createErrorHook` | function | an `onError` hook from a code map + envelope renderer — [guide](../guide/auth-and-errors.md#createerrorhook) |
3154
+ | `ErrorHookConfig` | _type_ | config for `createErrorHook` |
3155
+ | `ResolvedError` | _type_ | the normalised error handed to `createErrorHook`'s `render` |
2959
3156
  | `createBearerResolver` | function | a bearer-token identity resolver |
3157
+ | `signJwt` | function | sign an HS256 JWT |
2960
3158
  | `verifyJwt` | function | verify an HS256 JWT |
2961
3159
  | `extractToken` | function | read a bearer token from header or cookie |
3160
+ | `deriveCodeChallenge` | function | PKCE — derive the `code_challenge` from a verifier |
3161
+ | `verifyPkce` | function | PKCE — verify a verifier against a stored challenge |
2962
3162
  | `AuthHook` | _type_ | the hook `createAuthHook` returns |
2963
3163
  | `AuthHookConfig` | _type_ | config for `createAuthHook` |
2964
3164
  | `AuthRule` | _type_ | `'public' \| 'authenticated' \| predicate` |
2965
3165
  | `BearerResolverConfig` | _type_ | config for `createBearerResolver` |
2966
3166
  | `JwtPayload` | _type_ | a decoded JWT payload |
3167
+ | `SignJwtOptions` | _type_ | options for `signJwt` (expiry, claims) |
3168
+ | `PkceMethod` | _type_ | the PKCE challenge method — `'S256' \| 'plain'` |
2967
3169
 
2968
3170
  ### Cookies & CORS
2969
3171
 
@@ -2974,6 +3176,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
2974
3176
  | `serializeCookie` | function | build a `Set-Cookie` value |
2975
3177
  | `corsHeaders` | function | compute CORS response headers |
2976
3178
  | `corsPreflightResponse` | function | build a preflight `Response` |
3179
+ | `DEFAULT_CORS_ALLOW_HEADERS` | const | the default `Access-Control-Allow-Headers` (incl. `traceparent`) — extend it when overriding `cors.headers` |
2977
3180
  | `CookieDef` | _type_ | the `defineCookie` handle |
2978
3181
  | `CookieOptions` | _type_ | cookie attributes |
2979
3182
  | `CorsConfig` | _type_ | CORS policy |
@@ -3006,9 +3209,11 @@ Also re-exports the error helpers from `stitchkit/contract`.
3006
3209
  | `generateTraceId` | function | a fresh trace id |
3007
3210
  | `resolveTraceId` | function | the default per-request trace-id resolver |
3008
3211
  | `extractIp` | function | the caller IP from a request |
3212
+ | `resolveSocketIp` | function | the caller IP for a Socket.IO handshake (proxy-aware) |
3009
3213
  | `getClientInfo` | function | caller IP + user-agent |
3010
3214
  | `EventBus` | _type_ | the `createEventBus` handle |
3011
3215
  | `RateLimitConfig` | _type_ | config for `createRateLimiter` |
3216
+ | `ClientIpOptions` | _type_ | trusted-proxy config for `extractIp` / `resolveSocketIp` |
3012
3217
  | `ParseSSEOptions` | _type_ | options for `parseSSE` |
3013
3218
 
3014
3219
  ### OpenAPI
@@ -3048,6 +3253,8 @@ audit event. See the [Observability guide](../guide/observability.md).
3048
3253
  | `getTraceId` | function | the active trace id — pass as `traceId` to `createServer` |
3049
3254
  | `getUserId` | function | the active user id, once auth has resolved it |
3050
3255
  | `setRequestUser` | function | attach the resolved user to the active context |
3256
+ | `setRequestEndpoint` | function | attach the resolved endpoint identity to the active context |
3257
+ | `setRequestDimensions` | function | attach custom audit dimensions to the active context |
3051
3258
  | `setRequestError` | function | record the error outcome on the active context |
3052
3259
  | `runWithRequestContext` | function | run a function inside a given context |
3053
3260
  | `RequestContext` | _type_ | the per-request record |
@@ -3095,6 +3302,7 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
3095
3302
  | `mountViewFile` | function | a native multimodal "view file" MCP tool |
3096
3303
  | `resolveMedia` | function | resolve a media reference for a tool result |
3097
3304
  | `validateMcpSchemas` | function | assert every tool schema is JSON Schema-compatible — [guide](../guide/mcp-and-agents.md#incompatible-schemas--onincompatibleschema) |
3305
+ | `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
3306
  | `McpHandlerConfig` | _type_ | config for `createMcpHandler` |
3099
3307
  | `StdioMcpServerConfig` | _type_ | config for `createStdioMcpServer` |
3100
3308
  | `McpServerBuildConfig` | _type_ | shared config for `buildMcpServer` |
@@ -3109,10 +3317,98 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
3109
3317
  | `ToolExtend` | _type_ | extra-args extension for `mountMcp` / `mountAgent` |
3110
3318
  | `ToolLifecycle` | _type_ | `beforeHandle` / `afterHandle` gate for tool calls — [guide](../guide/mcp-and-agents.md#guarding-tools--lifecycle) |
3111
3319
  | `ToolCallHooks` | _type_ | `beforeToolCall` / `afterToolCall` observability hooks |
3320
+ | `ErrorHintFn` | _type_ | `(toolName, errorCode) => string \| null` — a per-tool recovery hint, shared by every mount |
3112
3321
  | `ToolResult` | _type_ | the result of one tool call |
3322
+ | `ToolNameEntry` | _type_ | one `listToolNames` row — `{ name, service, method, transports }` |
3113
3323
  | `IncompatibleSchemaPolicy` | _type_ | `'throw' \| 'skip' \| 'warn'` |
3114
3324
  | `McpMediaContent` | _type_ | a multimodal MCP content item |
3115
3325
 
3326
+ ### Native tools
3327
+
3328
+ Generic host-supplied tools mounted onto a server — not derived from a contract.
3329
+
3330
+ | Export | Kind | Summary |
3331
+ |--------|------|---------|
3332
+ | `mountDownload` | function | a "download a URL to disk" tool (SSRF-guarded, size-capped) |
3333
+ | `mountUpload` | function | an "upload a local file" tool |
3334
+ | `mountWait` | function | a generic `--wait`-style polling tool |
3335
+ | `DownloadToolConfig` | _type_ | config for `mountDownload` |
3336
+ | `UploadToolConfig` | _type_ | config for `mountUpload` |
3337
+ | `WaitToolConfig` | _type_ | config for `mountWait` |
3338
+
3339
+ ### OAuth 2.1 provider
3340
+
3341
+ A native remote-connector auth surface for MCP — [guide](../guide/mcp-and-agents.md#oauth-21--a-native-remote-connector).
3342
+
3343
+ | Export | Kind | Summary |
3344
+ |--------|------|---------|
3345
+ | `mountOAuthProvider` | function | the OAuth 2.1 provider routes (DCR, PKCE, token) |
3346
+ | `oauthProtectedResourceRoute` | function | the RFC 9728 protected-resource-metadata route |
3347
+ | `protectedResourceMetadataUrl` | function | build the metadata URL for a resource |
3348
+ | `wwwAuthenticateHeader` | function | build the `WWW-Authenticate` challenge header |
3349
+ | `PROTECTED_RESOURCE_PATH` | const | the well-known metadata path |
3350
+ | `OAuthProviderConfig` | _type_ | config for `mountOAuthProvider` |
3351
+ | `ProtectedResourceConfig` | _type_ | config for `oauthProtectedResourceRoute` |
3352
+ | `AuthCodeData` | _type_ | a stored authorization-code record |
3353
+ | `AuthRequest` | _type_ | a parsed authorization request |
3354
+ | `ClientMetadata` | _type_ | dynamic-client-registration metadata |
3355
+ | `RefreshData` | _type_ | a stored refresh-token record |
3356
+ | `RegisteredClient` | _type_ | a registered OAuth client |
3357
+
3358
+ ### MCP Apps (widgets)
3359
+
3360
+ Interactive MCP resources — [ADR 0019](../decisions/0019-generic-native-tools.md).
3361
+
3362
+ | Export | Kind | Summary |
3363
+ |--------|------|---------|
3364
+ | `mountMcpResource` | function | mount an MCP Apps widget resource |
3365
+ | `inlineMcpAppBundle` | function | inline a built widget bundle into a resource |
3366
+ | `EXT_APPS_BUNDLE_PLACEHOLDER` | const | the placeholder token `inlineMcpAppBundle` replaces |
3367
+ | `RESOURCE_MIME_TYPE` | const | the MCP Apps resource MIME type |
3368
+ | `McpResourceDef` | _type_ | an MCP Apps resource definition |
3369
+ | `McpAppResourceMeta` | _type_ | resource `_meta` for an MCP App |
3370
+ | `McpAppCsp` | _type_ | the widget content-security policy |
3371
+
3372
+ ### Introspection & internals
3373
+
3374
+ Advanced building blocks — the shared machinery the mounts are built on.
3375
+
3376
+ | Export | Kind | Summary |
3377
+ |--------|------|---------|
3378
+ | `collectTools` | function | resolve a service's methods to mountable tools (the shared resolver) |
3379
+ | `createToolLogger` | function | a ready `afterToolCall` that logs every tool call — [guide](../guide/mcp-and-agents.md#logging-tool-calls--createtoollogger) |
3380
+ | `summarizeTransports` | function | per-transport operation counts for a boot-time summary |
3381
+ | `buildToolManifest` | function | a searchable `{ name, description, inputSchema }` manifest for a `tool_search` tool |
3382
+ | `ToolLoggerConfig` | _type_ | config for `createToolLogger` |
3383
+ | `ToolCallRecord` | _type_ | the structured record `createToolLogger` passes to `onRecord` |
3384
+ | `TransportSummary` | _type_ | the result of `summarizeTransports` |
3385
+ | `TransportCounts` | _type_ | per-transport counts (`{ HTTP, MCP, AGENT, CLI }`) |
3386
+ | `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments |
3387
+ | `flattenDiscriminatedUnion` | function | flatten one discriminated union into a single object schema |
3388
+ | `flattenUnionsDeep` | function | flatten discriminated unions at every depth (advertised schema only) |
3389
+ | `MountableTool` | _type_ | one contract method resolved for mounting |
3390
+ | `ToolManifestEntry` | _type_ | one `buildToolManifest` row |
3391
+
3392
+ ---
3393
+
3394
+ ## `stitchkit/node`
3395
+
3396
+ Server-only, for Node ≥ 22 (Bun uses `stitchkit/server`). The runtime-agnostic
3397
+ core plus a Node HTTP adapter — [ADR 0013](../decisions/0013-runtime-agnostic-core.md),
3398
+ [deployment guide](../guide/testing-and-deployment.md#node). Re-exports the
3399
+ runtime-agnostic pieces of `stitchkit/server` and the error helpers.
3400
+
3401
+ | Export | Kind | Summary |
3402
+ |--------|------|---------|
3403
+ | `serveNode` | function | build the router and start a Node HTTP server (via `srvx`) |
3404
+ | `createHandler` | function | the router as a bare `(req) => Response` (same as `/server`) |
3405
+ | `createSocketIOServer` | function | the typed Socket.IO server (same as `/server`) |
3406
+ | `implement` / `createImplement` | function | bind a contract to typed handlers (same as `/server`) |
3407
+ | `NodeServerConfig` | _type_ | config for `serveNode` |
3408
+ | `NodeServerHandle` | _type_ | the `serveNode` handle (`{ port, stop }`) |
3409
+ | `HandlerConfig` / `ServiceDef` / `RawRoute` / `RawRouteContext` / `SocketIOServerConfig` / `SocketIOServerHandle` | _type_ | re-exported from `/server` |
3410
+ | `AppError` + `appError` / `badRequest` / `unauthorized` / `forbidden` / `notFound` / `conflict` / `rateLimited` | — | error helpers (same as `/contract`) |
3411
+
3116
3412
  ---
3117
3413
 
3118
3414
  ## `stitchkit/cli`
@@ -3147,6 +3443,10 @@ and `react-query-kit` peers.
3147
3443
  |--------|------|---------|
3148
3444
  | `createCursorQuery` | function | a cursor-paginated infinite query — [guide](../guide/client.md#cursor-pagination) |
3149
3445
  | `createCacheBridge` | function | sync socket events into the Query cache — [guide](../guide/realtime.md#cache-bridge) |
3446
+ | `createEntityCacheHandlers` | function | created/updated/deleted cache handlers for one entity — [guide](../guide/realtime.md#entity-cache-handlers) |
3447
+ | `EntityCacheConfig` | _type_ | config for `createEntityCacheHandlers` |
3448
+ | `EntityCacheHandlers` | _type_ | the `{ created, updated, deleted }` handlers it returns |
3449
+ | `DeletedPayload` | _type_ | a `deleted` event payload — the entity or a bare `{ id }` |
3150
3450
  | `CursorQueryConfig` | _type_ | config for `createCursorQuery` |
3151
3451
  | `CacheBridge` | _type_ | the `createCacheBridge` handle |
3152
3452
  | `CacheBridgeConfig` | _type_ | config for `createCacheBridge` |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.17.0",
3
+ "version": "0.19.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",