stitchkit 0.91.0 → 0.91.1

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/CHANGELOG.md CHANGED
@@ -15,6 +15,24 @@ additive**; the first breaking change landed in 0.10.0. Grep the file for
15
15
 
16
16
  ## [Unreleased]
17
17
 
18
+ ## [0.91.1] — 2026-09-22
19
+
20
+ ### Fixed
21
+
22
+ - **A refusal relayed from a discovered MCP tool no longer says "retryable" for
23
+ everything.** `mcpToolFailure` rebuilt the remote's refusal with a constant
24
+ 502 and read neither `retryable` nor `_hint` off the envelope it had just
25
+ parsed. 502 is a retryable class, so the status-derived answer was `true` for
26
+ every relayed failure there has ever been — `NOT_FOUND` included, and a model
27
+ reading it calls the missing thing again. That is the exact opposite of what
28
+ the field was added for in 0.91.0. Both fields are now carried as the remote
29
+ sent them: `retryable` only when it is a boolean, `_hint` only when it is a
30
+ string, so `0` or `'false'` stays undeclared rather than being coerced into a
31
+ decision. The status stays 502 in both branches — the failure did happen
32
+ upstream, and a declared `retryable` wins over it anyway. A remote that
33
+ declares nothing is unchanged, and the unstructured branch
34
+ (`UPSTREAM_TOOL_ERROR`) has nothing to declare and keeps the 502 default.
35
+
18
36
  ## [0.91.0] — 2026-09-22
19
37
 
20
38
  ### ⚠️ Breaking changes
@@ -543,7 +543,7 @@ function mcpToolFailure(toolName, result) {
543
543
  if (isRecord(payload) && typeof payload.error === "string") {
544
544
  const details = isRecord(payload.details) ? payload.details : undefined;
545
545
  const message = typeof details?.message === "string" ? details.message : payload.error;
546
- return new AppError(payload.error, message, 502, details);
546
+ return new AppError(payload.error, message, 502, details, typeof payload._hint === "string" ? payload._hint : undefined, undefined, typeof payload.retryable === "boolean" ? payload.retryable : undefined);
547
547
  }
548
548
  return new AppError(UPSTREAM_TOOL_ERROR, `External MCP tool "${toolName}" returned an error`, 502, payload === undefined ? undefined : { upstream: payload });
549
549
  }
@@ -34,11 +34,21 @@ export declare const UPSTREAM_TOOL_ERROR = "UPSTREAM_TOOL_ERROR";
34
34
  * it printed a code frame of the framework bundle before the JSON failure and
35
35
  * was then scrubbed to a bare `INTERNAL_SERVER_ERROR`.
36
36
  *
37
- * A structured `{ error, details }` body is the remote contract's own refusal
38
- * and is relayed as one. Anything else keeps the framework's sentence and
39
- * carries what the server did send — reporting less than we have is not caution.
37
+ * A structured `{ error, retryable, details, _hint }` body is the remote
38
+ * contract's own refusal and is relayed as one — every field of it. Anything
39
+ * else keeps the framework's sentence and carries what the server did send —
40
+ * reporting less than we have is not caution.
41
+ *
40
42
  * The status is 502 either way: whatever the code says, the failure happened
41
- * upstream, and a consumer re-serving it over HTTP should say so.
43
+ * upstream, and a consumer re-serving it over HTTP should say so. That constant
44
+ * is exactly why `retryable` has to be carried rather than re-derived. 502 is a
45
+ * retryable class, so a status-derived answer says `true` for every relayed
46
+ * refusal there has ever been — `NOT_FOUND` included, and a model reading it
47
+ * calls the missing thing again. The remote already decided, declared or
48
+ * derived from its own status, and its answer is the one that means something:
49
+ * it is the only side that knows whether repeating the call could work. `_hint`
50
+ * rides along for the same reason — it is the remote's instruction for what to
51
+ * do instead, and dropping it leaves the model with a refusal and no next move.
42
52
  */
43
53
  export declare function mcpToolFailure(toolName: string, result: unknown): AppError;
44
54
  //# sourceMappingURL=mcp-envelope.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-envelope.d.ts","sourceRoot":"","sources":["../../../src/tools/connections/mcp-envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAIjD,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAaxD;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AAEzD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,QAAQ,CAa1E"}
1
+ {"version":3,"file":"mcp-envelope.d.ts","sourceRoot":"","sources":["../../../src/tools/connections/mcp-envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAIjD,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAaxD;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,QAAQ,CAwB1E"}
package/llms-full.txt CHANGED
@@ -6110,6 +6110,15 @@ const signals = bindProcessSignals(app, {
6110
6110
  await app.start()
6111
6111
  ```
6112
6112
 
6113
+ **Bind before you start, and the order is load-bearing.** Reading it the other way
6114
+ round — start serving, then arrange for shutdown — leaves a window between the
6115
+ first accepted request and the first listener, and a supervisor that sends
6116
+ `SIGTERM` inside that window kills the process outright: exit 143, no shutdown
6117
+ chain, no `onComplete`, nothing drained. The window is small and opens exactly
6118
+ when the machine is busiest, which is when a supervisor is most likely to be
6119
+ restarting something. `bindProcessSignals` does not start anything, so there is
6120
+ no cost to binding first.
6121
+
6113
6122
  The exact resource callbacks are typed by their public configuration. The
6114
6123
  important ownership rule is stable: the application decides what the
6115
6124
  database/server/provider objects are and how they are configured; Stitchkit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stitchkit",
3
- "version": "0.91.0",
3
+ "version": "0.91.1",
4
4
  "description": "Contract-first backend framework — one defineContract() into an HTTP API, MCP tools, AI-agent tools and a typed client. Bun and Node.",
5
5
  "keywords": [
6
6
  "bun",