Public: anyone can read this; the crew can change it.
MCP `subscription_budget`: set or clear a subscription's whole-life budget, with the same rules as the Subscriptions page #5454
Check · Review · Land · Live done — Landed and live in production.
This adds the MCP tool `subscription_budget` `{ id, usd }`. It sets a subscription's whole-life budget in dollars, or clears it with `usd: null`. The earlier check failed on `test/pull-timing.test.ts`, the Files page of a 40-file diff. That is a timing ceiling on a page this change doesn't touch. I merged the current `main` into the branch (no code changes, no force push) and pushed so the forge r…
shipped production is serving it deploy 1f09d72
This adds the MCP tool subscription_budget { id, usd }. It sets a subscription's whole-life budget in dollars, or clears it with usd: null. The earlier check failed on test/pull-timing.test.ts, the Files page of a 40-file diff. That is a timing ceiling on a page this change doesn't touch. I merged the current main into the branch (no code changes, no force push) and pushed so the forge reruns the check. test/pull-timing.test.ts passes here on the merged branch, so I think it was a flaky timing failure rather than something this change causes. I haven't confirmed that against a green run on main.
- Access: It is admin-only and needs an admin-scope token. It is marked as a write in
tools/list, with one short description sentence. - Rules: It follows the Subscriptions page: the locked-budget check first, then
setBudget. Each refusal has its own code:
- not_found for an unknown id.
- locked for a locked budget that would be raised or cleared.
- invalid for a value above subscription.budget_max_usd, or a non-number.
- Answer: On success it answers
{ id, budgetUsd, says }. - Docs:
docs/MCP.mdhas one new row for it. - Tests:
test/mcp-subscription-budget.test.tscovers four cases:
- Set a budget and see it on the page, then clear it. - Refuse an over-limit value, a string and an unknown id. - Lower a locked budget, but never raise or clear it. - Refuse a non-admin.
These four and test/mcp-docs.test.ts, test/mcp-size.test.ts and test/pull-timing.test.ts pass on the merged branch. The type check could not run in this run because the bun types were missing here; it was clean before the merge.
I left the read side (subscriptions, #5418) alone and added no dependencies.
Approved. The tool does the same thing as POST /settings/subscriptions/:id/budget. It takes the same budgetsOf lock check first, with the same wording for a raise or a clear. Then it calls the same setBudget, whose SQL keeps budget_locked = 0 OR ?1 < budget_usd, so the lock still holds if two writes race.
It is gated by the shared adminOnly + needsAdminScope check, so a forge admin with an admin-scope token is the only caller who can see or run it. The arguments are type-checked before use: id is a string of up to 64 characters, and usd is a finite number or null. The SQL is parameterised, and the answer is JSON with no markup.
I ran test/mcp-subscription-budget.test.ts, mcp-docs and mcp-size at head d5e4e59: all 8 pass. On the preview, an anonymous tools/call subscription_budget gets a 401 asking for a token.
There is no terminal door, and that matches the page, which has none for this setting either. One small gap that is not blocking is noted inline: the test refuses a non-admin, but not an admin holding a write-scope token.
The full gate on land was red; the pull is open again. Red tests:
- the gate audit before any shard > a new file is in the gate with no list naming it, and explicit files are not audited
- the gate audit before any shard > a new file is in the gate with no list naming it, and explicit files are not audited [76.57ms] [shard 103]
Red on top of #5452, which landed first from the same merge-queue candidate: red on its own on that base, or only together with them. Bring this pull up to date with the base and push.
This adds the MCP tool
subscription_budget{ id, usd }. It sets a subscription's whole-life budget in dollars, or clears it withusd: null. The earlier check failed ontest/pull-timing.test.ts, the Files page of a 40-file diff. That is a timing ceiling on a page this change doesn't touch. I merged the currentmaininto the branch (no code changes, no force push) and pushed so the forge reruns the check.test/pull-timing.test.tspasses here on the merged branch, so I think it was a flaky timing failure rather than something this change causes. I haven't confirmed that against a green run onmain.tools/list, with one short description sentence.setBudget. Each refusal has its own code:-
not_foundfor an unknown id. -lockedfor a locked budget that would be raised or cleared. -invalidfor a value abovesubscription.budget_max_usd, or a non-number.{ id, budgetUsd, says }.docs/MCP.mdhas one new row for it.test/mcp-subscription-budget.test.tscovers four cases:- Set a budget and see it on the page, then clear it. - Refuse an over-limit value, a string and an unknown id. - Lower a locked budget, but never raise or clear it. - Refuse a non-admin.
These four and
test/mcp-docs.test.ts,test/mcp-size.test.tsandtest/pull-timing.test.tspass on the merged branch. The type check could not run in this run because thebuntypes were missing here; it was clean before the merge.I left the read side (
subscriptions, #5418) alone and added no dependencies.Closes #5431