voog-mcp
voog-mcp is an MCP server for managing Voog CMS websites — covering content, ecommerce, templates, multilingual settings, and more from an LLM agent or CLI.
Site & Configuration: List configured sites; get, update, and manage site settings and custom data keys.
Pages: List, get, create, update, duplicate, delete pages; manage visibility, layouts, custom data; add content areas; snapshot pages.
Articles & Blog: List, get, create, update, publish, delete articles; manage custom data and tags; list/delete/spam comments.
Layouts & Templates: Pull/push Liquid templates to local files; create, update, rename, delete layouts and layout assets (CSS, JS, images).
Content & Texts: Get and update text content areas; update content partials.
Elements: List, get, create, update, delete, and reorder structured content elements; list element definitions.
Products: List, get, create, update, duplicate, delete products; set product images; apply bulk actions.
Categories: Full CRUD for ecommerce product categories.
Orders: List and get orders (read-only, PII-stripped by default).
Discounts & Cart Rules: Full CRUD for discount codes and cart rules.
Ecommerce Settings: Get and update store settings; list shipping methods and payment gateways (read-only).
Multilingual: List, create, delete languages; manage page nodes for multilingual tree structure.
Redirects: List, add, update, delete redirect rules (supports 301, 302, 307, 410 and regex patterns).
Webhooks: List, create, update, delete webhooks.
Search: Full-text search across published pages, articles, elements, and products.
Snapshots: Full site or pages-only snapshots for backup/auditing before risky operations.
Passthrough APIs: Direct read-only or mutable access to the Voog Admin and Ecommerce APIs for endpoints not covered by typed tools.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@voog-mcp list pages on mysite"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
voog-mcp
CLI and MCP server for Voog CMS — manage Liquid templates, pages, products, ecommerce settings, and redirects from your terminal or directly from Claude / any MCP client.
What is Voog?
Voog is a multilingual website builder and CMS with built-in ecommerce, used for content sites and small online stores. This package wraps its admin API so you can edit templates, pages, products, and redirects from your shell or an LLM agent.
Related MCP server: Freelo MCP Server
Install
From PyPI:
pip install voog-mcp
# or, no install: uvx voog-mcp --helpOr directly from GitHub (latest unreleased main):
uvx --from git+https://github.com/runnel/voog-mcp.git voog --helpFor development:
git clone https://github.com/runnel/voog-mcp
cd voog-mcp
python3.10 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"Configure
Run voog config init to interactively create the global config:
voog config initThis creates ~/.config/voog/voog.json with your tokens inline:
{
"sites": {
"mysite": {"host": "mysite.com", "api_key": "vk_..."},
"client_a": {"host": "clienta.com", "api_key": "vk_..."}
},
"default_site": "mysite"
}Get a token from your Voog admin: Admin → API.
Shared / CI configs
If voog.json is checked into version control or shared across machines, keep the token out of the file by referencing an env var instead:
{
"sites": {
"client_a": {"host": "clienta.com", "api_key_env": "CLIENT_A_KEY"}
}
}Then put the token in ~/.config/voog/.env:
CLIENT_A_KEY=vk_...Both forms can coexist per-site. When both api_key and api_key_env are set, the env-var wins if it's defined — so an inline value acts as a default that the deployment overrides.
Per-repo site selection
In a repo dedicated to one Voog site, drop a voog.json at the repo root to pin the site:
{"default_site": "mysite"}The cwd-level voog.json deep-merges over the home config, with cwd winning per-key. Inside sites, the merge is per-site name — a cwd entry replaces the whole site definition (host + token), it does not merge individual fields. You can also redefine entire sites here (handy for client repos that should bring their own host/token without touching the home config):
{
"sites": {
"client_x": {"host": "clientx.com", "api_key": "vk_..."}
},
"default_site": "client_x"
}Now voog pull / voog push from that directory always target the right site, even if the home default differs.
Note:
voog-site.jsonfrom earlier versions still works but emits aDeprecationWarning. Replace it withvoog.jsoncontaining{"default_site": "<name>"}for the same effect.
Use the CLI
voog --help # all commands
voog config list-sites # show configured sites
voog --site mysite products # list products on mysite
voog pull # download templates (uses cwd-level voog.json)
voog push layouts/Front\ page.tpl
voog redirects
voog config check # verify all configured tokens
voog site-snapshot backup/ # full-site snapshot for diff/auditUse as MCP server
Add to your Claude Code config (or any MCP client). The simplest setup uses the published PyPI package:
{
"mcpServers": {
"voog": {
"command": "uvx",
"args": ["voog-mcp"]
}
}
}If you'd rather track unreleased main (e.g. for a fix that hasn't shipped yet), point uvx at the GitHub repo instead:
{
"mcpServers": {
"voog": {
"command": "uvx",
"args": ["--from", "git+https://github.com/runnel/voog-mcp.git", "voog-mcp"]
}
}
}Every tool requires a site parameter. Start with voog_list_sites to discover what's configured:
voog_list_sites()
→ [{"name": "mysite", "host": "mysite.com"}, ...]
page_get(site="mysite", page_id=42)
→ {...}Tools
Full endpoint coverage reference: docs/voog-mcp-endpoint-coverage.md
Group | Tools |
Sites |
|
Search |
|
Pages |
|
Articles |
|
Comments |
|
Tags |
|
Layouts |
|
Texts / contents |
|
Elements |
|
Products |
|
Categories |
|
Media library |
|
Media sets (galleries) |
|
Orders |
|
Discounts |
|
Cart rules |
|
Shipping / payments |
|
Ecommerce settings |
|
Multilingual |
|
Redirects |
|
Site |
|
Webhooks |
|
Snapshot |
|
Cross-site clone |
|
Read-only passthrough |
|
Generic passthrough (writes) |
|
What's NOT supported
voog-mcp covers content + ecommerce catalog management end-to-end as of v1.4. The following Voog API areas remain out of scope — drop down to the passthrough tools when you need them — voog_admin_api_read / voog_ecommerce_api_read to read, voog_admin_api_call / voog_ecommerce_api_call to write:
Order mutation —
orders_list/order_getare read-only typed tools (with PII stripping); creating / updating / cancelling orders goes via passthrough. Order writes carry finance / operations risk that a future release will design separately.Cart reads —
cart_rules_*tools cover cart-rule CRUD, but reading individual cart sessions (/carts) is passthrough-only.element_definitionsCRUD —element_definitions_listis wrapped; create / update / delete remain passthrough.People / site_user admin — full passthrough.
Form definitions and form responses — passthrough.
Site favicons and bulk file imports — product image galleries are first-class via
product_set_images; other multipart uploads go via passthrough.Site creation — voog-mcp targets existing sites.
If you need any of these, open an issue — or a PR.
License
MIT
Available Tools
111 toolsarticle_add_contentA
Create a content area + linked text on a blog article (POST /articles/{id}/contents) — the article counterpart of page_add_content, with identical semantics. Use it to seed a freshly-created article's body or gallery, which /contents reports as [] until edit-mode opens the article.
name must match the article layout's {% content %} tag (default 'body'; named areas match {% content name="..." %}). content_type defaults to "text"; valid values: text, gallery, form, content_partial, buy_button, code.
Refuses by default if an area with the same name already exists, since calling twice otherwise creates duplicates. Repeated names ARE legitimate in some article layouts (observed live: two 'text-images' areas on one article) — pass force=true for those.
Inputs: site (required), article_id (required), name, force, +1 more.
article_createA
Create a new blog article. Required: page_id (the parent blog page), title. Optional: body (HTML), excerpt, description (meta), path (auto from title if omitted), image_id, tag_names (array), data (custom dict), publish (default false). Title and body go to autosaved_* fields per Voog convention; if publish=true, publishing:true is set so values copy to published fields atomically. NOT idempotent — repeat calls create multiple articles.
Inputs: site (required), title (required), page_id (required), body, +7 more.
article_deleteBDestructive
Delete an article. IRREVERSIBLE — Voog does not retain deleted articles. Requires force=true.
Inputs: site (required), article_id (required), force.
article_delete_dataADestructive
Delete a single article.data. (DELETE /articles/{id}/data/{key}). IRREVERSIBLE — the key is removed permanently. Requires force=true; without it the call is rejected. Keys starting with 'internal_' are server-protected and rejected client-side.
Inputs: key (required), site (required), article_id (required), force.
article_getARead-onlyIdempotent
Get full article details by id (title, path, body, autosaved_*, published_at, language, page, data, image, tags). Read-only.
Inputs: site (required), article_id (required).
article_publishAIdempotent
Publish an article. Voog only copies autosaved_* → published fields when publishing:true is sent in the SAME PUT as the autosaved values — that's why this needs a separate tool rather than a publish flag on article_update.
Two modes:
FAST PATH (recommended) — pass ALL THREE autosaved_title, autosaved_body, autosaved_excerpt args. Tool issues a single PUT atomically; no race window.
FALLBACK — pass none of them. Tool does GET to fetch current autosaved_* values then PUTs them back with publishing:true. There is a small race window between the GET and the PUT — if the article is edited concurrently, the publish may capture a stale snapshot.
Mixed (some autosaved_* provided, some not) is rejected — the caller must be explicit.
Inputs: site (required), article_id (required), autosaved_body, autosaved_title, +1 more.
article_set_dataAIdempotent
Set a single article.data. value (PUT /articles/{id}/data/{key}). To delete a key use article_delete_data. Keys starting with 'internal_' are server-protected and rejected client-side.
Inputs: key (required), site (required), value (required), article_id (required).
articles_listARead-onlyIdempotent
List blog articles on the Voog site (simplified: id, title, path, public_url, published, published_at, updated_at, created_at, language_code, page_id). All filters optional. Read-only.
Inputs: site (required), tag, sort, filters, +3 more.
article_updateAIdempotent
Update an existing article. Title/body/excerpt go to autosaved_* per Voog convention (the public fields are read-only — call article_publish to push autosaved → published). description/path/image_id/tag_names/data are non-autosaved fields and update directly. At least one field must be supplied.
data field is sent via PATCH (merge semantics) — only the keys you pass are touched. To delete a key, use article_delete_data. Calls without data route via PUT as before.
Inputs: site (required), article_id (required), body, data, +6 more.
asset_replaceA
Rename a layout_asset by creating a new one with the desired filename (DELETE+POST workaround — Voog API rejects PUT with filename changes). Returns both old and new ids. The OLD asset is intentionally left in place; after updating templates that reference the old filename, delete the old asset manually with DELETE /layout_assets/{old_id}.
Inputs: site (required), asset_id (required), new_filename (required).
asset_uploadADestructiveIdempotent
Upload local image files into the site's media library (POST /assets -> PUT bytes -> PUT confirm) WITHOUT attaching them to anything. Use for images referenced from site.data / page.data maps, article galleries or template markup; use product_set_images when the images are a product's gallery.
Returns each asset's id, dimensions, public path (/photos/) and the derivative sizes Voog actually produced — build srcsets from those widths, never from guessed ones (a width Voog did not make answers 403 and the browser renders nothing). Voog caps the LONG side of each derivative at 150/600/1280/2048 and scales the other side to match the source aspect ratio, so a wide image's derivatives are capped on WIDTH, not height.
By default an existing asset with the same filename is REUSED rather than uploaded again: Voog auto-suffixes duplicate filenames (photo-1.jpg), so re-uploading silently orphans the original. Pass allow_duplicate=true to force a new asset (e.g. a corrected re-shoot under a fresh sequence letter).
Waits for Voog to finish its async resizes before returning — up to 120s per file, polling the API every 5s, so a large batch is slow by design. Each result carries sizes_complete: false means the wait timed out and sizes is PARTIAL, so build the srcset from a later read rather than from those widths. Pass wait_for_sizes=false to skip the wait entirely (then sizes is empty).
Do NOT request a derivative URL over HTTP to check whether it exists — a too-early request gets a 403 that the CDN caches for ~1h, breaking a URL that was about to work.
Inputs: site (required), files (required), wait_for_sizes, allow_duplicate.
cart_rule_createA
Create a cart rule (POST /admin/api/ecommerce/v1/cart_rules). Envelope {cart_rule: {...}}. Required: kind, target_kind, target_id, conditions[], result{}. Inner conditions[] entries are {value, comparator, field, value_type}. result is {value, field, value_type}. Inner key validation is left to Voog — invalid combos return a 422 with the offending field.
Inputs: kind (required), site (required), result (required), target_id (required), +6 more.
cart_rule_deleteADestructive
Delete a cart rule (DELETE /admin/api/ecommerce/v1/cart_rules/{id}). Requires force=true. Past orders that already had the rule applied are not affected.
Inputs: site (required), cart_rule_id (required), force.
cart_rule_getBRead-onlyIdempotent
Get a single cart rule by id (GET /admin/api/ecommerce/v1/cart_rules/{id}). Read-only.
Inputs: site (required), cart_rule_id (required).
cart_rules_listBRead-onlyIdempotent
List all cart rules (GET /admin/api/ecommerce/v1/cart_rules). Read-only.
Inputs: site (required).
cart_rule_updateBIdempotent
Update a cart rule (PUT /admin/api/ecommerce/v1/cart_rules/{id}). Envelope {cart_rule: {...}}. Partial — at least one field must be supplied. Common partial updates: enabled, position.
Inputs: site (required), cart_rule_id (required), kind, result, +7 more.
categories_listARead-onlyIdempotent
List all ecommerce product categories (GET /admin/api/ecommerce/v1/categories). Read-only. Each entry has id, name, slug, parent_id, depth, created_at, updated_at. Use category.id from the results as the products_list(category_id=...) filter.
Inputs: site (required).
category_createA
Create a category (POST /admin/api/ecommerce/v1/categories). Envelope: {category: {...}}. Required: name. Optional: slug (auto-generated if omitted), parent_id (for sub-categories). NOTE: Voog does not support description / image_id on categories despite some doc pages suggesting otherwise — verified empirically 2026-05-27.
Inputs: name (required), site (required), slug, parent_id.
category_deleteADestructive
Delete a category (DELETE /admin/api/ecommerce/v1/categories/{id}). Requires force=true. Products in the category are NOT deleted; they're orphaned from the category. Voog may reject if the category has child categories.
Inputs: site (required), category_id (required), force.
category_getARead-onlyIdempotent
Get a single category by id (GET /admin/api/ecommerce/v1/categories/{id}). Read-only.
Inputs: site (required), category_id (required).
category_updateAIdempotent
Update a category (PUT /admin/api/ecommerce/v1/categories/{id}). Envelope: {category: {...}}. Partial — at least one of name / slug / parent_id must be supplied.
Inputs: site (required), category_id (required), name, slug, +1 more.
comment_deleteADestructive
Remove a comment (DELETE /articles/{article_id}/comments/{comment_id}). Voog returns 204. Requires force=true; without it the call is rejected. Run comments_list first to confirm the id.
Inputs: site (required), article_id (required), comment_id (required), force.
comments_listARead-onlyIdempotent
List comments on an article (GET /articles/{article_id}/comments). Returns the full Voog comment shape (id, author, body, email, is_spam, created_at). Use article_id from articles_list. Read-only.
Inputs: site (required), article_id (required).
comment_toggle_spamAIdempotent
Flip a comment's spam flag (PUT /articles/{article_id}/comments/{comment_id}). Body is FLAT: {is_spam: bool}. Voog's moderation UI also uses this endpoint. For other field edits use voog_admin_api_call (rare).
Inputs: site (required), is_spam (required), article_id (required), comment_id (required).
content_partial_updateAIdempotent
Update a content partial (PUT /content_partials/{id}). Content partials are reusable template fragments embedded in pages and layouts. PUT body is flat (no envelope). At least one of body (HTML/text content) or metainfo (object) must be supplied. Update is idempotent — calling with the same payload twice has the same end state.
Inputs: site (required), content_partial_id (required), body, metainfo.
discount_createA
Create a discount (POST /admin/api/ecommerce/v1/discounts). Envelope {discount: {...}}. Required: code. Empirically-verified enum values (rejected client-side with a clear error before round-tripping to Voog): status ∈ {open, closed} amount_mode ∈ {net, gross} discount_type ∈ {fixed, percentage} applies_to ∈ {cart, cart_and_shipping, categories, products, shipping} Optional: name, description, amount, valid_from (ISO8601), valid_to, redemption_limit, stackable, currency.
Inputs: code (required), site (required), name, amount, +10 more.
discount_deleteADestructive
Delete a discount (DELETE /admin/api/ecommerce/v1/discounts/{id}). Requires force=true. Already-used discount records remain on past orders; deletion only prevents future use.
Inputs: site (required), discount_id (required), force.
discount_getARead-onlyIdempotent
Get a single discount by id (GET /admin/api/ecommerce/v1/discounts/{id}). Read-only.
Inputs: site (required), discount_id (required).
discounts_listARead-onlyIdempotent
List all ecommerce discounts (GET /admin/api/ecommerce/v1/discounts). Read-only.
Inputs: site (required).
discount_updateAIdempotent
Update a discount (PUT /admin/api/ecommerce/v1/discounts/{id}). Envelope {discount: {...}}. Partial — at least one discount field must be supplied. Enum-restricted fields (client-validated): status ∈ {open, closed}; amount_mode ∈ {net, gross}; discount_type ∈ {fixed, percentage}; applies_to ∈ {cart, cart_and_shipping, categories, products, shipping}.
Inputs: site (required), discount_id (required), code, name, +11 more.
ecommerce_settings_getARead-onlyIdempotent
Get ecommerce store settings (currency, tax_rate, value_date_days, default_language, decimal_places, company_name, bank_details, terms, privacy_policy, products_url_slug, etc.). Includes per-language translations. Read-only. Note: this is also the source of truth for price_entry_mode (net vs gross) — product tools' price fields are interpreted against this setting.
Inputs: site (required).
ecommerce_settings_updateAIdempotent
Update ecommerce settings. attributes: flat root-level fields (currency, tax_rate, notification_email, ...). translations: nested {field: {lang: value}} for translatable settings. The set of translatable keys is discovered at runtime from GET /settings?include=translations (cached 60s per-site) — new server-side keys are picked up automatically. Wraps payload in {settings: {...}} envelope.
Inputs: site (required), attributes, translations.
element_createA
Create an element (POST /elements). Body is FLAT. Required: element_definition_id OR element_definition_title (id takes precedence per Voog docs); page_id; title. Optional: path (auto-generated from title if omitted), values (custom-properties hash matching the element_definition's schema).
Inputs: site (required), title (required), page_id (required), path, +3 more.
element_definitions_listARead-onlyIdempotent
List element definitions (id, title, property_keys — the field keys each definition expects). Use the returned id for element_create.element_definition_id. Read-only. Mutating definitions (POST/PUT/DELETE) is deferred — handle via voog_admin_api_call when needed.
Inputs: site (required).
element_deleteADestructive
Delete an element (DELETE /elements/{id}). Voog returns 204. Requires force=true; without it the call is rejected. Run elements_list first to confirm the id.
Inputs: site (required), element_id (required), force.
element_getARead-onlyIdempotent
Get a single element by id, with full values hash. Use elements_list to find ids.
Inputs: site (required), element_id (required).
element_moveAIdempotent
Re-order or re-parent an element instance (PUT /elements/{element_id}/move). Inputs travel as QUERY-STRING params per Voog docs (mirrors node_move). All params optional; supply at least one of page_id, before, or after. page_id = new parent page id (integer); before / after = existing element id for positional placement on current or new parent page. SCOPE NOTE: this operates on element INSTANCES inside a definition, not on element_definitions (the schema). element_definition mutations remain passthrough — different resource. Use elements_list to find element ids; use element_definitions_list for schema discovery. Voog docs: https://www.voog.com/developers/api/resources/elements
Inputs: site (required), element_id (required), after, before, +1 more.
elements_listARead-onlyIdempotent
List elements (id, title, path, page_id, element_definition_id, position). Optional filters: page_id, language_id, language_code, element_definition_id, element_definition_title, page_path, page_path_prefix. Pass include_values=true to include the values hash in the projection (off by default — values clutter list views; use element_get for full shape). Read-only.
Inputs: site (required), filters, page_id, page_path, +6 more.
element_updateAIdempotent
Update an element (PUT /elements/{id}). Partial — supply ONLY the fields to change. Body is FLAT. Updatable: title, path, values. At least one besides element_id is required.
Inputs: site (required), element_id (required), path, title, +1 more.
gateways_listARead-onlyIdempotent
List all payment gateways (GET /admin/api/ecommerce/v1/gateways). Read-only. Each entry has code, name, enabled, enabled_methods[], all_payment_methods[], url, created_at, updated_at.
Inputs: site (required).
language_createA
Add a new language to the Voog site (POST /languages). Required: code (ISO 639-1 two-letter), title. Optional: region (ISO 3166-1 alpha-2), site_title, site_header, default_language, published, content_origin_id (duplicate content from another language). Body is FLAT — no envelope wrapper.
Inputs: code (required), site (required), title (required), region, +5 more.
language_deleteADestructive
Remove a language from the site (DELETE /languages/{id}). IRREVERSIBLE — Voog deletes the language and unbinds associated content. Requires force=true; without it the call is rejected. Run site_snapshot first if uncertain.
Inputs: site (required), language_id (required), force.
languages_listARead-onlyIdempotent
List all languages on the Voog site (id, code, title, default_language, published, position). Use the returned ids for page_create.language_id / article fields. Read-only.
Inputs: site (required).
layout_asset_createA
Create a layout_asset. TEXT content only, and data crosses a JSON boundary — literal \uXXXX escapes in a source file arrive already decoded, so raw U+2028/U+2029 and C0 controls are refused as the fingerprint of that (issue #138). Deploy a tracked file byte-exactly with layouts_push; upload binaries with layout_asset_upload. filename + asset_type + data required. asset_type ∈ {stylesheet, javascript, image, plain_text, video, pdf, ...}. For image uploads, use POST /assets + 3-step protocol via product_set_images instead — this tool is for text assets (CSS/JS/HTML fragments).
Inputs: data (required), site (required), filename (required), asset_type (required).
layout_asset_deleteADestructive
Delete a layout_asset. IRREVERSIBLE. Refuses without force=true. Templates referencing the deleted file will render with empty content.
Inputs: site (required), asset_id (required), force.
layout_asset_updateAIdempotent
Update a layout_asset's content (PUT /layout_assets/{id} {data}). filename is read-only — Voog returns 500 if filename is sent on PUT. Use asset_replace to rename. NOT byte-exact for a file on disk: data crosses a JSON boundary, so literal \uXXXX escapes in the source arrive decoded and Voog stores the decoded form under a clean ✓ (issue #138). To deploy a tracked .js/.css file, use layouts_push(files=[...]) — it reads from disk. This tool refuses content carrying raw U+2028/U+2029 or C0 controls, the decode fingerprints that change what a file means.
Inputs: data (required), site (required), asset_id (required), filename.
layout_asset_uploadADestructive
Upload a BINARY layout asset from disk — favicon, icon, font, inline image (multipart POST /layout_assets, issue #140 item 4). layout_asset_create only carries text data, so binaries previously needed a raw curl call.
Served from /images/ (or the site's asset path), not /photos — these live with the templates, not in the media library. For photos referenced from content or site.data use asset_upload instead.
Voog derives asset_type and content_type from the file; the result is editable=false (no text body to edit). Uploading the same filename again creates a SECOND asset — delete the old one, or use asset_replace semantics, if you meant to replace it.
Inputs: site (required), file_path (required), filename.
layout_createA
Create a new layout or component (POST /layouts). kind='layout' for full templates (defaults content_type='page'; use 'blog_article' for blog post templates); kind='component' for shared partials (content_type ignored). Returns the new id. NOT idempotent — calling twice creates two separate layouts.
Inputs: body (required), kind (required), site (required), title (required), +1 more.
layout_deleteADestructive
Delete a layout. IRREVERSIBLE — Voog does not retain deleted layouts. Refuses without force=true.
Voog blocks deletion of layouts that still have pages assigned — the API returns an error, the layout is NOT deleted. Reassign those pages first via page_set_layout, then retry. Back up with site_snapshot before this operation.
Inputs: site (required), layout_id (required), force.
layout_renameAIdempotent
Rename a layout (PUT /layouts/{id} {title}). Reversible — rename back to the original title to undo. The new title must not contain '/' or '\' or start with '.'.
Inputs: site (required), layout_id (required), new_title (required).
layouts_pullAIdempotent
Fetch every layout + component from /layouts, plus the editable layout_assets (CSS/JS), and write them under target_dir: .tpl files to layouts/ and components/, assets to stylesheets/ javascripts/ assets/. Binary assets are skipped (no text to round-trip — use layout_asset_upload for those). Builds manifest.json mapping each local path to {id, type, updated_at}. REFUSES to overwrite an existing tree that already contains .tpl files — pick a fresh location or clear it first. Empty/non-tpl content in target_dir is fine (e.g. README.md, .gitignore are preserved).
Inputs: site (required), target_dir (required).
layouts_pushAIdempotent
Read manifest.json + tracked files from target_dir and PUT each to the endpoint its manifest type maps to: type=layout → /layouts/{id} {body}, type=asset (legacy: layout_asset) → /layout_assets/{id} {data}. Optional files=["javascripts/app.js", ...] filter pushes only the named relative paths; files=null (or omitted) pushes every manifest entry. THE BYTE-EXACT WAY to deploy a .js/.css asset: content is read from disk here, whereas a string passed to layout_asset_update crosses a JSON boundary that decodes literal \uXXXX escapes in the source (issue #138). A PUT that returns 200 without persisting is reported as a failure, not a ✓. Returns per-file success/failure breakdown; missing files and PUT errors are captured per-entry and do not abort the remaining pushes. Recommended pre-flight: site_snapshot for full backup before a mass push.
Inputs: site (required), target_dir (required), files.
layout_updateAIdempotent
Update a layout — body (Liquid template source), title, or both. At least one must be supplied. Reversible by calling again with the previous values; idempotent. body crosses a JSON boundary, so literal \uXXXX escapes in a source file arrive already decoded; raw U+2028/U+2029 and C0 controls are refused as the fingerprint of that (issue #138). To deploy a tracked .tpl byte-exactly, use layouts_push.
Inputs: site (required), layout_id (required), body, title.
media_set_getARead-onlyIdempotent
Get a media_set (gallery) by id (GET /media_sets/{id}). Returns a curated view: media_set id, title, kind, and the ordered assets array with each asset's id, position, title, filename, and type. Read-only. Use this to discover asset ids and current titles before calling media_set_update_asset_titles.
Inputs: site (required), media_set_id (required).
media_set_set_assetsADestructiveIdempotent
Set a media_set's FULL asset list in one call — for filling, reordering or pruning an EXISTING gallery (issue #140 item 5; media_set_update_asset_titles only edits titles of what is already there).
asset_ids is the gallery's new content IN ORDER (an explicit 1-based position is sent per asset — array order alone is NOT enough, Voog re-derives it and can duplicate positions). A reorder does not always take on the first PUT, so the tool reads the gallery back and retries; if the order still has not applied it says so instead of reporting success. Any asset currently in the set but absent from the list is UNLINKED (the asset itself survives in the library; only its membership ends). Because that is easy to do by accident, any call that drops a current asset requires force=true — including a same-length swap. Reordering and adding need no force.
Titles and per-asset link settings are carried over for assets that stay; pass titles to set them for new ones. Upload files first with asset_upload to get ids.
The media_set must already exist: POST /media_sets returns 500, and a freshly created gallery content area has no media_set until Voog makes one. Find the id on the content area's gallery field (NOT media_set) via voog://{site}/articles/{id}/contents, then media_set_get.
Inputs: site (required), asset_ids (required), media_set_id (required), force, +1 more.
media_set_update_asset_titlesAIdempotent
Safely edit one or more asset titles in a media_set (gallery). Pass titles as an object mapping asset id -> new title, e.g. {"24898880": "New alt text"}.
WHY THIS TOOL: PUT /media_sets/{id} is replace-not-merge — any asset omitted from the request body is unlinked from the gallery (same destructive semantics as product variants). Hand-rolling a partial-assets PUT to change one title silently drops every other image (hit live 2026-05-20). This tool does the GET-then-PUT-full-array dance for you: it reads the current media_set, applies only the requested title changes, and PUTs the complete asset array back — preserving every other asset's id, order, title, and link settings.
Every key in titles must be an asset id already present in the media_set; an unknown id is rejected (no silent no-op). Titles may be empty strings (Voog allows clearing a title). Idempotent — re-running with the same titles is a no-op.
Inputs: site (required), titles (required), media_set_id (required).
node_getARead-onlyIdempotent
Get a single node by id, with its full pages array — one entry per language. Use this when preparing a parallel translation: read the node id from one page, then pass node_id to page_create with the second-language details.
Inputs: site (required), node_id (required).
node_moveAIdempotent
Move/reorder a node within the page tree (PUT /nodes/{id}/move). Inputs travel as query-string params per Voog docs. Required: parent_id (current or new parent — pass current to just reorder). Optional: position (1-indexed, Voog default 1).
Inputs: site (required), node_id (required), parent_id (required), position.
node_relocateAIdempotent
Relocate a node to a precise position relative to a sibling, or to the first slot under a new parent (PUT /nodes/{id}/relocate). Body is FLAT. Supply EXACTLY ONE of: before (place this node before the given sibling id), after (place after sibling id), or parent_node_id (move to first position under new parent). Mutually exclusive — handler rejects multiple.
Inputs: site (required), node_id (required), after, before, +1 more.
nodes_listCRead-onlyIdempotent
List all page nodes (id, title, parent_id, position). Each node represents a language-agnostic page identity; its parallel translations are pages sharing the same node.id. Read-only.
Inputs: site (required).
node_updateAIdempotent
Update a node's title (PUT /nodes/{id}). Per Voog docs, only title is documented as updatable. Body is FLAT — no envelope wrapper. For tree restructuring use node_move (parent + position) or node_relocate (positional placement).
Inputs: site (required), title (required), node_id (required).
order_getARead-onlyDestructiveIdempotent
Get a single order by id (GET /admin/api/ecommerce/v1/orders/{id}). Read-only. include_pii=false (default) strips PII via whitelist. include_pii=true REQUIRES force=true alongside (LLM-side PII-exfiltration gate; see SECURITY.md).
Inputs: site (required), order_id (required), force, include_pii.
orders_listARead-onlyDestructiveIdempotent
List ecommerce orders (GET /admin/api/ecommerce/v1/orders). Read-only. Optional filters: status (e.g. 'created', 'cancelled'), payment_status (e.g. 'paid', 'unpaid'), created_after (ISO8601), created_before (ISO8601). include_pii=false (default) strips customer email / name / address / phone / IP via whitelist. include_pii=true REQUIRES force=true alongside — passing include_pii=true without force=true is rejected (LLM-side PII-exfiltration gate; see SECURITY.md). MCP hosts surface force as a destructive-hint approval, which keeps prompt-injected include_pii=true calls behind operator consent.
Inputs: site (required), force, status, include_pii, +3 more.
page_add_contentA
Create a content area + linked text on a page (POST /pages/{id}/contents). Use this on freshly-created pages where /contents returns [] until the admin UI's edit-mode opens the page. name must match the layout's {% content %} tag — default 'body' for unnamed, 'gallery_1' for named. content_type defaults to 'text'; valid values: text, gallery, form, content_partial, buy_button, code.
By default, the tool first GETs /pages/{id}/contents and refuses if a content area with the same name already exists — calling twice with the same name was silently creating duplicates. To edit the existing area, use text_update on its text.id. Pass force=true to skip the pre-check (legitimate use: page templates with multiple areas sharing the same name).
Inputs: site (required), page_id (required), name, force, +1 more.
page_createA
Create a new page. Required: title, slug, language_id. Optional: parent_id (page id, NOT node_id) for subpages, node_id for parallel-translation pages of an existing page in another language, layout_id, content_type ('page'|'link'|'blog'|'product'|...), hidden, image_id, description, keywords, data (custom dict). Multilingual: pass node_id of the first-language page instead of parent_id when creating its translation in another language. Voog binds them as parallels (admin Translate UI works correctly). parent_id and node_id are mutually exclusive.
Inputs: site (required), slug (required), title (required), language_id (required), +10 more.
page_deleteADestructive
Delete a page. IRREVERSIBLE — Voog does not retain deleted pages. Requires force=true; without it the call is rejected to prevent accidental deletion. Run pages_snapshot or site_snapshot first if the page might be needed later.
Inputs: site (required), page_id (required), force.
page_delete_dataADestructive
Delete a single page.data. (DELETE /pages/{id}/data/{key}). IRREVERSIBLE — the key is removed permanently. Requires force=true; without it the call is rejected. Keys starting with 'internal_' are server-protected and rejected client-side.
Inputs: key (required), site (required), page_id (required), force.
page_duplicateA
POST /pages/{id}/duplicate — create a copy of the page (including its content). The new page is hidden by default per Voog convention.
Inputs: site (required), page_id (required).
page_getARead-onlyIdempotent
Get full details of a single page by id (title, path, hidden, layout, language, parent, timestamps, public_url).
Inputs: site (required), page_id (required), include_seo, include_children.
page_set_dataAIdempotent
Set a single page.data. value (PUT /pages/{id}/data/{key}). To delete a key use page_delete_data. Keys starting with 'internal_' are server-protected and rejected client-side.
Inputs: key (required), site (required), value (required), page_id (required).
page_set_layoutAIdempotent
Reassign a page's layout. Reversible — call again with the original layout_id to revert.
Inputs: site (required), page_id (required), layout_id (required).
pages_listARead-onlyIdempotent
List pages on the Voog site (id, path, title, hidden, layout name). All filters are optional; with no filters this returns every page. Read-only.
Inputs: site (required), sort, search, filters, +6 more.
pages_snapshotAIdempotent
Backup all pages + per-page contents to JSON files in output_dir. Creates the directory (and parents) if needed; overwrites existing pages.json. Lighter than site_snapshot — use this when you only need page structure and contents.
Inputs: site (required), output_dir (required).
page_updateAIdempotent
Update arbitrary fields on a page. At least one of title, slug, layout_id, image_id, content_type, parent_id, description, keywords, data must be supplied. For just hidden / layout id, prefer the dedicated page_set_hidden / page_set_layout — they're more explicit in tool listings.
data field is sent via PATCH (merge semantics) — only the keys you pass are touched. To delete a key, use page_delete_data. Calls without data route via PUT (full-field replace) as before.
Inputs: site (required), page_id (required), data, slug, +7 more.
product_createA
Create a new product (POST /products on ecommerce v1). Required: name, slug, price (Voog rejects POST without these). Three argument shapes (combinable):
attributes: flat object of root-level product fields. Allowed keys: name, slug, price, sale_price, status, description, sku, stock, reserved_quantity, category_ids, image_id, asset_ids, physical_properties, uses_variants, variant_types. Note: POST usesasset_ids(list of int), unlike PUT which usesassets:[{id}].translations: nested {field: {lang: value}} for translatable fields (name, slug, description). Each field-language pair must be non-empty.fields(legacy v1.1 shape): flat 'name-et', 'slug-en' keys — auto-routed to translations. Validates status enum {'draft', 'live'} and rejects unknown attribute keys. The POST result includes the newly assigned product id. Price fields (price,sale_price,effective_price) are net or gross depending onsettings.price_entry_mode. Callecommerce_settings_getto determine the mode. For customer-facing pricing always useeffective_priceand verify the mode first.
Inputs: site (required), fields, attributes, translations.
product_deleteADestructive
Delete a product (DELETE /admin/api/ecommerce/v1/products/{id}). IRREVERSIBLE — Voog does not retain deleted products. Requires force=true; without it the call is rejected to prevent accidental deletion. Run products_list or product_get first to confirm the id, and site_snapshot if the product might be needed later.
Inputs: site (required), product_id (required), force.
product_duplicateA
Duplicate a product (POST /admin/api/ecommerce/v1/products/{id}/duplicate). The new product inherits status='draft' per Voog default — call product_update(status='live') after editing if the duplicate should be public. Returns the new product's full payload; summary surfaces new_id and new title for easy chaining into product_update.
Inputs: site (required), product_id (required).
product_getARead-onlyIdempotent
Get full product details by id, including the per-variant variants array (with stock, reserved_quantity, variant_attributes_text), variant_types definitions, and translations (?include=variants,variant_types,translations). Read-only. Price fields (price, sale_price, effective_price) are net or gross depending on settings.price_entry_mode. Call ecommerce_settings_get to determine the mode. For customer-facing pricing always use effective_price and verify the mode first.
Inputs: site (required), product_id (required).
products_bulk_actionADestructive
Apply the same actions to many products in one request (PUT /admin/api/ecommerce/v1/products). This is NOT per-row arbitrary updates — every product in target_ids receives every action in actions. For one-off varied edits use product_update.
Request shape:
actions: list of {target_field, action, value, source_field?}. Allowed action verbs: set, increase_by_fixed, decrease_by_fixed, increase_by_percent, decrease_by_percent, round, round_upwards, round_downwards, merge, remove.
target_ids: list of integer product ids, OR the literal string 'all' to apply to every product on the site.
Response: {counters: {processed, failed}, processed_ids, failed_ids}. Duplicate ids in target_ids are collapsed server-side. No empirical batch-size cap observed up to 1001 ids (Stella, 2026-05-27); send what you need.
Inputs: site (required), actions (required), target_ids (required), force.
product_set_imagesADestructive
Replace a product's images. files is a list of absolute paths to local image files (jpg, jpeg, png, webp, gif). First file becomes the main image (image_id); rest are gallery images. Runs Voog's 3-step asset upload protocol per file (POST /assets → PUT upload_url → PUT confirm), then PUTs {image_id, assets:[{id:n}]} to /products/{id}. Refuses to replace existing images unless force=true. If any single upload fails, the product is NOT updated — successful uploads are surfaced in uploaded for manual re-linking.
Gallery ORDER is applied by re-reading it back and repeating the PUT: Voog lands the requested order only about half the time on the first write (200 either way). If the order still has not taken, the call comes back as an ERROR carrying order_verified: false and the stored_asset_ids Voog actually holds. In that case every image IS linked and only the sequence is wrong — do NOT re-run this tool to fix it, that re-uploads every file as a new asset. Re-send just the order via voog_ecommerce_api_call PUT /products/{id}.
Inputs: site (required), files (required), product_id (required), force.
products_listARead-onlyIdempotent
List all ecommerce products on the Voog site (simplified: id, name, slug, sku, status, in_stock, on_sale, price, effective_price, stock, reserved_quantity, uses_variants, variants_count, translations, created_at, updated_at). Read-only. Same shape as the voog://products resource — consistent across the tools and resources surfaces. For per-variant stock on a variant-bearing product, follow up with product_get. Pass category_id to filter to products in that category (maps to q.category.id.$eq). Price fields (price, sale_price, effective_price) are net or gross depending on settings.price_entry_mode. Call ecommerce_settings_get to determine the mode. For customer-facing pricing always use effective_price and verify the mode first.
Inputs: site (required), category_id.
product_updateAIdempotent
Update a product. Three argument shapes (combinable):
attributes: flat object of root-level product fields (status, price, sale_price, sku, stock, description, category_ids, image_id, asset_ids, physical_properties, uses_variants, variant_types, variants, variant_attributes). Note: asset_ids accepted; on PUT it's translated to theassets:[{id}]envelope Voog requires (sending raw asset_ids on PUT silently keeps only the hero image).variantswithoutvariant_attributeswipes ALL variants (even ones withid); pass both together, or setforce=trueto acknowledge.translations: nested {field: {lang: value}} for translatable fields (name, slug, description). Each field-language pair must be non-empty. Cannot overlap with attributes (e.g. attributes.description + translations.description in the same call is rejected).fields(legacy v1.1 shape): flat 'name-et', 'slug-en' keys — auto-routed to translations. Kept for back-compat. At least one of attributes/translations/fields must be non-empty. Validates status enum {'draft', 'live'} and rejects unknown attribute keys (catches typos before they round-trip to a 422). Reversible by calling with previous values; idempotent (same input twice = same end state). Price fields (price,sale_price,effective_price) are net or gross depending onsettings.price_entry_mode. Callecommerce_settings_getto determine the mode. For customer-facing pricing always useeffective_priceand verify the mode first.
Inputs: site (required), product_id (required), force, fields, +2 more.
redirect_addA
Add a redirect rule. source/destination are paths (e.g. /old → /new). redirect_type defaults to 301; allowed: 301, 302, 307, 410. Set regexp=true to treat source as a regex pattern. Set active=false to create the rule disabled. For 410 (Gone), destination is semantically meaningless — Voog still stores it but never redirects there; pass any value (e.g. source path).
Inputs: site (required), source (required), destination (required), active, +2 more.
redirect_deleteADestructive
Delete a redirect rule. Refuses without force=true. Reversible only by re-creating the rule via redirect_add.
Inputs: site (required), redirect_id (required), force.
redirects_listARead-onlyIdempotent
List all redirect rules on the Voog site (id, source, destination, redirect_type, active). Read-only.
Inputs: site (required).
redirect_updateAIdempotent
Update an existing redirect rule. At least one of source, destination, redirect_type, active, regexp must be supplied. redirect_type ∈ {301, 302, 307, 410}. Reversible by calling again with previous values.
Voog's PUT /redirect_rules/{id} is full-replace — missing fields are coerced to defaults (e.g. active flips to True). The tool GETs the current rule, merges your updates, then PUTs the full envelope, so unspecified fields are preserved.
Inputs: site (required), redirect_id (required), active, regexp, +3 more.
shipping_methods_listARead-onlyIdempotent
List all shipping methods (GET /admin/api/ecommerce/v1/shipping_methods). Read-only. Response includes the full options[] nested list for parcel-machine carriers (Omniva, SmartPost, …) — expect multi-KB payloads per method.
Inputs: site (required).
site_cloneADestructiveIdempotent
Copy one Voog site's content onto another (layouts, layout assets, media, site settings, pages, content areas, articles). site is the SOURCE (read-only); target_site is OVERWRITTEN. Both must be names from voog_list_sites.
DRY RUN BY DEFAULT — without force=true nothing is written and the result reports what would happen. Run phase 'plan' first: it returns source object counts, the target's remaining asset quota, and the specific things Voog will not let a clone reproduce.
RESUMABLE. state_dir holds source-id -> target-id maps that make every phase re-runnable: a run stopped by an asset quota or a transient error is continued by calling again with the same state_dir, and nothing is created twice. Use a FRESH state_dir per source/target pair — the tool refuses a directory that belongs to a different pair.
Phases run in dependency order regardless of the order you list them (a page cannot reference a layout that does not exist yet): layouts, layout_assets, assets, site, pages, contents, articles, cleanup, verify.
NOT COPIED: ecommerce (products, categories, discounts, cart rules, orders), elements, redirects, webhooks, forms, comments. Languages are matched by code, never created.
Voog limits the clone cannot work around: created_at is not settable — PUT returns 200 and the value resets to now, so every copied article and page carries the clone date. published_at is not settable, for the same reason. Duplicate article paths cannot be reproduced — Voog now auto-suffixes a colliding path with -1. Legacy sites that hold two articles on one path will have the twin land on a different URL. Asset storage is capped per plan. The clone checks the target's remaining quota before uploading and stops cleanly rather than hitting 422 quota_exceeded partway through.
Inputs: site (required), state_dir (required), target_site (required), force, +3 more.
site_delete_dataADestructive
Delete site.data. (DELETE /site/data/{key}). IRREVERSIBLE — the key is removed from site.data permanently. Requires force=true; without it the call is rejected. 'internal_*' keys are server-protected and refused client-side.
Inputs: key (required), site (required), force.
site_getARead-onlyIdempotent
Get the site singleton (title, code, data, languages, ...). Read-only.
Inputs: site (required).
site_set_dataAIdempotent
Set site.data. to a value (PUT /site/data/{key}). To delete a key use site_delete_data. 'internal_*' keys are server-protected and refused client-side.
Inputs: key (required), site (required), value (required).
site_snapshotAIdempotent
Comprehensive read-only backup of every mutable Voog resource: pages, articles, elements, layouts, layout_assets, languages, redirect_rules, nodes, texts, content_partials, tags, forms, media_sets, assets, webhooks, site, me, products (with translations + variant_types), per-page contents, per-article details, per-product details, and rendered HTML samples for VoogStyle capture. By default REFUSES to overwrite an existing directory — pick a fresh location. Pass overwrite=true to write into an existing directory (automation/cron use case); files from a prior snapshot may persist alongside new files if the underlying Voog state has shrunk. REQUIRED pre-flight before any risky operation: layout rename, mass push, layout swap, VoogStyle template push, page_delete. Writes _meta.json manifest to output_dir documenting voog-mcp version, attempted/succeeded/skipped/failed endpoints, request_count, duration_seconds, and (if the snapshot aborted mid-run) aborted_reason. Restore tooling reads this to refuse partial snapshots.
Inputs: site (required), output_dir (required), overwrite.
site_updateAIdempotent
Update site singleton. attributes: flat root-level fields. site.code is immutable once set — passing it raises an error. For per-key data, use site_set_data.
Inputs: site (required), attributes (required).
tag_deleteADestructive
Remove a tag (DELETE /tags/{id}). Voog returns 204. Requires force=true; without it the call is rejected. Run tags_list / tag_get first to confirm the id. Deletion removes the tag from all articles currently tagged with it.
Inputs: site (required), tag_id (required), force.
tag_getARead-onlyIdempotent
Get a single tag by id (GET /tags/{id}). Returns the full Voog tag shape. Use tags_list to discover ids. Read-only.
Inputs: site (required), tag_id (required).
tags_listARead-onlyIdempotent
List all tags on the site (GET /tags). Returns the full Voog tag shape (id, name, slug, taggings_count, created_at). Useful for categorising articles / suggesting related content. Read-only.
Inputs: site (required).
text_getARead-onlyIdempotent
Get a text resource by id (GET /texts/{id}). Texts hold the body of text-type content areas. Find the text_id via voog://{site}/pages/{page_id}/contents → text.id. Read-only.
Inputs: site (required), text_id (required).
text_updateAIdempotent
Update a text body (PUT /texts/{id} {body}). body is the raw HTML rendered into the page where the matching {% content %} Liquid tag lives. Reversible by calling again with the previous body.
Inputs: body (required), site (required), text_id (required).
voog_admin_api_callADestructive
Generic Admin API WRITE passthrough. Forward an HTTP request to https:///admin/api using the configured site's API token. method ∈ {POST, PUT, PATCH, DELETE}; body is JSON-serialised on POST/PUT/PATCH. Use this when no typed tool covers the endpoint (orders, forms, tickets, elements, tags, media_sets, webhooks, etc.). Conservative annotations (destructiveHint=true) — Claude will confirm before calling.
For READS use voog_admin_api_read — this tool no longer accepts method='GET' (removed in v1.5).
⚠️ PUT /media_sets/{id} is replace-not-merge: the assets array you send REPLACES the gallery — any asset omitted is unlinked. To edit asset titles safely use the typed media_set_update_asset_titles tool (GET-then-PUT-full-array); only hand-roll a media_sets PUT when you have the COMPLETE asset list. Same foot-gun as product variants.
⚠️ Ordered assets arrays (/media_sets/{id}, ecommerce /products/{id}) are applied only PARTIALLY by roughly half of single PUTs — 200 either way. Write, read the order back, and repeat if it disagrees, or use the typed tools (media_set_set_assets, product_set_images) which do that.
Inputs: path (required), site (required), method (required), body, +1 more.
voog_admin_api_readARead-onlyIdempotent
Read-only Admin API passthrough. Forward a GET request to https:///admin/api. Use this when no typed read tool covers the endpoint (forms, tickets, tags, media_sets, etc.). Read-only — MCP hosts may skip the destructive-action confirmation prompt.
Inputs: path (required), site (required), params.
voog_ecommerce_api_callADestructive
Generic Ecommerce v1 API WRITE passthrough. Forward an HTTP request to https:///admin/api/ecommerce/v1. Same shape as voog_admin_api_call, different base URL. Supports ?include=... and ?language_code=... per Voog ecommerce conventions. Use for orders, carts, discounts, shipping_methods, gateways, cart_fields, cart_rules, delivery_provider_configs, templates, bulk product actions, products imports, etc.
For READS use voog_ecommerce_api_read — this tool no longer accepts method='GET' (removed in v1.5).
PUT gotchas (Voog ecommerce v1 quirks — typed tools handle these for you, passthrough does not):
On
PUT /products/{id}, asset references must use the{"assets": [{"id": N}, ...]}shape. Sending the POST-shapeasset_ids: [N, ...]on PUT silently drops all but the hero image. Preferproduct_set_imagesfor image attachment; it handles the shape internally. The array ORDER is also applied only partially by about half of single PUTs (200 either way) — readasset_idsback and repeat the PUT until it matches.On
PUT /products/{id}, thevariantsarray is destructive: Voog deletes every variant not present in the array — even variants with a stableid. Always includevariant_attributesalongsidevariants, or send the full existing variant list. Preferproduct_update; it requires explicitforce=trueto bypass this guard.On
PUTto endpoints that accept adatahash (e.g./pages/{id},/articles/{id},/site), thedatafield REPLACES the entire hash — unspecified keys are dropped. Voog supportsPATCH(merge semantics) on these routes; use method='PATCH' here, or prefer the per-key toolspage_set_data/article_set_data/site_set_datawhich route through PATCH automatically (typed wrappers handle this as of v1.4).
Inputs: path (required), site (required), method (required), body, +1 more.
voog_ecommerce_api_readARead-onlyIdempotent
Read-only Ecommerce v1 API passthrough. Forward a GET request to https:///admin/api/ecommerce/v1. Supports ?include=... and ?language_code=.... Read-only — MCP hosts may skip the destructive-action confirmation prompt.
Inputs: path (required), site (required), params.
voog_list_my_sitesARead-onlyIdempotent
Probe GET /admin/api/me/sites to discover account metadata for a Voog token. Returns [{name, primary_domain, feature_flags}]. R6 NOTE: Voog API tokens are site-scoped, so this tool returns metadata for the ONE site the token belongs to — the array is always length 1. To enumerate multiple sites, the operator must supply each token separately. Token sourcing: prefer token_env= (env var name; secret stays in the environment). token= is a fallback for ad-hoc use BUT the token will appear in transcripts and host logs — avoid in production. host defaults to 'www.voog.com' (the canonical admin endpoint); override for tenants on private domains. Read-only.
Inputs: host, token, token_env.
voog_list_sitesA
List all sites configured in the global voog.json. Returns [{name, host}, ...]. Call this first to see what sites are available before invoking any other voog_* tool. If a site you just registered is missing, call voog_reload_config.
Inputs: no named parameters.
voog_reload_configAIdempotent
Re-read the global voog.json (and its .env) and drop cached clients, so sites registered AFTER this server started become usable without restarting the MCP host. Returns the current site list plus what was added/removed.
Use when 'unknown site' comes back for a site you just added, or after rotating a token in .env. Running tool calls are unaffected — they finish against the client they already hold. A malformed config leaves the current one in place.
Inputs: no named parameters.
voog_searchARead-onlyIdempotent
Full-text search across the site's published content (GET /admin/api/search). Returns hits across pages, articles, elements, and products. Use scope to narrow the search to one kind. Indexing is hourly and covers PUBLIC content only — fresh edits and draft pages will not appear here. For draft / freshly-edited discovery, use pages_list(filters=...), articles_list(...), or text_get(...) instead. If indexing is disabled on the tenant, this tool detects that via a sentinel query and explains rather than silently returning zero. Read-only.
Inputs: q (required), site (required), scope, per_page, +1 more.
webhook_createA
Create a webhook (POST /webhooks). Body is FLAT — no envelope wrapper. Required: target, event, url. Optional: enabled (default true), target_id, source (default 'api'), description. Voog target+event matrix: target='ticket' → create/update/delete; target='form' → submit; target='order' → create/update/delete/paid/cancelled/shipped/payment_failed. Voog returns 422 for invalid combinations.
Inputs: url (required), site (required), event (required), target (required), +4 more.
webhook_deleteADestructive
Remove a webhook (DELETE /webhooks/{id}). Voog returns 204. Requires force=true; without it the call is rejected. Run webhooks_list first to confirm the id.
Inputs: site (required), webhook_id (required), force.
webhooks_listARead-onlyIdempotent
List all webhooks on the site (id, enabled, target, event, url, target_id, description). Use the returned id for webhook_update / webhook_delete. Read-only.
Inputs: site (required).
webhook_updateAIdempotent
Update a webhook (PUT /webhooks/{id}). Partial — supply ONLY the fields to change. Body is FLAT. At least one updatable field besides webhook_id is required.
Inputs: site (required), webhook_id (required), url, event, +5 more.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 111 tools
Every tool targets a distinct resource and action. With 111 tools, the descriptions are detailed enough that there is no real overlap; even similar operations like node_move vs node_relocate are clearly separated.
Most tools follow a consistent resource_verb pattern (e.g., article_get, articles_list). Minor deviations include the voog_ prefix for administrative tools, layout_rename alongside layout_update, and asset_replace being a misnomer for a rename.
At 111 tools, this server is far beyond the typical well-scoped range. While Voog's API is broad, many tools are redundant (page_set_hidden vs page_update, layout_rename vs layout_update), and the huge number can overwhelm an agent.
The tool set covers most of Voog's resources with full CRUD for pages, products, layouts, nodes, redirects, webhooks, etc. However, article_delete, language_update, and content_partial creation are missing, though passthrough tools can fill these gaps.
Maintenance
Related MCP Connectors
Remote MCP server for supportsheep: run AI interviews and manage support content for your blog.
Remote MCP server for supportsheep: run AI interviews and manage support content for your blog.
An MCP server that let you interact with Cycloid.io Internal Development Portal and Platform
MCP server for Shoprocket — orders, products, customers, store stats and discount codes.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceAn MCP Server that enables interaction with VTEX's Headless Content Management System API, allowing users to manage content through natural language commands.-
- AlicenseNot gradedqualityCmaintenanceMCP server for Freelo project management API, enabling task and project management through natural language.63 npm10MIT
- AlicenseNot gradedqualityDmaintenanceMCP server for integrating AI assistants with VkusVill services, allowing natural language access to VkusVill data and operations.1MIT
- AlicenseCqualityDmaintenanceMCP server for Shopify Admin API. Enables product, order, customer, and inventory management via natural language.148 npm1MIT