Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/loopover-mcp/bin/loopover-mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1286,6 +1286,11 @@ const STDIO_TOOL_DESCRIPTORS = [
category: "agent",
description: "Set the autonomy level for one action class via a read-merge-write, so the other classes are left untouched. Same as `loopover-mcp maintain set-level <action> <level>`. Maintainer access required.",
},
{
name: "loopover_get_automation_state",
category: "agent",
description: "Return a repo's agent automation state: the per-action autonomy levels, kill-switch / dry-run mode, GitHub write-permission readiness, and how many auto_with_approval actions are awaiting a maintainer decision — the read-side view behind the pause/resume and set-level toggles. Same as `loopover-mcp maintain automation-state`. Maintainer access required.",
},
{
name: "loopover_get_gate_precision",
category: "maintainer",
Expand Down Expand Up @@ -2597,6 +2602,22 @@ registerStdioTool(
return toolResult(`Gate precision for ${owner}/${repo}.`, payload);
},
);

// #6742 added this endpoint's REST route and its `maintain automation-state` CLI mirror, but not the stdio tool,
// so it fell outside #6152's batch above (#7752). Read-only: the derived counterpart to the pause/resume and
// set-level writes registered above, reached through the same apiGet client those use.
registerStdioTool(
"loopover_get_automation_state",
{
description: stdioToolDescription("loopover_get_automation_state"),
inputSchema: ownerRepoShape,
},
async ({ owner, repo }: any) => {
const payload = await apiGet(`${toolRepoBase(owner, repo)}/automation-state`);
return toolResult(`Agent automation state for ${owner}/${repo}.`, payload);
},
);

// ── Write-tools (#6149): pure LOCAL-execution spec builders. loopover NEVER performs the write -- each tool
// returns a spec the caller runs with its OWN gh creds. Brings the local stdio server to parity with the
// miner-auto-dev profile's recommendedTools, using the same @loopover/engine builders as the remote server.
Expand Down
10 changes: 6 additions & 4 deletions test/unit/mcp-cli-maintain-tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ async function connect() {
const apiUrl = await startFixtureServer({
onApiRequest: (request) => {
const url = request.url ?? "";
if (/pending-actions|settings|gate-precision/.test(url)) capturedRequests.push({ url, method: request.method ?? "GET" });
if (/pending-actions|settings|gate-precision|automation-state/.test(url)) capturedRequests.push({ url, method: request.method ?? "GET" });
},
});
transport = new StdioClientTransport({
Expand Down Expand Up @@ -51,23 +51,25 @@ afterEach(async () => {

const REPO = { owner: "owner", repo: "repo" };

/** Every #6152 tool, with an argument set the fixture serves and a field its real payload carries. */
/** Every #6152 tool, plus #7752's automation-state read, with an argument set the fixture serves and a field its
* real payload carries. */
const MAINTAIN_TOOLS = [
{ name: "loopover_list_pending_actions", args: REPO, contains: "pa-1" },
{ name: "loopover_decide_pending_action", args: { ...REPO, id: "pa-1", decision: "accept" }, contains: "accepted" },
{ name: "loopover_set_agent_paused", args: { ...REPO, paused: true }, contains: "agentPaused" },
{ name: "loopover_set_action_autonomy", args: { ...REPO, action: "merge", level: "auto" }, contains: "autonomy" },
{ name: "loopover_get_gate_precision", args: REPO, contains: "falsePositiveRate" },
{ name: "loopover_get_automation_state", args: REPO, contains: "permissionReadiness" },
] as const;

describe("loopover-mcp maintain stdio proxies (#6152)", () => {
it("registers all 5 maintain tools in the stdio server tool list", async () => {
it("registers all 6 maintain tools in the stdio server tool list", async () => {
await connect();
const names = (await client!.listTools()).tools.map((tool) => tool.name);
for (const tool of MAINTAIN_TOOLS) expect(names).toContain(tool.name);
});

it("lists all 5 maintain tools via `loopover-mcp tools --json` with non-empty descriptions", async () => {
it("lists all 6 maintain tools via `loopover-mcp tools --json` with non-empty descriptions", async () => {
await connect();
const payload = JSON.parse(run(["tools", "--json"])) as { tools: Array<{ name: string; description: string; category?: string }> };
for (const tool of MAINTAIN_TOOLS) {
Expand Down
Loading