@zackbart/connecta 0.7.9 → 0.8.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 +135 -0
- package/README.md +47 -148
- package/dist/activity.d.ts +0 -8
- package/dist/activity.d.ts.map +1 -1
- package/dist/activity.js +0 -1
- package/dist/activity.js.map +1 -1
- package/dist/auth/bearer.d.ts +1 -7
- package/dist/auth/bearer.d.ts.map +1 -1
- package/dist/auth/bearer.js +3 -24
- package/dist/auth/bearer.js.map +1 -1
- package/dist/auth/clerk.d.ts +2 -11
- package/dist/auth/clerk.d.ts.map +1 -1
- package/dist/auth/clerk.js +31 -46
- package/dist/auth/clerk.js.map +1 -1
- package/dist/call-admission.d.ts.map +1 -1
- package/dist/call-admission.js +9 -4
- package/dist/call-admission.js.map +1 -1
- package/dist/catalog-fingerprint.d.ts +20 -0
- package/dist/catalog-fingerprint.d.ts.map +1 -0
- package/dist/catalog-fingerprint.js +34 -0
- package/dist/catalog-fingerprint.js.map +1 -0
- package/dist/catalog-limits.d.ts +10 -0
- package/dist/catalog-limits.d.ts.map +1 -0
- package/dist/catalog-limits.js +10 -0
- package/dist/catalog-limits.js.map +1 -0
- package/dist/catalog-service.d.ts +137 -0
- package/dist/catalog-service.d.ts.map +1 -0
- package/dist/catalog-service.js +401 -0
- package/dist/catalog-service.js.map +1 -0
- package/dist/catalog.d.ts +6 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +53 -2
- package/dist/catalog.js.map +1 -1
- package/dist/concurrency.d.ts +4 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +31 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/connectors/api.d.ts.map +1 -1
- package/dist/connectors/api.js +22 -12
- package/dist/connectors/api.js.map +1 -1
- package/dist/connectors/remote-mcp.d.ts.map +1 -1
- package/dist/connectors/remote-mcp.js +53 -41
- package/dist/connectors/remote-mcp.js.map +1 -1
- package/dist/credentials.d.ts +3 -2
- package/dist/credentials.d.ts.map +1 -1
- package/dist/errors.d.ts +19 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +10 -16
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +143 -330
- package/dist/execute.js.map +1 -1
- package/dist/executor-result.d.ts +0 -3
- package/dist/executor-result.d.ts.map +1 -1
- package/dist/executor-result.js +3 -3
- package/dist/executor-result.js.map +1 -1
- package/dist/executors/quickjs-child.js +3 -3
- package/dist/executors/quickjs-child.js.map +1 -1
- package/dist/executors/quickjs-protocol.d.ts +4 -1
- package/dist/executors/quickjs-protocol.d.ts.map +1 -1
- package/dist/executors/quickjs-runtime.d.ts.map +1 -1
- package/dist/executors/quickjs-runtime.js +7 -2
- package/dist/executors/quickjs-runtime.js.map +1 -1
- package/dist/executors/quickjs.d.ts.map +1 -1
- package/dist/executors/quickjs.js +4 -1
- package/dist/executors/quickjs.js.map +1 -1
- package/dist/favicon.js +2 -2
- package/dist/favicon.js.map +1 -1
- package/dist/index.d.ts +8 -82
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +58 -104
- package/dist/index.js.map +1 -1
- package/dist/invocation.d.ts +101 -0
- package/dist/invocation.d.ts.map +1 -0
- package/dist/invocation.js +405 -0
- package/dist/invocation.js.map +1 -0
- package/dist/meta-tools.d.ts +9 -95
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +235 -725
- package/dist/meta-tools.js.map +1 -1
- package/dist/operator-ui/generated.d.ts +3 -0
- package/dist/operator-ui/generated.d.ts.map +1 -0
- package/dist/operator-ui/generated.js +5 -0
- package/dist/operator-ui/generated.js.map +1 -0
- package/dist/operator-ui/model.d.ts +73 -0
- package/dist/operator-ui/model.d.ts.map +1 -0
- package/dist/operator-ui/model.js +28 -0
- package/dist/operator-ui/model.js.map +1 -0
- package/dist/registry.d.ts +33 -184
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +345 -330
- package/dist/registry.js.map +1 -1
- package/dist/retired-toolkits.d.ts +3 -0
- package/dist/retired-toolkits.d.ts.map +1 -0
- package/dist/retired-toolkits.js +9 -0
- package/dist/retired-toolkits.js.map +1 -0
- package/dist/routes/activity.d.ts +3 -0
- package/dist/routes/activity.d.ts.map +1 -0
- package/dist/routes/activity.js +170 -0
- package/dist/routes/activity.js.map +1 -0
- package/dist/routes/credentials.d.ts +3 -0
- package/dist/routes/credentials.d.ts.map +1 -0
- package/dist/routes/credentials.js +170 -0
- package/dist/routes/credentials.js.map +1 -0
- package/dist/routes/mcp.d.ts +8 -0
- package/dist/routes/mcp.d.ts.map +1 -0
- package/dist/routes/mcp.js +270 -0
- package/dist/routes/mcp.js.map +1 -0
- package/dist/routes/oauth.d.ts +4 -0
- package/dist/routes/oauth.d.ts.map +1 -0
- package/dist/routes/oauth.js +263 -0
- package/dist/routes/oauth.js.map +1 -0
- package/dist/routes/shared.d.ts +76 -0
- package/dist/routes/shared.d.ts.map +1 -0
- package/dist/routes/shared.js +140 -0
- package/dist/routes/shared.js.map +1 -0
- package/dist/routes/ui.d.ts +3 -0
- package/dist/routes/ui.d.ts.map +1 -0
- package/dist/routes/ui.js +116 -0
- package/dist/routes/ui.js.map +1 -0
- package/dist/server.d.ts +9 -40
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +62 -1322
- package/dist/server.js.map +1 -1
- package/dist/skills.d.ts +3 -21
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +15 -43
- package/dist/skills.js.map +1 -1
- package/dist/storage/file.d.ts.map +1 -1
- package/dist/storage/file.js +3 -1
- package/dist/storage/file.js.map +1 -1
- package/dist/storage/memory.d.ts.map +1 -1
- package/dist/storage/memory.js +3 -1
- package/dist/storage/memory.js.map +1 -1
- package/dist/timeout.d.ts.map +1 -1
- package/dist/timeout.js +2 -4
- package/dist/timeout.js.map +1 -1
- package/dist/types.d.ts +9 -59
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts +3 -94
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +30 -1427
- package/dist/ui.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +15 -8
- package/src/activity.ts +0 -9
- package/src/auth/bearer.ts +4 -33
- package/src/auth/clerk.ts +35 -48
- package/src/call-admission.ts +8 -6
- package/src/catalog-fingerprint.ts +52 -0
- package/src/catalog-limits.ts +11 -0
- package/src/catalog-service.ts +601 -0
- package/src/catalog.ts +57 -2
- package/src/concurrency.ts +39 -0
- package/src/connectors/api.ts +22 -12
- package/src/connectors/remote-mcp.ts +63 -45
- package/src/credentials.ts +2 -2
- package/src/errors.ts +21 -1
- package/src/execute.ts +193 -423
- package/src/executor-result.ts +3 -3
- package/src/executors/quickjs-child.ts +8 -4
- package/src/executors/quickjs-protocol.ts +1 -1
- package/src/executors/quickjs-runtime.ts +5 -1
- package/src/executors/quickjs.ts +11 -8
- package/src/favicon.ts +2 -2
- package/src/index.ts +70 -222
- package/src/invocation.ts +580 -0
- package/src/meta-tools.ts +393 -985
- package/src/operator-ui/browser.css +360 -0
- package/src/operator-ui/browser.ts +987 -0
- package/src/operator-ui/generated.ts +4 -0
- package/src/operator-ui/model.ts +105 -0
- package/src/registry.ts +459 -439
- package/src/retired-toolkits.ts +14 -0
- package/src/routes/activity.ts +215 -0
- package/src/routes/credentials.ts +254 -0
- package/src/routes/mcp.ts +339 -0
- package/src/routes/oauth.ts +322 -0
- package/src/routes/shared.ts +240 -0
- package/src/routes/ui.ts +143 -0
- package/src/server.ts +64 -1731
- package/src/skills.ts +15 -43
- package/src/storage/file.ts +3 -1
- package/src/storage/memory.ts +3 -1
- package/src/timeout.ts +2 -4
- package/src/types.ts +8 -60
- package/src/ui.ts +60 -1538
- package/src/version.ts +1 -1
- package/dist/credential-health.d.ts +0 -227
- package/dist/credential-health.d.ts.map +0 -1
- package/dist/credential-health.js +0 -606
- package/dist/credential-health.js.map +0 -1
- package/dist/toolkits.d.ts +0 -146
- package/dist/toolkits.d.ts.map +0 -1
- package/dist/toolkits.js +0 -322
- package/dist/toolkits.js.map +0 -1
- package/src/credential-health.ts +0 -832
- package/src/toolkits.ts +0 -461
package/src/execute.ts
CHANGED
|
@@ -1,40 +1,36 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
-
import {
|
|
4
|
-
import { recordToolActivity, type ActivityRequestContext } from "./activity.js";
|
|
3
|
+
import type { ActivityRequestContext } from "./activity.js";
|
|
5
4
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
type ToolResult,
|
|
12
|
-
} from "./meta-tools.js";
|
|
5
|
+
boundedDiscoveryText,
|
|
6
|
+
CatalogService,
|
|
7
|
+
flatSearchResult,
|
|
8
|
+
} from "./catalog-service.js";
|
|
9
|
+
import { errorResult, jsonResult, type ToolResult } from "./meta-tools.js";
|
|
13
10
|
import {
|
|
14
11
|
guardExecuteResultValue,
|
|
15
12
|
MAX_EXECUTE_LOG_CHARS,
|
|
16
13
|
truncateExecuteText,
|
|
17
14
|
} from "./executor-result.js";
|
|
18
|
-
import { classifyCallError, ConnectorCallError } from "./errors.js";
|
|
19
|
-
import { isCallAdmissionError } from "./call-admission.js";
|
|
20
15
|
import {
|
|
21
16
|
ExecutorAdmissionError,
|
|
22
17
|
isAdmittingExecutor,
|
|
23
18
|
} from "./executor-admission.js";
|
|
24
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
InvocationFailure,
|
|
21
|
+
InvocationService,
|
|
22
|
+
} from "./invocation.js";
|
|
25
23
|
import type { RegistryView } from "./registry.js";
|
|
26
24
|
import type {
|
|
27
|
-
Connector,
|
|
28
25
|
Executor,
|
|
29
26
|
ExecutorProvider,
|
|
30
27
|
Logger,
|
|
31
|
-
ToolDef,
|
|
32
28
|
} from "./types.js";
|
|
33
29
|
|
|
34
30
|
/** Keep one model-written program from amplifying into an unbounded fan-out. */
|
|
35
|
-
|
|
31
|
+
const EXECUTE_MAX_HOST_CALLS = 20;
|
|
36
32
|
export const EXECUTE_MAX_BATCH_CALLS = 10;
|
|
37
|
-
|
|
33
|
+
const EXECUTE_HOST_CALL_TIMEOUT_MS = 15_000;
|
|
38
34
|
|
|
39
35
|
// deno-fmt-ignore
|
|
40
36
|
const RESERVED = new Set([
|
|
@@ -89,27 +85,60 @@ export function sanitizeIdentifier(name: string): string {
|
|
|
89
85
|
return id;
|
|
90
86
|
}
|
|
91
87
|
|
|
88
|
+
const SANDBOX_RESERVED_NAMES = new Set([
|
|
89
|
+
"connecta",
|
|
90
|
+
"console",
|
|
91
|
+
"arguments",
|
|
92
|
+
"result",
|
|
93
|
+
"undefined",
|
|
94
|
+
"setTimeout",
|
|
95
|
+
"Promise",
|
|
96
|
+
"Error",
|
|
97
|
+
"WorkerEntrypoint",
|
|
98
|
+
"CodeExecutor",
|
|
99
|
+
"__invoke",
|
|
100
|
+
"__namespace",
|
|
101
|
+
"__call",
|
|
102
|
+
"__log",
|
|
103
|
+
"__dispatchers",
|
|
104
|
+
"__connectors",
|
|
105
|
+
"__logs",
|
|
106
|
+
"__CODEMODE_BINARY_TAG",
|
|
107
|
+
"__bytesToBase64",
|
|
108
|
+
"__base64ToBytes",
|
|
109
|
+
"__encodeCodemodeValue",
|
|
110
|
+
"__decodeCodemodeValue",
|
|
111
|
+
"__stringifyForCodemode",
|
|
112
|
+
"__parseForCodemode",
|
|
113
|
+
]);
|
|
114
|
+
|
|
92
115
|
function msg(err: unknown): string {
|
|
93
116
|
return err instanceof Error ? err.message : String(err);
|
|
94
117
|
}
|
|
95
118
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
)
|
|
106
|
-
|
|
119
|
+
function lazyNamespacePrelude(
|
|
120
|
+
connectors: Array<{ id: string; namespace: string }>,
|
|
121
|
+
): string {
|
|
122
|
+
const declarations = connectors
|
|
123
|
+
.map(
|
|
124
|
+
({ id, namespace }) =>
|
|
125
|
+
`globalThis[${JSON.stringify(namespace)}] = __makeConnectaNamespace(${JSON.stringify(id)});`,
|
|
126
|
+
)
|
|
127
|
+
.join("\n");
|
|
128
|
+
return `(() => {
|
|
129
|
+
const __makeConnectaNamespace = (connectorId) => Object.freeze(new Proxy(Object.create(null), {
|
|
130
|
+
get: (_target, toolName) => typeof toolName === "string"
|
|
131
|
+
? (args) => connecta.__callNamespace(connectorId, toolName, args)
|
|
132
|
+
: undefined
|
|
133
|
+
}));
|
|
134
|
+
${declarations}
|
|
135
|
+
})();`;
|
|
107
136
|
}
|
|
108
137
|
|
|
109
138
|
/**
|
|
110
|
-
* Expose
|
|
111
|
-
*
|
|
112
|
-
*
|
|
139
|
+
* Expose one fixed host provider plus trusted sandbox setup that creates a
|
|
140
|
+
* lazy proxy global per connector. No connector catalog is touched until code
|
|
141
|
+
* calls that namespace or explicitly asks search/describe.
|
|
113
142
|
*/
|
|
114
143
|
export async function buildSandboxProviders(
|
|
115
144
|
registry: RegistryView,
|
|
@@ -120,12 +149,19 @@ export async function buildSandboxProviders(
|
|
|
120
149
|
signal?: AbortSignal;
|
|
121
150
|
maxHostCalls?: number;
|
|
122
151
|
hostCallTimeoutMs?: number;
|
|
152
|
+
discoveryConcurrency?: number;
|
|
123
153
|
} = {},
|
|
124
154
|
): Promise<ExecutorProvider[]> {
|
|
125
|
-
const providers: ExecutorProvider[] = [];
|
|
126
155
|
// All host calls made by one execute_code invocation share a downstream
|
|
127
156
|
// connection, while a later invocation receives a fresh request scope.
|
|
128
157
|
const requestScope = {};
|
|
158
|
+
const catalog = new CatalogService(registry, baseUrl, {
|
|
159
|
+
requestScope,
|
|
160
|
+
...(limits.discoveryConcurrency !== undefined
|
|
161
|
+
? { concurrency: limits.discoveryConcurrency }
|
|
162
|
+
: {}),
|
|
163
|
+
});
|
|
164
|
+
const invocation = new InvocationService(registry, catalog, activity);
|
|
129
165
|
const maxHostCalls = Math.max(
|
|
130
166
|
1,
|
|
131
167
|
Math.trunc(limits.maxHostCalls ?? EXECUTE_MAX_HOST_CALLS),
|
|
@@ -135,405 +171,129 @@ export async function buildSandboxProviders(
|
|
|
135
171
|
Math.trunc(limits.hostCallTimeoutMs ?? EXECUTE_HOST_CALL_TIMEOUT_MS),
|
|
136
172
|
);
|
|
137
173
|
let hostCalls = 0;
|
|
138
|
-
// Reserve the `connecta` namespace plus every global the sandbox bridge
|
|
139
|
-
// installs (see setupScript/installBridge in quickjs.ts) — a connector id
|
|
140
|
-
// sanitizing to one of these would clobber log capture or the call bridge.
|
|
141
|
-
const used = new Set<string>([
|
|
142
|
-
"connecta",
|
|
143
|
-
"console",
|
|
144
|
-
"__invoke",
|
|
145
|
-
"__namespace",
|
|
146
|
-
"__call",
|
|
147
|
-
"__log",
|
|
148
|
-
]);
|
|
149
174
|
const connectors = registry.listConnectors();
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
const catalogs = new Map<string, ToolDef[]>();
|
|
165
|
-
const callAddress = async (address: unknown, args: unknown) => {
|
|
166
|
-
const resolved = registry.resolveAddress(String(address));
|
|
167
|
-
if (!resolved) throw new Error(`Unknown address "${String(address)}"`);
|
|
168
|
-
const definition = catalogs
|
|
169
|
-
.get(resolved.connector.id)
|
|
170
|
-
?.find((tool) => tool.name === resolved.toolName);
|
|
171
|
-
if (!definition) {
|
|
172
|
-
throw new Error(
|
|
173
|
-
`Unknown tool "${resolved.toolName}" on connector "${resolved.connector.id}"`,
|
|
174
|
-
);
|
|
175
|
-
}
|
|
176
|
-
if (
|
|
177
|
-
definition.annotations?.readOnlyHint !== true ||
|
|
178
|
-
definition.annotations?.destructiveHint === true
|
|
179
|
-
) {
|
|
180
|
-
throw new Error(
|
|
181
|
-
`Tool "${String(address)}" is not explicitly read-only and cannot run inside execute_code. Invoke it through call_destructive_tool so the MCP host can request explicit approval.`,
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
hostCalls++;
|
|
185
|
-
if (hostCalls > maxHostCalls) {
|
|
186
|
-
throw new Error(
|
|
187
|
-
`execute_code host-call budget exceeded (${maxHostCalls} calls maximum)`,
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
if (limits.signal?.aborted) {
|
|
191
|
-
throw new Error("execute_code host call cancelled");
|
|
175
|
+
const namespaces: Array<{ id: string; namespace: string }> = [];
|
|
176
|
+
const namespaceOwners = new Map<string, string>();
|
|
177
|
+
for (const connector of connectors) {
|
|
178
|
+
const namespace = sanitizeIdentifier(connector.id);
|
|
179
|
+
const owner = namespaceOwners.get(namespace);
|
|
180
|
+
const problem = SANDBOX_RESERVED_NAMES.has(namespace)
|
|
181
|
+
? `Connector "${connector.id}" sanitizes to reserved execute_code namespace "${namespace}". Rename or exclude the connector before using execute_code.`
|
|
182
|
+
: owner
|
|
183
|
+
? `Connector ids "${owner}" and "${connector.id}" both sanitize to execute_code namespace "${namespace}". Rename or exclude one connector before using execute_code.`
|
|
184
|
+
: undefined;
|
|
185
|
+
if (problem) {
|
|
186
|
+
logger.warn(`[connecta] execute_code: ${problem}`);
|
|
187
|
+
throw new Error(problem);
|
|
192
188
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
let rejectCancelled!: (reason: Error) => void;
|
|
208
|
-
const cancelled = new Promise<never>((_, reject) => {
|
|
209
|
-
rejectCancelled = reject;
|
|
210
|
-
});
|
|
211
|
-
onAbort = () => {
|
|
212
|
-
rejectCancelled(
|
|
213
|
-
controller.signal.reason instanceof Error
|
|
214
|
-
? controller.signal.reason
|
|
215
|
-
: new Error("execute_code host call cancelled"),
|
|
216
|
-
);
|
|
217
|
-
};
|
|
218
|
-
controller.signal.addEventListener("abort", onAbort, { once: true });
|
|
219
|
-
if (controller.signal.aborted) onAbort();
|
|
220
|
-
if (controller.signal.aborted) await cancelled;
|
|
221
|
-
const ctx = registry.contextFor(
|
|
222
|
-
resolved.connector.id,
|
|
223
|
-
baseUrl,
|
|
224
|
-
requestScope,
|
|
225
|
-
{ signal: controller.signal, timeoutMs: hostCallTimeoutMs },
|
|
226
|
-
);
|
|
227
|
-
timer = setTimeout(() => {
|
|
228
|
-
controller.abort(
|
|
229
|
-
new ConnectorCallError(
|
|
230
|
-
"timeout",
|
|
231
|
-
`Tool call timed out after ${hostCallTimeoutMs}ms`,
|
|
232
|
-
),
|
|
233
|
-
);
|
|
234
|
-
}, hostCallTimeoutMs);
|
|
235
|
-
const pending = resolved.connector.callTool(
|
|
236
|
-
resolved.toolName,
|
|
237
|
-
args ?? {},
|
|
238
|
-
ctx,
|
|
239
|
-
);
|
|
240
|
-
const value = unwrapForSandbox(
|
|
241
|
-
resolved.connector.kind,
|
|
242
|
-
await Promise.race([pending, cancelled]),
|
|
243
|
-
);
|
|
244
|
-
registry.recordSuccess(resolved.connector.id, Date.now() - started);
|
|
245
|
-
recordToolActivity(activity, {
|
|
246
|
-
connectorId: resolved.connector.id,
|
|
247
|
-
toolName: resolved.toolName,
|
|
248
|
-
address: `${resolved.connector.id}.${resolved.toolName}`,
|
|
249
|
-
source: "execute_code",
|
|
250
|
-
outcome: "success",
|
|
251
|
-
durationMs: Date.now() - started,
|
|
252
|
-
attempts: 1,
|
|
253
|
-
});
|
|
254
|
-
return value;
|
|
255
|
-
} catch (err) {
|
|
256
|
-
const callerCancelled =
|
|
257
|
-
limits.signal?.aborted === true ||
|
|
258
|
-
(isCallAdmissionError(err) && err.admissionKind === "cancelled");
|
|
259
|
-
if (!callerCancelled && !isCallAdmissionError(err)) {
|
|
260
|
-
registry.recordFailure(
|
|
261
|
-
resolved.connector.id,
|
|
262
|
-
Date.now() - started,
|
|
263
|
-
err,
|
|
189
|
+
namespaceOwners.set(namespace, connector.id);
|
|
190
|
+
namespaces.push({ id: connector.id, namespace });
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const invocationContext = () => ({
|
|
194
|
+
source: "execute_code" as const,
|
|
195
|
+
timeoutMs: hostCallTimeoutMs,
|
|
196
|
+
...(limits.signal !== undefined ? { requestSignal: limits.signal } : {}),
|
|
197
|
+
unwrapResult: true,
|
|
198
|
+
beforeDispatch: () => {
|
|
199
|
+
hostCalls++;
|
|
200
|
+
if (hostCalls > maxHostCalls) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`execute_code host-call budget exceeded (${maxHostCalls} calls maximum)`,
|
|
264
203
|
);
|
|
265
204
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
address: `${resolved.connector.id}.${resolved.toolName}`,
|
|
277
|
-
source: "execute_code",
|
|
278
|
-
outcome:
|
|
279
|
-
details.code === "timeout"
|
|
280
|
-
? "timeout"
|
|
281
|
-
: details.code === "cancelled"
|
|
282
|
-
? "cancelled"
|
|
283
|
-
: "error",
|
|
284
|
-
durationMs: Date.now() - started,
|
|
285
|
-
attempts: 1,
|
|
286
|
-
errorCode: details.code,
|
|
287
|
-
});
|
|
288
|
-
throw err;
|
|
289
|
-
} finally {
|
|
290
|
-
if (timer) clearTimeout(timer);
|
|
291
|
-
if (onAbort) controller.signal.removeEventListener("abort", onAbort);
|
|
292
|
-
limits.signal?.removeEventListener("abort", cancel);
|
|
293
|
-
permit?.release();
|
|
294
|
-
}
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
const callAddress = async (address: unknown, args: unknown) => {
|
|
208
|
+
const outcome = await invocation.invoke(
|
|
209
|
+
String(address),
|
|
210
|
+
args ?? {},
|
|
211
|
+
invocationContext(),
|
|
212
|
+
);
|
|
213
|
+
if (!outcome.ok) throw new InvocationFailure(outcome.error);
|
|
214
|
+
return outcome.value;
|
|
295
215
|
};
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
`[connecta] execute_code: connector "${connector.id}" skipped (${details.code}): ${msg(loadedTools.reason)}`,
|
|
326
|
-
);
|
|
327
|
-
continue;
|
|
328
|
-
}
|
|
329
|
-
const tools = loadedTools.value;
|
|
330
|
-
catalogs.set(connector.id, tools);
|
|
331
|
-
// Null-prototype map: tools named `toString`/`hasOwnProperty`/`constructor`
|
|
332
|
-
// are legitimate and must not collide with inherited Object members.
|
|
333
|
-
const fns: ExecutorProvider["fns"] = Object.create(null);
|
|
334
|
-
for (const t of tools) {
|
|
335
|
-
if (
|
|
336
|
-
t.annotations?.readOnlyHint !== true ||
|
|
337
|
-
t.annotations?.destructiveHint === true
|
|
338
|
-
) {
|
|
339
|
-
continue;
|
|
340
|
-
}
|
|
341
|
-
const key = sanitizeIdentifier(t.name);
|
|
342
|
-
if (Object.hasOwn(fns, key)) {
|
|
343
|
-
logger.warn(
|
|
344
|
-
`[connecta] execute_code: tool "${connector.id}.${t.name}" sanitizes to duplicate "${key}" — skipped`,
|
|
345
|
-
);
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
// `await` (not a bare promise return) so a synchronous throw inside
|
|
349
|
-
// callAddress never sits handler-less for the thenable-adoption
|
|
350
|
-
// microtask — workerd reports that gap as an unhandled rejection.
|
|
351
|
-
fns[key] = async (args: unknown) =>
|
|
352
|
-
await callAddress(`${connector.id}.${t.name}`, args);
|
|
353
|
-
}
|
|
354
|
-
if (Object.keys(fns).length > 0) {
|
|
355
|
-
providers.push({ name: ns, fns });
|
|
356
|
-
used.add(ns);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
providers.push({
|
|
360
|
-
name: "connecta",
|
|
361
|
-
fns: {
|
|
362
|
-
call: callAddress,
|
|
363
|
-
batch: async (calls: unknown) => {
|
|
364
|
-
if (!Array.isArray(calls)) throw new Error("calls must be an array");
|
|
365
|
-
if (calls.length > EXECUTE_MAX_BATCH_CALLS) {
|
|
366
|
-
throw new Error(
|
|
367
|
-
`connecta.batch accepts at most ${EXECUTE_MAX_BATCH_CALLS} calls`,
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
return await Promise.all(
|
|
371
|
-
calls.map(async (call) => {
|
|
372
|
-
const item = call as { address?: unknown; args?: unknown };
|
|
373
|
-
try {
|
|
374
|
-
return {
|
|
375
|
-
address: String(item.address),
|
|
376
|
-
ok: true,
|
|
377
|
-
data: await callAddress(item.address, item.args),
|
|
378
|
-
};
|
|
379
|
-
} catch (err) {
|
|
380
|
-
return {
|
|
381
|
-
address: String(item.address),
|
|
382
|
-
ok: false,
|
|
383
|
-
error: msg(err),
|
|
384
|
-
};
|
|
385
|
-
}
|
|
386
|
-
}),
|
|
387
|
-
);
|
|
388
|
-
},
|
|
389
|
-
search: async (raw: unknown) => {
|
|
390
|
-
const args = (raw ?? {}) as {
|
|
391
|
-
query?: string;
|
|
392
|
-
connector?: string;
|
|
393
|
-
limit?: number;
|
|
394
|
-
offset?: number;
|
|
395
|
-
fullDescriptions?: boolean;
|
|
396
|
-
includeSchemas?: "compact" | "json";
|
|
397
|
-
};
|
|
398
|
-
const matches: Array<{
|
|
399
|
-
connector: string;
|
|
400
|
-
tool: ToolDef;
|
|
401
|
-
score: number;
|
|
402
|
-
order: number;
|
|
403
|
-
}> = [];
|
|
404
|
-
let matchMode: "all" | "partial" = "all";
|
|
405
|
-
const collectMatches = (mode: "all" | "partial") => {
|
|
406
|
-
matches.length = 0;
|
|
407
|
-
let orderBase = 0;
|
|
408
|
-
for (const connector of connectors) {
|
|
409
|
-
if (args.connector && connector.id !== args.connector) continue;
|
|
410
|
-
const tools = catalogs.get(connector.id);
|
|
411
|
-
if (!tools) continue;
|
|
412
|
-
for (const ranked of rankTools(
|
|
413
|
-
tools,
|
|
414
|
-
args.query ?? "",
|
|
415
|
-
mode,
|
|
416
|
-
)) {
|
|
417
|
-
matches.push({
|
|
418
|
-
connector: connector.id,
|
|
419
|
-
tool: ranked.tool,
|
|
420
|
-
score: ranked.score,
|
|
421
|
-
order: orderBase + ranked.order,
|
|
422
|
-
});
|
|
423
|
-
}
|
|
424
|
-
orderBase += tools.length;
|
|
216
|
+
const callNamespace = async (
|
|
217
|
+
connectorId: unknown,
|
|
218
|
+
toolAlias: unknown,
|
|
219
|
+
args: unknown,
|
|
220
|
+
) => {
|
|
221
|
+
const outcome = await invocation.invokeToolAlias(
|
|
222
|
+
String(connectorId),
|
|
223
|
+
String(toolAlias),
|
|
224
|
+
sanitizeIdentifier,
|
|
225
|
+
args ?? {},
|
|
226
|
+
invocationContext(),
|
|
227
|
+
);
|
|
228
|
+
if (!outcome.ok) throw new InvocationFailure(outcome.error);
|
|
229
|
+
return outcome.value;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
return [
|
|
233
|
+
{
|
|
234
|
+
name: "connecta",
|
|
235
|
+
prelude: lazyNamespacePrelude(namespaces),
|
|
236
|
+
fns: {
|
|
237
|
+
__callNamespace: callNamespace,
|
|
238
|
+
call: callAddress,
|
|
239
|
+
batch: async (calls: unknown) => {
|
|
240
|
+
if (!Array.isArray(calls)) throw new Error("calls must be an array");
|
|
241
|
+
if (calls.length > EXECUTE_MAX_BATCH_CALLS) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
`connecta.batch accepts at most ${EXECUTE_MAX_BATCH_CALLS} calls`,
|
|
244
|
+
);
|
|
425
245
|
}
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
? {
|
|
454
|
-
outputSchema:
|
|
455
|
-
args.includeSchemas === "json"
|
|
456
|
-
? match.tool.outputSchema
|
|
457
|
-
: compactSchema(match.tool.outputSchema),
|
|
458
|
-
}
|
|
459
|
-
: {}),
|
|
460
|
-
...(match.tool.annotations
|
|
461
|
-
? { annotations: match.tool.annotations }
|
|
462
|
-
: {}),
|
|
246
|
+
return await Promise.all(
|
|
247
|
+
calls.map(async (call) => {
|
|
248
|
+
const item = call as { address?: unknown; args?: unknown };
|
|
249
|
+
try {
|
|
250
|
+
return {
|
|
251
|
+
address: String(item.address),
|
|
252
|
+
ok: true,
|
|
253
|
+
data: await callAddress(item.address, item.args),
|
|
254
|
+
};
|
|
255
|
+
} catch (err) {
|
|
256
|
+
return {
|
|
257
|
+
address: String(item.address),
|
|
258
|
+
ok: false,
|
|
259
|
+
error: msg(err),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
}),
|
|
263
|
+
);
|
|
264
|
+
},
|
|
265
|
+
search: async (raw: unknown) => {
|
|
266
|
+
const args = (raw ?? {}) as {
|
|
267
|
+
query?: string;
|
|
268
|
+
connector?: string;
|
|
269
|
+
limit?: number;
|
|
270
|
+
offset?: number;
|
|
271
|
+
fullDescriptions?: boolean;
|
|
272
|
+
includeSchemas?: "compact" | "json";
|
|
463
273
|
};
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
return result;
|
|
485
|
-
},
|
|
486
|
-
describe: async (raw: unknown) => {
|
|
487
|
-
const args = (raw ?? {}) as {
|
|
488
|
-
addresses?: unknown;
|
|
489
|
-
format?: "compact" | "json";
|
|
490
|
-
fullDescriptions?: boolean;
|
|
491
|
-
};
|
|
492
|
-
const addresses = discoveryAddresses(args.addresses);
|
|
493
|
-
const format = args.format ?? "compact";
|
|
494
|
-
const result = {
|
|
495
|
-
tools: addresses.map((rawAddress) => {
|
|
496
|
-
const address = String(rawAddress);
|
|
497
|
-
const resolved = registry.resolveAddress(address);
|
|
498
|
-
if (!resolved) {
|
|
499
|
-
return { address, error: `Unknown address "${address}"` };
|
|
500
|
-
}
|
|
501
|
-
const tool = catalogs
|
|
502
|
-
.get(resolved.connector.id)
|
|
503
|
-
?.find((item) => item.name === resolved.toolName);
|
|
504
|
-
if (!tool) {
|
|
505
|
-
return { address, error: `Unknown tool "${address}"` };
|
|
506
|
-
}
|
|
507
|
-
const input = tool.inputSchema ?? { type: "object" };
|
|
508
|
-
return {
|
|
509
|
-
address,
|
|
510
|
-
name: tool.name,
|
|
511
|
-
description: summarizeDescription(
|
|
512
|
-
tool.description,
|
|
513
|
-
args.fullDescriptions === true,
|
|
514
|
-
),
|
|
515
|
-
inputSchema: format === "json" ? input : compactSchema(input),
|
|
516
|
-
...(tool.outputSchema
|
|
517
|
-
? {
|
|
518
|
-
outputSchema:
|
|
519
|
-
format === "json"
|
|
520
|
-
? tool.outputSchema
|
|
521
|
-
: compactSchema(tool.outputSchema),
|
|
522
|
-
}
|
|
523
|
-
: {}),
|
|
524
|
-
...(tool.annotations ? { annotations: tool.annotations } : {}),
|
|
525
|
-
};
|
|
526
|
-
}),
|
|
527
|
-
};
|
|
528
|
-
assertDiscoveryResultSize(
|
|
529
|
-
result,
|
|
530
|
-
'Split the address list or use format: "compact".',
|
|
531
|
-
);
|
|
532
|
-
return result;
|
|
274
|
+
const result = flatSearchResult(await catalog.search(args));
|
|
275
|
+
boundedDiscoveryText(
|
|
276
|
+
result,
|
|
277
|
+
"Request a smaller limit, omit fullDescriptions, or use compact schemas.",
|
|
278
|
+
);
|
|
279
|
+
return result;
|
|
280
|
+
},
|
|
281
|
+
describe: async (raw: unknown) => {
|
|
282
|
+
const args = (raw ?? {}) as {
|
|
283
|
+
addresses?: unknown;
|
|
284
|
+
format?: "compact" | "json";
|
|
285
|
+
fullDescriptions?: boolean;
|
|
286
|
+
};
|
|
287
|
+
const result = { tools: await catalog.describe(args) };
|
|
288
|
+
boundedDiscoveryText(
|
|
289
|
+
result,
|
|
290
|
+
'Split the address list or use format: "compact".',
|
|
291
|
+
);
|
|
292
|
+
return result;
|
|
293
|
+
},
|
|
533
294
|
},
|
|
534
295
|
},
|
|
535
|
-
|
|
536
|
-
return providers;
|
|
296
|
+
];
|
|
537
297
|
}
|
|
538
298
|
|
|
539
299
|
/** The execute_code handler. Exported for direct testing. */
|
|
@@ -543,6 +303,7 @@ export function createExecuteTool(
|
|
|
543
303
|
executor: Executor,
|
|
544
304
|
logger: Logger,
|
|
545
305
|
activity?: ActivityRequestContext,
|
|
306
|
+
config: { discoveryConcurrency?: number } = {},
|
|
546
307
|
) {
|
|
547
308
|
return async (
|
|
548
309
|
{ code }: { code: string },
|
|
@@ -572,7 +333,12 @@ export function createExecuteTool(
|
|
|
572
333
|
baseUrl,
|
|
573
334
|
logger,
|
|
574
335
|
activity,
|
|
575
|
-
{
|
|
336
|
+
{
|
|
337
|
+
signal: controller.signal,
|
|
338
|
+
...(config.discoveryConcurrency !== undefined
|
|
339
|
+
? { discoveryConcurrency: config.discoveryConcurrency }
|
|
340
|
+
: {}),
|
|
341
|
+
},
|
|
576
342
|
);
|
|
577
343
|
if (controller.signal.aborted) {
|
|
578
344
|
throw new ExecutorAdmissionError(
|
|
@@ -647,7 +413,7 @@ const EXECUTE_DESC = `Use for dependent multi-step calls, loops, joins, branchin
|
|
|
647
413
|
Write an async arrow function. It runs with NO network, filesystem, timers, or imports — the only capabilities are:
|
|
648
414
|
- One global per connector: every address <connectorId>.<toolName> from search_tools is callable as <connectorId>.<toolName>(args) with a single args object matching the schema from describe_tools. Names are sanitized to JS identifiers: characters outside [A-Za-z0-9_$] become "_" (e.g. my-service.get.thing → my_service.get_thing), leading digits get "_" prefixed, reserved words get "_" appended.
|
|
649
415
|
- connecta.call(address, args) and connecta.batch(calls) — call raw addresses.
|
|
650
|
-
- connecta.search(args) and connecta.describe(args) —
|
|
416
|
+
- connecta.search(args) and connecta.describe(args) — load and inspect request-local catalogs on demand.
|
|
651
417
|
- console.log(...) — captured and returned alongside the result.
|
|
652
418
|
|
|
653
419
|
Tool calls return plain values (MCP text content is JSON-parsed when possible) and throw on downstream errors — use try/catch to handle them. Return a JSON-serializable value; large results are truncated, so reduce data in code instead of returning raw payloads.
|
|
@@ -665,6 +431,7 @@ export function registerExecuteTool(
|
|
|
665
431
|
logger: Logger;
|
|
666
432
|
activity?: ActivityRequestContext;
|
|
667
433
|
requestSignal?: AbortSignal;
|
|
434
|
+
discoveryConcurrency?: number;
|
|
668
435
|
},
|
|
669
436
|
): void {
|
|
670
437
|
const handler = createExecuteTool(
|
|
@@ -673,6 +440,9 @@ export function registerExecuteTool(
|
|
|
673
440
|
ctx.executor,
|
|
674
441
|
ctx.logger,
|
|
675
442
|
ctx.activity,
|
|
443
|
+
ctx.discoveryConcurrency !== undefined
|
|
444
|
+
? { discoveryConcurrency: ctx.discoveryConcurrency }
|
|
445
|
+
: {},
|
|
676
446
|
);
|
|
677
447
|
server.registerTool(
|
|
678
448
|
"execute_code",
|