@wdio/mcp 1.4.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.
package/lib/server.js ADDED
@@ -0,0 +1,2130 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/server.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+
7
+ // src/tools/browser.tool.ts
8
+ import { remote } from "webdriverio";
9
+ import { z } from "zod";
10
+ var startBrowserToolArguments = {
11
+ headless: z.boolean().optional(),
12
+ windowWidth: z.number().min(400).max(3840).optional(),
13
+ windowHeight: z.number().min(400).max(2160).optional()
14
+ };
15
+ var closeSessionToolArguments = {
16
+ detach: z.boolean().optional().describe("If true, disconnect from session without terminating it (preserves app state). Default: false")
17
+ };
18
+ var state = {
19
+ browsers: /* @__PURE__ */ new Map(),
20
+ currentSession: null,
21
+ sessionMetadata: /* @__PURE__ */ new Map()
22
+ };
23
+ var getBrowser = () => {
24
+ const browser = state.browsers.get(state.currentSession);
25
+ if (!browser) {
26
+ throw new Error("No active browser session");
27
+ }
28
+ return browser;
29
+ };
30
+ getBrowser.__state = state;
31
+ var startBrowserTool = async ({ headless = false, windowWidth = 1280, windowHeight = 1080 }) => {
32
+ const chromeArgs = [
33
+ `--window-size=${windowWidth},${windowHeight}`,
34
+ "--no-sandbox",
35
+ "--disable-search-engine-choice-screen",
36
+ "--disable-infobars",
37
+ "--log-level=3",
38
+ "--use-fake-device-for-media-stream",
39
+ "--use-fake-ui-for-media-stream",
40
+ "--disable-web-security",
41
+ "--allow-running-insecure-content"
42
+ ];
43
+ if (headless) {
44
+ chromeArgs.push("--headless=new");
45
+ chromeArgs.push("--disable-gpu");
46
+ chromeArgs.push("--disable-dev-shm-usage");
47
+ }
48
+ const browser = await remote({
49
+ capabilities: {
50
+ browserName: "chrome",
51
+ "goog:chromeOptions": {
52
+ args: chromeArgs
53
+ },
54
+ acceptInsecureCerts: true
55
+ }
56
+ });
57
+ const { sessionId } = browser;
58
+ state.browsers.set(sessionId, browser);
59
+ state.currentSession = sessionId;
60
+ state.sessionMetadata.set(sessionId, {
61
+ type: "browser",
62
+ capabilities: browser.capabilities,
63
+ isAttached: false
64
+ });
65
+ const modeText = headless ? "headless" : "headed";
66
+ return {
67
+ content: [{
68
+ type: "text",
69
+ text: `Browser started in ${modeText} mode with sessionId: ${sessionId} (${windowWidth}x${windowHeight})`
70
+ }]
71
+ };
72
+ };
73
+ var closeSessionTool = async (args = {}) => {
74
+ try {
75
+ const browser = getBrowser();
76
+ const sessionId = state.currentSession;
77
+ const metadata = state.sessionMetadata.get(sessionId);
78
+ if (!args.detach) {
79
+ await browser.deleteSession();
80
+ }
81
+ state.browsers.delete(sessionId);
82
+ state.sessionMetadata.delete(sessionId);
83
+ state.currentSession = null;
84
+ const action = args.detach ? "detached from" : "closed";
85
+ const note = args.detach && !metadata?.isAttached ? "\nNote: Session will remain active on Appium server." : "";
86
+ return {
87
+ content: [{ type: "text", text: `Session ${sessionId} ${action}${note}` }]
88
+ };
89
+ } catch (e) {
90
+ return {
91
+ content: [{ type: "text", text: `Error closing session: ${e}` }]
92
+ };
93
+ }
94
+ };
95
+
96
+ // src/tools/navigate.tool.ts
97
+ import { z as z2 } from "zod";
98
+ var navigateToolArguments = {
99
+ url: z2.string().nonempty("URL must be provided")
100
+ };
101
+ var navigateTool = async ({ url }) => {
102
+ try {
103
+ const browser = getBrowser();
104
+ await browser.url(url);
105
+ return {
106
+ content: [{ type: "text", text: `Navigated to ${url}` }]
107
+ };
108
+ } catch (e) {
109
+ return {
110
+ content: [{ type: "text", text: `Error navigating: ${e}` }]
111
+ };
112
+ }
113
+ };
114
+
115
+ // src/tools/click.tool.ts
116
+ import { z as z3 } from "zod";
117
+ var defaultTimeout = 3e3;
118
+ var clickToolArguments = {
119
+ selector: z3.string().describe(`Value for the selector, in the form of css selector or xpath ("button.my-class" or "//button[@class='my-class']" or "button=Exact text with spaces" or "a*=Link containing text")`),
120
+ scrollToView: z3.boolean().optional().describe("Whether to scroll the element into view before clicking").default(true),
121
+ timeout: z3.number().optional().describe("Maximum time to wait for element in milliseconds")
122
+ };
123
+ var clickAction = async (selector, timeout, scrollToView = true) => {
124
+ try {
125
+ const browser = getBrowser();
126
+ await browser.waitUntil(browser.$(selector).isExisting, { timeout });
127
+ if (scrollToView) {
128
+ await browser.$(selector).scrollIntoView({ block: "center", inline: "center" });
129
+ }
130
+ await browser.$(selector).click();
131
+ return {
132
+ content: [{ type: "text", text: `Element clicked (selector: ${selector})` }]
133
+ };
134
+ } catch (e) {
135
+ return {
136
+ content: [{ type: "text", text: `Error clicking element: ${e}` }]
137
+ };
138
+ }
139
+ };
140
+ var clickTool = async ({ selector, scrollToView, timeout = defaultTimeout }) => clickAction(selector, timeout, scrollToView);
141
+ var clickToolViaText = async ({ text, scrollToView, timeout = defaultTimeout }) => clickAction(`//a[contains(text(), '${text}')]`, timeout, scrollToView);
142
+
143
+ // src/tools/set-value.tool.ts
144
+ import { z as z4 } from "zod";
145
+ var defaultTimeout2 = 3e3;
146
+ var setValueToolArguments = {
147
+ selector: z4.string().describe(`Value for the selector, in the form of css selector or xpath ("button.my-class" or "//button[@class='my-class']")`),
148
+ value: z4.string().describe("Text to enter into the element"),
149
+ scrollToView: z4.boolean().optional().describe("Whether to scroll the element into view before typing").default(true),
150
+ timeout: z4.number().optional().describe("Maximum time to wait for element in milliseconds")
151
+ };
152
+ var setValueTool = async ({ selector, value, scrollToView = true, timeout = defaultTimeout2 }) => {
153
+ try {
154
+ const browser = getBrowser();
155
+ await browser.waitUntil(browser.$(selector).isExisting, { timeout });
156
+ if (scrollToView) {
157
+ await browser.$(selector).scrollIntoView({ block: "center", inline: "center" });
158
+ }
159
+ await browser.$(selector).clearValue();
160
+ await browser.$(selector).setValue(value);
161
+ return {
162
+ content: [{ type: "text", text: `Text "${value}" entered into element` }]
163
+ };
164
+ } catch (e) {
165
+ return {
166
+ content: [{ type: "text", text: `Error entering text: ${e}` }]
167
+ };
168
+ }
169
+ };
170
+
171
+ // src/tools/find-element.tool.ts
172
+ import { z as z5 } from "zod";
173
+ var defaultTimeout3 = 3e3;
174
+ var findElementToolArguments = {
175
+ selector: z5.string().describe(`Value for the selector, in the form of css selector or xpath ("button.my-class" or "//button[@class='my-class']")`),
176
+ timeout: z5.number().optional().describe("Maximum time to wait for element in milliseconds")
177
+ };
178
+ var findElementTool = async ({ selector, timeout = defaultTimeout3 }) => {
179
+ try {
180
+ const browser = getBrowser();
181
+ await browser.waitUntil(browser.$(selector).isExisting, { timeout });
182
+ return {
183
+ content: [{ type: "text", text: "Element found" }]
184
+ };
185
+ } catch (e) {
186
+ return {
187
+ content: [{ type: "text", text: `Error finding element: ${e}` }]
188
+ };
189
+ }
190
+ };
191
+
192
+ // src/tools/get-element-text.tool.ts
193
+ import { z as z6 } from "zod";
194
+ var defaultTimeout4 = 3e3;
195
+ var getElementTextToolArguments = {
196
+ selector: z6.string().describe(`Value for the selector, in the form of css selector or xpath ("button.my-class" or "//button[@class='my-class']")`),
197
+ timeout: z6.number().optional().describe("Maximum time to wait for element in milliseconds")
198
+ };
199
+ var getElementTextTool = async ({ selector, timeout = defaultTimeout4 }) => {
200
+ try {
201
+ const browser = getBrowser();
202
+ await browser.waitUntil(browser.$(selector).isExisting, { timeout });
203
+ const text = await browser.$(selector).getText();
204
+ return {
205
+ content: [{ type: "text", text: `Text from element "${selector}": ${text}` }]
206
+ };
207
+ } catch (e) {
208
+ return {
209
+ content: [{ type: "text", text: `Error getting element text: ${e}` }]
210
+ };
211
+ }
212
+ };
213
+
214
+ // src/tools/is-displayed.tool.ts
215
+ import { z as z7 } from "zod";
216
+ var defaultTimeout5 = 3e3;
217
+ var isDisplayedToolArguments = {
218
+ selector: z7.string().describe(`Value for the selector, in the form of css selector or xpath ("button.my-class" or "//button[@class='my-class']")`),
219
+ timeout: z7.number().optional().describe("Maximum time to wait for element in milliseconds")
220
+ };
221
+ var isDisplayedTool = async ({ selector, timeout = defaultTimeout5 }) => {
222
+ try {
223
+ const browser = getBrowser();
224
+ await browser.waitUntil(browser.$(selector).isExisting, { timeout });
225
+ const displayed = await browser.$(selector).isDisplayed();
226
+ return {
227
+ content: [{
228
+ type: "text",
229
+ text: `Element with selector "${selector}" is ${displayed ? "displayed" : "not displayed"}`
230
+ }]
231
+ };
232
+ } catch (e) {
233
+ return {
234
+ content: [{ type: "text", text: `Error checking if element is displayed: ${e}` }]
235
+ };
236
+ }
237
+ };
238
+
239
+ // src/tools/scroll-down.tool.ts
240
+ import { z as z8 } from "zod";
241
+ var scrollDownToolArguments = {
242
+ pixels: z8.number().optional().default(500)
243
+ };
244
+ var scrollDownTool = async ({ pixels = 500 }) => {
245
+ try {
246
+ const browser = getBrowser();
247
+ await browser.execute((scrollPixels) => {
248
+ window.scrollBy(0, scrollPixels);
249
+ }, pixels);
250
+ return {
251
+ content: [{ type: "text", text: `Scrolled down ${pixels} pixels` }]
252
+ };
253
+ } catch (e) {
254
+ return {
255
+ content: [{ type: "text", text: `Error scrolling down: ${e}` }]
256
+ };
257
+ }
258
+ };
259
+
260
+ // src/tools/scroll-up.tool.ts
261
+ import { z as z9 } from "zod";
262
+ var scrollUpToolArguments = {
263
+ pixels: z9.number().optional().default(500)
264
+ };
265
+ var scrollUpTool = async ({ pixels = 500 }) => {
266
+ try {
267
+ const browser = getBrowser();
268
+ await browser.execute((scrollPixels) => {
269
+ window.scrollBy(0, -scrollPixels);
270
+ }, pixels);
271
+ return {
272
+ content: [{ type: "text", text: `Scrolled up ${pixels} pixels` }]
273
+ };
274
+ } catch (e) {
275
+ return {
276
+ content: [{ type: "text", text: `Error scrolling up: ${e}` }]
277
+ };
278
+ }
279
+ };
280
+
281
+ // src/scripts/get-interactable-elements.ts
282
+ var elementsScript = () => (function() {
283
+ const interactableSelectors = [
284
+ "a[href]",
285
+ // Links with href
286
+ "button",
287
+ // Buttons
288
+ 'input:not([type="hidden"])',
289
+ // Input fields (except hidden)
290
+ "select",
291
+ // Select dropdowns
292
+ "textarea",
293
+ // Text areas
294
+ '[role="button"]',
295
+ // Elements with button role
296
+ '[role="link"]',
297
+ // Elements with link role
298
+ '[role="checkbox"]',
299
+ // Elements with checkbox role
300
+ '[role="radio"]',
301
+ // Elements with radio role
302
+ '[role="tab"]',
303
+ // Elements with tab role
304
+ '[role="menuitem"]',
305
+ // Elements with menuitem role
306
+ '[role="combobox"]',
307
+ // Elements with combobox role
308
+ '[role="option"]',
309
+ // Elements with option role
310
+ '[role="switch"]',
311
+ // Elements with switch role
312
+ '[role="slider"]',
313
+ // Elements with slider role
314
+ '[role="textbox"]',
315
+ // Elements with textbox role
316
+ '[role="searchbox"]',
317
+ // Elements with searchbox role
318
+ '[contenteditable="true"]',
319
+ // Editable content
320
+ '[tabindex]:not([tabindex="-1"])'
321
+ // Elements with tabindex
322
+ ];
323
+ function isVisible(element) {
324
+ if (typeof element.checkVisibility === "function") {
325
+ return element.checkVisibility({
326
+ opacityProperty: true,
327
+ visibilityProperty: true,
328
+ contentVisibilityAuto: true
329
+ });
330
+ }
331
+ const style = window.getComputedStyle(element);
332
+ return style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0" && element.offsetWidth > 0 && element.offsetHeight > 0;
333
+ }
334
+ function getCssSelector(element) {
335
+ if (element.id) {
336
+ return `#${CSS.escape(element.id)}`;
337
+ }
338
+ if (element.className) {
339
+ const classes = element.className.trim().split(/\s+/).filter(Boolean);
340
+ if (classes.length > 0) {
341
+ const classSelector = classes.slice(0, 2).map((c) => `.${CSS.escape(c)}`).join("");
342
+ const tagWithClass = `${element.tagName.toLowerCase()}${classSelector}`;
343
+ if (document.querySelectorAll(tagWithClass).length === 1) {
344
+ return tagWithClass;
345
+ }
346
+ }
347
+ }
348
+ let current = element;
349
+ const path = [];
350
+ while (current && current !== document.documentElement) {
351
+ let selector = current.tagName.toLowerCase();
352
+ if (current.id) {
353
+ selector = `#${CSS.escape(current.id)}`;
354
+ path.unshift(selector);
355
+ break;
356
+ }
357
+ const parent = current.parentElement;
358
+ if (parent) {
359
+ const siblings = Array.from(parent.children).filter(
360
+ (child) => child.tagName === current.tagName
361
+ );
362
+ if (siblings.length > 1) {
363
+ const index = siblings.indexOf(current) + 1;
364
+ selector += `:nth-child(${index})`;
365
+ }
366
+ }
367
+ path.unshift(selector);
368
+ current = current.parentElement;
369
+ if (path.length >= 4) {
370
+ break;
371
+ }
372
+ }
373
+ return path.join(" > ");
374
+ }
375
+ function getInteractableElements() {
376
+ const allElements = [];
377
+ interactableSelectors.forEach((selector) => {
378
+ const elements = document.querySelectorAll(selector);
379
+ elements.forEach((element) => {
380
+ if (!allElements.includes(element)) {
381
+ allElements.push(element);
382
+ }
383
+ });
384
+ });
385
+ const elementInfos = allElements.filter((element) => isVisible(element) && !element.disabled).map((element) => {
386
+ const rect = element.getBoundingClientRect();
387
+ const isInViewport = rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
388
+ return {
389
+ tagName: element.tagName.toLowerCase(),
390
+ type: element.getAttribute("type") || void 0,
391
+ id: element.id || void 0,
392
+ className: element.className || void 0,
393
+ textContent: element.textContent?.trim() || void 0,
394
+ value: element.value || void 0,
395
+ placeholder: element.placeholder || void 0,
396
+ href: element.getAttribute("href") || void 0,
397
+ ariaLabel: element.getAttribute("aria-label") || void 0,
398
+ role: element.getAttribute("role") || void 0,
399
+ cssSelector: getCssSelector(element),
400
+ isInViewport
401
+ };
402
+ });
403
+ return [
404
+ ...elementInfos
405
+ ];
406
+ }
407
+ return getInteractableElements();
408
+ })();
409
+ var get_interactable_elements_default = elementsScript;
410
+
411
+ // src/locators/source-parsing.ts
412
+ import { DOMParser } from "@xmldom/xmldom";
413
+ function childNodesOf(node) {
414
+ const children = [];
415
+ if (node.childNodes) {
416
+ for (let i = 0; i < node.childNodes.length; i++) {
417
+ const child = node.childNodes.item(i);
418
+ if (child?.nodeType === 1) {
419
+ children.push(child);
420
+ }
421
+ }
422
+ }
423
+ return children;
424
+ }
425
+ function translateRecursively(domNode, parentPath = "", index = null) {
426
+ const attributes = {};
427
+ const element = domNode;
428
+ if (element.attributes) {
429
+ for (let attrIdx = 0; attrIdx < element.attributes.length; attrIdx++) {
430
+ const attr = element.attributes.item(attrIdx);
431
+ if (attr) {
432
+ attributes[attr.name] = attr.value.replace(/(\n)/gm, "\\n");
433
+ }
434
+ }
435
+ }
436
+ const path = index === null ? "" : `${parentPath ? parentPath + "." : ""}${index}`;
437
+ return {
438
+ children: childNodesOf(domNode).map(
439
+ (childNode, childIndex) => translateRecursively(childNode, path, childIndex)
440
+ ),
441
+ tagName: domNode.nodeName,
442
+ attributes,
443
+ path
444
+ };
445
+ }
446
+ function xmlToJSON(sourceXML) {
447
+ try {
448
+ const parser = new DOMParser();
449
+ const sourceDoc = parser.parseFromString(sourceXML, "text/xml");
450
+ const parseErrors = sourceDoc.getElementsByTagName("parsererror");
451
+ if (parseErrors.length > 0) {
452
+ console.error("[xmlToJSON] XML parsing error:", parseErrors[0].textContent);
453
+ return null;
454
+ }
455
+ const children = childNodesOf(sourceDoc);
456
+ const firstChild = children[0] || (sourceDoc.documentElement ? childNodesOf(sourceDoc.documentElement)[0] : null);
457
+ return firstChild ? translateRecursively(firstChild) : { children: [], tagName: "", attributes: {}, path: "" };
458
+ } catch (e) {
459
+ console.error("[xmlToJSON] Failed to parse XML:", e);
460
+ return null;
461
+ }
462
+ }
463
+ function parseAndroidBounds(bounds) {
464
+ const match = bounds.match(/\[(\d+),(\d+)\]\[(\d+),(\d+)\]/);
465
+ if (!match) {
466
+ return { x: 0, y: 0, width: 0, height: 0 };
467
+ }
468
+ const x1 = parseInt(match[1], 10);
469
+ const y1 = parseInt(match[2], 10);
470
+ const x2 = parseInt(match[3], 10);
471
+ const y2 = parseInt(match[4], 10);
472
+ return {
473
+ x: x1,
474
+ y: y1,
475
+ width: x2 - x1,
476
+ height: y2 - y1
477
+ };
478
+ }
479
+ function parseIOSBounds(attributes) {
480
+ return {
481
+ x: parseInt(attributes.x || "0", 10),
482
+ y: parseInt(attributes.y || "0", 10),
483
+ width: parseInt(attributes.width || "0", 10),
484
+ height: parseInt(attributes.height || "0", 10)
485
+ };
486
+ }
487
+ function countAttributeOccurrences(sourceXML, attribute, value) {
488
+ const escapedValue = value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
489
+ const pattern = new RegExp(`${attribute}=["']${escapedValue}["']`, "g");
490
+ const matches = sourceXML.match(pattern);
491
+ return matches ? matches.length : 0;
492
+ }
493
+ function isAttributeUnique(sourceXML, attribute, value) {
494
+ return countAttributeOccurrences(sourceXML, attribute, value) === 1;
495
+ }
496
+
497
+ // src/locators/element-filter.ts
498
+ var ANDROID_INTERACTABLE_TAGS = [
499
+ // Input elements
500
+ "android.widget.EditText",
501
+ "android.widget.AutoCompleteTextView",
502
+ "android.widget.MultiAutoCompleteTextView",
503
+ "android.widget.SearchView",
504
+ // Button-like elements
505
+ "android.widget.Button",
506
+ "android.widget.ImageButton",
507
+ "android.widget.ToggleButton",
508
+ "android.widget.CompoundButton",
509
+ "android.widget.RadioButton",
510
+ "android.widget.CheckBox",
511
+ "android.widget.Switch",
512
+ "android.widget.FloatingActionButton",
513
+ "com.google.android.material.button.MaterialButton",
514
+ "com.google.android.material.floatingactionbutton.FloatingActionButton",
515
+ // Text elements (often tappable)
516
+ "android.widget.TextView",
517
+ "android.widget.CheckedTextView",
518
+ // Image elements (often tappable)
519
+ "android.widget.ImageView",
520
+ "android.widget.QuickContactBadge",
521
+ // Selection elements
522
+ "android.widget.Spinner",
523
+ "android.widget.SeekBar",
524
+ "android.widget.RatingBar",
525
+ "android.widget.ProgressBar",
526
+ "android.widget.DatePicker",
527
+ "android.widget.TimePicker",
528
+ "android.widget.NumberPicker",
529
+ // List/grid items
530
+ "android.widget.AdapterView"
531
+ ];
532
+ var IOS_INTERACTABLE_TAGS = [
533
+ // Input elements
534
+ "XCUIElementTypeTextField",
535
+ "XCUIElementTypeSecureTextField",
536
+ "XCUIElementTypeTextView",
537
+ "XCUIElementTypeSearchField",
538
+ // Button-like elements
539
+ "XCUIElementTypeButton",
540
+ "XCUIElementTypeLink",
541
+ // Text elements (often tappable)
542
+ "XCUIElementTypeStaticText",
543
+ // Image elements
544
+ "XCUIElementTypeImage",
545
+ "XCUIElementTypeIcon",
546
+ // Selection elements
547
+ "XCUIElementTypeSwitch",
548
+ "XCUIElementTypeSlider",
549
+ "XCUIElementTypeStepper",
550
+ "XCUIElementTypeSegmentedControl",
551
+ "XCUIElementTypePicker",
552
+ "XCUIElementTypePickerWheel",
553
+ "XCUIElementTypeDatePicker",
554
+ "XCUIElementTypePageIndicator",
555
+ // Table/list items
556
+ "XCUIElementTypeCell",
557
+ "XCUIElementTypeMenuItem",
558
+ "XCUIElementTypeMenuBarItem",
559
+ // Toggle elements
560
+ "XCUIElementTypeCheckBox",
561
+ "XCUIElementTypeRadioButton",
562
+ "XCUIElementTypeToggle",
563
+ // Other interactive
564
+ "XCUIElementTypeKey",
565
+ "XCUIElementTypeKeyboard",
566
+ "XCUIElementTypeAlert",
567
+ "XCUIElementTypeSheet"
568
+ ];
569
+ var ANDROID_LAYOUT_CONTAINERS = [
570
+ // Core ViewGroup classes
571
+ "android.view.ViewGroup",
572
+ "android.view.View",
573
+ "android.widget.FrameLayout",
574
+ "android.widget.LinearLayout",
575
+ "android.widget.RelativeLayout",
576
+ "android.widget.GridLayout",
577
+ "android.widget.TableLayout",
578
+ "android.widget.TableRow",
579
+ "android.widget.AbsoluteLayout",
580
+ // AndroidX layout classes
581
+ "androidx.constraintlayout.widget.ConstraintLayout",
582
+ "androidx.coordinatorlayout.widget.CoordinatorLayout",
583
+ "androidx.appcompat.widget.LinearLayoutCompat",
584
+ "androidx.cardview.widget.CardView",
585
+ "androidx.appcompat.widget.ContentFrameLayout",
586
+ "androidx.appcompat.widget.FitWindowsFrameLayout",
587
+ // Scrolling containers
588
+ "android.widget.ScrollView",
589
+ "android.widget.HorizontalScrollView",
590
+ "android.widget.NestedScrollView",
591
+ "androidx.core.widget.NestedScrollView",
592
+ "androidx.recyclerview.widget.RecyclerView",
593
+ "android.widget.ListView",
594
+ "android.widget.GridView",
595
+ "android.widget.AbsListView",
596
+ // App chrome / system elements
597
+ "android.widget.ActionBarContainer",
598
+ "android.widget.ActionBarOverlayLayout",
599
+ "android.view.ViewStub",
600
+ "androidx.appcompat.widget.ActionBarContainer",
601
+ "androidx.appcompat.widget.ActionBarContextView",
602
+ "androidx.appcompat.widget.ActionBarOverlayLayout",
603
+ // Decor views
604
+ "com.android.internal.policy.DecorView",
605
+ "android.widget.DecorView"
606
+ ];
607
+ var IOS_LAYOUT_CONTAINERS = [
608
+ // Generic containers
609
+ "XCUIElementTypeOther",
610
+ "XCUIElementTypeGroup",
611
+ "XCUIElementTypeLayoutItem",
612
+ // Scroll containers
613
+ "XCUIElementTypeScrollView",
614
+ "XCUIElementTypeTable",
615
+ "XCUIElementTypeCollectionView",
616
+ "XCUIElementTypeScrollBar",
617
+ // Navigation chrome
618
+ "XCUIElementTypeNavigationBar",
619
+ "XCUIElementTypeTabBar",
620
+ "XCUIElementTypeToolbar",
621
+ "XCUIElementTypeStatusBar",
622
+ "XCUIElementTypeMenuBar",
623
+ // Windows and views
624
+ "XCUIElementTypeWindow",
625
+ "XCUIElementTypeSheet",
626
+ "XCUIElementTypeDrawer",
627
+ "XCUIElementTypeDialog",
628
+ "XCUIElementTypePopover",
629
+ "XCUIElementTypePopUpButton",
630
+ // Outline elements
631
+ "XCUIElementTypeOutline",
632
+ "XCUIElementTypeOutlineRow",
633
+ "XCUIElementTypeBrowser",
634
+ "XCUIElementTypeSplitGroup",
635
+ "XCUIElementTypeSplitter",
636
+ // Application root
637
+ "XCUIElementTypeApplication"
638
+ ];
639
+ function matchesTagList(tagName, tagList) {
640
+ if (tagList.includes(tagName)) {
641
+ return true;
642
+ }
643
+ for (const tag of tagList) {
644
+ if (tagName.endsWith(tag) || tagName.includes(tag)) {
645
+ return true;
646
+ }
647
+ }
648
+ return false;
649
+ }
650
+ function matchesTagFilters(element, includeTagNames, excludeTagNames) {
651
+ if (includeTagNames.length > 0 && !matchesTagList(element.tagName, includeTagNames)) {
652
+ return false;
653
+ }
654
+ if (matchesTagList(element.tagName, excludeTagNames)) {
655
+ return false;
656
+ }
657
+ return true;
658
+ }
659
+ function matchesAttributeFilters(element, requireAttributes, minAttributeCount) {
660
+ if (requireAttributes.length > 0) {
661
+ const hasRequiredAttr = requireAttributes.some((attr) => element.attributes?.[attr]);
662
+ if (!hasRequiredAttr) return false;
663
+ }
664
+ if (element.attributes && minAttributeCount > 0) {
665
+ const attrCount = Object.values(element.attributes).filter(
666
+ (v) => v !== void 0 && v !== null && v !== ""
667
+ ).length;
668
+ if (attrCount < minAttributeCount) {
669
+ return false;
670
+ }
671
+ }
672
+ return true;
673
+ }
674
+ function isInteractableElement(element, isNative, automationName) {
675
+ const isAndroid = automationName.toLowerCase().includes("uiautomator");
676
+ const interactableTags = isAndroid ? ANDROID_INTERACTABLE_TAGS : IOS_INTERACTABLE_TAGS;
677
+ if (matchesTagList(element.tagName, interactableTags)) {
678
+ return true;
679
+ }
680
+ if (isAndroid) {
681
+ if (element.attributes?.clickable === "true" || element.attributes?.focusable === "true" || element.attributes?.checkable === "true" || element.attributes?.["long-clickable"] === "true") {
682
+ return true;
683
+ }
684
+ }
685
+ if (!isAndroid) {
686
+ if (element.attributes?.accessible === "true") {
687
+ return true;
688
+ }
689
+ }
690
+ return false;
691
+ }
692
+ function isLayoutContainer(element, platform) {
693
+ const containerList = platform === "android" ? ANDROID_LAYOUT_CONTAINERS : IOS_LAYOUT_CONTAINERS;
694
+ return matchesTagList(element.tagName, containerList);
695
+ }
696
+ function hasMeaningfulContent(element, platform) {
697
+ const attrs = element.attributes;
698
+ if (attrs.text && attrs.text.trim() !== "" && attrs.text !== "null") {
699
+ return true;
700
+ }
701
+ if (platform === "android") {
702
+ if (attrs["content-desc"] && attrs["content-desc"].trim() !== "" && attrs["content-desc"] !== "null") {
703
+ return true;
704
+ }
705
+ } else {
706
+ if (attrs.label && attrs.label.trim() !== "" && attrs.label !== "null") {
707
+ return true;
708
+ }
709
+ if (attrs.name && attrs.name.trim() !== "" && attrs.name !== "null") {
710
+ return true;
711
+ }
712
+ }
713
+ return false;
714
+ }
715
+ function shouldIncludeElement(element, filters, isNative, automationName) {
716
+ const {
717
+ includeTagNames = [],
718
+ excludeTagNames = ["hierarchy"],
719
+ // Always exclude root hierarchy node
720
+ requireAttributes = [],
721
+ minAttributeCount = 0,
722
+ fetchableOnly = false,
723
+ clickableOnly = false,
724
+ visibleOnly = true
725
+ } = filters;
726
+ if (!matchesTagFilters(element, includeTagNames, excludeTagNames)) {
727
+ return false;
728
+ }
729
+ if (!matchesAttributeFilters(element, requireAttributes, minAttributeCount)) {
730
+ return false;
731
+ }
732
+ if (clickableOnly && element.attributes?.clickable !== "true") {
733
+ return false;
734
+ }
735
+ if (visibleOnly) {
736
+ const isAndroid = automationName.toLowerCase().includes("uiautomator");
737
+ if (isAndroid && element.attributes?.displayed === "false") {
738
+ return false;
739
+ }
740
+ if (!isAndroid && element.attributes?.visible === "false") {
741
+ return false;
742
+ }
743
+ }
744
+ if (fetchableOnly && !isInteractableElement(element, isNative, automationName)) {
745
+ return false;
746
+ }
747
+ return true;
748
+ }
749
+ function getDefaultFilters(platform, includeContainers = false) {
750
+ const layoutContainers = platform === "android" ? ANDROID_LAYOUT_CONTAINERS : IOS_LAYOUT_CONTAINERS;
751
+ return {
752
+ excludeTagNames: includeContainers ? ["hierarchy"] : ["hierarchy", ...layoutContainers],
753
+ fetchableOnly: !includeContainers,
754
+ visibleOnly: true,
755
+ clickableOnly: false
756
+ };
757
+ }
758
+
759
+ // src/locators/locator-generation.ts
760
+ function isValidValue(value) {
761
+ return value !== void 0 && value !== null && value !== "null" && value.trim() !== "";
762
+ }
763
+ function escapeText(text) {
764
+ return text.replace(/"/g, '\\"').replace(/\n/g, "\\n");
765
+ }
766
+ function getSimpleSuggestedLocators(element, sourceXML, isNative, automationName) {
767
+ const results = [];
768
+ const isAndroid = automationName.toLowerCase().includes("uiautomator");
769
+ const attrs = element.attributes;
770
+ if (isAndroid) {
771
+ const resourceId = attrs["resource-id"];
772
+ if (isValidValue(resourceId) && isAttributeUnique(sourceXML, "resource-id", resourceId)) {
773
+ results.push(["id", `android=new UiSelector().resourceId("${resourceId}")`]);
774
+ }
775
+ const contentDesc = attrs["content-desc"];
776
+ if (isValidValue(contentDesc) && isAttributeUnique(sourceXML, "content-desc", contentDesc)) {
777
+ results.push(["accessibility-id", `~${contentDesc}`]);
778
+ }
779
+ const text = attrs.text;
780
+ if (isValidValue(text) && text.length < 100 && isAttributeUnique(sourceXML, "text", text)) {
781
+ results.push(["text", `android=new UiSelector().text("${escapeText(text)}")`]);
782
+ }
783
+ } else {
784
+ const name = attrs.name;
785
+ if (isValidValue(name) && isAttributeUnique(sourceXML, "name", name)) {
786
+ results.push(["accessibility-id", `~${name}`]);
787
+ }
788
+ const label = attrs.label;
789
+ if (isValidValue(label) && label !== name && isAttributeUnique(sourceXML, "label", label)) {
790
+ results.push(["predicate-string", `-ios predicate string:label == "${escapeText(label)}"`]);
791
+ }
792
+ const value = attrs.value;
793
+ if (isValidValue(value) && isAttributeUnique(sourceXML, "value", value)) {
794
+ results.push(["predicate-string", `-ios predicate string:value == "${escapeText(value)}"`]);
795
+ }
796
+ }
797
+ return results;
798
+ }
799
+ function buildUiAutomatorSelector(element) {
800
+ const attrs = element.attributes;
801
+ const parts = [];
802
+ if (isValidValue(attrs["resource-id"])) {
803
+ parts.push(`resourceId("${attrs["resource-id"]}")`);
804
+ }
805
+ if (isValidValue(attrs.text) && attrs.text.length < 100) {
806
+ parts.push(`text("${escapeText(attrs.text)}")`);
807
+ }
808
+ if (isValidValue(attrs["content-desc"])) {
809
+ parts.push(`description("${attrs["content-desc"]}")`);
810
+ }
811
+ if (isValidValue(attrs.class)) {
812
+ parts.push(`className("${attrs.class}")`);
813
+ }
814
+ if (parts.length === 0) return null;
815
+ return `android=new UiSelector().${parts.join(".")}`;
816
+ }
817
+ function buildPredicateString(element) {
818
+ const attrs = element.attributes;
819
+ const conditions = [];
820
+ if (isValidValue(attrs.name)) {
821
+ conditions.push(`name == "${escapeText(attrs.name)}"`);
822
+ }
823
+ if (isValidValue(attrs.label)) {
824
+ conditions.push(`label == "${escapeText(attrs.label)}"`);
825
+ }
826
+ if (isValidValue(attrs.value)) {
827
+ conditions.push(`value == "${escapeText(attrs.value)}"`);
828
+ }
829
+ if (attrs.visible === "true") {
830
+ conditions.push("visible == 1");
831
+ }
832
+ if (attrs.enabled === "true") {
833
+ conditions.push("enabled == 1");
834
+ }
835
+ if (conditions.length === 0) return null;
836
+ return `-ios predicate string:${conditions.join(" AND ")}`;
837
+ }
838
+ function buildClassChain(element) {
839
+ const attrs = element.attributes;
840
+ const tagName = element.tagName;
841
+ if (!tagName.startsWith("XCUI")) return null;
842
+ let selector = `**/${tagName}`;
843
+ if (isValidValue(attrs.label)) {
844
+ selector += `[\`label == "${escapeText(attrs.label)}"\`]`;
845
+ } else if (isValidValue(attrs.name)) {
846
+ selector += `[\`name == "${escapeText(attrs.name)}"\`]`;
847
+ }
848
+ return `-ios class chain:${selector}`;
849
+ }
850
+ function buildXPath(element, sourceXML, isAndroid) {
851
+ const attrs = element.attributes;
852
+ const tagName = element.tagName;
853
+ const conditions = [];
854
+ if (isAndroid) {
855
+ if (isValidValue(attrs["resource-id"])) {
856
+ conditions.push(`@resource-id="${attrs["resource-id"]}"`);
857
+ }
858
+ if (isValidValue(attrs["content-desc"])) {
859
+ conditions.push(`@content-desc="${attrs["content-desc"]}"`);
860
+ }
861
+ if (isValidValue(attrs.text) && attrs.text.length < 100) {
862
+ conditions.push(`@text="${escapeText(attrs.text)}"`);
863
+ }
864
+ } else {
865
+ if (isValidValue(attrs.name)) {
866
+ conditions.push(`@name="${attrs.name}"`);
867
+ }
868
+ if (isValidValue(attrs.label)) {
869
+ conditions.push(`@label="${attrs.label}"`);
870
+ }
871
+ if (isValidValue(attrs.value)) {
872
+ conditions.push(`@value="${attrs.value}"`);
873
+ }
874
+ }
875
+ if (conditions.length === 0) {
876
+ return `//${tagName}`;
877
+ }
878
+ return `//${tagName}[${conditions.join(" and ")}]`;
879
+ }
880
+ function getComplexSuggestedLocators(element, sourceXML, isNative, automationName) {
881
+ const results = [];
882
+ const isAndroid = automationName.toLowerCase().includes("uiautomator");
883
+ if (isAndroid) {
884
+ const uiAutomator = buildUiAutomatorSelector(element);
885
+ if (uiAutomator) {
886
+ results.push(["uiautomator", uiAutomator]);
887
+ }
888
+ const xpath = buildXPath(element, sourceXML, true);
889
+ if (xpath) {
890
+ results.push(["xpath", xpath]);
891
+ }
892
+ if (isValidValue(element.attributes.class)) {
893
+ results.push([
894
+ "class-name",
895
+ `android=new UiSelector().className("${element.attributes.class}")`
896
+ ]);
897
+ }
898
+ } else {
899
+ const predicate = buildPredicateString(element);
900
+ if (predicate) {
901
+ results.push(["predicate-string", predicate]);
902
+ }
903
+ const classChain = buildClassChain(element);
904
+ if (classChain) {
905
+ results.push(["class-chain", classChain]);
906
+ }
907
+ const xpath = buildXPath(element, sourceXML, false);
908
+ if (xpath) {
909
+ results.push(["xpath", xpath]);
910
+ }
911
+ const type = element.tagName;
912
+ if (type.startsWith("XCUIElementType")) {
913
+ results.push(["class-name", `-ios class chain:**/${type}`]);
914
+ }
915
+ }
916
+ return results;
917
+ }
918
+ function getSuggestedLocators(element, sourceXML, isNative, automationName) {
919
+ const simpleLocators = getSimpleSuggestedLocators(element, sourceXML, isNative, automationName);
920
+ const complexLocators = getComplexSuggestedLocators(element, sourceXML, isNative, automationName);
921
+ const seen = /* @__PURE__ */ new Set();
922
+ const results = [];
923
+ for (const locator of [...simpleLocators, ...complexLocators]) {
924
+ if (!seen.has(locator[1])) {
925
+ seen.add(locator[1]);
926
+ results.push(locator);
927
+ }
928
+ }
929
+ return results;
930
+ }
931
+ function locatorsToObject(locators) {
932
+ const result = {};
933
+ for (const [strategy, value] of locators) {
934
+ if (!result[strategy]) {
935
+ result[strategy] = value;
936
+ }
937
+ }
938
+ return result;
939
+ }
940
+
941
+ // src/locators/generate-all-locators.ts
942
+ function parseBounds(element, platform) {
943
+ return platform === "android" ? parseAndroidBounds(element.attributes.bounds || "") : parseIOSBounds(element.attributes);
944
+ }
945
+ function isWithinViewport(bounds, viewport) {
946
+ return bounds.x >= 0 && bounds.y >= 0 && bounds.width > 0 && bounds.height > 0 && bounds.x + bounds.width <= viewport.width && bounds.y + bounds.height <= viewport.height;
947
+ }
948
+ function transformElement(element, locators, ctx) {
949
+ const attrs = element.attributes;
950
+ const bounds = parseBounds(element, ctx.platform);
951
+ return {
952
+ tagName: element.tagName,
953
+ locators: locatorsToObject(locators),
954
+ text: attrs.text || attrs.label || "",
955
+ contentDesc: attrs["content-desc"] || "",
956
+ resourceId: attrs["resource-id"] || "",
957
+ accessibilityId: attrs.name || attrs["content-desc"] || "",
958
+ label: attrs.label || "",
959
+ value: attrs.value || "",
960
+ className: attrs.class || element.tagName,
961
+ clickable: attrs.clickable === "true" || attrs.accessible === "true" || attrs["long-clickable"] === "true",
962
+ enabled: attrs.enabled !== "false",
963
+ displayed: ctx.platform === "android" ? attrs.displayed !== "false" : attrs.visible !== "false",
964
+ bounds,
965
+ isInViewport: isWithinViewport(bounds, ctx.viewportSize)
966
+ };
967
+ }
968
+ function shouldProcess(element, ctx) {
969
+ if (shouldIncludeElement(element, ctx.filters, ctx.isNative, ctx.automationName)) {
970
+ return true;
971
+ }
972
+ return isLayoutContainer(element, ctx.platform) && hasMeaningfulContent(element, ctx.platform);
973
+ }
974
+ function processElement(element, ctx) {
975
+ if (!shouldProcess(element, ctx)) return;
976
+ try {
977
+ const locators = getSuggestedLocators(element, ctx.sourceXML, ctx.isNative, ctx.automationName);
978
+ if (locators.length === 0) return;
979
+ const transformed = transformElement(element, locators, ctx);
980
+ if (Object.keys(transformed.locators).length === 0) return;
981
+ ctx.results.push(transformed);
982
+ } catch (error) {
983
+ console.error(`[processElement] Error at path ${element.path}:`, error);
984
+ }
985
+ }
986
+ function traverseTree(element, ctx) {
987
+ if (!element) return;
988
+ processElement(element, ctx);
989
+ for (const child of element.children || []) {
990
+ traverseTree(child, ctx);
991
+ }
992
+ }
993
+ function generateAllElementLocators(sourceXML, options) {
994
+ const sourceJSON = xmlToJSON(sourceXML);
995
+ if (!sourceJSON) {
996
+ console.error("[generateAllElementLocators] Failed to parse page source XML");
997
+ return [];
998
+ }
999
+ const ctx = {
1000
+ sourceXML,
1001
+ platform: options.platform,
1002
+ automationName: options.platform === "android" ? "uiautomator2" : "xcuitest",
1003
+ isNative: options.isNative ?? true,
1004
+ viewportSize: options.viewportSize ?? { width: 9999, height: 9999 },
1005
+ filters: options.filters ?? {},
1006
+ results: []
1007
+ };
1008
+ traverseTree(sourceJSON, ctx);
1009
+ return ctx.results;
1010
+ }
1011
+
1012
+ // src/utils/mobile-elements.ts
1013
+ function toMobileElementInfo(element) {
1014
+ const locatorValues = Object.values(element.locators);
1015
+ return {
1016
+ selector: locatorValues[0] || "",
1017
+ alternativeSelectors: locatorValues.length > 1 ? locatorValues.slice(1, 3) : void 0,
1018
+ locators: element.locators,
1019
+ isInViewport: element.isInViewport,
1020
+ isEnabled: element.enabled,
1021
+ bounds: element.bounds,
1022
+ tagName: element.tagName || void 0,
1023
+ text: element.text || void 0,
1024
+ resourceId: element.resourceId || void 0,
1025
+ contentDesc: element.contentDesc || void 0,
1026
+ accessibilityId: element.accessibilityId || void 0,
1027
+ label: element.label || void 0,
1028
+ value: element.value || void 0,
1029
+ className: element.className || void 0
1030
+ };
1031
+ }
1032
+ async function getViewportSize(browser) {
1033
+ try {
1034
+ const size = await browser.getWindowSize();
1035
+ return { width: size.width, height: size.height };
1036
+ } catch {
1037
+ return { width: 9999, height: 9999 };
1038
+ }
1039
+ }
1040
+ async function getMobileVisibleElements(browser, platform, options = {}) {
1041
+ const { includeContainers = false, filterOptions } = options;
1042
+ const viewportSize = await getViewportSize(browser);
1043
+ const pageSource = await browser.getPageSource();
1044
+ const filters = {
1045
+ ...getDefaultFilters(platform, includeContainers),
1046
+ ...filterOptions
1047
+ };
1048
+ const elements = generateAllElementLocators(pageSource, {
1049
+ platform,
1050
+ viewportSize,
1051
+ filters
1052
+ });
1053
+ return elements.map(toMobileElementInfo);
1054
+ }
1055
+
1056
+ // src/tools/get-visible-elements.tool.ts
1057
+ import { encode } from "@toon-format/toon";
1058
+ import { z as z10 } from "zod";
1059
+ var getVisibleElementsToolArguments = {
1060
+ inViewportOnly: z10.boolean().optional().describe(
1061
+ "Only return elements within the visible viewport. Default: true. Set to false to get ALL elements on the page."
1062
+ ),
1063
+ includeContainers: z10.boolean().optional().describe(
1064
+ "Include layout containers (ViewGroup, FrameLayout, ScrollView, etc). Default: false. Set to true to see all elements including layouts."
1065
+ )
1066
+ };
1067
+ var getVisibleElementsTool = async (args) => {
1068
+ try {
1069
+ const browser = getBrowser();
1070
+ const { inViewportOnly = true, includeContainers = false } = args || {};
1071
+ if (browser.isAndroid || browser.isIOS) {
1072
+ const platform = browser.isAndroid ? "android" : "ios";
1073
+ let elements2 = await getMobileVisibleElements(browser, platform, {
1074
+ includeContainers
1075
+ });
1076
+ if (inViewportOnly) {
1077
+ elements2 = elements2.filter((el) => el.isInViewport);
1078
+ }
1079
+ return {
1080
+ content: [{ type: "text", text: encode(elements2) }]
1081
+ };
1082
+ }
1083
+ const elements = await browser.execute(get_interactable_elements_default);
1084
+ if (inViewportOnly) {
1085
+ const filteredElements = elements.filter((el) => el.isInViewport !== false);
1086
+ return {
1087
+ content: [{ type: "text", text: encode(filteredElements) }]
1088
+ };
1089
+ }
1090
+ return {
1091
+ content: [{ type: "text", text: encode(elements) }]
1092
+ };
1093
+ } catch (e) {
1094
+ return {
1095
+ content: [{ type: "text", text: `Error getting visible elements: ${e}` }]
1096
+ };
1097
+ }
1098
+ };
1099
+
1100
+ // src/tools/take-screenshot.tool.ts
1101
+ import { z as z11 } from "zod";
1102
+ var takeScreenshotToolArguments = {
1103
+ outputPath: z11.string().optional().describe("Optional path where to save the screenshot. If not provided, returns base64 data.")
1104
+ };
1105
+ var takeScreenshotTool = async ({ outputPath }) => {
1106
+ try {
1107
+ const browser = getBrowser();
1108
+ const screenshot = await browser.takeScreenshot();
1109
+ if (outputPath) {
1110
+ const fs = await import("fs");
1111
+ await fs.promises.writeFile(outputPath, screenshot, "base64");
1112
+ return {
1113
+ content: [{ type: "text", text: `Screenshot saved to ${outputPath}` }]
1114
+ };
1115
+ }
1116
+ return {
1117
+ content: [
1118
+ { type: "text", text: "Screenshot captured as base64:" },
1119
+ { type: "image", data: screenshot.toString(), mimeType: "image/png" }
1120
+ ]
1121
+ };
1122
+ } catch (e) {
1123
+ return {
1124
+ content: [{ type: "text", text: `Error taking screenshot: ${e.message}` }]
1125
+ };
1126
+ }
1127
+ };
1128
+
1129
+ // src/tools/cookies.tool.ts
1130
+ import { z as z12 } from "zod";
1131
+ var getCookiesToolArguments = {
1132
+ name: z12.string().optional().describe("Optional cookie name to retrieve a specific cookie. If not provided, returns all cookies")
1133
+ };
1134
+ var getCookiesTool = async ({ name }) => {
1135
+ try {
1136
+ const browser = getBrowser();
1137
+ if (name) {
1138
+ const cookie = await browser.getCookies([name]);
1139
+ if (cookie.length === 0) {
1140
+ return {
1141
+ content: [{ type: "text", text: `Cookie "${name}" not found` }]
1142
+ };
1143
+ }
1144
+ return {
1145
+ content: [{ type: "text", text: JSON.stringify(cookie[0], null, 2) }]
1146
+ };
1147
+ }
1148
+ const cookies = await browser.getCookies();
1149
+ if (cookies.length === 0) {
1150
+ return {
1151
+ content: [{ type: "text", text: "No cookies found" }]
1152
+ };
1153
+ }
1154
+ return {
1155
+ content: [{ type: "text", text: JSON.stringify(cookies, null, 2) }]
1156
+ };
1157
+ } catch (e) {
1158
+ return {
1159
+ content: [{ type: "text", text: `Error getting cookies: ${e}` }]
1160
+ };
1161
+ }
1162
+ };
1163
+ var setCookieToolArguments = {
1164
+ name: z12.string().describe("Cookie name"),
1165
+ value: z12.string().describe("Cookie value"),
1166
+ domain: z12.string().optional().describe("Cookie domain (defaults to current domain)"),
1167
+ path: z12.string().optional().describe('Cookie path (defaults to "/")'),
1168
+ expires: z12.number().optional().describe("Expiry date as Unix timestamp in seconds"),
1169
+ httpOnly: z12.boolean().optional().describe("HttpOnly flag"),
1170
+ secure: z12.boolean().optional().describe("Secure flag"),
1171
+ sameSite: z12.enum(["Strict", "Lax", "None"]).optional().describe("SameSite attribute")
1172
+ };
1173
+ var setCookieTool = async ({
1174
+ name,
1175
+ value,
1176
+ domain,
1177
+ path = "/",
1178
+ expires,
1179
+ httpOnly,
1180
+ secure,
1181
+ sameSite
1182
+ }) => {
1183
+ try {
1184
+ const browser = getBrowser();
1185
+ const cookie = {
1186
+ name,
1187
+ value,
1188
+ path,
1189
+ domain,
1190
+ expiry: expires,
1191
+ httpOnly,
1192
+ secure,
1193
+ sameSite
1194
+ };
1195
+ await browser.setCookies(cookie);
1196
+ return {
1197
+ content: [{ type: "text", text: `Cookie "${name}" set successfully` }]
1198
+ };
1199
+ } catch (e) {
1200
+ return {
1201
+ content: [{ type: "text", text: `Error setting cookie: ${e}` }]
1202
+ };
1203
+ }
1204
+ };
1205
+ var deleteCookiesToolArguments = {
1206
+ name: z12.string().optional().describe("Optional cookie name to delete a specific cookie. If not provided, deletes all cookies")
1207
+ };
1208
+ var deleteCookiesTool = async ({ name }) => {
1209
+ try {
1210
+ const browser = getBrowser();
1211
+ if (name) {
1212
+ await browser.deleteCookies([name]);
1213
+ return {
1214
+ content: [{ type: "text", text: `Cookie "${name}" deleted successfully` }]
1215
+ };
1216
+ }
1217
+ await browser.deleteCookies();
1218
+ return {
1219
+ content: [{ type: "text", text: "All cookies deleted successfully" }]
1220
+ };
1221
+ } catch (e) {
1222
+ return {
1223
+ content: [{ type: "text", text: `Error deleting cookies: ${e}` }]
1224
+ };
1225
+ }
1226
+ };
1227
+
1228
+ // src/tools/get-accessibility-tree.tool.ts
1229
+ import { encode as encode2 } from "@toon-format/toon";
1230
+ function flattenAccessibilityTree(node, result = []) {
1231
+ if (!node) return result;
1232
+ if (node.role !== "WebArea" || node.name) {
1233
+ result.push({
1234
+ role: node.role,
1235
+ name: node.name,
1236
+ value: node.value,
1237
+ description: node.description,
1238
+ keyshortcuts: node.keyshortcuts,
1239
+ roledescription: node.roledescription,
1240
+ valuetext: node.valuetext,
1241
+ disabled: node.disabled,
1242
+ expanded: node.expanded,
1243
+ focused: node.focused,
1244
+ modal: node.modal,
1245
+ multiline: node.multiline,
1246
+ multiselectable: node.multiselectable,
1247
+ readonly: node.readonly,
1248
+ required: node.required,
1249
+ selected: node.selected,
1250
+ checked: node.checked,
1251
+ pressed: node.pressed,
1252
+ level: node.level,
1253
+ valuemin: node.valuemin,
1254
+ valuemax: node.valuemax,
1255
+ autocomplete: node.autocomplete,
1256
+ haspopup: node.haspopup,
1257
+ invalid: node.invalid,
1258
+ orientation: node.orientation
1259
+ });
1260
+ }
1261
+ if (node.children && Array.isArray(node.children)) {
1262
+ for (const child of node.children) {
1263
+ flattenAccessibilityTree(child, result);
1264
+ }
1265
+ }
1266
+ return result;
1267
+ }
1268
+ var getAccessibilityTreeTool = async () => {
1269
+ try {
1270
+ const browser = getBrowser();
1271
+ const puppeteer = await browser.getPuppeteer();
1272
+ const pages = await puppeteer.pages();
1273
+ if (pages.length === 0) {
1274
+ return {
1275
+ content: [{ type: "text", text: "No active pages found" }]
1276
+ };
1277
+ }
1278
+ const page = pages[0];
1279
+ const snapshot = await page.accessibility.snapshot({
1280
+ interestingOnly: true
1281
+ // Filter to only interesting/semantic nodes
1282
+ });
1283
+ if (!snapshot) {
1284
+ return {
1285
+ content: [{ type: "text", text: "No accessibility tree available" }]
1286
+ };
1287
+ }
1288
+ const flattenedNodes = flattenAccessibilityTree(snapshot);
1289
+ return {
1290
+ content: [{
1291
+ type: "text",
1292
+ text: encode2(flattenedNodes)
1293
+ }]
1294
+ };
1295
+ } catch (e) {
1296
+ return {
1297
+ content: [{ type: "text", text: `Error getting accessibility tree: ${e}` }]
1298
+ };
1299
+ }
1300
+ };
1301
+
1302
+ // src/tools/app-session.tool.ts
1303
+ import { remote as remote2 } from "webdriverio";
1304
+ import { z as z13 } from "zod";
1305
+
1306
+ // src/config/appium.config.ts
1307
+ function getAppiumServerConfig(overrides) {
1308
+ return {
1309
+ hostname: overrides?.hostname || process.env.APPIUM_URL || "127.0.0.1",
1310
+ port: overrides?.port || Number(process.env.APPIUM_URL_PORT) || 4723,
1311
+ path: overrides?.path || process.env.APPIUM_PATH || "/"
1312
+ };
1313
+ }
1314
+ function buildIOSCapabilities(appPath, options) {
1315
+ const capabilities = {
1316
+ platformName: "iOS",
1317
+ "appium:platformVersion": options.platformVersion,
1318
+ "appium:deviceName": options.deviceName,
1319
+ "appium:automationName": options.automationName || "XCUITest"
1320
+ };
1321
+ if (appPath) {
1322
+ capabilities["appium:app"] = appPath;
1323
+ }
1324
+ if (options.udid) {
1325
+ capabilities["appium:udid"] = options.udid;
1326
+ }
1327
+ if (options.noReset !== void 0) {
1328
+ capabilities["appium:noReset"] = options.noReset;
1329
+ }
1330
+ if (options.fullReset !== void 0) {
1331
+ capabilities["appium:fullReset"] = options.fullReset;
1332
+ }
1333
+ capabilities["appium:autoGrantPermissions"] = options.autoGrantPermissions ?? true;
1334
+ capabilities["appium:autoAcceptAlerts"] = options.autoAcceptAlerts ?? true;
1335
+ if (options.autoDismissAlerts !== void 0) {
1336
+ capabilities["appium:autoDismissAlerts"] = options.autoDismissAlerts;
1337
+ capabilities["appium:autoAcceptAlerts"] = void 0;
1338
+ }
1339
+ for (const [key, value] of Object.entries(options)) {
1340
+ if (!["deviceName", "platformVersion", "automationName", "autoAcceptAlerts", "autoDismissAlerts", "udid", "noReset", "fullReset"].includes(
1341
+ key
1342
+ )) {
1343
+ capabilities[`appium:${key}`] = value;
1344
+ }
1345
+ }
1346
+ return capabilities;
1347
+ }
1348
+ function buildAndroidCapabilities(appPath, options) {
1349
+ const capabilities = {
1350
+ platformName: "Android",
1351
+ "appium:platformVersion": options.platformVersion,
1352
+ "appium:deviceName": options.deviceName,
1353
+ "appium:automationName": options.automationName || "UiAutomator2"
1354
+ };
1355
+ if (appPath) {
1356
+ capabilities["appium:app"] = appPath;
1357
+ }
1358
+ if (options.noReset !== void 0) {
1359
+ capabilities["appium:noReset"] = options.noReset;
1360
+ }
1361
+ if (options.fullReset !== void 0) {
1362
+ capabilities["appium:fullReset"] = options.fullReset;
1363
+ }
1364
+ capabilities["appium:autoGrantPermissions"] = options.autoGrantPermissions ?? true;
1365
+ capabilities["appium:autoAcceptAlerts"] = options.autoAcceptAlerts ?? true;
1366
+ if (options.autoDismissAlerts !== void 0) {
1367
+ capabilities["appium:autoDismissAlerts"] = options.autoDismissAlerts;
1368
+ capabilities["appium:autoAcceptAlerts"] = void 0;
1369
+ }
1370
+ if (options.appWaitActivity) {
1371
+ capabilities["appium:appWaitActivity"] = options.appWaitActivity;
1372
+ }
1373
+ for (const [key, value] of Object.entries(options)) {
1374
+ if (!["deviceName", "platformVersion", "automationName", "autoGrantPermissions", "appWaitActivity", "noReset", "fullReset"].includes(
1375
+ key
1376
+ )) {
1377
+ capabilities[`appium:${key}`] = value;
1378
+ }
1379
+ }
1380
+ return capabilities;
1381
+ }
1382
+
1383
+ // src/tools/app-session.tool.ts
1384
+ var startAppToolArguments = {
1385
+ platform: z13.enum(["iOS", "Android"]).describe("Mobile platform"),
1386
+ appPath: z13.string().optional().describe("Path to the app file (.app/.apk/.ipa). Required unless noReset=true (connecting to already-running app)"),
1387
+ deviceName: z13.string().describe("Device/emulator/simulator name"),
1388
+ platformVersion: z13.string().optional().describe('OS version (e.g., "17.0", "14")'),
1389
+ automationName: z13.enum(["XCUITest", "UiAutomator2", "Espresso"]).optional().describe("Automation driver name"),
1390
+ appiumHost: z13.string().optional().describe("Appium server hostname (overrides APPIUM_URL env var)"),
1391
+ appiumPort: z13.number().optional().describe("Appium server port (overrides APPIUM_URL_PORT env var)"),
1392
+ appiumPath: z13.string().optional().describe("Appium server path (overrides APPIUM_PATH env var)"),
1393
+ autoGrantPermissions: z13.boolean().optional().describe("Auto-grant app permissions (default: true)"),
1394
+ autoAcceptAlerts: z13.boolean().optional().describe("Auto-accept alerts (default: true)"),
1395
+ autoDismissAlerts: z13.boolean().optional().describe('Auto-dismiss alerts (default: false, will override "autoAcceptAlerts" to undefined if set)'),
1396
+ appWaitActivity: z13.string().optional().describe("Activity to wait for on launch (Android only)"),
1397
+ udid: z13.string().optional().describe('Unique Device Identifier for iOS real device testing (e.g., "00008030-001234567890002E")'),
1398
+ noReset: z13.boolean().optional().describe("Do not reset app state before session (preserves app data). Default: false"),
1399
+ fullReset: z13.boolean().optional().describe("Uninstall app before/after session. Default: true. Set to false with noReset=true to preserve app state completely")
1400
+ };
1401
+ var getState = () => {
1402
+ const sharedState = getBrowser.__state;
1403
+ if (!sharedState) {
1404
+ throw new Error("Browser state not initialized");
1405
+ }
1406
+ return sharedState;
1407
+ };
1408
+ var startAppTool = async (args) => {
1409
+ try {
1410
+ const {
1411
+ platform,
1412
+ appPath,
1413
+ deviceName,
1414
+ platformVersion,
1415
+ automationName,
1416
+ appiumHost,
1417
+ appiumPort,
1418
+ appiumPath,
1419
+ autoGrantPermissions = true,
1420
+ autoAcceptAlerts,
1421
+ autoDismissAlerts,
1422
+ appWaitActivity,
1423
+ udid,
1424
+ noReset,
1425
+ fullReset
1426
+ } = args;
1427
+ if (!appPath && noReset !== true) {
1428
+ return {
1429
+ content: [{
1430
+ type: "text",
1431
+ text: 'Error: Either "appPath" must be provided to install an app, or "noReset: true" must be set to connect to an already-running app.'
1432
+ }]
1433
+ };
1434
+ }
1435
+ const serverConfig = getAppiumServerConfig({
1436
+ hostname: appiumHost,
1437
+ port: appiumPort,
1438
+ path: appiumPath
1439
+ });
1440
+ const capabilities = platform === "iOS" ? buildIOSCapabilities(appPath, {
1441
+ deviceName,
1442
+ platformVersion,
1443
+ automationName: automationName || "XCUITest",
1444
+ autoGrantPermissions,
1445
+ autoAcceptAlerts,
1446
+ autoDismissAlerts,
1447
+ udid,
1448
+ noReset,
1449
+ fullReset
1450
+ }) : buildAndroidCapabilities(appPath, {
1451
+ deviceName,
1452
+ platformVersion,
1453
+ automationName: automationName || "UiAutomator2",
1454
+ autoGrantPermissions,
1455
+ autoAcceptAlerts,
1456
+ autoDismissAlerts,
1457
+ appWaitActivity,
1458
+ noReset,
1459
+ fullReset
1460
+ });
1461
+ const browser = await remote2({
1462
+ protocol: "http",
1463
+ hostname: serverConfig.hostname,
1464
+ port: serverConfig.port,
1465
+ path: serverConfig.path,
1466
+ capabilities
1467
+ });
1468
+ const { sessionId } = browser;
1469
+ const shouldAutoDetach = noReset === true || !appPath;
1470
+ const state2 = getState();
1471
+ state2.browsers.set(sessionId, browser);
1472
+ state2.currentSession = sessionId;
1473
+ state2.sessionMetadata.set(sessionId, {
1474
+ type: platform.toLowerCase(),
1475
+ capabilities,
1476
+ isAttached: shouldAutoDetach
1477
+ });
1478
+ const appInfo = appPath ? `
1479
+ App: ${appPath}` : "\nApp: (connected to running app)";
1480
+ const detachNote = shouldAutoDetach ? "\n\n(Auto-detach enabled: session will be preserved on close. Use close_session({ detach: false }) to force terminate.)" : "";
1481
+ return {
1482
+ content: [
1483
+ {
1484
+ type: "text",
1485
+ text: `${platform} app session started with sessionId: ${sessionId}
1486
+ Device: ${deviceName}${appInfo}
1487
+ Appium Server: ${serverConfig.hostname}:${serverConfig.port}${serverConfig.path}${detachNote}`
1488
+ }
1489
+ ]
1490
+ };
1491
+ } catch (e) {
1492
+ return {
1493
+ content: [{ type: "text", text: `Error starting app session: ${e}` }]
1494
+ };
1495
+ }
1496
+ };
1497
+
1498
+ // src/tools/gestures.tool.ts
1499
+ import { z as z14 } from "zod";
1500
+ var tapElementToolArguments = {
1501
+ selector: z14.string().optional().describe("Element selector (CSS, XPath, accessibility ID, or UiAutomator)"),
1502
+ x: z14.number().optional().describe("X coordinate for tap (if no selector provided)"),
1503
+ y: z14.number().optional().describe("Y coordinate for tap (if no selector provided)")
1504
+ };
1505
+ var tapElementTool = async (args) => {
1506
+ try {
1507
+ const browser = getBrowser();
1508
+ const { selector, x, y } = args;
1509
+ if (selector) {
1510
+ const element = await browser.$(selector);
1511
+ await element.tap();
1512
+ return {
1513
+ content: [{ type: "text", text: `Tapped element: ${selector}` }]
1514
+ };
1515
+ } else if (x !== void 0 && y !== void 0) {
1516
+ await browser.touchAction({
1517
+ action: "tap",
1518
+ x,
1519
+ y
1520
+ });
1521
+ return {
1522
+ content: [{ type: "text", text: `Tapped at coordinates: (${x}, ${y})` }]
1523
+ };
1524
+ }
1525
+ return {
1526
+ content: [{ type: "text", text: "Error: Must provide either selector or x,y coordinates" }]
1527
+ };
1528
+ } catch (e) {
1529
+ return {
1530
+ content: [{ type: "text", text: `Error tapping element: ${e}` }]
1531
+ };
1532
+ }
1533
+ };
1534
+ var swipeToolArguments = {
1535
+ direction: z14.enum(["up", "down", "left", "right"]).describe("Swipe direction"),
1536
+ duration: z14.number().min(100).max(5e3).optional().describe("Swipe duration in milliseconds (default: 500)"),
1537
+ startX: z14.number().optional().describe("Start X coordinate (optional, uses screen center)"),
1538
+ startY: z14.number().optional().describe("Start Y coordinate (optional, uses screen center)"),
1539
+ distance: z14.number().optional().describe("Swipe distance in pixels (optional, uses percentage of screen)")
1540
+ };
1541
+ var swipeTool = async (args) => {
1542
+ try {
1543
+ const browser = getBrowser();
1544
+ const { direction, duration = 500, startX, startY, distance } = args;
1545
+ const windowSize = await browser.getWindowSize();
1546
+ const screenWidth = windowSize.width;
1547
+ const screenHeight = windowSize.height;
1548
+ const centerX = startX ?? screenWidth / 2;
1549
+ const centerY = startY ?? screenHeight / 2;
1550
+ const swipeDistance = distance ?? Math.min(screenWidth, screenHeight) * 0.5;
1551
+ let endX = centerX;
1552
+ let endY = centerY;
1553
+ switch (direction) {
1554
+ case "up":
1555
+ endY = centerY - swipeDistance;
1556
+ break;
1557
+ case "down":
1558
+ endY = centerY + swipeDistance;
1559
+ break;
1560
+ case "left":
1561
+ endX = centerX - swipeDistance;
1562
+ break;
1563
+ case "right":
1564
+ endX = centerX + swipeDistance;
1565
+ break;
1566
+ }
1567
+ await browser.touchPerform([
1568
+ { action: "press", options: { x: centerX, y: centerY } },
1569
+ { action: "wait", options: { ms: duration } },
1570
+ { action: "moveTo", options: { x: endX, y: endY } },
1571
+ { action: "release", options: {} }
1572
+ ]);
1573
+ return {
1574
+ content: [
1575
+ {
1576
+ type: "text",
1577
+ text: `Swiped ${direction} from (${Math.round(centerX)}, ${Math.round(centerY)}) to (${Math.round(endX)}, ${Math.round(endY)}) over ${duration}ms`
1578
+ }
1579
+ ]
1580
+ };
1581
+ } catch (e) {
1582
+ return {
1583
+ content: [{ type: "text", text: `Error swiping: ${e}` }]
1584
+ };
1585
+ }
1586
+ };
1587
+ var longPressToolArguments = {
1588
+ selector: z14.string().optional().describe("Element selector (CSS, XPath, accessibility ID, or UiAutomator)"),
1589
+ x: z14.number().optional().describe("X coordinate for long press (if no selector provided)"),
1590
+ y: z14.number().optional().describe("Y coordinate for long press (if no selector provided)"),
1591
+ duration: z14.number().min(500).max(1e4).optional().describe("Long press duration in milliseconds (default: 1000)")
1592
+ };
1593
+ var longPressTool = async (args) => {
1594
+ try {
1595
+ const browser = getBrowser();
1596
+ const { selector, x, y, duration = 1e3 } = args;
1597
+ if (selector) {
1598
+ const element = await browser.$(selector);
1599
+ await element.touchAction([
1600
+ { action: "longPress" },
1601
+ { action: "wait", ms: duration },
1602
+ { action: "release" }
1603
+ ]);
1604
+ return {
1605
+ content: [{ type: "text", text: `Long pressed element: ${selector} for ${duration}ms` }]
1606
+ };
1607
+ } else if (x !== void 0 && y !== void 0) {
1608
+ await browser.touchPerform([
1609
+ { action: "press", options: { x, y } },
1610
+ { action: "wait", options: { ms: duration } },
1611
+ { action: "release", options: {} }
1612
+ ]);
1613
+ return {
1614
+ content: [{ type: "text", text: `Long pressed at coordinates: (${x}, ${y}) for ${duration}ms` }]
1615
+ };
1616
+ }
1617
+ return {
1618
+ content: [{ type: "text", text: "Error: Must provide either selector or x,y coordinates" }]
1619
+ };
1620
+ } catch (e) {
1621
+ return {
1622
+ content: [{ type: "text", text: `Error long pressing: ${e}` }]
1623
+ };
1624
+ }
1625
+ };
1626
+ var dragAndDropToolArguments = {
1627
+ fromSelector: z14.string().optional().describe("Source element selector"),
1628
+ fromX: z14.number().optional().describe("Source X coordinate"),
1629
+ fromY: z14.number().optional().describe("Source Y coordinate"),
1630
+ toSelector: z14.string().optional().describe("Target element selector"),
1631
+ toX: z14.number().optional().describe("Target X coordinate"),
1632
+ toY: z14.number().optional().describe("Target Y coordinate"),
1633
+ duration: z14.number().min(100).max(5e3).optional().describe("Drag duration in milliseconds (default: 500)")
1634
+ };
1635
+ var dragAndDropTool = async (args) => {
1636
+ try {
1637
+ const browser = getBrowser();
1638
+ const { fromSelector, fromX, fromY, toSelector, toX, toY, duration = 500 } = args;
1639
+ let startX;
1640
+ let startY;
1641
+ let endX;
1642
+ let endY;
1643
+ if (fromSelector) {
1644
+ const element = await browser.$(fromSelector);
1645
+ const location = await element.getLocation();
1646
+ const size = await element.getSize();
1647
+ startX = location.x + size.width / 2;
1648
+ startY = location.y + size.height / 2;
1649
+ } else if (fromX !== void 0 && fromY !== void 0) {
1650
+ startX = fromX;
1651
+ startY = fromY;
1652
+ } else {
1653
+ return {
1654
+ content: [{ type: "text", text: "Error: Must provide either fromSelector or fromX,fromY coordinates" }]
1655
+ };
1656
+ }
1657
+ if (toSelector) {
1658
+ const element = await browser.$(toSelector);
1659
+ const location = await element.getLocation();
1660
+ const size = await element.getSize();
1661
+ endX = location.x + size.width / 2;
1662
+ endY = location.y + size.height / 2;
1663
+ } else if (toX !== void 0 && toY !== void 0) {
1664
+ endX = toX;
1665
+ endY = toY;
1666
+ } else {
1667
+ return {
1668
+ content: [{ type: "text", text: "Error: Must provide either toSelector or toX,toY coordinates" }]
1669
+ };
1670
+ }
1671
+ await browser.touchPerform([
1672
+ { action: "press", options: { x: startX, y: startY } },
1673
+ { action: "wait", options: { ms: duration } },
1674
+ { action: "moveTo", options: { x: endX, y: endY } },
1675
+ { action: "release", options: {} }
1676
+ ]);
1677
+ return {
1678
+ content: [
1679
+ {
1680
+ type: "text",
1681
+ text: `Dragged from (${Math.round(startX)}, ${Math.round(startY)}) to (${Math.round(endX)}, ${Math.round(endY)}) over ${duration}ms`
1682
+ }
1683
+ ]
1684
+ };
1685
+ } catch (e) {
1686
+ return {
1687
+ content: [{ type: "text", text: `Error dragging and dropping: ${e}` }]
1688
+ };
1689
+ }
1690
+ };
1691
+
1692
+ // src/tools/app-actions.tool.ts
1693
+ import { z as z15 } from "zod";
1694
+ var getAppStateToolArguments = {
1695
+ bundleId: z15.string().describe("App bundle ID (e.g., com.example.app)")
1696
+ };
1697
+ var getAppStateTool = async (args) => {
1698
+ try {
1699
+ const browser = getBrowser();
1700
+ const { bundleId } = args;
1701
+ const appIdentifier = browser.isAndroid ? { appId: bundleId } : { bundleId };
1702
+ const state2 = await browser.execute("mobile: queryAppState", appIdentifier);
1703
+ const stateMap = {
1704
+ 0: "not installed",
1705
+ 1: "not running",
1706
+ 2: "running in background (suspended)",
1707
+ 3: "running in background",
1708
+ 4: "running in foreground"
1709
+ };
1710
+ return {
1711
+ content: [
1712
+ {
1713
+ type: "text",
1714
+ text: `App state for ${bundleId}: ${stateMap[state2] || "unknown: " + state2}`
1715
+ }
1716
+ ]
1717
+ };
1718
+ } catch (e) {
1719
+ return {
1720
+ content: [{ type: "text", text: `Error getting app state: ${e}` }]
1721
+ };
1722
+ }
1723
+ };
1724
+ var activateAppToolArguments = {
1725
+ bundleId: z15.string().describe("App bundle ID to activate (e.g., com.example.app)")
1726
+ };
1727
+ var activateAppTool = async (args) => {
1728
+ try {
1729
+ const browser = getBrowser();
1730
+ const { bundleId } = args;
1731
+ const appIdentifier = browser.isAndroid ? { appId: bundleId } : { bundleId };
1732
+ await browser.execute("mobile: activateApp", appIdentifier);
1733
+ return {
1734
+ content: [{ type: "text", text: `Activated app: ${bundleId}` }]
1735
+ };
1736
+ } catch (e) {
1737
+ return {
1738
+ content: [{ type: "text", text: `Error activating app: ${e}` }]
1739
+ };
1740
+ }
1741
+ };
1742
+ var terminateAppToolArguments = {
1743
+ bundleId: z15.string().describe("App bundle ID to terminate (e.g., com.example.app)")
1744
+ };
1745
+ var terminateAppTool = async (args) => {
1746
+ try {
1747
+ const browser = getBrowser();
1748
+ const { bundleId } = args;
1749
+ const appIdentifier = browser.isAndroid ? { appId: bundleId } : { bundleId };
1750
+ await browser.execute("mobile: terminateApp", appIdentifier);
1751
+ return {
1752
+ content: [{ type: "text", text: `Terminated app: ${bundleId}` }]
1753
+ };
1754
+ } catch (e) {
1755
+ return {
1756
+ content: [{ type: "text", text: `Error terminating app: ${e}` }]
1757
+ };
1758
+ }
1759
+ };
1760
+
1761
+ // src/tools/context.tool.ts
1762
+ import { z as z16 } from "zod";
1763
+ var getContextsTool = async () => {
1764
+ try {
1765
+ const browser = getBrowser();
1766
+ const contexts = await browser.getContexts();
1767
+ return {
1768
+ content: [
1769
+ {
1770
+ type: "text",
1771
+ text: `Available contexts:
1772
+ ${contexts.map((ctx, idx) => `${idx + 1}. ${ctx}`).join("\n")}`
1773
+ }
1774
+ ]
1775
+ };
1776
+ } catch (e) {
1777
+ return {
1778
+ content: [{ type: "text", text: `Error getting contexts: ${e}` }]
1779
+ };
1780
+ }
1781
+ };
1782
+ var getCurrentContextTool = async () => {
1783
+ try {
1784
+ const browser = getBrowser();
1785
+ const currentContext = await browser.getContext();
1786
+ return {
1787
+ content: [{ type: "text", text: `Current context: ${JSON.stringify(currentContext)}` }]
1788
+ };
1789
+ } catch (e) {
1790
+ return {
1791
+ content: [{ type: "text", text: `Error getting current context: ${e}` }]
1792
+ };
1793
+ }
1794
+ };
1795
+ var switchContextToolArguments = {
1796
+ context: z16.string().describe(
1797
+ 'Context name to switch to (e.g., "NATIVE_APP", "WEBVIEW_com.example.app", or use index from get_contexts)'
1798
+ )
1799
+ };
1800
+ var switchContextTool = async (args) => {
1801
+ try {
1802
+ const browser = getBrowser();
1803
+ const { context } = args;
1804
+ let targetContext = context;
1805
+ if (/^\d+$/.test(context)) {
1806
+ const contexts = await browser.getContexts();
1807
+ const index = parseInt(context, 10) - 1;
1808
+ if (index >= 0 && index < contexts.length) {
1809
+ targetContext = contexts[index];
1810
+ } else {
1811
+ return {
1812
+ content: [
1813
+ {
1814
+ type: "text",
1815
+ text: `Error: Invalid context index ${context}. Available contexts: ${contexts.length}`
1816
+ }
1817
+ ]
1818
+ };
1819
+ }
1820
+ }
1821
+ await browser.switchContext(targetContext);
1822
+ return {
1823
+ content: [{ type: "text", text: `Switched to context: ${targetContext}` }]
1824
+ };
1825
+ } catch (e) {
1826
+ return {
1827
+ content: [{ type: "text", text: `Error switching context: ${e}` }]
1828
+ };
1829
+ }
1830
+ };
1831
+
1832
+ // src/tools/device.tool.ts
1833
+ import { z as z17 } from "zod";
1834
+ var getDeviceInfoTool = async () => {
1835
+ try {
1836
+ const browser = getBrowser();
1837
+ const capabilities = browser.capabilities;
1838
+ const windowSize = await browser.getWindowSize();
1839
+ const info = {
1840
+ platformName: capabilities.platformName,
1841
+ platformVersion: capabilities["appium:platformVersion"],
1842
+ deviceName: capabilities["appium:deviceName"],
1843
+ automationName: capabilities["appium:automationName"],
1844
+ screenSize: `${windowSize.width}x${windowSize.height}`
1845
+ };
1846
+ return {
1847
+ content: [
1848
+ {
1849
+ type: "text",
1850
+ text: `Device Info:
1851
+ ${Object.entries(info).map(([key, value]) => ` ${key}: ${value}`).join("\n")}`
1852
+ }
1853
+ ]
1854
+ };
1855
+ } catch (e) {
1856
+ return {
1857
+ content: [{ type: "text", text: `Error getting device info: ${e}` }]
1858
+ };
1859
+ }
1860
+ };
1861
+ var rotateDeviceToolArguments = {
1862
+ orientation: z17.enum(["PORTRAIT", "LANDSCAPE"]).describe("Device orientation")
1863
+ };
1864
+ var rotateDeviceTool = async (args) => {
1865
+ try {
1866
+ const browser = getBrowser();
1867
+ const { orientation } = args;
1868
+ await browser.setOrientation(orientation);
1869
+ return {
1870
+ content: [{ type: "text", text: `Device rotated to: ${orientation}` }]
1871
+ };
1872
+ } catch (e) {
1873
+ return {
1874
+ content: [{ type: "text", text: `Error rotating device: ${e}` }]
1875
+ };
1876
+ }
1877
+ };
1878
+ var getOrientationTool = async () => {
1879
+ try {
1880
+ const browser = getBrowser();
1881
+ const orientation = await browser.getOrientation();
1882
+ return {
1883
+ content: [{ type: "text", text: `Current orientation: ${orientation}` }]
1884
+ };
1885
+ } catch (e) {
1886
+ return {
1887
+ content: [{ type: "text", text: `Error getting orientation: ${e}` }]
1888
+ };
1889
+ }
1890
+ };
1891
+ var lockDeviceTool = async () => {
1892
+ try {
1893
+ const browser = getBrowser();
1894
+ await browser.lock();
1895
+ return {
1896
+ content: [{ type: "text", text: "Device locked" }]
1897
+ };
1898
+ } catch (e) {
1899
+ return {
1900
+ content: [{ type: "text", text: `Error locking device: ${e}` }]
1901
+ };
1902
+ }
1903
+ };
1904
+ var unlockDeviceTool = async () => {
1905
+ try {
1906
+ const browser = getBrowser();
1907
+ await browser.unlock();
1908
+ return {
1909
+ content: [{ type: "text", text: "Device unlocked" }]
1910
+ };
1911
+ } catch (e) {
1912
+ return {
1913
+ content: [{ type: "text", text: `Error unlocking device: ${e}` }]
1914
+ };
1915
+ }
1916
+ };
1917
+ var isDeviceLockedTool = async () => {
1918
+ try {
1919
+ const browser = getBrowser();
1920
+ const isLocked = await browser.isLocked();
1921
+ return {
1922
+ content: [{ type: "text", text: `Device is ${isLocked ? "locked" : "unlocked"}` }]
1923
+ };
1924
+ } catch (e) {
1925
+ return {
1926
+ content: [{ type: "text", text: `Error checking lock status: ${e}` }]
1927
+ };
1928
+ }
1929
+ };
1930
+ var shakeDeviceTool = async () => {
1931
+ try {
1932
+ const browser = getBrowser();
1933
+ await browser.shake();
1934
+ return {
1935
+ content: [{ type: "text", text: "Device shaken" }]
1936
+ };
1937
+ } catch (e) {
1938
+ return {
1939
+ content: [{ type: "text", text: `Error shaking device: ${e}` }]
1940
+ };
1941
+ }
1942
+ };
1943
+ var sendKeysToolArguments = {
1944
+ keys: z17.array(z17.string()).describe('Array of keys to send (e.g., ["h", "e", "l", "l", "o"])')
1945
+ };
1946
+ var sendKeysTool = async (args) => {
1947
+ try {
1948
+ const browser = getBrowser();
1949
+ const { keys } = args;
1950
+ await browser.sendKeys(keys);
1951
+ return {
1952
+ content: [{ type: "text", text: `Sent keys: ${keys.join("")}` }]
1953
+ };
1954
+ } catch (e) {
1955
+ return {
1956
+ content: [{ type: "text", text: `Error sending keys: ${e}` }]
1957
+ };
1958
+ }
1959
+ };
1960
+ var pressKeyCodeToolArguments = {
1961
+ keyCode: z17.number().describe("Android key code (e.g., 4 for BACK, 3 for HOME)")
1962
+ };
1963
+ var pressKeyCodeTool = async (args) => {
1964
+ try {
1965
+ const browser = getBrowser();
1966
+ const { keyCode } = args;
1967
+ await browser.pressKeyCode(keyCode);
1968
+ return {
1969
+ content: [{ type: "text", text: `Pressed key code: ${keyCode}` }]
1970
+ };
1971
+ } catch (e) {
1972
+ return {
1973
+ content: [{ type: "text", text: `Error pressing key code: ${e}` }]
1974
+ };
1975
+ }
1976
+ };
1977
+ var hideKeyboardTool = async () => {
1978
+ try {
1979
+ const browser = getBrowser();
1980
+ await browser.hideKeyboard();
1981
+ return {
1982
+ content: [{ type: "text", text: "Keyboard hidden" }]
1983
+ };
1984
+ } catch (e) {
1985
+ return {
1986
+ content: [{ type: "text", text: `Error hiding keyboard: ${e}` }]
1987
+ };
1988
+ }
1989
+ };
1990
+ var isKeyboardShownTool = async () => {
1991
+ try {
1992
+ const browser = getBrowser();
1993
+ const isShown = await browser.isKeyboardShown();
1994
+ return {
1995
+ content: [{ type: "text", text: `Keyboard is ${isShown ? "shown" : "hidden"}` }]
1996
+ };
1997
+ } catch (e) {
1998
+ return {
1999
+ content: [{ type: "text", text: `Error checking keyboard status: ${e}` }]
2000
+ };
2001
+ }
2002
+ };
2003
+ var openNotificationsTool = async () => {
2004
+ try {
2005
+ const browser = getBrowser();
2006
+ await browser.openNotifications();
2007
+ return {
2008
+ content: [{ type: "text", text: "Opened notifications panel" }]
2009
+ };
2010
+ } catch (e) {
2011
+ return {
2012
+ content: [{ type: "text", text: `Error opening notifications: ${e}` }]
2013
+ };
2014
+ }
2015
+ };
2016
+ var getGeolocationTool = async () => {
2017
+ try {
2018
+ const browser = getBrowser();
2019
+ const location = await browser.getGeoLocation();
2020
+ return {
2021
+ content: [
2022
+ {
2023
+ type: "text",
2024
+ text: `Location:
2025
+ Latitude: ${location.latitude}
2026
+ Longitude: ${location.longitude}
2027
+ Altitude: ${location.altitude || "N/A"}`
2028
+ }
2029
+ ]
2030
+ };
2031
+ } catch (e) {
2032
+ return {
2033
+ content: [{ type: "text", text: `Error getting geolocation: ${e}` }]
2034
+ };
2035
+ }
2036
+ };
2037
+ var setGeolocationToolArguments = {
2038
+ latitude: z17.number().min(-90).max(90).describe("Latitude coordinate"),
2039
+ longitude: z17.number().min(-180).max(180).describe("Longitude coordinate"),
2040
+ altitude: z17.number().optional().describe("Altitude in meters (optional)")
2041
+ };
2042
+ var setGeolocationTool = async (args) => {
2043
+ try {
2044
+ const browser = getBrowser();
2045
+ const { latitude, longitude, altitude } = args;
2046
+ await browser.setGeoLocation({ latitude, longitude, altitude });
2047
+ return {
2048
+ content: [
2049
+ {
2050
+ type: "text",
2051
+ text: `Geolocation set to:
2052
+ Latitude: ${latitude}
2053
+ Longitude: ${longitude}${altitude ? `
2054
+ Altitude: ${altitude}m` : ""}`
2055
+ }
2056
+ ]
2057
+ };
2058
+ } catch (e) {
2059
+ return {
2060
+ content: [{ type: "text", text: `Error setting geolocation: ${e}` }]
2061
+ };
2062
+ }
2063
+ };
2064
+
2065
+ // src/server.ts
2066
+ console.log = (...args) => console.error("[LOG]", ...args);
2067
+ console.info = (...args) => console.error("[INFO]", ...args);
2068
+ console.warn = (...args) => console.error("[WARN]", ...args);
2069
+ console.debug = (...args) => console.error("[DEBUG]", ...args);
2070
+ var server = new McpServer({
2071
+ name: "MCP WebdriverIO",
2072
+ version: "1.4.0"
2073
+ }, {
2074
+ capabilities: {
2075
+ resources: {},
2076
+ tools: {}
2077
+ }
2078
+ });
2079
+ server.tool("start_browser", "starts a browser session and sets it to the current state", startBrowserToolArguments, startBrowserTool);
2080
+ server.tool("start_app_session", "starts a mobile app session (iOS/Android) via Appium", startAppToolArguments, startAppTool);
2081
+ server.tool("close_session", "closes or detaches from the current browser or app session", closeSessionToolArguments, closeSessionTool);
2082
+ server.tool("navigate", "navigates to a URL", navigateToolArguments, navigateTool);
2083
+ server.tool("get_visible_elements", "get a list of visible (in viewport & displayed) elements on the page, must prefer this to take_screenshot for interactions", getVisibleElementsToolArguments, getVisibleElementsTool);
2084
+ server.tool("get_accessibility", "gets accessibility tree snapshot with semantic information about page elements (roles, names, states)", {}, getAccessibilityTreeTool);
2085
+ server.tool("scroll_down", "scrolls the page down by specified pixels", scrollDownToolArguments, scrollDownTool);
2086
+ server.tool("scroll_up", "scrolls the page up by specified pixels", scrollUpToolArguments, scrollUpTool);
2087
+ server.tool("find_element", "finds an element", findElementToolArguments, findElementTool);
2088
+ server.tool("click_element", "clicks an element", clickToolArguments, clickTool);
2089
+ server.tool("click_via_text", "clicks an element", clickToolArguments, clickToolViaText);
2090
+ server.tool("set_value", "set value to an element, aka typing", setValueToolArguments, setValueTool);
2091
+ server.tool("get_element_text", "gets the text content of an element", getElementTextToolArguments, getElementTextTool);
2092
+ server.tool("is_displayed", "checks if an element is displayed", isDisplayedToolArguments, isDisplayedTool);
2093
+ server.tool("take_screenshot", "captures a screenshot of the current page", takeScreenshotToolArguments, takeScreenshotTool);
2094
+ server.tool("get_cookies", "gets all cookies or a specific cookie by name", getCookiesToolArguments, getCookiesTool);
2095
+ server.tool("set_cookie", "sets a cookie with specified name, value, and optional attributes", setCookieToolArguments, setCookieTool);
2096
+ server.tool("delete_cookies", "deletes all cookies or a specific cookie by name", deleteCookiesToolArguments, deleteCookiesTool);
2097
+ server.tool("tap_element", "taps an element by selector or coordinates (mobile)", tapElementToolArguments, tapElementTool);
2098
+ server.tool("swipe", "performs a swipe gesture in specified direction (mobile)", swipeToolArguments, swipeTool);
2099
+ server.tool("long_press", "performs a long press on element or coordinates (mobile)", longPressToolArguments, longPressTool);
2100
+ server.tool("drag_and_drop", "drags from one location to another (mobile)", dragAndDropToolArguments, dragAndDropTool);
2101
+ server.tool("get_app_state", "gets the state of an app (not installed, not running, background, foreground)", getAppStateToolArguments, getAppStateTool);
2102
+ server.tool("activate_app", "activates/brings an app to foreground", activateAppToolArguments, activateAppTool);
2103
+ server.tool("terminate_app", "terminates a running app", terminateAppToolArguments, terminateAppTool);
2104
+ server.tool("get_contexts", "lists available contexts (NATIVE_APP, WEBVIEW)", {}, getContextsTool);
2105
+ server.tool("get_current_context", "shows the currently active context", {}, getCurrentContextTool);
2106
+ server.tool("switch_context", "switches between native and webview contexts", switchContextToolArguments, switchContextTool);
2107
+ server.tool("get_device_info", "gets device information (platform, version, screen size)", {}, getDeviceInfoTool);
2108
+ server.tool("rotate_device", "rotates device to portrait or landscape orientation", rotateDeviceToolArguments, rotateDeviceTool);
2109
+ server.tool("get_orientation", "gets current device orientation", {}, getOrientationTool);
2110
+ server.tool("lock_device", "locks the device screen", {}, lockDeviceTool);
2111
+ server.tool("unlock_device", "unlocks the device screen", {}, unlockDeviceTool);
2112
+ server.tool("is_device_locked", "checks if device is locked", {}, isDeviceLockedTool);
2113
+ server.tool("shake_device", "shakes the device (iOS only)", {}, shakeDeviceTool);
2114
+ server.tool("send_keys", "sends keys to the app (Android only)", sendKeysToolArguments, sendKeysTool);
2115
+ server.tool("press_key_code", "presses an Android key code (Android only)", pressKeyCodeToolArguments, pressKeyCodeTool);
2116
+ server.tool("hide_keyboard", "hides the on-screen keyboard", {}, hideKeyboardTool);
2117
+ server.tool("is_keyboard_shown", "checks if keyboard is visible", {}, isKeyboardShownTool);
2118
+ server.tool("open_notifications", "opens the notifications panel (Android only)", {}, openNotificationsTool);
2119
+ server.tool("get_geolocation", "gets current device geolocation", {}, getGeolocationTool);
2120
+ server.tool("set_geolocation", "sets device geolocation (latitude, longitude, altitude)", setGeolocationToolArguments, setGeolocationTool);
2121
+ async function main() {
2122
+ const transport = new StdioServerTransport();
2123
+ await server.connect(transport);
2124
+ console.error("WebdriverIO MCP Server running on stdio");
2125
+ }
2126
+ main().catch((error) => {
2127
+ console.error("Fatal error in main():", error);
2128
+ process.exit(1);
2129
+ });
2130
+ //# sourceMappingURL=server.js.map