- 2.0.2: Now requires PHP 8.2, matching LH Content Converter, which this plugin requires and which needs PHP 8.2 for its bundled conversion library. No change to how the plugin works.
- 2.0.1: Ability descriptions rewritten so each one is the complete documentation a caller needs, ending with its permission requirement: edit_posts for every ability, plus edit_post on each post for the two that save to posts (convert-post-to-blocks and repair-sourced-attributes). They now cover when to use HTML rather than Markdown, how each ID in a batch is handled on its own, the live-post confirmation rule and what each result reports. Version history has been removed from the descriptions. No change to how any ability works.
- 2.0.0: Renamed from LH MCP Block Transformer to LH MCP Content Transformer, since it converts between HTML, Markdown and block markup in every direction and the conversion library now lives in LH Content Converter. The abilities are renamed accordingly: lh-mcp-content-transformer/convert-to-blocks, convert-from-blocks, convert-post-to-blocks, repair-sourced-attributes and get-pattern-guide replace their lh-mcp-block-transformer/ equivalents, with the same inputs and outputs. The hook prefix changes from lh_mcp_block_transformer_ to lh_mcp_content_transformer_ (including the _ability_classes and _unpublished_statuses filters), and error codes change to match. The abilities now call LH Content Converter directly and pass through its own error when its library is missing, instead of running a separate availability check first. Also removes the deprecated LH_MCP_Block_Transformer_Bridge file, which nothing loaded since 1.10.1.
- 1.10.1: Removed the deprecated LH_MCP_Block_Transformer_Bridge wrapper from loading, now that no plugin calls it (the bridge file itself is removed separately), and removed this plugin’s composer.json and composer.lock: the blocks-engine library comes from LH Content Converter, so this plugin has no Composer dependencies and needs no composer install.
- 1.10.0: Conversion now goes through LH Content Converter, which bundles the automattic/blocks-engine-php-transformer library, instead of this plugin’s own copy of it. LH Content Converter is now a required plugin (Requires Plugins header), so every LocalHero plugin that converts content shares one engine and one library version. The abilities, their inputs and outputs are unchanged; missing-library and conversion errors now carry lh_content_converter_ codes. LH_MCP_Block_Transformer_Bridge remains as a thin deprecated wrapper for plugins that still call it, and this plugin’s vendor directory is no longer loaded and can be removed. The version is now read from the plugin header.
- 1.9.1: Shortened the Description header to 150 characters or fewer, the wordpress.org limit that the compiled readme.txt enforces, so the plugin’s readme compiles and it can be pushed (lh-tasks 147801, part 5). The version is also normalised to MAJOR.MINOR.PATCH. No functional change.
- 1.09: Stop writing sourced attributes into block delimiters, add repair-sourced-attributes, and bump 1.08 -> 1.09. The transformer library serialises every attribute into the block comment JSON, including attributes the block type sources from its HTML, such as the rich-text content of paragraph, heading and list-item blocks. Gutenberg never does this. Such blocks render normally, but WP_Block_Type::prepare_attributes_for_render() validates each delimiter attribute against the block type’s schema, and rich-text is not a JSON-schema type, so WordPress raised a rest_validate_value_from_schema notice for every affected block on every render. This surfaced as six notices on a single front-end search page on lhero.org, and 58 portfolio posts carried the markup. The bridge gains strip_sourced_attributes(), which parses the markup, removes every delimiter attribute that the registered block type declares with a source, and re-serialises only when something was actually removed. convert() now applies it to all block output, so convert-to-blocks and convert-post-to-blocks both produce markup matching what the block editor itself would save. The saved HTML is unchanged; only the redundant delimiter copies go. New ability repair-sourced-attributes applies the same strip to existing posts by ID and saves the result through the shared post-batch class, so it has the per-post edit_post check and the fail-closed confirm gate. It skips posts with no block markup or nothing to remove, reports attributes_removed per post, and needs no transformer library since it uses only core block parsing. It is declared destructive and idempotent. readme.txt is updated for the fifth ability and for the library-independence of the new one.
- 1.08: Restructure into one class per ability and bump 1.07 -> 1.08. The plugin had grown into a single 30KB class in which each ability was spread across four places: its registration block, two schema methods and its executor. Each of the four abilities now lives in its own file under includes/abilities/, as a subclass of a new abstract LH_MCP_Block_Transformer_Ability that builds the wp_register_ability call, so category, permission_check and MCP public exposure are set identically for every ability. Two shared classes remove the duplication between abilities: LH_MCP_Block_Transformer_Bridge holds the autoload check, FormatBridge call, exception handling, output extraction and counts that were previously written out three times, and LH_MCP_Block_Transformer_Post_Batch holds the per-post write loop from convert-post-to-blocks. The main class now keeps only identity, bootstrap, category registration, a filterable list of ability classes (lh_mcp_block_transformer_ability_classes), the permission check and a new error_code() helper that builds error codes from return_plugin_namespace(); the codes themselves are unchanged. One deliberate behaviour change: the confirm gate on convert-post-to-blocks now fails closed. Previously only the publish status required confirmed:true, so private posts, scheduled posts and custom statuses such as the lh-membership member-visibility tiers were rewritten without confirmation even though they are live to someone. Now only draft, pending and auto-draft proceed without it, via a filterable list (lh_mcp_block_transformer_unpublished_statuses), so a status the plugin has never heard of is treated as live by default. The ability description and the confirmed schema text are updated to match. Every ability now also declares annotations for the first time: the three conversion and reference abilities as readonly, non-destructive and idempotent; convert-post-to-blocks as destructive and idempotent, since a second run skips posts that already contain block markup. Labels, descriptions, schemas and output shapes are otherwise unchanged. This release prepares for 1.09, which adds removal of sourced attributes from the serialized output and a repair-sourced-attributes ability built on the same post-batch class.
- 1.07: Exclude runtime detritus from the repository and bump 1.06 -> 1.07. The 1.06 push published an error_log file that was sitting in the plugin directory on the live server. Its contents were harmless β a single Composer deprecation notice from 10 August 2026, no credentials, no paths beyond /usr/local/bin/composer β but it had no business in a source repository, and the mechanism that put it there will keep doing so. PHP writes error_log into whatever directory the executing script lives in, so the file reappears on its own after any deletion; lh-github-publisher then runs git add -A over the directory as it stands. Deleting it fixes nothing past the next PHP notice, which makes .gitignore the only durable answer. The entry is generalised past the one file that caused it: error_log, *.log and .DS_Store. The principle is that the plugin directory on a live server accumulates files that are not source, and the publisher has no way to tell them apart. Anything of that kind belongs here rather than being caught by review each time, because review is exactly what failed at 1.06 β the pre-push security scan checked the code for secrets and never questioned which files were in the directory at all. list-plugin-files filters by extension and does not show an extensionless error_log, so the file was invisible to the obvious check. Worth recording for the next push of any plugin: enumerate what will actually be committed, not what the file-listing ability chooses to show. A force push replaces the branch outright, so re-pushing at 1.07 removes the file from history rather than merely deleting it in a later commit. No PHP behaviour changes. The two code files carry the version number and nothing else.
- 1.06: Add a .gitignore excluding vendor/, correct readme.md’s description, and bump 1.05 -> 1.06. lh-github-publisher pushes a plugin by running git init, git add -A and a force push over the plugin directory exactly as it sits on disk, then deleting .git afterwards. There is no .distignore, no exclusion filter and no build step anywhere in that path, so whatever is in the directory is what lands in the repository. For this plugin that would mean the entire automattic/blocks-engine-php-transformer tree, several hundred files of third-party code, committed as though it were ours and re-committed wholesale on every future push. A .gitignore file is the only lever available, since it lives on disk and therefore survives between pushes while .git does not. vendor/ is the only entry. composer.json and composer.lock stay tracked, which is what makes the exclusion safe rather than merely tidy: a clone has everything needed to reconstruct the exact dependency tree with composer install. The trade-off is accepted deliberately β a fresh clone is inert until that command is run, and three of the four abilities return their missing-vendor WP_Error until then. This is the GitHub-only position; distributing through wordpress.org, where a user cannot run composer at all, needs a deploy workflow that installs dependencies before the SVN sync, and that does not exist yet. readme.md’s description was written when the plugin only converted one way and understated what it now does: it converts in both directions and has a save path that takes post IDs. The installation section gains a sentence stating plainly that vendor/ is absent from the repository by design, so someone cloning it does not read the composer step as optional housekeeping. readme.txt moves its Stable tag and trimmed changelog forward per standing rule 8. No PHP behaviour changes. The two code files carry the version number and nothing else: the plugin header and plugin_version(), which must agree or the changelog automation tags entries against a stale version.
- 1.05: Reclassify this plugin as editorial rather than developer tooling, and move the capability gate accordingly. permission_check() drops from install_plugins to edit_posts, per the LH convention that editor-facing content-operation abilities are gated to edit_posts. Because convert-post-to-blocks writes to real posts and previously relied on install_plugins as its only authorisation, it now also checks current_user_can(‘edit_post’, $id) per individual post ID inside the loop, refusing that ID rather than the whole call β edit_posts alone says nothing about whether the caller may edit any particular post. The per-post check is added ahead of the gate change so there is no state in which the gate is open without it. Bump 1.04 -> 1.05.
- 1.04: Per Peter’s decision: moves the combined convert+save ability from lh-portfolio (where it started as a temporary, portfolio-specific fix for task #147196) into lh-mcp-block-transformer as a permanent, generalized ability β lh-mcp-block-transformer/convert-post-to-blocks. Works on any post type now, not just lh-portfolio entries. The confirm-gate simplifies from lh-portfolio’s ‘published root entry’ concept (which doesn’t generalize β most post types have no root/child hierarchy) to the more universal ‘already-published post’, mirroring the same confirmed:true semantics. Same underlying FormatBridge call and same core safety guarantee as before: only a post ID ever crosses the MCP boundary, never the block markup itself, since that transcription is what corrupted a live entry earlier in task #147196. Version bump to 1.04.
- 1.03: Add the License and License URI headers (GPL v2 or later, matching composer.json), which Plugin Check reported as plugin_header_no_license. Bump the plugin header and plugin_version() together. No behaviour change. Backfilled from proposal lhcp_ea54ca1a: this version predates the changelog automation.
- 1.02: Add two abilities. convert-from-blocks is the mirror of convert-to-blocks, turning existing block markup into HTML or Markdown through the same FormatBridge, so an existing block-based post can be read into an editable format and converted back. get-pattern-guide returns static reference data describing which HTML and CSS shapes trigger the richer blocks (cover, columns, media-text, buttons), the near-guaranteed plain element set, and how unmatched content falls back; it takes no real input, so its schema carries a sentinel underscore property because the abilities adapter rejects an empty schema. The serializer was generalised into extract_output_content(), shared by both conversion directions, and the vendor autoload check was factored into require_transformer_autoload(). Backfilled from proposal lhcp_04e03398: this version predates the changelog automation, and its timestamp is approximate, falling between 1.01 and 1.03.
- 1.01: Fix the ability and category namespace. Both now use the hyphenated text domain, lh-mcp-block-transformer, instead of the underscored hook namespace, lh_mcp_block_transformer. WP_Abilities_Registry only accepts ability names made of lowercase letters, digits and hyphens on either side of the slash, so the underscored prefix left convert-to-blocks unregistered with no fatal. Docblocks record the hyphen-versus-underscore distinction so it is not reintroduced. Backfilled from proposal lhcp_d1bcc7c6: this version predates the changelog automation.
- 1.00: Initial release. Registers one MCP ability, convert-to-blocks, which converts HTML or Markdown content into native Gutenberg block markup using automattic/blocks-engine-php-transformer, and reports fallback and diagnostic detail for anything that could not be matched to a specific block. Installed by zip upload rather than through lh-mcp-code-changes. Shipped with the underscored hook namespace as the ability and category prefix, which the Abilities API rejects, so the ability failed to register until 1.01. Backfilled: this version predates the changelog automation, and its timestamp is approximate.
Changelog
If you like this plugin please consider making a donation to further development
