algorembrant's picture
Upload 44 files
668d995 verified
#!/usr/bin/env node
/**
* MCP Browser Automation Server
*
* Provides browser automation capabilities for AI agents to interact with
* websites like Messenger through Microsoft Edge.
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ErrorCode,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import { browserManager } from './browser.js';
import * as messenger from './tools/messenger.js';
import * as web from './tools/web.js';
// Create MCP server
const server = new Server(
{
name: 'mcp-browser-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Define available tools
const TOOLS = [
// Messenger tools
{
name: 'messenger_list_conversations',
description: 'List recent Messenger conversations',
inputSchema: {
type: 'object' as const,
properties: {},
required: [],
},
},
{
name: 'messenger_search_conversation',
description: 'Search for a Messenger conversation by name',
inputSchema: {
type: 'object' as const,
properties: {
query: { type: 'string', description: 'Search query for finding a conversation' },
},
required: ['query'],
},
},
{
name: 'messenger_send_message',
description: 'Send a message to a Messenger conversation',
inputSchema: {
type: 'object' as const,
properties: {
conversationName: { type: 'string', description: 'Name of the conversation/contact' },
message: { type: 'string', description: 'Message text to send' },
},
required: ['conversationName', 'message'],
},
},
{
name: 'messenger_read_messages',
description: 'Read messages from the current Messenger conversation',
inputSchema: {
type: 'object' as const,
properties: {
count: { type: 'number', description: 'Number of messages to read (default: 20)' },
},
required: [],
},
},
{
name: 'messenger_send_file',
description: 'Send a file to a Messenger conversation',
inputSchema: {
type: 'object' as const,
properties: {
conversationName: { type: 'string', description: 'Name of the conversation/contact' },
filePath: { type: 'string', description: 'Absolute path to the file' },
},
required: ['conversationName', 'filePath'],
},
},
{
name: 'messenger_get_info',
description: 'Get information about the current Messenger conversation',
inputSchema: {
type: 'object' as const,
properties: {},
required: [],
},
},
// Web tools
{
name: 'web_navigate',
description: 'Navigate to a URL in the browser',
inputSchema: {
type: 'object' as const,
properties: {
url: { type: 'string', description: 'The URL to navigate to' },
},
required: ['url'],
},
},
{
name: 'web_screenshot',
description: 'Take a screenshot of the current page',
inputSchema: {
type: 'object' as const,
properties: {
fullPage: { type: 'boolean', description: 'Whether to capture the full page' },
},
required: [],
},
},
{
name: 'web_click',
description: 'Click on an element by CSS selector',
inputSchema: {
type: 'object' as const,
properties: {
selector: { type: 'string', description: 'CSS selector of the element to click' },
},
required: ['selector'],
},
},
{
name: 'web_type',
description: 'Type text into an input field',
inputSchema: {
type: 'object' as const,
properties: {
selector: { type: 'string', description: 'CSS selector of the input field' },
text: { type: 'string', description: 'Text to type' },
},
required: ['selector', 'text'],
},
},
{
name: 'web_extract_text',
description: 'Extract text content from the page or specific element',
inputSchema: {
type: 'object' as const,
properties: {
selector: { type: 'string', description: 'CSS selector (default: body)' },
},
required: [],
},
},
{
name: 'web_execute_js',
description: 'Execute JavaScript on the current page',
inputSchema: {
type: 'object' as const,
properties: {
script: { type: 'string', description: 'JavaScript code to execute' },
},
required: ['script'],
},
},
{
name: 'web_wait_for',
description: 'Wait for an element to appear on the page',
inputSchema: {
type: 'object' as const,
properties: {
selector: { type: 'string', description: 'CSS selector to wait for' },
timeout: { type: 'number', description: 'Timeout in milliseconds (default: 5000)' },
},
required: ['selector'],
},
},
{
name: 'web_get_url',
description: 'Get the current page URL',
inputSchema: {
type: 'object' as const,
properties: {},
required: [],
},
},
{
name: 'web_accessibility_snapshot',
description: 'Get accessibility snapshot of the page for AI understanding',
inputSchema: {
type: 'object' as const,
properties: {},
required: [],
},
},
{
name: 'web_list_tabs',
description: 'List all open tabs/pages in the browser',
inputSchema: {
type: 'object' as const,
properties: {},
required: [],
},
},
{
name: 'web_switch_tab',
description: 'Switch to a specific tab by index',
inputSchema: {
type: 'object' as const,
properties: {
index: { type: 'number', description: 'Index of the tab to switch to (from web_list_tabs)' },
},
required: ['index'],
},
},
{
name: 'browser_close',
description: 'Close the browser',
inputSchema: {
type: 'object' as const,
properties: {},
required: [],
},
},
];
// Handle list tools request
server.setRequestHandler(ListToolsRequestSchema, async () => {
return { tools: TOOLS };
});
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
let result: unknown;
switch (name) {
// Messenger tools
case 'messenger_list_conversations':
result = await messenger.listConversations();
break;
case 'messenger_search_conversation':
result = await messenger.searchConversation((args as { query: string }).query);
break;
case 'messenger_send_message':
result = await messenger.sendMessage(
(args as { conversationName: string; message: string }).conversationName,
(args as { conversationName: string; message: string }).message
);
break;
case 'messenger_read_messages':
result = await messenger.readMessages((args as { count?: number }).count);
break;
case 'messenger_send_file':
result = await messenger.sendFile(
(args as { conversationName: string; filePath: string }).conversationName,
(args as { conversationName: string; filePath: string }).filePath
);
break;
case 'messenger_get_info':
result = await messenger.getConversationInfo();
break;
// Web tools
case 'web_navigate':
result = await web.navigate((args as { url: string }).url);
break;
case 'web_screenshot':
result = await web.screenshot((args as { fullPage?: boolean }).fullPage);
break;
case 'web_click':
result = await web.click((args as { selector: string }).selector);
break;
case 'web_type':
result = await web.type(
(args as { selector: string; text: string }).selector,
(args as { selector: string; text: string }).text
);
break;
case 'web_extract_text':
result = await web.extractText((args as { selector?: string }).selector);
break;
case 'web_execute_js':
result = await web.executeJs((args as { script: string }).script);
break;
case 'web_wait_for':
result = await web.waitFor(
(args as { selector: string; timeout?: number }).selector,
(args as { selector: string; timeout?: number }).timeout
);
break;
case 'web_get_url':
result = await web.getCurrentUrl();
break;
case 'web_accessibility_snapshot':
result = await web.getAccessibilitySnapshot();
break;
case 'web_new_tab':
result = await web.newTab((args as { url?: string }).url);
break;
case 'web_close_tab':
result = await web.closeTab();
break;
case 'web_list_tabs':
result = await web.listTabs();
break;
case 'web_switch_tab':
result = await web.switchTab((args as { index: number }).index);
break;
case 'browser_close':
await browserManager.close();
result = { success: true };
break;
default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
if (error instanceof McpError) throw error;
throw new McpError(
ErrorCode.InternalError,
`Tool execution failed: ${error instanceof Error ? error.message : String(error)}`
);
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('MCP Browser Server running on stdio');
}
main().catch((error) => {
console.error('Failed to start MCP server:', error);
process.exit(1);
});