@zackbart/connecta 0.32.0 → 0.34.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 (158) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/README.md +95 -116
  3. package/SECURITY.md +1 -1
  4. package/bin/connecta.mjs +24 -6
  5. package/bin/version.mjs +1 -1
  6. package/dist/access-tokens.d.ts +167 -23
  7. package/dist/access-tokens.js +676 -140
  8. package/dist/activity.d.ts +3 -0
  9. package/dist/agent-identity.js +47 -0
  10. package/dist/auth/cloudflare-access.js +13 -5
  11. package/dist/branding.d.ts +7 -0
  12. package/dist/branding.js +76 -9
  13. package/dist/call-admission-context.js +15 -0
  14. package/dist/call-admission.js +30 -43
  15. package/dist/call-recovery.js +6 -2
  16. package/dist/catalog-service.js +15 -1
  17. package/dist/config-value-sources.js +16 -9
  18. package/dist/config.d.ts +48 -75
  19. package/dist/config.js +144 -36
  20. package/dist/connector-access.d.ts +4 -34
  21. package/dist/connector-access.js +15 -99
  22. package/dist/connector-caller.d.ts +7 -1
  23. package/dist/connector-caller.js +4 -2
  24. package/dist/connectors/api-connector.d.ts +27 -4
  25. package/dist/connectors/api-connector.js +2 -0
  26. package/dist/connectors/remote-mcp.js +2 -0
  27. package/dist/describe-config.d.ts +31 -7
  28. package/dist/describe-config.js +8 -17
  29. package/dist/execute.js +1 -11
  30. package/dist/fix-prompt.js +2 -2
  31. package/dist/index.d.ts +4 -3
  32. package/dist/index.js +30 -10
  33. package/dist/invocation.js +5 -3
  34. package/dist/membership.d.ts +53 -0
  35. package/dist/membership.js +121 -0
  36. package/dist/module-contracts.d.ts +23 -6
  37. package/dist/oauth-callback-outcome.js +2 -2
  38. package/dist/operator-record.d.ts +2 -2
  39. package/dist/operator-record.js +2 -4
  40. package/dist/operator-ui/agents.d.ts +74 -0
  41. package/dist/operator-ui/agents.js +104 -0
  42. package/dist/operator-ui/config-summary.js +562 -0
  43. package/dist/operator-ui/connection-map.js +375 -0
  44. package/dist/operator-ui/contract.d.ts +149 -28
  45. package/dist/operator-ui/endpoints.js +221 -0
  46. package/dist/operator-ui/fix-prompts.js +5 -5
  47. package/dist/operator-ui/generated.js +5 -4
  48. package/dist/operator-ui/member-page.js +97 -0
  49. package/dist/operator-ui/model.d.ts +14 -6
  50. package/dist/operator-ui/overview.js +134 -0
  51. package/dist/operator-ui/view.d.ts +107 -37
  52. package/dist/operator-ui/view.js +163 -86
  53. package/dist/page-styles.js +5 -3
  54. package/dist/provider.d.ts +61 -8
  55. package/dist/provider.js +46 -7
  56. package/dist/providers/_shared/google/workspace.d.ts +56 -17
  57. package/dist/providers/_shared/google/workspace.js +34 -8
  58. package/dist/providers/_shared/rest/tools.js +2 -2
  59. package/dist/providers/_shared/rest/value-safety.d.ts +50 -0
  60. package/dist/providers/_shared/rest/value-safety.js +699 -7
  61. package/dist/providers/ccb/index.js +2 -2
  62. package/dist/providers/cloudflare/index.js +3 -3
  63. package/dist/providers/cloudflare/rest.js +25 -3
  64. package/dist/providers/cloudflare/skill.generated.js +1 -1
  65. package/dist/providers/cloudflare/value-safety.js +32 -0
  66. package/dist/providers/docs/index.js +4 -4
  67. package/dist/providers/docs/skill.generated.js +2 -2
  68. package/dist/providers/drive/index.js +4 -4
  69. package/dist/providers/drive/skill.generated.js +2 -2
  70. package/dist/providers/forms/index.js +4 -4
  71. package/dist/providers/forms/skill.generated.js +2 -2
  72. package/dist/providers/gmail/index.js +11 -9
  73. package/dist/providers/gmail/skill.generated.js +2 -2
  74. package/dist/providers/mixpanel/index.js +1 -1
  75. package/dist/providers/mixpanel/skill.generated.js +3 -3
  76. package/dist/providers/notion/index.d.ts +7 -0
  77. package/dist/providers/notion/index.js +100 -11
  78. package/dist/providers/notion/rest.js +45 -5
  79. package/dist/providers/notion/skill.generated.js +2 -2
  80. package/dist/providers/notion/value-safety.js +30 -5
  81. package/dist/providers/overflow/index.js +2 -2
  82. package/dist/providers/overflow/skill.generated.js +2 -2
  83. package/dist/providers/planning-center/index.js +2 -2
  84. package/dist/providers/planning-center/skill.generated.js +4 -4
  85. package/dist/providers/revenuecat/skill.generated.js +2 -2
  86. package/dist/providers/sheets/index.d.ts +3 -0
  87. package/dist/providers/sheets/index.js +7 -4
  88. package/dist/providers/sheets/skill.generated.js +2 -2
  89. package/dist/providers/slides/index.js +4 -4
  90. package/dist/providers/slides/skill.generated.js +2 -2
  91. package/dist/providers/stripe/rest.js +9 -2
  92. package/dist/providers/stripe/skill.generated.js +1 -1
  93. package/dist/providers/stripe/value-safety.js +22 -0
  94. package/dist/providers/tithely/index.js +1 -1
  95. package/dist/providers/vercel/index.js +1 -1
  96. package/dist/providers/vercel/rest.js +20 -2
  97. package/dist/providers/vercel/skill.generated.js +2 -2
  98. package/dist/providers/vercel/value-safety.js +22 -0
  99. package/dist/registry.d.ts +2 -3
  100. package/dist/registry.js +23 -9
  101. package/dist/routes/access-tokens.js +82 -75
  102. package/dist/routes/activity-disclosure.js +17 -18
  103. package/dist/routes/activity.js +34 -54
  104. package/dist/routes/actor-labels.js +33 -4
  105. package/dist/routes/agent-path.js +43 -0
  106. package/dist/routes/connect.js +16 -12
  107. package/dist/routes/credentials.js +16 -40
  108. package/dist/routes/mcp.js +161 -104
  109. package/dist/routes/oauth-management.js +11 -5
  110. package/dist/routes/oauth.js +5 -11
  111. package/dist/routes/operator.js +24 -23
  112. package/dist/routes/shared.d.ts +60 -20
  113. package/dist/routes/shared.js +187 -96
  114. package/dist/routes/ui-config.js +437 -97
  115. package/dist/routes/ui-data.js +2 -2
  116. package/dist/routes/ui.js +9 -16
  117. package/dist/runtime/call-admission.js +9 -0
  118. package/dist/server.js +5 -0
  119. package/dist/skills.js +10 -0
  120. package/dist/storage/keys.js +9 -1
  121. package/dist/storage/sql.js +5 -1
  122. package/dist/types.d.ts +29 -7
  123. package/dist/ui.d.ts +1 -1
  124. package/dist/ui.js +23 -16
  125. package/dist/usage-guide.js +1 -1
  126. package/dist/version.d.ts +1 -1
  127. package/dist/version.js +1 -1
  128. package/dist/workspace-address.js +9 -0
  129. package/dist/workspace-subject.js +23 -0
  130. package/documentation/README.md +47 -23
  131. package/documentation/architecture.md +371 -61
  132. package/documentation/auth.md +897 -249
  133. package/documentation/code-mode.md +37 -16
  134. package/documentation/deploying.md +126 -25
  135. package/documentation/integrating.md +97 -23
  136. package/documentation/meta-tools.md +50 -31
  137. package/documentation/migration-0.33.md +352 -0
  138. package/documentation/migration-0.34.md +67 -0
  139. package/documentation/operating.md +3 -1
  140. package/documentation/operator-tests.md +147 -34
  141. package/documentation/operator-ui.md +519 -64
  142. package/documentation/provider-auth-migration.md +37 -23
  143. package/documentation/provider-migration-0.29.md +2 -2
  144. package/examples/worker/AGENTS.md +37 -9
  145. package/examples/worker/README.md +127 -61
  146. package/examples/worker/src/connecta.config.ts +40 -23
  147. package/examples/worker/wrangler.jsonc +4 -1
  148. package/package.json +19 -18
  149. package/templates/node/.env.example +3 -0
  150. package/templates/node/AGENTS.md +33 -6
  151. package/templates/node/Dockerfile +2 -2
  152. package/templates/node/README.md +80 -52
  153. package/templates/node/docker-compose.yml +3 -2
  154. package/templates/node/package.json +4 -3
  155. package/templates/node/src/adopt-token.ts +23 -0
  156. package/templates/node/src/connecta.config.ts +28 -12
  157. package/templates/node/src/rotate-token.ts +17 -0
  158. package/templates/node/src/provision-token.ts +0 -17
