Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v18.20.1
v20.19.0
2 changes: 1 addition & 1 deletion compiler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"prettier": "^3.3.3",
"prettier-plugin-hermes-parser": "^0.26.0",
"prompt-promise": "^1.0.3",
"rimraf": "^5.0.10",
"rimraf": "^6.0.1",
"to-fast-properties": "^2.0.0",
"tsup": "^8.4.0",
"typescript": "^5.4.3",
Expand Down
30 changes: 30 additions & 0 deletions compiler/packages/react-compiler-mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "react-compiler-mcp-server",
"version": "0.0.0",
"description": "React Compiler MCP Server (experimental)",
"bin": {
"react-compiler-mcp-server": "./dist/index.js"
},
"scripts": {
"build": "rimraf dist && tsup",
"test": "echo 'no tests'",
"watch": "yarn build --watch"
},
"dependencies": {
"@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",
"algoliasearch": "^5.23.3",
"prettier": "^3.3.3",
"zod": "^3.23.8"
},
"devDependencies": {},
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/facebook/react.git",
"directory": "compiler/packages/react-compiler-mcp-server"
}
}
66 changes: 66 additions & 0 deletions compiler/packages/react-compiler-mcp-server/src/compiler/index.ts
Original file line number Diff line number Diff line change
@@ -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;
}
254 changes: 254 additions & 0 deletions compiler/packages/react-compiler-mcp-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/**
* 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 {
McpServer,
ResourceTemplate,
} 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 {liteClient, type SearchResponse} from 'algoliasearch/lite';
import {DocSearchHit} from './types/algolia';
import {printHierarchy} from './utils/algolia';

const client = liteClient('1FCF9AYYAT', '1b7ad4e1c89e645e351e59d40544eda1');

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',
version: '0.0.0',
capabilities: {
resources: {},
tools: {},
},
});

server.resource(
'docs',
new ResourceTemplate('docs://search', {list: undefined}),
async (uri, {message}) => {
const {results} = await client.search<DocSearchHit>({
requests: [
{
query: Array.isArray(message) ? message.join('\n') : message,
indexName: 'beta-react',
attributesToRetrieve: [
'hierarchy.lvl0',
'hierarchy.lvl1',
'hierarchy.lvl2',
'hierarchy.lvl3',
'hierarchy.lvl4',
'hierarchy.lvl5',
'hierarchy.lvl6',
'content',
'url',
],
attributesToSnippet: [
`hierarchy.lvl1:10`,
`hierarchy.lvl2:10`,
`hierarchy.lvl3:10`,
`hierarchy.lvl4:10`,
`hierarchy.lvl5:10`,
`hierarchy.lvl6:10`,
`content:10`,
],
snippetEllipsisText: '…',
hitsPerPage: 30,
attributesToHighlight: [
'hierarchy.lvl0',
'hierarchy.lvl1',
'hierarchy.lvl2',
'hierarchy.lvl3',
'hierarchy.lvl4',
'hierarchy.lvl5',
'hierarchy.lvl6',
'content',
],
},
],
});
const firstResult = results[0] as SearchResponse<DocSearchHit>;
const {hits} = firstResult;
return {
contents: hits.map(hit => {
return {
uri: uri.href,
text: `${hit.url}\n\n${hit.content ?? printHierarchy(hit)}`,
};
}),
};
},
);

server.tool(
'optimize',
'Use React Compiler to optimize React code. Optionally, provide a pass name like "HIR" to see more information.',
{
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.stack}`}],
};
}
},
);

server.prompt('review-code', {code: z.string()}, ({code}) => ({
messages: [
{
role: 'assistant',
content: {
type: 'text',
text: `# React Expert Assistant

## Role
You are a React assistant that helps users write better React, following the rules of React in the react.dev docs.

## Available Resources
- 'docs': Look up documentation from React.dev. Returns urls that you must retrieve so you can view its content.

## Available Tools
- 'optimize': Run the users code through React Compiler

## Process
1. Check if the users code follows the rules of React
- Point out issues in the users code if it does not

2. Run the compiler on the users code and see if it can successfully optimize the code
- If the same code is returned by the compiler, it has bailed out or there is nothing to optimize

3. Iterate
- Guide the user on making adjustments to their code so that it can be successfully optimized.

## Special Instructions
Make sure to use information from react.dev as the main reference for your React knowledge. Information from unofficial sources such as blogs and articles can also be used but may sometimes be outdated or contain poor advice.

## Example 1: <todo>

## Example 2: <todo>
`,
},
},
],
}));

async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('React Compiler MCP Server running on stdio');
}

main().catch(error => {
console.error('Fatal error in main():', error);
process.exit(1);
});
Loading
Loading