Skip to main content
Glama
Patrizzos
by Patrizzos

StyleSpeak - MCP and CLI

A companion MCP server and CLI to stylesafe that makes CSS legible to AI agents — resolving cascade, tracing properties, predicting change impact, and explaining what applies and why before an agent touches a single line of styles.

The problem

AI coding agents write CSS without being able to see its effect. They modify a rule, assume it worked, and move on — unaware that a higher-specificity rule elsewhere already overrides it, a combinator rule in another file is silently winning, or a CSS variable resolves to something entirely different than expected. stylespeak gives agents a structured knowledge layer to consult before making changes.

Related MCP server: @designjs/mcp-server

What it does

resolve_styles — answers "what CSS actually applies to this selector?"

Given a selector and a set of files, returns every CSS property the selector would receive, which rule wins for each, and which rules were overridden — with confidence levels since no real DOM is available. CSS custom properties (var()) are resolved to their actual values, including chained variables, fallbacks, and media-context overrides.

trace_property — answers "everywhere this property is set, who wins?"

Given a property name and a set of files, returns every rule that sets it, groups competing rules that target overlapping selectors, and shows the full cascade chain for each group — with resolved variable values included.

impact_preview — answers "if I change this, what else breaks?"

Given a selector, property, and optional new value, predicts the full blast radius of the change before it's made — showing which selectors will see a different value, which are shielded by higher specificity, which cascade relationships are uncertain, and which downstream rules are affected through CSS variable chains or property inheritance. Works without a browser.

style_manifest — answers "what does this entire project's CSS look like at a glance?"

Builds a compressed, structured summary of a project's entire CSS knowledge — selectors, properties, variables, competition groups, and risk hotspots — that an agent can load once at the start of a session and keep in context instead of repeatedly querying individual files.

live_resolve — answers "what does the browser actually compute for this selector?"

Queries a running Chromium browser via CDP and returns exact computed styles and matched rules. No heuristics, no confidence levels — the browser resolved it. Requires Chrome running with --remote-debugging-port=9222.

Works on more than plain CSS. The four static tools above analyze .css, .scss, CSS Modules, and — as of v1.3 — styled-components and Emotion source files (.js / .jsx / .ts / .tsx), all through the same cascade engine.

Quick start

As a CLI tool

npm install -g @patrizzos/stylespeak
stylespeak resolve ".btn.primary" src/styles/main.css
stylespeak trace "color" --projectRoot src/styles
stylespeak impact ".btn" "background-color" src/styles/main.css --newValue "#ff0000"
stylespeak manifest --projectRoot src/styles

# styled-components / Emotion — components are addressed by variable name (const Button = styled.button`` -> .Button)
stylespeak resolve ".Button" src/components/Button.jsx
stylespeak manifest --projectRoot src

As an MCP server

Add to your MCP client config (Cursor: .cursor/mcp.json, VS Code: .vscode/mcp.json):

{
  "mcpServers": {
    "stylespeak": {
      "command": "node",
      "args": ["/absolute/path/to/stylespeak/src/server.js"]
    }
  }
}

Once connected, agents can call:

  • resolve_styles({ selector, files, projectRoot, componentFiles? })

  • trace_property({ property, files, projectRoot })

  • impact_preview({ selector, property, newValue?, files, projectRoot })

  • style_manifest({ files?, projectRoot?, maxSelectors? })

  • live_resolve({ selector, port?, tabUrl? })

Example output

impact_preview

stylespeak impact ".btn" "background-color" src/styles/buttons.css --newValue "#ff0000"
{
  "change": {
    "selector": ".btn",
    "property": "background-color",
    "currentValue": "var(--color-primary)",
    "newValue": "#ff0000"
  },
  "blastRadius": {
    "total": 3,
    "valueChanges": 1,
    "shielded": 1,
    "risks": 0,
    "variableDownstream": 0,
    "inheritanceDownstream": 1
  },
  "riskLevel": "low",
  "safeToChange": true,
  "impacts": [
    {
      "type": "value-change",
      "affectedSelector": ".btn",
      "currentValue": "var(--color-primary)",
      "newValue": "#ff0000",
      "confidence": "certain"
    },
    {
      "type": "shielded",
      "affectedSelector": ".btn.primary",
      "shieldingValue": "darkblue",
      "confidence": "certain",
      "reason": ".btn.primary has higher specificity — elements with both classes won't be affected"
    }
  ],
  "summary": "1 selector will see a different value, 1 selector is shielded by higher specificity.",
  "agentNote": "Change appears safe to make. Shielded selectors are safe — higher-specificity rules protect those elements."
}

