stitchkit 0.41.0 → 0.43.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/README.md +22 -5
- package/dist/cli.js +2 -2
- package/dist/contract/errors-factory.d.ts +88 -15
- package/dist/contract/errors-factory.d.ts.map +1 -1
- package/dist/contract/errors.d.ts +4 -4
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/factory.d.ts +29 -2
- package/dist/contract/factory.d.ts.map +1 -1
- package/dist/contract/index.d.ts +2 -2
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +2 -2
- package/dist/{index-dnkefke9.js → index-310bfer5.js} +1 -1
- package/dist/{index-4e1c0hsw.js → index-esqmem78.js} +101 -67
- package/dist/{index-sc67e454.js → index-gex6gxhe.js} +7 -4
- package/dist/{index-ncqqn1bc.js → index-h2wdcsby.js} +95 -17
- package/dist/{index-xax049k6.js → index-r6czv7yt.js} +58 -22
- package/dist/{index-809wc1tt.js → index-s4qsmgwe.js} +4 -1
- package/dist/index.js +2 -2
- package/dist/internal/typed.d.ts +8 -0
- package/dist/internal/typed.d.ts.map +1 -1
- package/dist/node.js +2 -2
- package/dist/observability/audit.d.ts +27 -15
- package/dist/observability/audit.d.ts.map +1 -1
- package/dist/observability/event.d.ts +2 -2
- package/dist/observability/index.d.ts +4 -4
- package/dist/observability/index.d.ts.map +1 -1
- package/dist/observability/index.js +70 -59
- package/dist/react.js +1 -1
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/error-hook.d.ts +8 -4
- package/dist/server/error-hook.d.ts.map +1 -1
- package/dist/server/index.js +7 -6
- package/dist/server/logger.d.ts +0 -1
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/types.d.ts +8 -13
- package/dist/server/types.d.ts.map +1 -1
- package/dist/tools/agent.d.ts +1 -1
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/flatten-join.d.ts.map +1 -1
- package/dist/tools/list-names.d.ts +8 -6
- package/dist/tools/list-names.d.ts.map +1 -1
- package/dist/tools/manifest.d.ts +13 -5
- package/dist/tools/manifest.d.ts.map +1 -1
- package/dist/tools/mcp-handler.d.ts +5 -4
- package/dist/tools/mcp-handler.d.ts.map +1 -1
- package/dist/tools/mcp-stdio.d.ts +3 -2
- package/dist/tools/mcp-stdio.d.ts.map +1 -1
- package/dist/tools/mcp.d.ts +49 -9
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/native-mcp.d.ts +4 -18
- package/dist/tools/native-mcp.d.ts.map +1 -1
- package/dist/tools/runtime-tool.d.ts +32 -1
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/surface.d.ts +37 -0
- package/dist/tools/surface.d.ts.map +1 -0
- package/dist/tools/toolkit.d.ts +4 -4
- package/dist/tools/toolkit.d.ts.map +1 -1
- package/dist/tools/transports.d.ts +11 -9
- package/dist/tools/transports.d.ts.map +1 -1
- package/dist/tools/view-file.d.ts +3 -3
- package/dist/tools.d.ts +6 -6
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +330 -155
- package/llms-full.txt +524 -131
- package/llms.txt +1 -0
- package/package.json +2 -2
package/llms-full.txt
CHANGED
|
@@ -44,7 +44,7 @@ keeping server-only code (`Bun.serve`, the MCP SDK) out of browser bundles.
|
|
|
44
44
|
| `stitchkit/node` | server (Node ≥ 22) | `serveNode` + the runtime-agnostic core — the Node mirror of `/server` |
|
|
45
45
|
| `stitchkit/tools` | server | `createMcpHandler`, `mountMcp`, `mountAgent`, the OAuth provider, native tools |
|
|
46
46
|
| `stitchkit/cli` | server | `createCli` — the CLI transport, light (no MCP SDK / `ai`) |
|
|
47
|
-
| `stitchkit/observability` | server |
|
|
47
|
+
| `stitchkit/observability` | server | request/tool event projections — `createObservability`, trace context, sanitisation |
|
|
48
48
|
| `stitchkit/react` | browser | `createCursorQuery`, `createCacheBridge` |
|
|
49
49
|
|
|
50
50
|
Rule of thumb: browser code imports `stitchkit` and `stitchkit/react`; server
|
|
@@ -168,7 +168,9 @@ actionable error naming the package and the install command — not a bare
|
|
|
168
168
|
- [Testing & deployment](./testing-and-deployment.md).
|
|
169
169
|
- [API reference](../api/reference.md) — every export, by entrypoint.
|
|
170
170
|
|
|
171
|
-
|
|
171
|
+
For a complete production-shaped app, run `bun create stitchkit my-app`. The
|
|
172
|
+
canonical generated topology is maintained in
|
|
173
|
+
[`packages/create-stitchkit/template`](../../packages/create-stitchkit/template).
|
|
172
174
|
|
|
173
175
|
|
|
174
176
|
==============================================================================
|
|
@@ -339,6 +341,19 @@ tools. Narrow it with `expose`:
|
|
|
339
341
|
- `expose: ['MCP', 'AGENT']` — a tool only; no HTTP route.
|
|
340
342
|
- omit `expose` — all transports.
|
|
341
343
|
|
|
344
|
+
For a curated or security-sensitive tool surface, opt into explicit tool
|
|
345
|
+
exposure at the scoped factory:
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
const { defineContract } = createContractFactory<AppScope>({
|
|
349
|
+
toolExposure: 'explicit',
|
|
350
|
+
})
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
With this policy, omitting `expose` materializes `['HTTP']` on the returned
|
|
354
|
+
endpoint. MCP, Agent and CLI then require an explicit endpoint array. The plain
|
|
355
|
+
factory and `defineContract` keep the default-on behaviour above.
|
|
356
|
+
|
|
342
357
|
Tool transports (`MCP`, `AGENT`) skip three kinds of endpoint automatically:
|
|
343
358
|
`multipart` (a file upload is not a tool call) and
|
|
344
359
|
[`rawResponse`](./server.md#raw-response-endpoints) (its answer is bytes, which
|
|
@@ -542,9 +557,11 @@ export const { defineContract } = createContractFactory<'public' | 'user' | 'adm
|
|
|
542
557
|
export const users = defineContract({ prefix: 'users', scope: 'user' }, { … })
|
|
543
558
|
```
|
|
544
559
|
|
|
545
|
-
The vocabulary is yours; each returned `
|
|
546
|
-
literal, so scope-aware registries select
|
|
547
|
-
wrapper.
|
|
560
|
+
The vocabulary is yours; each returned `ScopedContractDef` retains its concrete
|
|
561
|
+
scope literal as a required `meta.scope` field, so scope-aware registries select
|
|
562
|
+
the exact config without `NonNullable` or another wrapper. Plain `ContractDef`
|
|
563
|
+
keeps optional scope because ordinary `defineContract` still supports its
|
|
564
|
+
default-public overload.
|
|
548
565
|
|
|
549
566
|
## One source of truth
|
|
550
567
|
|
|
@@ -1664,6 +1681,11 @@ The server side is [`streamSSE`](./server.md#sse-streaming).
|
|
|
1664
1681
|
|
|
1665
1682
|
# MCP & AI agents
|
|
1666
1683
|
|
|
1684
|
+
The application generated by `bun create stitchkit` includes a working
|
|
1685
|
+
stateless MCP endpoint and a separate `bun run tools` manifest command. Both
|
|
1686
|
+
are assembled from the same implemented project contracts as HTTP and CLI, so
|
|
1687
|
+
the generated project is the canonical end-to-end example for this chapter.
|
|
1688
|
+
|
|
1667
1689
|
The same contract that drives the HTTP API also drives AI tooling. An endpoint
|
|
1668
1690
|
exposed on `MCP` becomes a [Model Context Protocol](https://modelcontextprotocol.io)
|
|
1669
1691
|
tool — callable from Claude, Cursor and other MCP clients. An endpoint exposed on
|
|
@@ -1703,16 +1725,16 @@ call —
|
|
|
1703
1725
|
### Pinning tool names — `listToolNames`
|
|
1704
1726
|
|
|
1705
1727
|
Derived tool names are part of your public surface — an MCP client config or an
|
|
1706
|
-
agent prompt refers to them by string. `listToolNames(services)`
|
|
1707
|
-
|
|
1708
|
-
`(service, method)` identity and transports, sorted — a stable shape
|
|
1709
|
-
snapshot:
|
|
1728
|
+
agent prompt refers to them by string. `listToolNames({ services, runtimeTools })`
|
|
1729
|
+
resolves the complete mixed surface (the exact resolver the mounts use), with
|
|
1730
|
+
its origin, `(service, method)` identity and transports, sorted — a stable shape
|
|
1731
|
+
to snapshot:
|
|
1710
1732
|
|
|
1711
1733
|
```ts
|
|
1712
1734
|
import { listToolNames } from 'stitchkit/tools'
|
|
1713
1735
|
|
|
1714
1736
|
test('tool names have not drifted', () => {
|
|
1715
|
-
expect(listToolNames(services)).toMatchSnapshot()
|
|
1737
|
+
expect(listToolNames({ services, runtimeTools })).toMatchSnapshot()
|
|
1716
1738
|
})
|
|
1717
1739
|
```
|
|
1718
1740
|
|
|
@@ -1806,13 +1828,16 @@ createServer({
|
|
|
1806
1828
|
| `serverInfo` | MCP server identity — `{ name, version }` |
|
|
1807
1829
|
| `auth` | `(req) => identity \| null` — `null` rejects with 401 |
|
|
1808
1830
|
| `services` | the services to expose — an array, or `(auth) => ServiceDef[]` |
|
|
1831
|
+
| `runtimeTools` | managed pathless operations — an array, or `(auth) => RuntimeToolDefinition[]` |
|
|
1832
|
+
| `surfaces` | finite `{ key: { services, runtimeTools } }` registry prepared eagerly |
|
|
1833
|
+
| `selectSurface` | `(auth) => declaredKey` — required with `surfaces` |
|
|
1809
1834
|
| `context` | `(auth) => {…}` — values merged into every tool handler's `ctx` |
|
|
1810
1835
|
| `lifecycle` | `beforeHandle` / `afterHandle` — the tool-side auth gate (see below) |
|
|
1811
1836
|
| `hooks` | tool-call observability hooks — `afterToolCall` fires on every result |
|
|
1812
1837
|
| `extend` | extra advertised arguments resolved into handler context |
|
|
1813
1838
|
| `schemaValidation` | compatibility policy, typed-property guard and portable-format guard |
|
|
1814
1839
|
| `logger` | a `StitchLogger` for the `'warn'` policy |
|
|
1815
|
-
| `
|
|
1840
|
+
| `rawTools` | `(server, auth) => …` — explicit SDK registration without framework guarantees |
|
|
1816
1841
|
| `resources` | MCP Apps `ui://` resources mounted on every server |
|
|
1817
1842
|
| `instructions` | a short host-facing usage hint, surfaced to MCP tool-search |
|
|
1818
1843
|
| `coerceJsonArgs` | coerce JSON-stringified object/array arguments (default `true`) |
|
|
@@ -1822,14 +1847,37 @@ createServer({
|
|
|
1822
1847
|
| `protectedResource` | RFC 9728 metadata used by HTTP `401` responses |
|
|
1823
1848
|
| `sessionMode` | `'stateless'` (default) or explicit `'stateful'` session/SSE continuity |
|
|
1824
1849
|
|
|
1825
|
-
`services
|
|
1826
|
-
tenant can be shown only its own
|
|
1850
|
+
Direct `services` / `runtimeTools` factories, `context`, `selectSurface` and
|
|
1851
|
+
`rawTools` receive the resolved identity, so a tenant can be shown only its own
|
|
1852
|
+
tools and every handler can read `ctx.tenantId`.
|
|
1853
|
+
|
|
1854
|
+
Use direct factories only when the definitions are genuinely arbitrary per
|
|
1855
|
+
identity. For a bounded role/plan set, declare a finite registry instead:
|
|
1856
|
+
|
|
1857
|
+
```ts
|
|
1858
|
+
const handleMcp = createMcpHandler({
|
|
1859
|
+
serverInfo,
|
|
1860
|
+
auth,
|
|
1861
|
+
surfaces: {
|
|
1862
|
+
admin: { services: allServices, runtimeTools: [renderPreview] },
|
|
1863
|
+
member: { services: memberServices, runtimeTools: [renderPreview] },
|
|
1864
|
+
},
|
|
1865
|
+
selectSurface: (identity) => identity.isAdmin ? 'admin' : 'member',
|
|
1866
|
+
context: (identity) => ({ identity }),
|
|
1867
|
+
})
|
|
1868
|
+
```
|
|
1869
|
+
|
|
1870
|
+
Every declared entry is schema-validated and prepared once when the handler is
|
|
1871
|
+
constructed. The selected immutable descriptors are shared; the SDK server,
|
|
1872
|
+
transport, auth-derived context, lifecycle runner and tool-call context are
|
|
1873
|
+
fresh for every stateless request (or every stateful session). Unknown keys
|
|
1874
|
+
fail before the server connects. The registry never retains auth values.
|
|
1827
1875
|
|
|
1828
1876
|
### Stateless by default; stateful only when required
|
|
1829
1877
|
|
|
1830
1878
|
The default `sessionMode: 'stateless'` creates a fresh SDK server, transport,
|
|
1831
|
-
resolved auth/context and runner for each HTTP request. Static
|
|
1832
|
-
are still prepared once when the handler is constructed. There is no session
|
|
1879
|
+
resolved auth/context and runner for each HTTP request. Static direct and finite
|
|
1880
|
+
registry schemas are still prepared once when the handler is constructed. There is no session
|
|
1833
1881
|
map, event store, sweep timer or `Mcp-Session-Id`, so process replacement and
|
|
1834
1882
|
load balancing cannot strand a client on an in-memory session.
|
|
1835
1883
|
|
|
@@ -1952,8 +2000,14 @@ as one thing. Where they agree, that is what you get; where they disagree — a
|
|
|
1952
2000
|
`.refine()` only one of them carries, two different bounds on the same number, an
|
|
1953
2001
|
enum against a free string — the *constraint* is dropped and the **type** is not.
|
|
1954
2002
|
A field that is a number in every variant is advertised as a number, not as a
|
|
1955
|
-
bare description.
|
|
1956
|
-
|
|
2003
|
+
bare description. When the kinds genuinely differ, the flat projection keeps
|
|
2004
|
+
every provable kind in a deterministic JSON Schema type array — for example
|
|
2005
|
+
`type: ['string', 'array']` — while dropping constraints that are not sound for
|
|
2006
|
+
all branches. Nested `oneOf` / `anyOf` values still contribute their provable
|
|
2007
|
+
base kinds without reintroducing union keywords. Only a branch whose kind is
|
|
2008
|
+
actually unknowable, such as a free-form schema or unresolved reference, leaves
|
|
2009
|
+
the collision unconstrained. → ADRs [0044](../decisions/0044-a-collided-field-keeps-its-type.md)
|
|
2010
|
+
and [0065](../decisions/0065-flat-collisions-preserve-every-known-kind.md)
|
|
1957
2011
|
|
|
1958
2012
|
It is **deep** because the projection walks the generated JSON Schema document,
|
|
1959
2013
|
including objects, arrays, tuples and schema-definition nodes. Structurally
|
|
@@ -1961,6 +2015,10 @@ identifiable discriminated object unions are flattened wherever they occur.
|
|
|
1961
2015
|
Plain unions and unions hidden behind unresolved external references remain
|
|
1962
2016
|
unions because Stitchkit cannot soundly invent a discriminator.
|
|
1963
2017
|
|
|
2018
|
+
Use the default `flattenUnionInput: false` when the model must see the exact
|
|
2019
|
+
relationship between a discriminator and each branch. The flat type array is a
|
|
2020
|
+
sound set of possible JSON kinds, not a reconstruction of those correlations.
|
|
2021
|
+
|
|
1964
2022
|
The flattened form is **lossy but never executable**. Per-variant refinements
|
|
1965
2023
|
and incompatible constraints are widened in the presentation document; the
|
|
1966
2024
|
original Zod contract enforces them exactly once inside `executeToolMethod`.
|
|
@@ -2006,8 +2064,8 @@ than a per-request `(req) => …`. Keep all logging on **stderr**: stdout is the
|
|
|
2006
2064
|
JSON-RPC channel.
|
|
2007
2065
|
|
|
2008
2066
|
Both transports build the server through the shared `buildMcpServer` — same
|
|
2009
|
-
contract pipeline, same
|
|
2010
|
-
|
|
2067
|
+
contract/runtime pipeline, same surface selection, context, hooks, raw escape
|
|
2068
|
+
hatch and instructions.
|
|
2011
2069
|
|
|
2012
2070
|
## OAuth 2.1 — a native remote connector
|
|
2013
2071
|
|
|
@@ -2249,9 +2307,9 @@ const handleMcp = createMcpHandler({
|
|
|
2249
2307
|
serverInfo: { name: 'my-app', version: '1.0.0' },
|
|
2250
2308
|
auth,
|
|
2251
2309
|
services: [service],
|
|
2310
|
+
runtimeTools: [renderPreview],
|
|
2252
2311
|
lifecycle: { beforeHandle: authHook },
|
|
2253
2312
|
hooks: audit.toolCall,
|
|
2254
|
-
nativeTools: ({ registerTool }) => registerTool(renderPreview),
|
|
2255
2313
|
})
|
|
2256
2314
|
|
|
2257
2315
|
const agentTools = mountAgent([service], {
|
|
@@ -2261,6 +2319,33 @@ const agentTools = mountAgent([service], {
|
|
|
2261
2319
|
})
|
|
2262
2320
|
```
|
|
2263
2321
|
|
|
2322
|
+
When several runtime tools share application context and identity, bind both
|
|
2323
|
+
once with `createRuntimeToolFactory`. Its Zod context schema is parsed once per
|
|
2324
|
+
call inside the same runner; the authored handler receives validated context and
|
|
2325
|
+
parsed `input`, while lifecycle, hooks, output validation and presenters remain
|
|
2326
|
+
unchanged:
|
|
2327
|
+
|
|
2328
|
+
```ts
|
|
2329
|
+
const knowledgeTools = createRuntimeToolFactory({
|
|
2330
|
+
serviceName: 'agentKnowledge',
|
|
2331
|
+
scope: 'user',
|
|
2332
|
+
context: z.object({ userId: z.string(), tz: z.string() }),
|
|
2333
|
+
})
|
|
2334
|
+
|
|
2335
|
+
const countRecords = knowledgeTools.define({
|
|
2336
|
+
name: 'count_records',
|
|
2337
|
+
action: 'countRecords',
|
|
2338
|
+
method: 'GET',
|
|
2339
|
+
description: 'Count records',
|
|
2340
|
+
input: z.object({ kind: z.string() }),
|
|
2341
|
+
output: z.object({ count: z.number() }),
|
|
2342
|
+
handler: async ({ userId, tz, input }) => countFor(userId, tz, input.kind),
|
|
2343
|
+
})
|
|
2344
|
+
```
|
|
2345
|
+
|
|
2346
|
+
`serviceName` and `scope` cannot be overridden by one definition. Use standalone
|
|
2347
|
+
`defineRuntimeTool` when tools do not share a context schema or identity.
|
|
2348
|
+
|
|
2264
2349
|
`transports` defaults to `['MCP', 'AGENT']`; set an explicit subset when an
|
|
2265
2350
|
operation belongs on only one surface. The configured identity becomes the
|
|
2266
2351
|
hook/lifecycle `OperationIdentity` and the tool `RequestEvent`
|
|
@@ -2281,7 +2366,7 @@ use the same validation, lifecycle and hook path as contract tools.
|
|
|
2281
2366
|
|
|
2282
2367
|
### Explicit raw SDK registration
|
|
2283
2368
|
|
|
2284
|
-
`
|
|
2369
|
+
`rawTools` is deliberately named as an escape hatch. A tool registered there
|
|
2285
2370
|
does **not** receive stitchkit schema policy, lifecycle, per-call context or
|
|
2286
2371
|
hooks. The built-in `mountViewFile` helper remains raw for callers that choose
|
|
2287
2372
|
that boundary; it fetches media with SSRF and path-traversal defenses:
|
|
@@ -2293,14 +2378,51 @@ const handleMcp = createMcpHandler({
|
|
|
2293
2378
|
serverInfo: { name: 'my-app', version: '1.0.0' },
|
|
2294
2379
|
auth,
|
|
2295
2380
|
services: [service],
|
|
2296
|
-
|
|
2297
|
-
mountViewFile(rawServer, { baseDir: '/srv/uploads' }),
|
|
2381
|
+
rawTools: (server) => mountViewFile(server, { baseDir: '/srv/uploads' }),
|
|
2298
2382
|
})
|
|
2299
2383
|
```
|
|
2300
2384
|
|
|
2301
2385
|
Use raw registration only when opting out is intentional. For a protected
|
|
2302
|
-
`view_file`, define it
|
|
2303
|
-
`resolveMedia` core from its handler.
|
|
2386
|
+
`view_file`, define it with `defineRuntimeTool`, include it in `runtimeTools`,
|
|
2387
|
+
and call the exported `resolveMedia` core from its handler.
|
|
2388
|
+
|
|
2389
|
+
## Introspecting the complete tool surface
|
|
2390
|
+
|
|
2391
|
+
Deferred tool search, name snapshots and boot diagnostics accept the same
|
|
2392
|
+
object-shaped surface as the mounts. Contract operations are followed by
|
|
2393
|
+
runtime definitions, matching mount order; exposure filters and collisions are
|
|
2394
|
+
resolved by Stitchkit rather than by consumer code:
|
|
2395
|
+
|
|
2396
|
+
```ts
|
|
2397
|
+
import {
|
|
2398
|
+
buildToolManifest,
|
|
2399
|
+
listToolNames,
|
|
2400
|
+
summarizeTransports,
|
|
2401
|
+
} from 'stitchkit/tools'
|
|
2402
|
+
|
|
2403
|
+
const surface = { services, runtimeTools: [renderPreview] }
|
|
2404
|
+
|
|
2405
|
+
const manifest = buildToolManifest({
|
|
2406
|
+
...surface,
|
|
2407
|
+
transport: 'AGENT',
|
|
2408
|
+
flattenUnionInput: true,
|
|
2409
|
+
})
|
|
2410
|
+
|
|
2411
|
+
const names = listToolNames(surface)
|
|
2412
|
+
const summary = summarizeTransports(surface)
|
|
2413
|
+
```
|
|
2414
|
+
|
|
2415
|
+
`buildToolManifest` returns the exact immutable presentation schema shown to
|
|
2416
|
+
the selected MCP or Agent transport. It does not execute validation effects and
|
|
2417
|
+
fails first on duplicate contract/runtime names. There is no runtime-only
|
|
2418
|
+
manifest helper or public mount adapter: the framework owns merging and schema
|
|
2419
|
+
projection.
|
|
2420
|
+
|
|
2421
|
+
`listToolNames` remains diagnostic and reports `kind: 'contract' | 'runtime'`
|
|
2422
|
+
for every identity. `summarizeTransports` returns `contractServices`,
|
|
2423
|
+
`runtimeTools`, aggregate `totals`, and a `sources` breakdown. Runtime tools
|
|
2424
|
+
contribute only to MCP/Agent according to their `transports`; they never inflate
|
|
2425
|
+
HTTP or CLI counts.
|
|
2304
2426
|
|
|
2305
2427
|
## Logging tool calls — `createToolLogger`
|
|
2306
2428
|
|
|
@@ -2320,9 +2442,9 @@ Pass `log` to redirect the line, or `onRecord` to feed a metrics sink the
|
|
|
2320
2442
|
structured `ToolCallRecord`. That record carries `traceId` whenever an
|
|
2321
2443
|
observability context is active, so a tool call made inside an HTTP request
|
|
2322
2444
|
joins that request's log line on one key — see
|
|
2323
|
-
[Observability](./observability.md). For a boot-time picture of what is exposed
|
|
2324
|
-
`summarizeTransports(services)` returns
|
|
2325
|
-
|
|
2445
|
+
[Observability](./observability.md). For a boot-time picture of what is exposed
|
|
2446
|
+
where, `summarizeTransports({ services, runtimeTools })` returns the mixed
|
|
2447
|
+
per-transport counts for you to log.
|
|
2326
2448
|
|
|
2327
2449
|
## One handler, three callers
|
|
2328
2450
|
|
|
@@ -3253,27 +3375,46 @@ onError: (ctx, err) => {
|
|
|
3253
3375
|
|
|
3254
3376
|
## Domain errors — `defineErrors`
|
|
3255
3377
|
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
`
|
|
3259
|
-
object):
|
|
3378
|
+
Declare each domain code, HTTP status and optional structured-details schema in
|
|
3379
|
+
one immutable registry. The generated functions construct typed branded
|
|
3380
|
+
`AppError` instances; ordinary `throw` remains explicit at the call site:
|
|
3260
3381
|
|
|
3261
3382
|
```ts
|
|
3262
|
-
|
|
3263
|
-
|
|
3264
|
-
|
|
3383
|
+
import { z } from 'zod'
|
|
3384
|
+
|
|
3385
|
+
export const { errors, codes, definitions, isCode } = defineErrors({
|
|
3386
|
+
SESSION_NOT_FOUND: { status: 404 },
|
|
3387
|
+
QUOTA_EXCEEDED: {
|
|
3388
|
+
status: 429,
|
|
3389
|
+
details: z.object({ retryAfterSeconds: z.number().int().positive() }),
|
|
3390
|
+
},
|
|
3391
|
+
})
|
|
3392
|
+
|
|
3393
|
+
throw errors.SESSION_NOT_FOUND({ message: 'No such session' })
|
|
3394
|
+
throw errors.QUOTA_EXCEEDED({
|
|
3395
|
+
message: 'Try later',
|
|
3396
|
+
details: { retryAfterSeconds: 30 },
|
|
3397
|
+
hint: 'Wait for the current window to expire',
|
|
3265
3398
|
})
|
|
3266
3399
|
|
|
3267
|
-
//
|
|
3268
|
-
|
|
3400
|
+
// Construction without throwing is useful for composition or inspection.
|
|
3401
|
+
const error = errors.QUOTA_EXCEEDED({ details: { retryAfterSeconds: 30 } })
|
|
3402
|
+
definitions.QUOTA_EXCEEDED.status // 429 — same source, no copied status map
|
|
3269
3403
|
|
|
3270
3404
|
// client — match the code, never a magic string
|
|
3271
3405
|
if (err instanceof ApiError && err.code === codes.SESSION_NOT_FOUND) { … }
|
|
3272
3406
|
```
|
|
3273
3407
|
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3408
|
+
With no `details` schema, the options object forbids `details`. A required
|
|
3409
|
+
`z.object` makes `details` required; `z.object(...).optional()` makes it
|
|
3410
|
+
optional. Supplied details are parsed when the error is constructed, before any
|
|
3411
|
+
transport sees them. `code` remains literal and the parsed details type is
|
|
3412
|
+
retained on the returned `AppError`.
|
|
3413
|
+
|
|
3414
|
+
HTTP renders the complete code/status/message/details/hint. Tool transports keep
|
|
3415
|
+
their established model-facing projection (code/details/hint, without HTTP
|
|
3416
|
+
status), while `invokeOrThrow` recovers the exact normalized `AppError`. The
|
|
3417
|
+
codes and schemas remain application-owned; Stitchkit stays domain-free.
|
|
3277
3418
|
|
|
3278
3419
|
## `createErrorHook`
|
|
3279
3420
|
|
|
@@ -3305,6 +3446,27 @@ createServer({ services, hooks: { onError } })
|
|
|
3305
3446
|
Codes you threw yourself (not stitchkit's) pass through `codeMap` unchanged; the
|
|
3306
3447
|
`satisfies Record<StitchErrorCode, …>` keeps the map exhaustive across upgrades.
|
|
3307
3448
|
|
|
3449
|
+
Both `onError` and `render` may be asynchronous and receive the matched endpoint
|
|
3450
|
+
as their final argument. The observer is awaited before rendering, so it can
|
|
3451
|
+
resolve identity or enrich the request context even for failures raised before
|
|
3452
|
+
`beforeHandle`:
|
|
3453
|
+
|
|
3454
|
+
```ts
|
|
3455
|
+
const onError = createErrorHook({
|
|
3456
|
+
onError: async (_error, _info, ctx, endpoint) => {
|
|
3457
|
+
await attributeFailedRequest(ctx, endpoint)
|
|
3458
|
+
},
|
|
3459
|
+
render: (info, ctx) => ({
|
|
3460
|
+
error: { code: info.code },
|
|
3461
|
+
actorId: ctx.actorId,
|
|
3462
|
+
}),
|
|
3463
|
+
})
|
|
3464
|
+
```
|
|
3465
|
+
|
|
3466
|
+
`endpoint` is `undefined` when the failure happened before route resolution.
|
|
3467
|
+
Synchronous callbacks and renderers that declare fewer parameters continue to
|
|
3468
|
+
work normally.
|
|
3469
|
+
|
|
3308
3470
|
Invalid input (a `ZodError`) is classified as `VALIDATION_ERROR` 400 before it
|
|
3309
3471
|
reaches `render` — a client fault is an honest 400, not a 500 — and the
|
|
3310
3472
|
offending fields arrive as structured `info.details.issues`, so your `render`
|
|
@@ -3340,10 +3502,10 @@ stitchkit answers this at two levels.
|
|
|
3340
3502
|
- **The raw hooks** — `LifecycleHooks` and `ToolCallHooks`. Every request and
|
|
3341
3503
|
every tool call passes through a point you can observe. The lowest level;
|
|
3342
3504
|
always available. [Jump ↓](#the-raw-hooks)
|
|
3343
|
-
- **`stitchkit/observability`** —
|
|
3344
|
-
trace context, an `AsyncLocalStorage` request context, payload
|
|
3345
|
-
and `
|
|
3346
|
-
|
|
3505
|
+
- **`stitchkit/observability`** — framework-owned HTTP completion plus canonical
|
|
3506
|
+
tool hooks: W3C trace context, an `AsyncLocalStorage` request context, payload
|
|
3507
|
+
sanitisation and `createObservability` with independent request/tool sinks.
|
|
3508
|
+
[Start here ↓](#the-observability-module)
|
|
3347
3509
|
|
|
3348
3510
|
stitchkit still ships no logger and no audit store — those are the app's choice.
|
|
3349
3511
|
What it ships is the machinery that turns a completed call into a clean,
|
|
@@ -3352,52 +3514,55 @@ normalised record.
|
|
|
3352
3514
|
## The observability module
|
|
3353
3515
|
|
|
3354
3516
|
`stitchkit/observability` is server-only. It has three parts — a trace context,
|
|
3355
|
-
a request context, and
|
|
3356
|
-
|
|
3357
|
-
###
|
|
3358
|
-
|
|
3359
|
-
`
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
```ts
|
|
3365
|
-
import {
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3517
|
+
a request context, and event projections — and you usually touch only the last.
|
|
3518
|
+
|
|
3519
|
+
### createObservability
|
|
3520
|
+
|
|
3521
|
+
`createObservability` configures request and tool projections independently.
|
|
3522
|
+
Every completed call is normalised into one `RequestEvent`; HTTP completion is
|
|
3523
|
+
owned directly by `createHandler`, while MCP/Agent completion uses the canonical
|
|
3524
|
+
`ToolCallHooks` runner. There is no nested HTTP audit wrapper.
|
|
3525
|
+
|
|
3526
|
+
```ts
|
|
3527
|
+
import { createObservability } from 'stitchkit/observability'
|
|
3528
|
+
|
|
3529
|
+
const write = (event) => db.auditLog.create({ data: {
|
|
3530
|
+
traceId: event.traceId,
|
|
3531
|
+
source: event.source,
|
|
3532
|
+
method: event.method,
|
|
3533
|
+
path: event.path,
|
|
3534
|
+
ok: event.ok,
|
|
3535
|
+
statusCode: event.statusCode,
|
|
3536
|
+
durationMs: event.durationMs,
|
|
3537
|
+
userId: event.userId,
|
|
3538
|
+
payload: event.payload,
|
|
3539
|
+
}})
|
|
3540
|
+
|
|
3541
|
+
export const observability = createObservability({
|
|
3542
|
+
request: {
|
|
3543
|
+
write,
|
|
3544
|
+
includePayload: false, // default: no Request.clone(), payload is null
|
|
3545
|
+
filter: (event) => event.method !== 'GET',
|
|
3546
|
+
},
|
|
3547
|
+
tools: {
|
|
3548
|
+
write,
|
|
3549
|
+
filter: (event) => event.source === 'mcp' || event.source === 'agent',
|
|
3381
3550
|
},
|
|
3382
|
-
// Optional — keep only the events you care about.
|
|
3383
|
-
filter: (event) => event.source !== 'http' || event.method !== 'GET',
|
|
3384
3551
|
})
|
|
3385
3552
|
```
|
|
3386
3553
|
|
|
3387
|
-
|
|
3554
|
+
Wire each projection where its completion is owned:
|
|
3388
3555
|
|
|
3389
3556
|
```ts
|
|
3390
|
-
|
|
3391
|
-
Bun.serve({ fetch: wrapInRequestContext(audit.http(handler)) })
|
|
3557
|
+
createServer({ services, observability: observability.request })
|
|
3392
3558
|
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
mountAgent(service, { hooks: audit.toolCall })
|
|
3559
|
+
createMcpHandler({ /* … */ hooks: observability.toolCall })
|
|
3560
|
+
mountAgent(service, { hooks: observability.toolCall })
|
|
3396
3561
|
```
|
|
3397
3562
|
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3563
|
+
Each sink runs fire-and-forget and fails independently: a slow or broken request
|
|
3564
|
+
sink cannot block the response, suppress operational logging or break the tool
|
|
3565
|
+
sink.
|
|
3401
3566
|
|
|
3402
3567
|
### RequestEvent
|
|
3403
3568
|
|
|
@@ -3416,32 +3581,26 @@ queryable across all three:
|
|
|
3416
3581
|
| `ok` / `statusCode` | outcome — real HTTP status, or `200`/`400` for a tool |
|
|
3417
3582
|
| `durationMs` / `startedAt` | timing |
|
|
3418
3583
|
| `errorCode` / `errorMessage` / `errorDetail` | failures only — `errorDetail` carries the structure the message flattens (e.g. Zod issues) |
|
|
3419
|
-
| `payload` |
|
|
3584
|
+
| `payload` | sanitised tool arguments; HTTP is `null` unless request `includePayload` is enabled |
|
|
3420
3585
|
| `resultSize` / `responseBytes` | result item count + serialised size |
|
|
3421
3586
|
| `userId` / `ipAddress` / `userAgent` | identity |
|
|
3422
3587
|
|
|
3423
3588
|
### Request context
|
|
3424
3589
|
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3590
|
+
When request observability is configured, `createHandler` establishes the
|
|
3591
|
+
`AsyncLocalStorage` request context itself and uses the same completion snapshot
|
|
3592
|
+
for operational logging and `RequestEvent`. No `wrapFetch` composition is
|
|
3593
|
+
needed:
|
|
3428
3594
|
|
|
3429
3595
|
```ts
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
Bun.serve({
|
|
3433
|
-
fetch: wrapInRequestContext(audit.http(handler)),
|
|
3434
|
-
})
|
|
3596
|
+
createServer({ services, logging, observability: observability.request })
|
|
3435
3597
|
```
|
|
3436
3598
|
|
|
3437
|
-
`
|
|
3438
|
-
|
|
3599
|
+
`wrapInRequestContext` remains available for a custom fetch pipeline that does
|
|
3600
|
+
not use `createHandler`; it is no longer part of built-in HTTP audit wiring:
|
|
3439
3601
|
|
|
3440
3602
|
```ts
|
|
3441
|
-
|
|
3442
|
-
services,
|
|
3443
|
-
wrapFetch: (fetch) => wrapInRequestContext(audit.http(fetch)),
|
|
3444
|
-
})
|
|
3603
|
+
Bun.serve({ fetch: wrapInRequestContext(customFetch) })
|
|
3445
3604
|
```
|
|
3446
3605
|
|
|
3447
3606
|
Some fields are filled in late. Set them from the hooks that know:
|
|
@@ -3477,6 +3636,12 @@ context carries nothing yet, so your value always wins.
|
|
|
3477
3636
|
`event.serviceName` / `event.action` are present on every event, including a
|
|
3478
3637
|
pre-handler 400. Nothing to wire.
|
|
3479
3638
|
|
|
3639
|
+
When failure attribution itself is asynchronous, use `createErrorHook`'s
|
|
3640
|
+
`onError(error, info, ctx, endpoint)` observer. The framework awaits it before
|
|
3641
|
+
calling `render`, so identity or audit enrichment is visible to both the final
|
|
3642
|
+
error envelope and the request event. `endpoint` is the matched `MethodDef`, or
|
|
3643
|
+
`undefined` when routing failed before a method was selected.
|
|
3644
|
+
|
|
3480
3645
|
**Domain dimensions** — attach your own tenant / project / entity id with
|
|
3481
3646
|
`setRequestDimensions`. It is an opaque `Record<string, string>` the core gives no
|
|
3482
3647
|
meaning to (→ ADR 0021). Resolve it cheaply from `ctx.params` / headers in
|
|
@@ -3570,8 +3735,8 @@ every tool call underneath it. With no inbound header a fresh root trace is
|
|
|
3570
3735
|
minted. Each tool call opens a [`childSpan`](#trace-context) of the request it
|
|
3571
3736
|
runs in.
|
|
3572
3737
|
|
|
3573
|
-
You rarely call the trace functions directly — `
|
|
3574
|
-
`
|
|
3738
|
+
You rarely call the trace functions directly — `createHandler` request
|
|
3739
|
+
observability and `wrapInRequestContext` use them for you. They are exported (`resolveTraceContext`,
|
|
3575
3740
|
`parseTraceparent`, `formatTraceparent`, `childSpan`) for when you need to
|
|
3576
3741
|
propagate a `traceparent` onward to another service.
|
|
3577
3742
|
|
|
@@ -3606,12 +3771,19 @@ A payload goes into an audit row only after `sanitizePayload`:
|
|
|
3606
3771
|
never the bytes;
|
|
3607
3772
|
- the result is **capped** — anything over the byte limit becomes a preview.
|
|
3608
3773
|
|
|
3609
|
-
`
|
|
3774
|
+
`createObservability` runs it on every emitted event; tune each sink separately:
|
|
3610
3775
|
|
|
3611
3776
|
```ts
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3777
|
+
createObservability({
|
|
3778
|
+
request: {
|
|
3779
|
+
write,
|
|
3780
|
+
includePayload: true,
|
|
3781
|
+
sanitize: { maxBytes: 8_000, sensitiveKeys: /password|token|pin/i },
|
|
3782
|
+
},
|
|
3783
|
+
tools: {
|
|
3784
|
+
write,
|
|
3785
|
+
sanitize: { maxBytes: 8_000, sensitiveKeys: /password|token|pin/i },
|
|
3786
|
+
},
|
|
3615
3787
|
})
|
|
3616
3788
|
```
|
|
3617
3789
|
|
|
@@ -3620,7 +3792,7 @@ need to sanitise something outside the audit path.
|
|
|
3620
3792
|
|
|
3621
3793
|
## The raw hooks
|
|
3622
3794
|
|
|
3623
|
-
|
|
3795
|
+
Tool observability is built on hooks you can also use directly — for a one-off
|
|
3624
3796
|
metric, a custom log line, anything that is not a full audit row.
|
|
3625
3797
|
|
|
3626
3798
|
| Surface | Hook | Fires |
|
|
@@ -3694,7 +3866,7 @@ observe. (This is also why it lives on `ToolCallHooks` rather than being an
|
|
|
3694
3866
|
object must stay assignable to `ToolLifecycle`.)
|
|
3695
3867
|
|
|
3696
3868
|
**Do not reach for `setRequestError` here.** It writes to the *request* context,
|
|
3697
|
-
which
|
|
3869
|
+
which the built-in **tool** row does not read: a tool event takes
|
|
3698
3870
|
`errorCode` / `errorMessage` / `errorDetail` from the `ToolResult`, and only
|
|
3699
3871
|
identity and `dimensions` from the context. Calling it in `onToolError` would
|
|
3700
3872
|
leave the tool row exactly as scrubbed as before. It is right for the **HTTP**
|
|
@@ -3720,7 +3892,7 @@ validation failure or a `beforeToolCall` rejection leaves it `undefined`, becaus
|
|
|
3720
3892
|
neither ever had a raw value to lose. Consumers destructure only the fields they
|
|
3721
3893
|
use; future optional fields do not change callback arity.
|
|
3722
3894
|
|
|
3723
|
-
`
|
|
3895
|
+
`createObservability({ tools })` uses it already. Where the envelope was scrubbed to
|
|
3724
3896
|
`INTERNAL_SERVER_ERROR`, the row's `errorMessage` becomes the real message
|
|
3725
3897
|
instead of the placeholder; a truthful envelope (a thrown `AppError`, a
|
|
3726
3898
|
`ZodError`) is left alone, `errorCode` and `errorDetail` are untouched, and the
|
|
@@ -3733,7 +3905,7 @@ sink of your own (a tracker, a stack, an alert), `afterToolCall` for the record.
|
|
|
3733
3905
|
|
|
3734
3906
|
### Keying a row on (service, action)
|
|
3735
3907
|
|
|
3736
|
-
|
|
3908
|
+
Built-in observability keys every event by **service** and **action**
|
|
3737
3909
|
(`event.serviceName` / `event.action`, → ADR 0029) — reach for the raw hook only
|
|
3738
3910
|
when you also need the handler **output**, which the audit wrapper never sees. For
|
|
3739
3911
|
that, read the endpoint identity off the `OperationIdentity` the tool hook
|
|
@@ -3761,11 +3933,11 @@ hooks: {
|
|
|
3761
3933
|
}
|
|
3762
3934
|
```
|
|
3763
3935
|
|
|
3764
|
-
> **Why
|
|
3765
|
-
> has a single `onError
|
|
3766
|
-
>
|
|
3767
|
-
>
|
|
3768
|
-
>
|
|
3936
|
+
> **Why HTTP observability is framework-owned, not a lifecycle hook.**
|
|
3937
|
+
> `LifecycleHooks` has a single `onError`; an audit built on it would compete
|
|
3938
|
+
> with the app's error renderer and miss raw/unmatched exits. `createHandler`
|
|
3939
|
+
> sees the final response on every path and emits one completion without
|
|
3940
|
+
> consuming an application hook.
|
|
3769
3941
|
|
|
3770
3942
|
Keep any sink **asynchronous and self-contained**: a slow or failing write must
|
|
3771
3943
|
never block or break the request. Swallow the sink's own errors.
|
|
@@ -3914,7 +4086,8 @@ Notes for a Node host:
|
|
|
3914
4086
|
stitchkit serves the API. A SPA front-end is built and hosted separately — a
|
|
3915
4087
|
static host or CDN in production, its own dev server in development. The backend
|
|
3916
4088
|
does not serve static files (`staticRoute` exists for the occasional asset, not
|
|
3917
|
-
a whole app).
|
|
4089
|
+
a whole app). `bun create stitchkit my-app` demonstrates the supported split:
|
|
4090
|
+
an independently built Next.js frontend and Bun/Stitchkit API.
|
|
3918
4091
|
|
|
3919
4092
|
### MCP
|
|
3920
4093
|
|
|
@@ -4051,6 +4224,76 @@ rule read it the same way on both surfaces — one contract, every surface, no
|
|
|
4051
4224
|
per-transport tenant plumbing.
|
|
4052
4225
|
|
|
4053
4226
|
|
|
4227
|
+
==============================================================================
|
|
4228
|
+
# Guide: Frontend integrations (docs/guide/frontend-integrations.md)
|
|
4229
|
+
==============================================================================
|
|
4230
|
+
|
|
4231
|
+
---
|
|
4232
|
+
title: Frontend integrations
|
|
4233
|
+
description: Compose Stitchkit with Next.js, React Router or a separate Vite development server
|
|
4234
|
+
type: architecture
|
|
4235
|
+
status: active
|
|
4236
|
+
created: 2026-08-08
|
|
4237
|
+
updated: 2026-08-08
|
|
4238
|
+
---
|
|
4239
|
+
|
|
4240
|
+
# Frontend integrations
|
|
4241
|
+
|
|
4242
|
+
The official `bun create stitchkit` application uses Next.js with a separate
|
|
4243
|
+
Bun API. Stitchkit remains a Fetch-native backend and does not own frontend
|
|
4244
|
+
routing, SSR or HMR.
|
|
4245
|
+
|
|
4246
|
+
## Theme boundary in the official starter
|
|
4247
|
+
|
|
4248
|
+
The generated Next.js application uses `@wrksz/themes`, not a Stitchkit-owned
|
|
4249
|
+
theme abstraction. Its root `ThemeProvider` comes from `@wrksz/themes/next` and
|
|
4250
|
+
lives directly in the server layout so Next 16 can inject the first-paint script
|
|
4251
|
+
through `useServerInsertedHTML`. The default `hybrid` storage reads a cookie
|
|
4252
|
+
during SSR and mirrors changes to localStorage for cross-tab synchronization.
|
|
4253
|
+
|
|
4254
|
+
Client components import typed hooks from the fine-grained
|
|
4255
|
+
`@wrksz/themes/client/*` entrypoints. Nested visual examples use
|
|
4256
|
+
`ClientThemeProvider` with a scoped target and `storage="none"`; they never
|
|
4257
|
+
become a second global provider. Applications may add account-backed theme
|
|
4258
|
+
preferences, CSP nonces or consent-aware storage, but those policies remain
|
|
4259
|
+
application concerns.
|
|
4260
|
+
|
|
4261
|
+
Theme state and theme animation are intentionally separate. `@wrksz/themes`
|
|
4262
|
+
owns selection, resolution, SSR prepaint and persistence. The generated app's
|
|
4263
|
+
`theme/transition.ts` wraps an interactive `setTheme` call with the native View
|
|
4264
|
+
Transition API and exposes typed style, duration, easing and origin settings.
|
|
4265
|
+
The default 250 ms crossfade matches the starter's visual language; the
|
|
4266
|
+
catalogue also demonstrates a radial reveal. The runner bypasses animation when
|
|
4267
|
+
the browser lacks the API or `prefers-reduced-motion: reduce` is active.
|
|
4268
|
+
|
|
4269
|
+
## React Router
|
|
4270
|
+
|
|
4271
|
+
Mount a `createHandler()` result in a catch-all resource route and pass the
|
|
4272
|
+
incoming `Request` through unchanged. Mount MCP as a second resource route. An
|
|
4273
|
+
SSR request creates its own typed client using that request's origin and auth;
|
|
4274
|
+
do not share request identity in a module singleton.
|
|
4275
|
+
|
|
4276
|
+
```ts
|
|
4277
|
+
export async function loader({ request }: LoaderFunctionArgs) {
|
|
4278
|
+
return apiHandler(request);
|
|
4279
|
+
}
|
|
4280
|
+
|
|
4281
|
+
export async function action({ request }: ActionFunctionArgs) {
|
|
4282
|
+
return apiHandler(request);
|
|
4283
|
+
}
|
|
4284
|
+
```
|
|
4285
|
+
|
|
4286
|
+
## Vite
|
|
4287
|
+
|
|
4288
|
+
Run Vite and the Stitchkit API as separate development processes. Declare one
|
|
4289
|
+
proxy for `/api`, `/mcp` and `/socket.io`; browser code still calls the typed
|
|
4290
|
+
client with same-origin paths. Production serves the static Vite output from a
|
|
4291
|
+
static host or reverse proxy and routes those backend paths to Stitchkit.
|
|
4292
|
+
|
|
4293
|
+
Do not duplicate DTOs or handwritten API wrappers in either integration. The
|
|
4294
|
+
shared contract remains the only transport schema source.
|
|
4295
|
+
|
|
4296
|
+
|
|
4054
4297
|
==============================================================================
|
|
4055
4298
|
# Guide: Upgrading (docs/guide/upgrading.md)
|
|
4056
4299
|
==============================================================================
|
|
@@ -4119,6 +4362,123 @@ current one *up to* your target, and apply each snippet.
|
|
|
4119
4362
|
|
|
4120
4363
|
## Unreleased breaking migrations
|
|
4121
4364
|
|
|
4365
|
+
HTTP observability now completes inside the framework handler instead of a
|
|
4366
|
+
nested fetch wrapper. Configure request and tool sinks explicitly:
|
|
4367
|
+
|
|
4368
|
+
```ts
|
|
4369
|
+
// before
|
|
4370
|
+
const audit = createAuditHook({ write })
|
|
4371
|
+
createServer({
|
|
4372
|
+
services,
|
|
4373
|
+
wrapFetch: (handler) => wrapInRequestContext(audit.http(handler)),
|
|
4374
|
+
})
|
|
4375
|
+
mountAgent(services, { hooks: audit.toolCall })
|
|
4376
|
+
|
|
4377
|
+
// after
|
|
4378
|
+
const observability = createObservability({
|
|
4379
|
+
request: { write, includePayload: true },
|
|
4380
|
+
tools: { write },
|
|
4381
|
+
})
|
|
4382
|
+
createServer({ services, observability: observability.request })
|
|
4383
|
+
mountAgent(services, { hooks: observability.toolCall })
|
|
4384
|
+
```
|
|
4385
|
+
|
|
4386
|
+
Body capture changed from always-on for body methods to opt-in. Set
|
|
4387
|
+
`includePayload: true` only when the request sink needs the sanitized JSON body.
|
|
4388
|
+
There is no `createAuditHook` or `audit.http` compatibility path;
|
|
4389
|
+
`wrapInRequestContext` remains only for custom fetch pipelines.
|
|
4390
|
+
|
|
4391
|
+
Tool introspection now accepts one object-shaped contract/runtime surface. Stop
|
|
4392
|
+
calling the internal contract collector or merging a locally converted runtime
|
|
4393
|
+
manifest:
|
|
4394
|
+
|
|
4395
|
+
```ts
|
|
4396
|
+
// before
|
|
4397
|
+
buildToolManifest(services.flatMap((service) => collectTools(service, 'AGENT')))
|
|
4398
|
+
listToolNames(services)
|
|
4399
|
+
summarizeTransports(services)
|
|
4400
|
+
|
|
4401
|
+
// after
|
|
4402
|
+
const surface = { services, runtimeTools }
|
|
4403
|
+
buildToolManifest({ ...surface, transport: 'AGENT' })
|
|
4404
|
+
listToolNames(surface)
|
|
4405
|
+
summarizeTransports(surface)
|
|
4406
|
+
```
|
|
4407
|
+
|
|
4408
|
+
`ToolNameEntry` adds `kind: 'contract' | 'runtime'`. `TransportSummary` is now
|
|
4409
|
+
`{ contractServices, runtimeTools, totals, sources }`; replace `services` and
|
|
4410
|
+
`perService` reads with the explicit counts and mixed-source breakdown. There
|
|
4411
|
+
is no positional overload and no `buildRuntimeToolManifest`: Stitchkit owns the
|
|
4412
|
+
combined order, transport filtering, canonical presentation schema and
|
|
4413
|
+
cross-origin collision checks.
|
|
4414
|
+
|
|
4415
|
+
`defineErrors` now uses one Zod-first definition object and returns constructors
|
|
4416
|
+
instead of positional throwers. Add explicit `throw`, move message/details/hint
|
|
4417
|
+
into one options object, and declare a details schema when that code carries
|
|
4418
|
+
structured context:
|
|
4419
|
+
|
|
4420
|
+
```ts
|
|
4421
|
+
// before
|
|
4422
|
+
const { errors } = defineErrors({ QUOTA_EXCEEDED: 429 })
|
|
4423
|
+
errors.QUOTA_EXCEEDED('Try later', { retryAfterSeconds: 30 }, 'Wait')
|
|
4424
|
+
|
|
4425
|
+
// after
|
|
4426
|
+
const { errors, definitions } = defineErrors({
|
|
4427
|
+
QUOTA_EXCEEDED: {
|
|
4428
|
+
status: 429,
|
|
4429
|
+
details: z.object({ retryAfterSeconds: z.number().positive() }),
|
|
4430
|
+
},
|
|
4431
|
+
})
|
|
4432
|
+
throw errors.QUOTA_EXCEEDED({
|
|
4433
|
+
message: 'Try later',
|
|
4434
|
+
details: { retryAfterSeconds: 30 },
|
|
4435
|
+
hint: 'Wait',
|
|
4436
|
+
})
|
|
4437
|
+
```
|
|
4438
|
+
|
|
4439
|
+
There is no positional overload. A code without `details` forbids them; use an
|
|
4440
|
+
optional object schema when the details object itself is optional. Read status
|
|
4441
|
+
and schemas from the frozen `definitions` registry instead of maintaining a
|
|
4442
|
+
parallel map.
|
|
4443
|
+
|
|
4444
|
+
Managed MCP runtime tools are now declared as immutable data. Move protected
|
|
4445
|
+
registrar calls to `runtimeTools`; rename deliberate raw SDK registration to
|
|
4446
|
+
`rawTools`. There is no registrar alias:
|
|
4447
|
+
|
|
4448
|
+
```ts
|
|
4449
|
+
// before — protected
|
|
4450
|
+
createMcpHandler({
|
|
4451
|
+
services,
|
|
4452
|
+
nativeTools: ({ registerTool }) => registerTool(preview),
|
|
4453
|
+
})
|
|
4454
|
+
|
|
4455
|
+
// after — protected and prepared with the rest of the surface
|
|
4456
|
+
createMcpHandler({ services, runtimeTools: [preview] })
|
|
4457
|
+
|
|
4458
|
+
// before — deliberate raw SDK opt-out
|
|
4459
|
+
nativeTools: ({ rawServer }, auth) => mountRaw(rawServer, auth)
|
|
4460
|
+
|
|
4461
|
+
// after — still a deliberate raw SDK opt-out
|
|
4462
|
+
rawTools: (server, auth) => mountRaw(server, auth)
|
|
4463
|
+
```
|
|
4464
|
+
|
|
4465
|
+
When identity selects from a bounded set, replace a repeatedly prepared
|
|
4466
|
+
`services(auth)` factory with a finite registry:
|
|
4467
|
+
|
|
4468
|
+
```ts
|
|
4469
|
+
createMcpHandler({
|
|
4470
|
+
surfaces: {
|
|
4471
|
+
admin: { services: allServices, runtimeTools: [preview] },
|
|
4472
|
+
member: { services: memberServices, runtimeTools: [preview] },
|
|
4473
|
+
},
|
|
4474
|
+
selectSurface: (auth) => auth.isAdmin ? 'admin' : 'member',
|
|
4475
|
+
})
|
|
4476
|
+
```
|
|
4477
|
+
|
|
4478
|
+
Keep direct `services(auth)` / `runtimeTools(auth)` only for genuinely
|
|
4479
|
+
unbounded definitions; Stitchkit intentionally does not cache arbitrary auth
|
|
4480
|
+
values.
|
|
4481
|
+
|
|
4122
4482
|
Contract success bodies are now determined by the presence of `output`, not by
|
|
4123
4483
|
the handler's runtime value. A nullable output returns JSON `null` with status
|
|
4124
4484
|
`200`; `undefined` with a declared output and non-null data without an output
|
|
@@ -4222,7 +4582,7 @@ const preview = defineRuntimeTool({
|
|
|
4222
4582
|
}),
|
|
4223
4583
|
},
|
|
4224
4584
|
})
|
|
4225
|
-
|
|
4585
|
+
runtimeTools: [preview]
|
|
4226
4586
|
```
|
|
4227
4587
|
|
|
4228
4588
|
The removed `NativeMcp*` types have no aliases. Use `RuntimeToolDefinition`,
|
|
@@ -4421,7 +4781,7 @@ name derivation, diff them mechanically:
|
|
|
4421
4781
|
|
|
4422
4782
|
```ts
|
|
4423
4783
|
import { listToolNames } from 'stitchkit/tools'
|
|
4424
|
-
console.log(JSON.stringify(listToolNames(services), null, 2))
|
|
4784
|
+
console.log(JSON.stringify(listToolNames({ services, runtimeTools }), null, 2))
|
|
4425
4785
|
```
|
|
4426
4786
|
|
|
4427
4787
|
`listToolNames` never throws on an illegal name — that is deliberate, so it can
|
|
@@ -4529,6 +4889,11 @@ from the root `stitchkit`.
|
|
|
4529
4889
|
|--------|------|---------|
|
|
4530
4890
|
| `defineContract` | function | declare a contract — [guide](../guide/contracts.md#definecontract) |
|
|
4531
4891
|
| `createContractFactory` | function | a `defineContract` with a required allowed scope that retains each concrete literal — [guide](../guide/contracts.md#scope) |
|
|
4892
|
+
| `ContractFactoryConfig` | _type_ | optional scoped-factory policy, including explicit tool exposure |
|
|
4893
|
+
| `ContractFactoryToolExposure` | _type_ | `'explicit'` — omitted endpoint exposure materializes as HTTP-only |
|
|
4894
|
+
| `ExplicitScopedDefineContract` | _type_ | scoped factory authoring with explicit tool opt-in |
|
|
4895
|
+
| `ExplicitToolExposureEndpoints` | _type_ | endpoint map after missing exposure is materialized as `['HTTP']` |
|
|
4896
|
+
| `ScopedContractDef` | _type_ | a factory-defined contract whose `meta.scope` is the required concrete literal |
|
|
4532
4897
|
| `ScopedDefineContract` | _type_ | the `defineContract` `createContractFactory` returns |
|
|
4533
4898
|
| `ALL_TRANSPORTS` | constant | `['HTTP', 'MCP', 'AGENT', 'CLI']` |
|
|
4534
4899
|
| `ContractDef` | _type_ | a defined contract |
|
|
@@ -4570,9 +4935,17 @@ from the root `stitchkit`.
|
|
|
4570
4935
|
| `conflict` | function | throw `409 CONFLICT` |
|
|
4571
4936
|
| `rateLimited` | function | throw `429 RATE_LIMITED` |
|
|
4572
4937
|
| `appError` | function | throw an `AppError` for any code |
|
|
4573
|
-
| `defineErrors` | function | declare domain error
|
|
4574
|
-
| `DefinedErrors` | _type_ | the `{ errors, codes, isCode }` handle `defineErrors` returns |
|
|
4575
|
-
| `
|
|
4938
|
+
| `defineErrors` | function | declare immutable domain error definitions → typed `AppError` constructors, codes and schemas — [guide](../guide/auth-and-errors.md#domain-errors--defineerrors) |
|
|
4939
|
+
| `DefinedErrors` | _type_ | the `{ errors, codes, definitions, isCode }` handle `defineErrors` returns |
|
|
4940
|
+
| `DefinedAppError` | _type_ | literal-code error instance with schema-refined details |
|
|
4941
|
+
| `ErrorDefinition` | _type_ | `{ status, details? }` definition for one domain code |
|
|
4942
|
+
| `ErrorDefinitions` | _type_ | string-keyed domain error definition registry |
|
|
4943
|
+
| `ErrorDetailsSchema` | _type_ | required or optional Zod object accepted for structured details |
|
|
4944
|
+
| `ErrorDetailsOutput` | _type_ | parsed details inferred from one definition |
|
|
4945
|
+
| `ErrorFactoryArguments` | _type_ | options tuple with forbidden/required/optional details inferred per code |
|
|
4946
|
+
| `ErrorFactory` | _type_ | one typed `AppError` constructor |
|
|
4947
|
+
| `ErrorFactories` | _type_ | mapped constructor registry derived from all definitions |
|
|
4948
|
+
| `FrozenErrorDefinitions` | _type_ | read-only definition registry returned to consumers |
|
|
4576
4949
|
| `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) |
|
|
4577
4950
|
| `StitchErrorCode` | _type_ | a code stitchkit itself emits (`keyof STITCH_ERROR_STATUS`) |
|
|
4578
4951
|
| `isStitchErrorCode` | function | type guard — is a code one of stitchkit's own? |
|
|
@@ -4640,8 +5013,8 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
4640
5013
|
| Export | Kind | Summary |
|
|
4641
5014
|
|--------|------|---------|
|
|
4642
5015
|
| `createAuthHook` | function | a scope-enforcing `beforeHandle` hook — [guide](../guide/auth-and-errors.md#createauthhook) |
|
|
4643
|
-
| `createErrorHook` | function | an `onError` hook from a code map + envelope renderer — [guide](../guide/auth-and-errors.md#createerrorhook) |
|
|
4644
|
-
| `ErrorHookConfig` | _type_ | config for `createErrorHook` |
|
|
5016
|
+
| `createErrorHook` | function | an async-capable, endpoint-aware `onError` hook from a code map + envelope renderer — [guide](../guide/auth-and-errors.md#createerrorhook) |
|
|
5017
|
+
| `ErrorHookConfig` | _type_ | async observer/renderer config for `createErrorHook` |
|
|
4645
5018
|
| `ResolvedError` | _type_ | the normalised error handed to `createErrorHook`'s `render` |
|
|
4646
5019
|
| `createBearerResolver` | function | a bearer-token identity resolver |
|
|
4647
5020
|
| `signJwt` | function | sign an HS256 JWT |
|
|
@@ -4731,14 +5104,18 @@ Server-only. The audit layer one level above the raw hooks — W3C trace context
|
|
|
4731
5104
|
an `AsyncLocalStorage` request context, payload sanitisation and a normalised
|
|
4732
5105
|
audit event. See the [Observability guide](../guide/observability.md).
|
|
4733
5106
|
|
|
4734
|
-
###
|
|
5107
|
+
### Events
|
|
4735
5108
|
|
|
4736
5109
|
| Export | Kind | Summary |
|
|
4737
5110
|
|--------|------|---------|
|
|
4738
|
-
| `
|
|
5111
|
+
| `createObservability` | function | configure framework-owned request completion and canonical tool event sinks — [guide](../guide/observability.md#createobservability) |
|
|
4739
5112
|
| `RequestEvent` | _type_ | the normalised audit event handed to the sink |
|
|
4740
|
-
| `
|
|
4741
|
-
| `
|
|
5113
|
+
| `ObservabilityConfig` | _type_ | independent request and tool sink configuration |
|
|
5114
|
+
| `Observability` | _type_ | the `{ request?, toolCall }` wiring result |
|
|
5115
|
+
| `RequestEventSinkConfig` | _type_ | `write`, `filter` and sanitisation for one event surface |
|
|
5116
|
+
| `RequestObservabilityConfig` | _type_ | request sink plus opt-in payload capture |
|
|
5117
|
+
| `HttpRequestCompletion` | _type_ | the single framework-owned HTTP outcome projected to logging and request events |
|
|
5118
|
+
| `HttpRequestObserver` | _type_ | server-facing projection consumed by `HandlerConfig.observability` |
|
|
4742
5119
|
|
|
4743
5120
|
### Request context
|
|
4744
5121
|
|
|
@@ -4790,31 +5167,44 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
4790
5167
|
|--------|------|---------|
|
|
4791
5168
|
| `createMcpHandler` | function | a complete Streamable-HTTP MCP server — [guide](../guide/mcp-and-agents.md#mcp--createmcphandler) |
|
|
4792
5169
|
| `createStdioMcpServer` | function | a complete stdio MCP server — [guide](../guide/mcp-and-agents.md#mcp-over-stdio--createstdiomcpserver) |
|
|
4793
|
-
| `buildMcpServer` | function | build an `McpServer` from
|
|
5170
|
+
| `buildMcpServer` | function | build an `McpServer` from contract/runtime surfaces — the transport-neutral core |
|
|
4794
5171
|
| `mountMcp` | function | add contract tools to an existing `McpServer` — [guide](../guide/mcp-and-agents.md#mountmcp) |
|
|
4795
5172
|
| `implementRemote` | function | bind a contract to a remote HTTP API — [guide](../guide/mcp-and-agents.md#proxying-a-remote-api--implementremote) |
|
|
4796
5173
|
| `mountAgent` | function | a Vercel AI SDK `ToolSet` from a service — [guide](../guide/mcp-and-agents.md#ai-agents--mountagent) |
|
|
4797
5174
|
| `defineRuntimeTool` | function | define one validated pathless operation for MCP, Agent or both — [guide](../guide/mcp-and-agents.md#pathless-runtime-tools-and-multimodal-results) |
|
|
5175
|
+
| `createRuntimeToolFactory` | function | bind shared identity and Zod-validated per-call context for runtime tools — [guide](../guide/mcp-and-agents.md#pathless-runtime-tools-and-multimodal-results) |
|
|
4798
5176
|
| `createToolInvoker` | function | compile an exposure-aware in-process dispatcher over the canonical tool runner — [guide](../guide/mcp-and-agents.md#in-process-calls--createtoolinvoker) |
|
|
4799
5177
|
| `createCli` | function | a command-line program from contracts — [guide](../guide/cli.md) (also on `stitchkit/cli`) |
|
|
4800
5178
|
| `createToolkit` | function | context-typed tool mounts — [guide](../guide/cli.md#typed-context) |
|
|
4801
5179
|
| `mountViewFile` | function | a native multimodal "view file" MCP tool |
|
|
4802
5180
|
| `resolveMedia` | function | resolve a media reference for a tool result |
|
|
4803
5181
|
| `validateMcpSchemas` | function | object-shaped assertion over the exact advertised schema surface — compatibility, typed properties and portable formats ([guide](../guide/mcp-and-agents.md#mcp-schema-validation-profile)) |
|
|
4804
|
-
| `listToolNames` | function | every
|
|
5182
|
+
| `listToolNames` | function | every contract/runtime tool name with origin, identity and transports — for stable snapshots — [guide](../guide/mcp-and-agents.md#pinning-tool-names--listtoolnames) |
|
|
4805
5183
|
| `McpHandlerConfig` | _type_ | config for `createMcpHandler` |
|
|
5184
|
+
| `McpHttpConfig` | _type_ | HTTP auth, protected-resource and session options composed into `McpHandlerConfig` |
|
|
4806
5185
|
| `McpSessionMode` | _type_ | `'stateless' \| 'stateful'`; HTTP defaults to request-isolated stateless mode |
|
|
4807
5186
|
| `StdioMcpServerConfig` | _type_ | config for `createStdioMcpServer` |
|
|
4808
5187
|
| `McpServerBuildConfig` | _type_ | shared config for `buildMcpServer` |
|
|
5188
|
+
| `McpServerSharedConfig` | _type_ | transport-neutral options shared by direct and finite surface configs |
|
|
5189
|
+
| `DirectMcpSurfaceConfig` | _type_ | static or identity-dynamic `services` / `runtimeTools` source |
|
|
5190
|
+
| `FiniteMcpSurfaceConfig` | _type_ | bounded `surfaces` registry plus typed selector |
|
|
5191
|
+
| `McpSurfaceDefinition` | _type_ | one immutable `{ services, runtimeTools }` MCP surface |
|
|
5192
|
+
| `McpSurfaceRegistry` | _type_ | finite keyed surface registry for eager preparation |
|
|
5193
|
+
| `StdioAuthConfig` | _type_ | startup identity composed into `StdioMcpServerConfig` |
|
|
4809
5194
|
| `ImplementRemoteOptions` | _type_ | options for `implementRemote` |
|
|
4810
5195
|
| `McpMountConfig` | _type_ | config for `mountMcp` |
|
|
4811
5196
|
| `McpSchemaValidationConfig` | _type_ | shared `{ policy, requireTypedProperties, allowUntyped, requirePortableFormats, allowFormats }` profile |
|
|
4812
5197
|
| `ValidateMcpSchemasConfig` | _type_ | standalone validation profile plus `services`, `extend`, flattening and logger |
|
|
4813
|
-
| `NativeMcpRegistrar` | _type_ | protected `registerTool` plus explicit unprotected `rawServer` access |
|
|
4814
5198
|
| `RuntimeToolDefinition` | _type_ | transport-neutral pathless operation with identity, schemas, handler and optional presenters |
|
|
4815
5199
|
| `RuntimeToolDefinitionBase` | _type_ | common name, identity, input, exposure and MCP metadata fields |
|
|
4816
5200
|
| `RuntimeToolDefinitionWithOutput` | _type_ | runtime definition whose handler and presenters share a validated output type |
|
|
4817
5201
|
| `RuntimeToolDefinitionWithoutOutput` | _type_ | runtime definition with a void handler and no presentation callbacks |
|
|
5202
|
+
| `RuntimeToolFactory` | _type_ | identity/context-bound runtime-tool definition factory |
|
|
5203
|
+
| `RuntimeToolFactoryConfig` | _type_ | factory service identity and context schema |
|
|
5204
|
+
| `RuntimeToolFactoryDefinitionWithOutput` | _type_ | factory-authored runtime tool with a validated output schema |
|
|
5205
|
+
| `RuntimeToolFactoryDefinitionWithoutOutput` | _type_ | factory-authored void runtime tool without presenters |
|
|
5206
|
+
| `RuntimeToolFactoryHandlerContext` | _type_ | parsed factory context plus parsed tool input |
|
|
5207
|
+
| `RuntimeToolFactoryIdentityFields` | _type_ | per-tool action, semantic method and optional identity metadata |
|
|
4818
5208
|
| `RuntimeToolIdentity` | _type_ | `{ serviceName, action, scope?, method, meta? }` for runtime lifecycle/audit |
|
|
4819
5209
|
| `RuntimeToolHandlerContext` | _type_ | runtime context with the definition's parsed input |
|
|
4820
5210
|
| `RuntimeToolOutput` | _type_ | output inferred from a runtime tool's optional Zod schema |
|
|
@@ -4850,7 +5240,7 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
4850
5240
|
| `findNonPortableFormats` | function | deep finder for formats outside the portable MCP/AJV baseline |
|
|
4851
5241
|
| `NonPortableFormat` | _type_ | one `{ path, format }` portability finding |
|
|
4852
5242
|
| `PORTABLE_JSON_SCHEMA_FORMATS` | constant | portable-format baseline used by MCP validation |
|
|
4853
|
-
| `ToolNameEntry` | _type_ | one `listToolNames` row — `{ name, service, method, transports }` |
|
|
5243
|
+
| `ToolNameEntry` | _type_ | one `listToolNames` row — `{ kind, name, service, method, transports }` |
|
|
4854
5244
|
| `IncompatibleSchemaPolicy` | _type_ | `'throw' \| 'skip' \| 'warn'` |
|
|
4855
5245
|
| `McpMediaContent` | _type_ | a multimodal MCP content item |
|
|
4856
5246
|
|
|
@@ -4909,14 +5299,17 @@ Advanced building blocks — the shared machinery the mounts are built on.
|
|
|
4909
5299
|
|--------|------|---------|
|
|
4910
5300
|
| `collectTools` | function | resolve a service's methods to mountable tools (the shared resolver) |
|
|
4911
5301
|
| `createToolLogger` | function | a ready `afterToolCall` that logs every tool call — [guide](../guide/mcp-and-agents.md#logging-tool-calls--createtoollogger) |
|
|
4912
|
-
| `summarizeTransports` | function |
|
|
4913
|
-
| `buildToolManifest` | function |
|
|
5302
|
+
| `summarizeTransports` | function | mixed contract/runtime operation counts and per-source breakdown for a boot-time summary |
|
|
5303
|
+
| `buildToolManifest` | function | transport-aware searchable `{ name, description, inputSchema }` rows from a mixed surface |
|
|
4914
5304
|
| `ToolLoggerConfig` | _type_ | config for `createToolLogger` |
|
|
4915
5305
|
| `ToolCallRecord` | _type_ | the structured record `createToolLogger` passes to `onRecord` |
|
|
4916
|
-
| `TransportSummary` | _type_ |
|
|
5306
|
+
| `TransportSummary` | _type_ | `{ contractServices, runtimeTools, totals, sources }` from `summarizeTransports` |
|
|
4917
5307
|
| `TransportCounts` | _type_ | per-transport counts (`{ HTTP, MCP, AGENT, CLI }`) |
|
|
5308
|
+
| `ToolSurfaceDefinition` | _type_ | shared object-shaped `{ services?, runtimeTools? }` introspection surface |
|
|
5309
|
+
| `ToolSurfaceTransport` | _type_ | tool collector transport: `'MCP' \| 'AGENT' \| 'CLI'` |
|
|
5310
|
+
| `ToolManifestConfig` | _type_ | mixed surface plus required model-facing `transport` and presentation options |
|
|
4918
5311
|
| `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments |
|
|
4919
|
-
| `flattenToolJsonSchema` | function | project structurally identifiable discriminated unions in a
|
|
5312
|
+
| `flattenToolJsonSchema` | function | project structurally identifiable discriminated unions into conservative object joins; divergent fields retain every provable base kind in a deterministic `type` array, and the projection never executes validation |
|
|
4920
5313
|
| `ToolPresentationSchema` | _type_ | immutable model-facing JSON Schema document shared by tool transports |
|
|
4921
5314
|
| `MountableTool` | _type_ | one operation with separate executable CLI argument schema and model-facing presentation schema |
|
|
4922
5315
|
| `ToolManifestEntry` | _type_ | one `buildToolManifest` row |
|