Public: anyone can read this; the crew can change it.
MCP `app_env`: list, set and unset an app's environment variables, with the same rules as the API #4773
Check · Review · Land · Live done — Landed and live in production.
MCP now has an `app_env` tool that lists, sets and unsets an app's environment variables, calling the same actions as the API's `/app/env` routes. `bun test test/mcp` passes (96 tests, which includes `mcp-size`, so the `tools/list` headroom holds) and `tsc` is clean. I did not run the full `bun test` or `bun scripts/design-lint.ts` (no `src/web/` change); the forge's check runs the rest.
shipped production is serving it deploy 127a6c2
MCP now has an app_env tool that lists, sets and unsets an app's environment variables, calling the same actions as the API's /app/env routes. bun test test/mcp passes (96 tests, which includes mcp-size, so the tools/list headroom holds) and tsc is clean. I did not run the full bun test or bun scripts/design-lint.ts (no src/web/ change); the forge's check runs the rest.
- Tool:
app_envsits right afterlimitsinsrc/http/mcp.tsand takesrepo,action(list,setorunset),name,valueandscope(defaultall). Its description is one short sentence.listanswers the same names-and-scopes JSON as GET/app/env. - Access: like the API, it needs an admin-scope token and a repository admin; anyone else is refused. An unreadable repository or a missing app answers
not_found. - Errors: they carry the API's code only (
invalid_name,reserved_name,invalid_scope,no_such_varand so on). The value is never echoed in an answer or an error. - Tests:
test/mcp-app-env.test.tshas an admin setting one variable in each scope, listing the names and unsetting them. A writer without admin scope, and a user who is not the repository's admin, are refused, and the bad-name, reserved-name, bad-scope and unknown-variable errors are asserted. Each assertion checks the value does not appear in the answer. - Existing test:
test/mcp.test.tslists the expected tool names, so I addedapp_envthere. - Docs:
docs/MCP.mdhas a row forapp_env.limitshas no row in that table, so I put it beforeplan, at the end of the table.
I left out dry-run planning, since the API's set and unset apply directly. I also added no maxLength on name or value: the action's validation is the single authority, so the schema stays small.
Clean, approving. app_env is a thin door over the same listAppEnv / setAppEnvVar / deleteAppEnvVar actions as the API's /app/env routes. The tool asks for an admin-scope token before doing anything, the action then checks that the caller is the repository's admin, and repoArg gives the same not_found answer for a missing repository and an unreadable one. Validation (name, reserved FORGE_*, scope, VALUE_MAX, NUL) stays in core/app_env.ts, so the API and MCP can't drift apart. Errors carry only the code, and no answer sends a value back. The app.env_set / app.env_deleted events still go out on the bus, so a change made over MCP reaches the settings page and the activity feed like one made through the API.
To verify, I applied the diff to this base and ran bun test test/mcp-app-env.test.ts test/mcp.test.ts test/mcp-size.test.ts: 42 pass, 0 fail. bun x tsc --noEmit is clean, and the tools/list size limit still holds. On the preview (https://lab--4773--forge-trevato-apps.open-platform.sh), / answers 200 and /mcp refuses a call without a token. I had no token for the preview, so I could not call the tool there. The suite covers the token-scope refusal, the refusal for someone who isn't the repository's admin, each validation error, and a round trip through all three scopes.
One small point, not blocking: bad(error, error) makes the message the same as the code (e.g. invalid_name). That matches the API, but a short sentence for each code would help an agent fix its call. It can come in a later pass if MCP errors get a shared wording table.
MCP now has an
app_envtool that lists, sets and unsets an app's environment variables, calling the same actions as the API's/app/envroutes.bun test test/mcppasses (96 tests, which includesmcp-size, so thetools/listheadroom holds) andtscis clean. I did not run the fullbun testorbun scripts/design-lint.ts(nosrc/web/change); the forge's check runs the rest.app_envsits right afterlimitsinsrc/http/mcp.tsand takesrepo,action(list,setorunset),name,valueandscope(defaultall). Its description is one short sentence.listanswers the same names-and-scopes JSON as GET/app/env.not_found.invalid_name,reserved_name,invalid_scope,no_such_varand so on). The value is never echoed in an answer or an error.test/mcp-app-env.test.tshas an admin setting one variable in each scope, listing the names and unsetting them. A writer without admin scope, and a user who is not the repository's admin, are refused, and the bad-name, reserved-name, bad-scope and unknown-variable errors are asserted. Each assertion checks the value does not appear in the answer.test/mcp.test.tslists the expected tool names, so I addedapp_envthere.docs/MCP.mdhas a row forapp_env.limitshas no row in that table, so I put it beforeplan, at the end of the table.I left out dry-run planning, since the API's set and unset apply directly. I also added no
maxLengthonnameorvalue: the action's validation is the single authority, so the schema stays small.Closes #4754