@zackbart/connecta 0.24.2 → 0.24.4

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 (97) hide show
  1. package/AGENTS.md +18 -20
  2. package/CHANGELOG.md +205 -1
  3. package/README.md +5 -6
  4. package/dist/auth/bearer.js +2 -0
  5. package/dist/auth/downstream-oauth.d.ts +12 -1
  6. package/dist/auth/downstream-oauth.js +147 -35
  7. package/dist/branding.d.ts +31 -2
  8. package/dist/branding.js +116 -8
  9. package/dist/call-admission.d.ts +4 -0
  10. package/dist/call-admission.js +26 -0
  11. package/dist/catalog-drift.js +9 -4
  12. package/dist/catalog-service.d.ts +2 -0
  13. package/dist/catalog-service.js +25 -8
  14. package/dist/catalog.d.ts +2 -0
  15. package/dist/catalog.js +246 -121
  16. package/dist/connectors/api.d.ts +1 -1
  17. package/dist/connectors/api.js +21 -3
  18. package/dist/connectors/guarded-fetch.d.ts +6 -2
  19. package/dist/connectors/guarded-fetch.js +61 -24
  20. package/dist/connectors/remote-mcp.js +92 -57
  21. package/dist/errors.d.ts +28 -3
  22. package/dist/errors.js +60 -1
  23. package/dist/execute.js +85 -23
  24. package/dist/executor-result.js +3 -1
  25. package/dist/executors/quickjs-child.js +5 -1
  26. package/dist/executors/quickjs-protocol.d.ts +4 -0
  27. package/dist/executors/quickjs-runtime.d.ts +1 -1
  28. package/dist/executors/quickjs-runtime.js +38 -21
  29. package/dist/executors/quickjs.js +68 -27
  30. package/dist/index.d.ts +15 -1
  31. package/dist/index.js +36 -4
  32. package/dist/invocation.js +134 -93
  33. package/dist/mcp-result.js +3 -2
  34. package/dist/meta-tools.js +191 -36
  35. package/dist/operator-ui/generated.js +2 -2
  36. package/dist/operator-ui/view.d.ts +38 -1
  37. package/dist/operator-ui/view.js +71 -0
  38. package/dist/providers/cloudflare.d.ts +14 -2
  39. package/dist/providers/cloudflare.js +107 -16
  40. package/dist/providers/linear.d.ts +26 -4
  41. package/dist/providers/linear.js +19 -4
  42. package/dist/providers/mixpanel.d.ts +16 -3
  43. package/dist/providers/mixpanel.js +13 -2
  44. package/dist/providers/notion.d.ts +8 -1
  45. package/dist/providers/notion.js +83 -10
  46. package/dist/providers/revenuecat.d.ts +30 -4
  47. package/dist/providers/revenuecat.js +42 -4
  48. package/dist/providers/stripe.d.ts +7 -1
  49. package/dist/providers/stripe.js +30 -4
  50. package/dist/providers/vercel.js +11 -1
  51. package/dist/registry.d.ts +22 -2
  52. package/dist/registry.js +101 -13
  53. package/dist/routes/mcp.d.ts +4 -1
  54. package/dist/routes/mcp.js +84 -13
  55. package/dist/routes/oauth.js +4 -0
  56. package/dist/routes/shared.d.ts +1 -0
  57. package/dist/routes/shared.js +4 -4
  58. package/dist/server.js +15 -3
  59. package/dist/skills.js +6 -5
  60. package/dist/storage/file.d.ts +6 -2
  61. package/dist/storage/file.js +312 -34
  62. package/dist/storage/memory.js +12 -1
  63. package/dist/types.d.ts +37 -0
  64. package/dist/ui.js +18 -10
  65. package/dist/validate.js +3 -3
  66. package/dist/version.d.ts +1 -1
  67. package/dist/version.js +1 -1
  68. package/documentation/architecture.md +193 -165
  69. package/documentation/auth.md +199 -145
  70. package/documentation/code-mode.md +433 -316
  71. package/documentation/meta-tools.md +363 -355
  72. package/examples/worker/AGENTS.md +2 -1
  73. package/examples/worker/README.md +12 -10
  74. package/examples/worker/src/index.ts +12 -15
  75. package/package.json +1 -2
  76. package/templates/node/.env.example +3 -3
  77. package/templates/node/AGENTS.md +5 -4
  78. package/templates/node/README.md +2 -1
  79. package/templates/node/package.json +1 -1
  80. package/templates/node/src/index.ts +23 -22
  81. package/documentation/call-admission.md +0 -142
  82. package/documentation/cloudflare.md +0 -471
  83. package/documentation/connector-guides.md +0 -176
  84. package/documentation/connectors.md +0 -389
  85. package/documentation/linear.md +0 -193
  86. package/documentation/mixpanel.md +0 -160
  87. package/documentation/notion.md +0 -308
  88. package/documentation/operations.md +0 -359
  89. package/documentation/operator-ui.md +0 -135
  90. package/documentation/optional-modules-upgrade.md +0 -243
  91. package/documentation/provider-conventions.md +0 -722
  92. package/documentation/request-admission.md +0 -170
  93. package/documentation/revenuecat.md +0 -305
  94. package/documentation/storage-and-credentials.md +0 -201
  95. package/documentation/stripe.md +0 -262
  96. package/documentation/upgrading.md +0 -754
  97. package/documentation/vercel.md +0 -241
