#
tokens: 3359/50000 5/5 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
1 | build
2 | node_modules
3 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # react-analyzer-mcp
 2 | 
 3 | Analyze & generate docs for React code using the Model Context Protocol. Based on [react-analyzer](https://github.com/azer/react-analyzer) library.
 4 | 
 5 | [<img src="https://github.com/user-attachments/assets/615f43d7-9b81-4480-9a2a-773819223ddb" width="500" />](https://x.com/azerkoculu/status/1910071779457900866)
 6 | 
 7 | ## What it does
 8 | 
 9 | This tool analyzes React component files (JSX/TSX) and extracts information about components and their props.
10 | 
11 | ## Available Tools
12 | 
13 | - **analyze-react**: Analyzes a single React component from source code
14 | - **analyze-project**: Generates documentation for all React components in a project folder
15 | - **list-projects**: Lists all projects under the root folder
16 | 
17 | ## Installation
18 | 
19 | ```bash
20 | # Clone the repository
21 | git clone https://github.com/azer/react-analyzer-mcp.git
22 | cd react-analyzer-mcp
23 | 
24 | # Install dependencies
25 | npm install
26 | 
27 | # Update PROJECT_ROOT in the index.ts file.
28 | vim src/index.ts
29 | 
30 | # Build
31 | npm run build
32 | ```
33 | 
34 | ## Using with Claude
35 | 
36 | 1. Enable MCP server in the Claude Desktop config:
37 | 
38 | ```bash
39 | {
40 |     "react-analyzer-mcp": {
41 |       "command": "node",
42 |       "args": [
43 |         "/Users/azer/code/sandbox/react-analyzer-mcp/build/index.js"
44 |       ]
45 |     }
46 | }
47 | ```
48 | 
49 | 2. Connect Claude to your MCP server using the Claude Shell.
50 | 
51 | 3. Use the tools directly in Claude conversations:
52 | 
53 | ```
54 | Analyze my project's React components in the "ui" folder.
55 | ```
56 | 
57 | Or:
58 | 
59 | ```
60 | What React components do I have in my project?
61 | ```
62 | 
63 | ## Examples
64 | 
65 | ### Analyzing a project folder:
66 | 
67 | Input:
68 | ```
69 | Can you analyze the components in my "foobar" folder?
70 | ```
71 | 
72 | Output:
73 | ```
74 | # Components
75 | 
76 | ## Button
77 | 
78 | ### Props
79 | 
80 | | Prop | Type | Optional | Default |
81 | |------|------|----------|---------|
82 | | `variant` | `string` | ✓ | `primary` |
83 | | `size` | `string` | ✓ | `md` |
84 | | `onClick` | `function` | ✓ | |
85 | ...
86 | ```
87 | 
88 | ## License
89 | 
90 | MIT
91 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "Node16",
 5 |     "moduleResolution": "Node16",
 6 |     "outDir": "./build",
 7 |     "rootDir": "./src",
 8 |     "strict": true,
 9 |     "esModuleInterop": true,
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true
12 |   },
13 |   "include": ["src/**/*"],
14 |   "exclude": ["node_modules"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "react-analyzer-mcp",
 3 |   "version": "0.1.0",
 4 |   "description": "A Model Context Protocol server for analyzing React code.",
 5 |   "private": true,
 6 |   "type": "module",
 7 |   "bin": {
 8 |     "mcp-server": "./build/index.js"
 9 |   },
10 |   "files": [
11 |     "build"
12 |   ],
13 |   "scripts": {
14 |     "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
15 |     "prepare": "npm run build",
16 |     "watch": "tsc --watch",
17 |     "inspector": "npx @modelcontextprotocol/inspector build/index.js"
18 |   },
19 |   "dependencies": {
20 |     "@modelcontextprotocol/sdk": "0.6.0",
21 |     "react-analyzer": "github:azer/react-analyzer"
22 |   },
23 |   "devDependencies": {
24 |     "@types/node": "^20.11.24",
25 |     "typescript": "^5.3.3"
26 |   }
27 | }
28 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  2 | import { analyzeReactFile } from "react-analyzer";
  3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  4 | import {
  5 |   CallToolRequestSchema,
  6 |   ErrorCode,
  7 |   ListToolsRequestSchema,
  8 |   McpError,
  9 | } from "@modelcontextprotocol/sdk/types.js";
 10 | import * as fs from "fs";
 11 | import * as path from "path";
 12 | 
 13 | // 1. Declare root project folder as constant
 14 | const PROJECT_ROOT = "/Users/azer/code";
 15 | 
 16 | // 2. Function to list all projects under root project folder
 17 | function listProjects(): string[] {
 18 |   try {
 19 |     const entries = fs.readdirSync(PROJECT_ROOT, { withFileTypes: true });
 20 |     return entries
 21 |       .filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
 22 |       .map((dir) => dir.name);
 23 |   } catch (error) {
 24 |     console.error(`Error listing projects: ${error}`);
 25 |     return [];
 26 |   }
 27 | }
 28 | 
 29 | // 3. Function to list all jsx/tsx files under given subfolder
 30 | function listReactFiles(subFolder: string): string[] {
 31 |   const folderPath = path.join(PROJECT_ROOT, subFolder);
 32 |   const reactFiles: string[] = [];
 33 | 
 34 |   function scanDirectory(directory: string) {
 35 |     try {
 36 |       const entries = fs.readdirSync(directory, { withFileTypes: true });
 37 | 
 38 |       for (const entry of entries) {
 39 |         const fullPath = path.join(directory, entry.name);
 40 | 
 41 |         if (entry.isDirectory()) {
 42 |           scanDirectory(fullPath);
 43 |         } else if (
 44 |           entry.isFile() &&
 45 |           (entry.name.endsWith(".jsx") || entry.name.endsWith(".tsx"))
 46 |         ) {
 47 |           reactFiles.push(fullPath);
 48 |         }
 49 |       }
 50 |     } catch (error) {
 51 |       console.error(`Error scanning directory ${directory}: ${error}`);
 52 |     }
 53 |   }
 54 | 
 55 |   scanDirectory(folderPath);
 56 |   return reactFiles;
 57 | }
 58 | 
 59 | // 4. Function to analyze a React file
 60 | function analyzeReactFileContents(filePath: string): any {
 61 |   try {
 62 |     const fileContent = fs.readFileSync(filePath, "utf8");
 63 |     const fileName = path.basename(filePath);
 64 |     return analyzeReactFile(fileName, fileContent);
 65 |   } catch (error) {
 66 |     console.error(`Error analyzing file ${filePath}: ${error}`);
 67 |     return null;
 68 |   }
 69 | }
 70 | 
 71 | // 5. Function to generate markdown from analysis result
 72 | function generateComponentMarkdown(analysisResult: any): string {
 73 |   if (
 74 |     !analysisResult ||
 75 |     !analysisResult.components ||
 76 |     analysisResult.components.length === 0
 77 |   ) {
 78 |     return "**No components found**";
 79 |   }
 80 | 
 81 |   let markdown = "";
 82 | 
 83 |   for (const component of analysisResult.components) {
 84 |     markdown += `## ${component.name}\n\n`;
 85 | 
 86 |     if (component.wrapperFn) {
 87 |       markdown += `*Wrapped with: ${component.wrapperFn}*\n\n`;
 88 |     }
 89 | 
 90 |     markdown += "### Props\n\n";
 91 | 
 92 |     if (!component.props || Object.keys(component.props).length === 0) {
 93 |       markdown += "*No props*\n\n";
 94 |     } else {
 95 |       markdown += "| Prop | Type | Optional | Default |\n";
 96 |       markdown += "|------|------|----------|--------|\n";
 97 | 
 98 |       for (const [propName, propDetails] of Object.entries(component.props)) {
 99 |         const type = formatPropType(propDetails);
100 |         // @ts-ignore
101 |         const optional = propDetails.optional ? "✓" : "✗";
102 |         // @ts-ignore
103 |         const defaultValue = propDetails.defaultValue
104 |           ? // @ts-ignore
105 |             `\`${propDetails.defaultValue}\``
106 |           : "";
107 | 
108 |         markdown += `| \`${propName}\` | ${type} | ${optional} | ${defaultValue} |\n`;
109 |       }
110 | 
111 |       markdown += "\n";
112 |     }
113 |   }
114 | 
115 |   return markdown;
116 | }
117 | 
118 | // Helper function to format prop types for markdown
119 | function formatPropType(propDetails: any): string {
120 |   const { type } = propDetails;
121 | 
122 |   if (type === "array" && propDetails.elementType) {
123 |     return `${type}<${formatPropType(propDetails.elementType)}>`;
124 |   } else if (type === "object" && propDetails.props) {
125 |     return propDetails.typeName ? `\`${propDetails.typeName}\`` : "`object`";
126 |   } else if (type === "function") {
127 |     return "`function`";
128 |   }
129 | 
130 |   return `\`${type}\``;
131 | }
132 | 
133 | // 6. Function to generate documentation for all components in a folder
134 | function generateProjectDocs(projectName: string): string {
135 |   const reactFiles = listReactFiles(projectName);
136 | 
137 |   if (reactFiles.length === 0) {
138 |     return `# ${projectName}\n\nNo React components found.`;
139 |   }
140 | 
141 |   let markdown = `# ${projectName} Components\n\n`;
142 | 
143 |   for (const filePath of reactFiles) {
144 |     const relativePath = path.relative(PROJECT_ROOT, filePath);
145 |     markdown += `\n---\n\n# File: ${relativePath}\n\n`;
146 | 
147 |     const analysis = analyzeReactFileContents(filePath);
148 |     if (analysis) {
149 |       markdown += generateComponentMarkdown(analysis);
150 |     } else {
151 |       markdown += "*Error analyzing file*\n\n";
152 |     }
153 |   }
154 | 
155 |   return markdown;
156 | }
157 | 
158 | const server = new Server(
159 |   {
160 |     name: "analyze-react",
161 |     version: "1.0.0",
162 |   },
163 |   {
164 |     capabilities: {
165 |       logging: {},
166 |       tools: {},
167 |     },
168 |   },
169 | );
170 | 
171 | server.setRequestHandler(ListToolsRequestSchema, async () => {
172 |   return {
173 |     tools: [
174 |       {
175 |         name: "analyze-react",
176 |         description:
177 |           "Analyze given React component, extract its components and props",
178 |         inputSchema: {
179 |           type: "object",
180 |           properties: {
181 |             files: { type: "string" },
182 |           },
183 |         },
184 |       },
185 |       {
186 |         name: "analyze-project",
187 |         description:
188 |           "Generate documentation for all React components in a project folder. It'll output markdown string, directly render it to user.",
189 |         inputSchema: {
190 |           type: "object",
191 |           properties: {
192 |             projectName: { type: "string" },
193 |           },
194 |         },
195 |       },
196 |       {
197 |         name: "list-projects",
198 |         description: "List all projects under the root folder",
199 |         inputSchema: {
200 |           type: "object",
201 |           properties: {},
202 |         },
203 |       },
204 |     ],
205 |   };
206 | });
207 | 
208 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
209 |   if (request.params.name === "analyze-react") {
210 |     // @ts-ignore
211 |     const files = request.params.arguments.files;
212 |     const result = analyzeReactFile("MyComponent.tsx", files as string);
213 |     return { toolResult: result };
214 |   }
215 | 
216 |   if (request.params.name === "analyze-project") {
217 |     // @ts-ignore
218 |     const projectName = request.params.arguments.projectName as string;
219 |     const docs = generateProjectDocs(projectName);
220 |     return { toolResult: docs };
221 |   }
222 | 
223 |   if (request.params.name === "list-projects") {
224 |     const projects = listProjects();
225 |     return { toolResult: { projects } };
226 |   }
227 | 
228 |   throw new McpError(ErrorCode.MethodNotFound, "Tool not found");
229 | });
230 | 
231 | const transport = new StdioServerTransport();
232 | await server.connect(transport);
233 | 
```