@wdio/mcp 1.6.0 → 2.0.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 CHANGED
@@ -12,8 +12,9 @@ var startBrowserToolDefinition = {
12
12
  description: "starts a browser session and sets it to the current state",
13
13
  inputSchema: {
14
14
  headless: z.boolean().optional(),
15
- windowWidth: z.number().min(400).max(3840).optional(),
16
- windowHeight: z.number().min(400).max(2160).optional()
15
+ windowWidth: z.number().min(400).max(3840).optional().default(1920),
16
+ windowHeight: z.number().min(400).max(2160).optional().default(1080),
17
+ navigationUrl: z.string().optional().describe("URL to navigate to after starting the browser")
17
18
  }
18
19
  };
19
20
  var closeSessionToolDefinition = {
@@ -36,7 +37,12 @@ var getBrowser = () => {
36
37
  return browser;
37
38
  };
38
39
  getBrowser.__state = state;
39
- var startBrowserTool = async ({ headless = false, windowWidth = 1280, windowHeight = 1080 }) => {
40
+ var startBrowserTool = async ({
41
+ headless = false,
42
+ windowWidth = 1920,
43
+ windowHeight = 1080,
44
+ navigationUrl
45
+ }) => {
40
46
  const chromeArgs = [
41
47
  `--window-size=${windowWidth},${windowHeight}`,
42
48
  "--no-sandbox",
@@ -70,11 +76,15 @@ var startBrowserTool = async ({ headless = false, windowWidth = 1280, windowHeig
70
76
  capabilities: browser.capabilities,
71
77
  isAttached: false
72
78
  });
79
+ if (navigationUrl) {
80
+ await browser.url(navigationUrl);
81
+ }
73
82
  const modeText = headless ? "headless" : "headed";
83
+ const urlText = navigationUrl ? ` and navigated to ${navigationUrl}` : "";
74
84
  return {
75
85
  content: [{
76
86
  type: "text",
77
- text: `Browser started in ${modeText} mode with sessionId: ${sessionId} (${windowWidth}x${windowHeight})`
87
+ text: `Browser started in ${modeText} mode with sessionId: ${sessionId} (${windowWidth}x${windowHeight})${urlText}`
78
88
  }]
79
89
  };
80
90
  };
@@ -136,15 +146,6 @@ var clickToolDefinition = {
136
146
  timeout: z3.number().optional().describe("Maximum time to wait for element in milliseconds")
137
147
  }
138
148
  };
139
- var clickViaTextToolDefinition = {
140
- name: "click_via_text",
141
- description: "clicks an element",
142
- inputSchema: {
143
- 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")`),
144
- scrollToView: z3.boolean().optional().describe("Whether to scroll the element into view before clicking").default(true),
145
- timeout: z3.number().optional().describe("Maximum time to wait for element in milliseconds")
146
- }
147
- };
148
149
  var clickAction = async (selector, timeout, scrollToView = true) => {
149
150
  try {
150
151
  const browser = getBrowser();
@@ -163,7 +164,6 @@ var clickAction = async (selector, timeout, scrollToView = true) => {
163
164
  }
164
165
  };
165
166
  var clickTool = async ({ selector, scrollToView, timeout = defaultTimeout }) => clickAction(selector, timeout, scrollToView);
166
- var clickToolViaText = async ({ text, scrollToView, timeout = defaultTimeout }) => clickAction(`//a[contains(text(), '${text}')]`, timeout, scrollToView);
167
167
 
168
168
  // src/tools/set-value.tool.ts
169
169
  import { z as z4 } from "zod";
@@ -197,137 +197,250 @@ var setValueTool = async ({ selector, value, scrollToView = true, timeout = defa
197
197
  }
198
198
  };
199
199
 
200
- // src/tools/find-element.tool.ts
200
+ // src/tools/app-session.tool.ts
201
+ import { remote as remote2 } from "webdriverio";
201
202
  import { z as z5 } from "zod";
202
- var defaultTimeout3 = 3e3;
203
- var findElementToolDefinition = {
204
- name: "find_element",
205
- description: "finds an element",
206
- inputSchema: {
207
- selector: z5.string().describe(`Value for the selector, in the form of css selector or xpath ("button.my-class" or "//button[@class='my-class']")`),
208
- timeout: z5.number().optional().describe("Maximum time to wait for element in milliseconds")
203
+
204
+ // src/config/appium.config.ts
205
+ function getAppiumServerConfig(overrides) {
206
+ return {
207
+ hostname: overrides?.hostname || process.env.APPIUM_URL || "127.0.0.1",
208
+ port: overrides?.port || Number(process.env.APPIUM_URL_PORT) || 4723,
209
+ path: overrides?.path || process.env.APPIUM_PATH || "/"
210
+ };
211
+ }
212
+ function buildIOSCapabilities(appPath, options) {
213
+ const capabilities = {
214
+ platformName: "iOS",
215
+ "appium:platformVersion": options.platformVersion,
216
+ "appium:deviceName": options.deviceName,
217
+ "appium:automationName": options.automationName || "XCUITest"
218
+ };
219
+ if (appPath) {
220
+ capabilities["appium:app"] = appPath;
209
221
  }
210
- };
211
- var findElementTool = async ({ selector, timeout = defaultTimeout3 }) => {
212
- try {
213
- const browser = getBrowser();
214
- await browser.waitUntil(browser.$(selector).isExisting, { timeout });
215
- return {
216
- content: [{ type: "text", text: "Element found" }]
217
- };
218
- } catch (e) {
219
- return {
220
- content: [{ type: "text", text: `Error finding element: ${e}` }]
221
- };
222
+ if (options.udid) {
223
+ capabilities["appium:udid"] = options.udid;
222
224
  }
223
- };
224
-
225
- // src/tools/get-element-text.tool.ts
226
- import { z as z6 } from "zod";
227
- var defaultTimeout4 = 3e3;
228
- var getElementTextToolDefinition = {
229
- name: "get_element_text",
230
- description: "gets the text content of an element",
231
- inputSchema: {
232
- selector: z6.string().describe(`Value for the selector, in the form of css selector or xpath ("button.my-class" or "//button[@class='my-class']")`),
233
- timeout: z6.number().optional().describe("Maximum time to wait for element in milliseconds")
225
+ if (options.noReset !== void 0) {
226
+ capabilities["appium:noReset"] = options.noReset;
234
227
  }
235
- };
236
- var getElementTextTool = async ({ selector, timeout = defaultTimeout4 }) => {
237
- try {
238
- const browser = getBrowser();
239
- await browser.waitUntil(browser.$(selector).isExisting, { timeout });
240
- const text = await browser.$(selector).getText();
241
- return {
242
- content: [{ type: "text", text: `Text from element "${selector}": ${text}` }]
243
- };
244
- } catch (e) {
245
- return {
246
- content: [{ type: "text", text: `Error getting element text: ${e}` }]
247
- };
228
+ if (options.fullReset !== void 0) {
229
+ capabilities["appium:fullReset"] = options.fullReset;
248
230
  }
249
- };
250
-
251
- // src/tools/is-displayed.tool.ts
252
- import { z as z7 } from "zod";
253
- var defaultTimeout5 = 3e3;
254
- var isDisplayedToolDefinition = {
255
- name: "is_displayed",
256
- description: "checks if an element is displayed",
257
- inputSchema: {
258
- selector: z7.string().describe(`Value for the selector, in the form of css selector or xpath ("button.my-class" or "//button[@class='my-class']")`),
259
- timeout: z7.number().optional().describe("Maximum time to wait for element in milliseconds")
231
+ if (options.newCommandTimeout !== void 0) {
232
+ capabilities["appium:newCommandTimeout"] = options.newCommandTimeout;
260
233
  }
261
- };
262
- var isDisplayedTool = async ({ selector, timeout = defaultTimeout5 }) => {
263
- try {
264
- const browser = getBrowser();
265
- await browser.waitUntil(browser.$(selector).isExisting, { timeout });
266
- const displayed = await browser.$(selector).isDisplayed();
267
- return {
268
- content: [{
269
- type: "text",
270
- text: `Element with selector "${selector}" is ${displayed ? "displayed" : "not displayed"}`
271
- }]
272
- };
273
- } catch (e) {
274
- return {
275
- content: [{ type: "text", text: `Error checking if element is displayed: ${e}` }]
276
- };
234
+ capabilities["appium:autoGrantPermissions"] = options.autoGrantPermissions ?? true;
235
+ capabilities["appium:autoAcceptAlerts"] = options.autoAcceptAlerts ?? true;
236
+ if (options.autoDismissAlerts !== void 0) {
237
+ capabilities["appium:autoDismissAlerts"] = options.autoDismissAlerts;
238
+ capabilities["appium:autoAcceptAlerts"] = void 0;
277
239
  }
278
- };
240
+ for (const [key, value] of Object.entries(options)) {
241
+ if (!["deviceName", "platformVersion", "automationName", "autoAcceptAlerts", "autoDismissAlerts", "udid", "noReset", "fullReset", "newCommandTimeout"].includes(
242
+ key
243
+ )) {
244
+ capabilities[`appium:${key}`] = value;
245
+ }
246
+ }
247
+ return capabilities;
248
+ }
249
+ function buildAndroidCapabilities(appPath, options) {
250
+ const capabilities = {
251
+ platformName: "Android",
252
+ "appium:platformVersion": options.platformVersion,
253
+ "appium:deviceName": options.deviceName,
254
+ "appium:automationName": options.automationName || "UiAutomator2"
255
+ };
256
+ if (appPath) {
257
+ capabilities["appium:app"] = appPath;
258
+ }
259
+ if (options.noReset !== void 0) {
260
+ capabilities["appium:noReset"] = options.noReset;
261
+ }
262
+ if (options.fullReset !== void 0) {
263
+ capabilities["appium:fullReset"] = options.fullReset;
264
+ }
265
+ if (options.newCommandTimeout !== void 0) {
266
+ capabilities["appium:newCommandTimeout"] = options.newCommandTimeout;
267
+ }
268
+ capabilities["appium:autoGrantPermissions"] = options.autoGrantPermissions ?? true;
269
+ capabilities["appium:autoAcceptAlerts"] = options.autoAcceptAlerts ?? true;
270
+ if (options.autoDismissAlerts !== void 0) {
271
+ capabilities["appium:autoDismissAlerts"] = options.autoDismissAlerts;
272
+ capabilities["appium:autoAcceptAlerts"] = void 0;
273
+ }
274
+ if (options.appWaitActivity) {
275
+ capabilities["appium:appWaitActivity"] = options.appWaitActivity;
276
+ }
277
+ for (const [key, value] of Object.entries(options)) {
278
+ if (!["deviceName", "platformVersion", "automationName", "autoGrantPermissions", "appWaitActivity", "noReset", "fullReset", "newCommandTimeout"].includes(
279
+ key
280
+ )) {
281
+ capabilities[`appium:${key}`] = value;
282
+ }
283
+ }
284
+ return capabilities;
285
+ }
279
286
 
280
- // src/tools/scroll-down.tool.ts
281
- import { z as z8 } from "zod";
282
- var scrollDownToolDefinition = {
283
- name: "scroll_down",
284
- description: "scrolls the page down by specified pixels",
287
+ // src/tools/app-session.tool.ts
288
+ var startAppToolDefinition = {
289
+ name: "start_app_session",
290
+ description: "starts a mobile app session (iOS/Android) via Appium",
285
291
  inputSchema: {
286
- pixels: z8.number().optional().default(500)
292
+ platform: z5.enum(["iOS", "Android"]).describe("Mobile platform"),
293
+ appPath: z5.string().optional().describe("Path to the app file (.app/.apk/.ipa). Required unless noReset=true (connecting to already-running app)"),
294
+ deviceName: z5.string().describe("Device/emulator/simulator name"),
295
+ platformVersion: z5.string().optional().describe('OS version (e.g., "17.0", "14")'),
296
+ automationName: z5.enum(["XCUITest", "UiAutomator2", "Espresso"]).optional().describe("Automation driver name"),
297
+ appiumHost: z5.string().optional().describe("Appium server hostname (overrides APPIUM_URL env var)"),
298
+ appiumPort: z5.number().optional().describe("Appium server port (overrides APPIUM_URL_PORT env var)"),
299
+ appiumPath: z5.string().optional().describe("Appium server path (overrides APPIUM_PATH env var)"),
300
+ autoGrantPermissions: z5.boolean().optional().describe("Auto-grant app permissions (default: true)"),
301
+ autoAcceptAlerts: z5.boolean().optional().describe("Auto-accept alerts (default: true)"),
302
+ autoDismissAlerts: z5.boolean().optional().describe('Auto-dismiss alerts (default: false, will override "autoAcceptAlerts" to undefined if set)'),
303
+ appWaitActivity: z5.string().optional().describe("Activity to wait for on launch (Android only)"),
304
+ udid: z5.string().optional().describe('Unique Device Identifier for iOS real device testing (e.g., "00008030-001234567890002E")'),
305
+ noReset: z5.boolean().optional().describe("Do not reset app state before session (preserves app data). Default: false"),
306
+ fullReset: z5.boolean().optional().describe("Uninstall app before/after session. Default: true. Set to false with noReset=true to preserve app state completely"),
307
+ newCommandTimeout: z5.number().min(0).optional().describe("How long (in seconds) Appium will wait for a new command before assuming the client has quit and ending the session. Default: 60. Set to 300 for 5 minutes, etc.")
308
+ }
309
+ };
310
+ var getState = () => {
311
+ const sharedState = getBrowser.__state;
312
+ if (!sharedState) {
313
+ throw new Error("Browser state not initialized");
287
314
  }
315
+ return sharedState;
288
316
  };
289
- var scrollDownTool = async ({ pixels = 500 }) => {
317
+ var startAppTool = async (args) => {
290
318
  try {
291
- const browser = getBrowser();
292
- await browser.execute((scrollPixels) => {
293
- window.scrollBy(0, scrollPixels);
294
- }, pixels);
319
+ const {
320
+ platform,
321
+ appPath,
322
+ deviceName,
323
+ platformVersion,
324
+ automationName,
325
+ appiumHost,
326
+ appiumPort,
327
+ appiumPath,
328
+ autoGrantPermissions = true,
329
+ autoAcceptAlerts,
330
+ autoDismissAlerts,
331
+ appWaitActivity,
332
+ udid,
333
+ noReset,
334
+ fullReset,
335
+ newCommandTimeout
336
+ } = args;
337
+ if (!appPath && noReset !== true) {
338
+ return {
339
+ content: [{
340
+ type: "text",
341
+ text: 'Error: Either "appPath" must be provided to install an app, or "noReset: true" must be set to connect to an already-running app.'
342
+ }]
343
+ };
344
+ }
345
+ const serverConfig = getAppiumServerConfig({
346
+ hostname: appiumHost,
347
+ port: appiumPort,
348
+ path: appiumPath
349
+ });
350
+ const capabilities = platform === "iOS" ? buildIOSCapabilities(appPath, {
351
+ deviceName,
352
+ platformVersion,
353
+ automationName: automationName || "XCUITest",
354
+ autoGrantPermissions,
355
+ autoAcceptAlerts,
356
+ autoDismissAlerts,
357
+ udid,
358
+ noReset,
359
+ fullReset,
360
+ newCommandTimeout
361
+ }) : buildAndroidCapabilities(appPath, {
362
+ deviceName,
363
+ platformVersion,
364
+ automationName: automationName || "UiAutomator2",
365
+ autoGrantPermissions,
366
+ autoAcceptAlerts,
367
+ autoDismissAlerts,
368
+ appWaitActivity,
369
+ noReset,
370
+ fullReset,
371
+ newCommandTimeout
372
+ });
373
+ const browser = await remote2({
374
+ protocol: "http",
375
+ hostname: serverConfig.hostname,
376
+ port: serverConfig.port,
377
+ path: serverConfig.path,
378
+ capabilities
379
+ });
380
+ const { sessionId } = browser;
381
+ const shouldAutoDetach = noReset === true || !appPath;
382
+ const state2 = getState();
383
+ state2.browsers.set(sessionId, browser);
384
+ state2.currentSession = sessionId;
385
+ state2.sessionMetadata.set(sessionId, {
386
+ type: platform.toLowerCase(),
387
+ capabilities,
388
+ isAttached: shouldAutoDetach
389
+ });
390
+ const appInfo = appPath ? `
391
+ App: ${appPath}` : "\nApp: (connected to running app)";
392
+ const detachNote = shouldAutoDetach ? "\n\n(Auto-detach enabled: session will be preserved on close. Use close_session({ detach: false }) to force terminate.)" : "";
295
393
  return {
296
- content: [{ type: "text", text: `Scrolled down ${pixels} pixels` }]
394
+ content: [
395
+ {
396
+ type: "text",
397
+ text: `${platform} app session started with sessionId: ${sessionId}
398
+ Device: ${deviceName}${appInfo}
399
+ Appium Server: ${serverConfig.hostname}:${serverConfig.port}${serverConfig.path}${detachNote}`
400
+ }
401
+ ]
297
402
  };
298
403
  } catch (e) {
299
404
  return {
300
- content: [{ type: "text", text: `Error scrolling down: ${e}` }]
405
+ content: [{ type: "text", text: `Error starting app session: ${e}` }]
301
406
  };
302
407
  }
303
408
  };
304
409
 
305
- // src/tools/scroll-up.tool.ts
306
- import { z as z9 } from "zod";
307
- var scrollUpToolDefinition = {
308
- name: "scroll_up",
309
- description: "scrolls the page up by specified pixels",
410
+ // src/tools/scroll.tool.ts
411
+ import { z as z6 } from "zod";
412
+ var scrollToolDefinition = {
413
+ name: "scroll",
414
+ description: "scrolls the page by specified pixels (browser only). For mobile, use the swipe tool.",
310
415
  inputSchema: {
311
- pixels: z9.number().optional().default(500)
416
+ direction: z6.enum(["up", "down"]).describe("Scroll direction"),
417
+ pixels: z6.number().optional().default(500).describe("Number of pixels to scroll")
312
418
  }
313
419
  };
314
- var scrollUpTool = async ({ pixels = 500 }) => {
420
+ var scrollTool = async ({ direction, pixels = 500 }) => {
315
421
  try {
316
422
  const browser = getBrowser();
317
- await browser.execute((scrollPixels) => {
318
- window.scrollBy(0, -scrollPixels);
319
- }, pixels);
423
+ const state2 = getState();
424
+ const metadata = state2.sessionMetadata.get(state2.currentSession);
425
+ const sessionType = metadata?.type;
426
+ if (sessionType !== "browser") {
427
+ throw new Error("scroll only works in browser sessions. For mobile, use the swipe tool.");
428
+ }
429
+ const scrollAmount = direction === "down" ? pixels : -pixels;
430
+ await browser.execute((amount) => {
431
+ window.scrollBy(0, amount);
432
+ }, scrollAmount);
320
433
  return {
321
- content: [{ type: "text", text: `Scrolled up ${pixels} pixels` }]
434
+ content: [{ type: "text", text: `Scrolled ${direction} ${pixels} pixels` }]
322
435
  };
323
436
  } catch (e) {
324
437
  return {
325
- content: [{ type: "text", text: `Error scrolling up: ${e}` }]
438
+ content: [{ type: "text", text: `Error scrolling: ${e}` }]
326
439
  };
327
440
  }
328
441
  };
329
442
 
330
- // src/scripts/get-interactable-elements.ts
443
+ // src/scripts/get-interactable-browser-elements.ts
331
444
  var elementsScript = (elementType = "interactable") => (function() {
332
445
  const interactableSelectors = [
333
446
  "a[href]",
@@ -494,7 +607,7 @@ var elementsScript = (elementType = "interactable") => (function() {
494
607
  }
495
608
  return getElements();
496
609
  })();
497
- var get_interactable_elements_default = elementsScript;
610
+ var get_interactable_browser_elements_default = elementsScript;
498
611
 
499
612
  // src/locators/source-parsing.ts
500
613
  import { DOMParser } from "@xmldom/xmldom";
@@ -1097,7 +1210,7 @@ function generateAllElementLocators(sourceXML, options) {
1097
1210
  return ctx.results;
1098
1211
  }
1099
1212
 
1100
- // src/utils/mobile-elements.ts
1213
+ // src/scripts/get-visible-mobile-elements.ts
1101
1214
  var LOCATOR_PRIORITY = [
1102
1215
  "accessibility-id",
1103
1216
  // Most stable, cross-platform
@@ -1185,7 +1298,7 @@ async function getMobileVisibleElements(browser, platform, options = {}) {
1185
1298
 
1186
1299
  // src/tools/get-visible-elements.tool.ts
1187
1300
  import { encode } from "@toon-format/toon";
1188
- import { z as z10 } from "zod";
1301
+ import { z as z7 } from "zod";
1189
1302
 
1190
1303
  // src/utils/strip-undefined.ts
1191
1304
  function stripUndefined(obj) {
@@ -1202,20 +1315,20 @@ var getVisibleElementsToolDefinition = {
1202
1315
  name: "get_visible_elements",
1203
1316
  description: 'get a list of visible (in viewport & displayed) interactable elements on the page (buttons, links, inputs). Use elementType="visual" for images/SVGs. Must prefer this to take_screenshot for interactions',
1204
1317
  inputSchema: {
1205
- inViewportOnly: z10.boolean().optional().describe(
1318
+ inViewportOnly: z7.boolean().optional().describe(
1206
1319
  "Only return elements within the visible viewport. Default: true. Set to false to get ALL elements on the page."
1207
1320
  ),
1208
- includeContainers: z10.boolean().optional().describe(
1321
+ includeContainers: z7.boolean().optional().describe(
1209
1322
  "Include layout containers (ViewGroup, FrameLayout, ScrollView, etc). Default: false. Set to true to see all elements including layouts."
1210
1323
  ),
1211
- includeBounds: z10.boolean().optional().describe(
1324
+ includeBounds: z7.boolean().optional().describe(
1212
1325
  "Include element bounds/coordinates (x, y, width, height). Default: false. Set to true for coordinate-based interactions or layout debugging."
1213
1326
  ),
1214
- elementType: z10.enum(["interactable", "visual", "all"]).optional().describe(
1327
+ elementType: z7.enum(["interactable", "visual", "all"]).optional().describe(
1215
1328
  'Type of elements to return: "interactable" (default) for buttons/links/inputs, "visual" for images/SVGs, "all" for both.'
1216
1329
  ),
1217
- limit: z10.number().optional().describe("Maximum number of elements to return. Default: 0 (unlimited)."),
1218
- offset: z10.number().optional().describe("Number of elements to skip (for pagination). Default: 0.")
1330
+ limit: z7.number().optional().describe("Maximum number of elements to return. Default: 0 (unlimited)."),
1331
+ offset: z7.number().optional().describe("Number of elements to skip (for pagination). Default: 0.")
1219
1332
  }
1220
1333
  };
1221
1334
  var getVisibleElementsTool = async (args) => {
@@ -1234,7 +1347,7 @@ var getVisibleElementsTool = async (args) => {
1234
1347
  const platform = browser.isAndroid ? "android" : "ios";
1235
1348
  elements = await getMobileVisibleElements(browser, platform, { includeContainers, includeBounds });
1236
1349
  } else {
1237
- const raw = await browser.execute(get_interactable_elements_default, elementType);
1350
+ const raw = await browser.execute(get_interactable_browser_elements_default, elementType);
1238
1351
  elements = stripUndefinedFromArray(raw);
1239
1352
  }
1240
1353
  if (inViewportOnly) {
@@ -1264,29 +1377,56 @@ var getVisibleElementsTool = async (args) => {
1264
1377
  };
1265
1378
 
1266
1379
  // src/tools/take-screenshot.tool.ts
1267
- import { z as z11 } from "zod";
1380
+ import { z as z8 } from "zod";
1381
+ import sharp from "sharp";
1382
+ var MAX_DIMENSION = 2e3;
1383
+ var MAX_FILE_SIZE_BYTES = 1024 * 1024;
1268
1384
  var takeScreenshotToolDefinition = {
1269
1385
  name: "take_screenshot",
1270
1386
  description: "captures a screenshot of the current page",
1271
1387
  inputSchema: {
1272
- outputPath: z11.string().optional().describe("Optional path where to save the screenshot. If not provided, returns base64 data.")
1388
+ outputPath: z8.string().optional().describe("Optional path where to save the screenshot. If not provided, returns base64 data.")
1273
1389
  }
1274
1390
  };
1391
+ async function processScreenshot(screenshotBase64) {
1392
+ const inputBuffer = Buffer.from(screenshotBase64, "base64");
1393
+ let image = sharp(inputBuffer);
1394
+ const metadata = await image.metadata();
1395
+ const width = metadata.width ?? 0;
1396
+ const height = metadata.height ?? 0;
1397
+ if (width > MAX_DIMENSION || height > MAX_DIMENSION) {
1398
+ const resizeOptions = width > height ? { width: MAX_DIMENSION } : { height: MAX_DIMENSION };
1399
+ image = image.resize(resizeOptions);
1400
+ }
1401
+ let outputBuffer = await image.png({ compressionLevel: 9 }).toBuffer();
1402
+ if (outputBuffer.length > MAX_FILE_SIZE_BYTES) {
1403
+ let quality = 90;
1404
+ while (quality >= 10 && outputBuffer.length > MAX_FILE_SIZE_BYTES) {
1405
+ outputBuffer = await image.jpeg({ quality, mozjpeg: true }).toBuffer();
1406
+ quality -= 10;
1407
+ }
1408
+ return { data: outputBuffer, mimeType: "image/jpeg" };
1409
+ }
1410
+ return { data: outputBuffer, mimeType: "image/png" };
1411
+ }
1275
1412
  var takeScreenshotTool = async ({ outputPath }) => {
1276
1413
  try {
1277
1414
  const browser = getBrowser();
1278
1415
  const screenshot = await browser.takeScreenshot();
1416
+ const { data, mimeType } = await processScreenshot(screenshot);
1279
1417
  if (outputPath) {
1280
1418
  const fs = await import("fs");
1281
- await fs.promises.writeFile(outputPath, screenshot, "base64");
1419
+ await fs.promises.writeFile(outputPath, data);
1420
+ const sizeKB2 = (data.length / 1024).toFixed(1);
1282
1421
  return {
1283
- content: [{ type: "text", text: `Screenshot saved to ${outputPath}` }]
1422
+ content: [{ type: "text", text: `Screenshot saved to ${outputPath} (${sizeKB2}KB, ${mimeType})` }]
1284
1423
  };
1285
1424
  }
1425
+ const sizeKB = (data.length / 1024).toFixed(1);
1286
1426
  return {
1287
1427
  content: [
1288
- { type: "text", text: "Screenshot captured as base64:" },
1289
- { type: "image", data: screenshot.toString(), mimeType: "image/png" }
1428
+ { type: "text", text: `Screenshot captured (${sizeKB}KB, ${mimeType}):` },
1429
+ { type: "image", data: data.toString("base64"), mimeType }
1290
1430
  ]
1291
1431
  };
1292
1432
  } catch (e) {
@@ -1297,12 +1437,12 @@ var takeScreenshotTool = async ({ outputPath }) => {
1297
1437
  };
1298
1438
 
1299
1439
  // src/tools/cookies.tool.ts
1300
- import { z as z12 } from "zod";
1440
+ import { z as z9 } from "zod";
1301
1441
  var getCookiesToolDefinition = {
1302
1442
  name: "get_cookies",
1303
1443
  description: "gets all cookies or a specific cookie by name",
1304
1444
  inputSchema: {
1305
- name: z12.string().optional().describe("Optional cookie name to retrieve a specific cookie. If not provided, returns all cookies")
1445
+ name: z9.string().optional().describe("Optional cookie name to retrieve a specific cookie. If not provided, returns all cookies")
1306
1446
  }
1307
1447
  };
1308
1448
  var getCookiesTool = async ({ name }) => {
@@ -1338,14 +1478,14 @@ var setCookieToolDefinition = {
1338
1478
  name: "set_cookie",
1339
1479
  description: "sets a cookie with specified name, value, and optional attributes",
1340
1480
  inputSchema: {
1341
- name: z12.string().describe("Cookie name"),
1342
- value: z12.string().describe("Cookie value"),
1343
- domain: z12.string().optional().describe("Cookie domain (defaults to current domain)"),
1344
- path: z12.string().optional().describe('Cookie path (defaults to "/")'),
1345
- expires: z12.number().optional().describe("Expiry date as Unix timestamp in seconds"),
1346
- httpOnly: z12.boolean().optional().describe("HttpOnly flag"),
1347
- secure: z12.boolean().optional().describe("Secure flag"),
1348
- sameSite: z12.enum(["Strict", "Lax", "None"]).optional().describe("SameSite attribute")
1481
+ name: z9.string().describe("Cookie name"),
1482
+ value: z9.string().describe("Cookie value"),
1483
+ domain: z9.string().optional().describe("Cookie domain (defaults to current domain)"),
1484
+ path: z9.string().optional().describe('Cookie path (defaults to "/")'),
1485
+ expiry: z9.number().optional().describe("Expiry date as Unix timestamp in seconds"),
1486
+ httpOnly: z9.boolean().optional().describe("HttpOnly flag"),
1487
+ secure: z9.boolean().optional().describe("Secure flag"),
1488
+ sameSite: z9.enum(["strict", "lax", "none"]).optional().describe("SameSite attribute")
1349
1489
  }
1350
1490
  };
1351
1491
  var setCookieTool = async ({
@@ -1353,23 +1493,14 @@ var setCookieTool = async ({
1353
1493
  value,
1354
1494
  domain,
1355
1495
  path = "/",
1356
- expires,
1496
+ expiry,
1357
1497
  httpOnly,
1358
1498
  secure,
1359
1499
  sameSite
1360
1500
  }) => {
1361
1501
  try {
1362
1502
  const browser = getBrowser();
1363
- const cookie = {
1364
- name,
1365
- value,
1366
- path,
1367
- domain,
1368
- expiry: expires,
1369
- httpOnly,
1370
- secure,
1371
- sameSite
1372
- };
1503
+ const cookie = { name, value, path, domain, expiry, httpOnly, secure, sameSite };
1373
1504
  await browser.setCookies(cookie);
1374
1505
  return {
1375
1506
  content: [{ type: "text", text: `Cookie "${name}" set successfully` }]
@@ -1384,7 +1515,7 @@ var deleteCookiesToolDefinition = {
1384
1515
  name: "delete_cookies",
1385
1516
  description: "deletes all cookies or a specific cookie by name",
1386
1517
  inputSchema: {
1387
- name: z12.string().optional().describe("Optional cookie name to delete a specific cookie. If not provided, deletes all cookies")
1518
+ name: z9.string().optional().describe("Optional cookie name to delete a specific cookie. If not provided, deletes all cookies")
1388
1519
  }
1389
1520
  };
1390
1521
  var deleteCookiesTool = async ({ name }) => {
@@ -1409,15 +1540,15 @@ var deleteCookiesTool = async ({ name }) => {
1409
1540
 
1410
1541
  // src/tools/get-accessibility-tree.tool.ts
1411
1542
  import { encode as encode2 } from "@toon-format/toon";
1412
- import { z as z13 } from "zod";
1543
+ import { z as z10 } from "zod";
1413
1544
  var getAccessibilityToolDefinition = {
1414
1545
  name: "get_accessibility",
1415
1546
  description: "gets accessibility tree snapshot with semantic information about page elements (roles, names, states). Browser-only - use when get_visible_elements does not return expected elements.",
1416
1547
  inputSchema: {
1417
- limit: z13.number().optional().describe("Maximum number of nodes to return. Default: 100. Use 0 for unlimited."),
1418
- offset: z13.number().optional().describe("Number of nodes to skip (for pagination). Default: 0."),
1419
- roles: z13.array(z13.string()).optional().describe('Filter to specific roles (e.g., ["button", "link", "textbox"]). Default: all roles.'),
1420
- namedOnly: z13.boolean().optional().describe("Only return nodes with a name/label. Default: true. Filters out anonymous containers.")
1548
+ limit: z10.number().optional().describe("Maximum number of nodes to return. Default: 100. Use 0 for unlimited."),
1549
+ offset: z10.number().optional().describe("Number of nodes to skip (for pagination). Default: 0."),
1550
+ roles: z10.array(z10.string()).optional().describe('Filter to specific roles (e.g., ["button", "link", "textbox"]). Default: all roles.'),
1551
+ namedOnly: z10.boolean().optional().describe("Only return nodes with a name/label. Default: true. Filters out anonymous containers.")
1421
1552
  }
1422
1553
  };
1423
1554
  function flattenAccessibilityTree(node, result = []) {
@@ -1518,215 +1649,15 @@ var getAccessibilityTreeTool = async (args) => {
1518
1649
  }
1519
1650
  };
1520
1651
 
1521
- // src/tools/app-session.tool.ts
1522
- import { remote as remote2 } from "webdriverio";
1523
- import { z as z14 } from "zod";
1524
-
1525
- // src/config/appium.config.ts
1526
- function getAppiumServerConfig(overrides) {
1527
- return {
1528
- hostname: overrides?.hostname || process.env.APPIUM_URL || "127.0.0.1",
1529
- port: overrides?.port || Number(process.env.APPIUM_URL_PORT) || 4723,
1530
- path: overrides?.path || process.env.APPIUM_PATH || "/"
1531
- };
1532
- }
1533
- function buildIOSCapabilities(appPath, options) {
1534
- const capabilities = {
1535
- platformName: "iOS",
1536
- "appium:platformVersion": options.platformVersion,
1537
- "appium:deviceName": options.deviceName,
1538
- "appium:automationName": options.automationName || "XCUITest"
1539
- };
1540
- if (appPath) {
1541
- capabilities["appium:app"] = appPath;
1542
- }
1543
- if (options.udid) {
1544
- capabilities["appium:udid"] = options.udid;
1545
- }
1546
- if (options.noReset !== void 0) {
1547
- capabilities["appium:noReset"] = options.noReset;
1548
- }
1549
- if (options.fullReset !== void 0) {
1550
- capabilities["appium:fullReset"] = options.fullReset;
1551
- }
1552
- capabilities["appium:autoGrantPermissions"] = options.autoGrantPermissions ?? true;
1553
- capabilities["appium:autoAcceptAlerts"] = options.autoAcceptAlerts ?? true;
1554
- if (options.autoDismissAlerts !== void 0) {
1555
- capabilities["appium:autoDismissAlerts"] = options.autoDismissAlerts;
1556
- capabilities["appium:autoAcceptAlerts"] = void 0;
1557
- }
1558
- for (const [key, value] of Object.entries(options)) {
1559
- if (!["deviceName", "platformVersion", "automationName", "autoAcceptAlerts", "autoDismissAlerts", "udid", "noReset", "fullReset"].includes(
1560
- key
1561
- )) {
1562
- capabilities[`appium:${key}`] = value;
1563
- }
1564
- }
1565
- return capabilities;
1566
- }
1567
- function buildAndroidCapabilities(appPath, options) {
1568
- const capabilities = {
1569
- platformName: "Android",
1570
- "appium:platformVersion": options.platformVersion,
1571
- "appium:deviceName": options.deviceName,
1572
- "appium:automationName": options.automationName || "UiAutomator2"
1573
- };
1574
- if (appPath) {
1575
- capabilities["appium:app"] = appPath;
1576
- }
1577
- if (options.noReset !== void 0) {
1578
- capabilities["appium:noReset"] = options.noReset;
1579
- }
1580
- if (options.fullReset !== void 0) {
1581
- capabilities["appium:fullReset"] = options.fullReset;
1582
- }
1583
- capabilities["appium:autoGrantPermissions"] = options.autoGrantPermissions ?? true;
1584
- capabilities["appium:autoAcceptAlerts"] = options.autoAcceptAlerts ?? true;
1585
- if (options.autoDismissAlerts !== void 0) {
1586
- capabilities["appium:autoDismissAlerts"] = options.autoDismissAlerts;
1587
- capabilities["appium:autoAcceptAlerts"] = void 0;
1588
- }
1589
- if (options.appWaitActivity) {
1590
- capabilities["appium:appWaitActivity"] = options.appWaitActivity;
1591
- }
1592
- for (const [key, value] of Object.entries(options)) {
1593
- if (!["deviceName", "platformVersion", "automationName", "autoGrantPermissions", "appWaitActivity", "noReset", "fullReset"].includes(
1594
- key
1595
- )) {
1596
- capabilities[`appium:${key}`] = value;
1597
- }
1598
- }
1599
- return capabilities;
1600
- }
1601
-
1602
- // src/tools/app-session.tool.ts
1603
- var startAppToolDefinition = {
1604
- name: "start_app_session",
1605
- description: "starts a mobile app session (iOS/Android) via Appium",
1606
- inputSchema: {
1607
- platform: z14.enum(["iOS", "Android"]).describe("Mobile platform"),
1608
- appPath: z14.string().optional().describe("Path to the app file (.app/.apk/.ipa). Required unless noReset=true (connecting to already-running app)"),
1609
- deviceName: z14.string().describe("Device/emulator/simulator name"),
1610
- platformVersion: z14.string().optional().describe('OS version (e.g., "17.0", "14")'),
1611
- automationName: z14.enum(["XCUITest", "UiAutomator2", "Espresso"]).optional().describe("Automation driver name"),
1612
- appiumHost: z14.string().optional().describe("Appium server hostname (overrides APPIUM_URL env var)"),
1613
- appiumPort: z14.number().optional().describe("Appium server port (overrides APPIUM_URL_PORT env var)"),
1614
- appiumPath: z14.string().optional().describe("Appium server path (overrides APPIUM_PATH env var)"),
1615
- autoGrantPermissions: z14.boolean().optional().describe("Auto-grant app permissions (default: true)"),
1616
- autoAcceptAlerts: z14.boolean().optional().describe("Auto-accept alerts (default: true)"),
1617
- autoDismissAlerts: z14.boolean().optional().describe('Auto-dismiss alerts (default: false, will override "autoAcceptAlerts" to undefined if set)'),
1618
- appWaitActivity: z14.string().optional().describe("Activity to wait for on launch (Android only)"),
1619
- udid: z14.string().optional().describe('Unique Device Identifier for iOS real device testing (e.g., "00008030-001234567890002E")'),
1620
- noReset: z14.boolean().optional().describe("Do not reset app state before session (preserves app data). Default: false"),
1621
- fullReset: z14.boolean().optional().describe("Uninstall app before/after session. Default: true. Set to false with noReset=true to preserve app state completely")
1622
- }
1623
- };
1624
- var getState = () => {
1625
- const sharedState = getBrowser.__state;
1626
- if (!sharedState) {
1627
- throw new Error("Browser state not initialized");
1628
- }
1629
- return sharedState;
1630
- };
1631
- var startAppTool = async (args) => {
1632
- try {
1633
- const {
1634
- platform,
1635
- appPath,
1636
- deviceName,
1637
- platformVersion,
1638
- automationName,
1639
- appiumHost,
1640
- appiumPort,
1641
- appiumPath,
1642
- autoGrantPermissions = true,
1643
- autoAcceptAlerts,
1644
- autoDismissAlerts,
1645
- appWaitActivity,
1646
- udid,
1647
- noReset,
1648
- fullReset
1649
- } = args;
1650
- if (!appPath && noReset !== true) {
1651
- return {
1652
- content: [{
1653
- type: "text",
1654
- text: 'Error: Either "appPath" must be provided to install an app, or "noReset: true" must be set to connect to an already-running app.'
1655
- }]
1656
- };
1657
- }
1658
- const serverConfig = getAppiumServerConfig({
1659
- hostname: appiumHost,
1660
- port: appiumPort,
1661
- path: appiumPath
1662
- });
1663
- const capabilities = platform === "iOS" ? buildIOSCapabilities(appPath, {
1664
- deviceName,
1665
- platformVersion,
1666
- automationName: automationName || "XCUITest",
1667
- autoGrantPermissions,
1668
- autoAcceptAlerts,
1669
- autoDismissAlerts,
1670
- udid,
1671
- noReset,
1672
- fullReset
1673
- }) : buildAndroidCapabilities(appPath, {
1674
- deviceName,
1675
- platformVersion,
1676
- automationName: automationName || "UiAutomator2",
1677
- autoGrantPermissions,
1678
- autoAcceptAlerts,
1679
- autoDismissAlerts,
1680
- appWaitActivity,
1681
- noReset,
1682
- fullReset
1683
- });
1684
- const browser = await remote2({
1685
- protocol: "http",
1686
- hostname: serverConfig.hostname,
1687
- port: serverConfig.port,
1688
- path: serverConfig.path,
1689
- capabilities
1690
- });
1691
- const { sessionId } = browser;
1692
- const shouldAutoDetach = noReset === true || !appPath;
1693
- const state2 = getState();
1694
- state2.browsers.set(sessionId, browser);
1695
- state2.currentSession = sessionId;
1696
- state2.sessionMetadata.set(sessionId, {
1697
- type: platform.toLowerCase(),
1698
- capabilities,
1699
- isAttached: shouldAutoDetach
1700
- });
1701
- const appInfo = appPath ? `
1702
- App: ${appPath}` : "\nApp: (connected to running app)";
1703
- const detachNote = shouldAutoDetach ? "\n\n(Auto-detach enabled: session will be preserved on close. Use close_session({ detach: false }) to force terminate.)" : "";
1704
- return {
1705
- content: [
1706
- {
1707
- type: "text",
1708
- text: `${platform} app session started with sessionId: ${sessionId}
1709
- Device: ${deviceName}${appInfo}
1710
- Appium Server: ${serverConfig.hostname}:${serverConfig.port}${serverConfig.path}${detachNote}`
1711
- }
1712
- ]
1713
- };
1714
- } catch (e) {
1715
- return {
1716
- content: [{ type: "text", text: `Error starting app session: ${e}` }]
1717
- };
1718
- }
1719
- };
1720
-
1721
- // src/tools/gestures.tool.ts
1722
- import { z as z15 } from "zod";
1723
- var tapElementToolDefinition = {
1724
- name: "tap_element",
1725
- description: "taps an element by selector or coordinates (mobile)",
1726
- inputSchema: {
1727
- selector: z15.string().optional().describe("Element selector (CSS, XPath, accessibility ID, or UiAutomator)"),
1728
- x: z15.number().optional().describe("X coordinate for tap (if no selector provided)"),
1729
- y: z15.number().optional().describe("Y coordinate for tap (if no selector provided)")
1652
+ // src/tools/gestures.tool.ts
1653
+ import { z as z11 } from "zod";
1654
+ var tapElementToolDefinition = {
1655
+ name: "tap_element",
1656
+ description: "taps an element by selector or screen coordinates (mobile)",
1657
+ inputSchema: {
1658
+ selector: z11.string().optional().describe("Element selector (CSS, XPath, accessibility ID, or UiAutomator)"),
1659
+ x: z11.number().optional().describe("X coordinate for screen tap (if no selector provided)"),
1660
+ y: z11.number().optional().describe("Y coordinate for screen tap (if no selector provided)")
1730
1661
  }
1731
1662
  };
1732
1663
  var tapElementTool = async (args) => {
@@ -1740,11 +1671,7 @@ var tapElementTool = async (args) => {
1740
1671
  content: [{ type: "text", text: `Tapped element: ${selector}` }]
1741
1672
  };
1742
1673
  } else if (x !== void 0 && y !== void 0) {
1743
- await browser.touchAction({
1744
- action: "tap",
1745
- x,
1746
- y
1747
- });
1674
+ await browser.tap({ x, y });
1748
1675
  return {
1749
1676
  content: [{ type: "text", text: `Tapped at coordinates: (${x}, ${y})` }]
1750
1677
  };
@@ -1754,7 +1681,7 @@ var tapElementTool = async (args) => {
1754
1681
  };
1755
1682
  } catch (e) {
1756
1683
  return {
1757
- content: [{ type: "text", text: `Error tapping element: ${e}` }]
1684
+ content: [{ type: "text", text: `Error tapping: ${e}` }]
1758
1685
  };
1759
1686
  }
1760
1687
  };
@@ -1762,52 +1689,29 @@ var swipeToolDefinition = {
1762
1689
  name: "swipe",
1763
1690
  description: "performs a swipe gesture in specified direction (mobile)",
1764
1691
  inputSchema: {
1765
- direction: z15.enum(["up", "down", "left", "right"]).describe("Swipe direction"),
1766
- duration: z15.number().min(100).max(5e3).optional().describe("Swipe duration in milliseconds (default: 500)"),
1767
- startX: z15.number().optional().describe("Start X coordinate (optional, uses screen center)"),
1768
- startY: z15.number().optional().describe("Start Y coordinate (optional, uses screen center)"),
1769
- distance: z15.number().optional().describe("Swipe distance in pixels (optional, uses percentage of screen)")
1692
+ direction: z11.enum(["up", "down", "left", "right"]).describe("Swipe direction"),
1693
+ duration: z11.number().min(100).max(5e3).optional().describe("Swipe duration in milliseconds (default: 500)"),
1694
+ percent: z11.number().min(0).max(1).optional().describe("Percentage of screen to swipe (0-1, default: 0.5 for up/down, 0.95 for left/right)")
1770
1695
  }
1771
1696
  };
1697
+ var contentToFingerDirection = {
1698
+ up: "down",
1699
+ down: "up",
1700
+ left: "right",
1701
+ right: "left"
1702
+ };
1772
1703
  var swipeTool = async (args) => {
1773
1704
  try {
1774
1705
  const browser = getBrowser();
1775
- const { direction, duration = 500, startX, startY, distance } = args;
1776
- const windowSize = await browser.getWindowSize();
1777
- const screenWidth = windowSize.width;
1778
- const screenHeight = windowSize.height;
1779
- const centerX = startX ?? screenWidth / 2;
1780
- const centerY = startY ?? screenHeight / 2;
1781
- const swipeDistance = distance ?? Math.min(screenWidth, screenHeight) * 0.5;
1782
- let endX = centerX;
1783
- let endY = centerY;
1784
- switch (direction) {
1785
- case "up":
1786
- endY = centerY - swipeDistance;
1787
- break;
1788
- case "down":
1789
- endY = centerY + swipeDistance;
1790
- break;
1791
- case "left":
1792
- endX = centerX - swipeDistance;
1793
- break;
1794
- case "right":
1795
- endX = centerX + swipeDistance;
1796
- break;
1797
- }
1798
- await browser.touchPerform([
1799
- { action: "press", options: { x: centerX, y: centerY } },
1800
- { action: "wait", options: { ms: duration } },
1801
- { action: "moveTo", options: { x: endX, y: endY } },
1802
- { action: "release", options: {} }
1803
- ]);
1706
+ const { direction, duration, percent } = args;
1707
+ const isVertical = direction === "up" || direction === "down";
1708
+ const defaultPercent = isVertical ? 0.5 : 0.95;
1709
+ const effectivePercent = percent ?? defaultPercent;
1710
+ const effectiveDuration = duration ?? 500;
1711
+ const fingerDirection = contentToFingerDirection[direction];
1712
+ await browser.swipe({ direction: fingerDirection, duration: effectiveDuration, percent: effectivePercent });
1804
1713
  return {
1805
- content: [
1806
- {
1807
- type: "text",
1808
- text: `Swiped ${direction} from (${Math.round(centerX)}, ${Math.round(centerY)}) to (${Math.round(endX)}, ${Math.round(endY)}) over ${duration}ms`
1809
- }
1810
- ]
1714
+ content: [{ type: "text", text: `Swiped ${direction}` }]
1811
1715
  };
1812
1716
  } catch (e) {
1813
1717
  return {
@@ -1815,126 +1719,51 @@ var swipeTool = async (args) => {
1815
1719
  };
1816
1720
  }
1817
1721
  };
1818
- var longPressToolDefinition = {
1819
- name: "long_press",
1820
- description: "performs a long press on element or coordinates (mobile)",
1821
- inputSchema: {
1822
- selector: z15.string().optional().describe("Element selector (CSS, XPath, accessibility ID, or UiAutomator)"),
1823
- x: z15.number().optional().describe("X coordinate for long press (if no selector provided)"),
1824
- y: z15.number().optional().describe("Y coordinate for long press (if no selector provided)"),
1825
- duration: z15.number().min(500).max(1e4).optional().describe("Long press duration in milliseconds (default: 1000)")
1826
- }
1827
- };
1828
- var longPressTool = async (args) => {
1829
- try {
1830
- const browser = getBrowser();
1831
- const { selector, x, y, duration = 1e3 } = args;
1832
- if (selector) {
1833
- const element = await browser.$(selector);
1834
- await element.touchAction([
1835
- { action: "longPress" },
1836
- { action: "wait", ms: duration },
1837
- { action: "release" }
1838
- ]);
1839
- return {
1840
- content: [{ type: "text", text: `Long pressed element: ${selector} for ${duration}ms` }]
1841
- };
1842
- } else if (x !== void 0 && y !== void 0) {
1843
- await browser.touchPerform([
1844
- { action: "press", options: { x, y } },
1845
- { action: "wait", options: { ms: duration } },
1846
- { action: "release", options: {} }
1847
- ]);
1848
- return {
1849
- content: [{ type: "text", text: `Long pressed at coordinates: (${x}, ${y}) for ${duration}ms` }]
1850
- };
1851
- }
1852
- return {
1853
- content: [{ type: "text", text: "Error: Must provide either selector or x,y coordinates" }]
1854
- };
1855
- } catch (e) {
1856
- return {
1857
- content: [{ type: "text", text: `Error long pressing: ${e}` }]
1858
- };
1859
- }
1860
- };
1861
1722
  var dragAndDropToolDefinition = {
1862
1723
  name: "drag_and_drop",
1863
- description: "drags from one location to another (mobile)",
1724
+ description: "drags an element to another element or coordinates (mobile)",
1864
1725
  inputSchema: {
1865
- fromSelector: z15.string().optional().describe("Source element selector"),
1866
- fromX: z15.number().optional().describe("Source X coordinate"),
1867
- fromY: z15.number().optional().describe("Source Y coordinate"),
1868
- toSelector: z15.string().optional().describe("Target element selector"),
1869
- toX: z15.number().optional().describe("Target X coordinate"),
1870
- toY: z15.number().optional().describe("Target Y coordinate"),
1871
- duration: z15.number().min(100).max(5e3).optional().describe("Drag duration in milliseconds (default: 500)")
1726
+ sourceSelector: z11.string().describe("Source element selector to drag"),
1727
+ targetSelector: z11.string().optional().describe("Target element selector to drop onto"),
1728
+ x: z11.number().optional().describe("Target X offset (if no targetSelector)"),
1729
+ y: z11.number().optional().describe("Target Y offset (if no targetSelector)"),
1730
+ duration: z11.number().min(100).max(5e3).optional().describe("Drag duration in milliseconds")
1872
1731
  }
1873
1732
  };
1874
1733
  var dragAndDropTool = async (args) => {
1875
1734
  try {
1876
1735
  const browser = getBrowser();
1877
- const { fromSelector, fromX, fromY, toSelector, toX, toY, duration = 500 } = args;
1878
- let startX;
1879
- let startY;
1880
- let endX;
1881
- let endY;
1882
- if (fromSelector) {
1883
- const element = await browser.$(fromSelector);
1884
- const location = await element.getLocation();
1885
- const size = await element.getSize();
1886
- startX = location.x + size.width / 2;
1887
- startY = location.y + size.height / 2;
1888
- } else if (fromX !== void 0 && fromY !== void 0) {
1889
- startX = fromX;
1890
- startY = fromY;
1891
- } else {
1736
+ const { sourceSelector, targetSelector, x, y, duration } = args;
1737
+ const sourceElement = await browser.$(sourceSelector);
1738
+ if (targetSelector) {
1739
+ const targetElement = await browser.$(targetSelector);
1740
+ await sourceElement.dragAndDrop(targetElement, { duration });
1892
1741
  return {
1893
- content: [{ type: "text", text: "Error: Must provide either fromSelector or fromX,fromY coordinates" }]
1742
+ content: [{ type: "text", text: `Dragged ${sourceSelector} to ${targetSelector}` }]
1894
1743
  };
1895
- }
1896
- if (toSelector) {
1897
- const element = await browser.$(toSelector);
1898
- const location = await element.getLocation();
1899
- const size = await element.getSize();
1900
- endX = location.x + size.width / 2;
1901
- endY = location.y + size.height / 2;
1902
- } else if (toX !== void 0 && toY !== void 0) {
1903
- endX = toX;
1904
- endY = toY;
1905
- } else {
1744
+ } else if (x !== void 0 && y !== void 0) {
1745
+ await sourceElement.dragAndDrop({ x, y }, { duration });
1906
1746
  return {
1907
- content: [{ type: "text", text: "Error: Must provide either toSelector or toX,toY coordinates" }]
1747
+ content: [{ type: "text", text: `Dragged ${sourceSelector} by (${x}, ${y})` }]
1908
1748
  };
1909
1749
  }
1910
- await browser.touchPerform([
1911
- { action: "press", options: { x: startX, y: startY } },
1912
- { action: "wait", options: { ms: duration } },
1913
- { action: "moveTo", options: { x: endX, y: endY } },
1914
- { action: "release", options: {} }
1915
- ]);
1916
1750
  return {
1917
- content: [
1918
- {
1919
- type: "text",
1920
- text: `Dragged from (${Math.round(startX)}, ${Math.round(startY)}) to (${Math.round(endX)}, ${Math.round(endY)}) over ${duration}ms`
1921
- }
1922
- ]
1751
+ content: [{ type: "text", text: "Error: Must provide either targetSelector or x,y coordinates" }]
1923
1752
  };
1924
1753
  } catch (e) {
1925
1754
  return {
1926
- content: [{ type: "text", text: `Error dragging and dropping: ${e}` }]
1755
+ content: [{ type: "text", text: `Error dragging: ${e}` }]
1927
1756
  };
1928
1757
  }
1929
1758
  };
1930
1759
 
1931
1760
  // src/tools/app-actions.tool.ts
1932
- import { z as z16 } from "zod";
1761
+ import { z as z12 } from "zod";
1933
1762
  var getAppStateToolDefinition = {
1934
1763
  name: "get_app_state",
1935
1764
  description: "gets the state of an app (not installed, not running, background, foreground)",
1936
1765
  inputSchema: {
1937
- bundleId: z16.string().describe("App bundle ID (e.g., com.example.app)")
1766
+ bundleId: z12.string().describe("App bundle ID (e.g., com.example.app)")
1938
1767
  }
1939
1768
  };
1940
1769
  var getAppStateTool = async (args) => {
@@ -1964,53 +1793,9 @@ var getAppStateTool = async (args) => {
1964
1793
  };
1965
1794
  }
1966
1795
  };
1967
- var activateAppToolDefinition = {
1968
- name: "activate_app",
1969
- description: "activates/brings an app to foreground",
1970
- inputSchema: {
1971
- bundleId: z16.string().describe("App bundle ID to activate (e.g., com.example.app)")
1972
- }
1973
- };
1974
- var activateAppTool = async (args) => {
1975
- try {
1976
- const browser = getBrowser();
1977
- const { bundleId } = args;
1978
- const appIdentifier = browser.isAndroid ? { appId: bundleId } : { bundleId };
1979
- await browser.execute("mobile: activateApp", appIdentifier);
1980
- return {
1981
- content: [{ type: "text", text: `Activated app: ${bundleId}` }]
1982
- };
1983
- } catch (e) {
1984
- return {
1985
- content: [{ type: "text", text: `Error activating app: ${e}` }]
1986
- };
1987
- }
1988
- };
1989
- var terminateAppToolDefinition = {
1990
- name: "terminate_app",
1991
- description: "terminates a running app",
1992
- inputSchema: {
1993
- bundleId: z16.string().describe("App bundle ID to terminate (e.g., com.example.app)")
1994
- }
1995
- };
1996
- var terminateAppTool = async (args) => {
1997
- try {
1998
- const browser = getBrowser();
1999
- const { bundleId } = args;
2000
- const appIdentifier = browser.isAndroid ? { appId: bundleId } : { bundleId };
2001
- await browser.execute("mobile: terminateApp", appIdentifier);
2002
- return {
2003
- content: [{ type: "text", text: `Terminated app: ${bundleId}` }]
2004
- };
2005
- } catch (e) {
2006
- return {
2007
- content: [{ type: "text", text: `Error terminating app: ${e}` }]
2008
- };
2009
- }
2010
- };
2011
1796
 
2012
1797
  // src/tools/context.tool.ts
2013
- import { z as z17 } from "zod";
1798
+ import { z as z13 } from "zod";
2014
1799
  var getContextsToolDefinition = {
2015
1800
  name: "get_contexts",
2016
1801
  description: "lists available contexts (NATIVE_APP, WEBVIEW)",
@@ -2025,7 +1810,7 @@ var switchContextToolDefinition = {
2025
1810
  name: "switch_context",
2026
1811
  description: "switches between native and webview contexts",
2027
1812
  inputSchema: {
2028
- context: z17.string().describe(
1813
+ context: z13.string().describe(
2029
1814
  'Context name to switch to (e.g., "NATIVE_APP", "WEBVIEW_com.example.app", or use index from get_contexts)'
2030
1815
  )
2031
1816
  }
@@ -2095,52 +1880,12 @@ var switchContextTool = async (args) => {
2095
1880
  };
2096
1881
 
2097
1882
  // src/tools/device.tool.ts
2098
- import { z as z18 } from "zod";
2099
- var getDeviceInfoToolDefinition = {
2100
- name: "get_device_info",
2101
- description: "gets device information (platform, version, screen size)",
2102
- inputSchema: {}
2103
- };
2104
- var getOrientationToolDefinition = {
2105
- name: "get_orientation",
2106
- description: "gets current device orientation",
2107
- inputSchema: {}
2108
- };
2109
- var lockDeviceToolDefinition = {
2110
- name: "lock_device",
2111
- description: "locks the device screen",
2112
- inputSchema: {}
2113
- };
2114
- var unlockDeviceToolDefinition = {
2115
- name: "unlock_device",
2116
- description: "unlocks the device screen",
2117
- inputSchema: {}
2118
- };
2119
- var isDeviceLockedToolDefinition = {
2120
- name: "is_device_locked",
2121
- description: "checks if device is locked",
2122
- inputSchema: {}
2123
- };
2124
- var shakeDeviceToolDefinition = {
2125
- name: "shake_device",
2126
- description: "shakes the device (iOS only)",
2127
- inputSchema: {}
2128
- };
1883
+ import { z as z14 } from "zod";
2129
1884
  var hideKeyboardToolDefinition = {
2130
1885
  name: "hide_keyboard",
2131
1886
  description: "hides the on-screen keyboard",
2132
1887
  inputSchema: {}
2133
1888
  };
2134
- var isKeyboardShownToolDefinition = {
2135
- name: "is_keyboard_shown",
2136
- description: "checks if keyboard is visible",
2137
- inputSchema: {}
2138
- };
2139
- var openNotificationsToolDefinition = {
2140
- name: "open_notifications",
2141
- description: "opens the notifications panel (Android only)",
2142
- inputSchema: {}
2143
- };
2144
1889
  var getGeolocationToolDefinition = {
2145
1890
  name: "get_geolocation",
2146
1891
  description: "gets current device geolocation",
@@ -2150,57 +1895,16 @@ var rotateDeviceToolDefinition = {
2150
1895
  name: "rotate_device",
2151
1896
  description: "rotates device to portrait or landscape orientation",
2152
1897
  inputSchema: {
2153
- orientation: z18.enum(["PORTRAIT", "LANDSCAPE"]).describe("Device orientation")
2154
- }
2155
- };
2156
- var sendKeysToolDefinition = {
2157
- name: "send_keys",
2158
- description: "sends keys to the app (Android only)",
2159
- inputSchema: {
2160
- keys: z18.array(z18.string()).describe('Array of keys to send (e.g., ["h", "e", "l", "l", "o"])')
2161
- }
2162
- };
2163
- var pressKeyCodeToolDefinition = {
2164
- name: "press_key_code",
2165
- description: "presses an Android key code (Android only)",
2166
- inputSchema: {
2167
- keyCode: z18.number().describe("Android key code (e.g., 4 for BACK, 3 for HOME)")
1898
+ orientation: z14.enum(["PORTRAIT", "LANDSCAPE"]).describe("Device orientation")
2168
1899
  }
2169
1900
  };
2170
1901
  var setGeolocationToolDefinition = {
2171
1902
  name: "set_geolocation",
2172
1903
  description: "sets device geolocation (latitude, longitude, altitude)",
2173
1904
  inputSchema: {
2174
- latitude: z18.number().min(-90).max(90).describe("Latitude coordinate"),
2175
- longitude: z18.number().min(-180).max(180).describe("Longitude coordinate"),
2176
- altitude: z18.number().optional().describe("Altitude in meters (optional)")
2177
- }
2178
- };
2179
- var getDeviceInfoTool = async () => {
2180
- try {
2181
- const browser = getBrowser();
2182
- const capabilities = browser.capabilities;
2183
- const windowSize = await browser.getWindowSize();
2184
- const info = {
2185
- platformName: capabilities.platformName,
2186
- platformVersion: capabilities["appium:platformVersion"],
2187
- deviceName: capabilities["appium:deviceName"],
2188
- automationName: capabilities["appium:automationName"],
2189
- screenSize: `${windowSize.width}x${windowSize.height}`
2190
- };
2191
- return {
2192
- content: [
2193
- {
2194
- type: "text",
2195
- text: `Device Info:
2196
- ${Object.entries(info).map(([key, value]) => ` ${key}: ${value}`).join("\n")}`
2197
- }
2198
- ]
2199
- };
2200
- } catch (e) {
2201
- return {
2202
- content: [{ type: "text", text: `Error getting device info: ${e}` }]
2203
- };
1905
+ latitude: z14.number().min(-90).max(90).describe("Latitude coordinate"),
1906
+ longitude: z14.number().min(-180).max(180).describe("Longitude coordinate"),
1907
+ altitude: z14.number().optional().describe("Altitude in meters (optional)")
2204
1908
  }
2205
1909
  };
2206
1910
  var rotateDeviceTool = async (args) => {
@@ -2217,99 +1921,6 @@ var rotateDeviceTool = async (args) => {
2217
1921
  };
2218
1922
  }
2219
1923
  };
2220
- var getOrientationTool = async () => {
2221
- try {
2222
- const browser = getBrowser();
2223
- const orientation = await browser.getOrientation();
2224
- return {
2225
- content: [{ type: "text", text: `Current orientation: ${orientation}` }]
2226
- };
2227
- } catch (e) {
2228
- return {
2229
- content: [{ type: "text", text: `Error getting orientation: ${e}` }]
2230
- };
2231
- }
2232
- };
2233
- var lockDeviceTool = async () => {
2234
- try {
2235
- const browser = getBrowser();
2236
- await browser.lock();
2237
- return {
2238
- content: [{ type: "text", text: "Device locked" }]
2239
- };
2240
- } catch (e) {
2241
- return {
2242
- content: [{ type: "text", text: `Error locking device: ${e}` }]
2243
- };
2244
- }
2245
- };
2246
- var unlockDeviceTool = async () => {
2247
- try {
2248
- const browser = getBrowser();
2249
- await browser.unlock();
2250
- return {
2251
- content: [{ type: "text", text: "Device unlocked" }]
2252
- };
2253
- } catch (e) {
2254
- return {
2255
- content: [{ type: "text", text: `Error unlocking device: ${e}` }]
2256
- };
2257
- }
2258
- };
2259
- var isDeviceLockedTool = async () => {
2260
- try {
2261
- const browser = getBrowser();
2262
- const isLocked = await browser.isLocked();
2263
- return {
2264
- content: [{ type: "text", text: `Device is ${isLocked ? "locked" : "unlocked"}` }]
2265
- };
2266
- } catch (e) {
2267
- return {
2268
- content: [{ type: "text", text: `Error checking lock status: ${e}` }]
2269
- };
2270
- }
2271
- };
2272
- var shakeDeviceTool = async () => {
2273
- try {
2274
- const browser = getBrowser();
2275
- await browser.shake();
2276
- return {
2277
- content: [{ type: "text", text: "Device shaken" }]
2278
- };
2279
- } catch (e) {
2280
- return {
2281
- content: [{ type: "text", text: `Error shaking device: ${e}` }]
2282
- };
2283
- }
2284
- };
2285
- var sendKeysTool = async (args) => {
2286
- try {
2287
- const browser = getBrowser();
2288
- const { keys } = args;
2289
- await browser.sendKeys(keys);
2290
- return {
2291
- content: [{ type: "text", text: `Sent keys: ${keys.join("")}` }]
2292
- };
2293
- } catch (e) {
2294
- return {
2295
- content: [{ type: "text", text: `Error sending keys: ${e}` }]
2296
- };
2297
- }
2298
- };
2299
- var pressKeyCodeTool = async (args) => {
2300
- try {
2301
- const browser = getBrowser();
2302
- const { keyCode } = args;
2303
- await browser.pressKeyCode(keyCode);
2304
- return {
2305
- content: [{ type: "text", text: `Pressed key code: ${keyCode}` }]
2306
- };
2307
- } catch (e) {
2308
- return {
2309
- content: [{ type: "text", text: `Error pressing key code: ${e}` }]
2310
- };
2311
- }
2312
- };
2313
1924
  var hideKeyboardTool = async () => {
2314
1925
  try {
2315
1926
  const browser = getBrowser();
@@ -2323,32 +1934,6 @@ var hideKeyboardTool = async () => {
2323
1934
  };
2324
1935
  }
2325
1936
  };
2326
- var isKeyboardShownTool = async () => {
2327
- try {
2328
- const browser = getBrowser();
2329
- const isShown = await browser.isKeyboardShown();
2330
- return {
2331
- content: [{ type: "text", text: `Keyboard is ${isShown ? "shown" : "hidden"}` }]
2332
- };
2333
- } catch (e) {
2334
- return {
2335
- content: [{ type: "text", text: `Error checking keyboard status: ${e}` }]
2336
- };
2337
- }
2338
- };
2339
- var openNotificationsTool = async () => {
2340
- try {
2341
- const browser = getBrowser();
2342
- await browser.openNotifications();
2343
- return {
2344
- content: [{ type: "text", text: "Opened notifications panel" }]
2345
- };
2346
- } catch (e) {
2347
- return {
2348
- content: [{ type: "text", text: `Error opening notifications: ${e}` }]
2349
- };
2350
- }
2351
- };
2352
1937
  var getGeolocationTool = async () => {
2353
1938
  try {
2354
1939
  const browser = getBrowser();
@@ -2393,6 +1978,69 @@ var setGeolocationTool = async (args) => {
2393
1978
  }
2394
1979
  };
2395
1980
 
1981
+ // src/tools/execute-script.tool.ts
1982
+ import { z as z15 } from "zod";
1983
+ var executeScriptToolDefinition = {
1984
+ name: "execute_script",
1985
+ description: `Executes JavaScript in browser or mobile commands via Appium.
1986
+
1987
+ **Browser:** Runs JavaScript in page context. Use 'return' to get values back.
1988
+ - Example: execute_script({ script: "return document.title" })
1989
+ - Example: execute_script({ script: "return window.scrollY" })
1990
+ - Example: execute_script({ script: "arguments[0].click()", args: ["#myButton"] })
1991
+
1992
+ **Mobile (Appium):** Executes mobile-specific commands using 'mobile: <command>' syntax.
1993
+ - Press key (Android): execute_script({ script: "mobile: pressKey", args: [{ keycode: 4 }] }) // BACK=4, HOME=3
1994
+ - Activate app: execute_script({ script: "mobile: activateApp", args: [{ appId: "com.example" }] })
1995
+ - Terminate app: execute_script({ script: "mobile: terminateApp", args: [{ appId: "com.example" }] })
1996
+ - Deep link: execute_script({ script: "mobile: deepLink", args: [{ url: "myapp://screen", package: "com.example" }] })
1997
+ - Shell command (Android): execute_script({ script: "mobile: shell", args: [{ command: "dumpsys", args: ["battery"] }] })`,
1998
+ inputSchema: {
1999
+ script: z15.string().describe('JavaScript code (browser) or mobile command string like "mobile: pressKey" (Appium)'),
2000
+ args: z15.array(z15.any()).optional().describe("Arguments to pass to the script. For browser: element selectors or values. For mobile commands: command-specific parameters as objects.")
2001
+ }
2002
+ };
2003
+ var executeScriptTool = async (args) => {
2004
+ try {
2005
+ const browser = getBrowser();
2006
+ const { script, args: scriptArgs = [] } = args;
2007
+ const resolvedArgs = await Promise.all(
2008
+ scriptArgs.map(async (arg) => {
2009
+ if (typeof arg === "string" && !script.startsWith("mobile:")) {
2010
+ try {
2011
+ const element = await browser.$(arg);
2012
+ if (await element.isExisting()) {
2013
+ return element;
2014
+ }
2015
+ } catch {
2016
+ }
2017
+ }
2018
+ return arg;
2019
+ })
2020
+ );
2021
+ const result = await browser.execute(script, ...resolvedArgs);
2022
+ let resultText;
2023
+ if (result === void 0 || result === null) {
2024
+ resultText = "Script executed successfully (no return value)";
2025
+ } else if (typeof result === "object") {
2026
+ try {
2027
+ resultText = `Result: ${JSON.stringify(result, null, 2)}`;
2028
+ } catch {
2029
+ resultText = `Result: ${String(result)}`;
2030
+ }
2031
+ } else {
2032
+ resultText = `Result: ${result}`;
2033
+ }
2034
+ return {
2035
+ content: [{ type: "text", text: resultText }]
2036
+ };
2037
+ } catch (e) {
2038
+ return {
2039
+ content: [{ type: "text", text: `Error executing script: ${e}` }]
2040
+ };
2041
+ }
2042
+ };
2043
+
2396
2044
  // package.json
2397
2045
  var package_default = {
2398
2046
  name: "@wdio/mcp",
@@ -2401,7 +2049,7 @@ var package_default = {
2401
2049
  type: "git",
2402
2050
  url: "git://github.com/webdriverio/mcp.git"
2403
2051
  },
2404
- version: "1.5.1",
2052
+ version: "1.6.1",
2405
2053
  description: "MCP server with WebdriverIO for browser and mobile app automation (iOS/Android via Appium)",
2406
2054
  main: "./lib/server.js",
2407
2055
  module: "./lib/server.js",
@@ -2433,6 +2081,7 @@ var package_default = {
2433
2081
  "@wdio/protocols": "^9.16.2",
2434
2082
  "@xmldom/xmldom": "^0.8.11",
2435
2083
  "puppeteer-core": "^24.35.0",
2084
+ sharp: "^0.34.5",
2436
2085
  webdriverio: "9.23",
2437
2086
  zod: "^4.3.5"
2438
2087
  },
@@ -2480,42 +2129,25 @@ registerTool(closeSessionToolDefinition, closeSessionTool);
2480
2129
  registerTool(navigateToolDefinition, navigateTool);
2481
2130
  registerTool(getVisibleElementsToolDefinition, getVisibleElementsTool);
2482
2131
  registerTool(getAccessibilityToolDefinition, getAccessibilityTreeTool);
2483
- registerTool(scrollDownToolDefinition, scrollDownTool);
2484
- registerTool(scrollUpToolDefinition, scrollUpTool);
2485
- registerTool(findElementToolDefinition, findElementTool);
2132
+ registerTool(scrollToolDefinition, scrollTool);
2486
2133
  registerTool(clickToolDefinition, clickTool);
2487
- registerTool(clickViaTextToolDefinition, clickToolViaText);
2488
2134
  registerTool(setValueToolDefinition, setValueTool);
2489
- registerTool(getElementTextToolDefinition, getElementTextTool);
2490
- registerTool(isDisplayedToolDefinition, isDisplayedTool);
2491
2135
  registerTool(takeScreenshotToolDefinition, takeScreenshotTool);
2492
2136
  registerTool(getCookiesToolDefinition, getCookiesTool);
2493
2137
  registerTool(setCookieToolDefinition, setCookieTool);
2494
2138
  registerTool(deleteCookiesToolDefinition, deleteCookiesTool);
2495
2139
  registerTool(tapElementToolDefinition, tapElementTool);
2496
2140
  registerTool(swipeToolDefinition, swipeTool);
2497
- registerTool(longPressToolDefinition, longPressTool);
2498
2141
  registerTool(dragAndDropToolDefinition, dragAndDropTool);
2499
2142
  registerTool(getAppStateToolDefinition, getAppStateTool);
2500
- registerTool(activateAppToolDefinition, activateAppTool);
2501
- registerTool(terminateAppToolDefinition, terminateAppTool);
2502
2143
  registerTool(getContextsToolDefinition, getContextsTool);
2503
2144
  registerTool(getCurrentContextToolDefinition, getCurrentContextTool);
2504
2145
  registerTool(switchContextToolDefinition, switchContextTool);
2505
- registerTool(getDeviceInfoToolDefinition, getDeviceInfoTool);
2506
2146
  registerTool(rotateDeviceToolDefinition, rotateDeviceTool);
2507
- registerTool(getOrientationToolDefinition, getOrientationTool);
2508
- registerTool(lockDeviceToolDefinition, lockDeviceTool);
2509
- registerTool(unlockDeviceToolDefinition, unlockDeviceTool);
2510
- registerTool(isDeviceLockedToolDefinition, isDeviceLockedTool);
2511
- registerTool(shakeDeviceToolDefinition, shakeDeviceTool);
2512
- registerTool(sendKeysToolDefinition, sendKeysTool);
2513
- registerTool(pressKeyCodeToolDefinition, pressKeyCodeTool);
2514
2147
  registerTool(hideKeyboardToolDefinition, hideKeyboardTool);
2515
- registerTool(isKeyboardShownToolDefinition, isKeyboardShownTool);
2516
- registerTool(openNotificationsToolDefinition, openNotificationsTool);
2517
2148
  registerTool(getGeolocationToolDefinition, getGeolocationTool);
2518
2149
  registerTool(setGeolocationToolDefinition, setGeolocationTool);
2150
+ registerTool(executeScriptToolDefinition, executeScriptTool);
2519
2151
  async function main() {
2520
2152
  const transport = new StdioServerTransport();
2521
2153
  await server.connect(transport);