aghilsabu commited on
Commit
42c47d4
·
1 Parent(s): 0a34306

feat: add MCP (Model Context Protocol) server

Browse files
Files changed (3) hide show
  1. src/mcp/__init__.py +14 -0
  2. src/mcp/server.py +19 -0
  3. src/mcp/tools.py +99 -0
src/mcp/__init__.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MCP Server"""
2
+
3
+
4
+ def create_mcp_server():
5
+ from .server import mcp
6
+ return mcp
7
+
8
+
9
+ def run_server():
10
+ from .server import run_server as _run
11
+ _run()
12
+
13
+
14
+ __all__ = ["create_mcp_server", "run_server"]
src/mcp/server.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MCP Server - FastMCP implementation"""
2
+
3
+ import logging
4
+ from fastmcp import FastMCP
5
+
6
+ logger = logging.getLogger("codeatlas.mcp")
7
+
8
+ mcp = FastMCP("CodeAtlas")
9
+
10
+ from . import tools # noqa: F401, E402
11
+
12
+
13
+ def run_server():
14
+ logger.info("Starting CodeAtlas MCP Server...")
15
+ mcp.run()
16
+
17
+
18
+ if __name__ == "__main__":
19
+ run_server()
src/mcp/tools.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """MCP Tools - CodeAtlas functions exposed via MCP protocol"""
2
+
3
+ import logging
4
+ from typing import Optional
5
+
6
+ from .server import mcp
7
+ from ..core.repository import RepositoryLoader
8
+ from ..core.analyzer import CodeAnalyzer
9
+ from ..core.diagram import DiagramGenerator
10
+
11
+ logger = logging.getLogger("codeatlas.mcp.tools")
12
+
13
+
14
+ @mcp.tool()
15
+ def analyze_codebase(api_key: str, github_url: Optional[str] = None, file_path: Optional[str] = None, model_name: str = "gemini-2.5-flash") -> str:
16
+ """Analyze a codebase and generate a Graphviz DOT architecture diagram."""
17
+ logger.info(f"analyze_codebase: url={github_url}, file={file_path}, model={model_name}")
18
+
19
+ if not github_url and not file_path:
20
+ return "Error: Please provide either a GitHub URL or a file path."
21
+ if not api_key:
22
+ return "Error: API key is required."
23
+
24
+ loader = RepositoryLoader()
25
+ result = loader.load_from_file(file_path) if file_path else loader.load_from_github(github_url)
26
+
27
+ if result.error:
28
+ return f"Error: {result.error}"
29
+
30
+ logger.info(f"Loaded: {result.stats.files_processed} files, {result.stats.total_characters:,} chars")
31
+
32
+ analyzer = CodeAnalyzer(api_key=api_key, model_name=model_name)
33
+ analysis = analyzer.generate_diagram(result.context)
34
+
35
+ if not analysis.success:
36
+ return f"Error: {analysis.error}"
37
+
38
+ DiagramGenerator()._save_diagram(analysis.content, "raw", result.repo_name)
39
+ return analysis.content
40
+
41
+
42
+ @mcp.tool()
43
+ def get_architecture_summary(api_key: str, github_url: Optional[str] = None, file_path: Optional[str] = None, model_name: str = "gemini-2.5-flash") -> str:
44
+ """Generate a text summary of a codebase's architecture."""
45
+ logger.info(f"get_architecture_summary: url={github_url}, file={file_path}")
46
+
47
+ if not github_url and not file_path:
48
+ return "Error: Please provide either a GitHub URL or a file path."
49
+ if not api_key:
50
+ return "Error: API key is required."
51
+
52
+ loader = RepositoryLoader()
53
+ result = loader.load_from_file(file_path) if file_path else loader.load_from_github(github_url)
54
+
55
+ if result.error:
56
+ return f"Error: {result.error}"
57
+
58
+ analyzer = CodeAnalyzer(api_key=api_key, model_name=model_name)
59
+ analysis = analyzer.generate_summary(result.context)
60
+
61
+ return analysis.content if analysis.success else f"Error: {analysis.error}"
62
+
63
+
64
+ @mcp.tool()
65
+ def chat_with_codebase(api_key: str, question: str, github_url: Optional[str] = None, file_path: Optional[str] = None, model_name: str = "gemini-2.5-flash") -> str:
66
+ """Ask questions about a codebase and get AI-powered answers."""
67
+ logger.info(f"chat_with_codebase: question={question[:50]}...")
68
+
69
+ if not github_url and not file_path:
70
+ return "Error: Please provide either a GitHub URL or a file path."
71
+ if not api_key:
72
+ return "Error: API key is required."
73
+ if not question or not question.strip():
74
+ return "Error: Please provide a question."
75
+
76
+ loader = RepositoryLoader()
77
+ result = loader.load_from_file(file_path) if file_path else loader.load_from_github(github_url)
78
+
79
+ if result.error:
80
+ return f"Error: {result.error}"
81
+
82
+ analyzer = CodeAnalyzer(api_key=api_key, model_name=model_name)
83
+ response = analyzer.chat(question, result.context)
84
+
85
+ return response.content if response.success else f"Error: {response.error}"
86
+
87
+
88
+ @mcp.tool()
89
+ def list_recent_analyses() -> str:
90
+ """List recently analyzed codebases."""
91
+ history = DiagramGenerator().get_history(limit=10)
92
+
93
+ if not history:
94
+ return "No recent analyses found."
95
+
96
+ lines = ["Recent Analyses:"]
97
+ for i, d in enumerate(history, 1):
98
+ lines.append(f"{i}. {d.repo_name} — {d.formatted_timestamp}")
99
+ return "\n".join(lines)