auto-feedback 0.1.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 (90) hide show
  1. package/README.md +180 -0
  2. package/build/capture/console-collector.d.ts +16 -0
  3. package/build/capture/console-collector.js +43 -0
  4. package/build/capture/error-collector.d.ts +15 -0
  5. package/build/capture/error-collector.js +47 -0
  6. package/build/capture/network-collector.d.ts +16 -0
  7. package/build/capture/network-collector.js +76 -0
  8. package/build/capture/process-collector.d.ts +16 -0
  9. package/build/capture/process-collector.js +48 -0
  10. package/build/capture/types.d.ts +61 -0
  11. package/build/capture/types.js +5 -0
  12. package/build/index.d.ts +6 -0
  13. package/build/index.js +41 -0
  14. package/build/interaction/selectors.d.ts +26 -0
  15. package/build/interaction/selectors.js +84 -0
  16. package/build/interaction/types.d.ts +56 -0
  17. package/build/interaction/types.js +5 -0
  18. package/build/process/cleanup.d.ts +23 -0
  19. package/build/process/cleanup.js +50 -0
  20. package/build/process/launcher.d.ts +22 -0
  21. package/build/process/launcher.js +54 -0
  22. package/build/process/monitor.d.ts +14 -0
  23. package/build/process/monitor.js +67 -0
  24. package/build/process/types.d.ts +84 -0
  25. package/build/process/types.js +5 -0
  26. package/build/screenshot/auto-capture.d.ts +14 -0
  27. package/build/screenshot/auto-capture.js +38 -0
  28. package/build/screenshot/capture.d.ts +21 -0
  29. package/build/screenshot/capture.js +48 -0
  30. package/build/screenshot/optimize.d.ts +19 -0
  31. package/build/screenshot/optimize.js +28 -0
  32. package/build/screenshot/types.d.ts +43 -0
  33. package/build/screenshot/types.js +4 -0
  34. package/build/server.d.ts +10 -0
  35. package/build/server.js +18 -0
  36. package/build/session-manager.d.ts +119 -0
  37. package/build/session-manager.js +284 -0
  38. package/build/tools/check-port.d.ts +10 -0
  39. package/build/tools/check-port.js +40 -0
  40. package/build/tools/click-element.d.ts +13 -0
  41. package/build/tools/click-element.js +118 -0
  42. package/build/tools/get-console-logs.d.ts +7 -0
  43. package/build/tools/get-console-logs.js +55 -0
  44. package/build/tools/get-element-state.d.ts +14 -0
  45. package/build/tools/get-element-state.js +116 -0
  46. package/build/tools/get-errors.d.ts +7 -0
  47. package/build/tools/get-errors.js +40 -0
  48. package/build/tools/get-network-logs.d.ts +7 -0
  49. package/build/tools/get-network-logs.js +58 -0
  50. package/build/tools/get-process-output.d.ts +7 -0
  51. package/build/tools/get-process-output.js +55 -0
  52. package/build/tools/get-screenshot.d.ts +7 -0
  53. package/build/tools/get-screenshot.js +32 -0
  54. package/build/tools/index.d.ts +9 -0
  55. package/build/tools/index.js +117 -0
  56. package/build/tools/launch-electron.d.ts +13 -0
  57. package/build/tools/launch-electron.js +97 -0
  58. package/build/tools/launch-web-server.d.ts +13 -0
  59. package/build/tools/launch-web-server.js +88 -0
  60. package/build/tools/launch-windows-exe.d.ts +13 -0
  61. package/build/tools/launch-windows-exe.js +81 -0
  62. package/build/tools/navigate.d.ts +13 -0
  63. package/build/tools/navigate.js +137 -0
  64. package/build/tools/run-workflow.d.ts +14 -0
  65. package/build/tools/run-workflow.js +207 -0
  66. package/build/tools/screenshot-desktop.d.ts +13 -0
  67. package/build/tools/screenshot-desktop.js +80 -0
  68. package/build/tools/screenshot-electron.d.ts +13 -0
  69. package/build/tools/screenshot-electron.js +72 -0
  70. package/build/tools/screenshot-web.d.ts +13 -0
  71. package/build/tools/screenshot-web.js +129 -0
  72. package/build/tools/stop-process.d.ts +14 -0
  73. package/build/tools/stop-process.js +41 -0
  74. package/build/tools/type-text.d.ts +13 -0
  75. package/build/tools/type-text.js +137 -0
  76. package/build/tools/wait-for-element.d.ts +14 -0
  77. package/build/tools/wait-for-element.js +93 -0
  78. package/build/types/index.d.ts +31 -0
  79. package/build/types/index.js +4 -0
  80. package/build/utils/errors.d.ts +26 -0
  81. package/build/utils/errors.js +62 -0
  82. package/build/utils/shutdown.d.ts +16 -0
  83. package/build/utils/shutdown.js +34 -0
  84. package/build/workflow/assertions.d.ts +25 -0
  85. package/build/workflow/assertions.js +326 -0
  86. package/build/workflow/executor.d.ts +34 -0
  87. package/build/workflow/executor.js +269 -0
  88. package/build/workflow/types.d.ts +95 -0
  89. package/build/workflow/types.js +6 -0
  90. package/package.json +36 -0
