stitchkit 0.36.1 → 0.38.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 -2
- package/dist/browser/client-url.d.ts +11 -0
- package/dist/browser/client-url.d.ts.map +1 -0
- package/dist/browser/client.d.ts +16 -4
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/http.d.ts +7 -3
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/contract/define.d.ts +102 -2
- package/dist/contract/define.d.ts.map +1 -1
- 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 +1 -1
- package/dist/{index-bx49hskg.js → index-bgdd42pt.js} +1 -1
- package/dist/{index-0ed3bx43.js → index-frfyw9fa.js} +2 -2
- package/dist/{index-82gncajj.js → index-n5t4gnfz.js} +557 -337
- package/dist/{index-dvrn81q4.js → index-p3kwf73n.js} +165 -55
- package/dist/{index-pmftwk2a.js → index-x4wbc8sz.js} +54 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +147 -114
- package/dist/node.d.ts +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +7 -3
- package/dist/observability/index.js +3 -3
- package/dist/server/bun.d.ts +32 -0
- package/dist/server/bun.d.ts.map +1 -0
- package/dist/server/context.d.ts +1 -1
- package/dist/server/context.d.ts.map +1 -1
- package/dist/server/create.d.ts +2 -3
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/implement.d.ts.map +1 -1
- package/dist/server/index.d.ts +3 -2
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +41 -7
- package/dist/server/logger.d.ts +5 -5
- package/dist/server/logger.d.ts.map +1 -1
- package/dist/server/logging.d.ts +4 -1
- package/dist/server/logging.d.ts.map +1 -1
- package/dist/server/middleware/auth.d.ts +2 -2
- package/dist/server/middleware/auth.d.ts.map +1 -1
- package/dist/server/openapi.d.ts.map +1 -1
- package/dist/server/request-body.d.ts +4 -0
- package/dist/server/request-body.d.ts.map +1 -0
- package/dist/server/response-metadata.d.ts +9 -0
- package/dist/server/response-metadata.d.ts.map +1 -0
- package/dist/server/router.d.ts +3 -3
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/socket-io-config.d.ts +20 -0
- package/dist/server/socket-io-config.d.ts.map +1 -0
- package/dist/server/socket-io-node.d.ts +14 -0
- package/dist/server/socket-io-node.d.ts.map +1 -0
- package/dist/server/socket-io.d.ts +5 -32
- package/dist/server/socket-io.d.ts.map +1 -1
- package/dist/server/types.d.ts +63 -50
- package/dist/server/types.d.ts.map +1 -1
- package/dist/tools/agent.d.ts.map +1 -1
- package/dist/tools/execute.d.ts +46 -16
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/flatten-join.d.ts +8 -0
- package/dist/tools/flatten-join.d.ts.map +1 -0
- package/dist/tools/flatten.d.ts +7 -44
- package/dist/tools/flatten.d.ts.map +1 -1
- package/dist/tools/json-schema.d.ts +1 -1
- package/dist/tools/json-schema.d.ts.map +1 -1
- package/dist/tools/manifest.d.ts.map +1 -1
- package/dist/tools/mcp-handler.d.ts +7 -13
- package/dist/tools/mcp-handler.d.ts.map +1 -1
- package/dist/tools/mcp.d.ts +61 -24
- package/dist/tools/mcp.d.ts.map +1 -1
- package/dist/tools/mount.d.ts +9 -6
- package/dist/tools/mount.d.ts.map +1 -1
- package/dist/tools/names.d.ts +1 -1
- package/dist/tools/native-mcp.d.ts +57 -0
- package/dist/tools/native-mcp.d.ts.map +1 -0
- package/dist/tools/portable-formats.d.ts +15 -0
- package/dist/tools/portable-formats.d.ts.map +1 -0
- package/dist/tools/presentation.d.ts +15 -0
- package/dist/tools/presentation.d.ts.map +1 -0
- package/dist/tools/remote.d.ts.map +1 -1
- package/dist/tools/schema.d.ts +3 -8
- package/dist/tools/schema.d.ts.map +1 -1
- package/dist/tools/tool-logger.d.ts.map +1 -1
- package/dist/tools/view-file.d.ts +5 -3
- package/dist/tools/view-file.d.ts.map +1 -1
- package/dist/tools.d.ts +7 -4
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +481 -205
- package/llms-full.txt +536 -87
- package/package.json +14 -11
- /package/dist/{index-17rdjw68.js → index-mzx0an0s.js} +0 -0
package/llms-full.txt
CHANGED
|
@@ -230,7 +230,7 @@ export const users = defineContract({ prefix: 'users' }, {
|
|
|
230
230
|
| Field | Required | Purpose |
|
|
231
231
|
|-------|----------|---------|
|
|
232
232
|
| `method` | yes | `GET` · `POST` · `PUT` · `PATCH` · `DELETE` |
|
|
233
|
-
| `path` | yes | route path under the contract `prefix`; `:name` marks a path param |
|
|
233
|
+
| `path` | yes | route path under the contract `prefix`; `:name` marks a path param and a terminal `/*` captures the remaining path |
|
|
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) |
|
|
@@ -239,10 +239,13 @@ export const users = defineContract({ prefix: 'users' }, {
|
|
|
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`) |
|
|
241
241
|
| `multipart` | no | field name of a file upload — see [below](#file-uploads) |
|
|
242
|
+
| `maxJsonBodyBytes` | no | per-route JSON body ceiling; overrides the server default |
|
|
242
243
|
| `timeout` | no | per-endpoint client timeout in ms, for slow endpoints |
|
|
243
244
|
| `idempotent` | no | safe to call twice with the same input (like `PUT`/`DELETE`); a retrying transport reads it — see [Realtime](./realtime.md#bring-your-own-transport) |
|
|
244
245
|
| `meta` | no | opaque app metadata — read in hooks / on tool mounts, never in OpenAPI ([below](#endpoint-metadata-meta)) |
|
|
245
246
|
| `rawResponse` | no | the handler returns the `Response` itself — a download, a file, an SSE stream. HTTP-only, never a tool, no `output`. See [Raw-response endpoints](./server.md#raw-response-endpoints) |
|
|
247
|
+
| `rawBody` | no | retain original JSON text for a signed HTTP webhook. See [Signed JSON webhooks](./server.md#signed-json-webhooks) |
|
|
248
|
+
| `responseMeta` | no | make a typed-data endpoint HTTP-only, optionally declare its success status and expose `ctx.response.headers` — [Typed JSON response metadata](./server.md#typed-json-response-metadata) |
|
|
246
249
|
| `contentType` | no | documented response media type of a `rawResponse` endpoint (OpenAPI only) |
|
|
247
250
|
|
|
248
251
|
## `params` vs `input` vs `output`
|
|
@@ -251,7 +254,10 @@ The three schemas are distinct on purpose:
|
|
|
251
254
|
|
|
252
255
|
- **`params`** — values in the URL path. `path: '/:id'` ⇒
|
|
253
256
|
`params: z.object({ id: z.string() })`. The client takes them from the call
|
|
254
|
-
argument and substitutes them into the URL.
|
|
257
|
+
argument and substitutes them into the URL. A terminal wildcard is the
|
|
258
|
+
quoted `'*'` field: `path: '/:slug/*'` with
|
|
259
|
+
`params: z.object({ slug: z.string(), '*': z.string() })` matches both
|
|
260
|
+
`/foo/page` and `/foo/a/b`; the handler receives `'page'` or `'a/b'`.
|
|
255
261
|
- **`input`** — the request payload. For `POST` / `PUT` / `PATCH` it is the JSON
|
|
256
262
|
body; for `GET` / `DELETE` it is the query string. The handler reads it as
|
|
257
263
|
`ctx.input`.
|
|
@@ -265,6 +271,9 @@ the body.
|
|
|
265
271
|
```ts
|
|
266
272
|
// path: '/:id', params: { id }, input: { text }
|
|
267
273
|
await api.update({ id: '1', text: 'new' }) // PUT /users/1 body: { text: 'new' }
|
|
274
|
+
|
|
275
|
+
// path: '/:slug/*', params: { slug, '*': remainder }
|
|
276
|
+
await api.app({ slug: 'foo', '*': 'a/b' }) // GET /apps/foo/a/b
|
|
268
277
|
```
|
|
269
278
|
|
|
270
279
|
### Input vs. output types
|
|
@@ -315,10 +324,12 @@ tools. Narrow it with `expose`:
|
|
|
315
324
|
- `expose: ['MCP', 'AGENT']` — a tool only; no HTTP route.
|
|
316
325
|
- omit `expose` — all transports.
|
|
317
326
|
|
|
318
|
-
Tool transports (`MCP`, `AGENT`) skip
|
|
327
|
+
Tool transports (`MCP`, `AGENT`) skip three kinds of endpoint automatically:
|
|
319
328
|
`multipart` (a file upload is not a tool call) and
|
|
320
329
|
[`rawResponse`](./server.md#raw-response-endpoints) (its answer is bytes, which
|
|
321
|
-
a tool result cannot carry — it would serialize to `{}`)
|
|
330
|
+
a tool result cannot carry — it would serialize to `{}`), plus
|
|
331
|
+
[`responseMeta`](./server.md#typed-json-response-metadata) (outbound HTTP
|
|
332
|
+
headers have no meaning on a tool call).
|
|
322
333
|
|
|
323
334
|
## `toolName`
|
|
324
335
|
|
|
@@ -617,6 +628,7 @@ server. See [Testing & deployment](./testing-and-deployment.md).
|
|
|
617
628
|
| `scopePrefixes` | `scope → path prefix` map — mount `services` by `service.scope` (see below) |
|
|
618
629
|
| `rawRoutes` | non-contract routes (see below) |
|
|
619
630
|
| `maxUploadBytes` | default multipart upload cap (bytes); per-route `EndpointDef.maxUploadBytes` overrides |
|
|
631
|
+
| `maxJsonBodyBytes` | optional JSON body cap (bytes); per-route value overrides; unset preserves existing behaviour |
|
|
620
632
|
| `port` / `hostname` | listen address — port defaults to `3000` |
|
|
621
633
|
| `cors` | CORS policy — `{ origin, credentials, methods, headers, exposeHeaders }` |
|
|
622
634
|
| `hooks` | lifecycle hooks (see below) |
|
|
@@ -682,10 +694,18 @@ is not involved.
|
|
|
682
694
|
way to see what `enrich` and the request context actually put on the record;
|
|
683
695
|
changing `NODE_ENV` is not needed, and neither is deploying.
|
|
684
696
|
|
|
685
|
-
Three more things about `enrich`:
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
697
|
+
Three more things about `enrich`:
|
|
698
|
+
|
|
699
|
+
- It runs at close, when the request body is already consumed.
|
|
700
|
+
- The framework owns `traceId`, `method`, `path`, `status`, `durationMs`,
|
|
701
|
+
`errorCode` and `ip` in both structured sinks; the built-in JSON line also
|
|
702
|
+
owns `ts`, `level` and `msg`. Its value wins a collision, and a discarded key
|
|
703
|
+
warns once per handler rather than disappearing silently.
|
|
704
|
+
- `errorCode` has one outcome-aware exception: enrichment may supply it for a
|
|
705
|
+
`4xx`/`5xx` response when the framework derived no code, such as an error
|
|
706
|
+
`Response` returned by a raw route. It cannot add one to a `2xx`/`3xx` or
|
|
707
|
+
replace a framework-derived code.
|
|
708
|
+
- A throw in `skip` or `enrich` is swallowed; neither can fail a request.
|
|
689
709
|
|
|
690
710
|
With an observability context active, the structured line also carries `userId`,
|
|
691
711
|
`serviceName`, `action` and `dimensions` for free. See
|
|
@@ -753,6 +773,17 @@ prefix param in the schema, use a non-strict `z.object` (extra keys are dropped
|
|
|
753
773
|
from `ctx.params`, but `ctx.tenantId` still works), or read the param off the
|
|
754
774
|
context root.
|
|
755
775
|
|
|
776
|
+
**Trailing wildcard.** A contract path may end in `/*`. `/app/:slug/*` matches
|
|
777
|
+
both `/app/foo` and nested paths such as `/app/foo/a/b`; the collected params are
|
|
778
|
+
`{ slug: 'foo', '*': '' }` and `{ slug: 'foo', '*': 'a/b' }` respectively. Put
|
|
779
|
+
the quoted `'*'` field in the endpoint's `params` schema to keep it in typed
|
|
780
|
+
`ctx.params`. Each captured segment is URL-decoded before the remainder is
|
|
781
|
+
joined, so encoded spaces and reserved characters reach the handler as their
|
|
782
|
+
semantic values while `/` remains the segment boundary. Static and named-param
|
|
783
|
+
routes are matched before a catch-all, so a
|
|
784
|
+
more specific endpoint wins regardless of declaration order. The same matcher
|
|
785
|
+
drives `405 Allow` resolution.
|
|
786
|
+
|
|
756
787
|
### Scope-driven mounting (`scopePrefixes`)
|
|
757
788
|
|
|
758
789
|
With several scopes, hand-partitioning services into `groups` duplicates the
|
|
@@ -785,7 +816,7 @@ createServer({
|
|
|
785
816
|
hooks: {
|
|
786
817
|
onRequest(req) { /* logging, global rate limit — may return a Response to short-circuit */ },
|
|
787
818
|
beforeHandle(ctx, endpoint) { /* auth, scope checks — throw to reject */ },
|
|
788
|
-
afterHandle(ctx, result, ep) { /* transform the result
|
|
819
|
+
afterHandle(ctx, result, ep) { /* transform the result data */ },
|
|
789
820
|
onError(ctx, error, ep) { /* custom error response — return a Response */ },
|
|
790
821
|
},
|
|
791
822
|
})
|
|
@@ -804,6 +835,80 @@ createServer({
|
|
|
804
835
|
Hooks see `RuntimeContext` (loose types); handlers see `HandlerContext` (typed).
|
|
805
836
|
That split is deliberate — see [ADR 0003](../decisions/0003-two-context-types.md).
|
|
806
837
|
|
|
838
|
+
## Signed JSON webhooks
|
|
839
|
+
|
|
840
|
+
A provider signs the original JSON text, not `JSON.stringify(ctx.input)`.
|
|
841
|
+
Declare `rawBody: true` to retain the same decoded text the router reads while
|
|
842
|
+
keeping normal Zod validation:
|
|
843
|
+
|
|
844
|
+
```ts
|
|
845
|
+
const webhooks = defineContract(
|
|
846
|
+
{ prefix: 'webhooks' },
|
|
847
|
+
{
|
|
848
|
+
receive: {
|
|
849
|
+
method: 'POST', path: '/provider', desc: 'Receive a signed event',
|
|
850
|
+
rawBody: true,
|
|
851
|
+
maxJsonBodyBytes: 256 * 1024,
|
|
852
|
+
input: ProviderEventSchema,
|
|
853
|
+
output: z.object({ accepted: z.boolean() }),
|
|
854
|
+
},
|
|
855
|
+
},
|
|
856
|
+
)
|
|
857
|
+
|
|
858
|
+
receive: async (ctx) => {
|
|
859
|
+
const signature = ctx.req.headers.get('x-signature')
|
|
860
|
+
await verifyWebhookHmac(ctx.rawBody, signature) // guaranteed string
|
|
861
|
+
return { accepted: true }
|
|
862
|
+
}
|
|
863
|
+
```
|
|
864
|
+
|
|
865
|
+
This endpoint is HTTP-only and cannot be multipart or exposed as a tool. The
|
|
866
|
+
router sets `ctx.rawBody` before JSON/Zod validation, so `onError` can inspect it
|
|
867
|
+
after malformed JSON or a schema failure. Endpoints without `rawBody: true` do
|
|
868
|
+
not retain the text. `maxJsonBodyBytes` may also be set once on `createServer` /
|
|
869
|
+
`createHandler`; a route value wins. Both limits are opt-in and abort an
|
|
870
|
+
oversized stream before it is fully buffered. → ADR 0051
|
|
871
|
+
|
|
872
|
+
## Typed JSON response metadata
|
|
873
|
+
|
|
874
|
+
A JSON endpoint that must attach dynamic HTTP headers while preserving typed
|
|
875
|
+
output declares `responseMeta`. The handler still returns ordinary data:
|
|
876
|
+
|
|
877
|
+
```ts
|
|
878
|
+
const auth = defineContract({ prefix: 'auth' }, {
|
|
879
|
+
complete: {
|
|
880
|
+
method: 'POST', path: '/complete', desc: 'Complete authentication',
|
|
881
|
+
input: CompleteAuthSchema,
|
|
882
|
+
output: AuthUserSchema,
|
|
883
|
+
responseMeta: { status: 200 },
|
|
884
|
+
},
|
|
885
|
+
})
|
|
886
|
+
|
|
887
|
+
complete: async ({ input, response }) => {
|
|
888
|
+
const result = await authenticate(input.token)
|
|
889
|
+
response.headers.append('Set-Cookie', session.set(result.sessionId))
|
|
890
|
+
response.headers.append('Set-Cookie', preferences.set(result.preferencesId))
|
|
891
|
+
return result.user
|
|
892
|
+
}
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
`ctx.response.headers` is a fresh Web Fetch `Headers` bag per request. `append`
|
|
896
|
+
preserves repeated `Set-Cookie` values on Bun and Node. The endpoint is
|
|
897
|
+
HTTP-only, but its typed client method still resolves to `AuthUser` — not
|
|
898
|
+
`Response` — and the final data still passes group/global `afterHandle` and the
|
|
899
|
+
declared `output` schema exactly once.
|
|
900
|
+
|
|
901
|
+
`responseMeta.status` is static contract metadata and OpenAPI publishes the same
|
|
902
|
+
2xx code. Without it, data keeps status `200` and no-data keeps `204`. Bodyless
|
|
903
|
+
`204`/`205` cannot be combined with `output`. Redirects, streams, files and
|
|
904
|
+
handler-owned status/body logic remain [`rawResponse: true`](#raw-response-endpoints).
|
|
905
|
+
|
|
906
|
+
Collected headers are merged only after the complete success pipeline. A
|
|
907
|
+
handler, hook or output-validation failure discards them. `Content-Type`,
|
|
908
|
+
`Content-Length`, `x-request-id` and every `Access-Control-*` header remain
|
|
909
|
+
framework-owned; trying to set one fails loudly with the endpoint identity.
|
|
910
|
+
→ [ADR 0052](../decisions/0052-typed-json-response-metadata.md)
|
|
911
|
+
|
|
807
912
|
## Raw-response endpoints
|
|
808
913
|
|
|
809
914
|
An endpoint that answers with **bytes rather than data** — a PDF download, a
|
|
@@ -846,6 +951,10 @@ const name = res.headers.get('Content-Disposition')
|
|
|
846
951
|
const blob = await res.blob()
|
|
847
952
|
```
|
|
848
953
|
|
|
954
|
+
When the browser should navigate or assign the endpoint directly to `src`, use
|
|
955
|
+
[`createUrlBuilder`](./client.md#contract-url-builders). Raw-response GET methods
|
|
956
|
+
are included, while mutation and multipart methods are intentionally absent.
|
|
957
|
+
|
|
849
958
|
Cross-origin, remember that those headers are readable only because CORS exposes
|
|
850
959
|
them — see [`cors.exposeHeaders`](#serving-files--range-requests).
|
|
851
960
|
|
|
@@ -853,8 +962,14 @@ them — see [`cors.exposeHeaders`](#serving-files--range-requests).
|
|
|
853
962
|
code. A raw-response *endpoint* stays in the contract: only its response is
|
|
854
963
|
raw — it is still routed, gated, typed and documented like every other endpoint.
|
|
855
964
|
A raw *route* is outside the contract entirely — no schemas, no auth gate, no
|
|
856
|
-
client — which is what you want for an OAuth redirect or a webhook
|
|
857
|
-
|
|
965
|
+
client — which is what you want for an OAuth redirect or a non-JSON webhook. A
|
|
966
|
+
signed JSON webhook can stay validated through
|
|
967
|
+
[`rawBody: true`](#signed-json-webhooks).
|
|
968
|
+
|
|
969
|
+
If an endpoint returns typed JSON and only needs an additional status/header,
|
|
970
|
+
use [`responseMeta`](#typed-json-response-metadata), not `rawResponse`: the raw
|
|
971
|
+
variant deliberately transfers response ownership and changes the client result
|
|
972
|
+
to `Response`.
|
|
858
973
|
|
|
859
974
|
⚠️ **Delete the old raw route when you move an endpoint into the contract.** Raw
|
|
860
975
|
routes are matched **first**, so a leftover one keeps serving the bytes and the
|
|
@@ -1089,6 +1204,14 @@ createServer({ services: [users, orders], rawRoutes: [openApiRoute('/openapi.jso
|
|
|
1089
1204
|
|
|
1090
1205
|
Only HTTP-exposed methods appear (an MCP/agent-only tool is skipped).
|
|
1091
1206
|
|
|
1207
|
+
OpenAPI 3.1 has no standard multi-segment path parameter. For a contract path
|
|
1208
|
+
ending in `/*`, Stitchkit keeps the literal runtime path, omits `*` from the
|
|
1209
|
+
standard `in: path` parameter list, and emits
|
|
1210
|
+
`x-stitchkit-trailing-wildcard` on the operation with its parameter name,
|
|
1211
|
+
schema and semantics. A generic OpenAPI client therefore cannot invent
|
|
1212
|
+
catch-all expansion; use Stitchkit's typed client or teach the generator that
|
|
1213
|
+
extension.
|
|
1214
|
+
|
|
1092
1215
|
### Curating the spec — `includeMethod`
|
|
1093
1216
|
|
|
1094
1217
|
To publish a **subset** — a public spec that advertises only some methods
|
|
@@ -1148,6 +1271,10 @@ import { createHttpClient } from 'stitchkit'
|
|
|
1148
1271
|
const http = createHttpClient({ baseUrl: '/api' })
|
|
1149
1272
|
```
|
|
1150
1273
|
|
|
1274
|
+
The returned `ConfiguredHttpClient` keeps that `baseUrl` as a readonly public
|
|
1275
|
+
field. Besides executing requests, it can therefore seed contract URL builders
|
|
1276
|
+
without repeating transport configuration.
|
|
1277
|
+
|
|
1151
1278
|
### `HttpClientConfig`
|
|
1152
1279
|
|
|
1153
1280
|
| Field | Default | Purpose |
|
|
@@ -1193,6 +1320,8 @@ Each call takes one argument object. The client routes each field by the
|
|
|
1193
1320
|
contract:
|
|
1194
1321
|
|
|
1195
1322
|
- a **path param** (`:id`) is substituted into the URL,
|
|
1323
|
+
- a terminal wildcard (`/*`) consumes the `'*'` field and preserves its path
|
|
1324
|
+
segments (`{ '*': 'a/b' }` → `/a/b`, not `/%2Fa%2Fb` or a query field),
|
|
1196
1325
|
- for `GET` / `DELETE`, the remaining fields become the **query string**
|
|
1197
1326
|
(arrays become repeated keys),
|
|
1198
1327
|
- for `POST` / `PUT` / `PATCH`, they become the **JSON body**,
|
|
@@ -1212,7 +1341,53 @@ await api.posts.create({ title: 'Hi' })
|
|
|
1212
1341
|
```
|
|
1213
1342
|
|
|
1214
1343
|
`createClients` builds one typed client per contract from a registry — list the
|
|
1215
|
-
contracts once, get the whole API typed.
|
|
1344
|
+
contracts once, get the whole API typed. It accepts the same optional scoped
|
|
1345
|
+
config as `createClient`, so a whole registry can share one resource prefix:
|
|
1346
|
+
|
|
1347
|
+
```ts
|
|
1348
|
+
const tenantApi = createClients({ users, posts }, http, {
|
|
1349
|
+
stripPrefixKeys: ['tenantId'],
|
|
1350
|
+
pathPrefix: ({ tenantId }) => `tenants/${tenantId}`,
|
|
1351
|
+
})
|
|
1352
|
+
```
|
|
1353
|
+
|
|
1354
|
+
Every method now requires `tenantId`, and the callback sees it as a `string`.
|
|
1355
|
+
The batch form delegates to the same single-contract client runtime, including
|
|
1356
|
+
HTTP exposure filtering, multipart, raw responses and output validation.
|
|
1357
|
+
|
|
1358
|
+
## Contract URL builders
|
|
1359
|
+
|
|
1360
|
+
Browser-native consumers such as `<img src>`, downloads and navigation need a
|
|
1361
|
+
URL, not a fetched response. `createUrlBuilder` derives those URLs from the same
|
|
1362
|
+
contract path planner used by both typed-client transports:
|
|
1363
|
+
|
|
1364
|
+
```ts
|
|
1365
|
+
import { createUrlBuilder, createUrlBuilders } from 'stitchkit'
|
|
1366
|
+
|
|
1367
|
+
const mediaUrls = createUrlBuilder(media, http, {
|
|
1368
|
+
stripPrefixKeys: ['tenantId'],
|
|
1369
|
+
pathPrefix: ({ tenantId }) => `tenants/${tenantId}`,
|
|
1370
|
+
})
|
|
1371
|
+
|
|
1372
|
+
const src = mediaUrls.file({
|
|
1373
|
+
tenantId: 't_123',
|
|
1374
|
+
fileId: 'f_456',
|
|
1375
|
+
thumbnail: true,
|
|
1376
|
+
})
|
|
1377
|
+
|
|
1378
|
+
const urls = createUrlBuilders({ media, exports }, http)
|
|
1379
|
+
```
|
|
1380
|
+
|
|
1381
|
+
Only HTTP-exposed, non-multipart `GET` endpoints appear on a URL builder. Raw
|
|
1382
|
+
response GET endpoints are included, so downloads and streams stay
|
|
1383
|
+
contract-driven. Path and scoped-prefix keys are consumed by the path; remaining
|
|
1384
|
+
GET input becomes the query string, including repeated keys for arrays.
|
|
1385
|
+
|
|
1386
|
+
Building a URL is synchronous and performs no request, auth event, header
|
|
1387
|
+
resolution or output validation. A `ConfiguredHttpClient` created by
|
|
1388
|
+
`createHttpClient` supplies its base URL; custom transports can pass an explicit
|
|
1389
|
+
`{ baseUrl: 'https://api.example.com' }` instead. Relative bases produce relative
|
|
1390
|
+
URLs.
|
|
1216
1391
|
|
|
1217
1392
|
## `ApiError`
|
|
1218
1393
|
|
|
@@ -1283,7 +1458,7 @@ segment to every URL — the client half of a multi-tenant API
|
|
|
1283
1458
|
interface ContractClientConfig {
|
|
1284
1459
|
/** Prepended to every request URL. A function is called per request with the
|
|
1285
1460
|
* call's argument object, so the prefix can depend on the arguments. */
|
|
1286
|
-
pathPrefix?: string | ((args:
|
|
1461
|
+
pathPrefix?: string | ((args: { [K in ConsumedKey]: string }) => string)
|
|
1287
1462
|
/** Argument keys consumed by `pathPrefix` — stripped from the query/body so
|
|
1288
1463
|
* they are not also sent there (the endpoint's own path `:params` are
|
|
1289
1464
|
* stripped automatically; list any *extra* keys here). */
|
|
@@ -1295,8 +1470,8 @@ A per-tenant client — `tenantId` goes into the URL, not the body:
|
|
|
1295
1470
|
|
|
1296
1471
|
```ts
|
|
1297
1472
|
const widgets = createClient(widgetsContract, http, {
|
|
1298
|
-
pathPrefix: (args) => `tenants/${args.tenantId}/`,
|
|
1299
1473
|
stripPrefixKeys: ['tenantId'],
|
|
1474
|
+
pathPrefix: ({ tenantId }) => `tenants/${tenantId}/`,
|
|
1300
1475
|
})
|
|
1301
1476
|
|
|
1302
1477
|
widgets.list({ tenantId: 't_123' }) // GET /tenants/t_123/widgets
|
|
@@ -1432,9 +1607,8 @@ and after, compare.
|
|
|
1432
1607
|
## MCP — `createMcpHandler`
|
|
1433
1608
|
|
|
1434
1609
|
`createMcpHandler` builds a complete Streamable-HTTP MCP server as a single
|
|
1435
|
-
`Request → Response` handler. It owns the
|
|
1436
|
-
|
|
1437
|
-
`@modelcontextprotocol/sdk` itself.
|
|
1610
|
+
`Request → Response` handler. It owns the SDK server and transport lifecycle, so
|
|
1611
|
+
your app never imports `@modelcontextprotocol/sdk` itself.
|
|
1438
1612
|
|
|
1439
1613
|
```ts
|
|
1440
1614
|
import { createMcpHandler } from 'stitchkit/tools'
|
|
@@ -1465,14 +1639,34 @@ createServer({
|
|
|
1465
1639
|
| `context` | `(auth) => {…}` — values merged into every tool handler's `ctx` |
|
|
1466
1640
|
| `lifecycle` | `beforeHandle` / `afterHandle` — the tool-side auth gate (see below) |
|
|
1467
1641
|
| `hooks` | tool-call observability hooks — `afterToolCall` fires on every result |
|
|
1468
|
-
| `
|
|
1642
|
+
| `extend` | extra advertised arguments resolved into handler context |
|
|
1643
|
+
| `schemaValidation` | compatibility policy, typed-property guard and portable-format guard |
|
|
1469
1644
|
| `logger` | a `StitchLogger` for the `'warn'` policy |
|
|
1470
|
-
| `nativeTools` | `(
|
|
1645
|
+
| `nativeTools` | `({ registerTool, rawServer }, auth) => …` — protected native registration plus an explicit raw SDK escape hatch |
|
|
1646
|
+
| `resources` | MCP Apps `ui://` resources mounted on every server |
|
|
1471
1647
|
| `instructions` | a short host-facing usage hint, surfaced to MCP tool-search |
|
|
1648
|
+
| `coerceJsonArgs` | coerce JSON-stringified object/array arguments (default `true`) |
|
|
1649
|
+
| `flattenUnionInput` | advertise discriminated unions as one object (default `false`) |
|
|
1650
|
+
| `errorHint` | add a project-owned hint to failed tool results |
|
|
1651
|
+
| `onOutputStrip` | observe output keys removed by contract validation |
|
|
1652
|
+
| `protectedResource` | RFC 9728 metadata used by HTTP `401` responses |
|
|
1653
|
+
| `sessionMode` | `'stateless'` (default) or explicit `'stateful'` session/SSE continuity |
|
|
1472
1654
|
|
|
1473
1655
|
`services`, `context` and `nativeTools` all receive the resolved identity, so a
|
|
1474
1656
|
tenant can be shown only its own tools and every handler can read `ctx.tenantId`.
|
|
1475
1657
|
|
|
1658
|
+
### Stateless by default; stateful only when required
|
|
1659
|
+
|
|
1660
|
+
The default `sessionMode: 'stateless'` creates a fresh SDK server, transport,
|
|
1661
|
+
resolved auth/context and runner for each HTTP request. Static contract schemas
|
|
1662
|
+
are still prepared once when the handler is constructed. There is no session
|
|
1663
|
+
map, event store, sweep timer or `Mcp-Session-Id`, so process replacement and
|
|
1664
|
+
load balancing cannot strand a client on an in-memory session.
|
|
1665
|
+
|
|
1666
|
+
Opt into `sessionMode: 'stateful'` only when the client needs server-initiated
|
|
1667
|
+
messages, cross-request progress or resumable SSE. That mode issues a server
|
|
1668
|
+
session id and retains the bounded session/event stores until idle expiry.
|
|
1669
|
+
|
|
1476
1670
|
### Guarding tools — `lifecycle`
|
|
1477
1671
|
|
|
1478
1672
|
A tool call runs the same handler an HTTP request would. `lifecycle` makes it
|
|
@@ -1490,15 +1684,20 @@ Without it, a tool call bypasses the HTTP `beforeHandle` — the contract's
|
|
|
1490
1684
|
`buildMcpServer` take `lifecycle` too.
|
|
1491
1685
|
|
|
1492
1686
|
The observability `hooks` are symmetric with the HTTP side too: `beforeToolCall`
|
|
1493
|
-
and `afterToolCall` receive the resolved **`MethodDef`** as
|
|
1687
|
+
and `afterToolCall` receive the resolved **`MethodDef`** as `endpoint` —
|
|
1494
1688
|
the tool-side twin of `afterHandle(ctx, result, endpoint)`. Read
|
|
1495
1689
|
`endpoint.serviceName` / `.key` / `.meta` directly for an audit row; you do not
|
|
1496
1690
|
need to rebuild a `toolName → identity` map:
|
|
1497
1691
|
|
|
1498
1692
|
```ts
|
|
1499
1693
|
hooks: {
|
|
1500
|
-
afterToolCall: (
|
|
1501
|
-
audit({
|
|
1694
|
+
afterToolCall: ({ result, durationMs, endpoint }) => {
|
|
1695
|
+
audit({
|
|
1696
|
+
service: endpoint.serviceName,
|
|
1697
|
+
action: endpoint.key,
|
|
1698
|
+
ok: result.ok,
|
|
1699
|
+
ms: durationMs,
|
|
1700
|
+
})
|
|
1502
1701
|
},
|
|
1503
1702
|
}
|
|
1504
1703
|
```
|
|
@@ -1509,10 +1708,10 @@ hooks: {
|
|
|
1509
1708
|
> has no identity and **fails closed**. See
|
|
1510
1709
|
> [Auth on the tool surface](./auth-and-errors.md#auth-on-the-tool-surface--resolvefromcontext).
|
|
1511
1710
|
|
|
1512
|
-
###
|
|
1711
|
+
### MCP schema validation profile
|
|
1513
1712
|
|
|
1514
1713
|
A contract schema that JSON Schema cannot represent (a `z.date()`, a `z.map()`)
|
|
1515
|
-
cannot become a tool. `
|
|
1714
|
+
cannot become a tool. `schemaValidation.policy` decides what happens:
|
|
1516
1715
|
|
|
1517
1716
|
- `'throw'` (default) — fail the build, listing every offending tool. A static
|
|
1518
1717
|
`services` array is checked when `createMcpHandler` is constructed, so a bad
|
|
@@ -1521,9 +1720,16 @@ cannot become a tool. `onIncompatibleSchema` decides what happens:
|
|
|
1521
1720
|
- `'warn'` — log through `logger` and drop the tool.
|
|
1522
1721
|
- `'skip'` — drop the tool silently.
|
|
1523
1722
|
|
|
1524
|
-
`validateMcpSchemas(services)` runs the same check on its own — useful in a
|
|
1723
|
+
`validateMcpSchemas({ services })` runs the same check on its own — useful in a
|
|
1525
1724
|
startup assertion or a test.
|
|
1526
1725
|
|
|
1726
|
+
For a static `services` array, collection, schema conversion and every enabled
|
|
1727
|
+
validation guard run once when the handler is created. Each HTTP request or
|
|
1728
|
+
stateful session still receives a fresh `McpServer`, runner, context and native
|
|
1729
|
+
registration over that immutable prepared surface. A `services(auth)` factory
|
|
1730
|
+
is deliberately prepared after resolving each identity because its tool set may
|
|
1731
|
+
change by tenant.
|
|
1732
|
+
|
|
1527
1733
|
#### Is every property actually usable by a model?
|
|
1528
1734
|
|
|
1529
1735
|
A tool schema is the only instruction a model gets about the shape of its
|
|
@@ -1533,17 +1739,30 @@ mount succeeds, the tool is advertised, and the model then guesses, retrying the
|
|
|
1533
1739
|
same wrong guess because the error does not say what the right one would be.
|
|
1534
1740
|
|
|
1535
1741
|
```ts
|
|
1536
|
-
validateMcpSchemas(
|
|
1537
|
-
|
|
1742
|
+
validateMcpSchemas({
|
|
1743
|
+
services,
|
|
1744
|
+
policy: 'throw',
|
|
1745
|
+
logger,
|
|
1746
|
+
extend,
|
|
1747
|
+
flattenUnionInput: true,
|
|
1538
1748
|
requireTypedProperties: true,
|
|
1539
1749
|
allowUntyped: ['docs_create.payload'], // deliberately free-form
|
|
1750
|
+
requirePortableFormats: true,
|
|
1751
|
+
allowFormats: [],
|
|
1540
1752
|
})
|
|
1541
1753
|
```
|
|
1542
1754
|
|
|
1543
1755
|
Off by default, because a contract may legitimately declare `z.unknown()`.
|
|
1544
1756
|
`allowUntyped` takes dotted `tool.property` paths — an entry there is a decision,
|
|
1545
|
-
anything else is a finding.
|
|
1546
|
-
|
|
1757
|
+
anything else is a finding. On `createMcpHandler`, put the policy under
|
|
1758
|
+
`schemaValidation`; the handler supplies its real `extend` and
|
|
1759
|
+
`flattenUnionInput`, so the check cannot vet a different document from the one
|
|
1760
|
+
advertised.
|
|
1761
|
+
|
|
1762
|
+
`requirePortableFormats` rejects custom `format` values common MCP/AJV clients
|
|
1763
|
+
do not know, such as the `cuid2` emitted by `z.cuid2()`. Use a portable
|
|
1764
|
+
schema/pattern, or list it in `allowFormats` only when every client supports it.
|
|
1765
|
+
stitchkit never removes or rewrites the keyword.
|
|
1547
1766
|
|
|
1548
1767
|
`findUntypedProperties(jsonSchema)` is the same walk, exported on its own if you
|
|
1549
1768
|
want to assert on a schema you built elsewhere.
|
|
@@ -1566,25 +1785,19 @@ A field that is a number in every variant is advertised as a number, not as a
|
|
|
1566
1785
|
bare description. Only genuinely different kinds (a string in one variant, a
|
|
1567
1786
|
number in another) fall back to unconstrained. → ADR 0044
|
|
1568
1787
|
|
|
1569
|
-
It is **deep
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
The flattened form is **lossy but never
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
contract would have rejected. → [ADR 0034](../decisions/0034-advertised-schema-key-policy.md).
|
|
1583
|
-
|
|
1584
|
-
A consequence worth knowing when you read logs: a `.strict()` violation is caught
|
|
1585
|
-
by the SDK **before** the tool callback runs, so it comes back as an MCP
|
|
1586
|
-
`InvalidParams` protocol error rather than a stitchkit `VALIDATION_ERROR`
|
|
1587
|
-
envelope, and `beforeToolCall` / `afterToolCall` do not fire for it.
|
|
1788
|
+
It is **deep** because the projection walks the generated JSON Schema document,
|
|
1789
|
+
including objects, arrays, tuples and schema-definition nodes. Structurally
|
|
1790
|
+
identifiable discriminated object unions are flattened wherever they occur.
|
|
1791
|
+
Plain unions and unions hidden behind unresolved external references remain
|
|
1792
|
+
unions because Stitchkit cannot soundly invent a discriminator.
|
|
1793
|
+
|
|
1794
|
+
The flattened form is **lossy but never executable**. Per-variant refinements
|
|
1795
|
+
and incompatible constraints are widened in the presentation document; the
|
|
1796
|
+
original Zod contract enforces them exactly once inside `executeToolMethod`.
|
|
1797
|
+
MCP and AI adapters forward the raw argument object unchanged, so defaults,
|
|
1798
|
+
coercions and transforms cannot run before Stitchkit. Strict violations return
|
|
1799
|
+
the normal `VALIDATION_ERROR` tool envelope and fire `beforeToolCall` /
|
|
1800
|
+
`afterToolCall`. → [ADR 0050](../decisions/0050-presentation-schema-is-not-a-parser.md).
|
|
1588
1801
|
|
|
1589
1802
|
## `mountMcp`
|
|
1590
1803
|
|
|
@@ -1820,10 +2033,60 @@ so one handler serves both surfaces. Pair `extend` with `lifecycle` (your
|
|
|
1820
2033
|
|
|
1821
2034
|
## Native multimodal tools
|
|
1822
2035
|
|
|
1823
|
-
Contract tools return JSON.
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
2036
|
+
Contract tools return JSON. A native tool can return MCP text/image/audio/
|
|
2037
|
+
resource content directly while still using stitchkit's input/output validation,
|
|
2038
|
+
isolated per-call context, lifecycle/RBAC and tool hooks:
|
|
2039
|
+
|
|
2040
|
+
```ts
|
|
2041
|
+
import { createMcpHandler } from 'stitchkit/tools'
|
|
2042
|
+
import { z } from 'zod'
|
|
2043
|
+
|
|
2044
|
+
const handleMcp = createMcpHandler({
|
|
2045
|
+
serverInfo: { name: 'my-app', version: '1.0.0' },
|
|
2046
|
+
auth,
|
|
2047
|
+
services: [service],
|
|
2048
|
+
lifecycle: { beforeHandle: authHook },
|
|
2049
|
+
hooks: audit.toolCall,
|
|
2050
|
+
nativeTools: ({ registerTool }, identity) => {
|
|
2051
|
+
registerTool({
|
|
2052
|
+
name: 'render_preview',
|
|
2053
|
+
description: 'Render and inspect a preview',
|
|
2054
|
+
identity: {
|
|
2055
|
+
serviceName: 'mediaTools',
|
|
2056
|
+
action: 'renderPreview',
|
|
2057
|
+
scope: 'admin',
|
|
2058
|
+
method: 'POST',
|
|
2059
|
+
},
|
|
2060
|
+
input: z.object({ prompt: z.string() }),
|
|
2061
|
+
output: z.object({ assetId: z.string() }),
|
|
2062
|
+
handler: async ({ input, traceId }) => ({
|
|
2063
|
+
content: [
|
|
2064
|
+
{ type: 'image', data: await renderBase64(input.prompt), mimeType: 'image/png' },
|
|
2065
|
+
{ type: 'text', text: `trace: ${traceId}` },
|
|
2066
|
+
],
|
|
2067
|
+
structuredContent: { assetId: await saveAsset(identity) },
|
|
2068
|
+
}),
|
|
2069
|
+
})
|
|
2070
|
+
},
|
|
2071
|
+
})
|
|
2072
|
+
```
|
|
2073
|
+
|
|
2074
|
+
The configured identity becomes the hook/lifecycle `OperationIdentity` and the
|
|
2075
|
+
tool `RequestEvent` (`serviceName`, `action`, `httpMethod`). A native operation
|
|
2076
|
+
has no HTTP route, so no fake `path` is added to that identity. If `output` is
|
|
2077
|
+
declared, stitchkit parses `structuredContent` with it after `afterHandle`; all
|
|
2078
|
+
other MCP fields and content blocks are preserved.
|
|
2079
|
+
|
|
2080
|
+
The MCP registration uses an identity carrier: the SDK advertises the compiled
|
|
2081
|
+
JSON Schema but forwards the raw object into Stitchkit. Input failures therefore
|
|
2082
|
+
use the same validation, lifecycle and hook path as contract tools.
|
|
2083
|
+
|
|
2084
|
+
### Explicit raw SDK registration
|
|
2085
|
+
|
|
2086
|
+
`rawServer` is deliberately named as an escape hatch. A tool registered there
|
|
2087
|
+
does **not** receive stitchkit schema policy, lifecycle, per-call context or
|
|
2088
|
+
hooks. The built-in `mountViewFile` helper remains raw for callers that choose
|
|
2089
|
+
that boundary; it fetches media with SSRF and path-traversal defenses:
|
|
1827
2090
|
|
|
1828
2091
|
```ts
|
|
1829
2092
|
import { createMcpHandler, mountViewFile } from 'stitchkit/tools'
|
|
@@ -1832,10 +2095,15 @@ const handleMcp = createMcpHandler({
|
|
|
1832
2095
|
serverInfo: { name: 'my-app', version: '1.0.0' },
|
|
1833
2096
|
auth,
|
|
1834
2097
|
services: [service],
|
|
1835
|
-
nativeTools: (
|
|
2098
|
+
nativeTools: ({ rawServer }) =>
|
|
2099
|
+
mountViewFile(rawServer, { baseDir: '/srv/uploads' }),
|
|
1836
2100
|
})
|
|
1837
2101
|
```
|
|
1838
2102
|
|
|
2103
|
+
Use raw registration only when opting out is intentional. For a protected
|
|
2104
|
+
`view_file`, define it through `registerTool` and call the exported
|
|
2105
|
+
`resolveMedia` core from its handler.
|
|
2106
|
+
|
|
1839
2107
|
## Logging tool calls — `createToolLogger`
|
|
1840
2108
|
|
|
1841
2109
|
Every tool mount fires an `afterToolCall` hook. `createToolLogger` is a ready
|
|
@@ -2605,6 +2873,20 @@ session.clear() // → a Set-Cookie value that expires it
|
|
|
2605
2873
|
config is not repeated at every call site. `parseCookies(header)` and
|
|
2606
2874
|
`serializeCookie(name, value, opts)` are the lower-level primitives.
|
|
2607
2875
|
|
|
2876
|
+
To set a cookie from a schema-validated JSON endpoint without losing its typed
|
|
2877
|
+
client result, declare [`responseMeta`](./server.md#typed-json-response-metadata)
|
|
2878
|
+
and append the generated value:
|
|
2879
|
+
|
|
2880
|
+
```ts
|
|
2881
|
+
complete: async ({ response }) => {
|
|
2882
|
+
response.headers.append('Set-Cookie', session.set('abc123'))
|
|
2883
|
+
return authenticatedUser
|
|
2884
|
+
}
|
|
2885
|
+
```
|
|
2886
|
+
|
|
2887
|
+
Append once per cookie; Stitchkit preserves separate `Set-Cookie` fields through
|
|
2888
|
+
both Bun and Node. Cookie/session policy remains application logic.
|
|
2889
|
+
|
|
2608
2890
|
## The error model
|
|
2609
2891
|
|
|
2610
2892
|
One error type, `AppError`, is shared by the contract, the server and the
|
|
@@ -2946,7 +3228,16 @@ either way, so your sink reads it as a column instead of re-parsing the path:
|
|
|
2946
3228
|
> same hooks object is assignable to `ToolLifecycle`, so this recipe is the one
|
|
2947
3229
|
> people apply to tools — and since ADR 0045 each tool call runs in its own
|
|
2948
3230
|
> context. Read the value off the tool row (`event.toolName != null`); both rows
|
|
2949
|
-
> carry the same `traceId
|
|
3231
|
+
> carry the same `traceId`, so recovering the enclosing request is one filter:
|
|
3232
|
+
>
|
|
3233
|
+
> ```bash
|
|
3234
|
+
> jq -s '[.[] | select(.dimensions.botId == "B7") | .traceId] as $t
|
|
3235
|
+
> | .[] | select(.traceId | IN($t[]))' audit.jsonl
|
|
3236
|
+
> ```
|
|
3237
|
+
>
|
|
3238
|
+
> Worth checking your **runbooks** as well as your code when this lands: a
|
|
3239
|
+
> dimension filter written against request rows keeps parsing and starts
|
|
3240
|
+
> returning nothing.
|
|
2950
3241
|
|
|
2951
3242
|
```ts
|
|
2952
3243
|
// beforeHandle (success) and onError (failure) alike:
|
|
@@ -3076,11 +3367,11 @@ metric, a custom log line, anything that is not a full audit row.
|
|
|
3076
3367
|
|---------|------|-------|
|
|
3077
3368
|
| HTTP | `LifecycleHooks.afterHandle` / `onError` | after each HTTP request |
|
|
3078
3369
|
| MCP & agent tools | `ToolCallHooks.afterToolCall` | after each tool call |
|
|
3079
|
-
| MCP & agent tools | `ToolCallHooks.onToolError` | when
|
|
3370
|
+
| MCP & agent tools | `ToolCallHooks.onToolError` | when executable parsing, extension resolution, lifecycle or the handler throws |
|
|
3080
3371
|
|
|
3081
3372
|
`afterHandle(ctx, result, endpoint)` runs after a handler returns;
|
|
3082
|
-
`onError(ctx, error, endpoint)` when one throws. `afterToolCall(
|
|
3083
|
-
|
|
3373
|
+
`onError(ctx, error, endpoint)` when one throws. `afterToolCall(options)` runs
|
|
3374
|
+
after every tool call —
|
|
3084
3375
|
success and error alike — carrying the tool name, the arguments, the result, the
|
|
3085
3376
|
duration, the call context, the endpoint identity, and (only when the call failed
|
|
3086
3377
|
by throwing) the raw thrown value.
|
|
@@ -3089,7 +3380,7 @@ by throwing) the raw thrown value.
|
|
|
3089
3380
|
createMcpHandler({
|
|
3090
3381
|
serverInfo, auth, services,
|
|
3091
3382
|
hooks: {
|
|
3092
|
-
afterToolCall: (toolName,
|
|
3383
|
+
afterToolCall: ({ toolName, result, durationMs }) => {
|
|
3093
3384
|
metrics.timing(`tool.${toolName}`, durationMs, { ok: String(result.ok) })
|
|
3094
3385
|
},
|
|
3095
3386
|
},
|
|
@@ -3115,7 +3406,7 @@ normalisation, stack and `cause` intact:
|
|
|
3115
3406
|
createMcpHandler({
|
|
3116
3407
|
serverInfo, auth, services,
|
|
3117
3408
|
hooks: {
|
|
3118
|
-
onToolError: (toolName, error,
|
|
3409
|
+
onToolError: ({ toolName, error, endpoint }) => {
|
|
3119
3410
|
reportToolFailure({
|
|
3120
3411
|
tool: toolName,
|
|
3121
3412
|
action: endpoint.key,
|
|
@@ -3127,13 +3418,12 @@ createMcpHandler({
|
|
|
3127
3418
|
})
|
|
3128
3419
|
```
|
|
3129
3420
|
|
|
3130
|
-
It fires for a throw from
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
`beforeToolCall` rejection: each
|
|
3135
|
-
`ToolResult
|
|
3136
|
-
double-logging.
|
|
3421
|
+
It fires for a throw from executable params/input/`ToolExtend` parsing,
|
|
3422
|
+
extension resolution, `beforeHandle`, the handler or `afterHandle` — every span
|
|
3423
|
+
where information is destroyed — and runs **before** `afterToolCall`. It does
|
|
3424
|
+
not fire for an ordinary validation result, an output-schema mismatch or a
|
|
3425
|
+
`beforeToolCall` rejection: each is already described in full by its
|
|
3426
|
+
`ToolResult`.
|
|
3137
3427
|
|
|
3138
3428
|
It observes, it does not handle: the tool envelope is always the framework's, a
|
|
3139
3429
|
returned value is ignored, and a throw from the hook itself is reported to
|
|
@@ -3155,11 +3445,11 @@ either — the call is simply not where that helper belongs.)
|
|
|
3155
3445
|
### One row that names the cause
|
|
3156
3446
|
|
|
3157
3447
|
You do not need to correlate the two hooks yourself. `afterToolCall` receives the
|
|
3158
|
-
same raw value as
|
|
3448
|
+
same raw value as the named `error` field, so one hook can build one record:
|
|
3159
3449
|
|
|
3160
3450
|
```ts
|
|
3161
3451
|
hooks: {
|
|
3162
|
-
afterToolCall: (toolName,
|
|
3452
|
+
afterToolCall: ({ toolName, result, durationMs, endpoint, error }) => {
|
|
3163
3453
|
void writeRow({ toolName, result, durationMs, cause: error, endpoint })
|
|
3164
3454
|
},
|
|
3165
3455
|
}
|
|
@@ -3167,8 +3457,8 @@ hooks: {
|
|
|
3167
3457
|
|
|
3168
3458
|
`error` is present only when the call failed by **throwing** — a
|
|
3169
3459
|
validation failure or a `beforeToolCall` rejection leaves it `undefined`, because
|
|
3170
|
-
neither ever had a raw value to lose.
|
|
3171
|
-
|
|
3460
|
+
neither ever had a raw value to lose. Consumers destructure only the fields they
|
|
3461
|
+
use; future optional fields do not change callback arity.
|
|
3172
3462
|
|
|
3173
3463
|
`createAuditHook` uses it already. Where the envelope was scrubbed to
|
|
3174
3464
|
`INTERNAL_SERVER_ERROR`, the row's `errorMessage` becomes the real message
|
|
@@ -3186,11 +3476,13 @@ sink of your own (a tracker, a stack, an alert), `afterToolCall` for the record.
|
|
|
3186
3476
|
`createAuditHook` already keys every event by **service** and **action**
|
|
3187
3477
|
(`event.serviceName` / `event.action`, → ADR 0029) — reach for the raw hook only
|
|
3188
3478
|
when you also need the handler **output**, which the audit wrapper never sees. For
|
|
3189
|
-
that, read the endpoint identity off the `
|
|
3190
|
-
`endpoint.serviceName`
|
|
3479
|
+
that, read the endpoint identity off the `OperationIdentity` the tool hook
|
|
3480
|
+
receives — `endpoint.serviceName` and `endpoint.key` (the endpoint key or native
|
|
3481
|
+
action,
|
|
3191
3482
|
e.g. `updatePartial`). They are stable and always present (→ ADR 0022); the action
|
|
3192
3483
|
is not in the URL and `toolName` is absent on HTTP-only endpoints, so this is the
|
|
3193
|
-
only reliable pair.
|
|
3484
|
+
only reliable pair. Contract operations are full `MethodDef` values; native MCP
|
|
3485
|
+
operations intentionally have no HTTP `path`. `afterHandle` also gives you the handler `result` — so it is
|
|
3194
3486
|
the home for a rich mutation audit that records output:
|
|
3195
3487
|
|
|
3196
3488
|
```ts
|
|
@@ -3324,6 +3616,10 @@ serveNode({
|
|
|
3324
3616
|
|
|
3325
3617
|
Notes for a Node host:
|
|
3326
3618
|
|
|
3619
|
+
- `stitchkit/node` declarations do not require `@types/bun`. Runtime-neutral
|
|
3620
|
+
raw routes use `RawRoute<TServer = unknown>`; supply a host server generic
|
|
3621
|
+
only when an embedding adapter passes one to `createHandler`.
|
|
3622
|
+
|
|
3327
3623
|
- Add **`@types/bun`** as a dev dependency — it is an optional peer that types the
|
|
3328
3624
|
shared `stitchkit/server` surface (without it `tsc` reports a missing `Bun`
|
|
3329
3625
|
namespace).
|
|
@@ -3456,7 +3752,7 @@ keeps `tenantId` out of the body/query
|
|
|
3456
3752
|
|
|
3457
3753
|
```ts
|
|
3458
3754
|
const widgetsApi = createClient(widgets, http, {
|
|
3459
|
-
pathPrefix: (
|
|
3755
|
+
pathPrefix: ({ tenantId }) => `tenants/${tenantId}/`,
|
|
3460
3756
|
stripPrefixKeys: ['tenantId'],
|
|
3461
3757
|
})
|
|
3462
3758
|
|
|
@@ -3561,6 +3857,130 @@ current one *up to* your target, and apply each snippet.
|
|
|
3561
3857
|
(`STITCH_ERROR_STATUS`, `serveFile`, `scopePrefixes`, `afterToolCall`'s
|
|
3562
3858
|
`MethodDef`, `maxUploadBytes`) are available to adopt, not required.
|
|
3563
3859
|
|
|
3860
|
+
## The 0.37 migration
|
|
3861
|
+
|
|
3862
|
+
Tool presentation is no longer an executable Zod parser. Replace the removed
|
|
3863
|
+
flatten helpers with the JSON Schema compiler, and choose the explicit
|
|
3864
|
+
`MountableTool` surface when using the advanced collection API:
|
|
3865
|
+
|
|
3866
|
+
```ts
|
|
3867
|
+
// before
|
|
3868
|
+
const flat = flattenUnionsDeep(zodSchema)
|
|
3869
|
+
mountable.schema
|
|
3870
|
+
|
|
3871
|
+
// after
|
|
3872
|
+
const flat = flattenToolJsonSchema(
|
|
3873
|
+
z.toJSONSchema(zodSchema, { target: 'draft-07', io: 'input' }),
|
|
3874
|
+
)
|
|
3875
|
+
mountable.presentationSchema // model/MCP/manifest JSON Schema
|
|
3876
|
+
mountable.argumentSchema // executable CLI argument adapter only
|
|
3877
|
+
```
|
|
3878
|
+
|
|
3879
|
+
Contract and native handlers keep their original Zod schemas; MCP and agent SDK
|
|
3880
|
+
adapters now forward raw arguments so defaults, coercions, refinements and
|
|
3881
|
+
transforms execute exactly once inside Stitchkit. There are no compatibility
|
|
3882
|
+
exports for `flattenDiscriminatedUnion`, `flattenUnionsDeep` or
|
|
3883
|
+
`MountableTool.schema`. → ADR 0050
|
|
3884
|
+
|
|
3885
|
+
Tool-call hooks now take one options object. Migrate all three callbacks; there
|
|
3886
|
+
are no positional overloads:
|
|
3887
|
+
|
|
3888
|
+
```ts
|
|
3889
|
+
// before
|
|
3890
|
+
beforeToolCall: (toolName, args, context, endpoint) => {}
|
|
3891
|
+
afterToolCall: (toolName, args, result, durationMs, context, endpoint, error) => {}
|
|
3892
|
+
onToolError: (toolName, error, context, endpoint) => {}
|
|
3893
|
+
|
|
3894
|
+
// after
|
|
3895
|
+
beforeToolCall: ({ toolName, args, context, endpoint }) => {}
|
|
3896
|
+
afterToolCall: ({ toolName, args, result, durationMs, context, endpoint, error }) => {}
|
|
3897
|
+
onToolError: ({ toolName, error, context, endpoint }) => {}
|
|
3898
|
+
```
|
|
3899
|
+
|
|
3900
|
+
### MCP schema validation
|
|
3901
|
+
|
|
3902
|
+
The standalone validator now takes one object and live MCP configs carry the
|
|
3903
|
+
same rules under `schemaValidation`. Migrate every positional call and every
|
|
3904
|
+
`onIncompatibleSchema` field; there is no old-shape overload:
|
|
3905
|
+
|
|
3906
|
+
```ts
|
|
3907
|
+
// before
|
|
3908
|
+
validateMcpSchemas(services, 'throw', logger, { requireTypedProperties: true })
|
|
3909
|
+
createMcpHandler({ services, onIncompatibleSchema: 'throw' })
|
|
3910
|
+
|
|
3911
|
+
// after
|
|
3912
|
+
validateMcpSchemas({ services, policy: 'throw', logger, requireTypedProperties: true })
|
|
3913
|
+
createMcpHandler({ services, schemaValidation: { policy: 'throw' } })
|
|
3914
|
+
```
|
|
3915
|
+
|
|
3916
|
+
Put `extend` and `flattenUnionInput` beside `services`, not inside
|
|
3917
|
+
`schemaValidation`. The handler applies the profile to the exact prepared schema
|
|
3918
|
+
it advertises. Add `requirePortableFormats: true` when every custom JSON Schema
|
|
3919
|
+
format must be rejected before a client sees it.
|
|
3920
|
+
|
|
3921
|
+
Native MCP registration also changed shape in 0.37. Move protected tools to the
|
|
3922
|
+
framework registrar; keep an SDK-raw tool only by naming the opt-out:
|
|
3923
|
+
|
|
3924
|
+
```ts
|
|
3925
|
+
// before
|
|
3926
|
+
nativeTools: (server, auth) => server.registerTool(name, config, handler)
|
|
3927
|
+
|
|
3928
|
+
// after — lifecycle, hooks and schema policy apply
|
|
3929
|
+
nativeTools: ({ registerTool }, auth) => registerTool({
|
|
3930
|
+
name, description, identity, input, output, handler,
|
|
3931
|
+
})
|
|
3932
|
+
|
|
3933
|
+
// after — intentionally raw
|
|
3934
|
+
nativeTools: ({ rawServer }, auth) => rawServer.registerTool(name, config, handler)
|
|
3935
|
+
```
|
|
3936
|
+
|
|
3937
|
+
If a tool hook annotated `endpoint` as `MethodDef`, remove that annotation or
|
|
3938
|
+
use `OperationIdentity`: native operations have service/action/scope/method but
|
|
3939
|
+
no HTTP path.
|
|
3940
|
+
|
|
3941
|
+
### MCP HTTP sessions
|
|
3942
|
+
|
|
3943
|
+
Finally, replace the MCP HTTP session boolean. Omission changed meaning:
|
|
3944
|
+
|
|
3945
|
+
```ts
|
|
3946
|
+
// before → after
|
|
3947
|
+
stateless: true // → sessionMode: 'stateless'
|
|
3948
|
+
stateless: false // → sessionMode: 'stateful'
|
|
3949
|
+
|
|
3950
|
+
// before: omission was stateful
|
|
3951
|
+
// after: omission is stateless
|
|
3952
|
+
```
|
|
3953
|
+
|
|
3954
|
+
If the client relies on `Mcp-Session-Id`, server push, progress across requests
|
|
3955
|
+
or resumable SSE, set `sessionMode: 'stateful'` explicitly. Synchronous tool
|
|
3956
|
+
servers should omit it and use the new restart-safe default.
|
|
3957
|
+
|
|
3958
|
+
### Node-facing server types
|
|
3959
|
+
|
|
3960
|
+
`stitchkit/server` remains Bun-concrete: an explicitly annotated `RawRoute`
|
|
3961
|
+
still receives `BunServer`. `stitchkit/node` no longer drags Bun declarations
|
|
3962
|
+
into a Node project. Its raw routes default the host server to `unknown`, and
|
|
3963
|
+
its Socket.IO handle exposes only Node capabilities:
|
|
3964
|
+
|
|
3965
|
+
```ts
|
|
3966
|
+
// before — Node entry still leaked Bun-only fields and ambient types
|
|
3967
|
+
const route: RawRoute = { handler: (_req, ctx) => ctx.server?.upgrade(...) }
|
|
3968
|
+
const socket = await createSocketIOServer(config)
|
|
3969
|
+
socket.websocket
|
|
3970
|
+
|
|
3971
|
+
// after — name a custom embedding host only when one exists
|
|
3972
|
+
const route: RawRoute<MyHostServer> = {
|
|
3973
|
+
handler: (_req, ctx) => useHost(ctx.server),
|
|
3974
|
+
}
|
|
3975
|
+
const socket = await createSocketIOServer(config)
|
|
3976
|
+
socket.io
|
|
3977
|
+
socket.attach(nodeHttpServer)
|
|
3978
|
+
```
|
|
3979
|
+
|
|
3980
|
+
Inline Bun routes passed to `createServer` continue to infer `BunServer`; no
|
|
3981
|
+
annotation is needed. Node consumers can remove `@types/bun` unless another
|
|
3982
|
+
dependency independently requires it.
|
|
3983
|
+
|
|
3564
3984
|
## Your handlers may be returning more than the contract declares
|
|
3565
3985
|
|
|
3566
3986
|
stitchkit validates every handler's return value against the endpoint's `output`
|
|
@@ -3631,12 +4051,17 @@ The browser-and-server entrypoint. Re-exports everything from
|
|
|
3631
4051
|
| Export | Kind | Summary |
|
|
3632
4052
|
|--------|------|---------|
|
|
3633
4053
|
| `createClient` | function | build a typed client from a contract — [guide](../guide/client.md#createclient) |
|
|
3634
|
-
| `createClients` | function | build one typed client per contract from a registry |
|
|
4054
|
+
| `createClients` | function | build one exact typed client per contract from a registry; accepts the same scoped config and transports as `createClient` |
|
|
4055
|
+
| `createUrlBuilder` | function | build synchronous browser-native URLs for one contract's HTTP GET endpoints — [guide](../guide/client.md#contract-url-builders) |
|
|
4056
|
+
| `createUrlBuilders` | function | build one exact URL builder per contract in a registry |
|
|
4057
|
+
| `UrlBuilderConfig` | _type_ | explicit `{ baseUrl }` source for a URL builder |
|
|
3635
4058
|
| `ClientConfig` | _type_ | config for `createClient`'s bare-fetch mode (2nd arg, no `HttpClient`) |
|
|
3636
4059
|
| `ContractClientConfig` | _type_ | per-tenant / resource-scoped client config — dynamic `pathPrefix` + `stripPrefixKeys` ([guide](../guide/client.md#contractclientconfig--per-tenant--resource-scoped-clients)) |
|
|
4060
|
+
| `PathPrefixArgs` | _type_ | required string-valued keys exposed to a typed dynamic `pathPrefix` callback |
|
|
3637
4061
|
| `createHttpClient` | function | the Ky-based HTTP transport — [guide](../guide/client.md#createhttpclient) |
|
|
3638
4062
|
| `ApiError` | class | a non-2xx response, with `code` / `status` / `details` / `hint` |
|
|
3639
4063
|
| `HttpClient` | _type_ | the transport interface `createClient` builds on |
|
|
4064
|
+
| `ConfiguredHttpClient` | _type_ | a framework-created `HttpClient` carrying its readonly `baseUrl` for URL builders |
|
|
3640
4065
|
| `HttpClientConfig` | _type_ | config for `createHttpClient` |
|
|
3641
4066
|
| `RequestOptions` | _type_ | per-call options — params, timeout, response type |
|
|
3642
4067
|
| `HeaderProvider` | _type_ | static or per-request headers |
|
|
@@ -3688,6 +4113,10 @@ from the root `stitchkit`.
|
|
|
3688
4113
|
| `ContractDef` | _type_ | a defined contract |
|
|
3689
4114
|
| `ContractMeta` | _type_ | a contract's `prefix` + optional `scope` and `meta` (a default every endpoint shallow-merges over) |
|
|
3690
4115
|
| `EndpointDef` | _type_ | a single endpoint definition |
|
|
4116
|
+
| `EndpointResponseMeta` | _type_ | static success metadata declared by an HTTP-only typed-data endpoint |
|
|
4117
|
+
| `ResponseMetadata` | _type_ | per-request outbound collector exposed as `ctx.response` only for a `responseMeta` endpoint |
|
|
4118
|
+
| `HttpSuccessStatus` | _type_ | supported declared 2xx success statuses |
|
|
4119
|
+
| `BodyHttpSuccessStatus` | _type_ | supported 2xx statuses excluding bodyless 204/205 |
|
|
3691
4120
|
| `HttpMethod` | _type_ | `GET \| POST \| PUT \| PATCH \| DELETE` |
|
|
3692
4121
|
| `Transport` | _type_ | `HTTP \| MCP \| AGENT \| CLI` |
|
|
3693
4122
|
| `TransportSource` | _type_ | `http \| mcp \| agent \| cli` — the value of `ctx.source` |
|
|
@@ -3698,6 +4127,9 @@ from the root `stitchkit`.
|
|
|
3698
4127
|
| `TypedHttpClient` | _type_ | the typed client, HTTP endpoints only (`= ScopedHttpClient<C, unknown>`) |
|
|
3699
4128
|
| `ScopedHttpClient` | _type_ | a client whose `stripPrefixKeys` become required args ([guide](../guide/multi-tenant.md)) |
|
|
3700
4129
|
| `ScopedEndpointFn` | _type_ | one method's signature with the consumed keys folded in |
|
|
4130
|
+
| `TypedUrlBuilder` | _type_ | one contract's HTTP, non-multipart GET endpoints as synchronous URL functions |
|
|
4131
|
+
| `ScopedUrlBuilder` | _type_ | a URL builder whose scoped-prefix keys are required method arguments |
|
|
4132
|
+
| `ScopedUrlFn` | _type_ | one URL method's signature with scoped-prefix keys folded in |
|
|
3701
4133
|
| `MultipartFile` | _type_ | a `multipart` file field — `Blob \| FileDescriptor` |
|
|
3702
4134
|
| `FileDescriptor` | _type_ | a React Native / Expo file — `{ uri, name, type }` |
|
|
3703
4135
|
| `EndpointToolAnnotations` | _type_ | MCP behavioural hints on an endpoint (`readOnlyHint` / `destructiveHint` / `title`) |
|
|
@@ -3762,23 +4194,24 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
3762
4194
|
| `zodIssues` | function | a `ZodError` → structured `{ path, code, message }[]` — the machine-readable sibling of `formatZodError` |
|
|
3763
4195
|
| `ZodIssueSummary` | _type_ | one structured validation issue (`{ path, code, message }`) |
|
|
3764
4196
|
| `parseBody` | function | parse + Zod-validate a JSON body → `data` or `null` (no throw) |
|
|
3765
|
-
| `HandlerConfig` | _type_ | config for `createHandler`
|
|
4197
|
+
| `HandlerConfig` | _type_ | config for `createHandler`, including optional `maxJsonBodyBytes`; bound to `BunServer` on this entrypoint |
|
|
3766
4198
|
| `BunServerConfig` | _type_ | config for `createServer` (Bun) |
|
|
3767
4199
|
| `ServiceDef` | _type_ | the result of `implement` |
|
|
3768
4200
|
| `MethodDef` | _type_ | one resolved endpoint inside a service |
|
|
4201
|
+
| `OperationIdentity` | _type_ | path-free service/action/scope/method identity shared by contract and native tool operations |
|
|
3769
4202
|
| `Handlers` | _type_ | the typed handler map `implement` expects |
|
|
3770
4203
|
| `LifecycleHooks` | _type_ | `onRequest` / `beforeHandle` / `afterHandle` / `onError` |
|
|
3771
4204
|
| `RouteGroup` | _type_ | a prefixed group of services with its own hooks |
|
|
3772
|
-
| `RawRoute` | _type_ | a non-contract `Request → Response` route |
|
|
3773
|
-
| `RawRouteContext` | _type_ | the routing context a raw handler receives |
|
|
4205
|
+
| `RawRoute` | _type_ | a non-contract `Request → Response` route with a concrete `BunServer` context |
|
|
4206
|
+
| `RawRouteContext` | _type_ | the Bun-bound routing context a raw handler receives |
|
|
3774
4207
|
| `BunServer` | _type_ | the `Bun.serve` instance type |
|
|
3775
4208
|
| `ServerPassthrough` | _type_ | extra `Bun.serve` options |
|
|
3776
4209
|
| `StitchLogger` | _type_ | the custom-logger interface |
|
|
3777
4210
|
| `LoggingConfig` | _type_ | the `logging` object — `logger` / `format` / `skip` / `enrich` |
|
|
3778
4211
|
| `LogFormat` | _type_ | `'pretty'` or `'json'` — what the built-in formatter writes |
|
|
3779
4212
|
| `LogOutcome` | _type_ | how a request finished, as `enrich` sees it |
|
|
3780
|
-
| `FetchHandler` | _type_ | what `createHandler` returns |
|
|
3781
|
-
| `FetchComposition` | _type_ | the `wrapFetch` seam
|
|
4213
|
+
| `FetchHandler` | _type_ | what `createHandler` returns, bound to `BunServer` here |
|
|
4214
|
+
| `FetchComposition` | _type_ | the Bun-bound `wrapFetch` seam |
|
|
3782
4215
|
|
|
3783
4216
|
### Auth
|
|
3784
4217
|
|
|
@@ -3943,13 +4376,21 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
3943
4376
|
| `createToolkit` | function | context-typed tool mounts — [guide](../guide/cli.md#typed-context) |
|
|
3944
4377
|
| `mountViewFile` | function | a native multimodal "view file" MCP tool |
|
|
3945
4378
|
| `resolveMedia` | function | resolve a media reference for a tool result |
|
|
3946
|
-
| `validateMcpSchemas` | function |
|
|
4379
|
+
| `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)) |
|
|
3947
4380
|
| `listToolNames` | function | every mounted tool name with its `(service, method)` identity — for name-baseline snapshots — [guide](../guide/mcp-and-agents.md#pinning-tool-names--listtoolnames) |
|
|
3948
4381
|
| `McpHandlerConfig` | _type_ | config for `createMcpHandler` |
|
|
4382
|
+
| `McpSessionMode` | _type_ | `'stateless' \| 'stateful'`; HTTP defaults to request-isolated stateless mode |
|
|
3949
4383
|
| `StdioMcpServerConfig` | _type_ | config for `createStdioMcpServer` |
|
|
3950
4384
|
| `McpServerBuildConfig` | _type_ | shared config for `buildMcpServer` |
|
|
3951
4385
|
| `ImplementRemoteOptions` | _type_ | options for `implementRemote` |
|
|
3952
4386
|
| `McpMountConfig` | _type_ | config for `mountMcp` |
|
|
4387
|
+
| `McpSchemaValidationConfig` | _type_ | shared `{ policy, requireTypedProperties, allowUntyped, requirePortableFormats, allowFormats }` profile |
|
|
4388
|
+
| `ValidateMcpSchemasConfig` | _type_ | standalone validation profile plus `services`, `extend`, flattening and logger |
|
|
4389
|
+
| `NativeMcpRegistrar` | _type_ | protected `registerTool` plus explicit unprotected `rawServer` access |
|
|
4390
|
+
| `NativeMcpToolDefinition` | _type_ | native name, operation identity, Zod schemas and MCP-result handler |
|
|
4391
|
+
| `NativeMcpOperationIdentity` | _type_ | `{ serviceName, action, scope?, method, meta? }` for native lifecycle/audit |
|
|
4392
|
+
| `NativeMcpHandlerContext` | _type_ | runtime context with the definition's parsed native input |
|
|
4393
|
+
| `NativeMcpResult` | _type_ | MCP content result, with typed `structuredContent` when output is declared |
|
|
3953
4394
|
| `AgentMountConfig` | _type_ | config for `mountAgent` |
|
|
3954
4395
|
| `AgentContext` | _type_ | the context merged into agent tool handlers |
|
|
3955
4396
|
| `CliConfig` | _type_ | config for `createCli` |
|
|
@@ -3958,7 +4399,11 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
3958
4399
|
| `Toolkit` | _type_ | the context-pinned tool surface from `createToolkit` |
|
|
3959
4400
|
| `ToolExtend` | _type_ | extra-args extension for `mountMcp` / `mountAgent` |
|
|
3960
4401
|
| `ToolLifecycle` | _type_ | `beforeHandle` / `afterHandle` gate for tool calls — [guide](../guide/mcp-and-agents.md#guarding-tools--lifecycle) |
|
|
3961
|
-
| `
|
|
4402
|
+
| `ToolOperation` | _type_ | executable path-free operation shape shared by contract and framework-native runners |
|
|
4403
|
+
| `ToolCallHooks` | _type_ | object-shaped `beforeToolCall` / `afterToolCall` / `onToolError` observability hooks; the raw thrown value reaches the last two as `error` ([guide](../guide/observability.md#the-cause-behind-a-failed-tool-call)) |
|
|
4404
|
+
| `BeforeToolCallOptions` | _type_ | `{ toolName, args, context, endpoint }` passed before execution |
|
|
4405
|
+
| `AfterToolCallOptions` | _type_ | completed call options plus `{ result, durationMs, error? }` |
|
|
4406
|
+
| `ToolErrorOptions` | _type_ | `{ toolName, error, context, endpoint }` for a thrown handler-path value |
|
|
3962
4407
|
| `ErrorHintFn` | _type_ | `(toolName, errorCode) => string \| null` — a per-tool recovery hint, shared by every mount |
|
|
3963
4408
|
| `ToolResult` | _type_ | the result of one tool call |
|
|
3964
4409
|
| `ToolCallContext` | _type_ | the context every tool hook receives — `{ source }` plus whatever the mount's `context` added |
|
|
@@ -3967,6 +4412,9 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
3967
4412
|
| `CollectToolsConfig` | _type_ | options for `collectTools` |
|
|
3968
4413
|
| `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)) |
|
|
3969
4414
|
| `UntypedProperty` | _type_ | one such property — `{ path, description? }` |
|
|
4415
|
+
| `findNonPortableFormats` | function | deep finder for formats outside the portable MCP/AJV baseline |
|
|
4416
|
+
| `NonPortableFormat` | _type_ | one `{ path, format }` portability finding |
|
|
4417
|
+
| `PORTABLE_JSON_SCHEMA_FORMATS` | constant | portable-format baseline used by MCP validation |
|
|
3970
4418
|
| `ToolNameEntry` | _type_ | one `listToolNames` row — `{ name, service, method, transports }` |
|
|
3971
4419
|
| `IncompatibleSchemaPolicy` | _type_ | `'throw' \| 'skip' \| 'warn'` |
|
|
3972
4420
|
| `McpMediaContent` | _type_ | a multimodal MCP content item |
|
|
@@ -4033,9 +4481,9 @@ Advanced building blocks — the shared machinery the mounts are built on.
|
|
|
4033
4481
|
| `TransportSummary` | _type_ | the result of `summarizeTransports` |
|
|
4034
4482
|
| `TransportCounts` | _type_ | per-transport counts (`{ HTTP, MCP, AGENT, CLI }`) |
|
|
4035
4483
|
| `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments |
|
|
4036
|
-
| `
|
|
4037
|
-
| `
|
|
4038
|
-
| `MountableTool` | _type_ | one
|
|
4484
|
+
| `flattenToolJsonSchema` | function | project structurally identifiable discriminated unions in a JSON Schema document into conservative object joins; never executes validation |
|
|
4485
|
+
| `ToolPresentationSchema` | _type_ | immutable model-facing JSON Schema document shared by tool transports |
|
|
4486
|
+
| `MountableTool` | _type_ | one operation with separate executable CLI argument schema and model-facing presentation schema |
|
|
4039
4487
|
| `ToolManifestEntry` | _type_ | one `buildToolManifest` row |
|
|
4040
4488
|
|
|
4041
4489
|
---
|
|
@@ -4051,11 +4499,12 @@ runtime-agnostic pieces of `stitchkit/server` and the error helpers.
|
|
|
4051
4499
|
|--------|------|---------|
|
|
4052
4500
|
| `serveNode` | function | build the router and start a Node HTTP server (via `srvx`) |
|
|
4053
4501
|
| `createHandler` | function | the router as a bare `(req) => Response` (same as `/server`) |
|
|
4054
|
-
| `createSocketIOServer` | function | the typed Socket.IO server (
|
|
4502
|
+
| `createSocketIOServer` | function | the typed Node Socket.IO server (`io` + `attach`; no Bun engine declarations) |
|
|
4055
4503
|
| `implement` / `createImplement` | function | bind a contract to typed handlers (same as `/server`) |
|
|
4056
4504
|
| `NodeServerConfig` | _type_ | config for `serveNode` |
|
|
4057
4505
|
| `NodeServerHandle` | _type_ | the `serveNode` handle (`{ port, stop }`) |
|
|
4058
|
-
| `HandlerConfig` / `ServiceDef` / `RawRoute` / `RawRouteContext`
|
|
4506
|
+
| `HandlerConfig` / `ServiceDef` / `RawRoute` / `RawRouteContext` | _type_ | runtime-neutral handler types; raw routes default their host server to `unknown` |
|
|
4507
|
+
| `SocketIOServerConfig` / `SocketIOServerHandle` | _type_ | shared config and the Node-only `{ io, attach }` handle |
|
|
4059
4508
|
| `AppError` + `appError` / `badRequest` / `unauthorized` / `forbidden` / `notFound` / `conflict` / `rateLimited` | — | error helpers (same as `/contract`) |
|
|
4060
4509
|
|
|
4061
4510
|
---
|