@samitouri / QOS-React-1 / commits / d8074cbc79

[mcp] Make tool more reliable and fix integration issues with babel (#33074)

## Summary Fix babel presets, and add a bit more context to the tool so that it is more reliable ## How did you test this change? Manually tested the mcp integrated with claude desktop

Jorge Cabiedes committed Apr 30, 2025 at 15:42 UTC d8074cbc798a500a5e76d19fe0c17edec22b2593
2 files changed +48 -50
compiler/packages/react-mcp-server/src/index.ts
+19 -13
@@ -356,7 +356,14 @@ Server Components - Shift data-heavy logic to the server whenever possible. Brea
356
357 server.tool(
358 'review-react-runtime',
359 - 'Review the runtime of the code and get performance data to evaluate the proposed solution, the react code that is passed into this tool MUST contain an App component.',
359 + `
360 + Run this tool every time you propose a performance related change to verify if your suggestion actually improves performance.
361 +
362 + This tool has some requirements on the code input:
363 + - The react code that is passed into this tool MUST contain an App functional component without arrow function.
364 + - DO NOT export anything since we can't parse export syntax with this tool.
365 + - Only import React from 'react' and use all hooks and imports using the React. prefix like React.useState and React.useEffect
366 + `,
367 {
368 text: z.string(),
369 },
@@ -387,25 +394,24 @@ server.tool(
394 for (let i = 0; i < iterations; i++) {
395 const performanceResults = await measurePerformance(text);
396 perfData.renderTime += performanceResults.renderTime;
390 - perfData.webVitals.cls += performanceResults.webVitals.cls?.value || 0;
391 - perfData.webVitals.lcp += performanceResults.webVitals.lcp?.value || 0;
392 - perfData.webVitals.inp += performanceResults.webVitals.inp?.value || 0;
393 - perfData.webVitals.fid += performanceResults.webVitals.fid?.value || 0;
394 - perfData.webVitals.ttfb +=
395 - performanceResults.webVitals.ttfb?.value || 0;
397 + perfData.webVitals.cls += performanceResults.webVitals.cls || 0;
398 + perfData.webVitals.lcp += performanceResults.webVitals.lcp || 0;
399 + perfData.webVitals.inp += performanceResults.webVitals.inp || 0;
400 + perfData.webVitals.fid += performanceResults.webVitals.fid || 0;
401 + perfData.webVitals.ttfb += performanceResults.webVitals.ttfb || 0;
402
403 perfData.reactProfilerMetrics.id +=
398 - performanceResults.reactProfilerMetrics.actualDuration?.value || 0;
404 + performanceResults.reactProfilerMetrics.actualDuration || 0;
405 perfData.reactProfilerMetrics.phase +=
400 - performanceResults.reactProfilerMetrics.phase?.value || 0;
406 + performanceResults.reactProfilerMetrics.phase || 0;
407 perfData.reactProfilerMetrics.actualDuration +=
402 - performanceResults.reactProfilerMetrics.actualDuration?.value || 0;
408 + performanceResults.reactProfilerMetrics.actualDuration || 0;
409 perfData.reactProfilerMetrics.baseDuration +=
404 - performanceResults.reactProfilerMetrics.baseDuration?.value || 0;
410 + performanceResults.reactProfilerMetrics.baseDuration || 0;
411 perfData.reactProfilerMetrics.startTime +=
406 - performanceResults.reactProfilerMetrics.startTime?.value || 0;
412 + performanceResults.reactProfilerMetrics.startTime || 0;
413 perfData.reactProfilerMetrics.commitTime +=
408 - performanceResults.reactProfilerMetrics.commitTim?.value || 0;
414 + performanceResults.reactProfilerMetrics.commitTime || 0;
415 }
416
417 const formattedResults = `
compiler/packages/react-mcp-server/src/utils/runtimePerf.ts
+29 -37
@@ -1,28 +1,48 @@
1 import * as babel from '@babel/core';
2 import puppeteer from 'puppeteer';
3
4 -export async function measurePerformance(code: any) {
5 - let options = {
4 +export async function measurePerformance(code: string) {
5 + const babelOptions = {
6 configFile: false,
7 babelrc: false,
8 - presets: [['@babel/preset-env'], '@babel/preset-react'],
8 + presets: [
9 + require.resolve('@babel/preset-env'),
10 + require.resolve('@babel/preset-react'),
11 + ],
12 };
13
11 - const parsed = await babel.parseAsync(code, options);
12 -
14 + // Parse the code to AST
15 + const parsed = await babel.parseAsync(code, babelOptions);
16 if (!parsed) {
17 throw new Error('Failed to parse code');
18 }
19
17 - const transpiled = await transformAsync(parsed);
20 + // Transform AST to browser-compatible JavaScript
21 + const transformResult = await babel.transformFromAstAsync(parsed, undefined, {
22 + ...babelOptions,
23 + filename: 'file.jsx',
24 + plugins: [
25 + () => ({
26 + visitor: {
27 + ImportDeclaration(
28 + path: babel.NodePath<babel.types.ImportDeclaration>,
29 + ) {
30 + const value = path.node.source.value;
31 + if (value === 'react' || value === 'react-dom') {
32 + path.remove();
33 + }
34 + },
35 + },
36 + }),
37 + ],
38 + });
39
40 + const transpiled = transformResult?.code || undefined;
41 if (!transpiled) {
42 throw new Error('Failed to transpile code');
43 }
44
23 - const browser = await puppeteer.launch({
24 - protocolTimeout: 600_000,
25 - });
45 + const browser = await puppeteer.launch();
46
47 const page = await browser.newPage();
48 await page.setViewport({width: 1280, height: 720});
@@ -31,7 +51,6 @@ export async function measurePerformance(code: any) {
51
52 await page.waitForFunction(
53 'window.__RESULT__ !== undefined && (window.__RESULT__.renderTime !== null || window.__RESULT__.error !== null)',
34 - {timeout: 600_000},
54 );
55
56 const result = await page.evaluate(() => {
@@ -42,33 +61,6 @@ export async function measurePerformance(code: any) {
61 return result;
62 }
63
45 -/**
46 - * Transform AST into browser-compatible JavaScript
47 - * @param {babel.types.File} ast - The AST to transform
48 - * @param {Object} opts - Transformation options
49 - * @returns {Promise<string>} - The transpiled code
50 - */
51 -async function transformAsync(ast: babel.types.Node) {
52 - const result = await babel.transformFromAstAsync(ast, undefined, {
53 - filename: 'file.jsx',
54 - presets: [['@babel/preset-env'], '@babel/preset-react'],
55 - plugins: [
56 - () => ({
57 - visitor: {
58 - ImportDeclaration(path: any) {
59 - const value = path.node.source.value;
60 - if (value === 'react' || value === 'react-dom') {
61 - path.remove();
62 - }
63 - },
64 - },
65 - }),
66 - ],
67 - });
68 -
69 - return result?.code || '';
70 -}
71 -
64 function buildHtml(transpiled: string) {
65 const html = `
66 <!DOCTYPE html>