Skip to content

Commit ec0316e

Browse files
feat(automation): an oauth2 connector with clientSource: 'installer' connects with the administrator's OAuth client (#29097)
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt <!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XKUuqUUMXTeftRSDmw2eTo8)_ Fixes #28866 Clause-②: yes (narrowing) Metadata-protocol: none Skills-docs: `content/docs/automation/connectors.mdx` teaches `clientSource: 'installer'` in this PR (the publish-door bullet and the connect body). `skills/objectstack-automation/references/integration-recipes.md` (governed Tier H) is not edited here. Follow-up owed — #29255 Ruling-ref: 6566728470 ## 维护者速读 - 改了什么:声明 `clientSource: 'installer'` 的 OAuth 连接器,管理员在「连接」时提交自己在厂商处登记的 client id 和 secret;授权、换取令牌、每次刷新都用这个客户端。secret 只加密存在连接记录里,读接口只显示「已设置客户端、id、有无 secret」。 - 为什么改:#28702 裁决 B 要求开源版 OAuth 应用不带官方客户端、由每个部署的管理员自带;规格半边 #28920 已合并,这是服务端半边。 - 风险与回滚:声明了自己 `clientId` 或交给 MCP 动态注册的实例行为不变。新增三种拒绝(400):安装方实例连接时没给客户端;给其他实例传了客户端;连接请求体带了别的键(以前都被静默忽略)。回滚即撤掉本 PR。 - 先看:`packages/services/service-automation/src/plugin.ts` 的 `installerClientOfBegin` 和 `completeConnectorConnection`;`packages/runtime/src/dispatcher-plugin.ts` 连接门挂载点(这轮修的真缺陷:以前 HTTP 请求体根本没传进门);`oauth2-grant/grant-handle.ts` 的 `endpointOf`。 - 两分钟验证:`pnpm --filter @objectstack/dogfood exec vitest run test/connector-installer-oauth-client.dogfood.test.ts`,应看到 4 条全过(真实 HTTP 门上:无客户端被拒、多余键被拒、用管理员客户端完成连接并撤销)。 - 需要你做的:无,席位落地。 ## What changes - **Contracts** (`packages/spec/src/contracts/automation-service.ts`, TS only): `ConnectorConnectionRequest.client?: { clientId; clientSecret? }`, read by `beginConnectorConnection` only; `ConnectorConnectionState.client?: { source: 'installer'; clientId; hasSecret }`, never the secret. Inline member types, so no export moves (`check:api-surface` exit 0). - **The door** (`packages/runtime/src/domains/automation.ts`): the begin leg (`POST`) reads a closed body set, `clientId` and `clientSecret`, and hands it to the service as `client`. No body, or `{}`, begins with no client as before. An unknown key, a non-object body, or a secret without its id is refused 400 `VALIDATION_ERROR` naming keys only. The read and revoke legs still read no body. - **The HTTP mount** (`packages/runtime/src/dispatcher-plugin.ts`, new in this round): the route mounted for `/automation/connectors/:name/connection` dispatched every verb with no body, so over HTTP the begin leg never saw the installer's client and such an instance could not be connected at all. The `POST` leg now defers its body (`deferRequestBody`), which the domain reads once the floor has admitted the caller, as every other body-bearing automation route does. Found by the new dogfood case (its first run was red on exactly this), invisible to the domain-level tests, which call `handleAutomation` with a body directly. - **The service** (`packages/services/service-automation`): - `installerClientOfBegin`: an installer instance begun without a client is refused 400 naming the instance, the vendor registration step and the redirect URI to register; a client offered to any other instance is refused 400 rather than ignored (the spec has no override form: #28920 refuses `clientId` beside `clientSource`); a malformed client is refused naming the key. The read and revoke members refuse a `client`. - The client rides the begun connect's sealed pending authorization (`installerClient`), the code is exchanged as it, and the connection keeps it in `registered_client` (`Field.secret`, the store and channel the refresh token uses), as `{ source: 'installer', clientId, clientSecret? }` plus the discovered endpoints on MCP over http. Two new plain columns on `sys_connector_connection`: `client_id` and `client_has_secret`, which the read answers from without decrypting. - `grant-handle.ts#endpointOf` reads the kept client through `parseKeptConnectionClient` (new `oauth2-grant/installer-client.ts`) and uses a kept client only when its kind matches the declaration: an installer's client under a declaration that no longer takes one, or the reverse, is "reconnect required", never used. - Servability: `oauth2NotServableReason` no longer lists `clientId` as missing for the installer form; `mcpDiscoveryServerUrl` treats it as a supplied client, so MCP discovery finds the endpoints and registers nothing. - A complete whose declaration changed between installer and declared client while the browser was away is refused, and its code is not used. - Revoke is the existing row delete; the client goes with the row. - **Spec lane's landing note** (card comment 6570978729): `clientSource` loses `[PLANNED — not enforced]`, the `connector/auth` liveness row loses its planned sentence, the PLANNED pin test is flipped, and `content/docs/references/integration/connector.mdx` is regenerated. - **Route ledger** (`packages/runtime/src/route-ledger.ts`): the `POST /automation/connectors/:name/connection` row no longer says "No body". It names the closed body set `{ clientId, clientSecret? }`, says that no body or `{}` begins with no client, and lists the three 400 `VALIDATION_ERROR` refusals (another body key, an installer instance begun with no client, a client offered to an instance that declares its own or leaves it to dynamic registration). The `GET` row names the `client` the read answers. This was should-fix 1 of the contract review. - **Docs**: `content/docs/automation/connectors.mdx` teaches the installer form and the connect body. - **Changeset**: the ADR-0087 disposition `not-required (no-migration-prescription)` and a **BREAKING** paragraph naming the three newly refused call shapes and what to do. ## Tests - `packages/qa/dogfood/test/connector-installer-oauth-client.dogfood.test.ts` (new, 4 tests, showcase boot, real HTTP door, a loopback token endpoint): served "not connected", never "missing `clientId`"; ⛔ a begin with no client is refused 400 and stores nothing; ⛔ a begin body with another key is refused 400 naming it, never echoing the secret; connected end to end as the administrator's client (authorize URL `client_id`, Basic on the code exchange, the read's `client`, the plain `client_id` column, no secret in any answer or row), then revoked. - `packages/services/service-automation/src/connector-connection-door.test.ts`, new describe (10 tests) over a real ObjectQL on SQLite and a local token endpoint: connect, dispatch, refresh on a 401 and revoke as the installer's client (Basic for a confidential one, `client_id` in the form for a public one); per-organization clients and reconnect replacement; the missing-client refusal; malformed clients; CONTROL: a packaged instance refuses an offered client and connects as before without one; read and revoke refuse a client; a declaration that changed while away; the secret absent from logs, descriptors, degraded reason, door answers and engine reads. - `oauth2-grant/grant-handle.test.ts`, new describe (4 tests): refresh as the installer's client at the declared endpoint and at a kept discovered one; per-organization clients; a kept client of the other kind is never used. - `packages/runtime/src/domains/automation-connector-connection-door.test.ts`, new describe (4 tests): the begin body is handed over as `client`; no body or `{}` is unchanged; refusals name keys only; the read and revoke legs read no body. - **Forced test edits**: `packages/spec/src/integration/connector-auth-grant-kinds.test.ts`, the PLANNED pin flipped to "served" (the spec lane's landing note asks for it); `oauth2-grant/grant-handle.test.ts`'s `fakeConnections` double gains `installerClientHasSecret: false`, the new required member of `ConnectorConnectionRecord`. ## Acceptance notes - **Reads checked against the redaction on main.** `sys_connector_connection` has `apiEnabled: false` and `apiMethods: []`, so the connection door is the only read, and it answers `ConnectorConnectionState` from plain columns. `collectMaskedReadFields` still answers exactly `refresh_token` and `registered_client` (pinned). - **The narrowing.** Before, the begin leg ignored any body, so a client offered to a packaged instance, or any body key, was silently dropped. Both are now refused 400, as is a `client` on the in-process read and revoke members. Nothing else that was accepted is refused. - **The Console cannot connect an installer instance yet.** Setup → Integrations → Connector Connections begins a connect with no body, so for a `clientSource: 'installer'` instance it now gets the 400 that names the registration step. The server half and the API are complete; a Console form for the client id and secret is ui-lane work, already carried by #28869. - **New public key names** (`client`, `clientId`, `clientSecret`, `hasSecret`, `source` on the two contract members and the connect body): maintainer approval is owed before landing; the seat asks. ## Verification Final head `2ca22f19`. It differs from `59630d94` only by the two route-ledger notes (string literals in one file); the readings below were taken on `59630d94` unless a line names another, and the new head's readings are in the os-dev-report on #28866. Heavy runs went through `os-verify-lock.sh`. <details><summary>Red first on main, then green</summary> - Implementation files (`plugin.ts`, `connector-connections.ts`, `sys-connector-connection.object.ts`, `oauth2-grant/{authorization,grant-handle,index}.ts`, runtime `domains/automation.ts`, spec `connector-auth.zod.ts`, `liveness/connector.json`) restored to `origin/main` fea2dbb blobs, tests at HEAD 52e2396: service-automation `Tests 13 failed | 47 passed (60)`, runtime `Tests 2 failed | 33 passed (35)`, spec `Tests 1 failed | 29 passed (30)` — every new test red, every pre-existing one green. Restored: every blob == HEAD, `git diff HEAD` empty, status clean. - The new dogfood case on a12d046 (before the mount fix): `Tests 2 failed | 2 passed (4)` — the unknown-key and the connected cases, the door seeing no body. On 59630d9: `Tests 4 passed (4)`. </details> <details><summary>Ablation of every negative pin (each red, each restored blob == HEAD, `git diff HEAD` empty)</summary> Through `scripts/ablation-replace.mjs`; the unit suites import their subject from `src/`. - A1 installer begin with no client proceeds (`if (offered === undefined) { return undefined;`): `× a connect without a client is refused 400…` — Tests 1 failed | 29 passed (30). - A2 a client offered to a non-installer instance is ignored: `× CONTROL: a client offered to an instance that declares its own is refused 400, not ignored…` — 1 failed. - A3 unknown begin body keys pass (runtime, `if (false) {`): `× refuses an unknown key, a non-object body and a secret without its id…` — 1 failed | 34 passed (35). - A4 a client on read/revoke is accepted: `× the read and revoke legs refuse a client…` — 1 failed. - A5 a kept client of the other kind is used (grant handle): `× a kept client of the other kind than the declaration takes is never used…` — 1 failed | 29 passed (30). - A6 a declaration that changed during the callback is not checked: `× a declaration that changed where its client comes from…` — 1 failed. - Dogfood, through `dist/` (rebuild + `ablation-dist-preflight.mjs` marker present; restore rebuild + `--absent`, tree clean): D1 the mount drops the body again → 2 failed | 2 passed (4); D2 = A1 in service-automation → `× a begin with no client is refused 400 VALIDATION_ERROR…` 1 failed | 3 passed (4). </details> <details><summary>Package suites and typecheck</summary> - `@objectstack/service-automation` test (52e2396): Test Files 257 passed (257), Tests 3323 passed (3323); typecheck (tsc + `check:test-typecheck`) VERDICT 0. - `@objectstack/runtime` test (59630d9): Test Files 514 passed (514), Tests 8322 passed | 19 skipped (8341); typecheck VERDICT 0. - `@objectstack/spec` test (52e2396): Test Files 790 passed (790), Tests 23357 passed | 1 todo; typecheck VERDICT 0; `check:generated` VERDICT 0 (`check:authorable-surface` exit 0 in the derived set). Spec source is unchanged after 52e2396. - `@objectstack/dogfood` typecheck (59630d9) VERDICT 0. `eslint --no-inline-config` over the touched runtime, service-automation and spec sources and the new dogfood file: exit 0. </details> <details><summary>Derived gates and dogfood battery (59630d9)</summary> - `dispatch-gates --commands`: 109 families; all 109 exit 0 (four first returned exit 3 PREREQUISITE NOT MET and were re-run after `git fetch --unshallow` and the missing builds: `check-plugin-teardown-shape --self-test`, `check:skill-examples`, `check:dual-build-cjs-loads`, `check:type-check-debt`). `--ran`: 109 derived, 109 run, 0 NOT-MEASURED, 0 UNRUN. `check-adr-0087-registration --base origin/main`: `[BREAKING+clause-②-narrowing] not-required (no-migration-prescription)`, exit 0. - Full dogfood battery, `pnpm --filter @objectstack/dogfood test` under the lock: Test Files 387 passed | 3 skipped (390), Tests 3199 passed | 28 skipped (3227), VERDICT command-exit 0. - CI on 59630d9: nine required contexts success (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard, Type Check UI, Test UI, Build & E2E UI, Live E2E UI) and Lint UI skipped by its path selector; Check Changeset and Dogfood Regression Gate success; no check failed. </details> --- _Generated by [Claude Code](https://claude.ai/code/session_01ADHkmSB1MQ9Npyv1kGL3DV)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 5f7d554 commit ec0316e

21 files changed

Lines changed: 1057 additions & 72 deletions
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/service-automation': patch
4+
'@objectstack/runtime': patch
5+
---
6+
7+
feat(automation): an `oauth2` connector instance with `clientSource: 'installer'` connects with the OAuth client its administrator entered, and uses it for every exchange and refresh
8+
9+
Clause-②: yes (narrowing)
10+
Metadata-protocol: none
11+
Ruling-ref: 6566728470
12+
13+
<!-- adr-0087: not-required (no-migration-prescription) No metadata key, schema, accepted value, stored row, export or error code is removed or renamed: the begin leg of the connector connection door now refuses, with the existing 400 VALIDATION_ERROR, a request body it used to drop unread, so nothing stored changes and there is nothing for objectstack migrate meta to rewrite, and the two contract types only gain OPTIONAL members no existing caller has to send. -->
14+
15+
**BREAKING** for three call shapes only, each of which used to be answered as if its extra input were absent and now answers `400 VALIDATION_ERROR` with nothing connected, read or revoked: a `POST /api/v1/automation/connectors/NAME/connection` whose JSON body carries a key other than `clientId` and `clientSecret`; the same begin carrying a client for an instance that does not declare `clientSource: 'installer'`; and an in-process `getConnectorConnection` or `revokeConnectorConnection` call whose request carries `client`. What to do: nothing, unless a caller sends a body to that begin leg. Send none (or `{}`) for an instance that declares its client or leaves it to MCP dynamic registration, and send only `clientId` and `clientSecret` for one that takes the installer's.
16+
17+
An `authorization_code` instance that declares `clientSource: 'installer'` names no client in its definition. Its administrator registers a client with the vendor against this deployment's redirect URI and hands it to the connect door:
18+
19+
```http
20+
POST /api/v1/automation/connectors/esign/connection
21+
{ "clientId": "CLIENT_ID", "clientSecret": "CLIENT_SECRET" }
22+
```
23+
24+
- **What it does.** The authorization request, the code exchange and every refresh of that connection go out as the administrator's client. Each organization's connection keeps its own client, and a reconnect replaces it. On `provider: 'mcp'` over http the MCP authorization flow still discovers the endpoints, and registers nothing.
25+
- **Where the client is kept.** In the connection record's encrypted client column, the one that keeps a dynamically registered client. The id alone is also kept in plain text, so the connection's read shows `client: { source: 'installer', clientId, hasSecret }`. The secret is never answered back, logged or written to metadata.
26+
- **What it refuses (400 `VALIDATION_ERROR`).** A connect of such an instance with no client: the message names the instance and the vendor registration step, with the redirect URI to register. A client offered to an instance that declares its own client or leaves it to dynamic registration, which used to be ignored. A body key other than `clientId` and `clientSecret` on the begin leg, which used to be ignored too. A client on the read or revoke leg. No refusal repeats a value the caller sent.
27+
- **Contracts.** `ConnectorConnectionRequest` gains an optional `client`, and `ConnectorConnectionState` an optional `client`.
28+
- **Unchanged.** An instance that declares its `clientId`, or leaves its client to MCP dynamic registration, connects, exchanges and refreshes exactly as before, and a begin with no body is still a begin with no client.
29+
30+
The `clientSource` key no longer carries `[PLANNED — not enforced]`, and the liveness ledger's `connector/auth` row no longer calls it planned.

