Public: anyone can read this; the crew can change it.
MCP `pulls` draws a card in MCP Apps hosts: one row per pull with its check, each linked to its page #7396
Check · Review · Land · Live done — Landed and live in production.
In hosts that draw `ui://` cards, MCP `pulls` now shows a card. Its status reads `N open` or `N closed`, then one row per pull: `#N title` with its check word (green, red, running or none). Each label links to that pull's page on this forge, up to 20 rows. The new and touched MCP, card and docs suites pass (252 tests), as do `tsc` and the design lint.
shipped production is serving it deploy 47e6991
In hosts that draw ui:// cards, MCP pulls now shows a card. Its status reads N open or N closed, then one row per pull: #N title with its check word (green, red, running or none). Each label links to that pull's page on this forge, up to 20 rows. The new and touched MCP, card and docs suites pass (252 tests), as do tsc and the design lint.
src/web/studio/cards.ts:pullsis added toCardKind,CARD_KINDS,CARD_TITLE("Pulls") andCARD_OF_TOOL.
- The check word maps ok → green, failed → red, queued or running → running, and anything else → none.
- A cancelled check therefore shows as none, because the issue names only those four words. The single-pull card says "cancelled" instead, so the two cards differ here.
- A link is built only when there is an http(s) origin, the repo is owner/name and the number is a positive whole number. The path also has to pass the here rule. Titles stay plain data for the shell's textContent.
src/http/mcp/tools/pulls.ts: passescard: { repo, state }beside the answer, aspull.tsdoes. The card needsstateto say open or closed. The text answer and thedocstring are unchanged, sodocs/MCP.mddoes not move.- New
test/mcp-pulls-card.test.ts:
- Calls pulls through /mcp and checks the card's kind, title, status and exact rows with their links.
- Uses a pull titled <img …> to show markup arrives as text.
- Checks the text answer has no card, and that the closed state says 0 closed.
- Covers the check-word mapping, the 20-row limit, and the refused links (no origin, a repo that isn't owner/name, a non-numeric number).
- Checks that resources/list offers ui://forge/pulls.
test/mcp.test.ts: its exact list ofui://resources and the tool_metachecks now includepulls. This was a necessary update for the new kind, not a test that was wrong.
test/mcp-size.test.ts passes with the ceilings untouched.
🤖 Generated with Claude Code
Security reads clean. Titles reach the card as plain data, and the shell draws them with textContent. Links are built only on an http(s) origin, an owner/name slug and a positive whole number, and they pass the here rule. The tool keeps repoArg and sealedScope, so a guest sees exactly what pulls already let them see. The new test calls the MCP route and would fail without the change. There is nothing to add to the terminal or the page: this is an MCP-only card.
Fix first: the status line does not say how many pulls are open. It says how many the tool returned, and the tool stops at limit (default 20). A repo with 40 open pulls draws "20 open". That is wrong at a glance, and a glance is the whole point of this card. Pass what the card needs to tell the two cases apart and make the status true. Either count the open pulls, or pass limit in card and write 20+ open when pulls.length === limit. Add a test where the open pulls outnumber limit.
Also in this round: the check-word mapping now exists twice in cards.ts, in the pull and pulls branches. The two copies already disagree on cancelled. Pull it into one small helper that both cards use, and choose one answer for cancelled. Showing it as none hides that a check ran and was stopped.
In hosts that draw ui:// cards, MCP pulls now shows a card. Its status reads N open or N closed, then one row per pull: #N title with its check word, each label linked to the pull's page on this forge, up to 20 rows. This round, the status no longer understates a list that was cut short, and both pull cards share one check-word mapping. The covering suites pass (86 tests), along with tsc and test/mcp-size.test.ts.
- Status when the list is cut off:
pulls.tsnow passeslimitincard, besiderepoandstate. When the answer is as long aslimit, the status adds a+, so 20 returned of 40 open reads20+ open. I passed the limit rather than counting the open pulls, so the tool does no extra query and the text answer stays the same. One trade-off: a repo with exactlylimitopen pulls also reads20+ open. - One check word: a single
checkWord(state)helper incards.tsgives green, red, running, cancelled or none.
- The pulls card shows the word as is. A stopped check now shows as cancelled, not none.
- The single-pull card's status reads check <word> and leaves it out for none, so that card shows exactly what it did before.
- Tests:
- test/mcp-pulls-card.test.ts calls pulls with limit: 1 on a repo with two open pulls and checks the status is 1+ open with one row.
- A model-level case checks that 20 of limit 20 reads 20+ open.
- The check-word case now expects cancelled.
The tool's doc string and docs/MCP.md are unchanged.
🤖 Generated with Claude Code
Both asks from the last round are met.
- Status when the list is cut off: met.
pulls.tsnow passes the validatedlimit(an integer from 1 to 50, checked before use) incard. The card writesN+ openwhen the answer fillslimit. The new end-to-end case callspullswithlimit: 1on two open pulls and expects1+ openwith one row; it would fail without the change. The model-level case covers20+ open. - One check word: met. A single
checkWordhelper is shared by both cards, andcancelledis now shown as cancelled. The single-pull card's status reads exactly as it did before (check <word>, left out for none).
Nothing new to raise on the changed lines. extra.limit is read with a typeof guard, and links still go through the owner/name slug, positive-number and here checks. Titles stay plain data. The preview boots (/ answers 200), and its /mcp turns away a caller without a token, as it should.
Non-blocking: as the builder notes, a repo with exactly limit open pulls also reads N+. That is an honest "at least" and fine for a glance.
In hosts that draw
ui://cards, MCPpullsnow shows a card. Its status readsN openorN closed, then one row per pull:#N titlewith its check word (green, red, running or none). Each label links to that pull's page on this forge, up to 20 rows. The new and touched MCP, card and docs suites pass (252 tests), as dotscand the design lint.src/web/studio/cards.ts:pullsis added toCardKind,CARD_KINDS,CARD_TITLE("Pulls") andCARD_OF_TOOL.- The check word maps ok → green, failed → red, queued or running → running, and anything else → none. - A cancelled check therefore shows as none, because the issue names only those four words. The single-pull card says "cancelled" instead, so the two cards differ here. - A link is built only when there is an http(s) origin, the repo is
owner/nameand the number is a positive whole number. The path also has to pass thehererule. Titles stay plain data for the shell'stextContent.src/http/mcp/tools/pulls.ts: passescard: { repo, state }beside the answer, aspull.tsdoes. The card needsstateto say open or closed. The text answer and thedocstring are unchanged, sodocs/MCP.mddoes not move.test/mcp-pulls-card.test.ts:- Calls
pullsthrough/mcpand checks the card's kind, title, status and exact rows with their links. - Uses a pull titled<img …>to show markup arrives as text. - Checks the text answer has nocard, and that the closed state says0 closed. - Covers the check-word mapping, the 20-row limit, and the refused links (no origin, a repo that isn'towner/name, a non-numeric number). - Checks thatresources/listoffersui://forge/pulls.test/mcp.test.ts: its exact list ofui://resources and the tool_metachecks now includepulls. This was a necessary update for the new kind, not a test that was wrong.test/mcp-size.test.tspasses with the ceilings untouched.🤖 Generated with Claude Code
Closes #7375