@zackbart/connecta 0.24.4 → 0.26.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 (155) hide show
  1. package/AGENTS.md +43 -12
  2. package/CHANGELOG.md +524 -0
  3. package/README.md +32 -15
  4. package/SECURITY.md +6 -5
  5. package/bin/connecta.mjs +5 -1
  6. package/dist/activity.d.ts +10 -3
  7. package/dist/activity.js +10 -1
  8. package/dist/advertised-schema.js +53 -0
  9. package/dist/artifacts/connector.d.ts +55 -0
  10. package/dist/artifacts/connector.js +724 -0
  11. package/dist/artifacts/document.js +76 -0
  12. package/dist/artifacts/guide.js +140 -0
  13. package/dist/artifacts/html-scan.js +329 -0
  14. package/dist/artifacts/json.js +68 -0
  15. package/dist/artifacts/kv-store.d.ts +20 -0
  16. package/dist/artifacts/kv-store.js +140 -0
  17. package/dist/artifacts/markdown.js +449 -0
  18. package/dist/artifacts/module.d.ts +37 -0
  19. package/dist/artifacts/module.js +60 -0
  20. package/dist/artifacts/operations.d.ts +195 -0
  21. package/dist/artifacts/operations.js +944 -0
  22. package/dist/artifacts/refresh.d.ts +53 -0
  23. package/dist/artifacts/refresh.js +235 -0
  24. package/dist/artifacts/routes.js +194 -0
  25. package/dist/artifacts/types.d.ts +214 -0
  26. package/dist/artifacts/types.js +22 -0
  27. package/dist/artifacts/validate.d.ts +62 -0
  28. package/dist/artifacts/validate.js +652 -0
  29. package/dist/artifacts.d.ts +6 -0
  30. package/dist/artifacts.js +9 -0
  31. package/dist/auth/downstream-oauth.js +490 -106
  32. package/dist/call-admission.js +139 -113
  33. package/dist/catalog-service.js +152 -106
  34. package/dist/catalog.js +297 -38
  35. package/dist/concurrency.js +24 -23
  36. package/dist/connector-access.d.ts +18 -4
  37. package/dist/connector-access.js +65 -20
  38. package/dist/connector-caller.d.ts +8 -0
  39. package/dist/connector-caller.js +19 -0
  40. package/dist/connector-scope.js +6 -37
  41. package/dist/connectors/api.d.ts +2 -2
  42. package/dist/connectors/remote-mcp.d.ts +6 -5
  43. package/dist/connectors/remote-mcp.js +330 -240
  44. package/dist/credential-contract.d.ts +9 -0
  45. package/dist/credential-rules.js +2 -2
  46. package/dist/credentials.d.ts +6 -1
  47. package/dist/credentials.js +45 -3
  48. package/dist/errors.d.ts +16 -1
  49. package/dist/errors.js +28 -0
  50. package/dist/execute.js +745 -282
  51. package/dist/executor-admission.d.ts +11 -1
  52. package/dist/executor-admission.js +145 -63
  53. package/dist/executors/quickjs-runtime.js +4 -2
  54. package/dist/executors/quickjs.js +255 -212
  55. package/dist/fix-prompt.js +37 -0
  56. package/dist/index.d.ts +72 -7
  57. package/dist/index.js +281 -26
  58. package/dist/invocation.js +371 -286
  59. package/dist/meta-tools.js +213 -94
  60. package/dist/module-contracts.d.ts +21 -1
  61. package/dist/oauth-callback-outcome.js +94 -0
  62. package/dist/oauth-sealing.js +29 -0
  63. package/dist/operator-ui/fix-prompts.js +87 -0
  64. package/dist/operator-ui/generated.js +2 -2
  65. package/dist/operator-ui/model.d.ts +103 -1
  66. package/dist/operator-ui/setup-commands.js +53 -0
  67. package/dist/operator-ui/view.js +128 -3
  68. package/dist/program-source.js +321 -0
  69. package/dist/providers/notion.js +2 -2
  70. package/dist/providers/revenuecat.js +32 -5
  71. package/dist/providers/stripe.d.ts +3 -2
  72. package/dist/providers/vercel.js +1 -1
  73. package/dist/registry.d.ts +43 -7
  74. package/dist/registry.js +434 -288
  75. package/dist/resumable.js +1291 -0
  76. package/dist/routes/activity.js +74 -154
  77. package/dist/routes/actor-labels.js +114 -0
  78. package/dist/routes/credentials.js +196 -163
  79. package/dist/routes/mcp.js +228 -112
  80. package/dist/routes/oauth-management.js +73 -71
  81. package/dist/routes/oauth.js +44 -16
  82. package/dist/routes/operator.js +83 -0
  83. package/dist/routes/shared.d.ts +25 -1
  84. package/dist/routes/shared.js +20 -2
  85. package/dist/routes/ui-data.js +211 -0
  86. package/dist/routes/ui.js +76 -35
  87. package/dist/run-journal.js +347 -0
  88. package/dist/runtime/admission.js +43 -0
  89. package/dist/runtime/call-admission.js +67 -0
  90. package/dist/runtime/connector-scope.js +72 -0
  91. package/dist/runtime/run.js +174 -0
  92. package/dist/runtime/services.js +130 -0
  93. package/dist/runtime/shared-read.js +106 -0
  94. package/dist/runtime/storage.js +66 -0
  95. package/dist/server.js +137 -89
  96. package/dist/skills.js +28 -9
  97. package/dist/storage/file.js +39 -2
  98. package/dist/storage/memory.js +28 -16
  99. package/dist/timeout.js +39 -43
  100. package/dist/tool-safety.d.ts +27 -1
  101. package/dist/tool-safety.js +31 -0
  102. package/dist/types.d.ts +45 -7
  103. package/dist/ui.d.ts +25 -4
  104. package/dist/ui.js +77 -167
  105. package/dist/url-safety.js +157 -0
  106. package/dist/version.d.ts +1 -1
  107. package/dist/version.js +1 -1
  108. package/documentation/architecture.md +367 -16
  109. package/documentation/auth.md +163 -2
  110. package/documentation/code-mode.md +274 -56
  111. package/documentation/meta-tools.md +200 -44
  112. package/ethos.md +46 -47
  113. package/examples/worker/AGENTS.md +6 -0
  114. package/examples/worker/README.md +224 -14
  115. package/examples/worker/src/d1-activity-row.ts +14 -2
  116. package/examples/worker/src/d1-activity.ts +4 -2
  117. package/examples/worker/src/d1-storage.ts +116 -0
  118. package/examples/worker/src/index.ts +42 -7
  119. package/examples/worker/src/r2-artifact-blobs.ts +29 -0
  120. package/examples/worker/wrangler.jsonc +21 -5
  121. package/package.json +16 -4
  122. package/templates/node/AGENTS.md +9 -2
  123. package/templates/node/README.md +31 -3
  124. package/templates/node/package.json +1 -1
  125. package/templates/node/src/index.ts +27 -0
  126. package/dist/auth/downstream-oauth.d.ts +0 -200
  127. package/dist/catalog-fingerprint.d.ts +0 -19
  128. package/dist/catalog-limits.d.ts +0 -9
  129. package/dist/catalog-service.d.ts +0 -263
  130. package/dist/catalog.d.ts +0 -87
  131. package/dist/concurrency.d.ts +0 -3
  132. package/dist/connectors/guarded-fetch.d.ts +0 -96
  133. package/dist/credential-rules.d.ts +0 -85
  134. package/dist/execute.d.ts +0 -154
  135. package/dist/executor-result.d.ts +0 -10
  136. package/dist/executors/quickjs-child.d.ts +0 -1
  137. package/dist/executors/quickjs-protocol.d.ts +0 -67
  138. package/dist/favicon.d.ts +0 -1
  139. package/dist/identity.d.ts +0 -4
  140. package/dist/invocation.d.ts +0 -74
  141. package/dist/mcp-result.d.ts +0 -8
  142. package/dist/meta-tools.d.ts +0 -129
  143. package/dist/operator-ui/generated.d.ts +0 -2
  144. package/dist/operator-ui/view.d.ts +0 -188
  145. package/dist/result-shapes.d.ts +0 -13
  146. package/dist/retired-toolkits.d.ts +0 -2
  147. package/dist/routes/activity.d.ts +0 -2
  148. package/dist/routes/credentials.d.ts +0 -2
  149. package/dist/routes/mcp.d.ts +0 -10
  150. package/dist/routes/oauth-management.d.ts +0 -2
  151. package/dist/routes/oauth.d.ts +0 -2
  152. package/dist/routes/ui.d.ts +0 -2
  153. package/dist/server.d.ts +0 -9
  154. package/dist/skills.d.ts +0 -44
  155. package/dist/timeout.d.ts +0 -21
