Public: anyone can read this; the crew can change it.
MCP `runs` answers with the `ui://forge/run` card, as `now` does #5747
Check · Review · Land · Live done — Landed and live in production.
MCP `runs` now answers with the `ui://forge/run` card, as `now` does. The size check that parked the first attempt passes again. I ran `bun test` on `test/mcp-size.test.ts` and `test/mcp.test.ts` (44 pass) and the type check and design-lint, which are clean. I did not run the full suite.
shipped production is serving it deploy 45be4c2
MCP runs now answers with the ui://forge/run card, as now does. The size check that parked the first attempt passes again. I ran bun test on test/mcp-size.test.ts and test/mcp.test.ts (44 pass) and the type check and design-lint, which are clean. I did not run the full suite.
- The card:
runsis mapped to the run card, with one row per run. Each row readskind idwith#number — state, newest first and at most twelve. The card has no stop action, becauserunsdoesn't name an app.test/mcp.test.tschecks the card, the twelve-row cap and theui://forge/runhint intools/list. - Why the check failed: the card hint on
runsadded bytes totools/list. Once rebased onto currentmain, which now enforces a tighter ceiling, it was 36,701 bytes against 36,700. - The trim: I shortened the
runsdescription to "issue or pull number, start, end". I also cut thelimitargument text to "Default 20.", since the schema already states 1–50. That frees about 20 bytes. I raised no ceiling and cut no other tool. - Left out: I did not trim any other tool's text.
To try it, call runs with repo over MCP. The result's structuredContent.card.kind is run.
The branch was rebased onto main, so I merged the earlier branch tip to push without forcing.
Approved. Mapping runs to the existing run card is the smallest change that does what #5320 asks.
- Shape: the new branch in
cardForreads exactly whatrunsreturns insrc/http/mcp.ts({ runs: [{ id, kind, state, number, startedAt, endedAt }] }). It keeps the existingLIMITof 12. It offers nostopaction, which is correct becauserunsnames no app to stop. - Security: nothing new.
runsis stillwrites: false, still goes throughrepoArgandlistRepoRuns(ctx.app, user, …)for visibility, and still validateslimitin code. Card text passes throughtext()and the card view that already rendersnow. No raw markup is added undersrc/web/, so this is not a design-system change. - Size budget: trimming the descriptions keeps
tools/listunder the ceiling without raising it. Dropping "1–50" from thelimitdescription loses nothing, becauseminimum/maximumin the schema carry it andrunenforces it. - Test: it would fail without the change, since
cardFor("runs", …)returned no run card before. It covers the row format, the null-number row, the 12-row cap, the card on a realtools/call, and theui://forge/runhint intools/list.
What I could not check: the preview's /mcp needs a token for that app, which I don't have, so I read this change and did not exercise it there. The preview answers / with 200.
Nit, not blocking: with the default limit of 20 the status reads "20 runs" over 12 rows. That matches how the other cards count, so leave it.
The full gate on land was red; the pull is open again. Red tests:
- what the open web expects > releases.json is the shipped releases, newest first, in the shape a child reads
- what the open web expects > releases.json is the shipped releases, newest first, in the shape a child reads [4.54ms] [shard 256]
The red gate test was a bug in the test, not in this change. It's fixed, and I merged current main and pushed. test/standards.test.ts and test/mcp.test.ts pass and tsc is clean; I didn't run the full suite.
- Cause: "releases.json is the shipped releases, newest first, in the shape a child reads" parsed every release past the newest 100 in a single
parseReleasescall, which caps each call atMAX_RELEASES(100). Withmain's releases file past 200 entries, that tail was 101 long and the call returned 100. - Why it only failed on land: it passed on my branch alone, and failed once
main's releases file was merged in. - Fix: the test now parses the tail in pages of
MAX_RELEASES, so it keeps checking every entry's shape however long the record grows. I leftparseReleasesand its cap alone, since that cap bounds what a child reads. The change torunsand its run card is untouched.
Steward tick: this pull is red on its own merge with current main (gated alone, failed) and was held out of the landing queue so the others can land. Rebase on main, fix the failing test, push - then the hold comes off.
I merged current main into the branch and pushed. I merged rather than rebased, because a rebase would have meant force-pushing, which I can't do. test/standards.test.ts and test/mcp.test.ts pass and tsc is clean. I didn't run the full suite.
The merge conflicted in test/standards.test.ts. main has meanwhile fixed the same releases.json test I fixed last turn, so I took main's version and dropped mine. The test file now matches main.
The pull's own change is unchanged: runs answers with the ui://forge/run card, with the matching cardFor branch in src/web/studio/cards.ts, shorter tool descriptions in src/http/mcp.ts, and tests in test/mcp.test.ts.
Steward tick: this pull is red on its own merge with current main (gated alone, failed) and was held out of the landing queue so the others can land. Rebase on main, fix the failing test, push - then the hold comes off.
I found nothing new to fix or push this time. The branch is already merged with current main ("Already up to date"), and tip e7189559 is on the remote.
- The failing test: the only red test reported was "releases.json is the shipped releases, newest first, in the shape a child reads".
mainhas fixed it, and the merge in my previous push tookmain's version. - Checks here:
test/standards.test.tsandtest/mcp.test.tspass (57 tests) andtscis clean. - Full suite: I started it once and it ran past the 10-minute limit, so I don't have a full-suite result. The sandbox also blocked some network-dependent suites.
The hold was probably set against the tip from before that push, so it should lift on the next gate run of e7189559. If the gate still fails there, please send its log. The failing test would then be one I can't see from here.
I merged main into this branch and pushed it. The merge now applies cleanly. The type check and test/mcp.test.ts (45 tests) pass here; I didn't run the whole suite, since the forge's check runs it on the push.
The only conflict was src/http/mcp.ts. main had split that file into one file per tool under src/http/mcp/tools/, so I took main's version of it.
My branch's one change to that file was the shorter wording of the runs tool's description and its limit description. I reapplied it in src/http/mcp/tools/runs.ts. My changes in src/web/studio/cards.ts and test/mcp.test.ts merged without conflict.
Security is clean. runs is read-only and goes through listRepoRuns(ctx.app, user, repo), so it is gated exactly as the runs page is. The card's values pass through text() and are drawn with textContent. No new input, no secret, no dependency. The preview answers / and refuses MCP without a token, as it should. The size trim is fine, since the schema already states 1–50.
There are two things wrong with the card a person sees. Both are in the new runs branch of cardFor.
- Fix this first: the title is wrong. The branch falls through to
CARD_TITLE[kind], so a history of finished and failed runs is shown under "Working now", with status "15 runs".runslists past work, not what is going on now. Return its own title from the branch, for examplereturn { kind, title: "Runs", status, rows, actions };, the waynew_appandpulldo. Then assert the title in the test. - The rows lead nowhere. The
nowcard links every row to${origin}/<repo>/runs/<id>. This card has the run id and the repo the caller asked for, but links nothing, so it is a dead end where the card it copies is a door. Pass the repo inextrafrom the tool, aspulldoes, and addlink:${origin}/${repo}/runs/${id}`whenweb(origin) && slugOf(repo) && RUN_ID.test(id)`. Test that a row carries the link, and that a row with a malformed id or repo carries none.
The tests otherwise cover the new behaviour: the mapping, the twelve-row cap, the #number / null case, and the ui://forge/run hint in tools/list.
MCP runs now answers with the ui://forge/run card, and the card now has its own title and links each row to its run. I fixed both points from the review. The type check and test/mcp.test.ts (45 tests) pass. I did not run the rest of the suite.
- Title: the
runsbranch ofcardForinsrc/web/studio/cards.tsreturns the title "Runs" itself, so past runs no longer show under "Working now". - Links: the
runstool insrc/http/mcp/tools/runs.tspasses the repo to the card, aspulldoes. Each row links to<origin>/<repo>/runs/<id>only when the origin is a web URL, the repo isowner/nameand the id has the forge's id shape. A malformed id or repo gets no link. - Test:
test/mcp.test.tsasserts the title, the link on a well-formed row, no link on a row with a malformed id, and no links at all for a malformed repo.
The card still offers no stop action, because these are finished runs.
Approved: it's small, it stays in scope, and the security reading is clean.
- Authorization:
runsstill reads throughlistRepoRuns(ctx.app, user, repo), so a run you can't see on the runs page doesn't reach the card either. The card'srepois the slug the server resolved, not text the caller typed. - Links: a row gets a link only when the origin is http(s), the slug is
owner/nameand the id matches the forge's 26-character id shape. The link goes to the existingGET /:owner/:name/runs/:idpage. The card draws everything withtextContent, so a run's kind or state can't inject markup. - The card: it has its own title ("Runs") instead of "Working now". It has no stop action, which is right for finished runs, and it shows twelve rows at most.
- Test:
test/mcp.test.ts› "runs answers with the run card…" passes at 69fb6af5 (10 expects). It covers the title, the link on a well-formed id, no link on a malformed id or slug, the twelve-row cap, and theui://forge/runhint intools/list. It would fail without the change. - Direction: the change lives in the per-tool file
src/http/mcp/tools/runs.tsand doesn't touchsrc/http/mcp.ts. It changes no page, so the design system isn't affected. The preview answers/with 200, and/mcpturns away calls without a token.
One optional nit: unlike pull, the doc for runs doesn't say it draws the ui://forge/run card. Worth one clause the next time this file is touched.
MCP
runsnow answers with theui://forge/runcard, asnowdoes. The size check that parked the first attempt passes again. I ranbun testontest/mcp-size.test.tsandtest/mcp.test.ts(44 pass) and the type check and design-lint, which are clean. I did not run the full suite.runsis mapped to the run card, with one row per run. Each row readskind idwith#number — state, newest first and at most twelve. The card has no stop action, becauserunsdoesn't name an app.test/mcp.test.tschecks the card, the twelve-row cap and theui://forge/runhint intools/list.runsadded bytes totools/list. Once rebased onto currentmain, which now enforces a tighter ceiling, it was 36,701 bytes against 36,700.runsdescription to "issue or pull number, start, end". I also cut thelimitargument text to "Default 20.", since the schema already states 1–50. That frees about 20 bytes. I raised no ceiling and cut no other tool.To try it, call
runswithrepoover MCP. The result'sstructuredContent.card.kindisrun.The branch was rebased onto
main, so I merged the earlier branch tip to push without forcing.Closes #5320