#!/usr/bin/env node
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 { readdir, readFile } from "fs/promises";
import { join, parse } from "path";
const README_TEMPLATE = {
sections: [
{
name: "Project Title",
description: "The main title/name of the project",
required: true,
},
{
name: "Description",
description: "A brief overview of what the project does",
required: true,
},
{
name: "Features",
description: "Key features and capabilities of the project",
required: false,
},
{
name: "Installation",
description: "Steps to install and set up the project",
required: true,
},
{
name: "Usage",
description: "How to use the project, including examples",
required: true,
},
{
name: "Project Structure",
description: "Directory/file structure overview",
required: false,
},
{
name: "Technologies Used",
description: "List of technologies, frameworks, and tools used",
required: false,
},
{
name: "Configuration",
description: "Configuration options, environment variables, etc.",
required: false,
},
{
name: "Contributing",
description: "Guidelines for contributing to the project",
required: false,
},
{
name: "License",
description: "License information",
required: false,
},
],
};
// Helper function to read directory structure recursively
async function getDirectoryStructure(
dirPath: string,
maxDepth: number = 3,
currentDepth: number = 0,
ignorePatterns: string[] = [
"node_modules",
".git",
"dist",
"build",
".next",
"coverage",
],
): Promise<any> {
if (currentDepth >= maxDepth) {
return null;
}
try {
const entries = await readdir(dirPath, { withFileTypes: true });
const structure: any = {
type: "directory",
name: parse(dirPath).base || dirPath,
children: [],
};
for (const entry of entries) {
if (ignorePatterns.some((pattern) => entry.name.includes(pattern))) {
continue;
}
const fullPath = join(dirPath, entry.name);
if (entry.isDirectory()) {
const subStructure = await getDirectoryStructure(
fullPath,
maxDepth,
currentDepth + 1,
ignorePatterns,
);
if (subStructure) {
structure.children.push(subStructure);
}
} else {
structure.children.push({
type: "file",
name: entry.name,
path: fullPath,
});
}
}
return structure;
} catch (error) {
throw new Error(`Failed to read directory: ${error}`);
}
}
// Helper function to analyze project and return structured data
async function analyzeProject(projectPath: string): Promise<any> {
try {
const structure = await getDirectoryStructure(projectPath);
let packageJson: any = null;
try {
const packagePath = join(projectPath, "package.json");
const packageContent = await readFile(packagePath, "utf-8");
packageJson = JSON.parse(packageContent);
} catch {
// package.json might not exist
}
const files = await readdir(projectPath);
const detectedTechnologies: string[] = [];
const configFiles: string[] = [];
if (files.includes("package.json")) {
detectedTechnologies.push("Node.js");
configFiles.push("package.json");
}
if (files.includes("tsconfig.json")) {
detectedTechnologies.push("TypeScript");
configFiles.push("tsconfig.json");
}
if (files.includes("requirements.txt")) {
detectedTechnologies.push("Python");
configFiles.push("requirements.txt");
}
if (files.includes("setup.py")) {
detectedTechnologies.push("Python");
configFiles.push("setup.py");
}
if (files.includes("Cargo.toml")) {
detectedTechnologies.push("Rust");
configFiles.push("Cargo.toml");
}
if (files.includes("go.mod")) {
detectedTechnologies.push("Go");
configFiles.push("go.mod");
}
if (files.includes("pom.xml")) {
detectedTechnologies.push("Java");
configFiles.push("pom.xml");
}
if (files.includes("build.gradle")) {
detectedTechnologies.push("Java/Gradle");
configFiles.push("build.gradle");
}
if (files.includes("Dockerfile")) {
detectedTechnologies.push("Docker");
configFiles.push("Dockerfile");
}
if (files.includes(".env.example") || files.includes(".env.template")) {
configFiles.push(
files.find((f) => f === ".env.example") || ".env.template",
);
}
const structureString = formatDirectoryStructure(structure, 0);
return {
template: README_TEMPLATE,
projectData: {
projectName: packageJson?.name || parse(projectPath).base,
description: packageJson?.description || null,
version: packageJson?.version || null,
author: packageJson?.author || null,
license: packageJson?.license || null,
homepage: packageJson?.homepage || null,
repository: packageJson?.repository || null,
scripts: packageJson?.scripts || {},
dependencies: packageJson?.dependencies
? Object.keys(packageJson.dependencies)
: [],
devDependencies: packageJson?.devDependencies
? Object.keys(packageJson.devDependencies)
: [],
detectedTechnologies,
configFiles,
directoryStructure: structure,
directoryStructureFormatted: structureString,
rootFiles: files,
},
};
} catch (error) {
throw new Error(`Failed to analyze project: ${error}`);
}
}
function formatDirectoryStructure(node: any, depth: number): string {
const indent = " ".repeat(depth);
let result = "";
if (node.type === "directory") {
result += `${indent}${node.name}/\n`;
if (node.children) {
for (const child of node.children) {
result += formatDirectoryStructure(child, depth + 1);
}
}
} else {
result += `${indent}${node.name}\n`;
}
return result;
}
// Helper function to generate a visually appealing README
function generateReadme(projectData: any): string {
const {
projectName,
description,
version,
author,
license,
homepage,
repository,
scripts,
dependencies,
devDependencies,
detectedTechnologies,
configFiles,
directoryStructureFormatted,
} = projectData;
let readme = "";
readme += `# ${projectName}\n\n`;
const badges: string[] = [];
if (version)
badges.push(
``,
);
if (license)
badges.push(
``,
);
if (detectedTechnologies.includes("Node.js"))
badges.push(
``,
);
if (detectedTechnologies.includes("TypeScript"))
badges.push(
``,
);
if (detectedTechnologies.includes("Python"))
badges.push(``);
if (detectedTechnologies.includes("Rust"))
badges.push(``);
if (detectedTechnologies.includes("Go"))
badges.push(``);
if (badges.length > 0) {
readme += badges.join(" ") + "\n\n";
}
if (description) {
readme += `## 📝 Description\n\n${description}\n\n`;
}
if (detectedTechnologies.length > 0) {
readme += `## 🛠️ Technologies Used\n\n`;
detectedTechnologies.forEach((tech: string) => {
readme += `- ${tech}\n`;
});
readme += `\n`;
}
readme += `## 📦 Installation\n\n`;
if (dependencies.length > 0 || devDependencies.length > 0) {
readme += `\`\`\`bash\n`;
if (detectedTechnologies.includes("Node.js")) {
readme += `npm install\n`;
} else if (detectedTechnologies.includes("Python")) {
readme += `pip install -r requirements.txt\n`;
} else if (detectedTechnologies.includes("Rust")) {
readme += `cargo build\n`;
} else if (detectedTechnologies.includes("Go")) {
readme += `go mod download\n`;
}
readme += `\`\`\`\n\n`;
} else {
readme += `Clone the repository and follow the setup instructions.\n\n`;
}
const scriptKeys = Object.keys(scripts);
if (scriptKeys.length > 0) {
readme += `## 🚀 Usage\n\n`;
readme += `Available scripts:\n\n`;
scriptKeys.forEach((script) => {
readme += `\`\`\`bash\nnpm run ${script}\n\`\`\`\n`;
readme += `${scripts[script]}\n\n`;
});
}
if (directoryStructureFormatted) {
readme += `## 📁 Project Structure\n\n`;
readme += `\`\`\`\n${directoryStructureFormatted}\`\`\`\n\n`;
}
if (dependencies.length > 0) {
readme += `## 📚 Dependencies\n\n`;
dependencies.forEach((dep: string) => {
readme += `- ${dep}\n`;
});
readme += `\n`;
}
if (devDependencies.length > 0) {
readme += `## 🔧 Dev Dependencies\n\n`;
devDependencies.forEach((dep: string) => {
readme += `- ${dep}\n`;
});
readme += `\n`;
}
if (configFiles) {
readme += `## 📝 Config-files\n\n${configFiles}\n\n`;
}
if (license) {
readme += `## 📄 License\n\n`;
readme += `This project is licensed under the ${license} License.\n\n`;
}
if (author) {
readme += `## 👤 Author\n\n`;
readme += `${typeof author === "string" ? author : JSON.stringify(author)}\n\n`;
}
if (homepage || repository) {
readme += `## 🔗 Links\n\n`;
if (homepage) readme += `- [Homepage](${homepage})\n`;
if (repository) {
const repoUrl =
typeof repository === "string" ? repository : repository.url;
readme += `- [Repository](${repoUrl})\n`;
}
readme += `\n`;
}
readme += `---\n\n`;
readme += `*Generated with ❤️ by README Generator MCP Server*\n`;
return readme;
}
const server = new Server(
{
name: "readme-generator-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
},
);
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "read_project_structure",
description:
"Read the directory structure of a project. Returns a tree-like structure of files and folders.",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "The absolute path to the project directory",
},
maxDepth: {
type: "number",
description: "Maximum depth to traverse (default: 3)",
default: 3,
},
},
required: ["path"],
},
},
{
name: "read_file",
description: "Read the contents of a file",
inputSchema: {
type: "object",
properties: {
path: {
type: "string",
description: "The absolute path to the file to read",
},
},
required: ["path"],
},
},
{
name: "analyze_project",
description:
"Analyze a project directory and return structured data about the project along with a README template. " +
"Returns: (1) A template structure with recommended README sections (some required, some optional), " +
"and (2) Detailed project analysis including detected technologies, package.json data, directory structure, scripts, dependencies, and configuration files. " +
"The LLM should use this information to construct a comprehensive README following the template structure as a guide, " +
"adapting sections based on what's relevant for the specific project.",
inputSchema: {
type: "object",
properties: {
projectPath: {
type: "string",
description: "The absolute path to the project directory",
},
},
required: ["projectPath"],
},
},
{
name: "generate_readme",
description:
"Generate a well-formatted, visually appealing README.md file for a project. " +
"This tool analyzes the project directory and automatically creates a comprehensive README with: " +
"badges, emojis, proper sections (description, installation, usage, project structure, dependencies, etc.), " +
"code blocks, and professional formatting. The generated README is ready to use and follows best practices.",
inputSchema: {
type: "object",
properties: {
projectPath: {
type: "string",
description: "The absolute path to the project directory",
},
},
required: ["projectPath"],
},
},
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
const { name, arguments: args } = request.params;
switch (name) {
case "read_project_structure": {
const { path, maxDepth = 3 } = args as {
path: string;
maxDepth?: number;
};
const structure = await getDirectoryStructure(path, maxDepth);
return {
content: [
{
type: "text",
text: JSON.stringify(structure, null, 2),
},
],
};
}
case "read_file": {
const { path } = args as { path: string };
const content = await readFile(path, "utf-8");
return {
content: [
{
type: "text",
text: content,
},
],
};
}
case "analyze_project": {
const { projectPath } = args as { projectPath: string };
const analysis = await analyzeProject(projectPath);
return {
content: [
{
type: "text",
text: JSON.stringify(analysis, null, 2),
},
],
};
}
case "generate_readme": {
const { projectPath } = args as { projectPath: string };
const analysis = await analyzeProject(projectPath);
const readme = generateReadme(analysis.projectData);
return {
content: [
{
type: "text",
text: readme,
},
],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("README Generator MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});