@@ -1,359 +0,0 @@
1
- # Operations
2
-
3
- Configuring, running, verifying, and upgrading a deployment — and the map of
4
- which suite proves what, which is the part an agent changing this repository
5
- needs most.
6
-
7
- ## Running it
8
-
9
- `createConnecta(config)` returns `{ fetch, registry, close }`. `fetch` takes
10
- the Workers `(request, env, ctx)` signature; passing `ctx` through is what lets
11
- connecta hand deferred work to `ctx.waitUntil` instead of losing it when the
12
- response returns. That work is best-effort activity writes and the bounded
13
- refresh an agent catalog read already demanded while it served a complete stale
14
- entry. Node's adapter tracks the same promises and drains them on shutdown.
15
-
16
- An `executor` is required. A deployment without one throws at construction
17
- rather than serving a smaller surface
18
- ([#273](https://github.com/zackbart/connecta/issues/273)): Node uses
19
- `quickJsExecutor()` from `@zackbart/connecta/quickjs`, Workers use
20
- `new DynamicWorkerExecutor({ loader: env.LOADER })` from
21
- `@cloudflare/codemode`. The Worker executor must stay loader-only: `bindings`,
22
- `modules`, and `globalOutbound` grant ambient guest authority.
23
-
24
- Both executor packages are optional peers: they never install with connecta,
25
- and a deployment installs the one its runtime needs. The manifest publishes the
26
- range each release supports — `^0.32.0` for `quickjs-emscripten`,
27
- `^0.4.4 || ^0.5.0` for `@cloudflare/codemode` — so npm answers the version
28
- question at install time rather than leaving a deployment to discover the skew
29
- when a program runs ([#376](https://github.com/zackbart/connecta/issues/376)).
30
- The version this repository tests against is one the published range admits,
31
- and `test/package-surface.test.ts` fails if the two ever drift apart.
32
-
33
- There are exactly two deployment shapes.
34
- [`templates/node/`](../templates/node/) is what `connecta init` copies — the
35
- one standalone Node project, Docker-ready rather than Docker-only — and
36
- [`examples/worker/`](../examples/worker/) is the Cloudflare shape. Both show explicit optional modules; each README walks through enablement.
37
- A third scaffold that is a diff away from either is the shape
38
- [#344](https://github.com/zackbart/connecta/issues/344) deleted, so do not add
39
- one.
40
-
41
- ### The CLI
42
-
43
- ```sh
44
- npx @zackbart/connecta init my-deployment
45
- cd my-deployment && npm install && npm start
46
- CONNECTA_TOKEN=… npx connecta doctor --url http://localhost:8787
47
-
48
- # A Worker protected by Cloudflare Access
49
- CF_ACCESS_CLIENT_ID=… CF_ACCESS_CLIENT_SECRET=… \
50
- npx connecta doctor --url https://connecta.example.workers.dev
51
- ```
52
-
53
- `init` copies the template, pins the generated deployment to the CLI package's
54
- exact version, restores the template `.gitignore` (npm renames it in a
55
- tarball), and refuses to merge into an existing path.
56
-
57
- `doctor` verifies a *running* deployment: `/health` reports ok, `tools/list` is
58
- exactly the seven prescribed names, and `execute_code` actually runs a trivial
59
- program. The executor it names is the one the deployment reports on `/health`,
60
- from that executor's own `name` or its constructor name, sanitized and bounded
61
- on the way out: `QuickJS` on the Node template, `DynamicWorkerExecutor` on the
62
- Worker example, and `code executed` when an executor identifies as nothing —
63
- a checker that asserts a sandbox it never saw is worse than one that says it
64
- does not know ([#368](https://github.com/zackbart/connecta/issues/368)). It
65
- refuses to send authentication credentials over remote plaintext HTTP, and it
66
- *reports* catalog drift without failing on it — an unclassified downstream tool
67
- already fails closed onto `call_destructive_tool`, so drift is a maintainer's
68
- next task rather than a broken deployment
69
- ([#343](https://github.com/zackbart/connecta/issues/343)).
70
-
71
- For an Access-protected Worker, doctor sends the service-token pair to both
72
- `/health` and `/mcp`; Access authenticates those requests before connecta runs.
73
- `CONNECTA_TOKEN` remains the Node and legacy Worker path, and may be supplied
74
- alongside the Access pair during rollback testing. A partial pair is refused.
75
-
76
- ### Configuration
77
-
78
- Structural seams stay top-level; tuning is grouped by subsystem. Every group is
79
- optional.
80
-
81
- | Option | Default | What it does |
82
- | --- | --- | --- |
83
- | `connectors` | — (required) | the connector set ([connectors](./connectors.md)) |
84
- | `executor` | — (required) | the sandbox `execute_code` runs in ([code mode](./code-mode.md#what-an-executor-must-implement)) |
85
- | `auth?` | none ⇒ open (dev only) | one `InboundAuth` or an array; bearer providers are checked before interactive providers ([inbound auth](./auth.md)) |
86
- | `identity?` | all visible; auth management denied; interactive activity reads | `{ connectorAccess?, credentialAdministration?, personalConnection?, activityAccess? }` derives separate use and management permissions ([identity](./auth.md#principals-visibility-and-operators)) |
87
- | `pools?` | none | `{ <name>: { tools, grant? } }` named slices served at `/mcp/<name>`, each intersected with the identity view and denied unless `grant` admits ([pools](./auth.md#pools)) |
88
- | `storage?` | `memoryStorage()` | connector state, catalogs, and result paging; pass storage explicitly to the optional vault ([storage](./storage-and-credentials.md)) |
89
- | `publicUrl?` | per-request origin | public base URL; an HTTPS value also redirects inbound HTTP |
90
- | `logger?` | `console`, prefixed `[connecta]` | `{ debug, info, warn, error }`, or `"silent"` to suppress diagnostic output; independent of activity history |
91
- | `ui?` | unset | `operatorUi({ branding? })` from `/ui`; omitted means no browser UI routes or bundle imports |
92
- | `serverInfo?` | `connecta` / package version | `{ name, version, title?, websiteUrl?, icons? }` per the MCP icons spec |
93
- | `deploymentInfo?` | unset | arbitrary metadata exposed by `/health` |
94
- | `activity?` | unset | `activityHistory({ store, readGate?, deploymentId? })` from `/activity`; omitted means no event recording or history reads |
95
- | `vault?` | unset | `CredentialVault`; `encryptedCredentialVault(storage, encryptionKey)` from `/credentials` supplies the built-in encrypted implementation |
96
- | `discovery.concurrency?` | 4 | connector catalogs/status probes in flight at once |
97
- | `discovery.catalogTtlSeconds?` | 300 | fresh TTL for cached tool lists |
98
- | `discovery.persistCatalog?` | true | persist serializable catalogs as a manifest plus revision-addressed chunks |
99
- | `discovery.staleCatalogSeconds?` | 3600 | how long a complete expired catalog stays usable for agent SWR and as a refresh-failure fallback |
100
- | `discovery.probeTimeoutMs?` | 30_000 | per-connector deadline for catalog fan-out; a timed-out connector degrades alone. Not a tool-call deadline |
101
- | `calls.defaultTimeoutMs?` | **unset (opt-in)** | deadline for calls that pass no `timeoutMs`. Each admitted call makes one attempt |
102
- | `calls.maxResultBytes?` | 50_000 | inline result cap before truncation and `get_result` paging; a connector may override it. Invalid values warn and fall back |
103
- | `execute.maxEmittedBytes?` | 4_000_000 | aggregate `connecta.emit` bytes per run — a transport bound, not a context bound |
104
- | `execute.maxEmittedBlocks?` | 32 | content blocks `connecta.emit` accepts per run |
105
- | `execute.maxHostCalls?` | 20 | connector calls one `execute_code` program may make |
106
- | `execute.hostCallTimeoutMs?` | 15_000 | deadline per `execute_code` host call; raise it for providers whose legitimate calls run longer. `call_tool`'s `timeoutMs` is separate |
107
- | `admission.requests?` | 16 active / 32 queued / 5 s / 1 s | global FIFO `/mcp` capacity, taken before auth ([request admission](./request-admission.md)) |
108
- | `admission.code?` | 2 active / 8 queued / 5 s / 1 s | fallback pool for an executor that owns no `acquire()`; ignored with a warning when it does |
109
-
110
- An unknown own option throws before construction does deployment work. The
111
- check covers the top level, every configuration group, admission pools,
112
- branding icons, and server icons; the error names the rejected path without
113
- copying its value. Removed options such as `toolkits`, `credentials.health`,
114
- `surface`, `calls.maxBatchResultBytes`, and the flat v0.6 paths now take that
115
- same path. The [upgrade guide](./upgrading.md#removed-options-that-throw) keeps
116
- their historical mappings. Silently ignoring either a typo or a removed option
117
- is how a deployment runs a policy its config file does not describe.
118
-
119
- ### Deployment as a release unit
120
-
121
- Treat the package and each running instance as separate release units:
122
-
123
- ```
124
- @zackbart/connecta release
125
- ↓ exact version
126
- deployment repository
127
- src/index.ts connector and auth configuration
128
- package-lock.json reproducible package graph
129
- wrangler.jsonc (Worker) domain, bindings
130
- migrations/ (Worker) deployment-owned D1 schema history
131
- ```
132
-
133
- An upgrade is an intentional dependency change followed by a normal build.
134
- Doing it to a deployment somebody else generated — reading its pin, diffing it
135
- against the template generation it came from, and crossing the version
136
- boundaries that break construction — is [upgrading](./upgrading.md), which is
137
- written for the agent sitting inside that deployment rather than inside this
138
- repository. Instances must not share KV namespaces, D1 databases, secrets, or
139
- encryption keys. Keeping deployment configuration private is sensible even
140
- though this package is public.
141
-
142
- ## Verification
143
-
144
- `npm run check` must pass before anything is claimed done. In order:
145
-
146
- | Script | What it gates |
147
- | --- | --- |
148
- | `check:docs` | local Markdown targets and fragments — including the `github.com` and `raw.githubusercontent.com` URLs that point back into this repository — guide and ethos size caps, duplicate heading anchors, a resurrected `docs/`, stale manual references |
149
- | `check:operator-ui` | the committed browser bundle matches its source, byte for byte |
150
- | `check:lint` | Oxlint's correctness category only — style is authored, not enforced |
151
- | `check:unused` | Knip's unused-export and dependency gate |
152
- | `typecheck` | `tsc --noEmit` for the package and the separate DOM-lib browser project |
153
- | `test` | both vitest projects |
154
- | `build` | the operator bundle, then `tsc -p tsconfig.build.json` into `dist/` |
155
- | `check:examples` | the Node template and the Worker example typecheck against the built package |
156
-
157
- `npm run release:check` adds `check:security` (`npm audit --omit=dev
158
- --audit-level=moderate`) and `check:package`, and is what CI runs on every push
159
- and pull request. `check:package` packs the tarball, asserts the required files
160
- are in it and that no unshippable path leaked in — including any
161
- Cloudflare-named connector or storage path (`connectors/cloudflare`,
162
- `storage/cloudflare`) anywhere in the artifact, `dist/` and `examples/` alike —
163
- derives the shipped guide list from which guides still carry a stub marker,
164
- hands the packed path list to `check-doc-links --packed`, and then runs
165
- `connecta init` and builds and runs the generated deployment's own container.
166
-
167
- That last step enforces the packed-link policy, which is one sentence: **every
168
- relative link in shipped Markdown must resolve to a path the tarball carries,
169
- and a target that is repository-only is cited as an absolute
170
- `https://github.com/zackbart/connecta/blob/main/...` URL** (the
171
- `raw.githubusercontent.com` form for an image, which is how the README hero
172
- still renders on npmjs.com). The tarball is built output, not a checkout: it
173
- carries no `eval/`, `test/`, `scripts/`, or `assets/`, so a relative pointer
174
- into any of them is a dead end for the reader who installed the package, and
175
- the fix is never to ship those directories — that would undo the trim of
176
- [#346](https://github.com/zackbart/connecta/issues/346). A repository URL keeps
177
- the citation verifiable in both directions: an outside reader can follow it, and
178
- `check:docs` resolves it back to the checkout and fails when the cited file
179
- moves ([#378](https://github.com/zackbart/connecta/issues/378)). `CHANGELOG.md`
180
- is exempt from both gates, because release notes quote the paths that existed
181
- when they shipped.
182
-
183
- The Worker example ships in the tarball, its Cloudflare KV and D1 adapters
184
- included: it is the Workers starting template a consumer copies. That is not a
185
- hole in the published surface, because nothing under `examples/` appears in the
186
- `exports` map — every export target resolves into `dist/`, so those adapters
187
- are reference source rather than an importable subpath. They also clear the
188
- platform-specific gate above on their names (`cloudflare-kv.ts`,
189
- `d1-activity.ts`) rather than by exemption: that gate is a blunt pattern over
190
- the whole artifact, so an example file renamed into `storage/cloudflare` would
191
- fail the pack even though nothing about the published surface had changed.
192
-
193
- Two more runners are deliberately outside `check`:
194
-
195
- - `npm run test:browser` — Playwright against a real headless Chromium
196
- (`npm run test:browser:install` once). It covers the embedded bundle without
197
- adding a browser download to the CI release check.
198
- - `npm run drift:check` — the lower-level maintainer provider contract check.
199
- It reads public MCP references and OpenAPI documents only. No provider
200
- credential is read and nothing files itself; findings are read by a human
201
- and become issues
202
- ([provider conventions](./provider-conventions.md#the-maintainer-run-drift-check)).
203
- - `npm run providers:check` — the normal provider check across every maintained
204
- provider: official MCP documentation plus the OpenAPI contracts for
205
- hand-written HTTP connections. It uses the network, so it stays outside the
206
- deterministic `check` chain.
207
- - `npm run load:admission` — the opt-in capacity matrix and soak
208
- ([request admission](./request-admission.md#measuring-capacity)).
209
-
210
- Releases: `npm run release:check`, tag `v<version>` matching `package.json`
211
- exactly (the publish workflow verifies this and fails otherwise), and
212
- publishing fires on GitHub **Release publication**, not on the tag push.
213
-
214
- ## The test map
215
-
216
- Suites live in `test/` and run as two vitest projects. `WORKERS_SUITES` holds
217
- runtime-portable suites; `NODE_ONLY_SUITES` holds Node-bound suites, each with
218
- a stated reason. The `node` project runs their union; the `workers` project
219
- re-runs the portable list inside workerd against the Worker example's
220
- compatibility settings — so a Workers-only regression, the class of bug the
221
- `CfWorkerJsonSchemaValidator` workaround exists for, fails CI instead of being
222
- found by hand. `test/suite-partition.test.ts` walks the directory and refuses
223
- an unclassified, double-classified, stale, or reasonless entry.
224
-
225
- **New behavior gets a row here.** A suite that is not in this table is either
226
- new and undocumented or dead, and neither is a state to leave the repository
227
- in.
228
-
229
- ### Runtime-portable (`WORKERS_SUITES`)
230
-
231
- | Suite | Covers |
232
- | --- | --- |
233
- | `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 |
234
- | `api-connector.test.ts` | `api()` — kind, description, tool defs, dispatch, default args, unknown tools, handler throws, argument validation, and the construction contract |
235
- | `bearer.test.ts` | constant-time bearer compare, case-insensitive scheme, 401 challenges, and the retired audience options refusing rather than silently unbinding |
236
- | `branding.test.ts` | branding fallbacks and overrides across the operator shells, OAuth result pages, `/favicon.*`, page titles, and escaping — branding is not an injection vector |
237
- | `call-admission.test.ts` | connector-scoped per-runtime downstream admission ([call admission](./call-admission.md)): independent partitions, exact rolling-window reset, cancellation that charges no budget, bounded partition state, local-refusal health isolation, one shared limiter across direct and program calls, and payload-free `/health` aggregates |
238
- | `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 |
239
- | `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 |
240
- | `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 |
241
- | `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 |
242
- | `cloudflare-provider.test.ts` | `cloudflare()` API and MCP construction, the code-mode safety manifest, API tool surface, current R2 and KV jurisdictions, useful output declarations, request building, projections including additive provider fields, typed failures, and credential test |
243
- | `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 no rendering instructions |
244
- | `codemode-compat.test.ts` | the `Executor` seam staying structurally compatible with `@cloudflare/codemode`'s `DynamicWorkerExecutor`, enforced by `tsc` |
245
- | `config.test.ts` | the grouped `ConnectaConfig` boundary — each group forwarding to its internals, malformed admission bounds failing construction, and unknown own-properties rejected by their complete path before construction does work |
246
- | `credentials.test.ts` | the pure stored-shape classifier (containment, not equality) and the AES-GCM vault: round-trip, ciphertext bound to its connector id, named field sets, masked metadata, wrong-key rejection, deletion, coexistence with OAuth keys |
247
- | `d1-activity-example.test.ts` | the Worker example's deployment-owned D1 activity store: actor namespace round-trip, payload-free friction reconstructed from the persisted code, and agreement with the package's friction table |
248
- | `downstream-oauth.test.ts` | `KvOAuthProvider` round-trips and generation races, runtime-local rotating-token refresh coordination across request scopes, refresh failure/retry, `auth_required` versus `error`, `startAuth`/`finishAuth`, callback refusal equality, bounded diagnostics, and HTML escaping |
249
- | `errors.test.ts` | `ConnectorCallError` codes, retryable defaults and overrides, `retryAfterMs` round-trip, typed-over-heuristic classification, `AbortError` as a retryable timeout, and framing errors |
250
- | `execute.test.ts` | the code-mode host bridge: identifier sanitization, account titles in program discovery, MCP-result unwrapping, sandbox provider construction, authenticated thrown-failure framing, fail-closed filtering of destructive and unannotated tools, MCP/code-mode invocation parity, and payload-free describe diagnostics |
251
- | `execute-emit.test.ts` | `connecta.emit` (M1–M10) — block validation, budgets, the provider, delivery after the result envelope on success only, and the defaults |
252
- | `executor-admission.test.ts` | the portable bounded FIFO both pools use: active and queue ceilings, stable retryable overload, queue timeout, cancellation removal, idempotent release, shutdown |
253
- | `guarded-fetch.test.ts` | the guarded transport — construction, request building, destination confinement, and response handling |
254
- | `guest-api-contract.test.ts` | the shared guest contract on the Dynamic Worker, including caught call, typed inline describe recovery, discovery, utility, parallel-call, and budget failure codes; plus the real authority boundary — local `data:` fetch, denied egress, unresolved DNS, empty environment paths, unavailable filesystem/HTTP builtins, and present runtime globals |
255
- | `identity-scope.test.ts` | identity-derived connector visibility, named pools at `/mcp/<pool>` (grant-gated, intersected with the identity ceiling, identical 404 for undeclared, refused, and throwing grants, construction-time refusals), exact `connector.tool` grants enforced identically across discovery, direct calls, the program host bridge, and the connection UI, fail-closed grant parsing, the once-per-isolate absent-grant warning, personal credential isolation, separate shared-auth and personal-auth management permissions, and personal OAuth callback ownership |
256
- | `linear-provider.test.ts` | the Linear proxy's construction, guide, plan-aware catalog superset, and current workspace, template, and issue-sharing classifications |
257
- | `meta-tools-call.test.ts` | registry-backed calls: structured errors, truncation and `get_result`, per-connector result bounds, JSON representation failures, MCP content bounds, and offset alignment |
258
- | `meta-tools-search.test.ts` | registry-backed discovery: bounded search with page and address maxima, compact and JSON schemas with constraints, typed describe recovery and suggestions, and structured-result compatibility |
259
- | `meta-tools.test.ts` | the remaining registry-backed meta-tools: the complete on-demand usage skill, connector-guide selection and summary bounds, stored-credential drift, catalog health, authorization, probe timeouts, and unavailable or unknown browse recovery |
260
- | `mixpanel-provider.test.ts` | the Mixpanel proxy, its conditional-input guide, destructive metadata fill, and complete 64-tool schema-digest manifest |
261
- | `notion-provider.test.ts` | Notion's API and MCP construction, the hosted safety manifest and drift behavior, the deliberate REST surface including declined expanded page inputs, request construction, lean projections, both pagination conventions, error mapping, and writes |
262
- | `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 |
263
- | `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 |
264
- | `optional-modules.test.ts` | absent modules, UI-free OAuth, fast lists and independent detail deadlines, explicit auth-management grants, invalid-resolver refusal, and passive OAuth consent-state protection |
265
- | `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) |
266
- | `provider-registry.test.ts` | all seven maintained providers inside real deployments: boot, description, address, catalog, storage, credential, admission, and activity isolation; plus provider-specific discovery and guide contracts |
267
- | `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 |
268
- | `remote-mcp.test.ts` | `remoteMcp()` against an in-process server through the `_transportFactory` seam: passthrough, downstream `isError`, Workers-safe output-schema validation, request-scoped client reuse and at-most-once scope close; plus the real transport's manual redirect policy, destination guard, credential containment, and downstream session termination |
269
- | `remote-mcp-credential.test.ts` | `remoteMcp()` drawing a static key from the connection UI: the declared slot and its refusal of named fields and bad header names, header framing (bearer, bare, and the two `Basic` forms) observed on the wire, an empty slot failing as `auth_required` rather than reaching the downstream, a value carrying a control character refused before framing and absent from every surface — `call_tool`, `status`, the Test result, the payload-free activity event, and the thrown error — rotation replacing the cached client and a connect already in flight while a wiped value fails the next call, the Test action's catalog probe and scope close, the cleartext-destination warning, and the vault and `authorize_connector` handoff end to end |
270
- | `remote-mcp-pagination.test.ts` | the `tools/list` cursor chain in both directions — exact cursor handoff, first-wins dedup, a failed later page rejecting rather than returning its prefix, the runaway backstops, the tool-metadata re-prime across pages, and paginated catalogs reaching the discovery path |
271
- | `request-admission.test.ts` | `/mcp` bounded before auth, the stable 503 and `Retry-After`, health and operator responsiveness under saturation, payload-free counters, queued cancellation, shutdown rejection while active work drains, and the separate fallback code pool |
272
- | `result-shapes.test.ts` | passive output-shape learning: value-free bounded inference, merging, 256-entry LRU eviction, 24-hour expiry, runtime isolation, read-only admission, declared-schema precedence, definition-change invalidation, discovery provenance, and failure isolation |
273
- | `revenuecat-provider.test.ts` | the RevenueCat proxy's per-project key scoping and account-wide OAuth guides, its purpose-bearing summary, the refund-preference read and argued borderline verdicts in its digest-free manifest, and the deliberately unclassified `render-paywall-screenshot` |
274
- | `server.test.ts` | end-to-end `/mcp` (401 → compact initialize instructions → seven compact definitions with bounded connector inventory, account titles, on-demand investigation guidance, and no Apps metadata or resource capability → complete usage skill → `call_tool`), conditional guide pointers, open routes, Clerk `.well-known` metadata without network, code mode, removed connector HTTP hooks rejected at construction, and deferred catalog reads through both discovery surfaces |
275
- | `server-route-contracts.test.ts` | the route contracts `server.ts` must keep byte-identical: built-in routes and unknown-path 404s inside the security wrapper, open data-free shells with framing denied, per-route auth and same-origin requirements with exact 401/403/405 bodies, and OAuth `verifyState`-before-`finishAuth` ordering |
276
- | `startup-warnings.test.ts` | every construction-time `logger.warn` and, as importantly, the conditions that must *not* trigger one: open mode with a credential or OAuth connector, `publicUrl` unset beside OAuth, dropped branding and `uiAuth` URLs, a missing `verifyState`, a credential test-hook mismatch, and an unusable `calls.maxResultBytes` |
277
- | `stripe-provider.test.ts` | the Stripe proxy's mixed-mode OAuth and fixed-mode header contracts, current eleven-tool classifications, admission, exact account selectors, and no-guess rule |
278
- | `operator-view.test.ts` | the app's pure state rules from `view.ts`: filtering, page routing, capability states, activity summaries, drift display, and identity reset |
279
- | `ui-credentials.test.ts` | credential-management routes: save, test, delete, validation, authentication, same-origin checks, and multi-field credential shapes |
280
- | `ui.test.ts` | the server shell and remaining `/ui/*` routes: gated `/ui/data` with broken-connector isolation and registry-owned catalog-observation containment, plus the URL safety gates |
281
- | `validate.test.ts` | `validateToolInput()` — a returned (not thrown) `invalid_args` naming the path, `additionalProperties: false` enforcement, per-schema validator caching, and an unusable schema passed through with one warning |
282
- | `vercel-provider.test.ts` | `vercel()` API and MCP construction, MCP inventory classification, team scoping, project and deployment projections, finite build and runtime logs, value-safe environment variables, domains, lifecycle writes, REST hatches, typed failures, and credential test |
283
-
284
- ### Node-bound (`NODE_ONLY_SUITES`)
285
-
286
- Each entry carries its reason in `vitest.config.ts`; the reason is the
287
- justification for *not* re-running it in workerd, so "it was easier" is not one.
288
-
289
- | Suite | Covers | Why Node |
290
- | --- | --- | --- |
291
- | `deployment-shapes.test.ts` | the Worker as the only example with a loader-only sandbox, its agent instructions and setup guide pinning Claude and both ChatGPT Managed OAuth callback forms, 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, explicit optional modules 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 |
292
- | `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 |
293
- | `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 |
294
- | `drift-check.test.ts` | the credential-free maintainer drift checker: recorded touched endpoints, heading, table, and inline MCP inventories, setup-only providers, live-schema ownership, a quiet revision bump, clear failures for unavailable inputs, `$ref` traversal, and one well-formed row per endpoint | spawns the checker against filesystem fixtures |
295
- | `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 |
296
- | `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 |
297
- | `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 |
298
- | `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 |
299
- | `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 |
300
- | `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 |
301
- | `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 |
302
- | `quickjs-child-stderr.test.ts` | the QuickJS child-process boundary: an explicitly empty environment despite parent secrets and `NODE_OPTIONS`, plus abnormal exits retaining only an 8 KiB stderr tail in the parent-side diagnostic | mocks Node child-process streams |
303
- | `quickjs-executor.test.ts` | executable usage example with dependent, missing, and approval-required evidence; the child-process sandbox — code normalization, provider bridges and canonical connector calls, bounded IPC, separate guest-CPU and wall budgets, saturation, cancellation and shutdown, crash and OOM recovery, host-call hangs, stalled-promise detection | runs the Node QuickJS child-process executor |
304
- | `quickjs-log-limits.test.ts` | bounded `console.*` capture — per-entry cut, cumulative character and transport budgets, escape-heavy floods preserving the guest result | runs the Node QuickJS child-process executor |
305
- | `suite-partition.test.ts` | this partition, including itself: every `*.test.ts` in exactly one list, stale entries and empty reasons refused | walks the test directory to guard the partition |
306
- | `template-file-activity.test.ts` | the Node template's own activity store — persistence across restart, torn-line repair, newest-first paging, and compaction past the slack window | runs it against real files |
307
- | `upgrade-guide.test.ts` | the [upgrade guide](./upgrading.md)'s claims about somebody else's deployment — the generated file inventory, the seven tool names doctor demands, version boundaries that actually shipped, the exact newest boundary and generation B endpoint matching this release, a bump target that is this release, and the three places a reader finds it | reads the guide, the template tree, and the CLI with Node filesystem APIs |
308
- | `version.test.ts` | `CONNECTA_VERSION` matching `package.json` and the Node template's exact dependency pin | reads both package manifests with Node filesystem APIs |
309
-
310
- ### Outside `npm run check`
311
-
312
- | Suite | Covers |
313
- | --- | --- |
314
- | `browser/operator-ui.spec.ts` | the operator wiring in a real browser: Clerk loader order across its version redirect and a real load failure, the shell staying open until authentication, credential and OAuth flows end to end, drift shown without naming a tool, and every failure and empty state |
315
-
316
- **The `_transportFactory` seam.** `RemoteMcpOptions._transportFactory` is
317
- internal, not public API: when set, `remoteMcp()` uses that `Transport` instead
318
- of building an HTTP one. Tests link an in-memory transport to an in-process MCP
319
- server, so remote-MCP behavior is exercised without a network or a real OAuth
320
- server. Two consequences worth knowing before you use it: an in-memory
321
- transport has no session semantics, so anything about `Mcp-Session-Id` needs
322
- the real HTTP transport, and anything about redirects or destination
323
- confinement does too.
324
-
325
- ## Troubleshooting
326
-
327
- - **MCP clients cache the tool list.** After adding a connector or completing a
328
- downstream OAuth flow, restart the client. It will not re-list on its own.
329
- Connecta declares a one-hour private `tools/list` cache hint, which is a
330
- ceiling on how long a well-behaved client may wait, not a promise it will.
331
- - **`auth_required` that never clears.** Confirm `publicUrl` is set and
332
- `GET <publicUrl>/oauth/callback/<connectorId>` is reachable from a browser,
333
- and that storage is durable rather than `memoryStorage()` across restarts.
334
- Then `authorize_connector` to restart the flow; `force: true` wipes stored
335
- credentials for a clean retry.
336
- - **A connector with no `verifyState` refuses every callback.** That is the
337
- designed behavior, not a bug: handing an unverified code to `finishAuth` is
338
- the vulnerability. The startup warning names the connector.
339
- - **401 loops from a Clerk client that cannot discover auth.** The client must reach
340
- the open `/.well-known/oauth-protected-resource` (and the `/mcp` variant);
341
- confirm CORS and the Clerk keys, and that DCR is enabled on the Clerk
342
- instance.
343
- - **An Access-protected MCP client receives redirects or loops.** Enable Managed
344
- OAuth on the Access application and use an RFC 8707-capable client. Access,
345
- not connecta, must answer the unauthenticated challenge and `/.well-known/`
346
- metadata. Do not bypass those routes. For unattended automation, use an
347
- Access Service Auth policy and service-token headers instead.
348
- - **No sessions and no server push, by design.** The transport is stateless.
349
- Scope resolves per request, which is also where the MCP spec has arrived.
350
- - **A tool that should be callable from a program is not.** Only tools
351
- explicitly annotated `readOnlyHint: true` are admissible inside the sandbox.
352
- A missing, false, or contradictory annotation fails closed, every time, and
353
- the recovery is `call_destructive_tool` — not a wider sandbox.
354
- - **`check:operator-ui` fails after a UI change.** Run
355
- `npm run build:operator-ui` and commit the regenerated
356
- `src/operator-ui/generated.ts` ([operator UI](./operator-ui.md#why-the-bundle-is-committed)).
357
- - **Upgrade the MCP SDK and Zod together**, then run `npm run release:check`.
358
- The SDK packages are pinned exactly and paired with Zod 4 to keep the
359
- optional code-mode peer graph valid.
@@ -1,135 +0,0 @@
1
- # Operator UI
2
-
3
- The browser surface a human uses to see what a deployment exposes and to manage
4
- the authentication material behind it. It is a small Preact app compiled by the
5
- repository's own esbuild step and inlined into a data-free server shell.
6
-
7
- Read [`ethos.md`](../ethos.md) first. Code declares capabilities and access;
8
- the UI displays the current user's effective permissions and manages only
9
- authentication material explicitly permitted by that code. It never edits the
10
- connector set, tool catalog, annotations, OAuth scopes, or permission rules.
11
-
12
- ## Enable the UI
13
-
14
- ```ts
15
- import { operatorUi } from "@zackbart/connecta/ui";
16
-
17
- createConnecta({
18
- connectors,
19
- executor,
20
- auth,
21
- ui: operatorUi({ branding: { productName: "Team connections" } }),
22
- });
23
- ```
24
-
25
- The UI module owns its browser bundle and routes. Omit `ui` to omit those
26
- routes and runtime imports. OAuth callbacks remain in core; authorized
27
- interactive MCP callers can complete consent without the UI. Branding belongs
28
- to `operatorUi` options, with neutral callback branding when no UI is mounted.
29
-
30
- ## Connections and activity
31
-
32
- Connections is the main page. Each connection combines its status, effective
33
- permissions, credential metadata, and permitted OAuth or credential actions.
34
- There is no separate Credentials or Tokens tab. A user may see and invoke a
35
- shared connector without permission to replace the grant everyone uses.
36
- `identity.credentialAdministration` and `identity.personalConnection` select
37
- shared and personal management rights, and both default to none.
38
-
39
- Activity appears only when the optional history module has a readable store
40
- and the caller passes `identity.activityAccess` and any additional read gate.
41
- It is a global history, so permission to use one connector does not imply
42
- permission to inspect that history. There is no member roster or policy editor.
43
-
44
- The Node and Worker deployment READMEs show how to enable the modules and grant
45
- the intended identities access. The configured bearer in the Node template can
46
- read connection status but never mutate credentials as an interactive human.
47
-
48
- ## Loading and request lifetime
49
-
50
- The server shell contains no connector or credential data. Authenticated
51
- `/ui/data` returns the configured visible connection list without waiting for
52
- provider status or tool discovery. Details load through `GET /ui/connectors/<id>`, independently,
53
- under a bounded request lifetime. Unknown and loading states stay explicit;
54
- a provider failure leaves the other connections usable.
55
-
56
- A status read does not start OAuth or create authorization handoffs. Connect is
57
- an explicit authorized POST. Successful save, reconnect, and disconnect actions
58
- show their result without waiting for an unrelated full-catalog reload. Server
59
- mutations still await catalog invalidation before replying, so another request
60
- cannot consume a persisted catalog from before a credential change.
61
-
62
- Each details request owns and closes its downstream connector scope. Never
63
- cache a transport, request signal, or awaited promise in the UI module.
64
-
65
- ## Browser identity and security
66
-
67
- Cloudflare Access is ambient browser auth. When the Worker invocation has
68
- `ctx.access`, the shell emits no Clerk loader or browser-readable token.
69
- Same-origin fetch carries the HttpOnly Access cookie, and the server uses the
70
- trusted runtime identity. Sign out navigates to `/cdn-cgi/access/logout`.
71
- Clerk deployments use their configured interactive provider.
72
-
73
- Mutation requires exact same-origin `Origin`, an interactive identity,
74
- connector visibility, and the relevant management permission. Personal actions
75
- resolve only to the current principal's partition. Credential reads return
76
- metadata, never saved values or masked fragments. Mutation cannot change any
77
- declared capability. `test/operator-boundary.test.ts` checks that boundary.
78
-
79
- The browser store fences responses by identity generation. Switching identity
80
- clears the prior identity's state and discards its outstanding responses.
81
- Components render elements, not HTML strings; links pass the shared URL gate.
82
- Loading, failure, empty, and success states must all provide a useful next
83
- step. A failed mutation preserves form input and does not masquerade as success.
84
-
85
- Catalog drift remains counts and a timestamp. A missing observation means
86
- "not observed", not that the downstream catalog is unchanged. The UI does not
87
- expose tool schemas or raw payloads as diagnostics.
88
-
89
- ## Working on it
90
-
91
- Source changes require a rebuild: `npm run build:operator-ui` regenerates
92
- `src/operator-ui/generated.ts`, and `npm run check:operator-ui` fails when the
93
- committed artifact is stale. Both run through the same esbuild call, so the
94
- check compares byte for byte.
95
-
96
- Tests split along the DOM line, because `test/ui.test.ts` runs in workerd as
97
- well as Node and there is no DOM in either:
98
-
99
- - `test/ui.test.ts` — the server shell, the `/ui/*` routes, and the app's pure
100
- state rules from `view.ts`.
101
- - `test/operator-store.test.ts` — `store.ts` itself, against a fake browser: the
102
- Clerk listener, `gate()`, the generation fence, and the request path. The
103
- rules in `view.ts` prove what an identity change *erases*; this suite proves
104
- something calls them when the identity actually changes. It typechecks in the
105
- DOM-lib program (`tsconfig.operator-ui.json`) because it imports the store.
106
- - `test/browser/operator-ui.spec.ts` — the wiring, in a real browser:
107
- Clerk loader order across its version redirect and a real load failure, plus
108
- credential and OAuth flows end to end, including their failure and
109
- empty states. Run it with `npm run test:browser`
110
- (`npm run test:browser:install` once, for Chromium). It is not part of
111
- `npm run check`.
112
-
113
- ## Why the bundle is committed
114
-
115
- `src/operator-ui/generated.ts` is generated and checked in. The alternatives
116
- were considered and lost:
117
-
118
- - **Build during `prepack`.** The artifact would still have to exist before
119
- `tsc` runs, so every contributor and every CI job would need the browser build
120
- before typechecking — and a published tarball would carry a build output
121
- nobody could diff against its source.
122
- - **Exclude it from the tarball.** `dist/operator-ui/generated.js` is imported
123
- by `src/ui.ts`; a deployment that installs the package needs it. Excluding it
124
- ships a broken import.
125
-
126
- Committing keeps one prebuilt string that Node and Workers read unchanged, with
127
- no browser toolchain at install, pack, or deploy time. The cost is a large
128
- generated diff on UI changes, paid deliberately: the bundle is not minified, so
129
- what actually reaches an operator's page can be read in review, and
130
- `check:operator-ui` is what keeps it honest.
131
-
132
- Preact is a `devDependency` for the same reason. It is inlined into the bundle
133
- at build time and never appears in a deployment's dependency tree — the package
134
- has no runtime dependency on it, and `test/package-surface.test.ts` keeps the
135
- published surface that way.