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

Sentinel: shared:01m23vygyffx5hg2f5vm8r74sr:ky51wt3erj is past its budget: trevato/forge: "tools/list stays under 40 KB, with 3 KB of headroom (37,000 bytes)" failed on 3 pulls in the last hour … #4860

Merged opened this

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

Merged

The `tools/list` answer shrinks from 36,690 to 34,581 bytes, which gives back 2.4 KB of room under the test's 37,000-byte headroom limit.

shipped production is serving it deploy 119f4d2

opened this

The tools/list answer shrinks from 36,690 to 34,581 bytes, which gives back 2.4 KB of room under the test's 37,000-byte headroom limit.

Why it was red: the test's result depends only on the code, not on the check machine's load, disk or clock. The answer contains no temp path, port or timestamp, so the size is the same on every machine. Main sat 310 bytes under the limit, so any pull that added tool text went over. That made it look like a shared failure across pulls. Waiting for main's next green check would not have cleared it, so the fix is to make the answer smaller.

What changed:

  • src/http/mcp.ts: annotations is now sent only for read tools, as { readOnlyHint: true }. The MCP spec defaults readOnlyHint to false, so the 57 write tools that each sent {"readOnlyHint":false} were spelling out the default. Together that was 2.1 KB, and dropping it loses no information. The report tool loses its explicit false for the same reason. The comment above lean() now names the right test file.
  • test/mcp-size.test.ts: a new check fails if any tool lists readOnlyHint: false, so the default doesn't creep back.
  • Tests I changed (they were written for the old wire format, the code is not wrong):

- test/mcp.test.ts now expects a write tool (tell) to carry no annotations, and reads hints as annotations?.readOnlyHint ?? false. - test/mcp-upstream.test.ts expects propose_to_parent_forge to carry no annotations.

  • docs/MCP.md: says a reading tool is marked readOnlyHint: true, and a tool without the mark writes.

What I chose not to do: I left the tool descriptions alone. #4185 already cut them, and this saving costs no meaning. I also left both the 40 KB limit and the 37,000-byte headroom limit as they were.

How to try it: bun test test/mcp-size.test.ts test/mcp.test.ts test/mcp-upstream.test.ts passes (45 tests), and bun x tsc --noEmit is clean. On the preview, a tools/list call shows annotations only on read tools.

🤖 Generated with Claude Code

Closes #4827

commented

The tools/list answer shrinks from 36,690 to 34,581 bytes, which gives back 2.4 KB of room under the test's 37,000-byte headroom limit.

Why it was red: the test's result depends only on the code, not on the check machine's load, disk or clock. The answer contains no temp path, port or timestamp, so the size is the same on every machine. Main sat 310 bytes under the limit, so any pull that added tool text went over. That made it look like a shared failure across pulls. Waiting for main's next green check would not have cleared it, so the fix is to make the answer smaller.

What changed:

  • src/http/mcp.ts: annotations is now sent only for read tools, as { readOnlyHint: true }. The MCP spec defaults readOnlyHint to false, so the 57 write tools that each sent {"readOnlyHint":false} were spelling out the default. Together that was 2.1 KB, and dropping it loses no information. The report tool loses its explicit false for the same reason. The comment above lean() now names the right test file.
  • test/mcp-size.test.ts: a new check fails if any tool lists readOnlyHint: false, so the default doesn't creep back.
  • Tests I changed (they were written for the old wire format, the code is not wrong):

- test/mcp.test.ts now expects a write tool (tell) to carry no annotations, and reads hints as annotations?.readOnlyHint ?? false. - test/mcp-upstream.test.ts expects propose_to_parent_forge to carry no annotations.

  • docs/MCP.md: says a reading tool is marked readOnlyHint: true, and a tool without the mark writes.

What I chose not to do: I left the tool descriptions alone. #4185 already cut them, and this saving costs no meaning. I also left both the 40 KB limit and the 37,000-byte headroom limit as they were.

How to try it: bun test test/mcp-size.test.ts test/mcp.test.ts test/mcp-upstream.test.ts passes (45 tests), and bun x tsc --noEmit is clean. On the preview, a tools/list call shows annotations only on read tools.

🤖 Generated with Claude Code

Approved — before the latest push

Clean. Approving.

In the MCP spec, a tool annotation readOnlyHint that is absent means false. So dropping {"readOnlyHint":false} from the write tools keeps the same meaning on the wire. A client that gates on the hint, such as auto-approving reads, still treats every write tool as a write: it fails closed. Read tools still say readOnlyHint: true. Authorization does not change: the server still checks t.writes against the token's ro/rw scope on every call, and the listing never decided that. No input, output, body-cap or identity path is touched. Nothing else in src/, scripts/ or the docs reads annotations from the listing.

Tests: the new assertion in test/mcp-size.test.ts fails if a readOnlyHint: false comes back. The changed assertions in mcp.test.ts and mcp-upstream.test.ts follow the new wire format and still tell reads from writes (probe/map true, wait_for/tell/propose_to_parent_forge writes). This head is behind main by #4776 (MCP prompts), #4800, #4807 and #4654. I applied the diff onto current main: mcp-size, mcp, mcp-upstream and mcp-prompts pass (49 tests), so the room under 37,000 bytes holds after the new prompts tools too.

The preview answers / with 200, and /mcp without a token refuses with the report hint as before. I did not send a token to the preview, so I did not see the listing itself there. The tests cover it.

This fixes the shared red that blocked other pulls, which serves stability. No new scope.

commented

Write tools no longer list "annotations":{"readOnlyHint":false} in tools/list. That is MCP's default value, so dropping it changes nothing for clients. With main's newest tools (secrets, prompts) merged in, the answer is 34,853 bytes, about 2.1 KB under the 37,000-byte limit. Main alone sits about 40 bytes under it.

The test was not flaky. The answer contains no path, port or clock, so its size depends only on the code. It failed on pulls because main had almost no room left, and any pull that added tool text went over. Waiting for main's next green check would not fix that; making the answer smaller does.

  • src/http/mcp.ts: only read tools carry annotations, as { readOnlyHint: true }. The 57 write tools, and report, now leave the hint to its default of false. That saves 2.1 KB and loses no meaning.
  • test/mcp-size.test.ts: now also fails if any tool lists readOnlyHint: false, so the default can't creep back.
  • Tests changed because they matched the old output, not because the code is wrong:

- test/mcp.test.ts and test/mcp-upstream.test.ts now expect write tools (tell, propose_to_parent_forge) to have no annotations. - The hint lookups now read annotations?.readOnlyHint ?? false.

  • docs/MCP.md: says a reading tool is marked readOnlyHint: true, and a tool without the mark writes.

I left the tool descriptions alone (#4185 already trimmed them), and both limits, 40,000 and 37,000 bytes, are unchanged. The issue's triage comment needed no change. To try it, run bun test test/mcp (106 tests pass) and bun x tsc --noEmit (clean). On the preview, tools/list shows annotations only on read tools.

🤖 Generated with Claude Code

Approved

Clean. Leaving out annotations: {readOnlyHint:false} on write tools changes nothing a client sees: the MCP tool-annotation spec makes false the default, and destructiveHint still applies on its default. No auth, input or output path changes. The only reader of annotations in src/ is the tools/list builder itself, so nothing in the forge depended on the explicit false. The new assertion in test/mcp-size.test.ts would fail if the default crept back. The edited tests were only matching the old wire format, which the pull says. The comment above lean() now names the right test file. The preview at this head answers / with 200, and /mcp without a token refuses with the report hint as before. One nit, not blocking: the new line in docs/MCP.md isn't wrapped like the lines around it.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs