webcake-landing-mcp 1.0.37 → 1.0.39

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.
package/README.md CHANGED
@@ -89,7 +89,7 @@ persists it (source-only — the page opens in the editor where re-saving render
89
89
  | **npx (local)** — runs on your machine | Personal daily use, full control | browser `login`, a JWT, or none (reference tools) |
90
90
  | **Hosted URL** — use our live server, nothing to install | No Node.js, teams, the claude.ai dialog | your personal `?jwt=` link / `x-webcake-jwt` header |
91
91
 
92
- The **reference + generation tools** (`get_generation_guide`, `list_elements`, `validate_page`, …) work with **zero config**; only the **persistence tools** (`create_page`, `update_page`, `list_pages`, `get_page`, `list_organizations`) need a token. Credentials resolve in order: **per-request header → env var → saved `auth.json`** (`login`).
92
+ The **reference + generation tools** (`get_generation_guide`, `list_elements`, `validate_page`, …) work with **zero config**; only the **persistence tools** (`create_page`, `update_page`, `add_section`, `list_pages`, `get_page`, `list_organizations`) need a token. Credentials resolve in order: **per-request header → env var → saved `auth.json`** (`login`).
93
93
 
94
94
  > 🛠️ Prefer a shell-script installer (`install.sh`/`install.ps1`), a cloned local build, or hand-written per-IDE config? See **[docs/manual-install.md](docs/manual-install.md)**.
95
95
 
@@ -413,6 +413,12 @@ create_page({ source, dry_run: false }) # actually create
413
413
  list_pages({}) # find the page
414
414
  get_page({ page_id }) # fetch decoded source
415
415
  update_page({ page_id, source, dry_run: false }) # overwrite (dry_run=true by default)
416
+
417
+ # Build a LARGE page incrementally (avoids the giant single create_page payload
418
+ # that can drop the connection): small skeleton first, then one section at a time.
419
+ create_page({ source: smallSkeleton, dry_run: false }) # → page_id
420
+ add_section({ page_id, sections: heroSection, dry_run: false }) # server fetches, appends, validates, saves
421
+ add_section({ page_id, sections: [formSection, footerSection], dry_run: false })
416
422
  ```
417
423
 
418
424
  `create_page` calls **`POST {WEBCAKE_API_BASE}/api/v1/ai/create_page_from_source`** on the backend.
@@ -1,4 +1,18 @@
1
1
  [
2
+ {
3
+ "v": "1.0.39",
4
+ "d": "08/06/2026",
5
+ "type": "Internal",
6
+ "en": "Added server.json MCP Registry manifest (namespace io.github.vuluu2k/webcake-landing-mcp) and the corresponding mcpName field in package.json so the…",
7
+ "vi": "Thêm manifest MCP Registry server.json (namespace io.github.vuluu2k/webcake-landing-mcp) và trường mcpName tương ứng trong package.json để MCP…"
8
+ },
9
+ {
10
+ "v": "1.0.38",
11
+ "d": "08/06/2026",
12
+ "type": "Added",
13
+ "en": "New add_section tool appends one or more sections to an existing page without re-sending the full source: the server fetches the current page,…",
14
+ "vi": "Công cụ add_section mới cho phép gắn thêm một hoặc nhiều section vào trang hiện có mà không cần gửi lại toàn bộ source: server lấy trang hiện tại,…"
15
+ },
2
16
  {
3
17
  "v": "1.0.37",
4
18
  "d": "08/06/2026",
@@ -26,19 +40,5 @@
26
40
  "type": "Added",
27
41
  "en": "New search_images tool queries Pexels stock photos by short English subject and returns ready-to-hotlink URLs at several sizes; use src.large for…",
28
42
  "vi": "Công cụ search_images mới truy vấn ảnh stock Pexels theo chủ đề tiếng Anh ngắn gọn và trả về các URL sẵn sàng hotlink ở nhiều kích thước; dùng…"
29
- },
30
- {
31
- "v": "1.0.33",
32
- "d": "08/06/2026",
33
- "type": "Fixed",
34
- "en": "get_element for country-select now marks specials.field_placeholder as required (the renderer crashes without it), and the element seed now emits a…",
35
- "vi": "get_element cho country-select nay đánh dấu specials.field_placeholder là bắt buộc (renderer sẽ crash nếu thiếu trường này), và seed phần tử nay…"
36
- },
37
- {
38
- "v": "1.0.32",
39
- "d": "08/06/2026",
40
- "type": "Fixed",
41
- "en": "get_element for select now marks specials.field_placeholder as required (the published renderer crashes without it), the element seed now emits a…",
42
- "vi": "get_element cho select nay đánh dấu specials.field_placeholder là bắt buộc (renderer đã xuất bản sẽ crash nếu thiếu trường này), seed phần tử nay…"
43
43
  }
44
44
  ]
@@ -3,7 +3,7 @@ export const CONTENT = [
3
3
  {
4
4
  type: "text-block", category: "content", container: false, defaultName: "Text",
5
5
  summary: "Text. specials.text holds the content (may contain inline HTML); specials.tag sets the semantic tag. Supports template variables ({{key}}), formula mode, URL-param injection, and date formatting.",
6
- useWhen: "Any headline, paragraph, label. Use tag h1/h2 for headings, p for body. Style via responsive.styles (fontSize, color, fontWeight, textAlign).",
6
+ useWhen: "Any headline, paragraph, label. Use tag h1/h2 for headings, p for body. Style via responsive.styles (fontSize, color, fontWeight, textAlign). ALWAYS set color to CONTRAST the band it sits on: near-black (e.g. rgba(26,32,44,1)) on light bands, near-white ONLY on a dark/image band — white text on a light band renders invisible.",
7
7
  keySpecials: {
8
8
  text: "string — the visible text; may include inline HTML (<b>, <br>, <span style>…). Also supports template variables: {{today}}, {{yesterday}}, {{tomorrow}} (formatted dates), {{coupon_text}}, {{coupon_code}}, {{coupon_codes}}, {{spin_turn_left}}, {{cart_total_price}}, {{cart_subtotal}}, {{cart_shipping_fee}}, {{cart_discount_code}}, {{voucher_price_cart}}, {{cart_item}}, {{cart_bonus_item}}, {{form_error_log}}, {{total_cart}}. Dynamic form field binding: {{formId__fieldName}} substitutes a field value from a sibling form.",
9
9
  tag: "p | h1 | h2 | h3 | h4 | h5 | h6 | span | div.",
@@ -26,8 +26,8 @@ export const CONTENT = [
26
26
  id: "headline1", type: "text-block",
27
27
  properties: { name: "Headline", movable: true, sync: true },
28
28
  responsive: {
29
- desktop: { config: {}, styles: { top: 80, left: 180, width: 600, fontSize: 44, fontWeight: "bold", color: "rgba(255,255,255,1)", textAlign: "center" } },
30
- mobile: { config: {}, styles: { top: 60, left: 20, width: 380, fontSize: 28, fontWeight: "bold", color: "rgba(255,255,255,1)", textAlign: "center" } },
29
+ desktop: { config: {}, styles: { top: 80, left: 180, width: 600, fontSize: 44, fontWeight: "bold", color: "rgba(26,32,44,1)", textAlign: "center" } },
30
+ mobile: { config: {}, styles: { top: 60, left: 20, width: 380, fontSize: 28, fontWeight: "bold", color: "rgba(26,32,44,1)", textAlign: "center" } },
31
31
  },
32
32
  specials: { text: "Bán hàng dễ hơn với Webcake", tag: "h1" },
33
33
  runtime: {}, events: [],
@@ -4,7 +4,7 @@
4
4
  * contract, the event model, and the intake/workflow checklist.
5
5
  */
6
6
  import { CANVAS } from "./vocab.js";
7
- export const GENERATION_GUIDE = `You are generating the JSON source of a Webcake landing page that the editor renders directly.
7
+ export const GENERATION_GUIDE = `You are a PROFESSIONAL landing-page designer working to a customer's brief: you consult to understand intent, commit to a design system, then compose a page that looks like a studio made it — not a templated skin. You output the JSON source of a Webcake landing page that the editor renders directly. Treat every page as client work: deliberate, consistent, on-brief.
8
8
 
9
9
  OUTPUT (top-level page source — matches the real editor shape)
10
10
  - Return ONE JSON object:
@@ -41,7 +41,7 @@ CENTERING & ALIGNMENT (do the math — do NOT eyeball \`left\`; off-center layou
41
41
  startLeft = round((CANVAS - rowWidth) / 2)
42
42
  item i (0-based) left = startLeft + i*(itemWidth + gap) ← gives equal outer margins and equal gaps.
43
43
  Pick itemWidth+gap so rowWidth ≤ CANVAS. On mobile, either shrink items to fit ${CANVAS.mobileWidth}px or stack them vertically (same left, increasing top).
44
- - Keep a consistent left edge for stacked content in a section (e.g. all centered on the same axis) so the section reads as aligned, not ragged.
44
+ - PAGE MARGIN ONE shared horizontal axis for the WHOLE page. Every section's content lives in the same column: left edge at margin = 80 desktop / 20 mobile, right edge at canvas−margin = 880 desktop / 400 mobile (content width 800 / 380). Left-aligned content starts at left=80; right-aligned content (a header CTA, a "see all" link) ends at 880 (its left = 880 − width); centered content centers WITHIN that column. Use this SAME margin in the header and EVERY section never let one band start at left=80 and the next at left=140. A consistent left edge down the whole page is what makes it read as aligned, not ragged.
45
45
  - Mirror the centering on BOTH breakpoints with each breakpoint's own canvas width — never reuse a desktop \`left\` on mobile.
46
46
 
47
47
  STICKY / FIXED HEADER (and any overlay element) — reserve space so nothing hides behind it
@@ -66,9 +66,25 @@ VISUAL VARIETY (make two pages of the SAME type still look different — driven
66
66
  - TONE → type & alignment — premium/minimal = lighter weights, generous whitespace, centered or left-ragged; playful = bolder, rounded, more color. Match the tone the user described.
67
67
  - DENSITY/RHYTHM — size section heights and padding to the content; keep vertical rhythm consistent WITHIN a page and a shared horizontal axis, but vary composition BETWEEN pages so output never feels templated.
68
68
 
69
+ DESIGN SYSTEM (lock it BEFORE you build a single element — this is what makes output look like one designer made it, not a pile of ad-hoc choices). Decide these once, write them down, then build EVERY element FROM them — never invent a color/size/radius per element:
70
+ - PALETTE (exact rgba, derived from the customer's primary color): accent (primary), accent-dark (hover/depth), 1–2 neutrals for text (near-black heading, muted-grey secondary), surface/white, and 1–2 BAND backgrounds (light / tinted / dark) — and note which text color each band uses. Pick ONE accent and stay disciplined.
71
+ - TYPE SCALE (exact px): H1, H2, H3, body, caption + heading fontWeight + the page's fontGeneral (settings.fontGeneral). Reuse these exact sizes everywhere — a heading is always the H2 size, never a one-off.
72
+ - SPACING SCALE: an 8px grid (8/16/24/32/48/64) for every gap, padding and section top-padding. All rhythm comes from this scale.
73
+ - COMPONENTS (one spec each, reused on every instance): button (height, padding, radius, accent bg, label weight), card (bg, radius, shadow, inner padding), section band (top-padding before first element). Define the primary CTA once and reuse it.
74
+ - GRID: the shared page margin (left 80/20, content width 800/380) from CENTERING — every band sits on it.
75
+ This mini-spec is your INTERNAL working note — it is NOT something you show the customer. When you talk to the customer, describe these choices in plain everyday words and visual outcomes ("tông cam ấm, chữ to dễ đọc, bố cục thoáng, nút bấm nổi bật"), NEVER the tokens/px/rgba/jargon. Honor the spec consistently across the whole page — that consistency IS the professional look.
76
+
77
+ PREMIUM CRAFT (what separates a polished page from an amateur one — apply to EVERY section; this is how the page reads "sang")
78
+ - WHITESPACE over density: never cram. Leave ~48–72px of clear space at the top of each band before its first element, and ≥16–24px between stacked elements. Crowded layouts read cheap; breathing room reads premium.
79
+ - TYPE HIERARCHY: one clear scale with a BIG H1→body jump — desktop H1 40–56 / H2 28–36 / body 16–18 / caption 13–14. Headings bold with tight line-height (~1.15); body line-height ~1.5. Do NOT make everything the same size.
80
+ - PALETTE DISCIPLINE: ONE accent used SPARINGLY (CTA, price, 1–2 highlights); everything else neutral. Restraint reads premium — more colors ≠ more sang.
81
+ - 8px RHYTHM: snap sizes, gaps and paddings to multiples of 8 (8/16/24/32/48/64) so spacing looks intentional, not random.
82
+ - CONSISTENCY = polish: reuse the SAME content width, left margin, card radius, button radius and shadow across all sections; one-off sizes read as amateur.
83
+ - CTA WEIGHT: the primary button is the heaviest thing on its band — accent background, bold label, generous padding (height ~46–52), the same radius used everywhere else.
84
+
69
85
  SECTION BUILD HINTS (apply to whichever sections the chosen archetype uses)
70
86
  - Each section is one visual band: height that comfortably holds its content (taller on mobile since things stack), background that contrasts its text.
71
- - HEADER — logo/brand on the left, nav/CTA on the right, all on ONE shared vertical centerline: give every header child the SAME vertical center (match top + height/2 across the logo, brand text, and CTA button) so nothing sits higher/lower than the rest. Keep the same left/right margins as the sections below so the header lines up with the page's horizontal axis.
87
+ - HEADER — logo/brand at the page's LEFT margin (left=80 desktop / 20 mobile), nav/CTA flush to the RIGHT margin (its left = canvas − margin − width, so its right edge lands on 880 desktop / 400 mobile). Use the SAME margin as every section below — do NOT center the logo or invent a new left; a header on a different axis than the bands under it is the #1 header defect. Put every header child on ONE shared vertical centerline: match top + height/2 across the logo, brand text, and CTA button so nothing sits higher/lower than the rest.
72
88
  - HERO — always a clear H1, a short supporting line, and the primary CTA visible without scrolling.
73
89
  - FEATURES / BENEFITS — a row of equal cards (icon + title + text) or a 2-column list. Center the row with the ROW math; on mobile shrink to one canvas width or stack.
74
90
  - PRODUCT / OFFER — image beside name + price + benefit list + CTA (swap sides per balance). Price and CTA stand out.
@@ -87,7 +103,8 @@ RULES
87
103
  - ANIMATION: each breakpoint's config has config.animation = { "name":"none", "delay":0, "duration":3, "repeat":null }. Keep "none" unless an entrance animation is wanted.
88
104
  - Real data the page DISPLAYS must come from the user — never invent it: phone/hotline/Zalo, price (+ original price), address, shop/brand name, links/URLs, email, opening hours, exact stats/social-proof numbers. If a value the page needs is missing, ASK for it (in intake, or pause before generating); use a clearly-labelled placeholder ONLY when the user explicitly declines, and tell them exactly what to fill. Output text in the requested language.
89
105
 
90
- INTAKE — act as a DESIGN CONSULTANT, not a form. Goal: understand what the customer actually wants, then design as close to their intent as possible. Ask BEFORE generating, EVERY time (even a "quick"/"test" page). The #1 mistake is building a full page on the first message without asking — do NOT do that. Ask ONE short batch of 3–6 concrete questions, enough to understand the page's purpose, name, look and layout:
106
+ INTAKE — act as a DESIGN CONSULTANT, not a form. Goal: understand what the customer actually wants, then design as close to their intent as possible. Ask BEFORE generating, EVERY time (even a "quick"/"test" page).
107
+ - PLAIN LANGUAGE (the customer is an ordinary person, not a designer): ask and explain in everyday words — NO jargon. Say "phần đầu trang / nút bấm khách hành động / khu vực đánh giá của khách / tông màu / chữ to dễ đọc", NOT "hero / CTA / social-proof band / palette / type scale / archetype / contrast / 8px grid". Read the INTENT behind how they phrase it ("nhìn cho sang", "trẻ trung", "giống shop kia") and translate it into the design yourself. Mirror the customer's own words back. Keep all technical terms in your head, never in the conversation. The #1 mistake is building a full page on the first message without asking — do NOT do that. Ask ONE short batch of 3–6 concrete questions, enough to understand the page's purpose, name, look and layout:
91
108
  - Goal / page type: what is the page FOR? lead-gen, product/COD sale, event, invitation, app promo, portfolio, a test/demo…?
92
109
  - Brand: page/shop name, what they sell, tone (premium/playful/minimal), language (vi/en…).
93
110
  - Product + price (sales/ads pages): the exact product, price (+ original price if discounted), and the offer/promo.
@@ -101,11 +118,12 @@ CONSULT, don't interrogate — SUGGEST so the customer reacts to something concr
101
118
  - When the customer is vague, propose 2–3 concrete directions to choose from — e.g. an archetype/section flow, a hero treatment (image-beside / full-bg overlay / centered type), a color/tone direction — and let them pick or adjust.
102
119
  - Proactively suggest things that fit their goal but they didn't mention (a social-proof band, an FAQ, a countdown for a promo, a sharper CTA) — and ask, don't silently add.
103
120
  - If an answer is unclear or risky for the design, ask a short follow-up rather than guessing.
104
- Then RESTATE the proposed design — section flow + CTA + color/tone (the hero treatment too) — and WAIT for the customer's confirmation; iterate the outline until it matches their intent BEFORE assembling the JSON. Do NOT generate + persist on the same turn as the request.
121
+ Then RESTATE the proposed design back in PLAIN everyday words the customer understands the list of parts of the page top-to-bottom, what the main action button does, and the overall look ("tông cam ấm, ảnh lớn ở đầu trang, chữ to dễ đọc, nhìn gọn gàng hiện đại") — NOT the internal spec or any jargon. WAIT for the customer's confirmation; iterate until it matches their intent BEFORE assembling the JSON. Do NOT generate + persist on the same turn as the request.
105
122
  NEVER invent prices, phone numbers, addresses, or statistics — ask, or leave a clear placeholder ONLY when the user declines to provide it.
106
123
 
107
124
  WORKFLOW (recommended)
108
125
  0. INTAKE (never skip — even for a quick/test page): ask the essentials above, WAIT for the answers, restate a short outline (sections + CTA + colors), and get the user's "yes" BEFORE any new_page_skeleton / create_page. Do not generate on the same turn as the request.
126
+ 0b. LOCK THE DESIGN SYSTEM (after the customer confirms): commit the exact palette, type scale, spacing scale, and component specs (see DESIGN SYSTEM) — these are your tokens for every element below. Set settings.fontGeneral to the chosen font.
109
127
  1. Call get_generation_guide (this) once, then new_page_skeleton for the top-level shape.
110
128
  2. For each element type you'll use, call get_element to learn its specials & see an example.
111
129
  3. Optionally call new_element to get a correct skeleton, then fill specials + coordinates.
@@ -3,7 +3,7 @@
3
3
  * always-on rules: intake-first, validate-before-persist, dry-run safety,
4
4
  * surgical edits, the essential page-source model, and the centering rule).
5
5
  */
6
- export const INSTRUCTIONS = `webcake-landing builds and edits Webcake landing pages (the editor "page_source" JSON).
6
+ export const INSTRUCTIONS = `webcake-landing builds and edits Webcake landing pages (the editor "page_source" JSON). Act as a PROFESSIONAL landing-page designer doing client work: consult to understand the brief, LOCK a design system (exact palette + type scale + spacing grid + button/card specs derived from the customer's primary color), then build EVERY element from those tokens so the page looks studio-made and consistent — never invent a color/size/radius per element. The customer is an ORDINARY person, not a designer: talk to them only in plain everyday words and visual outcomes ("tông cam ấm, chữ to dễ đọc, ảnh lớn ở đầu trang"), NEVER jargon (hero/CTA/palette/type scale/rgba/8px grid) — read the intent behind how they phrase it and translate it into the design yourself; keep all technical terms internal.
7
7
 
8
8
  RULES (follow for every request):
9
9
  - INTAKE FIRST — do this EVERY time, even for a "quick"/"test" page. Do NOT jump straight to new_page_skeleton/create_page on the same turn as the request: ask the essentials, restate an outline, get a "yes", THEN build. Ask ONE short batch (3–6, with sensible defaults so the user answers fast) enough to understand the page's PURPOSE, name, look and layout: page purpose/goal, brand/page name, what they sell + price (sales/ads pages), primary color + logo/branding, sections & layout in order, primary CTA + destination, desktop+mobile or mobile-only, which organization. CONSULT, don't interrogate: SUGGEST so the user reacts to something concrete — propose a section flow (pick the archetype matching the page type) + a look (hero treatment + color/tone), and when the user is vague offer 2–3 directions to choose from; proactively suggest sections that fit their goal (social-proof, FAQ, countdown), but ask, don't silently add. Then restate the proposed design (section flow + CTA + color/tone) and WAIT for the user's confirmation, iterating until it matches their intent, before generating. Never assume or silently placeholder the page name, product, price, or colors — ask; only placeholder a core fact when the user explicitly declines to give it.
@@ -11,6 +11,7 @@ RULES (follow for every request):
11
11
  - ALWAYS call validate_page and fix every error before create_page / update_page.
12
12
  - BUILD THE SOURCE IN ONE PASS — gather everything you need (call get_element / get_generation_guide for every section, popup, and element type) BEFORE assembling the source, then build the FULL tree once. Do NOT interleave get_element calls between create_page previews and rebuild. create_page/update_page take the entire source as input, so each call re-ships the whole page — re-previewing repeatedly wastes the request.
13
13
  - create_page and update_page DEFAULT to dry_run=true. Dry-run EXACTLY ONCE: show it, then only send dry_run=false after the user confirms. If the dry-run reports validation problems, fix them via validate_page and re-run once — do not loop dry-runs to "check" the source.
14
+ - LARGE PAGES (4+ sections) — build INCREMENTALLY to avoid the giant single create_page payload that can drop the connection: create_page with a SMALL skeleton (empty/near-empty page) to get a page_id, then call add_section once per section (each call ships only that section; the server fetches, appends, validates the whole tree, and saves). Small pages can still go in one create_page pass.
14
15
  - EDIT existing pages surgically: get_page → change ONLY what was asked → keep every other element, its id, and coordinates → validate_page → update_page. Never regenerate the whole tree for a small change.
15
16
  - Organizations: call list_organizations and ask which to use; default to the is_default org. Endpoints are owner-scoped (only the account's own pages).
16
17
 
@@ -18,8 +19,10 @@ MODEL (essentials):
18
19
  - Top-level: { page:[sections], popup:[popups], dynamic_pages:[], settings:{}, options:{mobileOnly,versionID}, cartConfigs:{isActive:false}, svariations:[] }. Popups are a SEPARATE top-level array, NOT inside page; currency lives in settings.currency (not options). Leave dynamic_pages/svariations as [] for a static page, but keep them on edit round-trips.
19
20
  - Element: { id, type, properties, responsive:{desktop,mobile:{config,styles}}, specials, children, runtime, events }. Absolute canvas: children carry numeric top/left/width/height (px) per breakpoint (canvas width desktop=960, mobile=420); sections own a height.
20
21
  - CENTERING (the #1 layout defect — do the math, don't eyeball): to center a box compute left = round((canvas - width)/2) — 960 desktop, 420 mobile. textAlign:center only centers text inside the box, not the box itself. For a row of N items, center the whole row block (startLeft = round((canvas - (N*item + (N-1)*gap))/2)). Keep 0 ≤ left and left+width ≤ canvas on each breakpoint.
22
+ - PAGE MARGIN (one shared axis — fixes the ragged/header-misaligned look): every section AND the header use the SAME column — left edge at 80 desktop / 20 mobile, right edge at 880 / 400 (content width 800 / 380). Header: logo at left=80, CTA right edge at 880 (its left = 880 − width). Never let one band start at left=80 and the next at left=140.
23
+ - 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.
21
24
  - 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.
22
25
  - 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).
23
26
  - IMAGES: include them (hero/product, feature icons, about photo). PREFER REAL PHOTOS — 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). Only if search_images returns ok:false, FALL BACK to a PLACEHOLDER sized to the box: https://placehold.co/<width>x<height>. (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.
24
27
 
25
- Start by calling get_generation_guide. Tools: get_generation_guide, list_elements, get_element, new_element, new_page_skeleton, get_page_schema, validate_page, search_images, list_organizations, create_page, list_pages, get_page, update_page.`;
28
+ Start by calling get_generation_guide. Tools: get_generation_guide, list_elements, get_element, new_element, new_page_skeleton, get_page_schema, validate_page, search_images, list_organizations, create_page, list_pages, get_page, update_page, add_section.`;
@@ -138,4 +138,112 @@ export function registerPersistenceTools(server, domain) {
138
138
  const outcome = await updatePageSource(config, page_id, parsed);
139
139
  return text({ updated: outcome.ok, ...outcome, warnings: result.warnings });
140
140
  });
141
+ // 13) Add section (incremental build) ---------------------------------------
142
+ // Why this exists: create_page/update_page take the ENTIRE source as one tool
143
+ // argument, so the model must generate the whole (often huge) page JSON inline
144
+ // — a long generation that can drop the client↔Claude connection on big pages.
145
+ // add_section keeps each call small: the model sends only the new section(s);
146
+ // the server fetches the page's current source, appends, validates the WHOLE
147
+ // tree, and saves. Build a large page as: create_page(small skeleton) → repeat
148
+ // add_section per section. The big merge lives server↔backend, never in a tool
149
+ // argument the model has to stream.
150
+ const asSections = (input) => {
151
+ let v = input;
152
+ if (typeof v === "string") {
153
+ try {
154
+ v = JSON.parse(v);
155
+ }
156
+ catch {
157
+ /* not JSON — fall through to the single-value wrap */
158
+ }
159
+ }
160
+ return Array.isArray(v) ? v : [v];
161
+ };
162
+ const sectionLabel = (s) => s?.properties?.name ?? s?.specials?.name ?? s?.id ?? s?.type ?? "section";
163
+ server.tool("add_section", "Append one or more SECTIONS to an existing page WITHOUT re-sending the whole source — the way to build a large page incrementally and avoid the giant single create_page payload that can drop the connection. Flow: create_page with a small/empty skeleton → call add_section once per section. The server fetches the page's current source, appends your section(s) to the END of `page`, validates the WHOLE merged tree (errors block), then saves. You send ONLY the new section(s), so each call stays small. DEFAULTS to dry_run=true (reads the page + previews the save; does NOT write). Set dry_run=false to actually append. Needs WEBCAKE_API_BASE + WEBCAKE_JWT.", {
164
+ page_id: z.string().describe("The page id to append to (from create_page or list_pages; must be owned by the account)."),
165
+ sections: z
166
+ .any()
167
+ .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."),
168
+ dry_run: z
169
+ .boolean()
170
+ .optional()
171
+ .describe("Default TRUE — read the page and preview the merged save without writing. Set false to actually append."),
172
+ }, async ({ page_id, sections, dry_run }, extra) => {
173
+ const isDry = dry_run !== false; // default true (safe)
174
+ const newSections = asSections(sections).filter((s) => s != null);
175
+ if (newSections.length === 0) {
176
+ return text({ added: false, reason: "no_sections", hint: "Pass a section object or a non-empty array of sections." });
177
+ }
178
+ const { config, missing } = cfgFor(extra);
179
+ if (!config) {
180
+ // add_section always operates on a LIVE page, so it needs creds even to
181
+ // preview (it reads the page to validate the merge).
182
+ return text({
183
+ added: false,
184
+ reason: "missing_env",
185
+ missing_env: missing,
186
+ hint: "Configure WEBCAKE_API_BASE and WEBCAKE_JWT (env), or send the x-webcake-jwt header (remote), then retry.",
187
+ });
188
+ }
189
+ // Fetch the page's current source so we append rather than overwrite.
190
+ const current = await getPageSource(config, page_id);
191
+ if (!current.ok || current.source == null) {
192
+ return text({
193
+ added: false,
194
+ reason: "fetch_failed",
195
+ status: current.status,
196
+ error: current.error ?? "Page source not found.",
197
+ hint: "Check the page_id (list_pages) and that the account owns it.",
198
+ });
199
+ }
200
+ let base = current.source;
201
+ if (typeof base === "string") {
202
+ try {
203
+ base = JSON.parse(base);
204
+ }
205
+ catch {
206
+ return text({ added: false, reason: "bad_source", hint: "The stored page source could not be parsed." });
207
+ }
208
+ }
209
+ const existing = Array.isArray(base.page) ? base.page : [];
210
+ const merged = { ...base, page: [...existing, ...newSections] };
211
+ const counts = { before: existing.length, after: existing.length + newSections.length };
212
+ const labels = newSections.map(sectionLabel);
213
+ // Validate the WHOLE merged tree — catches id collisions with existing
214
+ // sections, missing field_names, container rules, etc. Errors block.
215
+ const result = domain.validate(merged);
216
+ if (!result.valid) {
217
+ return text({
218
+ added: false,
219
+ reason: "validation_failed",
220
+ errors: result.errors,
221
+ warnings: result.warnings,
222
+ page_section_count: counts,
223
+ hint: "Fix the section(s) — duplicate ids vs existing sections are a common cause — then retry.",
224
+ });
225
+ }
226
+ const parsed = domain.coerce(merged);
227
+ if (isDry) {
228
+ return text({
229
+ dry_run: true,
230
+ page_id,
231
+ sections_added: newSections.length,
232
+ section_labels: labels,
233
+ page_section_count: counts,
234
+ validation: { valid: true, warnings: result.warnings, stats: result.stats },
235
+ request: buildUpdateRequestRedacted(config, page_id, parsed),
236
+ hint: "Re-run with dry_run=false to actually append the section(s).",
237
+ });
238
+ }
239
+ const outcome = await updatePageSource(config, page_id, parsed);
240
+ return text({
241
+ added: outcome.ok,
242
+ sections_added: newSections.length,
243
+ section_labels: labels,
244
+ page_section_count: counts,
245
+ ...outcome,
246
+ warnings: result.warnings,
247
+ });
248
+ });
141
249
  }
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "webcake-landing-mcp",
3
- "version": "1.0.37",
3
+ "version": "1.0.39",
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
+ "mcpName": "io.github.vuluu2k/webcake-landing-mcp",
5
6
  "type": "module",
6
7
  "license": "MIT",
7
8
  "bin": {