← Back to Documentation

Using Kapture with MCP Clients

This guide explains how to configure and use Kapture with Model Context Protocol (MCP) clients like Claude Desktop, Cline, and custom implementations.

Table of Contents

Quick Start

1. Install the Chrome Extension

  1. Download from releases or clone the repo
  2. Open chrome://extensions/
  3. Enable "Developer mode"
  4. Click "Load unpacked" and select the extension folder

2. Configure Your MCP Client

Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "kapture": {
      "command": "npx",
      "args": ["-y", "kapture-mcp@latest", "bridge"]
    }
  }
}

Cline/VS Code (settings.json):

{
  "cline.mcpServers": {
    "kapture": {
      "command": "npx",
      "args": ["-y", "kapture-mcp@latest", "bridge"]
    }
  }
}

3. Connect Browser Tab

  1. Open any website in Chrome
  2. Open DevTools (F12)
  3. Click the "Kapture" panel
  4. You'll see "Connected" status

4. Start Using

Ask your AI assistant to:

Understanding the Architecture

How Kapture Works

graph LR MCP[MCP Client
Claude/Cline] -->|stdio| B[bridge] B -->|WebSocket| KS[Kapture Server] KS --> CE[Chrome Extension] CE --> BT[Browser Tabs]

Key Components

  1. MCP Server: Runs on port 61822, handles all MCP protocol communication
  2. Bridge Mode: Translates between stdio (used by MCP clients) and WebSocket
  3. Chrome Extension: Injects into DevTools, executes browser commands
  4. WebSocket: Real-time bidirectional communication

Smart Server Management

The npx kapture-mcp command includes intelligent server detection:

Installation & Configuration

System Requirements

Configuration Options

Recommended: Bridge Command

{
  "mcpServers": {
    "kapture": {
      "command": "npx",
      "args": ["-y", "kapture-mcp@latest", "bridge"]
    }
  }
}

This approach:

Alternative: Local Installation

If you've cloned the repository:

{
  "mcpServers": {
    "kapture": {
      "command": "node",
      "args": ["/path/to/kapture/server/dist/index.js", "bridge"]
    }
  }
}

Advanced: Direct WebSocket

For custom integrations or manual server control:

  1. Start server manually: npx kapture-mcp
  2. Configure client:
    {
      "mcpServers": {
        "kapture": {
          "transport": "websocket",
          "url": "ws://localhost:61822/mcp"
        }
      }
    }

Configuration File Locations

Claude Desktop:

VS Code (Cline):

Available Tools

Kapture provides 28 tools organized into functional categories:

Navigation Tools

navigate

Navigate to a URL with optional timeout.

{
  tabId: "tab_1234567890",
  url: "https://example.com",
  timeout: 30000  // optional, ms
}

back / forward

Browser history navigation.

{
  tabId: "tab_1234567890"
}

reload

Reload the current page (similar to pressing F5).

{
  tabId: "tab_1234567890"
}

show

Bring the tab to the front and focus it.

{
  tabId: "tab_1234567890"
}

Interaction Tools

All interaction tools support both CSS selectors and XPath expressions.

click

Click elements, or click blind at a viewport coordinate (CSS pixels — reaches inside iframes, which selectors can't). Returns the unique selector of the clicked element.

{
  tabId: "tab_1234567890",
  selector: "button.submit",  // CSS
  // OR
  xpath: "//button[contains(text(), 'Submit')]"
  // OR: { x: 640, y: 360 }  // viewport coordinate
}

hover

Hover over elements to trigger effects — by selector, xpath, or viewport coordinate.

{
  tabId: "tab_1234567890",
  selector: ".dropdown-trigger"
  // OR: { x: 640, y: 360 }
}

fill

Fill text inputs by setting value directly (fast). For inputs that ignore .value, use type or insertText.

{
  tabId: "tab_1234567890",
  selector: "#email",
  value: "user@example.com"
}

type

Type a string as individual keystrokes (real keydown/keyup/input events per character). Works on "fake" inputs and rich editors that ignore .value. Types at the cursor; use clear first to replace.

{
  tabId: "tab_1234567890",
  selector: "#search",   // optional; else the focused element
  text: "hello world",
  delay: 0               // optional ms between keys
}

insertText

Insert a whole string at once (like an IME commit/paste — fires input but no per-key events). Best for bulk text and editors like Google Docs.

{
  tabId: "tab_1234567890",
  selector: "#editor",   // optional; else the focused element
  text: "a large block of text"
}

clear

Clear a text field by selecting all and deleting with real key events (Ctrl/Cmd+A then Backspace). Works on inputs, textareas, contenteditable, and "fake" inputs.

{
  tabId: "tab_1234567890",
  selector: "#search"    // optional; else the focused element
}

select

Select dropdown options (HTML <select> only).

{
  tabId: "tab_1234567890",
  selector: "#country",
  value: "us"  // option value
}

keypress

Send keyboard events with modifier support.

{
  tabId: "tab_1234567890",
  key: "Control+a",  // Select all
  selector: "#editor",  // optional
  delay: 100  // optional ms
}

Supported keys:

  • Single: "a", "Enter", "Tab"
  • Modifiers: "Control+c", "Shift+Tab"
  • Special: "PageDown", "F5"

focus / blur

Manage element focus.

{
  tabId: "tab_1234567890",
  selector: "#search-input"
}

scroll

Scroll an element into view (centered), the document to an absolute coordinate, or — selector/xpath and x/y together — within the element itself (an inner pane or infinite list). Returns the resulting scroll position.

{
  tabId: "tab_1234567890",
  selector: ".footer"
  // OR: { y: 0 }  // jump to top
  // OR: { x: 0, y: 1200 }
  // OR: { selector: "#sidebar", y: 500 }  // scroll within the element
}

dialog

Answer a JavaScript dialog (alert, confirm, prompt, onbeforeunload) blocking the tab. The command that triggered it returns a dialog field; every other command fails fast with DIALOG_OPEN until you respond.

{
  tabId: "tab_1234567890",
  accept: true            // OK / Leave; false = Cancel / Stay
  // text: "..."          // value for prompt() dialogs
}

Information Tools

screenshot

Capture screenshots with compression options.

{
  tabId: "tab_1234567890",
  selector: ".chart",  // optional
  scale: 0.5,  // 0.1-1.0
  format: "webp",  // webp|jpeg|png
  quality: 0.85  // 0.1-1.0
}

dom

Get HTML content.

{
  tabId: "tab_1234567890",
  selector: "article"  // optional
}

elements

Query multiple elements with visibility filtering.

{
  tabId: "tab_1234567890",
  selector: "a.external-link",
  visible: "true"  // true|false|all
}

console_logs

Get the tab's console contents — what you'd see in the DevTools console, including console messages, uncaught exceptions, and browser-generated entries. Newest first.

{
  tabId: "tab_1234567890",
  level: "error",  // optional
  limit: 100,  // optional
  before: "2024-01-01T00:00:00Z"
}

watch_console

Watch the console in real time. Collects everything logged during the timeout window, then returns it in chronological order. Great for observing the result of an action.

{
  tabId: "tab_1234567890",
  timeout: 30000  // required, 1000-60000 ms
}

elementsFromPoint

Get all elements at specific coordinates.

{
  tabId: "tab_1234567890",
  x: 500,
  y: 300
}

Network Monitoring

Capture has no history — the browser only reports requests that happen while monitoring is on. Turn it on before the traffic you want to observe (e.g. before navigating or clicking), then read the list and pull individual bodies. Watchers are tracked by client identity: enabling twice is idempotent, monitoring stays on until every watcher turns it off, and a disconnected client's watchers are released automatically. The buffer is cleared when monitoring fully stops.

network_monitor

Turn network capture on or off for a tab. While on, every request's metadata accumulates into a per-tab buffer. force:true with enabled:false stops it unconditionally. HTTP callers can pass a clientId as their identity (MCP clients are identified automatically).

{
  tabId: "tab_1234567890",
  enabled: true,   // false to stop
  force: false,    // optional, with enabled:false
  clientId: "ci-1" // optional, HTTP callers only
}

network_requests

List captured requests. Each carries a requestId for network_body, a monotonic seq, and hasPostData when the request had a payload; pass the prior cursor as since to poll only new ones.

{
  tabId: "tab_1234567890",
  since: 0,    // optional cursor
  limit: 50    // optional, 1-1000
}

network_body

Fetch a request's bodies (monitoring must be on): requestBody for the POST payload and body for the response. Read live, so the response returns bodyError if evicted or streaming (text/event-stream can't be read via CDP). Truncated to maxBytes with bodyTruncated:true while size reports the full length.

{
  tabId: "tab_1234567890",
  requestId: "1277.42",
  maxBytes: 65536  // optional, 1024-1048576
}

Composite

compose

Run a sequence of commands against one tab in a single call. The script is one command per line as <tool>?<query-string> — a URL query string whose keys are that tool's parameters — plus wait?t=<ms> to pause. The whole script is validated first (an invalid script runs nothing), then commands run in order and stop at the first failure. Returns an array of the per-command responses in order.

{
  tabId: "tab_1234567890",
  script: "fill?selector=%23email&value=a%40b.com\n" +
          "click?selector=%23submit\n" +
          "wait?t=500\n" +
          "screenshot"
}

Eligible: navigate, back, forward, reload, click, hover, focus, blur, fill, type, insertText, clear, select, keypress, scroll, wait. The final command may also be a data-returning read — screenshot, dom, elements, elementsFromPoint, console_logs, or watch_console — to verify the outcome in the same call. Data reads anywhere else, lifecycle (new_tab, close, show), and evaluate are not eligible.

Tab Management Tools

list_tabs

Get all connected tabs.

{}  // no parameters needed

tab_detail

Get comprehensive tab information.

{
  tabId: "tab_1234567890"
}

new_tab

Open a new browser tab.

{
  browser: "chrome"  // optional
}

Opens the Kapture how-to page.

close

Close a browser tab.

{
  tabId: "tab_1234567890"
}

JavaScript Execution

Gated tool: evaluate is off by default. It is hidden from tools/list (and calls are rejected) until the user turns on the "Allow JS execution" toggle in the Kapture extension popup or DevTools panel for a connected tab. The grant is in-memory only and resets whenever the tab disconnects. When availability changes, the server sends notifications/tools/list_changed.

evaluate

Execute JavaScript in the page's main world and return the result. Promises are awaited; the result must be JSON-serializable. Prefer the purpose-built tools — use evaluate only when no other tool can accomplish the task.

{
  tabId: "tab_1234567890",
  code: "document.title",
  timeout: 5000  // optional, 1000-60000 ms
}

Important Tool Behaviors

Key behaviors to remember:

  1. First Element Only: Tools that accept selectors only operate on the first matching element
  2. Unique Selectors: Tools return the unique selector of the element they operated on
  3. XPath Support: Use xpath parameter instead of selector for XPath expressions
  4. Error Handling: Element-not-found returns success with error details, not an exception

MCP Resources

Resources provide read-only access to browser data:

Resource URI Description Query Parameters
kapture://tabs List of all connected tabs None
kapture://tab/{tabId} Detailed tab information None
kapture://tab/{tabId}/console Console logs with pagination before, limit, level
kapture://tab/{tabId}/screenshot Screenshot as MCP resource selector, scale, format, quality
kapture://tab/{tabId}/dom DOM HTML content selector
kapture://tab/{tabId}/elements Query elements selector, visible
kapture://tab/{tabId}/elementsFromPoint Elements at coordinates x, y (required)

Examples & Patterns

Basic Navigation and Interaction

// Get available tabs
const tabs = await callTool('list_tabs', {});
const tabId = tabs[0].tabId;

// Navigate to a website
await callTool('navigate', {
  tabId,
  url: 'https://example.com'
});

// Fill and submit a form
await callTool('fill', {
  tabId,
  selector: '#username',
  value: 'myuser@example.com'
});

await callTool('fill', {
  tabId,
  selector: '#password',
  value: 'secretpass123'
});

await callTool('click', {
  tabId,
  selector: 'button[type="submit"]'
});

Working with Dynamic Content

// Wait for element to appear
async function waitForElement(tabId, selector, maxAttempts = 10) {
  for (let i = 0; i < maxAttempts; i++) {
    const result = await callTool('elements', {
      tabId,
      selector,
      visible: 'true'
    });

    if (result.elements.length > 0) {
      return result.elements[0];
    }

    await new Promise(resolve => setTimeout(resolve, 1000));
  }
  throw new Error(`Element ${selector} not found after ${maxAttempts} attempts`);
}

// Use it
const element = await waitForElement(tabId, '.dynamic-content');
await callTool('click', {
  tabId,
  selector: element.selector
});

Advanced Form Handling

// Handle custom dropdowns (not HTML select)
await callTool('click', {
  tabId,
  selector: '.custom-dropdown-trigger'
});

await new Promise(resolve => setTimeout(resolve, 500));

await callTool('click', {
  tabId,
  xpath: "//li[@role='option'][contains(text(), 'United States')]"
});

// Handle multi-step forms
async function fillMultiStepForm(tabId, formData) {
  // Step 1: Personal Info
  await callTool('fill', { tabId, selector: '#firstName', value: formData.firstName });
  await callTool('fill', { tabId, selector: '#lastName', value: formData.lastName });
  await callTool('click', { tabId, selector: 'button.next-step' });

  // Wait for next step
  await waitForElement(tabId, '#email');

  // Step 2: Contact Info
  await callTool('fill', { tabId, selector: '#email', value: formData.email });
  await callTool('fill', { tabId, selector: '#phone', value: formData.phone });
  await callTool('click', { tabId, selector: 'button.submit-form' });
}

Screenshot Automation

// Capture different parts of a page
async function capturePageSections(tabId) {
  const screenshots = {};

  // Full page
  screenshots.full = await callTool('screenshot', {
    tabId,
    scale: 0.3
  });

  // Specific sections
  const sections = ['header', 'main', 'footer'];
  for (const section of sections) {
    screenshots[section] = await callTool('screenshot', {
      tabId,
      selector: section,
      format: 'png',
      scale: 0.5
    });
  }

  return screenshots;
}

Console Monitoring

// Check for errors after automation
async function runWithErrorMonitoring(tabId, automation) {
  await automation();

  const logs = await callTool('console_logs', {
    tabId,
    level: 'error',
    limit: 100
  });

  if (logs.logs.length > 0) {
    console.warn('Errors detected during automation:', logs.logs);
  }

  return logs.logs;
}

// Or watch in real time: trigger an action, then collect
// everything the page logs over the next 30 seconds
await callTool('click', { tabId, selector: '#start-upload' });
const watched = await callTool('watch_console', {
  tabId,
  timeout: 30000
});
console.log('Logged during upload:', watched.logs);

Network Monitoring

// Capture the API calls a page makes during an action.
// Enable BEFORE the traffic - capture has no history.
async function captureApiCalls(tabId, action) {
  await callTool('network_monitor', { tabId, enabled: true });
  try {
    await action(); // navigate, click, submit a form, etc.

    const { requests } = await callTool('network_requests', { tabId });
    const api = requests.filter(r => r.url.includes('/api/'));

    // Pull what was sent and received, per request, on demand
    for (const r of api) {
      const res = await callTool('network_body', {
        tabId,
        requestId: r.requestId
      });
      console.log(r.method, r.url, '→', res.status);
      if (res.requestBody) console.log('  sent:', res.requestBody);
      console.log('  received:', res.bodyError ? `(${res.bodyError})` : res.body);
    }
    return api;
  } finally {
    await callTool('network_monitor', { tabId, enabled: false });
  }
}

// Poll incrementally on a long-running page using the cursor
let since = 0;
for (let i = 0; i < 5; i++) {
  const page = await callTool('network_requests', { tabId, since });
  since = page.cursor; // only newer requests next time
  console.log('new requests:', page.requests.length);
}

Troubleshooting

Common Issues

"No tabs connected"

  • Click the Kapture toolbar icon
  • Flip the connection toggle
  • Check the badge shows ✓
  • Reload the page if needed

"Element not found"

  • Verify selector is correct
  • Check if element is visible
  • Use elements tool to debug
  • Try XPath if CSS selector fails

Commands timing out

  • Increase timeout parameter
  • Check for JavaScript errors in console
  • Verify tab is still responsive

"Tab not found"

  • Tab may have disconnected
  • Use list_tabs to get current tabs
  • Reconnect via the toolbar popup

Performance Tips

  1. Use Efficient Selectors: ID selectors are fastest, followed by class, then complex selectors
  2. Batch Operations: Group related commands together
  3. Add Strategic Delays: Some interactions need time to complete

Debugging Strategies

1. Use HTTP Endpoints

Access data directly via browser:

2. Check Element Visibility

const elements = await callTool('elements', {
  tabId,
  selector: '.my-element',
  visible: 'all'
});

elements.forEach(el => {
  console.log(`${el.selector}: visible=${el.isVisible}`);
});

3. Monitor Console Output

Use console_logs to inspect the console, or watch_console to capture browser errors in real time as they happen.

Advanced Topics

Multiple AI Assistants

Kapture excels at supporting multiple AI clients simultaneously!

  1. Each client uses the same bridge configuration
  2. Server automatically manages shared access
  3. Commands are queued per tab to prevent conflicts
  4. All clients receive real-time notifications

See the Multi-Assistant Guide for detailed setup.

Custom MCP Client Integration

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { spawn } from 'child_process';

// Spawn the server with bridge
const proc = spawn('npx', ["-y", 'kapture-mcp', 'bridge']);
const transport = new StdioClientTransport({
  command: proc
});

const client = new Client({
  name: 'custom-client',
  version: '1.0.0'
}, {
  capabilities: {}
});

await client.connect(transport);

// Subscribe to notifications
client.on('notification', (notification) => {
  if (notification.method === 'kapture/tabs_changed') {
    console.log('Tabs changed:', notification.params);
  }
});

// Use tools
const result = await client.callTool('navigate', {
  tabId: 'tab_1234567890',
  url: 'https://example.com'
});

Security Considerations

  1. Localhost Only: Server only accepts connections from localhost
  2. Tab Isolation: Each tab has a unique ID, preventing cross-tab access
  3. No File System Access: Extension runs in browser sandbox
  4. Command Validation: All inputs are validated before execution

Best Practices

1. Always Check Tab Status

const tabs = await callTool('list_tabs', {});
if (tabs.length === 0) {
  throw new Error('No tabs connected');
}

2. Handle Dynamic Content Gracefully

3. Optimize Selectors

4. Error Recovery

async function safeClick(tabId, selector, retries = 3) {
  for (let i = 0; i < retries; i++) {
    const result = await callTool('click', { tabId, selector });
    if (result.clicked) return result;
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
  throw new Error(`Failed to click ${selector} after ${retries} attempts`);
}

Summary

Kapture provides a robust bridge between AI assistants and web browsers through:

Start with the basic configuration, connect a browser tab, and begin automating!