Spaces:
Paused
Paused
| from typing import AsyncIterator | |
| from contextlib import asynccontextmanager | |
| import httpx | |
| from fastmcp import FastMCP, Context | |
| from loguru import logger | |
| import uuid | |
| import os | |
| import sys | |
| from utils.config import API_CONFIG | |
| from utils.model import MultiModuleRequest, SingleModuleRequest, SearchSpaceRoutingRequest | |
| from utils.auth import DSContext, authenticate | |
| from fastmcp.server.dependencies import get_http_request | |
| from starlette.requests import Request | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| async def ds_lifespan(server: FastMCP) -> AsyncIterator[DSContext]: | |
| client = httpx.AsyncClient(timeout=API_CONFIG["timeout"]) | |
| token = await authenticate() | |
| if token: | |
| client.headers.update({"Authorization": f"Bearer {token}"}) | |
| ctx = DSContext(client=client, access_token=token) | |
| 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() | |
| 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 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) | |