UNPKG

chrome-mcp

Version:

Model Context Protocol (MCP) server for controlling Google Chrome browser on macOS. Enables AI assistants like Claude to automate Chrome via DevTools Protocol.

350 lines 10.2 kB
import * as chromeLauncher from 'chrome-launcher'; import CDP from 'chrome-remote-interface'; import { exec } from 'child_process'; import { promisify } from 'util'; const execAsync = promisify(exec); export class ChromeController { client = null; chrome = null; /** * Connect to an existing Chrome instance or launch a new one */ async connect() { try { // Try to connect to existing Chrome instance const targets = await CDP.List(); if (targets.length > 0) { this.client = await CDP({ target: targets[0] }); } else { // Launch Chrome if not running this.chrome = await chromeLauncher.launch({ chromeFlags: [ '--remote-debugging-port=9222', '--no-first-run', '--no-default-browser-check' ] }); this.client = await CDP({ port: this.chrome.port }); } // Enable necessary domains await this.client.Page.enable(); await this.client.Runtime.enable(); await this.client.Network.enable(); await this.client.DOM.enable(); } catch (error) { throw new Error(`Failed to connect to Chrome: ${error}`); } } /** * Navigate to a URL */ async navigate(url) { if (!this.client) { await this.connect(); } try { const { frameId } = await this.client.Page.navigate({ url }); await this.client.Page.loadEventFired(); return `Navigated to ${url}`; } catch (error) { throw new Error(`Failed to navigate: ${error}`); } } /** * Execute JavaScript in the page */ async executeScript(script) { if (!this.client) { await this.connect(); } try { const result = await this.client.Runtime.evaluate({ expression: script, returnByValue: true, awaitPromise: true }); if (result.exceptionDetails) { throw new Error(`Script execution failed: ${result.exceptionDetails.text}`); } return result.result.value; } catch (error) { throw new Error(`Failed to execute script: ${error}`); } } /** * Get the current page title */ async getTitle() { if (!this.client) { await this.connect(); } try { const result = await this.client.Runtime.evaluate({ expression: 'document.title', returnByValue: true }); return result.result.value; } catch (error) { throw new Error(`Failed to get title: ${error}`); } } /** * Get the current URL */ async getUrl() { if (!this.client) { await this.connect(); } try { const result = await this.client.Runtime.evaluate({ expression: 'window.location.href', returnByValue: true }); return result.result.value; } catch (error) { throw new Error(`Failed to get URL: ${error}`); } } /** * Take a screenshot */ async screenshot() { if (!this.client) { await this.connect(); } try { const screenshot = await this.client.Page.captureScreenshot({ format: 'png' }); return screenshot.data; } catch (error) { throw new Error(`Failed to take screenshot: ${error}`); } } /** * Get page content/HTML */ async getContent() { if (!this.client) { await this.connect(); } try { const result = await this.client.Runtime.evaluate({ expression: 'document.documentElement.outerHTML', returnByValue: true }); return result.result.value; } catch (error) { throw new Error(`Failed to get content: ${error}`); } } /** * Get visible text from the page */ async getVisibleText() { if (!this.client) { await this.connect(); } try { const result = await this.client.Runtime.evaluate({ expression: 'document.body.innerText', returnByValue: true }); return result.result.value; } catch (error) { throw new Error(`Failed to get visible text: ${error}`); } } /** * Click on an element using a selector */ async click(selector) { if (!this.client) { await this.connect(); } try { const script = ` (function() { const element = document.querySelector('${selector.replace(/'/g, "\\'")}'); if (!element) { throw new Error('Element not found: ${selector}'); } element.click(); return 'Clicked on element: ${selector}'; })() `; const result = await this.executeScript(script); return result; } catch (error) { throw new Error(`Failed to click: ${error}`); } } /** * Type text into an input field */ async type(selector, text) { if (!this.client) { await this.connect(); } try { const script = ` (function() { const element = document.querySelector('${selector.replace(/'/g, "\\'")}'); if (!element) { throw new Error('Element not found: ${selector}'); } element.value = '${text.replace(/'/g, "\\'")}'; element.dispatchEvent(new Event('input', { bubbles: true })); element.dispatchEvent(new Event('change', { bubbles: true })); return 'Typed text into: ${selector}'; })() `; const result = await this.executeScript(script); return result; } catch (error) { throw new Error(`Failed to type: ${error}`); } } /** * Open a new tab using AppleScript (Mac-specific) */ async openNewTab(url) { try { const urlPart = url ? `set URL of active tab of front window to "${url}"` : ''; const script = ` tell application "Google Chrome" tell front window make new tab ${urlPart} end tell activate end tell `; await execAsync(`osascript -e '${script}'`); return url ? `Opened new tab with URL: ${url}` : 'Opened new tab'; } catch (error) { throw new Error(`Failed to open new tab: ${error}`); } } /** * Close the current tab using AppleScript (Mac-specific) */ async closeTab() { try { const script = ` tell application "Google Chrome" close active tab of front window end tell `; await execAsync(`osascript -e '${script}'`); return 'Closed current tab'; } catch (error) { throw new Error(`Failed to close tab: ${error}`); } } /** * List all open tabs */ async listTabs() { try { const targets = await CDP.List(); return targets.map((target, index) => ({ index, title: target.title, url: target.url, type: target.type })); } catch (error) { throw new Error(`Failed to list tabs: ${error}`); } } /** * Reload the current page */ async reload() { if (!this.client) { await this.connect(); } try { await this.client.Page.reload(); return 'Page reloaded'; } catch (error) { throw new Error(`Failed to reload: ${error}`); } } /** * Go back in history */ async goBack() { if (!this.client) { await this.connect(); } try { const history = await this.client.Page.getNavigationHistory(); const currentIndex = history.currentIndex; if (currentIndex > 0) { const entry = history.entries[currentIndex - 1]; await this.client.Page.navigateToHistoryEntry({ entryId: entry.id }); return 'Navigated back'; } else { return 'Already at the first page'; } } catch (error) { throw new Error(`Failed to go back: ${error}`); } } /** * Go forward in history */ async goForward() { if (!this.client) { await this.connect(); } try { const history = await this.client.Page.getNavigationHistory(); const currentIndex = history.currentIndex; if (currentIndex < history.entries.length - 1) { const entry = history.entries[currentIndex + 1]; await this.client.Page.navigateToHistoryEntry({ entryId: entry.id }); return 'Navigated forward'; } else { return 'Already at the last page'; } } catch (error) { throw new Error(`Failed to go forward: ${error}`); } } /** * Disconnect from Chrome */ async disconnect() { if (this.client) { await this.client.close(); this.client = null; } if (this.chrome) { await this.chrome.kill(); this.chrome = null; } } } //# sourceMappingURL=chrome-controller.js.map