framewatch-mcp-server 0.1.0 → 0.2.0
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 +895 -15
- package/dist/constants.d.ts +274 -0
- package/dist/constants.js +279 -0
- package/dist/constants.js.map +1 -1
- package/dist/engine/browser.d.ts +20 -4
- package/dist/engine/browser.js +26 -9
- package/dist/engine/browser.js.map +1 -1
- package/dist/engine/clicks.d.ts +221 -0
- package/dist/engine/clicks.js +801 -0
- package/dist/engine/clicks.js.map +1 -0
- package/dist/engine/forms.d.ts +137 -0
- package/dist/engine/forms.js +474 -0
- package/dist/engine/forms.js.map +1 -0
- package/dist/engine/hmr.d.ts +41 -0
- package/dist/engine/hmr.js +91 -0
- package/dist/engine/hmr.js.map +1 -0
- package/dist/engine/inspect.d.ts +31 -0
- package/dist/engine/inspect.js +383 -0
- package/dist/engine/inspect.js.map +1 -0
- package/dist/engine/interaction.d.ts +12 -7
- package/dist/engine/interaction.js +110 -18
- package/dist/engine/interaction.js.map +1 -1
- package/dist/engine/links.d.ts +134 -0
- package/dist/engine/links.js +384 -0
- package/dist/engine/links.js.map +1 -0
- package/dist/engine/mocks.d.ts +53 -0
- package/dist/engine/mocks.js +148 -0
- package/dist/engine/mocks.js.map +1 -0
- package/dist/engine/rtl.d.ts +129 -0
- package/dist/engine/rtl.js +540 -0
- package/dist/engine/rtl.js.map +1 -0
- package/dist/engine/seo.d.ts +189 -0
- package/dist/engine/seo.js +398 -0
- package/dist/engine/seo.js.map +1 -0
- package/dist/engine/snapshot.d.ts +29 -0
- package/dist/engine/snapshot.js +10 -0
- package/dist/engine/snapshot.js.map +1 -0
- package/dist/engine/vue.d.ts +54 -0
- package/dist/engine/vue.js +419 -0
- package/dist/engine/vue.js.map +1 -0
- package/dist/index.js +45 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/accessibility.d.ts +4 -0
- package/dist/tools/accessibility.js +9 -2
- package/dist/tools/accessibility.js.map +1 -1
- package/dist/tools/api-mock.d.ts +405 -0
- package/dist/tools/api-mock.js +186 -0
- package/dist/tools/api-mock.js.map +1 -0
- package/dist/tools/capture.d.ts +90 -26
- package/dist/tools/capture.js +109 -58
- package/dist/tools/capture.js.map +1 -1
- package/dist/tools/compare.d.ts +4 -0
- package/dist/tools/compare.js +16 -5
- package/dist/tools/compare.js.map +1 -1
- package/dist/tools/dead-clicks.d.ts +128 -0
- package/dist/tools/dead-clicks.js +570 -0
- package/dist/tools/dead-clicks.js.map +1 -0
- package/dist/tools/form-test.d.ts +112 -0
- package/dist/tools/form-test.js +477 -0
- package/dist/tools/form-test.js.map +1 -0
- package/dist/tools/index.d.ts +17 -1
- package/dist/tools/index.js +45 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/inspect.d.ts +78 -0
- package/dist/tools/inspect.js +136 -0
- package/dist/tools/inspect.js.map +1 -0
- package/dist/tools/interact.d.ts +37 -18
- package/dist/tools/interact.js +113 -13
- package/dist/tools/interact.js.map +1 -1
- package/dist/tools/links.d.ts +129 -0
- package/dist/tools/links.js +640 -0
- package/dist/tools/links.js.map +1 -0
- package/dist/tools/responsive.d.ts +10 -6
- package/dist/tools/responsive.js +21 -4
- package/dist/tools/responsive.js.map +1 -1
- package/dist/tools/rtl.d.ts +241 -0
- package/dist/tools/rtl.js +410 -0
- package/dist/tools/rtl.js.map +1 -0
- package/dist/tools/save-auth.d.ts +263 -0
- package/dist/tools/save-auth.js +253 -0
- package/dist/tools/save-auth.js.map +1 -0
- package/dist/tools/screenshot.d.ts +4 -0
- package/dist/tools/screenshot.js +15 -4
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/tools/seo.d.ts +113 -0
- package/dist/tools/seo.js +281 -0
- package/dist/tools/seo.js.map +1 -0
- package/dist/tools/snapshot.d.ts +122 -0
- package/dist/tools/snapshot.js +183 -0
- package/dist/tools/snapshot.js.map +1 -0
- package/dist/tools/wait-for.d.ts +107 -0
- package/dist/tools/wait-for.js +167 -0
- package/dist/tools/wait-for.js.map +1 -0
- package/dist/utils/arabic-text.d.ts +14 -0
- package/dist/utils/arabic-text.js +193 -0
- package/dist/utils/arabic-text.js.map +1 -0
- package/dist/utils/budget.d.ts +41 -0
- package/dist/utils/budget.js +182 -0
- package/dist/utils/budget.js.map +1 -0
- package/dist/utils/format.d.ts +11 -1
- package/dist/utils/format.js +27 -4
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/highlight.d.ts +69 -0
- package/dist/utils/highlight.js +181 -0
- package/dist/utils/highlight.js.map +1 -0
- package/dist/utils/link-rules.d.ts +100 -0
- package/dist/utils/link-rules.js +284 -0
- package/dist/utils/link-rules.js.map +1 -0
- package/dist/utils/mock-rules.d.ts +144 -0
- package/dist/utils/mock-rules.js +224 -0
- package/dist/utils/mock-rules.js.map +1 -0
- package/dist/utils/rtl-rules.d.ts +142 -0
- package/dist/utils/rtl-rules.js +296 -0
- package/dist/utils/rtl-rules.js.map +1 -0
- package/dist/utils/seo-rules.d.ts +129 -0
- package/dist/utils/seo-rules.js +726 -0
- package/dist/utils/seo-rules.js.map +1 -0
- package/dist/utils/snapshot-rules.d.ts +33 -0
- package/dist/utils/snapshot-rules.js +111 -0
- package/dist/utils/snapshot-rules.js.map +1 -0
- package/dist/utils/storage-state.d.ts +76 -0
- package/dist/utils/storage-state.js +195 -0
- package/dist/utils/storage-state.js.map +1 -0
- package/dist/utils/style-rules.d.ts +107 -0
- package/dist/utils/style-rules.js +223 -0
- package/dist/utils/style-rules.js.map +1 -0
- package/dist/utils/test-data.d.ts +75 -0
- package/dist/utils/test-data.js +294 -0
- package/dist/utils/test-data.js.map +1 -0
- package/dist/utils/vue-rules.d.ts +72 -0
- package/dist/utils/vue-rules.js +108 -0
- package/dist/utils/vue-rules.js.map +1 -0
- package/package.json +6 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"links.js","sourceRoot":"","sources":["../../src/tools/links.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport type { BrowserContext, Page } from \"playwright\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport {\n DEFAULT_LINKS_WAIT_MS,\n DEFAULT_LINK_CONCURRENCY,\n DEFAULT_LINK_PAGES,\n DEFAULT_LINK_TIMEOUT_MS,\n DEFAULT_MAX_LINKS,\n DEFAULT_VIEWPORT,\n HIGHLIGHT_BROKEN_COLOUR,\n HIGHLIGHT_DEAD_COLOUR,\n MAX_HIGHLIGHTS,\n MAX_LINKS_CAP,\n MAX_LINKS_LISTED,\n MAX_LINK_CONCURRENCY,\n MAX_LINK_DEPTH,\n MAX_LINK_PAGES,\n MAX_LINK_SELECTOR_LENGTH,\n MAX_LINK_SOURCES_LISTED,\n MAX_LINK_TIMEOUT_MS,\n MAX_LINK_URL_LENGTH,\n MAX_VIEWPORT_HEIGHT,\n MAX_VIEWPORT_WIDTH,\n MIN_LINK_TIMEOUT_MS,\n NAVIGATION_TIMEOUT_MS,\n SELECTOR_TIMEOUT_MS,\n} from \"../constants.js\";\nimport { withPage } from \"../engine/browser.js\";\nimport {\n checkUrl,\n collectLinks,\n findFragmentTargets,\n watchLoads,\n type CheckOutcome,\n type LinkRole,\n type RawLink,\n} from \"../engine/links.js\";\nimport { resizeForOutput, toBase64 } from \"../utils/image.js\";\nimport { highlightElements, type Highlight } from \"../utils/highlight.js\";\nimport {\n classifyHref,\n dedupeKey,\n isAlwaysValidFragment,\n isInternal,\n judgeCheck,\n type LinkCategory,\n type LinkVerdict,\n} from \"../utils/link-rules.js\";\nimport { resolveStorageState, storageStateField, withAuthNote } from \"../utils/storage-state.js\";\n\nexport const LINKS_TOOL_NAME = \"framewatch_links\";\n\nexport const linksInputShape = {\n url: z\n .string()\n .url()\n .describe(\"URL to check, e.g. http://localhost:3000 (http, https and file URLs are accepted)\"),\n depth: z\n .number()\n .int()\n .min(0)\n .max(MAX_LINK_DEPTH)\n .default(0)\n .describe(\n \"How far to follow the site. 0 checks the links on this page only; 1 also opens each internal page this \" +\n \"one links to and checks its links, and so on. Each level costs a real browser navigation per page.\",\n ),\n check_external: z\n .boolean()\n .default(true)\n .describe(\n \"Also check links to other origins. This sends a request to somebody else's server — turn it off on a \" +\n \"page full of third-party links, or when working offline.\",\n ),\n include_resources: z\n .boolean()\n .default(true)\n .describe(\n \"Also check what the page loads for itself: images, scripts, stylesheets, iframes, media. Most of these \" +\n \"cost nothing to check — the browser already fetched them and its answer is reused.\",\n ),\n check_fragments: z\n .boolean()\n .default(true)\n .describe(\n \"Check that a link into this same page (`#pricing`) actually points at an element. Nothing over HTTP can \" +\n \"catch this: the page loads fine and the visitor simply does not arrive where the link said.\",\n ),\n timeout_ms: z\n .number()\n .int()\n .min(MIN_LINK_TIMEOUT_MS)\n .max(MAX_LINK_TIMEOUT_MS)\n .default(DEFAULT_LINK_TIMEOUT_MS)\n .describe(\"How long one link has to answer before it is reported as timed out\"),\n concurrency: z\n .number()\n .int()\n .min(1)\n .max(MAX_LINK_CONCURRENCY)\n .default(DEFAULT_LINK_CONCURRENCY)\n .describe(\"How many links to check at once. Lower it if a host starts answering 429.\"),\n max_links: z\n .number()\n .int()\n .min(1)\n .max(MAX_LINKS_CAP)\n .default(DEFAULT_MAX_LINKS)\n .describe(\"Distinct addresses to check. Links come before resources, so the cap is spent on links first.\"),\n max_pages: z\n .number()\n .int()\n .min(1)\n .max(MAX_LINK_PAGES)\n .default(DEFAULT_LINK_PAGES)\n .describe(\"Pages to open when `depth` is above 0, including the one you named\"),\n selector: z\n .string()\n .optional()\n .describe(\"Only collect links inside this container, e.g. 'main' or 'footer'\"),\n wait_ms: z\n .number()\n .int()\n .min(0)\n .default(DEFAULT_LINKS_WAIT_MS)\n .describe(\"Wait time (ms) after each page load before its links are read, so a client-rendered app can finish\"),\n wait_for: z.string().optional().describe(\"CSS selector to wait for (visible) after each load\"),\n wait_for_timeout_ms: z\n .number()\n .int()\n .min(1)\n .default(SELECTOR_TIMEOUT_MS)\n .describe(\"Max time (ms) to wait for `wait_for` to appear (must be > 0)\"),\n full_page: z\n .boolean()\n .default(false)\n .describe(\"Photograph the whole document instead of the viewport, so broken links below the fold are visible too\"),\n viewport: z\n .object({\n width: z.number().int().min(1).max(MAX_VIEWPORT_WIDTH).default(DEFAULT_VIEWPORT.width),\n height: z.number().int().min(1).max(MAX_VIEWPORT_HEIGHT).default(DEFAULT_VIEWPORT.height),\n })\n .optional()\n .describe(\"Viewport size (defaults to 1280x720)\"),\n storage_state: storageStateField,\n};\n\nexport const linksInputSchema = z.object(linksInputShape);\nexport type LinksInput = z.input<typeof linksInputSchema>;\ntype ParsedLinksInput = z.output<typeof linksInputSchema>;\n\n/* ── What one run collects ────────────────────────────────────────────── */\n\n/** Where one address was written down. */\ninterface Occurrence {\n page_url: string;\n description: string;\n selector: string;\n match_index: number;\n role: LinkRole;\n tag: string;\n}\n\n/** One distinct address, and every place it appears. */\ninterface Target {\n key: string;\n /** The address as it will be printed and requested. */\n url: string;\n occurrences: Occurrence[];\n external: boolean;\n role: LinkRole;\n outcome?: CheckOutcome;\n verdict?: LinkVerdict;\n}\n\n/** An address that was never requested, and why not. */\ninterface Skipped {\n key: string;\n display: string;\n reason: string;\n occurrences: Occurrence[];\n /** Worth leading with: an empty href or a malformed URL is a bug, not a decision. */\n suspect: boolean;\n}\n\n/** A `#fragment` on this page that points at no element. */\ninterface DeadFragment {\n fragment: string;\n page_url: string;\n occurrences: Occurrence[];\n}\n\n/** Everything one run produced. */\ninterface Crawl {\n targets: Target[];\n skipped: Skipped[];\n dead_fragments: DeadFragment[];\n /** Fragments that were checked and did resolve — counted, not listed. */\n fragments_ok: number;\n /** Href occurrences collected, before they were grouped by address. */\n found: number;\n pages: string[];\n /** Pages that could not be opened during a crawl, after the first one. */\n unreachable_pages: { url: string; error: string }[];\n /** Distinct addresses past `max_links`. */\n over_cap: number;\n png?: Buffer;\n unhighlighted: number;\n}\n\n/**\n * Check every link on a page — and, on request, every link on the pages it\n * leads to — and report the ones that do not work.\n *\n * The page is read after it has rendered rather than from its HTML source, so\n * a client-rendered app is checked properly instead of being reported as\n * having no links. The checks go out through the browser context, carrying the\n * same cookies as the page, so a link behind a login is checked as the\n * signed-in visitor sees it.\n *\n * Three things separate this from a HEAD loop. A HEAD that fails is retried as\n * a GET before anything is called broken, because a great many servers refuse\n * the method and serve the URL perfectly well. A status that means \"I will not\n * answer that\" (401, 403, 429) is reported apart from one that means \"there is\n * nothing here\", because a report that cries wolf gets ignored. And a\n * same-page `#fragment` is checked against the document, which is the one\n * broken link no HTTP check can ever find.\n */\nexport async function checkLinks(rawInput: LinksInput): Promise<CallToolResult> {\n const parsed = linksInputSchema.safeParse(rawInput);\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => `${i.path.join(\".\") || \"input\"}: ${i.message}`).join(\"; \");\n return errorResult(`Link check failed: invalid input — ${issues}`);\n }\n const input = parsed.data;\n const viewport = input.viewport ?? { ...DEFAULT_VIEWPORT };\n\n let contextOptions = {};\n let auth = null;\n try {\n auth = await resolveStorageState(input.storage_state);\n if (auth) contextOptions = { storageState: auth.state };\n } catch (error) {\n return errorResult(`Link check of ${input.url} failed: ${firstLine(error)}`);\n }\n\n let crawl: Crawl;\n try {\n crawl = await withPage({ viewport, contextOptions }, (page, context) => runCrawl(input, page, context));\n } catch (error) {\n return errorResult(describeLinksFailure(input, error));\n }\n\n return withAuthNote(await render(input, crawl), auth);\n}\n\n/* ── The crawl ────────────────────────────────────────────────────────── */\n\nasync function runCrawl(input: ParsedLinksInput, page: Page, context: BrowserContext): Promise<Crawl> {\n const watch = watchLoads(page);\n const targets = new Map<string, Target>();\n const skipped = new Map<string, Skipped>();\n const deadFragments = new Map<string, DeadFragment>();\n const visited = new Set<string>();\n const queue: { url: string; depth: number }[] = [{ url: input.url, depth: 0 }];\n const pages: string[] = [];\n const unreachablePages: { url: string; error: string }[] = [];\n let found = 0;\n let fragmentsOk = 0;\n let overCap = 0;\n\n const note = (map: Map<string, Skipped>, key: string, display: string, reason: string, suspect: boolean, where: Occurrence): void => {\n const existing = map.get(key);\n if (existing) existing.occurrences.push(where);\n else map.set(key, { key, display, reason, occurrences: [where], suspect });\n };\n\n try {\n while (queue.length > 0 && pages.length < input.max_pages) {\n const { url, depth } = queue.shift()!;\n const pageKey = dedupeKey(url);\n if (visited.has(pageKey)) continue;\n visited.add(pageKey);\n\n try {\n await load(input, page, url);\n } catch (error) {\n // The page the caller named is the whole run; anything found later is\n // one page of many and is reported rather than thrown.\n if (pages.length === 0) throw error;\n unreachablePages.push({ url, error: firstLine(error) });\n continue;\n }\n\n const pageUrl = safely(() => page.url()) ?? url;\n pages.push(pageUrl);\n\n const raw = await collectLinks(page, {\n ...(input.selector !== undefined ? { selector: input.selector } : {}),\n include_resources: input.include_resources,\n });\n found += raw.length;\n\n const wantedFragments = new Map<string, Occurrence[]>();\n const fresh: Target[] = [];\n\n for (const link of raw) {\n const where = occurrenceOf(link, pageUrl);\n const classified = classifyHref(link.href, pageUrl);\n\n switch (classified.kind) {\n case \"http\": {\n const external = !isInternal(classified.resolved!, pageUrl);\n if (external && !input.check_external) {\n note(skipped, `external:${dedupeKey(classified.resolved!)}`, classified.resolved!, \"another origin, and `check_external` is off\", false, where);\n continue;\n }\n const key = dedupeKey(classified.resolved!);\n const existing = targets.get(key);\n if (existing) {\n existing.occurrences.push(where);\n // A link matters more than a resource: if the same address is\n // both, it is reported as the link.\n if (where.role === \"link\") existing.role = \"link\";\n continue;\n }\n if (targets.size >= input.max_links) {\n overCap++;\n continue;\n }\n const target: Target = { key, url: key, occurrences: [where], external, role: where.role };\n targets.set(key, target);\n fresh.push(target);\n continue;\n }\n case \"same_page\": {\n const fragment = classified.fragment ?? \"\";\n if (!input.check_fragments) {\n note(skipped, `fragment:${pageUrl}#${fragment}`, `#${fragment}`, \"a link into this page, and `check_fragments` is off\", false, where);\n continue;\n }\n const list = wantedFragments.get(fragment) ?? [];\n list.push(where);\n wantedFragments.set(fragment, list);\n continue;\n }\n case \"empty\":\n case \"malformed\":\n case \"javascript\":\n note(skipped, `${classified.kind}:${link.href}`, link.href || \"(an empty href)\", classified.reason ?? classified.kind, classified.kind !== \"javascript\", where);\n continue;\n default: {\n const display = link.href;\n note(skipped, `${classified.kind}:${display}`, display, classified.reason ?? `a ${classified.kind}: link, which no checker can follow`, classified.reason !== undefined, where);\n continue;\n }\n }\n }\n\n if (wantedFragments.size > 0) {\n // `#` and `#top` are the top of the document in every browser and need\n // no element; everything else has to be found in the DOM.\n const wanted = [...wantedFragments.keys()].filter((fragment) => !isAlwaysValidFragment(fragment));\n const present = new Set([\n ...(await findFragmentTargets(page, wanted)),\n ...[...wantedFragments.keys()].filter(isAlwaysValidFragment),\n ]);\n for (const [fragment, occurrences] of wantedFragments) {\n if (present.has(fragment)) {\n // Counted per distinct fragment, not per link: three anchors to\n // #pricing are one thing that either resolves or does not.\n fragmentsOk++;\n continue;\n }\n const key = `${pageUrl}#${fragment}`;\n const existing = deadFragments.get(key);\n if (existing) existing.occurrences.push(...occurrences);\n else deadFragments.set(key, { fragment, page_url: pageUrl, occurrences });\n }\n }\n\n // Checked here rather than at the end, because what comes back decides\n // what is worth opening next: a 404 is not a page to crawl, and neither\n // is a PDF.\n await runPool(fresh, input.concurrency, async (target) => {\n const observed = target.role === \"resource\" ? watch.get(target.url) : undefined;\n const outcome: CheckOutcome = observed\n ? {\n chain: observed.chain.length > 0 ? observed.chain : [target.url],\n ...(observed.status !== undefined ? { status: observed.status } : {}),\n ...(observed.failure !== undefined ? { error: observed.failure } : {}),\n method: \"GET\",\n observed: true,\n }\n : await checkUrl(context, target.url, { timeout_ms: input.timeout_ms });\n target.outcome = outcome;\n target.verdict = judgeCheck(outcome);\n });\n\n if (depth < input.depth) {\n for (const target of fresh) {\n if (target.external || target.role !== \"link\") continue;\n if (target.verdict === undefined || (target.verdict.category !== \"ok\" && target.verdict.category !== \"redirect\")) continue;\n // Only a document is worth opening. An internal link to a PDF or a\n // zip answers perfectly well and has no links on it.\n if (!/html/i.test(target.outcome?.content_type ?? \"\")) continue;\n const next = target.verdict.final_url ?? target.url;\n if (visited.has(dedupeKey(next))) continue;\n queue.push({ url: next, depth: depth + 1 });\n }\n }\n }\n\n const crawl: Crawl = {\n targets: [...targets.values()],\n skipped: [...skipped.values()],\n dead_fragments: [...deadFragments.values()],\n fragments_ok: fragmentsOk,\n found,\n pages,\n unreachable_pages: unreachablePages,\n over_cap: overCap,\n unhighlighted: 0,\n };\n\n const marked = await drawFindings(input, page, crawl, pages[0]);\n if (marked.png) crawl.png = marked.png;\n crawl.unhighlighted = marked.unhighlighted;\n\n return crawl;\n } finally {\n watch.detach();\n }\n}\n\n/** Open a page the way every other tool does, so `wait_for` means the same thing everywhere. */\nasync function load(input: ParsedLinksInput, page: Page, url: string): Promise<void> {\n await page.goto(url, { waitUntil: \"load\", timeout: NAVIGATION_TIMEOUT_MS });\n if (input.wait_for) {\n await page.waitForSelector(input.wait_for, { state: \"visible\", timeout: input.wait_for_timeout_ms });\n }\n if (input.wait_ms > 0) {\n await page.waitForTimeout(input.wait_ms);\n }\n}\n\n/**\n * Put the entry page back on screen and box everything on it that failed.\n *\n * A selector says nothing about where the problem is; a picture of the page\n * with the broken links outlined says it immediately. Only the entry page is\n * marked up — a crawl of ten pages would otherwise return ten screenshots —\n * and nothing is photographed at all when there is nothing to mark.\n */\nasync function drawFindings(\n input: ParsedLinksInput,\n page: Page,\n crawl: Crawl,\n entryUrl: string,\n): Promise<{ png?: Buffer; unhighlighted: number }> {\n const failing = crawl.targets.filter((target) => FAILED.has(target.verdict?.category ?? \"ok\"));\n const boxes: { selector: string; match_index: number; broken: boolean }[] = [];\n for (const target of failing) {\n for (const where of target.occurrences) {\n if (!boxable(where, entryUrl)) continue;\n boxes.push({ selector: where.selector, match_index: where.match_index, broken: true });\n }\n }\n for (const fragment of crawl.dead_fragments) {\n if (fragment.page_url !== entryUrl) continue;\n for (const where of fragment.occurrences) {\n if (!boxable(where, entryUrl)) continue;\n boxes.push({ selector: where.selector, match_index: where.match_index, broken: false });\n }\n }\n if (boxes.length === 0) return { unhighlighted: 0 };\n\n try {\n if (safely(() => page.url()) !== entryUrl) await load(input, page, entryUrl);\n } catch {\n // The entry page will not open a second time; the report stands without a\n // picture of it.\n return { unhighlighted: boxes.length };\n }\n\n const highlights: Highlight[] = boxes.slice(0, MAX_HIGHLIGHTS).map((box, index) => ({\n selector: box.selector,\n match_index: box.match_index,\n label: String(index + 1),\n colour: box.broken ? HIGHLIGHT_DEAD_COLOUR : HIGHLIGHT_BROKEN_COLOUR,\n wash: box.broken ? \"rgba(229, 25, 75, 0.16)\" : \"rgba(240, 140, 0, 0.16)\",\n }));\n const drawn = await highlightElements(page, highlights);\n const png = await safeScreenshot(page, input.full_page);\n return { ...(png ? { png } : {}), unhighlighted: boxes.length - drawn.drawn.length };\n}\n\n/**\n * Elements that never have a box to draw over.\n *\n * A broken stylesheet is a real finding and lives in `<head>`, where it has no\n * geometry at all. Asking for a box over one is not a near miss to be counted\n * and explained — it is a question with no answer, so it is never asked.\n */\nconst UNRENDERED = new Set([\"script\", \"link\", \"meta\", \"source\", \"track\", \"base\"]);\n\n/** Can this occurrence be pointed at in a screenshot of the entry page? */\nfunction boxable(where: Occurrence, entryUrl: string): boolean {\n return where.page_url === entryUrl && where.selector !== \"\" && !UNRENDERED.has(where.tag);\n}\n\n/** Categories that mean the link does not work for a visitor. */\nconst FAILED: ReadonlySet<LinkCategory> = new Set<LinkCategory>([\"broken\", \"timeout\", \"error\"]);\n\nfunction occurrenceOf(link: RawLink, pageUrl: string): Occurrence {\n return {\n page_url: pageUrl,\n description: link.description,\n selector: link.selector,\n match_index: link.match_index,\n role: link.role,\n tag: link.tag,\n };\n}\n\n/** Run `worker` over `items`, `limit` at a time. Every one of these is a real request. */\nasync function runPool<T>(items: T[], limit: number, worker: (item: T) => Promise<void>): Promise<void> {\n let next = 0;\n const runners = Array.from({ length: Math.max(1, Math.min(limit, items.length)) }, async () => {\n for (;;) {\n const index = next++;\n if (index >= items.length) return;\n await worker(items[index]);\n }\n });\n await Promise.all(runners);\n}\n\n/* ── Rendering ────────────────────────────────────────────────────────── */\n\nasync function render(input: ParsedLinksInput, crawl: Crawl): Promise<CallToolResult> {\n const content: CallToolResult[\"content\"] = [{ type: \"text\", text: formatCrawl(input, crawl) }];\n\n if (crawl.png) {\n try {\n content.push({ type: \"image\", data: toBase64(await resizeForOutput(crawl.png)), mimeType: \"image/png\" });\n content.push({\n type: \"text\",\n text:\n `${crawl.pages[0]}, with every link that failed boxed in red and every dead fragment in orange, ` +\n \"numbered in the order they appear above.\" +\n (crawl.unhighlighted > 0\n ? ` ${crawl.unhighlighted} could not be boxed — those elements are hidden, or the page had re-rendered by the time the overlay was drawn.`\n : \"\"),\n });\n } catch {\n // Measured already; failing to re-encode the frame is not worth failing\n // the check for.\n }\n }\n\n return { content };\n}\n\nfunction formatCrawl(input: ParsedLinksInput, crawl: Crawl): string {\n const of = (category: LinkCategory): Target[] => crawl.targets.filter((t) => t.verdict?.category === category);\n const broken = of(\"broken\");\n const timedOut = of(\"timeout\");\n const unreachable = of(\"error\");\n const redirected = of(\"redirect\");\n const blocked = of(\"blocked\");\n const working = of(\"ok\");\n const notChecked = crawl.skipped.length + crawl.over_cap;\n\n const lines: string[] = [];\n const counts = [\n `${broken.length} broken`,\n ...(timedOut.length > 0 ? [`${timedOut.length} timed out`] : []),\n ...(unreachable.length > 0 ? [`${unreachable.length} unreachable`] : []),\n ...(crawl.dead_fragments.length > 0 ? [`${count(crawl.dead_fragments.length, \"dead fragment\")}`] : []),\n ...(redirected.length > 0 ? [`${redirected.length} redirected`] : []),\n ...(blocked.length > 0 ? [`${blocked.length} blocked`] : []),\n `${working.length} working`,\n ...(notChecked > 0 ? [`${notChecked} not checked`] : []),\n ];\n\n lines.push(\n `Link check of ${input.url} — ${count(crawl.found, \"link\")} found on ` +\n `${count(crawl.pages.length, \"page\")} (${unique(crawl)} unique), ${counts.join(\", \")}.`,\n );\n\n if (crawl.found === 0) {\n lines.push(\"Nothing on this page points anywhere — no link, image, script or stylesheet.\");\n return lines.join(\"\\n\");\n }\n\n const failures = broken.length + timedOut.length + unreachable.length + crawl.dead_fragments.length;\n if (failures === 0) {\n lines.push(\"Every link that could be checked answered.\");\n }\n if (crawl.fragments_ok > 0) {\n lines.push(\n `${count(crawl.fragments_ok, \"same-page fragment\")} resolved against the document rather than over HTTP.`,\n );\n }\n for (const page of crawl.unreachable_pages) {\n lines.push(`The crawl could not open ${elide(page.url, MAX_LINK_URL_LENGTH)} — ${page.error}`);\n }\n\n section(lines, `Broken (${broken.length})`, broken, crawl);\n section(lines, `Timed out (${timedOut.length})`, timedOut, crawl);\n section(lines, `Could not be reached (${unreachable.length})`, unreachable, crawl);\n\n if (crawl.dead_fragments.length > 0) {\n lines.push(\"\");\n lines.push(`Fragments that point at nothing (${crawl.dead_fragments.length}):`);\n for (const fragment of crawl.dead_fragments.slice(0, MAX_LINKS_LISTED)) {\n lines.push(` #${fragment.fragment} — no element on the page has that id, and no <a name> either`);\n for (const line of sourceLines(fragment.occurrences, crawl)) lines.push(line);\n }\n if (crawl.dead_fragments.length > MAX_LINKS_LISTED) {\n lines.push(` … and ${crawl.dead_fragments.length - MAX_LINKS_LISTED} more`);\n }\n }\n\n section(lines, `Redirected (${redirected.length})`, redirected, crawl);\n section(\n lines,\n `Blocked — the server refused the check, not necessarily the link (${blocked.length})`,\n blocked,\n crawl,\n );\n\n if (crawl.skipped.length > 0 || crawl.over_cap > 0) {\n lines.push(\"\");\n lines.push(`Not checked (${notChecked}):`);\n const ordered = [...crawl.skipped].sort((a, b) => Number(b.suspect) - Number(a.suspect));\n for (const entry of ordered.slice(0, MAX_LINKS_LISTED)) {\n lines.push(` ${elide(entry.display, MAX_LINK_URL_LENGTH)} — ${entry.reason}`);\n for (const line of sourceLines(entry.occurrences, crawl)) lines.push(line);\n }\n if (ordered.length > MAX_LINKS_LISTED) {\n lines.push(` … and ${ordered.length - MAX_LINKS_LISTED} more`);\n }\n if (crawl.over_cap > 0) {\n lines.push(\n ` ${count(crawl.over_cap, \"address\")} past \\`max_links\\` (${input.max_links}) — raise it, or narrow ` +\n \"the run with `selector`.\",\n );\n }\n }\n\n if (working.length > 0) {\n lines.push(\"\");\n lines.push(`Working (${working.length}):`);\n for (const target of working.slice(0, MAX_LINKS_LISTED)) {\n lines.push(` ${elide(target.url, MAX_LINK_URL_LENGTH)} — ${detailOf(target)}`);\n }\n if (working.length > MAX_LINKS_LISTED) {\n lines.push(` … and ${working.length - MAX_LINKS_LISTED} more that answered`);\n }\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Distinct addresses this run reached a verdict on.\n *\n * Everything that was judged, however it was judged: requested, refused,\n * skipped for a reason, or looked up in the document. Leaving the fragments\n * out would make the number quietly disagree with the sections beneath it.\n */\nfunction unique(crawl: Crawl): number {\n return crawl.targets.length + crawl.skipped.length + crawl.dead_fragments.length + crawl.fragments_ok;\n}\n\n/** One section of the report: the address, where it was written, and what to do. */\nfunction section(lines: string[], heading: string, targets: Target[], crawl: Crawl): void {\n if (targets.length === 0) return;\n lines.push(\"\");\n lines.push(`${heading}:`);\n for (const target of targets.slice(0, MAX_LINKS_LISTED)) {\n lines.push(` ${elide(target.url, MAX_LINK_URL_LENGTH)} — ${detailOf(target)}`);\n for (const line of sourceLines(target.occurrences, crawl)) lines.push(line);\n const fix = target.verdict?.fix;\n if (fix) lines.push(` → ${fix}`);\n }\n if (targets.length > MAX_LINKS_LISTED) {\n lines.push(` … and ${targets.length - MAX_LINKS_LISTED} more`);\n }\n}\n\n/**\n * What happened, plus a note when the answer came from the browser rather than\n * from a request of this tool's own — the two are not quite the same claim,\n * and the difference matters when they disagree with a curl.\n */\nfunction detailOf(target: Target): string {\n const detail = target.verdict?.detail ?? \"no verdict\";\n return target.outcome?.observed === true ? `${detail} (seen when the page loaded it)` : detail;\n}\n\n/** `from a \"Pricing\" (#nav-pricing)`, naming the page too once there is more than one. */\nfunction sourceLines(occurrences: Occurrence[], crawl: Crawl): string[] {\n const lines: string[] = [];\n const multipage = crawl.pages.length > 1;\n for (const where of occurrences.slice(0, MAX_LINK_SOURCES_LISTED)) {\n const selector = where.selector === \"\" ? \"\" : ` (${elide(where.selector, MAX_LINK_SELECTOR_LENGTH)})`;\n const page = multipage ? ` on ${elide(where.page_url, MAX_LINK_URL_LENGTH)}` : \"\";\n lines.push(` from ${where.description}${selector}${page}`);\n }\n if (occurrences.length > MAX_LINK_SOURCES_LISTED) {\n lines.push(` … and ${occurrences.length - MAX_LINK_SOURCES_LISTED} more places`);\n }\n return lines;\n}\n\n/* ── Helpers ──────────────────────────────────────────────────────────── */\n\n/** A screenshot is always worth having and never worth failing the check over. */\nasync function safeScreenshot(page: Page, fullPage: boolean): Promise<Buffer | undefined> {\n try {\n if (page.isClosed()) return undefined;\n return await page.screenshot({ type: \"png\", fullPage });\n } catch {\n return undefined;\n }\n}\n\nfunction elide(value: string, max: number): string {\n const flat = String(value).replace(/\\s+/g, \" \");\n return flat.length > max ? `${flat.slice(0, max)}…` : flat;\n}\n\nfunction count(n: number, noun: string): string {\n return `${n} ${noun}${n === 1 ? \"\" : \"s\"}`;\n}\n\nfunction safely<T>(read: () => T): T | undefined {\n try {\n return read();\n } catch {\n return undefined;\n }\n}\n\nfunction firstLine(error: unknown): string {\n return (error instanceof Error ? error.message : String(error)).split(\"\\n\")[0];\n}\n\nfunction errorResult(text: string): CallToolResult {\n return { isError: true, content: [{ type: \"text\", text }] };\n}\n\n/**\n * One actionable line for a check that could not run. Mirrors `describeFailure`\n * in screenshot.ts: match on the failing Playwright call, never on substrings\n * of a user-supplied selector.\n */\nexport function describeLinksFailure(\n input: { url: string; wait_for?: string; wait_for_timeout_ms: number },\n error: unknown,\n): string {\n const message = error instanceof Error ? error.message : String(error);\n const line = firstLine(message);\n const prefix = `Link check of ${input.url} failed:`;\n\n if (/Executable doesn't exist|browserType\\.launch/i.test(message)) {\n return (\n `${prefix} Playwright's Chromium browser is not installed. ` +\n `Run \\`npx playwright install chromium\\` and try again. (${line})`\n );\n }\n if (input.wait_for && /^page\\.waitForSelector:/.test(message)) {\n return `${prefix} selector \"${input.wait_for}\" did not become visible within ${input.wait_for_timeout_ms}ms.`;\n }\n if (/^page\\.goto:/.test(message)) {\n return `${prefix} the page could not be opened — ${line}`;\n }\n return `${prefix} ${line}`;\n}\n\nexport function registerLinksTool(server: McpServer): void {\n server.registerTool(\n LINKS_TOOL_NAME,\n {\n title: \"Links\",\n description:\n \"Check every link on a page and report the ones that do not work: 404s, server errors, redirect \" +\n \"chains, redirects that end on an error page, hosts that never answer, and `#fragment` links that \" +\n \"point at no element on the page. Links are read from the rendered DOM, so a client-rendered app is \" +\n \"checked properly. What the page loads for itself — images, scripts, stylesheets, iframes — is checked \" +\n \"too, reusing the browser's own result rather than requesting it again. A HEAD that fails is retried \" +\n \"as a GET before anything is called broken, and a 401/403/429 is reported as a refused check rather \" +\n \"than a broken link. Set `depth` above 0 to follow the site's own internal links a level at a time. \" +\n \"Returns the findings grouped by what went wrong, plus a screenshot of the page with the failing \" +\n \"links boxed. NOTE: with `check_external` on this sends requests to third-party servers.\",\n inputSchema: linksInputShape,\n annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },\n },\n async (args) => checkLinks(args),\n );\n}\n"]}
|
|
@@ -9,17 +9,18 @@ export declare const responsiveInputShape: {
|
|
|
9
9
|
width: z.ZodNumber;
|
|
10
10
|
height: z.ZodNumber;
|
|
11
11
|
}, "strip", z.ZodTypeAny, {
|
|
12
|
+
name: string;
|
|
12
13
|
width: number;
|
|
13
14
|
height: number;
|
|
14
|
-
name: string;
|
|
15
15
|
}, {
|
|
16
|
+
name: string;
|
|
16
17
|
width: number;
|
|
17
18
|
height: number;
|
|
18
|
-
name: string;
|
|
19
19
|
}>, "many">>;
|
|
20
20
|
wait_ms: z.ZodDefault<z.ZodNumber>;
|
|
21
21
|
wait_for: z.ZodOptional<z.ZodString>;
|
|
22
22
|
wait_for_timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
23
|
+
storage_state: z.ZodOptional<z.ZodString>;
|
|
23
24
|
};
|
|
24
25
|
export declare const responsiveInputSchema: z.ZodObject<{
|
|
25
26
|
url: z.ZodString;
|
|
@@ -28,36 +29,39 @@ export declare const responsiveInputSchema: z.ZodObject<{
|
|
|
28
29
|
width: z.ZodNumber;
|
|
29
30
|
height: z.ZodNumber;
|
|
30
31
|
}, "strip", z.ZodTypeAny, {
|
|
32
|
+
name: string;
|
|
31
33
|
width: number;
|
|
32
34
|
height: number;
|
|
33
|
-
name: string;
|
|
34
35
|
}, {
|
|
36
|
+
name: string;
|
|
35
37
|
width: number;
|
|
36
38
|
height: number;
|
|
37
|
-
name: string;
|
|
38
39
|
}>, "many">>;
|
|
39
40
|
wait_ms: z.ZodDefault<z.ZodNumber>;
|
|
40
41
|
wait_for: z.ZodOptional<z.ZodString>;
|
|
41
42
|
wait_for_timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
43
|
+
storage_state: z.ZodOptional<z.ZodString>;
|
|
42
44
|
}, "strip", z.ZodTypeAny, {
|
|
43
45
|
url: string;
|
|
44
46
|
wait_ms: number;
|
|
45
47
|
wait_for_timeout_ms: number;
|
|
46
48
|
viewports: {
|
|
49
|
+
name: string;
|
|
47
50
|
width: number;
|
|
48
51
|
height: number;
|
|
49
|
-
name: string;
|
|
50
52
|
}[];
|
|
51
53
|
wait_for?: string | undefined;
|
|
54
|
+
storage_state?: string | undefined;
|
|
52
55
|
}, {
|
|
53
56
|
url: string;
|
|
54
57
|
wait_ms?: number | undefined;
|
|
55
58
|
wait_for?: string | undefined;
|
|
56
59
|
wait_for_timeout_ms?: number | undefined;
|
|
60
|
+
storage_state?: string | undefined;
|
|
57
61
|
viewports?: {
|
|
62
|
+
name: string;
|
|
58
63
|
width: number;
|
|
59
64
|
height: number;
|
|
60
|
-
name: string;
|
|
61
65
|
}[] | undefined;
|
|
62
66
|
}>;
|
|
63
67
|
export type ResponsiveInput = z.input<typeof responsiveInputSchema>;
|
package/dist/tools/responsive.js
CHANGED
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { DEFAULT_RESPONSIVE_VIEWPORTS, DEFAULT_RESPONSIVE_WAIT_MS, MAX_RESPONSIVE_VIEWPORTS, MAX_VIEWPORT_HEIGHT, MAX_VIEWPORT_WIDTH, NAVIGATION_TIMEOUT_MS, OVERFLOW_TOLERANCE_PX, SELECTOR_TIMEOUT_MS, } from "../constants.js";
|
|
3
3
|
import { withPage } from "../engine/browser.js";
|
|
4
4
|
import { getDimensions, resizeForOutput, toBase64 } from "../utils/image.js";
|
|
5
|
+
import { resolveStorageState, storageStateField, withAuthNote } from "../utils/storage-state.js";
|
|
5
6
|
export const RESPONSIVE_TOOL_NAME = "framewatch_responsive";
|
|
6
7
|
const viewportSchema = z.object({
|
|
7
8
|
name: z.string().min(1).describe("Label for this size, e.g. 'mobile', 'tablet', 'desktop'"),
|
|
@@ -29,6 +30,7 @@ export const responsiveInputShape = {
|
|
|
29
30
|
.min(1)
|
|
30
31
|
.default(SELECTOR_TIMEOUT_MS)
|
|
31
32
|
.describe("Max time (ms) to wait for `wait_for` to appear (must be > 0)"),
|
|
33
|
+
storage_state: storageStateField,
|
|
32
34
|
};
|
|
33
35
|
export const responsiveInputSchema = z.object(responsiveInputShape);
|
|
34
36
|
/**
|
|
@@ -53,7 +55,18 @@ export async function captureResponsive(rawInput) {
|
|
|
53
55
|
return errorResult(`Responsive capture failed: invalid input — ${issues}`);
|
|
54
56
|
}
|
|
55
57
|
const input = parsed.data;
|
|
56
|
-
|
|
58
|
+
// Read the auth state once, before any context exists: a missing or stale
|
|
59
|
+
// file is one problem with the call, not one problem per viewport.
|
|
60
|
+
let storageState;
|
|
61
|
+
let auth = null;
|
|
62
|
+
try {
|
|
63
|
+
auth = await resolveStorageState(input.storage_state);
|
|
64
|
+
storageState = auth?.state;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
return errorResult(`Responsive capture of ${input.url} failed: ${describeViewportFailure(input, error)}`);
|
|
68
|
+
}
|
|
69
|
+
const shots = await Promise.all(input.viewports.map((viewport) => captureOne(input, viewport, storageState)));
|
|
57
70
|
const captured = shots.filter((shot) => shot.png !== undefined);
|
|
58
71
|
if (captured.length === 0) {
|
|
59
72
|
const reasons = shots.map((shot) => `${shot.viewport.name}: ${shot.error ?? "no screenshot"}`).join("; ");
|
|
@@ -75,12 +88,16 @@ export async function captureResponsive(rawInput) {
|
|
|
75
88
|
content.push({ type: "image", data: toBase64(resized), mimeType: "image/png" });
|
|
76
89
|
content.push({ type: "text", text: describeShot(shot, width, height) });
|
|
77
90
|
}
|
|
78
|
-
return { content };
|
|
91
|
+
return withAuthNote({ content }, auth);
|
|
79
92
|
}
|
|
80
93
|
/** Screenshot `url` at one viewport. Never throws — a failure is part of the result. */
|
|
81
|
-
async function captureOne(input, viewport) {
|
|
94
|
+
async function captureOne(input, viewport, storageState) {
|
|
82
95
|
try {
|
|
83
|
-
const
|
|
96
|
+
const options = {
|
|
97
|
+
viewport: { width: viewport.width, height: viewport.height },
|
|
98
|
+
...(storageState ? { contextOptions: { storageState } } : {}),
|
|
99
|
+
};
|
|
100
|
+
const result = await withPage(options, async (page) => {
|
|
84
101
|
await page.goto(input.url, { waitUntil: "load", timeout: NAVIGATION_TIMEOUT_MS });
|
|
85
102
|
if (input.wait_for) {
|
|
86
103
|
await page.waitForSelector(input.wait_for, { state: "visible", timeout: input.wait_for_timeout_ms });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"responsive.js","sourceRoot":"","sources":["../../src/tools/responsive.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport type { Page } from \"playwright\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport {\n DEFAULT_RESPONSIVE_VIEWPORTS,\n DEFAULT_RESPONSIVE_WAIT_MS,\n MAX_RESPONSIVE_VIEWPORTS,\n MAX_VIEWPORT_HEIGHT,\n MAX_VIEWPORT_WIDTH,\n NAVIGATION_TIMEOUT_MS,\n OVERFLOW_TOLERANCE_PX,\n SELECTOR_TIMEOUT_MS,\n} from \"../constants.js\";\nimport { withPage } from \"../engine/browser.js\";\nimport { getDimensions, resizeForOutput, toBase64 } from \"../utils/image.js\";\n\nexport const RESPONSIVE_TOOL_NAME = \"framewatch_responsive\";\n\nconst viewportSchema = z.object({\n name: z.string().min(1).describe(\"Label for this size, e.g. 'mobile', 'tablet', 'desktop'\"),\n width: z.number().int().min(1).max(MAX_VIEWPORT_WIDTH),\n height: z.number().int().min(1).max(MAX_VIEWPORT_HEIGHT),\n});\n\nexport const responsiveInputShape = {\n url: z.string().url().describe(\"URL to capture, e.g. http://localhost:3000 (http, https and file URLs are accepted)\"),\n viewports: z\n .array(viewportSchema)\n .min(1)\n .max(MAX_RESPONSIVE_VIEWPORTS)\n .default([...DEFAULT_RESPONSIVE_VIEWPORTS])\n .describe(\"Viewport sizes to capture (defaults to mobile 375x812, tablet 768x1024, desktop 1440x900)\"),\n wait_ms: z\n .number()\n .int()\n .min(0)\n .default(DEFAULT_RESPONSIVE_WAIT_MS)\n .describe(\"Wait time (ms) after page load before each screenshot\"),\n wait_for: z.string().optional().describe(\"CSS selector to wait for (visible) before each screenshot\"),\n wait_for_timeout_ms: z\n .number()\n .int()\n .min(1)\n .default(SELECTOR_TIMEOUT_MS)\n .describe(\"Max time (ms) to wait for `wait_for` to appear (must be > 0)\"),\n};\n\nexport const responsiveInputSchema = z.object(responsiveInputShape);\nexport type ResponsiveInput = z.input<typeof responsiveInputSchema>;\ntype ParsedViewport = z.output<typeof viewportSchema>;\n\n/** How one viewport turned out. Either it produced a shot, or it produced a reason. */\ninterface ViewportShot {\n viewport: ParsedViewport;\n png?: Buffer;\n /** Document width vs viewport width, for the overflow check. */\n layout?: LayoutInfo;\n error?: string;\n}\n\ninterface LayoutInfo {\n scroll_width: number;\n client_width: number;\n scroll_height: number;\n client_height: number;\n}\n\n/**\n * Capture one page at several viewport sizes and return one screenshot per\n * size, labelled, so a whole responsive range can be reviewed in a single\n * look.\n *\n * Each viewport gets its own browser context rather than one page being\n * resized, for two reasons: a page that has already laid itself out at 1440px\n * often keeps state a genuine mobile visitor never had (a menu built by a\n * matchMedia listener that ran once), and independent contexts can load\n * concurrently — three 2s waits cost 2s, not 6s.\n *\n * A viewport that fails is reported next to the ones that worked instead of\n * failing the call: \"desktop is fine, mobile times out\" is itself the finding.\n * Only a run where every viewport failed comes back as an error.\n */\nexport async function captureResponsive(rawInput: ResponsiveInput): Promise<CallToolResult> {\n const parsed = responsiveInputSchema.safeParse(rawInput);\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => `${i.path.join(\".\") || \"input\"}: ${i.message}`).join(\"; \");\n return errorResult(`Responsive capture failed: invalid input — ${issues}`);\n }\n const input = parsed.data;\n\n const shots = await Promise.all(input.viewports.map((viewport) => captureOne(input, viewport)));\n const captured = shots.filter((shot) => shot.png !== undefined);\n if (captured.length === 0) {\n const reasons = shots.map((shot) => `${shot.viewport.name}: ${shot.error ?? \"no screenshot\"}`).join(\"; \");\n return errorResult(`Responsive capture of ${input.url} failed at every viewport — ${reasons}`);\n }\n\n const content: CallToolResult[\"content\"] = [\n {\n type: \"text\",\n text: summarise(input.url, shots),\n },\n ];\n\n for (const shot of shots) {\n if (!shot.png) {\n content.push({ type: \"text\", text: `${label(shot.viewport)} — failed: ${shot.error ?? \"no screenshot\"}` });\n continue;\n }\n const resized = await resizeForOutput(shot.png);\n const { width, height } = await getDimensions(resized);\n content.push({ type: \"image\", data: toBase64(resized), mimeType: \"image/png\" });\n content.push({ type: \"text\", text: describeShot(shot, width, height) });\n }\n\n return { content };\n}\n\n/** Screenshot `url` at one viewport. Never throws — a failure is part of the result. */\nasync function captureOne(\n input: z.output<typeof responsiveInputSchema>,\n viewport: ParsedViewport,\n): Promise<ViewportShot> {\n try {\n const result = await withPage({ viewport: { width: viewport.width, height: viewport.height } }, async (page) => {\n await page.goto(input.url, { waitUntil: \"load\", timeout: NAVIGATION_TIMEOUT_MS });\n if (input.wait_for) {\n await page.waitForSelector(input.wait_for, { state: \"visible\", timeout: input.wait_for_timeout_ms });\n }\n if (input.wait_ms > 0) {\n await page.waitForTimeout(input.wait_ms);\n }\n const png = await page.screenshot({ type: \"png\" });\n return { png, layout: await readLayout(page) };\n });\n return { viewport, ...result };\n } catch (error) {\n return { viewport, error: describeViewportFailure(input, error) };\n }\n}\n\n/**\n * The document's laid-out size against the viewport it was given. Horizontal\n * overflow is the classic responsive bug and it is invisible in a screenshot —\n * the content that sticks out is simply cropped away — so it is measured\n * rather than left to the eye.\n *\n * Cosmetic: a page that dies before this is read still returns its screenshot.\n */\nasync function readLayout(page: Page): Promise<LayoutInfo | undefined> {\n try {\n // `globalThis as any` rather than the DOM globals: this package is\n // compiled with the Node lib only, and the page it lands in may be\n // mid-teardown, so nothing here can be assumed to exist.\n return await page.evaluate(() => {\n const el = (globalThis as any).document?.documentElement;\n if (!el) return undefined;\n return {\n scroll_width: Math.round(el.scrollWidth),\n client_width: Math.round(el.clientWidth),\n scroll_height: Math.round(el.scrollHeight),\n client_height: Math.round(el.clientHeight),\n };\n });\n } catch {\n return undefined;\n }\n}\n\nfunction label(viewport: ParsedViewport): string {\n return `${viewport.name} ${viewport.width}x${viewport.height}`;\n}\n\n/** One line per viewport: what it is, how big the image is, and whether the content fits. */\nfunction describeShot(shot: ViewportShot, imageWidth: number, imageHeight: number): string {\n const parts = [label(shot.viewport), `image ${imageWidth}x${imageHeight}`];\n\n const layout = shot.layout;\n if (layout) {\n if (layout.scroll_width > layout.client_width + OVERFLOW_TOLERANCE_PX) {\n parts.push(\n `horizontal overflow: content is ${layout.scroll_width}px wide in a ${layout.client_width}px viewport ` +\n `(+${layout.scroll_width - layout.client_width}px)`,\n );\n }\n if (layout.scroll_height > layout.client_height) {\n parts.push(`page scrolls to ${layout.scroll_height}px (${scrolls(layout)} screens)`);\n }\n }\n\n return parts.join(\" — \");\n}\n\nfunction scrolls(layout: LayoutInfo): string {\n return (layout.scroll_height / Math.max(1, layout.client_height)).toFixed(1);\n}\n\n/** The opening line: what was captured, and an up-front warning about anything that overflowed. */\nfunction summarise(url: string, shots: ViewportShot[]): string {\n const captured = shots.filter((shot) => shot.png !== undefined);\n const failed = shots.filter((shot) => shot.png === undefined);\n\n const names = captured.map((shot) => label(shot.viewport)).join(\", \");\n const lines = [`Captured ${url} at ${captured.length} of ${shots.length} viewports: ${names}`];\n\n if (failed.length > 0) {\n lines.push(`Failed: ${failed.map((shot) => shot.viewport.name).join(\", \")}`);\n }\n\n const overflowing = captured.filter(\n (shot) => shot.layout !== undefined && shot.layout.scroll_width > shot.layout.client_width + OVERFLOW_TOLERANCE_PX,\n );\n if (overflowing.length > 0) {\n lines.push(\n `Horizontal overflow at ${overflowing.map((shot) => shot.viewport.name).join(\", \")} — ` +\n \"content is wider than the viewport, so something is sticking out past the right edge.\",\n );\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction errorResult(text: string): CallToolResult {\n return { isError: true, content: [{ type: \"text\", text }] };\n}\n\n/**\n * One actionable line for a viewport that failed. Mirrors `describeFailure` in\n * screenshot.ts: match on the failing Playwright call, never on substrings of\n * a user-supplied selector.\n */\nexport function describeViewportFailure(input: { wait_for?: string; wait_for_timeout_ms: number }, error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n const firstLine = message.split(\"\\n\")[0];\n\n if (/Executable doesn't exist|browserType\\.launch/i.test(message)) {\n return \"Playwright's Chromium browser is not installed. Run `npx playwright install chromium` and try again.\";\n }\n if (input.wait_for && /^page\\.waitForSelector:/.test(message)) {\n return `selector \"${input.wait_for}\" did not become visible within ${input.wait_for_timeout_ms}ms`;\n }\n return firstLine;\n}\n\nexport function registerResponsiveTool(server: McpServer): void {\n server.registerTool(\n RESPONSIVE_TOOL_NAME,\n {\n title: \"Responsive\",\n description:\n \"Screenshot the same page at several viewport sizes in one call (mobile, tablet and desktop by default) \" +\n \"and return one labelled image per size. Each size loads in its own fresh browser context, so a mobile \" +\n \"shot is what a phone would really get rather than a resized desktop layout. Content that is wider than \" +\n \"its viewport is reported as horizontal overflow — the commonest responsive bug, and one a screenshot \" +\n \"alone hides because the overflowing part is simply cropped off.\",\n inputSchema: responsiveInputShape,\n annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },\n },\n async (args) => captureResponsive(args),\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"responsive.js","sourceRoot":"","sources":["../../src/tools/responsive.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport type { Page } from \"playwright\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport {\n DEFAULT_RESPONSIVE_VIEWPORTS,\n DEFAULT_RESPONSIVE_WAIT_MS,\n MAX_RESPONSIVE_VIEWPORTS,\n MAX_VIEWPORT_HEIGHT,\n MAX_VIEWPORT_WIDTH,\n NAVIGATION_TIMEOUT_MS,\n OVERFLOW_TOLERANCE_PX,\n SELECTOR_TIMEOUT_MS,\n} from \"../constants.js\";\nimport { withPage } from \"../engine/browser.js\";\nimport { getDimensions, resizeForOutput, toBase64 } from \"../utils/image.js\";\nimport { resolveStorageState, storageStateField, withAuthNote, type StorageState } from \"../utils/storage-state.js\";\n\nexport const RESPONSIVE_TOOL_NAME = \"framewatch_responsive\";\n\nconst viewportSchema = z.object({\n name: z.string().min(1).describe(\"Label for this size, e.g. 'mobile', 'tablet', 'desktop'\"),\n width: z.number().int().min(1).max(MAX_VIEWPORT_WIDTH),\n height: z.number().int().min(1).max(MAX_VIEWPORT_HEIGHT),\n});\n\nexport const responsiveInputShape = {\n url: z.string().url().describe(\"URL to capture, e.g. http://localhost:3000 (http, https and file URLs are accepted)\"),\n viewports: z\n .array(viewportSchema)\n .min(1)\n .max(MAX_RESPONSIVE_VIEWPORTS)\n .default([...DEFAULT_RESPONSIVE_VIEWPORTS])\n .describe(\"Viewport sizes to capture (defaults to mobile 375x812, tablet 768x1024, desktop 1440x900)\"),\n wait_ms: z\n .number()\n .int()\n .min(0)\n .default(DEFAULT_RESPONSIVE_WAIT_MS)\n .describe(\"Wait time (ms) after page load before each screenshot\"),\n wait_for: z.string().optional().describe(\"CSS selector to wait for (visible) before each screenshot\"),\n wait_for_timeout_ms: z\n .number()\n .int()\n .min(1)\n .default(SELECTOR_TIMEOUT_MS)\n .describe(\"Max time (ms) to wait for `wait_for` to appear (must be > 0)\"),\n storage_state: storageStateField,\n};\n\nexport const responsiveInputSchema = z.object(responsiveInputShape);\nexport type ResponsiveInput = z.input<typeof responsiveInputSchema>;\ntype ParsedViewport = z.output<typeof viewportSchema>;\n\n/** How one viewport turned out. Either it produced a shot, or it produced a reason. */\ninterface ViewportShot {\n viewport: ParsedViewport;\n png?: Buffer;\n /** Document width vs viewport width, for the overflow check. */\n layout?: LayoutInfo;\n error?: string;\n}\n\ninterface LayoutInfo {\n scroll_width: number;\n client_width: number;\n scroll_height: number;\n client_height: number;\n}\n\n/**\n * Capture one page at several viewport sizes and return one screenshot per\n * size, labelled, so a whole responsive range can be reviewed in a single\n * look.\n *\n * Each viewport gets its own browser context rather than one page being\n * resized, for two reasons: a page that has already laid itself out at 1440px\n * often keeps state a genuine mobile visitor never had (a menu built by a\n * matchMedia listener that ran once), and independent contexts can load\n * concurrently — three 2s waits cost 2s, not 6s.\n *\n * A viewport that fails is reported next to the ones that worked instead of\n * failing the call: \"desktop is fine, mobile times out\" is itself the finding.\n * Only a run where every viewport failed comes back as an error.\n */\nexport async function captureResponsive(rawInput: ResponsiveInput): Promise<CallToolResult> {\n const parsed = responsiveInputSchema.safeParse(rawInput);\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => `${i.path.join(\".\") || \"input\"}: ${i.message}`).join(\"; \");\n return errorResult(`Responsive capture failed: invalid input — ${issues}`);\n }\n const input = parsed.data;\n\n // Read the auth state once, before any context exists: a missing or stale\n // file is one problem with the call, not one problem per viewport.\n let storageState: StorageState | undefined;\n let auth = null;\n try {\n auth = await resolveStorageState(input.storage_state);\n storageState = auth?.state;\n } catch (error) {\n return errorResult(`Responsive capture of ${input.url} failed: ${describeViewportFailure(input, error)}`);\n }\n\n const shots = await Promise.all(input.viewports.map((viewport) => captureOne(input, viewport, storageState)));\n const captured = shots.filter((shot) => shot.png !== undefined);\n if (captured.length === 0) {\n const reasons = shots.map((shot) => `${shot.viewport.name}: ${shot.error ?? \"no screenshot\"}`).join(\"; \");\n return errorResult(`Responsive capture of ${input.url} failed at every viewport — ${reasons}`);\n }\n\n const content: CallToolResult[\"content\"] = [\n {\n type: \"text\",\n text: summarise(input.url, shots),\n },\n ];\n\n for (const shot of shots) {\n if (!shot.png) {\n content.push({ type: \"text\", text: `${label(shot.viewport)} — failed: ${shot.error ?? \"no screenshot\"}` });\n continue;\n }\n const resized = await resizeForOutput(shot.png);\n const { width, height } = await getDimensions(resized);\n content.push({ type: \"image\", data: toBase64(resized), mimeType: \"image/png\" });\n content.push({ type: \"text\", text: describeShot(shot, width, height) });\n }\n\n return withAuthNote({ content }, auth);\n}\n\n/** Screenshot `url` at one viewport. Never throws — a failure is part of the result. */\nasync function captureOne(\n input: z.output<typeof responsiveInputSchema>,\n viewport: ParsedViewport,\n storageState?: StorageState,\n): Promise<ViewportShot> {\n try {\n const options = {\n viewport: { width: viewport.width, height: viewport.height },\n ...(storageState ? { contextOptions: { storageState } } : {}),\n };\n const result = await withPage(options, async (page) => {\n await page.goto(input.url, { waitUntil: \"load\", timeout: NAVIGATION_TIMEOUT_MS });\n if (input.wait_for) {\n await page.waitForSelector(input.wait_for, { state: \"visible\", timeout: input.wait_for_timeout_ms });\n }\n if (input.wait_ms > 0) {\n await page.waitForTimeout(input.wait_ms);\n }\n const png = await page.screenshot({ type: \"png\" });\n return { png, layout: await readLayout(page) };\n });\n return { viewport, ...result };\n } catch (error) {\n return { viewport, error: describeViewportFailure(input, error) };\n }\n}\n\n/**\n * The document's laid-out size against the viewport it was given. Horizontal\n * overflow is the classic responsive bug and it is invisible in a screenshot —\n * the content that sticks out is simply cropped away — so it is measured\n * rather than left to the eye.\n *\n * Cosmetic: a page that dies before this is read still returns its screenshot.\n */\nasync function readLayout(page: Page): Promise<LayoutInfo | undefined> {\n try {\n // `globalThis as any` rather than the DOM globals: this package is\n // compiled with the Node lib only, and the page it lands in may be\n // mid-teardown, so nothing here can be assumed to exist.\n return await page.evaluate(() => {\n const el = (globalThis as any).document?.documentElement;\n if (!el) return undefined;\n return {\n scroll_width: Math.round(el.scrollWidth),\n client_width: Math.round(el.clientWidth),\n scroll_height: Math.round(el.scrollHeight),\n client_height: Math.round(el.clientHeight),\n };\n });\n } catch {\n return undefined;\n }\n}\n\nfunction label(viewport: ParsedViewport): string {\n return `${viewport.name} ${viewport.width}x${viewport.height}`;\n}\n\n/** One line per viewport: what it is, how big the image is, and whether the content fits. */\nfunction describeShot(shot: ViewportShot, imageWidth: number, imageHeight: number): string {\n const parts = [label(shot.viewport), `image ${imageWidth}x${imageHeight}`];\n\n const layout = shot.layout;\n if (layout) {\n if (layout.scroll_width > layout.client_width + OVERFLOW_TOLERANCE_PX) {\n parts.push(\n `horizontal overflow: content is ${layout.scroll_width}px wide in a ${layout.client_width}px viewport ` +\n `(+${layout.scroll_width - layout.client_width}px)`,\n );\n }\n if (layout.scroll_height > layout.client_height) {\n parts.push(`page scrolls to ${layout.scroll_height}px (${scrolls(layout)} screens)`);\n }\n }\n\n return parts.join(\" — \");\n}\n\nfunction scrolls(layout: LayoutInfo): string {\n return (layout.scroll_height / Math.max(1, layout.client_height)).toFixed(1);\n}\n\n/** The opening line: what was captured, and an up-front warning about anything that overflowed. */\nfunction summarise(url: string, shots: ViewportShot[]): string {\n const captured = shots.filter((shot) => shot.png !== undefined);\n const failed = shots.filter((shot) => shot.png === undefined);\n\n const names = captured.map((shot) => label(shot.viewport)).join(\", \");\n const lines = [`Captured ${url} at ${captured.length} of ${shots.length} viewports: ${names}`];\n\n if (failed.length > 0) {\n lines.push(`Failed: ${failed.map((shot) => shot.viewport.name).join(\", \")}`);\n }\n\n const overflowing = captured.filter(\n (shot) => shot.layout !== undefined && shot.layout.scroll_width > shot.layout.client_width + OVERFLOW_TOLERANCE_PX,\n );\n if (overflowing.length > 0) {\n lines.push(\n `Horizontal overflow at ${overflowing.map((shot) => shot.viewport.name).join(\", \")} — ` +\n \"content is wider than the viewport, so something is sticking out past the right edge.\",\n );\n }\n\n return lines.join(\"\\n\");\n}\n\nfunction errorResult(text: string): CallToolResult {\n return { isError: true, content: [{ type: \"text\", text }] };\n}\n\n/**\n * One actionable line for a viewport that failed. Mirrors `describeFailure` in\n * screenshot.ts: match on the failing Playwright call, never on substrings of\n * a user-supplied selector.\n */\nexport function describeViewportFailure(input: { wait_for?: string; wait_for_timeout_ms: number }, error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n const firstLine = message.split(\"\\n\")[0];\n\n if (/Executable doesn't exist|browserType\\.launch/i.test(message)) {\n return \"Playwright's Chromium browser is not installed. Run `npx playwright install chromium` and try again.\";\n }\n if (input.wait_for && /^page\\.waitForSelector:/.test(message)) {\n return `selector \"${input.wait_for}\" did not become visible within ${input.wait_for_timeout_ms}ms`;\n }\n return firstLine;\n}\n\nexport function registerResponsiveTool(server: McpServer): void {\n server.registerTool(\n RESPONSIVE_TOOL_NAME,\n {\n title: \"Responsive\",\n description:\n \"Screenshot the same page at several viewport sizes in one call (mobile, tablet and desktop by default) \" +\n \"and return one labelled image per size. Each size loads in its own fresh browser context, so a mobile \" +\n \"shot is what a phone would really get rather than a resized desktop layout. Content that is wider than \" +\n \"its viewport is reported as horizontal overflow — the commonest responsive bug, and one a screenshot \" +\n \"alone hides because the overflowing part is simply cropped off.\",\n inputSchema: responsiveInputShape,\n annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },\n },\n async (args) => captureResponsive(args),\n );\n}\n"]}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { type Highlight } from "../utils/highlight.js";
|
|
5
|
+
import { type ElementFinding } from "../utils/rtl-rules.js";
|
|
6
|
+
export declare const RTL_TOOL_NAME = "framewatch_rtl";
|
|
7
|
+
export declare const rtlInputShape: {
|
|
8
|
+
url: z.ZodString;
|
|
9
|
+
rtl_trigger: z.ZodOptional<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
10
|
+
type: z.ZodLiteral<"attribute">;
|
|
11
|
+
attr: z.ZodDefault<z.ZodString>;
|
|
12
|
+
value: z.ZodDefault<z.ZodString>;
|
|
13
|
+
target: z.ZodDefault<z.ZodString>;
|
|
14
|
+
}, "strip", z.ZodTypeAny, {
|
|
15
|
+
type: "attribute";
|
|
16
|
+
value: string;
|
|
17
|
+
target: string;
|
|
18
|
+
attr: string;
|
|
19
|
+
}, {
|
|
20
|
+
type: "attribute";
|
|
21
|
+
value?: string | undefined;
|
|
22
|
+
target?: string | undefined;
|
|
23
|
+
attr?: string | undefined;
|
|
24
|
+
}>, z.ZodObject<{
|
|
25
|
+
type: z.ZodLiteral<"class">;
|
|
26
|
+
class: z.ZodString;
|
|
27
|
+
target: z.ZodDefault<z.ZodString>;
|
|
28
|
+
}, "strip", z.ZodTypeAny, {
|
|
29
|
+
type: "class";
|
|
30
|
+
target: string;
|
|
31
|
+
class: string;
|
|
32
|
+
}, {
|
|
33
|
+
type: "class";
|
|
34
|
+
class: string;
|
|
35
|
+
target?: string | undefined;
|
|
36
|
+
}>, z.ZodObject<{
|
|
37
|
+
type: z.ZodLiteral<"locale">;
|
|
38
|
+
locale: z.ZodDefault<z.ZodString>;
|
|
39
|
+
}, "strip", z.ZodTypeAny, {
|
|
40
|
+
type: "locale";
|
|
41
|
+
locale: string;
|
|
42
|
+
}, {
|
|
43
|
+
type: "locale";
|
|
44
|
+
locale?: string | undefined;
|
|
45
|
+
}>, z.ZodObject<{
|
|
46
|
+
type: z.ZodLiteral<"url">;
|
|
47
|
+
rtl_url: z.ZodString;
|
|
48
|
+
}, "strip", z.ZodTypeAny, {
|
|
49
|
+
type: "url";
|
|
50
|
+
rtl_url: string;
|
|
51
|
+
}, {
|
|
52
|
+
type: "url";
|
|
53
|
+
rtl_url: string;
|
|
54
|
+
}>]>>;
|
|
55
|
+
inject_arabic: z.ZodDefault<z.ZodBoolean>;
|
|
56
|
+
selector: z.ZodOptional<z.ZodString>;
|
|
57
|
+
exclude: z.ZodOptional<z.ZodString>;
|
|
58
|
+
wait_ms: z.ZodDefault<z.ZodNumber>;
|
|
59
|
+
wait_for: z.ZodOptional<z.ZodString>;
|
|
60
|
+
wait_for_timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
61
|
+
max_elements: z.ZodDefault<z.ZodNumber>;
|
|
62
|
+
full_page: z.ZodDefault<z.ZodBoolean>;
|
|
63
|
+
viewport: z.ZodOptional<z.ZodObject<{
|
|
64
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
65
|
+
height: z.ZodDefault<z.ZodNumber>;
|
|
66
|
+
}, "strip", z.ZodTypeAny, {
|
|
67
|
+
width: number;
|
|
68
|
+
height: number;
|
|
69
|
+
}, {
|
|
70
|
+
width?: number | undefined;
|
|
71
|
+
height?: number | undefined;
|
|
72
|
+
}>>;
|
|
73
|
+
storage_state: z.ZodOptional<z.ZodString>;
|
|
74
|
+
};
|
|
75
|
+
export declare const rtlInputSchema: z.ZodObject<{
|
|
76
|
+
url: z.ZodString;
|
|
77
|
+
rtl_trigger: z.ZodOptional<z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
|
|
78
|
+
type: z.ZodLiteral<"attribute">;
|
|
79
|
+
attr: z.ZodDefault<z.ZodString>;
|
|
80
|
+
value: z.ZodDefault<z.ZodString>;
|
|
81
|
+
target: z.ZodDefault<z.ZodString>;
|
|
82
|
+
}, "strip", z.ZodTypeAny, {
|
|
83
|
+
type: "attribute";
|
|
84
|
+
value: string;
|
|
85
|
+
target: string;
|
|
86
|
+
attr: string;
|
|
87
|
+
}, {
|
|
88
|
+
type: "attribute";
|
|
89
|
+
value?: string | undefined;
|
|
90
|
+
target?: string | undefined;
|
|
91
|
+
attr?: string | undefined;
|
|
92
|
+
}>, z.ZodObject<{
|
|
93
|
+
type: z.ZodLiteral<"class">;
|
|
94
|
+
class: z.ZodString;
|
|
95
|
+
target: z.ZodDefault<z.ZodString>;
|
|
96
|
+
}, "strip", z.ZodTypeAny, {
|
|
97
|
+
type: "class";
|
|
98
|
+
target: string;
|
|
99
|
+
class: string;
|
|
100
|
+
}, {
|
|
101
|
+
type: "class";
|
|
102
|
+
class: string;
|
|
103
|
+
target?: string | undefined;
|
|
104
|
+
}>, z.ZodObject<{
|
|
105
|
+
type: z.ZodLiteral<"locale">;
|
|
106
|
+
locale: z.ZodDefault<z.ZodString>;
|
|
107
|
+
}, "strip", z.ZodTypeAny, {
|
|
108
|
+
type: "locale";
|
|
109
|
+
locale: string;
|
|
110
|
+
}, {
|
|
111
|
+
type: "locale";
|
|
112
|
+
locale?: string | undefined;
|
|
113
|
+
}>, z.ZodObject<{
|
|
114
|
+
type: z.ZodLiteral<"url">;
|
|
115
|
+
rtl_url: z.ZodString;
|
|
116
|
+
}, "strip", z.ZodTypeAny, {
|
|
117
|
+
type: "url";
|
|
118
|
+
rtl_url: string;
|
|
119
|
+
}, {
|
|
120
|
+
type: "url";
|
|
121
|
+
rtl_url: string;
|
|
122
|
+
}>]>>;
|
|
123
|
+
inject_arabic: z.ZodDefault<z.ZodBoolean>;
|
|
124
|
+
selector: z.ZodOptional<z.ZodString>;
|
|
125
|
+
exclude: z.ZodOptional<z.ZodString>;
|
|
126
|
+
wait_ms: z.ZodDefault<z.ZodNumber>;
|
|
127
|
+
wait_for: z.ZodOptional<z.ZodString>;
|
|
128
|
+
wait_for_timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
129
|
+
max_elements: z.ZodDefault<z.ZodNumber>;
|
|
130
|
+
full_page: z.ZodDefault<z.ZodBoolean>;
|
|
131
|
+
viewport: z.ZodOptional<z.ZodObject<{
|
|
132
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
133
|
+
height: z.ZodDefault<z.ZodNumber>;
|
|
134
|
+
}, "strip", z.ZodTypeAny, {
|
|
135
|
+
width: number;
|
|
136
|
+
height: number;
|
|
137
|
+
}, {
|
|
138
|
+
width?: number | undefined;
|
|
139
|
+
height?: number | undefined;
|
|
140
|
+
}>>;
|
|
141
|
+
storage_state: z.ZodOptional<z.ZodString>;
|
|
142
|
+
}, "strip", z.ZodTypeAny, {
|
|
143
|
+
url: string;
|
|
144
|
+
wait_ms: number;
|
|
145
|
+
wait_for_timeout_ms: number;
|
|
146
|
+
max_elements: number;
|
|
147
|
+
full_page: boolean;
|
|
148
|
+
inject_arabic: boolean;
|
|
149
|
+
viewport?: {
|
|
150
|
+
width: number;
|
|
151
|
+
height: number;
|
|
152
|
+
} | undefined;
|
|
153
|
+
wait_for?: string | undefined;
|
|
154
|
+
storage_state?: string | undefined;
|
|
155
|
+
selector?: string | undefined;
|
|
156
|
+
exclude?: string | undefined;
|
|
157
|
+
rtl_trigger?: {
|
|
158
|
+
type: "attribute";
|
|
159
|
+
value: string;
|
|
160
|
+
target: string;
|
|
161
|
+
attr: string;
|
|
162
|
+
} | {
|
|
163
|
+
type: "class";
|
|
164
|
+
target: string;
|
|
165
|
+
class: string;
|
|
166
|
+
} | {
|
|
167
|
+
type: "locale";
|
|
168
|
+
locale: string;
|
|
169
|
+
} | {
|
|
170
|
+
type: "url";
|
|
171
|
+
rtl_url: string;
|
|
172
|
+
} | undefined;
|
|
173
|
+
}, {
|
|
174
|
+
url: string;
|
|
175
|
+
viewport?: {
|
|
176
|
+
width?: number | undefined;
|
|
177
|
+
height?: number | undefined;
|
|
178
|
+
} | undefined;
|
|
179
|
+
wait_ms?: number | undefined;
|
|
180
|
+
wait_for?: string | undefined;
|
|
181
|
+
wait_for_timeout_ms?: number | undefined;
|
|
182
|
+
max_elements?: number | undefined;
|
|
183
|
+
storage_state?: string | undefined;
|
|
184
|
+
selector?: string | undefined;
|
|
185
|
+
exclude?: string | undefined;
|
|
186
|
+
full_page?: boolean | undefined;
|
|
187
|
+
rtl_trigger?: {
|
|
188
|
+
type: "attribute";
|
|
189
|
+
value?: string | undefined;
|
|
190
|
+
target?: string | undefined;
|
|
191
|
+
attr?: string | undefined;
|
|
192
|
+
} | {
|
|
193
|
+
type: "class";
|
|
194
|
+
class: string;
|
|
195
|
+
target?: string | undefined;
|
|
196
|
+
} | {
|
|
197
|
+
type: "locale";
|
|
198
|
+
locale?: string | undefined;
|
|
199
|
+
} | {
|
|
200
|
+
type: "url";
|
|
201
|
+
rtl_url: string;
|
|
202
|
+
} | undefined;
|
|
203
|
+
inject_arabic?: boolean | undefined;
|
|
204
|
+
}>;
|
|
205
|
+
export type RtlInput = z.input<typeof rtlInputSchema>;
|
|
206
|
+
/**
|
|
207
|
+
* Test a page in LTR and RTL and report what failed to mirror.
|
|
208
|
+
*
|
|
209
|
+
* The page is loaded twice — once as it ships, once flipped — and every
|
|
210
|
+
* element is measured in both. A finding is something that *did not change*
|
|
211
|
+
* when the LTR measurement proves it should have: a box that sits at the same
|
|
212
|
+
* x in both directions, text still hugging the left edge, physical padding
|
|
213
|
+
* that never swapped. Plus the one thing that should not change and did:
|
|
214
|
+
* content that fits in LTR and hangs off the edge in RTL, where it is silently
|
|
215
|
+
* cropped and therefore invisible in a screenshot.
|
|
216
|
+
*
|
|
217
|
+
* Nothing is judged from the RTL rendering alone, which is what separates this
|
|
218
|
+
* from a linter. `text-align: left` is correct on a code block and a number
|
|
219
|
+
* column; `padding-left` is correct on anything that should not mirror. Only
|
|
220
|
+
* the comparison can tell a deliberate physical value from a forgotten one,
|
|
221
|
+
* and a report that cannot tell them apart is one nobody reads twice.
|
|
222
|
+
*/
|
|
223
|
+
export declare function testRtl(rawInput: RtlInput): Promise<CallToolResult>;
|
|
224
|
+
/**
|
|
225
|
+
* Draw the findings onto the RTL page before it is photographed.
|
|
226
|
+
*
|
|
227
|
+
* Numbered to match the list, and coloured by severity. Drawn after everything
|
|
228
|
+
* has been measured, because the overlay is itself a DOM mutation.
|
|
229
|
+
*/
|
|
230
|
+
export declare function highlightsFor(findings: readonly ElementFinding[]): Highlight[];
|
|
231
|
+
/**
|
|
232
|
+
* One actionable line for a failed run. Mirrors `describeFailure` in
|
|
233
|
+
* screenshot.ts: match on the failing Playwright call, never on substrings of
|
|
234
|
+
* a user-supplied selector.
|
|
235
|
+
*/
|
|
236
|
+
export declare function describeRtlFailure(input: {
|
|
237
|
+
url: string;
|
|
238
|
+
wait_for?: string;
|
|
239
|
+
wait_for_timeout_ms: number;
|
|
240
|
+
}, error: unknown): string;
|
|
241
|
+
export declare function registerRtlTool(server: McpServer): void;
|