@wdio/mcp 3.4.0 → 3.4.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
@@ -46,7 +46,7 @@ var package_default = {
46
46
  type: "git",
47
47
  url: "git://github.com/webdriverio/mcp.git"
48
48
  },
49
- version: "3.3.0",
49
+ version: "3.4.1",
50
50
  description: "MCP server with WebdriverIO for browser and mobile app automation (iOS/Android via Appium)",
51
51
  main: "./lib/server.js",
52
52
  module: "./lib/server.js",
@@ -116,7 +116,15 @@ var package_default = {
116
116
  packageManager: "pnpm@10.32.1",
117
117
  pnpm: {
118
118
  overrides: {
119
- "release-it>undici": "^6.24.0"
119
+ "release-it>undici": "^6.24.0",
120
+ "basic-ftp": "^5.3.1",
121
+ lodash: "^4.18.1",
122
+ hono: "^4.12.17",
123
+ "@hono/node-server": "^1.19.14",
124
+ "fast-xml-parser": "^5.7.3",
125
+ defu: "^6.1.7",
126
+ "vitest>vite": "^7.3.2",
127
+ "ip-address": "^10.1.1"
120
128
  }
121
129
  }
122
130
  };
@@ -173,7 +181,8 @@ init_state();
173
181
  import { z } from "zod";
