stitchkit 0.40.0 → 0.42.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/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/http.d.ts +1 -1
- package/dist/browser/http.d.ts.map +1 -1
- 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/index.d.ts +1 -1
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +2 -2
- package/dist/{index-xax049k6.js → index-11x5dts2.js} +44 -13
- package/dist/{index-7rbzbnnf.js → index-181aebw8.js} +10 -15
- package/dist/{index-92gs1m5b.js → index-310bfer5.js} +1 -1
- package/dist/{index-fyfk537k.js → index-gex6gxhe.js} +25 -5
- package/dist/{index-p1x65gk7.js → index-p9vkwns1.js} +11 -13
- package/dist/{index-809wc1tt.js → index-s4qsmgwe.js} +4 -1
- package/dist/index.js +37 -13
- package/dist/internal/errors.d.ts +13 -0
- package/dist/internal/errors.d.ts.map +1 -1
- 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/index.js +1 -1
- 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/tools/agent.d.ts +1 -1
- package/dist/tools/agent.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 +3 -3
- 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 +5 -5
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +320 -165
- package/llms-full.txt +310 -53
- package/package.json +1 -1
package/llms-full.txt
CHANGED
|
@@ -234,7 +234,7 @@ export const users = defineContract({ prefix: 'users' }, {
|
|
|
234
234
|
| `desc` | yes | human description — also the MCP / agent tool description |
|
|
235
235
|
| `params` | no | Zod schema for **path params** (`:id`, …) |
|
|
236
236
|
| `input` | no | Zod schema for the **request body** (or query, for GET/DELETE) |
|
|
237
|
-
| `output` | no | Zod schema for the **response body
|
|
237
|
+
| `output` | no | Zod schema for the **response body**; its presence declares a JSON result (`null` is data, `undefined` is not) |
|
|
238
238
|
| `scope` | no | access scope for this endpoint — see [Auth & errors](./auth-and-errors.md) |
|
|
239
239
|
| `expose` | no | which transports carry this endpoint — see [below](#transports) |
|
|
240
240
|
| `toolName` | no | explicit MCP / agent tool name (default: a verb-aware derivation, see below — not a literal `prefix_key`) |
|
|
@@ -256,6 +256,13 @@ headers, while Stitchkit guarantees an empty wire body even if the returned
|
|
|
256
256
|
|
|
257
257
|
## `params` vs `input` vs `output`
|
|
258
258
|
|
|
259
|
+
`output` owns response presence across HTTP and tool transports. With a schema,
|
|
260
|
+
the handler must return a value that validates and can be represented as JSON;
|
|
261
|
+
a nullable schema may return `null`, but `undefined` is always a contract
|
|
262
|
+
violation. Without `output`, the handler is empty (`undefined`/`null`) and may
|
|
263
|
+
not return undeclared data. On HTTP this means a default `200` JSON response
|
|
264
|
+
with output and a default bodyless `204` without it.
|
|
265
|
+
|
|
259
266
|
The three schemas are distinct on purpose:
|
|
260
267
|
|
|
261
268
|
- **`params`** — values in the URL path. `path: '/:id'` ⇒
|
|
@@ -910,9 +917,13 @@ HTTP-only, but its typed client method still resolves to `AuthUser` — not
|
|
|
910
917
|
declared `output` schema exactly once.
|
|
911
918
|
|
|
912
919
|
`responseMeta.status` is static contract metadata and OpenAPI publishes the same
|
|
913
|
-
2xx code. Without it,
|
|
914
|
-
`
|
|
915
|
-
|
|
920
|
+
2xx code. Without it, an endpoint with `output` uses `200`; an endpoint without
|
|
921
|
+
`output` uses `204`. A nullable output returns JSON `null` with status `200` —
|
|
922
|
+
the runtime value never turns a declared response body into no content.
|
|
923
|
+
`undefined` violates a declared output, while returning non-null data without an
|
|
924
|
+
output schema is also a server fault. Bodyless `204`/`205` cannot be combined
|
|
925
|
+
with `output`. Redirects, streams, files and handler-owned status/body logic
|
|
926
|
+
remain [`rawResponse: true`](#raw-response-endpoints).
|
|
916
927
|
|
|
917
928
|
Collected headers are merged only after the complete success pipeline. A
|
|
918
929
|
handler, hook or output-validation failure discards them. `Content-Type`,
|
|
@@ -1352,6 +1363,13 @@ await api.update({ id: '1', name: 'M' }) // PUT /users/1 body: { name }
|
|
|
1352
1363
|
await api.delete({ id: '1' }) // DELETE /users/1
|
|
1353
1364
|
```
|
|
1354
1365
|
|
|
1366
|
+
The client follows response presence from the contract, not from the HTTP
|
|
1367
|
+
status or a truthy runtime value. An endpoint with nullable `output` resolves
|
|
1368
|
+
JSON `null` as `null`; an endpoint without `output` resolves `undefined`,
|
|
1369
|
+
including an explicitly declared empty `200` or `205`. A missing body for a
|
|
1370
|
+
declared output, or a body for an endpoint with no output, fails loudly instead
|
|
1371
|
+
of changing the typed result.
|
|
1372
|
+
|
|
1355
1373
|
An explicit contract `HEAD` operation is exposed like any other typed method.
|
|
1356
1374
|
Because HEAD endpoints are `rawResponse`, it resolves to the untouched
|
|
1357
1375
|
`Response`, giving the caller direct access to status and headers without JSON
|
|
@@ -1685,16 +1703,16 @@ call —
|
|
|
1685
1703
|
### Pinning tool names — `listToolNames`
|
|
1686
1704
|
|
|
1687
1705
|
Derived tool names are part of your public surface — an MCP client config or an
|
|
1688
|
-
agent prompt refers to them by string. `listToolNames(services)`
|
|
1689
|
-
|
|
1690
|
-
`(service, method)` identity and transports, sorted — a stable shape
|
|
1691
|
-
snapshot:
|
|
1706
|
+
agent prompt refers to them by string. `listToolNames({ services, runtimeTools })`
|
|
1707
|
+
resolves the complete mixed surface (the exact resolver the mounts use), with
|
|
1708
|
+
its origin, `(service, method)` identity and transports, sorted — a stable shape
|
|
1709
|
+
to snapshot:
|
|
1692
1710
|
|
|
1693
1711
|
```ts
|
|
1694
1712
|
import { listToolNames } from 'stitchkit/tools'
|
|
1695
1713
|
|
|
1696
1714
|
test('tool names have not drifted', () => {
|
|
1697
|
-
expect(listToolNames(services)).toMatchSnapshot()
|
|
1715
|
+
expect(listToolNames({ services, runtimeTools })).toMatchSnapshot()
|
|
1698
1716
|
})
|
|
1699
1717
|
```
|
|
1700
1718
|
|
|
@@ -1788,13 +1806,16 @@ createServer({
|
|
|
1788
1806
|
| `serverInfo` | MCP server identity — `{ name, version }` |
|
|
1789
1807
|
| `auth` | `(req) => identity \| null` — `null` rejects with 401 |
|
|
1790
1808
|
| `services` | the services to expose — an array, or `(auth) => ServiceDef[]` |
|
|
1809
|
+
| `runtimeTools` | managed pathless operations — an array, or `(auth) => RuntimeToolDefinition[]` |
|
|
1810
|
+
| `surfaces` | finite `{ key: { services, runtimeTools } }` registry prepared eagerly |
|
|
1811
|
+
| `selectSurface` | `(auth) => declaredKey` — required with `surfaces` |
|
|
1791
1812
|
| `context` | `(auth) => {…}` — values merged into every tool handler's `ctx` |
|
|
1792
1813
|
| `lifecycle` | `beforeHandle` / `afterHandle` — the tool-side auth gate (see below) |
|
|
1793
1814
|
| `hooks` | tool-call observability hooks — `afterToolCall` fires on every result |
|
|
1794
1815
|
| `extend` | extra advertised arguments resolved into handler context |
|
|
1795
1816
|
| `schemaValidation` | compatibility policy, typed-property guard and portable-format guard |
|
|
1796
1817
|
| `logger` | a `StitchLogger` for the `'warn'` policy |
|
|
1797
|
-
| `
|
|
1818
|
+
| `rawTools` | `(server, auth) => …` — explicit SDK registration without framework guarantees |
|
|
1798
1819
|
| `resources` | MCP Apps `ui://` resources mounted on every server |
|
|
1799
1820
|
| `instructions` | a short host-facing usage hint, surfaced to MCP tool-search |
|
|
1800
1821
|
| `coerceJsonArgs` | coerce JSON-stringified object/array arguments (default `true`) |
|
|
@@ -1804,14 +1825,37 @@ createServer({
|
|
|
1804
1825
|
| `protectedResource` | RFC 9728 metadata used by HTTP `401` responses |
|
|
1805
1826
|
| `sessionMode` | `'stateless'` (default) or explicit `'stateful'` session/SSE continuity |
|
|
1806
1827
|
|
|
1807
|
-
`services
|
|
1808
|
-
tenant can be shown only its own
|
|
1828
|
+
Direct `services` / `runtimeTools` factories, `context`, `selectSurface` and
|
|
1829
|
+
`rawTools` receive the resolved identity, so a tenant can be shown only its own
|
|
1830
|
+
tools and every handler can read `ctx.tenantId`.
|
|
1831
|
+
|
|
1832
|
+
Use direct factories only when the definitions are genuinely arbitrary per
|
|
1833
|
+
identity. For a bounded role/plan set, declare a finite registry instead:
|
|
1834
|
+
|
|
1835
|
+
```ts
|
|
1836
|
+
const handleMcp = createMcpHandler({
|
|
1837
|
+
serverInfo,
|
|
1838
|
+
auth,
|
|
1839
|
+
surfaces: {
|
|
1840
|
+
admin: { services: allServices, runtimeTools: [renderPreview] },
|
|
1841
|
+
member: { services: memberServices, runtimeTools: [renderPreview] },
|
|
1842
|
+
},
|
|
1843
|
+
selectSurface: (identity) => identity.isAdmin ? 'admin' : 'member',
|
|
1844
|
+
context: (identity) => ({ identity }),
|
|
1845
|
+
})
|
|
1846
|
+
```
|
|
1847
|
+
|
|
1848
|
+
Every declared entry is schema-validated and prepared once when the handler is
|
|
1849
|
+
constructed. The selected immutable descriptors are shared; the SDK server,
|
|
1850
|
+
transport, auth-derived context, lifecycle runner and tool-call context are
|
|
1851
|
+
fresh for every stateless request (or every stateful session). Unknown keys
|
|
1852
|
+
fail before the server connects. The registry never retains auth values.
|
|
1809
1853
|
|
|
1810
1854
|
### Stateless by default; stateful only when required
|
|
1811
1855
|
|
|
1812
1856
|
The default `sessionMode: 'stateless'` creates a fresh SDK server, transport,
|
|
1813
|
-
resolved auth/context and runner for each HTTP request. Static
|
|
1814
|
-
are still prepared once when the handler is constructed. There is no session
|
|
1857
|
+
resolved auth/context and runner for each HTTP request. Static direct and finite
|
|
1858
|
+
registry schemas are still prepared once when the handler is constructed. There is no session
|
|
1815
1859
|
map, event store, sweep timer or `Mcp-Session-Id`, so process replacement and
|
|
1816
1860
|
load balancing cannot strand a client on an in-memory session.
|
|
1817
1861
|
|
|
@@ -1988,8 +2032,8 @@ than a per-request `(req) => …`. Keep all logging on **stderr**: stdout is the
|
|
|
1988
2032
|
JSON-RPC channel.
|
|
1989
2033
|
|
|
1990
2034
|
Both transports build the server through the shared `buildMcpServer` — same
|
|
1991
|
-
contract pipeline, same
|
|
1992
|
-
|
|
2035
|
+
contract/runtime pipeline, same surface selection, context, hooks, raw escape
|
|
2036
|
+
hatch and instructions.
|
|
1993
2037
|
|
|
1994
2038
|
## OAuth 2.1 — a native remote connector
|
|
1995
2039
|
|
|
@@ -2231,9 +2275,9 @@ const handleMcp = createMcpHandler({
|
|
|
2231
2275
|
serverInfo: { name: 'my-app', version: '1.0.0' },
|
|
2232
2276
|
auth,
|
|
2233
2277
|
services: [service],
|
|
2278
|
+
runtimeTools: [renderPreview],
|
|
2234
2279
|
lifecycle: { beforeHandle: authHook },
|
|
2235
2280
|
hooks: audit.toolCall,
|
|
2236
|
-
nativeTools: ({ registerTool }) => registerTool(renderPreview),
|
|
2237
2281
|
})
|
|
2238
2282
|
|
|
2239
2283
|
const agentTools = mountAgent([service], {
|
|
@@ -2263,7 +2307,7 @@ use the same validation, lifecycle and hook path as contract tools.
|
|
|
2263
2307
|
|
|
2264
2308
|
### Explicit raw SDK registration
|
|
2265
2309
|
|
|
2266
|
-
`
|
|
2310
|
+
`rawTools` is deliberately named as an escape hatch. A tool registered there
|
|
2267
2311
|
does **not** receive stitchkit schema policy, lifecycle, per-call context or
|
|
2268
2312
|
hooks. The built-in `mountViewFile` helper remains raw for callers that choose
|
|
2269
2313
|
that boundary; it fetches media with SSRF and path-traversal defenses:
|
|
@@ -2275,14 +2319,51 @@ const handleMcp = createMcpHandler({
|
|
|
2275
2319
|
serverInfo: { name: 'my-app', version: '1.0.0' },
|
|
2276
2320
|
auth,
|
|
2277
2321
|
services: [service],
|
|
2278
|
-
|
|
2279
|
-
mountViewFile(rawServer, { baseDir: '/srv/uploads' }),
|
|
2322
|
+
rawTools: (server) => mountViewFile(server, { baseDir: '/srv/uploads' }),
|
|
2280
2323
|
})
|
|
2281
2324
|
```
|
|
2282
2325
|
|
|
2283
2326
|
Use raw registration only when opting out is intentional. For a protected
|
|
2284
|
-
`view_file`, define it
|
|
2285
|
-
`resolveMedia` core from its handler.
|
|
2327
|
+
`view_file`, define it with `defineRuntimeTool`, include it in `runtimeTools`,
|
|
2328
|
+
and call the exported `resolveMedia` core from its handler.
|
|
2329
|
+
|
|
2330
|
+
## Introspecting the complete tool surface
|
|
2331
|
+
|
|
2332
|
+
Deferred tool search, name snapshots and boot diagnostics accept the same
|
|
2333
|
+
object-shaped surface as the mounts. Contract operations are followed by
|
|
2334
|
+
runtime definitions, matching mount order; exposure filters and collisions are
|
|
2335
|
+
resolved by Stitchkit rather than by consumer code:
|
|
2336
|
+
|
|
2337
|
+
```ts
|
|
2338
|
+
import {
|
|
2339
|
+
buildToolManifest,
|
|
2340
|
+
listToolNames,
|
|
2341
|
+
summarizeTransports,
|
|
2342
|
+
} from 'stitchkit/tools'
|
|
2343
|
+
|
|
2344
|
+
const surface = { services, runtimeTools: [renderPreview] }
|
|
2345
|
+
|
|
2346
|
+
const manifest = buildToolManifest({
|
|
2347
|
+
...surface,
|
|
2348
|
+
transport: 'AGENT',
|
|
2349
|
+
flattenUnionInput: true,
|
|
2350
|
+
})
|
|
2351
|
+
|
|
2352
|
+
const names = listToolNames(surface)
|
|
2353
|
+
const summary = summarizeTransports(surface)
|
|
2354
|
+
```
|
|
2355
|
+
|
|
2356
|
+
`buildToolManifest` returns the exact immutable presentation schema shown to
|
|
2357
|
+
the selected MCP or Agent transport. It does not execute validation effects and
|
|
2358
|
+
fails first on duplicate contract/runtime names. There is no runtime-only
|
|
2359
|
+
manifest helper or public mount adapter: the framework owns merging and schema
|
|
2360
|
+
projection.
|
|
2361
|
+
|
|
2362
|
+
`listToolNames` remains diagnostic and reports `kind: 'contract' | 'runtime'`
|
|
2363
|
+
for every identity. `summarizeTransports` returns `contractServices`,
|
|
2364
|
+
`runtimeTools`, aggregate `totals`, and a `sources` breakdown. Runtime tools
|
|
2365
|
+
contribute only to MCP/Agent according to their `transports`; they never inflate
|
|
2366
|
+
HTTP or CLI counts.
|
|
2286
2367
|
|
|
2287
2368
|
## Logging tool calls — `createToolLogger`
|
|
2288
2369
|
|
|
@@ -2302,9 +2383,9 @@ Pass `log` to redirect the line, or `onRecord` to feed a metrics sink the
|
|
|
2302
2383
|
structured `ToolCallRecord`. That record carries `traceId` whenever an
|
|
2303
2384
|
observability context is active, so a tool call made inside an HTTP request
|
|
2304
2385
|
joins that request's log line on one key — see
|
|
2305
|
-
[Observability](./observability.md). For a boot-time picture of what is exposed
|
|
2306
|
-
`summarizeTransports(services)` returns
|
|
2307
|
-
|
|
2386
|
+
[Observability](./observability.md). For a boot-time picture of what is exposed
|
|
2387
|
+
where, `summarizeTransports({ services, runtimeTools })` returns the mixed
|
|
2388
|
+
per-transport counts for you to log.
|
|
2308
2389
|
|
|
2309
2390
|
## One handler, three callers
|
|
2310
2391
|
|
|
@@ -3235,27 +3316,46 @@ onError: (ctx, err) => {
|
|
|
3235
3316
|
|
|
3236
3317
|
## Domain errors — `defineErrors`
|
|
3237
3318
|
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
`
|
|
3241
|
-
object):
|
|
3319
|
+
Declare each domain code, HTTP status and optional structured-details schema in
|
|
3320
|
+
one immutable registry. The generated functions construct typed branded
|
|
3321
|
+
`AppError` instances; ordinary `throw` remains explicit at the call site:
|
|
3242
3322
|
|
|
3243
3323
|
```ts
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3324
|
+
import { z } from 'zod'
|
|
3325
|
+
|
|
3326
|
+
export const { errors, codes, definitions, isCode } = defineErrors({
|
|
3327
|
+
SESSION_NOT_FOUND: { status: 404 },
|
|
3328
|
+
QUOTA_EXCEEDED: {
|
|
3329
|
+
status: 429,
|
|
3330
|
+
details: z.object({ retryAfterSeconds: z.number().int().positive() }),
|
|
3331
|
+
},
|
|
3247
3332
|
})
|
|
3248
3333
|
|
|
3249
|
-
|
|
3250
|
-
throw errors.
|
|
3334
|
+
throw errors.SESSION_NOT_FOUND({ message: 'No such session' })
|
|
3335
|
+
throw errors.QUOTA_EXCEEDED({
|
|
3336
|
+
message: 'Try later',
|
|
3337
|
+
details: { retryAfterSeconds: 30 },
|
|
3338
|
+
hint: 'Wait for the current window to expire',
|
|
3339
|
+
})
|
|
3340
|
+
|
|
3341
|
+
// Construction without throwing is useful for composition or inspection.
|
|
3342
|
+
const error = errors.QUOTA_EXCEEDED({ details: { retryAfterSeconds: 30 } })
|
|
3343
|
+
definitions.QUOTA_EXCEEDED.status // 429 — same source, no copied status map
|
|
3251
3344
|
|
|
3252
3345
|
// client — match the code, never a magic string
|
|
3253
3346
|
if (err instanceof ApiError && err.code === codes.SESSION_NOT_FOUND) { … }
|
|
3254
3347
|
```
|
|
3255
3348
|
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3349
|
+
With no `details` schema, the options object forbids `details`. A required
|
|
3350
|
+
`z.object` makes `details` required; `z.object(...).optional()` makes it
|
|
3351
|
+
optional. Supplied details are parsed when the error is constructed, before any
|
|
3352
|
+
transport sees them. `code` remains literal and the parsed details type is
|
|
3353
|
+
retained on the returned `AppError`.
|
|
3354
|
+
|
|
3355
|
+
HTTP renders the complete code/status/message/details/hint. Tool transports keep
|
|
3356
|
+
their established model-facing projection (code/details/hint, without HTTP
|
|
3357
|
+
status), while `invokeOrThrow` recovers the exact normalized `AppError`. The
|
|
3358
|
+
codes and schemas remain application-owned; Stitchkit stays domain-free.
|
|
3259
3359
|
|
|
3260
3360
|
## `createErrorHook`
|
|
3261
3361
|
|
|
@@ -3287,6 +3387,27 @@ createServer({ services, hooks: { onError } })
|
|
|
3287
3387
|
Codes you threw yourself (not stitchkit's) pass through `codeMap` unchanged; the
|
|
3288
3388
|
`satisfies Record<StitchErrorCode, …>` keeps the map exhaustive across upgrades.
|
|
3289
3389
|
|
|
3390
|
+
Both `onError` and `render` may be asynchronous and receive the matched endpoint
|
|
3391
|
+
as their final argument. The observer is awaited before rendering, so it can
|
|
3392
|
+
resolve identity or enrich the request context even for failures raised before
|
|
3393
|
+
`beforeHandle`:
|
|
3394
|
+
|
|
3395
|
+
```ts
|
|
3396
|
+
const onError = createErrorHook({
|
|
3397
|
+
onError: async (_error, _info, ctx, endpoint) => {
|
|
3398
|
+
await attributeFailedRequest(ctx, endpoint)
|
|
3399
|
+
},
|
|
3400
|
+
render: (info, ctx) => ({
|
|
3401
|
+
error: { code: info.code },
|
|
3402
|
+
actorId: ctx.actorId,
|
|
3403
|
+
}),
|
|
3404
|
+
})
|
|
3405
|
+
```
|
|
3406
|
+
|
|
3407
|
+
`endpoint` is `undefined` when the failure happened before route resolution.
|
|
3408
|
+
Synchronous callbacks and renderers that declare fewer parameters continue to
|
|
3409
|
+
work normally.
|
|
3410
|
+
|
|
3290
3411
|
Invalid input (a `ZodError`) is classified as `VALIDATION_ERROR` 400 before it
|
|
3291
3412
|
reaches `render` — a client fault is an honest 400, not a 500 — and the
|
|
3292
3413
|
offending fields arrive as structured `info.details.issues`, so your `render`
|
|
@@ -3459,6 +3580,12 @@ context carries nothing yet, so your value always wins.
|
|
|
3459
3580
|
`event.serviceName` / `event.action` are present on every event, including a
|
|
3460
3581
|
pre-handler 400. Nothing to wire.
|
|
3461
3582
|
|
|
3583
|
+
When failure attribution itself is asynchronous, use `createErrorHook`'s
|
|
3584
|
+
`onError(error, info, ctx, endpoint)` observer. The framework awaits it before
|
|
3585
|
+
calling `render`, so identity or audit enrichment is visible to both the final
|
|
3586
|
+
error envelope and the request event. `endpoint` is the matched `MethodDef`, or
|
|
3587
|
+
`undefined` when routing failed before a method was selected.
|
|
3588
|
+
|
|
3462
3589
|
**Domain dimensions** — attach your own tenant / project / entity id with
|
|
3463
3590
|
`setRequestDimensions`. It is an opaque `Record<string, string>` the core gives no
|
|
3464
3591
|
meaning to (→ ADR 0021). Resolve it cheaply from `ctx.params` / headers in
|
|
@@ -4101,6 +4228,119 @@ current one *up to* your target, and apply each snippet.
|
|
|
4101
4228
|
|
|
4102
4229
|
## Unreleased breaking migrations
|
|
4103
4230
|
|
|
4231
|
+
Tool introspection now accepts one object-shaped contract/runtime surface. Stop
|
|
4232
|
+
calling the internal contract collector or merging a locally converted runtime
|
|
4233
|
+
manifest:
|
|
4234
|
+
|
|
4235
|
+
```ts
|
|
4236
|
+
// before
|
|
4237
|
+
buildToolManifest(services.flatMap((service) => collectTools(service, 'AGENT')))
|
|
4238
|
+
listToolNames(services)
|
|
4239
|
+
summarizeTransports(services)
|
|
4240
|
+
|
|
4241
|
+
// after
|
|
4242
|
+
const surface = { services, runtimeTools }
|
|
4243
|
+
buildToolManifest({ ...surface, transport: 'AGENT' })
|
|
4244
|
+
listToolNames(surface)
|
|
4245
|
+
summarizeTransports(surface)
|
|
4246
|
+
```
|
|
4247
|
+
|
|
4248
|
+
`ToolNameEntry` adds `kind: 'contract' | 'runtime'`. `TransportSummary` is now
|
|
4249
|
+
`{ contractServices, runtimeTools, totals, sources }`; replace `services` and
|
|
4250
|
+
`perService` reads with the explicit counts and mixed-source breakdown. There
|
|
4251
|
+
is no positional overload and no `buildRuntimeToolManifest`: Stitchkit owns the
|
|
4252
|
+
combined order, transport filtering, canonical presentation schema and
|
|
4253
|
+
cross-origin collision checks.
|
|
4254
|
+
|
|
4255
|
+
`defineErrors` now uses one Zod-first definition object and returns constructors
|
|
4256
|
+
instead of positional throwers. Add explicit `throw`, move message/details/hint
|
|
4257
|
+
into one options object, and declare a details schema when that code carries
|
|
4258
|
+
structured context:
|
|
4259
|
+
|
|
4260
|
+
```ts
|
|
4261
|
+
// before
|
|
4262
|
+
const { errors } = defineErrors({ QUOTA_EXCEEDED: 429 })
|
|
4263
|
+
errors.QUOTA_EXCEEDED('Try later', { retryAfterSeconds: 30 }, 'Wait')
|
|
4264
|
+
|
|
4265
|
+
// after
|
|
4266
|
+
const { errors, definitions } = defineErrors({
|
|
4267
|
+
QUOTA_EXCEEDED: {
|
|
4268
|
+
status: 429,
|
|
4269
|
+
details: z.object({ retryAfterSeconds: z.number().positive() }),
|
|
4270
|
+
},
|
|
4271
|
+
})
|
|
4272
|
+
throw errors.QUOTA_EXCEEDED({
|
|
4273
|
+
message: 'Try later',
|
|
4274
|
+
details: { retryAfterSeconds: 30 },
|
|
4275
|
+
hint: 'Wait',
|
|
4276
|
+
})
|
|
4277
|
+
```
|
|
4278
|
+
|
|
4279
|
+
There is no positional overload. A code without `details` forbids them; use an
|
|
4280
|
+
optional object schema when the details object itself is optional. Read status
|
|
4281
|
+
and schemas from the frozen `definitions` registry instead of maintaining a
|
|
4282
|
+
parallel map.
|
|
4283
|
+
|
|
4284
|
+
Managed MCP runtime tools are now declared as immutable data. Move protected
|
|
4285
|
+
registrar calls to `runtimeTools`; rename deliberate raw SDK registration to
|
|
4286
|
+
`rawTools`. There is no registrar alias:
|
|
4287
|
+
|
|
4288
|
+
```ts
|
|
4289
|
+
// before — protected
|
|
4290
|
+
createMcpHandler({
|
|
4291
|
+
services,
|
|
4292
|
+
nativeTools: ({ registerTool }) => registerTool(preview),
|
|
4293
|
+
})
|
|
4294
|
+
|
|
4295
|
+
// after — protected and prepared with the rest of the surface
|
|
4296
|
+
createMcpHandler({ services, runtimeTools: [preview] })
|
|
4297
|
+
|
|
4298
|
+
// before — deliberate raw SDK opt-out
|
|
4299
|
+
nativeTools: ({ rawServer }, auth) => mountRaw(rawServer, auth)
|
|
4300
|
+
|
|
4301
|
+
// after — still a deliberate raw SDK opt-out
|
|
4302
|
+
rawTools: (server, auth) => mountRaw(server, auth)
|
|
4303
|
+
```
|
|
4304
|
+
|
|
4305
|
+
When identity selects from a bounded set, replace a repeatedly prepared
|
|
4306
|
+
`services(auth)` factory with a finite registry:
|
|
4307
|
+
|
|
4308
|
+
```ts
|
|
4309
|
+
createMcpHandler({
|
|
4310
|
+
surfaces: {
|
|
4311
|
+
admin: { services: allServices, runtimeTools: [preview] },
|
|
4312
|
+
member: { services: memberServices, runtimeTools: [preview] },
|
|
4313
|
+
},
|
|
4314
|
+
selectSurface: (auth) => auth.isAdmin ? 'admin' : 'member',
|
|
4315
|
+
})
|
|
4316
|
+
```
|
|
4317
|
+
|
|
4318
|
+
Keep direct `services(auth)` / `runtimeTools(auth)` only for genuinely
|
|
4319
|
+
unbounded definitions; Stitchkit intentionally does not cache arbitrary auth
|
|
4320
|
+
values.
|
|
4321
|
+
|
|
4322
|
+
Contract success bodies are now determined by the presence of `output`, not by
|
|
4323
|
+
the handler's runtime value. A nullable output returns JSON `null` with status
|
|
4324
|
+
`200`; `undefined` with a declared output and non-null data without an output
|
|
4325
|
+
schema are contract violations:
|
|
4326
|
+
|
|
4327
|
+
```ts
|
|
4328
|
+
// nullable JSON data: 200 with body `null`
|
|
4329
|
+
session: {
|
|
4330
|
+
method: 'GET', path: '/session', desc: 'Current session',
|
|
4331
|
+
output: SessionSchema.nullable(),
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4334
|
+
// bodyless operation: 204 with no body
|
|
4335
|
+
logout: {
|
|
4336
|
+
method: 'POST', path: '/logout', desc: 'End the session',
|
|
4337
|
+
}
|
|
4338
|
+
```
|
|
4339
|
+
|
|
4340
|
+
Add an output schema to every handler that returns data. Omit `output` and
|
|
4341
|
+
return nothing for bodyless operations; runtime tools follow the same rule and
|
|
4342
|
+
type no-output handlers as `void`.
|
|
4343
|
+
|
|
4104
4344
|
`createToolInvoker` now separates immutable registry preparation from per-call
|
|
4105
4345
|
runtime state. Move source/context/lifecycle/hooks/output-strip reporting from
|
|
4106
4346
|
the factory config to the third invocation argument. Use `invokeOrThrow` when a
|
|
@@ -4182,7 +4422,7 @@ const preview = defineRuntimeTool({
|
|
|
4182
4422
|
}),
|
|
4183
4423
|
},
|
|
4184
4424
|
})
|
|
4185
|
-
|
|
4425
|
+
runtimeTools: [preview]
|
|
4186
4426
|
```
|
|
4187
4427
|
|
|
4188
4428
|
The removed `NativeMcp*` types have no aliases. Use `RuntimeToolDefinition`,
|
|
@@ -4381,7 +4621,7 @@ name derivation, diff them mechanically:
|
|
|
4381
4621
|
|
|
4382
4622
|
```ts
|
|
4383
4623
|
import { listToolNames } from 'stitchkit/tools'
|
|
4384
|
-
console.log(JSON.stringify(listToolNames(services), null, 2))
|
|
4624
|
+
console.log(JSON.stringify(listToolNames({ services, runtimeTools }), null, 2))
|
|
4385
4625
|
```
|
|
4386
4626
|
|
|
4387
4627
|
`listToolNames` never throws on an illegal name — that is deliberate, so it can
|
|
@@ -4493,7 +4733,7 @@ from the root `stitchkit`.
|
|
|
4493
4733
|
| `ALL_TRANSPORTS` | constant | `['HTTP', 'MCP', 'AGENT', 'CLI']` |
|
|
4494
4734
|
| `ContractDef` | _type_ | a defined contract |
|
|
4495
4735
|
| `ContractMeta` | _type_ | a contract's `prefix` + optional `scope` and `meta` (a default every endpoint shallow-merges over) |
|
|
4496
|
-
| `EndpointDef` | _type_ | a single endpoint definition |
|
|
4736
|
+
| `EndpointDef` | _type_ | a single endpoint definition; `output` declares JSON response presence (`null` is data, `undefined` is invalid) |
|
|
4497
4737
|
| `HeadEndpointDef` | _type_ | explicit HTTP-only, bodyless `HEAD` endpoint definition |
|
|
4498
4738
|
| `EndpointResponseMeta` | _type_ | static success metadata declared by an HTTP-only typed-data endpoint |
|
|
4499
4739
|
| `ResponseMetadata` | _type_ | per-request outbound collector exposed as `ctx.response` only for a `responseMeta` endpoint |
|
|
@@ -4530,9 +4770,17 @@ from the root `stitchkit`.
|
|
|
4530
4770
|
| `conflict` | function | throw `409 CONFLICT` |
|
|
4531
4771
|
| `rateLimited` | function | throw `429 RATE_LIMITED` |
|
|
4532
4772
|
| `appError` | function | throw an `AppError` for any code |
|
|
4533
|
-
| `defineErrors` | function | declare domain error
|
|
4534
|
-
| `DefinedErrors` | _type_ | the `{ errors, codes, isCode }` handle `defineErrors` returns |
|
|
4535
|
-
| `
|
|
4773
|
+
| `defineErrors` | function | declare immutable domain error definitions → typed `AppError` constructors, codes and schemas — [guide](../guide/auth-and-errors.md#domain-errors--defineerrors) |
|
|
4774
|
+
| `DefinedErrors` | _type_ | the `{ errors, codes, definitions, isCode }` handle `defineErrors` returns |
|
|
4775
|
+
| `DefinedAppError` | _type_ | literal-code error instance with schema-refined details |
|
|
4776
|
+
| `ErrorDefinition` | _type_ | `{ status, details? }` definition for one domain code |
|
|
4777
|
+
| `ErrorDefinitions` | _type_ | string-keyed domain error definition registry |
|
|
4778
|
+
| `ErrorDetailsSchema` | _type_ | required or optional Zod object accepted for structured details |
|
|
4779
|
+
| `ErrorDetailsOutput` | _type_ | parsed details inferred from one definition |
|
|
4780
|
+
| `ErrorFactoryArguments` | _type_ | options tuple with forbidden/required/optional details inferred per code |
|
|
4781
|
+
| `ErrorFactory` | _type_ | one typed `AppError` constructor |
|
|
4782
|
+
| `ErrorFactories` | _type_ | mapped constructor registry derived from all definitions |
|
|
4783
|
+
| `FrozenErrorDefinitions` | _type_ | read-only definition registry returned to consumers |
|
|
4536
4784
|
| `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) |
|
|
4537
4785
|
| `StitchErrorCode` | _type_ | a code stitchkit itself emits (`keyof STITCH_ERROR_STATUS`) |
|
|
4538
4786
|
| `isStitchErrorCode` | function | type guard — is a code one of stitchkit's own? |
|
|
@@ -4600,8 +4848,8 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
4600
4848
|
| Export | Kind | Summary |
|
|
4601
4849
|
|--------|------|---------|
|
|
4602
4850
|
| `createAuthHook` | function | a scope-enforcing `beforeHandle` hook — [guide](../guide/auth-and-errors.md#createauthhook) |
|
|
4603
|
-
| `createErrorHook` | function | an `onError` hook from a code map + envelope renderer — [guide](../guide/auth-and-errors.md#createerrorhook) |
|
|
4604
|
-
| `ErrorHookConfig` | _type_ | config for `createErrorHook` |
|
|
4851
|
+
| `createErrorHook` | function | an async-capable, endpoint-aware `onError` hook from a code map + envelope renderer — [guide](../guide/auth-and-errors.md#createerrorhook) |
|
|
4852
|
+
| `ErrorHookConfig` | _type_ | async observer/renderer config for `createErrorHook` |
|
|
4605
4853
|
| `ResolvedError` | _type_ | the normalised error handed to `createErrorHook`'s `render` |
|
|
4606
4854
|
| `createBearerResolver` | function | a bearer-token identity resolver |
|
|
4607
4855
|
| `signJwt` | function | sign an HS256 JWT |
|
|
@@ -4750,7 +4998,7 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
4750
4998
|
|--------|------|---------|
|
|
4751
4999
|
| `createMcpHandler` | function | a complete Streamable-HTTP MCP server — [guide](../guide/mcp-and-agents.md#mcp--createmcphandler) |
|
|
4752
5000
|
| `createStdioMcpServer` | function | a complete stdio MCP server — [guide](../guide/mcp-and-agents.md#mcp-over-stdio--createstdiomcpserver) |
|
|
4753
|
-
| `buildMcpServer` | function | build an `McpServer` from
|
|
5001
|
+
| `buildMcpServer` | function | build an `McpServer` from contract/runtime surfaces — the transport-neutral core |
|
|
4754
5002
|
| `mountMcp` | function | add contract tools to an existing `McpServer` — [guide](../guide/mcp-and-agents.md#mountmcp) |
|
|
4755
5003
|
| `implementRemote` | function | bind a contract to a remote HTTP API — [guide](../guide/mcp-and-agents.md#proxying-a-remote-api--implementremote) |
|
|
4756
5004
|
| `mountAgent` | function | a Vercel AI SDK `ToolSet` from a service — [guide](../guide/mcp-and-agents.md#ai-agents--mountagent) |
|
|
@@ -4761,20 +5009,26 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
4761
5009
|
| `mountViewFile` | function | a native multimodal "view file" MCP tool |
|
|
4762
5010
|
| `resolveMedia` | function | resolve a media reference for a tool result |
|
|
4763
5011
|
| `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)) |
|
|
4764
|
-
| `listToolNames` | function | every
|
|
5012
|
+
| `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) |
|
|
4765
5013
|
| `McpHandlerConfig` | _type_ | config for `createMcpHandler` |
|
|
5014
|
+
| `McpHttpConfig` | _type_ | HTTP auth, protected-resource and session options composed into `McpHandlerConfig` |
|
|
4766
5015
|
| `McpSessionMode` | _type_ | `'stateless' \| 'stateful'`; HTTP defaults to request-isolated stateless mode |
|
|
4767
5016
|
| `StdioMcpServerConfig` | _type_ | config for `createStdioMcpServer` |
|
|
4768
5017
|
| `McpServerBuildConfig` | _type_ | shared config for `buildMcpServer` |
|
|
5018
|
+
| `McpServerSharedConfig` | _type_ | transport-neutral options shared by direct and finite surface configs |
|
|
5019
|
+
| `DirectMcpSurfaceConfig` | _type_ | static or identity-dynamic `services` / `runtimeTools` source |
|
|
5020
|
+
| `FiniteMcpSurfaceConfig` | _type_ | bounded `surfaces` registry plus typed selector |
|
|
5021
|
+
| `McpSurfaceDefinition` | _type_ | one immutable `{ services, runtimeTools }` MCP surface |
|
|
5022
|
+
| `McpSurfaceRegistry` | _type_ | finite keyed surface registry for eager preparation |
|
|
5023
|
+
| `StdioAuthConfig` | _type_ | startup identity composed into `StdioMcpServerConfig` |
|
|
4769
5024
|
| `ImplementRemoteOptions` | _type_ | options for `implementRemote` |
|
|
4770
5025
|
| `McpMountConfig` | _type_ | config for `mountMcp` |
|
|
4771
5026
|
| `McpSchemaValidationConfig` | _type_ | shared `{ policy, requireTypedProperties, allowUntyped, requirePortableFormats, allowFormats }` profile |
|
|
4772
5027
|
| `ValidateMcpSchemasConfig` | _type_ | standalone validation profile plus `services`, `extend`, flattening and logger |
|
|
4773
|
-
| `NativeMcpRegistrar` | _type_ | protected `registerTool` plus explicit unprotected `rawServer` access |
|
|
4774
5028
|
| `RuntimeToolDefinition` | _type_ | transport-neutral pathless operation with identity, schemas, handler and optional presenters |
|
|
4775
5029
|
| `RuntimeToolDefinitionBase` | _type_ | common name, identity, input, exposure and MCP metadata fields |
|
|
4776
5030
|
| `RuntimeToolDefinitionWithOutput` | _type_ | runtime definition whose handler and presenters share a validated output type |
|
|
4777
|
-
| `RuntimeToolDefinitionWithoutOutput` | _type_ | runtime definition
|
|
5031
|
+
| `RuntimeToolDefinitionWithoutOutput` | _type_ | runtime definition with a void handler and no presentation callbacks |
|
|
4778
5032
|
| `RuntimeToolIdentity` | _type_ | `{ serviceName, action, scope?, method, meta? }` for runtime lifecycle/audit |
|
|
4779
5033
|
| `RuntimeToolHandlerContext` | _type_ | runtime context with the definition's parsed input |
|
|
4780
5034
|
| `RuntimeToolOutput` | _type_ | output inferred from a runtime tool's optional Zod schema |
|
|
@@ -4810,7 +5064,7 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
4810
5064
|
| `findNonPortableFormats` | function | deep finder for formats outside the portable MCP/AJV baseline |
|
|
4811
5065
|
| `NonPortableFormat` | _type_ | one `{ path, format }` portability finding |
|
|
4812
5066
|
| `PORTABLE_JSON_SCHEMA_FORMATS` | constant | portable-format baseline used by MCP validation |
|
|
4813
|
-
| `ToolNameEntry` | _type_ | one `listToolNames` row — `{ name, service, method, transports }` |
|
|
5067
|
+
| `ToolNameEntry` | _type_ | one `listToolNames` row — `{ kind, name, service, method, transports }` |
|
|
4814
5068
|
| `IncompatibleSchemaPolicy` | _type_ | `'throw' \| 'skip' \| 'warn'` |
|
|
4815
5069
|
| `McpMediaContent` | _type_ | a multimodal MCP content item |
|
|
4816
5070
|
|
|
@@ -4869,12 +5123,15 @@ Advanced building blocks — the shared machinery the mounts are built on.
|
|
|
4869
5123
|
|--------|------|---------|
|
|
4870
5124
|
| `collectTools` | function | resolve a service's methods to mountable tools (the shared resolver) |
|
|
4871
5125
|
| `createToolLogger` | function | a ready `afterToolCall` that logs every tool call — [guide](../guide/mcp-and-agents.md#logging-tool-calls--createtoollogger) |
|
|
4872
|
-
| `summarizeTransports` | function |
|
|
4873
|
-
| `buildToolManifest` | function |
|
|
5126
|
+
| `summarizeTransports` | function | mixed contract/runtime operation counts and per-source breakdown for a boot-time summary |
|
|
5127
|
+
| `buildToolManifest` | function | transport-aware searchable `{ name, description, inputSchema }` rows from a mixed surface |
|
|
4874
5128
|
| `ToolLoggerConfig` | _type_ | config for `createToolLogger` |
|
|
4875
5129
|
| `ToolCallRecord` | _type_ | the structured record `createToolLogger` passes to `onRecord` |
|
|
4876
|
-
| `TransportSummary` | _type_ |
|
|
5130
|
+
| `TransportSummary` | _type_ | `{ contractServices, runtimeTools, totals, sources }` from `summarizeTransports` |
|
|
4877
5131
|
| `TransportCounts` | _type_ | per-transport counts (`{ HTTP, MCP, AGENT, CLI }`) |
|
|
5132
|
+
| `ToolSurfaceDefinition` | _type_ | shared object-shaped `{ services?, runtimeTools? }` introspection surface |
|
|
5133
|
+
| `ToolSurfaceTransport` | _type_ | tool collector transport: `'MCP' \| 'AGENT' \| 'CLI'` |
|
|
5134
|
+
| `ToolManifestConfig` | _type_ | mixed surface plus required model-facing `transport` and presentation options |
|
|
4878
5135
|
| `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments |
|
|
4879
5136
|
| `flattenToolJsonSchema` | function | project structurally identifiable discriminated unions in a JSON Schema document into conservative object joins; never executes validation |
|
|
4880
5137
|
| `ToolPresentationSchema` | _type_ | immutable model-facing JSON Schema document shared by tool transports |
|
package/package.json
CHANGED