@wdio/mcp 2.4.1 → 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 CHANGED
@@ -85,6 +85,7 @@ appium
85
85
  - **Scrolling**: Smooth scrolling with configurable distances
86
86
  - **Attach to running Chrome**: Connect to an existing Chrome window via `--remote-debugging-port` — ideal for testing authenticated or pre-configured sessions
87
87
  - **Device emulation**: Apply mobile/tablet presets (iPhone 15, Pixel 7, etc.) to simulate responsive layouts without a physical device
88
+ - **Session Recording**: All tool calls are automatically recorded and exportable as runnable WebdriverIO JS
88
89
 
89
90
  ### Mobile App Automation (iOS/Android)
90
91
 
@@ -458,6 +459,16 @@ This eliminates the need to manually handle permission popups during automated t
458
459
  - **Data Format:** TOON (Token-Oriented Object Notation) for efficient LLM communication
459
460
  - **Element Detection:** XML-based page source parsing with intelligent filtering and multi-strategy locator generation
460
461
 
462
+ ### Session Recording & Code Export
463
+
464
+ Every tool call is automatically recorded to a session history. You can inspect sessions and export runnable code via MCP resources — no extra tool calls needed:
465
+
466
+ - `wdio://sessions` — lists all recorded sessions with type, timestamps, and step count
467
+ - `wdio://session/current/steps` — step log for the active session, plus a generated WebdriverIO JS script ready to run with `webdriverio`
468
+ - `wdio://session/{sessionId}/steps` — same for any past session by ID
469
+
470
+ The generated script reconstructs the full session — including capabilities, navigation, clicks, and inputs — as a standalone `import { remote } from 'webdriverio'` file.
471
+
461
472
  ## Troubleshooting
462
473
 
463
474
  **Browser automation not working?**
package/lib/server.js CHANGED
@@ -157,14 +157,7 @@ var startBrowserTool = async ({
157
157
  type: "browser",
158
158
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
159
159
  capabilities: wdioBrowser.capabilities,
160
- steps: [{
161
- index: 1,
162
- tool: "start_browser",
163
- params: { browser, headless, windowWidth, windowHeight, ...navigationUrl && { navigationUrl }, ...Object.keys(userCapabilities).length > 0 && { capabilities: userCapabilities } },
164
- status: "ok",
165
- durationMs: 0,
166
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
167
- }]
160
+ steps: []
168
161
  });
169
162
  state.currentSession = sessionId;
170
163
  let sizeNote = "";
@@ -211,6 +204,7 @@ var closeSessionTool = async (args = {}) => {
211
204
  };
212
205
  } catch (e) {
213
206
  return {
207
+ isError: true,
214
208
  content: [{ type: "text", text: `Error closing session: ${e}` }]
215
209
  };
216
210
  }
@@ -234,6 +228,7 @@ var navigateTool = async ({ url }) => {
234
228
  };
235
229
  } catch (e) {
236
230
  return {
231
+ isError: true,
237
232
  content: [{ type: "text", text: `Error navigating: ${e}` }]
238
233
  };
239
234
  }
@@ -264,6 +259,7 @@ var clickAction = async (selector, timeout, scrollToView = true) => {
264
259
  };
265
260
  } catch (e) {
266
261
  return {
262
+ isError: true,
267
263
  content: [{ type: "text", text: `Error clicking element: ${e}` }]
268
264
  };
269
265
  }
@@ -297,6 +293,7 @@ var setValueTool = async ({ selector, value, scrollToView = true, timeout = defa
297
293
  };
298
294
  } catch (e) {
299
295
  return {
296
+ isError: true,
300
297
  content: [{ type: "text", text: `Error entering text: ${e}` }]
301
298
  };
302
299
  }
@@ -343,7 +340,7 @@ function buildIOSCapabilities(appPath, options) {
343
340
  capabilities["appium:autoAcceptAlerts"] = void 0;
344
341
  }
