@zackbart/connecta 0.37.1 → 0.39.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 +94 -0
- package/README.md +5 -4
- package/bin/connecta.mjs +1 -39
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +2 -4
- package/dist/agent-identity.js +1 -1
- package/dist/agent-routes.js +231 -0
- package/dist/auth/clerk-sdk/generated.js +2 -1
- package/dist/auth/downstream-oauth/consent.js +519 -0
- package/dist/auth/downstream-oauth/grant-store.js +439 -0
- package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
- package/dist/auth/downstream-oauth/refresh-records.js +447 -0
- package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
- package/dist/auth/downstream-oauth.js +66 -2516
- package/dist/auth/downstream-revocation.js +1 -1
- package/dist/auth/native.js +27 -0
- package/dist/auth/oauth-grant.js +24 -0
- package/dist/auth/oauth-token-response.js +1 -1
- package/dist/auth/static-oauth.js +153 -131
- package/dist/auth/vercel-agent.js +2 -1
- package/dist/auth-elicitation.js +9 -3
- package/dist/authorization-handoff.js +11 -5
- package/dist/branding.d.ts +1 -15
- package/dist/branding.js +2 -2
- package/dist/bytes.js +42 -0
- package/dist/call-admission-context.js +2 -5
- package/dist/call-recovery.js +0 -3
- package/dist/catalog-cache.js +130 -43
- package/dist/catalog-fingerprint.js +2 -18
- package/dist/catalog-policy.js +18 -0
- package/dist/catalog-service.js +285 -48
- package/dist/catalog.js +18 -2
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +1 -1
- package/dist/config-schema.js +6 -2
- package/dist/config.d.ts +57 -25
- package/dist/config.js +30 -77
- package/dist/connector-caller.d.ts +1 -1
- package/dist/connector-health.js +59 -0
- package/dist/connector-status.js +117 -0
- package/dist/connectors/api-connector.d.ts +7 -2
- package/dist/connectors/api-connector.js +11 -7
- package/dist/connectors/guarded-fetch.d.ts +0 -6
- package/dist/connectors/guarded-fetch.js +1 -11
- package/dist/connectors/negotiation-cache.js +22 -4
- package/dist/connectors/remote-mcp/call.js +164 -0
- package/dist/connectors/remote-mcp/catalog.js +278 -0
- package/dist/connectors/remote-mcp/config.js +263 -0
- package/dist/connectors/remote-mcp/connection.js +671 -0
- package/dist/connectors/remote-mcp/errors.js +488 -0
- package/dist/connectors/remote-mcp/oauth.js +469 -0
- package/dist/connectors/remote-mcp/resources.js +473 -0
- package/dist/connectors/remote-mcp/runtime.js +1 -0
- package/dist/connectors/remote-mcp/transport.js +544 -0
- package/dist/connectors/remote-mcp.js +61 -2898
- package/dist/connectors/resource-uri.js +0 -4
- package/dist/credential-contract.d.ts +23 -0
- package/dist/credential-reads.js +17 -0
- package/dist/credential-verification.js +53 -0
- package/dist/credentials.d.ts +7 -0
- package/dist/credentials.js +60 -25
- package/dist/d1.d.ts +0 -89
- package/dist/d1.js +0 -34
- package/dist/describe-config.d.ts +12 -2
- package/dist/describe-config.js +2 -0
- package/dist/discovery-health.js +199 -0
- package/dist/downstream-input.js +4 -2
- package/dist/errors.d.ts +55 -17
- package/dist/errors.js +95 -41
- package/dist/execute.js +172 -125
- package/dist/executor-result.js +5 -2
- package/dist/executors/quickjs-runtime.js +2 -25
- package/dist/executors/quickjs.js +1 -1
- package/dist/guest-functions.js +83 -0
- package/dist/guest-runtime.js +63 -20
- package/dist/guest-types.d.ts +24 -10
- package/dist/http/argument-problems.js +35 -0
- package/dist/http/client.js +1 -1
- package/dist/http/errors.js +7 -6
- package/dist/http/fields.js +192 -13
- package/dist/http/hatch-response.js +73 -0
- package/dist/http/hatch.js +88 -23
- package/dist/http/json-schema.js +1 -1
- package/dist/http/node-transport.js +0 -1
- package/dist/http/oauth.js +4 -3
- package/dist/http/paging.js +6 -9
- package/dist/http/provider.js +33 -5
- package/dist/http/safety.js +9 -1
- package/dist/http/services.js +1 -8
- package/dist/http/tools.js +45 -30
- package/dist/http/vendor.js +7 -7
- package/dist/identity.js +2 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/invocation-auth.js +2 -1
- package/dist/invocation.js +21 -6
- package/dist/membership.d.ts +13 -5
- package/dist/membership.js +34 -2
- package/dist/meta-tools.js +50 -35
- package/dist/native-recovery.js +4 -16
- package/dist/native-tools.js +6 -2
- package/dist/oauth-handoff.js +2 -2
- package/dist/oauth-sealing.js +10 -0
- package/dist/operation-vocabulary.js +136 -14
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -49
- package/dist/operator-ui/contract.d.ts +12 -2
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +22 -1
- package/dist/program-writes.js +3 -8
- package/dist/provider-call.js +0 -7
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/kit.js +1 -6
- package/dist/providers/_shared/google/service-account.js +1 -3
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/guide.js +3 -0
- package/dist/providers/_shared/json.js +12 -0
- package/dist/providers/_shared/rest/compact-index.js +1 -1
- package/dist/providers/_shared/rest/kit.js +1 -3
- package/dist/providers/_shared/rest/operation-index.js +49 -53
- package/dist/providers/_shared/rest/value-safety.js +106 -28
- package/dist/providers/breeze/api.js +9 -8
- package/dist/providers/breeze/index.js +4 -2
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/breeze/tools.js +7 -7
- package/dist/providers/ccb/api.js +3 -7
- package/dist/providers/ccb/index.js +7 -1
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/ccb/tools.js +1 -6
- package/dist/providers/cloudflare/api.js +10 -3
- package/dist/providers/cloudflare/index.js +2 -1
- package/dist/providers/cloudflare/safety.js +3 -3
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/lib/schemas.js +24 -22
- package/dist/providers/docs/safety.js +2 -0
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/docs/tools.js +4 -14
- package/dist/providers/drive/api.js +1 -0
- package/dist/providers/drive/lib/contracts.js +8 -5
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/drive/tools.js +23 -41
- package/dist/providers/forms/api.js +1 -0
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/forms/tools.js +11 -16
- package/dist/providers/github/api.js +1 -0
- package/dist/providers/github/lib/discovery.js +1 -1
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +61 -17
- package/dist/providers/gmail/lib/schemas.js +60 -50
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/gmail/tools.js +12 -26
- package/dist/providers/granola/index.js +2 -0
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/api.js +8 -4
- package/dist/providers/overflow/index.js +2 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/overflow/tools.js +3 -7
- package/dist/providers/planning-center/api.js +5 -2
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +1 -9
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/planning-center/tools.js +2 -1
- package/dist/providers/quickbooks/safety.js +80 -39
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/api.js +1 -0
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/sheets/tools.js +14 -23
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/slides/tools.js +19 -26
- package/dist/providers/stripe/index.js +2 -1
- package/dist/providers/stripe/safety.js +2 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/api.js +5 -8
- package/dist/providers/tithely/index.js +3 -2
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/tithely/tools.js +7 -5
- package/dist/providers/vercel/api.js +2 -7
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/providers/vercel/safety.js +1 -1
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/tools.js +6 -4
- package/dist/redaction-marker.js +2 -2
- package/dist/registry.d.ts +11 -5
- package/dist/registry.js +46 -20
- package/dist/request-principal.js +1 -1
- package/dist/request-state.js +2 -2
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/activity-disclosure.js +1 -1
- package/dist/routes/connect.js +2 -2
- package/dist/routes/credentials.js +130 -61
- package/dist/routes/dispatch.js +5 -1
- package/dist/routes/mcp.js +34 -17
- package/dist/routes/oauth-management.js +1 -1
- package/dist/routes/operator.js +6 -5
- package/dist/routes/shared.d.ts +13 -8
- package/dist/routes/shared.js +15 -9
- package/dist/routes/ui-config.js +3 -3
- package/dist/routes/ui-data.js +126 -20
- package/dist/routes/ui.js +17 -1
- package/dist/runtime/call-admission.js +1 -1
- package/dist/runtime/call.js +8 -3
- package/dist/runtime/native-storage.js +9 -0
- package/dist/runtime/request-reads.js +182 -0
- package/dist/runtime/request-scope.js +16 -0
- package/dist/runtime/settlement.js +1 -1
- package/dist/runtime/shared-read.js +2 -15
- package/dist/runtime/storage.js +12 -0
- package/dist/sent-secrets.d.ts +1 -2
- package/dist/sent-secrets.js +3 -8
- package/dist/server.js +4 -4
- package/dist/skills.js +44 -54
- package/dist/sqlite.d.ts +0 -19
- package/dist/sqlite.js +2 -83
- package/dist/storage/keys.js +69 -128
- package/dist/storage/memory.js +8 -2
- package/dist/storage/sql-native.js +27 -101
- package/dist/storage/sql.d.ts +1 -24
- package/dist/storage/sql.js +13 -14
- package/dist/storage-digest.js +32 -0
- package/dist/timeout.js +8 -0
- package/dist/tool-error.js +4 -0
- package/dist/types.d.ts +33 -0
- package/dist/url-safety.js +0 -4
- package/dist/usage-guide.js +18 -37
- package/dist/validate.d.ts +3 -0
- package/dist/validate.js +220 -158
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +1 -1
- package/dist/write-outcome.js +0 -3
- package/documentation/README.md +19 -55
- package/documentation/architecture.md +436 -515
- package/documentation/auth.md +229 -88
- package/documentation/code-mode.md +201 -94
- package/documentation/deploying.md +25 -43
- package/documentation/integrating.md +18 -19
- package/documentation/meta-tools.md +225 -51
- package/documentation/migration-0.33.md +10 -6
- package/documentation/migration-0.34.md +5 -5
- package/documentation/migration-retired-layouts.md +74 -0
- package/documentation/operator-tests.md +20 -120
- package/documentation/operator-ui.md +108 -16
- package/documentation/pool-membership-upgrade.md +44 -0
- package/documentation/provider-auth-migration.md +4 -10
- package/documentation/provider-migration-0.29.md +4 -15
- package/documentation/providers.md +105 -0
- package/examples/worker/AGENTS.md +14 -16
- package/examples/worker/README.md +24 -118
- package/examples/worker/src/connecta.config.ts +1 -0
- package/guides/connecting-agents.md +9 -31
- package/guides/how-it-works.md +6 -4
- package/guides/safety-and-trust.md +4 -1
- package/package.json +19 -19
- package/templates/node/AGENTS.md +13 -12
- package/templates/node/README.md +9 -26
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +1 -0
- package/dist/auth/oauth-v2-migration.js +0 -135
- package/dist/provider-call-facts.d.ts +0 -115
- package/dist/provider-call.d.ts +0 -112
- package/dist/storage/d1-copy.js +0 -255
- package/dist/storage/sql-schema.d.ts +0 -6
- package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
- package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
package/documentation/auth.md
CHANGED
|
@@ -13,7 +13,7 @@ credential contracts; use [deployment setup](./deploying.md) for runnable steps.
|
|
|
13
13
|
| Lay out an agent's pools | [Organizing an agent's pools](#organizing-an-agents-pools) |
|
|
14
14
|
| Set up human sign-in | [Access on Workers](#cloudflare-access-on-workers), [Clerk tokens and sessions](#clerk-oauth-tokens-and-operator-sessions), [Clerk Organizations](#one-clerk-instance-one-organization-per-deployment), [management](#human-authentication-management) |
|
|
15
15
|
| Enable credential or connection controls | [Management permissions](#management-permissions), [start/restart/disconnect](#starting-restarting-and-disconnecting) |
|
|
16
|
-
| 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
|
+
| Diagnose consent or token refresh | [State at rest](#downstream-oauth-state-at-rest), [callbacks](#consents-and-callbacks), [refreshing before expiry](#refreshing-before-expiry), [refresh failures](#refresh-failures), [safe errors](#what-a-servers-errors-may-say) |
|
|
17
17
|
| 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), [principal forwarding](#forwarding-the-admitted-principal), [Google hosted MCP](#google-service-account-tokens-and-hosted-mcp) |
|
|
18
18
|
| Review browser or downstream URL boundaries | [Origins](#origins), [advertised URLs](#urls-a-downstream-advertises), [one server per grant](#one-server-per-grant) |
|
|
19
19
|
|
|
@@ -44,7 +44,9 @@ auth through the operator UI.
|
|
|
44
44
|
with several (Clerk's Frontend API URL or `cloudflare-access` as namespace).
|
|
45
45
|
Keep these stable: personal credentials are partitioned by principal.
|
|
46
46
|
4. Typecheck and restart/redeploy. Sign in as the person and verify their
|
|
47
|
-
intended endpoint and tools. A
|
|
47
|
+
intended endpoint and tools. A non-maintainer listed in exactly one pool
|
|
48
|
+
may use plain `/mcp`, which serves that pool; with zero or several pools,
|
|
49
|
+
plain `/mcp` answers the private 404 and they use `/mcp/<pool>`.
|
|
48
50
|
With the UI enabled, the [member page](./operator-ui.md#use-the-member-page)
|
|
49
51
|
shows their pool URLs and client setup; `/ui/api/config` returns 200 with
|
|
50
52
|
`role: "member"`. It does not grant the maintainer pages, shared credential
|
|
@@ -66,9 +68,11 @@ In the Node template and Worker example, `CONNECTA_MAINTAINERS` is a
|
|
|
66
68
|
comma-separated list of these ids; update that environment variable when
|
|
67
69
|
using their roster wiring. Pool membership remains code. To remove a person,
|
|
68
70
|
remove their memberships and maintainer entry, restart/redeploy, and remove
|
|
69
|
-
their sign-in admission as appropriate. Never add
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
their sign-in admission as appropriate. Never add a person to an agent's
|
|
72
|
+
pool: a pool lists only people or only agents, and mixing them throws at
|
|
73
|
+
construction. Give people who share an agent tier's reach their own people-only
|
|
74
|
+
pool that reuses the tier's tool-list constant, and keep each `<agent>-<user>`
|
|
75
|
+
pool agent-only; follow [the pool layout](#organizing-an-agents-pools).
|
|
72
76
|
|
|
73
77
|
## Connect a shared credential
|
|
74
78
|
|
|
@@ -119,6 +123,62 @@ recovery. These changes do not change
|
|
|
119
123
|
pool membership. Environment-backed header auth has no editable vault slot:
|
|
120
124
|
rotate that secret in the deployment's secret store and restart/redeploy.
|
|
121
125
|
|
|
126
|
+
### Test then save, and last verification
|
|
127
|
+
|
|
128
|
+
When the connector can test its credential (`testCredential` or
|
|
129
|
+
`testCredentials`, which every credential `remoteMcp()` and kit provider
|
|
130
|
+
has), the Connection tab's **Test and save** tests the entered value before
|
|
131
|
+
storing it. The value is tested in memory, under `discovery.probeTimeoutMs`,
|
|
132
|
+
and stored only if the test passes; a failure stores nothing. If the test
|
|
133
|
+
fails, **Save without testing** stores the value anyway, marked unverified,
|
|
134
|
+
so an outage at the service never blocks a replacement.
|
|
135
|
+
|
|
136
|
+
`PUT /ui/credentials/<id>` takes `{ "value": "…" }` (or `{ "values": {…} }`
|
|
137
|
+
for a multi-field slot) and an optional `"verify"`:
|
|
138
|
+
|
|
139
|
+
- `"test"`: test the candidate, then store it. A failure answers 200
|
|
140
|
+
`{ "saved": false, "failure": "<class>" }` and stores nothing.
|
|
141
|
+
- `"skip"` (the default when `verify` is absent, which keeps earlier API
|
|
142
|
+
callers working): store it unverified.
|
|
143
|
+
|
|
144
|
+
A stored save answers 200 `{ "saved": true, "credential": <metadata>,
|
|
145
|
+
"verification": { "outcome": "ok" | "unverified", "observedAt": "…" } }`.
|
|
146
|
+
`POST /ui/credentials/<id>/test` tests the stored value and answers
|
|
147
|
+
`{ "ok": true }` or `{ "ok": false, "failure": "<class>" }`. A connector
|
|
148
|
+
without a test hook refuses `verify: "test"` with 400, as its test route
|
|
149
|
+
refuses to test.
|
|
150
|
+
|
|
151
|
+
Failure classes are typed facts, never downstream text:
|
|
152
|
+
|
|
153
|
+
| Class | Meaning |
|
|
154
|
+
| ------------- | ------------------------------------------------------------------ |
|
|
155
|
+
| `rejected` | The service refused the credential. |
|
|
156
|
+
| `scope` | It accepted the credential but refused a needed permission. |
|
|
157
|
+
| `unreachable` | The service could not be reached, was unavailable or rate limited. |
|
|
158
|
+
| `timeout` | No answer within the probe deadline. |
|
|
159
|
+
| `failed` | Anything else. |
|
|
160
|
+
|
|
161
|
+
A custom hook may return `{ ok: false, failure: "scope" }` to name its class;
|
|
162
|
+
an unclassified failure is `failed`, and a thrown error is classified from its
|
|
163
|
+
`ConnectorCallError` code. Neither the response nor the log carries the value,
|
|
164
|
+
the hook's `message` or the downstream's reply.
|
|
165
|
+
|
|
166
|
+
Each test of the stored value and each save records a last-verified
|
|
167
|
+
observation for that connector and owner: its outcome class, its time and the
|
|
168
|
+
revision of the save it observed. A Test reads the stored values and their
|
|
169
|
+
revision from one envelope, and a save records the revision its own write
|
|
170
|
+
sealed, so a concurrent replacement is never reported as verified. The
|
|
171
|
+
operator page's connector data reports the record as
|
|
172
|
+
`verification: { outcome, observedAt }` only while that save is the stored
|
|
173
|
+
one, so a replacement reads as unverified until tested. Removing the
|
|
174
|
+
credential removes the record. A custom vault records verification only when
|
|
175
|
+
its `metadata` carries a per-save `revision` and, for Test, it implements
|
|
176
|
+
`snapshot`; otherwise its credentials are tested but never shown verified.
|
|
177
|
+
|
|
178
|
+
Reading the record is side-effect free: the operator page only reads it,
|
|
179
|
+
never probes the downstream or writes (INV-10). OAuth connectors keep no
|
|
180
|
+
verification record; their status is unchanged.
|
|
181
|
+
|
|
122
182
|
## Issue, rotate, or revoke an agent token
|
|
123
183
|
|
|
124
184
|
Each declared agent has exactly one token slot. Keep `accessTokens(storage)`
|
|
@@ -727,8 +787,17 @@ bypasses the scoped registry. These refusals happen before any request leaves. A
|
|
|
727
787
|
never reach it; a deployment's own connectors still cannot read the caller.
|
|
728
788
|
|
|
729
789
|
Access is read from one place, deployment code. Top-level
|
|
730
|
-
`maintainers` lists the people who
|
|
731
|
-
|
|
790
|
+
`maintainers` lists the people who receive the whole catalog at plain `/mcp`
|
|
791
|
+
with the deployment's `readOnly`, and who reach the operator routes. A
|
|
792
|
+
non-maintainer person listed in exactly one pool receives that pool at plain
|
|
793
|
+
`/mcp` exactly as `/mcp/<pool>` serves it: its `tools`, `readOnly`, `surface`,
|
|
794
|
+
`executeCode` and `description`. A maintainer keeps the whole catalog even when
|
|
795
|
+
also listed in pools. Membership is matched on the admitted, resolved
|
|
796
|
+
`{ namespace, id }`, so a bare `"bob"` and the explicit reference it resolves
|
|
797
|
+
to are the same person, and the same id in another namespace is not. Authentication, OAuth challenges and resource audiences are plain `/mcp`'s;
|
|
798
|
+
the pool is chosen after authentication. Construction warns, naming each
|
|
799
|
+
non-maintainer person listed in several pools, and `describeConfig()` lists them
|
|
800
|
+
in `peopleInSeveralPools`. Each pool's
|
|
732
801
|
`members` lists who reaches that pool, people at `/mcp/<pool>` and agents at
|
|
733
802
|
`/agent/<pool>`, which serve exactly that pool's `tools`. A maintainer opens no pool implicitly; list them as a member to use
|
|
734
803
|
one. Nothing intersects these lists with anything else, and a request never
|
|
@@ -776,6 +845,12 @@ replacement. A guarded read grant becomes a pool with `readOnly: true`.
|
|
|
776
845
|
|
|
777
846
|
### Team pools
|
|
778
847
|
|
|
848
|
+
Every person in exactly one pool can configure plain `/mcp`; their sign-in
|
|
849
|
+
selects that pool, so one client configuration serves every team. A person in
|
|
850
|
+
several pools uses each pool's `/mcp/<pool>`. Only people pools count: a pool
|
|
851
|
+
lists only people or only agents, so an agent's pools never change where a
|
|
852
|
+
person's plain `/mcp` routes.
|
|
853
|
+
|
|
779
854
|
Keep membership in deployment code; static lists can be built with ordinary
|
|
780
855
|
code at construction:
|
|
781
856
|
|
|
@@ -800,9 +875,9 @@ Marketing's every `campaigns` tool and only `issues.search`. Neither pool
|
|
|
800
875
|
serves `billing`; only the owner's plain `/mcp` does. A person in both pools
|
|
801
876
|
uses two endpoints, never a union.
|
|
802
877
|
|
|
803
|
-
A team pool
|
|
804
|
-
|
|
805
|
-
([
|
|
878
|
+
A human team pool and an agent tier are separate pools. Share a tool-list
|
|
879
|
+
constant between them, with people in `team` and only agent references in
|
|
880
|
+
`assistant-team` ([pool layout](#organizing-an-agents-pools)).
|
|
806
881
|
|
|
807
882
|
| Catalog change | Exact `connector.tool` in a pool |
|
|
808
883
|
| ----------------------- | ----------------------------------------------------------------------- |
|
|
@@ -891,16 +966,16 @@ warning says, every pool path answers the 404 because nobody can be a member,
|
|
|
891
966
|
and every agent endpoint answers the machine-only 401 because no agent can be
|
|
892
967
|
admitted.
|
|
893
968
|
|
|
894
|
-
| Rule | Contract
|
|
895
|
-
| ------------ |
|
|
896
|
-
| Scope | `/mcp/<pool>` and `/agent/<pool>` serve exactly the pool's `tools` (connector ids or exact `connector.tool` addresses), with the pool's `readOnly`, to its member people and member agents respectively. Plain `/mcp` serves the whole catalog to maintainers. Neither intersects with or widens anything.
|
|
897
|
-
| Refusal | A
|
|
898
|
-
| Agents | An agent member reaches its pools only at `/agent/<pool>`, with its `cta_` token. Human endpoints refuse `cta_`; see [agent endpoints](#agent-endpoints).
|
|
899
|
-
| Surface | From 0.35.0, optional `surface`: `"meta"` (default) serves the six meta-tools; `"native"` lists each of the pool's downstream tools directly, for clients with their own tool search, with optional `executeCode: false` to replace `execute_code` with `read_result` (invalid on meta pools). Calls take the same paths either way. See the [native tool surface](./meta-tools.md#native-tool-surface).
|
|
900
|
-
| Description | Optional `description`: plain text telling the model what this endpoint is for. `/mcp/<pool>` and `/agent/<pool>` append it to the MCP `initialize` instructions after `This endpoint: `; plain `/mcp` and a pool without one serve the shared instructions unchanged. It grants, hides, and refuses nothing, and changes no tool or tool description.
|
|
901
|
-
| Construction | Malformed names, unknown options or connectors, empty pools, empty or missing `members`, unresolvable member references, a non-boolean `readOnly`, an `owner` that is not a Workspace email address, a `description` that is not 1 to 500 characters of one-line text after trimming, a retired `trust` or `grant`, and addresses absent from an `api()` static catalog throw. Remote catalogs are checked at lazy load. |
|
|
902
|
-
| 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.
|
|
903
|
-
| Drift | No wildcards; pool tools are exact names. Missing live tools are unreachable and warned once while in a bounded 1,024-entry FIFO.
|
|
969
|
+
| Rule | Contract |
|
|
970
|
+
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
971
|
+
| Scope | `/mcp/<pool>` and `/agent/<pool>` serve exactly the pool's `tools` (connector ids or exact `connector.tool` addresses), with the pool's `readOnly`, to its member people and member agents respectively. Plain `/mcp` serves the whole catalog to maintainers and the unique member pool to other people. Neither intersects with or widens anything. |
|
|
972
|
+
| Refusal | A person with zero or several memberships at `/mcp`, a non-member, and an undeclared pool get an identical 404 status, body, and headers, after auth. The operator log records `not_maintainer` (zero memberships), `ambiguous_pool` (several memberships), `not_pool_member`, or `pool_not_declared`. Names are not anonymously enumerable; valid credentials cannot distinguish these cases by content. |
|
|
973
|
+
| Agents | An agent member reaches its pools only at `/agent/<pool>`, with its `cta_` token. Human endpoints refuse `cta_`; see [agent endpoints](#agent-endpoints). |
|
|
974
|
+
| Surface | From 0.35.0, optional `surface`: `"meta"` (default) serves the six meta-tools; `"native"` lists each of the pool's downstream tools directly, for clients with their own tool search, with optional `executeCode: false` to replace `execute_code` with `read_result` (invalid on meta pools). Calls take the same paths either way. See the [native tool surface](./meta-tools.md#native-tool-surface). |
|
|
975
|
+
| Description | Optional `description`: plain text telling the model what this endpoint is for. `/mcp/<pool>` and `/agent/<pool>` append it to the MCP `initialize` instructions after `This endpoint: `; a maintainer at plain `/mcp` and a pool without one serve the shared instructions unchanged. A routed person receives their pool description. It grants, hides, and refuses nothing, and changes no tool or tool description. |
|
|
976
|
+
| Construction | Malformed names, unknown options or connectors, empty pools, empty or missing `members`, mixed people and agent membership (a `ConfigError` naming the pool), unresolvable member references, a non-boolean `readOnly`, an `owner` that is not a Workspace email address, a `description` that is not 1 to 500 characters of one-line text after trimming, a retired `trust` or `grant`, and addresses absent from an `api()` static catalog throw. Remote catalogs are checked at lazy load. |
|
|
977
|
+
| 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. |
|
|
978
|
+
| Drift | No wildcards; pool tools are exact names. Missing live tools are unreachable and warned once while in a bounded 1,024-entry FIFO. |
|
|
904
979
|
|
|
905
980
|
Pool descriptions are trimmed before validation. The remaining text must be
|
|
906
981
|
1 to 500 characters and contain no C0 or C1 control characters (including tabs,
|
|
@@ -947,7 +1022,12 @@ Protected-resource metadata stays public at both default aliases,
|
|
|
947
1022
|
Discovery does not authenticate callers or evaluate membership, so fresh
|
|
948
1023
|
OAuth clients can discover how to authenticate. Admission applies after auth
|
|
949
1024
|
to MCP requests and result/page handles. Handles remain bound to their
|
|
950
|
-
originating
|
|
1025
|
+
originating requested pathname and admitted pool; paging re-admits the caller
|
|
1026
|
+
at that endpoint and re-resolves their pool, which must still match. A handle
|
|
1027
|
+
minted at plain `/mcp` never pages at `/mcp/<pool>` or the reverse, even for the
|
|
1028
|
+
same pool, and a membership change fails it closed. Handles minted before this
|
|
1029
|
+
binding existed fail closed. Auth-elicitation and downstream-input
|
|
1030
|
+
continuations carry the same pathname and pool binding. A pool
|
|
951
1031
|
cannot read a `/mcp` handle, and `/mcp` cannot read a pool's handle.
|
|
952
1032
|
|
|
953
1033
|
Operator catalog (`/ui/data`, `/ui/connectors/<id>`), activity, shared
|
|
@@ -964,9 +1044,10 @@ activity. Anyone else gets the same 404, and an agent token the machine-only
|
|
|
964
1044
|
shell carries no catalog data.
|
|
965
1045
|
|
|
966
1046
|
Each endpoint is writable or read-only. The deployment's `readOnly` sets the
|
|
967
|
-
plain `/mcp` endpoint and is required: a deployment that omits it fails at
|
|
1047
|
+
plain `/mcp` endpoint for maintainers and is required: a deployment that omits it fails at
|
|
968
1048
|
construction and must choose `true` or `false`. A pool inherits it unless the
|
|
969
|
-
pool sets its own `readOnly`, in either direction. A
|
|
1049
|
+
pool sets its own `readOnly`, in either direction. A person routed to their
|
|
1050
|
+
pool at plain `/mcp` gets that pool's effective `readOnly`. A writable endpoint lets
|
|
970
1051
|
`call_destructive_tool` and programs write, and the MCP client decides what to
|
|
971
1052
|
approve. On a read-only endpoint, write tools are absent from `search_tools`
|
|
972
1053
|
results, `connecta.search`, and `connecta.describe`, and `call_tool`,
|
|
@@ -997,9 +1078,9 @@ for agents, just as an `authScope: "personal"` connector is: `search_tools`,
|
|
|
997
1078
|
`connecta.search`, `connecta.describe`, `skills`, and connector discovery omit
|
|
998
1079
|
it, `authorize_connector` answers "Unknown connector", and a call by address
|
|
999
1080
|
fails `unknown_address`, the same as for a connector that does not exist.
|
|
1000
|
-
People
|
|
1001
|
-
|
|
1002
|
-
|
|
1081
|
+
People use a separate people-only pool, where they see the connector and
|
|
1082
|
+
act as themselves. Share the same tool-list constant between the people pool
|
|
1083
|
+
and an agent-only pool when both need the same tools.
|
|
1003
1084
|
|
|
1004
1085
|
`owner` is a Workspace email address, checked at construction and listed in
|
|
1005
1086
|
`describeConfig()` (`null` when unset). Over HTTP, `/ui/api/config` serves it
|
|
@@ -1025,7 +1106,7 @@ createConnecta({
|
|
|
1025
1106
|
pools: Object.fromEntries(
|
|
1026
1107
|
team.map((person) => [
|
|
1027
1108
|
`assistant-${person.handle}`,
|
|
1028
|
-
{ tools: ["gmail", "drive"], owner: person.email, members: [
|
|
1109
|
+
{ tools: ["gmail", "drive"], owner: person.email, members: ["agent:assistant"] },
|
|
1029
1110
|
]),
|
|
1030
1111
|
),
|
|
1031
1112
|
connectors: [
|
|
@@ -1037,12 +1118,11 @@ createConnecta({
|
|
|
1037
1118
|
```
|
|
1038
1119
|
|
|
1039
1120
|
The agent harness, not the model, picks the pool for each turn, which is what
|
|
1040
|
-
keeps one person's mailbox from another's.
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
([members of a personal pool](#organizing-an-agents-pools)).
|
|
1121
|
+
keeps one person's mailbox from another's. Personal agent pools list only
|
|
1122
|
+
the agent, whether or not they set an `owner`. People use separate pools;
|
|
1123
|
+
their Workspace calls use the mapping and always act as themselves. A shared-auth
|
|
1124
|
+
connector holding a user's own account runs as that account for every admitted
|
|
1125
|
+
agent ([personal pools](#organizing-an-agents-pools)).
|
|
1046
1126
|
|
|
1047
1127
|
A fixed `subject` address is the explicit shared-account exception: every
|
|
1048
1128
|
caller, person or agent, acts as it in every pool that lists the connector,
|
|
@@ -1067,10 +1147,10 @@ section holds the complete rules.
|
|
|
1067
1147
|
2. **Tiers.** An agent's tiers are named pools ordered from least to most
|
|
1068
1148
|
capable. Recommended names are `<agent>` for the lowest tier and
|
|
1069
1149
|
`<agent>-<tier>` for higher tiers (`assistant-team`, `assistant-leads`).
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1150
|
+
Every tier is agent-only. A human team with the same reach uses a separate
|
|
1151
|
+
pool sharing the same tool-list constant (rule 10). An agent with one tier
|
|
1152
|
+
uses it as the lowest. Tiers never hold per-user connectors and never set
|
|
1153
|
+
an `owner`.
|
|
1074
1154
|
3. **Personal pool `<agent>-<user>`.** `/agent/<agent>-<user>` serves pool
|
|
1075
1155
|
`<agent>-<user>`: the highest tier that user is cleared for, plus that
|
|
1076
1156
|
user's own connectors. Use it only in a one-on-one with that user.
|
|
@@ -1103,26 +1183,24 @@ section holds the complete rules.
|
|
|
1103
1183
|
reaches every tier above. For example, billing restricted to leads goes
|
|
1104
1184
|
in the leads tier. Per-user connectors (`notes_alice`) go only in personal
|
|
1105
1185
|
pools, and so do the Workspace connectors with a mapping `subject` that
|
|
1106
|
-
the agent uses
|
|
1186
|
+
the agent uses. People use a separate pool for their own accounts.
|
|
1107
1187
|
8. **Changes stay in config.** Adding an account for a user adds its
|
|
1108
1188
|
connector and lists it in that user's pool `tools`. Adding a user adds one
|
|
1109
1189
|
`<agent>-<user>` pool. Adding a tier adds one pool and one constant, and
|
|
1110
1190
|
the tier's place in the harness's tier list.
|
|
1111
|
-
9. **
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
are absent for the agent, since a tier has no owner and an agent has no
|
|
1125
|
-
principal.
|
|
1191
|
+
9. **Personal pools list only the agent.** An `<agent>-<user>` pool lists
|
|
1192
|
+
only `"agent:<agent>"`, with or without an `owner`. No person belongs
|
|
1193
|
+
there: mixed membership throws `ConfigError` at construction. A person's
|
|
1194
|
+
own use goes through a separate people-only pool or plain `/mcp`.
|
|
1195
|
+
10. **People use separate team pools.** A tier is never a human team pool.
|
|
1196
|
+
Define `teamTools` once, then use it for `team` with people in
|
|
1197
|
+
`members` and `assistant-team` with only the agent. People open
|
|
1198
|
+
`/mcp/team`; the agent opens `/agent/assistant-team`. Every person
|
|
1199
|
+
listed must be cleared for every shared connector in their pool.
|
|
1200
|
+
The human pool may also list Workspace mapping connectors and
|
|
1201
|
+
`authScope: "personal"` connectors, on which each person acts as
|
|
1202
|
+
themselves. Keep them out of the tier: they are absent for agents,
|
|
1203
|
+
since an agent has no principal and a tier has no owner.
|
|
1126
1204
|
|
|
1127
1205
|
**Choosing the pool each turn**
|
|
1128
1206
|
|
|
@@ -1176,8 +1254,9 @@ createConnecta({
|
|
|
1176
1254
|
pools: {
|
|
1177
1255
|
// Lowest tier and fallback: reads guests may use.
|
|
1178
1256
|
assistant: { tools: guestTools, readOnly: true, members: [agent] },
|
|
1179
|
-
//
|
|
1180
|
-
|
|
1257
|
+
// Same reach, separate membership: people and the agent each have a pool.
|
|
1258
|
+
team: { tools: teamTools, members: ["user_alice", "user_bob"] },
|
|
1259
|
+
"assistant-team": { tools: teamTools, members: [agent] },
|
|
1181
1260
|
"assistant-leads": { tools: leadsTools, members: [agent] },
|
|
1182
1261
|
// Each personal pool spreads its user's highest tier.
|
|
1183
1262
|
"assistant-alice": {
|
|
@@ -1235,8 +1314,8 @@ gets `assistant`, even if the platform marks the chat as direct. Rule 9 is
|
|
|
1235
1314
|
why each personal pool lists only the agent: people act as themselves only
|
|
1236
1315
|
through a Workspace mapping, and a shared-auth connector such as
|
|
1237
1316
|
`notes_alice` acts as Alice for whoever the pool admits.
|
|
1238
|
-
Rule 10
|
|
1239
|
-
|
|
1317
|
+
Rule 10 gives Alice and Bob a separate `team` pool sharing `teamTools`;
|
|
1318
|
+
`poolFor` selects only agent pools and its tier selection stays the same.
|
|
1240
1319
|
|
|
1241
1320
|
## Shared and personal auth
|
|
1242
1321
|
|
|
@@ -1257,8 +1336,8 @@ A personal connector is absent — not refused — from any request without a
|
|
|
1257
1336
|
stable namespaced principal. For one that can see it, connector storage, vault
|
|
1258
1337
|
records, OAuth generations, and observed result shapes are
|
|
1259
1338
|
partitioned under an opaque SHA-256 identity key. Remote catalog cache keys
|
|
1260
|
-
include that owner and the admitted pool, plus the credential digest and
|
|
1261
|
-
epoch; refreshes coalesce within
|
|
1339
|
+
include that owner and the admitted pool, plus the keyed credential digest and
|
|
1340
|
+
OAuth epoch (see [cache keys](./architecture.md#storage-credentials-and-connectors)); refreshes coalesce within
|
|
1262
1341
|
one owner, and another owner's epoch cannot retire them. Keep namespaces and
|
|
1263
1342
|
principal ids stable across upgrades, since changing either selects different
|
|
1264
1343
|
partitions. Literal `auth: { type: "headers" }` keeps its secret in deployment
|
|
@@ -1286,16 +1365,24 @@ connector](#downstream-oauth-on-api) stores no client. A vault without the
|
|
|
1286
1365
|
optional `seal`/`open` members keeps the body plaintext with a startup
|
|
1287
1366
|
warning; no vault, plaintext as always.
|
|
1288
1367
|
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1368
|
+
The body also holds `expiresAt`, the epoch milliseconds the stored access token
|
|
1369
|
+
expires: its `expires_in`, counted from just before the request that issued it
|
|
1370
|
+
was sent (the code exchange, or the refresh's token request), never from when
|
|
1371
|
+
its answer arrived or was saved, so a slow answer cannot push the expiry past
|
|
1372
|
+
the token's real one. An `expires_in` of `0` expires at issuance. It is written
|
|
1373
|
+
with the tokens it describes, replaced with them and dropped with them, in the
|
|
1374
|
+
sealed and plaintext layouts alike. A grant stored before this field existed,
|
|
1375
|
+
or one whose token response carried no `expires_in`, has none. Reading a grant
|
|
1376
|
+
never adds it (INV-10), so such a grant gains one only at its next token write,
|
|
1377
|
+
and until then is refreshed only after a 401.
|
|
1378
|
+
|
|
1379
|
+
**Pre-layout-3 grants.** The layout-2 migration and historical-key cleanup
|
|
1380
|
+
are removed. An owner with only old keys is treated as unauthorized; a warning
|
|
1381
|
+
names the unsupported layout and the need to Connect again or migrate on
|
|
1382
|
+
0.37.1. The check inspects key names only, never old credential values, and
|
|
1383
|
+
leaves them untouched. Existing layout-3 records remain readable even with an
|
|
1384
|
+
obsolete cleanup marker. Complete every owner's grant migration on 0.37.1
|
|
1385
|
+
before upgrading, or reconnect afterward; follow [Retired layout migrations](./migration-retired-layouts.md).
|
|
1299
1386
|
|
|
1300
1387
|
## Starting, restarting, and disconnecting
|
|
1301
1388
|
|
|
@@ -1397,12 +1484,8 @@ exchange. Both built-in `finishAuth` adapters require `callbackParams` with a
|
|
|
1397
1484
|
nonempty `state`, even with PKCE disabled or after a separate `verifyState` call.
|
|
1398
1485
|
A missing state is refused before discovery, registration, or token dispatch.
|
|
1399
1486
|
|
|
1400
|
-
|
|
1401
|
-
historical keys.
|
|
1402
|
-
marker by CAS only when deletion succeeds. Restart preserves this obligation.
|
|
1403
|
-
Disconnect commits a tombstone with the marker and always removes historical
|
|
1404
|
-
keys for the connector and owner, including when a modern grant already exists.
|
|
1405
|
-
A crash during cleanup cannot restore the disconnected grant.
|
|
1487
|
+
Disconnect commits a layout-3 tombstone. Restart and Disconnect never read or
|
|
1488
|
+
restore historical layout-2 keys.
|
|
1406
1489
|
|
|
1407
1490
|
Disconnect first removes the grant and fences its epoch with CAS. When that
|
|
1408
1491
|
removed grant's saved authorization-server metadata advertises a revocation
|
|
@@ -1426,6 +1509,47 @@ removal and revocation flow when deployment configuration supplies
|
|
|
1426
1509
|
`revocationEndpoint`; without it, removal remains local. The configured
|
|
1427
1510
|
client secret is used in memory and is never saved with the grant.
|
|
1428
1511
|
|
|
1512
|
+
## Refreshing before expiry
|
|
1513
|
+
|
|
1514
|
+
A call refreshes the grant before it sends a token that is within 60 seconds of
|
|
1515
|
+
its stored `expiresAt`, so a write never meets the 401 it could not be repeated
|
|
1516
|
+
after (INV-9). Sixty seconds covers the refresh's own 20-second token-request
|
|
1517
|
+
deadline and the call's send with margin, and matches the early refresh of
|
|
1518
|
+
Google service-account tokens; for a typical one-hour token it is under 2% of
|
|
1519
|
+
its life. A token that lives under a minute, or arrives already expired, is
|
|
1520
|
+
refreshed once per call, in the invocation's check. Later in the same request,
|
|
1521
|
+
`callTool` and `ctx.oauth.fetch` send the token that renewal produced without
|
|
1522
|
+
renewing it again until its stored expiry passes (or, for a token already
|
|
1523
|
+
expired when issued, for the next check only); a token another request rotated
|
|
1524
|
+
in since is checked as usual.
|
|
1525
|
+
|
|
1526
|
+
The renewal is the flow a 401 would start, through the same refresh
|
|
1527
|
+
coordinator: one redemption per owner partition and epoch in an isolate, the
|
|
1528
|
+
shared-storage lease and spent-fingerprint CAS across isolates, and the commit
|
|
1529
|
+
before anyone is released. Its token request names the access token it
|
|
1530
|
+
replaces, so a caller whose own read came before another request's rotation
|
|
1531
|
+
adopts that rotation instead of redeeming again. Two isolates that find the
|
|
1532
|
+
same token due spend one refresh token between them.
|
|
1533
|
+
|
|
1534
|
+
| Where | When | A failed renewal |
|
|
1535
|
+
| --------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1536
|
+
| The invocation's authorization check, before it enters the connector | Every `call_tool`, `call_destructive_tool`, and program call, after the grant read that check already makes | Fails the call before anything is sent: `downstream_oauth_required` (or retryable `unavailable` for a waiter deadline), with no `writeOutcome` and no `reconciliationRequired`. |
|
|
1537
|
+
| `remoteMcp()`'s `callTool`, before it sends `tools/call` on a reused client | The token the connection last read has come due since | Ends the call before `tools/call` is sent. A call already entered reports it as the invocation reports any failure after entry. |
|
|
1538
|
+
| `api()`'s `ctx.oauth.fetch`, before each request | The token the request just read is due, as in a handler that runs long | Is thrown into the handler, which may already have sent other requests, so a write keeps the invocation's conservative outcome. |
|
|
1539
|
+
|
|
1540
|
+
Whether a token is due is decided from the grant read the step already made: a
|
|
1541
|
+
grant not yet due costs no extra storage read. A due token costs one more grant
|
|
1542
|
+
read for the renewal's own flow, and then the refresh's own reads and writes.
|
|
1543
|
+
Status, listing (including the stored-catalog read without a connection),
|
|
1544
|
+
discovery, the operator UI, and `authorize_connector` never renew a token
|
|
1545
|
+
(INV-10). Nothing renews in the background: renewal belongs to the call that
|
|
1546
|
+
found the token due, which owns its preparation until the claim CAS is
|
|
1547
|
+
submitted, as for a 401.
|
|
1548
|
+
|
|
1549
|
+
A grant without `expiresAt` keeps the earlier behavior: it is refreshed after a
|
|
1550
|
+
downstream 401, a write's 401 is never resent, and the refresh that follows
|
|
1551
|
+
stores `expiresAt` for every later call.
|
|
1552
|
+
|
|
1429
1553
|
## Refresh failures
|
|
1430
1554
|
|
|
1431
1555
|
A refresh token is spent when dispatch begins. Every refresh request passes
|
|
@@ -1434,7 +1558,7 @@ lease's `claimed` to `dispatched` CAS and a separate fingerprint CAS at
|
|
|
1434
1558
|
`oauth:refresh-spent:<sha256(refresh_token)>`. The latter must succeed before
|
|
1435
1559
|
any HTTP request leaves. Connector and owner storage namespaces partition both
|
|
1436
1560
|
records. Spent records have **no TTL**, contain no token, and are never deleted
|
|
1437
|
-
by completion, failure,
|
|
1561
|
+
by completion, failure, Restart, Disconnect, or epoch sweeps.
|
|
1438
1562
|
Copy these durable records during storage migration.
|
|
1439
1563
|
|
|
1440
1564
|
Each fingerprint record names its dispatch epoch, holder, and resolution state:
|
|
@@ -1500,10 +1624,20 @@ a dispatched record. The refresh HTTP request and response body have a
|
|
|
1500
1624
|
20-second deadline. After the response or a dispatched exchange failure,
|
|
1501
1625
|
grant commit and cleanup share a separate 20-second deadline, including all
|
|
1502
1626
|
storage reads, CAS retries, fingerprint resolution, and lease release.
|
|
1503
|
-
Contenders wait at most 35 seconds
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1627
|
+
Contenders wait at most 35 seconds, and never past the caller's own signal,
|
|
1628
|
+
which carries the request's deadline. A waiter polls the lease from 50 ms,
|
|
1629
|
+
doubling to at most 2 seconds between polls, so a wait that runs the full 35
|
|
1630
|
+
seconds polls at most 22 times. Each poll is one batched storage read of the
|
|
1631
|
+
lease, the grant, the spent fingerprint, and the holder's liveness key named by
|
|
1632
|
+
the previous poll (one D1 statement); a store without batched reads reads them
|
|
1633
|
+
one at a time, lease first, so a completed lease is never paired with the grant
|
|
1634
|
+
from before its commit. A waiter deadline returns retryable `unavailable`,
|
|
1635
|
+
keeps the grant, and starts no consent. It never permits resending a spent
|
|
1636
|
+
token.
|
|
1637
|
+
|
|
1638
|
+
A grant with `expiresAt` is normally renewed before its token expires
|
|
1639
|
+
([Refreshing before expiry](#refreshing-before-expiry)). A token revoked early,
|
|
1640
|
+
or one stored without an expiry, can still draw a 401.
|
|
1507
1641
|
A `remoteMcp()` write the downstream answered with HTTP 401 is never sent
|
|
1508
1642
|
again (INV-9). The SDK still refreshes the grant, and connecta stops its
|
|
1509
1643
|
resend before it leaves, so any fresh call after the target is reconciled uses
|
|
@@ -1981,8 +2115,8 @@ anything, so SDK hook order cannot mix servers or cross a reset.
|
|
|
1981
2115
|
|
|
1982
2116
|
Management follows `maintainers` and pool `members`; no resolver configures it. The former `identity.credentialAdministration`,
|
|
1983
2117
|
`identity.personalConnection`, `identity.accessTokenManagement`, and
|
|
1984
|
-
`identity.activityAccess`
|
|
1985
|
-
|
|
2118
|
+
`identity.activityAccess` fail construction as unknown options under `identity`,
|
|
2119
|
+
including an empty `identity` section.
|
|
1986
2120
|
|
|
1987
2121
|
| Action | Who may |
|
|
1988
2122
|
| ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -2417,6 +2551,15 @@ Before an accepted retry restarts any call, it rechecks every entered read.
|
|
|
2417
2551
|
A write classification, changed classification digest, or stale catalog returns
|
|
2418
2552
|
`auth_replay_refused` with reconciliation guidance, including in programs on a writable endpoint.
|
|
2419
2553
|
Pre-invocation credential-resolution eligibility is unchanged.
|
|
2554
|
+
Custom vaults may implement the host-only `storageDigestKey` method, returning a
|
|
2555
|
+
non-extractable HMAC-SHA-256 signing `CryptoKey` that is stable across the
|
|
2556
|
+
deployment's processes, purpose-specific and never stored.
|
|
2557
|
+
`encryptedCredentialVault()` derives it with HKDF from its credential key.
|
|
2558
|
+
Remote catalog, negotiation-verdict and connector-health keys digest their
|
|
2559
|
+
inputs (configured URLs and headers, credentials, request tokens, OAuth token
|
|
2560
|
+
responses, described configuration) with it, so their partitions are
|
|
2561
|
+
deployment-keyed. Without it they use a random per-process key: partitions stay
|
|
2562
|
+
exact, but each process or isolate keeps its own entries.
|
|
2420
2563
|
Custom vaults may implement the host-only `requestStateKey` method; without it, the
|
|
2421
2564
|
deployment retains the ordinary handoff. It must return a stable,
|
|
2422
2565
|
purpose-specific deployment key of at least 32 bytes. Only the MCP boundary
|
|
@@ -2722,6 +2865,9 @@ owner's access token as `Authorization: Bearer` with these rules:
|
|
|
2722
2865
|
|
|
2723
2866
|
- Only `apiOrigins` receive tokens; refuse other origins before sending, including
|
|
2724
2867
|
untrusted response URLs. Return redirects unfollowed; handlers cannot set `Authorization`.
|
|
2868
|
+
- A token within a minute of its stored expiry is renewed before the handler
|
|
2869
|
+
runs, and again before any request that finds it due
|
|
2870
|
+
([Refreshing before expiry](#refreshing-before-expiry)).
|
|
2725
2871
|
- On 401, recover exactly once using an already rotated token or one coalesced
|
|
2726
2872
|
refresh through `remoteMcp()`'s coordinator. Persist rotation even when its
|
|
2727
2873
|
owner is cancelled after the answer. Only a positively read-classified
|
|
@@ -3127,10 +3273,6 @@ The encoding lattice is exactly this, and anything outside it is out of scope:
|
|
|
3127
3273
|
lattice, so redaction is not a defense against a downstream that reshapes a
|
|
3128
3274
|
value on purpose.
|
|
3129
3275
|
|
|
3130
|
-
`test/sent-secret-output-boundary.test.ts` holds the evidence, including its
|
|
3131
|
-
`review round 3`, `review round 4` and `review round 5` blocks for these limits;
|
|
3132
|
-
`test/writeonly-wire-redaction.node.test.ts` runs the QuickJS guest cases.
|
|
3133
|
-
|
|
3134
3276
|
### Source and tests
|
|
3135
3277
|
|
|
3136
3278
|
[Decision 0007](https://github.com/zackbart/connecta/blob/main/decisions/0007-agent-pool-layout.md)
|
|
@@ -3151,10 +3293,9 @@ for credential precedence and disclosure boundaries.
|
|
|
3151
3293
|
|
|
3152
3294
|
Downstream OAuth implementation and evidence are in
|
|
3153
3295
|
[downstream-oauth.ts](https://github.com/zackbart/connecta/blob/main/src/auth/downstream-oauth.ts),
|
|
3296
|
+
its [seam modules](https://github.com/zackbart/connecta/tree/main/src/auth/downstream-oauth),
|
|
3154
3297
|
[static-oauth.ts](https://github.com/zackbart/connecta/blob/main/src/auth/static-oauth.ts),
|
|
3155
3298
|
[OAuth connect tests](https://github.com/zackbart/connecta/blob/main/test/oauth-connect.test.ts),
|
|
3156
|
-
[callback fencing](https://github.com/zackbart/connecta/blob/main/test/oauth-callback-fencing.test.ts),
|
|
3157
|
-
[refresh leases](https://github.com/zackbart/connecta/blob/main/test/oauth-refresh-lease.test.ts),
|
|
3158
3299
|
and [API OAuth tests](https://github.com/zackbart/connecta/blob/main/test/api-oauth.test.ts).
|
|
3159
3300
|
Use the test pointers within each section for narrower failure cases.
|
|
3160
3301
|
|