webcake-landing-mcp 1.0.43 → 1.0.44
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`, …) and the **ingest tools** (`ingest_html`, `ingest_url` — turn an existing HTML or URL into a layout anchor so the AI can recreate or adapt it) 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`).
|
|
92
|
+
The **reference + generation tools** (`get_generation_guide`, `list_elements`, `validate_page`, …) and the **ingest tools** (`ingest_html`, `ingest_url` — turn an existing HTML or URL into a layout anchor so the AI can recreate or adapt it) work with **zero config**; only the **persistence tools** (`create_page`, `update_page`, `add_section`, `list_pages`, `find_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
|
|
|
@@ -464,6 +464,7 @@ Both `create_page` and `update_page` **default to `dry_run=true`** (validate and
|
|
|
464
464
|
| `list_organizations` | List the account's organizations (id, name, is_default). Default = the `is_default` org. |
|
|
465
465
|
| `create_page` | Persist a generated source as a new page (source-only). **Defaults to `dry_run=true`.** |
|
|
466
466
|
| `list_pages` | List the account's pages (id, name, organization_id, updated_at) to pick one to edit. |
|
|
467
|
+
| `find_pages` | Search the account's pages by name, domain, and/or page id (AND-combined) to locate one to edit; returns id, name, org, custom/default domain, updated_at. |
|
|
467
468
|
| `get_page` | Fetch an existing page's decoded source tree so you can edit it. |
|
|
468
469
|
| `update_page` | Overwrite an existing page's source with an edited tree. **Defaults to `dry_run=true`.** |
|
|
469
470
|
|
package/dist/changelog.json
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "1.0.44",
|
|
4
|
+
"d": "09/06/2026",
|
|
5
|
+
"type": "Added",
|
|
6
|
+
"en": "New find_pages tool searches the account's pages by name, domain (matches custom_domain or default_domain), and/or page id (filters are…",
|
|
7
|
+
"vi": "Công cụ find_pages mới tìm kiếm các trang trong tài khoản theo tên, domain (khớp với custom_domain hoặc default_domain), và/hoặc page id (các bộ lọc…"
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"v": "1.0.43",
|
|
4
11
|
"d": "09/06/2026",
|
|
@@ -33,12 +40,5 @@
|
|
|
33
40
|
"type": "Internal",
|
|
34
41
|
"en": "Added server.json MCP Registry manifest (namespace io.github.vuluu2k/webcake-landing-mcp) and the corresponding mcpName field in package.json so the…",
|
|
35
42
|
"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…"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.0.38",
|
|
39
|
-
"d": "08/06/2026",
|
|
40
|
-
"type": "Added",
|
|
41
|
-
"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,…",
|
|
42
|
-
"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,…"
|
|
43
43
|
}
|
|
44
44
|
]
|
|
@@ -13,7 +13,7 @@ RULES (follow for every request):
|
|
|
13
13
|
- BUILD THE SOURCE IN ONE PASS — gather everything you need BEFORE assembling the source, then build the FULL tree once. BATCH the reads: when a section needs several element types (section + text-block + image-block + button + form + input), call get_element({types:[…]}) ONCE instead of one call per type — same for images, call search_images({queries:[…]}) ONCE with one query per image slot (it dedups + parallelizes and returns one best photo per query). 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.
|
|
14
14
|
- create_page and update_page DEFAULT to dry_run=true (a safety net for ambiguous requests). When the user's intent is clear AND validate_page already passed (no errors), SKIP the dry-run and call with dry_run=false directly — saves one round-trip. Use dry_run=true only when (a) the request is ambiguous about target/content, (b) the user explicitly asks to "preview" or "xem trước", (c) this is an update_page that overwrites significant existing content, or (d) you genuinely need to inspect the redacted payload. Never loop dry-runs to "check" the source — validate_page is the validator. Do not run dry-run then dry-run again before the real write.
|
|
15
15
|
- 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 backend appends it server-side and rejects duplicate ids — no whole-source get+put). Small pages can still go in one create_page pass.
|
|
16
|
-
- 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.
|
|
16
|
+
- EDIT existing pages surgically: find_pages (locate the page by name/domain/id when you don't already have a page_id) → 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.
|
|
17
17
|
- 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).
|
|
18
18
|
- REFERENCE INPUT — if the user provides a layout reference, USE it as the layout anchor (don't ignore it, don't re-invent from scratch). Three input modes: (1) IMAGE/screenshot attached in chat → analyze it natively (no tool call): identify section flow (hero/features/form/cta/footer), heading hierarchy, dominant colors, font feel, then map sections to Webcake elements. (2) HTML string → call ingest_html(html) to get a compact AST. (3) URL → call ingest_url(url) for the same AST. The AST classifies sections by role and lists headings/subheadings/ctas/images/form_fields plus brand hints (colors/fonts) — use it for LAYOUT + HIERARCHY, then generate FRESH content tailored to the user's brand (don't 1:1 copy text). intent='clone' only when the user explicitly asks to mirror the original; default intent='adapt'. The reference workflow PRESERVES craft rules above (centering, page margin, premium spacing, real images) — apply them on top of the reference layout, don't bypass them.
|
|
19
19
|
|
|
@@ -28,4 +28,4 @@ MODEL (essentials):
|
|
|
28
28
|
- 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).
|
|
29
29
|
- 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.
|
|
30
30
|
|
|
31
|
-
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, ingest_html, ingest_url, list_organizations, create_page, list_pages, get_page, update_page, add_section.`;
|
|
31
|
+
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, ingest_html, ingest_url, list_organizations, create_page, list_pages, find_pages, get_page, update_page, add_section.`;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const CREATE_ENDPOINT = "/api/v1/ai/create_page_from_source";
|
|
2
2
|
const ORGS_ENDPOINT = "/api/v1/org/organizations";
|
|
3
3
|
const PAGES_ENDPOINT = "/api/v1/ai/pages";
|
|
4
|
+
const SEARCH_PAGES_ENDPOINT = "/api/v1/ai/search_pages";
|
|
4
5
|
const PAGE_SOURCE_ENDPOINT = "/api/v1/ai/page_source";
|
|
5
6
|
const UPDATE_ENDPOINT = "/api/v1/ai/update_page_source";
|
|
6
7
|
const APPEND_ENDPOINT = "/api/v1/ai/append_section";
|
|
@@ -167,6 +168,32 @@ export async function listPages(config) {
|
|
|
167
168
|
const pages = r.json?.data?.pages ?? r.json?.pages ?? [];
|
|
168
169
|
return { ok: true, status: r.status, pages };
|
|
169
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Search the account's pages by name / domain / id via the dedicated backend
|
|
173
|
+
* endpoint. Filters are AND-combined server-side; each row carries the page's
|
|
174
|
+
* `custom_domain` + `default_domain` so the caller can disambiguate by URL.
|
|
175
|
+
* Returns `endpoint_missing:true` on a 404 so the caller can fall back to
|
|
176
|
+
* filtering `listPages` client-side against an older backend lacking the route.
|
|
177
|
+
*/
|
|
178
|
+
export async function searchPages(config, filters) {
|
|
179
|
+
const qs = new URLSearchParams();
|
|
180
|
+
if (filters.name)
|
|
181
|
+
qs.set("name", filters.name);
|
|
182
|
+
if (filters.domain)
|
|
183
|
+
qs.set("domain", filters.domain);
|
|
184
|
+
if (filters.id)
|
|
185
|
+
qs.set("id", filters.id);
|
|
186
|
+
if (filters.limit != null)
|
|
187
|
+
qs.set("limit", `${filters.limit}`);
|
|
188
|
+
const url = `${config.base}${SEARCH_PAGES_ENDPOINT}${qs.toString() ? `?${qs}` : ""}`;
|
|
189
|
+
const r = await getJson(url, config);
|
|
190
|
+
if (r.status === 404)
|
|
191
|
+
return { ok: false, status: 404, endpoint_missing: true, error: "search_pages endpoint not found on backend" };
|
|
192
|
+
if (!r.ok)
|
|
193
|
+
return { ok: false, status: r.status, error: r.error };
|
|
194
|
+
const pages = r.json?.data?.pages ?? r.json?.pages ?? [];
|
|
195
|
+
return { ok: true, status: r.status, pages };
|
|
196
|
+
}
|
|
170
197
|
/** Read a page's decoded source tree (must be owned by the account). */
|
|
171
198
|
export async function getPageSource(config, pageId) {
|
|
172
199
|
const url = `${config.base}${PAGE_SOURCE_ENDPOINT}?page_id=${encodeURIComponent(pageId)}`;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { z } from "zod";
|
|
13
13
|
import { text } from "../mcp/response.js";
|
|
14
14
|
import { readConfig, configFromHeaders } from "../persistence/config.js";
|
|
15
|
-
import { buildRequestRedacted, buildUpdateRequestRedacted, buildAppendRequestRedacted, createPage, listOrganizations, listPages, getPageSource, updatePageSource, appendSection, } from "../persistence/webcake-client.js";
|
|
15
|
+
import { buildRequestRedacted, buildUpdateRequestRedacted, buildAppendRequestRedacted, createPage, listOrganizations, listPages, searchPages, getPageSource, updatePageSource, appendSection, } from "../persistence/webcake-client.js";
|
|
16
16
|
export function registerPersistenceTools(server, domain) {
|
|
17
17
|
// Resolve config from THIS request's headers (remote per-user JWT) first, then env.
|
|
18
18
|
const cfgFor = (extra) => readConfig(configFromHeaders(extra?.requestInfo?.headers));
|
|
@@ -107,6 +107,52 @@ export function registerPersistenceTools(server, domain) {
|
|
|
107
107
|
return text({ ok: false, reason: "missing_env", missing_env: missing });
|
|
108
108
|
return text(await listPages(config));
|
|
109
109
|
});
|
|
110
|
+
// 10b) Find pages (search by name / domain / id) ----------------------------
|
|
111
|
+
// The lookup step before an edit: locate the page the user means by name,
|
|
112
|
+
// domain (custom OR default), and/or page id, then feed its id to get_page →
|
|
113
|
+
// (edit) → update_page/add_section. Filters are AND-combined server-side and
|
|
114
|
+
// results carry both domain fields so the model can disambiguate by URL.
|
|
115
|
+
//
|
|
116
|
+
// Searches via the dedicated /api/v1/ai/search_pages endpoint (proper DB query,
|
|
117
|
+
// not limited to the 50 most-recent). If that route is missing (older backend
|
|
118
|
+
// → 404) it FALLS BACK to listing pages and filtering client-side by name/id
|
|
119
|
+
// (domain search is unavailable in the fallback — list_pages omits domains).
|
|
120
|
+
server.tool("find_pages", "Searches the account's pages by name, domain, and/or page id so you can locate the page to edit, then pass its id to get_page → update_page/add_section. Filters are AND-combined (e.g. name='sale' + domain='shop.com'). Each result includes id, name, organization_id, custom_domain, default_domain, updated_at. With no filters it returns the most-recent pages (like list_pages). Needs WEBCAKE_API_BASE + WEBCAKE_JWT.", {
|
|
121
|
+
name: z.string().optional().describe("Case-insensitive substring of the page name to match."),
|
|
122
|
+
domain: z
|
|
123
|
+
.string()
|
|
124
|
+
.optional()
|
|
125
|
+
.describe("Case-insensitive substring of the page's domain (matches custom_domain OR default_domain)."),
|
|
126
|
+
page_id: z.string().optional().describe("Exact page id — narrows to that single page (useful to confirm it exists/owned)."),
|
|
127
|
+
limit: z.number().int().positive().max(100).optional().describe("Max results (default 50, capped at 100)."),
|
|
128
|
+
}, { title: "Find Webcake Pages", readOnlyHint: true, openWorldHint: true }, async ({ name, domain, page_id, limit }, extra) => {
|
|
129
|
+
const { config, missing } = cfgFor(extra);
|
|
130
|
+
if (!config)
|
|
131
|
+
return text({ ok: false, reason: "missing_env", missing_env: missing });
|
|
132
|
+
const res = await searchPages(config, { name, domain, id: page_id, limit });
|
|
133
|
+
if (!res.endpoint_missing) {
|
|
134
|
+
return text({ ok: res.ok, pages: res.pages, count: res.pages?.length ?? 0, status: res.status, error: res.error });
|
|
135
|
+
}
|
|
136
|
+
// Fallback: older backend without /search_pages — list and filter client-side.
|
|
137
|
+
const listed = await listPages(config);
|
|
138
|
+
if (!listed.ok)
|
|
139
|
+
return text({ ok: false, status: listed.status, error: listed.error });
|
|
140
|
+
let pages = listed.pages ?? [];
|
|
141
|
+
const nameQ = name?.toLowerCase();
|
|
142
|
+
if (nameQ)
|
|
143
|
+
pages = pages.filter((p) => p.name?.toLowerCase().includes(nameQ));
|
|
144
|
+
if (page_id)
|
|
145
|
+
pages = pages.filter((p) => `${p.id}` === page_id);
|
|
146
|
+
if (limit != null)
|
|
147
|
+
pages = pages.slice(0, limit);
|
|
148
|
+
return text({
|
|
149
|
+
ok: true,
|
|
150
|
+
pages,
|
|
151
|
+
count: pages.length,
|
|
152
|
+
via: "legacy_list_filter_fallback",
|
|
153
|
+
...(domain ? { note: "Domain search is unavailable on this backend (list_pages omits domains); the domain filter was ignored." } : {}),
|
|
154
|
+
});
|
|
155
|
+
});
|
|
110
156
|
// 11) Get page (read source) ------------------------------------------------
|
|
111
157
|
server.tool("get_page", "Fetches an existing page's decoded source tree { page, popup, settings, options, cartConfigs } plus name and organization_id. Needs WEBCAKE_API_BASE + WEBCAKE_JWT.", { page_id: z.string().describe("The page id (from list_pages or a URL).") }, { title: "Get Webcake Page Source", readOnlyHint: true, openWorldHint: true }, async ({ page_id }, extra) => {
|
|
112
158
|
const { config, missing } = cfgFor(extra);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-landing-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.44",
|
|
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",
|