webcake-landing-mcp 1.0.68 → 1.0.70

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,18 @@
1
1
  [
2
+ {
3
+ "v": "1.0.70",
4
+ "d": "13/06/2026",
5
+ "type": "Changed",
6
+ "en": "upload_images default changed from dry_run:true to dry_run:false — the tool now uploads images and returns the hosted URL map on every call without…",
7
+ "vi": "Mặc định của upload_images được đổi từ dry_run:true thành dry_run:false — tool này nay upload ảnh và trả về bản đồ URL trong mọi lần gọi mà không…"
8
+ },
9
+ {
10
+ "v": "1.0.69",
11
+ "d": "12/06/2026",
12
+ "type": "Changed",
13
+ "en": "upload_images dry-run response now returns an action_required field (replacing the previous soft hint) that explicitly blocks the model from…",
14
+ "vi": "Phản hồi dry-run của upload_images nay trả về trường action_required (thay thế hint mềm trước đó) để chặn model lắp ráp trang hoặc dùng placeholder…"
15
+ },
2
16
  {
3
17
  "v": "1.0.68",
4
18
  "d": "12/06/2026",
@@ -26,19 +40,5 @@
26
40
  "type": "Added",
27
41
  "en": "validate_page now warns when a single-line text-block label sitting on a rounded rectangle (the badge/pill pattern) is vertically or horizontally…",
28
42
  "vi": "validate_page nay cảnh báo khi nhãn text-block một dòng đặt trên rectangle bo góc (kiểu badge/pill) bị lệch tâm theo chiều dọc hoặc ngang: sử dụng…"
29
- },
30
- {
31
- "v": "1.0.64",
32
- "d": "12/06/2026",
33
- "type": "Added",
34
- "en": "validate_page now warns when a rectangle element's svgMask is misconfigured: placed in specials or styles instead of responsive.<bp>.config (where…",
35
- "vi": "validate_page nay cảnh báo khi svgMask của phần tử rectangle bị cấu hình sai: đặt nhầm vào specials hoặc styles thay vì responsive.<bp>.config (nơi…"
36
- },
37
- {
38
- "v": "1.0.63",
39
- "d": "11/06/2026",
40
- "type": "Added",
41
- "en": "create_page now auto-publishes after a successful create: builds the rendered app via the build host and calls the editor's publish_html route so…",
42
- "vi": "create_page nay tự động publish sau khi tạo thành công: build rendered app qua build host rồi gọi route publish_html của editor để preview trang mới…"
43
43
  }
44
44
  ]
@@ -114,7 +114,7 @@ SECTION BUILD HINTS (apply to whichever sections the chosen archetype uses)
114
114
  RULES
115
115
  - Visible content goes in "specials" (text-block.specials.text, image-block.specials.src…), NEVER in "styles".
116
116
  - Colors as rgba(r,g,b,a). fontSize/borderWidth/top/left/width/height are NUMBERS (px). borderRadius is a STRING with CSS units ("8px", "50%", "16px 16px 0 0") — a bare number or unit-less string is auto-coerced to px by the server, but write the unit explicitly to avoid surprises.
