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.
Files changed (133) hide show
  1. package/README.md +895 -15
  2. package/dist/constants.d.ts +274 -0
  3. package/dist/constants.js +279 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/engine/browser.d.ts +20 -4
  6. package/dist/engine/browser.js +26 -9
  7. package/dist/engine/browser.js.map +1 -1
  8. package/dist/engine/clicks.d.ts +221 -0
  9. package/dist/engine/clicks.js +801 -0
  10. package/dist/engine/clicks.js.map +1 -0
  11. package/dist/engine/forms.d.ts +137 -0
  12. package/dist/engine/forms.js +474 -0
  13. package/dist/engine/forms.js.map +1 -0
  14. package/dist/engine/hmr.d.ts +41 -0
  15. package/dist/engine/hmr.js +91 -0
  16. package/dist/engine/hmr.js.map +1 -0
  17. package/dist/engine/inspect.d.ts +31 -0
  18. package/dist/engine/inspect.js +383 -0
  19. package/dist/engine/inspect.js.map +1 -0
  20. package/dist/engine/interaction.d.ts +12 -7
  21. package/dist/engine/interaction.js +110 -18
  22. package/dist/engine/interaction.js.map +1 -1
  23. package/dist/engine/links.d.ts +134 -0
  24. package/dist/engine/links.js +384 -0
  25. package/dist/engine/links.js.map +1 -0
  26. package/dist/engine/mocks.d.ts +53 -0
  27. package/dist/engine/mocks.js +148 -0
  28. package/dist/engine/mocks.js.map +1 -0
  29. package/dist/engine/rtl.d.ts +129 -0
  30. package/dist/engine/rtl.js +540 -0
  31. package/dist/engine/rtl.js.map +1 -0
  32. package/dist/engine/seo.d.ts +189 -0
  33. package/dist/engine/seo.js +398 -0
  34. package/dist/engine/seo.js.map +1 -0
  35. package/dist/engine/snapshot.d.ts +29 -0
  36. package/dist/engine/snapshot.js +10 -0
  37. package/dist/engine/snapshot.js.map +1 -0
  38. package/dist/engine/vue.d.ts +54 -0
  39. package/dist/engine/vue.js +419 -0
  40. package/dist/engine/vue.js.map +1 -0
  41. package/dist/index.js +45 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/tools/accessibility.d.ts +4 -0
  44. package/dist/tools/accessibility.js +9 -2
  45. package/dist/tools/accessibility.js.map +1 -1
  46. package/dist/tools/api-mock.d.ts +405 -0
  47. package/dist/tools/api-mock.js +186 -0
  48. package/dist/tools/api-mock.js.map +1 -0
  49. package/dist/tools/capture.d.ts +90 -26
  50. package/dist/tools/capture.js +109 -58
  51. package/dist/tools/capture.js.map +1 -1
  52. package/dist/tools/compare.d.ts +4 -0
  53. package/dist/tools/compare.js +16 -5
  54. package/dist/tools/compare.js.map +1 -1
  55. package/dist/tools/dead-clicks.d.ts +128 -0
  56. package/dist/tools/dead-clicks.js +570 -0
  57. package/dist/tools/dead-clicks.js.map +1 -0
  58. package/dist/tools/form-test.d.ts +112 -0
  59. package/dist/tools/form-test.js +477 -0
  60. package/dist/tools/form-test.js.map +1 -0
  61. package/dist/tools/index.d.ts +17 -1
  62. package/dist/tools/index.js +45 -1
  63. package/dist/tools/index.js.map +1 -1
  64. package/dist/tools/inspect.d.ts +78 -0
  65. package/dist/tools/inspect.js +136 -0
  66. package/dist/tools/inspect.js.map +1 -0
  67. package/dist/tools/interact.d.ts +37 -18
  68. package/dist/tools/interact.js +113 -13
  69. package/dist/tools/interact.js.map +1 -1
  70. package/dist/tools/links.d.ts +129 -0
  71. package/dist/tools/links.js +640 -0
  72. package/dist/tools/links.js.map +1 -0
  73. package/dist/tools/responsive.d.ts +10 -6
  74. package/dist/tools/responsive.js +21 -4
  75. package/dist/tools/responsive.js.map +1 -1
  76. package/dist/tools/rtl.d.ts +241 -0
  77. package/dist/tools/rtl.js +410 -0
  78. package/dist/tools/rtl.js.map +1 -0
  79. package/dist/tools/save-auth.d.ts +263 -0
  80. package/dist/tools/save-auth.js +253 -0
  81. package/dist/tools/save-auth.js.map +1 -0
  82. package/dist/tools/screenshot.d.ts +4 -0
  83. package/dist/tools/screenshot.js +15 -4
  84. package/dist/tools/screenshot.js.map +1 -1
  85. package/dist/tools/seo.d.ts +113 -0
  86. package/dist/tools/seo.js +281 -0
  87. package/dist/tools/seo.js.map +1 -0
  88. package/dist/tools/snapshot.d.ts +122 -0
  89. package/dist/tools/snapshot.js +183 -0
  90. package/dist/tools/snapshot.js.map +1 -0
  91. package/dist/tools/wait-for.d.ts +107 -0
  92. package/dist/tools/wait-for.js +167 -0
  93. package/dist/tools/wait-for.js.map +1 -0
  94. package/dist/utils/arabic-text.d.ts +14 -0
  95. package/dist/utils/arabic-text.js +193 -0
  96. package/dist/utils/arabic-text.js.map +1 -0
  97. package/dist/utils/budget.d.ts +41 -0
  98. package/dist/utils/budget.js +182 -0
  99. package/dist/utils/budget.js.map +1 -0
  100. package/dist/utils/format.d.ts +11 -1
  101. package/dist/utils/format.js +27 -4
  102. package/dist/utils/format.js.map +1 -1
  103. package/dist/utils/highlight.d.ts +69 -0
  104. package/dist/utils/highlight.js +181 -0
  105. package/dist/utils/highlight.js.map +1 -0
  106. package/dist/utils/link-rules.d.ts +100 -0
  107. package/dist/utils/link-rules.js +284 -0
  108. package/dist/utils/link-rules.js.map +1 -0
  109. package/dist/utils/mock-rules.d.ts +144 -0
  110. package/dist/utils/mock-rules.js +224 -0
  111. package/dist/utils/mock-rules.js.map +1 -0
  112. package/dist/utils/rtl-rules.d.ts +142 -0
  113. package/dist/utils/rtl-rules.js +296 -0
  114. package/dist/utils/rtl-rules.js.map +1 -0
  115. package/dist/utils/seo-rules.d.ts +129 -0
  116. package/dist/utils/seo-rules.js +726 -0
  117. package/dist/utils/seo-rules.js.map +1 -0
  118. package/dist/utils/snapshot-rules.d.ts +33 -0
  119. package/dist/utils/snapshot-rules.js +111 -0
  120. package/dist/utils/snapshot-rules.js.map +1 -0
  121. package/dist/utils/storage-state.d.ts +76 -0
  122. package/dist/utils/storage-state.js +195 -0
  123. package/dist/utils/storage-state.js.map +1 -0
  124. package/dist/utils/style-rules.d.ts +107 -0
  125. package/dist/utils/style-rules.js +223 -0
  126. package/dist/utils/style-rules.js.map +1 -0
  127. package/dist/utils/test-data.d.ts +75 -0
  128. package/dist/utils/test-data.js +294 -0
  129. package/dist/utils/test-data.js.map +1 -0
  130. package/dist/utils/vue-rules.d.ts +72 -0
  131. package/dist/utils/vue-rules.js +108 -0
  132. package/dist/utils/vue-rules.js.map +1 -0
  133. package/package.json +6 -4
@@ -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
- /** 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
+ */
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
@@ -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;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,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,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 { 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 {\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 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,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
- action: "type" | "click" | "tap" | "scroll" | "swipe" | "navigate" | "select" | "hover";
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
- timeout_ms: number;
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
- value?: string | undefined;
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" | "navigate" | "select" | "hover";
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
- timeout_ms?: number | undefined;
104
+ ref?: string | undefined;
105
+ include_snapshot?: boolean | undefined;
94
106
  }>, {
95
107
  wait_ms: number;
96
- action: "type" | "click" | "tap" | "scroll" | "swipe" | "navigate" | "select" | "hover";
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
- timeout_ms: number;
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
- value?: string | undefined;
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" | "navigate" | "select" | "hover";
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
- timeout_ms?: number | undefined;
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;