Feature Request: Support for computed styles
Summary
Please add first-class support for retrieving computed CSS styles through the Chrome DevTools MCP server. Today the MCP surface lets an AI agent inspect DOM/attributes and take screenshots, but it cannot read the browser’s computed styles, which are essential for reliable layout/debugging automation.
Why this matters
- LLMs need ground-truth layout data (final box model, visibility, z-index, display/position, resolved colors/margins/paddings) to diagnose “element not visible,” overlap, off-screen placement, or cascade/specificity issues.
- Visual diffs alone miss subtle regressions (1–2px shifts, collapsed margins, inherited font changes).
- Computed styles enable deterministic before/after comparisons during HMR or CI visual checks.
Requested capabilities (minimum viable)
- Get computed styles for a node: resolved values for all properties (option to filter list).
- Get box model metrics: content/padding/border/margin, client/offset/bounding rect, device-pixel-rounded values.
- Visibility diagnostics: isVisible (accounting for display/visibility/opacity/clip/path, 0×0, off-viewport, overflow hidden).
- Inheritance/precedence hints: which rule “wins” (selector + stylesheet URL/line), optional list of overridden declarations.
- Batch mode: request multiple node IDs in one call for perf.
- Diff helper (optional): server returns changed computed properties between two snapshots.
Sketch of API (illustrative)
dom.getComputedStyles({ nodeId, properties?: string[] }) -> { computed: Record<string,string>, sourceMap?: RuleOrigin[] }dom.getBoxModel({ nodeId }) -> { content, padding, border, margin, clientRect, boundingRect }dom.getVisibility({ nodeId }) -> { isVisible, reasons: string[] }dom.getComputedStylesBatch({ nodeIds: string[] }) -> {...}
Example use cases
- Verify a fix: “Confirm the button’s clickable area is ≥44×44 CSS px and vertically centered in its parent.”
- Root-cause: “Why is .toast not visible after CSS changes?” (visibility + winning rule).
- Regression diff in CI: “List computed properties that changed for #header vs. previous build.”
Thanks for the feature request! I think it is useful to have dedicated tools to debug styles but first we need to think about a general mechanism for inspecting DOM and how it would interact with text snapshot.
Note that the current MCP version is able to inspect computed styles, box models and visibility by using the evaluate_script tool. Have you tried that? Did the example use cases no work for you?
While we consider how to add these functions in the best way we are interesting in getting feedback from other users as well.
Hi all. I am going to throw in a superset idea (or maybe it needs to be separate tools, idk). I built a fork of BrowserTools MCP with feature like this called inspectElementsBySelector (with optional props). It gave a few things in one tool call.
For each element it finds it sends:
-
outerHTML and bounding box dimensions (https://github.com/Munawwar/browser-tools-mcp/blob/main/chrome-extension/devtools.js#L1106). I should have added scroll dimensions (could be an option)
-
CSS rules that affects the element (https://github.com/Munawwar/browser-tools-mcp/blob/main/chrome-extension/devtools.js#L1271). The idea was to give the same view as the Elements > Styles tab of chrome dev tools (I was aiming exactly what a human would see .. I didnt 100% achieve it, but got close)
-
Optionally specific computed styles. i.e only if the tool call asks for specific computed style, else it doesn't send them back to reduce token usage (a single element can have a lot of computed styles)
Maybe I overdid it, but listing all the cascaded CSS rules that affects an element was the most useful for me personally to let LLMs debug issues. Outer HTML also helped in a few cases for LLM to figure out where elements are and what CSS classes was dynamically added by JS which wasn't obvious from code. And to a lesser extent specific computed styles. $0 was a special selector so that I could tell the LLM "debug
I've been using Playwright MCP's browser_evaluate (same) for months... but it requires AI to repeatedly build generalized, DOM-walking one-off code like this on each tool call when troubleshooting style-related UI issues:
() => {
const host = document.querySelector('app-widget-checks');
if (!host) return { error: 'Checks widget <app-widget-checks> not found' };
const selector = [
'table tbody tr:first-of-type td:first-of-type',
'table tr:first-of-type td:first-of-type',
'[role="grid"] [role="row"]:first-of-type [role="gridcell"]:first-of-type',
'td:first-of-type',
'th:first-of-type'
].join(', ');
const cell = host.querySelector(selector);
if (!cell) return { error: 'First cell not found under app-widget-checks', tried: selector };
cell.scrollIntoView({ block: 'center', inline: 'nearest' });
const cs = getComputedStyle(cell);
const rect = cell.getBoundingClientRect();
const pick = (props) => Object.fromEntries(props.map(p => [p, cs.getPropertyValue(p)]));
return {
cellText: (cell.textContent || '').trim(),
rect: {
x: rect.x, y: rect.y, width: rect.width, height: rect.height,
top: rect.top, left: rect.left, right: rect.right, bottom: rect.bottom
},
boxMetrics: {
offsetWidth: cell.offsetWidth,
offsetHeight: cell.offsetHeight,
clientWidth: cell.clientWidth,
clientHeight: cell.clientHeight,
scrollWidth: cell.scrollWidth,
scrollHeight: cell.scrollHeight
},
styles: pick([
'display','position','z-index','box-sizing',
'width','height','min-width','min-height','max-width','max-height',
'margin-top','margin-right','margin-bottom','margin-left',
'padding-top','padding-right','padding-bottom','padding-left',
'border-top-width','border-right-width','border-bottom-width','border-left-width',
'border-top-style','border-right-style','border-bottom-style','border-left-style',
'border-top-color','border-right-color','border-bottom-color','border-left-color',
'color','background-color','font-family','font-size','font-weight','line-height',
'letter-spacing','text-align','vertical-align','white-space','overflow-x','overflow-y'
])
// For all properties (can be large):
// allStyles: Object.fromEntries(Array.from(cs).map(p => [p, cs.getPropertyValue(p)]))
};
}
Purpose-built endpoints would reduce token usage, resources, I/O, and allow simpler troubleshooting and comparisons just by reusing a nodeId across tool requests within session state.
A few examples... AI agents would discover and use these calls instead (much simpler)...
Single element triage:
await navigate_page({ url: 'https://app.local/dashboard' });
await wait_for({ text: 'Checks' });
const snapshot = await take_snapshot({ random_string: 'snap-1' });
// Identify CELL_UID from the snapshot: app-widget-checks → table → tbody → tr:first-of-type → td:first-of-type
const styles = await get_computed_styles({
uid: CELL_UID,
properties: [
'display','position','z-index','box-sizing',
'width','min-width','max-width','height','line-height',
'margin','padding','border','font','color','background-color',
'white-space','overflow-x','overflow-y'
],
includeSources: true
});
const box = await get_box_model({ uid: CELL_UID });
const vis = await get_visibility({ uid: CELL_UID });
Core facts in parallel:
const [styles, box, vis] = await Promise.all([
get_computed_styles({
uid: CELL_UID,
properties: [
'display','position','z-index','box-sizing',
'width','min-width','max-width','height','line-height',
'margin','padding','border','font','color','background-color',
'white-space','overflow-x','overflow-y'
],
includeSources: true
}),
get_box_model({ uid: CELL_UID }),
get_visibility({ uid: CELL_UID })
]);
Batch related nodes (target + context):
// Also pick these from the snapshot:
// ROW_UID: app-widget-checks → table → tbody → tr:first-of-type
// TABLE_UID: app-widget-checks → table
// WIDGET_UID: app-widget-checks
const batch = await get_computed_styles_batch({
uids: [CELL_UID, ROW_UID, TABLE_UID, WIDGET_UID],
properties: ['overflow', 'clip-path', 'opacity', 'display', 'visibility']
});
Rule origins for suspect properties:
const suspect = await get_computed_styles({
uid: CELL_UID,
properties: ['font-size', 'color', 'width'],
includeSources: true
});
Deterministic diffs (before/after state change):
await save_computed_styles_snapshot({
name: 'checks-cell-before',
uids: [CELL_UID],
properties: ['width','font-size','color','background-color','display','visibility','overflow']
});
await hover({ uid: CELL_UID });
// or: await click({ uid: SOME_TOGGLE_UID });
const diff = await diff_computed_styles_snapshot({
name: 'checks-cell-before',
uid: CELL_UID,
properties: ['width','font-size','color','background-color','display','visibility','overflow']
});
Please check out PR #93, which implements the proposed tools.
Thanks for the detailed feedback! Did you encounter any issues related to the fact that the MCP server does not expose the actual DOM tree to the client (and only the a11y tree)? Is it sufficient for your use case? And thanks for the PR. We have seen it and we will review the PR once we decide which direction we will go for this (generally though we would want to re-use code from the chrome-devtools-frontend dependency instead of re-implementing it again with direct CDP calls).
The a11y tree alone works for our initial use cases, although having access to pseudo-elements, etc. will sometimes be important. Using chrome-devtools-frontend directly makes sense. Thanks for providing this!
Hi! In my case I'm trying to troubleshoot why some Angular styles aren't applying correctly. Gemini asked for this:
Could you please show me the entire "Styles" pane in DevTools for the <mat-toolbar> element? I need to see all the matching rules and
which ones are active or crossed out, especially the background-color property. This will show the full picture of the cascade.
So the computed styles isn't enough. We need to see which styles aren't applied, and what order they're being evaluated in, etc.
Thanks!
It would be great to get some structured information that appears to be available to devtools, but (AFAIK) is not available through JS. For example:
- Breaking down the applicable CSS by the source and indicating which are overridden, and DOM inherited from if applicable.
- Computed CSS linked to the source rule
- List every listener for every event, linked to the source for the handler
I hacked CDP myself to build my mcp supporting DOM nodes, CSS rules, and computed style. In fact, I have been building library to handle CDP for months. Would like to discuss about this.
mcp: https://github.com/devtoolcss/chrome-inspector-mcp CDP's DOM & CSS wrapper: https://github.com/devtoolcss/chrome-inspector
generally though we would want to re-use code from the chrome-devtools-frontend dependency instead of re-implementing it again with direct CDP calls
Yeah in theory this mcp can just run another DevTools core, but you'll be dealing with something more complicated than CDP. That's why I would rather build my own library. I also have seen this project started adopting raw CDP calls. Hope to learn about current direction.