@@ -51,8 +51,11 @@ deployments and providers that return no identity share one result partition.
51
51
 
52
52
  `identity.connectorAccess` returns `"all"` — the default — or a list of grants:
53
53
  a declared connector id opens every tool on it, a `connector.tool` address opens
54
- that tool alone, and grants are additive, so a bare id beside addresses for the
55
- same connector means the whole connector. It governs discovery and use alike.
54
+ that tool alone, and `{ tool: "connector.tool", requireReadOnly: true }` opens
55
+ that exact tool only while the loaded catalog says it is explicitly read-only.
56
+ The guarded form accepts no connector-wide grant. Grants are additive, so a
57
+ bare id or an unrestricted address beside a guarded address wins for that
58
+ tool. It governs discovery and use alike.
56
59
 
57
60
  Tool grants are enforced in the scoped registry view, below the catalog service,
58
61
  so `search_tools`, both call tools, a program's `connecta.search`,
@@ -69,6 +72,84 @@ There is no *caller-selected* tool set. A narrower slice is a branch in this
69
72
  resolver or a config-declared pool; a bot that needs its own slice is its own
70
73
  bearer subject. What a request may never do is name its own scope.
71
74
 
75
+ ### Team read sets
76
+
77
+ `connectorAccess` is the supported way to give Engineering and Marketing
78
+ different views of one deployment. Keep membership in deployment code and
79
+ review the exact tool addresses you intend to grant:
80
+
81
+ ```ts
82
+ const engineering = new Set(["engineer-id"]);
83
+ const marketing = new Set(["marketer-id"]);
84
+ const engineeringGrants = ["issues", { tool: "campaigns.list", requireReadOnly: true }] as const;
85
+ const marketingGrants = ["campaigns", { tool: "issues.search", requireReadOnly: true }] as const;
86
+
87
+ createConnecta({
88
+ auth: cloudflareAccessAuth(),
89
+ identity: {
90
+ connectorAccess: ({ principal }) => {
91
+ const id = principal?.id;
92
+ if (!id) return [];
93
+ return [...new Set([
94
+ ...(engineering.has(id) ? engineeringGrants : []),
95
+ ...(marketing.has(id) ? marketingGrants : []),
96
+ ])];
97
+ },
98
+ },
99
+ connectors: [issues, campaigns, billing],
100
+ executor,
101
+ });
102
+ ```
103
+
104
+ Here Engineering sees every `issues` tool and only `campaigns.list`;
105
+ Marketing sees every `campaigns` tool and only `issues.search`. Neither sees
106
+ `billing`. Someone in both groups gets both connectors in full because grants
107
+ are additive. An unknown or unprincipalled identity gets an empty view. The
108
+ example assumes the two exact tools have been reviewed as read-only; the names
109
+ themselves carry no safety meaning. The guarded grants also check the current
110
+ loaded annotations before discovery or use. Use a stable authenticated
111
+ principal or subject for membership, never a request header or tool argument. A pool can
112
+ narrow any of these views on its endpoint, but cannot make plain `/mcp` narrower.
113
+
114
+ New tool names are excluded until added to a reviewed list. A removed or
115
+ renamed name is unreachable and warned when the scoped view reads its catalog.
116
+ For a guarded grant, missing, false, or contradictory read-only annotations
117
+ also remove that tool from discovery and every invocation path, including
118
+ `call_destructive_tool` and approval-exempt programs. An unrestricted exact
119
+ string grant has the earlier behavior: a tool that changes from read to write
120
+ stays granted, though `call_tool` refuses it and the write paths take over.
121
+ Changing schema alone does not revoke either kind of grant. Review schemas,
122
+ annotations, and downstream behavior before changing lists or upgrading a
123
+ maintained provider. An arbitrary remote MCP catalog can drift without a
124
+ package upgrade; a valid stale cached catalog may keep its earlier
125
+ classification until refresh. A downstream can also keep a read-only
126
+ annotation while changing the behavior behind it. For `api()` tools, the
127
+ deployment author owns both declaration and handler. Connecta enforces the
128
+ loaded declaration, not the downstream's promise of no side effects. A
129
+ restricted downstream credential adds protection where the provider offers
130
+ one, but does not replace the per-identity grant.
131
+
132
+ When the resolver returns `[]`, connector-level discovery and the connection
133
+ UI have no connectors to show. An address-only grant can leave a connector
134
+ visible while its named tool is absent; the tool stays unreachable. A failed
135
+ remote catalog load is an error, not an empty successful catalog; a valid stale
136
+ catalog may be served within its configured stale window. A guarded grant
137
+ whose tool loses qualification can leave its connector visible with no tools.
138
+ Personal OAuth ownership and credential-administration permissions remain separate from
139
+ visibility. These are the checks for a team read set:
140
+
141
+ | Boundary | Expected result for a reviewed address and an excluded write |
142
+ | --- | --- |
143
+ | `search_tools`, `describe_tools`, connector guides | Show the granted address; omit the write. |
144
+ | `call_tool` | Invoke an explicitly read-only granted address; return `unknown_tool` for the write. |
145
+ | `call_destructive_tool` | Return `unknown_tool` for the excluded write, without dispatch. |
146
+ | `execute_code` search, describe, call | See granted tools only; refuse the excluded write. A granted write still follows the separate approval rules. |
147
+ | Connection UI | List granted tools only; never turn visibility into credential administration. |
148
+ | `/mcp/<pool>` | Intersect its grants with the identity view; plain `/mcp` keeps the identity view. |
149
+
150
+ Integration tests in `test/identity-scope.test.ts` exercise these paths and
151
+ the older unrestricted exact-grant behavior.
152
+
72
153
  ## Pools
