ferrum-mcp 1.0.0 → 1.1.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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +37 -0
  3. data/CHANGELOG.md +72 -1
  4. data/README.md +30 -9
  5. data/docs/API_REFERENCE.md +300 -21
  6. data/docs/CONFIGURATION.md +34 -1
  7. data/docs/DEPLOYMENT.md +1 -0
  8. data/docs/DOCKER_BOTBROWSER.md +4 -0
  9. data/lib/ferrum_mcp/browser_manager.rb +56 -32
  10. data/lib/ferrum_mcp/cli/command_handler.rb +4 -3
  11. data/lib/ferrum_mcp/cli/server_runner.rb +19 -6
  12. data/lib/ferrum_mcp/configuration.rb +77 -18
  13. data/lib/ferrum_mcp/image_resizer.rb +43 -0
  14. data/lib/ferrum_mcp/server.rb +58 -91
  15. data/lib/ferrum_mcp/session.rb +67 -9
  16. data/lib/ferrum_mcp/session_manager.rb +15 -15
  17. data/lib/ferrum_mcp/tools/accept_cookies_tool.rb +11 -31
  18. data/lib/ferrum_mcp/tools/base_tool.rb +79 -38
  19. data/lib/ferrum_mcp/tools/clear_cookies_tool.rb +14 -43
  20. data/lib/ferrum_mcp/tools/click_tool.rb +58 -181
  21. data/lib/ferrum_mcp/tools/close_session_tool.rb +8 -30
  22. data/lib/ferrum_mcp/tools/close_tab_tool.rb +44 -0
  23. data/lib/ferrum_mcp/tools/create_session_tool.rb +38 -110
  24. data/lib/ferrum_mcp/tools/definition.rb +106 -0
  25. data/lib/ferrum_mcp/tools/drag_and_drop_tool.rb +43 -145
  26. data/lib/ferrum_mcp/tools/evaluate_js_tool.rb +6 -28
  27. data/lib/ferrum_mcp/tools/execute_script_tool.rb +6 -30
  28. data/lib/ferrum_mcp/tools/fill_form_tool.rb +37 -52
  29. data/lib/ferrum_mcp/tools/find_by_text_tool.rb +39 -114
  30. data/lib/ferrum_mcp/tools/get_attribute_tool.rb +10 -39
  31. data/lib/ferrum_mcp/tools/get_cookies_tool.rb +23 -52
  32. data/lib/ferrum_mcp/tools/get_html_tool.rb +22 -35
  33. data/lib/ferrum_mcp/tools/get_session_info_tool.rb +5 -21
  34. data/lib/ferrum_mcp/tools/get_text_tool.rb +22 -46
  35. data/lib/ferrum_mcp/tools/get_title_tool.rb +5 -28
  36. data/lib/ferrum_mcp/tools/get_url_tool.rb +5 -25
  37. data/lib/ferrum_mcp/tools/go_back_tool.rb +7 -30
  38. data/lib/ferrum_mcp/tools/go_forward_tool.rb +7 -30
  39. data/lib/ferrum_mcp/tools/hover_tool.rb +10 -51
  40. data/lib/ferrum_mcp/tools/list_sessions_tool.rb +5 -19
  41. data/lib/ferrum_mcp/tools/list_tabs_tool.rb +20 -0
  42. data/lib/ferrum_mcp/tools/navigate_tool.rb +17 -37
  43. data/lib/ferrum_mcp/tools/new_tab_tool.rb +36 -0
  44. data/lib/ferrum_mcp/tools/press_key_tool.rb +18 -63
  45. data/lib/ferrum_mcp/tools/query_shadow_dom_tool.rb +50 -205
  46. data/lib/ferrum_mcp/tools/refresh_tool.rb +7 -30
  47. data/lib/ferrum_mcp/tools/screenshot_tool.rb +29 -86
  48. data/lib/ferrum_mcp/tools/scroll_tool.rb +64 -0
  49. data/lib/ferrum_mcp/tools/select_option_tool.rb +56 -0
  50. data/lib/ferrum_mcp/tools/session_tool.rb +10 -9
  51. data/lib/ferrum_mcp/tools/set_cookie_tool.rb +15 -56
  52. data/lib/ferrum_mcp/tools/set_viewport_tool.rb +32 -0
  53. data/lib/ferrum_mcp/tools/snapshot_tool.rb +175 -0
  54. data/lib/ferrum_mcp/tools/solve_captcha_tool.rb +17 -35
  55. data/lib/ferrum_mcp/tools/switch_tab_tool.rb +29 -0
  56. data/lib/ferrum_mcp/tools/tab_tool.rb +40 -0
  57. data/lib/ferrum_mcp/tools/upload_file_tool.rb +50 -0
  58. data/lib/ferrum_mcp/tools/wait_for_network_idle_tool.rb +32 -0
  59. data/lib/ferrum_mcp/tools/wait_for_selector_tool.rb +59 -0
  60. data/lib/ferrum_mcp/tools/wait_for_text_tool.rb +53 -0
  61. data/lib/ferrum_mcp/transport/api_key_authenticator.rb +112 -0
  62. data/lib/ferrum_mcp/transport/client_address.rb +23 -0
  63. data/lib/ferrum_mcp/transport/http_server.rb +11 -1
  64. data/lib/ferrum_mcp/transport/rate_limiter.rb +2 -6
  65. data/lib/ferrum_mcp/transport/stdio_server.rb +8 -30
  66. data/lib/ferrum_mcp/url_policy.rb +88 -0
  67. data/lib/ferrum_mcp/version.rb +1 -1
  68. metadata +21 -3
