feat(mcp): register the maintain REST surface as local stdio tools - #6382
Conversation
The maintain CLI subcommands call real REST endpoints that the remote server has exposed as tools since JSONbored#6087, but the local stdio server registered none of them. An agent on the local server had to shell out to the CLI to list the approval queue, decide a staged action, toggle the kill-switch, set an autonomy level, or read gate precision. Register the five missing counterparts: loopover_list_pending_actions, loopover_decide_pending_action, loopover_set_agent_paused, loopover_set_action_autonomy, and loopover_get_gate_precision. Each calls the endpoint its CLI subcommand already calls, through the same apiGet/apiPost/apiFetch client, so auth, timeouts, and error shaping come from there rather than a second HTTP path. Shapes mirror the remote's, and categories mirror the remote's MCP_TOOL_CATEGORIES entries for the same names, so one caller sees one surface. The remote server is untouched. set_action_autonomy read-merge-writes like `maintain set-level` does: PUT /settings replaces the whole autonomy map, so sending one class alone would silently clear the others. A test pins that the other classes survive, and that the write is a GET-then-PUT. One deliberate divergence from the remote: its list_pending_actions takes an optional `status`, which it can honour because it queries the queue store directly. This server has only GET /agent/pending-actions, which takes no query parameters and hardcodes status "pending" (src/api/routes.ts). Offering the filter here would let a caller ask for "rejected", receive the pending list, and be told it succeeded -- so the schema omits it and the description names the queue it really returns. Tests cover success and an API-failure path for each tool, plus the read-merge-write invariant and pre-flight rejection of unknown action classes and autonomy levels. The alias-retirement suite pins the exact registered-tool count; these five take it from 42 to 47. Closes JSONbored#6152
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #6382 +/- ##
==========================================
- Coverage 95.60% 95.60% -0.01%
==========================================
Files 599 599
Lines 47221 47238 +17
Branches 15024 15031 +7
==========================================
+ Hits 45147 45160 +13
- Misses 1290 1291 +1
- Partials 784 787 +3
Flags with carried forward coverage won't be shown. Click here to find out more. |
|
Tip ✅ LoopOver review result - approve/merge recommendedReview updated: 2026-07-16 05:50:35 UTC
Review summary Nits — 5 non-blocking
Decision drivers
Context & advisory signals — never blocks the verdict
Linked issue satisfactionAddressed Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk 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.
Full command reference: https://loopover.ai/docs/loopover-commands 🧪 Experimental — new and may change. 🟩 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.
|
… tool (#7877) * feat(mcp): register loopover_get_outcome_calibration as a local stdio tool Mirrors the 5 registerStdioTool siblings PR #6382 added (same owner/repo/windowDays shape, same toolRepoBase helper), and reuses the REST call the existing `maintain outcome-calibration` CLI subcommand already makes -- no duplicated HTTP logic. Bumps mcp-tool-rename-aliases.test.ts's hardcoded stdio tool-count invariant from 79 to 80 to match. Refs #7758. * fix(ci): skip validate-tests-merge's global coverage threshold under scoped test selection Scoped test selection (#ci-scoped-test-selection) runs vitest --changed across all 3 shards for a miner/mcp/discoveryIndex/backend-only PR, so merging those shards reconstructs only that narrow subset's coverage, not the whole suite. validate-tests-merge's global 80% threshold check assumed a merged-shard total always meant whole-suite coverage and false-failed a fully-tested, scoped-selection PR as a result. Disables the threshold in that same case, mirroring the per-shard COVERAGE_NO_THRESHOLDS pattern already used above it -- Codecov's patch gate still enforces real per-line coverage on the actual diff regardless.
Closes JSONbored#7761 loopover_list_notifications was already registered as a remote MCP tool (src/mcp/server.ts) with a CLI mirror (`notifications`, JSONbored#6745), but had no local stdio MCP tool registration -- the same gap class PR JSONbored#6382 fixed for the 5 maintain-surface tools. Mirrors that pattern exactly: a registerStdioTool block using stdioToolDescription for the centralized description and toolResult for the response shape, placed alongside the other loginShape-based contributor tools (loopover_get_decision_pack / loopover_explain_repo_decision / loopover_monitor_open_prs / loopover_pr_outcome). The handler reuses getNotifications(login), the exact apiGet call the existing `notifications` CLI command already makes, so there is one HTTP call site for this route, not two. Adds a stdio-proxy test to test/unit/mcp-cli-notifications.test.ts following the same StdioClientTransport + fixture-server shape as the sibling loginShape tools' own suites (mcp-cli-pr-outcomes.test.ts, mcp-cli-monitor-open-prs.test.ts): registration, the one apiGet call, and tool/CLI mirror parity. Bumps the pinned stdio tool count in mcp-tool-rename-aliases.test.ts from 79 to 80.
Closes JSONbored#7753 Mirrors the exact registerStdioTool pattern PR JSONbored#6382 used for the 5 maintain-surface siblings (loopover_list_pending_actions et al): the handler calls the same bare POST .../agent/pending-actions endpoint `maintain propose` already calls, through the same apiPost client, and its description comes from the same stdioToolDescription centralized lookup. JSONbored#6744 added the route + CLI mirror without a stdio registration, so it fell outside JSONbored#6152's batch despite being the same family. The route's response always carries a fully-populated `action` (id/actionClass/status set unconditionally, per src/api/routes.ts's POST handler) -- only `created` genuinely varies, so that's the only branch the handler formats defensively. New dedicated suite (mcp-cli-propose-action-tool.test.ts) covers registration, the proxy contract, both the "Staged"/"Already staged" branches, an API-failure path, and pre-flight schema rejection -- following mcp-cli-maintain- tools.test.ts's shape. Bumped the pinned stdio tool count 80 -> 81 in mcp-tool-rename-aliases.test.ts (rebased past JSONbored#7877's own 79 -> 80 bump).
Mirror the maintain-family registerStdioTool pattern (JSONbored#6382/JSONbored#7877) and reuse the same REST endpoint the existing `maintain automation-state` CLI already calls. Bump the stdio tool-count invariant to 83 (correcting the stale pin left after JSONbored#7887). Closes JSONbored#7752. Co-authored-by: Cursor <cursoragent@cursor.com>
Closes JSONbored#7761 loopover_list_notifications was already registered as a remote MCP tool (src/mcp/server.ts) with a CLI mirror (`notifications`, JSONbored#6745), but had no local stdio MCP tool registration -- the same gap class PR JSONbored#6382 fixed for the 5 maintain-surface tools. Mirrors that pattern exactly: a registerStdioTool block using stdioToolDescription for the centralized description and toolResult for the response shape, placed alongside the other loginShape-based contributor tools (loopover_get_decision_pack / loopover_explain_repo_decision / loopover_monitor_open_prs / loopover_pr_outcome). The handler reuses getNotifications(login), the exact apiGet call the existing `notifications` CLI command already makes, so there is one HTTP call site for this route, not two. Adds a stdio-proxy test to test/unit/mcp-cli-notifications.test.ts following the same StdioClientTransport + fixture-server shape as the sibling loginShape tools' own suites (mcp-cli-pr-outcomes.test.ts, mcp-cli-monitor-open-prs.test.ts): registration, the one apiGet call, and tool/CLI mirror parity. Bumps the pinned stdio tool count in mcp-tool-rename-aliases.test.ts from 79 to 80.
Closes JSONbored#7753 Mirrors the exact registerStdioTool pattern PR JSONbored#6382 used for the 5 maintain-surface siblings (loopover_list_pending_actions et al): the handler calls the same bare POST .../agent/pending-actions endpoint `maintain propose` already calls, through the same apiPost client, and its description comes from the same stdioToolDescription centralized lookup. JSONbored#6744 added the route + CLI mirror without a stdio registration, so it fell outside JSONbored#6152's batch despite being the same family. The route's response always carries a fully-populated `action` (id/actionClass/status set unconditionally, per src/api/routes.ts's POST handler) -- only `created` genuinely varies, so that's the only branch the handler formats defensively. New dedicated suite (mcp-cli-propose-action-tool.test.ts) covers registration, the proxy contract, both the "Staged"/"Already staged" branches, an API-failure path, and pre-flight schema rejection -- following mcp-cli-maintain- tools.test.ts's shape. Bumped the pinned stdio tool count 80 -> 81 in mcp-tool-rename-aliases.test.ts (rebased past JSONbored#7877's own 79 -> 80 bump).
Closes JSONbored#7753 Mirrors the exact registerStdioTool pattern PR JSONbored#6382 used for the 5 maintain-surface siblings (loopover_list_pending_actions et al): the handler calls the same bare POST .../agent/pending-actions endpoint `maintain propose` already calls, through the same apiPost client, and its description comes from the same stdioToolDescription centralized lookup. JSONbored#6744 added the route + CLI mirror without a stdio registration, so it fell outside JSONbored#6152's batch despite being the same family. The route's response always carries a fully-populated `action` (id/actionClass/status set unconditionally, per src/api/routes.ts's POST handler) -- only `created` genuinely varies, so that's the only branch the handler formats defensively. New dedicated suite (mcp-cli-propose-action-tool.test.ts) covers registration, the proxy contract, both the "Staged"/"Already staged" branches, an API-failure path, and pre-flight schema rejection -- following mcp-cli-maintain- tools.test.ts's shape. Bumped the pinned stdio tool count 80 -> 81 in mcp-tool-rename-aliases.test.ts (rebased past JSONbored#7877's own 79 -> 80 bump).
Summary
The
maintainCLI subcommands call real REST endpoints that the remote server has exposed as tools since#6087, but the local stdio server registered none of them. An agent on the local server had to shell out to the CLI to list the approval queue, decide a staged action, toggle the kill-switch, set an autonomy level, or read gate precision.This registers the five missing counterparts:
loopover_list_pending_actionsGET /agent/pending-actionsmaintain queueloopover_decide_pending_actionPOST /agent/pending-actions/:id/:decisionmaintain approve|rejectloopover_set_agent_pausedPUT /settings(agentPaused)maintain pause|resumeloopover_set_action_autonomyGET+PUT /settings(autonomy)maintain set-levelloopover_get_gate_precisionGET /gate-precisionmaintain precisionEach calls the endpoint its CLI subcommand already calls, through the same
apiGet/apiPost/apiFetchclient — so auth, timeouts and error shaping come from there rather than a second HTTP path, per the issue's "reuse the CLI's existing HTTP-client logic" requirement. Input shapes mirror the remote's, and categories mirror the remote'sMCP_TOOL_CATEGORIESentries for the same names, so a caller sees one consistent surface across both servers. The remote server is untouched, as required.One deliberate divergence from the remote — please read
The remote's
listPendingActionsShapetakes an optionalstatus, which it can honour because it queries the approval-queue store directly. This server can only reach the queue throughGET /v1/repos/:owner/:repo/agent/pending-actions, which takes no query parameters at all and hardcodesstatus: "pending"(src/api/routes.ts).Mirroring the remote schema literally would therefore have shipped a filter that does nothing: a caller could ask for
"rejected", receive the pending list, and be told it succeeded. That is precisely the failure mode of #6153 (merged in #6378), so I leftstatusout of the schema and made the description name the queue it really returns. An agent picks its arguments from the published schema, so a filter that isn't there is one it won't ask for — and a key sent anyway is dropped by the MCP layer before the handler, so it can never reach the URL either. A test pins both halves.set_action_autonomyread-merge-writes exactly asmaintain set-leveldoes:PUT /settingsreplaces the whole autonomy map, so sending one class alone would silently clear the others. A test asserts the other classes survive and that the write is aGET-then-PUT.Validation
test/unit/mcp-cli-maintain-tools.test.ts— 15/15 pass: success + an API-failure path for each of the 5 tools, plus the read-merge-write invariant, the no-status-filter contract, and pre-flight rejection of unknown action classes / autonomy levels.mcp-tool-rename-aliases+mcp-cli-tools+mcp-cli-tools-search— 20/20 pass (the count canary, updated 42 → 47).basics,maintain,intake-tools,completion-spec,tool-categories,profiles,doctor) — 80/80 pass.npm run typecheck— 0 errors.npm run build:mcp— passes.npm run docs:drift-check— ok.git diff --check— clean.main— no base conflict.Coverage
No patch surface: coverage is collected over
src/**,packages/loopover-engine/src/**, andpackages/loopover-miner/lib/**. This PR touches onlypackages/loopover-mcp/**(not collected) andtest/**(ignored). The new tools are still exercised end-to-end by the suite above, against the fixture server.Scope
packages/,test/). Remote server untouched; existing CLI subcommands' behaviour unchanged.site/,CNAME, orlovablechanges.Safety
Closes #6152