This commit is contained in:
Varun 2025-07-15 14:32:59 +05:30
commit 4724d04367
14 changed files with 2204 additions and 0 deletions

42
Dockerfile Normal file
View File

@ -0,0 +1,42 @@
FROM ubuntu:24.04
LABEL Name="mcp-server" Version="1.0"
# Set non-interactive mode for apt
ENV DEBIAN_FRONTEND=noninteractive
# Install system tools including Node.js
RUN apt-get update && \
apt-get install -y \
python3 python3-pip python3-venv python3-full \
git curl ca-certificates unzip wget \
make gcc g++ && \
# Install Node.js 20.x
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get install -y nodejs && \
rm -rf /var/lib/apt/lists/*
# Create and activate virtual environment
RUN python3 -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
# Install Node.js tools
RUN npm install -g node-gyp @google/gemini-cli && \
# Verify installation
which gemini || ln -s /usr/local/lib/node_modules/@google/gemini-cli/bin/gemini.js /usr/local/bin/gemini
# Workdir setup
WORKDIR /app
# Install Python dependencies
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
# Copy project files
COPY . .
# Expose port
EXPOSE 8000
# Entrypoint
CMD ["python3", "main.py"]

233
README.md Normal file
View File

@ -0,0 +1,233 @@
# MCP Server - AI-Powered Code Editor
A comprehensive server that automatically clones Gitea repositories, analyzes code with AI models (Gemini/OpenAI), applies intelligent code changes, and commits them back to the repository.
## 🚀 Features
- **Repository Management**: Clone repositories from Gitea with authentication
- **AI-Powered Analysis**: Use Gemini CLI or OpenAI to analyze and edit code
- **Model Selection**: Choose specific AI models (e.g., gemini-1.5-pro, gpt-4)
- **Real-time Progress Tracking**: Web interface with live status updates
- **Modern UI**: Beautiful, responsive frontend with progress indicators
- **Background Processing**: Asynchronous task processing with status monitoring
- **Comprehensive Logging**: Full logging to both console and file
- **Docker Support**: Easy deployment with Docker and docker-compose
## 📋 Prerequisites
- Python 3.8+
- Git
- API keys for AI models (Gemini or OpenAI)
## 🛠️ Installation
### Option 1: Docker (Recommended)
1. **Clone the repository**
```bash
git clone <your-repo-url>
cd mcp-server
```
2. **Build and run with Docker Compose**
```bash
docker-compose up --build
```
3. **Or build and run manually**
```bash
docker build -t mcp-server .
docker run -p 8000:8000 mcp-server
```
### Option 2: Local Installation
1. **Clone the repository**
```bash
git clone <your-repo-url>
cd mcp-server
```
2. **Install Python dependencies**
```bash
pip install -r requirements.txt
```
3. **Install Gemini CLI (if using Gemini)**
```bash
# Download from GitHub releases
curl -L https://github.com/google/generative-ai-go/releases/latest/download/gemini-linux-amd64 -o /usr/local/bin/gemini
chmod +x /usr/local/bin/gemini
```
4. **Start the server**
```bash
python main.py
# or
python start.py
```
## 🚀 Usage
### Using the Web Interface
1. Open your browser and navigate to `http://localhost:8000`
2. Fill in the repository details:
- **Gitea Repository URL**: Your repository URL (e.g., `http://157.66.191.31:3000/user/repo.git`)
- **Gitea Token**: Your Gitea access token (get from Settings → Applications → Generate new token)
- **AI Model**: Choose between Gemini CLI or OpenAI
- **Model Name**: Specify the exact model (e.g., `gemini-1.5-pro`, `gpt-4`)
- **API Key**: Your AI model API key
- **Prompt**: Describe what changes you want to make to the code
3. Click "Process Repository" and monitor the progress
### API Endpoints
- `GET /` - Web interface
- `POST /process` - Start repository processing
- `GET /status/{task_id}` - Get processing status
- `GET /health` - Health check
## 🔧 Configuration
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `HOST` | Server host | `0.0.0.0` |
| `PORT` | Server port | `8000` |
### Supported AI Models
**Gemini Models:**
- `gemini-1.5-pro` (recommended)
- `gemini-1.5-flash`
- `gemini-1.0-pro`
**OpenAI Models:**
- `gpt-4`
- `gpt-4-turbo`
- `gpt-3.5-turbo`
### Supported File Types
The system analyzes and can modify:
- Python (`.py`)
- JavaScript (`.js`, `.jsx`)
- TypeScript (`.ts`, `.tsx`)
- HTML (`.html`)
- CSS (`.css`)
- JSON (`.json`)
- Markdown (`.md`)
## 📁 Project Structure
```
mcp-server/
├── main.py # FastAPI application
├── requirements.txt # Python dependencies
├── Dockerfile # Docker configuration
├── docker-compose.yml # Docker Compose configuration
├── README.md # This file
├── templates/
│ └── index.html # Frontend template
├── static/
│ ├── style.css # Frontend styles
│ └── script.js # Frontend JavaScript
└── logs/ # Log files (created by Docker)
```
## 🔄 How It Works
1. **Repository Cloning**: Authenticates with Gitea and clones the repository
2. **AI Analysis**: Sends code and prompt to selected AI model
3. **Code Modification**: Applies AI-suggested changes to the codebase
4. **Commit & Push**: Commits changes and pushes back to Gitea
## 🎯 Example Prompts
- "Add error handling to all API endpoints"
- "Optimize database queries for better performance"
- "Add comprehensive logging throughout the application"
- "Refactor the authentication system to use JWT tokens"
- "Add unit tests for all utility functions"
## 📊 Logging
The server provides comprehensive logging:
- **Console Output**: Real-time logs in the terminal
- **File Logging**: Logs saved to `mcp_server.log`
- **Task-specific Logging**: Each task has detailed logging with task ID
### Viewing Logs
**Docker:**
```bash
# View container logs
docker logs <container_id>
# Follow logs in real-time
docker logs -f <container_id>
```
**Local:**
```bash
# View log file
tail -f mcp_server.log
```
## 🔒 Security Considerations
- API keys are sent from frontend and not stored
- Use HTTPS in production
- Implement proper authentication for the web interface
- Regularly update dependencies
- Monitor API usage and costs
## 🐛 Troubleshooting
### Common Issues
1. **Repository cloning fails**
- Verify Gitea token is valid and has repository access
- Check repository URL format
- Ensure repository exists and is accessible
- Make sure token has appropriate permissions (read/write)
2. **AI model errors**
- Verify API keys are correct
- Check model name spelling
- Ensure internet connectivity
3. **Gemini CLI not found**
- Install Gemini CLI: `curl -L https://github.com/google/generative-ai-go/releases/latest/download/gemini-linux-amd64 -o /usr/local/bin/gemini && chmod +x /usr/local/bin/gemini`
### Logs
Check the logs for detailed error messages and processing status:
- **Frontend**: Real-time logs in the web interface
- **Backend**: Console and file logs with detailed information
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests if applicable
5. Submit a pull request
## 📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
## 🆘 Support
For issues and questions:
1. Check the troubleshooting section
2. Review the logs in the web interface and console
3. Create an issue in the repository
---
**Note**: This tool modifies code automatically. Always review changes before deploying to production environments.

47
create_gitea_token.md Normal file
View File

@ -0,0 +1,47 @@
# How to Create a Gitea Token
## Step-by-Step Guide
### 1. Log into your Gitea instance
- Go to your Gitea URL (e.g., `http://157.66.191.31:3000`)
- Log in with your username and password
### 2. Navigate to Settings
- Click on your profile picture in the top-right corner
- Select "Settings" from the dropdown menu
### 3. Go to Applications
- In the left sidebar, click on "Applications"
- Look for "Manage Access Tokens" or "Tokens"
### 4. Generate New Token
- Click "Generate new token"
- Fill in the form:
- **Token Name**: Give it a descriptive name (e.g., "MCP Server")
- **Scopes**: Select the following permissions:
- ✅ `repo` (Full control of private repositories)
- ✅ `write:packages` (Write packages)
- ✅ `read:packages` (Read packages)
### 5. Copy the Token
- Click "Generate token"
- **IMPORTANT**: Copy the token immediately - you won't be able to see it again!
- The token will look like: `37c322628fa57b0ec7b481c8655ae2bebd486f6f`
### 6. Use in MCP Server
- Paste the token in the "Gitea Token" field in the MCP Server interface
- The token is pre-filled with the example token for testing
## Security Notes
- **Never share your token** - it provides access to your repositories
- **Store tokens securely** - don't commit them to version control
- **Use different tokens** for different applications
- **Revoke tokens** when no longer needed
## Example Token (for testing)
```
37c322628fa57b0ec7b481c8655ae2bebd486f6f
```
This token is already pre-filled in the MCP Server interface for convenience.

22
docker-compose.yml Normal file
View File

@ -0,0 +1,22 @@
version: '3.8'
services:
mcp-server:
build: .
ports:
- "8000:8000"
environment:
- HOST=0.0.0.0
- PORT=8000
volumes:
- ./logs:/app/logs
- mcp-data:/app/data
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
volumes:
mcp-data:

20
env.example Normal file
View File

@ -0,0 +1,20 @@
# MCP Server Environment Configuration
# AI Model API Keys
# Get your Gemini API key from: https://makersuite.google.com/app/apikey
GEMINI_API_KEY=your_gemini_api_key_here
# Get your OpenAI API key from: https://platform.openai.com/api-keys
OPENAI_API_KEY=your_openai_api_key_here
# Server Configuration
HOST=0.0.0.0
PORT=8000
# Gitea Configuration (default values)
DEFAULT_GITEA_URL=http://157.66.191.31:3000
DEFAULT_USERNAME=risadmin_prod
DEFAULT_PASSWORD=adminprod1234
# Logging
LOG_LEVEL=INFO

390
main.py Normal file
View File

@ -0,0 +1,390 @@
import os
import shutil
import subprocess
import tempfile
import asyncio
import logging
from pathlib import Path
from typing import Optional, Dict, Any
import json
from fastapi import FastAPI, HTTPException, BackgroundTasks, Request
from fastapi.staticfiles import StaticFiles
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel
import git
import requests
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(),
logging.FileHandler('mcp_server.log')
]
)
logger = logging.getLogger(__name__)
app = FastAPI(title="MCP Server", description="AI-powered code editing server")
# Mount static files and templates
app.mount("/static", StaticFiles(directory="static"), name="static")
templates = Jinja2Templates(directory="templates")
# Models
class GiteaRequest(BaseModel):
repo_url: str
token: str # Gitea token instead of username/password
prompt: str
ai_model: str = "gemini" # gemini or openai
model_name: str = "gemini-1.5-pro" # specific model name
api_key: str # API key from frontend
class ProcessResponse(BaseModel):
task_id: str
status: str
message: str
# Global storage for task status
task_status = {}
class MCPServer:
def __init__(self):
self.repo_path = None
async def process_repository(self, task_id: str, request: GiteaRequest):
"""Main processing function"""
try:
logger.info(f"Task {task_id}: Starting process...")
task_status[task_id] = {"status": "processing", "message": "Starting process..."}
# Step 1: Clone repository
await self._clone_repository(task_id, request)
# Step 2: Analyze code with AI
await self._analyze_with_ai(task_id, request)
# Step 3: Commit and push changes
await self._commit_and_push(task_id, request)
logger.info(f"Task {task_id}: Successfully processed repository")
task_status[task_id] = {"status": "completed", "message": "Successfully processed repository"}
except Exception as e:
logger.error(f"Task {task_id}: Error - {str(e)}")
task_status[task_id] = {"status": "error", "message": str(e)}
# Do not delete the repo directory; keep for inspection
async def _clone_repository(self, task_id: str, request: GiteaRequest):
"""Clone repository from Gitea into a persistent directory"""
logger.info(f"Task {task_id}: Cloning repository...")
task_status[task_id] = {"status": "processing", "message": "Cloning repository..."}
# Extract repo name from URL
repo_name = request.repo_url.split('/')[-1].replace('.git', '')
# Persistent directory under /app/data
data_dir = "/app/data"
os.makedirs(data_dir, exist_ok=True)
self.repo_path = os.path.join(data_dir, f"{repo_name}_{task_id}")
try:
os.chmod(data_dir, 0o777) # Give full permissions to the data dir
logger.info(f"Task {task_id}: Created/using data directory: {self.repo_path}")
except Exception as e:
logger.warning(f"Task {task_id}: Could not set permissions on data dir: {e}")
# Clone repository using git command with credentials
try:
# Use git command with credentials in URL
auth_url = request.repo_url.replace('://', f'://{request.token}@')
result = subprocess.run(
['git', 'clone', auth_url, self.repo_path],
capture_output=True,
text=True,
timeout=300 # 5 minutes timeout
)
if result.returncode != 0:
logger.error(f"Task {task_id}: Git clone error - {result.stderr}")
raise Exception(f"Failed to clone repository: {result.stderr}")
logger.info(f"Task {task_id}: Successfully cloned repository to {self.repo_path}")
except subprocess.TimeoutExpired:
raise Exception("Repository cloning timed out after 5 minutes")
except Exception as e:
raise Exception(f"Failed to clone repository: {str(e)}")
async def _analyze_with_ai(self, task_id: str, request: GiteaRequest):
"""Analyze code with AI model and apply changes"""
logger.info(f"Task {task_id}: Analyzing code with AI...")
task_status[task_id] = {"status": "processing", "message": "Analyzing code with AI..."}
if request.ai_model == "gemini":
await self._use_gemini_cli(task_id, request.prompt, request.api_key, request.model_name)
elif request.ai_model == "openai":
await self._use_openai_ai(task_id, request.prompt, request.api_key, request.model_name)
else:
raise Exception(f"Unsupported AI model: {request.ai_model}")
async def _use_gemini_cli(self, task_id: str, prompt: str, api_key: str, model_name: str):
"""Use Gemini CLI for code analysis and editing"""
try:
# Check if Gemini CLI is installed
try:
subprocess.run(["gemini", "--version"], check=True, capture_output=True)
logger.info(f"Task {task_id}: Gemini CLI is available")
except (subprocess.CalledProcessError, FileNotFoundError):
raise Exception("Gemini CLI is not installed. Please install it first: https://github.com/google/generative-ai-go/tree/main/cmd/gemini")
# Read all code files
code_content = self._read_code_files()
logger.info(f"Task {task_id}: Read {len(code_content)} characters of code content")
# Create AI prompt
ai_prompt = f"""
Analyze the following codebase and make the requested changes:
USER REQUEST: {prompt}
CODEBASE:
{code_content}
Please provide:
1. A summary of what changes need to be made
2. The specific file changes in the format:
FILE: filename.py
CHANGES:
[describe changes or provide new code]
Be specific about which files to modify and what changes to make.
"""
# Set API key as environment variable for Gemini CLI
env = os.environ.copy()
env['GEMINI_API_KEY'] = api_key
logger.info(f"Task {task_id}: Calling Gemini CLI with model: {model_name}")
# Call Gemini CLI with specific model, passing prompt via stdin
result = subprocess.run(
["gemini", "generate", "--model", model_name],
input=ai_prompt,
capture_output=True,
text=True,
env=env,
cwd=self.repo_path,
timeout=600 # 10 minutes timeout
)
if result.returncode != 0:
logger.error(f"Task {task_id}: Gemini CLI error - {result.stderr}")
raise Exception(f"Gemini CLI error: {result.stderr}")
logger.info(f"Task {task_id}: Gemini CLI response received ({len(result.stdout)} characters)")
logger.info(f"Task {task_id}: Gemini CLI raw response:\n{result.stdout}")
# Store the raw AI response for frontend display
task_status[task_id]["ai_response"] = result.stdout
# Parse and apply changes
await self._apply_ai_changes(result.stdout, task_id)
except subprocess.TimeoutExpired:
raise Exception("Gemini CLI request timed out after 10 minutes")
except Exception as e:
raise Exception(f"Gemini CLI error: {str(e)}")
async def _use_openai_ai(self, task_id: str, prompt: str, api_key: str, model_name: str):
"""Use OpenAI for code analysis and editing"""
try:
from openai import OpenAI
# Configure OpenAI with API key from frontend
client = OpenAI(api_key=api_key)
# Read all code files
code_content = self._read_code_files()
logger.info(f"Task {task_id}: Read {len(code_content)} characters of code content")
# Create AI prompt
ai_prompt = f"""
Analyze the following codebase and make the requested changes:
USER REQUEST: {prompt}
CODEBASE:
{code_content}
Please provide:
1. A summary of what changes need to be made
2. The specific file changes in the format:
FILE: filename.py
CHANGES:
[describe changes or provide new code]
Be specific about which files to modify and what changes to make.
"""
logger.info(f"Task {task_id}: Calling OpenAI with model: {model_name}")
# Get AI response
response = client.chat.completions.create(
model=model_name,
messages=[
{"role": "system", "content": "You are a code analysis and editing assistant."},
{"role": "user", "content": ai_prompt}
]
)
logger.info(f"Task {task_id}: OpenAI response received")
# Parse and apply changes
await self._apply_ai_changes(response.choices[0].message.content, task_id)
except ImportError:
raise Exception("OpenAI library not installed. Run: pip install openai")
except Exception as e:
raise Exception(f"OpenAI error: {str(e)}")
def _read_code_files(self) -> str:
"""Read all code files in the repository"""
code_content = ""
file_count = 0
for root, dirs, files in os.walk(self.repo_path):
# Skip .git directory
if '.git' in dirs:
dirs.remove('.git')
for file in files:
if file.endswith(('.py', '.js', '.ts', '.jsx', '.tsx', '.html', '.css', '.json', '.md')):
file_path = os.path.join(root, file)
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
relative_path = os.path.relpath(file_path, self.repo_path)
code_content += f"\n\n=== {relative_path} ===\n{content}\n"
file_count += 1
except Exception as e:
logger.warning(f"Could not read {file_path}: {e}")
logger.info(f"Read {file_count} code files")
return code_content
async def _apply_ai_changes(self, ai_response: str, task_id: str):
"""Apply changes suggested by AI"""
logger.info(f"Task {task_id}: Applying AI suggestions...")
task_status[task_id] = {"status": "processing", "message": "Applying AI suggestions..."}
# Parse AI response for file changes
# This is a simplified parser - you might want to make it more robust
lines = ai_response.split('\n')
current_file = None
current_changes = []
files_modified = 0
for line in lines:
if line.startswith('FILE:'):
if current_file and current_changes:
await self._apply_file_changes(current_file, '\n'.join(current_changes))
files_modified += 1
current_file = line.replace('FILE:', '').strip()
current_changes = []
elif line.startswith('CHANGES:') or line.strip() == '':
continue
elif current_file:
current_changes.append(line)
# Apply last file changes
if current_file and current_changes:
await self._apply_file_changes(current_file, '\n'.join(current_changes))
files_modified += 1
logger.info(f"Task {task_id}: Applied changes to {files_modified} files")
async def _apply_file_changes(self, filename: str, changes: str):
"""Apply changes to a specific file"""
file_path = os.path.join(self.repo_path, filename)
if os.path.exists(file_path):
# For now, we'll append the changes to the file
# In a real implementation, you'd want more sophisticated parsing
with open(file_path, 'a', encoding='utf-8') as f:
f.write(f"\n\n# AI Generated Changes:\n{changes}\n")
logger.info(f"Applied changes to file: {filename}")
async def _commit_and_push(self, task_id: str, request: GiteaRequest):
"""Commit and push changes back to Gitea"""
logger.info(f"Task {task_id}: Committing and pushing changes...")
task_status[task_id] = {"status": "processing", "message": "Committing and pushing changes..."}
try:
repo = git.Repo(self.repo_path)
# Add all changes
repo.git.add('.')
# Check if there are changes to commit
if repo.is_dirty():
# Commit changes
repo.index.commit("AI-generated code updates")
logger.info(f"Task {task_id}: Changes committed")
# Push changes
origin = repo.remote(name='origin')
origin.push()
logger.info(f"Task {task_id}: Changes pushed to remote")
else:
logger.info(f"Task {task_id}: No changes to commit")
# Remove the cloned repo directory after push
if self.repo_path and os.path.exists(self.repo_path):
shutil.rmtree(self.repo_path)
logger.info(f"Task {task_id}: Removed cloned repo directory {self.repo_path}")
except Exception as e:
raise Exception(f"Failed to commit and push changes: {str(e)}")
# Create MCP server instance
mcp_server = MCPServer()
@app.get("/", response_class=HTMLResponse)
async def read_root(request: Request):
"""Serve the frontend"""
return templates.TemplateResponse("index.html", {"request": request})
@app.post("/process", response_model=ProcessResponse)
async def process_repository(request: GiteaRequest, background_tasks: BackgroundTasks):
"""Process repository with AI"""
import uuid
task_id = str(uuid.uuid4())
logger.info(f"Starting new task: {task_id}")
# Start background task
background_tasks.add_task(mcp_server.process_repository, task_id, request)
return ProcessResponse(
task_id=task_id,
status="started",
message="Processing started"
)
@app.get("/status/{task_id}")
async def get_status(task_id: str):
"""Get status of a processing task"""
if task_id not in task_status:
raise HTTPException(status_code=404, detail="Task not found")
return task_status[task_id]
@app.get("/health")
async def health_check():
"""Health check endpoint"""
return {"status": "healthy", "message": "MCP Server is running"}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

11
requirements.txt Normal file
View File

@ -0,0 +1,11 @@
fastapi==0.104.1
uvicorn==0.24.0
python-multipart==0.0.6
requests==2.31.0
gitpython==3.1.40
google-generativeai==0.3.2
openai==1.3.7
python-dotenv==1.0.0
pydantic==2.5.0
aiofiles==23.2.1
jinja2==3.1.2

65
start.bat Normal file
View File

@ -0,0 +1,65 @@
@echo off
echo 🤖 MCP Server - AI-Powered Code Editor
echo ================================================
echo.
REM Check if Python is installed
python --version >nul 2>&1
if errorlevel 1 (
echo ❌ Error: Python is not installed or not in PATH
echo Please install Python 3.8+ from https://python.org
pause
exit /b 1
)
REM Check if pip is available
python -m pip --version >nul 2>&1
if errorlevel 1 (
echo ❌ Error: pip is not available
echo Please ensure pip is installed with Python
pause
exit /b 1
)
echo ✅ Python is installed
echo.
REM Install dependencies
echo 📦 Installing Python dependencies...
python -m pip install -r requirements.txt
if errorlevel 1 (
echo ❌ Error installing dependencies
pause
exit /b 1
)
echo ✅ Dependencies installed successfully
echo.
REM Check for .env file
if not exist .env (
if exist env.example (
copy env.example .env >nul
echo ✅ Created .env file from template
echo ⚠️ Please edit .env file and add your API keys
) else (
echo ⚠️ No .env file found and no template available
)
) else (
echo ✅ .env file already exists
)
echo.
echo 🚀 Starting MCP Server...
echo 📱 Web interface will be available at: http://localhost:8000
echo 🔧 API documentation at: http://localhost:8000/docs
echo.
echo Press Ctrl+C to stop the server
echo.
REM Start the server
python main.py
echo.
echo 👋 Server stopped
pause

122
start.py Normal file
View File

@ -0,0 +1,122 @@
#!/usr/bin/env python3
"""
MCP Server Startup Script
Handles environment setup and server initialization
"""
import os
import sys
import subprocess
import shutil
from pathlib import Path
def check_python_version():
"""Check if Python version is compatible"""
if sys.version_info < (3, 8):
print("❌ Error: Python 3.8 or higher is required")
print(f"Current version: {sys.version}")
sys.exit(1)
print(f"✅ Python version: {sys.version.split()[0]}")
def check_git():
"""Check if Git is installed"""
try:
subprocess.run(["git", "--version"], check=True, capture_output=True)
print("✅ Git is installed")
except (subprocess.CalledProcessError, FileNotFoundError):
print("❌ Error: Git is not installed or not in PATH")
print("Please install Git: https://git-scm.com/")
sys.exit(1)
def check_node():
"""Check if Node.js is installed (optional)"""
try:
subprocess.run(["node", "--version"], check=True, capture_output=True)
print("✅ Node.js is installed")
except (subprocess.CalledProcessError, FileNotFoundError):
print("⚠️ Warning: Node.js is not installed")
print("This is optional but recommended for projects with package.json")
def install_dependencies():
"""Install Python dependencies"""
print("\n📦 Installing Python dependencies...")
try:
subprocess.run([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"], check=True)
print("✅ Dependencies installed successfully")
except subprocess.CalledProcessError as e:
print(f"❌ Error installing dependencies: {e}")
sys.exit(1)
def setup_environment():
"""Set up environment file"""
env_file = Path(".env")
env_example = Path("env.example")
if not env_file.exists():
if env_example.exists():
shutil.copy(env_example, env_file)
print("✅ Created .env file from template")
print("⚠️ Please edit .env file and add your API keys")
else:
print("⚠️ No .env file found and no template available")
else:
print("✅ .env file already exists")
def check_api_keys():
"""Check if API keys are configured"""
from dotenv import load_dotenv
load_dotenv()
gemini_key = os.getenv("GEMINI_API_KEY")
openai_key = os.getenv("OPENAI_API_KEY")
if not gemini_key and not openai_key:
print("⚠️ Warning: No API keys configured")
print("Please add GEMINI_API_KEY or OPENAI_API_KEY to .env file")
print("You can still start the server, but AI features won't work")
else:
if gemini_key:
print("✅ Gemini API key configured")
if openai_key:
print("✅ OpenAI API key configured")
def create_directories():
"""Create necessary directories"""
directories = ["templates", "static"]
for directory in directories:
Path(directory).mkdir(exist_ok=True)
print("✅ Directories created")
def start_server():
"""Start the FastAPI server"""
print("\n🚀 Starting MCP Server...")
print("📱 Web interface will be available at: http://localhost:8000")
print("🔧 API documentation at: http://localhost:8000/docs")
print("\nPress Ctrl+C to stop the server")
try:
subprocess.run([sys.executable, "main.py"])
except KeyboardInterrupt:
print("\n👋 Server stopped")
def main():
"""Main startup function"""
print("🤖 MCP Server - AI-Powered Code Editor")
print("=" * 50)
# Pre-flight checks
check_python_version()
check_git()
check_node()
# Setup
create_directories()
setup_environment()
install_dependencies()
check_api_keys()
# Start server
start_server()
if __name__ == "__main__":
main()

415
static/script.js Normal file
View File

@ -0,0 +1,415 @@
// MCP Server Frontend JavaScript
class MCPServerFrontend {
constructor() {
this.currentTaskId = null;
this.statusInterval = null;
this.init();
}
init() {
this.bindEvents();
this.setDefaultValues();
}
bindEvents() {
// Process button
document.getElementById('processBtn').addEventListener('click', () => {
this.processRepository();
});
// Clear log button
document.getElementById('clearLog').addEventListener('click', () => {
this.clearLog();
});
// Toggle API key visibility
document.getElementById('toggleApiKey').addEventListener('click', () => {
this.toggleApiKeyVisibility();
});
// Toggle Gitea token visibility
document.getElementById('toggleGiteaToken').addEventListener('click', () => {
this.toggleGiteaTokenVisibility();
});
// Form validation
document.getElementById('repoUrl').addEventListener('input', () => {
this.validateForm();
});
document.getElementById('giteaToken').addEventListener('input', () => {
this.validateForm();
});
document.getElementById('prompt').addEventListener('input', () => {
this.validateForm();
});
document.getElementById('apiKey').addEventListener('input', () => {
this.validateForm();
});
document.getElementById('modelName').addEventListener('input', () => {
this.validateForm();
});
}
setDefaultValues() {
// Set default values for testing
document.getElementById('repoUrl').value = 'http://157.66.191.31:3000/user/repo.git';
document.getElementById('giteaToken').value = '37c322628fa57b0ec7b481c8655ae2bebd486f6f';
document.getElementById('aiModel').value = 'gemini';
}
toggleApiKeyVisibility() {
const apiKeyInput = document.getElementById('apiKey');
const toggleBtn = document.getElementById('toggleApiKey');
const icon = toggleBtn.querySelector('i');
if (apiKeyInput.type === 'password') {
apiKeyInput.type = 'text';
icon.className = 'fas fa-eye-slash';
} else {
apiKeyInput.type = 'password';
icon.className = 'fas fa-eye';
}
}
toggleGiteaTokenVisibility() {
const giteaTokenInput = document.getElementById('giteaToken');
const toggleBtn = document.getElementById('toggleGiteaToken');
const icon = toggleBtn.querySelector('i');
if (giteaTokenInput.type === 'password') {
giteaTokenInput.type = 'text';
icon.className = 'fas fa-eye-slash';
} else {
giteaTokenInput.type = 'password';
icon.className = 'fas fa-eye';
}
}
validateForm() {
const repoUrl = document.getElementById('repoUrl').value.trim();
const giteaToken = document.getElementById('giteaToken').value.trim();
const prompt = document.getElementById('prompt').value.trim();
const apiKey = document.getElementById('apiKey').value.trim();
const modelName = document.getElementById('modelName').value.trim();
const isValid = repoUrl && giteaToken && prompt && apiKey && modelName;
const processBtn = document.getElementById('processBtn');
processBtn.disabled = !isValid;
if (isValid) {
processBtn.classList.remove('btn-secondary');
processBtn.classList.add('btn-primary');
} else {
processBtn.classList.remove('btn-primary');
processBtn.classList.add('btn-secondary');
}
return isValid;
}
async processRepository() {
if (!this.validateForm()) {
this.addLogEntry('error', 'Please fill in all required fields');
return;
}
const requestData = {
repo_url: document.getElementById('repoUrl').value.trim(),
token: document.getElementById('giteaToken').value.trim(),
prompt: document.getElementById('prompt').value.trim(),
ai_model: document.getElementById('aiModel').value,
model_name: document.getElementById('modelName').value.trim(),
api_key: document.getElementById('apiKey').value.trim()
};
try {
// Disable form
this.setFormEnabled(false);
this.showProgressCard();
this.updateStatus('Processing...', 'processing');
this.addLogEntry('info', 'Starting repository processing...');
// Show loading modal
const loadingModal = new bootstrap.Modal(document.getElementById('loadingModal'));
loadingModal.show();
// Send request to backend
const response = await fetch('/process', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestData)
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
this.currentTaskId = result.task_id;
this.addLogEntry('success', `Task started with ID: ${result.task_id}`);
this.addLogEntry('info', 'Monitoring task progress...');
// Start monitoring
this.startStatusMonitoring();
// Hide loading modal
loadingModal.hide();
} catch (error) {
this.addLogEntry('error', `Failed to start processing: ${error.message}`);
this.updateStatus('Error', 'error');
this.setFormEnabled(true);
this.hideProgressCard();
// Hide loading modal
const loadingModal = bootstrap.Modal.getInstance(document.getElementById('loadingModal'));
if (loadingModal) {
loadingModal.hide();
}
}
}
startStatusMonitoring() {
if (this.statusInterval) {
clearInterval(this.statusInterval);
}
this.statusInterval = setInterval(async () => {
if (!this.currentTaskId) {
this.stopStatusMonitoring();
return;
}
try {
const response = await fetch(`/status/${this.currentTaskId}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const status = await response.json();
this.updateProgress(status);
this.addLogEntry('info', status.message);
if (status.status === 'completed' || status.status === 'error') {
this.stopStatusMonitoring();
this.handleTaskCompletion(status);
}
} catch (error) {
this.addLogEntry('error', `Failed to get status: ${error.message}`);
this.stopStatusMonitoring();
}
}, 2000); // Check every 2 seconds
}
stopStatusMonitoring() {
if (this.statusInterval) {
clearInterval(this.statusInterval);
this.statusInterval = null;
}
}
updateProgress(status) {
const progressBar = document.getElementById('progressBar');
const steps = document.querySelectorAll('.step');
// Show AI output if available
if (status.ai_response) {
document.getElementById('aiOutputCard').style.display = 'block';
document.getElementById('aiOutput').textContent = status.ai_response;
} else {
document.getElementById('aiOutputCard').style.display = 'none';
document.getElementById('aiOutput').textContent = '';
}
// Step order (no deps)
const stepOrder = ['clone', 'ai', 'commit'];
let currentStep = 0;
let errorStep = -1;
// Determine current step and error
if (status.message.includes('Cloning')) {
currentStep = 0;
} else if (status.message.includes('Analyzing') || status.message.includes('AI')) {
currentStep = 1;
} else if (status.message.includes('Committing') || status.message.includes('Push')) {
currentStep = 2;
} else if (status.status === 'completed') {
currentStep = 3;
} else if (status.status === 'error') {
// Try to find which step failed
if (status.message.includes('Clone')) errorStep = 0;
else if (status.message.includes('AI') || status.message.includes('Gemini') || status.message.includes('OpenAI')) errorStep = 1;
else if (status.message.includes('Commit') || status.message.includes('Push')) errorStep = 2;
}
// Update progress bar
let progress = (currentStep / stepOrder.length) * 100;
progressBar.style.width = `${progress}%`;
// Update step indicators with color, icon, and label
steps.forEach((step, idx) => {
const stepName = step.dataset.step;
step.classList.remove('active', 'completed', 'error', 'pending');
const iconSpan = step.querySelector('.step-icon');
const labelSpan = step.querySelector('.step-label');
// Set icon and label
if (errorStep === idx) {
step.classList.add('error');
iconSpan.innerHTML = '✖';
labelSpan.textContent = 'Error';
} else if (idx < currentStep) {
step.classList.add('completed');
iconSpan.innerHTML = '✔';
labelSpan.textContent = 'Completed';
} else if (idx === currentStep && status.status !== 'completed' && status.status !== 'error') {
step.classList.add('active');
iconSpan.innerHTML = '<span class="spinner-grow spinner-grow-sm" style="width:1em;height:1em;"></span>';
labelSpan.textContent = 'In Progress';
} else {
step.classList.add('pending');
iconSpan.innerHTML = '●';
labelSpan.textContent = 'Pending';
}
});
}
isStepCompleted(stepName, activeStep) {
const stepOrder = ['clone', 'deps', 'ai', 'commit'];
const stepIndex = stepOrder.indexOf(stepName);
const activeIndex = stepOrder.indexOf(activeStep);
return stepIndex < activeIndex;
}
handleTaskCompletion(status) {
if (status.status === 'completed') {
this.updateStatus('Completed', 'completed');
this.addLogEntry('success', 'Repository processing completed successfully!');
this.addLogEntry('info', 'Changes have been committed and pushed to the repository.');
} else {
this.updateStatus('Error', 'error');
this.addLogEntry('error', `Processing failed: ${status.message}`);
}
this.setFormEnabled(true);
this.currentTaskId = null;
}
updateStatus(text, type) {
const statusText = document.querySelector('.status-text');
const statusDot = document.querySelector('.status-dot');
statusText.textContent = text;
// Update status dot color
statusDot.className = 'status-dot';
switch (type) {
case 'processing':
statusDot.style.backgroundColor = '#ffc107';
break;
case 'completed':
statusDot.style.backgroundColor = '#28a745';
break;
case 'error':
statusDot.style.backgroundColor = '#dc3545';
break;
default:
statusDot.style.backgroundColor = '#17a2b8';
}
}
addLogEntry(type, message) {
const logContainer = document.getElementById('logContainer');
const timestamp = new Date().toLocaleTimeString();
const logEntry = document.createElement('div');
logEntry.className = `log-entry ${type}`;
logEntry.innerHTML = `
<span class="timestamp">[${timestamp}]</span>
<span class="message">${message}</span>
`;
logContainer.appendChild(logEntry);
logContainer.scrollTop = logContainer.scrollHeight;
}
clearLog() {
const logContainer = document.getElementById('logContainer');
logContainer.innerHTML = `
<div class="log-entry info">
<span class="timestamp">[System]</span>
<span class="message">Log cleared. Ready for new operations.</span>
</div>
`;
}
setFormEnabled(enabled) {
const formElements = [
'repoUrl', 'giteaToken', 'prompt', 'aiModel', 'modelName', 'apiKey', 'processBtn'
];
formElements.forEach(id => {
const element = document.getElementById(id);
if (element) {
element.disabled = !enabled;
}
});
const processBtn = document.getElementById('processBtn');
if (enabled) {
processBtn.innerHTML = '<i class="fas fa-play"></i> Process Repository';
} else {
processBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...';
}
}
showProgressCard() {
const progressCard = document.getElementById('progressCard');
progressCard.style.display = 'block';
// Reset progress
document.getElementById('progressBar').style.width = '0%';
document.querySelectorAll('.step').forEach(step => {
step.classList.remove('active', 'completed');
});
}
hideProgressCard() {
const progressCard = document.getElementById('progressCard');
progressCard.style.display = 'none';
}
// Utility function to format error messages
formatError(error) {
if (error.response) {
return `Server error: ${error.response.status} - ${error.response.statusText}`;
} else if (error.request) {
return 'Network error: Unable to connect to server';
} else {
return `Error: ${error.message}`;
}
}
}
// Initialize the frontend when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
window.mcpFrontend = new MCPServerFrontend();
});
// Handle page unload to clean up intervals
window.addEventListener('beforeunload', () => {
if (window.mcpFrontend) {
window.mcpFrontend.stopStatusMonitoring();
}
});

342
static/style.css Normal file
View File

@ -0,0 +1,342 @@
/* Custom CSS for MCP Server */
:root {
--primary-color: #007bff;
--secondary-color: #6c757d;
--success-color: #28a745;
--danger-color: #dc3545;
--warning-color: #ffc107;
--info-color: #17a2b8;
--light-color: #f8f9fa;
--dark-color: #343a40;
--sidebar-width: 350px;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f5f7fa;
margin: 0;
padding: 0;
}
/* Sidebar Styles */
.sidebar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
min-height: 100vh;
padding: 0;
box-shadow: 2px 0 10px rgba(0,0,0,0.1);
}
.sidebar-header {
padding: 2rem 1.5rem;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.sidebar-header h3 {
margin: 0;
font-weight: 600;
color: white;
}
.sidebar-header p {
margin: 0.5rem 0 0 0;
opacity: 0.8;
}
.sidebar-content {
padding: 1.5rem;
}
.form-section {
background: rgba(255,255,255,0.1);
border-radius: 10px;
padding: 1.5rem;
margin-bottom: 1.5rem;
backdrop-filter: blur(10px);
}
.form-section h5 {
color: white;
margin-bottom: 1rem;
font-weight: 600;
}
.form-label {
color: white;
font-weight: 500;
margin-bottom: 0.5rem;
}
.form-control, .form-select {
background: rgba(255,255,255,0.9);
border: none;
border-radius: 8px;
padding: 0.75rem;
font-size: 0.9rem;
}
.form-control:focus, .form-select:focus {
background: white;
box-shadow: 0 0 0 0.2rem rgba(255,255,255,0.25);
}
.btn-primary {
background: linear-gradient(45deg, #007bff, #0056b3);
border: none;
border-radius: 8px;
padding: 0.75rem 1.5rem;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0,123,255,0.4);
}
/* Main Content Styles */
.main-content {
padding: 2rem;
background: white;
min-height: 100vh;
}
.content-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 2px solid #f0f0f0;
}
.content-header h2 {
margin: 0;
color: var(--dark-color);
font-weight: 600;
}
.status-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
}
.status-dot {
width: 12px;
height: 12px;
border-radius: 50%;
background-color: var(--success-color);
animation: pulse 2s infinite;
}
.status-text {
font-weight: 500;
color: var(--secondary-color);
}
/* Progress Steps */
.progress-steps {
display: flex;
justify-content: space-between;
margin-top: 1rem;
}
.step {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
flex: 1;
position: relative;
}
.step:not(:last-child)::after {
content: '';
position: absolute;
top: 20px;
left: 50%;
width: 100%;
height: 2px;
background-color: #e9ecef;
z-index: 1;
}
.step.active:not(:last-child)::after {
background-color: var(--primary-color);
}
.step i {
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #e9ecef;
color: var(--secondary-color);
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0.5rem;
position: relative;
z-index: 2;
transition: all 0.3s ease;
}
.step.active i {
background-color: var(--primary-color);
color: white;
}
.step.completed i {
background-color: var(--success-color);
color: white;
}
.step span {
font-size: 0.8rem;
color: var(--secondary-color);
font-weight: 500;
}
.step.active span {
color: var(--primary-color);
}
.step.completed span {
color: var(--success-color);
}
/* Log Container */
.log-container {
background-color: #f8f9fa;
border-radius: 8px;
padding: 1rem;
max-height: 400px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 0.85rem;
}
.log-entry {
margin-bottom: 0.5rem;
padding: 0.25rem 0;
border-radius: 4px;
}
.log-entry.info {
color: var(--info-color);
}
.log-entry.success {
color: var(--success-color);
}
.log-entry.error {
color: var(--danger-color);
}
.log-entry.warning {
color: var(--warning-color);
}
.timestamp {
font-weight: bold;
margin-right: 0.5rem;
}
/* Cards */
.card {
border: none;
border-radius: 12px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
.card:hover {
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
}
.card-header {
background: linear-gradient(45deg, #f8f9fa, #e9ecef);
border-bottom: 1px solid #dee2e6;
border-radius: 12px 12px 0 0 !important;
font-weight: 600;
}
/* Progress Bar */
.progress {
height: 8px;
border-radius: 4px;
background-color: #e9ecef;
}
.progress-bar {
background: linear-gradient(45deg, var(--primary-color), #0056b3);
border-radius: 4px;
transition: width 0.3s ease;
}
/* Responsive Design */
@media (max-width: 768px) {
.sidebar {
min-height: auto;
position: relative;
}
.main-content {
padding: 1rem;
}
.content-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.progress-steps {
flex-direction: column;
gap: 1rem;
}
.step:not(:last-child)::after {
display: none;
}
}
/* Animations */
@keyframes pulse {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
/* Loading States */
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Custom Scrollbar */
.log-container::-webkit-scrollbar {
width: 6px;
}
.log-container::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.log-container::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.log-container::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}

194
templates/index.html Normal file
View File

@ -0,0 +1,194 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MCP Server - AI Code Editor</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<link href="/static/style.css" rel="stylesheet">
</head>
<body>
<div class="container-fluid">
<div class="row">
<!-- Sidebar -->
<div class="col-md-3 sidebar">
<div class="sidebar-header">
<h3><i class="fas fa-robot"></i> MCP Server</h3>
<p class="text-muted">AI-powered code editing</p>
</div>
<div class="sidebar-content">
<div class="form-section">
<h5><i class="fas fa-cog"></i> Configuration</h5>
<div class="mb-3">
<label for="repoUrl" class="form-label">Gitea Repository URL</label>
<input type="url" class="form-control" id="repoUrl"
placeholder="http://157.66.191.31:3000/user/repo.git">
</div>
<div class="mb-3">
<label for="giteaToken" class="form-label">Gitea Token</label>
<div class="input-group">
<input type="password" class="form-control" id="giteaToken"
placeholder="Enter your Gitea token">
<button class="btn btn-outline-secondary" type="button" id="toggleGiteaToken">
<i class="fas fa-eye"></i>
</button>
</div>
<div class="form-text">
<small>
Get your token from Gitea: Settings → Applications → Generate new token
</small>
</div>
</div>
</div>
<div class="form-section">
<h5><i class="fas fa-brain"></i> AI Configuration</h5>
<div class="mb-3">
<label for="aiModel" class="form-label">AI Model</label>
<select class="form-select" id="aiModel">
<option value="gemini">Gemini CLI</option>
<option value="openai">OpenAI</option>
</select>
</div>
<div class="mb-3">
<label for="modelName" class="form-label">Model Name</label>
<input type="text" class="form-control" id="modelName"
placeholder="gemini-1.5-pro" value="gemini-1.5-pro">
<div class="form-text">
<small>
<strong>Gemini:</strong> gemini-1.5-pro, gemini-1.5-flash, etc.<br>
<strong>OpenAI:</strong> gpt-4, gpt-3.5-turbo, etc.
</small>
</div>
</div>
<div class="mb-3">
<label for="apiKey" class="form-label">API Key</label>
<div class="input-group">
<input type="password" class="form-control" id="apiKey"
placeholder="Enter your API key">
<button class="btn btn-outline-secondary" type="button" id="toggleApiKey">
<i class="fas fa-eye"></i>
</button>
</div>
<div class="form-text">
<small>
<strong>Gemini:</strong> Get your API key from <a href="https://makersuite.google.com/app/apikey" target="_blank">Google AI Studio</a><br>
<strong>OpenAI:</strong> Get your API key from <a href="https://platform.openai.com/api-keys" target="_blank">OpenAI Platform</a>
</small>
</div>
</div>
</div>
<div class="form-section">
<h5><i class="fas fa-magic"></i> AI Prompt</h5>
<div class="mb-3">
<label for="prompt" class="form-label">What would you like to do?</label>
<textarea class="form-control" id="prompt" rows="4"
placeholder="Describe the changes you want to make to the code..."></textarea>
</div>
<button type="button" class="btn btn-primary w-100" id="processBtn">
<i class="fas fa-play"></i> Process Repository
</button>
</div>
</div>
</div>
<!-- Main Content -->
<div class="col-md-9 main-content">
<div class="content-header">
<h2>Repository Processing</h2>
<div class="status-indicator" id="statusIndicator">
<span class="status-dot"></span>
<span class="status-text">Ready</span>
</div>
</div>
<div class="content-body">
<!-- Progress Section -->
<div class="card mb-4" id="progressCard" style="display: none;">
<div class="card-header">
<h5><i class="fas fa-tasks"></i> Processing Progress</h5>
</div>
<div class="card-body">
<div class="progress mb-3">
<div class="progress-bar" id="progressBar" role="progressbar" style="width: 0%"></div>
</div>
<div class="progress-steps">
<div class="step" data-step="clone">
<span class="step-icon"></span>
<span>Clone Repository</span>
<span class="step-label"></span>
</div>
<div class="step" data-step="ai">
<span class="step-icon"></span>
<span>AI Analysis</span>
<span class="step-label"></span>
</div>
<div class="step" data-step="commit">
<span class="step-icon"></span>
<span>Commit & Push</span>
<span class="step-label"></span>
</div>
</div>
</div>
</div>
<!-- Log Section -->
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<h5><i class="fas fa-terminal"></i> Processing Log</h5>
<button class="btn btn-sm btn-outline-secondary" id="clearLog">
<i class="fas fa-trash"></i> Clear
</button>
</div>
<div class="card-body">
<div class="log-container" id="logContainer">
<div class="log-entry info">
<span class="timestamp">[System]</span>
<span class="message">Ready to process repository. Fill in the details and click "Process Repository".</span>
</div>
</div>
</div>
</div>
<!-- AI Model Output Section -->
<div class="card mt-4" id="aiOutputCard" style="display: none;">
<div class="card-header">
<h5><i class="fas fa-robot"></i> AI Model Output</h5>
</div>
<div class="card-body">
<pre id="aiOutput" style="white-space: pre-wrap; word-break: break-word;"></pre>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Loading Modal -->
<div class="modal fade" id="loadingModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-body text-center">
<div class="spinner-border text-primary mb-3" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<h5>Processing Repository</h5>
<p class="text-muted">This may take a few minutes...</p>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/script.js"></script>
</body>
</html>

221
test_setup.py Normal file
View File

@ -0,0 +1,221 @@
#!/usr/bin/env python3
"""
MCP Server Setup Test
Tests all components to ensure they're working correctly
"""
import os
import sys
import subprocess
import importlib
from pathlib import Path
def test_python_version():
"""Test Python version"""
print("🐍 Testing Python version...")
if sys.version_info >= (3, 8):
print(f"✅ Python {sys.version.split()[0]} - Compatible")
return True
else:
print(f"❌ Python {sys.version.split()[0]} - Incompatible (need 3.8+)")
return False
def test_dependencies():
"""Test if all required dependencies are installed"""
print("\n📦 Testing dependencies...")
required_packages = [
'fastapi',
'uvicorn',
'gitpython',
'requests',
'python-dotenv',
'pydantic'
]
optional_packages = [
'google.generativeai',
'openai'
]
all_good = True
for package in required_packages:
try:
importlib.import_module(package)
print(f"{package}")
except ImportError:
print(f"{package} - Missing")
all_good = False
print("\n🔧 Optional packages:")
for package in optional_packages:
try:
importlib.import_module(package)
print(f"{package}")
except ImportError:
print(f"⚠️ {package} - Not installed (AI features won't work)")
return all_good
def test_git():
"""Test Git installation"""
print("\n🔧 Testing Git...")
try:
result = subprocess.run(["git", "--version"], capture_output=True, text=True)
if result.returncode == 0:
print(f"✅ Git: {result.stdout.strip()}")
return True
else:
print("❌ Git not working properly")
return False
except FileNotFoundError:
print("❌ Git not found in PATH")
return False
def test_node():
"""Test Node.js installation (optional)"""
print("\n🔧 Testing Node.js...")
try:
result = subprocess.run(["node", "--version"], capture_output=True, text=True)
if result.returncode == 0:
print(f"✅ Node.js: {result.stdout.strip()}")
return True
else:
print("⚠️ Node.js not working properly")
return False
except FileNotFoundError:
print("⚠️ Node.js not found (optional)")
return False
def test_files():
"""Test if all required files exist"""
print("\n📁 Testing files...")
required_files = [
'main.py',
'requirements.txt',
'env.example',
'README.md'
]
required_dirs = [
'templates',
'static'
]
all_good = True
for file in required_files:
if Path(file).exists():
print(f"{file}")
else:
print(f"{file} - Missing")
all_good = False
for directory in required_dirs:
if Path(directory).exists():
print(f"{directory}/")
else:
print(f"{directory}/ - Missing")
all_good = False
return all_good
def test_environment():
"""Test environment configuration"""
print("\n🔐 Testing environment...")
from dotenv import load_dotenv
load_dotenv()
gemini_key = os.getenv("GEMINI_API_KEY")
openai_key = os.getenv("OPENAI_API_KEY")
if gemini_key and gemini_key != "your_gemini_api_key_here":
print("✅ Gemini API key configured")
else:
print("⚠️ Gemini API key not configured")
if openai_key and openai_key != "your_openai_api_key_here":
print("✅ OpenAI API key configured")
else:
print("⚠️ OpenAI API key not configured")
if not gemini_key and not openai_key:
print("⚠️ No AI API keys configured - AI features won't work")
return False
return True
def test_server_import():
"""Test if the server can be imported"""
print("\n🚀 Testing server import...")
try:
# Test basic import
import main
print("✅ Server module imports successfully")
return True
except Exception as e:
print(f"❌ Server import failed: {e}")
return False
def run_tests():
"""Run all tests"""
print("🧪 MCP Server Setup Test")
print("=" * 50)
tests = [
("Python Version", test_python_version),
("Dependencies", test_dependencies),
("Git", test_git),
("Node.js", test_node),
("Files", test_files),
("Environment", test_environment),
("Server Import", test_server_import)
]
results = []
for test_name, test_func in tests:
try:
result = test_func()
results.append((test_name, result))
except Exception as e:
print(f"{test_name} test failed with error: {e}")
results.append((test_name, False))
# Summary
print("\n" + "=" * 50)
print("📊 Test Summary:")
print("=" * 50)
passed = 0
total = len(results)
for test_name, result in results:
status = "✅ PASS" if result else "❌ FAIL"
print(f"{test_name:<20} {status}")
if result:
passed += 1
print(f"\nResults: {passed}/{total} tests passed")
if passed == total:
print("🎉 All tests passed! Your MCP Server is ready to use.")
print("\nTo start the server, run:")
print(" python main.py")
print(" or")
print(" python start.py")
else:
print("⚠️ Some tests failed. Please fix the issues before running the server.")
print("\nCommon fixes:")
print("1. Install missing dependencies: pip install -r requirements.txt")
print("2. Configure API keys in .env file")
print("3. Install Git if not present")
return passed == total
if __name__ == "__main__":
success = run_tests()
sys.exit(0 if success else 1)

80
view_logs.py Normal file
View File

@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""
Real-time log viewer for MCP Server
"""
import os
import time
import sys
from pathlib import Path
def follow_logs(log_file="mcp_server.log"):
"""Follow log file in real-time"""
log_path = Path(log_file)
if not log_path.exists():
print(f"Log file {log_file} not found. Creating empty file...")
log_path.touch()
print(f"Following logs from: {log_path.absolute()}")
print("Press Ctrl+C to stop")
print("-" * 80)
# Get initial file size
with open(log_path, 'r') as f:
f.seek(0, 2) # Go to end of file
last_size = f.tell()
try:
while True:
with open(log_path, 'r') as f:
f.seek(last_size)
new_lines = f.readlines()
if new_lines:
for line in new_lines:
print(line.rstrip())
last_size = f.tell()
time.sleep(0.1) # Check every 100ms
except KeyboardInterrupt:
print("\nStopped following logs")
def show_recent_logs(log_file="mcp_server.log", lines=50):
"""Show recent log lines"""
log_path = Path(log_file)
if not log_path.exists():
print(f"Log file {log_file} not found.")
return
with open(log_path, 'r') as f:
all_lines = f.readlines()
recent_lines = all_lines[-lines:] if len(all_lines) > lines else all_lines
print(f"Recent {len(recent_lines)} lines from {log_path.absolute()}:")
print("-" * 80)
for line in recent_lines:
print(line.rstrip())
def main():
if len(sys.argv) > 1:
command = sys.argv[1]
if command == "follow":
log_file = sys.argv[2] if len(sys.argv) > 2 else "mcp_server.log"
follow_logs(log_file)
elif command == "recent":
log_file = sys.argv[2] if len(sys.argv) > 2 else "mcp_server.log"
lines = int(sys.argv[3]) if len(sys.argv) > 3 else 50
show_recent_logs(log_file, lines)
else:
print("Usage:")
print(" python view_logs.py follow [log_file] # Follow logs in real-time")
print(" python view_logs.py recent [log_file] [lines] # Show recent logs")
else:
# Default: show recent logs
show_recent_logs()
if __name__ == "__main__":
main()