@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
JavaScript
/**
* 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