MCP Development Server
by dillip285
- src
- mcp_dev_server
- project_manager
"""Project representation and management."""
import uuid
from typing import Dict, Any, Optional, List
from pathlib import Path
import git
from pydantic import BaseModel
class ProjectConfig(BaseModel):
"""Project configuration model."""
name: str
template: str
description: str = ""
version: str = "0.1.0"
class ProjectState:
"""Project state tracking."""
def __init__(self):
"""Initialize project state."""
self.git_initialized: bool = False
self.last_build: Optional[Dict[str, Any]] = None
self.last_test_run: Optional[Dict[str, Any]] = None
self.active_environments: List[str] = []
class Project:
"""Project instance representation."""
def __init__(self, path: str, config: ProjectConfig, state: ProjectState):
"""Initialize project instance.
Args:
path: Project directory path
config: Project configuration
state: Project state
"""
self.id = str(uuid.uuid4())
self.path = path
self.config = config
self.state = state
def get_structure(self) -> Dict[str, Any]:
"""Get project directory structure.
Returns:
Dict[str, Any]: Directory structure
"""
def scan_dir(path: Path) -> Dict[str, Any]:
structure = {}
for item in path.iterdir():
if item.name.startswith("."):
continue
if item.is_file():
structure[item.name] = "file"
elif item.is_dir():
structure[item.name] = scan_dir(item)
return structure
return scan_dir(Path(self.path))
def get_git_status(self) -> Dict[str, Any]:
"""Get Git repository status.
Returns:
Dict[str, Any]: Git status information
"""
if not self.state.git_initialized:
return {"initialized": False}
try:
repo = git.Repo(self.path)
return {
"initialized": True,
"branch": repo.active_branch.name,
"changed_files": [item.a_path for item in repo.index.diff(None)],
"untracked_files": repo.untracked_files,
"ahead": sum(1 for c in repo.iter_commits("origin/main..main")),
"behind": sum(1 for c in repo.iter_commits("main..origin/main"))
}
except Exception as e:
return {
"initialized": False,
"error": str(e)
}
async def create_git_commit(self, message: str, files: Optional[List[str]] = None) -> Dict[str, Any]:
"""Create a Git commit.
Args:
message: Commit message
files: Optional list of files to commit
Returns:
Dict[str, Any]: Commit information
"""
if not self.state.git_initialized:
raise ValueError("Git is not initialized for this project")
try:
repo = git.Repo(self.path)
if files:
repo.index.add(files)
else:
repo.index.add("*")
commit = repo.index.commit(message)
return {
"commit_id": commit.hexsha,
"message": message,
"author": str(commit.author),
"files": [item.a_path for item in commit.stats.files]
}
except Exception as e:
raise ValueError(f"Failed to create commit: {str(e)}")
def get_dependencies(self) -> Dict[str, Any]:
"""Get project dependencies.
Returns:
Dict[str, Any]: Dependency information
"""
dependencies = {}
# Check Python dependencies
req_file = Path(self.path) / "requirements.txt"
if req_file.exists():
with open(req_file, "r") as f:
dependencies["python"] = f.read().splitlines()
# Check Node.js dependencies
package_file = Path(self.path) / "package.json"
if package_file.exists():
import json
with open(package_file, "r") as f:
package_data = json.load(f)
dependencies["node"] = {
"dependencies": package_data.get("dependencies", {}),
"devDependencies": package_data.get("devDependencies", {})
}
return dependencies
def analyze_code(self) -> Dict[str, Any]:
"""Analyze project code.
Returns:
Dict[str, Any]: Code analysis results
"""
analysis = {
"files": {},
"summary": {
"total_files": 0,
"total_lines": 0,
"code_lines": 0,
"comment_lines": 0,
"blank_lines": 0
}
}
def analyze_file(path: Path) -> Dict[str, Any]:
with open(path, "r", encoding="utf-8") as f:
lines = f.readlines()
total_lines = len(lines)
blank_lines = sum(1 for line in lines if not line.strip())
comment_lines = sum(1 for line in lines if line.strip().startswith("#"))
code_lines = total_lines - blank_lines - comment_lines
return {
"total_lines": total_lines,
"code_lines": code_lines,
"comment_lines": comment_lines,
"blank_lines": blank_lines
}
for root, _, files in os.walk(self.path):
for file in files:
if file.endswith(".py"):
file_path = Path(root) / file
try:
file_analysis = analyze_file(file_path)
relative_path = str(file_path.relative_to(self.path))
analysis["files"][relative_path] = file_analysis
# Update summary
for key in ["total_lines", "code_lines", "comment_lines", "blank_lines"]:
analysis["summary"][key] += file_analysis[key]
analysis["summary"]["total_files"] += 1
except Exception:
continue
return analysis
def get_test_coverage(self) -> Dict[str, Any]:
"""Get test coverage information.
Returns:
Dict[str, Any]: Test coverage data
"""
try:
import coverage
cov = coverage.Coverage()
cov.load()
return {
"total_coverage": cov.report(),
"missing_lines": dict(cov.analysis2()),
"branch_coverage": cov.get_option("branch"),
"excluded_lines": cov.get_exclude_list()
}
except Exception:
return {
"error": "Coverage data not available"
}
def get_ci_config(self) -> Dict[str, Any]:
"""Get CI configuration.
Returns:
Dict[str, Any]: CI configuration data
"""
ci_configs = {}
# Check GitHub Actions
github_dir = Path(self.path) / ".github" / "workflows"
if github_dir.exists():
ci_configs["github_actions"] = []
for workflow in github_dir.glob("*.yml"):
with open(workflow, "r") as f:
ci_configs["github_actions"].append({
"name": workflow.stem,
"config": f.read()
})
# Check GitLab CI
gitlab_file = Path(self.path) / ".gitlab-ci.yml"
if gitlab_file.exists():
with open(gitlab_file, "r") as f:
ci_configs["gitlab"] = f.read()
return ci_configs
async def cleanup(self):
"""Clean up project resources."""
# Implementation will depend on what resources need cleanup
pass