stitchkit 0.53.2 → 0.55.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 +4 -1
- package/dist/browser/socket-io.d.ts +11 -1
- package/dist/browser/socket-io.d.ts.map +1 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +15 -8
- package/dist/contract/define.d.ts +27 -0
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/contract/file-ref.d.ts +13 -0
- package/dist/contract/file-ref.d.ts.map +1 -0
- package/dist/contract/index.d.ts +2 -1
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +21 -17
- package/dist/files/boundary.d.ts +52 -0
- package/dist/files/boundary.d.ts.map +1 -0
- package/dist/files.d.ts +3 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +16 -0
- package/dist/{index-c78w2y09.js → index-0gv7k0ra.js} +515 -156
- package/dist/index-1bx83sw4.js +4 -0
- package/dist/index-1k16zv57.js +269 -0
- package/dist/index-6k1937bx.js +20 -0
- package/dist/{index-bmnhqtya.js → index-7eq9va6e.js} +1 -4
- package/dist/{index-yxpe3phd.js → index-cby4ar3v.js} +1 -18
- package/dist/index-h60df7rj.js +110 -0
- package/dist/{index-yez419px.js → index-js7yexng.js} +19 -6
- package/dist/index-k4dftwf7.js +66 -0
- package/dist/{index-pdv1mjjr.js → index-npye5kt7.js} +30 -59
- package/dist/{index-yydqk4fh.js → index-pzc32v75.js} +90 -3
- package/dist/{index-e1es808r.js → index-qh4xevf5.js} +1 -1
- package/dist/{index-gvha32jh.js → index-qx84bqzk.js} +2 -2
- package/dist/{index-pfqjb5xy.js → index-rgd18hx3.js} +23 -5
- package/dist/index-sbdmyz75.js +18 -0
- package/dist/{index-173tvqcp.js → index-v4bbq3p4.js} +9 -114
- package/dist/index.js +162 -35
- package/dist/node.js +30 -27
- package/dist/observability/event.d.ts +2 -16
- package/dist/observability/event.d.ts.map +1 -1
- package/dist/observability/index.js +28 -25
- package/dist/react.js +2 -2
- package/dist/realtime/contract.d.ts +9 -0
- package/dist/realtime/contract.d.ts.map +1 -1
- package/dist/realtime/index.d.ts +2 -1
- package/dist/realtime/index.d.ts.map +1 -1
- package/dist/realtime/request.d.ts +21 -0
- package/dist/realtime/request.d.ts.map +1 -0
- package/dist/realtime/socket.d.ts +3 -0
- package/dist/realtime/socket.d.ts.map +1 -1
- package/dist/remote.d.ts +9 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +79 -0
- package/dist/server/context-contribution.d.ts +10 -0
- package/dist/server/context-contribution.d.ts.map +1 -0
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +133 -88
- package/dist/server/lifecycle.d.ts +4 -0
- package/dist/server/lifecycle.d.ts.map +1 -0
- package/dist/server/middleware/auth.d.ts +35 -7
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/process-signal-common.d.ts +6 -0
- package/dist/server/process-signal-common.d.ts.map +1 -0
- package/dist/server/process-signals.d.ts.map +1 -1
- package/dist/server/types.d.ts +1 -1
- package/dist/server/types.d.ts.map +1 -1
- package/dist/testing/surface-conformance.d.ts +47 -0
- package/dist/testing/surface-conformance.d.ts.map +1 -0
- package/dist/testing/surface-manifest.d.ts +156 -0
- package/dist/testing/surface-manifest.d.ts.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +366 -3
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/async-operation.d.ts +168 -0
- package/dist/tools/async-operation.d.ts.map +1 -0
- package/dist/tools/cli-args.d.ts +1 -1
- package/dist/tools/cli-args.d.ts.map +1 -1
- package/dist/tools/cli-command.d.ts +29 -0
- package/dist/tools/cli-command.d.ts.map +1 -0
- package/dist/tools/cli-format.d.ts +2 -2
- package/dist/tools/cli-format.d.ts.map +1 -1
- package/dist/tools/cli-wait.d.ts +11 -2
- package/dist/tools/cli-wait.d.ts.map +1 -1
- package/dist/tools/cli.d.ts +13 -6
- package/dist/tools/cli.d.ts.map +1 -1
- package/dist/tools/define-download-tool.d.ts +18 -0
- package/dist/tools/define-download-tool.d.ts.map +1 -0
- package/dist/tools/define-upload-tool.d.ts +16 -0
- package/dist/tools/define-upload-tool.d.ts.map +1 -0
- package/dist/tools/define-view-file-tool.d.ts +10 -0
- package/dist/tools/define-view-file-tool.d.ts.map +1 -0
- package/dist/tools/define-wait-tool.d.ts +22 -0
- package/dist/tools/define-wait-tool.d.ts.map +1 -0
- package/dist/tools/download-core.d.ts +20 -0
- package/dist/tools/download-core.d.ts.map +1 -0
- package/dist/tools/execute.d.ts +3 -1
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/lifecycle.d.ts +4 -0
- package/dist/tools/lifecycle.d.ts.map +1 -0
- package/dist/tools/mcp-round.d.ts +0 -1
- package/dist/tools/mcp-round.d.ts.map +1 -1
- package/dist/tools/mcp-stdio-signals.d.ts +33 -0
- package/dist/tools/mcp-stdio-signals.d.ts.map +1 -0
- package/dist/tools/mount-download.d.ts +10 -4
- package/dist/tools/mount-download.d.ts.map +1 -1
- package/dist/tools/mount-upload.d.ts +4 -2
- package/dist/tools/mount-upload.d.ts.map +1 -1
- package/dist/tools/mount-wait.d.ts.map +1 -1
- package/dist/tools/native-definition.d.ts +15 -0
- package/dist/tools/native-definition.d.ts.map +1 -0
- package/dist/tools/runtime-tool.d.ts +5 -4
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/surface.d.ts.map +1 -1
- package/dist/tools/upload-core.d.ts +4 -0
- package/dist/tools/upload-core.d.ts.map +1 -0
- package/dist/tools/view-file.d.ts +84 -23
- package/dist/tools/view-file.d.ts.map +1 -1
- package/dist/tools/wait-core.d.ts +16 -2
- package/dist/tools/wait-core.d.ts.map +1 -1
- package/dist/tools.d.ts +12 -3
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +827 -495
- package/llms-full.txt +720 -53
- package/package.json +10 -2
package/llms-full.txt
CHANGED
|
@@ -33,7 +33,7 @@ realtime, `@tanstack/react-query` + `react-query-kit` for React). See
|
|
|
33
33
|
|
|
34
34
|
## Entrypoints
|
|
35
35
|
|
|
36
|
-
stitchkit ships
|
|
36
|
+
stitchkit ships eleven entrypoints. Each is import-safe for one environment —
|
|
37
37
|
keeping server-only code (`Bun.serve`, the MCP SDK) out of browser bundles.
|
|
38
38
|
|
|
39
39
|
| Import | Use in | Holds |
|
|
@@ -44,12 +44,15 @@ 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/remote` | browser **and** server | peer-free `implementRemote` for thin HTTP proxy processes |
|
|
48
|
+
| `stitchkit/files` | server (Bun or Node) | peer-free managed local-file boundary |
|
|
47
49
|
| `stitchkit/observability` | server | request/tool event projections — `createObservability`, trace context, sanitisation |
|
|
48
50
|
| `stitchkit/testing` | tests on Bun or Node | in-process generated clients over a real Fetch handler, without a TCP port |
|
|
49
51
|
| `stitchkit/react` | browser | `createCursorQuery`, `createCacheBridge` |
|
|
50
52
|
|
|
51
53
|
Rule of thumb: browser code imports `stitchkit` and `stitchkit/react`; server
|
|
52
|
-
code adds `stitchkit/server` (or `stitchkit/node` on Node) and
|
|
54
|
+
code adds `stitchkit/server` (or `stitchkit/node` on Node) and opts into
|
|
55
|
+
`stitchkit/tools`, `stitchkit/remote` or `stitchkit/files` by capability.
|
|
53
56
|
The full export list of each is in the [API reference](../api/reference.md).
|
|
54
57
|
|
|
55
58
|
## Project layout
|
|
@@ -2364,8 +2367,9 @@ must not be used as authorization input.
|
|
|
2364
2367
|
### Modern discovery, routing and cache hints
|
|
2365
2368
|
|
|
2366
2369
|
Stitchkit forwards the negotiated protocol era and validated MCP request data
|
|
2367
|
-
to `RequestEvent.mcp
|
|
2368
|
-
identity. The SDK validates
|
|
2370
|
+
to both `RequestEvent.mcp` and the current managed call's typed `context.mcp`;
|
|
2371
|
+
untrusted routing headers never become application identity. The SDK validates
|
|
2372
|
+
`MCP-Protocol-Version`, JSON-RPC shape and routing
|
|
2369
2373
|
metadata before lifecycle or tool handlers run. Framework-owned tool and
|
|
2370
2374
|
resource registration preserves declaration order across HTTP and stdio. Tool
|
|
2371
2375
|
manifests preserve mount order; `listToolNames` is a sorted diagnostics view.
|
|
@@ -2380,6 +2384,30 @@ finite `surfaces` registry when identities share a provably immutable surface.
|
|
|
2380
2384
|
Stitchkit never advertises list-change or subscription capabilities
|
|
2381
2385
|
without an implementation.
|
|
2382
2386
|
|
|
2387
|
+
### Typed MCP call metadata
|
|
2388
|
+
|
|
2389
|
+
Contract handlers, runtime-tool handlers/factories, lifecycle and tool hooks all
|
|
2390
|
+
see the same optional `McpCallContext` for the current invocation:
|
|
2391
|
+
|
|
2392
|
+
```ts
|
|
2393
|
+
const sourceLabel = context.mcp?.clientInfo?.name ?? context.source
|
|
2394
|
+
|
|
2395
|
+
context.mcp?.era // 'modern' | 'legacy'
|
|
2396
|
+
context.mcp?.method // validated MCP method
|
|
2397
|
+
context.mcp?.toolName // resolved framework tool name
|
|
2398
|
+
context.mcp?.protocolVersion // when supplied by a modern host
|
|
2399
|
+
context.mcp?.clientInfo // { name, version } when validated
|
|
2400
|
+
context.mcp?.outcome // multi-round attempt outcome
|
|
2401
|
+
context.mcp?.round // multi-round attempt number
|
|
2402
|
+
```
|
|
2403
|
+
|
|
2404
|
+
The field is absent on HTTP, Agent, CLI and bring-your-own transports. A legacy
|
|
2405
|
+
MCP call exposes `era: 'legacy'` but does not invent modern client information.
|
|
2406
|
+
`clientInfo` is the host's self-description: use it for labels or analytics,
|
|
2407
|
+
never for authentication, RBAC, tenant selection or rate limiting. Verified
|
|
2408
|
+
identity still comes from `auth`; the `context(auth)` callback remains the
|
|
2409
|
+
application-context factory and receives no protocol metadata.
|
|
2410
|
+
|
|
2383
2411
|
### Multi-round tool input (`input_required`)
|
|
2384
2412
|
|
|
2385
2413
|
An MCP-only operation can require typed user input before its final side effect:
|
|
@@ -2598,7 +2626,7 @@ client (Claude Desktop, Claude Code, Cursor, Codex), on the user's machine, so
|
|
|
2598
2626
|
it can reach the local filesystem.
|
|
2599
2627
|
|
|
2600
2628
|
```ts
|
|
2601
|
-
import { createStdioMcpServer } from 'stitchkit/tools'
|
|
2629
|
+
import { bindStdioProcessSignals, createStdioMcpServer } from 'stitchkit/tools'
|
|
2602
2630
|
|
|
2603
2631
|
const stdio = await createStdioMcpServer({
|
|
2604
2632
|
serverInfo: { name: 'my-app', version: '1.0.0' },
|
|
@@ -2606,8 +2634,16 @@ const stdio = await createStdioMcpServer({
|
|
|
2606
2634
|
services: [usersService],
|
|
2607
2635
|
})
|
|
2608
2636
|
|
|
2609
|
-
|
|
2610
|
-
|
|
2637
|
+
const signals = bindStdioProcessSignals(stdio, {
|
|
2638
|
+
onClose: () => stopWorkers(),
|
|
2639
|
+
onComplete: () => { process.exitCode = 0 },
|
|
2640
|
+
onError: (_phase, error) => {
|
|
2641
|
+
console.error(error) // stderr only — stdout is JSON-RPC
|
|
2642
|
+
process.exitCode = 1
|
|
2643
|
+
},
|
|
2644
|
+
})
|
|
2645
|
+
|
|
2646
|
+
await signals.promise
|
|
2611
2647
|
```
|
|
2612
2648
|
|
|
2613
2649
|
A stdio server is a single process serving one client, so `auth` is a value (or
|
|
@@ -2620,6 +2656,14 @@ through the official stdio adapter. Use `legacy: 'reject'` for a modern-only
|
|
|
2620
2656
|
binary. Each invocation builds a fresh server; the returned handle owns
|
|
2621
2657
|
transport shutdown.
|
|
2622
2658
|
|
|
2659
|
+
Signal binding is explicit: `createStdioMcpServer` installs no global process
|
|
2660
|
+
listeners by itself. The first `SIGINT`/`SIGTERM` starts exactly one official
|
|
2661
|
+
`close()` chain. Stdio has no force primitive, so a later signal restores the
|
|
2662
|
+
OS default disposition instead of reporting a fictional forced result.
|
|
2663
|
+
`signals.close()` removes an idle binding; the framework never calls
|
|
2664
|
+
`process.exit()` or chooses the exit code. See
|
|
2665
|
+
[deployment lifecycle](./testing-and-deployment.md#stdio-process-signals).
|
|
2666
|
+
|
|
2623
2667
|
Both transports build the server through the shared `buildMcpServer` — same
|
|
2624
2668
|
contract/runtime pipeline, same surface selection, context, hooks, raw escape
|
|
2625
2669
|
hatch and instructions.
|
|
@@ -2736,7 +2780,8 @@ through the contract's typed client:
|
|
|
2736
2780
|
|
|
2737
2781
|
```ts
|
|
2738
2782
|
import { createHttpClient } from 'stitchkit'
|
|
2739
|
-
import {
|
|
2783
|
+
import { implementRemote } from 'stitchkit/remote'
|
|
2784
|
+
import { createStdioMcpServer } from 'stitchkit/tools'
|
|
2740
2785
|
|
|
2741
2786
|
const http = createHttpClient({
|
|
2742
2787
|
baseUrl: 'https://api.example.com',
|
|
@@ -2943,7 +2988,9 @@ const countRecords = knowledgeTools.define({
|
|
|
2943
2988
|
`defineRuntimeTool` when tools do not share a context schema or identity.
|
|
2944
2989
|
|
|
2945
2990
|
`transports` defaults to `['MCP', 'AGENT']`; set an explicit subset when an
|
|
2946
|
-
operation belongs on only one surface.
|
|
2991
|
+
operation belongs on only one surface. Include `'CLI'` explicitly to reuse the
|
|
2992
|
+
definition through `createCli({ runtimeTools })`; adding CLI support never
|
|
2993
|
+
widens existing definitions silently. The configured identity becomes the
|
|
2947
2994
|
hook/lifecycle `OperationIdentity` and the tool `RequestEvent`
|
|
2948
2995
|
(`serviceName`, `action`, `httpMethod`). A runtime operation has no HTTP route,
|
|
2949
2996
|
so no fake `path` is added.
|
|
@@ -2960,12 +3007,186 @@ The MCP registration uses an identity carrier: the SDK advertises the compiled
|
|
|
2960
3007
|
JSON Schema but forwards the raw object into Stitchkit. Input failures therefore
|
|
2961
3008
|
use the same validation, lifecycle and hook path as contract tools.
|
|
2962
3009
|
|
|
3010
|
+
### Managed wait, download, upload and view-file
|
|
3011
|
+
|
|
3012
|
+
The common imperative shapes have typed definition factories. They produce
|
|
3013
|
+
ordinary runtime-tool definitions, so one value can enter `runtimeTools` on MCP
|
|
3014
|
+
and Agent and receives the same lifecycle, hooks, context, cancellation,
|
|
3015
|
+
collision checks and introspection as any `defineRuntimeTool` operation:
|
|
3016
|
+
|
|
3017
|
+
```ts
|
|
3018
|
+
import {
|
|
3019
|
+
defineDownloadTool,
|
|
3020
|
+
defineUploadTool,
|
|
3021
|
+
defineViewFileTool,
|
|
3022
|
+
defineWaitTool,
|
|
3023
|
+
} from 'stitchkit/tools'
|
|
3024
|
+
import { createManagedFileBoundary } from 'stitchkit/files'
|
|
3025
|
+
import { z } from 'zod'
|
|
3026
|
+
|
|
3027
|
+
const files = await createManagedFileBoundary({ root: '/srv/job-media' })
|
|
3028
|
+
|
|
3029
|
+
const waitForJob = defineWaitTool({
|
|
3030
|
+
name: 'wait_for_job',
|
|
3031
|
+
description: 'Wait for a job to finish',
|
|
3032
|
+
identity: { serviceName: 'jobs', action: 'waitForJob', scope: 'user' },
|
|
3033
|
+
input: z.object({ id: z.string() }),
|
|
3034
|
+
state: z.object({ id: z.string(), status: z.enum(['RUNNING', 'DONE']) }),
|
|
3035
|
+
poll: ({ id }, context) => getJob(id, context.signal),
|
|
3036
|
+
done: (state) => state.status === 'DONE',
|
|
3037
|
+
backoff: [2, 3, 5],
|
|
3038
|
+
defaultTimeout: 120,
|
|
3039
|
+
})
|
|
3040
|
+
|
|
3041
|
+
const downloadResult = defineDownloadTool({
|
|
3042
|
+
name: 'download_result',
|
|
3043
|
+
description: 'Download a completed result',
|
|
3044
|
+
identity: { serviceName: 'jobs', action: 'downloadResult', scope: 'user' },
|
|
3045
|
+
input: z.object({ id: z.string() }),
|
|
3046
|
+
resolveUrl: ({ id }) => resolveResultUrl(id),
|
|
3047
|
+
files,
|
|
3048
|
+
pathFromInput: ({ id }) => `results/${id}.bin`,
|
|
3049
|
+
})
|
|
3050
|
+
|
|
3051
|
+
const uploadInput = defineUploadTool({
|
|
3052
|
+
name: 'upload_input',
|
|
3053
|
+
description: 'Upload a local input file',
|
|
3054
|
+
identity: { serviceName: 'jobs', action: 'uploadInput', scope: 'user' },
|
|
3055
|
+
output: z.object({ url: z.url() }),
|
|
3056
|
+
files,
|
|
3057
|
+
upload: (source, context) => uploadBytes(source.bytes, context.signal),
|
|
3058
|
+
})
|
|
3059
|
+
|
|
3060
|
+
const viewResult = defineViewFileTool({
|
|
3061
|
+
name: 'view_result',
|
|
3062
|
+
description: 'Inspect generated media',
|
|
3063
|
+
identity: { serviceName: 'jobs', action: 'viewResult', scope: 'user' },
|
|
3064
|
+
// Local files are disabled when files is omitted. URL fetches retain SSRF
|
|
3065
|
+
// protection; use allowPrivateHosts only for an explicitly trusted network.
|
|
3066
|
+
files,
|
|
3067
|
+
})
|
|
3068
|
+
|
|
3069
|
+
const runtimeTools = [waitForJob, downloadResult, uploadInput, viewResult]
|
|
3070
|
+
```
|
|
3071
|
+
|
|
3072
|
+
Managed factories return neutral validated data. Expected failures go through
|
|
3073
|
+
the standard failed `ToolResult` path rather than masquerading as a successful
|
|
3074
|
+
payload with `isError`. Wait sleep is abort-aware, guarded downloads receive the
|
|
3075
|
+
active signal, and upload implementations receive it on their typed context.
|
|
3076
|
+
Managed view-file accepts one or several paths/URLs, retains successful
|
|
3077
|
+
multimodal content beside structured per-item `errors`, and charges every item
|
|
3078
|
+
to one total 20 MB batch budget. Its MCP and Agent presenters are built in;
|
|
3079
|
+
remote fetch cancellation follows the active call signal.
|
|
3080
|
+
|
|
3081
|
+
The older `mountWait`, `mountDownload`, `mountUpload` and `mountViewFile`
|
|
3082
|
+
functions remain
|
|
3083
|
+
intentional raw MCP adapters with their text-envelope behavior. Use them under
|
|
3084
|
+
`rawTools` only when opting out of managed policy is deliberate; they share the
|
|
3085
|
+
same neutral mechanics but do not gain lifecycle or hooks. The raw view-file
|
|
3086
|
+
adapter preserves its content-only MCP envelope while using the same bounded
|
|
3087
|
+
batch operation.
|
|
3088
|
+
|
|
3089
|
+
### Async-operation protocol
|
|
3090
|
+
|
|
3091
|
+
For a complete long-running operation, define the linked runtime-only surface
|
|
3092
|
+
once. Stitchkit owns transport names, validation and wait mechanics; the
|
|
3093
|
+
application still owns storage, execution, retries and domain states:
|
|
3094
|
+
|
|
3095
|
+
```ts
|
|
3096
|
+
import {
|
|
3097
|
+
createAsyncOperationSnapshotSchema,
|
|
3098
|
+
defineAsyncOperation,
|
|
3099
|
+
} from 'stitchkit/tools'
|
|
3100
|
+
|
|
3101
|
+
const id = z.object({ id: z.string() })
|
|
3102
|
+
const state = z.object({
|
|
3103
|
+
phase: z.enum(['pending', 'running', 'succeeded']),
|
|
3104
|
+
value: z.string().optional(),
|
|
3105
|
+
})
|
|
3106
|
+
const snapshot = createAsyncOperationSnapshotSchema({
|
|
3107
|
+
failure: z.object({ code: z.string() }),
|
|
3108
|
+
progress: z.object({ current: z.number(), total: z.number() }),
|
|
3109
|
+
})
|
|
3110
|
+
|
|
3111
|
+
const exportOperation = defineAsyncOperation({
|
|
3112
|
+
mode: 'runtime-only',
|
|
3113
|
+
name: 'export',
|
|
3114
|
+
description: 'Export project data',
|
|
3115
|
+
identity: { serviceName: 'exports', action: 'export', scope: 'user' },
|
|
3116
|
+
startInput: z.object({ projectId: z.string() }),
|
|
3117
|
+
id,
|
|
3118
|
+
state,
|
|
3119
|
+
snapshot,
|
|
3120
|
+
start: ({ projectId }) => enqueueExport(projectId),
|
|
3121
|
+
authorize: ({ id }, capability, ctx) => authorizeExport(id, capability, ctx),
|
|
3122
|
+
inspect: ({ id }, ctx) => readExport(id, ctx.signal),
|
|
3123
|
+
classify: (job) => ({ phase: job.phase }),
|
|
3124
|
+
result: {
|
|
3125
|
+
output: z.object({ value: z.string() }),
|
|
3126
|
+
handler: (job) => ({ value: job.value ?? '' }),
|
|
3127
|
+
},
|
|
3128
|
+
})
|
|
3129
|
+
|
|
3130
|
+
const runtimeTools = exportOperation.runtimeTools
|
|
3131
|
+
```
|
|
3132
|
+
|
|
3133
|
+
Every follow-up repeats `authorize`; an opaque id is never authority. Aborting
|
|
3134
|
+
`wait` only stops waiting and never calls optional domain `cancel`. A
|
|
3135
|
+
contract-backed operation uses `bindContractAsyncOperation` with literal methods
|
|
3136
|
+
from a dedicated existing contract, so it creates no second HTTP router or
|
|
3137
|
+
duplicate schemas. Capability keys are narrowed by schema-compatible TypeScript
|
|
3138
|
+
input/output types. At runtime, the binder additionally requires the exact same
|
|
3139
|
+
id and snapshot Zod instances; declare each once and reuse it:
|
|
3140
|
+
|
|
3141
|
+
```ts
|
|
3142
|
+
import { defineContract } from 'stitchkit/contract'
|
|
3143
|
+
import { bindContractAsyncOperation } from 'stitchkit/tools'
|
|
3144
|
+
import { z } from 'zod'
|
|
3145
|
+
|
|
3146
|
+
const operationId = z.object({ id: z.string() })
|
|
3147
|
+
const operationSnapshot = z.object({
|
|
3148
|
+
phase: z.enum(['pending', 'running', 'succeeded', 'failed', 'cancelled']),
|
|
3149
|
+
})
|
|
3150
|
+
|
|
3151
|
+
const operations = defineContract(
|
|
3152
|
+
{ prefix: 'exports' },
|
|
3153
|
+
{
|
|
3154
|
+
start: {
|
|
3155
|
+
method: 'POST', path: '/', desc: 'Start export',
|
|
3156
|
+
input: z.object({ projectId: z.string() }), output: operationId,
|
|
3157
|
+
},
|
|
3158
|
+
status: {
|
|
3159
|
+
method: 'POST', path: '/status', desc: 'Read export status',
|
|
3160
|
+
input: operationId, output: operationSnapshot,
|
|
3161
|
+
},
|
|
3162
|
+
wait: {
|
|
3163
|
+
method: 'POST', path: '/wait', desc: 'Wait for export',
|
|
3164
|
+
input: operationId, output: operationSnapshot,
|
|
3165
|
+
},
|
|
3166
|
+
},
|
|
3167
|
+
)
|
|
3168
|
+
|
|
3169
|
+
const operationHandlers = {
|
|
3170
|
+
start: () => ({ id: 'example' }),
|
|
3171
|
+
status: (): z.output<typeof operationSnapshot> => ({ phase: 'pending' }),
|
|
3172
|
+
wait: (): z.output<typeof operationSnapshot> => ({ phase: 'succeeded' }),
|
|
3173
|
+
}
|
|
3174
|
+
|
|
3175
|
+
const bound = bindContractAsyncOperation({
|
|
3176
|
+
mode: 'contract-backed',
|
|
3177
|
+
contract: operations,
|
|
3178
|
+
capabilities: { start: 'start', status: 'status', wait: 'wait' },
|
|
3179
|
+
handlers: operationHandlers,
|
|
3180
|
+
})
|
|
3181
|
+
```
|
|
3182
|
+
|
|
2963
3183
|
### Explicit raw SDK registration
|
|
2964
3184
|
|
|
2965
3185
|
`rawTools` is deliberately named as an escape hatch. A tool registered there
|
|
2966
3186
|
does **not** receive stitchkit schema policy, lifecycle, per-call context or
|
|
2967
|
-
hooks. The built-in `mountViewFile`
|
|
2968
|
-
|
|
3187
|
+
hooks. The built-in `mountViewFile`, `mountWait`, `mountDownload` and
|
|
3188
|
+
`mountUpload` helpers remain raw for callers that choose that boundary; the
|
|
3189
|
+
media helper fetches with SSRF and path-traversal defenses:
|
|
2969
3190
|
|
|
2970
3191
|
```ts
|
|
2971
3192
|
import { createMcpHandler, mountViewFile } from 'stitchkit/tools'
|
|
@@ -2974,13 +3195,15 @@ const handleMcp = createMcpHandler({
|
|
|
2974
3195
|
serverInfo: { name: 'my-app', version: '1.0.0' },
|
|
2975
3196
|
auth,
|
|
2976
3197
|
services: [service],
|
|
2977
|
-
rawTools: (server) => mountViewFile(server, {
|
|
3198
|
+
rawTools: (server) => mountViewFile(server, { files }),
|
|
2978
3199
|
})
|
|
2979
3200
|
```
|
|
2980
3201
|
|
|
2981
3202
|
Use raw registration only when opting out is intentional. For a protected
|
|
2982
|
-
`view_file`,
|
|
2983
|
-
|
|
3203
|
+
`view_file`, use `defineViewFileTool` in `runtimeTools`; it supplies the
|
|
3204
|
+
canonical schemas, shared batch budget, structured partial failures and both
|
|
3205
|
+
multimodal presenters. `resolveMedia` remains the single-item core for custom
|
|
3206
|
+
operations whose contract intentionally differs.
|
|
2984
3207
|
|
|
2985
3208
|
## Introspecting the complete tool surface
|
|
2986
3209
|
|
|
@@ -3061,11 +3284,13 @@ identical. Write the handler once; it serves every surface.
|
|
|
3061
3284
|
|
|
3062
3285
|
# CLI
|
|
3063
3286
|
|
|
3064
|
-
The same contract that
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3287
|
+
The same contract and managed runtime tools that drive HTTP, MCP and agent
|
|
3288
|
+
surfaces can also drive a command-line program. `createCli` composes their
|
|
3289
|
+
commands with explicitly local binary commands in one router and help tree.
|
|
3290
|
+
Managed commands — `myapp generate "a fox" --wait`,
|
|
3291
|
+
`myapp models list --json | jq …` — run through the same validation, auth gate
|
|
3292
|
+
and error model as every other managed surface (HTTP ≡ MCP ≡ agent ≡ CLI,
|
|
3293
|
+
[ADR 0014](../decisions/0014-tool-http-parity.md)).
|
|
3069
3294
|
|
|
3070
3295
|
It exists for what the other three surfaces cannot do: a generation kicked off
|
|
3071
3296
|
with `Bash(run_in_background)` that notifies on exit, a `SKILL.md` that shells
|
|
@@ -3086,8 +3311,10 @@ the CLI never silently turns your existing API tools into shell commands.
|
|
|
3086
3311
|
```
|
|
3087
3312
|
|
|
3088
3313
|
A fresh contract shows **zero** CLI commands until methods opt in — that is the
|
|
3089
|
-
design, not a bug.
|
|
3090
|
-
|
|
3314
|
+
design, not a bug. A pathless runtime definition follows the same rule: it must
|
|
3315
|
+
explicitly include `'CLI'` in `transports`; the undefined default remains
|
|
3316
|
+
`['MCP', 'AGENT']`. The command name is the tool name — `toolName` if set, else
|
|
3317
|
+
a verb-aware name from the method + prefix (`list` → `list_widgets`, `get` →
|
|
3091
3318
|
`get_widget`), not a literal `prefix_key`.
|
|
3092
3319
|
|
|
3093
3320
|
## A minimal CLI
|
|
@@ -3105,10 +3332,16 @@ await createCli({
|
|
|
3105
3332
|
name: 'myapp',
|
|
3106
3333
|
version: '1.0.0',
|
|
3107
3334
|
services: [catalogService, generateService],
|
|
3108
|
-
|
|
3335
|
+
resolveAuth: () => resolveToken(process.env.MYAPP_TOKEN), // lazy, at most once
|
|
3109
3336
|
})
|
|
3110
3337
|
```
|
|
3111
3338
|
|
|
3339
|
+
`createCli({ signal })` and its wait loop honor an explicit caller
|
|
3340
|
+
`AbortSignal`. Stitchkit does not install a process-global SIGINT handler for
|
|
3341
|
+
ordinary CLI programs: if desired, the application binds SIGINT to an
|
|
3342
|
+
`AbortController` and passes its signal. Aborting wait stops polling only; it
|
|
3343
|
+
does not cancel the underlying job.
|
|
3344
|
+
|
|
3112
3345
|
```json
|
|
3113
3346
|
// package.json
|
|
3114
3347
|
{ "bin": { "myapp": "./dist/cli.js" } }
|
|
@@ -3117,6 +3350,81 @@ await createCli({
|
|
|
3117
3350
|
`stitchkit/cli` pulls in neither the MCP SDK nor `ai`, so a CLI binary needs no
|
|
3118
3351
|
MCP/agent peer dependencies.
|
|
3119
3352
|
|
|
3353
|
+
## Pathless managed commands
|
|
3354
|
+
|
|
3355
|
+
Use `runtimeTools` when an application operation has no HTTP path but must keep
|
|
3356
|
+
the canonical operation identity, context, lifecycle/RBAC, hooks and
|
|
3357
|
+
introspection. It can share one definition with MCP and Agent while opting into
|
|
3358
|
+
CLI explicitly:
|
|
3359
|
+
|
|
3360
|
+
```ts
|
|
3361
|
+
import { createCli } from 'stitchkit/cli'
|
|
3362
|
+
import { defineUploadTool } from 'stitchkit/tools'
|
|
3363
|
+
import { z } from 'zod'
|
|
3364
|
+
|
|
3365
|
+
const uploadInput = defineUploadTool({
|
|
3366
|
+
name: 'upload_input',
|
|
3367
|
+
description: 'Upload one local input file',
|
|
3368
|
+
identity: { serviceName: 'jobs', action: 'uploadInput', scope: 'user' },
|
|
3369
|
+
output: z.object({ url: z.url() }),
|
|
3370
|
+
transports: ['MCP', 'AGENT', 'CLI'],
|
|
3371
|
+
upload: (path, context) => uploadFile(path, context.signal),
|
|
3372
|
+
})
|
|
3373
|
+
|
|
3374
|
+
await createCli({
|
|
3375
|
+
name: 'myapp',
|
|
3376
|
+
version: '1.0.0',
|
|
3377
|
+
runtimeTools: [uploadInput],
|
|
3378
|
+
})
|
|
3379
|
+
```
|
|
3380
|
+
|
|
3381
|
+
`services` and `runtimeTools` may each be static arrays or factories receiving
|
|
3382
|
+
the resolved identity. A runtime-only CLI is valid; `stitchkit/cli` still pulls
|
|
3383
|
+
in neither MCP nor AI peers. Contract/runtime name collisions and reserved
|
|
3384
|
+
option fields fail through the same checks before managed dispatch.
|
|
3385
|
+
|
|
3386
|
+
## Native binary commands
|
|
3387
|
+
|
|
3388
|
+
Login, self-update, diagnostics, integration setup and shell completion belong
|
|
3389
|
+
to the executable, not to HTTP/MCP/Agent. Define them with `defineCliCommand`:
|
|
3390
|
+
|
|
3391
|
+
```ts
|
|
3392
|
+
import { createCli, defineCliCommand } from 'stitchkit/cli'
|
|
3393
|
+
import { z } from 'zod'
|
|
3394
|
+
|
|
3395
|
+
const login = defineCliCommand({
|
|
3396
|
+
name: 'login',
|
|
3397
|
+
description: 'Store credentials for later managed commands',
|
|
3398
|
+
input: z.object({ token: z.string() }),
|
|
3399
|
+
output: z.object({ configured: z.boolean() }),
|
|
3400
|
+
handler: async ({ input }) => {
|
|
3401
|
+
await saveToken(input.token)
|
|
3402
|
+
return { configured: true }
|
|
3403
|
+
},
|
|
3404
|
+
})
|
|
3405
|
+
|
|
3406
|
+
await createCli({
|
|
3407
|
+
name: 'myapp',
|
|
3408
|
+
version: '1.0.0',
|
|
3409
|
+
commands: [login],
|
|
3410
|
+
resolveAuth: loadStoredIdentity,
|
|
3411
|
+
services: (identity) => createRemoteServices(identity),
|
|
3412
|
+
})
|
|
3413
|
+
```
|
|
3414
|
+
|
|
3415
|
+
Native commands receive only typed `input`, parsed global `options` and the
|
|
3416
|
+
configured stdout/stderr writers. They reuse help, argv/stdin parsing,
|
|
3417
|
+
validation, dry-run, error envelopes and exit mapping, but deliberately have no
|
|
3418
|
+
fake service/action/scope/method identity, lifecycle or tool hooks and never
|
|
3419
|
+
appear in MCP/Agent manifests.
|
|
3420
|
+
|
|
3421
|
+
`--version`, a selected native command and its command help run before
|
|
3422
|
+
`resolveAuth`, services, context or runtime-tool factories. Top-level help is
|
|
3423
|
+
also credential-free when managed surfaces are static. A dynamic factory must
|
|
3424
|
+
resolve identity to discover its command names; its collisions are checked at
|
|
3425
|
+
that resolution boundary. If eager global collision proof matters, keep the
|
|
3426
|
+
surface static.
|
|
3427
|
+
|
|
3120
3428
|
## Calling commands
|
|
3121
3429
|
|
|
3122
3430
|
```
|
|
@@ -3147,7 +3455,7 @@ same Zod schema an HTTP or MCP call does.
|
|
|
3147
3455
|
|
|
3148
3456
|
| Flag | Effect |
|
|
3149
3457
|
| --------------------- | ---------------------------------------------------------- |
|
|
3150
|
-
| `--json` |
|
|
3458
|
+
| `--json` | Compact success/error JSON records for scripts |
|
|
3151
3459
|
| `--wait` | Block-poll an async result to a terminal state |
|
|
3152
3460
|
| `--wait-timeout <s>` | Override the `--wait` timeout |
|
|
3153
3461
|
| `--output-dir <dir>` | Download result media into a directory |
|
|
@@ -3155,10 +3463,18 @@ same Zod schema an HTTP or MCP call does.
|
|
|
3155
3463
|
| `--dry-run` | Print the resolved call without executing |
|
|
3156
3464
|
| `--help`, `-h` | Usage — top-level or per-command flag table |
|
|
3157
3465
|
|
|
3158
|
-
stdout carries the result; errors and progress go to stderr
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3466
|
+
stdout carries the result; structured errors and progress go to stderr. With
|
|
3467
|
+
`--json`, a success or structured failure is exactly one compact,
|
|
3468
|
+
newline-terminated JSON record on its respective stream; progress and CLI usage
|
|
3469
|
+
diagnostics remain ordinary stderr text. This keeps stdout pipeable and
|
|
3470
|
+
`2>/dev/null` clean. The process exit code carries the error class (`0` ok,
|
|
3471
|
+
`VALIDATION_ERROR → 1`, `UNAUTHORIZED → 2`, `FORBIDDEN → 3`, `NOT_FOUND → 4`,
|
|
3472
|
+
…) — override per app with `exitCodes`.
|
|
3473
|
+
|
|
3474
|
+
Per-command help derives the positional form from the same schema order as the
|
|
3475
|
+
argv parser. For example, a required `action` and optional `profile` render as
|
|
3476
|
+
`Usage: myapp skill <action> [profile] [--flags]`; the argument table also shows
|
|
3477
|
+
`<action> | --action` and `[profile] | --profile`. Boolean fields remain flags.
|
|
3162
3478
|
|
|
3163
3479
|
## `--wait` — background-friendly generation
|
|
3164
3480
|
|
|
@@ -3175,12 +3491,19 @@ await createCli({
|
|
|
3175
3491
|
generate: {
|
|
3176
3492
|
tool: 'get_generation',
|
|
3177
3493
|
poll: (r) => (isRecord(r) && typeof r.id === 'string' ? { id: r.id } : null),
|
|
3178
|
-
done: (r) => isRecord(r) &&
|
|
3494
|
+
done: (r) => isRecord(r) && ['COMPLETED', 'FAILED'].includes(String(r.status)),
|
|
3495
|
+
failed: (r) => isRecord(r) && r.status === 'FAILED',
|
|
3179
3496
|
},
|
|
3180
3497
|
},
|
|
3181
3498
|
})
|
|
3182
3499
|
```
|
|
3183
3500
|
|
|
3501
|
+
`failed` is optional. When it matches either the initial result or a later poll,
|
|
3502
|
+
polling stops and the CLI emits `WAIT_FAILED` on stderr with a non-zero exit;
|
|
3503
|
+
the terminal payload is retained under `details.result`. `failed` is checked
|
|
3504
|
+
before `done`, so overlapping predicates fail closed. A failed poll tool call
|
|
3505
|
+
keeps its own error code, and an elapsed deadline remains `TIMEOUT`.
|
|
3506
|
+
|
|
3184
3507
|
```bash
|
|
3185
3508
|
# foreground
|
|
3186
3509
|
myapp generate "a fox" --wait --output-dir ./out
|
|
@@ -3201,7 +3524,7 @@ const authHook = createAuthHook({ /* resolve, resolveFromContext, rules */ })
|
|
|
3201
3524
|
await createCli({
|
|
3202
3525
|
name: 'myapp',
|
|
3203
3526
|
version: '1.0.0',
|
|
3204
|
-
|
|
3527
|
+
resolveAuth: () => resolveIdentityFromToken(process.env.MYAPP_TOKEN),
|
|
3205
3528
|
context: (identity) => ({ user: identity }), // resolveFromContext reads this
|
|
3206
3529
|
lifecycle: { beforeHandle: authHook }, // same policy; HTTP wires it as authorize
|
|
3207
3530
|
services,
|
|
@@ -3233,11 +3556,12 @@ await tools.createCli({
|
|
|
3233
3556
|
})
|
|
3234
3557
|
```
|
|
3235
3558
|
|
|
3236
|
-
##
|
|
3559
|
+
## Remaining boundary
|
|
3237
3560
|
|
|
3238
|
-
File-upload (`multipart`) endpoints
|
|
3239
|
-
|
|
3240
|
-
|
|
3561
|
+
File-upload (`multipart`) contract endpoints remain CLI-invisible, the same as
|
|
3562
|
+
on MCP/Agent: their wire body is not a JSON tool form. Model file-oriented
|
|
3563
|
+
application behavior as a managed pathless command, or binary-only behavior as
|
|
3564
|
+
a native command. Streaming (SSE) output is not yet piped to stdout.
|
|
3241
3565
|
|
|
3242
3566
|
|
|
3243
3567
|
==============================================================================
|
|
@@ -3285,6 +3609,57 @@ runtime. A malformed inbound tuple never reaches the application handler;
|
|
|
3285
3609
|
invalid outbound data throws before Socket.IO publishes it. Rejections call the
|
|
3286
3610
|
optional `onRejected` hook with event, direction, phase and the Zod error.
|
|
3287
3611
|
|
|
3612
|
+
### Protocol generations
|
|
3613
|
+
|
|
3614
|
+
For a distributed producer/consumer pair, put a literal generation first in the
|
|
3615
|
+
first payload object. Zod validates object fields in declaration order, so an
|
|
3616
|
+
incompatible peer is identified before the rest of its payload is interpreted:
|
|
3617
|
+
|
|
3618
|
+
```ts
|
|
3619
|
+
const ReplicationMessage = z.object({
|
|
3620
|
+
v: z.literal(2),
|
|
3621
|
+
item: ReplicatedItem,
|
|
3622
|
+
})
|
|
3623
|
+
|
|
3624
|
+
const realtimeContract = defineRealtimeContract({
|
|
3625
|
+
serverToClient: {
|
|
3626
|
+
replicated: { args: z.tuple([ReplicationMessage]) },
|
|
3627
|
+
},
|
|
3628
|
+
clientToServer: {},
|
|
3629
|
+
})
|
|
3630
|
+
```
|
|
3631
|
+
|
|
3632
|
+
The original `z.ZodError` is retained as `RealtimeRejectedEvent.error.cause`.
|
|
3633
|
+
Because event arguments are a tuple, the first payload's `v` path is
|
|
3634
|
+
`[0, 'v']` (not `['v']`):
|
|
3635
|
+
|
|
3636
|
+
```ts
|
|
3637
|
+
import type { RealtimeRejectedEvent } from 'stitchkit'
|
|
3638
|
+
import { z } from 'zod'
|
|
3639
|
+
|
|
3640
|
+
function isProtocolGenerationMismatch(rejected: RealtimeRejectedEvent): boolean {
|
|
3641
|
+
const cause = rejected.error.cause
|
|
3642
|
+
if (!(cause instanceof z.ZodError)) return false
|
|
3643
|
+
const first = cause.issues[0]
|
|
3644
|
+
return first?.code === 'invalid_value'
|
|
3645
|
+
&& first.path.length === 2
|
|
3646
|
+
&& first.path[0] === 0
|
|
3647
|
+
&& first.path[1] === 'v'
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
const socket = createRealtimeClient(realtimeContract, {
|
|
3651
|
+
url,
|
|
3652
|
+
onRejected: (rejected) => {
|
|
3653
|
+
if (isProtocolGenerationMismatch(rejected)) schedulePeerUpgrade()
|
|
3654
|
+
else reportMalformedRealtimePayload(rejected)
|
|
3655
|
+
},
|
|
3656
|
+
})
|
|
3657
|
+
```
|
|
3658
|
+
|
|
3659
|
+
A generation mismatch means “upgrade the peer”; another schema rejection means
|
|
3660
|
+
“fix the producer or payload”. This stays an application convention, not a core
|
|
3661
|
+
API: only the application knows which field denotes protocol compatibility.
|
|
3662
|
+
|
|
3288
3663
|
## Server — `createSocketIOServer`
|
|
3289
3664
|
|
|
3290
3665
|
```ts
|
|
@@ -3383,6 +3758,35 @@ socket.on('note:created', (note) => { /* typed note */ })
|
|
|
3383
3758
|
socket.emit('room:join', 'general', ({ joined }) => { /* typed + validated */ })
|
|
3384
3759
|
```
|
|
3385
3760
|
|
|
3761
|
+
### Request-response over realtime
|
|
3762
|
+
|
|
3763
|
+
For an event with an `ack` schema, `request()` is the Promise form of the same
|
|
3764
|
+
native Socket.IO acknowledgement. Arguments and the resolved value are inferred
|
|
3765
|
+
from the contract and validated on both sides:
|
|
3766
|
+
|
|
3767
|
+
```ts
|
|
3768
|
+
const result = await socket.request(
|
|
3769
|
+
'room:join',
|
|
3770
|
+
'general',
|
|
3771
|
+
{ timeoutMs: 5_000 },
|
|
3772
|
+
)
|
|
3773
|
+
// result: { joined: boolean }
|
|
3774
|
+
```
|
|
3775
|
+
|
|
3776
|
+
An event without `ack` cannot be passed to `request()` by type. A disconnected
|
|
3777
|
+
client rejects immediately with `RealtimeRequestDisconnectedError`; an in-flight
|
|
3778
|
+
disconnect uses the same error; an elapsed native acknowledgement timeout uses
|
|
3779
|
+
`RealtimeRequestTimeoutError`. These are distinct stable classes and codes, so
|
|
3780
|
+
never parse Socket.IO error text. An invalid acknowledgement still fires the
|
|
3781
|
+
existing `onRejected` hook with phase `acknowledgement`, then rejects with
|
|
3782
|
+
`RealtimeRequestInvalidAcknowledgementError`.
|
|
3783
|
+
|
|
3784
|
+
`timeoutMs` must be finite and greater than zero. Use acknowledgements only for
|
|
3785
|
+
bounded request-response work. A job that runs for minutes, progress streaming,
|
|
3786
|
+
or resumable delivery should use separate correlated events or the async
|
|
3787
|
+
operation protocol; keeping one acknowledgement open is not a durable RPC/job
|
|
3788
|
+
transport.
|
|
3789
|
+
|
|
3386
3790
|
## Low-level transport
|
|
3387
3791
|
|
|
3388
3792
|
`createSocketIOClient` remains the low-level Socket.IO transport wrapper for
|
|
@@ -3867,10 +4271,11 @@ The value of each `rules` entry, keyed by scope:
|
|
|
3867
4271
|
|
|
3868
4272
|
- **`'public'`** — always passes; the identity is attached if present.
|
|
3869
4273
|
- **`'authenticated'`** — any resolved identity passes; no identity ⇒ 401.
|
|
3870
|
-
- **a function** `(identity, ctx) => boolean |
|
|
4274
|
+
- **a function** `(identity, ctx) => boolean | contribution`, sync or async — a custom
|
|
3871
4275
|
check. It receives request metadata and validated path params, so a
|
|
3872
4276
|
resource-scoped rule can do a DB lookup. It cannot read `input` or files: the
|
|
3873
|
-
body has deliberately not been consumed yet.
|
|
4277
|
+
body has deliberately not been consumed yet. `false` denies, `true` passes,
|
|
4278
|
+
and a returned plain object passes and contributes inferred handler fields.
|
|
3874
4279
|
|
|
3875
4280
|
#### Resource-scoped rule — reading a path/prefix param
|
|
3876
4281
|
|
|
@@ -3900,6 +4305,30 @@ identity *and* any derived values (role, parent-id) on `ctx` for handlers. Read
|
|
|
3900
4305
|
them back as `ctx.tenantRole` (typed `unknown` — narrow at the read site). The
|
|
3901
4306
|
endpoints under the group declare `scope: 'tenant'`.
|
|
3902
4307
|
|
|
4308
|
+
When the async authorization lookup already produced handler data, return it
|
|
4309
|
+
instead of repeating the lookup in `inject` and a handwritten scope map:
|
|
4310
|
+
|
|
4311
|
+
```ts
|
|
4312
|
+
const auth = createAuthHook({
|
|
4313
|
+
resolve: sessionResolver,
|
|
4314
|
+
rules: {
|
|
4315
|
+
project: async (user, ctx) => {
|
|
4316
|
+
const membership = await findMembership(user.id, String(ctx.projectId))
|
|
4317
|
+
return membership
|
|
4318
|
+
? { userId: user.id, projectId: membership.projectId, role: membership.role }
|
|
4319
|
+
: false
|
|
4320
|
+
},
|
|
4321
|
+
},
|
|
4322
|
+
})
|
|
4323
|
+
|
|
4324
|
+
const implementFor = createScopedImplement<AuthScopes<typeof auth>>()
|
|
4325
|
+
```
|
|
4326
|
+
|
|
4327
|
+
Contributions are merged only after full validation. Runtime-owned context keys,
|
|
4328
|
+
arrays, class instances, accessors, symbols, unsafe prototypes and partial proxy
|
|
4329
|
+
reads fail without mutating context. `false | object` contributes required
|
|
4330
|
+
fields; `true | object` correctly makes them optional.
|
|
4331
|
+
|
|
3903
4332
|
### `AuthHookConfig`
|
|
3904
4333
|
|
|
3905
4334
|
| Field | Purpose |
|
|
@@ -4767,6 +5196,25 @@ observe. (This is also why it lives on `ToolCallHooks` rather than being an
|
|
|
4767
5196
|
`Response`, which a tool call has no use for, and a whole `createServer` hooks
|
|
4768
5197
|
object must stay assignable to `ToolLifecycle`.)
|
|
4769
5198
|
|
|
5199
|
+
### Compose lifecycle policy explicitly
|
|
5200
|
+
|
|
5201
|
+
When auth, a feature policy and audit identity are independent modules, compose
|
|
5202
|
+
their existing hooks instead of forwarding each phase by hand:
|
|
5203
|
+
|
|
5204
|
+
```ts
|
|
5205
|
+
import { composeLifecycleHooks } from 'stitchkit/server'
|
|
5206
|
+
import { composeToolLifecycle } from 'stitchkit/tools'
|
|
5207
|
+
|
|
5208
|
+
const httpLifecycle = composeLifecycleHooks(authLifecycle, featurePolicy, auditIdentity)
|
|
5209
|
+
const toolLifecycle = composeToolLifecycle(authHook, featureToolPolicy, auditToolIdentity)
|
|
5210
|
+
```
|
|
5211
|
+
|
|
5212
|
+
Phases run left to right. `onRequest` stops on the first `Response`; `onError`
|
|
5213
|
+
falls through until a hook handles it; after transforms feed the current value
|
|
5214
|
+
forward and preserve it when a hook returns `undefined`. Throws remain owned by
|
|
5215
|
+
the existing outer error boundary, and the original context/signal identity is
|
|
5216
|
+
never replaced.
|
|
5217
|
+
|
|
4770
5218
|
**Do not reach for `setRequestError` here.** It writes to the *request* context,
|
|
4771
5219
|
which the built-in **tool** row does not read: a tool event takes
|
|
4772
5220
|
`errorCode` / `errorMessage` / `errorDetail` from the `ToolResult`, and only
|
|
@@ -4882,6 +5330,38 @@ contract registry. Both helpers are Fetch-only: they construct ordinary
|
|
|
4882
5330
|
absolute `Request` objects and call `createHandler` directly, so Bun and Node
|
|
4883
5331
|
exercise the same framework pipeline.
|
|
4884
5332
|
|
|
5333
|
+
### Transport conformance
|
|
5334
|
+
|
|
5335
|
+
`buildSurfaceManifest` snapshots actual HTTP topology, MCP/Agent/CLI names,
|
|
5336
|
+
CLI-only commands, extensions and canonical schema digests. Compare real runner
|
|
5337
|
+
discovery with `assertSurfaceDiscovery`, then use `runSurfaceProbes` only for
|
|
5338
|
+
the transports you explicitly provide:
|
|
5339
|
+
|
|
5340
|
+
```ts
|
|
5341
|
+
import {
|
|
5342
|
+
assertSurfaceDiscovery,
|
|
5343
|
+
buildSurfaceManifest,
|
|
5344
|
+
runSurfaceProbes,
|
|
5345
|
+
} from 'stitchkit/testing'
|
|
5346
|
+
|
|
5347
|
+
const manifest = buildSurfaceManifest({ services, runtimeTools, cliCommands })
|
|
5348
|
+
assertSurfaceDiscovery(manifest, {
|
|
5349
|
+
openApi,
|
|
5350
|
+
MCP: (await mcpClient.listTools()).tools.map((tool) => tool.name),
|
|
5351
|
+
AGENT: Object.keys(agentTools),
|
|
5352
|
+
CLI: cliHelpNames,
|
|
5353
|
+
})
|
|
5354
|
+
|
|
5355
|
+
await runSurfaceProbes({
|
|
5356
|
+
probes,
|
|
5357
|
+
drivers: { HTTP: httpDriver, MCP: mcpDriver, AGENT: agentDriver, CLI: cliDriver },
|
|
5358
|
+
})
|
|
5359
|
+
```
|
|
5360
|
+
|
|
5361
|
+
The kit never starts a server or invents credentials. Each probe declares its
|
|
5362
|
+
expected normalized outcome and has bounded timeout, setup/teardown and an
|
|
5363
|
+
AbortSignal. A missing driver is unsupported, not silently marked conformant.
|
|
5364
|
+
|
|
4885
5365
|
### Test handlers with raw Requests
|
|
4886
5366
|
|
|
4887
5367
|
`createHandler` is the router as a plain `(req) => Promise<Response>` function —
|
|
@@ -5079,6 +5559,38 @@ domain run-state remain application resources and close explicitly after server
|
|
|
5079
5559
|
drain. Do not call `runtime.stop()` or `socket.io.close()` in parallel with
|
|
5080
5560
|
`shutdown()`.
|
|
5081
5561
|
|
|
5562
|
+
### Stdio process signals
|
|
5563
|
+
|
|
5564
|
+
An MCP stdio handle has `close()`, not managed HTTP `shutdown()` with a force
|
|
5565
|
+
signal and deadline. Bind its process lifecycle with the truthful close-only
|
|
5566
|
+
sibling from `stitchkit/tools`:
|
|
5567
|
+
|
|
5568
|
+
```ts
|
|
5569
|
+
import {
|
|
5570
|
+
bindStdioProcessSignals,
|
|
5571
|
+
createStdioMcpServer,
|
|
5572
|
+
} from 'stitchkit/tools'
|
|
5573
|
+
|
|
5574
|
+
const stdio = await createStdioMcpServer(config)
|
|
5575
|
+
const binding = bindStdioProcessSignals(stdio, {
|
|
5576
|
+
onClose: () => stopWorkers(),
|
|
5577
|
+
onComplete: () => { process.exitCode = 0 },
|
|
5578
|
+
onError: (phase, error) => {
|
|
5579
|
+
console.error(phase, error) // stderr; stdout stays JSON-RPC-only
|
|
5580
|
+
process.exitCode = 1
|
|
5581
|
+
},
|
|
5582
|
+
})
|
|
5583
|
+
|
|
5584
|
+
await binding.promise
|
|
5585
|
+
```
|
|
5586
|
+
|
|
5587
|
+
The first signal starts exactly one close chain. A same-turn duplicate is
|
|
5588
|
+
ignored; a later signal restores the default OS disposition because the
|
|
5589
|
+
official stdio close is not abortable and Stitchkit will not pretend otherwise.
|
|
5590
|
+
`binding.close()` removes idle listeners and resolves the promise with
|
|
5591
|
+
`undefined`. No listener is installed until the binder is called, and the
|
|
5592
|
+
framework never calls `process.exit()`.
|
|
5593
|
+
|
|
5082
5594
|
### Deploy on Node
|
|
5083
5595
|
|
|
5084
5596
|
The contract, `implement`, hooks, auth and the client are runtime-agnostic. Only
|
|
@@ -5409,6 +5921,45 @@ current one *up to* your target, and apply each snippet.
|
|
|
5409
5921
|
runtime): bootstrap the server, one HTTP request, and any feature you rely on
|
|
5410
5922
|
(Socket.IO connect, an MCP tool call, a multipart upload, …).
|
|
5411
5923
|
|
|
5924
|
+
## Unreleased migration: peer-free `implementRemote`
|
|
5925
|
+
|
|
5926
|
+
`implementRemote` now has one canonical, optional-peer-free owner. This keeps
|
|
5927
|
+
MCP SDK and AI SDK modules out of CLI bundles that only proxy HTTP calls:
|
|
5928
|
+
|
|
5929
|
+
```ts
|
|
5930
|
+
// before
|
|
5931
|
+
import { implementRemote } from 'stitchkit/tools'
|
|
5932
|
+
// after
|
|
5933
|
+
import { implementRemote } from 'stitchkit/remote'
|
|
5934
|
+
```
|
|
5935
|
+
|
|
5936
|
+
## Unreleased migration: managed file boundary and strict auth returns
|
|
5937
|
+
|
|
5938
|
+
Create one boundary during application bootstrap and pass the capability, never
|
|
5939
|
+
a per-call directory or host path:
|
|
5940
|
+
|
|
5941
|
+
```ts
|
|
5942
|
+
import { createManagedFileBoundary } from 'stitchkit/files'
|
|
5943
|
+
|
|
5944
|
+
const files = await createManagedFileBoundary({ root: '/srv/app-files' })
|
|
5945
|
+
|
|
5946
|
+
// before
|
|
5947
|
+
defineDownloadTool({ defaultDir: '/srv/app-files', resolveUrl, ...common })
|
|
5948
|
+
defineUploadTool({ upload: (path) => provider.uploadFile(path), ...common })
|
|
5949
|
+
defineViewFileTool({ baseDir: '/srv/app-files', ...common })
|
|
5950
|
+
|
|
5951
|
+
// after
|
|
5952
|
+
defineDownloadTool({ files, resolveUrl, ...common })
|
|
5953
|
+
defineUploadTool({ files, upload: ({ bytes }) => provider.upload(bytes), ...common })
|
|
5954
|
+
defineViewFileTool({ files, ...common })
|
|
5955
|
+
```
|
|
5956
|
+
|
|
5957
|
+
Downloaded `path` is now relative to the boundary and MIME metadata is named
|
|
5958
|
+
`mediaType`. Update raw `mountDownload`/`mountUpload`/`mountViewFile` configs the
|
|
5959
|
+
same way. Auth predicates must explicitly return `true`, `false`, or a plain
|
|
5960
|
+
object of context fields; replace accidental `undefined` fallthroughs with the
|
|
5961
|
+
intended boolean.
|
|
5962
|
+
|
|
5412
5963
|
## Released migration: 0.53.0
|
|
5413
5964
|
|
|
5414
5965
|
### Realtime `emit` returns `boolean` instead of `void`
|
|
@@ -6378,11 +6929,18 @@ The browser-and-server entrypoint. Re-exports everything from
|
|
|
6378
6929
|
| `createRealtimeClient` | function | inferred, runtime-validated Socket.IO client — [guide](../guide/realtime.md#client--createrealtimeclient) |
|
|
6379
6930
|
| `createRetainedTopics` | function | retained last-value store for sticky events — [guide](../guide/realtime.md#sticky-events) |
|
|
6380
6931
|
| `parseSSE` | function | parse an SSE `Response` into an async generator — [guide](../guide/client.md#sse) |
|
|
6381
|
-
| `SocketIOClient` | _type_ |
|
|
6932
|
+
| `SocketIOClient` | _type_ | low-level client handle; `emit` reports disconnected drops and `emitWithAck` exposes the native Promise primitive used by validated `request()` |
|
|
6382
6933
|
| `SocketIOClientConfig` | _type_ | config for `createSocketIOClient` (incl. `retain`, `onConnectError`, `onDroppedEmit`) |
|
|
6383
6934
|
| `SocketEventMap` | _type_ | the shape of an event map |
|
|
6384
6935
|
| `RealtimeClient` | _type_ | validated client inferred from a realtime contract |
|
|
6385
6936
|
| `RealtimeClientOptions` | _type_ | transport options and the rejected-event hook for `createRealtimeClient` |
|
|
6937
|
+
| `RealtimeAcknowledgedEvent` | _type_ | event-name union restricted to definitions with an `ack` schema |
|
|
6938
|
+
| `RealtimeAcknowledgement` | _type_ | validated acknowledgement output inferred from an event definition |
|
|
6939
|
+
| `RealtimeRequestArguments` | _type_ | request arguments inferred from an acknowledged event tuple |
|
|
6940
|
+
| `RealtimeRequestOptions` | _type_ | finite positive native acknowledgement `timeoutMs` |
|
|
6941
|
+
| `RealtimeRequestTimeoutError` | class | stable `REALTIME_REQUEST_TIMEOUT` rejection |
|
|
6942
|
+
| `RealtimeRequestDisconnectedError` | class | stable `REALTIME_REQUEST_DISCONNECTED` rejection, including an immediate disconnected call |
|
|
6943
|
+
| `RealtimeRequestInvalidAcknowledgementError` | class | invalid ack was reported through `onRejected` and the request rejected |
|
|
6386
6944
|
| `RealtimeContract` | _type_ | shared server-to-client and client-to-server event registries |
|
|
6387
6945
|
| `RealtimeEventRegistry` | _type_ | string-keyed registry of event definitions |
|
|
6388
6946
|
| `RealtimeEventDefinition` | _type_ | one tuple-shaped event and optional acknowledgement schema |
|
|
@@ -6443,8 +7001,11 @@ from the root `stitchkit`.
|
|
|
6443
7001
|
| `HttpMethod` | _type_ | `GET \| HEAD \| POST \| PUT \| PATCH \| DELETE` |
|
|
6444
7002
|
| `Transport` | _type_ | `HTTP \| MCP \| AGENT \| CLI` |
|
|
6445
7003
|
| `TransportSource` | _type_ | `http \| mcp \| agent \| cli` — the value of `ctx.source` |
|
|
6446
|
-
| `RuntimeContext` | _type_ | the loose context seen by transport and hooks |
|
|
6447
|
-
| `HandlerContext` | _type_ | the typed context seen by a handler |
|
|
7004
|
+
| `RuntimeContext` | _type_ | the loose context seen by transport and hooks, including optional typed `mcp` metadata |
|
|
7005
|
+
| `HandlerContext` | _type_ | the typed context seen by a handler, including optional typed `mcp` metadata |
|
|
7006
|
+
| `McpCallContext` | _type_ | validated metadata for the active managed MCP call (`era`, method, tool, client and multi-round fields) |
|
|
7007
|
+
| `McpClientInfo` | _type_ | self-reported MCP host name/version; attribution only, never application identity |
|
|
7008
|
+
| `McpRoundOutcome` | _type_ | managed multi-round attempt outcome |
|
|
6448
7009
|
| `EndpointHandlerContext` | _type_ | one endpoint handler's fully inferred params, input, files and runtime context |
|
|
6449
7010
|
| `EndpointFn` | _type_ | the call signature of one client method |
|
|
6450
7011
|
| `TypedClient` | _type_ | the full typed client for a contract |
|
|
@@ -6560,6 +7121,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
6560
7121
|
| `OperationIdentity` | _type_ | path-free service/action/scope/method identity shared by contract and native tool operations |
|
|
6561
7122
|
| `Handlers` | _type_ | the typed handler map `implement` expects |
|
|
6562
7123
|
| `LifecycleHooks` | _type_ | `onRequest` / pre-body `authorize` / `beforeHandle` / `afterHandle` / `onError` |
|
|
7124
|
+
| `composeLifecycleHooks` | function | compose HTTP lifecycle phases in declaration order with short-circuit/fallthrough semantics |
|
|
6563
7125
|
| `AuthorizationContext` | _type_ | HTTP pre-body context with validated params, `input: undefined` and no files |
|
|
6564
7126
|
| `RouteGroup` | _type_ | a prefixed group of services with its own hooks |
|
|
6565
7127
|
| `RawRoute` | _type_ | a non-contract `Request → Response` route with a concrete `BunServer` context |
|
|
@@ -6591,6 +7153,7 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
6591
7153
|
| `AuthHook` | _type_ | the hook `createAuthHook` returns |
|
|
6592
7154
|
| `AuthHookConfig` | _type_ | config for `createAuthHook` |
|
|
6593
7155
|
| `AuthRule` | _type_ | `'public' \| 'authenticated' \| predicate` |
|
|
7156
|
+
| `AuthRuleContribution` | _type_ | plain context fields a sync/async rule may contribute after authorizing |
|
|
6594
7157
|
| `ScopedAuthRule` | _type_ | a rule plus its typed context contribution — `{ rule, inject? }` |
|
|
6595
7158
|
| `AuthRules` | _type_ | the `rules` map: bare rules or scoped rules |
|
|
6596
7159
|
| `RuleScopes` | _type_ | scope→context map derived from a `rules` object; `'public'` fields become optional |
|
|
@@ -6759,6 +7322,18 @@ audit event. See the [Observability guide](../guide/observability.md).
|
|
|
6759
7322
|
|
|
6760
7323
|
---
|
|
6761
7324
|
|
|
7325
|
+
## `stitchkit/remote`
|
|
7326
|
+
|
|
7327
|
+
Peer-free remote implementation boundary. Importing it does not load the MCP
|
|
7328
|
+
SDK, `ai`, or other optional tool peers, so it is safe in a thin CLI bundle.
|
|
7329
|
+
|
|
7330
|
+
| Export | Kind | Summary |
|
|
7331
|
+
|--------|------|---------|
|
|
7332
|
+
| `implementRemote` | function | bind a contract to a remote HTTP API — [guide](../guide/mcp-and-agents.md#proxying-a-remote-api--implementremote) |
|
|
7333
|
+
| `ImplementRemoteOptions` | _type_ | optional argument-rewrite hook for `implementRemote` |
|
|
7334
|
+
|
|
7335
|
+
---
|
|
7336
|
+
|
|
6762
7337
|
## `stitchkit/tools`
|
|
6763
7338
|
|
|
6764
7339
|
Server-only. Turns contracts into MCP and AI-agent tools. MCP server surfaces
|
|
@@ -6775,14 +7350,15 @@ payload.
|
|
|
6775
7350
|
| `createMcpHandler` | function | a stateless dual-era Streamable-HTTP MCP handler — [guide](../guide/mcp-and-agents.md#mcp--createmcphandler) |
|
|
6776
7351
|
| `createMcpHttpRoute` | function | framework-owned `RawRoute` adapter for an MCP HTTP handler |
|
|
6777
7352
|
| `createStdioMcpServer` | function | a complete stdio MCP server — [guide](../guide/mcp-and-agents.md#mcp-over-stdio--createstdiomcpserver) |
|
|
7353
|
+
| `bindStdioProcessSignals` | function | explicitly bind OS signals to one close-only stdio handle — [guide](../guide/testing-and-deployment.md#stdio-process-signals) |
|
|
6778
7354
|
| `buildMcpServer` | function | build an `McpServer` from contract/runtime surfaces; no-auth configs omit the second argument |
|
|
6779
7355
|
| `mountMcp` | function | add contract tools to an existing `McpServer` — [guide](../guide/mcp-and-agents.md#mountmcp) |
|
|
6780
|
-
| `implementRemote` | function | bind a contract to a remote HTTP API — [guide](../guide/mcp-and-agents.md#proxying-a-remote-api--implementremote) |
|
|
6781
7356
|
| `mountAgent` | function | a Vercel AI SDK `ToolSet` from a service — [guide](../guide/mcp-and-agents.md#ai-agents--mountagent) |
|
|
6782
|
-
| `defineRuntimeTool` | function | define one validated pathless operation for MCP, Agent or
|
|
7357
|
+
| `defineRuntimeTool` | function | define one validated pathless operation for explicit MCP, Agent and/or CLI surfaces — [guide](../guide/mcp-and-agents.md#pathless-runtime-tools-and-multimodal-results) |
|
|
6783
7358
|
| `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) |
|
|
6784
7359
|
| `createToolInvoker` | function | compile an exposure-aware in-process dispatcher over the canonical tool runner — [guide](../guide/mcp-and-agents.md#in-process-calls--createtoolinvoker) |
|
|
6785
7360
|
| `createCli` | function | a command-line program from contracts — [guide](../guide/cli.md) (also on `stitchkit/cli`) |
|
|
7361
|
+
| `defineCliCommand` | function | define one typed CLI-only command with no fake managed-tool identity — [guide](../guide/cli.md#native-binary-commands) (also on `stitchkit/cli`) |
|
|
6786
7362
|
| `createToolkit` | function | context-typed tool mounts — [guide](../guide/cli.md#typed-context) |
|
|
6787
7363
|
| `mountViewFile` | function | a native multimodal "view file" MCP tool |
|
|
6788
7364
|
| `resolveMedia` | function | resolve a media reference for a tool result |
|
|
@@ -6796,6 +7372,10 @@ payload.
|
|
|
6796
7372
|
| `McpLegacyPolicy` | _type_ | `'serve' \| 'reject'` protocol-era compatibility policy |
|
|
6797
7373
|
| `McpStdioHandle` | _type_ | closeable official stdio transport handle |
|
|
6798
7374
|
| `StdioMcpServerConfig` | _type_ | config for `createStdioMcpServer` |
|
|
7375
|
+
| `StdioCloseTarget` | _type_ | minimal close-only target accepted by `bindStdioProcessSignals` |
|
|
7376
|
+
| `StdioProcessSignalsOptions` | _type_ | signal source, close callbacks and phased error reporting |
|
|
7377
|
+
| `StdioProcessSignalsBinding` | _type_ | close-only signal binding with observed `promise` and listener `close()` |
|
|
7378
|
+
| `StdioProcessSignalsErrorPhase` | _type_ | `'prepare' \| 'close' \| 'complete'` reporting phase |
|
|
6799
7379
|
| `McpServerBuildConfig` | _type_ | shared config for `buildMcpServer` |
|
|
6800
7380
|
| `McpServerSharedConfig` | _type_ | transport-neutral options shared by direct and finite surface configs |
|
|
6801
7381
|
| `McpServer` | _type_ | official split-SDK server instance accepted by raw extension points |
|
|
@@ -6804,7 +7384,6 @@ payload.
|
|
|
6804
7384
|
| `McpSurfaceDefinition` | _type_ | one immutable `{ services, runtimeTools }` MCP surface |
|
|
6805
7385
|
| `McpSurfaceRegistry` | _type_ | finite keyed surface registry for eager preparation |
|
|
6806
7386
|
| `StdioAuthConfig` | _type_ | startup identity composed into `StdioMcpServerConfig` |
|
|
6807
|
-
| `ImplementRemoteOptions` | _type_ | options for `implementRemote` |
|
|
6808
7387
|
| `McpMountConfig` | _type_ | config for `mountMcp` |
|
|
6809
7388
|
| `McpSchemaValidationConfig` | _type_ | shared `{ policy, requireTypedProperties, allowUntyped, requirePortableFormats, allowFormats }` profile |
|
|
6810
7389
|
| `ValidateMcpSchemasConfig` | _type_ | standalone validation profile plus `services`, `extend`, flattening and logger |
|
|
@@ -6833,10 +7412,16 @@ payload.
|
|
|
6833
7412
|
| `CimdCacheEvent` | _type_ | observable CIMD cache hit, miss, revalidation and eviction event |
|
|
6834
7413
|
| `createSecureClientMetadataFetcher` | function | production HTTPS, DNS/IP-pinned CIMD fetcher |
|
|
6835
7414
|
| `RuntimeAgentModelOutput` | _type_ | AI SDK model-facing text/JSON/content output returned by `present.agent` |
|
|
6836
|
-
| `RuntimeToolTransport` | _type_ | runtime exposure: `'MCP' \| 'AGENT'
|
|
7415
|
+
| `RuntimeToolTransport` | _type_ | runtime exposure: `'MCP' \| 'AGENT' \| 'CLI'`; omission still means MCP+Agent only |
|
|
6837
7416
|
| `AgentMountConfig` | _type_ | config for `mountAgent` |
|
|
6838
7417
|
| `AgentContext` | _type_ | the context merged into agent tool handlers |
|
|
6839
7418
|
| `CliConfig` | _type_ | config for `createCli` |
|
|
7419
|
+
| `CliSurfaceSource` | _type_ | static managed surface or identity-dependent surface factory for `createCli` |
|
|
7420
|
+
| `CliCommandDefinition` | _type_ | Zod-first CLI-only command union |
|
|
7421
|
+
| `CliCommandDefinitionBase` | _type_ | native command name, description and input schema |
|
|
7422
|
+
| `CliCommandDefinitionWithOutput` | _type_ | native command with declared output schema and validated handler result |
|
|
7423
|
+
| `CliCommandDefinitionWithoutOutput` | _type_ | void native command with no output schema |
|
|
7424
|
+
| `CliCommandContext` | _type_ | parsed native command input, global options and injected writers |
|
|
6840
7425
|
| `CliWaitConfig` | _type_ | `--wait` polling config |
|
|
6841
7426
|
| `ExitCodeMap` | _type_ | `ToolResult.code` → process exit code |
|
|
6842
7427
|
| `Toolkit` | _type_ | the context-pinned tool surface from `createToolkit` |
|
|
@@ -6853,9 +7438,15 @@ payload.
|
|
|
6853
7438
|
| `ToolInvokerConfig` | _type_ | compile-time exposure, extension and presentation options |
|
|
6854
7439
|
| `ToolInvocationOptions` | _type_ | per-call source, context, lifecycle, hooks and output-strip reporter |
|
|
6855
7440
|
| `ToolInvokerTransport` | _type_ | invoker exposure policy: `MCP \| AGENT \| CLI` |
|
|
6856
|
-
| `ToolCallContext` | _type_ | the context every tool hook receives — `{ source }` plus whatever the mount's `context` added |
|
|
6857
|
-
| `ViewFileOptions` | _type_ |
|
|
7441
|
+
| `ToolCallContext` | _type_ | the context every tool hook receives — `{ source, mcp? }` plus whatever the mount's `context` added |
|
|
7442
|
+
| `ViewFileOptions` | _type_ | shared URL/managed-file-boundary policy for `defineViewFileTool`, `mountViewFile` and `resolveMedia` |
|
|
7443
|
+
| `ViewFileOutput` | _type_ | neutral managed batch result with multimodal `content` and per-item `errors` |
|
|
7444
|
+
| `ViewFileInputSchema` | constant | fixed one-or-many media path/URL input schema |
|
|
7445
|
+
| `ViewFileOutputSchema` | constant | Zod schema for the neutral managed view-file batch result |
|
|
7446
|
+
| `ViewFileErrorSchema` | constant | Zod schema for one structured per-item view failure |
|
|
6858
7447
|
| `McpAnnotations` | _type_ | MCP annotations on a media result |
|
|
7448
|
+
| `McpAnnotationsSchema` | constant | Zod schema for MCP media annotations |
|
|
7449
|
+
| `McpMediaContentSchema` | constant | Zod schema for text/image/audio media content |
|
|
6859
7450
|
| `CollectToolsConfig` | _type_ | options for `collectTools` |
|
|
6860
7451
|
| `findUntypedProperties` | function | every property in a JSON Schema with no `type`/`enum`/`$ref` — what a model is shown and cannot obey ([guide](../guide/mcp-and-agents.md)) |
|
|
6861
7452
|
| `UntypedProperty` | _type_ | one such property — `{ path, description? }` |
|
|
@@ -6868,13 +7459,44 @@ payload.
|
|
|
6868
7459
|
|
|
6869
7460
|
### Native tools
|
|
6870
7461
|
|
|
6871
|
-
Generic host-supplied
|
|
7462
|
+
Generic host-supplied operations — managed definitions for the canonical
|
|
7463
|
+
runtime-tool runner, plus deliberate raw MCP adapters over the same mechanics.
|
|
6872
7464
|
|
|
6873
7465
|
| Export | Kind | Summary |
|
|
6874
7466
|
|--------|------|---------|
|
|
6875
|
-
| `
|
|
6876
|
-
| `
|
|
6877
|
-
| `
|
|
7467
|
+
| `defineDownloadTool` | function | define a guarded, size-capped managed download for MCP/Agent `runtimeTools` |
|
|
7468
|
+
| `defineUploadTool` | function | define a typed managed local-file upload for MCP/Agent `runtimeTools` |
|
|
7469
|
+
| `defineWaitTool` | function | define a typed, cancellable managed polling operation for MCP/Agent `runtimeTools` |
|
|
7470
|
+
| `defineViewFileTool` | function | define a guarded, batch-capped managed multimodal operation for MCP/Agent `runtimeTools` |
|
|
7471
|
+
| `DefineDownloadToolConfig` | _type_ | Zod input, stable identity, URL resolver, storage policy and presenters for `defineDownloadTool` |
|
|
7472
|
+
| `DefineUploadToolConfig` | _type_ | stable identity, typed output, upload callback and presenters for `defineUploadTool` |
|
|
7473
|
+
| `DefineWaitToolConfig` | _type_ | Zod input/state, identity, poll/done/timeout policy and presenters for `defineWaitTool` |
|
|
7474
|
+
| `DefineViewFileToolConfig` | _type_ | identity, media security policy and optional presenters for `defineViewFileTool` |
|
|
7475
|
+
| `ManagedNativeToolConfig` | _type_ | shared name, description, identity, exposure and annotations for managed native factories |
|
|
7476
|
+
| `NativeToolIdentity` | _type_ | pathless service/action/scope/meta identity; semantic method is factory-owned |
|
|
7477
|
+
| `ManagedWaitRender` | _type_ | optional managed wait terminal text and failure classification |
|
|
7478
|
+
| `UploadToolInputSchema` | constant | fixed `{ path: string }` input schema for `defineUploadTool` |
|
|
7479
|
+
| `defineAsyncOperation` | function | runtime-only start/status/wait plus configured cancel/result/artifacts definitions |
|
|
7480
|
+
| `bindContractAsyncOperation` | function | bind literal methods from an existing contract without creating another HTTP surface |
|
|
7481
|
+
| `createAsyncOperationSnapshotSchema` | function | canonical pending/running/succeeded/failed/cancelled Zod snapshot |
|
|
7482
|
+
| `AsyncOperationCancelResultSchema` | constant | validated accepted/already_terminal/rejected cancellation result |
|
|
7483
|
+
| `AsyncOperationCancelResult` | _type_ | validated cancel capability result |
|
|
7484
|
+
| `AsyncOperationCancelCapability` | _type_ | optional typed domain cancellation callback |
|
|
7485
|
+
| `AsyncOperationCapability` | _type_ | generated capability-name union |
|
|
7486
|
+
| `AsyncOperationStartDefinition` / `AsyncOperationFollowDefinition` | _type_ | generated runtime definition types |
|
|
7487
|
+
| `AsyncOperationIdentity` | _type_ | shared service/action/scope/meta identity for an operation |
|
|
7488
|
+
| `AsyncOperationOutputCapability` | _type_ | optional result/artifact schema plus handler |
|
|
7489
|
+
| `RuntimeAsyncOperationConfig` | _type_ | runtime-only descriptor configuration |
|
|
7490
|
+
| `RuntimeAsyncOperation` | _type_ | inferred generated definitions and schemas |
|
|
7491
|
+
| `ContractAsyncOperationConfig` | _type_ | literal contract method binding and handlers |
|
|
7492
|
+
| `ContractAsyncOperationKeys` | _type_ | literal method-key union of a bound contract |
|
|
7493
|
+
| `ContractAsyncOperationStartKey` | _type_ | contract keys with a declared output schema, valid for `start` |
|
|
7494
|
+
| `ContractAsyncOperationFollowKey` | _type_ | contract keys whose input schema type matches the selected start output |
|
|
7495
|
+
| `ContractAsyncOperationWaitKey` | _type_ | follow-up keys whose output schema type also matches the selected status output |
|
|
7496
|
+
| `composeToolLifecycle` | function | ordered composition of tool before/after phases |
|
|
7497
|
+
| `mountDownload` | function | raw MCP "download a URL to disk" adapter (SSRF-guarded, size-capped) |
|
|
7498
|
+
| `mountUpload` | function | raw MCP "upload a local file" adapter |
|
|
7499
|
+
| `mountWait` | function | raw MCP generic `--wait`-style polling adapter |
|
|
6878
7500
|
| `DownloadToolConfig` | _type_ | config for `mountDownload` |
|
|
6879
7501
|
| `UploadToolConfig` | _type_ | config for `mountUpload` |
|
|
6880
7502
|
| `WaitToolConfig` | _type_ | config for `mountWait` |
|
|
@@ -6951,6 +7573,43 @@ handler pipeline without opening a TCP port.
|
|
|
6951
7573
|
| `HandlerTestClientConfig` | _type_ | handler, contract, path prefix, scoped config and client request defaults |
|
|
6952
7574
|
| `HandlerTestClientsConfig` | _type_ | batch helper configuration |
|
|
6953
7575
|
| `HandlerTestTransportConfig` | _type_ | shared in-process handler, origin, prefix, client defaults and optional server handle |
|
|
7576
|
+
| `buildSurfaceManifest` | function | deterministic actual HTTP/tool/CLI/extension topology with versioned schema digests |
|
|
7577
|
+
| `assertSurfaceManifestSnapshot` | function | bounded deterministic manifest drift assertion |
|
|
7578
|
+
| `assertSurfaceDiscovery` | function | compare real OpenAPI/MCP/Agent/CLI discovery to a manifest |
|
|
7579
|
+
| `runSurfaceProbes` | function | execute explicit transport drivers with per-probe setup, teardown, timeout and cancellation |
|
|
7580
|
+
| `TransportObservationSchema` | constant | normalized success/validation/domain-error/aborted probe outcome |
|
|
7581
|
+
| `ConformanceTransport` | _type_ | explicit probe transport union |
|
|
7582
|
+
| `RunSurfaceProbesConfig` | _type_ | probes, drivers and diagnostic byte cap |
|
|
7583
|
+
| `SurfaceDiscoveryObservation` | _type_ | real OpenAPI/tool/CLI/extension discovery values |
|
|
7584
|
+
| `SurfaceProbe` / `SurfaceProbeDriver` | _type_ | one bounded scenario and its consumer-supplied runner |
|
|
7585
|
+
| `TransportObservation` | _type_ | validated normalized driver result |
|
|
7586
|
+
| `SurfaceManifest` / `SurfaceManifestConfig` | _type_ | deterministic surface snapshot and its inputs |
|
|
7587
|
+
| `SurfaceManifestOperation` / `SurfaceManifestOperationSchema` | _type_ / schema | one contract or runtime operation row |
|
|
7588
|
+
| `SurfaceManifestExtension` / `SurfaceManifestExtensionSchema` | _type_ / schema | declared transport extension row |
|
|
7589
|
+
| `SurfaceManifestSchema` | schema | complete deterministic manifest schema |
|
|
7590
|
+
| `SurfaceRuntimeToolDefinition` | _type_ | peer-free structural runtime-operation subset accepted by manifests |
|
|
7591
|
+
| `SurfaceSchemaDigestsSchema` | schema | params/input/output/multipart digest object |
|
|
7592
|
+
| `serializeSurfaceValue` | function | canonical versioned serialization used for deterministic digests |
|
|
7593
|
+
|
|
7594
|
+
---
|
|
7595
|
+
|
|
7596
|
+
## `stitchkit/files`
|
|
7597
|
+
|
|
7598
|
+
Peer-free Bun/Node filesystem capability. Browser/contract-safe refs are also
|
|
7599
|
+
available from `stitchkit/contract`.
|
|
7600
|
+
|
|
7601
|
+
| Export | Kind | Summary |
|
|
7602
|
+
|--------|------|---------|
|
|
7603
|
+
| `createManagedFileBoundary` | function | bind one existing application-owned root for capped reads and atomic writes |
|
|
7604
|
+
| `ManagedFileBoundary` | _type_ | non-reopenable `read`/`write` capability over canonical relative paths |
|
|
7605
|
+
| `ManagedFileBoundaryConfig` | _type_ | bound root, finite limits, inspector and cleanup observer |
|
|
7606
|
+
| `ManagedFileRefSchema` / `ManagedFileRef` | schema / _type_ | transport-safe relative path, measured size and optional media metadata |
|
|
7607
|
+
| `ManagedFilePathSchema` / `ManagedFilePath` | schema / _type_ | canonical POSIX relative managed-file path |
|
|
7608
|
+
| `ManagedFileSource` | _type_ | bounded immutable bytes plus validated ref passed to upload callbacks |
|
|
7609
|
+
| `ManagedFileReadOptions` / `ManagedFileWriteOptions` | _type_ | per-operation byte cap, signal and atomic write policy |
|
|
7610
|
+
| `ManagedFileError` / `ManagedFileErrorCode` | class / _type_ | stable caller-safe boundary failures |
|
|
7611
|
+
| `ManagedFileInspector` | _type_ | bounded-prefix content inspection callback that cannot own path or size |
|
|
7612
|
+
| `ManagedFileInspectionInput` / `ManagedFileInspection` | _type_ | inspector input and normalized metadata-only result |
|
|
6954
7613
|
|
|
6955
7614
|
---
|
|
6956
7615
|
|
|
@@ -6979,20 +7638,28 @@ runtime-agnostic pieces of `stitchkit/server` and the error helpers.
|
|
|
6979
7638
|
|
|
6980
7639
|
## `stitchkit/cli`
|
|
6981
7640
|
|
|
6982
|
-
Server-only.
|
|
6983
|
-
Light by design: needs neither the MCP
|
|
7641
|
+
Server-only. Composes contract/runtime managed operations and CLI-only native
|
|
7642
|
+
commands into one command-line program. Light by design: needs neither the MCP
|
|
7643
|
+
SDK nor the `ai` peer.
|
|
6984
7644
|
|
|
6985
7645
|
| Export | Kind | Summary |
|
|
6986
7646
|
|--------|------|---------|
|
|
6987
7647
|
| `createCli` | function | build and run a CLI from contracts — [guide](../guide/cli.md) |
|
|
7648
|
+
| `defineCliCommand` | function | define one Zod-typed CLI-only executable command |
|
|
6988
7649
|
| `parseCliArgs` | function | argv → typed tool args against a schema (advanced) |
|
|
6989
7650
|
| `pollUntilDone` | function | the generic `--wait` poller (advanced) |
|
|
6990
|
-
| `emitResult` | function | write a `ToolResult` to stdout/stderr + exit code (advanced) |
|
|
7651
|
+
| `emitResult` | function | write a pretty or compact `ToolResult` record to stdout/stderr + exit code (advanced) |
|
|
6991
7652
|
| `DEFAULT_EXIT_CODES` | const | the default `ToolResult.code` → exit-code map |
|
|
6992
7653
|
| `CliConfig` | _type_ | config for `createCli` |
|
|
6993
|
-
| `
|
|
7654
|
+
| `CliSurfaceSource` | _type_ | static service/runtime array or identity-dependent factory |
|
|
7655
|
+
| `CliCommandDefinition` | _type_ | native command definition union |
|
|
7656
|
+
| `CliCommandDefinitionBase` | _type_ | native command name, description and input schema |
|
|
7657
|
+
| `CliCommandDefinitionWithOutput` | _type_ | native command with validated declared output |
|
|
7658
|
+
| `CliCommandDefinitionWithoutOutput` | _type_ | native void command without an output contract |
|
|
7659
|
+
| `CliCommandContext` | _type_ | parsed input, global options and stdout/stderr writers |
|
|
7660
|
+
| `CliRunOptions` | _type_ | parsed global flags (`--json` compacts success/error records, `--wait`, …) |
|
|
6994
7661
|
| `ParsedCliArgs` | _type_ | result of `parseCliArgs` |
|
|
6995
|
-
| `CliWaitConfig` | _type_ | per-command `--wait` polling config |
|
|
7662
|
+
| `CliWaitConfig` | _type_ | per-command `--wait` polling config; optional `failed(result)` maps a terminal domain failure to `WAIT_FAILED` and a non-zero exit |
|
|
6996
7663
|
| `ExitCodeMap` | _type_ | `ToolResult.code` → process exit code |
|
|
6997
7664
|
| `PollParams` | _type_ | params for `pollUntilDone` |
|
|
6998
7665
|
| `CliWriters` | _type_ | stdout/stderr sinks for `emitResult` |
|