@zackbart/connecta 0.7.9 → 0.8.1

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 (199) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/README.md +47 -148
  3. package/dist/activity.d.ts +0 -8
  4. package/dist/activity.d.ts.map +1 -1
  5. package/dist/activity.js +0 -1
  6. package/dist/activity.js.map +1 -1
  7. package/dist/auth/bearer.d.ts +1 -7
  8. package/dist/auth/bearer.d.ts.map +1 -1
  9. package/dist/auth/bearer.js +3 -24
  10. package/dist/auth/bearer.js.map +1 -1
  11. package/dist/auth/clerk.d.ts +2 -11
  12. package/dist/auth/clerk.d.ts.map +1 -1
  13. package/dist/auth/clerk.js +31 -46
  14. package/dist/auth/clerk.js.map +1 -1
  15. package/dist/call-admission.d.ts.map +1 -1
  16. package/dist/call-admission.js +9 -4
  17. package/dist/call-admission.js.map +1 -1
  18. package/dist/catalog-fingerprint.d.ts +20 -0
  19. package/dist/catalog-fingerprint.d.ts.map +1 -0
  20. package/dist/catalog-fingerprint.js +34 -0
  21. package/dist/catalog-fingerprint.js.map +1 -0
  22. package/dist/catalog-limits.d.ts +10 -0
  23. package/dist/catalog-limits.d.ts.map +1 -0
  24. package/dist/catalog-limits.js +10 -0
  25. package/dist/catalog-limits.js.map +1 -0
  26. package/dist/catalog-service.d.ts +137 -0
  27. package/dist/catalog-service.d.ts.map +1 -0
  28. package/dist/catalog-service.js +401 -0
  29. package/dist/catalog-service.js.map +1 -0
  30. package/dist/catalog.d.ts +6 -0
  31. package/dist/catalog.d.ts.map +1 -1
  32. package/dist/catalog.js +53 -2
  33. package/dist/catalog.js.map +1 -1
  34. package/dist/concurrency.d.ts +4 -0
  35. package/dist/concurrency.d.ts.map +1 -0
  36. package/dist/concurrency.js +31 -0
  37. package/dist/concurrency.js.map +1 -0
  38. package/dist/connectors/api.d.ts.map +1 -1
  39. package/dist/connectors/api.js +22 -12
  40. package/dist/connectors/api.js.map +1 -1
  41. package/dist/connectors/remote-mcp.d.ts.map +1 -1
  42. package/dist/connectors/remote-mcp.js +53 -41
  43. package/dist/connectors/remote-mcp.js.map +1 -1
  44. package/dist/credentials.d.ts +3 -2
  45. package/dist/credentials.d.ts.map +1 -1
  46. package/dist/errors.d.ts +19 -1
  47. package/dist/errors.d.ts.map +1 -1
  48. package/dist/errors.js.map +1 -1
  49. package/dist/execute.d.ts +10 -16
  50. package/dist/execute.d.ts.map +1 -1
  51. package/dist/execute.js +143 -330
  52. package/dist/execute.js.map +1 -1
  53. package/dist/executor-result.d.ts +0 -3
  54. package/dist/executor-result.d.ts.map +1 -1
  55. package/dist/executor-result.js +3 -3
  56. package/dist/executor-result.js.map +1 -1
  57. package/dist/executors/quickjs-child.js +3 -3
  58. package/dist/executors/quickjs-child.js.map +1 -1
  59. package/dist/executors/quickjs-protocol.d.ts +4 -1
  60. package/dist/executors/quickjs-protocol.d.ts.map +1 -1
  61. package/dist/executors/quickjs-runtime.d.ts.map +1 -1
  62. package/dist/executors/quickjs-runtime.js +7 -2
  63. package/dist/executors/quickjs-runtime.js.map +1 -1
  64. package/dist/executors/quickjs.d.ts.map +1 -1
  65. package/dist/executors/quickjs.js +4 -1
  66. package/dist/executors/quickjs.js.map +1 -1
  67. package/dist/favicon.js +2 -2
  68. package/dist/favicon.js.map +1 -1
  69. package/dist/index.d.ts +8 -82
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +58 -104
  72. package/dist/index.js.map +1 -1
  73. package/dist/invocation.d.ts +101 -0
  74. package/dist/invocation.d.ts.map +1 -0
  75. package/dist/invocation.js +405 -0
  76. package/dist/invocation.js.map +1 -0
  77. package/dist/meta-tools.d.ts +9 -95
  78. package/dist/meta-tools.d.ts.map +1 -1
  79. package/dist/meta-tools.js +235 -725
  80. package/dist/meta-tools.js.map +1 -1
  81. package/dist/operator-ui/generated.d.ts +3 -0
  82. package/dist/operator-ui/generated.d.ts.map +1 -0
  83. package/dist/operator-ui/generated.js +5 -0
  84. package/dist/operator-ui/generated.js.map +1 -0
  85. package/dist/operator-ui/model.d.ts +73 -0
  86. package/dist/operator-ui/model.d.ts.map +1 -0
  87. package/dist/operator-ui/model.js +28 -0
  88. package/dist/operator-ui/model.js.map +1 -0
  89. package/dist/registry.d.ts +33 -184
  90. package/dist/registry.d.ts.map +1 -1
  91. package/dist/registry.js +345 -330
  92. package/dist/registry.js.map +1 -1
  93. package/dist/retired-toolkits.d.ts +3 -0
  94. package/dist/retired-toolkits.d.ts.map +1 -0
  95. package/dist/retired-toolkits.js +9 -0
  96. package/dist/retired-toolkits.js.map +1 -0
  97. package/dist/routes/activity.d.ts +3 -0
  98. package/dist/routes/activity.d.ts.map +1 -0
  99. package/dist/routes/activity.js +170 -0
  100. package/dist/routes/activity.js.map +1 -0
  101. package/dist/routes/credentials.d.ts +3 -0
  102. package/dist/routes/credentials.d.ts.map +1 -0
  103. package/dist/routes/credentials.js +170 -0
  104. package/dist/routes/credentials.js.map +1 -0
  105. package/dist/routes/mcp.d.ts +8 -0
  106. package/dist/routes/mcp.d.ts.map +1 -0
  107. package/dist/routes/mcp.js +270 -0
  108. package/dist/routes/mcp.js.map +1 -0
  109. package/dist/routes/oauth.d.ts +4 -0
  110. package/dist/routes/oauth.d.ts.map +1 -0
  111. package/dist/routes/oauth.js +263 -0
  112. package/dist/routes/oauth.js.map +1 -0
  113. package/dist/routes/shared.d.ts +76 -0
  114. package/dist/routes/shared.d.ts.map +1 -0
  115. package/dist/routes/shared.js +140 -0
  116. package/dist/routes/shared.js.map +1 -0
  117. package/dist/routes/ui.d.ts +3 -0
  118. package/dist/routes/ui.d.ts.map +1 -0
  119. package/dist/routes/ui.js +116 -0
  120. package/dist/routes/ui.js.map +1 -0
  121. package/dist/server.d.ts +9 -40
  122. package/dist/server.d.ts.map +1 -1
  123. package/dist/server.js +62 -1322
  124. package/dist/server.js.map +1 -1
  125. package/dist/skills.d.ts +3 -21
  126. package/dist/skills.d.ts.map +1 -1
  127. package/dist/skills.js +15 -43
  128. package/dist/skills.js.map +1 -1
  129. package/dist/storage/file.d.ts.map +1 -1
  130. package/dist/storage/file.js +3 -1
  131. package/dist/storage/file.js.map +1 -1
  132. package/dist/storage/memory.d.ts.map +1 -1
  133. package/dist/storage/memory.js +3 -1
  134. package/dist/storage/memory.js.map +1 -1
  135. package/dist/timeout.d.ts.map +1 -1
  136. package/dist/timeout.js +2 -4
  137. package/dist/timeout.js.map +1 -1
  138. package/dist/types.d.ts +9 -59
  139. package/dist/types.d.ts.map +1 -1
  140. package/dist/ui.d.ts +3 -94
  141. package/dist/ui.d.ts.map +1 -1
  142. package/dist/ui.js +30 -1427
  143. package/dist/ui.js.map +1 -1
  144. package/dist/version.d.ts +1 -1
  145. package/dist/version.js +1 -1
  146. package/package.json +15 -8
  147. package/src/activity.ts +0 -9
  148. package/src/auth/bearer.ts +4 -33
  149. package/src/auth/clerk.ts +35 -48
  150. package/src/call-admission.ts +8 -6
  151. package/src/catalog-fingerprint.ts +52 -0
  152. package/src/catalog-limits.ts +11 -0
  153. package/src/catalog-service.ts +601 -0
  154. package/src/catalog.ts +57 -2
  155. package/src/concurrency.ts +39 -0
  156. package/src/connectors/api.ts +22 -12
  157. package/src/connectors/remote-mcp.ts +63 -45
  158. package/src/credentials.ts +2 -2
  159. package/src/errors.ts +21 -1
  160. package/src/execute.ts +193 -423
  161. package/src/executor-result.ts +3 -3
  162. package/src/executors/quickjs-child.ts +8 -4
  163. package/src/executors/quickjs-protocol.ts +1 -1
  164. package/src/executors/quickjs-runtime.ts +5 -1
  165. package/src/executors/quickjs.ts +11 -8
  166. package/src/favicon.ts +2 -2
  167. package/src/index.ts +70 -222
  168. package/src/invocation.ts +580 -0
  169. package/src/meta-tools.ts +393 -985
  170. package/src/operator-ui/browser.css +360 -0
  171. package/src/operator-ui/browser.ts +987 -0
  172. package/src/operator-ui/generated.ts +4 -0
  173. package/src/operator-ui/model.ts +105 -0
  174. package/src/registry.ts +459 -439
  175. package/src/retired-toolkits.ts +14 -0
  176. package/src/routes/activity.ts +215 -0
  177. package/src/routes/credentials.ts +254 -0
  178. package/src/routes/mcp.ts +339 -0
  179. package/src/routes/oauth.ts +322 -0
  180. package/src/routes/shared.ts +240 -0
  181. package/src/routes/ui.ts +143 -0
  182. package/src/server.ts +64 -1731
  183. package/src/skills.ts +15 -43
  184. package/src/storage/file.ts +3 -1
  185. package/src/storage/memory.ts +3 -1
  186. package/src/timeout.ts +2 -4
  187. package/src/types.ts +8 -60
  188. package/src/ui.ts +60 -1538
  189. package/src/version.ts +1 -1
  190. package/dist/credential-health.d.ts +0 -227
  191. package/dist/credential-health.d.ts.map +0 -1
  192. package/dist/credential-health.js +0 -606
  193. package/dist/credential-health.js.map +0 -1
  194. package/dist/toolkits.d.ts +0 -146
  195. package/dist/toolkits.d.ts.map +0 -1
  196. package/dist/toolkits.js +0 -322
  197. package/dist/toolkits.js.map +0 -1
  198. package/src/credential-health.ts +0 -832
  199. package/src/toolkits.ts +0 -461
