Skip to content

feat(cms): edit entries in place with textReplacements and pick the response shape - #895

Open
kmonsoe wants to merge 1 commit into
mainfrom
feat/cms-text-replacements
Open

feat(cms): edit entries in place with textReplacements and pick the response shape#895
kmonsoe wants to merge 1 commit into
mainfrom
feat/cms-text-replacements

Conversation

@kmonsoe

@kmonsoe kmonsoe commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Why

Revising a blog article today means resending the whole body: a markdown field is one string, and a blocks body is one array, so changing a sentence in one prose block regenerates every block. For an LLM that is the slow and expensive direction, and a regenerated body drifts, drops paragraphs, and mangles asset:// tokens.

What

cms_update_entry gains textReplacements — an ordered, all-or-nothing list of { field, oldText, newText, replaceAll? } edits applied to the stored text of a text, markdown, rich_text, array-of-text or blocks field.

  • Each oldText must match exactly once, or set replaceAll. Zero matches fails with cms_replacement_no_match, several with cms_replacement_ambiguous; nothing is written and version does not move.
  • On a blocks field the match runs across the prose of every block; block type names, keys and non-text props are never matched.
  • Inline images may be quoted as the stored asset:// token or as the public URL cms_get_entry showed. The server maps URLs back to the token before matching, so the image stays tracked.
  • A field may appear in data or textReplacements, not both.
  • The /v1/cms/drafts/:id PATCH accepts the same field so both surfaces stay thin wrappers over updateEntry.

responseFormat: "full" | "summary" on every entry-returning write tool. summary is the cms_list_entries shape. Create, update and restore default to full; publish, unpublish and schedule default to summary because they do not change content.

cms_get_entry takes fields to project data, mirroring cms_list_entries.

Design notes

The shape follows what agent-facing content APIs have converged on: Notion's update_content and Anthropic's text editor tool both use exact-match old_str/new_str with a replace-all switch, and Sanity mixes whole-value sets with in-string patches in one call. It is deliberately two flat optional siblings on the one update tool rather than a command discriminator — the oneOf shape Notion's MCP tool uses is what strict-validation hosts have rejected.

Skills

New skill://cms/revise-entry owns the editing loop. publish-entry, upload-asset-and-embed, migrate-content and author-with-blocks now show the replacement flow instead of resending bodies. Fixtures regenerated.

Tests

  • common/text-replacements.test.ts: match counting, ordering, atomicity, ambiguity, replaceAll.
  • cms.fields.test.ts: block traversal, type/key immunity, array-of-text, no-text fields.
  • cms.service.test.ts: happy path, coded failures leave the row untouched, unknown/non-text/overlapping fields, block prose + public-URL quoting round-trips to the stored asset:// token, fields projection, summary presenter.
  • cms.integration.test.ts: the whole surface through /mcp, including default response shapes on publish/unpublish.

A follow-up PR adds the same textReplacements and responseFormat to the KB document tools.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VJjhaB9bMU1agbqVKTwbB1

…esponse shape

cms_update_entry takes textReplacements — ordered, all-or-nothing exact-match
edits inside text, markdown, rich_text, array-of-text and blocks fields — so an
agent can change a sentence without regenerating the article. Entry-returning
write tools take responseFormat (full | summary); publish, unpublish and
schedule default to summary since they do not change content. cms_get_entry
takes fields to project data. New skill://cms/revise-entry documents the loop.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VJjhaB9bMU1agbqVKTwbB1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant