@wdio/mcp 2.5.0 → 2.5.2

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
@@ -1,7 +1,83 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // package.json
4
+ var package_default = {
5
+ name: "@wdio/mcp",
6
+ author: "Vince Graics",
7
+ repository: {
8
+ type: "git",
9
+ url: "git://github.com/webdriverio/mcp.git"
10
+ },
11
+ version: "2.5.1",
12
+ description: "MCP server with WebdriverIO for browser and mobile app automation (iOS/Android via Appium)",
13
+ main: "./lib/server.js",
14
+ module: "./lib/server.js",
15
+ types: "./lib/server.d.ts",
16
+ exports: {
17
+ ".": {
18
+ import: "./lib/server.js",
19
+ types: "./lib/server.d.ts"
20
+ },
21
+ "./snapshot": {
22
+ import: "./lib/snapshot.js",
23
+ types: "./lib/snapshot.d.ts"
24
+ }
25
+ },
26
+ bin: {
27
+ "wdio-mcp": "lib/server.js"
28
+ },
29
+ license: "MIT",
30
+ publishConfig: {
31
+ access: "public"
32
+ },
33
+ type: "module",
34
+ files: [
35
+ "lib",
36
+ "README.md"
37
+ ],
38
+ scripts: {
39
+ prebundle: "rimraf lib --glob ./*.tgz",
40
+ bundle: "tsup && shx chmod +x lib/server.js",
41
+ postbundle: "npm pack",
42
+ lint: "eslint src/ --fix && tsc --noEmit",
43
+ "lint:tests": "eslint tests/ --fix && tsc -p tsconfig.test.json --noEmit",
44
+ start: "node lib/server.js",
45
+ dev: "tsx --watch src/server.ts",
46
+ prepare: "husky",
47
+ test: "vitest run"
48
+ },
49
+ dependencies: {
50
+ "@modelcontextprotocol/sdk": "1.27",
51
+ "@toon-format/toon": "^2.1.0",
52
+ "@wdio/protocols": "^9.16.2",
53
+ "@xmldom/xmldom": "^0.8.11",
54
+ "puppeteer-core": "^24.35.0",
55
+ sharp: "^0.34.5",
56
+ webdriverio: "9.24",
57
+ xpath: "^0.0.34",
58
+ zod: "^4.3.5"
59
+ },
60
+ devDependencies: {
61
+ "@release-it/conventional-changelog": "^10.0.4",
62
+ "@types/node": "^20.11.0",
63
+ "@wdio/eslint": "^0.1.3",
64
+ "@wdio/types": "^9.20.0",
65
+ eslint: "^9.39.2",
66
+ "happy-dom": "^20.7.0",
67
+ husky: "^9.1.7",
68
+ "release-it": "^19.2.3",
69
+ rimraf: "^6.1.2",
70
+ shx: "^0.4.0",
71
+ tsup: "^8.5.1",
72
+ tsx: "^4.21.0",
73
+ typescript: "5.9",
74
+ vitest: "^4.0.18"
75
+ },
76
+ packageManager: "pnpm@10.32.1"
77
+ };
78
+
3
79
  // src/server.ts
4
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
80
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
5
81
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
82
 
7
83
  // src/tools/browser.tool.ts
