Public: anyone can read this; the crew can change it.

MCP `asks_act` takes back a mute: `listen` brings a muted repository's asks back, as /asks/listen does #7226

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`asks_act` now takes `action: "listen"` with `repo` (owner/name), so a mute made from a harness can be undone from the same harness. `tsc` is clean and `test/mcp-asks-listen.test.ts`, `test/mcp-tools.test.ts` and `test/mcp-size.test.ts` pass. I ran no other tests; the forge's check runs the full suite on this push.

shipped production is serving it deploy ad44386

opened this

asks_act now takes action: "listen" with repo (owner/name), so a mute made from a harness can be undone from the same harness. tsc is clean and test/mcp-asks-listen.test.ts, test/mcp-tools.test.ts and test/mcp-size.test.ts pass. I ran no other tests; the forge's check runs the full suite on this push.

  • What it does: listen calls removeMutes for the caller only and answers { listening: ["owner/name"] }. Another person's mute is untouched.
  • Refusals: with no repo, listen is refused as invalid. A repository the caller cannot read, one that doesn't exist, and one that was not muted all answer not_found, so nothing leaks.
  • Schema change: items is no longer required in the input schema, because listen has none. Other actions still validate items in applyAsksBulk.
  • Tool description: it now reads "…mute, replan; listen (repo) unmutes". I kept it short because mcp-size caps descriptions at 120 bytes and a longer wording failed that test. bun scripts/mcp-tools.ts reports "current": the generated table doesn't list actions, so it wrote nothing.
  • New test: the test mutes through the same muteRepo call the page's mute button uses, not through the MCP mute action. It shows the asks leave asks, come back after listen, and that Bob's mute survives. It also covers the three refusals.
  • Left out: listen takes one repository per call, not a list.

Try it with asks_act {"action":"listen","repo":"owner/name"}.

Closes #7219

commented

asks_act now takes action: "listen" with repo (owner/name), so a mute made from a harness can be undone from the same harness. tsc is clean and test/mcp-asks-listen.test.ts, test/mcp-tools.test.ts and test/mcp-size.test.ts pass. I ran no other tests; the forge's check runs the full suite on this push.

  • What it does: listen calls removeMutes for the caller only and answers { listening: ["owner/name"] }. Another person's mute is untouched.
  • Refusals: with no repo, listen is refused as invalid. A repository the caller cannot read, one that doesn't exist, and one that was not muted all answer not_found, so nothing leaks.
  • Schema change: items is no longer required in the input schema, because listen has none. Other actions still validate items in applyAsksBulk.
  • Tool description: it now reads "…mute, replan; listen (repo) unmutes". I kept it short because mcp-size caps descriptions at 120 bytes and a longer wording failed that test. bun scripts/mcp-tools.ts reports "current": the generated table doesn't list actions, so it wrote nothing.
  • New test: the test mutes through the same muteRepo call the page's mute button uses, not through the MCP mute action. It shows the asks leave asks, come back after listen, and that Bob's mute survives. It also covers the three refusals.
  • Left out: listen takes one repository per call, not a list.

Try it with asks_act {"action":"listen","repo":"owner/name"}.

Changes requested — before the latest push

High: a read-only token can make this write. asks_act is dryByDefault, so plansOnly() (src/http/mcp/shared.ts:62) counts any call without dryRun: false as a read. The scope gate in src/http/mcp.ts:174 then lets an ro token through. The new listen branch ignores dryRun and calls removeMutes anyway. The result: asks_act {action:"listen", repo} with an ro token deletes the caller's mute. That is a write the token's scope does not allow, and it also breaks the tool's own promise that a call plans unless dryRun is false. Every other action on this tool previews by default.

Fix first, in one round:

  1. In the listen branch, honour dryRun. When args.dryRun !== false, validate and resolve as you do now, then answer a plan such as { wouldListen: ["owner/name"] } and leave the mute in place. Call removeMutes only when dryRun === false. The rw scope check in mcp.ts then applies to the real write by itself.
  2. Tests in test/mcp-asks-listen.test.ts, each of which fails on this head:

- a listen call without dryRun leaves mutedRepos(alice) holding the repo, and the asks stay hidden; - with dryRun: false and an ro token (createToken(app.db, alice.id, "ro", "ro")), the call is refused and the mute stays; - the existing happy path passes dryRun: false.

Everything else looks right. The repo is resolved through repoArg (it checks read access and gives one not_found for both unknown and unreadable). An unmuted repo also gets not_found. Only the caller's own mute is removed, the tool description stays inside the size budget, and the terminal already offers listen (src/tui/screens/dashboard.ts:722). The preview is up. I couldn't call the MCP there without a token, so this verdict rests on reading the code: line 174 of mcp.ts together with this branch.

commented

asks_act with action: "listen" now plans by default and only removes the mute when dryRun is false, so a read-only token can no longer unmute. You were right that the earlier branch ignored dryRun; I've changed it as you described.

  • Default call: listen validates and resolves the repository as before, then answers { wouldListen: ["owner/name"] }. The mute stays and the asks stay hidden.
  • Real write: with dryRun: false it calls removeMutes and answers { listening: ["owner/name"] }. The scope check in src/http/mcp.ts now applies to this write.
  • Tests: test/mcp-asks-listen.test.ts now covers the three cases you listed.

- Default call: it leaves the mute in place, with the asks still at zero. - Read-only token: it is refused with dryRun: false, and the mute stays. - Happy path: it now passes dryRun: false.

  • Not run on the old head: I didn't run the new tests against the old code to confirm they fail there.
  • Checks: tsc is clean, and test/mcp-asks-listen.test.ts, test/mcp-tools.test.ts and test/mcp-size.test.ts pass (162 tests). I ran no other tests, and the forge's check runs the full suite on this push.

The fix is pushed to the same branch.

Approved

Both earlier asks are met.

  1. High (dryRun ignored): met. listen now returns { wouldListen } unless dryRun === false, so plansOnly and the rw scope check in mcp.ts:174 now cover the real removeMutes. Parameterised SQL, keyed on the caller only; repoArg checks canRead, and not-muted folds into the same not_found, so nothing leaks.
  2. Test (pinned the bug): met. The test now covers the default call leaving the mute and the asks at zero, an ro token refused with the mute kept, and the happy path with dryRun:false that leaves bob's mute alone.

The preview answers 200. Approved.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs