Skip to main content
Glama

inspect_ancestors

Read-only

Walk up the DOM tree to debug layout constraints. Shows width, margins, borders, overflow, flexbox, grid, and clipping points for each ancestor to identify unexpected centering, constrained width, or clipped content.

Instructions

DEBUG LAYOUT CONSTRAINTS: Walk up the DOM tree to find where width constraints, margins, borders, and overflow clipping come from. Shows for each ancestor: position/size, width constraints (w, max-w, min-w), margins with directional arrows (↑↓←→ format), padding, display type, borders (directional if non-uniform), overflow (πŸ”’=hidden, ↕️=scroll), flexbox context (flex direction justify items gap), grid context (cols rows gap), position/z-index/transform when set. Automatically detects horizontal centering via auto margins and flags clipping points (🎯). Essential for debugging unexpected centering, constrained width, or clipped content. Default: 10 ancestors (reaches in most React apps), max: 15. Use after inspect_dom() to understand parent layout constraints.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
selectorYesCSS selector or testid shorthand for the element to start from (e.g., 'testid:header', '#main')
limitNoMaximum number of ancestors to traverse (default: 10, max: 15). Increase for deeply nested component frameworks.

Implementation Reference

  • The main handler/execute function for the inspect_ancestors tool. It selects an element by CSS/testid selector, walks up the DOM tree extracting layout properties (width, margins, padding, border, overflow, flexbox, grid, position, z-index, transform) for each ancestor up to a configurable limit (default 10, max 15), then formats the chain for display.
    async execute(args: {
      selector: string;
      limit?: number;
    }, context: ToolContext): Promise<ToolResponse> {
      return this.safeExecute(context, async (page) => {
        const limit = Math.min(args.limit ?? 10, 15); // Default 10, max 15
    
        // Use consistent element selection (Playwright's visibility detection)
        const locator = await this.createScopedLocator(page, args.selector);
        const count = await locator.count();
    
        if (count === 0) {
          return {
            content: [
              {
                type: "text",
                text: `Error: Element not found with selector "${args.selector}"`,
              },
            ],
            isError: true,
          };
        }
    
        const { element, elementIndex, totalCount } = await this.selectPreferredLocator(locator, {
          originalSelector: args.selector,
        });
    
        // Use the selected element for ancestor traversal
        const ancestors = await element.evaluate(
          (el: Element, lim: number) => {
            const chain: any[] = [];
            let current: Element | null = el;
    
            for (let i = 0; i < lim && current; i++) {
              const rect = current.getBoundingClientRect();
              const computed = window.getComputedStyle(current);
    
              chain.push({
                tagName: current.tagName.toLowerCase(),
                testId: current.getAttribute("data-testid"),
                classes: current.className,
                rect: {
                  x: Math.round(rect.x),
                  y: Math.round(rect.y),
                  width: Math.round(rect.width),
                  height: Math.round(rect.height),
                },
    
                // Layout-critical properties
                width: computed.width,
                maxWidth: computed.maxWidth,
                minWidth: computed.minWidth,
                margin: computed.margin,
                marginTop: computed.marginTop,
                marginRight: computed.marginRight,
                marginBottom: computed.marginBottom,
                marginLeft: computed.marginLeft,
                padding: computed.padding,
                display: computed.display,
                overflow: computed.overflow,
                overflowX: computed.overflowX,
                overflowY: computed.overflowY,
                scrollHeight: current.scrollHeight,
                scrollWidth: current.scrollWidth,
                clientHeight: current.clientHeight,
                clientWidth: current.clientWidth,
                border: computed.border,
                borderTop: computed.borderTop,
                borderRight: computed.borderRight,
                borderBottom: computed.borderBottom,
                borderLeft: computed.borderLeft,
    
                // Flexbox
                flexDirection: computed.flexDirection,
                justifyContent: computed.justifyContent,
                alignItems: computed.alignItems,
                gap: computed.gap,
    
                // Grid
                gridTemplateColumns: computed.gridTemplateColumns,
                gridTemplateRows: computed.gridTemplateRows,
    
                // Conditional
                position:
                  computed.position !== "static" ? computed.position : undefined,
                zIndex: computed.zIndex !== "auto" ? computed.zIndex : undefined,
                transform:
                  computed.transform !== "none" ? computed.transform : undefined,
              });
    
              current = current.parentElement;
            }
    
            return chain;
          },
          limit
        );
    
        return {
          content: [
            {
              type: "text",
              text: await this.formatAncestorChain(
                ancestors,
                args.selector,
                elementIndex,
                totalCount
              ),
            },
          ],
          isError: false,
        };
      });
    }
  • The metadata definition including input schema (selector required, limit optional number), description, examples, and output documentation for the inspect_ancestors tool.
    export class InspectAncestorsTool extends BrowserToolBase {
      static getMetadata(sessionConfig?: SessionConfig): ToolMetadata {
        return {
          name: "inspect_ancestors",
          description: "DEBUG LAYOUT CONSTRAINTS: Walk up the DOM tree to find where width constraints, margins, borders, and overflow clipping come from. Shows for each ancestor: position/size, width constraints (w, max-w, min-w), margins with directional arrows (↑↓←→ format), padding, display type, borders (directional if non-uniform), overflow (πŸ”’=hidden, ↕️=scroll), flexbox context (flex direction justify items gap), grid context (cols rows gap), position/z-index/transform when set. Automatically detects horizontal centering via auto margins and flags clipping points (🎯). Essential for debugging unexpected centering, constrained width, or clipped content. Default: 10 ancestors (reaches <body> in most React apps), max: 15. Use after inspect_dom() to understand parent layout constraints.",
          annotations: ANNOTATIONS.readOnly,
          outputs: [
            "Header showing selected element index when selector matched multiple.",
            "For each ancestor (starting from target):",
            "- [i] <tag> | testid:... or classes",
            "- @ (x,y) widthΓ—height px",
            "- Inline summary: w, display (if not block), m/p, max-w, min-w",
            "- Flexbox/Grid context when present (direction, gap, grid templates)",
            "- Margin breakdown with arrows (↑↓←→) and centering diagnostics",
            "- Border details when set (directional if non-uniform)",
            "- Overflow state: πŸ”’ hidden, ↕️/↔️ scroll + overflow amount",
            "- Extra: position/z-index/transform when non-default",
            "- Diagnostics: 🎯 CLIPPING POINT / SCROLLABLE CONTAINER / WIDTH CONSTRAINT",
          ],
          examples: [
            "inspect_ancestors({ selector: 'testid:submit-button' })",
            "inspect_ancestors({ selector: '#content', limit: 15 })",
          ],
          priority: 1,
          exampleOutputs: [
            {
              call: "inspect_ancestors({ selector: 'testid:submit-button' })",
              output: `Selected: testid:submit-button (1 of 2 matches)\n\nAncestor Chain:\n\n[0] <button> | testid:submit-button\n    @ (860,540) 120x40px | w:120px display:inline-block\n    margin: ↑0px β†’0px ↓0px ←0px\n    border: 1px solid rgb(0, 122, 255)\n    ⚠ none\n\n[1] <div> | form-actions\n    @ (800,520) 240x80px | w:240px display:flex m:0px p:16px gap:8px\n    flex: row, justify:center, align:center, gap:8px\n    margin: β†’auto ←auto ← Horizontally centered (likely margin:0 auto)\n    border: none\n    overflow: πŸ”’ hidden\n    🎯 CLIPPING POINT - May clip overflowing children\n\n[2] <form> | #login-form\n    @ (640,200) 560x480px | w:560px max-w:600px\n    position:relative\n    🎯 WIDTH CONSTRAINT`
            }
          ],
          inputSchema: {
            type: "object",
            properties: {
              selector: {
                type: "string",
                description: "CSS selector or testid shorthand for the element to start from (e.g., 'testid:header', '#main')"
              },
              limit: {
                type: "number",
                description: "Maximum number of ancestors to traverse (default: 10, max: 15). Increase for deeply nested component frameworks."
              }
            },
            required: ["selector"],
          },
        };
  • The registration of InspectAncestorsTool in the BROWSER_TOOL_CLASSES array (line 85), imported from the inspection module (line 34).
    import { InspectAncestorsTool } from './inspection/inspect_ancestors.js';
    import { ElementExistsTool } from './inspection/element_exists.js';
    import { MeasureElementTool } from './inspection/measure_element.js';
    import { GetComputedStylesTool } from './inspection/get_computed_styles.js';
    
    // Evaluation
    import { EvaluateTool } from './evaluation/evaluate.js';
    
    // Console
    import { GetConsoleLogsTool, ClearConsoleLogsTool } from './console/get_console_logs.js';
    
    // Network
    import { ListNetworkRequestsTool } from './network/list_network_requests.js';
    import { GetRequestDetailsTool } from './network/get_request_details.js';
    
    // Waiting
    import { WaitForElementTool } from './waiting/wait_for_element.js';
    import { WaitForNetworkIdleTool } from './waiting/wait_for_network_idle.js';
    
    export const BROWSER_TOOL_CLASSES: ToolClass[] = [
      // Navigation (5)
      NavigateTool,
      GoHistoryTool,
      ScrollToElementTool,
      ScrollByTool,
    
      // Lifecycle (2)
      CloseTool,
      SetColorSchemeTool,
    
      // Interaction (7)
      ClickTool,
      FillTool,
      SelectTool,
      HoverTool,
      UploadFileTool,
      DragTool,
      PressKeyTool,
    
      // Content (3)
      ScreenshotTool,
      GetTextTool,
      GetHtmlTool,
    
      // Inspection (10)
      InspectDomTool,
      GetTestIdsTool,
      QuerySelectorTool,
      FindByTextTool,
      CheckVisibilityTool,
      CompareElementAlignmentTool,
      InspectAncestorsTool,
  • Helper formatting functions used to render the ancestor chain output: formatAncestorChain (main formatter), formatBorder, formatOverflow, formatLayoutContext (flexbox/grid), formatMarginDetails (with arrow notation and horizontal centering detection), and generateDiagnostics (CLIPPING POINT, SCROLLABLE CONTAINER, WIDTH CONSTRAINT markers).
      private async formatAncestorChain(
        ancestors: AncestorData[],
        originalSelector: string,
        elementIndex: number = 0,
        totalCount: number = 1
      ): Promise<string> {
        const lines: string[] = [];
    
        // Header with selector info
        const selectionInfo = await this.formatElementSelectionInfo(
          originalSelector,
          elementIndex,
          totalCount,
          true
        );
    
        if (selectionInfo) {
          lines.push(selectionInfo.replace(/\n\n$/, '')); // Remove trailing newlines for header
          lines.push(`Ancestor Chain:\n`);
        } else {
          lines.push(`Ancestor Chain: ${originalSelector}\n`);
        }
    
        ancestors.forEach((ancestor, index) => {
          const parts: string[] = [];
    
          // Tag and identifier
          let identifier = `[${index}] <${ancestor.tagName}>`;
          if (ancestor.testId) {
            identifier += ` | testid:${ancestor.testId}`;
          } else if (ancestor.classes) {
            // Show first few classes for context
            const classes = ancestor.classes.trim().split(/\s+/).slice(0, 3);
            if (classes.length > 0 && classes[0]) {
              identifier += ` | ${classes.join(" ")}`;
            }
          }
          parts.push(identifier);
    
          // Position and size (always show)
          const layoutInfo: string[] = [];
          parts.push(
            `\n    @ (${ancestor.rect.x},${ancestor.rect.y}) ${ancestor.rect.width}x${ancestor.rect.height}px`
          );
    
          // Width info (always show)
          layoutInfo.push(`w:${ancestor.width}`);
    
          // Display (only if not block)
          if (ancestor.display !== "block") {
            layoutInfo.push(`display:${ancestor.display}`);
          }
    
          // Only show non-default values
          if (ancestor.margin !== "0px") {
            layoutInfo.push(`m:${ancestor.margin}`);
          }
          if (ancestor.padding !== "0px") {
            layoutInfo.push(`p:${ancestor.padding}`);
          }
          if (ancestor.maxWidth !== "none") {
            layoutInfo.push(`max-w:${ancestor.maxWidth}`);
          }
          if (ancestor.minWidth !== "0px") {
            layoutInfo.push(`min-w:${ancestor.minWidth}`);
          }
    
          if (layoutInfo.length > 0) {
            parts.push(` | ${layoutInfo.join(" ")}`);
          }
    
          // Flexbox/Grid context (on separate line for clarity)
          const layoutContext = this.formatLayoutContext(ancestor);
          if (layoutContext) {
            parts.push(`\n    ${layoutContext}`);
          }
    
          // Margin details (only if non-zero or has auto)
          const marginDetails = this.formatMarginDetails(ancestor);
          if (marginDetails) {
            parts.push(`\n    ${marginDetails}`);
          }
    
          // Border - only if set
          const borderInfo = this.formatBorder(ancestor);
          if (borderInfo) {
            parts.push(`\n    ${borderInfo}`);
          }
    
          // Overflow - only if not visible
          const overflowInfo = this.formatOverflow(ancestor);
          if (overflowInfo) {
            parts.push(`\n    ${overflowInfo}`);
          }
    
          // Position, z-index, transform (only if set)
          const extraInfo: string[] = [];
          if (ancestor.position) {
            extraInfo.push(`position:${ancestor.position}`);
          }
          if (ancestor.zIndex) {
            extraInfo.push(`z-index:${ancestor.zIndex}`);
          }
          if (ancestor.transform) {
            extraInfo.push(`transform:${ancestor.transform}`);
          }
          if (extraInfo.length > 0) {
            parts.push(`\n    ${extraInfo.join(", ")}`);
          }
    
          // Add diagnostics
          const diagnostics = this.generateDiagnostics(ancestor, index);
          if (diagnostics) {
            parts.push(`\n    ${diagnostics}`);
          }
    
          lines.push(parts.join(""));
        });
    
        return lines.join("\n\n");
      }
    
      private formatBorder(ancestor: AncestorData): string | null {
        // Check if main border is set
        if (
          ancestor.border &&
          ancestor.border !== "none" &&
          ancestor.border !== "0px none" &&
          !ancestor.border.startsWith("0px")
        ) {
          return `border: ${ancestor.border}`;
        }
    
        // Check directional borders
        const borders: string[] = [];
        if (
          ancestor.borderTop &&
          ancestor.borderTop !== "none" &&
          !ancestor.borderTop.startsWith("0px")
        ) {
          borders.push(`top:${ancestor.borderTop}`);
        }
        if (
          ancestor.borderRight &&
          ancestor.borderRight !== "none" &&
          !ancestor.borderRight.startsWith("0px")
        ) {
          borders.push(`right:${ancestor.borderRight}`);
        }
        if (
          ancestor.borderBottom &&
          ancestor.borderBottom !== "none" &&
          !ancestor.borderBottom.startsWith("0px")
        ) {
          borders.push(`bottom:${ancestor.borderBottom}`);
        }
        if (
          ancestor.borderLeft &&
          ancestor.borderLeft !== "none" &&
          !ancestor.borderLeft.startsWith("0px")
        ) {
          borders.push(`left:${ancestor.borderLeft}`);
        }
    
        if (borders.length > 0) {
          return `border: ${borders.join(", ")}`;
        }
    
        return null;
      }
    
      private formatOverflow(ancestor: AncestorData): string | null {
        const parts: string[] = [];
    
        // Detect actual scrollable content
        const hasVerticalScroll = ancestor.scrollHeight > ancestor.clientHeight;
        const hasHorizontalScroll = ancestor.scrollWidth > ancestor.clientWidth;
        const verticalOverflow = hasVerticalScroll ? ancestor.scrollHeight - ancestor.clientHeight : 0;
        const horizontalOverflow = hasHorizontalScroll ? ancestor.scrollWidth - ancestor.clientWidth : 0;
    
        // Check if overflow CSS is set
        const hasOverflowX = ancestor.overflowX !== "visible";
        const hasOverflowY = ancestor.overflowY !== "visible";
    
        // Only show if there's either CSS overflow set OR actual scrollable content
        if (!hasOverflowX && !hasOverflowY && !hasVerticalScroll && !hasHorizontalScroll) {
          return null;
        }
    
        // Handle uniform overflow
        if (
          ancestor.overflow !== "visible" &&
          ancestor.overflowX === ancestor.overflow &&
          ancestor.overflowY === ancestor.overflow
        ) {
          let icon = "";
          let scrollInfo = "";
    
          if (ancestor.overflow === "hidden") {
            icon = "πŸ”’";
            if (hasVerticalScroll || hasHorizontalScroll) {
              const clippedParts: string[] = [];
              if (hasVerticalScroll) clippedParts.push(`↕️ ${verticalOverflow}px clipped`);
              if (hasHorizontalScroll) clippedParts.push(`↔️ ${horizontalOverflow}px clipped`);
              scrollInfo = ` (${clippedParts.join(', ')})`;
            }
          } else if (ancestor.overflow === "auto" || ancestor.overflow === "scroll") {
            const scrollParts: string[] = [];
            if (hasVerticalScroll) {
              icon = "↕️";
              scrollParts.push(`↕️ ${verticalOverflow}px`);
            }
            if (hasHorizontalScroll) {
              icon = hasVerticalScroll ? "↕️↔️" : "↔️";
              scrollParts.push(`↔️ ${horizontalOverflow}px`);
            }
            if (scrollParts.length > 0) {
              scrollInfo = ` (${scrollParts.join(', ')} scrollable)`;
            } else if (ancestor.overflow === "scroll") {
              scrollInfo = " (no overflow)";
            }
          }
    
          return `overflow: ${icon} ${ancestor.overflow}${scrollInfo}`;
        }
    
        // Handle different overflow-x/y
        if (hasOverflowX || hasOverflowY || hasVerticalScroll || hasHorizontalScroll) {
          const overflowParts: string[] = [];
    
          if (hasOverflowY || hasVerticalScroll) {
            let yIcon = "";
            let yInfo = "";
    
            if (ancestor.overflowY === "hidden") {
              yIcon = "πŸ”’";
              if (hasVerticalScroll) {
                yInfo = ` (${verticalOverflow}px clipped)`;
              }
            } else if (ancestor.overflowY === "auto" || ancestor.overflowY === "scroll") {
              if (hasVerticalScroll) {
                yIcon = "↕️";
                yInfo = ` (${verticalOverflow}px scrollable)`;
              } else if (ancestor.overflowY === "scroll") {
                yIcon = "↕️";
                yInfo = " (no overflow)";
              }
            }
    
            overflowParts.push(`overflow-y: ${yIcon} ${ancestor.overflowY}${yInfo}`);
          }
    
          if (hasOverflowX || hasHorizontalScroll) {
            let xIcon = "";
            let xInfo = "";
    
            if (ancestor.overflowX === "hidden") {
              xIcon = "πŸ”’";
              if (hasHorizontalScroll) {
                xInfo = ` (${horizontalOverflow}px clipped)`;
              }
            } else if (ancestor.overflowX === "auto" || ancestor.overflowX === "scroll") {
              if (hasHorizontalScroll) {
                xIcon = "↔️";
                xInfo = ` (${horizontalOverflow}px scrollable)`;
              } else if (ancestor.overflowX === "scroll") {
                xIcon = "↔️";
                xInfo = " (no overflow)";
              }
            }
    
            overflowParts.push(`overflow-x: ${xIcon} ${ancestor.overflowX}${xInfo}`);
          }
    
          return overflowParts.join(", ");
        }
    
        return null;
      }
    
      private formatLayoutContext(ancestor: AncestorData): string | null {
        const parts: string[] = [];
    
        // Flexbox
        if (ancestor.display === "flex" || ancestor.display === "inline-flex") {
          const flexParts = ["flex"];
    
          if (ancestor.flexDirection && ancestor.flexDirection !== "row") {
            flexParts.push(ancestor.flexDirection);
          }
    
          if (ancestor.justifyContent && ancestor.justifyContent !== "normal" && ancestor.justifyContent !== "flex-start") {
            flexParts.push(`justify:${ancestor.justifyContent}`);
          }
    
          if (ancestor.alignItems && ancestor.alignItems !== "normal" && ancestor.alignItems !== "stretch") {
            flexParts.push(`items:${ancestor.alignItems}`);
          }
    
          if (ancestor.gap && ancestor.gap !== "0px" && ancestor.gap !== "normal") {
            flexParts.push(`gap:${ancestor.gap}`);
          }
    
          parts.push(flexParts.join(" "));
        }
    
        // Grid
        if (ancestor.display === "grid" || ancestor.display === "inline-grid") {
          const gridParts = ["grid"];
    
          if (ancestor.gridTemplateColumns && ancestor.gridTemplateColumns !== "none") {
            gridParts.push(`cols:${ancestor.gridTemplateColumns}`);
          }
    
          if (ancestor.gridTemplateRows && ancestor.gridTemplateRows !== "none") {
            gridParts.push(`rows:${ancestor.gridTemplateRows}`);
          }
    
          if (ancestor.gap && ancestor.gap !== "0px" && ancestor.gap !== "normal") {
            gridParts.push(`gap:${ancestor.gap}`);
          }
    
          parts.push(gridParts.join(" "));
        }
    
        return parts.length > 0 ? parts.join(" | ") : null;
      }
    
      private formatMarginDetails(ancestor: AncestorData): string | null {
        // Check if any margin is "auto" (CSS value)
        // Note: computed styles show actual values, not "auto"
        const hasAuto = ancestor.margin.includes("auto") ||
                         ancestor.marginTop === "auto" ||
                         ancestor.marginRight === "auto" ||
                         ancestor.marginBottom === "auto" ||
                         ancestor.marginLeft === "auto";
    
        // Check if margins are non-uniform (can't be represented by shorthand)
        const isNonUniform = ancestor.marginTop !== ancestor.marginBottom ||
                             ancestor.marginLeft !== ancestor.marginRight ||
                             ancestor.marginTop !== ancestor.marginLeft;
    
        // Parse margin values to detect large symmetric margins (likely auto-centered)
        const parseMarginValue = (val: string): number => {
          const match = val.match(/^([\d.]+)px$/);
          return match ? parseFloat(match[1]) : 0;
        };
    
        const marginLeftPx = parseMarginValue(ancestor.marginLeft);
        const marginRightPx = parseMarginValue(ancestor.marginRight);
        const marginTopPx = parseMarginValue(ancestor.marginTop);
        const marginBottomPx = parseMarginValue(ancestor.marginBottom);
    
        // Detect horizontal centering: large equal left/right margins, small top/bottom
        const isHorizontallyCentered =
          marginLeftPx > 100 &&
          marginRightPx > 100 &&
          Math.abs(marginLeftPx - marginRightPx) < 2 && // Allow 1px rounding
          marginTopPx === 0 &&
          marginBottomPx === 0;
    
        if (!hasAuto && !isHorizontallyCentered && ancestor.margin === "0px") {
          return null; // All zeros, skip
        }
    
        // If has auto, always show detailed breakdown with arrows
        if (hasAuto) {
          const parts: string[] = [];
    
          if (ancestor.marginTop !== "0px") {
            parts.push(`↑${ancestor.marginTop}`);
          }
          if (ancestor.marginRight === "auto" || ancestor.marginRight !== "0px") {
            parts.push(`β†’${ancestor.marginRight}`);
          }
          if (ancestor.marginBottom !== "0px") {
            parts.push(`↓${ancestor.marginBottom}`);
          }
          if (ancestor.marginLeft === "auto" || ancestor.marginLeft !== "0px") {
            parts.push(`←${ancestor.marginLeft}`);
          }
    
          const marginStr = `margin: ${parts.join(" ")}`;
    
          // Add diagnostic if horizontally centered
          if (ancestor.marginLeft === "auto" && ancestor.marginRight === "auto") {
            return `${marginStr} ← Horizontally centered by auto margins`;
          }
    
          return marginStr;
        }
    
        // Show horizontal centering diagnostic
        if (isHorizontallyCentered) {
          return `margin: β†’${ancestor.marginRight} ←${ancestor.marginLeft} ← Horizontally centered (likely margin:0 auto)`;
        }
    
        // If non-uniform and non-zero, show with arrows
        if (isNonUniform && ancestor.margin !== "0px") {
          const parts: string[] = [];
    
          if (ancestor.marginTop !== "0px") {
            parts.push(`↑${ancestor.marginTop}`);
          }
          if (ancestor.marginRight !== "0px") {
            parts.push(`β†’${ancestor.marginRight}`);
          }
          if (ancestor.marginBottom !== "0px") {
            parts.push(`↓${ancestor.marginBottom}`);
          }
          if (ancestor.marginLeft !== "0px") {
            parts.push(`←${ancestor.marginLeft}`);
          }
    
          return `margin: ${parts.join(" ")}`;
        }
    
        return null;
      }
    
      private generateDiagnostics(
        ancestor: AncestorData,
        index: number
      ): string | null {
        const diagnostics: string[] = [];
    
        // Detect actual scrollable content
        const hasVerticalScroll = ancestor.scrollHeight > ancestor.clientHeight;
        const hasHorizontalScroll = ancestor.scrollWidth > ancestor.clientWidth;
    
        // Overflow hidden warning
        if (ancestor.overflow === "hidden" || ancestor.overflowY === "hidden") {
          diagnostics.push("🎯 CLIPPING POINT - May clip overflowing children");
        }
    
        // Scrollable container detection
        if (hasVerticalScroll || hasHorizontalScroll) {
          const scrollParts: string[] = [];
          if (hasVerticalScroll) scrollParts.push("vertically");
          if (hasHorizontalScroll) scrollParts.push("horizontally");
          diagnostics.push(`🎯 SCROLLABLE CONTAINER - ${scrollParts.join(" & ")}`);
        }
    
        // Width constraint detection
        if (ancestor.maxWidth !== "none" && index > 0) {
          diagnostics.push("🎯 WIDTH CONSTRAINT");
        }
    
        return diagnostics.length > 0 ? diagnostics.join("\n    ") : null;
      }
    }
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Despite readOnlyHint=true, the description adds rich behavioral details: position/size, constraints, margins with arrows, padding, display, borders, overflow, flexbox/grid context, auto centering detection, clipping flags, default/max limits. Exceeds annotation information.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Description is relatively long but efficiently packs many details. Front-loaded with core purpose. Every sentence adds value, though slightly verbose for a debug tool, it remains clear and structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Without output schema, description fully specifies what the tool returns: a list of ancestors with various properties. Covers all vital aspects for a layout debugging tool, leaving no ambiguity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3. Description adds context for limit parameter (increase for deeply nested frameworks) and clarifies selector usage, providing extra value beyond schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool walks up the DOM tree to find layout constraints, with specific details on what it shows. It distinguishes itself from siblings like inspect_dom by focusing on ancestors and layout debugging.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states it's essential for debugging unexpected centering, constrained width, or clipped content, and recommends using it after inspect_dom. Provides clear when-to-use guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

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/antonzherdev/mcp-web-inspector'

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