resolve_styles

stylespeak resolve ".btn" src/styles/buttons.css
{
  "properties": {
    "background-color": {
      "winner": {
        "value": "var(--color-primary)",
        "resolvedValue": "#2563eb",
        "variableChain": ["--color-primary → #2563eb"],
        "selector": ".btn",
        "specificity": "(0,0,1,0)"
      },
      "confidence": "certain"
    }
  },
  "variables": { "--color-primary": { "value": "#2563eb", "selector": ":root" } }
}

Confidence levels

Level

Meaning

certain

Exact selector match — rule definitively applies

likely

Rule tokens are a subset of the queried selector — applies in most cases

possible

Combinator rule — depends on DOM ancestry, unknown without rendering

exact

Returned by live_resolve only — browser-resolved, no heuristics

CSS custom property resolution

As of v0.2, stylespeak fully resolves CSS custom properties (var()) in all output:

  • value — the raw value as written (var(--color-primary))

  • resolvedValue — the actual resolved value (#2563eb)

  • variableChain — the full resolution path, including chained variables

  • conditionalValues — media-context overrides where the variable resolves differently

Supported: simple, fallback, nested fallback, chained, scoped, media-context, circular reference protection.

CSS-in-JS support (styled-components & Emotion)

As of v1.3, stylespeak statically extracts styles from .js, .jsx, .ts, .tsx, .mjs and .cjs files that import styled-components, @emotion/styled, @emotion/react or @emotion/css, and runs them through the same cascade engine as plain CSS. resolve_styles, trace_property, impact_preview and style_manifest all work on them, and CSS-in-JS and regular CSS can be analyzed together.

Pass JS/TS files in files, or point projectRoot at a directory — files that import one of these libraries are discovered automatically and all other JS/TS files are ignored.

Addressing components

Generated class names are hashed at runtime, so each styled component is addressed by its variable name as a class selector:

const Button = styled.button`...`;    // -> .Button
export default styled.section`...`;   // -> .Card  (named after the file, e.g. Card.jsx)

Rules are tagged cssInJs, library and component, and are treated as locally scoped (like CSS Modules): two components named Wrapper in different files are independent, never reported as conflicting, and never counted as a "declared in N locations" risk. Querying such a name is flagged as ambiguous, because results will mix both files.

What is extracted

Syntax

Handling

Tagged templates

styled.div`…`, styled(Comp)`…`, styled.div.attrs(…)`…`, styled.div<Props>`…` (TypeScript generics), css`…`

Object syntax

styled.div({ … }), styled(Comp)({ … }), css({ … }), styled.div(props => ({ … }))

Global styles

createGlobalStyle / injectGlobal — real selectors, not locally scoped

Nesting

&, && (specificity bump), &:hover, bare :hover, nested selectors, @media / @supports / @container

Component selectors

${Icon} { … } becomes .Icon, e.g. .Button .Icon

Constants

Same-file const strings, numbers and object paths: ${SPACING}, ${colors.primary}, @media ${bp.md}

css helpers

Same-file const mixin = css`…` is inlined where interpolated (${mixin}, ...mixin) and is also queryable by its own name (tagged css-helper) — useful because Emotion's css={mixin} prop makes the helper the only visible record of those styles

Object notation

camelCase becomes kebab-case, vendor prefixes (WebkitX → -webkit-x, msX → -ms-x), unitless numbers get px (except unitless properties like opacity, z-index, line-height)

What is flagged, never guessed

Values that depend on props or theme can't be known statically. stylespeak keeps the original expression, marks it, and tells the agent:

"color": {
  "winner": {
    "value": "${props => props.color}",
    "selector": ".Button",
    "cssInJs": true,
    "library": "styled-components",
    "component": "Button",
    "dynamic": true
  },
  "confidence": "certain",
  "dynamic": true
}
  • dynamic: true on a declaration, winner and property means the value depends on runtime props/theme — the winner may differ depending on props.

  • Conditional blocks and mixin calls (${p => p.active && css…}) can't be evaluated. They are dropped and counted in dynamicInterpolations, and agentNote warns that additional styles may apply at runtime.

  • Style blocks that can't be attached to a selector are listed in cssInJs.skipped in style_manifest output instead of being silently ignored.

  • styled(Base) records extends; agentNote tells the agent to also resolve the base component.

Limitations

This is static analysis, so there are boundaries:

  • styled(Base) does not merge the base component's styles into the child — resolve the base selector separately.

  • Constants and theme objects imported from other files are not resolved (same-file only). ThemeProvider themes are not resolved.

  • Emotion's css prop on JSX elements and style blocks not assigned to a named variable are anonymous — there is no selector to target, so they are reported as skipped.

  • keyframes are not modeled, and .attrs({ className }) is not considered.

  • Cross-file injection order is approximated by the order files are provided.

  • For ground truth on a running app, use live_resolve against the real element.

How it pairs with stylesafe

stylesafe catches problems in your CSS — conflicts, dead rules, Tailwind clashes — before they ship.

stylespeak explains your CSS — resolving cascade, tracing properties, predicting impact, resolving variables — so agents understand before they act.

Use stylesafe as a post-edit check. Use stylespeak as a pre-edit consultation. Together they give AI coding agents a complete feedback loop on styles.

Live browser inspection

live_resolve requires a Chromium browser running with remote debugging enabled:

# Windows
chrome.exe --remote-debugging-port=9222

# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222

# Linux
google-chrome --remote-debugging-port=9222

Security note: Never run --remote-debugging-port on a machine exposed to untrusted networks or in production. This flag opens a local API that any process on the machine can connect to.

File format support

Format

Support level

.css

Full

.scss

Full — nesting, & references, @media passthrough

.module.css / .module.scss

Full — locally scoped classes detected and tagged

vanilla-extract / Linaria / StyleX

Supported via compiled CSS output

styled-components / Emotion

Supported (v1.3) — static extraction from .js / .jsx / .ts / .tsx / .mjs / .cjs; props/theme-dependent values are flagged dynamic, not guessed

CSS-in-JS object syntax

Supported (v1.3) — styled.div({…}), css({…}), function-returned objects

Emotion css prop (inline JSX)

Not extractable — anonymous, reported as skipped

Architecture

src/
  cssParser.js            — CSS tokenizer with SCSS nesting support
  specificity.js          — standard (id, class, type) specificity calculator
  cssomBuilder.js         — in-memory cascade model builder (CSS, SCSS, CSS Modules, CSS-in-JS)
  selectorMatcher.js      — heuristic selector matching with confidence levels
  variableResolver.js     — CSS custom property resolution
  cssModulesAnalyzer.js   — CSS Modules local scope detection
  scssNestingExpander.js  — SCSS nesting pre-processor
  cssInJsExtractor.js     — styled-components / Emotion static extractor (template + object syntax)
  astComponentGraph.js    — AST component graph for graph-aware matching
  resolveStyles.js        — resolve_styles tool
  traceProperty.js        — trace_property tool
  impactPreview.js        — impact_preview tool
  styleManifest.js        — style_manifest project-wide knowledge builder
  cdpBridge.js            — Chrome DevTools Protocol WebSocket client
  liveResolve.js          — live_resolve tool
  server.js               — MCP server (stdio JSON-RPC) + CLI entry point

Zero external dependencies. Requires Node.js 21+.

Roadmap

  • v0.2 ✅ — CSS custom property resolution

  • v0.3 ✅ — SCSS nesting, CSS Modules scope awareness, AST component graph

  • v1.0 ✅ — Chrome DevTools Protocol live resolution

  • v1.1 ✅ — impact_preview: blast radius prediction before making a change

  • v1.2 ✅ — style_manifest: compressed project-wide CSS knowledge for agent context

  • v1.3 ✅ — CSS-in-JS: static extraction for styled-components and Emotion (template and object syntax)

Next (not yet scheduled)

Known gaps in CSS-in-JS support, in no particular order: merging styled(Base) styles into the child, resolving constants and theme objects imported from other files, and modeling keyframes and the Emotion css prop.

Available Tools

2 tools
resolve_stylesA

Resolves what CSS actually applies to a given selector. Returns every property the selector would receive, which rule wins for each property, and which rules were overridden — with confidence levels (certain/likely/possible) since no real DOM is available. Call this before modifying styles for an element to understand the full cascade context first.

ParametersJSON Schema
NameRequiredDescriptionDefault
filesNoAbsolute paths to CSS/SCSS files to analyze.
selectorYesCSS selector to resolve, e.g. ".btn.primary", "#header a:hover"
projectRootNoOptional: path to a project root. All CSS/SCSS files will be discovered recursively.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Discloses key behavioral trait of confidence levels due to no real DOM, which adds context beyond a simple read. No annotations exist, so description carries full burden; it sufficiently addresses limitations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two efficient sentences with no superfluous information. First sentence states core purpose, second adds usage guidance and behavioral nuance. Front-loaded and to the point.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given tool complexity (CSS cascade) and no output schema, description outlines expected return values (properties, winner, overridden, confidence). Sufficient for an agent to gauge use, though could mention error handling or file discovery behavior.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so description adds minimal new semantics beyond schema. The description reinforces the overall behavior but does not enhance parameter understanding beyond what schema provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool resolves CSS for a given selector, listing specific outputs (properties, winning rule, overridden rules) and distinguishes from sibling 'trace_property' by focusing on full cascade context.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly advises calling this before modifying styles to understand cascade context, providing clear usage guidance. Does not mention alternatives explicitly but implies it for initial diagnosis.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

trace_propertyA

Traces a CSS property across all provided files — finding every rule that sets it, grouping competing rules that target overlapping selectors, and showing who wins in each group. Use this to understand the blast radius of a property before changing it, or to find out why a property value isn't applying as expected.

ParametersJSON Schema
NameRequiredDescriptionDefault
filesNoAbsolute paths to CSS/SCSS files to analyze.
propertyYesCSS property name to trace, e.g. "color", "background-color", "padding"
projectRootNoOptional: path to a project root. All CSS/SCSS files will be discovered recursively.

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Without annotations, the description fully discloses the tool's behavior: it traces properties across files, groups competing rules, and shows winners. It implies a read-only analysis. It could mention file type restrictions or performance notes, but the existing detail is sufficient for safe invocation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences: the first explains functionality succinctly, the second gives use cases. Every sentence adds value, no fluff. It is front-loaded with the core action.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, the description adequately covers the tool's purpose and input requirements. It details what the tool does with the input (grouping, showing winners). It could briefly describe the output format, but it is mostly complete for a read-only analysis tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so baseline is 3. The description adds no extra meaning beyond the schema; it only restates that files are provided and property is traced. It does not elaborate on the optional projectRoot parameter or provide examples.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses specific verbs ('traces', 'finding', 'grouping', 'showing') and clearly identifies the resource ('CSS property across all provided files'). It details the outcome (every rule, competing rules grouped, winner shown) and distinguishes from the sibling tool 'resolve_styles' by focusing on properties vs. general styling.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states when to use the tool: 'to understand the blast radius of a property before changing it, or to find out why a property value isn't applying as expected.' It does not provide negative use cases or alternatives, but the context is clear and actionable.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv0.1.0
    • First observedresolve_styles
    • First observedtrace_property

TDQS

A4.2/5.0

Scored across 2 tools

Disambiguation5/5

Both tools have clearly distinct purposes: one resolves all CSS properties for a selector, the other traces a single property across files. No overlap.

Naming Consistency5/5

Both use consistent snake_case with verb_noun pattern (resolve_styles, trace_property), adhering to a clear naming convention.

Tool Count3/5

Two tools is on the low side, but appropriate for a focused CSS analysis utility. Could benefit from additional tools like listing files or selectors, but scope is narrow.

Completeness4/5

Covers the core use cases of understanding cascade and property conflicts. Minor gap: no tool to enumerate available selectors or files, but the stated purpose is well-served.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers