stitchkit 0.103.12 → 0.104.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.
Files changed (225) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/darwin-native.cjs +12 -0
  3. package/darwin-native.d.cts +2 -0
  4. package/dist/agent-runtime/store-conformance-lifecycle.d.ts +10 -10
  5. package/dist/application/directory-inbox-accept.d.ts +13 -0
  6. package/dist/application/directory-inbox-accept.d.ts.map +1 -0
  7. package/dist/application/directory-inbox-contract.d.ts +42 -5
  8. package/dist/application/directory-inbox-contract.d.ts.map +1 -1
  9. package/dist/application/directory-inbox-delivery.d.ts +14 -0
  10. package/dist/application/directory-inbox-delivery.d.ts.map +1 -0
  11. package/dist/application/directory-inbox-files.d.ts +28 -0
  12. package/dist/application/directory-inbox-files.d.ts.map +1 -0
  13. package/dist/application/directory-inbox-state.d.ts +14 -0
  14. package/dist/application/directory-inbox-state.d.ts.map +1 -0
  15. package/dist/application/directory-inbox.d.ts +2 -1
  16. package/dist/application/directory-inbox.d.ts.map +1 -1
  17. package/dist/application/file-state-store.d.ts +2 -8
  18. package/dist/application/file-state-store.d.ts.map +1 -1
  19. package/dist/application/state-store.d.ts +6 -1
  20. package/dist/application/state-store.d.ts.map +1 -1
  21. package/dist/browser/client-arguments.d.ts +4 -0
  22. package/dist/browser/client-arguments.d.ts.map +1 -0
  23. package/dist/browser/client-executors.d.ts.map +1 -1
  24. package/dist/browser/client.d.ts.map +1 -1
  25. package/dist/browser/contract-stream.d.ts.map +1 -1
  26. package/dist/browser/http.d.ts +2 -1
  27. package/dist/browser/http.d.ts.map +1 -1
  28. package/dist/browser/native-fetch.d.ts +8 -0
  29. package/dist/browser/native-fetch.d.ts.map +1 -0
  30. package/dist/contract/errors.d.ts +16 -12
  31. package/dist/contract/errors.d.ts.map +1 -1
  32. package/dist/contract/normalize.d.ts +5 -1
  33. package/dist/contract/normalize.d.ts.map +1 -1
  34. package/dist/contract/stream.d.ts +1 -0
  35. package/dist/contract/stream.d.ts.map +1 -1
  36. package/dist/entrypoints/agent-runtime/browser.js +3 -3
  37. package/dist/entrypoints/agent-runtime/coding-tools.js +8 -6
  38. package/dist/entrypoints/agent-runtime/harness-tools.js +8 -8
  39. package/dist/entrypoints/agent-runtime/harness.js +12 -11
  40. package/dist/entrypoints/agent-runtime/realtime.js +3 -3
  41. package/dist/entrypoints/agent-runtime/sandbox.js +8 -6
  42. package/dist/entrypoints/agent-runtime.js +13 -13
  43. package/dist/entrypoints/application/diagnostic-journal.js +3 -2
  44. package/dist/entrypoints/application/directory-inbox.js +424 -119
  45. package/dist/entrypoints/application/grammy.js +1 -1
  46. package/dist/entrypoints/application.d.ts +2 -2
  47. package/dist/entrypoints/application.d.ts.map +1 -1
  48. package/dist/entrypoints/application.js +7 -3
  49. package/dist/entrypoints/cli.d.ts +2 -0
  50. package/dist/entrypoints/cli.d.ts.map +1 -1
  51. package/dist/entrypoints/cli.js +757 -136
  52. package/dist/entrypoints/contract.js +3 -3
  53. package/dist/entrypoints/files.js +11 -107
  54. package/dist/entrypoints/index.js +7 -7
  55. package/dist/entrypoints/node.js +8 -8
  56. package/dist/entrypoints/observability.js +2 -2
  57. package/dist/entrypoints/process.js +2 -1
  58. package/dist/entrypoints/react.js +1 -1
  59. package/dist/entrypoints/remote.js +19 -6
  60. package/dist/entrypoints/server.d.ts +1 -1
  61. package/dist/entrypoints/server.d.ts.map +1 -1
  62. package/dist/entrypoints/server.js +20 -14
  63. package/dist/entrypoints/telegram.d.ts +1 -0
  64. package/dist/entrypoints/telegram.d.ts.map +1 -1
  65. package/dist/entrypoints/telegram.js +214 -98
  66. package/dist/entrypoints/testing.js +7 -7
  67. package/dist/entrypoints/tools/connections.d.ts +2 -2
  68. package/dist/entrypoints/tools/connections.d.ts.map +1 -1
  69. package/dist/entrypoints/tools/connections.js +427 -294
  70. package/dist/entrypoints/tools/invoker.js +6 -6
  71. package/dist/entrypoints/tools/mcp.d.ts +12 -0
  72. package/dist/entrypoints/tools/mcp.d.ts.map +1 -0
  73. package/dist/entrypoints/tools/mcp.js +45 -0
  74. package/dist/entrypoints/tools.d.ts +1 -0
  75. package/dist/entrypoints/tools.d.ts.map +1 -1
  76. package/dist/entrypoints/tools.js +82 -1147
  77. package/dist/entrypoints/tracking.js +1 -1
  78. package/dist/files/boundary.d.ts +2 -8
  79. package/dist/files/boundary.d.ts.map +1 -1
  80. package/dist/files/file-io.d.ts +0 -5
  81. package/dist/files/file-io.d.ts.map +1 -1
  82. package/dist/files/read.d.ts +2 -2
  83. package/dist/files/read.d.ts.map +1 -1
  84. package/dist/index-1a6jtnd9.js +88 -0
  85. package/dist/{index-fyphpknh.js → index-22rhg58f.js} +1 -1
  86. package/dist/index-269zqj8m.js +77 -0
  87. package/dist/{index-r34mvse1.js → index-27a2pj09.js} +1 -1
  88. package/dist/{index-r956rndn.js → index-2hyavjjj.js} +3 -3
  89. package/dist/{index-m2xqkv55.js → index-2vnh05w4.js} +83 -14
  90. package/dist/{index-pw99k9h5.js → index-43p08g7t.js} +1 -1
  91. package/dist/{index-xwq9j1sf.js → index-4e0ah203.js} +9 -7
  92. package/dist/{index-nybn2err.js → index-5csmzesf.js} +1 -1
  93. package/dist/{index-qapzv4p5.js → index-5z6jn897.js} +1 -1
  94. package/dist/{index-ahvsksw5.js → index-6d9xaxw1.js} +1 -1
  95. package/dist/{index-ahtnte39.js → index-6drst9sg.js} +46 -35
  96. package/dist/{index-x87j7csh.js → index-77nxk6ay.js} +2 -2
  97. package/dist/{index-dnn9z96q.js → index-a0zzh8ak.js} +3 -3
  98. package/dist/{index-kmpzj1cq.js → index-a3wma1hk.js} +1 -1
  99. package/dist/{index-j8aw2c0e.js → index-bakgedh8.js} +35 -7
  100. package/dist/index-bhb950qs.js +1145 -0
  101. package/dist/{index-tgzhq07n.js → index-cvda079a.js} +1 -1
  102. package/dist/{index-n2gre124.js → index-fa4wg312.js} +4 -4
  103. package/dist/{index-zp4jpkp0.js → index-fk18k56k.js} +2 -2
  104. package/dist/{index-afjvjyck.js → index-g2j7pnak.js} +2 -134
  105. package/dist/index-g95fk660.js +116 -0
  106. package/dist/{index-ej331hah.js → index-gxm9rc97.js} +1 -1
  107. package/dist/{index-r5c4fedv.js → index-h2fhjqy7.js} +2 -1
  108. package/dist/{index-sybedc8b.js → index-hbd32c6a.js} +23 -94
  109. package/dist/index-jd470k6q.js +109 -0
  110. package/dist/{index-b2bz0kdm.js → index-kb8styz3.js} +4 -3
  111. package/dist/{index-0c419yx7.js → index-kemm0ej5.js} +12 -9
  112. package/dist/{index-806d5phm.js → index-ptn0g0gj.js} +7 -7
  113. package/dist/{index-w575evhq.js → index-r4r70zmh.js} +1 -1
  114. package/dist/{index-12m2v2a9.js → index-s4evrmp5.js} +1 -1
  115. package/dist/{index-1khy8y7n.js → index-t1sr4aks.js} +3 -3
  116. package/dist/index-v5ee8ath.js +88 -0
  117. package/dist/{index-45tdvqa3.js → index-wh0ecvyk.js} +1 -1
  118. package/dist/{index-n53mswt3.js → index-x928baz2.js} +8 -1
  119. package/dist/index-yk4g8805.js +133 -0
  120. package/dist/{index-ygkka6zn.js → index-ywppsaby.js} +28 -5
  121. package/dist/{index-xvdd2rbb.js → index-za6j7nwy.js} +19 -5
  122. package/dist/internal/bounded-file-read.d.ts +41 -0
  123. package/dist/internal/bounded-file-read.d.ts.map +1 -0
  124. package/dist/internal/darwin-binding-error.d.ts +24 -0
  125. package/dist/internal/darwin-binding-error.d.ts.map +1 -0
  126. package/dist/internal/darwin-binding.d.ts.map +1 -1
  127. package/dist/internal/exclusive-lock.d.ts +2 -0
  128. package/dist/internal/exclusive-lock.d.ts.map +1 -1
  129. package/dist/internal/with-exclusive-lock.d.ts +4 -2
  130. package/dist/internal/with-exclusive-lock.d.ts.map +1 -1
  131. package/dist/telegram/broadcast-delivery.d.ts +23 -0
  132. package/dist/telegram/broadcast-delivery.d.ts.map +1 -0
  133. package/dist/telegram/broadcast-failure.d.ts +22 -0
  134. package/dist/telegram/broadcast-failure.d.ts.map +1 -0
  135. package/dist/telegram/broadcast-state.d.ts +6 -3
  136. package/dist/telegram/broadcast-state.d.ts.map +1 -1
  137. package/dist/telegram/broadcast.d.ts +10 -5
  138. package/dist/telegram/broadcast.d.ts.map +1 -1
  139. package/dist/tools/cli/args-fields.d.ts +2 -0
  140. package/dist/tools/cli/args-fields.d.ts.map +1 -1
  141. package/dist/tools/cli/args.d.ts.map +1 -1
  142. package/dist/tools/cli/format.d.ts.map +1 -1
  143. package/dist/tools/cli/invoke.d.ts +2 -2
  144. package/dist/tools/cli/invoke.d.ts.map +1 -1
  145. package/dist/tools/cli/publication-assets.d.ts +10 -0
  146. package/dist/tools/cli/publication-assets.d.ts.map +1 -0
  147. package/dist/tools/cli/publication-contract.d.ts +75 -0
  148. package/dist/tools/cli/publication-contract.d.ts.map +1 -0
  149. package/dist/tools/cli/publication-control.d.ts +4 -0
  150. package/dist/tools/cli/publication-control.d.ts.map +1 -0
  151. package/dist/tools/cli/publication-storage.d.ts +32 -0
  152. package/dist/tools/cli/publication-storage.d.ts.map +1 -0
  153. package/dist/tools/cli/publication.d.ts +35 -0
  154. package/dist/tools/cli/publication.d.ts.map +1 -0
  155. package/dist/tools/cli/update.d.ts.map +1 -1
  156. package/dist/tools/connections/define.d.ts.map +1 -1
  157. package/dist/tools/connections/error-projection.d.ts +4 -0
  158. package/dist/tools/connections/error-projection.d.ts.map +1 -0
  159. package/dist/tools/connections/errors.d.ts +24 -8
  160. package/dist/tools/connections/errors.d.ts.map +1 -1
  161. package/dist/tools/connections/limits.d.ts +11 -31
  162. package/dist/tools/connections/limits.d.ts.map +1 -1
  163. package/dist/tools/connections/mcp-client.d.ts +6 -4
  164. package/dist/tools/connections/mcp-client.d.ts.map +1 -1
  165. package/dist/tools/connections/mcp.d.ts.map +1 -1
  166. package/dist/tools/connections/openapi.d.ts.map +1 -1
  167. package/dist/tools/connections/operation-limits.d.ts +25 -0
  168. package/dist/tools/connections/operation-limits.d.ts.map +1 -0
  169. package/dist/tools/connections/sse-frames.d.ts +11 -2
  170. package/dist/tools/connections/sse-frames.d.ts.map +1 -1
  171. package/dist/tools/connections/sse-session.d.ts +15 -2
  172. package/dist/tools/connections/sse-session.d.ts.map +1 -1
  173. package/dist/tools/connections/types.d.ts +19 -1
  174. package/dist/tools/connections/types.d.ts.map +1 -1
  175. package/dist/tools/execute-result.d.ts.map +1 -1
  176. package/dist/tools/execute.d.ts.map +1 -1
  177. package/dist/tools/internal/observe-hook.d.ts +3 -0
  178. package/dist/tools/internal/observe-hook.d.ts.map +1 -0
  179. package/dist/tools/internal/surface-projector.d.ts +2 -1
  180. package/dist/tools/internal/surface-projector.d.ts.map +1 -1
  181. package/dist/tools/mcp/mount.d.ts +2 -2
  182. package/dist/tools/mcp/mount.d.ts.map +1 -1
  183. package/dist/tools/mcp/prepare.d.ts +4 -5
  184. package/dist/tools/mcp/prepare.d.ts.map +1 -1
  185. package/dist/tools/mcp/register.d.ts +4 -1
  186. package/dist/tools/mcp/register.d.ts.map +1 -1
  187. package/dist/tools/mcp/runtime-tools.d.ts.map +1 -1
  188. package/dist/tools/remote.d.ts.map +1 -1
  189. package/dist/tools/runtime-tool-execution.d.ts +65 -0
  190. package/dist/tools/runtime-tool-execution.d.ts.map +1 -0
  191. package/dist/tools/runtime-tool-mcp.d.ts +27 -0
  192. package/dist/tools/runtime-tool-mcp.d.ts.map +1 -0
  193. package/dist/tools/runtime-tool.d.ts +9 -60
  194. package/dist/tools/runtime-tool.d.ts.map +1 -1
  195. package/dist/tools/surface.d.ts +12 -14
  196. package/dist/tools/surface.d.ts.map +1 -1
  197. package/llms/application.2.txt +221 -44
  198. package/llms/application.3.txt +3 -1
  199. package/llms/application.txt +1 -1
  200. package/llms/cli.2.txt +144 -1
  201. package/llms/cli.txt +133 -104
  202. package/llms/contract.txt +1 -1
  203. package/llms/process.txt +34 -5
  204. package/llms/server.4.txt +4 -1
  205. package/llms/server.5.txt +1 -0
  206. package/llms/stitchkit.2.txt +14 -0
  207. package/llms/stitchkit.txt +5 -4
  208. package/llms/telegram.txt +64 -2
  209. package/llms/tools-connections.txt +3 -0
  210. package/llms/tools-mcp.txt +29 -0
  211. package/llms/tools.2.txt +59 -0
  212. package/llms/tools.3.txt +1 -0
  213. package/llms/tools.txt +11 -52
  214. package/llms/upgrading-0.37.0-0.44.0.txt +444 -0
  215. package/llms/{upgrading-0.44.0-0.60.0.txt → upgrading-0.44.0-0.61.0.txt} +140 -271
  216. package/llms/{upgrading-0.61.0-0.74.0.txt → upgrading-0.62.0-0.79.0.txt} +171 -140
  217. package/llms/{upgrading-0.75.0-0.103.0.txt → upgrading-0.80.0-0.104.0.txt} +120 -171
  218. package/llms/upgrading.txt +4 -4
  219. package/llms-full.txt +677 -64
  220. package/llms.txt +13 -12
  221. package/package.json +10 -1
  222. package/dist/index-d4pa2bgs.js +0 -42
  223. package/dist/index-wykebpn9.js +0 -221
  224. package/llms/upgrading-0.37.0.txt +0 -176
  225. package/dist/{index-69nvze1q.js → index-pptyv13k.js} +3 -3
