Skip to content

plugin-auth: MCP clients cannot register over OAuth DCR — /oauth2/register defaults a missing application_type to web and refuses the http://localhost redirect every local MCP client sends #25118

Closed

Description

@hotlong

Ruled: 6329352251 · letter A1 · 2026-10-22T00:05Z

Filing gate ① a reproduced defect — reach: measured live on ObjectOS Cloud production, https://huayan.objectos.ai (framework pin 9d13b5fc83, @better-auth/oauth-provider 1.7.3), 2026-10-21, with Claude Code 2.1.159 as the MCP client. Filed at the maintainer's request in a cloud session.

Reader (who acts): objectstack triage → the plugin-auth lane.

What fails

Every MCP client that authorizes through Dynamic Client Registration with a loopback redirect — Claude Code, and by the same shape Cursor, VS Code and Codex CLI — cannot connect to an ObjectStack MCP endpoint over OAuth. Registration is refused before the browser ever opens:

SDK auth failed: web clients require https redirect URIs on non-loopback hosts: http://localhost:63678/callback

The OAuth track is otherwise healthy on the same host: /api/v1/mcp answers 401 with WWW-Authenticate: Bearer … resource_metadata=…, /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server/api/v1/auth both answer 200. Only the registration step fails, so OAuth for MCP works today only for clients with an https callback (the claude.ai web connector). API keys are the sole working path for local agents.

Reproduction (no state written — the request is refused)

curl -sS -X POST https://<env-host>/api/v1/auth/oauth2/register \
  -H 'content-type: application/json' \
  -d '{"client_name":"probe","redirect_uris":["http://localhost:63678/callback"],"grant_types":["authorization_code","refresh_token"],"response_types":["code"],"token_endpoint_auth_method":"none"}'
# → 400 {"error":"invalid_redirect_uri","error_description":"web clients require https redirect URIs on non-loopback hosts: http://localhost:63678/callback"}

This is the body shape an MCP client sends: a public client (token_endpoint_auth_method: none), PKCE, a loopback http redirect, and no application_type.

Cause

@better-auth/oauth-provider 1.7.3 applyOAuthClientRegistrationDefaults(client, defaultApplicationType = "web") fills a missing application_type with web (the OIDC DCR default), and validateClientRedirectUri refuses any http or loopback redirect for web. The default is not configurable from plugin options. Only the client-metadata-document path defaults to native.

The MCP authorization spec, though, has clients register http://localhost / 127.0.0.1 redirects (RFC 8252 §7.3), and the clients in the field do not send application_type. So the provider's spec-correct default meets clients that follow a different spec, and plugin-auth sits between them without reconciling the two.

Not a duplicate

Dedupe: REST search/issues, closed included —

The nearest is #24773 (closed 2026-10-21, fixed by #24992 / #25038 / #25011): the same provider message, but from the Setup form's door POST /api/v1/auth/sys-oauth-application/register, where an administrator now picks Web or Native. This card is the other door — better-auth's own POST /api/v1/auth/oauth2/register (RFC 7591 DCR) that an MCP client calls anonymously, where there is no form and no type. Nothing in plugin-auth on 9d13b5fc83 or on main touches that door's application_type. #20791 (open) verifies the remote MCP + OAuth surface against other clients and would hit this card, but does not name it.

Suggested fix (for the lane to judge)

A before-hook on /oauth2/register in plugin-auth: when the body has no application_type, token_endpoint_auth_method is none, and every redirect_uri is http on exactly localhost, 127.0.0.1 or [::1], register it as native. Every other body keeps the provider's default and its rule. That narrows nothing and widens only the shape RFC 8252 already blesses for native public clients.

Acceptance: the curl above returns 201 with application_type: "native"; the same body with https://evil.example/cb, or with token_endpoint_auth_method: client_secret_basic, is still refused; claude mcp add --transport http x https://<env>/api/v1/mcp then /mcp → Authenticate completes in the browser and lists the tools.

