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

A talk build is reachable through MCP: `say` starts it, reads its versions and diffs, restores and publishes #11079

Merged opened this

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

Merged

`say` now drives a talk build through `/mcp`, and the work is pushed. `test/mcp-size.test.ts` passes with the ceilings unchanged, as do the new test, the other say/talk and MCP-doc tests, `bun scripts/gate.ts` and `tsc --noEmit`. I did not use the `keep_plan` tool, so no plan is posted on the issue.

shipped production is serving it deploy a985b29

opened this

say now drives a talk build through /mcp, and the work is pushed. test/mcp-size.test.ts passes with the ceilings unchanged, as do the new test, the other say/talk and MCP-doc tests, bun scripts/gate.ts and tsc --noEmit. I did not use the keep_plan tool, so no plan is posted on the issue.

What changed

  • New actions: say takes action: build | build_state | build_diff | build_restore | build_publish, plus two optional arguments, template and version. It is still one tool with an action enum; no tool was added.
  • Same functions as the API: each action calls the same function in src/actions/build.ts that /api/v1 calls. Errors come back as the BUILD_WORDS sentence under the same code, so the per-hour limit and the unpublished-builds cap are the API's too.
  • Input checks: the checks on prompt, template and version are copied from conversation_build.ts, so the MCP and API reject the same input.
  • Access: build_state and build_diff are reads, so an ro token may call them. build, build_restore and build_publish need rw. Another person's conversation answers not_found.
  • The forge's own agent: it is refused these actions because they sit in the existing branch of say that already refuses its via token.
  • Docs and tests: docs/MCP.md is re-rendered with bun scripts/mcp-table.ts. test/mcp-say-build.test.ts drives every action through /mcp on a seeded forge and covers ro refusals, a stranger's not_found, bad input, no-such-version and the hourly limit.

What I left out

  • conversation is required for every action except build. It does not default to your latest conversation, because the API takes an id too.

To try it

  • Call say with {action:"build", text:"a tip calculator", template:"owner/name"}.
  • Then call say with {action:"build_state", conversation:<id>}.

The first push was refused by the pre-push hook, which wants a "Try:" commit. I added an empty commit with that line.

🤖 Generated with Claude Code

Closes #11071

commented

say now drives a talk build through /mcp, and the work is pushed. test/mcp-size.test.ts passes with the ceilings unchanged, as do the new test, the other say/talk and MCP-doc tests, bun scripts/gate.ts and tsc --noEmit. I did not use the keep_plan tool, so no plan is posted on the issue.

What changed

  • New actions: say takes action: build | build_state | build_diff | build_restore | build_publish, plus two optional arguments, template and version. It is still one tool with an action enum; no tool was added.
  • Same functions as the API: each action calls the same function in src/actions/build.ts that /api/v1 calls. Errors come back as the BUILD_WORDS sentence under the same code, so the per-hour limit and the unpublished-builds cap are the API's too.
  • Input checks: the checks on prompt, template and version are copied from conversation_build.ts, so the MCP and API reject the same input.
  • Access: build_state and build_diff are reads, so an ro token may call them. build, build_restore and build_publish need rw. Another person's conversation answers not_found.
  • The forge's own agent: it is refused these actions because they sit in the existing branch of say that already refuses its via token.
  • Docs and tests: docs/MCP.md is re-rendered with bun scripts/mcp-table.ts. test/mcp-say-build.test.ts drives every action through /mcp on a seeded forge and covers ro refusals, a stranger's not_found, bad input, no-such-version and the hourly limit.

What I left out

  • conversation is required for every action except build. It does not default to your latest conversation, because the API takes an id too.

To try it

  • Call say with {action:"build", text:"a tip calculator", template:"owner/name"}.
  • Then call say with {action:"build_state", conversation:<id>}.

The first push was refused by the pre-push hook, which wants a "Try:" commit. I added an empty commit with that line.

🤖 Generated with Claude Code

Changes requested — before the latest push

Security and behaviour look right: each action calls the shared build function, ro is limited to the two reads, and a stranger gets not_found. One change to make. The prompt, template and id validation is copied from src/http/api/conversation_build.ts, so the API and MCP can drift apart. Move ID, TEMPLATE, the prompt check and the version check into one small shared module and import it from both doors. Optional: assert the specific refusal error for ro in the test instead of toBeDefined.

commented

Both doors now take their build input checks from one new module, src/actions/build_input.ts, and the change is pushed. tsc, scripts/gate.ts, the new say build test, talk-build, mcp-size and talk-conversations all pass.

  • Shared module: it exports BUILD_ID, BUILD_TEMPLATE and the prompt, template and version checks. Each check returns the refusal sentence, or nothing when the input is fine.
  • Callers: src/http/api/conversation_build.ts and src/http/mcp/tools/say.ts both import it, and the local copies of those constants and checks are gone.
  • Wording: the prompt sentence takes a field name, so the API still says prompt and MCP says text. The API's error codes and sentences are otherwise unchanged.
  • Test: the ro refusals in test/mcp-say-build.test.ts now assert forbidden instead of toBeDefined.

I did not re-render docs/MCP.md, since the tool's arguments and descriptions did not change in this commit.

🤖 Generated with Claude Code

Approved

Approved. The shared build_input.ts module removes the duplicated checks, and the ro test now asserts forbidden. Reads (build_state, build_diff) are open to ro via readsWhen; writes need rw; a stranger gets not_found; the build functions and BUILD_WORDS are the API's own. The tests cover each action through /mcp, and docs/MCP.md is re-rendered. I could not diff against 19a9e47284ac, which is not in my clone, so I read the whole change.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs