Spaces:
Running
Running
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() | |
class DSContext: | |
"""Typed context for DS MCP server""" | |
client: httpx.AsyncClient | |
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) | |
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)} | |
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)} | |
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)} | |
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. | |
""" | |
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. | |
""" | |
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. | |
""" | |
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. | |
""" | |
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> | |
""" | |
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> | |
""" | |
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) | |