Picture the UI debugging loop most of us run, every single day:
- Notice a bug. ("Why isn't this dropdown closing?")
- Open Claude.
- Paste the component code. Forget the parent. Paste the parent.
- Forget the state. Paste the state.
- Claude asks 3 follow-up questions you could have answered if Claude could just look.
- Five minutes in, you give up and use
console.log.
I lived this loop for about 18 months. In late 2024 I started building what fixes it: an MCP server that lets Claude Code query your running React app directly — component tree, props, state, hook values — without you copy-pasting anything.
What MCP is, in one paragraph
Model Context Protocol is Anthropic's open standard for exposing tools to LLMs. You write a server that publishes a list of tools (with names, schemas, and what they do); Claude Code lists them as callable functions. When Claude "wants" to use a tool, it calls the server, which returns structured data. Claude reasons over the result and either uses another tool or responds.
Crucially: MCP is just stdio. The server runs locally, talks JSON-RPC over stdin/stdout. No network, no authentication theater for local dev.
The setup
Two pieces:
- An in-app agent — a tiny browser script (
installAgent({port: 9474})) that exposes React fiber data over a localhost WebSocket. - The MCP server — runs as
npx mcp-react-devtools, connects to the in-app agent via WebSocket, exposes tools to Claude.
Claude Code → MCP stdio → MCP server → WebSocket → in-app agent → React fiber → result trickles all the way back.
The five tools that matter
react_tree— full component tree of the running appreact_inspect_component— props, state, hooks for any component by selectorreact_listen_render— subscribe to render events for N millisecondsreact_get_state— read live state from Redux, Zustand, or MobX storesreact_diff_state— compare app state at two timestamps
What it actually feels like to use
Real conversation from last week. I had a checkout form where the Submit button was stuck disabled even after the user filled all fields.
Me: "Submit stays disabled. Filled all required fields."
Claude: calling react_inspect_component
Claude: "SubmitButton reads isValidfrom a stale closure of the form context. Form's isValid is true; button still sees false. Replace the destructured const with formContext.formState.isValid."
I would have spent 20 minutes finding that bug. Claude found it in about 3 seconds because it could see the component state.
Production safety
The agent script refuses to install if:
process.env.NODE_ENV === "production"- The host isn't
localhostor127.0.0.1 - The port is exposed to non-loopback interfaces
It's strictly a dev tool. The risk model is: anyone with terminal access can already see your app's state — this just makes it scriptable for Claude.
What I learned building it
React fiber introspection is more tame than you'd think. The fiber root is exposed via __REACT_DEVTOOLS_GLOBAL_HOOK__ — the same API React DevTools uses. Walking it is straightforward; the fiddly part is matching CSS selectors to fiber nodes. I cribbed the approach from React DevTools's source.
Strict Mode double-renders broke my re-render counter for two weeks. I was counting fiber updates and reporting <Component> rendered 92 times in 3 seconds. It was actually 46 — Strict Mode runs render twice in dev. Fix: detect Strict Mode and divide.
Time-travel state diffs are the killer feature. Snapshot the store at t1, do a thing, snapshot at t2, ask Claude what changed. It's like reverse Redux DevTools — except the LLM does the analysis.
Try it
Repo: github.com/hii24/mcp-react-devtools. Install in your dev build, add to your ~/.config/claude/mcp.json, restart Claude Code.
It's rough — v0.4 covers the basics. Issues and PRs welcome, especially if you've got patterns for instrumenting Solid, Vue, or Svelte. Same architecture, different fiber walker.