Skip to content

feat(content): warn when WordPress silently drops unregistered meta keys#17

Open
kleintech wants to merge 1 commit into
InstaWP:mainfrom
kleintech:feature/warn-on-dropped-meta
Open

feat(content): warn when WordPress silently drops unregistered meta keys#17
kleintech wants to merge 1 commit into
InstaWP:mainfrom
kleintech:feature/warn-on-dropped-meta

Conversation

@kleintech
Copy link
Copy Markdown

Summary

WordPress core silently drops any meta key sent via /wp/v2/{type}/{id} unless the key was registered via register_post_meta(..., ['show_in_rest' => true]). The big SEO plugins (Yoast SEO, Rank Math, AIOSEO) deliberately do not register their _yoast_wpseo_* / rank_math_* / _aioseo_* keys for core REST exposure — they ship their own routes (e.g. Yoast's /yoast/v1/* indexable endpoints).

As a result, calling update_content (or create_content, or find_content_by_url with update_fields.meta) with SEO meta keys returns 200 OK while the values silently never land. The MCP tool result echoed the API response back to the LLM with no indication anything had gone wrong, so callers reported success.

This PR adds a defensive diff: after each successful write, the handler compares the meta keys sent in the request to the meta keys echoed in the WP response. If any are missing, it prepends a Warning: content block to the toolResult listing every dropped key with a short explanation. It does not change which keys WP will accept — that requires a small WordPress companion plugin (being scoped separately at mcp-wp-seo-bridge).

Root cause / diagnosis

Reproduced against a live Yoast-active install:

Key sent Registered with show_in_rest? Result in response.meta
_genesis_layout yes (by Genesis) accepted, round-trips
_test_made_up_key no silently dropped
_yoast_wpseo_focuskw no silently dropped

Single POST request, one key kept, two dropped, no error. This is core WP behavior and not something the MCP server can fix — but it can at least stop hiding the failure.

Changes

  • New module-level helpers in src/tools/unified-content.ts:
    • detectDroppedMetaKeys(sent, responseData) — returns the keys present in sent but missing from responseData.meta. Pure function, no I/O. Treats unexpected response shapes (no meta, or meta as an array) as "all dropped" — conservative.
    • buildDroppedMetaWarning(keys) — builds the warning text, including a pointer to the README "Meta field limitations" section (added in a follow-up doc PR).
  • Wired the helpers into the four write paths that accept meta:
    • create_content
    • update_content
    • find_content_by_url (both the priority-types-matched and all-types-fallback update branches)
  • scripts/manual-test-meta-warning.mjs — end-to-end check against a real WP install (this repo currently has no automated test harness; the script is the agreed manual verification step).

The warning is added as a separate text content block prepended to toolResult.content. The WP response JSON is untouched so anything downstream that parses the response keeps working.

What was tested

  • Manual: node scripts/manual-test-meta-warning.mjs against a Yoast-active install. Creates a draft post, calls update_content with three _yoast_wpseo_* keys, asserts the warning block appears and lists each sent key, deletes the post. Passes.
  • Build: npm run build. Two pre-existing TS errors in server.ts and media.ts are unrelated to this change (present on main before this PR).

Security considerations

  • No new auth boundary. The handlers still forward meta to WordPress exactly as before — WP's existing register_post_meta / auth_callback enforcement is the only gate on what actually persists.
  • The warning text contains the meta key names sent by the caller. These come from the MCP request, not from WP — so this can't leak server-side data the caller didn't already have.
  • The change does not add an allowlist. There is no allowlist on the MCP side by design; the fix for the underlying drop is plugin-side registration (separate PR/repo).

Keys that still won't work after this PR

All of them. This PR doesn't make any new keys writable — it just stops pretending the writes worked when they didn't. The follow-up companion plugin (mcp-wp-seo-bridge, separate repo) will register the actual SEO keys with show_in_rest.

Test plan

  • npm run build (warnings on pre-existing TS errors in server.ts and media.ts are unrelated)
  • node scripts/manual-test-meta-warning.mjs against a Yoast-active install — expects PASS lines and exit 0
  • In a Claude Desktop session: call update_content on a post with meta: {_yoast_wpseo_metadesc: "test"}. Tool output should include both a Warning block and the JSON response.
  • Same call with a known-registered meta key (e.g. _genesis_layout: "content-sidebar" on a Genesis site) — no warning block expected.

🤖 Generated with Claude Code

WordPress core silently drops any meta key sent via /wp/v2/{type}/{id}
unless the key was registered with show_in_rest. SEO plugin keys
(Yoast _yoast_wpseo_*, Rank Math rank_math_*, AIOSEO _aioseo_*) are
not registered, so writes returned success while the values never
persisted. The MCP tool result hid this from the LLM caller.

This change diffs the meta keys sent vs the keys echoed in the WP
response and prepends a Warning content block listing every key WP
dropped, on create_content, update_content, and the two update branches
of find_content_by_url. It does not change which keys WP accepts —
that requires a WordPress companion plugin (scoped separately).

Includes scripts/manual-test-meta-warning.mjs which exercises the
warning end-to-end against a live WP install. The repo has no test
harness yet, so this is the agreed manual verification step.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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