@zackbart/connecta 0.6.1 → 0.7.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 +256 -0
- package/README.md +24 -20
- package/dist/auth/bearer.d.ts +4 -3
- package/dist/auth/bearer.d.ts.map +1 -1
- package/dist/auth/bearer.js +10 -8
- package/dist/auth/bearer.js.map +1 -1
- package/dist/auth/clerk.d.ts +8 -7
- package/dist/auth/clerk.d.ts.map +1 -1
- package/dist/auth/clerk.js +27 -8
- package/dist/auth/clerk.js.map +1 -1
- package/dist/connector-scope.d.ts +13 -0
- package/dist/connector-scope.d.ts.map +1 -0
- package/dist/connector-scope.js +35 -0
- package/dist/connector-scope.js.map +1 -0
- package/dist/connectors/api.d.ts +5 -5
- package/dist/connectors/api.d.ts.map +1 -1
- package/dist/connectors/remote-mcp.d.ts +3 -3
- package/dist/connectors/remote-mcp.d.ts.map +1 -1
- package/dist/connectors/remote-mcp.js +309 -10
- package/dist/connectors/remote-mcp.js.map +1 -1
- package/dist/credential-health.d.ts +8 -5
- package/dist/credential-health.d.ts.map +1 -1
- package/dist/credential-health.js +99 -51
- package/dist/credential-health.js.map +1 -1
- package/dist/credentials.d.ts +51 -2
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +68 -3
- package/dist/credentials.js.map +1 -1
- package/dist/index.d.ts +83 -82
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +74 -24
- package/dist/index.js.map +1 -1
- package/dist/meta-tools.d.ts +3 -3
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +16 -7
- package/dist/meta-tools.js.map +1 -1
- package/dist/registry.d.ts +3 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +4 -3
- package/dist/registry.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +144 -52
- package/dist/server.js.map +1 -1
- package/dist/toolkits.js +1 -1
- package/dist/types.d.ts +41 -27
- package/dist/types.d.ts.map +1 -1
- package/dist/ui.d.ts +24 -10
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +594 -172
- 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/auth/bearer.ts +10 -8
- package/src/auth/clerk.ts +28 -9
- package/src/connector-scope.ts +41 -0
- package/src/connectors/api.ts +5 -5
- package/src/connectors/remote-mcp.ts +348 -25
- package/src/credential-health.ts +120 -57
- package/src/credentials.ts +96 -3
- package/src/index.ts +169 -106
- package/src/meta-tools.ts +22 -7
- package/src/registry.ts +4 -3
- package/src/server.ts +183 -70
- package/src/toolkits.ts +1 -1
- package/src/types.ts +41 -27
- package/src/ui.ts +631 -170
- package/src/version.ts +1 -1
package/src/index.ts
CHANGED
|
@@ -28,6 +28,84 @@ import type {
|
|
|
28
28
|
Logger,
|
|
29
29
|
} from "./types.js";
|
|
30
30
|
|
|
31
|
+
/** Payload-free activity storage and operator-read policy. */
|
|
32
|
+
export interface ConnectaActivityConfig {
|
|
33
|
+
/**
|
|
34
|
+
* Privacy-minimal downstream tool activity storage. Writes are best-effort
|
|
35
|
+
* and never change tool results. Implement `list` to enable the Activity UI.
|
|
36
|
+
*/
|
|
37
|
+
store: ActivityStore;
|
|
38
|
+
/**
|
|
39
|
+
* Optional authorization gate for the Activity read API. MCP authentication
|
|
40
|
+
* is still required first. Omit to admit every authenticated actor.
|
|
41
|
+
*/
|
|
42
|
+
readGate?: ActivityReadGate;
|
|
43
|
+
/** Stable deployment label included in activity events, e.g. "production". */
|
|
44
|
+
deploymentId?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Operator-vault encryption and proactive credential-health tuning. */
|
|
48
|
+
export interface ConnectaCredentialsConfig {
|
|
49
|
+
/**
|
|
50
|
+
* Base64-encoded 32-byte AES key for credentials managed on /credentials.
|
|
51
|
+
* Keep this in the runtime's secret store, never in KV or source control.
|
|
52
|
+
*/
|
|
53
|
+
encryptionKey?: string;
|
|
54
|
+
/**
|
|
55
|
+
* Tuning for proactive credential liveness checks that let a connector's
|
|
56
|
+
* status flip to `auth_required` before an agent's call fails. Defaults: one
|
|
57
|
+
* check per connector per 15 minutes, four in flight, 30 seconds each,
|
|
58
|
+
* triggered opportunistically by inbound authenticated traffic.
|
|
59
|
+
*
|
|
60
|
+
* Optional even without an encryption key because downstream OAuth connectors
|
|
61
|
+
* manage their own grants. `Connecta.checkCredentials()` runs the same checks
|
|
62
|
+
* on demand for a Worker cron trigger or Node interval.
|
|
63
|
+
*/
|
|
64
|
+
health?: CredentialHealthConfig;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Tool-catalog caching, persistence, stale fallback, and probe deadlines. */
|
|
68
|
+
export interface ConnectaDiscoveryConfig {
|
|
69
|
+
/** Tool-list cache TTL (seconds). Default 300. */
|
|
70
|
+
catalogTtlSeconds?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Persist serializable remote tool catalogs in storage so cold isolates can
|
|
73
|
+
* discover tools without a downstream handshake. Default true.
|
|
74
|
+
*/
|
|
75
|
+
persistCatalog?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* How long an expired persisted catalog remains available as a fallback
|
|
78
|
+
* when a live refresh fails. Default 3600 seconds.
|
|
79
|
+
*/
|
|
80
|
+
staleCatalogSeconds?: number;
|
|
81
|
+
/**
|
|
82
|
+
* Deadline (ms) for each downstream probe/catalog call fanned out by
|
|
83
|
+
* `list_connectors`, `search_tools`, and `describe_tools`. Defaults to
|
|
84
|
+
* 30_000. A timed-out connector degrades independently; this does not apply
|
|
85
|
+
* to tool calls or currently abort the underlying fetch.
|
|
86
|
+
*/
|
|
87
|
+
probeTimeoutMs?: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Deployment-wide call deadlines and inline-result paging threshold. */
|
|
91
|
+
export interface ConnectaCallsConfig {
|
|
92
|
+
/**
|
|
93
|
+
* Deadline (ms) for `call_tool`/`batch_call` calls that pass no `timeoutMs`.
|
|
94
|
+
* An explicit per-call value wins. Opt-in: unset by default, so existing
|
|
95
|
+
* long-running calls gain no surprise deadline.
|
|
96
|
+
*
|
|
97
|
+
* This bounds one attempt, not all retries. `execute_code` host calls are
|
|
98
|
+
* unaffected because they already carry their own bound.
|
|
99
|
+
*/
|
|
100
|
+
defaultTimeoutMs?: number;
|
|
101
|
+
/**
|
|
102
|
+
* Max inline result size (bytes) before truncation and `get_result` paging.
|
|
103
|
+
* Must be a finite whole number >= 1; invalid values warn and fall back to
|
|
104
|
+
* 50_000. Connectors may override it individually.
|
|
105
|
+
*/
|
|
106
|
+
maxResultBytes?: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
31
109
|
export interface ConnectaConfig {
|
|
32
110
|
connectors: Connector[];
|
|
33
111
|
/**
|
|
@@ -62,18 +140,6 @@ export interface ConnectaConfig {
|
|
|
62
140
|
* address, or an address naming no tool on an in-code connector all throw.
|
|
63
141
|
*/
|
|
64
142
|
toolkits?: ToolkitConfig;
|
|
65
|
-
/**
|
|
66
|
-
* Privacy-minimal downstream tool activity storage. Writes are best-effort
|
|
67
|
-
* and never change tool results. Implement `list` to enable the Activity UI.
|
|
68
|
-
*/
|
|
69
|
-
activity?: ActivityStore;
|
|
70
|
-
/**
|
|
71
|
-
* Optional authorization gate for the Activity read API. MCP authentication
|
|
72
|
-
* is still required first. Omit to admit every authenticated actor.
|
|
73
|
-
*/
|
|
74
|
-
activityReadGate?: ActivityReadGate;
|
|
75
|
-
/** Stable deployment label included in activity events, e.g. "production". */
|
|
76
|
-
activityDeploymentId?: string;
|
|
77
143
|
/** Inbound auth adapters. Includes bearerToken(...); omit for open (dev). */
|
|
78
144
|
auth?: InboundAuth | InboundAuth[];
|
|
79
145
|
/** KVStorage impl. Defaults to memoryStorage(). */
|
|
@@ -83,78 +149,17 @@ export interface ConnectaConfig {
|
|
|
83
149
|
* HTTPS URL also redirects matching inbound HTTP requests to HTTPS.
|
|
84
150
|
*/
|
|
85
151
|
publicUrl?: string;
|
|
86
|
-
/**
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
152
|
+
/** Payload-free tool activity storage and operator-read policy. */
|
|
153
|
+
activity?: ConnectaActivityConfig;
|
|
154
|
+
/** Operator credential vault and proactive liveness-check settings. */
|
|
155
|
+
credentials?: ConnectaCredentialsConfig;
|
|
156
|
+
/** Tool-catalog caching, persistence, stale fallback, and probe deadlines. */
|
|
157
|
+
discovery?: ConnectaDiscoveryConfig;
|
|
158
|
+
/** Deployment-wide call deadlines and result paging threshold. */
|
|
159
|
+
calls?: ConnectaCallsConfig;
|
|
91
160
|
/** Optional browser UI and OAuth result-page labels. */
|
|
92
161
|
branding?: ConnectaBranding;
|
|
93
162
|
logger?: Logger;
|
|
94
|
-
/** Tool-list cache TTL (seconds). Default 300. */
|
|
95
|
-
toolCacheTtlSeconds?: number;
|
|
96
|
-
/**
|
|
97
|
-
* Persist serializable remote tool catalogs in storage so cold isolates can
|
|
98
|
-
* discover tools without a downstream handshake. Default true.
|
|
99
|
-
*/
|
|
100
|
-
persistToolCatalog?: boolean;
|
|
101
|
-
/**
|
|
102
|
-
* How long an expired persisted catalog remains available as a fallback
|
|
103
|
-
* when a live refresh fails. Default 3600 seconds.
|
|
104
|
-
*/
|
|
105
|
-
toolCatalogStaleSeconds?: number;
|
|
106
|
-
/**
|
|
107
|
-
* Max inline result size (bytes) before call_tool/batch_call truncate and
|
|
108
|
-
* stash the full text for get_result paging. Must be a whole number of bytes
|
|
109
|
-
* >= 1; anything else (0, negative, fractional, NaN, Infinity) warns at
|
|
110
|
-
* startup and falls back to the default 50_000.
|
|
111
|
-
*/
|
|
112
|
-
maxResultBytes?: number;
|
|
113
|
-
/**
|
|
114
|
-
* Deadline (ms) applied to call_tool/batch_call calls that pass no
|
|
115
|
-
* `timeoutMs`, giving the connector both a budget (`ctx.timeoutMs`) and a
|
|
116
|
-
* cancellation signal (`ctx.signal`). An explicit per-call `timeoutMs` always
|
|
117
|
-
* wins. **Opt-in — undefined by default**, because switching it on globally
|
|
118
|
-
* would put a deadline on every call in an existing deployment and the
|
|
119
|
-
* failure mode is a working long-running call starting to time out.
|
|
120
|
-
* `execute_code` host calls are unaffected; they already carry a 15 s bound.
|
|
121
|
-
*
|
|
122
|
-
* Bounds a single attempt, not the whole call — the same as an explicit
|
|
123
|
-
* `timeoutMs` has always done. A call that also passes `maxRetries` can
|
|
124
|
-
* therefore run to roughly `(maxRetries + 1)` times this value plus backoff.
|
|
125
|
-
* `maxRetries` defaults to 0, so this is the total for every call that does
|
|
126
|
-
* not explicitly ask to retry.
|
|
127
|
-
*/
|
|
128
|
-
defaultToolTimeoutMs?: number;
|
|
129
|
-
/**
|
|
130
|
-
* Deadline (ms) applied to each individual downstream probe/catalog call that
|
|
131
|
-
* the discovery meta-tools fan out — `list_connectors` (with `probe`),
|
|
132
|
-
* `search_tools`, and `describe_tools` — so a single hung connector can no
|
|
133
|
-
* longer stall the whole meta-tool call. **Defaults to a generous 30_000**,
|
|
134
|
-
* chosen to trip only on a pathological hang, not on a realistically slow
|
|
135
|
-
* probe, so having it on by default will not break existing deployments.
|
|
136
|
-
* Bounds one downstream call, not the whole fan-out: a connector that outruns
|
|
137
|
-
* it degrades to an unavailable/errored entry while the rest are unaffected.
|
|
138
|
-
*
|
|
139
|
-
* Does NOT apply to `call_tool`/`batch_call` — those carry their own budget
|
|
140
|
-
* via `defaultToolTimeoutMs` or a per-call `timeoutMs`. Note this bounds the
|
|
141
|
-
* caller-facing wait only; the underlying fetch is not currently aborted, so
|
|
142
|
-
* real cancellation of the downstream request is a deferred follow-up.
|
|
143
|
-
*/
|
|
144
|
-
probeTimeoutMs?: number;
|
|
145
|
-
/**
|
|
146
|
-
* Tuning for the proactive credential liveness checks (issue #24) that let a
|
|
147
|
-
* connector's status flip to `auth_required` *before* an agent's call fails.
|
|
148
|
-
* Defaults are safe to leave alone: at most one check per connector per 15
|
|
149
|
-
* minutes, four in flight, 30 s each, triggered opportunistically by inbound
|
|
150
|
-
* authenticated traffic. Only connectors holding a credential connecta stores
|
|
151
|
-
* — an operator-managed `credential`, or a downstream-OAuth grant — are ever
|
|
152
|
-
* checked, and a check never calls a downstream tool.
|
|
153
|
-
*
|
|
154
|
-
* `Connecta.checkCredentials()` is the same check on demand, for a Worker cron
|
|
155
|
-
* trigger or a Node interval.
|
|
156
|
-
*/
|
|
157
|
-
credentialHealth?: CredentialHealthConfig;
|
|
158
163
|
serverInfo?: {
|
|
159
164
|
name?: string;
|
|
160
165
|
version?: string;
|
|
@@ -193,7 +198,7 @@ export interface Connecta {
|
|
|
193
198
|
*
|
|
194
199
|
* Returns one outcome per connector considered, including why a connector was
|
|
195
200
|
* skipped (`fresh` is the rate limit: a connector checked less than
|
|
196
|
-
* `
|
|
201
|
+
* `credentials.health.intervalSeconds` ago is not re-checked unless `force`).
|
|
197
202
|
* Never rejects on a connector failure — a broken connector becomes an `error`
|
|
198
203
|
* verdict. Needs a base URL for connector contexts: `publicUrl` supplies it,
|
|
199
204
|
* or pass one.
|
|
@@ -223,6 +228,59 @@ function normalizeAuth(auth: ConnectaConfig["auth"]): InboundAuth[] {
|
|
|
223
228
|
});
|
|
224
229
|
}
|
|
225
230
|
|
|
231
|
+
const LEGACY_CONFIG_MIGRATIONS = [
|
|
232
|
+
["activityReadGate", "activity.readGate"],
|
|
233
|
+
["activityDeploymentId", "activity.deploymentId"],
|
|
234
|
+
["credentialEncryptionKey", "credentials.encryptionKey"],
|
|
235
|
+
["credentialHealth", "credentials.health"],
|
|
236
|
+
["toolCacheTtlSeconds", "discovery.catalogTtlSeconds"],
|
|
237
|
+
["persistToolCatalog", "discovery.persistCatalog"],
|
|
238
|
+
["toolCatalogStaleSeconds", "discovery.staleCatalogSeconds"],
|
|
239
|
+
["probeTimeoutMs", "discovery.probeTimeoutMs"],
|
|
240
|
+
["defaultToolTimeoutMs", "calls.defaultTimeoutMs"],
|
|
241
|
+
["maxResultBytes", "calls.maxResultBytes"],
|
|
242
|
+
] as const;
|
|
243
|
+
|
|
244
|
+
const hasOwn = (value: object, key: PropertyKey): boolean =>
|
|
245
|
+
Object.prototype.hasOwnProperty.call(value, key);
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Fail closed at the public boundary: a JavaScript caller on the v0.6 shape
|
|
249
|
+
* must receive one complete migration error, never silently lose an option to
|
|
250
|
+
* a default. This runs before createConnecta reads any other config field.
|
|
251
|
+
*/
|
|
252
|
+
function assertNoLegacyConfig(config: ConnectaConfig): void {
|
|
253
|
+
const candidate = config as unknown as Record<PropertyKey, unknown>;
|
|
254
|
+
const found: Array<readonly [string, string]> = [];
|
|
255
|
+
if (hasOwn(candidate, "activity")) {
|
|
256
|
+
const activity = candidate.activity;
|
|
257
|
+
const isObject =
|
|
258
|
+
typeof activity === "object" && activity !== null;
|
|
259
|
+
// A valid v0.6 ActivityStore can itself own a backend field named `store`.
|
|
260
|
+
// Its required `record` method (including a prototype method) therefore
|
|
261
|
+
// takes precedence over the otherwise-new wrapper shape.
|
|
262
|
+
const hasLegacyRecord =
|
|
263
|
+
isObject &&
|
|
264
|
+
typeof (activity as { record?: unknown }).record === "function";
|
|
265
|
+
if (
|
|
266
|
+
activity !== undefined &&
|
|
267
|
+
(!isObject ||
|
|
268
|
+
hasLegacyRecord ||
|
|
269
|
+
!hasOwn(activity, "store"))
|
|
270
|
+
) {
|
|
271
|
+
found.push(["activity", "activity.store"]);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
for (const migration of LEGACY_CONFIG_MIGRATIONS) {
|
|
275
|
+
if (hasOwn(candidate, migration[0])) found.push(migration);
|
|
276
|
+
}
|
|
277
|
+
if (found.length === 0) return;
|
|
278
|
+
throw new Error(
|
|
279
|
+
"Unsupported v0.6.x ConnectaConfig options. Migrate each path for v0.7.0:\n" +
|
|
280
|
+
found.map(([oldPath, newPath]) => `- ${oldPath} -> ${newPath}`).join("\n"),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
|
|
226
284
|
/**
|
|
227
285
|
* One-time construction warnings for deployment shapes that run fine but are
|
|
228
286
|
* usually unintended. Warning-only — never throws and never changes behavior;
|
|
@@ -333,12 +391,12 @@ function warnInsecureConfig(
|
|
|
333
391
|
);
|
|
334
392
|
}
|
|
335
393
|
|
|
336
|
-
//
|
|
337
|
-
// offers one,
|
|
394
|
+
// Operator shells render exactly one provider's browser sign-in config — the
|
|
395
|
+
// first that offers one, matching the server route's `find` — and that
|
|
338
396
|
// provider's URLs reach the browser: frontendApiUrl as the loader's
|
|
339
397
|
// `<script src>`, signInUrl/signUpUrl as the addresses ClerkJS navigates to.
|
|
340
398
|
// Gate-or-drop like a branding href: rendering drops a rejected value and the
|
|
341
|
-
//
|
|
399
|
+
// operator page then reports that Clerk could not load or quietly signs in
|
|
342
400
|
// through Clerk's defaults — both confusing symptoms without this line naming
|
|
343
401
|
// the cause. Checking only the rendered provider keeps the claim true — a
|
|
344
402
|
// later provider's uiAuth never reaches the page, so there is nothing there
|
|
@@ -351,7 +409,7 @@ function warnInsecureConfig(
|
|
|
351
409
|
`${droppedUiAuth.join(", ")} dropped: every uiAuth URL reaches the ` +
|
|
352
410
|
"browser — as the sign-in loader's source, or as a place Clerk sends " +
|
|
353
411
|
"the operator — so each must be an absolute https URL. A dropped " +
|
|
354
|
-
"value reaches no part of the page: without frontendApiUrl
|
|
412
|
+
"value reaches no part of the page: without frontendApiUrl the operator shell renders " +
|
|
355
413
|
"no loader and cannot start a sign-in, and without signInUrl/signUpUrl " +
|
|
356
414
|
"it signs in through Clerk's defaults.",
|
|
357
415
|
);
|
|
@@ -359,7 +417,7 @@ function warnInsecureConfig(
|
|
|
359
417
|
|
|
360
418
|
// A credential test hook that cannot test the declared credential shape.
|
|
361
419
|
// The shape picks the hook (see `credentialTestRule`) and the other one is
|
|
362
|
-
// never substituted, so the connector is simply not testable: /
|
|
420
|
+
// never substituted, so the connector is simply not testable: /credentials offers no
|
|
363
421
|
// Test action and the route answers 400. Without this line the only way to
|
|
364
422
|
// discover the mistake is to click a button that isn't there.
|
|
365
423
|
for (const connector of config.connectors) {
|
|
@@ -367,20 +425,23 @@ function warnInsecureConfig(
|
|
|
367
425
|
if (!mismatch) continue;
|
|
368
426
|
logger.warn(
|
|
369
427
|
`[connecta] connector "${connector.id}" cannot test its credential: ` +
|
|
370
|
-
`${describeCredentialTestMismatch(mismatch)}. /
|
|
428
|
+
`${describeCredentialTestMismatch(mismatch)}. /credentials offers no Test ` +
|
|
371
429
|
`action and POST /ui/credentials/${connector.id}/test answers 400 ` +
|
|
372
430
|
"until the matching hook is implemented.",
|
|
373
431
|
);
|
|
374
432
|
}
|
|
375
433
|
|
|
376
|
-
// OAuth connectors whose callback
|
|
377
|
-
//
|
|
434
|
+
// OAuth connectors whose callback cannot perform a state/CSRF check. The
|
|
435
|
+
// public route refuses every callback for these connectors rather than hand
|
|
436
|
+
// an unverified code to finishAuth, so this warning explains why auth cannot
|
|
437
|
+
// complete instead of describing a vulnerability the server permits.
|
|
378
438
|
for (const connector of oauthConnectors) {
|
|
379
439
|
if (!connector.verifyState) {
|
|
380
440
|
logger.warn(
|
|
381
441
|
`[connecta] connector "${connector.id}" has an OAuth callback with no ` +
|
|
382
|
-
`state/CSRF check: /oauth/callback/${connector.id}
|
|
383
|
-
"
|
|
442
|
+
`state/CSRF check: /oauth/callback/${connector.id} refuses every ` +
|
|
443
|
+
"callback rather than exchange an unverified code. Implement " +
|
|
444
|
+
"`verifyState` to complete authorization (the shipped remoteMcp " +
|
|
384
445
|
"connector already does).",
|
|
385
446
|
);
|
|
386
447
|
}
|
|
@@ -388,26 +449,28 @@ function warnInsecureConfig(
|
|
|
388
449
|
}
|
|
389
450
|
|
|
390
451
|
export function createConnecta(config: ConnectaConfig): Connecta {
|
|
452
|
+
assertNoLegacyConfig(config);
|
|
391
453
|
const storage = config.storage ?? memoryStorage();
|
|
392
454
|
const logger = config.logger ?? defaultLogger();
|
|
393
455
|
const credentialConnectors = config.connectors.filter((c) => c.credential);
|
|
394
|
-
|
|
456
|
+
const encryptionKey = config.credentials?.encryptionKey;
|
|
457
|
+
if (credentialConnectors.length > 0 && !encryptionKey) {
|
|
395
458
|
throw new Error(
|
|
396
|
-
`
|
|
459
|
+
`credentials.encryptionKey is required by connector credentials: ${credentialConnectors.map((c) => c.id).join(", ")}`,
|
|
397
460
|
);
|
|
398
461
|
}
|
|
399
|
-
const credentialVault =
|
|
400
|
-
? new CredentialVault(storage,
|
|
462
|
+
const credentialVault = encryptionKey
|
|
463
|
+
? new CredentialVault(storage, encryptionKey)
|
|
401
464
|
: undefined;
|
|
402
465
|
const registry = new Registry(config.connectors, {
|
|
403
466
|
storage,
|
|
404
467
|
logger,
|
|
405
468
|
credentialVault,
|
|
406
|
-
toolCacheTtlSeconds: config.
|
|
407
|
-
persistToolCatalog: config.
|
|
408
|
-
toolCatalogStaleSeconds: config.
|
|
409
|
-
maxResultBytes: config.maxResultBytes,
|
|
410
|
-
credentialHealth: config.
|
|
469
|
+
toolCacheTtlSeconds: config.discovery?.catalogTtlSeconds,
|
|
470
|
+
persistToolCatalog: config.discovery?.persistCatalog,
|
|
471
|
+
toolCatalogStaleSeconds: config.discovery?.staleCatalogSeconds,
|
|
472
|
+
maxResultBytes: config.calls?.maxResultBytes,
|
|
473
|
+
credentialHealth: config.credentials?.health,
|
|
411
474
|
});
|
|
412
475
|
// Throws on every structural mistake it can see (see resolveToolkits): a
|
|
413
476
|
// typo must not become a scope the operator never wrote. Note this is about
|
|
@@ -430,12 +493,12 @@ export function createConnecta(config: ConnectaConfig): Connecta {
|
|
|
430
493
|
version: config.serverInfo?.version ?? CONNECTA_VERSION,
|
|
431
494
|
},
|
|
432
495
|
logger,
|
|
433
|
-
activity: config.activity,
|
|
434
|
-
activityReadGate: config.
|
|
435
|
-
activityDeploymentId: config.
|
|
496
|
+
activity: config.activity?.store,
|
|
497
|
+
activityReadGate: config.activity?.readGate,
|
|
498
|
+
activityDeploymentId: config.activity?.deploymentId,
|
|
436
499
|
executor: config.executor,
|
|
437
|
-
defaultToolTimeoutMs: config.
|
|
438
|
-
probeTimeoutMs: config.probeTimeoutMs,
|
|
500
|
+
defaultToolTimeoutMs: config.calls?.defaultTimeoutMs,
|
|
501
|
+
probeTimeoutMs: config.discovery?.probeTimeoutMs,
|
|
439
502
|
credentialVault,
|
|
440
503
|
deploymentInfo: config.deploymentInfo,
|
|
441
504
|
branding: config.branding,
|
package/src/meta-tools.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type ActivityCallSource,
|
|
7
7
|
type ActivityRequestContext,
|
|
8
8
|
} from "./activity.js";
|
|
9
|
+
import { closeConnectorScope } from "./connector-scope.js";
|
|
9
10
|
import { unwrapMcpResult } from "./mcp-result.js";
|
|
10
11
|
import {
|
|
11
12
|
classifyCallError,
|
|
@@ -458,9 +459,9 @@ export interface SkillArgs {
|
|
|
458
459
|
* optional tenth tool, is registered separately by registerExecuteTool.)
|
|
459
460
|
*
|
|
460
461
|
* The deployment-wide result-size cap is read off the registry view rather than
|
|
461
|
-
* passed in: `ConnectaConfig.maxResultBytes` and the per-connector
|
|
462
|
-
* the only places a cap is set, so there is one answer to where a
|
|
463
|
-
* sets it (issue #44).
|
|
462
|
+
* passed in: `ConnectaConfig.calls.maxResultBytes` and the per-connector
|
|
463
|
+
* override are the only places a cap is set, so there is one answer to where a
|
|
464
|
+
* deployment sets it (issue #44).
|
|
464
465
|
*/
|
|
465
466
|
export function createMetaTools(
|
|
466
467
|
registry: RegistryView,
|
|
@@ -788,8 +789,12 @@ export function createMetaTools(
|
|
|
788
789
|
|
|
789
790
|
async listConnectors(args: ListArgs = {}): Promise<ToolResult> {
|
|
790
791
|
const probe = args.probe ?? true;
|
|
792
|
+
// Live inventory owns a short-lived scope separate from the request's
|
|
793
|
+
// call scope. Closing it cannot defeat call_tool/batch/execute_code reuse.
|
|
794
|
+
const connectors = registry.listConnectors();
|
|
795
|
+
const scope = probe ? {} : requestScope;
|
|
791
796
|
const out = await Promise.all(
|
|
792
|
-
|
|
797
|
+
connectors.map(async (c) => {
|
|
793
798
|
const statusStarted = Date.now();
|
|
794
799
|
const observed = registry.healthFor(c.id);
|
|
795
800
|
const verdict = await registry.credentialHealthFor(c.id);
|
|
@@ -799,7 +804,7 @@ export function createMetaTools(
|
|
|
799
804
|
if (probe) {
|
|
800
805
|
try {
|
|
801
806
|
status = await withTimeout(
|
|
802
|
-
registry.statusFor(c.id, baseUrl,
|
|
807
|
+
registry.statusFor(c.id, baseUrl, scope),
|
|
803
808
|
probeTimeoutMs,
|
|
804
809
|
`list_connectors probe of "${c.id}"`,
|
|
805
810
|
);
|
|
@@ -886,7 +891,7 @@ export function createMetaTools(
|
|
|
886
891
|
if (probe && status.state === "ok") {
|
|
887
892
|
try {
|
|
888
893
|
tools = await withTimeout(
|
|
889
|
-
registry.refreshTools(c.id, baseUrl,
|
|
894
|
+
registry.refreshTools(c.id, baseUrl, scope),
|
|
890
895
|
probeTimeoutMs,
|
|
891
896
|
`list_connectors catalog refresh of "${c.id}"`,
|
|
892
897
|
);
|
|
@@ -918,7 +923,17 @@ export function createMetaTools(
|
|
|
918
923
|
...(status.message ? { message: status.message } : {}),
|
|
919
924
|
};
|
|
920
925
|
}),
|
|
921
|
-
)
|
|
926
|
+
).finally(async () => {
|
|
927
|
+
if (!probe) return;
|
|
928
|
+
await Promise.all(
|
|
929
|
+
connectors.map((connector) =>
|
|
930
|
+
closeConnectorScope(
|
|
931
|
+
connector,
|
|
932
|
+
registry.contextFor(connector.id, baseUrl, scope),
|
|
933
|
+
),
|
|
934
|
+
),
|
|
935
|
+
);
|
|
936
|
+
});
|
|
922
937
|
return jsonResult({ connectors: out });
|
|
923
938
|
},
|
|
924
939
|
|
package/src/registry.ts
CHANGED
|
@@ -33,7 +33,7 @@ export const MIN_MAX_RESULT_BYTES = 1;
|
|
|
33
33
|
/**
|
|
34
34
|
* The one definition of a usable `maxResultBytes`: a finite whole number of at
|
|
35
35
|
* least {@link MIN_MAX_RESULT_BYTES} bytes. Shared by all three intake points
|
|
36
|
-
* —
|
|
36
|
+
* — `calls.maxResultBytes`, the per-connector override, and `get_result`'s
|
|
37
37
|
* `maxBytes` argument — so a value that is valid at one is valid at all.
|
|
38
38
|
*
|
|
39
39
|
* Everything else is rejected rather than coerced, because each rejected shape
|
|
@@ -286,7 +286,7 @@ export class Registry implements RegistryView {
|
|
|
286
286
|
): void {
|
|
287
287
|
if (configured !== undefined && !isValidMaxResultBytes(configured)) {
|
|
288
288
|
logger.warn(
|
|
289
|
-
`[connecta] maxResultBytes ${configured} is not a whole number of ` +
|
|
289
|
+
`[connecta] calls.maxResultBytes ${configured} is not a whole number of ` +
|
|
290
290
|
`bytes >= ${MIN_MAX_RESULT_BYTES}: it would serve an empty, ` +
|
|
291
291
|
"oversized, or unguarded result instead of truncating. Using the " +
|
|
292
292
|
`default ${DEFAULT_MAX_RESULT_BYTES} instead.`,
|
|
@@ -612,7 +612,8 @@ export class Registry implements RegistryView {
|
|
|
612
612
|
|
|
613
613
|
/**
|
|
614
614
|
* Drop a connector's liveness verdict, because its credential just changed
|
|
615
|
-
* under us (OAuth callback completed, credential stored or removed
|
|
615
|
+
* under us (OAuth callback completed, credential stored or removed on
|
|
616
|
+
* /credentials). A
|
|
616
617
|
* stale `auth_required` must not outlive the re-authorization that fixed it —
|
|
617
618
|
* that is the difference between recovery working and needing a restart.
|
|
618
619
|
*/
|