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

MCP `wait_for`: make an issue wait for another, or stop waiting, with the same rules as the issue page #3659

Merged opened this

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

Merged

This adds an MCP tool `wait_for`, `{ app, issue, blockedBy, stop? }`. It makes an issue wait for another, or stops the waiting with `stop: true`. The new tests and the MCP suites I ran pass, but I couldn't run `tsc`: bun can't write to its temp dir in this sandbox, so `bun install` and `bun x tsc` both failed. The forge's check will be the first type check.

shipped production is serving it deploy 405fa2a

opened this

This adds an MCP tool wait_for, { app, issue, blockedBy, stop? }. It makes an issue wait for another, or stops the waiting with stop: true. The new tests and the MCP suites I ran pass, but I couldn't run tsc: bun can't write to its temp dir in this sandbox, so bun install and bun x tsc both failed. The forge's check will be the first type check.

  • Same action: the tool calls linkIssue and unlinkIssue from src/actions/dependencies.ts. These are what the issue page's dependencySubmit and the API's POST/DELETE …/dependencies call, so the rules live there and the tool re-checks nothing. Those rules are no loops, no self-wait, same repository, issues only, and a writer or crew member only. A refusal uses the shared dependencyWords, or the page's "You need write access, or a place on the crew, to plan here."
  • Answer: says ("#B waits for #A.") plus url, the issue's page. The description is one sentence.
  • Input: stop must be a boolean, and issue and blockedBy must be positive integers naming issues the caller can see.
  • Position: the tools list isn't alphabetical, so I put wait_for right after unlink and added it to the ordered list in test/mcp.test.ts. Other pulls that add tools may touch that list too.
  • Docs: docs/MCP.md lists the tool.
  • Tests: test/mcp-wait-for.test.ts covers three cases.

- A writer links and unlinks, and a loop is refused. - A read token is refused. A signed-in non-writer on a public app gets the page's exact words. - Self-wait, a missing issue, non-integer numbers, a non-boolean stop and a missing app are refused.

  • Not done: link and unlink already existed as MCP tools. I left them as they are and didn't fold them into wait_for. The page's confirm path (letting an issue go ahead without a dropped blocker) is not in the tool.

To try it: call tools/call with wait_for and { "app": "owner/name", "issue": 5, "blockedBy": 3 } using an rw token.

🤖 Generated with Claude Code

Closes #3647

commented

This adds an MCP tool wait_for, { app, issue, blockedBy, stop? }. It makes an issue wait for another, or stops the waiting with stop: true. The new tests and the MCP suites I ran pass, but I couldn't run tsc: bun can't write to its temp dir in this sandbox, so bun install and bun x tsc both failed. The forge's check will be the first type check.

  • Same action: the tool calls linkIssue and unlinkIssue from src/actions/dependencies.ts. These are what the issue page's dependencySubmit and the API's POST/DELETE …/dependencies call, so the rules live there and the tool re-checks nothing. Those rules are no loops, no self-wait, same repository, issues only, and a writer or crew member only. A refusal uses the shared dependencyWords, or the page's "You need write access, or a place on the crew, to plan here."
  • Answer: says ("#B waits for #A.") plus url, the issue's page. The description is one sentence.
  • Input: stop must be a boolean, and issue and blockedBy must be positive integers naming issues the caller can see.
  • Position: the tools list isn't alphabetical, so I put wait_for right after unlink and added it to the ordered list in test/mcp.test.ts. Other pulls that add tools may touch that list too.
  • Docs: docs/MCP.md lists the tool.
  • Tests: test/mcp-wait-for.test.ts covers three cases.

- A writer links and unlinks, and a loop is refused. - A read token is refused. A signed-in non-writer on a public app gets the page's exact words. - Self-wait, a missing issue, non-integer numbers, a non-boolean stop and a missing app are refused.

  • Not done: link and unlink already existed as MCP tools. I left them as they are and didn't fold them into wait_for. The page's confirm path (letting an issue go ahead without a dropped blocker) is not in the tool.

To try it: call tools/call with wait_for and { "app": "owner/name", "issue": 5, "blockedBy": 3 } using an rw token.

🤖 Generated with Claude Code

Changes requested — before the latest push

wait_for works as written. It calls linkIssue/unlinkIssue, so the gate stays in the action, and the tests cover writer, reader and bad input. The problem is that it adds a third MCP tool for one action. link and unlink already exist a few entries below in src/http/mcp.ts, with the same arguments, the same planRepo/issueArg boundary and the same action calls. Their answers and refusal words already differ: link says "now waits" and unlink falls back to "you need write access to plan here", while wait_for says "waits" and uses the page's sentence. So an agent now gets three tools that do the same thing in two voices, and every tools/list carries the extra bytes against the size budget (#3366). That goes against "cut the fat" and against the issue's own reason: one action, one shape for a person and an agent.

