SURIAPRAKASH1 commited on
Commit
ffc60b1
·
1 Parent(s): 809e4be

main -> server

Browse files
Files changed (1) hide show
  1. main.py +0 -338
main.py DELETED
@@ -1,338 +0,0 @@
1
- from mcp.server.fastmcp import FastMCP
2
- import httpx
3
-
4
- from pathlib import Path
5
- import subprocess
6
- from typing import Any, Literal, Optional
7
- from bs4 import BeautifulSoup
8
- import logging, os, json
9
- from dotenv import load_dotenv
10
- load_dotenv()
11
-
12
- # -----------
13
- # Logging
14
- # ------------
15
- logger = logging.getLogger(__name__)
16
- logger.setLevel(logger.debug)
17
-
18
- # formatter
19
- fmt = logging.Formatter("%(asctime)s -- %(name)s -- %(levelname)s -- %(message)s")
20
-
21
- # handlers
22
- # console_handler = logging.StreamHandler()
23
- file_handler = logging.FileHandler(filename= "multitools-server.log")
24
-
25
- # add to logger
26
- # logger.addHandler(console_handler)
27
- logger.addHandler(file_handler.setFormatter(fmt))
28
-
29
-
30
- # -------------------------
31
- # Initiating FastMCP server
32
- # -------------------------
33
- mcp = FastMCP("multitools-server")
34
-
35
-
36
- # --------------
37
- # Configuration
38
- #---------------
39
- BASE_CRICKET_URL = os.environ.get("BASE_CRICKET_URI", "False")
40
-
41
- # PR template directory (shared across all modules)
42
- TEMPLATES_DIR = Path(__file__).parent / "templates"
43
-
44
- # Default PR templates
45
- DEFAULT_TEMPLATES = {
46
- "bug.md": "Bug Fix",
47
- "feature.md": "Feature",
48
- "docs.md": "Documentation",
49
- "refactor.md": "Refactor",
50
- "test.md": "Test",
51
- "performance.md": "Performance",
52
- "security.md": "Security"
53
- }
54
-
55
- # Type mapping for PR templates
56
- TYPE_MAPPING = {
57
- "bug": "bug.md",
58
- "fix": "bug.md",
59
- "feature": "feature.md",
60
- "enhancement": "feature.md",
61
- "docs": "docs.md",
62
- "documentation": "docs.md",
63
- "refactor": "refactor.md",
64
- "cleanup": "refactor.md",
65
- "test": "test.md",
66
- "testing": "test.md",
67
- "performance": "performance.md",
68
- "optimization": "performance.md",
69
- "security": "security.md"
70
- }
71
-
72
-
73
- # ----------------------
74
- # Available tools for LLM
75
- # -----------------------
76
-
77
- async def cricket_source(mode: str) -> str:
78
- """Fetches whole html from source url then extracts html container that contains necessary details"""
79
-
80
- if mode == "live":
81
- url = f"{BASE_CRICKET_URL}/cricket-match/live-scores"
82
- elif mode == 'upcomming':
83
- url = f"{BASE_CRICKET_URL}/cricket-match/live-scores/upcoming-matches"
84
- else:
85
- error = f"Not Implemented: Currently there's no implementation to handle {mode}. Only handels live, upcomming"
86
- logger.error(msg= error)
87
- return json.dumps({"error": error})
88
-
89
- try:
90
- async with httpx.AsyncClient(timeout= 10.0) as client:
91
- response = await client.get(url= url)
92
- response.raise_for_status() # if not 2xx it will raise HTTP error
93
- except httpx.HTTPError as e:
94
- logger.error("\n%s", e)
95
- return json.dumps({'error': str(e)})
96
- except Exception as e:
97
- logger.error("\n%s", e)
98
- return json.dumps({'error': str(e)})
99
-
100
- if response:
101
- # convert htmldoc content to proper html form using bs
102
- html = BeautifulSoup(response.content, "html.parser")
103
- # find where the content is
104
- content = html.find("div", class_= 'cb-col cb-col-100 cb-rank-tabs')
105
- return json.dumps({'output': content})
106
- else:
107
- return json.dumps({"error": "No Available details right now!"})
108
-
109
- @mcp.tool()
110
- async def fetch_live_cricket_details(mode: Literal["live", "upcomming"])-> str:
111
- """ Get cricket live or upcomming match details
112
- Args:
113
- mode : Either "live" or "upcomming"
114
- """
115
-
116
- response = await cricket_source(mode.strip().lower())
117
- data = json.loads(response)
118
-
119
- if data['error']:
120
- return response
121
- live_details = data['content'].get_text(separator = "\n", strip = True)
122
- return json.dumps({'output': str(live_details)})
123
-
124
- @mcp.tools()
125
- async def live_cricket_scorecard_herf()-> str:
126
- """Returns string of comma separated anchor tags contains herf attributes that pointing to live cricket scorecards """
127
-
128
- response = await cricket_source("live")
129
- data = json.loads(response)
130
- if data['error']:
131
- return response
132
- herfs_list = data["content"].find_all("a", class_ = "cb-text-link cb-mtch-lnks") # here don't know is it possible
133
- herfs_string = ",".join(str(tag) for tag in herfs_list)
134
- return json.dumps({'output': herfs_string})
135
-
136
-
137
- @mcp.tool()
138
- async def live_cricket_scorecard(herf: str)-> str:
139
- """Live cricket match scorecard details for given herf.
140
- (e.g, herf = "/live-cricket-scorecard/119495/cd-vs-hbh-7th-match-global-super-league-2025")
141
-
142
- Args:
143
- herf (str): herf for scorescard endpoint
144
- """
145
- scorecard_url = f"{BASE_CRICKET_URL}{herf}"
146
-
147
- try:
148
- with httpx.AsyncClient(timeout= 10.0) as client:
149
- response = client.get(url = scorecard_url)
150
- response.raise_for_status()
151
- except httpx.HTTPError as e:
152
- logger.error("\n%s", e)
153
- return json.dumps({"error": str(e)})
154
- except Exception as e:
155
- logger.error("\n%s", e)
156
- return json.dumps({'error': str(e)})
157
-
158
- # extract html container
159
- if response:
160
- html = BeautifulSoup(response.content, "html.parser")
161
- live_scorecard = html.find("div", timeout = "30000")
162
- details = live_scorecard.get_text(separator="\n", strip=True)
163
- return json.dumps({'output': str(details)})
164
- else:
165
- return json.dumps({'error': "No Available details right now"})
166
-
167
-
168
- @mcp.tool()
169
- async def analyze_file_changes(
170
- base_branch: str = "main",
171
- include_diff: bool = True,
172
- max_diff_lines: int = 400,
173
- working_directory: Optional[str] = None
174
- ) -> str:
175
- """Get the full diff and list of changed files in the current git repository.
176
-
177
- Args:
178
- base_branch: Base branch to compare against (default: main)
179
- include_diff: Include the full diff content (default: true)
180
- max_diff_lines: Maximum number of diff lines to include (default: 400)
181
- working_directory: Directory to run git commands in (default: current directory)
182
- """
183
- try:
184
- # Try to get working directory from roots first
185
- if working_directory is None:
186
- try:
187
- context = mcp.get_context()
188
- roots_result = await context.session.list_roots()
189
- # Get the first root - Claude Code sets this to the CWD
190
- root = roots_result.roots[0]
191
- # FileUrl object has a .path property that gives us the path directly
192
- working_directory = root.uri.path
193
- except Exception:
194
- # If we can't get roots, fall back to current directory
195
- pass
196
-
197
- # Use provided working directory or current directory
198
- cwd = working_directory if working_directory else os.getcwd()
199
-
200
- # Debug output
201
- debug_info = {
202
- "provided_working_directory": working_directory,
203
- "actual_cwd": cwd,
204
- "server_process_cwd": os.getcwd(),
205
- "server_file_location": str(Path(__file__).parent),
206
- "roots_check": None
207
- }
208
-
209
- # Add roots debug info
210
- try:
211
- context = mcp.get_context()
212
- roots_result = await context.session.list_roots()
213
- debug_info["roots_check"] = {
214
- "found": True,
215
- "count": len(roots_result.roots),
216
- "roots": [str(root.uri) for root in roots_result.roots]
217
- }
218
- except Exception as e:
219
- debug_info["roots_check"] = {
220
- "found": False,
221
- "error": str(e)
222
- }
223
-
224
- # Get list of changed files
225
- files_result = subprocess.run(
226
- ["git", "diff", "--name-status", f"{base_branch}...HEAD"],
227
- capture_output=True,
228
- text=True,
229
- check=True,
230
- cwd=cwd
231
- )
232
-
233
- # Get diff statistics
234
- stat_result = subprocess.run(
235
- ["git", "diff", "--stat", f"{base_branch}...HEAD"],
236
- capture_output=True,
237
- text=True,
238
- cwd=cwd
239
- )
240
-
241
- # Get the actual diff if requested
242
- diff_content = ""
243
- truncated = False
244
- if include_diff:
245
- diff_result = subprocess.run(
246
- ["git", "diff", f"{base_branch}...HEAD"],
247
- capture_output=True,
248
- text=True,
249
- cwd=cwd
250
- )
251
- diff_lines = diff_result.stdout.split('\n')
252
-
253
- # Check if we need to truncate
254
- if len(diff_lines) > max_diff_lines:
255
- diff_content = '\n'.join(diff_lines[:max_diff_lines])
256
- diff_content += f"\n\n... Output truncated. Showing {max_diff_lines} of {len(diff_lines)} lines ..."
257
- diff_content += "\n... Use max_diff_lines parameter to see more ..."
258
- truncated = True
259
- else:
260
- diff_content = diff_result.stdout
261
-
262
- # Get commit messages for context
263
- commits_result = subprocess.run(
264
- ["git", "log", "--oneline", f"{base_branch}..HEAD"],
265
- capture_output=True,
266
- text=True,
267
- cwd=cwd
268
- )
269
-
270
- analysis = {
271
- "base_branch": base_branch,
272
- "files_changed": files_result.stdout,
273
- "statistics": stat_result.stdout,
274
- "commits": commits_result.stdout,
275
- "diff": diff_content if include_diff else "Diff not included (set include_diff=true to see full diff)",
276
- "truncated": truncated,
277
- "total_diff_lines": len(diff_lines) if include_diff else 0,
278
- "_debug": debug_info
279
- }
280
-
281
- return json.dumps(analysis, indent=2)
282
-
283
- except subprocess.CalledProcessError as e:
284
- return json.dumps({"error": f"Git error: {e.stderr}"})
285
- except Exception as e:
286
- return json.dumps({"error": str(e)})
287
-
288
-
289
-
290
- @mcp.tool()
291
- async def get_pr_templates() -> str:
292
- """List available PR templates with their content."""
293
- templates = [
294
- {
295
- "filename": filename,
296
- "type": template_type,
297
- "content": (TEMPLATES_DIR / filename).read_text()
298
- }
299
- for filename, template_type in DEFAULT_TEMPLATES.items()
300
- ]
301
-
302
- return json.dumps(templates, indent=2)
303
-
304
-
305
-
306
- @mcp.tool()
307
- async def suggest_template(changes_summary: str, change_type: str) -> str:
308
- """Let LLM analyze the changes and suggest the most appropriate PR template.
309
-
310
- Args:
311
- changes_summary: Your analysis of what the changes do
312
- change_type: The type of change you've identified (bug, feature, docs, refactor, test, etc.)
313
- """
314
-
315
- # Get available templates
316
- templates_response = await get_pr_templates()
317
- templates = json.loads(templates_response)
318
-
319
- # Find matching template
320
- template_file = TYPE_MAPPING.get(change_type.lower(), "feature.md")
321
- selected_template = next(
322
- (t for t in templates if t["filename"] == template_file),
323
- templates[0] # Default to first template if no match
324
- )
325
-
326
- suggestion = {
327
- "recommended_template": selected_template,
328
- "reasoning": f"Based on your analysis: '{changes_summary}', this appears to be a {change_type} change.",
329
- "template_content": selected_template["content"],
330
- "usage_hint": "LLM can help you fill out this template based on the specific changes in your PR."
331
- }
332
-
333
- return json.dumps(suggestion, indent=2)
334
-
335
- if __name__ == "__main__":
336
- mcp.run()
337
-
338
-