framewatch-mcp-server 0.1.1 → 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 +797 -8
- package/dist/constants.d.ts +264 -0
- package/dist/constants.js +268 -0
- package/dist/constants.js.map +1 -1
- package/dist/engine/browser.js +4 -0
- 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 +6 -1
- package/dist/engine/interaction.js +21 -6
- 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 +42 -1
- package/dist/index.js.map +1 -1
- package/dist/tools/accessibility.js +4 -3
- 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 +83 -23
- package/dist/tools/capture.js +105 -60
- package/dist/tools/capture.js.map +1 -1
- package/dist/tools/compare.js +4 -3
- 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 +16 -1
- package/dist/tools/index.js +42 -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 +29 -16
- package/dist/tools/interact.js +99 -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 +6 -6
- package/dist/tools/responsive.js +9 -9
- 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 +22 -22
- package/dist/tools/screenshot.js +13 -5
- 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 +42 -1
- package/dist/utils/storage-state.js +115 -4
- package/dist/utils/storage-state.js.map +1 -1
- 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
|
@@ -13,8 +13,8 @@ export declare const saveAuthInteractionSchema: z.ZodEffects<z.ZodObject<{
|
|
|
13
13
|
delta_y: z.ZodOptional<z.ZodNumber>;
|
|
14
14
|
action: z.ZodEnum<["click", "tap", "type", "key", "scroll", "swipe", "hover", "select", "wait", "navigate"]>;
|
|
15
15
|
}, "strip", z.ZodTypeAny, {
|
|
16
|
-
action: "type" | "click" | "tap" | "key" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
17
16
|
delay_ms: number;
|
|
17
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
18
18
|
value?: string | undefined;
|
|
19
19
|
x?: number | undefined;
|
|
20
20
|
y?: number | undefined;
|
|
@@ -22,17 +22,17 @@ export declare const saveAuthInteractionSchema: z.ZodEffects<z.ZodObject<{
|
|
|
22
22
|
delta_x?: number | undefined;
|
|
23
23
|
delta_y?: number | undefined;
|
|
24
24
|
}, {
|
|
25
|
-
action: "type" | "
|
|
25
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
26
26
|
value?: string | undefined;
|
|
27
|
+
delay_ms?: number | undefined;
|
|
27
28
|
x?: number | undefined;
|
|
28
29
|
y?: number | undefined;
|
|
29
30
|
selector?: string | undefined;
|
|
30
31
|
delta_x?: number | undefined;
|
|
31
32
|
delta_y?: number | undefined;
|
|
32
|
-
delay_ms?: number | undefined;
|
|
33
33
|
}>, {
|
|
34
|
-
action: "type" | "click" | "tap" | "key" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
35
34
|
delay_ms: number;
|
|
35
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
36
36
|
value?: string | undefined;
|
|
37
37
|
x?: number | undefined;
|
|
38
38
|
y?: number | undefined;
|
|
@@ -40,14 +40,14 @@ export declare const saveAuthInteractionSchema: z.ZodEffects<z.ZodObject<{
|
|
|
40
40
|
delta_x?: number | undefined;
|
|
41
41
|
delta_y?: number | undefined;
|
|
42
42
|
}, {
|
|
43
|
-
action: "type" | "
|
|
43
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
44
44
|
value?: string | undefined;
|
|
45
|
+
delay_ms?: number | undefined;
|
|
45
46
|
x?: number | undefined;
|
|
46
47
|
y?: number | undefined;
|
|
47
48
|
selector?: string | undefined;
|
|
48
49
|
delta_x?: number | undefined;
|
|
49
50
|
delta_y?: number | undefined;
|
|
50
|
-
delay_ms?: number | undefined;
|
|
51
51
|
}>;
|
|
52
52
|
export declare const saveAuthInputShape: {
|
|
53
53
|
url: z.ZodString;
|
|
@@ -61,8 +61,8 @@ export declare const saveAuthInputShape: {
|
|
|
61
61
|
delta_y: z.ZodOptional<z.ZodNumber>;
|
|
62
62
|
action: z.ZodEnum<["click", "tap", "type", "key", "scroll", "swipe", "hover", "select", "wait", "navigate"]>;
|
|
63
63
|
}, "strip", z.ZodTypeAny, {
|
|
64
|
-
action: "type" | "click" | "tap" | "key" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
65
64
|
delay_ms: number;
|
|
65
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
66
66
|
value?: string | undefined;
|
|
67
67
|
x?: number | undefined;
|
|
68
68
|
y?: number | undefined;
|
|
@@ -70,17 +70,17 @@ export declare const saveAuthInputShape: {
|
|
|
70
70
|
delta_x?: number | undefined;
|
|
71
71
|
delta_y?: number | undefined;
|
|
72
72
|
}, {
|
|
73
|
-
action: "type" | "
|
|
73
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
74
74
|
value?: string | undefined;
|
|
75
|
+
delay_ms?: number | undefined;
|
|
75
76
|
x?: number | undefined;
|
|
76
77
|
y?: number | undefined;
|
|
77
78
|
selector?: string | undefined;
|
|
78
79
|
delta_x?: number | undefined;
|
|
79
80
|
delta_y?: number | undefined;
|
|
80
|
-
delay_ms?: number | undefined;
|
|
81
81
|
}>, {
|
|
82
|
-
action: "type" | "click" | "tap" | "key" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
83
82
|
delay_ms: number;
|
|
83
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
84
84
|
value?: string | undefined;
|
|
85
85
|
x?: number | undefined;
|
|
86
86
|
y?: number | undefined;
|
|
@@ -88,14 +88,14 @@ export declare const saveAuthInputShape: {
|
|
|
88
88
|
delta_x?: number | undefined;
|
|
89
89
|
delta_y?: number | undefined;
|
|
90
90
|
}, {
|
|
91
|
-
action: "type" | "
|
|
91
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
92
92
|
value?: string | undefined;
|
|
93
|
+
delay_ms?: number | undefined;
|
|
93
94
|
x?: number | undefined;
|
|
94
95
|
y?: number | undefined;
|
|
95
96
|
selector?: string | undefined;
|
|
96
97
|
delta_x?: number | undefined;
|
|
97
98
|
delta_y?: number | undefined;
|
|
98
|
-
delay_ms?: number | undefined;
|
|
99
99
|
}>, "many">;
|
|
100
100
|
output_path: z.ZodDefault<z.ZodString>;
|
|
101
101
|
wait_for: z.ZodOptional<z.ZodString>;
|
|
@@ -130,8 +130,8 @@ export declare const saveAuthInputSchema: z.ZodObject<{
|
|
|
130
130
|
delta_y: z.ZodOptional<z.ZodNumber>;
|
|
131
131
|
action: z.ZodEnum<["click", "tap", "type", "key", "scroll", "swipe", "hover", "select", "wait", "navigate"]>;
|
|
132
132
|
}, "strip", z.ZodTypeAny, {
|
|
133
|
-
action: "type" | "click" | "tap" | "key" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
134
133
|
delay_ms: number;
|
|
134
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
135
135
|
value?: string | undefined;
|
|
136
136
|
x?: number | undefined;
|
|
137
137
|
y?: number | undefined;
|
|
@@ -139,17 +139,17 @@ export declare const saveAuthInputSchema: z.ZodObject<{
|
|
|
139
139
|
delta_x?: number | undefined;
|
|
140
140
|
delta_y?: number | undefined;
|
|
141
141
|
}, {
|
|
142
|
-
action: "type" | "
|
|
142
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
143
143
|
value?: string | undefined;
|
|
144
|
+
delay_ms?: number | undefined;
|
|
144
145
|
x?: number | undefined;
|
|
145
146
|
y?: number | undefined;
|
|
146
147
|
selector?: string | undefined;
|
|
147
148
|
delta_x?: number | undefined;
|
|
148
149
|
delta_y?: number | undefined;
|
|
149
|
-
delay_ms?: number | undefined;
|
|
150
150
|
}>, {
|
|
151
|
-
action: "type" | "click" | "tap" | "key" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
152
151
|
delay_ms: number;
|
|
152
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
153
153
|
value?: string | undefined;
|
|
154
154
|
x?: number | undefined;
|
|
155
155
|
y?: number | undefined;
|
|
@@ -157,14 +157,14 @@ export declare const saveAuthInputSchema: z.ZodObject<{
|
|
|
157
157
|
delta_x?: number | undefined;
|
|
158
158
|
delta_y?: number | undefined;
|
|
159
159
|
}, {
|
|
160
|
-
action: "type" | "
|
|
160
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
161
161
|
value?: string | undefined;
|
|
162
|
+
delay_ms?: number | undefined;
|
|
162
163
|
x?: number | undefined;
|
|
163
164
|
y?: number | undefined;
|
|
164
165
|
selector?: string | undefined;
|
|
165
166
|
delta_x?: number | undefined;
|
|
166
167
|
delta_y?: number | undefined;
|
|
167
|
-
delay_ms?: number | undefined;
|
|
168
168
|
}>, "many">;
|
|
169
169
|
output_path: z.ZodDefault<z.ZodString>;
|
|
170
170
|
wait_for: z.ZodOptional<z.ZodString>;
|
|
@@ -189,9 +189,10 @@ export declare const saveAuthInputSchema: z.ZodObject<{
|
|
|
189
189
|
}, "strip", z.ZodTypeAny, {
|
|
190
190
|
url: string;
|
|
191
191
|
wait_for_timeout_ms: number;
|
|
192
|
+
timeout_ms: number;
|
|
192
193
|
interactions: {
|
|
193
|
-
action: "type" | "click" | "tap" | "key" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
194
194
|
delay_ms: number;
|
|
195
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
195
196
|
value?: string | undefined;
|
|
196
197
|
x?: number | undefined;
|
|
197
198
|
y?: number | undefined;
|
|
@@ -199,7 +200,6 @@ export declare const saveAuthInputSchema: z.ZodObject<{
|
|
|
199
200
|
delta_x?: number | undefined;
|
|
200
201
|
delta_y?: number | undefined;
|
|
201
202
|
}[];
|
|
202
|
-
timeout_ms: number;
|
|
203
203
|
output_path: string;
|
|
204
204
|
viewport?: {
|
|
205
205
|
width: number;
|
|
@@ -211,14 +211,14 @@ export declare const saveAuthInputSchema: z.ZodObject<{
|
|
|
211
211
|
}, {
|
|
212
212
|
url: string;
|
|
213
213
|
interactions: {
|
|
214
|
-
action: "type" | "
|
|
214
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "wait" | "navigate";
|
|
215
215
|
value?: string | undefined;
|
|
216
|
+
delay_ms?: number | undefined;
|
|
216
217
|
x?: number | undefined;
|
|
217
218
|
y?: number | undefined;
|
|
218
219
|
selector?: string | undefined;
|
|
219
220
|
delta_x?: number | undefined;
|
|
220
221
|
delta_y?: number | undefined;
|
|
221
|
-
delay_ms?: number | undefined;
|
|
222
222
|
}[];
|
|
223
223
|
viewport?: {
|
|
224
224
|
width?: number | undefined;
|
package/dist/tools/screenshot.js
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { DEFAULT_SCREENSHOT_WAIT_MS, DEFAULT_VIEWPORT, NAVIGATION_TIMEOUT_MS, 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 {
|
|
5
|
+
import { loginFormVisible, resolveStorageState, storageStateField, withAuthNote } from "../utils/storage-state.js";
|
|
6
6
|
export const SCREENSHOT_TOOL_NAME = "framewatch_screenshot";
|
|
7
7
|
export const screenshotInputShape = {
|
|
8
8
|
url: z
|
|
@@ -48,7 +48,8 @@ export async function takeScreenshot(rawInput) {
|
|
|
48
48
|
const input = parsed.data;
|
|
49
49
|
const viewport = input.viewport ?? { ...DEFAULT_VIEWPORT };
|
|
50
50
|
try {
|
|
51
|
-
const
|
|
51
|
+
const auth = await resolveStorageState(input.storage_state);
|
|
52
|
+
const contextOptions = auth ? { storageState: auth.state } : {};
|
|
52
53
|
const shot = await withPage({ viewport, contextOptions }, async (page) => {
|
|
53
54
|
const response = await page.goto(input.url, { waitUntil: "load", timeout: NAVIGATION_TIMEOUT_MS });
|
|
54
55
|
if (input.wait_for) {
|
|
@@ -60,7 +61,14 @@ export async function takeScreenshot(rawInput) {
|
|
|
60
61
|
const png = input.selector
|
|
61
62
|
? await page.locator(input.selector).first().screenshot({ type: "png", timeout: input.wait_for_timeout_ms })
|
|
62
63
|
: await page.screenshot({ type: "png" });
|
|
63
|
-
return {
|
|
64
|
+
return {
|
|
65
|
+
png,
|
|
66
|
+
title: await page.title(),
|
|
67
|
+
finalUrl: page.url(),
|
|
68
|
+
status: response?.status() ?? null,
|
|
69
|
+
// A login form on the page after restoring a session is the expiry signal.
|
|
70
|
+
loginVisible: auth ? await loginFormVisible(page) : false,
|
|
71
|
+
};
|
|
64
72
|
});
|
|
65
73
|
const resized = await resizeForOutput(shot.png);
|
|
66
74
|
const { width, height } = await getDimensions(resized);
|
|
@@ -72,12 +80,12 @@ export async function takeScreenshot(rawInput) {
|
|
|
72
80
|
`viewport ${viewport.width}x${viewport.height}`,
|
|
73
81
|
input.selector ? `element ${input.selector}` : null,
|
|
74
82
|
].filter((p) => p !== null);
|
|
75
|
-
return {
|
|
83
|
+
return withAuthNote({
|
|
76
84
|
content: [
|
|
77
85
|
{ type: "image", data: toBase64(resized), mimeType: "image/png" },
|
|
78
86
|
{ type: "text", text: summaryParts.join(" — ") },
|
|
79
87
|
],
|
|
80
|
-
};
|
|
88
|
+
}, auth, shot.loginVisible);
|
|
81
89
|
}
|
|
82
90
|
catch (error) {
|
|
83
91
|
return errorResult(describeFailure(input, error));
|
|
@@ -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\";\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"]}
|
|
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 { loginFormVisible, resolveStorageState, storageStateField, withAuthNote } 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 auth = await resolveStorageState(input.storage_state);\n const contextOptions = auth ? { storageState: auth.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 {\n png,\n title: await page.title(),\n finalUrl: page.url(),\n status: response?.status() ?? null,\n // A login form on the page after restoring a session is the expiry signal.\n loginVisible: auth ? await loginFormVisible(page) : false,\n };\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 withAuthNote(\n {\n content: [\n { type: \"image\", data: toBase64(resized), mimeType: \"image/png\" },\n { type: \"text\", text: summaryParts.join(\" — \") },\n ],\n },\n auth,\n shot.loginVisible,\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,113 @@
|
|
|
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 PageSeo } from "../engine/seo.js";
|
|
5
|
+
import { type SeoReport } from "../utils/seo-rules.js";
|
|
6
|
+
export declare const SEO_TOOL_NAME = "framewatch_seo";
|
|
7
|
+
export declare const seoInputShape: {
|
|
8
|
+
url: z.ZodString;
|
|
9
|
+
wait_ms: z.ZodDefault<z.ZodNumber>;
|
|
10
|
+
wait_for: z.ZodOptional<z.ZodString>;
|
|
11
|
+
wait_for_timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
12
|
+
check_robots: z.ZodDefault<z.ZodBoolean>;
|
|
13
|
+
robots_user_agent: z.ZodDefault<z.ZodString>;
|
|
14
|
+
check_og_image: z.ZodDefault<z.ZodBoolean>;
|
|
15
|
+
include_performance: z.ZodDefault<z.ZodBoolean>;
|
|
16
|
+
viewport: z.ZodOptional<z.ZodObject<{
|
|
17
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
18
|
+
height: z.ZodDefault<z.ZodNumber>;
|
|
19
|
+
}, "strip", z.ZodTypeAny, {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
}, {
|
|
23
|
+
width?: number | undefined;
|
|
24
|
+
height?: number | undefined;
|
|
25
|
+
}>>;
|
|
26
|
+
storage_state: z.ZodOptional<z.ZodString>;
|
|
27
|
+
};
|
|
28
|
+
export declare const seoInputSchema: z.ZodObject<{
|
|
29
|
+
url: z.ZodString;
|
|
30
|
+
wait_ms: z.ZodDefault<z.ZodNumber>;
|
|
31
|
+
wait_for: z.ZodOptional<z.ZodString>;
|
|
32
|
+
wait_for_timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
33
|
+
check_robots: z.ZodDefault<z.ZodBoolean>;
|
|
34
|
+
robots_user_agent: z.ZodDefault<z.ZodString>;
|
|
35
|
+
check_og_image: z.ZodDefault<z.ZodBoolean>;
|
|
36
|
+
include_performance: z.ZodDefault<z.ZodBoolean>;
|
|
37
|
+
viewport: z.ZodOptional<z.ZodObject<{
|
|
38
|
+
width: z.ZodDefault<z.ZodNumber>;
|
|
39
|
+
height: z.ZodDefault<z.ZodNumber>;
|
|
40
|
+
}, "strip", z.ZodTypeAny, {
|
|
41
|
+
width: number;
|
|
42
|
+
height: number;
|
|
43
|
+
}, {
|
|
44
|
+
width?: number | undefined;
|
|
45
|
+
height?: number | undefined;
|
|
46
|
+
}>>;
|
|
47
|
+
storage_state: z.ZodOptional<z.ZodString>;
|
|
48
|
+
}, "strip", z.ZodTypeAny, {
|
|
49
|
+
url: string;
|
|
50
|
+
wait_ms: number;
|
|
51
|
+
wait_for_timeout_ms: number;
|
|
52
|
+
include_performance: boolean;
|
|
53
|
+
check_robots: boolean;
|
|
54
|
+
robots_user_agent: string;
|
|
55
|
+
check_og_image: boolean;
|
|
56
|
+
viewport?: {
|
|
57
|
+
width: number;
|
|
58
|
+
height: number;
|
|
59
|
+
} | undefined;
|
|
60
|
+
wait_for?: string | undefined;
|
|
61
|
+
storage_state?: string | undefined;
|
|
62
|
+
}, {
|
|
63
|
+
url: string;
|
|
64
|
+
viewport?: {
|
|
65
|
+
width?: number | undefined;
|
|
66
|
+
height?: number | undefined;
|
|
67
|
+
} | undefined;
|
|
68
|
+
wait_ms?: number | undefined;
|
|
69
|
+
wait_for?: string | undefined;
|
|
70
|
+
wait_for_timeout_ms?: number | undefined;
|
|
71
|
+
storage_state?: string | undefined;
|
|
72
|
+
include_performance?: boolean | undefined;
|
|
73
|
+
check_robots?: boolean | undefined;
|
|
74
|
+
robots_user_agent?: string | undefined;
|
|
75
|
+
check_og_image?: boolean | undefined;
|
|
76
|
+
}>;
|
|
77
|
+
export type SeoInput = z.input<typeof seoInputSchema>;
|
|
78
|
+
/**
|
|
79
|
+
* Audit what a search engine — and a link preview — would make of a page.
|
|
80
|
+
*
|
|
81
|
+
* Everything is read from the *rendered* document rather than the HTML source.
|
|
82
|
+
* A single-page app ships an empty `<div id="app">` and fills in the title,
|
|
83
|
+
* the description and the structured data afterwards, so reading the source
|
|
84
|
+
* would report every client-rendered page as having no SEO at all, which is
|
|
85
|
+
* both wrong and the opposite of useful.
|
|
86
|
+
*
|
|
87
|
+
* Three things are checked that are not on the page and cannot be seen from
|
|
88
|
+
* it: the response headers (an `X-Robots-Tag: noindex` is invisible in the
|
|
89
|
+
* DOM), robots.txt (a page can be perfect and simply not crawlable), and the
|
|
90
|
+
* share image itself, which is fetched and measured — an `og:image` pointing
|
|
91
|
+
* at a 404 looks exactly like a working one until somebody shares the link.
|
|
92
|
+
*/
|
|
93
|
+
export declare function auditSeo(rawInput: SeoInput): Promise<CallToolResult>;
|
|
94
|
+
/**
|
|
95
|
+
* The report as text: a verdict line, the problems up front, then every check
|
|
96
|
+
* in the order it was run.
|
|
97
|
+
*
|
|
98
|
+
* Passing checks are printed rather than counted. A report of four lines with
|
|
99
|
+
* nothing else in it is otherwise indistinguishable from an audit that only
|
|
100
|
+
* looked at four things.
|
|
101
|
+
*/
|
|
102
|
+
export declare function formatSeoReport(page: PageSeo, report: SeoReport): string;
|
|
103
|
+
/**
|
|
104
|
+
* One actionable line for an audit that could not run. Mirrors
|
|
105
|
+
* `describeFailure` in screenshot.ts: match on the failing Playwright call,
|
|
106
|
+
* never on substrings of a user-supplied selector.
|
|
107
|
+
*/
|
|
108
|
+
export declare function describeSeoFailure(input: {
|
|
109
|
+
url: string;
|
|
110
|
+
wait_for?: string;
|
|
111
|
+
wait_for_timeout_ms: number;
|
|
112
|
+
}, error: unknown): string;
|
|
113
|
+
export declare function registerSeoTool(server: McpServer): void;
|