webcake-landing-mcp 1.5.7 → 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.
@@ -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.includes(p.redirect_uri)) {
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.
@@ -1,4 +1,11 @@
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
+ },
2
9
  {
3
10
  "v": "1.5.7",
4
11
  "d": "13/10/2026",
@@ -33,12 +40,5 @@
33
40
  "type": "Added",
34
41
  "en": "validate_page now detects inline base64 images (data:image/…;base64,… URIs) anywhere in an element's specials, a styles background, gallery…",
35
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,…"
36
- },
37
- {
38
- "v": "1.5.2",
39
- "d": "25/08/2026",
40
- "type": "Fixed",
41
- "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()…",
42
- "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…"
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webcake-landing-mcp",
3
- "version": "1.5.7",
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",