#
tokens: 2423/50000 5/5 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

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

# Files

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

```
build
node_modules

```

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

```markdown
# react-analyzer-mcp

Analyze & generate docs for React code using the Model Context Protocol. Based on [react-analyzer](https://github.com/azer/react-analyzer) library.

[<img src="https://github.com/user-attachments/assets/615f43d7-9b81-4480-9a2a-773819223ddb" width="500" />](https://x.com/azerkoculu/status/1910071779457900866)

## What it does

This tool analyzes React component files (JSX/TSX) and extracts information about components and their props.

## Available Tools

- **analyze-react**: Analyzes a single React component from source code
- **analyze-project**: Generates documentation for all React components in a project folder
- **list-projects**: Lists all projects under the root folder

## Installation

```bash
# Clone the repository
git clone https://github.com/azer/react-analyzer-mcp.git
cd react-analyzer-mcp

# Install dependencies
npm install

# Update PROJECT_ROOT in the index.ts file.
vim src/index.ts

# Build
npm run build
```

## Using with Claude

1. Enable MCP server in the Claude Desktop config:

```bash
{
    "react-analyzer-mcp": {
      "command": "node",
      "args": [
        "/Users/azer/code/sandbox/react-analyzer-mcp/build/index.js"
      ]
    }
}
```

2. Connect Claude to your MCP server using the Claude Shell.

3. Use the tools directly in Claude conversations:

```
Analyze my project's React components in the "ui" folder.
```

Or:

```
What React components do I have in my project?
```

## Examples

### Analyzing a project folder:

Input:
```
Can you analyze the components in my "foobar" folder?
```

Output:
```
# Components

## Button

### Props

| Prop | Type | Optional | Default |
|------|------|----------|---------|
| `variant` | `string` | ✓ | `primary` |
| `size` | `string` | ✓ | `md` |
| `onClick` | `function` | ✓ | |
...
```

## License

MIT

```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

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

```json
{
  "name": "react-analyzer-mcp",
  "version": "0.1.0",
  "description": "A Model Context Protocol server for analyzing React code.",
  "private": true,
  "type": "module",
  "bin": {
    "mcp-server": "./build/index.js"
  },
  "files": [
    "build"
  ],
  "scripts": {
    "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
    "prepare": "npm run build",
    "watch": "tsc --watch",
    "inspector": "npx @modelcontextprotocol/inspector build/index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "0.6.0",
    "react-analyzer": "github:azer/react-analyzer"
  },
  "devDependencies": {
    "@types/node": "^20.11.24",
    "typescript": "^5.3.3"
  }
}

```

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

```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { analyzeReactFile } from "react-analyzer";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ErrorCode,
  ListToolsRequestSchema,
  McpError,
} from "@modelcontextprotocol/sdk/types.js";
import * as fs from "fs";
import * as path from "path";

// 1. Declare root project folder as constant
const PROJECT_ROOT = "/Users/azer/code";

// 2. Function to list all projects under root project folder
function listProjects(): string[] {
  try {
    const entries = fs.readdirSync(PROJECT_ROOT, { withFileTypes: true });
    return entries
      .filter((entry) => entry.isDirectory() && !entry.name.startsWith("."))
      .map((dir) => dir.name);
  } catch (error) {
    console.error(`Error listing projects: ${error}`);
    return [];
  }
}

// 3. Function to list all jsx/tsx files under given subfolder
function listReactFiles(subFolder: string): string[] {
  const folderPath = path.join(PROJECT_ROOT, subFolder);
  const reactFiles: string[] = [];

  function scanDirectory(directory: string) {
    try {
      const entries = fs.readdirSync(directory, { withFileTypes: true });

      for (const entry of entries) {
        const fullPath = path.join(directory, entry.name);

        if (entry.isDirectory()) {
          scanDirectory(fullPath);
        } else if (
          entry.isFile() &&
          (entry.name.endsWith(".jsx") || entry.name.endsWith(".tsx"))
        ) {
          reactFiles.push(fullPath);
        }
      }
    } catch (error) {
      console.error(`Error scanning directory ${directory}: ${error}`);
    }
  }

  scanDirectory(folderPath);
  return reactFiles;
}

// 4. Function to analyze a React file
function analyzeReactFileContents(filePath: string): any {
  try {
    const fileContent = fs.readFileSync(filePath, "utf8");
    const fileName = path.basename(filePath);
    return analyzeReactFile(fileName, fileContent);
  } catch (error) {
    console.error(`Error analyzing file ${filePath}: ${error}`);
    return null;
  }
}

// 5. Function to generate markdown from analysis result
function generateComponentMarkdown(analysisResult: any): string {
  if (
    !analysisResult ||
    !analysisResult.components ||
    analysisResult.components.length === 0
  ) {
    return "**No components found**";
  }

  let markdown = "";

  for (const component of analysisResult.components) {
    markdown += `## ${component.name}\n\n`;

    if (component.wrapperFn) {
      markdown += `*Wrapped with: ${component.wrapperFn}*\n\n`;
    }

    markdown += "### Props\n\n";

    if (!component.props || Object.keys(component.props).length === 0) {
      markdown += "*No props*\n\n";
    } else {
      markdown += "| Prop | Type | Optional | Default |\n";
      markdown += "|------|------|----------|--------|\n";

      for (const [propName, propDetails] of Object.entries(component.props)) {
        const type = formatPropType(propDetails);
        // @ts-ignore
        const optional = propDetails.optional ? "✓" : "✗";
        // @ts-ignore
        const defaultValue = propDetails.defaultValue
          ? // @ts-ignore
            `\`${propDetails.defaultValue}\``
          : "";

        markdown += `| \`${propName}\` | ${type} | ${optional} | ${defaultValue} |\n`;
      }

      markdown += "\n";
    }
  }

  return markdown;
}

