#
tokens: 28328/50000 1/57 files (page 4/4)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 4 of 4. Use http://codebase.md/vibheksoni/stealth-browser-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .dockerignore
├── .github
│   ├── FUNDING.yml
│   ├── ISSUE_TEMPLATE
│   │   ├── bug_report.md
│   │   ├── config.yml
│   │   ├── feature_request.md
│   │   └── showcase.yml
│   ├── labeler.yml
│   ├── pull_request_template.md
│   └── workflows
│       └── ci.yml
├── .gitignore
├── CHANGELOG.md
├── Checklist.md
├── CODE_OF_CONDUCT.md
├── CODEOWNERS
├── COMPARISON.md
├── CONTRIBUTING.md
├── demo
│   ├── augment-hero-clone.md
│   ├── augment-hero-recreation.html
│   └── README.md
├── Dockerfile
├── examples
│   └── claude_prompts.md
├── HALL_OF_FAME.md
├── LICENSE
├── media
│   ├── AugmentHeroClone.PNG
│   ├── Showcase Stealth Browser Mcp.mp4
│   ├── showcase-demo-full.gif
│   ├── showcase-demo.gif
│   └── UndetectedStealthBrowser.png
├── pyproject.toml
├── README.md
├── requirements.txt
├── ROADMAP.md
├── run_server.bat
├── run_server.sh
├── SECURITY.md
├── smithery.yaml
└── src
    ├── __init__.py
    ├── browser_manager.py
    ├── cdp_element_cloner.py
    ├── cdp_function_executor.py
    ├── comprehensive_element_cloner.py
    ├── debug_logger.py
    ├── dom_handler.py
    ├── dynamic_hook_ai_interface.py
    ├── dynamic_hook_system.py
    ├── element_cloner.py
    ├── file_based_element_cloner.py
    ├── hook_learning_system.py
    ├── js
    │   ├── comprehensive_element_extractor.js
    │   ├── extract_animations.js
    │   ├── extract_assets.js
    │   ├── extract_events.js
    │   ├── extract_related_files.js
    │   ├── extract_structure.js
    │   └── extract_styles.js
    ├── models.py
    ├── network_interceptor.py
    ├── persistent_storage.py
    ├── platform_utils.py
    ├── process_cleanup.py
    ├── progressive_element_cloner.py
    ├── response_handler.py
    ├── response_stage_hooks.py
    └── server.py
```

# Files

--------------------------------------------------------------------------------
/src/server.py:
--------------------------------------------------------------------------------

```python
   1 | """Main MCP server for browser automation."""
   2 | 
   3 | import asyncio
   4 | import base64
   5 | import importlib
   6 | import json
   7 | import os
   8 | import signal
   9 | import sys
  10 | import tempfile
  11 | from contextlib import asynccontextmanager
  12 | from datetime import datetime
  13 | from pathlib import Path
  14 | from typing import Any, Dict, List, Optional, Union
  15 | 
  16 | import nodriver as uc
  17 | from fastmcp import FastMCP
  18 | 
  19 | from browser_manager import BrowserManager
  20 | from cdp_element_cloner import CDPElementCloner
  21 | from cdp_function_executor import CDPFunctionExecutor
  22 | from comprehensive_element_cloner import comprehensive_element_cloner
  23 | from debug_logger import debug_logger
  24 | from dom_handler import DOMHandler
  25 | from element_cloner import element_cloner
  26 | from file_based_element_cloner import file_based_element_cloner
  27 | from models import (
  28 |     BrowserOptions,
  29 |     NavigationOptions,
  30 |     ScriptResult,
  31 |     BrowserState,
  32 |     PageState,
  33 | )
  34 | from network_interceptor import NetworkInterceptor
  35 | from dynamic_hook_system import dynamic_hook_system
  36 | from dynamic_hook_ai_interface import dynamic_hook_ai
  37 | from persistent_storage import persistent_storage
  38 | from progressive_element_cloner import progressive_element_cloner
  39 | from response_handler import response_handler
  40 | from platform_utils import validate_browser_environment, get_platform_info
  41 | from process_cleanup import process_cleanup
  42 | 
  43 | DISABLED_SECTIONS = set()
  44 | 
  45 | def is_section_enabled(section: str) -> bool:
  46 |     """Check if a tool section is enabled."""
  47 |     return section not in DISABLED_SECTIONS
  48 | 
  49 | def section_tool(section: str):
  50 |     """Decorator to conditionally register tools based on section status."""
  51 |     def decorator(func):
  52 |         if is_section_enabled(section):
  53 |             return mcp.tool(func)
  54 |         else:
  55 |             return func
  56 |     return decorator
  57 | 
  58 | @asynccontextmanager
  59 | async def app_lifespan(server):
  60 |     """
  61 |     Manage application lifecycle with proper cleanup.
  62 | 
  63 |     Args:
  64 |         server (Any): The server instance for which the lifespan is being managed.
  65 |     """
  66 |     debug_logger.log_info("server", "startup", "Starting Browser Automation MCP Server...")
  67 |     try:
  68 |         yield
  69 |     finally:
  70 |         debug_logger.log_info("server", "shutdown", "Shutting down Browser Automation MCP Server...")
  71 |         try:
  72 |             await browser_manager.close_all()
  73 |             debug_logger.log_info("server", "cleanup", "All browser instances closed")
  74 |         except Exception as e:
  75 |             debug_logger.log_error("server", "cleanup", e)
  76 |         
  77 |         try:
  78 |             process_cleanup._cleanup_all_tracked()
  79 |             debug_logger.log_info("server", "cleanup", "Process cleanup complete")
  80 |         except Exception as e:
  81 |             debug_logger.log_error("server", "cleanup", f"Process cleanup failed: {e}")
  82 |         try:
  83 |             persistent_instances = persistent_storage.list_instances()
  84 |             if persistent_instances.get("instances"):
  85 |                 debug_logger.log_info(
  86 |                     "server",
  87 |                     "storage_cleanup",
  88 |                     f"Clearing in-memory storage with {len(persistent_instances['instances'])} instances...",
  89 |                 )
  90 |                 persistent_storage.clear_all()
  91 |                 debug_logger.log_info("server", "storage_cleanup", "In-memory storage cleared")
  92 |         except Exception as e:
  93 |             debug_logger.log_error("server", "storage_cleanup", e)
  94 |         debug_logger.log_info("server", "shutdown", "Browser Automation MCP Server shutdown complete")
  95 | 
  96 | mcp = FastMCP(
  97 |     name="Browser Automation MCP",
  98 |     instructions="""
  99 |     This MCP server provides undetectable browser automation using nodriver (CDP-based).
 100 |     
 101 |     Key features:
 102 |     - Spawn and manage multiple browser instances
 103 |     - Navigate and interact with web pages
 104 |     - Query and manipulate DOM elements
 105 |     - Intercept and analyze network traffic
 106 |     - Execute JavaScript in page context
 107 |     - Manage cookies and storage
 108 |     
 109 |     All browser instances are undetectable by anti-bot systems.
 110 |     """,
 111 |     lifespan=app_lifespan,
 112 | )
 113 | 
 114 | browser_manager = BrowserManager()
 115 | network_interceptor = NetworkInterceptor()
 116 | dom_handler = DOMHandler()
 117 | cdp_function_executor = CDPFunctionExecutor()
 118 | 
 119 | @section_tool("browser-management")
 120 | async def spawn_browser(
 121 |     headless: bool = False,
 122 |     user_agent: Optional[str] = None,
 123 |     viewport_width: int = 1920,
 124 |     viewport_height: int = 1080,
 125 |     proxy: Optional[str] = None,
 126 |     block_resources: List[str] = None,
 127 |     extra_headers: Dict[str, str] = None,
 128 |     user_data_dir: Optional[str] = None,
 129 |     sandbox: Optional[Any] = None
 130 | ) -> Dict[str, Any]:
 131 |     """
 132 |     Spawn a new browser instance.
 133 | 
 134 |     Args:
 135 |         headless (bool): Run in headless mode.
 136 |         user_agent (Optional[str]): Custom user agent string.
 137 |         viewport_width (int): Viewport width in pixels.
 138 |         viewport_height (int): Viewport height in pixels.
 139 |         proxy (Optional[str]): Proxy server URL.
 140 |         block_resources (List[str]): List of resource types to block (e.g., ['image', 'font', 'stylesheet']).
 141 |         extra_headers (Dict[str, str]): Additional HTTP headers.
 142 |         user_data_dir (Optional[str]): Path to user data directory for persistent sessions.
 143 |         sandbox (Optional[Any]): Enable browser sandbox. Accepts bool, string ('true'/'false'), int (1/0), or None for auto-detect.
 144 | 
 145 |     Returns:
 146 |         Dict[str, Any]: Instance information including instance_id.
 147 |     """
 148 |     try:
 149 |         from platform_utils import is_running_as_root, is_running_in_container
 150 |         
 151 |         if sandbox is None:
 152 |             sandbox = not (is_running_as_root() or is_running_in_container())
 153 |         elif isinstance(sandbox, str):
 154 |             sandbox = sandbox.lower() in ('true', '1', 'yes', 'on', 'enabled')
 155 |         elif isinstance(sandbox, int):
 156 |             sandbox = bool(sandbox)
 157 |         elif not isinstance(sandbox, bool):
 158 |             sandbox = bool(sandbox)
 159 |         
 160 |         options = BrowserOptions(
 161 |             headless=headless,
 162 |             user_agent=user_agent,
 163 |             viewport_width=viewport_width,
 164 |             viewport_height=viewport_height,
 165 |             proxy=proxy,
 166 |             block_resources=block_resources or [],
 167 |             extra_headers=extra_headers or {},
 168 |             user_data_dir=user_data_dir,
 169 |             sandbox=sandbox
 170 |         )
 171 |         instance = await browser_manager.spawn_browser(options)
 172 |         tab = await browser_manager.get_tab(instance.instance_id)
 173 |         if tab:
 174 |             await network_interceptor.setup_interception(
 175 |                 tab, instance.instance_id, block_resources
 176 |             )
 177 |             await dynamic_hook_system.setup_interception(tab, instance.instance_id)
 178 |             dynamic_hook_system.add_instance(instance.instance_id)
 179 |         return {
 180 |             "instance_id": instance.instance_id,
 181 |             "state": instance.state,
 182 |             "headless": instance.headless,
 183 |             "viewport": instance.viewport
 184 |         }
 185 |     except Exception as e:
 186 |         raise Exception(f"Failed to spawn browser: {str(e)}")
 187 | 
 188 | @section_tool("browser-management")
 189 | async def list_instances() -> List[Dict[str, Any]]:
 190 |     """
 191 |     List all active browser instances.
 192 | 
 193 |     Returns:
 194 |         List[Dict[str, Any]]: List of browser instances with their current state.
 195 |     """
 196 |     memory_instances = await browser_manager.list_instances()
 197 |     storage_instances = persistent_storage.list_instances()
 198 |     result = []
 199 |     for inst in memory_instances:
 200 |         result.append({
 201 |             "instance_id": inst.instance_id,
 202 |             "state": inst.state,
 203 |             "current_url": inst.current_url,
 204 |             "title": inst.title,
 205 |             "source": "active"
 206 |         })
 207 |     memory_ids = {inst.instance_id for inst in memory_instances}
 208 |     for instance_id, inst_data in storage_instances.get("instances", {}).items():
 209 |         if instance_id not in memory_ids:
 210 |             result.append({
 211 |                 "instance_id": inst_data["instance_id"],
 212 |                 "state": inst_data["state"] + " (stored)",
 213 |                 "current_url": inst_data["current_url"],
 214 |                 "title": inst_data["title"],
 215 |                 "source": "stored"
 216 |             })
 217 |     return result
 218 | 
 219 | @section_tool("browser-management")
 220 | async def close_instance(instance_id: str) -> bool:
 221 |     """
 222 |     Close a browser instance.
 223 | 
 224 |     Args:
 225 |         instance_id (str): Browser instance ID.
 226 | 
 227 |     Returns:
 228 |         bool: True if closed successfully.
 229 |     """
 230 |     success = await browser_manager.close_instance(instance_id)
 231 |     if success:
 232 |         await network_interceptor.clear_instance_data(instance_id)
 233 |     return success
 234 | 
 235 | @section_tool("browser-management")
 236 | async def get_instance_state(instance_id: str) -> Optional[Dict[str, Any]]:
 237 |     """
 238 |     Get detailed state of a browser instance.
 239 | 
 240 |     Args:
 241 |         instance_id (str): Browser instance ID.
 242 | 
 243 |     Returns:
 244 |         Optional[Dict[str, Any]]: Complete state information.
 245 |     """
 246 |     state = await browser_manager.get_page_state(instance_id)
 247 |     if state:
 248 |         return state.dict()
 249 |     return None
 250 | 
 251 | @section_tool("browser-management")
 252 | async def navigate(
 253 |     instance_id: str,
 254 |     url: str,
 255 |     wait_until: str = "load",
 256 |     timeout: int = 30000,
 257 |     referrer: Optional[str] = None
 258 | ) -> Dict[str, Any]:
 259 |     """
 260 |     Navigate to a URL.
 261 | 
 262 |     Args:
 263 |         instance_id (str): Browser instance ID.
 264 |         url (str): URL to navigate to.
 265 |         wait_until (str): Wait condition - 'load', 'domcontentloaded', or 'networkidle'.
 266 |         timeout (int): Navigation timeout in milliseconds.
 267 |         referrer (Optional[str]): Referrer URL.
 268 | 
 269 |     Returns:
 270 |         Dict[str, Any]: Navigation result with final URL and title.
 271 |     """
 272 |     if isinstance(timeout, str):
 273 |         timeout = int(timeout)
 274 |     tab = await browser_manager.get_tab(instance_id)
 275 |     if not tab:
 276 |         raise Exception(f"Instance not found: {instance_id}")
 277 |     try:
 278 |         if referrer:
 279 |             await tab.send(uc.cdp.page.set_referrer_policy(
 280 |                 referrerPolicy='origin-when-cross-origin'
 281 |             ))
 282 |         await tab.get(url)
 283 |         if wait_until == "domcontentloaded":
 284 |             await tab.wait(uc.cdp.page.DomContentEventFired)
 285 |         elif wait_until == "networkidle":
 286 |             await asyncio.sleep(2)
 287 |         else:
 288 |             await tab.wait(uc.cdp.page.LoadEventFired)
 289 |         final_url = await tab.evaluate("window.location.href")
 290 |         title = await tab.evaluate("document.title")
 291 |         await browser_manager.update_instance_state(instance_id, final_url, title)
 292 |         return {
 293 |             "url": final_url,
 294 |             "title": title,
 295 |             "success": True
 296 |         }
 297 |     except Exception as e:
 298 |         raise
 299 | 
 300 | @section_tool("browser-management")
 301 | async def go_back(instance_id: str) -> bool:
 302 |     """
 303 |     Navigate back in history.
 304 | 
 305 |     Args:
 306 |         instance_id (str): Browser instance ID.
 307 | 
 308 |     Returns:
 309 |         bool: True if navigation was successful.
 310 |     """
 311 |     tab = await browser_manager.get_tab(instance_id)
 312 |     if not tab:
 313 |         raise Exception(f"Instance not found: {instance_id}")
 314 |     await tab.back()
 315 |     return True
 316 | 
 317 | @section_tool("browser-management")
 318 | async def go_forward(instance_id: str) -> bool:
 319 |     """
 320 |     Navigate forward in history.
 321 | 
 322 |     Args:
 323 |         instance_id (str): Browser instance ID.
 324 | 
 325 |     Returns:
 326 |         bool: True if navigation was successful.
 327 |     """
 328 |     tab = await browser_manager.get_tab(instance_id)
 329 |     if not tab:
 330 |         raise Exception(f"Instance not found: {instance_id}")
 331 |     await tab.forward()
 332 |     return True
 333 | 
 334 | @section_tool("browser-management")
 335 | async def reload_page(instance_id: str, ignore_cache: bool = False) -> bool:
 336 |     """
 337 |     Reload the current page.
 338 | 
 339 |     Args:
 340 |         instance_id (str): Browser instance ID.
 341 |         ignore_cache (bool): Whether to ignore cache when reloading.
 342 | 
 343 |     Returns:
 344 |         bool: True if reload was successful.
 345 |     """
 346 |     tab = await browser_manager.get_tab(instance_id)
 347 |     if not tab:
 348 |         raise Exception(f"Instance not found: {instance_id}")
 349 |     await tab.reload()
 350 |     return True
 351 | 
 352 | @section_tool("element-interaction")
 353 | async def query_elements(
 354 |     instance_id: str,
 355 |     selector: str,
 356 |     text_filter: Optional[str] = None,
 357 |     visible_only: bool = True,
 358 |     limit: Optional[Any] = None
 359 | ) -> List[Dict[str, Any]]:
 360 |     """
 361 |     Query DOM elements.
 362 | 
 363 |     Args:
 364 |         instance_id (str): Browser instance ID.
 365 |         selector (str): CSS selector or XPath (starts with '//').
 366 |         text_filter (Optional[str]): Filter by text content.
 367 |         visible_only (bool): Only return visible elements.
 368 |         limit (Optional[Any]): Maximum number of elements to return.
 369 | 
 370 |     Returns:
 371 |         List[Dict[str, Any]]: List of matching elements with their properties.
 372 |     """
 373 |     tab = await browser_manager.get_tab(instance_id)
 374 |     if not tab:
 375 |         raise Exception(f"Instance not found: {instance_id}")
 376 |     debug_logger.log_info('Server', 'query_elements', f'Received limit parameter: {limit} (type: {type(limit)})')
 377 |     elements = await dom_handler.query_elements(
 378 |         tab, selector, text_filter, visible_only, limit
 379 |     )
 380 |     debug_logger.log_info('Server', 'query_elements', f'DOM handler returned {len(elements)} elements')
 381 |     result = []
 382 |     for i, elem in enumerate(elements):
 383 |         try:
 384 |             if hasattr(elem, 'model_dump'):
 385 |                 elem_dict = elem.model_dump()
 386 |             else:
 387 |                 elem_dict = elem.dict()
 388 |             result.append(elem_dict)
 389 |             debug_logger.log_info('Server', 'query_elements', f'Converted element {i+1} to dict: {list(elem_dict.keys())}')
 390 |         except Exception as e:
 391 |             debug_logger.log_error('Server', 'query_elements', e, {'element_index': i})
 392 |     debug_logger.log_info('Server', 'query_elements', f'Returning {len(result)} results to MCP client')
 393 |     return result if result else []
 394 | 
 395 | @section_tool("element-interaction")
 396 | async def click_element(
 397 |     instance_id: str,
 398 |     selector: str,
 399 |     text_match: Optional[str] = None,
 400 |     timeout: int = 10000
 401 | ) -> bool:
 402 |     """
 403 |     Click an element.
 404 | 
 405 |     Args:
 406 |         instance_id (str): Browser instance ID.
 407 |         selector (str): CSS selector or XPath.
 408 |         text_match (Optional[str]): Click element with matching text.
 409 |         timeout (int): Timeout in milliseconds.
 410 | 
 411 |     Returns:
 412 |         bool: True if clicked successfully.
 413 |     """
 414 |     if isinstance(timeout, str):
 415 |         timeout = int(timeout)
 416 |     tab = await browser_manager.get_tab(instance_id)
 417 |     if not tab:
 418 |         raise Exception(f"Instance not found: {instance_id}")
 419 |     return await dom_handler.click_element(tab, selector, text_match, timeout)
 420 | 
 421 | @section_tool("element-interaction")
 422 | async def type_text(
 423 |     instance_id: str,
 424 |     selector: str,
 425 |     text: str,
 426 |     clear_first: bool = True,
 427 |     delay_ms: int = 50,
 428 |     parse_newlines: bool = False,
 429 |     shift_enter: bool = False
 430 | ) -> bool:
 431 |     """
 432 |     Type text into an input field.
 433 | 
 434 |     Args:
 435 |         instance_id (str): Browser instance ID.
 436 |         selector (str): CSS selector or XPath.
 437 |         text (str): Text to type.
 438 |         clear_first (bool): Clear field before typing.
 439 |         delay_ms (int): Delay between keystrokes in milliseconds.
 440 |         parse_newlines (bool): If True, parse \n as Enter key presses.
 441 |         shift_enter (bool): If True, use Shift+Enter instead of Enter (for chat apps).
 442 | 
 443 |     Returns:
 444 |         bool: True if typed successfully.
 445 |     """
 446 |     if isinstance(delay_ms, str):
 447 |         delay_ms = int(delay_ms)
 448 |     tab = await browser_manager.get_tab(instance_id)
 449 |     if not tab:
 450 |         raise Exception(f"Instance not found: {instance_id}")
 451 |     return await dom_handler.type_text(tab, selector, text, clear_first, delay_ms, parse_newlines, shift_enter)
 452 | 
 453 | @section_tool("element-interaction")
 454 | async def paste_text(
 455 |     instance_id: str,
 456 |     selector: str,
 457 |     text: str,
 458 |     clear_first: bool = True
 459 | ) -> bool:
 460 |     """
 461 |     Paste text instantly into an input field.
 462 | 
 463 |     Args:
 464 |         instance_id (str): Browser instance ID.
 465 |         selector (str): CSS selector or XPath.
 466 |         text (str): Text to paste.
 467 |         clear_first (bool): Clear field before pasting.
 468 | 
 469 |     Returns:
 470 |         bool: True if pasted successfully.
 471 |     """
 472 |     tab = await browser_manager.get_tab(instance_id)
 473 |     if not tab:
 474 |         raise Exception(f"Instance not found: {instance_id}")
 475 |     return await dom_handler.paste_text(tab, selector, text, clear_first)
 476 | 
 477 | @section_tool("element-interaction")
 478 | async def select_option(
 479 |     instance_id: str,
 480 |     selector: str,
 481 |     value: Optional[str] = None,
 482 |     text: Optional[str] = None,
 483 |     index: Optional[Any] = None
 484 | ) -> bool:
 485 |     """
 486 |     Select an option from a dropdown.
 487 | 
 488 |     Args:
 489 |         instance_id (str): Browser instance ID.
 490 |         selector (str): CSS selector for the select element.
 491 |         value (Optional[str]): Option value attribute.
 492 |         text (Optional[str]): Option text content.
 493 |         index (Optional[Any]): Option index (0-based). Can be string or int.
 494 | 
 495 |     Returns:
 496 |         bool: True if selected successfully.
 497 |     """
 498 |     tab = await browser_manager.get_tab(instance_id)
 499 |     if not tab:
 500 |         raise Exception(f"Instance not found: {instance_id}")
 501 |     
 502 |     converted_index = None
 503 |     if index is not None:
 504 |         try:
 505 |             converted_index = int(index)
 506 |         except (ValueError, TypeError):
 507 |             raise Exception(f"Invalid index value: {index}. Must be a number.")
 508 |     
 509 |     return await dom_handler.select_option(tab, selector, value, text, converted_index)
 510 | 
 511 | @section_tool("element-interaction")
 512 | async def get_element_state(
 513 |     instance_id: str,
 514 |     selector: str
 515 | ) -> Dict[str, Any]:
 516 |     """
 517 |     Get complete state of an element.
 518 | 
 519 |     Args:
 520 |         instance_id (str): Browser instance ID.
 521 |         selector (str): CSS selector or XPath.
 522 | 
 523 |     Returns:
 524 |         Dict[str, Any]: Element state including attributes, style, position, etc.
 525 |     """
 526 |     tab = await browser_manager.get_tab(instance_id)
 527 |     if not tab:
 528 |         raise Exception(f"Instance not found: {instance_id}")
 529 |     return await dom_handler.get_element_state(tab, selector)
 530 | 
 531 | @section_tool("element-interaction")
 532 | async def wait_for_element(
 533 |     instance_id: str,
 534 |     selector: str,
 535 |     timeout: int = 30000,
 536 |     visible: bool = True,
 537 |     text_content: Optional[str] = None
 538 | ) -> bool:
 539 |     """
 540 |     Wait for an element to appear.
 541 | 
 542 |     Args:
 543 |         instance_id (str): Browser instance ID.
 544 |         selector (str): CSS selector or XPath.
 545 |         timeout (int): Timeout in milliseconds.
 546 |         visible (bool): Wait for element to be visible.
 547 |         text_content (Optional[str]): Wait for specific text content.
 548 | 
 549 |     Returns:
 550 |         bool: True if element found.
 551 |     """
 552 |     if isinstance(timeout, str):
 553 |         timeout = int(timeout)
 554 |     tab = await browser_manager.get_tab(instance_id)
 555 |     if not tab:
 556 |         raise Exception(f"Instance not found: {instance_id}")
 557 |     return await dom_handler.wait_for_element(tab, selector, timeout, visible, text_content)
 558 | 
 559 | @section_tool("element-interaction")
 560 | async def scroll_page(
 561 |     instance_id: str,
 562 |     direction: str = "down",
 563 |     amount: int = 500,
 564 |     smooth: bool = True
 565 | ) -> bool:
 566 |     """
 567 |     Scroll the page.
 568 | 
 569 |     Args:
 570 |         instance_id (str): Browser instance ID.
 571 |         direction (str): 'down', 'up', 'left', 'right', 'top', or 'bottom'.
 572 |         amount (int): Pixels to scroll (ignored for 'top' and 'bottom').
 573 |         smooth (bool): Use smooth scrolling.
 574 | 
 575 |     Returns:
 576 |         bool: True if scrolled successfully.
 577 |     """
 578 |     if isinstance(amount, str):
 579 |         amount = int(amount)
 580 |     tab = await browser_manager.get_tab(instance_id)
 581 |     if not tab:
 582 |         raise Exception(f"Instance not found: {instance_id}")
 583 |     return await dom_handler.scroll_page(tab, direction, amount, smooth)
 584 | 
 585 | @section_tool("element-interaction")
 586 | async def execute_script(
 587 |     instance_id: str,
 588 |     script: str,
 589 |     args: Optional[List[Any]] = None
 590 | ) -> Dict[str, Any]:
 591 |     """
 592 |     Execute JavaScript in page context.
 593 | 
 594 |     Args:
 595 |         instance_id (str): Browser instance ID.
 596 |         script (str): JavaScript code to execute.
 597 |         args (Optional[List[Any]]): Arguments to pass to the script.
 598 | 
 599 |     Returns:
 600 |         Dict[str, Any]: Script execution result.
 601 |     """
 602 |     tab = await browser_manager.get_tab(instance_id)
 603 |     if not tab:
 604 |         raise Exception(f"Instance not found: {instance_id}")
 605 |     try:
 606 |         result = await dom_handler.execute_script(tab, script, args)
 607 |         return {
 608 |             "success": True,
 609 |             "result": result,
 610 |             "error": None
 611 |         }
 612 |     except Exception as e:
 613 |         return {
 614 |             "success": False,
 615 |             "result": None,
 616 |             "error": str(e)
 617 |         }
 618 | 
 619 | @section_tool("element-interaction")
 620 | async def get_page_content(
 621 |     instance_id: str,
 622 |     include_frames: bool = False
 623 | ) -> Dict[str, Any]:
 624 |     """
 625 |     Get page HTML and text content.
 626 | 
 627 |     Args:
 628 |         instance_id (str): Browser instance ID.
 629 |         include_frames (bool): Include iframe information.
 630 | 
 631 |     Returns:
 632 |         Dict[str, Any]: Page content including HTML, text, and metadata.
 633 |     """
 634 |     tab = await browser_manager.get_tab(instance_id)
 635 |     if not tab:
 636 |         raise Exception(f"Instance not found: {instance_id}")
 637 |     content = await dom_handler.get_page_content(tab, include_frames)
 638 |     
 639 |     return response_handler.handle_response(
 640 |         content, 
 641 |         "page_content", 
 642 |         {"instance_id": instance_id, "include_frames": include_frames}
 643 |     )
 644 | 
 645 | @section_tool("element-interaction")
 646 | async def take_screenshot(
 647 |     instance_id: str,
 648 |     full_page: bool = False,
 649 |     format: str = "png",
 650 |     file_path: Optional[str] = None
 651 | ) -> Union[str, Dict[str, Any]]:
 652 |     """
 653 |     Take a screenshot of the page.
 654 | 
 655 |     Args:
 656 |         instance_id (str): Browser instance ID.
 657 |         full_page (bool): Capture full page (not just viewport).
 658 |         format (str): Image format ('png' or 'jpeg').
 659 |         file_path (Optional[str]): Optional file path to save screenshot to.
 660 | 
 661 |     Returns:
 662 |         Union[str, Dict]: File path if file_path provided, otherwise optimized base64 data or file info dict.
 663 |     """
 664 |     from PIL import Image
 665 |     import io
 666 |     
 667 |     tab = await browser_manager.get_tab(instance_id)
 668 |     if not tab:
 669 |         raise Exception(f"Instance not found: {instance_id}")
 670 |     
 671 |     if file_path:
 672 |         save_path = Path(file_path)
 673 |         save_path.parent.mkdir(parents=True, exist_ok=True)
 674 |         await tab.save_screenshot(save_path)
 675 |         return f"Screenshot saved. AI agents should use the Read tool to view this image: {str(save_path.absolute())}"
 676 |     
 677 |     with tempfile.NamedTemporaryFile(suffix='.png', delete=False) as tmp_file:
 678 |         tmp_path = Path(tmp_file.name)
 679 |     
 680 |     try:
 681 |         await tab.save_screenshot(tmp_path)
 682 |         
 683 |         with Image.open(tmp_path) as img:
 684 |             if img.mode in ('RGBA', 'LA', 'P') and format.lower() == 'jpeg':
 685 |                 background = Image.new('RGB', img.size, (255, 255, 255))
 686 |                 if img.mode == 'P':
 687 |                     img = img.convert('RGBA')
 688 |                 background.paste(img, mask=img.split()[-1] if img.mode in ('RGBA', 'LA') else None)
 689 |                 img = background
 690 |             
 691 |             output_buffer = io.BytesIO()
 692 |             
 693 |             if format.lower() == 'jpeg':
 694 |                 img.save(output_buffer, format='JPEG', quality=85, optimize=True)
 695 |             else:
 696 |                 img.save(output_buffer, format='PNG', optimize=True)
 697 |             
 698 |             compressed_bytes = output_buffer.getvalue()
 699 |             
 700 |             base64_size = len(compressed_bytes) * 1.33
 701 |             estimated_tokens = int(base64_size / 4)
 702 |             
 703 |             if estimated_tokens > 20000:
 704 |                 timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
 705 |                 screenshot_filename = f"screenshot_{timestamp}_{instance_id[:8]}.{format.lower()}"
 706 |                 screenshot_path = response_handler.clone_dir / screenshot_filename
 707 |                 
 708 |                 with open(screenshot_path, 'wb') as f:
 709 |                     f.write(compressed_bytes)
 710 |                 
 711 |                 file_size_kb = len(compressed_bytes) / 1024
 712 |                 return {
 713 |                     "file_path": str(screenshot_path),
 714 |                     "filename": screenshot_filename,
 715 |                     "file_size_kb": round(file_size_kb, 2),
 716 |                     "estimated_tokens": estimated_tokens,
 717 |                     "reason": "Screenshot too large, automatically saved to file",
 718 |                     "message": f"Screenshot saved. AI agents should use the Read tool to view this image: {str(screenshot_path)}"
 719 |                 }
 720 |             
 721 |             return base64.b64encode(compressed_bytes).decode('utf-8')
 722 |             
 723 |     finally:
 724 |         if tmp_path.exists():
 725 |             os.unlink(tmp_path)
 726 | 
 727 | 
 728 | @section_tool("network-debugging")
 729 | async def list_network_requests(
 730 |     instance_id: str,
 731 |     filter_type: Optional[str] = None
 732 | ) -> Union[List[Dict[str, Any]], Dict[str, Any]]:
 733 |     """
 734 |     List captured network requests.
 735 | 
 736 |     Args:
 737 |         instance_id (str): Browser instance ID.
 738 |         filter_type (Optional[str]): Filter by resource type (e.g., 'image', 'script', 'xhr').
 739 | 
 740 |     Returns:
 741 |         Union[List[Dict[str, Any]], Dict[str, Any]]: List of network requests, or file metadata if response too large.
 742 |     """
 743 |     requests = await network_interceptor.list_requests(instance_id, filter_type)
 744 |     formatted_requests = [
 745 |         {
 746 |             "request_id": req.request_id,
 747 |             "url": req.url,
 748 |             "method": req.method,
 749 |             "resource_type": req.resource_type,
 750 |             "timestamp": req.timestamp.isoformat()
 751 |         }
 752 |         for req in requests
 753 |     ]
 754 |     
 755 |     return response_handler.handle_response(formatted_requests, "network_requests")
 756 | 
 757 | 
 758 | @section_tool("network-debugging")
 759 | async def get_request_details(
 760 |     request_id: str
 761 | ) -> Optional[Dict[str, Any]]:
 762 |     """
 763 |     Get detailed information about a network request.
 764 | 
 765 |     Args:
 766 |         request_id (str): Network request ID.
 767 | 
 768 |     Returns:
 769 |         Optional[Dict[str, Any]]: Request details including headers, cookies, and body.
 770 |     """
 771 |     request = await network_interceptor.get_request(request_id)
 772 |     if request:
 773 |         return request.dict()
 774 |     return None
 775 | 
 776 | 
 777 | @section_tool("network-debugging")
 778 | async def get_response_details(
 779 |     request_id: str
 780 | ) -> Optional[Dict[str, Any]]:
 781 |     """
 782 |     Get response details for a network request.
 783 | 
 784 |     Args:
 785 |         request_id (str): Network request ID.
 786 | 
 787 |     Returns:
 788 |         Optional[Dict[str, Any]]: Response details including status, headers, and metadata.
 789 |     """
 790 |     response = await network_interceptor.get_response(request_id)
 791 |     if response:
 792 |         return response.dict()
 793 |     return None
 794 | 
 795 | 
 796 | @section_tool("network-debugging")
 797 | async def get_response_content(
 798 |     instance_id: str,
 799 |     request_id: str
 800 | ) -> Optional[str]:
 801 |     """
 802 |     Get response body content.
 803 | 
 804 |     Args:
 805 |         instance_id (str): Browser instance ID.
 806 |         request_id (str): Network request ID.
 807 | 
 808 |     Returns:
 809 |         Optional[str]: Response body as text (base64 encoded for binary).
 810 |     """
 811 |     tab = await browser_manager.get_tab(instance_id)
 812 |     if not tab:
 813 |         raise Exception(f"Instance not found: {instance_id}")
 814 |     body = await network_interceptor.get_response_body(tab, request_id)
 815 |     if body:
 816 |         try:
 817 |             return body.decode('utf-8')
 818 |         except UnicodeDecodeError:
 819 |             import base64
 820 |             return base64.b64encode(body).decode('utf-8')
 821 |     return None
 822 | 
 823 | 
 824 | @section_tool("network-debugging")
 825 | async def modify_headers(
 826 |     instance_id: str,
 827 |     headers: Dict[str, str]
 828 | ) -> bool:
 829 |     """
 830 |     Modify request headers for future requests.
 831 | 
 832 |     Args:
 833 |         instance_id (str): Browser instance ID.
 834 |         headers (Dict[str, str]): Headers to add/modify.
 835 | 
 836 |     Returns:
 837 |         bool: True if modified successfully.
 838 |     """
 839 |     tab = await browser_manager.get_tab(instance_id)
 840 |     if not tab:
 841 |         raise Exception(f"Instance not found: {instance_id}")
 842 |     return await network_interceptor.modify_headers(tab, headers)
 843 | 
 844 | 
 845 | @section_tool("cookies-storage")
 846 | async def get_cookies(
 847 |     instance_id: str,
 848 |     urls: Optional[List[str]] = None
 849 | ) -> List[Dict[str, Any]]:
 850 |     """
 851 |     Get cookies for current page or specific URLs.
 852 | 
 853 |     Args:
 854 |         instance_id (str): Browser instance ID.
 855 |         urls (Optional[List[str]]): Optional list of URLs to get cookies for.
 856 | 
 857 |     Returns:
 858 |         List[Dict[str, Any]]: List of cookies.
 859 |     """
 860 |     tab = await browser_manager.get_tab(instance_id)
 861 |     if not tab:
 862 |         raise Exception(f"Instance not found: {instance_id}")
 863 |     return await network_interceptor.get_cookies(tab, urls)
 864 | 
 865 | 
 866 | @section_tool("cookies-storage")
 867 | async def set_cookie(
 868 |     instance_id: str,
 869 |     name: str,
 870 |     value: str,
 871 |     url: Optional[str] = None,
 872 |     domain: Optional[str] = None,
 873 |     path: str = "/",
 874 |     secure: bool = False,
 875 |     http_only: bool = False,
 876 |     same_site: Optional[str] = None
 877 | ) -> bool:
 878 |     """
 879 |     Set a cookie.
 880 | 
 881 |     Args:
 882 |         instance_id (str): Browser instance ID.
 883 |         name (str): Cookie name.
 884 |         value (str): Cookie value.
 885 |         url (Optional[str]): The request-URI to associate with the cookie.
 886 |         domain (Optional[str]): Cookie domain.
 887 |         path (str): Cookie path.
 888 |         secure (bool): Secure flag.
 889 |         http_only (bool): HttpOnly flag.
 890 |         same_site (Optional[str]): SameSite attribute ('Strict', 'Lax', or 'None').
 891 | 
 892 |     Returns:
 893 |         bool: True if set successfully.
 894 |     """
 895 |     tab = await browser_manager.get_tab(instance_id)
 896 |     if not tab:
 897 |         raise Exception(f"Instance not found: {instance_id}")
 898 |     
 899 |     if not url and not domain:
 900 |         current_url = tab.url if hasattr(tab, 'url') else None
 901 |         if current_url:
 902 |             url = current_url
 903 |         else:
 904 |             raise Exception("At least one of 'url' or 'domain' must be specified")
 905 |     
 906 |     cookie = {
 907 |         "name": name,
 908 |         "value": value,
 909 |         "path": path,
 910 |         "secure": secure,
 911 |         "http_only": http_only
 912 |     }
 913 |     if url:
 914 |         cookie["url"] = url
 915 |     if domain:
 916 |         cookie["domain"] = domain
 917 |     if same_site:
 918 |         cookie["same_site"] = same_site
 919 |     return await network_interceptor.set_cookie(tab, cookie)
 920 | 
 921 | 
 922 | @section_tool("cookies-storage")
 923 | async def clear_cookies(
 924 |     instance_id: str,
 925 |     url: Optional[str] = None
 926 | ) -> bool:
 927 |     """
 928 |     Clear cookies.
 929 | 
 930 |     Args:
 931 |         instance_id (str): Browser instance ID.
 932 |         url (Optional[str]): Optional URL to clear cookies for (clears all if not specified).
 933 | 
 934 |     Returns:
 935 |         bool: True if cleared successfully.
 936 |     """
 937 |     tab = await browser_manager.get_tab(instance_id)
 938 |     if not tab:
 939 |         raise Exception(f"Instance not found: {instance_id}")
 940 |     return await network_interceptor.clear_cookies(tab, url)
 941 | 
 942 | 
 943 | @mcp.resource("browser://{instance_id}/state")
 944 | async def get_browser_state_resource(instance_id: str) -> str:
 945 |     """
 946 |     Get current state of a browser instance.
 947 | 
 948 |     Args:
 949 |         instance_id (str): Browser instance ID.
 950 | 
 951 |     Returns:
 952 |         str: JSON string of the browser state or error message.
 953 |     """
 954 |     state = await browser_manager.get_page_state(instance_id)
 955 |     if state:
 956 |         return json.dumps(state.dict(), indent=2)
 957 |     return json.dumps({"error": "Instance not found"})
 958 | 
 959 | 
 960 | @mcp.resource("browser://{instance_id}/cookies")
 961 | async def get_cookies_resource(instance_id: str) -> str:
 962 |     """
 963 |     Get cookies for a browser instance.
 964 | 
 965 |     Args:
 966 |         instance_id (str): Browser instance ID.
 967 | 
 968 |     Returns:
 969 |         str: JSON string of cookies or error message.
 970 |     """
 971 |     tab = await browser_manager.get_tab(instance_id)
 972 |     if tab:
 973 |         cookies = await network_interceptor.get_cookies(tab)
 974 |         return json.dumps(cookies, indent=2)
 975 |     return json.dumps({"error": "Instance not found"})
 976 | 
 977 | 
 978 | @mcp.resource("browser://{instance_id}/network")
 979 | async def get_network_resource(instance_id: str) -> str:
 980 |     """
 981 |     Get network requests for a browser instance.
 982 | 
 983 |     Args:
 984 |         instance_id (str): Browser instance ID.
 985 | 
 986 |     Returns:
 987 |         str: JSON string of network requests.
 988 |     """
 989 |     requests = await network_interceptor.list_requests(instance_id)
 990 |     return json.dumps([req.dict() for req in requests], indent=2)
 991 | 
 992 | 
 993 | @mcp.resource("browser://{instance_id}/console")
 994 | async def get_console_resource(instance_id: str) -> str:
 995 |     """
 996 |     Get console logs for a browser instance.
 997 | 
 998 |     Args:
 999 |         instance_id (str): Browser instance ID.
1000 | 
1001 |     Returns:
1002 |         str: JSON string of console logs or error message.
1003 |     """
1004 |     state = await browser_manager.get_page_state(instance_id)
1005 |     if state:
1006 |         return json.dumps(state.console_logs, indent=2)
1007 |     return json.dumps({"error": "Instance not found"})
1008 | 
1009 | 
1010 | @section_tool("debugging")
1011 | async def get_debug_view(
1012 |     max_errors: int = 50,
1013 |     max_warnings: int = 50,
1014 |     max_info: int = 50,
1015 |     include_all: bool = False
1016 | ) -> Dict[str, Any]:
1017 |     """
1018 |     Get comprehensive debug view with all logged errors and statistics.
1019 | 
1020 |     Args:
1021 |         max_errors (int): Maximum number of errors to include (default: 50).
1022 |         max_warnings (int): Maximum number of warnings to include (default: 50).
1023 |         max_info (int): Maximum number of info logs to include (default: 50).
1024 |         include_all (bool): Include all logs regardless of limits (default: False).
1025 | 
1026 |     Returns:
1027 |         Dict[str, Any]: Debug information including errors, warnings, and statistics.
1028 |     """
1029 |     debug_data = debug_logger.get_debug_view_paginated(
1030 |         max_errors=max_errors if not include_all else None,
1031 |         max_warnings=max_warnings if not include_all else None,
1032 |         max_info=max_info if not include_all else None
1033 |     )
1034 |     return debug_data
1035 | 
1036 | 
1037 | @section_tool("debugging")
1038 | async def clear_debug_view() -> bool:
1039 |     """
1040 |     Clear all debug logs and statistics with timeout protection.
1041 | 
1042 |     Returns:
1043 |         bool: True if cleared successfully.
1044 |     """
1045 |     try:
1046 |         await asyncio.wait_for(
1047 |             asyncio.to_thread(debug_logger.clear_debug_view_safe),
1048 |             timeout=10.0
1049 |         )
1050 |         return True
1051 |     except asyncio.TimeoutError:
1052 |         return False
1053 | 
1054 | 
1055 | @section_tool("debugging")
1056 | async def export_debug_logs(
1057 |     filename: str = "debug_log.json",
1058 |     max_errors: int = 100,
1059 |     max_warnings: int = 100,
1060 |     max_info: int = 100,
1061 |     include_all: bool = False,
1062 |     format: str = "auto"
1063 | ) -> str:
1064 |     """
1065 |     Export debug logs to a file using the fastest available method with timeout protection.
1066 | 
1067 |     Args:
1068 |         filename (str): Name of the file to export to.
1069 |         max_errors (int): Maximum number of errors to export (default: 100).
1070 |         max_warnings (int): Maximum number of warnings to export (default: 100).
1071 |         max_info (int): Maximum number of info logs to export (default: 100).
1072 |         include_all (bool): Include all logs regardless of limits (default: False).
1073 |         format (str): Export format: 'json', 'pickle', 'gzip-pickle', 'auto' (default: 'auto').
1074 |                      'auto' chooses fastest format based on data size:
1075 |                      - Small data (<100 items): JSON (human readable)
1076 |                      - Medium data (100-1000 items): Pickle (fast binary)
1077 |                      - Large data (>1000 items): Gzip-Pickle (fastest, compressed)
1078 | 
1079 |     Returns:
1080 |         str: Path to the exported file.
1081 |     """
1082 |     try:
1083 |         filepath = await asyncio.wait_for(
1084 |             asyncio.to_thread(
1085 |                 debug_logger.export_to_file_paginated,
1086 |                 filename,
1087 |                 max_errors if not include_all else None,
1088 |                 max_warnings if not include_all else None,
1089 |                 max_info if not include_all else None,
1090 |                 format
1091 |             ),
1092 |             timeout=30.0
1093 |         )
1094 |         return filepath
1095 |     except asyncio.TimeoutError:
1096 |         return f"Export timeout - file too large. Try with smaller limits or 'gzip-pickle' format."
1097 | 
1098 | 
1099 | @section_tool("debugging")
1100 | async def get_debug_lock_status() -> Dict[str, Any]:
1101 |     """
1102 |     Get current debug logger lock status for debugging hanging exports.
1103 | 
1104 |     Returns:
1105 |         Dict[str, Any]: Lock status information.
1106 |     """
1107 |     try:
1108 |         return debug_logger.get_lock_status()
1109 |     except Exception as e:
1110 |         return {"error": str(e)}
1111 | 
1112 | 
1113 | @section_tool("tabs")
1114 | async def list_tabs(instance_id: str) -> List[Dict[str, str]]:
1115 |     """
1116 |     List all tabs for a browser instance.
1117 | 
1118 |     Args:
1119 |         instance_id (str): Browser instance ID.
1120 | 
1121 |     Returns:
1122 |         List[Dict[str, str]]: List of tabs with their details.
1123 |     """
1124 |     return await browser_manager.list_tabs(instance_id)
1125 | 
1126 | 
1127 | @section_tool("tabs")
1128 | async def switch_tab(
1129 |     instance_id: str,
1130 |     tab_id: str
1131 | ) -> bool:
1132 |     """
1133 |     Switch to a specific tab by bringing it to front.
1134 | 
1135 |     Args:
1136 |         instance_id (str): Browser instance ID.
1137 |         tab_id (str): Target tab ID to switch to.
1138 | 
1139 |     Returns:
1140 |         bool: True if switched successfully.
1141 |     """
1142 |     return await browser_manager.switch_to_tab(instance_id, tab_id)
1143 | 
1144 | 
1145 | @section_tool("tabs")
1146 | async def close_tab(
1147 |     instance_id: str,
1148 |     tab_id: str
1149 | ) -> bool:
1150 |     """
1151 |     Close a specific tab.
1152 | 
1153 |     Args:
1154 |         instance_id (str): Browser instance ID.
1155 |         tab_id (str): Tab ID to close.
1156 | 
1157 |     Returns:
1158 |         bool: True if closed successfully.
1159 |     """
1160 |     return await browser_manager.close_tab(instance_id, tab_id)
1161 | 
1162 | 
1163 | @section_tool("tabs")
1164 | async def get_active_tab(instance_id: str) -> Dict[str, Any]:
1165 |     """
1166 |     Get information about the currently active tab.
1167 | 
1168 |     Args:
1169 |         instance_id (str): Browser instance ID.
1170 | 
1171 |     Returns:
1172 |         Dict[str, Any]: Active tab information.
1173 |     """
1174 |     tab = await browser_manager.get_active_tab(instance_id)
1175 |     if not tab:
1176 |         return {"error": "No active tab found"}
1177 |     await tab
1178 |     return {
1179 |         "tab_id": str(tab.target.target_id),
1180 |         "url": getattr(tab, 'url', '') or '',
1181 |         "title": getattr(tab.target, 'title', '') or 'Untitled',
1182 |         "type": getattr(tab.target, 'type_', 'page')
1183 |     }
1184 | 
1185 | 
1186 | @section_tool("tabs")
1187 | async def new_tab(
1188 |     instance_id: str,
1189 |     url: str = "about:blank"
1190 | ) -> Dict[str, Any]:
1191 |     """
1192 |     Open a new tab in the browser instance.
1193 | 
1194 |     Args:
1195 |         instance_id (str): Browser instance ID.
1196 |         url (str): URL to open in the new tab.
1197 | 
1198 |     Returns:
1199 |         Dict[str, Any]: New tab information.
1200 |     """
1201 |     browser = await browser_manager.get_browser(instance_id)
1202 |     if not browser:
1203 |         raise Exception(f"Instance not found: {instance_id}")
1204 |     try:
1205 |         new_tab_obj = await browser.get(url, new_tab=True)
1206 |         await new_tab_obj
1207 |         return {
1208 |             "tab_id": str(new_tab_obj.target.target_id),
1209 |             "url": getattr(new_tab_obj, 'url', '') or url,
1210 |             "title": getattr(new_tab_obj.target, 'title', '') or 'New Tab',
1211 |             "type": getattr(new_tab_obj.target, 'type_', 'page')
1212 |         }
1213 |     except Exception as e:
1214 |         raise Exception(f"Failed to create new tab: {str(e)}")
1215 | 
1216 | 
1217 | @section_tool("element-extraction")
1218 | async def extract_element_styles(
1219 |     instance_id: str,
1220 |     selector: str,
1221 |     include_computed: bool = True,
1222 |     include_css_rules: bool = True,
1223 |     include_pseudo: bool = True,
1224 |     include_inheritance: bool = False
1225 | ) -> Dict[str, Any]:
1226 |     """
1227 |     Extract complete styling information from an element.
1228 | 
1229 |     Args:
1230 |         instance_id (str): Browser instance ID.
1231 |         selector (str): CSS selector for the element.
1232 |         include_computed (bool): Include computed styles.
1233 |         include_css_rules (bool): Include matching CSS rules.
1234 |         include_pseudo (bool): Include pseudo-element styles (::before, ::after).
1235 |         include_inheritance (bool): Include style inheritance chain.
1236 | 
1237 |     Returns:
1238 |         Dict[str, Any]: Complete styling data including computed styles, CSS rules, pseudo-elements.
1239 |     """
1240 |     tab = await browser_manager.get_tab(instance_id)
1241 |     if not tab:
1242 |         raise Exception(f"Instance not found: {instance_id}")
1243 |     return await element_cloner.extract_element_styles(
1244 |         tab,
1245 |         selector=selector,
1246 |         include_computed=include_computed,
1247 |         include_css_rules=include_css_rules,
1248 |         include_pseudo=include_pseudo,
1249 |         include_inheritance=include_inheritance
1250 |     )
1251 | 
1252 | 
1253 | @section_tool("element-extraction")
1254 | async def extract_element_structure(
1255 |     instance_id: str,
1256 |     selector: str,
1257 |     include_children: bool = False,
1258 |     include_attributes: bool = True,
1259 |     include_data_attributes: bool = True,
1260 |     max_depth: int = 3
1261 | ) -> Dict[str, Any]:
1262 |     """
1263 |     Extract complete HTML structure and DOM information.
1264 | 
1265 |     Args:
1266 |         instance_id (str): Browser instance ID.
1267 |         selector (str): CSS selector for the element.
1268 |         include_children (bool): Include child elements.
1269 |         include_attributes (bool): Include all attributes.
1270 |         include_data_attributes (bool): Include data-* attributes specifically.
1271 |         max_depth (int): Maximum depth for children extraction.
1272 | 
1273 |     Returns:
1274 |         Dict[str, Any]: HTML structure, attributes, position, and children data.
1275 |     """
1276 |     tab = await browser_manager.get_tab(instance_id)
1277 |     if not tab:
1278 |         raise Exception(f"Instance not found: {instance_id}")
1279 |     return await element_cloner.extract_element_structure(
1280 |         tab,
1281 |         selector=selector,
1282 |         include_children=include_children,
1283 |         include_attributes=include_attributes,
1284 |         include_data_attributes=include_data_attributes,
1285 |         max_depth=max_depth
1286 |     )
1287 | 
1288 | 
1289 | @section_tool("element-extraction")
1290 | async def extract_element_events(
1291 |     instance_id: str,
1292 |     selector: str,
1293 |     include_inline: bool = True,
1294 |     include_listeners: bool = True,
1295 |     include_framework: bool = True,
1296 |     analyze_handlers: bool = False
1297 | ) -> Dict[str, Any]:
1298 |     """
1299 |     Extract complete event listener and JavaScript handler information.
1300 | 
1301 |     Args:
1302 |         instance_id (str): Browser instance ID.
1303 |         selector (str): CSS selector for the element.
1304 |         include_inline (bool): Include inline event handlers (onclick, etc.).
1305 |         include_listeners (bool): Include addEventListener attached handlers.
1306 |         include_framework (bool): Include framework-specific handlers (React, Vue, etc.).
1307 |         analyze_handlers (bool): Analyze handler functions for full details (can be large).
1308 | 
1309 |     Returns:
1310 |         Dict[str, Any]: Event listeners, inline handlers, framework handlers, detected frameworks.
1311 |     """
1312 |     tab = await browser_manager.get_tab(instance_id)
1313 |     if not tab:
1314 |         raise Exception(f"Instance not found: {instance_id}")
1315 |     return await element_cloner.extract_element_events(
1316 |         tab,
1317 |         selector=selector,
1318 |         include_inline=include_inline,
1319 |         include_listeners=include_listeners,
1320 |         include_framework=include_framework,
1321 |         analyze_handlers=analyze_handlers
1322 |     )
1323 | 
1324 | 
1325 | @section_tool("element-extraction")
1326 | async def extract_element_animations(
1327 |     instance_id: str,
1328 |     selector: str,
1329 |     include_css_animations: bool = True,
1330 |     include_transitions: bool = True,
1331 |     include_transforms: bool = True,
1332 |     analyze_keyframes: bool = True
1333 | ) -> Dict[str, Any]:
1334 |     """
1335 |     Extract CSS animations, transitions, and transforms.
1336 | 
1337 |     Args:
1338 |         instance_id (str): Browser instance ID.
1339 |         selector (str): CSS selector for the element.
1340 |         include_css_animations (bool): Include CSS @keyframes animations.
1341 |         include_transitions (bool): Include CSS transitions.
1342 |         include_transforms (bool): Include CSS transforms.
1343 |         analyze_keyframes (bool): Analyze keyframe rules.
1344 | 
1345 |     Returns:
1346 |         Dict[str, Any]: Animation data, transition data, transform data, keyframe rules.
1347 |     """
1348 |     tab = await browser_manager.get_tab(instance_id)
1349 |     if not tab:
1350 |         raise Exception(f"Instance not found: {instance_id}")
1351 |     return await element_cloner.extract_element_animations(
1352 |         tab,
1353 |         selector=selector,
1354 |         include_css_animations=include_css_animations,
1355 |         include_transitions=include_transitions,
1356 |         include_transforms=include_transforms,
1357 |         analyze_keyframes=analyze_keyframes
1358 |     )
1359 | 
1360 | 
1361 | @section_tool("element-extraction")
1362 | async def extract_element_assets(
1363 |     instance_id: str,
1364 |     selector: str,
1365 |     include_images: bool = True,
1366 |     include_backgrounds: bool = True,
1367 |     include_fonts: bool = True,
1368 |     fetch_external: bool = False
1369 | ) -> Dict[str, Any]:
1370 |     """
1371 |     Extract all assets related to an element (images, fonts, etc.).
1372 | 
1373 |     Args:
1374 |         instance_id (str): Browser instance ID.
1375 |         selector (str): CSS selector for the element.
1376 |         include_images (bool): Include img src and related images.
1377 |         include_backgrounds (bool): Include background images.
1378 |         include_fonts (bool): Include font information.
1379 |         fetch_external (bool): Whether to fetch external assets for analysis.
1380 | 
1381 |     Returns:
1382 |         Dict[str, Any]: Images, background images, fonts, icons, videos, audio assets.
1383 |     """
1384 |     tab = await browser_manager.get_tab(instance_id)
1385 |     if not tab:
1386 |         raise Exception(f"Instance not found: {instance_id}")
1387 |     result = await element_cloner.extract_element_assets(
1388 |         tab,
1389 |         selector=selector,
1390 |         include_images=include_images,
1391 |         include_backgrounds=include_backgrounds,
1392 |         include_fonts=include_fonts,
1393 |         fetch_external=fetch_external
1394 |     )
1395 |     return await response_handler.handle_response(result, f"element_assets_{instance_id}_{selector.replace(' ', '_')}")
1396 | 
1397 | 
1398 | @section_tool("element-extraction")
1399 | async def extract_element_styles_cdp(
1400 |     instance_id: str,
1401 |     selector: str,
1402 |     include_computed: bool = True,
1403 |     include_css_rules: bool = True,
1404 |     include_pseudo: bool = True,
1405 |     include_inheritance: bool = False,
1406 | ) -> Dict[str, Any]:
1407 |     """
1408 |     Extract element styles using direct CDP calls (no JavaScript evaluation).
1409 |     This prevents hanging issues by using nodriver's native CDP methods.
1410 |     
1411 |     Args:
1412 |         instance_id (str): Browser instance ID
1413 |         selector (str): CSS selector for the element
1414 |         include_computed (bool): Include computed styles
1415 |         include_css_rules (bool): Include matching CSS rules
1416 |         include_pseudo (bool): Include pseudo-element styles
1417 |         include_inheritance (bool): Include style inheritance chain
1418 |     
1419 |     Returns:
1420 |         Dict[str, Any]: Styling data extracted using CDP
1421 |     """
1422 |     tab = await browser_manager.get_tab(instance_id)
1423 |     if not tab:
1424 |         raise Exception(f"Instance not found: {instance_id}")
1425 |     return await element_cloner.extract_element_styles_cdp(
1426 |         tab,
1427 |         selector=selector,
1428 |         include_computed=include_computed,
1429 |         include_css_rules=include_css_rules,
1430 |         include_pseudo=include_pseudo,
1431 |         include_inheritance=include_inheritance
1432 |     )
1433 | 
1434 | 
1435 | @section_tool("element-extraction")
1436 | async def extract_related_files(
1437 |     instance_id: str,
1438 |     analyze_css: bool = True,
1439 |     analyze_js: bool = True,
1440 |     follow_imports: bool = False,
1441 |     max_depth: int = 2
1442 | ) -> Dict[str, Any]:
1443 |     """
1444 |     Discover and analyze related CSS/JS files for context.
1445 | 
1446 |     Args:
1447 |         instance_id (str): Browser instance ID.
1448 |         analyze_css (bool): Analyze linked CSS files.
1449 |         analyze_js (bool): Analyze linked JS files.
1450 |         follow_imports (bool): Follow @import and module imports (uses network).
1451 |         max_depth (int): Maximum depth for following imports.
1452 | 
1453 |     Returns:
1454 |         Dict[str, Any]: Stylesheets, scripts, imports, modules, framework detection.
1455 |     """
1456 |     tab = await browser_manager.get_tab(instance_id)
1457 |     if not tab:
1458 |         raise Exception(f"Instance not found: {instance_id}")
1459 |     result = await element_cloner.extract_related_files(
1460 |         tab,
1461 |         analyze_css=analyze_css,
1462 |         analyze_js=analyze_js,
1463 |         follow_imports=follow_imports,
1464 |         max_depth=max_depth
1465 |     )
1466 |     return await response_handler.handle_response(result, f"related_files_{instance_id}")
1467 | 
1468 | 
1469 | @section_tool("element-extraction")
1470 | async def clone_element_complete(
1471 |     instance_id: str,
1472 |     selector: str,
1473 |     extraction_options: Optional[str] = None
1474 | ) -> Dict[str, Any]:
1475 |     """
1476 |     Master function that extracts ALL element data using specialized functions.
1477 | 
1478 |     This is the ultimate element cloning tool that combines all extraction methods.
1479 |     Use this when you want complete element fidelity for recreation or analysis.
1480 | 
1481 |     Args:
1482 |         instance_id (str): Browser instance ID.
1483 |         selector (str): CSS selector for the element.
1484 |         extraction_options (Optional[str]): Dict specifying what to extract and options for each.
1485 |             Example: {
1486 |                 'styles': {'include_computed': True, 'include_pseudo': True},
1487 |                 'structure': {'include_children': True, 'max_depth': 2},
1488 |                 'events': {'include_framework': True, 'analyze_handlers': False},
1489 |                 'animations': {'analyze_keyframes': True},
1490 |                 'assets': {'fetch_external': False},
1491 |                 'related_files': {'follow_imports': True, 'max_depth': 1}
1492 |             }
1493 | 
1494 |     Returns:
1495 |         Dict[str, Any]: Complete element clone with styles, structure, events, animations, assets, related files.
1496 |     """
1497 |     parsed_options = None
1498 |     if extraction_options:
1499 |         try:
1500 |             parsed_options = json.loads(extraction_options)
1501 |         except json.JSONDecodeError:
1502 |             raise Exception(f"Invalid JSON in extraction_options: {extraction_options}")
1503 |     tab = await browser_manager.get_tab(instance_id)
1504 |     if not tab:
1505 |         raise Exception(f"Instance not found: {instance_id}")
1506 |     result = await comprehensive_element_cloner.extract_complete_element(
1507 |         tab,
1508 |         selector=selector,
1509 |         include_children=parsed_options.get('structure', {}).get('include_children', True) if parsed_options else True
1510 |     )
1511 |     
1512 |     return response_handler.handle_response(
1513 |         result,
1514 |         fallback_filename_prefix="complete_clone",
1515 |         metadata={
1516 |             "selector": selector,
1517 |             "extraction_options": parsed_options,
1518 |             "url": getattr(tab, 'url', 'unknown')
1519 |         }
1520 |     )
1521 | 
1522 | 
1523 | @section_tool("debugging")
1524 | async def hot_reload() -> str:
1525 |     """
1526 |     Hot reload all modules without restarting the server.
1527 | 
1528 |     Returns:
1529 |         str: Status message.
1530 |     """
1531 |     try:
1532 |         modules_to_reload = [
1533 |             'browser_manager',
1534 |             'network_interceptor',
1535 |             'dom_handler',
1536 |             'debug_logger',
1537 |             'models'
1538 |         ]
1539 |         reloaded_modules = []
1540 |         for module_name in modules_to_reload:
1541 |             if module_name in sys.modules:
1542 |                 importlib.reload(sys.modules[module_name])
1543 |                 reloaded_modules.append(module_name)
1544 |                 if module_name == 'browser_manager':
1545 |                     global browser_manager, BrowserManager
1546 |                     browser_manager = BrowserManager()
1547 |                 elif module_name == 'network_interceptor':
1548 |                     global network_interceptor, NetworkInterceptor
1549 |                     network_interceptor = NetworkInterceptor()
1550 |                 elif module_name == 'dom_handler':
1551 |                     global dom_handler, DOMHandler
1552 |                     dom_handler = DOMHandler()
1553 |                 elif module_name == 'debug_logger':
1554 |                     global debug_logger
1555 |                     from debug_logger import debug_logger
1556 |         return f"✅ Hot reload completed! Reloaded modules: {', '.join(reloaded_modules)}"
1557 |     except Exception as e:
1558 |         return f"❌ Hot reload failed: {str(e)}"
1559 | 
1560 | 
1561 | @section_tool("debugging")
1562 | async def reload_status() -> str:
1563 |     """
1564 |     Check the status of loaded modules.
1565 | 
1566 |     Returns:
1567 |         str: Module status information.
1568 |     """
1569 |     try:
1570 |         modules_info = []
1571 |         modules_to_check = [
1572 |             'browser_manager',
1573 |             'network_interceptor',
1574 |             'dom_handler',
1575 |             'debug_logger',
1576 |             'models',
1577 |             'persistent_storage'
1578 |         ]
1579 |         for module_name in modules_to_check:
1580 |             if module_name in sys.modules:
1581 |                 module = sys.modules[module_name]
1582 |                 modules_info.append(f"✅ {module_name}: {getattr(module, '__file__', 'built-in')}")
1583 |             else:
1584 |                 modules_info.append(f"❌ {module_name}: Not loaded")
1585 |         return "\n".join(modules_info)
1586 |     except Exception as e:
1587 |         return f"Error checking module status: {str(e)}"
1588 | 
1589 | 
1590 | @section_tool("debugging")
1591 | async def validate_browser_environment_tool() -> Dict[str, Any]:
1592 |     """
1593 |     Validate browser environment and diagnose potential issues.
1594 |     
1595 |     Returns:
1596 |         Dict[str, Any]: Environment validation results with platform info and recommendations
1597 |     """
1598 |     try:
1599 |         return validate_browser_environment()
1600 |     except Exception as e:
1601 |         return {
1602 |             "error": str(e),
1603 |             "platform_info": get_platform_info(),
1604 |             "is_ready": False,
1605 |             "issues": [f"Validation failed: {str(e)}"],
1606 |             "warnings": []
1607 |         }
1608 | 
1609 | 
1610 | @section_tool("progressive-cloning")
1611 | async def clone_element_progressive(
1612 |     instance_id: str,
1613 |     selector: str,
1614 |     include_children: bool = True
1615 | ) -> Dict[str, Any]:
1616 |     """
1617 |     Clone element progressively - returns lightweight base structure with element_id.
1618 | 
1619 |     Args:
1620 |         instance_id (str): Browser instance ID.
1621 |         selector (str): CSS selector for the element.
1622 |         include_children (bool): Whether to extract child elements.
1623 | 
1624 |     Returns:
1625 |         Dict[str, Any]: Base structure with element_id for progressive expansion.
1626 |     """
1627 |     tab = await browser_manager.get_tab(instance_id)
1628 |     if not tab:
1629 |         raise Exception(f"Instance not found: {instance_id}")
1630 |     return await progressive_element_cloner.clone_element_progressive(tab, selector, include_children)
1631 | 
1632 | 
1633 | @section_tool("progressive-cloning")
1634 | async def expand_styles(
1635 |     element_id: str,
1636 |     categories: Optional[List[str]] = None,
1637 |     properties: Optional[List[str]] = None
1638 | ) -> Dict[str, Any]:
1639 |     """
1640 |     Expand styles data for a stored element.
1641 | 
1642 |     Args:
1643 |         element_id (str): Element ID from clone_element_progressive().
1644 |         categories (Optional[List[str]]): Style categories to include (layout, typography, colors, spacing, borders, backgrounds, effects, animation).
1645 |         properties (Optional[List[str]]): Specific CSS property names to include.
1646 | 
1647 |     Returns:
1648 |         Dict[str, Any]: Filtered styles data.
1649 |     """
1650 |     return progressive_element_cloner.expand_styles(element_id, categories, properties)
1651 | 
1652 | 
1653 | @section_tool("progressive-cloning")
1654 | async def expand_events(
1655 |     element_id: str,
1656 |     event_types: Optional[List[str]] = None
1657 | ) -> Dict[str, Any]:
1658 |     """
1659 |     Expand event listeners data for a stored element.
1660 | 
1661 |     Args:
1662 |         element_id (str): Element ID from clone_element_progressive().
1663 |         event_types (Optional[List[str]]): Event types or sources to include (click, react, inline, addEventListener).
1664 | 
1665 |     Returns:
1666 |         Dict[str, Any]: Filtered event listeners data.
1667 |     """
1668 |     return progressive_element_cloner.expand_events(element_id, event_types)
1669 | 
1670 | 
1671 | @section_tool("progressive-cloning")
1672 | async def expand_children(
1673 |     element_id: str,
1674 |     depth_range: Optional[List] = None,
1675 |     max_count: Optional[Any] = None
1676 | ) -> Dict[str, Any]:
1677 |     """
1678 |     Expand children data for a stored element.
1679 | 
1680 |     Args:
1681 |         element_id (str): Element ID from clone_element_progressive().
1682 |         depth_range (Optional[List]): [min_depth, max_depth] range to include.
1683 |         max_count (Optional[Any]): Maximum number of children to return.
1684 | 
1685 |     Returns:
1686 |         Dict[str, Any]: Filtered children data.
1687 |     """
1688 |     if isinstance(max_count, str):
1689 |         try:
1690 |             max_count = int(max_count) if max_count else None
1691 |         except ValueError:
1692 |             return {"error": f"Invalid max_count value: {max_count}"}
1693 |     
1694 |     if isinstance(depth_range, list):
1695 |         try:
1696 |             depth_range = [int(x) if isinstance(x, str) else x for x in depth_range]
1697 |         except ValueError:
1698 |             return {"error": f"Invalid depth_range values: {depth_range}"}
1699 |     
1700 |     depth_tuple = tuple(depth_range) if depth_range else None
1701 | 
1702 |     result = progressive_element_cloner.expand_children(element_id, depth_tuple, max_count)
1703 |     return response_handler.handle_response(result, f"expand_children_{element_id}")
1704 | 
1705 | 
1706 | @section_tool("progressive-cloning")
1707 | async def expand_css_rules(
1708 |     element_id: str,
1709 |     source_types: Optional[List[str]] = None
1710 | ) -> Dict[str, Any]:
1711 |     """
1712 |     Expand CSS rules data for a stored element.
1713 | 
1714 |     Args:
1715 |         element_id (str): Element ID from clone_element_progressive().
1716 |         source_types (Optional[List[str]]): CSS rule sources to include (inline, external stylesheet URLs).
1717 | 
1718 |     Returns:
1719 |         Dict[str, Any]: Filtered CSS rules data.
1720 |     """
1721 |     return progressive_element_cloner.expand_css_rules(element_id, source_types)
1722 | 
1723 | 
1724 | @section_tool("progressive-cloning")
1725 | async def expand_pseudo_elements(
1726 |     element_id: str
1727 | ) -> Dict[str, Any]:
1728 |     """
1729 |     Expand pseudo-elements data for a stored element.
1730 | 
1731 |     Args:
1732 |         element_id (str): Element ID from clone_element_progressive().
1733 | 
1734 |     Returns:
1735 |         Dict[str, Any]: Pseudo-elements data (::before, ::after, etc.).
1736 |     """
1737 |     return progressive_element_cloner.expand_pseudo_elements(element_id)
1738 | 
1739 | 
1740 | @section_tool("progressive-cloning")
1741 | async def expand_animations(
1742 |     element_id: str
1743 | ) -> Dict[str, Any]:
1744 |     """
1745 |     Expand animations and fonts data for a stored element.
1746 | 
1747 |     Args:
1748 |         element_id (str): Element ID from clone_element_progressive().
1749 | 
1750 |     Returns:
1751 |         Dict[str, Any]: Animations, transitions, and fonts data.
1752 |     """
1753 |     return progressive_element_cloner.expand_animations(element_id)
1754 | 
1755 | 
1756 | @section_tool("progressive-cloning")
1757 | async def list_stored_elements() -> Dict[str, Any]:
1758 |     """
1759 |     List all stored elements with their basic info.
1760 | 
1761 |     Returns:
1762 |         Dict[str, Any]: List of stored elements with metadata.
1763 |     """
1764 |     return progressive_element_cloner.list_stored_elements()
1765 | 
1766 | 
1767 | @section_tool("progressive-cloning")
1768 | async def clear_stored_element(
1769 |     element_id: str
1770 | ) -> Dict[str, Any]:
1771 |     """
1772 |     Clear a specific stored element.
1773 | 
1774 |     Args:
1775 |         element_id (str): Element ID to clear.
1776 | 
1777 |     Returns:
1778 |         Dict[str, Any]: Success/error message.
1779 |     """
1780 |     return progressive_element_cloner.clear_stored_element(element_id)
1781 | 
1782 | 
1783 | @section_tool("progressive-cloning")
1784 | async def clear_all_elements() -> Dict[str, Any]:
1785 |     """
1786 |     Clear all stored elements.
1787 | 
1788 |     Returns:
1789 |         Dict[str, Any]: Success message.
1790 |     """
1791 |     return progressive_element_cloner.clear_all_elements()
1792 | 
1793 | 
1794 | @section_tool("file-extraction")
1795 | async def clone_element_to_file(
1796 |     instance_id: str,
1797 |     selector: str,
1798 |     extraction_options: Optional[str] = None
1799 | ) -> Dict[str, Any]:
1800 |     """
1801 |     Clone element completely and save to file, returning file path instead of full data.
1802 | 
1803 |     This is ideal when you want complete element data but don't want to overwhelm
1804 |     the response with large JSON objects. The data is saved to a JSON file that
1805 |     can be read later.
1806 | 
1807 |     Args:
1808 |         instance_id (str): Browser instance ID.
1809 |         selector (str): CSS selector for the element.
1810 |         extraction_options (Optional[str]): JSON string with extraction options.
1811 | 
1812 |     Returns:
1813 |         Dict[str, Any]: File path and summary information about the cloned element.
1814 |     """
1815 |     tab = await browser_manager.get_tab(instance_id)
1816 |     if not tab:
1817 |         raise Exception(f"Instance not found: {instance_id}")
1818 |     parsed_options = None
1819 |     if extraction_options:
1820 |         try:
1821 |             parsed_options = json.loads(extraction_options)
1822 |         except json.JSONDecodeError:
1823 |             return {"error": "Invalid extraction_options JSON"}
1824 |     return await file_based_element_cloner.clone_element_complete_to_file(
1825 |         tab, selector=selector, extraction_options=parsed_options
1826 |     )
1827 | 
1828 | 
1829 | @section_tool("file-extraction")
1830 | async def extract_complete_element_to_file(
1831 |     instance_id: str,
1832 |     selector: str,
1833 |     include_children: bool = True
1834 | ) -> Dict[str, Any]:
1835 |     """
1836 |     Extract complete element using working comprehensive cloner and save to file.
1837 | 
1838 |     This uses the proven comprehensive extraction logic that returns large amounts
1839 |     of data, but saves it to a file instead of overwhelming the response.
1840 | 
1841 |     Args:
1842 |         instance_id (str): Browser instance ID.
1843 |         selector (str): CSS selector for the element.
1844 |         include_children (bool): Whether to include child elements.
1845 | 
1846 |     Returns:
1847 |         Dict[str, Any]: File path and concise summary instead of massive data dump.
1848 |     """
1849 |     tab = await browser_manager.get_tab(instance_id)
1850 |     if not tab:
1851 |         raise Exception(f"Instance not found: {instance_id}")
1852 |     return await file_based_element_cloner.extract_complete_element_to_file(
1853 |         tab, selector, include_children
1854 |     )
1855 | 
1856 | 
1857 | @section_tool("element-extraction")
1858 | async def extract_complete_element_cdp(
1859 |     instance_id: str,
1860 |     selector: str,
1861 |     include_children: bool = True
1862 | ) -> Dict[str, Any]:
1863 |     """
1864 |     Extract complete element using native CDP methods for 100% accuracy.
1865 | 
1866 |     This uses Chrome DevTools Protocol's native methods to extract:
1867 |     - Complete computed styles via CSS.getComputedStyleForNode
1868 |     - Matched CSS rules via CSS.getMatchedStylesForNode  
1869 |     - Event listeners via DOMDebugger.getEventListeners
1870 |     - Complete DOM structure and attributes
1871 | 
1872 |     This provides the most accurate element cloning possible by bypassing
1873 |     JavaScript limitations and using CDP's direct browser access.
1874 | 
1875 |     Args:
1876 |         instance_id (str): Browser instance ID.
1877 |         selector (str): CSS selector for the element.
1878 |         include_children (bool): Whether to include child elements.
1879 | 
1880 |     Returns:
1881 |         Dict[str, Any]: Complete element data with 100% accuracy.
1882 |     """
1883 |     tab = await browser_manager.get_tab(instance_id)
1884 |     if not tab:
1885 |         raise Exception(f"Instance not found: {instance_id}")
1886 |     cdp_cloner = CDPElementCloner()
1887 |     return await cdp_cloner.extract_complete_element_cdp(tab, selector, include_children)
1888 | 
1889 | 
1890 | @section_tool("file-extraction")
1891 | async def extract_element_styles_to_file(
1892 |     instance_id: str,
1893 |     selector: str,
1894 |     include_computed: bool = True,
1895 |     include_css_rules: bool = True,
1896 |     include_pseudo: bool = True,
1897 |     include_inheritance: bool = False
1898 | ) -> Dict[str, Any]:
1899 |     """
1900 |     Extract element styles and save to file, returning file path.
1901 | 
1902 |     Args:
1903 |         instance_id (str): Browser instance ID.
1904 |         selector (str): CSS selector for the element.
1905 |         include_computed (bool): Include computed styles.
1906 |         include_css_rules (bool): Include matching CSS rules.
1907 |         include_pseudo (bool): Include pseudo-element styles.
1908 |         include_inheritance (bool): Include style inheritance chain.
1909 | 
1910 |     Returns:
1911 |         Dict[str, Any]: File path and summary of extracted styles.
1912 |     """
1913 |     tab = await browser_manager.get_tab(instance_id)
1914 |     if not tab:
1915 |         raise Exception(f"Instance not found: {instance_id}")
1916 |     return await file_based_element_cloner.extract_element_styles_to_file(
1917 |         tab,
1918 |         selector=selector,
1919 |         include_computed=include_computed,
1920 |         include_css_rules=include_css_rules,
1921 |         include_pseudo=include_pseudo,
1922 |         include_inheritance=include_inheritance
1923 |     )
1924 | 
1925 | 
1926 | @section_tool("file-extraction")
1927 | async def extract_element_structure_to_file(
1928 |     instance_id: str,
1929 |     selector: str,
1930 |     include_children: bool = False,
1931 |     include_attributes: bool = True,
1932 |     include_data_attributes: bool = True,
1933 |     max_depth: int = 3
1934 | ) -> Dict[str, Any]:
1935 |     """
1936 |     Extract element structure and save to file, returning file path.
1937 | 
1938 |     Args:
1939 |         instance_id (str): Browser instance ID.
1940 |         selector (str): CSS selector for the element.
1941 |         include_children (bool): Include child elements.
1942 |         include_attributes (bool): Include all attributes.
1943 |         include_data_attributes (bool): Include data-* attributes.
1944 |         max_depth (int): Maximum depth for children extraction.
1945 | 
1946 |     Returns:
1947 |         Dict[str, Any]: File path and summary of extracted structure.
1948 |     """
1949 |     tab = await browser_manager.get_tab(instance_id)
1950 |     if not tab:
1951 |         raise Exception(f"Instance not found: {instance_id}")
1952 |     return await file_based_element_cloner.extract_element_structure_to_file(
1953 |         tab,
1954 |         selector=selector,
1955 |         include_children=include_children,
1956 |         include_attributes=include_attributes,
1957 |         include_data_attributes=include_data_attributes,
1958 |         max_depth=max_depth
1959 |     )
1960 | 
1961 | 
1962 | @section_tool("file-extraction")
1963 | async def extract_element_events_to_file(
1964 |     instance_id: str,
1965 |     selector: str,
1966 |     include_inline: bool = True,
1967 |     include_listeners: bool = True,
1968 |     include_framework: bool = True,
1969 |     analyze_handlers: bool = True
1970 | ) -> Dict[str, Any]:
1971 |     """
1972 |     Extract element events and save to file, returning file path.
1973 | 
1974 |     Args:
1975 |         instance_id (str): Browser instance ID.
1976 |         selector (str): CSS selector for the element.
1977 |         include_inline (bool): Include inline event handlers.
1978 |         include_listeners (bool): Include addEventListener handlers.
1979 |         include_framework (bool): Include framework-specific handlers.
1980 |         analyze_handlers (bool): Analyze handler functions.
1981 | 
1982 |     Returns:
1983 |         Dict[str, Any]: File path and summary of extracted events.
1984 |     """
1985 |     tab = await browser_manager.get_tab(instance_id)
1986 |     if not tab:
1987 |         raise Exception(f"Instance not found: {instance_id}")
1988 |     return await file_based_element_cloner.extract_element_events_to_file(
1989 |         tab,
1990 |         selector=selector,
1991 |         include_inline=include_inline,
1992 |         include_listeners=include_listeners,
1993 |         include_framework=include_framework,
1994 |         analyze_handlers=analyze_handlers
1995 |     )
1996 | 
1997 | 
1998 | @section_tool("file-extraction")
1999 | async def extract_element_animations_to_file(
2000 |     instance_id: str,
2001 |     selector: str,
2002 |     include_css_animations: bool = True,
2003 |     include_transitions: bool = True,
2004 |     include_transforms: bool = True,
2005 |     analyze_keyframes: bool = True
2006 | ) -> Dict[str, Any]:
2007 |     """
2008 |     Extract element animations and save to file, returning file path.
2009 | 
2010 |     Args:
2011 |         instance_id (str): Browser instance ID.
2012 |         selector (str): CSS selector for the element.
2013 |         include_css_animations (bool): Include CSS animations.
2014 |         include_transitions (bool): Include CSS transitions.
2015 |         include_transforms (bool): Include CSS transforms.
2016 |         analyze_keyframes (bool): Analyze keyframe rules.
2017 | 
2018 |     Returns:
2019 |         Dict[str, Any]: File path and summary of extracted animations.
2020 |     """
2021 |     tab = await browser_manager.get_tab(instance_id)
2022 |     if not tab:
2023 |         raise Exception(f"Instance not found: {instance_id}")
2024 |     return await file_based_element_cloner.extract_element_animations_to_file(
2025 |         tab,
2026 |         selector=selector,
2027 |         include_css_animations=include_css_animations,
2028 |         include_transitions=include_transitions,
2029 |         include_transforms=include_transforms,
2030 |         analyze_keyframes=analyze_keyframes
2031 |     )
2032 | 
2033 | 
2034 | @section_tool("file-extraction")
2035 | async def extract_element_assets_to_file(
2036 |     instance_id: str,
2037 |     selector: str,
2038 |     include_images: bool = True,
2039 |     include_backgrounds: bool = True,
2040 |     include_fonts: bool = True,
2041 |     fetch_external: bool = False
2042 | ) -> Dict[str, Any]:
2043 |     """
2044 |     Extract element assets and save to file, returning file path.
2045 | 
2046 |     Args:
2047 |         instance_id (str): Browser instance ID.
2048 |         selector (str): CSS selector for the element.
2049 |         include_images (bool): Include images.
2050 |         include_backgrounds (bool): Include background images.
2051 |         include_fonts (bool): Include font information.
2052 |         fetch_external (bool): Fetch external assets.
2053 | 
2054 |     Returns:
2055 |         Dict[str, Any]: File path and summary of extracted assets.
2056 |     """
2057 |     tab = await browser_manager.get_tab(instance_id)
2058 |     if not tab:
2059 |         raise Exception(f"Instance not found: {instance_id}")
2060 |     return await file_based_element_cloner.extract_element_assets_to_file(
2061 |         tab,
2062 |         selector=selector,
2063 |         include_images=include_images,
2064 |         include_backgrounds=include_backgrounds,
2065 |         include_fonts=include_fonts,
2066 |         fetch_external=fetch_external
2067 |     )
2068 | 
2069 | 
2070 | @section_tool("file-extraction")
2071 | async def list_clone_files() -> List[Dict[str, Any]]:
2072 |     """
2073 |     List all element clone files saved to disk.
2074 | 
2075 |     Returns:
2076 |         List[Dict[str, Any]]: List of clone files with metadata and file information.
2077 |     """
2078 |     return file_based_element_cloner.list_clone_files()
2079 | 
2080 | 
2081 | @section_tool("file-extraction")
2082 | async def cleanup_clone_files(
2083 |     max_age_hours: int = 24
2084 | ) -> Dict[str, int]:
2085 |     """
2086 |     Clean up old clone files to save disk space.
2087 | 
2088 |     Args:
2089 |         max_age_hours (int): Maximum age in hours for files to keep.
2090 | 
2091 |     Returns:
2092 |         Dict[str, int]: Number of files deleted.
2093 |     """
2094 |     deleted_count = file_based_element_cloner.cleanup_old_files(max_age_hours)
2095 |     return {"deleted_count": deleted_count}
2096 | 
2097 | 
2098 | @section_tool("cdp-functions")
2099 | async def list_cdp_commands() -> List[str]:
2100 |     """
2101 |     List all available CDP Runtime commands for function execution.
2102 | 
2103 |     Returns:
2104 |         List[str]: List of available CDP command names.
2105 |     """
2106 |     return await cdp_function_executor.list_cdp_commands()
2107 | 
2108 | 
2109 | @section_tool("cdp-functions")
2110 | async def execute_cdp_command(
2111 |     instance_id: str,
2112 |     command: str,
2113 |     params: Dict[str, Any] = None
2114 | ) -> Dict[str, Any]:
2115 |     """
2116 |     Execute any CDP Runtime command with given parameters.
2117 | 
2118 |     Args:
2119 |         instance_id (str): Browser instance ID.
2120 |         command (str): CDP command name (e.g., 'evaluate', 'callFunctionOn').
2121 |         params (Dict[str, Any], optional): Command parameters as a dictionary.
2122 |                 IMPORTANT: Use snake_case parameter names (e.g., 'return_by_value') 
2123 |                 NOT camelCase ('returnByValue'). The nodriver library expects 
2124 |                 Python-style parameter names.
2125 | 
2126 |     Returns:
2127 |         Dict[str, Any]: Command execution result.
2128 |         
2129 |     Example:
2130 |         # Correct - use snake_case
2131 |         params = {"expression": "document.title", "return_by_value": True}
2132 |         
2133 |         params = {"expression": "document.title", "returnByValue": True}
2134 |     """
2135 |     tab = await browser_manager.get_tab(instance_id)
2136 |     if not tab:
2137 |         return {"success": False, "error": f"Instance not found: {instance_id}"}
2138 |     return await cdp_function_executor.execute_cdp_command(tab, command, params or {})
2139 | 
2140 | 
2141 | @section_tool("cdp-functions")
2142 | async def get_execution_contexts(
2143 |     instance_id: str
2144 | ) -> List[Dict[str, Any]]:
2145 |     """
2146 |     Get all available JavaScript execution contexts.
2147 | 
2148 |     Args:
2149 |         instance_id (str): Browser instance ID.
2150 | 
2151 |     Returns:
2152 |         List[Dict[str, Any]]: List of execution contexts with their details.
2153 |     """
2154 |     tab = await browser_manager.get_tab(instance_id)
2155 |     if not tab:
2156 |         return []
2157 |     contexts = await cdp_function_executor.get_execution_contexts(tab)
2158 |     return [
2159 |         {
2160 |             "id": ctx.id,
2161 |             "name": ctx.name,
2162 |             "origin": ctx.origin,
2163 |             "unique_id": ctx.unique_id,
2164 |             "aux_data": ctx.aux_data
2165 |         }
2166 |         for ctx in contexts
2167 |     ]
2168 | 
2169 | 
2170 | @section_tool("cdp-functions")
2171 | async def discover_global_functions(
2172 |     instance_id: str,
2173 |     context_id: str = None
2174 | ) -> List[Dict[str, Any]]:
2175 |     """
2176 |     Discover all global JavaScript functions available in the page.
2177 | 
2178 |     Args:
2179 |         instance_id (str): Browser instance ID.
2180 |         context_id (str, optional): Optional execution context ID.
2181 | 
2182 |     Returns:
2183 |         List[Dict[str, Any]]: List of discovered functions with their details.
2184 |     """
2185 |     tab = await browser_manager.get_tab(instance_id)
2186 |     if not tab:
2187 |         return []
2188 |     functions = await cdp_function_executor.discover_global_functions(tab, context_id)
2189 |     result = [
2190 |         {
2191 |             "name": func.name,
2192 |             "path": func.path,
2193 |             "signature": func.signature,
2194 |             "description": func.description
2195 |         }
2196 |         for func in functions
2197 |     ]
2198 |     
2199 |     file_response = response_handler.handle_response(
2200 |         result,
2201 |         fallback_filename_prefix="global_functions",
2202 |         metadata={
2203 |             "context_id": context_id,
2204 |             "function_count": len(result),
2205 |             "url": getattr(tab, 'url', 'unknown')
2206 |         }
2207 |     )
2208 |     
2209 |     if isinstance(file_response, dict) and "file_path" in file_response:
2210 |         return [{
2211 |             "name": "LARGE_RESPONSE_SAVED_TO_FILE",
2212 |             "path": "file_storage",
2213 |             "signature": "automatic_file_fallback",
2214 |             "description": f"Response too large ({file_response['estimated_tokens']} tokens), saved to: {file_response['filename']}"
2215 |         }]
2216 |     
2217 |     return file_response
2218 | 
2219 | 
2220 | @section_tool("cdp-functions")
2221 | async def discover_object_methods(
2222 |     instance_id: str,
2223 |     object_path: str
2224 | ) -> List[Dict[str, Any]]:
2225 |     """
2226 |     Discover methods of a specific JavaScript object.
2227 | 
2228 |     Args:
2229 |         instance_id (str): Browser instance ID.
2230 |         object_path (str): Path to the object (e.g., 'document', 'window.localStorage').
2231 | 
2232 |     Returns:
2233 |         List[Dict[str, Any]]: List of discovered methods.
2234 |     """
2235 |     tab = await browser_manager.get_tab(instance_id)
2236 |     if not tab:
2237 |         return []
2238 |     methods = await cdp_function_executor.discover_object_methods(tab, object_path)
2239 |     methods_data = [
2240 |         {
2241 |             "name": method.name,
2242 |             "path": method.path,
2243 |             "signature": method.signature,
2244 |             "description": method.description
2245 |         }
2246 |         for method in methods
2247 |     ]
2248 |     
2249 |     return await response_handler.handle_response(
2250 |         methods_data,
2251 |         f"object_methods_{object_path.replace('.', '_')}"
2252 |     )
2253 | 
2254 | 
2255 | @section_tool("cdp-functions")
2256 | async def call_javascript_function(
2257 |     instance_id: str,
2258 |     function_path: str,
2259 |     args: List[Any] = None
2260 | ) -> Dict[str, Any]:
2261 |     """
2262 |     Call a JavaScript function with arguments.
2263 | 
2264 |     Args:
2265 |         instance_id (str): Browser instance ID.
2266 |         function_path (str): Full path to the function (e.g., 'document.getElementById').
2267 |         args (List[Any], optional): List of arguments to pass to the function.
2268 | 
2269 |     Returns:
2270 |         Dict[str, Any]: Function call result.
2271 |     """
2272 |     tab = await browser_manager.get_tab(instance_id)
2273 |     if not tab:
2274 |         return {"success": False, "error": f"Instance not found: {instance_id}"}
2275 |     return await cdp_function_executor.call_discovered_function(tab, function_path, args or [])
2276 | 
2277 | 
2278 | @section_tool("cdp-functions")
2279 | async def inspect_function_signature(
2280 |     instance_id: str,
2281 |     function_path: str
2282 | ) -> Dict[str, Any]:
2283 |     """
2284 |     Inspect a JavaScript function's signature and details.
2285 | 
2286 |     Args:
2287 |         instance_id (str): Browser instance ID.
2288 |         function_path (str): Full path to the function.
2289 | 
2290 |     Returns:
2291 |         Dict[str, Any]: Function signature and details.
2292 |     """
2293 |     tab = await browser_manager.get_tab(instance_id)
2294 |     if not tab:
2295 |         return {"success": False, "error": f"Instance not found: {instance_id}"}
2296 |     return await cdp_function_executor.inspect_function_signature(tab, function_path)
2297 | 
2298 | 
2299 | @section_tool("cdp-functions")
2300 | async def inject_and_execute_script(
2301 |     instance_id: str,
2302 |     script_code: str,
2303 |     context_id: str = None
2304 | ) -> Dict[str, Any]:
2305 |     """
2306 |     Inject and execute custom JavaScript code.
2307 | 
2308 |     Args:
2309 |         instance_id (str): Browser instance ID.
2310 |         script_code (str): JavaScript code to execute.
2311 |         context_id (str, optional): Optional execution context ID.
2312 | 
2313 |     Returns:
2314 |         Dict[str, Any]: Script execution result.
2315 |     """
2316 |     tab = await browser_manager.get_tab(instance_id)
2317 |     if not tab:
2318 |         return {"success": False, "error": f"Instance not found: {instance_id}"}
2319 |     return await cdp_function_executor.inject_and_execute_script(tab, script_code, context_id)
2320 | 
2321 | 
2322 | @section_tool("cdp-functions")
2323 | async def create_persistent_function(
2324 |     instance_id: str,
2325 |     function_name: str,
2326 |     function_code: str
2327 | ) -> Dict[str, Any]:
2328 |     """
2329 |     Create a persistent JavaScript function that survives page reloads.
2330 | 
2331 |     Args:
2332 |         instance_id (str): Browser instance ID.
2333 |         function_name (str): Name for the function.
2334 |         function_code (str): JavaScript function code.
2335 | 
2336 |     Returns:
2337 |         Dict[str, Any]: Function creation result.
2338 |     """
2339 |     tab = await browser_manager.get_tab(instance_id)
2340 |     if not tab:
2341 |         return {"success": False, "error": f"Instance not found: {instance_id}"}
2342 |     return await cdp_function_executor.create_persistent_function(tab, function_name, function_code, instance_id)
2343 | 
2344 | 
2345 | @section_tool("cdp-functions")
2346 | async def execute_function_sequence(
2347 |     instance_id: str,
2348 |     function_calls: List[Dict[str, Any]]
2349 | ) -> List[Dict[str, Any]]:
2350 |     """
2351 |     Execute a sequence of JavaScript function calls.
2352 | 
2353 |     Args:
2354 |         instance_id (str): Browser instance ID.
2355 |         function_calls (List[Dict[str, Any]]): List of function calls, each with 'function_path', 'args', and optional 'context_id'.
2356 | 
2357 |     Returns:
2358 |         List[Dict[str, Any]]: List of function call results.
2359 |     """
2360 |     from cdp_function_executor import FunctionCall
2361 |     tab = await browser_manager.get_tab(instance_id)
2362 |     if not tab:
2363 |         return [{"success": False, "error": f"Instance not found: {instance_id}"}]
2364 |     calls = []
2365 |     for call_data in function_calls:
2366 |         calls.append(FunctionCall(
2367 |             function_path=call_data['function_path'],
2368 |             args=call_data.get('args', []),
2369 |             context_id=call_data.get('context_id')
2370 |         ))
2371 |     return await cdp_function_executor.execute_function_sequence(tab, calls)
2372 | 
2373 | 
2374 | @section_tool("cdp-functions")
2375 | async def create_python_binding(
2376 |     instance_id: str,
2377 |     binding_name: str,
2378 |     python_code: str
2379 | ) -> Dict[str, Any]:
2380 |     """
2381 |     Create a binding that allows JavaScript to call Python functions.
2382 | 
2383 |     Args:
2384 |         instance_id (str): Browser instance ID.
2385 |         binding_name (str): Name for the binding.
2386 |         python_code (str): Python function code (as string).
2387 | 
2388 |     Returns:
2389 |         Dict[str, Any]: Binding creation result.
2390 |     """
2391 |     tab = await browser_manager.get_tab(instance_id)
2392 |     if not tab:
2393 |         return {"success": False, "error": f"Instance not found: {instance_id}"}
2394 |     try:
2395 |         exec_globals = {}
2396 |         exec(python_code, exec_globals)
2397 |         python_function = None
2398 |         for name, obj in exec_globals.items():
2399 |             if callable(obj) and not name.startswith('_'):
2400 |                 python_function = obj
2401 |                 break
2402 |         if not python_function:
2403 |             return {"success": False, "error": "No function found in Python code"}
2404 |         return await cdp_function_executor.create_python_binding(tab, binding_name, python_function)
2405 |     except Exception as e:
2406 |         return {"success": False, "error": f"Failed to create Python function: {str(e)}"}
2407 | 
2408 | 
2409 | @section_tool("cdp-functions")
2410 | async def execute_python_in_browser(
2411 |     instance_id: str,
2412 |     python_code: str
2413 | ) -> Dict[str, Any]:
2414 |     """
2415 |     Execute Python code by translating it to JavaScript.
2416 | 
2417 |     Args:
2418 |         instance_id (str): Browser instance ID.
2419 |         python_code (str): Python code to translate and execute.
2420 | 
2421 |     Returns:
2422 |         Dict[str, Any]: Execution result.
2423 |     """
2424 |     tab = await browser_manager.get_tab(instance_id)
2425 |     if not tab:
2426 |         return {"success": False, "error": f"Instance not found: {instance_id}"}
2427 |     return await cdp_function_executor.execute_python_in_browser(tab, python_code)
2428 | 
2429 | 
2430 | @section_tool("cdp-functions")
2431 | async def get_function_executor_info(
2432 |     instance_id: str = None
2433 | ) -> Dict[str, Any]:
2434 |     """
2435 |     Get information about the CDP function executor state.
2436 | 
2437 |     Args:
2438 |         instance_id (str, optional): Optional browser instance ID for specific info.
2439 | 
2440 |     Returns:
2441 |         Dict[str, Any]: Function executor state and capabilities.
2442 |     """
2443 |     return await cdp_function_executor.get_function_executor_info(instance_id)
2444 | 
2445 | 
2446 | @section_tool("dynamic-hooks")
2447 | async def create_dynamic_hook(
2448 |     name: str,
2449 |     requirements: Dict[str, Any],
2450 |     function_code: str,
2451 |     instance_ids: Optional[List[str]] = None,
2452 |     priority: int = 100
2453 | ) -> Dict[str, Any]:
2454 |     """
2455 |     Create a new dynamic hook with AI-generated Python function.
2456 |     
2457 |     This is the new powerful hook system that allows AI to write custom Python functions
2458 |     that process network requests in real-time with no pending state.
2459 |     
2460 |     Args:
2461 |         name (str): Human-readable hook name
2462 |         requirements (Dict[str, Any]): Matching criteria (url_pattern, method, resource_type, custom_condition)
2463 |         function_code (str): Python function code that processes requests (must define process_request(request))
2464 |         instance_ids (Optional[List[str]]): Browser instances to apply hook to (all if None)
2465 |         priority (int): Hook priority (lower = higher priority)
2466 |         
2467 |     Returns:
2468 |         Dict[str, Any]: Hook creation result with hook_id
2469 |         
2470 |     Example function_code:
2471 |         ```python
2472 |         def process_request(request):
2473 |             if "example.com" in request["url"]:
2474 |                 return HookAction(action="redirect", url="https://httpbin.org/get")
2475 |             return HookAction(action="continue")
2476 |         ```
2477 |     """
2478 |     return await dynamic_hook_ai.create_dynamic_hook(
2479 |         name=name,
2480 |         requirements=requirements,
2481 |         function_code=function_code,
2482 |         instance_ids=instance_ids,
2483 |         priority=priority
2484 |     )
2485 | 
2486 | 
2487 | @section_tool("dynamic-hooks")
2488 | async def create_simple_dynamic_hook(
2489 |     name: str,
2490 |     url_pattern: str,
2491 |     action: str,
2492 |     target_url: Optional[str] = None,
2493 |     custom_headers: Optional[Dict[str, str]] = None,
2494 |     instance_ids: Optional[List[str]] = None
2495 | ) -> Dict[str, Any]:
2496 |     """
2497 |     Create a simple dynamic hook using predefined templates (easier for AI).
2498 |     
2499 |     Args:
2500 |         name (str): Hook name
2501 |         url_pattern (str): URL pattern to match
2502 |         action (str): Action type - 'block', 'redirect', 'add_headers', or 'log'
2503 |         target_url (Optional[str]): Target URL for redirect action
2504 |         custom_headers (Optional[Dict[str, str]]): Headers to add for add_headers action
2505 |         instance_ids (Optional[List[str]]): Browser instances to apply hook to
2506 |         
2507 |     Returns:
2508 |         Dict[str, Any]: Hook creation result
2509 |     """
2510 |     return await dynamic_hook_ai.create_simple_hook(
2511 |         name=name,
2512 |         url_pattern=url_pattern,
2513 |         action=action,
2514 |         target_url=target_url,
2515 |         custom_headers=custom_headers,
2516 |         instance_ids=instance_ids
2517 |     )
2518 | 
2519 | 
2520 | @section_tool("dynamic-hooks")
2521 | async def list_dynamic_hooks(instance_id: Optional[str] = None) -> Dict[str, Any]:
2522 |     """
2523 |     List all dynamic hooks.
2524 |     
2525 |     Args:
2526 |         instance_id (Optional[str]): Optional filter by browser instance
2527 |         
2528 |     Returns:
2529 |         Dict[str, Any]: List of hooks with details and statistics
2530 |     """
2531 |     return await dynamic_hook_ai.list_dynamic_hooks(instance_id=instance_id)
2532 | 
2533 | 
2534 | @section_tool("dynamic-hooks")
2535 | async def get_dynamic_hook_details(hook_id: str) -> Dict[str, Any]:
2536 |     """
2537 |     Get detailed information about a specific dynamic hook.
2538 |     
2539 |     Args:
2540 |         hook_id (str): Hook identifier
2541 |         
2542 |     Returns:
2543 |         Dict[str, Any]: Detailed hook information including function code
2544 |     """
2545 |     return await dynamic_hook_ai.get_hook_details(hook_id=hook_id)
2546 | 
2547 | 
2548 | @section_tool("dynamic-hooks")
2549 | async def remove_dynamic_hook(hook_id: str) -> Dict[str, Any]:
2550 |     """
2551 |     Remove a dynamic hook.
2552 |     
2553 |     Args:
2554 |         hook_id (str): Hook identifier to remove
2555 |         
2556 |     Returns:
2557 |         Dict[str, Any]: Removal status
2558 |     """
2559 |     return await dynamic_hook_ai.remove_dynamic_hook(hook_id=hook_id)
2560 | 
2561 | 
2562 | @section_tool("dynamic-hooks")
2563 | def get_hook_documentation() -> Dict[str, Any]:
2564 |     """
2565 |     Get comprehensive documentation for creating hook functions (AI learning).
2566 |     
2567 |     Returns:
2568 |         Dict[str, Any]: Documentation of request object structure and HookAction types
2569 |     """
2570 |     return dynamic_hook_ai.get_request_documentation()
2571 | 
2572 | 
2573 | @section_tool("dynamic-hooks")
2574 | def get_hook_examples() -> Dict[str, Any]:
2575 |     """
2576 |     Get example hook functions for AI learning.
2577 |     
2578 |     Returns:
2579 |         Dict[str, Any]: Collection of example hook functions with explanations
2580 |     """
2581 |     return dynamic_hook_ai.get_hook_examples()
2582 | 
2583 | 
2584 | @section_tool("dynamic-hooks")
2585 | def get_hook_requirements_documentation() -> Dict[str, Any]:
2586 |     """
2587 |     Get documentation on hook requirements and matching criteria.
2588 |     
2589 |     Returns:
2590 |         Dict[str, Any]: Requirements documentation and best practices
2591 |     """
2592 |     return dynamic_hook_ai.get_requirements_documentation()
2593 | 
2594 | 
2595 | @section_tool("dynamic-hooks")
2596 | def get_hook_common_patterns() -> Dict[str, Any]:
2597 |     """
2598 |     Get common hook patterns and use cases.
2599 |     
2600 |     Returns:
2601 |         Dict[str, Any]: Common patterns like ad blocking, API proxying, etc.
2602 |     """
2603 |     return dynamic_hook_ai.get_common_patterns()
2604 | 
2605 | 
2606 | @section_tool("dynamic-hooks")
2607 | def validate_hook_function(function_code: str) -> Dict[str, Any]:
2608 |     """
2609 |     Validate hook function code for common issues before creating.
2610 |     
2611 |     Args:
2612 |         function_code (str): Python function code to validate
2613 |         
2614 |     Returns:
2615 |         Dict[str, Any]: Validation results with issues and warnings
2616 |     """
2617 |     return dynamic_hook_ai.validate_hook_function(function_code=function_code)
2618 | 
2619 | 
2620 | 
2621 | if __name__ == "__main__":
2622 |     import argparse
2623 |     
2624 |     parser = argparse.ArgumentParser(description="Stealth Browser MCP Server with 90 tools")
2625 |     parser.add_argument("--transport", choices=["stdio", "http"], default="stdio",
2626 |                       help="Transport protocol to use")
2627 |     parser.add_argument("--port", type=int, default=int(os.getenv("PORT", 8000)),
2628 |                       help="Port for HTTP transport")
2629 |     parser.add_argument("--host", default="0.0.0.0",
2630 |                       help="Host for HTTP transport")
2631 |     
2632 |     parser.add_argument("--disable-browser-management", action="store_true",
2633 |                       help="Disable browser management tools (spawn, navigate, close, etc.)")
2634 |     parser.add_argument("--disable-element-interaction", action="store_true",
2635 |                       help="Disable element interaction tools (click, type, scroll, etc.)")
2636 |     parser.add_argument("--disable-element-extraction", action="store_true",
2637 |                       help="Disable element extraction tools (styles, structure, events, etc.)")
2638 |     parser.add_argument("--disable-file-extraction", action="store_true",
2639 |                       help="Disable file-based extraction tools")
2640 |     parser.add_argument("--disable-network-debugging", action="store_true",
2641 |                       help="Disable network debugging and interception tools")
2642 |     parser.add_argument("--disable-cdp-functions", action="store_true",
2643 |                       help="Disable CDP function execution tools")
2644 |     parser.add_argument("--disable-progressive-cloning", action="store_true",
2645 |                       help="Disable progressive element cloning tools")
2646 |     parser.add_argument("--disable-cookies-storage", action="store_true",
2647 |                       help="Disable cookie and storage management tools")
2648 |     parser.add_argument("--disable-tabs", action="store_true",
2649 |                       help="Disable tab management tools")
2650 |     parser.add_argument("--disable-debugging", action="store_true",
2651 |                       help="Disable debug and system tools")
2652 |     parser.add_argument("--disable-dynamic-hooks", action="store_true",
2653 |                       help="Disable dynamic network hook system")
2654 |     
2655 |     parser.add_argument("--minimal", action="store_true",
2656 |                       help="Enable only core browser management and element interaction (disable everything else)")
2657 |     parser.add_argument("--list-sections", action="store_true",
2658 |                       help="List all available tool sections and exit")
2659 |     
2660 |     args = parser.parse_args()
2661 |     
2662 |     if args.list_sections:
2663 |         print("Available tool sections:")
2664 |         print("  browser-management: Core browser operations (11 tools)")
2665 |         print("  element-interaction: Page interaction and element manipulation (8 tools)")
2666 |         print("  element-extraction: Element cloning and extraction (10 tools)")
2667 |         print("  file-extraction: File-based extraction tools (9 tools)")
2668 |         print("  network-debugging: Network monitoring and interception (10 tools)")
2669 |         print("  cdp-functions: Chrome DevTools Protocol function execution (15 tools)")
2670 |         print("  progressive-cloning: Advanced element cloning system (10 tools)")
2671 |         print("  cookies-storage: Cookie and storage management (3 tools)")
2672 |         print("  tabs: Tab management (5 tools)")
2673 |         print("  debugging: Debug and system tools (6 tools)")
2674 |         print("  dynamic-hooks: AI-powered network hook system (12 tools)")
2675 |         print("\nUse --disable-<section-name> to disable specific sections")
2676 |         print("Use --minimal to enable only core functionality")
2677 |         sys.exit(0)
2678 |     
2679 |     if args.minimal:
2680 |         DISABLED_SECTIONS.update([
2681 |             "element-extraction", "file-extraction", "network-debugging",
2682 |             "cdp-functions", "progressive-cloning", "cookies-storage",
2683 |             "tabs", "debugging", "dynamic-hooks"
2684 |         ])
2685 |     
2686 |     if args.disable_browser_management:
2687 |         DISABLED_SECTIONS.add("browser-management")
2688 |     if args.disable_element_interaction:
2689 |         DISABLED_SECTIONS.add("element-interaction")
2690 |     if args.disable_element_extraction:
2691 |         DISABLED_SECTIONS.add("element-extraction")
2692 |     if args.disable_file_extraction:
2693 |         DISABLED_SECTIONS.add("file-extraction")
2694 |     if args.disable_network_debugging:
2695 |         DISABLED_SECTIONS.add("network-debugging")
2696 |     if args.disable_cdp_functions:
2697 |         DISABLED_SECTIONS.add("cdp-functions")
2698 |     if args.disable_progressive_cloning:
2699 |         DISABLED_SECTIONS.add("progressive-cloning")
2700 |     if args.disable_cookies_storage:
2701 |         DISABLED_SECTIONS.add("cookies-storage")
2702 |     if args.disable_tabs:
2703 |         DISABLED_SECTIONS.add("tabs")
2704 |     if args.disable_debugging:
2705 |         DISABLED_SECTIONS.add("debugging")
2706 |     if args.disable_dynamic_hooks:
2707 |         DISABLED_SECTIONS.add("dynamic-hooks")
2708 |     
2709 |     if DISABLED_SECTIONS:
2710 |         print(f"Disabled tool sections: {', '.join(sorted(DISABLED_SECTIONS))}")
2711 |     
2712 |     if args.transport == "http":
2713 |         mcp.run(transport="http", host=args.host, port=args.port)
2714 |     else:
2715 |         mcp.run(transport="stdio")
```
Page 4/4FirstPrevNextLast