@zackbart/connecta 0.28.0 → 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 (320) hide show
  1. package/CHANGELOG.md +961 -5
  2. package/README.md +62 -39
  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 +1632 -1509
  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 +526 -0
  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 +220 -0
  52. package/dist/connectors/api-connector.js +214 -0
  53. package/dist/connectors/api.d.ts +8 -71
  54. package/dist/connectors/api.js +13 -99
  55. package/dist/connectors/guarded-fetch.d.ts +144 -0
  56. package/dist/connectors/guarded-fetch.js +108 -25
  57. package/dist/connectors/negotiation-cache.js +46 -0
  58. package/dist/connectors/option-shapes.js +27 -0
  59. package/dist/connectors/remote-mcp.d.ts +30 -4
  60. package/dist/connectors/remote-mcp.js +1780 -351
  61. package/dist/connectors/resource-uri.js +335 -0
  62. package/dist/credential-contract.d.ts +10 -0
  63. package/dist/credential-rules.js +1 -3
  64. package/dist/credential-url.js +108 -0
  65. package/dist/credentials.d.ts +6 -1
  66. package/dist/credentials.js +27 -18
  67. package/dist/d1.d.ts +130 -0
  68. package/dist/d1.js +290 -0
  69. package/dist/describe-config.d.ts +165 -0
  70. package/dist/describe-config.js +315 -0
  71. package/dist/described.js +106 -0
  72. package/dist/downstream-input-context.js +119 -0
  73. package/dist/downstream-input.js +323 -0
  74. package/dist/errors.d.ts +97 -20
  75. package/dist/errors.js +158 -80
  76. package/dist/execute.js +453 -305
  77. package/dist/executor-admission.d.ts +1 -0
  78. package/dist/executor-admission.js +11 -15
  79. package/dist/executor-contract.d.ts +20 -0
  80. package/dist/executor-contract.js +48 -0
  81. package/dist/executor-result.js +24 -9
  82. package/dist/executors/quickjs-child.js +3 -4
  83. package/dist/executors/quickjs-runtime.js +147 -59
  84. package/dist/executors/quickjs.js +29 -35
  85. package/dist/favicon.js +1 -2
  86. package/dist/fix-prompt.js +1 -3
  87. package/dist/guest-runtime.js +188 -0
  88. package/dist/guest-types.d.ts +176 -0
  89. package/dist/guest-types.js +1 -0
  90. package/dist/html-security.js +24 -0
  91. package/dist/identity.js +1 -3
  92. package/dist/inbound-credential.js +52 -0
  93. package/dist/index.d.ts +25 -257
  94. package/dist/index.js +65 -478
  95. package/dist/invocation-auth.js +69 -0
  96. package/dist/invocation.js +230 -73
  97. package/dist/json-schema.d.ts +1 -1
  98. package/dist/mcp-client-context.js +35 -0
  99. package/dist/mcp-result.js +11 -7
  100. package/dist/meta-output.js +76 -0
  101. package/dist/meta-tool-names.d.ts +2 -0
  102. package/dist/meta-tool-names.js +3 -0
  103. package/dist/meta-tools.js +285 -209
  104. package/dist/module-contracts.d.ts +8 -23
  105. package/dist/node.d.ts +0 -2
  106. package/dist/node.js +2 -2
  107. package/dist/oauth-callback-outcome.js +1 -3
  108. package/dist/oauth-handoff.js +117 -0
  109. package/dist/oauth-partition.js +17 -0
  110. package/dist/oauth-sealing.js +20 -0
  111. package/dist/operator-record.d.ts +137 -0
  112. package/dist/operator-record.js +420 -0
  113. package/dist/operator-ui/assets.js +31 -0
  114. package/dist/operator-ui/contract.d.ts +72 -0
  115. package/dist/operator-ui/contract.js +1 -0
  116. package/dist/operator-ui/fix-prompts.d.ts +18 -0
  117. package/dist/operator-ui/generated.js +5 -4
  118. package/dist/operator-ui/model.d.ts +12 -87
  119. package/dist/operator-ui/model.js +2 -9
  120. package/dist/operator-ui/setup-commands.js +1 -3
  121. package/dist/operator-ui/view.d.ts +323 -0
  122. package/dist/operator-ui/view.js +62 -189
  123. package/dist/page-styles.js +2 -3
  124. package/dist/program-source.js +14 -2
  125. package/dist/{exempt-writes.js → program-writes.js} +55 -18
  126. package/dist/provider.d.ts +142 -0
  127. package/dist/provider.js +187 -0
  128. package/dist/providers/_shared/google/result-size.js +57 -0
  129. package/dist/providers/_shared/google/service-account.d.ts +58 -0
  130. package/dist/providers/_shared/google/service-account.js +500 -0
  131. package/dist/providers/_shared/google/workspace.d.ts +276 -0
  132. package/dist/providers/_shared/google/workspace.js +518 -0
  133. package/dist/providers/basecamp/index.d.ts +43 -0
  134. package/dist/providers/basecamp/index.js +997 -0
  135. package/dist/providers/basecamp/skill.generated.js +9 -0
  136. package/dist/providers/breeze/index.d.ts +34 -0
  137. package/dist/providers/breeze/index.js +1446 -0
  138. package/dist/providers/breeze/skill.generated.js +9 -0
  139. package/dist/providers/ccb/index.d.ts +103 -0
  140. package/dist/providers/ccb/index.js +1725 -0
  141. package/dist/providers/ccb/skill.generated.js +9 -0
  142. package/dist/providers/{cloudflare.d.ts → cloudflare/index.d.ts} +8 -7
  143. package/dist/providers/{cloudflare.js → cloudflare/index.js} +171 -738
  144. package/dist/providers/cloudflare/skill.generated.js +10 -0
  145. package/dist/providers/docs/index.d.ts +54 -0
  146. package/dist/providers/docs/index.js +1237 -0
  147. package/dist/providers/docs/skill.generated.js +9 -0
  148. package/dist/providers/drive/index.d.ts +53 -0
  149. package/dist/providers/drive/index.js +1523 -0
  150. package/dist/providers/drive/skill.generated.js +9 -0
  151. package/dist/providers/forms/index.d.ts +54 -0
  152. package/dist/providers/forms/index.js +1217 -0
  153. package/dist/providers/forms/skill.generated.js +11 -0
  154. package/dist/providers/github/auth.d.ts +32 -0
  155. package/dist/providers/github/auth.js +361 -0
  156. package/dist/providers/github/hosted.js +218 -0
  157. package/dist/providers/github/index.d.ts +13 -0
  158. package/dist/providers/github/index.js +140 -0
  159. package/dist/providers/github/key.js +100 -0
  160. package/dist/providers/github/rest.js +321 -0
  161. package/dist/providers/github/scope.d.ts +29 -0
  162. package/dist/providers/github/scope.js +124 -0
  163. package/dist/providers/github/skill.generated.js +8 -0
  164. package/dist/providers/gmail/index.d.ts +51 -0
  165. package/dist/providers/gmail/index.js +1436 -0
  166. package/dist/providers/gmail/skill.generated.js +9 -0
  167. package/dist/providers/{linear.d.ts → linear/index.d.ts} +11 -14
  168. package/dist/providers/linear/index.js +352 -0
  169. package/dist/providers/linear/skill.generated.js +8 -0
  170. package/dist/providers/{mixpanel.d.ts → mixpanel/index.d.ts} +5 -10
  171. package/dist/providers/mixpanel/index.js +416 -0
  172. package/dist/providers/mixpanel/skill.generated.js +9 -0
  173. package/dist/providers/{notion.d.ts → notion/index.d.ts} +8 -8
  174. package/dist/providers/{notion.js → notion/index.js} +226 -239
  175. package/dist/providers/notion/skill.generated.js +10 -0
  176. package/dist/providers/overflow/index.d.ts +60 -0
  177. package/dist/providers/overflow/index.js +1535 -0
  178. package/dist/providers/overflow/skill.generated.js +8 -0
  179. package/dist/providers/planning-center/index.d.ts +59 -0
  180. package/dist/providers/planning-center/index.js +2199 -0
  181. package/dist/providers/planning-center/skill.generated.js +10 -0
  182. package/dist/providers/{revenuecat.d.ts → revenuecat/index.d.ts} +5 -10
  183. package/dist/providers/revenuecat/index.js +557 -0
  184. package/dist/providers/revenuecat/skill.generated.js +11 -0
  185. package/dist/providers/sheets/index.d.ts +54 -0
  186. package/dist/providers/sheets/index.js +1237 -0
  187. package/dist/providers/sheets/skill.generated.js +10 -0
  188. package/dist/providers/slides/index.d.ts +53 -0
  189. package/dist/providers/slides/index.js +2645 -0
  190. package/dist/providers/slides/skill.generated.js +9 -0
  191. package/dist/providers/{stripe.d.ts → stripe/index.d.ts} +5 -12
  192. package/dist/providers/stripe/index.js +266 -0
  193. package/dist/providers/stripe/skill.generated.js +11 -0
  194. package/dist/providers/tithely/index.d.ts +49 -0
  195. package/dist/providers/tithely/index.js +1003 -0
  196. package/dist/providers/tithely/skill.generated.js +8 -0
  197. package/dist/providers/{vercel.d.ts → vercel/index.d.ts} +8 -8
  198. package/dist/providers/vercel/index.js +1038 -0
  199. package/dist/providers/vercel/mcp-ownership.js +212 -0
  200. package/dist/providers/vercel/skill.generated.js +11 -0
  201. package/dist/registry.d.ts +108 -171
  202. package/dist/registry.js +513 -925
  203. package/dist/request-state.js +21 -0
  204. package/dist/resource-read.js +100 -0
  205. package/dist/result-shapes.js +17 -49
  206. package/dist/retired-toolkits.js +1 -1
  207. package/dist/routes/access-tokens.js +3 -6
  208. package/dist/routes/activity-context.js +21 -0
  209. package/dist/routes/activity-disclosure.js +40 -0
  210. package/dist/routes/activity.js +59 -21
  211. package/dist/routes/actor-labels.js +3 -50
  212. package/dist/routes/connect.js +221 -0
  213. package/dist/routes/credentials.js +15 -27
  214. package/dist/routes/mcp.js +356 -130
  215. package/dist/routes/oauth-client-metadata.js +15 -0
  216. package/dist/routes/oauth-management.js +45 -107
  217. package/dist/routes/oauth.js +136 -57
  218. package/dist/routes/operator.js +3 -3
  219. package/dist/routes/shared.d.ts +25 -64
  220. package/dist/routes/shared.js +160 -80
  221. package/dist/routes/skills.js +30 -0
  222. package/dist/routes/ui-config.js +225 -0
  223. package/dist/routes/ui-data.js +40 -35
  224. package/dist/routes/ui.js +66 -56
  225. package/dist/runtime/admission.js +4 -2
  226. package/dist/runtime/call-admission.js +3 -1
  227. package/dist/runtime/connector-scope.js +1 -4
  228. package/dist/runtime/run.js +10 -39
  229. package/dist/runtime/services.js +12 -117
  230. package/dist/runtime/shared-read.js +1 -3
  231. package/dist/runtime/storage.js +8 -1
  232. package/dist/sent-secrets.d.ts +52 -0
  233. package/dist/sent-secrets.js +474 -0
  234. package/dist/server.js +28 -71
  235. package/dist/skills.js +406 -174
  236. package/dist/sqlite.d.ts +48 -0
  237. package/dist/sqlite.js +166 -0
  238. package/dist/storage/keys.js +428 -0
  239. package/dist/storage/memory.js +7 -0
  240. package/dist/storage/sql.d.ts +66 -0
  241. package/dist/storage/sql.js +534 -0
  242. package/dist/timeout.js +4 -2
  243. package/dist/tool-name.js +17 -0
  244. package/dist/tool-safety.d.ts +9 -35
  245. package/dist/tool-safety.js +20 -40
  246. package/dist/types.d.ts +320 -54
  247. package/dist/types.js +0 -1
  248. package/dist/ui.d.ts +7 -19
  249. package/dist/ui.js +76 -90
  250. package/dist/url-safety.js +3 -6
  251. package/dist/usage-guide.js +137 -0
  252. package/dist/validate.d.ts +7 -0
  253. package/dist/validate.js +309 -47
  254. package/dist/version.d.ts +1 -7
  255. package/dist/version.js +3 -7
  256. package/dist/worker.d.ts +11 -0
  257. package/dist/worker.js +434 -0
  258. package/documentation/architecture.md +665 -230
  259. package/documentation/auth.md +962 -458
  260. package/documentation/code-mode.md +501 -312
  261. package/documentation/meta-tools.md +449 -200
  262. package/documentation/operator-tests.md +93 -0
  263. package/documentation/provider-migration-0.29.md +60 -0
  264. package/examples/worker/AGENTS.md +34 -12
  265. package/examples/worker/README.md +286 -255
  266. package/examples/worker/kv-to-d1.wrangler.jsonc +26 -0
  267. package/examples/worker/scripts/copy-kv-to-d1.mjs +162 -0
  268. package/examples/worker/src/connecta.config.ts +151 -0
  269. package/examples/worker/src/index.ts +14 -197
  270. package/examples/worker/wrangler.jsonc +15 -35
  271. package/package.json +125 -35
  272. package/templates/node/.dockerignore +1 -2
  273. package/templates/node/.env.example +20 -21
  274. package/templates/node/AGENTS.md +21 -17
  275. package/templates/node/Dockerfile +3 -5
  276. package/templates/node/README.md +133 -58
  277. package/templates/node/docker-compose.yml +11 -12
  278. package/templates/node/package.json +4 -2
  279. package/templates/node/src/connecta.config.ts +119 -0
  280. package/templates/node/src/index.ts +9 -154
  281. package/templates/node/src/provision-token.ts +17 -0
  282. package/AGENTS.md +0 -170
  283. package/dist/artifacts/connector.d.ts +0 -58
  284. package/dist/artifacts/connector.js +0 -730
  285. package/dist/artifacts/document.js +0 -86
  286. package/dist/artifacts/guide.js +0 -140
  287. package/dist/artifacts/html-scan.js +0 -329
  288. package/dist/artifacts/json.js +0 -78
  289. package/dist/artifacts/kv-store.d.ts +0 -20
  290. package/dist/artifacts/kv-store.js +0 -140
  291. package/dist/artifacts/markdown.js +0 -478
  292. package/dist/artifacts/module.d.ts +0 -37
  293. package/dist/artifacts/module.js +0 -66
  294. package/dist/artifacts/operations.d.ts +0 -197
  295. package/dist/artifacts/operations.js +0 -1002
  296. package/dist/artifacts/refresh.d.ts +0 -55
  297. package/dist/artifacts/refresh.js +0 -236
  298. package/dist/artifacts/routes.js +0 -206
  299. package/dist/artifacts/types.d.ts +0 -214
  300. package/dist/artifacts/types.js +0 -22
  301. package/dist/artifacts/validate.d.ts +0 -62
  302. package/dist/artifacts/validate.js +0 -651
  303. package/dist/artifacts.d.ts +0 -6
  304. package/dist/artifacts.js +0 -9
  305. package/dist/auth/bearer.d.ts +0 -11
  306. package/dist/auth/bearer.js +0 -50
  307. package/dist/providers/linear.js +0 -217
  308. package/dist/providers/mixpanel.js +0 -247
  309. package/dist/providers/revenuecat.js +0 -317
  310. package/dist/providers/stripe.js +0 -253
  311. package/dist/providers/vercel.js +0 -1253
  312. package/dist/storage/file.d.ts +0 -15
  313. package/dist/storage/file.js +0 -455
  314. package/ethos.md +0 -93
  315. package/examples/worker/src/cloudflare-kv.ts +0 -43
  316. package/examples/worker/src/d1-activity-row.ts +0 -155
  317. package/examples/worker/src/d1-activity.ts +0 -147
  318. package/examples/worker/src/d1-storage.ts +0 -138
  319. package/examples/worker/src/r2-artifact-blobs.ts +0 -29
  320. package/templates/node/src/file-activity.ts +0 -182
