Skip to main content
Glama

chrome-devtools-mcp

by benjaminr
dom.py15.8 kB
#!/usr/bin/env python3 """DOM Element Inspection Tools This module provides DOM manipulation and inspection capabilities through the DevTools Protocol. It enables element querying, property analysis, structural inspection, and interaction with the document object model of web pages. The tools support DOM workflow operations including element discovery, attribute analysis, content extraction, layout inspection, and element interaction. All operations integrate with Chrome's DOM domain for accurate real-time document state access. Key Features: - Document structure retrieval with configurable depth - CSS selector-based element querying (single and multiple) - Element attribute and property inspection - HTML content extraction (outer HTML) - Box model and layout information analysis - Position-based element discovery - Text-based element searching with flexible queries - Element focus management and interaction Example: Inspecting and interacting with DOM elements: ```python # Get document structure document = await get_document(depth=2, pierce=True) # Find elements by CSS selector buttons = await query_selector_all(node_id=1, selector='button.primary') # Inspect element properties attrs = await get_element_attributes(node_id=123) # Get layout information box_model = await get_element_box_model(node_id=123) ``` Note: All DOM operations require an active connection to Chrome with the DOM domain enabled. Node IDs are specific to the current page session and become invalid after navigation. """ from __future__ import annotations from typing import Any from mcp.server.fastmcp import FastMCP from ..cdp_context import require_cdp_client from .utils import create_error_response, create_success_response def register_dom_tools(mcp: FastMCP) -> None: """Register comprehensive DOM inspection and manipulation tools with the MCP server. Adds all DOM analysis and interaction functions as MCP tools, providing complete document object model access, element querying, and structural analysis capabilities. Each tool includes robust error handling and detailed response formatting. The registered tools support the full DOM development workflow: - Document structure analysis and navigation - Element discovery through CSS selectors and text search - Attribute and property inspection - Content extraction and analysis - Layout and positioning information - Element interaction and focus management Args: mcp: FastMCP server instance to register tools with. Must be properly initialised before calling this function. Registered Tools: - get_document: Document structure retrieval with depth control - query_selector: Single element CSS selector querying - query_selector_all: Multiple element CSS selector querying - get_element_attributes: Element attribute enumeration - get_element_outer_html: HTML content extraction - get_element_box_model: Layout and positioning analysis - describe_element: Comprehensive element description - get_element_at_position: Position-based element discovery - search_elements: Text-based element searching - focus_element: Element focus management Note: All tools require access to the global CDP client instance and active browser connection with DOM domain enabled. Node IDs are session-specific and become invalid after page navigation. """ @mcp.tool() @require_cdp_client async def get_document(depth: int = 1, pierce: bool = False, **kwargs: Any) -> dict[str, Any]: """Retrieve the DOM document structure with configurable depth and shadow DOM access. Fetches the document tree starting from the root element, with control over traversal depth and shadow DOM boundary crossing. This provides the foundation for all DOM inspection operations by establishing the document structure. The depth parameter controls how deep into the DOM tree to traverse, while the pierce option enables inspection of shadow DOM content that would normally be encapsulated. Args: depth: Maximum depth to retrieve from document root (default: 1). Use -1 to retrieve the entire document tree. Higher depths provide more complete structure but increase response size. pierce: Whether to traverse shadow DOM boundaries (default: False). When True, includes shadow DOM content in the tree structure. Returns: Document structure dictionary containing: - success: Boolean indicating retrieval success - message: Summary of document structure retrieved - data: Complete DOM tree structure from the document root, including all child elements up to the specified depth Note: Large documents with high depth settings may produce substantial responses. Consider using moderate depths for initial exploration, then targeting specific areas for detailed analysis. """ try: cdp_client = kwargs["cdp_client"] result = await cdp_client.send_command( "DOM.getDocument", {"depth": depth, "pierce": pierce} ) return create_success_response( message=f"Retrieved DOM document structure (depth: {depth})", data=result["root"] ) except Exception as e: return create_error_response(f"Error getting document: {e}") @mcp.tool() @require_cdp_client async def query_selector(node_id: int, selector: str, **kwargs: Any) -> dict[str, Any]: """ Execute querySelector on a DOM node. Args: node_id: Target node ID to query within selector: CSS selector string Returns: Node ID of the matching element """ try: cdp_client = kwargs["cdp_client"] result = await cdp_client.send_command( "DOM.querySelector", {"nodeId": node_id, "selector": selector} ) if result["nodeId"] == 0: return create_success_response( message=f"No element found matching selector: {selector}", data={"nodeId": None, "found": False}, ) return create_success_response( message=f"Found element matching selector: {selector}", data={"nodeId": result["nodeId"], "found": True}, ) except Exception as e: return create_error_response(f"Error executing querySelector: {e}") @mcp.tool() @require_cdp_client async def query_selector_all(node_id: int, selector: str, **kwargs: Any) -> dict[str, Any]: """ Execute querySelectorAll on a DOM node. Args: node_id: Target node ID to query within selector: CSS selector string Returns: Array of node IDs matching the selector """ try: cdp_client = kwargs["cdp_client"] result = await cdp_client.send_command( "DOM.querySelectorAll", {"nodeId": node_id, "selector": selector} ) return create_success_response( message=f"Found {len(result['nodeIds'])} elements matching selector: {selector}", data={"nodeIds": result["nodeIds"], "count": len(result["nodeIds"])}, ) except Exception as e: return create_error_response(f"Error executing querySelectorAll: {e}") @mcp.tool() @require_cdp_client async def get_element_attributes(node_id: int, **kwargs: Any) -> dict[str, Any]: """ Get all attributes of a DOM element. Args: node_id: Node ID of the element Returns: Dictionary of element attributes """ try: cdp_client = kwargs["cdp_client"] result = await cdp_client.send_command("DOM.getAttributes", {"nodeId": node_id}) attributes = {} attr_list = result["attributes"] for i in range(0, len(attr_list), 2): if i + 1 < len(attr_list): attributes[attr_list[i]] = attr_list[i + 1] return create_success_response( message=f"Retrieved {len(attributes)} attributes for node {node_id}", data={"nodeId": node_id, "attributes": attributes}, ) except Exception as e: return create_error_response(f"Error getting element attributes: {e}") @mcp.tool() @require_cdp_client async def get_element_outer_html(node_id: int, **kwargs: Any) -> dict[str, Any]: """ Get the outer HTML of a DOM element. Args: node_id: Node ID of the element Returns: Outer HTML string of the element """ try: cdp_client = kwargs["cdp_client"] result = await cdp_client.send_command("DOM.getOuterHTML", {"nodeId": node_id}) return create_success_response( message=f"Retrieved outer HTML for node {node_id}", data={ "nodeId": node_id, "outerHTML": result["outerHTML"], "htmlLength": len(result["outerHTML"]), }, ) except Exception as e: return create_error_response(f"Error getting outer HTML: {e}") @mcp.tool() @require_cdp_client async def get_element_box_model(node_id: int, **kwargs: Any) -> dict[str, Any]: """ Get the box model (layout information) of a DOM element. Args: node_id: Node ID of the element Returns: Box model data including content, padding, border, and margin boxes """ try: cdp_client = kwargs["cdp_client"] result = await cdp_client.send_command("DOM.getBoxModel", {"nodeId": node_id}) model = result["model"] return create_success_response( message=f"Retrieved box model for node {node_id}", data={ "nodeId": node_id, "content": model["content"], "padding": model["padding"], "border": model["border"], "margin": model["margin"], "width": model["width"], "height": model["height"], }, ) except Exception as e: return create_error_response(f"Error getting box model: {e}") @mcp.tool() @require_cdp_client async def describe_element(node_id: int, depth: int = 1, **kwargs: Any) -> dict[str, Any]: """ Get detailed information about a DOM element. Args: node_id: Node ID of the element depth: Depth of child nodes to include Returns: Detailed element description including tag, attributes, and children """ try: cdp_client = kwargs["cdp_client"] result = await cdp_client.send_command( "DOM.describeNode", {"nodeId": node_id, "depth": depth} ) node = result["node"] return create_success_response( message=f"Retrieved description for node {node_id}", data={ "nodeId": node_id, "nodeName": node.get("nodeName"), "nodeType": node.get("nodeType"), "nodeValue": node.get("nodeValue"), "localName": node.get("localName"), "attributes": node.get("attributes", []), "childNodeCount": node.get("childNodeCount", 0), "children": node.get("children", []), }, ) except Exception as e: return create_error_response(f"Error describing element: {e}") @mcp.tool() @require_cdp_client async def get_element_at_position(x: int, y: int, **kwargs: Any) -> dict[str, Any]: """ Get the DOM element at a specific screen position. Args: x: X coordinate y: Y coordinate Returns: Node information at the specified position """ try: cdp_client = kwargs["cdp_client"] result = await cdp_client.send_command("DOM.getNodeForLocation", {"x": x, "y": y}) return create_success_response( message=f"Found element at position ({x}, {y})", data={ "position": {"x": x, "y": y}, "nodeId": result.get("nodeId"), "backendNodeId": result.get("backendNodeId"), "frameId": result.get("frameId"), }, ) except Exception as e: return create_error_response(f"Error getting element at position: {e}") @mcp.tool() @require_cdp_client async def search_elements(query: str, **kwargs: Any) -> dict[str, Any]: """ Search for DOM elements matching a query string. Args: query: Search query (text content, tag name, or attribute) Returns: Search results with matching elements """ try: cdp_client = kwargs["cdp_client"] search_result = await cdp_client.send_command( "DOM.performSearch", {"query": query, "includeUserAgentShadowDOM": False} ) search_id = search_result["searchId"] result_count = search_result["resultCount"] if result_count == 0: await cdp_client.send_command("DOM.discardSearchResults", {"searchId": search_id}) return create_success_response( message=f"No elements found matching query: {query}", data={"query": query, "resultCount": 0, "nodeIds": []}, ) limit = min(result_count, 50) results = await cdp_client.send_command( "DOM.getSearchResults", {"searchId": search_id, "fromIndex": 0, "toIndex": limit} ) await cdp_client.send_command("DOM.discardSearchResults", {"searchId": search_id}) return create_success_response( message=f"Found {result_count} elements matching query: {query}", data={ "query": query, "resultCount": result_count, "nodeIds": results["nodeIds"], "limitedResults": limit < result_count, }, ) except Exception as e: return create_error_response(f"Error searching elements: {e}") @mcp.tool() @require_cdp_client async def focus_element(node_id: int, **kwargs: Any) -> dict[str, Any]: """ Focus a DOM element. Args: node_id: Node ID of the element to focus Returns: Success status of the focus operation """ try: cdp_client = kwargs["cdp_client"] await cdp_client.send_command("DOM.focus", {"nodeId": node_id}) return create_success_response( message=f"Focused element with node ID {node_id}", data={"nodeId": node_id, "focused": True}, ) except Exception as e: return create_error_response(f"Error focusing element: {e}")

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/benjaminr/chrome-devtools-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server