Split out of #16530 (Side observations, first bullet). That card is a domain:services p1 about plugin-auth passing an option @better-auth/oauth-provider 1.7.2 no longer reads; this is an unrelated packages/cli printing face, and making the p1 carry it would pull a whole CLI package verification run into an auth-plugin PR.
Reproducible defect
os dev prints two MCP blocks from two different origin resolvers, and only one of them knows about the canonical origin. Measured on origin/main 5e53d73d:
| printer |
origin it uses |
packages/cli/src/utils/format.ts:816 — the ➜ MCP: row of the ready banner |
resolved base (link()), which honours OS_AUTH_URL |
packages/cli/src/commands/dev.ts:503-508 — the 🤖 MCP server — connect a coding agent block |
the child's listen URL |
dev.ts:504-505:
const base =
typeof msg.url === 'string' && msg.url ? msg.url.replace(/\/+$/, '') : `http://localhost:${actual}`;
msg.url is the objectstack:listening message the serve child emits once its HTTP server is up — the bound socket, by construction. OS_AUTH_URL never enters this expression, and the whole block (Endpoint, Skill, Connect) is built from that one base.
Reported observation from #16530, on hotcrm 789a7324 with objectstack dev -p 4001 and OS_AUTH_URL=https://localhost:4443 behind a TLS reverse proxy: the ➜ MCP: line says https://localhost:4443/… and the block below it says http://localhost:4001/…, in the same boot output.
Why it matters
The Connect line is not documentation, it is a command the reader is meant to paste. Pasting it registers an MCP entry against an origin that (a) is not the origin the discovery documents advertise and (b) on a TLS-fronted deployment is not reachable from the agent at all. The two blocks disagreeing in the same output also makes the correct line look like the typo.
Scoped deliberately narrow: this is about which origin the hint is built from. The Endpoint/Skill/Connect text itself is right.
Where
packages/cli/src/commands/dev.ts:503-508 — the block, and the base expression above it.
packages/cli/src/utils/format.ts:816 and its link() base — the resolver that already gets this right; the reference implementation, not a second thing to change.
packages/cli/src/commands/serve-auth-base-url-diagnostic.test.ts — the existing pins on the OS_AUTH_URL → BETTER_AUTH_URL → OS_BASE_URL precedence chain, including the set-but-empty case. Whatever this fix reuses must not re-derive that chain by hand.
Acceptance
- With
OS_AUTH_URL set, all three lines of the connect block print the canonical origin, and the ➜ MCP: row and the block agree — assert on both rows of one captured boot output, not on the block alone. Two printers that disagree cannot be pinned by reading one of them.
- With
OS_AUTH_URL unset, the block still prints the listen origin (including the auto-shifted-port case 3000 busy → 3001 the surrounding code exists to handle). This is the control: a fix that just hardcodes the canonical origin breaks the common local case.
- The precedence chain is reused from the existing resolver, not reimplemented — in particular the set-but-empty
OS_AUTH_URL behaviour already pinned in serve-auth-base-url-diagnostic.test.ts must not change.
Filed by the triage seat, unassigned. Related: #16530 (the p1 this came out of), #16399 (URL normalisation in getAuthIssuer / getMcpResourceUrl — a different layer, and the values in #16530 were well-formed).
Split out of #16530 (Side observations, first bullet). That card is a
domain:servicesp1 aboutplugin-authpassing an option@better-auth/oauth-provider1.7.2 no longer reads; this is an unrelatedpackages/cliprinting face, and making the p1 carry it would pull a whole CLI package verification run into an auth-plugin PR.Reproducible defect
os devprints two MCP blocks from two different origin resolvers, and only one of them knows about the canonical origin. Measured onorigin/main5e53d73d:packages/cli/src/utils/format.ts:816— the➜ MCP:row of the ready bannerlink()), which honoursOS_AUTH_URLpackages/cli/src/commands/dev.ts:503-508— the🤖 MCP server — connect a coding agentblockdev.ts:504-505:msg.urlis theobjectstack:listeningmessage the serve child emits once its HTTP server is up — the bound socket, by construction.OS_AUTH_URLnever enters this expression, and the whole block (Endpoint,Skill,Connect) is built from that onebase.Reported observation from #16530, on hotcrm
789a7324withobjectstack dev -p 4001andOS_AUTH_URL=https://localhost:4443behind a TLS reverse proxy: the➜ MCP:line sayshttps://localhost:4443/…and the block below it sayshttp://localhost:4001/…, in the same boot output.Why it matters
The
Connectline is not documentation, it is a command the reader is meant to paste. Pasting it registers an MCP entry against an origin that (a) is not the origin the discovery documents advertise and (b) on a TLS-fronted deployment is not reachable from the agent at all. The two blocks disagreeing in the same output also makes the correct line look like the typo.Scoped deliberately narrow: this is about which origin the hint is built from. The
Endpoint/Skill/Connecttext itself is right.Where
packages/cli/src/commands/dev.ts:503-508— the block, and thebaseexpression above it.packages/cli/src/utils/format.ts:816and itslink()base — the resolver that already gets this right; the reference implementation, not a second thing to change.packages/cli/src/commands/serve-auth-base-url-diagnostic.test.ts— the existing pins on theOS_AUTH_URL→BETTER_AUTH_URL→OS_BASE_URLprecedence chain, including the set-but-empty case. Whatever this fix reuses must not re-derive that chain by hand.Acceptance
OS_AUTH_URLset, all three lines of the connect block print the canonical origin, and the➜ MCP:row and the block agree — assert on both rows of one captured boot output, not on the block alone. Two printers that disagree cannot be pinned by reading one of them.OS_AUTH_URLunset, the block still prints the listen origin (including the auto-shifted-port case3000 busy → 3001the surrounding code exists to handle). This is the control: a fix that just hardcodes the canonical origin breaks the common local case.OS_AUTH_URLbehaviour already pinned inserve-auth-base-url-diagnostic.test.tsmust not change.Filed by the triage seat, unassigned. Related: #16530 (the p1 this came out of), #16399 (URL normalisation in
getAuthIssuer/getMcpResourceUrl— a different layer, and the values in #16530 were well-formed).