# Directory Structure
```
├── .gitignore
├── Dockerfile
├── index.js
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
└── smithery.yaml
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# React MCP (Model Context Protocol)
[](https://smithery.ai/server/@Streen9/react-mcp)
A powerful server implementation that enables Claude AI to interact with React applications through the Model Context Protocol.
<a href="https://glama.ai/mcp/servers/xsjsdumc7x">
<img width="380" height="200" src="https://glama.ai/mcp/servers/xsjsdumc7x/badge" alt="https://github.com/Streen9/react-mcp MCP server" />
</a>
## Sample Usage
- [Markdown Editor/Viewer By Claude](https://claude.ai/share/f68940f1-97cd-41df-9c14-f63dc6fb9faf)

- [API Tester By Claude](https://claude.ai/share/b0b3943c-5c90-4b8d-8613-e76eaa243407)

## Overview
React MCP provides a bridge between Claude AI and the React ecosystem, allowing Claude to:
- Create new React applications
- Run React development servers
- Manage files and directories
- Install npm packages
- Execute terminal commands
- Track and manage long-running processes
This server implements the Model Context Protocol, providing Claude with the ability to perform real-world actions in the development environment.
## Features
- **React Project Management**
- Create new React applications with optional templates
- Run development servers
- Manage dependencies
- **File Operations**
- Read and write files
- Edit React components and configuration
- **Process Management**
- Start and monitor long-running processes
- Track process output in real-time
- Terminate processes when needed
- **Command Execution**
- Run arbitrary terminal commands
- Install npm packages
- Execute development tasks
- **Comprehensive Logging**
- Detailed JSON and text logs
- Process tracking with timestamps
- Execution history
## Installation
### Installing via Smithery
To install React MCP for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@Streen9/react-mcp):
```bash
npx -y @smithery/cli install @Streen9/react-mcp --client claude
```
### Manual Installation
1. Clone this repository
2. Install dependencies:
```bash
npm install
```
## Usage
Add this in `claude_desktop_config`:
```
{
"mcpServers": {
"react-mcp": {
"command": "node",
"args": [
"C:/Users/kalip/OneDrive/Desktop/react-mcp/index.js"
]
},
}
}
```
The server runs on the stdio transport, allowing it to be used with Desktop Claude APP as a Model Context Protocol tool.
## Available Tools
### `create-react-app`
Creates a new React application.
Parameters:
- `name` (required): Name of the React app
- `template` (optional): Template to use (e.g., typescript, cra-template-pwa)
- `directory` (optional): Base directory to create the app in (defaults to home directory)
### `run-react-app`
Runs a React application in development mode.
Parameters:
- `projectPath` (required): Path to the React project folder
### `run-command`
Runs a terminal command.
Parameters:
- `command` (required): Command to execute
- `directory` (optional): Directory to run the command in (defaults to current directory)
### `get-process-output`
Gets the output from a running or completed process.
Parameters:
- `processId` (required): ID of the process to get output from
### `stop-process`
Stops a running process.
Parameters:
- `processId` (required): ID of the process to stop
### `list-processes`
Lists all running processes.
### `edit-file`
Creates or edits a file.
Parameters:
- `filePath` (required): Path to the file to edit
- `content` (required): Content to write to the file
### `read-file`
Reads the contents of a file.
Parameters:
- `filePath` (required): Path to the file to read
### `install-package`
Installs a npm package in a project.
Parameters:
- `packageName` (required): Name of the package to install (can include version)
- `directory` (optional): Directory of the project (defaults to current directory)
- `dev` (optional): Whether to install as a dev dependency
### `check-installation-status`
Checks the status of a package installation process.
Parameters:
- `processId` (required): ID of the installation process to check
## Logging
The server maintains detailed logs in the `logs` directory:
- `react-mcp-logs.json`: Structured JSON logs
- `react-mcp-logs.txt`: Human-readable text logs
## Architecture
The server uses the following key components:
- **Model Context Protocol SDK**: For communication with Claude AI
- **StdioServerTransport**: For I/O through standard input/output
- **Zod**: For schema validation and type safety
- **Child Process**: For spawning and managing external processes
## License
MIT
## Author
[@streen9](https://github.com/Streen9)
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
startCommand:
type: stdio
configSchema:
# JSON Schema defining the configuration options for the MCP.
{}
exampleConfig: {}
commandFunction:
# A JS function that produces the CLI command based on the given config to start the MCP on stdio.
|-
(config) => ({ command: 'node', args: ['index.js'], env: {} })
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "react-mcp",
"version": "0.1.0",
"description": "A React MCP server for Claude integration",
"type": "module",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "[email protected]",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"js-yaml": "^4.1.0",
"zod": "^3.22.4"
}
}
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
FROM node:lts-alpine
WORKDIR /app
# Copy package.json and package-lock.json (if available) for dependency installation
COPY package*.json ./
# Install dependencies without running any scripts to avoid potential issues
RUN npm install --ignore-scripts
# Copy the rest of the application files
COPY . .
# Expose port if needed (not required for stdio transport, but in case)
# EXPOSE 3000
# Command to run the MCP server using stdio transport
CMD ["npm", "start"]
```
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
```javascript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import { spawn, exec } from "child_process";
import fs from "fs";
import path from "path";
import os from "os";
// Initialize logging
const LOG_DIR = "logs";
if (!fs.existsSync(LOG_DIR)) {
fs.mkdirSync(LOG_DIR);
}
const getCurrentTimestamp = () => {
return new Date().toISOString().replace(/[:.]/g, "-");
};
const logToFile = (data, type = "json") => {
const timestamp = getCurrentTimestamp();
const logEntry = {
timestamp,
...data,
};
// JSON logging
const jsonLogPath = path.join(LOG_DIR, "react-mcp-logs.json");
let jsonLogs = [];
if (fs.existsSync(jsonLogPath)) {
const fileContent = fs.readFileSync(jsonLogPath, "utf8");
jsonLogs = fileContent ? JSON.parse(fileContent) : [];
}
jsonLogs.push(logEntry);
fs.writeFileSync(jsonLogPath, JSON.stringify(jsonLogs, null, 2));
// Text logging
const txtLogPath = path.join(LOG_DIR, "react-mcp-logs.txt");
const txtLogEntry = `[${timestamp}] ${JSON.stringify(data)}\n`;
fs.appendFileSync(txtLogPath, txtLogEntry);
};
// Keep track of running processes
const runningProcesses = new Map();
// Execute terminal commands
async function executeCommand(command, options = {}) {
return new Promise((resolve, reject) => {
exec(command, options, (error, stdout, stderr) => {
if (error) {
return reject({ error, stderr });
}
resolve({ stdout, stderr });
});
});
}
// Start a long-running process and return its output stream
function startProcess(command, args, cwd) {
const childProcess = spawn(command, args, {
cwd,
shell: true,
env: { ...process.env, FORCE_COLOR: "true" },
});
let output = "";
let errorOutput = "";
childProcess.stdout.on("data", (data) => {
const chunk = data.toString();
output += chunk;
});
childProcess.stderr.on("data", (data) => {
const chunk = data.toString();
errorOutput += chunk;
});
const processId = Math.random().toString(36).substring(2, 15);
runningProcesses.set(processId, {
process: childProcess,
command,
args,
cwd,
output,
errorOutput,
startTime: new Date(),
processId,
});
return processId;
}
// Tool handlers
async function handleCreateReactApp(params) {
try {
const { name, template, directory } = params;
if (!name) {
throw new Error("Project name is required");
}
// Determine base directory
const baseDir = directory || os.homedir();
const projectDir = path.join(baseDir, name);
// Check if directory already exists
if (fs.existsSync(projectDir)) {
throw new Error(`Directory ${projectDir} already exists`);
}
// Prepare create-react-app command
const createCommand = template
? `npx create-react-app ${name} --template ${template}`
: `npx create-react-app ${name}`;
console.log(
`Creating React app in ${baseDir} with command: ${createCommand}`
);
// Run the command
const processId = startProcess(createCommand, [], baseDir);
return {
message: `Creating React app "${name}" in ${projectDir}`,
processId: processId,
projectDir: projectDir,
};
} catch (error) {
return {
error: `Error creating React app: ${error.message}`,
};
}
}
async function handleRunReactApp(params) {
try {
const { projectPath } = params;
if (!projectPath) {
throw new Error("Project path is required");
}
// Check if directory exists
if (!fs.existsSync(projectPath)) {
throw new Error(`Directory ${projectPath} does not exist`);
}
// Check if it's a React app (package.json exists with react dependency)
const packageJsonPath = path.join(projectPath, "package.json");
if (!fs.existsSync(packageJsonPath)) {
throw new Error(
`Not a valid React app: package.json not found in ${projectPath}`
);
}
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
if (!packageJson.dependencies || !packageJson.dependencies.react) {
throw new Error(
`Not a valid React app: react dependency not found in package.json`
);
}
// Start the development server
const processId = startProcess("npm", ["start"], projectPath);
return {
message: `Starting React development server in ${projectPath}`,
processId: processId,
note: "The development server should be accessible at http://localhost:3000",
};
} catch (error) {
return {
error: `Error running React app: ${error.message}`,
};
}
}
async function handleRunCommand(params) {
try {
const { command, directory } = params;
if (!command) {
throw new Error("Command is required");
}
// Determine directory
const workingDir = directory || process.cwd();
// Check if directory exists
if (!fs.existsSync(workingDir)) {
throw new Error(`Directory ${workingDir} does not exist`);
}
// Run the command
const result = await executeCommand(command, { cwd: workingDir });
return {
command: command,
directory: workingDir,
output: result.stdout,
stderr: result.stderr || "",
};
} catch (error) {
return {
error: `Error executing command: ${error.message}`,
stderr: error.stderr || "",
};
}
}
async function handleGetProcessOutput(params) {
try {
const { processId } = params;
if (!processId) {
throw new Error("Process ID is required");
}
if (!runningProcesses.has(processId)) {
throw new Error(`Process with ID ${processId} not found`);
}
const processInfo = runningProcesses.get(processId);
const isRunning = processInfo.process.exitCode === null;
return {
processId: processId,
command: `${processInfo.command} ${processInfo.args.join(" ")}`,
directory: processInfo.cwd,
isRunning: isRunning,
exitCode: processInfo.process.exitCode,
output: processInfo.output,
errorOutput: processInfo.errorOutput,
startTime: processInfo.startTime.toISOString(),
runTime: `${Math.floor(
(new Date() - processInfo.startTime) / 1000
)} seconds`,
};
} catch (error) {
return {
error: `Error getting process output: ${error.message}`,
};
}
}
async function handleStopProcess(params) {
try {
const { processId } = params;
if (!processId) {
throw new Error("Process ID is required");
}
if (!runningProcesses.has(processId)) {
throw new Error(`Process with ID ${processId} not found`);
}
const processInfo = runningProcesses.get(processId);
// Kill the process
processInfo.process.kill();
return {
message: `Process ${processId} stopped`,
command: `${processInfo.command} ${processInfo.args.join(" ")}`,
directory: processInfo.cwd,
};
} catch (error) {
return {
error: `Error stopping process: ${error.message}`,
};
}
}
async function handleListProcesses() {
try {
const processes = [];
for (const [processId, processInfo] of runningProcesses.entries()) {
const isRunning = processInfo.process.exitCode === null;
processes.push({
processId: processId,
command: `${processInfo.command} ${processInfo.args.join(" ")}`,
directory: processInfo.cwd,
isRunning: isRunning,
exitCode: processInfo.process.exitCode,
startTime: processInfo.startTime.toISOString(),
runTime: `${Math.floor(
(new Date() - processInfo.startTime) / 1000
)} seconds`,
});
}
return {
processes: processes,
count: processes.length,
};
} catch (error) {
return {
error: `Error listing processes: ${error.message}`,
};
}
}
async function handleEditFile(params) {
try {
const { filePath, content } = params;
if (!filePath) {
throw new Error("File path is required");
}
if (content === undefined || content === null) {
throw new Error("File content is required");
}
// Make sure directory exists
const directory = path.dirname(filePath);
if (!fs.existsSync(directory)) {
fs.mkdirSync(directory, { recursive: true });
}
// Write content to file
fs.writeFileSync(filePath, content, "utf8");
return {
message: `File ${filePath} updated successfully`,
filePath: filePath,
size: Buffer.byteLength(content, "utf8"),
};
} catch (error) {
return {
error: `Error editing file: ${error.message}`,
};
}
}
async function handleReadFile(params) {
try {
const { filePath } = params;
if (!filePath) {
throw new Error("File path is required");
}
// Check if file exists
if (!fs.existsSync(filePath)) {
throw new Error(`File ${filePath} does not exist`);
}
// Read file content
const content = fs.readFileSync(filePath, "utf8");
return {
filePath: filePath,
content: content,
size: Buffer.byteLength(content, "utf8"),
};
} catch (error) {
return {
error: `Error reading file: ${error.message}`,
};
}
}
async function handleInstallPackage(params) {
try {
const { packageName, directory, dev } = params;
if (!packageName) {
throw new Error("Package name is required");
}
// Determine directory
const workingDir = directory || process.cwd();
// Check if directory exists
if (!fs.existsSync(workingDir)) {
throw new Error(`Directory ${workingDir} does not exist`);
}
// Check if package.json exists
const packageJsonPath = path.join(workingDir, "package.json");
if (!fs.existsSync(packageJsonPath)) {
throw new Error(
`Not a valid Node.js project: package.json not found in ${workingDir}`
);
}
// Install the package
const installCommand = dev
? `npm install ${packageName} --save-dev`
: `npm install ${packageName}`;
const processId = startProcess(installCommand, [], workingDir);
return {
message: `Installing ${packageName} in ${workingDir}`,
processId: processId,
command: installCommand,
};
} catch (error) {
return {
error: `Error installing package: ${error.message}`,
};
}
}
// Server setup
const server = new Server(
{
name: "react-mcp",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
// Define schemas
const CreateReactAppSchema = z.object({
name: z.string(),
template: z.string().optional(),
directory: z.string().optional(),
});
const RunReactAppSchema = z.object({
projectPath: z.string(),
});
const RunCommandSchema = z.object({
command: z.string(),
directory: z.string().optional(),
});
const GetProcessOutputSchema = z.object({
processId: z.string(),
});
const StopProcessSchema = z.object({
processId: z.string(),
});
const EditFileSchema = z.object({
filePath: z.string(),
content: z.string(),
});
const ReadFileSchema = z.object({
filePath: z.string(),
});
const InstallPackageSchema = z.object({
packageName: z.string(),
directory: z.string().optional(),
dev: z.boolean().optional(),
});
// Tool request handler
server.setRequestHandler(ListToolsRequestSchema, async () => {
const response = {
tools: [
{
name: "create-react-app",
description: "Create a new React application",
inputSchema: {
type: "object",
properties: {
name: {
type: "string",
description: "Name of the React app",
},
template: {
type: "string",
description:
"Template to use (e.g., typescript, cra-template-pwa)",
},
directory: {
type: "string",
description:
"Base directory to create the app in (defaults to home directory)",
},
},
required: ["name"],
},
},
{
name: "run-react-app",
description: "Run a React application in development mode",
inputSchema: {
type: "object",
properties: {
projectPath: {
type: "string",
description: "Path to the React project folder",
},
},
required: ["projectPath"],
},
},
{
name: "run-command",
description: "Run a terminal command",
inputSchema: {
type: "object",
properties: {
command: {
type: "string",
description: "Command to execute",
},
directory: {
type: "string",
description:
"Directory to run the command in (defaults to current directory)",
},
},
required: ["command"],
},
},
{
name: "get-process-output",
description: "Get the output from a running or completed process",
inputSchema: {
type: "object",
properties: {
processId: {
type: "string",
description: "ID of the process to get output from",
},
},
required: ["processId"],
},
},
{
name: "stop-process",
description: "Stop a running process",
inputSchema: {
type: "object",
properties: {
processId: {
type: "string",
description: "ID of the process to stop",
},
},
required: ["processId"],
},
},
{
name: "list-processes",
description: "List all running processes",
inputSchema: {
type: "object",
properties: {},
},
},
{
name: "edit-file",
description: "Create or edit a file",
inputSchema: {
type: "object",
properties: {
filePath: {
type: "string",
description: "Path to the file to edit",
},
content: {
type: "string",
description: "Content to write to the file",
},
},
required: ["filePath", "content"],
},
},
{
name: "read-file",
description: "Read the contents of a file",
inputSchema: {
type: "object",
properties: {
filePath: {
type: "string",
description: "Path to the file to read",
},
},
required: ["filePath"],
},
},
{
name: "install-package",
description: "Install a npm package in a project",
inputSchema: {
type: "object",
properties: {
packageName: {
type: "string",
description:
"Name of the package to install (can include version)",
},
directory: {
type: "string",
description:
"Directory of the project (defaults to current directory)",
},
dev: {
type: "boolean",
description: "Whether to install as a dev dependency",
},
},
required: ["packageName"],
},
},
],
};
logToFile({ event: "list_tools", response }, "json");
return response;
});
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request, context) => {
const { name, arguments: args } = request.params;
logToFile({ event: "call_tool", name, args }, "json");
try {
let result;
switch (name) {
case "create-react-app":
const createArgs = CreateReactAppSchema.parse(args);
result = await handleCreateReactApp(createArgs);
break;
case "run-react-app":
const runArgs = RunReactAppSchema.parse(args);
result = await handleRunReactApp(runArgs);
break;
case "run-command":
const commandArgs = RunCommandSchema.parse(args);
result = await handleRunCommand(commandArgs);
break;
case "get-process-output":
const outputArgs = GetProcessOutputSchema.parse(args);
result = await handleGetProcessOutput(outputArgs);
break;
case "stop-process":
const stopArgs = StopProcessSchema.parse(args);
result = await handleStopProcess(stopArgs);
break;
case "list-processes":
result = await handleListProcesses();
break;
case "edit-file":
const editArgs = EditFileSchema.parse(args);
result = await handleEditFile(editArgs);
break;
case "read-file":
const readArgs = ReadFileSchema.parse(args);
result = await handleReadFile(readArgs);
break;
case "install-package":
const installArgs = InstallPackageSchema.parse(args);
result = await handleInstallPackage(installArgs);
break;
default:
throw new Error(`Unknown tool: ${name}`);
}
return createTextResponse(JSON.stringify(result, null, 2));
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(
`Invalid arguments: ${error.errors
.map((e) => `${e.path.join(".")}: ${e.message}`)
.join(", ")}`
);
}
throw error;
}
});
// Start the server
const transport = new StdioServerTransport();
server.connect(transport).then(() => {
console.error("React MCP Server running on stdio");
});
const createTextResponse = (text) => ({
content: [{ type: "text", text }],
});
// Clean up processes on exit
process.on("exit", () => {
for (const [processId, processInfo] of runningProcesses.entries()) {
try {
processInfo.process.kill();
} catch (error) {
console.error(`Failed to kill process ${processId}:`, error);
}
}
});
process.on("SIGINT", () => {
process.exit(0);
});
process.on("SIGTERM", () => {
process.exit(0);
});
```