Skip to main content
Glama
tools.ts36.1 kB
import { Tool } from '@modelcontextprotocol/sdk/types.js'; import { BrowserManager } from './browser-manager.js'; import { ToolResult, NavigationOptions, ClickOptions, TypeOptions, ScreenshotOptions } from './types.js'; export class BrowserTools { constructor(private browserManager: BrowserManager) {} /** * Get all tool definitions */ getTools(): Tool[] { return [ // Instance management tools { name: 'browser_create_instance', description: 'Create a new browser instance', inputSchema: { type: 'object', properties: { browserType: { type: 'string', enum: ['chromium', 'firefox', 'webkit'], description: 'Browser type', default: 'chromium' }, headless: { type: 'boolean', description: 'Whether to run in headless mode', default: true }, viewport: { type: 'object', properties: { width: { type: 'number', default: 1280 }, height: { type: 'number', default: 720 } }, description: 'Viewport size' }, userAgent: { type: 'string', description: 'User agent string' }, metadata: { type: 'object', properties: { name: { type: 'string', description: 'Instance name' }, description: { type: 'string', description: 'Instance description' }, tags: { type: 'array', items: { type: 'string' }, description: 'Tags' } }, description: 'Instance metadata' } } } }, { name: 'browser_list_instances', description: 'List all browser instances', inputSchema: { type: 'object', properties: {} } }, { name: 'browser_close_instance', description: 'Close the specified browser instance', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' } }, required: ['instanceId'] } }, { name: 'browser_close_all_instances', description: 'Close all browser instances', inputSchema: { type: 'object', properties: {} } }, // Navigation tools { name: 'browser_navigate', description: 'Navigate to a specified URL', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' }, url: { type: 'string', description: 'Target URL', }, timeout: { type: 'number', description: 'Timeout in milliseconds', default: 30000 }, waitUntil: { type: 'string', enum: ['load', 'domcontentloaded', 'networkidle'], description: 'Wait condition', default: 'load' } }, required: ['instanceId', 'url'] } }, { name: 'browser_go_back', description: 'Go back to the previous page', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' } }, required: ['instanceId'] } }, { name: 'browser_go_forward', description: 'Go forward to the next page', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' } }, required: ['instanceId'] } }, { name: 'browser_refresh', description: 'Refresh the current page', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' } }, required: ['instanceId'] } }, // Page interaction tools { name: 'browser_click', description: 'Click on a page element', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' }, selector: { type: 'string', description: 'Element selector', }, button: { type: 'string', enum: ['left', 'right', 'middle'], description: 'Mouse button', default: 'left' }, clickCount: { type: 'number', description: 'Number of clicks', default: 1 }, delay: { type: 'number', description: 'Click delay in milliseconds', default: 0 }, timeout: { type: 'number', description: 'Timeout in milliseconds', default: 30000 } }, required: ['instanceId', 'selector'] } }, { name: 'browser_type', description: 'Type text into an element', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' }, selector: { type: 'string', description: 'Element selector', }, text: { type: 'string', description: 'Text to input', }, delay: { type: 'number', description: 'Input delay in milliseconds', default: 0 }, timeout: { type: 'number', description: 'Timeout in milliseconds', default: 30000 } }, required: ['instanceId', 'selector', 'text'] } }, { name: 'browser_fill', description: 'Fill a form field', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' }, selector: { type: 'string', description: 'Element selector', }, value: { type: 'string', description: 'Value to fill', }, timeout: { type: 'number', description: 'Timeout in milliseconds', default: 30000 } }, required: ['instanceId', 'selector', 'value'] } }, { name: 'browser_select_option', description: 'Select an option from a dropdown', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' }, selector: { type: 'string', description: 'Element selector', }, value: { type: 'string', description: 'Value to select', }, timeout: { type: 'number', description: 'Timeout in milliseconds', default: 30000 } }, required: ['instanceId', 'selector', 'value'] } }, // Page information tools { name: 'browser_get_page_info', description: 'Get detailed page information including full HTML content, page statistics, and metadata', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' } }, required: ['instanceId'] } }, { name: 'browser_get_element_text', description: 'Get element text content', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' }, selector: { type: 'string', description: 'Element selector', }, timeout: { type: 'number', description: 'Timeout in milliseconds', default: 30000 } }, required: ['instanceId', 'selector'] } }, { name: 'browser_get_element_attribute', description: 'Get element attribute value', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' }, selector: { type: 'string', description: 'Element selector', }, attribute: { type: 'string', description: 'Attribute name', }, timeout: { type: 'number', description: 'Timeout in milliseconds', default: 30000 } }, required: ['instanceId', 'selector', 'attribute'] } }, // Screenshot tool { name: 'browser_screenshot', description: 'Take a screenshot of the page or element', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' }, fullPage: { type: 'boolean', description: 'Whether to capture the full page', default: false }, selector: { type: 'string', description: 'Element selector (capture specific element)' }, type: { type: 'string', enum: ['png', 'jpeg'], description: 'Image format', default: 'png' }, quality: { type: 'number', description: 'Image quality (1-100, JPEG only)', minimum: 1, maximum: 100, default: 80 } }, required: ['instanceId'] } }, // Wait tools { name: 'browser_wait_for_element', description: 'Wait for an element to appear', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' }, selector: { type: 'string', description: 'Element selector', }, timeout: { type: 'number', description: 'Timeout in milliseconds', default: 30000 } }, required: ['instanceId', 'selector'] } }, { name: 'browser_wait_for_navigation', description: 'Wait for page navigation to complete', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' }, timeout: { type: 'number', description: 'Timeout in milliseconds', default: 30000 } }, required: ['instanceId'] } }, // JavaScript execution tool { name: 'browser_evaluate', description: 'Execute JavaScript code in the page context', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' }, script: { type: 'string', description: 'JavaScript code to execute', } }, required: ['instanceId', 'script'] } }, // Content extraction tool { name: 'browser_get_markdown', description: 'Get page content in Markdown format, optimized for large language models', inputSchema: { type: 'object', properties: { instanceId: { type: 'string', description: 'Instance ID' }, includeLinks: { type: 'boolean', description: 'Whether to include links', default: true }, maxLength: { type: 'number', description: 'Maximum content length in characters', default: 10000 }, selector: { type: 'string', description: 'Optional CSS selector to extract content from specific element only' } }, required: ['instanceId'] } } ]; } /** * Execute tools */ async executeTools(name: string, args: any): Promise<ToolResult> { try { switch (name) { case 'browser_create_instance': return await this.browserManager.createInstance( { browserType: args.browserType || 'chromium', headless: args.headless ?? true, viewport: args.viewport || { width: 1280, height: 720 }, userAgent: args.userAgent }, args.metadata ); case 'browser_list_instances': return this.browserManager.listInstances(); case 'browser_close_instance': return await this.browserManager.closeInstance(args.instanceId); case 'browser_close_all_instances': return await this.browserManager.closeAllInstances(); case 'browser_navigate': return await this.navigate(args.instanceId, args.url, { timeout: args.timeout || 30000, waitUntil: args.waitUntil || 'load' }); case 'browser_go_back': return await this.goBack(args.instanceId); case 'browser_go_forward': return await this.goForward(args.instanceId); case 'browser_refresh': return await this.refresh(args.instanceId); case 'browser_click': return await this.click(args.instanceId, args.selector, { button: args.button || 'left', clickCount: args.clickCount || 1, delay: args.delay || 0, timeout: args.timeout || 30000 }); case 'browser_type': return await this.type(args.instanceId, args.selector, args.text, { delay: args.delay || 0, timeout: args.timeout || 30000 }); case 'browser_fill': return await this.fill(args.instanceId, args.selector, args.value, args.timeout || 30000); case 'browser_select_option': return await this.selectOption(args.instanceId, args.selector, args.value, args.timeout || 30000); case 'browser_get_page_info': return await this.getPageInfo(args.instanceId); case 'browser_get_element_text': return await this.getElementText(args.instanceId, args.selector, args.timeout || 30000); case 'browser_get_element_attribute': return await this.getElementAttribute(args.instanceId, args.selector, args.attribute, args.timeout || 30000); case 'browser_screenshot': return await this.screenshot(args.instanceId, { fullPage: args.fullPage || false, type: args.type || 'png', quality: args.quality || 80 }, args.selector); case 'browser_wait_for_element': return await this.waitForElement(args.instanceId, args.selector, args.timeout || 30000); case 'browser_wait_for_navigation': return await this.waitForNavigation(args.instanceId, args.timeout || 30000); case 'browser_evaluate': return await this.evaluate(args.instanceId, args.script); case 'browser_get_markdown': return await this.getMarkdown(args.instanceId, { includeLinks: args.includeLinks ?? true, maxLength: args.maxLength || 10000, selector: args.selector }); default: return { success: false, error: `Unknown tool: ${name}` }; } } catch (error) { return { success: false, error: `Tool execution failed: ${error instanceof Error ? error.message : error}` }; } } // Implementation of specific tool methods private async navigate(instanceId: string, url: string, options: NavigationOptions): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { const gotoOptions: any = { waitUntil: options.waitUntil }; if (options.timeout) { gotoOptions.timeout = options.timeout; } await instance.page.goto(url, gotoOptions); return { success: true, data: { url: instance.page.url(), title: await instance.page.title() }, instanceId }; } catch (error) { return { success: false, error: `Navigation failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async goBack(instanceId: string): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { await instance.page.goBack(); return { success: true, data: { url: instance.page.url() }, instanceId }; } catch (error) { return { success: false, error: `Go back failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async goForward(instanceId: string): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { await instance.page.goForward(); return { success: true, data: { url: instance.page.url() }, instanceId }; } catch (error) { return { success: false, error: `Go forward failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async refresh(instanceId: string): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { await instance.page.reload(); return { success: true, data: { url: instance.page.url() }, instanceId }; } catch (error) { return { success: false, error: `Refresh failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async click(instanceId: string, selector: string, options: ClickOptions): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { const clickOptions: any = { button: options.button }; if (options.clickCount) clickOptions.clickCount = options.clickCount; if (options.delay) clickOptions.delay = options.delay; if (options.timeout) clickOptions.timeout = options.timeout; await instance.page.click(selector, clickOptions); return { success: true, data: { selector, clicked: true }, instanceId }; } catch (error) { return { success: false, error: `Click failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async type(instanceId: string, selector: string, text: string, options: TypeOptions): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { const typeOptions: any = {}; if (options.delay) typeOptions.delay = options.delay; if (options.timeout) typeOptions.timeout = options.timeout; await instance.page.type(selector, text, typeOptions); return { success: true, data: { selector, text, typed: true }, instanceId }; } catch (error) { return { success: false, error: `Type failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async fill(instanceId: string, selector: string, value: string, timeout: number): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { await instance.page.fill(selector, value, { timeout }); return { success: true, data: { selector, value, filled: true }, instanceId }; } catch (error) { return { success: false, error: `Fill failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async selectOption(instanceId: string, selector: string, value: string, timeout: number): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { await instance.page.selectOption(selector, value, { timeout }); return { success: true, data: { selector, value, selected: true }, instanceId }; } catch (error) { return { success: false, error: `Select option failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async getPageInfo(instanceId: string): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { const url = instance.page.url(); const title = await instance.page.title(); const content = await instance.page.content(); // Get additional page information const viewport = instance.page.viewportSize(); const loadState = await instance.page.evaluate(() => document.readyState); // Get basic page statistics const pageStats = await instance.page.evaluate(() => { const links = document.querySelectorAll('a[href]').length; const images = document.querySelectorAll('img').length; const forms = document.querySelectorAll('form').length; const scripts = document.querySelectorAll('script').length; const stylesheets = document.querySelectorAll('link[rel="stylesheet"]').length; return { linksCount: links, imagesCount: images, formsCount: forms, scriptsCount: scripts, stylesheetsCount: stylesheets }; }); return { success: true, data: { url, title, content, // Return complete HTML content contentLength: content.length, viewport, loadState, stats: pageStats, timestamp: new Date().toISOString() }, instanceId }; } catch (error) { return { success: false, error: `Get page info failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async getElementText(instanceId: string, selector: string, timeout: number): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { const text = await instance.page.textContent(selector, { timeout }); return { success: true, data: { selector, text }, instanceId }; } catch (error) { return { success: false, error: `Get element text failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async getElementAttribute(instanceId: string, selector: string, attribute: string, timeout: number): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { const value = await instance.page.getAttribute(selector, attribute, { timeout }); return { success: true, data: { selector, attribute, value }, instanceId }; } catch (error) { return { success: false, error: `Get element attribute failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async screenshot(instanceId: string, options: ScreenshotOptions, selector?: string): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { let screenshotData: Buffer; if (selector) { const element = await instance.page.$(selector); if (!element) { return { success: false, error: `Element not found: ${selector}`, instanceId }; } screenshotData = await element.screenshot({ type: options.type, quality: options.type === 'jpeg' ? options.quality : undefined }); } else { screenshotData = await instance.page.screenshot({ fullPage: options.fullPage, type: options.type, quality: options.type === 'jpeg' ? options.quality : undefined, clip: options.clip }); } return { success: true, data: { screenshot: screenshotData.toString('base64'), type: options.type, selector }, instanceId }; } catch (error) { return { success: false, error: `Screenshot failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async waitForElement(instanceId: string, selector: string, timeout: number): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { await instance.page.waitForSelector(selector, { timeout }); return { success: true, data: { selector, found: true }, instanceId }; } catch (error) { return { success: false, error: `Wait for element failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async waitForNavigation(instanceId: string, timeout: number): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { await instance.page.waitForNavigation({ timeout }); return { success: true, data: { url: instance.page.url() }, instanceId }; } catch (error) { return { success: false, error: `Wait for navigation failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async evaluate(instanceId: string, script: string): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { const result = await instance.page.evaluate(script); return { success: true, data: { script, result }, instanceId }; } catch (error) { return { success: false, error: `Evaluate failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } private async getMarkdown(instanceId: string, options: { includeLinks: boolean; maxLength: number; selector?: string; }): Promise<ToolResult> { const instance = this.browserManager.getInstance(instanceId); if (!instance) { return { success: false, error: `Instance ${instanceId} not found` }; } try { // JavaScript function to extract page content and convert to Markdown const markdownContent = await instance.page.evaluate((opts) => { const { includeLinks, maxLength, selector } = opts; // Select the root element to process const rootElement = selector ? document.querySelector(selector) : document.body; if (!rootElement) { return 'Specified element or page content not found'; } // HTML to Markdown conversion function function htmlToMarkdown(element: any, depth = 0) { let markdown = ''; const indent = ' '.repeat(depth); for (const node of element.childNodes) { if (node.nodeType === Node.TEXT_NODE) { const text = node.textContent?.trim(); if (text) { markdown += text + ' '; } } else if (node.nodeType === Node.ELEMENT_NODE) { const el = node as Element; const tagName = el.tagName.toLowerCase(); switch (tagName) { case 'h1': markdown += `\n\n# ${el.textContent?.trim()}\n\n`; break; case 'h2': markdown += `\n\n## ${el.textContent?.trim()}\n\n`; break; case 'h3': markdown += `\n\n### ${el.textContent?.trim()}\n\n`; break; case 'h4': markdown += `\n\n#### ${el.textContent?.trim()}\n\n`; break; case 'h5': markdown += `\n\n##### ${el.textContent?.trim()}\n\n`; break; case 'h6': markdown += `\n\n###### ${el.textContent?.trim()}\n\n`; break; case 'p': const pText = htmlToMarkdown(el, depth); if (pText.trim()) { markdown += `\n\n${pText.trim()}\n`; } break; case 'br': markdown += '\n'; break; case 'strong': case 'b': markdown += `**${el.textContent?.trim()}**`; break; case 'em': case 'i': markdown += `*${el.textContent?.trim()}*`; break; case 'code': markdown += `\`${el.textContent?.trim()}\``; break; case 'pre': markdown += `\n\`\`\`\n${el.textContent?.trim()}\n\`\`\`\n`; break; case 'a': const href = el.getAttribute('href'); const linkText = el.textContent?.trim(); if (includeLinks && href && linkText) { if (href.startsWith('http')) { markdown += `[${linkText}](${href})`; } else { markdown += linkText; } } else { markdown += linkText || ''; } break; case 'ul': case 'ol': markdown += '\n'; const listItems = el.querySelectorAll('li'); listItems.forEach((li, index) => { const bullet = tagName === 'ul' ? '-' : `${index + 1}.`; markdown += `${indent}${bullet} ${li.textContent?.trim()}\n`; }); markdown += '\n'; break; case 'blockquote': const quoteText = el.textContent?.trim(); if (quoteText) { markdown += `\n> ${quoteText}\n\n`; } break; case 'div': case 'section': case 'article': case 'main': // Recursively process container elements markdown += htmlToMarkdown(el, depth); break; case 'table': // Simplified table processing const rows = el.querySelectorAll('tr'); if (rows.length > 0) { markdown += '\n\n'; rows.forEach((row, rowIndex) => { const cells = row.querySelectorAll('td, th'); const cellTexts = Array.from(cells).map(cell => cell.textContent?.trim() || ''); markdown += '| ' + cellTexts.join(' | ') + ' |\n'; if (rowIndex === 0) { markdown += '|' + ' --- |'.repeat(cells.length) + '\n'; } }); markdown += '\n'; } break; case 'script': case 'style': case 'nav': case 'footer': case 'aside': // Ignore these elements break; default: // For other elements, continue recursive processing of child elements markdown += htmlToMarkdown(el, depth); break; } } } return markdown; } // Extract page title const title = document.title; const url = window.location.href; // Generate Markdown content let content = `# ${title}\n\n**URL:** ${url}\n\n`; content += htmlToMarkdown(rootElement); // Clean up extra line breaks and spaces content = content .replace(/\n{3,}/g, '\n\n') .replace(/[ \t]+/g, ' ') .trim(); // Truncate content if exceeds maximum length if (content.length > maxLength) { content = content.substring(0, maxLength) + '\n\n[Content truncated...]'; } return content; }, options); return { success: true, data: { markdown: markdownContent, length: markdownContent.length, truncated: markdownContent.length >= options.maxLength, url: instance.page.url(), title: await instance.page.title() }, instanceId }; } catch (error) { return { success: false, error: `Get markdown failed: ${error instanceof Error ? error.message : error}`, instanceId }; } } }

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/sailaoda/concurrent-browser-mcp'

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