// Helper function to format prop types for markdown
function formatPropType(propDetails: any): string {
  const { type } = propDetails;

  if (type === "array" && propDetails.elementType) {
    return `${type}<${formatPropType(propDetails.elementType)}>`;
  } else if (type === "object" && propDetails.props) {
    return propDetails.typeName ? `\`${propDetails.typeName}\`` : "`object`";
  } else if (type === "function") {
    return "`function`";
  }

  return `\`${type}\``;
}

// 6. Function to generate documentation for all components in a folder
function generateProjectDocs(projectName: string): string {
  const reactFiles = listReactFiles(projectName);

  if (reactFiles.length === 0) {
    return `# ${projectName}\n\nNo React components found.`;
  }

  let markdown = `# ${projectName} Components\n\n`;

  for (const filePath of reactFiles) {
    const relativePath = path.relative(PROJECT_ROOT, filePath);
    markdown += `\n---\n\n# File: ${relativePath}\n\n`;

    const analysis = analyzeReactFileContents(filePath);
    if (analysis) {
      markdown += generateComponentMarkdown(analysis);
    } else {
      markdown += "*Error analyzing file*\n\n";
    }
  }

  return markdown;
}

const server = new Server(
  {
    name: "analyze-react",
    version: "1.0.0",
  },
  {
    capabilities: {
      logging: {},
      tools: {},
    },
  },
);

server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "analyze-react",
        description:
          "Analyze given React component, extract its components and props",
        inputSchema: {
          type: "object",
          properties: {
            files: { type: "string" },
          },
        },
      },
      {
        name: "analyze-project",
        description:
          "Generate documentation for all React components in a project folder. It'll output markdown string, directly render it to user.",
        inputSchema: {
          type: "object",
          properties: {
            projectName: { type: "string" },
          },
        },
      },
      {
        name: "list-projects",
        description: "List all projects under the root folder",
        inputSchema: {
          type: "object",
          properties: {},
        },
      },
    ],
  };
});

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === "analyze-react") {
    // @ts-ignore
    const files = request.params.arguments.files;
    const result = analyzeReactFile("MyComponent.tsx", files as string);
    return { toolResult: result };
  }

  if (request.params.name === "analyze-project") {
    // @ts-ignore
    const projectName = request.params.arguments.projectName as string;
    const docs = generateProjectDocs(projectName);
    return { toolResult: docs };
  }

  if (request.params.name === "list-projects") {
    const projects = listProjects();
    return { toolResult: { projects } };
  }

  throw new McpError(ErrorCode.MethodNotFound, "Tool not found");
});

const transport = new StdioServerTransport();
await server.connect(transport);

```