webcake-landing-mcp 1.5.7 → 1.5.9
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/http.js +5 -0
- package/dist/smoke.js +4 -0
- 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.9",
|
|
4
|
+
"d": "13/10/2026",
|
|
5
|
+
"type": "Fixed",
|
|
6
|
+
"en": "The HTTP server now answers a request carrying an unknown mcp-session-id with 404 instead of 400, so clients re-initialize instead of staying stuck…",
|
|
7
|
+
"vi": "HTTP server nay trả về 404 (thay vì 400) khi nhận request với mcp-session-id không xác định, giúp client tự re-initialize thay vì bị kẹt ở trạng…"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"v": "1.5.8",
|
|
11
|
+
"d": "13/10/2026",
|
|
12
|
+
"type": "Fixed",
|
|
13
|
+
"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…",
|
|
14
|
+
"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…"
|
|
15
|
+
},
|
|
2
16
|
{
|
|
3
17
|
"v": "1.5.7",
|
|
4
18
|
"d": "13/10/2026",
|
|
@@ -26,19 +40,5 @@
|
|
|
26
40
|
"type": "Added",
|
|
27
41
|
"en": "ingest_html and ingest_url now return font_groups (the page's self-hosted @font-face fonts, already shaped for settings.fontGroups) and…",
|
|
28
42
|
"vi": "ingest_html và ingest_url nay trả về font_groups (các font @font-face tự host của trang, đã đóng gói theo đúng dạng settings.fontGroups) và…"
|
|
29
|
-
},
|
|
30
|
-
{
|
|
31
|
-
"v": "1.5.3",
|
|
32
|
-
"d": "17/09/2026",
|
|
33
|
-
"type": "Added",
|
|
34
|
-
"en": "validate_page now detects inline base64 images (data:image/…;base64,… URIs) anywhere in an element's specials, a styles background, gallery…",
|
|
35
|
-
"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/http.js
CHANGED
|
@@ -497,6 +497,11 @@ export async function startHttpServer(port) {
|
|
|
497
497
|
await transport.handleRequest(req, res, body);
|
|
498
498
|
return;
|
|
499
499
|
}
|
|
500
|
+
// Unknown session (e.g. sessions are in-memory and the server restarted):
|
|
501
|
+
// the spec requires 404 so the client re-initializes. A 400 here left
|
|
502
|
+
// claude.ai "connected" with no tools after every redeploy.
|
|
503
|
+
if (sessionId)
|
|
504
|
+
return rpcError(res, 404, "Session not found (expired or server restarted) — send a new initialize request.");
|
|
500
505
|
// New session: only a POST `initialize` may open one.
|
|
501
506
|
if (req.method === "POST") {
|
|
502
507
|
const body = await readBody(req);
|
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.
|
|
3
|
+
"version": "1.5.9",
|
|
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",
|