Files
Ruslan LesiutinandGitHub 54831395ea [react-devtools-cdt-mcp] chrome-devtools-mcp integration (#36600)
> [!NOTE]
> I had to invalidate `node_modules` cache keys on CI, because we are
now using `react-devtools-facade` package and we need to create a
symlink to it, otherwise Flow will fail. We use a hash value of
`yarn.lock`, but it's not invalidated when a package inside yarn
workspace is using another private package.

---

This change introduces new `react-devtools-cdt-mcp` package, which is
responsible for installing React tools for `chrome-devtools-mcp` server.
It is using `react-devtools-facade` as a dependency: it doesn't own the
implementation of these tools, it just relays Facade's tools to
`chrome-devtools-mcp` format.

When CDT MCP navigates to a fresh page, it emits unique
`devtoolsdiscovery` method, that has `respondWith` method, which is the
integration point between runtime libraries and MCP server.
`react-devtools-cdt-mcp` is the owner for this integration. More details
are available here -
https://github.com/ChromeDevTools/chrome-devtools-mcp/blob/main/docs/third-party-developer-tools.md.

Here is the list of tools that are being installed:
- `react_get_component_tree`
- `react_get_component_by_uid`
- `react_find_components`
- `react_get_component_source`
- `react_get_owner_stack_trace`
- `react_get_owner_stack`
- `react_start_profiling`
- `react_stop_profiling `
- `react_get_trace_overview`
- `react_get_commit_report`

--- 
Quick demo how to test the package with `chrome-devtools-mcp` CLI. Steps
are also mentioned in `README` file:


https://github.com/user-attachments/assets/af9fefce-03f0-4ebd-a94f-e9dd72b87f4d
2026-06-30 17:52:30 +02:00

118 lines
2.9 KiB
JavaScript

import * as React from 'react';
const {
useState,
useEffect,
useCallback,
useContext,
createContext,
memo,
forwardRef,
} = React;
// A custom hook, to exercise the hooks tree in react_get_component_by_uid.
function useToggle(initial) {
const [on, setOn] = useState(initial);
const toggle = useCallback(() => setOn(value => !value), []);
return [on, toggle];
}
// Function component with a single State hook + a button that triggers
// re-renders (useful for profiling).
function Counter() {
const [count, setCount] = useState(0);
return (
<div className="row">
<span>Count: {count}</span>
<button onClick={() => setCount(value => value + 1)}>+1</button>
</div>
);
}
// Uses the custom hook above, so its hooks tree nests a "Toggle" custom hook.
function Toggle() {
const [on, toggle] = useToggle(false);
return <button onClick={toggle}>{on ? 'ON' : 'OFF'}</button>;
}
// State + Effect hook with a timer — re-renders every second, handy for
// react_start_profiling / react_get_trace_overview / react_get_commit_report.
function Clock() {
const [now, setNow] = useState(() => new Date().toLocaleTimeString());
useEffect(() => {
const id = setInterval(() => setNow(new Date().toLocaleTimeString()), 1000);
return () => clearInterval(id);
}, []);
return <div className="row">Clock: {now}</div>;
}
// Context, to exercise the "context" node type and useContext.
const ThemeContext = createContext('light');
function ThemeProvider({children}) {
return <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>;
}
function ThemedPanel() {
const theme = useContext(ThemeContext);
return (
<section className="panel" data-theme={theme}>
<h2>Panel (theme: {theme})</h2>
<Counter />
<Toggle />
<Clock />
</section>
);
}
// List with keys, to exercise key reporting and find_components pagination.
function Todo({text}) {
return <li>{text}</li>;
}
function TodoList() {
const items = [
{id: 'learn', text: 'Learn the tools'},
{id: 'test', text: 'Test the fixture'},
{id: 'ship', text: 'Ship the package'},
];
return (
<ul className="todos">
{items.map(item => (
<Todo key={item.id} text={item.text} />
))}
</ul>
);
}
// memo and forwardRef, to exercise the "memo" and "forwardRef" node types.
const MemoBox = memo(function MemoBox({label}) {
return <div className="box">Memo: {label}</div>;
});
const FancyInput = forwardRef(function FancyInput({placeholder}, ref) {
return <input ref={ref} placeholder={placeholder} />;
});
function Header() {
return (
<header>
<h1>react-devtools-cdt-mcp fixture</h1>
</header>
);
}
export default function App() {
return (
<main className="app">
<Header />
<ThemeProvider>
<ThemedPanel />
</ThemeProvider>
<TodoList />
<MemoBox label="hello" />
<FancyInput placeholder="type here" />
</main>
);
}