UNPKG

@dorothywebb/any-browser-mcp

Version:

Any Browser MCP - Launch Chrome with your actual data in debug mode for comprehensive browser automation

268 lines • 11.6 kB
#!/usr/bin/env node /** * Any Browser MCP Server * * This server creates a duplicate of your primary Chrome installation running in debug mode: * 1. Copies all your Chrome data (bookmarks, passwords, extensions, history) * 2. Launches as a separate instance with debugging enabled * 3. Initializes lazily when MCP tools are first used * 4. Preserves your main Chrome session completely untouched */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { getConfigManager } from './core/ConfigManager.js'; import { createLazyBrowserManager } from './core/LazyBrowserManager.js'; import { BrowserConnectionError, LazyInitializationError } from './types/index.js'; import { BROWSER_TOOLS, getTool, getConfirmationTools } from './core/BrowserTools.js'; class AnyBrowserMCPServer { server; browserManager; config = getConfigManager(); constructor() { this.server = new Server({ name: 'any-browser-mcp', version: '2.0.0' }, { capabilities: { tools: {}, resources: {} } }); // Create lazy browser manager (does NOT connect yet) this.browserManager = createLazyBrowserManager(); this.setupHandlers(); if (this.config.isVerbose()) { console.log('šŸš€ Any Browser MCP Server initialized'); console.log(' āœ… WILL launch Chrome duplicate with your data when needed'); console.log(' āœ… WILL connect only when tools are used'); console.log(' āœ… WILL preserve your main Chrome session untouched'); } } setupHandlers() { // List available tools this.server.setRequestHandler(ListToolsRequestSchema, async () => { const tools = BROWSER_TOOLS.map(tool => ({ name: tool.name, description: tool.description, inputSchema: tool.inputSchema })); // Add status tool tools.push({ name: 'browser_status', description: 'Get browser connection status and information', inputSchema: { type: 'object', properties: {} } }); return { tools }; }); // Handle tool calls (with lazy initialization) this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { // Handle status tool separately if (name === 'browser_status') { const status = this.browserManager.getStatus(); return { content: [{ type: 'text', text: JSON.stringify({ ...status, config: { allowLaunch: this.config.getBrowserConfig().allowLaunch, useSeparateInstance: this.config.getBrowserConfig().useSeparateInstance, lazyInitialization: this.config.isLazyInitializationEnabled(), endpoint: this.config.getDebugEndpoint(), debugPort: this.config.getBrowserConfig().debugPort }, confirmationTools: getConfirmationTools() }, null, 2) }] }; } // Find and execute browser tool const tool = getTool(name); if (!tool) { throw new Error(`Unknown tool: ${name}`); } // Show confirmation warning for destructive actions if (tool.requiresConfirmation) { if (this.config.isVerbose()) { console.log(`āš ļø Executing destructive action: ${name}`); } } const result = await tool.handler(args, this.browserManager); // Format response based on result type if (result.data && name.includes('screenshot')) { return { content: [{ type: 'text', text: result.message || 'Screenshot captured' }, { type: 'image', data: result.data, mimeType: 'image/png' }] }; } else if (result.data && name === 'browser_pdf_save') { return { content: [{ type: 'text', text: result.message || 'PDF generated' }, { type: 'resource', resource: { uri: `data:application/pdf;base64,${result.data}`, mimeType: 'application/pdf' } }] }; } else if (result.data && typeof result.data === 'object') { return { content: [{ type: 'text', text: `${result.message || 'Success'}\n\nData:\n${JSON.stringify(result.data, null, 2)}` }] }; } else if (result.data && typeof result.data === 'string') { return { content: [{ type: 'text', text: result.data }] }; } else { return { content: [{ type: 'text', text: result.message || 'Operation completed successfully' }] }; } } catch (error) { if (error instanceof LazyInitializationError || error instanceof BrowserConnectionError) { return { content: [{ type: 'text', text: `Browser Connection Error: ${error.message}\n\n` + `Please ensure your browser is running with debugging enabled:\n` + `Chrome: chrome --remote-debugging-port=${this.config.getDebugPort()}\n` + `Edge: msedge --remote-debugging-port=${this.config.getDebugPort()}\n\n` + `This MCP server will NEVER launch browsers automatically for safety.` }], isError: true }; } return { content: [{ type: 'text', text: `Error executing ${name}: ${error.message}` }], isError: true }; } }); // List resources this.server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: 'browser://status', name: 'Browser Status', description: 'Current browser connection status and configuration', mimeType: 'application/json' } ] }; }); // Read resources this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; if (uri === 'browser://status') { const status = this.browserManager.getStatus(); return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify({ server: { name: 'any-browser-mcp', version: '2.0.0', startTime: new Date() }, browser: status, config: { neverLaunch: this.config.shouldNeverLaunch(), useExistingOnly: this.config.shouldUseExistingOnly(), lazyInitialization: this.config.isLazyInitializationEnabled(), debugPort: this.config.getDebugPort(), endpoint: this.config.getDebugEndpoint() }, safety: this.config.getSafetyConfig() }, null, 2) }] }; } throw new Error(`Unknown resource: ${uri}`); }); } async start() { const transport = new StdioServerTransport(); await this.server.connect(transport); if (this.config.isVerbose()) { console.log('āœ… MCP Server started and ready'); console.log(' šŸ“‹ Tools available but browser NOT connected yet'); console.log(' šŸ”„ Browser will connect on first tool use'); } } async stop() { await this.browserManager.disconnect(); if (this.config.isVerbose()) { console.log('šŸ›‘ MCP Server stopped'); } } } // Handle command line arguments function parseArgs() { const args = process.argv.slice(2); return { verbose: args.includes('--verbose') || args.includes('-v') }; } // Main execution async function main() { const args = parseArgs(); if (args.verbose) { const config = getConfigManager(); config.setVerbose(true); } const server = new AnyBrowserMCPServer(); // Graceful shutdown process.on('SIGINT', async () => { console.log('\nšŸ›‘ Received SIGINT, shutting down gracefully...'); await server.stop(); process.exit(0); }); process.on('SIGTERM', async () => { console.log('\nšŸ›‘ Received SIGTERM, shutting down gracefully...'); await server.stop(); process.exit(0); }); await server.start(); } // Start server only if this file is executed directly (not when imported) if (import.meta.url.startsWith('file:') && (process.argv[1].endsWith('server.ts') || process.argv[1].endsWith('dist/server.js'))) { main().catch((error) => { console.error('šŸ’„ Fatal error:', error); process.exit(1); }); } export { AnyBrowserMCPServer }; //# sourceMappingURL=server.js.map