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}`);
      }
    }

Schema Changelog

Changes observed during successful MCP inspections.

  1. First observed

TDQS

B3.1/5.0
Behavior2/5

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

No annotations are provided, and the description does not disclose what happens if connection fails, whether state changes, or prerequisites (e.g., Chrome must already be running with remote debugging).

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

Conciseness4/5

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

The description is a single sentence with 10 words, efficiently conveying the core purpose without unnecessary details.

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?

With no output schema and no annotations, the description is too brief for a connection tool. It lacks information about return values, success/failure indicators, and prerequisites for using the tool.

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?

Input schema coverage is 100% and already includes parameter descriptions. The tool description adds no additional behavioral details beyond what is in the schema, so baseline score of 3 is appropriate.

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 action ('Connect') and resource ('existing Chrome instance with remote debugging enabled'), distinguishing it from sibling tools that perform page manipulations.

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

Usage Guidelines2/5

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

No guidance on when to use this tool versus alternatives, such as launching a new browser. It does not indicate it is a prerequisite for other puppeteer actions.

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