About chrome-devtools-mcp
chrome-devtools-mcp is an MCP server for controlling and inspecting Chrome through Chrome DevTools, aimed at browser automation and testing. It offers page snapshots, interaction, script evaluation, screenshots, and performance and accessibility checks, with optional extension and memory tools.
Key features
- 01Navigate and wait for pages to load
- 02Inspect page structure and interact with elements
- 03Evaluate scripts to check page state
- 04Capture viewport screenshots
- 05Check performance, layout, and accessibility
- 06Enable optional extension and memory tools
Use cases
- 01Automate browser interactions and data retrieval
- 02Inspect page errors and network failures
- 03Test responsive layouts at different viewport sizes
- 04Verify extension behavior on web pages
MCP support
It acts as an MCP server that exposes Chrome browser control and inspection tools to MCP clients. Clients can target pages by page ID, with optional extension or memory tools enabled through configuration flags.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ gsmlg-dev/ code-agent --skill chrome-devtools-mcpAfter installing, type/chrome-devtools-mcpin Claude Code to confirm it loaded.
Details
From the project READMECore Concepts
Browser lifecycle: Browser starts automatically on first tool call using a persistent Chrome profile. Configure via CLI args in the MCP server configuration: npx chrome-devtools-mcp@latest --help.
Additional tooling can be enabled by providing the following flags:
- For extension tooling, use the
--categoryExtensionsflag. - For memory tooling, use the
--memoryDebuggingflag.
Page targeting: Page-scoped tools require a pageId parameter to target a specific page. Use list_pages to see available pages and their IDs (e.g. pageId: 1), or use the ID returned when creating a page with new_page.
Note: For evaluate_script, pageId is required when targeting pages. However, when --categoryExtensions is enabled, pageId is optional so you can pass serviceWorkerId instead to evaluate inside an extension background service worker.
Element interaction: Use take_snapshot to get page structure with element uids. Each element has a unique uid for interaction. If an element isn't found, take a fresh snapshot - the element may have been removed or the page changed.
Workflow Patterns
Before interacting with a page
- Navigate:
navigate_pageornew_page - Wait:
wait_forto ensure content is loaded if you know what you look for. - Snapshot:
take_snapshotwithpageIdto understand page structure - Interact: Use element
uids from snapshot forclick,fill, etc., passing the correspondingpageId.
Efficient data retrieval
- Use
filePathparameter for large outputs (screenshots, snapshots, traces) - Use pagination (
pageIdx,pageSize) and filtering (types) to minimize data - Set
includeSnapshot: falseon input actions unless you need updated page state
Tool selection
- Automation/interaction:
take_snapshot(text-based, faster, better for automation) - Visual inspection:
take_screenshot(when user needs to see visual state) - Additional details:
evaluate_scriptfor data not in accessibility tree
Parallel execution
You can send multiple tool calls in parallel, but maintain correct order: navigate → wait → snapshot → interact.
Testing an extension
Extension tools (install_extension, list_extensions, etc.) are available
only when the MCP server is started with the --categoryExtensions flag. If
these tools are unavailable, stop and ask the user to update their MCP server
configuration and restart the MCP server (or AI client).
- Install: Use
install_extensionwith the path to the unpacked extension. - Identify: Get the extension ID from the response or by calling
list_extensions. - Trigger Action: Use
trigger_extension_actionto open the popup or side panel if applicable. - Verify Service Worker: Use
evaluate_scriptwithserviceWorkerId(omittingpageIdandargs) to check extension state or trigger background actions. When evaluating in a page, passpageId(omittingserviceWorkerId). - Verify Page Behavior: Navigate to a page where the extension operates and use
take_snapshotto check if content scripts injected elements or modified the page correctly.
Troubleshooting
If chrome-devtools-mcp is insufficient, guide users to use Chrome DevTools UI:
If there are errors launching chrome-devtools-mcp or Chrome, refer to https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/docs/troubleshooting.md.
Chrome DevTools MCP Usage
Critical Constraint: Screenshot Size Limit
API submissions have a hard limit of 8000px on any dimension. Full-page screenshots frequently exceed this.
Solution: Always use viewport-only screenshots by default
// ✓ CORRECT: Viewport-only (default)
take_screenshot({ fullPage: false })
// ✓ CORRECT: Save to filesystem for large pages
take_screenshot({ fullPage: false, path: './screenshots/page.png' })
// ✗ AVOID: Full-page without size check
take_screenshot({ fullPage: true })
When full-page screenshots are needed
Check page height first:
const height = execute_script('return document.documentElement.scrollHeight')
if (height > 8000) {
take_screenshot({ fullPage: false, path: './screenshot.png' })
} else {
take_screenshot({ fullPage: true })
}
Alternative: Capture multiple viewport screenshots by scrolling:
take_screenshot({ fullPage: false, path: './top.png' })
execute_script('window.scrollBy(0, window.innerHeight)')
take_screenshot({ fullPage: false, path: './middle.png' })
Navigation and Timing
Always wait after navigation to allow JS execution and rendering:
navigate('https://example.com')
wait(1000) // Minimum recommended wait
// For dynamic apps, wait for specific elements
navigate('https://example.com')
execute_script(`
return new Promise(resolve => {
const check = () => {
if (document.querySelector('#app')) resolve();
else setTimeout(check, 100);
};
check();
});
`)
Error Detection Patterns
Console Errors
const errors = execute_script(`
return performance.getEntriesByType('navigation')[0].type === 'reload'
? []
: (window.__consoleErrors || []);
`)
Network Failures
const failed = execute_script(`
return performance.getEntriesByType('resource')
.filter(r => r.transferSize === 0 && r.duration > 0)
.map(r => ({ url: r.name, duration: r.duration }));
`)
Layout Issues
const issues = execute_script(`
const issues = [];
// Horizontal overflow
if (document.documentElement.scrollWidth > window.innerWidth) {
issues.push({ type: 'horizontal-overflow', width: document.documentElement.scrollWidth });
}
// Elements outside viewport
document.querySelectorAll('*').forEach(el => {
const rect = el.getBoundingClientRect();
if (rect.right > window.innerWidth) {
issues.push({ type: 'overflow-right', element: el.tagName });
}
});
return issues;
`)
Responsive Testing
Test multiple viewports efficiently:
const viewports = [
{ width: 375, height: 667, name: 'mobile' },
{ width: 768, height: 1024, name: 'tablet' },
{ width: 1440, height: 900, name: 'desktop' }
];
for (const vp of viewports) {
set_viewport(vp.width, vp.height);
wait(500); // Allow reflow
take_screenshot({
fullPage: false,
path: `./screenshots/${vp.name}.png`
});
}
Component State Testing
Test interactive states without user interaction:
// Hover state
execute_script(`
const el = document.querySelector('#button');
el.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true }));
`);
take_screenshot({ fullPage: false, path: './hover.png' });
// Focus state
execute_script(`
document.querySelector('#input').focus();
`);
take_screenshot({ fullPage: false, path: './focus.png' });
// Active/pressed state
execute_script(`
const el = document.querySelector('#button');
el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
`);
take_screenshot({ fullPage: false, path: './active.png' });
Performance Metrics
Collect web vitals and performance data:
const metrics = execute_script(`
return {
fcp: performance.getEntriesByName('first-contentful-paint')[0]?.startTime,
lcp: performance.getEntriesByType('largest-contentful-paint')[0]?.startTime,
cls: performance.getEntriesByType('layout-shift')
.reduce((sum, entry) => sum + (entry.hadRecentInput ? 0 : entry.value), 0),
resources: performance.getEntriesByType('resource').length,
totalSize: performance.getEntriesByType('resource')
.reduce((sum, r) => sum + r.transferSize, 0)
};
`);
Accessibility Checks
Basic accessibility validation:
const a11y = execute_script(`
const issues = [];
// Missing alt text
document.querySelectorAll('img:not([alt])').forEach(img => {
issues.push({ type: 'missing-alt', src: img.src });
});
// Missing form labels
document.querySelectorAll('input:not([aria-label]):not([id])').forEach(input => {
if (!input.closest('label')) {
issues.push({ type: 'missing-label', name: input.name });
}
});
// Empty links
document.querySelectorAll('a').forEach(link => {
if (!link.textContent.trim() && !link.getAttribute('aria-label')) {
issues.push({ type: 'empty-link', href: link.href });
}
});
return issues;
`);
Standard Audit Workflow
Use this pattern for systematic page audits:
function audit_page(url, name) {
// Navigate
navigate(url);
wait(1000);
// Screenshot (viewport only to avoid size errors)
const screenshot_path = `./screenshots/${name}.png`;
take_screenshot({ fullPage: false, path: screenshot_path });
// Page metrics
const height = execute_script('return document.documentElement.scrollHeight');
const viewport = execute_script('return window.innerHeight');
// Error detection
const console_errors = execute_script('return window.__errors || []');
const network_failures = execute_script(`
return performance.getEntriesByType('resource')
.filter(r => r.transferSize === 0 && r.duration > 0)
.map(r => r.name);
`);
const layout_issues = execute_script(`
return document.documentElement.scrollWidth > window.innerWidth
? ['horizontal-overflow']
: [];
`);
return {
name,
url,
screenshot_path,
page_height: height,
viewport_height: viewport,
needs_scroll: height > viewport,
console_errors,
network_failures,
layout_issues,
has_issues: console_errors.length > 0 ||
network_failures.length > 0 ||
layout_issues.length > 0
};
}
Error Handling
Screenshot Dimension Error
If you encounter dimension errors despite using fullPage: false, the page may be using unusual viewport settings. Save to file instead:
try {
take_screenshot({ fullPage: false });
} catch (error) {
if (error.includes('8000 pixels')) {
take_screenshot({ fullPage: false, path: './fallback.png' });
}
}
Navigation Timeout
try {
navigate(url);
wait(5000);
} catch (error) {
// Log failure and continue
console.log(`Failed to load: ${url}`);
}
Missing Elements
const exists = execute_script(`return !!document.querySelector('#target')`);
if (!exists) {
take_screenshot({ fullPage: false, path: './missing-element.png' });
// Handle accordingly
}
Key Reminders
- Always default to
fullPage: falseto avoid dimension errors - Save screenshots to filesystem when possible instead of API submission
- Wait after navigation to allow JS execution (minimum 1000ms)
- Use execute_script for DOM queries instead of brittle selector-based approaches
- Test multiple viewports for responsive applications
- Capture evidence early - take screenshots before elements change state
- Check page dimensions before deciding on screenshot strategy
Additional Tooling Categories
Addional tooling can be enabled by providing the following flags:
- For extension tooling, use the
--categoryExtensionsflag. - For memory tooling, use the
--memoryDebuggingflag.
Testing an extension
Before proceeding: Extension tools (
install_extension,list_extensions, etc.) are only available when the MCP server is started with the--categoryExtensionsflag. If these tools are not in your tool list, stop and ask the user to update their MCP server configuration:{ "mcpServers": { "chrome-devtools": { "command": "npx", "args": ["chrome-devtools-mcp@latest", "--categoryExtensions"] } } }After updating, the user must restart the MCP server (or their AI client) for the change to take effect.
- Install: Use
install_extensionwith the path to the unpacked extension. - Identify: Get the extension ID from the response or by calling
list_extensions. - Trigger Action: Use
trigger_extension_actionto open the popup or side panel if applicable. - Verify Service Worker: Use
evaluate_scriptwithserviceWorkerIdto check extension state or trigger background actions. - Verify Page Behavior: Navigate to a page where the extension operates and use
take_snapshotto check if content scripts injected elements or modified the page correctly.