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.
Files changed (127) hide show
  1. package/README.md +797 -8
  2. package/dist/constants.d.ts +264 -0
  3. package/dist/constants.js +268 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/engine/browser.js +4 -0
  6. package/dist/engine/browser.js.map +1 -1
  7. package/dist/engine/clicks.d.ts +221 -0
  8. package/dist/engine/clicks.js +801 -0
  9. package/dist/engine/clicks.js.map +1 -0
  10. package/dist/engine/forms.d.ts +137 -0
  11. package/dist/engine/forms.js +474 -0
  12. package/dist/engine/forms.js.map +1 -0
  13. package/dist/engine/hmr.d.ts +41 -0
  14. package/dist/engine/hmr.js +91 -0
  15. package/dist/engine/hmr.js.map +1 -0
  16. package/dist/engine/inspect.d.ts +31 -0
  17. package/dist/engine/inspect.js +383 -0
  18. package/dist/engine/inspect.js.map +1 -0
  19. package/dist/engine/interaction.d.ts +6 -1
  20. package/dist/engine/interaction.js +21 -6
  21. package/dist/engine/interaction.js.map +1 -1
  22. package/dist/engine/links.d.ts +134 -0
  23. package/dist/engine/links.js +384 -0
  24. package/dist/engine/links.js.map +1 -0
  25. package/dist/engine/mocks.d.ts +53 -0
  26. package/dist/engine/mocks.js +148 -0
  27. package/dist/engine/mocks.js.map +1 -0
  28. package/dist/engine/rtl.d.ts +129 -0
  29. package/dist/engine/rtl.js +540 -0
  30. package/dist/engine/rtl.js.map +1 -0
  31. package/dist/engine/seo.d.ts +189 -0
  32. package/dist/engine/seo.js +398 -0
  33. package/dist/engine/seo.js.map +1 -0
  34. package/dist/engine/snapshot.d.ts +29 -0
  35. package/dist/engine/snapshot.js +10 -0
  36. package/dist/engine/snapshot.js.map +1 -0
  37. package/dist/engine/vue.d.ts +54 -0
  38. package/dist/engine/vue.js +419 -0
  39. package/dist/engine/vue.js.map +1 -0
  40. package/dist/index.js +42 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/tools/accessibility.js +4 -3
  43. package/dist/tools/accessibility.js.map +1 -1
  44. package/dist/tools/api-mock.d.ts +405 -0
  45. package/dist/tools/api-mock.js +186 -0
  46. package/dist/tools/api-mock.js.map +1 -0
  47. package/dist/tools/capture.d.ts +83 -23
  48. package/dist/tools/capture.js +105 -60
  49. package/dist/tools/capture.js.map +1 -1
  50. package/dist/tools/compare.js +4 -3
  51. package/dist/tools/compare.js.map +1 -1
  52. package/dist/tools/dead-clicks.d.ts +128 -0
  53. package/dist/tools/dead-clicks.js +570 -0
  54. package/dist/tools/dead-clicks.js.map +1 -0
  55. package/dist/tools/form-test.d.ts +112 -0
  56. package/dist/tools/form-test.js +477 -0
  57. package/dist/tools/form-test.js.map +1 -0
  58. package/dist/tools/index.d.ts +16 -1
  59. package/dist/tools/index.js +42 -1
  60. package/dist/tools/index.js.map +1 -1
  61. package/dist/tools/inspect.d.ts +78 -0
  62. package/dist/tools/inspect.js +136 -0
  63. package/dist/tools/inspect.js.map +1 -0
  64. package/dist/tools/interact.d.ts +29 -16
  65. package/dist/tools/interact.js +99 -13
  66. package/dist/tools/interact.js.map +1 -1
  67. package/dist/tools/links.d.ts +129 -0
  68. package/dist/tools/links.js +640 -0
  69. package/dist/tools/links.js.map +1 -0
  70. package/dist/tools/responsive.d.ts +6 -6
  71. package/dist/tools/responsive.js +9 -9
  72. package/dist/tools/responsive.js.map +1 -1
  73. package/dist/tools/rtl.d.ts +241 -0
  74. package/dist/tools/rtl.js +410 -0
  75. package/dist/tools/rtl.js.map +1 -0
  76. package/dist/tools/save-auth.d.ts +22 -22
  77. package/dist/tools/screenshot.js +13 -5
  78. package/dist/tools/screenshot.js.map +1 -1
  79. package/dist/tools/seo.d.ts +113 -0
  80. package/dist/tools/seo.js +281 -0
  81. package/dist/tools/seo.js.map +1 -0
  82. package/dist/tools/snapshot.d.ts +122 -0
  83. package/dist/tools/snapshot.js +183 -0
  84. package/dist/tools/snapshot.js.map +1 -0
  85. package/dist/tools/wait-for.d.ts +107 -0
  86. package/dist/tools/wait-for.js +167 -0
  87. package/dist/tools/wait-for.js.map +1 -0
  88. package/dist/utils/arabic-text.d.ts +14 -0
  89. package/dist/utils/arabic-text.js +193 -0
  90. package/dist/utils/arabic-text.js.map +1 -0
  91. package/dist/utils/budget.d.ts +41 -0
  92. package/dist/utils/budget.js +182 -0
  93. package/dist/utils/budget.js.map +1 -0
  94. package/dist/utils/format.d.ts +11 -1
  95. package/dist/utils/format.js +27 -4
  96. package/dist/utils/format.js.map +1 -1
  97. package/dist/utils/highlight.d.ts +69 -0
  98. package/dist/utils/highlight.js +181 -0
  99. package/dist/utils/highlight.js.map +1 -0
  100. package/dist/utils/link-rules.d.ts +100 -0
  101. package/dist/utils/link-rules.js +284 -0
  102. package/dist/utils/link-rules.js.map +1 -0
  103. package/dist/utils/mock-rules.d.ts +144 -0
  104. package/dist/utils/mock-rules.js +224 -0
  105. package/dist/utils/mock-rules.js.map +1 -0
  106. package/dist/utils/rtl-rules.d.ts +142 -0
  107. package/dist/utils/rtl-rules.js +296 -0
  108. package/dist/utils/rtl-rules.js.map +1 -0
  109. package/dist/utils/seo-rules.d.ts +129 -0
  110. package/dist/utils/seo-rules.js +726 -0
  111. package/dist/utils/seo-rules.js.map +1 -0
  112. package/dist/utils/snapshot-rules.d.ts +33 -0
  113. package/dist/utils/snapshot-rules.js +111 -0
  114. package/dist/utils/snapshot-rules.js.map +1 -0
  115. package/dist/utils/storage-state.d.ts +42 -1
  116. package/dist/utils/storage-state.js +115 -4
  117. package/dist/utils/storage-state.js.map +1 -1
  118. package/dist/utils/style-rules.d.ts +107 -0
  119. package/dist/utils/style-rules.js +223 -0
  120. package/dist/utils/style-rules.js.map +1 -0
  121. package/dist/utils/test-data.d.ts +75 -0
  122. package/dist/utils/test-data.js +294 -0
  123. package/dist/utils/test-data.js.map +1 -0
  124. package/dist/utils/vue-rules.d.ts +72 -0
  125. package/dist/utils/vue-rules.js +108 -0
  126. package/dist/utils/vue-rules.js.map +1 -0
  127. package/package.json +6 -4
@@ -1,28 +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";
6
13
  import { registerSaveAuthTool } from "./save-auth.js";
7
14
  import { registerScreenshotTool } from "./screenshot.js";
15
+ import { registerSeoTool } from "./seo.js";
8
16
  import { registerServerTools } from "./server.js";
17
+ import { registerSnapshotTool } from "./snapshot.js";
18
+ import { registerWaitForTool } from "./wait-for.js";
9
19
  export { registerScreenshotTool, takeScreenshot, SCREENSHOT_TOOL_NAME } from "./screenshot.js";
10
20
  export { registerCaptureTool, capturePage, CAPTURE_TOOL_NAME } from "./capture.js";
21
+ export { registerApiMockTool, mockApi, API_MOCK_TOOL_NAME } from "./api-mock.js";
11
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";
12
26
  export { registerResponsiveTool, captureResponsive, RESPONSIVE_TOOL_NAME } from "./responsive.js";
13
27
  export { registerAccessibilityTool, auditAccessibility, ACCESSIBILITY_TOOL_NAME } from "./accessibility.js";
14
28
  export { registerCompareTool, comparePages, COMPARE_TOOL_NAME, CURRENT_PAGE } from "./compare.js";
15
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";
16
35
  export { registerServerTools, startServer, stopServer, START_SERVER_TOOL_NAME, STOP_SERVER_TOOL_NAME, } from "./server.js";
17
- /** Register every FrameWatch tool on the given server. */
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
+ */
18
43
  export function registerAllTools(server) {
44
+ budgetEveryTool(server);
19
45
  registerScreenshotTool(server);
20
46
  registerCaptureTool(server);
21
47
  registerInteractTool(server);
48
+ registerSnapshotTool(server);
49
+ registerInspectTool(server);
50
+ registerWaitForTool(server);
22
51
  registerResponsiveTool(server);
23
52
  registerAccessibilityTool(server);
24
53
  registerCompareTool(server);
25
54
  registerSaveAuthTool(server);
55
+ registerFormTestTool(server);
56
+ registerSeoTool(server);
57
+ registerDeadClicksTool(server);
58
+ registerLinksTool(server);
59
+ registerApiMockTool(server);
60
+ registerRtlTool(server);
26
61
  registerServerTools(server);
27
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
+ }
28
69
  //# sourceMappingURL=index.js.map
@@ -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,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC/F,OAAO,EAAE,mBAAmB,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAC7F,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAClG,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAC5G,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAClG,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrF,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,UAAU,EACV,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,aAAa,CAAC;AAErB,0DAA0D;AAC1D,MAAM,UAAU,gBAAgB,CAAC,MAAiB;IAChD,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/B,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/B,yBAAyB,CAAC,MAAM,CAAC,CAAC;IAClC,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5B,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAC7B,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAC9B,CAAC","sourcesContent":["import type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { registerAccessibilityTool } from \"./accessibility.js\";\nimport { registerCaptureTool } from \"./capture.js\";\nimport { registerCompareTool } from \"./compare.js\";\nimport { registerInteractTool } from \"./interact.js\";\nimport { registerResponsiveTool } from \"./responsive.js\";\nimport { registerSaveAuthTool } from \"./save-auth.js\";\nimport { registerScreenshotTool } from \"./screenshot.js\";\nimport { registerServerTools } from \"./server.js\";\n\nexport { registerScreenshotTool, takeScreenshot, SCREENSHOT_TOOL_NAME } from \"./screenshot.js\";\nexport { registerCaptureTool, capturePage, CAPTURE_TOOL_NAME } from \"./capture.js\";\nexport { registerInteractTool, performInteraction, INTERACT_TOOL_NAME } from \"./interact.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 {\n registerServerTools,\n startServer,\n stopServer,\n START_SERVER_TOOL_NAME,\n STOP_SERVER_TOOL_NAME,\n} from \"./server.js\";\n\n/** Register every FrameWatch tool on the given server. */\nexport function registerAllTools(server: McpServer): void {\n registerScreenshotTool(server);\n registerCaptureTool(server);\n registerInteractTool(server);\n registerResponsiveTool(server);\n registerAccessibilityTool(server);\n registerCompareTool(server);\n registerSaveAuthTool(server);\n registerServerTools(server);\n}\n"]}
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"]}
@@ -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,6 +21,7 @@ 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>;
23
25
  storage_state: z.ZodOptional<z.ZodString>;
24
26
  selector: z.ZodOptional<z.ZodString>;
25
27
  value: z.ZodOptional<z.ZodString>;
@@ -30,6 +32,7 @@ export declare const interactInputShape: {
30
32
  action: z.ZodEnum<["click", "tap", "type", "key", "scroll", "swipe", "navigate", "select", "hover"]>;
31
33
  };
32
34
  export declare const interactInputSchema: z.ZodEffects<z.ZodObject<{
35
+ ref: z.ZodOptional<z.ZodString>;
33
36
  url: z.ZodOptional<z.ZodString>;
34
37
  wait_ms: z.ZodDefault<z.ZodNumber>;
35
38
  timeout_ms: z.ZodDefault<z.ZodNumber>;
@@ -47,6 +50,7 @@ export declare const interactInputSchema: z.ZodEffects<z.ZodObject<{
47
50
  include_network: z.ZodDefault<z.ZodBoolean>;
48
51
  include_dom: z.ZodDefault<z.ZodBoolean>;
49
52
  include_performance: z.ZodDefault<z.ZodBoolean>;
53
+ include_snapshot: z.ZodDefault<z.ZodBoolean>;
50
54
  storage_state: z.ZodOptional<z.ZodString>;
51
55
  selector: z.ZodOptional<z.ZodString>;
52
56
  value: z.ZodOptional<z.ZodString>;
@@ -57,36 +61,39 @@ export declare const interactInputSchema: z.ZodEffects<z.ZodObject<{
57
61
  action: z.ZodEnum<["click", "tap", "type", "key", "scroll", "swipe", "navigate", "select", "hover"]>;
58
62
  }, "strip", z.ZodTypeAny, {
59
63
  wait_ms: number;
60
- action: "type" | "click" | "tap" | "key" | "scroll" | "swipe" | "hover" | "select" | "navigate";
64
+ timeout_ms: number;
65
+ action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "navigate";
61
66
  include_console: boolean;
62
67
  include_network: boolean;
63
68
  include_dom: boolean;
64
69
  include_performance: boolean;
65
- timeout_ms: number;
70
+ include_snapshot: boolean;
71
+ url?: string | undefined;
72
+ value?: string | undefined;
66
73
  viewport?: {
67
74
  width: number;
68
75
  height: number;
69
76
  } | undefined;
70
- value?: string | undefined;
71
- url?: string | undefined;
72
77
  storage_state?: string | undefined;
73
78
  x?: number | undefined;
74
79
  y?: number | undefined;
75
80
  selector?: string | undefined;
76
81
  delta_x?: number | undefined;
77
82
  delta_y?: number | undefined;
83
+ ref?: string | undefined;
78
84
  }, {
79
- action: "type" | "click" | "tap" | "key" | "scroll" | "swipe" | "hover" | "select" | "navigate";
85
+ action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "navigate";
86
+ url?: string | undefined;
87
+ value?: string | undefined;
80
88
  viewport?: {
81
89
  width: number;
82
90
  height: number;
83
91
  } | undefined;
84
- value?: string | undefined;
85
- url?: string | undefined;
86
92
  wait_ms?: number | undefined;
87
93
  storage_state?: string | undefined;
88
94
  x?: number | undefined;
89
95
  y?: number | undefined;
96
+ timeout_ms?: number | undefined;
90
97
  selector?: string | undefined;
91
98
  delta_x?: number | undefined;
92
99
  delta_y?: number | undefined;
@@ -94,39 +101,43 @@ export declare const interactInputSchema: z.ZodEffects<z.ZodObject<{
94
101
  include_network?: boolean | undefined;
95
102
  include_dom?: boolean | undefined;
96
103
  include_performance?: boolean | undefined;
97
- timeout_ms?: number | undefined;
104
+ ref?: string | undefined;
105
+ include_snapshot?: boolean | undefined;
98
106
  }>, {
99
107
  wait_ms: number;
100
- action: "type" | "click" | "tap" | "key" | "scroll" | "swipe" | "hover" | "select" | "navigate";
108
+ timeout_ms: number;
109
+ action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "navigate";
101
110
  include_console: boolean;
102
111
  include_network: boolean;
103
112
  include_dom: boolean;
104
113
  include_performance: boolean;
105
- timeout_ms: number;
114
+ include_snapshot: boolean;
115
+ url?: string | undefined;
116
+ value?: string | undefined;
106
117
  viewport?: {
107
118
  width: number;
108
119
  height: number;
109
120
  } | undefined;
110
- value?: string | undefined;
111
- url?: string | undefined;
112
121
  storage_state?: string | undefined;
113
122
  x?: number | undefined;
114
123
  y?: number | undefined;
115
124
  selector?: string | undefined;
116
125
  delta_x?: number | undefined;
117
126
  delta_y?: number | undefined;
127
+ ref?: string | undefined;
118
128
  }, {
119
- action: "type" | "click" | "tap" | "key" | "scroll" | "swipe" | "hover" | "select" | "navigate";
129
+ action: "type" | "key" | "click" | "tap" | "scroll" | "swipe" | "hover" | "select" | "navigate";
130
+ url?: string | undefined;
131
+ value?: string | undefined;
120
132
  viewport?: {
121
133
  width: number;
122
134
  height: number;
123
135
  } | undefined;
124
- value?: string | undefined;
125
- url?: string | undefined;
126
136
  wait_ms?: number | undefined;
127
137
  storage_state?: string | undefined;
128
138
  x?: number | undefined;
129
139
  y?: number | undefined;
140
+ timeout_ms?: number | undefined;
130
141
  selector?: string | undefined;
131
142
  delta_x?: number | undefined;
132
143
  delta_y?: number | undefined;
@@ -134,7 +145,8 @@ export declare const interactInputSchema: z.ZodEffects<z.ZodObject<{
134
145
  include_network?: boolean | undefined;
135
146
  include_dom?: boolean | undefined;
136
147
  include_performance?: boolean | undefined;
137
- timeout_ms?: number | undefined;
148
+ ref?: string | undefined;
149
+ include_snapshot?: boolean | undefined;
138
150
  }>;
139
151
  export type InteractInput = z.input<typeof interactInputSchema>;
140
152
  /**
@@ -162,5 +174,6 @@ export declare function performInteraction(rawInput: InteractInput): Promise<Cal
162
174
  */
163
175
  export declare function describeInteractFailure(input: {
164
176
  url?: string;
177
+ ref?: string;
165
178
  }, error: unknown): string;
166
179
  export declare function registerInteractTool(server: McpServer): void;