117
- - IMAGES: a real landing page has images (hero/product shot, feature icons, about photo). SOURCE PRIORITY: (1) images the user supplied — including local file paths from their computer (pass the path directly in upload_images 'urls'; NEVER upload a user's local file to a third-party host like catbox or imgur to obtain a URL first) — or that exist in the reference HTML/URL (ingest AST images/background_images/og_image) → re-host them via upload_images and use those EXACT images in their slots — never swap them for stock photos; (2) only for slots with NO source image → call search_images with a short English subject (e.g. 'fresh coffee cup', 'modern office team') and put a returned URL into image-block specials.src — use src.large for a hero/banner, src.medium for a card/thumb (avg_color helps pick a matching section background); (3) if search_images returns ok:false, is unreachable, or has NO photo that fits the slot → find a real image YOURSELF using whatever web search/fetch capability you have (the brand's own site, the product page, a free-to-use source), then re-host it via upload_images and use the returned URL; (4) a PLACEHOLDER is the LAST resort, ONLY after (2) AND (3) both failed — sized to the box: "https://placehold.co/<width>x<height>". Never jump straight from a failed search to a placeholder without trying (3). NEVER leave src empty — it renders blank on the live page. The server automatically derives styles.background from specials.src on every expand (create/update/validate) using the editor's exact format: 'center center/ cover no-repeat scroll content-box url(<src>) border-box' — you do NOT need to set styles.background manually; if you do hand-write it, it must contain url(...). A SECTION background may layer a gradient overlay over an image: 'linear-gradient(...), center center/ cover no-repeat scroll content-box url(<src>) border-box' — the server canonicalises any url() layer into that exact editor shorthand on expand (other url() formats survive the first save but get mangled to 'undefined/ undefined/ …' the moment the page is edited in the Webcake editor). gallery.media = array of OBJECTS {type:'image', link:'<real-or-placeholder-url>', linkVideo:'', typeVideo:'youtube', imageCompression:true} (NOT plain URL strings — the gallery reads item.link); video.specials.img = a poster image (real photo, else placeholder). Do NOT set a flat (no url()) styles.background on a video element — it suppresses the poster image.
117
+ - IMAGES: a real landing page has images (hero/product shot, feature icons, about photo). SOURCE PRIORITY: (1) images the user supplied — including local file paths from their computer (pass the path directly in upload_images 'urls'; NEVER upload a user's local file to a third-party host like catbox or imgur to obtain a URL first) — or that exist in the reference HTML/URL (ingest AST images/background_images/og_image) → re-host them via upload_images and use those EXACT images in their slots — never swap them for stock photos (upload_images UPLOADS BY DEFAULT and returns the images map — batch >20 entries into multiple calls and WAIT for the returned map before assembling the page; a slot whose upload succeeded must use the hosted URL, never a placeholder; dry_run:true is only an explicit no-op preview that uploads nothing); (2) only for slots with NO source image → call search_images with a short English subject (e.g. 'fresh coffee cup', 'modern office team') and put a returned URL into image-block specials.src — use src.large for a hero/banner, src.medium for a card/thumb (avg_color helps pick a matching section background); (3) if search_images returns ok:false, is unreachable, or has NO photo that fits the slot → find a real image YOURSELF using whatever web search/fetch capability you have (the brand's own site, the product page, a free-to-use source), then re-host it via upload_images and use the returned URL; (4) a PLACEHOLDER is the LAST resort, ONLY after (2) AND (3) both failed — sized to the box: "https://placehold.co/<width>x<height>". Never jump straight from a failed search to a placeholder without trying (3). NEVER leave src empty — it renders blank on the live page. The server automatically derives styles.background from specials.src on every expand (create/update/validate) using the editor's exact format: 'center center/ cover no-repeat scroll content-box url(<src>) border-box' — you do NOT need to set styles.background manually; if you do hand-write it, it must contain url(...). A SECTION background may layer a gradient overlay over an image: 'linear-gradient(...), center center/ cover no-repeat scroll content-box url(<src>) border-box' — the server canonicalises any url() layer into that exact editor shorthand on expand (other url() formats survive the first save but get mangled to 'undefined/ undefined/ …' the moment the page is edited in the Webcake editor). gallery.media = array of OBJECTS {type:'image', link:'<real-or-placeholder-url>', linkVideo:'', typeVideo:'youtube', imageCompression:true} (NOT plain URL strings — the gallery reads item.link); video.specials.img = a poster image (real photo, else placeholder). Do NOT set a flat (no url()) styles.background on a video element — it suppresses the poster image.
118
118
  - CONTRAST (check EVERY text element against the band it sits on, especially SATURATED / mid-tone bands like yellow, orange, teal, pink — there "light vs dark text" is not obvious, so decide by the band's luminance): light bands → near-black text (e.g. rgba(20,30,25,1)); dark bands → near-white text; a saturated/mid-tone band → whichever of near-black or near-white actually reads (for a bright yellow/amber band that means DARK text, not white/grey). NEVER use muted-grey, low-alpha (alpha < ~0.85), or near-white text on a colored band — that is exactly what makes labels look faded/sunken. Muted-grey is ONLY for secondary text on a white/very-light band. Icons and their captions follow the SAME rule as the text beside them.
119
119
  - movable:false for section/slide/grid-item/popup; otherwise true. runtime is always {}.
120
120
  - Every form input MUST have a unique specials.field_name.
@@ -146,7 +146,7 @@ WORKFLOW (recommended)
146
146
  1. Call get_generation_guide (this) once, then new_page_skeleton for the top-level shape.
147
147
  2. For each element type you'll use, call get_element to learn its specials & see an example.
148
148
  3. Optionally call new_element to get a correct skeleton, then fill specials + coordinates.
149
- 3b. For every image the page needs (hero, product, about, feature, gallery): if the slot has a source image (user-supplied — including local file paths from their computer, pass the path directly in upload_images 'urls'; NEVER relay the user's local file through a third-party host like catbox or imgur — or from the reference HTML/URL), upload_images it and use the returned Webcake URL; otherwise call search_images and put a returned URL into specials.src / gallery item.link. If search_images fails or has no fitting photo, find a real image yourself (web search/fetch → upload_images). Use placehold.co ONLY as the last resort when both search_images AND your own search failed.
149
+ 3b. For every image the page needs (hero, product, about, feature, gallery): if the slot has a source image (user-supplied — including local file paths from their computer, pass the path directly in upload_images 'urls'; NEVER relay the user's local file through a third-party host like catbox or imgur — or from the reference HTML/URL), upload_images it (uploads by default — wait for the images map BEFORE assembling the page) and use the returned Webcake URL; otherwise call search_images and put a returned URL into specials.src / gallery item.link. If search_images fails or has no fitting photo, find a real image yourself (web search/fetch → upload_images). Use placehold.co ONLY as the last resort when both search_images AND your own search failed.
150
150
  4. Assemble { page, popup, settings, options, cartConfigs }.
151
151
  5. Call validate_page and fix every error AND every warning — warnings are visible defects (text spilling onto the element below, off-canvas boxes, empty bands at a section's bottom, missing field_name, dead event targets), not advisory polish. Re-validate until the warning list is empty; only a demonstrably false positive may remain (tell the user which and why).
152
152
  6. To save: call list_organizations. If the account has EXACTLY ONE organization, create_page will auto-select it — no need to ask. If there are MULTIPLE organizations, show them to the user and ask which to use (highlight is_default as the suggested default); pass the chosen organization_id to create_page. If the user explicitly wants to save without any organization, pass organization_id:"personal". Then create_page (dry_run first, then dry_run:false). Note: create_page itself enforces this — it refuses to guess between multiple orgs and returns the org list asking you to pick.
@@ -37,7 +37,7 @@ MODEL (essentials):
37
37
  - PREMIUM CRAFT (read "sang"): generous whitespace (don't cram; ~48–72px above each band's first element, ≥16–24px between elements); clear type scale (H1 40–56 / body 16–18, big jump); ONE accent used sparingly + neutrals; snap spacing to an 8px grid; reuse the same content width / margin / card+button radius across sections.
38
38
  - STICKY HEADER: a sticky/fixed header (config.sticky) OVERLAYS the page — it does NOT push sections below it down. Offset the first section's top content DOWN by the header height (~60–72px) so nothing hides behind it, and do NOT duplicate the shop name in both the header and the top of the hero. A non-sticky header stacks normally and needs no offset.
39
39
  - Visible content lives in specials (text, src, field_name…), never in styles. Colors as rgba(). Animation in config.animation={name,delay,duration,repeat}. Form inputs need a unique specials.field_name (use canonical keys: full_name, phone_number, email, address, quantity).
40
- - IMAGES: include them (hero/product, feature icons, about photo). SOURCE PRIORITY: (1) images the user supplied (including local file paths from their computer — pass the path directly in upload_images urls; NEVER upload a user's local file to a third-party host like catbox or imgur first) or that exist in the reference HTML/URL → re-host via upload_images and use those exact images; (2) only for slots with NO source image → call search_images with a short English subject (e.g. 'fresh coffee cup') and put a returned URL (src.large for a hero/banner, src.medium for a card/thumb) into the image-block specials.src; it works out of the box (a shared proxy supplies images); (3) if search_images returns ok:false / is unreachable / has no fitting photo → find a real image YOURSELF using whatever web search/fetch capability you have (brand site, product page, free-to-use source) and re-host it via upload_images; (4) a PLACEHOLDER sized to the box (https://placehold.co/<width>x<height>) is the LAST resort, only after (2) AND (3) both failed. (gallery.media = array of OBJECTS {type:'image',link:'<url>',linkVideo:'',typeVideo:'youtube',imageCompression:true} — NOT plain strings, the gallery reads item.link; video.specials.img = poster). NEVER leave src empty (renders blank). Ensure text contrasts with its section background.
40
+ - IMAGES: include them (hero/product, feature icons, about photo). SOURCE PRIORITY: (1) images the user supplied (including local file paths from their computer — pass the path directly in upload_images urls; NEVER upload a user's local file to a third-party host like catbox or imgur first) or that exist in the reference HTML/URL → re-host via upload_images and use those exact images (upload_images UPLOADS BY DEFAULT and returns the images map — batch >20 into multiple calls and WAIT for the map before building; a slot whose upload succeeded must use the hosted URL, never a placeholder; dry_run:true is only an explicit no-op preview); (2) only for slots with NO source image → call search_images with a short English subject (e.g. 'fresh coffee cup') and put a returned URL (src.large for a hero/banner, src.medium for a card/thumb) into the image-block specials.src; it works out of the box (a shared proxy supplies images); (3) if search_images returns ok:false / is unreachable / has no fitting photo → find a real image YOURSELF using whatever web search/fetch capability you have (brand site, product page, free-to-use source) and re-host it via upload_images; (4) a PLACEHOLDER sized to the box (https://placehold.co/<width>x<height>) is the LAST resort, only after (2) AND (3) both failed. (gallery.media = array of OBJECTS {type:'image',link:'<url>',linkVideo:'',typeVideo:'youtube',imageCompression:true} — NOT plain strings, the gallery reads item.link; video.specials.img = poster). NEVER leave src empty (renders blank). Ensure text contrasts with its section background.
41
41
 
42
42
  - PREVIEW vs PUBLISH: for review share the EDITOR url (the builder renders the raw source). The editor_url SIGNS THE BROWSER IN automatically (it routes through the builder's /transport with the account token), so it works even when the user isn't logged in — but for the same reason it must go to the PAGE OWNER ONLY, never into anything public. create_page AUTO-PUBLISHES on success (builds the rendered app + publish_html), so a fresh page's preview_url renders right away — but the preview host (preview.localhost:5800 local / staging.webcake.me staging / www.webcake.me prod) only serves it for ~10 MINUTES after the last publish, then shows "Preview page is expired". The EDIT routes (update_page/add_section/patch_page) store source only — after finishing a round of edits, run publish_page({ page_id, dry_run:false }) to rebuild the rendered app (else the preview shows the STALE pre-edit build). ONLY a custom_domain (publish_page({ page_id, custom_domain, dry_run:false })) gives a permanent public URL; without one the page has just the ephemeral preview link — say so and suggest attaching a domain the user already points at Webcake.
43
43
 
@@ -989,8 +989,8 @@ const LADI_STYLE_KEYS = new Set([
989
989
  "opacity", "transform", "box-shadow", "fill",
990
990
  ]);
991
991
  const LADI_CONFIG_KEY_RE = /countdown_type|countdown_minute|thankyou_value|form_config_id|show_popup_welcome_page|delay_popup_welcome_page|autoplay|max_turn|time_show|time_delay/;
992
- const MAX_CANVAS_ELEMENTS = 500;
993
- const CANVAS_SIZE_CAP = 80_000;
992
+ const MAX_CANVAS_ELEMENTS = 1000;
993
+ const CANVAS_SIZE_CAP = 1_000_000;
994
994
  /** Shedding step 1 keeps only these style keys — the look-defining minimum for a rebuild. */
995
995
  const CANVAS_CORE_STYLE_KEYS = ["font-family", "font-size", "font-weight", "color", "text-align", "background-color", "border-radius", "fill"];
996
996
  const CANVAS_SVG_CAP = 1200;
@@ -222,7 +222,7 @@ export function registerMediaTools(server, allowLocalFiles = true) {
222
222
  return text({ queries: out });
223
223
  });
224
224
  // 14) Upload images to Webcake -----------------------------------------------
225
- server.tool("upload_images", "Converts external image URLs (typically collected from ingest_html/ingest_url results), data: URIs, or LOCAL FILE PATHS from the user's computer into Webcake-hosted URLs (statics.pancake.vn) by reading/downloading each image and re-uploading it to the Webcake backend via multipart upload (200 MB backend limit). Use this whenever the page is built from a reference HTML/URL (BOTH intents — adapt AND clone), the user supplies their own image URLs, OR the user provides local image files from their machine — pass the path directly in `urls`; NEVER upload a user's local file to a third-party host (catbox, imgur, transfer.sh…) to obtain a URL first. The returned URLs go directly into specials.src — same as search_images results. Processes up to 20 entries per call in parallel, with a 200 MB per-image cap. No Webcake credentials required (the upload endpoint is public). DEFAULTS to dry_run=true (returns a preview of what would be processed, no network calls); set dry_run=false to actually upload. Use search_images instead when you need stock photos. Local file paths are only permitted when the MCP server runs locally (stdio mode); on the remote HTTP transport they are rejected per-entry.", {
225
+ server.tool("upload_images", "Converts external image URLs (typically collected from ingest_html/ingest_url results), data: URIs, or LOCAL FILE PATHS from the user's computer into Webcake-hosted URLs (statics.pancake.vn) by reading/downloading each image and re-uploading it to the Webcake backend via multipart upload (200 MB backend limit). Use this whenever the page is built from a reference HTML/URL (BOTH intents — adapt AND clone), the user supplies their own image URLs, OR the user provides local image files from their machine — pass the path directly in `urls`; NEVER upload a user's local file to a third-party host (catbox, imgur, transfer.sh…) to obtain a URL first. The returned URLs go directly into specials.src — same as search_images results. Processes up to 20 entries per call in parallel, with a 200 MB per-image cap. No Webcake credentials required (the upload endpoint is public). UPLOADS BY DEFAULT (dry_run defaults to FALSE unlike the page-persistence tools, this touches no account data, so the default is the real upload): the call downloads/reads each entry, uploads it, and returns the images map (original URL → hosted URL); WAIT for that map before assembling the page and never fall back to a placeholder for a slot whose upload succeeded. Pass dry_run:true only to preview what would be processed without any network/filesystem activity. Use search_images instead when you need stock photos. Local file paths are only permitted when the MCP server runs locally (stdio mode); on the remote HTTP transport they are rejected per-entry.", {
226
226
  urls: z
227
227
  .array(z.string())
228
228
  .min(1)
@@ -235,9 +235,9 @@ export function registerMediaTools(server, allowLocalFiles = true) {
235
235
  dry_run: z
236
236
  .boolean()
237
237
  .optional()
238
- .describe("Default TRUEreturn a preview of the endpoint and entries that WOULD be processed, without any network or filesystem activity (local paths: reports whether the file exists and its size). Set false to actually read/download and upload."),
238
+ .describe("Default FALSEthe 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)."),
239
239
  }, { title: "Upload Images to Webcake", readOnlyHint: false, openWorldHint: true }, async ({ urls, dry_run }, extra) => {
240
- const isDry = dry_run !== false;
240
+ const isDry = dry_run === true;
241
241
  const base = resolveApiBase(extra?.requestInfo?.headers);
242
242
  // Stdio transport: extra.requestInfo is undefined (no HTTP request headers).
243
243
  // HTTP transport: extra.requestInfo is always present.
@@ -269,7 +269,7 @@ export function registerMediaTools(server, allowLocalFiles = true) {
269
269
  dry_run: true,
270
270
  endpoint: `${base}/external/upload_file`,
271
271
  urls_to_upload: urlsInfo,
272
- hint: "Re-call with dry_run:false to actually read/download and upload these images.",
272
+ action_required: "DRY RUN ONLY — nothing was uploaded and NO hosted URLs exist yet. Do NOT build the page and do NOT fall back to placeholders: re-call upload_images WITHOUT dry_run (uploads by default; batch >20 entries into multiple calls) and WAIT for the returned images map before filling any specials.src / gallery link / background.",
273
273
  });
274
274
  }
275
275
  // Process each entry in parallel; per-entry failures don't fail the whole call.
@@ -381,6 +381,13 @@ export function registerMediaTools(server, allowLocalFiles = true) {
381
381
  else
382
382
  failed++;
383
383
  }
384
- return text({ ok: true, images, uploaded, failed });
384
+ return text({
385
+ ok: true,
386
+ images,
387
+ uploaded,
388
+ failed,
389
+ usage: "Put images[<original>].url into EVERY element that used <original> (image specials.src, gallery item.link, section/box background url(...)). Slots whose entry uploaded ok MUST use the hosted URL — never a placeholder. Only for entries marked ok:false, fall back to the image-source chain (search_images → your own web search + re-upload → placeholder LAST)." +
390
+ (failed > 0 ? ` ${failed} entr${failed === 1 ? "y" : "ies"} failed — handle them via that fallback chain now.` : ""),
391
+ });
385
392
  });
386
393
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webcake-landing-mcp",
3
- "version": "1.0.68",
3
+ "version": "1.0.70",
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",