framewatch-mcp-server 0.1.0 → 0.1.1
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 +98 -7
- package/dist/constants.d.ts +10 -0
- package/dist/constants.js +11 -0
- package/dist/constants.js.map +1 -1
- package/dist/engine/browser.d.ts +20 -4
- package/dist/engine/browser.js +22 -9
- package/dist/engine/browser.js.map +1 -1
- package/dist/engine/interaction.d.ts +6 -6
- package/dist/engine/interaction.js +89 -12
- package/dist/engine/interaction.js.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/accessibility.d.ts +4 -0
- package/dist/tools/accessibility.js +7 -1
- package/dist/tools/accessibility.js.map +1 -1
- package/dist/tools/capture.d.ts +21 -17
- package/dist/tools/capture.js +7 -1
- package/dist/tools/capture.js.map +1 -1
- package/dist/tools/compare.d.ts +4 -0
- package/dist/tools/compare.js +14 -4
- package/dist/tools/compare.js.map +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/interact.d.ts +12 -6
- package/dist/tools/interact.js +21 -7
- package/dist/tools/interact.js.map +1 -1
- package/dist/tools/responsive.d.ts +4 -0
- package/dist/tools/responsive.js +20 -3
- package/dist/tools/responsive.js.map +1 -1
- 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 +4 -1
- package/dist/tools/screenshot.js.map +1 -1
- package/dist/utils/storage-state.d.ts +35 -0
- package/dist/utils/storage-state.js +84 -0
- package/dist/utils/storage-state.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { DEFAULT_SCREENSHOT_WAIT_MS, DEFAULT_VIEWPORT, NAVIGATION_TIMEOUT_MS, SELECTOR_TIMEOUT_MS } from \"../constants.js\";\nimport { withPage } from \"../engine/browser.js\";\nimport { getDimensions, resizeForOutput, toBase64 } from \"../utils/image.js\";\n\nexport const SCREENSHOT_TOOL_NAME = \"framewatch_screenshot\";\n\nexport const screenshotInputShape = {\n url: z\n .string()\n .url()\n .describe(\"URL to screenshot, e.g. http://localhost:3000 (http, https and file URLs are accepted)\"),\n wait_ms: z\n .number()\n .int()\n .min(0)\n .default(DEFAULT_SCREENSHOT_WAIT_MS)\n .describe(\"Wait time (ms) after page load before screenshot\"),\n viewport: z\n .object({\n width: z.number().int().min(1).default(DEFAULT_VIEWPORT.width),\n height: z.number().int().min(1).default(DEFAULT_VIEWPORT.height),\n })\n .optional()\n .describe(\"Viewport size (defaults to 1280x720)\"),\n selector: z.string().optional().describe(\"CSS selector to screenshot a specific element instead of the viewport\"),\n wait_for: z.string().optional().describe(\"CSS selector to wait for (visible) before taking the 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` / `selector` to appear (must be > 0)\"),\n};\n\nexport const screenshotInputSchema = z.object(screenshotInputShape);\nexport type ScreenshotInput = z.input<typeof screenshotInputSchema>;\ntype ParsedScreenshotInput = z.output<typeof screenshotInputSchema>;\n\n/**\n * Take a single screenshot of a page and return it as an MCP image content\n * block (base64 PNG, resized to max OUTPUT_MAX_WIDTH wide) plus a one-line\n * text summary. All failures — including invalid input — are reported as\n * `isError` results rather than thrown so the MCP client sees a useful message.\n */\nexport async function takeScreenshot(rawInput: ScreenshotInput): Promise<CallToolResult> {\n const parsed = screenshotInputSchema.safeParse(rawInput);\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => `${i.path.join(\".\") || \"input\"}: ${i.message}`).join(\"; \");\n return errorResult(`Screenshot failed: invalid input — ${issues}`);\n }\n const input = parsed.data;\n const viewport = input.viewport ?? { ...DEFAULT_VIEWPORT };\n\n try {\n const shot = await withPage({ viewport }, async (page) => {\n const response = await page.goto(input.url, { waitUntil: \"load\", timeout: NAVIGATION_TIMEOUT_MS });\n\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 const png = input.selector\n ? await page.locator(input.selector).first().screenshot({ type: \"png\", timeout: input.wait_for_timeout_ms })\n : await page.screenshot({ type: \"png\" });\n\n return { png, title: await page.title(), finalUrl: page.url(), status: response?.status() ?? null };\n });\n\n const resized = await resizeForOutput(shot.png);\n const { width, height } = await getDimensions(resized);\n\n const summaryParts = [\n `Screenshot of ${shot.finalUrl}`,\n shot.status !== null && shot.status >= 400 ? `HTTP ${shot.status}` : null,\n shot.title ? `\"${shot.title}\"` : null,\n `${width}x${height}`,\n `viewport ${viewport.width}x${viewport.height}`,\n input.selector ? `element ${input.selector}` : null,\n ].filter((p): p is string => p !== null);\n\n return {\n content: [\n { type: \"image\", data: toBase64(resized), mimeType: \"image/png\" },\n { type: \"text\", text: summaryParts.join(\" — \") },\n ],\n };\n } catch (error) {\n return errorResult(describeFailure(input, error));\n }\n}\n\nfunction errorResult(text: string): CallToolResult {\n return { isError: true, content: [{ type: \"text\", text }] };\n}\n\n/**\n * Turn a Playwright/Node error into a one-line, actionable message. Matches on\n * the failing Playwright call (the message prefix) rather than on substrings\n * of user-supplied selectors, so a navigation failure is never blamed on an\n * element.\n */\nexport function describeFailure(\n input: Pick<ParsedScreenshotInput, \"url\" | \"wait_for\" | \"selector\" | \"wait_for_timeout_ms\">,\n error: unknown,\n): string {\n const message = error instanceof Error ? error.message : String(error);\n const firstLine = message.split(\"\\n\")[0];\n const prefix = `Screenshot 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. (${firstLine})`\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 (input.selector && /^locator\\.screenshot:/.test(message)) {\n return `${prefix} element \"${input.selector}\" not found or not visible within ${input.wait_for_timeout_ms}ms. ${firstLine}`;\n }\n return `${prefix} ${firstLine}`;\n}\n\nexport function registerScreenshotTool(server: McpServer): void {\n server.registerTool(\n SCREENSHOT_TOOL_NAME,\n {\n title: \"Screenshot\",\n description:\n \"Take a single screenshot of a web page (or one element on it) and return it as a PNG image. \" +\n \"Good for checking the current visual state of a running app.\",\n inputSchema: screenshotInputShape,\n annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },\n },\n async (args) => takeScreenshot(args),\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"screenshot.js","sourceRoot":"","sources":["../../src/tools/screenshot.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport type { CallToolResult } from \"@modelcontextprotocol/sdk/types.js\";\nimport { DEFAULT_SCREENSHOT_WAIT_MS, DEFAULT_VIEWPORT, NAVIGATION_TIMEOUT_MS, SELECTOR_TIMEOUT_MS } from \"../constants.js\";\nimport { withPage } from \"../engine/browser.js\";\nimport { getDimensions, resizeForOutput, toBase64 } from \"../utils/image.js\";\nimport { readStorageState, storageStateField } from \"../utils/storage-state.js\";\n\nexport const SCREENSHOT_TOOL_NAME = \"framewatch_screenshot\";\n\nexport const screenshotInputShape = {\n url: z\n .string()\n .url()\n .describe(\"URL to screenshot, e.g. http://localhost:3000 (http, https and file URLs are accepted)\"),\n wait_ms: z\n .number()\n .int()\n .min(0)\n .default(DEFAULT_SCREENSHOT_WAIT_MS)\n .describe(\"Wait time (ms) after page load before screenshot\"),\n viewport: z\n .object({\n width: z.number().int().min(1).default(DEFAULT_VIEWPORT.width),\n height: z.number().int().min(1).default(DEFAULT_VIEWPORT.height),\n })\n .optional()\n .describe(\"Viewport size (defaults to 1280x720)\"),\n selector: z.string().optional().describe(\"CSS selector to screenshot a specific element instead of the viewport\"),\n wait_for: z.string().optional().describe(\"CSS selector to wait for (visible) before taking the 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` / `selector` to appear (must be > 0)\"),\n storage_state: storageStateField,\n};\n\nexport const screenshotInputSchema = z.object(screenshotInputShape);\nexport type ScreenshotInput = z.input<typeof screenshotInputSchema>;\ntype ParsedScreenshotInput = z.output<typeof screenshotInputSchema>;\n\n/**\n * Take a single screenshot of a page and return it as an MCP image content\n * block (base64 PNG, resized to max OUTPUT_MAX_WIDTH wide) plus a one-line\n * text summary. All failures — including invalid input — are reported as\n * `isError` results rather than thrown so the MCP client sees a useful message.\n */\nexport async function takeScreenshot(rawInput: ScreenshotInput): Promise<CallToolResult> {\n const parsed = screenshotInputSchema.safeParse(rawInput);\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => `${i.path.join(\".\") || \"input\"}: ${i.message}`).join(\"; \");\n return errorResult(`Screenshot failed: invalid input — ${issues}`);\n }\n const input = parsed.data;\n const viewport = input.viewport ?? { ...DEFAULT_VIEWPORT };\n\n try {\n const contextOptions = input.storage_state ? { storageState: await readStorageState(input.storage_state) } : {};\n const shot = await withPage({ viewport, contextOptions }, async (page) => {\n const response = await page.goto(input.url, { waitUntil: \"load\", timeout: NAVIGATION_TIMEOUT_MS });\n\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 const png = input.selector\n ? await page.locator(input.selector).first().screenshot({ type: \"png\", timeout: input.wait_for_timeout_ms })\n : await page.screenshot({ type: \"png\" });\n\n return { png, title: await page.title(), finalUrl: page.url(), status: response?.status() ?? null };\n });\n\n const resized = await resizeForOutput(shot.png);\n const { width, height } = await getDimensions(resized);\n\n const summaryParts = [\n `Screenshot of ${shot.finalUrl}`,\n shot.status !== null && shot.status >= 400 ? `HTTP ${shot.status}` : null,\n shot.title ? `\"${shot.title}\"` : null,\n `${width}x${height}`,\n `viewport ${viewport.width}x${viewport.height}`,\n input.selector ? `element ${input.selector}` : null,\n ].filter((p): p is string => p !== null);\n\n return {\n content: [\n { type: \"image\", data: toBase64(resized), mimeType: \"image/png\" },\n { type: \"text\", text: summaryParts.join(\" — \") },\n ],\n };\n } catch (error) {\n return errorResult(describeFailure(input, error));\n }\n}\n\nfunction errorResult(text: string): CallToolResult {\n return { isError: true, content: [{ type: \"text\", text }] };\n}\n\n/**\n * Turn a Playwright/Node error into a one-line, actionable message. Matches on\n * the failing Playwright call (the message prefix) rather than on substrings\n * of user-supplied selectors, so a navigation failure is never blamed on an\n * element.\n */\nexport function describeFailure(\n input: Pick<ParsedScreenshotInput, \"url\" | \"wait_for\" | \"selector\" | \"wait_for_timeout_ms\">,\n error: unknown,\n): string {\n const message = error instanceof Error ? error.message : String(error);\n const firstLine = message.split(\"\\n\")[0];\n const prefix = `Screenshot 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. (${firstLine})`\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 (input.selector && /^locator\\.screenshot:/.test(message)) {\n return `${prefix} element \"${input.selector}\" not found or not visible within ${input.wait_for_timeout_ms}ms. ${firstLine}`;\n }\n return `${prefix} ${firstLine}`;\n}\n\nexport function registerScreenshotTool(server: McpServer): void {\n server.registerTool(\n SCREENSHOT_TOOL_NAME,\n {\n title: \"Screenshot\",\n description:\n \"Take a single screenshot of a web page (or one element on it) and return it as a PNG image. \" +\n \"Good for checking the current visual state of a running app.\",\n inputSchema: screenshotInputShape,\n annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },\n },\n async (args) => takeScreenshot(args),\n );\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { BrowserContext } from "playwright";
|
|
3
|
+
/**
|
|
4
|
+
* Saved browser state — cookies plus per-origin localStorage.
|
|
5
|
+
*
|
|
6
|
+
* This is exactly what Playwright's `context.storageState()` returns and what
|
|
7
|
+
* `browser.newContext({ storageState })` accepts, which is the whole point:
|
|
8
|
+
* `framewatch_save_auth` runs a login once, writes this to disk, and every
|
|
9
|
+
* other tool loads it instead of replaying the flow (see CLAUDE.md
|
|
10
|
+
* "Auth persistence").
|
|
11
|
+
*
|
|
12
|
+
* The type is spelled out rather than imported because Playwright exports it
|
|
13
|
+
* only as the return type of a method, and the tools need to name it.
|
|
14
|
+
*/
|
|
15
|
+
export type StorageState = Awaited<ReturnType<BrowserContext["storageState"]>>;
|
|
16
|
+
/**
|
|
17
|
+
* Read a state file written by `framewatch_save_auth`.
|
|
18
|
+
*
|
|
19
|
+
* Every failure — no file, unreadable file, not JSON, JSON of the wrong shape
|
|
20
|
+
* — comes back as one line naming the path and the tool that produces a good
|
|
21
|
+
* one. A stale or missing auth file is the single most likely thing to go
|
|
22
|
+
* wrong in an authenticated capture, and "ENOENT: no such file or directory,
|
|
23
|
+
* open '…'" is not what the reader needs to see.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readStorageState(path: string): Promise<StorageState>;
|
|
26
|
+
/** Write a state file, creating the directories on the way to it. */
|
|
27
|
+
export declare function writeStorageState(state: StorageState, path: string): Promise<void>;
|
|
28
|
+
/** One phrase describing what a state actually carries, for the tool's summary. */
|
|
29
|
+
export declare function storageStateSummary(state: StorageState): string;
|
|
30
|
+
/**
|
|
31
|
+
* The `storage_state` input every page-opening tool accepts. One definition so
|
|
32
|
+
* the wording (and therefore what an agent believes it does) is the same in
|
|
33
|
+
* all of them.
|
|
34
|
+
*/
|
|
35
|
+
export declare const storageStateField: z.ZodOptional<z.ZodString>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
/**
|
|
5
|
+
* Read a state file written by `framewatch_save_auth`.
|
|
6
|
+
*
|
|
7
|
+
* Every failure — no file, unreadable file, not JSON, JSON of the wrong shape
|
|
8
|
+
* — comes back as one line naming the path and the tool that produces a good
|
|
9
|
+
* one. A stale or missing auth file is the single most likely thing to go
|
|
10
|
+
* wrong in an authenticated capture, and "ENOENT: no such file or directory,
|
|
11
|
+
* open '…'" is not what the reader needs to see.
|
|
12
|
+
*/
|
|
13
|
+
export async function readStorageState(path) {
|
|
14
|
+
let raw;
|
|
15
|
+
try {
|
|
16
|
+
raw = await readFile(path, "utf8");
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
const code = error.code;
|
|
20
|
+
const reason = code === "ENOENT"
|
|
21
|
+
? "not found"
|
|
22
|
+
: code === "EISDIR"
|
|
23
|
+
? "is a directory, not a file"
|
|
24
|
+
: code === "EACCES"
|
|
25
|
+
? "could not be read (permission denied)"
|
|
26
|
+
: `could not be read (${firstLine(error)})`;
|
|
27
|
+
throw new Error(`${prefix(path)} ${reason}. ${RECREATE}`);
|
|
28
|
+
}
|
|
29
|
+
let parsed;
|
|
30
|
+
try {
|
|
31
|
+
parsed = JSON.parse(raw);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new Error(`${prefix(path)} is not valid JSON (${firstLine(error)}). ${RECREATE}`);
|
|
35
|
+
}
|
|
36
|
+
if (!isStorageState(parsed)) {
|
|
37
|
+
throw new Error(`${prefix(path)} is not a browser state file — it has no \`cookies\` and \`origins\` arrays. ${RECREATE}`);
|
|
38
|
+
}
|
|
39
|
+
return parsed;
|
|
40
|
+
}
|
|
41
|
+
/** Write a state file, creating the directories on the way to it. */
|
|
42
|
+
export async function writeStorageState(state, path) {
|
|
43
|
+
const parent = dirname(path);
|
|
44
|
+
if (parent)
|
|
45
|
+
await mkdir(parent, { recursive: true });
|
|
46
|
+
await writeFile(path, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
47
|
+
}
|
|
48
|
+
/** One phrase describing what a state actually carries, for the tool's summary. */
|
|
49
|
+
export function storageStateSummary(state) {
|
|
50
|
+
const cookies = state.cookies.length;
|
|
51
|
+
const origins = state.origins.length;
|
|
52
|
+
const keys = state.origins.reduce((total, origin) => total + origin.localStorage.length, 0);
|
|
53
|
+
if (cookies === 0 && keys === 0)
|
|
54
|
+
return "no cookies and nothing in storage";
|
|
55
|
+
return `${count(cookies, "cookie")}, ${count(origins, "origin")} with ${count(keys, "stored key")}`;
|
|
56
|
+
}
|
|
57
|
+
function count(n, noun) {
|
|
58
|
+
return `${n} ${noun}${n === 1 ? "" : "s"}`;
|
|
59
|
+
}
|
|
60
|
+
const RECREATE = "Run framewatch_save_auth to create it.";
|
|
61
|
+
function prefix(path) {
|
|
62
|
+
return `Auth state file "${path}"`;
|
|
63
|
+
}
|
|
64
|
+
function firstLine(error) {
|
|
65
|
+
return (error instanceof Error ? error.message : String(error)).split("\n")[0];
|
|
66
|
+
}
|
|
67
|
+
function isStorageState(value) {
|
|
68
|
+
if (typeof value !== "object" || value === null)
|
|
69
|
+
return false;
|
|
70
|
+
const { cookies, origins } = value;
|
|
71
|
+
return Array.isArray(cookies) && Array.isArray(origins);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The `storage_state` input every page-opening tool accepts. One definition so
|
|
75
|
+
* the wording (and therefore what an agent believes it does) is the same in
|
|
76
|
+
* all of them.
|
|
77
|
+
*/
|
|
78
|
+
export const storageStateField = z
|
|
79
|
+
.string()
|
|
80
|
+
.optional()
|
|
81
|
+
.describe("Path to an auth state file saved by framewatch_save_auth. The page opens with those cookies and " +
|
|
82
|
+
"storage already in place, so a login/gate does not have to be replayed. If the saved session has " +
|
|
83
|
+
"expired the login screen simply shows up in the result — re-run framewatch_save_auth then.");
|
|
84
|
+
//# sourceMappingURL=storage-state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage-state.js","sourceRoot":"","sources":["../../src/utils/storage-state.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport { z } from \"zod\";\nimport type { BrowserContext } from \"playwright\";\n\n/**\n * Saved browser state — cookies plus per-origin localStorage.\n *\n * This is exactly what Playwright's `context.storageState()` returns and what\n * `browser.newContext({ storageState })` accepts, which is the whole point:\n * `framewatch_save_auth` runs a login once, writes this to disk, and every\n * other tool loads it instead of replaying the flow (see CLAUDE.md\n * \"Auth persistence\").\n *\n * The type is spelled out rather than imported because Playwright exports it\n * only as the return type of a method, and the tools need to name it.\n */\nexport type StorageState = Awaited<ReturnType<BrowserContext[\"storageState\"]>>;\n\n/**\n * Read a state file written by `framewatch_save_auth`.\n *\n * Every failure — no file, unreadable file, not JSON, JSON of the wrong shape\n * — comes back as one line naming the path and the tool that produces a good\n * one. A stale or missing auth file is the single most likely thing to go\n * wrong in an authenticated capture, and \"ENOENT: no such file or directory,\n * open '…'\" is not what the reader needs to see.\n */\nexport async function readStorageState(path: string): Promise<StorageState> {\n let raw: string;\n try {\n raw = await readFile(path, \"utf8\");\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n const reason =\n code === \"ENOENT\"\n ? \"not found\"\n : code === \"EISDIR\"\n ? \"is a directory, not a file\"\n : code === \"EACCES\"\n ? \"could not be read (permission denied)\"\n : `could not be read (${firstLine(error)})`;\n throw new Error(`${prefix(path)} ${reason}. ${RECREATE}`);\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (error) {\n throw new Error(`${prefix(path)} is not valid JSON (${firstLine(error)}). ${RECREATE}`);\n }\n\n if (!isStorageState(parsed)) {\n throw new Error(\n `${prefix(path)} is not a browser state file — it has no \\`cookies\\` and \\`origins\\` arrays. ${RECREATE}`,\n );\n }\n return parsed;\n}\n\n/** Write a state file, creating the directories on the way to it. */\nexport async function writeStorageState(state: StorageState, path: string): Promise<void> {\n const parent = dirname(path);\n if (parent) await mkdir(parent, { recursive: true });\n await writeFile(path, `${JSON.stringify(state, null, 2)}\\n`, \"utf8\");\n}\n\n/** One phrase describing what a state actually carries, for the tool's summary. */\nexport function storageStateSummary(state: StorageState): string {\n const cookies = state.cookies.length;\n const origins = state.origins.length;\n const keys = state.origins.reduce((total, origin) => total + origin.localStorage.length, 0);\n if (cookies === 0 && keys === 0) return \"no cookies and nothing in storage\";\n return `${count(cookies, \"cookie\")}, ${count(origins, \"origin\")} with ${count(keys, \"stored key\")}`;\n}\n\nfunction count(n: number, noun: string): string {\n return `${n} ${noun}${n === 1 ? \"\" : \"s\"}`;\n}\n\nconst RECREATE = \"Run framewatch_save_auth to create it.\";\n\nfunction prefix(path: string): string {\n return `Auth state file \"${path}\"`;\n}\n\nfunction firstLine(error: unknown): string {\n return (error instanceof Error ? error.message : String(error)).split(\"\\n\")[0];\n}\n\nfunction isStorageState(value: unknown): value is StorageState {\n if (typeof value !== \"object\" || value === null) return false;\n const { cookies, origins } = value as { cookies?: unknown; origins?: unknown };\n return Array.isArray(cookies) && Array.isArray(origins);\n}\n\n/**\n * The `storage_state` input every page-opening tool accepts. One definition so\n * the wording (and therefore what an agent believes it does) is the same in\n * all of them.\n */\nexport const storageStateField = z\n .string()\n .optional()\n .describe(\n \"Path to an auth state file saved by framewatch_save_auth. The page opens with those cookies and \" +\n \"storage already in place, so a login/gate does not have to be replayed. If the saved session has \" +\n \"expired the login screen simply shows up in the result — re-run framewatch_save_auth then.\",\n );\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "framewatch-mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "MCP server for smart visual testing of web apps — frame capture, diffing, and interaction replay",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "kekoDev (https://github.com/kekoDev)",
|