345
342
  for (const [key, value] of Object.entries(options)) {
346
- if (!["deviceName", "platformVersion", "automationName", "autoAcceptAlerts", "autoDismissAlerts", "udid", "noReset", "fullReset", "newCommandTimeout"].includes(
343
+ if (!["deviceName", "platformVersion", "automationName", "autoGrantPermissions", "autoAcceptAlerts", "autoDismissAlerts", "udid", "noReset", "fullReset", "newCommandTimeout"].includes(
347
344
  key
348
345
  )) {
349
346
  capabilities[`appium:${key}`] = value;
@@ -380,7 +377,7 @@ function buildAndroidCapabilities(appPath, options) {
380
377
  capabilities["appium:appWaitActivity"] = options.appWaitActivity;
381
378
  }
382
379
  for (const [key, value] of Object.entries(options)) {
383
- if (!["deviceName", "platformVersion", "automationName", "autoGrantPermissions", "appWaitActivity", "noReset", "fullReset", "newCommandTimeout"].includes(
380
+ if (!["deviceName", "platformVersion", "automationName", "autoGrantPermissions", "autoAcceptAlerts", "autoDismissAlerts", "appWaitActivity", "noReset", "fullReset", "newCommandTimeout"].includes(
384
381
  key
385
382
  )) {
386
383
  capabilities[`appium:${key}`] = value;
@@ -521,32 +518,8 @@ var startAppTool = async (args) => {
521
518
  type: platform.toLowerCase(),
522
519
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
523
520
  capabilities: mergedCapabilities,
524
- steps: [{
525
- index: 1,
526
- tool: "start_app_session",
527
- params: {
528
- platform,
529
- deviceName,
530
- ...platformVersion !== void 0 && { platformVersion },
531
- ...automationName !== void 0 && { automationName },
532
- ...appPath !== void 0 && { appPath },
533
- ...udid !== void 0 && { udid },
534
- ...noReset !== void 0 && { noReset },
535
- ...fullReset !== void 0 && { fullReset },
536
- ...autoGrantPermissions !== void 0 && { autoGrantPermissions },
537
- ...autoAcceptAlerts !== void 0 && { autoAcceptAlerts },
538
- ...autoDismissAlerts !== void 0 && { autoDismissAlerts },
539
- ...appWaitActivity !== void 0 && { appWaitActivity },
540
- ...newCommandTimeout !== void 0 && { newCommandTimeout },
541
- ...appiumHost !== void 0 && { appiumHost },
542
- ...appiumPort !== void 0 && { appiumPort },
543
- ...appiumPath !== void 0 && { appiumPath },
544
- ...Object.keys(userCapabilities).length > 0 && { capabilities: userCapabilities }
545
- },
546
- status: "ok",
547
- durationMs: 0,
548
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
549
- }]
521
+ appiumConfig: { hostname: serverConfig.hostname, port: serverConfig.port, path: serverConfig.path },
522
+ steps: []
550
523
  });
551
524
  state2.currentSession = sessionId;
552
525
  const appInfo = appPath ? `
@@ -564,6 +537,7 @@ Appium Server: ${serverConfig.hostname}:${serverConfig.port}${serverConfig.path}
564
537
  };
565
538
  } catch (e) {
566
539
  return {
540
+ isError: true,
567
541
  content: [{ type: "text", text: `Error starting app session: ${e}` }]
568
542
  };
569
543
  }
@@ -597,6 +571,7 @@ var scrollTool = async ({ direction, pixels = 500 }) => {
597
571
  };
598
572
  } catch (e) {
599
573
  return {
574
+ isError: true,
600
575
  content: [{ type: "text", text: `Error scrolling: ${e}` }]
601
576
  };
602
577
  }
@@ -1769,6 +1744,7 @@ var getVisibleElementsTool = async (args) => {
1769
1744
  };
1770
1745
  } catch (e) {
1771
1746
  return {
1747
+ isError: true,
1772
1748
  content: [{ type: "text", text: `Error getting visible elements: ${e}` }]
1773
1749
  };
1774
1750
  }
@@ -2088,6 +2064,7 @@ var getAccessibilityTreeTool = async (args) => {
2088
2064
  };
2089
2065
  } catch (e) {
2090
2066
  return {
2067
+ isError: true,
2091
2068
  content: [{ type: "text", text: `Error getting accessibility tree: ${e}` }]
2092
2069
  };
2093
2070
  }
@@ -2148,6 +2125,7 @@ var takeScreenshotTool = async ({ outputPath }) => {
2148
2125
  };
2149
2126
  } catch (e) {
2150
2127
  return {
2128
+ isError: true,
2151
2129
  content: [{ type: "text", text: `Error taking screenshot: ${e.message}` }]
2152
2130
  };
2153
2131
  }
@@ -2187,6 +2165,7 @@ var getCookiesTool = async ({ name }) => {
2187
2165
  };
2188
2166
  } catch (e) {
2189
2167
  return {
2168
+ isError: true,
2190
2169
  content: [{ type: "text", text: `Error getting cookies: ${e}` }]
2191
2170
  };
2192
2171
  }
@@ -2224,6 +2203,7 @@ var setCookieTool = async ({
2224
2203
  };
2225
2204
  } catch (e) {
2226
2205
  return {
2206
+ isError: true,
2227
2207
  content: [{ type: "text", text: `Error setting cookie: ${e}` }]
2228
2208
  };
2229
2209
  }
@@ -2250,6 +2230,7 @@ var deleteCookiesTool = async ({ name }) => {
2250
2230
  };
2251
2231
  } catch (e) {
2252
2232
  return {
2233
+ isError: true,
2253
2234
  content: [{ type: "text", text: `Error deleting cookies: ${e}` }]
2254
2235
  };
2255
2236
  }
@@ -2283,10 +2264,12 @@ var tapElementTool = async (args) => {
2283
2264
  };
2284
2265
  }
2285
2266
  return {
2267
+ isError: true,
2286
2268
  content: [{ type: "text", text: "Error: Must provide either selector or x,y coordinates" }]
2287
2269
  };
2288
2270
  } catch (e) {
2289
2271
  return {
2272
+ isError: true,
2290
2273
  content: [{ type: "text", text: `Error tapping: ${e}` }]
2291
2274
  };
2292
2275
  }
@@ -2321,6 +2304,7 @@ var swipeTool = async (args) => {
2321
2304
  };
2322
2305
  } catch (e) {
2323
2306
  return {
2307
+ isError: true,
2324
2308
  content: [{ type: "text", text: `Error swiping: ${e}` }]
2325
2309
  };
2326
2310
  }
@@ -2354,10 +2338,12 @@ var dragAndDropTool = async (args) => {
2354
2338
  };
2355
2339
  }
2356
2340
  return {
2341
+ isError: true,
2357
2342
  content: [{ type: "text", text: "Error: Must provide either targetSelector or x,y coordinates" }]
2358
2343
  };
2359
2344
  } catch (e) {
2360
2345
  return {
2346
+ isError: true,
2361
2347
  content: [{ type: "text", text: `Error dragging: ${e}` }]
2362
2348
  };
2363
2349
  }
@@ -2395,6 +2381,7 @@ var getAppStateTool = async (args) => {
2395
2381
  };
2396
2382
  } catch (e) {
2397
2383
  return {
2384
+ isError: true,
2398
2385
  content: [{ type: "text", text: `Error getting app state: ${e}` }]
2399
2386
  };
2400
2387
  }
@@ -2436,6 +2423,7 @@ ${contexts.map((ctx, idx) => `${idx + 1}. ${ctx}`).join("\n")}`
2436
2423
  };
2437
2424
  } catch (e) {
2438
2425
  return {
2426
+ isError: true,
2439
2427
  content: [{ type: "text", text: `Error getting contexts: ${e}` }]
2440
2428
  };
2441
2429
  }
@@ -2449,6 +2437,7 @@ var getCurrentContextTool = async () => {
2449
2437
  };
2450
2438
  } catch (e) {
2451
2439
  return {
2440
+ isError: true,
2452
2441
  content: [{ type: "text", text: `Error getting current context: ${e}` }]
2453
2442
  };
2454
2443
  }
@@ -2464,14 +2453,7 @@ var switchContextTool = async (args) => {
2464
2453
  if (index >= 0 && index < contexts.length) {
2465
2454
  targetContext = contexts[index];
2466
2455
  } else {
2467
- return {
2468
- content: [
2469
- {
2470
- type: "text",
2471
- text: `Error: Invalid context index ${context}. Available contexts: ${contexts.length}`
2472
- }
2473
- ]
2474
- };
2456
+ throw new Error(`Error: Invalid context index ${context}. Available contexts: ${contexts.length}`);
2475
2457
  }
2476
2458
  }
2477
2459
  await browser.switchContext(targetContext);
@@ -2480,6 +2462,7 @@ var switchContextTool = async (args) => {
2480
2462
  };
2481
2463
  } catch (e) {
2482
2464
  return {
2465
+ isError: true,
2483
2466
  content: [{ type: "text", text: `Error switching context: ${e}` }]
2484
2467
  };
2485
2468
  }
@@ -2523,6 +2506,7 @@ var rotateDeviceTool = async (args) => {
2523
2506
  };
2524
2507
  } catch (e) {
2525
2508
  return {
2509
+ isError: true,
2526
2510
  content: [{ type: "text", text: `Error rotating device: ${e}` }]
2527
2511
  };
2528
2512
  }
@@ -2536,6 +2520,7 @@ var hideKeyboardTool = async () => {
2536
2520
  };
2537
2521
  } catch (e) {
2538
2522
  return {
2523
+ isError: true,
2539
2524
  content: [{ type: "text", text: `Error hiding keyboard: ${e}` }]
2540
2525
  };
2541
2526
  }
@@ -2557,6 +2542,7 @@ var getGeolocationTool = async () => {
2557
2542
  };
2558
2543
  } catch (e) {
2559
2544
  return {
2545
+ isError: true,
2560
2546
  content: [{ type: "text", text: `Error getting geolocation: ${e}` }]
2561
2547
  };
2562
2548
  }
