From f492109eb76ab4837456d5643307bf5515bc316f Mon Sep 17 00:00:00 2001 From: saidsurucu Date: Sun, 13 Jul 2025 22:31:34 +0300 Subject: [PATCH] add health check tool --- mcp_server_main.py | 146 +++++++++++++++++++++++++++++++++- yargitay_mcp_module/client.py | 13 +++ yargitay_mcp_module/models.py | 8 +- 3 files changed, 161 insertions(+), 6 deletions(-) diff --git a/mcp_server_main.py b/mcp_server_main.py index 737b22e..7400be2 100644 --- a/mcp_server_main.py +++ b/mcp_server_main.py @@ -1003,7 +1003,7 @@ async def search_yargitay_detailed( logger.info(f"Tool 'search_yargitay_detailed' called: {search_query.model_dump_json(exclude_none=True, indent=2)}") try: api_response = await yargitay_client_instance.search_detailed_decisions(search_query) - if api_response.data: + if api_response and api_response.data and api_response.data.data: # Convert to clean decision entries without arananKelime field clean_decisions = [ CleanYargitayDecisionEntry( @@ -1018,7 +1018,7 @@ async def search_yargitay_detailed( ] return CompactYargitaySearchResult( decisions=clean_decisions, - total_records=api_response.data.recordsTotal, + total_records=api_response.data.recordsTotal if api_response.data else 0, requested_page=search_query.pageNumber, page_size=search_query.pageSize) logger.warning("API response for Yargitay search did not contain expected data structure.") @@ -2321,6 +2321,148 @@ def perform_cleanup(): atexit.register(perform_cleanup) +# --- Health Check Tools --- +@app.tool( + description="Check if Turkish government legal database servers are operational", + annotations={ + "readOnlyHint": True, + "idempotentHint": True + } +) +async def check_government_servers_health() -> Dict[str, Any]: + """Check health status of Turkish government legal database servers.""" + logger.info("Health check tool called for government servers") + + health_results = {} + + # Check Yargıtay server + try: + yargitay_payload = { + "data": { + "aranan": "karar", + "arananKelime": "karar", + "pageSize": 10, + "pageNumber": 1 + } + } + + async with httpx.AsyncClient( + headers={ + "Accept": "*/*", + "Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7", + "Connection": "keep-alive", + "Content-Type": "application/json; charset=UTF-8", + "Origin": "https://karararama.yargitay.gov.tr", + "Referer": "https://karararama.yargitay.gov.tr/", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36", + "X-Requested-With": "XMLHttpRequest" + }, + timeout=30.0, + verify=False + ) as client: + response = await client.post( + "https://karararama.yargitay.gov.tr/aramalist", + json=yargitay_payload + ) + + if response.status_code == 200: + response_data = response.json() + records_total = response_data.get("data", {}).get("recordsTotal", 0) + + if records_total > 0: + health_results["yargitay"] = { + "status": "healthy", + "records_total": records_total, + "response_time_ms": response.elapsed.total_seconds() * 1000 + } + else: + health_results["yargitay"] = { + "status": "unhealthy", + "reason": "recordsTotal is 0 or missing", + "response_time_ms": response.elapsed.total_seconds() * 1000 + } + else: + health_results["yargitay"] = { + "status": "unhealthy", + "reason": f"HTTP {response.status_code}", + "response_time_ms": response.elapsed.total_seconds() * 1000 + } + + except Exception as e: + health_results["yargitay"] = { + "status": "unhealthy", + "reason": f"Connection error: {str(e)}" + } + + # Check Bedesten API server + try: + bedesten_payload = { + "phrase": "karar", + "itemTypeList": ["YARGITAYKARARI"], + "pageSize": 5, + "page": 1 + } + + async with httpx.AsyncClient( + headers={ + "Content-Type": "application/json", + "Accept": "application/json", + "User-Agent": "Mozilla/5.0 Health Check" + }, + timeout=30.0, + verify=False + ) as client: + response = await client.post( + "https://bedesten.adalet.gov.tr/api/search", + json=bedesten_payload + ) + + if response.status_code == 200: + response_data = response.json() + total_found = response_data.get("totalFound", 0) + + if total_found > 0: + health_results["bedesten"] = { + "status": "healthy", + "total_found": total_found, + "response_time_ms": response.elapsed.total_seconds() * 1000 + } + else: + health_results["bedesten"] = { + "status": "unhealthy", + "reason": "totalFound is 0 or missing", + "response_time_ms": response.elapsed.total_seconds() * 1000 + } + else: + health_results["bedesten"] = { + "status": "unhealthy", + "reason": f"HTTP {response.status_code}", + "response_time_ms": response.elapsed.total_seconds() * 1000 + } + + except Exception as e: + health_results["bedesten"] = { + "status": "unhealthy", + "reason": f"Connection error: {str(e)}" + } + + # Overall health assessment + healthy_servers = sum(1 for server in health_results.values() if server["status"] == "healthy") + total_servers = len(health_results) + + overall_status = "healthy" if healthy_servers == total_servers else "degraded" if healthy_servers > 0 else "unhealthy" + + return { + "overall_status": overall_status, + "healthy_servers": healthy_servers, + "total_servers": total_servers, + "servers": health_results, + "check_timestamp": f"{__import__('datetime').datetime.now().isoformat()}" + } + # --- MCP Tools for KVKK --- @app.tool( description="Search KVKK decisions using Brave Search API for data protection authority decisions. Before using, read docs://tools/kvkk", diff --git a/yargitay_mcp_module/client.py b/yargitay_mcp_module/client.py index a2a898b..ef0ef7e 100644 --- a/yargitay_mcp_module/client.py +++ b/yargitay_mcp_module/client.py @@ -65,6 +65,19 @@ class YargitayOfficialApiClient: response.raise_for_status() # Raise an exception for HTTP 4xx or 5xx status codes response_json_data = response.json() + logger.debug(f"YargitayOfficialApiClient: Raw API response: {response_json_data}") + + # Handle None or empty data response from API + if response_json_data is None: + logger.warning("YargitayOfficialApiClient: API returned None response") + response_json_data = {"data": {"data": [], "recordsTotal": 0, "recordsFiltered": 0}} + elif not isinstance(response_json_data, dict): + logger.warning(f"YargitayOfficialApiClient: API returned unexpected response type: {type(response_json_data)}") + response_json_data = {"data": {"data": [], "recordsTotal": 0, "recordsFiltered": 0}} + elif response_json_data.get("data") is None: + logger.warning("YargitayOfficialApiClient: API response data field is None") + response_json_data["data"] = {"data": [], "recordsTotal": 0, "recordsFiltered": 0} + # Validate and parse the response using Pydantic models api_response = YargitayApiSearchResponse(**response_json_data) diff --git a/yargitay_mcp_module/models.py b/yargitay_mcp_module/models.py index b784819..9ea8ecc 100644 --- a/yargitay_mcp_module/models.py +++ b/yargitay_mcp_module/models.py @@ -74,14 +74,14 @@ class YargitayApiDecisionEntry(BaseModel): class YargitayApiResponseInnerData(BaseModel): """Model for the inner 'data' object in the Yargitay API search response.""" - data: List[YargitayApiDecisionEntry] + data: List[YargitayApiDecisionEntry] = Field(default_factory=list) # draw: Optional[int] = None # Typically used by DataTables, not essential for MCP - recordsTotal: int # Total number of records matching the query - recordsFiltered: int # Total number of records after filtering (usually same as recordsTotal) + recordsTotal: int = Field(default=0) # Total number of records matching the query + recordsFiltered: int = Field(default=0) # Total number of records after filtering (usually same as recordsTotal) class YargitayApiSearchResponse(BaseModel): """Model for the complete search response from the Yargitay API.""" - data: YargitayApiResponseInnerData + data: Optional[YargitayApiResponseInnerData] = Field(default_factory=lambda: YargitayApiResponseInnerData()) # metadata: Optional[Dict[str, Any]] = None # Optional metadata from API class YargitayDocumentMarkdown(BaseModel):