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
Unblocked (triage, 2026-10-30): ADR-0143 is Accepted on main (PR #27231); #26768's only open item is the cloud mirror record (objectstack-ai/cloud#3350), which gates no code in this repo.
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
29 remaining items
objectstack-fleet commentedon Oct 31, 2026
domain:server#1 · os-tesla · session_01L9qibwa6RkD7A2gK6rYRNT
os-dev-report
{ "issue": 26702, "status": "needs_decision", "branch": "claude/project-thread-8z6igj", "pr": "https://github.com/objectstack-ai/objectstack/pull/28399", "session": "session_01L9qibwa6RkD7A2gK6rYRNT", "slice": "S2 (providers), first half by provider: rest + openapi + the D2.2 app-credential transport. The mcp half is a second PR on the same branch after this one lands.", "premise_still_valid": true, "summary": "The rest and openapi plugins register with { credentialHandle: true }; with a handle every request goes through callWithBorrowedCredential for the run's organization (401 refresh once + retry once, second 401 = 503), transport errors are scrubbed of the credential, and a handle beside a static credential or signer is refused. openapi reads security: an operation with security [] is anonymous, and a required apiKey scheme's parameter is platform-filled and leaves the input schema. service-automation gains oauth2-grant/app-credential.ts: Feishu, DingTalk (v1.0) and WeCom exchanges per vendor docs, a memory-only handle with skew margin and single flight, no redirect, refusals naming only the vendor code; WeCom's in-body early invalidation (40014/42001) refreshes once and repeats once. The materializer serves app-credential on opted-in providers and holds it degraded otherwise. needs_decision is for the mcp discovery-organization point only; PR 28399 itself is complete for review.", "tests": "connector-rest vitest: 5 files, 43 passed (new rest-provider-credential-handle.test.ts 10, incl. WeCom end to end through AutomationServicePlugin + ConnectorRestPlugin). connector-openapi: 7 files, 89 passed (new 7). service-automation full suite 243 files / 3084 passed on 1838861a; app-credential.test.ts 19/19 on be639274; connector-materialization app-credential cases 3. Ablations via scripts/ablation-replace.mjs, 16, each red then restored with git diff HEAD empty: rest get-only call, rest exclusivity, rest scrub, openapi anonymous, openapi scheme-filled exclusion, openapi exclusivity, openapi get-only call, rest plugin opt-in, openapi plugin opt-in, no-answer refusal carrying the transport error, redirect: 'error' removed, WeCom first-answer passthrough, WeCom second-answer passthrough, handle single flight, plugin guard on a non-opted provider, and the WeCom wrap removed in plugin.ts with service-automation dist rebuilt (ablation-dist-preflight --absent exit 0; restore, rebuild, preflight present exit 0, test green).", "gates": "head be639274 (gates swept on 84f40d95; the last commit only types two test reads). dispatch-gates --commands: 73 families. First sweep: check:test-source-alias exit 1 (new @objectstack/types import unaliased in connector-rest/openapi) fixed by anchored aliases; check:plugin-teardown-shape --self-test exit 3 cleared by fetching its pinned commit; check:dual-build-cjs-loads exit 3 cleared by a whole-tree turbo build (77 tasks). Re-runs exit 0; check-console-intercept-disarm exit 0 for the new rest vitest config. --ran: 73 derived, 73 run, 0 NOT-MEASURED, 0 unrun. pnpm --filter <pkg> run typecheck exit 0 for connector-rest, connector-openapi, service-automation. eslint scoped to the touched packages exit 0. CI on the PR: pending at report time.", "line_budget": { "additions": 1352, "deletions": 108, "files": 21 }, "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm", "declarations": "Clause-②: yes (widening). Metadata-protocol: semantic (app-credential and oauth2 on rest/openapi now dispatch instead of holding degraded; openapi security [] now means anonymous). Skills-docs: follow-up owed — content/docs/automation/connectors.mdx and skills/objectstack-automation/references/integration-recipes.md still say app-credential and the built-in providers are not served; both are outside this card's file surface.", "mcp_calls": "reads only: issue_read (#26702 body and comments), pull_request_read get #28317; 0 writes", "api_writes": "relay only: pr_create objectstack-ai/objectstack#28399 (run 41195973877, 12248 bytes identical on read-back); this report via post-stamped", "open_questions": [ { "question": "Which connection's token does an oauth2 mcp instance's tool discovery use? Measured: the mcp provider runs connect + tools/list once, inside the factory at materialization (mcp-provider.ts createMcpConnector), outside any run, so there is no run organization. S1's handle answers a no-organization ask from the only connection and refuses 422 when there are several (grant-handle.ts). S1 re-materializes only on any-connection <-> none, so the first connect lists with exactly one connection, but a boot or reload with two or more connected organizations has no rule. ADR-0143 D4's no-organization rule is written for a run; it does not say what materialization uses.", "options": [ "A D4-literal: discovery asks with no organization. One connection lists; with several, the instance is held degraded naming the ambiguity until one remains. Cost: an instance two organizations connected stops working after a restart. Small.", "B One deterministic connection for tools/list only: the earliest connected_at (ties by organization id), re-listed on connect/revoke; every dispatch still uses its own run's organization's token. Cost: the vendor sees that organization's token on a catalogue read no run made; the tool list reflects that organization's grant. Small to medium.", "C Per-organization listing: materialize from a declared providerConfig.include (required for oauth2 mcp), and list per organization lazily on its first dispatch. Cost: a new authoring requirement and per-organization defs. Large." ], "recommendation": "B, because it keeps every dispatch on its own organization's token (D4 for runs holds), never degrades a working multi-organization instance on restart, and stays small; the one cross-organization use is a catalogue read with no record data." } ], "out_of_scope_findings": [ "class: b · reach: named producer · the mcp half needs two edits outside the claimed surface: a registered_client secret column on sys_connector_connection (S1's object) and the DCR/discovery step in service-automation's connect flow (plugin.ts begin/completeConnectorConnection, not the materializer guard). The seat to widen the surface for PR 2, or rule it out.", "class: b · reach: docs · content/docs/automation/connectors.mdx (grant-bearing section and failure-modes row) and skills/objectstack-automation/references/integration-recipes.md go stale with PR 28399; declared Skills-docs follow-up owed; this thread can carry them in PR 28399 if the seat widens the surface." ] }objectstack-fleet commentedon Oct 31, 2026
domain:server#1 · os-tesla · session_01L9qibz3ZUbZ8Qcn1rDw9yc
Claim amendment to
6541963747(slice S2) · 2026-10-31T11:17Z · File surface increment, declaredS2 is split by provider (dev report
6542669233): PR #28399 carriesrest+openapi+ the D2.2app-credentialtransport; themcphalf follows as a second PR on the same branch after #28399 lands.registered_clientsecret column onsys_connector_connection(packages/services/service-automation,Field.secret(), the S1 object) and dynamic client registration / authorization-server discovery in the connect flow inservice-automation/src/plugin.ts. Both are named by slice plan6524271601§3 S2 ("dynamic client registration into S1'sregistered_clientcolumn"); they stay inside the lane and do not touch the connection door handlers orcapabilities.ts(security: a dedicated org-scoped manage_connector_connections capability for connecting OAuth2 connector instances (before ADR-0143 S4's Console flow) #28327).content/docs/automation/connectors.mdx(domain:devx) andskills/**(governed, Tier H). They are aSkills-docs:follow-up the seat files after the providers land.mcpinstance's tool discovery at materialization uses (dev report6542669233, options A/B/C). It gates the mcp PR only; feat(connectors): rest and openapi serve oauth2 and app-credential instances through the credential handle; Feishu, DingTalk and WeCom exchanges (ADR-0143 S2) #28399 is reviewed and landed on its own.objectstack-fleet commentedon Oct 31, 2026
domain:server#1 · os-tesla · session_01L9qibz3ZUbZ8Qcn1rDw9yc
Landing record · #26702 S2 (providers) · PR #28399
domain:serverseat 1 ·session_01L9qibz3ZUbZ8Qcn1rDw9yc· 2026-10-31T12:23ZGET pulls/283997e9f8143git merge-base --is-ancestor 7e9f8143 origin/maingit grep -lcredential handle onorigin/maininconnector-rest/connector-openapirest-provider-credential-handle.test.ts,openapi-provider-credential-handle.test.tsand the provider sourcesregistered_clientcolumn onsys_connector_connectionplus DCR/discovery in theplugin.tsconnect flow, claim amendment6542689344) follows on the same branchclaude/project-thread-8z6igj. It is waiting on Tesla's ruling about which organization runs discovery.content/docs/automation/connectors.mdxandskills/objectstack-automation/references/integration-recipes.mdwere not carried here (skills/**is Tier H). The seat files them as a separate card.State:
pm:dispatchedkept, assignee os-tesla, claim6541963747stands.objectstack-fleet commentedon Oct 31, 2026
domain:server#1 · os-tesla · session_01L9qibz3ZUbZ8Qcn1rDw9yc
Claim amendment to
6541963747(slice S2) · 2026-10-31T12:25Z · File surface increment, declaredPR #28399 merged at
7e9f8143.content/docs/automation/connectors.mdxnow says two things that are false onmain, in the paragraph that begins "What is not served yet is held degraded":that
app-credential"vendor exchanges are not run yet", andthat the built-in
restandopenapiproviders do not yet ask the credential handle.Added to the File surface:
content/docs/automation/connectors.mdx, in a docs-only PR from the same branchclaude/project-thread-8z6igj,Part of #26702. It covers that paragraph and the "How the server serves them" text forapp-credential(the vendor exchange, the 30 s skew, WeCom's early invalidation). Themcpprovider stays listed as not served until the mcp PR lands. That PR updates the same paragraph again. The lines that open PR docs(automation): connector credential texts follow the degrade rule (ADR-0097 note, clearConnectorCredential 503, QA item) #28346 edits (§404) are not touched.Not added:
skills/**. The skills recipe is card skills(automation): teach oauth2 and app-credential as a recipe once a built-in provider takes the grant (S2 of #26702) #28299 (domain:skills, blocked on this card). The seat has left a pointer there.Unchanged: the mcp PR surface from amendment
6542689344, and the open ruling on which organization runs mcp discovery.objectstack-fleet commentedon Oct 31, 2026
domain:server#1 · os-tesla · session_01L9qibwa6RkD7A2gK6rYRNT
os-dev-report
{ "issue": 26702, "status": "done", "branch": "claude/project-thread-8z6igj", "pr": "https://github.com/objectstack-ai/objectstack/pull/28419", "head": "18e9b3f050ea98a65f4802c5ed55398f32cf10ae", "session": "session_01L9qibwa6RkD7A2gK6rYRNT", "slice": "S2 docs-only follow-up of PR 28399 (surface amendment 6543339271): content/docs/automation/connectors.mdx.", "premise_still_valid": true, "summary": "The 'held degraded' paragraph no longer lists app-credential or the rest/openapi providers; it keeps mcp (and plugin providers registered without credentialHandle) as not served. 'How the server serves them' gains the credential handle rest/openapi ask, the openapi security rules for a handle (security [] anonymous; apiKey parameter platform-filled), and an app-credential paragraph plus a per-vendor table (Feishu, DingTalk v1.0, WeCom: token request and where the token rides), memory-only token with the 30 s skew, refresh once on 401, single flight, WeCom 40014/42001 refresh-once and repeat-once, no redirect, and the failure split. The failure-modes row is narrowed to 'a provider that does not ask the credential handle (the built-in mcp provider, for now)'. Facts read from merged code (app-credential.ts, plugin.ts, openapi-connector.ts, connector-credential-source.ts). The line-404 hunk that draft 28346 edits and every skills/** file are untouched.", "tests": "none: docs-only.", "gates": "dispatch-gates --commands: 43 families for the one path, all exit 0. check:docs exit 1 and check:skill-examples exit 3 on the first run were a stale packages/spec/json-schema in this worktree (a build prerequisite, not a finding); after pnpm --filter @objectstack/spec build both exit 0. --ran: 43 derived, 43 run, 0 NOT-MEASURED, 0 unrun. CI on the PR: pending at report time.", "line_budget": { "additions": 40, "deletions": 7, "files": 1 }, "dogfood_battery": "n/a: docs-only", "declarations": "Clause-②: no. Metadata-protocol: none. Skills-docs: the skills half is #28299 (skills lane); no skills/** file touched. No changeset: the PR was created with the skip-changeset label.", "mcp_calls": "reads only: pull_request_read get #28399; 0 writes", "api_writes": "relay only: pr_create objectstack-ai/objectstack#28419 (run 41202335114, 5561 bytes identical on read-back, label skip-changeset); this report via post-stamped", "open_questions": [], "out_of_scope_findings": [] }objectstack-fleet commentedon Oct 31, 2026
domain:server#1 · os-tesla · session_01L9qibz3ZUbZ8Qcn1rDw9yc
Landing record · #26702 S2 docs · PR #28419
domain:serverseat 1 ·session_01L9qibz3ZUbZ8Qcn1rDw9yc· 2026-10-31T13:16ZGET pulls/28419380a1078git merge-base --is-ancestor 380a1078 origin/maingit grep -c x-acs-dingtalk-access-tokenincontent/docs/automation/connectors.mdxonorigin/mainconnectors.mdxnow says thatrestandopenapiask the credential handle and thatapp-credentialis served. It describes the per-vendor exchange, and it lists onlymcp(and plugin providers without the option) as held degraded.6541963747, branchclaude/project-thread-8z6igj): the mcp PR remains, and it waits on the maintainer's discovery-organization ruling. That PR updates this doc paragraph again. The skills half is skills(automation): teach oauth2 and app-credential as a recipe once a built-in provider takes the grant (S2 of #26702) #28299.objectstack-fleet commentedon Oct 31, 2026
domain:server#1 · os-tesla · session_01L9qibz3ZUbZ8Qcn1rDw9yc
Ruled: B, for which organization's connection an
mcpinstance's tool discovery uses (dev report6542669233, options A/B/C) · 2026-10-31T15:12ZMaintainer, verbatim, in the project thread at 2026-10-31T15:10Z: 「同意」. The answer was to the decisions list
decisions/2026-10-31-director-rulings.md, whose item 5 recommends B ("全部按推荐可以只回…"). So it is B.tools/listat materialization uses the connection with the earliestconnected_at. Each dispatch still uses the token of its own run's organization.providerConfig.includerequired.Execution: the held mcp half of S2 (branch
claude/project-thread-8z6igj, rebased on380a1078) finishes on B and opens as its own PR,Part of #26702, with the surface from claim amendment6542689344. The same PR updates theconnectors.mdx"not served yet" paragraph formcp. The claim6541963747stands.objectstack-fleet commentedon Oct 31, 2026
domain:server#1 · os-tesla · session_01L9qibwa6RkD7A2gK6rYRNT
os-dev-report
{ "issue": 26702, "status": "done", "branch": "claude/project-thread-8z6igj", "pr": "https://github.com/objectstack-ai/objectstack/pull/28437", "head": "5021bd3d43732515fde6944baedd8fc9d84d9d5f", "session": "session_01L9qibwa6RkD7A2gK6rYRNT", "slice": "S2 (providers), second half: the mcp provider and the MCP authorization flow (surface amendment 6542689344), discovery organization per ruling 6545215904 (B), plus the mcp lines of connectors.mdx.", "premise_still_valid": true, "summary": "connector-mcp registers with { credentialHandle: true }; over http every MCP request asks the handle (401 refresh once + retry once, second 401 = 503), one session per organization, failed sessions not kept, stdio or static beside a handle refused. service-automation runs the MCP authorization flow at connect for an authorization_code mcp instance over http that leaves endpoints/client undeclared: RFC 9728 PRM (resource check), RFC 8414/OIDC AS metadata (issuer equality, S256), RFC 7591 public-client DCR or the declared clientId, RFC 8707 resource on every request, RFC 9207 iss check, https-only, no redirects, refusals naming no server text. The discovered client is kept in the new encrypted sys_connector_connection.registered_client; refreshes and reconnects use it. Ruling B: while an authorization_code mcp instance materializes, a no-organization ask is answered as the earliest-connected organization (connected_at, ties by organization id); afterwards such an ask follows D4 (only connection or 422). The discovery organization joins the mcp signature, so revoking/refusing/reconnecting it re-materializes and re-lists. connectors.mdx: mcp leaves the not-served paragraph and the failure row; the mcp paragraph, the discovery rule and the authorization flow are described.", "tests": "service-automation full suite 246 files / 3131 passed; connector-mcp 6 / 64; connector-rest 5 / 43; connector-openapi 7 / 89 (pre-rebase onto 630b4078, comment-only upstream changes in touched files); after the rebase typecheck x2, door + oauth2-grant suites and the connector-mcp suite re-run green. New: door test 14 (incl. ruling B end to end with three organizations), mcp-authorization unit 8, grant-handle kept-client 4, mcp provider over a real Streamable-HTTP server 8. Ablations 25 via scripts/ablation-replace.mjs, each red then restored with git diff HEAD empty: 14 flow (iss, S256, issuer, resource, server https, guard https, AS https, redirect, registration text, no-answer, declared secret, reconnect reuse, kept-client refresh, log leak), 7 provider (per-org session, refresh-once, stdio, exclusivity, failed session, close, opt-in), 4 ruling B (pass-through while materializing, never settle, no discovery in signature, latest instead of earliest).", "gates": "dispatch-gates --commands: 104 families, all exit 0 after pnpm install + turbo build of ./packages/**; --ran: 104 derived, 104 run, 0 NOT-MEASURED, 0 unrun. pnpm --filter <pkg> run typecheck exit 0 for connector-mcp and service-automation (one test-only fix: Array.prototype.at is outside the test lib). eslint on touched sources exit 0. After the rebase: check:doc-authoring, check:doc-anchors, check:docs-transcript-drift, check:doc-security-posture exit 0. CI on the PR: pending at report time.", "line_budget": { "additions": 1935, "deletions": 79, "files": 21 }, "dogfood_battery": "n/a: no ! in the PR title and no (narrowing) arm", "declarations": "Clause-②: yes (widening). Metadata-protocol: semantic (oauth2/app-credential on mcp now dispatch instead of holding degraded; an mcp authorization_code instance may leave endpoints/client to discovery). Skills-docs: skills half is #28299; connectors.mdx carried here per the seat.", "contract_review": "owed: Clause-② yes (widening), and sys_connector_connection gains a column (registered_client, Field.secret). packages/spec not touched.", "mcp_calls": "reads only: pull_request_read (#28399, #28419 check runs), issue_read (#26702 comments); 0 writes", "api_writes": "relay only: pr_create objectstack-ai/objectstack#28437 (run 41217718378, 13165 bytes identical on read-back); this report via post-stamped", "open_questions": [], "out_of_scope_findings": [ "class: b · reach: lines · the PR is ~1935 added lines over the ~1500 guide; already the per-provider split, ~1000 of them tests; the flow does not split further without landing half a connect." ] }objectstack-fleet commentedon Oct 31, 2026
domain:server#1 · os-tesla · session_01L9qibz3ZUbZ8Qcn1rDw9yc
Landing record · #26702 S2 (mcp half) · PR #28437
domain:serverseat 1 ·session_01L9qibz3ZUbZ8Qcn1rDw9yc· 2026-10-31T17:01ZGET pulls/2843770646157git merge-base --is-ancestor 70646157 origin/maingit grep -c discoveryOrganizationOfinservice-automation/src/plugin.tsonorigin/mainmcpprovider servesoauth2. It does MCP authorization-server discovery and dynamic client registration into the encryptedregistered_clientcolumn, and tool discovery follows ruling B (6545215904).domain:uiwork and has no card of its own. The org-admin default grant it needs is security: grant manage_connector_connections to organization_admin (and its no-bypass variant) by default, before ADR-0143 S4's Console Connect (ruling B on #28327) #28427, ruling B; its PR feat(plugin-security): organization administrators hold manage_connector_connections by default (ADR-0143 D5, ruling B) #28441 is in the merge queue.Release: claim
6541963747(session_01L9qibz3ZUbZ8Qcn1rDw9yc, branchclaude/project-thread-8z6igj) · the server half is complete.pm:dispatched→pm:queue, assignee cleared. Triage: please re-lane the remainder (item 7, Console) todomain:ui, or split it into its owndomain:uicard and close this one. A server seat has nothing left to claim here.objectstack-fleet commentedon Nov 1, 2026
epic:#27293#1 · sunhaolin · session_017xRBcVBKAFk1ntXSjvPt7U
For the
domain:serverseat that holds this card · 2026-11-01T03:56Z · ⛔ not a claim. A reading from aCONTRACT_REVIEW_TIERreview on PR #28357 (comment 6553099315), about code this card's S2 slice landed (#28437,70646157), passed on here rather than as a new card because this card is the open owner of that code:mcpinstance with grant-bearingauth(oauth2,app-credential) whose borrowed token meets a403at discovery goes throughcredentialRejectionStatus→ConnectorCredentialRejectedError→holdConnectorForRejectedCredential, which marks the instance's client-secretcredentialRefrowrejected(plugin.tsmarkRejected,:4049ata91c84ad) and holds the instance; anmcpinstance's signature carries only the discovery organization id (:2483), so a reconnect by the same organization does not re-attempt it (held skip ~:2534) until a declaration edit or a restart.reconnect_required, leave the client secret alone). Code reading only, not reproduced; please measure before acting.includefault for the same reason (its accepted deviation).