@wdio/mcp 2.3.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/lib/server.js +188 -5
- package/lib/server.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,6 +83,8 @@ appium
|
|
|
83
83
|
- **Page Analysis**: Get visible elements, accessibility trees, take screenshots
|
|
84
84
|
- **Cookie Management**: Get, set, and delete cookies
|
|
85
85
|
- **Scrolling**: Smooth scrolling with configurable distances
|
|
86
|
+
- **Attach to running Chrome**: Connect to an existing Chrome window via `--remote-debugging-port` — ideal for testing authenticated or pre-configured sessions
|
|
87
|
+
- **Device emulation**: Apply mobile/tablet presets (iPhone 15, Pixel 7, etc.) to simulate responsive layouts without a physical device
|
|
86
88
|
|
|
87
89
|
### Mobile App Automation (iOS/Android)
|
|
88
90
|
|
|
@@ -102,6 +104,8 @@ appium
|
|
|
102
104
|
| `start_browser` | Start a browser session (Chrome, Firefox, Edge, Safari; headless/headed, custom dimensions) |
|
|
103
105
|
| `start_app_session` | Start an iOS or Android app session via Appium (supports state preservation via noReset) |
|
|
104
106
|
| `close_session` | Close or detach from the current browser or app session (supports detach mode) |
|
|
107
|
+
| `attach_browser` | Attach to a running Chrome instance via `--remote-debugging-port` (CDP) |
|
|
108
|
+
| `emulate_device` | Emulate a mobile/tablet device preset (viewport, DPR, UA, touch); requires BiDi session |
|
|
105
109
|
|
|
106
110
|
### Navigation & Page Interaction (Web & Mobile)
|
|
107
111
|
|
|
@@ -231,6 +235,37 @@ start_browser({
|
|
|
231
235
|
})
|
|
232
236
|
```
|
|
233
237
|
|
|
238
|
+
**Attach to a running Chrome instance:**
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
// First, launch Chrome with remote debugging enabled:
|
|
242
|
+
//
|
|
243
|
+
// macOS (must quit Chrome first — open -a ignores args if Chrome is already running):
|
|
244
|
+
// pkill -x "Google Chrome" && sleep 1
|
|
245
|
+
// /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
|
|
246
|
+
// --remote-debugging-port=9222 \
|
|
247
|
+
// --user-data-dir=/tmp/chrome-debug &
|
|
248
|
+
//
|
|
249
|
+
// Linux:
|
|
250
|
+
// google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug &
|
|
251
|
+
//
|
|
252
|
+
// Verify it's ready: curl http://localhost:9222/json/version
|
|
253
|
+
attach_browser()
|
|
254
|
+
attach_browser({port: 9333})
|
|
255
|
+
attach_browser({port: 9222, navigationUrl: 'https://app.example.com'})
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Device emulation (requires BiDi session):**
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
// Device emulation (requires BiDi session)
|
|
262
|
+
start_browser({capabilities: {webSocketUrl: true}})
|
|
263
|
+
emulate_device() // list available presets
|
|
264
|
+
emulate_device({device: 'iPhone 15'}) // activate emulation
|
|
265
|
+
emulate_device({device: 'Pixel 7'}) // switch device
|
|
266
|
+
emulate_device({device: 'reset'}) // restore desktop defaults
|
|
267
|
+
```
|
|
268
|
+
|
|
234
269
|
### Mobile App Automation
|
|
235
270
|
|
|
236
271
|
**Testing an iOS app on simulator:**
|
package/lib/server.js
CHANGED
|
@@ -164,13 +164,14 @@ var closeSessionTool = async (args = {}) => {
|
|
|
164
164
|
const browser = getBrowser();
|
|
165
165
|
const sessionId = state.currentSession;
|
|
166
166
|
const metadata = state.sessionMetadata.get(sessionId);
|
|
167
|
-
|
|
167
|
+
const effectiveDetach = args.detach || !!metadata?.isAttached;
|
|
168
|
+
if (!effectiveDetach) {
|
|
168
169
|
await browser.deleteSession();
|
|
169
170
|
}
|
|
170
171
|
state.browsers.delete(sessionId);
|
|
171
172
|
state.sessionMetadata.delete(sessionId);
|
|
172
173
|
state.currentSession = null;
|
|
173
|
-
const action =
|
|
174
|
+
const action = effectiveDetach ? "detached from" : "closed";
|
|
174
175
|
const note = args.detach && !metadata?.isAttached ? "\nNote: Session will remain active on Appium server." : "";
|
|
175
176
|
return {
|
|
176
177
|
content: [{ type: "text", text: `Session ${sessionId} ${action}${note}` }]
|
|
@@ -375,7 +376,7 @@ var startAppToolDefinition = {
|
|
|
375
376
|
udid: z5.string().optional().describe('Unique Device Identifier for iOS real device testing (e.g., "00008030-001234567890002E")'),
|
|
376
377
|
noReset: z5.boolean().optional().describe("Do not reset app state before session (preserves app data). Default: false"),
|
|
377
378
|
fullReset: z5.boolean().optional().describe("Uninstall app before/after session. Default: true. Set to false with noReset=true to preserve app state completely"),
|
|
378
|
-
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:
|
|
379
|
+
newCommandTimeout: z5.number().min(0).optional().default(300).describe("How long (in seconds) Appium will wait for a new command before assuming the client has quit and ending the session. Default: 300."),
|
|
379
380
|
capabilities: z5.record(z5.string(), z5.unknown()).optional().describe("Additional Appium/WebDriver capabilities to merge with defaults (e.g. appium:udid, appium:chromedriverExecutable, appium:autoWebview)")
|
|
380
381
|
}
|
|
381
382
|
};
|
|
@@ -404,7 +405,7 @@ var startAppTool = async (args) => {
|
|
|
404
405
|
udid,
|
|
405
406
|
noReset,
|
|
406
407
|
fullReset,
|
|
407
|
-
newCommandTimeout,
|
|
408
|
+
newCommandTimeout = 300,
|
|
408
409
|
capabilities: userCapabilities = {}
|
|
409
410
|
} = args;
|
|
410
411
|
if (!appPath && noReset !== true) {
|
|
@@ -2569,6 +2570,186 @@ var executeScriptTool = async (args) => {
|
|
|
2569
2570
|
}
|
|
2570
2571
|
};
|
|
2571
2572
|
|
|
2573
|
+
// src/tools/attach-browser.tool.ts
|
|
2574
|
+
import { remote as remote3 } from "webdriverio";
|
|
2575
|
+
import { z as z16 } from "zod";
|
|
2576
|
+
var attachBrowserToolDefinition = {
|
|
2577
|
+
name: "attach_browser",
|
|
2578
|
+
description: `Attach to a Chrome instance already running with --remote-debugging-port.
|
|
2579
|
+
|
|
2580
|
+
Start Chrome first (quit any running Chrome instance before launching):
|
|
2581
|
+
|
|
2582
|
+
macOS \u2014 with real profile (preserves extensions, cookies, logins):
|
|
2583
|
+
pkill -x "Google Chrome" && sleep 1
|
|
2584
|
+
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome --remote-debugging-port=9222 --user-data-dir="$HOME/Library/Application Support/Google/Chrome" --profile-directory=Default &
|
|
2585
|
+
|
|
2586
|
+
macOS \u2014 with fresh profile (lightweight, no extensions):
|
|
2587
|
+
pkill -x "Google Chrome" && sleep 1
|
|
2588
|
+
/Applications/Google Chrome.app/Contents/MacOS/Google Chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug &
|
|
2589
|
+
|
|
2590
|
+
Linux \u2014 with real profile:
|
|
2591
|
+
google-chrome --remote-debugging-port=9222 --user-data-dir="$HOME/.config/google-chrome" --profile-directory=Default &
|
|
2592
|
+
|
|
2593
|
+
Linux \u2014 with fresh profile:
|
|
2594
|
+
google-chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome-debug &
|
|
2595
|
+
|
|
2596
|
+
Verify Chrome is ready: curl http://localhost:9222/json/version
|
|
2597
|
+
|
|
2598
|
+
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.`,
|
|
2599
|
+
inputSchema: {
|
|
2600
|
+
port: z16.number().default(9222).describe("Chrome remote debugging port (default: 9222)"),
|
|
2601
|
+
host: z16.string().default("localhost").describe("Host where Chrome is running (default: localhost)"),
|
|
2602
|
+
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)'),
|
|
2603
|
+
navigationUrl: z16.string().optional().describe("URL to navigate to immediately after attaching")
|
|
2604
|
+
}
|
|
2605
|
+
};
|
|
2606
|
+
async function getActiveTabUrl(host, port) {
|
|
2607
|
+
try {
|
|
2608
|
+
const res = await fetch(`http://${host}:${port}/json`);
|
|
2609
|
+
const tabs = await res.json();
|
|
2610
|
+
const page = tabs.find((t) => t.type === "page" && t.url && !t.url.startsWith("devtools://"));
|
|
2611
|
+
return page?.url ?? null;
|
|
2612
|
+
} catch {
|
|
2613
|
+
return null;
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2616
|
+
var attachBrowserTool = async ({
|
|
2617
|
+
port = 9222,
|
|
2618
|
+
host = "localhost",
|
|
2619
|
+
userDataDir = "/tmp/chrome-debug",
|
|
2620
|
+
navigationUrl
|
|
2621
|
+
}) => {
|
|
2622
|
+
try {
|
|
2623
|
+
const state2 = getBrowser.__state;
|
|
2624
|
+
const activeUrl = navigationUrl ?? await getActiveTabUrl(host, port);
|
|
2625
|
+
const browser = await remote3({
|
|
2626
|
+
capabilities: {
|
|
2627
|
+
browserName: "chrome",
|
|
2628
|
+
"goog:chromeOptions": {
|
|
2629
|
+
debuggerAddress: `${host}:${port}`,
|
|
2630
|
+
args: [`--user-data-dir=${userDataDir}`]
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
});
|
|
2634
|
+
const { sessionId } = browser;
|
|
2635
|
+
state2.browsers.set(sessionId, browser);
|
|
2636
|
+
state2.currentSession = sessionId;
|
|
2637
|
+
state2.sessionMetadata.set(sessionId, {
|
|
2638
|
+
type: "browser",
|
|
2639
|
+
capabilities: browser.capabilities,
|
|
2640
|
+
isAttached: true
|
|
2641
|
+
});
|
|
2642
|
+
if (activeUrl) {
|
|
2643
|
+
await browser.url(activeUrl);
|
|
2644
|
+
}
|
|
2645
|
+
const title = await browser.getTitle();
|
|
2646
|
+
const url = await browser.getUrl();
|
|
2647
|
+
return {
|
|
2648
|
+
content: [{
|
|
2649
|
+
type: "text",
|
|
2650
|
+
text: `Attached to Chrome on ${host}:${port}
|
|
2651
|
+
Session ID: ${sessionId}
|
|
2652
|
+
Current page: "${title}" (${url})`
|
|
2653
|
+
}]
|
|
2654
|
+
};
|
|
2655
|
+
} catch (e) {
|
|
2656
|
+
return {
|
|
2657
|
+
content: [{ type: "text", text: `Error attaching to browser: ${e}` }]
|
|
2658
|
+
};
|
|
2659
|
+
}
|
|
2660
|
+
};
|
|
2661
|
+
|
|
2662
|
+
// src/tools/emulate-device.tool.ts
|
|
2663
|
+
import { z as z17 } from "zod";
|
|
2664
|
+
var restoreFunctions = /* @__PURE__ */ new Map();
|
|
2665
|
+
var emulateDeviceToolDefinition = {
|
|
2666
|
+
name: "emulate_device",
|
|
2667
|
+
description: `Emulate a mobile or tablet device in the current browser session (sets viewport, DPR, user-agent, touch events).
|
|
2668
|
+
|
|
2669
|
+
Requires a BiDi-enabled session: start_browser({ capabilities: { webSocketUrl: true } })
|
|
2670
|
+
|
|
2671
|
+
Usage:
|
|
2672
|
+
emulate_device() \u2014 list available device presets
|
|
2673
|
+
emulate_device({ device: "iPhone 15" }) \u2014 activate emulation
|
|
2674
|
+
emulate_device({ device: "reset" }) \u2014 restore desktop defaults`,
|
|
2675
|
+
inputSchema: {
|
|
2676
|
+
device: z17.string().optional().describe(
|
|
2677
|
+
'Device preset name (e.g. "iPhone 15", "Pixel 7"). Omit to list available presets. Pass "reset" to restore desktop defaults.'
|
|
2678
|
+
)
|
|
2679
|
+
}
|
|
2680
|
+
};
|
|
2681
|
+
var emulateDeviceTool = async ({
|
|
2682
|
+
device
|
|
2683
|
+
}) => {
|
|
2684
|
+
try {
|
|
2685
|
+
const browser = getBrowser();
|
|
2686
|
+
const state2 = getBrowser.__state;
|
|
2687
|
+
const sessionId = state2.currentSession;
|
|
2688
|
+
const metadata = state2.sessionMetadata.get(sessionId);
|
|
2689
|
+
if (metadata?.type === "ios" || metadata?.type === "android") {
|
|
2690
|
+
return {
|
|
2691
|
+
content: [{ type: "text", text: "Error: emulate_device is only supported for web browser sessions, not iOS/Android." }]
|
|
2692
|
+
};
|
|
2693
|
+
}
|
|
2694
|
+
if (!browser.isBidi) {
|
|
2695
|
+
return {
|
|
2696
|
+
content: [{
|
|
2697
|
+
type: "text",
|
|
2698
|
+
text: "Error: emulate_device requires a BiDi-enabled session.\nRestart the browser with: start_browser({ capabilities: { webSocketUrl: true } })"
|
|
2699
|
+
}]
|
|
2700
|
+
};
|
|
2701
|
+
}
|
|
2702
|
+
if (!device) {
|
|
2703
|
+
try {
|
|
2704
|
+
await browser.emulate("device", "\0");
|
|
2705
|
+
} catch (e) {
|
|
2706
|
+
const msg = String(e);
|
|
2707
|
+
const match = msg.match(/please use one of the following: (.+)$/);
|
|
2708
|
+
if (match) {
|
|
2709
|
+
const names = match[1].split(", ").sort();
|
|
2710
|
+
return {
|
|
2711
|
+
content: [{ type: "text", text: `Available devices (${names.length}):
|
|
2712
|
+
${names.join("\n")}` }]
|
|
2713
|
+
};
|
|
2714
|
+
}
|
|
2715
|
+
return { content: [{ type: "text", text: `Error listing devices: ${e}` }] };
|
|
2716
|
+
}
|
|
2717
|
+
return { content: [{ type: "text", text: "Could not retrieve device list." }] };
|
|
2718
|
+
}
|
|
2719
|
+
if (device === "reset") {
|
|
2720
|
+
const restoreFn = restoreFunctions.get(sessionId);
|
|
2721
|
+
if (!restoreFn) {
|
|
2722
|
+
return { content: [{ type: "text", text: "No active device emulation to reset." }] };
|
|
2723
|
+
}
|
|
2724
|
+
await restoreFn();
|
|
2725
|
+
restoreFunctions.delete(sessionId);
|
|
2726
|
+
return { content: [{ type: "text", text: "Device emulation reset to desktop defaults." }] };
|
|
2727
|
+
}
|
|
2728
|
+
try {
|
|
2729
|
+
const restoreFn = await browser.emulate("device", device);
|
|
2730
|
+
restoreFunctions.set(sessionId, restoreFn);
|
|
2731
|
+
return {
|
|
2732
|
+
content: [{ type: "text", text: `Emulating "${device}".` }]
|
|
2733
|
+
};
|
|
2734
|
+
} catch (e) {
|
|
2735
|
+
const msg = String(e);
|
|
2736
|
+
if (msg.includes("Unknown device name")) {
|
|
2737
|
+
return {
|
|
2738
|
+
content: [{
|
|
2739
|
+
type: "text",
|
|
2740
|
+
text: `Error: Unknown device "${device}". Call emulate_device() with no arguments to list valid names.`
|
|
2741
|
+
}]
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
return { content: [{ type: "text", text: `Error: ${e}` }] };
|
|
2745
|
+
}
|
|
2746
|
+
} catch (e) {
|
|
2747
|
+
return {
|
|
2748
|
+
content: [{ type: "text", text: `Error: ${e}` }]
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
};
|
|
2752
|
+
|
|
2572
2753
|
// package.json
|
|
2573
2754
|
var package_default = {
|
|
2574
2755
|
name: "@wdio/mcp",
|
|
@@ -2577,7 +2758,7 @@ var package_default = {
|
|
|
2577
2758
|
type: "git",
|
|
2578
2759
|
url: "git://github.com/webdriverio/mcp.git"
|
|
2579
2760
|
},
|
|
2580
|
-
version: "2.3.
|
|
2761
|
+
version: "2.3.1",
|
|
2581
2762
|
description: "MCP server with WebdriverIO for browser and mobile app automation (iOS/Android via Appium)",
|
|
2582
2763
|
main: "./lib/server.js",
|
|
2583
2764
|
module: "./lib/server.js",
|
|
@@ -2669,6 +2850,8 @@ var registerTool = (definition, callback) => server.registerTool(definition.name
|
|
|
2669
2850
|
registerTool(startBrowserToolDefinition, startBrowserTool);
|
|
2670
2851
|
registerTool(startAppToolDefinition, startAppTool);
|
|
2671
2852
|
registerTool(closeSessionToolDefinition, closeSessionTool);
|
|
2853
|
+
registerTool(attachBrowserToolDefinition, attachBrowserTool);
|
|
2854
|
+
registerTool(emulateDeviceToolDefinition, emulateDeviceTool);
|
|
2672
2855
|
registerTool(navigateToolDefinition, navigateTool);
|
|
2673
2856
|
registerTool(getVisibleElementsToolDefinition, getVisibleElementsTool);
|
|
2674
2857
|
registerTool(getAccessibilityToolDefinition, getAccessibilityTreeTool);
|