Repository navigation
Commit 467a01f
fix(mcp): agent_prompt reads agents through the protocol read GET /meta/agent/NAME serves (#31565)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"}
-->
_Requested by **Leehom** · [project
thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1XBK9EgGPfyQH8cn35jeTJe3)_
domain:server#6 · os-project-manager · session_01FEkQ5kNHZKmo4BTWcwTdNs
Fixes #31514
Clause-②: yes (widening)
Metadata-protocol: none
## 维护者速读
- 改了什么:MCP stdio 服务器的 `agent_prompt` 现在通过协议层的按名读取(就是 `GET
/api/v1/meta/agent/NAME` 用的那个 `getMetaItem`)取 agent;决定是否列出
`agent_prompt` 的 agent 名单也改读协议的合并列表(`GET /api/v1/meta/agent` 用的
`getMetaItems`)。没有协议服务的宿主照旧读内核 metadata 服务,每个宿主只有一个读者:用协议还是用 metadata
服务,由新的 `agentReadOf` 按宿主一次决定(两种读取都有才用协议),名单和按名读取都用这一个决定。
- 为什么改:包里自带的 agent 只在协议读取的注册表里,内核 metadata 服务没有它的副本。以前 MCP 读 metadata
服务,所以只装了包内 agent 的环境根本不列出 `agent_prompt`,也取不到这个 agent,而
`/meta/agent/NAME` 能取到。两个“按名取 agent”的入口答案不一致。
- 风险与回滚:环境自己的 agent 照旧可用;找不到时仍回答 `Error: Agent "X" not found`;协议读取报 503
时仍是原来的不可用提示、不给指令。`McpMergedMetadataRead` 类型多了一个可选成员
`getMetaItem`(公开类型放宽)。回滚即还原本 PR。
- 先看:`packages/mcp/src/mcp-server-runtime.ts` 的
`agentReadOf`(新,唯一的决定)、`readAgentByName`(新)、`buildAgentPromptResult`
里改用它的一行、`readAgentRoster` 改读 `mergedDiagnosedList`。
- 两分钟验证:`pnpm --filter @objectstack/mcp exec vitest run
src/mcp-server-runtime.agent-prompt-advertised.test.ts -t "one reader
with GET"`,预期 8 个用例通过:只有包内 agent 时列出 `agent_prompt`、取到的指令与协议按名读取一致、环境
agent 照旧、未知名字照旧报未找到、503 照旧报不可用、只有列表没有按名读取的宿主两边都读 metadata
服务、其他异常照旧抛出、协议在场时名单降级照旧列出。
- 需要你做的:无,席位落地。
## What changes
`packages/mcp/src/mcp-server-runtime.ts`, the long-lived (stdio)
server's prompt bridge:
- **`agentReadOf` (new, module-private): the one decision per host.** It
answers the host's read when that read carries both the merged listing
(`getMetaItems`) and the by-name read (`getMetaItem`), and `undefined`
(the metadata service) otherwise. The roster and the get both take this
one answer, so a host whose read has the listing but no by-name read
reads the metadata service for both, rather than advertising a package
agent that the get answers "not found" (the #28940 defect again;
contract review should-fix). `bridgePrompts` computes it once as
`agentRead` and passes it to both.
- **`readAgentByName` (new, module-private).** Where `agentReadOf` picks
the host's `mergedRead` (the `protocol` service, which `plugin.ts`
already hands `bridgePrompts`) carries a by-name read, the agent is read
through `getMetaItem({ type: 'agent', name })`, the read `GET
/api/v1/meta/agent/NAME` serves, and its `item` is the agent. A throw
coded `SERVICE_UNAVAILABLE` (the protocol's 503 when the metadata
service behind it could not be read and nothing resolved) is the #6055
outage; any other throw travels, as a throw of the metadata service's
read always did. Where it picks the metadata service, its
`getDiagnosed`/`get` is read exactly as before. One reader per host,
never a lookup that tries both (order ruling).
- **`buildAgentPromptResult`** takes the protocol as an optional fourth
argument and reads through `readAgentByName`. Its three answers are
unchanged: the instructions plus any UI context; `Error: Agent "X" not
found` on a miss; the `SERVICE_UNAVAILABLE` sentence on an outage, with
no instructions.
- **`readAgentRoster` (#28940)** reads
`mergedDiagnosedList(metadataService, agentReadOf(mergedRead),
'agent')`: the protocol's merged listing, `GET /api/v1/meta/agent`'s
read, with the metadata service's `listDiagnosed` completeness verdict
on top, the composition the skill listing already uses. It missed
package-shipped agents too (measured below), so per the ruling it moves
to the same reader; a boot whose only agent a package ships now
advertises `agent_prompt` and names that agent in the argument
description.
- **`McpMergedMetadataRead`** gains an optional `getMetaItem(request)`
member. It is the type of `MCPServerRuntime.bridgePrompts`' second
parameter, so it is in the published `index.d.ts`; that is why
`Clause-②` reads `yes (widening)`. No `plugin.ts` change: the object it
passes is the `protocol` service, which has both reads.
- **Changeset** `.changeset/31514-agent-prompt-protocol-read.md`:
`@objectstack/mcp` patch, `Clause-②: yes (widening)`,
`Metadata-protocol: none`.
Naming is the bare name `/meta/agent/NAME` serves today; nothing here
anticipates #31527's package-scoped address.
## Tests
**Unit,
`packages/mcp/src/mcp-server-runtime.agent-prompt-advertised.test.ts`**
(the existing `agent_prompt` harness, a new top-level `describe`, driven
over the real MCP wire with an in-memory transport). The metadata double
holds no copy of the package agent; the protocol double serves it
through `getMetaItems` and `getMetaItem`.
1. A boot whose only agent a package ships lists `agent_prompt`, and the
`agentName` description names `"pkg_helper"`.
2. `prompts/get` serves exactly the instructions the protocol's
`getMetaItem({ type: 'agent', name })` answers; the metadata double is
never asked for that agent.
3. CONTROL: an environment agent is listed and served as before (with
its UI context line).
4. An unknown name answers `[{ role: 'user', content: { type: 'text',
text: 'Error: Agent "nope" not found' } }]`, today's miss.
5. A by-name read thrown `{ code: 'SERVICE_UNAVAILABLE', status: 503 }`
answers one `user` message starting `Error: SERVICE_UNAVAILABLE — `,
carrying no instructions, and warns `agent prompt refused`.
6. A host whose read is `{ getMetaItems }` only (no `getMetaItem`) reads
the metadata service for both: the argument names the environment agent
`"support_triage"` and not `pkg_helper`, the listing is never asked for
`agent`, and the environment agent is served as before.
7. A by-name read thrown with any other code (`INTERNAL`) rejects
`prompts/get` with that error, serves no instructions, and does not warn
`agent prompt refused`.
8. With the protocol present, a degraded roster verdict and no agent
named keeps the prompt listed with no example, and warns with the
verdict's errors.
**Real kernel,
`packages/qa/dogfood/test/mcp-stdio-package-object-discovery.test.ts`**
(the existing MCP stdio package-discovery harness, a new top-level
`describe` per boot shape and registration). A package that ships one
agent and no object is registered through the `manifest` service on a
real `ObjectKernel` with `MetadataPlugin` + `ObjectQLPlugin`, and the
real `MCPServerPlugin` stdio server is driven over JSON-RPC. On all four
rows (`env_local` and no `environmentId`, registered during the boot and
installed after it): `prompts/list` advertises `agent_prompt` naming the
package agent, and `prompts/get` serves the instructions the kernel's
own `protocol.getMetaItem({ type: 'agent', name })` answers; a miss
keeps its answer. The `boot` helper's `pkg` parameter is narrowed to `{
artifact(): unknown }`, its only use, and `Booted` gains `rpc`.
<details><summary>Red first, on main dabef0f (cases 1–5) and on
959a0c6 (cases 6–8)</summary>
- Unit: 4 failed, 9 passed. Case 1 `expected [ 'case_management' ] to
include 'agent_prompt'`; cases 2, 4, 5 `MCP error -32602: Prompt
agent_prompt not found` (the prompt was never advertised). The control
passed.
- Dogfood: 8 failed, 80 passed, all four rows. In every row the test's
first assertion, that the kernel's `protocol.getMetaItem` serves the
package agent's instructions, passed; then `prompts/get` and
`prompts/list` answered `-32601 Method not found`: with no agent in the
roster and no skill, the server advertised no prompts at all.
- Cases 6–8, added after the contract review on 959a0c6: case 6 red
(`expected 'Name of the agent to load (e.g. "pkg_…' to contain
'"support_triage"'`: the roster read the protocol listing while the get
stayed on the metadata service); 7 and 8 green there, as they pin
behaviour 959a0c6 already had (their ablations below show they can go
red).
</details>
<details><summary>Ablations (scripts/ablation-replace.mjs on committed
heads 2946313 and 9d09636; unit tests import sources, no dist on the
path; each restored with blob == HEAD and an empty git diff
HEAD)</summary>
- **The get read back on the metadata service.** In
`buildAgentPromptResult`, `readAgentByName(metadataService, protocol,
agentName)` → `diagnosedGet(metadataService, 'agent', agentName)`. Case
2 goes red (`expected [ { role: 'user', … } ] to deeply equal [ { role:
'assistant', … } ]`: the package agent answered "not found"), and case
5. Cases 1, 3, 4 stay green. Restored.
- **The roster back on the metadata service.** In `readAgentRoster`,
`mergedDiagnosedList(metadataService, mergedRead, 'agent')` →
`diagnosedList(metadataService, 'agent')`. Cases 1, 2, 4, 5 go red (the
prompt is not advertised); the control stays green. Restored.
- **The shared gate split again (9d09636).** In `agentReadOf`, the `&&
typeof mergedRead.getMetaItem === 'function'` clause deleted. Case 6
goes red (`expected 'Name of the agent to load (e.g. "pkg_…' to contain
'"support_triage"'`); the other 15 stay green. Restored.
- **Every by-name throw read as an outage (9d09636).** In
`readAgentByName`, the `SERVICE_UNAVAILABLE` test and `throw err`
replaced by an unconditional degraded answer. Case 7 goes red (`promise
resolved "{ messages: [ { role: 'user', …(1) } ] }" instead of
rejecting`). Restored.
- **A degraded roster read as complete (9d09636).** In
`readAgentRoster`, `if (read.degraded) return { state: 'unknown',
errors: read.errors };` deleted. Case 8 goes red, and the existing
metadata-only degraded pin with it. Restored.
</details>
## Acceptance notes
- **The HTTP surface (`/api/v1/mcp`) has no `agent_prompt`.** Its
per-request server bridges skill prompts only (`buildMcpBridge` in
`packages/runtime`), so this PR has no second surface to point.
- **Degraded roster.** The roster's completeness verdict is still the
metadata service's `listDiagnosed`, as for skill prompts (#8328): a
degraded verdict with no agent named keeps the prompt listed with no
example, as before (case 8 pins it with the protocol present).
- **Governed surface.** None of the four paths is in the register (`node
scripts/pm/check-governed-merges.mjs --branch HEAD`, see Verification).
- **Branch.** The harness branch was restarted from `origin/main` after
#31464 merged; its remote had been deleted on merge, so the first push
created it fresh and no force push was needed.
## Verification
All readings are on head `863aa0f5`: the fix commits `2946313b` and
`9d09636b` (the review's should-fix), with `origin/main` merged three
times, never rebased (`959a0c61`, `72b76773`, `863aa0f5`; none of the
merged commits touches `packages/mcp` or `packages/qa/dogfood`). Each
exit code is captured before any pipe.
- **mcp suite**: `pnpm --filter @objectstack/mcp test` (through
`scripts/pm/os-verify-lock.sh`) exit 0 on `72b76773`, Tests 915 passed
(915); `pnpm --filter @objectstack/mcp exec vitest run` exit 0 again on
`863aa0f5`, Tests 915 passed (915).
- **dogfood file**: after `pnpm --filter "@objectstack/dogfood^..."
build` (exit 0), `pnpm --filter @objectstack/dogfood exec vitest run
test/mcp-stdio-package-object-discovery.test.ts`, exit 0, Tests 88
passed (88), on `72b76773`.
- **Typecheck**: `pnpm --filter @objectstack/mcp typecheck`, exit 0; the
test layer holds at its recorded debt (6 files, 53 errors, 8 pinned
signatures, none new). `pnpm --filter @objectstack/dogfood typecheck`,
exit 0.
- **Lint**: `eslint packages/mcp
packages/qa/dogfood/test/mcp-stdio-package-object-discovery.test.ts
--no-inline-config` (scoped to the touched paths), exit 0.
- **i18n**: `pnpm check:i18n-coverage`, exit 0 after the last merge.
- **Derived gate union**: `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` derived 67 families on `72b76773`,
all 67 exit 0 (`check:dual-build-cjs-loads` included); the last merge
added one family, `check:adr-symbol-anchors`, run on `863aa0f5`, exit 0.
`--ran`: "68 derived famil(ies) accounted for — 68 run, 0 NOT-MEASURED".
- **Governed surface**: `node scripts/pm/check-governed-merges.mjs
--branch HEAD`, exit 0, "NOT governed"; 395 changed lines, under the
3000 threshold.
The `test:repo` project and `Test Core` shards are CI's and this PR's
check-runs carry them. The dogfood shards are skipped on `pull_request`
and run only in the merge queue, so the PR's check-runs do not cover the
dogfood file; the local run above does.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(not touched)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (scoped to the touched
paths)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (not touched)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English. (the 维护者速读 block is
the lane's required Chinese summary)
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01FEkQ5kNHZKmo4BTWcwTdNs
---
_Generated by [Claude
Code](https://claude.ai/code/session_01FEkQ5kNHZKmo4BTWcwTdNs)_
<!-- signed: domain:server#6 · os-project-manager ·
session_01FEkQ5p3T5YWhLjurajudnQ -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent ed4b832 commit 467a01f
4 files changed
Lines changed: 381 additions & 14 deletions
File tree
- .changeset
- packages
- mcp/src
- qa/dogfood/test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
232 | 232 | | |
233 | 233 | | |
234 | 234 | | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
| 342 | + | |
| 343 | + | |
| 344 | + | |
| 345 | + | |
| 346 | + | |
| 347 | + | |
| 348 | + | |
| 349 | + | |
| 350 | + | |
| 351 | + | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
| 358 | + | |
| 359 | + | |
| 360 | + | |
| 361 | + | |
| 362 | + | |
| 363 | + | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
| 425 | + | |
| 426 | + | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
526 | 526 | | |
527 | 527 | | |
528 | 528 | | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
529 | 537 | | |
530 | 538 | | |
531 | 539 | | |
| |||
698 | 706 | | |
699 | 707 | | |
700 | 708 | | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
| 730 | + | |
| 731 | + | |
| 732 | + | |
| 733 | + | |
| 734 | + | |
| 735 | + | |
| 736 | + | |
| 737 | + | |
| 738 | + | |
| 739 | + | |
| 740 | + | |
| 741 | + | |
| 742 | + | |
| 743 | + | |
| 744 | + | |
| 745 | + | |
| 746 | + | |
| 747 | + | |
| 748 | + | |
| 749 | + | |
| 750 | + | |
| 751 | + | |
| 752 | + | |
| 753 | + | |
| 754 | + | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
| 760 | + | |
| 761 | + | |
| 762 | + | |
701 | 763 | | |
702 | 764 | | |
703 | 765 | | |
| |||
727 | 789 | | |
728 | 790 | | |
729 | 791 | | |
| 792 | + | |
| 793 | + | |
| 794 | + | |
| 795 | + | |
| 796 | + | |
730 | 797 | | |
731 | 798 | | |
732 | 799 | | |
733 | 800 | | |
734 | 801 | | |
| 802 | + | |
735 | 803 | | |
736 | 804 | | |
737 | 805 | | |
738 | 806 | | |
739 | 807 | | |
740 | 808 | | |
741 | | - | |
| 809 | + | |
742 | 810 | | |
743 | 811 | | |
744 | 812 | | |
| |||
799 | 867 | | |
800 | 868 | | |
801 | 869 | | |
802 | | - | |
803 | | - | |
804 | | - | |
805 | | - | |
806 | | - | |
807 | | - | |
| 870 | + | |
| 871 | + | |
| 872 | + | |
| 873 | + | |
| 874 | + | |
| 875 | + | |
| 876 | + | |
| 877 | + | |
| 878 | + | |
| 879 | + | |
| 880 | + | |
| 881 | + | |
808 | 882 | | |
809 | | - | |
| 883 | + | |
| 884 | + | |
| 885 | + | |
| 886 | + | |
810 | 887 | | |
811 | 888 | | |
812 | | - | |
| 889 | + | |
813 | 890 | | |
814 | 891 | | |
815 | 892 | | |
| |||
1708 | 1785 | | |
1709 | 1786 | | |
1710 | 1787 | | |
1711 | | - | |
| 1788 | + | |
| 1789 | + | |
| 1790 | + | |
| 1791 | + | |
| 1792 | + | |
1712 | 1793 | | |
1713 | 1794 | | |
1714 | 1795 | | |
1715 | | - | |
| 1796 | + | |
1716 | 1797 | | |
1717 | 1798 | | |
1718 | 1799 | | |
| |||
1743 | 1824 | | |
1744 | 1825 | | |
1745 | 1826 | | |
1746 | | - | |
1747 | | - | |
| 1827 | + | |
| 1828 | + | |
| 1829 | + | |
1748 | 1830 | | |
1749 | 1831 | | |
1750 | 1832 | | |
| |||
0 commit comments