@zackbart/connecta 0.15.0 → 0.16.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 (332) hide show
  1. package/AGENTS.md +28 -15
  2. package/CHANGELOG.md +493 -0
  3. package/README.md +19 -4
  4. package/bin/connecta.mjs +44 -3
  5. package/dist/access-tokens.d.ts +0 -1
  6. package/dist/access-tokens.js +0 -1
  7. package/dist/activity.d.ts +50 -2
  8. package/dist/activity.js +34 -1
  9. package/dist/apps-shell.d.ts +0 -1
  10. package/dist/apps-shell.js +0 -1
  11. package/dist/auth/bearer.d.ts +0 -1
  12. package/dist/auth/bearer.js +0 -1
  13. package/dist/auth/clerk.d.ts +0 -1
  14. package/dist/auth/clerk.js +45 -9
  15. package/dist/auth/downstream-oauth.d.ts +0 -1
  16. package/dist/auth/downstream-oauth.js +0 -1
  17. package/dist/call-admission.d.ts +0 -1
  18. package/dist/call-admission.js +0 -1
  19. package/dist/catalog-drift.d.ts +99 -0
  20. package/dist/catalog-drift.js +251 -0
  21. package/dist/catalog-fingerprint.d.ts +0 -1
  22. package/dist/catalog-fingerprint.js +0 -1
  23. package/dist/catalog-limits.d.ts +0 -1
  24. package/dist/catalog-limits.js +0 -1
  25. package/dist/catalog-service.d.ts +0 -1
  26. package/dist/catalog-service.js +0 -1
  27. package/dist/catalog.d.ts +0 -1
  28. package/dist/catalog.js +0 -1
  29. package/dist/concurrency.d.ts +0 -1
  30. package/dist/concurrency.js +0 -1
  31. package/dist/connector-scope.d.ts +0 -1
  32. package/dist/connector-scope.js +0 -1
  33. package/dist/connectors/api.d.ts +29 -21
  34. package/dist/connectors/api.js +45 -19
  35. package/dist/connectors/guarded-fetch.d.ts +106 -0
  36. package/dist/connectors/guarded-fetch.js +289 -0
  37. package/dist/connectors/remote-mcp.d.ts +0 -1
  38. package/dist/connectors/remote-mcp.js +0 -1
  39. package/dist/credentials.d.ts +0 -1
  40. package/dist/credentials.js +0 -1
  41. package/dist/errors.d.ts +0 -1
  42. package/dist/errors.js +0 -1
  43. package/dist/execute.d.ts +0 -1
  44. package/dist/execute.js +0 -1
  45. package/dist/executor-admission.d.ts +11 -1
  46. package/dist/executor-admission.js +28 -1
  47. package/dist/executor-result.d.ts +0 -1
  48. package/dist/executor-result.js +0 -1
  49. package/dist/executors/quickjs-child.d.ts +0 -1
  50. package/dist/executors/quickjs-child.js +0 -1
  51. package/dist/executors/quickjs-protocol.d.ts +0 -1
  52. package/dist/executors/quickjs-protocol.js +0 -1
  53. package/dist/executors/quickjs-runtime.d.ts +0 -1
  54. package/dist/executors/quickjs-runtime.js +0 -1
  55. package/dist/executors/quickjs.d.ts +0 -1
  56. package/dist/executors/quickjs.js +2 -1
  57. package/dist/favicon.d.ts +0 -1
  58. package/dist/favicon.js +0 -1
  59. package/dist/index.d.ts +2 -3
  60. package/dist/index.js +24 -7
  61. package/dist/invocation.d.ts +0 -1
  62. package/dist/invocation.js +0 -1
  63. package/dist/json-schema.d.ts +0 -1
  64. package/dist/json-schema.js +0 -1
  65. package/dist/mcp-result.d.ts +0 -1
  66. package/dist/mcp-result.js +0 -1
  67. package/dist/meta-tools.d.ts +0 -1
  68. package/dist/meta-tools.js +0 -1
  69. package/dist/node.d.ts +0 -1
  70. package/dist/node.js +0 -1
  71. package/dist/operator-ui/generated.d.ts +2 -3
  72. package/dist/operator-ui/generated.js +3 -4
  73. package/dist/operator-ui/model.d.ts +10 -1
  74. package/dist/operator-ui/model.js +0 -1
  75. package/dist/operator-ui/view.d.ts +167 -0
  76. package/dist/operator-ui/view.js +270 -0
  77. package/dist/providers/cloudflare.d.ts +5 -2
  78. package/dist/providers/cloudflare.js +435 -328
  79. package/dist/providers/linear.d.ts +26 -5
  80. package/dist/providers/linear.js +32 -60
  81. package/dist/providers/mixpanel.d.ts +35 -4
  82. package/dist/providers/mixpanel.js +53 -78
  83. package/dist/providers/notion.d.ts +0 -1
  84. package/dist/providers/notion.js +87 -42
  85. package/dist/providers/stripe.d.ts +13 -1
  86. package/dist/providers/stripe.js +31 -59
  87. package/dist/registry.d.ts +34 -2
  88. package/dist/registry.js +85 -5
  89. package/dist/retired-toolkits.d.ts +0 -1
  90. package/dist/retired-toolkits.js +0 -1
  91. package/dist/routes/access-tokens.d.ts +0 -1
  92. package/dist/routes/access-tokens.js +0 -1
  93. package/dist/routes/activity.d.ts +0 -1
  94. package/dist/routes/activity.js +0 -1
  95. package/dist/routes/credentials.d.ts +0 -1
  96. package/dist/routes/credentials.js +0 -1
  97. package/dist/routes/mcp.d.ts +0 -1
  98. package/dist/routes/mcp.js +0 -1
  99. package/dist/routes/oauth.d.ts +0 -1
  100. package/dist/routes/oauth.js +0 -1
  101. package/dist/routes/shared.d.ts +2 -1
  102. package/dist/routes/shared.js +0 -1
  103. package/dist/routes/ui.d.ts +0 -1
  104. package/dist/routes/ui.js +0 -1
  105. package/dist/server.d.ts +0 -1
  106. package/dist/server.js +11 -1
  107. package/dist/skills.d.ts +0 -1
  108. package/dist/skills.js +0 -1
  109. package/dist/storage/file.d.ts +0 -1
  110. package/dist/storage/file.js +0 -1
  111. package/dist/storage/memory.d.ts +0 -1
  112. package/dist/storage/memory.js +0 -1
  113. package/dist/timeout.d.ts +0 -1
  114. package/dist/timeout.js +0 -1
  115. package/dist/tool-safety.d.ts +0 -1
  116. package/dist/tool-safety.js +0 -1
  117. package/dist/types.d.ts +45 -1
  118. package/dist/types.js +0 -1
  119. package/dist/ui.d.ts +0 -1
  120. package/dist/ui.js +13 -137
  121. package/dist/validate.d.ts +13 -15
  122. package/dist/validate.js +24 -11
  123. package/dist/version.d.ts +1 -2
  124. package/dist/version.js +1 -2
  125. package/documentation/architecture.md +186 -5
  126. package/documentation/auth.md +11 -0
  127. package/documentation/call-admission.md +142 -6
  128. package/documentation/cloudflare.md +95 -27
  129. package/documentation/code-mode.md +12 -12
  130. package/documentation/connector-guides.md +172 -5
  131. package/documentation/connectors.md +98 -7
  132. package/documentation/linear.md +26 -9
  133. package/documentation/mcp-ui-design.md +2 -2
  134. package/documentation/mixpanel.md +48 -16
  135. package/documentation/notion.md +16 -0
  136. package/documentation/operations.md +283 -5
  137. package/documentation/operator-ui.md +120 -5
  138. package/documentation/provider-audit.md +160 -0
  139. package/documentation/provider-conventions.md +631 -0
  140. package/documentation/request-admission.md +167 -5
  141. package/documentation/rich-output-design.md +1 -2
  142. package/documentation/stripe.md +27 -12
  143. package/ethos.md +14 -9
  144. package/examples/worker/README.md +96 -12
  145. package/examples/worker/src/index.ts +46 -1
  146. package/examples/worker/wrangler.jsonc +14 -1
  147. package/package.json +4 -5
  148. package/templates/node/.dockerignore +10 -0
  149. package/templates/node/.env.example +40 -3
  150. package/templates/node/AGENTS.md +11 -1
  151. package/templates/node/Dockerfile +49 -0
  152. package/templates/node/README.md +117 -2
  153. package/templates/node/docker-compose.yml +39 -0
  154. package/templates/node/package.json +1 -1
  155. package/templates/node/src/file-activity.ts +182 -0
  156. package/templates/node/src/index.ts +80 -4
  157. package/assets/connecta-clay-hero.png +0 -0
  158. package/dist/access-tokens.d.ts.map +0 -1
  159. package/dist/access-tokens.js.map +0 -1
  160. package/dist/activity.d.ts.map +0 -1
  161. package/dist/activity.js.map +0 -1
  162. package/dist/apps-shell.d.ts.map +0 -1
  163. package/dist/apps-shell.js.map +0 -1
  164. package/dist/auth/bearer.d.ts.map +0 -1
  165. package/dist/auth/bearer.js.map +0 -1
  166. package/dist/auth/clerk.d.ts.map +0 -1
  167. package/dist/auth/clerk.js.map +0 -1
  168. package/dist/auth/downstream-oauth.d.ts.map +0 -1
  169. package/dist/auth/downstream-oauth.js.map +0 -1
  170. package/dist/call-admission.d.ts.map +0 -1
  171. package/dist/call-admission.js.map +0 -1
  172. package/dist/catalog-fingerprint.d.ts.map +0 -1
  173. package/dist/catalog-fingerprint.js.map +0 -1
  174. package/dist/catalog-limits.d.ts.map +0 -1
  175. package/dist/catalog-limits.js.map +0 -1
  176. package/dist/catalog-service.d.ts.map +0 -1
  177. package/dist/catalog-service.js.map +0 -1
  178. package/dist/catalog.d.ts.map +0 -1
  179. package/dist/catalog.js.map +0 -1
  180. package/dist/concurrency.d.ts.map +0 -1
  181. package/dist/concurrency.js.map +0 -1
  182. package/dist/connector-scope.d.ts.map +0 -1
  183. package/dist/connector-scope.js.map +0 -1
  184. package/dist/connectors/api.d.ts.map +0 -1
  185. package/dist/connectors/api.js.map +0 -1
  186. package/dist/connectors/remote-mcp.d.ts.map +0 -1
  187. package/dist/connectors/remote-mcp.js.map +0 -1
  188. package/dist/credentials.d.ts.map +0 -1
  189. package/dist/credentials.js.map +0 -1
  190. package/dist/errors.d.ts.map +0 -1
  191. package/dist/errors.js.map +0 -1
  192. package/dist/execute.d.ts.map +0 -1
  193. package/dist/execute.js.map +0 -1
  194. package/dist/executor-admission.d.ts.map +0 -1
  195. package/dist/executor-admission.js.map +0 -1
  196. package/dist/executor-result.d.ts.map +0 -1
  197. package/dist/executor-result.js.map +0 -1
  198. package/dist/executors/quickjs-child.d.ts.map +0 -1
  199. package/dist/executors/quickjs-child.js.map +0 -1
  200. package/dist/executors/quickjs-protocol.d.ts.map +0 -1
  201. package/dist/executors/quickjs-protocol.js.map +0 -1
  202. package/dist/executors/quickjs-runtime.d.ts.map +0 -1
  203. package/dist/executors/quickjs-runtime.js.map +0 -1
  204. package/dist/executors/quickjs.d.ts.map +0 -1
  205. package/dist/executors/quickjs.js.map +0 -1
  206. package/dist/favicon.d.ts.map +0 -1
  207. package/dist/favicon.js.map +0 -1
  208. package/dist/index.d.ts.map +0 -1
  209. package/dist/index.js.map +0 -1
  210. package/dist/invocation.d.ts.map +0 -1
  211. package/dist/invocation.js.map +0 -1
  212. package/dist/json-schema.d.ts.map +0 -1
  213. package/dist/json-schema.js.map +0 -1
  214. package/dist/mcp-result.d.ts.map +0 -1
  215. package/dist/mcp-result.js.map +0 -1
  216. package/dist/meta-tools.d.ts.map +0 -1
  217. package/dist/meta-tools.js.map +0 -1
  218. package/dist/node.d.ts.map +0 -1
  219. package/dist/node.js.map +0 -1
  220. package/dist/operator-ui/generated.d.ts.map +0 -1
  221. package/dist/operator-ui/generated.js.map +0 -1
  222. package/dist/operator-ui/model.d.ts.map +0 -1
  223. package/dist/operator-ui/model.js.map +0 -1
  224. package/dist/providers/cloudflare.d.ts.map +0 -1
  225. package/dist/providers/cloudflare.js.map +0 -1
  226. package/dist/providers/linear.d.ts.map +0 -1
  227. package/dist/providers/linear.js.map +0 -1
  228. package/dist/providers/mixpanel.d.ts.map +0 -1
  229. package/dist/providers/mixpanel.js.map +0 -1
  230. package/dist/providers/notion.d.ts.map +0 -1
  231. package/dist/providers/notion.js.map +0 -1
  232. package/dist/providers/stripe.d.ts.map +0 -1
  233. package/dist/providers/stripe.js.map +0 -1
  234. package/dist/registry.d.ts.map +0 -1
  235. package/dist/registry.js.map +0 -1
  236. package/dist/retired-toolkits.d.ts.map +0 -1
  237. package/dist/retired-toolkits.js.map +0 -1
  238. package/dist/routes/access-tokens.d.ts.map +0 -1
  239. package/dist/routes/access-tokens.js.map +0 -1
  240. package/dist/routes/activity.d.ts.map +0 -1
  241. package/dist/routes/activity.js.map +0 -1
  242. package/dist/routes/credentials.d.ts.map +0 -1
  243. package/dist/routes/credentials.js.map +0 -1
  244. package/dist/routes/mcp.d.ts.map +0 -1
  245. package/dist/routes/mcp.js.map +0 -1
  246. package/dist/routes/oauth.d.ts.map +0 -1
  247. package/dist/routes/oauth.js.map +0 -1
  248. package/dist/routes/shared.d.ts.map +0 -1
  249. package/dist/routes/shared.js.map +0 -1
  250. package/dist/routes/ui.d.ts.map +0 -1
  251. package/dist/routes/ui.js.map +0 -1
  252. package/dist/server.d.ts.map +0 -1
  253. package/dist/server.js.map +0 -1
  254. package/dist/skills.d.ts.map +0 -1
  255. package/dist/skills.js.map +0 -1
  256. package/dist/storage/file.d.ts.map +0 -1
  257. package/dist/storage/file.js.map +0 -1
  258. package/dist/storage/memory.d.ts.map +0 -1
  259. package/dist/storage/memory.js.map +0 -1
  260. package/dist/timeout.d.ts.map +0 -1
  261. package/dist/timeout.js.map +0 -1
  262. package/dist/tool-safety.d.ts.map +0 -1
  263. package/dist/tool-safety.js.map +0 -1
  264. package/dist/types.d.ts.map +0 -1
  265. package/dist/types.js.map +0 -1
  266. package/dist/ui.d.ts.map +0 -1
  267. package/dist/ui.js.map +0 -1
  268. package/dist/validate.d.ts.map +0 -1
  269. package/dist/validate.js.map +0 -1
  270. package/dist/version.d.ts.map +0 -1
  271. package/dist/version.js.map +0 -1
  272. package/examples/node/README.md +0 -52
  273. package/examples/node/src/index.ts +0 -71
  274. package/src/access-tokens.ts +0 -289
  275. package/src/activity.ts +0 -234
  276. package/src/apps-shell.ts +0 -370
  277. package/src/auth/bearer.ts +0 -61
  278. package/src/auth/clerk.ts +0 -553
  279. package/src/auth/downstream-oauth.ts +0 -575
  280. package/src/call-admission.ts +0 -521
  281. package/src/catalog-fingerprint.ts +0 -52
  282. package/src/catalog-limits.ts +0 -11
  283. package/src/catalog-service.ts +0 -1153
  284. package/src/catalog.ts +0 -790
  285. package/src/concurrency.ts +0 -39
  286. package/src/connector-scope.ts +0 -66
  287. package/src/connectors/api.ts +0 -159
  288. package/src/connectors/remote-mcp.ts +0 -1090
  289. package/src/credentials.ts +0 -428
  290. package/src/errors.ts +0 -364
  291. package/src/execute.ts +0 -1406
  292. package/src/executor-admission.ts +0 -328
  293. package/src/executor-result.ts +0 -107
  294. package/src/executors/quickjs-child.ts +0 -172
  295. package/src/executors/quickjs-protocol.ts +0 -99
  296. package/src/executors/quickjs-runtime.ts +0 -470
  297. package/src/executors/quickjs.ts +0 -776
  298. package/src/favicon.ts +0 -53
  299. package/src/index.ts +0 -666
  300. package/src/invocation.ts +0 -655
  301. package/src/json-schema.ts +0 -11
  302. package/src/mcp-result.ts +0 -43
  303. package/src/meta-tools.ts +0 -1607
  304. package/src/node.ts +0 -206
  305. package/src/operator-ui/browser.css +0 -423
  306. package/src/operator-ui/browser.ts +0 -1281
  307. package/src/operator-ui/generated.ts +0 -4
  308. package/src/operator-ui/model.ts +0 -111
  309. package/src/providers/cloudflare.ts +0 -3803
  310. package/src/providers/linear.ts +0 -301
  311. package/src/providers/mixpanel.ts +0 -228
  312. package/src/providers/notion.ts +0 -1879
  313. package/src/providers/stripe.ts +0 -306
  314. package/src/registry.ts +0 -1085
  315. package/src/retired-toolkits.ts +0 -14
  316. package/src/routes/access-tokens.ts +0 -115
  317. package/src/routes/activity.ts +0 -215
  318. package/src/routes/credentials.ts +0 -254
  319. package/src/routes/mcp.ts +0 -429
  320. package/src/routes/oauth.ts +0 -322
  321. package/src/routes/shared.ts +0 -247
  322. package/src/routes/ui.ts +0 -152
  323. package/src/server.ts +0 -195
  324. package/src/skills.ts +0 -271
  325. package/src/storage/file.ts +0 -123
  326. package/src/storage/memory.ts +0 -44
  327. package/src/timeout.ts +0 -84
  328. package/src/tool-safety.ts +0 -15
  329. package/src/types.ts +0 -523
  330. package/src/ui.ts +0 -744
  331. package/src/validate.ts +0 -387
  332. package/src/version.ts +0 -7
@@ -1,7 +1,169 @@
1
1
  # Request admission
2
2
 
3
- > **Stub.** The old manual was retired in the phase-1 docs restructure. This
4
- > document will be rewritten as an agent-facing guide what the subsystem is
5
- > for, how to work on it, and what it must never do — once the ideas in
6
- > [ethos.md](../ethos.md) settle. The prior text lives in git history as
7
- > `docs/request-admission.md`.
3
+ Connecta bounds work at the Web-standard request boundary before inbound
4
+ auth, before the MCP server exists, before any catalog is touched. A burst
5
+ therefore meets an explicit active count and an explicit queue instead of
6
+ asking traffic shape and the runtime allocator to pick the process high-water
7
+ mark.
8
+
9
+ There are two pools here and a third elsewhere. This guide covers the first
10
+ two: the deployment-wide `/mcp` pool and the fallback code pool. Per-connector
11
+ downstream bounds are [call admission](./call-admission.md), which is a
12
+ different question — request admission bounds the whole MCP envelope, call
13
+ admission bounds the individual `Connector.callTool` attempts fanned out inside
14
+ it.
15
+
16
+ ## The pools
17
+
18
+ Every non-preflight `/mcp` request takes one permit from a deployment-wide FIFO
19
+ pool. Initialization, discovery, ordinary calls, and `execute_code` all pay it.
20
+ A program then takes a *second* permit from the deliberately smaller code pool,
21
+ so one request cannot trade ordinary capacity for an unbounded number of
22
+ sandboxes.
23
+
24
+ ```ts
25
+ const connecta = createConnecta({
26
+ admission: {
27
+ requests: { concurrency: 16, maxQueueSize: 32, queueTimeoutMs: 5_000, retryAfterMs: 1_000 },
28
+ code: { concurrency: 2, maxQueueSize: 8, queueTimeoutMs: 5_000, retryAfterMs: 1_000 },
29
+ },
30
+ // …
31
+ });
32
+ ```
33
+
34
+ Those values are the defaults, and both pools are the same
35
+ `AdmissionController` (`src/executor-admission.ts`) with different numbers.
36
+ `maxQueueSize: 0` is the fail-fast shape. Every value is a finite whole number;
37
+ concurrency and the queue timeout must be positive, while queue size and the
38
+ retry hint may be zero. Invalid bounds throw at construction rather than
39
+ silently removing the deployment's memory boundary — a pool that quietly became
40
+ unbounded is worse than a deployment that refuses to boot.
41
+
42
+ `admission.code` is a *fallback*. An executor that implements `acquire()` is an
43
+ `AdmittingExecutor` and already owns a bounded pool, so its own settings win and
44
+ connecta warns that `admission.code` was ignored. `quickJsExecutor()` is one of
45
+ those: it defaults to one active execution and 32 queued callers, configured on
46
+ the executor rather than here. Cloudflare's `DynamicWorkerExecutor` is not, so
47
+ a Worker deployment gets the fallback pool wrapped around it at construction —
48
+ which is also why `/health` always has a code-admission shape to report.
49
+
50
+ The request pool is global FIFO across identities. It is a capacity boundary,
51
+ not tenant fairness: one busy caller can occupy it. Per-tenant fairness needs a
52
+ policy above connecta, and one deployment serves one audience anyway
53
+ ([`ethos.md`](../ethos.md)), so a global queue is not pretending to supply
54
+ something it does not.
55
+
56
+ ## Admission before auth
57
+
58
+ `/mcp` acquires its permit *before* running the auth gate. This looks backwards
59
+ until you price it: authenticating first means an unauthenticated flood buys a
60
+ Clerk network lookup per request, so the cheapest possible attack becomes the
61
+ most expensive request the server can serve. Admitting first means it buys a
62
+ queue slot and a 503.
63
+
64
+ The permit is released with the response *body*, not when the handler returns.
65
+ A slow client draining a large result still counts as active work, because its
66
+ bytes and its socket still exist. `releaseAdmissionWithResponse` re-wraps the
67
+ response stream to do this, absorbs a rejecting `cancel()` rather than leaking
68
+ an unhandled rejection, and releases exactly once — release is idempotent, and
69
+ `test/request-admission.test.ts` pins both the stream-cancel path and the
70
+ double-release case.
71
+
72
+ Every other route bypasses the pool entirely. `/health` and the operator
73
+ surface stay responsive while MCP is saturated, which is the whole point: an
74
+ operator diagnosing an overload must not have to queue behind it. `/health`
75
+ names the exempt routes in `admission.reservedRoutes` so the claim is checkable
76
+ from outside.
77
+
78
+ ## Overload, cancellation, shutdown
79
+
80
+ A full queue or an expired queue deadline answers HTTP 503 with `Retry-After`,
81
+ CORS headers, and a stable JSON-RPC error:
82
+
83
+ ```json
84
+ {
85
+ "jsonrpc": "2.0",
86
+ "id": null,
87
+ "error": {
88
+ "code": -32001,
89
+ "message": "Server capacity is exhausted. Retry later.",
90
+ "data": { "code": "server_overloaded", "retryable": true, "retryAfterMs": 1000 }
91
+ }
92
+ }
93
+ ```
94
+
95
+ `Retry-After` is that hint rounded up to at least one whole second. It is
96
+ advice, not a reservation. Shutdown uses `-32002` / `server_shutting_down` and
97
+ is not retryable. Code-pool overload never reaches this layer: it surfaces as
98
+ an ordinary MCP tool error with `executor_overloaded`, `retryable: true`, and
99
+ the executor's own `retryAfterMs`.
100
+
101
+ A cancelled queued request is removed immediately and never receives a later
102
+ permit — cancelling and then being admitted would hold capacity for a caller
103
+ that is gone. An admitted request keeps its permit until its body completes,
104
+ errors, or is cancelled.
105
+
106
+ `connecta.close()` closes both queues before releasing executor resources:
107
+ queued and future MCP work is rejected with `server_shutting_down` while
108
+ admitted work drains. Node's `listen()` calls it on SIGTERM or SIGINT, stops
109
+ accepting connections, drains, and enforces `shutdownTimeoutMs` (10 s default)
110
+ — SIGTERM arrives on every `docker compose up` recreate, and Node's default
111
+ response to it is to die mid-request.
112
+
113
+ ## What admission is not
114
+
115
+ The Node adapter's `maxBodyBytes` (10 MiB default) is a separate ingress guard.
116
+ It caps the HTTP body while constructing the Web `Request`, which happens
117
+ *before* the portable `/mcp` boundary can run. Admission bounds MCP, auth,
118
+ catalog, and response work; it is not a byte budget for many simultaneous slow
119
+ or near-limit uploads. Hostile public traffic wants an ingress proxy with a
120
+ body-rate limit in front, and `maxBodyBytes` set to the smallest value the
121
+ deployment actually needs.
122
+
123
+ The rejection warning is rate-limited to one per second, and each line reports
124
+ how many were suppressed since the last one. The `/health` totals count every
125
+ rejection, so the log is a sample and the counters are the record. Queue waits
126
+ log at debug level. Nothing on this path records or exposes request bodies,
127
+ tool arguments, identities, or results.
128
+
129
+ ## Observations
130
+
131
+ `/health` exposes payload-free snapshots under `admission.requests`,
132
+ `admission.code`, and `admission.downstreamCalls`. The first two carry
133
+ configured bounds, current active and queued counts, cumulative
134
+ admitted/queued/rejected/cancelled/closed totals, and queue-wait count, total,
135
+ and maximum. The request policy is labelled `global-fifo`; downstream policy is
136
+ labelled `connector-partitioned-per-runtime`. An executor that owns its own
137
+ pool and exposes no snapshot reports `{ managedByExecutor: true }`.
138
+
139
+ With `execute_code({ diagnostics: true })`, a caller sees the same split from
140
+ the inside: `admissionMs` is time spent waiting for a permit and `connectorMs`
141
+ is the admitted attempt.
142
+
143
+ ## Measuring capacity
144
+
145
+ `npm run load:admission` builds the package, then starts server and generator
146
+ in separate processes over real loopback TCP, warms a 10,000-tool catalog,
147
+ verifies every returned value, and records throughput, p50/p95/p99, server-only
148
+ peak RSS, RSS after forced GC, and live heap after GC. Each matrix cell gets a
149
+ fresh server so an earlier allocator high-water mark cannot contaminate the
150
+ next baseline; the three-round soak deliberately reuses one, because allocator
151
+ high-water retention and a live-object climb look identical in a single run and
152
+ different across three.
153
+
154
+ `CONNECTA_LOAD_CATALOG_SIZE`, `CONNECTA_LOAD_CONCURRENCY`, and
155
+ `CONNECTA_LOAD_MAX_QUEUE_SIZE` change catalog size, server concurrency, and
156
+ queue depth. The script prints its own numbers; no baseline is checked in,
157
+ deliberately. A laptop matrix is an example capacity profile, not a portable
158
+ SLO, and downstream payload size moves it more than any setting here does — pin
159
+ a runner before enforcing a regression ratio, and measure the connector mix the
160
+ deployment actually runs.
161
+
162
+ ## Tests that enforce this
163
+
164
+ | Invariant | Suite |
165
+ | --- | --- |
166
+ | FIFO bounds, active and queue ceilings, stable retryable overload, queue timeout, cancellation removal, idempotent release, shutdown | `test/executor-admission.test.ts` (Node + Workers) |
167
+ | `/mcp` bounded before auth, stable 503 and `Retry-After`, health and operator responsiveness under saturation, payload-free counters, queued cancellation, shutdown rejection while active work drains, the separate fallback code pool | `test/request-admission.test.ts` |
168
+ | A client disconnect propagating through the Web `Request` into a program's connector call, releasing both permits | `test/node.test.ts` |
169
+ | The `/health` admission payload alongside the drift counts | `test/catalog-drift.test.ts`, `test/server.test.ts` |
@@ -208,5 +208,4 @@ count of what was discarded (`M4`).
208
208
 
