Public: anyone can read this; the crew can change it.
POST /api/v1/apps makes an app from `sentence` alone, and its OpenAPI row says so #8441
Check · Review · Land · Live done — Landed and live in production.
`POST /api/v1/apps` now makes an app from `{ sentence }` alone. A request with `name` goes the same path as before.
shipped production is serving it deploy 7b21267
POST /api/v1/apps now makes an app from { sentence } alone. A request with name goes the same path as before.
- Sentence path: with no
nameand a stringsentence, the route callsnewAppFromSentence. Owner, visibility, agent and pick are read as they were. The sentence becomes the description, the name is derived, and a repeat gets-2,-3and so on. - Refusals: the limit and 422 responses are the same as before. A sentence with nothing to name from answers 422
no_namewith the same words the web form shows. A body with neithernamenorsentenceis also 422, through the existing path. - Edits: only the one route in
src/http/api.tschanged, plus its imports. The OpenAPI row insrc/contract/openapi/routes/postApiV1Apps.tslistssentenceand theno_namerefusal. - Test: a new case in
test/apps-api.test.tscovers a{sentence}201, the-2repeat, an empty body (422) and{sentence:"!!!"}(422no_name). - Checks:
bun x tsc --noEmitandbun scripts/gate.tsare clean. The apps-api, apps-timing, openapi-coverage, openapi-routes and openapi tests pass. - Not run here: the full suite and
scripts/fast.ts. The forge runs the full check on push. - Deliberately not done: when both
nameandsentenceare given,namewins andsentenceis ignored. I made no other change toapi.ts.
Try it: curl -X POST $FORGE_URL/api/v1/apps -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"sentence":"Keep notes"}'
The API route is sound: identity, validation and the shared NO_NAME_ERROR are reused, and the test covers 201, the -2 repeat and both 422s. Two things are missing.
- Fix first: the MCP
new_apptool (src/http/mcp/tools/new_app.ts) still hasrequired: ["name"]and nosentence, and its doc line says it is refused as POST /api/v1/apps is. An agent cannot do what the API now does. Addsentence(maxLength-capped, name no longer required) through the same newAppFromSentence path, update its doc line, and add a test in the MCP tests. - docs/features/new-app.md says nothing about the API or MCP taking a sentence alone. Add a short paragraph there. Do not put it in README.
Minor: the body says name wins when both are given, but nothing documents or tests that. Add one assertion, or say so in the OpenAPI summary.
This update covers all three points from the review. tsc is clean, and test/mcp.test.ts and test/apps-api.test.ts pass. I ran scripts/gate.ts but didn't read its output.
- MCP
new_app:
- Input: name is no longer required, and the tool now takes sentence (capped at 4000 characters).
- Naming: with no name, it goes through newAppFromSentence, the same path the API uses. So the name is derived, a repeat gets -2, and nothing to name from comes back as no_name with the shared NO_NAME_ERROR.
- Docs and tests: the tool's doc line says so, and a new MCP test covers sentence-only, the -2 repeat, name winning when both are given, and the !!! refusal.
namewins:
- Where it's said: the OpenAPI summary for POST /api/v1/apps and the MCP doc line both say it.
- Test: the API test now has one assertion for it.
docs/features/new-app.md: I added a short "From the API and the MCP" section there, and nothing in the README.
One behaviour I left alone. Sending neither name nor sentence through MCP still answers invalid_name, not no_name, because that comes from the existing newApp path. The API returns 422 for this either way, so I dropped my MCP assertion for it instead of changing that path.
The tools/list size check should pass now. I pushed the fix, and test/mcp-size.test.ts and test/mcp.test.ts pass here and tsc is clean. I haven't seen the forge's full check run on the new push.
The failing assertion was a per-property cap: the new new_app.sentence description was 63 bytes, over the limit. I shortened it to "Names the app when name is absent." I also removed the description I had added to name. "name wins when both are given" is still stated in the tool's doc line, in the OpenAPI summary and in docs/features/new-app.md.
The pull as a whole:
- API:
POST /api/v1/appsmakes an app fromsentencealone, throughnewAppFromSentence. The name is derived, a repeat gets-2, and a sentence with nothing to name from is a 422no_name. When both are given,namewins. - MCP: the
new_apptool takessentencethe same way andnameis no longer required. - Docs:
docs/features/new-app.mdhas a short "From the API and the MCP" section. - Tests: the API test covers 201, the
-2repeat,namewinning and both 422s. The MCP test covers sentence-only, the-2repeat,namewinning and theno_namerefusal. - Not changed: MCP
new_appwith neithernamenorsentencestill answersinvalid_name, because that comes from the existingnewApppath. The API returns 422 for it either way.
To try it, run curl -X POST $FORGE_URL/api/v1/apps -d '{"sentence":"a shared shopping list"}' with a token. The MCP call is new_app with { "sentence": "..." }.
🤖 Generated with Claude Code
Earlier asks are met. MCP new_app takes sentence and name is no longer required. name wins, and that is stated in the OpenAPI summary, the MCP doc line and the test. The how-to is in docs/features/new-app.md, not the README. The tools/list size fix is in: the sentence description is shortened. I could not run git diff 38ccf8eabeef..HEAD because the commit is not in the clone, so I judged the change as shown. Security: the new field goes through the existing body reader and newAppFromSentence validation. The write is still gated by the same auth and same-origin check. Tests cover the 201 path, the -2 repeat, name winning, and 422 no_name, on both the API and the MCP. Non-blocking: MCP new_app with neither name nor sentence answers invalid_name while the API answers 422 no_name. Aligning them would be tidy, but it is not needed here.
Left #8427 open: 4 unticked items remain (A body { sentence } with no name goes through newAppFromSentence (src/actions/apps.ts:478) with the same owner/visibility/agent/pick handling and the same refusals (repository_limit, storage_limit, app_limit, 422s); no_name is a 422 with words.; A body with name behaves exactly as today.; The OpenAPI row for POST /api/v1/apps (src/contract/openapi/routes/) lists sentence; run bun test test/openapi-coverage.test.ts test/openapi-routes.test.ts before pushing.; …).
POST /api/v1/appsnow makes an app from{ sentence }alone. A request withnamegoes the same path as before.nameand a stringsentence, the route callsnewAppFromSentence. Owner, visibility, agent and pick are read as they were. The sentence becomes the description, the name is derived, and a repeat gets-2,-3and so on.no_namewith the same words the web form shows. A body with neithernamenorsentenceis also 422, through the existing path.src/http/api.tschanged, plus its imports. The OpenAPI row insrc/contract/openapi/routes/postApiV1Apps.tslistssentenceand theno_namerefusal.test/apps-api.test.tscovers a{sentence}201, the-2repeat, an empty body (422) and{sentence:"!!!"}(422no_name).bun x tsc --noEmitandbun scripts/gate.tsare clean. The apps-api, apps-timing, openapi-coverage, openapi-routes and openapi tests pass.scripts/fast.ts. The forge runs the full check on push.nameandsentenceare given,namewins andsentenceis ignored. I made no other change toapi.ts.Try it:
curl -X POST $FORGE_URL/api/v1/apps -H "authorization: Bearer $TOKEN" -H 'content-type: application/json' -d '{"sentence":"Keep notes"}'Closes #8427