@zackbart/connecta 0.28.1 → 0.29.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 (319) hide show
  1. package/CHANGELOG.md +788 -5
  2. package/README.md +62 -42
  3. package/bin/connecta.mjs +110 -139
  4. package/bin/meta-tool-names.d.mts +2 -0
  5. package/bin/meta-tool-names.mjs +9 -0
  6. package/bin/version.mjs +3 -0
  7. package/dist/access-tokens.js +53 -63
  8. package/dist/activity-request.js +8 -0
  9. package/dist/activity.d.ts +76 -42
  10. package/dist/activity.js +110 -42
  11. package/dist/auth/clerk-sdk/client.d.ts +65 -0
  12. package/dist/auth/clerk-sdk/client.js +5 -0
  13. package/dist/auth/clerk-sdk/generated.js +1845 -0
  14. package/dist/auth/clerk-transport.js +19 -0
  15. package/dist/auth/clerk.d.ts +17 -7
  16. package/dist/auth/clerk.js +189 -107
  17. package/dist/auth/cloudflare-access.js +25 -26
  18. package/dist/auth/downstream-client-metadata.d.ts +29 -0
  19. package/dist/auth/downstream-client-metadata.js +133 -0
  20. package/dist/auth/downstream-oauth.js +1584 -1580
  21. package/dist/auth/downstream-revocation.js +106 -0
  22. package/dist/auth/oauth-token-response.js +231 -0
  23. package/dist/auth/oauth-v2-migration.js +116 -0
  24. package/dist/auth/static-oauth.js +93 -71
  25. package/dist/auth-elicitation.js +283 -0
  26. package/dist/branding.d.ts +8 -16
  27. package/dist/branding.js +26 -59
  28. package/dist/byte-read-response.js +39 -0
  29. package/dist/call-admission.js +12 -16
  30. package/dist/catalog-cache.js +739 -0
  31. package/dist/catalog-drift.d.ts +50 -15
  32. package/dist/catalog-drift.js +365 -128
  33. package/dist/catalog-fingerprint.js +2 -4
  34. package/dist/catalog-freshness.js +13 -0
  35. package/dist/catalog-limits.js +2 -2
  36. package/dist/catalog-service.js +368 -224
  37. package/dist/catalog.js +43 -84
  38. package/dist/concurrency.js +3 -4
  39. package/dist/config-defaults.d.ts +39 -0
  40. package/dist/config-defaults.js +46 -0
  41. package/dist/config-schema.d.ts +181 -0
  42. package/dist/config-schema.js +435 -0
  43. package/dist/config-value-sources.js +102 -0
  44. package/dist/config.d.ts +631 -0
  45. package/dist/config.js +503 -0
  46. package/dist/connector-access.d.ts +1 -0
  47. package/dist/connector-access.js +13 -15
  48. package/dist/connector-caller.d.ts +8 -0
  49. package/dist/connector-caller.js +15 -6
  50. package/dist/connector-scope.js +1 -1
  51. package/dist/connectors/api-connector.d.ts +59 -7
  52. package/dist/connectors/api-connector.js +106 -19
  53. package/dist/connectors/api.js +4 -1
  54. package/dist/connectors/guarded-fetch.d.ts +144 -0
  55. package/dist/connectors/guarded-fetch.js +84 -35
  56. package/dist/connectors/negotiation-cache.js +46 -0
  57. package/dist/connectors/option-shapes.js +27 -0
  58. package/dist/connectors/remote-mcp.d.ts +30 -4
  59. package/dist/connectors/remote-mcp.js +1774 -320
  60. package/dist/connectors/resource-uri.js +335 -0
  61. package/dist/credential-contract.d.ts +10 -0
  62. package/dist/credential-rules.js +1 -3
  63. package/dist/credential-url.js +108 -0
  64. package/dist/credentials.d.ts +6 -1
  65. package/dist/credentials.js +27 -18
  66. package/dist/d1.d.ts +130 -0
  67. package/dist/d1.js +290 -0
  68. package/dist/describe-config.d.ts +165 -0
  69. package/dist/describe-config.js +315 -0
  70. package/dist/described.js +106 -0
  71. package/dist/downstream-input-context.js +119 -0
  72. package/dist/downstream-input.js +323 -0
  73. package/dist/errors.d.ts +97 -20
  74. package/dist/errors.js +158 -80
  75. package/dist/execute.js +453 -305
  76. package/dist/executor-admission.d.ts +1 -0
  77. package/dist/executor-admission.js +11 -15
  78. package/dist/executor-contract.d.ts +20 -0
  79. package/dist/executor-contract.js +48 -0
  80. package/dist/executor-result.js +24 -9
  81. package/dist/executors/quickjs-child.js +3 -4
  82. package/dist/executors/quickjs-runtime.js +147 -59
  83. package/dist/executors/quickjs.js +29 -35
  84. package/dist/favicon.js +1 -2
  85. package/dist/fix-prompt.js +1 -3
  86. package/dist/guest-runtime.js +188 -0
  87. package/dist/guest-types.d.ts +176 -0
  88. package/dist/guest-types.js +1 -0
  89. package/dist/html-security.js +24 -0
  90. package/dist/identity.js +1 -3
  91. package/dist/inbound-credential.js +52 -0
  92. package/dist/index.d.ts +24 -256
  93. package/dist/index.js +65 -478
  94. package/dist/invocation-auth.js +69 -0
  95. package/dist/invocation.js +230 -73
  96. package/dist/json-schema.d.ts +1 -1
  97. package/dist/mcp-client-context.js +35 -0
  98. package/dist/mcp-result.js +11 -7
  99. package/dist/meta-output.js +76 -0
  100. package/dist/meta-tool-names.d.ts +2 -0
  101. package/dist/meta-tool-names.js +3 -0
  102. package/dist/meta-tools.js +285 -209
  103. package/dist/module-contracts.d.ts +8 -23
  104. package/dist/node.d.ts +0 -2
  105. package/dist/node.js +2 -2
  106. package/dist/oauth-callback-outcome.js +1 -3
  107. package/dist/oauth-handoff.js +117 -0
  108. package/dist/oauth-sealing.js +20 -0
  109. package/dist/operator-record.d.ts +137 -0
  110. package/dist/operator-record.js +420 -0
  111. package/dist/operator-ui/assets.js +31 -0
  112. package/dist/operator-ui/contract.d.ts +72 -0
  113. package/dist/operator-ui/contract.js +1 -0
  114. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  115. package/dist/operator-ui/generated.js +5 -4
  116. package/dist/operator-ui/model.d.ts +12 -87
  117. package/dist/operator-ui/model.js +2 -9
  118. package/dist/operator-ui/setup-commands.js +1 -3
  119. package/dist/operator-ui/view.d.ts +323 -0
  120. package/dist/operator-ui/view.js +62 -189
  121. package/dist/page-styles.js +2 -3
  122. package/dist/program-source.js +14 -2
  123. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  124. package/dist/provider.d.ts +142 -0
  125. package/dist/provider.js +187 -0
  126. package/dist/providers/_shared/google/result-size.js +57 -0
  127. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  128. package/dist/providers/_shared/google/service-account.js +500 -0
  129. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  130. package/dist/providers/_shared/google/workspace.js +518 -0
  131. package/dist/providers/{basecamp.d.ts → basecamp/index.d.ts} +6 -28
  132. package/dist/providers/basecamp/index.js +997 -0
  133. package/dist/providers/basecamp/skill.generated.js +9 -0
  134. package/dist/providers/{breeze.d.ts → breeze/index.d.ts} +2 -2
  135. package/dist/providers/{breeze.js → breeze/index.js} +184 -140
  136. package/dist/providers/breeze/skill.generated.js +9 -0
  137. package/dist/providers/{ccb.d.ts → ccb/index.d.ts} +2 -2
  138. package/dist/providers/{ccb.js → ccb/index.js} +271 -116
  139. package/dist/providers/ccb/skill.generated.js +9 -0
  140. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  141. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  142. package/dist/providers/cloudflare/skill.generated.js +10 -0
  143. package/dist/providers/docs/index.d.ts +54 -0
  144. package/dist/providers/docs/index.js +1237 -0
  145. package/dist/providers/docs/skill.generated.js +9 -0
  146. package/dist/providers/drive/index.d.ts +53 -0
  147. package/dist/providers/drive/index.js +1523 -0
  148. package/dist/providers/drive/skill.generated.js +9 -0
  149. package/dist/providers/forms/index.d.ts +54 -0
  150. package/dist/providers/forms/index.js +1217 -0
  151. package/dist/providers/forms/skill.generated.js +11 -0
  152. package/dist/providers/github/auth.d.ts +32 -0
  153. package/dist/providers/github/auth.js +361 -0
  154. package/dist/providers/github/hosted.js +218 -0
  155. package/dist/providers/github/index.d.ts +13 -0
  156. package/dist/providers/github/index.js +140 -0
  157. package/dist/providers/github/key.js +100 -0
  158. package/dist/providers/github/rest.js +321 -0
  159. package/dist/providers/github/scope.d.ts +29 -0
  160. package/dist/providers/github/scope.js +124 -0
  161. package/dist/providers/github/skill.generated.js +8 -0
  162. package/dist/providers/gmail/index.d.ts +51 -0
  163. package/dist/providers/gmail/index.js +1436 -0
  164. package/dist/providers/gmail/skill.generated.js +9 -0
  165. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  166. package/dist/providers/linear/index.js +352 -0
  167. package/dist/providers/linear/skill.generated.js +8 -0
  168. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  169. package/dist/providers/mixpanel/index.js +416 -0
  170. package/dist/providers/mixpanel/skill.generated.js +9 -0
  171. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  172. package/dist/providers/{notion.js → notion/index.js} +226 -239
  173. package/dist/providers/notion/skill.generated.js +10 -0
  174. package/dist/providers/{overflow.d.ts → overflow/index.d.ts} +2 -2
  175. package/dist/providers/{overflow.js → overflow/index.js} +74 -111
  176. package/dist/providers/overflow/skill.generated.js +8 -0
  177. package/dist/providers/{planning-center.d.ts → planning-center/index.d.ts} +2 -2
  178. package/dist/providers/{planning-center.js → planning-center/index.js} +264 -104
  179. package/dist/providers/planning-center/skill.generated.js +10 -0
  180. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  181. package/dist/providers/revenuecat/index.js +557 -0
  182. package/dist/providers/revenuecat/skill.generated.js +11 -0
  183. package/dist/providers/sheets/index.d.ts +54 -0
  184. package/dist/providers/sheets/index.js +1237 -0
  185. package/dist/providers/sheets/skill.generated.js +10 -0
  186. package/dist/providers/slides/index.d.ts +53 -0
  187. package/dist/providers/slides/index.js +2645 -0
  188. package/dist/providers/slides/skill.generated.js +9 -0
  189. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  190. package/dist/providers/stripe/index.js +266 -0
  191. package/dist/providers/stripe/skill.generated.js +11 -0
  192. package/dist/providers/{tithely.d.ts → tithely/index.d.ts} +2 -2
  193. package/dist/providers/{tithely.js → tithely/index.js} +60 -91
  194. package/dist/providers/tithely/skill.generated.js +8 -0
  195. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  196. package/dist/providers/vercel/index.js +1038 -0
  197. package/dist/providers/vercel/mcp-ownership.js +212 -0
  198. package/dist/providers/vercel/skill.generated.js +11 -0
  199. package/dist/registry.d.ts +108 -171
  200. package/dist/registry.js +513 -925
  201. package/dist/request-state.js +21 -0
  202. package/dist/resource-read.js +100 -0
  203. package/dist/result-shapes.js +17 -49
  204. package/dist/retired-toolkits.js +1 -1
  205. package/dist/routes/access-tokens.js +3 -6
  206. package/dist/routes/activity-context.js +21 -0
  207. package/dist/routes/activity-disclosure.js +40 -0
  208. package/dist/routes/activity.js +59 -21
  209. package/dist/routes/actor-labels.js +3 -50
  210. package/dist/routes/connect.js +221 -0
  211. package/dist/routes/credentials.js +15 -27
  212. package/dist/routes/mcp.js +356 -130
  213. package/dist/routes/oauth-client-metadata.js +15 -0
  214. package/dist/routes/oauth-management.js +45 -107
  215. package/dist/routes/oauth.js +110 -57
  216. package/dist/routes/operator.js +3 -3
  217. package/dist/routes/shared.d.ts +25 -64
  218. package/dist/routes/shared.js +160 -80
  219. package/dist/routes/skills.js +30 -0
  220. package/dist/routes/ui-config.js +225 -0
  221. package/dist/routes/ui-data.js +40 -35
  222. package/dist/routes/ui.js +66 -56
  223. package/dist/runtime/admission.js +4 -2
  224. package/dist/runtime/call-admission.js +3 -1
  225. package/dist/runtime/connector-scope.js +1 -4
  226. package/dist/runtime/run.js +10 -39
  227. package/dist/runtime/services.js +12 -117
  228. package/dist/runtime/shared-read.js +1 -3
  229. package/dist/runtime/storage.js +8 -1
  230. package/dist/sent-secrets.d.ts +52 -0
  231. package/dist/sent-secrets.js +474 -0
  232. package/dist/server.js +28 -71
  233. package/dist/skills.js +406 -174
  234. package/dist/sqlite.d.ts +48 -0
  235. package/dist/sqlite.js +166 -0
  236. package/dist/storage/keys.js +428 -0
  237. package/dist/storage/memory.js +7 -0
  238. package/dist/storage/sql.d.ts +66 -0
  239. package/dist/storage/sql.js +534 -0
  240. package/dist/timeout.js +4 -2
  241. package/dist/tool-name.js +17 -0
  242. package/dist/tool-safety.d.ts +9 -35
  243. package/dist/tool-safety.js +20 -40
  244. package/dist/types.d.ts +320 -54
  245. package/dist/types.js +0 -1
  246. package/dist/ui.d.ts +7 -19
  247. package/dist/ui.js +76 -90
  248. package/dist/url-safety.js +3 -6
  249. package/dist/usage-guide.js +137 -0
  250. package/dist/validate.d.ts +7 -0
  251. package/dist/validate.js +309 -47
  252. package/dist/version.d.ts +1 -7
  253. package/dist/version.js +3 -7
  254. package/dist/worker.d.ts +11 -0
  255. package/dist/worker.js +434 -0
  256. package/documentation/architecture.md +664 -231
  257. package/documentation/auth.md +936 -546
  258. package/documentation/code-mode.md +501 -312
  259. package/documentation/meta-tools.md +449 -200
  260. package/documentation/operator-tests.md +93 -0
  261. package/documentation/provider-migration-0.29.md +60 -0
  262. package/examples/worker/AGENTS.md +34 -12
  263. package/examples/worker/README.md +286 -255
  264. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  265. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  266. package/examples/worker/src/connecta.config.ts +151 -0
  267. package/examples/worker/src/index.ts +14 -197
  268. package/examples/worker/wrangler.jsonc +15 -35
  269. package/package.json +120 -54
  270. package/templates/node/.dockerignore +1 -2
  271. package/templates/node/.env.example +20 -21
  272. package/templates/node/AGENTS.md +21 -17
  273. package/templates/node/Dockerfile +3 -5
  274. package/templates/node/README.md +133 -58
  275. package/templates/node/docker-compose.yml +11 -12
  276. package/templates/node/package.json +4 -2
  277. package/templates/node/src/connecta.config.ts +119 -0
  278. package/templates/node/src/index.ts +9 -154
  279. package/templates/node/src/provision-token.ts +17 -0
  280. package/AGENTS.md +0 -170
  281. package/dist/artifacts/connector.d.ts +0 -58
  282. package/dist/artifacts/connector.js +0 -730
  283. package/dist/artifacts/document.js +0 -86
  284. package/dist/artifacts/guide.js +0 -140
  285. package/dist/artifacts/html-scan.js +0 -329
  286. package/dist/artifacts/json.js +0 -78
  287. package/dist/artifacts/kv-store.d.ts +0 -20
  288. package/dist/artifacts/kv-store.js +0 -140
  289. package/dist/artifacts/markdown.js +0 -478
  290. package/dist/artifacts/module.d.ts +0 -37
  291. package/dist/artifacts/module.js +0 -66
  292. package/dist/artifacts/operations.d.ts +0 -197
  293. package/dist/artifacts/operations.js +0 -1002
  294. package/dist/artifacts/refresh.d.ts +0 -55
  295. package/dist/artifacts/refresh.js +0 -236
  296. package/dist/artifacts/routes.js +0 -206
  297. package/dist/artifacts/types.d.ts +0 -214
  298. package/dist/artifacts/types.js +0 -22
  299. package/dist/artifacts/validate.d.ts +0 -62
  300. package/dist/artifacts/validate.js +0 -651
  301. package/dist/artifacts.d.ts +0 -6
  302. package/dist/artifacts.js +0 -9
  303. package/dist/auth/bearer.d.ts +0 -11
  304. package/dist/auth/bearer.js +0 -50
  305. package/dist/providers/basecamp.js +0 -445
  306. package/dist/providers/linear.js +0 -217
  307. package/dist/providers/mixpanel.js +0 -247
  308. package/dist/providers/revenuecat.js +0 -317
  309. package/dist/providers/stripe.js +0 -253
  310. package/dist/providers/vercel.js +0 -1253
  311. package/dist/storage/file.d.ts +0 -15
  312. package/dist/storage/file.js +0 -455
  313. package/ethos.md +0 -93
  314. package/examples/worker/src/cloudflare-kv.ts +0 -43
  315. package/examples/worker/src/d1-activity-row.ts +0 -155
  316. package/examples/worker/src/d1-activity.ts +0 -147
  317. package/examples/worker/src/d1-storage.ts +0 -138
  318. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  319. package/templates/node/src/file-activity.ts +0 -182
@@ -1,7 +1,6 @@
1
- import type { ConnectaBranding, Connector } from "./types.js";
2
- import type { ActivityStore, ActivityReadGate, recordToolActivity, recordCatalogDriftActivity } from "./activity.js";
1
+ import type { ConnectaBranding } from "./types.js";
2
+ import type { ActivityStore, ActivityReadGate, recordToolActivity, recordCatalogChangeActivity } from "./activity.js";
3
3
  import type { RouteContext } from "./routes/shared.js";
4
- import type { ArtifactRefreshRuntime } from "./artifacts/refresh.js";
5
4
  /** Construction-time UI contract. Core never imports the implementation. */
6
5
  export interface OperatorSurface {
7
6
  readonly branding?: ConnectaBranding;
@@ -16,29 +15,15 @@ export interface ActivityModule {
16
15
  readonly readGate?: ActivityReadGate;
17
16
  handle(context: RouteContext): Promise<Response | null>;
18
17
  readonly recordTool: typeof recordToolActivity;
19
- readonly recordDrift: typeof recordCatalogDriftActivity;
20
- }
21
- /**
22
- * The artifacts module, created by `artifacts()` from /artifacts. Core appends
23
- * its one prebuilt connector to the configured set — the same catalog,
24
- * invocation, admission, and activity paths as any other — and imports none
25
- * of its implementation.
26
- */
27
- export interface ArtifactsModule {
28
- /** The built-in `artifacts` connector. */
29
- readonly connector: Connector;
30
- /**
31
- * Serve the pages' JSON API (`/artifacts/_api/*`) and the sandboxed frame
32
- * (`/artifacts/_frame`); null for any other path. The operator UI delegates
33
- * here and serves the page shells itself, so without `ui` there are no
34
- * artifact routes at all.
35
- */
36
- handle(context: RouteContext): Promise<Response | null>;
37
- /** Bind the optional refresh runner after core has built its registry and executor. */
38
- bindRefresh?(runtime: ArtifactRefreshRuntime): void;
18
+ readonly recordChange: typeof recordCatalogChangeActivity;
39
19
  }
40
20
  /** Optional client-token authentication and operator lifecycle. */
41
21
  export interface AccessTokensModule {
42
22
  readonly auth: import("./types.js").InboundAuth;
43
23
  handle(context: RouteContext): Promise<Response | null>;
24
+ /** Token policy for `describeConfig()`; never token material. */
25
+ describe?(): {
26
+ maxActive: number;
27
+ optionSources?: Readonly<Record<string, "default" | "config">>;
28
+ };
44
29
  }
package/dist/node.d.ts CHANGED
@@ -1,7 +1,5 @@
1
1
  import type { Server } from "node:http";
2
2
  import type { Connecta } from "./index.js";
3
- export { fileStorage } from "./storage/file.js";
4
- export type { FileStorageOptions } from "./storage/file.js";
5
3
  export interface ListenOptions {
6
4
  port: number;
7
5
  /** Interface to bind. Defaults to Node's own (all interfaces). */
package/dist/node.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createServer } from "node:http";
2
2
  import { Readable } from "node:stream";
3
3
  import { pipeline } from "node:stream/promises";
4
- export { fileStorage } from "./storage/file.js";
4
+ import { failureRecord, logFailure } from "./operator-record.js";
5
5
  /** 10 MiB. Tool arguments are JSON; nothing legitimate approaches this. */
6
6
  const DEFAULT_MAX_BODY_BYTES = 10 * 1024 * 1024;
7
7
  const BODY_TOO_LARGE = Symbol("body-too-large");
@@ -113,7 +113,7 @@ export function listen(connecta, portOrOptions) {
113
113
  if (controller.signal.aborted)
114
114
  return;
115
115
  // A headless deployment has nothing else to go on; never swallow this.
116
- console.error("[connecta] request failed", error);
116
+ logFailure(console, "request failed", failureRecord({}, error), "error");
117
117
  if (res.headersSent) {
118
118
  // Mid-stream failure: appending an error string here would corrupt
119
119
  // the partial body the client has already begun reading. Cutting
@@ -107,9 +107,7 @@ export function oauthCallbackOutcome(reason, connector, productName = "Connecta"
107
107
  tone: outcome.tone,
108
108
  heading: outcome.heading.replace("{name}", () => name),
109
109
  message: outcome.message.replace("{product}", () => productName),
110
- ...(outcome.fix
111
- ? { fixPrompt: renderFixPrompt(outcome.fix, named?.id) }
112
- : {}),
110
+ ...(outcome.fix ? { fixPrompt: renderFixPrompt(outcome.fix, named?.id) } : {}),
113
111
  };
114
112
  }
115
113
  /** A connector's title when it has a usable one, otherwise its id. */
@@ -0,0 +1,117 @@
1
+ import { oauthConnectKeys } from "./storage/keys.js";
2
+ const HANDOFF_TTL_MS = 15 * 60_000;
3
+ const INTERACTIVE_OAUTH_REQUIRED = "An interactive provider (Clerk or Cloudflare Access) is required to connect OAuth connectors.";
4
+ export function oauthConnectUnavailable(opts) {
5
+ if (!opts.config.auth.some((provider) => provider.interactiveOperator &&
6
+ (provider.uiAuth?.kind === "clerk" || provider.uiAuth?.kind === "cloudflare-access"))) {
7
+ return INTERACTIVE_OAUTH_REQUIRED;
8
+ }
9
+ if (!opts.config.vault?.signOAuthHandoff || !opts.config.vault.verifyOAuthHandoff) {
10
+ return "Connecting OAuth connectors requires a credential vault with a handoff signing key. Configure encryptedCredentialVault(storage, key).";
11
+ }
12
+ return undefined;
13
+ }
14
+ export async function oauthConnectUrl(opts, baseUrl, connectorId, principal, force = false, opaque = false) {
15
+ return (await oauthConnectLink(opts, baseUrl, connectorId, principal, force, opaque)).url;
16
+ }
17
+ export async function oauthConnectLink(opts, baseUrl, connectorId, principal, force = false, opaque = false, after) {
18
+ const unavailable = oauthConnectUnavailable(opts);
19
+ if (unavailable)
20
+ throw new Error(unavailable);
21
+ const connector = opts.registry.getConnector(connectorId);
22
+ if (!connector || !principal)
23
+ throw new Error("OAuth connection requires an initiating user identity.");
24
+ const handoff = {
25
+ connector: connectorId,
26
+ owner: connector.authScope === "personal" ? principal : "shared",
27
+ principal,
28
+ origin: new URL(baseUrl).origin,
29
+ expiresAt: Date.now() + HANDOFF_TTL_MS,
30
+ nonce: crypto.randomUUID(),
31
+ force,
32
+ ...(after ? { after } : {}),
33
+ };
34
+ const vault = opts.config.vault;
35
+ if (opaque && (!vault.seal || !vault.open))
36
+ throw new Error("URL elicitation requires a sealing credential vault.");
37
+ const payload = opaque
38
+ ? `v2:${btoa(await vault.seal(connectorId, "connecta:connect-link:v2", JSON.stringify(handoff)))}`
39
+ : btoa(JSON.stringify(handoff));
40
+ const signature = await opts.config.vault.signOAuthHandoff(payload);
41
+ const url = new URL(`/connect/${connectorId}`, baseUrl);
42
+ url.searchParams.set("h", `${payload}.${signature}`);
43
+ return { url: url.toString(), nonce: handoff.nonce };
44
+ }
45
+ /** A claim precedes OAuth reset; only a completed start can prove recovery. */
46
+ export async function oauthConnectLinkProgress(opts, baseUrl, id, nonce) {
47
+ const stage = await opts.registry.contextFor(id, baseUrl).storage.get(oauthConnectKeys.used(nonce));
48
+ return stage === "used" ? "claimed" : stage === "started" || stage === "failed" ? stage : undefined;
49
+ }
50
+ /** Make outstanding flow links single-use refusals before a completion check. */
51
+ export async function closeOAuthConnectLinks(opts, baseUrl, id, nonces) {
52
+ const storage = opts.registry.contextFor(id, baseUrl).storage;
53
+ for (const nonce of nonces) {
54
+ await storage.compareAndSet(oauthConnectKeys.used(nonce), null, "closed", { ttlSeconds: HANDOFF_TTL_MS / 1000 });
55
+ }
56
+ }
57
+ /** Consuming a round prevents replay from creating sibling retry lineages. */
58
+ export async function claimAuthRetry(opts, baseUrl, id, nonce) {
59
+ return opts.registry
60
+ .contextFor(id, baseUrl)
61
+ .storage.compareAndSet(oauthConnectKeys.retry(nonce), null, "used", { ttlSeconds: HANDOFF_TTL_MS / 1000 });
62
+ }
63
+ export async function verifyOAuthHandoff(opts, baseUrl, connectorId, token) {
64
+ if (!token || token.length > 4096)
65
+ return null;
66
+ const [payload, signature, ...rest] = token.split(".");
67
+ if (!payload || !signature || rest.length)
68
+ return null;
69
+ try {
70
+ if (!(await opts.config.vault?.verifyOAuthHandoff?.(payload, signature)))
71
+ return null;
72
+ const h = JSON.parse(payload.startsWith("v2:")
73
+ ? await opts.config.vault.open(connectorId, "connecta:connect-link:v2", atob(payload.slice(3)))
74
+ : atob(payload));
75
+ const connector = opts.registry.getConnector(connectorId);
76
+ if (!connector ||
77
+ h.connector !== connectorId ||
78
+ typeof h.principal !== "string" ||
79
+ !h.principal ||
80
+ h.owner !== (connector.authScope === "personal" ? h.principal : "shared") ||
81
+ h.origin !== new URL(baseUrl).origin ||
82
+ !Number.isSafeInteger(h.expiresAt) ||
83
+ h.expiresAt <= Date.now() ||
84
+ h.expiresAt > Date.now() + HANDOFF_TTL_MS ||
85
+ typeof h.nonce !== "string" ||
86
+ !h.nonce ||
87
+ typeof h.force !== "boolean")
88
+ return null;
89
+ if (h.after !== undefined &&
90
+ (typeof h.after !== "string" || !/^[a-f0-9-]{36}$/.test(h.after) || h.after === h.nonce || h.force))
91
+ return null;
92
+ if (await opts.registry.contextFor(connectorId, baseUrl).storage.get(oauthConnectKeys.used(h.nonce)))
93
+ return null;
94
+ return h;
95
+ }
96
+ catch {
97
+ return null;
98
+ }
99
+ }
100
+ /** Claim only after browser identity and permissions pass, before starting OAuth. */
101
+ export async function consumeOAuthConnectLink(opts, handoff) {
102
+ if (handoff.expiresAt <= Date.now())
103
+ return false;
104
+ const storage = opts.registry.contextFor(handoff.connector, handoff.origin).storage;
105
+ if (handoff.after && (await storage.get(oauthConnectKeys.used(handoff.after))) !== "started")
106
+ return false;
107
+ const expiry = { ttlSeconds: Math.max(1, Math.ceil((handoff.expiresAt - Date.now()) / 1000)) };
108
+ return storage.compareAndSet(oauthConnectKeys.used(handoff.nonce), null, "used", expiry);
109
+ }
110
+ /** Terminal marker only after the start and every tracked reset have settled. */
111
+ export async function finishOAuthConnectStart(opts, handoff, outcome) {
112
+ if (handoff.expiresAt <= Date.now())
113
+ return false;
114
+ const storage = opts.registry.contextFor(handoff.connector, handoff.origin).storage;
115
+ const expiry = { ttlSeconds: Math.max(1, Math.ceil((handoff.expiresAt - Date.now()) / 1000)) };
116
+ return storage.compareAndSet(oauthConnectKeys.used(handoff.nonce), "used", outcome, expiry);
117
+ }
@@ -27,3 +27,23 @@ export function oauthSealerFor(ctx) {
27
27
  export function inheritOAuthSealer(from, to) {
28
28
  return attachOAuthSealer(to, sealers.get(from));
29
29
  }
30
+ /** A separate HMAC key for browser handoffs, derived from the credential key. */
31
+ export async function deriveOAuthHandoffKey(raw) {
32
+ const material = await crypto.subtle.importKey("raw", new Uint8Array(raw), "HKDF", false, ["deriveKey"]);
33
+ return crypto.subtle.deriveKey({
34
+ name: "HKDF",
35
+ hash: "SHA-256",
36
+ salt: new TextEncoder().encode("connecta:oauth-handoff:v1"),
37
+ info: new TextEncoder().encode("browser-connect"),
38
+ }, material, { name: "HMAC", hash: "SHA-256", length: 256 }, false, ["sign", "verify"]);
39
+ }
40
+ /** A purpose-specific MCP retry key, available only to the host's codec. */
41
+ export async function deriveRequestStateKey(raw) {
42
+ const material = await crypto.subtle.importKey("raw", new Uint8Array(raw), "HKDF", false, ["deriveBits"]);
43
+ return new Uint8Array(await crypto.subtle.deriveBits({
44
+ name: "HKDF",
45
+ hash: "SHA-256",
46
+ salt: new TextEncoder().encode("connecta:request-state:v1"),
47
+ info: new TextEncoder().encode("auth-elicitation"),
48
+ }, material, 256));
49
+ }
@@ -0,0 +1,137 @@
1
+ import { type CallErrorDetails } from "./errors.js";
2
+ import type { ConnectorStatus, Logger } from "./types.js";
3
+ /** Where in connecta's work a failure happened. Closed, so it carries no prose. */
4
+ type FailureStep = "MCP handshake" | "tools/list" | "tools/call" | "resources/read" | "OAuth discovery" | "OAuth client registration" | "OAuth token request" | "OAuth flow" | "handler";
5
+ /**
6
+ * Registered OAuth `error` codes: the pinned SDK's `OAuthErrorCode` values
7
+ * (RFC 6749, 6750, 7591, 8707, 9449) and RFC 7591's software-statement pair.
8
+ */
9
+ export declare const OAUTH_ERROR_CODES: ReadonlySet<string>;
10
+ /** Inbound denial facts, never identity text or provider diagnostics. */
11
+ export type AuthDenialReason = "authentication_failed" | "authorization_header_invalid" | "token_type_mismatch" | "oauth_verification_failed" | "oauth_client_not_allowed" | "oauth_binding_mismatch" | "session_origin_mismatch" | "user_missing" | "email_lookup_failed" | "verified_email_invalid" | "email_domain_denied" | "gate_denied" | "gate_failed" | "pool_not_declared" | "pool_grant_denied" | "pool_grant_threw";
12
+ /**
13
+ * Name `ctor`'s instances `label` in records and fixed text. Call once, at
14
+ * module scope, beside the class. An error whose prototype chain reaches no
15
+ * labelled class has no label; its `name` is never consulted.
16
+ */
17
+ export declare function labelErrorClass(ctor: {
18
+ readonly prototype: object;
19
+ }, label: string): void;
20
+ /**
21
+ * The label of the nearest labelled class on `failure`'s prototype chain.
22
+ * A genuine DOMException is told as `AbortError` or `TimeoutError` when its
23
+ * name is exactly one of those, else `DOMException`.
24
+ */
25
+ export declare function errorLabel(failure: unknown): string | undefined;
26
+ /** Facts connecta derived about a failure where it saw it. */
27
+ export interface FailureFacts {
28
+ step?: FailureStep;
29
+ /** Scheme, host, and port of the request that failed. */
30
+ origin?: string;
31
+ /** The HTTP status the downstream answered with. */
32
+ httpStatus?: number;
33
+ /** A registered OAuth `error` code the server named. */
34
+ oauthError?: string;
35
+ }
36
+ /** Note what connecta knows about `failure` for any record made of it later. */
37
+ export declare function attachFailureFacts<T>(failure: T, facts: FailureFacts): T;
38
+ /** The facts and class label of `from`, kept for `to` (its classified form, say). */
39
+ export declare function carryFailureFacts<T>(from: unknown, to: T): T;
40
+ /** What a failure record may be about. Only these fields are read. */
41
+ export interface FailureSubject {
42
+ reason?: AuthDenialReason;
43
+ /** A registered connector's id. */
44
+ connector?: string;
45
+ /**
46
+ * The catalog entry the call resolved to, never a name alone: a tool is
47
+ * named only when connecta found it in that connector's catalog.
48
+ */
49
+ tool?: {
50
+ readonly name: string;
51
+ } | undefined;
52
+ source?: string;
53
+ /** A status state, for a status record. */
54
+ state?: ConnectorStatus["state"] | "failed";
55
+ /** Whether an OAuth start continued or restarted authorization. */
56
+ mode?: "continue" | "restart";
57
+ attempts?: number;
58
+ durationMs?: number;
59
+ }
60
+ declare const recordBrand: unique symbol;
61
+ /** One failure as an operator may read it. Only `failureRecord` makes one. */
62
+ export interface FailureRecord {
63
+ readonly [recordBrand]: true;
64
+ readonly reason?: AuthDenialReason;
65
+ readonly connector?: string;
66
+ readonly tool?: string;
67
+ readonly source?: string;
68
+ readonly state?: string;
69
+ readonly mode?: string;
70
+ readonly attempts?: number;
71
+ readonly durationMs?: number;
72
+ readonly step?: string;
73
+ readonly origin?: string;
74
+ readonly httpStatus?: number;
75
+ readonly oauthError?: string;
76
+ /** Classification code, such as `unavailable` or `connector_call_failed`. */
77
+ readonly code?: string;
78
+ readonly retryable?: boolean;
79
+ /** The error's class label, by identity, such as `TypeError`. */
80
+ readonly errorClass?: string;
81
+ /** A network errno or `timeout`. */
82
+ readonly errno?: string;
83
+ }
84
+ /**
85
+ * How an operator record names a catalog entry: its name when it fits MCP's
86
+ * tool-name grammar, else `<withheld>`. Activity rows and the operator page's
87
+ * catalog use it too, so a withheld name appears nowhere an operator reads.
88
+ */
89
+ export declare function recordedToolName(entry: {
90
+ readonly name: string;
91
+ }): string;
92
+ /** Vouch for `details` as a classification connecta itself computed. */
93
+ export declare function classifiedFailure<T extends CallErrorDetails>(details: T): T;
94
+ /**
95
+ * The record of a failure: the subject's listed fields, each checked; the
96
+ * facts attached to the failure; its class label; and the typed
97
+ * classification it carries (a `ConnectorCallError`'s code, retryability,
98
+ * origin, and errno, or those of a `CallErrorDetails` registered with
99
+ * `classifiedFailure`). Never its
100
+ * text, and nothing else from `subject`.
101
+ */
102
+ export declare function failureRecord(subject: FailureSubject, failure?: unknown): FailureRecord;
103
+ /**
104
+ * The events an operator log reports failures as. Closed: a new one is a
105
+ * reviewed line here, never a string assembled at the call site.
106
+ */
107
+ export type FailureEvent = "negotiation cache read failed" | "negotiation cache write failed" | "connectorAccess grant is unreachable" | "call failed" | "input schema unusable; arguments are not validated" | "catalog read failed" | "catalog persistence failed" | "catalog invalidation failed" | "catalog refresh observation failed" | "catalog refresh failed; serving stale catalog" | "deferred catalog refresh failed" | "deferred catalog refresh could not attach to the runtime" | "catalog drift check failed" | "schema digest check failed; serving digested reviews as writes" | "credential shape read failed" | "credential test failed" | "credential test threw" | "session termination refused or failed; the downstream session may remain until its provider timeout" | "activity record failed" | "catalog drift record failed" | "activity read failed" | "OAuth start failed" | "OAuth disconnect failed" | "OAuth callback verifyState threw; no authorization code was exchanged" | "OAuth callback handoff could not be consumed; no authorization code was exchanged" | "MCP pool request denied" | "OAuth error callback consent could not be consumed; no authorization code was exchanged" | "MCP handler error" | "operator status" | "result paging unavailable" | "request failed" | "Clerk request denied" | "Clerk authentication failed";
108
+ /**
109
+ * Log one failure as `[connecta] <event>` and its record. A record
110
+ * `failureRecord` did not build is replaced, whatever its shape, by a fixed
111
+ * `{ record: "<rejected>" }`. Never throws: it runs on failure paths, and a
112
+ * logger that throws is contained here.
113
+ */
114
+ export declare function logFailure(logger: Pick<Logger, "info" | "warn" | "error">, event: FailureEvent, record: FailureRecord, level?: "info" | "warn" | "error"): void;
115
+ /**
116
+ * A failure in connecta's words, from its record alone:
117
+ * `Connector "<id>" <step> with <origin> failed with HTTP <status> (<class>, <code>, <errno>).`
118
+ * Used for status messages, and for agent-facing text where the error's own
119
+ * may not pass.
120
+ */
121
+ export declare function describeFailure(connectorId: string, failure: unknown): string;
122
+ /** A status whose message connecta wrote, such as an auth_required notice. */
123
+ export declare function ownStatus<T extends ConnectorStatus>(status: T): T;
124
+ /** An error status described from `failure`'s record, remembering the failure. */
125
+ export declare function failureStatus(connectorId: string, failure: unknown): ConnectorStatus;
126
+ /**
127
+ * `status` rebuilt from what connecta approved. A status connecta wrote
128
+ * (`ownStatus`, `failureStatus`) is rebuilt from the snapshot taken then,
129
+ * whatever has since been assigned to it. Any other status contributes its
130
+ * typed state alone: a plugin `status()` seam returns its author's text,
131
+ * which can quote a downstream, and connecta cannot vouch for it whatever it
132
+ * looks like.
133
+ */
134
+ export declare function boundedStatus(status: ConnectorStatus): ConnectorStatus;
135
+ /** The failure connecta saw behind a status it described, if any. */
136
+ export declare function statusFailure(status: ConnectorStatus): unknown;
137
+ export {};