73
154
 
74
155
  A pool is a named slice of the deployment served at its own endpoint,
@@ -149,6 +230,86 @@ partitions. Literal `auth: { type: "headers" }` cannot be personal, because its
149
230
  secret lives in deployment code; `remoteMcp()` refuses that combination at
150
231
  construction.
151
232
 
233
+ ## Downstream OAuth state at rest
234
+
235
+ With a vault configured, a `remoteMcp()` OAuth connector's tokens, dynamically
236
+ registered client (secret included), and PKCE verifier are sealed with the
237
+ vault's AES-GCM key before they reach storage. The additional authenticated data
238
+ names the connector, the owner partition, and the physical key, which carries
239
+ the authorization epoch, so ciphertext copied to another connector, principal,
240
+ or epoch does not open. Anything that fails to open — a tampered value, a
241
+ rotated key — reads as absent: the connector reports `auth_required` and logs a
242
+ warning. The flow bookkeeping stays plaintext: `state`, the pending URL,
243
+ discovery metadata, and the generation. The callback route reads `state`
244
+ directly, and none of it authenticates anything by itself.
245
+
246
+ Plaintext left by an older release is read, then sealed where it lies through
247
+ the same generation fence as any write, so an upgrade keeps the grant. Sealing
248
+ is one-way. An older release reads sealed state as unusable, so rolling back
249
+ means authorizing again. A vault without the optional `seal`/`open` members
250
+ keeps these values plaintext and draws a startup warning. Without any vault,
251
+ nothing changes: the state is plaintext, as it always was.
252
+
253
+ ## Refresh failures
254
+
255
+ A failed token refresh means one of two things, and connecta decides which
256
+ from the token endpoint's answer, not the SDK's reading of it. A **dead grant**
257
+ is any 4xx except 408, 425, and 429, or a 2xx carrying an OAuth `error` (GitHub
258
+ answers `200 {"error":"bad_refresh_token"}`). It ends as `auth_required`, and
259
+ the refused tokens are deleted on the spot, so no later request or isolate
260
+ sends them again and the next `authorize_connector` goes straight to consent.
261
+ An **outage** is a 5xx, 408, 425, 429, a network failure, or a 2xx that is not
262
+ a token response. It ends as retryable `unavailable`, or `rate_limited` for a
263
+ 429, with `retryAfterMs` when the server sent `Retry-After`. The grant is kept,
264
+ and a passive call writes no consent URL. Every request joined on the same
265
+ in-flight refresh gets the same verdict, even when the request that sent the
266
+ refresh is cancelled after the answer arrives: the refused tokens are deleted
267
+ before anyone waiting is released, so a request arriving meanwhile joins the
268
+ refusal instead of sending the dead token again.
269
+
270
+ The SDK needs this help. On a refresh failure it cannot parse, or one marked
271
+ `server_error`, it falls through to starting authorization, which reports a
272
+ healthy grant as needing consent. It rethrows every other OAuth error
273
+ untouched, which reports a dead grant as a generic outage and keeps re-sending
274
+ it. So the refresh coordinator hands the SDK an answer it classifies correctly,
275
+ and the provider hooks it calls next finish the job. The pinned SDK behavior is
276
+ spelled out beside `refreshResponseOutcome`. An explicit authorization during
277
+ an outage still falls through to consent, since that is what it asked for.
278
+
279
+ ## URLs a downstream advertises
280
+
281
+ A `remoteMcp()` OAuth connector learns most of the URLs it fetches from the
282
+ downstream itself: the `resource_metadata` in its 401 challenge, the
283
+ `authorization_servers` in that metadata, and the token and registration
284
+ endpoints the authorization server publishes. Connecta fetches every one of
285
+ them server-side, so a compromised downstream could otherwise aim a Node or
286
+ Docker host at its own network — cloud metadata at `169.254.169.254`, an admin
287
+ panel on the LAN. Config is the security model, so the rule splits on it:
288
+
289
+ - A URL on the connector's configured origin is trusted. The operator wrote it
290
+ down.
291
+ - Any other URL must be `https` and must not name a private host: `localhost`
292
+ and `*.localhost`, `127/8`, `10/8`, `172.16/12`, `192.168/16`, `169.254/16`,
293
+ `100.64/10`, `0/8`, `::`, `::1`, `fc00::/7`, `fe80::/10`, or an IPv4-mapped
294
+ IPv6 form of any of those.
295
+ - A connector configured on a loopback host may also learn loopback URLs, over
296
+ `http` too, so a local MCP server with a local authorization server keeps
297
+ working. LAN and link-local addresses stay refused.
298
+
299
+ A refused URL is never requested. Discovery, registration, and code exchange
300
+ fail with a non-retryable `connector_call_failed` that names the host and
301
+ nothing else from the URL. A refused refresh never reaches the token endpoint,
302
+ so there is no verdict to act on: the grant is kept, and the SDK falls through
303
+ to consent as it does for any refresh it could not complete. Redirects cannot
304
+ route around the rule, because the redirect policy only follows same-origin
305
+ hops.
306
+
307
+ The check is syntactic. It reads the host after the WHATWG URL parser has
308
+ folded `2130706433` and `0x7f.1` into `127.0.0.1`, and it never resolves a
309
+ name, because the Workers-safe core has no DNS. A public name that resolves to
310
+ a private address, or rebinds to one after the check, is out of scope; a host
311
+ that must stop that runs connecta behind an egress policy that does.
312
+
152
313
  ## Management permissions
153
314
 
154
315
  Visibility alone grants no authentication-management permission. Two independent