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
package/dist/tools/index.js
CHANGED
|
@@ -1,25 +1,69 @@
|
|
|
1
|
+
import { fitToBudget } from "../utils/budget.js";
|
|
1
2
|
import { registerAccessibilityTool } from "./accessibility.js";
|
|
3
|
+
import { registerApiMockTool } from "./api-mock.js";
|
|
2
4
|
import { registerCaptureTool } from "./capture.js";
|
|
3
5
|
import { registerCompareTool } from "./compare.js";
|
|
6
|
+
import { registerDeadClicksTool } from "./dead-clicks.js";
|
|
7
|
+
import { registerFormTestTool } from "./form-test.js";
|
|
8
|
+
import { registerInspectTool } from "./inspect.js";
|
|
4
9
|
import { registerInteractTool } from "./interact.js";
|
|
10
|
+
import { registerLinksTool } from "./links.js";
|
|
5
11
|
import { registerResponsiveTool } from "./responsive.js";
|
|
12
|
+
import { registerRtlTool } from "./rtl.js";
|
|
13
|
+
import { registerSaveAuthTool } from "./save-auth.js";
|
|
6
14
|
import { registerScreenshotTool } from "./screenshot.js";
|
|
15
|
+
import { registerSeoTool } from "./seo.js";
|
|
7
16
|
import { registerServerTools } from "./server.js";
|
|
17
|
+
import { registerSnapshotTool } from "./snapshot.js";
|
|
18
|
+
import { registerWaitForTool } from "./wait-for.js";
|
|
8
19
|
export { registerScreenshotTool, takeScreenshot, SCREENSHOT_TOOL_NAME } from "./screenshot.js";
|
|
9
20
|
export { registerCaptureTool, capturePage, CAPTURE_TOOL_NAME } from "./capture.js";
|
|
21
|
+
export { registerApiMockTool, mockApi, API_MOCK_TOOL_NAME } from "./api-mock.js";
|
|
10
22
|
export { registerInteractTool, performInteraction, INTERACT_TOOL_NAME } from "./interact.js";
|
|
23
|
+
export { registerSnapshotTool, snapshotPage, SNAPSHOT_TOOL_NAME } from "./snapshot.js";
|
|
24
|
+
export { registerInspectTool, inspectElements, INSPECT_TOOL_NAME } from "./inspect.js";
|
|
25
|
+
export { registerWaitForTool, waitFor, WAIT_FOR_TOOL_NAME } from "./wait-for.js";
|
|
11
26
|
export { registerResponsiveTool, captureResponsive, RESPONSIVE_TOOL_NAME } from "./responsive.js";
|
|
12
27
|
export { registerAccessibilityTool, auditAccessibility, ACCESSIBILITY_TOOL_NAME } from "./accessibility.js";
|
|
13
28
|
export { registerCompareTool, comparePages, COMPARE_TOOL_NAME, CURRENT_PAGE } from "./compare.js";
|
|
29
|
+
export { registerSaveAuthTool, saveAuth, SAVE_AUTH_TOOL_NAME } from "./save-auth.js";
|
|
30
|
+
export { registerFormTestTool, testForms, FORM_TEST_TOOL_NAME } from "./form-test.js";
|
|
31
|
+
export { registerSeoTool, auditSeo, SEO_TOOL_NAME } from "./seo.js";
|
|
32
|
+
export { registerDeadClicksTool, findDeadClicks, DEAD_CLICKS_TOOL_NAME } from "./dead-clicks.js";
|
|
33
|
+
export { registerLinksTool, checkLinks, LINKS_TOOL_NAME } from "./links.js";
|
|
34
|
+
export { registerRtlTool, testRtl, RTL_TOOL_NAME } from "./rtl.js";
|
|
14
35
|
export { registerServerTools, startServer, stopServer, START_SERVER_TOOL_NAME, STOP_SERVER_TOOL_NAME, } from "./server.js";
|
|
15
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* Register every FrameWatch tool on the given server.
|
|
38
|
+
*
|
|
39
|
+
* Every handler is wrapped so its result passes through the image budget on
|
|
40
|
+
* the way out (see `utils/budget.ts`): a result over the client's cap loses
|
|
41
|
+
* all its images, so this is the one place that guarantees none ever is.
|
|
42
|
+
*/
|
|
16
43
|
export function registerAllTools(server) {
|
|
44
|
+
budgetEveryTool(server);
|
|
17
45
|
registerScreenshotTool(server);
|
|
18
46
|
registerCaptureTool(server);
|
|
19
47
|
registerInteractTool(server);
|
|
48
|
+
registerSnapshotTool(server);
|
|
49
|
+
registerInspectTool(server);
|
|
50
|
+
registerWaitForTool(server);
|
|
20
51
|
registerResponsiveTool(server);
|
|
21
52
|
registerAccessibilityTool(server);
|
|
22
53
|
registerCompareTool(server);
|
|
54
|
+
registerSaveAuthTool(server);
|
|
55
|
+
registerFormTestTool(server);
|
|
56
|
+
registerSeoTool(server);
|
|
57
|
+
registerDeadClicksTool(server);
|
|
58
|
+
registerLinksTool(server);
|
|
59
|
+
registerApiMockTool(server);
|
|
60
|
+
registerRtlTool(server);
|
|
23
61
|
registerServerTools(server);
|
|
24
62
|
}
|
|
63
|
+
function budgetEveryTool(server) {
|
|
64
|
+
const original = server.registerTool.bind(server);
|
|
65
|
+
// The generic signature cannot be re-expressed around a wrapper without
|
|
66
|
+
// restating the SDK's types; the shape is (name, config, handler).
|
|
67
|
+
server.registerTool = (name, config, handler) => original(name, config, async (...args) => fitToBudget((await handler(...args))));
|
|
68
|
+
}
|
|
25
69
|
//# sourceMappingURL=index.js.map
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { fitToBudget } from \"../utils/budget.js\";\nimport { registerAccessibilityTool } from \"./accessibility.js\";\nimport { registerApiMockTool } from \"./api-mock.js\";\nimport { registerCaptureTool } from \"./capture.js\";\nimport { registerCompareTool } from \"./compare.js\";\nimport { registerDeadClicksTool } from \"./dead-clicks.js\";\nimport { registerFormTestTool } from \"./form-test.js\";\nimport { registerInspectTool } from \"./inspect.js\";\nimport { registerInteractTool } from \"./interact.js\";\nimport { registerLinksTool } from \"./links.js\";\nimport { registerResponsiveTool } from \"./responsive.js\";\nimport { registerRtlTool } from \"./rtl.js\";\nimport { registerSaveAuthTool } from \"./save-auth.js\";\nimport { registerScreenshotTool } from \"./screenshot.js\";\nimport { registerSeoTool } from \"./seo.js\";\nimport { registerServerTools } from \"./server.js\";\nimport { registerSnapshotTool } from \"./snapshot.js\";\nimport { registerWaitForTool } from \"./wait-for.js\";\n\nexport { registerScreenshotTool, takeScreenshot, SCREENSHOT_TOOL_NAME } from \"./screenshot.js\";\nexport { registerCaptureTool, capturePage, CAPTURE_TOOL_NAME } from \"./capture.js\";\nexport { registerApiMockTool, mockApi, API_MOCK_TOOL_NAME } from \"./api-mock.js\";\nexport { registerInteractTool, performInteraction, INTERACT_TOOL_NAME } from \"./interact.js\";\nexport { registerSnapshotTool, snapshotPage, SNAPSHOT_TOOL_NAME } from \"./snapshot.js\";\nexport { registerInspectTool, inspectElements, INSPECT_TOOL_NAME } from \"./inspect.js\";\nexport { registerWaitForTool, waitFor, WAIT_FOR_TOOL_NAME } from \"./wait-for.js\";\nexport { registerResponsiveTool, captureResponsive, RESPONSIVE_TOOL_NAME } from \"./responsive.js\";\nexport { registerAccessibilityTool, auditAccessibility, ACCESSIBILITY_TOOL_NAME } from \"./accessibility.js\";\nexport { registerCompareTool, comparePages, COMPARE_TOOL_NAME, CURRENT_PAGE } from \"./compare.js\";\nexport { registerSaveAuthTool, saveAuth, SAVE_AUTH_TOOL_NAME } from \"./save-auth.js\";\nexport { registerFormTestTool, testForms, FORM_TEST_TOOL_NAME } from \"./form-test.js\";\nexport { registerSeoTool, auditSeo, SEO_TOOL_NAME } from \"./seo.js\";\nexport { registerDeadClicksTool, findDeadClicks, DEAD_CLICKS_TOOL_NAME } from \"./dead-clicks.js\";\nexport { registerLinksTool, checkLinks, LINKS_TOOL_NAME } from \"./links.js\";\nexport { registerRtlTool, testRtl, RTL_TOOL_NAME } from \"./rtl.js\";\nexport {\n registerServerTools,\n startServer,\n stopServer,\n START_SERVER_TOOL_NAME,\n STOP_SERVER_TOOL_NAME,\n} from \"./server.js\";\n\n/**\n * Register every FrameWatch tool on the given server.\n *\n * Every handler is wrapped so its result passes through the image budget on\n * the way out (see `utils/budget.ts`): a result over the client's cap loses\n * all its images, so this is the one place that guarantees none ever is.\n */\nexport function registerAllTools(server: McpServer): void {\n budgetEveryTool(server);\n registerScreenshotTool(server);\n registerCaptureTool(server);\n registerInteractTool(server);\n registerSnapshotTool(server);\n registerInspectTool(server);\n registerWaitForTool(server);\n registerResponsiveTool(server);\n registerAccessibilityTool(server);\n registerCompareTool(server);\n registerSaveAuthTool(server);\n registerFormTestTool(server);\n registerSeoTool(server);\n registerDeadClicksTool(server);\n registerLinksTool(server);\n registerApiMockTool(server);\n registerRtlTool(server);\n registerServerTools(server);\n}\n\nfunction budgetEveryTool(server: McpServer): void {\n const original = server.registerTool.bind(server) as (...args: unknown[]) => unknown;\n // The generic signature cannot be re-expressed around a wrapper without\n // restating the SDK's types; the shape is (name, config, handler).\n (server as unknown as { registerTool: unknown }).registerTool = (name: unknown, config: unknown, handler: (...args: unknown[]) => unknown) =>\n original(name, config, async (...args: unknown[]) => fitToBudget((await handler(...args)) as Parameters<typeof fitToBudget>[0]));\n}\n"]}
|
|
@@ -0,0 +1,78 @@
|
|
|
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
|
+
export declare const INSPECT_TOOL_NAME = "framewatch_inspect";
|
|
5
|
+
export declare const inspectInputShape: {
|
|
6
|
+
url: z.ZodOptional<z.ZodString>;
|
|
7
|
+
targets: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
8
|
+
selector: z.ZodOptional<z.ZodString>;
|
|
9
|
+
include_screenshot: z.ZodDefault<z.ZodBoolean>;
|
|
10
|
+
wait_ms: z.ZodDefault<z.ZodNumber>;
|
|
11
|
+
wait_for: z.ZodOptional<z.ZodString>;
|
|
12
|
+
wait_for_timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
13
|
+
viewport: z.ZodOptional<z.ZodObject<{
|
|
14
|
+
width: z.ZodNumber;
|
|
15
|
+
height: z.ZodNumber;
|
|
16
|
+
}, "strip", z.ZodTypeAny, {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
}, {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
}>>;
|
|
23
|
+
storage_state: z.ZodOptional<z.ZodString>;
|
|
24
|
+
};
|
|
25
|
+
export declare const inspectInputSchema: z.ZodObject<{
|
|
26
|
+
url: z.ZodOptional<z.ZodString>;
|
|
27
|
+
targets: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
28
|
+
selector: z.ZodOptional<z.ZodString>;
|
|
29
|
+
include_screenshot: z.ZodDefault<z.ZodBoolean>;
|
|
30
|
+
wait_ms: z.ZodDefault<z.ZodNumber>;
|
|
31
|
+
wait_for: z.ZodOptional<z.ZodString>;
|
|
32
|
+
wait_for_timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
33
|
+
viewport: z.ZodOptional<z.ZodObject<{
|
|
34
|
+
width: z.ZodNumber;
|
|
35
|
+
height: z.ZodNumber;
|
|
36
|
+
}, "strip", z.ZodTypeAny, {
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
}, {
|
|
40
|
+
width: number;
|
|
41
|
+
height: number;
|
|
42
|
+
}>>;
|
|
43
|
+
storage_state: z.ZodOptional<z.ZodString>;
|
|
44
|
+
}, "strip", z.ZodTypeAny, {
|
|
45
|
+
wait_ms: number;
|
|
46
|
+
wait_for_timeout_ms: number;
|
|
47
|
+
include_screenshot: boolean;
|
|
48
|
+
url?: string | undefined;
|
|
49
|
+
viewport?: {
|
|
50
|
+
width: number;
|
|
51
|
+
height: number;
|
|
52
|
+
} | undefined;
|
|
53
|
+
wait_for?: string | undefined;
|
|
54
|
+
storage_state?: string | undefined;
|
|
55
|
+
selector?: string | undefined;
|
|
56
|
+
targets?: string[] | undefined;
|
|
57
|
+
}, {
|
|
58
|
+
url?: string | undefined;
|
|
59
|
+
viewport?: {
|
|
60
|
+
width: number;
|
|
61
|
+
height: number;
|
|
62
|
+
} | undefined;
|
|
63
|
+
wait_ms?: number | undefined;
|
|
64
|
+
wait_for?: string | undefined;
|
|
65
|
+
wait_for_timeout_ms?: number | undefined;
|
|
66
|
+
storage_state?: string | undefined;
|
|
67
|
+
selector?: string | undefined;
|
|
68
|
+
include_screenshot?: boolean | undefined;
|
|
69
|
+
targets?: string[] | undefined;
|
|
70
|
+
}>;
|
|
71
|
+
export type InspectInput = z.input<typeof inspectInputSchema>;
|
|
72
|
+
/**
|
|
73
|
+
* Measure how elements are built — box, type, colours with contrast, spacing,
|
|
74
|
+
* alignment — or, with no targets, what the whole page is built from. Runs on
|
|
75
|
+
* the interact session's page so a ref from the last snapshot resolves.
|
|
76
|
+
*/
|
|
77
|
+
export declare function inspectElements(rawInput: InspectInput): Promise<CallToolResult>;
|
|
78
|
+
export declare function registerInspectTool(server: McpServer): void;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { DEFAULT_SNAPSHOT_WAIT_MS, HIGHLIGHT_INSPECT_COLOUR, MAX_INSPECT_TARGETS, MAX_VIEWPORT_HEIGHT, MAX_VIEWPORT_WIDTH, SELECTOR_TIMEOUT_MS, } from "../constants.js";
|
|
3
|
+
import { withSessionLock } from "../engine/browser.js";
|
|
4
|
+
import { hmrFor } from "../engine/hmr.js";
|
|
5
|
+
import { inventoryPage, measureElements } from "../engine/inspect.js";
|
|
6
|
+
import { describeScale } from "../utils/format.js";
|
|
7
|
+
import { clearHighlights, highlightElements } from "../utils/highlight.js";
|
|
8
|
+
import { resizeForOutput, toBase64 } from "../utils/image.js";
|
|
9
|
+
import { storageStateField, withAuthNote } from "../utils/storage-state.js";
|
|
10
|
+
import { formatInspection, formatInventory } from "../utils/style-rules.js";
|
|
11
|
+
import { describeVue, formatComponentLine } from "../utils/vue-rules.js";
|
|
12
|
+
import { describeSessionFailure, openSessionPage } from "./snapshot.js";
|
|
13
|
+
export const INSPECT_TOOL_NAME = "framewatch_inspect";
|
|
14
|
+
export const inspectInputShape = {
|
|
15
|
+
url: z
|
|
16
|
+
.string()
|
|
17
|
+
.url()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Open this URL first. Omit to measure the page left open by framewatch_interact / framewatch_snapshot."),
|
|
20
|
+
targets: z
|
|
21
|
+
.array(z.string().min(1))
|
|
22
|
+
.max(MAX_INSPECT_TARGETS)
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("What to measure: snapshot refs (`e8`) and/or CSS selectors, in order. Omit for a design inventory of the whole page instead."),
|
|
25
|
+
selector: z.string().optional().describe("For the inventory: only count elements inside this container"),
|
|
26
|
+
include_screenshot: z.boolean().default(true).describe("With `targets`: return a screenshot with each target boxed and numbered"),
|
|
27
|
+
wait_ms: z
|
|
28
|
+
.number()
|
|
29
|
+
.int()
|
|
30
|
+
.min(0)
|
|
31
|
+
.default(DEFAULT_SNAPSHOT_WAIT_MS)
|
|
32
|
+
.describe("Settle time (ms) after opening `url`, so a client-rendered app has drawn"),
|
|
33
|
+
wait_for: z.string().optional().describe("CSS selector to wait for (visible) before measuring"),
|
|
34
|
+
wait_for_timeout_ms: z.number().int().min(1).default(SELECTOR_TIMEOUT_MS).describe("Max time (ms) to wait for `wait_for`"),
|
|
35
|
+
viewport: z
|
|
36
|
+
.object({
|
|
37
|
+
width: z.number().int().min(1).max(MAX_VIEWPORT_WIDTH),
|
|
38
|
+
height: z.number().int().min(1).max(MAX_VIEWPORT_HEIGHT),
|
|
39
|
+
})
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Resize the page to this first (defaults to leaving it as it is)"),
|
|
42
|
+
storage_state: storageStateField,
|
|
43
|
+
};
|
|
44
|
+
export const inspectInputSchema = z.object(inspectInputShape);
|
|
45
|
+
/**
|
|
46
|
+
* Measure how elements are built — box, type, colours with contrast, spacing,
|
|
47
|
+
* alignment — or, with no targets, what the whole page is built from. Runs on
|
|
48
|
+
* the interact session's page so a ref from the last snapshot resolves.
|
|
49
|
+
*/
|
|
50
|
+
export function inspectElements(rawInput) {
|
|
51
|
+
return withSessionLock(() => runInspect(rawInput));
|
|
52
|
+
}
|
|
53
|
+
async function runInspect(rawInput) {
|
|
54
|
+
const parsed = inspectInputSchema.safeParse(rawInput);
|
|
55
|
+
if (!parsed.success) {
|
|
56
|
+
const issues = parsed.error.issues.map((i) => `${i.path.join(".") || "input"}: ${i.message}`).join("; ");
|
|
57
|
+
return errorResult(`Inspect failed: invalid input — ${issues}`);
|
|
58
|
+
}
|
|
59
|
+
const input = parsed.data;
|
|
60
|
+
try {
|
|
61
|
+
const opened = await openSessionPage(input, "Inspect");
|
|
62
|
+
if ("error" in opened)
|
|
63
|
+
return errorResult(opened.error);
|
|
64
|
+
const { page, vue, auth, login_visible } = opened;
|
|
65
|
+
const viewport = page.viewportSize() ?? { width: 0, height: 0 };
|
|
66
|
+
const vueLine = vue ? ` — ${describeVue(vue)}` : "";
|
|
67
|
+
if (!input.targets || input.targets.length === 0) {
|
|
68
|
+
const inventory = await inventoryPage(page, input.selector);
|
|
69
|
+
const lines = formatInventory(inventory);
|
|
70
|
+
lines.push(`Page: ${page.url()} — ${describeScale(viewport).replace(/, images.*$/, "")}${vueLine}${input.selector ? ` — scope ${input.selector}` : ""}`);
|
|
71
|
+
hmrFor(page).markSeen();
|
|
72
|
+
return withAuthNote({ content: [{ type: "text", text: lines.join("\n") }] }, auth, login_visible);
|
|
73
|
+
}
|
|
74
|
+
// Component data is only worth asking for on a Vue page; elsewhere the
|
|
75
|
+
// question costs an evaluate per target and always answers "none".
|
|
76
|
+
const results = await measureElements(page, input.targets, { components: vue !== null });
|
|
77
|
+
const measured = results.filter((r) => r.measurement !== undefined).length;
|
|
78
|
+
const lines = [
|
|
79
|
+
`Inspected ${measured} of ${results.length} targets on ${page.url()} — ${describeScale(viewport)}${vueLine}`,
|
|
80
|
+
...results.flatMap((result, i) => formatResult(result, i + 1)),
|
|
81
|
+
];
|
|
82
|
+
const content = [{ type: "text", text: lines.join("\n") }];
|
|
83
|
+
if (input.include_screenshot && measured > 0) {
|
|
84
|
+
content.push({ type: "image", data: await boxedScreenshot(page, results), mimeType: "image/png" });
|
|
85
|
+
}
|
|
86
|
+
hmrFor(page).markSeen();
|
|
87
|
+
return withAuthNote({ content }, auth, login_visible);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
return errorResult(describeSessionFailure("Inspect", input, error));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function formatResult(result, index) {
|
|
94
|
+
if (!result.measurement)
|
|
95
|
+
return [`${index}. ${result.target} — ${result.error ?? "not measured"}`];
|
|
96
|
+
const lines = formatInspection(result.measurement, index);
|
|
97
|
+
if (result.component)
|
|
98
|
+
lines.push(` ${formatComponentLine(result.component)}`);
|
|
99
|
+
return lines;
|
|
100
|
+
}
|
|
101
|
+
/** The page with every measured target boxed, numbered to match the list. */
|
|
102
|
+
async function boxedScreenshot(page, results) {
|
|
103
|
+
const highlights = [];
|
|
104
|
+
results.forEach((result, i) => {
|
|
105
|
+
const m = result.measurement;
|
|
106
|
+
if (!m || m.box.width <= 0 || m.box.height <= 0)
|
|
107
|
+
return;
|
|
108
|
+
highlights.push({ selector: result.target, box: m.box, label: String(i + 1), colour: HIGHLIGHT_INSPECT_COLOUR });
|
|
109
|
+
});
|
|
110
|
+
await highlightElements(page, highlights);
|
|
111
|
+
try {
|
|
112
|
+
const png = await page.screenshot({ type: "png" });
|
|
113
|
+
return toBase64(await resizeForOutput(png));
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
await clearHighlights(page);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function errorResult(text) {
|
|
120
|
+
return { isError: true, content: [{ type: "text", text }] };
|
|
121
|
+
}
|
|
122
|
+
export function registerInspectTool(server) {
|
|
123
|
+
server.registerTool(INSPECT_TOOL_NAME, {
|
|
124
|
+
title: "Inspect",
|
|
125
|
+
description: "Measure how elements are actually built, to check UI work against what was intended: the box in viewport " +
|
|
126
|
+
"pixels, font family/size/weight/line-height, text and effective background colour with the WCAG contrast " +
|
|
127
|
+
"ratio, padding, margin, gap, border and radius, whether it is visible, clipped or overflowing, and how it " +
|
|
128
|
+
"aligns to its parent and previous sibling — plus a screenshot with each target boxed. Targets are refs from " +
|
|
129
|
+
"framewatch_snapshot (`e8`) or CSS selectors. With no targets it returns a design inventory of the page: every " +
|
|
130
|
+
"font, size, weight, colour, spacing value and radius in use, with counts — the fastest way to spot the one " +
|
|
131
|
+
"13px label on a 14px page.",
|
|
132
|
+
inputSchema: inspectInputShape,
|
|
133
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
134
|
+
}, async (args) => inspectElements(args));
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=inspect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.js","sourceRoot":"","sources":["../../src/tools/inspect.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 {\n DEFAULT_SNAPSHOT_WAIT_MS,\n HIGHLIGHT_INSPECT_COLOUR,\n MAX_INSPECT_TARGETS,\n MAX_VIEWPORT_HEIGHT,\n MAX_VIEWPORT_WIDTH,\n SELECTOR_TIMEOUT_MS,\n} from \"../constants.js\";\nimport { withSessionLock } from \"../engine/browser.js\";\nimport { hmrFor } from \"../engine/hmr.js\";\nimport { inventoryPage, measureElements, type InspectResult } from \"../engine/inspect.js\";\nimport { describeScale } from \"../utils/format.js\";\nimport { clearHighlights, highlightElements, type Highlight } from \"../utils/highlight.js\";\nimport { resizeForOutput, toBase64 } from \"../utils/image.js\";\nimport { storageStateField, withAuthNote } from \"../utils/storage-state.js\";\nimport { formatInspection, formatInventory } from \"../utils/style-rules.js\";\nimport { describeVue, formatComponentLine } from \"../utils/vue-rules.js\";\nimport { describeSessionFailure, openSessionPage } from \"./snapshot.js\";\n\nexport const INSPECT_TOOL_NAME = \"framewatch_inspect\";\n\nexport const inspectInputShape = {\n url: z\n .string()\n .url()\n .optional()\n .describe(\"Open this URL first. Omit to measure the page left open by framewatch_interact / framewatch_snapshot.\"),\n targets: z\n .array(z.string().min(1))\n .max(MAX_INSPECT_TARGETS)\n .optional()\n .describe(\n \"What to measure: snapshot refs (`e8`) and/or CSS selectors, in order. Omit for a design inventory of the whole page instead.\",\n ),\n selector: z.string().optional().describe(\"For the inventory: only count elements inside this container\"),\n include_screenshot: z.boolean().default(true).describe(\"With `targets`: return a screenshot with each target boxed and numbered\"),\n wait_ms: z\n .number()\n .int()\n .min(0)\n .default(DEFAULT_SNAPSHOT_WAIT_MS)\n .describe(\"Settle time (ms) after opening `url`, so a client-rendered app has drawn\"),\n wait_for: z.string().optional().describe(\"CSS selector to wait for (visible) before measuring\"),\n wait_for_timeout_ms: z.number().int().min(1).default(SELECTOR_TIMEOUT_MS).describe(\"Max time (ms) to wait for `wait_for`\"),\n viewport: z\n .object({\n width: z.number().int().min(1).max(MAX_VIEWPORT_WIDTH),\n height: z.number().int().min(1).max(MAX_VIEWPORT_HEIGHT),\n })\n .optional()\n .describe(\"Resize the page to this first (defaults to leaving it as it is)\"),\n storage_state: storageStateField,\n};\n\nexport const inspectInputSchema = z.object(inspectInputShape);\nexport type InspectInput = z.input<typeof inspectInputSchema>;\n\n/**\n * Measure how elements are built — box, type, colours with contrast, spacing,\n * alignment — or, with no targets, what the whole page is built from. Runs on\n * the interact session's page so a ref from the last snapshot resolves.\n */\nexport function inspectElements(rawInput: InspectInput): Promise<CallToolResult> {\n return withSessionLock(() => runInspect(rawInput));\n}\n\nasync function runInspect(rawInput: InspectInput): Promise<CallToolResult> {\n const parsed = inspectInputSchema.safeParse(rawInput);\n if (!parsed.success) {\n const issues = parsed.error.issues.map((i) => `${i.path.join(\".\") || \"input\"}: ${i.message}`).join(\"; \");\n return errorResult(`Inspect failed: invalid input — ${issues}`);\n }\n const input = parsed.data;\n\n try {\n const opened = await openSessionPage(input, \"Inspect\");\n if (\"error\" in opened) return errorResult(opened.error);\n const { page, vue, auth, login_visible } = opened;\n const viewport = page.viewportSize() ?? { width: 0, height: 0 };\n const vueLine = vue ? ` — ${describeVue(vue)}` : \"\";\n\n if (!input.targets || input.targets.length === 0) {\n const inventory = await inventoryPage(page, input.selector);\n const lines = formatInventory(inventory);\n lines.push(`Page: ${page.url()} — ${describeScale(viewport).replace(/, images.*$/, \"\")}${vueLine}${input.selector ? ` — scope ${input.selector}` : \"\"}`);\n hmrFor(page).markSeen();\n return withAuthNote({ content: [{ type: \"text\", text: lines.join(\"\\n\") }] }, auth, login_visible);\n }\n\n // Component data is only worth asking for on a Vue page; elsewhere the\n // question costs an evaluate per target and always answers \"none\".\n const results = await measureElements(page, input.targets, { components: vue !== null });\n const measured = results.filter((r) => r.measurement !== undefined).length;\n const lines = [\n `Inspected ${measured} of ${results.length} targets on ${page.url()} — ${describeScale(viewport)}${vueLine}`,\n ...results.flatMap((result, i) => formatResult(result, i + 1)),\n ];\n\n const content: CallToolResult[\"content\"] = [{ type: \"text\", text: lines.join(\"\\n\") }];\n if (input.include_screenshot && measured > 0) {\n content.push({ type: \"image\", data: await boxedScreenshot(page, results), mimeType: \"image/png\" });\n }\n hmrFor(page).markSeen();\n return withAuthNote({ content }, auth, login_visible);\n } catch (error) {\n return errorResult(describeSessionFailure(\"Inspect\", input, error));\n }\n}\n\nfunction formatResult(result: InspectResult, index: number): string[] {\n if (!result.measurement) return [`${index}. ${result.target} — ${result.error ?? \"not measured\"}`];\n const lines = formatInspection(result.measurement, index);\n if (result.component) lines.push(` ${formatComponentLine(result.component)}`);\n return lines;\n}\n\n/** The page with every measured target boxed, numbered to match the list. */\nasync function boxedScreenshot(page: Parameters<typeof highlightElements>[0], results: InspectResult[]): Promise<string> {\n const highlights: Highlight[] = [];\n results.forEach((result, i) => {\n const m = result.measurement;\n if (!m || m.box.width <= 0 || m.box.height <= 0) return;\n highlights.push({ selector: result.target, box: m.box, label: String(i + 1), colour: HIGHLIGHT_INSPECT_COLOUR });\n });\n await highlightElements(page, highlights);\n try {\n const png = await page.screenshot({ type: \"png\" });\n return toBase64(await resizeForOutput(png));\n } finally {\n await clearHighlights(page);\n }\n}\n\nfunction errorResult(text: string): CallToolResult {\n return { isError: true, content: [{ type: \"text\", text }] };\n}\n\nexport function registerInspectTool(server: McpServer): void {\n server.registerTool(\n INSPECT_TOOL_NAME,\n {\n title: \"Inspect\",\n description:\n \"Measure how elements are actually built, to check UI work against what was intended: the box in viewport \" +\n \"pixels, font family/size/weight/line-height, text and effective background colour with the WCAG contrast \" +\n \"ratio, padding, margin, gap, border and radius, whether it is visible, clipped or overflowing, and how it \" +\n \"aligns to its parent and previous sibling — plus a screenshot with each target boxed. Targets are refs from \" +\n \"framewatch_snapshot (`e8`) or CSS selectors. With no targets it returns a design inventory of the page: every \" +\n \"font, size, weight, colour, spacing value and radius in use, with counts — the fastest way to spot the one \" +\n \"13px label on a 14px page.\",\n inputSchema: inspectInputShape,\n annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },\n },\n async (args) => inspectElements(args),\n );\n}\n"]}
|
package/dist/tools/interact.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3
3
|
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
4
4
|
export declare const INTERACT_TOOL_NAME = "framewatch_interact";
|
|
5
5
|
export declare const interactInputShape: {
|
|
6
|
+
ref: z.ZodOptional<z.ZodString>;
|
|
6
7
|
url: z.ZodOptional<z.ZodString>;
|
|
7
8
|
wait_ms: z.ZodDefault<z.ZodNumber>;
|
|
8
9
|
timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
@@ -20,15 +21,18 @@ export declare const interactInputShape: {
|
|
|
20
21
|
include_network: z.ZodDefault<z.ZodBoolean>;
|
|
21
22
|
include_dom: z.ZodDefault<z.ZodBoolean>;
|
|
22
23
|
include_performance: z.ZodDefault<z.ZodBoolean>;
|
|
24
|
+
include_snapshot: z.ZodDefault<z.ZodBoolean>;
|
|
25
|
+
storage_state: z.ZodOptional<z.ZodString>;
|
|
23
26
|
selector: z.ZodOptional<z.ZodString>;
|
|
24
27
|
value: z.ZodOptional<z.ZodString>;
|
|
25
28
|
x: z.ZodOptional<z.ZodNumber>;
|
|
26
29
|
y: z.ZodOptional<z.ZodNumber>;
|
|
27
30
|
delta_x: z.ZodOptional<z.ZodNumber>;
|
|
28
31
|
delta_y: z.ZodOptional<z.ZodNumber>;
|
|
29
|
-
action: z.ZodEnum<["click", "tap", "type", "scroll", "swipe", "navigate", "select", "hover"]>;
|
|
32
|
+
action: z.ZodEnum<["click", "tap", "type", "key", "scroll", "swipe", "navigate", "select", "hover"]>;
|
|
30
33
|
};
|
|
31
34
|
export declare const interactInputSchema: z.ZodEffects<z.ZodObject<{
|
|
35
|
+
ref: z.ZodOptional<z.ZodString>;
|
|
32
36
|
url: z.ZodOptional<z.ZodString>;
|
|
33
37
|
wait_ms: z.ZodDefault<z.ZodNumber>;
|
|
34
38
|
timeout_ms: z.ZodDefault<z.ZodNumber>;
|
|
@@ -46,43 +50,50 @@ export declare const interactInputSchema: z.ZodEffects<z.ZodObject<{
|
|
|
46
50
|
include_network: z.ZodDefault<z.ZodBoolean>;
|
|
47
51
|
include_dom: z.ZodDefault<z.ZodBoolean>;
|
|
48
52
|
include_performance: z.ZodDefault<z.ZodBoolean>;
|
|
53
|
+
include_snapshot: z.ZodDefault<z.ZodBoolean>;
|
|
54
|
+
storage_state: z.ZodOptional<z.ZodString>;
|
|
49
55
|
selector: z.ZodOptional<z.ZodString>;
|
|
50
56
|
value: z.ZodOptional<z.ZodString>;
|
|
51
57
|
x: z.ZodOptional<z.ZodNumber>;
|
|
52
58
|
y: z.ZodOptional<z.ZodNumber>;
|
|
53
59
|
delta_x: z.ZodOptional<z.ZodNumber>;
|
|
54
60
|
delta_y: z.ZodOptional<z.ZodNumber>;
|
|
55
|
-
action: z.ZodEnum<["click", "tap", "type", "scroll", "swipe", "navigate", "select", "hover"]>;
|
|
61
|
+
action: z.ZodEnum<["click", "tap", "type", "key", "scroll", "swipe", "navigate", "select", "hover"]>;
|
|
56
62
|
}, "strip", z.ZodTypeAny, {
|
|
57
63
|
wait_ms: number;
|
|
58
|
-
|
|
64
|
+
timeout_ms: number;
|
|
65
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "navigate";
|
|
59
66
|
include_console: boolean;
|
|
60
67
|
include_network: boolean;
|
|
61
68
|
include_dom: boolean;
|
|
62
69
|
include_performance: boolean;
|
|
63
|
-
|
|
70
|
+
include_snapshot: boolean;
|
|
71
|
+
url?: string | undefined;
|
|
72
|
+
value?: string | undefined;
|
|
64
73
|
viewport?: {
|
|
65
74
|
width: number;
|
|
66
75
|
height: number;
|
|
67
76
|
} | undefined;
|
|
68
|
-
|
|
69
|
-
url?: string | undefined;
|
|
77
|
+
storage_state?: string | undefined;
|
|
70
78
|
x?: number | undefined;
|
|
71
79
|
y?: number | undefined;
|
|
72
80
|
selector?: string | undefined;
|
|
73
81
|
delta_x?: number | undefined;
|
|
74
82
|
delta_y?: number | undefined;
|
|
83
|
+
ref?: string | undefined;
|
|
75
84
|
}, {
|
|
76
|
-
action: "type" | "click" | "tap" | "scroll" | "swipe" | "
|
|
85
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "navigate";
|
|
86
|
+
url?: string | undefined;
|
|
87
|
+
value?: string | undefined;
|
|
77
88
|
viewport?: {
|
|
78
89
|
width: number;
|
|
79
90
|
height: number;
|
|
80
91
|
} | undefined;
|
|
81
|
-
value?: string | undefined;
|
|
82
|
-
url?: string | undefined;
|
|
83
92
|
wait_ms?: number | undefined;
|
|
93
|
+
storage_state?: string | undefined;
|
|
84
94
|
x?: number | undefined;
|
|
85
95
|
y?: number | undefined;
|
|
96
|
+
timeout_ms?: number | undefined;
|
|
86
97
|
selector?: string | undefined;
|
|
87
98
|
delta_x?: number | undefined;
|
|
88
99
|
delta_y?: number | undefined;
|
|
@@ -90,37 +101,43 @@ export declare const interactInputSchema: z.ZodEffects<z.ZodObject<{
|
|
|
90
101
|
include_network?: boolean | undefined;
|
|
91
102
|
include_dom?: boolean | undefined;
|
|
92
103
|
include_performance?: boolean | undefined;
|
|
93
|
-
|
|
104
|
+
ref?: string | undefined;
|
|
105
|
+
include_snapshot?: boolean | undefined;
|
|
94
106
|
}>, {
|
|
95
107
|
wait_ms: number;
|
|
96
|
-
|
|
108
|
+
timeout_ms: number;
|
|
109
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "navigate";
|
|
97
110
|
include_console: boolean;
|
|
98
111
|
include_network: boolean;
|
|
99
112
|
include_dom: boolean;
|
|
100
113
|
include_performance: boolean;
|
|
101
|
-
|
|
114
|
+
include_snapshot: boolean;
|
|
115
|
+
url?: string | undefined;
|
|
116
|
+
value?: string | undefined;
|
|
102
117
|
viewport?: {
|
|
103
118
|
width: number;
|
|
104
119
|
height: number;
|
|
105
120
|
} | undefined;
|
|
106
|
-
|
|
107
|
-
url?: string | undefined;
|
|
121
|
+
storage_state?: string | undefined;
|
|
108
122
|
x?: number | undefined;
|
|
109
123
|
y?: number | undefined;
|
|
110
124
|
selector?: string | undefined;
|
|
111
125
|
delta_x?: number | undefined;
|
|
112
126
|
delta_y?: number | undefined;
|
|
127
|
+
ref?: string | undefined;
|
|
113
128
|
}, {
|
|
114
|
-
action: "type" | "click" | "tap" | "scroll" | "swipe" | "
|
|
129
|
+
action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "navigate";
|
|
130
|
+
url?: string | undefined;
|
|
131
|
+
value?: string | undefined;
|
|
115
132
|
viewport?: {
|
|
116
133
|
width: number;
|
|
117
134
|
height: number;
|
|
118
135
|
} | undefined;
|
|
119
|
-
value?: string | undefined;
|
|
120
|
-
url?: string | undefined;
|
|
121
136
|
wait_ms?: number | undefined;
|
|
137
|
+
storage_state?: string | undefined;
|
|
122
138
|
x?: number | undefined;
|
|
123
139
|
y?: number | undefined;
|
|
140
|
+
timeout_ms?: number | undefined;
|
|
124
141
|
selector?: string | undefined;
|
|
125
142
|
delta_x?: number | undefined;
|
|
126
143
|
delta_y?: number | undefined;
|
|
@@ -128,7 +145,8 @@ export declare const interactInputSchema: z.ZodEffects<z.ZodObject<{
|
|
|
128
145
|
include_network?: boolean | undefined;
|
|
129
146
|
include_dom?: boolean | undefined;
|
|
130
147
|
include_performance?: boolean | undefined;
|
|
131
|
-
|
|
148
|
+
ref?: string | undefined;
|
|
149
|
+
include_snapshot?: boolean | undefined;
|
|
132
150
|
}>;
|
|
133
151
|
export type InteractInput = z.input<typeof interactInputSchema>;
|
|
134
152
|
/**
|
|
@@ -156,5 +174,6 @@ export declare function performInteraction(rawInput: InteractInput): Promise<Cal
|
|
|
156
174
|
*/
|
|
157
175
|
export declare function describeInteractFailure(input: {
|
|
158
176
|
url?: string;
|
|
177
|
+
ref?: string;
|
|
159
178
|
}, error: unknown): string;
|
|
160
179
|
export declare function registerInteractTool(server: McpServer): void;
|