@zackbart/connecta 0.26.3 → 0.28.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 (69) hide show
  1. package/AGENTS.md +11 -11
  2. package/CHANGELOG.md +128 -1
  3. package/README.md +15 -19
  4. package/bin/connecta.mjs +10 -6
  5. package/dist/access-tokens.js +13 -2
  6. package/dist/activity.d.ts +5 -4
  7. package/dist/activity.js +1 -10
  8. package/dist/artifacts/connector.d.ts +3 -0
  9. package/dist/artifacts/connector.js +6 -0
  10. package/dist/artifacts/document.js +6 -0
  11. package/dist/artifacts/json.js +10 -0
  12. package/dist/artifacts/module.js +7 -1
  13. package/dist/artifacts/operations.d.ts +3 -1
  14. package/dist/artifacts/operations.js +69 -11
  15. package/dist/artifacts/refresh.d.ts +2 -0
  16. package/dist/artifacts/refresh.js +3 -2
  17. package/dist/artifacts/routes.js +4 -15
  18. package/dist/artifacts/validate.js +2 -4
  19. package/dist/auth/downstream-oauth.js +95 -36
  20. package/dist/catalog-service.js +1 -1
  21. package/dist/connector-scope.d.ts +2 -0
  22. package/dist/connector-scope.js +27 -0
  23. package/dist/connectors/api.js +8 -2
  24. package/dist/connectors/remote-mcp.js +57 -17
  25. package/dist/credentials.js +2 -1
  26. package/dist/execute.js +107 -368
  27. package/dist/executors/quickjs.js +1 -1
  28. package/dist/exempt-writes.js +162 -0
  29. package/dist/index.d.ts +12 -31
  30. package/dist/index.js +15 -47
  31. package/dist/invocation.js +6 -19
  32. package/dist/meta-tools.js +1 -2
  33. package/dist/oauth-partition.js +32 -0
  34. package/dist/operator-ui/generated.js +1 -1
  35. package/dist/operator-ui/model.d.ts +3 -3
  36. package/dist/operator-ui/view.js +6 -4
  37. package/dist/providers/notion.js +4 -1
  38. package/dist/providers/vercel.js +13 -9
  39. package/dist/registry.d.ts +1 -0
  40. package/dist/registry.js +58 -25
  41. package/dist/routes/credentials.js +16 -8
  42. package/dist/routes/mcp.js +5 -21
  43. package/dist/routes/oauth.js +70 -64
  44. package/dist/routes/shared.d.ts +1 -6
  45. package/dist/runtime/run.js +7 -1
  46. package/dist/server.js +0 -4
  47. package/dist/skills.js +8 -23
  48. package/dist/storage/file.js +57 -20
  49. package/dist/storage/memory.js +8 -1
  50. package/dist/tool-safety.d.ts +1 -1
  51. package/dist/tool-safety.js +1 -1
  52. package/dist/types.d.ts +2 -2
  53. package/dist/version.d.ts +1 -1
  54. package/dist/version.js +1 -1
  55. package/documentation/architecture.md +51 -14
  56. package/documentation/auth.md +7 -4
  57. package/documentation/code-mode.md +91 -194
  58. package/documentation/meta-tools.md +38 -41
  59. package/ethos.md +6 -7
  60. package/examples/worker/README.md +14 -17
  61. package/examples/worker/src/d1-activity-row.ts +4 -1
  62. package/examples/worker/src/d1-storage.ts +27 -5
  63. package/examples/worker/src/index.ts +1 -9
  64. package/package.json +4 -4
  65. package/templates/node/AGENTS.md +4 -3
  66. package/templates/node/README.md +3 -4
  67. package/templates/node/package.json +2 -2
  68. package/dist/resumable.js +0 -1291
  69. package/dist/run-journal.js +0 -347
@@ -2,7 +2,7 @@
2
2
 
3
3
  A deployable Worker that aggregates a downstream remote MCP and an in-code HTTP
