Skip to main content
Glama

get_text

Read-only

Extract all visible text content from a web page for content analysis, text search, or text-only snapshots. Supports scoping to specific selectors and dialogs.

Instructions

[may return preview+token] ⚠️ RARELY NEEDED: Get ALL visible text content from the entire page (no structure, just raw text). Most tasks need structured inspection instead. ONLY use get_text for: (1) extracting text for content analysis (word count, language detection), (2) searching for text when location is completely unknown, (3) text-only snapshots for comparison. For structured tasks, use: inspect_dom() to understand page structure, find_by_text() to locate specific text with context, query_selector() to find elements. Auto-returns text if <2000 chars (small elements); if larger, returns a preview and a one-time token to fetch the full output via confirm_output. Supports testid shortcuts and the dialog::SELECTOR scope to read inside the topmost open dialog/sheet.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
selectorNoCSS selector, text selector, or testid shorthand to limit text extraction to a specific container. Omit to get text from entire page. Examples: 'testid:article-body', '#main-content', 'dialog::section' (scopes lookup to the topmost open dialog/sheet — useful when a sheet covers ambiguous page chrome). Use bare 'dialog::' for the whole topmost dialog.
maxLengthNoMaximum number of characters to return (default: 20000)

Implementation Reference

  • The GetTextTool class containing both the execute() method (the main handler that extracts visible text from a page, supports selector scoping, auto-scoping to modals, preview for large text, and truncation) and the getMetadata() method defining the schema.
    export class GetTextTool extends BrowserToolBase {
      static getMetadata(sessionConfig?: SessionConfig): ToolMetadata {
        return {
          name: "get_text",
          description: "[may return preview+token] ⚠️ RARELY NEEDED: Get ALL visible text content from the entire page (no structure, just raw text). Most tasks need structured inspection instead. ONLY use get_text for: (1) extracting text for content analysis (word count, language detection), (2) searching for text when location is completely unknown, (3) text-only snapshots for comparison. For structured tasks, use: inspect_dom() to understand page structure, find_by_text() to locate specific text with context, query_selector() to find elements. Auto-returns text if <2000 chars (small elements); if larger, returns a preview and a one-time token to fetch the full output via confirm_output. Supports testid shortcuts and the `dialog::SELECTOR` scope to read inside the topmost open dialog/sheet.",
          annotations: ANNOTATIONS.readOnly,
          inputSchema: {
            type: "object",
            properties: {
              selector: {
                type: "string",
                description: "CSS selector, text selector, or testid shorthand to limit text extraction to a specific container. Omit to get text from entire page. Examples: 'testid:article-body', '#main-content', 'dialog::section' (scopes lookup to the topmost open dialog/sheet — useful when a sheet covers ambiguous page chrome). Use bare 'dialog::' for the whole topmost dialog."
              },
              maxLength: {
                type: "number",
                description: "Maximum number of characters to return (default: 20000)"
              }
            },
            required: [],
          },
        };
      }
    
      async execute(args: any, context: ToolContext): Promise<ToolResponse> {
        const requestedMaxLength =
          typeof args.maxLength === 'number' && Number.isFinite(args.maxLength) && args.maxLength > 0
            ? Math.floor(args.maxLength)
            : 20000;
        const PREVIEW_THRESHOLD = 2000;
    
        if (!context.page) {
          return createErrorResponse('Page is not available');
        }
    
        if (context.browser && !context.browser.isConnected()) {
          return createErrorResponse('Browser is not connected');
        }
    
        if (context.page.isClosed()) {
          return createErrorResponse('Page is not available or has been closed');
        }
    
        return this.safeExecute(context, async (page) => {
          try {
            let effectiveSelector: string | undefined =
              typeof args.selector === 'string' && args.selector.length > 0 ? args.selector : undefined;
    
            // Auto-scope to the topmost open modal when no selector was given —
            // this matches what a human sees: when a sheet covers the page, only
            // the sheet's content is reachable. Reading the whole page would
            // hand the LLM mostly inert content behind the backdrop.
            let autoScopeNotice = '';
            if (!effectiveSelector) {
              const modal = await this.detectActiveModal(page);
              if (modal) {
                effectiveSelector = 'dialog::';
                autoScopeNotice =
                  `🪟 Auto-scoped to open modal: ${modal.descriptor}. ` +
                  `Pass an explicit selector to override (e.g. selector: 'body' for the full page).`;
              }
            }
    
            const hasSelector = !!effectiveSelector;
            const scopeLabel = hasSelector ? ` (from "${effectiveSelector}")` : ' (entire page)';
            const lines: string[] = [`Visible text content${scopeLabel}`];
            if (autoScopeNotice) lines.push(autoScopeNotice);
    
            let selectionWarning = '';
            let textContent = '';
    
            if (hasSelector) {
              const locator = await this.createScopedLocator(page, effectiveSelector!);
    
              const { element, elementIndex, totalCount } = await this.selectPreferredLocator(locator, {
                originalSelector: effectiveSelector!,
              });
    
              selectionWarning = await this.formatElementSelectionInfo(
                effectiveSelector!,
                elementIndex,
                totalCount,
                true
              );
    
              textContent = await element.evaluate((target: HTMLElement | null) => {
                if (!target) {
                  return '';
                }
                if (typeof target.innerText === 'string') {
                  return target.innerText;
                }
                return target.textContent ?? '';
              });
            } else {
              textContent = await page.evaluate(() => document.body?.innerText ?? '');
            }
    
            textContent = textContent ?? '';
    
            if (selectionWarning) {
              lines.push(selectionWarning.trimEnd());
            }
    
            const safeMaxLength = requestedMaxLength > 0 ? requestedMaxLength : 20000;
            const totalLength = textContent.length;
    
            // Large-output guard: return preview + confirm_output token when text is big
            if (totalLength >= PREVIEW_THRESHOLD) {
              const preview = makeConfirmPreview(() => textContent, {
                counts: {
                  totalLength,
                  shownLength: Math.min(500, totalLength),
                  truncated: true,
                },
                previewLines: [
                  'Preview (first 500 chars):',
                  textContent.slice(0, 500),
                  ...(totalLength > 500 ? ['...'] : []),
                  '',
                  '⚠️ Full text not returned to save tokens',
                  '',
                  '💡 RECOMMENDED: Use token-efficient alternatives:',
                  '   • inspect_dom() - structured view with positions and layout',
                  '   • find_by_text() - locate specific text with context',
                  '   • query_selector() - inspect specific elements',
                ],
              });
    
              lines.push(`Text size: ${totalLength.toLocaleString()} characters (exceeds ${PREVIEW_THRESHOLD} char threshold)`);
              lines.push('');
              lines.push(...preview.lines);
              return createSuccessResponse(lines.join('\n'));
            }
    
            lines.push('');
    
            let displayText = textContent;
            const truncated = displayText.length > safeMaxLength;
    
            if (truncated) {
              displayText = `${displayText.slice(0, safeMaxLength)}\n[Output truncated due to size limits]`;
            }
    
            lines.push(displayText);
    
            if (truncated) {
              lines.push('');
              lines.push(
                `Output truncated due to size limits (returned ${safeMaxLength} of ${textContent.length} characters)`
              );
            }
    
            lines.push('');
            lines.push('💡 TIP: If you need structured inspection, try inspect_dom(), find_by_text(), or query_selector().');
    
            return createSuccessResponse(lines.join('\n'));
          } catch (error) {
            return createErrorResponse(`Failed to get visible text content: ${(error as Error).message}`);
          }
        });
      }
    }
  • Input schema for get_text: optional 'selector' (CSS/text/testid selector) and optional 'maxLength' (number, default 20000).
      inputSchema: {
        type: "object",
        properties: {
          selector: {
            type: "string",
            description: "CSS selector, text selector, or testid shorthand to limit text extraction to a specific container. Omit to get text from entire page. Examples: 'testid:article-body', '#main-content', 'dialog::section' (scopes lookup to the topmost open dialog/sheet — useful when a sheet covers ambiguous page chrome). Use bare 'dialog::' for the whole topmost dialog."
          },
          maxLength: {
            type: "number",
            description: "Maximum number of characters to return (default: 20000)"
          }
        },
        required: [],
      },
    };
  • Import of GetTextTool in the browser tools registry (line 24) and its inclusion in the BROWSER_TOOL_CLASSES array (line 75).
    import { GetTextTool } from './content/get_text.js';
    import { GetHtmlTool } from './content/get_html.js';
    
    // Inspection
    import { InspectDomTool } from './inspection/inspect_dom.js';
    import { GetTestIdsTool } from './inspection/get_test_ids.js';
    import { QuerySelectorTool } from './inspection/query_selector.js';
    import { FindByTextTool } from './inspection/find_by_text.js';
    import { CheckVisibilityTool } from './inspection/check_visibility.js';
    import { CompareElementAlignmentTool } from './inspection/compare_element_alignment.js';
    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[] = [
  • detectActiveModal() method on BrowserToolBase (the parent class) used by get_text to auto-scope to the topmost open dialog/sheet when no explicit selector is given.
    /**
     * Detect a "user-dominating" open modal — i.e. one that a human would
     * visually focus on and interact with to the exclusion of the rest of the
     * page. Used by inspect_dom / get_text / get_html to auto-scope when no
     * selector is provided, so the LLM's view matches the human's view.
     *
     * Strict criterion: requires `aria-modal="true"` (or native `dialog[open]`)
     * because non-modal `[role="dialog"]` includes things like side panels and
     * tooltips that don't dominate the page.
     *
     * Returns null if no active modal is open. Otherwise returns the topmost
     * one, ranked by the same z-index walk used by `createScopedLocator()`.
     */
    protected async detectActiveModal(page: Page): Promise<{
      descriptor: string;
      suggestion: string;
    } | null> {
      const ACTIVE_MODAL_SELECTOR =
        '[role="dialog"][aria-modal="true"]:not([aria-hidden="true"]),' +
        '[role="alertdialog"][aria-modal="true"]:not([aria-hidden="true"]),' +
        'dialog[open]';
    
      return await page.evaluate((rootsSelector: string) => {
        const isUserVisible = (el: Element): boolean => {
  • Re-export of GetTextTool from the content barrel index file.
    export { GetTextTool } from './get_text.js';
    export { GetHtmlTool } from './get_html.js';
Behavior5/5

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

Beyond annotations (readOnlyHint=true, openWorldHint=false), the description reveals critical behaviors: auto-return for small text (<2000 chars), preview+token mechanism for larger text, support for testid shortcuts, and the dialog::SELECTOR scope. No contradiction with annotations.

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

Conciseness5/5

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

The description is front-loaded with a warning and purpose, then organized into bullet-like use cases and alternatives. Each sentence serves a distinct purpose: warning, purpose, use cases, alternatives, algorithmic behavior, and supported scopes. It's dense but not verbose.

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?

Despite no output schema, the description fully explains the tool's behavior: how text is returned (preview+token for large), default thresholds, and scoping options. It addresses edge cases (small vs large elements) and provides enough context for an agent to use it correctly alongside siblings.

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% with both parameters documented. The description adds practical context (e.g., 'Examples: testid:article-body', 'Use bare dialog:: for the whole topmost dialog') and mentions default maxLength (20000). While schema already covers parameter roles, the examples enhance usability.

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 extracts all visible text from the entire page without structure. It explicitly calls out the tool's purpose and distinguishes it from sibling tools like inspect_dom, find_by_text, and query_selector by naming specific use cases where get_text is appropriate.

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?

The description provides explicit guidance on when to use this tool (three enumerated use cases) and when not to use it (structured tasks), with direct references to alternative sibling tools. The warning '⚠️ RARELY NEEDED' sets clear expectations.

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