@@ -0,0 +1,13 @@
1
+ /**
2
+ * screenshot_electron MCP tool
3
+ * Captures a screenshot of a running Electron app via stored Page reference
4
+ */
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { SessionManager } from "../session-manager.js";
7
+ /**
8
+ * Register the screenshot_electron tool with the MCP server
9
+ *
10
+ * @param server - MCP server instance
11
+ * @param sessionManager - Session manager for resource tracking
12
+ */
13
+ export declare function registerScreenshotElectronTool(server: McpServer, sessionManager: SessionManager): void;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * screenshot_electron MCP tool
3
+ * Captures a screenshot of a running Electron app via stored Page reference
4
+ */
5
+ import { z } from "zod";
6
+ import { createToolError, createScreenshotResult } from "../utils/errors.js";
7
+ import { capturePlaywrightPage } from "../screenshot/capture.js";
8
+ import { optimizeScreenshot } from "../screenshot/optimize.js";
9
+ /**
10
+ * Register the screenshot_electron tool with the MCP server
11
+ *
12
+ * @param server - MCP server instance
13
+ * @param sessionManager - Session manager for resource tracking
14
+ */
15
+ export function registerScreenshotElectronTool(server, sessionManager) {
16
+ server.tool("screenshot_electron", "Capture a screenshot of a running Electron app. Use after launch_electron to see the current visual state.", {
17
+ sessionId: z.string().describe("Session ID of the Electron app"),
18
+ fullPage: z
19
+ .boolean()
20
+ .optional()
21
+ .describe("Capture full page (true) or viewport only (false, default)"),
22
+ maxWidth: z
23
+ .number()
24
+ .int()
25
+ .min(100)
26
+ .max(3840)
27
+ .optional()
28
+ .describe("Max image width in pixels (default: 1280)"),
29
+ quality: z
30
+ .number()
31
+ .int()
32
+ .min(1)
33
+ .max(100)
34
+ .optional()
35
+ .describe("WebP quality 1-100 (default: 80)"),
36
+ }, async ({ sessionId, fullPage, maxWidth, quality }) => {
37
+ try {
38
+ const session = sessionManager.get(sessionId);
39
+ if (!session) {
40
+ return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended", "Create a session and launch an Electron app first.");
41
+ }
42
+ const pageRef = sessionManager.getPageRef(sessionId, "electron");
43
+ if (!pageRef) {
44
+ return createToolError("No Electron app found for this session", `Session ${sessionId} has no Electron page reference`, "Launch an Electron app first with launch_electron.");
45
+ }
46
+ console.error(`[screenshot_electron] Capturing for session ${sessionId}`);
47
+ // Capture raw PNG from Playwright Page
48
+ const rawBuffer = await capturePlaywrightPage(pageRef.page, {
49
+ fullPage: fullPage ?? false,
50
+ });
51
+ // Optimize: resize + WebP conversion
52
+ const optimized = await optimizeScreenshot(rawBuffer, {
53
+ maxWidth: maxWidth ?? 1280,
54
+ quality: quality ?? 80,
55
+ });
56
+ const imageBase64 = optimized.data.toString("base64");
57
+ return createScreenshotResult({
58
+ sessionId,
59
+ type: "electron",
60
+ mode: fullPage ? "full-page" : "viewport",
61
+ width: optimized.width,
62
+ height: optimized.height,
63
+ originalSize: rawBuffer.length,
64
+ optimizedSize: optimized.data.length,
65
+ }, imageBase64, optimized.mimeType);
66
+ }
67
+ catch (error) {
68
+ const message = error instanceof Error ? error.message : String(error);
69
+ return createToolError("Failed to capture Electron screenshot", message, "Ensure the Electron app is still running and the window is visible.");
70
+ }
71
+ });
72
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * screenshot_web MCP tool
3
+ * Captures a screenshot of a web page by URL with lazy browser creation
4
+ */
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { SessionManager } from "../session-manager.js";
7
+ /**
8
+ * Register the screenshot_web tool with the MCP server
9
+ *
10
+ * @param server - MCP server instance
11
+ * @param sessionManager - Session manager for resource tracking
12
+ */
13
+ export declare function registerScreenshotWebTool(server: McpServer, sessionManager: SessionManager): void;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * screenshot_web MCP tool
3
+ * Captures a screenshot of a web page by URL with lazy browser creation
4
+ */
5
+ import { z } from "zod";
6
+ import { chromium } from "playwright";
7
+ import { createToolError, createScreenshotResult } from "../utils/errors.js";
8
+ import { capturePlaywrightPage } from "../screenshot/capture.js";
9
+ import { optimizeScreenshot } from "../screenshot/optimize.js";
10
+ import { setupAutoCapture } from "../screenshot/auto-capture.js";
11
+ import { attachConsoleCollector } from "../capture/console-collector.js";
12
+ import { attachErrorCollector } from "../capture/error-collector.js";
13
+ import { attachNetworkCollector } from "../capture/network-collector.js";
14
+ /**
15
+ * Register the screenshot_web tool with the MCP server
16
+ *
17
+ * @param server - MCP server instance
18
+ * @param sessionManager - Session manager for resource tracking
19
+ */
20
+ export function registerScreenshotWebTool(server, sessionManager) {
21
+ server.tool("screenshot_web", "Capture a screenshot of a web page by URL. Creates a browser automatically on first use. Use to see visual state of web apps.", {
22
+ sessionId: z
23
+ .string()
24
+ .describe("Session ID to associate the browser with"),
25
+ url: z
26
+ .string()
27
+ .url()
28
+ .describe("URL to screenshot (e.g., http://localhost:3000)"),
29
+ fullPage: z
30
+ .boolean()
31
+ .optional()
32
+ .describe("Capture full page (true) or viewport only (false, default)"),
33
+ maxWidth: z
34
+ .number()
35
+ .int()
36
+ .min(100)
37
+ .max(3840)
38
+ .optional()
39
+ .describe("Max image width in pixels (default: 1280)"),
40
+ quality: z
41
+ .number()
42
+ .int()
43
+ .min(1)
44
+ .max(100)
45
+ .optional()
46
+ .describe("WebP quality 1-100 (default: 80)"),
47
+ }, async ({ sessionId, url, fullPage, maxWidth, quality }) => {
48
+ try {
49
+ const session = sessionManager.get(sessionId);
50
+ if (!session) {
51
+ return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended", "Create a session first with create_session.");
52
+ }
53
+ // Check for existing page reference for this URL
54
+ let pageRef = sessionManager.getPageRef(sessionId, url);
55
+ if (!pageRef) {
56
+ // Lazy browser creation: first screenshot of this URL
57
+ console.error(`[screenshot_web] Creating browser for ${url} in session ${sessionId}`);
58
+ const browser = await chromium.launch({ headless: true });
59
+ const context = await browser.newContext({
60
+ viewport: { width: 1280, height: 720 },
61
+ });
62
+ const page = await context.newPage();
63
+ await page.goto(url, { waitUntil: "load", timeout: 30000 });
64
+ // Store page reference for reuse
65
+ pageRef = {
66
+ type: "web",
67
+ page,
68
+ browser,
69
+ browserContext: context,
70
+ url,
71
+ };
72
+ sessionManager.setPageRef(sessionId, url, pageRef);
73
+ // Attach auto-capture on navigation
74
+ setupAutoCapture(page, sessionId, sessionManager);
75
+ // Attach diagnostic collectors
76
+ const consoleCollector = attachConsoleCollector(page);
77
+ const errorCollector = attachErrorCollector(page);
78
+ const networkCollector = attachNetworkCollector(page);
79
+ sessionManager.setConsoleCollector(sessionId, url, consoleCollector);
80
+ sessionManager.setErrorCollector(sessionId, url, errorCollector);
81
+ sessionManager.setNetworkCollector(sessionId, url, networkCollector);
82
+ // Register browser cleanup as a session resource
83
+ sessionManager.addResource(sessionId, {
84
+ cleanup: async () => {
85
+ console.error(`[screenshot_web] Closing browser for ${url}`);
86
+ await context.close().catch(() => { });
87
+ await browser.close().catch(() => { });
88
+ },
89
+ });
90
+ console.error(`[screenshot_web] Browser ready for ${url}`);
91
+ }
92
+ else {
93
+ // Reuse existing page — navigate if URL changed
94
+ const currentUrl = pageRef.page.url();
95
+ if (currentUrl !== url) {
96
+ await pageRef.page.goto(url, {
97
+ waitUntil: "load",
98
+ timeout: 30000,
99
+ });
100
+ }
101
+ }
102
+ console.error(`[screenshot_web] Capturing ${url} for session ${sessionId}`);
103
+ // Capture raw PNG
104
+ const rawBuffer = await capturePlaywrightPage(pageRef.page, {
105
+ fullPage: fullPage ?? false,
106
+ });
107
+ // Optimize: resize + WebP
108
+ const optimized = await optimizeScreenshot(rawBuffer, {
109
+ maxWidth: maxWidth ?? 1280,
110
+ quality: quality ?? 80,
111
+ });
112
+ const imageBase64 = optimized.data.toString("base64");
113
+ return createScreenshotResult({
114
+ sessionId,
115
+ type: "web",
116
+ url,
117
+ mode: fullPage ? "full-page" : "viewport",
118
+ width: optimized.width,
119
+ height: optimized.height,
120
+ originalSize: rawBuffer.length,
121
+ optimizedSize: optimized.data.length,
122
+ }, imageBase64, optimized.mimeType);
123
+ }
124
+ catch (error) {
125
+ const message = error instanceof Error ? error.message : String(error);
126
+ return createToolError("Failed to capture web screenshot", message, "Check the URL is accessible. For dev servers, ensure the server is running first (use launch_web_server).");
127
+ }
128
+ });
129
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * stop_process MCP tool (PROC-05)
3
+ * Stops all processes in a session by destroying the session and its resources
4
+ */
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { SessionManager } from "../session-manager.js";
7
+ /**
8
+ * Register the stop_process tool with the MCP server
9
+ *
10
+ * Destroys a session, which triggers cleanup of all registered resources
11
+ * (process trees, Electron apps, etc.). This is intentionally session-wide
12
+ * because each session typically manages one application under test.
13
+ */
14
+ export declare function registerStopProcessTool(server: McpServer, sessionManager: SessionManager): void;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * stop_process MCP tool (PROC-05)
3
+ * Stops all processes in a session by destroying the session and its resources
4
+ */
5
+ import { z } from "zod";
6
+ import { createToolError, createToolResult } from "../utils/errors.js";
7
+ /**
8
+ * Register the stop_process tool with the MCP server
9
+ *
10
+ * Destroys a session, which triggers cleanup of all registered resources
11
+ * (process trees, Electron apps, etc.). This is intentionally session-wide
12
+ * because each session typically manages one application under test.
13
+ */
14
+ export function registerStopProcessTool(server, sessionManager) {
15
+ server.tool("stop_process", "Stop all processes in a session and clean up resources. Use to terminate running apps when done or to free ports.", {
16
+ sessionId: z.string().describe("Session ID whose processes to stop"),
17
+ }, async ({ sessionId }) => {
18
+ try {
19
+ // Validate session exists
20
+ const session = sessionManager.get(sessionId);
21
+ if (!session) {
22
+ const availableSessions = sessionManager.list();
23
+ return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended or never existed", availableSessions.length > 0
24
+ ? `Available sessions: ${availableSessions.join(", ")}`
25
+ : "No active sessions. Use list_sessions to check.");
26
+ }
27
+ console.error(`[stop_process] Stopping all processes in session ${sessionId}`);
28
+ // Destroy session -- triggers cleanup of all registered resources
29
+ await sessionManager.destroy(sessionId);
30
+ return createToolResult({
31
+ sessionId,
32
+ stopped: true,
33
+ message: "All processes in session stopped and resources cleaned up",
34
+ });
35
+ }
36
+ catch (error) {
37
+ const message = error instanceof Error ? error.message : String(error);
38
+ return createToolError("Failed to stop processes", message, "The session may be in an inconsistent state. Try end_session or create a new session.");
39
+ }
40
+ });
41
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * type_text MCP tool
3
+ * Types text into input fields on web or Electron pages using Playwright Locator API
4
+ */
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { SessionManager } from "../session-manager.js";
7
+ /**
8
+ * Register the type_text tool with the MCP server
9
+ *
10
+ * @param server - MCP server instance
11
+ * @param sessionManager - Session manager for resource tracking
12
+ */
13
+ export declare function registerTypeTextTool(server: McpServer, sessionManager: SessionManager): void;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * type_text MCP tool
3
+ * Types text into input fields on web or Electron pages using Playwright Locator API
4
+ */
5
+ import { z } from "zod";
6
+ import { createToolError, createScreenshotResult } from "../utils/errors.js";
7
+ import { capturePlaywrightPage } from "../screenshot/capture.js";
8
+ import { optimizeScreenshot } from "../screenshot/optimize.js";
9
+ import { resolveSelector, getActivePage } from "../interaction/selectors.js";
10
+ /**
11
+ * Register the type_text tool with the MCP server
12
+ *
13
+ * @param server - MCP server instance
14
+ * @param sessionManager - Session manager for resource tracking
15
+ */
16
+ export function registerTypeTextTool(server, sessionManager) {
17
+ server.tool("type_text", "Type text into an input field or textarea on a web or Electron page. Returns a screenshot after typing. Uses fill (paste) by default; set pressSequentially for apps with keystroke handlers.", {
18
+ sessionId: z
19
+ .string()
20
+ .describe("Session ID from create_session"),
21
+ selector: z
22
+ .string()
23
+ .describe("Element selector for the input field. CSS: #email, .search-box, input[name='query']. Role: role=textbox[name='Email']. Test ID: testid=search-input"),
24
+ text: z
25
+ .string()
26
+ .describe("Text to type into the field"),
27
+ pageIdentifier: z
28
+ .string()
29
+ .optional()
30
+ .describe("URL or 'electron' to target a specific page. Omit if session has only one page."),
31
+ pressSequentially: z
32
+ .boolean()
33
+ .optional()
34
+ .describe("Type one character at a time instead of fill/paste (default: false). Use for inputs with autocomplete or per-keystroke handlers."),
35
+ delay: z
36
+ .number()
37
+ .int()
38
+ .min(0)
39
+ .optional()
40
+ .describe("Delay between keystrokes in ms (only used with pressSequentially, default: 50)"),
41
+ clear: z
42
+ .boolean()
43
+ .optional()
44
+ .describe("Clear the field before typing (default: true). Set false to append text."),
45
+ timeout: z
46
+ .number()
47
+ .int()
48
+ .min(0)
49
+ .optional()
50
+ .describe("Max wait time in ms for element readiness (default: 30000)"),
51
+ }, async ({ sessionId, selector, text, pageIdentifier, pressSequentially, delay, clear, timeout, }) => {
52
+ try {
53
+ // Validate session exists
54
+ const session = sessionManager.get(sessionId);
55
+ if (!session) {
56
+ const availableSessions = sessionManager.list();
57
+ return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended", availableSessions.length > 0
58
+ ? `Available sessions: ${availableSessions.join(", ")}`
59
+ : "Create a session first with create_session.");
60
+ }
61
+ // Find the active page
62
+ const pageResult = getActivePage(sessionManager, sessionId, pageIdentifier);
63
+ if (!pageResult.success) {
64
+ return createToolError(pageResult.error, `Session: ${sessionId}`, pageResult.availablePages
65
+ ? `Available pages: ${pageResult.availablePages.join(", ")}`
66
+ : undefined);
67
+ }
68
+ const { page } = pageResult;
69
+ // Resolve selector to Playwright Locator
70
+ const locator = resolveSelector(page, selector);
71
+ const effectiveTimeout = timeout ?? 30000;
72
+ // Perform the type action based on mode
73
+ if (clear === false && pressSequentially) {
74
+ // Append mode + pressSequentially: click to focus, then type char-by-char
75
+ await locator.click({ timeout: effectiveTimeout });
76
+ await locator.pressSequentially(text, {
77
+ delay: delay ?? 50,
78
+ timeout: effectiveTimeout,
79
+ });
80
+ }
81
+ else if (pressSequentially) {
82
+ // Clear first, then type char-by-char
83
+ await locator.fill("", { timeout: effectiveTimeout });
84
+ await locator.pressSequentially(text, {
85
+ delay: delay ?? 50,
86
+ timeout: effectiveTimeout,
87
+ });
88
+ }
89
+ else if (clear === false) {
90
+ // Append mode + fill: click to focus, then insertText to append
91
+ await locator.click({ timeout: effectiveTimeout });
92
+ await page.keyboard.insertText(text);
93
+ }
94
+ else {
95
+ // Default: fill() clears and types in one step
96
+ await locator.fill(text, { timeout: effectiveTimeout });
97
+ }
98
+ // Capture post-type screenshot
99
+ const rawBuffer = await capturePlaywrightPage(page, {
100
+ fullPage: false,
101
+ });
102
+ const optimized = await optimizeScreenshot(rawBuffer, {
103
+ maxWidth: 1280,
104
+ quality: 80,
105
+ });
106
+ const imageBase64 = optimized.data.toString("base64");
107
+ return createScreenshotResult({
108
+ sessionId,
109
+ action: "type",
110
+ selector,
111
+ textLength: text.length,
112
+ mode: pressSequentially ? "pressSequentially" : "fill",
113
+ success: true,
114
+ }, imageBase64, optimized.mimeType);
115
+ }
116
+ catch (error) {
117
+ const message = error instanceof Error ? error.message : String(error);
118
+ // Strict mode violation: selector matched multiple elements
119
+ if (message.includes("strict mode violation")) {
120
+ return createToolError("Selector matched multiple elements", `Selector "${selector}" matched more than one element (strict mode violation)`, "Use a more specific selector or add :nth-child(), :first-of-type, or similar to target a single element.");
121
+ }
122
+ // Timeout: element not found or not actionable within timeout
123
+ if (message.includes("Timeout") ||
124
+ message.includes("timeout")) {
125
+ return createToolError("Element not found or not editable within timeout", `Selector "${selector}" did not match any visible, editable element within ${timeout ?? 30000}ms`, "Check the selector is correct, the element is visible and editable, or increase the timeout. Take a screenshot first to verify the page state.");
126
+ }
127
+ // Element is not editable (not an input/textarea/contenteditable)
128
+ if (message.includes("not an <input>") ||
129
+ message.includes("not editable") ||
130
+ message.includes("Element is not")) {
131
+ return createToolError("Element is not a text input", `Selector "${selector}" matched an element that cannot accept text input`, "Ensure the selector targets an <input>, <textarea>, or contenteditable element. Take a screenshot to verify.");
132
+ }
133
+ // Default error
134
+ return createToolError("Failed to type text", `Selector: "${selector}" — ${message}`, "Take a screenshot to verify the element exists and is an editable text field.");
135
+ }
136
+ });
137
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * wait_for_element MCP tool
3
+ * Waits for an element to reach a specific state (visible, hidden, attached, detached)
4
+ * and returns a screenshot after the wait since the page likely changed.
5
+ */
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import { SessionManager } from "../session-manager.js";
8
+ /**
9
+ * Register the wait_for_element tool with the MCP server
10
+ *
11
+ * @param server - MCP server instance
12
+ * @param sessionManager - Session manager for resource tracking
13
+ */
14
+ export declare function registerWaitForElementTool(server: McpServer, sessionManager: SessionManager): void;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * wait_for_element MCP tool
3
+ * Waits for an element to reach a specific state (visible, hidden, attached, detached)
4
+ * and returns a screenshot after the wait since the page likely changed.
5
+ */
6
+ import { z } from "zod";
7
+ import { createToolError, createScreenshotResult } from "../utils/errors.js";
8
+ import { resolveSelector, getActivePage } from "../interaction/selectors.js";
9
+ import { capturePlaywrightPage } from "../screenshot/capture.js";
10
+ import { optimizeScreenshot } from "../screenshot/optimize.js";
11
+ /**
12
+ * Register the wait_for_element tool with the MCP server
13
+ *
14
+ * @param server - MCP server instance
15
+ * @param sessionManager - Session manager for resource tracking
16
+ */
17
+ export function registerWaitForElementTool(server, sessionManager) {
18
+ server.tool("wait_for_element", "Wait for an element to reach a specific state. Use before interacting with elements that may not be ready yet (loading spinners, disabled buttons, lazy content).", {
19
+ sessionId: z
20
+ .string()
21
+ .describe("Session ID from create_session"),
22
+ selector: z
23
+ .string()
24
+ .describe("Element selector. CSS: #id, .class, div > span. Text: text=Click me. Role: role=button[name='Submit']. Test ID: testid=my-btn"),
25
+ state: z
26
+ .enum(["visible", "hidden", "attached", "detached"])
27
+ .describe("Target state to wait for. 'visible': element visible on page. 'hidden': element hidden or removed. 'attached': element in DOM (may be hidden). 'detached': element removed from DOM."),
28
+ pageIdentifier: z
29
+ .string()
30
+ .optional()
31
+ .describe("URL or 'electron' to target a specific page. Omit if session has only one page."),
32
+ timeout: z
33
+ .number()
34
+ .int()
35
+ .min(0)
36
+ .optional()
37
+ .describe("Max wait time in ms (default: 30000)"),
38
+ }, async ({ sessionId, selector, state, pageIdentifier, timeout }) => {
39
+ try {
40
+ // Validate session exists
41
+ const session = sessionManager.get(sessionId);
42
+ if (!session) {
43
+ const availableSessions = sessionManager.list();
44
+ return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended", availableSessions.length > 0
45
+ ? `Available sessions: ${availableSessions.join(", ")}`
46
+ : "Create a session first with create_session.");
47
+ }
48
+ // Find the active page
49
+ const pageResult = getActivePage(sessionManager, sessionId, pageIdentifier);
50
+ if (!pageResult.success) {
51
+ return createToolError(pageResult.error, `Session: ${sessionId}`, pageResult.availablePages
52
+ ? `Available pages: ${pageResult.availablePages.join(", ")}`
53
+ : undefined);
54
+ }
55
+ const { page } = pageResult;
56
+ const effectiveTimeout = timeout ?? 30000;
57
+ // Resolve selector to Playwright Locator
58
+ const locator = resolveSelector(page, selector);
59
+ // Wait for the specified state
60
+ await locator.waitFor({ state, timeout: effectiveTimeout });
61
+ // Capture post-wait screenshot (page likely changed during wait)
62
+ const rawBuffer = await capturePlaywrightPage(page, {
63
+ fullPage: false,
64
+ });
65
+ const optimized = await optimizeScreenshot(rawBuffer, {
66
+ maxWidth: 1280,
67
+ quality: 80,
68
+ });
69
+ const imageBase64 = optimized.data.toString("base64");
70
+ return createScreenshotResult({
71
+ sessionId,
72
+ action: "wait_for_element",
73
+ selector,
74
+ state,
75
+ success: true,
76
+ }, imageBase64, optimized.mimeType);
77
+ }
78
+ catch (error) {
79
+ const message = error instanceof Error ? error.message : String(error);
80
+ // Strict mode violation: selector matched multiple elements
81
+ if (message.includes("strict mode violation")) {
82
+ return createToolError("Selector matched multiple elements", `Selector "${selector}" matched more than one element (strict mode violation)`, "Use a more specific selector or add :nth-child(), :first-of-type, or similar to target a single element.");
83
+ }
84
+ // Timeout: element did not reach expected state
85
+ if (message.includes("Timeout") ||
86
+ message.includes("timeout")) {
87
+ return createToolError(`Element did not reach state '${state}' within ${timeout ?? 30000}ms`, `Selector: "${selector}", target state: "${state}"`, `The element exists but did not become ${state}. Take a screenshot to see the current page state.`);
88
+ }
89
+ // Default error
90
+ return createToolError("Failed to wait for element", `Selector: "${selector}", state: "${state}" — ${message}`, "Take a screenshot to verify the element exists and is visible on the page.");
91
+ }
92
+ });
93
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Shared type definitions for the Feedback MCP server
3
+ */
4
+ /**
5
+ * Resource with cleanup capability
6
+ */
7
+ export interface Resource {
8
+ cleanup: () => Promise<void>;
9
+ }
10
+ /**
11
+ * Session tracking active resources
12
+ */
13
+ export interface Session {
14
+ id: string;
15
+ createdAt: Date;
16
+ resources: Resource[];
17
+ }
18
+ /**
19
+ * MCP tool result format (supports text and image content)
20
+ */
21
+ export type ToolResult = {
22
+ content: Array<{
23
+ type: "text";
24
+ text: string;
25
+ } | {
26
+ type: "image";
27
+ data: string;
28
+ mimeType: string;
29
+ }>;
30
+ isError?: boolean;
31
+ };
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Shared type definitions for the Feedback MCP server
3
+ */
4
+ export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Structured error formatting for tool results
3
+ */
4
+ import { ToolResult } from "../types/index.js";
5
+ /**
6
+ * Create a tool error response with structured information
7
+ * @param message Error message
8
+ * @param context Optional context about what was being attempted
9
+ * @param suggestedFix Optional suggestion for how to fix the issue
10
+ * @returns ToolResult with isError: true
11
+ */
12
+ export declare function createToolError(message: string, context?: string, suggestedFix?: string): ToolResult;
13
+ /**
14
+ * Create a successful tool result
15
+ * @param data Data to serialize as JSON
16
+ * @returns ToolResult with isError: false
17
+ */
18
+ export declare function createToolResult(data: unknown): ToolResult;
19
+ /**
20
+ * Create a screenshot tool result with text metadata and image content
21
+ * @param metadata Metadata to include as JSON text
22
+ * @param imageBase64 Base64-encoded image data
23
+ * @param mimeType Image MIME type (default: 'image/webp')
24
+ * @returns ToolResult with text + image content
25
+ */
26
+ export declare function createScreenshotResult(metadata: Record<string, unknown>, imageBase64: string, mimeType?: string): ToolResult;