Skip to main content
Glama

firefox-devtools-mcp

pages.ts8.36 kB
/** * Page navigation and management tools for MCP */ import { successResponse, errorResponse } from '../utils/response-helpers.js'; import type { McpToolResponse } from '../types/common.js'; // Tool definitions export const listPagesTool = { name: 'list_pages', description: 'List all open tabs with index, title, and URL. The currently selected tab is marked. Use the index with select_page.', inputSchema: { type: 'object', properties: {}, }, }; export const newPageTool = { name: 'new_page', description: 'Open a new tab and navigate it to the provided URL. Returns the new tab index in the response.', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL to load in a new page', }, }, required: ['url'], }, }; export const navigatePageTool = { name: 'navigate_page', description: 'Navigate the currently selected tab to the provided URL.', inputSchema: { type: 'object', properties: { url: { type: 'string', description: 'URL to navigate the page to', }, }, required: ['url'], }, }; export const selectPageTool = { name: 'select_page', description: 'Select the active tab by index (preferred), or by matching URL/title. Index takes precedence when multiple parameters are provided.', inputSchema: { type: 'object', properties: { pageIdx: { type: 'number', description: 'The index of the page to select (e.g., 0, 1, 2). ' + 'Use list_pages first to see all available page indices. Most reliable method.', }, url: { type: 'string', description: 'Select page by URL (partial match, case-insensitive). ' + 'Example: "github.com" will match "https://github.com/user/repo"', }, title: { type: 'string', description: 'Select page by title (partial match, case-insensitive). ' + 'Example: "Google" will match "Google Search - About"', }, }, required: [], }, }; export const closePageTool = { name: 'close_page', description: 'Close the tab at the given index. Use list_pages to find valid indices.', inputSchema: { type: 'object', properties: { pageIdx: { type: 'number', description: 'The index of the page to close', }, }, required: ['pageIdx'], }, }; /** * Format page list with consistent output */ function formatPageList( tabs: Array<{ title?: string; url?: string }>, 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 lines.join('\n'); } // Handlers export async function handleListPages(_args: unknown): Promise<McpToolResponse> { try { const { getFirefox } = await import('../index.js'); const firefox = await getFirefox(); await firefox.refreshTabs(); const tabs = firefox.getTabs(); const selectedIdx = firefox.getSelectedTabIdx(); return successResponse(formatPageList(tabs, selectedIdx)); } catch (error) { return errorResponse(error as Error); } } export async function handleNewPage(args: unknown): Promise<McpToolResponse> { try { const { url } = args as { url: string }; if (!url || typeof url !== 'string') { throw new Error('url parameter is required and must be a string'); } const { getFirefox } = await import('../index.js'); const firefox = await getFirefox(); 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}` ); } catch (error) { return errorResponse(error as Error); } } export async function handleNavigatePage(args: unknown): Promise<McpToolResponse> { try { const { url } = args as { url: string }; if (!url || typeof url !== 'string') { throw new Error('url parameter is required and must be a string'); } const { getFirefox } = await import('../index.js'); const firefox = await getFirefox(); // Refresh tabs to get latest list await firefox.refreshTabs(); const tabs = firefox.getTabs(); const selectedIdx = firefox.getSelectedTabIdx(); const page = tabs[selectedIdx]; if (!page) { throw new Error('No page selected'); } await firefox.navigate(url); return successResponse( `✅ Navigated page [${selectedIdx}] to: ${url}\n` + ` Previous URL: ${page.url}` ); } catch (error) { return errorResponse(error as Error); } } export async function handleSelectPage(args: unknown): Promise<McpToolResponse> { try { const { pageIdx, url, title } = args as { pageIdx?: number; url?: string; title?: string }; const { getFirefox } = await import('../index.js'); const firefox = await getFirefox(); // Refresh tabs to get latest list await firefox.refreshTabs(); 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.` ); } 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.` ); } 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.' ); } // 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.` ); } // Select the tab await firefox.selectTab(selectedIdx); return successResponse( `✅ Selected page [${selectedIdx}] ${selectionMethod}\n` + ` Title: ${page.title || 'Untitled'}\n` + ` URL: ${page.url || 'about:blank'}` ); } catch (error) { return errorResponse(error as Error); } } export async function handleClosePage(args: unknown): Promise<McpToolResponse> { try { const { pageIdx } = args as { pageIdx: number }; if (typeof pageIdx !== 'number') { throw new Error('pageIdx parameter is required and must be a number'); } const { getFirefox } = await import('../index.js'); const firefox = await getFirefox(); // Refresh tabs to get latest list await firefox.refreshTabs(); const tabs = firefox.getTabs(); const pageToClose = tabs[pageIdx]; if (!pageToClose) { throw new Error(`Page with index ${pageIdx} not found`); } await firefox.closeTab(pageIdx); return successResponse( `✅ Closed page [${pageIdx}]: ${pageToClose.title}\n ${pageToClose.url}` ); } catch (error) { return errorResponse(error as Error); } }

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/freema/firefox-devtools-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server