@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.
- package/AGENTS.md +11 -11
- package/CHANGELOG.md +128 -1
- package/README.md +15 -19
- package/bin/connecta.mjs +10 -6
- package/dist/access-tokens.js +13 -2
- package/dist/activity.d.ts +5 -4
- package/dist/activity.js +1 -10
- package/dist/artifacts/connector.d.ts +3 -0
- package/dist/artifacts/connector.js +6 -0
- package/dist/artifacts/document.js +6 -0
- package/dist/artifacts/json.js +10 -0
- package/dist/artifacts/module.js +7 -1
- package/dist/artifacts/operations.d.ts +3 -1
- package/dist/artifacts/operations.js +69 -11
- package/dist/artifacts/refresh.d.ts +2 -0
- package/dist/artifacts/refresh.js +3 -2
- package/dist/artifacts/routes.js +4 -15
- package/dist/artifacts/validate.js +2 -4
- package/dist/auth/downstream-oauth.js +95 -36
- package/dist/catalog-service.js +1 -1
- package/dist/connector-scope.d.ts +2 -0
- package/dist/connector-scope.js +27 -0
- package/dist/connectors/api.js +8 -2
- package/dist/connectors/remote-mcp.js +57 -17
- package/dist/credentials.js +2 -1
- package/dist/execute.js +107 -368
- package/dist/executors/quickjs.js +1 -1
- package/dist/exempt-writes.js +162 -0
- package/dist/index.d.ts +12 -31
- package/dist/index.js +15 -47
- package/dist/invocation.js +6 -19
- package/dist/meta-tools.js +1 -2
- package/dist/oauth-partition.js +32 -0
- package/dist/operator-ui/generated.js +1 -1
- package/dist/operator-ui/model.d.ts +3 -3
- package/dist/operator-ui/view.js +6 -4
- package/dist/providers/notion.js +4 -1
- package/dist/providers/vercel.js +13 -9
- package/dist/registry.d.ts +1 -0
- package/dist/registry.js +58 -25
- package/dist/routes/credentials.js +16 -8
- package/dist/routes/mcp.js +5 -21
- package/dist/routes/oauth.js +70 -64
- package/dist/routes/shared.d.ts +1 -6
- package/dist/runtime/run.js +7 -1
- package/dist/server.js +0 -4
- package/dist/skills.js +8 -23
- package/dist/storage/file.js +57 -20
- package/dist/storage/memory.js +8 -1
- package/dist/tool-safety.d.ts +1 -1
- package/dist/tool-safety.js +1 -1
- package/dist/types.d.ts +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +51 -14
- package/documentation/auth.md +7 -4
- package/documentation/code-mode.md +91 -194
- package/documentation/meta-tools.md +38 -41
- package/ethos.md +6 -7
- package/examples/worker/README.md +14 -17
- package/examples/worker/src/d1-activity-row.ts +4 -1
- package/examples/worker/src/d1-storage.ts +27 -5
- package/examples/worker/src/index.ts +1 -9
- package/package.json +4 -4
- package/templates/node/AGENTS.md +4 -3
- package/templates/node/README.md +3 -4
- package/templates/node/package.json +2 -2
- package/dist/resumable.js +0 -1291
- 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
|
|
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
|
|
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
|
|
197
|
-
Alchemy deploy or adoption
|
|
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
|
|
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
|
|
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
|
|
332
|
-
|
|
333
|
-
|
|
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
|
|
481
|
-
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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.
|
|
189
|
+
"typescript": "^5.9.3",
|
|
190
190
|
"vitest": "^4.1.6",
|
|
191
191
|
"wrangler": "^4.114.0"
|
|
192
192
|
}
|
package/templates/node/AGENTS.md
CHANGED
|
@@ -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
|
|
7
|
-
surface.
|
|
8
|
-
write
|
|
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.
|
package/templates/node/README.md
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
|
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.
|
|
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.
|
|
24
|
+
"typescript": "^5.9.3"
|
|
25
25
|
}
|
|
26
26
|
}
|