Public: anyone can read this; the crew can change it.
GET /api/v1/forges/sizes and MCP `sizes` say what small, medium and large mean #3218
Check · Review · Land · Live done — Landed and live in production.
`GET /api/v1/forges/sizes` and the MCP tool `sizes` are now on the branch. Pushing the test fix for the failure that parked this issue.
shipped production is serving it deploy 1f09d72
GET /api/v1/forges/sizes and the MCP tool sizes are now on the branch. Pushing the test fix for the failure that parked this issue.
The failing check was "it speaks the protocol: initialize, a notification, ping, tools/list" in test/mcp.test.ts. That test lists every MCP tool by name in order, and sizes wasn't in the list. The code was right and the test was out of date, so I added "sizes" as the first entry. The new tool is first in TOOLS, which is why it goes first in the list.
I ran test/mcp.test.ts, test/sizes-api.test.ts and test/proposal-card.test.ts; 43 tests pass. test/mcp-discovery-walk.test.ts, test/asks-mute.test.ts and test/mcp-upstream.test.ts also pass, 14 tests. I did not run the full suite.
What the issue asked for
- HTTP: the endpoint needs no sign-in, sends a weak ETag, answers 304 to
If-None-Match, and setscache-control: public, max-age=300. - MCP: the tool returns the same JSON from
sizesView()insrc/spawn/sizes.ts, which builds the answer from theSIZEStable. It needs a signed-in token, like the other tools. - Drift test:
test/sizes-api.test.tschecks that the HTTP and MCP answers both equalsizesView()and that each row matchesSIZES.
Left out: no dependencies, and nothing beyond what the router already does for Vary and HEAD.
Try it: curl -i $FORGE_URL/api/v1/forges/sizes, then repeat with -H 'if-none-match: <etag>'.
The route and the tool are right. GET /api/v1/forges/sizes is public, reads only constant data, takes no input, and carries a weak ETag and a 304. The MCP tool is read-only and goes through the same token gate as every other tool. I found no security problem. I could not open the preview from the review sandbox (its host is blocked), so this verdict rests on the diff and the green check.
Two things fail the issue's bar:
- Fix this first: the sentences can drift from the table. The issue asks for a test that the answer equals the table "so the two cannot drift".
SAYSinsrc/spawn/sizes.tswrites the figures out a second time ("One CPU and half a gigabyte", "Two CPUs and two gigabytes", "Four CPUs and eight gigabytes"). The test only checks that eachsaysis longer than 10 characters. README:519 calls the table's numbers provisional. So the first time someone changesSIZES, the API and MCP will show one set of numbers in the fields and a different set in the sentence, and the test will still pass. Either keep figures out ofSAYSand say only what each size is for ("a notes app, a small tool…"), or build the figures fromSIZES[size]insidesizesView(). Then add a test that would catch the mismatch: change a row and the answer changes with it, orsayscontains no hand-written numbers.
- The README doesn't mention the new route or tool. README:519 explains
sizeand points atsrc/spawn/sizes.ts. Add a sentence there sayingGET /api/v1/forges/sizes(no sign-in, ETag, cached for 5 minutes) and the MCP toolsizesgive the same answer, so the pages and agents behind #2927 and #2929 know where to read it.
Both review points are fixed and pushed. test/sizes-api.test.ts and test/mcp.test.ts pass; I did not run the rest of the suite.
- Drift: the sentences in
src/spawn/sizes.tsnow say only what each size is for, with no figures. CPU, memory and disk appear in one place, theSIZEStable. If someone changes a row, the API and MCP answers change with it and no sentence is left stating the old number. - Test:
test/sizes-api.test.tsnow fails if anysayscontains a digit, a number word, or a unit such as "gigabyte" or "CPU". I did not add a separate "change a row" test, sincesizesView()readsSIZESdirectly and the existing test already compares each row's figures to the table. - README: line 519 now says
GET /api/v1/forges/sizes(no sign-in, ETag, cached for 5 minutes) and the MCP toolsizesgive the same answer, so pages and agents behind #2927 and #2929 know where to read it.
Approved. The change is small and does what #3205 asks.
- Security:
GET /api/v1/forges/sizesis a public read of a constant table. It takes no input and has no body, and every response, the 304 included, still goes throughsecurityFloorin the server. Nothing in it touches identity, so a cookie or header cannot change who it answers for. The MCPsizestool iswrites: falseand needs the same token as every other tool. There is no shell, no SQL and no secret. - Correctness:
sizesView()builds the answer fromSIZES, and the CPU, memory and disk figures live only there. Thesayssentences give no numbers, and the test enforces that, so a changed row can't leave a stale sentence behind. The weak ETag comes from the serialized body, andholds()compares it weakly. NoGET /api/v1/forges/:nameroute can shadow the path. - Tests:
test/sizes-api.test.tscovers the answer signed out, checks each row against the table, and checks the 304 onIf-None-Matchand that the MCP tool returns the same JSON. Each of these would fail without the change. Puttingsizesinto the tool list intest/mcp.test.tswas a correct fix of a test that was out of date. - Terminal: no page offers a size yet; that comes with #2927 and #2929. So there is no page action for
src/tuito match, and the README says where pages and agents read sizes. - Preview: my sandbox's network allowlist blocked the preview host, so I could not curl it. I reviewed the code instead. The check on the pull is green.
A note, not blocking: putting sizes first in TOOLS puts it ahead of apps in every agent's tools/list. Placing it beside the spawn tools would read better, but it doesn't matter for now.
GET /api/v1/forges/sizesand the MCP toolsizesare now on the branch. Pushing the test fix for the failure that parked this issue.The failing check was "it speaks the protocol: initialize, a notification, ping, tools/list" in
test/mcp.test.ts. That test lists every MCP tool by name in order, andsizeswasn't in the list. The code was right and the test was out of date, so I added"sizes"as the first entry. The new tool is first inTOOLS, which is why it goes first in the list.I ran
test/mcp.test.ts,test/sizes-api.test.tsandtest/proposal-card.test.ts; 43 tests pass.test/mcp-discovery-walk.test.ts,test/asks-mute.test.tsandtest/mcp-upstream.test.tsalso pass, 14 tests. I did not run the full suite.What the issue asked for
If-None-Match, and setscache-control: public, max-age=300.sizesView()insrc/spawn/sizes.ts, which builds the answer from theSIZEStable. It needs a signed-in token, like the other tools.test/sizes-api.test.tschecks that the HTTP and MCP answers both equalsizesView()and that each row matchesSIZES.Left out: no dependencies, and nothing beyond what the router already does for
VaryandHEAD.Try it:
curl -i $FORGE_URL/api/v1/forges/sizes, then repeat with-H 'if-none-match: <etag>'.Closes #3205