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

1#!/usr/bin/env python3 

2 

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 

9 

10app = FastAPI(title="MCP Documentation Server Web Interface") 

11 

12# Setup Jinja2 templates 

13templates = Jinja2Templates(directory=str(Path(__file__).parent / "templates")) 

14 

15# Global server instance (imported at runtime to avoid circular import) 

16doc_server: Optional["MCPDocumentationServer"] = None 

17 

18@app.get("/", response_class=HTMLResponse) 

19async def root(request: Request): 

20 """Main web interface""" 

21 return templates.TemplateResponse(request, "index.html") 

22 

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") 

28 

29 # Use root files structure for file-based navigation 

30 return doc_server.get_root_files_structure() 

31 

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") 

37 

38 return doc_server.get_metadata(path) 

39 

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") 

45 

46 return doc_server.get_dependencies() 

47 

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") 

53 

54 return doc_server.validate_structure() 

55 

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") 

61 

62 return doc_server.doc_api.search_content(q) 

63 

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 } 

76 

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 

82 

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 

93 

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") 

99 

100 if section_id not in doc_server.sections: 

101 raise HTTPException(status_code=404, detail="Section not found") 

102 

103 section = doc_server.sections[section_id] 

104 response = _build_base_section_response(section) 

105 

106 if context == "full": 

107 _add_full_document_context(response, section) 

108 

109 return response 

110 

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) 

121 

122if __name__ == "__main__": 

123 import uvicorn 

124 import sys 

125 import argparse 

126 

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)') 

130 

131 args = parser.parse_args() 

132 

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) 

137 

138 init_server(project_root) 

139 

140 uvicorn.run(app, host="0.0.0.0", port=args.port)