mcp-web-inspector 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 (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1017 -0
  3. package/dist/evals/evals.d.ts +5 -0
  4. package/dist/evals/evals.js +41 -0
  5. package/dist/index.d.ts +2 -0
  6. package/dist/index.js +62 -0
  7. package/dist/requestHandler.d.ts +3 -0
  8. package/dist/requestHandler.js +53 -0
  9. package/dist/toolHandler.d.ts +91 -0
  10. package/dist/toolHandler.js +725 -0
  11. package/dist/tools/api/base.d.ts +33 -0
  12. package/dist/tools/api/base.js +49 -0
  13. package/dist/tools/api/index.d.ts +2 -0
  14. package/dist/tools/api/index.js +3 -0
  15. package/dist/tools/api/requests.d.ts +47 -0
  16. package/dist/tools/api/requests.js +168 -0
  17. package/dist/tools/browser/base.d.ts +51 -0
  18. package/dist/tools/browser/base.js +111 -0
  19. package/dist/tools/browser/cleanSession.d.ts +10 -0
  20. package/dist/tools/browser/cleanSession.js +42 -0
  21. package/dist/tools/browser/comparePositions.d.ts +11 -0
  22. package/dist/tools/browser/comparePositions.js +149 -0
  23. package/dist/tools/browser/computedStyles.d.ts +11 -0
  24. package/dist/tools/browser/computedStyles.js +128 -0
  25. package/dist/tools/browser/console.d.ts +37 -0
  26. package/dist/tools/browser/console.js +106 -0
  27. package/dist/tools/browser/elementExists.d.ts +9 -0
  28. package/dist/tools/browser/elementExists.js +57 -0
  29. package/dist/tools/browser/elementInspection.d.ts +21 -0
  30. package/dist/tools/browser/elementInspection.js +151 -0
  31. package/dist/tools/browser/elementPosition.d.ts +11 -0
  32. package/dist/tools/browser/elementPosition.js +107 -0
  33. package/dist/tools/browser/elementVisibility.d.ts +12 -0
  34. package/dist/tools/browser/elementVisibility.js +224 -0
  35. package/dist/tools/browser/findByText.d.ts +13 -0
  36. package/dist/tools/browser/findByText.js +207 -0
  37. package/dist/tools/browser/getRequestDetails.d.ts +9 -0
  38. package/dist/tools/browser/getRequestDetails.js +137 -0
  39. package/dist/tools/browser/getTestIds.d.ts +12 -0
  40. package/dist/tools/browser/getTestIds.js +148 -0
  41. package/dist/tools/browser/index.d.ts +7 -0
  42. package/dist/tools/browser/index.js +7 -0
  43. package/dist/tools/browser/inspectDom.d.ts +12 -0
  44. package/dist/tools/browser/inspectDom.js +447 -0
  45. package/dist/tools/browser/interaction.d.ts +104 -0
  46. package/dist/tools/browser/interaction.js +259 -0
  47. package/dist/tools/browser/listNetworkRequests.d.ts +10 -0
  48. package/dist/tools/browser/listNetworkRequests.js +74 -0
  49. package/dist/tools/browser/measureElement.d.ts +9 -0
  50. package/dist/tools/browser/measureElement.js +139 -0
  51. package/dist/tools/browser/navigation.d.ts +38 -0
  52. package/dist/tools/browser/navigation.js +109 -0
  53. package/dist/tools/browser/output.d.ts +11 -0
  54. package/dist/tools/browser/output.js +29 -0
  55. package/dist/tools/browser/querySelectorAll.d.ts +12 -0
  56. package/dist/tools/browser/querySelectorAll.js +201 -0
  57. package/dist/tools/browser/response.d.ts +29 -0
  58. package/dist/tools/browser/response.js +67 -0
  59. package/dist/tools/browser/screenshot.d.ts +16 -0
  60. package/dist/tools/browser/screenshot.js +70 -0
  61. package/dist/tools/browser/useragent.d.ts +15 -0
  62. package/dist/tools/browser/useragent.js +32 -0
  63. package/dist/tools/browser/visiblePage.d.ts +20 -0
  64. package/dist/tools/browser/visiblePage.js +170 -0
  65. package/dist/tools/browser/waitForElement.d.ts +10 -0
  66. package/dist/tools/browser/waitForElement.js +38 -0
  67. package/dist/tools/browser/waitForNetworkIdle.d.ts +8 -0
  68. package/dist/tools/browser/waitForNetworkIdle.js +32 -0
  69. package/dist/tools/codegen/generator.d.ts +21 -0
  70. package/dist/tools/codegen/generator.js +158 -0
  71. package/dist/tools/codegen/index.d.ts +11 -0
  72. package/dist/tools/codegen/index.js +187 -0
  73. package/dist/tools/codegen/recorder.d.ts +14 -0
  74. package/dist/tools/codegen/recorder.js +62 -0
  75. package/dist/tools/codegen/types.d.ts +28 -0
  76. package/dist/tools/codegen/types.js +1 -0
  77. package/dist/tools/common/types.d.ts +17 -0
  78. package/dist/tools/common/types.js +20 -0
  79. package/dist/tools/index.d.ts +2 -0
  80. package/dist/tools/index.js +2 -0
  81. package/dist/tools.d.ts +557 -0
  82. package/dist/tools.js +554 -0
  83. package/dist/types.d.ts +16 -0
  84. package/dist/types.js +1 -0
  85. package/package.json +60 -0
@@ -0,0 +1,725 @@
1
+ import { chromium, firefox, webkit, devices } from 'playwright';
2
+ import { BROWSER_TOOLS } from './tools.js';
3
+ import { ScreenshotTool, NavigationTool, CloseBrowserTool, ConsoleLogsTool } from './tools/browser/index.js';
4
+ import { ClickTool, FillTool, SelectTool, HoverTool, EvaluateTool, UploadFileTool } from './tools/browser/interaction.js';
5
+ import { VisibleTextTool, VisibleHtmlTool } from './tools/browser/visiblePage.js';
6
+ import { ElementVisibilityTool } from './tools/browser/elementVisibility.js';
7
+ import { ElementPositionTool } from './tools/browser/elementPosition.js';
8
+ import { InspectDomTool } from './tools/browser/inspectDom.js';
9
+ import { GetTestIdsTool } from './tools/browser/getTestIds.js';
10
+ import { QuerySelectorAllTool } from './tools/browser/querySelectorAll.js';
11
+ import { FindByTextTool } from './tools/browser/findByText.js';
12
+ import { GetComputedStylesTool } from './tools/browser/computedStyles.js';
13
+ import { MeasureElementTool } from './tools/browser/measureElement.js';
14
+ import { ElementExistsTool } from './tools/browser/elementExists.js';
15
+ import { ComparePositionsTool } from './tools/browser/comparePositions.js';
16
+ import { GoBackTool, GoForwardTool } from './tools/browser/navigation.js';
17
+ import { DragTool, PressKeyTool } from './tools/browser/interaction.js';
18
+ import { WaitForElementTool } from './tools/browser/waitForElement.js';
19
+ import { WaitForNetworkIdleTool } from './tools/browser/waitForNetworkIdle.js';
20
+ import { ListNetworkRequestsTool } from './tools/browser/listNetworkRequests.js';
21
+ import { GetRequestDetailsTool } from './tools/browser/getRequestDetails.js';
22
+ // Global state
23
+ let browser;
24
+ let page;
25
+ let currentBrowserType = 'chromium';
26
+ let networkLog = [];
27
+ let sessionConfig = {
28
+ saveSession: false,
29
+ userDataDir: './.mcp-web-inspector/user-data',
30
+ screenshotsDir: './.mcp-web-inspector/screenshots',
31
+ };
32
+ /**
33
+ * Sets the session configuration
34
+ */
35
+ export function setSessionConfig(config) {
36
+ sessionConfig = { ...sessionConfig, ...config };
37
+ }
38
+ /**
39
+ * Gets the screenshots directory
40
+ */
41
+ export function getScreenshotsDir() {
42
+ return sessionConfig.screenshotsDir;
43
+ }
44
+ /**
45
+ * Resets browser and page variables
46
+ * Used when browser is closed
47
+ */
48
+ export function resetBrowserState() {
49
+ browser = undefined;
50
+ page = undefined;
51
+ currentBrowserType = 'chromium';
52
+ networkLog = [];
53
+ }
54
+ /**
55
+ * Gets the network log
56
+ */
57
+ export function getNetworkLog() {
58
+ return networkLog;
59
+ }
60
+ /**
61
+ * Clears the network log
62
+ */
63
+ export function clearNetworkLog() {
64
+ networkLog = [];
65
+ }
66
+ /**
67
+ * Sets the provided page to the global page variable
68
+ * @param newPage The Page object to set as the global page
69
+ */
70
+ export function setGlobalPage(newPage) {
71
+ page = newPage;
72
+ page.bringToFront(); // Bring the new tab to the front
73
+ console.log("Global page has been updated.");
74
+ }
75
+ // Tool instances
76
+ let screenshotTool;
77
+ let navigationTool;
78
+ let closeBrowserTool;
79
+ let consoleLogsTool;
80
+ let clickTool;
81
+ let fillTool;
82
+ let selectTool;
83
+ let hoverTool;
84
+ let uploadFileTool;
85
+ let evaluateTool;
86
+ let visibleTextTool;
87
+ let visibleHtmlTool;
88
+ let goBackTool;
89
+ let goForwardTool;
90
+ let dragTool;
91
+ let pressKeyTool;
92
+ let elementVisibilityTool;
93
+ let elementPositionTool;
94
+ let inspectDomTool;
95
+ let getTestIdsTool;
96
+ let querySelectorAllTool;
97
+ let findByTextTool;
98
+ let getComputedStylesTool;
99
+ let measureElementTool;
100
+ let elementExistsTool;
101
+ let comparePositionsTool;
102
+ let waitForElementTool;
103
+ let waitForNetworkIdleTool;
104
+ let listNetworkRequestsTool;
105
+ let getRequestDetailsTool;
106
+ /**
107
+ * Device preset mapping to Playwright device descriptors
108
+ */
109
+ const DEVICE_PRESETS = {
110
+ 'iphone-se': 'iPhone SE',
111
+ 'iphone-14': 'iPhone 14',
112
+ 'iphone-14-pro': 'iPhone 14 Pro',
113
+ 'pixel-5': 'Pixel 5',
114
+ 'ipad': 'iPad (gen 7)',
115
+ 'samsung-s21': 'Galaxy S21'
116
+ };
117
+ /**
118
+ * Register network event listeners
119
+ */
120
+ async function registerNetworkListeners(page) {
121
+ page.on('request', (request) => {
122
+ networkLog.push({
123
+ index: networkLog.length,
124
+ method: request.method(),
125
+ url: request.url(),
126
+ resourceType: request.resourceType(),
127
+ timestamp: Date.now(),
128
+ requestData: {
129
+ headers: request.headers(),
130
+ postData: request.postData() || null
131
+ }
132
+ });
133
+ });
134
+ page.on('response', async (response) => {
135
+ // Find the matching request by URL and method (most recent match)
136
+ const url = response.url();
137
+ const method = response.request().method();
138
+ for (let i = networkLog.length - 1; i >= 0; i--) {
139
+ if (networkLog[i].url === url &&
140
+ networkLog[i].method === method &&
141
+ !networkLog[i].status) {
142
+ networkLog[i].status = response.status();
143
+ networkLog[i].statusText = response.statusText();
144
+ networkLog[i].timing = Date.now() - networkLog[i].timestamp;
145
+ // Try to capture response body (may fail for some resource types)
146
+ let responseBody = null;
147
+ try {
148
+ responseBody = await response.text();
149
+ }
150
+ catch (e) {
151
+ // Ignore errors (e.g., image/binary responses)
152
+ responseBody = null;
153
+ }
154
+ networkLog[i].responseData = {
155
+ headers: response.headers(),
156
+ body: responseBody
157
+ };
158
+ break;
159
+ }
160
+ }
161
+ });
162
+ }
163
+ async function registerConsoleMessage(page) {
164
+ page.on("console", (msg) => {
165
+ if (consoleLogsTool) {
166
+ const type = msg.type();
167
+ const text = msg.text();
168
+ // "Unhandled Rejection In Promise" we injected
169
+ if (text.startsWith("[Playwright]")) {
170
+ const payload = text.replace("[Playwright]", "");
171
+ consoleLogsTool.registerConsoleMessage("exception", payload);
172
+ }
173
+ else {
174
+ consoleLogsTool.registerConsoleMessage(type, text);
175
+ }
176
+ }
177
+ });
178
+ // Uncaught exception
179
+ page.on("pageerror", (error) => {
180
+ if (consoleLogsTool) {
181
+ const message = error.message;
182
+ const stack = error.stack || "";
183
+ const truncatedStack = stack
184
+ ? '\n ' + stack.split('\n').slice(0, 3).join('\n ') + '\n ...[truncated]'
185
+ : '';
186
+ consoleLogsTool.registerConsoleMessage("exception", `${message}${truncatedStack}`);
187
+ }
188
+ });
189
+ // Unhandled rejection in promise
190
+ await page.addInitScript(() => {
191
+ window.addEventListener("unhandledrejection", (event) => {
192
+ const reason = event.reason;
193
+ const message = typeof reason === "object" && reason !== null
194
+ ? reason.message || JSON.stringify(reason)
195
+ : String(reason);
196
+ const stack = reason?.stack || "";
197
+ const truncatedStack = stack
198
+ ? '\n ' + stack.split('\n').slice(0, 3).join('\n ') + '\n ...[truncated]'
199
+ : '';
200
+ // Use console.error get "Unhandled Rejection In Promise"
201
+ console.error(`[Playwright][Unhandled Rejection In Promise] ${message}${truncatedStack}`);
202
+ });
203
+ });
204
+ }
205
+ /**
206
+ * Ensures a browser is launched and returns the page
207
+ */
208
+ export async function ensureBrowser(browserSettings) {
209
+ try {
210
+ // Check if browser exists but is disconnected
211
+ if (browser && !browser.isConnected()) {
212
+ console.error("Browser exists but is disconnected. Cleaning up...");
213
+ try {
214
+ await browser.close().catch(err => console.error("Error closing disconnected browser:", err));
215
+ }
216
+ catch (e) {
217
+ // Ignore errors when closing disconnected browser
218
+ }
219
+ // Reset browser and page references
220
+ resetBrowserState();
221
+ }
222
+ // Launch new browser if needed
223
+ if (!browser) {
224
+ const { viewport, userAgent, headless = false, browserType = 'chromium', device } = browserSettings ?? {};
225
+ // If browser type is changing, force a new browser instance
226
+ if (browser && currentBrowserType !== browserType) {
227
+ try {
228
+ await browser.close().catch(err => console.error("Error closing browser on type change:", err));
229
+ }
230
+ catch (e) {
231
+ // Ignore errors
232
+ }
233
+ resetBrowserState();
234
+ }
235
+ // Get device configuration if device preset is specified
236
+ let deviceConfig = null;
237
+ if (device && DEVICE_PRESETS[device]) {
238
+ const playwrightDeviceName = DEVICE_PRESETS[device];
239
+ deviceConfig = devices[playwrightDeviceName];
240
+ if (deviceConfig) {
241
+ console.error(`Using device preset: ${device} (${playwrightDeviceName})`);
242
+ }
243
+ else {
244
+ console.error(`Warning: Device preset ${playwrightDeviceName} not found in Playwright devices`);
245
+ }
246
+ }
247
+ console.error(`Launching new ${browserType} browser instance...`);
248
+ // Use the appropriate browser engine
249
+ let browserInstance;
250
+ switch (browserType) {
251
+ case 'firefox':
252
+ browserInstance = firefox;
253
+ break;
254
+ case 'webkit':
255
+ browserInstance = webkit;
256
+ break;
257
+ case 'chromium':
258
+ default:
259
+ browserInstance = chromium;
260
+ break;
261
+ }
262
+ const executablePath = process.env.CHROME_EXECUTABLE_PATH;
263
+ // Prepare context options
264
+ const contextOptions = {
265
+ headless,
266
+ executablePath: executablePath,
267
+ };
268
+ // If device config exists, use it; otherwise use manual viewport/userAgent
269
+ if (deviceConfig) {
270
+ Object.assign(contextOptions, deviceConfig);
271
+ }
272
+ else {
273
+ if (userAgent) {
274
+ contextOptions.userAgent = userAgent;
275
+ }
276
+ contextOptions.viewport = {
277
+ width: viewport?.width ?? 1280,
278
+ height: viewport?.height ?? 720,
279
+ };
280
+ contextOptions.deviceScaleFactor = 1;
281
+ }
282
+ // Use persistent context if session saving is enabled
283
+ if (sessionConfig.saveSession) {
284
+ console.error(`Launching ${browserType} with persistent context at ${sessionConfig.userDataDir}...`);
285
+ const context = await browserInstance.launchPersistentContext(sessionConfig.userDataDir, contextOptions);
286
+ // Get the browser instance from the context
287
+ browser = context.browser();
288
+ currentBrowserType = browserType;
289
+ // Add cleanup logic when browser is disconnected
290
+ browser.on('disconnected', () => {
291
+ console.error("Browser disconnected event triggered");
292
+ browser = undefined;
293
+ page = undefined;
294
+ });
295
+ // Get or create the first page
296
+ const pages = context.pages();
297
+ page = pages.length > 0 ? pages[0] : await context.newPage();
298
+ }
299
+ else {
300
+ browser = await browserInstance.launch({
301
+ headless,
302
+ executablePath: executablePath
303
+ });
304
+ currentBrowserType = browserType;
305
+ // Add cleanup logic when browser is disconnected
306
+ browser.on('disconnected', () => {
307
+ console.error("Browser disconnected event triggered");
308
+ browser = undefined;
309
+ page = undefined;
310
+ });
311
+ // Prepare new context options (without headless and executablePath which are for launch)
312
+ const newContextOptions = {};
313
+ if (deviceConfig) {
314
+ Object.assign(newContextOptions, deviceConfig);
315
+ }
316
+ else {
317
+ if (userAgent) {
318
+ newContextOptions.userAgent = userAgent;
319
+ }
320
+ newContextOptions.viewport = {
321
+ width: viewport?.width ?? 1280,
322
+ height: viewport?.height ?? 720,
323
+ };
324
+ newContextOptions.deviceScaleFactor = 1;
325
+ }
326
+ const context = await browser.newContext(newContextOptions);
327
+ page = await context.newPage();
328
+ }
329
+ // Register console message handler and network listeners
330
+ await registerConsoleMessage(page);
331
+ await registerNetworkListeners(page);
332
+ }
333
+ // Verify page is still valid
334
+ if (!page || page.isClosed()) {
335
+ console.error("Page is closed or invalid. Creating new page...");
336
+ // Create a new page if the current one is invalid
337
+ const context = browser.contexts()[0] || await browser.newContext();
338
+ page = await context.newPage();
339
+ // Re-register console message handler and network listeners
340
+ await registerConsoleMessage(page);
341
+ await registerNetworkListeners(page);
342
+ }
343
+ return page;
344
+ }
345
+ catch (error) {
346
+ console.error("Error ensuring browser:", error);
347
+ // If something went wrong, clean up completely and retry once
348
+ try {
349
+ if (browser) {
350
+ await browser.close().catch(() => { });
351
+ }
352
+ }
353
+ catch (e) {
354
+ // Ignore errors during cleanup
355
+ }
356
+ resetBrowserState();
357
+ // Try one more time from scratch
358
+ const { viewport, userAgent, headless = false, browserType = 'chromium', device } = browserSettings ?? {};
359
+ // Get device configuration if device preset is specified
360
+ let deviceConfig = null;
361
+ if (device && DEVICE_PRESETS[device]) {
362
+ const playwrightDeviceName = DEVICE_PRESETS[device];
363
+ deviceConfig = devices[playwrightDeviceName];
364
+ }
365
+ // Use the appropriate browser engine
366
+ let browserInstance;
367
+ switch (browserType) {
368
+ case 'firefox':
369
+ browserInstance = firefox;
370
+ break;
371
+ case 'webkit':
372
+ browserInstance = webkit;
373
+ break;
374
+ case 'chromium':
375
+ default:
376
+ browserInstance = chromium;
377
+ break;
378
+ }
379
+ const executablePath = process.env.CHROME_EXECUTABLE_PATH;
380
+ // Prepare context options
381
+ const retryContextOptions = {
382
+ headless,
383
+ executablePath: executablePath,
384
+ };
385
+ // If device config exists, use it; otherwise use manual viewport/userAgent
386
+ if (deviceConfig) {
387
+ Object.assign(retryContextOptions, deviceConfig);
388
+ }
389
+ else {
390
+ if (userAgent) {
391
+ retryContextOptions.userAgent = userAgent;
392
+ }
393
+ retryContextOptions.viewport = {
394
+ width: viewport?.width ?? 1280,
395
+ height: viewport?.height ?? 720,
396
+ };
397
+ retryContextOptions.deviceScaleFactor = 1;
398
+ }
399
+ // Use persistent context if session saving is enabled
400
+ if (sessionConfig.saveSession) {
401
+ console.error(`Launching ${browserType} with persistent context at ${sessionConfig.userDataDir} (retry)...`);
402
+ const context = await browserInstance.launchPersistentContext(sessionConfig.userDataDir, retryContextOptions);
403
+ browser = context.browser();
404
+ currentBrowserType = browserType;
405
+ browser.on('disconnected', () => {
406
+ console.error("Browser disconnected event triggered (retry)");
407
+ browser = undefined;
408
+ page = undefined;
409
+ });
410
+ const pages = context.pages();
411
+ page = pages.length > 0 ? pages[0] : await context.newPage();
412
+ }
413
+ else {
414
+ browser = await browserInstance.launch({
415
+ headless,
416
+ executablePath: executablePath
417
+ });
418
+ currentBrowserType = browserType;
419
+ browser.on('disconnected', () => {
420
+ console.error("Browser disconnected event triggered (retry)");
421
+ browser = undefined;
422
+ page = undefined;
423
+ });
424
+ // Prepare new context options (without headless and executablePath which are for launch)
425
+ const retryNewContextOptions = {};
426
+ if (deviceConfig) {
427
+ Object.assign(retryNewContextOptions, deviceConfig);
428
+ }
429
+ else {
430
+ if (userAgent) {
431
+ retryNewContextOptions.userAgent = userAgent;
432
+ }
433
+ retryNewContextOptions.viewport = {
434
+ width: viewport?.width ?? 1280,
435
+ height: viewport?.height ?? 720,
436
+ };
437
+ retryNewContextOptions.deviceScaleFactor = 1;
438
+ }
439
+ const context = await browser.newContext(retryNewContextOptions);
440
+ page = await context.newPage();
441
+ }
442
+ await registerConsoleMessage(page);
443
+ await registerNetworkListeners(page);
444
+ return page;
445
+ }
446
+ }
447
+ /**
448
+ * Initialize all tool instances
449
+ */
450
+ function initializeTools(server) {
451
+ // Browser tools
452
+ if (!screenshotTool)
453
+ screenshotTool = new ScreenshotTool(server);
454
+ if (!navigationTool)
455
+ navigationTool = new NavigationTool(server);
456
+ if (!closeBrowserTool)
457
+ closeBrowserTool = new CloseBrowserTool(server);
458
+ if (!consoleLogsTool)
459
+ consoleLogsTool = new ConsoleLogsTool(server);
460
+ if (!clickTool)
461
+ clickTool = new ClickTool(server);
462
+ if (!fillTool)
463
+ fillTool = new FillTool(server);
464
+ if (!selectTool)
465
+ selectTool = new SelectTool(server);
466
+ if (!hoverTool)
467
+ hoverTool = new HoverTool(server);
468
+ if (!uploadFileTool)
469
+ uploadFileTool = new UploadFileTool(server);
470
+ if (!evaluateTool)
471
+ evaluateTool = new EvaluateTool(server);
472
+ if (!visibleTextTool)
473
+ visibleTextTool = new VisibleTextTool(server);
474
+ if (!visibleHtmlTool)
475
+ visibleHtmlTool = new VisibleHtmlTool(server);
476
+ if (!goBackTool)
477
+ goBackTool = new GoBackTool(server);
478
+ if (!goForwardTool)
479
+ goForwardTool = new GoForwardTool(server);
480
+ if (!dragTool)
481
+ dragTool = new DragTool(server);
482
+ if (!pressKeyTool)
483
+ pressKeyTool = new PressKeyTool(server);
484
+ if (!elementVisibilityTool)
485
+ elementVisibilityTool = new ElementVisibilityTool(server);
486
+ if (!elementPositionTool)
487
+ elementPositionTool = new ElementPositionTool(server);
488
+ if (!inspectDomTool)
489
+ inspectDomTool = new InspectDomTool(server);
490
+ if (!getTestIdsTool)
491
+ getTestIdsTool = new GetTestIdsTool(server);
492
+ if (!querySelectorAllTool)
493
+ querySelectorAllTool = new QuerySelectorAllTool(server);
494
+ if (!findByTextTool)
495
+ findByTextTool = new FindByTextTool(server);
496
+ if (!getComputedStylesTool)
497
+ getComputedStylesTool = new GetComputedStylesTool(server);
498
+ if (!measureElementTool)
499
+ measureElementTool = new MeasureElementTool(server);
500
+ if (!elementExistsTool)
501
+ elementExistsTool = new ElementExistsTool(server);
502
+ if (!comparePositionsTool)
503
+ comparePositionsTool = new ComparePositionsTool(server);
504
+ if (!waitForElementTool)
505
+ waitForElementTool = new WaitForElementTool(server);
506
+ if (!waitForNetworkIdleTool)
507
+ waitForNetworkIdleTool = new WaitForNetworkIdleTool(server);
508
+ if (!listNetworkRequestsTool)
509
+ listNetworkRequestsTool = new ListNetworkRequestsTool(server);
510
+ if (!getRequestDetailsTool)
511
+ getRequestDetailsTool = new GetRequestDetailsTool(server);
512
+ }
513
+ /**
514
+ * Main handler for tool calls
515
+ */
516
+ export async function handleToolCall(name, args, server) {
517
+ // Initialize tools
518
+ initializeTools(server);
519
+ try {
520
+ // Special case for browser close to ensure it always works
521
+ if (name === "close") {
522
+ if (browser) {
523
+ try {
524
+ if (browser.isConnected()) {
525
+ await browser.close().catch(e => console.error("Error closing browser:", e));
526
+ }
527
+ }
528
+ catch (error) {
529
+ console.error("Error during browser close in handler:", error);
530
+ }
531
+ finally {
532
+ resetBrowserState();
533
+ }
534
+ return {
535
+ content: [{
536
+ type: "text",
537
+ text: "Browser closed successfully",
538
+ }],
539
+ isError: false,
540
+ };
541
+ }
542
+ return {
543
+ content: [{
544
+ type: "text",
545
+ text: "No browser instance to close",
546
+ }],
547
+ isError: false,
548
+ };
549
+ }
550
+ // Check if we have a disconnected browser that needs cleanup
551
+ if (browser && !browser.isConnected() && BROWSER_TOOLS.includes(name)) {
552
+ console.error("Detected disconnected browser before tool execution, cleaning up...");
553
+ try {
554
+ await browser.close().catch(() => { }); // Ignore errors
555
+ }
556
+ catch (e) {
557
+ // Ignore any errors during cleanup
558
+ }
559
+ resetBrowserState();
560
+ }
561
+ // Prepare context based on tool requirements
562
+ const context = {
563
+ server
564
+ };
565
+ // Set up browser if needed
566
+ if (BROWSER_TOOLS.includes(name)) {
567
+ const browserSettings = {
568
+ viewport: {
569
+ width: args.width,
570
+ height: args.height
571
+ },
572
+ userAgent: name === "set_user_agent" ? args.userAgent : undefined,
573
+ headless: args.headless,
574
+ browserType: args.browserType || 'chromium',
575
+ device: args.device
576
+ };
577
+ try {
578
+ context.page = await ensureBrowser(browserSettings);
579
+ context.browser = browser;
580
+ }
581
+ catch (error) {
582
+ console.error("Failed to ensure browser:", error);
583
+ return {
584
+ content: [{
585
+ type: "text",
586
+ text: `Failed to initialize browser: ${error.message}. Please try again.`,
587
+ }],
588
+ isError: true,
589
+ };
590
+ }
591
+ }
592
+ // Route to appropriate tool
593
+ switch (name) {
594
+ // Browser tools
595
+ case "navigate":
596
+ return await navigationTool.execute(args, context);
597
+ case "screenshot":
598
+ return await screenshotTool.execute(args, context);
599
+ case "close":
600
+ return await closeBrowserTool.execute(args, context);
601
+ case "get_console_logs":
602
+ return await consoleLogsTool.execute(args, context);
603
+ case "click":
604
+ return await clickTool.execute(args, context);
605
+ case "fill":
606
+ return await fillTool.execute(args, context);
607
+ case "select":
608
+ return await selectTool.execute(args, context);
609
+ case "hover":
610
+ return await hoverTool.execute(args, context);
611
+ case "upload_file":
612
+ return await uploadFileTool.execute(args, context);
613
+ case "evaluate":
614
+ return await evaluateTool.execute(args, context);
615
+ case "get_text":
616
+ return await visibleTextTool.execute(args, context);
617
+ case "get_html":
618
+ return await visibleHtmlTool.execute(args, context);
619
+ case "go_back":
620
+ return await goBackTool.execute(args, context);
621
+ case "go_forward":
622
+ return await goForwardTool.execute(args, context);
623
+ case "drag":
624
+ return await dragTool.execute(args, context);
625
+ case "press_key":
626
+ return await pressKeyTool.execute(args, context);
627
+ case "check_visibility":
628
+ return await elementVisibilityTool.execute(args, context);
629
+ case "get_position":
630
+ return await elementPositionTool.execute(args, context);
631
+ case "inspect_dom":
632
+ return await inspectDomTool.execute(args, context);
633
+ case "get_test_ids":
634
+ return await getTestIdsTool.execute(args, context);
635
+ case "query_selector":
636
+ return await querySelectorAllTool.execute(args, context);
637
+ case "find_by_text":
638
+ return await findByTextTool.execute(args, context);
639
+ case "get_computed_styles":
640
+ return await getComputedStylesTool.execute(args, context);
641
+ case "measure_element":
642
+ return await measureElementTool.execute(args, context);
643
+ case "element_exists":
644
+ return await elementExistsTool.execute(args, context);
645
+ case "compare_positions":
646
+ return await comparePositionsTool.execute(args, context);
647
+ case "wait_for_element":
648
+ return await waitForElementTool.execute(args, context);
649
+ case "wait_for_network_idle":
650
+ return await waitForNetworkIdleTool.execute(args, context);
651
+ case "list_network_requests":
652
+ return await listNetworkRequestsTool.execute(args, context);
653
+ case "get_request_details":
654
+ return await getRequestDetailsTool.execute(args, context);
655
+ default:
656
+ return {
657
+ content: [{
658
+ type: "text",
659
+ text: `Unknown tool: ${name}`,
660
+ }],
661
+ isError: true,
662
+ };
663
+ }
664
+ }
665
+ catch (error) {
666
+ console.error(`Error handling tool ${name}:`, error);
667
+ // Handle browser-specific errors at the top level
668
+ if (BROWSER_TOOLS.includes(name)) {
669
+ const errorMessage = error.message;
670
+ if (errorMessage.includes("Target page, context or browser has been closed") ||
671
+ errorMessage.includes("Browser has been disconnected") ||
672
+ errorMessage.includes("Target closed") ||
673
+ errorMessage.includes("Protocol error") ||
674
+ errorMessage.includes("Connection closed")) {
675
+ // Reset browser state if it's a connection issue
676
+ resetBrowserState();
677
+ return {
678
+ content: [{
679
+ type: "text",
680
+ text: `Browser connection error: ${errorMessage}. Browser state has been reset, please try again.`,
681
+ }],
682
+ isError: true,
683
+ };
684
+ }
685
+ }
686
+ return {
687
+ content: [{
688
+ type: "text",
689
+ text: error instanceof Error ? error.message : String(error),
690
+ }],
691
+ isError: true,
692
+ };
693
+ }
694
+ }
695
+ /**
696
+ * Get console logs
697
+ */
698
+ export function getConsoleLogs() {
699
+ return consoleLogsTool?.getConsoleLogs() ?? [];
700
+ }
701
+ /**
702
+ * Get screenshots
703
+ */
704
+ export function getScreenshots() {
705
+ return screenshotTool?.getScreenshots() ?? new Map();
706
+ }
707
+ /**
708
+ * Update last interaction timestamp
709
+ */
710
+ export function updateLastInteractionTimestamp() {
711
+ consoleLogsTool?.updateLastInteractionTimestamp();
712
+ }
713
+ /**
714
+ * Update last navigation timestamp
715
+ */
716
+ export function updateLastNavigationTimestamp() {
717
+ consoleLogsTool?.updateLastNavigationTimestamp();
718
+ }
719
+ /**
720
+ * Clear console logs
721
+ */
722
+ export function clearConsoleLogs() {
723
+ consoleLogsTool?.clearConsoleLogs();
724
+ }
725
+ export { registerConsoleMessage };