Repository navigation
fix(mcp): stdio lists agent_prompt only on a boot that holds an agent (#28940) - #29201
Conversation
The long-lived server registered agent_prompt unconditionally, with an agentName argument suggesting "ask" / "build". On a boot that registers no agent (every open-framework boot) prompts/list advertised a prompt nothing could serve, and each get answered a successful prompt whose one message was `Error: Agent "X" not found`. The agent roster is now read at bridge time, beside the skill snapshot: no agent and a complete read => not advertised (a get is the SDK's -32602 unknown-prompt error) and one boot info line says why; agents => advertised, argument examples drawn from the registered names, a miss byte-identical to before (#6055); degraded or thrown read => advertised as before with no example names, plus a warn. Claude-Session: https://claude.ai/code/session_01ADHkmTPD7FhpC9y956LJJ9 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check8 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 12 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin eead7f4b5d932036e53b280241d86c80a7f7a031 && git checkout eead7f4b5d932036e53b280241d86c80a7f7a031
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 692e0dc8b936ad37311352868083d78745c76a21 9126fc31d4e306ffa339b9e6dc02984c350d0c1d && git checkout -B drift-repro 692e0dc8b936ad37311352868083d78745c76a21 && git merge --no-ff 9126fc31d4e306ffa339b9e6dc02984c350d0c1d
node scripts/docs-audit/affected-docs.mjs --json 692e0dc8b936ad37311352868083d78745c76a21 |
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 席位核对 · ACCEPT · PR #29201 · 2026-11-02T09:18ZHead-sha: 维护者速读(席位终稿)
核对六项
|
Requested by Steve · project thread
Fixes #28940
Clause-②: no
Metadata-protocol: none
维护者速读
agent_prompt;参数说明里的示例名改为取自已注册的 agent,不再写死 "ask" / "build"。agent_prompt,照说明填 "ask" 得到一条「成功」的提示,内容是Error: Agent "ask" not found,模型会把它当指令读。packages/mcp/src/mcp-server-runtime.ts的bridgePrompts开头(是否注册的分支)与readAgentRoster;packages/mcp/src/mcp-server-runtime.agent-prompt-advertised.test.ts。pnpm --filter @objectstack/mcp exec vitest run src/mcp-server-runtime.agent-prompt-advertised.test.ts,预期 8/8 通过。What changes
bridgePrompts(the long-lived server the stdio transport serves) reads the agent roster from the metadata service before registeringagent_prompt, at the same moment as the existing skill snapshot:agent_promptis not registered.prompts/listdoes not carry it, aprompts/geton it is the SDK's own unknown-prompt error (JSON-RPC-32602), and one bootinfoline says why. A boot with no agent and no skill therefore advertises nopromptscapability, which is the mcp-stdio-fail-closed c3 (follow-on): the stdio transport now answers, but registers ZERO tools — advertisestoolscapability,tools/list→ -32601 #8034 capability-equals-served rule.agentNamedescription draws its examples from the registered names (first two) instead of the fixede.g. "ask", "build". The served body and the miss textError: Agent "X" not foundare unchanged (mcp-server-runtime:443 把 metadata outage 答成 'Agent "X" not found' —— fail-closed 但误述,#5840 定性后的 cli 侧跟进 #6055 kept that text byte-stable on purpose).warnsays the roster could not be read and points at the loaders.packages/mcp/README.mdPrompts section says whenagent_promptis listed..changeset/28940-mcp-agent-prompt-advertised.md(@objectstack/mcppatch).Why
The same file's skill projection is built on "declared IS served": a skill that cannot be served is not listed.
agent_promptwas the opposite shape on a boot with no agent. It was a prompt advertised where nothing can serve it, with an argument teaching two names that resolve to nothing, and a miss answered as a success.Route check against recorded decisions (the delta asked for it):
buildAgentPromptResultdocblock): it fixed outage vs miss inside the get and kept the miss text byte-identical. Untouched. The new roster read applies the same outage-is-not-absence rule one step earlier.skills/objectstack-aitells authorsmetadata_assistantis "not vocabulary" while the platform's own Studio app pinsdefaultAgent: 'metadata_assistant'#14461 (the old comment on the description): its concern was that the example an author copies is a canonical id, never a retired alias. Examples drawn from registered agents keep that by construction, because a registered agent's name is its id. The old[#14461]comment is replaced by one pointing atagentNameArgumentDescription, which carries the reasoning.agentis code-only, with no runtime create, so a roster read at bridge time cannot be outgrown by a runtime write.The two transports after this change:
agent_promptonly on a boot holding an agent./api/v1/mcp,handleHttpRequest→registerSkillPrompts) serves skill prompts only and neveragent_prompt. That is unchanged.On a boot with no agent, the open-framework case the card measured, the two transports now agree.
Tests
packages/mcp/src/mcp-server-runtime.agent-prompt-advertised.test.ts, 8 cases over the real wire (an in-memory transport into the sameMcpServerthe stdio transport attaches to). Red first on unmodified code: 7/8 failed, and each failure was the defect itself (expected [ 'agent_prompt', 'case_management' ] to not include 'agent_prompt'; a get on the no-agent boot returnedError: Agent "ask" not foundas a result; the description read(e.g. "ask"…;capabilities.promptswas{ listChanged: true }with nothing to list). The 8th case, served body plus byte-stable miss, is the preservation pin and passes on both trees.packages/mcp/src/mcp-stdio-tools.test.ts, the mcp-stdio-fail-closed c3 (follow-on): the stdio transport now answers, but registers ZERO tools — advertisestoolscapability,tools/list→ -32601 #8034 "bridged server" double:listnow returns one agent foragent. That test asserts the assembly really serves all three primitives. Its double held no agent and no skill, so after this change it would serve no prompts.packages/mcp/src/__tests__/mcp-server-runtime.test.ts, "survives a metadata service that cannot list skills": its replacementlistkept answering[]foragent. It now keeps the double's agents, because the case is about the skill read alone.pnpm --filter @objectstack/mcp exec vitest run: 54 files, 652/652 passed.pnpm --filter @objectstack/mcp typecheck: exit 0.Ablations (one per negative pin, each restored with a blob-hash match)
Every mutation went through
scripts/ablation-replace.mjs(the anchor hit once, the replacement landed, then a restore). The suite imports./mcp-server-runtime.jsfrom source, so no dist rebuild was involved. After the run,git diff HEADwas empty and the file blob was back at09f63b589939.if (roster.state === 'none' && false)) turned 4 red: no-agent list, get is-32602, boot line, no-capability case.e.g. "ask", "build"description put back turned 2 red: agents-boot description and degraded description.none) turned 1 red: degraded keeps the prompt.noneturned 1 red: throw keeps the prompt.Acceptance notes
agent_promptand HTTP still does not. That divergence predates this card and is not in its acceptance. Noted, not filed.start(), beside the skill snapshot. An agent provider that registers after that point in the same boot would leaveagent_promptwithheld on stdio until restart. No such provider exists in this repository (no open package registers an agent). Whether a cloud host registers its platform agents later thanstart()cannot be measured from here, and the stdio transport is a local-host surface. Raised as an open question in the report, not worked around.Verification
All readings at head
9126fc31.Derived gate list and lint
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 59 commands. On the first sweep 56 exited 0. Three exited 3 because a prerequisite was missing, which is NOT MEASURED, not a pass:node scripts/check-plugin-teardown-shape.mjs --self-testneeded its pinned positive-control commit. Aftergit fetch origin 621a4876…it exited 0 (48 cases).pnpm check:dual-build-cjs-loadsandpnpm check:lean-entry-closureread built output. After a fullpnpm build(117/117 tasks) both exited 0.pnpm check:dts-closurewas rerun on the same build and exited 0 (293/293 declaration files present).--ranreconciliation: 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN.pnpm check:startup-registry-verdictexited 0, reporting none recording a verdict the boot can contradict.pnpm lint(eslint . --no-inline-config, whole repo) printed no diagnostic. The four changed.tsfiles linted alone also exited 0.Human Minimum
packages/specholds no runtime logic. (not touched)pnpm lintpasses.main, one pull request.packages/spectouched: n/a.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Generated by Claude Code