stitchkit 0.36.1 → 0.37.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/http.d.ts +2 -2
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/contract/define.d.ts +28 -2
- package/dist/contract/define.d.ts.map +1 -1
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/index.js +1 -1
- package/dist/{index-dvrn81q4.js → index-4pvtq6h2.js} +83 -34
- package/dist/{index-0ed3bx43.js → index-frfyw9fa.js} +2 -2
- package/dist/{index-pmftwk2a.js → index-g3jrbd0z.js} +26 -0
- package/dist/{index-82gncajj.js → index-s6yhmg1k.js} +555 -337
- package/dist/{index-bx49hskg.js → index-xq45akyd.js} +1 -1
- package/dist/index.js +1 -1
- 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 +25 -5
- 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/request-body.d.ts +4 -0
- package/dist/server/request-body.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 +54 -49
- 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 +357 -95
- package/llms-full.txt +371 -77
- package/package.json +14 -11
- /package/dist/{index-17rdjw68.js → index-e497hxcy.js} +0 -0
package/llms-full.txt
CHANGED
|
@@ -239,10 +239,12 @@ 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) |
|
|
246
248
|
| `contentType` | no | documented response media type of a `rawResponse` endpoint (OpenAPI only) |
|
|
247
249
|
|
|
248
250
|
## `params` vs `input` vs `output`
|
|
@@ -617,6 +619,7 @@ server. See [Testing & deployment](./testing-and-deployment.md).
|
|
|
617
619
|
| `scopePrefixes` | `scope → path prefix` map — mount `services` by `service.scope` (see below) |
|
|
618
620
|
| `rawRoutes` | non-contract routes (see below) |
|
|
619
621
|
| `maxUploadBytes` | default multipart upload cap (bytes); per-route `EndpointDef.maxUploadBytes` overrides |
|
|
622
|
+
| `maxJsonBodyBytes` | optional JSON body cap (bytes); per-route value overrides; unset preserves existing behaviour |
|
|
620
623
|
| `port` / `hostname` | listen address — port defaults to `3000` |
|
|
621
624
|
| `cors` | CORS policy — `{ origin, credentials, methods, headers, exposeHeaders }` |
|
|
622
625
|
| `hooks` | lifecycle hooks (see below) |
|
|
@@ -682,10 +685,18 @@ is not involved.
|
|
|
682
685
|
way to see what `enrich` and the request context actually put on the record;
|
|
683
686
|
changing `NODE_ENV` is not needed, and neither is deploying.
|
|
684
687
|
|
|
685
|
-
Three more things about `enrich`:
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
688
|
+
Three more things about `enrich`:
|
|
689
|
+
|
|
690
|
+
- It runs at close, when the request body is already consumed.
|
|
691
|
+
- The framework owns `traceId`, `method`, `path`, `status`, `durationMs`,
|
|
692
|
+
`errorCode` and `ip` in both structured sinks; the built-in JSON line also
|
|
693
|
+
owns `ts`, `level` and `msg`. Its value wins a collision, and a discarded key
|
|
694
|
+
warns once per handler rather than disappearing silently.
|
|
695
|
+
- `errorCode` has one outcome-aware exception: enrichment may supply it for a
|
|
696
|
+
`4xx`/`5xx` response when the framework derived no code, such as an error
|
|
697
|
+
`Response` returned by a raw route. It cannot add one to a `2xx`/`3xx` or
|
|
698
|
+
replace a framework-derived code.
|
|
699
|
+
- A throw in `skip` or `enrich` is swallowed; neither can fail a request.
|
|
689
700
|
|
|
690
701
|
With an observability context active, the structured line also carries `userId`,
|
|
691
702
|
`serviceName`, `action` and `dimensions` for free. See
|
|
@@ -804,6 +815,40 @@ createServer({
|
|
|
804
815
|
Hooks see `RuntimeContext` (loose types); handlers see `HandlerContext` (typed).
|
|
805
816
|
That split is deliberate — see [ADR 0003](../decisions/0003-two-context-types.md).
|
|
806
817
|
|
|
818
|
+
## Signed JSON webhooks
|
|
819
|
+
|
|
820
|
+
A provider signs the original JSON text, not `JSON.stringify(ctx.input)`.
|
|
821
|
+
Declare `rawBody: true` to retain the same decoded text the router reads while
|
|
822
|
+
keeping normal Zod validation:
|
|
823
|
+
|
|
824
|
+
```ts
|
|
825
|
+
const webhooks = defineContract(
|
|
826
|
+
{ prefix: 'webhooks' },
|
|
827
|
+
{
|
|
828
|
+
receive: {
|
|
829
|
+
method: 'POST', path: '/provider', desc: 'Receive a signed event',
|
|
830
|
+
rawBody: true,
|
|
831
|
+
maxJsonBodyBytes: 256 * 1024,
|
|
832
|
+
input: ProviderEventSchema,
|
|
833
|
+
output: z.object({ accepted: z.boolean() }),
|
|
834
|
+
},
|
|
835
|
+
},
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
receive: async (ctx) => {
|
|
839
|
+
const signature = ctx.req.headers.get('x-signature')
|
|
840
|
+
await verifyWebhookHmac(ctx.rawBody, signature) // guaranteed string
|
|
841
|
+
return { accepted: true }
|
|
842
|
+
}
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
This endpoint is HTTP-only and cannot be multipart or exposed as a tool. The
|
|
846
|
+
router sets `ctx.rawBody` before JSON/Zod validation, so `onError` can inspect it
|
|
847
|
+
after malformed JSON or a schema failure. Endpoints without `rawBody: true` do
|
|
848
|
+
not retain the text. `maxJsonBodyBytes` may also be set once on `createServer` /
|
|
849
|
+
`createHandler`; a route value wins. Both limits are opt-in and abort an
|
|
850
|
+
oversized stream before it is fully buffered. → ADR 0051
|
|
851
|
+
|
|
807
852
|
## Raw-response endpoints
|
|
808
853
|
|
|
809
854
|
An endpoint that answers with **bytes rather than data** — a PDF download, a
|
|
@@ -853,8 +898,9 @@ them — see [`cors.exposeHeaders`](#serving-files--range-requests).
|
|
|
853
898
|
code. A raw-response *endpoint* stays in the contract: only its response is
|
|
854
899
|
raw — it is still routed, gated, typed and documented like every other endpoint.
|
|
855
900
|
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
|
-
|
|
901
|
+
client — which is what you want for an OAuth redirect or a non-JSON webhook. A
|
|
902
|
+
signed JSON webhook can stay validated through
|
|
903
|
+
[`rawBody: true`](#signed-json-webhooks).
|
|
858
904
|
|
|
859
905
|
⚠️ **Delete the old raw route when you move an endpoint into the contract.** Raw
|
|
860
906
|
routes are matched **first**, so a leftover one keeps serving the bytes and the
|
|
@@ -1432,9 +1478,8 @@ and after, compare.
|
|
|
1432
1478
|
## MCP — `createMcpHandler`
|
|
1433
1479
|
|
|
1434
1480
|
`createMcpHandler` builds a complete Streamable-HTTP MCP server as a single
|
|
1435
|
-
`Request → Response` handler. It owns the
|
|
1436
|
-
|
|
1437
|
-
`@modelcontextprotocol/sdk` itself.
|
|
1481
|
+
`Request → Response` handler. It owns the SDK server and transport lifecycle, so
|
|
1482
|
+
your app never imports `@modelcontextprotocol/sdk` itself.
|
|
1438
1483
|
|
|
1439
1484
|
```ts
|
|
1440
1485
|
import { createMcpHandler } from 'stitchkit/tools'
|
|
@@ -1465,14 +1510,34 @@ createServer({
|
|
|
1465
1510
|
| `context` | `(auth) => {…}` — values merged into every tool handler's `ctx` |
|
|
1466
1511
|
| `lifecycle` | `beforeHandle` / `afterHandle` — the tool-side auth gate (see below) |
|
|
1467
1512
|
| `hooks` | tool-call observability hooks — `afterToolCall` fires on every result |
|
|
1468
|
-
| `
|
|
1513
|
+
| `extend` | extra advertised arguments resolved into handler context |
|
|
1514
|
+
| `schemaValidation` | compatibility policy, typed-property guard and portable-format guard |
|
|
1469
1515
|
| `logger` | a `StitchLogger` for the `'warn'` policy |
|
|
1470
|
-
| `nativeTools` | `(
|
|
1516
|
+
| `nativeTools` | `({ registerTool, rawServer }, auth) => …` — protected native registration plus an explicit raw SDK escape hatch |
|
|
1517
|
+
| `resources` | MCP Apps `ui://` resources mounted on every server |
|
|
1471
1518
|
| `instructions` | a short host-facing usage hint, surfaced to MCP tool-search |
|
|
1519
|
+
| `coerceJsonArgs` | coerce JSON-stringified object/array arguments (default `true`) |
|
|
1520
|
+
| `flattenUnionInput` | advertise discriminated unions as one object (default `false`) |
|
|
1521
|
+
| `errorHint` | add a project-owned hint to failed tool results |
|
|
1522
|
+
| `onOutputStrip` | observe output keys removed by contract validation |
|
|
1523
|
+
| `protectedResource` | RFC 9728 metadata used by HTTP `401` responses |
|
|
1524
|
+
| `sessionMode` | `'stateless'` (default) or explicit `'stateful'` session/SSE continuity |
|
|
1472
1525
|
|
|
1473
1526
|
`services`, `context` and `nativeTools` all receive the resolved identity, so a
|
|
1474
1527
|
tenant can be shown only its own tools and every handler can read `ctx.tenantId`.
|
|
1475
1528
|
|
|
1529
|
+
### Stateless by default; stateful only when required
|
|
1530
|
+
|
|
1531
|
+
The default `sessionMode: 'stateless'` creates a fresh SDK server, transport,
|
|
1532
|
+
resolved auth/context and runner for each HTTP request. Static contract schemas
|
|
1533
|
+
are still prepared once when the handler is constructed. There is no session
|
|
1534
|
+
map, event store, sweep timer or `Mcp-Session-Id`, so process replacement and
|
|
1535
|
+
load balancing cannot strand a client on an in-memory session.
|
|
1536
|
+
|
|
1537
|
+
Opt into `sessionMode: 'stateful'` only when the client needs server-initiated
|
|
1538
|
+
messages, cross-request progress or resumable SSE. That mode issues a server
|
|
1539
|
+
session id and retains the bounded session/event stores until idle expiry.
|
|
1540
|
+
|
|
1476
1541
|
### Guarding tools — `lifecycle`
|
|
1477
1542
|
|
|
1478
1543
|
A tool call runs the same handler an HTTP request would. `lifecycle` makes it
|
|
@@ -1490,15 +1555,20 @@ Without it, a tool call bypasses the HTTP `beforeHandle` — the contract's
|
|
|
1490
1555
|
`buildMcpServer` take `lifecycle` too.
|
|
1491
1556
|
|
|
1492
1557
|
The observability `hooks` are symmetric with the HTTP side too: `beforeToolCall`
|
|
1493
|
-
and `afterToolCall` receive the resolved **`MethodDef`** as
|
|
1558
|
+
and `afterToolCall` receive the resolved **`MethodDef`** as `endpoint` —
|
|
1494
1559
|
the tool-side twin of `afterHandle(ctx, result, endpoint)`. Read
|
|
1495
1560
|
`endpoint.serviceName` / `.key` / `.meta` directly for an audit row; you do not
|
|
1496
1561
|
need to rebuild a `toolName → identity` map:
|
|
1497
1562
|
|
|
1498
1563
|
```ts
|
|
1499
1564
|
hooks: {
|
|
1500
|
-
afterToolCall: (
|
|
1501
|
-
audit({
|
|
1565
|
+
afterToolCall: ({ result, durationMs, endpoint }) => {
|
|
1566
|
+
audit({
|
|
1567
|
+
service: endpoint.serviceName,
|
|
1568
|
+
action: endpoint.key,
|
|
1569
|
+
ok: result.ok,
|
|
1570
|
+
ms: durationMs,
|
|
1571
|
+
})
|
|
1502
1572
|
},
|
|
1503
1573
|
}
|
|
1504
1574
|
```
|
|
@@ -1509,10 +1579,10 @@ hooks: {
|
|
|
1509
1579
|
> has no identity and **fails closed**. See
|
|
1510
1580
|
> [Auth on the tool surface](./auth-and-errors.md#auth-on-the-tool-surface--resolvefromcontext).
|
|
1511
1581
|
|
|
1512
|
-
###
|
|
1582
|
+
### MCP schema validation profile
|
|
1513
1583
|
|
|
1514
1584
|
A contract schema that JSON Schema cannot represent (a `z.date()`, a `z.map()`)
|
|
1515
|
-
cannot become a tool. `
|
|
1585
|
+
cannot become a tool. `schemaValidation.policy` decides what happens:
|
|
1516
1586
|
|
|
1517
1587
|
- `'throw'` (default) — fail the build, listing every offending tool. A static
|
|
1518
1588
|
`services` array is checked when `createMcpHandler` is constructed, so a bad
|
|
@@ -1521,9 +1591,16 @@ cannot become a tool. `onIncompatibleSchema` decides what happens:
|
|
|
1521
1591
|
- `'warn'` — log through `logger` and drop the tool.
|
|
1522
1592
|
- `'skip'` — drop the tool silently.
|
|
1523
1593
|
|
|
1524
|
-
`validateMcpSchemas(services)` runs the same check on its own — useful in a
|
|
1594
|
+
`validateMcpSchemas({ services })` runs the same check on its own — useful in a
|
|
1525
1595
|
startup assertion or a test.
|
|
1526
1596
|
|
|
1597
|
+
For a static `services` array, collection, schema conversion and every enabled
|
|
1598
|
+
validation guard run once when the handler is created. Each HTTP request or
|
|
1599
|
+
stateful session still receives a fresh `McpServer`, runner, context and native
|
|
1600
|
+
registration over that immutable prepared surface. A `services(auth)` factory
|
|
1601
|
+
is deliberately prepared after resolving each identity because its tool set may
|
|
1602
|
+
change by tenant.
|
|
1603
|
+
|
|
1527
1604
|
#### Is every property actually usable by a model?
|
|
1528
1605
|
|
|
1529
1606
|
A tool schema is the only instruction a model gets about the shape of its
|
|
@@ -1533,17 +1610,30 @@ mount succeeds, the tool is advertised, and the model then guesses, retrying the
|
|
|
1533
1610
|
same wrong guess because the error does not say what the right one would be.
|
|
1534
1611
|
|
|
1535
1612
|
```ts
|
|
1536
|
-
validateMcpSchemas(
|
|
1537
|
-
|
|
1613
|
+
validateMcpSchemas({
|
|
1614
|
+
services,
|
|
1615
|
+
policy: 'throw',
|
|
1616
|
+
logger,
|
|
1617
|
+
extend,
|
|
1618
|
+
flattenUnionInput: true,
|
|
1538
1619
|
requireTypedProperties: true,
|
|
1539
1620
|
allowUntyped: ['docs_create.payload'], // deliberately free-form
|
|
1621
|
+
requirePortableFormats: true,
|
|
1622
|
+
allowFormats: [],
|
|
1540
1623
|
})
|
|
1541
1624
|
```
|
|
1542
1625
|
|
|
1543
1626
|
Off by default, because a contract may legitimately declare `z.unknown()`.
|
|
1544
1627
|
`allowUntyped` takes dotted `tool.property` paths — an entry there is a decision,
|
|
1545
|
-
anything else is a finding.
|
|
1546
|
-
|
|
1628
|
+
anything else is a finding. On `createMcpHandler`, put the policy under
|
|
1629
|
+
`schemaValidation`; the handler supplies its real `extend` and
|
|
1630
|
+
`flattenUnionInput`, so the check cannot vet a different document from the one
|
|
1631
|
+
advertised.
|
|
1632
|
+
|
|
1633
|
+
`requirePortableFormats` rejects custom `format` values common MCP/AJV clients
|
|
1634
|
+
do not know, such as the `cuid2` emitted by `z.cuid2()`. Use a portable
|
|
1635
|
+
schema/pattern, or list it in `allowFormats` only when every client supports it.
|
|
1636
|
+
stitchkit never removes or rewrites the keyword.
|
|
1547
1637
|
|
|
1548
1638
|
`findUntypedProperties(jsonSchema)` is the same walk, exported on its own if you
|
|
1549
1639
|
want to assert on a schema you built elsewhere.
|
|
@@ -1566,25 +1656,19 @@ A field that is a number in every variant is advertised as a number, not as a
|
|
|
1566
1656
|
bare description. Only genuinely different kinds (a string in one variant, a
|
|
1567
1657
|
number in another) fall back to unconstrained. → ADR 0044
|
|
1568
1658
|
|
|
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.
|
|
1659
|
+
It is **deep** because the projection walks the generated JSON Schema document,
|
|
1660
|
+
including objects, arrays, tuples and schema-definition nodes. Structurally
|
|
1661
|
+
identifiable discriminated object unions are flattened wherever they occur.
|
|
1662
|
+
Plain unions and unions hidden behind unresolved external references remain
|
|
1663
|
+
unions because Stitchkit cannot soundly invent a discriminator.
|
|
1664
|
+
|
|
1665
|
+
The flattened form is **lossy but never executable**. Per-variant refinements
|
|
1666
|
+
and incompatible constraints are widened in the presentation document; the
|
|
1667
|
+
original Zod contract enforces them exactly once inside `executeToolMethod`.
|
|
1668
|
+
MCP and AI adapters forward the raw argument object unchanged, so defaults,
|
|
1669
|
+
coercions and transforms cannot run before Stitchkit. Strict violations return
|
|
1670
|
+
the normal `VALIDATION_ERROR` tool envelope and fire `beforeToolCall` /
|
|
1671
|
+
`afterToolCall`. → [ADR 0050](../decisions/0050-presentation-schema-is-not-a-parser.md).
|
|
1588
1672
|
|
|
1589
1673
|
## `mountMcp`
|
|
1590
1674
|
|
|
@@ -1820,10 +1904,60 @@ so one handler serves both surfaces. Pair `extend` with `lifecycle` (your
|
|
|
1820
1904
|
|
|
1821
1905
|
## Native multimodal tools
|
|
1822
1906
|
|
|
1823
|
-
Contract tools return JSON.
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1907
|
+
Contract tools return JSON. A native tool can return MCP text/image/audio/
|
|
1908
|
+
resource content directly while still using stitchkit's input/output validation,
|
|
1909
|
+
isolated per-call context, lifecycle/RBAC and tool hooks:
|
|
1910
|
+
|
|
1911
|
+
```ts
|
|
1912
|
+
import { createMcpHandler } from 'stitchkit/tools'
|
|
1913
|
+
import { z } from 'zod'
|
|
1914
|
+
|
|
1915
|
+
const handleMcp = createMcpHandler({
|
|
1916
|
+
serverInfo: { name: 'my-app', version: '1.0.0' },
|
|
1917
|
+
auth,
|
|
1918
|
+
services: [service],
|
|
1919
|
+
lifecycle: { beforeHandle: authHook },
|
|
1920
|
+
hooks: audit.toolCall,
|
|
1921
|
+
nativeTools: ({ registerTool }, identity) => {
|
|
1922
|
+
registerTool({
|
|
1923
|
+
name: 'render_preview',
|
|
1924
|
+
description: 'Render and inspect a preview',
|
|
1925
|
+
identity: {
|
|
1926
|
+
serviceName: 'mediaTools',
|
|
1927
|
+
action: 'renderPreview',
|
|
1928
|
+
scope: 'admin',
|
|
1929
|
+
method: 'POST',
|
|
1930
|
+
},
|
|
1931
|
+
input: z.object({ prompt: z.string() }),
|
|
1932
|
+
output: z.object({ assetId: z.string() }),
|
|
1933
|
+
handler: async ({ input, traceId }) => ({
|
|
1934
|
+
content: [
|
|
1935
|
+
{ type: 'image', data: await renderBase64(input.prompt), mimeType: 'image/png' },
|
|
1936
|
+
{ type: 'text', text: `trace: ${traceId}` },
|
|
1937
|
+
],
|
|
1938
|
+
structuredContent: { assetId: await saveAsset(identity) },
|
|
1939
|
+
}),
|
|
1940
|
+
})
|
|
1941
|
+
},
|
|
1942
|
+
})
|
|
1943
|
+
```
|
|
1944
|
+
|
|
1945
|
+
The configured identity becomes the hook/lifecycle `OperationIdentity` and the
|
|
1946
|
+
tool `RequestEvent` (`serviceName`, `action`, `httpMethod`). A native operation
|
|
1947
|
+
has no HTTP route, so no fake `path` is added to that identity. If `output` is
|
|
1948
|
+
declared, stitchkit parses `structuredContent` with it after `afterHandle`; all
|
|
1949
|
+
other MCP fields and content blocks are preserved.
|
|
1950
|
+
|
|
1951
|
+
The MCP registration uses an identity carrier: the SDK advertises the compiled
|
|
1952
|
+
JSON Schema but forwards the raw object into Stitchkit. Input failures therefore
|
|
1953
|
+
use the same validation, lifecycle and hook path as contract tools.
|
|
1954
|
+
|
|
1955
|
+
### Explicit raw SDK registration
|
|
1956
|
+
|
|
1957
|
+
`rawServer` is deliberately named as an escape hatch. A tool registered there
|
|
1958
|
+
does **not** receive stitchkit schema policy, lifecycle, per-call context or
|
|
1959
|
+
hooks. The built-in `mountViewFile` helper remains raw for callers that choose
|
|
1960
|
+
that boundary; it fetches media with SSRF and path-traversal defenses:
|
|
1827
1961
|
|
|
1828
1962
|
```ts
|
|
1829
1963
|
import { createMcpHandler, mountViewFile } from 'stitchkit/tools'
|
|
@@ -1832,10 +1966,15 @@ const handleMcp = createMcpHandler({
|
|
|
1832
1966
|
serverInfo: { name: 'my-app', version: '1.0.0' },
|
|
1833
1967
|
auth,
|
|
1834
1968
|
services: [service],
|
|
1835
|
-
nativeTools: (
|
|
1969
|
+
nativeTools: ({ rawServer }) =>
|
|
1970
|
+
mountViewFile(rawServer, { baseDir: '/srv/uploads' }),
|
|
1836
1971
|
})
|
|
1837
1972
|
```
|
|
1838
1973
|
|
|
1974
|
+
Use raw registration only when opting out is intentional. For a protected
|
|
1975
|
+
`view_file`, define it through `registerTool` and call the exported
|
|
1976
|
+
`resolveMedia` core from its handler.
|
|
1977
|
+
|
|
1839
1978
|
## Logging tool calls — `createToolLogger`
|
|
1840
1979
|
|
|
1841
1980
|
Every tool mount fires an `afterToolCall` hook. `createToolLogger` is a ready
|
|
@@ -2946,7 +3085,16 @@ either way, so your sink reads it as a column instead of re-parsing the path:
|
|
|
2946
3085
|
> same hooks object is assignable to `ToolLifecycle`, so this recipe is the one
|
|
2947
3086
|
> people apply to tools — and since ADR 0045 each tool call runs in its own
|
|
2948
3087
|
> context. Read the value off the tool row (`event.toolName != null`); both rows
|
|
2949
|
-
> carry the same `traceId
|
|
3088
|
+
> carry the same `traceId`, so recovering the enclosing request is one filter:
|
|
3089
|
+
>
|
|
3090
|
+
> ```bash
|
|
3091
|
+
> jq -s '[.[] | select(.dimensions.botId == "B7") | .traceId] as $t
|
|
3092
|
+
> | .[] | select(.traceId | IN($t[]))' audit.jsonl
|
|
3093
|
+
> ```
|
|
3094
|
+
>
|
|
3095
|
+
> Worth checking your **runbooks** as well as your code when this lands: a
|
|
3096
|
+
> dimension filter written against request rows keeps parsing and starts
|
|
3097
|
+
> returning nothing.
|
|
2950
3098
|
|
|
2951
3099
|
```ts
|
|
2952
3100
|
// beforeHandle (success) and onError (failure) alike:
|
|
@@ -3076,11 +3224,11 @@ metric, a custom log line, anything that is not a full audit row.
|
|
|
3076
3224
|
|---------|------|-------|
|
|
3077
3225
|
| HTTP | `LifecycleHooks.afterHandle` / `onError` | after each HTTP request |
|
|
3078
3226
|
| MCP & agent tools | `ToolCallHooks.afterToolCall` | after each tool call |
|
|
3079
|
-
| MCP & agent tools | `ToolCallHooks.onToolError` | when
|
|
3227
|
+
| MCP & agent tools | `ToolCallHooks.onToolError` | when executable parsing, extension resolution, lifecycle or the handler throws |
|
|
3080
3228
|
|
|
3081
3229
|
`afterHandle(ctx, result, endpoint)` runs after a handler returns;
|
|
3082
|
-
`onError(ctx, error, endpoint)` when one throws. `afterToolCall(
|
|
3083
|
-
|
|
3230
|
+
`onError(ctx, error, endpoint)` when one throws. `afterToolCall(options)` runs
|
|
3231
|
+
after every tool call —
|
|
3084
3232
|
success and error alike — carrying the tool name, the arguments, the result, the
|
|
3085
3233
|
duration, the call context, the endpoint identity, and (only when the call failed
|
|
3086
3234
|
by throwing) the raw thrown value.
|
|
@@ -3089,7 +3237,7 @@ by throwing) the raw thrown value.
|
|
|
3089
3237
|
createMcpHandler({
|
|
3090
3238
|
serverInfo, auth, services,
|
|
3091
3239
|
hooks: {
|
|
3092
|
-
afterToolCall: (toolName,
|
|
3240
|
+
afterToolCall: ({ toolName, result, durationMs }) => {
|
|
3093
3241
|
metrics.timing(`tool.${toolName}`, durationMs, { ok: String(result.ok) })
|
|
3094
3242
|
},
|
|
3095
3243
|
},
|
|
@@ -3115,7 +3263,7 @@ normalisation, stack and `cause` intact:
|
|
|
3115
3263
|
createMcpHandler({
|
|
3116
3264
|
serverInfo, auth, services,
|
|
3117
3265
|
hooks: {
|
|
3118
|
-
onToolError: (toolName, error,
|
|
3266
|
+
onToolError: ({ toolName, error, endpoint }) => {
|
|
3119
3267
|
reportToolFailure({
|
|
3120
3268
|
tool: toolName,
|
|
3121
3269
|
action: endpoint.key,
|
|
@@ -3127,13 +3275,12 @@ createMcpHandler({
|
|
|
3127
3275
|
})
|
|
3128
3276
|
```
|
|
3129
3277
|
|
|
3130
|
-
It fires for a throw from
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
`beforeToolCall` rejection: each
|
|
3135
|
-
`ToolResult
|
|
3136
|
-
double-logging.
|
|
3278
|
+
It fires for a throw from executable params/input/`ToolExtend` parsing,
|
|
3279
|
+
extension resolution, `beforeHandle`, the handler or `afterHandle` — every span
|
|
3280
|
+
where information is destroyed — and runs **before** `afterToolCall`. It does
|
|
3281
|
+
not fire for an ordinary validation result, an output-schema mismatch or a
|
|
3282
|
+
`beforeToolCall` rejection: each is already described in full by its
|
|
3283
|
+
`ToolResult`.
|
|
3137
3284
|
|
|
3138
3285
|
It observes, it does not handle: the tool envelope is always the framework's, a
|
|
3139
3286
|
returned value is ignored, and a throw from the hook itself is reported to
|
|
@@ -3155,11 +3302,11 @@ either — the call is simply not where that helper belongs.)
|
|
|
3155
3302
|
### One row that names the cause
|
|
3156
3303
|
|
|
3157
3304
|
You do not need to correlate the two hooks yourself. `afterToolCall` receives the
|
|
3158
|
-
same raw value as
|
|
3305
|
+
same raw value as the named `error` field, so one hook can build one record:
|
|
3159
3306
|
|
|
3160
3307
|
```ts
|
|
3161
3308
|
hooks: {
|
|
3162
|
-
afterToolCall: (toolName,
|
|
3309
|
+
afterToolCall: ({ toolName, result, durationMs, endpoint, error }) => {
|
|
3163
3310
|
void writeRow({ toolName, result, durationMs, cause: error, endpoint })
|
|
3164
3311
|
},
|
|
3165
3312
|
}
|
|
@@ -3167,8 +3314,8 @@ hooks: {
|
|
|
3167
3314
|
|
|
3168
3315
|
`error` is present only when the call failed by **throwing** — a
|
|
3169
3316
|
validation failure or a `beforeToolCall` rejection leaves it `undefined`, because
|
|
3170
|
-
neither ever had a raw value to lose.
|
|
3171
|
-
|
|
3317
|
+
neither ever had a raw value to lose. Consumers destructure only the fields they
|
|
3318
|
+
use; future optional fields do not change callback arity.
|
|
3172
3319
|
|
|
3173
3320
|
`createAuditHook` uses it already. Where the envelope was scrubbed to
|
|
3174
3321
|
`INTERNAL_SERVER_ERROR`, the row's `errorMessage` becomes the real message
|
|
@@ -3186,11 +3333,13 @@ sink of your own (a tracker, a stack, an alert), `afterToolCall` for the record.
|
|
|
3186
3333
|
`createAuditHook` already keys every event by **service** and **action**
|
|
3187
3334
|
(`event.serviceName` / `event.action`, → ADR 0029) — reach for the raw hook only
|
|
3188
3335
|
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`
|
|
3336
|
+
that, read the endpoint identity off the `OperationIdentity` the tool hook
|
|
3337
|
+
receives — `endpoint.serviceName` and `endpoint.key` (the endpoint key or native
|
|
3338
|
+
action,
|
|
3191
3339
|
e.g. `updatePartial`). They are stable and always present (→ ADR 0022); the action
|
|
3192
3340
|
is not in the URL and `toolName` is absent on HTTP-only endpoints, so this is the
|
|
3193
|
-
only reliable pair.
|
|
3341
|
+
only reliable pair. Contract operations are full `MethodDef` values; native MCP
|
|
3342
|
+
operations intentionally have no HTTP `path`. `afterHandle` also gives you the handler `result` — so it is
|
|
3194
3343
|
the home for a rich mutation audit that records output:
|
|
3195
3344
|
|
|
3196
3345
|
```ts
|
|
@@ -3324,6 +3473,10 @@ serveNode({
|
|
|
3324
3473
|
|
|
3325
3474
|
Notes for a Node host:
|
|
3326
3475
|
|
|
3476
|
+
- `stitchkit/node` declarations do not require `@types/bun`. Runtime-neutral
|
|
3477
|
+
raw routes use `RawRoute<TServer = unknown>`; supply a host server generic
|
|
3478
|
+
only when an embedding adapter passes one to `createHandler`.
|
|
3479
|
+
|
|
3327
3480
|
- Add **`@types/bun`** as a dev dependency — it is an optional peer that types the
|
|
3328
3481
|
shared `stitchkit/server` surface (without it `tsc` reports a missing `Bun`
|
|
3329
3482
|
namespace).
|
|
@@ -3561,6 +3714,130 @@ current one *up to* your target, and apply each snippet.
|
|
|
3561
3714
|
(`STITCH_ERROR_STATUS`, `serveFile`, `scopePrefixes`, `afterToolCall`'s
|
|
3562
3715
|
`MethodDef`, `maxUploadBytes`) are available to adopt, not required.
|
|
3563
3716
|
|
|
3717
|
+
## The 0.37 migration
|
|
3718
|
+
|
|
3719
|
+
Tool presentation is no longer an executable Zod parser. Replace the removed
|
|
3720
|
+
flatten helpers with the JSON Schema compiler, and choose the explicit
|
|
3721
|
+
`MountableTool` surface when using the advanced collection API:
|
|
3722
|
+
|
|
3723
|
+
```ts
|
|
3724
|
+
// before
|
|
3725
|
+
const flat = flattenUnionsDeep(zodSchema)
|
|
3726
|
+
mountable.schema
|
|
3727
|
+
|
|
3728
|
+
// after
|
|
3729
|
+
const flat = flattenToolJsonSchema(
|
|
3730
|
+
z.toJSONSchema(zodSchema, { target: 'draft-07', io: 'input' }),
|
|
3731
|
+
)
|
|
3732
|
+
mountable.presentationSchema // model/MCP/manifest JSON Schema
|
|
3733
|
+
mountable.argumentSchema // executable CLI argument adapter only
|
|
3734
|
+
```
|
|
3735
|
+
|
|
3736
|
+
Contract and native handlers keep their original Zod schemas; MCP and agent SDK
|
|
3737
|
+
adapters now forward raw arguments so defaults, coercions, refinements and
|
|
3738
|
+
transforms execute exactly once inside Stitchkit. There are no compatibility
|
|
3739
|
+
exports for `flattenDiscriminatedUnion`, `flattenUnionsDeep` or
|
|
3740
|
+
`MountableTool.schema`. → ADR 0050
|
|
3741
|
+
|
|
3742
|
+
Tool-call hooks now take one options object. Migrate all three callbacks; there
|
|
3743
|
+
are no positional overloads:
|
|
3744
|
+
|
|
3745
|
+
```ts
|
|
3746
|
+
// before
|
|
3747
|
+
beforeToolCall: (toolName, args, context, endpoint) => {}
|
|
3748
|
+
afterToolCall: (toolName, args, result, durationMs, context, endpoint, error) => {}
|
|
3749
|
+
onToolError: (toolName, error, context, endpoint) => {}
|
|
3750
|
+
|
|
3751
|
+
// after
|
|
3752
|
+
beforeToolCall: ({ toolName, args, context, endpoint }) => {}
|
|
3753
|
+
afterToolCall: ({ toolName, args, result, durationMs, context, endpoint, error }) => {}
|
|
3754
|
+
onToolError: ({ toolName, error, context, endpoint }) => {}
|
|
3755
|
+
```
|
|
3756
|
+
|
|
3757
|
+
### MCP schema validation
|
|
3758
|
+
|
|
3759
|
+
The standalone validator now takes one object and live MCP configs carry the
|
|
3760
|
+
same rules under `schemaValidation`. Migrate every positional call and every
|
|
3761
|
+
`onIncompatibleSchema` field; there is no old-shape overload:
|
|
3762
|
+
|
|
3763
|
+
```ts
|
|
3764
|
+
// before
|
|
3765
|
+
validateMcpSchemas(services, 'throw', logger, { requireTypedProperties: true })
|
|
3766
|
+
createMcpHandler({ services, onIncompatibleSchema: 'throw' })
|
|
3767
|
+
|
|
3768
|
+
// after
|
|
3769
|
+
validateMcpSchemas({ services, policy: 'throw', logger, requireTypedProperties: true })
|
|
3770
|
+
createMcpHandler({ services, schemaValidation: { policy: 'throw' } })
|
|
3771
|
+
```
|
|
3772
|
+
|
|
3773
|
+
Put `extend` and `flattenUnionInput` beside `services`, not inside
|
|
3774
|
+
`schemaValidation`. The handler applies the profile to the exact prepared schema
|
|
3775
|
+
it advertises. Add `requirePortableFormats: true` when every custom JSON Schema
|
|
3776
|
+
format must be rejected before a client sees it.
|
|
3777
|
+
|
|
3778
|
+
Native MCP registration also changed shape in 0.37. Move protected tools to the
|
|
3779
|
+
framework registrar; keep an SDK-raw tool only by naming the opt-out:
|
|
3780
|
+
|
|
3781
|
+
```ts
|
|
3782
|
+
// before
|
|
3783
|
+
nativeTools: (server, auth) => server.registerTool(name, config, handler)
|
|
3784
|
+
|
|
3785
|
+
// after — lifecycle, hooks and schema policy apply
|
|
3786
|
+
nativeTools: ({ registerTool }, auth) => registerTool({
|
|
3787
|
+
name, description, identity, input, output, handler,
|
|
3788
|
+
})
|
|
3789
|
+
|
|
3790
|
+
// after — intentionally raw
|
|
3791
|
+
nativeTools: ({ rawServer }, auth) => rawServer.registerTool(name, config, handler)
|
|
3792
|
+
```
|
|
3793
|
+
|
|
3794
|
+
If a tool hook annotated `endpoint` as `MethodDef`, remove that annotation or
|
|
3795
|
+
use `OperationIdentity`: native operations have service/action/scope/method but
|
|
3796
|
+
no HTTP path.
|
|
3797
|
+
|
|
3798
|
+
### MCP HTTP sessions
|
|
3799
|
+
|
|
3800
|
+
Finally, replace the MCP HTTP session boolean. Omission changed meaning:
|
|
3801
|
+
|
|
3802
|
+
```ts
|
|
3803
|
+
// before → after
|
|
3804
|
+
stateless: true // → sessionMode: 'stateless'
|
|
3805
|
+
stateless: false // → sessionMode: 'stateful'
|
|
3806
|
+
|
|
3807
|
+
// before: omission was stateful
|
|
3808
|
+
// after: omission is stateless
|
|
3809
|
+
```
|
|
3810
|
+
|
|
3811
|
+
If the client relies on `Mcp-Session-Id`, server push, progress across requests
|
|
3812
|
+
or resumable SSE, set `sessionMode: 'stateful'` explicitly. Synchronous tool
|
|
3813
|
+
servers should omit it and use the new restart-safe default.
|
|
3814
|
+
|
|
3815
|
+
### Node-facing server types
|
|
3816
|
+
|
|
3817
|
+
`stitchkit/server` remains Bun-concrete: an explicitly annotated `RawRoute`
|
|
3818
|
+
still receives `BunServer`. `stitchkit/node` no longer drags Bun declarations
|
|
3819
|
+
into a Node project. Its raw routes default the host server to `unknown`, and
|
|
3820
|
+
its Socket.IO handle exposes only Node capabilities:
|
|
3821
|
+
|
|
3822
|
+
```ts
|
|
3823
|
+
// before — Node entry still leaked Bun-only fields and ambient types
|
|
3824
|
+
const route: RawRoute = { handler: (_req, ctx) => ctx.server?.upgrade(...) }
|
|
3825
|
+
const socket = await createSocketIOServer(config)
|
|
3826
|
+
socket.websocket
|
|
3827
|
+
|
|
3828
|
+
// after — name a custom embedding host only when one exists
|
|
3829
|
+
const route: RawRoute<MyHostServer> = {
|
|
3830
|
+
handler: (_req, ctx) => useHost(ctx.server),
|
|
3831
|
+
}
|
|
3832
|
+
const socket = await createSocketIOServer(config)
|
|
3833
|
+
socket.io
|
|
3834
|
+
socket.attach(nodeHttpServer)
|
|
3835
|
+
```
|
|
3836
|
+
|
|
3837
|
+
Inline Bun routes passed to `createServer` continue to infer `BunServer`; no
|
|
3838
|
+
annotation is needed. Node consumers can remove `@types/bun` unless another
|
|
3839
|
+
dependency independently requires it.
|
|
3840
|
+
|
|
3564
3841
|
## Your handlers may be returning more than the contract declares
|
|
3565
3842
|
|
|
3566
3843
|
stitchkit validates every handler's return value against the endpoint's `output`
|
|
@@ -3762,23 +4039,24 @@ Also re-exports the error helpers from `stitchkit/contract`.
|
|
|
3762
4039
|
| `zodIssues` | function | a `ZodError` → structured `{ path, code, message }[]` — the machine-readable sibling of `formatZodError` |
|
|
3763
4040
|
| `ZodIssueSummary` | _type_ | one structured validation issue (`{ path, code, message }`) |
|
|
3764
4041
|
| `parseBody` | function | parse + Zod-validate a JSON body → `data` or `null` (no throw) |
|
|
3765
|
-
| `HandlerConfig` | _type_ | config for `createHandler`
|
|
4042
|
+
| `HandlerConfig` | _type_ | config for `createHandler`, including optional `maxJsonBodyBytes`; bound to `BunServer` on this entrypoint |
|
|
3766
4043
|
| `BunServerConfig` | _type_ | config for `createServer` (Bun) |
|
|
3767
4044
|
| `ServiceDef` | _type_ | the result of `implement` |
|
|
3768
4045
|
| `MethodDef` | _type_ | one resolved endpoint inside a service |
|
|
4046
|
+
| `OperationIdentity` | _type_ | path-free service/action/scope/method identity shared by contract and native tool operations |
|
|
3769
4047
|
| `Handlers` | _type_ | the typed handler map `implement` expects |
|
|
3770
4048
|
| `LifecycleHooks` | _type_ | `onRequest` / `beforeHandle` / `afterHandle` / `onError` |
|
|
3771
4049
|
| `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 |
|
|
4050
|
+
| `RawRoute` | _type_ | a non-contract `Request → Response` route with a concrete `BunServer` context |
|
|
4051
|
+
| `RawRouteContext` | _type_ | the Bun-bound routing context a raw handler receives |
|
|
3774
4052
|
| `BunServer` | _type_ | the `Bun.serve` instance type |
|
|
3775
4053
|
| `ServerPassthrough` | _type_ | extra `Bun.serve` options |
|
|
3776
4054
|
| `StitchLogger` | _type_ | the custom-logger interface |
|
|
3777
4055
|
| `LoggingConfig` | _type_ | the `logging` object — `logger` / `format` / `skip` / `enrich` |
|
|
3778
4056
|
| `LogFormat` | _type_ | `'pretty'` or `'json'` — what the built-in formatter writes |
|
|
3779
4057
|
| `LogOutcome` | _type_ | how a request finished, as `enrich` sees it |
|
|
3780
|
-
| `FetchHandler` | _type_ | what `createHandler` returns |
|
|
3781
|
-
| `FetchComposition` | _type_ | the `wrapFetch` seam
|
|
4058
|
+
| `FetchHandler` | _type_ | what `createHandler` returns, bound to `BunServer` here |
|
|
4059
|
+
| `FetchComposition` | _type_ | the Bun-bound `wrapFetch` seam |
|
|
3782
4060
|
|
|
3783
4061
|
### Auth
|
|
3784
4062
|
|
|
@@ -3943,13 +4221,21 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
3943
4221
|
| `createToolkit` | function | context-typed tool mounts — [guide](../guide/cli.md#typed-context) |
|
|
3944
4222
|
| `mountViewFile` | function | a native multimodal "view file" MCP tool |
|
|
3945
4223
|
| `resolveMedia` | function | resolve a media reference for a tool result |
|
|
3946
|
-
| `validateMcpSchemas` | function |
|
|
4224
|
+
| `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
4225
|
| `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
4226
|
| `McpHandlerConfig` | _type_ | config for `createMcpHandler` |
|
|
4227
|
+
| `McpSessionMode` | _type_ | `'stateless' \| 'stateful'`; HTTP defaults to request-isolated stateless mode |
|
|
3949
4228
|
| `StdioMcpServerConfig` | _type_ | config for `createStdioMcpServer` |
|
|
3950
4229
|
| `McpServerBuildConfig` | _type_ | shared config for `buildMcpServer` |
|
|
3951
4230
|
| `ImplementRemoteOptions` | _type_ | options for `implementRemote` |
|
|
3952
4231
|
| `McpMountConfig` | _type_ | config for `mountMcp` |
|
|
4232
|
+
| `McpSchemaValidationConfig` | _type_ | shared `{ policy, requireTypedProperties, allowUntyped, requirePortableFormats, allowFormats }` profile |
|
|
4233
|
+
| `ValidateMcpSchemasConfig` | _type_ | standalone validation profile plus `services`, `extend`, flattening and logger |
|
|
4234
|
+
| `NativeMcpRegistrar` | _type_ | protected `registerTool` plus explicit unprotected `rawServer` access |
|
|
4235
|
+
| `NativeMcpToolDefinition` | _type_ | native name, operation identity, Zod schemas and MCP-result handler |
|
|
4236
|
+
| `NativeMcpOperationIdentity` | _type_ | `{ serviceName, action, scope?, method, meta? }` for native lifecycle/audit |
|
|
4237
|
+
| `NativeMcpHandlerContext` | _type_ | runtime context with the definition's parsed native input |
|
|
4238
|
+
| `NativeMcpResult` | _type_ | MCP content result, with typed `structuredContent` when output is declared |
|
|
3953
4239
|
| `AgentMountConfig` | _type_ | config for `mountAgent` |
|
|
3954
4240
|
| `AgentContext` | _type_ | the context merged into agent tool handlers |
|
|
3955
4241
|
| `CliConfig` | _type_ | config for `createCli` |
|
|
@@ -3958,7 +4244,11 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
3958
4244
|
| `Toolkit` | _type_ | the context-pinned tool surface from `createToolkit` |
|
|
3959
4245
|
| `ToolExtend` | _type_ | extra-args extension for `mountMcp` / `mountAgent` |
|
|
3960
4246
|
| `ToolLifecycle` | _type_ | `beforeHandle` / `afterHandle` gate for tool calls — [guide](../guide/mcp-and-agents.md#guarding-tools--lifecycle) |
|
|
3961
|
-
| `
|
|
4247
|
+
| `ToolOperation` | _type_ | executable path-free operation shape shared by contract and framework-native runners |
|
|
4248
|
+
| `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)) |
|
|
4249
|
+
| `BeforeToolCallOptions` | _type_ | `{ toolName, args, context, endpoint }` passed before execution |
|
|
4250
|
+
| `AfterToolCallOptions` | _type_ | completed call options plus `{ result, durationMs, error? }` |
|
|
4251
|
+
| `ToolErrorOptions` | _type_ | `{ toolName, error, context, endpoint }` for a thrown handler-path value |
|
|
3962
4252
|
| `ErrorHintFn` | _type_ | `(toolName, errorCode) => string \| null` — a per-tool recovery hint, shared by every mount |
|
|
3963
4253
|
| `ToolResult` | _type_ | the result of one tool call |
|
|
3964
4254
|
| `ToolCallContext` | _type_ | the context every tool hook receives — `{ source }` plus whatever the mount's `context` added |
|
|
@@ -3967,6 +4257,9 @@ Server-only. Turns contracts into MCP and AI-agent tools. Needs the
|
|
|
3967
4257
|
| `CollectToolsConfig` | _type_ | options for `collectTools` |
|
|
3968
4258
|
| `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
4259
|
| `UntypedProperty` | _type_ | one such property — `{ path, description? }` |
|
|
4260
|
+
| `findNonPortableFormats` | function | deep finder for formats outside the portable MCP/AJV baseline |
|
|
4261
|
+
| `NonPortableFormat` | _type_ | one `{ path, format }` portability finding |
|
|
4262
|
+
| `PORTABLE_JSON_SCHEMA_FORMATS` | constant | portable-format baseline used by MCP validation |
|
|
3970
4263
|
| `ToolNameEntry` | _type_ | one `listToolNames` row — `{ name, service, method, transports }` |
|
|
3971
4264
|
| `IncompatibleSchemaPolicy` | _type_ | `'throw' \| 'skip' \| 'warn'` |
|
|
3972
4265
|
| `McpMediaContent` | _type_ | a multimodal MCP content item |
|
|
@@ -4033,9 +4326,9 @@ Advanced building blocks — the shared machinery the mounts are built on.
|
|
|
4033
4326
|
| `TransportSummary` | _type_ | the result of `summarizeTransports` |
|
|
4034
4327
|
| `TransportCounts` | _type_ | per-transport counts (`{ HTTP, MCP, AGENT, CLI }`) |
|
|
4035
4328
|
| `coerceJsonArgs` | function | coerce JSON-stringified array/object tool arguments |
|
|
4036
|
-
| `
|
|
4037
|
-
| `
|
|
4038
|
-
| `MountableTool` | _type_ | one
|
|
4329
|
+
| `flattenToolJsonSchema` | function | project structurally identifiable discriminated unions in a JSON Schema document into conservative object joins; never executes validation |
|
|
4330
|
+
| `ToolPresentationSchema` | _type_ | immutable model-facing JSON Schema document shared by tool transports |
|
|
4331
|
+
| `MountableTool` | _type_ | one operation with separate executable CLI argument schema and model-facing presentation schema |
|
|
4039
4332
|
| `ToolManifestEntry` | _type_ | one `buildToolManifest` row |
|
|
4040
4333
|
|
|
4041
4334
|
---
|
|
@@ -4051,11 +4344,12 @@ runtime-agnostic pieces of `stitchkit/server` and the error helpers.
|
|
|
4051
4344
|
|--------|------|---------|
|
|
4052
4345
|
| `serveNode` | function | build the router and start a Node HTTP server (via `srvx`) |
|
|
4053
4346
|
| `createHandler` | function | the router as a bare `(req) => Response` (same as `/server`) |
|
|
4054
|
-
| `createSocketIOServer` | function | the typed Socket.IO server (
|
|
4347
|
+
| `createSocketIOServer` | function | the typed Node Socket.IO server (`io` + `attach`; no Bun engine declarations) |
|
|
4055
4348
|
| `implement` / `createImplement` | function | bind a contract to typed handlers (same as `/server`) |
|
|
4056
4349
|
| `NodeServerConfig` | _type_ | config for `serveNode` |
|
|
4057
4350
|
| `NodeServerHandle` | _type_ | the `serveNode` handle (`{ port, stop }`) |
|
|
4058
|
-
| `HandlerConfig` / `ServiceDef` / `RawRoute` / `RawRouteContext`
|
|
4351
|
+
| `HandlerConfig` / `ServiceDef` / `RawRoute` / `RawRouteContext` | _type_ | runtime-neutral handler types; raw routes default their host server to `unknown` |
|
|
4352
|
+
| `SocketIOServerConfig` / `SocketIOServerHandle` | _type_ | shared config and the Node-only `{ io, attach }` handle |
|
|
4059
4353
|
| `AppError` + `appError` / `badRequest` / `unauthorized` / `forbidden` / `notFound` / `conflict` / `rateLimited` | — | error helpers (same as `/contract`) |
|
|
4060
4354
|
|
|
4061
4355
|
---
|