[mcp] Add MCP tool to print out the component tree of the currently open React App#33305
[mcp] Add MCP tool to print out the component tree of the currently open React App#33305
Conversation
|
Comparing: 462d08f...ca1d5e0 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 |
| 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.
| 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.
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.
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.
| __IS_FIREFOX__: IS_FIREFOX, | ||
| __IS_EDGE__: IS_EDGE, | ||
| __IS_NATIVE__: false, | ||
| __IS_INTERNAL__: IS_INTERNAL, |
There was a problem hiding this comment.
| __IS_INTERNAL__: IS_INTERNAL, | |
| __IS_INTERNAL_MCP_BUILD__: IS_INTERNAL_MCP_BUILD, |
.eslintrc.js
Outdated
| __IS_FIREFOX__: 'readonly', | ||
| __IS_EDGE__: 'readonly', | ||
| __IS_NATIVE__: 'readonly', | ||
| __IS_INTERNAL__: 'readonly', |
There was a problem hiding this comment.
| __IS_INTERNAL__: 'readonly', | |
| __IS_INTERNAL_MCP_BUILD__: 'readonly', |
| getNearestMountedDOMNode, | ||
| getElementIDForHostInstance, | ||
| getInstanceAndStyle, | ||
| ...(__IS_INTERNAL__ && {internal_only_getComponentTree}), |
There was a problem hiding this comment.
| ...(__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.
| .getComponentTree(); | |
| .__internal_only_getComponentTree(); |
| return unresolvedSource; | ||
| } | ||
|
|
||
| function internal_only_getComponentTree(): string { |
There was a problem hiding this comment.
| function internal_only_getComponentTree(): string { | |
| function __internal_only_getComponentTree(): string { |
There was a problem hiding this comment.
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 {
...
}
}
There was a problem hiding this comment.
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 { ... } }
Hmm this doesn't seem to work, I get a not defined error
| 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.
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.
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.
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, ...
There was a problem hiding this comment.
Oh yes, I think we'll benefit from having more data to pipe into the LLM I'll remove this line
| const componentTree = await parseReactComponentTree(url); | ||
|
|
||
| return { | ||
| content: [ | ||
| { | ||
| type: 'text' as const, | ||
| text: componentTree, | ||
| }, | ||
| ], | ||
| }; |
There was a problem hiding this comment.
probably want to wrap this in a try/catch and return the error response to the llm, like we do in other tools
|
|
||
| return componentTree; | ||
| } else { | ||
| throw new Error('Localhost page not found'); |
There was a problem hiding this comment.
| throw new Error('Localhost page not found'); | |
| throw new Error(`Could not open the page at ${url}. Is your server running?`); |
hoxyq
left a comment
There was a problem hiding this comment.
Please still try gating __internal_only_getComponentTree function definition under __IS_INTERNAL_MCP_BUILD__.
I just want to make sure this dead code doesn't get into production builds, you can double-check it by building it and then inspecting the source scripts.
If ESLint is unhappy about it, just suppress the error.
Full list of changes: * devtools: emit performance entries only when profiling ([hoxyq](https://github.com/hoxyq) in [#33652](#33652)) * Get Server Component Function Location for Parent Stacks using Child's Owner Stack ([sebmarkbage](https://github.com/sebmarkbage) in [#33629](#33629)) * Added minimum indent size to Component Tree ([jsdf](https://github.com/jsdf) in [#33517](#33517)) * [devtools-shell] layout options for testing ([jsdf](https://github.com/jsdf) in [#33516](#33516)) * Remove feature flag enableRenderableContext ([kassens](https://github.com/kassens) in [#33505](#33505)) * refactor[devtools]: update css for settings and support css variables in shadow dom scnenario ([hoxyq](https://github.com/hoxyq) in [#33487](#33487)) * [mcp] Add MCP tool to print out the component tree of the currently open React App ([jorge-cab](https://github.com/jorge-cab) in [#33305](#33305)) * [scripts] Switch back to flow parser for prettier ([rickhanlonii](https://github.com/rickhanlonii) in [#33414](#33414)) * upgrade json5 ([rickhanlonii](https://github.com/rickhanlonii) in [#33358](#33358)) * Get source location from structured callsites in prepareStackTrace ([sebmarkbage](https://github.com/sebmarkbage) in [#33143](#33143)) * Clean up enableSiblingPrerendering flag ([jackpope](https://github.com/jackpope) in [#32319](#32319))
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