@zackbart/connecta 0.20.0 → 0.21.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/CHANGELOG.md +49 -0
- package/README.md +3 -1
- package/bin/connecta.mjs +23 -6
- package/dist/auth/bearer.d.ts +2 -2
- package/dist/auth/bearer.js +2 -2
- package/dist/auth/clerk.js +1 -0
- package/dist/auth/cloudflare-access.d.ts +8 -0
- package/dist/auth/cloudflare-access.js +66 -0
- package/dist/execute.js +12 -6
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/meta-tools.js +3 -3
- package/dist/operator-ui/generated.js +1 -1
- package/dist/operator-ui/model.d.ts +3 -3
- package/dist/operator-ui/view.d.ts +1 -1
- package/dist/operator-ui/view.js +6 -3
- package/dist/routes/access-tokens.d.ts +1 -1
- package/dist/routes/access-tokens.js +2 -2
- package/dist/routes/activity.js +2 -2
- package/dist/routes/credentials.js +1 -1
- package/dist/routes/mcp.js +1 -1
- package/dist/routes/oauth.js +1 -1
- package/dist/routes/shared.d.ts +4 -4
- package/dist/routes/shared.js +10 -10
- package/dist/routes/ui.js +12 -9
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +7 -4
- package/dist/types.d.ts +37 -22
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +7 -4
- package/documentation/auth.md +71 -7
- package/documentation/code-mode.md +4 -4
- package/documentation/meta-tools.md +22 -20
- package/documentation/operations.md +23 -8
- package/documentation/operator-ui.md +21 -5
- package/documentation/upgrading.md +76 -4
- package/ethos.md +2 -3
- package/examples/worker/README.md +52 -32
- package/examples/worker/src/index.ts +32 -38
- package/examples/worker/wrangler.jsonc +12 -4
- package/package.json +5 -1
- package/templates/node/package.json +1 -1
|
@@ -45,6 +45,10 @@ one.
|
|
|
45
45
|
npx @zackbart/connecta init my-deployment
|
|
46
46
|
cd my-deployment && npm install && npm start
|
|
47
47
|
CONNECTA_TOKEN=… npx connecta doctor --url http://localhost:8787
|
|
48
|
+
|
|
49
|
+
# A Worker protected by Cloudflare Access
|
|
50
|
+
CF_ACCESS_CLIENT_ID=… CF_ACCESS_CLIENT_SECRET=… \
|
|
51
|
+
npx connecta doctor --url https://connecta.example.workers.dev
|
|
48
52
|
```
|
|
49
53
|
|
|
50
54
|
`init` copies the template, pins the generated deployment to the CLI package's
|
|
@@ -59,12 +63,17 @@ on the way out: `QuickJS` on the Node template, `DynamicWorkerExecutor` on the
|
|
|
59
63
|
Worker example, and `code executed` when an executor identifies as nothing —
|
|
60
64
|
a checker that asserts a sandbox it never saw is worse than one that says it
|
|
61
65
|
does not know ([#368](https://github.com/zackbart/connecta/issues/368)). It
|
|
62
|
-
refuses to send
|
|
66
|
+
refuses to send authentication credentials over remote plaintext HTTP, and it
|
|
63
67
|
*reports* catalog drift without failing on it — an unclassified downstream tool
|
|
64
68
|
already fails closed onto `call_destructive_tool`, so drift is a maintainer's
|
|
65
69
|
next task rather than a broken deployment
|
|
66
70
|
([#343](https://github.com/zackbart/connecta/issues/343)).
|
|
67
71
|
|
|
72
|
+
For an Access-protected Worker, doctor sends the service-token pair to both
|
|
73
|
+
`/health` and `/mcp`; Access authenticates those requests before connecta runs.
|
|
74
|
+
`CONNECTA_TOKEN` remains the Node and legacy Worker path, and may be supplied
|
|
75
|
+
alongside the Access pair during rollback testing. A partial pair is refused.
|
|
76
|
+
|
|
68
77
|
### Configuration
|
|
69
78
|
|
|
70
79
|
Structural seams stay top-level; tuning is grouped by subsystem. Every group is
|
|
@@ -74,7 +83,7 @@ optional.
|
|
|
74
83
|
| --- | --- | --- |
|
|
75
84
|
| `connectors` | — (required) | the connector set ([connectors](./connectors.md)) |
|
|
76
85
|
| `executor` | — (required) | the sandbox `execute_code` runs in ([code mode](./code-mode.md#what-an-executor-must-implement)) |
|
|
77
|
-
| `auth?` | none ⇒ open (dev only) | one `InboundAuth` or an array; bearer providers are checked before
|
|
86
|
+
| `auth?` | none ⇒ open (dev only) | one `InboundAuth` or an array; bearer providers are checked before interactive providers ([inbound auth](./auth.md)) |
|
|
78
87
|
| `storage?` | `memoryStorage()` | the one state seam for catalogs, result paging, credentials, and access tokens ([storage](./storage-and-credentials.md)) |
|
|
79
88
|
| `publicUrl?` | per-request origin | public base URL; an HTTPS value also redirects inbound HTTP |
|
|
80
89
|
| `logger?` | `console`, prefixed `[connecta]` | `{ debug, info, warn, error }` |
|
|
@@ -83,7 +92,7 @@ optional.
|
|
|
83
92
|
| `deploymentInfo?` | unset | arbitrary metadata exposed by `/health` |
|
|
84
93
|
| `activity?` | unset | `{ store, readGate?, deploymentId? }` — payload-free activity storage, an optional operator-read gate, and a stable event label |
|
|
85
94
|
| `credentials.encryptionKey?` | unset | base64 32-byte AES key for the connector vault. Without it, connectors declaring `credential` warn and their slots stay unmanageable |
|
|
86
|
-
| `accessTokens?` | unset | `{ maxActive? }` (default 100) for operator-issued MCP bearer tokens. Requires
|
|
95
|
+
| `accessTokens?` | unset | `{ maxActive? }` (default 100) for operator-issued MCP bearer tokens. Requires an interactive operator provider, or construction throws ([access tokens](./auth.md#operator-issued-access-tokens)) |
|
|
87
96
|
| `discovery.concurrency?` | 4 | connector catalogs/status probes in flight at once |
|
|
88
97
|
| `discovery.catalogTtlSeconds?` | 300 | fresh TTL for cached tool lists |
|
|
89
98
|
| `discovery.persistCatalog?` | true | persist serializable catalogs as a manifest plus revision-addressed chunks |
|
|
@@ -214,7 +223,7 @@ in.
|
|
|
214
223
|
|
|
215
224
|
| Suite | Covers |
|
|
216
225
|
| --- | --- |
|
|
217
|
-
| `access-tokens.test.ts` | the `AccessTokenManager` — a one-time secret created, authenticated, renamed, and revoked, bounded names and active count, enumerable storage required, a deployment with no
|
|
226
|
+
| `access-tokens.test.ts` | the `AccessTokenManager` — a one-time secret created, authenticated, renamed, and revoked, bounded names and active count, enumerable storage required, a deployment with no interactive operator refused — and the operator-only routes, down to historical activity still resolving a revoked token's name |
|
|
218
227
|
| `activity.test.ts` | payload-free delivery: a rejected async write attaches to `waitUntil` instead of throwing, approved destructive calls record under their real entry point, result-size friction records without retaining the result, and a hallucinated connector id or invented identity is clamped so the event still cannot carry a payload |
|
|
219
228
|
| `api-connector.test.ts` | `api()` — kind, description, tool defs, dispatch, default args, unknown tools, handler throws, argument validation, and the construction contract |
|
|
220
229
|
| `bearer.test.ts` | constant-time bearer compare, case-insensitive scheme, 401 challenges, and the retired audience options refusing rather than silently unbinding |
|
|
@@ -223,6 +232,7 @@ in.
|
|
|
223
232
|
| `catalog-drift.test.ts` | `vettedCatalog()`, `detectCatalogDrift()`, and `withVettedCatalog()`; drift on the registry surface and on `/health`; the connector seam projected rather than echoed; and the drift types being public |
|
|
224
233
|
| `catalog.test.ts` | lexical ranking and the compact schema renderer — `const`, `allOf` beside siblings, `$ref`, the depth limit, per-schema caching, and 2020-12 keyword compatibility |
|
|
225
234
|
| `clerk.test.ts` | protected-resource metadata, the browser sign-in config, OAuth and session tokens, cached best-effort activity labels with their caps, the hand-applied `azp` rejection, and the `allowedDomains` allowlist including every lookalike that must not be repaired into a match |
|
|
235
|
+
| `cloudflare-access-auth.test.ts` | trusted `ctx.access` human and service identities, absent/error fail-closed behavior, service-token MCP admission without operator mutation, human same-origin mutation, and the Clerk-to-ambient shell switch |
|
|
226
236
|
| `cloudflare-provider.test.ts` | `cloudflare()` construction, tool surface, request building, projections, typed failures, and credential test |
|
|
227
237
|
| `code-first-surface.test.ts` | the seven-tool surface itself — an executor required, every removed option and top-level tool refused, compact always-loaded routing pinned below 1,000 characters, complete on-demand usage served, and `connecta.ui` findable before connector search |
|
|
228
238
|
| `codemode-compat.test.ts` | the `Executor` seam staying structurally compatible with `@cloudflare/codemode`'s `DynamicWorkerExecutor`, enforced by `tsc` |
|
|
@@ -244,7 +254,7 @@ in.
|
|
|
244
254
|
| `mixpanel-provider.test.ts` | the Mixpanel proxy, its conditional-input guide and complete reviewed schema-digest manifest |
|
|
245
255
|
| `notion-provider.test.ts` | Notion's deliberate tool surface, including declined expanded page inputs, request construction, lean projections, both pagination conventions, error mapping, and writes |
|
|
246
256
|
| `operator-boundary.test.ts` | the operator row of the decisions table, after every mutation route: authentication material managed without moving a declared structure, and the one honest exception — a credential write making a remote catalog appear, which is discovery arriving, not an operator editing the deployment |
|
|
247
|
-
| `operator-store.test.ts` | `src/operator-ui/app/store.ts` against a fake browser: the Clerk listener, `gate()`, the generation fence, and the request path |
|
|
257
|
+
| `operator-store.test.ts` | `src/operator-ui/app/store.ts` against a fake browser: the Clerk listener, ambient Access requests without a browser-readable token, `gate()`, the generation fence, and the request path |
|
|
248
258
|
| `provider-conventions.test.ts` | the conventions a test can hold: hand-written providers refusing schemas they cannot enforce (H5), their compact discovery schemas staying complete (H7), Cloudflare stating its second pagination convention in the schema (H10), and Notion saying it has no escape hatch (H14) |
|
|
249
259
|
| `provider-registry.test.ts` | all six maintained providers inside real deployments: boot, description, address, catalog, storage, credential, admission, and activity isolation; plus provider-specific discovery and guide contracts |
|
|
250
260
|
| `registry.test.ts` | construction and id validation, startup warnings, address resolution, version 2 catalog TTL/persistence/completeness, agent-only stale-while-revalidate with cross-request single-flight shared with blocking reads in both start orders, owned teardown, invalidation/fingerprint guards, blocking diagnostics, and broken-connector isolation |
|
|
@@ -272,13 +282,13 @@ justification for *not* re-running it in workerd, so "it was easier" is not one.
|
|
|
272
282
|
| --- | --- | --- |
|
|
273
283
|
| `deployment-shapes.test.ts` | the Worker as the only example with a loader-only sandbox, one Node template that is also its own container, the same source running locally and in the container, the Node template's pinned esbuild install-script approval, the full operator surface in both, a template that cannot start on its own `.env.example`, a Worker README naming every optional peer its entrypoint imports, and the initializer's `.gitignore` staying in step | walks the template and example trees with Node filesystem APIs |
|
|
274
284
|
| `doc-links.test.ts` | the documentation checker itself — local file and fragment resolution, repository URLs resolved back to the checkout, duplicate heading slugs, fenced-code exclusion, and useful failures | spawns the Node checker against filesystem fixtures |
|
|
275
|
-
| `doctor-cli.test.ts` | `connecta doctor`'s executor line end to end — the sandbox the deployment reports is the one named, an unidentifiable executor gets an executor-neutral line,
|
|
285
|
+
| `doctor-cli.test.ts` | `connecta doctor`'s executor line and credentials end to end — the sandbox the deployment reports is the one named, an unidentifiable executor gets an executor-neutral line, a hostile name is bounded, and a complete Cloudflare Access service-token pair is accepted while a partial pair is refused | spawns the CLI against a Node HTTP deployment over real sockets |
|
|
276
286
|
| `drift-check.test.ts` | the maintainer drift checker — hosted-provider credential framing, recorded touched endpoints, a quiet revision bump, clear failures for an unavailable spec/manifest/credential, `$ref` traversal, and one well-formed row per endpoint | spawns the Node checker against filesystem fixtures |
|
|
277
287
|
| `file-storage.test.ts` | `fileStorage()` across instances, logical TTL plus physical pruning without clobbering a newer value, and corrupt-file quarantine | exercises the Node filesystem storage adapter |
|
|
278
288
|
| `guest-api-contract-quickjs.test.ts` | the shared guest-contract cases on the real QuickJS executor, including identical caught failure codes and inline describe recovery, its exact absent globals, and blocked runtime imports | runs the contract cases on the Node QuickJS executor |
|
|
279
289
|
| `node.test.ts` | the `listen()` adapter propagating an HTTP client disconnect through the Web `Request` and the MCP handler into a program's connector call, releasing both admission permits | exercises the Node HTTP adapter over real TCP sockets |
|
|
280
290
|
| `packed-links.test.ts` | the packed-link gate itself — shipped targets and repository URLs accepted, relative links into unshipped paths and directories rejected with the citation to write instead, reference definitions seen, fenced examples ignored, the changelog exempt | spawns the Node packed-link gate against filesystem fixtures |
|
|
281
|
-
| `package-surface.test.ts` | the published boundary — built output shipped, the `exports` map carrying exactly the documented subpaths plus `./package.json`, only generic factories, platform storage kept in examples, Clerk and QuickJS behind optional subpaths, every provider independently importable, and the Cloudflare provider free of bare specifiers | walks the package tree with Node filesystem APIs |
|
|
291
|
+
| `package-surface.test.ts` | the published boundary — built output shipped, the `exports` map carrying exactly the documented subpaths plus `./package.json`, only generic factories, platform storage kept in examples, Clerk and QuickJS behind optional subpaths, dependency-free Cloudflare Access behind its Worker subpath, every provider independently importable, and the Cloudflare API provider free of bare specifiers | walks the package tree with Node filesystem APIs |
|
|
282
292
|
| `purity.test.ts` | the import-graph guardrail ([architecture](./architecture.md#import-graph-purity)) — the core stays Workers-clean | walks the source import graph with Node filesystem APIs |
|
|
283
293
|
| `quickjs-child-entry.test.ts` | a missing QuickJS child entry failing before `fork()`, with the expected path and the bundler-externalization constraint | mocks Node child-process and filesystem APIs |
|
|
284
294
|
| `quickjs-child-stderr.test.ts` | abnormal child exits retaining only an 8 KiB stderr tail, included in the parent-side diagnostic | mocks Node child-process streams |
|
|
@@ -319,10 +329,15 @@ confinement does too.
|
|
|
319
329
|
- **A connector with no `verifyState` refuses every callback.** That is the
|
|
320
330
|
designed behavior, not a bug: handing an unverified code to `finishAuth` is
|
|
321
331
|
the vulnerability. The startup warning names the connector.
|
|
322
|
-
- **401 loops from a client that cannot discover auth.** The client must reach
|
|
332
|
+
- **401 loops from a Clerk client that cannot discover auth.** The client must reach
|
|
323
333
|
the open `/.well-known/oauth-protected-resource` (and the `/mcp` variant);
|
|
324
334
|
confirm CORS and the Clerk keys, and that DCR is enabled on the Clerk
|
|
325
335
|
instance.
|
|
336
|
+
- **An Access-protected MCP client receives redirects or loops.** Enable Managed
|
|
337
|
+
OAuth on the Access application and use an RFC 8707-capable client. Access,
|
|
338
|
+
not connecta, must answer the unauthenticated challenge and `/.well-known/`
|
|
339
|
+
metadata. Do not bypass those routes. For unattended automation, use an
|
|
340
|
+
Access Service Auth policy and service-token headers instead.
|
|
326
341
|
- **No sessions and no server push, by design.** The transport is stateless.
|
|
327
342
|
Scope resolves per request, which is also where the MCP spec has arrived.
|
|
328
343
|
- **A tool that should be callable from a program is not.** Only tools
|
|
@@ -39,10 +39,25 @@ their walkthrough, which is the honest version of the same page count.
|
|
|
39
39
|
| `src/operator-ui/browser.css` | One stylesheet, inlined into the shell. |
|
|
40
40
|
| `src/operator-ui/generated.ts` | The build output: the bundle and the stylesheet as two exported strings. |
|
|
41
41
|
|
|
42
|
-
The server renders a mount point, not a page. Branding, the Clerk
|
|
43
|
-
every operator-configured URL stay in `src/ui.ts`, where they are
|
|
44
|
-
they can become an attribute; the bundle renders everything that
|
|
45
|
-
Two roots share one store: `#operatorNav` and `#operatorContent`.
|
|
42
|
+
The server renders a mount point, not a page. Branding, the optional Clerk
|
|
43
|
+
loader, and every operator-configured URL stay in `src/ui.ts`, where they are
|
|
44
|
+
gated before they can become an attribute; the bundle renders everything that
|
|
45
|
+
has a state. Two roots share one store: `#operatorNav` and `#operatorContent`.
|
|
46
|
+
|
|
47
|
+
Cloudflare Access is ambient browser auth. When the current Worker invocation
|
|
48
|
+
has `ctx.access`, the shell selects the `cloudflare-access` UI mode, emits no
|
|
49
|
+
Clerk loader, and sends no browser-readable token. Same-origin fetch includes
|
|
50
|
+
the HttpOnly `CF_Authorization` cookie, Access admits it at the edge, and the
|
|
51
|
+
server reads the resulting runtime identity. Sign out navigates to
|
|
52
|
+
`/cdn-cgi/access/logout`. Mutations still require an exact same-origin
|
|
53
|
+
`Origin`; an ambient cookie does not weaken the CSRF boundary.
|
|
54
|
+
|
|
55
|
+
This runtime selection is the Clerk migration seam. A deployment may contain
|
|
56
|
+
both providers: before Worker-level Access is attached, the data-free shell
|
|
57
|
+
selects Clerk; after Access supplies `ctx.access`, it selects ambient auth. That
|
|
58
|
+
is not two same-hostname gates running in parallel. Access is upstream and a
|
|
59
|
+
request it rejects never reaches Clerk. Keeping Clerk in the array preserves a
|
|
60
|
+
code-level rollback after Access is detached.
|
|
46
61
|
|
|
47
62
|
The Clerk loader is intentionally blocking. The inline operator bundle calls
|
|
48
63
|
`boot()` as soon as the parser reaches the end of the body, so a deferred Clerk
|
|
@@ -58,7 +73,8 @@ the same ordering.
|
|
|
58
73
|
credential, token, and activity data arrives only through the authenticated
|
|
59
74
|
`/ui/*` APIs, and the shell is identical whether or not a caller is signed in.
|
|
60
75
|
- **One store, one identity.** `store.ts` is the only file that touches `fetch`,
|
|
61
|
-
`localStorage`, or
|
|
76
|
+
`localStorage`, Clerk, or the ambient Access mode. Every token-bearing request carries the current session's token,
|
|
77
|
+
while Access requests deliberately carry none,
|
|
62
78
|
and every response is dropped unless the identity that asked for it is still
|
|
63
79
|
the one on screen. `resetIdentity` replaces all identity-scoped state at once
|
|
64
80
|
and bumps a generation that work already in flight compares itself against.
|
|
@@ -57,7 +57,7 @@ exist so far:
|
|
|
57
57
|
| --- | --- | --- |
|
|
58
58
|
| **pre-template** | before 0.10.2 | no `connecta init` existed; hand-written, or copied from the retired `examples/node` |
|
|
59
59
|
| **A** | 0.10.2 – 0.15.1 | `.env.example`, `.gitignore`, `AGENTS.md`, `CLAUDE.md`, `README.md`, `package.json`, `src/index.ts`, `tsconfig.json` |
|
|
60
|
-
| **B** | 0.16.0 – 0.
|
|
60
|
+
| **B** | 0.16.0 – 0.21.0 | adds `.dockerignore`, `Dockerfile`, `docker-compose.yml`, and `src/file-activity.ts`; `src/index.ts` grows the four commented operator blocks; `.env.example` ships `CONNECTA_TOKEN=` empty |
|
|
61
61
|
|
|
62
62
|
Generation A is a decade in template years and identifying it precisely does
|
|
63
63
|
not matter, because you are about to reconstruct it exactly rather than guess
|
|
@@ -106,7 +106,7 @@ know what to preserve, once to know what to re-verify at the end.
|
|
|
106
106
|
### Bump the pin and install
|
|
107
107
|
|
|
108
108
|
```sh
|
|
109
|
-
npm pkg set dependencies.@zackbart/connecta=0.
|
|
109
|
+
npm pkg set dependencies.@zackbart/connecta=0.21.0
|
|
110
110
|
npm install
|
|
111
111
|
```
|
|
112
112
|
|
|
@@ -130,7 +130,7 @@ Generate the *current* template beside the base you already made, into the same
|
|
|
130
130
|
`$SCRATCH`:
|
|
131
131
|
|
|
132
132
|
```sh
|
|
133
|
-
(cd "$SCRATCH" && npx @zackbart/connecta@0.
|
|
133
|
+
(cd "$SCRATCH" && npx @zackbart/connecta@0.21.0 init current)
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
You now have a three-way merge with a real base: `$SCRATCH/base` is what this
|
|
@@ -186,7 +186,7 @@ A deployment older than 0.10.2 has no base to diff against. Do not try to
|
|
|
186
186
|
manufacture one. Instead:
|
|
187
187
|
|
|
188
188
|
1. `SCRATCH=$(mktemp -d)`, then
|
|
189
|
-
`(cd "$SCRATCH" && npx @zackbart/connecta@0.
|
|
189
|
+
`(cd "$SCRATCH" && npx @zackbart/connecta@0.21.0 init current)` — there is no
|
|
190
190
|
`base` leg here, only the current template to read from.
|
|
191
191
|
2. Copy `$SCRATCH/current` into the deployment file by file, **skipping
|
|
192
192
|
`src/index.ts`**.
|
|
@@ -207,6 +207,78 @@ first, so cross them bottom-up: start at the oldest one still above this
|
|
|
207
207
|
deployment's pin and work back up the page, because each boundary assumes the
|
|
208
208
|
older ones are already done.
|
|
209
209
|
|
|
210
|
+
### 0.20.0 → 0.21.0
|
|
211
|
+
|
|
212
|
+
This is additive for Node and existing Clerk deployments. The new Worker path
|
|
213
|
+
uses Cloudflare Access identity directly and removes Clerk only after the edge
|
|
214
|
+
cutover has been verified. An agent can perform every repository edit; a human
|
|
215
|
+
must attach Access, choose its policy, create service credentials, and enable
|
|
216
|
+
Managed OAuth in the Cloudflare dashboard.
|
|
217
|
+
|
|
218
|
+
For a Worker currently using Clerk, keep rollback live through the cutover:
|
|
219
|
+
|
|
220
|
+
1. Bump and install 0.21.0. Add the new provider **before** the existing Clerk
|
|
221
|
+
provider, but remove nothing:
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
import { cloudflareAccessAuth } from
|
|
225
|
+
"@zackbart/connecta/auth/cloudflare-access";
|
|
226
|
+
import { clerkAuth } from "@zackbart/connecta/auth/clerk";
|
|
227
|
+
|
|
228
|
+
auth: [
|
|
229
|
+
cloudflareAccessAuth(),
|
|
230
|
+
// Keep the deployment's existing options and secrets unchanged.
|
|
231
|
+
clerkAuth({ /* existing configuration */ }),
|
|
232
|
+
],
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Before Access is attached, the new provider fails closed and the operator
|
|
236
|
+
shell selects Clerk. Deploy this state and run doctor with the existing
|
|
237
|
+
`CONNECTA_TOKEN`. This separates the package/code change from the edge
|
|
238
|
+
change and proves the old path still works.
|
|
239
|
+
|
|
240
|
+
2. In Cloudflare, attach Access to the Worker itself, apply the intended human
|
|
241
|
+
policy, and enable Managed OAuth. Through the API this is an Access
|
|
242
|
+
application destination of `{ "type": "worker", "worker_id": "<script
|
|
243
|
+
tag>" }`, not a hostname application for the `workers.dev` URL: the latter
|
|
244
|
+
gates traffic but does not provide `ctx.access`. Create an Access service
|
|
245
|
+
token and a **Service Auth** policy for doctor and fully unattended clients.
|
|
246
|
+
Do not create a bypass for `/.well-known/*`; Managed OAuth owns that
|
|
247
|
+
discovery surface.
|
|
248
|
+
|
|
249
|
+
3. Reconnect interactive MCP clients to `<PUBLIC_URL>/mcp`. Their old Clerk
|
|
250
|
+
OAuth tokens are not Cloudflare credentials, so each client performs one new
|
|
251
|
+
browser authorization. An agent can edit client configuration and start the
|
|
252
|
+
flow; the user still completes the identity-provider prompt. Move CI, cron,
|
|
253
|
+
and server-to-server callers from connecta bearers to the two Access service
|
|
254
|
+
headers. The cutover warning is literal: once Access is attached, a static
|
|
255
|
+
bearer or `cta_…` token by itself is stopped at the edge before connecta can
|
|
256
|
+
inspect it.
|
|
257
|
+
|
|
258
|
+
4. Verify the edge path:
|
|
259
|
+
|
|
260
|
+
```sh
|
|
261
|
+
CF_ACCESS_CLIENT_ID=… CF_ACCESS_CLIENT_SECRET=… \
|
|
262
|
+
npx connecta doctor --url https://connecta.example.workers.dev
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Open `/` as a human and exercise any enabled credential, token, and OAuth
|
|
266
|
+
controls. A service token may pass doctor and MCP but must receive 403 from
|
|
267
|
+
operator mutations.
|
|
268
|
+
|
|
269
|
+
5. After an observation window, remove `clerkAuth`, its import,
|
|
270
|
+
`@clerk/backend`, and the Clerk variables/secrets. Until then they are inert
|
|
271
|
+
behind Access but preserve rollback. Rollback order matters: detach Access
|
|
272
|
+
first, then the untouched Clerk sessions and connecta bearers reach the
|
|
273
|
+
Worker again. Reverting code first cannot help a request the edge still
|
|
274
|
+
blocks. There is no storage migration and no token-format conversion.
|
|
275
|
+
|
|
276
|
+
If the Worker exposes an intentionally public connector route, create a
|
|
277
|
+
more-specific hostname/path Access application with a Bypass policy for that
|
|
278
|
+
route only. `/health`, downstream OAuth callbacks, operator shells, and MCP are
|
|
279
|
+
private under the canonical whole-Worker shape; doctor knows how to authenticate
|
|
280
|
+
its health request.
|
|
281
|
+
|
|
210
282
|
### 0.19.0 → 0.20.0
|
|
211
283
|
|
|
212
284
|
Three intake paths become deliberately strict. None changes storage, the two
|
package/ethos.md
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# connecta — ethos
|
|
2
2
|
|
|
3
3
|
What connecta is, what it refuses to be, and the invariants every change must
|
|
4
|
-
preserve.
|
|
5
|
-
change is wrong or this file needs amending — in that order, and amending it is
|
|
6
|
-
a design decision, not a drive-by edit.
|
|
4
|
+
preserve. A contradiction needs a design decision, not a drive-by edit.
|
|
7
5
|
|
|
8
6
|
## What this is
|
|
9
7
|
|
|
@@ -78,6 +76,7 @@ CHANGELOG, not here.
|
|
|
78
76
|
| MRTR / `input_required` passthrough | gated | relayable statelessly; no host or downstream emits it yet ([#176](https://github.com/zackbart/connecta/issues/176)) |
|
|
79
77
|
| Downstream `ttlMs` cache hints | gated | needs refresh-churn evidence ([#206](https://github.com/zackbart/connecta/issues/206)) |
|
|
80
78
|
| Downstream MCP Apps template passthrough | gated | needs a downstream that ships one ([#266](https://github.com/zackbart/connecta/issues/266)) |
|
|
79
|
+
| Worker Access inbound auth | provisional | Managed OAuth and Clerk migration need production evidence ([#506](https://github.com/zackbart/connecta/issues/506)) |
|
|
81
80
|
| Program UI tool calls | removed | the read bridge added a second contract without improving agent data retrieval; views are display-only again ([#287](https://github.com/zackbart/connecta/issues/287), [#484](https://github.com/zackbart/connecta/issues/484)) |
|
|
82
81
|
|
|
83
82
|
## Invariants
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# connecta — Cloudflare Worker example
|
|
2
2
|
|
|
3
3
|
A deployable Worker that aggregates a downstream remote MCP and an in-code HTTP
|
|
4
|
-
API connector, guarded by
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
API connector, guarded by Cloudflare Access, with state in a KV namespace. Its
|
|
5
|
+
required Worker Loader binding backs the seven-tool surface and requires the
|
|
6
|
+
Workers Paid plan.
|
|
7
7
|
|
|
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
|
|
@@ -37,18 +37,27 @@ npm install # from the package root
|
|
|
37
37
|
wrangler kv namespace create CONNECTA_KV # paste the id into wrangler.jsonc
|
|
38
38
|
|
|
39
39
|
cd examples/worker
|
|
40
|
-
wrangler secret put SUPPORT_TOKEN # one headless client
|
|
41
|
-
wrangler secret put EXEC_TOKEN # another headless client
|
|
42
|
-
wrangler secret put CLERK_SECRET_KEY
|
|
43
40
|
wrangler secret put DOWNSTREAM_TOKEN
|
|
44
41
|
wrangler secret put CREDENTIAL_ENCRYPTION_KEY # base64 32-byte AES key
|
|
45
42
|
wrangler deploy
|
|
46
43
|
```
|
|
47
44
|
|
|
48
|
-
`PUBLIC_URL`
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
`PUBLIC_URL` is a plain var in `wrangler.jsonc`. After the first deploy, attach
|
|
46
|
+
Cloudflare Access to the Worker itself (the API destination type is `worker`,
|
|
47
|
+
not a hostname application) and choose the account, email-domain, or
|
|
48
|
+
advanced Zero Trust policy that owns admission. Enable **Managed OAuth** on
|
|
49
|
+
that Access application for interactive MCP clients. Access then serves OAuth
|
|
50
|
+
discovery and turns the client's opaque token into the trusted `ctx.access`
|
|
51
|
+
identity connecta reads. A cron job or CI client uses an Access service token
|
|
52
|
+
instead.
|
|
53
|
+
|
|
54
|
+
Cloudflare's [Worker Access guide](https://developers.cloudflare.com/workers/configuration/cloudflare-access/)
|
|
55
|
+
owns the dashboard/API steps; its [Managed OAuth guide](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/managed-oauth/)
|
|
56
|
+
owns client registration, redirect allowlists, and token lifetimes.
|
|
57
|
+
|
|
58
|
+
The checked-in `access.dev` block gives `wrangler dev` a local operator
|
|
59
|
+
identity. Remove the block to test the missing-Access refusal. It has no effect
|
|
60
|
+
on a deployed Worker's production identity.
|
|
52
61
|
|
|
53
62
|
### Copied into its own repository
|
|
54
63
|
|
|
@@ -57,24 +66,22 @@ dependency this file imports. A copy with its own `package.json` installs three
|
|
|
57
66
|
things, because two of them are not part of connecta and never install with it:
|
|
58
67
|
|
|
59
68
|
```sh
|
|
60
|
-
npm install @zackbart/connecta @cloudflare/codemode
|
|
69
|
+
npm install @zackbart/connecta @cloudflare/codemode
|
|
61
70
|
```
|
|
62
71
|
|
|
63
|
-
|
|
64
|
-
never installed with it, and
|
|
65
|
-
`@cloudflare/codemode` is the executor behind `execute_code`, published as
|
|
72
|
+
`@cloudflare/codemode` is the optional peer behind `execute_code`, declared in
|
|
73
|
+
connecta's manifest but never installed with it, and published as
|
|
66
74
|
`^0.4.4 || ^0.5.0`: install a version inside that and npm stays quiet, install
|
|
67
75
|
one outside and npm says so at install time instead of leaving a Worker to
|
|
68
76
|
discover the skew in production ([#376](https://github.com/zackbart/connecta/issues/376)).
|
|
69
77
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
without it can never write a credential or issue an access token.
|
|
78
|
+
`cloudflareAccessAuth()` has no dependency of its own. A deployment keeping
|
|
79
|
+
Clerk for rollback still installs `@clerk/backend` and keeps the commented
|
|
80
|
+
provider shape in `src/index.ts` until the migration is verified.
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
npm install @clerk/backend # migration window only
|
|
84
|
+
```
|
|
78
85
|
|
|
79
86
|
Then point an MCP client at `<PUBLIC_URL>/mcp`, and open `<PUBLIC_URL>/` for
|
|
80
87
|
Connections. Credentials is at `/credentials`, named MCP access tokens are at
|
|
@@ -87,11 +94,11 @@ the next section for what turns each one on.
|
|
|
87
94
|
This example ships the whole operator feature set. Three quarters of it is on
|
|
88
95
|
as deployed; the fourth needs a database, so it is commented in place.
|
|
89
96
|
|
|
90
|
-
**Operator sign-in** is the `
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
**Operator sign-in** is the `cloudflareAccessAuth()` entry in `src/index.ts`.
|
|
98
|
+
Access authenticates before the Worker runs. A human Access identity can use
|
|
99
|
+
MCP and operator pages; a service-token identity can use MCP but cannot write a
|
|
100
|
+
credential, run downstream OAuth, or issue a connecta token. Narrow admission
|
|
101
|
+
in the Access policy rather than repeating email domains or groups in code.
|
|
95
102
|
|
|
96
103
|
**The credential vault** is `credentials: { encryptionKey: … }`, backed by the
|
|
97
104
|
same KV namespace as everything else and encrypted with the
|
|
@@ -116,12 +123,18 @@ shape on `echo` is exactly it) or use a provider connector such as `notion()`,
|
|
|
116
123
|
which declares its own, and Credentials appears for a signed-in operator on the
|
|
117
124
|
next load.
|
|
118
125
|
|
|
119
|
-
**Access tokens** are `accessTokens: {}`. A signed-in operator mints named,
|
|
126
|
+
**Access tokens** are `accessTokens: {}`. A signed-in human operator mints named,
|
|
120
127
|
revocable Bearer tokens at `/tokens` for header-capable clients that will not do
|
|
121
128
|
OAuth. Secrets are shown once and only their hashes enter KV; a lost token is
|
|
122
129
|
reissued, never recovered. Note the KV caveat above — revocation is visible
|
|
123
130
|
everywhere only as fast as the namespace converges.
|
|
124
131
|
|
|
132
|
+
Worker-level Access still runs before these tokens. A `cta_…` token therefore
|
|
133
|
+
does not reach connecta by itself; retain the feature as a rollback path or for
|
|
134
|
+
a caller that already supplies separate Access service-token headers. Normal
|
|
135
|
+
interactive MCP clients should use Managed OAuth, and unattended clients should
|
|
136
|
+
use Access service tokens.
|
|
137
|
+
|
|
125
138
|
**Activity** is the commented block in `src/index.ts` and the commented
|
|
126
139
|
`d1_databases` binding in `wrangler.jsonc`; the section below creates the
|
|
127
140
|
database and applies the schema.
|
|
@@ -134,10 +147,17 @@ never the connector set, the tool catalog, or its annotations.
|
|
|
134
147
|
this on: connector count, executor, seven tools. The executor it names is this
|
|
135
148
|
one — `DynamicWorkerExecutor executed`, not the Node template's QuickJS, which
|
|
136
149
|
is what doctor used to claim everywhere
|
|
137
|
-
([#368](https://github.com/zackbart/connecta/issues/368)).
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
150
|
+
([#368](https://github.com/zackbart/connecta/issues/368)). Against Access it
|
|
151
|
+
carries `CF_ACCESS_CLIENT_ID` and `CF_ACCESS_CLIENT_SECRET`, and the service
|
|
152
|
+
identity learns the model-facing surface rather than deployment topology:
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
CF_ACCESS_CLIENT_ID=… CF_ACCESS_CLIENT_SECRET=… \
|
|
156
|
+
npx connecta doctor --url "$PUBLIC_URL"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Confirm the operator surface the way an operator will: sign in at
|
|
160
|
+
`<PUBLIC_URL>/` and check that Tokens is live. Credentials joins it
|
|
141
161
|
once a connector declares a `credential` slot, and Activity once the D1 wiring
|
|
142
162
|
below is on — the nav shows a page when the deployment can actually serve it,
|
|
143
163
|
so a missing page is the honest report that its half is still off.
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* connecta on Cloudflare Workers.
|
|
3
3
|
*
|
|
4
4
|
* One MCP endpoint aggregating a downstream remote MCP and an HTTP API, guarded
|
|
5
|
-
* by
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* by Cloudflare Access, with OAuth/cache state in a KV namespace. Access
|
|
6
|
+
* authenticates the request before this Worker runs and supplies the trusted
|
|
7
|
+
* identity through ctx.access. The required Worker Loader binding in
|
|
8
|
+
* wrangler.jsonc backs the seven-tool surface.
|
|
8
9
|
*
|
|
9
10
|
* The operator surface is wired here except for activity history, which needs
|
|
10
11
|
* a database this example does not create for you: sign-in, the credential
|
|
@@ -15,19 +16,16 @@
|
|
|
15
16
|
* installed `@zackbart/connecta` package):
|
|
16
17
|
* 1. `npm install` in the connecta package root (../../ from here) so the
|
|
17
18
|
* package import and wrangler resolve. A copy in its own repository
|
|
18
|
-
* installs `@zackbart/connecta @cloudflare/codemode
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* installs `@zackbart/connecta @cloudflare/codemode` instead. Codemode is
|
|
20
|
+
* an optional peer; a migrating deployment also keeps `@clerk/backend`
|
|
21
|
+
* until it removes the commented rollback provider below.
|
|
21
22
|
* 2. Create a KV namespace and put its id in wrangler.jsonc under `kv_namespaces`.
|
|
22
23
|
* 3. Set secrets:
|
|
23
|
-
* wrangler secret put SUPPORT_TOKEN
|
|
24
|
-
* wrangler secret put EXEC_TOKEN
|
|
25
|
-
* wrangler secret put CLERK_SECRET_KEY
|
|
26
24
|
* wrangler secret put DOWNSTREAM_TOKEN
|
|
27
25
|
* wrangler secret put CREDENTIAL_ENCRYPTION_KEY
|
|
28
|
-
* and
|
|
29
|
-
* 4.
|
|
30
|
-
*
|
|
26
|
+
* and PUBLIC_URL as a plain var in wrangler.jsonc.
|
|
27
|
+
* 4. Attach Cloudflare Access to this Worker. Enable Managed OAuth on the
|
|
28
|
+
* Access application for interactive MCP clients.
|
|
31
29
|
* 5. Use the Workers Paid plan required by the `worker_loaders` binding.
|
|
32
30
|
* 6. `wrangler deploy` from this folder (examples/worker), where wrangler.jsonc
|
|
33
31
|
* lives. Point your MCP client at `<PUBLIC_URL>/mcp`.
|
|
@@ -35,23 +33,21 @@
|
|
|
35
33
|
import { DynamicWorkerExecutor } from "@cloudflare/codemode";
|
|
36
34
|
import {
|
|
37
35
|
api,
|
|
38
|
-
bearerToken,
|
|
39
36
|
createConnecta,
|
|
40
37
|
remoteMcp,
|
|
41
38
|
} from "@zackbart/connecta";
|
|
42
|
-
import {
|
|
39
|
+
import { cloudflareAccessAuth } from "@zackbart/connecta/auth/cloudflare-access";
|
|
40
|
+
// Rollback for a deployment migrating from Clerk:
|
|
41
|
+
// import { clerkAuth } from "@zackbart/connecta/auth/clerk";
|
|
43
42
|
import { cloudflareKvStorage } from "./cloudflare-kv.js";
|
|
44
43
|
// Activity history, off by default because it needs a D1 database.
|
|
45
44
|
// import { d1ActivityStore } from "./d1-activity.js";
|
|
46
45
|
|
|
47
46
|
interface Env {
|
|
48
47
|
CONNECTA_KV: KVNamespace;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
EXEC_TOKEN: string;
|
|
53
|
-
CLERK_PUBLISHABLE_KEY: string;
|
|
54
|
-
CLERK_SECRET_KEY: string;
|
|
48
|
+
// Keep these during a Clerk migration until Access has been verified:
|
|
49
|
+
// CLERK_PUBLISHABLE_KEY: string;
|
|
50
|
+
// CLERK_SECRET_KEY: string;
|
|
55
51
|
/**
|
|
56
52
|
* Base64 32-byte AES key encrypting operator-managed credentials in KV.
|
|
57
53
|
* Unset means no vault: /credentials stays read-only and connecta says so at
|
|
@@ -75,22 +71,19 @@ function build(env: Env) {
|
|
|
75
71
|
storage: cloudflareKvStorage(env.CONNECTA_KV),
|
|
76
72
|
executor: new DynamicWorkerExecutor({ loader: env.LOADER }),
|
|
77
73
|
auth: [
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
publicUrl: env.PUBLIC_URL,
|
|
92
|
-
// allowedDomains: ["acme.com"],
|
|
93
|
-
}),
|
|
74
|
+
// Access owns admission policy. A human identity may use MCP and the
|
|
75
|
+
// operator pages; a service token may use MCP but cannot mutate operator
|
|
76
|
+
// state. Neither path asks connecta to parse a JWT.
|
|
77
|
+
cloudflareAccessAuth(),
|
|
78
|
+
// Leave the previous Clerk provider below this entry during migration.
|
|
79
|
+
// It is a rollback path until Worker-level Access is detached; Access
|
|
80
|
+
// itself decides whether a request reaches this array.
|
|
81
|
+
// clerkAuth({
|
|
82
|
+
// publishableKey: env.CLERK_PUBLISHABLE_KEY,
|
|
83
|
+
// secretKey: env.CLERK_SECRET_KEY,
|
|
84
|
+
// publicUrl: env.PUBLIC_URL,
|
|
85
|
+
// allowedDomains: ["acme.com"],
|
|
86
|
+
// }),
|
|
94
87
|
],
|
|
95
88
|
// Connectors that declare a `credential` slot become editable at
|
|
96
89
|
// /credentials, encrypted with this key before anything reaches KV. A
|
|
@@ -105,8 +98,9 @@ function build(env: Env) {
|
|
|
105
98
|
// `echo`, or use a provider connector like `notion()`, which declares its
|
|
106
99
|
// own) and the page appears on the next load.
|
|
107
100
|
credentials: { encryptionKey: env.CREDENTIAL_ENCRYPTION_KEY },
|
|
108
|
-
// Eligible
|
|
109
|
-
// at /tokens.
|
|
101
|
+
// Eligible human operators can create named, revocable MCP Bearer tokens
|
|
102
|
+
// at /tokens. Under Worker-level Access those tokens are a rollback tool,
|
|
103
|
+
// not standalone edge credentials: Access still runs before connecta.
|
|
110
104
|
accessTokens: {},
|
|
111
105
|
// Payload-free activity at /activity, off until a database exists to hold
|
|
112
106
|
// it. Uncomment the `d1_databases` binding in wrangler.jsonc, apply the
|
|
@@ -6,12 +6,20 @@
|
|
|
6
6
|
"compatibility_flags": ["nodejs_compat"],
|
|
7
7
|
"observability": { "enabled": true },
|
|
8
8
|
|
|
9
|
-
// Plain vars. Secrets (
|
|
10
|
-
// CREDENTIAL_ENCRYPTION_KEY, DOWNSTREAM_TOKEN) are set with
|
|
9
|
+
// Plain vars. Secrets (CREDENTIAL_ENCRYPTION_KEY, DOWNSTREAM_TOKEN) are set with
|
|
11
10
|
// `wrangler secret put <NAME>`, not here.
|
|
12
11
|
"vars": {
|
|
13
|
-
"PUBLIC_URL": "https://connecta.example.workers.dev"
|
|
14
|
-
|
|
12
|
+
"PUBLIC_URL": "https://connecta.example.workers.dev"
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
// Local-only Access identity. Remove this block to exercise the
|
|
16
|
+
// unauthenticated path in `wrangler dev`; production identity comes from the
|
|
17
|
+
// Worker-level Access application, not from this file.
|
|
18
|
+
"access": {
|
|
19
|
+
"dev": {
|
|
20
|
+
"aud": "connecta-local",
|
|
21
|
+
"identity": { "email": "operator@example.com" }
|
|
22
|
+
}
|
|
15
23
|
},
|
|
16
24
|
|
|
17
25
|
// Create with `wrangler kv namespace create CONNECTA_KV` and paste the id.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zackbart/connecta",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.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.",
|
|
@@ -63,6 +63,10 @@
|
|
|
63
63
|
"types": "./dist/auth/clerk.d.ts",
|
|
64
64
|
"import": "./dist/auth/clerk.js"
|
|
65
65
|
},
|
|
66
|
+
"./auth/cloudflare-access": {
|
|
67
|
+
"types": "./dist/auth/cloudflare-access.d.ts",
|
|
68
|
+
"import": "./dist/auth/cloudflare-access.js"
|
|
69
|
+
},
|
|
66
70
|
"./providers/cloudflare": {
|
|
67
71
|
"types": "./dist/providers/cloudflare.d.ts",
|
|
68
72
|
"import": "./dist/providers/cloudflare.js"
|