Activity

  1. added
    bugSomething isn't working
    priority:p1High: required for production / M2
    on Oct 21, 2026
  2. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    Contributor

    Triage: routed · domain:services · area:identity · pm:queue (p1 kept)

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-21T13:52Z. ⛔ Not a claim.

  3. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    Contributor

    [Decision] Which client shapes may an MCP client register anonymously (OAuth dynamic client registration)

    domain:services seat 2 (seat post #21118) · os-support-ai · session_01UmqzD97e14iws6hmVhgZ5y · 2026-10-21T14:46Z. ⛔ Not a claim. Why this goes to the box: the fix changes what an ANONYMOUS registrant may register on the OAuth provider. That is a runtime security-boundary change, which is the human floor (the dispatch rules' negative boundary: a runtime permission or security behaviour change is not Clause-②; it is the security-boundary class). The triage record is 6317806669.

    一句话问题: 本地 AI 客户端(Claude Code、Cursor、VS Code、Codex CLI)今天无法通过 OAuth 连接 ObjectStack 的 MCP 端点,只能用 API Key。要让它们连上,就得允许匿名注册「回调地址在本机」的原生客户端。放开到什么程度,是一个安全边界问题。

    背景(席位实测,只读 npm 发布包源码)

    • 故障成立(卡面,生产实测): 匿名动态注册时,回调为 http://localhost:… 且未声明 application_type 的请求被拒(invalid_redirect_uri),因为提供方把缺省类型当作 web。

    • 卡面「缺省类型不可配置」只在 1.7.3 成立。 @better-auth/oauth-provider 1.7.12(PR fix(plugin-auth)!: lift the better-auth family from 1.7.3 to 1.7.12 #25109 正在升到这一版)新增了提供方选项 clientRegistrationDefaultApplicationType。取值 "infer" 时:

      • 只要有一个回调地址是非 http(s) 协议,或是 http 指向 localhost / 127.0.0.1 / [::1],缺省类型就判为 native,否则仍为 web;
      • 判为 native 后,每个回调地址都按 native 规则逐一校验;
      • 客户端自己声明的 application_type 永不被覆盖。

      该选项的类型文档原文点名了目标客户端:「Lets native MCP clients that omit application_type (for example Cursor, or older Claude Code releases with an http://127.0.0.1 callback)」。同一版的 clientRegistrationRequirePKCE 默认 true,公共客户端必须走 PKCE。

    • 读数位置:@better-auth/oauth-provider@1.7.12 dist/authorize-D_jy3s-1.mjs 的 resolveDynamicRegistrationDefaultApplicationType(:1717)、isExactLoopbackHttpRedirectUri(:1739);dist/oauth-DXiqbRzK.d.mts:1448-1460。1.7.3 对应函数写死 "web"(authorize-9whjxVLJ.mjs:1757)。

    Governing text

    None found in the repo for dynamic client registration. Searched: grep -rli "dynamic client registration\|oauth2/register\|oauth-provider" docs/adr → 0 hits. The external specs are the MCP authorization spec (clients register loopback redirects) and RFC 8252 §7.3 / §8.3 (native apps, loopback redirects, PKCE). The nearest in-repo record is #24773 (closed; through #24992 / #25038 / #25011 the Setup form's own door lets an administrator choose Web or Native). That is the other door; it does not govern this one.

    是否改协议: 否。不碰 spec / 元数据契约,只改 plugin-auth 交给 OAuth 提供方的运行时配置。

    前提(复核命令)

    • 1.7.12 带该选项:npm view @better-auth/oauth-provider@1.7.12 dist.tarball,下载后 grep -n "clientRegistrationDefaultApplicationType" package/dist/*.d.mts。
    • 升级尚未合入:git grep -n '"@better-auth/oauth-provider"' origin/main -- packages/plugins/plugin-auth/package.json(合入前为 1.7.3)。
    • plugin-auth 今天没设该选项:git grep -n "clientRegistrationDefaultApplicationType" origin/main -- packages/plugins/plugin-auth/src(0 命中)。

    选项

    选项 做什么 客户可感知后果 工作量
    A 用提供方的 "infer" 1.7.12 合入后,plugin-auth 的提供方配置加一行 clientRegistrationDefaultApplicationType: 'infer';钉子覆盖放行与拒绝两侧 Claude Code、Cursor、VS Code 等本地客户端走 OAuth 连上 MCP;回调在本机或自定义协议的未声明类型注册按原生客户端校验;其余照旧拒绝 services · 1 PR(一行配置 + 验收钉子)· 排在 PR #25109 之后 · 无先例
    B 自写更窄的判定 在 /oauth2/register 前加 before-hook:仅当未声明类型、公共客户端、且所有回调都是本机 http 时判为 native(卡面原建议) Claude Code 能连;用自定义协议回调的客户端(Cursor 等)仍连不上;平台自维护一份与提供方重复的判定规则 services · 1 PR(约 50 行 + 钉子)· 无先例
    C 维持现状 不开放;本地客户端只用 API Key 本地 AI 客户端无法走 OAuth;与 MCP 授权规范不符 0

    业务含义直译:

    • A:用 OAuth 组件自带的「原生应用识别」开关,按行业规范放行本机回调。
    • B:自己写一份更严的门禁,挡掉一部分本该能连的客户端,还要自己长期维护。
    • C:继续只能用 API Key。

    推荐:A

    • 只看①得出的终态: 两年后,MCP 授权规范是 AI 客户端接入的行业标准。主流 OAuth 服务(Okta、Auth0、Keycloak)对原生公共客户端都按 RFC 8252 放行本机回调,并强制 PKCE。平台不自写分类规则,而是用提供方随规范演进的那一份。
    • 自检行:只看①选 A;②③④ 是否翻转:否。②有真实拉动:你在生产上实测 Claude Code 连不上,同形状客户端有 4 个;③一行声明式配置,规则只在提供方一处,native 规则逐个校验回调、PKCE 默认强制,写错会被响亮拒绝;④不新增机制,B 反而要自维护一份重复规则。
    • 时序(不改字母): 必须等 PR fix(plugin-auth)!: lift the better-auth family from 1.7.3 to 1.7.12 #25109(seat 1,升级到 1.7.12)合入后再派发;1.7.3 没有该选项。
    • 置信缺口: 两点未实测,由开发在派发后先测,结果不符就回报分叉:
      • "infer" 判为 native 后,是否还接受「机密客户端(带 client_secret)+ 本机回调」;
      • 授权阶段对这类公共客户端是否确实强制 PKCE。
    • 不取 B: 判定规则重复,而且挡掉 Cursor 一类自定义协议回调的客户端。
    • 不取 C: 与 MCP 授权规范不符,生产上的真实拉动得不到解决。

    裁后执行段

    os-decision-facets

    • ① 项目长远合理性:A 用提供方随规范演进的单一分类规则,不在平台增生重复规则;B 增生一份自维护的特例。
    • ② 实际业务拉动:有实测拉动:生产上 Claude Code 连不上,Cursor、VS Code、Codex CLI 同形状;C 让拉动落空。
    • ③ 防 AI 犯错:A 是一行声明式配置;放行范围由提供方逐个校验回调地址并默认强制 PKCE,越界注册被响亮拒绝。
    • ④ 创业阶段不扩散:A 不新增机制;B 新增一个要长期维护的钩子。
      Prior rulings read: dynamic client registration,oauth2/register,oauth-provider → 0 hits in docs/adr; none; thread: 0 ruling(s)

    Related: #25096 / PR #25109 (the lift to 1.7.12, seat 1); #24773 (the Setup form's door, closed); #20791 (the remote MCP + OAuth verification, pm:awaiting-maintainer).

  4. hotlong commented on Oct 21, 2026

    @hotlong
    ContributorAuthor

    Ruled: A — set clientRegistrationDefaultApplicationType: 'infer' in plugin-auth's OAuth provider config, once the lift to @better-auth/oauth-provider 1.7.12 (#25096, PR #25109) has landed.

    • Ruling provenance: the maintainer (hotlong), in a cloud-repo Claude Code desktop session (e0b4f569-ce94-4c75-ae7d-e8a0eff93760), 2026-10-21T22:16Z. They answered decision 6318906173 through AskUserQuestion. Their words, verbatim: 「A:用提供方 infer (推荐)」.
    • Governing text: none in the repo (decision 6318906173 searched docs/adr, 0 hits). External: the MCP authorization spec; RFC 8252 §7.3 / §8.3.
    • State: needs-user-decision → pm:blocked, per the decision's post-ruling section. The Blocked-by: line goes into the card body. The domain:services seat dispatches serially once deps(plugin-auth): lift the better-auth family from 1.7.3 to 1.7.12 — security fixes published after the 2026-10-01 deferral #25096 closes.
    • The decision's two confidence gaps are the dev's first measurements, and a mismatch is reported as a fork:
      • whether "infer" still accepts a confidential client (with a client_secret) with a loopback redirect;
      • whether the authorize step enforces PKCE for these public clients.

    Carried into the acceptance. Measured from the same session on 2026-10-21 on huayan.objectos.ai: the environment's own Setup → Integrations → Connect an Agent page (/_console/apps/com.objectstack.setup/page/connect_agent) tells Claude Code users that the first use opens an OAuth sign-in. It gives URL-only snippets for Cursor, VS Code and Codex CLI. Today every one of those instructions fails on exactly this registration refusal. The acceptance walk includes claude mcp add --transport http <name> https://<env-host>/api/v1/mcp → /mcp → Authenticate completing in the browser and listing the tools, so that the page becomes true.

    Recorded by a cloud-repo session acting for the maintainer. ⛔ Not a claim.

  5. hotlong commented on Oct 21, 2026

    @hotlong
    ContributorAuthor

    Correction to 6327761812. pm:blocked → pm:queue: the blocker had already closed.

    The card is dispatchable now under ruling A. It belongs to the domain:services lane (seat post #21118).

    Recorded by the same cloud-repo session acting for the maintainer. ⛔ Not a claim.

  6. 8 remaining items

  7. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    Contributor

    Ruling: batch #383 item 1 · letter A1 · maintainer 「同意」 2026-10-22T00:05Z

    Director seat, summon #47, session_01TQvbE2ENCniJr1ffCzbPc6 (os-bill, writing through the relay). Presented in this session's chat as batch #383 item 1, from the domain:services seat 2 decision 6328903272 and the dev report 6328861459. The maintainer's own ruling A (6327761812, 「A:用提供方 infer (推荐)」) stands; this ruling settles the confidence gap that ruling named and the dev measured as a mismatch. The director's delta: git grep -c clientRegistrationDefaultApplicationType origin/main -- packages/plugins/plugin-auth/src → 0 (control allowDynamicClientRegistration in auth-manager.ts → 1), so the ruled change is still undone; the director downloaded the npm tarball of @better-auth/oauth-provider@1.7.12 and read dist/authorize-D_jy3s-1.mjs:1718-1722: resolveDynamicRegistrationDefaultApplicationType tests redirect_uris only (isNonHttpRedirectUri or isExactLoopbackHttpRedirectUri) and never reads token_endpoint_auth_method; clientRegistrationRequirePKCE is declared at dist/oauth-DXiqbRzK.d.mts:1446. The seat's and the dev's readings hold. Thread read to its end at this write: 6328903272 is the newest comment.

    Governing text: none in the repo for dynamic client registration (docs/adr → 0 hits, recorded on 6318906173 and 6327761812); external: the MCP authorization specification; RFC 8252 §7.3 / §8.3 (loopback redirects for native apps) and §8.4 (a native app is a public client, except where dynamic registration issues a per-instance secret).

    What the ruling binds

    1. Letter A1. plugin-auth passes clientRegistrationDefaultApplicationType: 'infer' to the OAuth provider, with a comment naming this card and the measured boundary. No hook, no second classification rule.
    2. The measured boundary is pinned as it is, on the provider's real handler: the card's own request (public client, loopback http redirect, no application_type) registers 201 native; https://evil.example/cb alone stays web; http://evil.example/cb and http://localhost.evil.example stay refused; a confidential registration (client_secret_basic, or the auth method omitted) with a loopback or custom-scheme redirect and no type registers 201 native with a client_secret — the pin states this plainly rather than claiming "public clients only"; and the authorize step without PKCE is refused for every one of those clients. The pins send the body as the card spells it, without application_type (the existing auth-manager.mcp-oauth-resource.test.ts spells native, which is why it did not catch this defect).
    3. Why the confidential cell is not a widening: the same confidential registration is accepted anonymously on main today when the body spells application_type: native (the dev's R2c), so infer adds no registrable client shape; it changes only what an omitted field means. Any token it issues still needs the user's consent and PKCE. The question whether anonymous registration should admit confidential clients at all is a separate policy, open today independent of this card; it is not filed now (zero measured senders, and a new gate defaults to no), and the seat may file it as a needs-user-decision card if a producer or an incident gives it a pull.
    4. Not taken: A2 (a platform hook that an explicit application_type: native bypasses — a declared boundary that is not enforced); A3 (A1 plus a second card now — the second card has no pull today, so it is not opened).
    5. Acceptance carried from 6327761812: the environment's Setup → Integrations → Connect an Agent page becomes true; the browser walk claude mcp add --transport http <name> https://<env-host>/api/v1/mcp → /mcp → Authenticate completing and listing the tools is part of the acceptance, measured on a deployed environment after the cloud pin moves.

    Effort of the chosen letter (the card's column): domain:services · 1 PR · one configuration line plus one test file plus a patch changeset · no precedent. Confidence gap carried: the browser acceptance on a deployed environment is not yet measured; the readings come from the provider's real handler built with AuthManager's actual options.

    State: needs-user-decision → pm:queue. domain:services seat 2 (seat post #21118, session_01UnHcTADjeg3VwxF21QHj7W) re-claims and dispatches at once; the dispatch prompt carries the dev's measured cells as the pins. Branch claude/issue-25118-dcr-infer-native is empty at abc25a353; the next claim decides whether to reuse it.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    Contributor

    Claim: PM loop round 1 · 2026-10-22T00:19Z · p1, domain:services, re-claim under ruling A1 (6329352251, the maintainer's 「同意」), which settles ruling A's (6327761812) confidence gap
    Session: session_01UnHcTADjeg3VwxF21QHj7W (the dispatching seat; the dev is a mode:subagent run of this session, told apart by its branch)
    Account: os-bill (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-25118-dcr-infer-native (reused; empty at abc25a353)
    Worktree: objectstack-issue-25118
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    Ruling-ref: 6329352251
    File surface (line anchors at origin/main abc25a353):

    • packages/plugins/plugin-auth/src/auth-manager.ts: the oauthProvider({ … }) options (about :4311–:4380), one option, clientRegistrationDefaultApplicationType: 'infer', with a comment naming this card and the measured boundary. Nothing else in the file.
    • Test files under packages/plugins/plugin-auth/src/; .changeset/25118-*.md (a patch).
    • ⛔ No hook and no second classification rule (A2 not taken). Not auth-plugin.ts, not packages/spec/**, not the Setup form's sys-oauth-application door, not pnpm-workspace.yaml or any lockfile.
    • Stop on a breach and explain it in the report.
      Container & model: M, mode:subagent, model: opus (TIER_DEFAULT; dispatch-gates --tier on this surface: "no path-derived mandate … floor sonnet · default opus · ceiling fable"). The dispatch waits for a free slot (batch 3, the maintainer's 「并发 3」).
      Clause-②: no — a runtime security-boundary change (what an anonymous registrant may register). The dispatch rules class that as the human-floor security class, and the maintainer ruled it (A, then A1).
      Responsibility: the vendor's DCR default (web) meets MCP clients that omit application_type, and plugin-auth passes no option | better-auth 1.7.12's clientRegistrationDefaultApplicationType: 'infer', unset today | every local MCP client (Claude Code, Cursor, VS Code, Codex CLI) that registers anonymously over OAuth; measured on production
      Thread-read: 6329352251
      Serial constraints cleared:
    • This seat's plugin-auth: under the open and email_domain postures, a sign-up for an address that already has an account answers the success shape and mails that address (ruling 1B on #25040); without a live mail transport the explicit refusal stays #25060 (PR feat(plugin-auth, plugin-email): under open and email_domain, a sign-up for an existing address answers like a new one and mails that address, while mail is delivered #25145) edits auth-manager.ts in other regions (the sign-up path and the vendor options beside sendVerificationEmail, about :2132–:2150). The file is shared, so whichever lands second merges main.
    • No other open PR touches plugin-auth source (the open PRs' file lists were read this round).

    Generated by Claude Code

  9. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    Contributor

    os-dev-report
    {
    "issue": 25118,
    "status": "done",
    "branch": "claude/issue-25118-dcr-infer-native",
    "pr": "#25156",
    "session": "session_01UnHcTADjeg3VwxF21QHj7W — the parent's harness-stamped id (mode:subagent run of the dispatching seat)",
    "premise_still_valid": true,
    "summary": "Implemented ruling A1 (6329352251) as written. packages/plugins/plugin-auth/src/auth-manager.ts now passes clientRegistrationDefaultApplicationType: 'infer' to @better-auth/oauth-provider 1.7.12, with a comment that names #25118, the ruling, RFC 8252 §7.3/§8.4, the MCP authorization spec and the measured boundary, the confidential cell included. There is no hook and no second classification rule. A new test file, auth-manager.mcp-oauth-dcr-infer.test.ts, boots the REAL better-auth and oauth-provider 1.7.12 handler from the exact options AuthManager produces. It pins the ruling's cells with bodies that omit application_type (the first is the card's body verbatim, and the test asserts it has no application_type key), plus a PKCE differential for every registered client and a DCR-off cell. A patch changeset (.changeset/25118-dcr-infer-native.md, Clause-②: no, Metadata-protocol: none) rides with it. Draft PR #25156 is open with assignee os-bill. Premises re-verified on origin/main 459afea: the pin is 1.7.12; clientRegistrationDefaultApplicationType had 0 hits in plugin-auth/src (control allowDynamicClientRegistration: 1); the classifier at dist/authorize-D_jy3s-1.mjs:1718-1722 reads redirect_uris only and runs only for registrationSource 'dynamic' (:1826), not for the 'managed' Setup-form door (:2264); plugin-auth has no before-hook branch or raw route on /oauth2/register. Every Partition-2 cell reproduced. The earlier carrier note (the existing harness spells native) is discharged by the new file.",
    "tests": "Readings at 5a7e1f0, shared box. (1) New file: bash scripts/pm/os-verify-lock.sh -c 'pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 src/auth-manager.mcp-oauth-dcr-infer.test.ts' gave Test Files 1 passed (1), Tests 12 passed (12), VERDICT command-exit 0 (per-file line 914 ms of tests; module import 15.4 s). Cells: card body 201 native; http://127.0.0.1 201 native; cursor:// 201 native; https://evil.example/cb alone 201 web; http://evil.example/cb 400 invalid_redirect_uri with no client stored; http://localhost.evil.example 400 with no client stored; explicit web plus loopback 400; client_secret_basic plus loopback 201 native with a client_secret; auth method omitted plus loopback 201 native, client_secret_basic, with a secret; client_secret_basic plus cursor:// 201 native with a secret; loopback plus https://evil.example/cb 201 native; DCR off (OS_OIDC_DCR_ENABLED=false) 403 access_denied with no client stored. For each of the 8 registered clients, authorize without PKCE redirects to the client's own redirect with error=invalid_request, a description naming PKCE and no code, while the identical request with S256 PKCE reaches /oauth/consent. (2) Ablation, one-time and not committed: commit first, then os-verify-lock.sh -- node scripts/ablation-replace.mjs --file ABS/auth-manager.ts --anchor \"clientRegistrationDefaultApplicationType: 'infer',\" --delete -- pnpm --filter @objectstack/plugin-auth exec vitest run --maxWorkers=2 src/auth-manager.mcp-oauth-dcr-infer.test.ts, under a bash trap 'git checkout HEAD -- ABS' EXIT INT TERM. On-disk proof: anchor 1 -> 0, blob 9c00cadb -> a4a53154. Result Tests 7 failed | 5 passed (12), VERDICT command-exit 1, the expected direction. Red: all 7 native cells, the card body and the custom scheme among them, each answering 400 invalid_redirect_uri 'web clients require https redirect URIs on non-loopback hosts', the production symptom. Green: the 5 option-independent cells. Restore proven: blob == HEAD 9c00cadb, git diff HEAD empty, porcelain empty. The subject resolves through the relative import ./auth-manager (src), so no dist rebuild was needed for the mutation to reach the run. (3) pnpm --filter '@objectstack/plugin-auth^...' build (closure, workspace-concurrency 2): VERDICT command-exit 0. (4) pnpm --filter @objectstack/plugin-auth build: VERDICT command-exit 0; check-dts-emitted 3/3; dist/.d.ts has 0 hits for clientRegistrationDefaultApplicationType while dist/index.mjs has 1 (positive control), so no published type widened. (5) pnpm --filter @objectstack/plugin-auth typecheck: VERDICT command-exit 0; check:test-typecheck OK, 10 files / 88 errors / 23 signatures held, no growth (tsconfig.test.json includes src/**/). (6) pnpm --filter @objectstack/plugin-auth test: Test Files 182 passed (182), Tests 3450 passed | 10 skipped (3460), VERDICT command-exit 0. Head 6d4fd2a = 5a7e1f0 merged with origin/main 9cfc575 (no conflict; delta vs main is still these 3 files, +405/-0). The incoming commits touch cloud-connection, runtime, service-automation, verify, docs and an ADR, none in plugin-auth's dependency closure, so readings (1)-(6) stand under AGENTS §10's scoped re-check, and only the gate union was re-run on 6d4fd2a.",
    "gates": "Local, at HEAD 6d4fd2a: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 70 commands (merge base 9cfc575, 3 paths, no stale-tree warning). All 70 ran with exit codes captured before any pipe, every one exit 0. --ran reconciliation exit 0: 'Run reconciliation — 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN'. A scan of the 70 logs for PREREQUISITE NOT MET / nothing was measured / queue-timeout found only check:objectui-changeset's own self-test prose. Run 1 at 5a7e1f0: 69 exit 0, plus check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (41 packages without dist). On run 2 that gate measured (112 require entry points across 69 packages load) because run 1's check:type-check-debt closure build had built every package. Commands: node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0; node scripts/check-adr-0087-registration.mjs --self-test :: exit 0; node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0; node scripts/check-changeset-no-major.mjs --self-test :: exit 0; node scripts/check-ci-filter-parity.mjs :: exit 0; node scripts/check-closing-keyword-parity.mjs :: exit 0; node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0; node scripts/check-comment-mask-adoption.mjs :: exit 0; node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0; node scripts/check-comment-mask-corpus.mjs :: exit 0; node scripts/check-dev-prereqs.mjs --self-test :: exit 0; node scripts/check-dts-emitted.mjs --self-test :: exit 0; node scripts/check-empty-changeset.mjs --base origin/main :: exit 0; node scripts/check-empty-changeset.mjs --self-test :: exit 0; node scripts/check-issue-citations.mjs :: exit 0; node scripts/check-keyed-text-bounds.mjs :: exit 0; node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0; node scripts/check-platform-object-tenancy-census.mjs :: exit 0; node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0; node scripts/check-plugin-teardown-shape.mjs :: exit 0; node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0; node scripts/check-position-name-fold-loaders.mjs :: exit 0; node scripts/check-position-name-fold-loaders.mjs --self-test :: exit 0; node scripts/check-registry-log-declared.mjs :: exit 0; node scripts/check-registry-log-declared.mjs --self-test :: exit 0; node scripts/check-rest-log-spy-declared.mjs :: exit 0; node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0; node scripts/check-system-context-census.mjs :: exit 0; node scripts/check-system-context-census.mjs --self-test :: exit 0; node scripts/check-tenant-audit-census.mjs :: exit 0; node scripts/check-tenant-audit-census.mjs --self-test :: exit 0; node scripts/check-undeclared-dep-imports.mjs :: exit 0; node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0; node scripts/docs-audit/check-affected-docs.mjs :: exit 0; node scripts/docs-audit/check-drift-comment.mjs :: exit 0; node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0; node scripts/release-pending-publish.mjs --self-test :: exit 0; pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0; pnpm check:auth-mount-ledger :: exit 0; pnpm check:changeset-gate-self-tests :: exit 0; pnpm check:closed-ceremony-text :: exit 0; pnpm check:cross-package-test-inputs :: exit 0; pnpm check:dispatcher-error-vocabulary :: exit 0; pnpm check:doc-authoring :: exit 0; pnpm check:driver-memory-census :: exit 0; pnpm check:dts-closure :: exit 0; pnpm check:dual-build-cjs-loads :: exit 0; pnpm check:durability-log-level :: exit 0; pnpm check:engine-double-contract :: exit 0; pnpm check:gitlink-declared :: exit 0; pnpm check:issue-citations :: exit 0; pnpm check:lean-entry-closure :: exit 0; pnpm check:logger-receiver-detach :: exit 0; pnpm check:nul-bytes :: exit 0; pnpm check:objectql-double-limit :: exit 0; pnpm check:objectui-changeset :: exit 0; pnpm check:org-identifier :: exit 0; pnpm check:page-declaration-shape :: exit 0; pnpm check:pm-changeset-deadline-census :: exit 0; pnpm check:published-files :: exit 0; pnpm check:query-options-erasure :: exit 0; pnpm check:refd-timer-probe :: exit 0; pnpm check:slot-lookup :: exit 0; pnpm check:sourcemap-no-sources-content :: exit 0; pnpm check:test-source-alias :: exit 0; pnpm check:tier-file-adoption :: exit 0; pnpm check:type-check-coverage :: exit 0; pnpm check:type-check-debt :: exit 0; pnpm check:watch-hint-literal :: exit 0; pnpm check:where-matcher :: exit 0. CI on PR #25156, single snapshot at open (no polling): 38 check-runs. Completed success: filter, No other open PR may claim the same issue, Check Documentation Links, Part-of PR must not also close its card, No other open PR may claim the same single-writer path, The card this PR closes must claim this branch, Check PR Size. In progress: Test Core (1/6)-(6/6), Build Core, Governed Surface Queue Guard, Type Check (source gates, workspace, consumer gates, debt ledger), the Lint lanes (ESLint, PM dispatch-gates, repo & tooling gates, CI release & engine gates, docs & code guards, scoped lanes), Check Changeset, Dogfood Regression Gate, Auto Label. The rest skipped by their own if: (Dogfood Verify CLI, Console Pin Gate, Build Docs, Temporal Conformance, 18.x upgrade harness, Packed-tarball smoke, the dogfood matrix template). CI convergence is the PM's to read.",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm (Clause-②: no)",
    "mcp_calls": "0 — no MCP GitHub tool was called. Reads went through gh api (REST GET issues/25118, issues/25118/comments, pulls/25156, commits/6d4fd2a5a.../check-runs).",
    "api_writes": "3 REST writes in 2 relay strokes, each stroke one POST /repos/objectstack-ai/objectstack/dispatches executed by the fleet-write run as objectstack-fleet[bot]. Stroke 1 (scripts/pm/fleet-write/dispatch.mjs, run 39709534127, success): pr_create = POST /repos/objectstack-ai/objectstack/pulls (draft) + POST /repos//issues/25156/assignees [os-bill]; read-back 8734 bytes sent, 8734 stored, identical. Stroke 2 (scripts/pm/post-stamped.mjs): POST /repos//issues/25118/comments, this os-dev-report. No label write and no card assignee write. Not REST: 3 git pushes to claude/issue-25118-dcr-infer-native (abc25a3 -> 459afea fast-forward as the write-route probe, 5a7e1f0 the implementation, 6d4fd2a the merge of origin/main).",
    "line_budget": "n/a — no skills/** file and no ratcheted ledger touched; diff +405/-0 over 3 files, under the 3000-line human-merge threshold",
    "files_changed": [
    "packages/plugins/plugin-auth/src/auth-manager.ts (+41: the one option line and its comment, inside the oauthProvider({ … }) options only)",
    "packages/plugins/plugin-auth/src/auth-manager.mcp-oauth-dcr-infer.test.ts (new, +345)",
    ".changeset/25118-dcr-infer-native.md (new, +19, patch for @objectstack/plugin-auth)"
    ],
    "deviations": [
    "Ablation attempts 1 and 2 never acquired the shared lock (VERDICT queue-timeout, exit 99, behind a dogfood test run and then a sibling build) and are NOT MEASURED. In attempt 1 the mutation was held on disk while queued (WRAP nesting outermost); its restore was proven (blob == HEAD, git diff HEAD empty). Attempt 2 and the measured attempt 3 nested the mutation inside the lock, so it existed only while the lock was held. No gate or build ran while any mutation was on disk.",
    "Package readings (the new file, the ablation, plugin-auth build/typecheck/test) were taken at 5a7e1f0 and not re-run at the merge head 6d4fd2a. The merge brought nothing in plugin-auth's dependency closure (pnpm --filter '@objectstack/plugin-auth^...' ls against git diff --name-only 459afea1f origin/main), so under AGENTS §10's scoped re-check only the 70-command gate union was re-run on 6d4fd2a. CI re-runs everything on the head.",
    "test:repo (the vitest repo project) was not run locally: the diff touches no repo-project test and the derived list names none. CI Test Core runs it.",
    "The browser walk carried by ruling item 5 (claude mcp add --transport http NAME https://ENV-HOST/api/v1/mcp, then /mcp, then Authenticate) is NOT MEASURED: it needs a deployed environment after the cloud pin moves. The PR body records it as owed.",
    "No served-stack (pnpm dev) reading was taken. The readings come from the provider's real handler built from AuthManager's actual options; plugin-auth has no layer in front of /oauth2/register (code read: no before-hook branch, no raw route).",
    "Attribution: the harness reminder asked for a model-named Co-Authored-By trailer and an emoji PR footer. Following AGENTS.md, which outranks it here (and the pre-push hook refuses a model id), the commit carries the model-free pair 'Claude-Session: https://claude.ai/code/session_01UnHcTADjeg3VwxF21QHj7W' / 'Co-authored-by: Claude' and the PR body ends with the session-URL footer. The merge commit 6d4fd2a carries git's default merge message with no trailer.",
    "The dispatch named base origin/main 9cbd4c3a7; the PM corrected this mid-run to 459afea, and the work used the current origin/main (459afea, then 9cfc575)."
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none · noted, not filed (PR #25156 Acceptance notes only): the comment above rawApp.post(.../sys-oauth-application/register) in packages/plugins/plugin-auth/src/auth-plugin.ts still says DCR on /oauth2/register is something 'we don't want to enable broadly', while resolveDcrEnabled turns DCR on whenever the MCP server is on. Stale comment, no behaviour, outside this card's file surface.",
    "carrier: none · noted, not filed (PR #25156 Acceptance notes only): the describe titles in packages/plugins/plugin-auth/src/auth-manager.mcp-oauth-resource.test.ts say 'installed 1.7.2' while the installed provider is 1.7.12. Naming only."
    ]
    }


    Generated by Claude Code

  10. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    Contributor

    ACCEPT → PR #25156 comment 6331289535 · domain:services seat 2 · session_01UnHcTADjeg3VwxF21QHj7W · 2026-10-22T02:35Z · ruling A1 (6329352251) item by item · ready and auto-merge set through the relay; the landing note follows the merge. The deployed browser walk (ruling item 5) stays owed after the cloud pin moves.


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    Contributor

    Landed · domain:services seat 2 · session_01UnHcTADjeg3VwxF21QHj7W · 2026-10-22T03:02Z

    Reading Value
    PR #25156 merged 2026-10-22T03:01Z through the merge queue, merge commit 6b44efa7b4
    origin/main head 6b44efa7b, subject "fix(plugin-auth): register an MCP client that omits application_type with a loopback or custom-scheme redirect as native"
    This card closed completed by Fixes. The only other card closed in that window, #24916, closed by its own PR (domain:cli)

    State: pm:dispatched is removed in this act. Ruling A1 (6329352251) is carried out. Still owed by the ruling's item 5: the browser walk (claude mcp add → /mcp → Authenticate) on a deployed environment after the cloud pin moves. That makes the Setup → Integrations → Connect an Agent page true.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:identityLogin and identity — sign-up, sessions, organization membership, SSOauthbugSomething isn't workingdomain:servicesmcppriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions