Coverage for src/web_server.py: 68%
80 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-08 05:40 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-08 05:40 +0000
1#!/usr/bin/env python3
3from fastapi import FastAPI, HTTPException, Request
4from fastapi.responses import HTMLResponse, JSONResponse
5from fastapi.templating import Jinja2Templates
6from pathlib import Path
7import json
8from typing import Optional
10app = FastAPI(title="MCP Documentation Server Web Interface")
12# Setup Jinja2 templates
13templates = Jinja2Templates(directory=str(Path(__file__).parent / "templates"))
15# Global server instance (imported at runtime to avoid circular import)
16doc_server: Optional["MCPDocumentationServer"] = None
18@app.get("/", response_class=HTMLResponse)
19async def root(request: Request):
20 """Main web interface"""
21 return templates.TemplateResponse(request, "index.html")
23@app.get("/api/structure")
24async def get_structure():
25 """Get document structure - shows root files with their sections"""
26 if not doc_server:
27 raise HTTPException(status_code=500, detail="Server not initialized")
29 # Use root files structure for file-based navigation
30 return doc_server.get_root_files_structure()
32@app.get("/api/metadata")
33async def get_metadata(path: Optional[str] = None):
34 """Get metadata"""
35 if not doc_server:
36 raise HTTPException(status_code=500, detail="Server not initialized")
38 return doc_server.get_metadata(path)
40@app.get("/api/dependencies")
41async def get_dependencies():
42 """Get dependencies"""
43 if not doc_server:
44 raise HTTPException(status_code=500, detail="Server not initialized")
46 return doc_server.get_dependencies()
48@app.get("/api/validate")
49async def validate_structure():
50 """Validate structure"""
51 if not doc_server:
52 raise HTTPException(status_code=500, detail="Server not initialized")
54 return doc_server.validate_structure()
56@app.get("/api/search")
57async def search_content(q: str):
58 """Search content in sections"""
59 if not doc_server:
60 raise HTTPException(status_code=500, detail="Server not initialized")
62 return doc_server.doc_api.search_content(q)
64def _build_base_section_response(section) -> dict:
65 """Build the base section response with common fields"""
66 return {
67 'id': section.id,
68 'title': section.title,
69 'level': section.level,
70 'content': section.content,
71 'children': section.children,
72 'source_file': section.source_file,
73 'line_start': section.line_start,
74 'line_end': section.line_end
75 }
77def _add_full_document_context(response: dict, section) -> None:
78 """Add full document content and position metadata to response"""
79 source_file_path = Path(section.source_file)
80 if not source_file_path.exists():
81 return
83 try:
84 full_content = source_file_path.read_text(encoding='utf-8')
85 response['full_content'] = full_content
86 response['section_position'] = {
87 'line_start': section.line_start,
88 'line_end': section.line_end
89 }
90 except (OSError, UnicodeDecodeError) as e:
91 # Log error but don't fail the request - continue without full content
92 pass
94@app.get("/api/section/{section_id:path}")
95async def get_section(section_id: str, context: str = "section"):
96 """Get specific section with optional full document context"""
97 if not doc_server:
98 raise HTTPException(status_code=500, detail="Server not initialized")
100 if section_id not in doc_server.sections:
101 raise HTTPException(status_code=404, detail="Section not found")
103 section = doc_server.sections[section_id]
104 response = _build_base_section_response(section)
106 if context == "full":
107 _add_full_document_context(response, section)
109 return response
111def init_server(project_root: Path):
112 """Initialize the documentation server"""
113 global doc_server
114 try:
115 # Try relative import first (when used as module)
116 from .mcp_server import MCPDocumentationServer
117 except ImportError:
118 # Fall back to absolute import (when run as script)
119 from mcp_server import MCPDocumentationServer
120 doc_server = MCPDocumentationServer(project_root, enable_webserver=False)
122if __name__ == "__main__":
123 import uvicorn
124 import sys
125 import argparse
127 parser = argparse.ArgumentParser(description='Start MCP Documentation Web Server')
128 parser.add_argument('project_root', help='Path to project root directory')
129 parser.add_argument('--port', type=int, default=8082, help='Port to run server on (default: 8082)')
131 args = parser.parse_args()
133 project_root = Path(args.project_root)
134 if not project_root.exists():
135 print(f"Project root does not exist: {project_root}")
136 sys.exit(1)
138 init_server(project_root)
140 uvicorn.run(app, host="0.0.0.0", port=args.port)