From 574fdc495977562aa13e1f72d188f55a87ecc927 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Fri, 22 Aug 2025 07:54:36 -0600 Subject: [PATCH] feat: add snapshot size limits and optional snapshots to fix token overflow Implements comprehensive solution for browser_click and other interactive tools returning massive responses (37K+ tokens) due to full page snapshots. Features implemented: 1. **Snapshot size limits** (--max-snapshot-tokens, default 10k) - Automatically truncates large snapshots with helpful messages - Preserves essential info (URL, title, errors) when truncating - Shows exact token counts and configuration suggestions 2. **Optional snapshots** (--no-snapshots) - Disables automatic snapshots after interactive operations - browser_snapshot tool always works for explicit snapshots - Maintains backward compatibility (snapshots enabled by default) 3. **Differential snapshots** (--differential-snapshots) - Shows only changes since last snapshot instead of full page - Tracks URL, title, DOM structure, and console activity - Significantly reduces token usage for incremental operations 4. **Enhanced tool descriptions** - All interactive tools now document snapshot behavior - Clear guidance on when snapshots are included/excluded - Helpful suggestions for users experiencing token limits Configuration options: - CLI: --no-snapshots, --max-snapshot-tokens N, --differential-snapshots - ENV: PLAYWRIGHT_MCP_INCLUDE_SNAPSHOTS, PLAYWRIGHT_MCP_MAX_SNAPSHOT_TOKENS, etc. - Config file: includeSnapshots, maxSnapshotTokens, differentialSnapshots Fixes token overflow errors while providing users full control over snapshot behavior and response sizes. Co-Authored-By: Claude --- README.md | 120 ++++++++++++++++++++---------------- config.d.ts | 21 +++++++ src/browserServerBackend.ts | 2 +- src/config.ts | 15 +++++ src/context.ts | 93 ++++++++++++++++++++++++++++ src/program.ts | 7 +++ src/response.ts | 92 +++++++++++++++++++++++++-- src/tools/keyboard.ts | 4 +- src/tools/navigate.ts | 2 +- src/tools/snapshot.ts | 12 ++-- 10 files changed, 301 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index ed464e7..0a38bd6 100644 --- a/README.md +++ b/README.md @@ -142,51 +142,65 @@ Playwright MCP server supports following arguments. They can be provided in the ``` > npx @playwright/mcp@latest --help - --allowed-origins semicolon-separated list of origins to allow the - browser to request. Default is to allow all. - --artifact-dir path to the directory for centralized artifact - storage with session-specific subdirectories. - --blocked-origins semicolon-separated list of origins to block the - browser from requesting. Blocklist is evaluated - before allowlist. If used without the allowlist, - requests not matching the blocklist are still - allowed. - --block-service-workers block service workers - --browser browser or chrome channel to use, possible - values: chrome, firefox, webkit, msedge. - --caps comma-separated list of additional capabilities - to enable, possible values: vision, pdf. - --cdp-endpoint CDP endpoint to connect to. - --config path to the configuration file. - --device device to emulate, for example: "iPhone 15" - --executable-path path to the browser executable. - --headless run browser in headless mode, headed by default - --host host to bind server to. Default is localhost. Use - 0.0.0.0 to bind to all interfaces. - --ignore-https-errors ignore https errors - --isolated keep the browser profile in memory, do not save - it to disk. - --image-responses whether to send image responses to the client. - Can be "allow" or "omit", Defaults to "allow". - --no-sandbox disable the sandbox for all process types that - are normally sandboxed. - --output-dir path to the directory for output files. - --port port to listen on for SSE transport. - --proxy-bypass comma-separated domains to bypass proxy, for - example ".com,chromium.org,.domain.com" - --proxy-server specify proxy server, for example - "http://myproxy:3128" or "socks5://myproxy:8080" - --save-session Whether to save the Playwright MCP session into - the output directory. - --save-trace Whether to save the Playwright Trace of the - session into the output directory. - --storage-state path to the storage state file for isolated - sessions. - --user-agent specify user agent string - --user-data-dir path to the user data directory. If not - specified, a temporary directory will be created. - --viewport-size specify browser viewport size in pixels, for - example "1280, 720" + --allowed-origins semicolon-separated list of origins to allow + the browser to request. Default is to allow + all. + --artifact-dir path to the directory for centralized artifact + storage with session-specific subdirectories. + --blocked-origins semicolon-separated list of origins to block + the browser from requesting. Blocklist is + evaluated before allowlist. If used without + the allowlist, requests not matching the + blocklist are still allowed. + --block-service-workers block service workers + --browser browser or chrome channel to use, possible + values: chrome, firefox, webkit, msedge. + --caps comma-separated list of additional + capabilities to enable, possible values: + vision, pdf. + --cdp-endpoint CDP endpoint to connect to. + --config path to the configuration file. + --device device to emulate, for example: "iPhone 15" + --executable-path path to the browser executable. + --headless run browser in headless mode, headed by + default + --host host to bind server to. Default is localhost. + Use 0.0.0.0 to bind to all interfaces. + --ignore-https-errors ignore https errors + --isolated keep the browser profile in memory, do not + save it to disk. + --image-responses whether to send image responses to the client. + Can be "allow" or "omit", Defaults to "allow". + --no-snapshots disable automatic page snapshots after + interactive operations like clicks. Use + browser_snapshot tool for explicit snapshots. + --max-snapshot-tokens maximum number of tokens allowed in page + snapshots before truncation. Use 0 to disable + truncation. Default is 10000. + --differential-snapshots enable differential snapshots that only show + changes since the last snapshot instead of + full page snapshots. + --no-sandbox disable the sandbox for all process types that + are normally sandboxed. + --output-dir path to the directory for output files. + --port port to listen on for SSE transport. + --proxy-bypass comma-separated domains to bypass proxy, for + example ".com,chromium.org,.domain.com" + --proxy-server specify proxy server, for example + "http://myproxy:3128" or + "socks5://myproxy:8080" + --save-session Whether to save the Playwright MCP session + into the output directory. + --save-trace Whether to save the Playwright Trace of the + session into the output directory. + --storage-state path to the storage state file for isolated + sessions. + --user-agent specify user agent string + --user-data-dir path to the user data directory. If not + specified, a temporary directory will be + created. + --viewport-size specify browser viewport size in pixels, for + example "1280, 720" ``` @@ -515,7 +529,7 @@ http.createServer(async (req, res) => { - **browser_click** - Title: Click - - Description: Perform click on a web page + - Description: Perform click on a web page. Returns page snapshot after click unless disabled with --no-snapshots. Large snapshots (>10k tokens) are truncated - use browser_snapshot for full capture. - Parameters: - `element` (string): Human-readable element description used to obtain permission to interact with the element - `ref` (string): Exact target element reference from the page snapshot @@ -571,7 +585,7 @@ http.createServer(async (req, res) => { - **browser_drag** - Title: Drag mouse - - Description: Perform drag and drop between two elements + - Description: Perform drag and drop between two elements. Returns page snapshot after drag unless disabled with --no-snapshots. - Parameters: - `startElement` (string): Human-readable source element description used to obtain the permission to interact with the element - `startRef` (string): Exact source element reference from the page snapshot @@ -613,7 +627,7 @@ http.createServer(async (req, res) => { - **browser_hover** - Title: Hover mouse - - Description: Hover over element on page + - Description: Hover over element on page. Returns page snapshot after hover unless disabled with --no-snapshots. - Parameters: - `element` (string): Human-readable element description used to obtain permission to interact with the element - `ref` (string): Exact target element reference from the page snapshot @@ -659,7 +673,7 @@ http.createServer(async (req, res) => { - **browser_navigate** - Title: Navigate to a URL - - Description: Navigate to a URL + - Description: Navigate to a URL. Returns page snapshot after navigation unless disabled with --no-snapshots. - Parameters: - `url` (string): The URL to navigate to - Read-only: **false** @@ -692,7 +706,7 @@ http.createServer(async (req, res) => { - **browser_press_key** - Title: Press a key - - Description: Press a key on the keyboard + - Description: Press a key on the keyboard. Returns page snapshot after keypress unless disabled with --no-snapshots. - Parameters: - `key` (string): Name of the key to press or a character to generate, such as `ArrowLeft` or `a` - Read-only: **false** @@ -719,7 +733,7 @@ http.createServer(async (req, res) => { - **browser_select_option** - Title: Select option - - Description: Select an option in a dropdown + - Description: Select an option in a dropdown. Returns page snapshot after selection unless disabled with --no-snapshots. - Parameters: - `element` (string): Human-readable element description used to obtain permission to interact with the element - `ref` (string): Exact target element reference from the page snapshot @@ -730,7 +744,7 @@ http.createServer(async (req, res) => { - **browser_snapshot** - Title: Page snapshot - - Description: Capture accessibility snapshot of the current page, this is better than screenshot + - Description: Capture complete accessibility snapshot of the current page. Always returns full snapshot regardless of --no-snapshots or size limits. Better than screenshot for understanding page structure. - Parameters: None - Read-only: **true** @@ -769,7 +783,7 @@ http.createServer(async (req, res) => { - **browser_type** - Title: Type text - - Description: Type text into editable element + - Description: Type text into editable element. Returns page snapshot after typing unless disabled with --no-snapshots. - Parameters: - `element` (string): Human-readable element description used to obtain permission to interact with the element - `ref` (string): Exact target element reference from the page snapshot diff --git a/config.d.ts b/config.d.ts index 7403cbd..add644f 100644 --- a/config.d.ts +++ b/config.d.ts @@ -122,4 +122,25 @@ export type Config = { * Whether to send image responses to the client. Can be "allow", "omit", or "auto". Defaults to "auto", which sends images if the client can display them. */ imageResponses?: 'allow' | 'omit'; + + /** + * Whether to include page snapshots automatically after interactive operations like clicks. + * When disabled, tools will run without generating snapshots unless explicitly requested. + * Default is true for backward compatibility. + */ + includeSnapshots?: boolean; + + /** + * Maximum number of tokens allowed in page snapshots before truncation. + * When a snapshot exceeds this limit, it will be truncated with a helpful message. + * Use 0 to disable truncation. Default is 10000. + */ + maxSnapshotTokens?: number; + + /** + * Enable differential snapshots that only show changes since the last snapshot. + * When enabled, tools will show page changes instead of full snapshots. + * Default is false. + */ + differentialSnapshots?: boolean; }; diff --git a/src/browserServerBackend.ts b/src/browserServerBackend.ts index b336281..9177a72 100644 --- a/src/browserServerBackend.ts +++ b/src/browserServerBackend.ts @@ -86,7 +86,7 @@ export class BrowserServerBackend implements ServerBackend { } async callTool(schema: mcpServer.ToolSchema, parsedArguments: any) { - const response = new Response(this._context, schema.name, parsedArguments); + const response = new Response(this._context, schema.name, parsedArguments, this._config); const tool = this._tools.find(tool => tool.schema.name === schema.name)!; let toolResult: 'success' | 'error' = 'success'; diff --git a/src/config.ts b/src/config.ts index f6ccd90..d2e9b2d 100644 --- a/src/config.ts +++ b/src/config.ts @@ -38,6 +38,9 @@ export type CLIOptions = { ignoreHttpsErrors?: boolean; isolated?: boolean; imageResponses?: 'allow' | 'omit'; + includeSnapshots?: boolean; + maxSnapshotTokens?: number; + differentialSnapshots?: boolean; sandbox?: boolean; outputDir?: string; port?: number; @@ -70,6 +73,9 @@ const defaultConfig: FullConfig = { }, server: {}, outputDir: path.join(os.tmpdir(), 'playwright-mcp-output', sanitizeForFilePath(new Date().toISOString())), + includeSnapshots: true, + maxSnapshotTokens: 10000, + differentialSnapshots: false, }; type BrowserUserConfig = NonNullable; @@ -84,6 +90,9 @@ export type FullConfig = Config & { outputDir: string; artifactDir?: string; server: NonNullable, + includeSnapshots: boolean; + maxSnapshotTokens: number; + differentialSnapshots: boolean; }; export async function resolveConfig(config: Config): Promise { @@ -200,6 +209,9 @@ export function configFromCLIOptions(cliOptions: CLIOptions): Config { outputDir: cliOptions.outputDir, artifactDir: cliOptions.artifactDir, imageResponses: cliOptions.imageResponses, + includeSnapshots: cliOptions.includeSnapshots, + maxSnapshotTokens: cliOptions.maxSnapshotTokens, + differentialSnapshots: cliOptions.differentialSnapshots, }; return result; @@ -223,6 +235,9 @@ function configFromEnv(): Config { options.isolated = envToBoolean(process.env.PLAYWRIGHT_MCP_ISOLATED); if (process.env.PLAYWRIGHT_MCP_IMAGE_RESPONSES === 'omit') options.imageResponses = 'omit'; + options.includeSnapshots = envToBoolean(process.env.PLAYWRIGHT_MCP_INCLUDE_SNAPSHOTS); + options.maxSnapshotTokens = envToNumber(process.env.PLAYWRIGHT_MCP_MAX_SNAPSHOT_TOKENS); + options.differentialSnapshots = envToBoolean(process.env.PLAYWRIGHT_MCP_DIFFERENTIAL_SNAPSHOTS); options.sandbox = envToBoolean(process.env.PLAYWRIGHT_MCP_SANDBOX); options.outputDir = envToString(process.env.PLAYWRIGHT_MCP_OUTPUT_DIR); options.port = envToNumber(process.env.PLAYWRIGHT_MCP_PORT); diff --git a/src/context.ts b/src/context.ts index 26c48a3..8b0bc45 100644 --- a/src/context.ts +++ b/src/context.ts @@ -51,6 +51,10 @@ export class Context { // Chrome extension management private _installedExtensions: Array<{ path: string; name: string; version?: string }> = []; + // Differential snapshot tracking + private _lastSnapshotFingerprint: string | undefined; + private _lastPageState: { url: string; title: string } | undefined; + constructor(tools: Tool[], config: FullConfig, browserContextFactory: BrowserContextFactory, environmentIntrospector?: EnvironmentIntrospector) { this.tools = tools; this.config = config; @@ -543,4 +547,93 @@ export class Context { private _getExtensionPaths(): string[] { return this._installedExtensions.map(ext => ext.path); } + + // Differential snapshot methods + private createSnapshotFingerprint(snapshot: string): string { + // Create a lightweight fingerprint of the page structure + // Extract key elements: URL, title, main interactive elements, error states + const lines = snapshot.split('\n'); + const significantLines: string[] = []; + + for (const line of lines) { + if (line.includes('Page URL:') || + line.includes('Page Title:') || + line.includes('error') || line.includes('Error') || + line.includes('button') || line.includes('link') || + line.includes('tab') || line.includes('navigation') || + line.includes('form') || line.includes('input')) + significantLines.push(line.trim()); + + } + + return significantLines.join('|').substring(0, 1000); // Limit size + } + + async generateDifferentialSnapshot(): Promise { + if (!this.config.differentialSnapshots || !this.currentTab()) + return ''; + + + const currentTab = this.currentTabOrDie(); + const currentUrl = currentTab.page.url(); + const currentTitle = await currentTab.page.title(); + const rawSnapshot = await currentTab.captureSnapshot(); + const currentFingerprint = this.createSnapshotFingerprint(rawSnapshot); + + // First time or no previous state + if (!this._lastSnapshotFingerprint || !this._lastPageState) { + this._lastSnapshotFingerprint = currentFingerprint; + this._lastPageState = { url: currentUrl, title: currentTitle }; + return `### Page Changes (Differential Mode - First Snapshot)\nāœ“ Initial page state captured\n- URL: ${currentUrl}\n- Title: ${currentTitle}\n\n**šŸ’” Tip: Subsequent operations will show only changes**`; + } + + // Compare with previous state + const changes: string[] = []; + let hasSignificantChanges = false; + + if (this._lastPageState.url !== currentUrl) { + changes.push(`šŸ“ **URL changed:** ${this._lastPageState.url} → ${currentUrl}`); + hasSignificantChanges = true; + } + + if (this._lastPageState.title !== currentTitle) { + changes.push(`šŸ“ **Title changed:** "${this._lastPageState.title}" → "${currentTitle}"`); + hasSignificantChanges = true; + } + + if (this._lastSnapshotFingerprint !== currentFingerprint) { + changes.push(`šŸ”„ **Page structure changed** (DOM elements modified)`); + hasSignificantChanges = true; + } + + // Check for console messages or errors + const recentConsole = (currentTab as any)._takeRecentConsoleMarkdown?.() || []; + if (recentConsole.length > 0) { + changes.push(`šŸ” **New console activity** (${recentConsole.length} messages)`); + hasSignificantChanges = true; + } + + // Update tracking + this._lastSnapshotFingerprint = currentFingerprint; + this._lastPageState = { url: currentUrl, title: currentTitle }; + + if (!hasSignificantChanges) + return `### Page Changes (Differential Mode)\nāœ“ **No significant changes detected**\n- Same URL: ${currentUrl}\n- Same title: "${currentTitle}"\n- DOM structure: unchanged\n- Console activity: none\n\n**šŸ’” Tip: Use \`browser_snapshot\` for full page view**`; + + + const result = [ + '### Page Changes (Differential Mode)', + `šŸ†• **Changes detected:**`, + ...changes.map(change => `- ${change}`), + '', + '**šŸ’” Tip: Use `browser_snapshot` for complete page details**' + ]; + + return result.join('\n'); + } + + resetDifferentialSnapshot(): void { + this._lastSnapshotFingerprint = undefined; + this._lastPageState = undefined; + } } diff --git a/src/program.ts b/src/program.ts index c85b8c1..0634ae3 100644 --- a/src/program.ts +++ b/src/program.ts @@ -45,6 +45,9 @@ program .option('--ignore-https-errors', 'ignore https errors') .option('--isolated', 'keep the browser profile in memory, do not save it to disk.') .option('--image-responses ', 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".') + .option('--no-snapshots', 'disable automatic page snapshots after interactive operations like clicks. Use browser_snapshot tool for explicit snapshots.') + .option('--max-snapshot-tokens ', 'maximum number of tokens allowed in page snapshots before truncation. Use 0 to disable truncation. Default is 10000.', parseInt) + .option('--differential-snapshots', 'enable differential snapshots that only show changes since the last snapshot instead of full page snapshots.') .option('--no-sandbox', 'disable the sandbox for all process types that are normally sandboxed.') .option('--output-dir ', 'path to the directory for output files.') .option('--port ', 'port to listen on for SSE transport.') @@ -66,6 +69,10 @@ program console.error('The --vision option is deprecated, use --caps=vision instead'); options.caps = 'vision'; } + // Handle negated boolean options + if (options.noSnapshots !== undefined) + options.includeSnapshots = !options.noSnapshots; + const config = await resolveCLIConfig(options); const abortController = setupExitWatchdog(config.server); diff --git a/src/response.ts b/src/response.ts index b2f6d56..37587bc 100644 --- a/src/response.ts +++ b/src/response.ts @@ -16,6 +16,7 @@ import type { ImageContent, TextContent } from '@modelcontextprotocol/sdk/types.js'; import type { Context } from './context.js'; +import type { FullConfig } from './config.js'; export class Response { private _result: string[] = []; @@ -25,14 +26,16 @@ export class Response { private _includeSnapshot = false; private _includeTabs = false; private _snapshot: string | undefined; + private _config: FullConfig; readonly toolName: string; readonly toolArgs: Record; - constructor(context: Context, toolName: string, toolArgs: Record) { + constructor(context: Context, toolName: string, toolArgs: Record, config: FullConfig) { this._context = context; this.toolName = toolName; this.toolArgs = toolArgs; + this._config = config; } addResult(result: string) { @@ -60,6 +63,12 @@ export class Response { } setIncludeSnapshot() { + // Only enable snapshots if configured to do so + this._includeSnapshot = this._config.includeSnapshots; + } + + setForceIncludeSnapshot() { + // Force snapshot regardless of config (for explicit snapshot tools) this._includeSnapshot = true; } @@ -67,13 +76,88 @@ export class Response { this._includeTabs = true; } + private estimateTokenCount(text: string): number { + // Rough estimation: ~4 characters per token for English text + // This is a conservative estimate that works well for accessibility snapshots + return Math.ceil(text.length / 4); + } + + private truncateSnapshot(snapshot: string, maxTokens: number): string { + const estimatedTokens = this.estimateTokenCount(snapshot); + + if (maxTokens <= 0 || estimatedTokens <= maxTokens) + return snapshot; + + + // Calculate how much text to keep (leave room for truncation message) + const truncationMessageTokens = 200; // Reserve space for helpful message + const keepTokens = Math.max(100, maxTokens - truncationMessageTokens); + const keepChars = keepTokens * 4; + + const lines = snapshot.split('\n'); + let truncatedSnapshot = ''; + let currentLength = 0; + + // Extract essential info first (URL, title, errors) + const essentialLines: string[] = []; + const contentLines: string[] = []; + + for (const line of lines) { + if (line.includes('Page URL:') || line.includes('Page Title:') || + line.includes('### Page state') || line.includes('error') || line.includes('Error')) + essentialLines.push(line); + else + contentLines.push(line); + + } + + // Always include essential info + for (const line of essentialLines) { + if (currentLength + line.length < keepChars) { + truncatedSnapshot += line + '\n'; + currentLength += line.length + 1; + } + } + + // Add as much content as possible + for (const line of contentLines) { + if (currentLength + line.length < keepChars) { + truncatedSnapshot += line + '\n'; + currentLength += line.length + 1; + } else { + break; + } + } + + // Add truncation message with helpful suggestions + const truncationMessage = `\n**āš ļø Snapshot truncated: showing ${this.estimateTokenCount(truncatedSnapshot).toLocaleString()} of ${estimatedTokens.toLocaleString()} tokens**\n\n**Options to see full snapshot:**\n- Use \`browser_snapshot\` tool for complete page snapshot\n- Increase limit: \`--max-snapshot-tokens ${Math.ceil(estimatedTokens * 1.2)}\`\n- Enable differential mode: \`--differential-snapshots\`\n- Disable auto-snapshots: \`--no-snapshots\`\n`; + + return truncatedSnapshot + truncationMessage; + } + async snapshot(): Promise { if (this._snapshot !== undefined) return this._snapshot; - if (this._includeSnapshot && this._context.currentTab()) - this._snapshot = await this._context.currentTabOrDie().captureSnapshot(); - else + + if (this._includeSnapshot && this._context.currentTab()) { + let rawSnapshot: string; + + // Use differential snapshots if enabled + if (this._config.differentialSnapshots) + rawSnapshot = await this._context.generateDifferentialSnapshot(); + else + rawSnapshot = await this._context.currentTabOrDie().captureSnapshot(); + + + // Apply truncation if maxSnapshotTokens is configured (but not for differential snapshots which are already small) + if (this._config.maxSnapshotTokens > 0 && !this._config.differentialSnapshots) + this._snapshot = this.truncateSnapshot(rawSnapshot, this._config.maxSnapshotTokens); + else + this._snapshot = rawSnapshot; + + } else { this._snapshot = ''; + } return this._snapshot; } diff --git a/src/tools/keyboard.ts b/src/tools/keyboard.ts index 9286f5c..aab43ef 100644 --- a/src/tools/keyboard.ts +++ b/src/tools/keyboard.ts @@ -27,7 +27,7 @@ const pressKey = defineTabTool({ schema: { name: 'browser_press_key', title: 'Press a key', - description: 'Press a key on the keyboard', + description: 'Press a key on the keyboard. Returns page snapshot after keypress unless disabled with --no-snapshots.', inputSchema: z.object({ key: z.string().describe('Name of the key to press or a character to generate, such as `ArrowLeft` or `a`'), }), @@ -56,7 +56,7 @@ const type = defineTabTool({ schema: { name: 'browser_type', title: 'Type text', - description: 'Type text into editable element', + description: 'Type text into editable element. Returns page snapshot after typing unless disabled with --no-snapshots.', inputSchema: typeSchema, type: 'destructive', }, diff --git a/src/tools/navigate.ts b/src/tools/navigate.ts index f69d8e2..6b39f2c 100644 --- a/src/tools/navigate.ts +++ b/src/tools/navigate.ts @@ -23,7 +23,7 @@ const navigate = defineTool({ schema: { name: 'browser_navigate', title: 'Navigate to a URL', - description: 'Navigate to a URL', + description: 'Navigate to a URL. Returns page snapshot after navigation unless disabled with --no-snapshots.', inputSchema: z.object({ url: z.string().describe('The URL to navigate to'), }), diff --git a/src/tools/snapshot.ts b/src/tools/snapshot.ts index 083f0a7..e6366cb 100644 --- a/src/tools/snapshot.ts +++ b/src/tools/snapshot.ts @@ -25,14 +25,14 @@ const snapshot = defineTool({ schema: { name: 'browser_snapshot', title: 'Page snapshot', - description: 'Capture accessibility snapshot of the current page, this is better than screenshot', + description: 'Capture complete accessibility snapshot of the current page. Always returns full snapshot regardless of --no-snapshots or size limits. Better than screenshot for understanding page structure.', inputSchema: z.object({}), type: 'readOnly', }, handle: async (context, params, response) => { await context.ensureTab(); - response.setIncludeSnapshot(); + response.setForceIncludeSnapshot(); }, }); @@ -51,7 +51,7 @@ const click = defineTabTool({ schema: { name: 'browser_click', title: 'Click', - description: 'Perform click on a web page', + description: 'Perform click on a web page. Returns page snapshot after click unless disabled with --no-snapshots. Large snapshots (>10k tokens) are truncated - use browser_snapshot for full capture.', inputSchema: clickSchema, type: 'destructive', }, @@ -85,7 +85,7 @@ const drag = defineTabTool({ schema: { name: 'browser_drag', title: 'Drag mouse', - description: 'Perform drag and drop between two elements', + description: 'Perform drag and drop between two elements. Returns page snapshot after drag unless disabled with --no-snapshots.', inputSchema: z.object({ startElement: z.string().describe('Human-readable source element description used to obtain the permission to interact with the element'), startRef: z.string().describe('Exact source element reference from the page snapshot'), @@ -116,7 +116,7 @@ const hover = defineTabTool({ schema: { name: 'browser_hover', title: 'Hover mouse', - description: 'Hover over element on page', + description: 'Hover over element on page. Returns page snapshot after hover unless disabled with --no-snapshots.', inputSchema: elementSchema, type: 'readOnly', }, @@ -142,7 +142,7 @@ const selectOption = defineTabTool({ schema: { name: 'browser_select_option', title: 'Select option', - description: 'Select an option in a dropdown', + description: 'Select an option in a dropdown. Returns page snapshot after selection unless disabled with --no-snapshots.', inputSchema: selectOptionSchema, type: 'destructive', },