@zackbart/connecta 0.21.1 → 0.22.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/CHANGELOG.md +83 -0
- package/README.md +7 -0
- package/dist/access-tokens.d.ts +2 -2
- package/dist/access-tokens.js +14 -2
- package/dist/auth/downstream-oauth.d.ts +65 -2
- package/dist/auth/downstream-oauth.js +408 -20
- package/dist/connectors/api.d.ts +2 -0
- package/dist/connectors/api.js +1 -0
- package/dist/connectors/remote-mcp.d.ts +2 -0
- package/dist/connectors/remote-mcp.js +14 -4
- package/dist/credentials.d.ts +6 -6
- package/dist/credentials.js +25 -21
- package/dist/executors/quickjs.js +4 -0
- package/dist/identity.d.ts +4 -0
- package/dist/identity.js +17 -0
- package/dist/index.d.ts +16 -2
- package/dist/index.js +6 -1
- package/dist/meta-tools.js +7 -2
- package/dist/operator-ui/generated.js +1 -1
- package/dist/operator-ui/model.d.ts +4 -2
- package/dist/operator-ui/view.js +1 -1
- package/dist/providers/cloudflare.d.ts +2 -0
- package/dist/providers/cloudflare.js +1 -0
- package/dist/providers/linear.d.ts +2 -0
- package/dist/providers/linear.js +1 -0
- package/dist/providers/mixpanel.d.ts +2 -0
- package/dist/providers/mixpanel.js +1 -0
- package/dist/providers/notion.d.ts +2 -0
- package/dist/providers/notion.js +1 -0
- package/dist/providers/revenuecat.d.ts +2 -0
- package/dist/providers/revenuecat.js +1 -0
- package/dist/providers/stripe.d.ts +2 -0
- package/dist/providers/stripe.js +1 -0
- package/dist/registry.d.ts +25 -0
- package/dist/registry.js +200 -4
- package/dist/routes/access-tokens.js +2 -2
- package/dist/routes/activity.js +4 -1
- package/dist/routes/credentials.js +31 -12
- package/dist/routes/mcp.js +17 -2
- package/dist/routes/oauth.js +55 -11
- package/dist/routes/shared.d.ts +20 -4
- package/dist/routes/shared.js +92 -24
- package/dist/routes/ui.js +32 -13
- package/dist/types.d.ts +28 -2
- package/dist/ui.d.ts +3 -3
- package/dist/ui.js +18 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +31 -8
- package/documentation/auth.md +90 -10
- package/documentation/code-mode.md +4 -4
- package/documentation/connectors.md +13 -0
- package/documentation/meta-tools.md +4 -3
- package/documentation/operations.md +5 -3
- package/documentation/operator-ui.md +13 -4
- package/documentation/request-admission.md +2 -1
- package/documentation/storage-and-credentials.md +77 -4
- package/documentation/upgrading.md +38 -7
- package/ethos.md +8 -8
- package/examples/worker/AGENTS.md +44 -0
- package/examples/worker/README.md +63 -14
- package/examples/worker/src/index.ts +26 -22
- package/package.json +1 -1
- package/templates/node/README.md +7 -0
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +13 -4
package/documentation/auth.md
CHANGED
|
@@ -6,6 +6,63 @@ Access identities on Workers, or a mixture. Static bearers are checked first;
|
|
|
6
6
|
the remaining providers keep configuration order. The first successful
|
|
7
7
|
identity owns the activity actor for that request.
|
|
8
8
|
|
|
9
|
+
## Principals, visibility, and operators
|
|
10
|
+
|
|
11
|
+
Connecta distinguishes three identities. The actor is the exact caller written
|
|
12
|
+
to activity. The subject is any stable authenticated caller and owns transient
|
|
13
|
+
results such as `get_result` pages. The principal is the human owner of personal
|
|
14
|
+
connector auth. An interactive Clerk or Access user supplies all three. A
|
|
15
|
+
Cloudflare service identity has an actor and subject but no principal. A
|
|
16
|
+
connecta access token has its own actor and subject and inherits the principal
|
|
17
|
+
that created it, so agents using that token reach the creator's personal
|
|
18
|
+
connections without becoming operators.
|
|
19
|
+
|
|
20
|
+
`identity.connectorAccess` derives the connector ids a caller may discover and
|
|
21
|
+
invoke. The resolver receives authenticated identity data, never request input,
|
|
22
|
+
and returns `"all"` or a list of ids declared in `connectors`. An unknown id or
|
|
23
|
+
a thrown resolver fails the request closed.
|
|
24
|
+
|
|
25
|
+
`identity.connectorAccess` is also the credential-management boundary. A
|
|
26
|
+
signed-in human may save, test, disconnect, and authorize every visible
|
|
27
|
+
connector: personal auth changes only that principal's partition, while shared
|
|
28
|
+
auth changes the deployment-wide grant for everyone who can see the connector.
|
|
29
|
+
Use `authScope: "personal"` when one member must not rotate another member's
|
|
30
|
+
connection.
|
|
31
|
+
|
|
32
|
+
`identity.operatorAccess` separately reserves deployment-wide administration:
|
|
33
|
+
access-token creation and global activity history. Omit the resolver to
|
|
34
|
+
preserve the prior rule that every interactive human is an operator. When it is
|
|
35
|
+
configured, activity history is operator-only because its global event stream
|
|
36
|
+
contains other principals' connector names and actors.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
createConnecta({
|
|
40
|
+
auth: cloudflareAccessAuth(),
|
|
41
|
+
identity: {
|
|
42
|
+
connectorAccess: ({ principal }) =>
|
|
43
|
+
principal?.id === "user_a"
|
|
44
|
+
? ["shared_docs", "personal_linear"]
|
|
45
|
+
: ["shared_docs"],
|
|
46
|
+
operatorAccess: ({ id }) => id === "user_a",
|
|
47
|
+
},
|
|
48
|
+
connectors: [
|
|
49
|
+
remoteMcp("shared_docs", { url: "https://example.com/mcp" }),
|
|
50
|
+
remoteMcp("personal_linear", {
|
|
51
|
+
url: "https://mcp.linear.app/mcp",
|
|
52
|
+
authScope: "personal",
|
|
53
|
+
auth: { type: "oauth" },
|
|
54
|
+
}),
|
|
55
|
+
],
|
|
56
|
+
executor,
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Identity namespaces matter. Built-in Clerk and Access providers supply one.
|
|
61
|
+
A custom interactive provider must set `activityActorNamespace` before its
|
|
62
|
+
users can own personal auth. It may still use the legacy operator behavior
|
|
63
|
+
without one, but connecta will not merge unnamespaced users into personal
|
|
64
|
+
storage.
|
|
65
|
+
|
|
9
66
|
## Cloudflare Access on Workers
|
|
10
67
|
|
|
11
68
|
[`cloudflareAccessAuth()`](https://developers.cloudflare.com/workers/configuration/cloudflare-access/)
|
|
@@ -35,7 +92,8 @@ also means it is deliberately not a Node or `cloudflared` origin adapter, and
|
|
|
35
92
|
it does not survive a Service Binding hop: those shapes need their own explicit
|
|
36
93
|
trust boundary.
|
|
37
94
|
|
|
38
|
-
A human identity gets MCP and
|
|
95
|
+
A human identity gets MCP and personal-connection access. It gets operator
|
|
96
|
+
access unless `identity.operatorAccess` says otherwise. A Cloudflare service-token
|
|
39
97
|
identity gets MCP access and a stable activity subject, but no `userId`, so it
|
|
40
98
|
cannot write credentials, run downstream OAuth mutations, or issue connecta
|
|
41
99
|
tokens. Access policy decides who reaches the Worker; connecta does not mirror
|
|
@@ -48,7 +106,27 @@ the Worker. Enable [**Managed OAuth**](https://developers.cloudflare.com/cloudfl
|
|
|
48
106
|
on that Worker-level application for interactive MCP clients.
|
|
49
107
|
Cloudflare then owns the unauthenticated challenge and `/.well-known/`
|
|
50
108
|
metadata, issues opaque RFC 8707 tokens, and resolves them into the same trusted
|
|
51
|
-
Worker identity.
|
|
109
|
+
Worker identity. Managed OAuth allows no hosted client callback by default, so
|
|
110
|
+
enable Dynamic Client Registration and add all three values to **Allowed
|
|
111
|
+
redirect URIs**:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
https://claude.ai/api/mcp/auth_callback
|
|
115
|
+
https://chatgpt.com/connector_platform_oauth_redirect
|
|
116
|
+
https://chatgpt.com/connector/oauth/*
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Cloudflare exposes that list as
|
|
120
|
+
`oauth_configuration.dynamic_client_registration.allowed_uris`. It belongs to
|
|
121
|
+
the Access application's Managed OAuth settings, not the Access policy that
|
|
122
|
+
selects admitted identities. Claude uses the fixed first value. ChatGPT may use
|
|
123
|
+
its stable callback or a callback-id path covered by the third value. If a
|
|
124
|
+
client registers a different redirect, add that exact URI or the narrowest path
|
|
125
|
+
wildcard that covers it; do not allow the client's whole origin. Without these
|
|
126
|
+
entries discovery succeeds and client registration fails later, which makes a
|
|
127
|
+
missing allowlist look like a broken MCP server.
|
|
128
|
+
|
|
129
|
+
Do not add a bypass for the discovery routes. A fully
|
|
52
130
|
automated client instead uses a [Cloudflare Access service token](https://developers.cloudflare.com/cloudflare-one/access-controls/service-credentials/service-tokens/)
|
|
53
131
|
through the
|
|
54
132
|
`CF-Access-Client-Id` and `CF-Access-Client-Secret` headers.
|
|
@@ -65,7 +143,7 @@ Worker-level Access runs before every connecta route. Consequently:
|
|
|
65
143
|
OAuth discovery paths when Managed OAuth is enabled.
|
|
66
144
|
|
|
67
145
|
The [Worker example](../examples/worker/) carries the complete deployment shape
|
|
68
|
-
and the [upgrade guide](./upgrading.md#0200--
|
|
146
|
+
and the [upgrade guide](./upgrading.md#0200--0212) gives the reversible Clerk
|
|
69
147
|
migration.
|
|
70
148
|
|
|
71
149
|
## Clerk configuration is checked at construction
|
|
@@ -102,7 +180,9 @@ secure.
|
|
|
102
180
|
Each token has an immutable ID. Activity records store that ID and resolve its
|
|
103
181
|
current friendly name only while an authorized operator reads activity.
|
|
104
182
|
Revoked records remain as metadata tombstones so historical calls keep their
|
|
105
|
-
friendly attribution.
|
|
183
|
+
friendly attribution. New tokens also retain the creating principal. Their MCP
|
|
184
|
+
requests use that principal's connector visibility and personal auth while the
|
|
185
|
+
token itself remains the activity actor and result owner.
|
|
106
186
|
|
|
107
187
|
Access tokens authenticate MCP clients; they are never operator credentials.
|
|
108
188
|
Creation, rename, and revocation require the same eligible human identity and
|
|
@@ -113,20 +193,20 @@ Issuance and revocation inherit the consistency guarantees of the configured
|
|
|
113
193
|
storage adapter. Use strongly consistent storage when either change must take
|
|
114
194
|
effect globally without a convergence window.
|
|
115
195
|
|
|
116
|
-
|
|
196
|
+
Human credential mutation is a separate, narrower boundary. The
|
|
117
197
|
`/credentials` shell contains no secret data before authentication, and the
|
|
118
|
-
mutation API requires same-origin requests from an admitted
|
|
119
|
-
|
|
120
|
-
connector.
|
|
198
|
+
mutation API requires same-origin requests from an admitted interactive human.
|
|
199
|
+
That human may mutate only visible connector slots. An MCP bearer is never
|
|
200
|
+
treated as a browser credential, even when it can call every connector.
|
|
121
201
|
|
|
122
202
|
This split is visible in recovery:
|
|
123
203
|
|
|
124
204
|
- a bearer-authenticated agent may receive `recovery: "operator_config"` and
|
|
125
205
|
pass its `operatorUrl` to a human;
|
|
126
|
-
- an interactive
|
|
206
|
+
- an interactive human with connector access opens that URL, signs in, and updates the
|
|
127
207
|
credential; and
|
|
128
208
|
- a bearer-only deployment still returns the handoff honestly, but mutation
|
|
129
|
-
remains unavailable until interactive
|
|
209
|
+
remains unavailable until interactive user auth is configured.
|
|
130
210
|
|
|
131
211
|
See [meta-tools](./meta-tools.md#authorization-recovery) for the stable recovery
|
|
132
212
|
envelope and [storage and credentials](./storage-and-credentials.md) for vault
|
|
@@ -746,7 +746,7 @@ Worker renders arguments with `String()` (so an object logs as
|
|
|
746
746
|
latter two. Only the three captured everywhere are contract (`R5`); rendering is
|
|
747
747
|
not.
|
|
748
748
|
|
|
749
|
-
**X5. Leftover authority.** QuickJS blocks imports and has no `fetch`, `process`, timers, `crypto`, or `WebSocket`. A Dynamic Worker has those globals plus a non-contract set of runtime builtins through `import()` and `process.getBuiltinModule()`, including `node:path`, `node:crypto`, `node:net`, `node:tls`, `node:dns`, `node:module`, and `cloudflare:workers`. The upstream set can drift; this list is not an allowlist.
|
|
749
|
+
**X5. Leftover authority.** QuickJS blocks imports and has no `fetch`, `process`, timers, `crypto`, or `WebSocket`. Its Node child starts with an explicitly empty process environment rather than inheriting deployment variables or `NODE_OPTIONS`. A Dynamic Worker has those globals plus a non-contract set of runtime builtins through `import()` and `process.getBuiltinModule()`, including `node:path`, `node:crypto`, `node:net`, `node:tls`, `node:dns`, `node:module`, and `cloudflare:workers`. The upstream set can drift; this list is not an allowlist.
|
|
750
750
|
The supported Worker construction is exactly `new DynamicWorkerExecutor({ loader })`. Do not pass `bindings`, `modules`, or `globalOutbound`: each can grant ambient configuration, code, or egress. Under it, `process.env`, lexical `this.env`, and `cloudflare:workers.env` are empty; `node:fs`, `node:http`, and `node:https` are unavailable through either access route; external `fetch`, `WebSocket`, `node:net`, and `node:tls` fail with workerd's outbound-denial error; DNS lookup ends unresolved; and `fetch("data:...")` resolves locally.
|
|
751
751
|
`P2` is the portable contract. Programs use none of this runtime-only authority, including timers and `crypto`, because the same code fails on QuickJS. The `execute_code` description and served `usage` skill say so before an agent writes code.
|
|
752
752
|
|
|
@@ -785,8 +785,8 @@ The human message is unchanged; a mismatched frame is ordinary untyped prose.
|
|
|
785
785
|
|
|
786
786
|
## Changes from earlier code mode
|
|
787
787
|
|
|
788
|
-
Six behaviors changed with this contract, matching the
|
|
789
|
-
|
|
788
|
+
Six behaviors changed with this contract, matching the 0.10.0 release notes.
|
|
789
|
+
Programs that ran before still run.
|
|
790
790
|
|
|
791
791
|
- **Caught Connecta failures expose their classification** (`E1`, `X11`). Their human message and thrown semantics stay unchanged; `code`, `retryable`, and `details` are additive.
|
|
792
792
|
|
|
@@ -832,7 +832,7 @@ the upstream `Executor` shape assignable.
|
|
|
832
832
|
| Clauses | Test |
|
|
833
833
|
| --- | --- |
|
|
834
834
|
| `P1`, `P5` | `test/guest-api-contract.test.ts` (TypeScript syntax), `test/quickjs-executor.test.ts` (`normalizeCode`) |
|
|
835
|
-
| `P2`, `X5` | `test/guest-api-contract.test.ts` (Dynamic globals plus loader-only filesystem, HTTP, environment, egress, DNS, and local `data:` boundaries), `test/guest-api-contract-quickjs.test.ts` (exact absent globals and blocked imports), `test/deployment-shapes.test.ts` (loader-only Worker construction) |
|
|
835
|
+
| `P2`, `X5` | `test/guest-api-contract.test.ts` (Dynamic globals plus loader-only filesystem, HTTP, environment, egress, DNS, and local `data:` boundaries), `test/guest-api-contract-quickjs.test.ts` (exact absent globals and blocked imports), `test/quickjs-child-stderr.test.ts` (empty child-process environment), `test/deployment-shapes.test.ts` (loader-only Worker construction) |
|
|
836
836
|
| `P3`, `X9` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` |
|
|
837
837
|
| `P4` | `test/guest-api-contract.test.ts` (no cross-run leakage), `test/execute.test.ts` (one catalog load per connector per execution) |
|
|
838
838
|
| `A1`, `A2` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (sanitizing), `test/server.test.ts` (bounded live connector inventory) |
|
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Connectors
|
|
2
2
|
|
|
3
|
+
Every connector may set `authScope: "shared" | "personal"`. Shared is the
|
|
4
|
+
default and keeps one deployment-wide downstream grant. Personal auth requires
|
|
5
|
+
a stable human principal and partitions connector state, credentials, OAuth,
|
|
6
|
+
catalogs, and observed shapes by that principal. Connector visibility is a
|
|
7
|
+
separate deployment rule under `identity.connectorAccess`; hiding a connector
|
|
8
|
+
does not change who owns its auth. See [shared and personal auth](./storage-and-credentials.md#shared-and-personal-auth).
|
|
9
|
+
|
|
10
|
+
`authScope` partitions connecta-owned context, not arbitrary variables captured
|
|
11
|
+
by connector code. A custom personal connector must read auth from
|
|
12
|
+
`ctx.credential` or `ctx.storage`; a secret closed over by its handler remains
|
|
13
|
+
shared JavaScript state. `remoteMcp()` rejects the equivalent mistake when
|
|
14
|
+
literal headers are combined with personal scope.
|
|
15
|
+
|
|
3
16
|
Connectors are the boundary between Connecta's fixed meta-tool surface and
|
|
4
17
|
downstream capabilities. Prefer a prebuilt connection when Connecta maintains
|
|
5
18
|
one for the provider. Use `api()` to define a deliberate HTTP API surface and
|
|
@@ -307,9 +307,10 @@ credential.
|
|
|
307
307
|
|
|
308
308
|
The tool accepts no secret. `force` applies only to OAuth and may discard its
|
|
309
309
|
stored grant before restarting consent. Static credential values are written
|
|
310
|
-
only through the same-origin
|
|
311
|
-
|
|
312
|
-
read from the vault on the next call
|
|
310
|
+
only through the same-origin interactive-user credential route, and only for a
|
|
311
|
+
connector visible to that user. After OAuth consent or a human update, retry
|
|
312
|
+
the original operation; a static update is read from the vault on the next call
|
|
313
|
+
and needs no redeploy.
|
|
313
314
|
|
|
314
315
|
## Routing recovery
|
|
315
316
|
|
|
@@ -84,6 +84,7 @@ optional.
|
|
|
84
84
|
| `connectors` | — (required) | the connector set ([connectors](./connectors.md)) |
|
|
85
85
|
| `executor` | — (required) | the sandbox `execute_code` runs in ([code mode](./code-mode.md#what-an-executor-must-implement)) |
|
|
86
86
|
| `auth?` | none ⇒ open (dev only) | one `InboundAuth` or an array; bearer providers are checked before interactive providers ([inbound auth](./auth.md)) |
|
|
87
|
+
| `identity?` | all connectors; every interactive human is an operator | `{ connectorAccess?, operatorAccess? }` derives the request's connector view and shared-auth authority from its authenticated identity ([principals](./auth.md#principals-visibility-and-operators)) |
|
|
87
88
|
| `storage?` | `memoryStorage()` | the one state seam for catalogs, result paging, credentials, and access tokens ([storage](./storage-and-credentials.md)) |
|
|
88
89
|
| `publicUrl?` | per-request origin | public base URL; an HTTPS value also redirects inbound HTTP |
|
|
89
90
|
| `logger?` | `console`, prefixed `[connecta]` | `{ debug, info, warn, error }` |
|
|
@@ -239,7 +240,7 @@ in.
|
|
|
239
240
|
| `config.test.ts` | the grouped `ConnectaConfig` boundary — each group forwarding to its internals, malformed admission bounds failing construction, and unknown own-properties rejected by their complete path before construction does work |
|
|
240
241
|
| `credentials.test.ts` | the pure stored-shape classifier (containment, not equality) and the AES-GCM vault: round-trip, ciphertext bound to its connector id, named field sets, masked metadata, wrong-key rejection, deletion, coexistence with OAuth keys |
|
|
241
242
|
| `d1-activity-example.test.ts` | the Worker example's deployment-owned D1 activity store: actor namespace round-trip, payload-free friction reconstructed from the persisted code, and agreement with the package's friction table |
|
|
242
|
-
| `downstream-oauth.test.ts` | `KvOAuthProvider` round-trips and races, `auth_required` versus `error`, `startAuth`/`finishAuth`, callback refusal equality, bounded diagnostics, and HTML escaping |
|
|
243
|
+
| `downstream-oauth.test.ts` | `KvOAuthProvider` round-trips and generation races, runtime-local rotating-token refresh coordination across request scopes, refresh failure/retry, `auth_required` versus `error`, `startAuth`/`finishAuth`, callback refusal equality, bounded diagnostics, and HTML escaping |
|
|
243
244
|
| `errors.test.ts` | `ConnectorCallError` codes, retryable defaults and overrides, `retryAfterMs` round-trip, typed-over-heuristic classification, `AbortError` as a retryable timeout, and framing errors |
|
|
244
245
|
| `execute.test.ts` | the code-mode host bridge: identifier sanitization, MCP-result unwrapping, sandbox provider construction, authenticated thrown-failure framing, fail-closed filtering of destructive and unannotated tools, MCP/code-mode invocation parity, and payload-free describe diagnostics |
|
|
245
246
|
| `execute-emit.test.ts` | `connecta.emit` (M1–M10) — block validation, budgets, the provider, delivery after the result envelope on success only, and the defaults |
|
|
@@ -247,6 +248,7 @@ in.
|
|
|
247
248
|
| `executor-admission.test.ts` | the portable bounded FIFO both pools use: active and queue ceilings, stable retryable overload, queue timeout, cancellation removal, idempotent release, shutdown |
|
|
248
249
|
| `guarded-fetch.test.ts` | the guarded transport — construction, request building, destination confinement, and response handling |
|
|
249
250
|
| `guest-api-contract.test.ts` | the shared guest contract on the Dynamic Worker, including caught call, typed inline describe recovery, discovery, utility, batch, and budget failure codes; plus the real authority boundary — local `data:` fetch, denied egress, unresolved DNS, empty environment paths, unavailable filesystem/HTTP builtins, and present runtime globals |
|
|
251
|
+
| `identity-scope.test.ts` | identity-derived connector visibility, personal credential isolation, shared-auth operator control, and personal OAuth callback ownership |
|
|
250
252
|
| `linear-provider.test.ts` | the Linear proxy's construction, guide, plan-aware catalog superset, and current workspace, template, and issue-sharing classifications |
|
|
251
253
|
| `meta-tools-call.test.ts` | registry-backed calls: structured errors, truncation and `get_result`, per-connector result bounds, JSON representation failures, MCP content bounds, and offset alignment |
|
|
252
254
|
| `meta-tools-search.test.ts` | registry-backed discovery: bounded search with page and address maxima, compact and JSON schemas with constraints, typed describe recovery and suggestions, and structured-result compatibility |
|
|
@@ -280,7 +282,7 @@ justification for *not* re-running it in workerd, so "it was easier" is not one.
|
|
|
280
282
|
|
|
281
283
|
| Suite | Covers | Why Node |
|
|
282
284
|
| --- | --- | --- |
|
|
283
|
-
| `deployment-shapes.test.ts` | the Worker as the only example with a loader-only sandbox, one Node template that is also its own container, the same source running locally and in the container, the Node template's pinned esbuild install-script approval, the full operator surface in both, a template that cannot start on its own `.env.example`, a Worker README naming every optional peer its entrypoint imports, and the initializer's `.gitignore` staying in step | walks the template and example trees with Node filesystem APIs |
|
|
285
|
+
| `deployment-shapes.test.ts` | the Worker as the only example with a loader-only sandbox, its agent instructions and setup guide pinning Claude and both ChatGPT Managed OAuth callback forms, one Node template that is also its own container, the same source running locally and in the container, the Node template's pinned esbuild install-script approval, the full operator surface in both, a template that cannot start on its own `.env.example`, a Worker README naming every optional peer its entrypoint imports, and the initializer's `.gitignore` staying in step | walks the template and example trees with Node filesystem APIs |
|
|
284
286
|
| `doc-links.test.ts` | the documentation checker itself — local file and fragment resolution, repository URLs resolved back to the checkout, duplicate heading slugs, fenced-code exclusion, and useful failures | spawns the Node checker against filesystem fixtures |
|
|
285
287
|
| `doctor-cli.test.ts` | `connecta doctor`'s executor line and credentials end to end — the sandbox the deployment reports is the one named, an unidentifiable executor gets an executor-neutral line, a hostile name is bounded, and a complete Cloudflare Access service-token pair is accepted while a partial pair is refused | spawns the CLI against a Node HTTP deployment over real sockets |
|
|
286
288
|
| `drift-check.test.ts` | the maintainer drift checker — hosted-provider credential framing, recorded touched endpoints, a quiet revision bump, clear failures for an unavailable spec/manifest/credential, `$ref` traversal, and one well-formed row per endpoint | spawns the Node checker against filesystem fixtures |
|
|
@@ -291,7 +293,7 @@ justification for *not* re-running it in workerd, so "it was easier" is not one.
|
|
|
291
293
|
| `package-surface.test.ts` | the published boundary — built output shipped, the `exports` map carrying exactly the documented subpaths plus `./package.json`, only generic factories, platform storage kept in examples, Clerk and QuickJS behind optional subpaths, dependency-free Cloudflare Access behind its Worker subpath, every provider independently importable, and the Cloudflare API provider free of bare specifiers | walks the package tree with Node filesystem APIs |
|
|
292
294
|
| `purity.test.ts` | the import-graph guardrail ([architecture](./architecture.md#import-graph-purity)) — the core stays Workers-clean | walks the source import graph with Node filesystem APIs |
|
|
293
295
|
| `quickjs-child-entry.test.ts` | a missing QuickJS child entry failing before `fork()`, with the expected path and the bundler-externalization constraint | mocks Node child-process and filesystem APIs |
|
|
294
|
-
| `quickjs-child-stderr.test.ts` |
|
|
296
|
+
| `quickjs-child-stderr.test.ts` | the QuickJS child-process boundary: an explicitly empty environment despite parent secrets and `NODE_OPTIONS`, plus abnormal exits retaining only an 8 KiB stderr tail in the parent-side diagnostic | mocks Node child-process streams |
|
|
295
297
|
| `quickjs-executor.test.ts` | the child-process sandbox — code normalization, lazy namespace proxies, bounded IPC, separate guest-CPU and wall budgets, saturation, cancellation and shutdown, crash and OOM recovery, host-call hangs, stalled-promise detection | runs the Node QuickJS child-process executor |
|
|
296
298
|
| `quickjs-log-limits.test.ts` | bounded `console.*` capture — per-entry cut, cumulative character and transport budgets, escape-heavy floods preserving the guest result | runs the Node QuickJS child-process executor |
|
|
297
299
|
| `suite-partition.test.ts` | this partition, including itself: every `*.test.ts` in exactly one list, stale entries and empty reasons refused | walks the test directory to guard the partition |
|
|
@@ -5,10 +5,10 @@ the authentication material behind it. It is a small Preact app compiled by the
|
|
|
5
5
|
repository's own esbuild step and inlined into a data-free server shell.
|
|
6
6
|
|
|
7
7
|
Read [`ethos.md`](../ethos.md) first. The boundary this subsystem lives inside
|
|
8
|
-
is the
|
|
9
|
-
|
|
10
|
-
and may
|
|
11
|
-
OAuth scopes, admission policy,
|
|
8
|
+
is the human-management invariant: **members may manage authentication material
|
|
9
|
+
for every connector their code-derived view includes, operators may also manage
|
|
10
|
+
deployment tokens and global activity, and neither may change the connector set, tool catalog,
|
|
11
|
+
annotations, requested OAuth scopes, admission policy, or identity rules.**
|
|
12
12
|
`test/operator-boundary.test.ts` proves it after every mutation route.
|
|
13
13
|
|
|
14
14
|
Both deployment shapes ship the whole feature set behind it, because pages for
|
|
@@ -52,6 +52,15 @@ server reads the resulting runtime identity. Sign out navigates to
|
|
|
52
52
|
`/cdn-cgi/access/logout`. Mutations still require an exact same-origin
|
|
53
53
|
`Origin`; an ambient cookie does not weaken the CSRF boundary.
|
|
54
54
|
|
|
55
|
+
The shell is shared by members and operators. `/ui/data` uses the same
|
|
56
|
+
identity-scoped registry view as `/mcp`, so it cannot list a connector the
|
|
57
|
+
current caller cannot discover. A member sees credential and OAuth controls for
|
|
58
|
+
every visible connector. Personal actions resolve to that member's principal
|
|
59
|
+
partition; shared actions change the deployment-wide grant. The access-token
|
|
60
|
+
and global activity pages require `identity.operatorAccess`. Existing
|
|
61
|
+
deployments that omit that resolver keep every interactive human as an
|
|
62
|
+
operator.
|
|
63
|
+
|
|
55
64
|
This runtime selection is the Clerk migration seam. A deployment may contain
|
|
56
65
|
both providers: before Worker-level Access is attached, the data-free shell
|
|
57
66
|
selects Clerk; after Access supplies `ctx.access`, it selects ambient auth. That
|
|
@@ -49,7 +49,8 @@ which is also why `/health` always has a code-admission shape to report.
|
|
|
49
49
|
|
|
50
50
|
The request pool is global FIFO across identities. It is a capacity boundary,
|
|
51
51
|
not tenant fairness: one busy caller can occupy it. Per-tenant fairness needs a
|
|
52
|
-
policy above connecta, and one deployment serves one
|
|
52
|
+
policy above connecta, and one deployment still serves one tenant even when
|
|
53
|
+
identity rules give its principals different connector views
|
|
53
54
|
([`ethos.md`](../ethos.md)), so a global queue is not pretending to supply
|
|
54
55
|
something it does not.
|
|
55
56
|
|
|
@@ -6,7 +6,7 @@ token is an independent record rather than one shared, race-prone manifest.
|
|
|
6
6
|
The built-in memory and file adapters implement it, as does the Cloudflare KV
|
|
7
7
|
example.
|
|
8
8
|
|
|
9
|
-
Connectors may declare
|
|
9
|
+
Connectors may declare a human-managed `credential` slot. When
|
|
10
10
|
`credentials.encryptionKey` is configured, Connecta encrypts values in the
|
|
11
11
|
deployment storage and exposes read-only access only through that connector's
|
|
12
12
|
`ctx.credential`. Values, masked values, call arguments, and raw errors never
|
|
@@ -27,13 +27,40 @@ Credential mutation is intentionally narrower than MCP access:
|
|
|
27
27
|
|
|
28
28
|
- a static bearer may call tools and receive the operator handoff, but it
|
|
29
29
|
cannot write credentials;
|
|
30
|
-
-
|
|
31
|
-
|
|
30
|
+
- an admitted interactive human may mutate credentials for every visible
|
|
31
|
+
connector: their own partition for personal auth, or the deployment-wide
|
|
32
|
+
value for shared auth; and
|
|
32
33
|
- saving, replacing, testing, or removing a value never returns that value.
|
|
33
34
|
|
|
34
|
-
The vault is read for each call. Once
|
|
35
|
+
The vault is read for each call. Once a signed-in human saves a replacement,
|
|
35
36
|
the agent can retry immediately without restarting or redeploying Connecta.
|
|
36
37
|
|
|
38
|
+
## Shared and personal auth
|
|
39
|
+
|
|
40
|
+
Connector auth defaults to `authScope: "shared"`. Its credential, OAuth state,
|
|
41
|
+
tokens, catalog cache, and connector storage belong to the deployment. Set
|
|
42
|
+
`authScope: "personal"` when every human principal needs a separate downstream
|
|
43
|
+
account:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
remoteMcp("linear", {
|
|
47
|
+
url: "https://mcp.linear.app/mcp",
|
|
48
|
+
authScope: "personal",
|
|
49
|
+
auth: { type: "oauth" },
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Personal connectors disappear from a request that has no stable human
|
|
54
|
+
principal. For a principal that can see one, connecta partitions connector
|
|
55
|
+
storage, encrypted vault records, catalog caches, OAuth generations, and
|
|
56
|
+
observed result shapes under an opaque SHA-256 identity key. Results used by
|
|
57
|
+
`get_result` are partitioned by the authenticated subject, so one token cannot
|
|
58
|
+
page another token's call even when both tokens belong to the same principal.
|
|
59
|
+
|
|
60
|
+
Literal `auth: { type: "headers" }` cannot be personal because its secret lives
|
|
61
|
+
in deployment code. `remoteMcp()` refuses that combination at construction.
|
|
62
|
+
Use operator-managed credential auth or OAuth instead.
|
|
63
|
+
|
|
37
64
|
## A remote MCP connector's static credential
|
|
38
65
|
|
|
39
66
|
`remoteMcp()` accepts a third auth shape beside OAuth and literal headers:
|
|
@@ -97,6 +124,17 @@ Registration and token envelopes are bound to the validated authorization
|
|
|
97
124
|
server `issuer`. An unbound pre-0.9 envelope is upgraded in place on its first
|
|
98
125
|
issuer-aware read, preserving the existing grant.
|
|
99
126
|
|
|
127
|
+
For personal OAuth, the authorization handoff also stores a 15-minute mapping
|
|
128
|
+
from a SHA-256 digest of `state` to the principal partition. The public callback
|
|
129
|
+
uses that mapping before it verifies state or exchanges the code. Neither the
|
|
130
|
+
browser nor a callback parameter can select a principal. The callback deletes
|
|
131
|
+
the mapping before it exchanges the code, so a second callback cannot replay
|
|
132
|
+
the principal handoff in strongly consistent storage. Cloudflare KV deletion
|
|
133
|
+
is eventually consistent, so handoff consumption there is best-effort across
|
|
134
|
+
PoPs; the downstream authorization code remains single-use. If the callback
|
|
135
|
+
request also carries an interactive identity, Connecta refuses it when that
|
|
136
|
+
principal did not start the flow.
|
|
137
|
+
|
|
100
138
|
If later discovery resolves a different issuer, Connecta does not send the old
|
|
101
139
|
client identifier or tokens to it. The provider publishes a new generation
|
|
102
140
|
epoch, makes every older credential namespace unreadable, cleans up the retired
|
|
@@ -107,3 +145,38 @@ the retired grant.
|
|
|
107
145
|
The OAuth callback verifies the one-shot `state` first, then hands the complete
|
|
108
146
|
query string—including RFC 9207 `iss`—to the SDK transport. One-shot state,
|
|
109
147
|
verifier, and pending URL are cleared only after a successful exchange.
|
|
148
|
+
|
|
149
|
+
Within one `remoteMcp()` runtime, one request scope owns refresh-token
|
|
150
|
+
redemption for an OAuth generation. Concurrent scopes wait for the owner's
|
|
151
|
+
token save or bounded failure, then either read storage again or receive that
|
|
152
|
+
failure. A scope that had already read the retired refresh token reuses the
|
|
153
|
+
newly stored rotating token locally instead of sending the retired value
|
|
154
|
+
upstream. Force reauthorization retires the old generation's gate, and a
|
|
155
|
+
failed flow releases ownership for a later attempt. The coordinator retains
|
|
156
|
+
only a completion signal and one temporary owner-abort listener until that
|
|
157
|
+
exact flight settles, never the token response or downstream transport. A
|
|
158
|
+
follower may stop waiting when its own request is cancelled without cancelling
|
|
159
|
+
the owner or poisoning the generation for later callers. If the owner's
|
|
160
|
+
credential mutation fails, joined callers receive that same bounded failure
|
|
161
|
+
instead of waking to redeem the unchanged token; a later independent call may
|
|
162
|
+
retry. Non-success and malformed token responses settle current waiters at the
|
|
163
|
+
fetch boundary, before any later authorization callback can itself fail.
|
|
164
|
+
Cancelling the owner aborts its fetch and fails current joiners rather than
|
|
165
|
+
promoting one: once a request reaches the authorization server, repeating its
|
|
166
|
+
old refresh token is not known to be safe. If that cancellation lands while
|
|
167
|
+
the valid response's credential write is already running, a same-generation
|
|
168
|
+
attempt receives `temporarily_unavailable` until the exact write succeeds or
|
|
169
|
+
fails. This mutation marker contains no retained promise; force
|
|
170
|
+
reauthorization removes it when the old generation becomes unreadable.
|
|
171
|
+
An additional opaque success identity lets a request recognize a refresh that
|
|
172
|
+
completed after its issuer-aware token read even when the authorization server
|
|
173
|
+
returned byte-identical credentials. The identity is generation-scoped and is
|
|
174
|
+
discarded with the retired generation. Every authoritative storage-generation
|
|
175
|
+
read also retires coordinator state from other epochs, so an externally
|
|
176
|
+
advanced generation cannot be overwritten in runtime state by late old work.
|
|
177
|
+
|
|
178
|
+
This guarantee is runtime-local. `KVStorage` has no atomic lock or
|
|
179
|
+
compare-and-set operation, so separate processes or Worker isolates can still
|
|
180
|
+
redeem the same refresh token concurrently. Generation envelopes continue to
|
|
181
|
+
fence their writes, but Connecta does not claim cross-isolate exactly-once
|
|
182
|
+
refresh.
|
|
@@ -57,7 +57,7 @@ exist so far:
|
|
|
57
57
|
| --- | --- | --- |
|
|
58
58
|
| **pre-template** | before 0.10.2 | no `connecta init` existed; hand-written, or copied from the retired `examples/node` |
|
|
59
59
|
| **A** | 0.10.2 – 0.15.1 | `.env.example`, `.gitignore`, `AGENTS.md`, `CLAUDE.md`, `README.md`, `package.json`, `src/index.ts`, `tsconfig.json` |
|
|
60
|
-
| **B** | 0.16.0 – 0.
|
|
60
|
+
| **B** | 0.16.0 – 0.22.0 | adds `.dockerignore`, `Dockerfile`, `docker-compose.yml`, and `src/file-activity.ts`; `src/index.ts` grows the four commented operator blocks; `.env.example` ships `CONNECTA_TOKEN=` empty |
|
|
61
61
|
|
|
62
62
|
Generation A is a decade in template years and identifying it precisely does
|
|
63
63
|
not matter, because you are about to reconstruct it exactly rather than guess
|
|
@@ -106,7 +106,7 @@ know what to preserve, once to know what to re-verify at the end.
|
|
|
106
106
|
### Bump the pin and install
|
|
107
107
|
|
|
108
108
|
```sh
|
|
109
|
-
npm pkg set dependencies.@zackbart/connecta=0.
|
|
109
|
+
npm pkg set dependencies.@zackbart/connecta=0.22.0
|
|
110
110
|
npm install
|
|
111
111
|
```
|
|
112
112
|
|
|
@@ -130,7 +130,7 @@ Generate the *current* template beside the base you already made, into the same
|
|
|
130
130
|
`$SCRATCH`:
|
|
131
131
|
|
|
132
132
|
```sh
|
|
133
|
-
(cd "$SCRATCH" && npx @zackbart/connecta@0.
|
|
133
|
+
(cd "$SCRATCH" && npx @zackbart/connecta@0.22.0 init current)
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
You now have a three-way merge with a real base: `$SCRATCH/base` is what this
|
|
@@ -186,7 +186,7 @@ A deployment older than 0.10.2 has no base to diff against. Do not try to
|
|
|
186
186
|
manufacture one. Instead:
|
|
187
187
|
|
|
188
188
|
1. `SCRATCH=$(mktemp -d)`, then
|
|
189
|
-
`(cd "$SCRATCH" && npx @zackbart/connecta@0.
|
|
189
|
+
`(cd "$SCRATCH" && npx @zackbart/connecta@0.22.0 init current)` — there is no
|
|
190
190
|
`base` leg here, only the current template to read from.
|
|
191
191
|
2. Copy `$SCRATCH/current` into the deployment file by file, **skipping
|
|
192
192
|
`src/index.ts`**.
|
|
@@ -207,9 +207,25 @@ first, so cross them bottom-up: start at the oldest one still above this
|
|
|
207
207
|
deployment's pin and work back up the page, because each boundary assumes the
|
|
208
208
|
older ones are already done.
|
|
209
209
|
|
|
210
|
-
### 0.
|
|
210
|
+
### 0.21.2 → 0.22.0
|
|
211
211
|
|
|
212
|
-
|
|
212
|
+
Connector and user policy remain config-as-code. If `identity.connectorAccess`
|
|
213
|
+
is configured, every interactive human may now manage the authentication of
|
|
214
|
+
each connector that resolver makes visible. A personal connector changes only
|
|
215
|
+
that principal's partition; a shared connector changes the deployment-wide
|
|
216
|
+
grant. Keep shared connectors out of a member's view, or change them to
|
|
217
|
+
`authScope: "personal"`, when that member must not rotate the shared grant.
|
|
218
|
+
`identity.operatorAccess` continues to govern deployment access tokens and
|
|
219
|
+
global activity.
|
|
220
|
+
|
|
221
|
+
Paged results also move under the authenticated subject's storage partition.
|
|
222
|
+
Finish any important in-flight `get_result` sequence before upgrading; its old
|
|
223
|
+
result id is not readable from the new partition after deployment. No persisted
|
|
224
|
+
connector catalog or credential migration is required.
|
|
225
|
+
|
|
226
|
+
### 0.20.0 → 0.21.2
|
|
227
|
+
|
|
228
|
+
0.21.2 adds no deployment migration beyond 0.21.0. The boundary is additive
|
|
213
229
|
for Node and existing Clerk deployments. The new Worker path
|
|
214
230
|
uses Cloudflare Access identity directly and removes Clerk only after the edge
|
|
215
231
|
cutover has been verified. An agent can perform every repository edit; a human
|
|
@@ -218,7 +234,7 @@ Managed OAuth in the Cloudflare dashboard.
|
|
|
218
234
|
|
|
219
235
|
For a Worker currently using Clerk, keep rollback live through the cutover:
|
|
220
236
|
|
|
221
|
-
1. Bump and install 0.21.
|
|
237
|
+
1. Bump and install 0.21.2. Add the new provider **before** the existing Clerk
|
|
222
238
|
provider, but remove nothing:
|
|
223
239
|
|
|
224
240
|
```ts
|
|
@@ -244,6 +260,21 @@ For a Worker currently using Clerk, keep rollback live through the cutover:
|
|
|
244
260
|
tag>" }`, not a hostname application for the `workers.dev` URL: the latter
|
|
245
261
|
gates traffic but does not provide `ctx.access`. Create an Access service
|
|
246
262
|
token and a **Service Auth** policy for doctor and fully unattended clients.
|
|
263
|
+
In the application's Managed OAuth settings, enable Dynamic Client
|
|
264
|
+
Registration and add these three **Allowed redirect URIs**:
|
|
265
|
+
|
|
266
|
+
```text
|
|
267
|
+
https://claude.ai/api/mcp/auth_callback
|
|
268
|
+
https://chatgpt.com/connector_platform_oauth_redirect
|
|
269
|
+
https://chatgpt.com/connector/oauth/*
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
They map to
|
|
273
|
+
`oauth_configuration.dynamic_client_registration.allowed_uris` in the
|
|
274
|
+
Access API, not to the identity policy. The two ChatGPT entries cover its
|
|
275
|
+
stable and callback-id forms. An empty list fails client registration only
|
|
276
|
+
after discovery, so do not treat a working `/.well-known/*` response as
|
|
277
|
+
proof that this step is complete.
|
|
247
278
|
Do not create a bypass for `/.well-known/*`; Managed OAuth owns that
|
|
248
279
|
discovery surface.
|
|
249
280
|
|
package/ethos.md
CHANGED
|
@@ -8,8 +8,8 @@ preserve. A contradiction needs a design decision, not a drive-by edit.
|
|
|
8
8
|
- **One MCP endpoint, one programmable surface.** Every integration you chose
|
|
9
9
|
sits behind a capability catalog that agents reach by writing JavaScript,
|
|
10
10
|
ringed by a few explicit tools for the boundaries code must not cross.
|
|
11
|
-
- **A deployment is
|
|
12
|
-
|
|
11
|
+
- **A deployment is config-as-code.** One tenant and connector set; principals
|
|
12
|
+
receive config-derived views.
|
|
13
13
|
- **Curated when available, open when not.** Prefer a maintained prebuilt
|
|
14
14
|
connection; `remoteMcp()` and `api()` stay first-class for everything else.
|
|
15
15
|
Every path yields the same `Connector` with the same rules.
|
|
@@ -30,8 +30,8 @@ preserve. A contradiction needs a design decision, not a drive-by edit.
|
|
|
30
30
|
- **Not a platform.** No runtime registration, admin-editable capability,
|
|
31
31
|
policy engine, approvals, or pauses.
|
|
32
32
|
- **Not a schema ingester.** No OpenAPI or GraphQL → tools.
|
|
33
|
-
- **Not multi-tenant.** No
|
|
34
|
-
|
|
33
|
+
- **Not multi-tenant.** No accounts, groups, or sessions. Inbound auth owns
|
|
34
|
+
identity; personal state stays within one tenant.
|
|
35
35
|
- **Not stateful.** No protocol sessions, no server push; scope resolves per
|
|
36
36
|
request.
|
|
37
37
|
- **Not a nanny.** Credentials fail loudly at use; nothing probes one.
|
|
@@ -47,7 +47,7 @@ CHANGELOG, not here.
|
|
|
47
47
|
| Decision | Verdict | Why |
|
|
48
48
|
| --- | --- | --- |
|
|
49
49
|
| OpenAPI / GraphQL ingestion | refused | the disease is a tool nobody chose — a document authored it; hand-written literals, even through a shared factory, are still authorship |
|
|
50
|
-
| Multi-tenancy / account model | refused | one deployment per tenant;
|
|
50
|
+
| Multi-tenancy / account model | refused | one deployment per tenant; inbound auth owns identity |
|
|
51
51
|
| Policy engine, approvals, pauses | refused | the host asks the human; connecta only annotates |
|
|
52
52
|
| Runtime connector registration | refused | config-as-code is the security model |
|
|
53
53
|
| Provider registry / marketplace | refused | prebuilt connections are imports; discovery happens in docs ([#297](https://github.com/zackbart/connecta/issues/297)) |
|
|
@@ -67,7 +67,7 @@ CHANGELOG, not here.
|
|
|
67
67
|
| Legacy embedded `UIResource` delivery | refused | superseded upstream, rendered by no client we face ([#266](https://github.com/zackbart/connecta/issues/266)) |
|
|
68
68
|
| Effect as the core effect system | refused | −4% of the core for +75 KB gzip and a second async paradigm; re-measure at v4 stable ([#470](https://github.com/zackbart/connecta/issues/470)) |
|
|
69
69
|
| Shared bounded queue under both admission controllers | refused | built and measured −17 lines for a hook-parameterised abstraction ([#453](https://github.com/zackbart/connecta/issues/453)) |
|
|
70
|
-
|
|
|
70
|
+
| Caller-selected toolkits | removed | only config may derive an identity's connector view ([#178](https://github.com/zackbart/connecta/issues/178)) |
|
|
71
71
|
| Proactive credential liveness | removed | fail-at-use is enough ([#179](https://github.com/zackbart/connecta/issues/179)) |
|
|
72
72
|
| Classic (executor-free) surface | removed | an executor is mandatory ([#273](https://github.com/zackbart/connecta/issues/273)) |
|
|
73
73
|
| Per-result lexical query coverage | removed | did not earn its response bytes in a precommitted gate ([#323](https://github.com/zackbart/connecta/issues/323)) |
|
|
@@ -91,10 +91,10 @@ Breaking one is a design change wearing a disguise.
|
|
|
91
91
|
- **A downstream catalog is complete or it is a failure.** A partial catalog is never cached, persisted, or served.
|
|
92
92
|
- **Activity is payload-free by construction.** The event type has nowhere to put arguments, results, code, or raw errors.
|
|
93
93
|
- **An observed shape is never a declaration.** Names and broad types only, labeled, and gone behind any declared schema.
|
|
94
|
-
- **Credentials never leave the host.** Encrypted at rest, readable only by the owning connector, rendered by nothing.
|
|
94
|
+
- **Credentials never leave the host.** Encrypted at rest, readable only by the owning connector and, for personal auth, its owning principal; rendered by nothing.
|
|
95
95
|
- **Import-graph purity.** Nothing reachable from the root entry imports a `node:` builtin.
|
|
96
96
|
- **The published surface is a boundary.** Heavyweight or platform-bound code goes behind an optional-peer subpath.
|
|
97
|
-
- **
|
|
97
|
+
- **Human routes manage auth, never capability.** Signed-in humans manage auth for visible connectors; operators also manage tokens and global activity.
|
|
98
98
|
- **Structural mistakes throw at construction.** Booting into the wrong shape is worse than not booting.
|
|
99
99
|
|
|
100
100
|
Connecta began as a radical simplification of
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Working on this Connecta Worker deployment
|
|
2
|
+
|
|
3
|
+
This repository is deployment configuration, not a copy of Connecta itself.
|
|
4
|
+
|
|
5
|
+
- Edit `src/index.ts` for connectors, authentication, storage, and public URL.
|
|
6
|
+
- Keep `cloudflareAccessAuth()` as the inbound auth provider. Cloudflare Access
|
|
7
|
+
authenticates the request before the Worker runs; do not add JWT parsing or a
|
|
8
|
+
second Worker-side identity gate.
|
|
9
|
+
- Attach Access to the Worker itself, not only its hostname. Enable Managed
|
|
10
|
+
OAuth and Dynamic Client Registration on that Access application.
|
|
11
|
+
- Managed OAuth's **Allowed redirect URIs** must contain all three entries
|
|
12
|
+
below. This is application configuration under
|
|
13
|
+
`oauth_configuration.dynamic_client_registration.allowed_uris`, not an
|
|
14
|
+
Access Allow policy:
|
|
15
|
+
|
|
16
|
+
```text
|
|
17
|
+
https://claude.ai/api/mcp/auth_callback
|
|
18
|
+
https://chatgpt.com/connector_platform_oauth_redirect
|
|
19
|
+
https://chatgpt.com/connector/oauth/*
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The first is Claude's hosted MCP callback. The two ChatGPT entries cover its
|
|
23
|
+
stable callback and its callback-id form. An empty allowlist lets Access
|
|
24
|
+
discovery work but makes client registration fail with `redirect_uri` not
|
|
25
|
+
allowed. If a client presents a different callback, copy that exact URI from
|
|
26
|
+
its registration attempt and add the narrowest matching entry rather than
|
|
27
|
+
broadening the allowlist to an entire origin.
|
|
28
|
+
- Keep `new DynamicWorkerExecutor({ loader: env.LOADER })` loader-only. Do not
|
|
29
|
+
add bindings, modules, or outbound access to generated code.
|
|
30
|
+
- Keep credentials in Worker secrets. Never commit credential values, Access
|
|
31
|
+
service-token secrets, or `CREDENTIAL_ENCRYPTION_KEY`.
|
|
32
|
+
- Add application logic only inside deliberate `api()` connector handlers.
|
|
33
|
+
Do not copy or modify Connecta package internals here.
|
|
34
|
+
- Prefer `api()` when the agent must see an exact reviewed capability set;
|
|
35
|
+
`remoteMcp()` follows the downstream server's evolving tool catalog.
|
|
36
|
+
- Use Access service credentials for `connecta doctor` and unattended clients.
|
|
37
|
+
A `cta_` token or static Connecta bearer cannot cross the Access edge alone.
|
|
38
|
+
- Run the repository's `npm run check:examples` after configuration changes.
|
|
39
|
+
After deployment, connect both Claude and ChatGPT to `<PUBLIC_URL>/mcp` and
|
|
40
|
+
complete their browser authorization flows before calling setup complete.
|
|
41
|
+
|
|
42
|
+
Do not add alternate entrypoints, policy layers, generated connector catalogs,
|
|
43
|
+
or runtime connector registration. Keep the deployment small enough to review
|
|
44
|
+
as configuration.
|