mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[compiler] Add a basic mcp tool
Adds the ability to compile as a tool
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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<PluginOptions> | null;
|
||||
};
|
||||
export async function compile({
|
||||
text,
|
||||
file,
|
||||
options,
|
||||
}: CompileOptions): Promise<BabelCore.BabelFileResult> {
|
||||
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;
|
||||
}
|
||||
@@ -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<PrintedCompilerPipelineValue>
|
||||
>();
|
||||
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);
|
||||
|
||||
+79
-1
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user