Tools reference
111 tools the shop MCP server exposes. Generated from the live server registration.
Meta & GraphQL4
get_contextReport the resolved operating context: GraphQL endpoint, organization_id, store_id, store slug/name, the theme_id you are editing, WHETHER THAT THEME IS THE ONE THE STOREFRONT SERVES, and commerce_enabled. High-level tools auto-fill these; use this to learn the values for raw GraphQL.
graphql_mutationRun a raw GraphQL MUTATION against the store. LAST-RESORT escape hatch ONLY for writes with NO dedicated tool. For pages use create_page/update_page/update_page_sections/publish_page; theme files use upsert_theme_file; content use create_content_type/create_content_record; products use create_product; menus use create_menu; publish uses publish_draft_theme. Those apply required contracts + defaults that a raw mutation skips. Remember store/org-scoped mutations need organization_id/store_id — call get_context to fetch them.
| Argument | Type | Description |
|---|---|---|
| query* | string | A GraphQL document. Use graphql_query for queries and graphql_mutation for mutations. |
| variables | object | Variables object for the document. |
graphql_queryRun a raw GraphQL QUERY against the store. Escape hatch for anything not covered by a high-level tool. The API key's org scopes apply. Returns the `data` object.
| Argument | Type | Description |
|---|---|---|
| query* | string | A GraphQL document. Use graphql_query for queries and graphql_mutation for mutations. |
| variables | object | Variables object for the document. |
introspect_schemaDiscover the GraphQL schema. With type_name, returns that type's fields and their argument/field types (use 'Query' or 'Mutation' to list all operations, or an input type to learn its shape). Without it, lists all type names.
| Argument | Type | Description |
|---|---|---|
| type_name | string | Inspect a single type's fields/inputs (e.g. CreateProductInput, Mutation, Query). Omit to list all type names. |
Products & catalog1
list_productsList the shop's products (paged, searched and sorted in SQL). Use it to check what already exists before creating more, and to read back ids for an update. Search is diacritic-insensitive.
| Argument | Type | Description |
|---|---|---|
| category | string | |
| page | integer | 1-based page (default 1). |
| per_page | integer | Page size (default 20). |
| search | string | Diacritic-insensitive match on the title/SKU/category: an unaccented query still finds accented names. |
| sort | string | newest | price | -price | title. Default is the merchant's manual order. |
| status | string | draft | published. |
Collections1
upsert_collectionCreate or update a COLLECTION — a group of products defined by CONDITIONS, not by a list of ids, published at /collections/<slug>. 'everything under 500,000' stays true by itself as the merchant adds products, which a hand-picked list cannot. Conditions use the platform's filter grammar: match all|any over rules of field/op/value.
| Argument | Type | Description |
|---|---|---|
| description | string | |
| id | string | Collection id — set to UPDATE, omit to CREATE. |
| image | string | Cover image URL, auto re-hosted. |
| match | string | all (every condition must hold — the default) or any (at least one). |
| rules | nullarray | The CONDITIONS that decide membership. A collection stores no product ids: a product added later joins by itself if it matches. NO rules = every published product, which is rarely what a named collection means. |
| slug | string | URL handle; the page is /collections/<slug>. Derived from the title if omitted. |
| sort_key | string | How its products are ordered: newest | price | -price | title, or omit for the merchant's manual order. |
| status | string | draft (default) or published. |
| title* | string | REQUIRED — the collection name in the store's language, e.g. 'On sale'. |
Content8
create_content_recordCreate (omit id) or update (set id) a record of a content type. Field values go in `values` keyed by field code.
| Argument | Type | Description |
|---|---|---|
| id | string | Record id — set to UPDATE an existing record; omit to CREATE. |
| slug | string | URL slug; auto-derived + de-duplicated if omitted. |
| status | string | draft (default) or published. |
| title* | string | REQUIRED — the record's display NAME: one short line of plain text (the product/service/article name, under 60 characters, no HTML, not a sentence). It is what every list, card and detail page shows as the heading, and it cannot be derived from your other values — a call without it is rejected. |
| type_code* | string | Content type code (required). |
| values | object | Field values keyed by field code, e.g. {"price":"150000","bedrooms":3}. Gallery/image fields take URLs. |
create_content_typeDefine a custom content type (e.g. houses, menu_items, projects) with typed fields. EAV-backed, no new tables. Records render on the storefront via the `content` Liquid Drop and the url_pattern router.
| Argument | Type | Description |
|---|---|---|
| code | string | Machine code (lowercase_underscore); derived from label if omitted. |
| embedded | boolean | TRUE for SUPPORTING content that renders ONLY inside sections (FAQ items, testimonials/reviews, logo strips, stats, process steps you insist on storing as data): NO public list/detail routes, no templates, no nav entry — records reach the storefront through the content Drop in your sections. Leave false for DESTINATION entities that deserve their own pages (products, projects, rooms, courses…). Decide from the brief: 'would a visitor ever open /x or /x/<slug> directly?' |
| fields* | nullarray | Field definitions (required, at least one). |
| icon | string | |
| is_form | boolean | TRUE for a form/lead-capture type (contact, booking, newsletter): submissions become the merchant's PRIVATE inbox — public reads are blocked, and NO public list/detail routes or templates are scaffolded. Any section form that fetch-POSTs to /api/storefront/forms/submit needs its type created with is_form:true or the submit returns disabled. |
| label* | string | Type display name, e.g. 'Houses' (required). |
| role | string | WHAT THIS TYPE IS FOR — declare it, do not leave it to be guessed: 'display' = public content the site lists (products, articles, projects); 'order' = THE checkout inbox, whose submissions carry the cart (create exactly ONE per store); 'contact' = contact/booking/consultation submissions; 'newsletter' = an email-only subscription list. Declaring order/contact/newsletter implies is_form. The checkout page, the duplicate-inbox cleanup and the admin all read this, so an undeclared type leaves them inferring from field names. |
| singular | string | Singular form for Liquid access, e.g. 'house'. |
| template | string | Detail template name; defaults to code. |
| url_pattern | string | Storefront route with :slug, e.g. '/properties/:slug'. Defaults to /<code>/:slug. |
delete_content_recordDelete one record. The type, its fields and its sections stay — only this entry goes.
| Argument | Type | Description |
|---|---|---|
| id* | string | Record id (required) — from list_content_records. |
| type_code* | string | Content type code (required). |
delete_content_typeDelete a content type and every record filed under it. Requires confirm:true. Use it to clear a type an earlier build left behind — a type whose records nothing links to is invisible content, and leaving it means the site carries data no visitor can reach.
| Argument | Type | Description |
|---|---|---|
| code* | string | Content type code (required). |
| confirm* | boolean | Must be true. Deleting a type deletes EVERY record filed under it and leaves its sections looping nothing — set this only when you mean that. |
list_content_recordsList records of a content type with optional search, facet filters, sort, and pagination. Filtering/search/sort run server-side over the full set, then the page is returned. Mirrors the storefront content-list (which a theme drives via ?q=/?<field>=/?sort=/?per_page= URL params). Returns { data, total (after filter), total_all, page, per_page }.
| Argument | Type | Description |
|---|---|---|
| fields | nullarray | Keep ONLY these value fields, e.g. ["price","category"]. id/title/slug/status always come back. Omit for every field — which on a type with richtext is how 64 records became 1.1 MB. |
| filters | object | Facet filter: field code -> allowed values, e.g. {"category":["Fashion"],"style":["Modern","Minimal"]}. Exact (case-insensitive); array fields (multiselect/gallery) match if ANY value is present. AND across fields, OR within a field. |
| page | integer | 1-based page (default 1). |
| per_page | integer | Page size (default 20). |
| published_only | boolean | |
| search | string | Case-insensitive substring match on the title + text/richtext/url field values. |
| sort | string | Sort key: a field code, 'title', 'newest', or 'oldest'; prefix '-' for descending (e.g. '-price'). Numeric fields sort numerically. |
| type_code* | string | Content type code (required). |
list_content_typesList the org's content types and their fields.
scaffold_content_routesGive a content type the LIST surface that makes its route render: a listing section built from the type's real fields plus the template that mounts it. A type with records and no template shows NOTHING — its route answers, and the page is empty. Idempotent: it never overwrites a section a design pass already wrote.
| Argument | Type | Description |
|---|---|---|
| type_code* | string | Content type code (required). Its fields are read from the platform — you do not describe them here. |
update_content_typeRename a content type or change where its detail pages live (label, singular, url_pattern, template, icon). Only what you send changes. ⚠️ FIELDS are not edited here: the type's columns are decided once, from the design, and re-deciding them downstream is how a site ends up with two names for one thing.
| Argument | Type | Description |
|---|---|---|
| code* | string | Content type code to update (required). The code itself never changes — it is the type's identity and its route base. |
| icon | string | |
| label | string | New display name. |
| singular | string | New singular form for Liquid access. |
| template | string | Detail template name. |
| url_pattern | string | New detail route with :slug, e.g. '/properties/:slug'. Changing this changes every record's address — check_links afterwards. |
Theme & sections14
build_sectionGenerate a customizer-editable Liquid section (body + complete {% schema %}) from a spec and write it to sections/<handle>.liquid. Always ships a preset so it shows in the 'add section' picker. After this, call set_section_settings to place it on a page template.
| Argument | Type | Description |
|---|---|---|
| overwrite | boolean | Allow overwriting an existing section file with the same handle. |
| spec* | object | Section definition object: name (required), category, settings[] ({type,id,label,default,...}), blocks[] ({type,name,settings[],limit,blocks[] — blocks NEST recursively for item-within-item trees, max_blocks caps children}), presets[], and optional Liquid body. Settings use section.settings.<id>; reference the global palette via CSS vars like var(--accent) / var(--font-display). |
| theme_id | string | Theme id to write to. Defaults to the store's active theme. |
clear_storefront_cachePurge the storefront edge/Redis cache so theme/product edits show immediately. (Requires a key with settings/full-access scope.)
create_theme_previewCreate a short-lived preview token to view a theme (e.g. a draft) on the storefront without publishing.
| Argument | Type | Description |
|---|---|---|
| theme_id | string | |
| ttl_seconds | integer | Token lifetime; default 3600. |
customize_themeOpen a theme for editing — returns or lazily creates an unpublished draft clone you can safely edit before publishing.
| Argument | Type | Description |
|---|---|---|
| theme_id | string | Defaults to the store's active theme. |
get_theme_fileRead a single theme file's content by path.
| Argument | Type | Description |
|---|---|---|
| include_inherited | boolean | |
| path* | string | e.g. sections/header.liquid or templates/index.json (required). |
| theme_id | string |
list_liquid_objectsList the Liquid objects, per-template context variables, the dynamic `content` Drop, and custom filters the storefront exposes to theme sections. Use this BEFORE writing a section so you reference real variables (e.g. product.url_key, collection.products, content.<type>) instead of guessing.
list_theme_filesList theme files (path + content_type + source), optionally filtered by path prefix.
| Argument | Type | Description |
|---|---|---|
| include_inherited | boolean | Include files inherited from the parent theme. |
| path_prefix | string | Filter, e.g. 'sections/' or 'templates/'. |
| theme_id | string |
publish_draft_themePublish the theme: what you have been editing becomes what visitors get. A draft (unpublished) theme is also promoted to live (role=main). Publishing the theme you are already on is NOT a no-op — every write lands on the theme's DRAFT surface, which the storefront does not serve until you publish.
| Argument | Type | Description |
|---|---|---|
| theme_id | string | Defaults to the store's active theme. |
restore_theme_versionRestore the theme to a snapshot taken by snapshot_theme: every file AND settings_data revert to exactly that checkpoint (files added since are deleted).
| Argument | Type | Description |
|---|---|---|
| version_id* | string | The theme_versions id returned by snapshot_theme (required). |
set_global_settingsMerge global theme palette/typography/spacing into settings_data (CSS variables). Reads current settings_data, merges the given top-level keys (never clobbering the nested `sections` map), and writes it back.
| Argument | Type | Description |
|---|---|---|
| settings* | object | settings_data keys to MERGE (others preserved), e.g. {"color_accent":"#2d4a3e","color_accent_2":"#c0532b","color_paper":"#faf6ec","font_heading":"Cormorant Garamond","font_body":"Inter","space_section":80,"radius":12}. These map to CSS vars (color_accent->--accent, color_accent_2->--accent-2 (the OPTIONAL secondary brand hue for bg-accent-2/text-accent-2), font_heading->--font-display, space_section->--section-y). |
| theme_id | string | Defaults to the store's active theme. |
set_section_settingsAdd or update a section instance on a page template (templates/<template>.json), setting its per-section settings/blocks and order. Reads the current template (inherited if the store has none), merges, and writes it back. ⚠️ CHROME sections (web-header / web-footer / announcement-bar) are LAYOUT-rendered, not template-placed: pass template:"layout" with their type and this writes the instance into the theme's settings_data.sections map — this is THE way to seed the header nav menu items (blocks: link + nested dropdown entries with real /pages/<slug> urls).
| Argument | Type | Description |
|---|---|---|
| blocks | nullarray | Block instances: [{"type":"feature","settings":{...}}]. |
| position | nullinteger | 0-based insert position in page order; appended if omitted. |
| section_id* | string | Unique instance id for the section on this page, e.g. 'hero' or 'features-1'. |
| settings | object | Per-section setting values (section.settings.*). |
| template* | string | Page template without extension: index, product, collection, page, blog, cart, etc. |
| theme_id | string | |
| type* | string | Section type = the section file handle (sections/<type>.liquid). |
snapshot_themeCapture an immutable checkpoint of the theme (every file + settings_data) into theme_versions. Returns the version id to restore later.
| Argument | Type | Description |
|---|---|---|
| label | string | Optional label shown in the version history. Default 'AI checkpoint'. |
| theme_id | string | Defaults to the store's active theme. |
update_page_sectionsAdd or update ONE section on a CMS page, INCREMENTALLY (store_pages.sections_data) — the page-record twin of set_section_settings. Reads the page's current sections, merges this one in (settings per-field: keys you don't pass are preserved; blocks replaced only when given), updates the render order, validates, and writes it back (auto-republishing a live page). Use it to build a CMS page SECTION-BY-SECTION so the user watches it fill in: create_page (empty) FIRST, then call this once per section, top to bottom — instead of dumping the whole page at once. Returns the page's storefront `path` so the preview follows to it.
| Argument | Type | Description |
|---|---|---|
| blocks | nullarray | Block instances: [{"type":"feature","settings":{...}}]. Replaces this section's blocks when given; preserved when omitted. |
| id* | string | Page id (required) — find it with list_pages. Create the page first (create_page) so it exists. |
| position | nullinteger | 0-based insert position in the page's section order; appended if omitted. |
| section_id* | string | Unique instance id for the section on this page, e.g. 'hero' or 'features-1' (required). |
| settings | object | Per-section setting values (section.settings.*). MERGED into the existing instance — settings you don't pass are preserved. |
| type* | string | Section type = the section file handle (sections/<type>.liquid). Build it first with build_section (required). |
upsert_theme_fileCreate or replace a theme file. Writes are validated against the platform design contract and REJECTED on violation: layout/theme.liquid must load assets/theme.js, render content_for_layout/content_for_header, AND carry the <style id="theme-settings-vars"> block (the customizer Theme-settings panel's canonical color_*/font_* keys only apply through it); sections/*.liquid need one complete {% schema %} whose settings include the canonical Style group (visible, bg_color, text_color, heading_color, padding_top/bottom, border_color/width/radius) and whose ids are referenced in the body; templates/*.json must be well-formed and only place existing section types. Error messages include the exact fix.
| Argument | Type | Description |
|---|---|---|
| content* | string | Full file content (required). |
| content_type | string | liquid|json|css|js|html|text. Inferred from extension if omitted. |
| path* | string | layout/|templates/|sections/|snippets/|assets/|config/|locales/ path (required). |
| theme_id | string |
Pages, blog & menus14
create_blog_postPublish an article on the store's own blog (posts, categories, tags, authors, permalinks and archives are a PLATFORM surface — never build a content type for articles). Give it the whole body as HTML; the platform mints the slug, the permalink and the taxonomy from the names you pass. Use update_blog_post to change one afterwards.
| Argument | Type | Description |
|---|---|---|
| author_name | string | The writer's display name. The author row is created when it does not exist; give it a bio later with upsert_blog_author. |
| category_names | nullarray | Categories BY NAME — the platform mints the slug and creates the term when missing. Supplying this REPLACES the post's categories. A category classifies; a tag refines: never send the same word as both. |
| content | string | The article BODY as HTML — the whole piece, printed as one block by the post template. Use h2/h3, p, lists, tables, blockquote, pre/code. No <h1> (the template prints the title), no inline styles or classes, no Liquid. Images inside are re-hosted automatically. |
| cover_image | string | Cover image URL (auto re-hosted; never hotlink). Omit when the body already opens with its own figure. |
| excerpt | string | One or two sentences shown under the title and in every card. Plain text. |
| id | string | Post id — set to UPDATE, omit to CREATE. An update only touches the fields you send; the rest are kept. |
| meta_description | string | SEO meta description; the excerpt is used when omitted. |
| meta_title | string | SEO <title> for this post; the headline is used when omitted. |
| og_image | string | Share image URL (auto re-hosted); the cover is used when omitted. |
| published_at | string | RFC-3339 timestamp. Defaults to now on publish. |
| slug | string | URL slug; derived from the title and de-duplicated when omitted. The permalink is built by the store's blog settings — never paste a prefix here. |
| status | string | draft (default) or published. A published post dated in the future is scheduled: the storefront shows it when that time arrives. |
| tag_names | nullarray | Tags BY NAME (2-4 of the piece's own topic terms). Supplying this REPLACES the post's tags. Never repeat a category name here. |
| title | string | The post's headline, plain text. Required when creating. |
create_pageCreate a CMS page (default type 'custom'). store_id is auto-filled. Pass `sections` (page-builder document — THE standard for designed pages, visually editable afterwards) or `body` (prose HTML, text-only pages). Without either the page is BLANK. Publish it with publish_page.
| Argument | Type | Description |
|---|---|---|
| body | string | PROSE fallback: the page's content as HTML (page.body). Use ONLY for text-only pages (legal/privacy/terms/policies). Any DESIGNED page must pass sections instead — a prose page cannot be edited visually in the page builder. |
| meta_description | string | |
| meta_title | string | |
| page_type | string | home|product|collection|blog_index|cart|checkout|account|contact|faq|custom. Default custom. |
| sections | object | THE STANDARD for designed pages: a page-builder OBJECT {"sections":{"<id>":{"type":"<existing section type>","settings":{...},"blocks":[...]}},"order":["<id>",...]} — same shape as templates/*.json sections. Example: {"sections":{"main":{"type":"brand-hero","settings":{"heading":"Hi"}}},"order":["main"]}. The page then renders these sections AND is visually editable in the page builder. Takes precedence over body. |
| slug | string | |
| title* | string | Page title (required). |
delete_blog_postDelete one post permanently. Its categories, tags and author stay — they belong to the blog, not to the post.
| Argument | Type | Description |
|---|---|---|
| id* | string | Post id (required). |
delete_blog_termDelete a blog category, tag or author. The posts filed under it stay published; they simply lose that term.
| Argument | Type | Description |
|---|---|---|
| id* | string | Term id (required). Deleting a term does not delete its posts. |
| kind* | string | category | tag | author (required). |
delete_pagePERMANENTLY delete a CMS page (e.g. an accidental duplicate). Find the id with list_pages first; double-check it's the right page — this cannot be undone.
| Argument | Type | Description |
|---|---|---|
| id* | string | Page id (required) — find it with list_pages. Deletion is permanent. |
get_blog_settingsRead whether this site has a blog and the permalink grammar it serves — the post prefix and the category/tag/author bases. Every blog URL is built from these, so read them instead of assembling a path by hand.
list_blog_postsList the store's posts with their ids, permalinks, status and taxonomy — the way to find a post before updating or deleting it, and to check what is already published before writing more.
| Argument | Type | Description |
|---|---|---|
| author | string | Author SLUG. |
| category | string | Category SLUG. |
| page | integer | 1-based page (default 1). |
| per_page | integer | Page size (default 20, max 100). |
| search | string | Case-insensitive match on title and body. |
| status | string | draft | published. Omit for both. |
| tag | string | Tag SLUG. |
list_blog_termsList the blog's categories, tags or authors with their ids, slugs, post counts and archive URLs — read this before naming a term, so a menu links a shelf that exists rather than a slug someone guessed.
| Argument | Type | Description |
|---|---|---|
| kind* | string | category | tag | author (required). |
list_pagesList the store's CMS pages (id, title, slug, type, status, content mode). ALWAYS check here before create_page — creating an existing slug mints a -2 duplicate.
| Argument | Type | Description |
|---|---|---|
| status | string | Filter: draft|published. Omit for all. |
publish_pagePublish a CMS page so it's live on the storefront.
| Argument | Type | Description |
|---|---|---|
| id* | string | Page id (required). |
update_blog_postChange an existing post. Only the fields you send are written — everything else is kept, so you can fix a title or add a body without re-sending the article. Supplying category_names or tag_names REPLACES that axis.
| Argument | Type | Description |
|---|---|---|
| author_name | string | The writer's display name. The author row is created when it does not exist; give it a bio later with upsert_blog_author. |
| category_names | nullarray | Categories BY NAME — the platform mints the slug and creates the term when missing. Supplying this REPLACES the post's categories. A category classifies; a tag refines: never send the same word as both. |
| content | string | The article BODY as HTML — the whole piece, printed as one block by the post template. Use h2/h3, p, lists, tables, blockquote, pre/code. No <h1> (the template prints the title), no inline styles or classes, no Liquid. Images inside are re-hosted automatically. |
| cover_image | string | Cover image URL (auto re-hosted; never hotlink). Omit when the body already opens with its own figure. |
| excerpt | string | One or two sentences shown under the title and in every card. Plain text. |
| id | string | Post id — set to UPDATE, omit to CREATE. An update only touches the fields you send; the rest are kept. |
| meta_description | string | SEO meta description; the excerpt is used when omitted. |
| meta_title | string | SEO <title> for this post; the headline is used when omitted. |
| og_image | string | Share image URL (auto re-hosted); the cover is used when omitted. |
| published_at | string | RFC-3339 timestamp. Defaults to now on publish. |
| slug | string | URL slug; derived from the title and de-duplicated when omitted. The permalink is built by the store's blog settings — never paste a prefix here. |
| status | string | draft (default) or published. A published post dated in the future is scheduled: the storefront shows it when that time arrives. |
| tag_names | nullarray | Tags BY NAME (2-4 of the piece's own topic terms). Supplying this REPLACES the post's tags. Never repeat a category name here. |
| title | string | The post's headline, plain text. Required when creating. |
update_blog_settingsTurn the blog on, or change its permalink grammar (post prefix, category/tag/author bases) and its WordPress REST access. Changing a prefix keeps the old one working: the platform 301s every old address. Only the fields you send change.
| Argument | Type | Description |
|---|---|---|
| author_base | string | Author archive base, e.g. 'author' → /author/<slug>. |
| category_base | string | Category archive base, e.g. 'category' → /category/<slug>. |
| enabled | nullboolean | Turn the blog on (its routes, templates and admin) or off. |
| post_prefix | string | Path segment posts live under, e.g. 'blog' → /blog/<slug>. Empty means posts sit at the site root (/<slug>), WordPress's 'post name' permalink. Changing it 301s the old addresses. |
| tag_base | string | Tag archive base, e.g. 'tag' → /tag/<slug>. |
| wp_api_enabled | nullboolean | Serve the WordPress-shaped REST API at /wp-json for this site, so an outside publisher can post here. |
| wp_app_password | string | Application Password for that REST API (stored hashed, never returned). Send "" to clear it. |
update_pageUpdate an existing CMS page: title/slug/meta, and its CONTENT — `sections` (page-builder document, the standard for designed pages) or `body` (prose HTML, text-only pages). Use this instead of re-creating a page.
| Argument | Type | Description |
|---|---|---|
| body | string | Replace the prose HTML body (switches the page to prose mode). Text-only pages. |
| id* | string | Page id (required) — find it with list_pages. |
| meta_description | string | |
| meta_title | string | |
| sections | object | Replace the page-builder document (switches the page to sections mode): {"sections":{"<id>":{"type":"...","settings":{...}}},"order":[...]}. |
| slug | string | |
| title | string |
upsert_blog_termCreate or edit a blog CATEGORY, TAG or AUTHOR. Omit the id to create. An update keeps every field you do not send — so an author can be given a bio without losing their name. Categories classify a post; tags refine it; never use the same word for both.
| Argument | Type | Description |
|---|---|---|
| avatar | string | Author only: portrait URL (auto re-hosted). |
| bio | string | Author only: the profile paragraph shown in the author box under every post they sign. |
| description | string | Category only: the archive page's own paragraph. |
| id | string | Term id — set to UPDATE, omit to CREATE. An update keeps every field you do not send. |
| image | string | Category only: archive image URL (auto re-hosted). |
| kind* | string | category | tag | author (required). |
| name | string | Display name. Required when creating. |
| parent_id | string | Category only: parent category id (one level, like WordPress). |
| slug | string | URL slug; minted from the name when omitted. Let the platform mint it — a hand-written slug is how a menu ends up linking a shelf that does not exist. |
Translation & i18n4
get_languagesGet the store's default language + the full set of languages the storefront serves (supported_locales). The default is served at the root URL; others at /<locale>/.
list_translationsList existing translation overrides for a language (optionally filtered by resource type). Returns rows of {resource_type, resource_id, field, value}.
| Argument | Type | Description |
|---|---|---|
| locale* | string | Language code to read overrides for, e.g. "vi". |
| resource_types | nullarray | Optional filter: theme_section | page_section | theme_setting | page | blog_post | product | collection | content_record | menu_item | eav_attribute | eav_option | brand | store | personalization_template | personalization_field | personalization_choice | sales_rule | customer_tier. Empty = all. |
set_languagesSet which languages the storefront serves (supported_locales on the store). Include the default locale. After this, translate content with upsert_translations and the storefront serves each non-default language at /<locale>/.
| Argument | Type | Description |
|---|---|---|
| locales* | nullarray | Full list of language codes the storefront serves, e.g. ["en","vi"]. MUST include the store's default locale. Empty list = single-language. Use short codes that become the URL prefix (vi → /vi/). |
upsert_translationsWrite per-language translation overrides into the content_translations overlay. The default-language record is never changed — only the given language. An empty value removes an override. The storefront serves it at /<locale>/. Enable the language first with set_languages.
| Argument | Type | Description |
|---|---|---|
| entries* | nullarray | Batch of translation overrides to write. |
Media2
search_stock_imagesSearch Pexels for real, royalty-free stock photos and return candidate image URLs. This is the DEFAULT image source for every slot — hero/section/blog/content photos. Pick the best-matching candidate URL and write it straight into the image setting; the server automatically re-hosts it into the store's media library (never hotlinked, no upload_image needed).
| Argument | Type | Description |
|---|---|---|
| orientation | string | landscape | portrait | square — match the slot (hero/banner=landscape, avatar/logo=square). Default any. |
| per_page | integer | How many candidates to return (1-20, default 8). |
| query* | string | What to search for — a concrete subject in English (e.g. 'minimalist ceramic coffee mug', 'modern green apartment building at golden hour'). One subject per call. |
upload_imageUpload an image (from a local file PATH or a remote URL) to the store's media library and return the hosted URL. Use this for ANY image — content-record galleries, section backgrounds, page OG images, blog featured/inline images — then pass the returned url into the matching field (a gallery value, a section image_picker, featured_image_url, etc.).
| Argument | Type | Description |
|---|---|---|
| alt | string | Alt text. |
| folder | string | Media folder, e.g. 'blog', 'content'. Default 'general'. |
| path | string | Local file path to upload. Use this OR url. |
| url | string | Remote image URL to fetch and re-host. Use this OR path. |
Skills2
get_skillLoad the full content of one shop skill (a build/redesign playbook) by name. Read shop-core-rules first — the other skills reference its §A–§I anchors.
| Argument | Type | Description |
|---|---|---|
| name* | string | The skill name, e.g. shop-core-rules, shop-builder, shop-content-site. Use list_skills to see all names. |
list_skillsList the available shop build/redesign skills (reusable playbooks) with a one-line description each. Call get_skill to load a full playbook before doing that kind of work.
Other61
add_backend_api_domainAttach a hostname the MERCHANT owns to this backend, e.g. api.their-brand.com. Same DNS proof and TLS path a website domain takes — one implementation, two screens. It comes back `pending`: tell the merchant to add EITHER the CNAME (name = the hostname, target = `cname_target`) OR the TXT record (`txt_record_name` = `txt_record_value`) — one is enough, and the TXT is the only one that works behind Cloudflare's orange cloud, which rewrites the CNAME. Read those three values off the tool's reply; never compose them, they must match the verifier byte for byte. Then call verify_backend_api_domain. A hostname already serving their WEBSITE is refused rather than re-pointed — that would take a live site down.
| Argument | Type | Description |
|---|---|---|
| domain* | string | The hostname, e.g. api.their-brand.com. No scheme, no path. |
check_linksVERIFY every internal link on ONE storefront page resolves (no 404s) — the core-rules §E acceptance check. Fetches the page, follows each internal href, returns the broken ones. Run it on EVERY page you created/changed and once on the homepage at the end; a page is NOT done while this returns broken links.
| Argument | Type | Description |
|---|---|---|
| path | string | Storefront path to check, e.g. '/' or '/pages/services'. Default '/'. |
configure_paymentsSet which OFFLINE payment methods the checkout offers (cash on delivery, bank transfer) and the transfer details. There is no online gateway on this platform — do not promise one in the site's copy.
| Argument | Type | Description |
|---|---|---|
| bank_account_holder | string | |
| bank_account_number | string | |
| bank_instructions | string | Extra wording shown with the transfer details. |
| bank_name | string | |
| bank_transfer | nullboolean | Bank transfer. It switches itself OFF unless account_number is set — a transfer method with no account number is a dead end at the last step of checkout. |
| catalog_prefix | string | The catalogue path. Leave it alone: it is /products by default and system routes are English on every store. |
| cod | nullboolean | Cash on delivery. Needs no configuration, so it is the safe default for a new shop. |
create_backend_api_keyMint a key for THIS backend and return it ONCE — the plaintext is in `token`. Needed to call an api_key route for real, which is the only way to verify one works — publishing proves a graph is well formed, not that it does the right thing. These are bbk_ keys and are NOT platform keys: they open one merchant's endpoints and carry no platform permissions. Store it immediately; it is never readable again.
| Argument | Type | Description |
|---|---|---|
| name* | string | What this key is for, e.g. 'mobile app' — it is how a merchant knows which one to revoke later. |
create_backend_tableDeclare a NEW data table for the merchant's backend. The declaration decides everything downstream — what the API accepts, what the admin grid shows, and what the generated MCP tools advertise. Every problem is reported at once, each naming its column.
| Argument | Type | Description |
|---|---|---|
| code* | string | Table machine name, snake_case. This is the API path segment and the generated MCP tool name. |
| columns* | nullarray | The full column list. On update this REPLACES the declaration — send every column you want to keep. |
| label | string | Display name for the admin, plural — "Orders". |
| singular | string | Display name for ONE row — "Order". Shown wherever the admin talks about a single record. |
create_catalog_price_ruleCreate or update a CATALOG price rule — a SALE PRICE on the products a condition selects. No coupon: the price is simply lower on the listing, the product page and in the cart, with the regular price struck through. Use it for 'everything made of wood is 10% off'. Conditions use the platform's filter grammar, including attr.<code> over the store's declared attributes.
| Argument | Type | Description |
|---|---|---|
| active | nullboolean | Default true. This is the switch that acts IMMEDIATELY; a date window on a catalog rule only takes effect once the storefront's cached pages expire. |
| coupon_code | string | Cart scope only. The code a shopper types, e.g. 'SALE10' — case-insensitive, one rule per code. OMIT for a discount that applies by itself with nothing to type. |
| discount_type* | string | REQUIRED. For a catalog sale: by_percent (take X% off) | by_fixed (take X off) | to_fixed (charge exactly X). For a cart discount: percent_off | fixed_cart (X off the basket) | fixed_item (X off every matching unit) | free_shipping. |
| discount_value | nullnumber | A PERCENTAGE for the percent types (10 = 10%), otherwise MAJOR units (30000 = 30,000 off). Omit only for free_shipping. |
| ends_at | string | Optional window end. |
| id | string | Discount id — set to UPDATE, omit to CREATE. |
| match | string | all (every condition must hold — the default) or any. |
| max_discount | nullnumber | Ceiling for a percentage, in MAJOR units. Ignored on the fixed types. |
| min_subtotal | nullnumber | Cart scope only — the basket must reach this (MAJOR units) before the rule applies. |
| name* | string | REQUIRED — what the shopper reads on the discount line, in the store's language. An unnamed discount shows as a blank row giving money away. |
| priority | nullinteger | Lower runs first. Catalog rules never stack: the FIRST matching rule sets the price. |
| rules | nullarray | The CONDITIONS selecting which products the discount is about — the same grammar collections use, including attr.<code>. NO conditions means EVERY product, which for a discount is money given away; a real sale names a subset. |
| scope* | string | REQUIRED. 'catalog' = a SALE PRICE on the products the conditions select: no code, the price simply IS lower on the listing, the product page and in the cart. 'cart' = a COUPON (or an automatic rule) taken off the whole basket at checkout. |
| starts_at | string | Optional window start, YYYY-MM-DD or an ISO-8601 timestamp. |
| usage_limit | nullinteger | Cart scope only — how many orders may use the code. 0 or omitted means unlimited. |
create_discountCreate or update a CART price rule — a coupon code, or a discount that applies by itself, taken off the whole basket at checkout. Use it for 'SALE10 takes 10% off orders over 500,000'. Omit coupon_code for an automatic discount.
| Argument | Type | Description |
|---|---|---|
| active | nullboolean | Default true. This is the switch that acts IMMEDIATELY; a date window on a catalog rule only takes effect once the storefront's cached pages expire. |
| coupon_code | string | Cart scope only. The code a shopper types, e.g. 'SALE10' — case-insensitive, one rule per code. OMIT for a discount that applies by itself with nothing to type. |
| discount_type* | string | REQUIRED. For a catalog sale: by_percent (take X% off) | by_fixed (take X off) | to_fixed (charge exactly X). For a cart discount: percent_off | fixed_cart (X off the basket) | fixed_item (X off every matching unit) | free_shipping. |
| discount_value | nullnumber | A PERCENTAGE for the percent types (10 = 10%), otherwise MAJOR units (30000 = 30,000 off). Omit only for free_shipping. |
| ends_at | string | Optional window end. |
| id | string | Discount id — set to UPDATE, omit to CREATE. |
| match | string | all (every condition must hold — the default) or any. |
| max_discount | nullnumber | Ceiling for a percentage, in MAJOR units. Ignored on the fixed types. |
| min_subtotal | nullnumber | Cart scope only — the basket must reach this (MAJOR units) before the rule applies. |
| name* | string | REQUIRED — what the shopper reads on the discount line, in the store's language. An unnamed discount shows as a blank row giving money away. |
| priority | nullinteger | Lower runs first. Catalog rules never stack: the FIRST matching rule sets the price. |
| rules | nullarray | The CONDITIONS selecting which products the discount is about — the same grammar collections use, including attr.<code>. NO conditions means EVERY product, which for a discount is money given away; a real sale names a subset. |
| scope* | string | REQUIRED. 'catalog' = a SALE PRICE on the products the conditions select: no code, the price simply IS lower on the listing, the product page and in the cart. 'cart' = a COUPON (or an automatic rule) taken off the whole basket at checkout. |
| starts_at | string | Optional window start, YYYY-MM-DD or an ISO-8601 timestamp. |
| usage_limit | nullinteger | Cart scope only — how many orders may use the code. 0 or omitted means unlimited. |
delete_backend_auth_providerRemove a social sign-in provider's configuration, secret included. People who signed up through it KEEP their accounts — but with no password they will have no way back in, so tell the merchant that before doing it.
| Argument | Type | Description |
|---|---|---|
| provider* | string | google, facebook or github. |
delete_backend_fileDelete one stored file by key. Permanent.
| Argument | Type | Description |
|---|---|---|
| key* | string | The object key, as list_backend_files reports it. |
delete_backend_routeUnbind an API path. The endpoint stops answering at once — the workflow itself is left alone, so this is how you retire a path without losing the work behind it.
| Argument | Type | Description |
|---|---|---|
| id* | string | The row id, as the matching list tool reports it. |
delete_backend_rowDelete one record from a backend table.
| Argument | Type | Description |
|---|---|---|
| id* | string | Record id (required). |
| table* | string | Table code (required). |
delete_backend_secretForget a stored credential. Any workflow still referring to {{ secret.<code> }} will fail its next call — check first, because the failure surfaces to whoever calls the endpoint, not here.
| Argument | Type | Description |
|---|---|---|
| code* | string | The secret's code. |
delete_backend_tableRemove a data table. Its records go with it.
| Argument | Type | Description |
|---|---|---|
| code* | string | Table code (required). |
delete_backend_variableRemove a stored setting. Publishing any workflow that still names it is then refused, with the node and the reference named — so a delete cannot quietly empty a URL.
| Argument | Type | Description |
|---|---|---|
| code* | string | The variable code. |
delete_backend_workflowDelete a workflow AND the API paths bound to it — a binding to something that is gone is not a binding, and a route left behind is a live endpoint nobody can reach and nobody can remove. To retire a path but keep the work behind it, use delete_backend_route instead.
| Argument | Type | Description |
|---|---|---|
| code* | string | Workflow code (required). |
enable_commerceSwitch this website into SELLING mode: it gains /products, /cart, /checkout, /order/<token> and the admin's order desk, and gets default delivery options seeded so the checkout is never a dead end. Idempotent — safe to call on every build.
fetch_urlFetch the TEXT content of a public http(s) URL — use this to read a design handoff (e.g. a claude.ai/design or Anthropic design-file export, an HTML/CSS file), a reference page to clone, or a JSON/Markdown spec the user links. Handles gzip-compressed responses automatically. Returns the raw body (HTML/CSS/JS/JSON/text), capped at 512KB. NOT for images: to put an image in the store, pass its URL to upload_image instead. Private/internal addresses are refused.
| Argument | Type | Description |
|---|---|---|
| url* | string | Public http(s) URL to fetch (a design HTML/CSS file, a reference page, a JSON spec). Returns the raw text. |
generate_product_variantsFill in EVERY missing combination of a product's option axes (2 sizes × 3 colours = 6 rows) in one call. Each row inherits the product's price unless you pass `prices`, and existing rows are left untouched, so it is safe to call again after adding a value to an axis. Use this instead of writing out combinations by hand — hand-written sets are where a size goes missing.
| Argument | Type | Description |
|---|---|---|
| prices | nullarray | Optional per-combination prices in MAJOR units, in generated order. Omit entirely to have every combination inherit the product price. |
| product_id* | string | Product id (required). |
| stock | integer | Units to seed on every generated combination. |
get_backend_api_domainThe hostname this website's backend answers on — every route and every auth endpoint is <this>/<path>. Read it BEFORE trying to call anything: the API deliberately does NOT share a hostname with the website, or /orders would be ambiguous between a page and an endpoint. Empty list means no hostname yet — call provision_backend_api_domain.
get_backend_node_catalogList every workflow node type, its settings, which of them the publish gate demands, and the Go imports a code node may use. Read this BEFORE drawing a workflow — it is generated from the gate's own table, so a node it lists is a node the gate knows.
get_backend_run_stepsEvery step of ONE REAL run — what each node returned, what it printed (wm.Log), how long it took and which one failed. list_backend_runs says a run failed; this says where. Not the same as test_backend_workflow, which runs the DRAFT with a request you made up: this is what actually happened to a real caller.
| Argument | Type | Description |
|---|---|---|
| id* | string | The row id, as the matching list tool reports it. |
get_backend_shop_catalogList the WEBSITE's own data a workflow may reach — orders, products, blog posts, contacts, media — with the columns of each, which of them are read-only, what can be filtered and sorted, and the row ceiling per read. These are named as ordinary tables in the db nodes: table: "shop:order". Resolved for THIS website, so shop:product appears only where there is a real catalogue — a content site's products are a content type, not a pretend product. Read this before drawing a workflow that touches website data; it is generated from the publish gate's own registry, so a column it lists is a column the gate accepts. Every update is a PATCH: a column you do not name is left alone.
get_backend_shop_eventsList the events the WEBSITE publishes — an order placed, a form submitted — and which workflows already answer each. To run a workflow on one, DRAW an `event_trigger` node as its entry with that event name; publishing binds it, and removing the node unbinds it. The event's body arrives as input.body in the SAME shape a shop: read returns, so {{ input.body.total }} on order.placed means what {{ find.rows.0.total }} means in a query. The list is short because the platform emits only these today — there is no product or blog event, and offering one that never fires would be worse than its absence.
get_backend_signature_schemesThe webhook signature schemes a `signed` route may declare, each with the header it arrives in and which stored secret code it reads. Served from the SAME table the verifier uses, so a scheme listed here is one the gateway implements — read it instead of guessing a name, because an unknown scheme is refused rather than falling back.
insert_backend_rowWrite ONE record into a backend table. A key with no declared column is refused with that key named — this API never silently drops a field it was given.
| Argument | Type | Description |
|---|---|---|
| data* | object | The record as {columnCode: value}. A key with no declared column is REFUSED with that key named — never silently dropped. |
| table* | string | Table code (required). |
list_backend_api_keysList the keys that open this backend's api_key routes and its built-in /files and /_bb endpoints. Only the prefix comes back — the key itself existed once, in the reply to the call that made it.
list_backend_auth_providersList every social sign-in provider this website CAN use, configured or not, with the exact callback URL to register with each. Providers differ in one way that is not cosmetic: asserts_email_verification. Google states whether an address was verified, so a Google sign-in can adopt an existing account with that email. Facebook and GitHub never state it, so a colliding email is REFUSED with an instruction (sign in with the password, then link) rather than silently becoming the same person — matching an unverified address is account takeover.
list_backend_end_usersList the merchant's APP users — the people who register and sign in to what they built, not platform accounts and not storefront customers. Password hashes are never returned.
| Argument | Type | Description |
|---|---|---|
| limit | integer | How many (default 50). |
| offset | integer | Rows to skip. |
list_backend_filesList what the backend has stored — the objects behind /files/<key>.
list_backend_routesList the API paths this website serves, and which workflow each runs.
list_backend_runsList recent workflow runs — what ran, how long it took, and what failed. The first place to look when an endpoint misbehaves.
| Argument | Type | Description |
|---|---|---|
| limit | integer | How many (default 50). |
| offset | integer | Skip this many, to page back through older runs. |
| workflow | string | Only runs of this workflow code. Without it you get the whole store's runs, and the one you just published is buried under everything else's traffic. |
list_backend_schedulesList the workflows that run on a timetable, with the next run, the last run and whether it failed.
list_backend_secretsList the CODES of the credentials this website stores — API keys a workflow calls other services with. Values are never returned by anything. Use a code as {{ secret.<code> }} in an http_request node's header, URL or body, or as wm.Secret("<code>") in a code node.
list_backend_tablesList the data tables this website's backend declares, with their columns and record counts. Read this before writing records: the declaration is the contract, and a write naming an undeclared column is refused.
list_backend_variablesList this website's stored SETTINGS — code, value and note. Unlike a credential these come back WITH their values, because the whole point of a variable is that it can be read and checked. Use one in a workflow as {{ var.<code> }}, anywhere: a URL, a header, a body field, a branch rule, a code node.
list_backend_workflow_versionsThe graphs a workflow has previously SERVED, newest first — the current published one is not in the list, it is on the workflow. Publishing overwrites the live graph, so this is the only record of what it used to be; pair it with revert_backend_workflow when a publish made things worse.
| Argument | Type | Description |
|---|---|---|
| code* | string | Workflow code (required). |
list_backend_workflowsList the merchant's workflows with their draft and published graphs.
list_storesList the websites this API key can act on — their ids, slugs and whether each sells. A key whose organization owns several sites does not resolve one by itself: pick from this list and send it as the X-Store-Slug (or X-Store-Id) header on every call, which is what pins the session to that site.
provision_backend_api_domainMint api-<slug>.<apex> for this website. Idempotent — calling it again returns the hostname that is already there rather than a second one. Inside the platform's wildcard certificate, so it works immediately with nothing to verify.
publish_backend_workflowRun the publish gate and promote the draft. The gate measures BOTH directions — a required setting left empty, a path that never reaches a respond node, a reference to a node that does not run first, a db node touching an undeclared column, a code node importing outside the allowed set — and REFUSES with the offending node named. Nothing is published when it refuses; fix the node it names and call again.
| Argument | Type | Description |
|---|---|---|
| code* | string | Workflow code (required). |
query_backend_rowsRead records from a backend table, filtered and paged in SQL.
| Argument | Type | Description |
|---|---|---|
| desc | boolean | Sort descending. |
| filters | nullarray | Predicates as {column, op, value}. op is one of eq ne gt gte lt lte contains starts in is_null not_null. A filter on an undeclared column is REFUSED — a filter that silently matched nothing looks exactly like an empty table. |
| limit | integer | Page size (default 100, max 500). |
| match | string | all (default) or any. |
| offset | integer | Rows to skip. |
| order_by | string | A declared column code. |
| table* | string | Table code (required). |
remove_backend_api_domainDetach a custom API hostname. The built-in api-<slug> one cannot be removed — it is what every published route answers on.
| Argument | Type | Description |
|---|---|---|
| id* | string | The row id, as the matching list tool reports it. |
revert_backend_workflowPut a previously-live graph back, by version from list_backend_workflow_versions. It PUBLISHES rather than restores: the old graph goes through the same gate as any other publish, because the world it ran in has moved — a table it wrote to may have been deleted, a variable it read may be gone — so you can get violations back instead of a revert. The graph being replaced is archived like any other, so a revert is itself revertible. The DRAFT is left alone.
| Argument | Type | Description |
|---|---|---|
| code* | string | Workflow code (required). |
| version* | integer | Version to go back to, from list_backend_workflow_versions (required). |
revoke_backend_api_keyRevoke one key. Calls using it start failing immediately.
| Argument | Type | Description |
|---|---|---|
| id* | string | The row id, as the matching list tool reports it. |
save_backend_workflowCreate or update a workflow's DRAFT graph — UPDATE genuinely works now; it used to fall through to create and fail on the unique index, so the only way to change one line was delete-and-recreate, which also deletes every route bound to it. Saved exactly as drawn — the gate runs at publish, so a half-finished graph can be stored without losing work. Nodes are {id, type, config} — and ONLY those keys plus label. DO NOT SET x/y: you have never seen the canvas, so you cannot know a card is 220x62, and a guessed stride buries each card under the next (measured: a graph saved at 180px apart had 8 overlapping pairs out of 10 nodes, titles cut off mid-word). Omit them and the canvas lays the graph out from its shape. Every key inside config must be one the node catalogue lists for that type, or the save is refused naming the node and the key (nothing is dropped in silence). http_in takes no config: its method and path live on the route. Edges are {from, to, port} where port is 'true'/'false' out of a branch — or 'error' out of ANY node, which is the path taken when that node fails instead of the whole run returning 500. The failing node's id then holds {error, failed} so the recovery path can read {{ ask.error }} and say something true. An error path must still reach a respond node.
| Argument | Type | Description |
|---|---|---|
| code* | string | Workflow machine name, snake_case. |
| draft | object | The graph: {nodes:[{id,type,config}], edges:[{from,to,port}]}. Call get_backend_node_catalog first — it lists every node type and which settings the publish gate demands. Saved as drawn; the gate runs at publish. |
| name | string | Display name. |
search_stock_videosSearch Pexels for real, royalty-free stock VIDEOS and return candidate looping-clip URLs (mp4, ~720–1080p) + a poster still. Use for a HERO / section BACKGROUND VIDEO when the design calls for a cinematic, motion-led look — NOT every store wants one. Write the returned `url` into the section's `video_url` setting and `poster` into `video_poster`; both auto re-host into the store's media library on write (never hotlinked). The hero renders it muted + autoplay + loop + playsinline with the poster as the still. For a plain still image use search_stock_images instead.
| Argument | Type | Description |
|---|---|---|
| orientation | string | landscape | portrait | square — match the slot (hero/banner=landscape, avatar/logo=square). Default any. |
| per_page | integer | How many candidates to return (1-20, default 8). |
| query* | string | What to search for — a concrete subject in English (e.g. 'minimalist ceramic coffee mug', 'modern green apartment building at golden hour'). One subject per call. |
set_backend_auth_providerConfigure one social sign-in provider. The client secret is sealed and never readable again; OMIT it to keep the stored one, because sending an empty string would erase it. Switching a provider on requires a client id, a secret, and at least one redirect_allowlist entry — a callback that redirects anywhere is an open redirect on the endpoint that issues sessions, so the platform refuses to enable one without the list. Register the callback_url from list_backend_auth_providers with the provider EXACTLY as given: providers compare it byte for byte.
| Argument | Type | Description |
|---|---|---|
| client_id | string | The OAuth client id from the provider's console. |
| client_secret | string | The OAuth client secret. Omit to keep the one already stored — sending "" would erase it. |
| enabled | nullboolean | Turn the provider on. Refused unless a client id, a secret and a redirect allow-list are all present. |
| link_by_verified_email | nullboolean | Default true. Only ever consulted for a provider that asserts verification, so it changes nothing for Facebook or GitHub. |
| provider* | string | google, facebook or github. |
| redirect_allowlist | nullarray | Absolute https URLs the app may be returned to, e.g. ["https://app.example.com"]. Matched by scheme+host+path prefix. REQUIRED to enable a provider. |
| scopes | string | Space-separated scope override. Blank uses the provider's defaults, which is almost always right. |
set_backend_secretStore or replace a credential. It is sealed and can never be read back — not by this tool, not by the admin. A workflow reaches it by code. The platform substitutes the value at the moment it dials out, so it never enters the process that runs merchant code and is scrubbed out of the run log; publishing is refused if you put one in a respond node.
| Argument | Type | Description |
|---|---|---|
| code* | string | Machine name, e.g. gemini_key. |
| value* | string | The secret itself. Stored sealed; never returned. |
set_backend_variableStore or replace a SETTING — an API base URL, a sender name, a page size, a feature flag, the address a webhook posts back to. Anything a workflow needs that is not a credential belongs here, NOT in a secret. A secret is deliberately unreadable: it never enters the runner, never appears in a step log, is scrubbed from the ones it might, cannot be read back and is refused in a respond node. Put a base URL there and the one value you need while debugging is the one you cannot see. A variable is the opposite on every count, and a mistyped {{ var.… }} is refused at PUBLISH instead of resolving to nothing and calling the wrong host.
| Argument | Type | Description |
|---|---|---|
| code* | string | Machine name, lowercase with underscores — e.g. api_base_url. This is what you type inside {{ var.… }}. |
| note | string | What it is for, in the merchant's words. Shown beside it in the admin. |
| value* | string | The value. Stored as written and readable by anyone who can open the admin — never put a credential here. |
set_backend_workflow_test_inputPin the request this workflow is tested with — {method, path, params, query, headers, body}. test_backend_workflow replays it when you call that tool with no input of your own, and the merchant sees the same request on the trigger node, so the case you reproduced does not vanish when you stop. Stored BESIDE the graph: it is never published to the runner.
| Argument | Type | Description |
|---|---|---|
| code* | string | Workflow code. |
| input* | object | The request to pin, e.g. {"method":"POST","path":"/todos","body":{"title":"mua sua"}}. May include "as_user_id" to run as one of the app's users. |
set_commerce_enabledToggle the store between a shop (true) and a content-only website (false). Merges store.settings.commerce_enabled. (Requires a key with settings/full-access scope.)
| Argument | Type | Description |
|---|---|---|
| enabled* | boolean | true = normal shop; false = content-only site (404s ecom routes, hides ecom admin menus). |
set_product_attributesDeclare the SPEC FIELDS every product in this shop can fill (material, origin, warranty…). They appear in the admin product form and print on the product page, so declare them BEFORE creating products that carry them — an attrs value with no declared code is dropped.
| Argument | Type | Description |
|---|---|---|
| attributes* | nullarray | The spec fields EVERY product in this shop can fill (material, origin, warranty…). Supplying this replaces the schema. |
test_backend_workflowEXECUTE a workflow's draft once with a request you make up, and get every step back: what each node returned, what it PRINTED (wm.Log from a code node), how long it took, and which one failed. This is how to debug a node — read the steps, not the final answer. If the gate refuses the draft you get violations instead of a run, because a graph the gate refuses cannot be usefully executed. Metered like a real call.
| Argument | Type | Description |
|---|---|---|
| code* | string | Workflow code (required). |
| input | object | The request to pretend: {method, path, query, params, headers, body, ip}. Anything omitted gets a sensible default. Omit the whole thing to replay the request pinned on the workflow. ⚠️ Add "as_user_id": <id from list_backend_end_users> when the workflow touches a table with an owner column — a trial run is nobody's session by default, so such a write fails on "owner_id: required column is null". |
| node | string | Stop after this node id. The nodes before it are REUSED from the last run rather than re-executed, so iterating on one step does not re-send its emails or re-write its rows. Omit to run the whole graph. |
update_backend_rowChange part of a record. Keys you send are replaced; keys you omit are left alone.
| Argument | Type | Description |
|---|---|---|
| data* | object | A PARTIAL change: keys present are replaced, keys absent are left alone. |
| id* | string | Record id (required). |
| table* | string | Table code (required). |
update_backend_tableREPLACE a table's column declaration. Send every column you want to keep — this is not a merge. Records already stored are left exactly as they are: the new declaration governs writes from now on, and nothing walks the existing rows.
| Argument | Type | Description |
|---|---|---|
| code* | string | Table machine name, snake_case. This is the API path segment and the generated MCP tool name. |
| columns* | nullarray | The full column list. On update this REPLACES the declaration — send every column you want to keep. |
| label | string | Display name for the admin, plural — "Orders". |
| singular | string | Display name for ONE row — "Order". Shown wherever the admin talks about a single record. |
update_store_brandingWrite the store's ADMIN-level SEO + branding (Settings ▸ Branding/SEO): meta_title, meta_description, og_image_url, logo_url, favicon_url. THIS is where the SEO pass PERSISTS store SEO — the storefront <title>/<meta description>/OG read shop.meta_title/meta_description from here (never bake SEO into the theme, and do NOT hand-write a graphql mutation — there is a real tool, this one). Call it in the SEO step from the real brand + what the store sells.
| Argument | Type | Description |
|---|---|---|
| favicon_url | string | Favicon URL (auto re-hosted). |
| logo_url | string | Brand logo URL (auto re-hosted). |
| meta_description | string | Store-level SEO meta description (~150 chars), real brand copy — not boilerplate. |
| meta_title | string | Store-level SEO <title> (~50-60 chars) — the site's default title + the homepage title. Derived from the real brand + what the store sells. |
| og_image_url | string | Open-Graph share image URL (auto re-hosted; never hotlink). |
upsert_backend_routeBind an API path to a workflow, and optionally put it live. For a webhook receiver (auth_mode 'signed') you MUST also pass signature_scheme and signature_secret_code — a signed route left on the default scheme refuses the sender's real deliveries. The reply carries them back, so read it and check what was stored.
| Argument | Type | Description |
|---|---|---|
| auth_mode | string | public | api_key (default) | end_user | signed. |
| id | string | Existing route id. Given, the write is a PATCH — fields you omit keep their stored value. Omitted, this creates a route. |
| input_schema | object | Optional JSON shape this route accepts, for documentation and validation. |
| is_published | nullboolean | Put the route live. Refused unless the workflow has been published — an endpoint answering 500 because its graph was never published is the merchant finding out from a customer. |
| method* | string | GET | POST | PUT | PATCH | DELETE. |
| path* | string | Path on the store's api-domain, e.g. /orders or /orders/:id. /auth/* and /files/* belong to the built-in services and are refused. |
| signature_scheme | string | Only for auth_mode 'signed': WHICH signature to verify — webmati (default) | stripe | github. Get the list from get_backend_signature_schemes. A webhook route left on the default rejects the sender's real deliveries. |
| signature_secret_code | string | Only for auth_mode 'signed': the code of the stored secret holding the signing key (create it with upsert_backend_secret). |
| workflow_code* | string | The workflow this path runs. |
upsert_productCreate or update a PRODUCT (the real catalogue, not a content type). Prices are NUMBERS IN MAJOR UNITS — 150000 means 150,000 in the store's currency, never a formatted string like '150,000 d' and never 15000000. A product with option axes MUST also carry its variants (or call generate_product_variants right after), because a shopper cannot add an unresolved combination to the cart and the platform refuses to guess a size. Set status:'published' for anything the shop actually sells — a catalogue of drafts renders empty.
| Argument | Type | Description |
|---|---|---|
| attrs | object | Merchant-declared spec fields keyed by their attribute code (material, origin…). Codes must exist in the store's commerce attribute schema — anything else is dropped. |
| category | string | One-level category label in the store's language, e.g. 'Table lamps'. It drives the catalogue's filter chips and can be tested by a collection's conditions. There is no category tree. |
| compare_at_price | nullnumber | The struck-through 'was' price, in MAJOR units. Must be HIGHER than price or it is ignored — 'was 100, now 100' is a bug, not a sale. |
| description | string | Product copy. Simple HTML is allowed (<p>, <ul>, <strong>). |
| id | string | Product id — set to UPDATE, omit to CREATE. |
| images | nullarray | Photo URLs, auto re-hosted. The FIRST one is the card thumbnail, so lead with the shot that reads at 300px. |
| options | nullarray | The variant AXES. Declaring options WITHOUT variants leaves a product nobody can add to the cart — pass variants too, or use generate_product_variants. |
| price | nullnumber | Price in MAJOR units — 150000 means 150,000 in the store's currency. NEVER a formatted string ('150,000 d') and never minor units (15000000): the storefront applies the store's currency itself. |
| sku | string | |
| slug | string | URL handle; derived from the title (accented letters are transliterated, never dropped) and de-duplicated if omitted. |
| sort_order | integer | Manual position in the catalogue (lower first). This is what 'the order I arranged' means to a merchant. |
| status | string | draft (default) or published. Only a PUBLISHED product is sellable and routable — a shop full of drafts renders an empty catalogue. |
| stock | integer | Units on hand, for a product WITHOUT variants. With variants, put the count on each variant instead. |
| title* | string | REQUIRED — the product NAME: one short line of plain text in the store's language, no HTML, under 60 characters. |
| track_stock | boolean | TRUE only when the merchant actually counts inventory. Left false (the default) the shop always sells — refusing an order because a field defaulted to 0 is the worse failure. |
| variants | nullarray | The sellable combinations. Supplying this REPLACES the whole set (rows whose id you include survive). One row per combination the shop actually sells. |
upsert_shipping_methodCreate or update ONE delivery option the shopper picks at checkout. A shop with no enabled method cannot take an order, so a new shop needs at least one. Fees are MAJOR units.
| Argument | Type | Description |
|---|---|---|
| enabled | nullboolean | Default true. A store with no ENABLED method cannot take an order at all. |
| fee | nullnumber | The fee in MAJOR units. Ignored for pickup. |
| id | string | Method id — set to UPDATE, omit to CREATE. |
| kind* | string | flat (one fee), free_over (free once the subtotal reaches min_order) or pickup (collect in store: no fee, no address asked). |
| min_order | nullnumber | free_over only: the subtotal at which delivery becomes free. |
| title* | string | What the shopper reads at checkout, in the store's language, e.g. 'Nationwide delivery'. |
validate_backend_workflowRun the PUBLISH GATE against a workflow's draft WITHOUT publishing it. Returns the same violations publish would, each naming the node and the setting. Call this after every edit instead of publishing to find out: it is the same rules, so nothing new can appear at publish time.
| Argument | Type | Description |
|---|---|---|
| code* | string | Workflow code (required). |
verify_backend_api_domainRe-run the DNS check on a custom API hostname. Returns the row with its new status — `verified` means it is live, `failed` means the records are not visible yet. DNS takes minutes to hours, so a failure right after the records are added is normal; wait and call again rather than telling the merchant something is wrong.
| Argument | Type | Description |
|---|---|---|
| id* | string | The row id, as the matching list tool reports it. |
