yootheme-mcp
Three products from one repo — see PRODUCTS.md:
| # | Product | Entry |
|---|---|---|
| 1 | Skill yootheme-remote |
skill/SKILL.md |
| 2 | MCP | yootheme-mcp / python -m yootheme_mcp |
| 3 | CLI | yootheme … |
CLI (and optional MCP server) to manage YOOtheme Pro 5.x layouts, design settings, custom code, the new cookie consent manager and saved element presets on WordPress and Joomla 4/5 sites.
Talks to your sites via their standard REST API — no plugins, no MU-plugins, no SSH, no file-system access required on the remote side. Just an Application Password (WordPress) or an API Token (Joomla).
Status: beta. Package: yootheme-mcp 0.11.3 (MCP + CLI + Skill). Read-only tools and dry-run previews are free; writing to a site needs a licence key — see Licensing. What leaves your machine: see Privacy. Current prices and licence terms: https://fuerteventuratv.net/en/cms/yootheme-mcp
Two entry points
The package ships two console scripts after pip install -e .:
| Command | When to use it |
|---|---|
yootheme ... |
Daily ops from a terminal (you, scripts, CI) |
yootheme-mcp |
Run as an MCP server so Claude / agents can call the tools |
The CLI is the recommended entry point. The MCP server is the same logic exposed to language models — useful if you want Claude Desktop to manage your sites in chat.
CLI quick reference
# discover what's configured
yootheme sites
# verify auth + reachability
yootheme ping tiserve
# list pages/posts/articles/modules — mark which have a YT layout
yootheme list tiserve --limit 50 --only-with-layout
# read a layout and save it to disk
yootheme get tiserve 32 --type page -o page32.json
# write a layout back
yootheme set tiserve 32 page32.json --type page
# copy a layout to another site (cross-CMS works!)
yootheme duplicate tiserve 32 staging-joomla 17 \
--source-type page --target-type article
# bulk find/replace across every layout — dry-run by default
yootheme bulk-replace tiserve "https://old.example.com" "https://new.example.com"
yootheme bulk-replace tiserve "https://old.example.com" "https://new.example.com" --apply
# style customizer
yootheme customizer get tiserve -o customizer-backup.json
yootheme customizer set tiserve customizer-edited.json
# settings panel (custom CSS/JS, cookie consent, favicon)
yootheme settings get tiserve --key custom_less -o custom.less
yootheme settings get tiserve --key consent # cookie consent manager
yootheme settings get tiserve --key scripts # custom JavaScript list
yootheme settings set tiserve custom_less new-custom.less
# style customizer panels live under their own keys too
yootheme settings get tiserve --key style # active style picker (e.g. "fuse")
yootheme settings get tiserve --key header.layout # any Layout/Theme-settings panel
# saved element presets ("My Presets")
yootheme presets list tiserve
yootheme presets export tiserve my-grid-preset preset.json
yootheme presets import tiserve my-grid-preset preset.json
# Joomla-only: article CRUD
yootheme article create loquehay --title "New article" --catid 9
yootheme article update loquehay 42 --title "Renamed"
yootheme article publish loquehay 42
yootheme article delete loquehay 42 # -> Joomla trash (recoverable)
yootheme article delete loquehay 42 --permanent # destroys it, no undo
# Joomla-only: media manager (list / upload / delete)
yootheme media list loquehay --path images
yootheme media upload loquehay ./hero.jpg --dest images
yootheme media delete loquehay images/hero.jpg
# Joomla-only: menus and menu items
yootheme menu types loquehay
yootheme menu items loquehay --menutype mainmenu
yootheme menu get loquehay 101
yootheme menu set loquehay 101 patch.json
# Joomla-only: list YT template styles + assignment
yootheme templates loquehay
# global flag: print machine-readable JSON
yootheme --json list tiserve --limit 5
Most write commands prompt for confirmation; pass --yes to skip. bulk-replace
--apply, presets import and article create/publish/unpublish do NOT prompt —
in particular --apply rewrites every matching layout on the site, so read the
dry-run report first.
Feature coverage
| Feature | CLI / MCP support |
|---|---|
| Page layouts (sections / rows / columns) | yootheme get/set/list/duplicate |
| Element library / element-level props | edit via get + set |
| Element Presets (My Presets tab) | yootheme presets list/export/import |
| Style Customizer | yootheme customizer get/set |
| Custom CSS / Less (Settings panel) | yootheme settings ... --key custom_less |
| Custom JavaScript (Settings → Scripts) | yootheme settings ... --key scripts |
| Cookie Consent Manager (v5.0) | yootheme settings ... --key consent |
| Favicon / Touch icon | yootheme settings ... --key favicon / --key touchicon |
| Joomla modules with YT Builder layout | --type module on get/set/list |
| Scroll-driven animations / parallax (v5.0) | edit via get + set (MCP inspect_animations tool finds them) |
| Display Conditions, Menu Element, Date Filter, Margin/Padding splits, Multilingual switcher, lazy/on-click videos (all v5.0) | inside the layout JSON — edit via get + set |
| Joomla article CRUD | yootheme article create/update/publish/unpublish/delete |
| Joomla media manager | yootheme media list/upload/delete |
| Joomla menus & menu items | yootheme menu types/items/get/set |
| Joomla template styles + assignment | yootheme templates |
| Pro Presets cloud library | NOT exposed — admin-only |
| Child theme files | NOT exposed — use SFTP |
Customizer coverage map (vs. yootheme.com docs)
YOOtheme Pro's Customizer (tested against 5.0.x) groups everything into seven
areas. They all serialize into one JSON config blob (Joomla: the template
style's params.config; WordPress: /wp-json/yootheme/v1/style + settings),
which customizer/settings get/set read and write by key. Mapping:
| Customizer area (docs) | Config key(s) | How to reach it |
|---|---|---|
| Style → active style picker | style (e.g. "fuse") |
settings get/set --key style |
| Style → Global / Theme / Inverse / component Less variables | less (map of @variable: value) |
customizer get/set or --key less |
| Style → Google Fonts | within less / style |
customizer get/set |
| Layout → Site & Logo | site, logo |
settings --key site / logo |
| Layout → Header & Navbar | header, navbar, top, bottom |
settings --key header … |
| Layout → Mobile header / off-canvas | mobile, dialog |
settings --key mobile |
| Layout → Sidebar | main_sidebar |
settings --key main_sidebar |
| Layout → Footer builder | footer |
settings --key footer |
| Layout → Blog / Post / Category | blog, post, page_category, search_module |
settings --key blog … |
| Menu (positions & items) | menu (positions); Joomla menu items |
settings --key menu / menu cmds |
| Settings → CSS (custom Less) | custom_less |
settings --key custom_less |
| Settings → Scripts (custom JS) | scripts (list of descriptors) |
settings --key scripts |
| Settings → Consent Manager | consent |
settings --key consent |
| Settings → Favicon / Touch icon | favicon, favicon_svg, touchicon |
settings --key favicon |
| Settings → Advanced | child_theme, media_folder, fontawesome, bootstrap, jquery, webp, lazyload, highlight |
settings --key <name> |
| Settings → External Services / API Key | scripts prebuilt entries / provider keys |
settings get/set |
| Pages (per-page builder layouts) | WP post_content / Joomla article fulltext JSON comment |
get/set/list/duplicate |
| Templates (styles + assignment) | Joomla template styles | templates |
| Modules | Joomla module content |
--type module |
| Settings → Cache / System Check / Recompile / Download Less | — (admin-only UI actions) | NOT exposed |
Every customizer/settings value is reachable through the generic
settings get/set --key <dotted.path>(andcustomizer get/set). The only things with no REST surface are admin-UI actions (recompile, clear cache, download Less, system check) and the style library / cloud presets.
WordPress caveat (verified on Joomla only). On Joomla
get_settings()andget_customizer()are the sameconfigblob, soconsent/custom_less/scriptsare always reachable. On WordPress they are different REST endpoints (yootheme/v1/settingsvsyootheme/v1/style), and the Settings shortcut tools (*_cookie_consent,*_custom_code) read and write viaget_settings(). Confirm those keys actually live in the settings blob on your WP install before relying on the shortcuts — compare the read-onlyyootheme settings get <site> --key consentagainstyootheme customizer get <site>: if the key appears only in the customizer output, the shortcuts are reading the wrong store.
Install
Full English guide (this MCP + elementor-mcp): INSTALL.md
Windows quick path: extras/INSTALL_WINDOWS.md
From PyPI (MCP Marketplace buyers, or to try the free read-only tools):
python -m venv .venv && source .venv/bin/activate # PowerShell: .venv\Scripts\Activate.ps1
pip install yootheme-mcp
yootheme --version # prints the installed version
# Or on Windows (PowerShell), from the unzipped package folder bought on the GMC store:
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -e .
Windows step-by-step (config paths, Claude Desktop wiring): extras/INSTALL_WINDOWS.md.
Configure your sites
A single JSON file holds every site you want to manage. Path:
- Linux/macOS:
~/.config/yootheme-mcp/sites.json - Windows:
%APPDATA%\yootheme-mcp\sites.json
Or set YOOTHEME_MCP_CONFIG to a custom location.
{
"sites": {
"tiserve": {
"platform": "wordpress",
"url": "https://tiserve.it",
"wp_username": "your-wp-username",
"wp_app_password": "xxxx xxxx xxxx xxxx xxxx xxxx"
},
"loquehay": {
"platform": "joomla",
"url": "https://loquehay.es",
"joomla_token": "base64TokenFromUserProfile"
}
}
}
See extras/sites.example.windows.json for a Windows-friendly starter file.
WordPress Application Password
- WP-Admin → Users → your profile → scroll to Application Passwords
- Name it
yootheme-cli→ Add New Application Password - Copy the displayed value (format
xxxx xxxx xxxx xxxx xxxx xxxx) — not shown again
Your normal admin login password does NOT work for the REST API. Application Passwords are core to WordPress since 5.6.
Joomla API Token
- Enable plugins API Authentication - Web Services Joomla Token and User - Joomla API Token
- Edit a Super User profile → Joomla API Token tab → Generate
- Copy the token as displayed (already base64-encoded)
WordPress native layout storage
YOOtheme Pro 5 stores its builder root in a JSON HTML comment in post_content.
The default WordPress REST adapter reads editable raw content and replaces only
that comment. Existing text before and after the comment is preserved, including
the search excerpt; regenerate that excerpt in the YOOtheme builder if needed.
Native layout access requires an account allowed to edit the target post, but
does not require the bundled meta-exposer MU-plugin.
Versions through 0.10.1 used _yootheme_page meta and could report a successful
write without changing the rendered page. A previous meta-only layout can be
read for recovery; writing it to an empty page now creates native content.
Converting a non-builder post requires explicit --replace-body / replace_body.
That operation returns the replaced text: retain it alongside the layout backup.
A non-default wp_meta_key retains custom meta integration behavior.
The optional SSH adapter still uses meta storage and has not received the native content correction. Use WordPress REST for native YOOtheme 5 page layouts.
On Joomla an article layout is an HTML comment at the start of fulltext, while
a mod_yootheme_builder module stores its layout tree in content. Both are
accessible via the Web Services API without any extra step.
A Joomla article's builder layout IS its body. YOOtheme renders it from an HTML comment at the start of
fulltext, which has to be the whole body, so writing a layout onto an article that already has text replaces that text — irrecoverably, since the Web Services API keeps no version of it.set,importandduplicaterefuse to do that and tell you so; pass--replace-body(CLI) orreplace_body: true(MCP) to accept the loss. The replaced text comes back to you asreplaced_body, and nowhere else.
Run as MCP server (optional)
If you also want Claude Desktop to call these tools in chat:
{
"mcpServers": {
"yootheme": {
"command": "yootheme-mcp",
"env": {
"YOOTHEME_MCP_CONFIG": "C:\\Users\\<you>\\AppData\\Roaming\\yootheme-mcp\\sites.json",
"MCP_LICENSE_KEY": "mcp_live_<your key - optional, enables the write tools>"
}
}
}
}
Leave MCP_LICENSE_KEY out to run the free read-only tools and previews, or use
YOOTHEME_MCP_LICENSE_KEY / license.key for a key bought on the GMC store
(see Licensing). Run the server over stdio only: --http
starts an HTTP transport with no authentication, for your own machine.
See extras/INSTALL_WINDOWS.md for the full Windows guide.
Licensing
yootheme-mcp is commercial software with a free read-only tier.
| Without a key | With a valid key | |
|---|---|---|
| Read-only MCP tools (list, get, export, find, inspect, ping — 20 tools) | yes | yes |
Read-only CLI commands (sites, ping, list, get, customizer get, settings get, presets list / export, menu types / items / get, media list, templates) |
yes | yes |
| Previews (dry runs) of the write tools that have one — see below | yes | yes |
| Tools and commands that change a site (set, import, duplicate, replace, save, delete, upload, publish, patch — 21 tools) | refused, with an explanation | yes |
Each tool is declared read-only or writing in the code, and the MCP annotations
(readOnlyHint, destructiveHint) say the same thing to your client.
Free previews. Four write operations have a dry run, and the dry run is free:
the MCP tools yootheme_replace_text, yootheme_replace_image and
yootheme_bulk_find_replace (with dry_run left at its default, true), and the
CLI command bulk-replace without --apply. A dry run reads the site and reports
what would change; it writes nothing — not to the site, and no local backup
either. While a free preview runs, the tool is technically unable to send
anything but read requests. Applying the change (dry_run: false, or
bulk-replace --apply) needs a key. No other tool has a dry run: a confirmation
prompt (--yes) is not a preview.
Two kinds of key unlock the write tools; either one is enough:
| Key | Bought on | Set it as | Verified by |
|---|---|---|---|
MCP Marketplace key (mcp_live_…) |
MCP Marketplace (mcp-marketplace.io) — annual subscription, equivalent to the GMC Solo tier (1 person) | MCP_LICENSE_KEY in the environment of the MCP server or shell |
MCP Marketplace |
| GMC key | https://fuerteventuratv.net/en/cms/yootheme-mcp — Solo, Base and Agency tiers | YOOTHEME_MCP_LICENSE_KEY, or one line in license.key in the config folder (%APPDATA%\yootheme-mcp\ on Windows, ~/.config/yootheme-mcp/ on Linux) |
GMC Licenses |
The prefix decides where a key is checked, so a key in the "wrong" variable still works. How the check behaves:
- The key is checked the first time a write tool or command runs (and once when the MCP server starts, to report the status on stderr). A successful check is remembered on this machine — 24 hours for an MCP Marketplace key, 7 days for a GMC key — in a signed file that does not contain the key.
- If the licence server cannot be reached, a successful check from the last 7 days still counts; otherwise write tools are refused until it can be reached. Read-only tools never need the network for licensing.
- A key the server rejects (revoked, expired, replaced, unknown) stops the write tools at once; the read-only tools keep working.
- There is no switch that skips the check.
Multi-seat and agency use: buy the Base or Agency tier on the GMC store. Full
terms: LICENSE.
Privacy
yootheme-mcp runs on your computer. There is no telemetry.
-
Site credentials (WordPress Application Passwords, Joomla API tokens, SSH details) are read from your local
sites.json, environment or env file and are sent only to the sites you configured. -
Page content and layouts travel only between your machine and your own sites. Pre-write backups are written to your local disk.
-
Licence check: only the licence key and the product slug (
yootheme-mcp) are sent, and only to the server that issued that kind of key:- MCP Marketplace keys →
https://virupvwhtkpkjsiskckg.supabase.co/functions/v1/verify-key(MCP Marketplace's verification service, hosted on Supabase), as{"key": …, "slug": …}; - GMC keys →
https://fuerteventuratv.net(GMC Licenses,api.entitlement), asproductandlicense_keyquery parameters.
Like any HTTPS request, these also reveal your IP address to that server. Without a key, nothing is sent anywhere except to your own sites.
- MCP Marketplace keys →
Architecture
yootheme_mcp/
cli.py # Click CLI (yootheme command)
server.py # FastMCP server (yootheme-mcp command)
config.py # sites.json + env-var config loader
errors.py # actionable error messages
models.py # Pydantic models
license.py # licence gate: free reads, key-checked writes
adapters/
base.py # abstract adapter
wordpress.py # WP REST + Application Password
# auto-detect pretty vs Plain permalinks
joomla.py # Joomla Web Services + Bearer token
# supports article AND module object_types
tools/ # MCP-only tools (sites, layouts, bulk, customizer,
# settings, presets, animations)
_registry.py # read_tool / write_tool: the one read-or-write declaration
Both cli.py and tools/*.py delegate to adapters/ — same REST code,
two front-ends.
Changelog
v0.11.3 — Editing a Joomla menu item keeps its language links
- On a multilingual Joomla site,
yootheme menu setand the MCP toolyootheme_patch_menu_itemunlinked the menu item from its translations: Joomla drops a menu item's language associations when a save does not send them, and it drops them for the whole group, so the English, Italian and Spanish versions of the item all stopped pointing at each other in the language switcher. The tool now sends the item's current associations back with every change, so a title or publish change leaves them as they were. - To unlink an item on purpose, send
"associations": {}in the patch. - If you changed menu items on a multilingual Joomla site with an earlier version, check their Associations tab in Joomla (Menus -> the item -> Associations, or Components -> Multilingual Associations) and re-link the translations that lost their link. Single-language sites were not affected, and nothing else about the items changed.
v0.11.2 — bulk-replace scans a WordPress site once, every page
- The CLI
bulk-replaceon WordPress re-read the same page of results instead of moving on (up to ~50 times), then usually stopped with400 Bad Requestwhen it asked for a page past the last one, without printing its report. It now reads each page of results once and each page and post exactly once. - If you ran
bulk-replace --applyon WordPress with a replacement that contains the search text (for examplefoo->foobar), the objects on a re-read page were rewritten more than once. Unless backups were turned off, each rewrite left a backup in your backup folder: the oldest backup of an object is its original state. - WordPress, pages and posts together (no
--type, and the default ofyootheme_bulk_find_replace): when one of the two ran out of pages before the other, WordPress answered400and the scan stopped (the MCP tool reported it asaborted). A type with no more pages is now simply finished. - Joomla, articles and modules together (no
--type): the CLIbulk-replacecould skip objects when one of the two had more than 50. With 60 articles and 5 modules, articles 51-55 were never scanned, so neither the preview nor--applytouched them. The MCP tool was not affected.
v0.11.1 — Previews (dry runs) are free
- The dry run of
yootheme_replace_text,yootheme_replace_imageandyootheme_bulk_find_replace(the default,dry_run: true) and of the CLIbulk-replace(without--apply) now works without a licence key: see what a replace would change before you buy. Applying it still needs a key, and is refused before the site is contacted. - A free preview cannot write: while it runs, the WordPress and Joomla clients refuse every request that is not a read, the SSH backend refuses its write commands, and no pre-write backup is taken (a preview never took one).
- The licence (section 1A) now includes previews in the free use.
v0.11.0 — Free read-only tier, licence enforced on writes, ready for PyPI
- Licensing changed. The 20 read-only MCP tools and the read-only CLI commands now work without a key. The 21 tools and 14 commands that change a site need a valid key and refuse clearly without one — before any confirmation prompt and before any request to the site. Until 0.10.x a missing key or an unreachable licence server let everything run.
- Two key types. MCP Marketplace keys (
MCP_LICENSE_KEY,mcp_live_…) are accepted next to GMC store keys (YOOTHEME_MCP_LICENSE_KEY/license.key). - No more exit at start-up. A missing or rejected key no longer stops the MCP
server or the CLI (exit code 2 in 0.10.x); the status is reported on stderr.
yootheme-mcp --helpno longer contacts the licence server. - Bypasses removed. The two environment variables that skipped the check or kept a rejected key running no longer exist and are ignored if set.
- Signed licence cache. A successful check is cached in a signed file that holds a hash of the key instead of its last characters; an offline machine keeps write access for at most 7 days after the last successful check. The 0.10.x cache file is ignored and replaced.
- Joomla tools annotated. The 12 Joomla tools now declare
readOnlyHint/destructiveHintlike the others, so MCP clients know which ones to confirm. - PyPI packaging. Wheel and sdist are built from the same allowlist as the
customer zip and audited against it (
scripts/build_pypi.py). NewLicensingandPrivacysections in this README;server.jsonfor the MCP Registry.
v0.10.2 — Native WordPress builder content
- Read and discover YOOtheme 5 layouts from editable post content.
- Write the native JSON comment and independently verify persisted content; preserve surrounding text and fail on missing edit access or ambiguous roots.
- Keep automatic pre-write layout backups. Refuse conversion of non-builder content unless explicitly requested; return the replaced body when requested.
- Escape HTML comment delimiters inside layout text and retain custom meta keys.
- Correct stale package and price information in this README.
v0.10.1 — "local" is a hostname, not a word in the URL (security)
Read this before updating: a site entry that this client accepted until now can be
rejected after the update. If any site in your sites.json (or .env) uses http://
for anything other than the loopback machine itself — localhost, 127.0.0.1, ::1 —
or writes the credentials inside the URL (https://user:password@host/), the client will
now refuse that entry with a configuration error instead of connecting. The fix is to give
the site its real https:// URL and keep user and password in their own fields
(wp_username / wp_app_password, or joomla_token). Nothing else about your
configuration changes, and no data on any site is touched by this update.
- The local-development exemption tested the whole URL, so remote sites could pass as
local. This client allows plain
http://and relaxed TLS checking for a site running on the operator's own machine. That exemption was implemented as a text search for the loopback names anywhere in the URL string, rather than a test of the host the request actually goes to. A perfectly ordinary remote address could therefore satisfy it — for example because a loopback name happened to appear further along in the path, or because it formed the leading part of a longer, publicly registrable domain name. When that happened the client would talk to a real, remote site over unencrypted HTTP, or over HTTPS with certificate verification switched off — while still attaching theAuthorizationheader (WordPress application password, or Joomla Bearer token) that it sends with every request. Anyone positioned on that network path could read the credentials, or present any certificate at all and be believed. - Now the decision is made on the host, not on the text. The URL is parsed and its
normalised hostname compared against an exact set (
localhost,127.0.0.1,::1). Both the WordPress and the Joomla transport ask the same single function whether to verify TLS, so the rule enforced when a site is loaded and the rule enforced when a request goes out can no longer drift apart. URLs that carry credentials in the userinfo part, that have no host, or that use a scheme other thanhttp/httpsare rejected outright. - A configuration error quoted your password back at you. Pydantic renders the
offending input value into its validation message; the config loader interpolated that
message into its own error, and the MCP layer rendered it into the tool reply. So
mistyping a field name —
wp_app_passworddforwp_app_password, say — made the value of that field part of the answer returned byyootheme_list_sites, a tool whose contract is that it never returns secrets. Validation errors now report only which field failed and why; the value is dropped. The two configuration models additionally set pydantic's ownhide_input_in_errors, so a future validator cannot reintroduce the leak by accident. - New offline regression suites
tests/test_loopback_transport.py(it inspects the SSL context the HTTP client actually built, for both adapters, without opening a socket) andtests/test_config_error_redaction.py(a canary value, with a negative control that proves the leak is real when the fix is removed). Suite: 126 tests, all passing.
Coming from a 0.9.x release? Two earlier changes matter more than this one. 0.9.2
fixed a fresh install that could not start the MCP server at all: the mcp dependency had
no upper bound, so a new pip install resolved a 2.x release that no longer contains the
module the server imports, and python -m yootheme_mcp — the exact command in the Claude
Desktop configuration — died on import while the yootheme CLI kept working. 0.10.0
gave every write path a backup: a YOOtheme layout is versioned by neither WordPress nor
Joomla, so before that release a set, import, duplicate or find-replace through this
client was final. Each write now snapshots what it replaces and returns the path as
backup_file; re-importing that file is the undo. Install as usual — unpack the zip over a
fresh directory and run pip install -e . in it (full steps in INSTALL.md), then confirm
with yootheme --version.
v0.10.0 — A half-failed write now leaves something to go back to
- An overwrite was final, and the tool said otherwise.
yootheme_set_layouttold the operator "on WordPress this is destructive but WP revisions usually allow recovery". It is not true: a YOOtheme layout lives in post meta on WordPress, and WordPress does not carry post meta into revisions; on Joomla it lives in an article'sfulltextor a module'scontent, which have no builder versioning either. So neither platform could undo aset/import/duplicate/ find-replace done through this client — the one product of the three (yootheme-mcp, elementor-mcp, gmcbuilder-mcp) with no backup and no restore. Every write path now snapshots the layout it is about to replace to disk first and returns the path asbackup_file; feeding that file toyootheme_import_layoutis the undo. Default~/.yootheme-mcp/backups, override withYOOTHEME_BACKUP_DIR. - A backup that cannot be taken now refuses the write instead of proceeding quietly — a
safety net nobody can tell is missing is worse than none. Set
YOOTHEME_BACKUP=0to accept the loss deliberately. Likewise a read that fails for any reason other than "there is no layout here" blocks the write: a site that cannot be read reliably must not be overwritten blind. - A site-wide replace that died half way threw away the list of what it had already
changed.
yootheme_bulk_find_replacewrites one object at a time and is not atomic; when the paging call timed out at object 51, the 50 layouts already rewritten on the live site were reported as a single line readingError: Request timed out. The per-object report is now always returned, withaborted,aborted_error,objects_committed, and abackup_fileper committed object. yootheme bulk-replace --applyasks before committing (--yesto skip). It rewrites every matching layout on a whole site and had no prompt at all.yootheme presets importasks too — it overwrites any preset of the same name.- New regression suite
tests/test_failure_recovery.py(14 tests) drives the destructive paths against a transport double that fails exactly where told: mid-page timeouts, refused writes, an unwritable backup directory.
v0.9.9 — The shared install guide names the twin's real versions
INSTALL.md(the guide mirrored with elementor-mcp) still described elementor-mcp0.5.0: it pointed at the companion zipgmc-elementor-mcp-0.5.0.zipunder the vendor's dev pathD:\elementor-mcp\..., promised a ping answer ofplugin_version 0.5.0, and the versions table was three elementor releases behind. A reader following it would fetch a superseded companion from a folder that only exists on the vendor's machine and then judge a correct install broken when ping answered a different number. Re-synced to the elementor-mcp 0.5.6 mirror (the twin fixed its copy in its own 0.5.6 release): the companion isgmc-elementor-mcp-0.5.1.zipshipped inside the elementor package, ping answers0.5.1, and the versions note records that client (0.5.6) and companion (0.5.1) move independently. Docs-only — no code changes.
v0.9.8 — The delivery zip tells the truth about itself
- The 0.9.7 zip shipped a README that said 0.9.6 (no 0.9.7 changelog either): the zip was built from the tree before the release docs were committed. Docs are now aligned and the zip is rebuilt from the committed tree, so the package a customer opens names the version it actually is.
- Install docs told you to run
INSTALL.ps1— a file the zip deliberately does not contain (it is the vendor's personal installer, excluded by the packaging allowlist since 0.8.3). README / INSTALL.md / PRODUCTS.md now give the real steps:pip install -e .plus the shippedextras/INSTALL_WINDOWS.mdguide.
v0.9.7 — Windows APPDATA paths + MEDIUMTEXT ops
- Licence / default
sites.jsonon Windows use%APPDATA%\yootheme-mcp\(aligned with docs). scripts/alter_mediumtext.sqlshipped for sites still on TEXT 64KB; local jtest54 modules raised to MEDIUMTEXT.
v0.9.6 — Unified MCP/CLI/Skill package + soft licence check
- One storefront SKU / one zip: MCP + CLI + agent Skill (skill/).
- Soft licence check against GMC Licenses (api.entitlement); key in %APPDATA%/yootheme-mcp/license.key or YOOTHEME_MCP_LICENSE_KEY.
- MCP Joomla ops parity: article CRUD, media, menus, templates (clear error on WordPress).
- Layout schemas document Joomla
module(production path). - List price EUR 39 one-time (was 29). Existing keys stay valid.
v0.9.5 — ArticleHelper-faithful article layouts + Pro 5.0.37
- Article
has_layout/ list use the leading<!-- {json} -->fulltext comment only (attribs mirror is write-only BC — no more false positives after body wipe). - Serialize forces single-line compact JSON; extract matches
ArticleHelper::PATTERN(start-anchored, no DOTALL). - List requests
text+fulltextfor articles; clearer "no layout" errors. - WordPress adapter:
verify=Falseon localhost (parity with Joomla). - Docs target bumped to YOOtheme Pro 5.0.37.
v0.9.4 — Reliable failures, bulk operations and Joomla module layouts
- Failures are no longer reported as success. Joomla discovery now raises when every endpoint fails, WordPress settings writes verify that a real YOOtheme option changed, SSH list failures surface, and preset deletion reports whether anything was actually removed.
- Bulk operations preserve a truthful report. Read and write failures are returned per object, partial progress is retained, pagination terminates correctly, and the SSH backend can scan beyond its first page.
- Joomla builder modules now use their real storage. Reads and writes go
through the module
contentfield used bymod_yootheme_builder; the oldparams.yootheme_provalue remains only as a compatibility mirror. - CLI and settings correctness fixes. Bracket-indexed settings paths can be
written,
--http --porthonors the requested port, and multi-sitebulkinserts the site argument in the right position. - Safer operator messaging. Recursive media-folder deletion is described as permanent, commands that intentionally skip confirmation are documented, and the customer ZIP no longer refers to files it does not ship.
v0.9.3 — The install example printed the old version too
- The Install section showed
yootheme --versionreturning0.8.2. 0.9.2 fixed the version the command prints but not the one the documentation promises, so the first thing a reader checks after installing still showed the superseded number — the same defect, one layer over. The example no longer pins a version literal at all, and a test refuses one, because a number restated in prose is a number that drifts.
v0.9.2 — A fresh install starts the MCP server again
Packaging, plus four more destructive-path defects — two of them introduced by the fixes in 0.9.0 and 0.9.1.
article update --textdestroyed a builder layout. The mirror image of what 0.9.0 fixed in the other direction: the read-more split sendsfulltext: ""whenever the new body carries no marker, andfulltextis where an article's layout lives. The page reverted to plain text with no copy anywhere, whilelist/get_layoutkept reporting a layout because they fall back toattribs. Now refused unlessreplace_layoutis passed.- A failed trash was reported as trashed.
article deletetolerates 400/409/422 because an already-trashed article answers that way — but on the trash-only path it then returned{"trashed": true}after a PATCH that had failed, so the article sat live and untouched while the operator was told it was in the trash. Only the destroy path may treat that as harmless. set --replace-bodydiscarded the body it promised to hand back. It was returned "in the JSON output and nowhere else", and the default human mode has no JSON output at all. The replaced body is now written to a file beside the layout — or printed when there is nowhere to put it — in every mode, andduplicatesurfaces it too.- The scripts-removal guard had a type-shaped hole. It only ran when the
value was a Python list, so a JSON-encoded string or a single descriptor dict
skipped the check and replaced the whole list while reporting
wrote: true. The value is normalised before the diff; anything that cannot be a scripts list is refused rather than written. mcp[cli]had no upper bound, and mcp 2.x removedmcp.server.fastmcp. A virtualenv created today resolved mcp 2.0.0, sopython -m yootheme_mcp— the command in the Claude Desktop configuration — died at import withModuleNotFoundError. The terminal CLI kept working, because it never imports the server: the failure was invisible from the command line and total in Claude Desktop. The requirement is now>=1.2.0,<2.yootheme --versionreported 0.8.2.__version__had stayed behind while the package moved to 0.8.3, 0.9.0 and 0.9.1, so the CLI named the version the customer had just replaced. Both are now covered bytests/test_packaging_contract.py, which asserts the declared range and the version string rather than the local environment — the environment is what hid the first defect.
v0.9.1 — A scripts write can no longer delete what it omits
yootheme_set_custom_code(kind='scripts')replaced the entire list, so any script or External-Services entry the caller left out was deleted from a live site. The docstring said so; nothing enforced it, which makes it a trap rather than a warning — and an agent composing a list from memory drops entries very easily. The write is now refused when it would remove existing entries, naming them inwould_remove;allow_removals: trueaccepts the loss and hands the removed descriptors back asremoved. Adding entries needs no flag.
v0.9.0 — Destructive commands stop lying
Three ways this tool could destroy work while reporting success. Each one is
covered by a regression test in tests/test_destructive_commands.py.
article deletedestroyed the article while promising the trash. Its confirmation read "Joomla trashes it first, second delete is permanent", but the implementation ran the trash and the permanent delete in that one confirmed command. The operator was told the article was still recoverable; it was already gone. Delete now trashes — recoverable from Joomla's trash — and destroying needs--permanent, whose prompt says exactly that.set --type articleblanked the article body. A Joomla article's builder layout has to be the entire body (an HTML comment at the start offulltext, withintrotextempty), so writing a layout onto an article that had text replaced that text, silently, with no version to restore from. Writing a layout over a body is now refused;--replace-body/replace_body: trueaccepts the loss and returns the previous body asreplaced_body. Editing an article that already has a layout is unaffected — no flag needed.- Every write through the SSH backend failed.
_wp_stdinpassed a literal-as the value, on the belief that WP-CLI reads stdin when it sees a dash. It doesn't:wp post meta update <id> <key> [<value>]andwp option update <key> [<value>]read stdin when the value is omitted, so the dash was stored as the value — and under--format=jsona bare-is invalid JSON, so the command errored. The command line is now built in one place, so a second copy cannot drift away from the first again. - Also: the SSH runner waited for the remote exit status before draining stdout,
which deadlocks whenever output exceeds the channel window —
wp option get yoothemeon a real site is comfortably that big.
v0.8.2 — Settings and Joomla article updates
- Fix: Settings convenience accessors used wrong YOOtheme Pro 5.x keys.
Verified against live Joomla 5.x sites and the yootheme.com Settings docs, the
real config keys are
consent(Cookie Consent Manager),scripts(custom JavaScript, a list of descriptors) andcustom_less(Settings → CSS). The tool previously usedcookies,head_scripts/body_scriptsandcustom_css— keys that don't exist in the config, soyootheme_get/set_cookie_consentand thehead/body/csscustom-code shortcuts were silent no-ops (getreturned{},setwrote dead keys). Now: cookie-consent reads/writesconsent(with acookiesread-fallback); the custom-codekindiscss/less→custom_lessandscripts→ the JS list. The genericsettings get/set --key <path>was already correct (it operates on raw keys); only the docs/help examples were updated to match. Added a Customizer coverage map to the feature docs cross-referencing every customizer area against the yootheme.com documentation. - Fix: Joomla
article updatebody edits now persist. The update path sent the new body underarticletext, but Joomla's JSON Web Services API ignores that key on PATCH (it's a form-layer alias) — the request returned HTTP 200 and bumpedmodified/versionwhile leaving the stored body unchanged. The update path now remapsarticletext→introtext(the writable model field). CREATE is unaffected (it still acceptsarticletext).
v0.8.0 — Packaging & SSH adapter
- New WordPress SSH adapter (
wordpress_ssh.py): routes WordPress sites withssh_hostconfigured through wp-cli over SSH instead of the REST API. - Project packaged as a distributable wheel + sdist (
python -m build). - Canonical version is now declared once in
pyproject.tomland re-exported asyootheme_mcp.__version__.
v0.4.0 — CLI-first
- New
yoothemeCLI as the primary entry point. - Removed
status=anyfrom the default page listing (was triggering HTTP 400 on sites without elevated REST permissions). - MCP server still ships under
yootheme-mcpfor Claude Desktop integration. - README rewritten around CLI usage; MU-plugin downgraded from required to optional workaround.
v0.3.0 — Real-world hardening
- WordPress: auto-detect of pretty vs Plain permalinks (auto-fallback to
?rest_route=). - Joomla: new
object_type='module'alongside'article'. extras/folder withmu-yootheme-rest.php, Windows config samples and install guide.
v0.2.0 — YOOtheme Pro 5.0.34 coverage
- Tools for Settings panel, Cookie Consent Manager, Element Presets, scroll animations inspector.
v0.1.0
- Layouts, Customizer, bulk find/replace, WP + Joomla adapters.
License
Commercial, sold per licence through GMC Licenses and MCP Marketplace — see
LICENSE. The read-only features may be used without a key (see
Licensing). Not open-source: you may use and modify it for your
own sites, but not redistribute or resell it. The bundled WordPress companion
extras/mu-yootheme-rest.php is GPL-2.0-or-later. Third-party Python
dependencies keep their own licences.
Metadata
Release files for yootheme-mcp 0.11.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| yootheme_mcp-0.11.3.tar.gz | 118.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| yootheme_mcp-0.11.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 216.8 kB
Release files / yootheme_mcp-0.11.3.tar.gz
| Download URL | yootheme_mcp-0.11.3.tar.gz |
|---|---|
| Size | 118.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8bb164ad40ea81dd01f9832adf2cbf604526cef00211f9cb6450036a8024c2af
|
|
BLAKE2b-256 checksum How to use checksums |
5bbfe758227cf3ea0bb7296f8083c82099af5925238bb7d7898bb41a52b43fb2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.11
|
Release files / yootheme_mcp-0.11.3-py3-none-any.whl
| Download URL | yootheme_mcp-0.11.3-py3-none-any.whl |
|---|---|
| Size | 98.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2f96386d1c95ecbb920b9611610956f2bfc3a4c69dbf9e7908a86afb5ed6a283
|
|
BLAKE2b-256 checksum How to use checksums |
1ec5963fae4e3aa9e30d5a653bbc8809f1d8f735d8f4efbf53dca413104d02ce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.11
|