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
),
},
],
};
}