@zackbart/connecta 0.22.2 → 0.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +1 -1
  3. package/dist/auth/downstream-oauth.d.ts +3 -1
  4. package/dist/auth/downstream-oauth.js +11 -0
  5. package/dist/catalog-service.d.ts +1 -6
  6. package/dist/catalog-service.js +3 -53
  7. package/dist/errors.d.ts +1 -1
  8. package/dist/execute.d.ts +5 -52
  9. package/dist/execute.js +40 -347
  10. package/dist/executors/quickjs-protocol.d.ts +0 -7
  11. package/dist/executors/quickjs-protocol.js +2 -10
  12. package/dist/executors/quickjs.js +1 -1
  13. package/dist/index.d.ts +1 -1
  14. package/dist/invocation.d.ts +0 -33
  15. package/dist/invocation.js +54 -121
  16. package/dist/meta-tools.d.ts +3 -6
  17. package/dist/meta-tools.js +6 -17
  18. package/dist/registry.js +4 -0
  19. package/dist/routes/mcp.js +0 -48
  20. package/dist/server.d.ts +1 -2
  21. package/dist/server.js +1 -19
  22. package/dist/skills.d.ts +1 -1
  23. package/dist/skills.js +55 -19
  24. package/dist/types.d.ts +1 -15
  25. package/dist/version.d.ts +1 -1
  26. package/dist/version.js +1 -1
  27. package/documentation/architecture.md +7 -11
  28. package/documentation/auth.md +4 -5
  29. package/documentation/call-admission.md +10 -11
  30. package/documentation/code-mode.md +65 -260
  31. package/documentation/connectors.md +7 -5
  32. package/documentation/meta-tools.md +31 -14
  33. package/documentation/operations.md +7 -9
  34. package/documentation/provider-conventions.md +2 -2
  35. package/documentation/upgrading.md +59 -9
  36. package/ethos.md +14 -13
  37. package/package.json +1 -1
  38. package/templates/node/package.json +1 -1
  39. package/dist/apps-shell.d.ts +0 -37
  40. package/dist/apps-shell.js +0 -174
@@ -8,9 +8,8 @@ annotations, and `get_result` pages bounded results.
8
8
 
9
9
  Every deployment requires an executor and `tools/list` is exactly seven:
10
10
  `execute_code`, `search_tools`, `call_tool`, `call_destructive_tool`,
