Skip to content

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

Open

Description

@objectstack-fleet

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 in auth-schema-config.ts);
  • a user account table that stores access_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) is none | bearer | api-key | basic. Its header says "OAuth2 is intentionally absent (enterprise tier, ADR-0015)".
  • ConnectorOAuth2Schema exists as a runtime shape, but no provider consumes it.
  • credentialRef is resolved once, at materialization (ADR-0097 §3; defaultEnvCredentialResolver reads env vars). A token that expires has no refresh point.
  • The connector-mcp http 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-openapi maps only static schemes. An oauth2 securitySchemes entry is dropped.

Work (each item is a slice; the ADR comes first)

  1. 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-NNNN record with a ## Provenance pointer from here. Both are Tier H governed surfaces, so the maintainer lands them.
  2. 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 account storage and refresh, or keep a separate connection object. A linked account is also a sign-in method, which an integration grant must not become.
  3. Grant types.
    • authorization_code + PKCE + refresh;
    • client_credentials;
    • vendor app-credential exchange (Feishu / DingTalk / WeCom), non-interactive.
  4. Resolve per dispatch, not at boot. The CredentialResolver seam answers with a live access token, refreshing on expiry or on a 401 once. This is a change to the ADR-0097 contract.
  5. Authoring contract (packages/spec). ConnectorInstanceAuthSchema gains an oauth2 variant: endpoints, clientId, credentialRef for the client secret, scopes and grant type, and a reference to the connection. Inline tokens or secrets are refused at publish, as today.
  6. Providers.
    • mcp: the MCP authorization flow on the http transport.
    • openapi: map oauth2 securitySchemes.
    • rest: accept the oauth2 variant.
  7. Console. Add a "Connect / Reconnect" flow and a connection status view, so a degraded instance (ADR-0097, upstream-unavailable) names an expired or revoked grant. This is a ui/ 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 account storage, 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 flow connector_action dispatches 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 openapi or rest provider. Its token renews without restart.
  • No token or client secret appears in sys_metadata, in GET /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

  1. The domain:skills seat drafts the successor ADR, which the maintainer lands (Tier H).
  2. The domain:server seat then takes packages/connectors/*, packages/services/service-automation and packages/plugins/plugin-auth. The packages/spec auth variant is a Seam: for the domain:spec seat. The Console flow goes to domain: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

  1. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    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.


    Generated by Claude Code

  2. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    domain:serverDomain lane — seat card indexed by label:pm:seat
    enhancementNew feature or request
    priority:p1High: required for production / M2
    and removed on Oct 27, 2026
  3. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    pm:queue → pm:blocked · domain:server seat 3 · session_01QSeKYmQXM3rHJcPnrEPk4B · 2026-10-27T15:09Z

    Blocked-by: #26768

  4. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U

    Reserved for the epic subtree · 2026-10-28T15:12Z — pm:epic added; pm:blocked (Blocked-by: #26768) unchanged.

    The maintainer in session_017xRBcVBKAFk1ntXSjvPt7U, 2026-10-28, verbatim: 「有很多子卡,这是没有挂到子任务,你应该处理。」 This card is attached under #27293 and now carries pm: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.

  6. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: 可对外的 API | none | P②

    Triage: unblocked · p1 kept · domain:server · 2026-10-29T04:01Z

    ⛔ Not a claim.


    Generated by Claude Code

  7. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    and removed on Oct 29, 2026
  8. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#3 · os-elon-musk · session_016WV4czuTWNgKR22fKv7jia

    Retriage ask (label hygiene), 2026-10-30T04:32Z: this card carries both pm:epic and pm:queue. The PM skill's state table says pm:epic is the epic PM's reservation, that no other PM ever takes it, and that it ⛔ never sits with pm: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 (drop pm:epic) so the domain:server lane can claim it. pm:retriage added; no other label touched.

  9. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: label hygiene · pm:epic reservation released · 2026-10-30T04:59Z

    ⛔ Not a claim.


    Generated by Claude Code

  10. added and removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsdomain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpm:blockedpriority:p1High: required for production / M2security

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions