@zackbart/connecta 0.7.9 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +48 -0
- package/README.md +67 -144
- package/dist/auth/bearer.d.ts +1 -1
- package/dist/auth/bearer.js +4 -4
- package/dist/auth/bearer.js.map +1 -1
- package/dist/auth/clerk.d.ts +1 -1
- package/dist/auth/clerk.js +1 -1
- package/dist/call-admission.d.ts.map +1 -1
- package/dist/call-admission.js +9 -3
- 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 +138 -0
- package/dist/catalog-service.d.ts.map +1 -0
- package/dist/catalog-service.js +407 -0
- package/dist/catalog-service.js.map +1 -0
- package/dist/catalog.js +2 -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 +44 -19
- package/dist/connectors/remote-mcp.js.map +1 -1
- package/dist/credential-health.d.ts +0 -22
- package/dist/credential-health.d.ts.map +1 -1
- package/dist/credential-health.js +14 -10
- package/dist/credential-health.js.map +1 -1
- package/dist/credentials.d.ts +3 -2
- package/dist/credentials.d.ts.map +1 -1
- package/dist/errors.d.ts +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +9 -8
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +144 -321
- 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 +2 -2
- 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 +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +44 -17
- package/dist/index.js.map +1 -1
- package/dist/invocation.d.ts +96 -0
- package/dist/invocation.d.ts.map +1 -0
- package/dist/invocation.js +369 -0
- package/dist/invocation.js.map +1 -0
- package/dist/meta-tools.d.ts +8 -86
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +145 -583
- 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 +94 -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 +22 -6
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +328 -35
- package/dist/registry.js.map +1 -1
- package/dist/routes/activity.d.ts +3 -0
- package/dist/routes/activity.d.ts.map +1 -0
- package/dist/routes/activity.js +173 -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 +181 -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 +383 -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 +268 -0
- package/dist/routes/oauth.js.map +1 -0
- package/dist/routes/shared.d.ts +97 -0
- package/dist/routes/shared.d.ts.map +1 -0
- package/dist/routes/shared.js +199 -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 +122 -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 +63 -1293
- package/dist/server.js.map +1 -1
- package/dist/skills.d.ts +0 -15
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +6 -5
- 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/toolkits.d.ts +0 -22
- package/dist/toolkits.d.ts.map +1 -1
- package/dist/toolkits.js +2 -2
- package/dist/toolkits.js.map +1 -1
- package/dist/types.d.ts +9 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts +3 -93
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +26 -1393
- 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/auth/bearer.ts +4 -4
- package/src/auth/clerk.ts +1 -1
- package/src/call-admission.ts +8 -5
- 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 +2 -2
- package/src/concurrency.ts +39 -0
- package/src/connectors/api.ts +22 -12
- package/src/connectors/remote-mcp.ts +52 -22
- package/src/credential-health.ts +13 -10
- package/src/credentials.ts +2 -2
- package/src/errors.ts +1 -1
- package/src/execute.ts +194 -409
- package/src/executor-result.ts +2 -2
- 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 +49 -17
- package/src/invocation.ts +527 -0
- package/src/meta-tools.ts +366 -906
- package/src/operator-ui/browser.css +413 -0
- package/src/operator-ui/browser.ts +1121 -0
- package/src/operator-ui/generated.ts +4 -0
- package/src/operator-ui/model.ts +127 -0
- package/src/registry.ts +434 -44
- package/src/routes/activity.ts +220 -0
- package/src/routes/credentials.ts +266 -0
- package/src/routes/mcp.ts +485 -0
- package/src/routes/oauth.ts +328 -0
- package/src/routes/shared.ts +330 -0
- package/src/routes/ui.ts +153 -0
- package/src/server.ts +66 -1703
- package/src/skills.ts +6 -5
- package/src/storage/file.ts +3 -1
- package/src/storage/memory.ts +3 -1
- package/src/toolkits.ts +2 -2
- package/src/types.ts +9 -2
- package/src/ui.ts +52 -1493
- package/src/version.ts +1 -1
package/dist/meta-tools.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { boundedDiscoveryText, CatalogService, DiscoveryPolicyError, groupedSearchResult, MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_SEARCH_LIMIT, } from "./catalog-service.js";
|
|
3
|
+
import { mapSettledWithConcurrency, resolveDiscoveryConcurrency, } from "./concurrency.js";
|
|
4
4
|
import { closeConnectorScope, } from "./connector-scope.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { isCallAdmissionError } from "./call-admission.js";
|
|
5
|
+
import { classifyCallError, messageLooksRetryable, } from "./errors.js";
|
|
6
|
+
import { InvocationService, MAX_RETRY_BACKOFF_MS, retryBackoffMs, } from "./invocation.js";
|
|
8
7
|
import { isValidMaxResultBytes, MIN_MAX_RESULT_BYTES, resolveMaxResultBytes, } from "./registry.js";
|
|
9
|
-
import {
|
|
8
|
+
import { hasConnectorGuides, listSkills, resolveSkill, } from "./skills.js";
|
|
10
9
|
import { DEFAULT_PROBE_TIMEOUT_MS, normalizeTimeoutMs, withAbortableTimeout, } from "./timeout.js";
|
|
11
10
|
import { credentialVerdictApplies } from "./credential-health.js";
|
|
11
|
+
export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_RETRY_BACKOFF_MS, MAX_SEARCH_LIMIT, DiscoveryPolicyError, retryBackoffMs, };
|
|
12
12
|
const RESULT_TTL_SECONDS = 900;
|
|
13
|
+
const enc = new TextEncoder();
|
|
14
|
+
const dec = new TextDecoder();
|
|
13
15
|
export function jsonResult(obj) {
|
|
14
16
|
return {
|
|
15
17
|
content: [{ type: "text", text: JSON.stringify(obj, null, 2) }],
|
|
@@ -24,68 +26,8 @@ export function errorResult(message) {
|
|
|
24
26
|
function msg(err) {
|
|
25
27
|
return err instanceof Error ? err.message : String(err);
|
|
26
28
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
* A discovery page is for choosing the next tool, not exporting the catalog.
|
|
30
|
-
* One hundred leaves room for broad browsing while keeping each deliberate
|
|
31
|
-
* page far below the catalog sizes Connecta supports.
|
|
32
|
-
*/
|
|
33
|
-
export const MAX_SEARCH_LIMIT = 100;
|
|
34
|
-
/** Same one-request work bound for address-based discovery. */
|
|
35
|
-
export const MAX_DESCRIBE_ADDRESSES = 100;
|
|
36
|
-
/**
|
|
37
|
-
* Final UTF-8 ceiling for a generated search/describe response. The count
|
|
38
|
-
* limits are the ordinary guard; this catches unusually large full schemas or
|
|
39
|
-
* descriptions that make even a bounded page expensive.
|
|
40
|
-
*/
|
|
41
|
-
export const MAX_DISCOVERY_RESULT_BYTES = 256_000;
|
|
42
|
-
const enc = new TextEncoder();
|
|
43
|
-
const dec = new TextDecoder();
|
|
44
|
-
export class DiscoveryPolicyError extends Error {
|
|
45
|
-
code;
|
|
46
|
-
constructor(code, message) {
|
|
47
|
-
super(message);
|
|
48
|
-
this.code = code;
|
|
49
|
-
this.name = "DiscoveryPolicyError";
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/** Validate before ranking so a huge page request does no proportional work. */
|
|
53
|
-
export function discoverySearchLimit(value) {
|
|
54
|
-
if (value === undefined)
|
|
55
|
-
return DEFAULT_SEARCH_LIMIT;
|
|
56
|
-
if (typeof value !== "number" ||
|
|
57
|
-
!Number.isInteger(value) ||
|
|
58
|
-
value < 1 ||
|
|
59
|
-
value > MAX_SEARCH_LIMIT) {
|
|
60
|
-
throw new DiscoveryPolicyError("invalid_args", `limit must be a whole number from 1 through ${MAX_SEARCH_LIMIT}. Page through larger catalogs with offset.`);
|
|
61
|
-
}
|
|
62
|
-
return value;
|
|
63
|
-
}
|
|
64
|
-
/** Validate the raw list so duplicate addresses consume the same bound. */
|
|
65
|
-
export function discoveryAddresses(value) {
|
|
66
|
-
if (!Array.isArray(value)) {
|
|
67
|
-
throw new DiscoveryPolicyError("invalid_args", "addresses must be an array.");
|
|
68
|
-
}
|
|
69
|
-
if (value.length > MAX_DESCRIBE_ADDRESSES) {
|
|
70
|
-
throw new DiscoveryPolicyError("invalid_args", `addresses must contain at most ${MAX_DESCRIBE_ADDRESSES} entries. Split a larger list across describe_tools calls.`);
|
|
71
|
-
}
|
|
72
|
-
return value;
|
|
73
|
-
}
|
|
74
|
-
/** Serialize once and count the exact bytes jsonResult would emit. */
|
|
75
|
-
function boundedDiscoveryText(value, hint) {
|
|
76
|
-
const text = JSON.stringify(value, null, 2);
|
|
77
|
-
if (text === undefined) {
|
|
78
|
-
throw new TypeError("Discovery result is not JSON-serializable.");
|
|
79
|
-
}
|
|
80
|
-
const bytes = enc.encode(text).length;
|
|
81
|
-
if (bytes > MAX_DISCOVERY_RESULT_BYTES) {
|
|
82
|
-
throw new DiscoveryPolicyError("result_too_large", `Discovery result is ${bytes} UTF-8 bytes, over the ${MAX_DISCOVERY_RESULT_BYTES}-byte ceiling. ${hint}`);
|
|
83
|
-
}
|
|
84
|
-
return text;
|
|
85
|
-
}
|
|
86
|
-
/** Apply the same final result guard to code-mode discovery helpers. */
|
|
87
|
-
export function assertDiscoveryResultSize(value, hint) {
|
|
88
|
-
boundedDiscoveryText(value, hint);
|
|
29
|
+
function errorDetails(code, message) {
|
|
30
|
+
return { code, message, retryable: messageLooksRetryable(message) };
|
|
89
31
|
}
|
|
90
32
|
function discoveryErrorResult(error) {
|
|
91
33
|
const result = jsonResult({
|
|
@@ -115,57 +57,12 @@ function discoveryResult(value, hint) {
|
|
|
115
57
|
throw err;
|
|
116
58
|
}
|
|
117
59
|
}
|
|
118
|
-
/**
|
|
119
|
-
* The longest the engine will park a synchronous inbound request in *waiting
|
|
120
|
-
* alone*. The engine already treats ~15 s as the outer bound of one reasonable
|
|
121
|
-
* connector call (EXECUTE_HOST_CALL_TIMEOUT_MS), so sleeping for minutes trades
|
|
122
|
-
* a fast, informative failure for a hung one. A connector-reported window this
|
|
123
|
-
* long isn't truncated — it's declined (see `retryBackoffMs`) and reported
|
|
124
|
-
* verbatim as `error.retryAfterMs`, so the agent, which can afford to wait,
|
|
125
|
-
* decides when to re-issue.
|
|
126
|
-
*/
|
|
127
|
-
export const MAX_RETRY_BACKOFF_MS = 10_000;
|
|
128
|
-
/**
|
|
129
|
-
* How long to wait before the next attempt, or `undefined` for "don't retry".
|
|
130
|
-
*
|
|
131
|
-
* A connector that read a `Retry-After` header knows the window exactly, so it
|
|
132
|
-
* is honoured **exactly or not at all**: truncating an exponential *guess* is
|
|
133
|
-
* harmless, but truncating a *known* window means deliberately retrying inside
|
|
134
|
-
* a rate limit — the harm this channel exists to prevent. A window longer than
|
|
135
|
-
* `MAX_RETRY_BACKOFF_MS` therefore declines the retry rather than shortening
|
|
136
|
-
* it. (`retryAfterMs` is normalized non-negative, so `0` means "retry now".)
|
|
137
|
-
* Connectors that report no window keep the historical exponential guess.
|
|
138
|
-
*
|
|
139
|
-
* Waits are per attempt, matching the per-attempt `timeoutMs` race in
|
|
140
|
-
* `runCall`. Exported for direct testing.
|
|
141
|
-
*/
|
|
142
|
-
export function retryBackoffMs(attempt, retryAfterMs) {
|
|
143
|
-
if (retryAfterMs === undefined) {
|
|
144
|
-
return Math.min(250 * 2 ** (attempt - 1), 1_000);
|
|
145
|
-
}
|
|
146
|
-
return retryAfterMs <= MAX_RETRY_BACKOFF_MS ? retryAfterMs : undefined;
|
|
147
|
-
}
|
|
148
|
-
/** Details for failures that never reached a connector (no thrown value). */
|
|
149
|
-
function errorDetails(code, message) {
|
|
150
|
-
return { code, message, retryable: messageLooksRetryable(message) };
|
|
151
|
-
}
|
|
152
|
-
function callerCancelledDetails() {
|
|
153
|
-
return {
|
|
154
|
-
code: "cancelled",
|
|
155
|
-
message: "Tool call was cancelled by the caller.",
|
|
156
|
-
retryable: false,
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
function isCallerCancellation(error, signal) {
|
|
160
|
-
return (signal?.aborted === true ||
|
|
161
|
-
(isCallAdmissionError(error) && error.admissionKind === "cancelled"));
|
|
162
|
-
}
|
|
163
60
|
/** True if `b` is a UTF-8 continuation byte (0b10xxxxxx). */
|
|
164
61
|
function isContinuationByte(b) {
|
|
165
|
-
return (b & 0xc0) === 0x80;
|
|
62
|
+
return b !== undefined && (b & 0xc0) === 0x80;
|
|
166
63
|
}
|
|
167
64
|
/** Smallest accepted `get_result` byte offset. */
|
|
168
|
-
|
|
65
|
+
const MIN_RESULT_OFFSET = 0;
|
|
169
66
|
/**
|
|
170
67
|
* The one definition of a usable `get_result` offset: a whole number of bytes
|
|
171
68
|
* at or past {@link MIN_RESULT_OFFSET}. Shared by the registered zod schema and
|
|
@@ -180,7 +77,7 @@ export const MIN_RESULT_OFFSET = 0;
|
|
|
180
77
|
* error. An offset past the end of the payload stays legal: it is a whole
|
|
181
78
|
* number of bytes, and it answers with an empty final page.
|
|
182
79
|
*/
|
|
183
|
-
|
|
80
|
+
function isValidResultOffset(value) {
|
|
184
81
|
return Number.isInteger(value) && value >= MIN_RESULT_OFFSET;
|
|
185
82
|
}
|
|
186
83
|
/**
|
|
@@ -236,9 +133,10 @@ export function alignEndToCharBoundary(bytes, offset, end, total) {
|
|
|
236
133
|
// --- fields selection (feature 2) -----------------------------------------
|
|
237
134
|
/** Resolve a dot-path (segments) against a value; `key[]` maps the tail over an array. */
|
|
238
135
|
function resolvePath(value, segments) {
|
|
239
|
-
|
|
136
|
+
const seg = segments[0];
|
|
137
|
+
if (seg === undefined)
|
|
240
138
|
return value;
|
|
241
|
-
const
|
|
139
|
+
const rest = segments.slice(1);
|
|
242
140
|
const isArr = seg.endsWith("[]");
|
|
243
141
|
const key = isArr ? seg.slice(0, -2) : seg;
|
|
244
142
|
let next = value;
|
|
@@ -295,7 +193,7 @@ function applyFieldsToContent(content, fields) {
|
|
|
295
193
|
* the three give one answer to the same question. A value JSON cannot serialize
|
|
296
194
|
* at all (a BigInt) still throws, as before, and is reported as a failure.
|
|
297
195
|
*/
|
|
298
|
-
|
|
196
|
+
function serializeResultText(value) {
|
|
299
197
|
const serialized = JSON.stringify(value, null, 2);
|
|
300
198
|
return serialized === undefined ? String(value) : serialized;
|
|
301
199
|
}
|
|
@@ -414,309 +312,101 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
414
312
|
const batchCap = registry.maxBatchResultBytes;
|
|
415
313
|
const defaultToolTimeoutMs = normalizeTimeoutMs(opts.defaultToolTimeoutMs);
|
|
416
314
|
const probeTimeoutMs = normalizeTimeoutMs(opts.probeTimeoutMs) ?? DEFAULT_PROBE_TIMEOUT_MS;
|
|
315
|
+
const discoveryConcurrency = resolveDiscoveryConcurrency(opts.discoveryConcurrency);
|
|
417
316
|
// createMetaTools() is called once per inbound MCP request. Sharing this
|
|
418
317
|
// identity lets remote connectors reuse one downstream client inside that
|
|
419
318
|
// request without leaking request-bound I/O into the next one.
|
|
420
319
|
const requestScope = {};
|
|
320
|
+
const catalog = new CatalogService(registry, baseUrl, {
|
|
321
|
+
requestScope,
|
|
322
|
+
probeTimeoutMs,
|
|
323
|
+
concurrency: discoveryConcurrency,
|
|
324
|
+
});
|
|
325
|
+
const invocation = new InvocationService(registry, catalog, opts.activity);
|
|
421
326
|
const withProbeDeadline = (label, operation) => withAbortableTimeout((signal) => operation({ signal, timeoutMs: probeTimeoutMs }), probeTimeoutMs, label);
|
|
422
|
-
/**
|
|
327
|
+
/** MCP adapter: shared invocation semantics plus MCP-only result shaping. */
|
|
423
328
|
async function runCall(call, source, options = {}) {
|
|
424
|
-
const started = Date.now();
|
|
425
|
-
let catalogMs = 0;
|
|
426
|
-
let admissionMs = 0;
|
|
427
|
-
let connectorMs = 0;
|
|
428
|
-
let backoffMs = 0;
|
|
429
|
-
let resultProcessingMs = 0;
|
|
430
|
-
let attempts = 0;
|
|
431
|
-
const timing = () => ({
|
|
432
|
-
catalogMs,
|
|
433
|
-
admissionMs,
|
|
434
|
-
connectorMs,
|
|
435
|
-
backoffMs,
|
|
436
|
-
resultProcessingMs,
|
|
437
|
-
totalMs: Date.now() - started,
|
|
438
|
-
});
|
|
439
|
-
const resolved = registry.resolveAddress(call.address);
|
|
440
|
-
const record = (outcome, errorCode) => {
|
|
441
|
-
if (!resolved)
|
|
442
|
-
return;
|
|
443
|
-
recordToolActivity(opts.activity, {
|
|
444
|
-
connectorId: resolved.connector.id,
|
|
445
|
-
toolName: resolved.toolName,
|
|
446
|
-
address: `${resolved.connector.id}.${resolved.toolName}`,
|
|
447
|
-
source,
|
|
448
|
-
outcome,
|
|
449
|
-
durationMs: Date.now() - started,
|
|
450
|
-
attempts,
|
|
451
|
-
...(errorCode ? { errorCode } : {}),
|
|
452
|
-
});
|
|
453
|
-
};
|
|
454
|
-
const failed = (error) => {
|
|
455
|
-
const durationMs = Date.now() - started;
|
|
456
|
-
const diagnostics = timing();
|
|
457
|
-
record(error.code === "timeout"
|
|
458
|
-
? "timeout"
|
|
459
|
-
: error.code === "cancelled"
|
|
460
|
-
? "cancelled"
|
|
461
|
-
: "error", error.code);
|
|
462
|
-
return {
|
|
463
|
-
toolResult: call.resultMode === "value"
|
|
464
|
-
? jsonResult({
|
|
465
|
-
ok: false,
|
|
466
|
-
error,
|
|
467
|
-
durationMs,
|
|
468
|
-
attempts,
|
|
469
|
-
...(call.diagnostics ? { timing: diagnostics } : {}),
|
|
470
|
-
})
|
|
471
|
-
: errorResult(error.message),
|
|
472
|
-
durationMs,
|
|
473
|
-
attempts,
|
|
474
|
-
timing: diagnostics,
|
|
475
|
-
error,
|
|
476
|
-
};
|
|
477
|
-
};
|
|
478
|
-
if (!resolved) {
|
|
479
|
-
return failed(errorDetails("unknown_address", `Unknown address "${call.address}"`));
|
|
480
|
-
}
|
|
481
329
|
const results = registry.resultsStorage();
|
|
482
|
-
// Result-size cap for THIS call: the connector's own override wins, then
|
|
483
|
-
// the deployment-wide value, then the built-in default (already folded
|
|
484
|
-
// into `globalCap`). Resolved per call so one batch_call can mix a
|
|
485
|
-
// tight-capped connector with siblings on the global cap. An override the
|
|
486
|
-
// registry already warned about at startup is dropped here, so the
|
|
487
|
-
// connector simply inherits `globalCap`.
|
|
488
|
-
const cap = resolveMaxResultBytes(resolved.connector.maxResultBytes, globalCap);
|
|
489
330
|
const fields = call.fields && call.fields.length > 0 ? call.fields : null;
|
|
490
|
-
// An explicit per-call deadline always wins; the config default only fills
|
|
491
|
-
// the gap, and stays off entirely when the deployment sets none.
|
|
492
331
|
const timeoutMs = normalizeTimeoutMs(call.timeoutMs) ?? defaultToolTimeoutMs;
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
}
|
|
522
|
-
catalogMs += Date.now() - catalogStarted;
|
|
523
|
-
if (!definition) {
|
|
524
|
-
return failed(errorDetails("unknown_tool", `Unknown tool "${resolved.toolName}" on connector "${resolved.connector.id}"`));
|
|
525
|
-
}
|
|
526
|
-
const explicitlyReadOnly = definition.annotations?.readOnlyHint === true &&
|
|
527
|
-
definition.annotations?.destructiveHint !== true;
|
|
528
|
-
if (!explicitlyReadOnly && !options.allowDestructive) {
|
|
529
|
-
return failed(errorDetails("destructive_tool_requires_approval", `Tool "${call.address}" is not explicitly read-only. Invoke it through call_destructive_tool so the MCP host can request explicit approval.`));
|
|
530
|
-
}
|
|
531
|
-
const retrySafe = definition.annotations?.readOnlyHint === true ||
|
|
532
|
-
definition.annotations?.idempotentHint === true;
|
|
533
|
-
let result;
|
|
534
|
-
while (true) {
|
|
535
|
-
attempts++;
|
|
536
|
-
let permit;
|
|
537
|
-
const controller = timeoutMs || opts.requestSignal ? new AbortController() : undefined;
|
|
538
|
-
const forwardAbort = () => controller?.abort(opts.requestSignal?.reason);
|
|
539
|
-
if (opts.requestSignal?.aborted)
|
|
540
|
-
forwardAbort();
|
|
541
|
-
else {
|
|
542
|
-
opts.requestSignal?.addEventListener("abort", forwardAbort, {
|
|
543
|
-
once: true,
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
let timer;
|
|
547
|
-
let onAbort;
|
|
548
|
-
let attemptFailed = false;
|
|
549
|
-
let attemptError;
|
|
550
|
-
try {
|
|
551
|
-
const admissionStarted = Date.now();
|
|
552
|
-
try {
|
|
553
|
-
permit = await registry.admitCall(resolved.connector.id, {
|
|
554
|
-
toolName: resolved.toolName,
|
|
555
|
-
args: call.args ?? {},
|
|
556
|
-
signal: opts.requestSignal,
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
finally {
|
|
560
|
-
admissionMs += Date.now() - admissionStarted;
|
|
561
|
-
}
|
|
562
|
-
const ctx = registry.contextFor(resolved.connector.id, baseUrl, requestScope, { signal: controller?.signal, timeoutMs });
|
|
563
|
-
let rejectCancelled;
|
|
564
|
-
const cancelled = controller
|
|
565
|
-
? new Promise((_, reject) => {
|
|
566
|
-
rejectCancelled = reject;
|
|
567
|
-
})
|
|
568
|
-
: undefined;
|
|
569
|
-
onAbort = () => {
|
|
570
|
-
rejectCancelled(controller?.signal.reason ??
|
|
571
|
-
new ConnectorCallError("timeout", "Tool call was cancelled"));
|
|
572
|
-
};
|
|
573
|
-
controller?.signal.addEventListener("abort", onAbort, { once: true });
|
|
574
|
-
if (controller?.signal.aborted)
|
|
575
|
-
onAbort();
|
|
576
|
-
if (controller?.signal.aborted)
|
|
577
|
-
await cancelled;
|
|
578
|
-
if (timeoutMs) {
|
|
579
|
-
timer = setTimeout(() => {
|
|
580
|
-
controller?.abort(new ConnectorCallError("timeout", `Tool call timed out after ${timeoutMs}ms`));
|
|
581
|
-
}, timeoutMs);
|
|
582
|
-
}
|
|
583
|
-
const connectorStarted = Date.now();
|
|
584
|
-
try {
|
|
585
|
-
const pending = resolved.connector.callTool(resolved.toolName, call.args ?? {}, ctx);
|
|
586
|
-
result = cancelled
|
|
587
|
-
? await Promise.race([pending, cancelled])
|
|
588
|
-
: await pending;
|
|
589
|
-
}
|
|
590
|
-
finally {
|
|
591
|
-
connectorMs += Date.now() - connectorStarted;
|
|
592
|
-
}
|
|
593
|
-
const mcpResult = result;
|
|
594
|
-
if (resolved.connector.kind === "mcp" && mcpResult?.isError) {
|
|
595
|
-
throw new Error(mcpResult.content?.map((block) => block.text).join("") ||
|
|
596
|
-
"Downstream tool call failed");
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
catch (err) {
|
|
600
|
-
attemptFailed = true;
|
|
601
|
-
attemptError = err;
|
|
602
|
-
}
|
|
603
|
-
finally {
|
|
604
|
-
if (timer)
|
|
605
|
-
clearTimeout(timer);
|
|
606
|
-
if (onAbort) {
|
|
607
|
-
controller?.signal.removeEventListener("abort", onAbort);
|
|
608
|
-
}
|
|
609
|
-
opts.requestSignal?.removeEventListener("abort", forwardAbort);
|
|
610
|
-
permit?.release();
|
|
611
|
-
}
|
|
612
|
-
if (attemptFailed) {
|
|
613
|
-
const callerCancelled = isCallerCancellation(attemptError, opts.requestSignal);
|
|
614
|
-
const details = callerCancelled
|
|
615
|
-
? callerCancelledDetails()
|
|
616
|
-
: classifyCallError(attemptError);
|
|
617
|
-
if (!callerCancelled &&
|
|
618
|
-
attempts <= maxRetries &&
|
|
619
|
-
retrySafe &&
|
|
620
|
-
details.retryable) {
|
|
621
|
-
const wait = retryBackoffMs(attempts, details.retryAfterMs);
|
|
622
|
-
if (wait !== undefined) {
|
|
623
|
-
const backoffStarted = Date.now();
|
|
624
|
-
if (wait > 0) {
|
|
625
|
-
const completed = await new Promise((resolve) => {
|
|
626
|
-
let settled = false;
|
|
627
|
-
const finish = (value) => {
|
|
628
|
-
if (settled)
|
|
629
|
-
return;
|
|
630
|
-
settled = true;
|
|
631
|
-
clearTimeout(timer);
|
|
632
|
-
opts.requestSignal?.removeEventListener("abort", cancel);
|
|
633
|
-
resolve(value);
|
|
634
|
-
};
|
|
635
|
-
const timer = setTimeout(() => finish(true), wait);
|
|
636
|
-
const cancel = () => finish(false);
|
|
637
|
-
opts.requestSignal?.addEventListener("abort", cancel, {
|
|
638
|
-
once: true,
|
|
639
|
-
});
|
|
640
|
-
if (opts.requestSignal?.aborted)
|
|
641
|
-
cancel();
|
|
642
|
-
});
|
|
643
|
-
backoffMs += Date.now() - backoffStarted;
|
|
644
|
-
if (!completed)
|
|
645
|
-
return failed(callerCancelledDetails());
|
|
646
|
-
}
|
|
647
|
-
else {
|
|
648
|
-
backoffMs += Date.now() - backoffStarted;
|
|
649
|
-
}
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
652
|
-
// The reported window is longer than the engine will park a
|
|
653
|
-
// synchronous request for. Fall through to failure with
|
|
654
|
-
// retryAfterMs reported verbatim so the agent can re-issue.
|
|
332
|
+
const outcome = await invocation.invoke(call.address, call.args ?? {}, {
|
|
333
|
+
source,
|
|
334
|
+
...(options.allowDestructive !== undefined
|
|
335
|
+
? { allowDestructive: options.allowDestructive }
|
|
336
|
+
: {}),
|
|
337
|
+
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
338
|
+
...(call.maxRetries !== undefined
|
|
339
|
+
? { maxRetries: call.maxRetries }
|
|
340
|
+
: {}),
|
|
341
|
+
...(opts.requestSignal !== undefined
|
|
342
|
+
? { requestSignal: opts.requestSignal }
|
|
343
|
+
: {}),
|
|
344
|
+
unwrapResult: call.resultMode === "value",
|
|
345
|
+
processResult: async (result, resolved) => {
|
|
346
|
+
// Result-size cap for THIS call: the connector's own override wins,
|
|
347
|
+
// then the deployment-wide value, then the built-in default (already
|
|
348
|
+
// folded into `globalCap`). Resolved per call so one batch_call can
|
|
349
|
+
// mix a tight-capped connector with siblings on the global cap. An
|
|
350
|
+
// override the registry already warned about at startup is dropped
|
|
351
|
+
// here, so the connector simply inherits `globalCap`.
|
|
352
|
+
const cap = resolveMaxResultBytes(resolved.connector.maxResultBytes, globalCap);
|
|
353
|
+
if (call.resultMode === "value") {
|
|
354
|
+
let value = fields ? applyFields(result, fields) : result;
|
|
355
|
+
value = await guardValue(value, results, cap);
|
|
356
|
+
return {
|
|
357
|
+
toolResult: jsonResult({ ok: true, data: value }),
|
|
358
|
+
value,
|
|
359
|
+
};
|
|
655
360
|
}
|
|
656
|
-
if (
|
|
657
|
-
|
|
361
|
+
if (resolved.connector.kind === "mcp") {
|
|
362
|
+
const mcpResult = result;
|
|
363
|
+
let content = mcpResult?.content ?? [];
|
|
364
|
+
if (fields)
|
|
365
|
+
content = applyFieldsToContent(content, fields);
|
|
366
|
+
return { toolResult: await guardContent(content, results, cap) };
|
|
658
367
|
}
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
break;
|
|
662
|
-
}
|
|
663
|
-
registry.recordSuccess(resolved.connector.id, Date.now() - started);
|
|
664
|
-
const processingStarted = Date.now();
|
|
665
|
-
try {
|
|
666
|
-
const mr = result;
|
|
667
|
-
if (call.resultMode === "value") {
|
|
668
|
-
let value = unwrapMcpResult(resolved.connector.kind, result);
|
|
669
|
-
if (fields)
|
|
670
|
-
value = applyFields(value, fields);
|
|
671
|
-
value = await guardValue(value, results, cap);
|
|
672
|
-
resultProcessingMs += Date.now() - processingStarted;
|
|
673
|
-
const durationMs = Date.now() - started;
|
|
674
|
-
const diagnostics = timing();
|
|
675
|
-
record("success");
|
|
368
|
+
const value = fields ? applyFields(result, fields) : result;
|
|
676
369
|
return {
|
|
677
|
-
toolResult:
|
|
678
|
-
ok: true,
|
|
679
|
-
data: value,
|
|
680
|
-
durationMs,
|
|
681
|
-
attempts,
|
|
682
|
-
...(call.diagnostics ? { timing: diagnostics } : {}),
|
|
683
|
-
}),
|
|
684
|
-
durationMs,
|
|
685
|
-
attempts,
|
|
686
|
-
timing: diagnostics,
|
|
370
|
+
toolResult: await guardText(serializeResultText(value), results, cap),
|
|
687
371
|
value,
|
|
688
372
|
};
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
if (fields)
|
|
693
|
-
content = applyFieldsToContent(content, fields);
|
|
694
|
-
const toolResult = await guardContent(content, results, cap);
|
|
695
|
-
resultProcessingMs += Date.now() - processingStarted;
|
|
696
|
-
record("success");
|
|
697
|
-
return {
|
|
698
|
-
toolResult,
|
|
699
|
-
durationMs: Date.now() - started,
|
|
700
|
-
attempts,
|
|
701
|
-
timing: timing(),
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
const value = fields ? applyFields(result, fields) : result;
|
|
705
|
-
const toolResult = await guardText(serializeResultText(value), results, cap);
|
|
706
|
-
resultProcessingMs += Date.now() - processingStarted;
|
|
707
|
-
record("success");
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
if (!outcome.ok) {
|
|
708
376
|
return {
|
|
709
|
-
toolResult
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
377
|
+
toolResult: call.resultMode === "value"
|
|
378
|
+
? jsonResult({
|
|
379
|
+
ok: false,
|
|
380
|
+
error: outcome.error,
|
|
381
|
+
durationMs: outcome.durationMs,
|
|
382
|
+
attempts: outcome.attempts,
|
|
383
|
+
...(call.diagnostics ? { timing: outcome.timing } : {}),
|
|
384
|
+
})
|
|
385
|
+
: errorResult(outcome.error.message),
|
|
386
|
+
durationMs: outcome.durationMs,
|
|
387
|
+
attempts: outcome.attempts,
|
|
388
|
+
timing: outcome.timing,
|
|
389
|
+
error: outcome.error,
|
|
714
390
|
};
|
|
715
391
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
392
|
+
const valueModeResult = call.resultMode === "value"
|
|
393
|
+
? jsonResult({
|
|
394
|
+
ok: true,
|
|
395
|
+
data: outcome.value.value,
|
|
396
|
+
durationMs: outcome.durationMs,
|
|
397
|
+
attempts: outcome.attempts,
|
|
398
|
+
...(call.diagnostics ? { timing: outcome.timing } : {}),
|
|
399
|
+
})
|
|
400
|
+
: outcome.value.toolResult;
|
|
401
|
+
return {
|
|
402
|
+
toolResult: valueModeResult,
|
|
403
|
+
durationMs: outcome.durationMs,
|
|
404
|
+
attempts: outcome.attempts,
|
|
405
|
+
timing: outcome.timing,
|
|
406
|
+
...(Object.prototype.hasOwnProperty.call(outcome.value, "value")
|
|
407
|
+
? { value: outcome.value.value }
|
|
408
|
+
: {}),
|
|
409
|
+
};
|
|
720
410
|
}
|
|
721
411
|
return {
|
|
722
412
|
async skills(args = {}) {
|
|
@@ -745,7 +435,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
745
435
|
// call scope. Closing it cannot defeat call_tool/batch/execute_code reuse.
|
|
746
436
|
const connectors = registry.listConnectors();
|
|
747
437
|
const scope = probe ? {} : requestScope;
|
|
748
|
-
const
|
|
438
|
+
const inspect = async (c) => {
|
|
749
439
|
const statusStarted = Date.now();
|
|
750
440
|
const observed = registry.healthFor(c.id);
|
|
751
441
|
const verdict = await registry.credentialHealthFor(c.id);
|
|
@@ -858,7 +548,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
858
548
|
await registry.recordCredentialHealth(c.id, {
|
|
859
549
|
state: "auth_required",
|
|
860
550
|
checkedAt,
|
|
861
|
-
...(status.message
|
|
551
|
+
...(status.message && { message: status.message }),
|
|
862
552
|
...("authorizationUrl" in status &&
|
|
863
553
|
status.authorizationUrl
|
|
864
554
|
? { authorizationUrl: status.authorizationUrl }
|
|
@@ -885,19 +575,19 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
885
575
|
checkedAt,
|
|
886
576
|
latencyMs,
|
|
887
577
|
probe,
|
|
888
|
-
...(latestObserved ?? observed
|
|
578
|
+
...(latestObserved ?? observed),
|
|
889
579
|
...(credentialCheck ? { credentialCheck } : {}),
|
|
890
|
-
...("authorizationUrl" in status &&
|
|
891
|
-
|
|
892
|
-
:
|
|
893
|
-
|
|
580
|
+
...("authorizationUrl" in status &&
|
|
581
|
+
status.authorizationUrl && {
|
|
582
|
+
authorizationUrl: status.authorizationUrl,
|
|
583
|
+
}),
|
|
584
|
+
...(status.message && { message: status.message }),
|
|
894
585
|
};
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
|
|
586
|
+
};
|
|
587
|
+
const settled = await mapSettledWithConcurrency(connectors, discoveryConcurrency, inspect);
|
|
588
|
+
if (probe) {
|
|
589
|
+
await mapSettledWithConcurrency(connectors, discoveryConcurrency, (connector) => closeConnectorScope(connector, registry.contextFor(connector.id, baseUrl, scope), opts.defer));
|
|
898
590
|
}
|
|
899
|
-
const settled = await Promise.allSettled(pending);
|
|
900
|
-
await Promise.all(connectors.map((connector) => closeConnectorScope(connector, registry.contextFor(connector.id, baseUrl, scope), opts.defer)));
|
|
901
591
|
const out = settled.map((result) => {
|
|
902
592
|
if (result.status === "rejected")
|
|
903
593
|
throw result.reason;
|
|
@@ -906,10 +596,8 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
906
596
|
return jsonResult({ connectors: out });
|
|
907
597
|
},
|
|
908
598
|
async searchTools(args) {
|
|
909
|
-
const q = args.query ?? "";
|
|
910
|
-
let limit;
|
|
911
599
|
try {
|
|
912
|
-
|
|
600
|
+
return discoveryResult(groupedSearchResult(await catalog.search(args)), "Request a smaller limit, omit fullDescriptions, or use compact schemas.");
|
|
913
601
|
}
|
|
914
602
|
catch (err) {
|
|
915
603
|
if (err instanceof DiscoveryPolicyError) {
|
|
@@ -917,103 +605,10 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
917
605
|
}
|
|
918
606
|
throw err;
|
|
919
607
|
}
|
|
920
|
-
const offset = Math.max(0, Math.trunc(args.offset ?? 0));
|
|
921
|
-
const conns = args.connector
|
|
922
|
-
? [registry.getConnector(args.connector)].filter((c) => Boolean(c))
|
|
923
|
-
: registry.listConnectors();
|
|
924
|
-
const matches = [];
|
|
925
|
-
const catalogs = await Promise.allSettled(conns.map((c) => withProbeDeadline(`search_tools probe of "${c.id}"`, (options) => registry.getTools(c.id, baseUrl, requestScope, options))));
|
|
926
|
-
let matchMode = "all";
|
|
927
|
-
const collectMatches = (mode) => {
|
|
928
|
-
matches.length = 0;
|
|
929
|
-
let orderBase = 0;
|
|
930
|
-
catalogs.forEach((catalog, connectorIndex) => {
|
|
931
|
-
const c = conns[connectorIndex];
|
|
932
|
-
if (catalog.status === "fulfilled") {
|
|
933
|
-
for (const ranked of rankTools(catalog.value, q, mode)) {
|
|
934
|
-
matches.push({
|
|
935
|
-
connectorId: c.id,
|
|
936
|
-
connectorTitle: c.title,
|
|
937
|
-
connectorDescription: c.description,
|
|
938
|
-
...(connectorGuide(c)
|
|
939
|
-
? { connectorGuideSkill: connectorSkillName(c.id) }
|
|
940
|
-
: {}),
|
|
941
|
-
tool: ranked.tool,
|
|
942
|
-
score: ranked.score,
|
|
943
|
-
order: orderBase + ranked.order,
|
|
944
|
-
});
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
orderBase +=
|
|
948
|
-
catalog.status === "fulfilled" ? catalog.value.length : 1;
|
|
949
|
-
});
|
|
950
|
-
};
|
|
951
|
-
collectMatches("all");
|
|
952
|
-
if (q.trim() && matches.length === 0) {
|
|
953
|
-
matchMode = "partial";
|
|
954
|
-
collectMatches(matchMode);
|
|
955
|
-
}
|
|
956
|
-
matches.sort((a, b) => b.score - a.score || a.order - b.order);
|
|
957
|
-
const page = matches.slice(offset, offset + limit);
|
|
958
|
-
const groups = [];
|
|
959
|
-
const byConnector = new Map();
|
|
960
|
-
for (const match of page) {
|
|
961
|
-
let group = byConnector.get(match.connectorId);
|
|
962
|
-
if (!group) {
|
|
963
|
-
group = {
|
|
964
|
-
id: match.connectorId,
|
|
965
|
-
...(match.connectorTitle ? { title: match.connectorTitle } : {}),
|
|
966
|
-
description: match.connectorDescription,
|
|
967
|
-
...(match.connectorGuideSkill
|
|
968
|
-
? { guide: match.connectorGuideSkill }
|
|
969
|
-
: {}),
|
|
970
|
-
tools: [],
|
|
971
|
-
};
|
|
972
|
-
byConnector.set(match.connectorId, group);
|
|
973
|
-
groups.push(group);
|
|
974
|
-
}
|
|
975
|
-
const schema = match.tool.inputSchema ?? { type: "object" };
|
|
976
|
-
group.tools.push({
|
|
977
|
-
name: match.tool.name,
|
|
978
|
-
address: `${match.connectorId}.${match.tool.name}`,
|
|
979
|
-
description: summarizeDescription(match.tool.description, args.fullDescriptions === true),
|
|
980
|
-
...(args.includeSchemas
|
|
981
|
-
? {
|
|
982
|
-
inputSchema: args.includeSchemas === "json"
|
|
983
|
-
? schema
|
|
984
|
-
: compactSchema(schema),
|
|
985
|
-
}
|
|
986
|
-
: {}),
|
|
987
|
-
...(args.includeSchemas && match.tool.outputSchema
|
|
988
|
-
? {
|
|
989
|
-
outputSchema: args.includeSchemas === "json"
|
|
990
|
-
? match.tool.outputSchema
|
|
991
|
-
: compactSchema(match.tool.outputSchema),
|
|
992
|
-
}
|
|
993
|
-
: {}),
|
|
994
|
-
...(match.tool.annotations
|
|
995
|
-
? { annotations: match.tool.annotations }
|
|
996
|
-
: {}),
|
|
997
|
-
});
|
|
998
|
-
}
|
|
999
|
-
const nextOffset = offset + page.length < matches.length
|
|
1000
|
-
? offset + page.length
|
|
1001
|
-
: undefined;
|
|
1002
|
-
return discoveryResult({
|
|
1003
|
-
connectors: groups,
|
|
1004
|
-
total: matches.length,
|
|
1005
|
-
offset,
|
|
1006
|
-
limit,
|
|
1007
|
-
hasMore: nextOffset !== undefined,
|
|
1008
|
-
...(nextOffset !== undefined ? { nextOffset } : {}),
|
|
1009
|
-
...(matchMode === "partial" && matches.length > 0
|
|
1010
|
-
? { matchMode }
|
|
1011
|
-
: {}),
|
|
1012
|
-
}, "Request a smaller limit, omit fullDescriptions, or use compact schemas.");
|
|
1013
608
|
},
|
|
1014
609
|
async describeTools(args) {
|
|
1015
610
|
try {
|
|
1016
|
-
|
|
611
|
+
return discoveryResult({ tools: await catalog.describe(args) }, 'Split the address list or use format: "compact".');
|
|
1017
612
|
}
|
|
1018
613
|
catch (err) {
|
|
1019
614
|
if (err instanceof DiscoveryPolicyError) {
|
|
@@ -1021,60 +616,6 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
1021
616
|
}
|
|
1022
617
|
throw err;
|
|
1023
618
|
}
|
|
1024
|
-
const format = args.format ?? "compact";
|
|
1025
|
-
const resolved = args.addresses.map((address) => ({
|
|
1026
|
-
address,
|
|
1027
|
-
resolved: registry.resolveAddress(address),
|
|
1028
|
-
}));
|
|
1029
|
-
const connectorIds = [
|
|
1030
|
-
...new Set(resolved
|
|
1031
|
-
.map((entry) => entry.resolved?.connector.id)
|
|
1032
|
-
.filter((id) => Boolean(id))),
|
|
1033
|
-
];
|
|
1034
|
-
const loaded = await Promise.allSettled(connectorIds.map((id) => withProbeDeadline(`describe_tools probe of "${id}"`, (options) => registry.getTools(id, baseUrl, requestScope, options))));
|
|
1035
|
-
const catalogs = new Map();
|
|
1036
|
-
loaded.forEach((result, index) => {
|
|
1037
|
-
catalogs.set(connectorIds[index], result.status === "fulfilled"
|
|
1038
|
-
? result.value
|
|
1039
|
-
: result.reason instanceof Error
|
|
1040
|
-
? result.reason
|
|
1041
|
-
: new Error(String(result.reason)));
|
|
1042
|
-
});
|
|
1043
|
-
const out = resolved.map(({ address, resolved }) => {
|
|
1044
|
-
if (!resolved) {
|
|
1045
|
-
return { address, error: `Unknown address "${address}"` };
|
|
1046
|
-
}
|
|
1047
|
-
const catalog = catalogs.get(resolved.connector.id);
|
|
1048
|
-
if (catalog instanceof Error) {
|
|
1049
|
-
return { address, error: catalog.message };
|
|
1050
|
-
}
|
|
1051
|
-
const tool = catalog?.find((t) => t.name === resolved.toolName);
|
|
1052
|
-
if (!tool) {
|
|
1053
|
-
return {
|
|
1054
|
-
address,
|
|
1055
|
-
error: `Unknown tool "${resolved.toolName}" on connector "${resolved.connector.id}"`,
|
|
1056
|
-
};
|
|
1057
|
-
}
|
|
1058
|
-
const schema = tool.inputSchema ?? { type: "object" };
|
|
1059
|
-
return {
|
|
1060
|
-
address,
|
|
1061
|
-
name: tool.name,
|
|
1062
|
-
description: summarizeDescription(tool.description, args.fullDescriptions === true),
|
|
1063
|
-
...(connectorGuide(resolved.connector)
|
|
1064
|
-
? { guide: connectorSkillName(resolved.connector.id) }
|
|
1065
|
-
: {}),
|
|
1066
|
-
inputSchema: format === "json" ? schema : compactSchema(schema),
|
|
1067
|
-
...(tool.outputSchema
|
|
1068
|
-
? {
|
|
1069
|
-
outputSchema: format === "json"
|
|
1070
|
-
? tool.outputSchema
|
|
1071
|
-
: compactSchema(tool.outputSchema),
|
|
1072
|
-
}
|
|
1073
|
-
: {}),
|
|
1074
|
-
...(tool.annotations ? { annotations: tool.annotations } : {}),
|
|
1075
|
-
};
|
|
1076
|
-
});
|
|
1077
|
-
return discoveryResult({ tools: out }, 'Split the address list or use format: "compact".');
|
|
1078
619
|
},
|
|
1079
620
|
async callTool(args) {
|
|
1080
621
|
return (await runCall(args, "call_tool")).toolResult;
|
|
@@ -1133,13 +674,25 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
1133
674
|
const batchStarted = Date.now();
|
|
1134
675
|
const settled = await Promise.allSettled(args.calls.map((c) => runCall({
|
|
1135
676
|
...c,
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
677
|
+
...((c.resultMode ?? args.resultMode) !== undefined
|
|
678
|
+
? { resultMode: c.resultMode ?? args.resultMode }
|
|
679
|
+
: {}),
|
|
680
|
+
...((c.timeoutMs ?? args.timeoutMs) !== undefined
|
|
681
|
+
? { timeoutMs: c.timeoutMs ?? args.timeoutMs }
|
|
682
|
+
: {}),
|
|
683
|
+
...((c.maxRetries ?? args.maxRetries) !== undefined
|
|
684
|
+
? { maxRetries: c.maxRetries ?? args.maxRetries }
|
|
685
|
+
: {}),
|
|
686
|
+
...((c.diagnostics ?? args.diagnostics) !== undefined
|
|
687
|
+
? { diagnostics: c.diagnostics ?? args.diagnostics }
|
|
688
|
+
: {}),
|
|
1140
689
|
}, "batch_call")));
|
|
1141
690
|
const results = settled.map((s, i) => {
|
|
1142
|
-
const
|
|
691
|
+
const call = args.calls[i];
|
|
692
|
+
if (!call) {
|
|
693
|
+
throw new Error("Batch result has no corresponding call");
|
|
694
|
+
}
|
|
695
|
+
const { address } = call;
|
|
1143
696
|
if (s.status === "rejected") {
|
|
1144
697
|
return {
|
|
1145
698
|
address,
|
|
@@ -1157,19 +710,19 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
1157
710
|
errorDetails: r.error,
|
|
1158
711
|
durationMs: r.durationMs,
|
|
1159
712
|
attempts: r.attempts,
|
|
1160
|
-
...((
|
|
713
|
+
...((call.diagnostics ?? args.diagnostics)
|
|
1161
714
|
? { timing: r.timing }
|
|
1162
715
|
: {}),
|
|
1163
716
|
};
|
|
1164
717
|
}
|
|
1165
|
-
if ((
|
|
718
|
+
if ((call.resultMode ?? args.resultMode) === "value") {
|
|
1166
719
|
return {
|
|
1167
720
|
address,
|
|
1168
721
|
ok: true,
|
|
1169
722
|
data: r.value,
|
|
1170
723
|
durationMs: r.durationMs,
|
|
1171
724
|
attempts: r.attempts,
|
|
1172
|
-
...((
|
|
725
|
+
...((call.diagnostics ?? args.diagnostics)
|
|
1173
726
|
? { timing: r.timing }
|
|
1174
727
|
: {}),
|
|
1175
728
|
};
|
|
@@ -1180,7 +733,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
1180
733
|
result: r.toolResult.content,
|
|
1181
734
|
durationMs: r.durationMs,
|
|
1182
735
|
attempts: r.attempts,
|
|
1183
|
-
...((
|
|
736
|
+
...((call.diagnostics ?? args.diagnostics)
|
|
1184
737
|
? { timing: r.timing }
|
|
1185
738
|
: {}),
|
|
1186
739
|
};
|
|
@@ -1237,7 +790,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
1237
790
|
}
|
|
1238
791
|
const ctx = registry.contextFor(connector.id, baseUrl, requestScope);
|
|
1239
792
|
try {
|
|
1240
|
-
const status = await connector.startAuth(ctx, { force: args.force });
|
|
793
|
+
const status = await connector.startAuth(ctx, args.force !== undefined ? { force: args.force } : {});
|
|
1241
794
|
// startAuth just spoke to the downstream about this exact credential, so
|
|
1242
795
|
// its answer replaces any older liveness verdict — including the stale
|
|
1243
796
|
// `auth_required` that sent the agent here, once it reports ok.
|
|
@@ -1328,11 +881,20 @@ const READ_ONLY_LOCAL = {
|
|
|
1328
881
|
/** Register the nine meta-tools onto an McpServer instance. */
|
|
1329
882
|
export function registerMetaTools(server, registry, ctx) {
|
|
1330
883
|
const mt = createMetaTools(registry, ctx.baseUrl, {
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
884
|
+
...(ctx.defaultToolTimeoutMs !== undefined
|
|
885
|
+
? { defaultToolTimeoutMs: ctx.defaultToolTimeoutMs }
|
|
886
|
+
: {}),
|
|
887
|
+
...(ctx.probeTimeoutMs !== undefined
|
|
888
|
+
? { probeTimeoutMs: ctx.probeTimeoutMs }
|
|
889
|
+
: {}),
|
|
890
|
+
...(ctx.discoveryConcurrency !== undefined
|
|
891
|
+
? { discoveryConcurrency: ctx.discoveryConcurrency }
|
|
892
|
+
: {}),
|
|
893
|
+
...(ctx.activity !== undefined ? { activity: ctx.activity } : {}),
|
|
894
|
+
...(ctx.requestSignal !== undefined
|
|
895
|
+
? { requestSignal: ctx.requestSignal }
|
|
896
|
+
: {}),
|
|
897
|
+
...(ctx.defer !== undefined ? { defer: ctx.defer } : {}),
|
|
1336
898
|
});
|
|
1337
899
|
server.registerTool("skills", {
|
|
1338
900
|
description: describedFor(registry, SKILLS_DESC, "skills"),
|