stitchkit 0.103.13 → 0.104.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 +120 -0
- package/darwin-native.cjs +1 -3
- package/dist/agent-runtime/store-conformance-lifecycle.d.ts +10 -10
- package/dist/agent-runtime/store-conformance-support.d.ts +5 -5
- package/dist/application/directory-inbox-accept.d.ts +13 -0
- package/dist/application/directory-inbox-accept.d.ts.map +1 -0
- package/dist/application/directory-inbox-contract.d.ts +42 -5
- package/dist/application/directory-inbox-contract.d.ts.map +1 -1
- package/dist/application/directory-inbox-delivery.d.ts +14 -0
- package/dist/application/directory-inbox-delivery.d.ts.map +1 -0
- package/dist/application/directory-inbox-files.d.ts +28 -0
- package/dist/application/directory-inbox-files.d.ts.map +1 -0
- package/dist/application/directory-inbox-state.d.ts +14 -0
- package/dist/application/directory-inbox-state.d.ts.map +1 -0
- package/dist/application/directory-inbox.d.ts +2 -1
- package/dist/application/directory-inbox.d.ts.map +1 -1
- package/dist/application/file-state-store.d.ts +2 -8
- package/dist/application/file-state-store.d.ts.map +1 -1
- package/dist/application/state-store.d.ts +6 -1
- package/dist/application/state-store.d.ts.map +1 -1
- package/dist/browser/client-arguments.d.ts +4 -0
- package/dist/browser/client-arguments.d.ts.map +1 -0
- package/dist/browser/client-executors.d.ts.map +1 -1
- package/dist/browser/client.d.ts.map +1 -1
- package/dist/browser/contract-stream.d.ts.map +1 -1
- package/dist/browser/http.d.ts +2 -1
- package/dist/browser/http.d.ts.map +1 -1
- package/dist/browser/native-fetch.d.ts +8 -0
- package/dist/browser/native-fetch.d.ts.map +1 -0
- package/dist/contract/errors.d.ts +16 -12
- package/dist/contract/errors.d.ts.map +1 -1
- package/dist/contract/normalize.d.ts +5 -1
- package/dist/contract/normalize.d.ts.map +1 -1
- package/dist/contract/stream.d.ts +1 -0
- package/dist/contract/stream.d.ts.map +1 -1
- package/dist/entrypoints/agent-runtime/browser.js +3 -3
- package/dist/entrypoints/agent-runtime/coding-tools.js +6 -6
- package/dist/entrypoints/agent-runtime/harness-tools.js +8 -8
- package/dist/entrypoints/agent-runtime/harness.js +11 -11
- package/dist/entrypoints/agent-runtime/realtime.js +3 -3
- package/dist/entrypoints/agent-runtime/sandbox.js +6 -6
- package/dist/entrypoints/agent-runtime.js +13 -13
- package/dist/entrypoints/application/diagnostic-journal.js +1 -1
- package/dist/entrypoints/application/directory-inbox.js +424 -119
- package/dist/entrypoints/application/grammy.js +1 -1
- package/dist/entrypoints/application.d.ts +2 -2
- package/dist/entrypoints/application.d.ts.map +1 -1
- package/dist/entrypoints/application.js +7 -3
- package/dist/entrypoints/cli.d.ts +2 -0
- package/dist/entrypoints/cli.d.ts.map +1 -1
- package/dist/entrypoints/cli.js +757 -136
- package/dist/entrypoints/contract.js +3 -3
- package/dist/entrypoints/files/packaging.d.ts +3 -0
- package/dist/entrypoints/files/packaging.d.ts.map +1 -0
- package/dist/entrypoints/files/packaging.js +133 -0
- package/dist/entrypoints/files.js +10 -107
- package/dist/entrypoints/index.js +7 -7
- package/dist/entrypoints/node.js +8 -8
- package/dist/entrypoints/observability.js +2 -2
- package/dist/entrypoints/react.js +1 -1
- package/dist/entrypoints/remote.js +19 -6
- package/dist/entrypoints/server.d.ts +1 -1
- package/dist/entrypoints/server.d.ts.map +1 -1
- package/dist/entrypoints/server.js +23 -17
- package/dist/entrypoints/telegram.d.ts +1 -0
- package/dist/entrypoints/telegram.d.ts.map +1 -1
- package/dist/entrypoints/telegram.js +214 -98
- package/dist/entrypoints/testing.js +7 -7
- package/dist/entrypoints/tools/connections.d.ts +2 -2
- package/dist/entrypoints/tools/connections.d.ts.map +1 -1
- package/dist/entrypoints/tools/connections.js +427 -294
- package/dist/entrypoints/tools/invoker.js +6 -6
- package/dist/entrypoints/tools/mcp.d.ts +12 -0
- package/dist/entrypoints/tools/mcp.d.ts.map +1 -0
- package/dist/entrypoints/tools/mcp.js +45 -0
- package/dist/entrypoints/tools.d.ts +1 -0
- package/dist/entrypoints/tools.d.ts.map +1 -1
- package/dist/entrypoints/tools.js +82 -1147
- package/dist/entrypoints/tracking.js +1 -1
- package/dist/files/boundary.d.ts +2 -8
- package/dist/files/boundary.d.ts.map +1 -1
- package/dist/files/file-io.d.ts +0 -5
- package/dist/files/file-io.d.ts.map +1 -1
- package/dist/files/native-packaging-layout.d.ts +12 -0
- package/dist/files/native-packaging-layout.d.ts.map +1 -0
- package/dist/files/native-packaging.d.ts +72 -0
- package/dist/files/native-packaging.d.ts.map +1 -0
- package/dist/files/read.d.ts +2 -2
- package/dist/files/read.d.ts.map +1 -1
- package/dist/index-1a6jtnd9.js +88 -0
- package/dist/index-269zqj8m.js +77 -0
- package/dist/{index-r34mvse1.js → index-27a2pj09.js} +1 -1
- package/dist/{index-r956rndn.js → index-2hyavjjj.js} +3 -3
- package/dist/{index-m2xqkv55.js → index-2vnh05w4.js} +83 -14
- package/dist/{index-pw99k9h5.js → index-43p08g7t.js} +1 -1
- package/dist/{index-xwq9j1sf.js → index-4e0ah203.js} +9 -7
- package/dist/{index-qapzv4p5.js → index-5z6jn897.js} +1 -1
- package/dist/{index-ahvsksw5.js → index-6d9xaxw1.js} +1 -1
- package/dist/{index-8zfsgar0.js → index-6drst9sg.js} +11 -5
- package/dist/{index-x87j7csh.js → index-77nxk6ay.js} +2 -2
- package/dist/{index-dnn9z96q.js → index-a0zzh8ak.js} +3 -3
- package/dist/{index-kmpzj1cq.js → index-a3wma1hk.js} +1 -1
- package/dist/{index-j8aw2c0e.js → index-bakgedh8.js} +35 -7
- package/dist/index-bhb950qs.js +1145 -0
- package/dist/{index-tgzhq07n.js → index-cvda079a.js} +1 -1
- package/dist/{index-n2gre124.js → index-fa4wg312.js} +4 -4
- package/dist/{index-zp4jpkp0.js → index-fk18k56k.js} +2 -2
- package/dist/{index-afjvjyck.js → index-g2j7pnak.js} +2 -134
- package/dist/index-g95fk660.js +116 -0
- package/dist/{index-r5c4fedv.js → index-h2fhjqy7.js} +2 -1
- package/dist/{index-sybedc8b.js → index-hbd32c6a.js} +23 -94
- package/dist/{index-fyphpknh.js → index-hp9d77wk.js} +1 -1
- package/dist/index-jd470k6q.js +109 -0
- package/dist/{index-b2bz0kdm.js → index-kb8styz3.js} +4 -3
- package/dist/{index-0c419yx7.js → index-kemm0ej5.js} +12 -9
- package/dist/{index-db2spkv1.js → index-ptn0g0gj.js} +3 -3
- package/dist/{index-w575evhq.js → index-r4r70zmh.js} +1 -1
- package/dist/{index-12m2v2a9.js → index-s4evrmp5.js} +1 -1
- package/dist/{index-1khy8y7n.js → index-t1sr4aks.js} +3 -3
- package/dist/{index-45tdvqa3.js → index-wh0ecvyk.js} +1 -1
- package/dist/{index-n53mswt3.js → index-x928baz2.js} +8 -1
- package/dist/index-yk4g8805.js +133 -0
- package/dist/{index-ygkka6zn.js → index-ywppsaby.js} +28 -5
- package/dist/{index-9853ymwb.js → index-za6j7nwy.js} +18 -4
- package/dist/internal/bounded-file-read.d.ts +41 -0
- package/dist/internal/bounded-file-read.d.ts.map +1 -0
- package/dist/internal/exclusive-lock.d.ts +2 -0
- package/dist/internal/exclusive-lock.d.ts.map +1 -1
- package/dist/internal/with-exclusive-lock.d.ts +4 -2
- package/dist/internal/with-exclusive-lock.d.ts.map +1 -1
- package/dist/telegram/broadcast-delivery.d.ts +23 -0
- package/dist/telegram/broadcast-delivery.d.ts.map +1 -0
- package/dist/telegram/broadcast-failure.d.ts +22 -0
- package/dist/telegram/broadcast-failure.d.ts.map +1 -0
- package/dist/telegram/broadcast-state.d.ts +6 -3
- package/dist/telegram/broadcast-state.d.ts.map +1 -1
- package/dist/telegram/broadcast.d.ts +10 -5
- package/dist/telegram/broadcast.d.ts.map +1 -1
- package/dist/tools/cli/format.d.ts.map +1 -1
- package/dist/tools/cli/invoke.d.ts +2 -2
- package/dist/tools/cli/invoke.d.ts.map +1 -1
- package/dist/tools/cli/publication-assets.d.ts +10 -0
- package/dist/tools/cli/publication-assets.d.ts.map +1 -0
- package/dist/tools/cli/publication-contract.d.ts +75 -0
- package/dist/tools/cli/publication-contract.d.ts.map +1 -0
- package/dist/tools/cli/publication-control.d.ts +4 -0
- package/dist/tools/cli/publication-control.d.ts.map +1 -0
- package/dist/tools/cli/publication-storage.d.ts +32 -0
- package/dist/tools/cli/publication-storage.d.ts.map +1 -0
- package/dist/tools/cli/publication.d.ts +35 -0
- package/dist/tools/cli/publication.d.ts.map +1 -0
- package/dist/tools/cli/update.d.ts.map +1 -1
- package/dist/tools/connections/define.d.ts.map +1 -1
- package/dist/tools/connections/error-projection.d.ts +4 -0
- package/dist/tools/connections/error-projection.d.ts.map +1 -0
- package/dist/tools/connections/errors.d.ts +24 -8
- package/dist/tools/connections/errors.d.ts.map +1 -1
- package/dist/tools/connections/limits.d.ts +11 -31
- package/dist/tools/connections/limits.d.ts.map +1 -1
- package/dist/tools/connections/mcp-client.d.ts +6 -4
- package/dist/tools/connections/mcp-client.d.ts.map +1 -1
- package/dist/tools/connections/mcp.d.ts.map +1 -1
- package/dist/tools/connections/openapi.d.ts.map +1 -1
- package/dist/tools/connections/operation-limits.d.ts +25 -0
- package/dist/tools/connections/operation-limits.d.ts.map +1 -0
- package/dist/tools/connections/sse-frames.d.ts +11 -2
- package/dist/tools/connections/sse-frames.d.ts.map +1 -1
- package/dist/tools/connections/sse-session.d.ts +15 -2
- package/dist/tools/connections/sse-session.d.ts.map +1 -1
- package/dist/tools/connections/types.d.ts +19 -1
- package/dist/tools/connections/types.d.ts.map +1 -1
- package/dist/tools/execute-result.d.ts.map +1 -1
- package/dist/tools/execute.d.ts.map +1 -1
- package/dist/tools/internal/observe-hook.d.ts +3 -0
- package/dist/tools/internal/observe-hook.d.ts.map +1 -0
- package/dist/tools/internal/surface-projector.d.ts +2 -1
- package/dist/tools/internal/surface-projector.d.ts.map +1 -1
- package/dist/tools/mcp/mount.d.ts +2 -2
- package/dist/tools/mcp/mount.d.ts.map +1 -1
- package/dist/tools/mcp/prepare.d.ts +4 -5
- package/dist/tools/mcp/prepare.d.ts.map +1 -1
- package/dist/tools/mcp/register.d.ts +4 -1
- package/dist/tools/mcp/register.d.ts.map +1 -1
- package/dist/tools/mcp/runtime-tools.d.ts.map +1 -1
- package/dist/tools/remote.d.ts.map +1 -1
- package/dist/tools/runtime-tool-execution.d.ts +65 -0
- package/dist/tools/runtime-tool-execution.d.ts.map +1 -0
- package/dist/tools/runtime-tool-mcp.d.ts +27 -0
- package/dist/tools/runtime-tool-mcp.d.ts.map +1 -0
- package/dist/tools/runtime-tool.d.ts +9 -60
- package/dist/tools/runtime-tool.d.ts.map +1 -1
- package/dist/tools/surface.d.ts +12 -14
- package/dist/tools/surface.d.ts.map +1 -1
- package/llms/application.2.txt +221 -44
- package/llms/application.3.txt +3 -1
- package/llms/application.txt +1 -1
- package/llms/cli.2.txt +144 -1
- package/llms/cli.txt +123 -104
- package/llms/contract.txt +1 -1
- package/llms/files-packaging.txt +27 -0
- package/llms/process.txt +63 -1
- package/llms/server.4.txt +4 -1
- package/llms/server.5.txt +1 -0
- package/llms/stitchkit.2.txt +14 -0
- package/llms/stitchkit.txt +6 -4
- package/llms/telegram.txt +64 -2
- package/llms/tools-connections.txt +3 -0
- package/llms/tools-mcp.txt +29 -0
- package/llms/tools.2.txt +59 -0
- package/llms/tools.3.txt +1 -0
- package/llms/tools.txt +11 -52
- package/llms/upgrading-0.37.0-0.44.0.txt +444 -0
- package/llms/{upgrading-0.44.0-0.60.0.txt → upgrading-0.44.0-0.61.0.txt} +140 -271
- package/llms/{upgrading-0.61.0-0.74.0.txt → upgrading-0.62.0-0.80.0.txt} +217 -140
- package/llms/{upgrading-0.75.0-0.103.0.txt → upgrading-0.81.0-0.104.0.txt} +154 -217
- package/llms/upgrading.txt +4 -4
- package/llms-full.txt +745 -60
- package/llms.txt +14 -12
- package/native-assets.json +8 -0
- package/package.json +12 -3
- package/dist/index-wykebpn9.js +0 -221
- package/llms/upgrading-0.37.0.txt +0 -176
- 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-
|
|
5
|
+
} from "../../index-77nxk6ay.js";
|
|
6
6
|
import {
|
|
7
7
|
AppError2
|
|
8
|
-
} from "../../index-
|
|
9
|
-
import"../../index-
|
|
8
|
+
} from "../../index-x928baz2.js";
|
|
9
|
+
import"../../index-kb8styz3.js";
|
|
10
10
|
import {
|
|
11
11
|
defineRuntimeTool2
|
|
12
|
-
} from "../../index-
|
|
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/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
259
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
if (
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
return this.
|
|
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
|
-
|
|
436
|
-
}
|
|
568
|
+
}, context);
|
|
437
569
|
}
|
|
438
|
-
streamableRequest(message, token,
|
|
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
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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,
|
|
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,
|
|
643
|
+
async sseRequest(message, token, context) {
|
|
508
644
|
if (!this.sse)
|
|
509
|
-
await this.openSse(token,
|
|
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
|
|
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
|
|
941
|
+
throw new ConnectionAuthorizationRequiredError2(connection.name, instanceId, readContext);
|
|
808
942
|
}
|
|
809
943
|
if (!response.ok) {
|
|
810
|
-
throw new
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|