@@ -2,14 +2,22 @@ import"../../index-ct75c3at.js";
2
2
  import {
3
3
  declaresDraft07,
4
4
  withDefsDialect
5
- } from "../../index-x87j7csh.js";
5
+ } from "../../index-77nxk6ay.js";
6
6
  import {
7
7
  AppError2
8
- } from "../../index-n53mswt3.js";
9
- import"../../index-b2bz0kdm.js";
8
+ } from "../../index-x928baz2.js";
9
+ import"../../index-kb8styz3.js";
10
10
  import {
11
11
  defineRuntimeTool2
12
- } from "../../index-tgzhq07n.js";
12
+ } from "../../index-cvda079a.js";
13
+ import {
14
+ ConnectionAuthorizationRequiredError2,
15
+ ConnectionRequestError2,
16
+ ConnectionUrlError2,
17
+ ConnectionBudgetExceededError2,
18
+ ConnectionTimeoutError2,
19
+ ConnectionResponseTooLargeError2
20
+ } from "../../index-1a6jtnd9.js";
13
21
  import {
14
22
  safeJsonParse
15
23
  } from "../../index-zsarb5xp.js";
@@ -17,72 +25,152 @@ import {
17
25
  isRecord
18
26
  } from "../../index-9jfdm5tg.js";
