@zackbart/connecta 0.8.0 → 0.9.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 +128 -0
- package/README.md +31 -55
- package/SECURITY.md +5 -11
- 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 +2 -23
- 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/auth/downstream-oauth.d.ts +15 -6
- package/dist/auth/downstream-oauth.d.ts.map +1 -1
- package/dist/auth/downstream-oauth.js +60 -11
- package/dist/auth/downstream-oauth.js.map +1 -1
- package/dist/call-admission.d.ts.map +1 -1
- package/dist/call-admission.js +0 -1
- package/dist/call-admission.js.map +1 -1
- package/dist/catalog-service.d.ts +1 -2
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog-service.js +12 -18
- package/dist/catalog-service.js.map +1 -1
- package/dist/catalog.d.ts +6 -0
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +51 -0
- package/dist/catalog.js.map +1 -1
- package/dist/connectors/remote-mcp.d.ts +1 -1
- package/dist/connectors/remote-mcp.d.ts.map +1 -1
- package/dist/connectors/remote-mcp.js +73 -71
- package/dist/connectors/remote-mcp.js.map +1 -1
- package/dist/errors.d.ts +19 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +4 -9
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +41 -20
- package/dist/execute.js.map +1 -1
- package/dist/executor-result.js +1 -1
- package/dist/executor-result.js.map +1 -1
- package/dist/index.d.ts +3 -82
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -90
- package/dist/index.js.map +1 -1
- package/dist/invocation.d.ts +5 -0
- package/dist/invocation.d.ts.map +1 -1
- package/dist/invocation.js +46 -10
- package/dist/invocation.js.map +1 -1
- package/dist/meta-tools.d.ts +4 -12
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +119 -169
- package/dist/meta-tools.js.map +1 -1
- package/dist/operator-ui/generated.d.ts +2 -2
- package/dist/operator-ui/generated.d.ts.map +1 -1
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/generated.js.map +1 -1
- package/dist/operator-ui/model.d.ts +1 -22
- package/dist/operator-ui/model.d.ts.map +1 -1
- package/dist/operator-ui/model.js.map +1 -1
- package/dist/registry.d.ts +11 -178
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +52 -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.map +1 -1
- package/dist/routes/activity.js +2 -5
- package/dist/routes/activity.js.map +1 -1
- package/dist/routes/credentials.d.ts.map +1 -1
- package/dist/routes/credentials.js +1 -12
- package/dist/routes/credentials.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +94 -188
- package/dist/routes/mcp.js.map +1 -1
- package/dist/routes/oauth.d.ts.map +1 -1
- package/dist/routes/oauth.js +3 -8
- package/dist/routes/oauth.js.map +1 -1
- package/dist/routes/shared.d.ts +9 -30
- package/dist/routes/shared.d.ts.map +1 -1
- package/dist/routes/shared.js +10 -69
- package/dist/routes/shared.js.map +1 -1
- package/dist/routes/ui.d.ts.map +1 -1
- package/dist/routes/ui.js +4 -10
- package/dist/routes/ui.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +1 -31
- package/dist/server.js.map +1 -1
- package/dist/skills.d.ts +3 -6
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +9 -38
- package/dist/skills.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 +8 -61
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts +1 -2
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +5 -35
- package/dist/ui.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -2
- package/src/activity.ts +0 -9
- package/src/auth/bearer.ts +3 -32
- package/src/auth/clerk.ts +35 -48
- package/src/auth/downstream-oauth.ts +106 -24
- package/src/call-admission.ts +0 -1
- package/src/catalog-service.ts +23 -23
- package/src/catalog.ts +55 -0
- package/src/connectors/remote-mcp.ts +106 -86
- package/src/errors.ts +22 -0
- package/src/execute.ts +43 -24
- package/src/executor-result.ts +1 -1
- package/src/index.ts +24 -208
- package/src/invocation.ts +63 -10
- package/src/meta-tools.ts +139 -187
- package/src/operator-ui/browser.css +1 -54
- package/src/operator-ui/browser.ts +16 -150
- package/src/operator-ui/generated.ts +2 -2
- package/src/operator-ui/model.ts +1 -23
- package/src/registry.ts +59 -429
- package/src/retired-toolkits.ts +14 -0
- package/src/routes/activity.ts +1 -6
- package/src/routes/credentials.ts +0 -12
- package/src/routes/mcp.ts +110 -230
- package/src/routes/oauth.ts +2 -8
- package/src/routes/shared.ts +10 -100
- package/src/routes/ui.ts +2 -12
- package/src/server.ts +1 -31
- package/src/skills.ts +9 -38
- package/src/timeout.ts +2 -4
- package/src/types.ts +11 -62
- package/src/ui.ts +9 -46
- package/src/version.ts +1 -1
- package/dist/credential-health.d.ts +0 -205
- package/dist/credential-health.d.ts.map +0 -1
- package/dist/credential-health.js +0 -610
- package/dist/credential-health.js.map +0 -1
- package/dist/toolkits.d.ts +0 -124
- 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 -835
- package/src/toolkits.ts +0 -461
package/dist/meta-tools.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { boundedDiscoveryText, CatalogService, DiscoveryPolicyError, groupedSearchResult, MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_SEARCH_LIMIT, } from "./catalog-service.js";
|
|
2
|
+
import { boundedDiscoveryText, CatalogService, DEFAULT_SEARCH_LIMIT, DiscoveryPolicyError, groupedSearchResult, MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_SEARCH_LIMIT, } from "./catalog-service.js";
|
|
3
3
|
import { mapSettledWithConcurrency, resolveDiscoveryConcurrency, } from "./concurrency.js";
|
|
4
4
|
import { closeConnectorScope, } from "./connector-scope.js";
|
|
5
5
|
import { classifyCallError, messageLooksRetryable, } from "./errors.js";
|
|
@@ -7,14 +7,13 @@ import { InvocationService, MAX_RETRY_BACKOFF_MS, retryBackoffMs, } from "./invo
|
|
|
7
7
|
import { isValidMaxResultBytes, MIN_MAX_RESULT_BYTES, resolveMaxResultBytes, } from "./registry.js";
|
|
8
8
|
import { hasConnectorGuides, listSkills, resolveSkill, } from "./skills.js";
|
|
9
9
|
import { DEFAULT_PROBE_TIMEOUT_MS, normalizeTimeoutMs, withAbortableTimeout, } from "./timeout.js";
|
|
10
|
-
|
|
11
|
-
export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_RETRY_BACKOFF_MS, MAX_SEARCH_LIMIT, DiscoveryPolicyError, retryBackoffMs, };
|
|
10
|
+
export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_RETRY_BACKOFF_MS, MAX_SEARCH_LIMIT, retryBackoffMs, };
|
|
12
11
|
const RESULT_TTL_SECONDS = 900;
|
|
13
12
|
const enc = new TextEncoder();
|
|
14
13
|
const dec = new TextDecoder();
|
|
15
14
|
export function jsonResult(obj) {
|
|
16
15
|
return {
|
|
17
|
-
content: [{ type: "text", text: JSON.stringify(obj
|
|
16
|
+
content: [{ type: "text", text: JSON.stringify(obj) }],
|
|
18
17
|
...(obj !== null && typeof obj === "object" && !Array.isArray(obj)
|
|
19
18
|
? { structuredContent: obj }
|
|
20
19
|
: {}),
|
|
@@ -40,8 +39,9 @@ function discoveryErrorResult(error) {
|
|
|
40
39
|
result.isError = true;
|
|
41
40
|
return result;
|
|
42
41
|
}
|
|
43
|
-
function discoveryResult(
|
|
42
|
+
async function discoveryResult(operation, hint) {
|
|
44
43
|
try {
|
|
44
|
+
const value = await operation();
|
|
45
45
|
const text = boundedDiscoveryText(value, hint);
|
|
46
46
|
return {
|
|
47
47
|
content: [{ type: "text", text }],
|
|
@@ -174,7 +174,7 @@ function applyFieldsToContent(content, fields) {
|
|
|
174
174
|
catch {
|
|
175
175
|
return b;
|
|
176
176
|
}
|
|
177
|
-
return { ...b, text: JSON.stringify(applyFields(parsed, fields)
|
|
177
|
+
return { ...b, text: JSON.stringify(applyFields(parsed, fields)) };
|
|
178
178
|
});
|
|
179
179
|
}
|
|
180
180
|
// --- result-size guard + get_result (feature 1) ---------------------------
|
|
@@ -194,7 +194,7 @@ function applyFieldsToContent(content, fields) {
|
|
|
194
194
|
* at all (a BigInt) still throws, as before, and is reported as a failure.
|
|
195
195
|
*/
|
|
196
196
|
function serializeResultText(value) {
|
|
197
|
-
const serialized = JSON.stringify(value
|
|
197
|
+
const serialized = JSON.stringify(value);
|
|
198
198
|
return serialized === undefined ? String(value) : serialized;
|
|
199
199
|
}
|
|
200
200
|
/**
|
|
@@ -275,7 +275,7 @@ async function guardValue(value, results, cap) {
|
|
|
275
275
|
async function guardContent(content, results, cap) {
|
|
276
276
|
let text;
|
|
277
277
|
try {
|
|
278
|
-
text = JSON.stringify(content
|
|
278
|
+
text = JSON.stringify(content);
|
|
279
279
|
}
|
|
280
280
|
catch {
|
|
281
281
|
// A block carrying a BigInt or a cycle cannot be serialized, so it cannot
|
|
@@ -373,16 +373,23 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
373
373
|
},
|
|
374
374
|
});
|
|
375
375
|
if (!outcome.ok) {
|
|
376
|
+
const failedResult = outcome.error.code === "auth_required" ||
|
|
377
|
+
outcome.error.code === "input_required_unsupported" ||
|
|
378
|
+
call.resultMode === "value"
|
|
379
|
+
? jsonResult({
|
|
380
|
+
ok: false,
|
|
381
|
+
error: outcome.error,
|
|
382
|
+
durationMs: outcome.durationMs,
|
|
383
|
+
attempts: outcome.attempts,
|
|
384
|
+
...(call.diagnostics ? { timing: outcome.timing } : {}),
|
|
385
|
+
})
|
|
386
|
+
: errorResult(outcome.error.message);
|
|
387
|
+
if (outcome.error.code === "auth_required" ||
|
|
388
|
+
outcome.error.code === "input_required_unsupported") {
|
|
389
|
+
failedResult.isError = true;
|
|
390
|
+
}
|
|
376
391
|
return {
|
|
377
|
-
toolResult:
|
|
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),
|
|
392
|
+
toolResult: failedResult,
|
|
386
393
|
durationMs: outcome.durationMs,
|
|
387
394
|
attempts: outcome.attempts,
|
|
388
395
|
timing: outcome.timing,
|
|
@@ -438,9 +445,12 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
438
445
|
const inspect = async (c) => {
|
|
439
446
|
const statusStarted = Date.now();
|
|
440
447
|
const observed = registry.healthFor(c.id);
|
|
441
|
-
const
|
|
448
|
+
const drift = await registry.credentialDriftFor(c.id);
|
|
442
449
|
let status;
|
|
443
|
-
if (
|
|
450
|
+
if (drift) {
|
|
451
|
+
status = { state: "auth_required", message: drift };
|
|
452
|
+
}
|
|
453
|
+
else if (probe) {
|
|
444
454
|
try {
|
|
445
455
|
status = await withProbeDeadline(`list_connectors probe of "${c.id}"`, (options) => registry.statusFor(c.id, baseUrl, scope, options));
|
|
446
456
|
}
|
|
@@ -451,73 +461,20 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
451
461
|
status = { state: "error", message: msg(err) };
|
|
452
462
|
}
|
|
453
463
|
}
|
|
454
|
-
else if (verdict &&
|
|
455
|
-
// Deployment-wide, deliberately, like `hasObservedSuccess` beside
|
|
456
|
-
// it: a sibling toolkit's successful call proves the shared
|
|
457
|
-
// credential works, and a verdict retired for one view but not
|
|
458
|
-
// another would make the same connector read differently per scope
|
|
459
|
-
// for a reason that has nothing to do with scope.
|
|
460
|
-
credentialVerdictApplies(verdict, registry.observedSuccessAt(c.id))) {
|
|
461
|
-
// The proactive layer (issue #24): a liveness check already found
|
|
462
|
-
// the stored credential dead, so say so on the cheap path instead of
|
|
463
|
-
// waiting for an agent's real call to discover it. Only while it is
|
|
464
|
-
// the freshest evidence — a successful call since then retires it.
|
|
465
|
-
status = {
|
|
466
|
-
state: verdict.state,
|
|
467
|
-
...(verdict.message ? { message: verdict.message } : {}),
|
|
468
|
-
...(verdict.authorizationUrl
|
|
469
|
-
? { authorizationUrl: verdict.authorizationUrl }
|
|
470
|
-
: {}),
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
464
|
else {
|
|
474
|
-
// "error" comes from THIS view's own observations — a sibling
|
|
475
|
-
// toolkit's failure is not this session's experience — while
|
|
476
|
-
// ok/unknown may lean on the deployment-wide success signal, since
|
|
477
|
-
// "the connector answers at all" is a fact about the connector.
|
|
478
|
-
// Unscoped, the two are the same log, so this is unchanged there.
|
|
479
465
|
const derived = observed?.consecutiveFailures && observed.consecutiveFailures > 0
|
|
480
466
|
? "error"
|
|
481
467
|
: registry.hasObservedSuccess(c.id) || c.kind === "api"
|
|
482
468
|
? "ok"
|
|
483
469
|
: "unknown";
|
|
484
470
|
status = {
|
|
485
|
-
|
|
486
|
-
// been called yet, but the credential was verified, which is how
|
|
487
|
-
// re-authorization shows up here as ok rather than as an absence
|
|
488
|
-
// of evidence. It never DOWNgrades an observed failure: a real
|
|
489
|
-
// call that failed is stronger evidence than a background check.
|
|
490
|
-
state: derived === "unknown" && verdict?.state === "ok"
|
|
491
|
-
? "ok"
|
|
492
|
-
: derived,
|
|
471
|
+
state: derived,
|
|
493
472
|
...(observed?.lastError ? { message: observed.lastError } : {}),
|
|
494
473
|
};
|
|
495
474
|
}
|
|
496
|
-
// Stamped
|
|
497
|
-
//
|
|
498
|
-
// report a verdict older than it is, and would lose the race against a
|
|
499
|
-
// real call that succeeded WHILE it ran (that success must retire the
|
|
500
|
-
// verdict, and only an honest timestamp says so).
|
|
475
|
+
// Stamped after any live probe so the response reports when its
|
|
476
|
+
// observation completed, not when a potentially slow request began.
|
|
501
477
|
const checkedAt = new Date().toISOString();
|
|
502
|
-
// A live status probe IS a liveness observation of the stored
|
|
503
|
-
// credential, so it updates the same verdict a background check
|
|
504
|
-
// writes: the cached read afterwards agrees with what the operator
|
|
505
|
-
// just saw, and they are not swept again moments later. Recorded from
|
|
506
|
-
// the STATUS phase only, and only when the connector actually answered
|
|
507
|
-
// — a catalog refresh below is not a credential check (the sweep never
|
|
508
|
-
// fetches one), it is already counted in the health log, and letting
|
|
509
|
-
// its failure land here would spend the freshness budget on it. The
|
|
510
|
-
// registry ignores this for connectors storing no credential of ours.
|
|
511
|
-
if (probe && (status.state === "ok" || status.state === "auth_required")) {
|
|
512
|
-
await registry.recordCredentialHealth(c.id, {
|
|
513
|
-
state: status.state,
|
|
514
|
-
checkedAt,
|
|
515
|
-
...(status.message ? { message: status.message } : {}),
|
|
516
|
-
...("authorizationUrl" in status && status.authorizationUrl
|
|
517
|
-
? { authorizationUrl: status.authorizationUrl }
|
|
518
|
-
: {}),
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
478
|
let tools = registry.peekTools(c.id);
|
|
522
479
|
// An auth_required status may have just started OAuth. A second
|
|
523
480
|
// listTools probe would overwrite its state/verifier while returning
|
|
@@ -545,15 +502,6 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
545
502
|
state: "auth_required",
|
|
546
503
|
message: details.message,
|
|
547
504
|
};
|
|
548
|
-
await registry.recordCredentialHealth(c.id, {
|
|
549
|
-
state: "auth_required",
|
|
550
|
-
checkedAt,
|
|
551
|
-
...(status.message && { message: status.message }),
|
|
552
|
-
...("authorizationUrl" in status &&
|
|
553
|
-
status.authorizationUrl
|
|
554
|
-
? { authorizationUrl: status.authorizationUrl }
|
|
555
|
-
: {}),
|
|
556
|
-
});
|
|
557
505
|
}
|
|
558
506
|
else {
|
|
559
507
|
status = { state: "error", message: msg(err) };
|
|
@@ -563,9 +511,6 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
563
511
|
}
|
|
564
512
|
const latencyMs = Date.now() - statusStarted;
|
|
565
513
|
const latestObserved = registry.healthFor(c.id);
|
|
566
|
-
const credentialCheck = probe
|
|
567
|
-
? await registry.credentialHealthFor(c.id)
|
|
568
|
-
: verdict;
|
|
569
514
|
return {
|
|
570
515
|
id: c.id,
|
|
571
516
|
...(c.title ? { title: c.title } : {}),
|
|
@@ -576,7 +521,6 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
576
521
|
latencyMs,
|
|
577
522
|
probe,
|
|
578
523
|
...(latestObserved ?? observed),
|
|
579
|
-
...(credentialCheck ? { credentialCheck } : {}),
|
|
580
524
|
...("authorizationUrl" in status &&
|
|
581
525
|
status.authorizationUrl && {
|
|
582
526
|
authorizationUrl: status.authorizationUrl,
|
|
@@ -596,26 +540,10 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
596
540
|
return jsonResult({ connectors: out });
|
|
597
541
|
},
|
|
598
542
|
async searchTools(args) {
|
|
599
|
-
|
|
600
|
-
return discoveryResult(groupedSearchResult(await catalog.search(args)), "Request a smaller limit, omit fullDescriptions, or use compact schemas.");
|
|
601
|
-
}
|
|
602
|
-
catch (err) {
|
|
603
|
-
if (err instanceof DiscoveryPolicyError) {
|
|
604
|
-
return discoveryErrorResult(err);
|
|
605
|
-
}
|
|
606
|
-
throw err;
|
|
607
|
-
}
|
|
543
|
+
return discoveryResult(async () => groupedSearchResult(await catalog.search(args)), "Request a smaller limit, omit fullDescriptions, or use compact schemas.");
|
|
608
544
|
},
|
|
609
545
|
async describeTools(args) {
|
|
610
|
-
|
|
611
|
-
return discoveryResult({ tools: await catalog.describe(args) }, 'Split the address list or use format: "compact".');
|
|
612
|
-
}
|
|
613
|
-
catch (err) {
|
|
614
|
-
if (err instanceof DiscoveryPolicyError) {
|
|
615
|
-
return discoveryErrorResult(err);
|
|
616
|
-
}
|
|
617
|
-
throw err;
|
|
618
|
-
}
|
|
546
|
+
return discoveryResult(async () => ({ tools: await catalog.describe(args) }), 'Split the address list or use format: "compact".');
|
|
619
547
|
},
|
|
620
548
|
async callTool(args) {
|
|
621
549
|
return (await runCall(args, "call_tool")).toolResult;
|
|
@@ -773,6 +701,21 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
773
701
|
...(details.retryAfterMs !== undefined
|
|
774
702
|
? { retryAfterMs: details.retryAfterMs }
|
|
775
703
|
: {}),
|
|
704
|
+
...(details.connector !== undefined
|
|
705
|
+
? { connector: batchSummaryString(details.connector) }
|
|
706
|
+
: {}),
|
|
707
|
+
...(details.operation !== undefined
|
|
708
|
+
? { operation: batchSummaryString(details.operation) }
|
|
709
|
+
: {}),
|
|
710
|
+
...(details.recovery !== undefined
|
|
711
|
+
? { recovery: details.recovery }
|
|
712
|
+
: {}),
|
|
713
|
+
...(details.nextAction !== undefined
|
|
714
|
+
? { nextAction: details.nextAction }
|
|
715
|
+
: {}),
|
|
716
|
+
...(details.retry !== undefined
|
|
717
|
+
? { retry: batchSummaryString(details.retry) }
|
|
718
|
+
: {}),
|
|
776
719
|
},
|
|
777
720
|
};
|
|
778
721
|
}),
|
|
@@ -786,28 +729,58 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
786
729
|
return errorResult(`Unknown connector "${args.connector}"`);
|
|
787
730
|
}
|
|
788
731
|
if (!connector.startAuth) {
|
|
789
|
-
|
|
732
|
+
if (!connector.credential) {
|
|
733
|
+
return jsonResult({
|
|
734
|
+
connector: connector.id,
|
|
735
|
+
recovery: "unavailable",
|
|
736
|
+
message: `Connector "${connector.id}" declares neither downstream OAuth ` +
|
|
737
|
+
"nor an operator-managed credential slot. Update the connector " +
|
|
738
|
+
"or deployment configuration before retrying.",
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
const ctx = registry.contextFor(connector.id, baseUrl, requestScope);
|
|
742
|
+
if (!ctx.credential) {
|
|
743
|
+
return jsonResult({
|
|
744
|
+
connector: connector.id,
|
|
745
|
+
recovery: "unavailable",
|
|
746
|
+
message: "Credential storage is not configured. Configure " +
|
|
747
|
+
"credentials.encryptionKey, redeploy, then call " +
|
|
748
|
+
"authorize_connector again.",
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
const fields = connector.credential.fields?.map((field) => ({
|
|
752
|
+
name: field.name,
|
|
753
|
+
guidance: field.description ?? field.label,
|
|
754
|
+
})) ?? [
|
|
755
|
+
{
|
|
756
|
+
name: "value",
|
|
757
|
+
guidance: connector.credential.description ??
|
|
758
|
+
connector.credential.label,
|
|
759
|
+
},
|
|
760
|
+
];
|
|
761
|
+
return jsonResult({
|
|
762
|
+
connector: connector.id,
|
|
763
|
+
recovery: "operator_config",
|
|
764
|
+
credential: {
|
|
765
|
+
label: connector.credential.label,
|
|
766
|
+
fields,
|
|
767
|
+
},
|
|
768
|
+
operatorUrl: new URL("/credentials", baseUrl).toString(),
|
|
769
|
+
instructions: "Have the operator open operatorUrl, set and test the credential, " +
|
|
770
|
+
"then retry the original call. No redeploy is needed. Credential " +
|
|
771
|
+
"mutation requires a Clerk-authenticated operator.",
|
|
772
|
+
});
|
|
790
773
|
}
|
|
791
774
|
const ctx = registry.contextFor(connector.id, baseUrl, requestScope);
|
|
792
775
|
try {
|
|
793
776
|
const status = await connector.startAuth(ctx, args.force !== undefined ? { force: args.force } : {});
|
|
794
|
-
// startAuth just spoke to the downstream about this exact credential, so
|
|
795
|
-
// its answer replaces any older liveness verdict — including the stale
|
|
796
|
-
// `auth_required` that sent the agent here, once it reports ok.
|
|
797
|
-
await registry.recordCredentialHealth(connector.id, {
|
|
798
|
-
state: status.state,
|
|
799
|
-
checkedAt: new Date().toISOString(),
|
|
800
|
-
...(status.message ? { message: status.message } : {}),
|
|
801
|
-
...(status.authorizationUrl
|
|
802
|
-
? { authorizationUrl: status.authorizationUrl }
|
|
803
|
-
: {}),
|
|
804
|
-
});
|
|
805
777
|
if (status.state === "auth_required" && !status.authorizationUrl) {
|
|
806
778
|
// auth_required with nothing to open is a dead end for the operator.
|
|
807
779
|
return errorResult(`Connector "${connector.id}": authorization required but no URL is available — retry authorize_connector.`);
|
|
808
780
|
}
|
|
809
781
|
return jsonResult({
|
|
810
782
|
connector: connector.id,
|
|
783
|
+
recovery: "oauth",
|
|
811
784
|
status: status.state,
|
|
812
785
|
...(status.authorizationUrl
|
|
813
786
|
? {
|
|
@@ -830,13 +803,13 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
|
830
803
|
};
|
|
831
804
|
}
|
|
832
805
|
const LIST_DESC = "List connectors with status, cached tool count, and recent real-call health. Use probe=false for a fast inventory; use probe=true (default) only to diagnose live health or authorization.";
|
|
833
|
-
const SEARCH_DESC = `Start here when a tool address is unknown. Exact/name matches rank above description matches; an empty query browses all.
|
|
806
|
+
const SEARCH_DESC = `Start here when a tool address is unknown. Exact/name matches rank above description matches; an empty query browses all. The default page has ${DEFAULT_SEARCH_LIMIT} tools; explicit limit can request up to ${MAX_SEARCH_LIMIT}. includeSchemas="compact" usually removes the describe_tools round trip.`;
|
|
834
807
|
const DESCRIBE_DESC = `Inspect up to ${MAX_DESCRIBE_ADDRESSES} known tool addresses when search_tools did not include a sufficient schema. Returns descriptions, input/output schemas, and behavior annotations; format "compact" is the default.`;
|
|
835
808
|
const CALL_DESC = 'Use for one tool explicitly annotated readOnlyHint: true. For 2–10 independent read-only calls use batch_call; for dependent steps or data reduction use execute_code when available. Unannotated, write-capable, and destructive tools are refused and require call_destructive_tool. fields selects JSON dot-paths, resultMode "value" unwraps results, timeoutMs sets a deadline, safe maxRetries are annotation-gated, diagnostics adds timing, and large results page through get_result.';
|
|
836
809
|
const CALL_DESTRUCTIVE_DESC = "Invoke any tool that is not explicitly annotated readOnlyHint: true, including unannotated, write-capable, or destructive tools. The MCP destructiveHint on this meta-tool lets the host request human approval before execution. Use only after reviewing the downstream tool schema and consequences.";
|
|
837
810
|
const GET_RESULT_DESC = "Page a truncated result stashed by call_tool/batch_call. Input { id, offset?, maxBytes? } → { text, offset, nextOffset?, totalBytes } sliced by byte offset. maxBytes is a whole number of bytes >= 1 (omit for the deployment default) and offset a whole number of bytes >= 0; an offset inside a multi-byte character is moved back to that character's first byte and the offset served is returned. Unknown/expired id is an error.";
|
|
838
811
|
const BATCH_DESC = "Use for 2–10 independent tools explicitly annotated readOnlyHint: true. Calls run in parallel with shared request-scoped clients; use execute_code when available instead for dependencies or in-sandbox reduction. Unannotated, write-capable, and destructive tools are refused. Batch timeout, safe retry, result mode, and diagnostics defaults may be overridden per call. An oversized final envelope returns ordered outcome summaries plus a get_result page handle.";
|
|
839
|
-
const AUTHORIZE_DESC = "Use after
|
|
812
|
+
const AUTHORIZE_DESC = "Use after auth_required. Returns an OAuth or operator-credential handoff, or reports required deployment configuration. force=true restarts OAuth only; this tool never accepts credentials.";
|
|
840
813
|
const SKILLS_DESC = 'List or fetch concise guidance for choosing among Connecta meta-tools. Call skills({ name: "usage" }) once when the routing workflow is unfamiliar; do not refetch it in the same task.';
|
|
841
814
|
/**
|
|
842
815
|
* Sentences appended to a meta-tool description only when this connection
|
|
@@ -844,10 +817,7 @@ const SKILLS_DESC = 'List or fetch concise guidance for choosing among Connecta
|
|
|
844
817
|
* so a deployment with no guides gets every base description unchanged rather
|
|
845
818
|
* than paying for text about a feature it does not use.
|
|
846
819
|
*
|
|
847
|
-
* Registration is per connection and reads the
|
|
848
|
-
* so under a toolkit these sentences reflect the SCOPED connector set: a scoped
|
|
849
|
-
* session whose connectors carry no guides sees the base descriptions, and
|
|
850
|
-
* never learns from a tool description that guides exist out of scope.
|
|
820
|
+
* Registration is per connection and reads the configured connector set.
|
|
851
821
|
*/
|
|
852
822
|
const GUIDE_NOTES = {
|
|
853
823
|
skills: ' skills({}) also lists this deployment\'s per-connector usage guides as "connector:<connectorId>"; fetch the guide for a connector before working with it for the first time.',
|
|
@@ -878,6 +848,15 @@ const READ_ONLY_LOCAL = {
|
|
|
878
848
|
idempotentHint: true,
|
|
879
849
|
openWorldHint: false,
|
|
880
850
|
};
|
|
851
|
+
const CALL_INPUT_SCHEMA = {
|
|
852
|
+
address: z.string(),
|
|
853
|
+
args: z.record(z.string(), z.unknown()).optional(),
|
|
854
|
+
fields: z.array(z.string()).optional(),
|
|
855
|
+
resultMode: z.enum(["mcp", "value"]).optional(),
|
|
856
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
857
|
+
maxRetries: z.number().int().min(0).max(2).optional(),
|
|
858
|
+
diagnostics: z.boolean().optional(),
|
|
859
|
+
};
|
|
881
860
|
/** Register the nine meta-tools onto an McpServer instance. */
|
|
882
861
|
export function registerMetaTools(server, registry, ctx) {
|
|
883
862
|
const mt = createMetaTools(registry, ctx.baseUrl, {
|
|
@@ -898,66 +877,45 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
898
877
|
});
|
|
899
878
|
server.registerTool("skills", {
|
|
900
879
|
description: describedFor(registry, SKILLS_DESC, "skills"),
|
|
901
|
-
inputSchema: { name: z.string().optional() },
|
|
902
|
-
annotations:
|
|
903
|
-
readOnlyHint: true,
|
|
904
|
-
destructiveHint: false,
|
|
905
|
-
idempotentHint: true,
|
|
906
|
-
openWorldHint: false,
|
|
907
|
-
},
|
|
880
|
+
inputSchema: z.object({ name: z.string().optional() }),
|
|
881
|
+
annotations: READ_ONLY_LOCAL,
|
|
908
882
|
}, async (args) => mt.skills(args));
|
|
909
883
|
server.registerTool("list_connectors", {
|
|
910
884
|
description: LIST_DESC,
|
|
911
|
-
inputSchema: { probe: z.boolean().optional() },
|
|
885
|
+
inputSchema: z.object({ probe: z.boolean().optional() }),
|
|
912
886
|
annotations: READ_ONLY_REMOTE,
|
|
913
887
|
}, async (args) => mt.listConnectors(args));
|
|
914
888
|
server.registerTool("search_tools", {
|
|
915
889
|
description: describedFor(registry, SEARCH_DESC, "search"),
|
|
916
|
-
inputSchema: {
|
|
890
|
+
inputSchema: z.object({
|
|
917
891
|
query: z.string().optional(),
|
|
918
892
|
connector: z.string().optional(),
|
|
919
893
|
limit: z.number().int().positive().max(MAX_SEARCH_LIMIT).optional(),
|
|
920
894
|
offset: z.number().int().nonnegative().optional(),
|
|
921
895
|
fullDescriptions: z.boolean().optional(),
|
|
922
896
|
includeSchemas: z.enum(["compact", "json"]).optional(),
|
|
923
|
-
},
|
|
897
|
+
}),
|
|
924
898
|
annotations: READ_ONLY_REMOTE,
|
|
925
899
|
}, async (args) => mt.searchTools(args));
|
|
926
900
|
server.registerTool("describe_tools", {
|
|
927
901
|
description: describedFor(registry, DESCRIBE_DESC, "describe"),
|
|
928
|
-
inputSchema: {
|
|
902
|
+
inputSchema: z.object({
|
|
929
903
|
addresses: z.array(z.string()).max(MAX_DESCRIBE_ADDRESSES),
|
|
930
904
|
format: z.enum(["compact", "json"]).optional(),
|
|
931
905
|
fullDescriptions: z.boolean().optional(),
|
|
932
|
-
},
|
|
906
|
+
}),
|
|
933
907
|
annotations: READ_ONLY_REMOTE,
|
|
934
908
|
}, async (args) => mt.describeTools(args));
|
|
935
909
|
server.registerTool("call_tool", {
|
|
936
910
|
description: CALL_DESC,
|
|
937
|
-
inputSchema:
|
|
938
|
-
address: z.string(),
|
|
939
|
-
args: z.record(z.string(), z.unknown()).optional(),
|
|
940
|
-
fields: z.array(z.string()).optional(),
|
|
941
|
-
resultMode: z.enum(["mcp", "value"]).optional(),
|
|
942
|
-
timeoutMs: z.number().int().positive().optional(),
|
|
943
|
-
maxRetries: z.number().int().min(0).max(2).optional(),
|
|
944
|
-
diagnostics: z.boolean().optional(),
|
|
945
|
-
},
|
|
911
|
+
inputSchema: z.object(CALL_INPUT_SCHEMA),
|
|
946
912
|
// call_tool admits only tools that are themselves explicitly read-only;
|
|
947
913
|
// anything else is refused and routed to call_destructive_tool.
|
|
948
914
|
annotations: READ_ONLY_REMOTE,
|
|
949
915
|
}, async (args) => mt.callTool(args));
|
|
950
916
|
server.registerTool("call_destructive_tool", {
|
|
951
917
|
description: CALL_DESTRUCTIVE_DESC,
|
|
952
|
-
inputSchema:
|
|
953
|
-
address: z.string(),
|
|
954
|
-
args: z.record(z.string(), z.unknown()).optional(),
|
|
955
|
-
fields: z.array(z.string()).optional(),
|
|
956
|
-
resultMode: z.enum(["mcp", "value"]).optional(),
|
|
957
|
-
timeoutMs: z.number().int().positive().optional(),
|
|
958
|
-
maxRetries: z.number().int().min(0).max(2).optional(),
|
|
959
|
-
diagnostics: z.boolean().optional(),
|
|
960
|
-
},
|
|
918
|
+
inputSchema: z.object(CALL_INPUT_SCHEMA),
|
|
961
919
|
annotations: {
|
|
962
920
|
destructiveHint: true,
|
|
963
921
|
readOnlyHint: false,
|
|
@@ -966,10 +924,10 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
966
924
|
}, async (args) => mt.callDestructiveTool(args));
|
|
967
925
|
server.registerTool("authorize_connector", {
|
|
968
926
|
description: AUTHORIZE_DESC,
|
|
969
|
-
inputSchema: {
|
|
927
|
+
inputSchema: z.object({
|
|
970
928
|
connector: z.string(),
|
|
971
929
|
force: z.boolean().optional(),
|
|
972
|
-
},
|
|
930
|
+
}),
|
|
973
931
|
// Starts (or with force, resets) a downstream OAuth flow — it changes
|
|
974
932
|
// stored connector auth state, so it is deliberately not read-only.
|
|
975
933
|
annotations: {
|
|
@@ -980,7 +938,7 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
980
938
|
}, async (args) => mt.authorizeConnector(args));
|
|
981
939
|
server.registerTool("get_result", {
|
|
982
940
|
description: GET_RESULT_DESC,
|
|
983
|
-
inputSchema: {
|
|
941
|
+
inputSchema: z.object({
|
|
984
942
|
id: z.string(),
|
|
985
943
|
// Both bounds are the shared rules (isValidResultOffset,
|
|
986
944
|
// isValidMaxResultBytes) expressed for the wire: spelling them against
|
|
@@ -988,29 +946,21 @@ export function registerMetaTools(server, registry, ctx) {
|
|
|
988
946
|
// in-handler checks if either floor ever moves.
|
|
989
947
|
offset: z.number().int().min(MIN_RESULT_OFFSET).optional(),
|
|
990
948
|
maxBytes: z.number().int().min(MIN_MAX_RESULT_BYTES).optional(),
|
|
991
|
-
},
|
|
949
|
+
}),
|
|
992
950
|
annotations: READ_ONLY_LOCAL,
|
|
993
951
|
}, async (args) => mt.getResult(args));
|
|
994
952
|
server.registerTool("batch_call", {
|
|
995
953
|
description: BATCH_DESC,
|
|
996
|
-
inputSchema: {
|
|
954
|
+
inputSchema: z.object({
|
|
997
955
|
calls: z
|
|
998
|
-
.array(z.object(
|
|
999
|
-
address: z.string(),
|
|
1000
|
-
args: z.record(z.string(), z.unknown()).optional(),
|
|
1001
|
-
fields: z.array(z.string()).optional(),
|
|
1002
|
-
resultMode: z.enum(["mcp", "value"]).optional(),
|
|
1003
|
-
timeoutMs: z.number().int().positive().optional(),
|
|
1004
|
-
maxRetries: z.number().int().min(0).max(2).optional(),
|
|
1005
|
-
diagnostics: z.boolean().optional(),
|
|
1006
|
-
}))
|
|
956
|
+
.array(z.object(CALL_INPUT_SCHEMA))
|
|
1007
957
|
.min(1)
|
|
1008
958
|
.max(10),
|
|
1009
959
|
resultMode: z.enum(["mcp", "value"]).optional(),
|
|
1010
960
|
timeoutMs: z.number().int().positive().optional(),
|
|
1011
961
|
maxRetries: z.number().int().min(0).max(2).optional(),
|
|
1012
962
|
diagnostics: z.boolean().optional(),
|
|
1013
|
-
},
|
|
963
|
+
}),
|
|
1014
964
|
// Same gate as call_tool: every call in the batch must be explicitly
|
|
1015
965
|
// read-only or the batch is refused.
|
|
1016
966
|
annotations: READ_ONLY_REMOTE,
|