@zackbart/connecta 0.22.3 → 0.24.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.
Files changed (94) hide show
  1. package/AGENTS.md +5 -0
  2. package/CHANGELOG.md +82 -0
  3. package/README.md +19 -11
  4. package/dist/activity-friction.d.ts +3 -0
  5. package/dist/activity-friction.js +19 -0
  6. package/dist/activity.d.ts +11 -2
  7. package/dist/activity.js +15 -19
  8. package/dist/auth/downstream-oauth.d.ts +2 -1
  9. package/dist/auth/downstream-oauth.js +10 -1
  10. package/dist/branding.d.ts +67 -0
  11. package/dist/branding.js +176 -0
  12. package/dist/catalog-service.d.ts +1 -6
  13. package/dist/catalog-service.js +3 -53
  14. package/dist/connectors/remote-mcp.js +3 -5
  15. package/dist/credential-contract.d.ts +24 -0
  16. package/dist/credential-contract.js +1 -0
  17. package/dist/credential-rules.d.ts +85 -0
  18. package/dist/credential-rules.js +107 -0
  19. package/dist/credentials.d.ts +4 -100
  20. package/dist/credentials.js +3 -107
  21. package/dist/errors.d.ts +1 -1
  22. package/dist/execute.d.ts +5 -52
  23. package/dist/execute.js +40 -347
  24. package/dist/executors/quickjs-protocol.d.ts +0 -7
  25. package/dist/executors/quickjs-protocol.js +2 -10
  26. package/dist/executors/quickjs.js +1 -1
  27. package/dist/index.d.ts +23 -56
  28. package/dist/index.js +30 -58
  29. package/dist/invocation.d.ts +0 -33
  30. package/dist/invocation.js +56 -124
  31. package/dist/meta-tools.d.ts +7 -6
  32. package/dist/meta-tools.js +14 -21
  33. package/dist/module-contracts.d.ts +19 -0
  34. package/dist/module-contracts.js +1 -0
  35. package/dist/operator-ui/generated.js +2 -2
  36. package/dist/operator-ui/model.d.ts +6 -3
  37. package/dist/operator-ui/view.d.ts +2 -18
  38. package/dist/operator-ui/view.js +3 -20
  39. package/dist/registry.d.ts +4 -1
  40. package/dist/registry.js +8 -6
  41. package/dist/routes/activity.js +1 -1
  42. package/dist/routes/credentials.js +5 -2
  43. package/dist/routes/mcp.js +7 -51
  44. package/dist/routes/oauth-management.d.ts +2 -0
  45. package/dist/routes/oauth-management.js +108 -0
  46. package/dist/routes/oauth.d.ts +0 -1
  47. package/dist/routes/oauth.js +21 -121
  48. package/dist/routes/shared.d.ts +19 -17
  49. package/dist/routes/shared.js +48 -44
  50. package/dist/routes/ui.js +36 -33
  51. package/dist/server.d.ts +1 -2
  52. package/dist/server.js +7 -45
  53. package/dist/skills.d.ts +1 -1
  54. package/dist/skills.js +55 -19
  55. package/dist/types.d.ts +3 -15
  56. package/dist/ui.d.ts +15 -70
  57. package/dist/ui.js +176 -317
  58. package/dist/version.d.ts +1 -1
  59. package/dist/version.js +1 -1
  60. package/documentation/architecture.md +31 -26
  61. package/documentation/auth.md +65 -111
  62. package/documentation/call-admission.md +10 -11
  63. package/documentation/cloudflare.md +1 -1
  64. package/documentation/code-mode.md +65 -260
  65. package/documentation/connectors.md +8 -6
  66. package/documentation/linear.md +1 -1
  67. package/documentation/meta-tools.md +37 -18
  68. package/documentation/mixpanel.md +1 -1
  69. package/documentation/notion.md +2 -2
  70. package/documentation/operations.md +19 -23
  71. package/documentation/operator-ui.md +82 -104
  72. package/documentation/optional-modules-upgrade.md +243 -0
  73. package/documentation/provider-conventions.md +7 -5
  74. package/documentation/revenuecat.md +1 -1
  75. package/documentation/storage-and-credentials.md +59 -40
  76. package/documentation/stripe.md +1 -1
  77. package/documentation/upgrading.md +83 -8
  78. package/ethos.md +36 -43
  79. package/examples/worker/AGENTS.md +3 -1
  80. package/examples/worker/README.md +68 -84
  81. package/examples/worker/src/d1-activity.ts +1 -1
  82. package/examples/worker/src/index.ts +11 -6
  83. package/package.json +18 -2
  84. package/templates/node/AGENTS.md +8 -6
  85. package/templates/node/README.md +56 -67
  86. package/templates/node/package.json +1 -1
  87. package/templates/node/src/file-activity.ts +1 -1
  88. package/templates/node/src/index.ts +11 -12
  89. package/dist/access-tokens.d.ts +0 -31
  90. package/dist/access-tokens.js +0 -236
  91. package/dist/apps-shell.d.ts +0 -37
  92. package/dist/apps-shell.js +0 -174
  93. package/dist/routes/access-tokens.d.ts +0 -6
  94. package/dist/routes/access-tokens.js +0 -83
@@ -1,10 +1,9 @@
1
- import { recordToolActivity, } from "./activity.js";
2
1
  import { isCallAdmissionError } from "./call-admission.js";
3
2
  import { classifyCallError, ConnectorCallError, echoedCallArgs, framingError, } from "./errors.js";
4
3
  import { unwrapMcpResult } from "./mcp-result.js";
5
4
  import { splitAddress } from "./registry.js";
6
5
  import { isExplicitlyReadOnly } from "./tool-safety.js";
7
- import { sleep, withDeadline } from "./timeout.js";
6
+ import { withDeadline } from "./timeout.js";
8
7
  import { validateToolInput } from "./validate.js";
9
8
  function defined(values) {
10
9
  return Object.fromEntries(Object.entries(values).filter(([, value]) => value !== undefined));
@@ -18,40 +17,6 @@ async function timed(bucket, fn) {
18
17
  bucket(Date.now() - started);
19
18
  }
20
19
  }
21
- /**
22
- * The longest the engine will park a synchronous inbound request in *waiting
23
- * alone*. The engine already treats ~15 s as the outer bound of one reasonable
24
- * connector call (EXECUTE_HOST_CALL_TIMEOUT_MS), so sleeping for minutes trades
25
- * a fast, informative failure for a hung one. A connector-reported window this
26
- * long isn't truncated — it's declined (see `retryBackoffMs`) and reported
27
- * verbatim as `error.retryAfterMs`, so the agent, which can afford to wait,
28
- * decides when to re-issue.
29
- */
30
- export const MAX_RETRY_BACKOFF_MS = 10_000;
31
- /**
32
- * How long to wait before the next attempt, or `undefined` for "don't retry".
33
- *
34
- * A connector that read a `Retry-After` header knows the window exactly, so it
35
- * is honoured **exactly or not at all**: truncating an exponential *guess* is
36
- * harmless, but truncating a *known* window means deliberately retrying inside
37
- * a rate limit — the harm this channel exists to prevent. A window longer than
38
- * `MAX_RETRY_BACKOFF_MS` therefore declines the retry rather than shortening
39
- * it. (`retryAfterMs` is normalized non-negative, so `0` means "retry now".)
40
- * Connectors that report no window keep the historical exponential guess.
41
- *
42
- * Waits are per attempt, matching the per-attempt `timeoutMs` race in
43
- * `InvocationService.invoke`. Exported for direct testing.
44
- */
45
- export function retryBackoffMs(attempt, retryAfterMs) {
46
- if (retryAfterMs === undefined) {
47
- return Math.min(250 * 2 ** (attempt - 1), 1_000);
48
- }
49
- return retryAfterMs <= MAX_RETRY_BACKOFF_MS ? retryAfterMs : undefined;
50
- }
51
- function retrySafe(definition) {
52
- return (definition.annotations?.readOnlyHint === true ||
53
- definition.annotations?.idempotentHint === true);
54
- }
55
20
  function callerCancelledDetails() {
56
21
  return {
57
22
  code: "cancelled",
@@ -62,7 +27,7 @@ function callerCancelledDetails() {
62
27
  function recoveryMode(registry, connector, baseUrl) {
63
28
  if (connector.startAuth)
64
29
  return "oauth";
65
- if (connector.credential &&
30
+ if (registry.credentialUiAvailable() && connector.credential &&
66
31
  registry.contextFor(connector.id, baseUrl).credential) {
67
32
  return "operator_config";
68
33
  }
@@ -122,23 +87,10 @@ export class InvocationService {
122
87
  this.activity = activity;
123
88
  }
124
89
  async invoke(address, args, context) {
125
- const options = defined({ signal: context.requestSignal });
126
- return this.invokeWithResolution(address, args, context, () => this.catalog.resolveTool(address, options));
127
- }
128
- /**
129
- * Code-mode namespace dispatch preserves JavaScript-safe tool aliases while
130
- * still feeding the resolved catalog entry through the one invocation path.
131
- */
132
- async invokeToolAlias(connectorId, toolAlias, aliasFor, args, context) {
133
- const options = defined({ signal: context.requestSignal });
134
- return this.invokeWithResolution(`${connectorId}.${toolAlias}`, args, context, () => this.catalog.resolveToolAlias(connectorId, toolAlias, aliasFor, options));
135
- }
136
- async invokeWithResolution(address, args, context, resolve) {
137
90
  const started = Date.now();
138
91
  let catalogMs = 0;
139
92
  let admissionMs = 0;
140
93
  let connectorMs = 0;
141
- let backoffMs = 0;
142
94
  let resultProcessingMs = 0;
143
95
  let attempts = 0;
144
96
  let resolved;
@@ -151,7 +103,6 @@ export class InvocationService {
151
103
  catalogMs,
152
104
  admissionMs,
153
105
  connectorMs,
154
- backoffMs,
155
106
  resultProcessingMs,
156
107
  totalMs: Date.now() - started,
157
108
  });
@@ -164,7 +115,7 @@ export class InvocationService {
164
115
  : attempted;
165
116
  if (!identity)
166
117
  return;
167
- recordToolActivity(this.activity, {
118
+ this.activity?.recordTool?.(this.activity, {
168
119
  connectorId: identity.connectorId,
169
120
  toolName: identity.toolName,
170
121
  address: `${identity.connectorId}.${identity.toolName}`,
@@ -248,7 +199,7 @@ export class InvocationService {
248
199
  error: details,
249
200
  };
250
201
  };
251
- const resolution = await resolve();
202
+ const resolution = await this.catalog.resolveTool(address, defined({ signal: context.requestSignal }));
252
203
  catalogMs += resolution.catalogMs;
253
204
  if (!resolution.ok) {
254
205
  if (resolution.connector && resolution.toolName) {
@@ -290,80 +241,61 @@ export class InvocationService {
290
241
  ? error.details
291
242
  : classifyCallError(error));
292
243
  }
293
- const maxRetries = Math.min(2, Math.max(0, Math.trunc(context.maxRetries ?? 0)));
294
244
  let result;
295
245
  let observedResult;
296
- while (true) {
297
- attempts++;
298
- let permit;
299
- let attemptError;
300
- let attemptFailed = false;
301
- try {
302
- permit = await timed((elapsed) => { admissionMs += elapsed; }, () => this.registry.admitCall(resolved.connector.id, {
303
- toolName: resolved.toolName,
304
- args: args ?? {},
305
- ...defined({ signal: context.requestSignal }),
306
- }));
307
- const raw = await timed((elapsed) => { connectorMs += elapsed; }, () => {
308
- const call = (callSignal) => {
309
- const connectorContext = this.registry.contextFor(resolved.connector.id, this.catalog.baseUrl, this.catalog.requestScope, defined({ signal: callSignal, timeoutMs: context.timeoutMs }));
310
- if (resolved.connector.credential &&
311
- !connectorContext.credential) {
312
- throw new ConnectorCallError("auth_required", "Operator-managed credential storage is not configured. Call " +
313
- `authorize_connector({ connector: "${resolved.connector.id}" }).`);
314
- }
315
- // Cancellation can arrive during admission or context construction.
316
- if (callSignal?.aborted)
317
- throw callSignal.reason;
318
- return resolved.connector.callTool(resolved.toolName, args ?? {}, connectorContext);
319
- };
320
- if (!context.timeoutMs && !context.requestSignal)
321
- return call();
322
- return withDeadline(call, {
323
- ...defined({
324
- timeoutMs: context.timeoutMs,
325
- signal: context.requestSignal,
326
- }),
327
- timeoutError: new ConnectorCallError("timeout", `Tool call timed out after ${context.timeoutMs}ms`),
328
- });
329
- });
330
- // isError is checked here for BOTH result shapes so every adapter
331
- // reports the same downstream-failure wording, and the throw lands
332
- // inside the attempt where it stays retry-eligible and feeds health.
333
- assertRawMcpSuccess(resolved.connector.kind, raw);
334
- observedResult = unwrapMcpResult(resolved.connector.kind, raw);
335
- result = context.unwrapResult ? observedResult : raw;
336
- }
337
- catch (error) {
338
- attemptFailed = true;
339
- attemptError = error;
340
- }
341
- finally {
342
- permit?.release();
343
- }
344
- if (attemptFailed) {
345
- const callerCancelled = isCallerCancellation(attemptError, context.requestSignal);
346
- const details = callerCancelled
347
- ? callerCancelledDetails()
348
- : classifyCallError(attemptError);
349
- if (!callerCancelled &&
350
- attempts <= maxRetries &&
351
- retrySafe(resolved.definition) &&
352
- details.retryable) {
353
- const wait = retryBackoffMs(attempts, details.retryAfterMs);
354
- if (wait !== undefined) {
355
- const completed = await timed((elapsed) => { backoffMs += elapsed; }, () => sleep(wait, context.requestSignal));
356
- if (!completed)
357
- return failed(callerCancelledDetails());
358
- continue;
246
+ attempts = 1;
247
+ let permit;
248
+ let attemptError;
249
+ let attemptFailed = false;
250
+ try {
251
+ permit = await timed((elapsed) => { admissionMs += elapsed; }, () => this.registry.admitCall(resolved.connector.id, {
252
+ toolName: resolved.toolName,
253
+ args: args ?? {},
254
+ ...defined({ signal: context.requestSignal }),
255
+ }));
256
+ const raw = await timed((elapsed) => { connectorMs += elapsed; }, () => {
257
+ const call = (callSignal) => {
258
+ const connectorContext = this.registry.contextFor(resolved.connector.id, this.catalog.baseUrl, this.catalog.requestScope, defined({ signal: callSignal, timeoutMs: context.timeoutMs }));
259
+ if (resolved.connector.credential &&
260
+ !connectorContext.credential) {
261
+ throw new ConnectorCallError("auth_required", "Operator-managed credential storage is not configured. Call " +
262
+ `authorize_connector({ connector: "${resolved.connector.id}" }).`);
359
263
  }
360
- // The reported window is longer than the engine will park a
361
- // synchronous request for. Fall through to failure with
362
- // retryAfterMs reported verbatim so the agent can re-issue.
363
- }
364
- return failed(details);
365
- }
366
- break;
264
+ // Cancellation can arrive during admission or context construction.
265
+ if (callSignal?.aborted)
266
+ throw callSignal.reason;
267
+ return resolved.connector.callTool(resolved.toolName, args ?? {}, connectorContext);
268
+ };
269
+ if (!context.timeoutMs && !context.requestSignal)
270
+ return call();
271
+ return withDeadline(call, {
272
+ ...defined({
273
+ timeoutMs: context.timeoutMs,
274
+ signal: context.requestSignal,
275
+ }),
276
+ timeoutError: new ConnectorCallError("timeout", `Tool call timed out after ${context.timeoutMs}ms`),
277
+ });
278
+ });
279
+ // isError is checked here for BOTH result shapes so every adapter
280
+ // reports the same downstream-failure wording, and the throw lands
281
+ // inside the attempt where it feeds health.
282
+ assertRawMcpSuccess(resolved.connector.kind, raw);
283
+ observedResult = unwrapMcpResult(resolved.connector.kind, raw);
284
+ result = context.unwrapResult ? observedResult : raw;
285
+ }
286
+ catch (error) {
287
+ attemptFailed = true;
288
+ attemptError = error;
289
+ }
290
+ finally {
291
+ permit?.release();
292
+ }
293
+ if (attemptFailed) {
294
+ const callerCancelled = isCallerCancellation(attemptError, context.requestSignal);
295
+ const details = callerCancelled
296
+ ? callerCancelledDetails()
297
+ : classifyCallError(attemptError);
298
+ return failed(details);
367
299
  }
368
300
  try {
369
301
  const value = await timed((elapsed) => { resultProcessingMs += elapsed; }, async () => {
@@ -2,9 +2,8 @@ import type { McpServer } from "@modelcontextprotocol/server";
2
2
  import type { ActivityRequestContext } from "./activity.js";
3
3
  import { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_SEARCH_LIMIT } from "./catalog-service.js";
4
4
  import type { DeferredWork } from "./connector-scope.js";
5
- import { MAX_RETRY_BACKOFF_MS, retryBackoffMs } from "./invocation.js";
6
5
  import { type RegistryView } from "./registry.js";
7
- export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_RETRY_BACKOFF_MS, MAX_SEARCH_LIMIT, retryBackoffMs, };
6
+ export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_SEARCH_LIMIT, };
8
7
  interface TextContent {
9
8
  type: "text";
10
9
  text: string;
@@ -48,8 +47,6 @@ export interface CallArgs {
48
47
  args?: Record<string, unknown>;
49
48
  resultMode?: ResultMode;
50
49
  timeoutMs?: number;
51
- /** Retries after the first attempt; honored only for safely annotated tools. */
52
- maxRetries?: number;
53
50
  /** Include connector/catalog/result-processing timing segments. */
54
51
  diagnostics?: boolean;
55
52
  }
@@ -97,6 +94,8 @@ export declare function createMetaTools(registry: RegistryView, baseUrl: string,
97
94
  /** Maximum simultaneous connector discovery operations. Default 4. */
98
95
  discoveryConcurrency?: number | undefined;
99
96
  activity?: ActivityRequestContext | undefined;
97
+ canManageAuth?: ((id: string) => boolean) | undefined;
98
+ credentialHandoffUrl?: string | undefined;
100
99
  /** Inbound request cancellation shared by every call this request makes. */
101
100
  requestSignal?: AbortSignal | undefined;
102
101
  /** Runtime-owned tail for stale catalog refreshes. */
@@ -112,8 +111,8 @@ export declare function createMetaTools(registry: RegistryView, baseUrl: string,
112
111
  /**
113
112
  * Register the six explicit meta-tools onto an McpServer instance.
114
113
  * `registerExecuteTool` adds the seventh, `execute_code`. Broad discovery and
115
- * multi-call work is reached through `connecta.search` / `connecta.describe` /
116
- * `connecta.batch` inside a program, which `execute_code` builds over the same
114
+ * multi-call work uses discovery and ordinary JavaScript promises inside a
115
+ * program, which `execute_code` builds over the same
117
116
  * `CatalogService` and `InvocationService` these handlers use — one shared
118
117
  * services layer, two adapters above it.
119
118
  */
@@ -123,6 +122,8 @@ export declare function registerMetaTools(server: McpServer, registry: RegistryV
123
122
  probeTimeoutMs?: number | undefined;
124
123
  discoveryConcurrency?: number | undefined;
125
124
  activity?: ActivityRequestContext | undefined;
125
+ canManageAuth?: ((id: string) => boolean) | undefined;
126
+ credentialHandoffUrl?: string | undefined;
126
127
  requestSignal?: AbortSignal | undefined;
127
128
  defer?: DeferredWork | undefined;
128
129
  }): void;
@@ -3,11 +3,11 @@ import { boundedDiscoveryText, CatalogService, DEFAULT_SEARCH_LIMIT, DiscoveryPo
3
3
  import { resolveDiscoveryConcurrency } from "./concurrency.js";
4
4
  import { msg } from "./errors.js";
5
5
  import { serializeResultText } from "./executor-result.js";
6
- import { InvocationService, MAX_RETRY_BACKOFF_MS, retryBackoffMs, } from "./invocation.js";
6
+ import { InvocationService, } from "./invocation.js";
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, } from "./timeout.js";
10
- export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_RETRY_BACKOFF_MS, MAX_SEARCH_LIMIT, retryBackoffMs, };
10
+ export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_SEARCH_LIMIT, };
11
11
  const RESULT_TTL_SECONDS = 900;
12
12
  const enc = new TextEncoder();
13
13
  const dec = new TextDecoder();
@@ -267,9 +267,6 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
267
267
  ? { allowDestructive: options.allowDestructive }
268
268
  : {}),
269
269
  ...(timeoutMs !== undefined ? { timeoutMs } : {}),
270
- ...(call.maxRetries !== undefined
271
- ? { maxRetries: call.maxRetries }
272
- : {}),
273
270
  ...(opts.requestSignal !== undefined
274
271
  ? { requestSignal: opts.requestSignal }
275
272
  : {}),
@@ -451,12 +448,12 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
451
448
  });
452
449
  }
453
450
  const ctx = registry.contextFor(connector.id, baseUrl, requestScope);
454
- if (!ctx.credential) {
451
+ if (!ctx.credential || !opts.credentialHandoffUrl) {
455
452
  return jsonResult({
456
453
  connector: connector.id,
457
454
  recovery: "unavailable",
458
- message: "Credential storage is not configured. Configure " +
459
- "credentials.encryptionKey, redeploy, then call " +
455
+ message: "Credential recovery needs both a vault and the optional UI. Configure " +
456
+ "vault and ui in deployment code, then call " +
460
457
  "authorize_connector again.",
461
458
  });
462
459
  }
@@ -477,7 +474,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
477
474
  label: connector.credential.label,
478
475
  fields,
479
476
  },
480
- operatorUrl: new URL("/credentials", baseUrl).toString(),
477
+ operatorUrl: opts.credentialHandoffUrl,
481
478
  instructions: "Have the operator open operatorUrl, set and test the credential, " +
482
479
  "then retry the original call. No redeploy is needed. " +
483
480
  (connector.authScope === "personal"
@@ -485,6 +482,8 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
485
482
  : "Shared credential mutation requires a signed-in human with access to this connector."),
486
483
  });
487
484
  }
485
+ if (!opts.canManageAuth?.(connector.id))
486
+ return jsonResult({ connector: connector.id, recovery: "unavailable", message: "Your identity is not permitted to manage authentication for this connection." });
488
487
  const ctx = registry.contextFor(connector.id, baseUrl, requestScope);
489
488
  try {
490
489
  const status = await connector.startAuth(ctx, args.force !== undefined ? { force: args.force } : {});
@@ -568,14 +567,13 @@ const CALL_INPUT_SCHEMA = {
568
567
  args: z.record(z.string(), z.unknown()).optional(),
569
568
  resultMode: z.enum(["mcp", "value"]).optional(),
570
569
  timeoutMs: z.number().int().positive().optional(),
571
- maxRetries: z.number().int().min(0).max(2).optional(),
572
570
  diagnostics: z.boolean().optional(),
573
571
  };
574
572
  /**
575
573
  * Register the six explicit meta-tools onto an McpServer instance.
576
574
  * `registerExecuteTool` adds the seventh, `execute_code`. Broad discovery and
577
- * multi-call work is reached through `connecta.search` / `connecta.describe` /
578
- * `connecta.batch` inside a program, which `execute_code` builds over the same
575
+ * multi-call work uses discovery and ordinary JavaScript promises inside a
576
+ * program, which `execute_code` builds over the same
579
577
  * `CatalogService` and `InvocationService` these handlers use — one shared
580
578
  * services layer, two adapters above it.
581
579
  */
@@ -585,6 +583,8 @@ export function registerMetaTools(server, registry, ctx) {
585
583
  probeTimeoutMs: ctx.probeTimeoutMs,
586
584
  discoveryConcurrency: ctx.discoveryConcurrency,
587
585
  activity: ctx.activity,
586
+ canManageAuth: ctx.canManageAuth,
587
+ credentialHandoffUrl: ctx.credentialHandoffUrl,
588
588
  requestSignal: ctx.requestSignal,
589
589
  defer: ctx.defer,
590
590
  });
@@ -592,7 +592,6 @@ export function registerMetaTools(server, registry, ctx) {
592
592
  description: describedFor(registry, SKILLS_DESC, "skills"),
593
593
  inputSchema: z.object({ name: z.string().optional() }),
594
594
  annotations: READ_ONLY_LOCAL,
595
- _meta: { ui: { visibility: ["model"] } },
596
595
  }, async (args) => mt.skills(args));
597
596
  server.registerTool("search_tools", {
598
597
  description: describedFor(registry, SEARCH_DESC, "search"),
@@ -608,20 +607,17 @@ export function registerMetaTools(server, registry, ctx) {
608
607
  includeSchemas: z.enum(["compact", "json"]).optional(),
609
608
  }),
610
609
  annotations: READ_ONLY_REMOTE,
611
- _meta: { ui: { visibility: ["model"] } },
612
610
  }, async (args) => mt.searchTools(args));
613
611
  server.registerTool("call_tool", {
614
612
  description: CALL_DESC,
615
- inputSchema: z.object(CALL_INPUT_SCHEMA),
613
+ inputSchema: z.strictObject(CALL_INPUT_SCHEMA),
616
614
  // call_tool admits only tools that are themselves explicitly read-only;
617
615
  // anything else is refused and routed to call_destructive_tool.
618
616
  annotations: READ_ONLY_REMOTE,
619
- // Omission defaults to model + app. Display-only views may call no tool.
620
- _meta: { ui: { visibility: ["model"] } },
621
617
  }, async (args) => mt.callTool(args));
622
618
  server.registerTool("call_destructive_tool", {
623
619
  description: describedFor(registry, CALL_DESTRUCTIVE_DESC, "destructive"),
624
- inputSchema: z.object({
620
+ inputSchema: z.strictObject({
625
621
  ...CALL_INPUT_SCHEMA,
626
622
  // Bounded above, but with no lower bound: a model that sends `""` or
627
623
  // whitespace has written no reason, and failing an entire consequential
@@ -634,7 +630,6 @@ export function registerMetaTools(server, registry, ctx) {
634
630
  readOnlyHint: false,
635
631
  openWorldHint: true,
636
632
  },
637
- _meta: { ui: { visibility: ["model"] } },
638
633
  }, async (args) => {
639
634
  // `reason` is the host's to display and connecta's to keep out of the
640
635
  // downstream call, so this destructuring is the whole of its handling:
@@ -657,7 +652,6 @@ export function registerMetaTools(server, registry, ctx) {
657
652
  destructiveHint: false,
658
653
  openWorldHint: true,
659
654
  },
660
- _meta: { ui: { visibility: ["model"] } },
661
655
  }, async (args) => mt.authorizeConnector(args));
662
656
  server.registerTool("get_result", {
663
657
  description: GET_RESULT_DESC,
@@ -671,6 +665,5 @@ export function registerMetaTools(server, registry, ctx) {
671
665
  maxBytes: z.number().int().min(MIN_MAX_RESULT_BYTES).optional(),
672
666
  }),
673
667
  annotations: READ_ONLY_LOCAL,
674
- _meta: { ui: { visibility: ["model"] } },
675
668
  }, async (args) => mt.getResult(args));
676
669
  }
@@ -0,0 +1,19 @@
1
+ import type { ConnectaBranding } from "./types.js";
2
+ import type { ActivityStore, ActivityReadGate, recordToolActivity, recordCatalogDriftActivity } from "./activity.js";
3
+ import type { RouteContext } from "./routes/shared.js";
4
+ /** Construction-time UI contract. Core never imports the implementation. */
5
+ export interface OperatorSurface {
6
+ readonly branding?: ConnectaBranding;
7
+ readonly reservedPaths: readonly string[];
8
+ credentialHandoffUrl(baseUrl: string): string;
9
+ handle(context: RouteContext): Promise<Response | null>;
10
+ }
11
+ /** Optional recording and reading callbacks supplied by /activity. */
12
+ export interface ActivityModule {
13
+ readonly store: ActivityStore;
14
+ readonly deploymentId?: string;
15
+ readonly readGate?: ActivityReadGate;
16
+ handle(context: RouteContext): Promise<Response | null>;
17
+ readonly recordTool: typeof recordToolActivity;
18
+ readonly recordDrift: typeof recordCatalogDriftActivity;
19
+ }
@@ -0,0 +1 @@
1
+ export {};