project_generator.py•5.88 kB
#!/usr/bin/env python3
"""
Generates boilerplate code and file structures for new projects.
"""
import os
from typing import Dict, List, TypedDict, NotRequired
# --- Project Templates ---
TEMPLATES: Dict[str, Dict[str, str]] = {
"fastapi": {
"main.py": """
from fastapi import FastAPI
app = FastAPI(
title="My FastAPI Project",
description="A new project generated by MCP.",
version="0.1.0",
)
@app.get("/")
async def read_root():
return {"message": "Hello, World!"}
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: str | None = None):
return {"item_id": item_id, "q": q}
""",
"pyproject.toml": """
[project]
name = "PROJECT_NAME_PLACEHOLDER"
version = "0.1.0"
description = "A new FastAPI project."
authors = [{ name = "Your Name", email = "you@example.com" }]
requires-python = ">=3.12"
dependencies = [
"fastapi",
"uvicorn[standard]",
]
[project.optional-dependencies]
dev = ["pytest"]
""",
"README.md": """
# PROJECT_NAME_PLACEHOLDER
A new FastAPI project generated by Documentation Search Enhanced MCP.
## To run:
1. `uv pip sync`
2. `uv run uvicorn main:app --reload`
## To test:
`uv run pytest`
""",
".gitignore": """
__pycache__/
*.pyc
.env
.venv/
dist/
build/
*.egg-info
""",
"tests/test_main.py": """
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_read_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Hello, World!"}
""",
},
"react-vite": {
"index.html": """
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PROJECT_NAME_PLACEHOLDER</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
""",
"package.json": """
{{
"name": "PROJECT_NAME_PLACEHOLDER",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {{
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
}},
"dependencies": {{
"react": "^18.2.0",
"react-dom": "^18.2.0"
}},
"devDependencies": {{
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@vitejs/plugin-react": "^4.0.3",
"eslint": "^8.45.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"vite": "^4.4.5"
}}
}}
""",
"vite.config.js": """
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})
""",
".gitignore": """
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Dependency directories
node_modules/
dist/
# IDE files
.idea/
.vscode/
# Environment variables
.env
.env.local
""",
"src/App.jsx": """
import './App.css'
function App() {
return (
<>
<h1>PROJECT_NAME_PLACEHOLDER</h1>
<p className="read-the-docs">
React + Vite project generated by MCP.
</p>
</>
)
}
export default App
""",
"src/main.jsx": """
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
""",
"src/index.css": """
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
}
""",
"src/App.css": """
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
""",
},
}
class ProjectCreationSummary(TypedDict):
project_name: str
template_used: str
project_path: str
directories_created: List[str]
files_created: List[str]
user_summary: NotRequired[str]
def generate_project(
project_name: str, template_name: str, base_path: str = "."
) -> ProjectCreationSummary:
"""
Generates a new project from a template.
Args:
project_name: The name of the new project (will be created as a directory).
template_name: The name of the template to use (e.g., 'fastapi').
base_path: The path where the project directory will be created.
Returns:
A dictionary summarizing the created files and directories.
"""
if template_name not in TEMPLATES:
raise ValueError(
f"Template '{template_name}' not found. Available templates: {list(TEMPLATES.keys())}"
)
project_path = os.path.join(base_path, project_name)
if os.path.exists(project_path):
raise FileExistsError(f"Directory '{project_path}' already exists.")
os.makedirs(project_path)
template = TEMPLATES[template_name]
created_files = []
created_dirs = {project_path}
for file_path, content in template.items():
# Handle nested directories
full_path = os.path.join(project_path, file_path)
dir_name = os.path.dirname(full_path)
if not os.path.exists(dir_name):
os.makedirs(dir_name)
created_dirs.add(dir_name)
# Replace project name placeholder
formatted_content = content.replace("PROJECT_NAME_PLACEHOLDER", project_name)
with open(full_path, "w", encoding="utf-8") as f:
f.write(formatted_content.strip())
created_files.append(full_path)
return {
"project_name": project_name,
"template_used": template_name,
"project_path": project_path,
"directories_created": sorted(list(created_dirs)),
"files_created": sorted(created_files),
}