@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.
- package/AGENTS.md +43 -12
- package/CHANGELOG.md +524 -0
- package/README.md +32 -15
- package/SECURITY.md +6 -5
- package/bin/connecta.mjs +5 -1
- package/dist/activity.d.ts +10 -3
- package/dist/activity.js +10 -1
- package/dist/advertised-schema.js +53 -0
- package/dist/artifacts/connector.d.ts +55 -0
- package/dist/artifacts/connector.js +724 -0
- package/dist/artifacts/document.js +76 -0
- package/dist/artifacts/guide.js +140 -0
- package/dist/artifacts/html-scan.js +329 -0
- package/dist/artifacts/json.js +68 -0
- package/dist/artifacts/kv-store.d.ts +20 -0
- package/dist/artifacts/kv-store.js +140 -0
- package/dist/artifacts/markdown.js +449 -0
- package/dist/artifacts/module.d.ts +37 -0
- package/dist/artifacts/module.js +60 -0
- package/dist/artifacts/operations.d.ts +195 -0
- package/dist/artifacts/operations.js +944 -0
- package/dist/artifacts/refresh.d.ts +53 -0
- package/dist/artifacts/refresh.js +235 -0
- package/dist/artifacts/routes.js +194 -0
- package/dist/artifacts/types.d.ts +214 -0
- package/dist/artifacts/types.js +22 -0
- package/dist/artifacts/validate.d.ts +62 -0
- package/dist/artifacts/validate.js +652 -0
- package/dist/artifacts.d.ts +6 -0
- package/dist/artifacts.js +9 -0
- package/dist/auth/downstream-oauth.js +490 -106
- package/dist/call-admission.js +139 -113
- package/dist/catalog-service.js +152 -106
- package/dist/catalog.js +297 -38
- package/dist/concurrency.js +24 -23
- package/dist/connector-access.d.ts +18 -4
- package/dist/connector-access.js +65 -20
- package/dist/connector-caller.d.ts +8 -0
- package/dist/connector-caller.js +19 -0
- package/dist/connector-scope.js +6 -37
- package/dist/connectors/api.d.ts +2 -2
- package/dist/connectors/remote-mcp.d.ts +6 -5
- package/dist/connectors/remote-mcp.js +330 -240
- package/dist/credential-contract.d.ts +9 -0
- package/dist/credential-rules.js +2 -2
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +45 -3
- package/dist/errors.d.ts +16 -1
- package/dist/errors.js +28 -0
- package/dist/execute.js +745 -282
- package/dist/executor-admission.d.ts +11 -1
- package/dist/executor-admission.js +145 -63
- package/dist/executors/quickjs-runtime.js +4 -2
- package/dist/executors/quickjs.js +255 -212
- package/dist/fix-prompt.js +37 -0
- package/dist/index.d.ts +72 -7
- package/dist/index.js +281 -26
- package/dist/invocation.js +371 -286
- package/dist/meta-tools.js +213 -94
- package/dist/module-contracts.d.ts +21 -1
- package/dist/oauth-callback-outcome.js +94 -0
- package/dist/oauth-sealing.js +29 -0
- package/dist/operator-ui/fix-prompts.js +87 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +103 -1
- package/dist/operator-ui/setup-commands.js +53 -0
- package/dist/operator-ui/view.js +128 -3
- package/dist/program-source.js +321 -0
- package/dist/providers/notion.js +2 -2
- package/dist/providers/revenuecat.js +32 -5
- package/dist/providers/stripe.d.ts +3 -2
- package/dist/providers/vercel.js +1 -1
- package/dist/registry.d.ts +43 -7
- package/dist/registry.js +434 -288
- package/dist/resumable.js +1291 -0
- package/dist/routes/activity.js +74 -154
- package/dist/routes/actor-labels.js +114 -0
- package/dist/routes/credentials.js +196 -163
- package/dist/routes/mcp.js +228 -112
- package/dist/routes/oauth-management.js +73 -71
- package/dist/routes/oauth.js +44 -16
- package/dist/routes/operator.js +83 -0
- package/dist/routes/shared.d.ts +25 -1
- package/dist/routes/shared.js +20 -2
- package/dist/routes/ui-data.js +211 -0
- package/dist/routes/ui.js +76 -35
- package/dist/run-journal.js +347 -0
- package/dist/runtime/admission.js +43 -0
- package/dist/runtime/call-admission.js +67 -0
- package/dist/runtime/connector-scope.js +72 -0
- package/dist/runtime/run.js +174 -0
- package/dist/runtime/services.js +130 -0
- package/dist/runtime/shared-read.js +106 -0
- package/dist/runtime/storage.js +66 -0
- package/dist/server.js +137 -89
- package/dist/skills.js +28 -9
- package/dist/storage/file.js +39 -2
- package/dist/storage/memory.js +28 -16
- package/dist/timeout.js +39 -43
- package/dist/tool-safety.d.ts +27 -1
- package/dist/tool-safety.js +31 -0
- package/dist/types.d.ts +45 -7
- package/dist/ui.d.ts +25 -4
- package/dist/ui.js +77 -167
- package/dist/url-safety.js +157 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +367 -16
- package/documentation/auth.md +163 -2
- package/documentation/code-mode.md +274 -56
- package/documentation/meta-tools.md +200 -44
- package/ethos.md +46 -47
- package/examples/worker/AGENTS.md +6 -0
- package/examples/worker/README.md +224 -14
- package/examples/worker/src/d1-activity-row.ts +14 -2
- package/examples/worker/src/d1-activity.ts +4 -2
- package/examples/worker/src/d1-storage.ts +116 -0
- package/examples/worker/src/index.ts +42 -7
- package/examples/worker/src/r2-artifact-blobs.ts +29 -0
- package/examples/worker/wrangler.jsonc +21 -5
- package/package.json +16 -4
- package/templates/node/AGENTS.md +9 -2
- package/templates/node/README.md +31 -3
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +27 -0
- package/dist/auth/downstream-oauth.d.ts +0 -200
- package/dist/catalog-fingerprint.d.ts +0 -19
- package/dist/catalog-limits.d.ts +0 -9
- package/dist/catalog-service.d.ts +0 -263
- package/dist/catalog.d.ts +0 -87
- package/dist/concurrency.d.ts +0 -3
- package/dist/connectors/guarded-fetch.d.ts +0 -96
- package/dist/credential-rules.d.ts +0 -85
- package/dist/execute.d.ts +0 -154
- package/dist/executor-result.d.ts +0 -10
- package/dist/executors/quickjs-child.d.ts +0 -1
- package/dist/executors/quickjs-protocol.d.ts +0 -67
- package/dist/favicon.d.ts +0 -1
- package/dist/identity.d.ts +0 -4
- package/dist/invocation.d.ts +0 -74
- package/dist/mcp-result.d.ts +0 -8
- package/dist/meta-tools.d.ts +0 -129
- package/dist/operator-ui/generated.d.ts +0 -2
- package/dist/operator-ui/view.d.ts +0 -188
- package/dist/result-shapes.d.ts +0 -13
- package/dist/retired-toolkits.d.ts +0 -2
- package/dist/routes/activity.d.ts +0 -2
- package/dist/routes/credentials.d.ts +0 -2
- package/dist/routes/mcp.d.ts +0 -10
- package/dist/routes/oauth-management.d.ts +0 -2
- package/dist/routes/oauth.d.ts +0 -2
- package/dist/routes/ui.d.ts +0 -2
- package/dist/server.d.ts +0 -9
- package/dist/skills.d.ts +0 -44
- package/dist/timeout.d.ts +0 -21
package/documentation/auth.md
CHANGED
|
@@ -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
|
|
55
|
-
|
|
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
|