| name | webmcp-chrome-devtools-quickstart |
| description | AI-driven browser automation using Chrome DevTools MCP with WebMCP tools for structured, token-efficient interactions |
| triggers | ["how do I set up WebMCP tools with Chrome DevTools","integrate AI agents with browser automation using WebMCP","create structured browser tools for AI instead of screenshots","register JavaScript functions as AI-callable tools","connect Claude or Cursor to browser automation with MCP","reduce token usage in browser automation workflows","implement WebMCP tool discovery and execution","build AI-driven web interactions with Chrome DevTools MCP"] |
WebMCP Chrome DevTools Quickstart
Skill by ara.so — Devtools Skills collection.
This skill teaches AI agents how to use WebMCP with Chrome DevTools MCP to enable structured, token-efficient browser automation. Instead of screenshot-based workflows, WebMCP lets you register JavaScript functions as AI-callable tools, reducing token usage by up to 89%.
What This Project Does
WebMCP turns your website's JavaScript functions into AI-callable tools using the Model Context Protocol (MCP). The Chrome DevTools MCP server connects to Chrome via the Chrome DevTools Protocol (CDP) and provides:
- 26 browser automation tools (navigation, interaction, inspection, tab management)
- WebMCP tool discovery (
list_webmcp_tools)
- WebMCP tool execution (
call_webmcp_tool)
Architecture:
AI Client → Chrome DevTools MCP → Chrome (CDP) → Your Website (navigator.modelContext)
Installation
1. Clone and Run Demo
git clone https://github.com/WebMCP-org/chrome-devtools-quickstart.git
cd chrome-devtools-quickstart
npm install
npm run dev
2. Add MCP Server to AI Client
Claude Code:
claude mcp add chrome-devtools npx @mcp-b/chrome-devtools-mcp@latest
claude mcp add --transport http webmcp-docs https://docs.mcp-b.ai/mcp
Cursor (.cursor/mcp.json):
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": ["@mcp-b/chrome-devtools-mcp@latest"]
},
"webmcp-docs": {
"url": "https://docs.mcp-b.ai/mcp"
}
}
}
Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": ["@mcp-b/chrome-devtools-mcp@latest"]
},
"webmcp-docs": {
"url": "https://docs.mcp-b.ai/mcp"
}
}
}
Windsurf (mcp_config.json):
{
"mcpServers": {
"chrome-devtools": {
"command": "npx",
"args": ["@mcp-b/chrome-devtools-mcp@latest"]
},
"webmcp-docs": {
"command": "npx",
"args": ["mcp-remote", "https://docs.mcp-b.ai/mcp"]
}
}
}
3. Install in Your Own Project
npm install @mcp-b/global
import '@mcp-b/global';
Registering WebMCP Tools
Basic Tool (No Parameters)
import '@mcp-b/global';
navigator.modelContext.registerTool({
name: "get_page_title",
description: "Returns the current page title",
inputSchema: {
type: "object",
properties: {}
},
async execute() {
return {
content: [
{ type: "text", text: document.title }
]
};
}
});
Tool with Parameters
import '@mcp-b/global';
let counter = 0;
navigator.modelContext.registerTool({
name: "set_counter",
description: "Sets the counter to the desired value",
inputSchema: {
type: "object",
properties: {
newCounterValue: {
type: "number",
description: "The number to set the counter to"
}
},
required: ["newCounterValue"]
},
async execute(args) {
counter = args.newCounterValue;
document.getElementById('counter').textContent = counter;
return {
content: [
{ type: "text", text: `Counter is now ${counter}` }
]
};
}
});
Tool with Complex Input Schema
import '@mcp-b/global';
navigator.modelContext.registerTool({
name: "create_calendar_event",
description: "Creates a new calendar event",
inputSchema: {
type: "object",
properties: {
title: {
type: "string",
description: "Event title"
},
date: {
type: "string",
description: "Event date in YYYY-MM-DD format"
},
startTime: {
type: "string",
description: "Start time in HH:MM format"
},
endTime: {
type: "string",
description: "End time in HH:MM format"
},
description: {
type: "string",
description: "Event description (optional)"
}
},
required: ["title", "date", "startTime", "endTime"]
},
async execute(args) {
const event = {
id: crypto.randomUUID(),
...args
};
window.calendarEvents = window.calendarEvents || [];
window.calendarEvents.push(event);
renderCalendar();
return {
content: [
{
type: "text",
text: `Created event "${event.title}" on ${event.date} from ${event.startTime} to ${event.endTime}`
}
]
};
}
});
Tool with Error Handling
import '@mcp-b/global';
navigator.modelContext.registerTool({
name: "toggle_theme",
description: "Toggles between light and dark theme",
inputSchema: {
type: "object",
properties: {}
},
async execute() {
try {
const body = document.body;
const currentTheme = body.getAttribute('data-theme') || 'light';
const newTheme = currentTheme === 'light' ? 'dark' : 'light';
body.setAttribute('data-theme', newTheme);
return {
content: [
{ type: "text", text: `Theme switched to ${newTheme} mode` }
]
};
} catch (error) {
return {
content: [
{ type: "text", text: `Error toggling theme: ${error.message}` }
],
isError: true
};
}
}
});
Key Chrome DevTools MCP Tools
Navigation
await use_mcp_tool("chrome-devtools", "navigate_page", {
url: "http://localhost:5173"
});
await use_mcp_tool("chrome-devtools", "go_back", {});
await use_mcp_tool("chrome-devtools", "refresh", {});
WebMCP Tool Discovery
const result = await use_mcp_tool("chrome-devtools", "list_webmcp_tools", {});
WebMCP Tool Execution
const result = await use_mcp_tool("chrome-devtools", "call_webmcp_tool", {
name: "set_counter",
arguments: {
newCounterValue: 42
}
});
Screenshots and Inspection
const screenshot = await use_mcp_tool("chrome-devtools", "take_screenshot", {});
const result = await use_mcp_tool("chrome-devtools", "evaluate_script", {
script: "document.querySelectorAll('.event-card').length"
});
Interaction
await use_mcp_tool("chrome-devtools", "click", {
selector: "#submit-button"
});
await use_mcp_tool("chrome-devtools", "fill", {
selector: "#email-input",
value: "user@example.com"
});
await use_mcp_tool("chrome-devtools", "hover", {
selector: ".tooltip-trigger"
});
Common Patterns
Complete AI Workflow
await use_mcp_tool("chrome-devtools", "navigate_page", {
url: "http://localhost:5173"
});
const tools = await use_mcp_tool("chrome-devtools", "list_webmcp_tools", {});
console.log("Available tools:", tools);
const result = await use_mcp_tool("chrome-devtools", "call_webmcp_tool", {
name: "set_counter",
arguments: { newCounterValue: 42 }
});
const verification = await use_mcp_tool("chrome-devtools", "call_webmcp_tool", {
name: "get_counter",
arguments: {}
});
Multi-Step Form Interaction
import '@mcp-b/global';
navigator.modelContext.registerTool({
name: "submit_contact_form",
description: "Submits the contact form with user details",
inputSchema: {
type: "object",
properties: {
name: { type: "string", description: "User's full name" },
email: { type: "string", description: "User's email address" },
message: { type: "string", description: "Contact message" }
},
required: ["name", "email", "message"]
},
async execute(args) {
document.getElementById('name').value = args.name;
document.getElementById('email').value = args.email;
document.getElementById('message').value = args.message;
const form = document.getElementById('contact-form');
const isValid = form.checkValidity();
if (!isValid) {
return {
content: [{ type: "text", text: "Form validation failed" }],
isError: true
};
}
form.submit();
return {
content: [{ type: "text", text: "Contact form submitted successfully" }]
};
}
});
State Management Tool
import '@mcp-b/global';
navigator.modelContext.registerTool({
name: "get_app_state",
description: "Returns the current application state",
inputSchema: {
type: "object",
properties: {}
},
async execute() {
const state = {
user: window.currentUser || null,
theme: document.body.getAttribute('data-theme'),
activeView: window.location.hash.slice(1),
notifications: window.notifications?.length || 0
};
return {
content: [
{ type: "text", text: JSON.stringify(state, null, 2) }
]
};
}
});
navigator.modelContext.registerTool({
name: "update_app_state",
description: "Updates application state",
inputSchema: {
type: "object",
properties: {
theme: { type: "string", enum: ["light", "dark"] },
activeView: { type: "string" }
}
},
async execute(args) {
if (args.theme) {
document.body.setAttribute('data-theme', args.theme);
}
if (args.activeView) {
window.location.hash = args.activeView;
}
return {
content: [{ type: "text", text: "State updated successfully" }]
};
}
});
Tool with DOM Manipulation
import '@mcp-b/global';
navigator.modelContext.registerTool({
name: "add_todo_item",
description: "Adds a new todo item to the list",
inputSchema: {
type: "object",
properties: {
text: {
type: "string",
description: "The todo item text"
},
priority: {
type: "string",
enum: ["low", "medium", "high"],
description: "Priority level"
}
},
required: ["text"]
},
async execute(args) {
const todoList = document.getElementById('todo-list');
const todoItem = document.createElement('li');
todoItem.className = `todo-item priority-${args.priority || 'medium'}`;
todoItem.innerHTML = `
<span class="todo-text">${args.text}</span>
<button class="delete-btn">Delete</button>
`;
todoList.appendChild(todoItem);
todoItem.querySelector('.delete-btn').addEventListener('click', () => {
todoItem.remove();
});
return {
content: [
{
type: "text",
text: `Added todo: "${args.text}" with ${args.priority || 'medium'} priority`
}
]
};
}
});
Running Benchmarks
Compare token usage between screenshot-based and WebMCP-based workflows:
echo "ANTHROPIC_API_KEY=sk-ant-..." > .env
npm run benchmark:simple:direct
npm run benchmark:complex:direct
Expected Results:
- Simple task: 89% token reduction
- Complex task: 77% token reduction
Configuration
Custom Chrome Launch Options
Create chrome-devtools.config.js:
export default {
chromeOptions: {
headless: false,
userDataDir: './chrome-profile',
args: [
'--window-size=1920,1080',
'--disable-blink-features=AutomationControlled'
]
},
port: 9222
};
Tool Registration Best Practices
import '@mcp-b/global';
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', registerTools);
} else {
registerTools();
}
function registerTools() {
navigator.modelContext.registerTool({
name: "my_tool",
description: "Clear, specific description of what this tool does",
inputSchema: {
type: "object",
properties: {
},
required: []
},
async execute(args) {
try {
return {
content: [{ type: "text", text: "Success message" }]
};
} catch (error) {
return {
content: [{ type: "text", text: error.message }],
isError: true
};
}
}
});
}
Troubleshooting
navigator.modelContext is undefined
Cause: @mcp-b/global not imported or imported after tool registration.
Solution:
import '@mcp-b/global';
import './my-tools.js';
import './my-tools.js';
import '@mcp-b/global';
No tools found by list_webmcp_tools
Cause: Tools not registered yet or page not fully loaded.
Solution:
window.addEventListener('load', () => {
navigator.modelContext.registerTool({
});
});
await use_mcp_tool("chrome-devtools", "evaluate_script", {
script: "typeof navigator.modelContext !== 'undefined' && navigator.modelContext.tools.size"
});
Tool execution fails with isError: true
Debug by inspecting console:
const consoleOutput = await use_mcp_tool("chrome-devtools", "evaluate_script", {
script: `
const errors = [];
console.error = (...args) => errors.push(args.join(' '));
// ... trigger tool call ...
JSON.stringify(errors);
`
});
Chrome connection fails
Cause: Chrome not running or firewall blocking port 9222.
Solution:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
--remote-debugging-port=9222 \
--user-data-dir=/tmp/chrome-debug
lsof -i :9222
Hot reload breaks tool registration
Cause: Vite HMR doesn't re-run @mcp-b/global initialization.
Solution:
if (import.meta.hot) {
import.meta.hot.accept(() => {
window.location.reload();
});
}
Advanced Usage
Multiple Tools in One File
import '@mcp-b/global';
const tools = [
{
name: "get_user_info",
description: "Get current user information",
inputSchema: { type: "object", properties: {} },
async execute() {
return {
content: [{ type: "text", text: JSON.stringify(window.currentUser) }]
};
}
},
{
name: "logout",
description: "Log out the current user",
inputSchema: { type: "object", properties: {} },
async execute() {
window.currentUser = null;
localStorage.removeItem('authToken');
return {
content: [{ type: "text", text: "Logged out successfully" }]
};
}
}
];
tools.forEach(tool => navigator.modelContext.registerTool(tool));
Async Tool with External API
import '@mcp-b/global';
navigator.modelContext.registerTool({
name: "fetch_weather",
description: "Fetches weather data for a city",
inputSchema: {
type: "object",
properties: {
city: { type: "string", description: "City name" }
},
required: ["city"]
},
async execute(args) {
try {
const apiKey = import.meta.env.VITE_WEATHER_API_KEY;
const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${args.city}&appid=${apiKey}`
);
if (!response.ok) {
throw new Error(`Weather API returned ${response.status}`);
}
const data = await response.json();
return {
content: [
{
type: "text",
text: `Weather in ${args.city}: ${data.weather[0].description}, ${Math.round(data.main.temp - 273.15)}°C`
}
]
};
} catch (error) {
return {
content: [{ type: "text", text: `Failed to fetch weather: ${error.message}` }],
isError: true
};
}
}
});
Resources