Skip to content

mcp(contract): advertise _meta.category on all three servers and assert it in checkAdvertisedMetadata #10038

Description

@JSONbored

⚠️ Definition of Done: this issue must be completed in full, in a single PR. Do not split this
work across multiple PRs, and do not defer any Deliverable below to a follow-up issue. A PR that
satisfies only some of the Deliverables, stubs a required test, or leaves a checkbox
partially-done does NOT resolve this issue and will be closed.

Context

packages/loopover-contract/src/tool-definition.ts:14 describes category as something the servers —
plural — already publish over the wire:

/** Categories a tool can belong to, mirroring the ids the servers already advertise as
 *  `_meta.category` and the stdio CLI groups its `tools` output by. */

Exactly one server does. grep -rn "_meta" src packages --include='*.ts' finds the marker at three
places, and only one of them attaches a category:

  • Remote, src/mcp/server.ts:838: _meta: { category: advertised.category },
  • Stdio proxy, packages/loopover-mcp/bin/loopover-mcp.ts:2570:
    _meta: { ...(tool._meta ?? {}), transport: "proxied" }, — this preserves whatever the remote sent,
    so proxied tools carry a category by inheritance.
  • Stdio local, packages/loopover-mcp/bin/loopover-mcp.ts:913-928: the registerTool config carries
    title, description, inputSchema, outputSchema, annotations — and no _meta.
  • Miner, packages/loopover-miner/bin/loopover-miner-mcp.ts:132-141: same five fields, no _meta.

Two consequences, both observable from a client:

  1. One stdio server's tools/list is internally inconsistent. In gateway mode the ~110 proxied tools
    carry _meta.category and the ~110 locally-registered ones do not, so a client that groups by category
    renders half the surface as uncategorised — from a single server, for tools whose categories are
    declared side by side in the same registry file.
  2. The miner server publishes no categories at all, even though every one of its entries declares one
    (packages/loopover-contract/src/tools/miner.ts, miner-ops.ts).

src/mcp/server.ts:814 states the intent that is only half-delivered: the category "rides along as MCP
_meta.category, exposed in tools/list for clients (and mirrored by the CLI tools command)". The CLI
half works — STDIO_TOOL_DESCRIPTORS (packages/loopover-mcp/bin/loopover-mcp.ts:819) reads the category
straight from the contract — so only MCP clients see the gap.

Nothing catches it. checkAdvertisedMetadata (scripts/lib/validate-mcp/invariants.ts) compares title,
description, readOnlyHint and destructiveHint, and ListedTool does not even model _meta. That
check exists because "three servers could -- and did -- serve three different descriptions, titles and
annotation postures from one contract entry"; category is the one projected field it was never extended
to cover.

Requirements

  • registerStdioTool (packages/loopover-mcp/bin/loopover-mcp.ts:897) and registerMinerTool
    (packages/loopover-miner/bin/loopover-miner-mcp.ts:126) must advertise
    _meta: { category: advertised.category }, taken from the same projectToolDefinition(contract) result
    they already use for title/description/annotations — not from a second lookup and not from a
    literal.
  • ListedTool in scripts/lib/validate-mcp/invariants.ts must model _meta, and
    checkAdvertisedMetadata must fail when a listed tool's _meta.category is absent or differs from the
    registry's projected category, in the same "advertises X, registry says Y" style as the existing
    title/description/annotation failures.
  • The new check must run on all three surfaces, because checkAdvertisedMetadata is already called for
    each of them from validateSurface (test/contract/validate-mcp.test.ts:164).
  • What must NOT change: the stdio proxy's _meta merge at packages/loopover-mcp/bin/loopover-mcp.ts:2570
    must keep setting transport: "proxied" and must keep preserving any other _meta key the remote sent.
  • What must NOT change: the CLI tools command's grouping, which already reads the contract directly, and
    the TOOL_CATEGORIES ordering in packages/loopover-contract/src/tool-definition.ts:22.

⚠️ Required pattern: mirror src/mcp/server.ts:831-841, where _meta is spread from the projected
definition alongside title/description/annotations in one place. What does NOT satisfy this issue:
(a) adding _meta at each individual registerStdioTool/registerMinerTool call site instead of in the
shared wrapper — that is the per-call-site restatement the contract package exists to remove; (b)
introducing a new _meta key (e.g. auth or locality) that only one server sends, which recreates the
defect one field over; (c) shipping the registration change without extending checkAdvertisedMetadata,
which leaves the next regression invisible.

Deliverables

  • registerStdioTool advertises _meta: { category } from projectToolDefinition(contract).
  • registerMinerTool advertises _meta: { category } from the advertised value it already computes
    at packages/loopover-miner/bin/loopover-miner-mcp.ts:131.
  • checkAdvertisedMetadata in scripts/lib/validate-mcp/invariants.ts reports a failure for a listed
    tool whose _meta.category is missing, and for one whose _meta.category differs from the registry's.
  • Unit tests in the existing test/unit/validate-mcp-helpers.test.ts (do not create a second file for
    these helpers) covering both new branches: a listed tool with no _meta at all, and one whose
    _meta.category disagrees.
  • A regression test named for this bug asserting that a tools/list from the stdio server and from the
    miner server carries _meta.category for every listed tool, matching getToolDefinition(name).category.

All Deliverables above are required in a single PR. A PR that satisfies only some of them — for
example adding _meta to the stdio server but not the miner, or adding it to both without the
checkAdvertisedMetadata extension — does not resolve this issue.

Test Coverage Requirements

This repo enforces 99%+ Codecov patch coverage, branch-counted. vitest.config.ts's
coverage.include covers packages/loopover-mcp/bin/**/*.ts (line 120) and
packages/loopover-miner/bin/**/*.ts (line 100), so those two touched paths are measured and gated.
scripts/lib/validate-mcp/invariants.ts is not listed in coverage.include, so Codecov does not
gate the invariant change — the unit tests for it are still mandatory here, they are simply not what
the patch gate measures. Every branch added to checkAdvertisedMetadata (absent _meta, absent
_meta.category, mismatched _meta.category, agreeing _meta.category) needs a test.

Expected Outcome

Any MCP client asking any of LoopOver's three servers for tools/list gets the same _meta.category the
registry declares, for every tool — instead of categories on the remote's tools and on the stdio server's
proxied half only — and a server that stops sending it fails validate:mcp.

Links & Resources

  • packages/loopover-contract/src/tool-definition.ts:14 — the claim that the servers advertise _meta.category
  • src/mcp/server.ts:838 — the only registration that actually does
  • packages/loopover-mcp/bin/loopover-mcp.ts:913registerStdioTool's config, no _meta
  • packages/loopover-miner/bin/loopover-miner-mcp.ts:132registerMinerTool's config, no _meta
  • packages/loopover-mcp/bin/loopover-mcp.ts:2570 — the proxy's _meta merge, which must keep working
  • scripts/lib/validate-mcp/invariants.tsListedTool and checkAdvertisedMetadata

Metadata

Metadata

Assignees

No one assigned

    Labels

    gittensor:bugGittensor-scored bug fix — scores a 0.05x multiplier.help wantedExtra attention is needed

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions