webcake-landing-mcp 1.5.4 → 1.5.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,11 @@
1
1
  [
2
+ {
3
+ "v": "1.5.5",
4
+ "d": "13/10/2026",
5
+ "type": "Fixed",
6
+ "en": "The page/source/sections/patches parameters on validate_page, create_page, update_page, add_section, and patch_page now declare a typed JSON Schema…",
7
+ "vi": "Các tham số page/source/sections/patches của validate_page, create_page, update_page, add_section, và patch_page nay khai báo JSON Schema có kiểu rõ…"
8
+ },
2
9
  {
3
10
  "v": "1.5.4",
4
11
  "d": "09/10/2026",
@@ -33,12 +40,5 @@
33
40
  "type": "Added",
34
41
  "en": "search_images now returns a sizes map on every photo (the delivered width/height in px for each src variant, accounting for retina dpr) plus a…",
35
42
  "vi": "search_images nay trả về một sizes map trên mỗi ảnh (kích thước width/height thực tế theo px của từng biến thể src, có tính đến dpr cho màn hình…"
36
- },
37
- {
38
- "v": "1.4.0",
39
- "d": "23/07/2026",
40
- "type": "Changed",
41
- "en": "publish_page now reuses the page's currently-attached domain when custom_domain is omitted — mirroring the editor's publish modal, a plain…",
42
- "vi": "publish_page nay tự động dùng lại domain đang gắn với trang khi bỏ trống custom_domain — giống cách modal publish của editor hoạt động, gọi…"
43
43
  }
44
44
  ]
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ // Typed stand-ins for z.any(): directory reviewers (and some clients) reject
3
+ // params whose JSON Schema has no `type`. Handlers still parse JSON strings.
4
+ // Fresh instance per use: a shared one makes zod-to-json-schema emit a `$ref`.
5
+ const jsonObject = () => z.record(z.any());
6
+ /** A JSON object, or a JSON string of one. */
7
+ export const objectOrJson = () => z.union([jsonObject(), z.string()]);
8
+ /** A JSON object, an array of them, or a JSON string of either. */
9
+ export const objectsOrJson = () => z.union([jsonObject(), z.array(jsonObject()), z.string()]);
@@ -6,6 +6,7 @@
6
6
  import { z } from "zod";
7
7
  import { sparseTemplate } from "../core/compact.js";
8
8
  import { text, warningsField, autoFixedField } from "../mcp/response.js";
9
+ import { objectOrJson } from "../mcp/params.js";
9
10
  export function registerGenerationTools(server, domain) {
10
11
  // 5) New element ------------------------------------------------------------
11
12
  server.tool("new_element", "Returns a default element node for a type in the SPARSE authoring shape (fresh id, both breakpoints' seeded styles, seeded specials). Emit elements exactly like this — fill in specials + top/left coordinates; OMIT properties/runtime/empty events/config (the server hydrates them from factory defaults on validate/persist).", {
@@ -33,8 +34,7 @@ export function registerGenerationTools(server, domain) {
33
34
  })));
34
35
  // 7) Validate page ----------------------------------------------------------
35
36
  server.tool("validate_page", "Validates a page source against the schema + semantic rules (unique ids, dangling event targets, children only on containers, missing field_name, top-level types) plus form-data bindings (duplicate field_name within one form, dangling option-event promoId / connectedSurvey / connectedForm / set_field_value targets). FIRST auto-fixes the layout defects that can be resolved deterministically (off-canvas boxes pulled on-canvas; elements below wrapped text pushed down to clear the spill — the same corrections create_page/add_section apply on save) and reports them in auto_fixed. Then returns errors (blocking — fix before persisting) and warnings (visible design defects — fix these too and re-validate to an empty list; only a demonstrably false positive may remain).", {
36
- page: z
37
- .any()
37
+ page: objectOrJson()
38
38
  .describe("The page source object { page:[...], settings:{} } OR a JSON string of it."),
39
39
  }, { title: "Validate Page Source", readOnlyHint: true, openWorldHint: false }, async ({ page }) => {
40
40
  // Hydrate sparse nodes (the model may omit boilerplate) before validating,
@@ -369,7 +369,7 @@ export function registerMediaTools(server, allowLocalFiles = true) {
369
369
  .boolean()
370
370
  .optional()
371
371
  .describe("Default FALSE — the call actually reads/downloads and uploads, returning hosted URLs. Set true to only preview the endpoint and entries that WOULD be processed, without any network or filesystem activity (local paths: reports whether the file exists and its size)."),
372
- }, { title: "Upload Images to Webcake", readOnlyHint: false, openWorldHint: true }, async ({ urls, in_folder, organization_id, dry_run }, extra) => {
372
+ }, { title: "Upload Images to Webcake", readOnlyHint: false, destructiveHint: false, openWorldHint: true }, async ({ urls, in_folder, organization_id, dry_run }, extra) => {
373
373
  const isDry = dry_run === true;
374
374
  const headers = extra?.requestInfo?.headers;
375
375
  const base = resolveApiBase(headers);
@@ -11,6 +11,7 @@
11
11
  */
12
12
  import { z } from "zod";
13
13
  import { text, warningsField, autoFixedField } from "../mcp/response.js";
14
+ import { objectOrJson, objectsOrJson } from "../mcp/params.js";
14
15
  import { readConfig, configFromHeaders } from "../persistence/config.js";
15
16
  import { buildRequestRedacted, buildUpdateRequestRedacted, buildAppendRequestRedacted, buildPublishRequestRedacted, buildPageApp, createPage, listOrganizations, listPages, searchPages, getPageSource, getPageVersion, updatePageSource, appendSection, publishPage, toPreviewUrl, } from "../persistence/webcake-client.js";
16
17
  import { putDraft, getDraft, updateDraft, deleteDraft } from "../persistence/draft-cache.js";
@@ -175,8 +176,7 @@ export function registerPersistenceTools(server, domain) {
175
176
  });
176
177
  // 9) Create page (persist) --------------------------------------------------
177
178
  server.tool("create_page", "Persists a page source to the configured Webcake backend: creates a NEW page, saves the source, then AUTO-PUBLISHES it (builds the rendered app on the build host + publishes via the editor's publish_html route) so the preview renders immediately — set publish:false to skip, and note the no-domain preview link still expires ~10 minutes after each publish (publish_page with custom_domain gives a permanent URL). A failed auto-publish never fails the create (result.publish says how to retry). Auto-fixes the deterministically-resolvable layout defects first (off-canvas boxes pulled on-canvas; elements below wrapped text pushed down to clear the spill; containers grown to fit) and reports them in auto_fixed — so the saved tree is corrected without a patch round-trip. Then validates. DEFAULTS to dry_run=true (validates, caches the source as draft_id, returns the HTTP request it WOULD send, token masked); dry_run=false to actually create. Accepts draft_id from a previous call (validation failure, dry_run, or a timed-out create) — re-runs from the cached source without re-sending the full JSON. Organization resolution on the real run (dry_run=false): (1) explicit organization_id wins; pass the string 'personal' to save without any org. (2) WEBCAKE_ORG_ID env / x-webcake-org-id header wins. (3) Otherwise list_organizations is called: 0 orgs or lookup fails → personal (no org); exactly 1 org → used automatically (result includes organization_auto_selected:true); 2+ orgs → returns ok:false with the org list and asks the caller to re-call with organization_id. Real writes need WEBCAKE_API_BASE + WEBCAKE_JWT.", {
178
- source: z
179
- .any()
179
+ source: objectOrJson()
180
180
  .optional()
181
181
  .describe("Page source { page, popup, settings, options, cartConfigs } (object or JSON string). Required unless draft_id is given. Author elements SPARSE — only id, type, responsive.<bp>.styles for BOTH breakpoints, specials, and real events; OMIT properties/runtime/empty events+children/per-breakpoint config — the server hydrates them from factory defaults (a full node also works)."),
182
182
  draft_id: z
@@ -518,8 +518,7 @@ export function registerPersistenceTools(server, domain) {
518
518
  // 12) Update page (edit existing) -------------------------------------------
519
519
  server.tool("update_page", "Overwrites an EXISTING page's source with an edited tree (source-only; re-render in the editor for preview/publish). Validates first. DEFAULTS to dry_run=true (validates, caches the source as draft_id, previews the request, token masked); dry_run=false to actually save. Accepts draft_id from a previous call (dry_run, or a timed-out/failed update) — re-runs from the cached source without re-sending the full JSON. Needs WEBCAKE_API_BASE + WEBCAKE_JWT.", {
520
520
  page_id: z.string().optional().describe("The page id to update (must be owned by the account). Required unless draft_id is given (the page_id is stored in the draft)."),
521
- source: z
522
- .any()
521
+ source: objectOrJson()
523
522
  .optional()
524
523
  .describe("The edited page source { page, popup, settings, options, cartConfigs } (object or JSON string). Required unless draft_id is given. The compacted tree from get_page can be edited and sent back AS-IS — sparse nodes are re-hydrated from factory defaults (a full tree also works)."),
525
524
  draft_id: z
@@ -698,8 +697,7 @@ export function registerPersistenceTools(server, domain) {
698
697
  const sectionLabel = (s) => s?.properties?.name ?? s?.specials?.name ?? s?.id ?? s?.type ?? "section";
699
698
  server.tool("add_section", "Appends one or more SECTIONS to an existing page WITHOUT re-sending the whole source — the incremental-build path that avoids large create_page payloads. The backend appends section(s) to the END of `page` server-side and rejects duplicate element ids, so the caller sends only the new section(s) (no whole-source get+put). DEFAULTS to dry_run=true (validates the section(s) + previews the request; caches the payload as draft_id so you never have to re-send sections between dry-run → real call); dry_run=false to actually append. On validation failure also returns a draft_id — call patch_page({ draft_id, patches, dry_run:false }) to fix ONLY the bad elements without rebuilding the batch. Needs WEBCAKE_API_BASE + WEBCAKE_JWT.", {
700
699
  page_id: z.string().describe("The page id to append to (from create_page or list_pages; must be owned by the account)."),
701
- sections: z
702
- .any()
700
+ sections: objectsOrJson()
703
701
  .optional()
704
702
  .describe("One section node, or an array of section nodes, to append to the END of `page` (object/array or JSON string). Each is a normal section element { id, type:'section', responsive, children, … } with a UNIQUE id; they stack vertically after the existing sections. Author SPARSE nodes — omit properties/runtime/empty events+children/per-breakpoint config; the server hydrates them from factory defaults. Required unless draft_id is supplied."),
705
703
  draft_id: z
@@ -986,8 +984,7 @@ export function registerPersistenceTools(server, domain) {
986
984
  server.tool("patch_page", "Edits a page by element id WITHOUT re-sending the whole source — the surgical-edit and fix-after-error path. Targets EITHER a live page (page_id) OR a cached draft source (draft_id). Draft sources come from: (a) create_page — failed validation or timed-out network call → patched/committed tree is CREATED as a new page once valid; (b) add_section dry_run or validation/network failure → patched/committed shell is APPENDED to the stored page once valid; (c) update_page or live-page patch_page — timed-out/failed network call → re-committed via updatePageSource. Send only a list of per-element ops; the MCP loads the source, applies them, validates the WHOLE merged tree (blocks on errors), and saves. Ops: {op:'update',id,type?,specials?,styles?:{desktop?,mobile?},config?:{desktop?,mobile?},events?,properties?} (shallow-merges; op defaults to 'update'; `type` fixes a wrong element type; update CANNOT delete an existing/stray key — schema 'additional properties' errors need op:'replace'), {op:'replace',id,element}, {op:'remove',id}, {op:'add',parent_id,element}. EMPTY/OMITTED patches with a draft_id = commit the cached draft as-is (skip apply, still validate, then honor dry_run) — this is the RETRY PATH after a timeout. Use this to fix the elements a failed create_page/update_page/add_section reported instead of rebuilding. DEFAULTS to dry_run=true (loads + merges + validates + previews, no write); dry_run=false to save. Needs WEBCAKE_API_BASE + WEBCAKE_JWT (a draft_id sections-patch only needs creds to actually append; a page_id patch reads the live page so needs creds even on dry_run).", {
987
985
  page_id: z.string().optional().describe("Edit a LIVE page by id (from create_page, list_pages, or find_pages; must be owned by the account). Provide page_id OR draft_id. For a sections or update draft_id you may also pass page_id here to override the stored page target."),
988
986
  draft_id: z.string().optional().describe("Commit or fix a CACHED source: from create_page (failed/timed-out → new page created once valid), add_section (dry_run or failure → sections appended), or update_page/live-page patch (timed-out/failed → updatePageSource retried). The originating tool's error/dry_run response returns draft_id. Provide page_id OR draft_id. Empty/omitted patches = commit the cached draft as-is (the RETRY PATH after a timeout)."),
989
- patches: z
990
- .any()
987
+ patches: objectsOrJson()
991
988
  .optional()
992
989
  .describe("One op object or an array of them (object/array or JSON string). Each targets an element by id: {op:'update',id,type?,specials?,styles?:{desktop?,mobile?},config?:{desktop?,mobile?},events?,properties?} merges fields into the element (op may be omitted; set `type` to fix a wrong element type; update MERGES — it cannot DELETE an existing/stray key, so 'must NOT have additional properties' errors need op:'replace' with a clean node); {op:'replace',id,element} swaps the node; {op:'remove',id} deletes it; {op:'add',parent_id,element} appends a child to a container. `element` may be a SPARSE node (id/type/styles/specials/events only) — the server hydrates omitted boilerplate from factory defaults. OMIT (or pass empty array) when draft_id is given and you just want to commit/retry the cached draft as-is."),
993
990
  force: z
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webcake-landing-mcp",
3
- "version": "1.5.4",
3
+ "version": "1.5.5",
4
4
  "description": "MCP server exposing Webcake landing-page element schemas + AI usage hints, and persisting LLM-generated page sources to a Webcake backend.",
5
5
  "mcpName": "io.github.vuluu2k/webcake-landing-mcp",
6
6
  "type": "module",