Initial commit

This commit is contained in:
2025-11-04 00:23:51 +00:00
commit a397a14cab
15 changed files with 9102 additions and 0 deletions
+255
View File
@@ -0,0 +1,255 @@
#!/usr/bin/env python3
"""
HTTP server for MCP codebase RAG with REST API
"""
import uvicorn
import logging
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Optional
import signal
import sys
import json
from enhanced_toon import EnhancedToon
from mcp_codebase import (
startup,
search_codebase as _search_codebase,
find_code_references as _find_code_references,
read_file_lines,
rebuild_index as _rebuild_index
)
logger = logging.getLogger("rag-mcp")
def signal_handler(sig, frame):
"""Handle graceful shutdown on Ctrl+C."""
logger.info("\n=== HTTP Server Shutdown ===")
logger.info("Goodbye!\n")
sys.exit(0)
# Request/Response models
class SearchRequest(BaseModel):
query: str
top_k: int = 5
rerank: bool = True
class ReferenceRequest(BaseModel):
symbol: str
top_k: int = 20
class ReadFileRequest(BaseModel):
path: str
start: int = 1
end: Optional[int] = None
class SymbolRequest(BaseModel):
symbol: str
def create_app():
"""Create FastAPI app with MCP tool endpoints"""
app = FastAPI(
title="MCP Codebase RAG Server",
description="Codebase search and analysis via MCP tools over HTTP",
version="1.0.0"
)
# Add CORS middleware for remote access
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Configure this appropriately for production
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Health check endpoint
@app.get("/")
async def root():
return {
"status": "running",
"service": "MCP Codebase RAG Server",
"version": "1.0.0",
"endpoints": {
"docs": "/docs",
"health": "/health",
"tools": "/tools",
"search": "POST /search",
"references": "POST /references",
"read_file": "POST /read_file",
"rebuild": "POST /rebuild"
}
}
@app.get("/health")
async def health():
"""Get server health and statistics"""
try:
# Import here to avoid circular import issues
from mcp_codebase import health_check
result = health_check()
# Parse JSON string to dict for better API response
try:
result_dict = json.loads(result)
return result_dict
except:
return {"result": result}
except Exception as e:
logger.exception("Health check failed")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/tools")
async def list_tools():
"""List available MCP tools"""
return {
"tools": [
{
"name": "search_codebase",
"description": "Search the entire codebase using hybrid RAG",
"endpoint": "POST /search",
"parameters": {
"query": "string (required)",
"top_k": "int (default: 5)",
"rerank": "bool (default: true)"
}
},
{
"name": "find_code_references",
"description": "Find all references to a specific symbol",
"endpoint": "POST /references",
"parameters": {
"symbol": "string (required)",
"top_k": "int (default: 20)"
}
},
{
"name": "read_file_lines",
"description": "Read specific lines from a file with context",
"endpoint": "POST /read_file",
"parameters": {
"path": "string (required)",
"start": "int (default: 1)",
"end": "int (optional)"
}
},
{
"name": "rebuild_index",
"description": "Force rebuild of search indexes",
"endpoint": "POST /rebuild",
"parameters": {}
}
]
}
@app.post("/search")
async def search(request: SearchRequest):
"""Search codebase - returns enhanced Toon format"""
try:
result = _search_codebase(
query=request.query,
top_k=request.top_k,
rerank=request.rerank
)
enhanced_result = EnhancedToon.search_results(result, request.query)
return {
"result": enhanced_result,
"query": request.query,
"top_k": request.top_k
}
except Exception as e:
logger.exception("Search failed")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/references")
async def references(request: ReferenceRequest):
"""Find all references to a symbol - returns enhanced Toon format"""
try:
result = _find_code_references(
symbol=request.symbol,
top_k=request.top_k
)
enhanced_result = EnhancedToon.reference_results(result, request.symbol)
return {
"result": enhanced_result,
"symbol": request.symbol,
"top_k": request.top_k
}
except Exception as e:
logger.exception("Reference search failed")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/read_file")
async def read_file(request: ReadFileRequest):
"""Read file lines with context - returns enhanced Toon format"""
try:
result = read_file_lines(
path=request.path,
start=request.start,
end=request.end
)
enhanced_result = EnhancedToon.file_content_results(result, request.path)
return {
"result": enhanced_result,
"path": request.path,
"start_line": request.start,
"end_line": request.end
}
except Exception as e:
logger.exception("Read file failed")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/rebuild")
async def rebuild():
"""Rebuild search indexes"""
try:
result = _rebuild_index()
# For rebuild, we might not need enhanced format as it's usually a simple status message
return {"result": result}
except Exception as e:
logger.exception("Rebuild failed")
raise HTTPException(status_code=500, detail=str(e))
return app
if __name__ == "__main__":
# Register signal handlers
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
try:
# Initialize the indexes (this will load or build them)
startup()
logger.info("="*60)
logger.info("🚀 Starting HTTP server on: http://0.0.0.0:8000")
logger.info("📚 REST API endpoints:")
logger.info(" GET / - Service info")
logger.info(" GET /health - Server health & stats")
logger.info(" GET /tools - List available tools")
logger.info(" POST /search - Search codebase")
logger.info(" POST /references - Find symbol references")
logger.info(" POST /read_file - Read file with context")
logger.info(" POST /rebuild - Rebuild indexes")
logger.info("📖 API docs available at: http://0.0.0.0:8000/docs")
logger.info("⏹️ Press Ctrl+C to stop")
logger.info("="*60)
# Create and run app
app = create_app()
uvicorn.run(
app,
host="0.0.0.0",
port=8000,
log_level="info",
access_log=True
)
except KeyboardInterrupt:
signal_handler(signal.SIGINT, None)
except Exception as e:
logger.exception("Fatal error during HTTP server startup")
sys.exit(1)