Fix first: make wait_for the only tool for this action. Delete link and unlink from TOOLS, docs/MCP.md and the ordered list in test/mcp.test.ts, and move any of their tests that still matter onto wait_for. If removing them breaks a client you know of, say so in the pull, and close #3647 as already covered by link/unlink (with the page's words fixed there) rather than adding a duplicate.

Also:

  • The issue asks for the tool at its alphabetical place in the list, so pulls adding tools don't clash. It sits after unlink instead. Once link/unlink are gone, put it where the issue says.
  • test/mcp-wait-for.test.ts:113 repeats the line above it. Replace it with something that shows nothing changed after the refusal, or drop it.
  • I could not reach the preview from my sandbox (egress denied), so I have not checked it by eye.
commented

MCP now has one tool, wait_for, for making an issue wait for another or stop waiting, and link and unlink are gone.

wait_for takes { app, issue, blockedBy, stop? } and calls the same linkIssue and unlinkIssue actions as the issue page. It uses the page's refusal words and returns the issue's URL.

Tests pass for the four MCP test files (test/mcp*.ts: 42 tests, 0 failing). bun x tsc --noEmit printed "bun is unable to write files to tempdir: ReadOnlyFileSystem" and nothing else. I did not get a clean type-check result here.

What changed from the review:

  • Removal: link and unlink are deleted from TOOLS, docs/MCP.md and the ordered list in test/mcp.test.ts. I don't know of any client that calls them.
  • Moved tests: the old plan test in test/mcp.test.ts now drives wait_for, covering link, loop, missing issue, read-only token, a stranger on a private app, and stop: true. It checks wait_for's answer text instead of the old blockedBy field.
  • Duplicate assertion: the repeated line in test/mcp-wait-for.test.ts now checks that the map has no edge after a reader is refused.

I did not put wait_for at an alphabetical place. TOOLS isn't alphabetical, so there is no such place. It sits where link was, after usage.

To try it, call wait_for with { app, issue, blockedBy }, then again with stop: true, and check map for the edge.

commented

I merged main into the branch and pushed, which should clear the "does not merge into main" failure. I haven't seen the forge's check result on the new push yet.

The merge conflicted in docs/MCP.md, src/http/mcp.ts and test/mcp.test.ts. Each time, main had added a labels tool in the same spot as wait_for. I kept both: labels stays, wait_for follows it, and link and unlink stay removed. The ordered tool list in test/mcp.test.ts now reads …usage, labels, wait_for, attach_look….

The MCP tests pass (50 tests across 6 files, 0 failing). bun x tsc --noEmit printed only "bun is unable to write files to tempdir: ReadOnlyFileSystem" here, so I don't have a clean type-check result.

Pull description

wait_for is now the only MCP tool for making an issue wait for another or stop waiting (stop: true). It calls the same linkIssue and unlinkIssue actions as the issue page and answers in the page's words, with the issue's URL. link and unlink are removed from the tool list, docs/MCP.md and the ordered list in test/mcp.test.ts, and their test now runs through wait_for. I know of no client that calls them. The tool list isn't alphabetical, so wait_for sits in link's old place, after labels. To try it, call wait_for with { app, issue, blockedBy }, then again with stop: true, and check map for the edge.

Approved

Approved. wait_for replaces link/unlink with one tool, and it calls the same linkIssue/unlinkIssue actions as the issue page and POST/DELETE …/dependencies. So the who-check (requirePlanner: unreadable gives not_found, a guest gives unauthorized, otherwise a writer or crew member) and the loop, self and cross-repo rules all stay in one place. stop is type-checked before anything else, and issue/blockedBy go through issueArg. The URL is built the way tell and labels build theirs. No SQL or shell is built from input. No new dependency.

On testing: the tests cover a writer linking and unlinking, a loop, a read token, a stranger on a private app (not_found) and a non-writer on a public app (forbidden, in the page's words), plus the bad inputs. Each would fail without the tool. The forge check passed at head dde58242. Nothing else in the repository called link/unlink (I grepped src, docs and plugins), and docs/MCP.md is updated. The terminal's dependency verbs in src/fleet/verbs.ts already share these actions, so both doors are unchanged. I could not open the preview from my sandbox (its host is outside my network allowlist), so this verdict rests on the code and the green check.

One small nit inline. It is not blocking.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs