Skip to content

fix(mcp): read skill prompts from the protocol's merged metadata listing - #8724

Merged
qq9340100 merged 3 commits into
mainfrom
claude/issue-8328-mcp-bridge-merged-skill-read
Aug 14, 2026
Merged

fix(mcp): read skill prompts from the protocol's merged metadata listing#8724
qq9340100 merged 3 commits into
mainfrom
claude/issue-8328-mcp-bridge-merged-skill-read

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Part of #8328

Implements the maintainer ruling recorded on #8328 (option 3, 2026-08-13 ~23:50Z): point the MCP prompt bridge's skill read at the protocol layer's merged read, mark the trap at its source, and archive the contract question unscheduled.

⚠️Part of, not Fixes — merging this does not close the card. The card's own three-step reproduction runs through a second skill read that lives outside this PR's declared file surface. Measured, not assumed; see "The half this PR does not fix" below.

The defect, measured on a booted showcase

origin/main @ 3508678, real showcase, PUT /api/v1/meta/skill/repro_skill with {active:true} → 200:

surfaceanswer
GET /api/v1/meta/skill (protocol getMetaItems)serves the row, active:true
MCP prompts/list{"prompts":[]}

Re-queried minutes later, well past the DatabaseLoader 60s TTL and the list cache: still []. Structural, not a cache artifact.

The cause is the layer: the bridge read IMetadataService.list('skill'), which answers from the registry and its loaders and performs nosys_metadata overlay merge. That merge lives one layer up, in the protocol's getMetaItems.

What changed

  1. bridgePrompts takes an optional merged read (McpMergedMetadataRead, a duck-typed getMetaItems seam) and takes its skill items from it. plugin.ts resolves the protocol service and passes it — the assembly is the only place that can see both services, since the runtime is handed its collaborators and holds no service registry.

  2. IMetadataService.list() presents a known-partial answer as a complete one — the #5840 shape on the plural read #6504's completeness verdict is preserved, and this is the part that needed care.getMetaItems cannot express it: it swallows a MetadataService read failure into its own catch and returns a merged list either way. So items come from the merged read and the verdict is asked of listDiagnosed alongside it — the same composition this file already uses for objectstack://objects. Taking getMetaItems' answer alone would have silently spent the contract, sending a known-partial prompt surface back to presenting as complete.

  3. No fallback to the un-merged listing when the merged read throws. That would answer registry rows in the shape of merged ones — this exact defect, restored silently at the moment an overlay is most likely to be the thing being missed. getMetaItems already answers registry-only for the one benign case (sys_metadata not provisioned yet).

  4. Ruling item 2 — the trap is marked at its source.IMetadataService.list()'s TSDoc now states that the read performs no overlay merging, names getMetaItems as the merged read, and says why there is no local symptom. Comment-only, verified mechanically: the non-comment diff of that file is empty.

  5. Ruling item 3 — the contract question is archived as Should the sys_metadata overlay merge move down into MetadataService.list(), so every consumer gets merged rows? #8722 (finding + domain:metadata, unassigned, back-linked). Deduped against open issues by keyword and file path first; no existing card covers it.

Ruling item 4 is respected: the override merge is not pushed down into MetadataService. Nothing outside packages/mcp changed except the authorized comment.

Live before/after — same persistent DB, same overlay row

Boot, PUT the skill, restart, read the bridge's own boot line:

BEFORE: [MCP] Bridged 0 skill prompts
AFTER: [MCP] Bridged 1 skill prompts

This also settles the dispatch's flagged assumption — that getMetaItems might not be reachable from the bridge's construction context. It is reachable.Protocol service registered (MetadataProtocolPlugin) lands well before the MCP plugin's start(), and the "protocol service not available" fallback line was never emitted (0 occurrences). No wiring beyond what the assembly already had.

The half this PR does not fix

There are two skill reads behind MCP prompts, not one:

  • packages/mcp/src/mcp-server-runtime.tsbridgePrompts — the long-lived (stdio) server. Fixed here.
  • packages/runtime/src/domains/mcp.tsbuildMcpBridge.listSkills — the HTTP surface at /api/v1/mcp, built per request by the runtime. Reads meta.list('skill') directly, with no diagnosed wrapper at all. Not fixed here — outside the declared file surface.

The card's reproduction and the pnpm dev banner both point at the HTTP endpoint, so after this PR that reproduction still returns {"prompts":[]} — verified against the running server with the fix in place. The ruling's decision (read from the merged layer rather than pushing the merge down) is unaffected; only its scope estimate — "entirely inside packages/mcp" — turned out to cover half the surface. The remaining half applies the same decision at a second call site in a different lane and is left for the PM to route.

Tests

New: packages/mcp/src/mcp-server-runtime.merged-skill-read.test.ts — 8 cases pinning the layer the bridge reads from, that the service's own listing contributes no items, the enveloped and bare-array shapes, both directions of the active flip, the no-fallback rule, and that the #6504 degraded verdict still reaches the operator.

Reverse verification, direction predicted before running: 6 red / 2 green. Measured 5 red / 3 green — the prediction was wrong. The extra green was a defective assertion, not a third invariant: the case asserted list() was never called, which is true in both directions because diagnosedList prefers listDiagnosed whenever the service has it. Rewritten to assert provenance directly (service holds a projectable skill, merged read holds none, nothing is bridged); re-measured 6 red / 2 green, with the two green being the two predicted invariants. Both numbers and the reason are recorded in the test file header.

Verification

All at final head d23577894, after the last commit:

  • pnpm --filter @objectstack/mcp test18 files / 193 tests passed
  • pnpm --filter @objectstack/mcp --filter @objectstack/spec typecheck — pass
  • pnpm check:type-check-debt (--re-measure, 33 ledger entries, 585s) — pass, "none above its recorded number"
  • check:nul-bytes, check:cross-package-test-inputs (+ the direct script), check:changeset-gate-self-tests, check:merge-driver, check:spec-parsed-alias, check:type-source-resolution, check-adr-0087-registration.mjs, check:query-options-erasure, check:type-check-coverage — all pass

The gate set was re-derived from the actual diff rather than taken from the dispatch list: because ruling 2's comment lands in packages/spec, seven gate families applied that the dispatch prompt did not name (the spec-scoped ones plus the new-test-file conventions). They are included above.


Generated by Claude Code

Claudeand others added 3 commits August 14, 2026 15:40
The MCP prompt bridge read `IMetadataService.list('skill')` — one layer
below where the `sys_metadata` overlay merge happens — so a runtime
`PUT /api/v1/meta/skill/<name>` returned 200 and never reached MCP
prompts, while `GET /api/v1/meta/skill` served the overridden row from
the protocol's `getMetaItems`.
The bridge now takes an optional merged read (the protocol service,
wired by the plugin assembly that can see both services) and takes its
items from there, keeping #6504's completeness verdict by asking
`listDiagnosed` for it alongside — `getMetaItems` cannot express that
verdict, so taking its answer alone would have spent the contract.
A merged read that throws does NOT fall back to the un-merged listing:
that would answer registry rows in the shape of merged ones at exactly
the moment an overlay is most likely to be missed.
Also marks the trap at its source: `IMetadataService.list()` now states
that it performs no overlay merging (comment only).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NaS1PAHJcPfAA2acnV53Tn
The case asserted that `list()` was never called, which is true in both
directions — `diagnosedList` prefers `listDiagnosed` whenever the service
has it, so the un-merged path never touches `list` either. Measured 5
red / 3 green against a predicted 6 / 2; the third green was this
defective assertion, not a third invariant.
Now asserts provenance directly: the service holds a projectable skill,
the merged read holds none, and nothing is bridged. Re-measured 6 / 2.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NaS1PAHJcPfAA2acnV53Tn
@vercel

vercelBot commented Aug 14, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 14, 2026 4:30pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/mcp, @objectstack/spec.

111 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/actions-as-tools.mdx(via @objectstack/mcp)
  • content/docs/ai/agents.mdx(via @objectstack/mcp, @objectstack/spec)
  • content/docs/ai/connect-mcp.mdx(via @objectstack/mcp)
  • content/docs/ai/index.mdx(via @objectstack/mcp)
  • content/docs/ai/natural-language-queries.mdx(via @objectstack/mcp)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/mcp, @objectstack/spec)
  • content/docs/automation/approvals.mdx(via @objectstack/spec)
  • content/docs/automation/connectors.mdx(via @objectstack/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/cli.mdx(via @objectstack/spec)
  • content/docs/deployment/environment-variables.mdx(via @objectstack/mcp)
  • content/docs/deployment/tenancy-modes.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via @objectstack/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/kernel/services.mdx(via @objectstack/spec)
  • content/docs/permissions/authorization.mdx(via @objectstack/mcp, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/permissions/system-context.mdx(via packages/mcp, packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/mcp, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/mcp, @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/apps.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx(via @objectstack/mcp, @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/spec)
  • content/docs/releases/v17.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 14, 2026
@qq9340100
qq9340100 marked this pull request as ready for review August 14, 2026 16:54
@qq9340100
qq9340100 added this pull request to the merge queueAug 14, 2026
Merged via the queue into main with commit ff4ba6aAug 14, 2026
28 checks passed
@qq9340100
qq9340100 deleted the claude/issue-8328-mcp-bridge-merged-skill-read branch August 14, 2026 17:13
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@qq9340100