@@ -2,17 +2,14 @@
2
2
 
3
3
  Use this upgrade guide when moving a Stripe, Notion, Vercel, or Cloudflare
4
4
  deployment from 0.30 to 0.31, where `auth` selects each provider's
5
- implementation ([#801](https://github.com/zackbart/connecta/issues/801)).
6
- The [0.31.0 release notes](https://github.com/zackbart/connecta/blob/main/release-notes/0.31.0.md) summarize the change.
5
+ implementation.
7
6
  [Deployment upgrades](./deploying.md#upgrade-an-existing-deployment) cover other
8
7
  version changes; [integrating services](./integrating.md) covers new connectors.
9
8
 
10
9
  A dual provider requires `auth`. `{ type: "oauth" }` connects the vendor's
11
10
  hosted MCP server, OAuth only. A key type connects Connecta's REST connector
12
11
  with an operator-managed credential. There is no default, and one connector id
13
- uses one implementation: configure two ids for both. The
14
- [decision record](https://github.com/zackbart/connecta/blob/main/decisions/0005-auth-selects-implementation.md)
15
- explains why.
12
+ uses one implementation: configure two ids for both.
16
13
 
17
14
  ## Stripe
18
15
 
@@ -62,6 +59,10 @@ contracts: search finds operations in a pinned API index, every call is
62
59
  checked against that index before it is sent, results arrive as
63
60
  `{ status, data, page? }`, and POST writes send the caller's `idempotencyKey`
64
61
  or a generated one as `Idempotency-Key` and return it (DELETE sends none).
62
+ Caller-supplied keys may contain up to 255 characters. An ambiguous POST write
63
+ also returns the key in `uncertainCall.recovery.idempotencyKey`, including when
64
+ the call deadline interrupts it. See [argument recovery](./meta-tools.md#argument-recovery)
65
+ before retrying.
65
66
  Every request, v1 and v2, sends `Stripe-Version: 2026-09-30.endive`, the
66
67
  version of the pinned index, whatever the account's default API version, so
67
68
  response shapes and accepted parameters follow that version. Check fields
@@ -73,9 +74,7 @@ A key whose `sk_live_`/`rk_live_` or `sk_test_`/`rk_test_` prefix contradicts
73
74
  Lost on the key path: natural-language Sigma (`stripe_analytics`), metrics,
74
75
  documentation search, the implementation planner, reports, and feedback exist
75
76
  only on the hosted OAuth connection. Programs calling those tools on a key
76
- connector need an OAuth connector beside it. The
77
- [Stripe skill](https://github.com/zackbart/connecta/blob/main/src/providers/stripe/SKILL.md)
78
- owns the conventions for both.
77
+ connector need an OAuth connector beside it. Read the connector runtime guide with `connecta.skill("connector:<id>")` in `execute_code` for its usage conventions.
79
78
 
80
79
  ## Notion
81
80
 
@@ -101,6 +100,15 @@ connectors: [
101
100
  | `integration_list_comments`, `integration_add_comment` | `notion_api_read` `GET /v1/comments`; `notion_api_write` `POST /v1/comments`. |
102
101
  | `integration_update_page_properties`, `integration_trash_page` | `notion_api_write` `PATCH /v1/pages/{page_id}` with `properties`, or `in_trash` to trash or restore. |
103
102
 
103
+ Both auth variants accept optional `callAdmission`. On a token connector it
104
+ replaces the whole default policy of 180 calls per minute and 3 concurrent
105
+ per runtime. Malformed policies throw at construction. Notion's
106
+ [request limits](https://developers.notion.com/reference/request-limits) allow
107
+ 600 requests per minute per connection on Business and Enterprise and 180 on
108
+ other plans, with a separate workspace-wide limit shared by all connections.
109
+ Account for other runtimes and tools that make multiple HTTP requests when
110
+ choosing an override.
111
+
104
112
  The token connector gains Connecta's generic REST tools over Notion's pinned
105
113
  API index (`notion_api_search`, `notion_api_details`, `notion_api_read`,
106
114
  `notion_api_write`), so every public operation is reachable with the bot's
@@ -119,8 +127,7 @@ duplication, team and skill search, attachment downloads, or page-to-skill
119
127
  conversion. An OAuth connector acts as the user, and has none of the generic
120
128
  REST tools or the `integration_*` projections and authoring helpers. Configure
121
129
  both ids when a deployment needs both; a failed call is never retried on the
122
- other. The [Notion skill](https://github.com/zackbart/connecta/blob/main/src/providers/notion/SKILL.md)
123
- owns the conventions for both.
130
+ other. Read the connector runtime guide with `connecta.skill("connector:<id>")` in `execute_code` for its usage conventions.
124
131
 
125
132
  ## Vercel
126
133
 
@@ -156,7 +163,7 @@ variable tools (`list_project_env_vars`, `upsert_project_env_var`,
156
163
  (the runtime stream, stopped after `waitMs` or `maxRows`), and
157
164
  `vercel_api_upload`.
158
165
 
159
- Value safety follows a reviewed table (`src/providers/vercel/value-safety.ts`).
166
+ Value safety follows a reviewed table bundled with the connector.
160
167
  Every operation whose response schema in the pinned spec may carry a credential
161
168
  or a stored secret has a verdict: refused (credential minting, Connect
162
169
  authorization, decrypted environment values, Global Config items and tokens,
@@ -186,9 +193,7 @@ The hosted connection now lists `filter_project_envs`, `get_project_env`,
186
193
  return decrypted values, so they are reviewed as writes, and a `readOnly`
187
194
  endpoint refuses them. Vercel admits only reviewed and approved MCP clients to its
188
195
  hosted server; if authorization is refused for that reason, the deployment
189
- needs Vercel's approval or a token connector. The
190
- [Vercel skill](https://github.com/zackbart/connecta/blob/main/src/providers/vercel/SKILL.md)
191
- owns the conventions for both.
196
+ needs Vercel's approval or a token connector. Read the connector runtime guide with `connecta.skill("connector:<id>")` in `execute_code` for its usage conventions.
192
197
 
193
198
  ## Cloudflare
194
199
 
@@ -254,9 +259,7 @@ Lost on each path. OAuth has no index validation, named tools, GraphQL tool,
254
259
  upload tool, or pin; Cloudflare's OAuth grant is the only boundary. A key has
255
260
  no hosted `search` over Cloudflare's live OpenAPI document; the pinned index
256
261
  replaces it and `providers:check` reports when it falls behind. A Global API
257
- Key cannot reach R2, which accepts API tokens only. The
258
- [Cloudflare skill](https://github.com/zackbart/connecta/blob/main/src/providers/cloudflare/SKILL.md)
259
- owns the conventions for both.
262
+ Key cannot reach R2, which accepts API tokens only. Read the connector runtime guide with `connecta.skill("connector:<id>")` in `execute_code` for its usage conventions.
260
263
 
261
264
  ## Value safety on every key connector
262
265
 
@@ -289,12 +292,16 @@ one; read or rotate withheld secrets in the vendor's dashboard.
289
292
  Failures of credential-bearing operations keep Stripe's type, code, and
290
293
  param but not its message. Create client-side sessions and links from your
291
294
  own server code.
292
- - **Notion** results withhold the signature, credential, and security-token
293
- parameters of pre-signed file URLs, so a returned file URL no longer
294
- downloads; credential-named fields, including database properties named
295
- like credentials, read as `"[redacted]"`. Failures of the OAuth, file
296
- upload, bot user, and agent operations keep Notion's code but not its
297
- message.
295
+ - **Notion** REST results and error text withhold the whole query and
296
+ fragment of canonical Notion file links, including links embedded in
297
+ Markdown and async task results. External proxy URLs keep their bytes;
298
+ AWS and Google signing-parameter values are withheld at any host. Writes
299
+ carrying the plain or encoded `[redacted]` placeholder in a Notion file
300
+ link's own query or fragment are refused before dispatch. See the
301
+ connector runtime guide through `connecta.skill("connector:<id>")` in `execute_code` for recognized hosts and scanning limits. Credential-named fields,
302
+ including database properties named like credentials, read as
303
+ `"[redacted]"`. Failures of the OAuth, file upload, bot user, and agent
304
+ operations keep Notion's code but not its message.
298
305
  - **Vercel** replaces environment values with `"[redacted]"` instead of
299
306
  dropping the field, and withholds Vercel's error text for every reviewed
300
307
  operation and every operation whose request takes a credential-named
@@ -306,3 +313,10 @@ one; read or rotate withheld secrets in the vendor's dashboard.
306
313
  destinations to their scheme and host. A credential-named field now loses
307
314
  its whole value, not only its strings (a `cookies` list, a variable named
308
315
  `API_KEY`).
316
+
317
+ ## Contributor reference
318
+
319
+ [Decision 0005](https://github.com/zackbart/connecta/blob/main/decisions/0005-auth-selects-implementation.md)
320
+ explains why auth selects the implementation. Maintained provider source
321
+ skills and review tables live under `src/providers/` in the repository;
322
+ `src/providers/vercel/value-safety.ts` owns Vercel's reviewed table.
@@ -7,8 +7,8 @@ version changes; [integrating services](./integrating.md) covers new connectors.
7
7
 
8
8
  This is the Phase 1 item 5d migration input for [Phase 5 release #709](https://github.com/zackbart/connecta/issues/709).
9
9
  Deploy it with the consolidated 0.29 release, not while the rework is in progress.
10
- BePresent and One&Many use Notion and Cloudflare; their actual grants and
11
- connector ids must be checked in deployment configuration before that release.
10
+ Deployments that use Notion or Cloudflare must check their actual grants and
11
+ connector ids in deployment configuration before that release.
12
12
  The provider audit establishes available auth modes, not production OAuth grants.
13
13
 
14
14
  Cloudflare's part of this guide is superseded: `cloudflare()` now selects its
@@ -2,6 +2,12 @@
2
2
 
3
3
  This repository is deployment configuration, not a copy of Connecta itself.
4
4
 
5
+ Start every operator task at
6
+ `node_modules/@zackbart/connecta/documentation/README.md#operator-tasks`.
7
+ That installed index routes adding/removing connectors, organizing agent pools,
8
+ adding people, connecting shared credentials, issuing/rotating/revoking agent
9
+ tokens, upgrading, and diagnosis with `connecta doctor` to their procedures.
10
+
5
11
  - Edit `src/connecta.config.ts` for connectors, authentication, storage, public
6
12
  URL, and optional modules. `src/index.ts` only starts it. Set plain vars
7
13
  `CONNECTA_ACTIVITY="on"` for preserved activity.
@@ -9,8 +15,8 @@ This repository is deployment configuration, not a copy of Connecta itself.
9
15
  always installed over the same `CONNECTA_DB` for machines. Cloudflare Access
10
16
  admits the request before the Worker runs; a service identity alone is
11
17
  refused inside connecta. Do not add JWT parsing or another identity gate.
12
- Grant `identity.accessTokenManagement` explicitly to interactive operators;
13
- this example permits every signed-in human to manage connectors and tokens.
18
+ Signed-in maintainers alone manage shared credentials and agent tokens; pool
19
+ members connect only their own accounts on personal connectors in their pools.
14
20
  - Attach Access to the Worker itself, not only its hostname. Enable Managed
15
21
  OAuth on that Access application. CIMD is the spec's preferred client
16
22
  registration; enable DCR as the fallback Access currently requires.
@@ -57,21 +63,43 @@ This repository is deployment configuration, not a copy of Connecta itself.
57
63
  Do not copy or modify Connecta package internals here.
58
64
  - Prefer `api()` when the agent must see an exact reviewed capability set;
59
65
  `remoteMcp()` follows the downstream server's evolving tool catalog.
60
- - Unattended clients and `connecta doctor` need Access service credentials
61
- plus a stored `cta_` bearer. Set `CF_ACCESS_CLIENT_ID`,
62
- `CF_ACCESS_CLIENT_SECRET`, and `CONNECTA_TOKEN` together for doctor.
63
- Bootstrap tokens through the Access-authenticated operator page or trusted
64
- `AccessTokenManager.create` tooling against this same D1 storage, as README
66
+ - Agents (unattended clients) use `<PUBLIC_URL>/agent/<pool>` with a stored
67
+ `cta_` bearer as their only credential. That needs one self-hosted Access
68
+ application per hostname for `<host>/agent/*` with a single Bypass policy,
69
+ beside the Worker-level application (README.md § "Let agents through
70
+ Access"). Never bypass any other path. `/mcp` and `/mcp/<pool>` refuse `cta_`.
71
+ `connecta doctor` also reads `/health` behind Access: set
72
+ `CF_ACCESS_CLIENT_ID`, `CF_ACCESS_CLIENT_SECRET`, and `CONNECTA_TOKEN`
73
+ together, and pass `--pool ci`, a pool that lists the agent.
74
+ Machine clients are the `agents` declared in `src/connecta.config.ts`. Issue
75
+ or rotate a token through the operator Tokens page as a signed-in maintainer or trusted
76
+ `AccessTokenManager.rotate` tooling against this same D1 storage, as README
65
77
  "Machine tokens" describes. Never configure a static bearer or open startup.
66
78
  UI, vault, and activity use explicit optional imports;
67
- auth changes require code-derived shared or personal management permissions.
79
+ auth changes require a maintainer, or a pool member for their own personal account.
80
+ - Organize agent pools by connecta's agent pool layout:
81
+ `node_modules/@zackbart/connecta/documentation/auth.md#organizing-an-agents-pools`
82
+ ([online](https://github.com/zackbart/connecta/blob/main/documentation/auth.md#organizing-an-agents-pools)).
83
+ Each agent keeps one Connecta connection whose harness, never the model,
84
+ picks the pool per turn. Tiers are nested pools of shared connectors with
85
+ no `owner`, each spreading the tool list below. Recommended names are
86
+ `<agent>` for the lowest and `<agent>-<tier>` above it; any named pool,
87
+ including an existing `team` or `leadership` pool, may be a tier if all its
88
+ human members are cleared for it. A chat gets the most capable tier everyone
89
+ present is cleared for, and the lowest when anyone present is unknown.
90
+ `<agent>-<user>` (that user's highest tier plus their own connector ids,
91
+ `owner` set to their Workspace address) is used only in a one-on-one with
92
+ that user. List only cleared people in a tier, and never another person in
93
+ a `<agent>-<user>` pool: its per-user connectors act as that user for every
94
+ member. Never add a second Connecta connection or mount downstream tools
95
+ directly.
68
96
  - Brand the operator UI in `operatorUi({ branding })`: product and owner names,
69
97
  description, favicon, and `theme` (`accent`, `radius`, `fontFamily`,
70
98
  `monoFamily`, `colorScheme`). Ask the deployment's owner for their brand
71
99
  rather than leaving the default. README "UI and encrypted credentials" shows
72
100
  the shape.
73
101
  - After configuration changes, typecheck: inside the connecta repository that
74
- is `npm run check:examples`; a copied deployment runs its own `tsc --noEmit`.
102
+ is `pnpm run check:examples`; a copied deployment runs its own `tsc --noEmit`.
75
103
  After deployment, connect both Claude and ChatGPT to `<PUBLIC_URL>/mcp` and
76
104
  complete their browser authorization flows before calling setup complete.
77
105
 
@@ -11,7 +11,8 @@ A deployable Worker that aggregates a downstream remote MCP and an in-code HTTP
11
11
  API connector, guarded by Cloudflare Access. It uses three Cloudflare resources
12
12
  and nothing else: one D1 database (`CONNECTA_DB`) for every piece of state, the
13
13
  Worker Loader binding (`LOADER`) behind the six-tool surface, which requires
14
- the Workers Paid plan, and one secret (`CREDENTIAL_ENCRYPTION_KEY`).
14
+ the Workers Paid plan, and two secrets: `DOWNSTREAM_TOKEN` (the example remote
15
+ MCP connector's token) and `CREDENTIAL_ENCRYPTION_KEY` (the credential vault).
15
16
 
16
17
  This is also the **starting template for a deployment**: a real deployment
17
18
  should be its own repository that pins an exact `@zackbart/connecta` version and
@@ -46,9 +47,11 @@ This example has no `package.json` of its own — it resolves the installed
46
47
  package from the repository root.
47
48
 
48
49
  ```sh
49
- npm install # from the package root
50
+ pnpm install # from the repository root
50
51
 
51
52
  wrangler d1 create connecta # paste the id into wrangler.jsonc
53
+ # In wrangler.jsonc also set PUBLIC_URL to this Worker's URL and
54
+ # CONNECTA_MAINTAINERS to your Access user ids (user_uuid, else email).
52
55
 
53
56
  cd examples/worker
54
57
  wrangler secret put DOWNSTREAM_TOKEN
@@ -81,10 +84,10 @@ attempt and add the narrowest matching entry rather than allowing its entire
81
84
  origin.
82
85
 
83
86
  Access then serves OAuth discovery and turns the client's opaque token into the
84
- trusted human `ctx.access` identity connecta reads. A cron job or CI client
85
- needs Access service headers to cross the edge and a stored `cta_` bearer to
86
- authenticate inside connecta. An Access service identity alone is refused.
87
- See [Machine tokens](#machine-tokens).
87
+ trusted human `ctx.access` identity connecta reads. A cron job or CI client is
88
+ an agent: it uses `/agent/<pool>` with a stored `cta_` bearer as its only
89
+ credential, once [the agent bypass](#let-agents-through-access) exists. An
90
+ Access service identity alone is refused. See [Machine tokens](#machine-tokens).
88
91
 
89
92
  Through the API, the relevant part of the application is:
90
93
 
@@ -112,8 +115,36 @@ owns client registration, redirect allowlists, and token lifetimes.
112
115
  working in a copied deployment. Do not remove the entries there when changing
113
116
  the Access policy or application.
114
117
 
118
+ ### Let agents through Access
119
+
120
+ Add one more self-hosted Access application for each hostname agents use (the
121
+ `workers.dev` hostname and each custom domain): domain set to that hostname,
122
+ path `agent/*`, and a single policy whose action is **Bypass** and whose include
123
+ rule is **Everyone**. Keep the Worker-level application; it still protects every
124
+ other path. Through the API:
125
+
126
+ ```json
127
+ {
128
+ "type": "self_hosted",
129
+ "name": "connecta agents",
130
+ "destinations": [{ "type": "public", "uri": "connecta.example.com/agent/*" }],
131
+ "policies": [{ "name": "agents bypass", "decision": "bypass", "include": [{ "everyone": {} }] }]
132
+ }
133
+ ```
134
+
135
+ It must be a hostname-and-path (`public`) destination: a second Worker
136
+ destination is refused because the Worker-level application owns it. Changes
137
+ took 15–19 seconds to settle when probed, occasionally longer, with transient
138
+ edge errors meanwhile. Do not rely on a fixed wait: verify that `/agent/ci`
139
+ without credentials returns connecta's 401 (`WWW-Authenticate: Bearer`) while
140
+ `/mcp` still returns Access's refusal, several times in a row. Connecta ignores Access
141
+ context on `/agent/*` and refuses every other spelling of that namespace before
142
+ auth. [Deploying Connecta](https://github.com/zackbart/connecta/blob/main/documentation/deploying.md#agent-endpoints-behind-cloudflare-access)
143
+ records the untested cases and the propagation details.
144
+
115
145
  The checked-in `access.dev` block gives `wrangler dev` a local operator
116
- identity. Remove the block to test the missing-Access refusal. It has no effect
146
+ identity; it requires Wrangler 4.123.0 or later, and earlier versions ignore it
147
+ with an unexpected-field warning. Remove the block to test the missing-Access refusal. It has no effect
117
148
  on a deployed Worker's production identity.
118
149
 
119
150
  ### Optional resource management with Alchemy
@@ -201,12 +232,34 @@ Access adoption or preserve production identity.
201
232
 
202
233
  ### Copied into its own repository
203
234
 
204
- The `npm install` above is the connecta repository's, which already has every
235
+ The `pnpm install` above is the connecta repository's, which already has every
205
236
  dependency this file imports. A copy with its own `package.json` installs
206
237
  Connecta and the separately installed `@cloudflare/codemode` peer:
207
238
 
208
239
  ```sh
209
- npm install @zackbart/connecta @cloudflare/codemode
240
+ npm install --save-exact @zackbart/connecta@<version>
241
+ npm install @cloudflare/codemode
242
+ npm install --save-dev wrangler @cloudflare/workers-types typescript @types/node
243
+ ```
244
+
245
+ A `tsconfig.json` like the following lets `tsc --noEmit` resolve `D1Database`,
246
+ `WorkerLoader`, and `ExecutionContext`. `wrangler` also serves
247
+ `scripts/copy-kv-to-d1.mjs`.
248
+
249
+ ```json
250
+ {
251
+ "compilerOptions": {
252
+ "target": "ES2022",
253
+ "module": "ES2022",
254
+ "moduleResolution": "Bundler",
255
+ "lib": ["ES2022"],
256
+ "types": ["@cloudflare/workers-types", "node"],
257
+ "strict": true,
258
+ "skipLibCheck": true,
259
+ "noEmit": true
260
+ },
261
+ "include": ["src/**/*.ts"]
262
+ }
210
263
  ```
211
264
 
212
265
  `@cloudflare/codemode` is the optional peer behind `execute_code`, declared in
@@ -219,57 +272,63 @@ discover the skew in production ([#376](https://github.com/zackbart/connecta/iss
219
272
  no Clerk import, secret, package, or fallback provider. Docker deployments keep
220
273
  the Clerk path in the Node template.
221
274
 
222
- Then point an MCP client at `<PUBLIC_URL>/mcp`. The example explicitly enables
223
- `ui: operatorUi()`; open `<PUBLIC_URL>/` for Connections and each connection's
224
- authentication controls. The page labels tool safety, offers fixed repair
275
+ Then list the maintainers' Access user ids (`user_uuid`, else email) in the
276
+ `CONNECTA_MAINTAINERS` var and point a maintainer's MCP client at
277
+ `<PUBLIC_URL>/mcp`. Other people use `<PUBLIC_URL>/mcp/<pool>`, and agents
278
+ `<PUBLIC_URL>/agent/<pool>`, for a pool that lists them in `members`. The example explicitly enables
279
+ `ui: operatorUi()`; open `<PUBLIC_URL>/` for endpoints, and each connector's Connection tab
280
+ (`/connectors/<id>`) for its authentication controls. The page labels tool safety, offers fixed repair
225
281
  prompts for classified failures, and shows client setup commands for endpoints
226
282
  the signed-in person may use. Those commands contain no token. There is no
227
- separate Credentials tab. The Access tokens page is available to permitted
228
- interactive operators.
283
+ separate Credentials tab. The Tokens page, listing one token slot per
284
+ declared agent, is available to signed-in maintainers.
229
285
 
230
286
  ## Select optional modules
231
287
 
232
288
  `cloudflareAccessAuth()` reads trusted identity after Access admits the Worker
233
- request. Humans may use their code-derived connector view and operator pages.
234
- Machines authenticate with `accessTokens(storage)` over the same D1 database;
235
- Access service headers alone never grant MCP access or interactive authority.
289
+ request. Maintainers use plain `/mcp` and the operator pages; people in a pool
290
+ use `/mcp/<pool>`, and agents in it `/agent/<pool>`, which serve exactly that
291
+ pool's tools. Agents authenticate with `accessTokens(storage)` over the same D1
292
+ database; human endpoints refuse their tokens, and Access service headers alone
293
+ never grant MCP access or interactive authority.
236
294
  Keep users, groups, and admission in Access. Connector visibility and management
237
295
  permissions belong in `src/connecta.config.ts`.
238
296
 
239
- `identity.connectorAccess` selects discoverable and callable connectors.
240
- `credentialAdministration` separately allows shared credential and OAuth
241
- management, while `personalConnection` allows a human to connect their own
242
- account. Both management permissions default to none. Grant the intended
243
- owner's shared permissions explicitly, and grant users personal permissions
244
- only for connectors configured with `authScope: "personal"`. Static headers
245
- remain deployment configuration. `activityAccess` governs global history reads.
246
- `accessTokenManagement` separately allows interactive operators to create, rename,
247
- and revoke machine tokens. This example grants that permission to every signed-in
248
- human, matching its connector-management permissions; narrow all three for a team.
297
+ `maintainers` and each pool's `members` select who reaches which endpoint and
298
+ who manages what. Signed-in maintainers alone administer shared credentials
299
+ and OAuth grants, issue, rotate, and revoke agent tokens, and read activity.
300
+ A pool member connects their own account only on connectors configured with
301
+ `authScope: "personal"` in their pools. Static headers remain deployment
302
+ configuration.
249
303
  See [inbound identity](../../documentation/auth.md#principals-visibility-and-operators).
250
304
 
251
305
  ### Machine tokens
252
306
 
253
- `accessTokens(storage)` is always installed over `CONNECTA_DB`, with no new
254
- binding. Empty token storage admits no machine client. A human authenticated
255
- through Access can still sign in and bootstrap the first token. Open
256
- `<PUBLIC_URL>/`, select Access tokens, and create a named token. Save the
257
- returned `cta_` secret privately; it is returned once. UI-issued tokens retain
258
- the issuing human's principal, while connector and pool rules are evaluated
259
- on every request. They never grant interactive operator authority.
260
-
261
- A machine sends all three headers:
307
+ Machine clients are agents declared by name in `agents` in
308
+ `src/connecta.config.ts`; this example declares `ci`. Each agent has one
309
+ `cta_` token held by `accessTokens(storage)` over `CONNECTA_DB`, with no new
310
+ binding. An agent with no issued token admits nothing. A maintainer (an Access
311
+ identity listed in `CONNECTA_MAINTAINERS`) signs in and issues the first token.
312
+ Open `<PUBLIC_URL>/`, select Tokens, and issue the agent's token. Save the
313
+ returned `cta_` secret privately; it is returned once. Rotating replaces it
314
+ immediately: the old secret stops on its next request. An agent has no human
315
+ principal, so personal connectors are absent for it; connector and pool rules
316
+ recognize it by `identity.agent` on every request. It never has interactive
317
+ operator authority. Removing a name from `agents` stops its token on the next
318
+ request.
319
+
320
+ An agent connects to `<PUBLIC_URL>/agent/<pool>` for a pool that lists it and
321
+ sends one header:
262
322
 
263
323
  ```text
264
- CF-Access-Client-Id: <access-service-client-id>
265
- CF-Access-Client-Secret: <access-service-client-secret>
266
324
  Authorization: Bearer <stored-cta-token>
267
325
  ```
268
326
 
269
- Permit the service credentials in the Worker-level Access application's policy.
270
- The service headers admit the request at the edge; connecta verifies the bearer
271
- against D1. Neither credential works alone for machine access. Doctor uses
272
- the same three credentials; see [Client authentication and activity](#client-authentication-and-activity).
327
+ [The agent bypass](#let-agents-through-access) lets the request past the edge;
328
+ connecta verifies the bearer against D1 and ignores any Access context or
329
+ headers there. `/mcp` and `/mcp/<pool>` refuse the token. Doctor also reads
330
+ `/health`, which stays behind Access, so it needs Access service credentials
331
+ too; see [Client authentication and activity](#client-authentication-and-activity).
273
332
 
274
333
  For programmatic bootstrap, trusted provisioning code that already holds this
275
334
  deployment's D1 binding can use the published API against that same storage:
@@ -278,16 +337,18 @@ deployment's D1 binding can use the published API against that same storage:
278
337
  import { AccessTokenManager } from "@zackbart/connecta/auth/access-tokens";
279
338
  import { d1Storage } from "@zackbart/connecta/d1";
280
339
 
281
- const manager = new AccessTokenManager(d1Storage(env.CONNECTA_DB));
282
- const { token } = await manager.create("ci-client", "trusted-provisioning");
340
+ const manager = new AccessTokenManager(d1Storage(env.CONNECTA_DB), { agents: ["ci"] });
341
+ const { token } = await manager.rotate("ci", "trusted-provisioning");
283
342
  // Deliver token once to the client's secret store, outside logs and source control.
284
343
  ```
285
344
 
286
- This creates a token without a human principal. The name is a label, never a
287
- grant. Provision only through trusted operator tooling; do not expose this
288
- code as a public route or mint a token at startup. Existing unrevoked `cta_`
289
- tokens survive a state migration into this same D1 namespace. Keep
290
- `accessTokens(storage)` configured and preserve the identity and pool rules.
345
+ Provision only through trusted operator tooling; do not expose this code as a
346
+ public route or rotate a token at startup. Tokens issued before agents survive a
347
+ state migration into this same D1 namespace but stop admitting until trusted
348
+ tooling binds each machine's token once with
349
+ `manager.adopt(tokenId, "ci")`, which keeps its secret working. Human-bound
350
+ tokens cannot be adopted; issue the agent a new token instead. Keep
351
+ `accessTokens(storage)` configured over the same storage.
291
352
 
292
353
  ### UI and encrypted credentials
293
354
 
@@ -323,11 +384,13 @@ does not create credentials or permissions by itself.
323
384
 
324
385
  ### Client authentication and activity
325
386
 
326
- Interactive MCP clients use Access Managed OAuth. Machine clients need both
327
- Access service credentials and a stored `cta_` bearer, provisioned as described
328
- in [Machine tokens](#machine-tokens). `accessTokens(storage)` is always enabled,
329
- and the example grants token management to every interactive Access operator.
330
- Keep token, connector, and pool grants in deployment code.
387
+ Interactive MCP clients use Access Managed OAuth. Agents send only a declared
388
+ agent's `cta_` bearer to `/agent/<pool>`, issued as described in
389
+ [Machine tokens](#machine-tokens). `accessTokens(storage)` is always
390
+ enabled, and every signed-in maintainer manages agent tokens.
391
+ Keep maintainers, pools, and their members in deployment code. An agent reaches
392
+ only `/agent/<pool>` for a pool that lists it (the example's `ci` pool lists
393
+ `agent:ci`); `/mcp` and `/mcp/<pool>` refuse its token.
331
394
 
332
395
  Activity uses `activityHistory({ store: d1ActivityStore(env.CONNECTA_DB) })`,
333
396
  with `activityHistory` from `@zackbart/connecta/activity` and `d1ActivityStore`
@@ -335,13 +398,15 @@ from `@zackbart/connecta/d1`. See [Activity history](#activity-history-optional)
335
398
  Omit the module and store wiring to record no history and show no Activity tab. Diagnostics remain independent; `logger: "silent"` suppresses
336
399
  them explicitly.
337
400
 
338
- Verify MCP health and the exact six tools with:
401
+ Verify MCP health and the exact six tools with the following. Doctor reads
402
+ `/health` through Access with the service credentials, then checks `/agent/ci`
403
+ with the `cta_` token alone:
339
404
 
340
405
  ```sh
341
406
  CF_ACCESS_CLIENT_ID='REPLACE_WITH_ACCESS_CLIENT_ID' \
342
407
  CF_ACCESS_CLIENT_SECRET='REPLACE_WITH_ACCESS_CLIENT_SECRET' \
343
408
  CONNECTA_TOKEN='cta_REPLACE_WITH_STORED_SECRET' \
344
- connecta doctor --url "$PUBLIC_URL"
409
+ npx connecta doctor --url https://connecta.example.workers.dev --pool ci
345
410
  ```
346
411
 
347
412
  Doctor reports the configured `DynamicWorkerExecutor`, not a presumed Node
@@ -400,7 +465,8 @@ CREATE TABLE IF NOT EXISTS connecta_kv (
400
465
  );
401
466
  CREATE INDEX IF NOT EXISTS connecta_kv_expiry ON connecta_kv (expires_at_ms);
402
467
  -- tool_call_activity: one row per completed call, keyset-paged on
403
- -- (occurred_at_ms, id); see src/storage/sql.ts in the package.
468
+ -- (occurred_at_ms, id); see
469
+ -- https://github.com/zackbart/connecta/blob/main/src/storage/sql.ts
404
470
  ```
405
471
 
406
472
  ### Upgrading from 0.28
@@ -430,7 +496,7 @@ CREATE INDEX IF NOT EXISTS connecta_kv_expiry ON connecta_kv (expires_at_ms);
430
496
  - **Workers KV** (`CONNECTA_KV` and `cloudflare-kv.ts`). Workers KV is no longer
431
497
  supported: `createConnecta` refuses storage without `compareAndSet` at boot.
432
498
  Copy its state into D1 once, as described below, so OAuth grants, vault
433
- credentials, and `cta_` access tokens keep working.
499
+ credentials, and `cta_` access token records carry over.
434
500
 
435
501
  #### Copying Workers KV state into D1
436
502
 
@@ -462,7 +528,7 @@ then `wrangler login` and check the prepared deployment with a dry run.
462
528
  1. Back up KV with a bulk export including values and expiration metadata.
463
529
  Export D1 with `wrangler d1 export <db> --remote --output before-cutover.sql`.
464
530
  Store backups securely and preserve the exact credential encryption key,
465
- including One&Many's `CONNECTA_CREDENTIALS_KEY`.
531
+ whatever secret name the deployment stores it under.
466
532
  2. Block traffic using maintenance mode or turn the route off. Stop cron,
467
533
  queue consumers, scheduled jobs, and every other KV/D1 writer. Drain all
468
534
  in-flight requests, including OAuth callbacks and token refreshes.
@@ -488,8 +554,8 @@ then `wrangler login` and check the prepared deployment with a dry run.
488
554
  invalid entry or mismatch. Keep traffic and writers stopped until these
489
555
  counts are resolved. A verification pass does not rewrite copied entries.
490
556
  5. Deploy the D1 configuration with `wrangler deploy` while maintenance remains.
491
- Run `connecta doctor` using Access service credentials plus a stored `cta_`
492
- bearer through `CONNECTA_TOKEN`. Verify an existing
557
+ Run `connecta doctor --pool <pool>` using Access service credentials plus a
558
+ stored `cta_` bearer through `CONNECTA_TOKEN`. Verify an existing
493
559
  OAuth connector without re-consent, an existing `cta_` token, a connector
494
560
  using a vault credential, and prior activity in the Activity view. Run
495
561
  these controlled checks with other traffic and background writers blocked.