@zackbart/connecta 0.38.0 → 0.39.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 +63 -0
- package/README.md +3 -2
- package/bin/version.mjs +1 -1
- package/dist/agent-routes.js +231 -0
- package/dist/auth/downstream-oauth/consent.js +519 -0
- package/dist/auth/downstream-oauth/grant-store.js +439 -0
- package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
- package/dist/auth/downstream-oauth/refresh-records.js +447 -0
- package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
- package/dist/auth/downstream-oauth.js +66 -2382
- package/dist/auth/native.js +27 -0
- package/dist/auth/oauth-grant.js +13 -0
- package/dist/auth/static-oauth.js +153 -131
- package/dist/auth-elicitation.js +7 -1
- package/dist/authorization-handoff.js +11 -5
- package/dist/catalog-cache.js +130 -43
- package/dist/catalog-policy.js +18 -0
- package/dist/catalog-service.js +284 -47
- package/dist/catalog.js +18 -2
- package/dist/config.d.ts +16 -11
- package/dist/config.js +9 -6
- package/dist/connector-caller.d.ts +1 -1
- package/dist/connector-health.js +59 -0
- package/dist/connector-status.js +117 -0
- package/dist/connectors/api-connector.d.ts +7 -2
- package/dist/connectors/api-connector.js +7 -4
- package/dist/connectors/negotiation-cache.js +22 -4
- package/dist/connectors/remote-mcp/call.js +164 -0
- package/dist/connectors/remote-mcp/catalog.js +278 -0
- package/dist/connectors/remote-mcp/config.js +263 -0
- package/dist/connectors/remote-mcp/connection.js +671 -0
- package/dist/connectors/remote-mcp/errors.js +488 -0
- package/dist/connectors/remote-mcp/oauth.js +469 -0
- package/dist/connectors/remote-mcp/resources.js +473 -0
- package/dist/connectors/remote-mcp/runtime.js +1 -0
- package/dist/connectors/remote-mcp/transport.js +544 -0
- package/dist/connectors/remote-mcp.js +59 -2863
- package/dist/credential-contract.d.ts +23 -0
- package/dist/credential-reads.js +17 -0
- package/dist/credential-verification.js +53 -0
- package/dist/credentials.d.ts +7 -0
- package/dist/credentials.js +60 -25
- package/dist/describe-config.d.ts +12 -2
- package/dist/describe-config.js +2 -0
- package/dist/discovery-health.js +199 -0
- package/dist/downstream-input.js +2 -0
- package/dist/errors.d.ts +55 -17
- package/dist/errors.js +95 -41
- package/dist/execute.js +152 -114
- package/dist/executor-result.js +3 -1
- package/dist/guest-functions.js +83 -0
- package/dist/guest-runtime.js +60 -19
- package/dist/guest-types.d.ts +19 -5
- package/dist/http/argument-problems.js +35 -0
- package/dist/http/errors.js +5 -4
- package/dist/http/fields.js +192 -13
- package/dist/http/hatch-response.js +73 -0
- package/dist/http/hatch.js +88 -23
- package/dist/http/oauth.js +3 -2
- package/dist/http/provider.js +32 -4
- package/dist/http/safety.js +8 -0
- package/dist/http/tools.js +44 -23
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/invocation.js +21 -6
- package/dist/membership.d.ts +13 -5
- package/dist/membership.js +34 -2
- package/dist/meta-tools.js +23 -8
- package/dist/native-recovery.js +4 -16
- package/dist/native-tools.js +4 -1
- package/dist/oauth-sealing.js +10 -0
- package/dist/operation-vocabulary.js +135 -13
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +1 -0
- package/dist/operator-ui/contract.d.ts +10 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +22 -1
- package/dist/providers/_shared/rest/operation-index.js +48 -50
- package/dist/providers/_shared/rest/value-safety.js +80 -0
- package/dist/providers/breeze/api.js +7 -1
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/breeze/tools.js +4 -3
- package/dist/providers/ccb/api.js +2 -1
- package/dist/providers/ccb/index.js +5 -0
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/cloudflare/api.js +8 -1
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/lib/schemas.js +24 -22
- package/dist/providers/docs/safety.js +2 -0
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/docs/tools.js +2 -4
- package/dist/providers/drive/api.js +1 -0
- package/dist/providers/drive/lib/contracts.js +8 -5
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/drive/tools.js +11 -16
- package/dist/providers/forms/api.js +1 -0
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/forms/tools.js +10 -7
- package/dist/providers/github/api.js +1 -0
- package/dist/providers/github/lib/discovery.js +1 -1
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +61 -17
- package/dist/providers/gmail/lib/schemas.js +60 -50
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/gmail/tools.js +7 -5
- package/dist/providers/granola/index.js +2 -0
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/api.js +7 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/planning-center/api.js +3 -1
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/quickbooks/safety.js +80 -39
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/api.js +1 -0
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/sheets/tools.js +13 -14
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/slides/tools.js +15 -13
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/api.js +3 -1
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/vercel/api.js +1 -1
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/tools.js +4 -3
- package/dist/registry.d.ts +10 -4
- package/dist/registry.js +41 -13
- package/dist/routes/credentials.js +129 -60
- package/dist/routes/dispatch.js +5 -1
- package/dist/routes/mcp.js +30 -14
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +6 -1
- package/dist/routes/shared.js +7 -1
- package/dist/routes/ui-config.js +2 -2
- package/dist/routes/ui-data.js +126 -20
- package/dist/routes/ui.js +16 -0
- package/dist/runtime/call.js +7 -2
- package/dist/runtime/native-storage.js +9 -0
- package/dist/runtime/request-reads.js +182 -0
- package/dist/runtime/request-scope.js +16 -0
- package/dist/runtime/storage.js +12 -0
- package/dist/skills.js +37 -20
- package/dist/storage/keys.js +53 -3
- package/dist/storage/memory.js +8 -2
- package/dist/storage/sql-native.js +27 -0
- package/dist/storage/sql.js +12 -2
- package/dist/storage-digest.js +32 -0
- package/dist/timeout.js +8 -0
- package/dist/types.d.ts +33 -0
- package/dist/usage-guide.js +16 -35
- package/dist/validate.d.ts +3 -0
- package/dist/validate.js +220 -158
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/write-outcome.js +0 -3
- package/documentation/README.md +13 -13
- package/documentation/architecture.md +350 -19
- package/documentation/auth.md +217 -63
- package/documentation/code-mode.md +122 -39
- package/documentation/deploying.md +6 -3
- package/documentation/integrating.md +15 -1
- package/documentation/meta-tools.md +220 -46
- package/documentation/migration-0.33.md +5 -1
- package/documentation/migration-0.34.md +3 -1
- package/documentation/operator-tests.md +1 -1
- package/documentation/operator-ui.md +105 -12
- package/documentation/pool-membership-upgrade.md +44 -0
- package/documentation/providers.md +105 -0
- package/examples/worker/AGENTS.md +10 -10
- package/examples/worker/README.md +4 -3
- package/examples/worker/src/connecta.config.ts +1 -0
- package/guides/connecting-agents.md +2 -1
- package/guides/how-it-works.md +4 -2
- package/package.json +1 -1
- package/templates/node/AGENTS.md +13 -12
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +1 -0
package/dist/invocation.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { routePhrases } from "./agent-routes.js";
|
|
1
2
|
import { authCall, registerAuthMethod } from "./auth/native.js";
|
|
2
3
|
import { withAuthorizationHandoffEffect } from "./authorization-handoff.js";
|
|
3
4
|
import { hasControlCharacters } from "./tool-name.js";
|
|
@@ -9,7 +10,7 @@ import { downstreamValue } from "./mcp-result.js";
|
|
|
9
10
|
import { carryFailureFacts, classifiedFailure, failureRecord, logFailure, recordedToolName, } from "./operator-record.js";
|
|
10
11
|
import { splitAddress } from "./registry.js";
|
|
11
12
|
import { causeError, runEdge, withDeadlineEffect } from "./runtime/run.js";
|
|
12
|
-
import { validateCatalogToolInput } from "./validate.js";
|
|
13
|
+
import { schemaExample, validateCatalogToolInput } from "./validate.js";
|
|
13
14
|
import { sentSecretsFor, sentSecretsForRequest } from "./sent-secrets.js";
|
|
14
15
|
import { recoveryRecord, recordRecoveryFacts } from "./call-recovery.js";
|
|
15
16
|
import { callFromContext, contextView, viewServices, Credentials } from "./runtime/call.js";
|
|
@@ -141,6 +142,7 @@ export class InvocationFailure extends Error {
|
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
144
|
keepErrorName(InvocationFailure, "InvocationFailure");
|
|
145
|
+
const withoutExample = ({ example: _example, ...stated }) => stated;
|
|
144
146
|
/**
|
|
145
147
|
* Shared downstream invocation engine. Address/catalog resolution is delegated
|
|
146
148
|
* to the request-local CatalogService; every remaining call semantic lives
|
|
@@ -406,6 +408,14 @@ export class InvocationService {
|
|
|
406
408
|
return error;
|
|
407
409
|
return {
|
|
408
410
|
...error,
|
|
411
|
+
// An example comes only from the published schema, never a connector's copy of the
|
|
412
|
+
// call, unless the connector states why there is none.
|
|
413
|
+
validation: {
|
|
414
|
+
...withoutExample(error.validation),
|
|
415
|
+
...(error.validation.exampleUnavailable === undefined && resolved?.definition.inputSchema
|
|
416
|
+
? schemaExample(resolved.definition.inputSchema)
|
|
417
|
+
: {}),
|
|
418
|
+
},
|
|
409
419
|
connector: target.connector.id,
|
|
410
420
|
operation: `${target.connector.id}.${target.toolName}`,
|
|
411
421
|
nextAction: this.catalog.searchRecovery({
|
|
@@ -552,7 +562,7 @@ export class InvocationService {
|
|
|
552
562
|
return framingError("read_only_endpoint", `Tool "${canonicalAddress}" is a write, and ${connectorReadOnly ? `connector "${target.connector.id}"` : "this endpoint"} is read-only. Nothing here can send it.`);
|
|
553
563
|
}
|
|
554
564
|
if (refusal === "destructive_tool_requires_approval") {
|
|
555
|
-
return framingError("destructive_tool_requires_approval", `Tool "${canonicalAddress}" is a write.
|
|
565
|
+
return framingError("destructive_tool_requires_approval", `Tool "${canonicalAddress}" is a write. ${routePhrases(context).writeNeedsApproval}`);
|
|
556
566
|
}
|
|
557
567
|
// Remote MCP tools advertise their input schema in the catalog. Validate
|
|
558
568
|
// against that same request-local definition before admission or provider
|
|
@@ -580,7 +590,12 @@ export class InvocationService {
|
|
|
580
590
|
return yield* Effect.fail(callSignal.reason);
|
|
581
591
|
attempts = 1;
|
|
582
592
|
const call = (permit) => Effect.gen({ self: this }, function* () {
|
|
583
|
-
const operationContext = this.registry.contextFor(target.connector.id, this.catalog.baseUrl, this.catalog.requestScope, defined({
|
|
593
|
+
const operationContext = this.registry.contextFor(target.connector.id, this.catalog.baseUrl, this.catalog.requestScope, defined({
|
|
594
|
+
signal: callSignal,
|
|
595
|
+
timeoutMs: context.timeoutMs,
|
|
596
|
+
defer: this.catalog.defer,
|
|
597
|
+
routes: context.routes,
|
|
598
|
+
}));
|
|
584
599
|
const lease = callFromContext(operationContext, write ? "write" : "read", {
|
|
585
600
|
admitted: true,
|
|
586
601
|
// An invocation deadline aborts when its call finishes. A
|
|
@@ -604,11 +619,11 @@ export class InvocationService {
|
|
|
604
619
|
yield* Effect.gen(function* () {
|
|
605
620
|
if (target.connector.credential) {
|
|
606
621
|
if (!connectorContext.credential) {
|
|
607
|
-
throw new ConnectorCallError("auth_required", "Operator-managed credential storage is not configured.
|
|
608
|
-
|
|
622
|
+
throw new ConnectorCallError("auth_required", "Operator-managed credential storage is not configured. " +
|
|
623
|
+
routePhrases(connectorContext).authorizeCall(target.connector.id));
|
|
609
624
|
}
|
|
610
625
|
if (!(yield* Context.get(lease.services, Credentials).values)) {
|
|
611
|
-
throw new ConnectorCallError("auth_required", `Connector "${target.connector.id}" has no stored credential.
|
|
626
|
+
throw new ConnectorCallError("auth_required", `Connector "${target.connector.id}" has no stored credential. ${routePhrases(connectorContext).authorizeCall()}`);
|
|
612
627
|
}
|
|
613
628
|
}
|
|
614
629
|
yield* resolveInvocationAuthEffect(connector, callTool, connectorContext);
|
package/dist/membership.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export interface ResolvedPool {
|
|
|
22
22
|
readOnly: boolean;
|
|
23
23
|
/** Exactly the pool's `tools`; nothing intersects or widens it. */
|
|
24
24
|
access: ConnectorAccess;
|
|
25
|
+
/** Non-empty and homogeneous: all people or all agents. */
|
|
25
26
|
members: readonly Member[];
|
|
26
27
|
/** Trimmed `owner`: the Workspace address agents in this pool act as. */
|
|
27
28
|
owner?: string;
|
|
@@ -40,11 +41,8 @@ export interface MemberContext {
|
|
|
40
41
|
}
|
|
41
42
|
/** The namespaces bare ids may resolve in, read from the resolved providers. */
|
|
42
43
|
export declare function memberContext(auth: readonly InboundAuth[], agents: readonly string[]): MemberContext;
|
|
43
|
-
/**
|
|
44
|
-
|
|
45
|
-
* ambiguous, unknown, or repeated (INV-11).
|
|
46
|
-
*/
|
|
47
|
-
export declare function resolveMembers(value: unknown, path: string, context: MemberContext): Member[];
|
|
44
|
+
/** Resolve a pool's members, which are all people or all agents (INV-11). */
|
|
45
|
+
export declare function resolvePoolMembers(value: unknown, path: string, context: MemberContext): Member[];
|
|
48
46
|
/**
|
|
49
47
|
* `maintainers`: people only. Required whenever an inbound provider can admit
|
|
50
48
|
* a person, which is any provider but the built-in agent-token adapter. With
|
|
@@ -53,5 +51,15 @@ export declare function resolveMembers(value: unknown, path: string, context: Me
|
|
|
53
51
|
export declare function resolveMaintainers(value: unknown, admitsPeople: boolean, context: MemberContext): readonly IdentityReference[];
|
|
54
52
|
/** Whether an admitted identity is this member. An agent never matches a person. */
|
|
55
53
|
export declare function isMember(member: Member, identity: Readonly<AuthenticatedIdentity>): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Non-maintainer people listed in several pools, in declaration order. Plain
|
|
56
|
+
* `/mcp` routes a person by their single pool, so it refuses each of these.
|
|
57
|
+
* An open deployment admits no people, so it has none.
|
|
58
|
+
*/
|
|
59
|
+
export declare function peopleInSeveralPools(pools: ReadonlyMap<string, ResolvedPool>, maintainers: readonly IdentityReference[]): Array<{
|
|
60
|
+
namespace: string;
|
|
61
|
+
id: string;
|
|
62
|
+
pools: string[];
|
|
63
|
+
}>;
|
|
56
64
|
/** Whether an admitted identity is a listed maintainer. */
|
|
57
65
|
export declare function isMaintainer(maintainers: readonly IdentityReference[], identity: Readonly<AuthenticatedIdentity>): boolean;
|
package/dist/membership.js
CHANGED
|
@@ -65,7 +65,7 @@ const memberKey = (member) => member.kind === "agent" ? `agent\0${member.name}`
|
|
|
65
65
|
* Resolve one list of references, throwing on any that is malformed,
|
|
66
66
|
* ambiguous, unknown, or repeated (INV-11).
|
|
67
67
|
*/
|
|
68
|
-
|
|
68
|
+
function resolveMembers(value, path, context) {
|
|
69
69
|
if (!Array.isArray(value)) {
|
|
70
70
|
throw new ConfigError(`${path} must be an array of principal ids, { namespace, id }, or "agent:<name>".`);
|
|
71
71
|
}
|
|
@@ -79,6 +79,14 @@ export function resolveMembers(value, path, context) {
|
|
|
79
79
|
return Object.freeze(member);
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
+
/** Resolve a pool's members, which are all people or all agents (INV-11). */
|
|
83
|
+
export function resolvePoolMembers(value, path, context) {
|
|
84
|
+
const members = resolveMembers(value, path, context);
|
|
85
|
+
if (!members.every((member) => member.kind === members[0].kind)) {
|
|
86
|
+
throw new ConfigError(`${path} mixes people and agents; list only one kind.`);
|
|
87
|
+
}
|
|
88
|
+
return members;
|
|
89
|
+
}
|
|
82
90
|
/**
|
|
83
91
|
* `maintainers`: people only. Required whenever an inbound provider can admit
|
|
84
92
|
* a person, which is any provider but the built-in agent-token adapter. With
|
|
@@ -88,7 +96,7 @@ export function resolveMaintainers(value, admitsPeople, context) {
|
|
|
88
96
|
const path = "ConnectaConfig.maintainers";
|
|
89
97
|
if (value === undefined || (Array.isArray(value) && value.length === 0)) {
|
|
90
98
|
if (admitsPeople) {
|
|
91
|
-
throw new ConfigError(`${path} is required with inbound auth: list the people who
|
|
99
|
+
throw new ConfigError(`${path} is required with inbound auth: list the people who receive the whole catalog at plain /mcp, administer shared ` +
|
|
92
100
|
"credentials, manage agent tokens, and read activity.");
|
|
93
101
|
}
|
|
94
102
|
return Object.freeze([]);
|
|
@@ -115,6 +123,30 @@ export function isMember(member, identity) {
|
|
|
115
123
|
principal.namespace === member.namespace &&
|
|
116
124
|
principal.id === member.id);
|
|
117
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Non-maintainer people listed in several pools, in declaration order. Plain
|
|
128
|
+
* `/mcp` routes a person by their single pool, so it refuses each of these.
|
|
129
|
+
* An open deployment admits no people, so it has none.
|
|
130
|
+
*/
|
|
131
|
+
export function peopleInSeveralPools(pools, maintainers) {
|
|
132
|
+
if (maintainers.length === 0)
|
|
133
|
+
return [];
|
|
134
|
+
const people = new Map();
|
|
135
|
+
for (const [name, pool] of pools) {
|
|
136
|
+
for (const member of pool.members) {
|
|
137
|
+
if (member.kind !== "human")
|
|
138
|
+
continue;
|
|
139
|
+
if (maintainers.some((m) => m.namespace === member.namespace && m.id === member.id))
|
|
140
|
+
continue;
|
|
141
|
+
const key = JSON.stringify([member.namespace, member.id]);
|
|
142
|
+
const person = people.get(key) ?? { namespace: member.namespace, id: member.id, pools: [] };
|
|
143
|
+
if (!person.pools.includes(name))
|
|
144
|
+
person.pools.push(name);
|
|
145
|
+
people.set(key, person);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return [...people.values()].filter((person) => person.pools.length > 1);
|
|
149
|
+
}
|
|
118
150
|
/** Whether an admitted identity is a listed maintainer. */
|
|
119
151
|
export function isMaintainer(maintainers, identity) {
|
|
120
152
|
return maintainers.some((maintainer) => isMember({ kind: "human", ...maintainer }, identity));
|
package/dist/meta-tools.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { META_ROUTES } from "./agent-routes.js";
|
|
1
2
|
import { toolError } from "./tool-error.js";
|
|
2
3
|
import { upwardStep } from "./upward.js";
|
|
3
4
|
import { authCall, authInvoke, authFlow, authMethod, authStorage, registerAuth } from "./auth/native.js";
|
|
@@ -178,7 +179,7 @@ function isWellFormedText(text) {
|
|
|
178
179
|
* reported the decoy near the top instead. A write never gets this: repeating
|
|
179
180
|
* it is the one thing its notice forbids.
|
|
180
181
|
*/
|
|
181
|
-
const
|
|
182
|
+
const readReduceFirst = (routes) => `To find something specific, repeat this read inside ${routes.program} with connecta.call and filter or search the result there, returning only what matters`;
|
|
182
183
|
function pagingHint(results, totalBytes, preview) {
|
|
183
184
|
if (results.pagingTool === "read_result") {
|
|
184
185
|
const shown = preview.kind === "prefix"
|
|
@@ -190,13 +191,13 @@ function pagingHint(results, totalBytes, preview) {
|
|
|
190
191
|
}
|
|
191
192
|
if (!results.write) {
|
|
192
193
|
if (results.binding.program)
|
|
193
|
-
return
|
|
194
|
+
return `filter/map/slice data inside ${results.routes.program} and return only what you need; page this completed return with connecta.result using nextAction.`;
|
|
194
195
|
const [shown, full] = preview.kind === "prefix"
|
|
195
196
|
? [`Bytes 0-${preview.bytes} of ${totalBytes} follow.`, "to read it in full, page the rest"]
|
|
196
197
|
: preview.kind === "text-of-envelope"
|
|
197
198
|
? ["Its text follows.", `to read the full ${totalBytes}-byte content array as JSON, page it`]
|
|
198
199
|
: [`The result is ${totalBytes} bytes.`, "to read it in full, page it"];
|
|
199
|
-
return `${shown} ${
|
|
200
|
+
return `${shown} ${readReduceFirst(results.routes)}; ${full} with connecta.result using nextAction.`;
|
|
200
201
|
}
|
|
201
202
|
const body = preview.kind === "prefix"
|
|
202
203
|
? `Bytes 0-${preview.bytes} of ${totalBytes} follow; page the rest with connecta.result using nextAction.`
|
|
@@ -263,7 +264,7 @@ const stashResult = function (bytes, results, preview, rawText) {
|
|
|
263
264
|
? `Paging is unavailable. ${WRITE_ALREADY_RAN}`
|
|
264
265
|
: results.pagingTool
|
|
265
266
|
? "Paging is unavailable."
|
|
266
|
-
:
|
|
267
|
+
: `Paging is unavailable. Use ${results.routes.program} to reduce read-only results before returning them.`,
|
|
267
268
|
};
|
|
268
269
|
}
|
|
269
270
|
return {
|
|
@@ -302,6 +303,8 @@ const pageProgramResultNative = /* @__PURE__ */ registerAuth(pageProgramResult,
|
|
|
302
303
|
const bytes = enc.encode(text);
|
|
303
304
|
const write = resolved.definition.classification !== "read";
|
|
304
305
|
const notice = yield* upwardStep(stashResult(bytes, {
|
|
306
|
+
// Program-call pages name only execute_code, listed wherever programs run.
|
|
307
|
+
routes: META_ROUTES,
|
|
305
308
|
binding: {
|
|
306
309
|
identity: registry.resultIdentity(),
|
|
307
310
|
baseUrl,
|
|
@@ -341,6 +344,8 @@ const guardProgramReturnNative = /* @__PURE__ */ registerAuth(guardProgramReturn
|
|
|
341
344
|
const cap = Math.min(registry.maxResultBytes, ...dependencies.map((dependency) => resolveMaxResultBytes(registry.getConnector(dependency.connector)?.maxResultBytes, registry.maxResultBytes)));
|
|
342
345
|
const results = {
|
|
343
346
|
secrets,
|
|
347
|
+
// A program return names only execute_code, which every surface running programs lists.
|
|
348
|
+
routes: META_ROUTES,
|
|
344
349
|
binding: {
|
|
345
350
|
identity: registry.resultIdentity(),
|
|
346
351
|
baseUrl,
|
|
@@ -541,12 +546,13 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
541
546
|
// identity lets remote connectors reuse one downstream client inside that
|
|
542
547
|
// request without leaking request-bound I/O into the next one.
|
|
543
548
|
const requestScope = opts.requestScope ?? {};
|
|
549
|
+
const routes = opts.routes ?? META_ROUTES;
|
|
544
550
|
if (opts.canManageAuth || opts.oauthConnectUrl || opts.credentialHandoffUrl || opts.oauthConnectUnavailable) {
|
|
545
551
|
bindAuthorizationHandoffs(requestScope, authMethod((connector) => authCall(authorizationHandoff, [
|
|
546
552
|
registry,
|
|
547
553
|
baseUrl,
|
|
548
554
|
requestScope,
|
|
549
|
-
opts,
|
|
555
|
+
{ ...opts, routes },
|
|
550
556
|
{ connector },
|
|
551
557
|
])));
|
|
552
558
|
}
|
|
@@ -567,6 +573,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
567
573
|
requestSignal: opts.requestSignal,
|
|
568
574
|
// searchRoute keeps its top-level default. In-program callers use a
|
|
569
575
|
// separate CatalogService configured for connecta.search.
|
|
576
|
+
routes,
|
|
570
577
|
});
|
|
571
578
|
const invocation = new InvocationService(registry, catalog, opts.activity);
|
|
572
579
|
/** MCP adapter: shared invocation semantics plus MCP-only result shaping. */
|
|
@@ -584,6 +591,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
584
591
|
activitySource: opts.activitySource,
|
|
585
592
|
sentSecrets,
|
|
586
593
|
readOnly: opts.readOnly,
|
|
594
|
+
routes,
|
|
587
595
|
...(timeoutMs !== undefined ? { timeoutMs } : {}),
|
|
588
596
|
...(opts.requestSignal !== undefined ? { requestSignal: opts.requestSignal } : {}),
|
|
589
597
|
unwrapResult: call.resultMode === "value",
|
|
@@ -612,6 +620,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
612
620
|
const cap = resolveMaxResultBytes(resolved.connector.maxResultBytes, globalCap);
|
|
613
621
|
const results = {
|
|
614
622
|
secrets,
|
|
623
|
+
routes,
|
|
615
624
|
binding: {
|
|
616
625
|
identity: registry.resultIdentity(),
|
|
617
626
|
baseUrl,
|
|
@@ -836,6 +845,7 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
836
845
|
binding.identity.subject !== identity.subject ||
|
|
837
846
|
binding.identity.principal !== identity.principal ||
|
|
838
847
|
binding.identity.endpoint !== identity.endpoint ||
|
|
848
|
+
binding.identity.pool !== identity.pool ||
|
|
839
849
|
binding.identity.origin !== identity.origin ||
|
|
840
850
|
(binding.program === undefined
|
|
841
851
|
? typeof binding.connector !== "string" || typeof binding.tool !== "string"
|
|
@@ -990,11 +1000,15 @@ function metaToolsForRequest(registry, baseUrl, opts = {}) {
|
|
|
990
1000
|
})),
|
|
991
1001
|
};
|
|
992
1002
|
}
|
|
993
|
-
|
|
994
|
-
|
|
1003
|
+
// Each description states only its own tool's scope; the full routing rule
|
|
1004
|
+
// lives once in the server instructions. A client that drops instructions
|
|
1005
|
+
// still routes correctly from these scopes plus execute_code's terse lead.
|
|
1006
|
+
// Only the meta surface lists these tools, so they use its names.
|
|
1007
|
+
const SEARCH_DESC = (readOnly) => `${readOnly ? "Catalog inspection; this read-only endpoint omits write tools." : `Catalog inspection, or finding a write before ${META_ROUTES.write}.`} For read work, search inside one ${META_ROUTES.program} program that also calls and returns the answer. Use 2–4 action/object terms and includeSchemas="compact"; the default limit is ${DEFAULT_SEARCH_LIMIT}. Set connector when known. safety="readOnly" selects reads; "approvalRequired" selects the fail-closed write complement; neither grants authority. Empty query browses. Read catalogErrors and absence before selecting a tool. Compact schemas have schemaFormat="text".`;
|
|
1008
|
+
const CALL_DESC = (readOnly) => `Call one known-address read: a tool explicitly annotated readOnlyHint: true. Other read work: ${META_ROUTES.program}. ${readOnly ? "This endpoint is read-only and refuses every write." : `Unannotated or write-capable tools fail closed to ${META_ROUTES.write}.`} A truncated result carries a connecta.result action.`;
|
|
995
1009
|
const CALL_DESTRUCTIVE_DESC = "Call any tool not explicitly annotated readOnlyHint: true. Include a short reason for the human reviewer after checking the schema and consequences. The reason grants no authority and is not sent downstream.";
|
|
996
1010
|
/** A read-only endpoint keeps the six-tool surface, so this tool says why it never writes here. */
|
|
997
|
-
const CALL_DESTRUCTIVE_READ_ONLY_DESC =
|
|
1011
|
+
const CALL_DESTRUCTIVE_READ_ONLY_DESC = `This endpoint is read-only: search and describe omit write tools, and every write call here is refused. Reads still run; prefer ${META_ROUTES.read} or ${META_ROUTES.program} for them.`;
|
|
998
1012
|
const AUTHORIZE_DESC = "Use after auth_required. Returns an OAuth or operator-credential handoff, or required deployment configuration. A configured credential reports presence only: if a call just failed with an authentication or operator-configuration error, give the retained handoff to the operator; otherwise proceed with the call. force=true requests an OAuth restart when the verified user opens the /connect URL; this tool never accepts credentials.";
|
|
999
1013
|
const SKILLS_DESC = "List or fetch on-demand guidance. Fetch usage only when the always-loaded instructions are insufficient or a program needs repair.";
|
|
1000
1014
|
/**
|
|
@@ -1084,6 +1098,7 @@ const AUTHORIZE_INPUT = advertisedSchema(z.object({
|
|
|
1084
1098
|
export function registerMetaTools(server, registry, ctx) {
|
|
1085
1099
|
const mt = createMetaTools(registry, ctx.baseUrl, {
|
|
1086
1100
|
maxProgramResultChars: ctx.maxProgramResultChars,
|
|
1101
|
+
routes: ctx.routes,
|
|
1087
1102
|
...(ctx.native && !ctx.native.executeCode ? { pagingTool: "read_result" } : {}),
|
|
1088
1103
|
...(ctx.native ? { activitySource: "native" } : {}),
|
|
1089
1104
|
downstreamInput: Boolean(ctx.authElicitation),
|
package/dist/native-recovery.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Adapt only Connecta error envelopes; successful downstream payloads stay untouched. */
|
|
2
|
-
export function nativeRecovery(result,
|
|
2
|
+
export function nativeRecovery(result, wireName) {
|
|
3
3
|
const response = result;
|
|
4
4
|
const body = response?.structuredContent;
|
|
5
5
|
if (!response?.isError || !body || typeof body.error !== "object" || !body.error)
|
|
@@ -28,22 +28,10 @@ export function nativeRecovery(result, executeCode, wireName) {
|
|
|
28
28
|
}
|
|
29
29
|
: { host: "search", purpose: action.purpose };
|
|
30
30
|
}
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
? "the returned authorization handoff"
|
|
34
|
-
: tool.startsWith("call_")
|
|
35
|
-
? "the original native tool"
|
|
36
|
-
: tool === "execute_code" && executeCode
|
|
37
|
-
? tool
|
|
38
|
-
: "your host's tool search");
|
|
39
|
-
for (const key of ["message", "instructions", "retry"])
|
|
40
|
-
if (typeof error[key] === "string")
|
|
41
|
-
error[key] = rewrite(error[key]);
|
|
31
|
+
// Recovery text already uses the native wording: native pools build it with
|
|
32
|
+
// native route names. Only the address in retry text becomes the wire name.
|
|
42
33
|
if (wireName && typeof error.operation === "string" && typeof error.retry === "string")
|
|
43
|
-
error.retry = error.retry.replaceAll(
|
|
44
|
-
if (!executeCode && typeof error.instructions === "string") {
|
|
45
|
-
error.instructions = error.instructions.replace(/; connecta\.search[\s\S]*$/, ". Retry the original native call after recovery.");
|
|
46
|
-
}
|
|
34
|
+
error.retry = error.retry.replaceAll(error.operation, wireName);
|
|
47
35
|
return {
|
|
48
36
|
...response,
|
|
49
37
|
structuredContent: { ...body, error },
|
package/dist/native-tools.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { bytesToHex } from "./bytes.js";
|
|
2
|
+
import { routeNames } from "./agent-routes.js";
|
|
2
3
|
import { duringViewEffect } from "./runtime/call.js";
|
|
3
4
|
import { RequestScope, requestOwner } from "./runtime/request-scope.js";
|
|
4
5
|
import { Effect } from "effect";
|
|
@@ -149,6 +150,8 @@ export class NativeSurface {
|
|
|
149
150
|
// Listing omits a read-only endpoint's writes, as search does.
|
|
150
151
|
readOnly: options.readOnly,
|
|
151
152
|
defer: options.defer,
|
|
153
|
+
// Declared schema descriptions read as this pool serves them.
|
|
154
|
+
routes: routeNames(options.executeCode ? "native-code" : "native"),
|
|
152
155
|
});
|
|
153
156
|
}
|
|
154
157
|
/**
|
|
@@ -183,7 +186,7 @@ export class NativeSurface {
|
|
|
183
186
|
...(entry ? { _meta: { "dev.connecta/address": `${entry.connector.id}.${entry.tool.name}` } } : {}),
|
|
184
187
|
}, authMethod((args, request) => Effect.gen({ self: this }, function* () {
|
|
185
188
|
const result = yield* authInvoke(this, "call", [name, args ?? {}, request]);
|
|
186
|
-
return nativeRecovery(result,
|
|
189
|
+
return nativeRecovery(result, name);
|
|
187
190
|
})));
|
|
188
191
|
}
|
|
189
192
|
/**
|
package/dist/oauth-sealing.js
CHANGED
|
@@ -46,3 +46,13 @@ export async function deriveRequestStateKey(raw) {
|
|
|
46
46
|
info: new TextEncoder().encode("auth-elicitation"),
|
|
47
47
|
}, material, 256));
|
|
48
48
|
}
|
|
49
|
+
/** A non-extractable HMAC key for secret-bearing stored cache keys; nothing is stored. */
|
|
50
|
+
export async function deriveStorageDigestKey(raw) {
|
|
51
|
+
const material = await crypto.subtle.importKey("raw", new Uint8Array(raw), "HKDF", false, ["deriveKey"]);
|
|
52
|
+
return crypto.subtle.deriveKey({
|
|
53
|
+
name: "HKDF",
|
|
54
|
+
hash: "SHA-256",
|
|
55
|
+
salt: new TextEncoder().encode("connecta:storage-digest:v1"),
|
|
56
|
+
info: new TextEncoder().encode("cache-keys"),
|
|
57
|
+
}, material, { name: "HMAC", hash: "SHA-256", length: 256 }, false, ["sign"]);
|
|
58
|
+
}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
// Private, request-free discovery facts. No provider, schema, handler or I/O dependency.
|
|
2
2
|
import { lexicalSearchContent, lexicalQueryTerms } from "./catalog.js";
|
|
3
|
+
/** Ready calls one vocabulary offers a search: only its top-ranked operation. */
|
|
4
|
+
const MAX_READY_PER_VOCABULARY = 1;
|
|
5
|
+
/**
|
|
6
|
+
* Normalized action words. A summary may supply these; every other query word
|
|
7
|
+
* names an object and must match the operation's resource identity.
|
|
8
|
+
*/
|
|
9
|
+
const ACTIONS = new Set(["get", "list", "count", "show", "create", "update", "delete"]);
|
|
10
|
+
/** Path tokens that version an API rather than name a resource. */
|
|
11
|
+
const VERSION = /^v?\d+$/;
|
|
3
12
|
const tools = new WeakMap();
|
|
4
13
|
const connectors = new WeakMap();
|
|
14
|
+
const synonymGroups = new WeakMap();
|
|
5
15
|
const OPERATION_VOCABULARY_BOUNDS = {
|
|
6
16
|
operations: 8192,
|
|
7
17
|
rowChars: 1024,
|
|
@@ -28,20 +38,56 @@ const aliases = {
|
|
|
28
38
|
modify: "update",
|
|
29
39
|
remove: "delete",
|
|
30
40
|
};
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
41
|
+
const normal = (word) => aliases[word] ?? token(word);
|
|
42
|
+
const terms = (text) => [...new Set(lexicalQueryTerms(lexicalSearchContent(text)).map(normal))];
|
|
43
|
+
/** Resource words of a path segment or operation id, without version or action words. */
|
|
44
|
+
const identity = (text) => terms(text).filter((word) => !VERSION.test(word) && !ACTIONS.has(word));
|
|
45
|
+
function resourceOf(path, operationId) {
|
|
46
|
+
const segments = path.slice(0, OPERATION_VOCABULARY_BOUNDS.rowChars).split("/").map(identity);
|
|
47
|
+
const names = [...segments, identity(operationId.slice(0, OPERATION_VOCABULARY_BOUNDS.rowChars))].filter((name) => name.length > 0);
|
|
48
|
+
return { names, last: segments.at(-1) ?? [], terms: new Set(names.flat()) };
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Attach a maintained provider's lexical synonym groups to its connector. Each
|
|
52
|
+
* group's words are interchangeable query terms for that connector's tool
|
|
53
|
+
* ranking and operation vocabulary; nothing is inferred.
|
|
54
|
+
*/
|
|
55
|
+
export function registerSearchSynonyms(connector, groups) {
|
|
56
|
+
synonymGroups.set(connector, groups);
|
|
57
|
+
}
|
|
58
|
+
/** A connector's groups, including those a provider stamp inherits. */
|
|
59
|
+
function groupsOf(connector) {
|
|
60
|
+
let target = connector;
|
|
61
|
+
while (target && !synonymGroups.has(target))
|
|
62
|
+
target = Object.getPrototypeOf(target);
|
|
63
|
+
return target ? synonymGroups.get(target) : undefined;
|
|
64
|
+
}
|
|
65
|
+
/** The other words of every group holding `word`, each word under `normalize`. */
|
|
66
|
+
function synonymsIn(groups, word, normalize = (raw) => raw) {
|
|
67
|
+
return groups.flatMap((group) => {
|
|
68
|
+
const words = group.map(normalize);
|
|
69
|
+
return words.includes(word) ? words.filter((other) => other !== word) : [];
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Word to its declared synonyms for a connector's tool ranking, when its provider declares any. */
|
|
73
|
+
export function searchSynonyms(connector) {
|
|
74
|
+
const groups = groupsOf(connector);
|
|
75
|
+
return groups && ((word) => synonymsIn(groups, word));
|
|
76
|
+
}
|
|
34
77
|
/** Register only construction-authored compact summaries; never whole request/response schemas. */
|
|
35
78
|
export function registerOperationVocabulary(tool, rows) {
|
|
36
79
|
let postings;
|
|
37
80
|
const routes = [];
|
|
38
81
|
const cache = new Map();
|
|
39
|
-
tools.set(tool, (query, visible, safety, readOnly) => {
|
|
82
|
+
tools.set(tool, (query, visible, safety, readOnly, synonyms) => {
|
|
83
|
+
const none = { matched: false, ready: [] };
|
|
40
84
|
if (query.length > OPERATION_VOCABULARY_BOUNDS.queryChars)
|
|
41
|
-
return
|
|
85
|
+
return none;
|
|
42
86
|
const wanted = terms(query);
|
|
43
87
|
if (!wanted.length || wanted.length > OPERATION_VOCABULARY_BOUNDS.terms)
|
|
44
|
-
return
|
|
88
|
+
return none;
|
|
89
|
+
// Each wanted word matches through itself or any declared synonym.
|
|
90
|
+
const alternatives = wanted.map((word) => [word, ...(synonyms ? synonymsIn(synonyms, word, normal) : [])]);
|
|
45
91
|
if (!postings) {
|
|
46
92
|
postings = new Map();
|
|
47
93
|
let chars = 0;
|
|
@@ -52,7 +98,19 @@ export function registerOperationVocabulary(tool, rows) {
|
|
|
52
98
|
break;
|
|
53
99
|
chars += text.length;
|
|
54
100
|
const id = routes.length;
|
|
55
|
-
routes.push({
|
|
101
|
+
routes.push({
|
|
102
|
+
route: row.route,
|
|
103
|
+
write: row.write,
|
|
104
|
+
...(row.ready
|
|
105
|
+
? {
|
|
106
|
+
ready: {
|
|
107
|
+
path: row.ready.path,
|
|
108
|
+
summary: row.ready.summary.slice(0, OPERATION_VOCABULARY_BOUNDS.rowChars),
|
|
109
|
+
...resourceOf(row.ready.path, row.ready.operationId),
|
|
110
|
+
},
|
|
111
|
+
}
|
|
112
|
+
: {}),
|
|
113
|
+
});
|
|
56
114
|
for (const word of terms(text)) {
|
|
57
115
|
const hits = postings.get(word);
|
|
58
116
|
if (hits)
|
|
@@ -62,17 +120,20 @@ export function registerOperationVocabulary(tool, rows) {
|
|
|
62
120
|
}
|
|
63
121
|
}
|
|
64
122
|
}
|
|
65
|
-
const key =
|
|
123
|
+
const key = alternatives.map((words) => words.join("|")).join(" ");
|
|
66
124
|
let found = cache.get(key);
|
|
67
125
|
if (!found) {
|
|
68
|
-
const lists =
|
|
126
|
+
const lists = alternatives
|
|
127
|
+
.map((words) => [...new Set(words.flatMap((word) => postings.get(word) ?? []))])
|
|
128
|
+
.sort((a, b) => a.length - b.length);
|
|
69
129
|
const rest = lists.slice(1).map((list) => new Set(list));
|
|
70
130
|
found = lists[0].filter((id) => rest.every((set) => set.has(id)));
|
|
71
131
|
if (cache.size >= OPERATION_VOCABULARY_BOUNDS.cache)
|
|
72
132
|
cache.delete(cache.keys().next().value);
|
|
73
133
|
cache.set(key, found);
|
|
74
134
|
}
|
|
75
|
-
|
|
135
|
+
// The same endpoint, safety and visibility checks decide both answers.
|
|
136
|
+
const admitted = found.filter((id) => {
|
|
76
137
|
const row = routes[id];
|
|
77
138
|
if (row.write && readOnly)
|
|
78
139
|
return false;
|
|
@@ -80,6 +141,47 @@ export function registerOperationVocabulary(tool, rows) {
|
|
|
80
141
|
return false;
|
|
81
142
|
return row.route !== undefined && visible.has(row.route);
|
|
82
143
|
});
|
|
144
|
+
// A ready call must name what the query asks for. Every object word, or
|
|
145
|
+
// a declared synonym, belongs to a whole resource name whose words the
|
|
146
|
+
// query states in order. A word met only in a summary, or only as part of
|
|
147
|
+
// a compound name, can route a search, never stand in for the resource.
|
|
148
|
+
const objects = alternatives.filter((words) => !ACTIONS.has(words[0]));
|
|
149
|
+
const at = (name, start) => start + name.length <= objects.length && name.every((word, offset) => objects[start + offset].includes(word));
|
|
150
|
+
const covers = (names) => {
|
|
151
|
+
const covered = objects.map(() => false);
|
|
152
|
+
for (const name of names)
|
|
153
|
+
for (let start = 0; start < objects.length; start++)
|
|
154
|
+
if (at(name, start))
|
|
155
|
+
name.forEach((_, offset) => (covered[start + offset] = true));
|
|
156
|
+
return covered.every(Boolean);
|
|
157
|
+
};
|
|
158
|
+
const asked = new Set(alternatives.flat());
|
|
159
|
+
return {
|
|
160
|
+
matched: admitted.length > 0,
|
|
161
|
+
ready: objects.length
|
|
162
|
+
? admitted
|
|
163
|
+
.flatMap((id) => {
|
|
164
|
+
const { route, write, ready } = routes[id];
|
|
165
|
+
if (!ready || write || !route || !covers(ready.names))
|
|
166
|
+
return [];
|
|
167
|
+
return [
|
|
168
|
+
{
|
|
169
|
+
path: ready.path,
|
|
170
|
+
summary: ready.summary,
|
|
171
|
+
route,
|
|
172
|
+
id,
|
|
173
|
+
// Relevance first: the last segment names the resource asked
|
|
174
|
+
// for, then the fewest resource words the query did not ask for.
|
|
175
|
+
last: Number(objects.some((_, start) => ready.last.length > 0 && at(ready.last, start))),
|
|
176
|
+
extra: [...ready.terms].filter((word) => !asked.has(word) && !ACTIONS.has(word)).length,
|
|
177
|
+
},
|
|
178
|
+
];
|
|
179
|
+
})
|
|
180
|
+
.sort((a, b) => b.last - a.last || a.extra - b.extra || a.path.length - b.path.length || a.id - b.id)
|
|
181
|
+
.slice(0, MAX_READY_PER_VOCABULARY)
|
|
182
|
+
.map(({ path, summary, route }) => ({ path, summary, route }))
|
|
183
|
+
: [],
|
|
184
|
+
};
|
|
83
185
|
});
|
|
84
186
|
}
|
|
85
187
|
/** Move private facts across the public ApiTool -> Connector construction boundary. */
|
|
@@ -91,11 +193,31 @@ export function registerConnectorVocabulary(connector, declarations) {
|
|
|
91
193
|
if (entries.length)
|
|
92
194
|
connectors.set(connector, new Map(entries));
|
|
93
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Read tools whose operation vocabulary matches every query word, and the
|
|
198
|
+
* fully determined reads among those operations, each through a visible read
|
|
199
|
+
* tool of `catalog`: the caller's view, after endpoint, connector, hidden-tool
|
|
200
|
+
* and grant filtering.
|
|
201
|
+
*/
|
|
94
202
|
export function operationVocabularyMatches(connector, catalog, query, safety, readOnly) {
|
|
95
203
|
const registered = connectors.get(connector);
|
|
204
|
+
const tools = [];
|
|
205
|
+
const ready = [];
|
|
96
206
|
if (!registered)
|
|
97
|
-
return
|
|
207
|
+
return { tools, ready };
|
|
98
208
|
const visible = new Set(catalog.map((tool) => tool.name));
|
|
99
|
-
|
|
100
|
-
|
|
209
|
+
const synonyms = groupsOf(connector);
|
|
210
|
+
for (const tool of catalog) {
|
|
211
|
+
const vocabulary = tool.classification === "read" ? registered.get(tool.name) : undefined;
|
|
212
|
+
const found = vocabulary?.(query, visible, safety, readOnly || connector.readOnly === true, synonyms);
|
|
213
|
+
if (!found?.matched)
|
|
214
|
+
continue;
|
|
215
|
+
tools.push(tool);
|
|
216
|
+
for (const { route, ...call } of found.ready) {
|
|
217
|
+
const target = catalog.find((candidate) => candidate.name === route);
|
|
218
|
+
if (target?.classification === "read")
|
|
219
|
+
ready.push({ ...call, tool: target });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return { tools, ready };
|
|
101
223
|
}
|
|
@@ -8,7 +8,7 @@ type FailureStep = "MCP handshake" | "tools/list" | "tools/call" | "resources/re
|
|
|
8
8
|
*/
|
|
9
9
|
export declare const OAUTH_ERROR_CODES: ReadonlySet<string>;
|
|
10
10
|
/** Inbound denial facts, never identity text or provider diagnostics. */
|
|
11
|
-
export type AuthDenialReason = "authentication_abandoned_saturated" | "authentication_busy" | "authentication_failed" | "authentication_provider_missing" | "authentication_unreachable" | "access_context_missing" | "access_context_unavailable" | "access_audience_invalid" | "access_identity_unavailable" | "access_authorization_header_rejected" | "authorization_header_invalid" | "token_type_mismatch" | "oauth_verification_failed" | "oauth_client_not_allowed" | "oauth_binding_mismatch" | "session_origin_mismatch" | "token_identity_mismatch" | "organization_membership_denied" | "organization_lookup_failed" | "organization_lookup_busy" | "user_missing" | "email_lookup_failed" | "verified_email_invalid" | "email_domain_denied" | "gate_denied" | "gate_failed" | "not_maintainer" | "pool_not_declared" | "not_pool_member";
|
|
11
|
+
export type AuthDenialReason = "authentication_abandoned_saturated" | "authentication_busy" | "authentication_failed" | "authentication_provider_missing" | "authentication_unreachable" | "access_context_missing" | "access_context_unavailable" | "access_audience_invalid" | "access_identity_unavailable" | "access_authorization_header_rejected" | "authorization_header_invalid" | "token_type_mismatch" | "oauth_verification_failed" | "oauth_client_not_allowed" | "oauth_binding_mismatch" | "session_origin_mismatch" | "token_identity_mismatch" | "organization_membership_denied" | "organization_lookup_failed" | "organization_lookup_busy" | "user_missing" | "email_lookup_failed" | "verified_email_invalid" | "email_domain_denied" | "gate_denied" | "gate_failed" | "ambiguous_pool" | "not_maintainer" | "pool_not_declared" | "not_pool_member";
|
|
12
12
|
/**
|
|
13
13
|
* Name `ctor`'s instances `label` in records and fixed text. Call once, at
|
|
14
14
|
* module scope, beside the class. An error whose prototype chain reaches no
|
|
@@ -110,7 +110,7 @@ export declare function failureRecord(subject: FailureSubject, failure?: unknown
|
|
|
110
110
|
* The events an operator log reports failures as. Closed: a new one is a
|
|
111
111
|
* reviewed line here, never a string assembled at the call site.
|
|
112
112
|
*/
|
|
113
|
-
export type FailureEvent = "Authentication admission refused" | "Cloudflare Access verification unavailable" | "operator_refused" | "Authentication released after its pending work became unreachable" | "negotiation cache read failed" | "negotiation cache write failed" | "pool tool grant is unreachable" | "call failed" | "input schema unusable; arguments are not validated" | "catalog read failed" | "catalog persistence failed" | "catalog invalidation failed" | "catalog refresh observation failed" | "catalog refresh failed; serving stale catalog" | "deferred catalog refresh failed" | "deferred catalog refresh could not attach to the runtime" | "catalog drift check failed" | "schema digest check failed; serving digested reviews as writes" | "credential shape read failed" | "credential test failed" | "credential test threw" | "session termination refused or failed; the downstream session may remain until its provider timeout" | "activity record failed" | "catalog drift record failed" | "activity read failed" | "OAuth start failed" | "OAuth disconnect failed" | "OAuth callback verifyState threw; no authorization code was exchanged" | "OAuth callback handoff could not be consumed; no authorization code was exchanged" | "MCP default endpoint request denied" | "MCP pool request denied" | "MCP agent endpoint request denied" | "OAuth error callback consent could not be consumed; no authorization code was exchanged" | "MCP handler error" | "operator status" | "operator agent slots read failed" | "result paging unavailable" | "native catalog unavailable" | "native name collision" | "native tools/list refused a pool over the 1,000-tool native cap" | "request failed" | "Clerk request denied" | "Clerk authentication failed";
|
|
113
|
+
export type FailureEvent = "Authentication admission refused" | "Cloudflare Access verification unavailable" | "operator_refused" | "Authentication released after its pending work became unreachable" | "negotiation cache read failed" | "negotiation cache write failed" | "discovery health read failed" | "discovery health write failed" | "pool tool grant is unreachable" | "call failed" | "input schema unusable; arguments are not validated" | "catalog read failed" | "catalog persistence failed" | "catalog invalidation failed" | "catalog refresh observation failed" | "catalog refresh failed; serving stale catalog" | "deferred catalog refresh failed" | "deferred catalog refresh could not attach to the runtime" | "catalog drift check failed" | "schema digest check failed; serving digested reviews as writes" | "credential shape read failed" | "credential test failed" | "credential test threw" | "credential verification record failed" | "session termination refused or failed; the downstream session may remain until its provider timeout" | "activity record failed" | "catalog drift record failed" | "activity read failed" | "OAuth start failed" | "OAuth disconnect failed" | "OAuth callback verifyState threw; no authorization code was exchanged" | "OAuth callback handoff could not be consumed; no authorization code was exchanged" | "MCP default endpoint request denied" | "MCP pool request denied" | "MCP agent endpoint request denied" | "OAuth error callback consent could not be consumed; no authorization code was exchanged" | "MCP handler error" | "operator status" | "operator agent slots read failed" | "result paging unavailable" | "native catalog unavailable" | "native name collision" | "native tools/list refused a pool over the 1,000-tool native cap" | "request failed" | "Clerk request denied" | "Clerk authentication failed";
|
|
114
114
|
/**
|
|
115
115
|
* Log one failure as `[connecta] <event>` and its record. A record
|
|
116
116
|
* `failureRecord` did not build is replaced, whatever its shape, by a fixed
|