diff --git a/compiler/packages/react-mcp-server/src/index.ts b/compiler/packages/react-mcp-server/src/index.ts
index 7cb943ae836d8..e250770c15125 100644
--- a/compiler/packages/react-mcp-server/src/index.ts
+++ b/compiler/packages/react-mcp-server/src/index.ts
@@ -275,6 +275,83 @@ server.tool(
},
);
+server.tool(
+ 'review-react-runtime',
+ `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
+
+
+
+ - LCP - loading speed: good ≤ 2.5 s, needs-improvement 2.5-4 s, poor > 4 s
+ - INP - input responsiveness: good ≤ 200 ms, needs-improvement 200-500 ms, poor > 500 ms
+ - CLS - visual stability: good ≤ 0.10, needs-improvement 0.10-0.25, poor > 0.25
+ - (Optional: FCP ≤ 1.8 s, TTFB ≤ 0.8 s)
+
+
+
+ Classify each metric with the thresholds above. Identify the worst category in the order poor > needs-improvement > good.
+
+
+
+ (repeat until every metric is good or two consecutive cycles show no gain)
+ - Apply one focused change based on the failing metric plus React-specific guidance:
+ - LCP: lazy-load off-screen images, inline critical CSS, preconnect, use React.lazy + Suspense for below-the-fold modules. if the user requests for it, use React Server Components for static content (Server Components).
+ - INP: wrap non-critical updates in useTransition, avoid calling setState inside useEffect.
+ - CLS: reserve space via explicit width/height or aspect-ratio, keep stable list keys, use fixed-size skeleton loaders, animate only transform/opacity, avoid inserting ads or banners without placeholders.
+
+ Stop when every metric is classified as good. Return the final metric table and the list of applied changes.
+
+ `,
+ {
+ text: z.string(),
+ iterations: z.number().optional().default(2),
+ },
+ async ({text, iterations}) => {
+ try {
+ const results = await measurePerformance(text, iterations);
+ const formattedResults = `
+# React Component Performance Results
+
+## Mean Render Time
+${results.renderTime / iterations}ms
+
+## Mean Web Vitals
+- Cumulative Layout Shift (CLS): ${results.webVitals.cls / iterations}ms
+- Largest Contentful Paint (LCP): ${results.webVitals.lcp / iterations}ms
+- Interaction to Next Paint (INP): ${results.webVitals.inp / iterations}ms
+- First Input Delay (FID): ${results.webVitals.fid / iterations}ms
+
+## Mean React Profiler
+- Actual Duration: ${results.reactProfiler.actualDuration / iterations}ms
+- Base Duration: ${results.reactProfiler.baseDuration / iterations}ms
+`;
+
+ return {
+ content: [
+ {
+ type: 'text' as const,
+ text: formattedResults,
+ },
+ ],
+ };
+ } catch (error) {
+ return {
+ isError: true,
+ content: [
+ {
+ type: 'text' as const,
+ text: `Error measuring performance: ${error.message}\n\n${error.stack}`,
+ },
+ ],
+ };
+ }
+ },
+);
+
server.prompt('review-react-code', () => ({
messages: [
{
@@ -354,129 +431,6 @@ Server Components - Shift data-heavy logic to the server whenever possible. Brea
],
}));
-server.tool(
- 'review-react-runtime',
- `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
-
-
-
- - LCP - loading speed: good ≤ 2.5 s, needs-improvement 2.5-4 s, poor > 4 s
- - INP - input responsiveness: good ≤ 200 ms, needs-improvement 200-500 ms, poor > 500 ms
- - CLS - visual stability: good ≤ 0.10, needs-improvement 0.10-0.25, poor > 0.25
- - (Optional: FCP ≤ 1.8 s, TTFB ≤ 0.8 s)
-
-
-
- Classify each metric with the thresholds above. Identify the worst category in the order poor > needs-improvement > good.
-
-
-
- (repeat until every metric is good or two consecutive cycles show no gain)
- - Apply one focused change based on the failing metric plus React-specific guidance:
- - LCP: lazy-load off-screen images, inline critical CSS, preconnect, use React.lazy + Suspense for below-the-fold modules. if the user requests for it, use React Server Components for static content (Server Components).
- - INP: wrap non-critical updates in useTransition, avoid calling setState inside useEffect.
- - CLS: reserve space via explicit width/height or aspect-ratio, keep stable list keys, use fixed-size skeleton loaders, animate only transform/opacity, avoid inserting ads or banners without placeholders.
-
- Stop when every metric is classified as good. Return the final metric table and the list of applied changes.
-
- `,
- {
- text: z.string(),
- },
- async ({text}) => {
- try {
- const iterations = 20;
-
- let perfData = {
- renderTime: 0,
- webVitals: {
- cls: 0,
- lcp: 0,
- inp: 0,
- fid: 0,
- ttfb: 0,
- },
- reactProfilerMetrics: {
- id: 0,
- phase: 0,
- actualDuration: 0,
- baseDuration: 0,
- startTime: 0,
- commitTime: 0,
- },
- error: null,
- };
-
- for (let i = 0; i < iterations; i++) {
- const performanceResults = await measurePerformance(text);
- perfData.renderTime += performanceResults.renderTime;
- 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 || 0;
- perfData.reactProfilerMetrics.phase +=
- performanceResults.reactProfilerMetrics.phase || 0;
- perfData.reactProfilerMetrics.actualDuration +=
- performanceResults.reactProfilerMetrics.actualDuration || 0;
- perfData.reactProfilerMetrics.baseDuration +=
- performanceResults.reactProfilerMetrics.baseDuration || 0;
- perfData.reactProfilerMetrics.startTime +=
- performanceResults.reactProfilerMetrics.startTime || 0;
- perfData.reactProfilerMetrics.commitTime +=
- performanceResults.reactProfilerMetrics.commitTime || 0;
- }
-
- const formattedResults = `
-# React Component Performance Results
-
-## Mean Render Time
-${perfData.renderTime / iterations}ms
-
-## Mean Web Vitals
-- Cumulative Layout Shift (CLS): ${perfData.webVitals.cls / iterations}
-- Largest Contentful Paint (LCP): ${perfData.webVitals.lcp / iterations}ms
-- Interaction to Next Paint (INP): ${perfData.webVitals.inp / iterations}ms
-- First Input Delay (FID): ${perfData.webVitals.fid / iterations}ms
-- Time to First Byte (TTFB): ${perfData.webVitals.ttfb / iterations}ms
-
-## Mean React Profiler
-- Actual Duration: ${perfData.reactProfilerMetrics.actualDuration / iterations}ms
-- Base Duration: ${perfData.reactProfilerMetrics.baseDuration / iterations}ms
-- Start Time: ${perfData.reactProfilerMetrics.startTime / iterations}ms
-- Commit Time: ${perfData.reactProfilerMetrics.commitTime / iterations}ms
-`;
-
- return {
- content: [
- {
- type: 'text' as const,
- text: formattedResults,
- },
- ],
- };
- } catch (error) {
- return {
- isError: true,
- content: [
- {
- type: 'text' as const,
- text: `Error measuring performance: ${error.message}\n\n${error.stack}`,
- },
- ],
- };
- }
- },
-);
-
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
diff --git a/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts b/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts
index 73044a09e9ccc..bc46f496ec463 100644
--- a/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts
+++ b/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts
@@ -1,8 +1,32 @@
import * as babel from '@babel/core';
import puppeteer from 'puppeteer';
-export async function measurePerformance(code: string) {
+type PerformanceResults = {
+ renderTime: number;
+ webVitals: {
+ cls: number;
+ lcp: number;
+ inp: number;
+ fid: number;
+ ttfb: number;
+ };
+ reactProfiler: {
+ id: number;
+ phase: number;
+ actualDuration: number;
+ baseDuration: number;
+ startTime: number;
+ commitTime: number;
+ };
+ error: Error | null;
+};
+
+export async function measurePerformance(
+ code: string,
+ iterations: number,
+): Promise {
const babelOptions = {
+ filename: 'anonymous.tsx',
configFile: false,
babelrc: false,
presets: [
@@ -12,16 +36,13 @@ export async function measurePerformance(code: string) {
],
};
- // Parse the code to AST
const parsed = await babel.parseAsync(code, babelOptions);
if (!parsed) {
throw new Error('Failed to parse code');
}
- // Transform AST to browser-compatible JavaScript
const transformResult = await babel.transformFromAstAsync(parsed, undefined, {
...babelOptions,
- filename: 'file.jsx',
plugins: [
() => ({
visitor: {
@@ -44,104 +65,158 @@ export async function measurePerformance(code: string) {
}
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)',
- );
+ let performanceResults: PerformanceResults = {
+ renderTime: 0,
+ webVitals: {
+ cls: 0,
+ lcp: 0,
+ inp: 0,
+ fid: 0,
+ ttfb: 0,
+ },
+ reactProfiler: {
+ id: 0,
+ phase: 0,
+ actualDuration: 0,
+ baseDuration: 0,
+ startTime: 0,
+ commitTime: 0,
+ },
+ error: null,
+ };
- const result = await page.evaluate(() => {
- return (window as any).__RESULT__;
- });
+ for (let ii = 0; ii < iterations; ii++) {
+ await page.setContent(html, {waitUntil: 'networkidle0'});
+ await page.waitForFunction(
+ 'window.__RESULT__ !== undefined && (window.__RESULT__.renderTime !== null || window.__RESULT__.error !== null)',
+ );
+ // ui chaos monkey
+ await page.waitForFunction(`window.__RESULT__ !== undefined && (function() {
+ for (const el of [...document.querySelectorAll('a'), ...document.querySelectorAll('button')]) {
+ console.log(el);
+ el.click();
+ }
+ return true;
+ })() `);
+ const evaluationResult: PerformanceResults = await page.evaluate(() => {
+ return (window as any).__RESULT__;
+ });
+
+ // TODO: investigate why webvital metrics are not populating correctly
+ performanceResults.renderTime += evaluationResult.renderTime;
+ performanceResults.webVitals.cls += evaluationResult.webVitals.cls || 0;
+ performanceResults.webVitals.lcp += evaluationResult.webVitals.lcp || 0;
+ performanceResults.webVitals.inp += evaluationResult.webVitals.inp || 0;
+ performanceResults.webVitals.fid += evaluationResult.webVitals.fid || 0;
+ performanceResults.webVitals.ttfb += evaluationResult.webVitals.ttfb || 0;
+
+ performanceResults.reactProfiler.id +=
+ evaluationResult.reactProfiler.actualDuration || 0;
+ performanceResults.reactProfiler.phase +=
+ evaluationResult.reactProfiler.phase || 0;
+ performanceResults.reactProfiler.actualDuration +=
+ evaluationResult.reactProfiler.actualDuration || 0;
+ performanceResults.reactProfiler.baseDuration +=
+ evaluationResult.reactProfiler.baseDuration || 0;
+ performanceResults.reactProfiler.startTime +=
+ evaluationResult.reactProfiler.startTime || 0;
+ performanceResults.reactProfiler.commitTime +=
+ evaluationResult.reactProfiler.commitTime || 0;
+
+ performanceResults.error = evaluationResult.error;
+ }
await browser.close();
- return result;
+
+ return performanceResults;
}
function buildHtml(transpiled: string) {
const html = `
-
-
-
-
- React Performance Test
-
-
-
-
-
-
-
-
-
-
-
- `;
+
+
+
+
+ React Performance Test
+
+
+
+
+
+
+
+
+
+
+
+`;
return html;
}