19
27
 
20
- // src/tools/connections/errors.ts
21
- class ConnectionAuthorizationRequiredError extends Error {
22
- connectionName;
23
- instanceId;
24
- constructor(connectionName, instanceId) {
25
- super(`Connection "${connectionName}" requires authorization`);
26
- this.name = "ConnectionAuthorizationRequiredError";
27
- this.connectionName = connectionName;
28
- this.instanceId = instanceId;
28
+ // src/tools/connections/limits.ts
29
+ var DEFAULT_CONNECTION_TIMEOUT_MS = 30000;
30
+ var DEFAULT_MAX_RESPONSE_BYTES = 1024 * 1024;
31
+ var MAX_TIMER_MS = 2147483647;
32
+ function connectionTimeoutMs(value) {
33
+ if (value === undefined)
34
+ return DEFAULT_CONNECTION_TIMEOUT_MS;
35
+ if (!Number.isSafeInteger(value) || value <= 0 || value > MAX_TIMER_MS) {
36
+ throw new RangeError("Connection timeoutMs must be a positive integer within timer range");
29
37
  }
38
+ return value;
30
39
  }
31
-
32
- class ConnectionRequestError extends Error {
33
- connectionName;
34
- status;
35
- body;
36
- constructor(connectionName, status, body) {
37
- super(`Connection "${connectionName}" request failed with status ${status}`);
38
- this.name = "ConnectionRequestError";
39
- this.connectionName = connectionName;
40
- this.status = status;
41
- this.body = body;
40
+ function connectionMaxResponseBytes(value) {
41
+ if (value === undefined)
42
+ return DEFAULT_MAX_RESPONSE_BYTES;
43
+ if (!Number.isSafeInteger(value) || value <= 0) {
44
+ throw new RangeError("Connection maxResponseBytes must be a positive safe integer");
42
45
  }
46
+ return value;
43
47
  }
44
-
45
- class ConnectionUrlError extends Error {
46
- url;
47
- constructor(message, url) {
48
- super(message);
49
- this.name = "ConnectionUrlError";
50
- this.url = url;
48
+ async function awaitConnection(pending, signal) {
49
+ if (!signal)
50
+ return pending;
51
+ if (signal.aborted) {
52
+ pending.catch(() => {
53
+ return;
54
+ });
55
+ signal.throwIfAborted();
56
+ }
57
+ const aborted = Promise.withResolvers();
58
+ const onAbort = () => aborted.reject(signal.reason);
59
+ signal.addEventListener("abort", onAbort, { once: true });
60
+ try {
61
+ signal.throwIfAborted();
62
+ const result = await Promise.race([pending, aborted.promise]);
63
+ signal.throwIfAborted();
64
+ return result;
65
+ } catch (error) {
66
+ signal.throwIfAborted();
67
+ throw error;
68
+ } finally {
69
+ signal.removeEventListener("abort", onAbort);
51
70
  }
52
71
  }
53
-
54
- class ConnectionBudgetExceededError extends Error {
55
- limit;
56
- actual;
57
- constructor(limit, actual, unit = "tools") {
58
- super(`Connection surface exposes ${actual} ${unit}, above the budget of ${limit}`);
59
- this.name = "ConnectionBudgetExceededError";
60
- this.limit = limit;
61
- this.actual = actual;
72
+ async function withConnectionDeadline(connectionName, timeoutMs, callerSignal, body, context) {
73
+ connectionTimeoutMs(timeoutMs);
74
+ const controller = new AbortController;
75
+ const timer = setTimeout(() => controller.abort(new ConnectionTimeoutError2(connectionName, timeoutMs, context)), timeoutMs);
76
+ const onAbort = () => controller.abort(callerSignal?.reason);
77
+ if (callerSignal) {
78
+ if (callerSignal.aborted)
79
+ controller.abort(callerSignal.reason);
80
+ else
81
+ callerSignal.addEventListener("abort", onAbort, { once: true });
82
+ }
83
+ if (context)
84
+ context.signal = controller.signal;
85
+ try {
86
+ controller.signal.throwIfAborted();
87
+ return await awaitConnection(body(controller.signal), controller.signal);
88
+ } finally {
89
+ clearTimeout(timer);
90
+ callerSignal?.removeEventListener("abort", onAbort);
62
91
  }
63
92
  }
64
-
65
- class ConnectionTimeoutError extends Error {
66
- connectionName;
67
- timeoutMs;
68
- constructor(connectionName, timeoutMs) {
69
- super(`Connection "${connectionName}" timed out after ${timeoutMs} ms`);
70
- this.name = "ConnectionTimeoutError";
71
- this.connectionName = connectionName;
72
- this.timeoutMs = timeoutMs;
93
+ async function readBoundedText(response, maxBytes, connectionName, context) {
94
+ context?.signal?.throwIfAborted();
95
+ if (!response.body)
96
+ return "";
97
+ const reader = response.body.getReader();
98
+ const decoder = new TextDecoder;
99
+ let total = 0;
100
+ let text = "";
101
+ const cancel = () => void reader.cancel(context?.signal?.reason).catch(() => {
102
+ return;
103
+ });
104
+ context?.signal?.addEventListener("abort", cancel, { once: true });
105
+ try {
106
+ for (;; ) {
107
+ const { done, value } = await awaitConnection(reader.read(), context?.signal);
108
+ if (done)
109
+ break;
110
+ total += value.byteLength;
111
+ if (context)
112
+ context.observedReadBytes += value.byteLength;
113
+ if (total > maxBytes) {
114
+ throw new ConnectionResponseTooLargeError2(connectionName, maxBytes, {
115
+ ...context,
116
+ observedReadBytes: total
117
+ });
118
+ }
119
+ text += decoder.decode(value, { stream: true });
120
+ }
121
+ text += decoder.decode();
122
+ return text;
123
+ } finally {
124
+ context?.signal?.removeEventListener("abort", cancel);
125
+ cancel();
126
+ reader.releaseLock();
127
+ }
128
+ }
129
+ async function readBoundedJson(response, maxBytes, connectionName, context) {
130
+ return JSON.parse(await readBoundedText(response, maxBytes, connectionName, context));
131
+ }
132
+ async function readConnectionErrorText(response, connectionName, context) {
133
+ try {
134
+ return (await readBoundedText(response, context.maxResponseBytes, connectionName, context)).slice(0, 512);
135
+ } catch (error) {
136
+ context.signal?.throwIfAborted();
137
+ if (error instanceof ConnectionResponseTooLargeError2 || error instanceof ConnectionTimeoutError2) {
138
+ throw error;
139
+ }
140
+ return "";
73
141
  }
74
142
  }
75
143
 
76
- class ConnectionResponseTooLargeError extends Error {
77
- connectionName;
78
- maxBytes;
79
- constructor(connectionName, maxBytes) {
80
- super(`Connection "${connectionName}" response exceeded the ${maxBytes} byte limit`);
81
- this.name = "ConnectionResponseTooLargeError";
82
- this.connectionName = connectionName;
83
- this.maxBytes = maxBytes;
144
+ // src/tools/connections/operation-limits.ts
145
+ function resolveMcpLimits(config) {
146
+ const shared = {
147
+ timeoutMs: connectionTimeoutMs(config.timeoutMs),
148
+ maxResponseBytes: connectionMaxResponseBytes(config.maxResponseBytes)
149
+ };
150
+ const resolve = (phase) => {
151
+ const override = config.limits?.[phase];
152
+ return {
153
+ timeoutMs: connectionTimeoutMs(override?.timeoutMs === undefined ? shared.timeoutMs : override.timeoutMs),
154
+ maxResponseBytes: connectionMaxResponseBytes(override?.maxResponseBytes === undefined ? shared.maxResponseBytes : override.maxResponseBytes)
155
+ };
156
+ };
157
+ return { discovery: resolve("discovery"), call: resolve("call") };
158
+ }
159
+ function mcpOperation(method) {
160
+ switch (method) {
161
+ case "initialize":
162
+ case "notifications/initialized":
163
+ case "tools/list":
164
+ case "tools/call":
165
+ return method;
166
+ default:
167
+ return "request";
84
168
  }
85
169
  }
170
+ function connectionReadContext(operation, limits) {
171
+ const phase = operation === "initialize" || operation === "notifications/initialized" || operation === "tools/list" || operation === "endpoint" || operation === "openapi/spec" ? "discovery" : "call";
172
+ return { ...limits[phase], phase, operation, observedReadBytes: 0 };
173
+ }
86
174
 
87
175
  // src/tools/connections/ssrf.ts
88
176
  function assertConnectionUrl(rawUrl, connectionName) {
@@ -90,10 +178,10 @@ function assertConnectionUrl(rawUrl, connectionName) {
90
178
  try {
91
179
  url = new URL(rawUrl);
92
180
  } catch {
93
- throw new ConnectionUrlError(`Connection "${connectionName}" has an unparseable URL: ${rawUrl}`, rawUrl);
181
+ throw new ConnectionUrlError2(`Connection "${connectionName}" has an unparseable URL: ${rawUrl}`, rawUrl);
94
182
  }
95
183
  if (url.protocol !== "http:" && url.protocol !== "https:") {
96
- throw new ConnectionUrlError(`Connection "${connectionName}" must use http(s), got "${url.protocol}"`, rawUrl);
184
+ throw new ConnectionUrlError2(`Connection "${connectionName}" must use http(s), got "${url.protocol}"`, rawUrl);
97
185
  }
98
186
  return url;
99
187
  }
@@ -105,7 +193,7 @@ function connectionAllowedHosts(connectionUrl, extra) {
105
193
  }
106
194
  function assertAllowedHost(url, allowedHosts, connectionName) {
107
195
  if (!allowedHosts.has(url.host.toLowerCase())) {
108
- throw new ConnectionUrlError(`Connection "${connectionName}" refused request to host "${url.host}": not the connection host or an allowed host`, url.toString());
196
+ throw new ConnectionUrlError2(`Connection "${connectionName}" refused request to host "${url.host}": not the connection host or an allowed host`, url.toString());
109
197
  }
110
198
  }
111
199
 
@@ -114,6 +202,7 @@ function defineMcpClientConnection(config) {
114
202
  if (!config.name.trim())
115
203
  throw new Error("MCP connection requires a name");
116
204
  assertConnectionUrl(config.transport.url, config.name);
205
+ resolveMcpLimits(config);
117
206
  return { ...config, kind: "mcp" };
118
207
  }
119
208
  function defineOpenApiConnection(config) {
@@ -121,6 +210,8 @@ function defineOpenApiConnection(config) {
121
210
  throw new Error("OpenAPI connection requires a name");
122
211
  if (config.baseUrl)
123
212
  assertConnectionUrl(config.baseUrl, config.name);
213
+ connectionTimeoutMs(config.timeoutMs);
214
+ connectionMaxResponseBytes(config.maxResponseBytes);
124
215
  return { ...config, kind: "openapi" };
125
216
  }
126
217
  // src/tools/connections/instance.ts
@@ -132,65 +223,6 @@ function connectionInstanceId(name, url, instanceKey) {
132
223
  // src/tools/connections/mcp.ts
133
224
  import { z as z2 } from "zod";
134
225
 
135
- // src/tools/connections/limits.ts
136
- var DEFAULT_CONNECTION_TIMEOUT_MS = 30000;
137
- var DEFAULT_MAX_RESPONSE_BYTES = 1024 * 1024;
138
- function connectionTimeoutMs(value) {
139
- return value !== undefined && value > 0 ? value : DEFAULT_CONNECTION_TIMEOUT_MS;
140
- }
141
- function connectionMaxResponseBytes(value) {
142
- return value !== undefined && value > 0 ? value : DEFAULT_MAX_RESPONSE_BYTES;
143
- }
144
- async function withConnectionDeadline(connectionName, timeoutMs, callerSignal, body) {
145
- const controller = new AbortController;
146
- const timer = setTimeout(() => controller.abort(new ConnectionTimeoutError(connectionName, timeoutMs)), timeoutMs);
147
- const onAbort = () => controller.abort(callerSignal?.reason);
148
- if (callerSignal) {
149
- if (callerSignal.aborted)
150
- controller.abort(callerSignal.reason);
151
- else
152
- callerSignal.addEventListener("abort", onAbort, { once: true });
153
- }
154
- try {
155
- return await body(controller.signal);
156
- } finally {
157
- clearTimeout(timer);
158
- callerSignal?.removeEventListener("abort", onAbort);
159
- }
160
- }
161
- async function readBoundedText(response, maxBytes, connectionName) {
162
- const stream = response.body;
163
- if (!stream)
164
- return "";
165
- const reader = stream.getReader();
166
- const decoder = new TextDecoder;
167
- let total = 0;
168
- let text = "";
169
- try {
170
- for (;; ) {
171
- const { done, value } = await reader.read();
172
- if (done)
173
- break;
174
- total += value.byteLength;
175
- if (total > maxBytes) {
176
- await reader.cancel().catch(() => {
177
- return;
178
- });
179
- throw new ConnectionResponseTooLargeError(connectionName, maxBytes);
180
- }
181
- text += decoder.decode(value, { stream: true });
182
- }
183
- text += decoder.decode();
184
- return text;
185
- } finally {
186
- reader.releaseLock();
187
- }
188
- }
189
- async function readBoundedJson(response, maxBytes, connectionName) {
190
- const text = await readBoundedText(response, maxBytes, connectionName);
191
- return JSON.parse(text);
192
- }
193
-
194
226
  // src/tools/connections/http.ts
195
227
  async function fetchConnection(target, init, allowedHosts, connectionName) {
196
228
  const url = assertConnectionUrl(target.toString(), connectionName);
@@ -198,7 +230,7 @@ async function fetchConnection(target, init, allowedHosts, connectionName) {
198
230
  const response = await fetch(url, { ...init, redirect: "manual" });
199
231
  if (response.status >= 300 && response.status < 400) {
200
232
  await response.body?.cancel();
201
- throw new ConnectionUrlError(`Connection "${connectionName}" refused a redirect`, url.toString());
233
+ throw new ConnectionUrlError2(`Connection "${connectionName}" refused a redirect`, url.toString());
202
234
  }
203
235
  return response;
204
236
  }
@@ -213,54 +245,85 @@ function unwrap(payload, id, connectionName) {
213
245
  }
214
246
 
215
247
  // src/tools/connections/sse-frames.ts
216
- async function* readSseFrames(response, maxBytes, connectionName, totalLimit = false) {
248
+ async function* readSseFrames(response, maxBytes, connectionName, options = {}) {
217
249
  if (!response.body)
218
250
  throw new Error("MCP stream ended with no body");
219
251
  const reader = response.body.getReader();
220
252
  const decoder = new TextDecoder;
221
- let buffer = "";
253
+ const signal = options.signal ?? options.context?.signal;
254
+ const cancel = () => void reader.cancel(signal?.reason).catch(() => {
255
+ return;
256
+ });
257
+ signal?.addEventListener("abort", cancel, { once: true });
258
+ let pieces = [];
259
+ let frameBytes = 0;
260
+ let tail = 0;
222
261
  let bytes = 0;
262
+ const context = () => options.currentContext?.() ?? options.context;
263
+ const tooLarge = (observedReadBytes) => {
264
+ const current = context();
265
+ return new ConnectionResponseTooLargeError2(connectionName, current?.maxResponseBytes ?? maxBytes, { ...current, observedReadBytes });
266
+ };
223
267
  try {
224
268
  for (;; ) {
225
- const { done, value } = await reader.read();
269
+ const { done, value } = await awaitConnection(reader.read(), signal);
226
270
  if (done)
227
271
  break;
228
- if (totalLimit) {
229
- bytes += value.byteLength;
230
- if (bytes > maxBytes)
231
- throw new ConnectionResponseTooLargeError(connectionName, maxBytes);
272
+ bytes += value.byteLength;
273
+ if (options.onChunk)
274
+ options.onChunk(value.byteLength);
275
+ else if (options.context)
276
+ options.context.observedReadBytes += value.byteLength;
277
+ if (options.totalLimit && bytes > (context()?.maxResponseBytes ?? maxBytes)) {
278
+ throw tooLarge(bytes);
232
279
  }
233
- for (let offset = 0;offset < value.byteLength; offset += 1024) {
234
- buffer += decoder.decode(value.subarray(offset, offset + 1024), { stream: true });
235
- let match = /\r?\n\r?\n/.exec(buffer);
236
- while (match) {
237
- const frame = buffer.slice(0, match.index);
238
- if (new TextEncoder().encode(frame).byteLength > maxBytes)
239
- throw new ConnectionResponseTooLargeError(connectionName, maxBytes);
240
- buffer = buffer.slice(match.index + match[0].length);
241
- let event = "message";
242
- const data = [];
243
- for (const line of frame.split(/\r?\n/)) {
244
- if (line.startsWith("event:"))
245
- event = line.slice(6).trim();
246
- if (line.startsWith("data:"))
247
- data.push(line.slice(5).replace(/^ /, ""));
248
- }
249
- yield { event, data: data.join(`
250
- `) };
251
- match = /\r?\n\r?\n/.exec(buffer);
252
- }
253
- if (new TextEncoder().encode(buffer).byteLength > maxBytes)
254
- throw new ConnectionResponseTooLargeError(connectionName, maxBytes);
280
+ let start = 0;
281
+ let ceiling = context()?.maxResponseBytes ?? maxBytes;
282
+ for (let offset = 0;offset < value.byteLength; offset++) {
283
+ const byte = value[offset];
284
+ if (byte === undefined)
285
+ continue;
286
+ frameBytes++;
287
+ if (frameBytes > ceiling)
288
+ throw tooLarge(frameBytes);
289
+ tail = (tail << 8 | byte) >>> 0;
290
+ if ((tail & 65535) !== 2570 && (tail & 16777215) !== 658698)
291
+ continue;
292
+ pieces.push(value.subarray(start, offset + 1));
293
+ let text = "";
294
+ for (const piece of pieces)
295
+ text += decoder.decode(piece, { stream: true });
296
+ text += decoder.decode();
297
+ pieces = [];
298
+ frameBytes = 0;
299
+ tail = 0;
300
+ start = offset + 1;
301
+ yield parseSseFrame(text);
302
+ ceiling = context()?.maxResponseBytes ?? maxBytes;
303
+ signal?.throwIfAborted();
304
+ }
305
+ if (start < value.byteLength) {
306
+ pieces.push(value.subarray(start));
255
307
  }
256
308
  }
257
309
  } finally {
258
- await reader.cancel().catch(() => {
259
- return;
260
- });
310
+ signal?.removeEventListener("abort", cancel);
311
+ cancel();
261
312
  reader.releaseLock();
262
313
  }
263
314
  }
315
+ function parseSseFrame(text) {
316
+ let event = "message";
317
+ const data = [];
318
+ for (const line of text.split(/\r?\n/)) {
319
+ if (line.startsWith("event:"))
320
+ event = line.slice(6).trim();
321
+ if (line.startsWith("data:"))
322
+ data.push(line.slice(5).replace(/^ /, ""));
323
+ }
324
+ return { event, data: data.join(`
325
+ `) };
326
+ }
264
327
 
265
328
  // src/tools/connections/sse-session.ts
266
329
  class SseSession {
@@ -269,77 +332,125 @@ class SseSession {
269
332
  options;
270
333
  controller = new AbortController;
271
334
  pending = new Map;
335
+ active = new Set;
336
+ streamBytes = new WeakMap;
272
337
  endpoint = Promise.withResolvers();
338
+ limits;
339
+ endpointContext;
340
+ endpointReady = false;
273
341
  failure;
274
342
  constructor(connectionName, instanceId, options) {
275
343
  this.connectionName = connectionName;
276
344
  this.instanceId = instanceId;
277
345
  this.options = options;
346
+ this.limits = options.phaseLimits ?? resolveMcpLimits(options);
347
+ this.endpointContext = options.initialContext ?? connectionReadContext("endpoint", this.limits);
278
348
  this.endpoint.promise.catch(() => {
279
349
  return;
280
350
  });
281
351
  this.open();
282
352
  }
283
- async ready(signal) {
284
- return withConnectionDeadline(this.connectionName, this.options.timeoutMs, signal, async (deadline) => {
285
- const onAbort = () => this.close(deadline.reason);
286
- deadline.addEventListener("abort", onAbort, { once: true });
287
- try {
288
- deadline.throwIfAborted();
289
- return await this.endpoint.promise;
290
- } finally {
291
- deadline.removeEventListener("abort", onAbort);
292
- }
293
- });
353
+ async ready(signal, context) {
354
+ if (context) {
355
+ this.endpointContext = context;
356
+ return this.waitForEndpoint(signal);
357
+ }
358
+ const readiness = this.endpointContext;
359
+ return withConnectionDeadline(this.connectionName, readiness.timeoutMs, signal, (deadline) => this.waitForEndpoint(deadline), readiness);
294
360
  }
295
361
  close(reason = new Error("MCP SSE session closed")) {
362
+ if (this.controller.signal.aborted)
363
+ return;
296
364
  this.failure = reason;
297
365
  this.controller.abort(reason);
298
366
  this.endpoint.reject(reason);
299
367
  for (const pending of this.pending.values())
300
368
  pending.reject(reason);
301
369
  this.pending.clear();
370
+ this.active.clear();
302
371
  }
303
- async request(message, token, signal) {
304
- return withConnectionDeadline(this.connectionName, this.options.timeoutMs, signal, async (deadline) => {
305
- const abort = () => this.close(deadline.reason);
306
- deadline.addEventListener("abort", abort, { once: true });
307
- try {
308
- deadline.throwIfAborted();
309
- if (this.controller.signal.aborted)
310
- throw this.failure;
311
- const endpoint = await this.endpoint.promise;
312
- const response = Promise.withResolvers();
313
- response.promise.catch(() => {
314
- return;
315
- });
316
- if (message.id !== undefined)
317
- this.pending.set(message.id, response);
318
- const post = await fetchConnection(endpoint, {
319
- method: "POST",
320
- headers: {
321
- ...this.options.headers,
322
- "content-type": "application/json",
323
- ...token ? { authorization: `Bearer ${token}` } : {}
324
- },
325
- body: JSON.stringify(message),
326
- signal: deadline
327
- }, this.options.allowedHosts, this.connectionName);
328
- this.assertStatus(post);
329
- await post.body?.cancel();
330
- return message.id === undefined ? undefined : await response.promise;
331
- } finally {
332
- if (message.id !== undefined)
333
- this.pending.delete(message.id);
334
- deadline.removeEventListener("abort", abort);
335
- }
336
- });
372
+ async waitForEndpoint(signal) {
373
+ const onAbort = () => this.close(signal?.reason);
374
+ signal?.addEventListener("abort", onAbort, { once: true });
375
+ try {
376
+ signal?.throwIfAborted();
377
+ return await awaitConnection(this.endpoint.promise, signal);
378
+ } finally {
379
+ signal?.removeEventListener("abort", onAbort);
380
+ }
381
+ }
382
+ async request(message, token, signal, context) {
383
+ if (context)
384
+ return this.requestWithin(message, token, context, signal);
385
+ const operation = connectionReadContext(mcpOperation(message.method), this.limits);
386
+ return withConnectionDeadline(this.connectionName, operation.timeoutMs, signal, (deadline) => this.requestWithin(message, token, operation, deadline), operation);
337
387
  }
338
- assertStatus(response) {
339
- if (response.status === 401)
340
- throw new ConnectionAuthorizationRequiredError(this.connectionName, this.instanceId);
341
- if (!response.ok)
342
- throw new ConnectionRequestError(this.connectionName, response.status, "");
388
+ async requestWithin(message, token, context, signal) {
389
+ const abort = () => this.close(signal?.reason);
390
+ signal?.addEventListener("abort", abort, { once: true });
391
+ try {
392
+ signal?.throwIfAborted();
393
+ if (this.controller.signal.aborted)
394
+ throw this.failure;
395
+ const endpoint = await awaitConnection(this.endpoint.promise, signal);
396
+ const response = Promise.withResolvers();
397
+ response.promise.catch(() => {
398
+ return;
399
+ });
400
+ this.active.add(context);
401
+ if (message.id !== undefined)
402
+ this.pending.set(message.id, { ...response, context });
403
+ const post = await fetchConnection(endpoint, {
404
+ method: "POST",
405
+ headers: {
406
+ ...this.options.headers,
407
+ "content-type": "application/json",
408
+ ...token ? { authorization: `Bearer ${token}` } : {}
409
+ },
410
+ body: JSON.stringify(message),
411
+ signal
412
+ }, this.options.allowedHosts, this.connectionName);
413
+ await this.assertStatus(post, context);
414
+ await readBoundedText(post, context.maxResponseBytes, this.connectionName, context);
415
+ return message.id === undefined ? undefined : await awaitConnection(response.promise, signal);
416
+ } finally {
417
+ if (message.id !== undefined)
418
+ this.pending.delete(message.id);
419
+ this.active.delete(context);
420
+ signal?.removeEventListener("abort", abort);
421
+ }
422
+ }
423
+ async assertStatus(response, context) {
424
+ if (response.status === 401) {
425
+ response.body?.cancel().catch(() => {
426
+ return;
427
+ });
428
+ throw new ConnectionAuthorizationRequiredError2(this.connectionName, this.instanceId, context);
429
+ }
430
+ if (!response.ok) {
431
+ throw new ConnectionRequestError2(this.connectionName, response.status, await readConnectionErrorText(response, this.connectionName, context), context);
432
+ }
433
+ }
434
+ currentContext() {
435
+ if (!this.endpointReady)
436
+ return this.endpointContext;
437
+ let selected;
438
+ for (const context of this.active) {
439
+ if (!selected || context.maxResponseBytes < selected.maxResponseBytes)
440
+ selected = context;
441
+ }
442
+ return selected ?? connectionReadContext("endpoint", this.limits);
443
+ }
444
+ observeChunk(bytes) {
445
+ const active = this.endpointReady ? this.active : new Set([this.endpointContext]);
446
+ for (const context of active) {
447
+ context.observedReadBytes += bytes;
448
+ const total = (this.streamBytes.get(context) ?? 0) + bytes;
449
+ this.streamBytes.set(context, total);
450
+ if (total > context.maxResponseBytes) {
451
+ throw new ConnectionResponseTooLargeError2(this.connectionName, context.maxResponseBytes, { ...context, observedReadBytes: total });
452
+ }
453
+ }
343
454
  }
344
455
  async open() {
345
456
  try {
@@ -348,11 +459,16 @@ class SseSession {
348
459
  headers: { ...this.options.headers, accept: "text/event-stream" },
349
460
  signal: this.controller.signal
350
461
  }, this.options.allowedHosts, this.connectionName);
351
- this.assertStatus(response);
352
- for await (const frame of readSseFrames(response, this.options.maxResponseBytes, this.connectionName)) {
462
+ await this.assertStatus(response, this.endpointContext);
463
+ for await (const frame of readSseFrames(response, this.options.maxResponseBytes, this.connectionName, {
464
+ signal: this.controller.signal,
465
+ currentContext: () => this.currentContext(),
466
+ onChunk: (bytes) => this.observeChunk(bytes)
467
+ })) {
353
468
  if (frame.event === "endpoint") {
354
469
  const url = assertConnectionUrl(new URL(frame.data, this.options.baseUrl).toString(), this.connectionName);
355
470
  assertAllowedHost(url, this.options.allowedHosts, this.connectionName);
471
+ this.endpointReady = true;
356
472
  this.endpoint.resolve(url.toString());
357
473
  } else if (frame.event === "message" && frame.data) {
358
474
  const message = JSON.parse(frame.data);
@@ -389,14 +505,18 @@ class McpHttpClient {
389
505
  protocolVersion;
390
506
  nextId = 1;
391
507
  initialized = false;
508
+ initialInitializePending = true;
392
509
  sse;
510
+ limits;
393
511
  constructor(connectionName, instanceId, options) {
394
512
  this.connectionName = connectionName;
395
513
  this.instanceId = instanceId;
396
514
  this.options = options;
515
+ this.limits = resolveMcpLimits(options);
397
516
  }
398
517
  teardown() {
399
518
  this.initialized = false;
519
+ this.initialInitializePending = true;
400
520
  this.sessionId = undefined;
401
521
  this.sse?.close();
402
522
  this.sse = undefined;
@@ -413,71 +533,85 @@ class McpHttpClient {
413
533
  if (isRecord(result) && typeof result.protocolVersion === "string") {
414
534
  this.protocolVersion = result.protocolVersion;
415
535
  }
416
- this.initialized = true;
417
536
  try {
418
537
  await this.dispatch({ jsonrpc: "2.0", method: "notifications/initialized" }, token, signal);
419
- } catch {}
538
+ } catch (error) {
539
+ signal?.throwIfAborted();
540
+ if (error instanceof ConnectionTimeoutError2 || error instanceof ConnectionResponseTooLargeError2) {
541
+ throw error;
542
+ }
543
+ }
544
+ this.initialized = true;
420
545
  }
421
546
  request(method, params, token, signal) {
422
547
  return this.dispatch({ jsonrpc: "2.0", id: this.nextId++, method, params }, token, signal);
423
548
  }
424
549
  async dispatch(message, token, signal) {
425
- if (this.mode === "sse")
426
- return this.sseRequest(message, token, signal);
427
- try {
428
- return await this.streamableRequest(message, token, signal);
429
- } catch (error) {
430
- if (error instanceof ConnectionRequestError && FALLBACK_STATUSES.has(error.status)) {
431
- await this.openSse(token, signal);
432
- this.mode = "sse";
433
- return this.sseRequest(message, token, signal);
550
+ const context = connectionReadContext(mcpOperation(message.method), this.limits);
551
+ const negotiate = message.method === "initialize" && this.initialInitializePending;
552
+ if (message.method === "initialize")
553
+ this.initialInitializePending = false;
554
+ return withConnectionDeadline(this.connectionName, context.timeoutMs, signal, async () => {
555
+ if (this.mode === "sse")
556
+ return this.sseRequest(message, token, context);
557
+ try {
558
+ return await this.streamableRequest(message, token, context);
559
+ } catch (error) {
560
+ context.signal?.throwIfAborted();
561
+ if (negotiate && error instanceof ConnectionRequestError2 && FALLBACK_STATUSES.has(error.status)) {
562
+ await this.openSse(token, context);
563
+ this.mode = "sse";
564
+ return this.sseRequest(message, token, context);
565
+ }
566
+ throw error;
434
567
  }
435
- throw error;
436
- }
568
+ }, context);
437
569
  }
438
- streamableRequest(message, token, signal) {
570
+ async streamableRequest(message, token, context) {
439
571
  const target = new URL(this.options.transport.url);
440
572
  assertAllowedHost(target, this.options.allowedHosts, this.connectionName);
441
- return withConnectionDeadline(this.connectionName, this.options.timeoutMs, signal, async (deadline) => {
442
- const response = await fetchConnection(target, {
443
- method: "POST",
444
- headers: this.headers(token),
445
- body: JSON.stringify(message),
446
- signal: deadline
447
- }, this.options.allowedHosts, this.connectionName);
448
- if (response.status === 401) {
449
- throw new ConnectionAuthorizationRequiredError(this.connectionName, this.instanceId);
450
- }
451
- if (!response.ok) {
452
- throw new ConnectionRequestError(this.connectionName, response.status, await this.safeText(response));
453
- }
454
- const session = response.headers.get("mcp-session-id");
455
- if (session)
456
- this.sessionId = session;
457
- if (message.id === undefined) {
458
- await response.body?.cancel();
573
+ context.signal?.throwIfAborted();
574
+ const response = await fetchConnection(target, {
575
+ method: "POST",
576
+ headers: this.headers(token),
577
+ body: JSON.stringify(message),
578
+ signal: context.signal
579
+ }, this.options.allowedHosts, this.connectionName);
580
+ if (response.status === 401) {
581
+ response.body?.cancel().catch(() => {
459
582
  return;
583
+ });
584
+ throw new ConnectionAuthorizationRequiredError2(this.connectionName, this.instanceId, context);
585
+ }
586
+ if (!response.ok) {
587
+ throw new ConnectionRequestError2(this.connectionName, response.status, await readConnectionErrorText(response, this.connectionName, context), context);
588
+ }
589
+ const session = response.headers.get("mcp-session-id");
590
+ if (session)
591
+ this.sessionId = session;
592
+ if (message.id === undefined) {
593
+ await readBoundedText(response, context.maxResponseBytes, this.connectionName, context);
594
+ return;
595
+ }
596
+ const contentType = response.headers.get("content-type") ?? "";
597
+ if (contentType.includes("text/event-stream")) {
598
+ for await (const frame of readSseFrames(response, context.maxResponseBytes, this.connectionName, { totalLimit: true, context })) {
599
+ if (!frame.data)
600
+ continue;
601
+ const payload = JSON.parse(frame.data);
602
+ if (isRecord(payload) && payload.id === message.id)
603
+ return unwrap(payload, message.id, this.connectionName);
460
604
  }
461
- const contentType = response.headers.get("content-type") ?? "";
462
- if (contentType.includes("text/event-stream")) {
463
- for await (const frame of readSseFrames(response, this.options.maxResponseBytes, this.connectionName, true)) {
464
- if (!frame.data)
465
- continue;
466
- const payload = JSON.parse(frame.data);
467
- if (isRecord(payload) && payload.id === message.id)
468
- return unwrap(payload, message.id, this.connectionName);
469
- }
470
- throw new Error("MCP stream ended without a response");
471
- }
472
- const text = await readBoundedText(response, this.options.maxResponseBytes, this.connectionName);
473
- let payload;
474
- try {
475
- payload = JSON.parse(text);
476
- } catch {
477
- throw new Error(`MCP response from "${this.connectionName}" was not JSON`);
478
- }
479
- return unwrap(payload, message.id, this.connectionName);
480
- });
605
+ throw new Error("MCP stream ended without a response");
606
+ }
607
+ const text = await readBoundedText(response, context.maxResponseBytes, this.connectionName, context);
608
+ let payload;
609
+ try {
610
+ payload = JSON.parse(text);
611
+ } catch {
612
+ throw new Error(`MCP response from "${this.connectionName}" was not JSON`);
613
+ }
614
+ return unwrap(payload, message.id, this.connectionName);
481
615
  }
482
616
  headers(token) {
483
617
  const headers = {
@@ -493,31 +627,26 @@ class McpHttpClient {
493
627
  headers["mcp-protocol-version"] = this.protocolVersion;
494
628
  return headers;
495
629
  }
496
- async openSse(token, signal) {
630
+ async openSse(token, context) {
497
631
  this.sse?.close();
498
632
  this.sse = new SseSession(this.connectionName, this.instanceId, {
499
633
  baseUrl: this.options.transport.url,
500
634
  allowedHosts: this.options.allowedHosts,
501
635
  headers: this.headers(token),
502
636
  timeoutMs: this.options.timeoutMs,
503
- maxResponseBytes: this.options.maxResponseBytes
637
+ maxResponseBytes: this.options.maxResponseBytes,
638
+ phaseLimits: this.limits,
639
+ initialContext: context
504
640
  });
505
- await this.sse.ready(signal);
641
+ await this.sse.ready(context.signal, context);
506
642
  }
507
- async sseRequest(message, token, signal) {
643
+ async sseRequest(message, token, context) {
508
644
  if (!this.sse)
509
- await this.openSse(token, signal);
645
+ await this.openSse(token, context);
510
646
  const session = this.sse;
511
647
  if (!session)
512
648
  throw new Error("MCP SSE session failed to open");
513
- return session.request(message, token, signal);
514
- }
515
- async safeText(response) {
516
- try {
517
- return (await readBoundedText(response, this.options.maxResponseBytes, this.connectionName)).slice(0, 512);
518
- } catch {
519
- return "";
520
- }
649
+ return session.request(message, token, context.signal, context);
521
650
  }
522
651
  }
523
652
 
@@ -558,7 +687,7 @@ async function withConnectionToken(params, body) {
558
687
  try {
559
688
  return await body(token);
560
689
  } catch (error) {
561
- if (error instanceof ConnectionAuthorizationRequiredError) {
690
+ if (error instanceof ConnectionAuthorizationRequiredError2) {
562
691
  params.client?.teardown();
563
692
  }
564
693
  throw error;
@@ -620,7 +749,8 @@ async function mountMcpConnection(connection, instanceId, onSkipped) {
620
749
  transport: connection.transport,
621
750
  allowedHosts,
622
751
  timeoutMs: connectionTimeoutMs(connection.timeoutMs),
623
- maxResponseBytes: connectionMaxResponseBytes(connection.maxResponseBytes)
752
+ maxResponseBytes: connectionMaxResponseBytes(connection.maxResponseBytes),
753
+ ...connection.limits && { limits: connection.limits }
624
754
  });
625
755
  const discovery = createClient();
626
756
  const listed = await withConnectionToken({ instanceId, provider: connection.token, client: discovery }, async (token) => {
@@ -795,6 +925,10 @@ async function mountOpenApiConnection(connection, instanceId, onSkipped) {
795
925
  }
796
926
  const body = requestBody(entry.operation);
797
927
  applyAuth(auth, token, url, headers);
928
+ const readContext = connectionReadContext("openapi/call", {
929
+ discovery: { timeoutMs, maxResponseBytes },
930
+ call: { timeoutMs, maxResponseBytes }
931
+ });
798
932
  return withConnectionDeadline(connection.name, timeoutMs, context.signal, async (signal) => {
799
933
  const requestInit = { method: entry.method, headers, signal };
800
934
  if (body && args.body !== undefined) {
@@ -804,13 +938,13 @@ async function mountOpenApiConnection(connection, instanceId, onSkipped) {
804
938
  const response = await fetchConnection(url, requestInit, allowedHosts, connection.name);
805
939
  if (response.status === 401) {
806
940
  await response.body?.cancel();
807
- throw new ConnectionAuthorizationRequiredError(connection.name, instanceId);
941
+ throw new ConnectionAuthorizationRequiredError2(connection.name, instanceId, readContext);
808
942
  }
809
943
  if (!response.ok) {
810
- throw new ConnectionRequestError(connection.name, response.status, (await safeText(response, maxResponseBytes)).slice(0, 512));
944
+ throw new ConnectionRequestError2(connection.name, response.status, await readConnectionErrorText(response, connection.name, readContext), readContext);
811
945
  }
812
- return readResponse(response, maxResponseBytes, connection.name);
813
- });
946
+ return readResponse(response, maxResponseBytes, connection.name, readContext);
947
+ }, readContext);
814
948
  })
815
949
  });
816
950
  recordForeignSchemaBytes(definition, jsonSchemaBytes(schema));
@@ -832,13 +966,17 @@ async function loadOpenApiDocument(connection, timeoutMs, maxResponseBytes) {
832
966
  return { document: parsed };
833
967
  }
834
968
  const url = assertConnectionUrl(text, connection.name);
969
+ const readContext = connectionReadContext("openapi/spec", {
970
+ discovery: { timeoutMs, maxResponseBytes },
971
+ call: { timeoutMs, maxResponseBytes }
972
+ });
835
973
  const parsed = await withConnectionDeadline(connection.name, timeoutMs, undefined, async (signal) => {
836
974
  const response = await fetchConnection(url, { signal }, connectionAllowedHosts(url, connection.allowHosts), connection.name);
837
975
  if (!response.ok) {
838
- throw new ConnectionRequestError(connection.name, response.status, "");
976
+ throw new ConnectionRequestError2(connection.name, response.status, await readConnectionErrorText(response, connection.name, readContext), readContext);
839
977
  }
840
- return readBoundedJson(response, maxResponseBytes, connection.name);
841
- });
978
+ return readBoundedJson(response, maxResponseBytes, connection.name, readContext);
979
+ }, readContext);
842
980
  if (!isRecord(parsed))
843
981
  throw new Error(`OpenAPI spec at ${url} is not an object`);
844
982
  return { document: parsed, url: url.toString() };
@@ -1045,20 +1183,13 @@ function buildOperationUrl(baseUrl, entry, args) {
1045
1183
  const resolved = `${origin.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
1046
1184
  return new URL(resolved);
1047
1185
  }
1048
- async function readResponse(response, maxBytes, connectionName) {
1186
+ async function readResponse(response, maxBytes, connectionName, context) {
1049
1187
  if (response.status === 204)
1050
1188
  return null;
1051
1189
  const contentType = response.headers.get("content-type") ?? "";
1052
1190
  if (contentType.includes("json"))
1053
- return readBoundedJson(response, maxBytes, connectionName);
1054
- return readBoundedText(response, maxBytes, connectionName);
1055
- }
1056
- async function safeText(response, maxBytes) {
1057
- try {
1058
- return await readBoundedText(response, maxBytes, "response");
1059
- } catch {
1060
- return "";
1061
- }
1191
+ return readBoundedJson(response, maxBytes, connectionName, context);
1192
+ return readBoundedText(response, maxBytes, connectionName, context);
1062
1193
  }
1063
1194
  function asRecord(value) {
1064
1195
  return isRecord(value) ? value : undefined;
@@ -1075,13 +1206,13 @@ async function mountConnections(connections, options = {}) {
1075
1206
  }
1076
1207
  const limit = options.budget?.maxTools;
1077
1208
  if (limit !== undefined && tools.length > limit) {
1078
- throw new ConnectionBudgetExceededError(limit, tools.length);
1209
+ throw new ConnectionBudgetExceededError2(limit, tools.length);
1079
1210
  }
1080
1211
  const schemaLimit = options.budget?.maxSchemaBytes;
1081
1212
  if (schemaLimit !== undefined) {
1082
1213
  const schemaBytes = tools.reduce((total, tool) => total + foreignSchemaBytes(tool), 0);
1083
1214
  if (schemaBytes > schemaLimit) {
1084
- throw new ConnectionBudgetExceededError(schemaLimit, schemaBytes, "schema bytes");
1215
+ throw new ConnectionBudgetExceededError2(schemaLimit, schemaBytes, "schema bytes");
1085
1216
  }
1086
1217
  }
1087
1218
  return tools;
@@ -1100,10 +1231,12 @@ function instanceIdOf(connection) {
1100
1231
  return connectionInstanceId(connection.name, url, connection.instanceKey);
1101
1232
  }
1102
1233
  export {
1103
- ConnectionAuthorizationRequiredError,
1104
- ConnectionBudgetExceededError,
1105
- ConnectionRequestError,
1106
- ConnectionUrlError,
1234
+ ConnectionAuthorizationRequiredError2 as ConnectionAuthorizationRequiredError,
1235
+ ConnectionBudgetExceededError2 as ConnectionBudgetExceededError,
1236
+ ConnectionRequestError2 as ConnectionRequestError,
1237
+ ConnectionResponseTooLargeError2 as ConnectionResponseTooLargeError,
1238
+ ConnectionTimeoutError2 as ConnectionTimeoutError,
1239
+ ConnectionUrlError2 as ConnectionUrlError,
1107
1240
  defineMcpClientConnection,
1108
1241
  defineOpenApiConnection,
1109
1242
  mountConnections