@@ -2579,6 +2565,7 @@ var setGeolocationTool = async (args) => {
2579
2565
  };
2580
2566
  } catch (e) {
2581
2567
  return {
2568
+ isError: true,
2582
2569
  content: [{ type: "text", text: `Error setting geolocation: ${e}` }]
2583
2570
  };
2584
2571
  }
@@ -2644,6 +2631,7 @@ var executeScriptTool = async (args) => {
2644
2631
  };
2645
2632
  } catch (e) {
2646
2633
  return {
2634
+ isError: true,
2647
2635
  content: [{ type: "text", text: `Error executing script: ${e}` }]
2648
2636
  };
2649
2637
  }
@@ -2718,6 +2706,19 @@ var attachBrowserTool = async ({
2718
2706
  capabilities: browser.capabilities,
2719
2707
  isAttached: true
2720
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
+ });
2721
2722
  if (activeUrl) {
2722
2723
  await browser.url(activeUrl);
2723
2724
  }
@@ -2733,6 +2734,7 @@ Current page: "${title}" (${url})`
2733
2734
  };
2734
2735
  } catch (e) {
2735
2736
  return {
2737
+ isError: true,
2736
2738
  content: [{ type: "text", text: `Error attaching to browser: ${e}` }]
2737
2739
  };
2738
2740
  }
@@ -2767,11 +2769,13 @@ var emulateDeviceTool = async ({
2767
2769
  const metadata = state2.sessionMetadata.get(sessionId);
2768
2770
  if (metadata?.type === "ios" || metadata?.type === "android") {
2769
2771
  return {
2772
+ isError: true,
2770
2773
  content: [{ type: "text", text: "Error: emulate_device is only supported for web browser sessions, not iOS/Android." }]
2771
2774
  };
2772
2775
  }
2773
2776
  if (!browser.isBidi) {
2774
2777
  return {
2778
+ isError: true,
2775
2779
  content: [{
2776
2780
  type: "text",
2777
2781
  text: "Error: emulate_device requires a BiDi-enabled session.\nRestart the browser with: start_browser({ capabilities: { webSocketUrl: true } })"
@@ -2791,7 +2795,7 @@ var emulateDeviceTool = async ({
2791
2795
  ${names.join("\n")}` }]
2792
2796
  };
2793
2797
  }
2794
- return { content: [{ type: "text", text: `Error listing devices: ${e}` }] };
2798
+ return { isError: true, content: [{ type: "text", text: `Error listing devices: ${e}` }] };
2795
2799
  }
2796
2800
  return { content: [{ type: "text", text: "Could not retrieve device list." }] };
2797
2801
  }
@@ -2820,10 +2824,11 @@ ${names.join("\n")}` }]
2820
2824
  }]
2821
2825
  };
2822
2826
  }
2823
- return { content: [{ type: "text", text: `Error: ${e}` }] };
2827
+ return { isError: true, content: [{ type: "text", text: `Error: ${e}` }] };
2824
2828
  }
2825
2829
  } catch (e) {
2826
2830
  return {
2831
+ isError: true,
2827
2832
  content: [{ type: "text", text: `Error: ${e}` }]
2828
2833
  };
2829
2834
  }
@@ -2837,7 +2842,7 @@ var package_default = {
2837
2842
  type: "git",
2838
2843
  url: "git://github.com/webdriverio/mcp.git"
2839
2844
  },
2840
- version: "2.4.0",
2845
+ version: "2.4.1",
2841
2846
  description: "MCP server with WebdriverIO for browser and mobile app automation (iOS/Android via Appium)",
2842
2847
  main: "./lib/server.js",
2843
2848
  module: "./lib/server.js",
@@ -2940,9 +2945,7 @@ function withRecording(toolName, callback) {
2940
2945
  return async (params, extra) => {
2941
2946
  const start = Date.now();
2942
2947
  const result = await callback(params, extra);
2943
- const isError = result.content.some(
2944
- (c) => c.type === "text" && typeof c.text === "string" && c.text.startsWith("Error")
2945
- );
2948
+ const isError = result.isError === true;
2946
2949
  appendStep(
2947
2950
  toolName,
2948
2951
  params,
@@ -2964,7 +2967,7 @@ function formatParams(params) {
2964
2967
  function indentJson(value) {
2965
2968
  return JSON.stringify(value, null, 2).split("\n").map((line, i) => i > 0 ? ` ${line}` : line).join("\n");
2966
2969
  }
2967
- function generateStep(step) {
2970
+ function generateStep(step, history) {
2968
2971
  if (step.tool === "__session_transition__") {
2969
2972
  const newId = step.params.newSessionId ?? "unknown";
2970
2973
  return `// --- new session: ${newId} started at ${step.timestamp} ---`;
@@ -2975,52 +2978,29 @@ function generateStep(step) {
2975
2978
  const p = step.params;
2976
2979
  switch (step.tool) {
2977
2980
  case "start_browser": {
2978
- const browserName = p.browser === "edge" ? "msedge" : String(p.browser ?? "chrome");
2979
- const headless = p.headless !== false;
2980
- const width = p.windowWidth ?? 1920;
2981
- const height = p.windowHeight ?? 1080;
2982
- const args = [`--window-size=${width},${height}`];
2983
- if (headless && browserName !== "safari") {
2984
- args.push("--headless=new", "--disable-gpu", "--disable-dev-shm-usage");
2985
- }
2986
- const caps = { browserName };
2987
- if (browserName === "chrome") caps["goog:chromeOptions"] = { args };
2988
- else if (browserName === "msedge") caps["ms:edgeOptions"] = { args };
2989
- else if (browserName === "firefox" && headless) caps["moz:firefoxOptions"] = { args: ["-headless"] };
2990
- const extra = p.capabilities;
2991
- const merged = extra ? { ...caps, ...extra } : caps;
2992
2981
  const nav = p.navigationUrl ? `
2993
2982
  await browser.url('${escapeStr(p.navigationUrl)}');` : "";
2994
2983
  return `const browser = await remote({
2995
- capabilities: ${indentJson(merged)}
2984
+ capabilities: ${indentJson(history.capabilities)}
2996
2985
  });${nav}`;
2997
2986
  }
2998
2987
  case "start_app_session": {
2999
- const caps = {
3000
- platformName: p.platform,
3001
- "appium:deviceName": p.deviceName,
3002
- ...p.platformVersion !== void 0 && { "appium:platformVersion": p.platformVersion },
3003
- ...p.automationName !== void 0 && { "appium:automationName": p.automationName },
3004
- ...p.appPath !== void 0 && { "appium:app": p.appPath },
3005
- ...p.udid !== void 0 && { "appium:udid": p.udid },
3006
- ...p.noReset !== void 0 && { "appium:noReset": p.noReset },
3007
- ...p.fullReset !== void 0 && { "appium:fullReset": p.fullReset },
3008
- ...p.autoGrantPermissions !== void 0 && { "appium:autoGrantPermissions": p.autoGrantPermissions },
3009
- ...p.autoAcceptAlerts !== void 0 && { "appium:autoAcceptAlerts": p.autoAcceptAlerts },
3010
- ...p.autoDismissAlerts !== void 0 && { "appium:autoDismissAlerts": p.autoDismissAlerts },
3011
- ...p.appWaitActivity !== void 0 && { "appium:appWaitActivity": p.appWaitActivity },
3012
- ...p.newCommandTimeout !== void 0 && { "appium:newCommandTimeout": p.newCommandTimeout },
3013
- ...p.capabilities ?? {}
3014
- };
3015
2988
  const config = {
3016
2989
  protocol: "http",
3017
- hostname: p.appiumHost ?? "localhost",
3018
- port: p.appiumPort ?? 4723,
3019
- path: p.appiumPath ?? "/",
3020
- capabilities: caps
2990
+ hostname: history.appiumConfig?.hostname ?? "localhost",
2991
+ port: history.appiumConfig?.port ?? 4723,
2992
+ path: history.appiumConfig?.path ?? "/",
2993
+ capabilities: history.capabilities
3021
2994
  };
3022
2995
  return `const browser = await remote(${indentJson(config)});`;
3023
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
+ }
3024
3004
  case "navigate":
3025
3005
  return `await browser.url('${escapeStr(p.url)}');`;
3026
3006
  case "click_element":
@@ -3048,7 +3028,7 @@ await browser.url('${escapeStr(p.navigationUrl)}');` : "";
3048
3028
  }
3049
3029
  }
3050
3030
  function generateCode(history) {
3051
- const steps = history.steps.map(generateStep).join("\n");
3031
+ const steps = history.steps.map((step) => generateStep(step, history)).join("\n");
3052
3032
  return `import { remote } from 'webdriverio';
3053
3033
 
3054
3034
  ${steps}
@@ -3062,47 +3042,37 @@ function getCurrentSessionId() {
3062
3042
  }
3063
3043
  function buildSessionsIndex() {
3064
3044
  const histories = getSessionHistory();
3065
- if (histories.size === 0) return "No sessions recorded.";
3066
3045
  const currentId = getCurrentSessionId();
3067
- const lines = [`Sessions (${histories.size} total):
3068
- `];
3069
- for (const [id, h] of histories) {
3070
- const ended = h.endedAt ?? "-";
3071
- const current = id === currentId ? " [current]" : "";
3072
- lines.push(`- ${id} ${h.type} started: ${h.startedAt} ended: ${ended} ${h.steps.length} steps${current}`);
3073
- }
3074
- return lines.join("\n");
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 });
3075
3055
  }
