algorembrant's picture
Upload 44 files
668d995 verified
/**
* General Web Interaction MCP Tools
* Provides tools for interacting with any website
*/
import { z } from 'zod';
import { browserManager } from '../browser.js';
// Tool schemas
export const navigateSchema = z.object({
url: z.string().url().describe('The URL to navigate to'),
});
export const screenshotSchema = z.object({
fullPage: z.boolean().optional().default(false).describe('Whether to capture the full page'),
});
export const clickSchema = z.object({
selector: z.string().describe('CSS selector of the element to click'),
});
export const typeSchema = z.object({
selector: z.string().describe('CSS selector of the input field'),
text: z.string().describe('Text to type'),
});
export const extractTextSchema = z.object({
selector: z.string().optional().describe('CSS selector to extract text from (default: body)'),
});
export const executeJsSchema = z.object({
script: z.string().describe('JavaScript code to execute on the page'),
});
export const waitForSchema = z.object({
selector: z.string().describe('CSS selector to wait for'),
timeout: z.number().optional().default(5000).describe('Timeout in milliseconds'),
});
/**
* Navigate to a URL
*/
export async function navigate(url: string): Promise<{ success: boolean; title?: string; error?: string }> {
try {
const page = await browserManager.getPage();
await page.goto(url);
await page.waitForLoadState('networkidle');
const title = await page.title();
return { success: true, title };
} catch (error) {
return { success: false, error: String(error) };
}
}
/**
* Take a screenshot of the current page
*/
export async function screenshot(fullPage: boolean = false): Promise<{
success: boolean;
base64?: string;
error?: string
}> {
try {
const page = await browserManager.getPage();
const buffer = await page.screenshot({ fullPage });
const base64 = buffer.toString('base64');
return { success: true, base64 };
} catch (error) {
return { success: false, error: String(error) };
}
}
/**
* Click on an element
*/
export async function click(selector: string): Promise<{ success: boolean; error?: string }> {
try {
const page = await browserManager.getPage();
await page.click(selector);
return { success: true };
} catch (error) {
return { success: false, error: String(error) };
}
}
/**
* Type text into an input field
*/
export async function type(selector: string, text: string): Promise<{ success: boolean; error?: string }> {
try {
const page = await browserManager.getPage();
await page.fill(selector, text);
return { success: true };
} catch (error) {
return { success: false, error: String(error) };
}
}
/**
* Extract text content from the page or specific element
*/
export async function extractText(selector?: string): Promise<{
success: boolean;
text?: string;
error?: string
}> {
try {
const page = await browserManager.getPage();
const element = selector ? await page.$(selector) : await page.$('body');
if (!element) {
return { success: false, error: `Element not found: ${selector || 'body'}` };
}
const text = await element.textContent();
return { success: true, text: text?.trim() || '' };
} catch (error) {
return { success: false, error: String(error) };
}
}
/**
* Execute JavaScript on the page
*/
export async function executeJs(script: string): Promise<{
success: boolean;
result?: unknown;
error?: string
}> {
try {
const page = await browserManager.getPage();
const result = await page.evaluate(script);
return { success: true, result };
} catch (error) {
return { success: false, error: String(error) };
}
}
/**
* Wait for an element to appear
*/
export async function waitFor(selector: string, timeout: number = 5000): Promise<{
success: boolean;
error?: string
}> {
try {
const page = await browserManager.getPage();
await page.waitForSelector(selector, { timeout });
return { success: true };
} catch (error) {
return { success: false, error: String(error) };
}
}
/**
* Get the current page URL
*/
export async function getCurrentUrl(): Promise<{ url: string; error?: string }> {
try {
const page = await browserManager.getPage();
return { url: page.url() };
} catch (error) {
return { url: '', error: String(error) };
}
}
/**
* Open a new tab and return its index
*/
export async function newTab(url?: string): Promise<{ index: number; success: boolean; error?: string }> {
try {
const page = await browserManager.getPage();
const context = page.context();
const newPage = await context.newPage();
// Update active page in manager
await browserManager.setActivePage(newPage);
if (url) {
await newPage.goto(url);
await newPage.waitForLoadState('networkidle');
}
const index = context.pages().length - 1;
return { index, success: true };
} catch (error) {
return { index: -1, success: false, error: String(error) };
}
}
/**
* Close the current tab
*/
export async function closeTab(): Promise<{ success: boolean; error?: string }> {
try {
const page = await browserManager.getPage();
const context = page.context();
const pages = context.pages();
if (pages.length <= 1) {
return { success: false, error: 'Cannot close the last remaining tab' };
}
await page.close();
// Set active page to the last one
const remainingPages = context.pages();
await browserManager.setActivePage(remainingPages[remainingPages.length - 1]);
return { success: true };
} catch (error) {
return { success: false, error: String(error) };
}
}
/**
* List all open tabs in the current browser context
*/
export async function listTabs(): Promise<{
tabs: { title: string; url: string; index: number }[];
error?: string
}> {
try {
const page = await browserManager.getPage();
const context = page.context();
const pages = context.pages();
const tabs = await Promise.all(pages.map(async (p, i) => ({
title: await p.title(),
url: p.url(),
index: i
})));
return { tabs };
} catch (error) {
return { tabs: [], error: String(error) };
}
}
/**
* Switch to a specific tab by index
*/
export async function switchTab(index: number): Promise<{ success: boolean; error?: string }> {
try {
const page = await browserManager.getPage();
const context = page.context();
const pages = context.pages();
if (index < 0 || index >= pages.length) {
return { success: false, error: `Invalid tab index: ${index}` };
}
// In Playwright, we conceptually just "use" the other page object
// For MCP, we'll tell the BrowserManager to update its active page
await browserManager.setActivePage(pages[index]);
return { success: true };
} catch (error) {
return { success: false, error: String(error) };
}
}
/**
* Get page accessibility snapshot for AI understanding
*/
export async function getAccessibilitySnapshot(): Promise<{
success: boolean;
snapshot?: unknown;
error?: string
}> {
try {
const page = await browserManager.getPage();
// Use locator-based approach for accessibility info
const bodyHandle = await page.$('body');
if (!bodyHandle) {
return { success: false, error: 'Could not get page body' };
}
// Get a simplified accessibility tree by extracting key elements
const snapshot = await page.evaluate(() => {
const getAccessibleTree = (element: Element, depth = 0): object | null => {
if (depth > 5) return null;
const role = element.getAttribute('role') || element.tagName.toLowerCase();
const label = element.getAttribute('aria-label') ||
element.getAttribute('title') ||
(element as HTMLElement).innerText?.slice(0, 100);
const children: object[] = [];
for (const child of element.children) {
const childTree = getAccessibleTree(child as Element, depth + 1);
if (childTree) children.push(childTree);
}
return {
role,
name: label || undefined,
children: children.length > 0 ? children : undefined,
};
};
return getAccessibleTree(document.body);
});
return { success: true, snapshot };
} catch (error) {
return { success: false, error: String(error) };
}
}