Skip to main content
Glama
browserTools.ts21.1 kB
import type { Page } from 'puppeteer'; import { browserState, clearConsoleLogs, clearNetworkRequests, initBrowser, getBrowserStatus, } from './browser.js'; import type { NavigateArgs, GetDomArgs, GetElementArgs, ExecuteJsArgs, ScreenshotArgs, GetConsoleLogsArgs, GetNetworkActivityArgs, GetPageSourceArgs, QuerySelectorAllArgs, ClickElementArgs, TypeTextArgs, EvaluateXPathArgs, OpenBrowserArgs, LoginArgs, BrowserStatus, ElementInfo, PageInfo, PageSource, ElementSummary, } from './types.js'; // Tipo de retorno compatível com MCP SDK export type ToolResponse = | { content: Array<{ type: 'text'; text: string; }>; isError?: boolean; } | { content: Array<{ type: 'text'; text: string; }>; }; /** * Handler para a ferramenta navigate */ export async function handleNavigate(args: unknown, currentPage: Page): Promise<ToolResponse> { const typedArgs = args as unknown as NavigateArgs; const { url, waitUntil = 'networkidle2', timeout = 30000 } = typedArgs; // Limpar logs e requisições clearConsoleLogs(); clearNetworkRequests(); await currentPage.goto(url, { waitUntil: waitUntil, timeout: timeout, }); const title = await currentPage.title(); const finalUrl = currentPage.url(); return { content: [ { type: 'text', text: JSON.stringify( { success: true, title: title, url: finalUrl, message: `Navegado para ${finalUrl}`, }, null, 2 ), }, ], }; } /** * Handler para a ferramenta get_dom */ export async function handleGetDom(args: unknown, currentPage: Page): Promise<ToolResponse> { const typedArgs = args as unknown as GetDomArgs; const { prettify = true } = typedArgs; let html = await currentPage.content(); if (prettify) { html = await currentPage.evaluate(() => { const div = document.createElement('div'); div.innerHTML = document.documentElement.outerHTML; return div.innerHTML; }); } return { content: [ { type: 'text', text: html, }, ], }; } /** * Handler para a ferramenta get_element */ export async function handleGetElement(args: unknown, currentPage: Page): Promise<ToolResponse> { const typedArgs = args as unknown as GetElementArgs; const { selector, includeStyles = true, includeAttributes = true } = typedArgs; const elementInfo = await currentPage.evaluate( (sel: string, incStyles: boolean, incAttrs: boolean): ElementInfo | null => { const element = document.querySelector(sel); if (!element) return null; const info: ElementInfo = { tagName: element.tagName, innerHTML: element.innerHTML, outerHTML: element.outerHTML, textContent: element.textContent || '', boundingBox: { x: 0, y: 0, width: 0, height: 0 }, }; if (incAttrs) { info.attributes = {}; for (let i = 0; i < element.attributes.length; i++) { const attr = element.attributes[i]; info.attributes[attr.name] = attr.value; } } if (incStyles) { const styles = window.getComputedStyle(element); info.computedStyles = {}; for (let i = 0; i < styles.length; i++) { const prop = styles[i]; info.computedStyles[prop] = styles.getPropertyValue(prop); } } const rect = element.getBoundingClientRect(); info.boundingBox = { x: rect.x, y: rect.y, width: rect.width, height: rect.height, }; return info; }, selector, includeStyles, includeAttributes ); if (!elementInfo) { return { content: [ { type: 'text', text: JSON.stringify({ error: `Elemento não encontrado: ${selector}` }, null, 2), }, ], }; } return { content: [ { type: 'text', text: JSON.stringify(elementInfo, null, 2), }, ], }; } /** * Handler para a ferramenta execute_js */ export async function handleExecuteJs(args: unknown, currentPage: Page): Promise<ToolResponse> { const typedArgs = args as unknown as ExecuteJsArgs; const { code } = typedArgs; const result = await currentPage.evaluate((jsCode: string) => { try { const func = new Function(jsCode); return { success: true, result: func() }; } catch (error) { const err = error as Error; return { success: false, error: err.message, stack: err.stack }; } }, code); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } /** * Handler para a ferramenta screenshot */ export async function handleScreenshot(args: unknown, currentPage: Page): Promise<ToolResponse> { const typedArgs = args as unknown as ScreenshotArgs; const { selector, fullPage = false, path } = typedArgs; const screenshotOptions: { fullPage: boolean; path?: string; } = { fullPage }; if (path) screenshotOptions.path = path; let screenshot: Buffer | string; if (selector) { const element = await currentPage.$(selector); if (!element) { return { content: [ { type: 'text', text: JSON.stringify({ error: `Elemento não encontrado: ${selector}` }), }, ], }; } const result = await element.screenshot(screenshotOptions); screenshot = Buffer.from(result); } else { const result = await currentPage.screenshot(screenshotOptions); screenshot = Buffer.from(result); } return { content: [ { type: 'text', text: JSON.stringify( { success: true, message: path ? `Screenshot salva em: ${path}` : 'Screenshot capturada', base64: typeof screenshot === 'string' ? screenshot : screenshot.toString('base64'), }, null, 2 ), }, ], }; } /** * Handler para a ferramenta get_console_logs */ export async function handleGetConsoleLogs(args: unknown): Promise<ToolResponse> { const typedArgs = args as unknown as GetConsoleLogsArgs; const { filterType = 'all', clear = false } = typedArgs; let logs = browserState.consoleLogs; if (filterType !== 'all') { logs = logs.filter((log) => log.type === filterType); } const result = { count: logs.length, logs: logs, }; if (clear) { clearConsoleLogs(); } return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } /** * Handler para a ferramenta get_network_activity */ export async function handleGetNetworkActivity(args: unknown): Promise<ToolResponse> { const typedArgs = args as unknown as GetNetworkActivityArgs; const { filterType = 'all', clear = false } = typedArgs; let activity = browserState.networkRequests; if (filterType !== 'all') { activity = activity.filter((req) => req.type === filterType); } const result = { count: activity.length, requests: activity, }; if (clear) { clearNetworkRequests(); } return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } /** * Handler para a ferramenta get_page_source */ export async function handleGetPageSource(args: unknown, currentPage: Page): Promise<ToolResponse> { const typedArgs = args as unknown as GetPageSourceArgs; const { includeScripts = true, includeStyles = true } = typedArgs; const sources = await currentPage.evaluate( (incScripts: boolean, incStyles: boolean): PageSource => { const result: PageSource = { html: document.documentElement.outerHTML, scripts: [], styles: [], links: [], }; if (incScripts) { document.querySelectorAll('script').forEach((script) => { result.scripts.push({ src: script.src || null, inline: !script.src, content: script.src ? null : script.textContent, type: script.type || 'text/javascript', }); }); } if (incStyles) { document.querySelectorAll('style').forEach((style) => { result.styles.push({ inline: true, content: style.textContent || '', }); }); document.querySelectorAll('link[rel="stylesheet"]').forEach((link) => { result.styles.push({ inline: false, href: (link as HTMLLinkElement).href, }); }); } document.querySelectorAll('link').forEach((link) => { result.links.push({ rel: link.rel, href: link.href, type: link.type, }); }); return result; }, includeScripts, includeStyles ); return { content: [ { type: 'text', text: JSON.stringify(sources, null, 2), }, ], }; } /** * Handler para a ferramenta query_selector_all */ export async function handleQuerySelectorAll( args: unknown, currentPage: Page ): Promise<ToolResponse> { const typedArgs = args as unknown as QuerySelectorAllArgs; const { selector, limit = 100 } = typedArgs; const elements = await currentPage.evaluate( (sel: string, lim: number): ElementSummary[] => { const elements = Array.from(document.querySelectorAll(sel)); return elements.slice(0, lim).map((el) => ({ tagName: el.tagName, className: (el as HTMLElement).className || '', id: (el as HTMLElement).id || '', textContent: (el.textContent || '').substring(0, 100), outerHTML: el.outerHTML.substring(0, 200), })); }, selector, limit ); return { content: [ { type: 'text', text: JSON.stringify( { count: elements.length, elements: elements, }, null, 2 ), }, ], }; } /** * Handler para a ferramenta get_page_info */ export async function handleGetPageInfo(currentPage: Page): Promise<ToolResponse> { const info = await currentPage.evaluate((): PageInfo => { return { title: document.title, url: window.location.href, domain: window.location.hostname, path: window.location.pathname, protocol: window.location.protocol, referrer: document.referrer, characterSet: document.characterSet, contentType: document.contentType, readyState: document.readyState, lastModified: document.lastModified, viewport: { width: window.innerWidth, height: window.innerHeight, }, screen: { width: window.screen.width, height: window.screen.height, }, documentElement: { scrollHeight: document.documentElement.scrollHeight, scrollWidth: document.documentElement.scrollWidth, }, }; }); return { content: [ { type: 'text', text: JSON.stringify(info, null, 2), }, ], }; } /** * Handler para a ferramenta click_element */ export async function handleClickElement(args: unknown, currentPage: Page): Promise<ToolResponse> { const typedArgs = args as unknown as ClickElementArgs; const { selector, waitForNavigation = false } = typedArgs; if (waitForNavigation) { await Promise.all([currentPage.waitForNavigation(), currentPage.click(selector)]); } else { await currentPage.click(selector); } return { content: [ { type: 'text', text: JSON.stringify({ success: true, message: `Elemento clicado: ${selector}`, }), }, ], }; } /** * Handler para a ferramenta type_text */ export async function handleTypeText(args: unknown, currentPage: Page): Promise<ToolResponse> { const typedArgs = args as unknown as TypeTextArgs; const { selector, text, clearFirst = true } = typedArgs; if (clearFirst) { await currentPage.click(selector, { clickCount: 3 }); } await currentPage.type(selector, text); return { content: [ { type: 'text', text: JSON.stringify({ success: true, message: `Texto digitado em: ${selector}`, }), }, ], }; } /** * Handler para a ferramenta get_local_storage */ export async function handleGetLocalStorage(currentPage: Page): Promise<ToolResponse> { const storage = await currentPage.evaluate((): Record<string, string> => { const items: Record<string, string> = {}; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key) { items[key] = localStorage.getItem(key) || ''; } } return items; }); return { content: [ { type: 'text', text: JSON.stringify(storage, null, 2), }, ], }; } /** * Handler para a ferramenta get_cookies */ export async function handleGetCookies(currentPage: Page): Promise<ToolResponse> { const cookies = await currentPage.cookies(); return { content: [ { type: 'text', text: JSON.stringify(cookies, null, 2), }, ], }; } /** * Handler para a ferramenta evaluate_xpath */ export async function handleEvaluateXPath(args: unknown, currentPage: Page): Promise<ToolResponse> { const typedArgs = args as unknown as EvaluateXPathArgs; const { xpath } = typedArgs; const elements = await currentPage.evaluate((xp: string) => { const result = document.evaluate( xp, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null ); const nodes = []; for (let i = 0; i < result.snapshotLength; i++) { const node = result.snapshotItem(i) as Element; nodes.push({ tagName: node.tagName, textContent: (node.textContent || '').substring(0, 100), outerHTML: node.outerHTML.substring(0, 200), }); } return nodes; }, xpath); return { content: [ { type: 'text', text: JSON.stringify( { count: elements.length, elements: elements, }, null, 2 ), }, ], }; } /** * Handler para a ferramenta open_browser * Abre o browser em modo visível (headful) para você ver o que está acontecendo */ export async function handleOpenBrowser(args: unknown): Promise<ToolResponse> { const typedArgs = args as unknown as OpenBrowserArgs; const { url, headless = false, width = 1920, height = 1080 } = typedArgs; // Inicializa o browser em modo headful com dimensões corretas const page = await initBrowser(headless, width, height); // Se URL foi fornecida, navega if (url) { await page.goto(url, { waitUntil: 'networkidle2' }); } const currentUrl = url ? page.url() : 'about:blank'; const title = url ? await page.title() : 'Browser aberto'; return { content: [ { type: 'text', text: JSON.stringify( { success: true, mode: headless ? 'headless' : 'headful (visível)', url: currentUrl, title: title, viewport: { width, height }, message: headless ? 'Browser aberto em modo headless' : '✅ Browser aberto e VISÍVEL! Você pode ver o que o agent está fazendo.', }, null, 2 ), }, ], }; } /** * Handler para a ferramenta login * Faz login automatizado em um site e mantém a sessão aberta */ export async function handleLogin(args: unknown, currentPage: Page): Promise<ToolResponse> { const typedArgs = args as unknown as LoginArgs; const { url, usernameSelector, passwordSelector, submitSelector, username, password, waitForSelector, timeout = 30000, delayAfterType = 500, } = typedArgs; try { // Navega para a página de login await currentPage.goto(url, { waitUntil: 'networkidle2', timeout }); // Aguarda o formulário de login aparecer await currentPage.waitForSelector(usernameSelector, { timeout: 10000 }); // Preenche o campo de username/email await currentPage.type(usernameSelector, username, { delay: 50 }); await new Promise((resolve) => setTimeout(resolve, delayAfterType)); // Preenche o campo de senha await currentPage.type(passwordSelector, password, { delay: 50 }); await new Promise((resolve) => setTimeout(resolve, delayAfterType)); // Clica no botão de submit const navigationPromise = currentPage.waitForNavigation({ waitUntil: 'networkidle2', timeout, }); await currentPage.click(submitSelector); try { await navigationPromise; } catch { // Algumas SPAs não navegam, apenas atualizam o DOM console.log('Navegação não detectada, continuando...'); } // Aguarda um pouco para a página carregar await new Promise((resolve) => setTimeout(resolve, 2000)); // Se foi fornecido um seletor de verificação, aguarda ele if (waitForSelector) { try { await currentPage.waitForSelector(waitForSelector, { timeout: 10000 }); } catch { return { content: [ { type: 'text', text: JSON.stringify( { success: false, error: `Seletor de verificação '${waitForSelector}' não encontrado. Login pode ter falhado.`, currentUrl: currentPage.url(), }, null, 2 ), }, ], isError: true, }; } } const finalUrl = currentPage.url(); const title = await currentPage.title(); // Captura cookies para verificar se login funcionou const cookies = await currentPage.cookies(); return { content: [ { type: 'text', text: JSON.stringify( { success: true, message: '✅ Login realizado com sucesso! Sessão mantida aberta.', url: finalUrl, title: title, cookiesCount: cookies.length, note: 'A sessão de login está ativa. Você pode usar outras ferramentas para inspecionar páginas autenticadas.', }, null, 2 ), }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: JSON.stringify( { success: false, error: errorMessage, currentUrl: currentPage.url(), tip: 'Verifique se os seletores CSS estão corretos e se a página carregou completamente.', }, null, 2 ), }, ], isError: true, }; } } /** * Handler para a ferramenta get_browser_status * Verifica se o browser está aberto e retorna informações sobre o estado atual */ export async function handleGetBrowserStatus(): Promise<ToolResponse> { const status = getBrowserStatus(); let pageTitle: string | null = null; if (browserState.page) { try { pageTitle = await browserState.page.title(); } catch { pageTitle = 'Erro ao obter título'; } } const statusInfo: BrowserStatus = { ...status, pageTitle, message: status.isOpen ? `✅ Browser está ABERTO e ${status.headless ? 'INVISÍVEL (headless)' : 'VISÍVEL (headful)'}. A sessão está ativa!` : '❌ Browser está FECHADO. Use open_browser para abrir.', }; return { content: [ { type: 'text', text: JSON.stringify(statusInfo, null, 2), }, ], }; } /** * Handler para a ferramenta close_browser * Fecha o browser e limpa toda a sessão */ export async function handleCloseBrowser(): Promise<ToolResponse> { const wasOpen = browserState.browser !== null; if (!wasOpen) { return { content: [ { type: 'text', text: JSON.stringify( { success: false, message: '⚠️ O browser já está fechado.', }, null, 2 ), }, ], }; } // Importa closeBrowser do browser.ts const { closeBrowser } = await import('./browser.js'); await closeBrowser(); // Limpa logs e requisições clearConsoleLogs(); clearNetworkRequests(); return { content: [ { type: 'text', text: JSON.stringify( { success: true, message: '✅ Browser fechado com sucesso. Sessão encerrada.', note: 'Todos os cookies, localStorage e sessões de login foram perdidos. Use open_browser para abrir um novo browser.', }, null, 2 ), }, ], }; }

Implementation Reference

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/EmmanuelBarbosaMonteiro/mcp-server-browser'

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