Skip to main content
Glama
navigate.ts8.05 kB
import { BrowserToolBase } from '../base.js'; import { ToolContext, ToolResponse, ToolMetadata, SessionConfig, createSuccessResponse, createErrorResponse } from '../../common/types.js'; import { gatherConsoleErrorsSince, quickNetworkIdleNote } from '../common/postAction.js'; async function resetState() { const { resetBrowserState } = await import('../../../toolHandler.js'); resetBrowserState(); } async function getLogsSinceLastNav(): Promise<string[]> { const { getConsoleLogsSinceLastNavigation } = await import('../../../toolHandler.js'); return getConsoleLogsSinceLastNavigation(); } /** * Tool for navigating to URLs */ export class NavigateTool extends BrowserToolBase { static getMetadata(sessionConfig?: SessionConfig): ToolMetadata { const sessionEnabled = sessionConfig?.saveSession ?? true; const userDataDir = sessionConfig?.userDataDir || './.mcp-web-inspector/user-data'; const headlessDefault = sessionConfig?.headlessDefault ?? false; const description = sessionEnabled ? `Navigate to a URL. Browser sessions (cookies, localStorage, sessionStorage) are automatically saved in ${userDataDir} directory and persist across restarts. To clear saved sessions, delete the directory.` : "Navigate to a URL. Browser starts fresh each time with no persistent session state (started with --no-save-session flag)."; return { name: "navigate", description, inputSchema: { type: "object", properties: { url: { type: "string", description: "URL to navigate to the website specified" }, browserType: { type: "string", description: "Browser type to use (chromium, firefox, webkit). Defaults to chromium", enum: ["chromium", "firefox", "webkit"] }, device: { type: "string", description: "Device preset to emulate. Uses device configurations for viewport, user agent, and device scale factor. When specified, overrides width/height parameters. Mobile: iphone-se, iphone-14, iphone-14-pro, pixel-5, ipad, samsung-s21. Desktop: desktop-1080p (1920x1080), desktop-2k (2560x1440), laptop-hd (1366x768).", enum: ["iphone-se", "iphone-14", "iphone-14-pro", "pixel-5", "ipad", "samsung-s21", "desktop-1080p", "desktop-2k", "laptop-hd"] }, width: { type: "number", description: "Viewport width in pixels. If not specified, automatically matches screen width. Ignored if device is specified." }, height: { type: "number", description: "Viewport height in pixels. If not specified, automatically matches screen height. Ignored if device is specified." }, timeout: { type: "number", description: "Navigation timeout in milliseconds" }, waitUntil: { type: "string", description: "Navigation wait condition" }, headless: { type: "boolean", description: `Run browser in headless mode (default: ${headlessDefault ? 'true - no window shown' : 'false - browser window visible'})` } }, required: ["url"], }, }; } async execute(args: any, context: ToolContext): Promise<ToolResponse> { // Check if browser is available if (!context.browser || !context.browser.isConnected()) { // If browser is not connected, we need to reset the state to force recreation await resetState(); return createErrorResponse( "Browser is not connected. The connection has been reset - please retry your navigation." ); } // Check if page is available and not closed if (!context.page || context.page.isClosed()) { return createErrorResponse( "Page is not available or has been closed. Please retry your navigation." ); } this.recordNavigation(); return this.safeExecute(context, async (page) => { const sleep = (ms: number) => new Promise(res => setTimeout(res, ms)); try { await page.goto(args.url, { timeout: args.timeout || 30000, waitUntil: args.waitUntil || "load" }); // Detect common Next.js dev boot error and auto-reload up to N times // Example logs: // "Uncaught SyntaxError: Invalid or unexpected token (at layout.js:62:29)" // "main-app.js:... Download the React DevTools ..." // "error-boundary-callbacks.js:83" try { const maxRetries = 2; // keep parameters minimal const delay = 800; // ms let attempts = 0; // Give the console a brief moment to emit init errors if (delay) await sleep(delay); while (attempts <= maxRetries) { const logs = await getLogsSinceLastNav(); const hasInvalidToken = logs.some(l => l.toLowerCase().includes('invalid or unexpected token')); const hasNextMarkers = logs.some(l => l.includes('main-app.js') || l.includes('error-boundary-callbacks.js') || l.toLowerCase().includes('download the react devtools') ); if (hasInvalidToken && hasNextMarkers && attempts < maxRetries) { attempts++; this.recordNavigation(); await page.reload({ timeout: args.timeout || 30000, waitUntil: args.waitUntil || 'load' }); if (delay) await sleep(delay); continue; } // Either no error pattern or we exhausted retries if (attempts > 0 && hasInvalidToken && hasNextMarkers) { return createSuccessResponse(`Navigated to ${args.url} (attempted ${attempts} auto-reload(s); init error persisted)`); } if (attempts > 0) { return createSuccessResponse(`Navigated to ${args.url} (auto-reloaded ${attempts} time(s) after detecting Next.js init error)`); } // No error detected on first attempt break; } } catch { // Best-effort detection; ignore and proceed } // First, perform a quick network-idle check to allow any errors to flush // before we examine console errors. Keep it best-effort with small timeout. const messages: string[] = [`Navigated to ${args.url}`]; try { const note = await quickNetworkIdleNote(page); if (note) messages.push(note); } catch { // Ignore failures in the quick check } // After waiting briefly, surface hard page errors if any try { const errs = await gatherConsoleErrorsSince('navigation'); if (errs.length > 0) { // Include page title (best-effort) to aid debugging let titleInfo = ''; try { const t = await page.title(); if (t) titleInfo = `\nTitle: ${t}`; } catch {} return createErrorResponse(`Console error after navigation: ${errs[0]}${titleInfo}`); } } catch { // If log retrieval fails, continue normally } // Add page title to help the agent orient itself try { const title = await page.title(); if (title) messages.push(`Title: ${title}`); } catch { // Ignore title failures } return createSuccessResponse(messages); } catch (error) { const errorMessage = (error as Error).message; // Check for common disconnection errors if ( errorMessage.includes("Target page, context or browser has been closed") || errorMessage.includes("Target closed") || errorMessage.includes("Browser has been disconnected") ) { // Reset browser state to force recreation on next attempt await resetState(); return createErrorResponse( `Browser connection issue: ${errorMessage}. Connection has been reset - please retry your navigation.` ); } // For other errors, return the standard error throw error; } }); } }

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