package/README.md CHANGED
@@ -10,7 +10,7 @@ writing code instead of loading a thousand tool definitions.
10
10
  You ask your agent a question that touches a service — Linear, Stripe, an
11
11
  internal API, anything you have connected. Here is what happens:
12
12
 
13
- 1. The agent talks to one endpoint, yours, and sees seven tools. Always seven,
13
+ 1. The agent talks to one endpoint, yours, and sees six tools. Always six,
14
14
  no matter how many services sit behind it.
15
15
  2. It writes a short JavaScript program. Connecta runs it in a sandbox next to
16
16
  your integrations. The program can search for tools, call them, chain the
@@ -18,10 +18,9 @@ internal API, anything you have connected. Here is what happens:
18
18
  3. Only the answer comes back into the agent's context — not raw pages of
19
19
  API output.
20
20
  4. If the agent wants to change something — create, update, delete — it
21
- cannot do that from a program, unless your config exempts that tool. The
22
- program refuses the write before anything is sent and hands back the exact
23
- call; the agent makes it through `call_destructive_tool`, one visible call
24
- your MCP client can put in front of you first.
21
+ can do that from a program in a `trusted` pool. A `read-only` pool
22
+ routes each write through `call_destructive_tool`. The host controls approval; trusted endpoints annotate `execute_code` as a
23
+ write. Pool trust defaults to `read-only`.
25
24
 
