Add targeted partial content edits#2
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
…hema fix.
Includes uncommitted WIP that was in the working tree: site_id param via
siteManager, DANGEROUS_PATTERNS / multi-statement guards, and calling the
/mcp/v1/query endpoint directly with axios (makeWordPressRequest prepends
/wp-json/wp/v2/ so it can't reach it).
Also fixes the published parameter schema: server.ts rebuilds each tool
schema with z.object(inputSchema.properties), so properties must be zod
shapes; the WIP passed raw JSON Schema, collapsing the published schema
to {} and causing clients to strip all params.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces targeted, substring-level editing for WordPress content to reduce token/network cost in MCP/LLM workflows, and adds an opt-in raw-content read path to make exact matching reliable against content.raw (WordPress context=edit).
Changes:
- Add
content_edittoupdate_contentandfind_content_by_url.update_fieldsto support partial operations (append,prepend,insert_before,insert_after,replace) against raw stored content. - Add
include_raw_contenttoget_content/find_content_by_urland exposecontent_rawfor exacttarget_textdiscovery. - Adjust SQL query tool schema publishing and add
site_idsupport for multi-site SQL queries.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/types/wordpress-types.ts | Expand WP REST typings to include optional raw fields for edit-context responses. |
| src/tools/unified-content.ts | Implement content_edit, raw-content fetching (context=edit), and response content_raw aliasing. |
| src/tools/sql-query.ts | Add site_id, fix schema publishing approach, and build SQL endpoint requests directly from site config. |
| README.md | Document targeted content edits and the include_raw_content / content_raw workflow. |
| CLAUDE.md | Update tool documentation to reflect targeted updates and raw-content retrieval. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const site = siteManager.getSite(params.site_id); | ||
| const sqlPath = process.env.WORDPRESS_SQL_ENDPOINT || '/mcp/v1/query'; | ||
| const siteBase = site.url.replace(/\/$/, ''); | ||
| const url = `${siteBase}/wp-json${sqlPath}`; | ||
|
|
| matches.push(matchIndex); | ||
| fromIndex = matchIndex + targetText.length; | ||
| } |
| await makeWordPressRequest('POST', `${endpoint}/${content.id}`, updateData, { siteId: params.site_id }); | ||
| const responseContent = params.include_raw_content | ||
| ? await fetchContentById(endpoint, content.id, params.site_id, true) | ||
| : await fetchContentById(endpoint, content.id, params.site_id, false); |
| await makeWordPressRequest('POST', `${endpoint}/${content.id}`, updateData, { siteId: params.site_id }); | ||
| const responseContent = params.include_raw_content | ||
| ? await fetchContentById(endpoint, content.id, params.site_id, true) | ||
| : await fetchContentById(endpoint, content.id, params.site_id, false); |
Problem
update_contentcurrently treats the post body as an all-or-nothing string. That works for small edits, but it is expensive for MCP/LLM workflows because even a one-line change requires resending the full document. WordPress itself supports partial field updates at the post level, but it does not offer substring-level edits insidecontent, so the server needs to provide that behavior if we want to reduce token usage.A related problem showed up immediately in review: even when targeted edits work, callers often copy
target_textfrom rendered HTML. That is unreliable because rendered WordPress output can differ from the stored raw document. Entities may be escaped, marker comments may be wrapped, and image markup can diverge from what was originally saved.Approach
This PR adds a new
content_editcontract toupdate_contentand mirrors the same contract infind_content_by_url.update_fields.Supported operations:
appendprependinsert_beforeinsert_afterreplaceThe existing
contentfield remains the full-replacement path.contentandcontent_editare mutually exclusive so the tool contract stays explicit.Follow-up from review:
include_raw_contenttoget_contentinclude_raw_contenttofind_content_by_urlcontent_rawfield in those responses so callers can copy the exact raw fragment needed forcontent_edit.target_textWhy This Design
The goal here is to reduce token cost without introducing a fragile document-editing system.
I intentionally kept the edit path deterministic:
target_textforinsert_before,insert_after, andreplaceoccurrenceis providedThat keeps the feature useful for practical marker-based and fragment-based edits while avoiding a much larger HTML or Gutenberg AST editing project.
The read-path follow-up is meant to remove the biggest usability pain in that design. Instead of making the matcher heuristic, the server now exposes the exact stored raw content when requested.
How It Works
When a partial edit is requested, the server:
context=editcontent.rawprocessContentflowcontentback through the normal WordPress update endpointFor exact-match discovery,
get_content(include_raw_content=true)andfind_content_by_url(include_raw_content=true)also fetch withcontext=editand includecontent_rawin the returned payload.This means callers can now:
content_rawor a fragment from itcontent_edit.target_textAPI Surface
New edit object on
update_content:content_edit.operationcontent_edit.valuecontent_edit.target_textcontent_edit.occurrencecontent_edit.content_formatcontent_edit.convert_to_blocksThe same object is supported under
find_content_by_url.update_fields.content_edit.New read flags:
get_content.include_raw_contentfind_content_by_url.include_raw_contentNew convenience field in raw-read responses:
content_rawValidation added:
content+content_edittogethertarget_textoccurrencedisambiguatescontent.rawImplementation Notes
src/tools/unified-content.tsupdate_contentandfind_content_by_urlreuse the same content-update builder so the behavior stays alignedcontext=editwhen explicitly requestedcontent.rawVerification
npm run buildhttps://drunk.supportcurl .../wp-json/wp/v2/posts/<id>?context=editupdate_contentwithcontent_edit.replacefind_content_by_urlwithcontent_edit.appendget_content(include_raw_content=true)returnscontent_rawfind_content_by_url(include_raw_content=true)returnscontent_rawcurlcurlchecksRepresentative
curlcheck showing why raw content matters:{ "id": 576, "raw": "<p>Smoke & verify 20260325022944</p>\n<!-- raw-marker -->\n<p>Target & replace 20260325022944</p>\n<!-- /raw-marker -->", "rendered": "<p>Smoke & verify 20260325022944</p>\n<p><!-- raw-marker --></p>\n<p>Target & replace 20260325022944</p>\n<p><!-- /raw-marker --></p>\n" }Representative cleanup check:
{ "code": "rest_post_invalid_id", "message": "Invalid post ID.", "data": { "status": 404 } }Stack Note
This PR is intentionally stacked on top of
feature/media-tool-cleanup-file-upload, which is upstream PRInstaWP/mcp-wp#13. Once#13merges, this branch can be rebased ontomainand opened upstream as a normal standalone PR.