-
Notifications
You must be signed in to change notification settings - Fork 48.5k
[mcp] Add MCP tool to print out the component tree of the currently open React App #33305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Comparing: 462d08f...1e4614b Critical size changesIncludes critical production bundles, as well as any change greater than 2%:
Significant size changesIncludes any change greater than 0.2%: (No significant changes) |
@hoxyq Added the flag to |
@@ -33,6 +33,8 @@ const IS_FIREFOX = process.env.IS_FIREFOX === 'true'; | |||
const IS_EDGE = process.env.IS_EDGE === 'true'; | |||
const IS_INTERNAL_VERSION = process.env.FEATURE_FLAG_TARGET === 'extension-fb'; | |||
|
|||
const IS_INTERNAL = process.env.IS_INTERNAL === 'true'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const IS_INTERNAL = process.env.IS_INTERNAL === 'true'; | |
const IS_INTERNAL_MCP_BUILD = process.env.IS_INTERNAL_MCP_BUILD === 'true'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see your comment here, this should be correct, yeah. Because installHook
entrypoint, which imports fiber/renderer.js
is listed in webpack.config.js
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To fix failing jobs on CI, please add __IS_INTERNAL_MCP_BUILD__: false
to other build scripts, where applicable. You can check where __IS_CHROME__: false
is defined, for example.
@@ -113,6 +115,7 @@ module.exports = { | |||
__IS_FIREFOX__: IS_FIREFOX, | |||
__IS_EDGE__: IS_EDGE, | |||
__IS_NATIVE__: false, | |||
__IS_INTERNAL__: IS_INTERNAL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__IS_INTERNAL__: IS_INTERNAL, | |
__IS_INTERNAL_MCP_BUILD__: IS_INTERNAL_MCP_BUILD, |
@@ -504,6 +505,7 @@ module.exports = { | |||
__IS_FIREFOX__: 'readonly', | |||
__IS_EDGE__: 'readonly', | |||
__IS_NATIVE__: 'readonly', | |||
__IS_INTERNAL__: 'readonly', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
__IS_INTERNAL__: 'readonly', | |
__IS_INTERNAL_MCP_BUILD__: 'readonly', |
@@ -5873,6 +5946,7 @@ export function attach( | |||
getNearestMountedDOMNode, | |||
getElementIDForHostInstance, | |||
getInstanceAndStyle, | |||
...(__IS_INTERNAL__ && {internal_only_getComponentTree}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...(__IS_INTERNAL__ && {internal_only_getComponentTree}), | |
...(__IS_INTERNAL_MCP_BUILD__ && {internal_only_getComponentTree}), |
const componentTree = await localhostPage.evaluate(() => { | ||
return (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces | ||
.get(1) | ||
.getComponentTree(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.getComponentTree(); | |
.__internal_only_getComponentTree(); |
@@ -5859,6 +5859,79 @@ export function attach( | |||
return unresolvedSource; | |||
} | |||
|
|||
function internal_only_getComponentTree(): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
function internal_only_getComponentTree(): string { | |
function __internal_only_getComponentTree(): string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, can you try gating the definition of this function in __IS_INTERNAL_MCP_BUILD__
?
if (__IS_INTERNAL_MCP_BUILD__) {
function __internal_only_getComponentTree(): string {
...
}
}
if (localhostPage) { | ||
const componentTree = await localhostPage.evaluate(() => { | ||
return (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces | ||
.get(1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get(1)
will usually return you the Fiber renderer, basically the client-side renderer of React.
In case of RSC, there could also be another renderer. I am not sure about the order of registration, but it would probably be registered after the Fiber one.
For component tree, we probably care only about Fiber renderer, but worth keeping in mind that there could be rare cases where there are multiple renderers.
|
||
const name = | ||
(instance.kind !== VIRTUAL_INSTANCE | ||
? getDisplayNameForFiber(instance.data) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is a custom logic for Compiler, whereas every Fiber that has a trace of useMemoCache
would have a Forget(...)
prefix. Also for React.memo
and HOC.
You kinda creating a dependency here between RDT and MCP, because if next time we decide to change Forget
to anything else like Compiled
, it would require updating MCP prompt or whatever.
I am not against keeping it like this for now, but maybe worth forking the getDisplayNameForFiber
function and adding some customisation.
idToDevToolsInstanceMap.forEach(instance => { | ||
if ( | ||
instance.parent === null || | ||
(instance.parent.kind === FILTERED_FIBER_INSTANCE && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How complete you want this tree representation to be? Right now we filter out lots of things, see shouldFilterFiber
implementation.
Maybe you should get a tree representation as full as it is.
Also, this filter out things that are defined in user filters. For example, I think we have a default filter for DOM-elements, like div
, span
, ...
Summary
This tool leverages DevTools to get the component tree from the currently open React App. This gives realtime information to agents about the state of the app.
How did you test this change?
Tested integration with Claude Desktop