4
4
  API connector, guarded by Cloudflare Access, with state in a KV namespace. Its
5
- required Worker Loader binding backs the eight-tool surface and requires the
5
+ required Worker Loader binding backs the seven-tool surface and requires the
6
6
  Workers Paid plan.
7
7
 
8
8
  This is also the **starting template for a deployment**: a real deployment
@@ -118,7 +118,7 @@ Keep this example's Worker on Wrangler. A copied deployment may use
118
118
  [Alchemy](https://alchemy.run) to manage its KV namespace, optional D1 databases
119
119
  and R2 bucket, and the Access application without adding Alchemy to connecta or
120
120
  changing this template. In that deployment, install `alchemy@2.0.0-beta.79`
121
- and `effect@4.0.0-rc.117` at exact versions. The latter is connecta's own
121
+ and `effect@4.0.0` at exact versions. The latter is connecta's own
122
122
  Effect pin; check `npm ls effect` for one resolved copy after installation.
123
123
  The following `alchemy.run.ts` is a resource stack, not a Worker deployment:
124
124
 
@@ -193,8 +193,9 @@ deleting the state store loses Alchemy's record of what it owns.
193
193
 
194
194
  Verification for this optional path stops at the API and types. The fenced
195
195
  snippet was copied to an isolated `alchemy.run.ts` and passed `tsc --noEmit`
196
- with TypeScript 5.9.3, Alchemy 2.0.0-beta.79, and Effect 4.0.0-rc.117; no
197
- Alchemy deploy or adoption was run.
196
+ with TypeScript 5.9.3, Alchemy 2.0.0-beta.79, and Effect 4.0.0, with
197
+ `npm ls effect` confirming one resolved copy. No Alchemy deploy or adoption
198
+ was run.
198
199
  [Alchemy's Worker resource](https://alchemy.run/cloudflare/compute/workers/)
199
200
  can declare `WorkerLoader`, domains, Browser Rendering, and cron wiring, but
200
201
  those properties are coupled to its script deploy. Its current full deploy
@@ -295,7 +296,7 @@ below. Omit the module and store wiring to record no history and show no
295
296
  Activity tab. Diagnostics remain independent; `logger: "silent"` suppresses
296
297
  them explicitly.
297
298
 
298
- Verify MCP health and the exact eight tools with:
299
+ Verify MCP health and the exact seven tools with:
299
300
 
300
301
  ```sh
301
302
  CF_ACCESS_CLIENT_ID=… CF_ACCESS_CLIENT_SECRET=… \
@@ -320,7 +321,7 @@ The required Worker Loader binding is checked into `wrangler.jsonc`:
320
321
  ```
321
322
 
322
323
  `src/index.ts` constructs `DynamicWorkerExecutor` with only `env.LOADER` and
323
- serves the eight-tool surface. Do not add `bindings`, `modules`, or
324
+ serves the seven-tool surface. Do not add `bindings`, `modules`, or
324
325
  `globalOutbound`; they grant guest code ambient authority. A copied deployment
325
326
  owns the package install — see
326
327
  [copied into its own repository](#copied-into-its-own-repository).
@@ -328,11 +329,9 @@ owns the package install — see
328
329
  ## Strongly consistent storage (optional)
329
330
 
330
331
  `KVStorage` has an optional atomic `compareAndSet`, for a subsystem that must
331
- claim a key exactly once — resumable writes, where two `resume_execution` calls
332
- racing for one paused program must send its writes once. **Workers KV cannot
333
- provide it**, so on KV this example turns resumable writes off
334
- (`execute: { resumableWrites: false }` in `src/index.ts`) and its programs
335
- refuse writes. It is eventually
332
+ claim a key exactly once: artifacts commit every write by it, issuing access
333
+ tokens requires it, and an OAuth handoff binds its owner with it when it is
334
+ there. **Workers KV cannot provide it.** It is eventually
336
335
  consistent: two locations can each read a key as absent and both write, so
337
336
  `cloudflare-kv.ts` declares no `compareAndSet` rather than fake one with a read
338
337
  followed by a write. `src/d1-storage.ts` is a complete `KVStorage` over D1
@@ -369,10 +368,6 @@ of connecta state lives in one store.
369
368
  const storage = d1Storage(env.STORAGE_DB);
370
369
  ```
371
370
 
372
- Then delete the `execute: { resumableWrites: false }` line: on D1,
373
- resumable writes default on, and programs pause at a write until
374
- `resume_execution` approves it.
375
-
376
371
  Switching an existing deployment starts from empty state: downstream OAuth
377
372
  connections must be re-authorized and vault credentials re-entered, because
378
373
  nothing copies them out of the KV namespace.
@@ -477,8 +472,10 @@ To enable it:
477
472
  ALTER TABLE tool_call_activity ADD COLUMN approval TEXT;
478
473
  ```
479
474
 
480
- `approval` is set only on an `approved` row — the scope a resumed
481
- program's approval covered, `call` or `tool` — and is otherwise null.
475
+ `approval` is history: it is set only on an `approved` row written before
476
+ 0.28.0 removed program pauses — the scope a resumed program's approval
477
+ covered, `call` or `tool` — and is null on every row since. Keep the
478
+ column; the Activity tab still renders those older rows.
482
479
 
483
480
  `friction` is stored rather than derived because one of its classes belongs
484
481
  to a call that *succeeded*: a result too large to return inline is friction
@@ -19,12 +19,14 @@ export interface ActivityEvent {
19
19
  | "call_destructive_tool"
20
20
  | "batch_call"
21
21
  | "execute_code"
22
+ // History only: rows written before 0.28.0 removed program pauses.
22
23
  | "resume_execution";
23
24
  outcome:
24
25
  | "success"
25
26
  | "error"
26
27
  | "timeout"
27
28
  | "cancelled"
29
+ // History only, like `resume_execution` above.
28
30
  | "paused"
29
31
  | "approved";
30
32
  durationMs: number;
@@ -36,6 +38,7 @@ export interface ActivityEvent {
36
38
  | "destructive_reroute"
37
39
  | "auth_required"
38
40
  | "result_too_large";
41
+ /** History only: the scope an `approved` row covered, before 0.28.0. */
39
42
  approval?: "call" | "tool";
40
43
  serverName: string;
41
44
  serverVersion: string;
@@ -84,7 +87,7 @@ export interface ActivityRow {
84
87
  attempts: number;
85
88
  error_code: string | null;
86
89
  friction: ActivityEvent["friction"] | null;
87
- /** Absent from tables created before resumable writes; read as null. */
90
+ /** Absent from tables created before 0.26.0; null on every row since 0.28.0. */
88
91
  approval?: ActivityEvent["approval"] | null;
89
92
  server_name: string;
90
93
  server_version: string;
@@ -18,8 +18,17 @@ import type { KVStorage } from "@zackbart/connecta";
18
18
  export function d1Storage(db: D1Database): KVStorage {
19
19
  // Every statement that tests liveness binds the current time as ?2.
20
20
  const live = "(expires_at_ms IS NULL OR expires_at_ms > ?2)";
21
- const expiresAt = (now: number, ttlSeconds?: number) =>
22
- ttlSeconds ? now + ttlSeconds * 1000 : null;
21
+ const expiresAt = (now: number, ttlSeconds?: number): number | null => {
22
+ if (ttlSeconds !== undefined && !Number.isFinite(ttlSeconds)) {
23
+ throw new RangeError("ttlSeconds must produce a finite expiration timestamp");
24
+ }
25
+ if (!ttlSeconds) return null;
26
+ const expiry = now + ttlSeconds * 1000;
27
+ if (!Number.isFinite(expiry)) {
28
+ throw new RangeError("ttlSeconds must produce a finite expiration timestamp");
29
+ }
30
+ return expiry;
31
+ };
23
32
  const changed = (result: D1Result) => result.meta.changes > 0;
24
33
  return {
25
34
  async get(key) {
@@ -31,6 +40,7 @@ export function d1Storage(db: D1Database): KVStorage {
31
40
  },
32
41
  async set(key, value, opts) {
33
42
  const now = Date.now();
43
+ const expiry = expiresAt(now, opts?.ttlSeconds);
34
44
  await db.batch([
35
45
  db
36
46
  .prepare(
@@ -46,7 +56,7 @@ export function d1Storage(db: D1Database): KVStorage {
46
56
  ON CONFLICT (key) DO UPDATE SET
47
57
  value = excluded.value, expires_at_ms = excluded.expires_at_ms`,
48
58
  )
49
- .bind(key, value, expiresAt(now, opts?.ttlSeconds)),
59
+ .bind(key, value, expiry),
50
60
  ]);
51
61
  },
52
62
  async delete(key) {
@@ -56,7 +66,7 @@ export function d1Storage(db: D1Database): KVStorage {
56
66
  const { results } = await db
57
67
  .prepare(
58
68
  `SELECT key FROM connecta_kv
59
- WHERE key >= ?1 AND substr(key, 1, length(?1)) = ?1 AND ${live}`,
69
+ WHERE key >= ?1 AND substr(CAST(key AS BLOB), 1, length(CAST(?1 AS BLOB))) = CAST(?1 AS BLOB) AND ${live}`,
60
70
  )
61
71
  .bind(prefix, Date.now())
62
72
  .all<{ key: string }>();
@@ -84,7 +94,19 @@ export function d1Storage(db: D1Database): KVStorage {
84
94
  .run(),
85
95
  );
86
96
  }
87
- const expiry = expiresAt(now, opts?.ttlSeconds);
97
+ let expiry: number | null;
98
+ try {
99
+ expiry = expiresAt(now, opts?.ttlSeconds);
100
+ } catch (error) {
101
+ // Invalid write options do not change a failed comparison's result.
102
+ // Ordinary claims still use one atomic statement without this read.
103
+ const current = await db
104
+ .prepare(`SELECT value FROM connecta_kv WHERE key = ?1 AND ${live}`)
105
+ .bind(key, now)
106
+ .first<{ value: string }>();
107
+ if ((current?.value ?? null) !== expected) return false;
108
+ throw error;
109
+ }
88
110
  if (expected === null) {
89
111
  // Insert, or take over a row that has expired. A live row makes the
90
112
  // upsert's WHERE false, so nothing changes and the claim is refused.
@@ -8,7 +8,7 @@ import { encryptedCredentialVault } from "@zackbart/connecta/credentials";
8
8
  * by Cloudflare Access, with OAuth/cache state in a KV namespace. Access
9
9
  * authenticates the request before this Worker runs and supplies the trusted
10
10
  * identity through ctx.access. The required Worker Loader binding in
11
- * wrangler.jsonc backs the eight-tool surface.
11
+ * wrangler.jsonc backs the seven-tool surface.
12
12
  *
13
13
  * The operator surface is wired here except for activity history, which needs
14
14
  * a database this example does not create for you: sign-in and the credential
@@ -76,14 +76,6 @@ function build(env: Env) {
76
76
  publicUrl: env.PUBLIC_URL,
77
77
  storage,
78
78
  executor: new DynamicWorkerExecutor({ loader: env.LOADER }),
79
- // Resumable writes — a program pausing at a write until resume_execution
80
- // approves it — need storage with an atomic compareAndSet, and Workers KV
81
- // cannot provide one. On KV they are off: programs refuse writes and
82
- // resume_execution says there is nothing to resume. This line states that
83
- // choice (without it connecta would warn once at startup). To let programs
84
- // pause, switch to the D1 store in README.md § "Strongly consistent
85
- // storage" and delete this line.
86
- execute: { resumableWrites: false },
87
79
  auth: [
88
80
  // Access owns admission policy. A human identity may use MCP and the
89
81
  // operator pages; a service token may use MCP but cannot mutate operator
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@zackbart/connecta",
3
- "version": "0.26.3",
3
+ "version": "0.28.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
- "description": "One MCP to rule them all — a single MCP endpoint aggregating many downstream connectors behind a code-first surface of eight meta-tools.",
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.",
7
7
  "license": "MIT",
8
8
  "engines": {
9
9
  "node": ">=22.0.0"
@@ -154,7 +154,7 @@
154
154
  "@cfworker/json-schema": "^4.1.1",
155
155
  "@modelcontextprotocol/client": "2.0.0",
156
156
  "@modelcontextprotocol/server": "2.0.0",
157
- "effect": "4.0.0-rc.117",
157
+ "effect": "4.0.0",
158
158
  "zod": "^4.4.3"
159
159
  },
160
160
  "peerDependencies": {
@@ -186,7 +186,7 @@
186
186
  "preact": "^10.29.8",
187
187
  "quickjs-emscripten": "^0.32.0",
188
188
  "tsx": "^4.23.1",
189
- "typescript": "^5.6.0",
189
+ "typescript": "^5.9.3",
190
190
  "vitest": "^4.1.6",
191
191
  "wrangler": "^4.114.0"
192
192
  }
@@ -3,9 +3,10 @@
3
3
  This repository is deployment configuration, not a copy of Connecta itself.
4
4
 
5
5
  - Edit `src/index.ts` for connectors, authentication, storage, and public URL.
6
- - Keep `executor: quickJsExecutor()` for the prescribed eight-tool code-first
7
- surface. File storage supports resumable writes, so a program pauses at a
8
- write until `resume_execution` approves it.
6
+ - Keep `executor: quickJsExecutor()` for the prescribed seven-tool code-first
7
+ surface. Programs run read-only tools and any writes `execute.approval`
8
+ exempts; every other write is its own `call_destructive_tool` call, which
9
+ the MCP host asks about.
9
10
  - Keep credentials in environment variables or an external secret store.
10
11
  Never commit `.env`, `.connecta-state.json`, `.connecta-activity.jsonl`,
11
12
  tokens, or credential values.
@@ -159,17 +159,16 @@ npm run typecheck
159
159
  CONNECTA_TOKEN=dev-token npm run doctor
160
160
  ```
161
161
 
162
- Doctor checks health, the executor, and the exact prescribed eight-tool
162
+ Doctor checks health, the executor, and the exact prescribed seven-tool
163
163
  model-facing surface by running a harmless sandbox program. It reads the bearer
164
164
  from `CONNECTA_TOKEN`; it never accepts the secret as a command-line argument.
165
165
  Remote URLs must use HTTPS.
166
166
 
167
167
  A fully-wired deployment reports exactly the same line as a bare one —
168
- connector count, QuickJS executed, eight tools, plus any catalog drift, and
169
- a note if resumable writes are off:
168
+ connector count, QuickJS executed, seven tools, plus any catalog drift:
170
169
 
171
170
  ```text
172
- Connecta doctor passed: 1 connector(s), QuickJS executed, prescribed eight-tool surface.
171
+ Connecta doctor passed: 1 connector(s), QuickJS executed, prescribed seven-tool surface.
173
172
  ```
174
173
 
175
174
  `QuickJS` is this deployment's sandbox, reported by the deployment itself —
@@ -15,12 +15,12 @@
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
- "@zackbart/connecta": "0.26.3",
18
+ "@zackbart/connecta": "0.28.0",
19
19
  "quickjs-emscripten": "0.32.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^22.0.0",
23
23
  "tsx": "^4.23.1",
24
- "typescript": "^5.6.0"
24
+ "typescript": "^5.9.3"
25
25
  }
26
26
  }