[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
This commit is contained in:
Jorge Cabiedes 2025-04-30 15:42:00 -07:00 committed by GitHub
parent 71797c871b
commit d8074cbc79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 55 additions and 57 deletions

View File

@ -356,7 +356,14 @@ Server Components - Shift data-heavy logic to the server whenever possible. Brea
server.tool(
'review-react-runtime',
'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.',
`
Run this tool every time you propose a performance related change to verify if your suggestion actually improves performance.
This tool has some requirements on the code input:
- The react code that is passed into this tool MUST contain an App functional component without arrow function.
- DO NOT export anything since we can't parse export syntax with this tool.
- Only import React from 'react' and use all hooks and imports using the React. prefix like React.useState and React.useEffect
`,
{
text: z.string(),
},
@ -387,25 +394,24 @@ server.tool(
for (let i = 0; i < iterations; i++) {
const performanceResults = await measurePerformance(text);
perfData.renderTime += performanceResults.renderTime;
perfData.webVitals.cls += performanceResults.webVitals.cls?.value || 0;
perfData.webVitals.lcp += performanceResults.webVitals.lcp?.value || 0;
perfData.webVitals.inp += performanceResults.webVitals.inp?.value || 0;
perfData.webVitals.fid += performanceResults.webVitals.fid?.value || 0;
perfData.webVitals.ttfb +=
performanceResults.webVitals.ttfb?.value || 0;
perfData.webVitals.cls += performanceResults.webVitals.cls || 0;
perfData.webVitals.lcp += performanceResults.webVitals.lcp || 0;
perfData.webVitals.inp += performanceResults.webVitals.inp || 0;
perfData.webVitals.fid += performanceResults.webVitals.fid || 0;
perfData.webVitals.ttfb += performanceResults.webVitals.ttfb || 0;
perfData.reactProfilerMetrics.id +=
performanceResults.reactProfilerMetrics.actualDuration?.value || 0;
performanceResults.reactProfilerMetrics.actualDuration || 0;
perfData.reactProfilerMetrics.phase +=
performanceResults.reactProfilerMetrics.phase?.value || 0;
performanceResults.reactProfilerMetrics.phase || 0;
perfData.reactProfilerMetrics.actualDuration +=
performanceResults.reactProfilerMetrics.actualDuration?.value || 0;
performanceResults.reactProfilerMetrics.actualDuration || 0;
perfData.reactProfilerMetrics.baseDuration +=
performanceResults.reactProfilerMetrics.baseDuration?.value || 0;
performanceResults.reactProfilerMetrics.baseDuration || 0;
perfData.reactProfilerMetrics.startTime +=
performanceResults.reactProfilerMetrics.startTime?.value || 0;
performanceResults.reactProfilerMetrics.startTime || 0;
perfData.reactProfilerMetrics.commitTime +=
performanceResults.reactProfilerMetrics.commitTim?.value || 0;
performanceResults.reactProfilerMetrics.commitTime || 0;
}
const formattedResults = `

View File

@ -1,61 +1,32 @@
import * as babel from '@babel/core';
import puppeteer from 'puppeteer';
export async function measurePerformance(code: any) {
let options = {
export async function measurePerformance(code: string) {
const babelOptions = {
configFile: false,
babelrc: false,
presets: [['@babel/preset-env'], '@babel/preset-react'],
presets: [
require.resolve('@babel/preset-env'),
require.resolve('@babel/preset-react'),
],
};
const parsed = await babel.parseAsync(code, options);
// Parse the code to AST
const parsed = await babel.parseAsync(code, babelOptions);
if (!parsed) {
throw new Error('Failed to parse code');
}
const transpiled = await transformAsync(parsed);
if (!transpiled) {
throw new Error('Failed to transpile code');
}
const browser = await puppeteer.launch({
protocolTimeout: 600_000,
});
const page = await browser.newPage();
await page.setViewport({width: 1280, height: 720});
const html = buildHtml(transpiled);
await page.setContent(html, {waitUntil: 'networkidle0'});
await page.waitForFunction(
'window.__RESULT__ !== undefined && (window.__RESULT__.renderTime !== null || window.__RESULT__.error !== null)',
{timeout: 600_000},
);
const result = await page.evaluate(() => {
return (window as any).__RESULT__;
});
await browser.close();
return result;
}
/**
* Transform AST into browser-compatible JavaScript
* @param {babel.types.File} ast - The AST to transform
* @param {Object} opts - Transformation options
* @returns {Promise<string>} - The transpiled code
*/
async function transformAsync(ast: babel.types.Node) {
const result = await babel.transformFromAstAsync(ast, undefined, {
// Transform AST to browser-compatible JavaScript
const transformResult = await babel.transformFromAstAsync(parsed, undefined, {
...babelOptions,
filename: 'file.jsx',
presets: [['@babel/preset-env'], '@babel/preset-react'],
plugins: [
() => ({
visitor: {
ImportDeclaration(path: any) {
ImportDeclaration(
path: babel.NodePath<babel.types.ImportDeclaration>,
) {
const value = path.node.source.value;
if (value === 'react' || value === 'react-dom') {
path.remove();
@ -66,7 +37,28 @@ async function transformAsync(ast: babel.types.Node) {
],
});
return result?.code || '';
const transpiled = transformResult?.code || undefined;
if (!transpiled) {
throw new Error('Failed to transpile code');
}
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.setViewport({width: 1280, height: 720});
const html = buildHtml(transpiled);
await page.setContent(html, {waitUntil: 'networkidle0'});
await page.waitForFunction(
'window.__RESULT__ !== undefined && (window.__RESULT__.renderTime !== null || window.__RESULT__.error !== null)',
);
const result = await page.evaluate(() => {
return (window as any).__RESULT__;
});
await browser.close();
return result;
}
function buildHtml(transpiled: string) {