26
25
  Credentials never leave the server. The program never sees them, and neither
27
26
  does the agent.
@@ -30,8 +29,8 @@ does the agent.
30
29
  flowchart TB
31
30
  Client["Your MCP client<br/>Claude, Cursor, …"]
32
31
 
33
- subgraph Connecta["Connecta — one endpoint, seven tools, your credentials"]
34
- Sandbox["execute_code<br/>the agent's program runs here<br/>read-only tools, plus writes<br/>your config exempts"]
32
+ subgraph Connecta["Connecta — one endpoint, six tools, your credentials"]
33
+ Sandbox["execute_code<br/>the agent's program runs here<br/>reads, plus writes<br/>in trusted pools"]
35
34
  Explicit["call_destructive_tool<br/>one visible call per write<br/>your client can ask you first"]
36
35
  end
37
36
 
@@ -47,7 +46,7 @@ This is the kind of thing the agent writes, not you:
47
46
 
48
47
  ```js
49
48
  async () => {
50
- const { nodes } = await connecta.call("tracker.list_issues", { state: "started" });
49
+ const { data: { nodes } } = await connecta.call("tracker.list_issues", { state: "started" });
51
50
  const byOwner = {};
52
51
  for (const issue of nodes) {
53
52
  (byOwner[issue.assignee?.name ?? "unassigned"] ??= []).push(issue.identifier);
@@ -64,11 +63,19 @@ Fifty issues in, one small object out. Your context window notices.
64
63
  services in a config file; your client never changes.
65
64
  - **Wrap any HTTP API by hand.** A few lines per tool. No OpenAPI conversion —
66
65
  generated tool sprawl is the problem, not the fix.
67
- - **Use maintained connections** for Cloudflare, Linear, Mixpanel, Notion,
68
- RevenueCat, Stripe, and Vercel: known endpoints, auth defaults, and vetted
69
- read/write classifications, imported one at a time. Cloudflare, Notion, and
70
- Vercel each let the deployment choose their hand-written API interface or
71
- official hosted MCP.
66
+ - **Use maintained connections** for <!-- providers:start -->Basecamp, Breeze ChMS, Church Community Builder, Cloudflare, GitHub, Gmail, Google Docs, Google Drive, Google Forms, Google Sheets, Google Slides, Linear, Mixpanel, Notion, Overflow, Planning Center, RevenueCat, Stripe, Tithe.ly, and Vercel<!-- providers:end -->: known endpoints, auth defaults, and
67
+ vetted read/write classifications, imported one at a time.
68
+ Cloudflare, Notion, and Vercel each let the deployment choose their
69
+ hand-written API interface or official hosted MCP. Planning Center, Overflow,
70
+ Tithe.ly, Breeze, and Church Community Builder are hand-written over their
71
+ whole APIs, with every money-moving write behind approval. Gmail reads mail
72
+ and writes drafts — never sends — and Google Drive reads, writes, and shares
73
+ files — never deletes one for good — as each signed-in Workspace user through
74
+ domain-wide delegation, with no per-user consent step; Google Docs reads and
75
+ edits documents, and Google Sheets reads and writes spreadsheets, the same
76
+ way. Google Slides reads, creates, edits, and comments on each user's decks the
77
+ same way.
78
+ Google Forms reads and edits forms and reads their responses the same way.
72
79
  - **Let the agent work in code.** Search, chain, filter, join, and reduce
73
80
  inside the sandbox instead of round-tripping every call through the model.
74
81
  - **Teach undeclared result shapes by using them.** Successful read-only calls
@@ -80,45 +87,61 @@ Fifty issues in, one small object out. Your context window notices.
80
87
  notice also points the agent to reduce or search the result inside a
81
88
  program. Discovery can show compact schemas, exact JSON Schema, or a
82
89
  TypeScript signature to read while writing JavaScript.
83
- - **Keep writes deliberate.** Only tools marked read-only run in a program.
84
- Every other write is a separate, visible `call_destructive_tool` call your
85
- client can gate, so the host's permission prompt is the one approval there
86
- is. Config — and only config — can exempt a cheap, reversible write from
87
- asking, per tool or per connector, and then a program may make it.
90
+ - **Keep writes deliberate.** The registry classifies tools as reads or writes.
91
+ Read-only pools route writes through `call_destructive_tool`; trusted pools
92
+ let programs write and annotate `execute_code` as a write. The host controls
93
+ approval. Exact deployment classification overrides beat provider review
94
+ and downstream annotations.
88
95
  - **Run it on Node or Cloudflare Workers.** The core is shared; each deployment
89
- supplies its platform's executor and storage. The Node template also runs
90
- unchanged in Docker.
96
+ supplies its platform's executor and one store: a D1 database on Workers
97
+ (`@zackbart/connecta/d1`), a SQLite file on Node (`@zackbart/connecta/sqlite`).
98
+ The Node template also runs unchanged in Docker.
91
99
 
92
100
  Deployments explicitly compose optional features: `operatorUi()` from
93
101
  `@zackbart/connecta/ui`, `encryptedCredentialVault()` from `/credentials`,
94
- `activityHistory()` from `/activity`, `artifacts()` from `/artifacts`, and
102
+ `activityHistory()` from `/activity`, and
95
103
  inbound authentication adapters from `/auth/*`. Omit a module and its implementation does no runtime work. Core
96
104
  keeps connector discovery, execution, invocation, and enforcement together.
97
-
98
- The optional UI shows each person's connections and effective permissions.
99
- Authentication controls live inside each connection, with optional activity
100
- history. The configured connection list loads before downstream checks finish;
101
- a slow provider does not hold up the page. Connector selection and access rules
102
- remain in deployment code. It also explains which tools can run in programs or
103
- need approval, offers a fixed repair prompt for classified failures, and shows
104
- client setup commands for the endpoints the signed-in person can use.
105
+ Both deployment shapes write configuration as `defineConfig((env) => …)`, with
106
+ each optional module a type-checked expression the environment switches on.
107
+ `createConnecta` validates it against one schema — an unknown option or an
108
+ unusable value refuses to boot — and `connecta.describeConfig()` returns a
109
+ secret-free snapshot of what the deployment runs with. With `operatorUi()` enabled,
110
+ `GET /ui/api/config` returns that snapshot scoped to the caller, live connector
111
+ facts, and the caller's grants and permissions. `CONNECTA_TOKEN=<bearer> connecta
112
+ doctor --config --url https://connecta.example` prints only the scoped snapshot
113
+ as JSON, using the same serializer. It does not run the diagnostic program.
114
+
115
+ The optional UI has Overview, Connectors, Tools, Access, Activity and
116
+ Config pages. Connector detail separates configuration, schemas, authentication,
117
+ activity and diagnostics into tabs. Overview shows health, attention items and
118
+ endpoint setup in a drawer; Config marks each snapshot value as default or
119
+ configured. Catalog descriptions and schemas render as text. Activity filters
120
+ stay in the URL and calls group by recorded request id. Authentication, history,
121
+ and token controls use the signed-in person's existing permissions.
122
+ Connector selection and access rules remain in deployment code.
105
123
 
106
124
  One deployment may serve several authenticated people inside the same tenant.
107
- Cloudflare Access supplies Worker identity; Node can use Clerk or the optional
108
- configured bearer adapter. Connecta owns no accounts or groups and issues no
109
- client access tokens. Shared-credential administration and personal connection
125
+ Cloudflare Access supplies Worker identity; Node uses Clerk for human auth. Machine clients use connecta-issued `cta_`
126
+ access tokens. Static bearer secrets are retired. Connecta owns no accounts or groups. Shared-credential administration and personal connection
110
127
  setup require separate explicit permissions, both denied by default. See
111
128
  [inbound auth](./documentation/auth.md#principals-visibility-and-operators).
129
+ Clerk deployments enable `aud_claim_enabled` and use resource-bound JWT or
130
+ opaque OAuth tokens by default. An explicit `allowedOAuthClientIds` list is a
131
+ fallback for unbound tokens from dedicated clients. Clerk session
132
+ tokens authenticate operator routes only; [Clerk auth](./documentation/auth.md#clerk-oauth-tokens-and-operator-sessions)
133
+ describes endpoint audience validation and the configuration migration.
112
134
 
113
135
  Connecta is not a platform, a marketplace, a policy engine, or a multi-tenant
114
- service. Those are decisions, and the [ethos](./ethos.md) records each one
115
- and why.
136
+ service. The [principles](https://github.com/zackbart/connecta/blob/main/PRINCIPLES.md)
137
+ state the goals and invariants; [decision records](https://github.com/zackbart/connecta/tree/main/decisions)
138
+ explain past choices.
116
139
 
117
140
  ## Getting started
118
141
 
119
- Setup is written for an agent. Point yours at [`AGENTS.md`](./AGENTS.md) and
142
+ Setup is written for an agent. Point yours at [`AGENTS.md`](https://github.com/zackbart/connecta/blob/main/AGENTS.md) and
120
143
  ask it to set up a Connecta deployment; the
121
- [documentation](./documentation/) covers the architecture, the seven tools,
144
+ [documentation](./documentation/) covers the architecture, the six tools,
122
145
  code mode, and inbound auth if you want to go deeper. When upgrading an
123
146
  existing deployment, each [changelog](./CHANGELOG.md) release opens with what
124
147
  breaks and what a deployment can ignore.
@@ -136,6 +159,6 @@ Upgrading from v0.23 does not require rotating managed `cta_…` tokens. Import
136
159
  `accessTokens: true` with `accessTokens: accessTokens(storage)`, and keep the
137
160
  same persistent storage namespace and identity/tool/pool grant rules. Enable
138
161
  `identity.accessTokenManagement` only for the interactive operators who should
139
- manage tokens. New issuance needs storage with atomic `compareAndSet`; older
140
- storage adapters can still verify existing tokens. See the package's
141
- `documentation/auth.md` for the migration and storage requirements.
162
+ manage tokens. Records written by v0.23 and later move with the store: a 0.28
163
+ JSON state file migrates with `connecta migrate-state`, and a D1 table is
164
+ read as is. See the package's `documentation/auth.md` for the migration.
package/bin/connecta.mjs CHANGED
@@ -1,18 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import {
4
- copyFile,
5
- cp,
6
- lstat,
7
- mkdtemp,
8
- readFile,
9
- rename,
10
- rm,
11
- symlink,
12
- writeFile,
13
- } from "node:fs/promises";
3
+ import { copyFile, cp, lstat, mkdtemp, readFile, rename, rm, symlink, writeFile } from "node:fs/promises";
4
+ import { CONNECTA_VERSION } from "./version.mjs";
5
+ import { META_TOOL_NAMES } from "./meta-tool-names.mjs";
14
6
  import { basename, dirname, join, resolve } from "node:path";
15
- import { fileURLToPath } from "node:url";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
16
8
 
17
9
  const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
18
10
  const [command, ...args] = process.argv.slice(2);
@@ -27,7 +19,8 @@ function shellCd(path) {
27
19
  function usage() {
28
20
  console.log(`Usage:
29
21
  connecta init [directory]
30
- CONNECTA_TOKEN=<bearer> connecta doctor [--url http://localhost:8787]
22
+ connecta migrate-state <state.json> <connecta.sqlite>
23
+ CONNECTA_TOKEN=<bearer> connecta doctor [--config] [--url http://localhost:8787]
31
24
  CF_ACCESS_CLIENT_ID=<id> CF_ACCESS_CLIENT_SECRET=<secret> connecta doctor --url https://worker.example`);
32
25
  }
33
26
 
@@ -44,18 +37,13 @@ async function init() {
44
37
  await lstat(target);
45
38
  throw new Error(`Refusing to overwrite existing path: ${target}`);
46
39
  } catch (error) {
47
- const code =
48
- error && typeof error === "object" && "code" in error
49
- ? String(error.code)
50
- : "";
40
+ const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
51
41
  if (code !== "ENOENT") throw error;
52
42
  }
53
43
 
54
44
  // Build beside the destination, then rename once complete. A failed copy or
55
45
  // rewrite leaves no partial destination that blocks a clean retry.
56
- let stage = await mkdtemp(
57
- join(parent, `.${basename(target)}.connecta-init-`),
58
- );
46
+ let stage = await mkdtemp(join(parent, `.${basename(target)}.connecta-init-`));
59
47
  try {
60
48
  await cp(join(packageRoot, "templates", "node"), stage, {
61
49
  recursive: true,
@@ -63,10 +51,7 @@ async function init() {
63
51
 
64
52
  // npm excludes .gitignore files and symlinks from packed dependencies.
65
53
  // Restore both conventions explicitly in the generated project.
66
- await writeFile(
67
- join(stage, ".gitignore"),
68
- ".connecta-state.json\n.connecta-activity.jsonl\n.env\nnode_modules/\n",
69
- );
54
+ await writeFile(join(stage, ".gitignore"), ".connecta.sqlite*\n.env\nnode_modules/\n");
70
55
  await rm(join(stage, "CLAUDE.md"), { force: true });
71
56
  try {
72
57
  await symlink("AGENTS.md", join(stage, "CLAUDE.md"));
@@ -76,18 +61,11 @@ async function init() {
76
61
  await copyFile(join(stage, "AGENTS.md"), join(stage, "CLAUDE.md"));
77
62
  }
78
63
 
79
- const rootPackage = JSON.parse(
80
- await readFile(join(packageRoot, "package.json"), "utf8"),
81
- );
64
+ const rootPackage = JSON.parse(await readFile(join(packageRoot, "package.json"), "utf8"));
82
65
  const deploymentPath = join(stage, "package.json");
83
- const deploymentPackage = JSON.parse(
84
- await readFile(deploymentPath, "utf8"),
85
- );
66
+ const deploymentPackage = JSON.parse(await readFile(deploymentPath, "utf8"));
86
67
  deploymentPackage.dependencies["@zackbart/connecta"] = rootPackage.version;
87
- await writeFile(
88
- deploymentPath,
89
- JSON.stringify(deploymentPackage, null, 2) + "\n",
90
- );
68
+ await writeFile(deploymentPath, JSON.stringify(deploymentPackage, null, 2) + "\n");
91
69
 
92
70
  await rename(stage, target);
93
71
  stage = "";
@@ -99,7 +77,10 @@ async function init() {
99
77
  console.log("Next:");
100
78
  console.log(` ${shellCd(target)}`);
101
79
  console.log(" npm install");
102
- console.log(" CONNECTA_TOKEN=dev-token npm start");
80
+ console.log(' npm run --silent provision-token -- "local-machine"');
81
+ console.log(" # Save the returned cta_ token privately for your MCP client and doctor.");
82
+ console.log(" npm start");
83
+ console.log(" # README.md covers database paths and Docker provisioning.");
103
84
  }
104
85
 
105
86
  function option(name, fallback) {
@@ -135,7 +116,7 @@ async function doctorFetch(url, init = {}) {
135
116
  const response = await fetch(url, {
136
117
  ...init,
137
118
  redirect: "manual",
138
- signal: AbortSignal.timeout(DOCTOR_TIMEOUT_MS),
119
+ signal: AbortSignal.any([AbortSignal.timeout(DOCTOR_TIMEOUT_MS), ...(init.signal ? [init.signal] : [])]),
139
120
  });
140
121
  if ([301, 302, 303, 307, 308].includes(response.status)) {
141
122
  await response.body?.cancel().catch(() => {});
@@ -145,10 +126,7 @@ async function doctorFetch(url, init = {}) {
145
126
  }
146
127
  return response;
147
128
  } catch (error) {
148
- if (
149
- error instanceof Error &&
150
- (error.name === "TimeoutError" || error.name === "AbortError")
151
- ) {
129
+ if (error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError")) {
152
130
  throw new Error(`Timed out after ${DOCTOR_TIMEOUT_MS}ms contacting ${url}`);
153
131
  }
154
132
  throw error;
@@ -156,9 +134,9 @@ async function doctorFetch(url, init = {}) {
156
134
  }
157
135
 
158
136
  async function doctor() {
159
- const known = new Set(["--url"]);
160
- for (let index = 0; index < args.length; index += 2) {
161
- if (!known.has(args[index]) || !args[index + 1]) {
137
+ for (let index = 0; index < args.length; index++) {
138
+ if (args[index] === "--config") continue;
139
+ if (args[index] !== "--url" || !args[++index] || args[index].startsWith("--")) {
162
140
  usage();
163
141
  process.exitCode = 1;
164
142
  return;
@@ -173,22 +151,15 @@ async function doctor() {
173
151
  throw new Error("Doctor URL must use http or https.");
174
152
  }
175
153
  const loopbackHosts = new Set(["localhost", "127.0.0.1", "[::1]"]);
176
- if (
177
- parsedUrl.protocol === "http:" &&
178
- !loopbackHosts.has(parsedUrl.hostname)
179
- ) {
180
- throw new Error(
181
- "Refusing to send authentication credentials over remote plaintext HTTP. Use HTTPS.",
182
- );
154
+ if (parsedUrl.protocol === "http:" && !loopbackHosts.has(parsedUrl.hostname)) {
155
+ throw new Error("Refusing to send authentication credentials over remote plaintext HTTP. Use HTTPS.");
183
156
  }
184
157
  const baseUrl = requestedUrl.replace(/\/+$/, "");
185
158
  const token = process.env.CONNECTA_TOKEN;
186
159
  const accessClientId = process.env.CF_ACCESS_CLIENT_ID;
187
160
  const accessClientSecret = process.env.CF_ACCESS_CLIENT_SECRET;
188
161
  if (Boolean(accessClientId) !== Boolean(accessClientSecret)) {
189
- throw new Error(
190
- "Set both CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET.",
191
- );
162
+ throw new Error("Set both CF_ACCESS_CLIENT_ID and CF_ACCESS_CLIENT_SECRET.");
192
163
  }
193
164
  if (!token && !accessClientId) {
194
165
  throw new Error(
@@ -205,9 +176,28 @@ async function doctor() {
205
176
  : {}),
206
177
  };
207
178
 
208
- const health = await jsonResponse(
209
- await doctorFetch(`${baseUrl}/health`, { headers: authHeaders }),
210
- );
179
+ if (args.includes("--config")) {
180
+ const response = await doctorFetch(`${baseUrl}/ui/api/config`, { headers: authHeaders });
181
+ if (!response.ok) {
182
+ await response.body?.cancel().catch(() => {});
183
+ throw new Error(`HTTP ${response.status}: config snapshot unavailable.`);
184
+ }
185
+ let contract;
186
+ try {
187
+ contract = await response.json();
188
+ } catch {
189
+ throw new Error("Deployment returned an invalid config contract.");
190
+ }
191
+ if (contract?.schemaVersion !== 1 || contract.config?.schemaVersion !== 1) {
192
+ throw new Error("Deployment returned an unsupported config contract.");
193
+ }
194
+ // The server builds this with describeConfig's allowlist; do not print
195
+ // the overlay, identity, health deploymentInfo, or a raw error body.
196
+ console.log(JSON.stringify(contract.config, null, 2));
197
+ return;
198
+ }
199
+
200
+ const health = await jsonResponse(await doctorFetch(`${baseUrl}/health`, { headers: authHeaders }));
211
201
  if (health.status !== "ok") {
212
202
  throw new Error(`Unexpected health status: ${String(health.status)}`);
213
203
  }
@@ -225,107 +215,88 @@ async function doctor() {
225
215
  .slice(0, 40)
226
216
  .trim()
227
217
  : "";
228
- let requestId = 0;
229
- const mcp = async (method, params) =>
230
- jsonResponse(
231
- await doctorFetch(`${baseUrl}/mcp`, {
232
- method: "POST",
233
- headers: {
234
- ...authHeaders,
235
- "Content-Type": "application/json",
236
- Accept: "application/json, text/event-stream",
237
- },
238
- body: JSON.stringify({
239
- jsonrpc: "2.0",
240
- id: ++requestId,
241
- method,
242
- params,
243
- }),
244
- }),
245
- );
246
-
247
- const listed = await mcp("tools/list", {});
248
- if (listed.error) {
249
- throw new Error(`tools/list failed: ${JSON.stringify(listed.error)}`);
250
- }
251
- const actual = listed.result?.tools
252
- ?.map((tool) => tool.name)
253
- .sort();
254
- const expected = [
255
- "authorize_connector",
256
- "call_destructive_tool",
257
- "call_tool",
258
- "execute_code",
259
- "get_result",
260
- "search_tools",
261
- "skills",
262
- ];
263
- if (JSON.stringify(actual) !== JSON.stringify(expected)) {
264
- throw new Error(
265
- `Unexpected MCP surface. Expected ${expected.join(", ")}; received ` +
266
- `${Array.isArray(actual) ? actual.join(", ") : "no tool list"}.`,
267
- );
268
- }
269
-
270
- const executed = await mcp("tools/call", {
271
- name: "execute_code",
272
- arguments: { code: "async () => 42" },
218
+ const { Client, StreamableHTTPClientTransport } = await import("@modelcontextprotocol/client");
219
+ const client = new Client(
220
+ { name: "connecta-doctor", version: CONNECTA_VERSION },
221
+ { versionNegotiation: { mode: "auto", probe: { timeoutMs: DOCTOR_TIMEOUT_MS } } },
222
+ );
223
+ const transport = new StreamableHTTPClientTransport(new URL(`${baseUrl}/mcp`), {
224
+ requestInit: { headers: authHeaders },
225
+ fetch: doctorFetch,
273
226
  });
274
- if (executed.error) {
275
- throw new Error(`execute_code failed: ${JSON.stringify(executed.error)}`);
276
- }
277
- const executionResult =
278
- executed.result?.structuredContent ??
279
- JSON.parse(executed.result?.content?.[0]?.text ?? "null");
280
- if (executed.result?.isError || executionResult?.result !== 42) {
281
- throw new Error(
282
- `${executorName ? `${executorName} execution` : "execute_code"} check ` +
283
- `failed: ${JSON.stringify(executed.result)}`,
284
- );
285
- }
227
+ let negotiatedVersion;
228
+ try {
229
+ await client.connect(transport);
230
+ negotiatedVersion = client.getNegotiatedProtocolVersion();
231
+ const listed = await client.listTools({}, { timeout: DOCTOR_TIMEOUT_MS });
232
+ const actual = listed.tools.map((tool) => tool.name).sort();
233
+ const expected = [...META_TOOL_NAMES].sort();
234
+ if (JSON.stringify(actual) !== JSON.stringify(expected)) {
235
+ throw new Error(
236
+ `Unexpected MCP surface. Expected ${expected.join(", ")}; received ` +
237
+ `${Array.isArray(actual) ? actual.join(", ") : "no tool list"}.`,
238
+ );
239
+ }
286
240
 
287
- // Drift is reported, never failed on. A downstream that grew a tool nobody
288
- // has classified is a maintainer's next task, not a broken deployment: the
289
- // unclassified tool already fails closed onto call_destructive_tool. These
290
- // counts come from refreshes the deployment already served, so a deployment
291
- // that has answered no catalog request yet reports nothing here (#343).
292
- const drifted = Object.entries(health.catalogDrift ?? {}).filter(
293
- ([, report]) =>
294
- report.unclassifiedTools ||
295
- report.unservedTools ||
296
- report.annotationConflicts ||
297
- report.schemaChanges,
298
- );
299
- for (const [connectorId, report] of drifted) {
300
- console.warn(
301
- `[connecta] catalog drift on "${connectorId}" (observed ` +
302
- `${report.observedAt}): ${report.unclassifiedTools} unclassified, ` +
303
- `${report.unservedTools} no longer served, ` +
304
- `${report.annotationConflicts} annotation conflict(s), ` +
305
- `${report.schemaChanges} schema change(s).`,
241
+ const executed = await client.callTool(
242
+ {
243
+ name: "execute_code",
244
+ arguments: { code: "async () => 42" },
245
+ },
246
+ undefined,
247
+ { timeout: DOCTOR_TIMEOUT_MS },
306
248
  );
249
+ const executionResult = executed.structuredContent ?? JSON.parse(executed.content?.[0]?.text ?? "null");
250
+ if (executed.isError || executionResult?.result !== 42) {
251
+ throw new Error(
252
+ `${executorName ? `${executorName} execution` : "execute_code"} check ` + `failed: ${JSON.stringify(executed)}`,
253
+ );
254
+ }
255
+ } finally {
256
+ await client.close();
307
257
  }
308
258
 
309
259
  console.log(
310
260
  `Connecta doctor passed: ${health.connectors} connector(s), ` +
311
261
  `${executorName ? `${executorName} executed` : "code executed"}, ` +
312
- "prescribed seven-tool surface" +
313
- (drifted.length > 0
314
- ? `, catalog drift on ${drifted.length} connector(s).`
315
- : "."),
262
+ `prescribed ${META_TOOL_NAMES.length}-tool surface, MCP ${negotiatedVersion}, package ${CONNECTA_VERSION}.`,
316
263
  );
317
264
  }
318
265
 
266
+ /**
267
+ * One-shot copy of a 0.28 `fileStorage` JSON state file into the SQLite
268
+ * database 0.29 stores everything in. Stop the old deployment first; the file
269
+ * is read, never changed.
270
+ */
271
+ async function migrateState() {
272
+ if (args.length !== 2 || args.some((arg) => arg.startsWith("--"))) {
273
+ usage();
274
+ process.exitCode = 1;
275
+ return;
276
+ }
277
+ const [statePath, databasePath] = args.map((arg) => resolve(process.cwd(), arg));
278
+ const { importStateFile, openSqlite } = await import(pathToFileURL(join(packageRoot, "dist", "sqlite.js")).href);
279
+ const database = openSqlite(databasePath);
280
+ try {
281
+ const result = importStateFile(database, statePath);
282
+ console.log(
283
+ `Imported ${result.imported} entries from ${statePath} into ${databasePath}` +
284
+ ` (${result.kept} already present, ${result.expired} expired).`,
285
+ );
286
+ } finally {
287
+ database.close();
288
+ }
289
+ }
290
+
319
291
  try {
320
292
  if (command === "init") await init();
321
293
  else if (command === "doctor") await doctor();
294
+ else if (command === "migrate-state") await migrateState();
322
295
  else {
323
296
  usage();
324
297
  process.exitCode = command === "--help" || command === "-h" ? 0 : 1;
325
298
  }
326
299
  } catch (error) {
327
- console.error(
328
- `[connecta] ${error instanceof Error ? error.message : String(error)}`,
329
- );
300
+ console.error(`[connecta] ${error instanceof Error ? error.message : String(error)}`);
330
301
  process.exitCode = 1;
331
302
  }
@@ -0,0 +1,2 @@
1
+ /** The complete upward meta-tool set, shared by discovery checks and doctor. */
2
+ export declare const META_TOOL_NAMES: readonly string[];
@@ -0,0 +1,9 @@
1
+ /** The complete upward meta-tool set, shared by discovery checks and doctor. */
2
+ export const META_TOOL_NAMES = Object.freeze([
3
+ "authorize_connector",
4
+ "call_destructive_tool",
5
+ "call_tool",
6
+ "execute_code",
7
+ "search_tools",
8
+ "skills",
9
+ ]);
@@ -0,0 +1,3 @@
1
+ // Generated from package.json by scripts/generate-version.mjs.
2
+ // Workers-safe: no runtime filesystem read or deployment override.
3
+ export const CONNECTA_VERSION = "0.29.0";