Skip to content

Commit d8c611d

Browse files
feat(spec): an oauth2 authorization_code connector instance can take its OAuth client from the installer — clientSource: 'installer' (#28920)
<!-- claude-projects-attribution: {"github_login":"os-steve"} --> _Requested by **Steve** · [project thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi97JUcgFYCr7uuKzrKbe2paU)_ Fixes #28864 Clause-②: yes (widening) Metadata-protocol: added Skills-docs: follow-up owed — `skills/objectstack-automation/references/integration-recipes.md` (its e-signature recipe pins `clientId: 'objectstack-prod'`, the publisher-owned client ruling 6566728470 rules out for a listing) and `content/docs/automation/connectors.mdx` (the `oauth2` section). Both should teach `clientSource: 'installer'` once the server slice (#28866) serves it: taught today, the key sends an author to an instance the service holds degraded. The skill file is governed (Tier H). Ruling-ref: 6566728470 ## 维护者速读 - 改了什么:`oauth2` 的 `authorization_code` 连接器实例新增可选键 `clientSource: 'installer'`,声明「OAuth 客户端由安装方自带」:每个部署的管理员自己到厂商处登记客户端(回调填本部署的地址),把 client id 和 secret 填在连接上;定义里不写 `clientId`、`credentialRef`,两者同写会被拒。ADR-0143 D2.1 追加一条修订说明。 - 为什么改:厂商只认登记过回调地址的客户端,而回调地址每个部署都不同;包里写死的 `clientId` 只在发布者登记过的部署上能连上。#28702 裁决 B(「同意」)要求开源版 OAuth 应用不带官方客户端、由安装方自带,DocuSign 应用等这一步。 - 风险与回滚:纯放宽,不写新键的实例解析结果完全不变,包里写 `clientId` 的私有应用照旧可用。服务端还不读这个键(#28866 再做):非 MCP 实例声明它会被标为降级,绝不会无客户端发请求。回滚即撤掉本 PR。 - 先看:`packages/spec/src/integration/connector.zod.ts` 的 ADR-0143 D2.1 规则块(两处 `installerClient`);`packages/spec/src/shared/connector-auth.zod.ts` 的 `clientSource` 与上方 docblock;`docs/adr/0143-outbound-connector-auth-is-open-tier.md` D2 末尾的新修订说明。 - 两分钟验证:`pnpm --filter @objectstack/spec exec vitest run src/integration/connector-auth-grant-kinds.test.ts`,应看到 29 条全过(其中 9 条是本次新增)。 - 需要你做的:批准本 PR,连同新公开键名 `clientSource`(值 `'installer'`)。ADR 受管,合并要等你的批准。 ## What changes - **`clientSource` on the `authorization_code` instance shape** (`ConnectorInstanceOAuth2AuthorizationCodeAuthSchema`, `@objectstack/spec` and `@objectstack/spec/integration`): an optional closed enum with one member, `'installer'`. The administrator of each deployment registers their own client with the vendor, against that deployment's callback, and enters its id and secret on the instance's connection. The definition then names no client and no secret. - A `clientId` beside it is refused at publish, `custom @ auth.clientId`, on every provider: the definition would name two clients. The rule is in `DeclarativeConnectorEntrySchema`'s ADR-0143 D2.1 block (claim amended to include it), beside the other client-key rules. - Off `provider: 'mcp'` over http, the installer form no longer needs `clientId`. `tokenUrl` and `authorizationUrl` are still required there: the installer owns the client, not the vendor's authorization server. - A `credentialRef` without `clientId` stays refused at `auth.credentialRef` by the shape's existing `dependentRequired`; its message now names the installer form. - On `grant: 'client_credentials'` the key is refused as an unknown key with a guidance entry: that grant keeps no connection record, so there is nowhere to enter a client. - The `clientId` row of each grant now says only where else a client may come from for that grant (`oauth2SharedShape(clientIdNote)`). - **What the D2.1 block accepted before and after**, for the seat: before, an `oauth2` entry off MCP-over-http was refused at `auth.clientId` whenever `clientId` was absent. After, the same holds unless the entry is `authorization_code` with `clientSource: 'installer'`. A new refusal fires only for an entry that carries `clientSource`, a key every earlier input was refused for (`unrecognized_keys`). No input that parsed before is refused now, and no input refused before is accepted unless it carries `clientSource`. - **ADR-0143 D2.1 amendment** (append-only, dated 2026-11-02, under D2 after the 2026-10-31 note): why, what the variant carries, where the installer's client lives (the connection record's per-connection client record, beside a dynamically registered client; D3's persisted list grows by it), why a packaged `clientId` is kept, `client_credentials` unchanged, and the per-account API host left as a follow-up. - **Generated:** `content/docs/references/integration/connector.mdx` (regenerated), `packages/spec/authorable-surface/integration.json` gains `ConnectorInstanceOAuth2AuthorizationCodeAuth:clientSource`. No export moved (`check:api-surface` green). `docs/adr/README.md` does not move (`adr-index` OK). - **Changeset:** `.changeset/28864-oauth2-installer-client.md`, `@objectstack/spec: minor`. ### Forced test edits - `packages/spec/src/integration/connector-credential-redaction.test.ts`: the fail-closed census `DECLARED_NOT_A_SECRET` lists `clientSource`. The census reds on any newly declared instance-auth key until it is confirmed not to be a secret; `clientSource` is a closed enum value. ## Why Ruling 6566728470 on #28702 (letter B, maintainer 「同意」 2026-11-02): an open-edition OAuth connector app ships no ObjectStack-owned `clientId` or secret, and the administrator of each deployment registers their own integration key against that deployment's callback and enters it on the connection. Today the client is a literal of the definition (`clientId`), so whoever publishes the package fixes it, and the vendor's consent page refuses it on every deployment whose callback that client's owner never registered. This is the spec slice; #28866 (server) and #28869 (ui) are `Blocked-by` it, and #28277 (DocuSign) waits on all three. ## Tests `packages/spec/src/integration/connector-auth-grant-kinds.test.ts`, a new describe with 9 tests, each refusal pinned by `code @ path`: - CONTROL accepts: the installer form with endpoints on `openapi`; the installer form on `mcp` over http with no endpoints; a packaged `clientId` without `clientSource` (the accept set does not move). - Refusals: `clientId` beside `clientSource` (alone, with `credentialRef`, and on `mcp` over http) at `auth.clientId`; `credentialRef` with the installer form at `auth.credentialRef`; the endpoints still required off MCP-over-http (`auth.tokenUrl`, `auth.authorizationUrl`, and no `auth.clientId`); an unknown value at `auth.clientSource` (`invalid_value`); `clientSource` on `client_credentials` (`unrecognized_keys`). - The parsed installer definition carries exactly `authorizationUrl`, `clientSource`, `grant`, `scopes`, `tokenUrl`, `type`: no client, no secret. ## Acceptance notes - **The packaged-`clientId` choice: kept, for private apps.** Measured on `origin/main` `76bf6c50`: no example, packaged app or `connector-*` source authors an `oauth2` instance; the authors are test fixtures (spec, service-automation, connector-openapi, lint, ui app-shell), the docs example in `content/docs/automation/connectors.mdx`, the skill recipe above, and ADR-0143 Q3's HubSpot case (a pre-registered client on an MCP server without dynamic registration). Refusing a packaged `clientId` would refuse every one of those inputs, a narrowing beside this widening, and the schema cannot tell a private app (whose author registers every callback) from a listing. So it stays accepted, and the ruling's listing rule is held where listings are reviewed (suggested carrier: #27293). - **Why the `clientId` exclusion is on the entry, not the shape.** The closed list in `shared/refinement-projection.ts` has no arm for "this key excludes that one", so a shape-level `.refine` would add a dropped-refinement site to every published schema that embeds the auth shape. The entry's `superRefine` is already marked as not stated (`integration/DeclarativeConnectorEntry` `out` in `dropped-refinements.baseline.json`), holds the other provider-dependent client-key rules, and is what both authoring doors parse. A standalone `ConnectorInstanceAuthSchema.parse` accepts the pair; no runtime door parses the auth shape alone. - **Interim, until #28866.** The automation service does not read `clientSource`. Off MCP, `oauth2NotServableReason` (`packages/services/service-automation/src/plugin.ts`) holds such an instance degraded for a missing `clientId`, never called without a client. On `mcp` over http, `mcpDiscoveryServerUrl` sees `clientId` undefined and runs the MCP authorization flow, registering a client where the server allows it. The changeset says both. - **Server-slice seams** (for #28866, read on this head): `plugin.ts#oauth2NotServableReason` and `#mcpDiscoveryServerUrl` (treat the installer form as supplying the client), `#mcpAuthorizationClient` (`kept.registered === (auth.clientId === undefined)`, and no registration for the installer form), the connection door's begin step (take the client id and secret), `oauth2-grant/grant-handle.ts#createOAuth2GrantHandle` (`declaredClient`), and `sys-connector-connection.object.ts`'s `registered_client` description. UI slice (#28869): `ui/packages/app-shell/src/views/setup/connectorConnections.ts` and `ConnectorConnectionsPage.tsx` (the form, with the deployment's redirect URI). - **DocuSign production host.** Not measured (the container cannot reach the vendor). The ADR note leaves a per-account API host to a follow-up; endpoints stay in the definition, sandbox first. ## Verification All readings on head `23acaa6a` (this branch with `origin/main` `b15b634c` merged in) unless a line says otherwise; every heavy run went through `os-verify-lock.sh`, whose `VERDICT command-exit` line is quoted. <details><summary>Package suites and type checks (packages/spec and every consumer that parses a connector entry)</summary> - `pnpm turbo run build --concurrency=2 --filter='./packages/**'`: Tasks 78 successful, 78 total. VERDICT command-exit 0. - `pnpm --filter @objectstack/spec check:generated`: all 14 generated artifacts up to date (exit 0). Before the merge it named `content/docs/references/**` stale once; `gen:docs` regenerated it, and the build wrote `authorable-surface/integration.json`. Both are committed. - `pnpm --filter @objectstack/spec test`: Test Files 789 passed (789), Tests 23324 passed, 1 todo. VERDICT command-exit 0. - `pnpm --filter @objectstack/spec typecheck` (includes `check:test-typecheck`): VERDICT command-exit 0. - `pnpm --filter @objectstack/service-automation test`: Test Files 255 passed (255), Tests 3281 passed (3281). `typecheck`: VERDICT command-exit 0. - Every connector package, `pnpm --filter @objectstack/connector-NAME test`: imap 42/42, mcp 102/102, openapi 109/109, rest 57/57, slack 10/10, each VERDICT command-exit 0. Their `typecheck` (on `497c4a04`, before the merge, which did not touch them): all five exit 0. - Other consumers that parse a connector entry or the auth union (on `497c4a04`): `@objectstack/lint` `src/lint-flow-credential-literals.test.ts` 17/17; `@objectstack/objectql` `src/protocol-meta.test.ts` 93/93; `@objectstack/metadata` `src/serializers/typescript-serializer-annotation.test.ts` 7/7; `ui` `packages/app-shell/src/views/metadata-admin/clientValidation.optOuts.test.ts` and `packages/app-shell/src/views/setup/ConnectorConnectionsPage.test.tsx` 32/32 (run from `ui/`, as its vitest guard requires), each VERDICT command-exit 0. - `eslint --no-inline-config` over the four changed `.ts` files: exit 0. </details> <details><summary>Ablations (each landed on disk through ablation-replace and restored to the HEAD blob)</summary> - `connector.zod.ts`, the exclusion guard `if (installerClient && entry.auth.clientId !== undefined) {` → `if (false && …) {`: `connector-auth-grant-kinds.test.ts` 1 failed | 28 passed ("refuses a packaged `clientId` beside `clientSource`"). Restored: blob `eebb0a85` == HEAD. - `connector.zod.ts`, the relaxation `if (entry.auth.clientId === undefined && !installerClient) {` → without `&& !installerClient`: 4 failed | 25 passed (the openapi CONTROL accept, the `credentialRef` refusal's single issue, the endpoints-still-required list, the declared-keys pin). Restored: blob == HEAD. - Reverse type verification: a probe in `packages/services/service-automation` typed `ConnectorInstanceAuth` from `@objectstack/spec/integration`: `clientSource: 'installer'` compiles, `clientSource: 'publisher'` is TS2322 (`'"publisher"' is not assignable to type '"installer"'`), so the consumer reads the rebuilt `.d.ts`. Probe deleted; `git status` clean. Ablation and probe readings were taken on `497c4a04`; the merge brought no change to either file. </details> <details><summary>Derived gate families (dispatch-gates)</summary> - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `23acaa6a`: 107 commands, change set 8 paths vs merge base `b15b634c1`, 280 changed lines (+249 / -31). Same list as at `497c4a04`. - All 107 exit 0 on `23acaa6a` (and all 107 on `497c4a04`). On the first run `check-plugin-teardown-shape.mjs --self-test` exited 3: its pinned fixture commit was outside the shallow clone. After `git fetch --depth=1 origin 621a487…` it reported 48 cases pass, exit 0. - `dispatch-gates --ran`: "107 derived famil(ies) accounted for — 107 run, 0 NOT-MEASURED (a DERIVED zero …)". - `check-changeset-no-major.mjs --base origin/main --event` (this body, offline): exit 0, LEVEL AXIS `yes (widening)` + `added`, `@objectstack/spec: minor`. - ADR families: `check:adr-symbol-anchors` (2977 anchors resolve), `check:adr-links`, `check:adr-anchors`, `check:adr-index` (README current): all exit 0. - Local-only families: `check:skill-frame-freshness` exit 0. `check:dev-prereqs` exit 1 is a box precondition (35 `ui/` workspace dists not built in this container; the `packages/**` build is 78/78), not a reading about this diff. - Full dogfood not run: no `!` in the title and no `(narrowing)` arm. </details> ## Human Minimum - [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic. - [x] 2. Every name I added uses the casing for its kind. - [x] 3. No formatter ran; `pnpm lint` passes. - [x] 4. One issue, one branch off an up-to-date `main`, one pull request. - [x] 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. - [x] 6. `packages/spec` touched: its generated artifacts are regenerated and committed. - [x] 7. No hand edits to `content/docs/references/`, `packages/*/CHANGELOG.md` or `content/docs/releases/`. - [x] 8. Title, body and comments are in English. - [x] The *Before Submitting* list in CONTRIBUTING.md is done. --- _Generated by [Claude Code](https://claude.ai/code/session_01ADHkmPS39tCwZRCvghgPQf)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent ee7518f commit d8c611d

9 files changed

Lines changed: 295 additions & 37 deletions

File tree

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
feat(spec): an `oauth2` `authorization_code` connector instance can take its OAuth client from the administrator who installs it — `clientSource: 'installer'`
6+
7+
Clause-②: yes (widening)
8+
Metadata-protocol: added
9+
Ruling-ref: 6566728470
10+
11+
A vendor accepts an OAuth client only at the callback URLs its owner registered, and the platform's callback is fixed per deployment. A `clientId` written into a connector definition is the publisher's client, so an app installed on a deployment its publisher never saw could not connect there. The `authorization_code` instance shape gains one optional key that says the client comes from the installer instead:
12+
13+
```ts
14+
connectors: [{
15+
name: 'esign',
16+
label: 'E-Signature',
17+
type: 'api',
18+
provider: 'openapi',
19+
providerConfig: { spec: 'https://api.vendor.example.com/openapi.json' },
20+
auth: {
21+
type: 'oauth2',
22+
grant: 'authorization_code',
23+
authorizationUrl: 'https://account.vendor.example.com/oauth/authorize',
24+
tokenUrl: 'https://account.vendor.example.com/oauth/token',
25+
scopes: ['signature'],
26+
clientSource: 'installer',
27+
},
28+
}]
29+
```
30+
31+
- **What it means.** The administrator of each deployment registers their own client with the vendor, against that deployment's callback, and enters its id and secret on the instance's connection. The definition names no client and no secret.
32+
- **What it refuses.** A `clientId` beside `clientSource` is refused at publish, located at `auth.clientId`: the definition would name two clients. A `credentialRef` without its `clientId` is refused as before, and its message now names this form. An unknown value is refused at `auth.clientSource`. On `grant: 'client_credentials'` the key is refused with guidance, because that grant keeps no connection to enter a client on.
33+
- **What it does not change.** The endpoints stay declared, or discovered on `provider: 'mcp'` over http: the installer owns the client, not the vendor's authorization server. An instance that names a pre-registered `clientId` without the key parses exactly as before. That form is kept for an app whose author registers the callback of every deployment that installs it, such as an organization's own app.
34+
- **Not yet served.** The automation service does not read the key yet. The connection door that takes the installer's client, and the form that asks for it, land in follow-up changes. Until then an instance that declares it on a non-MCP provider is held degraded, with a reason naming the missing `clientId`, and is never called without a client. On `provider: 'mcp'` over http the service runs the MCP authorization flow as it does for an instance that leaves its client to dynamic registration. The key's reference row carries `[PLANNED — not enforced]` until then, and the liveness ledger's `connector/auth` row records it as `planned`.
35+
36+
ADR-0143 D2.1 carries the amendment (2026-11-02).

‎content/docs/references/integration/connector.mdx‎

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -267,10 +267,11 @@ const result = ConnectorSchema.parse(data);
267267
| **tokenUrl** | `string` | optional | Token endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
268268
| **scopes** | `string[]` | optional | Scopes to request; sent space-separated. |
269269
| **resource** | `string` | optional | Resource indicator (RFC 8707) — the API the token is for, where the authorization server asks for one (some call it the audience). |
270-
| **clientId** | `string` | optional | OAuth2 client id (not a secret). Required (except `provider: 'mcp'` over http, where the provider may register a client dynamically). |
270+
| **clientId** | `string` | optional | OAuth2 client id of a pre-registered client (not a secret). Required, except where the client comes from elsewhere: `provider: 'mcp'` over http may register one dynamically, and with `clientSource: 'installer'` the administrator enters it on the connection, so it is not named here. |
271271
| **grant** | `'authorization_code'` | ✅ | An administrator connects once; the platform refreshes the grant per dispatch. |
272272
| **authorizationUrl** | `string` | optional | Authorization endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
273-
| **credentialRef** | `string` | optional | Secrets-layer reference resolved to the client secret of a pre-registered client (requires `clientId`). Omit for a public client. Never an inline secret. |
273+
| **clientSource** | `Enum<'installer'>` | optional | [PLANNED — not enforced] Where the OAuth client comes from. `'installer'`: the administrator of each deployment registers their own client with the vendor, against that deployment's callback, and enters its id and secret on the instance's connection, so the definition names no `clientId` and no `credentialRef`. Omit it to name a pre-registered `clientId` here, or to let `provider: 'mcp'` over http register a client. Not served yet: the automation service reads it once the server half of the ADR-0143 D2.1 amendment lands. Until then, off `provider: 'mcp'` over http, an instance that declares it is held degraded for a missing `clientId`; on `provider: 'mcp'` over http the provider registers a client dynamically instead of taking the installer's. |
274+
| **credentialRef** | `string` | optional | Secrets-layer reference resolved to the client secret of a pre-registered client (requires `clientId`). Omit for a public client, and with `clientSource: 'installer'`, where the administrator enters the secret on the connection. Never an inline secret. |
274275

275276
### Nested Shape: `Connector.auth[option 5][grant='client_credentials']`
276277

@@ -280,7 +281,7 @@ const result = ConnectorSchema.parse(data);
280281
| **tokenUrl** | `string` | optional | Token endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
281282
| **scopes** | `string[]` | optional | Scopes to request; sent space-separated. |
282283
| **resource** | `string` | optional | Resource indicator (RFC 8707) — the API the token is for, where the authorization server asks for one (some call it the audience). |
283-
| **clientId** | `string` | optional | OAuth2 client id (not a secret). Required (except `provider: 'mcp'` over http, where the provider may register a client dynamically). |
284+
| **clientId** | `string` | optional | OAuth2 client id of a pre-registered client (not a secret). Required (except `provider: 'mcp'` over http, where the provider may register a client dynamically). |
284285
| **grant** | `'client_credentials'` | ✅ | No user step: the client exchanges its own credentials for a token. |
285286
| **credentialRef** | `string` | optional | Secrets-layer reference resolved to the client secret; declared together with `clientId` (a `client_credentials` client is confidential). Never an inline secret. |
286287

@@ -561,10 +562,11 @@ This schema accepts one of the following structures:
561562
| **tokenUrl** | `string` | optional | Token endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
562563
| **scopes** | `string[]` | optional | Scopes to request; sent space-separated. |
563564
| **resource** | `string` | optional | Resource indicator (RFC 8707) — the API the token is for, where the authorization server asks for one (some call it the audience). |
564-
| **clientId** | `string` | optional | OAuth2 client id (not a secret). Required (except `provider: 'mcp'` over http, where the provider may register a client dynamically). |
565+
| **clientId** | `string` | optional | OAuth2 client id of a pre-registered client (not a secret). Required, except where the client comes from elsewhere: `provider: 'mcp'` over http may register one dynamically, and with `clientSource: 'installer'` the administrator enters it on the connection, so it is not named here. |
565566
| **grant** | `'authorization_code'` | ✅ | An administrator connects once; the platform refreshes the grant per dispatch. |
566567
| **authorizationUrl** | `string` | optional | Authorization endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
567-
| **credentialRef** | `string` | optional | Secrets-layer reference resolved to the client secret of a pre-registered client (requires `clientId`). Omit for a public client. Never an inline secret. |
568+
| **clientSource** | `Enum<'installer'>` | optional | [PLANNED — not enforced] Where the OAuth client comes from. `'installer'`: the administrator of each deployment registers their own client with the vendor, against that deployment's callback, and enters its id and secret on the instance's connection, so the definition names no `clientId` and no `credentialRef`. Omit it to name a pre-registered `clientId` here, or to let `provider: 'mcp'` over http register a client. Not served yet: the automation service reads it once the server half of the ADR-0143 D2.1 amendment lands. Until then, off `provider: 'mcp'` over http, an instance that declares it is held degraded for a missing `clientId`; on `provider: 'mcp'` over http the provider registers a client dynamically instead of taking the installer's. |
569+
| **credentialRef** | `string` | optional | Secrets-layer reference resolved to the client secret of a pre-registered client (requires `clientId`). Omit for a public client, and with `clientSource: 'installer'`, where the administrator enters the secret on the connection. Never an inline secret. |
568570

569571
---
570572

@@ -580,7 +582,7 @@ This schema accepts one of the following structures:
580582
| **tokenUrl** | `string` | optional | Token endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
581583
| **scopes** | `string[]` | optional | Scopes to request; sent space-separated. |
582584
| **resource** | `string` | optional | Resource indicator (RFC 8707) — the API the token is for, where the authorization server asks for one (some call it the audience). |
583-
| **clientId** | `string` | optional | OAuth2 client id (not a secret). Required (except `provider: 'mcp'` over http, where the provider may register a client dynamically). |
585+
| **clientId** | `string` | optional | OAuth2 client id of a pre-registered client (not a secret). Required (except `provider: 'mcp'` over http, where the provider may register a client dynamically). |
584586
| **grant** | `'client_credentials'` | ✅ | No user step: the client exchanges its own credentials for a token. |
585587
| **credentialRef** | `string` | optional | Secrets-layer reference resolved to the client secret; declared together with `clientId` (a `client_credentials` client is confidential). Never an inline secret. |
586588

@@ -599,10 +601,11 @@ This schema accepts one of the following structures:
599601
| **tokenUrl** | `string` | optional | Token endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
600602
| **scopes** | `string[]` | optional | Scopes to request; sent space-separated. |
601603
| **resource** | `string` | optional | Resource indicator (RFC 8707) — the API the token is for, where the authorization server asks for one (some call it the audience). |
602-
| **clientId** | `string` | optional | OAuth2 client id (not a secret). Required (except `provider: 'mcp'` over http, where the provider may register a client dynamically). |
604+
| **clientId** | `string` | optional | OAuth2 client id of a pre-registered client (not a secret). Required, except where the client comes from elsewhere: `provider: 'mcp'` over http may register one dynamically, and with `clientSource: 'installer'` the administrator enters it on the connection, so it is not named here. |
603605
| **grant** | `'authorization_code'` | ✅ | An administrator connects once; the platform refreshes the grant per dispatch. |
604606
| **authorizationUrl** | `string` | optional | Authorization endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
605-
| **credentialRef** | `string` | optional | Secrets-layer reference resolved to the client secret of a pre-registered client (requires `clientId`). Omit for a public client. Never an inline secret. |
607+
| **clientSource** | `Enum<'installer'>` | optional | [PLANNED — not enforced] Where the OAuth client comes from. `'installer'`: the administrator of each deployment registers their own client with the vendor, against that deployment's callback, and enters its id and secret on the instance's connection, so the definition names no `clientId` and no `credentialRef`. Omit it to name a pre-registered `clientId` here, or to let `provider: 'mcp'` over http register a client. Not served yet: the automation service reads it once the server half of the ADR-0143 D2.1 amendment lands. Until then, off `provider: 'mcp'` over http, an instance that declares it is held degraded for a missing `clientId`; on `provider: 'mcp'` over http the provider registers a client dynamically instead of taking the installer's. |
608+
| **credentialRef** | `string` | optional | Secrets-layer reference resolved to the client secret of a pre-registered client (requires `clientId`). Omit for a public client, and with `clientSource: 'installer'`, where the administrator enters the secret on the connection. Never an inline secret. |
606609

607610

608611
---
@@ -617,7 +620,7 @@ This schema accepts one of the following structures:
617620
| **tokenUrl** | `string` | optional | Token endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
618621
| **scopes** | `string[]` | optional | Scopes to request; sent space-separated. |
619622
| **resource** | `string` | optional | Resource indicator (RFC 8707) — the API the token is for, where the authorization server asks for one (some call it the audience). |
620-
| **clientId** | `string` | optional | OAuth2 client id (not a secret). Required (except `provider: 'mcp'` over http, where the provider may register a client dynamically). |
623+
| **clientId** | `string` | optional | OAuth2 client id of a pre-registered client (not a secret). Required (except `provider: 'mcp'` over http, where the provider may register a client dynamically). |
621624
| **grant** | `'client_credentials'` | ✅ | No user step: the client exchanges its own credentials for a token. |
622625
| **credentialRef** | `string` | optional | Secrets-layer reference resolved to the client secret; declared together with `clientId` (a `client_credentials` client is confidential). Never an inline secret. |
623626

@@ -870,10 +873,11 @@ Connector type
870873
| **tokenUrl** | `string` | optional | Token endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
871874
| **scopes** | `string[]` | optional | Scopes to request; sent space-separated. |
872875
| **resource** | `string` | optional | Resource indicator (RFC 8707) — the API the token is for, where the authorization server asks for one (some call it the audience). |
873-
| **clientId** | `string` | optional | OAuth2 client id (not a secret). Required (except `provider: 'mcp'` over http, where the provider may register a client dynamically). |
876+
| **clientId** | `string` | optional | OAuth2 client id of a pre-registered client (not a secret). Required, except where the client comes from elsewhere: `provider: 'mcp'` over http may register one dynamically, and with `clientSource: 'installer'` the administrator enters it on the connection, so it is not named here. |
874877
| **grant** | `'authorization_code'` | ✅ | An administrator connects once; the platform refreshes the grant per dispatch. |
875878
| **authorizationUrl** | `string` | optional | Authorization endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
876-
| **credentialRef** | `string` | optional | Secrets-layer reference resolved to the client secret of a pre-registered client (requires `clientId`). Omit for a public client. Never an inline secret. |
879+
| **clientSource** | `Enum<'installer'>` | optional | [PLANNED — not enforced] Where the OAuth client comes from. `'installer'`: the administrator of each deployment registers their own client with the vendor, against that deployment's callback, and enters its id and secret on the instance's connection, so the definition names no `clientId` and no `credentialRef`. Omit it to name a pre-registered `clientId` here, or to let `provider: 'mcp'` over http register a client. Not served yet: the automation service reads it once the server half of the ADR-0143 D2.1 amendment lands. Until then, off `provider: 'mcp'` over http, an instance that declares it is held degraded for a missing `clientId`; on `provider: 'mcp'` over http the provider registers a client dynamically instead of taking the installer's. |
880+
| **credentialRef** | `string` | optional | Secrets-layer reference resolved to the client secret of a pre-registered client (requires `clientId`). Omit for a public client, and with `clientSource: 'installer'`, where the administrator enters the secret on the connection. Never an inline secret. |
877881

878882
### Nested Shape: `DeclarativeConnectorEntry.auth[option 5][grant='client_credentials']`
879883

@@ -883,7 +887,7 @@ Connector type
883887
| **tokenUrl** | `string` | optional | Token endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
884888
| **scopes** | `string[]` | optional | Scopes to request; sent space-separated. |
885889
| **resource** | `string` | optional | Resource indicator (RFC 8707) — the API the token is for, where the authorization server asks for one (some call it the audience). |
886-
| **clientId** | `string` | optional | OAuth2 client id (not a secret). Required (except `provider: 'mcp'` over http, where the provider may register a client dynamically). |
890+
| **clientId** | `string` | optional | OAuth2 client id of a pre-registered client (not a secret). Required (except `provider: 'mcp'` over http, where the provider may register a client dynamically). |
887891
| **grant** | `'client_credentials'` | ✅ | No user step: the client exchanges its own credentials for a token. |
888892
| **credentialRef** | `string` | optional | Secrets-layer reference resolved to the client secret; declared together with `clientId` (a `client_credentials` client is confidential). Never an inline secret. |
889893

0 commit comments

Comments
 (0)