Problem
Ability results are MCP/JSON data, but many callbacks run stored values through HTML display escaping before returning them. Characters such as &, quotes, apostrophes, and angle brackets therefore reach clients as entities such as & and ' instead of the values WordPress stores.
This is observable data corruption, and a client that writes a returned value back can double-encode it.
Evidence
includes/Server/Handlers/Tools/ToolsHandler.php:186-187 JSON-encodes each ability result and also returns the same array as structuredContent; there is no HTML rendering boundary here.
includes/Abilities/Content/Posts.php:139-147 applies esc_html() to post titles, statuses, dates, and author names.
includes/Abilities/Content/Pages.php:149-159 applies it to page titles, slugs, templates, and other fields.
includes/Abilities/Content/Taxonomy.php:56-63 applies it to term names and descriptions.
includes/Abilities/Media/Media.php:191-204 applies it to attachment titles, alt text, captions, and descriptions.
- The same pattern is pervasive: there are currently 119
esc_html() calls under includes/Abilities.
For example, a stored title of Rock & Roll is returned as Rock & Roll even though the transport is JSON.
Minimal fix
Return raw, correctly typed WordPress values from ability result arrays. Keep input sanitation and URL normalization such as esc_url_raw(), but move HTML escaping to actual HTML views. Structured messages should use translation without HTML escaping as well.
Done when
- A representative value such as
Rock & Roll <draft> "quoted" O'Brien round-trips through MCP exactly as stored.
- Both text content and
structuredContent contain the same unescaped data.
- Ability output no longer applies HTML display escaping to structured fields.
- Existing dashboard/admin HTML continues to escape values at render time.
Problem
Ability results are MCP/JSON data, but many callbacks run stored values through HTML display escaping before returning them. Characters such as
&, quotes, apostrophes, and angle brackets therefore reach clients as entities such as&and'instead of the values WordPress stores.This is observable data corruption, and a client that writes a returned value back can double-encode it.
Evidence
includes/Server/Handlers/Tools/ToolsHandler.php:186-187JSON-encodes each ability result and also returns the same array asstructuredContent; there is no HTML rendering boundary here.includes/Abilities/Content/Posts.php:139-147appliesesc_html()to post titles, statuses, dates, and author names.includes/Abilities/Content/Pages.php:149-159applies it to page titles, slugs, templates, and other fields.includes/Abilities/Content/Taxonomy.php:56-63applies it to term names and descriptions.includes/Abilities/Media/Media.php:191-204applies it to attachment titles, alt text, captions, and descriptions.esc_html()calls underincludes/Abilities.For example, a stored title of
Rock & Rollis returned asRock & Rolleven though the transport is JSON.Minimal fix
Return raw, correctly typed WordPress values from ability result arrays. Keep input sanitation and URL normalization such as
esc_url_raw(), but move HTML escaping to actual HTML views. Structured messages should use translation without HTML escaping as well.Done when
Rock & Roll <draft> "quoted" O'Brienround-trips through MCP exactly as stored.structuredContentcontain the same unescaped data.