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,43 @@
1
+ /**
2
+ * Screenshot-specific type definitions
3
+ */
4
+ import type { Page, Browser, BrowserContext, ElectronApplication } from "playwright";
5
+ /**
6
+ * Options for taking a screenshot
7
+ */
8
+ export interface ScreenshotOptions {
9
+ fullPage?: boolean;
10
+ maxWidth?: number;
11
+ quality?: number;
12
+ }
13
+ /**
14
+ * Result of a captured and optimized screenshot
15
+ */
16
+ export interface ScreenshotResult {
17
+ data: string;
18
+ mimeType: string;
19
+ width: number;
20
+ height: number;
21
+ originalSize: number;
22
+ optimizedSize: number;
23
+ }
24
+ /**
25
+ * Stored reference to a Playwright Page for screenshot access
26
+ */
27
+ export interface PageReference {
28
+ type: "web" | "electron";
29
+ page: Page;
30
+ browser?: Browser;
31
+ browserContext?: BrowserContext;
32
+ electronApp?: ElectronApplication;
33
+ url?: string;
34
+ }
35
+ /**
36
+ * Data stored from an auto-capture event
37
+ */
38
+ export interface AutoCaptureData {
39
+ imageBase64: string;
40
+ mimeType: string;
41
+ url: string;
42
+ capturedAt: Date;
43
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Screenshot-specific type definitions
3
+ */
4
+ export {};
@@ -0,0 +1,10 @@
1
+ /**
2
+ * MCP server factory
3
+ */
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { SessionManager } from "./session-manager.js";
6
+ /**
7
+ * Create and configure the Feedback MCP server
8
+ * Does not connect transport - that's the caller's responsibility
9
+ */
10
+ export declare function createServer(sessionManager: SessionManager): McpServer;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * MCP server factory
3
+ */
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { registerTools } from "./tools/index.js";
6
+ /**
7
+ * Create and configure the Feedback MCP server
8
+ * Does not connect transport - that's the caller's responsibility
9
+ */
10
+ export function createServer(sessionManager) {
11
+ const server = new McpServer({
12
+ name: "feedback",
13
+ version: "0.1.0",
14
+ });
15
+ // Register MCP tools
16
+ registerTools(server, sessionManager);
17
+ return server;
18
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Session lifecycle management
3
+ * Tracks multiple concurrent sessions with unique UUIDs
4
+ */
5
+ import { Session, Resource } from "./types/index.js";
6
+ import { PageReference, AutoCaptureData } from "./screenshot/types.js";
7
+ import { Collector, ConsoleEntry, ErrorEntry, NetworkEntry, ProcessOutputEntry } from "./capture/types.js";
8
+ export declare class SessionManager {
9
+ private sessions;
10
+ private pageRefs;
11
+ private autoCaptures;
12
+ private consoleCollectors;
13
+ private errorCollectors;
14
+ private networkCollectors;
15
+ private processCollectors;
16
+ /**
17
+ * Create a new session with a unique UUID
18
+ * @returns Session ID
19
+ */
20
+ create(): string;
21
+ /**
22
+ * Get a session by ID
23
+ * @returns Session or undefined if not found
24
+ */
25
+ get(id: string): Session | undefined;
26
+ /**
27
+ * List all active session IDs
28
+ * @returns Array of session IDs
29
+ */
30
+ list(): string[];
31
+ /**
32
+ * Add a resource to a session
33
+ * @throws Error if session not found
34
+ */
35
+ addResource(sessionId: string, resource: Resource): void;
36
+ /**
37
+ * Store a typed Playwright Page reference for a session
38
+ * Key format: ${sessionId}:${identifier} where identifier is URL or 'electron'
39
+ */
40
+ setPageRef(sessionId: string, identifier: string, ref: PageReference): void;
41
+ /**
42
+ * Get a specific page reference by session and identifier
43
+ */
44
+ getPageRef(sessionId: string, identifier: string): PageReference | undefined;
45
+ /**
46
+ * Get all page references for a session
47
+ */
48
+ getPageRefs(sessionId: string): PageReference[];
49
+ /**
50
+ * Remove a specific page reference
51
+ */
52
+ removePageRef(sessionId: string, identifier: string): void;
53
+ /**
54
+ * Store the latest auto-captured screenshot for a session
55
+ */
56
+ setAutoCapture(sessionId: string, data: AutoCaptureData): void;
57
+ /**
58
+ * Get the latest auto-captured screenshot for a session
59
+ */
60
+ getAutoCapture(sessionId: string): AutoCaptureData | undefined;
61
+ /**
62
+ * Store a console collector for a session
63
+ */
64
+ setConsoleCollector(sessionId: string, identifier: string, collector: Collector<ConsoleEntry>): void;
65
+ /**
66
+ * Get a specific console collector by session and identifier
67
+ */
68
+ getConsoleCollector(sessionId: string, identifier: string): Collector<ConsoleEntry> | undefined;
69
+ /**
70
+ * Get all console collectors for a session
71
+ */
72
+ getConsoleCollectors(sessionId: string): Collector<ConsoleEntry>[];
73
+ /**
74
+ * Store an error collector for a session
75
+ */
76
+ setErrorCollector(sessionId: string, identifier: string, collector: Collector<ErrorEntry>): void;
77
+ /**
78
+ * Get a specific error collector by session and identifier
79
+ */
80
+ getErrorCollector(sessionId: string, identifier: string): Collector<ErrorEntry> | undefined;
81
+ /**
82
+ * Get all error collectors for a session
83
+ */
84
+ getErrorCollectors(sessionId: string): Collector<ErrorEntry>[];
85
+ /**
86
+ * Store a network collector for a session
87
+ */
88
+ setNetworkCollector(sessionId: string, identifier: string, collector: Collector<NetworkEntry>): void;
89
+ /**
90
+ * Get a specific network collector by session and identifier
91
+ */
92
+ getNetworkCollector(sessionId: string, identifier: string): Collector<NetworkEntry> | undefined;
93
+ /**
94
+ * Get all network collectors for a session
95
+ */
96
+ getNetworkCollectors(sessionId: string): Collector<NetworkEntry>[];
97
+ /**
98
+ * Store a process output collector for a session
99
+ */
100
+ setProcessCollector(sessionId: string, identifier: string, collector: Collector<ProcessOutputEntry>): void;
101
+ /**
102
+ * Get a specific process collector by session and identifier
103
+ */
104
+ getProcessCollector(sessionId: string, identifier: string): Collector<ProcessOutputEntry> | undefined;
105
+ /**
106
+ * Get all process collectors for a session
107
+ */
108
+ getProcessCollectors(sessionId: string): Collector<ProcessOutputEntry>[];
109
+ /**
110
+ * Destroy a session and clean up all its resources
111
+ * Logs cleanup errors but doesn't throw
112
+ * No-op if session doesn't exist
113
+ */
114
+ destroy(sessionId: string): Promise<void>;
115
+ /**
116
+ * Destroy all sessions (for shutdown cleanup)
117
+ */
118
+ destroyAll(): Promise<void>;
119
+ }
@@ -0,0 +1,284 @@
1
+ /**
2
+ * Session lifecycle management
3
+ * Tracks multiple concurrent sessions with unique UUIDs
4
+ */
5
+ import { randomUUID } from "crypto";
6
+ export class SessionManager {
7
+ sessions = new Map();
8
+ pageRefs = new Map();
9
+ autoCaptures = new Map();
10
+ consoleCollectors = new Map();
11
+ errorCollectors = new Map();
12
+ networkCollectors = new Map();
13
+ processCollectors = new Map();
14
+ /**
15
+ * Create a new session with a unique UUID
16
+ * @returns Session ID
17
+ */
18
+ create() {
19
+ const id = randomUUID();
20
+ const session = {
21
+ id,
22
+ createdAt: new Date(),
23
+ resources: [],
24
+ };
25
+ this.sessions.set(id, session);
26
+ console.error(`Session created: ${id}`);
27
+ return id;
28
+ }
29
+ /**
30
+ * Get a session by ID
31
+ * @returns Session or undefined if not found
32
+ */
33
+ get(id) {
34
+ return this.sessions.get(id);
35
+ }
36
+ /**
37
+ * List all active session IDs
38
+ * @returns Array of session IDs
39
+ */
40
+ list() {
41
+ return Array.from(this.sessions.keys());
42
+ }
43
+ /**
44
+ * Add a resource to a session
45
+ * @throws Error if session not found
46
+ */
47
+ addResource(sessionId, resource) {
48
+ const session = this.sessions.get(sessionId);
49
+ if (!session) {
50
+ throw new Error(`Session not found: ${sessionId}`);
51
+ }
52
+ session.resources.push(resource);
53
+ }
54
+ /**
55
+ * Store a typed Playwright Page reference for a session
56
+ * Key format: ${sessionId}:${identifier} where identifier is URL or 'electron'
57
+ */
58
+ setPageRef(sessionId, identifier, ref) {
59
+ const session = this.sessions.get(sessionId);
60
+ if (!session) {
61
+ throw new Error(`Session not found: ${sessionId}`);
62
+ }
63
+ this.pageRefs.set(`${sessionId}:${identifier}`, ref);
64
+ }
65
+ /**
66
+ * Get a specific page reference by session and identifier
67
+ */
68
+ getPageRef(sessionId, identifier) {
69
+ return this.pageRefs.get(`${sessionId}:${identifier}`);
70
+ }
71
+ /**
72
+ * Get all page references for a session
73
+ */
74
+ getPageRefs(sessionId) {
75
+ const refs = [];
76
+ const prefix = `${sessionId}:`;
77
+ for (const [key, ref] of this.pageRefs) {
78
+ if (key.startsWith(prefix)) {
79
+ refs.push(ref);
80
+ }
81
+ }
82
+ return refs;
83
+ }
84
+ /**
85
+ * Remove a specific page reference
86
+ */
87
+ removePageRef(sessionId, identifier) {
88
+ this.pageRefs.delete(`${sessionId}:${identifier}`);
89
+ }
90
+ /**
91
+ * Store the latest auto-captured screenshot for a session
92
+ */
93
+ setAutoCapture(sessionId, data) {
94
+ this.autoCaptures.set(sessionId, data);
95
+ }
96
+ /**
97
+ * Get the latest auto-captured screenshot for a session
98
+ */
99
+ getAutoCapture(sessionId) {
100
+ return this.autoCaptures.get(sessionId);
101
+ }
102
+ // --- Console Collectors ---
103
+ /**
104
+ * Store a console collector for a session
105
+ */
106
+ setConsoleCollector(sessionId, identifier, collector) {
107
+ this.consoleCollectors.set(`${sessionId}:${identifier}`, collector);
108
+ }
109
+ /**
110
+ * Get a specific console collector by session and identifier
111
+ */
112
+ getConsoleCollector(sessionId, identifier) {
113
+ return this.consoleCollectors.get(`${sessionId}:${identifier}`);
114
+ }
115
+ /**
116
+ * Get all console collectors for a session
117
+ */
118
+ getConsoleCollectors(sessionId) {
119
+ const collectors = [];
120
+ const prefix = `${sessionId}:`;
121
+ for (const [key, collector] of this.consoleCollectors) {
122
+ if (key.startsWith(prefix)) {
123
+ collectors.push(collector);
124
+ }
125
+ }
126
+ return collectors;
127
+ }
128
+ // --- Error Collectors ---
129
+ /**
130
+ * Store an error collector for a session
131
+ */
132
+ setErrorCollector(sessionId, identifier, collector) {
133
+ this.errorCollectors.set(`${sessionId}:${identifier}`, collector);
134
+ }
135
+ /**
136
+ * Get a specific error collector by session and identifier
137
+ */
138
+ getErrorCollector(sessionId, identifier) {
139
+ return this.errorCollectors.get(`${sessionId}:${identifier}`);
140
+ }
141
+ /**
142
+ * Get all error collectors for a session
143
+ */
144
+ getErrorCollectors(sessionId) {
145
+ const collectors = [];
146
+ const prefix = `${sessionId}:`;
147
+ for (const [key, collector] of this.errorCollectors) {
148
+ if (key.startsWith(prefix)) {
149
+ collectors.push(collector);
150
+ }
151
+ }
152
+ return collectors;
153
+ }
154
+ // --- Network Collectors ---
155
+ /**
156
+ * Store a network collector for a session
157
+ */
158
+ setNetworkCollector(sessionId, identifier, collector) {
159
+ this.networkCollectors.set(`${sessionId}:${identifier}`, collector);
160
+ }
161
+ /**
162
+ * Get a specific network collector by session and identifier
163
+ */
164
+ getNetworkCollector(sessionId, identifier) {
165
+ return this.networkCollectors.get(`${sessionId}:${identifier}`);
166
+ }
167
+ /**
168
+ * Get all network collectors for a session
169
+ */
170
+ getNetworkCollectors(sessionId) {
171
+ const collectors = [];
172
+ const prefix = `${sessionId}:`;
173
+ for (const [key, collector] of this.networkCollectors) {
174
+ if (key.startsWith(prefix)) {
175
+ collectors.push(collector);
176
+ }
177
+ }
178
+ return collectors;
179
+ }
180
+ // --- Process Collectors ---
181
+ /**
182
+ * Store a process output collector for a session
183
+ */
184
+ setProcessCollector(sessionId, identifier, collector) {
185
+ this.processCollectors.set(`${sessionId}:${identifier}`, collector);
186
+ }
187
+ /**
188
+ * Get a specific process collector by session and identifier
189
+ */
190
+ getProcessCollector(sessionId, identifier) {
191
+ return this.processCollectors.get(`${sessionId}:${identifier}`);
192
+ }
193
+ /**
194
+ * Get all process collectors for a session
195
+ */
196
+ getProcessCollectors(sessionId) {
197
+ const collectors = [];
198
+ const prefix = `${sessionId}:`;
199
+ for (const [key, collector] of this.processCollectors) {
200
+ if (key.startsWith(prefix)) {
201
+ collectors.push(collector);
202
+ }
203
+ }
204
+ return collectors;
205
+ }
206
+ /**
207
+ * Destroy a session and clean up all its resources
208
+ * Logs cleanup errors but doesn't throw
209
+ * No-op if session doesn't exist
210
+ */
211
+ async destroy(sessionId) {
212
+ const session = this.sessions.get(sessionId);
213
+ if (!session) {
214
+ return; // No-op if session doesn't exist
215
+ }
216
+ console.error(`Destroying session ${sessionId} (${session.resources.length} resources)`);
217
+ // Clean up page references for this session
218
+ const prefix = `${sessionId}:`;
219
+ for (const [key, ref] of this.pageRefs) {
220
+ if (key.startsWith(prefix)) {
221
+ try {
222
+ if (ref.browserContext) {
223
+ await ref.browserContext.close();
224
+ }
225
+ if (ref.browser) {
226
+ await ref.browser.close();
227
+ }
228
+ }
229
+ catch (error) {
230
+ console.error(`Error cleaning up page ref in session ${sessionId}:`, error);
231
+ }
232
+ this.pageRefs.delete(key);
233
+ }
234
+ }
235
+ // Clean up diagnostic collectors for this session
236
+ for (const [key, collector] of this.consoleCollectors) {
237
+ if (key.startsWith(prefix)) {
238
+ collector.detach();
239
+ this.consoleCollectors.delete(key);
240
+ }
241
+ }
242
+ for (const [key, collector] of this.errorCollectors) {
243
+ if (key.startsWith(prefix)) {
244
+ collector.detach();
245
+ this.errorCollectors.delete(key);
246
+ }
247
+ }
248
+ for (const [key, collector] of this.networkCollectors) {
249
+ if (key.startsWith(prefix)) {
250
+ collector.detach();
251
+ this.networkCollectors.delete(key);
252
+ }
253
+ }
254
+ for (const [key, collector] of this.processCollectors) {
255
+ if (key.startsWith(prefix)) {
256
+ collector.detach();
257
+ this.processCollectors.delete(key);
258
+ }
259
+ }
260
+ // Clean up auto-capture data
261
+ this.autoCaptures.delete(sessionId);
262
+ // Clean up each resource individually
263
+ for (const resource of session.resources) {
264
+ try {
265
+ await resource.cleanup();
266
+ }
267
+ catch (error) {
268
+ console.error(`Error cleaning up resource in session ${sessionId}:`, error);
269
+ }
270
+ }
271
+ this.sessions.delete(sessionId);
272
+ console.error(`Session destroyed: ${sessionId}`);
273
+ }
274
+ /**
275
+ * Destroy all sessions (for shutdown cleanup)
276
+ */
277
+ async destroyAll() {
278
+ const sessionIds = this.list();
279
+ console.error(`Destroying all sessions (${sessionIds.length} total)`);
280
+ for (const id of sessionIds) {
281
+ await this.destroy(id);
282
+ }
283
+ }
284
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * check_port MCP tool (PROC-04)
3
+ * Reports whether a TCP port is available and suggests alternatives
4
+ */
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { SessionManager } from "../session-manager.js";
7
+ /**
8
+ * Register the check_port tool with the MCP server
9
+ */
10
+ export declare function registerCheckPortTool(server: McpServer, _sessionManager: SessionManager): void;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * check_port MCP tool (PROC-04)
3
+ * Reports whether a TCP port is available and suggests alternatives
4
+ */
5
+ import { z } from "zod";
6
+ import { createToolError, createToolResult } from "../utils/errors.js";
7
+ import detect from "detect-port";
8
+ /**
9
+ * Register the check_port tool with the MCP server
10
+ */
11
+ export function registerCheckPortTool(server, _sessionManager) {
12
+ server.tool("check_port", "Check if a TCP port is available for use. Use before launching a web server to avoid EADDRINUSE errors.", {
13
+ port: z
14
+ .number()
15
+ .int()
16
+ .min(1)
17
+ .max(65535)
18
+ .describe("Port number to check availability for"),
19
+ }, async ({ port }) => {
20
+ try {
21
+ const availablePort = await detect(port);
22
+ if (availablePort === port) {
23
+ return createToolResult({
24
+ port,
25
+ available: true,
26
+ });
27
+ }
28
+ return createToolResult({
29
+ port,
30
+ available: false,
31
+ suggestedAlternative: availablePort,
32
+ message: `Port ${port} is in use. Port ${availablePort} is available.`,
33
+ });
34
+ }
35
+ catch (error) {
36
+ const message = error instanceof Error ? error.message : String(error);
37
+ return createToolError(`Failed to check port ${port}`, message, "Ensure the port number is valid (1-65535)");
38
+ }
39
+ });
40
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * click_element MCP tool
3
+ * Clicks elements 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 click_element 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 registerClickElementTool(server: McpServer, sessionManager: SessionManager): void;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * click_element MCP tool
3
+ * Clicks elements 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 click_element tool with the MCP server
12
+ *
13
+ * @param server - MCP server instance
14
+ * @param sessionManager - Session manager for resource tracking
15
+ */
16
+ export function registerClickElementTool(server, sessionManager) {
17
+ server.tool("click_element", "Click an element on a web or Electron page. Returns a screenshot after clicking. Use CSS selectors, text content, role, or test IDs to target elements.", {
18
+ sessionId: z
19
+ .string()
20
+ .describe("Session ID from create_session"),
21
+ selector: z
22
+ .string()
23
+ .describe("Element selector. CSS: #id, .class, div > span. Text: text=Click me. Role: role=button[name='Submit']. Test ID: testid=my-btn"),
24
+ pageIdentifier: z
25
+ .string()
26
+ .optional()
27
+ .describe("URL or 'electron' to target a specific page. Omit if session has only one page."),
28
+ button: z
29
+ .enum(["left", "right", "middle"])
30
+ .optional()
31
+ .describe("Mouse button (default: left)"),
32
+ clickCount: z
33
+ .number()
34
+ .int()
35
+ .min(1)
36
+ .max(3)
37
+ .optional()
38
+ .describe("Number of clicks (2 for double-click)"),
39
+ position: z
40
+ .object({ x: z.number(), y: z.number() })
41
+ .optional()
42
+ .describe("Click position within element"),
43
+ force: z
44
+ .boolean()
45
+ .optional()
46
+ .describe("Bypass actionability checks (use sparingly)"),
47
+ timeout: z
48
+ .number()
49
+ .int()
50
+ .min(0)
51
+ .optional()
52
+ .describe("Max wait time in ms (default: 30000)"),
53
+ }, async ({ sessionId, selector, pageIdentifier, button, clickCount, position, force, timeout, }) => {
54
+ try {
55
+ // Validate session exists
56
+ const session = sessionManager.get(sessionId);
57
+ if (!session) {
58
+ const availableSessions = sessionManager.list();
59
+ return createToolError(`Session not found: ${sessionId}`, "The session may have already been ended", availableSessions.length > 0
60
+ ? `Available sessions: ${availableSessions.join(", ")}`
61
+ : "Create a session first with create_session.");
62
+ }
63
+ // Find the active page
64
+ const pageResult = getActivePage(sessionManager, sessionId, pageIdentifier);
65
+ if (!pageResult.success) {
66
+ return createToolError(pageResult.error, `Session: ${sessionId}`, pageResult.availablePages
67
+ ? `Available pages: ${pageResult.availablePages.join(", ")}`
68
+ : undefined);
69
+ }
70
+ const { page } = pageResult;
71
+ // Resolve selector to Playwright Locator
72
+ const locator = resolveSelector(page, selector);
73
+ // Perform the click with provided options
74
+ await locator.click({
75
+ button: button ?? undefined,
76
+ clickCount: clickCount ?? undefined,
77
+ position: position ?? undefined,
78
+ force: force ?? undefined,
79
+ timeout: timeout ?? 30000,
80
+ });
81
+ // Wait briefly for any navigation triggered by the click
82
+ await Promise.race([
83
+ page.waitForLoadState("load").catch(() => { }),
84
+ new Promise((resolve) => setTimeout(resolve, 2000)),
85
+ ]);
86
+ // Capture post-click screenshot
87
+ const rawBuffer = await capturePlaywrightPage(page, {
88
+ fullPage: false,
89
+ });
90
+ const optimized = await optimizeScreenshot(rawBuffer, {
91
+ maxWidth: 1280,
92
+ quality: 80,
93
+ });
94
+ const imageBase64 = optimized.data.toString("base64");
95
+ return createScreenshotResult({
96
+ sessionId,
97
+ action: "click",
98
+ selector,
99
+ button: button ?? "left",
100
+ success: true,
101
+ }, imageBase64, optimized.mimeType);
102
+ }
103
+ catch (error) {
104
+ const message = error instanceof Error ? error.message : String(error);
105
+ // Strict mode violation: selector matched multiple elements
106
+ if (message.includes("strict mode violation")) {
107
+ 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.");
108
+ }
109
+ // Timeout: element not found or not actionable within timeout
110
+ if (message.includes("Timeout") ||
111
+ message.includes("timeout")) {
112
+ return createToolError("Element not found within timeout", `Selector "${selector}" did not match any visible element within ${timeout ?? 30000}ms`, "Check the selector is correct, the element is visible, or increase the timeout. Take a screenshot first to verify the page state.");
113
+ }
114
+ // Default error
115
+ return createToolError("Failed to click element", `Selector: "${selector}" — ${message}`, "Take a screenshot to verify the element exists and is visible on the page.");
116
+ }
117
+ });
118
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * MCP Tool 19: get_console_logs
3
+ * Retrieves browser console logs captured during a session.
4
+ */
5
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import { SessionManager } from "../session-manager.js";
7
+ export declare function registerGetConsoleLogsTool(server: McpServer, sessionManager: SessionManager): void;