# Directory Structure
```
├── .gitignore
├── index.ts
├── package-lock.json
├── package.json
├── README.md
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
dist
node_modules
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# BrowserCat MCP Server
A Model Context Protocol server that provides browser automation capabilities using BrowserCat's cloud browser service. This server enables LLMs to interact with web pages, take screenshots, and execute JavaScript in a real browser environment without needing to install browsers locally.
## Components
### Tools
- **browsercat_navigate**
    - Navigate to any URL in the browser
    - Input: `url` (string)
- **browsercat_screenshot**
    - Capture screenshots of the entire page or specific elements
    - Inputs:
        - `name` (string, required): Name for the screenshot
        - `selector` (string, optional): CSS selector for element to screenshot
        - `width` (number, optional, default: 800): Screenshot width
        - `height` (number, optional, default: 600): Screenshot height
- **browsercat_click**
    - Click elements on the page
    - Input: `selector` (string): CSS selector for element to click
- **browsercat_hover**
    - Hover elements on the page
    - Input: `selector` (string): CSS selector for element to hover
- **browsercat_fill**
    - Fill out input fields
    - Inputs:
        - `selector` (string): CSS selector for input field
        - `value` (string): Value to fill
- **browsercat_select**
    - Select an option from a dropdown menu
    - Inputs:
        - `selector` (string): CSS selector for select element
        - `value` (string): Value to select
- **browsercat_evaluate**
    - Execute JavaScript in the browser console
    - Input: `script` (string): JavaScript code to execute
### Resources
The server provides access to two types of resources:
1. **Console Logs** (`console://logs`)
    - Browser console output in text format
    - Includes all console messages from the browser
2. **Screenshots** (`screenshot://<name>`)
    - PNG images of captured screenshots
    - Accessible via the screenshot name specified during capture
## Key Features
- Cloud-based browser automation
- No local browser installation required
- Console log monitoring
- Screenshot capabilities
- JavaScript execution
- Basic web interaction (navigation, clicking, form filling)
## Configuration to use BrowserCat MCP Server
### Environment Variables
The BrowserCat MCP server requires the following environment variable:
- `BROWSERCAT_API_KEY`: Your BrowserCat API key (required). You can get one for free at https://browsercat.xyz/mcp.
### NPX Configuration
```json
{
  "mcpServers": {
    "browsercat": {
      "command": "npx",
      "args": ["-y", "@browsercatco/mcp-server"],
      "env": {
        "BROWSERCAT_API_KEY": "your-api-key-here"
      }
    }
  }
}
```
## License
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "outDir": "./dist",
    "rootDir": "."
  },
  "include": [
    "./**/*.ts"
  ],
  "exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
  "name": "@browsercatco/mcp-server",
  "version": "0.1.0",
  "description": "MCP server for remote browser automation using BrowserCat",
  "license": "MIT",
  "author": "BrowserCat",
  "homepage": "https://www.browsercat.com",
  "type": "module",
  "bin": {
    "mcp-server-puppeteer": "dist/index.js"
  },
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "tsc && shx chmod +x dist/*.js",
    "prepare": "npm run build",
    "watch": "tsc --watch"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "1.0.1",
    "puppeteer": "^23.4.0"
  },
  "devDependencies": {
    "shx": "^0.3.4",
    "typescript": "^5.6.2"
  }
}
```
--------------------------------------------------------------------------------
/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListResourcesRequestSchema,
  ListToolsRequestSchema,
  ReadResourceRequestSchema,
  CallToolResult,
  TextContent,
  ImageContent,
  Tool,
} from "@modelcontextprotocol/sdk/types.js";
import puppeteer, { Browser, Page } from "puppeteer";
// Environment variables configuration
const requiredEnvVars = {
  BROWSERCAT_API_KEY: process.env.BROWSERCAT_API_KEY,
};
// Validate required environment variables
Object.entries(requiredEnvVars).forEach(([name, value]) => {
  if (!value) throw new Error(`${name} environment variable is required. You can get one for free at https://browsercat.xyz/mcp.`);
});
// Define the tools available for browser automation
const TOOLS: Tool[] = [
  {
    name: "browsercat_navigate",
    description: "Navigate to a URL",
    inputSchema: {
      type: "object",
      properties: {
        url: { type: "string" },
      },
      required: ["url"],
    },
  },
  {
    name: "browsercat_screenshot",
    description: "Take a screenshot of the current page or a specific element",
    inputSchema: {
      type: "object",
      properties: {
        name: { type: "string", description: "Name for the screenshot" },
        selector: { type: "string", description: "CSS selector for element to screenshot" },
        width: { type: "number", description: "Width in pixels (default: 800)" },
        height: { type: "number", description: "Height in pixels (default: 600)" },
      },
      required: ["name"],
    },
  },
  {
    name: "browsercat_click",
    description: "Click an element on the page",
    inputSchema: {
      type: "object",
      properties: {
        selector: { type: "string", description: "CSS selector for element to click" },
      },
      required: ["selector"],
    },
  },
  {
    name: "browsercat_fill",
    description: "Fill out an input field",
    inputSchema: {
      type: "object",
      properties: {
        selector: { type: "string", description: "CSS selector for input field" },
        value: { type: "string", description: "Value to fill" },
      },
      required: ["selector", "value"],
    },
  },
  {
    name: "browsercat_select",
    description: "Select an option from a dropdown menu",
    inputSchema: {
      type: "object",
      properties: {
        selector: { type: "string", description: "CSS selector for select element" },
        value: { type: "string", description: "Value to select" },
      },
      required: ["selector", "value"],
    },
  },
  {
    name: "browsercat_hover",
    description: "Hover over an element on the page",
    inputSchema: {
      type: "object",
      properties: {
        selector: { type: "string", description: "CSS selector for element to hover" },
      },
      required: ["selector"],
    },
  },
  {
    name: "browsercat_evaluate",
    description: "Execute JavaScript in the browser console",
    inputSchema: {
      type: "object",
      properties: {
        script: { type: "string", description: "JavaScript code to execute" },
      },
      required: ["script"],
    },
  },
];
// Global state for browser instance and resources
let browser: Browser | undefined;
let page: Page | undefined;
const consoleLogs: string[] = [];
const screenshots = new Map<string, string>();
/**
 * Ensures that a browser instance is running and returns the active page
 */
async function ensureBrowser() {
  if (!browser) {
    // Connect to BrowserCat remote browser using the correct endpoint and header format
    const browsercatEndpoint = 'wss://api.browsercat.com/connect';
    
    browser = await puppeteer.connect({
      browserWSEndpoint: browsercatEndpoint,
      headers: {
        'Api-Key': requiredEnvVars.BROWSERCAT_API_KEY!
      }
    });
    
    const pages = await browser.pages();
    if (pages.length === 0) {
      page = await browser.newPage();
    } else {
      page = pages[0];
    }
    // Capture console logs from the browser
    page.on("console", (msg) => {
      const logEntry = `[${msg.type()}] ${msg.text()}`;
      consoleLogs.push(logEntry);
      server.notification({
        method: "notifications/resources/updated",
        params: { uri: "console://logs" },
      });
    });
  }
  return page!;
}
// Extend Window interface for our console log capture mechanism
declare global {
  interface Window {
    mcpHelper: {
      logs: string[],
      originalConsole: Partial<typeof console>,
    }
  }
}
/**
 * Handles tool calls from the model and executes corresponding browser actions
 */
async function handleToolCall(name: string, args: any): Promise<CallToolResult> {
  const page = await ensureBrowser();
  switch (name) {
    case "browsercat_navigate":
      await page.goto(args.url);
      return {
        content: [{
          type: "text",
          text: `Navigated to ${args.url}`,
        }],
        isError: false,
      };
    case "browsercat_screenshot": {
      const width = args.width ?? 800;
      const height = args.height ?? 600;
      await page.setViewport({ width, height });
      
      const screenshot = await (args.selector ?
        (await page.$(args.selector))?.screenshot({ encoding: "base64" }) :
        page.screenshot({ encoding: "base64", fullPage: false }));
      if (!screenshot) {
        return {
          content: [{
            type: "text",
            text: args.selector ? `Element not found: ${args.selector}` : "Screenshot failed",
          }],
          isError: true,
        };
      }
      screenshots.set(args.name, screenshot as string);
      server.notification({
        method: "notifications/resources/list_changed",
      });
      return {
        content: [
          {
            type: "text",
            text: `Screenshot '${args.name}' taken at ${width}x${height}`,
          } as TextContent,
          {
            type: "image",
            data: screenshot,
            mimeType: "image/png",
          } as ImageContent,
        ],
        isError: false,
      };
    }
    case "browsercat_click":
      try {
        await page.click(args.selector);
        return {
          content: [{
            type: "text",
            text: `Clicked: ${args.selector}`,
          }],
          isError: false,
        };
      } catch (error) {
        return {
          content: [{
            type: "text",
            text: `Failed to click ${args.selector}: ${(error as Error).message}`,
          }],
          isError: true,
        };
      }
    case "browsercat_fill":
      try {
        await page.waitForSelector(args.selector);
        await page.type(args.selector, args.value);
        return {
          content: [{
            type: "text",
            text: `Filled ${args.selector} with: ${args.value}`,
          }],
          isError: false,
        };
      } catch (error) {
        return {
          content: [{
            type: "text",
            text: `Failed to fill ${args.selector}: ${(error as Error).message}`,
          }],
          isError: true,
        };
      }
    case "browsercat_select":
      try {
        await page.waitForSelector(args.selector);
        await page.select(args.selector, args.value);
        return {
          content: [{
            type: "text",
            text: `Selected ${args.selector} with: ${args.value}`,
          }],
          isError: false,
        };
      } catch (error) {
        return {
          content: [{
            type: "text",
            text: `Failed to select ${args.selector}: ${(error as Error).message}`,
          }],
          isError: true,
        };
      }
    case "browsercat_hover":
      try {
        await page.waitForSelector(args.selector);
        await page.hover(args.selector);
        return {
          content: [{
            type: "text",
            text: `Hovered ${args.selector}`,
          }],
          isError: false,
        };
      } catch (error) {
        return {
          content: [{
            type: "text",
            text: `Failed to hover ${args.selector}: ${(error as Error).message}`,
          }],
          isError: true,
        };
      }
    case "browsercat_evaluate":
      try {
        // Set up console log capture
        await page.evaluate(() => {
          window.mcpHelper = {
            logs: [],
            originalConsole: { ...console },
          };
          ['log', 'info', 'warn', 'error'].forEach(method => {
            (console as any)[method] = (...args: any[]) => {
              window.mcpHelper.logs.push(`[${method}] ${args.join(' ')}`);
              (window.mcpHelper.originalConsole as any)[method](...args);
            };
          });
        });
        const result = await page.evaluate(args.script);
        // Restore original console and get captured logs
        const logs = await page.evaluate(() => {
          Object.assign(console, window.mcpHelper.originalConsole);
          const logs = window.mcpHelper.logs;
          delete (window as any).mcpHelper;
          return logs;
        });
        return {
          content: [
            {
              type: "text",
              text: `Execution result:\n${JSON.stringify(result, null, 2)}\n\nConsole output:\n${logs.join('\n')}`,
            },
          ],
          isError: false,
        };
      } catch (error) {
        return {
          content: [{
            type: "text",
            text: `Script execution failed: ${(error as Error).message}`,
          }],
          isError: true,
        };
      }
    default:
      return {
        content: [{
          type: "text",
          text: `Unknown tool: ${name}`,
        }],
        isError: true,
      };
  }
}
// Initialize the MCP server
const server = new Server(
  {
    name: "browsercat-mcp-server",
    version: "0.1.0",
  },
  {
    capabilities: {
      resources: {},
      tools: {},
    },
  },
);
// Setup request handlers for MCP protocol
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
  resources: [
    {
      uri: "console://logs",
      mimeType: "text/plain",
      name: "Browser console logs",
    },
    ...Array.from(screenshots.keys()).map(name => ({
      uri: `screenshot://${name}`,
      mimeType: "image/png",
      name: `Screenshot: ${name}`,
    })),
  ],
}));
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  const uri = request.params.uri.toString();
  if (uri === "console://logs") {
    return {
      contents: [{
        uri,
        mimeType: "text/plain",
        text: consoleLogs.join("\n"),
      }],
    };
  }
  if (uri.startsWith("screenshot://")) {
    const name = uri.split("://")[1];
    const screenshot = screenshots.get(name);
    if (screenshot) {
      return {
        contents: [{
          uri,
          mimeType: "image/png",
          blob: screenshot,
        }],
      };
    }
  }
  throw new Error(`Resource not found: ${uri}`);
});
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: TOOLS,
}));
server.setRequestHandler(CallToolRequestSchema, async (request) =>
  handleToolCall(request.params.name, request.params.arguments ?? {})
);
/**
 * Main function to run the MCP server
 */
async function runServer() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
}
runServer().catch(console.error);
// Clean up when process ends
process.stdin.on("close", () => {
  console.error("BrowserCat MCP Server closed");
  server.close();
});
```