@@ -420,7 +496,7 @@ var getState = () => {
420
496
  var startAppTool = async (args) => {
421
497
  try {
422
498
  const {
423
- platform,
499
+ platform: platform2,
424
500
  appPath,
425
501
  deviceName,
426
502
  platformVersion,
@@ -451,7 +527,7 @@ var startAppTool = async (args) => {
451
527
  port: appiumPort,
452
528
  path: appiumPath
453
529
  });
454
- const capabilities = platform === "iOS" ? buildIOSCapabilities(appPath, {
530
+ const capabilities = platform2 === "iOS" ? buildIOSCapabilities(appPath, {
455
531
  deviceName,
456
532
  platformVersion,
457
533
  automationName: automationName || "XCUITest",
@@ -495,7 +571,7 @@ var startAppTool = async (args) => {
495
571
  const state2 = getState();
496
572
  state2.browsers.set(sessionId, browser);
497
573
  state2.sessionMetadata.set(sessionId, {
498
- type: platform.toLowerCase(),
574
+ type: platform2.toLowerCase(),
499
575
  capabilities: mergedCapabilities,
500
576
  isAttached: shouldAutoDetach
501
577
  });
@@ -515,7 +591,7 @@ var startAppTool = async (args) => {
515
591
  }
516
592
  state2.sessionHistory.set(sessionId, {
517
593
  sessionId,
518
- type: platform.toLowerCase(),
594
+ type: platform2.toLowerCase(),
519
595
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
520
596
  capabilities: mergedCapabilities,
521
597
  appiumConfig: { hostname: serverConfig.hostname, port: serverConfig.port, path: serverConfig.path },
@@ -529,7 +605,7 @@ App: ${appPath}` : "\nApp: (connected to running app)";
529
605
  content: [
530
606
  {
531
607
  type: "text",
532
- text: `${platform} app session started with sessionId: ${sessionId}
608
+ text: `${platform2} app session started with sessionId: ${sessionId}
533
609
  Device: ${deviceName}${appInfo}
534
610
  Appium Server: ${serverConfig.hostname}:${serverConfig.port}${serverConfig.path}${detachNote}`
535
611
  }
@@ -1108,16 +1184,16 @@ function isInteractableElement(element, isNative, automationName) {
1108
1184
  }
1109
1185
  return false;
1110
1186
  }
1111
- function isLayoutContainer(element, platform) {
1112
- const containerList = platform === "android" ? ANDROID_LAYOUT_CONTAINERS : IOS_LAYOUT_CONTAINERS;
1187
+ function isLayoutContainer(element, platform2) {
1188
+ const containerList = platform2 === "android" ? ANDROID_LAYOUT_CONTAINERS : IOS_LAYOUT_CONTAINERS;
1113
1189
  return matchesTagList(element.tagName, containerList);
1114
1190
  }
1115
- function hasMeaningfulContent(element, platform) {
1191
+ function hasMeaningfulContent(element, platform2) {
1116
1192
  const attrs = element.attributes;
1117
1193
  if (attrs.text && attrs.text.trim() !== "" && attrs.text !== "null") {
1118
1194
  return true;
1119
1195
  }
1120
- if (platform === "android") {
1196
+ if (platform2 === "android") {
1121
1197
  if (attrs["content-desc"] && attrs["content-desc"].trim() !== "" && attrs["content-desc"] !== "null") {
1122
1198
  return true;
1123
1199
  }
@@ -1164,8 +1240,8 @@ function shouldIncludeElement(element, filters, isNative, automationName) {
1164
1240
  }
1165
1241
  return true;
1166
1242
  }
1167
- function getDefaultFilters(platform, includeContainers = false) {
1168
- const layoutContainers = platform === "android" ? ANDROID_LAYOUT_CONTAINERS : IOS_LAYOUT_CONTAINERS;
1243
+ function getDefaultFilters(platform2, includeContainers = false) {
1244
+ const layoutContainers = platform2 === "android" ? ANDROID_LAYOUT_CONTAINERS : IOS_LAYOUT_CONTAINERS;
1169
1245
  return {
1170
1246
  excludeTagNames: includeContainers ? ["hierarchy"] : ["hierarchy", ...layoutContainers],
1171
1247
  fetchableOnly: !includeContainers,
@@ -1179,7 +1255,7 @@ function isValidValue(value) {
1179
1255
  return value !== void 0 && value !== null && value !== "null" && value.trim() !== "";
1180
1256
  }
1181
1257
  function escapeText(text) {
1182
- return text.replace(/"/g, '\\"').replace(/\n/g, "\\n");
1258
+ return text.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
1183
1259
  }
1184
1260
  function escapeXPathValue(value) {
1185
1261
  if (!value.includes("'")) {
@@ -1535,8 +1611,8 @@ function locatorsToObject(locators) {
1535
1611
  }
1536
1612
 
1537
1613
  // src/locators/index.ts
1538
- function parseBounds(element, platform) {
1539
- return platform === "android" ? parseAndroidBounds(element.attributes.bounds || "") : parseIOSBounds(element.attributes);
1614
+ function parseBounds(element, platform2) {
1615
+ return platform2 === "android" ? parseAndroidBounds(element.attributes.bounds || "") : parseIOSBounds(element.attributes);
1540
1616
  }
1541
1617
  function isWithinViewport(bounds, viewport) {
1542
1618
  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;
@@ -1675,16 +1751,16 @@ async function getViewportSize(browser) {
1675
1751
  return { width: 9999, height: 9999 };
1676
1752
  }
1677
1753
  }
1678
- async function getMobileVisibleElements(browser, platform, options = {}) {
1754
+ async function getMobileVisibleElements(browser, platform2, options = {}) {
1679
1755
  const { includeContainers = false, includeBounds = false, filterOptions } = options;
1680
1756
  const viewportSize = await getViewportSize(browser);
1681
1757
  const pageSource = await browser.getPageSource();
1682
1758
  const filters = {
1683
- ...getDefaultFilters(platform, includeContainers),
1759
+ ...getDefaultFilters(platform2, includeContainers),
1684
1760
  ...filterOptions
1685
1761
  };
1686
1762
  const elements = generateAllElementLocators(pageSource, {
1687
- platform,
1763
+ platform: platform2,
1688
1764
  viewportSize,
1689
1765
  filters
1690
1766
  });
@@ -1717,8 +1793,8 @@ var getVisibleElementsTool = async (args) => {
1717
1793
  } = args || {};
1718
1794
  let elements;
1719
1795
  if (browser.isAndroid || browser.isIOS) {
1720
- const platform = browser.isAndroid ? "android" : "ios";
1721
- elements = await getMobileVisibleElements(browser, platform, { includeContainers, includeBounds });
1796
+ const platform2 = browser.isAndroid ? "android" : "ios";
1797
+ elements = await getMobileVisibleElements(browser, platform2, { includeContainers, includeBounds });
1722
1798
  } else {
1723
1799
  elements = await getInteractableBrowserElements(browser, { includeBounds });
1724
1800
  }
@@ -2644,57 +2720,78 @@ var attachBrowserToolDefinition = {
2644
2720
  name: "attach_browser",
2645
2721
  description: `Attach to a Chrome instance already running with --remote-debugging-port.
2646
2722
 
2647
- Start Chrome first (quit any running Chrome instance before launching):
2648
-
2649
- macOS \u2014 with real profile (preserves extensions, cookies, logins):
2650
- pkill -x "Google Chrome" && sleep 1
2651
- /Applications/Google Chrome.app/Contents/MacOS/Google Chrome --remote-debugging-port=9222 --user-data-dir="$HOME/Library/Application Support/Google/Chrome" --profile-directory=Default &
2652
-
2653
- macOS \u2014 with fresh profile (lightweight, no extensions):
2654
- pkill -x "Google Chrome" && sleep 1
2655
- /Applications/Google Chrome.app/Contents/MacOS/Google Chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug &
2656
-
2657
- Linux \u2014 with real profile:
2658
- google-chrome --remote-debugging-port=9222 --user-data-dir="$HOME/.config/google-chrome" --profile-directory=Default &
2659
-
2660
- Linux \u2014 with fresh profile:
2661
- google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug &
2662
-
2663
- Verify Chrome is ready: curl http://localhost:9222/json/version
2664
-
2665
- Then call attach_browser() to hand control to the AI. All other tools (navigate, click, get_visible_elements, etc.) will work on the attached session. Use close_session() to detach without closing Chrome.`,
2723
+ Use launch_chrome() first to prepare and launch Chrome with remote debugging enabled.`,
2666
2724
  inputSchema: {
2667
2725
  port: z16.number().default(9222).describe("Chrome remote debugging port (default: 9222)"),
2668
2726
  host: z16.string().default("localhost").describe("Host where Chrome is running (default: localhost)"),
2669
- userDataDir: z16.string().default("/tmp/chrome-debug").describe('Chrome user data directory \u2014 must match the --user-data-dir used when launching Chrome. Use your real profile path (e.g. "$HOME/Library/Application Support/Google/Chrome") to preserve extensions and logins, or /tmp/chrome-debug for a fresh profile (default: /tmp/chrome-debug)'),
2670
2727
  navigationUrl: z16.string().optional().describe("URL to navigate to immediately after attaching")
2671
2728
  }
2672
2729
  };
2673
- async function getActiveTabUrl(host, port) {
2730
+ async function closeStaleMappers(host, port) {
2674
2731
  try {
2675
2732
  const res = await fetch(`http://${host}:${port}/json`);
2676
- const tabs = await res.json();
2677
- const page = tabs.find((t) => t.type === "page" && t.url && !t.url.startsWith("devtools://"));
2678
- return page?.url ?? null;
2733
+ const targets = await res.json();
2734
+ const mappers = targets.filter((t) => t.title?.includes("BiDi"));
2735
+ await Promise.all(mappers.map((t) => fetch(`http://${host}:${port}/json/close/${t.id}`)));
2736
+ const pages = targets.filter((t) => t.type === "page" && !t.title?.includes("BiDi"));
2737
+ return { activeTabUrl: pages[0]?.url, allTabUrls: pages.map((t) => t.url) };
2679
2738
  } catch {
2680
- return null;
2739
+ return { activeTabUrl: void 0, allTabUrls: [] };
2740
+ }
2741
+ }
2742
+ async function restoreAndSwitchToActiveTab(browser, activeTabUrl, allTabUrls) {
2743
+ const handles = await browser.getWindowHandles();
2744
+ const currentUrls = [];
2745
+ for (const handle of handles) {
2746
+ await browser.switchToWindow(handle);
2747
+ currentUrls.push(await browser.getUrl());
2748
+ }
2749
+ const missingUrls = allTabUrls.filter((u) => !currentUrls.includes(u));
2750
+ let missingIdx = 0;
2751
+ for (let i = 0; i < handles.length; i++) {
2752
+ if (currentUrls[i] === "about:blank" && missingIdx < missingUrls.length) {
2753
+ await browser.switchToWindow(handles[i]);
2754
+ await browser.url(missingUrls[missingIdx]);
2755
+ currentUrls[i] = missingUrls[missingIdx++];
2756
+ }
2757
+ }
2758
+ for (let i = 0; i < handles.length; i++) {
2759
+ if (currentUrls[i] === activeTabUrl) {
2760
+ await browser.switchToWindow(handles[i]);
2761
+ break;
2762
+ }
2681
2763
  }
2682
2764
  }
2765
+ async function waitForCDP(host, port, timeoutMs = 1e4) {
2766
+ const deadline = Date.now() + timeoutMs;
2767
+ while (Date.now() < deadline) {
2768
+ try {
2769
+ const res = await fetch(`http://${host}:${port}/json/version`);
2770
+ if (res.ok) return;
2771
+ } catch {
2772
+ }
2773
+ await new Promise((r) => setTimeout(r, 300));
2774
+ }
2775
+ throw new Error(`Chrome did not expose CDP on ${host}:${port} within ${timeoutMs}ms`);
2776
+ }
2683
2777
  var attachBrowserTool = async ({
2684
2778
  port = 9222,
2685
2779
  host = "localhost",
2686
- userDataDir = "/tmp/chrome-debug",
2687
2780
  navigationUrl
2688
2781
  }) => {
2689
2782
  try {
2690
2783
  const state2 = getBrowser.__state;
2691
- const activeUrl = navigationUrl ?? await getActiveTabUrl(host, port);
2784
+ await waitForCDP(host, port);
2785
+ const { activeTabUrl, allTabUrls } = await closeStaleMappers(host, port);
2692
2786
  const browser = await remote3({
2787
+ connectionRetryTimeout: 3e4,
2788
+ connectionRetryCount: 3,
2693
2789
  capabilities: {
2694
2790
  browserName: "chrome",
2791
+ unhandledPromptBehavior: "dismiss",
2792
+ webSocketUrl: false,
2695
2793
  "goog:chromeOptions": {
2696
- debuggerAddress: `${host}:${port}`,
2697
- args: [`--user-data-dir=${userDataDir}`]
2794
+ debuggerAddress: `${host}:${port}`
2698
2795
  }
2699
2796
  }
2700
2797
  });
@@ -2713,14 +2810,15 @@ var attachBrowserTool = async ({
2713
2810
  capabilities: {
2714
2811
  browserName: "chrome",
2715
2812
  "goog:chromeOptions": {
2716
- debuggerAddress: `${host}:${port}`,
2717
- args: [`--user-data-dir=${userDataDir}`]
2813
+ debuggerAddress: `${host}:${port}`
2718
2814
  }
2719
2815
  },
2720
2816
  steps: []
2721
2817
  });
2722
- if (activeUrl) {
2723
- await browser.url(activeUrl);
2818
+ if (navigationUrl) {
2819
+ await browser.url(navigationUrl);
2820
+ } else if (activeTabUrl) {
2821
+ await restoreAndSwitchToActiveTab(browser, activeTabUrl, allTabUrls);
2724
2822
  }
2725
2823
  const title = await browser.getTitle();
2726
2824
  const url = await browser.getUrl();
@@ -2740,8 +2838,127 @@ Current page: "${title}" (${url})`
2740
2838
  }
2741
2839
  };
2742
2840
 
2743
- // src/tools/emulate-device.tool.ts
2841
+ // src/tools/launch-chrome.tool.ts
2842
+ import { spawn } from "child_process";
2843
+ import { copyFileSync, cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
2844
+ import { homedir, platform, tmpdir } from "os";
2845
+ import { join } from "path";
2744
2846
  import { z as z17 } from "zod";
2847
+ var USER_DATA_DIR = join(tmpdir(), "chrome-debug");
2848
+ var launchChromeToolDefinition = {
2849
+ name: "launch_chrome",
2850
+ description: `Prepares and launches Chrome with remote debugging enabled so attach_browser() can connect.
2851
+
2852
+ Two modes:
2853
+
2854
+ newInstance (default): Opens a Chrome window alongside your existing one using a separate
2855
+ profile dir. Your current Chrome session is untouched.
2856
+
2857
+ freshSession: Launches Chrome with an empty profile (no cookies, no logins).
2858
+
2859
+ Use copyProfileFiles: true to carry over your cookies and logins into the debug session.
2860
+ Note: changes made during the session won't sync back to your main profile.
2861
+
2862
+ After this tool succeeds, call attach_browser() to connect.`,
2863
+ inputSchema: {
2864
+ port: z17.number().default(9222).describe("Remote debugging port (default: 9222)"),
2865
+ mode: z17.enum(["newInstance", "freshSession"]).default("newInstance").describe(
2866
+ "newInstance: open alongside existing Chrome | freshSession: clean profile"
2867
+ ),
2868
+ copyProfileFiles: z17.boolean().default(false).describe(
2869
+ "Copy your Default Chrome profile (cookies, logins) into the debug session."
2870
+ )
2871
+ }
2872
+ };
2873
+ function isMac() {
2874
+ return platform() === "darwin";
2875
+ }
2876
+ function chromeExec() {
2877
+ if (isMac()) return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome";
2878
+ if (platform() === "win32") {
2879
+ const candidates = [
2880
+ join("C:", "Program Files", "Google", "Chrome", "Application", "chrome.exe"),
2881
+ join("C:", "Program Files (x86)", "Google", "Chrome", "Application", "chrome.exe")
2882
+ ];
2883
+ return candidates.find((p) => existsSync(p)) ?? candidates[0];
2884
+ }
2885
+ return "google-chrome";
2886
+ }
2887
+ function defaultProfileDir() {
2888
+ const home = homedir();
2889
+ if (isMac()) return join(home, "Library", "Application Support", "Google", "Chrome");
2890
+ if (platform() === "win32") return join(home, "AppData", "Local", "Google", "Chrome", "User Data");
2891
+ return join(home, ".config", "google-chrome");
2892
+ }
2893
+ function copyProfile() {
2894
+ const srcDir = defaultProfileDir();
2895
+ rmSync(USER_DATA_DIR, { recursive: true, force: true });
2896
+ mkdirSync(USER_DATA_DIR, { recursive: true });
2897
+ copyFileSync(join(srcDir, "Local State"), join(USER_DATA_DIR, "Local State"));
2898
+ cpSync(join(srcDir, "Default"), join(USER_DATA_DIR, "Default"), { recursive: true });
2899
+ for (const f of ["SingletonLock", "SingletonCookie", "SingletonSocket"]) {
2900
+ rmSync(join(USER_DATA_DIR, f), { force: true });
2901
+ }
2902
+ for (const f of ["Current Session", "Current Tabs", "Last Session", "Last Tabs"]) {
2903
+ rmSync(join(USER_DATA_DIR, "Default", f), { force: true });
2904
+ }
2905
+ writeFileSync(join(USER_DATA_DIR, "First Run"), "");
2906
+ }
2907
+ function launchChrome(port) {
2908
+ spawn(chromeExec(), [
2909
+ `--remote-debugging-port=${port}`,
2910
+ `--user-data-dir=${USER_DATA_DIR}`,
2911
+ "--profile-directory=Default",
2912
+ "--no-first-run",
2913
+ "--disable-session-crashed-bubble"
2914
+ ], { detached: true, stdio: "ignore" }).unref();
2915
+ }
2916
+ async function waitForCDP2(port, timeoutMs = 15e3) {
2917
+ const deadline = Date.now() + timeoutMs;
2918
+ while (Date.now() < deadline) {
2919
+ try {
2920
+ const res = await fetch(`http://localhost:${port}/json/version`);
2921
+ if (res.ok) return;
2922
+ } catch {
2923
+ }
2924
+ await new Promise((r) => setTimeout(r, 300));
2925
+ }
2926
+ throw new Error(`Chrome did not expose CDP on port ${port} within ${timeoutMs}ms`);
2927
+ }
2928
+ var launchChromeTool = async ({
2929
+ port = 9222,
2930
+ mode = "newInstance",
2931
+ copyProfileFiles = false
2932
+ }) => {
2933
+ const warnings = [];
2934
+ const notes = [];
2935
+ try {
2936
+ if (copyProfileFiles) {
2937
+ warnings.push("\u26A0\uFE0F Cookies and logins were copied at this moment. Changes during this session won't sync back to your main profile.");
2938
+ copyProfile();
2939
+ } else {
2940
+ notes.push(mode === "newInstance" ? "No profile copied \u2014 this instance starts with no cookies or logins." : "Fresh profile \u2014 no existing cookies or logins.");
2941
+ rmSync(USER_DATA_DIR, { recursive: true, force: true });
2942
+ mkdirSync(USER_DATA_DIR, { recursive: true });
2943
+ }
2944
+ launchChrome(port);
2945
+ await waitForCDP2(port);
2946
+ const lines = [
2947
+ `Chrome launched on port ${port} (mode: ${mode}).`,
2948
+ ...warnings,
2949
+ ...notes
2950
+ ];
2951
+ return { content: [{ type: "text", text: lines.join("\n") }] };
2952
+ } catch (e) {
2953
+ return {
2954
+ isError: true,
2955
+ content: [{ type: "text", text: `Error launching Chrome: ${e}` }]
2956
+ };
2957
+ }
2958
+ };
2959
+
2960
+ // src/tools/emulate-device.tool.ts
2961
+ import { z as z18 } from "zod";
2745
2962
  var restoreFunctions = /* @__PURE__ */ new Map();
2746
2963
  var emulateDeviceToolDefinition = {
2747
2964
  name: "emulate_device",
@@ -2754,7 +2971,7 @@ Usage:
2754
2971
  emulate_device({ device: "iPhone 15" }) \u2014 activate emulation
2755
2972
  emulate_device({ device: "reset" }) \u2014 restore desktop defaults`,
2756
2973
  inputSchema: {
2757
- device: z17.string().optional().describe(
2974
+ device: z18.string().optional().describe(
2758
2975
  'Device preset name (e.g. "iPhone 15", "Pixel 7"). Omit to list available presets. Pass "reset" to restore desktop defaults.'
2759
2976
  )
2760
2977
  }
@@ -2834,85 +3051,6 @@ ${names.join("\n")}` }]
2834
3051
  }
2835
3052
  };
2836
3053
 
2837
- // package.json
2838
- var package_default = {
2839
- name: "@wdio/mcp",
2840
- author: "Vince Graics",
2841
- repository: {
2842
- type: "git",
2843
- url: "git://github.com/webdriverio/mcp.git"
2844
- },
2845
- version: "2.4.1",
2846
- description: "MCP server with WebdriverIO for browser and mobile app automation (iOS/Android via Appium)",
2847
- main: "./lib/server.js",
2848
- module: "./lib/server.js",
2849
- types: "./lib/server.d.ts",
2850
- exports: {
2851
- ".": {
2852
- import: "./lib/server.js",
2853
- types: "./lib/server.d.ts"
2854
- },
2855
- "./snapshot": {
2856
- import: "./lib/snapshot.js",
2857
- types: "./lib/snapshot.d.ts"
2858
- }
2859
- },
2860
- bin: {
2861
- "wdio-mcp": "lib/server.js"
2862
- },
2863
- license: "MIT",
2864
- publishConfig: {
2865
- access: "public"
2866
- },
2867
- type: "module",
2868
- files: [
2869
- "lib",
2870
- "README.md"
2871
- ],
2872
- scripts: {
2873
- prebundle: "rimraf lib --glob ./*.tgz",
2874
- bundle: "tsup && shx chmod +x lib/server.js",
2875
- postbundle: "npm pack",
2876
- lint: "eslint src/ --fix && tsc --noEmit",
2877
- "lint:tests": "eslint tests/ --fix && tsc -p tsconfig.test.json --noEmit",
2878
- start: "node lib/server.js",
2879
- dev: "tsx --watch src/server.ts",
2880
- prepare: "husky",
2881
- test: "vitest run"
2882
- },
2883
- dependencies: {
2884
- "@modelcontextprotocol/sdk": "1.27",
2885
- "@toon-format/toon": "^2.1.0",
2886
- "@wdio/protocols": "^9.16.2",
2887
- "@xmldom/xmldom": "^0.8.11",
2888
- "puppeteer-core": "^24.35.0",
2889
- sharp: "^0.34.5",
2890
- webdriverio: "9.24",
2891
- xpath: "^0.0.34",
2892
- zod: "^4.3.5"
2893
- },
2894
- devDependencies: {
2895
- "@release-it/conventional-changelog": "^10.0.4",
2896
- "@types/node": "^20.11.0",
2897
- "@wdio/eslint": "^0.1.3",
2898
- "@wdio/types": "^9.20.0",
2899
- eslint: "^9.39.2",
2900
- "happy-dom": "^20.7.0",
2901
- husky: "^9.1.7",
2902
- "release-it": "^19.2.3",
2903
- rimraf: "^6.1.2",
2904
- shx: "^0.4.0",
2905
- tsup: "^8.5.1",
2906
- tsx: "^4.21.0",
2907
- typescript: "5.9",
2908
- vitest: "^4.0.18"
2909
- },
2910
- packageManager: "pnpm@10.12.4"
2911
- };
2912
-
2913
- // src/server.ts
2914
- import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
2915
-
2916
3054
  // src/recording/step-recorder.ts
2917
3055
  function getState2() {
2918
3056
  return getBrowser.__state;
@@ -3100,6 +3238,7 @@ var registerTool = (definition, callback) => server.registerTool(definition.name
3100
3238
  registerTool(startBrowserToolDefinition, withRecording("start_browser", startBrowserTool));
3101
3239
  registerTool(startAppToolDefinition, withRecording("start_app_session", startAppTool));
3102
3240
  registerTool(closeSessionToolDefinition, closeSessionTool);
3241
+ registerTool(launchChromeToolDefinition, withRecording("launch_chrome", launchChromeTool));
3103
3242
  registerTool(attachBrowserToolDefinition, withRecording("attach_browser", attachBrowserTool));
3104
3243
  registerTool(emulateDeviceToolDefinition, emulateDeviceTool);
3105
3244
  registerTool(navigateToolDefinition, withRecording("navigate", navigateTool));
@@ -3139,7 +3278,11 @@ server.registerResource(
3139
3278
  async () => {
3140
3279
  const payload = buildCurrentSessionSteps();
3141
3280
  return {
3142
- contents: [{ uri: "wdio://session/current/steps", mimeType: "application/json", text: payload?.stepsJson ?? '{"error":"No active session"}' }]
3281
+ contents: [{
3282
+ uri: "wdio://session/current/steps",
3283
+ mimeType: "application/json",
3284
+ text: payload?.stepsJson ?? '{"error":"No active session"}'
3285
+ }]
3143
3286
  };
3144
3287
  }
3145
3288
  );
@@ -3150,7 +3293,11 @@ server.registerResource(
3150
3293
  async () => {
3151
3294
  const payload = buildCurrentSessionSteps();
3152
3295
  return {
3153
- contents: [{ uri: "wdio://session/current/code", mimeType: "text/plain", text: payload?.generatedJs ?? "// No active session" }]
3296
+ contents: [{
3297
+ uri: "wdio://session/current/code",
3298
+ mimeType: "text/plain",
3299
+ text: payload?.generatedJs ?? "// No active session"
3300
+ }]
3154
3301
  };
3155
3302
  }
3156
3303
  );
@@ -3161,7 +3308,11 @@ server.registerResource(
3161
3308
  async (uri, { sessionId }) => {
3162
3309
  const payload = buildSessionStepsById(sessionId);
3163
3310
  return {
3164
- contents: [{ uri: uri.href, mimeType: "application/json", text: payload?.stepsJson ?? `{"error":"Session not found: ${sessionId}"}` }]
3311
+ contents: [{
3312
+ uri: uri.href,
3313
+ mimeType: "application/json",
3314
+ text: payload?.stepsJson ?? `{"error":"Session not found: ${sessionId}"}`
3315
+ }]
3165
3316
  };
3166
3317
  }
3167
3318
  );
@@ -3172,7 +3323,11 @@ server.registerResource(
3172
3323
  async (uri, { sessionId }) => {
3173
3324
  const payload = buildSessionStepsById(sessionId);
3174
3325
  return {
3175
- contents: [{ uri: uri.href, mimeType: "text/plain", text: payload?.generatedJs ?? `// Session not found: ${sessionId}` }]
3326
+ contents: [{
3327
+ uri: uri.href,
3328
+ mimeType: "text/plain",
3329
+ text: payload?.generatedJs ?? `// Session not found: ${sessionId}`
3330
+ }]
3176
3331
  };
3177
3332
  }
3178
3333
  );