174
182
  var navigateToolDefinition = {
175
183
  name: "navigate",
176
- description: "Loads a URL in the current tab and waits for the page load event. Resets page state (DOM, JS runtime). Use instead of clicking links to go directly to a known URL.",
184
+ description: "Loads a URL in the current tab and waits for the page load event. Resets page state \u2014 DOM, JS runtime, timers, and frame context are destroyed. Use instead of clicking links when the target URL is known.",
185
+ annotations: { title: "Navigate to URL", destructiveHint: false, idempotentHint: true },
177
186
  inputSchema: {
178
187
  url: z.string().min(1).describe("The URL to navigate to")
179
188
  }
@@ -214,7 +223,8 @@ var coerceBoolean = z2.preprocess((val) => {
214
223
  var defaultTimeout = 3e3;
215
224
  var clickToolDefinition = {
216
225
  name: "click_element",
217
- description: "Waits for an element to exist, scrolls it into view, and calls element.click(). For browser sessions. On iOS, element.click() is sometimes ignored \u2014 use tap_element (which calls element.tap()) instead.",
226
+ description: "Waits for an element, scrolls it into view, and fires element.click(). May trigger navigation, form submission, or modals. Browser sessions only \u2014 on iOS element.click() is silently ignored; use tap_element instead. Default timeout: 3000ms.",
227
+ annotations: { title: "Click Element", destructiveHint: false },
218
228
  inputSchema: {
219
229
  selector: z3.string().describe(`Value for the selector, in the form of css selector or xpath ("button.my-class" or "//button[@class='my-class']" or "button=Exact text with spaces" or "a*=Link containing text")`),
220
230
  scrollToView: coerceBoolean.optional().describe("Whether to scroll the element into view before clicking").default(true),
@@ -247,7 +257,8 @@ import { z as z4 } from "zod";
247
257
  var defaultTimeout2 = 3e3;
248
258
  var setValueToolDefinition = {
249
259
  name: "set_value",
250
- description: "Clears an input or textarea and types the given text. Always replaces existing content. Fails if the element is not found or not interactable within the timeout.",
260
+ description: "Clears an input or textarea then types the given text character by character. Always replaces existing content \u2014 clearValue() runs first. Triggers input, change, and key events which may fire validation or autocomplete. Scrolls into view by default.",
261
+ annotations: { title: "Set Input Value", destructiveHint: false, idempotentHint: true },
251
262
  inputSchema: {
252
263
  selector: z4.string().describe(`Value for the selector, in the form of css selector or xpath ("button.my-class" or "//button[@class='my-class']")`),
253
264
  value: z4.string().describe("Text to enter into the element"),
@@ -281,7 +292,8 @@ init_state();
281
292
  import { z as z5 } from "zod";
282
293
  var scrollToolDefinition = {
283
294
  name: "scroll",
284
- description: "scrolls the page by specified pixels (browser only). For mobile, use the swipe tool.",
295
+ description: "Scrolls the page vertically by a pixel amount. Browser-only \u2014 for mobile scrolling use swipe. Only supports up/down; no horizontal scrolling.",
296
+ annotations: { title: "Scroll Page", destructiveHint: false },
285
297
  inputSchema: {
286
298
  direction: z5.enum(["up", "down"]).describe("Scroll direction"),
287
299
  pixels: z5.number().optional().default(500).describe("Number of pixels to scroll")
@@ -316,7 +328,8 @@ var scrollTool = async ({ direction, pixels = 500 }) => scrollAction(direction,
316
328
  import { z as z6 } from "zod";
317
329
  var setCookieToolDefinition = {
318
330
  name: "set_cookie",
319
- description: "Sets a browser cookie for the active session. The browser must already be on the target domain \u2014 cookies cannot be set cross-domain. Use to inject session tokens or feature flags without going through login flows.",
331
+ description: "Sets a browser cookie on the active session. The browser must already be on the target domain \u2014 cookies cannot be set cross-domain. Use to inject session tokens or feature flags without login flows.",
332
+ annotations: { title: "Set Cookie", destructiveHint: false, idempotentHint: true },
320
333
  inputSchema: {
321
334
  name: z6.string().describe("Cookie name"),
322
335
  value: z6.string().describe("Cookie value"),
@@ -355,7 +368,8 @@ var setCookieTool = async ({
355
368
  };
356
369
  var deleteCookiesToolDefinition = {
357
370
  name: "delete_cookies",
358
- description: "deletes all cookies or a specific cookie by name",
371
+ description: "Deletes all cookies or a single cookie by name from the current browser session. Irreversible \u2014 deleted cookies cannot be recovered.",
372
+ annotations: { title: "Delete Cookies", destructiveHint: true, idempotentHint: true },
359
373
  inputSchema: {
360
374
  name: z6.string().optional().describe("Optional cookie name to delete a specific cookie. If not provided, deletes all cookies")
361
375
  }
@@ -387,7 +401,8 @@ init_state();
387
401
  import { z as z7 } from "zod";
388
402
  var tapElementToolDefinition = {
389
403
  name: "tap_element",
390
- description: "Calls element.tap() on a matched element or taps at absolute screen coordinates. Use on iOS when element.click() (click_element) is ignored \u2014 tap is the native gesture iOS responds to. Mobile-only.",
404
+ description: "Taps a matched element via element.tap() or at absolute screen coordinates (x, y). No scroll-into-view or wait \u2014 element must already be visible on screen. Use instead of click_element on iOS where element.click() is ignored. Provide selector OR both x and y. Mobile-only.",
405
+ annotations: { title: "Tap Element", destructiveHint: false },
391
406
  inputSchema: {
392
407
  selector: z7.string().optional().describe("Element selector (CSS, XPath, accessibility ID, or UiAutomator)"),
393
408
  x: z7.number().optional().describe("X coordinate for screen tap (if no selector provided)"),
@@ -424,7 +439,8 @@ var tapAction = async (args) => {
424
439
  var tapElementTool = async (args) => tapAction(args);
425
440
  var swipeToolDefinition = {
426
441
  name: "swipe",
427
- description: 'Performs a full-screen swipe gesture on mobile. Direction is content movement (e.g. "up" scrolls a list upward, not the finger direction). Use to scroll past visible bounds; for moving a specific element use drag_and_drop. Mobile-only \u2014 use scroll for browsers.',
442
+ description: 'Performs a full-screen swipe gesture. Direction is content movement \u2014 "up" scrolls content upward (finger moves down). For browser scrolling use scroll; for dragging a specific element use drag_and_drop. No error if content cannot scroll further. Mobile-only.',
443
+ annotations: { title: "Swipe Screen", destructiveHint: false },
428
444
  inputSchema: {
429
445
  direction: z7.enum(["up", "down", "left", "right"]).describe("Swipe direction"),
430
446
  duration: z7.number().min(100).max(5e3).optional().describe("Swipe duration in milliseconds (default: 500)"),
@@ -460,7 +476,8 @@ var swipeAction = async (args) => {
460
476
  var swipeTool = async (args) => swipeAction(args);
461
477
  var dragAndDropToolDefinition = {
462
478
  name: "drag_and_drop",
463
- description: "drags an element to another element or coordinates (mobile)",
479
+ description: "Drags an element to another element or to relative x/y offsets. x and y are offsets from the source element, not absolute screen coordinates (unlike tap_element). Provide targetSelector OR both x and y. Mobile-only.",
480
+ annotations: { title: "Drag and Drop", destructiveHint: false },
464
481
  inputSchema: {
465
482
  sourceSelector: z7.string().describe("Source element selector to drag"),
466
483
  targetSelector: z7.string().optional().describe("Target element selector to drop onto"),
@@ -504,7 +521,8 @@ init_state();
504
521
  import { z as z8 } from "zod";
505
522
  var switchContextToolDefinition = {
506
523
  name: "switch_context",
507
- description: "Switches between native and webview automation contexts in a hybrid mobile app. Required before using CSS/XPath selectors inside an embedded webview \u2014 switch to WEBVIEW_* first, then switch back to NATIVE_APP for native elements. List available contexts using get_contexts tool or wdio://session/current/contexts resource.",
524
+ description: "Switches between native and webview automation contexts in a hybrid mobile app. In NATIVE_APP context, use accessibility IDs; in WEBVIEW_* context, use CSS/XPath. Changes persist for all subsequent commands. Accepts context name or 1-based index. Use get_contexts to discover available targets. Mobile-only.",
525
+ annotations: { title: "Switch Context", destructiveHint: false, idempotentHint: true },
508
526
  inputSchema: {
509
527
  context: z8.string().describe(
510
528
  'Context name to switch to (e.g., "NATIVE_APP", "WEBVIEW_com.example.app", or use index from wdio://session/current/contexts resource)'
@@ -542,19 +560,22 @@ init_state();
542
560
  import { z as z9 } from "zod";
543
561
  var hideKeyboardToolDefinition = {
544
562
  name: "hide_keyboard",
545
- description: "Dismisses the software keyboard on mobile. Call after text entry when the keyboard obscures elements you need to interact with next. No-op if already hidden. Mobile-only.",
563
+ description: "Dismisses the on-screen keyboard on mobile. Call after text entry when the keyboard obscures elements. No-op if already hidden. Mobile-only.",
564
+ annotations: { title: "Hide Keyboard", destructiveHint: false, idempotentHint: true },
546
565
  inputSchema: {}
547
566
  };
548
567
  var rotateDeviceToolDefinition = {
549
568
  name: "rotate_device",
550
- description: "Rotates a mobile device to portrait or landscape and waits for the OS rotation to complete. Use to test orientation-dependent layouts. Mobile-only; no effect in browser sessions.",
569
+ description: "Rotates a mobile device to portrait or landscape orientation. Waits for the OS rotation animation to complete. Use to test orientation-dependent layouts. Mobile-only; no effect in browser sessions.",
570
+ annotations: { title: "Rotate Device", destructiveHint: false, idempotentHint: true },
551
571
  inputSchema: {
552
572
  orientation: z9.enum(["PORTRAIT", "LANDSCAPE"]).describe("Device orientation")
553
573
  }
554
574
  };
555
575
  var setGeolocationToolDefinition = {
556
576
  name: "set_geolocation",
557
- description: "Overrides the device GPS coordinates for the session. Affects navigator.geolocation on web and location services on mobile. Location permissions must be granted to the app before calling this.",
577
+ description: "Overrides GPS coordinates for the session. Affects navigator.geolocation in browsers and location services on mobile. Location permissions must already be granted to the app.",
578
+ annotations: { title: "Set Geolocation", destructiveHint: false, idempotentHint: true },
558
579
  inputSchema: {
559
580
  latitude: z9.number().min(-90).max(90).describe("Latitude coordinate"),
560
581
  longitude: z9.number().min(-180).max(180).describe("Longitude coordinate"),
@@ -619,21 +640,8 @@ init_state();
619
640
  import { z as z10 } from "zod";
620
641
  var executeScriptToolDefinition = {
621
642
  name: "execute_script",
622
- description: `Executes JavaScript in browser or mobile commands via Appium.
623
-
624
- **Option B for browser interaction** \u2014 prefer get_visible_elements or click_element/set_value with a selector instead. Use execute_script only when no dedicated tool covers the action (e.g. reading computed values, triggering custom events, scrolling to a position).
625
-
626
- **Browser:** Runs JavaScript in page context. Use 'return' to get values back.
627
- - Example: execute_script({ script: "return document.title" })
628
- - Example: execute_script({ script: "return window.scrollY" })
629
- - Example: execute_script({ script: "arguments[0].click()", args: ["#myButton"] })
630
-
631
- **Mobile (Appium):** Executes mobile-specific commands using 'mobile: <command>' syntax.
632
- - Press key (Android): execute_script({ script: "mobile: pressKey", args: [{ keycode: 4 }] }) // BACK=4, HOME=3
633
- - Activate app: execute_script({ script: "mobile: activateApp", args: [{ appId: "com.example" }] })
634
- - Terminate app: execute_script({ script: "mobile: terminateApp", args: [{ appId: "com.example" }] })
635
- - Deep link: execute_script({ script: "mobile: deepLink", args: [{ url: "myapp://screen", package: "com.example" }] })
636
- - Shell command (Android): execute_script({ script: "mobile: shell", args: [{ command: "dumpsys", args: ["battery"] }] })`,
643
+ description: `Executes arbitrary JavaScript in browser page context or Appium mobile: commands. Can read/modify DOM, trigger events, terminate apps, or run Android shell commands \u2014 use only when no dedicated tool covers the action. Browser: pass JS in script, use 'return' for values, string args matching selectors auto-resolve to elements. Mobile: use 'mobile: <command>' syntax in script with args array (e.g. "mobile: pressKey", "mobile: activateApp"). Prefer click_element/set_value/get_elements for standard interactions.`,
644
+ annotations: { title: "Execute Script", destructiveHint: false },
637
645
  inputSchema: {
638
646
  script: z10.string().describe('JavaScript code (browser) or mobile command string like "mobile: pressKey" (Appium)'),
639
647
  args: z10.array(z10.any()).optional().describe("Arguments to pass to the script. For browser: element selectors or values. For mobile commands: command-specific parameters as objects.")
@@ -1839,7 +1847,8 @@ async function getElements(browser, params) {
1839
1847
  import { encode } from "@toon-format/toon";
1840
1848
  var getElementsToolDefinition = {
1841
1849
  name: "get_elements",
1842
- description: "Get interactable elements on the current page. Use when wdio://session/current/elements does not return the desired elements.",
1850
+ description: "Returns interactable elements on the current page with selectors, text, and bounding boxes. Supports filtering by element type, viewport visibility, and pagination. Use when the wdio://session/current/elements resource does not return desired elements.",
1851
+ annotations: { title: "Get Visible Elements", readOnlyHint: true, idempotentHint: true },
1843
1852
  inputSchema: {
1844
1853
  inViewportOnly: coerceBoolean.optional().default(false).describe("Only return elements visible in the current viewport (default: false)."),
1845
1854
  includeContainers: coerceBoolean.optional().default(false).describe("Include container elements like divs and sections (default: false)"),
@@ -1874,19 +1883,8 @@ import { z as z12 } from "zod";
1874
1883
  var USER_DATA_DIR = join(tmpdir(), "chrome-debug");
1875
1884
  var launchChromeToolDefinition = {
1876
1885
  name: "launch_chrome",
1877
- description: `Prepares and launches Chrome with remote debugging enabled so attach_browser() can connect.
1878
-
1879
- Two modes:
1880
-
1881
- newInstance (default): Opens a Chrome window alongside your existing one using a separate
1882
- profile dir. Your current Chrome session is untouched.
1883
-
1884
- freshSession: Launches Chrome with an empty profile (no cookies, no logins).
1885
-
1886
- Use copyProfileFiles: true to carry over your cookies and logins into the debug session.
1887
- Note: changes made during the session won't sync back to your main profile.
1888
-
1889
- After this tool succeeds, call attach_browser() to connect.`,
1886
+ description: 'Launches Chrome with remote debugging enabled. Wipes and recreates a temporary profile directory on each call. Mode "newInstance" (default) runs alongside existing Chrome; "freshSession" starts with an empty profile. Set copyProfileFiles to copy cookies/logins from your Default profile \u2014 changes do not sync back. After launch, call start_session with attach: true to connect. Spawns a detached Chrome process that persists if the server exits.',
1887
+ annotations: { title: "Launch Chrome", destructiveHint: false },
1890
1888
  inputSchema: {
1891
1889
  port: z12.number().default(9222).describe("Remote debugging port (default: 9222)"),
1892
1890
  mode: z12.enum(["newInstance", "freshSession"]).default("newInstance").describe(
@@ -1990,14 +1988,8 @@ import { z as z13 } from "zod";
1990
1988
  var restoreFunctions = /* @__PURE__ */ new Map();
1991
1989
  var emulateDeviceToolDefinition = {
1992
1990
  name: "emulate_device",
1993
- description: `Emulate a mobile or tablet device in the current browser session (sets viewport, DPR, user-agent, touch events).
1994
-
1995
- Requires a BiDi-enabled session: start_browser({ capabilities: { webSocketUrl: true } })
1996
-
1997
- Usage:
1998
- emulate_device() \u2014 list available device presets
1999
- emulate_device({ device: "iPhone 15" }) \u2014 activate emulation
2000
- emulate_device({ device: "reset" }) \u2014 restore desktop defaults`,
1991
+ description: 'Emulates a mobile or tablet device in the current browser session by setting viewport, DPR, user-agent, and touch events. Requires a BiDi-enabled session (start_session with capabilities: { webSocketUrl: true }). Omit device to list available presets. Pass "reset" to restore desktop defaults. Changes persist for all subsequent tool calls until reset or session close. Browser-only.',
1992
+ annotations: { title: "Emulate Device", destructiveHint: false, idempotentHint: true },
2001
1993
  inputSchema: {
2002
1994
  device: z13.string().optional().describe(
2003
1995
  'Device preset name (e.g. "iPhone 15", "Pixel 7"). Omit to list available presets. Pass "reset" to restore desktop defaults.'
@@ -3502,7 +3494,8 @@ var browserEnum = z14.enum(["chrome", "firefox", "edge", "safari"]);
3502
3494
  var automationEnum = z14.enum(["XCUITest", "UiAutomator2"]);
3503
3495
  var startSessionToolDefinition = {
3504
3496
  name: "start_session",
3505
- description: 'Starts a new browser or mobile automation session. Only one active session at a time \u2014 starting a new one closes the existing one. Use platform "browser" with a browser name, or "ios"/"android" with a deviceName. Use attach mode to connect to an already-running Chrome instance via CDP.',
3497
+ description: 'Starts a browser or mobile automation session. Only one active session at a time \u2014 starting a new one closes the existing session first. Use platform "browser" with a browser name, or "ios"/"android" with deviceName. Set attach: true to connect to a running Chrome via CDP instead of launching a new browser.',
3498
+ annotations: { title: "Start Session", destructiveHint: false },
3506
3499
  inputSchema: {
3507
3500
  provider: z14.enum(["local", "browserstack"]).optional().default("local").describe("Session provider (default: local)"),
3508
3501
  platform: platformEnum.describe("Session platform type"),
@@ -3549,7 +3542,8 @@ var startSessionToolDefinition = {
3549
3542
  };
3550
3543
  var closeSessionToolDefinition = {
3551
3544
  name: "close_session",
3552
- description: "Closes or detaches from the current session. Detach disconnects without terminating the process, preserving app state on the Appium server. Sessions started with noReset: true auto-detach by default.",
3545
+ description: "Closes the current session or detaches without terminating. Detach preserves app state on the Appium server \u2014 sessions with noReset: true auto-detach by default. Closing a browser attach session terminates chromedriver but the Chrome process spawned by launch_chrome remains running.",
3546
+ annotations: { title: "Close Session", destructiveHint: true },
3553
3547
  inputSchema: {
3554
3548
  detach: coerceBoolean.optional().describe("If true, disconnect without terminating (preserves app state). Default: false")
3555
3549
  }
@@ -3799,7 +3793,8 @@ init_state();
3799
3793
  import { z as z15 } from "zod";
3800
3794
  var switchTabToolDefinition = {
3801
3795
  name: "switch_tab",
3802
- description: "Focuses a browser tab by window handle or 0-based index. All subsequent tool calls operate on the newly active tab. Get handles from get_tabs tool or wdio://session/current/tabs resource. Browser-only \u2014 use switch_context for mobile webviews.",
3796
+ description: "Focuses a browser tab by window handle or 0-based index. All subsequent tool calls operate on the active tab. Provide handle OR index \u2014 use get_tabs to find them. Browser-only; use switch_context for mobile webviews.",
3797
+ annotations: { title: "Switch Browser Tab", destructiveHint: false, idempotentHint: true },
3803
3798
  inputSchema: {
3804
3799
  handle: z15.string().optional().describe("Window handle to switch to"),
3805
3800
  index: z15.number().int().min(0).optional().describe("0-based tab index to switch to")
@@ -3830,7 +3825,8 @@ init_state();
3830
3825
  import { z as z16 } from "zod";
3831
3826
  var switchFrameToolDefinition = {
3832
3827
  name: "switch_frame",
3833
- description: "Switches into an iframe by CSS or XPath selector, or back to the top-level frame if no selector is given. Required before interacting with elements inside iframes \u2014 click, set_value, and get_elements only see the current frame context. Browser-only.",
3828
+ description: "Switches WebDriver frame context into an iframe by CSS/XPath selector, or back to top-level if selector is omitted. Changes persist \u2014 all subsequent click_element, set_value, get_elements calls operate within the switched frame until you switch back. Waits up to 5s for the iframe. Browser-only.",
3829
+ annotations: { title: "Switch Frame", destructiveHint: false, idempotentHint: true },
3834
3830
  inputSchema: {
3835
3831
  selector: z16.string().optional().describe(
3836
3832
  "CSS/XPath selector for the iframe element. Omit to switch back to the top-level frame."
@@ -3875,6 +3871,7 @@ function formatAppList(apps) {
3875
3871
  var listAppsToolDefinition = {
3876
3872
  name: "list_apps",
3877
3873
  description: "List apps uploaded to BrowserStack App Automate. Reads BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY from environment.",
3874
+ annotations: { title: "List BrowserStack Apps", readOnlyHint: true, idempotentHint: true },
3878
3875
  inputSchema: {
3879
3876
  sortBy: z17.enum(["app_name", "uploaded_at"]).optional().default("uploaded_at").describe("Sort order for results"),
3880
3877
  organizationWide: coerceBoolean.optional().default(false).describe("List apps uploaded by all users in the organization (uses recent_group_apps endpoint). Defaults to false (own uploads only)."),
@@ -3907,6 +3904,7 @@ var listAppsTool = async ({ sortBy = "uploaded_at", organizationWide = false, li
3907
3904
  var uploadAppToolDefinition = {
3908
3905
  name: "upload_app",
3909
3906
  description: "Upload a local .apk or .ipa to BrowserStack App Automate. Returns a bs:// URL for use in start_session.",
3907
+ annotations: { title: "Upload App to BrowserStack", destructiveHint: false },
3910
3908
  inputSchema: {
3911
3909
  path: z17.string().describe("Absolute path to the .apk or .ipa file"),
3912
3910
  customId: z17.string().optional().describe("Optional custom ID for the app (used to reference it later)")
@@ -3951,6 +3949,7 @@ Use this URL as the "app" parameter in start_session with provider: "browserstac
3951
3949
  var screenshotToolDefinition = {
3952
3950
  name: "get_screenshot",
3953
3951
  description: "Takes a screenshot of the current page or screen and returns a base64-encoded image, resized and compressed for model context limits.",
3952
+ annotations: { title: "Get Screenshot", readOnlyHint: true, idempotentHint: true },
3954
3953
  inputSchema: {}
3955
3954
  };
3956
3955
  var screenshotTool = async () => {
@@ -3966,6 +3965,7 @@ import { z as z18 } from "zod";
3966
3965
  var accessibilityToolDefinition = {
3967
3966
  name: "get_accessibility_tree",
3968
3967
  description: "Returns the page accessibility tree with roles, names, and selectors. Browser-only. Supports filtering by ARIA roles and pagination via limit/offset.",
3968
+ annotations: { title: "Get Accessibility Tree", readOnlyHint: true, idempotentHint: true },
3969
3969
  inputSchema: {
3970
3970
  limit: z18.number().optional().default(0).describe("Maximum number of nodes to return (0 = no limit)"),
3971
3971
  offset: z18.number().optional().default(0).describe("Number of nodes to skip for pagination"),
@@ -3984,6 +3984,7 @@ var accessibilityTool = async ({ limit = 0, offset = 0, roles }) => {
3984
3984
  var getTabsToolDefinition = {
3985
3985
  name: "get_tabs",
3986
3986
  description: "Lists all browser tabs with handle, title, URL, and which is active. Use before switch_tab to find the target handle or index. Browser-only.",
3987
+ annotations: { title: "Get Browser Tabs", readOnlyHint: true, idempotentHint: true },
3987
3988
  inputSchema: {}
3988
3989
  };
3989
3990
  var getTabsTool = async () => {
@@ -3998,6 +3999,7 @@ var getTabsTool = async () => {
3998
3999
  var getContextsToolDefinition = {
3999
4000
  name: "get_contexts",
4000
4001
  description: "Returns available automation contexts and the currently active one. Use before switch_context to discover NATIVE_APP and WEBVIEW_* targets. Mobile-only.",
4002
+ annotations: { title: "Get Automation Contexts", readOnlyHint: true, idempotentHint: true },
4001
4003
  inputSchema: {}
4002
4004
  };
4003
4005
  var getContextsTool = async () => {
@@ -4020,6 +4022,7 @@ import { z as z19 } from "zod";
4020
4022
  var appStateToolDefinition = {
4021
4023
  name: "get_app_state",
4022
4024
  description: "Returns the current state of a mobile app: not installed, not running, background, or foreground. Mobile-only.",
4025
+ annotations: { title: "Get App State", readOnlyHint: true, idempotentHint: true },
4023
4026
  inputSchema: {
4024
4027
  bundleId: z19.string().describe('App bundle ID (iOS) or package name (Android), e.g. "com.example.app"')
4025
4028
  }
@@ -4037,6 +4040,7 @@ import { z as z20 } from "zod";
4037
4040
  var getCookiesToolDefinition = {
4038
4041
  name: "get_cookies",
4039
4042
  description: "Returns all cookies for the current session, or a single cookie by name. Use to verify auth state, session tokens, or feature flags after login flows.",
4043
+ annotations: { title: "Get Cookies", readOnlyHint: true, idempotentHint: true },
4040
4044
  inputSchema: {
4041
4045
  name: z20.string().optional().describe("Cookie name to retrieve a specific cookie. If omitted, returns all cookies.")
4042
4046
  }
@@ -4070,7 +4074,8 @@ function createServer() {
4070
4074
  });
4071
4075
  const registerTool = (definition, callback) => server.registerTool(definition.name, {
4072
4076
  description: definition.description,
4073
- inputSchema: definition.inputSchema
4077
+ inputSchema: definition.inputSchema,
4078
+ ...definition.annotations && { annotations: definition.annotations }
4074
4079
  }, callback);
4075
4080
  const registerResource = (definition) => {
4076
4081
  if ("uri" in definition) {