11
- `authorize_connector`, `get_result`, and `skills`. Discovery breadth and
12
- batching live in `connecta.search`, `connecta.describe`, and `connecta.batch`
13
- inside a program ([#273](https://github.com/zackbart/connecta/issues/273)).
11
+ `authorize_connector`, `get_result`, and `skills`. Discovery uses `connecta.search` and `connecta.describe`; programs compose
12
+ calls with JavaScript promises ([#273](https://github.com/zackbart/connecta/issues/273)).
14
13
 
15
14
  Code-first is what a model sees. Read-only work has two routes: `call_tool` for
16
15
  one known address, and `execute_code` when discovery or any wider work is
@@ -21,8 +20,11 @@ The [guest API contract](./code-mode.md) is what a program is promised.
21
20
 
22
21
  The route is chosen before discovery. An unknown address, a result that will be
23
22
  reduced, a call whose arguments depend on an earlier result, or work with
24
- multiple operations starts with one `execute_code` call and keeps discovery,
25
- calls, and reduction inside it. Distinct operations get distinct short
23
+ multiple operations starts with `execute_code` and keeps discovery, calls, and reduction inside it
24
+ when the schemas and result shapes suffice. An unfamiliar provider result may
25
+ return a small sample for inspection before continuing in another call. This
26
+ exception avoids repeated guesses at text formats or collection roots; it does
27
+ not restore a mandatory discovery-only round trip. Distinct operations get distinct short
26
28
  `connecta.search` queries in that program. A known address needs only
27
29
  `call_tool`.
28
30
 
@@ -41,11 +43,13 @@ The measurements never contain program source, arguments, values, addresses,
41
43
  credentials, logs, or raw error text.
42
44
 
43
45
  Nothing became unreachable. `connecta.describe` takes the same addresses and
44
- formats as the internal catalog service, `connecta.batch` runs 1–10 parallel
45
- read-only calls and returns typed outcomes, and an unfiltered
46
+ formats as the internal catalog service, ordinary promises compose read-only
47
+ calls, and an unfiltered
46
48
  `connecta.search({})` browses every catalog a program can reach. Live connector
47
49
  probing is an operator concern: the operator pages and `/health` own it.
48
50
 
51
+ Program search includes a bounded `connectorTitle` on each tool when configured, so choosing an account or environment does not require a provider read. It is context, not a ranking input or proof of live access.
52
+
49
53
  The three discovery routes use deliberately different envelopes. These are
50
54
  their smallest successful one-tool shapes:
51
55
 
@@ -62,7 +66,7 @@ their smallest successful one-tool shapes:
62
66
 
63
67
  The deployment-derived `execute_code` description includes a live connector
64
68
  inventory before any catalog search. It preserves registry order and uses each
65
- canonical id, adding `shortcut <name>` only when the program namespace differs.
69
+ canonical id and a distinct configured title without generating a second name for programs. Titles normalize whitespace and are capped at 48 UTF-8 bytes, so account and environment hints cannot consume the entire inventory.
66
70
  The complete inventory line is capped at 256 UTF-8 bytes. Entries stay whole,
67
71
  and a truncated line ends with the exact `+N more` count. This reads only the
68
72
  configured registry: it loads no catalog, probes no credential, grants no
@@ -177,6 +181,22 @@ This split avoids two normative copies while preserving a valid first program
177
181
  for clients that never fetch the skill. Deployments without connector guides
178
182
  receive none of the short conditional guide pointers in their definitions.
179
183
 
184
+ ## Task guidance
185
+
186
+ `skills({ name: "investigate" })` provides on-demand guidance for purchase
187
+ verification, experiment checks, and customer or deployment investigations.
188
+ The execute description points to it when planning is unclear; routine reads need no additional guide fetch. It explains how to
189
+ resolve app/account/environment, follow evidence across services, establish
190
+ capability limits, and stop with a clear answer or a specific gap. It is shared
191
+ guidance, not a saved workflow or a source of deployment-specific ids. Existing
192
+ connector titles, purposes, and guides still own those distinctions.
193
+
194
+ The usage skill keeps the executable mechanics. Its dependent-call example
195
+ searches each operation separately, uses the page's `tools` array and canonical
196
+ addresses, and reports unresolved evidence instead of inventing an address or
197
+ querying another account. Its source runs against local fixtures in the QuickJS
198
+ suite, including missing and approval-required evidence.
199
+
180
200
  ## Result representation
181
201
 
182
202
  For object results, `structuredContent` is the canonical full-fidelity value.
@@ -338,8 +358,7 @@ That route echoes the caller's own arguments back only while they fit a
338
358
  512-byte budget, and then whole — never clipped. An error envelope is not
339
359
  size-guarded the way a result is, so an unbounded echo would let a large
340
360
  argument object produce a refusal many times the deployment's result cap, on
341
- both `call_tool` and calls a program routes through `connecta.call` or
342
- `connecta.batch`. Over budget, `args` is absent and the `purpose` says to
361
+ both `call_tool` and program calls through `connecta.call`. Over budget, `args` is absent and the `purpose` says to
343
362
  re-send what was just sent: the agent already holds its own arguments, and half
344
363
  of them would describe a call nobody made.
345
364
 
@@ -352,9 +371,7 @@ recovery query, each of which lands in both the text content and
352
371
  the address is the thing being corrected, a clipped one still identifies the
353
372
  mistake, and a short one — every real one — comes back exact and untagged.
354
373
 
355
- Shortcut ambiguity inside `execute_code` returns every colliding canonical
356
- address and points at `connecta.call`; the program or model must still choose
357
- which one matches the user's intent. `call_destructive_tool` accepts an optional
374
+ `call_destructive_tool` accepts an optional
358
375
  `reason` of at most 500 characters for the host's human approval view. It is
359
376
  outer-call context only: Connecta neither treats it as authority nor passes it
360
377
  to the downstream connector, and an empty or whitespace-only one is read as no
@@ -395,7 +412,7 @@ about the mistake as all 40,000 would.
395
412
  A remote MCP tool's advertised `inputSchema` is checked in the shared
396
413
  invocation path before admission and provider dispatch. A mismatch is the
397
414
  non-retryable `invalid_args`, consistently across `call_tool`,
398
- `call_destructive_tool`, batch outcomes, and generated-code failures. The error
415
+ `call_destructive_tool`, generated-code failures, and rejected promises. The error
399
416
  names the connector and operation and carries bounded `validation.issues`:
400
417
  JSON Pointer `path`, schema-keyword `code`, and expected shape. Submitted
401
418
  values are never copied into those findings.
@@ -99,7 +99,7 @@ optional.
99
99
  | `discovery.persistCatalog?` | true | persist serializable catalogs as a manifest plus revision-addressed chunks |
100
100
  | `discovery.staleCatalogSeconds?` | 3600 | how long a complete expired catalog stays usable for agent SWR and as a refresh-failure fallback |
101
101
  | `discovery.probeTimeoutMs?` | 30_000 | per-connector deadline for catalog fan-out; a timed-out connector degrades alone. Not a tool-call deadline |
102
- | `calls.defaultTimeoutMs?` | **unset (opt-in)** | deadline for calls that pass no `timeoutMs`. Bounds one attempt, so retries can still extend total duration |
102
+ | `calls.defaultTimeoutMs?` | **unset (opt-in)** | deadline for calls that pass no `timeoutMs`. Each admitted call makes one attempt |
103
103
  | `calls.maxResultBytes?` | 50_000 | inline result cap before truncation and `get_result` paging; a connector may override it. Invalid values warn and fall back |
104
104
  | `execute.maxEmittedBytes?` | 4_000_000 | aggregate `connecta.emit` bytes per run — a transport bound, not a context bound |
105
105
  | `execute.maxEmittedBlocks?` | 32 | content blocks `connecta.emit` accepts per run |
@@ -240,19 +240,18 @@ in.
240
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
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
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 `connecta.ui` findable before connector search |
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
244
  | `codemode-compat.test.ts` | the `Executor` seam staying structurally compatible with `@cloudflare/codemode`'s `DynamicWorkerExecutor`, enforced by `tsc` |
245
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
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
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
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
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, 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 |
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
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
- | `execute-ui.test.ts` | display-only `connecta.ui` (U1–U13) — one-string validation, multiplicity and budget, the provider, `_meta` delivery, shell isolation, and the absence of a payload-to-host call path |
253
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 |
254
253
  | `guarded-fetch.test.ts` | the guarded transport — construction, request building, destination confinement, and response handling |
255
- | `guest-api-contract.test.ts` | the shared guest contract on the Dynamic Worker, including caught call, typed inline describe recovery, discovery, utility, batch, 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 |
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 |
256
255
  | `identity-scope.test.ts` | identity-derived connector visibility, personal credential isolation, shared-auth operator control, and personal OAuth callback ownership |
257
256
  | `linear-provider.test.ts` | the Linear proxy's construction, guide, plan-aware catalog superset, and current workspace, template, and issue-sharing classifications |
258
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 |
@@ -271,8 +270,8 @@ in.
271
270
  | `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
271
  | `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
272
  | `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 and exact model-only Apps metadata → complete usage skill → `call_tool`), conditional guide pointers, open routes, Clerk `.well-known` metadata without network, code mode, and deferred catalog reads through both discovery surfaces |
275
- | `server-route-contracts.test.ts` | the route contracts `server.ts` must keep byte-identical: every built-in answered ahead of connector routes 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 |
273
+ | `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 |
274
+ | `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
275
  | `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
276
  | `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
277
  | `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 |
@@ -300,7 +299,7 @@ justification for *not* re-running it in workerd, so "it was easier" is not one.
300
299
  | `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
300
  | `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
301
  | `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` | the child-process sandbox — code normalization, lazy namespace proxies, 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 |
302
+ | `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
303
  | `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
304
  | `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
305
  | `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 |
@@ -312,7 +311,6 @@ justification for *not* re-running it in workerd, so "it was easier" is not one.
312
311
  | Suite | Covers |
313
312
  | --- | --- |
314
313
  | `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 access-token and OAuth flows end to end, drift shown without naming a tool, and every failure and empty state |
315
- | `browser/program-ui.spec.ts` | the display-only Apps shell in a real browser: local payload JavaScript runs, `connecta` stays absent, and forged payload messages never become host tool calls |
316
314
 
317
315
  **The `_transportFactory` seam.** `RemoteMcpOptions._transportFactory` is
318
316
  internal, not public API: when set, `remoteMcp()` uses that `Transport` instead
@@ -237,8 +237,8 @@ not send the agent to `authorize_connector`, you do not repair the argument
237
237
  object. You re-address — look the id up again, or accept the absence and carry
238
238
  on — and a program looping over ids inside `execute_code` can continue past
239
239
  `not_found` where `connector_call_failed` would have to abort the run, reading
240
- the code off a `connecta.batch` entry's `errorDetails` rather than off a caught
241
- error, which the guest bridge has already stripped to prose. That
240
+ the code from a caught error or a rejected promise. The guest bridge preserves
241
+ the typed classification. That
242
242
  control-flow difference is the H11 test being met; it is not a label for the
243
243
  cause.
244
244
 
@@ -17,6 +17,51 @@ the release notes broke, and prove it with `connecta doctor`.
17
17
  Work on a branch. Every step below is reversible until you delete the old
18
18
  lockfile, and you want the diff reviewable by whoever owns this deployment.
19
19
 
20
+ ## 0.23.0 program API pruning
21
+
22
+ This update removes MCP Apps rendering, connector shortcut globals,
23
+ `connecta.batch`, automatic direct-call retries, and connector-owned HTTP routes. Refresh the client's MCP
24
+ instructions and tool definitions after upgrading. The seven top-level tools,
25
+ operator pages, credentials, result paging, and media emission remain.
26
+
27
+ | Before | After |
28
+ | --- | --- |
29
+ | Connector `handleRequest(request, ctx)` | Move custom HTTP routes into the existing deployment fetch handler |
30
+ | `await tracker.list_issues(args)` | `await connecta.call("tracker.list_issues", args)` using the exact address from discovery |
31
+ | `await connecta.batch(calls)` | `Promise.all` over `connecta.call`, or `Promise.allSettled` to keep failures alongside successes |
32
+ | `await connecta.ui(html)` | Return the data the client needs to render its own view |
33
+ | `call_tool` or `call_destructive_tool` with `maxRetries` | Omit the removed argument; each call makes one attempt and returns typed failures with provider retry hints |
34
+
35
+ A connector that still declares `handleRequest` refuses construction. Handle
36
+ custom routes in the existing Node or Worker deployment before delegating other
37
+ requests to `connecta.fetch`. The deployment owns authentication and security
38
+ headers for its custom responses. Connecta returns 404 for unknown paths.
39
+ Cloudflare Global API Key authentication and multi-field credentials remain
40
+ supported; they need no migration.
41
+
42
+ The direct-call schemas reject unknown arguments, including `maxRetries`, before
43
+ invocation. Review stored programs and deployment-owned usage guides for the
44
+ removed guest functions. Canonical addresses preserve punctuation; do not copy
45
+ the old sanitized shortcut into the address string.
46
+
47
+ ```js
48
+ async () => {
49
+ const outcomes = await Promise.allSettled([
50
+ connecta.call("tracker.list_issues", { state: "started" }),
51
+ connecta.call("tracker.list_projects", {}),
52
+ ]);
53
+ return outcomes.map((outcome) => outcome.status === "fulfilled"
54
+ ? { ok: true, data: outcome.value }
55
+ : { ok: false, code: outcome.reason.code, message: outcome.reason.message });
56
+ }
57
+ ```
58
+
59
+ Reduce large successful values before returning. Promise rejection reasons are
60
+ Error objects; explicitly select their fields for JSON output. Every call still
61
+ passes through the existing read-only checks, admission limits, deadlines, and
62
+ 20-call program budget. Rate-limited calls return `retryAfterMs` without waiting;
63
+ the client can reissue after that delay. No storage migration is required.
64
+
20
65
  ## Read what you have first
21
66
 
22
67
  Three questions, in order. Answer all three before editing anything — the
@@ -57,7 +102,7 @@ exist so far:
57
102
  | --- | --- | --- |
58
103
  | **pre-template** | before 0.10.2 | no `connecta init` existed; hand-written, or copied from the retired `examples/node` |
59
104
  | **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.22.2 | 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 |
105
+ | **B** | 0.16.0 – 0.23.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
106
 
62
107
  Generation A is a decade in template years and identifying it precisely does
63
108
  not matter, because you are about to reconstruct it exactly rather than guess
@@ -106,7 +151,7 @@ know what to preserve, once to know what to re-verify at the end.
106
151
  ### Bump the pin and install
107
152
 
108
153
  ```sh
109
- npm pkg set dependencies.@zackbart/connecta=0.22.2
154
+ npm pkg set dependencies.@zackbart/connecta=0.23.0
110
155
  npm install
111
156
  ```
112
157
 
@@ -130,7 +175,7 @@ Generate the *current* template beside the base you already made, into the same
130
175
  `$SCRATCH`:
131
176
 
132
177
  ```sh
133
- (cd "$SCRATCH" && npx @zackbart/connecta@0.22.2 init current)
178
+ (cd "$SCRATCH" && npx @zackbart/connecta@0.23.0 init current)
134
179
  ```
135
180
 
136
181
  You now have a three-way merge with a real base: `$SCRATCH/base` is what this
@@ -186,7 +231,7 @@ A deployment older than 0.10.2 has no base to diff against. Do not try to
186
231
  manufacture one. Instead:
187
232
 
188
233
  1. `SCRATCH=$(mktemp -d)`, then
189
- `(cd "$SCRATCH" && npx @zackbart/connecta@0.22.2 init current)` — there is no
234
+ `(cd "$SCRATCH" && npx @zackbart/connecta@0.23.0 init current)` — there is no
190
235
  `base` leg here, only the current template to read from.
191
236
  2. Copy `$SCRATCH/current` into the deployment file by file, **skipping
192
237
  `src/index.ts`**.
@@ -207,7 +252,14 @@ first, so cross them bottom-up: start at the oldest one still above this
207
252
  deployment's pin and work back up the page, because each boundary assumes the
208
253
  older ones are already done.
209
254
 
210
- ### 0.21.2 → 0.22.2
255
+ ### 0.22.3 → 0.23.0
256
+
257
+ Migrate stored programs and custom connector routes using the
258
+ [0.23.0 migration](#0230-program-api-pruning). Refresh client instructions
259
+ and tool definitions so account titles and the revised discovery guidance take
260
+ effect. No storage migration is required.
261
+
262
+ ### 0.21.2 → 0.22.3
211
263
 
212
264
  Connector and user policy remain config-as-code. If `identity.connectorAccess`
213
265
  is configured, every interactive human may now manage the authentication of
@@ -572,10 +624,8 @@ all, so this is not a hook to stub out with `() => true`.
572
624
  previously fell through to connector `handleRequest` and then to a 404, so a
573
625
  connector that served any of the three is now shadowed without warning. `GET /`
574
626
  returns the operator shell where 0.6.1 returned 404, and a non-GET on those
575
- routes or on `/ui` returns 405 instead of falling through. Move such a handler
576
- to a path the core does not own: `handleRequest` still runs for everything the
577
- built-in routes miss, so it can add a route and never shadow one
578
- ([architecture](./architecture.md)).
627
+ routes or on `/ui` returns 405 instead of falling through. For the current release, move custom handlers into the deployment: connector
628
+ `handleRequest` is now removed. See the [current migration](#0230-program-api-pruning).
579
629
 
580
630
  ### Removed options that throw
581
631
 
package/ethos.md CHANGED
@@ -1,7 +1,6 @@
1
1
  # connecta — ethos
2
2
 
3
- What connecta is, what it refuses to be, and the invariants every change must
4
- preserve. A contradiction needs a design decision, not a drive-by edit.
3
+ Connecta’s scope, refusals, and invariants. Contradictions require a design decision.
5
4
 
6
5
  ## What this is
7
6
 
@@ -40,9 +39,8 @@ preserve. A contradiction needs a design decision, not a drive-by edit.
40
39
 
41
40
  ## Decisions
42
41
 
43
- Shapes considered and turned down. Proposing one again without a new argument
44
- is not allowed. Accepted designs live in their subsystem guide and the
45
- CHANGELOG, not here.
42
+ Revisiting a verdict requires a new argument. Accepted designs live in
43
+ subsystem guides and the CHANGELOG.
46
44
 
47
45
  | Decision | Verdict | Why |
48
46
  | --- | --- | --- |
@@ -53,7 +51,7 @@ CHANGELOG, not here.
53
51
  | Provider registry / marketplace | refused | prebuilt connections are imports; discovery happens in docs ([#297](https://github.com/zackbart/connecta/issues/297)) |
54
52
  | Expanded Notion page create/update options | refused | different workflows, not missing fields; use `api()` ([#408](https://github.com/zackbart/connecta/issues/408)) |
55
53
  | Protocol sessions & server push | refused | stateless per request |
56
- | Resources & prompts aggregation | refused | tools only; the Apps shell is the one `resources/read` carve-out ([#266](https://github.com/zackbart/connecta/issues/266)) |
54
+ | Resources & prompts aggregation | refused | tools only; clients own presentation ([#266](https://github.com/zackbart/connecta/issues/266)) |
57
55
  | Elicitation passthrough | refused | no route through a stateless aggregator |
58
56
  | Repository formatter | refused | style is authored, not enforced |
59
57
  | Host-side projection of program results | refused | a program projects; a heuristic drops fields invisibly ([#223](https://github.com/zackbart/connecta/issues/223)) |
@@ -67,6 +65,11 @@ CHANGELOG, not here.
67
65
  | Legacy embedded `UIResource` delivery | refused | superseded upstream, rendered by no client we face ([#266](https://github.com/zackbart/connecta/issues/266)) |
68
66
  | Effect as the core effect system | refused | −4% of the core for +75 KB gzip and a second async paradigm; re-measure at v4 stable ([#470](https://github.com/zackbart/connecta/issues/470)) |
69
67
  | Shared bounded queue under both admission controllers | refused | built and measured −17 lines for a hook-parameterised abstraction ([#453](https://github.com/zackbart/connecta/issues/453)) |
68
+ | MCP Apps rendering and `connecta.ui` | removed | clients render returned data |
69
+ | Connector shortcut globals | removed | canonical addresses need no sanitization |
70
+ | `connecta.batch` | removed | JavaScript promises suffice |
71
+ | Automatic direct-call retries | removed | callers own retry timing |
72
+ | Connector HTTP routes | removed | deployments own custom routes |
70
73
  | Caller-selected toolkits | removed | only config may derive an identity's connector view ([#178](https://github.com/zackbart/connecta/issues/178)) |
71
74
  | Proactive credential liveness | removed | fail-at-use is enough ([#179](https://github.com/zackbart/connecta/issues/179)) |
72
75
  | Classic (executor-free) surface | removed | an executor is mandatory ([#273](https://github.com/zackbart/connecta/issues/273)) |
@@ -75,14 +78,14 @@ CHANGELOG, not here.
75
78
  | Semantic tool search | gated | keyword search has not been shown to fail ([#27](https://github.com/zackbart/connecta/issues/27)) |
76
79
  | MRTR / `input_required` passthrough | gated | relayable statelessly; no host or downstream emits it yet ([#176](https://github.com/zackbart/connecta/issues/176)) |
77
80
  | Downstream `ttlMs` cache hints | gated | needs refresh-churn evidence ([#206](https://github.com/zackbart/connecta/issues/206)) |
78
- | Downstream MCP Apps template passthrough | gated | needs a downstream that ships one ([#266](https://github.com/zackbart/connecta/issues/266)) |
81
+ | Downstream MCP Apps template passthrough | refused | clients own presentation; Connecta serves tools and data |
79
82
  | Worker Access inbound auth | provisional | Managed OAuth and Clerk migration need production evidence ([#506](https://github.com/zackbart/connecta/issues/506)) |
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)) |
83
+ | Program UI tool calls | removed | duplicated calls without improving retrieval ([#287](https://github.com/zackbart/connecta/issues/287), [#484](https://github.com/zackbart/connecta/issues/484)) |
81
84
 
82
85
  ## Invariants
83
86
 
84
- One line each; the enforcing tests live beside the subsystem documentation.
85
- Breaking one is a design change wearing a disguise.
87
+ Tests beside subsystem documentation enforce these invariants. Breaking one
88
+ requires a design decision.
86
89
 
87
90
  - **Fail-closed read-only.** A missing, false, or contradictory annotation never gets the benefit of the doubt.
88
91
  - **Generated code cannot mint capabilities.** Admission, credentials, and classification are enforced below the sandbox.
@@ -97,6 +100,4 @@ Breaking one is a design change wearing a disguise.
97
100
  - **Human routes manage auth, never capability.** Signed-in humans manage auth for visible connectors; operators also manage tokens and global activity.
98
101
  - **Structural mistakes throw at construction.** Booting into the wrong shape is worse than not booting.
99
102
 
100
- Connecta began as a radical simplification of
101
- [executor](https://github.com/UsefulSoftwareCo/executor); this file is the
102
- record of that simplification holding.
103
+ Connecta simplifies [executor](https://github.com/UsefulSoftwareCo/executor).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zackbart/connecta",
3
- "version": "0.22.2",
3
+ "version": "0.23.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.",
@@ -15,7 +15,7 @@
15
15
  "typecheck": "tsc --noEmit"
16
16
  },
17
17
  "dependencies": {
18
- "@zackbart/connecta": "0.22.2",
18
+ "@zackbart/connecta": "0.23.0",
19
19
  "quickjs-emscripten": "0.32.0"
20
20
  },
21
21
  "devDependencies": {
@@ -1,37 +0,0 @@
1
- /**
2
- * The one MCP Apps template connecta serves (`U5`, `U6`).
3
- *
4
- * A build-time string constant, not a file read at startup: the core is
5
- * Web-API-only so it runs unchanged on Workers, and the same bytes have to
6
- * serve everywhere. The shell is display-only: it renders whatever HTML a
7
- * program handed `connecta.ui` inside a nested `srcdoc` frame and forwards no
8
- * channel back from that frame to the host, so program-authored markup is
9
- * inert beyond its own pixels.
10
- *
11
- * The address carries a version segment because hosts are permitted to
12
- * prefetch and cache templates by URI: change these bytes, bump the version.
13
- */
14
- /** The only `ui://` URI in the system. No program input reaches it. */
15
- export declare const PROGRAM_UI_RESOURCE_URI = "ui://connecta/program-ui/v3";
16
- /** The mimeType the Apps spec requires of an HTML template. */
17
- export declare const PROGRAM_UI_MIME_TYPE = "text/html;profile=mcp-app";
18
- /**
19
- * The result `_meta` key carrying the payload (`U3`). A plain single-label
20
- * prefix rather than the reverse-DNS form MCP's SHOULD prefers: connecta has
21
- * no domain to reverse, and fabricating one to satisfy a SHOULD is a worse
22
- * answer than the shape the key format's MUST already permits.
23
- */
24
- export declare const PROGRAM_UI_META_KEY = "connecta/ui";
25
- /** The one extension identifier connecta advertises (`U11`). */
26
- export declare const MCP_APPS_EXTENSION = "io.modelcontextprotocol/ui";
27
- /**
28
- * The shell document. Dependency-free and deliberately small: it speaks the
29
- * Apps postMessage dialect (`ui/initialize`, `ui/notifications/initialized`,
30
- * `ui/notifications/tool-result`, `ui/notifications/size-changed`,
31
- * `ui/resource-teardown`), lifts `_meta["connecta/ui"].html` out of the
32
- * delivered tool result, and puts it in a frame. It declares no CSP domains,
33
- * so the host applies its restrictive default and the `srcdoc` frame inherits
34
- * `default-src 'none'`. The payload gets scripts and local interactivity, and
35
- * no network.
36
- */
37
- export declare const PROGRAM_UI_SHELL_HTML = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>connecta program view</title>\n <style>\n html,\n body {\n margin: 0;\n padding: 0;\n background: transparent;\n }\n #program-view {\n display: block;\n width: 100%;\n min-height: 480px;\n border: 0;\n }\n </style>\n </head>\n <body>\n <iframe\n id=\"program-view\"\n title=\"Program-rendered view\"\n sandbox=\"allow-scripts\"\n srcdoc=\"\"\n ></iframe>\n <script>\n (function () {\n \"use strict\";\n // The host frame is the only peer this shell speaks to, in either\n // direction. The payload frame below is sandboxed to scripts alone,\n // with no same-origin escape, and is never handed a reply path:\n // anything it posts fails the source check and is dropped. There is\n // no bridge from program HTML to the host, by construction rather\n // than by validation.\n var host = window.parent;\n var view = document.getElementById(\"program-view\");\n var initializeId = \"connecta-ui-initialize\";\n var lastWidth = 0;\n var lastHeight = 0;\n\n function send(message) {\n if (!host || host === window) return;\n host.postMessage(message, \"*\");\n }\n\n function notify(method, params) {\n send({ jsonrpc: \"2.0\", method: method, params: params });\n }\n\n // Program views are fixed-height by construction. The shell has no\n // bridge to the payload frame \u2014 that is the security posture, not an\n // omission \u2014 so it can never learn the payload's content height, and\n // what it reports here is its own box: the min-height above, unless\n // the host has given it more. Taller content scrolls inside the inner\n // frame rather than growing the view. Raising the min-height is the\n // only lever; a content-height signal would cost the isolation.\n function reportSize() {\n var width = Math.ceil(document.documentElement.clientWidth);\n var height = Math.ceil(document.documentElement.scrollHeight);\n if (width === lastWidth && height === lastHeight) return;\n lastWidth = width;\n lastHeight = height;\n notify(\"ui/notifications/size-changed\", {\n width: width,\n height: height\n });\n }\n\n function payloadHtml(result) {\n if (!result || typeof result !== \"object\") return null;\n var meta = result._meta;\n if (!meta || typeof meta !== \"object\") return null;\n var payload = meta[\"connecta/ui\"];\n if (!payload || typeof payload !== \"object\") return null;\n var html = payload.html;\n return typeof html === \"string\" && html.length > 0 ? html : null;\n }\n\n function render(params) {\n var html =\n payloadHtml(params) ||\n payloadHtml(params && params.result) ||\n payloadHtml(params && params.toolResult);\n if (html === null) return;\n view.srcdoc = html;\n reportSize();\n }\n\n window.addEventListener(\"message\", function (event) {\n if (event.source !== host) return;\n var message = event.data;\n if (!message || message.jsonrpc !== \"2.0\") return;\n if (message.method === \"ui/notifications/tool-result\") {\n render(message.params);\n return;\n }\n if (message.method === \"ui/resource-teardown\") {\n // A host->view request, not a notification: the host waits for\n // this reply before it tears the view down. There is nothing to\n // release, so answer immediately rather than make it time out.\n if (message.id !== undefined && message.id !== null) {\n send({ jsonrpc: \"2.0\", id: message.id, result: {} });\n }\n return;\n }\n // Only a completed handshake earns \"initialized\". A JSON-RPC error\n // response carries the same id, and announcing initialization on one\n // would assert a handshake that never happened.\n if (message.id === initializeId && message.result !== undefined) {\n notify(\"ui/notifications/initialized\", {});\n }\n });\n\n window.addEventListener(\"resize\", reportSize);\n view.addEventListener(\"load\", reportSize);\n\n // Every field here is required by the Apps initialize schema, and a\n // conforming host rejects the request outright when one is missing \u2014\n // which would strand the shell before any tool result arrives.\n send({\n jsonrpc: \"2.0\",\n id: initializeId,\n method: \"ui/initialize\",\n params: {\n appInfo: { name: \"connecta program view\", version: \"1\" },\n appCapabilities: {},\n protocolVersion: \"2026-01-26\"\n }\n });\n reportSize();\n })();\n </script>\n </body>\n</html>\n";
@@ -1,174 +0,0 @@
1
- /**
2
- * The one MCP Apps template connecta serves (`U5`, `U6`).
3
- *
4
- * A build-time string constant, not a file read at startup: the core is
5
- * Web-API-only so it runs unchanged on Workers, and the same bytes have to
6
- * serve everywhere. The shell is display-only: it renders whatever HTML a
7
- * program handed `connecta.ui` inside a nested `srcdoc` frame and forwards no
8
- * channel back from that frame to the host, so program-authored markup is
9
- * inert beyond its own pixels.
10
- *
11
- * The address carries a version segment because hosts are permitted to
12
- * prefetch and cache templates by URI: change these bytes, bump the version.
13
- */
14
- /** The only `ui://` URI in the system. No program input reaches it. */
15
- export const PROGRAM_UI_RESOURCE_URI = "ui://connecta/program-ui/v3";
16
- /** The mimeType the Apps spec requires of an HTML template. */
17
- export const PROGRAM_UI_MIME_TYPE = "text/html;profile=mcp-app";
18
- /**
19
- * The result `_meta` key carrying the payload (`U3`). A plain single-label
20
- * prefix rather than the reverse-DNS form MCP's SHOULD prefers: connecta has
21
- * no domain to reverse, and fabricating one to satisfy a SHOULD is a worse
22
- * answer than the shape the key format's MUST already permits.
23
- */
24
- export const PROGRAM_UI_META_KEY = "connecta/ui";
25
- /** The one extension identifier connecta advertises (`U11`). */
26
- export const MCP_APPS_EXTENSION = "io.modelcontextprotocol/ui";
27
- /**
28
- * The shell document. Dependency-free and deliberately small: it speaks the
29
- * Apps postMessage dialect (`ui/initialize`, `ui/notifications/initialized`,
30
- * `ui/notifications/tool-result`, `ui/notifications/size-changed`,
31
- * `ui/resource-teardown`), lifts `_meta["connecta/ui"].html` out of the
32
- * delivered tool result, and puts it in a frame. It declares no CSP domains,
33
- * so the host applies its restrictive default and the `srcdoc` frame inherits
34
- * `default-src 'none'`. The payload gets scripts and local interactivity, and
35
- * no network.
36
- */
37
- export const PROGRAM_UI_SHELL_HTML = `<!doctype html>
38
- <html lang="en">
39
- <head>
40
- <meta charset="utf-8" />
41
- <meta name="viewport" content="width=device-width, initial-scale=1" />
42
- <title>connecta program view</title>
43
- <style>
44
- html,
45
- body {
46
- margin: 0;
47
- padding: 0;
48
- background: transparent;
49
- }
50
- #program-view {
51
- display: block;
52
- width: 100%;
53
- min-height: 480px;
54
- border: 0;
55
- }
56
- </style>
57
- </head>
58
- <body>
59
- <iframe
60
- id="program-view"
61
- title="Program-rendered view"
62
- sandbox="allow-scripts"
63
- srcdoc=""
64
- ></iframe>
65
- <script>
66
- (function () {
67
- "use strict";
68
- // The host frame is the only peer this shell speaks to, in either
69
- // direction. The payload frame below is sandboxed to scripts alone,
70
- // with no same-origin escape, and is never handed a reply path:
71
- // anything it posts fails the source check and is dropped. There is
72
- // no bridge from program HTML to the host, by construction rather
73
- // than by validation.
74
- var host = window.parent;
75
- var view = document.getElementById("program-view");
76
- var initializeId = "connecta-ui-initialize";
77
- var lastWidth = 0;
78
- var lastHeight = 0;
79
-
80
- function send(message) {
81
- if (!host || host === window) return;
82
- host.postMessage(message, "*");
83
- }
84
-
85
- function notify(method, params) {
86
- send({ jsonrpc: "2.0", method: method, params: params });
87
- }
88
-
89
- // Program views are fixed-height by construction. The shell has no
90
- // bridge to the payload frame — that is the security posture, not an
91
- // omission — so it can never learn the payload's content height, and
92
- // what it reports here is its own box: the min-height above, unless
93
- // the host has given it more. Taller content scrolls inside the inner
94
- // frame rather than growing the view. Raising the min-height is the
95
- // only lever; a content-height signal would cost the isolation.
96
- function reportSize() {
97
- var width = Math.ceil(document.documentElement.clientWidth);
98
- var height = Math.ceil(document.documentElement.scrollHeight);
99
- if (width === lastWidth && height === lastHeight) return;
100
- lastWidth = width;
101
- lastHeight = height;
102
- notify("ui/notifications/size-changed", {
103
- width: width,
104
- height: height
105
- });
106
- }
107
-
108
- function payloadHtml(result) {
109
- if (!result || typeof result !== "object") return null;
110
- var meta = result._meta;
111
- if (!meta || typeof meta !== "object") return null;
112
- var payload = meta["connecta/ui"];
113
- if (!payload || typeof payload !== "object") return null;
114
- var html = payload.html;
115
- return typeof html === "string" && html.length > 0 ? html : null;
116
- }
117
-
118
- function render(params) {
119
- var html =
120
- payloadHtml(params) ||
121
- payloadHtml(params && params.result) ||
122
- payloadHtml(params && params.toolResult);
123
- if (html === null) return;
124
- view.srcdoc = html;
125
- reportSize();
126
- }
127
-
128
- window.addEventListener("message", function (event) {
129
- if (event.source !== host) return;
130
- var message = event.data;
131
- if (!message || message.jsonrpc !== "2.0") return;
132
- if (message.method === "ui/notifications/tool-result") {
133
- render(message.params);
134
- return;
135
- }
136
- if (message.method === "ui/resource-teardown") {
137
- // A host->view request, not a notification: the host waits for
138
- // this reply before it tears the view down. There is nothing to
139
- // release, so answer immediately rather than make it time out.
140
- if (message.id !== undefined && message.id !== null) {
141
- send({ jsonrpc: "2.0", id: message.id, result: {} });
142
- }
143
- return;
144
- }
145
- // Only a completed handshake earns "initialized". A JSON-RPC error
146
- // response carries the same id, and announcing initialization on one
147
- // would assert a handshake that never happened.
148
- if (message.id === initializeId && message.result !== undefined) {
149
- notify("ui/notifications/initialized", {});
150
- }
151
- });
152
-
153
- window.addEventListener("resize", reportSize);
154
- view.addEventListener("load", reportSize);
155
-
156
- // Every field here is required by the Apps initialize schema, and a
157
- // conforming host rejects the request outright when one is missing —
158
- // which would strand the shell before any tool result arrives.
159
- send({
160
- jsonrpc: "2.0",
161
- id: initializeId,
162
- method: "ui/initialize",
163
- params: {
164
- appInfo: { name: "connecta program view", version: "1" },
165
- appCapabilities: {},
166
- protocolVersion: "2026-01-26"
167
- }
168
- });
169
- reportSize();
170
- })();
171
- </script>
172
- </body>
173
- </html>
174
- `;