navigate
Navigate to a URL in a real browser with configurable device emulation and viewport settings. Browser sessions persist for testing continuity.
Instructions
Navigate to a URL. Browser sessions (cookies, localStorage, sessionStorage) are automatically saved in ./.mcp-web-inspector/user-data directory and persist across restarts. To clear saved sessions, delete the directory.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | URL to navigate to the website specified | |
| browserType | No | Browser type to use (chromium, firefox, webkit). Defaults to chromium | |
| device | No | 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). | |
| width | No | Viewport width in pixels. If not specified, automatically matches screen width. Ignored if device is specified. | |
| height | No | Viewport height in pixels. If not specified, automatically matches screen height. Ignored if device is specified. | |
| timeout | No | Navigation timeout in milliseconds | |
| waitUntil | No | Navigation wait condition | |
| headless | No | Run browser in headless mode (no visible window). Defaults to visible on desktop, headless on Linux without a display or when --headless is passed. |
Implementation Reference
- NavigateTool class: the main handler for the 'navigate' tool. Extends BrowserToolBase. getMetadata() returns tool name 'navigate', description, annotations, and inputSchema (url required, optional browserType/device/width/height/timeout/waitUntil/headless). execute() checks browser/page connection, then calls page.goto() with optional auto-reload for Next.js init errors, followed by network idle check, page title retrieval, and console error collection.
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 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, annotations: ANNOTATIONS.navigation, 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 (no visible window). Defaults to visible on desktop, headless on Linux without a display or when --headless is passed." } }, 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 } // 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 } // Surface any console errors observed during navigation as a warning, not a failure. // The page loaded; pre-existing app errors (analytics, flag SDKs, etc.) shouldn't // make navigate() return isError=true. try { const errs = await gatherConsoleErrorsSince('navigation'); if (errs.length > 0) { messages.push(''); messages.push(`⚠ Console errors observed during navigation (${errs.length}):`); errs.slice(0, 3).forEach(e => messages.push(` ${e}`)); if (errs.length > 3) messages.push(` …and ${errs.length - 3} more (use get_console_logs)`); } } catch { // If log retrieval fails, continue normally } 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; } }); } } - Input schema for the navigate tool: requires 'url' (string), optional browserType, device, width, height, timeout, waitUntil, headless. Annotations set to ANNOTATIONS.navigation (readOnlyHint=false, idempotentHint=true, openWorldHint=true).
return { name: "navigate", description, annotations: ANNOTATIONS.navigation, 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 (no visible window). Defaults to visible on desktop, headless on Linux without a display or when --headless is passed." } }, required: ["url"], }, }; - src/tools/browser/register.ts:53-104 (registration)NavigateTool is registered in the BROWSER_TOOL_CLASSES array (line 55) which is consumed by registerTools(BROWSER_TOOL_CLASSES) in src/tools/common/registry.ts (line 65).
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, ElementExistsTool, MeasureElementTool, GetComputedStylesTool, // Evaluation (1) EvaluateTool, // Console (2) GetConsoleLogsTool, ClearConsoleLogsTool, // Network (2) ListNetworkRequestsTool, GetRequestDetailsTool, // Waiting (2) WaitForElementTool, WaitForNetworkIdleTool, ]; - Helper functions used by NavigateTool.execute(): resetState() calls resetBrowserState() to force browser recreation, getLogsSinceLastNav() retrieves console logs since the last navigation (used for Next.js error detection).
async function resetState() { const { resetBrowserState } = await import('../../../toolHandler.js'); resetBrowserState(); } async function getLogsSinceLastNav(): Promise<string[]> { const { getConsoleLogsSinceLastNavigation } = await import('../../../toolHandler.js'); return getConsoleLogsSinceLastNavigation(); } - gatherConsoleErrorsSince() and quickNetworkIdleNote() are helper utilities imported by navigate.ts. gatherConsoleErrorsSince('navigation') filters console logs for errors/exceptions since the last navigation. quickNetworkIdleNote() performs a best-effort network idle check.
export async function gatherConsoleErrorsSince(since: 'navigation' | 'interaction'): Promise<string[]> { const { getConsoleLogsSinceLastNavigation, getConsoleLogsSinceLastInteraction } = await import('../../../toolHandler.js'); const logs: string[] = since === 'navigation' ? getConsoleLogsSinceLastNavigation() : getConsoleLogsSinceLastInteraction(); return logs.filter(l => l.startsWith('[error]') || l.startsWith('[exception]')); } // Provide a compact, best-effort network idle note export async function quickNetworkIdleNote(page: Page): Promise<string> { try { const start = Date.now(); const anyPage: any = page as any; const wait = anyPage?.waitForLoadState?.bind(page); if (typeof wait === 'function') { await wait('networkidle', { timeout: 500 }); const ms = Date.now() - start; return `✓ Network idle after ${ms}ms, 0 pending requests`; } } catch { // fall through to no-activity note } return 'No new network activity detected (quick check)'; }