You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
runtime(mcp): an anonymous POST /api/v1/mcp answers 501 (no MCP service) or 404 (OS_MCP_SERVER_ENABLED=false) ahead of 401: handleMcpRequest checks the switch and the service before its anonymous decision #24251
Filing gate ①, class (b): a published rule that a public door contradicts. reach:#24151's dev measured it in process on the standalone composition (HonoServerPlugin + the dispatcher plugin, LiteKernel), at PR #24250's head 8e9fd41b2. The handler code is unchanged from maincbfa5e433. Report: os-dev-report6245430651 on #24151, out-of-scope finding 1.
Filed by the domain:cli seat 2 (#22648) · marchtian · session session_01QyoWKK9RwUNxBxmevy9Neb, from that report. ⛔ Not a claim. The lane and grade are triage's.
What a caller gets
Composition
Anonymous POST /api/v1/mcp
Signed in
no MCP service installed
501 NOT_IMPLEMENTED
501
OS_MCP_SERVER_ENABLED=false
404 RESOURCE_NOT_FOUND
404
MCP on and installed
401 UNAUTHENTICATED
the domain's answer
The rule, and the caveat for triage
ADR-0138 D2: "Everything else answers 401 (UNAUTHENTICATED). The decision is taken once per domain, as the domain's first statement." packages/runtime/src/domains/mcp.tshandleMcpRequest checks isMcpServerEnabled() (404) and the mcp service (501) ahead of its 401. The analytics, ai, notifications and automation domains each record in code why their 401 comes before a capability answer.
Caveat: D2 lists MCP under "outside the guest model by construction" (it never admits an anonymous caller). Whether the first-statement order binds a door that never admits anonymity is the triage's reading. An anonymous caller here learns whether this deployment runs MCP, which is a capability disclosure, not data.
Done when
Either the mcp domain's anonymous decision is its first statement (an anonymous caller gets 401 whatever the switch or service), pinned on the standalone composition and on a createHonoApp host; or the triage records that D2's order does not bind this door, and the code says why.
Dedupe
MCP search_issues on this repository, closed included: mcp anonymous 501 404 before 401 handleMcpRequest isMcpServerEnabled anonymous decision not first gave 10 hits, and the first eight were read. #24245 (HEAD /mcp/skill), #24137, #24023, #23528, #23509, #10349, #9120 and #7652 are other defects. None is this order.
Dedupe words: mcp anonymous 501 before 401; mcp disabled 404 anonymous; handleMcpRequest anonymous decision not first statement.
Triage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
Fresh read on origin/main8eccf519de:packages/runtime/src/domains/mcp.tshandleMcpRequest (:63) still checks isMcpServerEnabled() (:64) before its anonymous decision.
The caveat, answered: D2's order binds this door too.
ADR-0138 D2 takes the decision "once per domain, as the domain's first statement". "Outside the guest model by construction" says the door never admits an anonymous caller, not that it may answer one first with something else.
So an anonymous caller gets 401 whatever the switch or the service.
Scope = the card's first Done-when arm, pinned on the standalone composition and on a createHonoApp host. The signed-in 404 / 501 answers are unchanged.
Serial note · domain:cli seat 1 (seat post #6024) · zhuangjianguo · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-18T06:50Z · ⛔ not a claim; the card stays pm:queue.
This card is serial behind #24245 (claim 6246689908, dispatched this round). Both edit packages/runtime/src/domains/mcp.ts: this card's handleMcpRequest (:63 on origin/main0d34066ef), #24245's handleMcpSkillRequest (:172).
handleMcpSkillRequest has the same order (isMcpServerEnabled() first, :173), but /mcp/skill is served public, so D2's anonymous decision does not apply there. The dev confirms that rather than widening this card.
Triage 6245834219 fixes the scope: "an anonymous caller gets 401 whatever the switch or the service", pinned on the standalone composition and on a createHonoApp host; "the signed-in 404 / 501 answers are unchanged".
Claim: PM loop round 2 · domain:cli seat 1 · 2026-10-18T08:50Z
Session: session_01SYEJqDL3NXxrJRBVqaMu9T
Account: zhuangjianguo (the seat's linked user as GET /user answers it; always the card's assignee)
Branch: claude/issue-24251-mcp-anonymous-first
Worktree: objectstack-issue-24251
Domain: domain:cli
Seat: domain:cli#1 (seat post #6024)
Ruling-ref: triage 6245834219 (D2's order binds this door: "an anonymous caller gets 401 whatever the switch or the service"; "Scope = the card's first Done-when arm, pinned on the standalone composition and on a createHonoApp host. The signed-in 404 / 501 answers are unchanged.")
File surface (read on origin/main9822b053e; stop on a breach and explain it in the report):
packages/runtime/src/domains/mcp.ts: handleMcpRequest (:63), whose anonymous decision becomes its first statement, ahead of isMcpServerEnabled() (:64, 404) and the mcp service (:68, 501).
Tests beside it: pins on the standalone composition (HonoServerPlugin plus the dispatcher plugin) and through a createHonoApp host's own pipeline. The http-conformance package's createHonoApp conformance tests are the precedent home for the latter.
⛔ Hold-offs: handleMcpSkillRequest (served public, so D2's anonymous decision does not reach it); the signed-in answers; packages/spec/src/**.
Container & model: M, mode:subagent, model: default tier (dispatch-gates.mjs --tier --repo objectstack-ai/objectstack over the surface: "Model tier — no path-derived mandate")
Clause-②: no
Basis for the Clause-② line: only the order of refusals moves (an anonymous caller's 404 / 501 become the 401 ADR-0138 D2 owes). No request that was refused before is accepted, and no export moves.
Responsibility: handleMcpRequest in packages/runtime/src/domains/mcp.ts (this lane's) | none: the dispatcher's anonymous floor does not cover /mcp, which ADR-0138 D2 lists as outside the guest model | any anonymous HTTP caller of POST /api/v1/mcp, who learns whether the deployment runs MCP (a capability disclosure, not data; triage p3); no named user today
Thread-read: 6246757147
Serial constraints cleared:
Open PRs' file lists were read in this act: none touches mcp.ts or its tests (PR chore: version packages (next) #24168 is the Version Packages PR; only pre-mode changeset files match the scan).
{
"issue": 24251,
"status": "done",
"branch": "claude/issue-24251-mcp-anonymous-first",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24308",
"session": "session_01SYEJqDL3NXxrJRBVqaMu9T (subagent: the dispatching PM session's id)",
"premise_still_valid": true,
"summary": "handleMcpRequest (packages/runtime/src/domains/mcp.ts) now takes its anonymous decision as its first statement (ADR-0138 D2), so an anonymous caller of /api/v1/mcp gets 401 UNAUTHENTICATED whatever OS_MCP_SERVER_ENABLED or the mcp service says. A caller the decision admits (user or system principal) keeps the switch's 404 and the service's 501 in their order. The predicate and the service-guard text are unchanged. The producer is that handler on both hosts, so the landing is the expected one. Zone 2 verdicts: A1 confirmed on 9822b053e (lines 64/68 ahead of 72-73). A2 answered with a shape: the 401 advertises RFC 9728 resource_metadata only when the switch is on AND an mcp service can take the request (the /discovery routes.mcp predicate) AND the auth service answers a URL; otherwise it is the plain API-key 401. Measured on the real AuthManager: with the switch off and OIDC forced on (env OS_OIDC_PROVIDER_ENABLED=true or config oidcProvider: true), getMcpResourceMetadataUrl() still answers the URL, while plugin-auth mounts /.well-known/oauth-protected-resource only when the switch is on. The service probe is resolveService, so no crash when the service is absent. A3 confirmed: the only in-process entry is HttpDispatcher.handleMcp via dispatch(), and the system-principal answers (404/501/transport) are pinned unchanged. A4 measured: before, a createHonoApp host answered anonymous POST /mcp 501 (no service) and 404 (switch off), identical to the standalone host; after, 401 on both hosts. Hold-off confirmed, not widened: handleMcpSkillRequest is a public document classified as an outside family with no 401 expectation, so there is no anonymous decision to move. Clause-2 no holds: no request that was refused is now accepted, and no export moves.",
"tests": "BEFORE (base 9822b053e, new pins run against unfixed src and dist). runtime: 10 red, 24 green. Standalone anonymous POST/GET/DELETE /mcp answered 501 (no service) and 404 (switch off); unit anonymous rows answered 404 and 501. http-conformance (createHonoApp): 2 red, 10 green, 'expected 501 to be 401' and 'expected 404 to be 401'; the hosted/standalone parity rows were green, so both doors were wrong identically. Signed-in controls (200/501/404) green. AFTER at 84585ac0e, runtime rebuilt and ablation-dist-preflight 'refuseAnonymousMcpCaller' exit 0: runtime targeted 4 files 163 passed; conformance 2 files 36 passed (including hono-head-as-get). FULL at b4159a3c3 (after merging origin/main d537a318b): pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 gave 418 files, 6583 passed, 19 skipped; http-conformance 17 files 226 passed; http-conformance typecheck exit 0; spec check:generated exit 0 ('All 14 generated artifacts are up to date'). runtime typecheck exit 1 there: check:test-typecheck 'src/http-dispatcher.mcp.test.ts: 20 type error(s), ledger records 11' (TS18048 from the 9 new reads). Fixed in a3fba9c8a, which adds non-null assertions in the new block only. At a3fba9c8a: runtime typecheck exit 0 ('check:test-typecheck: OK'), and http-dispatcher.mcp.test.ts plus mcp-anonymous-first.hono.integration.test.ts 34 passed. ABLATION (one-time, not kept), via scripts/ablation-replace.mjs (anchor 1 to 0, blob 55cde0fd74e2 changed) in WRAP mode with a trap, each leg rebuilt, ablation-dist-preflight exit 0 before its run. Leg 1, order (anonymous condition guarded by switch-on and service-present, which restores the old answers): runtime 10 red / 24 green, conformance 2 red / 10 green, 501 and 404 vs 401. Leg 2, advertisement gate ('served' forced true): runtime 2 red / 32 green, conformance 2 red / 10 green, a WWW-Authenticate 'Bearer realm=ObjectStack MCP, resource_metadata=...' where null was expected. Both directions as predicted. Restore: blob == HEAD 55cde0fd74e2, git diff HEAD 0 bytes, rebuild exit 0, preflight --absent for both markers exit 0, fix marker present exit 0, reruns 34 and 12 passed, git status --porcelain empty. A2 probe: a throwaway plugin-auth test file, run once (4 passed) and deleted, never committed.",
"gates": "Local subset at final head a3fba9c8a. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 65 commands vs merge base d537a318b; every one exited 0, each code captured before any pipe. --ran: 'Run reconciliation — 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN' (RAN-EXIT=0). The first round at 84585ac0e also had 64x 0, plus check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET, no workspace dist); it measured exit 0 in the final round once the closure had been built. Roster families under changed dirs, each exit 0: node scripts/check-changeset-fixed.mjs, pnpm check:authz-resolver, pnpm check:error-code-casing, pnpm check:filter-alias-parity, pnpm check:route-ledger-census. Lint blind spot: pnpm exec eslint --no-inline-config over the 5 changed .ts files exit 0, no output; repo-wide pnpm lint left to CI. Dogfood authz census (the Dogfood gate does not run on PRs): authz-probe-blind-spot.test.ts + authz-conformance.test.ts, 2 files 108 passed; the mcp.ts census controls are unchanged from base (2 / 1 / 4). No platform-checklist run: no exported symbol was renamed or deleted. CI check-runs on a3fba9c8a, read once at PR open: 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped; in_progress: Type Check · workspace, Type Check · debt ledger, Type Check · consumer gates, Type Check · source gates, Lint · repo & tooling gates, Lint · PM dispatch-gates, Lint · ESLint, Lint · CI, release & engine gates, Lint · docs & code guards, Lint · scoped · (query-options erasure, tooling self-tests, slot lookup & corpus), Governed Surface Queue Guard, Check Changeset, Check PR Size, Check Documentation Links, Flag docs affected by code changes, The card this PR closes must claim this branch, Part-of PR must not also close its card, No other open PR may claim the same single-writer path, filter. Test Core and Build Core had not been scheduled at read time. Not waited on.",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "2 relay strokes. (1) pr_create via fleet-write repository_dispatch: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos/objectstack-ai/objectstack/issues/24308/assignees; read back as identical bytes, assignee zhuangjianguo. (2) this os-dev-report comment: POST /repos/objectstack-ai/objectstack/issues/24251/comments. Each stroke also POSTs one repository_dispatch to the board. git push (5 pushes of the branch) is not a REST write.",
"line_budget": "527 (additions 499 + deletions 28, 6 files; PR #24308 reads the same)",
"files_changed": [
".changeset/24251-mcp-anonymous-first.md",
"packages/qa/http-conformance/src/hono-mcp-anonymous-first.conformance.test.ts",
"packages/runtime/src/dispatcher-body-after-anonymous-floor.hono.integration.test.ts",
"packages/runtime/src/domains/mcp.ts",
"packages/runtime/src/http-dispatcher.mcp.test.ts",
"packages/runtime/src/mcp-anonymous-first.hono.integration.test.ts"
],
"deviations": [
"File surface: one edit outside mcp.ts and the new pins. A comment in packages/runtime/src/dispatcher-body-after-anonymous-floor.hono.integration.test.ts (a runtime test beside the handler, comment only) said /mcp's anonymous decision follows its enabled and available checks; this change made that false, so it was corrected.",
"Merged origin/main (d537a318b: #24285 runtime and #24274 objectql/spec) into the branch before opening the PR, per AGENTS.md section 10; no conflicts, no lockfile change. The full suites ran after the merge.",
"First-round overlap: the tail of check:type-check-debt (about 10 s after 09:45:13Z) overlapped the start of ablation leg 1. Its closure build had already released the lock, and its re-measure program is the repo root, whose tsconfig excludes packages/. The final round at a3fba9c8a reran it cleanly (OK).",
"Attribution: commits carry only the model-free trailer pair as dispatched. The PR body ends with the AGENTS.md session-URL footer form, not the harness's suggested footer text.",
"One status line was sent to the parent via SendMessage in answer to the PM's probe (not a GitHub write)."
],
"open_questions": [],
"out_of_scope_findings": [
"carrier: none (承接者:无) · noted, not filed · content/docs/ai/connect-mcp.mdx's callout and the OS_MCP_SERVER_ENABLED row of content/docs/deployment/environment-variables.mdx say the endpoint 404s with the switch off. That stays true for a credentialed caller, the reader those lines address; an anonymous probe now gets 401. Not edited: outside this claim's file surface. If a seat wants the anonymous case spelled out, it is a two-line docs-only edit. Recorded in the PR's Acceptance notes."
]
}
REWORK (patch round 1, docs only) → PR #24308 comment 6248974571 · domain:cli seat 1 · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-18T10:55Z · the code is accepted; the published 404 / 501 sentences in connect-mcp.mdx and environment-variables.mdx are qualified in this PR (claim amended in place; declared on #201636248986418).
{
"issue": 24251,
"status": "done",
"branch": "claude/issue-24251-mcp-anonymous-first",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24308",
"session": "session_01SYEJqDL3NXxrJRBVqaMu9T (subagent: the dispatching PM session's id)",
"premise_still_valid": true,
"summary": "Patch round 1 (PM review 6248974571). New head 63bf9f463, one docs-only commit on the same branch; the code, tests and changeset are unchanged from a3fba9c8a. Qualified exactly the three published spots the PM named, so each says what both callers see. (1) content/docs/ai/connect-mcp.mdx callout: with the switch off, a caller with an API key or token gets 404; an anonymous caller still gets 401 first, with no OAuth metadata advertised. (2) The same page's troubleshooting rows. `404` on /api/v1/mcp: a client with a key or token sees the 404; an anonymous client, an OAuth client before it signs in included, sees a plain 401 with no resource_metadata in WWW-Authenticate. `501 Not Implemented`: the same split. (3) content/docs/deployment/environment-variables.mdx, the OS_MCP_SERVER_ENABLED row: the endpoint answers 404 to a caller with a key or token and 401 to an anonymous one. No new section, no code change, no changeset. Worktree recreated from origin/claude/issue-24251-mcp-anonymous-first (a3fba9c8a); no second branch. The out-of-scope docs note from the first report is now fixed in this PR, so that item is withdrawn. PR body not edited. Change for the PM to write into it: the Acceptance-notes bullet 'Docs, not edited' should now read that the three sentences are qualified in 63bf9f463.",
"tests": "No code or test change in this round, so no test was rerun; the round-0 readings at a3fba9c8a stand (runtime full 418 files / 6583 passed / 19 skipped at b4159a3c3, typecheck OK at a3fba9c8a, http-conformance 17 files / 226 passed). No ablation: docs only.",
"gates": "Local, at 63bf9f463, in a fresh worktree. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 94 commands vs merge base d537a318b (8 paths, +505/-33). First pass: 85 exit 0, and 9 refused on a missing build (exit 3: check:doc-formula-expressions, check:doc-security-posture, check:skill-examples, check:docs-transcript-drift, check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure, check:sourcemap-no-sources-content; and spec check:docs exit 1 on 'packages/spec/json-schema is missing'). check:type-check-debt's closure build (turbo build of ./packages/*, under the lock) then built the closure, and all 9 were rerun: each exit 0. --ran over the final record: 'Run reconciliation — 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN' (exit 0). The docs families, each exit 0: pnpm check:doc-authoring; node scripts/docs-audit/check-affected-docs.mjs; node scripts/docs-audit/check-drift-comment.mjs; pnpm check:docs-audit-scope; node scripts/check-issue-citations.mjs; pnpm check:issue-citations; pnpm --filter @objectstack/spec run check:docs (on the rerun). Links: node scripts/check-adr-links.mjs exit 0 ('769 relative link destination(s) under docs/adr/ resolve'); the lychee link check is CI's 'Check Documentation Links': success. The derivation flagged the tree as 7 commits behind origin/main 30da04f8e, with 2 gate inputs changed upstream (scripts/check-single-authz-resolver.mjs, scripts/slot-lookup-baseline.json). Not merged: no conflict, no main-caused red, so those two families were read at their branch copies, and CI reads the merge ref. CI on 63bf9f463, read once: 41 check-runs, 36 success, 5 skipped. Among them: TypeScript Type Check, Lint & Repo Gates, Test Core (1/6), Build Core, Check Changeset, Governed Surface Queue Guard and Check Documentation Links all success.",
"mcp_calls": "0 — no MCP GitHub tool was called",
"api_writes": "This round: 1 relay stroke. POST /repos/objectstack-ai/objectstack/issues/24251/comments, this follow-up os-dev-report, plus its repository_dispatch to the board. git push of 63bf9f463 is not a REST write. Round 0's 2 strokes (pr_create + assignee; the first report comment 6248951819) are unchanged.",
"line_budget": "538 (additions 505 + deletions 33, 8 files; the docs round adds +6/-5 in 2 files)",
"files_changed": [
".changeset/24251-mcp-anonymous-first.md",
"content/docs/ai/connect-mcp.mdx",
"content/docs/deployment/environment-variables.mdx",
"packages/qa/http-conformance/src/hono-mcp-anonymous-first.conformance.test.ts",
"packages/runtime/src/dispatcher-body-after-anonymous-floor.hono.integration.test.ts",
"packages/runtime/src/domains/mcp.ts",
"packages/runtime/src/http-dispatcher.mcp.test.ts",
"packages/runtime/src/mcp-anonymous-first.hono.integration.test.ts"
],
"deviations": [
"The first-report finding 'carrier: none' was wrong by the PM's ruling (a PR fixes published text it makes false); it is fixed in this PR at 63bf9f463.",
"The 9 gates that first refused for a missing build were rerun after the closure existed; the --ran record carries those rerun codes in place of the first refusals.",
"Not merged with origin/main (7 commits ahead of the branch's base): AGENTS.md section 10 allows a merge after the PR is open only on a conflict, a main-caused red or a queue removal, and none applies."
],
"open_questions": [],
"out_of_scope_findings": []
}
ACCEPT → PR #24308 comment 6249510271 · domain:cli seat 1 · session session_01SYEJqDL3NXxrJRBVqaMu9T · 2026-10-18T11:44Z · after patch round 1 (docs qualified at 63bf9f463); landing waits on every check of that head, Check Changeset re-running after the seat's body edit.
State: the card closed completed on the merge; pm:dispatched is removed in the stroke after this comment. The lane's open set after the merge is as expected (no card closed by a stray keyword).
Activity
objectstack-fleet commentedon Oct 18, 2026
Triage: p3 ·
area:access·domain:cli·pm:queue· D2's order binds · triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-18T04:52ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch.
origin/main8eccf519de:packages/runtime/src/domains/mcp.tshandleMcpRequest(:63) still checksisMcpServerEnabled()(:64) before its anonymous decision.401whatever the switch or the service.401answer shape), was p3.createHonoApphost. The signed-in404/501answers are unchanged.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Serial note ·
domain:cliseat 1 (seat post #6024) ·zhuangjianguo· sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T06:50Z · ⛔ not a claim; the card stayspm:queue.This card is serial behind #24245 (claim
6246689908, dispatched this round). Both editpackages/runtime/src/domains/mcp.ts: this card'shandleMcpRequest(:63onorigin/main0d34066ef), #24245'shandleMcpSkillRequest(:172).HEADare different defects with different fixes, so they are not folded. Same file ⇒ hard serial; the merge of runtime(mcp): on a createHonoApp host, HEAD /api/v1/mcp/skill answers 405 with Allow: GET while GET is served — the catch-all hands dispatch() the raw HEAD and handleMcpSkillRequest refuses every non-GET #24245 releases it.mcp.tsorcreateHonoApp's catch-all (packages/adapters/hono/src/index.ts). If it lands in the catch-all,HEADreachesdispatch()asGET, so this card'screateHonoApppin should be re-read against thatmain.handleMcpSkillRequesthas the same order (isMcpServerEnabled()first,:173), but/mcp/skillis served public, so D2's anonymous decision does not apply there. The dev confirms that rather than widening this card.6245834219fixes the scope: "an anonymous caller gets401whatever the switch or the service", pinned on the standalone composition and on acreateHonoApphost; "the signed-in404/501answers are unchanged".Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Claim: PM loop round 2 ·
domain:cliseat 1 · 2026-10-18T08:50ZSession:
session_01SYEJqDL3NXxrJRBVqaMu9TAccount:
zhuangjianguo(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24251-mcp-anonymous-firstWorktree:
objectstack-issue-24251Domain:
domain:cliSeat:
domain:cli#1(seat post #6024)Ruling-ref: triage
6245834219(D2's order binds this door: "an anonymous caller gets401whatever the switch or the service"; "Scope = the card's first Done-when arm, pinned on the standalone composition and on acreateHonoApphost. The signed-in404/501answers are unchanged.")File surface (read on
origin/main9822b053e; stop on a breach and explain it in the report):packages/runtime/src/domains/mcp.ts:handleMcpRequest(:63), whose anonymous decision becomes its first statement, ahead ofisMcpServerEnabled()(:64,404) and themcpservice (:68,501).HonoServerPluginplus the dispatcher plugin) and through acreateHonoApphost's own pipeline. Thehttp-conformancepackage'screateHonoAppconformance tests are the precedent home for the latter.patchchangeset for@objectstack/runtime.6248974571on PR fix(runtime): an anonymous /mcp caller is answered 401 ahead of the switch's 404 and the service's 501 #24308): the change makes published sentences false for an anonymous caller, so they are qualified in this PR:content/docs/ai/connect-mcp.mdx(the callout and the404/501troubleshooting rows) andcontent/docs/deployment/environment-variables.mdx(theOS_MCP_SERVER_ENABLEDrow). These aredomain:devxfiles, declared on [PM seat] domain:devx · seat 2 — 🟢 os-sales · session_016PVjjuVPte5XEJvaQx1E4t · serial (maintainer) #20163. A comment inpackages/runtime/src/dispatcher-body-after-anonymous-floor.hono.integration.test.tsthat this change made false was corrected in the first round.handleMcpSkillRequest(served public, so D2's anonymous decision does not reach it); the signed-in answers;packages/spec/src/**.Container & model:
M,mode:subagent,model: default tier(dispatch-gates.mjs --tier --repo objectstack-ai/objectstackover the surface: "Model tier — no path-derived mandate")Clause-②: no
Basis for the Clause-② line: only the order of refusals moves (an anonymous caller's
404/501become the401ADR-0138 D2 owes). No request that was refused before is accepted, and no export moves.Responsibility:
handleMcpRequestinpackages/runtime/src/domains/mcp.ts(this lane's) | none: the dispatcher's anonymous floor does not cover/mcp, which ADR-0138 D2 lists as outside the guest model | any anonymous HTTP caller ofPOST /api/v1/mcp, who learns whether the deployment runs MCP (a capability disclosure, not data; triage p3); no named user todayThread-read: 6246757147
Serial constraints cleared:
packages/runtime/src/domains/mcp.ts: runtime(mcp): on a createHonoApp host, HEAD /api/v1/mcp/skill answers 405 with Allow: GET while GET is served — the catch-all hands dispatch() the raw HEAD and handleMcpSkillRequest refuses every non-GET #24245 landed as9822b053e(PR fix(hono): on a createHonoApp host a HEAD is answered as its GET, so HEAD /api/v1/mcp/skill no longer answers 405 Allow: GET #24286) without touching this file, which releases the serial note6246757147. Re-priced after that landing: unaffected. The catch-all now dispatchesHEADasGET, which does not reach aPOST /mcp.mcp.tsor its tests (PR chore: version packages (next) #24168 is the Version Packages PR; only pre-mode changeset files match the scan).dispatcher-plugin.ts,api-endpoint-step.ts), [maintainer] refactor(rest): splitpackages/rest/src/rest-server.ts(13,701 lines, 70 commits in 30 days) along itsregister*Endpointsseams, one move-only slice per PR,/metaafter #22877 stage 2 #23050 13d (packages/rest), runtime: one internal request-to-ExecutionContext resolver for the platform's five hand-rolled copies (rest, runtime, service-settings, service-storage, plugin-hono-server) — not a published export (ruling C′ on #23128, p3) #23217 slice 3 (service-settings;packages/runtime'spackage.jsonandsecurity/request-execution-context.conformance.test.ts).Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{ "issue": 24251, "status": "done", "branch": "claude/issue-24251-mcp-anonymous-first", "pr": "https://github.com/objectstack-ai/objectstack/pull/24308", "session": "session_01SYEJqDL3NXxrJRBVqaMu9T (subagent: the dispatching PM session's id)", "premise_still_valid": true, "summary": "handleMcpRequest (packages/runtime/src/domains/mcp.ts) now takes its anonymous decision as its first statement (ADR-0138 D2), so an anonymous caller of /api/v1/mcp gets 401 UNAUTHENTICATED whatever OS_MCP_SERVER_ENABLED or the mcp service says. A caller the decision admits (user or system principal) keeps the switch's 404 and the service's 501 in their order. The predicate and the service-guard text are unchanged. The producer is that handler on both hosts, so the landing is the expected one. Zone 2 verdicts: A1 confirmed on 9822b053e (lines 64/68 ahead of 72-73). A2 answered with a shape: the 401 advertises RFC 9728 resource_metadata only when the switch is on AND an mcp service can take the request (the /discovery routes.mcp predicate) AND the auth service answers a URL; otherwise it is the plain API-key 401. Measured on the real AuthManager: with the switch off and OIDC forced on (env OS_OIDC_PROVIDER_ENABLED=true or config oidcProvider: true), getMcpResourceMetadataUrl() still answers the URL, while plugin-auth mounts /.well-known/oauth-protected-resource only when the switch is on. The service probe is resolveService, so no crash when the service is absent. A3 confirmed: the only in-process entry is HttpDispatcher.handleMcp via dispatch(), and the system-principal answers (404/501/transport) are pinned unchanged. A4 measured: before, a createHonoApp host answered anonymous POST /mcp 501 (no service) and 404 (switch off), identical to the standalone host; after, 401 on both hosts. Hold-off confirmed, not widened: handleMcpSkillRequest is a public document classified as an outside family with no 401 expectation, so there is no anonymous decision to move. Clause-2 no holds: no request that was refused is now accepted, and no export moves.", "tests": "BEFORE (base 9822b053e, new pins run against unfixed src and dist). runtime: 10 red, 24 green. Standalone anonymous POST/GET/DELETE /mcp answered 501 (no service) and 404 (switch off); unit anonymous rows answered 404 and 501. http-conformance (createHonoApp): 2 red, 10 green, 'expected 501 to be 401' and 'expected 404 to be 401'; the hosted/standalone parity rows were green, so both doors were wrong identically. Signed-in controls (200/501/404) green. AFTER at 84585ac0e, runtime rebuilt and ablation-dist-preflight 'refuseAnonymousMcpCaller' exit 0: runtime targeted 4 files 163 passed; conformance 2 files 36 passed (including hono-head-as-get). FULL at b4159a3c3 (after merging origin/main d537a318b): pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 gave 418 files, 6583 passed, 19 skipped; http-conformance 17 files 226 passed; http-conformance typecheck exit 0; spec check:generated exit 0 ('All 14 generated artifacts are up to date'). runtime typecheck exit 1 there: check:test-typecheck 'src/http-dispatcher.mcp.test.ts: 20 type error(s), ledger records 11' (TS18048 from the 9 new reads). Fixed in a3fba9c8a, which adds non-null assertions in the new block only. At a3fba9c8a: runtime typecheck exit 0 ('check:test-typecheck: OK'), and http-dispatcher.mcp.test.ts plus mcp-anonymous-first.hono.integration.test.ts 34 passed. ABLATION (one-time, not kept), via scripts/ablation-replace.mjs (anchor 1 to 0, blob 55cde0fd74e2 changed) in WRAP mode with a trap, each leg rebuilt, ablation-dist-preflight exit 0 before its run. Leg 1, order (anonymous condition guarded by switch-on and service-present, which restores the old answers): runtime 10 red / 24 green, conformance 2 red / 10 green, 501 and 404 vs 401. Leg 2, advertisement gate ('served' forced true): runtime 2 red / 32 green, conformance 2 red / 10 green, a WWW-Authenticate 'Bearer realm=ObjectStack MCP, resource_metadata=...' where null was expected. Both directions as predicted. Restore: blob == HEAD 55cde0fd74e2, git diff HEAD 0 bytes, rebuild exit 0, preflight --absent for both markers exit 0, fix marker present exit 0, reruns 34 and 12 passed, git status --porcelain empty. A2 probe: a throwaway plugin-auth test file, run once (4 passed) and deleted, never committed.", "gates": "Local subset at final head a3fba9c8a. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 65 commands vs merge base d537a318b; every one exited 0, each code captured before any pipe. --ran: 'Run reconciliation — 65 derived, 65 run, 0 NOT-MEASURED, 0 UNRUN' (RAN-EXIT=0). The first round at 84585ac0e also had 64x 0, plus check:dual-build-cjs-loads exit 3 (PREREQUISITE NOT MET, no workspace dist); it measured exit 0 in the final round once the closure had been built. Roster families under changed dirs, each exit 0: node scripts/check-changeset-fixed.mjs, pnpm check:authz-resolver, pnpm check:error-code-casing, pnpm check:filter-alias-parity, pnpm check:route-ledger-census. Lint blind spot: pnpm exec eslint --no-inline-config over the 5 changed .ts files exit 0, no output; repo-wide pnpm lint left to CI. Dogfood authz census (the Dogfood gate does not run on PRs): authz-probe-blind-spot.test.ts + authz-conformance.test.ts, 2 files 108 passed; the mcp.ts census controls are unchanged from base (2 / 1 / 4). No platform-checklist run: no exported symbol was renamed or deleted. CI check-runs on a3fba9c8a, read once at PR open: 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped; in_progress: Type Check · workspace, Type Check · debt ledger, Type Check · consumer gates, Type Check · source gates, Lint · repo & tooling gates, Lint · PM dispatch-gates, Lint · ESLint, Lint · CI, release & engine gates, Lint · docs & code guards, Lint · scoped · (query-options erasure, tooling self-tests, slot lookup & corpus), Governed Surface Queue Guard, Check Changeset, Check PR Size, Check Documentation Links, Flag docs affected by code changes, The card this PR closes must claim this branch, Part-of PR must not also close its card, No other open PR may claim the same single-writer path, filter. Test Core and Build Core had not been scheduled at read time. Not waited on.", "mcp_calls": "0 — no MCP GitHub tool was called", "api_writes": "2 relay strokes. (1) pr_create via fleet-write repository_dispatch: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos/objectstack-ai/objectstack/issues/24308/assignees; read back as identical bytes, assignee zhuangjianguo. (2) this os-dev-report comment: POST /repos/objectstack-ai/objectstack/issues/24251/comments. Each stroke also POSTs one repository_dispatch to the board. git push (5 pushes of the branch) is not a REST write.", "line_budget": "527 (additions 499 + deletions 28, 6 files; PR #24308 reads the same)", "files_changed": [ ".changeset/24251-mcp-anonymous-first.md", "packages/qa/http-conformance/src/hono-mcp-anonymous-first.conformance.test.ts", "packages/runtime/src/dispatcher-body-after-anonymous-floor.hono.integration.test.ts", "packages/runtime/src/domains/mcp.ts", "packages/runtime/src/http-dispatcher.mcp.test.ts", "packages/runtime/src/mcp-anonymous-first.hono.integration.test.ts" ], "deviations": [ "File surface: one edit outside mcp.ts and the new pins. A comment in packages/runtime/src/dispatcher-body-after-anonymous-floor.hono.integration.test.ts (a runtime test beside the handler, comment only) said /mcp's anonymous decision follows its enabled and available checks; this change made that false, so it was corrected.", "Merged origin/main (d537a318b: #24285 runtime and #24274 objectql/spec) into the branch before opening the PR, per AGENTS.md section 10; no conflicts, no lockfile change. The full suites ran after the merge.", "First-round overlap: the tail of check:type-check-debt (about 10 s after 09:45:13Z) overlapped the start of ablation leg 1. Its closure build had already released the lock, and its re-measure program is the repo root, whose tsconfig excludes packages/. The final round at a3fba9c8a reran it cleanly (OK).", "Attribution: commits carry only the model-free trailer pair as dispatched. The PR body ends with the AGENTS.md session-URL footer form, not the harness's suggested footer text.", "One status line was sent to the parent via SendMessage in answer to the PM's probe (not a GitHub write)." ], "open_questions": [], "out_of_scope_findings": [ "carrier: none (承接者:无) · noted, not filed · content/docs/ai/connect-mcp.mdx's callout and the OS_MCP_SERVER_ENABLED row of content/docs/deployment/environment-variables.mdx say the endpoint 404s with the switch off. That stays true for a credentialed caller, the reader those lines address; an anonymous probe now gets 401. Not edited: outside this claim's file surface. If a seat wants the anonymous case spelled out, it is a two-line docs-only edit. Recorded in the PR's Acceptance notes." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
REWORK (patch round 1, docs only) → PR #24308 comment
6248974571·domain:cliseat 1 · sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T10:55Z · the code is accepted; the published404/501sentences inconnect-mcp.mdxandenvironment-variables.mdxare qualified in this PR (claim amended in place; declared on #201636248986418).Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
os-dev-report
{ "issue": 24251, "status": "done", "branch": "claude/issue-24251-mcp-anonymous-first", "pr": "https://github.com/objectstack-ai/objectstack/pull/24308", "session": "session_01SYEJqDL3NXxrJRBVqaMu9T (subagent: the dispatching PM session's id)", "premise_still_valid": true, "summary": "Patch round 1 (PM review 6248974571). New head 63bf9f463, one docs-only commit on the same branch; the code, tests and changeset are unchanged from a3fba9c8a. Qualified exactly the three published spots the PM named, so each says what both callers see. (1) content/docs/ai/connect-mcp.mdx callout: with the switch off, a caller with an API key or token gets 404; an anonymous caller still gets 401 first, with no OAuth metadata advertised. (2) The same page's troubleshooting rows. `404` on /api/v1/mcp: a client with a key or token sees the 404; an anonymous client, an OAuth client before it signs in included, sees a plain 401 with no resource_metadata in WWW-Authenticate. `501 Not Implemented`: the same split. (3) content/docs/deployment/environment-variables.mdx, the OS_MCP_SERVER_ENABLED row: the endpoint answers 404 to a caller with a key or token and 401 to an anonymous one. No new section, no code change, no changeset. Worktree recreated from origin/claude/issue-24251-mcp-anonymous-first (a3fba9c8a); no second branch. The out-of-scope docs note from the first report is now fixed in this PR, so that item is withdrawn. PR body not edited. Change for the PM to write into it: the Acceptance-notes bullet 'Docs, not edited' should now read that the three sentences are qualified in 63bf9f463.", "tests": "No code or test change in this round, so no test was rerun; the round-0 readings at a3fba9c8a stand (runtime full 418 files / 6583 passed / 19 skipped at b4159a3c3, typecheck OK at a3fba9c8a, http-conformance 17 files / 226 passed). No ablation: docs only.", "gates": "Local, at 63bf9f463, in a fresh worktree. node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 94 commands vs merge base d537a318b (8 paths, +505/-33). First pass: 85 exit 0, and 9 refused on a missing build (exit 3: check:doc-formula-expressions, check:doc-security-posture, check:skill-examples, check:docs-transcript-drift, check:dts-closure, check:dual-build-cjs-loads, check:lean-entry-closure, check:sourcemap-no-sources-content; and spec check:docs exit 1 on 'packages/spec/json-schema is missing'). check:type-check-debt's closure build (turbo build of ./packages/*, under the lock) then built the closure, and all 9 were rerun: each exit 0. --ran over the final record: 'Run reconciliation — 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN' (exit 0). The docs families, each exit 0: pnpm check:doc-authoring; node scripts/docs-audit/check-affected-docs.mjs; node scripts/docs-audit/check-drift-comment.mjs; pnpm check:docs-audit-scope; node scripts/check-issue-citations.mjs; pnpm check:issue-citations; pnpm --filter @objectstack/spec run check:docs (on the rerun). Links: node scripts/check-adr-links.mjs exit 0 ('769 relative link destination(s) under docs/adr/ resolve'); the lychee link check is CI's 'Check Documentation Links': success. The derivation flagged the tree as 7 commits behind origin/main 30da04f8e, with 2 gate inputs changed upstream (scripts/check-single-authz-resolver.mjs, scripts/slot-lookup-baseline.json). Not merged: no conflict, no main-caused red, so those two families were read at their branch copies, and CI reads the merge ref. CI on 63bf9f463, read once: 41 check-runs, 36 success, 5 skipped. Among them: TypeScript Type Check, Lint & Repo Gates, Test Core (1/6), Build Core, Check Changeset, Governed Surface Queue Guard and Check Documentation Links all success.", "mcp_calls": "0 — no MCP GitHub tool was called", "api_writes": "This round: 1 relay stroke. POST /repos/objectstack-ai/objectstack/issues/24251/comments, this follow-up os-dev-report, plus its repository_dispatch to the board. git push of 63bf9f463 is not a REST write. Round 0's 2 strokes (pr_create + assignee; the first report comment 6248951819) are unchanged.", "line_budget": "538 (additions 505 + deletions 33, 8 files; the docs round adds +6/-5 in 2 files)", "files_changed": [ ".changeset/24251-mcp-anonymous-first.md", "content/docs/ai/connect-mcp.mdx", "content/docs/deployment/environment-variables.mdx", "packages/qa/http-conformance/src/hono-mcp-anonymous-first.conformance.test.ts", "packages/runtime/src/dispatcher-body-after-anonymous-floor.hono.integration.test.ts", "packages/runtime/src/domains/mcp.ts", "packages/runtime/src/http-dispatcher.mcp.test.ts", "packages/runtime/src/mcp-anonymous-first.hono.integration.test.ts" ], "deviations": [ "The first-report finding 'carrier: none' was wrong by the PM's ruling (a PR fixes published text it makes false); it is fixed in this PR at 63bf9f463.", "The 9 gates that first refused for a missing build were rerun after the closure existed; the --ran record carries those rerun codes in place of the first refusals.", "Not merged with origin/main (7 commits ahead of the branch's base): AGENTS.md section 10 allows a merge after the PR is open only on a conflict, a main-caused red or a queue removal, and none applies." ], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
ACCEPT → PR #24308 comment
6249510271·domain:cliseat 1 · sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T11:44Z · after patch round 1 (docs qualified at63bf9f463); landing waits on every check of that head,Check Changesetre-running after the seat's body edit.Generated by Claude Code
objectstack-fleet commentedon Oct 18, 2026
Landed: PR #24308 →
889bf122e·domain:cliseat 1 ·zhuangjianguo· sessionsession_01SYEJqDL3NXxrJRBVqaMu9T· 2026-10-18T12:12Zmerged: true, merge commit889bf122e, merged 2026-10-18T12:12Z through the merge queue (enqueued 2026-10-18T11:47Z)origin/main889bf122eis an ancestor oforigin/main, with a single parent (squash). The 8 files and +505 / −33 match what was reviewed at63bf9f463gh-readonly-queue/main/*pr-24308*head remains6247892917→ report6248951819→ REWORK (patch round 1, docs only)6248974571on PR fix(runtime): an anonymous /mcp caller is answered 401 ahead of the switch's 404 and the service's 501 #24308, pointer6248993457→ report6249482696→ ACCEPT6249510271on PR fix(runtime): an anonymous /mcp caller is answered 401 ahead of the switch's 404 and the service's 501 #24308 → pointer6249516486. Nothing was filed from the review: the round-0 docs note was fixed inside the PR, and no out-of-scope note stayed open.completedon the merge;pm:dispatchedis removed in the stroke after this comment. The lane's open set after the merge is as expected (no card closed by a stray keyword).Generated by Claude Code