@zackbart/connecta 0.31.0 → 0.32.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 +34 -0
- package/README.md +15 -13
- package/bin/connecta.mjs +1 -0
- package/bin/version.mjs +1 -1
- package/dist/auth-elicitation.js +1 -1
- package/dist/catalog-service.js +7 -2
- package/dist/config-value-sources.js +6 -3
- package/dist/config.d.ts +26 -8
- package/dist/config.js +38 -16
- package/dist/connector-access.d.ts +2 -1
- package/dist/connectors/guarded-fetch.d.ts +9 -0
- package/dist/connectors/guarded-fetch.js +7 -0
- package/dist/describe-config.d.ts +6 -3
- package/dist/describe-config.js +3 -2
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -0
- package/dist/execute.js +16 -14
- package/dist/index.js +4 -3
- package/dist/invocation.js +7 -4
- package/dist/meta-tools.js +17 -19
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -0
- package/dist/operator-ui/contract.d.ts +5 -4
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +1 -1
- package/dist/operator-ui/view.js +1 -0
- package/dist/program-writes.js +2 -2
- package/dist/provider.d.ts +2 -3
- package/dist/providers/_shared/google/workspace.js +1 -1
- package/dist/providers/breeze/skill.generated.js +2 -2
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/index.d.ts +1 -1
- package/dist/providers/docs/index.js +2 -2
- package/dist/providers/drive/index.d.ts +1 -1
- package/dist/providers/drive/index.js +1 -1
- package/dist/providers/forms/index.d.ts +1 -1
- package/dist/providers/forms/index.js +2 -2
- package/dist/providers/github/skill.generated.js +2 -2
- package/dist/providers/gmail/index.d.ts +1 -1
- package/dist/providers/gmail/index.js +274 -23
- package/dist/providers/gmail/skill.generated.js +2 -2
- package/dist/providers/linear/index.js +1 -1
- package/dist/providers/mixpanel/index.d.ts +27 -12
- package/dist/providers/mixpanel/index.js +58 -6
- package/dist/providers/mixpanel/operations.js +20 -0
- package/dist/providers/mixpanel/rest.js +603 -0
- package/dist/providers/mixpanel/skill.generated.js +3 -2
- package/dist/providers/mixpanel/value-safety.js +16 -0
- package/dist/providers/notion/index.js +16 -0
- package/dist/providers/notion/rest.js +138 -19
- package/dist/providers/notion/skill.generated.js +4 -4
- package/dist/providers/planning-center/index.d.ts +27 -0
- package/dist/providers/planning-center/index.js +286 -24
- package/dist/providers/planning-center/skill.generated.js +24 -4
- package/dist/providers/sheets/index.d.ts +1 -1
- package/dist/providers/sheets/index.js +3 -3
- package/dist/providers/slides/index.d.ts +1 -1
- package/dist/providers/slides/index.js +3 -3
- package/dist/providers/stripe/skill.generated.js +2 -2
- package/dist/providers/vercel/index.js +2 -2
- package/dist/providers/vercel/skill.generated.js +2 -2
- package/dist/providers/wispr-flow/index.d.ts +14 -0
- package/dist/providers/wispr-flow/index.js +117 -0
- package/dist/providers/wispr-flow/skill.generated.js +8 -0
- package/dist/routes/mcp.js +30 -10
- package/dist/routes/operator.js +14 -7
- package/dist/routes/ui-config.js +2 -2
- package/dist/server.js +3 -0
- package/dist/skills.js +5 -4
- package/dist/tool-safety.js +13 -5
- package/dist/types.d.ts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +1 -1
- package/dist/usage-guide.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +38 -23
- package/documentation/auth.md +86 -27
- package/documentation/code-mode.md +81 -72
- package/documentation/deploying.md +10 -5
- package/documentation/integrating.md +56 -4
- package/documentation/meta-tools.md +44 -37
- package/documentation/operating.md +11 -8
- package/documentation/operator-tests.md +2 -3
- package/documentation/operator-ui.md +9 -3
- package/documentation/provider-auth-migration.md +5 -4
- package/documentation/provider-migration-0.29.md +1 -1
- package/examples/worker/AGENTS.md +5 -0
- package/examples/worker/src/connecta.config.ts +5 -0
- package/package.json +5 -1
- package/templates/node/AGENTS.md +6 -4
- package/templates/node/README.md +4 -2
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +5 -0
- package/dist/tool-safety.d.ts +0 -9
|
@@ -85,20 +85,20 @@ table, security headers. Route _order_ is the contract — several routes would
|
|
|
85
85
|
behave differently if they were reachable in another order — so read the table
|
|
86
86
|
top to bottom.
|
|
87
87
|
|
|
88
|
-
| Order | Route | Notes
|
|
89
|
-
| ----- | --------------------------------------------------------- |
|
|
90
|
-
| 0 | MCP Origin check | A disallowed `Origin` on `/mcp*` is a fixed 403 before redirects, admission, auth, or preflight — costing no permit and no auth lookup. Originless requests are admitted.
|
|
91
|
-
| 0 | HTTPS upgrade | 308 to an HTTPS `publicUrl`, with path and query _assigned_ onto it rather than resolved against it, so a `//host` pathname cannot replace the origin. `/health` is exempt: a loopback probe must not need public DNS.
|
|
92
|
-
| 0 | Cloudflare Access (Worker, when enabled) | Edge admission ahead of this table; an admitted invocation carries trusted identity in `ctx.access`.
|
|
93
|
-
| 1 | `/oauth/client-metadata/<connectorId>` | Public GET-only CIMD for configured self-hosted OAuth connectors; fixed `publicUrl`, no auth or storage.
|
|
94
|
-
| 1 | Mounted UI routes | Before wildcard OPTIONS, so mutation routes refuse preflight rather than inheriting MCP CORS. No UI module, no routes.
|
|
95
|
-
| 1 | `/connect/<connectorId>`, `/oauth/callback/<connectorId>` | GET-only browser OAuth routes, before wildcard OPTIONS. Both verify the initiating user and management permission, independent of the UI.
|
|
96
|
-
| 2 | MCP preflight | Allowed `OPTIONS` on `/mcp*`: 204 without admission or auth.
|
|
97
|
-
| 2 | Other `OPTIONS` | Auth metadata first, otherwise compatibility CORS preflight.
|
|
98
|
-
| 3 | `/.well-known/*` |
|
|
99
|
-
| 4 | `/health` | Open and payload-free: health, executor, admission, and deployment metadata.
|
|
100
|
-
| 6 | `/mcp`, `/mcp/<pool>` | Admission, then auth, then a request-local MCP server. Body-confirmed modern listens skip admission and are refused after auth and SDK validation. An undeclared pool, a refusing grant, and a throwing grant are one identical 404; see [pools](./auth.md#pools). |
|
|
101
|
-
| 7 | Other paths | 404. Custom HTTP routes belong to the deployment.
|
|
88
|
+
| Order | Route | Notes |
|
|
89
|
+
| ----- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
90
|
+
| 0 | MCP Origin check | A disallowed `Origin` on `/mcp*` is a fixed 403 before redirects, admission, auth, or preflight — costing no permit and no auth lookup. Originless requests are admitted. |
|
|
91
|
+
| 0 | HTTPS upgrade | 308 to an HTTPS `publicUrl`, with path and query _assigned_ onto it rather than resolved against it, so a `//host` pathname cannot replace the origin. `/health` is exempt: a loopback probe must not need public DNS. |
|
|
92
|
+
| 0 | Cloudflare Access (Worker, when enabled) | Edge admission ahead of this table; an admitted invocation carries trusted identity in `ctx.access`. |
|
|
93
|
+
| 1 | `/oauth/client-metadata/<connectorId>` | Public GET-only CIMD for configured self-hosted OAuth connectors; fixed `publicUrl`, no auth or storage. |
|
|
94
|
+
| 1 | Mounted UI routes | Before wildcard OPTIONS, so mutation routes refuse preflight rather than inheriting MCP CORS. No UI module, no routes. |
|
|
95
|
+
| 1 | `/connect/<connectorId>`, `/oauth/callback/<connectorId>` | GET-only browser OAuth routes, before wildcard OPTIONS. Both verify the initiating user and management permission, independent of the UI. |
|
|
96
|
+
| 2 | MCP preflight | Allowed `OPTIONS` on `/mcp*`: 204 without admission or auth. |
|
|
97
|
+
| 2 | Other `OPTIONS` | Auth metadata first, otherwise compatibility CORS preflight. |
|
|
98
|
+
| 3 | `/.well-known/*` | Public auth metadata, or 404. Protected-resource discovery does not authenticate callers or evaluate endpoint grants. |
|
|
99
|
+
| 4 | `/health` | Open and payload-free: health, executor, admission, and deployment metadata. |
|
|
100
|
+
| 6 | `/mcp`, `/mcp/<pool>` | Admission, then auth, then a request-local MCP server. Body-confirmed modern listens skip admission and are refused after auth and SDK validation. An undeclared pool, a refusing endpoint grant, and a throwing endpoint grant are one identical 404; see [pools](./auth.md#pools). |
|
|
101
|
+
| 7 | Other paths | 404. Custom HTTP routes belong to the deployment. |
|
|
102
102
|
|
|
103
103
|
Every response leaves through `withSecurityHeaders`. HTML uses one common
|
|
104
104
|
security-header helper, including status and error pages. Operator shells
|
|
@@ -131,12 +131,17 @@ An admitted non-preflight `/mcp` request then takes five steps in
|
|
|
131
131
|
3. **Narrow to the pool.** On `/mcp/<pool>`, look the name up, run its grant
|
|
132
132
|
against the identity, then `intersectAccess` the pool with the identity's own
|
|
133
133
|
access. A pool can never widen a view; anything else is a 404 naming no pool.
|
|
134
|
+
The endpoint's `readOnly` is the pool's own value, else the deployment's;
|
|
135
|
+
plain `/mcp` uses the deployment's. It travels to the meta-tools and the
|
|
136
|
+
program as one boolean that drops writes from `CatalogService` search and
|
|
137
|
+
describe and makes `writeRefusal` (`src/tool-safety.ts`) refuse every write
|
|
138
|
+
call. Guides and skills are not filtered.
|
|
134
139
|
4. **Derive the registry view.** One `registry.scoped(...)` call with those
|
|
135
140
|
connector ids, exact `connector.tool` addresses when the identity declares a
|
|
136
141
|
narrower slice, and the subject and principal keys. Personal connectors use
|
|
137
142
|
the principal partition and result paging the subject partition with principal,
|
|
138
143
|
endpoint/pool, origin, connector, and tool bindings. Paging rechecks current
|
|
139
|
-
grants, pool membership, and
|
|
144
|
+
grants, pool membership, and the endpoint's `readOnly`; no caller parameter selects a partition (`test/identity-scope.test.ts`).
|
|
140
145
|
5. **Serve.** Refuse `?toolkit=` with a 404 — the toolkits are gone
|
|
141
146
|
([#178](https://github.com/zackbart/connecta/issues/178)) but their URLs were
|
|
142
147
|
handed out, and retiring a scoping boundary into fail-open is worse than any
|
|
@@ -217,13 +222,15 @@ or hands out, and a change usually belongs in exactly one of them:
|
|
|
217
222
|
| `src/invocation.ts` | One tool call: argument validation, call admission, one-attempt timeout, provider retry hints, result unwrapping, size capping, and the activity record. |
|
|
218
223
|
| `src/catalog.ts` | Ranking, description summarizing, and the compact and TypeScript schema renderers discovery shows. |
|
|
219
224
|
| `src/result-shapes.ts` | Bounded runtime-only inference and merging for output shapes learned from successful read-only calls whose providers declared none. |
|
|
220
|
-
| `src/program-writes.ts` |
|
|
225
|
+
| `src/program-writes.ts` | Program write accounting on writable endpoints: write budgets, dispatch draining, and known or unknown outcomes. |
|
|
221
226
|
|
|
222
227
|
`src/meta-tools.ts` and `src/execute.ts` are two front doors onto the same two
|
|
223
228
|
services, `CatalogService` and `InvocationService`. That is the point: a
|
|
224
229
|
program's `connecta.call` and a top-level `call_tool` reach
|
|
225
230
|
`InvocationService.invoke` by different routes and get the same admission, the
|
|
226
|
-
same credential resolution, and the same fail-closed read-only check
|
|
231
|
+
same credential resolution, and the same fail-closed read-only check: one
|
|
232
|
+
`writeRefusal` decision per call, from the stored classification, the calling
|
|
233
|
+
surface, and the endpoint's `readOnly`.
|
|
227
234
|
`test/execute.test.ts` asserts the parity directly, because a sandbox path that
|
|
228
235
|
quietly diverges is how generated code would mint a capability.
|
|
229
236
|
|
|
@@ -723,18 +730,26 @@ connection context `create` supplies, the maintained text, then deployment
|
|
|
723
730
|
instructions, which append and never replace. `src/provider.ts` imports neither
|
|
724
731
|
transport, so an `api()` provider gains no MCP client or Effect graph from it
|
|
725
732
|
(`test/purity.node.test.ts`). The factory carries its `definition`, which build and
|
|
726
|
-
check tools read instead of keeping provider lists.
|
|
727
|
-
live in their own folders.
|
|
733
|
+
check tools read instead of keeping provider lists. Providers
|
|
734
|
+
live in their own folders. Hosted implementations, including the
|
|
728
735
|
MCP branches of Notion, Vercel, and Cloudflare, use reviewed presets over
|
|
729
|
-
`remoteMcp({ classify })`. The
|
|
736
|
+
`remoteMcp({ classify })`. The migrated API-only factories retain an internal
|
|
730
737
|
adapter to the same definition and description-stamping path. GitHub composes hosted tools with a scope-enforced REST complement.
|
|
731
|
-
A `"dual"` provider
|
|
738
|
+
A `"dual"` provider selects one implementation at construction, usually through `auth`
|
|
732
739
|
([decision 0005](https://github.com/zackbart/connecta/blob/main/decisions/0005-auth-selects-implementation.md)):
|
|
733
740
|
`variants(["auth", "type"], …)` closes each case's options, and
|
|
734
741
|
`byAuth()` in `src/providers/_shared/rest/dispatch.ts` sends OAuth to
|
|
735
742
|
`hostedOAuth()` (`remoteMcp()`, OAuth only, the reviewed classification) and a
|
|
736
|
-
key to Connecta's REST connector. Stripe, Notion, Cloudflare, and Vercel
|
|
737
|
-
|
|
743
|
+
key to Connecta's REST connector. Stripe, Notion, Cloudflare, and Vercel require `auth`. Mixpanel is also dual,
|
|
744
|
+
with explicit `via: "mcp" | "api"` and MCP as the unchanged default: service accounts
|
|
745
|
+
reach both implementations, so auth cannot distinguish them. API mode requires
|
|
746
|
+
operator-managed service-account credentials and never falls back to MCP.
|
|
747
|
+
Mixpanel uses the same REST transport, operation matching and value-safety engine
|
|
748
|
+
for a narrow reviewed subset. Its official contracts are YAML and app identity
|
|
749
|
+
has no published response schema, so its guide and drift record carry explicit
|
|
750
|
+
manual review evidence instead of claiming a machine-checkable JSON pin.
|
|
751
|
+
|
|
752
|
+
The generic REST connector (`src/providers/_shared/rest/`) serves
|
|
738
753
|
`<vendor>_api_search`, `_api_details`, `_api_read`, and `_api_write` over an
|
|
739
754
|
`OperationIndex` that `npm run providers:spec` compiles offline from the
|
|
740
755
|
provider's pinned `openapi.source.json` into `openapi.generated.ts`.
|
package/documentation/auth.md
CHANGED
|
@@ -6,15 +6,15 @@
|
|
|
6
6
|
and package maintenance. This guide owns identity, grants, and downstream
|
|
7
7
|
credential contracts; use [deployment setup](./deploying.md) for runnable steps.
|
|
8
8
|
|
|
9
|
-
| Task
|
|
10
|
-
|
|
|
11
|
-
| Connect a machine or migrate an old client
|
|
12
|
-
| Configure caller visibility,
|
|
13
|
-
| Set up human sign-in
|
|
14
|
-
| Enable credential or connection controls
|
|
15
|
-
| Diagnose consent or token refresh
|
|
16
|
-
| Configure a downstream OAuth client or HTTP handler
|
|
17
|
-
| Review browser or downstream URL boundaries
|
|
9
|
+
| Task | Sections |
|
|
10
|
+
| ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
+
| Connect a machine or migrate an old client | [Managed tokens](#managed-client-tokens-and-upgrading-from-v023), [static bearer migration](#migrating-static-bearer-clients) |
|
|
12
|
+
| Configure caller visibility, read-only endpoints, or auth ownership | [Principals and operators](#principals-visibility-and-operators), [pools](#pools), [shared and personal auth](#shared-and-personal-auth) |
|
|
13
|
+
| Set up human sign-in | [Access on Workers](#cloudflare-access-on-workers), [Clerk tokens and sessions](#clerk-oauth-tokens-and-operator-sessions), [management](#human-authentication-management) |
|
|
14
|
+
| Enable credential or connection controls | [Management permissions](#management-permissions), [start/restart/disconnect](#starting-restarting-and-disconnecting) |
|
|
15
|
+
| Diagnose consent or token refresh | [State at rest](#downstream-oauth-state-at-rest), [callbacks](#consents-and-callbacks), [refresh failures](#refresh-failures), [safe errors](#what-a-servers-errors-may-say) |
|
|
16
|
+
| Configure a downstream OAuth client or HTTP handler | [Client registration](#downstream-oauth-client-registration), [api() OAuth](#downstream-oauth-on-api), [request-local bearer tokens](#request-local-downstream-bearer-tokens) |
|
|
17
|
+
| Review browser or downstream URL boundaries | [Origins](#origins), [advertised URLs](#urls-a-downstream-advertises), [one server per grant](#one-server-per-grant) |
|
|
18
18
|
|
|
19
19
|
[Credential recognition and admission](#credential-recognition-and-admission)
|
|
20
20
|
defines refusal precedence.
|
|
@@ -90,6 +90,7 @@ createConnecta({
|
|
|
90
90
|
principal.id === "your-operator-id",
|
|
91
91
|
},
|
|
92
92
|
connectors, executor,
|
|
93
|
+
readOnly: false, // required: true refuses every write call
|
|
93
94
|
});
|
|
94
95
|
```
|
|
95
96
|
|
|
@@ -101,7 +102,7 @@ the identity namespaces and connector/pool grant callbacks too; storage
|
|
|
101
102
|
compatibility translates no configuration and invents no grants. `access_token`
|
|
102
103
|
actor ids, the `connecta:access-tokens:v1` activity namespace, friendly names,
|
|
103
104
|
and stored principals carry over; a token without a principal stays unbound.
|
|
104
|
-
Every request evaluates current identity, tool, and
|
|
105
|
+
Every request evaluates current identity, tool, and endpoint grants, and a token
|
|
105
106
|
never becomes an interactive operator. Names are labels, never permissions. A
|
|
106
107
|
UI-issued token belongs to the issuing human's principal, and any explicitly
|
|
107
108
|
permitted token manager can list, rename, or revoke deployment tokens. Machine tokens manage neither tokens nor connection credentials; operators
|
|
@@ -163,6 +164,10 @@ deployment's anonymous admission. Otherwise, or on `undefined`, the call fails
|
|
|
163
164
|
`auth_required` before any request leaves. Arguments, headers, and programs
|
|
164
165
|
never reach it; a deployment's own connectors still cannot read the caller.
|
|
165
166
|
|
|
167
|
+
The deployment's top-level `grant` decides who may use plain `/mcp`; named
|
|
168
|
+
pools use their own grants. These endpoint decisions receive the admitted
|
|
169
|
+
identity below and never widen its connector access.
|
|
170
|
+
|
|
166
171
|
`identity.connectorAccess` returns `"all"` (the default) or grants, for
|
|
167
172
|
discovery and use alike: a connector id opens every tool on it, a
|
|
168
173
|
`connector.tool` address that tool alone, and
|
|
@@ -206,6 +211,7 @@ createConnecta({
|
|
|
206
211
|
},
|
|
207
212
|
},
|
|
208
213
|
connectors: [issues, campaigns, billing], executor,
|
|
214
|
+
readOnly: false, // required: true refuses every write call
|
|
209
215
|
});
|
|
210
216
|
```
|
|
211
217
|
|
|
@@ -215,12 +221,12 @@ grant both connectors in full. Unknown or unprincipalled identities get `[]`.
|
|
|
215
221
|
Use a stable authenticated principal or subject, never request headers or tool
|
|
216
222
|
arguments. A token's stored principal is vouched for by inbound auth. Pools narrow their endpoint, not plain `/mcp`.
|
|
217
223
|
|
|
218
|
-
| Catalog change | Guarded exact grant
|
|
219
|
-
| -------------------------------------------------------- |
|
|
220
|
-
| New name | Excluded until reviewed and added
|
|
221
|
-
| Removed or renamed name | Unreachable; warned when the scoped view reads the catalog
|
|
222
|
-
| Stored write verdict after overrides and provider review | Removed from discovery and every invocation path, including `call_destructive_tool` and
|
|
223
|
-
| Schema change alone | Does not revoke, unless it breaks a reviewed digest: then it is a write, as above
|
|
224
|
+
| Catalog change | Guarded exact grant | Unrestricted exact string grant |
|
|
225
|
+
| -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
|
|
226
|
+
| New name | Excluded until reviewed and added | Excluded until reviewed and added |
|
|
227
|
+
| Removed or renamed name | Unreachable; warned when the scoped view reads the catalog | Same |
|
|
228
|
+
| Stored write verdict after overrides and provider review | Removed from discovery and every invocation path, including `call_destructive_tool` and programs on a writable endpoint | Still granted; `call_tool` refuses writes and write paths take over |
|
|
229
|
+
| Schema change alone | Does not revoke, unless it breaks a reviewed digest: then it is a write, as above | Same as above |
|
|
224
230
|
|
|
225
231
|
The example assumes both exact tools were reviewed as read-only; names imply no safety.
|
|
226
232
|
Review schemas, annotations, and downstream behavior
|
|
@@ -236,7 +242,7 @@ Failed remote loads are errors, not empty catalogs. A transient first-page tool
|
|
|
236
242
|
listing failure may serve complete facts in the same auth partition for five
|
|
237
243
|
minutes after expiry. Failed pagination, permission and auth failures do not
|
|
238
244
|
use this fallback. The expired deadline cannot authorize auth recovery. Personal OAuth ownership and credential
|
|
239
|
-
administration are separate from visibility;
|
|
245
|
+
administration are separate from visibility; writes follow the endpoint's `readOnly` setting and client approval. `test/identity-scope.test.ts` exercises these boundaries.
|
|
240
246
|
|
|
241
247
|
## Migrating static bearer clients
|
|
242
248
|
|
|
@@ -284,6 +290,8 @@ createConnecta({
|
|
|
284
290
|
storage,
|
|
285
291
|
accessTokens: accessTokens(storage),
|
|
286
292
|
auth: clerkAuth({ publishableKey, secretKey }),
|
|
293
|
+
readOnly: false, // required; pools without their own readOnly inherit it
|
|
294
|
+
grant: ({ actor }) => actor.kind !== "access_token", // machines use their pools
|
|
287
295
|
pools: {
|
|
288
296
|
support: {
|
|
289
297
|
tools: ["linear", "notion.search_pages", "notion.fetch_page"],
|
|
@@ -293,6 +301,11 @@ createConnecta({
|
|
|
293
301
|
tools: ["calendar.create_event"],
|
|
294
302
|
grant: ({ actor }) => actor.kind === "access_token" && actor.id === calendarTokenId,
|
|
295
303
|
},
|
|
304
|
+
reporting: {
|
|
305
|
+
tools: ["linear", "notion"],
|
|
306
|
+
readOnly: true,
|
|
307
|
+
grant: ({ principal }) => analysts.has(principal?.id ?? ""),
|
|
308
|
+
},
|
|
296
309
|
},
|
|
297
310
|
identity: { connectorAccess },
|
|
298
311
|
connectors, executor,
|
|
@@ -302,14 +315,59 @@ createConnecta({
|
|
|
302
315
|
Without configured `auth`, an open deployment has one anonymous, non-interactive
|
|
303
316
|
identity; grants like these return false and every pool path 404s.
|
|
304
317
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
318
|
+
The optional top-level `grant: (identity) => boolean | Promise<boolean>`
|
|
319
|
+
controls plain `/mcp`. Omitted, it admits everyone who passes auth, including
|
|
320
|
+
anonymous callers on an open deployment. Configured, only literal `true`
|
|
321
|
+
admits; other returns and throws deny. A refusal has the same 404 status,
|
|
322
|
+
body bytes, and headers as a refused or undeclared pool, after auth. The
|
|
323
|
+
operator log records `default_grant_denied` or `default_grant_threw`, never
|
|
324
|
+
the callback's error text. Invalid config values throw at construction.
|
|
325
|
+
Named pools use only their own grants, so a caller refused at `/mcp` may
|
|
326
|
+
still use a pool that grants it.
|
|
327
|
+
|
|
328
|
+
Protected-resource metadata stays public at both default aliases,
|
|
329
|
+
`/.well-known/oauth-protected-resource` and
|
|
330
|
+
`/.well-known/oauth-protected-resource/mcp`, just as at named-pool aliases.
|
|
331
|
+
Discovery does not authenticate callers or evaluate endpoint grants, so fresh
|
|
332
|
+
OAuth clients can discover how to authenticate. The grant applies after auth
|
|
333
|
+
to MCP requests and result/page handles. Handles remain bound
|
|
334
|
+
to their originating endpoint; paging rechecks the current endpoint grant,
|
|
335
|
+
identity, and connector access. A granting pool cannot read a `/mcp` handle,
|
|
336
|
+
and `/mcp` cannot read that pool's handle.
|
|
337
|
+
|
|
338
|
+
`describeConfig().hasGrant` and the operator UI report whether plain `/mcp`
|
|
339
|
+
has a configured grant without exposing callback source. `describeConfig()`
|
|
340
|
+
does not evaluate it. Operator catalog, config, activity, and connector auth
|
|
341
|
+
routes require the same default endpoint grant after their own authentication.
|
|
342
|
+
A refused caller gets a private JSON 404; the UI provides no per-pool view.
|
|
343
|
+
The public HTML shell carries no catalog data.
|
|
344
|
+
|
|
345
|
+
Each endpoint is writable or read-only. The deployment's `readOnly` sets the
|
|
346
|
+
plain `/mcp` endpoint and is required: a deployment that omits it fails at
|
|
347
|
+
construction and must choose `true` or `false`. A pool inherits it unless the
|
|
348
|
+
pool sets its own `readOnly`, in either direction. A writable endpoint lets
|
|
349
|
+
`call_destructive_tool` and programs write, and the MCP client decides what to
|
|
350
|
+
approve. On a read-only endpoint, write tools are absent from `search_tools`
|
|
351
|
+
results, `connecta.search`, and `connecta.describe`, and `call_tool`,
|
|
352
|
+
`call_destructive_tool`, and program writes are refused with
|
|
353
|
+
`read_only_endpoint` before validation or dispatch. Reads, including reads
|
|
354
|
+
through `call_destructive_tool`, still run.
|
|
355
|
+
Guides are documentation, not discovery: provider `SKILL.md` and usage guides,
|
|
356
|
+
surfaced through `queryAnalysis.guideSummary`, `skills`, `connecta.skill`, and
|
|
357
|
+
skill `resources/read`, are the same on every endpoint and may name tools a
|
|
358
|
+
read-only endpoint refuses.
|
|
359
|
+
Resource reads, grants, and `connectorAccess`, including guarded
|
|
360
|
+
`requireReadOnly` grants, are the same on both. The removed `trust` option fails
|
|
361
|
+
construction with a message naming `readOnly`.
|
|
362
|
+
|
|
363
|
+
| Rule | Contract |
|
|
364
|
+
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
365
|
+
| Scope | `/mcp/<pool>` intersects pool tools with the identity's `connectorAccess`, never widening it. Plain `/mcp` serves the identity's `connectorAccess` after its optional top-level `grant` admits it. |
|
|
366
|
+
| Grant | Pool grants default to deny; only literal `true` admits. Other returns, throws, and undeclared names produce an identical 404 status, body, and headers, after auth. Names are not anonymously enumerable; valid credentials cannot distinguish these cases by content. |
|
|
367
|
+
| Timing | Declared names await grants, so timing is not hidden. Keep grants pure and fast and names unsecret; names grant no access. The operator log records the refusal reason. |
|
|
368
|
+
| Construction | Malformed names, unknown options or connectors, empty pools, a non-boolean `readOnly`, a retired `trust`, and addresses absent from an `api()` static catalog throw. Remote catalogs are checked at lazy load. |
|
|
369
|
+
| Discovery | Clerk's pool 401 names `/.well-known/oauth-protected-resource/mcp/<pool>` with the pool URL as `resource`, matching RFC 9728. Cloudflare Managed OAuth is application-level and needs no pool setup. |
|
|
370
|
+
| Drift | No wildcards; tool grants are exact names. Missing live tools are unreachable and warned once while in a bounded 1,024-entry FIFO. |
|
|
313
371
|
|
|
314
372
|
## Shared and personal auth
|
|
315
373
|
|
|
@@ -809,6 +867,7 @@ createConnecta({
|
|
|
809
867
|
activityAccess: ({ id }) => id === "owner-id",
|
|
810
868
|
},
|
|
811
869
|
connectors, executor,
|
|
870
|
+
readOnly: false, // required: true refuses every write call
|
|
812
871
|
});
|
|
813
872
|
```
|
|
814
873
|
|
|
@@ -821,7 +880,7 @@ Access trusts the identity validated at the Worker edge.
|
|
|
821
880
|
|
|
822
881
|
```ts
|
|
823
882
|
import { cloudflareAccessAuth } from "@zackbart/connecta/auth/cloudflare-access";
|
|
824
|
-
createConnecta({ auth: cloudflareAccessAuth(), connectors, executor });
|
|
883
|
+
createConnecta({ auth: cloudflareAccessAuth(), connectors, executor, readOnly: false });
|
|
825
884
|
```
|
|
826
885
|
|
|
827
886
|
The adapter trusts only `ctx.access`, which Cloudflare attaches after validating
|
|
@@ -1011,7 +1070,7 @@ fallbacks, including a failed listing followed by cached annotations, cannot
|
|
|
1011
1070
|
authorize a re-run. Normal calls retain their existing stale-fallback behavior.
|
|
1012
1071
|
Before an accepted retry restarts any call, it rechecks every entered read.
|
|
1013
1072
|
A write classification, changed classification digest, or stale catalog returns
|
|
1014
|
-
`auth_replay_refused` with reconciliation guidance, including in
|
|
1073
|
+
`auth_replay_refused` with reconciliation guidance, including in programs on a writable endpoint.
|
|
1015
1074
|
Pre-invocation credential-resolution eligibility is unchanged.
|
|
1016
1075
|
Custom vaults may implement the host-only `requestStateKey` method; without it, the
|
|
1017
1076
|
deployment retains the ordinary handoff. It must return a stable,
|