Skip to content

Commit d0d623d

Browse files
authored
[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
1 parent 3a620bb commit d0d623d

24 files changed

Lines changed: 4616 additions & 19 deletions

.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ packages/react-devtools-shared/src/hooks/__tests__/__source__/__untransformed__/
2828
packages/react-devtools-shell/dist
2929
packages/react-devtools-timeline/dist
3030
packages/react-devtools-timeline/static
31+
packages/react-devtools-cdt-mcp/dist
32+
packages/react-devtools-cdt-mcp/fixtures
3133

3234
# Imported third-party Flow types
3335
flow-typed/

.github/workflows/devtools_regression_tests.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ jobs:
6969
with:
7070
path: |
7171
**/node_modules
72-
key: runtime-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
72+
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
7373
- name: Ensure clean build directory
7474
run: rm -rf build
7575
- run: yarn install --frozen-lockfile
@@ -129,7 +129,7 @@ jobs:
129129
with:
130130
path: |
131131
**/node_modules
132-
key: runtime-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
132+
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
133133
- run: yarn install --frozen-lockfile
134134
if: steps.node_modules.outputs.cache-hit != 'true'
135135
- name: Restore all archived build artifacts
@@ -163,7 +163,7 @@ jobs:
163163
with:
164164
path: |
165165
**/node_modules
166-
key: runtime-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
166+
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
167167
- run: yarn install --frozen-lockfile
168168
if: steps.node_modules.outputs.cache-hit != 'true'
169169
- name: Restore all archived build artifacts

.github/workflows/runtime_build_and_test.yml

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ jobs:
4444
with:
4545
path: |
4646
**/node_modules
47-
key: runtime-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
47+
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
4848
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
4949

5050
- uses: actions/setup-node@v4
@@ -71,7 +71,7 @@ jobs:
7171
with:
7272
path: |
7373
**/node_modules
74-
key: runtime-and-compiler-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
74+
key: runtime-and-compiler-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
7575
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
7676
- run: yarn install --frozen-lockfile
7777
if: steps.node_modules.outputs.cache-hit != 'true'
@@ -116,7 +116,7 @@ jobs:
116116
with:
117117
path: |
118118
**/node_modules
119-
key: runtime-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
119+
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
120120
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
121121
- name: Ensure clean build directory
122122
run: rm -rf build
@@ -142,7 +142,7 @@ jobs:
142142
with:
143143
path: |
144144
**/node_modules
145-
key: runtime-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
145+
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
146146
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
147147
- name: Ensure clean build directory
148148
run: rm -rf build
@@ -170,7 +170,7 @@ jobs:
170170
with:
171171
path: |
172172
**/node_modules
173-
key: runtime-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
173+
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
174174
- name: Ensure clean build directory
175175
run: rm -rf build
176176
- run: yarn install --frozen-lockfile
@@ -224,7 +224,7 @@ jobs:
224224
with:
225225
path: |
226226
**/node_modules
227-
key: runtime-and-compiler-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
227+
key: runtime-and-compiler-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
228228
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
229229
- name: Ensure clean build directory
230230
run: rm -rf build
@@ -251,7 +251,7 @@ jobs:
251251
with:
252252
path: |
253253
**/node_modules
254-
key: runtime-and-compiler-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
254+
key: runtime-and-compiler-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
255255
- name: Install runtime dependencies
256256
run: yarn install --frozen-lockfile
257257
if: steps.node_modules.outputs.cache-hit != 'true'
@@ -290,7 +290,7 @@ jobs:
290290
with:
291291
path: |
292292
**/node_modules
293-
key: runtime-and-compiler-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
293+
key: runtime-and-compiler-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
294294
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
295295
- name: Ensure clean build directory
296296
run: rm -rf build
@@ -372,7 +372,7 @@ jobs:
372372
with:
373373
path: |
374374
**/node_modules
375-
key: runtime-and-compiler-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
375+
key: runtime-and-compiler-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock', 'compiler/yarn.lock') }}
376376
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
377377
- name: Ensure clean build directory
378378
run: rm -rf build
@@ -417,7 +417,7 @@ jobs:
417417
with:
418418
path: |
419419
**/node_modules
420-
key: runtime-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
420+
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
421421
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
422422
- name: Ensure clean build directory
423423
run: rm -rf build
@@ -455,7 +455,7 @@ jobs:
455455
with:
456456
path: |
457457
**/node_modules
458-
key: runtime-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
458+
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
459459
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
460460
- name: Ensure clean build directory
461461
run: rm -rf build
@@ -516,7 +516,7 @@ jobs:
516516
with:
517517
path: |
518518
**/node_modules
519-
key: runtime-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
519+
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
520520
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
521521
- name: Ensure clean build directory
522522
run: rm -rf build
@@ -552,7 +552,7 @@ jobs:
552552
with:
553553
path: |
554554
**/node_modules
555-
key: runtime-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
555+
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
556556
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
557557
- name: Ensure clean build directory
558558
run: rm -rf build
@@ -696,7 +696,7 @@ jobs:
696696
with:
697697
path: |
698698
**/node_modules
699-
key: runtime-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
699+
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
700700
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
701701
- name: Ensure clean build directory
702702
run: rm -rf build
@@ -754,7 +754,7 @@ jobs:
754754
with:
755755
path: |
756756
**/node_modules
757-
key: runtime-node_modules-v9-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
757+
key: runtime-node_modules-v10-${{ runner.arch }}-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
758758
# Don't use restore-keys here. Otherwise the cache grows indefinitely.
759759
- name: Ensure clean build directory
760760
run: rm -rf build

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ packages/react-devtools-fusebox/dist
4242
packages/react-devtools-inline/dist
4343
packages/react-devtools-shell/dist
4444
packages/react-devtools-timeline/dist
45+
packages/react-devtools-cdt-mcp/dist
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# react-devtools-cdt-mcp
2+
3+
Integrates React tools with
4+
[chrome-devtools-mcp](https://github.com/ChromeDevTools/chrome-devtools-mcp).
5+
6+
Importing this package **before React** installs the DevTools hook and registers
7+
a React tool group via chrome-devtools-mcp's `devtoolstooldiscovery` / `__dtmcp`
8+
third-party-tool protocol. The React tools then become discoverable and callable
9+
inside a chrome-devtools-mcp session — no separate server.
10+
11+
## Usage
12+
13+
Import the package **before** React so the hook is installed before React
14+
initializes:
15+
16+
```js
17+
import 'react-devtools-cdt-mcp';
18+
import React from 'react';
19+
```
20+
21+
When the page runs under chrome-devtools-mcp, the React tools are listed by
22+
`list_3p_developer_tools` and callable either via
23+
`execute_3p_developer_tool({toolName, params})` or directly via `evaluate_script`
24+
(`window.__dtmcp.executeTool(toolName, params)`).
25+
26+
## Conventions
27+
28+
- **UIDs** — components are identified by a stable uid like `r5`. UIDs
29+
are consistent across every tool and across re-renders. These UIDs don't survive page reloads.
30+
- **Output** — every tool returns the shape described below as a plain
31+
JavaScript value. On failure a tool returns `{error: string}` instead.
32+
- **Durations** — profiler durations are in milliseconds, or `null` when the
33+
build does not collect profiling timing.
34+
35+
## Tools
36+
37+
### `react_get_component_tree`
38+
39+
Snapshot of the component tree.
40+
41+
- **Input:** `depth?` (number, max depth, default 20), `rootUid?` (string,
42+
start from this component).
43+
- **Output:** `{nodes}` where `nodes` is an array of
44+
`{uid, type, name, key, firstChild, nextSibling}`. `firstChild` and
45+
`nextSibling` reference other nodes by uid (or are `null`).
46+
47+
### `react_get_component_by_uid`
48+
49+
Detailed info for a single component.
50+
51+
- **Input:** `uid` (string, required), `includeHooks?` (boolean, default
52+
`false`).
53+
- **Output:** `{uid, type, name, key?, props?, hooks?}`. `props` excludes
54+
children and is normalized to a serialization-safe shape; when `includeHooks`
55+
is true, `hooks` (function, forwardRef, and memo components) is an array of
56+
`{id, name, value, subHooks}`.
57+
58+
### `react_find_components`
59+
60+
Find components by case-insensitive name substring.
61+
62+
- **Input:** `name` (string, required), `rootUid?` (string, limit to subtree),
63+
`page?` (number, default 1), `pageSize?` (number, default 10).
64+
- **Output:** `{page, pageSize, totalCount, totalPages, results}` where
65+
`results` is an array of tree nodes (same shape as `react_get_component_tree`).
66+
67+
### `react_get_component_source`
68+
69+
Definition source location of a component.
70+
71+
- **Input:** `uid` (string, required).
72+
- **Output:** `{source: {name, fileName, line, column}}`, or `{source: null}`
73+
when the location cannot be determined (e.g. host components, production
74+
builds).
75+
76+
### `react_get_owner_stack_trace`
77+
78+
Raw owner stack trace — the chain of JSX creation locations up to the root.
79+
80+
- **Input:** `uid` (string, required).
81+
- **Output:** `{stack: string}` (DEV-only; empty in production).
82+
83+
### `react_get_owner_stack`
84+
85+
Structured owner list — which components rendered this one.
86+
87+
- **Input:** `uid` (string, required).
88+
- **Output:** an array of `{uid, name, type}`, ordered from immediate owner to
89+
root ancestor (empty for a root component). DEV-only.
90+
91+
### `react_start_profiling`
92+
93+
Start a profiling session that records per-commit render timing.
94+
95+
- **Input:** `traceName?` (string, trace name; auto-generated if omitted).
96+
- **Output:** `{status: "started", traceName}`.
97+
98+
### `react_stop_profiling`
99+
100+
Stop the active profiling session.
101+
102+
- **Input:** none.
103+
- **Output:** `{status: "stopped", traceName, commits}` (`commits` is the number of
104+
commits recorded).
105+
106+
### `react_get_trace_overview`
107+
108+
Per-commit overview of a recorded trace.
109+
110+
- **Input:** `traceName` (string, required).
111+
- **Output:** array of
112+
`{commit, committedAt, renderDuration, layoutDuration, passiveDuration, componentsChanged}`,
113+
one row per commit.
114+
115+
### `react_get_commit_report`
116+
117+
Detailed report for a single commit.
118+
119+
- **Input:** `traceName` (string, required), `commitIndex` (number, required;
120+
zero-based).
121+
- **Output:**
122+
`{committedAt, priority, renderDuration, layoutDuration, passiveDuration, components}`
123+
where `components` is an array of
124+
`{uid, name, type, actualDuration, selfDuration}` sorted by `actualDuration`
125+
descending.

0 commit comments

Comments
 (0)