@@ -92,10 +92,10 @@ export interface ApiOptions {
92
92
  export function api(id: string, opts: ApiOptions): Connector {
93
93
  const defs: ToolDef[] = opts.tools.map((t) => ({
94
94
  name: t.name,
95
- description: t.description,
96
- inputSchema: t.inputSchema,
97
- outputSchema: t.outputSchema,
98
- annotations: t.annotations,
95
+ ...(t.description !== undefined ? { description: t.description } : {}),
96
+ ...(t.inputSchema !== undefined ? { inputSchema: t.inputSchema } : {}),
97
+ ...(t.outputSchema !== undefined ? { outputSchema: t.outputSchema } : {}),
98
+ ...(t.annotations !== undefined ? { annotations: t.annotations } : {}),
99
99
  }));
100
100
  const byName = new Map(opts.tools.map((t) => [t.name, t]));
101
101
  const validateArgs = opts.validateArgs ?? true;
@@ -110,15 +110,25 @@ export function api(id: string, opts: ApiOptions): Connector {
110
110
  }
111
111
  return {
112
112
  id,
113
- title: opts.title,
113
+ ...(opts.title !== undefined ? { title: opts.title } : {}),
114
114
  kind: "api",
115
- description: opts.description,
116
- maxResultBytes: opts.maxResultBytes,
117
- callAdmission: opts.callAdmission,
118
- usageGuide: opts.usageGuide,
119
- credential: opts.credential,
120
- testCredential: opts.testCredential,
121
- testCredentials: opts.testCredentials,
115
+ ...(opts.description !== undefined
116
+ ? { description: opts.description }
117
+ : {}),
118
+ ...(opts.maxResultBytes !== undefined
119
+ ? { maxResultBytes: opts.maxResultBytes }
120
+ : {}),
121
+ ...(opts.callAdmission !== undefined
122
+ ? { callAdmission: opts.callAdmission }
123
+ : {}),
124
+ ...(opts.usageGuide !== undefined ? { usageGuide: opts.usageGuide } : {}),
125
+ ...(opts.credential !== undefined ? { credential: opts.credential } : {}),
126
+ ...(opts.testCredential !== undefined
127
+ ? { testCredential: opts.testCredential }
128
+ : {}),
129
+ ...(opts.testCredentials !== undefined
130
+ ? { testCredentials: opts.testCredentials }
131
+ : {}),
122
132
  staticTools: defs,
123
133
  async listTools() {
124
134
  return defs;
@@ -9,6 +9,7 @@ import { ListToolsResultSchema } from "@modelcontextprotocol/sdk/types.js";
9
9
  import { CfWorkerJsonSchemaValidator } from "@modelcontextprotocol/sdk/validation/cfworker";
10
10
  import { z } from "zod";
11
11
  import { KvOAuthProvider } from "../auth/downstream-oauth.js";
12
+ import { MAX_CATALOG_TOOLS } from "../catalog-limits.js";
12
13
  import { ConnectorCallError } from "../errors.js";
13
14
  import { CONNECTA_VERSION } from "../version.js";
14
15
  import type {
@@ -109,18 +110,16 @@ const TERMINATE_SESSION_BUDGET_MS = 1_000;
109
110
  * cap the bytes a caller can be made to hold, not the number of round trips it
110
111
  * took to get them.
111
112
  */
112
- const MAX_TOOLS = 100_000;
113
-
114
113
  /**
115
114
  * Absolute backstop on `tools/list` pages in one refresh — a runaway guard, not
116
115
  * the primary defense.
117
116
  *
118
117
  * The walk terminates on its own well before this: a cursor handed back twice
119
118
  * is a definite loop, two consecutive pages that add no new tools are a server
120
- * going nowhere, and MAX_TOOLS caps what any of it can accumulate. This exists
121
- * only so the loop is finite even if a downstream somehow satisfies all three
122
- * forever on a path with no discovery deadline. Set high enough that no honest
123
- * server reaches it.
119
+ * going nowhere, and MAX_CATALOG_TOOLS caps what any of it can accumulate.
120
+ * This exists only so the loop is finite even if a downstream somehow
121
+ * satisfies all three forever on a path with no discovery deadline. Set high
122
+ * enough that no honest server reaches it.
124
123
  */
125
124
  const MAX_TOOL_PAGES = 10_000;
126
125
 
@@ -191,8 +190,7 @@ function msg(err: unknown): string {
191
190
  * `Client.close()` only unwinds our side — it aborts the transport's controller
192
191
  * and fires `onclose`. Spec session termination is a separate DELETE carrying
193
192
  * `Mcp-Session-Id`, and without it a stateful provider keeps the session alive
194
- * until its own (often hour-long) timeout, which a periodic probe would then
195
- * accumulate several of per connector.
193
+ * until its own (often hour-long) timeout, accumulating abandoned sessions.
196
194
  *
197
195
  * Ordering is load-bearing: the SDK sends that DELETE on the transport's
198
196
  * AbortSignal, so calling this *after* close would abort the request on issue
@@ -298,7 +296,9 @@ function redirectedInit(init: RequestInit, status: number): RequestInit {
298
296
  if (!becomesGet) return init;
299
297
  const headers = new Headers(init.headers);
300
298
  for (const name of BODY_HEADERS) headers.delete(name);
301
- return { ...init, method: "GET", body: undefined, headers };
299
+ const redirected = { ...init, method: "GET", headers };
300
+ delete redirected.body;
301
+ return redirected;
302
302
  }
303
303
 
304
304
  /**
@@ -467,6 +467,14 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector {
467
467
  const scopeEndedError = () =>
468
468
  new Error(`Connector "${id}" scope ended during connection.`);
469
469
 
470
+ const requestOptions = (ctx: ConnectorContext) =>
471
+ ctx.timeoutMs || ctx.signal
472
+ ? {
473
+ ...(ctx.timeoutMs ? { timeout: ctx.timeoutMs } : {}),
474
+ ...(ctx.signal ? { signal: ctx.signal } : {}),
475
+ }
476
+ : undefined;
477
+
470
478
  /**
471
479
  * One `tools/list` request. The SDK schema is retained wholesale except for
472
480
  * accepting `null` as the common, unambiguous end-of-chain spelling. Other
@@ -484,12 +492,7 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector {
484
492
  ...(cursor === undefined ? {} : { params: { cursor } }),
485
493
  },
486
494
  CompatibleListToolsResultSchema,
487
- ctx.timeoutMs || ctx.signal
488
- ? {
489
- ...(ctx.timeoutMs ? { timeout: ctx.timeoutMs } : {}),
490
- ...(ctx.signal ? { signal: ctx.signal } : {}),
491
- }
492
- : undefined,
495
+ requestOptions(ctx),
493
496
  );
494
497
  } catch (err) {
495
498
  if (!isCursorShapeError(err)) throw err;
@@ -546,10 +549,14 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector {
546
549
  const url = new URL(opts.url);
547
550
  const guardedFetch = redirectSafeFetch(id, opts.redirects);
548
551
  if (opts.auth?.type === "oauth") {
552
+ // The SDK class declares `sessionId` as an own `string | undefined`
553
+ // property while its Transport interface declares it optional. They are
554
+ // runtime-compatible; exact optional types only exposes that declaration
555
+ // mismatch at this boundary.
549
556
  return new StreamableHTTPClientTransport(url, {
550
557
  authProvider: provider ?? newProvider(ctx),
551
558
  fetch: guardedFetch,
552
- });
559
+ }) as unknown as Transport;
553
560
  }
554
561
  const headers =
555
562
  opts.auth?.type === "headers" ? opts.auth.headers : undefined;
@@ -559,7 +566,7 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector {
559
566
  ...(headers ? { requestInit: { headers } } : {}),
560
567
  fetch: guardedFetch,
561
568
  },
562
- );
569
+ ) as unknown as Transport;
563
570
  };
564
571
 
565
572
  const reset = (state: ConnectionState) => {
@@ -742,12 +749,18 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector {
742
749
 
743
750
  const connector: Connector = {
744
751
  id,
745
- title: opts.title,
752
+ ...(opts.title !== undefined ? { title: opts.title } : {}),
746
753
  kind: "mcp",
747
- description: opts.description,
748
- maxResultBytes: opts.maxResultBytes,
749
- callAdmission: opts.callAdmission,
750
- usageGuide: opts.usageGuide,
754
+ ...(opts.description !== undefined
755
+ ? { description: opts.description }
756
+ : {}),
757
+ ...(opts.maxResultBytes !== undefined
758
+ ? { maxResultBytes: opts.maxResultBytes }
759
+ : {}),
760
+ ...(opts.callAdmission !== undefined
761
+ ? { callAdmission: opts.callAdmission }
762
+ : {}),
763
+ ...(opts.usageGuide !== undefined ? { usageGuide: opts.usageGuide } : {}),
751
764
 
752
765
  // `tools/list` is cursor-paginated: the server chooses the page size and
753
766
  // signals "there is more" with a `nextCursor`, which the SDK's
@@ -830,9 +843,9 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector {
830
843
  }
831
844
  // Checked here rather than on arrival: this bounds what a *walk* may
832
845
  // accumulate; a one-page server was always free to send its page.
833
- if (listed.length > MAX_TOOLS) {
846
+ if (listed.length > MAX_CATALOG_TOOLS) {
834
847
  throw new Error(
835
- `Connector "${id}" advertised further tools/list pages past ${listed.length} tools, over the ${MAX_TOOLS}-tool ceiling one catalog refresh will collect.`,
848
+ `Connector "${id}" advertised further tools/list pages past ${listed.length} tools, over the ${MAX_CATALOG_TOOLS}-tool ceiling one catalog refresh will collect.`,
836
849
  );
837
850
  }
838
851
  // Opaque by contract: handed straight back, never parsed, rewritten,
@@ -863,10 +876,28 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector {
863
876
  primeToolMetadata(client, listed);
864
877
  return listed.map((t) => ({
865
878
  name: t.name,
866
- description: t.description,
867
- inputSchema: t.inputSchema as ToolDef["inputSchema"],
868
- outputSchema: t.outputSchema as ToolDef["outputSchema"],
869
- annotations: t.annotations as ToolDef["annotations"],
879
+ ...(t.description !== undefined ? { description: t.description } : {}),
880
+ ...(t.inputSchema !== undefined
881
+ ? {
882
+ inputSchema: t.inputSchema as NonNullable<
883
+ ToolDef["inputSchema"]
884
+ >,
885
+ }
886
+ : {}),
887
+ ...(t.outputSchema !== undefined
888
+ ? {
889
+ outputSchema: t.outputSchema as NonNullable<
890
+ ToolDef["outputSchema"]
891
+ >,
892
+ }
893
+ : {}),
894
+ ...(t.annotations !== undefined
895
+ ? {
896
+ annotations: t.annotations as NonNullable<
897
+ ToolDef["annotations"]
898
+ >,
899
+ }
900
+ : {}),
870
901
  }));
871
902
  },
872
903
 
@@ -881,12 +912,7 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector {
881
912
  arguments: (args ?? {}) as Record<string, unknown>,
882
913
  },
883
914
  undefined,
884
- ctx.timeoutMs || ctx.signal
885
- ? {
886
- ...(ctx.timeoutMs ? { timeout: ctx.timeoutMs } : {}),
887
- ...(ctx.signal ? { signal: ctx.signal } : {}),
888
- }
889
- : undefined,
915
+ requestOptions(ctx),
890
916
  );
891
917
  } catch (err) {
892
918
  // A grant revoked after connect surfaces here, not in ensureConnected.
@@ -942,7 +968,7 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector {
942
968
  const url = await getProvider(ctx, state).pendingAuthorizationUrl();
943
969
  return {
944
970
  state: "auth_required",
945
- authorizationUrl: url,
971
+ ...(url !== undefined ? { authorizationUrl: url } : {}),
946
972
  message: "Authorization required — open the URL to connect.",
947
973
  };
948
974
  }
@@ -969,15 +995,6 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector {
969
995
  };
970
996
 
971
997
  if (opts.auth?.type === "oauth") {
972
- // The credential liveness checks (issue #24) probe only connectors that
973
- // actually hold a stored grant: with no tokens there is nothing whose
974
- // liveness could have lapsed, and a `status()` probe would kick off DCR +
975
- // consent on a timer for a connector nobody has authorized yet.
976
- connector.hasStoredCredential = async (ctx) => {
977
- const state = stateFor(ctx);
978
- return (await getProvider(ctx, state).tokens()) !== undefined;
979
- };
980
-
981
998
  connector.verifyState = async (oauthState, ctx) => {
982
999
  const state = stateFor(ctx);
983
1000
  return getProvider(ctx, state).verifyState(oauthState);
@@ -1013,9 +1030,10 @@ export function remoteMcp(id: string, opts: RemoteMcpOptions): Connector {
1013
1030
  };
1014
1031
  } catch (err) {
1015
1032
  if (state.authRequired) {
1033
+ const authorizationUrl = await p.pendingAuthorizationUrl();
1016
1034
  return {
1017
1035
  state: "auth_required",
1018
- authorizationUrl: await p.pendingAuthorizationUrl(),
1036
+ ...(authorizationUrl !== undefined ? { authorizationUrl } : {}),
1019
1037
  message: "Authorization required — open the URL to connect.",
1020
1038
  };
1021
1039
  }
@@ -24,7 +24,7 @@ interface CredentialPlaintext {
24
24
  updatedBy: string;
25
25
  }
26
26
 
27
- export interface CredentialFieldMetadata {
27
+ interface CredentialFieldMetadata {
28
28
  configured: true;
29
29
  /** Only emitted when the value is long enough that four chars don't leak much. */
30
30
  lastFour?: string;
@@ -41,7 +41,7 @@ export interface CredentialMetadata {
41
41
  }
42
42
 
43
43
  /** Which hook a testable credential is checked with. */
44
- export type CredentialTestMode = "single" | "multiple";
44
+ type CredentialTestMode = "single" | "multiple";
45
45
 
46
46
  /** Operator-safe explanation shared by every surface that detects shape drift. */
47
47
  export const STORED_CREDENTIAL_SHAPE_MISMATCH_ERROR =
package/src/errors.ts CHANGED
@@ -10,6 +10,12 @@ export type ConnectorCallErrorCode =
10
10
  | "invalid_args"
11
11
  | "connector_call_failed";
12
12
 
13
+ /** Agent-visible recovery class attached only to `auth_required` failures. */
14
+ export type AuthRecoveryMode =
15
+ | "oauth"
16
+ | "operator_config"
17
+ | "unavailable";
18
+
13
19
  const RETRYABLE_BY_CODE: Record<ConnectorCallErrorCode, boolean> = {
14
20
  timeout: true,
15
21
  rate_limited: true,
@@ -49,7 +55,7 @@ export class ConnectorCallError extends Error {
49
55
  * the assignment would not keep it off the instance. Keeping the window out
50
56
  * of the wire format is `classifyCallError`'s job, not this constructor's.
51
57
  */
52
- readonly retryAfterMs?: number;
58
+ readonly retryAfterMs: number | undefined;
53
59
 
54
60
  constructor(
55
61
  code: ConnectorCallErrorCode,
@@ -78,6 +84,20 @@ export interface CallErrorDetails {
78
84
  * window so it can schedule a re-issue.
79
85
  */
80
86
  retryAfterMs?: number;
87
+ /** Connector whose failed operation needs recovery. */
88
+ connector?: string;
89
+ /** Canonical downstream address the agent may retry after recovery. */
90
+ operation?: string;
91
+ /** Which safe recovery path `authorize_connector` will return. */
92
+ recovery?: AuthRecoveryMode;
93
+ /** The single model-facing entry point for every credential class. */
94
+ nextAction?: {
95
+ tool: "authorize_connector";
96
+ arguments: { connector: string };
97
+ operatorHandoff: string;
98
+ };
99
+ /** Explicit retry guidance; recovery never retries or mutates by itself. */
100
+ retry?: string;
81
101
  }
82
102
 
83
103
  const RETRYABLE_MESSAGE_RE =