Skip to content

Commit dc2b118

Browse files
authored
[mcp] Refactor (facebook#33085)
Just some cleanup. Mainly, we now take the number of iterations as an argument. Everything else is just code movement and small tweaks.
1 parent b5450b0 commit dc2b118

File tree

2 files changed

+243
-214
lines changed

2 files changed

+243
-214
lines changed

compiler/packages/react-mcp-server/src/index.ts

Lines changed: 77 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,83 @@ server.tool(
275275
},
276276
);
277277

278+
server.tool(
279+
'review-react-runtime',
280+
`Run this tool every time you propose a performance related change to verify if your suggestion actually improves performance.
281+
<requirements>
282+
This tool has some requirements on the code input:
283+
- The react code that is passed into this tool MUST contain an App functional component without arrow function.
284+
- DO NOT export anything since we can't parse export syntax with this tool.
285+
- Only import React from 'react' and use all hooks and imports using the React. prefix like React.useState and React.useEffect
286+
</requirements>
287+
288+
<goals>
289+
- LCP - loading speed: good ≤ 2.5 s, needs-improvement 2.5-4 s, poor > 4 s
290+
- INP - input responsiveness: good ≤ 200 ms, needs-improvement 200-500 ms, poor > 500 ms
291+
- CLS - visual stability: good ≤ 0.10, needs-improvement 0.10-0.25, poor > 0.25
292+
- (Optional: FCP ≤ 1.8 s, TTFB ≤ 0.8 s)
293+
</goals>
294+
295+
<evaluation>
296+
Classify each metric with the thresholds above. Identify the worst category in the order poor > needs-improvement > good.
297+
</evaluation>
298+
299+
<iterate>
300+
(repeat until every metric is good or two consecutive cycles show no gain)
301+
- Apply one focused change based on the failing metric plus React-specific guidance:
302+
- 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).
303+
- INP: wrap non-critical updates in useTransition, avoid calling setState inside useEffect.
304+
- 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.
305+
306+
Stop when every metric is classified as good. Return the final metric table and the list of applied changes.
307+
</iterate>
308+
`,
309+
{
310+
text: z.string(),
311+
iterations: z.number().optional().default(2),
312+
},
313+
async ({text, iterations}) => {
314+
try {
315+
const results = await measurePerformance(text, iterations);
316+
const formattedResults = `
317+
# React Component Performance Results
318+
319+
## Mean Render Time
320+
${results.renderTime / iterations}ms
321+
322+
## Mean Web Vitals
323+
- Cumulative Layout Shift (CLS): ${results.webVitals.cls / iterations}ms
324+
- Largest Contentful Paint (LCP): ${results.webVitals.lcp / iterations}ms
325+
- Interaction to Next Paint (INP): ${results.webVitals.inp / iterations}ms
326+
- First Input Delay (FID): ${results.webVitals.fid / iterations}ms
327+
328+
## Mean React Profiler
329+
- Actual Duration: ${results.reactProfiler.actualDuration / iterations}ms
330+
- Base Duration: ${results.reactProfiler.baseDuration / iterations}ms
331+
`;
332+
333+
return {
334+
content: [
335+
{
336+
type: 'text' as const,
337+
text: formattedResults,
338+
},
339+
],
340+
};
341+
} catch (error) {
342+
return {
343+
isError: true,
344+
content: [
345+
{
346+
type: 'text' as const,
347+
text: `Error measuring performance: ${error.message}\n\n${error.stack}`,
348+
},
349+
],
350+
};
351+
}
352+
},
353+
);
354+
278355
server.prompt('review-react-code', () => ({
279356
messages: [
280357
{
@@ -354,129 +431,6 @@ Server Components - Shift data-heavy logic to the server whenever possible. Brea
354431
],
355432
}));
356433

357-
server.tool(
358-
'review-react-runtime',
359-
`Run this tool every time you propose a performance related change to verify if your suggestion actually improves performance.
360-
<requirements>
361-
This tool has some requirements on the code input:
362-
- The react code that is passed into this tool MUST contain an App functional component without arrow function.
363-
- DO NOT export anything since we can't parse export syntax with this tool.
364-
- Only import React from 'react' and use all hooks and imports using the React. prefix like React.useState and React.useEffect
365-
</requirements>
366-
367-
<goals>
368-
- LCP - loading speed: good ≤ 2.5 s, needs-improvement 2.5-4 s, poor > 4 s
369-
- INP - input responsiveness: good ≤ 200 ms, needs-improvement 200-500 ms, poor > 500 ms
370-
- CLS - visual stability: good ≤ 0.10, needs-improvement 0.10-0.25, poor > 0.25
371-
- (Optional: FCP ≤ 1.8 s, TTFB ≤ 0.8 s)
372-
</goals>
373-
374-
<evaluation>
375-
Classify each metric with the thresholds above. Identify the worst category in the order poor > needs-improvement > good.
376-
</evaluation>
377-
378-
<iterate>
379-
(repeat until every metric is good or two consecutive cycles show no gain)
380-
- Apply one focused change based on the failing metric plus React-specific guidance:
381-
- 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).
382-
- INP: wrap non-critical updates in useTransition, avoid calling setState inside useEffect.
383-
- 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.
384-
385-
Stop when every metric is classified as good. Return the final metric table and the list of applied changes.
386-
</iterate>
387-
`,
388-
{
389-
text: z.string(),
390-
},
391-
async ({text}) => {
392-
try {
393-
const iterations = 20;
394-
395-
let perfData = {
396-
renderTime: 0,
397-
webVitals: {
398-
cls: 0,
399-
lcp: 0,
400-
inp: 0,
401-
fid: 0,
402-
ttfb: 0,
403-
},
404-
reactProfilerMetrics: {
405-
id: 0,
406-
phase: 0,
407-
actualDuration: 0,
408-
baseDuration: 0,
409-
startTime: 0,
410-
commitTime: 0,
411-
},
412-
error: null,
413-
};
414-
415-
for (let i = 0; i < iterations; i++) {
416-
const performanceResults = await measurePerformance(text);
417-
perfData.renderTime += performanceResults.renderTime;
418-
perfData.webVitals.cls += performanceResults.webVitals.cls || 0;
419-
perfData.webVitals.lcp += performanceResults.webVitals.lcp || 0;
420-
perfData.webVitals.inp += performanceResults.webVitals.inp || 0;
421-
perfData.webVitals.fid += performanceResults.webVitals.fid || 0;
422-
perfData.webVitals.ttfb += performanceResults.webVitals.ttfb || 0;
423-
424-
perfData.reactProfilerMetrics.id +=
425-
performanceResults.reactProfilerMetrics.actualDuration || 0;
426-
perfData.reactProfilerMetrics.phase +=
427-
performanceResults.reactProfilerMetrics.phase || 0;
428-
perfData.reactProfilerMetrics.actualDuration +=
429-
performanceResults.reactProfilerMetrics.actualDuration || 0;
430-
perfData.reactProfilerMetrics.baseDuration +=
431-
performanceResults.reactProfilerMetrics.baseDuration || 0;
432-
perfData.reactProfilerMetrics.startTime +=
433-
performanceResults.reactProfilerMetrics.startTime || 0;
434-
perfData.reactProfilerMetrics.commitTime +=
435-
performanceResults.reactProfilerMetrics.commitTime || 0;
436-
}
437-
438-
const formattedResults = `
439-
# React Component Performance Results
440-
441-
## Mean Render Time
442-
${perfData.renderTime / iterations}ms
443-
444-
## Mean Web Vitals
445-
- Cumulative Layout Shift (CLS): ${perfData.webVitals.cls / iterations}
446-
- Largest Contentful Paint (LCP): ${perfData.webVitals.lcp / iterations}ms
447-
- Interaction to Next Paint (INP): ${perfData.webVitals.inp / iterations}ms
448-
- First Input Delay (FID): ${perfData.webVitals.fid / iterations}ms
449-
- Time to First Byte (TTFB): ${perfData.webVitals.ttfb / iterations}ms
450-
451-
## Mean React Profiler
452-
- Actual Duration: ${perfData.reactProfilerMetrics.actualDuration / iterations}ms
453-
- Base Duration: ${perfData.reactProfilerMetrics.baseDuration / iterations}ms
454-
- Start Time: ${perfData.reactProfilerMetrics.startTime / iterations}ms
455-
- Commit Time: ${perfData.reactProfilerMetrics.commitTime / iterations}ms
456-
`;
457-
458-
return {
459-
content: [
460-
{
461-
type: 'text' as const,
462-
text: formattedResults,
463-
},
464-
],
465-
};
466-
} catch (error) {
467-
return {
468-
isError: true,
469-
content: [
470-
{
471-
type: 'text' as const,
472-
text: `Error measuring performance: ${error.message}\n\n${error.stack}`,
473-
},
474-
],
475-
};
476-
}
477-
},
478-
);
479-
480434
async function main() {
481435
const transport = new StdioServerTransport();
482436
await server.connect(transport);

0 commit comments

Comments
 (0)