@zackbart/connecta 0.34.1 → 0.36.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 +418 -0
- package/README.md +65 -105
- package/SECURITY.md +3 -2
- package/bin/connecta.mjs +41 -16
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.d.ts +2 -1
- package/dist/access-tokens.js +66 -30
- package/dist/activity.d.ts +1 -1
- package/dist/activity.js +2 -0
- package/dist/agent-config.d.ts +23 -0
- package/dist/agent-config.js +59 -0
- package/dist/agent-description.js +16 -0
- package/dist/agent-identity.js +4 -4
- package/dist/agent-observation.js +39 -0
- package/dist/auth/clerk-sdk/client.d.ts +2 -0
- package/dist/auth/clerk-sdk/generated.js +554 -563
- package/dist/auth/clerk-transport.js +70 -3
- package/dist/auth/clerk.d.ts +13 -0
- package/dist/auth/clerk.js +653 -246
- package/dist/auth/cloudflare-access.js +46 -42
- package/dist/auth/downstream-client-metadata.js +2 -2
- package/dist/auth/downstream-oauth.js +2044 -1161
- package/dist/auth/downstream-revocation.js +88 -88
- package/dist/auth/native-call.js +20 -0
- package/dist/auth/native.js +184 -0
- package/dist/auth/oauth-start-reset.js +11 -5
- package/dist/auth/oauth-token-response.js +121 -102
- package/dist/auth/oauth-v2-migration.js +97 -78
- package/dist/auth/renewable-grant.js +10 -0
- package/dist/auth/static-oauth.js +751 -281
- package/dist/auth/vercel-agent.js +266 -0
- package/dist/auth-admission.js +204 -0
- package/dist/auth-elicitation.js +350 -241
- package/dist/authorization-handoff.js +164 -90
- package/dist/call-admission-context.js +3 -10
- package/dist/call-admission.js +2 -0
- package/dist/call-classification.js +9 -0
- package/dist/call-recovery.js +32 -13
- package/dist/canonical-json.js +8 -0
- package/dist/catalog-cache.js +369 -213
- package/dist/catalog-service.js +659 -530
- package/dist/catalog.js +5 -1
- package/dist/concurrency.js +0 -25
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +3 -1
- package/dist/config-schema.js +13 -3
- package/dist/config.d.ts +40 -6
- package/dist/config.js +40 -26
- package/dist/connector-caller.d.ts +4 -0
- package/dist/connector-caller.js +17 -4
- package/dist/connectors/api-connector.d.ts +29 -7
- package/dist/connectors/api-connector.js +145 -57
- package/dist/connectors/guarded-fetch.d.ts +51 -17
- package/dist/connectors/guarded-fetch.js +215 -121
- package/dist/connectors/negotiation-cache.js +20 -10
- package/dist/connectors/remote-mcp.d.ts +10 -30
- package/dist/connectors/remote-mcp.js +955 -563
- package/dist/d1.js +10 -243
- package/dist/derived-context.js +5 -0
- package/dist/describe-config.d.ts +3 -1
- package/dist/describe-config.js +2 -0
- package/dist/downstream-input.js +347 -260
- package/dist/errors.d.ts +87 -6
- package/dist/errors.js +114 -4
- package/dist/execute.js +87 -50
- package/dist/executor-admission.js +3 -0
- package/dist/executors/quickjs.js +163 -112
- package/dist/guarded-transport.js +50 -0
- package/dist/guest-runtime.js +1 -1
- package/dist/http/client.js +439 -0
- package/dist/http/errors.js +558 -0
- package/dist/http/fields.js +49 -0
- package/dist/http/hatch-paging.js +318 -0
- package/dist/http/hatch.js +1279 -0
- package/dist/http/json-schema.js +267 -0
- package/dist/http/node-transport.js +241 -0
- package/dist/http/oauth.js +162 -0
- package/dist/http/paging.js +227 -0
- package/dist/http/provider.js +221 -0
- package/dist/http/safety.js +82 -0
- package/dist/http/services.js +79 -0
- package/dist/http/tools.js +379 -0
- package/dist/http/vendor-index.js +138 -0
- package/dist/http/vendor.js +448 -0
- package/dist/http/write-request.js +66 -0
- package/dist/http-policy.d.ts +11 -0
- package/dist/http-policy.js +45 -0
- package/dist/inbound-credential.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -0
- package/dist/invocation-auth.js +30 -19
- package/dist/invocation.js +218 -85
- package/dist/mcp-result.js +0 -4
- package/dist/membership.d.ts +4 -0
- package/dist/meta-output.js +1 -1
- package/dist/meta-tools.js +531 -406
- package/dist/native-recovery.js +52 -0
- package/dist/native-tools.js +382 -0
- package/dist/node.d.ts +4 -3
- package/dist/node.js +45 -8
- package/dist/oauth-handoff.js +177 -104
- package/dist/oauth-partition.js +6 -20
- package/dist/oauth-sealing.js +6 -7
- package/dist/operation-vocabulary.js +101 -0
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +17 -4
- package/dist/operator-ui/contract.d.ts +8 -3
- package/dist/operator-ui/generated.js +4 -4
- package/dist/operator-ui/model.d.ts +2 -3
- package/dist/program-writes.js +47 -26
- package/dist/provider-call-facts.d.ts +115 -0
- package/dist/provider-call-facts.js +182 -0
- package/dist/provider-call.d.ts +112 -0
- package/dist/provider-call.js +271 -0
- package/dist/provider.d.ts +65 -31
- package/dist/provider.js +116 -39
- package/dist/providers/_shared/google/hatch.js +91 -0
- package/dist/providers/_shared/google/kit.js +427 -0
- package/dist/providers/_shared/google/result-size.js +15 -2
- package/dist/providers/_shared/google/service-account.d.ts +2 -1
- package/dist/providers/_shared/google/service-account.js +8 -4
- package/dist/providers/_shared/google/workspace.d.ts +6 -210
- package/dist/providers/_shared/google/workspace.js +6 -425
- package/dist/providers/_shared/rest/compact-index.js +86 -0
- package/dist/providers/_shared/rest/kit.js +146 -0
- package/dist/providers/_shared/rest/operation-index.js +230 -14
- package/dist/providers/_shared/rest/response-media.js +11 -0
- package/dist/providers/_shared/rest/value-safety.js +353 -195
- package/dist/providers/_shared/sensitive-fields.js +27 -0
- package/dist/providers/breeze/api.js +584 -0
- package/dist/providers/breeze/index.d.ts +1 -3
- package/dist/providers/breeze/index.js +67 -1420
- package/dist/providers/breeze/openapi.generated.js +60 -0
- package/dist/providers/breeze/skill.generated.js +2 -6
- package/dist/providers/breeze/tools.js +696 -0
- package/dist/providers/ccb/api.js +496 -0
- package/dist/providers/ccb/index.d.ts +15 -37
- package/dist/providers/ccb/index.js +77 -1702
- package/dist/providers/ccb/lib/constants.d.ts +34 -0
- package/dist/providers/ccb/lib/constants.js +74 -0
- package/dist/providers/ccb/lib/guide.js +9 -0
- package/dist/providers/ccb/lib/verdicts.js +387 -0
- package/dist/providers/ccb/openapi.generated.js +397 -0
- package/dist/providers/ccb/safety.js +295 -0
- package/dist/providers/ccb/skill.generated.js +2 -6
- package/dist/providers/ccb/tools.js +853 -0
- package/dist/providers/cloudflare/api.js +1066 -0
- package/dist/providers/cloudflare/index.d.ts +5 -22
- package/dist/providers/cloudflare/index.js +79 -197
- package/dist/providers/cloudflare/lib/config.d.ts +16 -0
- package/dist/providers/cloudflare/lib/config.js +2 -0
- package/dist/providers/cloudflare/lib/discovery.js +29 -0
- package/dist/providers/cloudflare/{graphql.js → lib/graphql.js} +1 -1
- package/dist/providers/cloudflare/lib/hatch.js +103 -0
- package/dist/providers/cloudflare/lib/path-bounds.js +28 -0
- package/dist/providers/cloudflare/lib/response-media.js +75 -0
- package/dist/providers/cloudflare/openapi.generated.js +4 -3
- package/dist/providers/cloudflare/{value-safety.js → safety.js} +487 -0
- package/dist/providers/cloudflare/skill.generated.js +2 -10
- package/dist/providers/cloudflare/tools.js +360 -0
- package/dist/providers/docs/api.js +51 -0
- package/dist/providers/docs/index.d.ts +1 -12
- package/dist/providers/docs/index.js +53 -1165
- package/dist/providers/docs/lib/constants.d.ts +18 -0
- package/dist/providers/docs/lib/constants.js +18 -0
- package/dist/providers/docs/lib/discovery.js +26 -0
- package/dist/providers/docs/lib/requests.js +48 -0
- package/dist/providers/docs/lib/schemas.js +168 -0
- package/dist/providers/docs/openapi.generated.js +16 -0
- package/dist/providers/docs/safety.js +54 -0
- package/dist/providers/docs/skill.generated.js +2 -6
- package/dist/providers/docs/tools.js +801 -0
- package/dist/providers/drive/api.js +177 -0
- package/dist/providers/drive/index.d.ts +1 -12
- package/dist/providers/drive/index.js +38 -1448
- package/dist/providers/drive/lib/constants.d.ts +6 -0
- package/dist/providers/drive/lib/constants.js +6 -0
- package/dist/providers/drive/lib/contracts.js +532 -0
- package/dist/providers/drive/lib/hatch.js +197 -0
- package/dist/providers/drive/openapi.generated.js +77 -0
- package/dist/providers/drive/safety.js +90 -0
- package/dist/providers/drive/skill.generated.js +2 -6
- package/dist/providers/drive/tools.js +1290 -0
- package/dist/providers/forms/api.js +71 -0
- package/dist/providers/forms/index.d.ts +1 -12
- package/dist/providers/forms/index.js +49 -1145
- package/dist/providers/forms/lib/constants.d.ts +14 -0
- package/dist/providers/forms/lib/constants.js +19 -0
- package/dist/providers/forms/lib/hatch.js +72 -0
- package/dist/providers/forms/openapi.generated.js +23 -0
- package/dist/providers/forms/safety.js +34 -0
- package/dist/providers/forms/skill.generated.js +2 -8
- package/dist/providers/forms/tools.js +1023 -0
- package/dist/providers/github/api.js +150 -0
- package/dist/providers/github/index.d.ts +4 -5
- package/dist/providers/github/index.js +30 -87
- package/dist/providers/github/{auth.d.ts → lib/auth.d.ts} +5 -3
- package/dist/providers/github/{auth.js → lib/auth.js} +40 -43
- package/dist/providers/github/lib/discovery.js +132 -0
- package/dist/providers/github/lib/operations.js +58 -0
- package/dist/providers/github/{scope.d.ts → lib/scope.d.ts} +2 -0
- package/dist/providers/github/{scope.js → lib/scope.js} +25 -1
- package/dist/providers/github/openapi.generated.js +1216 -0
- package/dist/providers/github/safety.js +216 -0
- package/dist/providers/github/skill.generated.js +2 -5
- package/dist/providers/github/tools.js +506 -0
- package/dist/providers/gmail/api.js +114 -0
- package/dist/providers/gmail/index.d.ts +1 -12
- package/dist/providers/gmail/index.js +73 -1621
- package/dist/providers/gmail/lib/constants.d.ts +13 -0
- package/dist/providers/gmail/lib/constants.js +16 -0
- package/dist/providers/gmail/lib/index.js +173 -0
- package/dist/providers/gmail/lib/schemas.js +176 -0
- package/dist/providers/gmail/lib/threading.js +120 -0
- package/dist/providers/gmail/openapi.generated.js +92 -0
- package/dist/providers/gmail/safety.js +21 -0
- package/dist/providers/gmail/skill.generated.js +2 -6
- package/dist/providers/gmail/tools.js +1435 -0
- package/dist/providers/overflow/api.js +1008 -0
- package/dist/providers/overflow/index.d.ts +1 -17
- package/dist/providers/overflow/index.js +60 -1488
- package/dist/providers/overflow/skill.generated.js +2 -5
- package/dist/providers/overflow/tools.js +873 -0
- package/dist/providers/planning-center/api.js +467 -0
- package/dist/providers/planning-center/index.d.ts +4 -35
- package/dist/providers/planning-center/index.js +69 -2445
- package/dist/providers/planning-center/lib/constants.d.ts +58 -0
- package/dist/providers/planning-center/lib/constants.js +83 -0
- package/dist/providers/planning-center/lib/discovery.js +40 -0
- package/dist/providers/planning-center/lib/guide.js +39 -0
- package/dist/providers/planning-center/lib/json-api.js +83 -0
- package/dist/providers/planning-center/lib/verdicts.js +1758 -0
- package/dist/providers/planning-center/openapi.generated.js +1764 -0
- package/dist/providers/planning-center/safety.js +188 -0
- package/dist/providers/planning-center/skill.generated.js +2 -27
- package/dist/providers/planning-center/tools.js +1010 -0
- package/dist/providers/quickbooks/api.js +299 -0
- package/dist/providers/quickbooks/index.d.ts +13 -0
- package/dist/providers/quickbooks/index.js +93 -0
- package/dist/providers/quickbooks/lib/query.js +118 -0
- package/dist/providers/quickbooks/lib/writes.js +168 -0
- package/dist/providers/quickbooks/safety.js +222 -0
- package/dist/providers/quickbooks/skill.generated.js +5 -0
- package/dist/providers/quickbooks/tools.js +117 -0
- package/dist/providers/sheets/api.js +133 -0
- package/dist/providers/sheets/index.d.ts +4 -16
- package/dist/providers/sheets/index.js +61 -1172
- package/dist/providers/sheets/lib/constants.d.ts +14 -0
- package/dist/providers/sheets/lib/constants.js +14 -0
- package/dist/providers/sheets/lib/hatch.js +163 -0
- package/dist/providers/sheets/lib/requests.js +105 -0
- package/dist/providers/sheets/lib/schemas.js +138 -0
- package/dist/providers/sheets/openapi.generated.js +30 -0
- package/dist/providers/sheets/safety.js +27 -0
- package/dist/providers/sheets/skill.generated.js +2 -7
- package/dist/providers/sheets/tools.js +733 -0
- package/dist/providers/slides/api.js +147 -0
- package/dist/providers/slides/index.d.ts +2 -10
- package/dist/providers/slides/index.js +48 -2555
- package/dist/providers/slides/lib/constants.d.ts +11 -0
- package/dist/providers/slides/lib/constants.js +11 -0
- package/dist/providers/slides/lib/requests.js +60 -0
- package/dist/providers/slides/lib/schemas.js +565 -0
- package/dist/providers/slides/openapi.generated.js +18 -0
- package/dist/providers/slides/skill.generated.js +2 -6
- package/dist/providers/slides/tools.js +1968 -0
- package/dist/providers/stripe/api.js +401 -0
- package/dist/providers/stripe/index.d.ts +5 -27
- package/dist/providers/stripe/index.js +74 -210
- package/dist/providers/stripe/lib/config.d.ts +4 -0
- package/dist/providers/stripe/lib/config.js +2 -0
- package/dist/providers/stripe/openapi.generated.js +4 -3
- package/dist/providers/stripe/{value-safety.js → safety.js} +5 -5
- package/dist/providers/stripe/skill.generated.js +2 -11
- package/dist/providers/stripe/tools.js +77 -0
- package/dist/providers/tithely/api.js +531 -0
- package/dist/providers/tithely/index.d.ts +17 -0
- package/dist/providers/tithely/index.js +86 -965
- package/dist/providers/tithely/skill.generated.js +2 -5
- package/dist/providers/tithely/tools.js +426 -0
- package/dist/providers/vercel/api.js +551 -0
- package/dist/providers/vercel/index.d.ts +9 -25
- package/dist/providers/vercel/index.js +65 -260
- package/dist/providers/vercel/lib/config.d.ts +2 -0
- package/dist/providers/vercel/lib/config.js +2 -0
- package/dist/providers/vercel/lib/index.js +117 -0
- package/dist/providers/vercel/lib/logs.js +100 -0
- package/dist/providers/vercel/lib/pin.js +270 -0
- package/dist/providers/vercel/openapi.generated.js +10 -5
- package/dist/providers/vercel/{value-safety.js → safety.js} +247 -38
- package/dist/providers/vercel/skill.generated.js +2 -7
- package/dist/providers/vercel/tools.js +642 -0
- package/dist/redaction-identity.js +47 -0
- package/dist/redaction-marker.js +125 -0
- package/dist/registry.d.ts +21 -15
- package/dist/registry.js +409 -376
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/actor-labels.js +1 -1
- package/dist/routes/connect.js +1 -1
- package/dist/routes/dispatch.js +103 -0
- package/dist/routes/mcp.js +170 -79
- package/dist/routes/oauth.js +6 -1
- package/dist/routes/operator.js +2 -2
- package/dist/routes/shared.d.ts +5 -4
- package/dist/routes/shared.js +244 -177
- package/dist/routes/skills.js +10 -8
- package/dist/routes/ui-config.js +27 -3
- package/dist/routes/ui-data.js +21 -20
- package/dist/routes/ui.js +1 -1
- package/dist/runtime/call-admission.js +0 -26
- package/dist/runtime/call-entry.js +30 -0
- package/dist/runtime/call.js +552 -0
- package/dist/runtime/connector-scope.js +7 -0
- package/dist/runtime/credential-probe.js +34 -0
- package/dist/runtime/executor.js +40 -0
- package/dist/runtime/native-storage.js +63 -0
- package/dist/runtime/registry.js +27 -0
- package/dist/runtime/request-scope.js +72 -0
- package/dist/runtime/run.js +15 -7
- package/dist/runtime/shared-read.js +34 -5
- package/dist/runtime/storage.js +34 -21
- package/dist/sent-secrets.d.ts +194 -37
- package/dist/sent-secrets.js +1743 -340
- package/dist/server.js +5 -56
- package/dist/skills.js +322 -223
- package/dist/sqlite.js +15 -14
- package/dist/storage/d1-copy.js +255 -0
- package/dist/storage/d1-driver.js +15 -0
- package/dist/storage/keys.js +28 -0
- package/dist/storage/sql-driver.js +88 -0
- package/dist/storage/sql-native.js +544 -0
- package/dist/storage/sql-schema.d.ts +6 -0
- package/dist/storage/sql-schema.js +15 -0
- package/dist/storage/sql.d.ts +14 -20
- package/dist/storage/sql.js +29 -535
- package/dist/timeout.js +0 -26
- package/dist/tool-safety.js +20 -10
- package/dist/types.d.ts +9 -109
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +0 -1
- package/dist/upward.js +3 -0
- package/dist/usage-guide.js +3 -1
- package/dist/validate.d.ts +4 -2
- package/dist/validate.js +86 -9
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +312 -292
- package/dist/workspace-subject.js +5 -1
- package/dist/write-outcome.js +454 -34
- package/documentation/README.md +28 -7
- package/documentation/architecture.md +2409 -255
- package/documentation/auth.md +867 -114
- package/documentation/code-mode.md +49 -16
- package/documentation/deploying.md +164 -20
- package/documentation/hosted-mcp-migration.md +460 -0
- package/documentation/integrating.md +129 -144
- package/documentation/meta-tools.md +409 -32
- package/documentation/operating.md +27 -3
- package/documentation/operator-tests.md +3 -0
- package/documentation/operator-ui.md +84 -48
- package/documentation/provider-auth-migration.md +16 -63
- package/documentation/provider-migration-0.29.md +10 -5
- package/documentation/provider-migration-0.35.md +975 -0
- package/documentation/providers.md +1347 -0
- package/examples/worker/AGENTS.md +23 -9
- package/examples/worker/README.md +254 -135
- package/examples/worker/alchemy.run.ts +116 -0
- package/examples/worker/gitignore +10 -0
- package/examples/worker/src/connecta.config.ts +23 -21
- package/examples/worker/wrangler.jsonc +14 -5
- package/guides/connecting-agents.md +157 -0
- package/guides/how-it-works.md +156 -0
- package/guides/safety-and-trust.md +153 -0
- package/package.json +30 -50
- package/templates/node/.env.example +3 -0
- package/templates/node/README.md +22 -2
- package/templates/node/docker-compose.yml +1 -0
- package/templates/node/package.json +3 -2
- package/templates/node/src/connecta.config.ts +8 -0
- package/dist/catalog-drift.d.ts +0 -138
- package/dist/catalog-drift.js +0 -578
- package/dist/operator-ui/agents.d.ts +0 -74
- package/dist/operator-ui/agents.js +0 -104
- package/dist/operator-ui/config-summary.js +0 -565
- package/dist/operator-ui/connection-map.js +0 -376
- package/dist/operator-ui/endpoints.js +0 -221
- package/dist/operator-ui/fix-prompts.d.ts +0 -18
- package/dist/operator-ui/fix-prompts.js +0 -87
- package/dist/operator-ui/health.js +0 -52
- package/dist/operator-ui/member-page.js +0 -97
- package/dist/operator-ui/overview.js +0 -123
- package/dist/operator-ui/setup-commands.js +0 -51
- package/dist/operator-ui/view.d.ts +0 -401
- package/dist/operator-ui/view.js +0 -705
- package/dist/providers/_shared/request-token.js +0 -83
- package/dist/providers/_shared/rest/dispatch.js +0 -41
- package/dist/providers/_shared/rest/operation-index.d.ts +0 -133
- package/dist/providers/_shared/rest/tools.d.ts +0 -141
- package/dist/providers/_shared/rest/tools.js +0 -569
- package/dist/providers/_shared/rest/value-safety.d.ts +0 -201
- package/dist/providers/basecamp/index.d.ts +0 -45
- package/dist/providers/basecamp/index.js +0 -997
- package/dist/providers/basecamp/skill.generated.js +0 -9
- package/dist/providers/cloudflare/rest.d.ts +0 -37
- package/dist/providers/cloudflare/rest.js +0 -1097
- package/dist/providers/github/hosted.js +0 -218
- package/dist/providers/github/rest.js +0 -321
- package/dist/providers/infisical/index.d.ts +0 -7
- package/dist/providers/infisical/index.js +0 -713
- package/dist/providers/infisical/skill.generated.js +0 -8
- package/dist/providers/intercom/index.d.ts +0 -24
- package/dist/providers/intercom/index.js +0 -121
- package/dist/providers/intercom/skill.generated.js +0 -8
- package/dist/providers/linear/index.d.ts +0 -55
- package/dist/providers/linear/index.js +0 -537
- package/dist/providers/linear/skill.generated.js +0 -8
- package/dist/providers/mixpanel/index.d.ts +0 -59
- package/dist/providers/mixpanel/index.js +0 -472
- package/dist/providers/mixpanel/operations.js +0 -20
- package/dist/providers/mixpanel/rest.js +0 -603
- package/dist/providers/mixpanel/skill.generated.js +0 -10
- package/dist/providers/mixpanel/value-safety.js +0 -16
- package/dist/providers/notion/index.d.ts +0 -67
- package/dist/providers/notion/index.js +0 -368
- package/dist/providers/notion/openapi.generated.js +0 -75
- package/dist/providers/notion/rest.d.ts +0 -24
- package/dist/providers/notion/rest.js +0 -1226
- package/dist/providers/notion/skill.generated.js +0 -11
- package/dist/providers/notion/value-safety.js +0 -56
- package/dist/providers/revenuecat/index.d.ts +0 -51
- package/dist/providers/revenuecat/index.js +0 -697
- package/dist/providers/revenuecat/skill.generated.js +0 -11
- package/dist/providers/stripe/rest.d.ts +0 -14
- package/dist/providers/stripe/rest.js +0 -402
- package/dist/providers/vercel/rest.d.ts +0 -20
- package/dist/providers/vercel/rest.js +0 -1020
- package/dist/providers/wispr-flow/index.d.ts +0 -14
- package/dist/providers/wispr-flow/index.js +0 -117
- package/dist/providers/wispr-flow/skill.generated.js +0 -8
- /package/dist/providers/github/{key.js → lib/key.js} +0 -0
package/documentation/auth.md
CHANGED
|
@@ -6,16 +6,16 @@
|
|
|
6
6
|
and package maintenance. This guide owns identity, membership, and downstream
|
|
7
7
|
credential contracts; use [deployment setup](./deploying.md) for runnable steps.
|
|
8
8
|
|
|
9
|
-
| Task | Sections
|
|
10
|
-
| ------------------------------------------------------------------- |
|
|
11
|
-
| Connect a machine or migrate an old client | [Agent tokens](#agent-tokens-and-upgrading-from-earlier-tokens), [agent endpoints](#agent-endpoints), [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
|
-
| Lay out an agent's pools | [Organizing an agent's pools](#organizing-an-agents-pools)
|
|
14
|
-
| 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)
|
|
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)
|
|
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)
|
|
18
|
-
| Review browser or downstream URL boundaries | [Origins](#origins), [advertised URLs](#urls-a-downstream-advertises), [one server per grant](#one-server-per-grant)
|
|
9
|
+
| Task | Sections |
|
|
10
|
+
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
11
|
+
| Connect a machine or migrate an old client | [Agent tokens](#agent-tokens-and-upgrading-from-earlier-tokens), [agent endpoints](#agent-endpoints), [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
|
+
| Lay out an agent's pools | [Organizing an agent's pools](#organizing-an-agents-pools) |
|
|
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
|
+
| 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) |
|
|
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) |
|
|
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
|
|
|
20
20
|
[Credential recognition and admission](#credential-recognition-and-admission)
|
|
21
21
|
defines refusal precedence.
|
|
@@ -30,7 +30,8 @@ Finish [Clerk setup](#clerk-oauth-tokens-and-operator-sessions) or
|
|
|
30
30
|
provider yet. A machine-only deployment cannot admit people or manage shared
|
|
31
31
|
auth through the operator UI.
|
|
32
32
|
|
|
33
|
-
1. Admit the person in the identity provider's sign-in policy.
|
|
33
|
+
1. Admit the person in the identity provider's sign-in policy. With Clerk's
|
|
34
|
+
`organizationId`, add them to the deployment's Clerk Organization. Obtain their
|
|
34
35
|
stable principal id there: Clerk's user id, or Access's `user_uuid` (email
|
|
35
36
|
only when no `user_uuid` is supplied). A display name or an email for a
|
|
36
37
|
Clerk user is not their principal id.
|
|
@@ -223,15 +224,262 @@ Clerk, a `cta_` token refused at `/mcp` or `/mcp/<pool>` still receives Clerk's
|
|
|
223
224
|
resource metadata challenge.
|
|
224
225
|
An open deployment with connectors warns at construction.
|
|
225
226
|
|
|
227
|
+
### Authentication capacity and ingress limiting
|
|
228
|
+
|
|
229
|
+
Connecta admits at most **16 active inbound adapter authorizations** per
|
|
230
|
+
loaded core in a process or Worker isolate. Human MCP endpoints, agent endpoints,
|
|
231
|
+
operator APIs, connection starts (`/connect/<connector>`), and OAuth callbacks
|
|
232
|
+
share this fixed bound, including network calls made inside an adapter's SDK.
|
|
233
|
+
It has no queue and no configuration option. When full, a request receives HTTP
|
|
234
|
+
503 with `Retry-After: 1`, `Cache-Control: no-store`, and
|
|
235
|
+
`error: { code: "authentication_busy", message: "Authentication capacity reached.", retryable: true }`.
|
|
236
|
+
The operator log records only the checked `authentication_busy` reason, never
|
|
237
|
+
credentials or identity-provider diagnostics. Retry after the indicated delay;
|
|
238
|
+
Connecta does not automatically retry the authentication.
|
|
239
|
+
|
|
240
|
+
Underlying work stays charged until it settles, including cancellation cleanup.
|
|
241
|
+
A caller leaving alone never releases capacity. After an authorization's hard
|
|
242
|
+
deadline plus **one second of settlement grace**, unfinished work moves from the
|
|
243
|
+
active pool to a separate **abandoned allowance of 16 operations**. The native
|
|
244
|
+
Access lookup and agent-token verification have five-second deadlines; Clerk's
|
|
245
|
+
deadline is ten seconds, and custom adapters share that ten-second threshold.
|
|
246
|
+
The allowance equals the active limit, so normal authentication continues
|
|
247
|
+
while a partial wave of stalled operations is abandoned, and at most **32
|
|
248
|
+
authorizations** are ever outstanding per loaded core. A full wave of 16
|
|
249
|
+
stalled operations fills the allowance. While the allowance is full, no
|
|
250
|
+
authorization starts, even when active slots are free. Overdue work that finds
|
|
251
|
+
the allowance full keeps its active slot until an abandoned operation ends and
|
|
252
|
+
makes room. A binding that never settles therefore fills the allowance and
|
|
253
|
+
fails closed instead of accumulating I/O.
|
|
254
|
+
|
|
255
|
+
Each charge ends exactly once, in one of two ways:
|
|
256
|
+
|
|
257
|
+
- **Settlement.** The adapter's promise and every native operation it
|
|
258
|
+
registered have settled. This applies on every runtime and through every
|
|
259
|
+
entry.
|
|
260
|
+
- **Unreachable work.** Nothing can ever settle the charge: the garbage
|
|
261
|
+
collector has freed every promise and callback that could have. On Workers
|
|
262
|
+
this follows invocation cancellation. Cloudflare ends a fetch invocation at
|
|
263
|
+
most 30 seconds after its response is sent or its client disconnects
|
|
264
|
+
([`waitUntil` limit](https://developers.cloudflare.com/workers/runtime-apis/context/)),
|
|
265
|
+
and drops each I/O operation it still owns without running its callbacks.
|
|
266
|
+
Once the collector frees that work, Connecta releases the charge, and the
|
|
267
|
+
next admission logs the checked reason `authentication_unreachable`.
|
|
268
|
+
|
|
269
|
+
Release through unreachable work is **eventual, never timed**. It waits for a
|
|
270
|
+
garbage collection, which runs as the process or isolate allocates. On
|
|
271
|
+
workerd, a collection triggered by allocation can keep a recently freed object
|
|
272
|
+
for several later collections, so one charge may linger after the rest are
|
|
273
|
+
released. While anything can still settle the work, the charge counts: a
|
|
274
|
+
connected caller, middleware that reads Connecta's response and then awaits
|
|
275
|
+
other work, and a scheduled handler that is still running all keep their
|
|
276
|
+
charges. A late settlement after such a release changes nothing, so no count
|
|
277
|
+
is released twice.
|
|
278
|
+
|
|
279
|
+
Release through unreachable work needs `FinalizationRegistry`. Node always
|
|
280
|
+
provides it. On Workers, set the `enable_weak_ref` compatibility flag, or use
|
|
281
|
+
a compatibility date of 2025-05-05 or later, and keep `enable_request_signal`;
|
|
282
|
+
the installed Worker example sets both flags. Without `FinalizationRegistry`,
|
|
283
|
+
only settlement releases a charge. A native authentication operation that
|
|
284
|
+
hangs past invocation cancellation then consumes one abandoned slot until the
|
|
285
|
+
isolate is recycled, and 16 such operations in one isolate make every later
|
|
286
|
+
authentication return `authentication_busy`, visible on `/health`.
|
|
287
|
+
|
|
288
|
+
A request refused because the allowance is full receives the same 503
|
|
289
|
+
`authentication_busy` response and headers. The operator log records the
|
|
290
|
+
checked reason `authentication_abandoned_saturated` instead. `GET /health`
|
|
291
|
+
reports `admission.authentication` with `active`, `limit`, `abandoned`,
|
|
292
|
+
`abandonedLimit`, `abandonedSaturated`, and `unreachableRelease`, for example:
|
|
293
|
+
|
|
294
|
+
```json
|
|
295
|
+
{
|
|
296
|
+
"active": 0,
|
|
297
|
+
"limit": 16,
|
|
298
|
+
"abandoned": 16,
|
|
299
|
+
"abandonedLimit": 16,
|
|
300
|
+
"abandonedSaturated": true,
|
|
301
|
+
"unreachableRelease": true
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
`unreachableRelease` is `true` when the runtime provides
|
|
306
|
+
`FinalizationRegistry`. On Workers, `false` means neither `enable_weak_ref` nor
|
|
307
|
+
a compatibility date of 2025-05-05 or later is in effect. A saturated allowance
|
|
308
|
+
refuses all new authentication until an underlying operation settles or its
|
|
309
|
+
unreachable work is collected. Investigate the identity binding or token
|
|
310
|
+
storage; restarting the process or isolate clears its local accounting.
|
|
311
|
+
|
|
312
|
+
Clerk creates a client per authorization: SDK verification (opaque
|
|
313
|
+
access tokens, JWT/JWKS, browser handshakes and session refresh), domain checks,
|
|
314
|
+
and gate-client lookups all use that request's signal and a ten-second
|
|
315
|
+
authentication deadline. Its transport reads at most 1 MiB per SDK response,
|
|
316
|
+
cancels the upstream body on every exit, and gives the SDK only buffered bytes.
|
|
317
|
+
JWKS fetches make one attempt; cancellation starts no retry timer. Additional
|
|
318
|
+
Backend API admission reads retain their 64 KiB response bound.
|
|
319
|
+
|
|
320
|
+
On Workers, the event's `ctx` reaches Connecta through the `ctx` argument of
|
|
321
|
+
`connecta.fetch`. Its `waitUntil` keeps only that request's
|
|
322
|
+
already-running authorization and cleanup runnable after disconnect, so an
|
|
323
|
+
aborted upstream settles before its slot is released. No other request shares
|
|
324
|
+
its promise or I/O. Custom adapters must honor `request.signal` and return a promise that settles
|
|
325
|
+
only after their work ends; ignoring cancellation consumes the bounded
|
|
326
|
+
abandoned allowance, and work the promise does not await is not counted.
|
|
327
|
+
The current adapters cannot cheaply establish a fully authenticated cache hit
|
|
328
|
+
before verification, so every adapter invocation counts, including one that
|
|
329
|
+
later uses a cached membership or policy verdict. Cache hits finish their slots
|
|
330
|
+
quickly; a cached policy verdict never bypasses credential verification.
|
|
331
|
+
Domain/gate and organization caches store only completed verdicts, never a
|
|
332
|
+
lookup failure, cancellation, or timeout. A `gate` that returns `false` after
|
|
333
|
+
its request was cancelled is not cached either. The JWKS cache stores only
|
|
334
|
+
signing keys from a completed fetch.
|
|
335
|
+
An adapter returning a synchronous local verdict releases before yielding;
|
|
336
|
+
it consumes no pending slot between requests.
|
|
337
|
+
|
|
338
|
+
This bound is independent of MCP request/program admission and downstream call
|
|
339
|
+
limits. `Mcp-Method: subscriptions/listen` still defers its MCP request permit:
|
|
340
|
+
after authentication and endpoint admission, a body-confirmed modern listen is
|
|
341
|
+
refused promptly even when execution capacity is full. It always passes the
|
|
342
|
+
authentication bound, and no request body is read before authentication.
|
|
343
|
+
|
|
344
|
+
Routes that do not invoke an inbound verifier consume no authentication slot:
|
|
345
|
+
health, public OAuth discovery and client metadata, UI shells and static assets,
|
|
346
|
+
preflight, redirects, unknown paths, and local header/origin/path refusals.
|
|
347
|
+
An invalid `/connect` handoff or unknown OAuth callback target also refuses
|
|
348
|
+
before a verifier. Downstream OAuth exchanges and display-label reads are
|
|
349
|
+
separate operations, with their existing limits; this bound covers the inbound
|
|
350
|
+
authentication that admits their caller. Cloudflare Access edge authentication
|
|
351
|
+
runs before the Worker; its in-Worker identity lookup is covered. Access uses
|
|
352
|
+
only the trusted runtime binding, with no JWT/JWKS fetch. That native binding
|
|
353
|
+
and token storage expose no cancellation API. Both answer promptly on caller
|
|
354
|
+
cancellation or their five-second deadline and keep the unfinished operation
|
|
355
|
+
in active or abandoned accounting until it settles. The five-second agent
|
|
356
|
+
deadline covers hashing and every verification read together, and returns the
|
|
357
|
+
machine `401` challenge; no further storage read starts after cancellation or
|
|
358
|
+
timeout. A late native result never admits its original caller.
|
|
359
|
+
|
|
360
|
+
This is a concurrency bound, **not a rate limit or a shared provider quota**.
|
|
361
|
+
Require ingress rate limiting for public deployments across all authenticated
|
|
362
|
+
paths, including `/agent/*`, `/ui/*`, `/connect/*`, and `/oauth/callback/*`.
|
|
363
|
+
For example, a deployment at `https://assistant.example.com` using Clerk must
|
|
364
|
+
budget all isolates and deployments sharing its Clerk secret key together.
|
|
365
|
+
Fast invalid tokens and repeated cache misses can exhaust a provider's request
|
|
366
|
+
budget without ever exceeding 16 active authentications. Set ingress limits
|
|
367
|
+
to that provider's quota and expected traffic; increasing MCP concurrency does
|
|
368
|
+
not increase authentication capacity.
|
|
369
|
+
|
|
226
370
|
Clerk denial logs carry checked fixed reason codes. Provider user IDs, email
|
|
227
371
|
addresses, and email domains stay out of denial logs, activity, and status,
|
|
228
372
|
including email lookup failures and admission policy refusals.
|
|
229
373
|
|
|
374
|
+
## Vercel workload credentials
|
|
375
|
+
|
|
376
|
+
Available from 0.35.0; earlier releases do not support workload admission.
|
|
377
|
+
|
|
378
|
+
An agent running on Vercel can present its project's signed OIDC token instead
|
|
379
|
+
of provisioning a Connecta token. Declare the binding beside its name:
|
|
380
|
+
|
|
381
|
+
```ts
|
|
382
|
+
agents: [{
|
|
383
|
+
name: "assistant",
|
|
384
|
+
vercel: {
|
|
385
|
+
team: "team_example", // immutable owner_id, required; team slugs are refused
|
|
386
|
+
project: "prj_example", // immutable project_id (preferred), or exact project name
|
|
387
|
+
// environments: ["production"], // default; preview/development require explicit entries
|
|
388
|
+
},
|
|
389
|
+
}],
|
|
390
|
+
pools: {
|
|
391
|
+
assistant: { tools: ["crm", "tickets"], members: ["agent:assistant"] },
|
|
392
|
+
},
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Enable OIDC federation in the calling Vercel project's security settings.
|
|
396
|
+
Use exact values from that project's settings; wildcards and empty environment
|
|
397
|
+
lists are refused. The team must be its immutable `team_...` ID, matched against
|
|
398
|
+
`owner_id`; a team slug is not accepted. Prefer an immutable `prj_...` project ID,
|
|
399
|
+
matched against `project_id`. A project name instead matches `project` and follows
|
|
400
|
+
that name, including reuse within the team. Project names may contain dots, such
|
|
401
|
+
as `assistant.example.com`; colons are refused. A workload that could match two
|
|
402
|
+
agents is refused at construction; use project IDs consistently across agents
|
|
403
|
+
to avoid ambiguous bindings.
|
|
404
|
+
|
|
405
|
+
The default admits production only. List `preview` or `development` explicitly
|
|
406
|
+
when those environments should have the same pool access. Every deployment of
|
|
407
|
+
the bound project in an allowed environment can act as the agent. This is
|
|
408
|
+
project workload identity, with no end-user identity or forwarding. Enabling
|
|
409
|
+
`development` lets project members who can obtain development tokens act as the
|
|
410
|
+
agent. Enabling `preview` lets code on any pushed preview branch act as the agent.
|
|
411
|
+
|
|
412
|
+
Treat the project's OIDC token as a bearer credential for all of this agent's
|
|
413
|
+
pools. Its default audience, `https://vercel.com/<team-slug>`, is shared by every
|
|
414
|
+
service accepting Vercel's default OIDC tokens; it is not specific to Connecta.
|
|
415
|
+
Anything holding the project's token can act as the agent:
|
|
416
|
+
project code, builds (including build steps and dependency install scripts with
|
|
417
|
+
access to `VERCEL_OIDC_TOKEN`), and services the project sends the token to,
|
|
418
|
+
including other agents and third-party APIs. A holder may be able to obtain
|
|
419
|
+
fresh Vercel tokens through `@vercel/oidc`'s exchange, so access may outlast the
|
|
420
|
+
original token's expiry. Binding a project means trusting everything that can
|
|
421
|
+
obtain that project's tokens. Send them only to services trusted with the same
|
|
422
|
+
agent access. Signature verification establishes the project identity; it does
|
|
423
|
+
not isolate Connecta from these other token holders.
|
|
424
|
+
|
|
425
|
+
Both team (`https://oidc.vercel.com/<team-slug>`) and global
|
|
426
|
+
(`https://oidc.vercel.com`) issuer modes work. Connecta verifies RS256 signatures
|
|
427
|
+
against Vercel's fixed public JWKS, exact issuer/audience/subject consistency,
|
|
428
|
+
the configured team/project/environment, required expiration and issued-at,
|
|
429
|
+
and not-before when present, with 30 seconds of clock skew. External-user
|
|
430
|
+
tokens carrying `external_sub` are refused. Public keys cache for five minutes;
|
|
431
|
+
an unknown key can trigger one additional refresh per 30 seconds. Failed fetches
|
|
432
|
+
back off for 30 seconds and never admit with expired cached keys. Fetches have
|
|
433
|
+
the same five-second authentication deadline as agent tokens. Neither token
|
|
434
|
+
contents nor upstream errors enter activity; activity names the declared agent.
|
|
435
|
+
If the two concurrent key-fetch slots are occupied on a cold or expired cache,
|
|
436
|
+
the endpoint returns 503 with `Retry-After: 1`, rather than rejecting the credential
|
|
437
|
+
with 401.
|
|
438
|
+
|
|
439
|
+
Send the token as `Authorization: Bearer <OIDC token>` to
|
|
440
|
+
`https://connecta.example.com/agent/assistant`. The inbound
|
|
441
|
+
`x-vercel-oidc-token` header alone is not a Connecta credential. In Eve:
|
|
442
|
+
|
|
443
|
+
```ts
|
|
444
|
+
import { getVercelOidcToken } from "@vercel/oidc";
|
|
445
|
+
import { defineMcpClientConnection } from "eve/connections";
|
|
446
|
+
|
|
447
|
+
export default defineMcpClientConnection({
|
|
448
|
+
url: "https://connecta.example.com/agent/assistant",
|
|
449
|
+
description: "Use the assistant's shared CRM and ticket tools.",
|
|
450
|
+
auth: {
|
|
451
|
+
principalType: "app",
|
|
452
|
+
getToken: async () => ({ token: await getVercelOidcToken() }),
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Resolve the token inside the request through `getToken`; never obtain it at
|
|
458
|
+
module initialization or cache it. Vercel supplies request tokens through
|
|
459
|
+
`x-vercel-oidc-token`; the helper can also read `VERCEL_OIDC_TOKEN` in builds
|
|
460
|
+
or development. Eve's inbound `vercelOidc()` and `vercelSubject()` configure
|
|
461
|
+
who may call Eve, separately from this outbound connection.
|
|
462
|
+
|
|
463
|
+
OIDC-only agents need no `accessTokens` module. To accept either credential
|
|
464
|
+
for the same agent, also configure `accessTokens(storage)`; its token slot,
|
|
465
|
+
rotation and revocation keep their existing behavior. Revoking a `cta_` token
|
|
466
|
+
does not revoke the workload binding: remove the binding or agent in code to
|
|
467
|
+
stop OIDC admission. Both credentials reach exactly the pools listing the
|
|
468
|
+
agent. Human endpoints refuse Vercel workload credentials without consulting
|
|
469
|
+
human adapters. Removing the agent stops both credential paths.
|
|
470
|
+
|
|
471
|
+
The [operator agent view](./operator-ui.md#operator-data-contract) lists each
|
|
472
|
+
declared agent's supported credential kinds and latest sampled successful pool
|
|
473
|
+
admission. Last seen is payload-free admission evidence sampled within a minute,
|
|
474
|
+
independent of completed-call history; a refused credential or pool does not
|
|
475
|
+
update it. Recording is best effort within 500 ms or the shorter configured probe
|
|
476
|
+
deadline; failure loses a sample and a later eligible admission can try again.
|
|
477
|
+
|
|
230
478
|
## Agent tokens and upgrading from earlier tokens
|
|
231
479
|
|
|
232
|
-
Machine clients are **agents**, declared by name in code. `agents
|
|
233
|
-
|
|
234
|
-
unique; anything else, or agents without `accessTokens`, throws at
|
|
480
|
+
Machine clients are **agents**, declared by name in code. String entries in `agents`
|
|
481
|
+
list them: each name matches `^[a-z0-9_-]+$`, at most 64 characters, and is
|
|
482
|
+
unique; anything else, or token-only agents without `accessTokens`, throws at
|
|
235
483
|
construction. `describeConfig()` lists the names and never token state.
|
|
236
484
|
`accessTokens` from `@zackbart/connecta/auth/access-tokens` installs the `cta_`
|
|
237
485
|
inbound adapter and, beside `ui`, the Tokens page and `/ui/access-tokens`
|
|
@@ -322,7 +570,7 @@ request without any storage write. An admitted agent's identity is:
|
|
|
322
570
|
}
|
|
323
571
|
```
|
|
324
572
|
|
|
325
|
-
`identity.agent` is set only by
|
|
573
|
+
`identity.agent` is set only by built-in agent credential adapters; a result from any other
|
|
326
574
|
inbound provider that carries `agent` is refused like an unknown token. An
|
|
327
575
|
agent has no principal, so personal connectors are absent for it and it never
|
|
328
576
|
reads activity. Its subject partitions result handles per
|
|
@@ -491,7 +739,7 @@ A member reference is one of:
|
|
|
491
739
|
- `"agent:<name>"`, naming an agent declared in `agents` (pool `members` only).
|
|
492
740
|
|
|
493
741
|
A person matches when the admitted principal equals the reference exactly; an
|
|
494
|
-
agent matches by `identity.agent`, which only
|
|
742
|
+
agent matches by `identity.agent`, which only built-in agent credential adapters set.
|
|
495
743
|
Construction throws on a bare id without exactly one interactive
|
|
496
744
|
provider, a bare id containing `:`, an undeclared agent, a repeated reference,
|
|
497
745
|
an empty or missing pool `members`, an agent in `maintainers`, `maintainers`
|
|
@@ -607,8 +855,8 @@ and reports.
|
|
|
607
855
|
## Pools
|
|
608
856
|
|
|
609
857
|
A pool is a named slice of the deployment served to its members: people at
|
|
610
|
-
`/mcp/<pool>` and agents at `/agent/<pool>`. A support team sees
|
|
611
|
-
tools and
|
|
858
|
+
`/mcp/<pool>` and agents at `/agent/<pool>`. A support team sees two notes
|
|
859
|
+
tools and tickets, a calendar bot sees one tool, both over the same credentials. Private remote catalog
|
|
612
860
|
cache entries remain partitioned by the admitted pool.
|
|
613
861
|
|
|
614
862
|
```ts
|
|
@@ -621,12 +869,12 @@ createConnecta({
|
|
|
621
869
|
agents: ["calendar-bot"],
|
|
622
870
|
pools: {
|
|
623
871
|
support: {
|
|
624
|
-
tools: ["
|
|
872
|
+
tools: ["tickets", "notes.search_pages", "notes.fetch_page"],
|
|
625
873
|
members: supportTeam,
|
|
626
|
-
description: "Support tools:
|
|
874
|
+
description: "Support tools: tickets and two notes reads. Ask a maintainer for anything else.",
|
|
627
875
|
},
|
|
628
876
|
calendar_bot: { tools: ["calendar.create_event"], members: ["agent:calendar-bot"] },
|
|
629
|
-
reporting: { tools: ["
|
|
877
|
+
reporting: { tools: ["tickets", "notes"], readOnly: true, members: analysts },
|
|
630
878
|
},
|
|
631
879
|
connectors, executor,
|
|
632
880
|
});
|
|
@@ -643,6 +891,7 @@ admitted.
|
|
|
643
891
|
| 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. |
|
|
644
892
|
| Refusal | A non-maintainer 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`, `not_pool_member`, or `pool_not_declared`. Names are not anonymously enumerable; valid credentials cannot distinguish these cases by content. |
|
|
645
893
|
| Agents | An agent member reaches its pools only at `/agent/<pool>`, with its `cta_` token. Human endpoints refuse `cta_`; see [agent endpoints](#agent-endpoints). |
|
|
894
|
+
| 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). |
|
|
646
895
|
| 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. |
|
|
647
896
|
| 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. |
|
|
648
897
|
| 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. |
|
|
@@ -667,11 +916,11 @@ at the edge so an agent sends one credential, and it behaves the same on Node.
|
|
|
667
916
|
`/%61gent/x`, `//agent/x`, `/agent/%2f..`, `/agentx`) answers the pool 404
|
|
668
917
|
before any admission or auth, so a path Access bypasses never reaches a
|
|
669
918
|
human route. The query string is not part of the match.
|
|
670
|
-
- **Auth.** Only
|
|
919
|
+
- **Auth.** Only built-in `cta_` and configured Vercel workload adapters are consulted. Clerk and Access
|
|
671
920
|
adapters never run; `ctx.access`, which Access attaches even to a bypassed
|
|
672
921
|
path, is never read or passed on; cookies, Clerk handshake parameters,
|
|
673
922
|
`Cf-Access-Jwt-Assertion`, and Access service headers are ignored. A
|
|
674
|
-
missing, malformed, unknown, revoked, or
|
|
923
|
+
missing, malformed, unknown, revoked, or unaccepted credential gets 401 with
|
|
675
924
|
`WWW-Authenticate: Bearer` and no `resource_metadata`.
|
|
676
925
|
- **Membership.** An admitted agent must be listed in that pool's `members`.
|
|
677
926
|
A non-member agent, a pool that lists no agents, and an undeclared but
|
|
@@ -995,7 +1244,7 @@ intake-redacted successes, keyed by connector configuration. Set
|
|
|
995
1244
|
`authScope: "personal"` when every human needs a separate downstream account:
|
|
996
1245
|
|
|
997
1246
|
```ts
|
|
998
|
-
remoteMcp("
|
|
1247
|
+
remoteMcp("crm", { url: "https://crm.example.com/mcp", authScope: "personal",
|
|
999
1248
|
auth: { type: "oauth" } });
|
|
1000
1249
|
```
|
|
1001
1250
|
|
|
@@ -1141,8 +1390,10 @@ leaves the local grant removed. The management route returns HTTP 200 with
|
|
|
1141
1390
|
code. Revoke the old grant in the provider's console if removal there matters.
|
|
1142
1391
|
With no advertised endpoint, Disconnect makes no request. Repeating Disconnect
|
|
1143
1392
|
never retries revocation, because the removed grant is no longer available.
|
|
1144
|
-
Restart makes no revocation request. Static `api()` OAuth
|
|
1145
|
-
|
|
1393
|
+
Restart makes no revocation request. Static `api()` OAuth uses the same
|
|
1394
|
+
removal and revocation flow when deployment configuration supplies
|
|
1395
|
+
`revocationEndpoint`; without it, removal remains local. The configured
|
|
1396
|
+
client secret is used in memory and is never saved with the grant.
|
|
1146
1397
|
|
|
1147
1398
|
## Refresh failures
|
|
1148
1399
|
|
|
@@ -1157,11 +1408,11 @@ Copy these durable records during storage migration.
|
|
|
1157
1408
|
|
|
1158
1409
|
Each fingerprint record names its dispatch epoch, holder, and resolution state:
|
|
1159
1410
|
|
|
1160
|
-
| State | Send gate
|
|
1161
|
-
| ------------- |
|
|
1162
|
-
| `outstanding` | Dispatched without a definitive outcome yet. Refuse the fingerprint in every epoch.
|
|
1163
|
-
| `ambiguous` | The sent request's outcome is unknowable. Refuse the fingerprint in every epoch, including when re-consent returns it again.
|
|
1164
|
-
| `resolved` | The tokens were committed or a definitive failure was recorded.
|
|
1411
|
+
| State | Send gate |
|
|
1412
|
+
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1413
|
+
| `outstanding` | Dispatched without a definitive outcome yet. Refuse the fingerprint in every epoch. |
|
|
1414
|
+
| `ambiguous` | The sent request's outcome is unknowable. Refuse the fingerprint in every epoch, including when re-consent returns it again. |
|
|
1415
|
+
| `resolved` | The tokens were committed or a definitive failure was recorded. A completed lease whose token digest matches the current grant may start the next generation in the same epoch. A later epoch needs a code exchange that observed this exact resolved record. |
|
|
1165
1416
|
|
|
1166
1417
|
Resolution writes use CAS against the exact outstanding record and record a
|
|
1167
1418
|
`resolvedAt` timestamp with a resolved outcome. When accepting a code response,
|
|
@@ -1174,6 +1425,14 @@ outstanding dispatch. An earlier consent cannot become eligible merely because
|
|
|
1174
1425
|
the pending refresh later resolves. Competing epochs cannot both replace the
|
|
1175
1426
|
same record, and a late answer cannot turn an ambiguous record into resolved.
|
|
1176
1427
|
|
|
1428
|
+
For vendors that return unchanged refresh-token bytes, a successful token
|
|
1429
|
+
commit and its matching completed lease authorize the next refresh generation
|
|
1430
|
+
in that epoch. It must replace the exact resolved fingerprint by CAS before
|
|
1431
|
+
sending. Outstanding, ambiguous and failed attempts never authorize reuse;
|
|
1432
|
+
concurrent callers with an older access token adopt the committed result.
|
|
1433
|
+
An unsent successor retains the prior successful proof during cleanup, so a
|
|
1434
|
+
transient storage failure before dispatch can be retried without losing the grant.
|
|
1435
|
+
|
|
1177
1436
|
An ambiguous token stays blocked even if re-consent returns identical bytes.
|
|
1178
1437
|
Recovery requires re-consent that yields a different refresh token, or
|
|
1179
1438
|
revocation at the provider. Restart never deletes spent records or moves old
|
|
@@ -1211,6 +1470,38 @@ a dispatched record. The refresh HTTP request and response body have a
|
|
|
1211
1470
|
backoff. A waiter deadline returns retryable `unavailable`, keeps the grant,
|
|
1212
1471
|
and starts no consent. It never permits resending a spent token.
|
|
1213
1472
|
|
|
1473
|
+
A `remoteMcp()` write the downstream answered with HTTP 401 is never sent
|
|
1474
|
+
again (INV-9). The SDK still refreshes the grant, and connecta stops its
|
|
1475
|
+
resend before it leaves, so any fresh call after the target is reconciled uses
|
|
1476
|
+
the fresh token. The call
|
|
1477
|
+
fails with a non-retryable `connector_call_failed` whose outcome is unknown.
|
|
1478
|
+
If the refresh fails instead, whatever it met (a dead grant, a waiter
|
|
1479
|
+
deadline, the caller leaving), or hands back the token the API just
|
|
1480
|
+
rejected, the 401's verdict stays:
|
|
1481
|
+
`downstream_oauth_required`, never retryable, with `reconciliationRequired`
|
|
1482
|
+
and `writeOutcome: "unknown"`, and an `authorize_connector` handoff that says
|
|
1483
|
+
to re-read the target before any fresh call. A read is refreshed and sent
|
|
1484
|
+
once more. MCP sends a tool only once when either its effective classification
|
|
1485
|
+
or its original server hints say write. A deployment write-to-read override
|
|
1486
|
+
cannot make a server-declared write replayable. Its failure after possible
|
|
1487
|
+
dispatch is also an unknown outcome, non-retryable and without wait advice;
|
|
1488
|
+
a proven unsent failure retains its own recovery advice. A direct call outside an
|
|
1489
|
+
invocation uses the dispatched definition, a reviewed write verdict, or the
|
|
1490
|
+
tool's hints, and fails closed as a write.
|
|
1491
|
+
|
|
1492
|
+
A remote MCP tool call that loses its internal dispatch record is refused
|
|
1493
|
+
before sending unless active calls for that tool prove it is a read. A
|
|
1494
|
+
write or an unproven call fails non-retryable with `writeOutcome: "unknown"`:
|
|
1495
|
+
tracking loss cannot establish whether an earlier message already sent it.
|
|
1496
|
+
|
|
1497
|
+
Google Workspace also uses the invocation's served classification
|
|
1498
|
+
before considering the HTTP method. A write-classified GET is never replayed
|
|
1499
|
+
after a 401; a read-classified POST may replay once. Without a served verdict,
|
|
1500
|
+
this transport uses GET as read and other methods as write. Google drops the
|
|
1501
|
+
rejected token for the next deliberate call and retains `auth_required` with
|
|
1502
|
+
`writeOutcome: "unknown"` and the operator handoff. A renewed credential does not establish the write outcome. Recovery
|
|
1503
|
+
that hands back the same token renewed nothing.
|
|
1504
|
+
|
|
1214
1505
|
One preparation deadline covers initial epoch and grant reads, claim acquisition,
|
|
1215
1506
|
dispatch preparation, and local joiners waiting for a grant commit. Cancellation
|
|
1216
1507
|
before sending settles the local flight without awaiting cleanup storage. An
|
|
@@ -1287,7 +1578,29 @@ contain `key`, `token`, `secret`, `auth`, `signature`, or `session`. Custom
|
|
|
1287
1578
|
`api()` handlers use `ctx.fetch` for this tracking; `ctx.oauth.fetch` and
|
|
1288
1579
|
maintained-provider transports track the final request too. Credential-slot
|
|
1289
1580
|
reads cover values sent in headers, queries, or bodies. Echoed sensitive
|
|
1290
|
-
header lines are also withheld
|
|
1581
|
+
header lines are also withheld, read before any echoed value in them is
|
|
1582
|
+
replaced: a private value that changes part of a header's name or takes a
|
|
1583
|
+
credential prefix (such as `Authorization: Bearer`) still leaves the whole
|
|
1584
|
+
header value withheld, every cookie pair included. A line that is not a
|
|
1585
|
+
sensitive header keeps its text.
|
|
1586
|
+
|
|
1587
|
+
Redaction removes every registered form whole and every run of eight
|
|
1588
|
+
consecutive code units (K) of a registered form of a value at least eight units
|
|
1589
|
+
long, wherever it starts, in the raw text and in each JSON-decoded layer. A cut,
|
|
1590
|
+
trim, or page can therefore leave at most seven units of a form outside the
|
|
1591
|
+
cases below. Text Connecta or a maintained provider shortens before returning
|
|
1592
|
+
it, such as a long error message, a capped field, or one page of a long text, is
|
|
1593
|
+
redacted whole before any trimming, whitespace normalization, number parsing,
|
|
1594
|
+
extraction, or cut, so derived titles, previews, summaries, sizes, and raw
|
|
1595
|
+
branches share the same protection. An extraction whose source changed under
|
|
1596
|
+
redaction returns `[redacted]`. Text read only up to a bound also loses a tail
|
|
1597
|
+
that begins a sent value the read stopped inside, at every JSON decoding layer.
|
|
1598
|
+
Complete matches and unfinished tails are found against the original text and
|
|
1599
|
+
replaced together; even a one-unit prefix is withheld. This can conservatively
|
|
1600
|
+
suppress a legitimate tail that shares a secret's prefix. Text paging that
|
|
1601
|
+
counts redacted units binds its cursor to that representation and refuses
|
|
1602
|
+
continuation if a changed request secret set changes it, even when the vendor
|
|
1603
|
+
revision is unchanged.
|
|
1291
1604
|
|
|
1292
1605
|
Submitted values under JSON Schema `writeOnly: true` join this memory-only
|
|
1293
1606
|
request set, including nested objects, arrays, and local references. Strings
|
|
@@ -1304,69 +1617,200 @@ submitted data and cached catalogs never depend on one request's arguments.
|
|
|
1304
1617
|
|
|
1305
1618
|
Payload rules apply where downstream or guest data enters: downstream results
|
|
1306
1619
|
and errors, resource reads, connector usage guides and downstream skill files,
|
|
1307
|
-
program returns, logs, and emits, and relayed input prompts.
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1620
|
+
program returns, logs, and emits, and relayed input prompts. Each redaction
|
|
1621
|
+
projects the whole value: string and number leaves in serialization order,
|
|
1622
|
+
JSON objects, arrays, and string literals parsed into their own leaves whether
|
|
1623
|
+
they are wire text or a string inside a result, and errors' own fields. The
|
|
1624
|
+
joined projection is matched once, so a value split across adjacent leaves,
|
|
1625
|
+
such as text blocks or array items, is found; each match is replaced only in
|
|
1626
|
+
the leaves it touches, and the containers around them keep their shape. A
|
|
1627
|
+
number that a match touches becomes the string `[redacted]`, so the JSON stays
|
|
1628
|
+
valid. Text is reserialized only when something changed; otherwise its original
|
|
1629
|
+
bytes remain. Serialized JSON whose keys, syntax, and leaves together hold a run
|
|
1630
|
+
that no single leaf holds, as a value spanning tokens does, is withheld whole.
|
|
1631
|
+
Matching runs on the raw text and through one more layer of JSON string escapes
|
|
1632
|
+
at a time while the last layer still decodes an escape, at most eight layers;
|
|
1633
|
+
text still holding escapes past that is withheld. Each decoded layer is scanned
|
|
1634
|
+
around the escapes it decoded, since every other match was found a layer up. Every match is mapped back to
|
|
1635
|
+
the original text, overlapping spans merge, and each merged span is replaced
|
|
1636
|
+
once, so one match can never split another. A span wholly inside an existing
|
|
1322
1637
|
`[redacted]` placeholder is that placeholder; a private value that contains the
|
|
1323
|
-
placeholder text is still redacted.
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1638
|
+
placeholder text is still redacted.
|
|
1639
|
+
|
|
1640
|
+
A maintained provider's parsed vendor JSON has every value redacted on arrival
|
|
1641
|
+
and keeps its object keys until the connector returns, so the provider's value
|
|
1642
|
+
safety finds each field by the name the vendor gave it. The connector boundary
|
|
1643
|
+
then applies payload rules to the whole result or failure, keys included.
|
|
1644
|
+
Response header names are kept; their values are redacted. When a redacted
|
|
1645
|
+
value is one value safety decides on, such as an object's type, a record's
|
|
1646
|
+
label, a link's parameter name or the response's Content-Type, value safety
|
|
1647
|
+
takes its most protective choice, so more of that response may be withheld.
|
|
1648
|
+
|
|
1649
|
+
Every string that can reach an agent, an executor, a page or storage goes
|
|
1650
|
+
through one checker that applies all of the checks in this section: text and
|
|
1651
|
+
number leaves, object keys, declared binary fields, the bytes of typed binary,
|
|
1652
|
+
cursor claims, error fields, provider text lanes, bounded reads, and structural
|
|
1653
|
+
fields that are refused instead of rewritten. Object keys are checked on their
|
|
1654
|
+
own, and a key holding a match is renamed. Image, audio, resource-blob, or
|
|
1655
|
+
`format: "base64"` content, or a REST body's `base64` beside its `contentType`,
|
|
1656
|
+
is checked as text and also decoded whole and checked as bytes and as UTF-8,
|
|
1657
|
+
then withheld whole as the base64 of `[redacted]` on a match; content that is
|
|
1658
|
+
not valid base64 as it stands is still checked through every text view. Base64
|
|
1659
|
+
or base64url in any string is decoded and checked in each of the string's text
|
|
1660
|
+
views: the text itself, the text with one layer of JSON escapes decoded
|
|
1661
|
+
(`\uXXXX` and the standard escapes), the text with its percent escapes
|
|
1662
|
+
decoded, and its form decoding, which reads a `+` as a space and decodes any
|
|
1663
|
+
percent escapes, whether or not the text holds one. So `x+bm90ZXM=` is read
|
|
1664
|
+
both as one run and as `x` followed by `bm90ZXM=`, the base64 of `notes`.
|
|
1665
|
+
A view that is wholly base64 is decoded from six characters. A run inside a
|
|
1666
|
+
view is decoded from six characters when a private value of four to seven
|
|
1667
|
+
characters is registered, and from eleven otherwise: each length is the
|
|
1668
|
+
shortest run that can carry the smallest value the pass matches, four bytes or
|
|
1669
|
+
eight, at any alignment, padded or not. Decoded bytes are checked as bytes, as
|
|
1670
|
+
UTF-8, and through base64 they carry, up to three encodings deep, and the field
|
|
1671
|
+
is withheld whole as `[redacted]` when only its decoding holds a match. Every
|
|
1672
|
+
decode is paid for from the call's work budget; a field the budget cannot
|
|
1673
|
+
check is withheld.
|
|
1674
|
+
|
|
1675
|
+
Only fixed words are copied unchanged whatever was sent: Connecta, MCP, and
|
|
1676
|
+
JSON-RPC envelope member names, tag values such as a content `type` or result
|
|
1677
|
+
`format`, booleans under outcome names (`isError`, `ok`, `retryable`,
|
|
1678
|
+
`truncated`, `hasMore`), and, on an error, its classification code and a fixed
|
|
1679
|
+
name: one a built-in error, `DOMException` (`DOMException`, `AbortError`,
|
|
1680
|
+
`TimeoutError`), Connecta's own classes, or the pinned MCP SDK's classes give
|
|
1681
|
+
their instances. Any other error `name`, own or inherited, a data field or an
|
|
1682
|
+
accessor's answer, including one a custom class gives itself, is a leaf of the
|
|
1683
|
+
projection like any other field: it is matched joined with the fields beside it
|
|
1684
|
+
and through any JSON it holds, and replaced whole by `[redacted]` when anything
|
|
1685
|
+
changes any part of it. The copy keeps its class, so classification and
|
|
1686
|
+
identity facts still see it. An error's fields are joined in its copy's order:
|
|
1687
|
+
its own fields, then what its class answers for `stack`, `message`, `cause`,
|
|
1688
|
+
`code` and `name`, then the other accessors its class defines. A number or a string under any name,
|
|
1689
|
+
such as `retryAfterMs` or a plain object's `code`, is matched like any other
|
|
1690
|
+
value; a matching typed number on an error is dropped. Relay states and connect
|
|
1691
|
+
links Connecta minted are kept whole. The agent boundary around Connecta's own
|
|
1692
|
+
envelopes repeats every check except short private values, so envelope
|
|
1693
|
+
identifiers, counts, and JSON-RPC ids are never rewritten.
|
|
1694
|
+
|
|
1695
|
+
Every base64 or base64url encoding Connecta itself returns, such as a Drive or
|
|
1696
|
+
Gmail download, a REST body's `base64`, or a page cursor, is produced by one
|
|
1697
|
+
helper that checks the content first and encodes `[redacted]` instead when it
|
|
1698
|
+
holds a match. A cursor holds Connecta's own names, counts and fingerprints
|
|
1699
|
+
beside vendor values redacted when they were placed: a vendor page token or
|
|
1700
|
+
page id is redacted, so one that echoes a sent value fails on the next page
|
|
1701
|
+
instead of carrying it; a vendor revision or id the cursor only compares is
|
|
1702
|
+
replaced by its digest when it holds a sent value. Before a cursor is encoded,
|
|
1703
|
+
every string in it is checked under payload rules, and its whole text and
|
|
1704
|
+
encoding under the agent boundary's, which leave Connecta's own keys and counts
|
|
1705
|
+
to short private values. A cursor that holds a match encodes `[redacted]`,
|
|
1706
|
+
which the next page refuses.
|
|
1707
|
+
|
|
1708
|
+
What redaction returns is what it checked, and only JSON data: strings,
|
|
1709
|
+
numbers, booleans, `null`, plain objects and arrays, a fresh built-in
|
|
1710
|
+
`Uint8Array`, and copies of errors. A caller's object or array is
|
|
1711
|
+
returned as it stands only as verified plain data: its prototype is
|
|
1712
|
+
`Object.prototype`, `Array.prototype`, or `null`; no `toJSON` is on its chain;
|
|
1713
|
+
it has no symbol-keyed field; every field is a data property the pass read, an
|
|
1714
|
+
array's holes read through its prototypes; and nothing in it changed. Anything
|
|
1715
|
+
else is returned as a copy of writable, configurable data properties, so a
|
|
1716
|
+
frozen or read-only input never makes redaction throw, and the caller's value is
|
|
1717
|
+
never mutated:
|
|
1718
|
+
|
|
1719
|
+
- An error is always copied, as its own class. Its stack, and every accessor its
|
|
1720
|
+
class defines, such as DOMException's `name` and `message` or an SDK error's
|
|
1721
|
+
`status`, is read once into a checked field, under its name checked as any key
|
|
1722
|
+
is. Its `toJSON`, a data field or an
|
|
1723
|
+
accessor, is never read and is shadowed by an `undefined` field on the copy,
|
|
1724
|
+
and a field holding a function is withheld, so the copy carries no
|
|
1725
|
+
serializer. Methods its class defines stay on its prototype; JSON calls none
|
|
1726
|
+
of them.
|
|
1727
|
+
- An object's own accessor is never invoked and is left out of the copy; an
|
|
1728
|
+
array index read through an accessor holds `[redacted]`.
|
|
1729
|
+
|
|
1730
|
+
Every other value becomes the data JSON shows for it, checked, or is withheld:
|
|
1731
|
+
|
|
1732
|
+
| Value | Returned as |
|
|
1733
|
+
| ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
|
|
1734
|
+
| Function | `[redacted]`; a `toJSON` it holds is never called |
|
|
1735
|
+
| Symbol | `[redacted]` |
|
|
1736
|
+
| `bigint` | itself, checked as its digits like a number; JSON exits refuse it, as before |
|
|
1737
|
+
| `undefined` | itself; JSON omits it |
|
|
1738
|
+
| Object or array with a callable `toJSON`, such as a `Date` | the result of calling it once, checked: a `Date` becomes its ISO string, or `null` |
|
|
1739
|
+
| `String`, `Number`, or `Boolean` wrapper | its primitive, checked |
|
|
1740
|
+
| `Map`, `Set`, `WeakMap`, `WeakSet`, `Promise`, `RegExp`, or an instance of any other class | a plain object of its own data fields, checked: `{}` for the built-ins, as JSON shows them; accessors are never read |
|
|
1741
|
+
| Array of another class | a plain array of its indices, checked |
|
|
1742
|
+
| Typed array, `DataView`, or `ArrayBuffer`, including a subclass | a fresh built-in `Uint8Array` of the bytes it held, or `[redacted]` |
|
|
1743
|
+
|
|
1744
|
+
Typed binary is withheld as `[redacted]` when its bytes hold a match, checked as
|
|
1745
|
+
decoded binary content is, or when one of the numbers JSON shows for it does:
|
|
1746
|
+
its elements and, for the `Uint8Array` returned, its bytes, each checked as a
|
|
1747
|
+
JSON number and joined with its neighbours. A one-to-three-character numeric
|
|
1748
|
+
private value, such as `7`, therefore withholds a typed array holding that
|
|
1749
|
+
element, as it withholds an equal JSON number. Each of those numbers is an entry
|
|
1750
|
+
of the walk's 1,048,576-entry budget, so binary past it is withheld whole.
|
|
1751
|
+
Connecta returns downstream binary as base64 text, which none of this limits;
|
|
1752
|
+
only in-process code can return a typed array. With nothing registered for a
|
|
1753
|
+
request, redaction has nothing to match and returns its input as it stands.
|
|
1754
|
+
|
|
1755
|
+
A change copies only the containers on its path. An array's fields other than
|
|
1756
|
+
its indices are not read: JSON, which the MCP wire, the result stash, and every
|
|
1757
|
+
executor marshal values with, never serializes them.
|
|
1329
1758
|
|
|
1330
1759
|
Empty private strings are exempt because they contain nothing to leak. A
|
|
1331
|
-
|
|
1332
|
-
string
|
|
1333
|
-
supported encoded form,
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1760
|
+
private value of one to three characters is matched only as a whole field
|
|
1761
|
+
value: a string, property name, number, boolean, or decoded binary view equal
|
|
1762
|
+
to it or a supported encoded form is withheld, and text around it stays, so a
|
|
1763
|
+
private `1` leaves `row 10` alone. A value of four to seven characters is also
|
|
1764
|
+
replaced wherever it occurs in payload text, numbers, property names, and
|
|
1765
|
+
decoded views, so `prod-db` becomes `[redacted]-db` for `prod`. Pieces of either
|
|
1766
|
+
are not matched. Private values of at least eight characters and credentials
|
|
1767
|
+
are matched whole and by every eight-unit run. At a cut, an unfinished prefix of
|
|
1768
|
+
any registered form is still withheld, however short.
|
|
1769
|
+
|
|
1770
|
+
Identifiers sent beside a credential are matched whole only, because consent
|
|
1771
|
+
URLs and vendor metadata carry their parts by design: a confidential OAuth
|
|
1772
|
+
client's ID, a Basic username beside a password of at least eight characters,
|
|
1773
|
+
and credential-slot fields named as identifiers (ending in `id`, `user`,
|
|
1774
|
+
`username`, `login`, or `email`, or starting with `public`). A Basic username
|
|
1775
|
+
beside a shorter or empty password is the credential itself and is fully
|
|
1776
|
+
matched. Query parameters named as continuation cursors (`pageToken`,
|
|
1777
|
+
`nextPageToken`, `next_token`, `continuationToken`, `syncToken`) are public
|
|
1778
|
+
result state and are not registered. Header values a connector adds to token
|
|
1779
|
+
requests are registered under any name, except values equal to Connecta's own
|
|
1780
|
+
public ones (`Accept: application/json`, and the media types maintained
|
|
1781
|
+
providers send): registering a media type would redact a vendor's name wherever
|
|
1782
|
+
its fragments recur.
|
|
1783
|
+
|
|
1784
|
+
The eight-unit runs are indexed as two independent 32-bit hashes per run, at
|
|
1785
|
+
most 512 Ki distinct runs, so a false match is vanishingly rare and only redacts
|
|
1786
|
+
more; repeated runs, as in a self-similar credential, are stored once. Indexing
|
|
1787
|
+
and scanning are linear in the registered forms and the scanned text, whatever
|
|
1788
|
+
the number of values. The `Bearer ` and `token ` framings are public, so no run
|
|
1789
|
+
starting inside them is indexed; an echo carrying a framing is replaced with it.
|
|
1790
|
+
Short private values and identifiers are matched whole by one Aho-Corasick
|
|
1791
|
+
automaton each, which also finds the unfinished tails of bounded reads. High-
|
|
1792
|
+
entropy credentials and private values do not match ordinary text. A value that
|
|
1793
|
+
embeds ordinary text, such as a password holding a word, a token holding a
|
|
1794
|
+
vendor's name, or a credential that is a URL, also redacts that text where it
|
|
1795
|
+
recurs within the same request. Submitted values register at most 512 KiB of
|
|
1796
|
+
encoded forms and 4,096 argument nodes. A value that does not fit either limit,
|
|
1797
|
+
or a set past the index's capacity, withholds every string of eight units or
|
|
1798
|
+
more and every run of adjacent shorter leaves reaching eight units; an argument
|
|
1799
|
+
tree that cannot be walked withholds all downstream text.
|
|
1358
1800
|
|
|
1359
1801
|
One redaction call has one work budget across parsing and every scanned view:
|
|
1360
1802
|
64 Mi units with private values, and 1 Gi for credentials alone, which keeps
|
|
1361
|
-
multi-MiB document and skill-file reads.
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1803
|
+
multi-MiB document and skill-file reads. When the joined projection does not
|
|
1804
|
+
fit, leaves are matched alone, in order, and one that does not fit is withheld
|
|
1805
|
+
without spending what a smaller one needs; runs crossing two kept leaves are
|
|
1806
|
+
then matched 512 units deep on each side of their seam. The same call visits at
|
|
1807
|
+
most 512 containers deep and 1 Mi entries in total. A container's entries are
|
|
1808
|
+
counted before it is walked.
|
|
1809
|
+
One that does not fit the remaining entries is withheld whole: it is never
|
|
1810
|
+
serialized and checked as text, so nothing past the budget reaches a result, a
|
|
1811
|
+
page, or the result stash. A cycle or a container past the depth limit is
|
|
1812
|
+
withheld. No limit throws or
|
|
1813
|
+
exposes a matcher diagnostic, and typed success outcomes remain.
|
|
1370
1814
|
|
|
1371
1815
|
Only credentials of at least eight characters enter the redaction and structural
|
|
1372
1816
|
matchers. Credentials shorter than 8 characters are not redacted from echoes;
|
|
@@ -1376,9 +1820,9 @@ this floor produces one operator warning per connector with the typed code
|
|
|
1376
1820
|
`short_secret_not_redacted` and no credential value. Remote connectors warn at
|
|
1377
1821
|
construction; static `api()` OAuth warns on its first provider operation.
|
|
1378
1822
|
Connecta's own messages never quote credential values, regardless of length.
|
|
1379
|
-
|
|
1380
|
-
JSON unwrapping or joining text blocks
|
|
1381
|
-
structured
|
|
1823
|
+
The index and automata are cached until the secret set changes, and an empty set skips matching. Redaction
|
|
1824
|
+
runs before JSON unwrapping or joining text blocks, on final serialized text and every
|
|
1825
|
+
structured value, and before result paging, emits, and program outputs/errors/logs. See
|
|
1382
1826
|
[the agent boundary](./architecture.md#errors-and-records).
|
|
1383
1827
|
|
|
1384
1828
|
## URLs a downstream advertises
|
|
@@ -1498,8 +1942,8 @@ createConnecta({
|
|
|
1498
1942
|
auth: cloudflareAccessAuth(),
|
|
1499
1943
|
// Administers shared credentials, agent tokens, and activity.
|
|
1500
1944
|
maintainers: ["owner-id"],
|
|
1501
|
-
// Each member connects their own account on
|
|
1502
|
-
pools: { team: { tools: ["shared_docs", "
|
|
1945
|
+
// Each member connects their own account on personal_crm.
|
|
1946
|
+
pools: { team: { tools: ["shared_docs", "personal_crm", "notes.search_pages"], members: team } },
|
|
1503
1947
|
connectors, executor,
|
|
1504
1948
|
readOnly: false, // required: true refuses every write call
|
|
1505
1949
|
});
|
|
@@ -1539,7 +1983,8 @@ Connecta configuration still decides connector access and management: `maintaine
|
|
|
1539
1983
|
Protect the Worker with a Worker-level Access application whose destination is
|
|
1540
1984
|
`{ "type": "worker", "worker_id": "<the Worker script tag>" }`. A traditional
|
|
1541
1985
|
hostname-level application blocks the URL but does not attach `ctx.access` to
|
|
1542
|
-
the Worker.
|
|
1986
|
+
the Worker. The Worker example's `alchemy.run.ts` declares this application and
|
|
1987
|
+
attaches it through the Worker's `access` property. Enable [**Managed OAuth**](https://developers.cloudflare.com/cloudflare-one/access-controls/applications/http-apps/managed-oauth/)
|
|
1543
1988
|
on that Worker-level application for interactive MCP clients. Cloudflare then
|
|
1544
1989
|
owns the unauthenticated challenge and `/.well-known/` metadata, issues opaque
|
|
1545
1990
|
RFC 8707 tokens, and resolves them into the same trusted Worker identity.
|
|
@@ -1627,7 +2072,10 @@ claims. SDK 3.12.0 drops opaque `aud`, so connecta makes one additional Backend
|
|
|
1627
2072
|
API verification request and reads the raw response. Its subject and client
|
|
1628
2073
|
must agree with the SDK, and it must report an unrevoked, unexpired token.
|
|
1629
2074
|
The extra call follows request cancellation, times out after ten seconds,
|
|
1630
|
-
caches no token or audience, and
|
|
2075
|
+
reads at most 64 KiB, follows no redirect, caches no token or audience, and
|
|
2076
|
+
denies admission on failure, including a redirect. It ends its request and
|
|
2077
|
+
response body on every outcome, including an error status and a body that
|
|
2078
|
+
stops arriving.
|
|
1631
2079
|
|
|
1632
2080
|
### Explicit fallback for unbound tokens
|
|
1633
2081
|
|
|
@@ -1644,6 +2092,140 @@ for this fallback. Hosts may cache registrations, but the list is impractical
|
|
|
1644
2092
|
for standard onboarding. A CIMD client identifies the host, not the deployment
|
|
1645
2093
|
resource. Prefer resource audiences with `allowedOAuthClientIds` omitted or `[]`.
|
|
1646
2094
|
|
|
2095
|
+
### One Clerk instance, one Organization per deployment
|
|
2096
|
+
|
|
2097
|
+
`organizationId` lets one Clerk instance serve several deployments. Give each
|
|
2098
|
+
deployment its own Clerk Organization and pass that Organization's id. In the
|
|
2099
|
+
Node template, set `CLERK_ORGANIZATION_ID` instead of editing source.
|
|
2100
|
+
|
|
2101
|
+
```ts
|
|
2102
|
+
clerkAuth({ publishableKey, secretKey,
|
|
2103
|
+
publicUrl: "https://crm.example.com",
|
|
2104
|
+
organizationId: "org_..." });
|
|
2105
|
+
```
|
|
2106
|
+
|
|
2107
|
+
Admission in organization mode:
|
|
2108
|
+
|
|
2109
|
+
- Membership is the only organization rule. Every admitted Clerk user is a
|
|
2110
|
+
member of that Organization. Connecta asks Clerk's Backend API,
|
|
2111
|
+
`GET /v1/organizations/{organization_id}/memberships?user_id={user_id}`,
|
|
2112
|
+
for MCP OAuth tokens (JWT and opaque) and for operator sessions. It caches
|
|
2113
|
+
each answer per Organization and user, counted from when the lookup
|
|
2114
|
+
started: a member for 60 seconds, a non-member for 30. Each request that
|
|
2115
|
+
misses the cache runs its own lookup, subject to the bounds below. An answer
|
|
2116
|
+
is cached only if no lookup that started later has already cached one, so an
|
|
2117
|
+
older answer never replaces a newer one. If eviction suppresses a newer
|
|
2118
|
+
answer, any older answer for that user is invalidated. Assuming the clock
|
|
2119
|
+
does not step back unseen within the TTL window described below, every
|
|
2120
|
+
request reaching the check 60 seconds or more after a member's removal in
|
|
2121
|
+
Clerk is refused, even while their day-long access token remains valid.
|
|
2122
|
+
Under the same clock assumption, a non-member answer expires within 30
|
|
2123
|
+
seconds of an addition; admission then requires a successful lookup.
|
|
2124
|
+
- The cache reads the deployment's clock. An answer is used only while the
|
|
2125
|
+
clock reads from its lookup's start to that start plus 60 or 30 seconds.
|
|
2126
|
+
A step forward only ends it early. A step back to before the start ends it
|
|
2127
|
+
too, and once an answer has been found expired no later step revives it. A
|
|
2128
|
+
step back that lands within that window before any request has seen the
|
|
2129
|
+
answer expire cannot be detected, and keeps the answer until the clock
|
|
2130
|
+
again reads its start plus the TTL. That unseen rollback can extend cached
|
|
2131
|
+
admission beyond 60 seconds of elapsed time after removal.
|
|
2132
|
+
- Organization claims are never read. A token's `org_id` and a session's
|
|
2133
|
+
active Organization neither admit nor refuse. A member's token is bound to
|
|
2134
|
+
this deployment's URL whichever Organization they chose in Clerk, and a
|
|
2135
|
+
non-member gets `403` from the lookup.
|
|
2136
|
+
- A lookup that fails, answers with a redirect, an error status other than
|
|
2137
|
+
`429` (handled below), or an unreadable or oversized body, or times out ten seconds after it started
|
|
2138
|
+
refuses its request with `403`. Redirects are never followed. A caller that
|
|
2139
|
+
leaves cancels its own lookup and is refused; other requests' lookups go
|
|
2140
|
+
on. Connecta ends the Backend API request and its body on every outcome and
|
|
2141
|
+
caches neither a failure nor a cancelled lookup, so the next request asks
|
|
2142
|
+
Clerk again, subject to the bounds and 429 backoff below.
|
|
2143
|
+
- Connecta checks the token's audience, issuer, and subject before the lookup.
|
|
2144
|
+
The issuer must be the Frontend API origin encoded in `publishableKey`, and
|
|
2145
|
+
the subject must be the user the lookup names. An opaque token's
|
|
2146
|
+
verification response comes from the instance's Backend API and is matched by
|
|
2147
|
+
subject instead. A mismatch gets `401`.
|
|
2148
|
+
- Organization roles and permissions grant nothing. `maintainers` and pool
|
|
2149
|
+
`members` still list Clerk user ids in code. A member of the Organization in
|
|
2150
|
+
neither list gets `404` at `/mcp` and at every `/mcp/<pool>`.
|
|
2151
|
+
- `allowedDomains` and `gate` still apply after membership. Every configured
|
|
2152
|
+
check must pass, and an outsider never reaches `gate`.
|
|
2153
|
+
- Resource-bound tokens are required. A nonempty `allowedOAuthClientIds`
|
|
2154
|
+
throws at construction; omit it or pass `[]`. Every deployment on a shared
|
|
2155
|
+
instance gets tokens from one issuer, so the resource audience is what keeps
|
|
2156
|
+
one deployment's token out of another.
|
|
2157
|
+
- Advertised scopes are unchanged. Organization mode needs no Clerk
|
|
2158
|
+
organization scope, operator page, or sign-in page setting.
|
|
2159
|
+
|
|
2160
|
+
Protect the shared Backend API quota:
|
|
2161
|
+
|
|
2162
|
+
- Put ingress rate limiting in front of every deployment. Budget the combined
|
|
2163
|
+
traffic of all deployments and other applications using the instance,
|
|
2164
|
+
including authenticated non-members and opaque-token authentication. For
|
|
2165
|
+
example, rate limit both `crm.example.com` and `tickets.example.com` within
|
|
2166
|
+
one shared quota budget when they use the same Clerk instance; independent
|
|
2167
|
+
limits that each consume the whole quota do not protect the other deployment.
|
|
2168
|
+
[Clerk's system limits](https://clerk.com/docs/guides/how-clerk-works/system-limits)
|
|
2169
|
+
apply per instance identified by the secret key: 1,000 Backend API requests
|
|
2170
|
+
per ten seconds in production, 100 in development. Membership reads use that
|
|
2171
|
+
general limit. A deployment's MCP concurrency setting does not bound all
|
|
2172
|
+
authentication paths, including `subscriptions/listen` and operator routes.
|
|
2173
|
+
The independent [authentication capacity bound](#authentication-capacity-and-ingress-limiting)
|
|
2174
|
+
covers those paths and the SDK's authentication calls before this adapter's
|
|
2175
|
+
organization-specific bounds; it still does not impose a rate budget.
|
|
2176
|
+
- Organization mode permits at most 16 concurrent membership/extra opaque
|
|
2177
|
+
verification calls per `clerkAuth` instance (per isolate on Workers), with at
|
|
2178
|
+
most two per user. Each call owns its signal, deadline, and response body;
|
|
2179
|
+
there is no shared promise or waiting queue. Excess calls immediately get
|
|
2180
|
+
HTTP `503`, fixed code `organization_lookup_busy`, `retryable: true`, and
|
|
2181
|
+
`Retry-After: 1`. These fixed caps limit bursts relative to the documented
|
|
2182
|
+
quotas; they do not limit sustained request rates or coordinate isolates,
|
|
2183
|
+
deployments, or the SDK's own authentication calls. Do not treat them as a
|
|
2184
|
+
substitute for ingress rate limiting.
|
|
2185
|
+
- A `429` from a membership read or extra opaque verification starts backoff
|
|
2186
|
+
shared by those calls in that isolate. Connecta honors `Retry-After` seconds
|
|
2187
|
+
(or an HTTP date), clamped to 1–60 seconds; missing or malformed values use
|
|
2188
|
+
ten seconds. Concurrent 429s can extend, never shorten, the current backoff.
|
|
2189
|
+
The triggering request and new misses during backoff receive the same
|
|
2190
|
+
retryable `503` and a `Retry-After` for the remaining whole seconds, rounded
|
|
2191
|
+
up. Backoff is timed on the wall clock, so a clock step back while it runs
|
|
2192
|
+
lengthens the window by the size of the step. No membership verdict is
|
|
2193
|
+
cached for these refusals. Valid cached
|
|
2194
|
+
membership answers remain usable; opaque tokens still need verification.
|
|
2195
|
+
After backoff expires the next miss asks Clerk again, with no automatic
|
|
2196
|
+
retry of the original request. Monitor the payload-free
|
|
2197
|
+
`organization_lookup_busy` log reason and adjust the ingress budget when it
|
|
2198
|
+
recurs. A quota exhausted by another deployment can still refuse uncached
|
|
2199
|
+
admission here.
|
|
2200
|
+
|
|
2201
|
+
Set up a shared instance:
|
|
2202
|
+
|
|
2203
|
+
1. Enable Organizations on the Clerk instance. Create one Organization per
|
|
2204
|
+
deployment. Add each person who signs in to that deployment as a member;
|
|
2205
|
+
any role works.
|
|
2206
|
+
2. Enable resource audiences as described in
|
|
2207
|
+
[Enable resource audiences in Clerk](#enable-resource-audiences-in-clerk).
|
|
2208
|
+
Each deployment has its own public URL, so its tokens name only it.
|
|
2209
|
+
3. Set `organizationId` to the deployment's Organization id. List
|
|
2210
|
+
`maintainers` and pool `members` by Clerk user id as usual.
|
|
2211
|
+
4. Restart or redeploy. Verify that a member is admitted at `/mcp` and on the
|
|
2212
|
+
operator pages, a signed-in non-member gets `403`, and a token issued for
|
|
2213
|
+
another deployment's URL gets `401`.
|
|
2214
|
+
|
|
2215
|
+
Isolation tradeoff: the secret key is instance-wide. It reaches every
|
|
2216
|
+
Organization, user, and OAuth application on the instance, so whoever reads it
|
|
2217
|
+
from one deployment's environment can read and change every other
|
|
2218
|
+
deployment's membership. Organization mode separates admission, not
|
|
2219
|
+
administration. When a deployment needs its own keys, users, or instance
|
|
2220
|
+
settings, give it its own Clerk instance and omit `organizationId`; that shape
|
|
2221
|
+
remains fully supported.
|
|
2222
|
+
|
|
2223
|
+
Organization denials log the fixed codes `organization_membership_denied`,
|
|
2224
|
+
`organization_lookup_failed`, and `token_identity_mismatch`. They carry no user
|
|
2225
|
+
ids, Organization ids, or Backend API response text.
|
|
2226
|
+
|
|
2227
|
+
### Session and diagnostic boundaries
|
|
2228
|
+
|
|
1647
2229
|
Clerk session tokens authenticate operator routes and browser OAuth callbacks
|
|
1648
2230
|
only, with the deployment-origin `azp` check. MCP rejections retain the `401`
|
|
1649
2231
|
Bearer challenge and endpoint `resource_metadata` URL required by
|
|
@@ -1679,7 +2261,9 @@ that is not `pk_test_`/`pk_live_` and a base64-encoded domain throws when
|
|
|
1679
2261
|
`clerkAuth` is called, as `allowedDomains` does, naming the option without
|
|
1680
2262
|
quoting the value — usually the _secret_ key pasted into the publishable slot.
|
|
1681
2263
|
A per-request deployment, as on Workers, sees that error on its first request
|
|
1682
|
-
instead of a base64 stack on every route.
|
|
2264
|
+
instead of a base64 stack on every route. `organizationId` must be `org_`
|
|
2265
|
+
followed by 27 letters or digits, and it cannot be combined with a nonempty
|
|
2266
|
+
`allowedOAuthClientIds`; either mistake throws at construction.
|
|
1683
2267
|
|
|
1684
2268
|
## Human authentication management
|
|
1685
2269
|
|
|
@@ -1739,6 +2323,25 @@ and Origin checks. Vaults without sealing keep ordinary handoffs and cannot
|
|
|
1739
2323
|
serve URL elicitation. See [authorization recovery](./meta-tools.md#authorization-recovery)
|
|
1740
2324
|
for capability gating, retry termination, and dispatch restrictions.
|
|
1741
2325
|
|
|
2326
|
+
Downstream OAuth credential POSTs (code exchange, refresh, dynamic client
|
|
2327
|
+
registration and revocation) use a transport that sends each request at most
|
|
2328
|
+
once and never follows redirects. On Node, serve with `listen` from
|
|
2329
|
+
`@zackbart/connecta/node` and retain the `undici` peer; custom root-only Node
|
|
2330
|
+
hosts refuse these POSTs before dispatch. Node's explicit `listen({ proxy })`
|
|
2331
|
+
option applies to them. Discovery GETs keep native fetch and its host proxy
|
|
2332
|
+
policy. Workers use their native fetch transport. See [Node transport setup](./deploying.md#start-on-node).
|
|
2333
|
+
|
|
2334
|
+
A refresh without a confirmed usable answer may have rotated the refresh token.
|
|
2335
|
+
Connecta blocks its fingerprint and requires a fresh authorization flow, without
|
|
2336
|
+
replaying that token or claiming the server did not receive it. Authorization
|
|
2337
|
+
can still be restarted; an unresolved fingerprint cannot become redeemable again
|
|
2338
|
+
by writing the same token into an old grant. Registration failure can leave a
|
|
2339
|
+
client at the provider; check the provider's client administration before
|
|
2340
|
+
starting another registration if its reply was lost. Disconnect removes the
|
|
2341
|
+
local grant before attempting revocation, and an unconfirmed revocation asks the
|
|
2342
|
+
operator to revoke it at the provider. No token forms or provider error prose
|
|
2343
|
+
are included in diagnostics.
|
|
2344
|
+
|
|
1742
2345
|
A verified `/connect` visit gives downstream OAuth work and its state handoff 30 seconds. The request signal
|
|
1743
2346
|
and deadline reach discovery, registration, and other downstream fetches; expiry returns `504 OAuth
|
|
1744
2347
|
authorization start timed out`. A restart's grant write, catalog invalidation, and scope close drain before
|
|
@@ -1759,9 +2362,15 @@ its URL.
|
|
|
1759
2362
|
|
|
1760
2363
|
Credential and OAuth mutation require interactive identity, connector visibility, management permission, and
|
|
1761
2364
|
an exact same-origin `Origin` for browser mutations. A static credential `authorize_connector` call needs
|
|
1762
|
-
visibility only
|
|
1763
|
-
without either it returns `unavailable`.
|
|
1764
|
-
`
|
|
2365
|
+
visibility only. With `ui: operatorUi()` and a vault, a missing credential returns a secret-free
|
|
2366
|
+
`operator_config` handoff; without either it returns `unavailable`. When all declared credential
|
|
2367
|
+
fields are configured, the explicit result returns `ready` with conditional guidance and retains
|
|
2368
|
+
the operator handoff fields (or the deployment configuration message when recovery is unavailable).
|
|
2369
|
+
If a call just failed with an authentication or operator-configuration error, give that handoff
|
|
2370
|
+
to the operator; otherwise proceed with the call. Presence does not validate a key, and this
|
|
2371
|
+
stateless result cannot remember failures from earlier requests. Authentication errors retain
|
|
2372
|
+
`oauth`, `operator_config` or `unavailable` recovery, never `ready`. The OAuth branch also checks
|
|
2373
|
+
management permission and returns `unavailable` when refused.
|
|
1765
2374
|
|
|
1766
2375
|
Core owns `/connect/<connector>` and the callback without the optional UI. Both require Clerk sessions or
|
|
1767
2376
|
Cloudflare Access users; MCP OAuth tokens, `cta_` tokens, and Access service identities
|
|
@@ -1801,6 +2410,13 @@ need `consumeAuthError` to accept error callbacks, sharing `finishAuth`'s atomic
|
|
|
1801
2410
|
consent claim. Reissue
|
|
1802
2411
|
pending consent links after upgrading; callbacks without a saved initiating user cannot complete.
|
|
1803
2412
|
|
|
2413
|
+
Custom connectors may implement `verifyCallbackParameters(params, ctx)` for
|
|
2414
|
+
provider-specific callback assertions. The route calls it after successful
|
|
2415
|
+
state verification and before issuer validation, handoff consumption, error
|
|
2416
|
+
handling, or code exchange. Returning false or throwing produces the generic
|
|
2417
|
+
invalid-callback refusal. Apply the same assertions inside `finishAuth` when
|
|
2418
|
+
supporting direct programmatic completion.
|
|
2419
|
+
|
|
1804
2420
|
`compareAndSet` atomically claims link nonces, callback handoffs, and consents, with one concurrent winner,
|
|
1805
2421
|
so each completes once.
|
|
1806
2422
|
[Meta-tools](./meta-tools.md#authorization-recovery) describes recovery.
|
|
@@ -1844,9 +2460,9 @@ path, without credentials or a fragment. Its `client_id` must equal its URL,
|
|
|
1844
2460
|
and its metadata must include the deployment's exact callback and public-client
|
|
1845
2461
|
authentication. Changing from an external document to the self-hosted URL needs
|
|
1846
2462
|
one Disconnect and reconnect because tokens belong to the old client ID.
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
2463
|
+
Basecamp restricts DCR for HTTPS callbacks, so its generic `remoteMcp()` entry
|
|
2464
|
+
needs a public HTTPS deployment or an external document. Preserve its client
|
|
2465
|
+
identity when following the [hosted MCP migration guide](./hosted-mcp-migration.md).
|
|
1850
2466
|
|
|
1851
2467
|
For a pre-registered client, use:
|
|
1852
2468
|
|
|
@@ -1907,15 +2523,28 @@ api("church", {
|
|
|
1907
2523
|
No discovery or registration occurs; all URLs are configuration, so the
|
|
1908
2524
|
[advertised-URL rule](#urls-a-downstream-advertises) does not apply.
|
|
1909
2525
|
|
|
1910
|
-
| Option or binding
|
|
1911
|
-
|
|
|
1912
|
-
| Endpoints
|
|
1913
|
-
| `pkce`
|
|
1914
|
-
| `authorizationParams`
|
|
1915
|
-
| `
|
|
1916
|
-
| `
|
|
1917
|
-
|
|
|
1918
|
-
|
|
|
2526
|
+
| Option or binding | Contract |
|
|
2527
|
+
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
2528
|
+
| Endpoints | Checked at construction: HTTPS, HTTP only on loopback, no credentials or fragment. |
|
|
2529
|
+
| `pkce` | S256 by default; `false` omits challenge and verifier. |
|
|
2530
|
+
| `authorizationParams` | Adds provider parameters; cannot restate the grant's own. |
|
|
2531
|
+
| `expectedCallbackParameters` | Optional `Readonly<Record<string, string>>` of fixed provider response values. Each configured parameter must occur exactly once with its exact decoded value, after pending-state verification and before issuer validation, flow claims, code exchange, or grant persistence. Missing, duplicate, or mismatched values refuse without exchanging a code or returning parameter values. Omission preserves existing behavior. |
|
|
2532
|
+
| `revocationEndpoint` | Optional trusted HTTPS revocation URL (loopback HTTP allowed), without credentials, query or fragment. Disconnect removes the local grant before one bounded attempt using the configured client. Failure reports `oauth_revocation_failed`; a repeated Disconnect never retries. |
|
|
2533
|
+
| `revocationRequestFormat` | `"form"` by default; `"json"` sends JSON without `token_type_hint`. Requires `revocationEndpoint` when set. |
|
|
2534
|
+
| `tokenRequestHeaders` | Adds code-exchange and every refresh's headers; cannot set `Authorization`, `Content-Type`, `Content-Length`, `Cookie`, or `Host`. |
|
|
2535
|
+
| `tokenEndpointAuthMethod` | Defaults to `client_secret_basic` with a secret, `none` without; mismatched pairings refuse construction. |
|
|
2536
|
+
| Issuer/resource | No advertised issuer means no required RFC 9207 `iss` or sent RFC 8707 `resource`. The grant binds to the token endpoint; after it changes, old tokens are never sent and the next consent replaces them. |
|
|
2537
|
+
| Client | One deployment-config identity, never written or sealed to storage; no owner vault slot. Store leaks contain no client secret; Disconnect deletes none and Restart carries none. Read from environment or Worker secrets; empty strings refuse construction without quoting values. |
|
|
2538
|
+
|
|
2539
|
+
For a connector bound to a fixed company, set
|
|
2540
|
+
`expectedCallbackParameters: { realmId: "company_alice" }`. This checks all
|
|
2541
|
+
callbacks, including errors, and direct `finishAuth` calls. Names must be
|
|
2542
|
+
nonempty ASCII letters, digits, dots, underscores, or hyphens; values must be
|
|
2543
|
+
nonempty strings. The protocol fields `code`, `state`, `iss`, `error`,
|
|
2544
|
+
`error_description`, and `error_uri` are reserved. Malformed expectations throw
|
|
2545
|
+
at construction without quoting values. Unconfigured provider parameters are
|
|
2546
|
+
ignored. Expectations are deployment configuration, never grant metadata or
|
|
2547
|
+
dynamic company selection; they do not inspect or rebind an existing grant.
|
|
1919
2548
|
|
|
1920
2549
|
Handlers never see the grant. `ctx.oauth.fetch(url, init)` sends the calling
|
|
1921
2550
|
owner's access token as `Authorization: Bearer` with these rules:
|
|
@@ -1924,10 +2553,28 @@ owner's access token as `Authorization: Bearer` with these rules:
|
|
|
1924
2553
|
untrusted response URLs. Return redirects unfollowed; handlers cannot set `Authorization`.
|
|
1925
2554
|
- On 401, recover exactly once using an already rotated token or one coalesced
|
|
1926
2555
|
refresh through `remoteMcp()`'s coordinator. Persist rotation even when its
|
|
1927
|
-
owner is cancelled after the answer.
|
|
2556
|
+
owner is cancelled after the answer. Only a positively read-classified
|
|
2557
|
+
GET/HEAD without method-override headers replays once;
|
|
2558
|
+
stream bodies are refused.
|
|
2559
|
+
- A request the core classifies as a write never replays (INV-9). Positive read
|
|
2560
|
+
classification comes from deployment `classification` first and otherwise the rule the
|
|
2561
|
+
registry applies to the tool's hints (a contradictory `readOnlyHint: true`
|
|
2562
|
+
with `destructiveHint: true` is a write). The actual method must also be GET
|
|
2563
|
+
or HEAD; an override header, missing read evidence, or a POST remains a
|
|
2564
|
+
one-attempt write, even when the tool calls it a read. After the recovery,
|
|
2565
|
+
the call fails with a non-retryable `connector_call_failed` whose outcome is
|
|
2566
|
+
unknown, and the renewed grant serves any fresh call the agent makes after
|
|
2567
|
+
reconciling the target.
|
|
2568
|
+
- Whatever that recovery meets, a write's failure is never retryable and its
|
|
2569
|
+
outcome is unknown. Until a refresh succeeds the 401's verdict stands:
|
|
2570
|
+
`downstream_oauth_required`, which the invocation marks
|
|
2571
|
+
`reconciliationRequired`, with the `authorize_connector` handoff, whether
|
|
2572
|
+
the refresh was [dead](#refresh-failures), lost after it was sent, raced
|
|
2573
|
+
by a disconnect or reset, failed in storage or contention, or handed back
|
|
2574
|
+
the token the API just rejected, which renews nothing.
|
|
1928
2575
|
- No grant, a second 401, or a [dead refresh](#refresh-failures) means
|
|
1929
2576
|
`downstream_oauth_required`, directing `authorize_connector`. Refresh waiter deadlines
|
|
1930
|
-
return retryable `unavailable
|
|
2577
|
+
return retryable `unavailable` for a read. Latch second 401s for the request scope,
|
|
1931
2578
|
preventing repeated refreshes by later program calls.
|
|
1932
2579
|
- Handlers can name no storage, sealing, or owner partition; the registry owns them.
|
|
1933
2580
|
- The answer's `.text()` and `.json()`, clones' too, decode its bytes as UTF-8, so
|
|
@@ -1963,6 +2610,90 @@ provider skill explains App permissions and installation prerequisites.
|
|
|
1963
2610
|
|
|
1964
2611
|
## Contributor reference
|
|
1965
2612
|
|
|
2613
|
+
### INV-5 output redaction limits
|
|
2614
|
+
|
|
2615
|
+
The output boundary's guarantee is K = 8 units, and these are its exact limits:
|
|
2616
|
+
|
|
2617
|
+
- A run of eight consecutive UTF-16 units of a registered form is matched
|
|
2618
|
+
wherever it appears, raw, under JSON escapes, or in a decoded base64 view.
|
|
2619
|
+
- Pieces shorter than eight units are matched only where they are adjacent:
|
|
2620
|
+
the projection joins neighbouring leaves (array items, text blocks), and
|
|
2621
|
+
provider lanes join one source's runs (one Slides shape's text runs, one Docs document's text). Pieces shorter than eight
|
|
2622
|
+
units that are not adjacent, such as the two seven-unit halves of a fourteen-
|
|
2623
|
+
unit value in two Slides shapes with other leaves between them, survive: they
|
|
2624
|
+
are indistinguishable from ordinary text without redacting ordinary words.
|
|
2625
|
+
- Object keys are checked on their own and never join the value projection, so
|
|
2626
|
+
a key never separates two adjacent values. A value split between a key and
|
|
2627
|
+
its value with each piece shorter than eight units, such as a seven-unit key
|
|
2628
|
+
holding a seven-unit value, survives like any other non-adjacent pieces; a
|
|
2629
|
+
piece of eight or more units is still matched in either place.
|
|
2630
|
+
- Private values of one to three units match whole field values only; four to
|
|
2631
|
+
seven also as substrings; eight and more also by every eight-unit run.
|
|
2632
|
+
- One checker (`checked()` in `src/sent-secrets.ts`) applies everything below
|
|
2633
|
+
to every output-bound string: leaves, keys, scalars, declared binary, typed
|
|
2634
|
+
binary, cursor claims and Connecta's own encodings, provider lanes, bounded
|
|
2635
|
+
reads and structural refusals. Its matching steps run only inside it; a
|
|
2636
|
+
serialized container's cross-token scan, which can only withhold it whole, is
|
|
2637
|
+
the one other reader of the fragment index. The test "there is one string
|
|
2638
|
+
checker" spies on every step and fails when one runs outside it.
|
|
2639
|
+
- A Proxy, which only in-process code can construct, can answer a later read
|
|
2640
|
+
differently from the one redaction made; the guarantee covers what redaction
|
|
2641
|
+
read. So can code on an error class's prototype, which its copy keeps so that
|
|
2642
|
+
classification still sees its class; JSON calls none of it once `toJSON` is
|
|
2643
|
+
shadowed. Only verified plain data is returned as it stands; everything else
|
|
2644
|
+
is a copy of checked data properties, and every value is JSON data, a fresh
|
|
2645
|
+
`Uint8Array`, or an error copy.
|
|
2646
|
+
- An array's fields other than its indices are never read, because JSON never
|
|
2647
|
+
serializes them.
|
|
2648
|
+
|
|
2649
|
+
The encoding lattice is exactly this, and anything outside it is out of scope:
|
|
2650
|
+
|
|
2651
|
+
- Text views of every string, whether a leaf, a key, a declared binary field or
|
|
2652
|
+
a cursor claim: the raw text; one layer of JSON escapes decoded
|
|
2653
|
+
(`\uXXXX` and `\" \\ \/ \b \f \n \r \t`); percent escapes decoded; and
|
|
2654
|
+
the form decoding, a `+` read as a space with any percent escapes decoded,
|
|
2655
|
+
generated whenever a `+` appears. Text matching itself also reads
|
|
2656
|
+
through up to eight JSON escape layers and matches registered percent forms.
|
|
2657
|
+
- In every text view, base64 and base64url runs are decoded. A run is a maximal
|
|
2658
|
+
span of `A-Z a-z 0-9 + / - _`, decoded from its first unit; up to two `=`
|
|
2659
|
+
after it are padding and are never counted. A whole view is decoded from six
|
|
2660
|
+
units; a run inside one from `SHORT_RUN_UNITS` = 6 under payload rules with a
|
|
2661
|
+
four-to-seven-unit private value registered, otherwise `FRAGMENT_RUN_UNITS` = 11.
|
|
2662
|
+
- Derivation: a run of L units decodes to floor(3L/4) bytes, so p bytes that start
|
|
2663
|
+
a bytes into the encoding (alignment a = 0, 1 or 2) need L >= ceil(4(a + p)/3),
|
|
2664
|
+
least at a = 0. The smallest piece a payload pass matches is a short value,
|
|
2665
|
+
four units and so at least four bytes: ceil(16/3) = 6. Otherwise it is K = 8
|
|
2666
|
+
units, at least eight bytes: ceil(32/3) = 11. Padding follows the run, so a
|
|
2667
|
+
padded and an unpadded encoding decode alike. The tests cover four-, seven-
|
|
2668
|
+
and eight-unit values at every alignment, padded and unpadded, in both
|
|
2669
|
+
alphabets.
|
|
2670
|
+
- Decoded bytes are matched as bytes, as UTF-8 when a form is not ASCII, and
|
|
2671
|
+
through their own JSON escapes within the eight-layer limit. Every decode is
|
|
2672
|
+
charged to the call's work budget, and content the budget cannot check is
|
|
2673
|
+
withheld.
|
|
2674
|
+
- Bytes, decoded or held by a typed array, are checked as bytes, through their
|
|
2675
|
+
JSON escapes, as UTF-8, and through the base64 they carry. Text views are
|
|
2676
|
+
views of strings: bytes that need a percent or form decoding before their
|
|
2677
|
+
base64 shows are two decodes chained, outside the lattice.
|
|
2678
|
+
- Out of scope: two or more text-layer decodes chained (percent escapes inside
|
|
2679
|
+
JSON escapes, or two JSON layers before base64); base64 of base64 (decoded
|
|
2680
|
+
content is searched for base64 up to three levels deep, but the guarantee does
|
|
2681
|
+
not depend on it); a base64 encoding glued to preceding alphabet units with no
|
|
2682
|
+
separator, such as a path segment after `/`, which decodes misaligned; HTML
|
|
2683
|
+
entities; compression; custom alphabets; encryption; bytes written as a
|
|
2684
|
+
JSON array of numbers; and one encoding split across separate leaves, such as
|
|
2685
|
+
the base64 of a value cut into array items. Raw text and JSON escapes are
|
|
2686
|
+
matched across adjacent leaves, but base64 and base64url decoding operates
|
|
2687
|
+
within each string.
|
|
2688
|
+
- The boundary removes echoes of values Connecta sent. A downstream already
|
|
2689
|
+
holds what it was sent and can always return it in a form outside this
|
|
2690
|
+
lattice, so redaction is not a defense against a downstream that reshapes a
|
|
2691
|
+
value on purpose.
|
|
2692
|
+
|
|
2693
|
+
`test/sent-secret-output-boundary.test.ts` holds the evidence, including its
|
|
2694
|
+
`review round 3`, `review round 4` and `review round 5` blocks for these limits;
|
|
2695
|
+
`test/writeonly-wire-redaction.node.test.ts` runs the QuickJS guest cases.
|
|
2696
|
+
|
|
1966
2697
|
### Source and tests
|
|
1967
2698
|
|
|
1968
2699
|
[Decision 0007](https://github.com/zackbart/connecta/blob/main/decisions/0007-agent-pool-layout.md)
|
|
@@ -1973,6 +2704,8 @@ boundaries, and `src/storage/keys.ts` owns credential key families.
|
|
|
1973
2704
|
Inbound adapters live in [src/auth](https://github.com/zackbart/connecta/tree/main/src/auth);
|
|
1974
2705
|
[auth routes](https://github.com/zackbart/connecta/blob/main/src/routes/oauth.ts)
|
|
1975
2706
|
own connection handoffs and callbacks. Check [inbound auth tests](https://github.com/zackbart/connecta/blob/main/test/inbound-auth.test.ts),
|
|
2707
|
+
[Clerk organization tests](https://github.com/zackbart/connecta/blob/main/test/clerk-organization.test.ts)
|
|
2708
|
+
and their [workerd counterpart](https://github.com/zackbart/connecta/blob/main/test/clerk-organization-workerd.test.ts),
|
|
1976
2709
|
[Access tests](https://github.com/zackbart/connecta/blob/main/test/cloudflare-access-auth.test.ts),
|
|
1977
2710
|
and [identity-scope tests](https://github.com/zackbart/connecta/blob/main/test/identity-scope.test.ts)
|
|
1978
2711
|
for credential precedence and disclosure boundaries.
|
|
@@ -1985,3 +2718,23 @@ Downstream OAuth implementation and evidence are in
|
|
|
1985
2718
|
[refresh leases](https://github.com/zackbart/connecta/blob/main/test/oauth-refresh-lease.test.ts),
|
|
1986
2719
|
and [API OAuth tests](https://github.com/zackbart/connecta/blob/main/test/api-oauth.test.ts).
|
|
1987
2720
|
Use the test pointers within each section for narrower failure cases.
|
|
2721
|
+
|
|
2722
|
+
### Authentication execution (contributor)
|
|
2723
|
+
|
|
2724
|
+
Built-in authentication and downstream OAuth use private native programs behind
|
|
2725
|
+
the same Promise adapter and SDK contracts. Recognition still selects one
|
|
2726
|
+
verifier, and a refusal never falls back to another identity provider. Native
|
|
2727
|
+
operations use the existing request owner; cancellation cannot make a closed
|
|
2728
|
+
caller consume a refresh result or send another resource request. Once the
|
|
2729
|
+
claim CAS is submitted, the bounded refresh coordinator completes independently
|
|
2730
|
+
and commits a successful rotation under the existing epoch and spent-token
|
|
2731
|
+
rules. Code exchange does not acquire that coordinator ownership.
|
|
2732
|
+
|
|
2733
|
+
Authentication capacity remains charged until the underlying uncancellable work
|
|
2734
|
+
settles or collection proves that work unreachable. Abandonment alone never
|
|
2735
|
+
releases capacity. A native
|
|
2736
|
+
refactor does not change membership cache TTLs, audience/resource bindings,
|
|
2737
|
+
agent namespaces, credential partitions, OAuth transport policy or the rule of
|
|
2738
|
+
one physical attempt per credential-bearing POST. See the
|
|
2739
|
+
[native auth implementation](./architecture.md#native-auth-programs-contributor)
|
|
2740
|
+
for contributor boundaries and retained Promise leaves.
|