@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
@@ -4,8 +4,9 @@
4
4
  * One MCP endpoint aggregating a downstream remote MCP and an HTTP API, guarded
5
5
  * by Cloudflare Access. It needs three resources and nothing else: one D1
6
6
  * database (CONNECTA_DB) holding every piece of state, the Worker Loader
7
- * binding (LOADER) behind execute_code, and one secret
8
- * (CREDENTIAL_ENCRYPTION_KEY) sealing credentials in that database. Access
7
+ * binding (LOADER) behind execute_code, and two secrets: DOWNSTREAM_TOKEN for
8
+ * the example Notion connector and CREDENTIAL_ENCRYPTION_KEY sealing
9
+ * credentials in that database. Access
9
10
  * admits the request before this Worker runs and supplies trusted human
10
11
  * identity through ctx.access. Machines use stored cta_ tokens inside connecta.
11
12
  *
@@ -16,7 +17,7 @@
16
17
  *
17
18
  * Setup (this example has no package.json of its own — it self-references the
18
19
  * installed `@zackbart/connecta` package):
19
- * 1. `npm install` in the connecta package root (../../ from here) so the
20
+ * 1. `pnpm install` in the connecta repository root (../../ from here) so the
20
21
  * package import and wrangler resolve. A copy in its own repository
21
22
  * installs `@zackbart/connecta @cloudflare/codemode` instead. Codemode is
22
23
  * an optional peer.
@@ -31,9 +32,15 @@
31
32
  * Claude's https://claude.ai/api/mcp/auth_callback plus ChatGPT's
32
33
  * https://chatgpt.com/connector_platform_oauth_redirect and
33
34
  * https://chatgpt.com/connector/oauth/* forms (see ../AGENTS.md).
35
+ * Then add one self-hosted Access application per hostname for
36
+ * `<host>/agent/*` with a single Bypass policy (Everyone), so agents send
37
+ * only their cta_ token (README.md § "Let agents through Access").
34
38
  * 5. Use the Workers Paid plan required by the `worker_loaders` binding.
35
- * 6. `wrangler deploy` from this folder (examples/worker), where wrangler.jsonc
36
- * lives. Point your MCP client at `<PUBLIC_URL>/mcp`.
39
+ * 6. List the maintainers' Access user ids (the identity's user_uuid, else
40
+ * its email) in the CONNECTA_MAINTAINERS var, comma-separated.
41
+ * 7. `wrangler deploy` from this folder (examples/worker), where wrangler.jsonc
42
+ * lives. Point a maintainer's MCP client at `<PUBLIC_URL>/mcp`, and an
43
+ * agent's at `<PUBLIC_URL>/agent/ci`.
37
44
  */
38
45
  import { api, defineConfig, remoteMcp } from "@zackbart/connecta";
39
46
  import { activityHistory } from "@zackbart/connecta/activity";
