@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
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,14 +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 Basecamp, Breeze ChMS, Church Community
68
- Builder, Cloudflare, Linear, Mixpanel, Notion, Overflow, Planning Center,
69
- RevenueCat, Stripe, Tithe.ly, and Vercel: known endpoints, auth defaults, and
70
- vetted read/write classifications, imported one at a time. Cloudflare,
71
- Notion, and Vercel each let the deployment choose their hand-written API
72
- interface or official hosted MCP. Planning Center, Overflow, Tithe.ly,
73
- Breeze, and Church Community Builder are hand-written over their whole APIs,
74
- with every money-moving write behind approval.
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.
75
79
  - **Let the agent work in code.** Search, chain, filter, join, and reduce
76
80
  inside the sandbox instead of round-tripping every call through the model.
77
81
  - **Teach undeclared result shapes by using them.** Successful read-only calls
@@ -83,45 +87,61 @@ Fifty issues in, one small object out. Your context window notices.
83
87
  notice also points the agent to reduce or search the result inside a
84
88
  program. Discovery can show compact schemas, exact JSON Schema, or a
85
89
  TypeScript signature to read while writing JavaScript.
86
- - **Keep writes deliberate.** Only tools marked read-only run in a program.
87
- Every other write is a separate, visible `call_destructive_tool` call your
88
- client can gate, so the host's permission prompt is the one approval there
89
- is. Config — and only config — can exempt a cheap, reversible write from
90
- 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.
91
95
  - **Run it on Node or Cloudflare Workers.** The core is shared; each deployment
92
- supplies its platform's executor and storage. The Node template also runs
93
- 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.
94
99
 
95
100
  Deployments explicitly compose optional features: `operatorUi()` from
96
101
  `@zackbart/connecta/ui`, `encryptedCredentialVault()` from `/credentials`,
97
- `activityHistory()` from `/activity`, `artifacts()` from `/artifacts`, and
102
+ `activityHistory()` from `/activity`, and
98
103
  inbound authentication adapters from `/auth/*`. Omit a module and its implementation does no runtime work. Core
99
104
  keeps connector discovery, execution, invocation, and enforcement together.
100
-
101
- The optional UI shows each person's connections and effective permissions.
102
- Authentication controls live inside each connection, with optional activity
103
- history. The configured connection list loads before downstream checks finish;
104
- a slow provider does not hold up the page. Connector selection and access rules
105
- remain in deployment code. It also explains which tools can run in programs or
106
- need approval, offers a fixed repair prompt for classified failures, and shows
107
- 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.
108
123
 
109
124
  One deployment may serve several authenticated people inside the same tenant.
110
- Cloudflare Access supplies Worker identity; Node can use Clerk or the optional
111
- configured bearer adapter. Connecta owns no accounts or groups and issues no
112
- 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
113
127
  setup require separate explicit permissions, both denied by default. See
114
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.
115
134
 
116
135
  Connecta is not a platform, a marketplace, a policy engine, or a multi-tenant
117
- service. Those are decisions, and the [ethos](./ethos.md) records each one
118
- 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.
119
139
 
120
140
  ## Getting started
121
141
 
122
- 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
123
143
  ask it to set up a Connecta deployment; the
124
- [documentation](./documentation/) covers the architecture, the seven tools,
144
+ [documentation](./documentation/) covers the architecture, the six tools,
125
145
  code mode, and inbound auth if you want to go deeper. When upgrading an
126
146
  existing deployment, each [changelog](./CHANGELOG.md) release opens with what
127
147
  breaks and what a deployment can ignore.
@@ -139,6 +159,6 @@ Upgrading from v0.23 does not require rotating managed `cta_…` tokens. Import
139
159
  `accessTokens: true` with `accessTokens: accessTokens(storage)`, and keep the
140
160
  same persistent storage namespace and identity/tool/pool grant rules. Enable
141
161
  `identity.accessTokenManagement` only for the interactive operators who should
142
- manage tokens. New issuance needs storage with atomic `compareAndSet`; older
143
- storage adapters can still verify existing tokens. See the package's
144
- `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";