compiler/packages/react-mcp-server/src/index.ts TYPESCRIPT 498 lines View on github.com → Search inside
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 */78import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';9import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js';10import {z} from 'zod/v4';11import {compile, type PrintedCompilerPipelineValue} from './compiler';12import {13  CompilerPipelineValue,14  printReactiveFunctionWithOutlined,15  printFunctionWithOutlined,16  PluginOptions,17  SourceLocation,18} from 'babel-plugin-react-compiler/src';19import * as cheerio from 'cheerio';20import {queryAlgolia} from './utils/algolia';21import assertExhaustive from './utils/assertExhaustive';22import {convert} from 'html-to-text';23import {measurePerformance} from './tools/runtimePerf';24import {parseReactComponentTree} from './tools/componentTree';2526function calculateMean(values: number[]): string {27  return values.length > 028    ? values.reduce((acc, curr) => acc + curr, 0) / values.length + 'ms'29    : 'could not collect';30}3132const server = new McpServer({33  name: 'React',34  version: '0.0.0',35});3637server.tool(38  'query-react-dev-docs',39  'This tool lets you search for official docs from react.dev. This always has the most up to date information on React. You can look for documentation on APIs such as <ViewTransition>, <Activity>, and hooks like useOptimistic, useSyncExternalStore, useTransition, and more. Whenever you think hard about React, use this tool to get more information before proceeding.',40  {41    query: z.string(),42  },43  async ({query}) => {44    try {45      const pages = await queryAlgolia(query);46      if (pages.length === 0) {47        return {48          content: [{type: 'text' as const, text: `No results`}],49        };50      }51      const content = pages.map(html => {52        const $ = cheerio.load(html);53        // react.dev should always have at least one <article> with the main content54        const article = $('article').html();55        if (article != null) {56          return {57            type: 'text' as const,58            text: convert(article),59          };60        } else {61          return {62            type: 'text' as const,63            // Fallback to converting the whole page to text.64            text: convert($.html()),65          };66        }67      });68      return {69        content,70      };71    } catch (err) {72      return {73        isError: true,74        content: [{type: 'text' as const, text: `Error: ${err.stack}`}],75      };76    }77  },78);7980server.tool(81  'compile',82  `Compile code with React Compiler. This tool will return the compiled output, which is automatically memoized React components and hooks, written in JavaScript or TypeScript. You can run this tool whenever you want to check if some React code will compile successfully. You can also run this tool every time you make a suggestion to code, to see how it affects the compiled output. If the compiler returns a diagnostic message, you should read the diagnostic message and try to fix the code and run the compiler again to verify. After compiling code successfully, you should run it through the review-react-runtime to verify the compiled code is faster than the original.8384  <bailouts>85  When you encounter a bailout or diagnostic message, first think and try to understand where the error is coming from. You can use tools such as puppeteer if available to browse the documentation links provided in the diagnostic, and then read that information to understand the error in more detail. You can propose fixes after doing so.8687  This is a non-exhaustive list of bailouts where you should take specific actions:88  - React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved: fix this by first removing the manual memo (useMemo/useCallback) and then try compiling again. use the review-react-runtime tool to verify that the compiled code can run without crashing. if it crashes, the original code likely breaks the Rules of React and so cannot be safely compiled.89  </bailouts>90  `,91  {92    text: z.string(),93    passName: z.enum(['HIR', 'ReactiveFunction', 'All', '@DEBUG']).optional(),94  },95  async ({text, passName}) => {96    const pipelinePasses = new Map<97      string,98      Array<PrintedCompilerPipelineValue>99    >();100    const recordPass: (101      result: PrintedCompilerPipelineValue,102    ) => void = result => {103      const entry = pipelinePasses.get(result.name);104      if (Array.isArray(entry)) {105        entry.push(result);106      } else {107        pipelinePasses.set(result.name, [result]);108      }109    };110    const logIR = (result: CompilerPipelineValue): void => {111      switch (result.kind) {112        case 'ast': {113          break;114        }115        case 'hir': {116          recordPass({117            kind: 'hir',118            fnName: result.value.id,119            name: result.name,120            value: printFunctionWithOutlined(result.value),121          });122          break;123        }124        case 'reactive': {125          recordPass({126            kind: 'reactive',127            fnName: result.value.id,128            name: result.name,129            value: printReactiveFunctionWithOutlined(result.value),130          });131          break;132        }133        case 'debug': {134          recordPass({135            kind: 'debug',136            fnName: null,137            name: result.name,138            value: result.value,139          });140          break;141        }142        default: {143          assertExhaustive(result, `Unhandled result ${result}`);144        }145      }146    };147    const errors: Array<{message: string; loc: SourceLocation | null}> = [];148    const compilerOptions: PluginOptions = {149      panicThreshold: 'none',150      logger: {151        debugLogIRs: logIR,152        logEvent: (_filename, event): void => {153          if (event.kind === 'CompileError') {154            const detail = event.detail;155            const loc =156              detail.loc == null || typeof detail.loc == 'symbol'157                ? event.fnLoc158                : detail.loc;159            errors.push({160              message: detail.reason,161              loc,162            });163          }164        },165      },166    };167    try {168      const result = await compile({169        text,170        file: 'anonymous.tsx',171        options: compilerOptions,172      });173      if (result.code == null) {174        return {175          isError: true,176          content: [{type: 'text' as const, text: 'Error: Could not compile'}],177        };178      }179      const requestedPasses: Array<{type: 'text'; text: string}> = [];180      if (passName != null) {181        switch (passName) {182          case 'All': {183            const hir = pipelinePasses.get('PropagateScopeDependenciesHIR');184            if (hir !== undefined) {185              for (const pipelineValue of hir) {186                requestedPasses.push({187                  type: 'text' as const,188                  text: pipelineValue.value,189                });190              }191            }192            const reactiveFunc = pipelinePasses.get('PruneHoistedContexts');193            if (reactiveFunc !== undefined) {194              for (const pipelineValue of reactiveFunc) {195                requestedPasses.push({196                  type: 'text' as const,197                  text: pipelineValue.value,198                });199              }200            }201            break;202          }203          case 'HIR': {204            // Last pass before HIR -> ReactiveFunction205            const requestedPass = pipelinePasses.get(206              'PropagateScopeDependenciesHIR',207            );208            if (requestedPass !== undefined) {209              for (const pipelineValue of requestedPass) {210                requestedPasses.push({211                  type: 'text' as const,212                  text: pipelineValue.value,213                });214              }215            } else {216              console.error(`Could not find requested pass ${passName}`);217            }218            break;219          }220          case 'ReactiveFunction': {221            // Last pass222            const requestedPass = pipelinePasses.get('PruneHoistedContexts');223            if (requestedPass !== undefined) {224              for (const pipelineValue of requestedPass) {225                requestedPasses.push({226                  type: 'text' as const,227                  text: pipelineValue.value,228                });229              }230            } else {231              console.error(`Could not find requested pass ${passName}`);232            }233            break;234          }235          case '@DEBUG': {236            for (const [, pipelinePass] of pipelinePasses) {237              for (const pass of pipelinePass) {238                requestedPasses.push({239                  type: 'text' as const,240                  text: `${pass.name}\n\n${pass.value}`,241                });242              }243            }244            break;245          }246          default: {247            assertExhaustive(248              passName,249              `Unhandled passName option: ${passName}`,250            );251          }252        }253        const requestedPass = pipelinePasses.get(passName);254        if (requestedPass !== undefined) {255          for (const pipelineValue of requestedPass) {256            if (pipelineValue.name === passName) {257              requestedPasses.push({258                type: 'text' as const,259                text: pipelineValue.value,260              });261            }262          }263        }264      }265      if (errors.length > 0) {266        return {267          content: errors.map(err => {268            return {269              type: 'text' as const,270              text:271                err.loc === null || typeof err.loc === 'symbol'272                  ? `React Compiler bailed out:\n\n${err.message}`273                  : `React Compiler bailed out:\n\n${err.message}@${err.loc.start.line}:${err.loc.end.line}`,274            };275          }),276        };277      }278      return {279        content: [280          {type: 'text' as const, text: result.code},281          ...requestedPasses,282        ],283      };284    } catch (err) {285      return {286        isError: true,287        content: [{type: 'text' as const, text: `Error: ${err.stack}`}],288      };289    }290  },291);292293server.tool(294  'review-react-runtime',295  `Run this tool every time you propose a performance related change to verify if your suggestion actually improves performance.296297  <requirements>298  This tool has some requirements on the code input:299  - The react code that is passed into this tool MUST contain an App functional component without arrow function.300  - DO NOT export anything since we can't parse export syntax with this tool.301  - Only import React from 'react' and use all hooks and imports using the React. prefix like React.useState and React.useEffect302  </requirements>303304  <goals>305  - LCP - loading speed: good  2.5 s, needs-improvement 2.5-4 s, poor > 4 s306  - INP - input responsiveness: good  200 ms, needs-improvement 200-500 ms, poor > 500 ms307  - CLS - visual stability: good  0.10, needs-improvement 0.10-0.25, poor > 0.25308  - (Optional: FCP  1.8 s, TTFB  0.8 s)309  </goals>310311  <evaluation>312  Classify each metric with the thresholds above. Identify the worst category in the order poor > needs-improvement > good.313  </evaluation>314315  <iterate>316  (repeat until every metric is good or two consecutive cycles show no gain)317  - Always run the tool once on the original code before any modification318  - Run the tool again after making the modification, and apply one focused change based on the failing metric plus React-specific guidance:319    - 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).320    - INP: wrap non-critical updates in useTransition, avoid calling setState inside useEffect.321    - 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.322  - Compare the results of your modified code compared to the original to verify that your changes have improved performance.323  </iterate>324  `,325  {326    text: z.string(),327    iterations: z.number().optional().default(2),328  },329  async ({text, iterations}) => {330    try {331      const results = await measurePerformance(text, iterations);332      const formattedResults = `333# React Component Performance Results334335## Mean Render Time336${calculateMean(results.renderTime)}337338## Mean Web Vitals339- Cumulative Layout Shift (CLS): ${calculateMean(results.webVitals.cls)}340- Largest Contentful Paint (LCP): ${calculateMean(results.webVitals.lcp)}341- Interaction to Next Paint (INP): ${calculateMean(results.webVitals.inp)}342343## Mean React Profiler344- Actual Duration: ${calculateMean(results.reactProfiler.actualDuration)}345- Base Duration: ${calculateMean(results.reactProfiler.baseDuration)}346`;347348      return {349        content: [350          {351            type: 'text' as const,352            text: formattedResults,353          },354        ],355      };356    } catch (error) {357      return {358        isError: true,359        content: [360          {361            type: 'text' as const,362            text: `Error measuring performance: ${error.message}\n\n${error.stack}`,363          },364        ],365      };366    }367  },368);369370server.tool(371  'parse-react-component-tree',372  `373  This tool gets the component tree of a React App.374  passing in a url will attempt to connect to the browser and get the current state of the component tree. If no url is passed in,375  the default url will be used (http://localhost:3000).376377  <requirements>378  - The url should be a full url with the protocol (http:// or https://) and the domain name (e.g. localhost:3000).379  - Also the user should be running a Chrome browser running on debug mode on port 9222. If you receive an error message, advise the user to run380  the following comand in the terminal:381  MacOS: "/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --user-data-dir=/tmp/chrome"382  Windows: "chrome.exe --remote-debugging-port=9222 --user-data-dir=C:\temp\chrome"383  </requirements>384  `,385  {386    url: z.string().optional().default('http://localhost:3000'),387  },388  async ({url}) => {389    try {390      const componentTree = await parseReactComponentTree(url);391392      return {393        content: [394          {395            type: 'text' as const,396            text: componentTree,397          },398        ],399      };400    } catch (err) {401      return {402        isError: true,403        content: [{type: 'text' as const, text: `Error: ${err.stack}`}],404      };405    }406  },407);408409server.prompt('review-react-code', () => ({410  messages: [411    {412      role: 'assistant',413      content: {414        type: 'text',415        text: `416## Role417You are a React assistant that helps users write more efficient and optimizable React code. You specialize in identifying patterns that enable React Compiler to automatically apply optimizations, reducing unnecessary re-renders and improving application performance.418419## Follow these guidelines in all code you produce and suggest420Use functional components with Hooks: Do not generate class components or use old lifecycle methods. Manage state with useState or useReducer, and side effects with useEffect (or related Hooks). Always prefer functions and Hooks for any new component logic.421422Keep components pure and side-effect-free during rendering: Do not produce code that performs side effects (like subscriptions, network requests, or modifying external variables) directly inside the component's function body. Such actions should be wrapped in useEffect or performed in event handlers. Ensure your render logic is a pure function of props and state.423424Respect one-way data flow: Pass data down through props and avoid any global mutations. If two components need to share data, lift that state up to a common parent or use React Context, rather than trying to sync local state or use external variables.425426Never mutate state directly: Always generate code that updates state immutably. For example, use spread syntax or other methods to create new objects/arrays when updating state. Do not use assignments like state.someValue = ... or array mutations like array.push() on state variables. Use the state setter (setState from useState, etc.) to update state.427428Accurately use useEffect and other effect Hooks: whenever you think you could useEffect, think and reason harder to avoid it. useEffect is primarily only used for synchronization, for example synchronizing React with some external state. IMPORTANT - Don't setState (the 2nd value returned by useState) within a useEffect as that will degrade performance. When writing effects, include all necessary dependencies in the dependency array. Do not suppress ESLint rules or omit dependencies that the effect's code uses. Structure the effect callbacks to handle changing values properly (e.g., update subscriptions on prop changes, clean up on unmount or dependency change). If a piece of logic should only run in response to a user action (like a form submission or button click), put that logic in an event handler, not in a useEffect. Where possible, useEffects should return a cleanup function.429430Follow the Rules of Hooks: Ensure that any Hooks (useState, useEffect, useContext, custom Hooks, etc.) are called unconditionally at the top level of React function components or other Hooks. Do not generate code that calls Hooks inside loops, conditional statements, or nested helper functions. Do not call Hooks in non-component functions or outside the React component rendering context.431432Use refs only when necessary: Avoid using useRef unless the task genuinely requires it (such as focusing a control, managing an animation, or integrating with a non-React library). Do not use refs to store application state that should be reactive. If you do use refs, never write to or read from ref.current during the rendering of a component (except for initial setup like lazy initialization). Any ref usage should not affect the rendered output directly.433434Prefer composition and small components: Break down UI into small, reusable components rather than writing large monolithic components. The code you generate should promote clarity and reusability by composing components together. Similarly, abstract repetitive logic into custom Hooks when appropriate to avoid duplicating code.435436Optimize for concurrency: Assume React may render your components multiple times for scheduling purposes (especially in development with Strict Mode). Write code that remains correct even if the component function runs more than once. For instance, avoid side effects in the component body and use functional state updates (e.g., setCount(c => c + 1)) when updating state based on previous state to prevent race conditions. Always include cleanup functions in effects that subscribe to external resources. Don't write useEffects for "do this when this changes" side-effects. This ensures your generated code will work with React's concurrent rendering features without issues.437438Optimize to reduce network waterfalls - Use parallel data fetching wherever possible (e.g., start multiple requests at once rather than one after another). Leverage Suspense for data loading and keep requests co-located with the component that needs the data. In a server-centric approach, fetch related data together in a single request on the server side (using Server Components, for example) to reduce round trips. Also, consider using caching layers or global fetch management to avoid repeating identical requests.439440Rely on React Compiler - useMemo, useCallback, and React.memo can be omitted if React Compiler is enabled. Avoid premature optimization with manual memoization. Instead, focus on writing clear, simple components with direct data flow and side-effect-free render functions. Let the React Compiler handle tree-shaking, inlining, and other performance enhancements to keep your code base simpler and more maintainable.441442Design for a good user experience - Provide clear, minimal, and non-blocking UI states. When data is loading, show lightweight placeholders (e.g., skeleton screens) rather than intrusive spinners everywhere. Handle errors gracefully with a dedicated error boundary or a friendly inline message. Where possible, render partial data as it becomes available rather than making the user wait for everything. Suspense allows you to declare the loading states in your component tree in a natural way, preventing flash states and improving perceived performance.443444Server Components - Shift data-heavy logic to the server whenever possible. Break up the more static parts of the app into server components. Break up data fetching into server components. Only client components (denoted by the 'use client' top level directive) need interactivity. By rendering parts of your UI on the server, you reduce the client-side JavaScript needed and avoid sending unnecessary data over the wire. Use Server Components to prefetch and pre-render data, allowing faster initial loads and smaller bundle sizes. This also helps manage or eliminate certain waterfalls by resolving data on the server before streaming the HTML (and partial React tree) to the client.445446## Available Tools447- 'docs': Look up documentation from react.dev. Returns text as a string.448- 'compile': Run the user's code through React Compiler. Returns optimized JS/TS code with potential diagnostics.449450## Process4511. Analyze the user's code for optimization opportunities:452   - Check for React anti-patterns that prevent compiler optimization453   - Identify unnecessary manual optimizations (useMemo, useCallback, React.memo) that the compiler can handle454   - Look for component structure issues that limit compiler effectiveness455   - Think about each suggestion you are making and consult React docs using the docs://{query} resource for best practices4564572. Use React Compiler to verify optimization potential:458   - Run the code through the compiler and analyze the output459   - You can run the compiler multiple times to verify your work460   - Check for successful optimization by looking for const $ = _c(n) cache entries, where n is an integer461   - Identify bailout messages that indicate where code could be improved462   - Compare before/after optimization potential4634643. Provide actionable guidance:465   - Explain specific code changes with clear reasoning466   - Show before/after examples when suggesting changes467   - Include compiler results to demonstrate the impact of optimizations468   - Only suggest changes that meaningfully improve optimization potential469470## Optimization Guidelines471- Avoid mutation of values that are memoized by the compiler472- State updates should be structured to enable granular updates473- Side effects should be isolated and dependencies clearly defined474- The compiler automatically inserts memoization, so manually added useMemo/useCallback/React.memo can often be removed475476## Understanding Compiler Output477- Successful optimization adds import { c as _c } from "react/compiler-runtime";478- Successful optimization initializes a constant sized cache with const $ = _c(n), where n is the size of the cache as an integer479- When suggesting changes, try to increase or decrease the number of cached expressions (visible in const $ = _c(n))480  - Increase: more memoization coverage481  - Decrease: if there are unnecessary dependencies, less dependencies mean less re-rendering482`,483      },484    },485  ],486}));487488async function main() {489  const transport = new StdioServerTransport();490  await server.connect(transport);491  console.error('React Compiler MCP Server running on stdio');492}493494main().catch(error => {495  console.error('Fatal error in main():', error);496  process.exit(1);497});

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.