@zackbart/connecta 0.24.2 → 0.24.4
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 +18 -20
- package/CHANGELOG.md +205 -1
- package/README.md +5 -6
- package/dist/auth/bearer.js +2 -0
- package/dist/auth/downstream-oauth.d.ts +12 -1
- package/dist/auth/downstream-oauth.js +147 -35
- package/dist/branding.d.ts +31 -2
- package/dist/branding.js +116 -8
- package/dist/call-admission.d.ts +4 -0
- package/dist/call-admission.js +26 -0
- package/dist/catalog-drift.js +9 -4
- package/dist/catalog-service.d.ts +2 -0
- package/dist/catalog-service.js +25 -8
- package/dist/catalog.d.ts +2 -0
- package/dist/catalog.js +246 -121
- package/dist/connectors/api.d.ts +1 -1
- package/dist/connectors/api.js +21 -3
- package/dist/connectors/guarded-fetch.d.ts +6 -2
- package/dist/connectors/guarded-fetch.js +61 -24
- package/dist/connectors/remote-mcp.js +92 -57
- package/dist/errors.d.ts +28 -3
- package/dist/errors.js +60 -1
- package/dist/execute.js +85 -23
- package/dist/executor-result.js +3 -1
- package/dist/executors/quickjs-child.js +5 -1
- package/dist/executors/quickjs-protocol.d.ts +4 -0
- package/dist/executors/quickjs-runtime.d.ts +1 -1
- package/dist/executors/quickjs-runtime.js +38 -21
- package/dist/executors/quickjs.js +68 -27
- package/dist/index.d.ts +15 -1
- package/dist/index.js +36 -4
- package/dist/invocation.js +134 -93
- package/dist/mcp-result.js +3 -2
- package/dist/meta-tools.js +191 -36
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/view.d.ts +38 -1
- package/dist/operator-ui/view.js +71 -0
- package/dist/providers/cloudflare.d.ts +14 -2
- package/dist/providers/cloudflare.js +107 -16
- package/dist/providers/linear.d.ts +26 -4
- package/dist/providers/linear.js +19 -4
- package/dist/providers/mixpanel.d.ts +16 -3
- package/dist/providers/mixpanel.js +13 -2
- package/dist/providers/notion.d.ts +8 -1
- package/dist/providers/notion.js +83 -10
- package/dist/providers/revenuecat.d.ts +30 -4
- package/dist/providers/revenuecat.js +42 -4
- package/dist/providers/stripe.d.ts +7 -1
- package/dist/providers/stripe.js +30 -4
- package/dist/providers/vercel.js +11 -1
- package/dist/registry.d.ts +22 -2
- package/dist/registry.js +101 -13
- package/dist/routes/mcp.d.ts +4 -1
- package/dist/routes/mcp.js +84 -13
- package/dist/routes/oauth.js +4 -0
- package/dist/routes/shared.d.ts +1 -0
- package/dist/routes/shared.js +4 -4
- package/dist/server.js +15 -3
- package/dist/skills.js +6 -5
- package/dist/storage/file.d.ts +6 -2
- package/dist/storage/file.js +312 -34
- package/dist/storage/memory.js +12 -1
- package/dist/types.d.ts +37 -0
- package/dist/ui.js +18 -10
- package/dist/validate.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +193 -165
- package/documentation/auth.md +199 -145
- package/documentation/code-mode.md +433 -316
- package/documentation/meta-tools.md +363 -355
- package/examples/worker/AGENTS.md +2 -1
- package/examples/worker/README.md +12 -10
- package/examples/worker/src/index.ts +12 -15
- package/package.json +1 -2
- package/templates/node/.env.example +3 -3
- package/templates/node/AGENTS.md +5 -4
- package/templates/node/README.md +2 -1
- package/templates/node/package.json +1 -1
- package/templates/node/src/index.ts +23 -22
- package/documentation/call-admission.md +0 -142
- package/documentation/cloudflare.md +0 -471
- package/documentation/connector-guides.md +0 -176
- package/documentation/connectors.md +0 -389
- package/documentation/linear.md +0 -193
- package/documentation/mixpanel.md +0 -160
- package/documentation/notion.md +0 -308
- package/documentation/operations.md +0 -359
- package/documentation/operator-ui.md +0 -135
- package/documentation/optional-modules-upgrade.md +0 -243
- package/documentation/provider-conventions.md +0 -722
- package/documentation/request-admission.md +0 -170
- package/documentation/revenuecat.md +0 -305
- package/documentation/storage-and-credentials.md +0 -201
- package/documentation/stripe.md +0 -262
- package/documentation/upgrading.md +0 -754
- package/documentation/vercel.md +0 -241
package/documentation/auth.md
CHANGED
|
@@ -1,43 +1,87 @@
|
|
|
1
1
|
# Inbound auth
|
|
2
2
|
|
|
3
|
-
Inbound auth decides who may reach the MCP endpoint. Import
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
Inbound auth decides who may reach the MCP endpoint. Import `bearerToken` from
|
|
4
|
+
`@zackbart/connecta/auth/bearer`, `clerkAuth` from `/auth/clerk`, or
|
|
5
|
+
`cloudflareAccessAuth` from `/auth/cloudflare-access`. Providers may be combined;
|
|
6
6
|
static bearers are checked first, then other providers in configuration order.
|
|
7
|
-
|
|
7
|
+
An `InboundAuth` provider's `authorize(request, baseUrl, runtimeContext)`
|
|
8
|
+
returns either `{ ok: true, userId?, subjectId?, principal? }` or a refusal
|
|
9
|
+
carrying its own `Response`, so the provider owns its challenge. Connecta
|
|
10
|
+
issues no tokens of its own and serves no token-management routes.
|
|
11
|
+
|
|
12
|
+
The bearer adapter challenges with `WWW-Authenticate: Bearer` and deliberately
|
|
13
|
+
omits `resource_metadata`: its credential is configured out of band, so it has no
|
|
14
|
+
authorization server or registration endpoint to advertise. Interactive adapters
|
|
15
|
+
or the edge own OAuth discovery. An open deployment with any connector warns at
|
|
16
|
+
construction — including API connectors carrying static auth headers, and with
|
|
17
|
+
sharper wording for credential and OAuth connectors.
|
|
18
|
+
|
|
19
|
+
## Origins
|
|
20
|
+
|
|
21
|
+
`allowedOrigins?: readonly string[] | "*"` bounds which browsers may speak to
|
|
22
|
+
the MCP endpoint. A disallowed `Origin` on `/mcp` or any `/mcp/<pool>` gets a
|
|
23
|
+
fixed 403 before HTTPS redirects, admission, auth, and CORS preflight — the
|
|
24
|
+
check cannot depend on anything a caller has yet proved. The default admits the
|
|
25
|
+
`publicUrl` origin plus HTTP(S) loopback at any port; a supplied list replaces
|
|
26
|
+
that default rather than extending it, and `"*"` waives the check. Requests with
|
|
27
|
+
no `Origin` pass, since a non-browser client is not the threat here, and an
|
|
28
|
+
entry that is not an exact HTTP(S) origin throws at construction instead of
|
|
29
|
+
silently never matching. A permitted origin still has to authenticate.
|
|
8
30
|
|
|
9
31
|
## Principals, visibility, and operators
|
|
10
32
|
|
|
11
|
-
The actor identifies the caller in
|
|
12
|
-
such as `get_result` pages
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
`
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
One authorization yields three roles. The **actor** identifies the caller in
|
|
34
|
+
activity, the **subject** owns transient results such as `get_result` pages, and
|
|
35
|
+
the **principal** is the human owner of personal connector auth. An interactive
|
|
36
|
+
Clerk or Access user supplies all three; a Cloudflare service identity has an
|
|
37
|
+
actor and subject but no principal.
|
|
38
|
+
|
|
39
|
+
The principal is whichever comes first: an explicit `principal: { namespace, id }`
|
|
40
|
+
returned by `authorize`, accepted whenever it validates, or else one derived from
|
|
41
|
+
`userId` plus the provider's `activityActorNamespace`. A provider that returns its
|
|
42
|
+
own principal therefore needs no namespace declared; one relying on the derived
|
|
43
|
+
path does.
|
|
44
|
+
|
|
45
|
+
A subject or user id always selects a result-stash partition, even with no
|
|
46
|
+
`activityActorNamespace` declared — the namespace is then
|
|
47
|
+
`connecta:auth:<provider kind>`, and subject ids must be distinct within it. That
|
|
48
|
+
fallback grants no personal-auth ownership and changes no activity attribution. A
|
|
49
|
+
provider supplying only an explicit principal uses it as the subject too. Open
|
|
50
|
+
deployments and providers that return no identity share one result partition.
|
|
51
|
+
|
|
52
|
+
`identity.connectorAccess` returns `"all"` — the default — or a list of grants:
|
|
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.
|
|
56
|
+
|
|
57
|
+
Tool grants are enforced in the scoped registry view, below the catalog service,
|
|
58
|
+
so `search_tools`, both call tools, a program's `connecta.search`,
|
|
59
|
+
`connecta.describe`, and `connecta.call`, and the connection UI all read the same
|
|
60
|
+
filtered list. Connector-level discovery, guides, and `authorize_connector` keep
|
|
61
|
+
a connector when any tool on it is granted, so a `docs.read` grant permits the
|
|
62
|
+
`docs` authorization handoff. Without any grant on `docs`, `authorize_connector`
|
|
63
|
+
returns the same "Unknown connector" refusal as an absent connector, and an
|
|
64
|
+
ungranted tool fails exactly like one the connector never had: `unknown_tool`,
|
|
65
|
+
with no hint that it exists. That is the whole security claim, and it lives in
|
|
66
|
+
one place on purpose.
|
|
67
|
+
|
|
68
|
+
There is no *caller-selected* tool set. A narrower slice is a branch in this
|
|
69
|
+
resolver or a config-declared pool; a bot that needs its own slice is its own
|
|
70
|
+
bearer subject. What a request may never do is name its own scope.
|
|
30
71
|
|
|
31
72
|
## Pools
|
|
32
73
|
|
|
33
74
|
A pool is a named slice of the deployment served at its own endpoint,
|
|
34
|
-
`/mcp/<pool>`, for
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
catalog cache.
|
|
75
|
+
`/mcp/<pool>`, for when one identity needs different capability sets on different
|
|
76
|
+
clients: a support agent that sees three Notion tools and Linear, a calendar bot
|
|
77
|
+
that sees one tool, both over the same credentials and catalog cache.
|
|
38
78
|
|
|
39
79
|
```ts
|
|
40
80
|
createConnecta({
|
|
81
|
+
auth: [
|
|
82
|
+
bearerToken(botSecret, { subjectId: "calendar-bot" }),
|
|
83
|
+
clerkAuth({ publishableKey, secretKey }),
|
|
84
|
+
],
|
|
41
85
|
pools: {
|
|
42
86
|
support: {
|
|
43
87
|
tools: ["linear", "notion.search_pages", "notion.fetch_page"],
|
|
@@ -54,53 +98,76 @@ createConnecta({
|
|
|
54
98
|
});
|
|
55
99
|
```
|
|
56
100
|
|
|
57
|
-
|
|
101
|
+
Pools are meaningless without configured `auth`: an open deployment builds one
|
|
102
|
+
anonymous, non-interactive identity, so grants like these evaluate false and
|
|
103
|
+
every pool path 404s. The rules, each of which is a test:
|
|
58
104
|
|
|
59
105
|
- **A pool narrows; it never widens.** The view on `/mcp/<pool>` is the pool
|
|
60
106
|
intersected with the identity's own `connectorAccess`. Plain `/mcp` is
|
|
61
|
-
unchanged. The security boundary is still the resolver; the pool decides
|
|
62
|
-
|
|
107
|
+
unchanged. The security boundary is still the resolver; the pool decides which
|
|
108
|
+
part of it a given client sees.
|
|
63
109
|
- **Grant defaults to deny.** A pool with no `grant` serves nobody. Only a
|
|
64
|
-
literal `true` admits; any other return, a throw, and an undeclared pool
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
declared
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
110
|
+
literal `true` admits; any other return, a throw, and an undeclared pool name
|
|
111
|
+
produce one 404 identical in status, body, and headers, reached only after auth
|
|
112
|
+
succeeds — so pool names are not anonymously enumerable, and a valid credential
|
|
113
|
+
cannot tell the three cases apart by response content. Timing is not hidden: a
|
|
114
|
+
declared name awaits its grant while an undeclared name returns without that
|
|
115
|
+
lookup. We accept that oracle because names grant no access and a fixed delay
|
|
116
|
+
could not hide unbounded grant I/O anyway. Keep grants pure and fast; don't
|
|
117
|
+
treat pool names as secrets. The operator log carries the refusal reason.
|
|
118
|
+
- **Structural mistakes throw at construction.** A malformed name, an unknown
|
|
119
|
+
option, an unknown connector, an empty pool, and a `connector.tool` address an
|
|
120
|
+
`api()` connector's static catalog lacks all refuse to boot. Remote catalogs
|
|
121
|
+
load lazily, so their addresses are checked at load instead.
|
|
75
122
|
- **OAuth discovery follows the path.** On Clerk, the 401 challenge for
|
|
76
|
-
`/mcp/<pool>` names `/.well-known/oauth-protected-resource/mcp/<pool>`,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
123
|
+
`/mcp/<pool>` names `/.well-known/oauth-protected-resource/mcp/<pool>`, whose
|
|
124
|
+
`resource` is the pool URL, so RFC 9728 clients see a match. Cloudflare
|
|
125
|
+
Managed OAuth is application-level and needs nothing.
|
|
126
|
+
|
|
127
|
+
An address the live catalog does not contain is unreachable and warned once
|
|
128
|
+
while it sits in a 1,024-entry FIFO, so caller-derived grant text cannot grow
|
|
129
|
+
retained warning state without bound. Later catalog drift can never widen a
|
|
130
|
+
grant: there is no wildcard, and every tool grant is an exact name.
|
|
131
|
+
|
|
132
|
+
## Shared and personal auth
|
|
133
|
+
|
|
134
|
+
Connector auth defaults to `authScope: "shared"` — its credential, OAuth state,
|
|
135
|
+
tokens, catalog cache, and connector storage belong to the deployment. Set
|
|
136
|
+
`authScope: "personal"` when every human needs a separate downstream account:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
remoteMcp("linear", { url: "https://mcp.linear.app/mcp", authScope: "personal",
|
|
140
|
+
auth: { type: "oauth" } });
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
A personal connector is absent — not refused — from any request without a
|
|
144
|
+
stable namespaced principal. For a principal that can see one, connecta
|
|
145
|
+
partitions connector storage, vault records, catalog caches, OAuth generations,
|
|
146
|
+
and observed result shapes under an opaque SHA-256 identity key. Keep namespaces
|
|
147
|
+
and principal ids stable across upgrades; changing either selects different
|
|
148
|
+
partitions. Literal `auth: { type: "headers" }` cannot be personal, because its
|
|
149
|
+
secret lives in deployment code; `remoteMcp()` refuses that combination at
|
|
150
|
+
construction.
|
|
151
|
+
|
|
152
|
+
## Management permissions
|
|
153
|
+
|
|
154
|
+
Visibility alone grants no authentication-management permission. Two independent
|
|
155
|
+
resolvers take `Readonly<AuthenticatedIdentity>`, return `"all"`, `"none"`, or
|
|
156
|
+
declared connector ids, and both default to `"none"`:
|
|
157
|
+
`credentialAdministration` for shared credentials and shared OAuth grants, and
|
|
158
|
+
`personalConnection` for a human's own grants on personal connectors. Each action
|
|
159
|
+
needs visibility *and* the relevant permission; both resolvers run only for an
|
|
160
|
+
interactive identity; personal actions additionally need a stable namespaced
|
|
161
|
+
principal and always use that principal's partition. Resolver exceptions and
|
|
162
|
+
unknown ids fail closed, and permissions come from authenticated identity, never
|
|
163
|
+
from caller input.
|
|
164
|
+
|
|
165
|
+
`identity.activityAccess` takes `Readonly<IdentityReference>` — `id` and
|
|
166
|
+
`namespace` — and controls reading global activity. Undeclared, it admits every
|
|
167
|
+
interactive human, the one default here that is open, because a single-operator
|
|
168
|
+
deployment would otherwise be locked out of its own event stream. Team
|
|
169
|
+
deployments should set it. There is no general administrator role and no
|
|
170
|
+
token-management authority.
|
|
104
171
|
|
|
105
172
|
```ts
|
|
106
173
|
createConnecta({
|
|
@@ -122,56 +189,47 @@ createConnecta({
|
|
|
122
189
|
});
|
|
123
190
|
```
|
|
124
191
|
|
|
125
|
-
Built-in Clerk and Access providers supply identity namespaces. A custom
|
|
126
|
-
interactive provider must set `activityActorNamespace` before its users can
|
|
127
|
-
own personal auth. Keep the namespace and principal ids stable across upgrades;
|
|
128
|
-
changing them selects different personal storage partitions.
|
|
129
|
-
|
|
130
192
|
## Cloudflare Access on Workers
|
|
131
193
|
|
|
132
194
|
[`cloudflareAccessAuth()`](https://developers.cloudflare.com/workers/configuration/cloudflare-access/)
|
|
133
|
-
is the Worker-specific path
|
|
195
|
+
is the Worker-specific path. `ethos.md` records it as **provisional**: Managed
|
|
196
|
+
OAuth and the Clerk migration still want production evidence
|
|
197
|
+
([#506](https://github.com/zackbart/connecta/issues/506)).
|
|
134
198
|
|
|
135
199
|
```ts
|
|
136
200
|
import { cloudflareAccessAuth } from
|
|
137
201
|
"@zackbart/connecta/auth/cloudflare-access";
|
|
138
202
|
|
|
139
|
-
createConnecta({
|
|
140
|
-
auth: cloudflareAccessAuth(),
|
|
141
|
-
connectors,
|
|
142
|
-
executor,
|
|
143
|
-
});
|
|
203
|
+
createConnecta({ auth: cloudflareAccessAuth(), connectors, executor });
|
|
144
204
|
```
|
|
145
205
|
|
|
146
206
|
The adapter trusts only `ctx.access`, which Cloudflare creates after Access has
|
|
147
|
-
authenticated a request that directly invokes the Worker
|
|
148
|
-
`ctx.access.getIdentity()
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
207
|
+
authenticated a request that directly invokes the Worker, and it reads identity
|
|
208
|
+
through `ctx.access.getIdentity()`. A human yields `user_uuid` or `email` as both
|
|
209
|
+
user and subject. A *service* identity yields `service_token_id`, or failing that
|
|
210
|
+
`common_name`, so distinct service tokens normally get distinct attribution;
|
|
211
|
+
either identity kind with no usable id is a 403. Only when Access returns no
|
|
212
|
+
identity at all does the adapter fall back to the Access application audience as
|
|
213
|
+
the subject — that, and only that, is the case where tokens on one application
|
|
214
|
+
share attribution. It never reads `Cf-Access-Jwt-Assertion`, downloads signing
|
|
215
|
+
keys, or accepts a JWT from the caller, and a missing context or throwing lookup
|
|
216
|
+
fails closed. It is therefore deliberately not a Node or `cloudflared` origin
|
|
217
|
+
adapter and does not survive a Service Binding hop; those shapes need their own
|
|
157
218
|
trust boundary.
|
|
158
219
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
Access decides admission and identity; Connecta configuration selects connector
|
|
163
|
-
access and these narrower permissions.
|
|
220
|
+
Access decides admission and identity; connecta configuration decides connector
|
|
221
|
+
access and management permissions. An Access service identity, having no human
|
|
222
|
+
principal, cannot mutate connection auth at all.
|
|
164
223
|
|
|
165
224
|
Protect the Worker with a Worker-level Access application whose destination is
|
|
166
225
|
`{ "type": "worker", "worker_id": "<the Worker script tag>" }`. A traditional
|
|
167
226
|
hostname-level application blocks the URL but does not attach `ctx.access` to
|
|
168
227
|
the Worker. Enable [**Managed OAuth**](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/managed-oauth/)
|
|
169
|
-
on that Worker-level application for interactive MCP clients.
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
redirect URIs**:
|
|
228
|
+
on that Worker-level application for interactive MCP clients. Cloudflare then
|
|
229
|
+
owns the unauthenticated challenge and `/.well-known/` metadata, issues opaque
|
|
230
|
+
RFC 8707 tokens, and resolves them into the same trusted Worker identity.
|
|
231
|
+
Managed OAuth allows no hosted client callback by default, so enable Dynamic
|
|
232
|
+
Client Registration and add all three values to **Allowed redirect URIs**:
|
|
175
233
|
|
|
176
234
|
```text
|
|
177
235
|
https://claude.ai/api/mcp/auth_callback
|
|
@@ -180,43 +238,33 @@ https://chatgpt.com/connector/oauth/*
|
|
|
180
238
|
```
|
|
181
239
|
|
|
182
240
|
Cloudflare exposes that list as
|
|
183
|
-
`oauth_configuration.dynamic_client_registration.allowed_uris
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
policy says otherwise;
|
|
201
|
-
- a static Connecta bearer is not a standalone edge credential, because Cloudflare rejects them before connecta sees them; and
|
|
202
|
-
- custom public webhooks belong to the deployment outside Connecta and need
|
|
203
|
-
their own Access routing policy. Keep Connecta's OAuth discovery paths
|
|
204
|
-
protected when Managed OAuth is enabled.
|
|
205
|
-
|
|
206
|
-
The [Worker example](../examples/worker/) carries the complete deployment shape
|
|
207
|
-
and the [upgrade guide](./upgrading.md#0200--0212) gives the reversible Clerk
|
|
208
|
-
migration.
|
|
241
|
+
`oauth_configuration.dynamic_client_registration.allowed_uris`, on the Managed
|
|
242
|
+
OAuth settings rather than the Access policy that picks admitted identities.
|
|
243
|
+
Claude uses the first value; ChatGPT uses its stable callback or a callback-id
|
|
244
|
+
path covered by the third. For any other client add that exact URI or the
|
|
245
|
+
narrowest wildcard covering it, never the client's whole origin. Missing entries
|
|
246
|
+
let discovery succeed and registration fail later, which looks like a broken MCP
|
|
247
|
+
server rather than a console setting.
|
|
248
|
+
|
|
249
|
+
Do not add a bypass for the discovery routes; a fully automated client uses a
|
|
250
|
+
[service token](https://developers.cloudflare.com/cloudflare-one/access-controls/service-credentials/service-tokens/)
|
|
251
|
+
through `CF-Access-Client-Id` and `CF-Access-Client-Secret` instead. Worker-level
|
|
252
|
+
Access runs before every connecta route, so `/health`, operator pages, downstream
|
|
253
|
+
OAuth callbacks, and `/mcp` all require Access unless a more-specific policy says
|
|
254
|
+
otherwise, and a static connecta bearer is not a standalone edge credential
|
|
255
|
+
because Cloudflare rejects the request before connecta sees it. Custom public
|
|
256
|
+
webhooks live outside connecta and need their own Access routing policy. The
|
|
257
|
+
[Worker example](../examples/worker/) carries the whole deployment shape.
|
|
209
258
|
|
|
210
259
|
## Clerk configuration is checked at construction
|
|
211
260
|
|
|
212
261
|
`clerkAuth` reads its Frontend API origin out of `publishableKey`, so a key that
|
|
213
|
-
is not `pk_test_`/`pk_live_` followed by the base64-encoded domain cannot
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
request instead of a base64 stack on every route.
|
|
262
|
+
is not `pk_test_`/`pk_live_` followed by the base64-encoded domain cannot produce
|
|
263
|
+
one. That throws where `allowedDomains` throws, when `clerkAuth` is called, naming
|
|
264
|
+
the option and never quoting the rejected value back — the usual way to land here
|
|
265
|
+
is pasting the *secret* key into the publishable slot. A deployment that builds per
|
|
266
|
+
request, as the Workers shape does, sees that same error on its first request
|
|
267
|
+
instead of a base64 stack on every route.
|
|
220
268
|
|
|
221
269
|
## Human authentication management
|
|
222
270
|
|
|
@@ -225,14 +273,20 @@ visibility, the appropriate shared or personal permission, and an exact
|
|
|
225
273
|
same-origin `Origin` for browser requests. A configured MCP bearer never becomes
|
|
226
274
|
a browser management credential.
|
|
227
275
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
`
|
|
231
|
-
|
|
232
|
-
`
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
276
|
+
`authorize_connector` splits along what it would change. For a connector with a
|
|
277
|
+
static credential slot it mutates nothing, so visibility is enough: with
|
|
278
|
+
`ui: operatorUi()` and a vault it returns a secret-free `operator_config` handoff
|
|
279
|
+
naming the credential fields and the operator URL, and without either the recovery
|
|
280
|
+
is `unavailable`, since connecta does not hand back a link to a missing page. Only
|
|
281
|
+
the downstream-OAuth branch consults the management permissions, and an identity
|
|
282
|
+
lacking them gets `unavailable` there.
|
|
283
|
+
|
|
284
|
+
Core owns the OAuth callback and verifies state and principal ownership
|
|
285
|
+
independently of the optional browser application. A browser returning from
|
|
286
|
+
downstream consent normally carries no MCP `Authorization` header, so an
|
|
287
|
+
interactive bearer provider's 401 does not reject the callback. The verified state
|
|
288
|
+
and its saved principal handoff select the owner; a browser identity, when
|
|
289
|
+
present, must match that owner and may then manage the connector, while an
|
|
290
|
+
interactive provider's explicit 403 still refuses the flow. See
|
|
291
|
+
[meta-tools](./meta-tools.md#authorization-recovery) for the recovery shapes a
|
|
292
|
+
caller actually receives.
|