209
209
  The implementation issue carries these as acceptance criteria; the clauses
210
210
  fold into `code-mode.md` under a new "Emitted output" section when it closes,
211
- and the test-map row in [`operations.md`](./operations.md) lands with the
212
- suite.
211
+ and the suite takes its row in the [test map](./operations.md#the-test-map).
@@ -137,18 +137,18 @@ exists rather than a fresh object appearing beside it. Additive writes
137
137
  `readOnlyHint: false` already routes them through `call_destructive_tool`, and
138
138
  asserting destruction only inflates the approval copy the host shows a human.
139
139
 
140
- That classification is **fill-in only**, and unconditionally so: it supplies
141
- the annotations Stripe leaves unset — Stripe documents no MCP annotations at
142
- all and contradicts an explicit downstream annotation in neither direction. A
143
- tool on the read allowlist arriving with `destructiveHint: true` or
144
- `readOnlyHint: false` keeps exactly what the downstream said and stays behind
145
- `call_destructive_tool`. A tool on neither maintained list arriving with
146
- `readOnlyHint: true` keeps that too, and stays callable from `execute_code`.
147
- Both are the downstream telling you this release's allowlist is stale, and on a
148
- name no release has reviewed its word is the only evidence there is. The one
149
- classification that still outranks the downstream is a name this release
150
- reviewed and filed destructive: a `create_refund` claiming `readOnlyHint: true`
151
- is a downstream bug rather than news, and stays on the approval path.
140
+ That classification fills in downstream silence and otherwise preserves
141
+ explicit annotations. It supplies the annotations Stripe leaves unset — Stripe
142
+ documents no MCP annotations at all. A tool on the read allowlist arriving with
143
+ `destructiveHint: true` or `readOnlyHint: false` keeps exactly what the
144
+ downstream said and stays behind `call_destructive_tool`. A tool on neither
145
+ maintained list arriving with `readOnlyHint: true` keeps that too, and stays
146
+ callable from `execute_code`. Both are the downstream telling you this release's
147
+ allowlist is stale, and on a name no release has reviewed its word is the only
148
+ evidence there is. One narrow fail-closed exception applies to a name this
149
+ release reviewed and filed destructive: a `create_refund` claiming
150
+ `readOnlyHint: true` is a downstream bug rather than news, and stays on the
151
+ approval path.
152
152
 
153
153
  An unfamiliar tool that annotates nothing fails closed onto
154
154
  `call_destructive_tool` until a Connecta release reviews it. That is not
@@ -158,6 +158,14 @@ serves, an unclassified and unannotated `create_customer` lands on the approval
158
158
  path. Expect the undocumented Treasury tools Stripe alludes to to arrive
159
159
  unclassified as well — annotated ones will be taken at their word.
160
160
 
161
+ The upshot is that this account's tool list is not a fixed set, and the usage
162
+ guide tells the agent so: search this connector for what it actually exposes
163
+ rather than assuming a documented tool is present. The guide also names the id
164
+ discipline the downstream schemas cannot enforce — Stripe ids are typed
165
+ prefixes (`cus_`, `sub_`, `ch_`, `pi_`, `in_`, `acct_`), a plausible-looking one
166
+ belongs to a different object or to nobody, and the id a write takes comes from
167
+ `stripe_api_search` or a list read rather than from a guess.
168
+
161
169
  Stripe publishes no stability or deprecation policy for this tool set and
162
170
  invites tool requests by email, so treat the list as unversioned. `get_balance_summary`
163
171
  is Treasury, which Stripe labels public preview and gates behind an access
@@ -200,3 +208,10 @@ about rather than read:
200
208
  are Stripe's documented API conventions; how they thread through the generic
201
209
  tools' arguments is not documented. The guide states them because an agent
202
210
  that ignores them is wrong either way.
211
+
212
+ ## Conventions
213
+
214
+ This connection is audited against
215
+ [the provider conventions](./provider-conventions.md). Its verdict per
216
+ convention, including every recorded exception, is the Stripe section of
217
+ [the provider audit](./provider-audit.md).
package/ethos.md CHANGED
@@ -38,22 +38,22 @@ order, and amending it is a design decision, not a drive-by edit.
38
38
  - **One fetch-native core, two runtimes.** The same code runs unchanged on
39
39
  Cloudflare Workers and in Node — a Worker or a Docker stack, your pick. Web
40
40
  APIs only in the core; Node touches live behind explicit subpaths.
41
- - **Observable, never administrable.** Operator pages show connector status,
42
- masked credentials, and payload-free activity. They can rotate a secret;
43
- they cannot add a connector, change policy, or alter what an agent can call.
41
+ - **Observable, actionable only over authentication material.** Operator pages
42
+ show connector status, masked credentials, and payload-free activity and
43
+ they act: rotate a credential, issue or revoke an access token, run a
44
+ downstream OAuth flow. Declared capability is what they cannot touch.
44
45
 
45
46
  ## What this isn't
46
47
 
47
48
  - **Not a platform.** No runtime connector registration, no admin UI that
48
- changes behavior, no policy engine, no approvals, no pauses.
49
+ changes declared capability, no policy engine, no approvals, no pauses.
49
50
  - **Not a schema ingester.** No OpenAPI or GraphQL → tools. Generated tool
50
51
  sprawl is the disease the meta-tools treat, not a feature to add.
51
52
  - **Not multi-tenant.** No accounts dimension, no per-user credential store,
52
53
  no org hierarchy. Two accounts on one service are two connector instances.
53
54
  - **Not stateful.** No protocol sessions, no server push. Scope resolves per
54
55
  request — which is also where the MCP spec itself has now arrived.
55
- - **Not a nanny.** Credentials are stored safely and fail loudly at use;
56
- connecta doesn't probe them behind your back.
56
+ - **Not a nanny.** Credentials fail loudly at use; connecta never probes one.
57
57
  - **Not a promise to strangers — yet.** Built for its author's deployments
58
58
  first, published openly. Breaking changes are cheap and the version number
59
59
  signals change, not stability.
@@ -69,8 +69,9 @@ proposing one without a new argument is not.
69
69
  | Multi-tenancy / account model | refused | one deployment per tenant; deploy again instead |
70
70
  | Policy engine, approvals, pauses | refused | the host asks the human; connecta only annotates |
71
71
  | Runtime connector registration | refused | config-as-code is the security model |
72
- | Prebuilt connections as the preferred authoring path | accepted | an a-la-carte provider constructor, imported and constructed in the deployment file, encodes maintained defaults for providers connecta actually uses — preferred *when maintained*, with no promise of one per provider; it returns exactly one ordinary `Connector` with no extra privileges — never a bundle, a group, a preset, or a registry — its tools are hand-written or proxied from a downstream MCP catalog, never generated from a schema document; its vetted annotations classify what the downstream leaves unannotated and never overrule an explicit one; `remoteMcp()` and `api()` stay first-class ([#297](https://github.com/zackbart/connecta/issues/297)) |
72
+ | Prebuilt connections as the preferred authoring path | accepted | an a-la-carte provider constructor, imported and constructed in the deployment file, encodes maintained defaults for providers connecta actually uses — preferred *when maintained*, with no promise of one per provider; it returns exactly one ordinary `Connector` with no extra privileges — never a bundle, a group, a preset, or a registry — its tools are hand-written or proxied from a downstream MCP catalog, never generated from a schema document; its vetted annotations classify what the downstream leaves unannotated and otherwise preserve explicit annotations, with one fail-closed exception: a release-reviewed destructive classification outranks a contradictory downstream `readOnlyHint: true`, because Connecta has independently established that the tool mutates existing state; `remoteMcp()` and `api()` stay first-class ([#297](https://github.com/zackbart/connecta/issues/297), [#315](https://github.com/zackbart/connecta/issues/315)) |
73
73
  | Guarded raw REST escape hatches in a prebuilt connection | accepted | a large, fast-moving provider cannot be honestly represented by a small frozen list: a GET-only tool may expose provider-relative reads, while JSON mutations and explicit-content uploads stay separate and always cross the destructive boundary; the connector owns authentication, rate limits, error mapping, URL confinement, and safe method classification, while the provider token remains the capability boundary — this is not schema ingestion, runtime connector registration, or permission widening |
74
+ | Hosted-provider drift detection during catalog refreshes | accepted | a vetted classification is a claim about somebody else's catalog, and an allowlist nobody can tell is stale is an allowlist that is wrong — so each maintained hosted-MCP proxy ships the tool names, classifications, and (once a release records them) schemas it reviewed, and compares them against the live listing *inside* a catalog refresh the deployment already asked for; the boundary is the piggyback itself, which is what keeps this from being proactive credential liveness wearing a new hat ([#179](https://github.com/zackbart/connecta/issues/179)): no scheduled job, no background request, no credential probe, and not one byte of network traffic that would not have happened anyway; what it produces is four counts — unclassified additions, names no longer served, explicit annotation conflicts, schema changes — on connector status, `connecta doctor`, and one payload-free activity event with nowhere to put a tool name or a schema; the two read surfaces answer for the runtime that served the refresh, because the observation is isolate-local and only the activity event is durable, and unknown tools already fail closed, so drift costs approval round trips and stale guidance rather than capability ([#343](https://github.com/zackbart/connecta/issues/343)) |
74
75
  | Provider registry / integration marketplace | refused | prebuilt connections are imports, not listings; discovery happens in documentation, never at runtime ([#297](https://github.com/zackbart/connecta/issues/297)) |
75
76
  | Protocol sessions & server push | refused | stateless per request |
76
77
  | Resources & prompts aggregation | refused | tools only; connecta's own Apps shell is the one `resources/read` carve-out ([#266](https://github.com/zackbart/connecta/issues/266)) |
@@ -80,6 +81,7 @@ proposing one without a new argument is not.
80
81
  | Proactive credential liveness | removed | fail-at-use is enough ([#179](https://github.com/zackbart/connecta/issues/179)) |
81
82
  | Agent credential recovery | accepted | one `auth_required` route through `authorize_connector`; only an operator handles secrets ([#192](https://github.com/zackbart/connecta/issues/192)) |
82
83
  | Operator-issued MCP access tokens | accepted | named, revocable authentication gives header-capable clients a small alternative to OAuth; tokens identify callers but never scope tools or become operator credentials |
84
+ | Operator boundary reworded: authentication material, never declared capability | accepted | supersedes "observable, never administrable", which had stopped describing the surface: operator routes already rotate credentials, issue and revoke access tokens, and drive downstream OAuth, each under its own accepted row, and the owner has decided the surface stays actionable — so the boundary now says what is actually true, that operator routes may manage authentication material for capabilities declared in deployment configuration and may not change the connector set, the declared tool catalog or annotations, requested OAuth scopes, admission policy, authorization rules, or caller tool scope; the claim is deliberately about *declared* capability, and twice so, because replacing an API token with a broader-scoped one widens downstream reach and no browser page can honestly promise otherwise, and because a remote MCP server's catalog is discovered rather than declared — connecta declares the connector, its credential slot, and its admission policy, while the tools that server serves are its own answer, so storing a credential or finishing an OAuth flow can legitimately take an `mcp()` connector from no tools to N, which is discovery arriving, not an operator editing the deployment ([#338](https://github.com/zackbart/connecta/issues/338)) |
83
85
  | Structured result surface | accepted | canonical `structuredContent` plus complete compact `content`; summary-only text is gated on host-forwarding evidence ([#191](https://github.com/zackbart/connecta/issues/191)) |
84
86
  | Code mode (`execute_code`) | accepted | the primary read, discovery, and composition surface: smaller serialized definitions, far smaller results once composition and projection happen before the model sees them, and a cold-start model that read the interface without help ([exploration](./documentation/code-first-exploration.md), [#224](https://github.com/zackbart/connecta/issues/224)) |
85
87
  | Code-first as the default; the eval gate retired | accepted | owner decision, 2026-07-30: one operator, no deploy-time flip; [`eval/code-first-gate`](./eval/code-first-gate/README.md) survives as measurement, but nothing waits on its verdict ([#222](https://github.com/zackbart/connecta/issues/222), [#224](https://github.com/zackbart/connecta/issues/224)) |
@@ -132,8 +134,11 @@ Breaking one is not a bug fix — it is a design change wearing a disguise.
132
134
  `node:` builtin.
133
135
  - **The published surface is a boundary.** Heavyweight or platform-bound code
134
136
  goes behind an optional-peer subpath, never into core.
135
- - **No runtime admin.** If a browser could change what an agent can reach,
136
- that feature is a non-goal wearing a disguise.
137
+ - **Operator routes manage authentication material, never declared
138
+ capability.** Authenticating a declared capability is allowed; the connector
139
+ set, declared catalog and annotations, OAuth scopes, admission, authorization
140
+ rules, and caller tool scope take a config edit. A downstream catalog is
141
+ discovered, not declared — remote MCP tools appear when its credential does.
137
142
  - **Structural mistakes throw at construction.** A deployment that boots into
138
143
  the wrong shape is worse than one that refuses to boot.
139
144
 
@@ -8,7 +8,7 @@ and requires the Workers Paid plan.
8
8
  This is also the **starting template for a deployment**: a real deployment
9
9
  should be its own repository that pins an exact `@zackbart/connecta` version and
10
10
  owns only its connector configuration, auth policy, domain, bindings,
11
- migrations, and secrets. See [deployment architecture](../../documentation/operations.md).
11
+ migrations, and secrets. See [the Cloudflare guide](../../documentation/cloudflare.md).
12
12
 
13
13
  ## Files
14
14
 
@@ -41,6 +41,7 @@ wrangler secret put SUPPORT_TOKEN # one headless client
41
41
  wrangler secret put EXEC_TOKEN # another headless client
42
42
  wrangler secret put CLERK_SECRET_KEY
43
43
  wrangler secret put DOWNSTREAM_TOKEN
44
+ wrangler secret put CREDENTIAL_ENCRYPTION_KEY # base64 32-byte AES key
44
45
  wrangler deploy
45
46
  ```
46
47
 
@@ -49,9 +50,91 @@ Enable Dynamic Client Registration on the Clerk instance (OAuth Applications →
49
50
  DCR) so Claude/Cursor can self-register — full walkthrough in
50
51
  [setting up Clerk](../../documentation/auth.md).
51
52
 
53
+ ### Copied into its own repository
54
+
55
+ The `npm install` above is the connecta repository's, which already has every
56
+ dependency this file imports. A copy with its own `package.json` installs three
57
+ things, because two of them are not part of connecta and never install with it:
58
+
59
+ ```sh
60
+ npm install @zackbart/connecta @cloudflare/codemode @clerk/backend
61
+ ```
62
+
63
+ `@cloudflare/codemode` is the executor behind `execute_code`, and
64
+ `@clerk/backend` is the optional peer behind `@zackbart/connecta/auth/clerk` —
65
+ which `src/index.ts` imports at the top level, so wrangler must resolve it at
66
+ build time. Miss it and the build stops at
67
+ `Could not resolve "@clerk/backend"`, which is a missing peer rather than a
68
+ broken example. Drop `clerkAuth` from `auth` if this deployment has no operator
69
+ sign-in, and the peer goes with it — but read
70
+ [the operator surface](#the-operator-surface) first, because a deployment
71
+ without it can never write a credential or issue an access token.
72
+
52
73
  Then point an MCP client at `<PUBLIC_URL>/mcp`, and open `<PUBLIC_URL>/` for
53
74
  Connections. Credentials is at `/credentials`, named MCP access tokens are at
54
- `/tokens`, Activity is at `/activity`, and legacy `/ui` redirects to `/`.
75
+ `/tokens`, Activity is at `/activity`, and legacy `/ui` redirects to `/`. Each
76
+ of those three appears in the nav only when this deployment can serve it — see
77
+ the next section for what turns each one on.
78
+
79
+ ## The operator surface
80
+
81
+ This example ships the whole operator feature set. Three quarters of it is on
82
+ as deployed; the fourth needs a database, so it is commented in place.
83
+
84
+ **Operator sign-in** is the `clerkAuth` entry in `src/index.ts`, alongside two
85
+ static bearers. The split is deliberate: a bearer is a client key that may call
86
+ tools and read connector status, while writing a credential or issuing an
87
+ access token requires an interactive Clerk identity. Narrow who that can be
88
+ with `allowedDomains`, or with a `gate` for anything a domain cannot express.
89
+
90
+ **The credential vault** is `credentials: { encryptionKey: … }`, backed by the
91
+ same KV namespace as everything else and encrypted with the
92
+ `CREDENTIAL_ENCRYPTION_KEY` secret before a value reaches it. Generate one with:
93
+
94
+ ```sh
95
+ node -e "console.log(crypto.randomBytes(32).toString('base64'))"
96
+ ```
97
+
98
+ Leave the secret unset and the deployment still runs — `/credentials` stays
99
+ read-only and connecta says so at startup. Keep the key in Worker secrets and
100
+ nowhere near KV: it is the only thing that makes a copied namespace useless.
101
+ Rotation takes effect on the next call, with no redeploy and no liveness probe,
102
+ because credentials fail at use.
103
+
104
+ The vault is ready here, and the Credentials page is still hidden, because that
105
+ page lists connector credential slots rather than deployments. Neither
106
+ connector in `src/index.ts` declares one — Notion carries a deployment-owned
107
+ static header and echo has no secret — so nothing would be on the page. Add
108
+ `credential: { label: "API token" }` to an `api()` connector (the commented
109
+ shape on `echo` is exactly it) or use a provider connector such as `notion()`,
110
+ which declares its own, and Credentials appears for a signed-in operator on the
111
+ next load.
112
+
113
+ **Access tokens** are `accessTokens: {}`. A signed-in operator mints named,
114
+ revocable Bearer tokens at `/tokens` for header-capable clients that will not do
115
+ OAuth. Secrets are shown once and only their hashes enter KV; a lost token is
116
+ reissued, never recovered. Note the KV caveat above — revocation is visible
117
+ everywhere only as fast as the namespace converges.
118
+
119
+ **Activity** is the commented block in `src/index.ts` and the commented
120
+ `d1_databases` binding in `wrangler.jsonc`; the section below creates the
121
+ database and applies the schema.
122
+
123
+ None of these change what agents can reach. Operator routes manage the
124
+ authentication material behind capabilities `src/index.ts` already declares —
125
+ never the connector set, the tool catalog, or its annotations.
126
+
127
+ `connecta doctor` reports the same line here as for a deployment with none of
128
+ this on: connector count, executor, seven tools. The executor it names is this
129
+ one — `DynamicWorkerExecutor executed`, not the Node template's QuickJS, which
130
+ is what doctor used to claim everywhere
131
+ ([#368](https://github.com/zackbart/connecta/issues/368)). It carries a bearer, and a
132
+ bearer learns the model-facing surface rather than the deployment's
133
+ configuration topology. Confirm the operator surface the way an operator will:
134
+ sign in at `<PUBLIC_URL>/` and check that Tokens is live. Credentials joins it
135
+ once a connector declares a `credential` slot, and Activity once the D1 wiring
136
+ below is on — the nav shows a page when the deployment can actually serve it,
137
+ so a missing page is the honest report that its half is still off.
55
138
 
56
139
  ## Code mode
57
140
 
@@ -64,20 +147,19 @@ The required Worker Loader binding is checked into `wrangler.jsonc`:
64
147
  ```
65
148
 
66
149
  `src/index.ts` constructs `DynamicWorkerExecutor` from `env.LOADER` and serves
67
- the seven-tool surface. A deployment copied into its own repository must also
68
- install the executor package:
69
-
70
- ```sh
71
- npm install @cloudflare/codemode
72
- ```
150
+ the seven-tool surface. The executor package is one of the installs a copied
151
+ deployment owns see
152
+ [copied into its own repository](#copied-into-its-own-repository).
73
153
 
74
154
  ## Activity history (optional)
75
155
 
76
156
  `src/d1-activity.ts` is a complete `ActivityStore` over D1 — keyset paging on
77
- `(occurred_at_ms, id)` plus a batched retention pass — but it is **not wired
78
- into `src/index.ts`**, so the example deploys without a database. To enable it:
157
+ `(occurred_at_ms, id)` plus a batched retention pass — but the wiring in
158
+ `src/index.ts` is **commented out**, so the example deploys without a database.
159
+ To enable it:
79
160
 
80
- 1. Create the database and bind it in `wrangler.jsonc`:
161
+ 1. Create the database and uncomment the `d1_databases` binding in
162
+ `wrangler.jsonc`, pasting in the id it prints:
81
163
 
82
164
  ```sh
83
165
  wrangler d1 create connecta-activity
@@ -137,7 +219,9 @@ into `src/index.ts`**, so the example deploys without a database. To enable it:
137
219
  the failure and returns the tool result unharmed — so the symptom is not an
138
220
  error your agent sees, it is an activity log that quietly stops recording.
139
221
 
140
- 3. Pass the store to `createConnecta`:
222
+ 3. In `src/index.ts`, uncomment the `d1ActivityStore` import, the `ACTIVITY_DB`
223
+ field on `Env`, and the `activity` block — the three commented fragments
224
+ that together read:
141
225
 
142
226
  ```ts
143
227
  import { d1ActivityStore } from "./d1-activity.js";
@@ -6,16 +6,25 @@
6
6
  * namespace. The required Worker Loader binding in wrangler.jsonc backs the
7
7
  * seven-tool surface.
8
8
  *
9
+ * The operator surface is wired here except for activity history, which needs
10
+ * a database this example does not create for you: sign-in, the credential
11
+ * vault, and access-token issuance are on, and activity is three commented
12
+ * lines below. README.md § "The operator surface" walks through all four.
13
+ *
9
14
  * Setup (this example has no package.json of its own — it self-references the
10
15
  * installed `@zackbart/connecta` package):
11
16
  * 1. `npm install` in the connecta package root (../../ from here) so the
12
- * package import and wrangler resolve.
17
+ * package import and wrangler resolve. A copy in its own repository
18
+ * installs `@zackbart/connecta @cloudflare/codemode @clerk/backend`
19
+ * instead — the last two are not part of connecta, and the Clerk import
20
+ * below is an optional peer wrangler resolves at build time.
13
21
  * 2. Create a KV namespace and put its id in wrangler.jsonc under `kv_namespaces`.
14
22
  * 3. Set secrets:
15
23
  * wrangler secret put SUPPORT_TOKEN
16
24
  * wrangler secret put EXEC_TOKEN
17
25
  * wrangler secret put CLERK_SECRET_KEY
18
26
  * wrangler secret put DOWNSTREAM_TOKEN
27
+ * wrangler secret put CREDENTIAL_ENCRYPTION_KEY
19
28
  * and CLERK_PUBLISHABLE_KEY + PUBLIC_URL as plain vars in wrangler.jsonc.
20
29
  * 4. Enable Dynamic Client Registration in the Clerk dashboard
21
30
  * (OAuth Applications -> DCR toggle) so Claude/Cursor can self-register.
@@ -32,6 +41,8 @@ import {
32
41
  } from "@zackbart/connecta";
33
42
  import { clerkAuth } from "@zackbart/connecta/auth/clerk";
34
43
  import { cloudflareKvStorage } from "./cloudflare-kv.js";
44
+ // Activity history, off by default because it needs a D1 database.
45
+ // import { d1ActivityStore } from "./d1-activity.js";
35
46
 
36
47
  interface Env {
37
48
  CONNECTA_KV: KVNamespace;
@@ -41,8 +52,16 @@ interface Env {
41
52
  EXEC_TOKEN: string;
42
53
  CLERK_PUBLISHABLE_KEY: string;
43
54
  CLERK_SECRET_KEY: string;
55
+ /**
56
+ * Base64 32-byte AES key encrypting operator-managed credentials in KV.
57
+ * Unset means no vault: /credentials stays read-only and connecta says so at
58
+ * startup. Never put it in KV — it is what protects KV.
59
+ */
60
+ CREDENTIAL_ENCRYPTION_KEY: string;
44
61
  DOWNSTREAM_TOKEN: string;
45
62
  PUBLIC_URL: string;
63
+ /** Uncomment with the `d1_databases` binding to enable activity history. */
64
+ // ACTIVITY_DB: D1Database;
46
65
  /**
47
66
  * Worker Loader binding (wrangler.jsonc `worker_loaders`) powering
48
67
  * execute_code. Dynamic Workers require the Workers Paid plan.
@@ -73,9 +92,30 @@ function build(env: Env) {
73
92
  // allowedDomains: ["acme.com"],
74
93
  }),
75
94
  ],
95
+ // Connectors that declare a `credential` slot become editable at
96
+ // /credentials, encrypted with this key before anything reaches KV. A
97
+ // saved replacement takes effect on the next call — no redeploy, and no
98
+ // liveness probe: credentials fail at use.
99
+ //
100
+ // The key is the vault, not the page: /credentials is a list of connector
101
+ // slots, so it stays hidden until a connector declares one. Neither
102
+ // connector below does — Notion here carries a deployment-owned static
103
+ // header and echo has no secret at all — so this example ships the vault
104
+ // ready and the page empty. Declare a slot (see the commented shape on
105
+ // `echo`, or use a provider connector like `notion()`, which declares its
106
+ // own) and the page appears on the next load.
107
+ credentials: { encryptionKey: env.CREDENTIAL_ENCRYPTION_KEY },
76
108
  // Eligible Clerk operators can create named, revocable MCP Bearer tokens
77
109
  // at /tokens. Secrets are shown once; only their hashes enter KV.
78
110
  accessTokens: {},
111
+ // Payload-free activity at /activity, off until a database exists to hold
112
+ // it. Uncomment the `d1_databases` binding in wrangler.jsonc, apply the
113
+ // schema in README.md § "Activity history", then these three lines and the
114
+ // import above.
115
+ // activity: {
116
+ // store: d1ActivityStore(env.ACTIVITY_DB),
117
+ // deploymentId: "production",
118
+ // },
79
119
  connectors: [
80
120
  remoteMcp("notion", {
81
121
  url: "https://mcp.notion.com/mcp",
@@ -87,6 +127,11 @@ function build(env: Env) {
87
127
  }),
88
128
  api("echo", {
89
129
  description: "Echo — text transforms",
130
+ // What a vault-backed connector adds — an operator edits this slot at
131
+ // /credentials and the handler reads it with
132
+ // `await ctx.credential?.get()`, so the secret never lives in source
133
+ // or in a Worker variable:
134
+ // credential: { label: "API token" },
90
135
  tools: [
91
136
  {
92
137
  name: "shout",
@@ -7,7 +7,8 @@
7
7
  "observability": { "enabled": true },
8
8
 
9
9
  // Plain vars. Secrets (SUPPORT_TOKEN, EXEC_TOKEN, CLERK_SECRET_KEY,
10
- // DOWNSTREAM_TOKEN) are set with `wrangler secret put <NAME>`, not here.
10
+ // CREDENTIAL_ENCRYPTION_KEY, DOWNSTREAM_TOKEN) are set with
11
+ // `wrangler secret put <NAME>`, not here.
11
12
  "vars": {
12
13
  "PUBLIC_URL": "https://connecta.example.workers.dev",
13
14
  "CLERK_PUBLISHABLE_KEY": "pk_test_replace-me"
@@ -18,6 +19,18 @@
18
19
  { "binding": "CONNECTA_KV", "id": "replace-with-kv-namespace-id" }
19
20
  ],
20
21
 
22
+ // Activity history. Create with `wrangler d1 create connecta-activity`,
23
+ // paste the id, apply the schema in README.md, then uncomment the matching
24
+ // lines in src/index.ts. Off by default so this example deploys without a
25
+ // database.
26
+ // "d1_databases": [
27
+ // {
28
+ // "binding": "ACTIVITY_DB",
29
+ // "database_name": "connecta-activity",
30
+ // "database_id": "replace-with-d1-database-id"
31
+ // }
32
+ // ],
33
+
21
34
  // Required by execute_code; Dynamic Workers use the Workers Paid plan.
22
35
  "worker_loaders": [{ "binding": "LOADER" }]
23
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zackbart/connecta",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "description": "One MCP to rule them all — a single MCP endpoint aggregating many downstream connectors behind a code-first surface of seven meta-tools.",
@@ -31,10 +31,7 @@
31
31
  "files": [
32
32
  "bin",
33
33
  "dist",
34
- "src",
35
- "assets",
36
34
  "documentation",
37
- "examples/node",
38
35
  "examples/worker",
39
36
  "templates",
40
37
  "AGENTS.md",
@@ -100,10 +97,11 @@
100
97
  "check:docs": "node scripts/check-doc-links.mjs",
101
98
  "check:lint": "oxlint -D correctness --report-unused-disable-directives .",
102
99
  "check:unused": "knip --include dependencies,unlisted,exports,nsExports,types,nsTypes",
103
- "check:examples": "tsc -p tsconfig.examples.node.json && tsc -p tsconfig.examples.worker.json",
100
+ "check:examples": "tsc -p tsconfig.template.node.json && tsc -p tsconfig.examples.worker.json",
104
101
  "check:security": "npm audit --omit=dev --audit-level=moderate",
105
102
  "check:package": "node scripts/check-package.mjs",
106
103
  "load:admission": "npm run build && node scripts/admission-load.mjs",
104
+ "drift:check": "tsx scripts/drift-check.mjs",
107
105
  "check": "npm run check:docs && npm run check:operator-ui && npm run check:lint && npm run check:unused && npm run typecheck && npm run test && npm run build && npm run check:examples",
108
106
  "release:check": "npm run check && npm run check:security && npm run check:package",
109
107
  "prepack": "npm run check",
@@ -137,6 +135,7 @@
137
135
  "esbuild": "^0.28.0",
138
136
  "knip": "^6.29.0",
139
137
  "oxlint": "^1.76.0",
138
+ "preact": "^10.29.8",
140
139
  "quickjs-emscripten": "^0.32.0",
141
140
  "tsx": "^4.23.1",
142
141
  "typescript": "^5.6.0",
@@ -0,0 +1,10 @@
1
+ # The image installs its own dependencies and never needs local state or
2
+ # secrets in the build context.
3
+ node_modules
4
+ .git
5
+ .env
6
+ .connecta-state.json
7
+ .connecta-activity.jsonl
8
+ *.log
9
+ Dockerfile
10
+ docker-compose.yml