@wdio/mcp 2.4.0 → 2.5.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 +11 -0
- package/lib/server.js +332 -30
- package/lib/server.js.map +1 -1
- package/package.json +1 -1
package/lib/server.js
CHANGED
|
@@ -11,7 +11,7 @@ var supportedBrowsers = ["chrome", "firefox", "edge", "safari"];
|
|
|
11
11
|
var browserSchema = z.enum(supportedBrowsers).default("chrome");
|
|
12
12
|
var startBrowserToolDefinition = {
|
|
13
13
|
name: "start_browser",
|
|
14
|
-
description: "starts a browser session (Chrome, Firefox, Edge, Safari) and sets it to the current state",
|
|
14
|
+
description: "starts a browser session (Chrome, Firefox, Edge, Safari) and sets it to the current state. Prefer headless: true unless the user explicitly asks to see the browser.",
|
|
15
15
|
inputSchema: {
|
|
16
16
|
browser: browserSchema.describe("Browser to launch: chrome, firefox, edge, safari (default: chrome)"),
|
|
17
17
|
headless: z.boolean().optional().default(true),
|
|
@@ -31,7 +31,8 @@ var closeSessionToolDefinition = {
|
|
|
31
31
|
var state = {
|
|
32
32
|
browsers: /* @__PURE__ */ new Map(),
|
|
33
33
|
currentSession: null,
|
|
34
|
-
sessionMetadata: /* @__PURE__ */ new Map()
|
|
34
|
+
sessionMetadata: /* @__PURE__ */ new Map(),
|
|
35
|
+
sessionHistory: /* @__PURE__ */ new Map()
|
|
35
36
|
};
|
|
36
37
|
var getBrowser = () => {
|
|
37
38
|
const browser = state.browsers.get(state.currentSession);
|
|
@@ -132,12 +133,33 @@ var startBrowserTool = async ({
|
|
|
132
133
|
});
|
|
133
134
|
const { sessionId } = wdioBrowser;
|
|
134
135
|
state.browsers.set(sessionId, wdioBrowser);
|
|
135
|
-
state.currentSession = sessionId;
|
|
136
136
|
state.sessionMetadata.set(sessionId, {
|
|
137
137
|
type: "browser",
|
|
138
138
|
capabilities: wdioBrowser.capabilities,
|
|
139
139
|
isAttached: false
|
|
140
140
|
});
|
|
141
|
+
if (state.currentSession && state.currentSession !== sessionId) {
|
|
142
|
+
const outgoing = state.sessionHistory.get(state.currentSession);
|
|
143
|
+
if (outgoing) {
|
|
144
|
+
outgoing.steps.push({
|
|
145
|
+
index: outgoing.steps.length + 1,
|
|
146
|
+
tool: "__session_transition__",
|
|
147
|
+
params: { newSessionId: sessionId },
|
|
148
|
+
status: "ok",
|
|
149
|
+
durationMs: 0,
|
|
150
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
151
|
+
});
|
|
152
|
+
outgoing.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
state.sessionHistory.set(sessionId, {
|
|
156
|
+
sessionId,
|
|
157
|
+
type: "browser",
|
|
158
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
159
|
+
capabilities: wdioBrowser.capabilities,
|
|
160
|
+
steps: []
|
|
161
|
+
});
|
|
162
|
+
state.currentSession = sessionId;
|
|
141
163
|
let sizeNote = "";
|
|
142
164
|
try {
|
|
143
165
|
await wdioBrowser.setWindowSize(windowWidth, windowHeight);
|
|
@@ -164,6 +186,10 @@ var closeSessionTool = async (args = {}) => {
|
|
|
164
186
|
const browser = getBrowser();
|
|
165
187
|
const sessionId = state.currentSession;
|
|
166
188
|
const metadata = state.sessionMetadata.get(sessionId);
|
|
189
|
+
const history = state.sessionHistory.get(sessionId);
|
|
190
|
+
if (history) {
|
|
191
|
+
history.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
192
|
+
}
|
|
167
193
|
const effectiveDetach = args.detach || !!metadata?.isAttached;
|
|
168
194
|
if (!effectiveDetach) {
|
|
169
195
|
await browser.deleteSession();
|
|
@@ -178,6 +204,7 @@ var closeSessionTool = async (args = {}) => {
|
|
|
178
204
|
};
|
|
179
205
|
} catch (e) {
|
|
180
206
|
return {
|
|
207
|
+
isError: true,
|
|
181
208
|
content: [{ type: "text", text: `Error closing session: ${e}` }]
|
|
182
209
|
};
|
|
183
210
|
}
|
|
@@ -201,6 +228,7 @@ var navigateTool = async ({ url }) => {
|
|
|
201
228
|
};
|
|
202
229
|
} catch (e) {
|
|
203
230
|
return {
|
|
231
|
+
isError: true,
|
|
204
232
|
content: [{ type: "text", text: `Error navigating: ${e}` }]
|
|
205
233
|
};
|
|
206
234
|
}
|
|
@@ -231,6 +259,7 @@ var clickAction = async (selector, timeout, scrollToView = true) => {
|
|
|
231
259
|
};
|
|
232
260
|
} catch (e) {
|
|
233
261
|
return {
|
|
262
|
+
isError: true,
|
|
234
263
|
content: [{ type: "text", text: `Error clicking element: ${e}` }]
|
|
235
264
|
};
|
|
236
265
|
}
|
|
@@ -264,6 +293,7 @@ var setValueTool = async ({ selector, value, scrollToView = true, timeout = defa
|
|
|
264
293
|
};
|
|
265
294
|
} catch (e) {
|
|
266
295
|
return {
|
|
296
|
+
isError: true,
|
|
267
297
|
content: [{ type: "text", text: `Error entering text: ${e}` }]
|
|
268
298
|
};
|
|
269
299
|
}
|
|
@@ -310,7 +340,7 @@ function buildIOSCapabilities(appPath, options) {
|
|
|
310
340
|
capabilities["appium:autoAcceptAlerts"] = void 0;
|
|
311
341
|
}
|
|
312
342
|
for (const [key, value] of Object.entries(options)) {
|
|
313
|
-
if (!["deviceName", "platformVersion", "automationName", "autoAcceptAlerts", "autoDismissAlerts", "udid", "noReset", "fullReset", "newCommandTimeout"].includes(
|
|
343
|
+
if (!["deviceName", "platformVersion", "automationName", "autoGrantPermissions", "autoAcceptAlerts", "autoDismissAlerts", "udid", "noReset", "fullReset", "newCommandTimeout"].includes(
|
|
314
344
|
key
|
|
315
345
|
)) {
|
|
316
346
|
capabilities[`appium:${key}`] = value;
|
|
@@ -347,7 +377,7 @@ function buildAndroidCapabilities(appPath, options) {
|
|
|
347
377
|
capabilities["appium:appWaitActivity"] = options.appWaitActivity;
|
|
348
378
|
}
|
|
349
379
|
for (const [key, value] of Object.entries(options)) {
|
|
350
|
-
if (!["deviceName", "platformVersion", "automationName", "autoGrantPermissions", "appWaitActivity", "noReset", "fullReset", "newCommandTimeout"].includes(
|
|
380
|
+
if (!["deviceName", "platformVersion", "automationName", "autoGrantPermissions", "autoAcceptAlerts", "autoDismissAlerts", "appWaitActivity", "noReset", "fullReset", "newCommandTimeout"].includes(
|
|
351
381
|
key
|
|
352
382
|
)) {
|
|
353
383
|
capabilities[`appium:${key}`] = value;
|
|
@@ -464,12 +494,34 @@ var startAppTool = async (args) => {
|
|
|
464
494
|
const shouldAutoDetach = noReset === true || !appPath;
|
|
465
495
|
const state2 = getState();
|
|
466
496
|
state2.browsers.set(sessionId, browser);
|
|
467
|
-
state2.currentSession = sessionId;
|
|
468
497
|
state2.sessionMetadata.set(sessionId, {
|
|
469
498
|
type: platform.toLowerCase(),
|
|
470
499
|
capabilities: mergedCapabilities,
|
|
471
500
|
isAttached: shouldAutoDetach
|
|
472
501
|
});
|
|
502
|
+
if (state2.currentSession && state2.currentSession !== sessionId) {
|
|
503
|
+
const outgoing = state2.sessionHistory.get(state2.currentSession);
|
|
504
|
+
if (outgoing) {
|
|
505
|
+
outgoing.steps.push({
|
|
506
|
+
index: outgoing.steps.length + 1,
|
|
507
|
+
tool: "__session_transition__",
|
|
508
|
+
params: { newSessionId: sessionId },
|
|
509
|
+
status: "ok",
|
|
510
|
+
durationMs: 0,
|
|
511
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
512
|
+
});
|
|
513
|
+
outgoing.endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
state2.sessionHistory.set(sessionId, {
|
|
517
|
+
sessionId,
|
|
518
|
+
type: platform.toLowerCase(),
|
|
519
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
520
|
+
capabilities: mergedCapabilities,
|
|
521
|
+
appiumConfig: { hostname: serverConfig.hostname, port: serverConfig.port, path: serverConfig.path },
|
|
522
|
+
steps: []
|
|
523
|
+
});
|
|
524
|
+
state2.currentSession = sessionId;
|
|
473
525
|
const appInfo = appPath ? `
|
|
474
526
|
App: ${appPath}` : "\nApp: (connected to running app)";
|
|
475
527
|
const detachNote = shouldAutoDetach ? "\n\n(Auto-detach enabled: session will be preserved on close. Use close_session({ detach: false }) to force terminate.)" : "";
|
|
@@ -485,6 +537,7 @@ Appium Server: ${serverConfig.hostname}:${serverConfig.port}${serverConfig.path}
|
|
|
485
537
|
};
|
|
486
538
|
} catch (e) {
|
|
487
539
|
return {
|
|
540
|
+
isError: true,
|
|
488
541
|
content: [{ type: "text", text: `Error starting app session: ${e}` }]
|
|
489
542
|
};
|
|
490
543
|
}
|
|
@@ -518,6 +571,7 @@ var scrollTool = async ({ direction, pixels = 500 }) => {
|
|
|
518
571
|
};
|
|
519
572
|
} catch (e) {
|
|
520
573
|
return {
|
|
574
|
+
isError: true,
|
|
521
575
|
content: [{ type: "text", text: `Error scrolling: ${e}` }]
|
|
522
576
|
};
|
|
523
577
|
}
|
|
@@ -1690,6 +1744,7 @@ var getVisibleElementsTool = async (args) => {
|
|
|
1690
1744
|
};
|
|
1691
1745
|
} catch (e) {
|
|
1692
1746
|
return {
|
|
1747
|
+
isError: true,
|
|
1693
1748
|
content: [{ type: "text", text: `Error getting visible elements: ${e}` }]
|
|
1694
1749
|
};
|
|
1695
1750
|
}
|
|
@@ -1905,7 +1960,7 @@ var accessibilityTreeScript = () => (function() {
|
|
|
1905
1960
|
if (ariaLevel) return parseInt(ariaLevel, 10);
|
|
1906
1961
|
return void 0;
|
|
1907
1962
|
}
|
|
1908
|
-
function
|
|
1963
|
+
function getState3(el) {
|
|
1909
1964
|
const inputEl = el;
|
|
1910
1965
|
const isCheckable = ["input", "menuitemcheckbox", "menuitemradio"].includes(el.tagName.toLowerCase()) || ["checkbox", "radio", "switch"].includes(el.getAttribute("role") || "");
|
|
1911
1966
|
return {
|
|
@@ -1933,7 +1988,7 @@ var accessibilityTreeScript = () => (function() {
|
|
|
1933
1988
|
const isLandmark = LANDMARK_ROLES.has(role);
|
|
1934
1989
|
const hasIdentity = !!(name || isLandmark);
|
|
1935
1990
|
const selector = hasIdentity ? getSelector(el) : "";
|
|
1936
|
-
const node = { role, name, selector, level: getLevel(el) ?? "", ...
|
|
1991
|
+
const node = { role, name, selector, level: getLevel(el) ?? "", ...getState3(el) };
|
|
1937
1992
|
result.push(node);
|
|
1938
1993
|
for (const child of Array.from(el.children)) {
|
|
1939
1994
|
walk(child, depth + 1);
|
|
@@ -2009,6 +2064,7 @@ var getAccessibilityTreeTool = async (args) => {
|
|
|
2009
2064
|
};
|
|
2010
2065
|
} catch (e) {
|
|
2011
2066
|
return {
|
|
2067
|
+
isError: true,
|
|
2012
2068
|
content: [{ type: "text", text: `Error getting accessibility tree: ${e}` }]
|
|
2013
2069
|
};
|
|
2014
2070
|
}
|
|
@@ -2069,6 +2125,7 @@ var takeScreenshotTool = async ({ outputPath }) => {
|
|
|
2069
2125
|
};
|
|
2070
2126
|
} catch (e) {
|
|
2071
2127
|
return {
|
|
2128
|
+
isError: true,
|
|
2072
2129
|
content: [{ type: "text", text: `Error taking screenshot: ${e.message}` }]
|
|
2073
2130
|
};
|
|
2074
2131
|
}
|
|
@@ -2108,6 +2165,7 @@ var getCookiesTool = async ({ name }) => {
|
|
|
2108
2165
|
};
|
|
2109
2166
|
} catch (e) {
|
|
2110
2167
|
return {
|
|
2168
|
+
isError: true,
|
|
2111
2169
|
content: [{ type: "text", text: `Error getting cookies: ${e}` }]
|
|
2112
2170
|
};
|
|
2113
2171
|
}
|
|
@@ -2145,6 +2203,7 @@ var setCookieTool = async ({
|
|
|
2145
2203
|
};
|
|
2146
2204
|
} catch (e) {
|
|
2147
2205
|
return {
|
|
2206
|
+
isError: true,
|
|
2148
2207
|
content: [{ type: "text", text: `Error setting cookie: ${e}` }]
|
|
2149
2208
|
};
|
|
2150
2209
|
}
|
|
@@ -2171,6 +2230,7 @@ var deleteCookiesTool = async ({ name }) => {
|
|
|
2171
2230
|
};
|
|
2172
2231
|
} catch (e) {
|
|
2173
2232
|
return {
|
|
2233
|
+
isError: true,
|
|
2174
2234
|
content: [{ type: "text", text: `Error deleting cookies: ${e}` }]
|
|
2175
2235
|
};
|
|
2176
2236
|
}
|
|
@@ -2204,10 +2264,12 @@ var tapElementTool = async (args) => {
|
|
|
2204
2264
|
};
|
|
2205
2265
|
}
|
|
2206
2266
|
return {
|
|
2267
|
+
isError: true,
|
|
2207
2268
|
content: [{ type: "text", text: "Error: Must provide either selector or x,y coordinates" }]
|
|
2208
2269
|
};
|
|
2209
2270
|
} catch (e) {
|
|
2210
2271
|
return {
|
|
2272
|
+
isError: true,
|
|
2211
2273
|
content: [{ type: "text", text: `Error tapping: ${e}` }]
|
|
2212
2274
|
};
|
|
2213
2275
|
}
|
|
@@ -2242,6 +2304,7 @@ var swipeTool = async (args) => {
|
|
|
2242
2304
|
};
|
|
2243
2305
|
} catch (e) {
|
|
2244
2306
|
return {
|
|
2307
|
+
isError: true,
|
|
2245
2308
|
content: [{ type: "text", text: `Error swiping: ${e}` }]
|
|
2246
2309
|
};
|
|
2247
2310
|
}
|
|
@@ -2275,10 +2338,12 @@ var dragAndDropTool = async (args) => {
|
|
|
2275
2338
|
};
|
|
2276
2339
|
}
|
|
2277
2340
|
return {
|
|
2341
|
+
isError: true,
|
|
2278
2342
|
content: [{ type: "text", text: "Error: Must provide either targetSelector or x,y coordinates" }]
|
|
2279
2343
|
};
|
|
2280
2344
|
} catch (e) {
|
|
2281
2345
|
return {
|
|
2346
|
+
isError: true,
|
|
2282
2347
|
content: [{ type: "text", text: `Error dragging: ${e}` }]
|
|
2283
2348
|
};
|
|
2284
2349
|
}
|
|
@@ -2316,6 +2381,7 @@ var getAppStateTool = async (args) => {
|
|
|
2316
2381
|
};
|
|
2317
2382
|
} catch (e) {
|
|
2318
2383
|
return {
|
|
2384
|
+
isError: true,
|
|
2319
2385
|
content: [{ type: "text", text: `Error getting app state: ${e}` }]
|
|
2320
2386
|
};
|
|
2321
2387
|
}
|
|
@@ -2357,6 +2423,7 @@ ${contexts.map((ctx, idx) => `${idx + 1}. ${ctx}`).join("\n")}`
|
|
|
2357
2423
|
};
|
|
2358
2424
|
} catch (e) {
|
|
2359
2425
|
return {
|
|
2426
|
+
isError: true,
|
|
2360
2427
|
content: [{ type: "text", text: `Error getting contexts: ${e}` }]
|
|
2361
2428
|
};
|
|
2362
2429
|
}
|
|
@@ -2370,6 +2437,7 @@ var getCurrentContextTool = async () => {
|
|
|
2370
2437
|
};
|
|
2371
2438
|
} catch (e) {
|
|
2372
2439
|
return {
|
|
2440
|
+
isError: true,
|
|
2373
2441
|
content: [{ type: "text", text: `Error getting current context: ${e}` }]
|
|
2374
2442
|
};
|
|
2375
2443
|
}
|
|
@@ -2385,14 +2453,7 @@ var switchContextTool = async (args) => {
|
|
|
2385
2453
|
if (index >= 0 && index < contexts.length) {
|
|
2386
2454
|
targetContext = contexts[index];
|
|
2387
2455
|
} else {
|
|
2388
|
-
|
|
2389
|
-
content: [
|
|
2390
|
-
{
|
|
2391
|
-
type: "text",
|
|
2392
|
-
text: `Error: Invalid context index ${context}. Available contexts: ${contexts.length}`
|
|
2393
|
-
}
|
|
2394
|
-
]
|
|
2395
|
-
};
|
|
2456
|
+
throw new Error(`Error: Invalid context index ${context}. Available contexts: ${contexts.length}`);
|
|
2396
2457
|
}
|
|
2397
2458
|
}
|
|
2398
2459
|
await browser.switchContext(targetContext);
|
|
@@ -2401,6 +2462,7 @@ var switchContextTool = async (args) => {
|
|
|
2401
2462
|
};
|
|
2402
2463
|
} catch (e) {
|
|
2403
2464
|
return {
|
|
2465
|
+
isError: true,
|
|
2404
2466
|
content: [{ type: "text", text: `Error switching context: ${e}` }]
|
|
2405
2467
|
};
|
|
2406
2468
|
}
|
|
@@ -2444,6 +2506,7 @@ var rotateDeviceTool = async (args) => {
|
|
|
2444
2506
|
};
|
|
2445
2507
|
} catch (e) {
|
|
2446
2508
|
return {
|
|
2509
|
+
isError: true,
|
|
2447
2510
|
content: [{ type: "text", text: `Error rotating device: ${e}` }]
|
|
2448
2511
|
};
|
|
2449
2512
|
}
|
|
@@ -2457,6 +2520,7 @@ var hideKeyboardTool = async () => {
|
|
|
2457
2520
|
};
|
|
2458
2521
|
} catch (e) {
|
|
2459
2522
|
return {
|
|
2523
|
+
isError: true,
|
|
2460
2524
|
content: [{ type: "text", text: `Error hiding keyboard: ${e}` }]
|
|
2461
2525
|
};
|
|
2462
2526
|
}
|
|
@@ -2478,6 +2542,7 @@ var getGeolocationTool = async () => {
|
|
|
2478
2542
|
};
|
|
2479
2543
|
} catch (e) {
|
|
2480
2544
|
return {
|
|
2545
|
+
isError: true,
|
|
2481
2546
|
content: [{ type: "text", text: `Error getting geolocation: ${e}` }]
|
|
2482
2547
|
};
|
|
2483
2548
|
}
|
|
@@ -2500,6 +2565,7 @@ var setGeolocationTool = async (args) => {
|
|
|
2500
2565
|
};
|
|
2501
2566
|
} catch (e) {
|
|
2502
2567
|
return {
|
|
2568
|
+
isError: true,
|
|
2503
2569
|
content: [{ type: "text", text: `Error setting geolocation: ${e}` }]
|
|
2504
2570
|
};
|
|
2505
2571
|
}
|
|
@@ -2565,6 +2631,7 @@ var executeScriptTool = async (args) => {
|
|
|
2565
2631
|
};
|
|
2566
2632
|
} catch (e) {
|
|
2567
2633
|
return {
|
|
2634
|
+
isError: true,
|
|
2568
2635
|
content: [{ type: "text", text: `Error executing script: ${e}` }]
|
|
2569
2636
|
};
|
|
2570
2637
|
}
|
|
@@ -2639,6 +2706,19 @@ var attachBrowserTool = async ({
|
|
|
2639
2706
|
capabilities: browser.capabilities,
|
|
2640
2707
|
isAttached: true
|
|
2641
2708
|
});
|
|
2709
|
+
state2.sessionHistory.set(sessionId, {
|
|
2710
|
+
sessionId,
|
|
2711
|
+
type: "browser",
|
|
2712
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2713
|
+
capabilities: {
|
|
2714
|
+
browserName: "chrome",
|
|
2715
|
+
"goog:chromeOptions": {
|
|
2716
|
+
debuggerAddress: `${host}:${port}`,
|
|
2717
|
+
args: [`--user-data-dir=${userDataDir}`]
|
|
2718
|
+
}
|
|
2719
|
+
},
|
|
2720
|
+
steps: []
|
|
2721
|
+
});
|
|
2642
2722
|
if (activeUrl) {
|
|
2643
2723
|
await browser.url(activeUrl);
|
|
2644
2724
|
}
|
|
@@ -2654,6 +2734,7 @@ Current page: "${title}" (${url})`
|
|
|
2654
2734
|
};
|
|
2655
2735
|
} catch (e) {
|
|
2656
2736
|
return {
|
|
2737
|
+
isError: true,
|
|
2657
2738
|
content: [{ type: "text", text: `Error attaching to browser: ${e}` }]
|
|
2658
2739
|
};
|
|
2659
2740
|
}
|
|
@@ -2688,11 +2769,13 @@ var emulateDeviceTool = async ({
|
|
|
2688
2769
|
const metadata = state2.sessionMetadata.get(sessionId);
|
|
2689
2770
|
if (metadata?.type === "ios" || metadata?.type === "android") {
|
|
2690
2771
|
return {
|
|
2772
|
+
isError: true,
|
|
2691
2773
|
content: [{ type: "text", text: "Error: emulate_device is only supported for web browser sessions, not iOS/Android." }]
|
|
2692
2774
|
};
|
|
2693
2775
|
}
|
|
2694
2776
|
if (!browser.isBidi) {
|
|
2695
2777
|
return {
|
|
2778
|
+
isError: true,
|
|
2696
2779
|
content: [{
|
|
2697
2780
|
type: "text",
|
|
2698
2781
|
text: "Error: emulate_device requires a BiDi-enabled session.\nRestart the browser with: start_browser({ capabilities: { webSocketUrl: true } })"
|
|
@@ -2712,7 +2795,7 @@ var emulateDeviceTool = async ({
|
|
|
2712
2795
|
${names.join("\n")}` }]
|
|
2713
2796
|
};
|
|
2714
2797
|
}
|
|
2715
|
-
return { content: [{ type: "text", text: `Error listing devices: ${e}` }] };
|
|
2798
|
+
return { isError: true, content: [{ type: "text", text: `Error listing devices: ${e}` }] };
|
|
2716
2799
|
}
|
|
2717
2800
|
return { content: [{ type: "text", text: "Could not retrieve device list." }] };
|
|
2718
2801
|
}
|
|
@@ -2741,10 +2824,11 @@ ${names.join("\n")}` }]
|
|
|
2741
2824
|
}]
|
|
2742
2825
|
};
|
|
2743
2826
|
}
|
|
2744
|
-
return { content: [{ type: "text", text: `Error: ${e}` }] };
|
|
2827
|
+
return { isError: true, content: [{ type: "text", text: `Error: ${e}` }] };
|
|
2745
2828
|
}
|
|
2746
2829
|
} catch (e) {
|
|
2747
2830
|
return {
|
|
2831
|
+
isError: true,
|
|
2748
2832
|
content: [{ type: "text", text: `Error: ${e}` }]
|
|
2749
2833
|
};
|
|
2750
2834
|
}
|
|
@@ -2758,7 +2842,7 @@ var package_default = {
|
|
|
2758
2842
|
type: "git",
|
|
2759
2843
|
url: "git://github.com/webdriverio/mcp.git"
|
|
2760
2844
|
},
|
|
2761
|
-
version: "2.
|
|
2845
|
+
version: "2.4.1",
|
|
2762
2846
|
description: "MCP server with WebdriverIO for browser and mobile app automation (iOS/Android via Appium)",
|
|
2763
2847
|
main: "./lib/server.js",
|
|
2764
2848
|
module: "./lib/server.js",
|
|
@@ -2826,6 +2910,171 @@ var package_default = {
|
|
|
2826
2910
|
packageManager: "pnpm@10.12.4"
|
|
2827
2911
|
};
|
|
2828
2912
|
|
|
2913
|
+
// src/server.ts
|
|
2914
|
+
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2915
|
+
|
|
2916
|
+
// src/recording/step-recorder.ts
|
|
2917
|
+
function getState2() {
|
|
2918
|
+
return getBrowser.__state;
|
|
2919
|
+
}
|
|
2920
|
+
function appendStep(toolName, params, status, durationMs, error) {
|
|
2921
|
+
const state2 = getState2();
|
|
2922
|
+
const sessionId = state2.currentSession;
|
|
2923
|
+
if (!sessionId) return;
|
|
2924
|
+
const history = state2.sessionHistory.get(sessionId);
|
|
2925
|
+
if (!history) return;
|
|
2926
|
+
const step = {
|
|
2927
|
+
index: history.steps.length + 1,
|
|
2928
|
+
tool: toolName,
|
|
2929
|
+
params,
|
|
2930
|
+
status,
|
|
2931
|
+
durationMs,
|
|
2932
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2933
|
+
...error !== void 0 && { error }
|
|
2934
|
+
};
|
|
2935
|
+
history.steps.push(step);
|
|
2936
|
+
}
|
|
2937
|
+
function getSessionHistory() {
|
|
2938
|
+
return getState2().sessionHistory;
|
|
2939
|
+
}
|
|
2940
|
+
function extractErrorText(result) {
|
|
2941
|
+
const textContent = result.content.find((c) => c.type === "text");
|
|
2942
|
+
return textContent ? textContent.text : "Unknown error";
|
|
2943
|
+
}
|
|
2944
|
+
function withRecording(toolName, callback) {
|
|
2945
|
+
return async (params, extra) => {
|
|
2946
|
+
const start = Date.now();
|
|
2947
|
+
const result = await callback(params, extra);
|
|
2948
|
+
const isError = result.isError === true;
|
|
2949
|
+
appendStep(
|
|
2950
|
+
toolName,
|
|
2951
|
+
params,
|
|
2952
|
+
isError ? "error" : "ok",
|
|
2953
|
+
Date.now() - start,
|
|
2954
|
+
isError ? extractErrorText(result) : void 0
|
|
2955
|
+
);
|
|
2956
|
+
return result;
|
|
2957
|
+
};
|
|
2958
|
+
}
|
|
2959
|
+
|
|
2960
|
+
// src/recording/code-generator.ts
|
|
2961
|
+
function escapeStr(value) {
|
|
2962
|
+
return String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
2963
|
+
}
|
|
2964
|
+
function formatParams(params) {
|
|
2965
|
+
return Object.entries(params).map(([k, v]) => `${k}="${v}"`).join(" ");
|
|
2966
|
+
}
|
|
2967
|
+
function indentJson(value) {
|
|
2968
|
+
return JSON.stringify(value, null, 2).split("\n").map((line, i) => i > 0 ? ` ${line}` : line).join("\n");
|
|
2969
|
+
}
|
|
2970
|
+
function generateStep(step, history) {
|
|
2971
|
+
if (step.tool === "__session_transition__") {
|
|
2972
|
+
const newId = step.params.newSessionId ?? "unknown";
|
|
2973
|
+
return `// --- new session: ${newId} started at ${step.timestamp} ---`;
|
|
2974
|
+
}
|
|
2975
|
+
if (step.status === "error") {
|
|
2976
|
+
return `// [error] ${step.tool}: ${formatParams(step.params)} \u2014 ${step.error ?? "unknown error"}`;
|
|
2977
|
+
}
|
|
2978
|
+
const p = step.params;
|
|
2979
|
+
switch (step.tool) {
|
|
2980
|
+
case "start_browser": {
|
|
2981
|
+
const nav = p.navigationUrl ? `
|
|
2982
|
+
await browser.url('${escapeStr(p.navigationUrl)}');` : "";
|
|
2983
|
+
return `const browser = await remote({
|
|
2984
|
+
capabilities: ${indentJson(history.capabilities)}
|
|
2985
|
+
});${nav}`;
|
|
2986
|
+
}
|
|
2987
|
+
case "start_app_session": {
|
|
2988
|
+
const config = {
|
|
2989
|
+
protocol: "http",
|
|
2990
|
+
hostname: history.appiumConfig?.hostname ?? "localhost",
|
|
2991
|
+
port: history.appiumConfig?.port ?? 4723,
|
|
2992
|
+
path: history.appiumConfig?.path ?? "/",
|
|
2993
|
+
capabilities: history.capabilities
|
|
2994
|
+
};
|
|
2995
|
+
return `const browser = await remote(${indentJson(config)});`;
|
|
2996
|
+
}
|
|
2997
|
+
case "attach_browser": {
|
|
2998
|
+
const nav = p.navigationUrl ? `
|
|
2999
|
+
await browser.url('${escapeStr(p.navigationUrl)}');` : "";
|
|
3000
|
+
return `const browser = await remote({
|
|
3001
|
+
capabilities: ${indentJson(history.capabilities)}
|
|
3002
|
+
});${nav}`;
|
|
3003
|
+
}
|
|
3004
|
+
case "navigate":
|
|
3005
|
+
return `await browser.url('${escapeStr(p.url)}');`;
|
|
3006
|
+
case "click_element":
|
|
3007
|
+
return `await browser.$('${escapeStr(p.selector)}').click();`;
|
|
3008
|
+
case "set_value":
|
|
3009
|
+
return `await browser.$('${escapeStr(p.selector)}').setValue('${escapeStr(p.value)}');`;
|
|
3010
|
+
case "scroll": {
|
|
3011
|
+
const scrollAmount = p.direction === "down" ? p.pixels : -p.pixels;
|
|
3012
|
+
return `await browser.execute(() => window.scrollBy(0, ${scrollAmount}));`;
|
|
3013
|
+
}
|
|
3014
|
+
case "tap_element":
|
|
3015
|
+
if (p.selector !== void 0) {
|
|
3016
|
+
return `await browser.$('${escapeStr(p.selector)}').click();`;
|
|
3017
|
+
}
|
|
3018
|
+
return `await browser.tap({ x: ${p.x}, y: ${p.y} });`;
|
|
3019
|
+
case "swipe":
|
|
3020
|
+
return `await browser.execute('mobile: swipe', { direction: '${escapeStr(p.direction)}' });`;
|
|
3021
|
+
case "drag_and_drop":
|
|
3022
|
+
if (p.targetSelector !== void 0) {
|
|
3023
|
+
return `await browser.$('${escapeStr(p.sourceSelector)}').dragAndDrop(browser.$('${escapeStr(p.targetSelector)}'));`;
|
|
3024
|
+
}
|
|
3025
|
+
return `await browser.$('${escapeStr(p.sourceSelector)}').dragAndDrop({ x: ${p.x}, y: ${p.y} });`;
|
|
3026
|
+
default:
|
|
3027
|
+
return `// [unknown tool] ${step.tool}`;
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
function generateCode(history) {
|
|
3031
|
+
const steps = history.steps.map((step) => generateStep(step, history)).join("\n");
|
|
3032
|
+
return `import { remote } from 'webdriverio';
|
|
3033
|
+
|
|
3034
|
+
${steps}
|
|
3035
|
+
|
|
3036
|
+
await browser.deleteSession();`;
|
|
3037
|
+
}
|
|
3038
|
+
|
|
3039
|
+
// src/recording/resources.ts
|
|
3040
|
+
function getCurrentSessionId() {
|
|
3041
|
+
return getBrowser.__state?.currentSession ?? null;
|
|
3042
|
+
}
|
|
3043
|
+
function buildSessionsIndex() {
|
|
3044
|
+
const histories = getSessionHistory();
|
|
3045
|
+
const currentId = getCurrentSessionId();
|
|
3046
|
+
const sessions = Array.from(histories.values()).map((h) => ({
|
|
3047
|
+
sessionId: h.sessionId,
|
|
3048
|
+
type: h.type,
|
|
3049
|
+
startedAt: h.startedAt,
|
|
3050
|
+
...h.endedAt ? { endedAt: h.endedAt } : {},
|
|
3051
|
+
stepCount: h.steps.length,
|
|
3052
|
+
isCurrent: h.sessionId === currentId
|
|
3053
|
+
}));
|
|
3054
|
+
return JSON.stringify({ sessions });
|
|
3055
|
+
}
|
|
3056
|
+
function buildCurrentSessionSteps() {
|
|
3057
|
+
const currentId = getCurrentSessionId();
|
|
3058
|
+
if (!currentId) return null;
|
|
3059
|
+
return buildSessionStepsById(currentId);
|
|
3060
|
+
}
|
|
3061
|
+
function buildSessionStepsById(sessionId) {
|
|
3062
|
+
const history = getSessionHistory().get(sessionId);
|
|
3063
|
+
if (!history) return null;
|
|
3064
|
+
return buildSessionPayload(history);
|
|
3065
|
+
}
|
|
3066
|
+
function buildSessionPayload(history) {
|
|
3067
|
+
const stepsJson = JSON.stringify({
|
|
3068
|
+
sessionId: history.sessionId,
|
|
3069
|
+
type: history.type,
|
|
3070
|
+
startedAt: history.startedAt,
|
|
3071
|
+
...history.endedAt ? { endedAt: history.endedAt } : {},
|
|
3072
|
+
stepCount: history.steps.length,
|
|
3073
|
+
steps: history.steps
|
|
3074
|
+
});
|
|
3075
|
+
return { stepsJson, generatedJs: generateCode(history) };
|
|
3076
|
+
}
|
|
3077
|
+
|
|
2829
3078
|
// src/server.ts
|
|
2830
3079
|
console.log = (...args) => console.error("[LOG]", ...args);
|
|
2831
3080
|
console.info = (...args) => console.error("[INFO]", ...args);
|
|
@@ -2840,31 +3089,32 @@ var server = new McpServer({
|
|
|
2840
3089
|
}, {
|
|
2841
3090
|
instructions: "MCP server for browser and mobile app automation using WebDriverIO. Supports Chrome, Firefox, Edge, and Safari browser control plus iOS/Android native app testing via Appium.",
|
|
2842
3091
|
capabilities: {
|
|
2843
|
-
tools: {}
|
|
3092
|
+
tools: {},
|
|
3093
|
+
resources: {}
|
|
2844
3094
|
}
|
|
2845
3095
|
});
|
|
2846
3096
|
var registerTool = (definition, callback) => server.registerTool(definition.name, {
|
|
2847
3097
|
description: definition.description,
|
|
2848
3098
|
inputSchema: definition.inputSchema
|
|
2849
3099
|
}, callback);
|
|
2850
|
-
registerTool(startBrowserToolDefinition, startBrowserTool);
|
|
2851
|
-
registerTool(startAppToolDefinition, startAppTool);
|
|
3100
|
+
registerTool(startBrowserToolDefinition, withRecording("start_browser", startBrowserTool));
|
|
3101
|
+
registerTool(startAppToolDefinition, withRecording("start_app_session", startAppTool));
|
|
2852
3102
|
registerTool(closeSessionToolDefinition, closeSessionTool);
|
|
2853
|
-
registerTool(attachBrowserToolDefinition, attachBrowserTool);
|
|
3103
|
+
registerTool(attachBrowserToolDefinition, withRecording("attach_browser", attachBrowserTool));
|
|
2854
3104
|
registerTool(emulateDeviceToolDefinition, emulateDeviceTool);
|
|
2855
|
-
registerTool(navigateToolDefinition, navigateTool);
|
|
3105
|
+
registerTool(navigateToolDefinition, withRecording("navigate", navigateTool));
|
|
2856
3106
|
registerTool(getVisibleElementsToolDefinition, getVisibleElementsTool);
|
|
2857
3107
|
registerTool(getAccessibilityToolDefinition, getAccessibilityTreeTool);
|
|
2858
|
-
registerTool(scrollToolDefinition, scrollTool);
|
|
2859
|
-
registerTool(clickToolDefinition, clickTool);
|
|
2860
|
-
registerTool(setValueToolDefinition, setValueTool);
|
|
3108
|
+
registerTool(scrollToolDefinition, withRecording("scroll", scrollTool));
|
|
3109
|
+
registerTool(clickToolDefinition, withRecording("click_element", clickTool));
|
|
3110
|
+
registerTool(setValueToolDefinition, withRecording("set_value", setValueTool));
|
|
2861
3111
|
registerTool(takeScreenshotToolDefinition, takeScreenshotTool);
|
|
2862
3112
|
registerTool(getCookiesToolDefinition, getCookiesTool);
|
|
2863
3113
|
registerTool(setCookieToolDefinition, setCookieTool);
|
|
2864
3114
|
registerTool(deleteCookiesToolDefinition, deleteCookiesTool);
|
|
2865
|
-
registerTool(tapElementToolDefinition, tapElementTool);
|
|
2866
|
-
registerTool(swipeToolDefinition, swipeTool);
|
|
2867
|
-
registerTool(dragAndDropToolDefinition, dragAndDropTool);
|
|
3115
|
+
registerTool(tapElementToolDefinition, withRecording("tap_element", tapElementTool));
|
|
3116
|
+
registerTool(swipeToolDefinition, withRecording("swipe", swipeTool));
|
|
3117
|
+
registerTool(dragAndDropToolDefinition, withRecording("drag_and_drop", dragAndDropTool));
|
|
2868
3118
|
registerTool(getAppStateToolDefinition, getAppStateTool);
|
|
2869
3119
|
registerTool(getContextsToolDefinition, getContextsTool);
|
|
2870
3120
|
registerTool(getCurrentContextToolDefinition, getCurrentContextTool);
|
|
@@ -2874,6 +3124,58 @@ registerTool(hideKeyboardToolDefinition, hideKeyboardTool);
|
|
|
2874
3124
|
registerTool(getGeolocationToolDefinition, getGeolocationTool);
|
|
2875
3125
|
registerTool(setGeolocationToolDefinition, setGeolocationTool);
|
|
2876
3126
|
registerTool(executeScriptToolDefinition, executeScriptTool);
|
|
3127
|
+
server.registerResource(
|
|
3128
|
+
"sessions",
|
|
3129
|
+
"wdio://sessions",
|
|
3130
|
+
{ description: "JSON index of all browser and app sessions with metadata and step counts" },
|
|
3131
|
+
async () => ({
|
|
3132
|
+
contents: [{ uri: "wdio://sessions", mimeType: "application/json", text: buildSessionsIndex() }]
|
|
3133
|
+
})
|
|
3134
|
+
);
|
|
3135
|
+
server.registerResource(
|
|
3136
|
+
"session-current-steps",
|
|
3137
|
+
"wdio://session/current/steps",
|
|
3138
|
+
{ description: "JSON step log for the currently active session" },
|
|
3139
|
+
async () => {
|
|
3140
|
+
const payload = buildCurrentSessionSteps();
|
|
3141
|
+
return {
|
|
3142
|
+
contents: [{ uri: "wdio://session/current/steps", mimeType: "application/json", text: payload?.stepsJson ?? '{"error":"No active session"}' }]
|
|
3143
|
+
};
|
|
3144
|
+
}
|
|
3145
|
+
);
|
|
3146
|
+
server.registerResource(
|
|
3147
|
+
"session-current-code",
|
|
3148
|
+
"wdio://session/current/code",
|
|
3149
|
+
{ description: "Generated WebdriverIO JS code for the currently active session" },
|
|
3150
|
+
async () => {
|
|
3151
|
+
const payload = buildCurrentSessionSteps();
|
|
3152
|
+
return {
|
|
3153
|
+
contents: [{ uri: "wdio://session/current/code", mimeType: "text/plain", text: payload?.generatedJs ?? "// No active session" }]
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3156
|
+
);
|
|
3157
|
+
server.registerResource(
|
|
3158
|
+
"session-steps",
|
|
3159
|
+
new ResourceTemplate("wdio://session/{sessionId}/steps", { list: void 0 }),
|
|
3160
|
+
{ description: "JSON step log for a specific session by ID" },
|
|
3161
|
+
async (uri, { sessionId }) => {
|
|
3162
|
+
const payload = buildSessionStepsById(sessionId);
|
|
3163
|
+
return {
|
|
3164
|
+
contents: [{ uri: uri.href, mimeType: "application/json", text: payload?.stepsJson ?? `{"error":"Session not found: ${sessionId}"}` }]
|
|
3165
|
+
};
|
|
3166
|
+
}
|
|
3167
|
+
);
|
|
3168
|
+
server.registerResource(
|
|
3169
|
+
"session-code",
|
|
3170
|
+
new ResourceTemplate("wdio://session/{sessionId}/code", { list: void 0 }),
|
|
3171
|
+
{ description: "Generated WebdriverIO JS code for a specific session by ID" },
|
|
3172
|
+
async (uri, { sessionId }) => {
|
|
3173
|
+
const payload = buildSessionStepsById(sessionId);
|
|
3174
|
+
return {
|
|
3175
|
+
contents: [{ uri: uri.href, mimeType: "text/plain", text: payload?.generatedJs ?? `// Session not found: ${sessionId}` }]
|
|
3176
|
+
};
|
|
3177
|
+
}
|
|
3178
|
+
);
|
|
2877
3179
|
async function main() {
|
|
2878
3180
|
const transport = new StdioServerTransport();
|
|
2879
3181
|
await server.connect(transport);
|