Repository navigation
connectors: outbound OAuth in the open tier — connection-scoped authorization-code / client-credentials / app-credential tokens, refreshed per dispatch, for the openapi, mcp and rest providers #26702
Description
Blocked-by: #26768
Filing-gate category: ③ maintainer-directed task.
Maintainer ruling (verbatim, not translated)
OAuth 放开源层,先起草 ADR 修订
平台基于 better-auth ,怎么会有没有 oauth
创建issue 就行
The first line moves the boundary. The third replaces the ADR draft with this card. The ADR revision is still owed and is listed under "Work" below.
Business reason
Who benefits: app builders whose flows and agents must act in the SaaS their customers already run. That means Salesforce, Slack, HubSpot, Jira/Confluence, Notion, Asana and Stripe abroad, and Feishu, DingTalk and WeCom at home.
Scenario: an AI-authored app declares a connector instance as metadata. An admin clicks "Connect" once. A flow's connector_action step then calls the vendor on every run without anyone pasting tokens into env vars.
Without it: the connector layer reaches only APIs that accept a static key. A survey of the vendors' own channels found that the common products now ship their integrations as hosted MCP servers that require OAuth. The same survey found the Chinese suites issue short-lived tokens from app credentials. Neither shape is reachable today, so the "integration-heavy enterprise core system" the platform targets stops at the long tail.
| Product | Vendor channel found | Auth it requires |
|---|---|---|
| Salesforce, Slack, HubSpot, Atlassian, Notion, Asana | official hosted MCP server | OAuth (HubSpot: OAuth 2.1 + PKCE) |
| Stripe | stripe/openapi (MIT, OpenAPI 3) + hosted MCP |
static key for the spec path; OAuth for MCP |
| Feishu | official lark-openapi-mcp (MIT, beta) |
App ID/Secret → tenant_access_token (~2 h) |
| DingTalk | official open-dingtalk/dingtalk-mcp + MCP square |
AppKey/Secret → access token |
The bulk-catalog alternatives were measured and rejected for this goal; the numbers are in the session analysis. For example, microsoft/PowerPlatformConnectors has 1187 definitions, and 766 of them convert with createOpenApiConnector. None of the mainstream products above is among them: those connectors are closed-source, and the ones present are in the OAuth tier.
Current state (read at 71a4b4ea)
Inbound OAuth exists; outbound connector OAuth does not. better-auth already gives the platform three things:
- sign-in OAuth (
socialProviders,packages/plugins/plugin-auth/src/auth-plugin.ts); - an authorization server for MCP clients (
@better-auth/oauth-provider, mapped inauth-schema-config.ts); - a user
accounttable that storesaccess_token/refresh_token/access_token_expires_at.
Nothing on the connector side uses any of it:
ConnectorInstanceAuthSchema(packages/spec/src/shared/connector-auth.zod.ts) isnone | bearer | api-key | basic. Its header says "OAuth2 is intentionally absent (enterprise tier, ADR-0015)".ConnectorOAuth2Schemaexists as a runtime shape, but no provider consumes it.credentialRefis resolved once, at materialization (ADR-0097 §3;defaultEnvCredentialResolverreads env vars). A token that expires has no refresh point.- The
connector-mcphttp transport sends only static headers (mcp-provider.ts,applyAuthToHeaders). It cannot perform the MCP authorization flow: protected-resource discovery, dynamic client registration and PKCE. connector-openapimaps only static schemes. Anoauth2securitySchemesentry is dropped.
Work (each item is a slice; the ADR comes first)
- Governance. Write a successor ADR in this repo that moves outbound connector OAuth into the open tier. Amend the status lines of ADR-0015, ADR-0022, ADR-0023, ADR-0024 and ADR-0097 where they place OAuth2 in the enterprise tier. Per AGENTS.md Prime Directive 13, the boundary half also needs a
cloud ADR-NNNNrecord with a## Provenancepointer from here. Both are Tier H governed surfaces, so the maintainer lands them. - Connection record. Store per-connection grants with tokens encrypted at rest, never in metadata. Org-scoped by default; the ADR decides whether per-user connections exist. Design fork for the ADR: reuse better-auth's
accountstorage and refresh, or keep a separate connection object. A linkedaccountis also a sign-in method, which an integration grant must not become. - Grant types.
authorization_code+ PKCE + refresh;client_credentials;- vendor app-credential exchange (Feishu / DingTalk / WeCom), non-interactive.
- Resolve per dispatch, not at boot. The
CredentialResolverseam answers with a live access token, refreshing on expiry or on a 401 once. This is a change to the ADR-0097 contract. - Authoring contract (
packages/spec).ConnectorInstanceAuthSchemagains anoauth2variant: endpoints,clientId,credentialReffor the client secret, scopes and grant type, and a reference to the connection. Inline tokens or secrets are refused at publish, as today. - Providers.
mcp: the MCP authorization flow on the http transport.openapi: mapoauth2securitySchemes.rest: accept the oauth2 variant.
- Console. Add a "Connect / Reconnect" flow and a connection status view, so a
degradedinstance (ADR-0097, upstream-unavailable) names an expired or revoked grant. This is aui/follow-up.
Decisions the successor ADR must put to the maintainer
- Connection ownership and execution identity. Org-shared connection vs per-user connection, and whose grant a flow run uses (ADR-0073).
- Storage. Reuse better-auth
accountstorage, or a dedicated connection object. - First products. Which products the first acceptance targets.
Acceptance (to be pinned by the ADR)
- A declarative
provider: 'mcp'instance pointing at an OAuth-protected vendor MCP server is authorized once from the Console. A flowconnector_actiondispatches end to end. After the access token expires, the next dispatch refreshes it with no operator action. - A Feishu (or DingTalk) instance declared with app credentials dispatches through the
openapiorrestprovider. Its token renews without restart. - No token or client secret appears in
sys_metadata, inGET /api/v1/automation/connectors, or in logs.
Out of scope
- Curating which actions each product exposes. That is a separate catalog card once auth lands.
- Bulk import of third-party catalogs.
Who acts
- The
domain:skillsseat drafts the successor ADR, which the maintainer lands (Tier H). - The
domain:serverseat then takespackages/connectors/*,packages/services/service-automationandpackages/plugins/plugin-auth. Thepackages/specauth variant is aSeam:for thedomain:specseat. The Console flow goes todomain:ui.
Triage assigns the final lanes.
Duplicate check
Semantic issue search on objectstack-ai/objectstack, open and closed:
| Query | Hits | Relevant |
|---|---|---|
| "connector OAuth2 authorization code refresh token connection credential" | 0 | — |
| "credentialRef vault CredentialResolver token exchange client credentials Feishu DingTalk" | 0 | — |
| "connector oauth" | 4 | none — all inbound MCP-server OAuth (25118, 24166, 24137, 21885) |
| "MCP connector remote server OAuth client authentication" | 12 (10 returned and read) | none — all inbound: our MCP server and better-auth as authorization server, e.g. 20791, 25118, 24137 |
No card covers outbound connector OAuth.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
enhancement·security·priority:p1·domain:server·area:api·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T14:12Z⛔ Not a claim, ⛔ not a dispatch.
security: outbound OAuth in the open tier (connection-scoped tokens, refreshed per dispatch) moves the open/enterprise boundary, which the maintainer ruled (「OAuth 放开源层」). New connector auth kinds are protocol additions. Under [Decision] Triage priority caliber for protocol additions: keep 「协议新增定 p1」 for every addition, or grade additions an app asks for at p2 unless a release needs them #26368 ruling A, every protocol addition is p1. Handled directly in 18.x. Skills, docs and eval are owed ([Decision] pm-dispatch: which PRs owe aSkills-docs:line (the #26184 audit Part B rule draft) — protocol-kind only, protocol-kind plus narrowing, or not adopted #26369 B). The ADR revision is owed first and is Tier H. Token storage stays encrypted at rest, and refresh failures are loud. ⛔ No token in logs or errors (skills: five principle gaps the #9296 wave exposed in checklist-test / checklist-author / dogfood-verification #9387 applies to the PR text).Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
pm:queue→pm:blocked·domain:serverseat 3 ·session_01QSeKYmQXM3rHJcPnrEPk4B· 2026-10-27T15:09ZBlocked-by: #26768
docs/adr/**, Tier H) thedomain:skillsseat drafts and the maintainer lands. Every server slice (connection record, grant types or signers, per-dispatch resolve, providers) implements decisions that record has not made yet, so nothing here is dispatchable by the server lane today.carries item 1 of #26702 and item 1 of #26710), so one record decides OAuth2, the app-credential exchange and request signing together, as connectors: request-signing auth kind for the openapi and rest providers (RSA2 / HMAC canonical-request signers: Alipay, WeChat Pay, Aliyun, Tencent Cloud, e签宝) — the vendor-auth half that #26702 does not cover #26710 asked. This card keeps its other items unchanged; its grade and labels stay.pm:queue; re-read its premises against the merged record before claiming.objectstack-fleet commentedon Oct 28, 2026
Part of #27293 (E1 platform prerequisites) · 2026-10-28T14:59Z
Epic link on the maintainer's ruling 「是否建议立专题卡统一处理。」→「同意」 (2026-10-28,
session_01J8VVAjRKqdoEYQ4hXxtBuf). Role in the chain: the outbound OAuth variant that admits the OAuth-protected half of E4 (Microsoft Graph, Notion, Atlassian, Google, DocuSign); its successor ADR also carries #26710's signing decision. Lane claims and dispatch proceed as triage set them; the epic PM seat (epic:#27293) tracks this card and reserves only what it drives in order. ⛔ Not a claim.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
Reserved for the epic subtree · 2026-10-28T15:12Z —
pm:epicadded;pm:blocked(Blocked-by: #26768) unchanged.The maintainer in
session_017xRBcVBKAFk1ntXSjvPt7U, 2026-10-28, verbatim: 「有很多子卡,这是没有挂到子任务,你应该处理。」 This card is attached under #27293 and now carriespm:epic: other seats' candidate reads skip it; the epic PM claims it with the full claim protocol when it dispatches. ⛔ Not a claim, ⛔ not a dispatch.objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 可对外的 API | none | P②
Triage: unblocked · p1 kept ·
domain:server· 2026-10-29T04:01Z⛔ Not a claim.
main(PR docs(adr): ADR-0143 accepted — the maintainer's Q1–Q4 decisions recorded; D2.1, D2.3, D3, D4 and D7 state what they settle #27231,d745bb5e, Q1–Q4 answered).## Provenancepointer (cloud#3350, PR cloud#3354, waiting on review). That record restates ADR-0143 for the cloud tree, and nothing in this card's open-tier code waits on it.pm:blocked→pm:queue, with p1 kept. The PR cites ADR-0143.connector-tiering.md), triage puts this card back on hold.Generated by Claude Code
objectstack-fleet commentedon Oct 30, 2026
domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia
Retriage ask (label hygiene), 2026-10-30T04:32Z: this card carries both
pm:epicandpm:queue. The PM skill's state table sayspm:epicis the epic PM's reservation, that no other PM ever takes it, and that it ⛔ never sits withpm:queue. Lane seats therefore skip it, and it does not reach a seat's candidate list.Ask: pick one state. Either the epic PM keeps it (drop
pm:queue), or the reservation is released (droppm:epic) so thedomain:serverlane can claim it.pm:retriageadded; no other label touched.objectstack-fleet commentedon Oct 30, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: label hygiene ·
pm:epicreservation released · 2026-10-30T04:59Z⛔ Not a claim.
pm:epicandpm:queuenever sit together. Its epic PM (parent [Epic] Marketplace and connector auto-shelf — E1 platform prerequisites (openapi/mcp hardening, outbound OAuth, request signing), E2 administrator credential hand-off, E3 author recipes and handbook, E4 the first shelf of official connector apps #27293) has had no own output on this card for more than a day (last at 2026-10-28 15:16Z), so under seat-lifecycle the reservation is released and the card goes to the lane. But it is not claimable: its body carriesBlocked-by: #26768, and that card (cloud, ADR) is open andpm:blocked. Sopm:epicandpm:queueare dropped andpm:blockedis set.pm:retriageremoved.Generated by Claude Code