Skip to content

orb(mcp): give loopover_admin_rotate_secret a contract entry and smoke-cover the admin #9657

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

After #9518/#9536/#9537 every MCP tool registers from @loopover/contract — with exactly one
exception. src/mcp/server.ts:2398 registers loopover_admin_rotate_secret with
inputSchema: adminRotateSecretShape and outputSchema: adminRotateSecretOutputSchema, both
declared locally in src/mcp/server.ts. Its four siblings (loopover_admin_get_config,
loopover_admin_write_config, loopover_admin_list_config_backups,
loopover_admin_trigger_redeploy) all have contract entries in
packages/loopover-contract/src/tools/admin-config.ts; rotate_secret does not, and
TOOL_CONTRACTS (packages/loopover-contract/src/tools/index.ts:152-155) lists only those four.

The reason it went unnoticed is the second half of this issue. The five admin tools are registered
only when isMcpAdminEnabled(env) is true (src/mcp/server.ts:2355), and
test/contract/validate-mcp.test.ts:181 boots the remote server with createTestEnv(), which does
not set LOOPOVER_MCP_ADMIN_ENABLED. So the admin category is never in tools/list during
validate:mcp: never diffed against the registry, never Ajv-compiled, never smoke-called, never
output-schema-validated. The validator's own strict assertion — "nothing is registered without a
contract entry" (test/contract/validate-mcp.test.ts:186-195) — is structurally unable to see the
one tool that violates it.

This is also why the #9522 catalog says "existing 4 admin tools migrate to the registry": the
fifth is invisible to every inventory the repo can currently produce.

Requirements

  1. adminRotateSecretShape and adminRotateSecretOutputSchema move into
    packages/loopover-contract/src/tools/admin-config.ts as AdminRotateSecretInput /
    AdminRotateSecretOutput, with an adminRotateSecretTool defineTool entry
    (category: "admin", auth: "mcp-admin", locality: "remote", availability: "selfhost",
    annotations: { readOnlyHint: false, destructiveHint: true }, matching
    adminWriteConfigTool/adminTriggerRedeployTool at admin-config.ts:93 and :161), added to
    TOOL_CONTRACTS, and the registration at src/mcp/server.ts:2398 reads from it. The local
    declarations are deleted.
  2. The schemas move verbatim. The accepted field set, every bound, and the returned shape are
    byte-identical before and after; test/unit/mcp-admin-rotate-secret-tool.test.ts must pass
    unchanged apart from import paths.
  3. test/contract/validate-mcp.test.ts's remote surface boots a second LoopoverMcp with
    LOOPOVER_MCP_ADMIN_ENABLED set, and runs the full validateSurface pass over it, so all five
    admin tools are diffed, compiled, smoke-called and output-validated like every other tool.
  4. The admin tools must answer (not throw) in that cold fixture environment — the capability
    registries are unfilled there, so each returns its configured: false result and the smoke call
    records a normal validated or declined outcome. Add SMOKE_ARGUMENT_OVERRIDES entries in
    scripts/lib/validate-mcp/overrides.ts only where the synthesized minimum is not semantically
    inert.
  5. No change to the rotation behaviour, the redeploy-companion protocol, the requireMcpAdmin gate,
    or isMcpAdminEnabled. This issue relocates schemas and adds test coverage; it touches no
    secret-handling code path.

⚠️ Required pattern: mirror adminTriggerRedeployTool in
packages/loopover-contract/src/tools/admin-config.ts:120-164 — the sibling tool with the same
auth level, the same capability-registry "not configured" result, and the same destructive
annotations. Leaving the schemas in src/mcp/server.ts and merely re-exporting them, or enabling
admin registration in the validator without adding the contract entry (which makes the run fail
rather than pass), do NOT satisfy this issue.

Deliverables

  • AdminRotateSecretInput, AdminRotateSecretOutput and adminRotateSecretTool in
    packages/loopover-contract/src/tools/admin-config.ts; adminRotateSecretTool in
    TOOL_CONTRACTS
  • adminRotateSecretShape / adminRotateSecretOutputSchema deleted from src/mcp/server.ts;
    the register("loopover_admin_rotate_secret", ...) call reads the contract
  • test/unit/mcp-admin-rotate-secret-tool.test.ts passes with no assertion changes
  • A new case in test/contract/validate-mcp.test.ts that boots the remote server with
    LOOPOVER_MCP_ADMIN_ENABLED truthy and runs validateSurface over it, asserting all five
    admin tool names are present and result.failures is empty
  • A test asserting listToolDefinitions({ category: ["admin"] }) has exactly 5 entries, so a
    sixth admin tool added without a contract entry fails

All Deliverables above are required in a single PR. A PR that satisfies only some of them — for
example adding the contract entry without the admin-enabled validator pass, so the next admin tool
can repeat the same escape — does not resolve this issue.

Test Coverage Requirements

99%+ Codecov patch coverage, branch-counted, applies: src/mcp/server.ts and
packages/loopover-contract/src/** are both in coverage.include (vitest.config.ts:55-100). Every
branch of the moved zod schema that has a distinct accept/reject outcome needs a case on both sides
(the existing test/unit/mcp-admin-rotate-secret-tool.test.ts rejection table is the model), and a
named regression test must assert getToolContract("loopover_admin_rotate_secret") is defined.
scripts/lib/validate-mcp/** and test/** are outside coverage.include.

Expected Outcome

Every tool any LoopOver MCP server registers has a contract entry, and npm run validate:mcp
exercises the admin category instead of silently skipping it — so the "nothing is registered without
a registry entry" assertion is true rather than unfalsifiable.

Links & Resources

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