This guide explains how to configure and use Kapture with Model Context Protocol (MCP) clients like Claude Desktop, Cline, and custom implementations.
chrome://extensions/extension folderClaude 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"]
}
}
}
Ask your AI assistant to:
The npx kapture-mcp command includes intelligent server detection:
{
"mcpServers": {
"kapture": {
"command": "npx",
"args": ["-y", "kapture-mcp@latest", "bridge"]
}
}
}
This approach:
If you've cloned the repository:
{
"mcpServers": {
"kapture": {
"command": "node",
"args": ["/path/to/kapture/server/dist/index.js", "bridge"]
}
}
}
For custom integrations or manual server control:
npx kapture-mcp{
"mcpServers": {
"kapture": {
"transport": "websocket",
"url": "ws://localhost:61822/mcp"
}
}
}
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.json~/.config/Claude/claude_desktop_config.jsonKapture provides 28 tools organized into functional categories:
Navigate to a URL with optional timeout.
{
tabId: "tab_1234567890",
url: "https://example.com",
timeout: 30000 // optional, ms
}
Browser history navigation.
{
tabId: "tab_1234567890"
}
Reload the current page (similar to pressing F5).
{
tabId: "tab_1234567890"
}
Bring the tab to the front and focus it.
{
tabId: "tab_1234567890"
}
All interaction tools support both CSS selectors and XPath expressions.
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 over elements to trigger effects — by selector, xpath, or viewport coordinate.
{
tabId: "tab_1234567890",
selector: ".dropdown-trigger"
// OR: { x: 640, y: 360 }
}
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 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
}
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 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 dropdown options (HTML <select> only).
{
tabId: "tab_1234567890",
selector: "#country",
value: "us" // option value
}
Send keyboard events with modifier support.
{
tabId: "tab_1234567890",
key: "Control+a", // Select all
selector: "#editor", // optional
delay: 100 // optional ms
}
Supported keys:
"a", "Enter", "Tab""Control+c", "Shift+Tab""PageDown", "F5"Manage element focus.
{
tabId: "tab_1234567890",
selector: "#search-input"
}
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
}
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
}
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
}
Get HTML content.
{
tabId: "tab_1234567890",
selector: "article" // optional
}
Query multiple elements with visibility filtering.
{
tabId: "tab_1234567890",
selector: "a.external-link",
visible: "true" // true|false|all
}
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 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
}
Get all elements at specific coordinates.
{
tabId: "tab_1234567890",
x: 500,
y: 300
}
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.
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
}
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
}
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
}
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.
Get all connected tabs.
{} // no parameters needed
Get comprehensive tab information.
{
tabId: "tab_1234567890"
}
Open a new browser tab.
{
browser: "chrome" // optional
}
Opens the Kapture how-to page.
Close a browser tab.
{
tabId: "tab_1234567890"
}
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.
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
}
Key behaviors to remember:
xpath parameter instead of selector for XPath expressionsResources 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) |
// 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"]'
});
// 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
});
// 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' });
}
// 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;
}
// 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);
// 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);
}
elements tool to debuglist_tabs to get current tabsAccess data directly via browser:
http://localhost:61822/ - Server statushttp://localhost:61822/tabs - Tab listhttp://localhost:61822/tab/{tabId}/screenshot/view - View screenshotconst elements = await callTool('elements', {
tabId,
selector: '.my-element',
visible: 'all'
});
elements.forEach(el => {
console.log(`${el.selector}: visible=${el.isVisible}`);
});
Use console_logs to inspect the console, or watch_console to capture browser errors in real time as they happen.
Kapture excels at supporting multiple AI clients simultaneously!
See the Multi-Assistant Guide for detailed setup.
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'
});
const tabs = await callTool('list_tabs', {});
if (tabs.length === 0) {
throw new Error('No tabs connected');
}
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`);
}
Kapture provides a robust bridge between AI assistants and web browsers through:
Start with the basic configuration, connect a browser tab, and begin automating!