Skip to main content
Glama
merajmehrabi

Puppeteer MCP Server

by merajmehrabi

puppeteer_connect_active_tab

Attach to an active Chrome tab via remote debugging port to automate browser actions with Puppeteer.

Instructions

Connect to an existing Chrome instance with remote debugging enabled

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
targetUrlNoOptional URL of the target tab to connect to. If not provided, connects to the first available tab.
debugPortNoOptional Chrome debugging port (default: 9222)

Implementation Reference

  • Handler case in handleToolCall switch: gets WebSocket URL from Chrome debug port, connects to existing browser, returns success/error response.
    case "puppeteer_connect_active_tab":
      try {
        const wsEndpoint = await getDebuggerWebSocketUrl(args.debugPort);
        const connectedPage = await connectToExistingBrowser(
          wsEndpoint, 
          args.targetUrl,
          (logEntry) => {
            state.consoleLogs.push(logEntry);
            notifyConsoleUpdate(server);
          }
        );
        const url = await connectedPage.url();
        const title = await connectedPage.title();
        return {
          content: [{
            type: "text",
            text: `Successfully connected to browser\nActive webpage: ${title} (${url})`,
          }],
          isError: false,
        };
      } catch (error) {
        const errorMessage = (error as Error).message;
        const isConnectionError = errorMessage.includes('connect to Chrome debugging port') || 
                                errorMessage.includes('Target closed');
        
        return {
          content: [{
            type: "text",
            text: `Failed to connect to browser: ${errorMessage}\n\n` +
                  (isConnectionError ? 
                    "To connect to Chrome:\n" +
                    "1. Close Chrome completely\n" +
                    "2. Reopen Chrome with remote debugging enabled:\n" +
                    "   Windows: chrome.exe --remote-debugging-port=9222\n" +
                    "   Mac: /Applications/Google\\ Chrome.app/Contents/MacOS/Google\\ Chrome --remote-debugging-port=9222\n" +
                    "3. Navigate to your desired webpage\n" +
                    "4. Try the operation again" : 
                    "Please check if Chrome is running and try again.")
          }],
          isError: true,
        };
      }
  • Tool definition with inputSchema: optional targetUrl (string) and debugPort (number, default 9222).
      name: "puppeteer_connect_active_tab",
      description: "Connect to an existing Chrome instance with remote debugging enabled",
      inputSchema: {
        type: "object",
        properties: {
          targetUrl: { 
            type: "string", 
            description: "Optional URL of the target tab to connect to. If not provided, connects to the first available tab." 
          },
          debugPort: {
            type: "number",
            description: "Optional Chrome debugging port (default: 9222)",
            default: 9222
          }
        },
        required: [],
      },
    },
  • src/server.ts:35-41 (registration)
    Registration: TOOLS array (including puppeteer_connect_active_tab) is registered via ListToolsRequestSchema, and tool calls are dispatched via CallToolRequestSchema to handleToolCall.
    server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: TOOLS,
    }));
    
    server.setRequestHandler(CallToolRequestSchema, async (request) =>
      handleToolCall(request.params.name, request.params.arguments ?? {}, state, server)
    );
  • Helper function: connects to an existing Chrome browser via WebSocket, finds active non-extension pages, selects a target by URL (or first available), configures headers and console listeners.
    export async function connectToExistingBrowser(
      wsEndpoint: string, 
      targetUrl?: string,
      onConsoleMessage?: (logEntry: string) => void
    ): Promise<Page> {
      logger.info('Connecting to existing browser', { wsEndpoint, targetUrl });
      try {
        // If we have an existing Puppeteer-launched browser, close it
        if (browser && !browser.isConnected()) {
          logger.debug('Closing existing browser connection');
          await browser.close();
          browser = undefined;
          page = undefined;
        }
    
        // Connect to the browser instance with null viewport to maintain browser's viewport
        logger.debug('Establishing connection to browser');
        browser = await puppeteer.connect({ 
          browserWSEndpoint: wsEndpoint,
          defaultViewport: null
        });
        logger.info('Successfully connected to browser');
    
        // Get all pages and find non-extension pages
        const pages = await browser.pages();
        const activeTabs: ActiveTab[] = [];
        
        for (const p of pages) {
          const url = await p.url();
          if (!url.startsWith('chrome-extension://')) {
            const title = await p.title();
            logger.info('Found active webpage:', { url, title });
            activeTabs.push({ page: p, url, title });
          }
        }
    
        if (activeTabs.length === 0) {
          throw new Error("No active non-extension pages found in the browser");
        }
    
        // Select appropriate page
        if (targetUrl) {
          // Find the page with matching URL
          const targetTab = activeTabs.find(tab => tab.url === targetUrl);
          page = targetTab ? targetTab.page : activeTabs[0].page;
        } else {
          // Use the first active non-extension page
          page = activeTabs[0].page;
        }
    
        // Configure page settings
        await page.setExtraHTTPHeaders({
          'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
        });
    
        // Set up console message handling
        if (onConsoleMessage) {
          page.on("console", (msg) => {
            const logEntry = `[${msg.type()}] ${msg.text()}`;
            onConsoleMessage(logEntry);
          });
        }
    
        return page;
      } catch (error) {
        throw error;
      }
    }
  • Helper function: fetches the WebSocket debugger URL from the Chrome DevTools Protocol endpoint at localhost:{port}/json/version.
    export async function getDebuggerWebSocketUrl(port: number = 9222): Promise<string> {
      try {
        const response = await fetch(`http://localhost:${port}/json/version`);
        if (!response.ok) {
          throw new Error(`Failed to fetch debugger info: ${response.statusText}`);
        }
        const data = await response.json();
        if (!data.webSocketDebuggerUrl) {
          throw new Error("No WebSocket debugger URL found. Is Chrome running with --remote-debugging-port?");
        }
        return data.webSocketDebuggerUrl;
      } catch (error) {
        throw new Error(`Failed to connect to Chrome debugging port ${port}: ${(error as Error).message}`);
      }
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description bears full responsibility for behavioral transparency. It fails to disclose side effects (e.g., potential connection failures, state changes like setting a current tab), error conditions, or prerequisites. The description is too sparse given the tool's role in establishing a connection.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, concise sentence with no unnecessary words. It is well-structured and front-loaded, immediately conveying the action and context.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's importance (establishing a connection before other operations), the description is incomplete. It does not explain the connection lifecycle, that this tool is a prerequisite for sibling tools, or what the return value (if any) indicates. The lack of an output schema increases the need for description completeness, which is absent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description does not add any additional meaning beyond the schema; it merely restates the purpose without elaborating on parameter usage, such as how to construct the targetUrl or what debugging port implies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: connecting to an existing Chrome instance with remote debugging. It specifies the verb 'connect' and the resource 'Chrome instance', which distinguishes it from sibling tools that perform actions on a connected page.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies that the Chrome instance must already have remote debugging enabled, but it does not explicitly state when to use this tool versus alternatives, such as launching a new browser. No exclusions or alternative tool references are provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/merajmehrabi/puppeteer-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server