Skip to content

The MCP prompt bridge's listSkills reads the un-merged registry listing, so a runtime meta PUT never reaches prompts #8328

Description

@os-zhuang

Split out of #7654 (symptom 2) at dispatch time, per the lane ruling recorded there: the metadata half landed in packages/metadata-protocol, and this half lives in packages/mcp, which is domain:cli's lane. Filed unassigned with no domain:* label so triage routes it.

Blocked-by: #7654

The fact

PUT /api/v1/meta/skill/{name} with {active:true} returns 200, and the flip is not reflected over MCP prompts.

The two surfaces read different sources:

  • The MCP prompt bridge's listSkills calls metadataService.list('skill') — the registry/package listing, which carries no sys_metadata store overrides. Located in packages/mcp/src/mcp-server-runtime.ts as listSkills: async () => (await metadataService.list('skill')) ?? [] (anchor on the symbol, not the line — the file has moved under several merges).
  • The HTTP meta list goes through protocol.getMetaItems, which merges the store overrides.

So a runtime override lands on the getMetaItems path and never on the path the bridge consumes.

What #7654 changed, and what it did NOT change

#7654 fixed the metadata half only — the duplicate-row defect on GET /api/v1/meta/skill. Measured there: getMetaItems ran two different merge implementations that disagreed about a package-less row, and the MetadataService merge now uses the same package-aware resolution as the overlay merge.

That fix does not change the bridge's answer at all, which is the question the triage comment on #7654 asked to have settled before this was treated as one bug. It does not, because the bridge never calls getMetaItems; it calls metadataService.list directly, one layer below where any overlay merging happens. The two symptoms shared a description, not a cause.

What #7654 does provide is the thing this card needs: a getMetaItems read that now returns exactly one correctly-merged row per skill name. That is why this is Blocked-by rather than independent — the merged read is worth pointing the bridge at only once it is correct, and as of #7654 it is.

Coordination — read this before starting

⚠️ #6504 is open, assigned, and rewrites this same expression for a different reason. It quotes (await metadataService.list('skill')) ?? [] as one of its two measured consumers and proposes adding a listDiagnosed counterpart to IMetadataService so a known-partial answer stops presenting as a complete one.

Both cards change the same call with different intents, which is the failure mode where each side is individually green and the merged result is wrong. Whoever picks this up should re-read #6504's outcome first: if listDiagnosed has landed, express this change against the new method rather than the old one.

The design question this card should answer, not dodge

The framing #7654 opened with still stands and is the real deliverable here: what is the single source of truth for skill rows that both the meta HTTP list and the MCP prompt bridge read from? Pointing listSkills at the merged read is the obvious repair, but it is worth stating explicitly whether the bridge should consume getMetaItems (protocol layer, overlay-aware) or whether the override merge belongs lower down in the MetadataService so every consumer of list() gets it — the second is a wider contract change and would subsume this card and part of #6504.

A needs_decision exit naming that fork is a legitimate outcome; answering "just point the bridge at getMetaItems" without addressing it leaves the two paths structurally divergent and merely hides the symptom.

Reproduction

  1. Boot a showcase with a packaged skill.
  2. PUT /api/v1/meta/skill/{name} with {active:true} — 200.
  3. Query the skill over MCP prompts — the active:true flip is not reflected.

Source

Symptom 2 of #7654, itself extracted from the QA run #7627 (framework 92f26f7, console 6314e87f). The original observation was recorded as unconfirmed; symptom 1 has since been confirmed and fixed, this half has not been independently root-caused beyond the source divergence above.


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions