feat: add wordvibe_build_page (Word Page Builder over MCP)

Wraps the site tool wpb_build_page: build or update a Word Page from {kind, html} sections. The page lands as a native Word Page (payload in post meta, builder-rendered), so it stays editable and undoable in the builder. Also documents the builder tools in the bundled skill and README.
This commit is contained in:
2026-09-22 01:59:54 +03:00
parent ab1cfc88da
commit 6eeb8b9087
6 changed files with 103 additions and 0 deletions
+1
View File
@@ -60,6 +60,7 @@ Debug a plugin that won't load with `HERMES_PLUGINS_DEBUG=1 hermes plugins list`
| `wordvibe_write_file` | Overwrite a file (the site snapshots the previous version first) |
| `wordvibe_edit_file` | Targeted `search``replace` edit (safer than a full write) |
| `wordvibe_search` | Codebase search across themes/plugins/core, plain text or regex |
| `wordvibe_build_page` | Build/update a **Word Page Builder** page from `{kind, html}` sections (native in the builder) |
Slash command for when you don't want to spend a model turn:
+1
View File
@@ -65,6 +65,7 @@ def register(ctx):
("wordvibe_write_file", schemas.WRITE_FILE, tools.write_file),
("wordvibe_edit_file", schemas.EDIT_FILE, tools.edit_file),
("wordvibe_search", schemas.SEARCH, tools.search),
("wordvibe_build_page", schemas.BUILD_PAGE, tools.build_page),
]
for name, schema, handler in registry:
+1
View File
@@ -16,6 +16,7 @@ provides_tools:
- wordvibe_write_file
- wordvibe_edit_file
- wordvibe_search
- wordvibe_build_page
requires_env:
- name: WORDVIBE_SITE_URL
description: "WordPress site URL (e.g. https://mysite.com). MCP Server Mode must be enabled in WordVibe → Settings → MCP."
+43
View File
@@ -13,6 +13,49 @@ _SITE = {
),
}
BUILD_PAGE = {
"name": "wordvibe_build_page",
"description": (
"Build or update a page in the site's Word Page Builder (WPB) from section HTML. Each "
"section is wrapped automatically in <section data-wpb-section=\"kind\" data-wpb-id=\"sec-…\">, "
"so the page opens natively in the builder and stays editable/undoable there. Use this to "
"publish a landing page, or to rebuild a page's sections, without a browser."
),
"parameters": {
"type": "object",
"properties": {
"title": {"type": "string", "description": "Page title."},
"sections": {
"type": "array",
"description": (
"Ordered list of sections. Each item: {kind, html} — kind is a kebab-case section "
"name (hero, features, pricing, testimonials, faq, cta, footer, gallery, contact, "
"about, services, custom); html is the section's inner HTML (inline styles are fine)."
),
"items": {
"type": "object",
"properties": {
"kind": {"type": "string", "description": "Section kind, e.g. 'hero'."},
"html": {"type": "string", "description": "Inner HTML for the section."},
},
"required": ["kind", "html"],
},
},
"slug": {"type": "string", "description": "URL slug (optional)."},
"status": {
"type": "string",
"description": "draft, publish, pending or private (default: draft).",
},
"post_id": {
"type": "integer",
"description": "Existing page ID to update; omit to create a new page.",
},
"site": _SITE,
},
"required": ["title", "sections"],
},
}
SITES = {
"name": "wordvibe_sites",
"description": (
+19
View File
@@ -48,8 +48,27 @@ configured site is the default; when several are configured, pass `site` (name o
| Database lookups | `wordvibe_call(tool="db_query", arguments={"sql":"SELECT COUNT(*) FROM wp_posts"})` |
| Security sweep | `wordvibe_call(tool="security_scan_installed")` |
| Roll back an edit | `wordvibe_call(tool="rollback_list")``rollback_restore` |
| Build a page | `wordvibe_build_page(title=…, sections=[{kind, html}], status="publish")` |
| Site basics | `wordvibe_call(tool="site_info")`, `theme_list`, `plugin_list` |
## Building pages (Word Page Builder)
`wordvibe_build_page` creates or updates a **Word Page** — a page the site's Word Page Builder owns and
renders from its own stored payload, so it opens natively in the builder (edit, undo, redo all work).
- One call builds the whole page: `title`, `sections: [{kind, html}]`, optional `slug`, `status`, `post_id`.
- Section kinds: `hero`, `features`, `pricing`, `testimonials`, `faq`, `cta`, `footer`, `gallery`,
`contact`, `about`, `services`, `custom`. Each becomes `<section data-wpb-section="kind" data-wpb-id="sec-…">`.
- Write inline styles rather than inventing class names — the builder preserves what you give it, but
the site's stylesheet won't contain classes that don't exist.
- Editing an existing page: `wordvibe_tools(filter="wpb")` lists the builder tools, then use
`wordvibe_call` with `wpb_get_page` / `wpb_get_page_full`, `wpb_replace_section_html`,
`wpb_add_section_to_page`, `wpb_delete_section`, `wpb_generate_page` (AI), `wpb_chat_edit_page` (AI, one
instruction per call), `wpb_list_pages`.
- Needs the page builder present in the site's build (WordVibe 1.0.x cloud/ide). Where it was stripped,
the tools answer "The Word Page Builder is not available in this build of the plugin." — say so instead
of faking it with a plain page.
## Multi-site
With `WORDVIBE_MCP_SITES` configured, name the target explicitly on every call — do not let a default
+38
View File
@@ -212,3 +212,41 @@ def search(args: dict, **kwargs) -> str:
if args.get("regex"):
payload["regex"] = True
return _call_tool(args.get("site") or "", "codebase_search", payload)
def build_page(args: dict, **kwargs) -> str:
"""Build/update a Word Page Builder page (wraps the site's wpb_build_page tool)."""
title = str(args.get("title") or "").strip()
if not title:
return _err("Missing 'title'.")
sections = args.get("sections")
if isinstance(sections, str):
try:
sections = json.loads(sections)
except Exception as exc: # noqa: BLE001
return _err(f"'sections' must be a JSON array: {exc}")
if not isinstance(sections, list) or not sections:
return _err("'sections' must be a non-empty array of {kind, html}.")
clean = []
for item in sections:
if isinstance(item, str):
clean.append({"kind": "custom", "html": item})
continue
if not isinstance(item, dict) or "html" not in item:
return _err("Each section needs {kind, html}.")
clean.append({"kind": str(item.get("kind") or "custom"), "html": str(item.get("html") or "")})
payload = {"title": title, "sections": clean}
for key in ("slug", "status"):
val = str(args.get(key) or "").strip()
if val:
payload[key] = val
if args.get("post_id"):
try:
payload["post_id"] = int(args["post_id"])
except Exception: # noqa: BLE001
return _err("'post_id' must be an integer.")
return _call_tool(args.get("site") or "", "wpb_build_page", payload)