@@ -22,9 +22,13 @@ Comprehensive documentation for all FerrumMCP browser automation tools.
22
22
  - [press_key](#press_key)
23
23
  - [hover](#hover)
24
24
  - [drag_and_drop](#drag_and_drop)
25
+ - [scroll](#scroll)
26
+ - [select_option](#select_option)
27
+ - [upload_file](#upload_file)
25
28
  - [accept_cookies](#accept_cookies)
26
29
  - [solve_captcha](#solve_captcha)
27
30
  - [Extraction](#extraction)
31
+ - [snapshot](#snapshot)
28
32
  - [get_text](#get_text)
29
33
  - [get_html](#get_html)
30
34
  - [screenshot](#screenshot)
@@ -39,16 +43,22 @@ Comprehensive documentation for all FerrumMCP browser automation tools.
39
43
  - [clear_cookies](#clear_cookies)
40
44
  - [get_attribute](#get_attribute)
41
45
  - [query_shadow_dom](#query_shadow_dom)
42
- - [Waiting (Currently Disabled)](#waiting-currently-disabled)
43
- - [wait_for_element](#wait_for_element)
44
- - [wait_for_navigation](#wait_for_navigation)
45
- - [wait](#wait)
46
+ - [Waiting](#waiting)
47
+ - [wait_for_selector](#wait_for_selector)
48
+ - [wait_for_text](#wait_for_text)
49
+ - [wait_for_network_idle](#wait_for_network_idle)
50
+ - [Tabs & Viewport](#tabs--viewport)
51
+ - [list_tabs](#list_tabs)
52
+ - [new_tab](#new_tab)
53
+ - [switch_tab](#switch_tab)
54
+ - [close_tab](#close_tab)
55
+ - [set_viewport](#set_viewport)
46
56
 
47
57
  ---
48
58
 
49
59
  ## Overview
50
60
 
51
- FerrumMCP provides 27+ browser automation tools through the Model Context Protocol (MCP). All tools return responses in a standardized JSON format with a `success` boolean and either `data` or `error` fields.
61
+ FerrumMCP provides 40 browser automation tools through the Model Context Protocol (MCP). All tools return responses in a standardized JSON format with a `success` boolean and either `data` or `error` fields.
52
62
 
53
63
  ## Important Notes
54
64
 
@@ -57,7 +67,8 @@ FerrumMCP provides 27+ browser automation tools through the Model Context Protoc
57
67
  3. **Session Lifecycle**: Sessions auto-close after 30 minutes of inactivity or can be manually closed with `close_session`
58
68
  4. **Multiple Sessions**: You can run multiple concurrent browser sessions with different configurations
59
69
  5. **Screenshot Format**: The `screenshot` tool returns base64-encoded image data
60
- 6. **Selector Support**: Most tools support both CSS selectors and XPath (use `xpath:` prefix for XPath)
70
+ 6. **Selector Support**: Every element-based tool accepts a CSS selector, an XPath (`xpath:` prefix or starting with `//`) or a snapshot ref (`ref:e12`, see [snapshot](#snapshot))
71
+ 7. **Tabs**: tools act on the session's current tab; use [new_tab](#new_tab) / [switch_tab](#switch_tab) to change it
61
72
 
62
73
  ---
63
74
 
@@ -76,9 +87,9 @@ Create a new browser session with custom options. Returns a `session_id` to use
76
87
  | bot_profile_id | string | No | BotBrowser profile ID from `ferrum://bot-profiles` resource |
77
88
  | browser_path | string | No | Path to browser executable (legacy) |
78
89
  | botbrowser_profile | string | No | Path to BotBrowser profile (legacy) |
79
- | headless | boolean | No | Run in headless mode (default: false) |
90
+ | headless | boolean | No | Run in headless mode (default: `BROWSER_HEADLESS`) |
80
91
  | timeout | number | No | Browser timeout in seconds (default: 60) |
81
- | browser_options | object | No | Additional browser options (e.g., `{"--window-size": "1920,1080"}`) |
92
+ | browser_options | object | No | Extra Chrome flags without the leading dashes (e.g., `{"window-size": "1920,1080", "lang": "fr-FR"}`) |
82
93
  | metadata | object | No | Custom metadata for this session |
83
94
 
84
95
  **Example Request:**
@@ -90,7 +101,7 @@ Create a new browser session with custom options. Returns a `session_id` to use
90
101
  "headless": true,
91
102
  "timeout": 60,
92
103
  "browser_options": {
93
- "--window-size": "1920,1080"
104
+ "window-size": "1920,1080"
94
105
  },
95
106
  "metadata": {
96
107
  "user": "john",
@@ -110,7 +121,7 @@ Create a new browser session with custom options. Returns a `session_id` to use
110
121
  "headless": true,
111
122
  "timeout": 60,
112
123
  "browser_options": {
113
- "--window-size": "1920,1080"
124
+ "window-size": "1920,1080"
114
125
  }
115
126
  }
116
127
  }
@@ -255,6 +266,8 @@ Navigate to a specific URL in the browser.
255
266
  | Name | Type | Required | Description |
256
267
  |------|------|----------|-------------|
257
268
  | url | string | Yes | URL to navigate to (must include http:// or https://) |
269
+ | wait_for_idle | boolean | No | Wait for the network to settle after navigation (default: true) |
270
+ | timeout | number | No | Seconds to wait for network idle (default: 30) |
258
271
  | session_id | string | Yes | Session ID to use |
259
272
 
260
273
  **Example Request:**
@@ -280,7 +293,8 @@ Navigate to a specific URL in the browser.
280
293
 
281
294
  **Notes:**
282
295
  - URL must start with `http://` or `https://`
283
- - Automatically waits for network to be idle after navigation
296
+ - Automatically waits for network to be idle after navigation (disable with `wait_for_idle: false` for pages that stream forever)
297
+ - Refused when the host is denied by `ALLOWED_HOSTS` / `BLOCKED_HOSTS` (see the Configuration Guide)
284
298
  - Throws timeout error if navigation takes longer than browser timeout
285
299
 
286
300
  ---
@@ -403,7 +417,7 @@ Click on an element using a CSS selector or XPath.
403
417
 
404
418
  | Name | Type | Required | Description |
405
419
  |------|------|----------|-------------|
406
- | selector | string | Yes | CSS selector or XPath (use `xpath:` prefix for XPath) |
420
+ | selector | string | Yes | CSS selector, XPath (`xpath:` prefix) or snapshot ref (`ref:e12`) |
407
421
  | wait | number | No | Seconds to wait for element (default: 5) |
408
422
  | force | boolean | No | Force click even if hidden/not visible (default: false) |
409
423
  | session_id | string | Yes | Session ID to use |
@@ -453,8 +467,9 @@ Fill one or more form fields with values.
453
467
 
454
468
  | Name | Type | Required | Description |
455
469
  |------|------|----------|-------------|
456
- | selector | string | Yes | CSS selector of the field |
457
- | value | string | Yes | Value to fill |
470
+ | selector | string | Yes | CSS selector, XPath or snapshot ref of the field |
471
+ | value | string | Yes | Value to type |
472
+ | clear | boolean | No | Clear the field before typing (default: false) |
458
473
 
459
474
  **Example Request:**
460
475
 
@@ -631,6 +646,92 @@ Drag an element and drop it onto another element or coordinates.
631
646
 
632
647
  ---
633
648
 
649
+ ### scroll
650
+
651
+ Scroll the page or a scrollable element, or bring an element into view.
652
+
653
+ **Parameters:**
654
+
655
+ | Name | Type | Required | Description |
656
+ |------|------|----------|-------------|
657
+ | selector | string | No | Element to scroll into view, or the scrollable container when `direction` is given |
658
+ | direction | string | No | `up`, `down`, `left`, `right`, `top`, `bottom` |
659
+ | amount | number | No | Pixels for up/down/left/right (default: 500) |
660
+ | x | number | No | Absolute horizontal scroll position |
661
+ | y | number | No | Absolute vertical scroll position |
662
+ | session_id | string | Yes | Session ID to use |
663
+
664
+ **Example Request:**
665
+
666
+ ```json
667
+ { "name": "scroll", "arguments": { "direction": "down", "amount": 800, "session_id": "uuid-1234" } }
668
+ ```
669
+
670
+ **Example Response:**
671
+
672
+ ```json
673
+ { "x": 0, "y": 800, "target": "window" }
674
+ ```
675
+
676
+ **Notes:**
677
+ - `selector` alone scrolls that element into the middle of the viewport
678
+ - `selector` + `direction` scrolls inside that container (infinite lists, modals)
679
+
680
+ ---
681
+
682
+ ### select_option
683
+
684
+ Select option(s) in a `<select>` element and fire `input`/`change` events.
685
+
686
+ **Parameters:**
687
+
688
+ | Name | Type | Required | Description |
689
+ |------|------|----------|-------------|
690
+ | selector | string | Yes | Selector of the `<select>` |
691
+ | value | string | No | Option value |
692
+ | label | string | No | Option visible text |
693
+ | index | integer | No | Zero-based option index |
694
+ | values | array | No | Several option values (multiple selects) |
695
+ | session_id | string | Yes | Session ID to use |
696
+
697
+ **Example Request:**
698
+
699
+ ```json
700
+ { "name": "select_option", "arguments": { "selector": "#plan", "label": "Pro plan", "session_id": "uuid-1234" } }
701
+ ```
702
+
703
+ **Example Response:**
704
+
705
+ ```json
706
+ { "selector": "#plan", "selected": [{ "value": "pro", "label": "Pro plan" }] }
707
+ ```
708
+
709
+ ---
710
+
711
+ ### upload_file
712
+
713
+ Attach files from the **server's** filesystem to an `<input type="file">`.
714
+
715
+ **Parameters:**
716
+
717
+ | Name | Type | Required | Description |
718
+ |------|------|----------|-------------|
719
+ | selector | string | Yes | Selector of the file input |
720
+ | paths | array | Yes | Absolute paths of the files |
721
+ | session_id | string | Yes | Session ID to use |
722
+
723
+ **Example Request:**
724
+
725
+ ```json
726
+ { "name": "upload_file", "arguments": { "selector": "#avatar", "paths": ["/tmp/avatar.png"], "session_id": "uuid-1234" } }
727
+ ```
728
+
729
+ **Notes:**
730
+ - Files must live under one of `UPLOAD_ALLOWED_DIRS` (default: current directory and the temp dir); symlinks are resolved
731
+ - Missing files are rejected before touching the browser
732
+
733
+ ---
734
+
634
735
  ### accept_cookies
635
736
 
636
737
  Automatically detect and accept cookie consent banners.
@@ -736,6 +837,50 @@ Automatically detect and solve audio CAPTCHA challenges using Whisper speech rec
736
837
 
737
838
  ## Extraction
738
839
 
840
+ ### snapshot
841
+
842
+ Compact, agent-friendly view of the page: interactive elements (links, buttons, inputs, selects, custom roles) and headings, each with a **stable ref** that every other tool accepts as selector (`ref:e12`). Far cheaper than `get_html` for deciding what to do next.
843
+
844
+ **Parameters:**
845
+
846
+ | Name | Type | Required | Description |
847
+ |------|------|----------|-------------|
848
+ | mode | string | No | `interactive` (default) or `full` (adds images, labels, paragraphs, alerts) |
849
+ | selector | string | No | Restrict the snapshot to a subtree |
850
+ | include_hidden | boolean | No | Include hidden elements (default: false) |
851
+ | max_elements | integer | No | Maximum number of elements (default: 200) |
852
+ | format | string | No | `text` (default) or `json` |
853
+ | session_id | string | Yes | Session ID to use |
854
+
855
+ **Example Request:**
856
+
857
+ ```json
858
+ { "name": "snapshot", "arguments": { "session_id": "uuid-1234" } }
859
+ ```
860
+
861
+ **Example Response (text):**
862
+
863
+ ```json
864
+ {
865
+ "url": "https://example.com/signup",
866
+ "title": "Sign up",
867
+ "count": 6,
868
+ "total": 6,
869
+ "truncated": false,
870
+ "snapshot": "[e1] heading(1) \"Create your account\"\n[e2] textbox \"Email address\" type=email placeholder=\"you@example.com\"\n[e3] checkbox \"newsletter\" checked\n[e4] combobox \"plan\"\n[e5] button \"Create account\"\n[e6] link \"Documentation\" href=/docs"
871
+ }
872
+ ```
873
+
874
+ **Example Response (json):** `elements` is an array of `{ ref, role, tag, name, selector, href?, type?, placeholder?, value?, checked?, disabled?, level? }`.
875
+
876
+ **Notes:**
877
+ - Refs are stored on the elements (`data-fmcp-ref`) and stay stable across snapshots until the page navigates
878
+ - Typical loop: `snapshot` → `click ref:e5` → `wait_for_text` → `snapshot`
879
+ - Names follow accessibility rules: `aria-label`, `<label for>`, placeholder, name, then visible text
880
+ - Radio buttons also show their `value`, so unlabeled buttons of one group stay distinguishable
881
+
882
+ ---
883
+
739
884
  ### get_text
740
885
 
741
886
  Extract text content from one or more elements.
@@ -744,8 +889,10 @@ Extract text content from one or more elements.
744
889
 
745
890
  | Name | Type | Required | Description |
746
891
  |------|------|----------|-------------|
747
- | selector | string | Yes | CSS selector or XPath (use `xpath:` prefix) |
892
+ | selector | string | Yes | CSS selector, XPath (`xpath:` prefix) or snapshot ref |
748
893
  | multiple | boolean | No | Extract from all matching elements (default: false) |
894
+ | wait | number | No | Seconds to wait for the element (default: 5) |
895
+ | raw | boolean | No | Return the text exactly as in the DOM (default: false, whitespace is collapsed and trimmed) |
749
896
  | session_id | string | Yes | Session ID to use |
750
897
 
751
898
  **Example Request (Single Element):**
@@ -809,7 +956,8 @@ Get HTML content of the page or a specific element.
809
956
 
810
957
  | Name | Type | Required | Description |
811
958
  |------|------|----------|-------------|
812
- | selector | string | No | CSS selector to get HTML of specific element |
959
+ | selector | string | No | Selector of the element to get the outerHTML of |
960
+ | max_length | integer | No | Truncate the HTML to this many characters (response has `truncated: true`) |
813
961
  | session_id | string | Yes | Session ID to use |
814
962
 
815
963
  **Example Request (Full Page):**
@@ -1040,6 +1188,134 @@ Find elements by their text content using XPath.
1040
1188
 
1041
1189
  ---
1042
1190
 
1191
+ ## Waiting
1192
+
1193
+ ### wait_for_selector
1194
+
1195
+ Wait until an element reaches a state.
1196
+
1197
+ **Parameters:**
1198
+
1199
+ | Name | Type | Required | Description |
1200
+ |------|------|----------|-------------|
1201
+ | selector | string | Yes | CSS selector, XPath or snapshot ref |
1202
+ | state | string | No | `visible` (default), `hidden`, `attached`, `detached` |
1203
+ | timeout | number | No | Maximum seconds to wait (default: 10) |
1204
+ | session_id | string | Yes | Session ID to use |
1205
+
1206
+ **Example Response:**
1207
+
1208
+ ```json
1209
+ { "found": true, "state": "visible", "selector": "#results", "elapsed_ms": 640 }
1210
+ ```
1211
+
1212
+ On timeout the tool fails with `Timed out after 10s waiting for #results to be visible`.
1213
+
1214
+ ---
1215
+
1216
+ ### wait_for_text
1217
+
1218
+ Wait until text is visible on the page (optionally inside an element).
1219
+
1220
+ **Parameters:**
1221
+
1222
+ | Name | Type | Required | Description |
1223
+ |------|------|----------|-------------|
1224
+ | text | string | Yes | Text to wait for |
1225
+ | selector | string | No | CSS selector of the element to search in (default: body) |
1226
+ | exact | boolean | No | Match the whole text exactly (default: substring) |
1227
+ | timeout | number | No | Maximum seconds to wait (default: 10) |
1228
+ | session_id | string | Yes | Session ID to use |
1229
+
1230
+ ---
1231
+
1232
+ ### wait_for_network_idle
1233
+
1234
+ Wait until the page has no pending requests (after a click that triggers XHR/fetch, for instance).
1235
+
1236
+ **Parameters:**
1237
+
1238
+ | Name | Type | Required | Description |
1239
+ |------|------|----------|-------------|
1240
+ | timeout | number | No | Maximum seconds to wait (default: 30) |
1241
+ | connections | integer | No | Pending connections tolerated as idle (default: 0) |
1242
+ | session_id | string | Yes | Session ID to use |
1243
+
1244
+ **Example Response:**
1245
+
1246
+ ```json
1247
+ { "idle": true, "elapsed_ms": 120, "url": "https://example.com/results" }
1248
+ ```
1249
+
1250
+ ---
1251
+
1252
+ ## Tabs & Viewport
1253
+
1254
+ Tools operate on the session's **current tab**. Links with `target="_blank"` open new tabs that you can activate with `switch_tab`.
1255
+
1256
+ ### list_tabs
1257
+
1258
+ **Example Response:**
1259
+
1260
+ ```json
1261
+ {
1262
+ "count": 2,
1263
+ "tabs": [
1264
+ { "index": 0, "tab_id": "A1B2...", "url": "https://example.com/", "title": "Example", "current": false },
1265
+ { "index": 1, "tab_id": "C3D4...", "url": "https://example.com/help", "title": "Help", "current": true }
1266
+ ]
1267
+ }
1268
+ ```
1269
+
1270
+ ---
1271
+
1272
+ ### new_tab
1273
+
1274
+ Open a new tab (optionally at a URL) and make it current.
1275
+
1276
+ | Name | Type | Required | Description |
1277
+ |------|------|----------|-------------|
1278
+ | url | string | No | URL to open (subject to the navigation policy) |
1279
+ | session_id | string | Yes | Session ID to use |
1280
+
1281
+ ---
1282
+
1283
+ ### switch_tab
1284
+
1285
+ Make another tab current, by `tab_id` (from `list_tabs`) or zero-based `index`.
1286
+
1287
+ | Name | Type | Required | Description |
1288
+ |------|------|----------|-------------|
1289
+ | tab_id | string | No | Tab to activate |
1290
+ | index | integer | No | Zero-based index (alternative to tab_id) |
1291
+ | session_id | string | Yes | Session ID to use |
1292
+
1293
+ ---
1294
+
1295
+ ### close_tab
1296
+
1297
+ Close a tab (the current one by default). The last remaining tab cannot be closed; closing the current tab activates the first remaining one.
1298
+
1299
+ | Name | Type | Required | Description |
1300
+ |------|------|----------|-------------|
1301
+ | tab_id | string | No | Tab to close (default: current) |
1302
+ | index | integer | No | Zero-based index (alternative to tab_id) |
1303
+ | session_id | string | Yes | Session ID to use |
1304
+
1305
+ ---
1306
+
1307
+ ### set_viewport
1308
+
1309
+ | Name | Type | Required | Description |
1310
+ |------|------|----------|-------------|
1311
+ | width | integer | Yes | Viewport width in pixels |
1312
+ | height | integer | Yes | Viewport height in pixels |
1313
+ | scale_factor | number | No | Device scale factor (0 keeps the default) |
1314
+ | mobile | boolean | No | Emulate a mobile device (default: false) |
1315
+ | session_id | string | Yes | Session ID to use |
1316
+
1317
+ ---
1318
+
1043
1319
  ## Advanced
1044
1320
 
1045
1321
  ### execute_script
@@ -1179,6 +1455,7 @@ Set a cookie in the browser.
1179
1455
  | path | string | No | Cookie path (default: /) |
1180
1456
  | secure | boolean | No | Secure flag (default: false) |
1181
1457
  | httponly | boolean | No | HttpOnly flag (default: false) |
1458
+ | expires | integer | No | Expiry as a Unix timestamp in seconds |
1182
1459
  | session_id | string | Yes | Session ID to use |
1183
1460
 
1184
1461
  **Example Request:**
@@ -1387,7 +1664,7 @@ All tools return responses in this standard format:
1387
1664
  1. **Missing session_id**: "session_id is required. Create a session first using create_session tool."
1388
1665
  2. **Invalid session**: "Session not found: {session_id}"
1389
1666
  3. **Element not found**: "Element not found: {selector}"
1390
- 4. **Timeout errors**: "Navigation timed out" / "Timeout waiting for element"
1667
+ 4. **Timeout errors**: "Navigation timed out" / "Timed out after 10s waiting for #x to be visible"
1391
1668
  5. **JavaScript errors**: "Failed to execute script: {error}"
1392
1669
 
1393
1670
  ---
@@ -1397,10 +1674,12 @@ All tools return responses in this standard format:
1397
1674
  1. **Always create a session first**: Use `create_session` before any browser operations
1398
1675
  2. **Use resource discovery**: Query `ferrum://browsers` and `ferrum://bot-profiles` to see available configurations
1399
1676
  3. **Handle sessions properly**: Close sessions when done to free resources
1400
- 4. **Use appropriate selectors**: Prefer CSS selectors for performance, XPath for complex queries
1401
- 5. **Set timeouts wisely**: Increase timeout for slow-loading pages
1402
- 6. **Force clicks sparingly**: Only use `force: true` when necessary, as it bypasses visibility checks
1403
- 7. **Screenshot optimization**: Use `jpeg` format for smaller file sizes when quality is not critical
1677
+ 4. **Snapshot before acting**: call `snapshot` and use the returned refs (`ref:e12`) instead of guessing selectors
1678
+ 5. **Wait explicitly**: use `wait_for_selector` / `wait_for_text` / `wait_for_network_idle` rather than fixed delays
1679
+ 6. **Use appropriate selectors**: Prefer CSS selectors for performance, XPath for complex queries
1680
+ 7. **Set timeouts wisely**: Increase timeout for slow-loading pages
1681
+ 8. **Force clicks sparingly**: Only use `force: true` when necessary, as it bypasses visibility checks
1682
+ 9. **Screenshot optimization**: Use `jpeg` format for smaller file sizes when quality is not critical
1404
1683
 
1405
1684
  ---
1406
1685
 
@@ -17,12 +17,18 @@ See `.env.example` for a complete example configuration.
17
17
  | `MCP_SERVER_HOST` | HTTP server host | `0.0.0.0` |
18
18
  | `MCP_SERVER_PORT` | HTTP server port | `3000` |
19
19
  | `LOG_LEVEL` | Logging level (debug/info/warn/error) | `info` |
20
+ | `LOG_FILE` | Log destination: a file path or `stderr` | `./logs/ferrum_mcp.log` |
21
+ | `TRUST_PROXY` | Use `X-Forwarded-For` for rate limiting and audit logs (only behind a trusted proxy) | `false` |
22
+
23
+ **Logging**: logs never go to STDOUT because the stdio transport uses it for the protocol. The default file lives
24
+ under the current working directory (never inside the installed gem); when that directory is not writable the
25
+ server falls back to the system temp directory. Use `LOG_FILE=stderr` in containers.
20
26
 
21
27
  ### Browser Defaults
22
28
 
23
29
  | Variable | Description | Default |
24
30
  |----------|-------------|---------|
25
- | `BROWSER_HEADLESS` | Run browser in headless mode | `true` |
31
+ | `BROWSER_HEADLESS` | Run browser in headless mode | `false` |
26
32
  | `BROWSER_TIMEOUT` | Browser timeout in seconds | `60` |
27
33
 
28
34
  ### Session Management
@@ -42,6 +48,33 @@ See `.env.example` for a complete example configuration.
42
48
  | `RATE_LIMIT_WINDOW` | Time window in seconds | `60` |
43
49
 
44
50
  **Note**: Rate limiting is applied per client IP address. When exceeded, HTTP 429 (Too Many Requests) is returned with a `Retry-After` header.
51
+ The client address is the socket peer unless `TRUST_PROXY=true`, in which case the first `X-Forwarded-For` entry is used.
52
+ Never enable `TRUST_PROXY` on a server reachable directly by clients: they could rotate the header to escape the limit.
53
+
54
+ ### Navigation Policy
55
+
56
+ | Variable | Description | Default |
57
+ |----------|-------------|---------|
58
+ | `ALLOWED_HOSTS` | Comma-separated hosts that may be visited (only these when set) | _(unset)_ |
59
+ | `BLOCKED_HOSTS` | Comma-separated hosts that may never be visited (wins over the allow list) | _(unset)_ |
60
+
61
+ Entries accept an exact host (`example.com`), a wildcard suffix (`*.example.com`, which also matches
62
+ `example.com`) or a CIDR range for IP literals (`10.0.0.0/8`). The policy is enforced by `navigate` and
63
+ `new_tab` before the browser is touched. It checks the URL as written and does not resolve DNS, so treat it
64
+ as a guard rail for exposed HTTP deployments rather than a full SSRF defense.
65
+
66
+ ```bash
67
+ BLOCKED_HOSTS=localhost,127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,169.254.0.0/16,*.internal
68
+ ```
69
+
70
+ ### File Uploads
71
+
72
+ | Variable | Description | Default |
73
+ |----------|-------------|---------|
74
+ | `UPLOAD_ALLOWED_DIRS` | Comma-separated directories `upload_file` may read from | current directory, temp dir |
75
+
76
+ The `upload_file` tool attaches files from the **server's** filesystem. Paths are resolved (symlinks included)
77
+ and must live under one of the allowed directories.
45
78
 
46
79
  ## Multi-Browser Configuration
47
80
 
data/docs/DEPLOYMENT.md CHANGED
@@ -489,6 +489,7 @@ server {
489
489
  proxy_set_header X-Real-IP $remote_addr;
490
490
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
491
491
  proxy_set_header X-Forwarded-Proto $scheme;
492
+ # Set TRUST_PROXY=true on the FerrumMCP side so rate limiting uses this header
492
493
 
493
494
  # Timeouts
494
495
  proxy_connect_timeout 60s;
@@ -1,5 +1,9 @@
1
1
  # Docker with BotBrowser Integration
2
2
 
3
+ > **BotBrowser version**: the image installs the latest upstream BotBrowser release by default, because old
4
+ > releases are removed upstream. Pin one with `--build-arg BOTBROWSER_VERSION=155.0.8059.5`. Building behind a
5
+ > shared IP may hit the GitHub API rate limit; pass a token with `--secret id=github_token,env=GITHUB_TOKEN`.
6
+
3
7
  This guide explains how to build and run FerrumMCP Docker images with BotBrowser support for anti-detection capabilities.
4
8
 
5
9
  ## Overview
@@ -9,6 +9,32 @@ module FerrumMCP
9
9
  @config = config
10
10
  @logger = config.logger
11
11
  @browser = nil
12
+ @page = nil
13
+ end
14
+
15
+ # Current tab. Tools operate on this page so that tab switching works.
16
+ def page
17
+ raise BrowserError, 'Browser is not active' unless @browser
18
+
19
+ @page = nil if @page && !page_open?(@page)
20
+ @page ||= @browser.page
21
+ end
22
+
23
+ # All open tabs of the default browser context
24
+ def pages
25
+ raise BrowserError, 'Browser is not active' unless @browser
26
+
27
+ @browser.pages
28
+ end
29
+
30
+ def select_page(page)
31
+ @page = page
32
+ end
33
+
34
+ def create_page
35
+ raise BrowserError, 'Browser is not active' unless @browser
36
+
37
+ @browser.create_page
12
38
  end
13
39
 
14
40
  def start
@@ -45,10 +71,12 @@ module FerrumMCP
45
71
 
46
72
  logger.info 'Stopping browser...'
47
73
  @browser.quit
48
- @browser = nil
49
74
  logger.info 'Browser stopped'
50
75
  rescue StandardError => e
51
76
  logger.error "Error stopping browser: #{e.message}"
77
+ ensure
78
+ @browser = nil
79
+ @page = nil
52
80
  end
53
81
 
54
82
  def restart
@@ -60,41 +88,37 @@ module FerrumMCP
60
88
  !@browser.nil?
61
89
  end
62
90
 
63
- private
64
-
65
- # Compute browser options, merging defaults with session-specific options
66
- def computed_browser_options
67
- # Use merged options if config supports it (SessionConfiguration)
68
- options = if config.respond_to?(:merged_browser_options)
69
- config.merged_browser_options
70
- else
71
- browser_options
72
- end
73
-
74
- # Log BotBrowser profile usage
75
- if config.using_botbrowser? && config.botbrowser_profile && File.exist?(config.botbrowser_profile)
76
- logger.info "Using BotBrowser profile: #{config.botbrowser_profile}"
77
- end
78
-
79
- options
91
+ # True when a browser is started and its Chrome process still exists.
92
+ # Cheap (no CDP round-trip): a signal-0 check on the process id.
93
+ def healthy?
94
+ return false unless @browser
95
+
96
+ pid = @browser.process&.pid
97
+ return true unless pid
98
+
99
+ Process.kill(0, pid)
100
+ true
101
+ rescue Errno::ESRCH
102
+ logger.warn "Browser process #{pid} is gone"
103
+ false
104
+ rescue Errno::EPERM
105
+ true
80
106
  end
81
107
 
82
- def browser_options
83
- options = {
84
- 'no-sandbox' => nil,
85
- 'disable-dev-shm-usage' => nil,
86
- 'disable-blink-features' => 'AutomationControlled',
87
- 'disable-gpu' => nil
88
- }
89
-
90
- # Additional options for CI environments
91
- options['disable-setuid-sandbox'] = nil if ENV['CI']
108
+ private
92
109
 
93
- # Add BotBrowser profile if configured
94
- if config.using_botbrowser? && config.botbrowser_profile && File.exist?(config.botbrowser_profile)
95
- options['bot-profile'] = config.botbrowser_profile
96
- end
110
+ def page_open?(page)
111
+ @browser.pages.any? { |p| p.target_id == page.target_id }
112
+ rescue StandardError
113
+ false
114
+ end
97
115
 
116
+ # Browser flags come from the session configuration (defaults merged with
117
+ # session-specific options, keys without leading dashes).
118
+ def computed_browser_options
119
+ options = config.merged_browser_options
120
+ logger.info "Using BotBrowser profile: #{options['bot-profile']}" if options['bot-profile']
121
+ logger.info "Using user profile: #{options['user-data-dir']}" if options['user-data-dir']
98
122
  options
99
123
  end
100
124
  end
@@ -21,9 +21,10 @@ module FerrumMCP
21
21
  end
22
22
 
23
23
  def self.start_server(options)
24
- # Load dependencies only when starting server
25
- require 'bundler/setup'
26
- require 'dotenv/load'
24
+ # Load .env (if any) only when starting the server. No bundler/setup
25
+ # here: the installed gem must work outside a Bundler project.
26
+ require 'dotenv'
27
+ Dotenv.load
27
28
 
28
29
  # Set environment variables from options
29
30
  ENV['MCP_SERVER_HOST'] = options[:host]