‎content/docs/automation/connectors.mdx‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,11 @@ What the publish door checks, each refusal located at its key under `auth`:
558558
`authorization_code`. The one exception is `provider: 'mcp'` over an `http`
559559
transport, which may omit the endpoints and the client id: the MCP
560560
authorization flow discovers the server's metadata and can register the
561-
client. `client_credentials` has no authorization step, so
561+
client. An `authorization_code` instance may instead declare
562+
`clientSource: 'installer'` and name no `clientId` and no `credentialRef`:
563+
the administrator of each deployment registers their own client with the
564+
vendor and enters it when they connect (below); a `clientId` beside it is
565+
refused. `client_credentials` has no authorization step, so
562566
`authorizationUrl` is refused there, and it requires `credentialRef`.
563567
A `credentialRef` without its `clientId` is refused. An inline
564568
`clientSecret`, `refreshToken` or `accessToken` is refused by name, pointing
@@ -640,6 +644,17 @@ a rotating refresh token is never spent twice.
640644
DELETE /api/v1/automation/connectors/:name/connection → revoke
641645
```
642646

647+
An instance declaring `clientSource: 'installer'` takes its client in the
648+
connect's body, `{ "clientId": "…", "clientSecret": "…" }` (omit
649+
`clientSecret` for a public client). Register that client with the vendor
650+
first, with the redirect URI `<origin>/api/v1/automation/connectors/oauth/callback`.
651+
The authorization request, the code exchange and every refresh of that
652+
organization's connection then go out as that client, and a reconnect may
653+
bring a new one. The connection keeps it encrypted; the status read shows
654+
`client: { source: 'installer', clientId, hasSecret }`, never the secret. A
655+
connect of such an instance without a client, a client offered to any other
656+
instance, and any other body key are refused `400 VALIDATION_ERROR`.
657+
643658
**In the Console**, **Setup → Integrations → Connector Connections** drives
644659
these calls; the entry is shown to holders of `manage_connector_connections`.
645660
It lists each `authorization_code` instance with the organization's status,

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ const result = ConnectorSchema.parse(data);
270270
| **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-
| **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. |
273+
| **clientSource** | `Enum<'installer'>` | optional | 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. The connection keeps the client encrypted, and every authorization, code exchange and refresh of that connection uses it (ADR-0143 D2.1). |
274274
| **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. |
275275

276276
### Nested Shape: `Connector.auth[option 5][grant='client_credentials']`
@@ -565,7 +565,7 @@ This schema accepts one of the following structures:
565565
| **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. |
566566
| **grant** | `'authorization_code'` | ✅ | An administrator connects once; the platform refreshes the grant per dispatch. |
567567
| **authorizationUrl** | `string` | optional | Authorization endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
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. |
568+
| **clientSource** | `Enum<'installer'>` | optional | 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. The connection keeps the client encrypted, and every authorization, code exchange and refresh of that connection uses it (ADR-0143 D2.1). |
569569
| **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. |
570570

571571
---
@@ -604,7 +604,7 @@ This schema accepts one of the following structures:
604604
| **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. |
605605
| **grant** | `'authorization_code'` | ✅ | An administrator connects once; the platform refreshes the grant per dispatch. |
606606
| **authorizationUrl** | `string` | optional | Authorization endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
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. |
607+
| **clientSource** | `Enum<'installer'>` | optional | 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. The connection keeps the client encrypted, and every authorization, code exchange and refresh of that connection uses it (ADR-0143 D2.1). |
608608
| **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. |
609609

610610

@@ -876,7 +876,7 @@ Connector type
876876
| **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. |
877877
| **grant** | `'authorization_code'` | ✅ | An administrator connects once; the platform refreshes the grant per dispatch. |
878878
| **authorizationUrl** | `string` | optional | Authorization endpoint. Required (except `provider: 'mcp'` over http, which discovers it). |
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. |
879+
| **clientSource** | `Enum<'installer'>` | optional | 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. The connection keeps the client encrypted, and every authorization, code exchange and refresh of that connection uses it (ADR-0143 D2.1). |
880880
| **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. |
881881

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

0 commit comments

Comments
 (0)