webcake-landing-mcp 1.5.6 → 1.5.8
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/dist/auth/oauth-server.js +22 -1
- package/dist/changelog.json +14 -14
- package/dist/smoke.js +4 -0
- package/dist/tools/ingest.js +1 -1
- package/dist/tools/media.js +1 -1
- package/package.json +1 -1
|
@@ -263,6 +263,27 @@ export async function getClient(clientId) {
|
|
|
263
263
|
return undefined;
|
|
264
264
|
return (await getStore()).getClient(clientId);
|
|
265
265
|
}
|
|
266
|
+
const LOOPBACK_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
|
|
267
|
+
/**
|
|
268
|
+
* Exact match, except an http loopback redirect ignores the port (RFC 8252 §7.3):
|
|
269
|
+
* native clients like Claude Code register `http://localhost/callback` and then
|
|
270
|
+
* redirect to an ephemeral port per session (`http://localhost:3118/callback`).
|
|
271
|
+
*/
|
|
272
|
+
export function redirectMatches(registered, requested) {
|
|
273
|
+
if (registered === requested)
|
|
274
|
+
return true;
|
|
275
|
+
let a, b;
|
|
276
|
+
try {
|
|
277
|
+
a = new URL(registered);
|
|
278
|
+
b = new URL(requested);
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
return false;
|
|
282
|
+
}
|
|
283
|
+
return (a.protocol === "http:" && b.protocol === "http:" &&
|
|
284
|
+
LOOPBACK_HOSTS.has(a.hostname) && a.hostname === b.hostname &&
|
|
285
|
+
a.pathname === b.pathname && a.search === b.search);
|
|
286
|
+
}
|
|
266
287
|
/**
|
|
267
288
|
* Validate an /authorize request and park it. Returns an `internalState` to send
|
|
268
289
|
* to the login page as its `state`; the callback uses it to find this request.
|
|
@@ -275,7 +296,7 @@ export async function startAuthorize(p) {
|
|
|
275
296
|
if (!client) {
|
|
276
297
|
return { ok: false, error: "invalid_client", error_description: "Unknown client_id. Register first via /register.", redirectable: false };
|
|
277
298
|
}
|
|
278
|
-
if (!p.redirect_uri || !client.redirect_uris.
|
|
299
|
+
if (!p.redirect_uri || !client.redirect_uris.some((r) => redirectMatches(r, p.redirect_uri))) {
|
|
279
300
|
return { ok: false, error: "invalid_request", error_description: "redirect_uri does not match a registered URI.", redirectable: false };
|
|
280
301
|
}
|
|
281
302
|
// From here errors CAN go back to the client's redirect_uri.
|
package/dist/changelog.json
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "1.5.8",
|
|
4
|
+
"d": "13/10/2026",
|
|
5
|
+
"type": "Fixed",
|
|
6
|
+
"en": "The OAuth authorize endpoint now accepts http://localhost/127.0.0.1/[::1] redirect URIs on any port as long as the scheme, host, path, and query…",
|
|
7
|
+
"vi": "Endpoint OAuth authorize nay chấp nhận redirect URI http://localhost/127.0.0.1/[::1] ở bất kỳ port nào miễn là scheme, host, path, và query khớp với…"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"v": "1.5.7",
|
|
11
|
+
"d": "13/10/2026",
|
|
12
|
+
"type": "Changed",
|
|
13
|
+
"en": "The ingest_url and render_preview tool descriptions now link to the API docs (docs/tools.md for both, plus the Microlink API reference for…",
|
|
14
|
+
"vi": "Mô tả của tool ingest_url và render_preview nay có thêm link tới tài liệu API (docs/tools.md cho cả hai, cùng với link tham chiếu API của Microlink…"
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"v": "1.5.6",
|
|
4
18
|
"d": "13/10/2026",
|
|
@@ -26,19 +40,5 @@
|
|
|
26
40
|
"type": "Added",
|
|
27
41
|
"en": "validate_page now detects inline base64 images (data:image/…;base64,… URIs) anywhere in an element's specials, a styles background, gallery…",
|
|
28
42
|
"vi": "validate_page nay phát hiện ảnh base64 nhúng (URI data:image/…;base64,…) ở bất kỳ đâu trong specials của phần tử, nền styles, gallery item.link,…"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"v": "1.5.2",
|
|
32
|
-
"d": "25/08/2026",
|
|
33
|
-
"type": "Fixed",
|
|
34
|
-
"en": "Colors written in any CSS notation other than the legacy rgba(r,g,b,a) form (hex, rgb(), hsl(), CSS named colors, and the modern space/slash rgba()…",
|
|
35
|
-
"vi": "Các giá trị màu được viết ở bất kỳ dạng CSS nào khác ngoài dạng rgba(r,g,b,a) kiểu cũ (hex, rgb(), hsl(), tên màu CSS, và cú pháp rgba() mới dùng…"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.5.1",
|
|
39
|
-
"d": "24/08/2026",
|
|
40
|
-
"type": "Added",
|
|
41
|
-
"en": "update_page now detects when the live page was edited outside this session (e.g. in the Webcake editor) since it was last read, and rejects the…",
|
|
42
|
-
"vi": "update_page nay phát hiện khi trang đang sống đã bị chỉnh sửa bên ngoài phiên làm việc này (ví dụ trong editor Webcake) kể từ lần đọc gần nhất, và…"
|
|
43
43
|
}
|
|
44
44
|
]
|
package/dist/smoke.js
CHANGED
|
@@ -18,6 +18,7 @@ import { toEditorUrl, toEditorLoginUrl, toPreviewUrl, buildPublishRequestRedacte
|
|
|
18
18
|
import { normalizePhoto, resolvePexelsKey, pexelsKeyFromHeaders, resolvePexelsProxyBase, buildSearchQuery, PEXELS_PROXY_DEFAULT, variantPixels, annotateVariantSizes } from "./persistence/pexels-client.js";
|
|
19
19
|
import { putDraft, getDraft, updateDraft, deleteDraft } from "./persistence/draft-cache.js";
|
|
20
20
|
import { buildConnectUrl, parseCallback } from "./auth/login.js";
|
|
21
|
+
import { redirectMatches } from "./auth/oauth-server.js";
|
|
21
22
|
import { isLocalPath, resolveLocalPath, sniffMime, localContentType } from "./tools/media.js";
|
|
22
23
|
import { buildMicrolinkUrl } from "./persistence/screenshot-client.js";
|
|
23
24
|
import { isPrivateHost, isAllowedScreenshotUrl } from "./persistence/screenshot-playwright.js";
|
|
@@ -122,6 +123,9 @@ const good = {
|
|
|
122
123
|
};
|
|
123
124
|
const r1 = validatePage(good);
|
|
124
125
|
check("good page valid", r1.valid, r1.errors);
|
|
126
|
+
check("oauth redirect: exact claude.ai match", redirectMatches("https://claude.ai/api/mcp/auth_callback", "https://claude.ai/api/mcp/auth_callback"));
|
|
127
|
+
check("oauth redirect: loopback any port (Claude Code)", redirectMatches("http://localhost/callback", "http://localhost:3118/callback") && redirectMatches("http://127.0.0.1/callback", "http://127.0.0.1:51234/callback"));
|
|
128
|
+
check("oauth redirect: rejects other path/host/https port drift", !redirectMatches("http://localhost/callback", "http://localhost:3118/evil") && !redirectMatches("http://localhost/callback", "http://127.0.0.1:3118/callback") && !redirectMatches("https://claude.ai/api/mcp/auth_callback", "https://claude.ai:8443/api/mcp/auth_callback") && !redirectMatches("http://evil.com/callback", "http://evil.com:1/callback"));
|
|
125
129
|
check("good page has no dangling-target warnings", r1.warnings.length === 0, r1.warnings);
|
|
126
130
|
check("good page stats", r1.stats.sections === 2 && r1.stats.ids === 3, r1.stats);
|
|
127
131
|
console.log("== validate: catches problems ==");
|
package/dist/tools/ingest.js
CHANGED
|
@@ -89,7 +89,7 @@ export function registerIngestTools(server, domain) {
|
|
|
89
89
|
const extraCss = baseUrl ? await fetchStylesheets(html, baseUrl) : undefined;
|
|
90
90
|
return text({ intent: intent ?? "adapt", ...withCloneSource(domain, parseHtml(html, detail ?? "compact", { sections, baseUrl, extraCss })) });
|
|
91
91
|
});
|
|
92
|
-
server.tool("ingest_url", "Fetches a public webpage (GET, 10s timeout, 2MB cap) and parses it into the same reference AST as ingest_html (including per-section size_hint desktop heights). Returns a warning when the page appears client-rendered (empty <body>) so the caller can fall back to a screenshot — Claude can analyze a screenshot natively without this tool. Does not execute JavaScript; sites built with React/Vue/Next.js may return little content. Use detail:'full' for clone-faithful rebuilds — adds CSS palette, background_images, per-section blocks, lists, images as { src, alt } objects, and widgets (source HTML + CSS of composite mockups for html-box rebuilds). ABSOLUTE-CANVAS builder exports (LadiPage-family / Webcake-published pages) are auto-detected the same way as ingest_html and converted DETERMINISTICALLY into a ready-to-save `source` (+ `clone_notes` + `clone_notice`, with the per-element geometry summarized to `canvas_summary`) — save it straight to create_page; external images auto-host on save. Image URLs in the result are the user's assets — re-host them via upload_images and reuse them for BOTH intents; use search_images only for slots with no source image. Also fetches the page's external stylesheets (+ @imports) so `font_groups` (self-hosted fonts → settings.fontGroups, uploaded on save) and the computed `typography`/`text_styles` (font_size/mobile_font_size/font_weight — copy exactly) see rules outside the HTML.", {
|
|
92
|
+
server.tool("ingest_url", "Fetches a public webpage (GET, 10s timeout, 2MB cap) and parses it into the same reference AST as ingest_html (including per-section size_hint desktop heights). Returns a warning when the page appears client-rendered (empty <body>) so the caller can fall back to a screenshot — Claude can analyze a screenshot natively without this tool. Does not execute JavaScript; sites built with React/Vue/Next.js may return little content. Use detail:'full' for clone-faithful rebuilds — adds CSS palette, background_images, per-section blocks, lists, images as { src, alt } objects, and widgets (source HTML + CSS of composite mockups for html-box rebuilds). ABSOLUTE-CANVAS builder exports (LadiPage-family / Webcake-published pages) are auto-detected the same way as ingest_html and converted DETERMINISTICALLY into a ready-to-save `source` (+ `clone_notes` + `clone_notice`, with the per-element geometry summarized to `canvas_summary`) — save it straight to create_page; external images auto-host on save. Image URLs in the result are the user's assets — re-host them via upload_images and reuse them for BOTH intents; use search_images only for slots with no source image. Also fetches the page's external stylesheets (+ @imports) so `font_groups` (self-hosted fonts → settings.fontGroups, uploaded on save) and the computed `typography`/`text_styles` (font_size/mobile_font_size/font_weight — copy exactly) see rules outside the HTML. Docs: https://github.com/vuluu2k/webcake-landing-mcp/blob/main/docs/tools.md (ingest_url).", {
|
|
93
93
|
url: z.string().describe("Public HTTP(S) URL of the page to fetch."),
|
|
94
94
|
intent: z
|
|
95
95
|
.enum(["adapt", "clone"])
|
package/dist/tools/media.js
CHANGED
|
@@ -291,7 +291,7 @@ export function registerMediaTools(server, allowLocalFiles = true) {
|
|
|
291
291
|
});
|
|
292
292
|
});
|
|
293
293
|
// 13c) Render a page/URL to a screenshot the model can SEE --------------------
|
|
294
|
-
server.tool("render_preview", "Renders a PUBLIC URL to a PNG and returns it as an image so the model can SEE the result and compare it visually to the reference — the fidelity-check step of the clone loop (build → see → patch_page → re-check). Pass `page_id` to shoot a created page's preview (/preview/<id>) or `url` for any public page (e.g. the reference you're cloning). full_page defaults to true (whole scrollable page). AGENT-FIRST: if YOU already have a screenshot/browser capability (a shell + headless browser, or a screenshot tool), screenshot the preview URL YOURSELF instead — it's fresh and unlimited; use this tool only when you cannot. ENGINE: zero-config via Microlink's free tier (rate-limited ~50/day PER IP, so heavy looping can hit HTTP 429 — then this returns ok:false and you should SKIP the visual check that round, not fail); a host can set RENDER_SCREENSHOT_BASE (or the x-render-screenshot-base header) to a keyed proxy, or MICROLINK_API_KEY / x-microlink-key for a higher quota. NOTE: a no-domain preview only renders for ~10 minutes after the last publish — call this promptly after create_page/publish_page, and re-publish before re-checking a stale page. TALL PAGES: pass tiles:true to get the page as a STACK of top→bottom band images (each readable at full detail) instead of one full-page image squished small — needs a self-hosted Playwright host (RENDER_SCREENSHOT_BASE); falls back to a single image otherwise.", {
|
|
294
|
+
server.tool("render_preview", "Renders a PUBLIC URL to a PNG and returns it as an image so the model can SEE the result and compare it visually to the reference — the fidelity-check step of the clone loop (build → see → patch_page → re-check). Pass `page_id` to shoot a created page's preview (/preview/<id>) or `url` for any public page (e.g. the reference you're cloning). full_page defaults to true (whole scrollable page). AGENT-FIRST: if YOU already have a screenshot/browser capability (a shell + headless browser, or a screenshot tool), screenshot the preview URL YOURSELF instead — it's fresh and unlimited; use this tool only when you cannot. ENGINE: zero-config via Microlink's free tier (rate-limited ~50/day PER IP, so heavy looping can hit HTTP 429 — then this returns ok:false and you should SKIP the visual check that round, not fail); a host can set RENDER_SCREENSHOT_BASE (or the x-render-screenshot-base header) to a keyed proxy, or MICROLINK_API_KEY / x-microlink-key for a higher quota. NOTE: a no-domain preview only renders for ~10 minutes after the last publish — call this promptly after create_page/publish_page, and re-publish before re-checking a stale page. TALL PAGES: pass tiles:true to get the page as a STACK of top→bottom band images (each readable at full detail) instead of one full-page image squished small — needs a self-hosted Playwright host (RENDER_SCREENSHOT_BASE); falls back to a single image otherwise. Docs: https://github.com/vuluu2k/webcake-landing-mcp/blob/main/docs/tools.md (render_preview); engine API: https://microlink.io/docs/api/getting-started/overview.", {
|
|
295
295
|
page_id: z.string().optional().describe("A created page's id — screenshots its /preview/<id> URL (built from the preview base). Provide page_id OR url."),
|
|
296
296
|
url: z.string().optional().describe("Any public http(s) URL to screenshot (e.g. the reference page being cloned). Wins over page_id."),
|
|
297
297
|
full_page: z.boolean().optional().describe("Capture the whole scrollable page (default true) vs just the viewport."),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-landing-mcp",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.8",
|
|
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",
|