From 4bbff3f0d3efabb28189204f48660b7ed59fcbc4 Mon Sep 17 00:00:00 2001 From: Lauren Tan Date: Fri, 11 Apr 2025 21:29:13 -0400 Subject: [PATCH] [compiler] Add a basic mcp tool Adds the ability to compile as a tool --- .../react-compiler-mcp-server/package.json | 8 +- .../src/compiler/index.ts | 66 ++++++++++ .../react-compiler-mcp-server/src/index.ts | 122 ++++++++++++++++++ compiler/yarn.lock | 80 +++++++++++- 4 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 compiler/packages/react-compiler-mcp-server/src/compiler/index.ts diff --git a/compiler/packages/react-compiler-mcp-server/package.json b/compiler/packages/react-compiler-mcp-server/package.json index 0896c5406e..e34a7000be 100644 --- a/compiler/packages/react-compiler-mcp-server/package.json +++ b/compiler/packages/react-compiler-mcp-server/package.json @@ -11,7 +11,13 @@ "watch": "yarn build --watch" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.9.0" + "@babel/core": "^7.26.0", + "@babel/parser": "^7.26", + "@babel/plugin-syntax-typescript": "^7.25.9", + "@babel/types": "^7.26.0", + "@modelcontextprotocol/sdk": "^1.9.0", + "prettier": "^3.3.3", + "zod": "^3.23.8" }, "devDependencies": {}, "license": "MIT", diff --git a/compiler/packages/react-compiler-mcp-server/src/compiler/index.ts b/compiler/packages/react-compiler-mcp-server/src/compiler/index.ts new file mode 100644 index 0000000000..2401e3291c --- /dev/null +++ b/compiler/packages/react-compiler-mcp-server/src/compiler/index.ts @@ -0,0 +1,66 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type * as BabelCore from '@babel/core'; +import {parseAsync, transformFromAstAsync} from '@babel/core'; +import BabelPluginReactCompiler, { + type PluginOptions, +} from 'babel-plugin-react-compiler/src'; +import * as babelParser from 'prettier/plugins/babel.js'; +import estreeParser from 'prettier/plugins/estree'; +import * as typescriptParser from 'prettier/plugins/typescript'; +import * as prettier from 'prettier/standalone'; + +export let lastResult: BabelCore.BabelFileResult | null = null; + +type CompileOptions = { + text: string; + file: string; + options: Partial | null; +}; +export async function compile({ + text, + file, + options, +}: CompileOptions): Promise { + const ast = await parseAsync(text, { + sourceFileName: file, + parserOpts: { + plugins: ['typescript', 'jsx'], + }, + sourceType: 'module', + }); + if (ast == null) { + throw new Error('Could not parse'); + } + const plugins = + options != null + ? [[BabelPluginReactCompiler, options]] + : [[BabelPluginReactCompiler]]; + const result = await transformFromAstAsync(ast, text, { + filename: file, + highlightCode: false, + retainLines: true, + plugins, + sourceType: 'module', + sourceFileName: file, + }); + if (result?.code == null) { + throw new Error( + `Expected BabelPluginReactCompiler to compile successfully, got ${result}`, + ); + } + result.code = await prettier.format(result.code, { + semi: false, + parser: 'babel-ts', + plugins: [babelParser, estreeParser, typescriptParser], + }); + if (result.code != null) { + lastResult = result; + } + return result; +} diff --git a/compiler/packages/react-compiler-mcp-server/src/index.ts b/compiler/packages/react-compiler-mcp-server/src/index.ts index 47734e4e70..7e737bd6b5 100644 --- a/compiler/packages/react-compiler-mcp-server/src/index.ts +++ b/compiler/packages/react-compiler-mcp-server/src/index.ts @@ -7,6 +7,24 @@ import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'; import {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js'; +import {z} from 'zod'; +import {compile} from './compiler'; +import { + CompilerPipelineValue, + printReactiveFunctionWithOutlined, + printFunctionWithOutlined, +} from 'babel-plugin-react-compiler/src'; +import {type CallToolResult} from '@modelcontextprotocol/sdk/types.js'; + +export type PrintedCompilerPipelineValue = + | { + kind: 'hir'; + name: string; + fnName: string | null; + value: string; + } + | {kind: 'reactive'; name: string; fnName: string | null; value: string} + | {kind: 'debug'; name: string; fnName: string | null; value: string}; const server = new McpServer({ name: 'React Compiler', @@ -17,6 +35,110 @@ const server = new McpServer({ }, }); +server.tool( + 'analyze', + 'Use React Compiler to analyze React code', + { + text: z.string(), + passName: z.string().optional(), + }, + async ({text, passName}) => { + const pipelinePasses = new Map< + string, + Array + >(); + const recordPass: ( + result: PrintedCompilerPipelineValue, + ) => void = result => { + const entry = pipelinePasses.get(result.name); + if (Array.isArray(entry)) { + entry.push(result); + } else { + pipelinePasses.set(result.name, [result]); + } + }; + const logIR = (result: CompilerPipelineValue): void => { + switch (result.kind) { + case 'ast': { + break; + } + case 'hir': { + recordPass({ + kind: 'hir', + fnName: result.value.id, + name: result.name, + value: printFunctionWithOutlined(result.value), + }); + break; + } + case 'reactive': { + recordPass({ + kind: 'reactive', + fnName: result.value.id, + name: result.name, + value: printReactiveFunctionWithOutlined(result.value), + }); + break; + } + case 'debug': { + recordPass({ + kind: 'debug', + fnName: null, + name: result.name, + value: result.value, + }); + break; + } + default: { + const _: never = result; + throw new Error(`Unhandled result ${result}`); + } + } + }; + const compilerOptions = { + logger: { + debugLogIRs: logIR, + logEvent: () => {}, + }, + }; + try { + const result = await compile({ + text, + file: 'anonymous.tsx', + options: compilerOptions, + }); + if (result.code == null) { + return { + isError: true, + content: [{type: 'text', text: 'Error: Could not compile'}], + }; + } + const requestedPasses: Array<{type: 'text'; text: string}> = []; + if (passName != null) { + const requestedPass = pipelinePasses.get(passName); + if (requestedPass !== undefined) { + for (const pipelineValue of requestedPass) { + if (pipelineValue.name === passName) { + requestedPasses.push({ + type: 'text', + text: pipelineValue.value, + }); + } + } + } + } + return { + content: [{type: 'text', text: result.code}, ...requestedPasses], + }; + } catch (err) { + return { + isError: true, + content: [{type: 'text', text: `Error: ${err}`}], + }; + } + }, +); + async function main() { const transport = new StdioServerTransport(); await server.connect(transport); diff --git a/compiler/yarn.lock b/compiler/yarn.lock index fd51ee6770..08cbaf5ebf 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -63,6 +63,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.3.tgz#99488264a56b2aded63983abd6a417f03b92ed02" integrity sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g== +"@babel/compat-data@^7.26.8": + version "7.26.8" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.8.tgz#821c1d35641c355284d4a870b8a4a7b0c141e367" + integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== + "@babel/core@^7.0.0", "@babel/core@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.2.0.tgz#a4dd3814901998e93340f0086e9867fefa163ada" @@ -104,6 +109,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.26.0": + version "7.26.10" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.10.tgz#5c876f83c8c4dcb233ee4b670c0606f2ac3000f9" + integrity sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.10" + "@babel/helper-compilation-targets" "^7.26.5" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.10" + "@babel/parser" "^7.26.10" + "@babel/template" "^7.26.9" + "@babel/traverse" "^7.26.10" + "@babel/types" "^7.26.10" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/generator@7.2.0", "@babel/generator@^7.0.0", "@babel/generator@^7.2.0": version "7.2.0" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.2.0.tgz#eaf3821fa0301d9d4aef88e63d4bcc19b73ba16c" @@ -137,6 +163,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" +"@babel/generator@^7.27.0": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.27.0.tgz#764382b5392e5b9aff93cadb190d0745866cbc2c" + integrity sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw== + dependencies: + "@babel/parser" "^7.27.0" + "@babel/types" "^7.27.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" @@ -187,6 +224,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.26.5": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz#de0c753b1cd1d9ab55d473c5a5cf7170f0a81880" + integrity sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA== + dependencies: + "@babel/compat-data" "^7.26.8" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.18.6": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.9.tgz#c36ea240bb3348f942f08b0fbe28d6d979fab236" @@ -629,6 +677,14 @@ "@babel/template" "^7.25.9" "@babel/types" "^7.26.0" +"@babel/helpers@^7.26.10": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.27.0.tgz#53d156098defa8243eab0f32fa17589075a1b808" + integrity sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg== + dependencies: + "@babel/template" "^7.27.0" + "@babel/types" "^7.27.0" + "@babel/highlight@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" @@ -667,7 +723,7 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.3.tgz#8dd36d17c53ff347f9e55c328710321b49479a9a" integrity sha512-pJ9xOlNWHiy9+FuFP09DEAFbAn4JskgRsVcc169w2xRBC3FRGuQEwjeIMMND9L2zc0iEhO/tGv4Zq+km+hxNpQ== -"@babel/parser@^7.20.15": +"@babel/parser@^7.20.15", "@babel/parser@^7.26", "@babel/parser@^7.27.0": version "7.27.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.27.0.tgz#3d7d6ee268e41d2600091cbd4e145ffee85a44ec" integrity sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg== @@ -1653,6 +1709,15 @@ "@babel/parser" "^7.26.9" "@babel/types" "^7.26.9" +"@babel/template@^7.27.0": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.27.0.tgz#b253e5406cc1df1c57dcd18f11760c2dbf40c0b4" + integrity sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/parser" "^7.27.0" + "@babel/types" "^7.27.0" + "@babel/template@^7.3.3": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31" @@ -1690,6 +1755,19 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.26.10": + version "7.27.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.27.0.tgz#11d7e644779e166c0442f9a07274d02cd91d4a70" + integrity sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.27.0" + "@babel/parser" "^7.27.0" + "@babel/template" "^7.27.0" + "@babel/types" "^7.27.0" + debug "^4.3.1" + globals "^11.1.0" + "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.9": version "7.26.10" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.10.tgz#43cca33d76005dbaa93024fae536cc1946a4c380"