@zackbart/connecta 0.7.1 → 0.7.3

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 (54) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +2 -1
  3. package/dist/connectors/remote-mcp.d.ts.map +1 -1
  4. package/dist/connectors/remote-mcp.js +117 -79
  5. package/dist/connectors/remote-mcp.js.map +1 -1
  6. package/dist/credential-health.d.ts +8 -5
  7. package/dist/credential-health.d.ts.map +1 -1
  8. package/dist/credential-health.js +20 -13
  9. package/dist/credential-health.js.map +1 -1
  10. package/dist/executors/quickjs.d.ts.map +1 -1
  11. package/dist/executors/quickjs.js +57 -5
  12. package/dist/executors/quickjs.js.map +1 -1
  13. package/dist/index.d.ts +2 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js.map +1 -1
  16. package/dist/meta-tools.d.ts.map +1 -1
  17. package/dist/meta-tools.js +47 -11
  18. package/dist/meta-tools.js.map +1 -1
  19. package/dist/registry.d.ts +18 -22
  20. package/dist/registry.d.ts.map +1 -1
  21. package/dist/registry.js +33 -21
  22. package/dist/registry.js.map +1 -1
  23. package/dist/server.d.ts.map +1 -1
  24. package/dist/server.js +9 -6
  25. package/dist/server.js.map +1 -1
  26. package/dist/timeout.d.ts +9 -4
  27. package/dist/timeout.d.ts.map +1 -1
  28. package/dist/timeout.js +34 -4
  29. package/dist/timeout.js.map +1 -1
  30. package/dist/toolkits.d.ts +8 -0
  31. package/dist/toolkits.d.ts.map +1 -1
  32. package/dist/toolkits.js +3 -0
  33. package/dist/toolkits.js.map +1 -1
  34. package/dist/types.d.ts +2 -2
  35. package/dist/types.d.ts.map +1 -1
  36. package/dist/ui.d.ts +12 -1
  37. package/dist/ui.d.ts.map +1 -1
  38. package/dist/ui.js +187 -6
  39. package/dist/ui.js.map +1 -1
  40. package/dist/version.d.ts +1 -1
  41. package/dist/version.js +1 -1
  42. package/package.json +1 -1
  43. package/src/connectors/remote-mcp.ts +128 -83
  44. package/src/credential-health.ts +20 -18
  45. package/src/executors/quickjs.ts +65 -5
  46. package/src/index.ts +2 -1
  47. package/src/meta-tools.ts +75 -26
  48. package/src/registry.ts +48 -20
  49. package/src/server.ts +11 -8
  50. package/src/timeout.ts +41 -4
  51. package/src/toolkits.ts +11 -0
  52. package/src/types.ts +2 -2
  53. package/src/ui.ts +212 -11
  54. package/src/version.ts +1 -1
package/src/meta-tools.ts CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  import {
31
31
  DEFAULT_PROBE_TIMEOUT_MS,
32
32
  normalizeTimeoutMs,
33
- withTimeout,
33
+ withAbortableTimeout,
34
34
  } from "./timeout.js";
35
35
  import { credentialVerdictApplies } from "./credential-health.js";
36
36
  import type { ConnectorStatus, KVStorage, ToolDef } from "./types.js";
@@ -597,6 +597,18 @@ export function createMetaTools(
597
597
  // identity lets remote connectors reuse one downstream client inside that
598
598
  // request without leaking request-bound I/O into the next one.
599
599
  const requestScope = {};
600
+ const withProbeDeadline = <T>(
601
+ label: string,
602
+ operation: (options: {
603
+ signal: AbortSignal;
604
+ timeoutMs: number;
605
+ }) => Promise<T>,
606
+ ) =>
607
+ withAbortableTimeout(
608
+ (signal) => operation({ signal, timeoutMs: probeTimeoutMs }),
609
+ probeTimeoutMs,
610
+ label,
611
+ );
600
612
 
601
613
  interface RunCallOutcome {
602
614
  toolResult: ToolResult;
@@ -907,8 +919,8 @@ export function createMetaTools(
907
919
  // call scope. Closing it cannot defeat call_tool/batch/execute_code reuse.
908
920
  const connectors = registry.listConnectors();
909
921
  const scope = probe ? {} : requestScope;
910
- const out = await Promise.all(
911
- connectors.map(async (c) => {
922
+ const pending = connectors.map(
923
+ async (c) => {
912
924
  const statusStarted = Date.now();
913
925
  const observed = registry.healthFor(c.id);
914
926
  const verdict = await registry.credentialHealthFor(c.id);
@@ -917,10 +929,10 @@ export function createMetaTools(
917
929
  | { state: "ok" | "error" | "unknown"; message?: string };
918
930
  if (probe) {
919
931
  try {
920
- status = await withTimeout(
921
- registry.statusFor(c.id, baseUrl, scope),
922
- probeTimeoutMs,
932
+ status = await withProbeDeadline(
923
933
  `list_connectors probe of "${c.id}"`,
934
+ (options) =>
935
+ registry.statusFor(c.id, baseUrl, scope, options),
924
936
  );
925
937
  } catch (err) {
926
938
  // A probe that outran probeTimeoutMs (or otherwise threw)
@@ -1004,14 +1016,45 @@ export function createMetaTools(
1004
1016
  // the first (now stale) authorization URL.
1005
1017
  if (probe && status.state === "ok") {
1006
1018
  try {
1007
- tools = await withTimeout(
1008
- registry.refreshTools(c.id, baseUrl, scope),
1009
- probeTimeoutMs,
1019
+ tools = await withProbeDeadline(
1010
1020
  `list_connectors catalog refresh of "${c.id}"`,
1021
+ (options) =>
1022
+ registry.refreshTools(c.id, baseUrl, scope, options),
1011
1023
  );
1012
1024
  registry.recordSuccess(c.id, Date.now() - statusStarted);
1013
1025
  } catch (err) {
1014
- status = { state: "error" as const, message: msg(err) };
1026
+ const details = classifyCallError(err);
1027
+ if (details.code === "auth_required") {
1028
+ let authStatus: ConnectorStatus | undefined;
1029
+ try {
1030
+ authStatus = await withProbeDeadline(
1031
+ `list_connectors authorization status of "${c.id}"`,
1032
+ (options) =>
1033
+ registry.statusFor(c.id, baseUrl, scope, options),
1034
+ );
1035
+ } catch {
1036
+ // The typed auth verdict is still authoritative; this second
1037
+ // read exists only to recover the connector's pending URL.
1038
+ }
1039
+ status =
1040
+ authStatus?.state === "auth_required"
1041
+ ? authStatus
1042
+ : {
1043
+ state: "auth_required" as const,
1044
+ message: details.message,
1045
+ };
1046
+ await registry.recordCredentialHealth(c.id, {
1047
+ state: "auth_required",
1048
+ checkedAt,
1049
+ ...(status.message ? { message: status.message } : {}),
1050
+ ...("authorizationUrl" in status &&
1051
+ status.authorizationUrl
1052
+ ? { authorizationUrl: status.authorizationUrl }
1053
+ : {}),
1054
+ });
1055
+ } else {
1056
+ status = { state: "error" as const, message: msg(err) };
1057
+ }
1015
1058
  registry.recordFailure(c.id, Date.now() - statusStarted, err);
1016
1059
  }
1017
1060
  }
@@ -1036,17 +1079,23 @@ export function createMetaTools(
1036
1079
  : {}),
1037
1080
  ...(status.message ? { message: status.message } : {}),
1038
1081
  };
1039
- }),
1040
- ).finally(async () => {
1041
- if (!probe) return;
1042
- await Promise.all(
1043
- connectors.map((connector) =>
1044
- closeConnectorScope(
1045
- connector,
1046
- registry.contextFor(connector.id, baseUrl, scope),
1047
- ),
1082
+ },
1083
+ );
1084
+ if (!probe) {
1085
+ return jsonResult({ connectors: await Promise.all(pending) });
1086
+ }
1087
+ const settled = await Promise.allSettled(pending);
1088
+ await Promise.all(
1089
+ connectors.map((connector) =>
1090
+ closeConnectorScope(
1091
+ connector,
1092
+ registry.contextFor(connector.id, baseUrl, scope),
1048
1093
  ),
1049
- );
1094
+ ),
1095
+ );
1096
+ const out = settled.map((result) => {
1097
+ if (result.status === "rejected") throw result.reason;
1098
+ return result.value;
1050
1099
  });
1051
1100
  return jsonResult({ connectors: out });
1052
1101
  },
@@ -1079,10 +1128,10 @@ export function createMetaTools(
1079
1128
  }> = [];
1080
1129
  const catalogs = await Promise.allSettled(
1081
1130
  conns.map((c) =>
1082
- withTimeout(
1083
- registry.getTools(c.id, baseUrl, requestScope),
1084
- probeTimeoutMs,
1131
+ withProbeDeadline(
1085
1132
  `search_tools probe of "${c.id}"`,
1133
+ (options) =>
1134
+ registry.getTools(c.id, baseUrl, requestScope, options),
1086
1135
  ),
1087
1136
  ),
1088
1137
  );
@@ -1208,10 +1257,10 @@ export function createMetaTools(
1208
1257
  ];
1209
1258
  const loaded = await Promise.allSettled(
1210
1259
  connectorIds.map((id) =>
1211
- withTimeout(
1212
- registry.getTools(id, baseUrl, requestScope),
1213
- probeTimeoutMs,
1260
+ withProbeDeadline(
1214
1261
  `describe_tools probe of "${id}"`,
1262
+ (options) =>
1263
+ registry.getTools(id, baseUrl, requestScope, options),
1215
1264
  ),
1216
1265
  ),
1217
1266
  );
package/src/registry.ts CHANGED
@@ -6,7 +6,11 @@ import type {
6
6
  Logger,
7
7
  ToolDef,
8
8
  } from "./types.js";
9
- import type { CredentialVault } from "./credentials.js";
9
+ import {
10
+ storedCredentialShape,
11
+ type CredentialVault,
12
+ } from "./credentials.js";
13
+ import { ConnectorCallError } from "./errors.js";
10
14
  import {
11
15
  CredentialHealthChecker,
12
16
  type CredentialCheckOptions,
@@ -154,6 +158,11 @@ function msg(err: unknown): string {
154
158
  return err instanceof Error ? err.message : String(err);
155
159
  }
156
160
 
161
+ type ConnectorOperationOptions = Pick<
162
+ ConnectorContext,
163
+ "signal" | "timeoutMs"
164
+ >;
165
+
157
166
  /**
158
167
  * The registry surface a per-connection MCP server consumes: every meta-tool
159
168
  * (`src/meta-tools.ts`) and the `execute_code` sandbox bridge (`src/execute.ts`)
@@ -178,18 +187,20 @@ export interface RegistryView {
178
187
  id: string,
179
188
  baseUrl: string,
180
189
  requestScope?: object,
190
+ callOptions?: ConnectorOperationOptions,
181
191
  ): Promise<ToolDef[]>;
182
192
  refreshTools(
183
193
  id: string,
184
194
  baseUrl: string,
185
195
  requestScope?: object,
196
+ callOptions?: ConnectorOperationOptions,
186
197
  ): Promise<ToolDef[]>;
187
198
  peekTools(id: string): ToolDef[] | undefined;
188
199
  contextFor(
189
200
  id: string,
190
201
  baseUrl: string,
191
202
  requestScope?: object,
192
- callOptions?: { signal?: AbortSignal; timeoutMs?: number },
203
+ callOptions?: ConnectorOperationOptions,
193
204
  ): ConnectorContext;
194
205
  resultsStorage(): KVStorage;
195
206
  recordSuccess(id: string, latencyMs: number): void;
@@ -209,6 +220,7 @@ export interface RegistryView {
209
220
  id: string,
210
221
  baseUrl: string,
211
222
  requestScope?: object,
223
+ callOptions?: ConnectorOperationOptions,
212
224
  ): Promise<ConnectorStatus>;
213
225
  invalidateStored(id: string): Promise<void>;
214
226
  }
@@ -352,21 +364,31 @@ export class Registry implements RegistryView {
352
364
  id: string,
353
365
  baseUrl: string,
354
366
  requestScope: object = {},
355
- callOptions: { signal?: AbortSignal; timeoutMs?: number } = {},
367
+ callOptions: ConnectorOperationOptions = {},
356
368
  ): ConnectorContext {
369
+ const credentialConfig = this.connectors.get(id)?.credential;
370
+ let credentialAccess: ConnectorContext["credential"];
371
+ if (this.opts.credentialVault && credentialConfig) {
372
+ const vault = this.opts.credentialVault;
373
+ const readValues = async () => {
374
+ const values = await vault.getAll(id);
375
+ const shape = storedCredentialShape(credentialConfig, values);
376
+ if (shape.state === "mismatch") {
377
+ throw new ConnectorCallError("auth_required", shape.message);
378
+ }
379
+ return values;
380
+ };
381
+ credentialAccess = {
382
+ get: async (field = "value") =>
383
+ (await readValues())?.[field] ?? null,
384
+ getAll: readValues,
385
+ };
386
+ }
357
387
  return {
358
388
  storage: namespaced(this.opts.storage, `conn:${id}:`),
359
389
  logger: this.opts.logger,
360
390
  baseUrl,
361
- ...(this.opts.credentialVault && this.connectors.get(id)?.credential
362
- ? {
363
- credential: {
364
- get: (field?: string) =>
365
- this.opts.credentialVault!.get(id, field),
366
- getAll: () => this.opts.credentialVault!.getAll(id),
367
- },
368
- }
369
- : {}),
391
+ ...(credentialAccess ? { credential: credentialAccess } : {}),
370
392
  requestScope,
371
393
  ...callOptions,
372
394
  };
@@ -441,13 +463,14 @@ export class Registry implements RegistryView {
441
463
  id: string,
442
464
  baseUrl: string,
443
465
  requestScope?: object,
466
+ callOptions: ConnectorOperationOptions = {},
444
467
  ): Promise<ToolDef[]> {
445
468
  const connector = this.connectors.get(id);
446
469
  if (!connector) throw new Error(`Unknown connector "${id}"`);
447
470
  const tools = connector.staticTools
448
471
  ? connector.staticTools
449
472
  : await connector.listTools(
450
- this.contextFor(id, baseUrl, requestScope),
473
+ this.contextFor(id, baseUrl, requestScope, callOptions),
451
474
  );
452
475
  const now = Date.now();
453
476
  const previous = this.cache.get(id);
@@ -479,6 +502,7 @@ export class Registry implements RegistryView {
479
502
  id: string,
480
503
  baseUrl: string,
481
504
  requestScope?: object,
505
+ callOptions: ConnectorOperationOptions = {},
482
506
  ): Promise<ToolDef[]> {
483
507
  const connector = this.connectors.get(id);
484
508
  if (!connector) throw new Error(`Unknown connector "${id}"`);
@@ -512,7 +536,7 @@ export class Registry implements RegistryView {
512
536
  }
513
537
 
514
538
  try {
515
- return await this.refreshTools(id, baseUrl, requestScope);
539
+ return await this.refreshTools(id, baseUrl, requestScope, callOptions);
516
540
  } catch (err) {
517
541
  if (stale) {
518
542
  this.opts.logger.warn(
@@ -626,10 +650,11 @@ export class Registry implements RegistryView {
626
650
  id: string,
627
651
  baseUrl: string,
628
652
  requestScope: object = {},
653
+ callOptions: ConnectorOperationOptions = {},
629
654
  ): Promise<ConnectorStatus> {
630
655
  const connector = this.connectors.get(id);
631
656
  if (!connector) return { state: "error", message: "Unknown connector" };
632
- const ctx = this.contextFor(id, baseUrl, requestScope);
657
+ const ctx = this.contextFor(id, baseUrl, requestScope, callOptions);
633
658
  if (connector.status) {
634
659
  try {
635
660
  return await connector.status(ctx);
@@ -638,7 +663,7 @@ export class Registry implements RegistryView {
638
663
  }
639
664
  }
640
665
  try {
641
- await this.getTools(id, baseUrl, requestScope);
666
+ await this.getTools(id, baseUrl, requestScope, callOptions);
642
667
  return { state: "ok" };
643
668
  } catch (err) {
644
669
  return { state: "error", message: msg(err) };
@@ -782,11 +807,12 @@ export class ScopedRegistry implements RegistryView {
782
807
  id: string,
783
808
  baseUrl: string,
784
809
  requestScope?: object,
810
+ callOptions: ConnectorOperationOptions = {},
785
811
  ): Promise<ToolDef[]> {
786
812
  if (!this.visible(id)) throw this.unknownConnector(id);
787
813
  return this.inScopeTools(
788
814
  id,
789
- await this.base.getTools(id, baseUrl, requestScope),
815
+ await this.base.getTools(id, baseUrl, requestScope, callOptions),
790
816
  );
791
817
  }
792
818
 
@@ -794,11 +820,12 @@ export class ScopedRegistry implements RegistryView {
794
820
  id: string,
795
821
  baseUrl: string,
796
822
  requestScope?: object,
823
+ callOptions: ConnectorOperationOptions = {},
797
824
  ): Promise<ToolDef[]> {
798
825
  if (!this.visible(id)) throw this.unknownConnector(id);
799
826
  return this.inScopeTools(
800
827
  id,
801
- await this.base.refreshTools(id, baseUrl, requestScope),
828
+ await this.base.refreshTools(id, baseUrl, requestScope, callOptions),
802
829
  );
803
830
  }
804
831
 
@@ -812,7 +839,7 @@ export class ScopedRegistry implements RegistryView {
812
839
  id: string,
813
840
  baseUrl: string,
814
841
  requestScope: object = {},
815
- callOptions: { signal?: AbortSignal; timeoutMs?: number } = {},
842
+ callOptions: ConnectorOperationOptions = {},
816
843
  ): ConnectorContext {
817
844
  // Unreachable through the meta-tools (they resolve first), so a throw here
818
845
  // is a loud backstop rather than a silent grant of connector storage and
@@ -897,12 +924,13 @@ export class ScopedRegistry implements RegistryView {
897
924
  id: string,
898
925
  baseUrl: string,
899
926
  requestScope: object = {},
927
+ callOptions: { signal?: AbortSignal; timeoutMs?: number } = {},
900
928
  ): Promise<ConnectorStatus> {
901
929
  // Same shape the unscoped registry returns for an unregistered id.
902
930
  if (!this.visible(id)) {
903
931
  return { state: "error", message: "Unknown connector" };
904
932
  }
905
- return this.base.statusFor(id, baseUrl, requestScope);
933
+ return this.base.statusFor(id, baseUrl, requestScope, callOptions);
906
934
  }
907
935
 
908
936
  async invalidateStored(id: string): Promise<void> {
package/src/server.ts CHANGED
@@ -222,8 +222,8 @@ async function authorize(
222
222
  | {
223
223
  ok: true;
224
224
  actor: ActivityActor;
225
- providerKind?: string;
226
- userId?: string;
225
+ /** True only when the admitting provider can also authorize UI mutation. */
226
+ uiAdminEligible?: boolean;
227
227
  /** The admitting identity's toolkit binding (docs/toolkits.md). */
228
228
  toolkitBinding?: ToolkitBinding;
229
229
  }
@@ -261,8 +261,9 @@ async function authorize(
261
261
  kind: provider.kind,
262
262
  ...(subjectId ? { id: subjectId } : {}),
263
263
  },
264
- providerKind: provider.kind,
265
- ...(result.userId ? { userId: result.userId } : {}),
264
+ ...(result.userId && provider.uiAuth?.kind === "clerk"
265
+ ? { uiAdminEligible: true }
266
+ : {}),
266
267
  ...(binding.binding ? { toolkitBinding: binding.binding } : {}),
267
268
  };
268
269
  }
@@ -1197,7 +1198,7 @@ export function createFetchHandler(
1197
1198
 
1198
1199
  const operatorPage = operatorPageForPath(path);
1199
1200
  if (operatorPage) {
1200
- if (request.method !== "GET") {
1201
+ if (request.method !== "GET" && request.method !== "HEAD") {
1201
1202
  return privateJson({ error: "method not allowed" }, { status: 405 });
1202
1203
  }
1203
1204
  // Open shell — carries no operator data; everything comes from the
@@ -1212,7 +1213,9 @@ export function createFetchHandler(
1212
1213
  // — only script execution, the XSS sink, is gated.
1213
1214
  const nonce = uiScriptNonce();
1214
1215
  return new Response(
1215
- renderUiHtml(uiAuth, mcpUrl, opts.branding, nonce, operatorPage),
1216
+ request.method === "HEAD"
1217
+ ? null
1218
+ : renderUiHtml(uiAuth, mcpUrl, opts.branding, nonce, operatorPage),
1216
1219
  {
1217
1220
  status: 200,
1218
1221
  headers: {
@@ -1235,8 +1238,7 @@ export function createFetchHandler(
1235
1238
  // After the restriction check, not before: an identity that may not
1236
1239
  // read this surface should not get to trigger background work from it.
1237
1240
  sweepCredentials();
1238
- const eligibleClerkOperator =
1239
- authz.providerKind === "clerk" && Boolean(authz.userId);
1241
+ const eligibleClerkOperator = authz.uiAdminEligible === true;
1240
1242
  const credentialManagement = credentialManagementCapability({
1241
1243
  eligibleClerkOperator,
1242
1244
  hasCredentialSlots: registry
@@ -1253,6 +1255,7 @@ export function createFetchHandler(
1253
1255
  eligibleClerkOperator ? opts.credentialVault : undefined,
1254
1256
  Boolean(opts.activity?.list),
1255
1257
  credentialManagement,
1258
+ opts.toolkits,
1256
1259
  );
1257
1260
  return privateJson(data);
1258
1261
  }
package/src/timeout.ts CHANGED
@@ -21,10 +21,9 @@ export function normalizeTimeoutMs(
21
21
 
22
22
  /**
23
23
  * Reject `promise` after `ms` if it has not settled, so one hung downstream
24
- * cannot stall a whole fan-out. NOTE: this bounds only the caller-facing wait
25
- * the registry probe methods take no AbortSignal, so the underlying fetch is
26
- * NOT cancelled and keeps running in the background. Real cancellation
27
- * (AbortSignal plumbed through the registry) is a deferred follow-up.
24
+ * cannot stall a whole fan-out. This form bounds only the caller-facing wait;
25
+ * use `withAbortableTimeout` when the operation accepts an AbortSignal and the
26
+ * underlying work must stop too.
28
27
  */
29
28
  export function withTimeout<T>(
30
29
  promise: Promise<T>,
@@ -47,3 +46,41 @@ export function withTimeout<T>(
47
46
  );
48
47
  });
49
48
  }
49
+
50
+ /**
51
+ * Give one operation a caller-facing deadline and the matching cancellation
52
+ * signal. The timeout rejects with the stable, labelled error while aborting
53
+ * any in-flight work that honors the signal.
54
+ */
55
+ export function withAbortableTimeout<T>(
56
+ operation: (signal: AbortSignal) => Promise<T>,
57
+ ms: number,
58
+ label: string,
59
+ ): Promise<T> {
60
+ const controller = new AbortController();
61
+ return new Promise<T>((resolve, reject) => {
62
+ const timeoutError = new Error(`${label} timed out after ${ms}ms`);
63
+ const timer = setTimeout(() => {
64
+ controller.abort(timeoutError);
65
+ reject(timeoutError);
66
+ }, ms);
67
+ let promise: Promise<T>;
68
+ try {
69
+ promise = operation(controller.signal);
70
+ } catch (err) {
71
+ clearTimeout(timer);
72
+ reject(err);
73
+ return;
74
+ }
75
+ promise.then(
76
+ (value) => {
77
+ clearTimeout(timer);
78
+ resolve(value);
79
+ },
80
+ (err) => {
81
+ clearTimeout(timer);
82
+ reject(err);
83
+ },
84
+ );
85
+ });
86
+ }
package/src/toolkits.ts CHANGED
@@ -41,6 +41,14 @@ export type ToolkitConfig = Record<string, ToolkitDefinition>;
41
41
  export interface Toolkit {
42
42
  readonly name: string;
43
43
  readonly description?: string;
44
+ /**
45
+ * The validated config this scope came from. Kept as immutable data so
46
+ * deployment-wide operator surfaces can explain the running configuration
47
+ * without inventing a second source of truth.
48
+ */
49
+ readonly connectors: readonly string[];
50
+ readonly includeTools: readonly string[];
51
+ readonly excludeTools: readonly string[];
44
52
  /** True when `connectorId` is inside this toolkit's scope. */
45
53
  hasConnector(connectorId: string): boolean;
46
54
  /** True when `<connectorId>.<toolName>` is inside this toolkit's scope. */
@@ -178,6 +186,9 @@ function resolveToolkit(
178
186
  return {
179
187
  name,
180
188
  ...(definition.description ? { description: definition.description } : {}),
189
+ connectors: Object.freeze([...connectorIds]),
190
+ includeTools: Object.freeze([...(definition.includeTools ?? [])]),
191
+ excludeTools: Object.freeze([...(definition.excludeTools ?? [])]),
181
192
  hasConnector: (connectorId) => connectorIds.has(connectorId),
182
193
  hasTool: (connectorId, toolName) => {
183
194
  if (!connectorIds.has(connectorId)) return false;
package/src/types.ts CHANGED
@@ -113,9 +113,9 @@ export interface ConnectorContext {
113
113
  * when omitted.
114
114
  */
115
115
  requestScope?: object;
116
- /** Best-effort cancellation signal for this individual tool call. */
116
+ /** Best-effort cancellation signal for this connector operation. */
117
117
  signal?: AbortSignal;
118
- /** Requested tool-call deadline in milliseconds. */
118
+ /** Requested connector-operation deadline in milliseconds. */
119
119
  timeoutMs?: number;
120
120
  }
121
121