Spaces:
Sleeping
Sleeping
File size: 16,206 Bytes
9d6b77b 3516027 19c605a acc20c1 5b53be2 3516027 e948c11 3516027 329c6dd 19c605a acc20c1 7f82b3d acc20c1 7f82b3d 3516027 19c605a 3516027 19c605a 3516027 19c605a 3516027 e948c11 3516027 f5a007a 8557742 e948c11 f5a007a e948c11 f5a007a e948c11 f5a007a 3516027 e948c11 3516027 e948c11 7f82b3d ef321a7 e8f51cf 329c6dd 13489f0 b823bbe 13489f0 329c6dd e8f51cf 3516027 19c605a 7f82b3d 19c605a 3516027 9f06dc4 19c605a e8f51cf 51d0f96 3516027 19c605a 5b53be2 5370a22 19c605a 533df7e 19c605a 533df7e e948c11 533df7e 3516027 329c6dd 533df7e 3516027 525e485 8557742 525e485 ef321a7 525e485 329c6dd 525e485 19c605a 7f82b3d 19c605a 525e485 19c605a 525e485 19c605a 5b53be2 5370a22 19c605a 525e485 329c6dd 525e485 3516027 363d5b1 ef321a7 363d5b1 329c6dd 363d5b1 19c605a 7f82b3d 8557742 363d5b1 19c605a 363d5b1 19c605a 5b53be2 5370a22 19c605a 363d5b1 329c6dd 363d5b1 3516027 8557742 e4fc996 7693ee9 19c605a 8557742 e4fc996 7693ee9 fd927e8 7693ee9 8557742 7693ee9 19c605a 8557742 e4fc996 7693ee9 8557742 7693ee9 19c605a 8557742 e4fc996 7693ee9 8557742 7693ee9 8557742 7693ee9 8557742 7693ee9 19c605a 8557742 e4fc996 8557742 7693ee9 9d6b77b 7693ee9 8557742 e4fc996 8557742 7693ee9 3516027 19c605a ddbbdce 4c2b48a ddbbdce 8557742 4c2b48a 8557742 3516027 876fb90 acc20c1 d255229 acc20c1 329c6dd 5b53be2 24961f7 7f82b3d 5b53be2 acc20c1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
from typing import AsyncIterator
from contextlib import asynccontextmanager
import httpx
from fastmcp import FastMCP, Context
from loguru import logger
from dataclasses import dataclass
import uuid
import os
import sys
from utils.config import API_CONFIG
from utils.model import MultiModuleRequest, SingleModuleRequest, SearchSpaceRoutingRequest
from fastmcp.server.dependencies import get_http_request
from starlette.requests import Request
from dotenv import load_dotenv
load_dotenv()
@dataclass
class DSContext:
"""Typed context for DS MCP server"""
client: httpx.AsyncClient
@asynccontextmanager
async def ds_lifespan(server: FastMCP) -> AsyncIterator[DSContext]:
client = httpx.AsyncClient(timeout=API_CONFIG["timeout"])
ctx = DSContext(client=client)
try:
yield ctx
finally:
await client.aclose()
mcp = FastMCP(name="defect-solver-server", lifespan=ds_lifespan)
@mcp.tool(
name="multi_module_bug_localization",
description=(
"""
Performs bug localization across the entire microservice architecture by first identifying likely microservices
(search spaces) and then locating the bug-related files within them.
Use this tool when you do not know which microservice is responsible for the bug.
Args:
request (MultiModuleRequest): Issue key and descriptive details from the bug report.
ctx (Context): MCP context object.
Returns:
dict: Includes selected microservices and localized file paths.
"""
),
)
async def multi_module_bug_localization(request: MultiModuleRequest, ctx: Context) -> dict:
ds_ctx: DSContext = ctx.request_context.lifespan_context
client = ds_ctx.client
api_url = API_CONFIG["api_base_url"] + API_CONFIG["api_multimodule_endpoint"]
# hf_token to access private HF Space serving Defect Solver API
hf_token = API_CONFIG.get("hf_access_token", None)
# Get per-user API key from MCP Context
user_request: Request = get_http_request()
logger.info("Received request headers: {}".format(user_request.headers))
defect_solver_api_key = user_request.headers.get("DS-API-Key", None)
logger.info("defect_solver_api_key: {}".format(defect_solver_api_key) )
logger.info(
"Using API key: {}".format(defect_solver_api_key)
if defect_solver_api_key
else "No Defect Solver API key provided"
)
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {hf_token}" if hf_token else None,
"DS-API-Key": defect_solver_api_key,
}
headers = {k: v for k, v in headers.items() if v is not None}
try:
# Generate a UUID for issue_key even if provided (overwrite)
request.issue_key = str(uuid.uuid4())
logger.info(
f"Sending multi-module bug localization request to {api_url} with data: {str(request.model_dump())[:300]}..."
)
response = await client.post(
api_url,
json={
"key": request.issue_key,
"fields": {
"summary": request.summary,
"description": request.description,
},
},
headers=headers,
)
response.raise_for_status()
logger.info(f"Received response: {str(response.text)[:300]}...")
logger.debug(f"Response JSON: {response.json()}")
return {"issue_key": request.issue_key, "result": response.json()}
except httpx.HTTPStatusError as e:
logger.error(
f"HTTP error {e.response.status_code} from multimodule bug localization endpoint: {e.response.text}"
)
return {"error": f"HTTP error: {e.response.status_code}", "details": e.response.text}
except httpx.RequestError as e:
logger.error(f"Request error while calling multimodule bug localization endpoint: {e}")
return {"error": "Request error", "details": str(e)}
except Exception as e:
logger.error("Unexpected error during multimodule bug localization request")
return {"error": "Unexpected error", "details": str(e)}
@mcp.tool(
name="single_module_bug_localization",
description=(
"""
Performs bug localization within a specific module (microservice) of a microservice architecture.
Use this tool when you already know the module where the bug is likely located.
Args:
request (SingleModuleRequest): Bug description, issue key, and the known module name.
ctx (Context): The MCP context containing the lifespan context.
Returns:
dict: Includes the selected module and localized file paths.
"""
),
)
async def single_module_bug_localization(request: SingleModuleRequest, ctx: Context) -> dict:
ds_ctx: DSContext = ctx.request_context.lifespan_context
client = ds_ctx.client
api_url = API_CONFIG["api_base_url"] + API_CONFIG["api_singlemodule_endpoint"]
# hf token to access Defect Solver API
hf_token = API_CONFIG.get("hf_access_token", None)
# Get per-user API key from MCP Context
user_request: Request = get_http_request()
defect_solver_api_key = user_request.headers.get("DS-API-Key", None)
logger.info(
"Using API key: {}".format(defect_solver_api_key)
if defect_solver_api_key
else "No Defect Solver API key provided"
)
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {hf_token}" if hf_token else None,
"DS-API-Key": defect_solver_api_key,
}
headers = {k: v for k, v in headers.items() if v is not None}
try:
# Generate a UUID for issue_key even if provided (overwrite)
request.issue_key = str(uuid.uuid4())
logger.info(
f"Sending single-module bug localization request to {api_url} with data: {str(request.model_dump())[:300]}..."
)
response = await client.post(
api_url,
json={
"key": request.issue_key,
"fields": {
"summary": request.summary,
"description": request.description,
"module": request.module,
},
},
headers=headers,
)
response.raise_for_status()
logger.info(f"Received response: {str(response.text)[:300]}...")
logger.debug(f"Response JSON: {response.json()}")
return {"issue_key": request.issue_key, "result": response.json()}
except httpx.HTTPStatusError as e:
logger.error(f"HTTP error {e.response.status_code} from endpoint: {e.response.text}")
return {"error": f"HTTP error: {e.response.status_code}", "details": e.response.text}
except httpx.RequestError as e:
logger.error(f"Request error while calling endpoint: {e}")
return {"error": "Request error", "details": str(e)}
except Exception as e:
logger.error("Unexpected error during single-module bug localization")
return {"error": "Unexpected error", "details": str(e)}
@mcp.tool(
name="search_space_routing",
description=(
"""
Identifies and returns the most likely microservices (search spaces) that could be the source of a reported bug.
Use this tool when the source of the bug is unknown and you want to narrow down the investigation to top candidate microservices.
Args:
request (SearchSpaceRoutingRequest): Bug description and optional issue key/summary.
ctx (Context): The MCP context with lifespan context.
Returns:
dict: A message and the list of selected search spaces (microservices).
"""
),
)
async def search_space_routing(request: SearchSpaceRoutingRequest, ctx: Context) -> dict:
ds_ctx: DSContext = ctx.request_context.lifespan_context
client = ds_ctx.client
api_url = API_CONFIG["api_base_url"] + API_CONFIG["api_searchspace_endpoint"]
# hf_token to access private HF Space serving Defect Solver API
hf_token = API_CONFIG.get("hf_access_token", None)
# Get per-user API key from MCP Context
user_request: Request = get_http_request()
defect_solver_api_key = user_request.headers.get("DS-API-Key", None)
logger.info(
"Using API key: {}".format(defect_solver_api_key)
if defect_solver_api_key
else "No Defect Solver API key provided"
)
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {hf_token}" if hf_token else None,
"DS-API-Key": defect_solver_api_key,
}
headers = {k: v for k, v in headers.items() if v is not None}
try:
# Generate a UUID for issue_key even if provided (overwrite)
request.issue_key = str(uuid.uuid4())
logger.info(
f"Sending search space routing request to {api_url} with data: {str(request.model_dump())[:300]}..."
)
response = await client.post(
api_url,
json={
"key": request.issue_key,
"fields": {
"summary": request.summary,
"description": request.description,
},
},
headers=headers,
)
response.raise_for_status()
logger.info(f"Received response: {str(response.text)[:300]}...")
logger.debug(f"Response JSON: {response.json()}")
return {"issue_key": request.issue_key, "result": response.json()}
except httpx.HTTPStatusError as e:
logger.error(f"HTTP error {e.response.status_code} from endpoint: {e.response.text}")
return {"error": f"HTTP error: {e.response.status_code}", "details": e.response.text}
except httpx.RequestError as e:
logger.error(f"Request error while calling endpoint: {e}")
return {"error": "Request error", "details": str(e)}
except Exception as e:
logger.error("Unexpected error during search space routing")
return {"error": "Unexpected error", "details": str(e)}
@mcp.prompt(title="Select Bug Localization Tool")
async def prompt_select_tool() -> str:
return """
You have access to bug localization tools in this MCP server. Determine the best approach for the current bug based on the description provided in this conversation.
Recommend:
1. **Primary Tool** - Which tool to use first and why
2. **Tool Sequence** - If multiple tools needed, in what order
3. **Reasoning** - Why this approach is optimal
4. **Expected Workflow** - Step-by-step tool usage plan
Provide specific tool recommendations with rationale.
"""
@mcp.prompt(title="Localize and Find Bug Using Selected Tool")
async def prompt_find_bug() -> str:
return """
You are using a bug localization tool to identify potential files related to the bug described in this conversation. Execute the following steps:
1. **Input the augmented bug description** - Use the detailed description from the previous step
2. **Run the localization tool** - Execute the tool to find areas related to the bug
3. **Collect results** - Gather the results identified by the tool
4. **Return results** - Provide the results and any additional metadata
The tool will return info that are likely related to the bug.
"""
@mcp.prompt(title="Explain Localization Results")
async def prompt_explain() -> str:
return """
You received a response from a bug localization tool in this conversation. Interpret and explain the results for actionable next steps.
Analyze and provide:
1. **Key Files Identified** - Prioritized list of files to investigate
2. **Investigation Order** - Which files to check first and why
3. **Code Patterns to Look For** - Specific methods, classes, or patterns
4. **Next Tool Recommendations** - Should you use single_module on specific modules?
5. **Confidence Assessment** - How reliable are these results?
Provide actionable guidance for the developer's next steps.
"""
@mcp.prompt(title="Fix Localized Bug")
async def prompt_fix_bug() -> str:
return """
You have codebase access. A bug localization tool has identified files as potential starting points in this conversation.
Instructions:
1. **Directly fix the bug** - Use the identified files as entry points, but your goal is to discover the root cause and apply the necessary code changes.
2. **Explore the codebase** - Investigate related code, dependencies, and connections as needed.
3. **Make concrete changes** - Show the exact code modifications required to resolve the bug, with before/after code snippets.
4. **Do not just analyze** - You must provide and apply the fix, not just discuss possible causes.
5. **Suggest verification** - Briefly describe how to test and confirm the fix works.
Your task is to discover, modify, and fix the bug in the codebase using the provided starting points.
NOTE: If you cannot find the bug and fix it, provide a brief explanation of your investigation process and why you could not resolve it.
"""
@mcp.prompt(title="Full Bug Resolution Workflow")
async def prompt_full_workflow() -> str:
return """
You are a bug resolution expert with access to this codebase and bug localization tools. Complete the full workflow from bug description to fix.
Follow this workflow:
1. **AUGMENT** - First, examine the codebase and augment the bug description with technical details, specific modules, file types, and architectural context.
2. **ROUTE** - Decide which bug localization tool to use:
- search_space_routing: If you don't know which microservices are involved
- multi_module_bug_localization: If multiple modules likely affected
- single_module_bug_localization: If you know the specific module
3. **LOCALIZE** - Call the appropriate tool(s) with the augmented description.
4. **INTERPRET** - Analyze the localization results to identify key files and investigation priorities.
5. **FIX** - Examine the identified files (as starting points), find the actual bug, and provide concrete code fixes.
Execute each step and provide the final resolution with specific code changes.
===== Bug Description =====
<insert_here>
"""
@mcp.prompt(title="Augment Bug report with Technical Details")
async def prompt_augment_bug_report() -> str:
return """
You are a bug localization expert with access to this codebase. Your task is to augment the bug description from this conversation with technical details that help pinpoint the bug location.
Steps:
1. **Analyze the codebase** - Examine project structure, modules, and architectural patterns
2. **Map bug to code** - Identify likely affected areas based on the bug symptoms
3. **Augment the description** - Add:
- Specific module/package/class names that might be involved
- File types, naming patterns, and directory paths
- Technical keywords, error types, and exception names
- Affected architectural layers and components
- Cross-cutting concerns and dependencies
- External system interactions or API calls
- Component relationships that could cause the bug
Return a 2-3x more detailed bug report with precise technical terms for effective bug localization, while preserving the original description.
===== Bug Description =====
<insert_here>
"""
@mcp.prompt(title="Revise Bug Report")
async def prompt_revise_bug_report() -> str:
return """
Enhance the bug description from this conversation by adding relevant technical context that supports precise root cause analysis.
Return a 2-3x more detailed/enhanced bug report with precise technical terms for effective bug localization, while preserving the original description.
===== Bug Description =====
<insert_here>
"""
if __name__ == "__main__":
TRANSPORT_MODE = os.getenv("TRANSPORT_MODE", "http")
HOST = os.getenv("HOST", "0.0.0.0")
PORT = int(os.getenv("PORT", "7860"))
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
# Configure logger
logger.remove()
logger.add(sys.stderr, level=LOG_LEVEL, format="{level}:\t\t{time:YYYY-MM-DD HH:mm:ss} - {message}")
mcp.run(transport=TRANSPORT_MODE, host=HOST, port=PORT, log_level=LOG_LEVEL)
|