-
Notifications
You must be signed in to change notification settings - Fork 50.3k
[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
Changes from 12 commits
0e5c79c
8fa3dfc
a75932b
76dddd1
94718f1
2852c9d
26315d6
d6d929e
789e5f0
a85b0b0
049bfbb
c5ab27a
1e4614b
ab86a5e
81c3a53
183bd4f
9cae1ce
9275c83
6c71a77
df0a663
a586117
ecb1861
ca1d5e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,36 @@ | ||||||
| import puppeteer from 'puppeteer'; | ||||||
|
|
||||||
| export async function parseReactComponentTree(url: string): Promise<string> { | ||||||
| try { | ||||||
| const browser = await puppeteer.connect({ | ||||||
| browserURL: 'http://127.0.0.1:9222', | ||||||
| defaultViewport: null, | ||||||
| }); | ||||||
|
|
||||||
| const pages = await browser.pages(); | ||||||
|
|
||||||
| let localhostPage = null; | ||||||
| for (const page of pages) { | ||||||
| const pageUrl = await page.url(); | ||||||
|
|
||||||
| if (pageUrl.startsWith(url)) { | ||||||
| localhostPage = page; | ||||||
| break; | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| if (localhostPage) { | ||||||
| const componentTree = await localhostPage.evaluate(() => { | ||||||
| return (window as any).__REACT_DEVTOOLS_GLOBAL_HOOK__.rendererInterfaces | ||||||
| .get(1) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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. |
||||||
| .getComponentTree(); | ||||||
|
||||||
| .getComponentTree(); | |
| .__internal_only_getComponentTree(); |
Outdated
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.
| throw new Error('Localhost page not found'); | |
| throw new Error(`Could not open the page at ${url}. Is your server running?`); |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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'; | ||||||
|
||||||
| 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.
Outdated
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, |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -5859,6 +5859,79 @@ | |||||
| return unresolvedSource; | ||||||
| } | ||||||
|
|
||||||
| function internal_only_getComponentTree(): string { | ||||||
|
||||||
| 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 {
...
}
}
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 { ... } }
Hmm this doesn't seem to work, I get a not defined error
Outdated
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.
Outdated
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, ...
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.
Oh yes, I think we'll benefit from having more data to pipe into the LLM I'll remove this line
Outdated
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}), |
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.
probably want to wrap this in a try/catch and return the error response to the llm, like we do in other tools