Skip to content

feat(ams): migrate the AMS miner MCP server to @loopover/contract - #9542

Merged
JSONbored merged 3 commits into
mainfrom
feat/miner-mcp-contract-9536
Jul 28, 2026
Merged

feat(ams): migrate the AMS miner MCP server to @loopover/contract#9542
JSONbored merged 3 commits into
mainfrom
feat/miner-mcp-contract-9536

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Summary

Migrates the AMS miner MCP server's all 11 tools to @loopover/contract — the last of LoopOver's three MCP servers to gain a shared source of truth, and the only one with zero output schemas before this. Every handler returned unschematized text; now every tool emits structuredContent validated against a real schema.

Stacked on #9530 (base branch here is feat/loopover-contract-package-9517, not main) — this diff is additive on top of that PR and should merge after it. Only the commit feat(ams): migrate the AMS miner MCP server... is this issue's actual change; everything before it is #9530/#9528's history.

Defects the migration surfaced, not assumed going in

  1. loopover_miner_get_audit_feed's output schema initially double-wrapped the payload. collectEventLedgerAuditFeed already returns { repoFullName?, events }; my first pass at the handler wrapped that again in { events: ... }. The MCP SDK's own structured-output validation caught it immediately — "expected array, received object" — proof the schema is a real runtime contract, not documentation. Fixed by returning the aggregator's result directly.
  2. Three tools returned bare arrays as their text payload, which an MCP structuredContent value cannot be (must be a JSON object per spec). list_claims, list_plans, and governor_decisions all did JSON.stringify(store.listX(filter)) — a raw array. Verified this against the real handlers, not just the aggregators' declared TS types. Fixed with a small structured/text split in the result helper: structuredContent gets the object wrapper the schema requires ({ claims: [...] }), while the text block stays the original bare array — so every current text-parsing consumer keeps working unchanged.

Unified error envelope

Before this, only loopover_miner_get_audit_feed caught a store failure and returned isError: true; the other ten threw, so the identical failure (a missing or corrupted local SQLite file) surfaced as a clean structured result from one tool and a raw protocol error from the rest. All 11 now route through one withMinerToolErrorHandling wrapper with a closed error-code set (store_unavailable | unknown_error).

Two hand-maintained test surfaces replaced

  • test/unit/miner-mcp-scaffold.test.ts hardcoded the 11 tool names in an array — adding a tool needed a manual edit here too. Now asserts against listToolDefinitions({ locality: ["miner"] }) directly.
  • test/unit/miner-mcp-tool-docs-parity.test.ts regex-scraped server.registerTool("loopover_miner_...") calls out of the compiled dist bundle — a pattern my own earlier investigation for this epic flagged as fragile (a reformatted registration call silently breaks the guard, not the code; it also required a fresh npm run build:miner to see any real change). Now reads the registry directly: no build step, no regex, same list every server registers from.

Closes #9536

Scope

  • The PR title follows type(scope): short summary Conventional Commit format, for example fix(api): restore profile access checks.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked a currently open issue this PR resolves (e.g. Closes #123) — a linked open issue is required for every contributor PR.

Validation

  • git diff --check
  • npm run actionlint
  • npm run typecheck
  • npm run test:coverage locally (scoped — see note)
  • npm run test:workers
  • npm run build:mcp
  • npm run test:mcp-pack
  • npm run ui:openapi:check
  • npm run ui:lint
  • npm run ui:typecheck
  • npm run ui:build
  • npm audit --audit-level=moderate
  • New or changed behavior has unit/integration tests for new branches, fallback paths, and sanitizer boundaries

Green: full unit suite (23041 tests, 1208 files), build:miner (135 files, check-syntax at full count), test:miner-pack, test:miner-deployment-docs-audit, all 254 tests across every miner-mcp-* / contract-registry test file explicitly re-run, and typecheck.

If any required check was skipped, explain why:

  • Unchecked items cover surfaces this diff does not touch (no OpenAPI, worker, or UI changes). CI runs them in full.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests. (n/a — this server is local stdio only, no session/cookie surface)
  • API/OpenAPI/MCP behavior is updated and tested where needed. (MCP output behavior is additive: structuredContent is new on every tool; every text block is byte-identical to before, verified explicitly for the three array-returning tools.)
  • UI changes use live API data or real empty/error/loading states, not production mock/demo fallbacks. (n/a)
  • Visible UI changes include a UI Evidence section below. (n/a — no visible change)
  • Public docs/changelogs are updated where needed; changelogs are only edited for release-prep PRs.

Notes

Part of #9515. Blocked by #9517 (stacked on its branch). All three MCP servers now register from @loopover/contract for at least their initial tool sets — the miner server completely, the remote/stdio servers for the #9517 pilot batch, with the rest landing in #9518/#9537.

@loopover-orb

loopover-orb Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Caution

🛑 LoopOver review result - fixes required

Review updated: 2026-07-28 08:38:57 UTC

6 files · 1 AI reviewer · no blockers · CI failing · unstable

🛑 Suggested Action - Manual Review

Review summary
This completes the AMS miner MCP server's migration to `@​loopover/contract`, moving all 11 tool schemas out of the bin file into `packages/loopover-contract/src/tools/miner.ts` and wiring a shared error envelope (`withMinerToolErrorHandling`) plus a `structured`/`text` split so tools that returned bare arrays (`list_claims`, `list_plans`, `governor_decisions`) can satisfy the MCP spec's object-only `structuredContent` requirement while keeping the text block unchanged. I traced both fixes claimed in the description against the diff: `loopover_miner_get_audit_feed` now returns `collectEventLedgerAuditFeed()`'s result directly instead of re-wrapping it (bin file, audit-feed handler), and the three array-returning tools now emit `{ structured: {...}, text: rawArray }` correctly. The tool-name assertion tests were also usefully converted from a hardcoded array to reading the shared registry (`test/unit/miner-mcp-scaffold.test.ts`), removing a drift-prone hand-copy.

Nits — 6 non-blocking
  • No visible test exercises the new `withMinerToolErrorHandling` catch branch or `toolErrorCode`'s regex classification (store_unavailable vs unknown_error) — worth a test that injects a throwing store opener and asserts `isError: true` with the right code.
  • `isTextOverride` (packages/loopover-miner/bin/loopover-miner-mcp.ts) narrows on the literal presence of `"structured"` and `"text"` keys; none of today's tool payloads use those names but it's a fragile discriminator if a future tool's real output happens to contain a `text` field.
  • packages/loopover-contract/src/tools/miner.ts is ~424 lines, over the repo's usual single-file size comfort zone — consider splitting per tool family (claims/plans/governor/etc.) the way other contract families are organized.
  • The new `@​loopover/contract` dependency pin (`^0.1.0`) has no attestation info to verify provenance; not something this diff can fix, but worth flagging since it's a new direct dependency of `@​loopover/miner`.
  • Add a unit test in test/unit/miner-mcp-scaffold.test.ts that forces a store opener to throw and asserts the unified `{ error: { code, message } }` envelope and `isError: true`, covering both `store_unavailable` and `unknown_error` branches of `toolErrorCode`.
  • PR author also opened the linked issue — Link an issue that was opened by a different contributor, or provide a rationale for why this self-authored issue represents genuine discovery work.

CI checks failing

  • codecov/patch — 78.19% of diff hit (target 99.00%)
  • validate

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #9536
Related work ⚠️ 1 scoped overlap Top overlaps are listed below; lower-confidence bulk is hidden.
Change scope ❌ 8/20 High review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 14 registered-repo PR(s), 13 merged, 326 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 14 PR(s), 326 issue(s).
Improvement ✅ Minor risk: clean · value: minor
Linked issue satisfaction

Addressed
The diff adds all 11 contract entries with real input/output schemas and locality "miner", wires structuredContent (with byte-identical text payloads for array-shaped results), unifies the error envelope via withMinerToolErrorHandling/isError across all 11 tools, and replaces the hardcoded tool-name list in miner-mcp-scaffold.test.ts with a registry-derived assertion, matching the issue's requirem

Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: Python, TypeScript, Ruby, Go, MDX, Shell, Solidity, JavaScript
  • Official Gittensor activity: 14 PR(s), 326 issue(s).
  • Related work: Titles/paths share 8 meaningful terms. (issue #9536, issue #9537)
Contributor next steps
  • Start here: Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Then work through the remaining 3 steps in the Signals table above.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask <question> answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat <question> answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

Decision record
  • action: hold · clause: success
  • config: e3aa4ff687ee39d0876d2b22942be9f533f938a10c9ca9ea8e1dfffb2a2091f4 · pack: oss-anti-slop · ci: failed
  • record: 139fb5a1f252359ceceeab5540dd0561ab6c201430335661801a6aecf211961b (schema v5, head b885089)

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.19549% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.70%. Comparing base (78061a0) to head (b77e154).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/loopover-miner/bin/loopover-miner-mcp.ts 67.77% 0 Missing and 29 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #9542      +/-   ##
==========================================
- Coverage   89.59%   88.70%   -0.89%     
==========================================
  Files         858      859       +1     
  Lines      110456   110512      +56     
  Branches    26300    26304       +4     
==========================================
- Hits        98961    98029     -932     
- Misses      10231    11513    +1282     
+ Partials     1264      970     -294     
Flag Coverage Δ
backend 93.68% <78.19%> (-1.62%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
packages/loopover-contract/src/tools/index.ts 100.00% <ø> (ø)
packages/loopover-contract/src/tools/miner.ts 100.00% <100.00%> (ø)
packages/loopover-miner/bin/loopover-miner-mcp.ts 72.22% <67.77%> (+2.74%) ⬆️

... and 2 files with indirect coverage changes

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 28, 2026
@JSONbored
JSONbored force-pushed the feat/loopover-contract-package-9517 branch from d9cea33 to 6192bad Compare July 28, 2026 07:56
@JSONbored
JSONbored force-pushed the feat/miner-mcp-contract-9536 branch from 5aed607 to b885089 Compare July 28, 2026 08:01
Base automatically changed from feat/loopover-contract-package-9517 to main July 28, 2026 08:17
All 11 loopover_miner_* tools now register from the shared contract instead of
declaring their own zod shapes -- the last of LoopOver's three MCP servers to
gain a source of truth, and the only one with zero output schemas before this
(every handler returned unschematized text). Every tool now emits
structuredContent alongside its text block.

Two real defects surfaced during the migration, not before it:

- The output schema for loopover_miner_get_audit_feed initially wrapped
  collectEventLedgerAuditFeed's return value in a second { events: ... } layer,
  double-nesting a payload that already has that shape ({ repoFullName?, events }).
  The MCP SDK's own structured-output validation caught it immediately --
  "expected array, received object" -- confirming the schema is doing its job
  as a runtime contract, not just documentation.
- list_claims, list_plans, and governor_decisions previously returned BARE ARRAYS
  as their text payload (JSON.stringify(ledger.listClaims(...)), etc.), but an MCP
  structuredContent value must be a JSON object per spec. Wrapping structuredContent
  in { claims }/{ plans }/{ decisions } while leaving the text block as the
  original array (via a small structured/text split in the result helper) is what
  keeps every existing text-parsing consumer working unchanged -- confirmed by
  running the real handler before committing to the shape, not by inspecting the
  aggregator's declared TS return type alone.

Also: a unified error envelope across all 11 tools. Before, only
loopover_miner_get_audit_feed caught a store failure and returned isError: true;
the other ten threw, so the same failure surfaced as a clean result from one tool
and a raw protocol error from the rest.

test/unit/miner-mcp-scaffold.test.ts's hardcoded 11-name array and
miner-mcp-tool-docs-parity.test.ts's regex-scrape of the COMPILED dist bundle
(a pattern my own earlier investigation flagged as fragile -- a reformatted
registration call would silently break the guard, not the code) are both
replaced by a direct read of listToolDefinitions({ locality: ["miner"] }): one
list every server and every test derives from, no build step, no regex.

Full unit suite: 23041 tests, 1208 files, all green. build:miner (135 files,
check-syntax), test:miner-pack, and test:miner-deployment-docs-audit all pass
unchanged.

Closes #9536
…ct.input accessor

Registering with minerXTool.input.shape/output.shape compiled fine standalone
but broke once each handler declared an explicit z.infer parameter type:
ToolContract.input is typed as the general z.ZodObject class (the type every
contract entry shares), so z.infer<typeof minerXTool.input> collapses to a
generic/incorrect shape instead of the tool's real fields -- and passing that
same widened .shape to registerTool's inputSchema, while the handler declares
the SPECIFIC narrower type, is a real contravariant mismatch the MCP SDK's
overload correctly rejected (planId missing in type ShapeOutput<...>).

Every registration and handler now references the tool's own named
MinerXInput/MinerXOutput export from @loopover/contract/tools instead of the
ToolContract.input/.output accessor -- the schema passed to registerTool and
the type the handler destructures against are now the same concrete object.

build:miner and the full repo typecheck are clean; 106 tests green across
miner-mcp-scaffold/contract/audit-feed/manage-status/calibration-report/
governor-decisions/tool-docs-parity and contract-registry.

Refs #9536
@JSONbored
JSONbored force-pushed the feat/miner-mcp-contract-9536 branch from b885089 to b77e154 Compare July 28, 2026 08:21
@JSONbored
JSONbored merged commit 9713f26 into main Jul 28, 2026
6 of 11 checks passed
@JSONbored
JSONbored deleted the feat/miner-mcp-contract-9536 branch July 28, 2026 08:42
JSONbored added a commit that referenced this pull request Jul 28, 2026
…ontract

Completes #9518. All seven remote-server categories -- admin, maintainer,
review, branch, discovery, utility and agent -- now take their input and output
schemas from the contract package instead of ~120 declarations local to
src/mcp/server.ts. The remote server is the second of the three to be migrated
to completion, after the AMS miner (#9542).

A handful of inputs deliberately stay server-side, each for a stated reason:
schemas carrying a .transform() or .default() (a caller must not be able to
assert its own branch eligibility into its own score), one input shared verbatim
with a REST route, and three bounded by constants a zod-only leaf package cannot
import. Their outputs migrate either way.

Two fixes fall out of the move:

- The changed-file and validation-entry schemas behind loopover_run_local_scorer
  and loopover_preflight_local_diff, and both nested schemas behind
  loopover_suggest_boundary_tests, lost their .strict() in an earlier batch of
  this migration. That strictness is the no-upload boundary: without it a caller
  that smuggles a patch/content field gets a silently-stripped call instead of a
  rejected one, and believes the upload succeeded. Restored, with regression
  tests on both surfaces.

- PLAN_STEP_STATUSES said 'in_progress' where both real surfaces -- the remote
  plan DAG and the miner's plan store -- say 'running'. Nothing consumed it yet,
  so nothing broke, but the first consumer would have rejected every running step
  the store has ever persisted. Corrected, aliased from the miner contract so
  there is one vocabulary, and pinned by a test.

The REST<->MCP parity guards now read the contract rather than server-local
declarations that no longer exist. Tool descriptions are relocated verbatim.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

contract(ams): migrate the AMS miner MCP server to @loopover/contract — structured output for all 11 tools

1 participant