diff --git a/src/firefox/snapshot/formatter.ts b/src/firefox/snapshot/formatter.ts index 20150cd..207ab9b 100644 --- a/src/firefox/snapshot/formatter.ts +++ b/src/firefox/snapshot/formatter.ts @@ -6,9 +6,9 @@ import type { SnapshotNode } from './types.js'; /** - * Max attribute value length + * Max attribute value length (aggressive truncation for token efficiency) */ -const MAX_ATTR_LENGTH = 50; +const MAX_ATTR_LENGTH = 30; /** * Formatting options diff --git a/src/tools/console.ts b/src/tools/console.ts index 6dfd86c..49f762e 100644 --- a/src/tools/console.ts +++ b/src/tools/console.ts @@ -218,7 +218,7 @@ export async function handleListConsoleMessages(args: unknown): Promise try { await firefox.clickByUid(uid, dblClick); - return successResponse( - `✅ ${dblClick ? 'Double-clicked' : 'Clicked'} element with UID "${uid}"` - ); + return successResponse(`✅ ${dblClick ? 'dblclick' : 'click'} ${uid}`); } catch (error) { throw handleUidError(error as Error, uid); } @@ -187,7 +181,7 @@ export async function handleHoverByUid(args: unknown): Promise try { await firefox.hoverByUid(uid); - return successResponse(`✅ Hovered over element with UID "${uid}"`); + return successResponse(`✅ hover ${uid}`); } catch (error) { throw handleUidError(error as Error, uid); } @@ -213,9 +207,7 @@ export async function handleFillByUid(args: unknown): Promise { try { await firefox.fillByUid(uid, value); - return successResponse( - `✅ Filled element with UID "${uid}"\nValue: ${value.substring(0, 50)}${value.length > 50 ? '...' : ''}` - ); + return successResponse(`✅ fill ${uid}`); } catch (error) { throw handleUidError(error as Error, uid); } @@ -241,16 +233,12 @@ export async function handleDragByUidToUid(args: unknown): Promise - ` - ${el.uid}: ${el.value.substring(0, 30)}${el.value.length > 30 ? '...' : ''}` - ) - .join('\n') - ); + return successResponse(`✅ filled ${elements.length} fields`); } catch (error) { const errorMsg = (error as Error).message; if (errorMsg.includes('stale') || errorMsg.includes('Snapshot') || errorMsg.includes('UID')) { - throw new Error( - `One or more UIDs are stale or invalid.\n\n` + - 'The page may have changed since the snapshot was taken.\n' + - 'Please call take_snapshot to get fresh UIDs and try again.' - ); + throw new Error(`UIDs stale/invalid. Call take_snapshot first.`); } throw error; } @@ -324,7 +300,7 @@ export async function handleUploadFileByUid(args: unknown): Promise element.\n\n` + - 'Please ensure the UID points to a file input element.' - ); + throw new Error(`${uid} is not a file input`); } if (errorMsg.includes('hidden') || errorMsg.includes('not visible')) { - throw new Error( - `File input element with UID "${uid}" is hidden or not interactable.\n\n` + - 'Some file inputs are hidden and cannot be directly interacted with.' - ); + throw new Error(`${uid} is hidden/not interactable`); } throw error; diff --git a/src/tools/network.ts b/src/tools/network.ts index eb8d194..733b844 100644 --- a/src/tools/network.ts +++ b/src/tools/network.ts @@ -232,16 +232,8 @@ export async function handleListNetworkRequests(args: unknown): Promise ({ @@ -256,7 +248,7 @@ export async function handleListNetworkRequests(args: unknown): Promise req.id === id); if (!request) { - return errorResponse( - `No network request found with ID: ${id}\n\n` + - 'TIP: The request may have been cleared. Call list_network_requests to see available requests.' - ); + return errorResponse(`ID ${id} not found`); } } else if (url) { // Fallback: lookup by URL (with collision detection) const matches = requests.filter((req) => req.url === url); if (matches.length === 0) { - return errorResponse( - `No network request found with URL: ${url}\n\n` + - 'TIP: Use list_network_requests to see available requests.' - ); + return errorResponse(`URL not found: ${url}`); } if (matches.length > 1) { - const matchInfo = matches - .map((req) => ` - ID: ${req.id} | ${req.method} [${req.status || 'pending'}]`) - .join('\n'); - return errorResponse( - `Multiple requests (${matches.length}) found with URL: ${url}\n\n` + - 'Please use one of these IDs with the "id" parameter:\n' + - matchInfo - ); + const ids = matches.map((req) => req.id).join(', '); + return errorResponse(`Multiple matches, use id: ${ids}`); } request = matches[0]; @@ -364,10 +339,8 @@ export async function handleGetNetworkRequest(args: unknown): Promise, selectedIdx: number ): string { - const lines: string[] = [`📄 Open pages (${tabs.length} total, selected: [${selectedIdx}]):`]; - if (tabs.length === 0) { - lines.push(' (no pages open)'); - } else { - for (const tab of tabs) { - const idx = tabs.indexOf(tab); - const indicator = idx === selectedIdx ? '👉' : ' '; - const title = tab.title || 'Untitled'; - const url = tab.url || 'about:blank'; - lines.push(`${indicator} [${idx}] ${title}`); - lines.push(` ${url}`); - } + return '📄 No pages'; + } + const lines: string[] = [`📄 ${tabs.length} pages (selected: ${selectedIdx})`]; + for (const tab of tabs) { + const idx = tabs.indexOf(tab); + const marker = idx === selectedIdx ? '>' : ' '; + const title = (tab.title || 'Untitled').substring(0, 40); + lines.push(`${marker}[${idx}] ${title}`); } - return lines.join('\n'); } @@ -137,16 +132,7 @@ export async function handleNewPage(args: unknown): Promise { const newIdx = await firefox.createNewPage(url); - // Refresh tabs to update the list - await firefox.refreshTabs(); - const tabs = firefox.getTabs(); - const newTab = tabs[newIdx]; - - return successResponse( - `✅ Created new page [${newIdx}] and navigated to: ${url}\n` + - ` Title: ${newTab?.title || 'Loading...'}\n` + - ` Total pages: ${tabs.length}` - ); + return successResponse(`✅ new page [${newIdx}] → ${url}`); } catch (error) { return errorResponse(error as Error); } @@ -175,9 +161,7 @@ export async function handleNavigatePage(args: unknown): Promise const tabs = firefox.getTabs(); let selectedIdx: number; - let selectionMethod: string; // Priority 1: Select by index if (typeof pageIdx === 'number') { selectedIdx = pageIdx; - selectionMethod = 'by index'; } // Priority 2: Select by URL pattern else if (url && typeof url === 'string') { const urlLower = url.toLowerCase(); const foundIdx = tabs.findIndex((tab) => tab.url?.toLowerCase().includes(urlLower)); - if (foundIdx === -1) { - throw new Error( - `No page found with URL matching "${url}". Use list_pages to see all available pages.` - ); + throw new Error(`No page matching URL "${url}"`); } selectedIdx = foundIdx; - selectionMethod = `by URL pattern "${url}"`; } // Priority 3: Select by title pattern else if (title && typeof title === 'string') { const titleLower = title.toLowerCase(); const foundIdx = tabs.findIndex((tab) => tab.title?.toLowerCase().includes(titleLower)); - if (foundIdx === -1) { - throw new Error( - `No page found with title matching "${title}". Use list_pages to see all available pages.` - ); + throw new Error(`No page matching title "${title}"`); } selectedIdx = foundIdx; - selectionMethod = `by title pattern "${title}"`; } else { - throw new Error( - 'At least one of pageIdx, url, or title must be provided. Use list_pages to see available pages.' - ); + throw new Error('Provide pageIdx, url, or title'); } // Validate the selected index - const page = tabs[selectedIdx]; - if (!page) { - throw new Error( - `Page at index ${selectedIdx} not found. Use list_pages to see valid indices.` - ); + if (!tabs[selectedIdx]) { + throw new Error(`Page [${selectedIdx}] not found`); } // Select the tab await firefox.selectTab(selectedIdx); - return successResponse( - `✅ Selected page [${selectedIdx}] ${selectionMethod}\n` + - ` Title: ${page.title || 'Untitled'}\n` + - ` URL: ${page.url || 'about:blank'}` - ); + return successResponse(`✅ selected [${selectedIdx}]`); } catch (error) { return errorResponse(error as Error); } @@ -276,9 +241,7 @@ export async function handleClosePage(args: unknown): Promise { await firefox.closeTab(pageIdx); - return successResponse( - `✅ Closed page [${pageIdx}]: ${pageToClose.title}\n ${pageToClose.url}` - ); + return successResponse(`✅ closed [${pageIdx}]`); } catch (error) { return errorResponse(error as Error); } diff --git a/src/tools/screenshot.ts b/src/tools/screenshot.ts index 67130d9..a9a3e81 100644 --- a/src/tools/screenshot.ts +++ b/src/tools/screenshot.ts @@ -2,12 +2,7 @@ * Screenshot tools for visual capture */ -import { - successResponse, - errorResponse, - TOKEN_LIMITS, - estimateTokens, -} from '../utils/response-helpers.js'; +import { successResponse, errorResponse, TOKEN_LIMITS } from '../utils/response-helpers.js'; import type { McpToolResponse } from '../types/common.js'; // Tool definitions @@ -38,31 +33,18 @@ export const screenshotByUidTool = { /** * Build screenshot response with size safeguards. */ -function buildScreenshotResponse(base64Png: string, context: string): McpToolResponse { +function buildScreenshotResponse(base64Png: string, label: string): McpToolResponse { const sizeKB = Math.round(base64Png.length / 1024); - const estimatedTokens = estimateTokens(base64Png); // Check if screenshot exceeds size limit if (base64Png.length > TOKEN_LIMITS.MAX_SCREENSHOT_CHARS) { const truncatedData = base64Png.slice(0, TOKEN_LIMITS.MAX_SCREENSHOT_CHARS); - return successResponse( - `📸 ${context} (${sizeKB}KB)\n\n` + - `⚠️ Screenshot truncated (~${Math.round(estimatedTokens / 1000)}k tokens exceeds limit)\n` + - `Only first ${Math.round(TOKEN_LIMITS.MAX_SCREENSHOT_CHARS / 1024)}KB shown.\n` + - `TIP: For full screenshots, use a dedicated screenshot tool or save to file.\n\n` + - `Base64 PNG data (truncated):\n${truncatedData}\n\n[...truncated]` - ); + return successResponse(`📸 ${label} (${sizeKB}KB) [truncated]\n${truncatedData}`); } - // Add warning for large but not truncated screenshots - let warning = ''; - if (base64Png.length > TOKEN_LIMITS.WARNING_THRESHOLD_CHARS) { - warning = `⚠️ Large screenshot (~${Math.round(estimatedTokens / 1000)}k tokens) - may fill context quickly\n\n`; - } - - return successResponse( - `📸 ${context} (${sizeKB}KB)\n\n` + warning + `Base64 PNG data:\n${base64Png}` - ); + // Add warning for large screenshots + const warn = base64Png.length > TOKEN_LIMITS.WARNING_THRESHOLD_CHARS ? ' [large]' : ''; + return successResponse(`📸 ${label} (${sizeKB}KB)${warn}\n${base64Png}`); } // Handlers @@ -73,19 +55,13 @@ export async function handleScreenshotPage(_args: unknown): Promise maxLines; const displayLines = truncated ? lines.slice(0, maxLines) : lines; - // Build output with guidance - let output = '📸 Snapshot taken\n\n'; - - // Add warning if maxLines was capped + // Build compact output + let output = `📸 Snapshot (id=${snapshot.json.snapshotId})`; if (wasCapped) { - output += `⚠️ maxLines capped at ${TOKEN_LIMITS.MAX_SNAPSHOT_LINES_CAP} (requested: ${requestedMaxLines}) to prevent token overflow\n\n`; + output += ` [maxLines capped: ${TOKEN_LIMITS.MAX_SNAPSHOT_LINES_CAP}]`; } - - // Add guidance section - output += '═══ HOW TO USE THIS SNAPSHOT ═══\n'; - output += - '• To interact with elements: use click_by_uid, hover_by_uid, or fill_by_uid with the UID\n'; - output += '• After navigation: always call take_snapshot again (UIDs become stale)\n'; - output += '• On stale UID errors: call take_snapshot → retry your action\n'; - output += '═════════════════════════════════\n\n'; - - // Add snapshot metadata - output += `Snapshot ID: ${snapshot.json.snapshotId}\n`; if (snapshot.json.truncated) { - output += '⚠️ Snapshot content was truncated (too many elements in DOM)\n'; + output += ' [DOM truncated]'; } - output += '\n'; + output += '\n\n'; // Add snapshot tree output += displayLines.join('\n'); if (truncated) { - output += `\n\n... and ${lines.length - maxLines} more lines (use maxLines parameter to see more)`; + output += `\n\n[+${lines.length - maxLines} lines, use maxLines to see more]`; } return successResponse(output); @@ -153,22 +140,18 @@ export async function handleResolveUidToSelector(args: unknown): Promise