3076
3056
  function buildCurrentSessionSteps() {
3077
3057
  const currentId = getCurrentSessionId();
3078
- if (!currentId) return "No active session.";
3058
+ if (!currentId) return null;
3079
3059
  return buildSessionStepsById(currentId);
3080
3060
  }
3081
3061
  function buildSessionStepsById(sessionId) {
3082
3062
  const history = getSessionHistory().get(sessionId);
3083
- if (!history) return `Session not found: ${sessionId}`;
3084
- return formatSessionSteps(history);
3085
- }
3086
- function formatSessionSteps(history) {
3087
- const header = `Session: ${history.sessionId} (${history.type}) \u2014 ${history.steps.length} steps
3088
- `;
3089
- const stepLines = history.steps.map((step) => {
3090
- if (step.tool === "__session_transition__") {
3091
- return `--- session transitioned to ${step.params.newSessionId ?? "unknown"} at ${step.timestamp} ---`;
3092
- }
3093
- const statusLabel = step.status === "ok" ? "[ok] " : "[error]";
3094
- const params = Object.entries(step.params).map(([k, v]) => `${k}="${v}"`).join(" ");
3095
- const errorSuffix = step.error ? ` \u2014 ${step.error}` : "";
3096
- return `${step.index}. ${statusLabel} ${step.tool.padEnd(24)} ${params}${errorSuffix} ${step.durationMs}ms`;
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
3097
3074
  });
3098
- const stepsText = stepLines.length > 0 ? stepLines.join("\n") : "(no steps yet)";
3099
- const jsCode = generateCode(history);
3100
- return `${header}
3101
- Steps:
3102
- ${stepsText}
3103
-
3104
- --- Generated WebdriverIO JS ---
3105
- ${jsCode}`;
3075
+ return { stepsJson, generatedJs: generateCode(history) };
3106
3076
  }
3107
3077
 
3108
3078
  // src/server.ts
@@ -3127,10 +3097,10 @@ var registerTool = (definition, callback) => server.registerTool(definition.name
3127
3097
  description: definition.description,
3128
3098
  inputSchema: definition.inputSchema
3129
3099
  }, callback);
3130
- registerTool(startBrowserToolDefinition, startBrowserTool);
3131
- registerTool(startAppToolDefinition, startAppTool);
3100
+ registerTool(startBrowserToolDefinition, withRecording("start_browser", startBrowserTool));
3101
+ registerTool(startAppToolDefinition, withRecording("start_app_session", startAppTool));
3132
3102
  registerTool(closeSessionToolDefinition, closeSessionTool);
3133
- registerTool(attachBrowserToolDefinition, attachBrowserTool);
3103
+ registerTool(attachBrowserToolDefinition, withRecording("attach_browser", attachBrowserTool));
3134
3104
  registerTool(emulateDeviceToolDefinition, emulateDeviceTool);
3135
3105
  registerTool(navigateToolDefinition, withRecording("navigate", navigateTool));
3136
3106
  registerTool(getVisibleElementsToolDefinition, getVisibleElementsTool);
@@ -3157,30 +3127,54 @@ registerTool(executeScriptToolDefinition, executeScriptTool);
3157
3127
  server.registerResource(
3158
3128
  "sessions",
3159
3129
  "wdio://sessions",
3160
- { description: "Index of all browser and app sessions with step counts" },
3130
+ { description: "JSON index of all browser and app sessions with metadata and step counts" },
3161
3131
  async () => ({
3162
- contents: [{ uri: "wdio://sessions", mimeType: "text/plain", text: buildSessionsIndex() }]
3132
+ contents: [{ uri: "wdio://sessions", mimeType: "application/json", text: buildSessionsIndex() }]
3163
3133
  })
3164
3134
  );
3165
3135
  server.registerResource(
3166
3136
  "session-current-steps",
3167
3137
  "wdio://session/current/steps",
3168
- { description: "Steps for the currently active session with generated WebdriverIO JS" },
3169
- async () => ({
3170
- contents: [{ uri: "wdio://session/current/steps", mimeType: "text/plain", text: buildCurrentSessionSteps() }]
3171
- })
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
+ }
3172
3156
  );
3173
3157
  server.registerResource(
3174
3158
  "session-steps",
3175
3159
  new ResourceTemplate("wdio://session/{sessionId}/steps", { list: void 0 }),
3176
- { description: "Steps for a specific session by ID with generated WebdriverIO JS" },
3177
- async (uri, { sessionId }) => ({
3178
- contents: [{
3179
- uri: uri.href,
3180
- mimeType: "text/plain",
3181
- text: buildSessionStepsById(sessionId)
3182
- }]
3183
- })
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
+ }
3184
3178
  );
3185
3179
  async function main() {
3186
3180
  const transport = new StdioServerTransport();