@@ -55,6 +62,8 @@ export interface Env {
55
62
  CREDENTIAL_ENCRYPTION_KEY?: string;
56
63
  DOWNSTREAM_TOKEN: string;
57
64
  PUBLIC_URL: string;
65
+ /** Comma-separated Access user ids (user_uuid, else email) of the maintainers. */
66
+ CONNECTA_MAINTAINERS: string;
58
67
  /** "on" switches on activity history, in CONNECTA_DB. */
59
68
  CONNECTA_ACTIVITY?: string;
60
69
  /**
@@ -77,26 +86,34 @@ export default defineConfig((env: Env) => {
77
86
  // Required adapter owns each run's handles; direct upstream construction throws.
78
87
  executor: workerExecutor({ loader: env.LOADER }),
79
88
  // Access owns edge admission and proves human identity for MCP and the
80
- // operator pages. Machines need Access service headers at the edge plus a
81
- // stored cta_ bearer inside connecta; a service identity alone is refused.
89
+ // operator pages. Agents use /agent/<pool>, which Access bypasses and
90
+ // connecta authenticates with the cta_ bearer alone; a service identity
91
+ // alone is refused everywhere.
82
92
  auth: [cloudflareAccessAuth()],
83
- // Always installed over the same D1 storage. Empty storage admits no
84
- // machine client; an Access-authenticated operator can provision tokens.
93
+ // Machine clients are agents declared here by name, each with one cta_
94
+ // token. A signed-in maintainer issues, rotates, and revokes it
95
+ // in the operator UI; removing a name stops its token on the next request.
96
+ agents: ["ci"],
97
+ // Always installed over the same D1 storage. An agent with no issued token
98
+ // admits nothing.
85
99
  accessTokens: accessTokens(storage),
86
- // Code-owned roster. Access proves the identity; connecta derives
87
- // connector visibility and management permissions from the stable id it
88
- // supplies. Add `connectorAccess` and `activityAccess` to split members
89
- // from operators; here every signed-in human may manage every connector
90
- // and access token. Machine tokens never receive interactive authority.
91
- identity: {
92
- credentialAdministration: () => "all",
93
- personalConnection: () => "all",
94
- accessTokenManagement: () => true,
95
- },
96
- // Connectors that declare a `credential` slot become editable by every
97
- // signed-in human who can see that connector, encrypted with this key
98
- // before anything reaches D1. A saved replacement takes effect on the
99
- // next call; credentials fail at use.
100
+ // Code-owned roster. Access proves the identity; maintainers alone use
101
+ // plain /mcp, which serves every connector, and the operator UI, where they
102
+ // administer shared credentials, manage agent tokens, and read activity.
103
+ // Startup refuses an empty list.
104
+ maintainers: env.CONNECTA_MAINTAINERS.split(",")
105
+ .map((id) => id.trim())
106
+ .filter(Boolean),
107
+ // Everyone else uses a pool that lists them, which serves exactly its
108
+ // tools: people at /mcp/<name>, agents at /agent/<name>. Members are
109
+ // Access user ids or "agent:<name>". The starter pool is the agent's base
110
+ // pool (shared connectors, no owner), named after the agent as the agent
111
+ // pool layout requires.
112
+ pools: { ci: { tools: ["echo"], members: ["agent:ci"] } },
113
+ // Connectors that declare a `credential` slot become editable by a
114
+ // maintainer (shared) or by the member it belongs to (personal),
115
+ // encrypted with this key before anything reaches D1. A saved replacement
116
+ // takes effect on the next call; credentials fail at use.
100
117
  vault: env.CREDENTIAL_ENCRYPTION_KEY ? encryptedCredentialVault(storage, env.CREDENTIAL_ENCRYPTION_KEY) : undefined,
101
118
  // Payload-free activity at /ui/api/activity in CONNECTA_DB. The shared
102
119
  // D1 mapping persists the build version and validated client facts, and
@@ -13,9 +13,12 @@
13
13
  "vars": {
14
14
  // "CONNECTA_ACTIVITY": "on",
15
15
  "PUBLIC_URL": "https://connecta.example.workers.dev",
16
+ // Comma-separated Access user ids (user_uuid, else email) of the
17
+ // maintainers. This one matches the local dev identity below.
18
+ "CONNECTA_MAINTAINERS": "operator@example.com",
16
19
  },
17
20
 
18
- // Local-only Access identity. Remove this block to exercise the
21
+ // Local-only Access identity (Wrangler 4.123.0 or later). Remove this block to exercise the
19
22
  // unauthenticated path in `wrangler dev`; production identity comes from the
20
23
  // Worker-level Access application, not from this file.
21
24
  "access": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zackbart/connecta",
3
- "version": "0.32.0",
3
+ "version": "0.34.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 six meta-tools.",
@@ -8,6 +8,7 @@
8
8
  "engines": {
9
9
  "node": ">=22.13.0"
10
10
  },
11
+ "packageManager": "pnpm@12.4.2",
11
12
  "publishConfig": {
12
13
  "access": "public"
13
14
  },
@@ -187,20 +188,20 @@
187
188
  "build:operator-ui": "node scripts/build-operator-ui.mjs",
188
189
  "check:operator-ui": "node scripts/build-operator-ui.mjs --check",
189
190
  "check:clerk-sdk": "node scripts/build-clerk-sdk.mjs --check",
190
- "build": "npm run providers:generate && npm run clean && tsc -p tsconfig.build.json && node scripts/prune-declarations.mjs",
191
+ "build": "pnpm run providers:generate && pnpm run clean && tsc -p tsconfig.build.json && node scripts/prune-declarations.mjs",
191
192
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.operator-ui.json --noEmit",
192
193
  "test": "vitest run",
193
194
  "test:browser:install": "playwright install chromium",
194
195
  "test:browser": "playwright test",
195
196
  "test:browser:snapshots": "node scripts/update-operator-snapshots.mjs",
196
- "pretest:browser:snapshots": "npm run build:operator-ui",
197
+ "pretest:browser:snapshots": "pnpm run build:operator-ui",
197
198
  "test:node": "vitest run --project node",
198
199
  "test:workers": "vitest run --project workers",
199
200
  "check:docs": "node scripts/check-doc-links.mjs",
200
201
  "check:lint": "oxlint -D correctness --report-unused-disable-directives .",
201
202
  "check:unused": "knip --include dependencies,unlisted,exports,nsExports,types,nsTypes",
202
203
  "check:examples": "tsc -p tsconfig.template.node.json && tsc -p tsconfig.examples.worker.json",
203
- "check:security": "npm audit --omit=dev --audit-level=moderate",
204
+ "check:security": "pnpm audit --prod --audit-level moderate",
204
205
  "check:package": "node scripts/check-package.mjs",
205
206
  "check:declarations": "node scripts/check-declarations.mjs --dist .",
206
207
  "check:bundle": "node scripts/bundle-size.mjs",
@@ -211,23 +212,23 @@
211
212
  "eval:smoke": "tsx eval/run-smoke.ts",
212
213
  "eval:perf": "tsx eval/run-perf.ts",
213
214
  "eval:report": "tsx eval/report.ts",
214
- "check:core": "npm run format:check && npm run check:providers-generated && npm run check:docs && npm run check:changes && npm run check:operator-ui && npm run check:clerk-sdk && npm run check:lint && npm run check:unused && npm run typecheck && npm run test && npm run build && npm run check:declarations && npm run check:bundle && npm run check:examples",
215
+ "check:core": "pnpm run format:check && pnpm run check:providers-generated && pnpm run check:docs && pnpm run check:changes && pnpm run check:operator-ui && pnpm run check:clerk-sdk && pnpm run check:lint && pnpm run check:unused && pnpm run typecheck && pnpm run test && pnpm run build && pnpm run check:declarations && pnpm run check:bundle && pnpm run check:examples",
215
216
  "check:fast": "node scripts/check-fast.mjs",
216
- "check": "npm run check:core && npm run test:browser",
217
- "release:check": "npm run check && npm run check:security && npm run check:package",
218
- "prepack": "npm run check",
217
+ "check": "pnpm run check:core && pnpm run test:browser",
218
+ "release:check": "pnpm run check && pnpm run check:security && pnpm run check:package",
219
+ "prepack": "pnpm run check",
219
220
  "check:changes": "node scripts/changelog.mjs --check && node scripts/check-node-suites.mjs && node --test scripts/changelog.test.mjs",
220
221
  "changelog:assemble": "node scripts/changelog.mjs",
221
222
  "providers:generate": "tsx scripts/generate-providers.mjs",
222
223
  "check:providers-generated": "tsx scripts/generate-providers.mjs --check",
223
- "prebuild": "node scripts/generate-version.mjs && npm run build:operator-ui",
224
- "pretest": "node scripts/generate-version.mjs && npm run build:operator-ui",
225
- "pretest:node": "node scripts/generate-version.mjs && npm run build:operator-ui",
226
- "pretest:workers": "node scripts/generate-version.mjs && npm run build:operator-ui",
227
- "pretest:browser": "npm run build:operator-ui",
228
- "pretypecheck": "node scripts/generate-version.mjs && npm run build:operator-ui",
229
- "precheck:bundle": "npm run build:operator-ui",
230
- "precheck:fast": "npm run check:operator-ui",
224
+ "prebuild": "node scripts/generate-version.mjs && pnpm run build:operator-ui",
225
+ "pretest": "node scripts/generate-version.mjs && pnpm run build:operator-ui",
226
+ "pretest:node": "node scripts/generate-version.mjs && pnpm run build:operator-ui",
227
+ "pretest:workers": "node scripts/generate-version.mjs && pnpm run build:operator-ui",
228
+ "pretest:browser": "pnpm run build:operator-ui",
229
+ "pretypecheck": "node scripts/generate-version.mjs && pnpm run build:operator-ui",
230
+ "precheck:bundle": "pnpm run build:operator-ui",
231
+ "precheck:fast": "pnpm run check:operator-ui",
231
232
  "eval:regrade": "tsx eval/regrade.ts",
232
233
  "eval:compare": "tsx eval/compare.ts",
233
234
  "format": "oxfmt --write .",
@@ -263,7 +264,8 @@
263
264
  "@cloudflare/codemode": "^0.5.0",
264
265
  "@cloudflare/vitest-pool-workers": "^0.18.8",
265
266
  "@cloudflare/workers-types": "^4.20250101.0 || ^5.0.0",
266
- "@fontsource-variable/inter": "^5.3.0",
267
+ "@fontsource-variable/hanken-grotesk": "^5.3.0",
268
+ "@fontsource-variable/jetbrains-mono": "^5.3.0",
267
269
  "@playwright/test": "^1.62.0",
268
270
  "@radix-ui/react-dialog": "^1.2.0",
269
271
  "@radix-ui/react-slot": "^1.4.0",
@@ -272,7 +274,6 @@
272
274
  "@tailwindcss/oxide": "^4.3.3",
273
275
  "@tanstack/react-query": "^5.104.1",
274
276
  "@tanstack/react-router": "^1.170.41",
275
- "@tanstack/react-table": "^8.21.3",
276
277
  "@types/node": "^22.0.0",
277
278
  "@types/react": "^19.3.0",
278
279
  "@types/react-dom": "^19.3.0",
@@ -26,6 +26,9 @@ CONNECTA_DATABASE=
26
26
  # may write a credential; machine access tokens grant no interactive authority.
27
27
  CLERK_PUBLISHABLE_KEY=
28
28
  CLERK_SECRET_KEY=
29
+ # Required with Clerk: comma-separated Clerk user ids of the maintainers, who
30
+ # alone use plain /mcp and the operator UI.
31
+ CONNECTA_MAINTAINERS=
29
32
 
30
33
  # Credential vault. Base64-encoded 32-byte AES key encrypting the values an
31
34
  # operator saves inside a connection on /. Generate one with:
@@ -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.
7
13
  - Keep `executor: quickJsExecutor()` for the prescribed six-tool code-first
@@ -19,15 +25,36 @@ This repository is deployment configuration, not a copy of Connecta itself.
19
25
  - Prefer `api()` when the agent must see an exact reviewed capability surface;
20
26
  `remoteMcp()` follows the downstream server's evolving tool catalog.
21
27
  - Optional modules (Clerk sign-in, credential vault, activity history) are type-checked code in `src/connecta.config.ts`, switched on by
22
- their environment variables. Follow README "Select optional modules". Auth management requires explicit
23
- `credentialAdministration` or `personalConnection` permissions; visibility
24
- alone never grants it. Clerk supplies human identity; `cta_` access tokens cover machine clients.
28
+ their environment variables. Follow README "Select optional modules". Shared auth management
29
+ requires a signed-in maintainer, and a pool member manages only their own
30
+ account on a personal connector in their pools; visibility alone never grants it. Clerk supplies human identity; machine clients are the
31
+ `agents` declared by name in `src/connecta.config.ts`, each with one `cta_` token.
25
32
  Keep `accessTokens(storage)` installed even before the first token exists.
26
- Bootstrap machines with `npm run provision-token -- "machine-name"` against
27
- the same database the server uses. `CONNECTA_TOKEN` is only for clients and
28
- doctor; never add a configured static bearer or an open startup mode.
33
+ Issue or rotate an agent's token with `npm run --silent rotate-token -- <agent>`
34
+ against the same configuration and database the server uses; bind a token
35
+ from before agents once with `npm run adopt-token -- <token-id> <agent>`.
36
+ Plain `/mcp` serves `maintainers` only; other people reach `/mcp/<pool>` and
37
+ agents `/agent/<pool>` for a pool listing them in `members` (`npm run doctor`
38
+ checks `/agent/local-machine`). Human endpoints refuse `cta_` tokens. `CONNECTA_TOKEN` is only for clients and doctor; never add a configured
39
+ static bearer or an open startup mode.
29
40
  All state, activity included, lives in the one SQLite file `CONNECTA_DATABASE`
30
41
  names, through `@zackbart/connecta/sqlite`.
42
+ - Organize agent pools by connecta's agent pool layout:
43
+ `node_modules/@zackbart/connecta/documentation/auth.md#organizing-an-agents-pools`
44
+ ([online](https://github.com/zackbart/connecta/blob/main/documentation/auth.md#organizing-an-agents-pools)).
45
+ Each agent keeps one Connecta connection whose harness, never the model,
46
+ picks the pool per turn. Tiers are nested pools of shared connectors with
47
+ no `owner`, each spreading the tool list below. Recommended names are
48
+ `<agent>` for the lowest and `<agent>-<tier>` above it; any named pool,
49
+ including an existing `team` or `leadership` pool, may be a tier if all its
50
+ human members are cleared for it. A chat gets the most capable tier everyone
51
+ present is cleared for, and the lowest when anyone present is unknown.
52
+ `<agent>-<user>` (that user's highest tier plus their own connector ids,
53
+ `owner` set to their Workspace address) is used only in a one-on-one with
54
+ that user. List only cleared people in a tier, and never another person in
55
+ a `<agent>-<user>` pool: its per-user connectors act as that user for every
56
+ member. Never add a second Connecta connection or mount downstream tools
57
+ directly.
31
58
  - Brand the operator UI in `operatorUi({ branding })`: product and owner names,
32
59
  description, favicon, and `theme` (`accent`, `radius`, `fontFamily`,
33
60
  `monoFamily`, `colorScheme`). Ask the deployment's owner for their brand
@@ -2,8 +2,8 @@
2
2
  # image installs @zackbart/connecta from the registry exactly like any other
3
3
  # consumer, so nothing here depends on the Connecta repository.
4
4
  #
5
- # Provision a stored machine token on the volume first; README.md gives the
6
- # exact build, provisioning, and startup commands. Empty storage is fail-closed.
5
+ # Issue a declared agent's token on the volume first; README.md gives the
6
+ # exact build, rotation, and startup commands. Empty storage is fail-closed.
7
7
  FROM node:22-slim
8
8
 
9
9
  WORKDIR /app
@@ -8,11 +8,12 @@ maintenance. This README and [local agent instructions](./AGENTS.md) own the
8
8
  runnable steps for a copied Node deployment.
9
9
 
10
10
  This is the prescribed Node deployment. It runs locally from source and as
11
- a container. Install, provision a machine token, then start it:
11
+ a container. Install, issue the declared `local-machine` agent's token, then
12
+ start it:
12
13
 
13
14
  ```sh
14
15
  npm install
15
- npm run --silent provision-token -- "local-machine"
16
+ npm run --silent rotate-token -- local-machine
16
17
  # Save the returned cta_ token privately. It is shown only once.
17
18
  npm start
18
19
  ```
@@ -24,39 +25,59 @@ warning, and npm 12 does not block the script. A later esbuild version needs a
24
25
  new explicit review and approval; do not replace the pinned entry with a broad
25
26
  package-name approval.
26
27
 
27
- Point an MCP client at `http://localhost:8787/mcp` with
28
- `Authorization: Bearer <returned-cta-token>`. `CONNECTA_TOKEN` is a client and
28
+ Point an agent's MCP client at `http://localhost:8787/agent/local-machine`, the agent
29
+ endpoint of the pool that lists `agent:local-machine`, with
30
+ `Authorization: Bearer <returned-cta-token>`. Agents use only `/agent/<pool>`:
31
+ `/mcp` and `/mcp/<pool>` are for people and refuse `cta_` tokens, and plain
32
+ `/mcp` serves only the `maintainers`. `CONNECTA_TOKEN` is a client and
29
33
  doctor variable only. Setting it never configures server authentication.
30
34
 
31
- `accessTokens(storage)` is always installed. With no stored tokens and no
32
- Clerk keys, the server can start and answer `/health`, but every MCP request
33
- is refused. There is no open startup or configured static bearer fallback.
35
+ `accessTokens(storage)` is always installed. With no issued agent token and
36
+ no Clerk keys, the server can start and answer `/health`, but every MCP
37
+ request is refused. There is no open startup or configured static bearer
38
+ fallback.
34
39
 
35
- ## Provision machine tokens
40
+ ## Agent tokens
36
41
 
37
- Run `npm run --silent provision-token -- "machine-name"` from this project
38
- on a trusted machine. `src/provision-token.ts` imports the installed package's
39
- `AccessTokenManager`, then calls `manager.create(name, "local-provisioning")`
40
- on `sqliteStorage(openSqlite(process.env.CONNECTA_DATABASE || "./.connecta.sqlite"))`.
41
- The server uses that same database path. If you override it, use the same
42
- `CONNECTA_DATABASE` for provisioning and startup:
42
+ Machine clients are agents, declared by name in `agents` in
43
+ `src/connecta.config.ts`; the template declares `local-machine`. Each agent
44
+ has one token. Run `npm run --silent rotate-token -- <agent>` from this
45
+ project on a trusted machine to issue it, or to rotate it: the previous secret
46
+ stops working on its next request. `src/rotate-token.ts` loads the same
47
+ configuration the server does, so it uses the same database and refuses an
48
+ agent the configuration does not declare. If you override the database path,
49
+ use the same `CONNECTA_DATABASE` for rotation and startup:
43
50
 
44
51
  ```sh
45
52
  export CONNECTA_DATABASE=/absolute/path/connecta.sqlite
46
- npm run --silent provision-token -- "build-agent"
53
+ npm run --silent rotate-token -- local-machine
47
54
  npm start
48
55
  ```
49
56
 
50
57
  The local commands read the process environment; they do not load `.env`.
58
+ To run them with the values in `.env`, export it into the shell first:
59
+ `set -a; . ./.env; set +a`.
51
60
  Compose reads `.env` for the container commands below. Keep the returned
52
- secret in a client secret store and out of source control and logs. Each run
53
- creates a new token; it cannot recover an old secret. Only the hash persists.
54
- The token is unbound to a human principal and has no interactive management
55
- authority. Its name is a label, and deployment identity and pool rules decide
56
- its access on every request. To revoke it locally, use `manager.list()` to
57
- find its id, then `manager.revoke(id, "local-provisioning")` against that same
58
- storage. An explicitly permitted Clerk operator can also manage tokens.
59
- Never expose this provisioning script as an HTTP route or run it at startup.
61
+ secret in a client secret store and out of source control and logs. A
62
+ rotation cannot recover an old secret; only the hash persists. An agent has no
63
+ human principal, no personal connectors, and no interactive management
64
+ authority; deployment identity and pool rules decide its access on every
65
+ request through `identity.agent`. To stop an agent, remove its name from
66
+ `agents` and restart, or revoke its token with
67
+ `new AccessTokenManager(storage, { agents }).revoke(name)` against the same
68
+ storage. An explicitly permitted Clerk operator can also issue, rotate, and
69
+ revoke from the operator UI. Never expose these scripts as an HTTP route or
70
+ run them at startup.
71
+
72
+ ### Tokens issued before agents
73
+
74
+ Tokens from earlier releases stay in storage but stop admitting until they are
75
+ bound to an agent. Declare the agent, then run `npm run adopt-token` with no
76
+ arguments to list the machine tokens that can be bound, and
77
+ `npm run adopt-token -- <token-id> <agent>` to bind one, so that machine's
78
+ existing secret keeps working. A token binds once, and only to an agent whose
79
+ token was never issued. Human-bound tokens issued from the operator UI cannot
80
+ be adopted and stay refused; issue the agent a new token instead.
60
81
 
61
82
  ## Run it in Docker
62
83
 
@@ -66,12 +87,12 @@ Same source, same configuration, one long-lived service:
66
87
  cp .env.example .env
67
88
  # edit .env for the database path and any optional modules
68
89
  docker compose build
69
- docker compose run --rm --no-deps connecta npm run --silent provision-token -- "container-machine"
90
+ docker compose run --rm --no-deps connecta npm run --silent rotate-token -- local-machine
70
91
  # Save the returned cta_ token privately for clients and doctor.
71
92
  docker compose up -d
72
93
  ```
73
94
 
74
- The provisioning container writes through the installed package API into the
95
+ The rotation container writes through the installed package API into the
75
96
  same named volume and `CONNECTA_DATABASE` as the service. An empty volume
76
97
  admits no machine client. Compose never passes `CONNECTA_TOKEN` to the server;
77
98
  no environment value can substitute for a stored token.
@@ -99,9 +120,9 @@ server, then copy the old file into the database once, before the first start:
99
120
  npx connecta migrate-state .connecta-state.json .connecta.sqlite
100
121
  ```
101
122
 
102
- In the container, run it against the volume, e.g. `docker compose run --rm
103
- connecta npx connecta migrate-state /data/connecta-state.json
104
- /data/connecta.sqlite`. It copies every live entry with its expiry, keeps any
123
+ In the container, mount the old file and import it into the volume:
124
+ `docker compose run --rm -v "$PWD/.connecta-state.json:/import/state.json:ro"
125
+ connecta npx connecta migrate-state /import/state.json /data/connecta.sqlite`. It copies every live entry with its expiry, keeps any
105
126
  key the database already holds, and never changes the old file; delete that
106
127
  once the deployment is verified. The 0.28 activity log
107
128
  (`.connecta-activity.jsonl`) is not imported; history starts fresh.
@@ -120,9 +141,9 @@ off. `.env.example` lists each one, and `npm run typecheck` checks every module
120
141
  whether or not it is switched on.
121
142
 
122
143
  The template explicitly enables `ui: operatorUi()` from
123
- `@zackbart/connecta/ui`. Open `http://localhost:8787/` and supply a
124
- stored machine token to inspect Connections. Sign in with Clerk for human
125
- connection management. Omit that option and import for an API-only
144
+ `@zackbart/connecta/ui`. Open `http://localhost:8787/` and sign in with
145
+ Clerk as a maintainer to inspect Connections and manage connections. Agent
146
+ `cta_` tokens are refused by every human route, the operator UI included. Omit that option and import for an API-only
126
147
  server. OAuth callbacks remain in core even with no UI.
127
148
 
128
149
  The page labels each tool as a read or write and each endpoint as writable or
@@ -144,7 +165,10 @@ Connection management needs an interactive identity. A machine access token
144
165
  never authorizes browser credential mutations. `@clerk/backend`
145
166
  ships as a dependency of this template. To enable Clerk, set both
146
167
  `CLERK_PUBLISHABLE_KEY` and `CLERK_SECRET_KEY` (one without the other refuses to
147
- start) and set `PUBLIC_URL`.
168
+ start), set `PUBLIC_URL`, and list your Clerk user id in `CONNECTA_MAINTAINERS`
169
+ (comma-separated; startup refuses Clerk without a maintainer). Maintainers
170
+ alone use plain `/mcp` and the operator UI; add other people to a pool's
171
+ `members` in `src/connecta.config.ts`.
148
172
  Enable `aud_claim_enabled: true` in Clerk's instance OAuth application settings
149
173
  (`PATCH /v1/instance/oauth_application_settings` in Clerk's Backend API), then
150
174
  read the setting back. Both JWT and opaque OAuth tokens are supported.
@@ -164,18 +188,20 @@ audience. New DCR registrations need new entries, so prefer audience binding
164
188
  for standard host onboarding. Clerk session tokens work only on operator
165
189
  routes. [Inbound auth](https://github.com/zackbart/connecta/blob/main/documentation/auth.md#clerk-oauth-tokens-and-operator-sessions)
166
190
  explains configuration, verification, and fixed rejection reason codes.
167
- Machine clients use connecta-issued `cta_` access tokens
168
- through `accessTokens(storage)`, which `src/connecta.config.ts` configures on
169
- the database. Grant explicit `identity.accessTokenManagement` permissions to
170
- let a signed-in human mint them. Trusted local provisioning above works without
191
+ Machine clients are the declared `agents`, each using a connecta-issued
192
+ `cta_` token through `accessTokens(storage)`, which `src/connecta.config.ts`
193
+ configures on the database. A signed-in maintainer issues, rotates, and
194
+ revokes them from the operator UI. Trusted local rotation above works without
171
195
  Clerk and never requires opening the server.
172
196
 
173
- Set the code-owned identity resolvers deliberately. `connectorAccess` governs
174
- use; `credentialAdministration` permits shared-auth changes, and
175
- `personalConnection` permits the signed-in principal's personal-auth changes.
176
- Both management permissions default to none. Use `authScope: "personal"` for a
177
- connector where each person should connect their own downstream account.
178
- `activityAccess` separately selects readers of global activity.
197
+ Set who uses what in code: `maintainers` for plain `/mcp` and the operator UI,
198
+ and each pool's `members` for `/mcp/<pool>` (people) and `/agent/<pool>`
199
+ (agents), which serve exactly that pool's `tools`. Agent endpoints publish no
200
+ protected-resource metadata and accept only `cta_` tokens. Maintainers alone
201
+ administer shared credentials and OAuth grants, manage agent tokens, and read
202
+ activity. Use `authScope: "personal"` for a connector where each person should
203
+ connect their own downstream account: a member connects their own account on
204
+ the personal connectors in their pools.
179
205
 
180
206
  ### Credential vault
181
207
 
@@ -191,13 +217,13 @@ Keep this key outside the database. Losing it makes saved values unreadable;
191
217
  upgrades must reuse it. The shipped `time` connector declares no credential
192
218
  slot. Add `credential: { label: "API token" }` to an `api()` connector and read
193
219
  it through `await ctx.credential?.get()`, or use a Notion
194
- connector with `auth: { type: "token" }`, which declares its own slot. Authorized humans manage the slot inside that
195
- connection on `/`; there is no separate Credentials tab.
220
+ connector with `auth: { type: "token" }`, which declares its own slot. Maintainers manage the slot on that
221
+ connector's Connection tab (`/connectors/<id>`); there is no separate Credentials tab.
196
222
 
197
223
  In 0.25.0, this vault also seals downstream OAuth tokens on their first read.
198
224
  That is a one-way state migration for those tokens: rolling back to 0.24.x
199
225
  requires authorizing each OAuth connector again. See the
200
- [0.25.0 changelog](../../CHANGELOG.md#0250--2026-09-23) before upgrading a
226
+ [0.25.0 changelog](https://github.com/zackbart/connecta/blob/main/CHANGELOG.md#0250--2026-09-23) before upgrading a
201
227
  deployment with existing OAuth connections.
202
228
 
203
229
  A saved replacement takes effect on the next call. Connecta tests credentials
@@ -220,7 +246,7 @@ Diagnostics are independent. Keep the default logger or provide your own;
220
246
 
221
247
  The UI displays connections and current permissions. Configuration still owns
222
248
  the connector set, tool definitions, and access rules. Access token management
223
- requires explicit Clerk operator permission. There is no team roster or policy editor.
249
+ requires a signed-in Clerk maintainer. There is no team roster or policy editor.
224
250
 
225
251
  ## Deployment contract
226
252
 
@@ -249,10 +275,11 @@ from `CONNECTA_TOKEN`; it never accepts the secret as a command-line argument.
249
275
  Remote URLs must use HTTPS.
250
276
 
251
277
  A fully-wired deployment reports exactly the same line as a bare one —
252
- connector count, QuickJS executed, six tools, plus any catalog drift:
278
+ connector count, the executor, the tool count, the negotiated MCP version, and
279
+ the package version:
253
280
 
254
281
  ```text
255
- Connecta doctor passed: 1 connector(s), QuickJS executed, prescribed six-tool surface.
282
+ Connecta doctor passed: 1 connector(s), QuickJS executed, prescribed 6-tool surface, MCP <version>, package <version>.
256
283
  ```
257
284
 
258
285
  `QuickJS` is this deployment's sandbox, reported by the deployment itself —
@@ -268,8 +295,9 @@ feature should not leave a tab behind.
268
295
  Upgrading from v0.23 does not require rotating managed `cta_…` tokens. Import
269
296
  `accessTokens` from `@zackbart/connecta/auth/access-tokens`, replace the old
270
297
  `accessTokens: true` with `accessTokens: accessTokens(storage)`, and keep the
271
- same persistent storage namespace and identity/tool/pool grant rules. Enable
272
- `identity.accessTokenManagement` only for the interactive operators who should
273
- manage tokens. New issuance needs storage with atomic `compareAndSet`; older
274
- storage adapters can still verify existing tokens. See the package's
298
+ same persistent storage namespace. From 0.33 `maintainers` and pool `members`
299
+ replace `identity.connectorAccess` and every `grant`, and maintainers replace
300
+ the four `identity` management permissions; delete them and the `identity`
301
+ section, and follow the package's `documentation/migration-0.33.md`. From 0.33 a stored token admits only once it is bound to a
302
+ declared agent: follow "Tokens issued before agents" above. See the package's
275
303
  `documentation/auth.md` for the migration and storage requirements.
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # cp .env.example .env
4
4
  # docker compose build
5
- # docker compose run --rm --no-deps connecta npm run --silent provision-token -- "machine-name"
5
+ # docker compose run --rm --no-deps connecta npm run --silent rotate-token -- local-machine
6
6
  # docker compose up -d --build
7
7
  #
8
8
  # Compose reads `.env` from this directory, so one file configures both the
@@ -16,7 +16,7 @@ services:
16
16
  ports:
17
17
  - "${PORT:-8787}:${PORT:-8787}"
18
18
  environment:
19
- # accessTokens(storage) is always installed; an empty database is closed.
19
+ # accessTokens(storage) is always installed; an agent with no issued token is closed.
20
20
  # CONNECTA_TOKEN belongs to clients and is never configured on this server.
21
21
  PORT: ${PORT:-8787}
22
22
  # Downstream OAuth calls back to this origin. Unset is fine until this
@@ -28,6 +28,7 @@ services:
28
28
  # on when set and off when empty.
29
29
  CLERK_PUBLISHABLE_KEY: ${CLERK_PUBLISHABLE_KEY:-}
30
30
  CLERK_SECRET_KEY: ${CLERK_SECRET_KEY:-}
31
+ CONNECTA_MAINTAINERS: ${CONNECTA_MAINTAINERS:-}
31
32
  CONNECTA_CREDENTIAL_KEY: ${CONNECTA_CREDENTIAL_KEY:-}
32
33
  CONNECTA_ACTIVITY: ${CONNECTA_ACTIVITY:-}
33
34
  volumes:
@@ -10,14 +10,15 @@
10
10
  "esbuild@0.28.2": true
11
11
  },
12
12
  "scripts": {
13
- "doctor": "connecta doctor",
14
- "provision-token": "tsx src/provision-token.ts",
13
+ "adopt-token": "tsx src/adopt-token.ts",
14
+ "doctor": "connecta doctor --pool local-machine",
15
+ "rotate-token": "tsx src/rotate-token.ts",
15
16
  "start": "tsx src/index.ts",
16
17
  "typecheck": "tsc --noEmit"
17
18
  },
18
19
  "dependencies": {
19
20
  "@clerk/backend": "^3.12.0",
20
- "@zackbart/connecta": "0.32.0",
21
+ "@zackbart/connecta": "0.34.0",
21
22
  "quickjs-emscripten": "0.32.0"
22
23
  },
23
24
  "devDependencies": {
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Trusted one-time migration: bind a machine token issued before agents to a
3
+ * declared agent, so that machine's existing cta_ secret keeps working. Run
4
+ * with no arguments to list the tokens that can be adopted. A token binds
5
+ * once, only to an agent that has never been issued one; human-bound tokens
6
+ * are refused and stop admitting.
7
+ */
8
+ import { AccessTokenManager } from "@zackbart/connecta/auth/access-tokens";
9
+ import connectaConfig from "./connecta.config.js";
10
+
11
+ const [tokenId, agent] = process.argv.slice(2);
12
+ const { storage, agents } = connectaConfig(process.env);
13
+ const manager = new AccessTokenManager(storage!, { agents: agents ?? [] });
14
+ if (tokenId === undefined) {
15
+ for (const token of await manager.unboundTokens()) {
16
+ process.stdout.write(`${token.id} ${token.createdAt} ${token.name}\n`);
17
+ }
18
+ } else if (!agent || process.argv.length !== 4) {
19
+ throw new Error("Usage: npm run adopt-token -- [<token-id> <agent>]");
20
+ } else {
21
+ await manager.adopt(tokenId, agent);
22
+ process.stdout.write(`${agent} now uses token ${tokenId}\n`);
23
+ }