browser-mcp.js•13.1 kB
/**
* Browser MCP Integration
* Connects to Playwright MCP server for browser automation and screenshots
*/
const { Client } = require('@modelcontextprotocol/sdk/client/index.js');
const { StdioClientTransport } = require('@modelcontextprotocol/sdk/client/stdio.js');
class BrowserMCP {
constructor() {
this.client = null;
this.connected = false;
this.capabilities = null;
}
async connect() {
try {
console.log('Connecting to Playwright MCP server...');
const transport = new StdioClientTransport({
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-playwright'],
env: { ...process.env }
});
this.client = new Client({
name: 'voice-assistant-browser',
version: '1.0.0'
}, {
capabilities: {}
});
await this.client.connect(transport);
this.connected = true;
// Get available tools
const tools = await this.client.listTools();
this.capabilities = tools;
console.log('✅ Browser MCP connected');
console.log(`Available tools: ${tools.tools?.map(t => t.name).join(', ')}`);
return true;
} catch (error) {
console.error('Failed to connect to Browser MCP:', error);
this.connected = false;
return false;
}
}
async disconnect() {
if (this.client) {
await this.client.close();
this.client = null;
this.connected = false;
console.log('Browser MCP disconnected');
}
}
isConnected() {
return this.connected;
}
async navigate(url) {
if (!this.connected) {
throw new Error('Browser MCP not connected');
}
try {
const result = await this.client.callTool('browser_navigate', { url });
return result;
} catch (error) {
console.error('Navigation failed:', error);
throw error;
}
}
async takeScreenshot(options = {}) {
if (!this.connected) {
throw new Error('Browser MCP not connected');
}
try {
const params = {
fullPage: options.fullPage || false,
raw: options.raw || false,
filename: options.filename
};
if (options.element) {
params.element = options.element;
params.ref = options.ref;
}
const result = await this.client.callTool('browser_take_screenshot', params);
// Extract image data from result
if (result.content && result.content[0]) {
const content = result.content[0];
if (content.type === 'image') {
return {
data: content.data,
mimeType: content.mimeType || 'image/png'
};
} else if (content.type === 'text') {
// Base64 encoded image
return {
data: content.text,
mimeType: 'image/png'
};
}
}
throw new Error('No screenshot data received');
} catch (error) {
console.error('Screenshot failed:', error);
throw error;
}
}
async captureSnapshot() {
if (!this.connected) {
throw new Error('Browser MCP not connected');
}
try {
const result = await this.client.callTool('browser_snapshot', {});
// Parse snapshot data
if (result.content && result.content[0]) {
const content = result.content[0];
return {
type: 'snapshot',
data: content.text || content.data,
timestamp: new Date().toISOString()
};
}
throw new Error('No snapshot data received');
} catch (error) {
console.error('Snapshot failed:', error);
throw error;
}
}
async click(element, ref) {
if (!this.connected) {
throw new Error('Browser MCP not connected');
}
try {
const result = await this.client.callTool('browser_click', {
element,
ref
});
return result;
} catch (error) {
console.error('Click failed:', error);
throw error;
}
}
async type(element, ref, text, options = {}) {
if (!this.connected) {
throw new Error('Browser MCP not connected');
}
try {
const result = await this.client.callTool('browser_type', {
element,
ref,
text,
slowly: options.slowly || false,
submit: options.submit || false
});
return result;
} catch (error) {
console.error('Type failed:', error);
throw error;
}
}
async evaluate(functionStr, element = null, ref = null) {
if (!this.connected) {
throw new Error('Browser MCP not connected');
}
try {
const params = { function: functionStr };
if (element && ref) {
params.element = element;
params.ref = ref;
}
const result = await this.client.callTool('browser_evaluate', params);
return result;
} catch (error) {
console.error('Evaluate failed:', error);
throw error;
}
}
async getConsoleMessages() {
if (!this.connected) {
throw new Error('Browser MCP not connected');
}
try {
const result = await this.client.callTool('browser_console_messages', {});
return result;
} catch (error) {
console.error('Failed to get console messages:', error);
throw error;
}
}
async getNetworkRequests() {
if (!this.connected) {
throw new Error('Browser MCP not connected');
}
try {
const result = await this.client.callTool('browser_network_requests', {});
return result;
} catch (error) {
console.error('Failed to get network requests:', error);
throw error;
}
}
async waitFor(options = {}) {
if (!this.connected) {
throw new Error('Browser MCP not connected');
}
try {
const params = {};
if (options.text) params.text = options.text;
if (options.textGone) params.textGone = options.textGone;
if (options.time) params.time = options.time;
const result = await this.client.callTool('browser_wait_for', params);
return result;
} catch (error) {
console.error('Wait failed:', error);
throw error;
}
}
async close() {
if (!this.connected) {
throw new Error('Browser MCP not connected');
}
try {
const result = await this.client.callTool('browser_close', {});
return result;
} catch (error) {
console.error('Close failed:', error);
throw error;
}
}
// Tab management
async listTabs() {
if (!this.connected) {
throw new Error('Browser MCP not connected');
}
try {
const result = await this.client.callTool('browser_tab_list', {});
return result;
} catch (error) {
console.error('Failed to list tabs:', error);
throw error;
}
}
async newTab(url = null) {
if (!this.connected) {
throw new Error('Browser MCP not connected');
}
try {
const params = {};
if (url) params.url = url;
const result = await this.client.callTool('browser_tab_new', params);
return result;
} catch (error) {
console.error('Failed to create new tab:', error);
throw error;
}
}
async selectTab(index) {
if (!this.connected) {
throw new Error('Browser MCP not connected');
}
try {
const result = await this.client.callTool('browser_tab_select', { index });
return result;
} catch (error) {
console.error('Failed to select tab:', error);
throw error;
}
}
async closeTab(index = null) {
if (!this.connected) {
throw new Error('Browser MCP not connected');
}
try {
const params = {};
if (index !== null) params.index = index;
const result = await this.client.callTool('browser_tab_close', params);
return result;
} catch (error) {
console.error('Failed to close tab:', error);
throw error;
}
}
// Utility methods for common tasks
async captureFullPage(url, filename) {
try {
await this.navigate(url);
await this.waitFor({ time: 2 }); // Wait for page to load
const screenshot = await this.takeScreenshot({
fullPage: true,
filename: filename
});
return screenshot;
} catch (error) {
console.error('Full page capture failed:', error);
throw error;
}
}
async captureElementScreenshot(url, selector, filename) {
try {
await this.navigate(url);
await this.waitFor({ text: selector });
// Get element reference
const snapshot = await this.captureSnapshot();
// Parse snapshot to find element ref (simplified)
const screenshot = await this.takeScreenshot({
element: selector,
ref: 'element_ref', // Would need to extract from snapshot
filename: filename
});
return screenshot;
} catch (error) {
console.error('Element capture failed:', error);
throw error;
}
}
async recordPageInteraction(url, actions) {
const recording = {
url,
timestamp: new Date().toISOString(),
actions: [],
screenshots: []
};
try {
await this.navigate(url);
for (const action of actions) {
switch (action.type) {
case 'click':
await this.click(action.element, action.ref);
break;
case 'type':
await this.type(action.element, action.ref, action.text, action.options);
break;
case 'wait':
await this.waitFor(action.options);
break;
case 'screenshot':
const screenshot = await this.takeScreenshot(action.options);
recording.screenshots.push(screenshot);
break;
}
recording.actions.push({
...action,
timestamp: new Date().toISOString()
});
}
return recording;
} catch (error) {
console.error('Recording failed:', error);
recording.error = error.message;
return recording;
}
}
// Debug helper
async getPageInfo() {
if (!this.connected) {
return { connected: false };
}
try {
const [console, network, snapshot] = await Promise.all([
this.getConsoleMessages(),
this.getNetworkRequests(),
this.captureSnapshot()
]);
return {
connected: true,
console,
network,
snapshot,
timestamp: new Date().toISOString()
};
} catch (error) {
return {
connected: true,
error: error.message
};
}
}
}
// Singleton instance
let browserMCPInstance = null;
function getBrowserMCP() {
if (!browserMCPInstance) {
browserMCPInstance = new BrowserMCP();
}
return browserMCPInstance;
}
module.exports = {
BrowserMCP,
getBrowserMCP
};