chrome-bridge-mcp 1.30.3 → 1.30.4

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "chrome-bridge-mcp",
3
3
  "mcpName": "io.github.frsorrentino/chrome-bridge",
4
- "version": "1.30.3",
4
+ "version": "1.30.4",
5
5
  "type": "module",
6
6
  "description": "Chrome Bridge by frsorrentino: MCP server that lets Claude Code borrow a tab from your own logged-in Chrome and hand it back — logins, 2FA and CAPTCHAs stay with you (handoff). 67 tools; 2.8-4.3× cheaper and up to 9.3× fewer turns than Claude in Chrome, measured; ChromeOS included.",
7
7
  "main": "server/index.js",
@@ -100,7 +100,7 @@ export const MessageType = Object.freeze({
100
100
  });
101
101
 
102
102
  // Versione
103
- export const VERSION = '1.30.3';
103
+ export const VERSION = '1.30.4';
104
104
 
105
105
  // Configurazione
106
106
  export const DEFAULT_PORT = 8765;
package/server/tools.js CHANGED
@@ -361,7 +361,7 @@ export const TOOL_ANNOTATIONS = {
361
361
  get_frames: ro(),
362
362
  get_interactives: ro(),
363
363
  get_page_info: ro(),
364
- get_status: ro(),
364
+ get_status: rw({ idempotent: true }), // enable accende gruppi di tool per la sessione: ro() lo negava
365
365
  get_storage: ro(),
366
366
  get_tabs: ro(),
367
367
  manage_downloads: rw({ open: true }), // action=download scrive un file sul disco e va in rete
@@ -379,7 +379,7 @@ export const TOOL_ANNOTATIONS = {
379
379
  read_page: ro(),
380
380
  screenshot: ro(),
381
381
  wait_for: ro(),
382
- watch_dom: ro(),
382
+ watch_dom: rw({ destructive: true }), // installa un observer nella pagina; clear e stop cancellano le mutation raccolte
383
383
  audit: ro(),
384
384
  read_form: ro(),
385
385
  watch: rw({ idempotent: true }),
@@ -838,10 +838,11 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
838
838
  // --- get_status ---
839
839
  server.tool(
840
840
  'get_status',
841
- 'Bridge status: extension connection, server mode (primary/relay), host, port, server and extension versions, js_evaluation '
842
- + '(false under --no-js), write_root and read_root, caps_active and caps_available (a tool missing from the list lives in an '
843
- + 'inactive cap), session_tab_id (the implicit tab), owned_tabs (created by this session), uptime_sec. '
844
- + 'enable switches caps on for this session, no restart. Optional caps: '
841
+ 'Status of the bridge itself, not of a page: call it when a tool you expect is missing or browser commands fail '
842
+ + '(connected=false: the extension is not reachable). Also reports mode (primary/relay), host, port, server and '
843
+ + 'extension versions, js_evaluation (false under --no-js), write_root, read_root, session_tab_id (the implicit tab), '
844
+ + 'owned_tabs (created by this session), uptime_sec. caps_active and caps_available: a missing tool lives in an inactive cap. '
845
+ + 'enable is the only change it makes: it switches caps on for this session, no restart, and lists the tools added. Optional caps: '
845
846
  + `${Object.entries(TOOL_CAPS).map(([g, t]) => `${g} (${t.join(', ')})`).join('; ')}.`,
846
847
  {
847
848
  enable: z.array(z.enum(['all', ...Object.keys(TOOL_CAPS)])).optional()
@@ -895,7 +896,7 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
895
896
  // --- get_tabs ---
896
897
  server.tool(
897
898
  'get_tabs',
898
- 'List every open tab with id, url, title, active flag and mine (created by this session). Read-only. Find a tab_id when the implicit '
899
+ 'List every open tab with id, url, title, active flag and mine (created by this session). Find a tab_id when the implicit '
899
900
  + 'target is not the one you mean. include_windows adds position, size, state and type of each window — needed before moving or tiling. '
900
901
  + 'Session tabs gone since the last call come back once in closed_session_tabs with the reason (closed, window_closed, replaced, closed_by_bridge).',
901
902
  {
@@ -934,7 +935,9 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
934
935
  // --- navigate ---
935
936
  server.tool(
936
937
  'navigate',
937
- 'Navigate a Chrome tab to a URL. Returns a preview of interactive elements with refs usable in click/type_text/hover.',
938
+ 'Navigate a Chrome tab to a URL and wait for the load; DNS and connection errors fail the call. Without tab_id it reuses the tab '
939
+ + 'this session last navigated or created, else opens a new one rather than taking over the active tab. Returns a preview of '
940
+ + 'interactive elements with refs usable in click/type_text/hover, plus the first console errors and failed requests, if any.',
938
941
  {
939
942
  url: z.string().describe('Absolute URL, or a path resolved against the current page'),
940
943
  tab_id: tabId,
@@ -1052,7 +1055,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
1052
1055
  server.tool(
1053
1056
  'execute_js',
1054
1057
  'Run JavaScript in the page (MAIN world). Requires the extension\'s "Allow user scripts" toggle; errors explain setup if disabled. '
1055
- + 'A promise is awaited up to timeout. A cut result stays valid JSON and says truncated; save_to writes the whole result to disk.',
1058
+ + 'Use it when no dedicated tool fits: extract or extract_table read data, query_dom inspects elements, inject_css restyles, '
1059
+ + 'and none of them needs the toggle. A promise is awaited up to timeout. A cut result stays valid JSON and says truncated; save_to writes the whole result to disk.',
1056
1060
  {
1057
1061
  code: z.string().describe('JS evaluated in the page; the value of the last expression is returned'),
1058
1062
  tab_id: tabId,
@@ -1177,7 +1181,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
1177
1181
  'Page metadata: title, url, visibility, doctype, charset, meta tags, scripts, stylesheets, links and forms. visibility=hidden means '
1178
1182
  + 'the window is minimized, covered or in the background: the page does not render (screenshots fail) and its timers slow down. '
1179
1183
  + 'dev.server names a dev server (vite, webpack-dev-server, next, nuxt) and dev.overlay carries the compiler message when an error '
1180
- + 'overlay is open: read it before treating the page as valid.',
1184
+ + 'overlay is open: read it before treating the page as valid. For the text use read_page, for specific elements query_dom, '
1185
+ + 'for the bridge itself get_status.',
1181
1186
  {
1182
1187
  tab_id: tabId,
1183
1188
  frame_id: frameId,
@@ -1217,7 +1222,9 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
1217
1222
  // --- query_dom ---
1218
1223
  server.tool(
1219
1224
  'query_dom',
1220
- 'Query DOM elements by CSS selector, returning structure, attributes, bounding rect, and computed styles.',
1225
+ 'Query DOM elements by CSS selector, returning structure, attributes, bounding rect and, with properties, computed styles. '
1226
+ + 'Use it when you already have a selector (no match: count 0); to find one, get_interactives or find_text; '
1227
+ + 'for the rule that set a style, get_css_styles.',
1221
1228
  {
1222
1229
  selector: z.string().describe('CSS selector; ">>>" pierces shadow DOM. Matches all, not just the first'),
1223
1230
  properties: z.array(z.string()).optional().describe('Computed styles to include, e.g. ["color"]'),
@@ -1306,7 +1313,9 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
1306
1313
  // --- read_console ---
1307
1314
  server.tool(
1308
1315
  'read_console',
1309
- 'Read console messages captured since page load, incl. uncaught errors and unhandled rejections.',
1316
+ 'Read console messages captured since page load, incl. uncaught errors and unhandled rejections. '
1317
+ + 'clear deletes the messages it returns, hence not read-only; since=cursor reads only newer ones without deleting, '
1318
+ + 'and failed requests are in monitor_network.',
1310
1319
  {
1311
1320
  clear: z.boolean().optional().default(false).describe('Clear buffer after read'),
1312
1321
  level: z.enum(['all', 'log', 'warn', 'error', 'info', 'debug']).optional().default('all').describe('all merges every level in one chronological list'),
@@ -1351,7 +1360,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
1351
1360
  // --- monitor_network ---
1352
1361
  server.tool(
1353
1362
  'monitor_network',
1354
- 'Monitor network requests. source=page: XHR/fetch hook (installed on first call); source=browser: all requests incl. static assets; source=websocket: connections and messages. format=har exports HAR 1.2.',
1363
+ 'Monitor network requests. source=page: XHR/fetch hook (installed on first call); source=browser: all requests incl. static assets; source=websocket: connections and messages. format=har exports HAR 1.2. '
1364
+ + 'It only observes: to block, stub or rewrite requests use network_rules, to send one yourself http_request.',
1355
1365
  {
1356
1366
  clear: z.boolean().optional().default(false).describe('Clear buffer after read'),
1357
1367
  source: z.enum(['page', 'browser', 'websocket']).optional().default('page').describe('page: XHR/fetch only; browser: static assets too; websocket: connections and messages'),
@@ -1467,7 +1477,9 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
1467
1477
  // --- scroll ---
1468
1478
  server.tool(
1469
1479
  'scroll',
1470
- 'Scroll. action=to: once to element/coordinates; action=until: repeatedly until element visible, network idle, or no new content (infinite scroll).',
1480
+ 'Scroll. action=to: once to element/coordinates; action=until: repeatedly until element visible, network idle, or no new content (infinite scroll). '
1481
+ + 'It moves the tab\'s scroll position and returns it (to) or stopped_reason and steps (until); full_page_screenshot '
1482
+ + 'scrolls by itself, wait_for waits without scrolling.',
1471
1483
  {
1472
1484
  action: z.enum(['to', 'until']).optional().default('to').describe('to jumps to a position or element; until scrolls repeatedly to load more'),
1473
1485
  selector: z.string().optional().describe('Target (to) or stop element (until=element)'),
@@ -1724,7 +1736,9 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
1724
1736
  // --- watch_dom ---
1725
1737
  server.tool(
1726
1738
  'watch_dom',
1727
- 'Watch DOM mutations (MutationObserver). First call installs the watcher; later calls read accumulated mutations.',
1739
+ 'Watch DOM mutations (MutationObserver) under selector, to see what an action changed. First call installs the watcher in the '
1740
+ + 'page; later calls return the latest mutations (type, tag, attribute, nodes added/removed), clear deletes the ones returned, '
1741
+ + 'stop disconnects and empties the buffer. To wait for one element or text use wait_for.',
1728
1742
  {
1729
1743
  selector: z.string().optional().default('body').describe('Subtree to observe; ">>>" pierces shadow DOM'),
1730
1744
  attributes: z.boolean().optional().default(true).describe('Report attribute changes'),
@@ -1838,7 +1852,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
1838
1852
  + 'composited_estimate guesses whether only transform/opacity/filter move (an estimate, not the compositor\'s answer); fade_only marks '
1839
1853
  + 'opacity-only fades, what prefers-reduced-motion should leave. Without duration_ms: what runs now. With duration_ms: records every '
1840
1854
  + 'animation that starts in the window, optionally after an action (hover, click, scroll, key), since a 150 ms transition is over '
1841
- + 'before a second call. Refuses on a hidden page. Pair with emulate_media reducedMotion to check the reduced version.',
1855
+ + 'before a second call. Refuses on a hidden page. Pair with emulate_media reducedMotion to check the reduced version; '
1856
+ + 'how smoothly they run is frames.',
1842
1857
  {
1843
1858
  scope: z.string().optional().describe('CSS selector: only animations on this element or inside it'),
1844
1859
  duration_ms: z.number().optional().describe('Record for this many ms (max 30000) instead of a snapshot'),
@@ -1874,7 +1889,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
1874
1889
  + '(duration, blocking time, render and style/layout start, the scripts responsible), layout shifts with the elements that moved (CLS '
1875
1890
  + 'for the window only), and the slowest interaction (INP for the window, split into input delay, processing, presentation; needs trusted input, synthetic events have no interaction id). '
1876
1891
  + 'Optionally runs an action (click, hover, scroll, key) right after recording starts. Animations that started are counted too. '
1877
- + 'Compositor-only animations can stay smooth while the main thread drops frames. Refuses on a hidden page: rAF and observers stop there.',
1892
+ + 'Compositor-only animations can stay smooth while the main thread drops frames. Refuses on a hidden page: rAF and observers stop there. '
1893
+ + 'For load speed use perf_trace; for the settings of each animation, animations.',
1878
1894
  {
1879
1895
  duration_ms: z.number().optional().default(3000).describe('Recording window in ms (max 30000)'),
1880
1896
  action: motionAction,
@@ -1948,7 +1964,7 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
1948
1964
  + 'function responsible, long animation frames. The raw trace goes to a JSON file (path and size in the result) that DevTools > '
1949
1965
  + 'Performance can load. action record (default) = start, reload unless reload: false, optional action, wait duration_ms, stop. '
1950
1966
  + 'start/stop bracket anything else done meanwhile. Uses chrome.debugger: Chrome shows its debugging bar while tracing; in launch '
1951
- + 'mode nobody sees it. Refuses on a hidden page.',
1967
+ + 'mode nobody sees it. Refuses on a hidden page. For smoothness during an interaction use frames, for scores lighthouse.',
1952
1968
  {
1953
1969
  action: z.enum(['record', 'start', 'stop']).optional().default('record').describe('record does start → wait → stop in one call'),
1954
1970
  reload: z.boolean().optional().describe('Reload the page right after tracing starts (default true for record, false for start)'),
@@ -2302,7 +2318,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
2302
2318
  // --- find_text ---
2303
2319
  server.tool(
2304
2320
  'find_text',
2305
- 'Find text on the page: parent selector, context, visibility, position per match. Attaches nearby interactive elements (with refs for click/type_text/hover) for the first visible match.',
2321
+ 'Find text on the page: parent selector, context, visibility, position per match. Attaches nearby interactive elements (with refs for click/type_text/hover) for the first visible match. '
2322
+ + 'Use it when you know the words on screen but not the selector (query_dom needs one); no match returns count 0.',
2306
2323
  {
2307
2324
  text: z.string().describe('Literal text to find, not a regex'),
2308
2325
  case_sensitive: z.boolean().optional().default(false).describe('Match case exactly'),
@@ -2341,7 +2358,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
2341
2358
  // --- network_rules ---
2342
2359
  server.tool(
2343
2360
  'network_rules',
2344
- 'Network interception, browser-wide, survives reloads until cleared: block, redirect, set/remove headers, stub a synthetic body, or record real API responses and replay them with forced errors/latency (local helper; from HTTPS pages the stub host must be trusted).',
2361
+ 'Network interception, browser-wide, survives reloads until cleared: block, redirect, set/remove headers, stub a synthetic body, or record real API responses and replay them with forced errors/latency (local helper; from HTTPS pages the stub host must be trusted). '
2362
+ + 'It changes traffic, while monitor_network only observes it and http_request sends one request; each rule comes back as added, list shows them, clear removes them all.',
2345
2363
  {
2346
2364
  action: z.enum(['block', 'redirect', 'modify_header', 'stub', 'record', 'replay', 'list', 'clear']).describe('record saves the page\'s API responses (url_filter) as a fixture, replay serves them with overrides; list/clear'),
2347
2365
  name: z.string().optional().describe('Fixture name for record/replay'),
@@ -2607,7 +2625,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
2607
2625
  'watch',
2608
2626
  'Keep watching a page in the background — "tell me when the pipeline is green / the Approve button appears / this number changes" — '
2609
2627
  + 'checked every interval_s by the extension. Events are collected, not pushed: action=poll, or a script blocking on '
2610
- + '"chrome-bridge watch --wait <name>". Survives extension idling, not a browser restart.',
2628
+ + '"chrome-bridge watch --wait <name>". Survives extension idling, not a browser restart. '
2629
+ + 'poll returns one line per event (time, name, kind, url, value, previous); a change due within seconds is wait_for.',
2611
2630
  {
2612
2631
  action: z.enum(['add', 'poll', 'list', 'remove']).optional().default('add').describe('poll = events since a timestamp'),
2613
2632
  name: z.string().optional().describe('Watch id'),
@@ -2639,7 +2658,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
2639
2658
  server.tool(
2640
2659
  'read_form',
2641
2660
  'Read a form as the user filled it — label, type, value, checked/selected, required-but-empty, browser validity — to check it against the '
2642
- + 'project\'s documents before an irreversible Submit. Password and card values come back [redacted]. Read-only, on request.',
2661
+ + 'project\'s documents before an irreversible Submit. Password and card values come back [redacted]. Read-only, on request; '
2662
+ + 'to change values use fill_form.',
2643
2663
  {
2644
2664
  selector: z.string().optional().describe('The form or container; omitted = every visible control on the page'),
2645
2665
  tab_id: tabId,
@@ -2657,7 +2677,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
2657
2677
  'audit',
2658
2678
  'One-call page audit, pick the kinds: accessibility, keyboard (tab order and focus issues), SEO, security headers, broken links, '
2659
2679
  + 'Core Web Vitals, unused CSS, resources (which plugin/theme/module/host slows the page), cache (is the CDN serving the new version). '
2660
- + 'A summary line per kind; with save_to the full Markdown report with every finding. Read-only.',
2680
+ + 'A summary line per kind; with save_to the full Markdown report with every finding. Read-only. '
2681
+ + 'For one deep look at load speed use perf_trace, for Lighthouse scores lighthouse.',
2661
2682
  {
2662
2683
  kinds: z.array(z.enum(AUDIT_KINDS)).optional().default(DEFAULT_KINDS).describe('Default skips keyboard, css (slow, approximate), resources and cache'),
2663
2684
  save_to: saveToField('the full Markdown report'),
@@ -2684,8 +2705,9 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
2684
2705
  // --- find_setting ---
2685
2706
  server.tool(
2686
2707
  'find_setting',
2687
- 'Where is a setting in an unknown admin panel: follows the panel\'s menu links (same origin) until a page contains the keyword and '
2688
- + 'reports the menu path. Navigates the tab, leaves it on the page found, stops at max_pages.',
2708
+ 'Where is a setting in an unknown admin panel: follows the panel\'s menu links (same origin) until a page contains the keyword. '
2709
+ + 'Returns the URL, the menu path (Settings › Media) and the matching text; when nothing matches, how many pages it tried. '
2710
+ + 'Navigates the tab and leaves it on the last page visited, at most max_pages.',
2689
2711
  {
2690
2712
  keyword: z.string().describe('What you are looking for, e.g. "webp", "cron", "maintenance mode"'),
2691
2713
  max_pages: z.number().optional().default(25).describe('Pages visited at most'),
@@ -2736,10 +2758,11 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
2736
2758
  // --- extract_table ---
2737
2759
  server.tool(
2738
2760
  'extract_table',
2739
- 'Read a real <table> as JSON: thead cells become keys, each row an object. Read-only. '
2761
+ 'Read a real <table> as JSON: thead cells become keys, each row an object. '
2740
2762
  + 'Only for tabular markup — `where` filters rows server-side, so one matching row out of 1500 '
2741
2763
  + 'costs a few hundred bytes instead of the whole document. `row_count` is the table total, '
2742
- + '`match_count` how many rows passed the filter.',
2764
+ + '`match_count` how many rows passed the filter. No table at index is an error saying how many the selector found; '
2765
+ + 'for cards and lists use extract.',
2743
2766
  {
2744
2767
  selector: z.string().optional().default('table').describe('The table to read; ">>>" pierces shadow DOM'),
2745
2768
  index: z.number().optional().default(0).describe('Which table to take when the selector matches several, 0-based'),
@@ -2760,7 +2783,9 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
2760
2783
  // --- drag_and_drop ---
2761
2784
  server.tool(
2762
2785
  'drag_and_drop',
2763
- 'Drag an element onto another. html5 = DragEvent+DataTransfer; pointer = pointer/mouse events (sortable libraries).',
2786
+ 'Drag an element onto another. html5 = DragEvent+DataTransfer; pointer = pointer/mouse events (sortable libraries). '
2787
+ + 'It scrolls the source into view and returns once the events are sent, without knowing whether the app accepted the drop '
2788
+ + '(check with assert or query_dom); a missing element is an error.',
2764
2789
  {
2765
2790
  source_selector: z.string().describe('Element to drag; ">>>" pierces shadow DOM'),
2766
2791
  target_selector: z.string().describe('Drop target; ">>>" pierces shadow DOM'),
@@ -2777,8 +2802,9 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
2777
2802
  // --- clipboard ---
2778
2803
  server.tool(
2779
2804
  'clipboard',
2780
- 'Read or write the system clipboard (text). Activates the tab first. '
2781
- + 'write overwrites whatever the user had copied, which is not recoverable.',
2805
+ 'Read or write the system clipboard (text). Activates the tab and focuses its window first. '
2806
+ + 'write overwrites whatever the user had copied, which is not recoverable. '
2807
+ + 'To fill a field use type_text instead of write and paste; read checks what a Copy button copied, and an empty clipboard returns text "".',
2782
2808
  {
2783
2809
  action: z.enum(['read', 'write']).describe('write takes text; read returns the current clipboard contents'),
2784
2810
  text: z.string().optional().describe('For write'),
@@ -2793,7 +2819,9 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
2793
2819
  // --- set_geolocation ---
2794
2820
  server.tool(
2795
2821
  'set_geolocation',
2796
- 'Override navigator.geolocation with fixed coordinates (page-level patch). reset restores native.',
2822
+ 'Override navigator.geolocation with fixed coordinates (page-level patch). reset restores native. '
2823
+ + 'For location-based pages (store locators, delivery zones): it answers without a permission prompt, and a reload or '
2824
+ + 'navigation drops it, so call it after navigate.',
2797
2825
  {
2798
2826
  latitude: z.number().optional().describe('Decimal degrees, -90 to 90'),
2799
2827
  longitude: z.number().optional().describe('Decimal degrees, -180 to 180'),
@@ -2888,7 +2916,9 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
2888
2916
  // --- save_page ---
2889
2917
  server.tool(
2890
2918
  'save_page',
2891
- 'Save the full page (DOM, styles, images) as an MHTML archive file on the server filesystem.',
2919
+ 'Save the full page (DOM, styles, images) as an MHTML archive file on the server filesystem. '
2920
+ + 'An offline copy Chrome reopens as it looked, for evidence or a later comparison (for an image use screenshot, for the text '
2921
+ + 'read_page); it overwrites output_path and returns path and size.',
2892
2922
  {
2893
2923
  output_path: z.string().describe('Absolute file path to write (e.g. /tmp/page.mhtml)'),
2894
2924
  tab_id: tabId,
@@ -2991,7 +3021,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
2991
3021
  'tile_windows',
2992
3022
  'Tile Chrome windows over one monitor in equal parts with no gap. Only Chrome windows. The monitor is the one of a '
2993
3023
  + 'window already on it, whose work area is read from a page there: at least one target needs a scriptable tab '
2994
- + '(not chrome://). Maximized windows are restored first, since maximized ignores bounds.',
3024
+ + '(not chrome://). Maximized windows are restored first, since maximized ignores bounds. '
3025
+ + 'To restore a saved arrangement use window_layout; to move one tab into another window, move_tab.',
2995
3026
  {
2996
3027
  window_ids: z.array(z.number()).optional().describe('Windows to tile; omitted = every normal window on the reference monitor'),
2997
3028
  reference_window_id: z.number().optional().describe('Window whose monitor is used; omitted = the focused one'),
@@ -3114,7 +3145,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
3114
3145
  // --- get_interactives ---
3115
3146
  server.tool(
3116
3147
  'get_interactives',
3117
- 'List actionable elements (buttons, links, inputs, [role], [onclick]) with ready-to-use CSS selector, label, position, flags. Prefer this over dumping HTML to discover selectors.',
3148
+ 'List actionable elements (buttons, links, inputs, [role], [onclick]) with ready-to-use CSS selector, label, position, flags. Prefer this over dumping HTML to discover selectors. '
3149
+ + 'Document order, cut at limit (count equal to limit: there may be more), each with a ref (n1, n2…) for click, type_text and hover.',
3118
3150
  {
3119
3151
  scope: z.string().optional().describe('Limit search (CSS selector)'),
3120
3152
  limit: z.number().optional().default(100).describe('Max elements returned; raise it on a dense page'),
@@ -3163,7 +3195,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
3163
3195
  'extract',
3164
3196
  'Read repeated non-tabular structures — product cards, list items, search results — as one record per item: '
3165
3197
  + 'item_selector matches the repeating block, fields map output names to selectors relative to it. '
3166
- + 'Read-only, deterministic, parsed server-side from the main-document HTML.',
3198
+ + 'Read-only, deterministic, parsed server-side from the main-document HTML. Default output: total and shown, '
3199
+ + 'then one tab-separated row per item under the field names; a field with no match is empty (null in json).',
3167
3200
  {
3168
3201
  item_selector: z.string().describe('CSS selector matching each item'),
3169
3202
  fields: z.record(z.string(), z.object({
@@ -3207,7 +3240,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
3207
3240
  // --- assert ---
3208
3241
  server.tool(
3209
3242
  'assert',
3210
- 'Assert a page condition, polling until timeout: element exists/visible (optionally with count or containing text), text on page, tab url/title. Patterns: substring, or "/…/" for regex. Recorded flows replay it as a test.',
3243
+ 'Assert a page condition, polling until timeout: element exists/visible (optionally with count or containing text), text on page, tab url/title. Patterns: substring, or "/…/" for regex. Recorded flows replay it as a test. '
3244
+ + 'Unlike wait_for, which returns found: false, a failure is an error naming what did not match; success returns passed and waited_ms.',
3211
3245
  {
3212
3246
  selector: z.string().optional().describe('Element the assertion is about; ">>>" pierces shadow DOM'),
3213
3247
  state: z.enum(['attached', 'visible']).optional().default('attached').describe('attached means present in the DOM, visible also requires a rendered box'),
@@ -3237,7 +3271,8 @@ export function registerTools(server, wsManager, caps = 'all', options = {}) {
3237
3271
  // --- session_record ---
3238
3272
  server.tool(
3239
3273
  'session_record',
3240
- 'Record the commands of this session as a replayable jsonl (chrome-bridge replay --file <path>); observe what the user does in a tab ("watch how I do it") into the same format plus a readable procedure, never recording sensitive values; export a recording as a Playwright test for CI. Replays target the tab they navigate.',
3274
+ 'Record the commands of this session as a replayable jsonl (chrome-bridge replay --file <path>); observe what the user does in a tab ("watch how I do it") into the same format plus a readable procedure, never recording sensitive values; export a recording as a Playwright test for CI. Replays target the tab they navigate. '
3275
+ + 'Use it for a flow to repeat or a procedure to hand over; for one check now, assert.',
3241
3276
  {
3242
3277
  action: z.enum(['start', 'stop', 'status', 'list', 'export', 'observe']).describe('start records this session; observe records what the USER does; stop writes the file; export makes a Playwright test'),
3243
3278
  name: z.string().optional().describe('Required for start, observe and export (recording name, or a .jsonl path)'),