MCP: add `update_document` so a coding agent can revise a plan it created
lavaman131 opened ·
### Problem
The `/mcp` contract lets an external agent `create_document`, `read_document`, `list_documents`, `rename_document`, `archive_document`, and `restore_document`, plus the experimental implementation lifecycle. None of these change a document's body after creation. `rename_document` is explicitly scoped to the title and slug and leaves "canonical plan source, plan revision … intact."
The only actors that can revise plan content today are people in the browser and the hosted Planner via `@chopin`, which runs under a browser user's GitHub App session and Copilot entitlement. An MCP bearer never becomes that owner, so a coding agent's relationship with a document is write-once, read-many.
That is a good fit for "hand a settled conversation to the team," but it blocks the obvious next step: the same agent that drafted the plan wants to fold in reviewer findings, a corrected signature, or a resolved open question without asking a human to paste the new text or re-prompt the Planner. Creating a second document splits Decisions and Chat across two channels.
### Proposal
Add one tool:
``` update_document id document UUID (or canonical URL, like read_document) revision integer — the plan revision the caller last read plan full replacement MDX source idempotencyKey string ≤128 ```
Semantics:
- **Optimistic concurrency.** If `revision` does not match the current plan revision, return `{ code: "revision-conflict", revision: <current> }` and change nothing. The caller re-reads and retries. This keeps a stale agent from silently overwriting edits people or the Planner made in the meantime. - **Same validation as create.** Run the source through the existing `prepare` / `canonical` path in `apps/server/src/mcp/create.ts` (parse → mint missing component ids → serialize → `validate` → `room.validate`) and return `{ issues }` on failure, exactly as `create_document` does. - **Preserve durable state.** `Questionnaire`/`Decision` blocks are server-owned projections of sidecar records. An update that drops or alters an existing one should be refused (or the server should re-project them), so a rewrite cannot erase what the team decided — the same guarantee the Planner's editing tools already provide. - **Respect the graph lock.** While an implementation run holds the graph `locked`, refuse with `document-locked`, matching the existing rule that "plan changes that would invalidate the claimed work" are refused during an active run. - **Authorization.** Push or admin access, same as `create_document`. Idempotent replay with the same key returns the already-applied result. - **Provenance.** Record the MCP client and revision transition in the same place the Planner's edits are attributed, so the browser can show "revised by agent" markers for the changed ranges.
Return the same document shape as `read_document` (`id`, `title`, `source`, `revision`, `url`).
### Alternatives considered
- **Patch-style edits** (range or anchor based). Smaller payloads, but MDX offsets shift under concurrent Yjs edits and the dialect canonicalizer rewrites the source anyway; a full-source replace guarded by `revision` is simpler and matches how `create_document` already treats `plan`. - **Let MCP callers trigger a Planner turn.** Would need the bearer to acquire Copilot ownership, which the ownership model deliberately avoids. - **Archive and recreate.** Loses the channel's Chat, Decisions, and any child research documents.
### Notes
`apps/server/src/mcp.ts` already dispatches per tool with `additionalProperties: false` schemas and outcome codes, and `rename_document` shows the shape of a "mutate one field with conflict codes" tool, so this looks like a contained addition rather than a new subsystem. Happy to help test against a self-hosted instance.
lavaman131assigned lavaman131last month on GitHub
lavaman131referenced this in feat(mcp): support revision-guarded document updates #158last month on GitHub