#
tokens: 41745/50000 1/145 files (page 11/11)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 11 of 11. Use http://codebase.md/saidsurucu/yargi-mcp?lines=true&page={x} to view the full context.

# Directory Structure

```
├── __main__.py
├── .dockerignore
├── .env.example
├── .gitattributes
├── .github
│   └── workflows
│       └── publish.yml
├── .gitignore
├── .serena
│   ├── .gitignore
│   └── project.yml
├── 5ire-settings.png
├── analyze_kik_hash_generation.py
├── anayasa_mcp_module
│   ├── __init__.py
│   ├── bireysel_client.py
│   ├── client.py
│   ├── models.py
│   └── unified_client.py
├── asgi_app.py
├── bddk_mcp_module
│   ├── __init__.py
│   ├── client.py
│   └── models.py
├── bedesten_mcp_module
│   ├── __init__.py
│   ├── client.py
│   ├── enums.py
│   └── models.py
├── check_response_format.py
├── CLAUDE.md
├── danistay_mcp_module
│   ├── __init__.py
│   ├── client.py
│   └── models.py
├── docker-compose.yml
├── Dockerfile
├── docs
│   └── DEPLOYMENT.md
├── emsal_mcp_module
│   ├── __init__.py
│   ├── client.py
│   └── models.py
├── example_fastapi_app.py
├── fly-no-auth.toml
├── fly.toml
├── kik_mcp_module
│   ├── __init__.py
│   ├── client_v2.py
│   ├── client.py
│   ├── models_v2.py
│   └── models.py
├── kvkk_mcp_module
│   ├── __init__.py
│   ├── client.py
│   └── models.py
├── LICENSE
├── mcp_auth
│   ├── __init__.py
│   ├── clerk_config.py
│   ├── middleware.py
│   ├── oauth.py
│   ├── policy.py
│   └── storage.py
├── mcp_auth_factory.py
├── mcp_auth_http_adapter.py
├── mcp_auth_http_simple.py
├── mcp_server_main.py
├── nginx.conf
├── ornek.png
├── Procfile
├── pyproject.toml
├── railway.json
├── README.md
├── redis_session_store.py
├── rekabet_mcp_module
│   ├── __init__.py
│   ├── client.py
│   └── models.py
├── requirements.txt
├── run_asgi.py
├── saidsurucu-yargi-mcp-f5fa007
│   ├── __main__.py
│   ├── .dockerignore
│   ├── .env.example
│   ├── .gitattributes
│   ├── .github
│   │   └── workflows
│   │       └── publish.yml
│   ├── .gitignore
│   ├── 5ire-settings.png
│   ├── anayasa_mcp_module
│   │   ├── __init__.py
│   │   ├── bireysel_client.py
│   │   ├── client.py
│   │   ├── models.py
│   │   └── unified_client.py
│   ├── asgi_app.py
│   ├── bddk_mcp_module
│   │   ├── __init__.py
│   │   ├── client.py
│   │   └── models.py
│   ├── bedesten_mcp_module
│   │   ├── __init__.py
│   │   ├── client.py
│   │   ├── enums.py
│   │   └── models.py
│   ├── check_response_format.py
│   ├── danistay_mcp_module
│   │   ├── __init__.py
│   │   ├── client.py
│   │   └── models.py
│   ├── docker-compose.yml
│   ├── Dockerfile
│   ├── docs
│   │   └── DEPLOYMENT.md
│   ├── emsal_mcp_module
│   │   ├── __init__.py
│   │   ├── client.py
│   │   └── models.py
│   ├── example_fastapi_app.py
│   ├── kik_mcp_module
│   │   ├── __init__.py
│   │   ├── client.py
│   │   └── models.py
│   ├── kvkk_mcp_module
│   │   ├── __init__.py
│   │   ├── client.py
│   │   └── models.py
│   ├── LICENSE
│   ├── mcp_auth
│   │   ├── __init__.py
│   │   ├── clerk_config.py
│   │   ├── middleware.py
│   │   ├── oauth.py
│   │   ├── policy.py
│   │   └── storage.py
│   ├── mcp_auth_factory.py
│   ├── mcp_auth_http_adapter.py
│   ├── mcp_auth_http_simple.py
│   ├── mcp_server_main.py
│   ├── nginx.conf
│   ├── ornek.png
│   ├── Procfile
│   ├── pyproject.toml
│   ├── railway.json
│   ├── README.md
│   ├── redis_session_store.py
│   ├── rekabet_mcp_module
│   │   ├── __init__.py
│   │   ├── client.py
│   │   └── models.py
│   ├── run_asgi.py
│   ├── sayistay_mcp_module
│   │   ├── __init__.py
│   │   ├── client.py
│   │   ├── enums.py
│   │   ├── models.py
│   │   └── unified_client.py
│   ├── starlette_app.py
│   ├── stripe_webhook.py
│   ├── uyusmazlik_mcp_module
│   │   ├── __init__.py
│   │   ├── client.py
│   │   └── models.py
│   └── yargitay_mcp_module
│       ├── __init__.py
│       ├── client.py
│       └── models.py
├── sayistay_mcp_module
│   ├── __init__.py
│   ├── client.py
│   ├── enums.py
│   ├── models.py
│   └── unified_client.py
├── starlette_app.py
├── stripe_webhook.py
├── uv.lock
├── uyusmazlik_mcp_module
│   ├── __init__.py
│   ├── client.py
│   └── models.py
└── yargitay_mcp_module
    ├── __init__.py
    ├── client.py
    └── models.py
```

# Files

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/mcp_server_main.py:
--------------------------------------------------------------------------------

```python
   1 | # mcp_server_main.py
   2 | import asyncio
   3 | import atexit
   4 | import logging
   5 | import os
   6 | import httpx
   7 | import json
   8 | import time
   9 | from collections import defaultdict
  10 | from pydantic import HttpUrl, Field 
  11 | from typing import Optional, Dict, List, Literal, Any, Union
  12 | import urllib.parse
  13 | import tiktoken
  14 | from fastmcp.server.middleware import Middleware, MiddlewareContext
  15 | from fastmcp.server.dependencies import get_access_token, AccessToken
  16 | from fastmcp import Context
  17 | 
  18 | # Use standard exception for tool errors
  19 | class ToolError(Exception):
  20 |     """Tool execution error"""
  21 |     pass
  22 | 
  23 | # --- Logging Configuration Start ---
  24 | LOG_DIRECTORY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logs")
  25 | if not os.path.exists(LOG_DIRECTORY):
  26 |     os.makedirs(LOG_DIRECTORY)
  27 | LOG_FILE_PATH = os.path.join(LOG_DIRECTORY, "mcp_server.log")
  28 | 
  29 | root_logger = logging.getLogger()
  30 | root_logger.setLevel(logging.DEBUG) 
  31 | 
  32 | log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(threadName)s - %(message)s')
  33 | 
  34 | file_handler = logging.FileHandler(LOG_FILE_PATH, mode='a', encoding='utf-8')
  35 | file_handler.setFormatter(log_formatter)
  36 | file_handler.setLevel(logging.DEBUG)
  37 | root_logger.addHandler(file_handler)
  38 | 
  39 | console_handler = logging.StreamHandler()
  40 | console_handler.setFormatter(log_formatter)
  41 | console_handler.setLevel(logging.INFO) 
  42 | root_logger.addHandler(console_handler)
  43 | 
  44 | logger = logging.getLogger(__name__)
  45 | # --- Logging Configuration End ---
  46 | 
  47 | # --- Token Counting Middleware ---
  48 | class TokenCountingMiddleware(Middleware):
  49 |     """Middleware for counting input/output tokens using tiktoken."""
  50 |     
  51 |     def __init__(self, model: str = "cl100k_base"):
  52 |         """Initialize token counting middleware.
  53 |         
  54 |         Args:
  55 |             model: Tiktoken model name (cl100k_base for GPT-4/Claude compatibility)
  56 |         """
  57 |         self.encoder = tiktoken.get_encoding(model)
  58 |         self.model = model
  59 |         self.token_stats = defaultdict(lambda: {"input": 0, "output": 0, "calls": 0})
  60 |         self.logger = logging.getLogger("token_counter")
  61 |         
  62 |         # Create separate log file for token metrics
  63 |         token_log_path = os.path.join(LOG_DIRECTORY, "token_metrics.log")
  64 |         token_handler = logging.FileHandler(token_log_path, mode='a', encoding='utf-8')
  65 |         token_formatter = logging.Formatter('%(asctime)s - %(message)s')
  66 |         token_handler.setFormatter(token_formatter)
  67 |         token_handler.setLevel(logging.INFO)
  68 |         self.logger.addHandler(token_handler)
  69 |         self.logger.setLevel(logging.INFO)
  70 |     
  71 |     def count_tokens(self, text: str) -> int:
  72 |         """Count tokens in text using tiktoken."""
  73 |         if not text:
  74 |             return 0
  75 |         try:
  76 |             return len(self.encoder.encode(str(text)))
  77 |         except Exception as e:
  78 |             logger.warning(f"Token counting failed: {e}")
  79 |             return 0
  80 |     
  81 |     def extract_text_content(self, data: Any) -> str:
  82 |         """Extract text content from various data types."""
  83 |         if isinstance(data, str):
  84 |             return data
  85 |         elif isinstance(data, dict):
  86 |             # Extract text from common response fields
  87 |             text_parts = []
  88 |             for key, value in data.items():
  89 |                 if isinstance(value, str):
  90 |                     text_parts.append(value)
  91 |                 elif isinstance(value, list):
  92 |                     for item in value:
  93 |                         if isinstance(item, str):
  94 |                             text_parts.append(item)
  95 |                         elif isinstance(item, dict) and 'text' in item:
  96 |                             text_parts.append(str(item['text']))
  97 |             return ' '.join(text_parts)
  98 |         elif isinstance(data, list):
  99 |             text_parts = []
 100 |             for item in data:
 101 |                 text_parts.append(self.extract_text_content(item))
 102 |             return ' '.join(text_parts)
 103 |         else:
 104 |             return str(data)
 105 |     
 106 |     def log_token_usage(self, operation: str, input_tokens: int, output_tokens: int, 
 107 |                        tool_name: str = None, duration_ms: float = None):
 108 |         """Log token usage with structured format."""
 109 |         log_data = {
 110 |             "operation": operation,
 111 |             "tool_name": tool_name,
 112 |             "input_tokens": input_tokens,
 113 |             "output_tokens": output_tokens,
 114 |             "total_tokens": input_tokens + output_tokens,
 115 |             "duration_ms": duration_ms,
 116 |             "timestamp": time.time()
 117 |         }
 118 |         
 119 |         # Update statistics
 120 |         key = tool_name if tool_name else operation
 121 |         self.token_stats[key]["input"] += input_tokens
 122 |         self.token_stats[key]["output"] += output_tokens
 123 |         self.token_stats[key]["calls"] += 1
 124 |         
 125 |         # Log as JSON for easy parsing
 126 |         self.logger.info(json.dumps(log_data))
 127 |         
 128 |         # Also log human-readable format to main logger
 129 |         logger.info(f"Token Usage - {operation}" + 
 130 |                    (f" ({tool_name})" if tool_name else "") +
 131 |                    f": {input_tokens} in + {output_tokens} out = {input_tokens + output_tokens} total")
 132 |     
 133 |     async def on_call_tool(self, context: MiddlewareContext, call_next):
 134 |         """Count tokens for tool calls."""
 135 |         start_time = time.perf_counter()
 136 |         
 137 |         # Extract tool name and arguments
 138 |         tool_name = getattr(context.message, 'name', 'unknown_tool')
 139 |         tool_args = getattr(context.message, 'arguments', {})
 140 |         
 141 |         # Count input tokens (tool arguments)
 142 |         input_text = self.extract_text_content(tool_args)
 143 |         input_tokens = self.count_tokens(input_text)
 144 |         
 145 |         try:
 146 |             # Execute the tool
 147 |             result = await call_next(context)
 148 |             
 149 |             # Count output tokens (tool result)
 150 |             output_text = self.extract_text_content(result)
 151 |             output_tokens = self.count_tokens(output_text)
 152 |             
 153 |             # Calculate duration
 154 |             duration_ms = (time.perf_counter() - start_time) * 1000
 155 |             
 156 |             # Log token usage
 157 |             self.log_token_usage("tool_call", input_tokens, output_tokens, 
 158 |                                tool_name, duration_ms)
 159 |             
 160 |             return result
 161 |             
 162 |         except Exception as e:
 163 |             duration_ms = (time.perf_counter() - start_time) * 1000
 164 |             self.log_token_usage("tool_call_error", input_tokens, 0, 
 165 |                                tool_name, duration_ms)
 166 |             raise
 167 |     
 168 |     async def on_read_resource(self, context: MiddlewareContext, call_next):
 169 |         """Count tokens for resource reads."""
 170 |         start_time = time.perf_counter()
 171 |         
 172 |         # Extract resource URI
 173 |         resource_uri = getattr(context.message, 'uri', 'unknown_resource')
 174 |         
 175 |         try:
 176 |             # Execute the resource read
 177 |             result = await call_next(context)
 178 |             
 179 |             # Count output tokens (resource content)
 180 |             output_text = self.extract_text_content(result)
 181 |             output_tokens = self.count_tokens(output_text)
 182 |             
 183 |             # Calculate duration
 184 |             duration_ms = (time.perf_counter() - start_time) * 1000
 185 |             
 186 |             # Log token usage (no input tokens for resource reads)
 187 |             self.log_token_usage("resource_read", 0, output_tokens, 
 188 |                                resource_uri, duration_ms)
 189 |             
 190 |             return result
 191 |             
 192 |         except Exception as e:
 193 |             duration_ms = (time.perf_counter() - start_time) * 1000
 194 |             self.log_token_usage("resource_read_error", 0, 0, 
 195 |                                resource_uri, duration_ms)
 196 |             raise
 197 |     
 198 |     async def on_get_prompt(self, context: MiddlewareContext, call_next):
 199 |         """Count tokens for prompt retrievals."""
 200 |         start_time = time.perf_counter()
 201 |         
 202 |         # Extract prompt name
 203 |         prompt_name = getattr(context.message, 'name', 'unknown_prompt')
 204 |         
 205 |         try:
 206 |             # Execute the prompt retrieval
 207 |             result = await call_next(context)
 208 |             
 209 |             # Count output tokens (prompt content)
 210 |             output_text = self.extract_text_content(result)
 211 |             output_tokens = self.count_tokens(output_text)
 212 |             
 213 |             # Calculate duration
 214 |             duration_ms = (time.perf_counter() - start_time) * 1000
 215 |             
 216 |             # Log token usage
 217 |             self.log_token_usage("prompt_get", 0, output_tokens, 
 218 |                                prompt_name, duration_ms)
 219 |             
 220 |             return result
 221 |             
 222 |         except Exception as e:
 223 |             duration_ms = (time.perf_counter() - start_time) * 1000
 224 |             self.log_token_usage("prompt_get_error", 0, 0, 
 225 |                                prompt_name, duration_ms)
 226 |             raise
 227 |     
 228 |     def get_token_stats(self) -> Dict[str, Any]:
 229 |         """Get current token usage statistics."""
 230 |         return dict(self.token_stats)
 231 |     
 232 |     def reset_token_stats(self):
 233 |         """Reset token usage statistics."""
 234 |         self.token_stats.clear()
 235 | 
 236 | # --- End Token Counting Middleware ---
 237 | 
 238 | # Create FastMCP app directly without authentication wrapper
 239 | from fastmcp import FastMCP
 240 | 
 241 | def create_app(auth=None):
 242 |     """Create FastMCP app with standard capabilities and optional auth."""
 243 |     global app
 244 |     if auth:
 245 |         # Set auth on existing app instead of creating new one
 246 |         app.auth = auth
 247 |         app.name = "Yargı MCP Server"
 248 |         logger.info("MCP server created with Bearer authentication enabled")
 249 |     else:
 250 |         # Update placeholder app name only
 251 |         app.name = "Yargı MCP Server"
 252 |         logger.info("MCP server created with standard capabilities (FastMCP handles tools.listChanged automatically)")
 253 |     
 254 |     # Add token counting middleware
 255 |     token_counter = TokenCountingMiddleware()
 256 |     app.add_middleware(token_counter)
 257 |     logger.info("Token counting middleware added to MCP server")
 258 |     
 259 |     return app
 260 | 
 261 | # --- Module Imports ---
 262 | from yargitay_mcp_module.client import YargitayOfficialApiClient
 263 | from yargitay_mcp_module.models import (
 264 |     YargitayDetailedSearchRequest, YargitayDocumentMarkdown, CompactYargitaySearchResult,
 265 |     YargitayBirimEnum, CleanYargitayDecisionEntry
 266 | )
 267 | from bedesten_mcp_module.client import BedestenApiClient
 268 | from bedesten_mcp_module.models import (
 269 |     BedestenSearchRequest, BedestenSearchData,
 270 |     BedestenDocumentMarkdown, BedestenCourtTypeEnum
 271 | )
 272 | from bedesten_mcp_module.enums import BirimAdiEnum
 273 | from danistay_mcp_module.client import DanistayApiClient
 274 | from danistay_mcp_module.models import (
 275 |     DanistayKeywordSearchRequest, DanistayDetailedSearchRequest,
 276 |     DanistayDocumentMarkdown, CompactDanistaySearchResult
 277 | )
 278 | from emsal_mcp_module.client import EmsalApiClient
 279 | from emsal_mcp_module.models import (
 280 |     EmsalSearchRequest, EmsalDocumentMarkdown, CompactEmsalSearchResult
 281 | )
 282 | from uyusmazlik_mcp_module.client import UyusmazlikApiClient
 283 | from uyusmazlik_mcp_module.models import (
 284 |     UyusmazlikSearchRequest, UyusmazlikSearchResponse, UyusmazlikDocumentMarkdown,
 285 |     UyusmazlikBolumEnum, UyusmazlikTuruEnum, UyusmazlikKararSonucuEnum
 286 | )
 287 | from anayasa_mcp_module.client import AnayasaMahkemesiApiClient
 288 | from anayasa_mcp_module.bireysel_client import AnayasaBireyselBasvuruApiClient
 289 | from anayasa_mcp_module.unified_client import AnayasaUnifiedClient
 290 | from anayasa_mcp_module.models import (
 291 |     AnayasaNormDenetimiSearchRequest,
 292 |     AnayasaSearchResult,
 293 |     AnayasaDocumentMarkdown,
 294 |     AnayasaBireyselReportSearchRequest,
 295 |     AnayasaBireyselReportSearchResult,
 296 |     AnayasaBireyselBasvuruDocumentMarkdown,
 297 |     AnayasaUnifiedSearchRequest,
 298 |     AnayasaUnifiedSearchResult,
 299 |     AnayasaUnifiedDocumentMarkdown,
 300 |     # Removed enum imports - now using Literal strings in models
 301 | )
 302 | # KIK Module Imports
 303 | from kik_mcp_module.client import KikApiClient
 304 | from kik_mcp_module.models import ( 
 305 |     KikKararTipi, 
 306 |     KikSearchRequest,
 307 |     KikSearchResult,
 308 |     KikDocumentMarkdown 
 309 | )
 310 | 
 311 | from rekabet_mcp_module.client import RekabetKurumuApiClient
 312 | from rekabet_mcp_module.models import (
 313 |     RekabetKurumuSearchRequest,
 314 |     RekabetSearchResult,
 315 |     RekabetDocument,
 316 |     RekabetKararTuruGuidEnum
 317 | )
 318 | 
 319 | from sayistay_mcp_module.client import SayistayApiClient
 320 | from sayistay_mcp_module.models import (
 321 |     GenelKurulSearchRequest, GenelKurulSearchResponse,
 322 |     TemyizKuruluSearchRequest, TemyizKuruluSearchResponse,
 323 |     DaireSearchRequest, DaireSearchResponse,
 324 |     SayistayDocumentMarkdown,
 325 |     SayistayUnifiedSearchRequest, SayistayUnifiedSearchResult,
 326 |     SayistayUnifiedDocumentMarkdown
 327 | )
 328 | from sayistay_mcp_module.enums import DaireEnum, KamuIdaresiTuruEnum, WebKararKonusuEnum
 329 | from sayistay_mcp_module.unified_client import SayistayUnifiedClient
 330 | 
 331 | # KVKK Module Imports
 332 | from kvkk_mcp_module.client import KvkkApiClient
 333 | from kvkk_mcp_module.models import (
 334 |     KvkkSearchRequest,
 335 |     KvkkSearchResult,
 336 |     KvkkDocumentMarkdown
 337 | )
 338 | 
 339 | # BDDK Module Imports
 340 | from bddk_mcp_module.client import BddkApiClient
 341 | from bddk_mcp_module.models import (
 342 |     BddkSearchRequest,
 343 |     BddkSearchResult,
 344 |     BddkDocumentMarkdown
 345 | )
 346 | 
 347 | 
 348 | # Create a placeholder app that will be properly initialized after tools are defined
 349 | from fastmcp import FastMCP
 350 | 
 351 | # Placeholder app for decorators - will be replaced in create_app() after all tools are defined
 352 | app = FastMCP("Yargı MCP Server Placeholder")
 353 | 
 354 | # --- Tool Documentation Resources ---
 355 | @app.resource("docs://tools/yargitay")
 356 | async def get_yargitay_tools_documentation() -> str:
 357 |     """Get document content as Markdown."""
 358 |     return """
 359 | # Yargıtay (Court of Cassation) Tools Documentation
 360 | 
 361 | ## Court Hierarchy and Position
 362 | Yargıtay is Turkey's highest civil and criminal court. It serves as the final appellate authority and establishes legal precedents for civil and criminal cases.
 363 | 
 364 | **Dual API System:**
 365 | - **Primary API (search_yargitay_detailed)**: Official karararama.yargitay.gov.tr
 366 | - **Bedesten API (search_bedesten_unified)**: Unified access to bedesten.adalet.gov.tr (see docs://tools/bedesten_unified)
 367 | 
 368 | ## Chamber Filtering Options (52 Total)
 369 | 
 370 | ### Civil Chambers (Hukuk Daireleri)
 371 | - **Civil General Assembly** (Hukuk Genel Kurulu)
 372 | - **1st Civil Chamber** through **23rd Civil Chamber** (23 civil chambers)
 373 | - **Civil Chambers Presidents Board** (Hukuk Daireleri Başkanlar Kurulu)
 374 | 
 375 | ### Criminal Chambers (Ceza Daireleri)  
 376 | - **Criminal General Assembly** (Ceza Genel Kurulu)
 377 | - **1st Criminal Chamber** through **23rd Criminal Chamber** (23 criminal chambers)
 378 | - **Criminal Chambers Presidents Board** (Ceza Daireleri Başkanlar Kurulu)
 379 | 
 380 | ### General Assemblies
 381 | - **Grand General Assembly** (Büyük Genel Kurulu)
 382 | 
 383 | ## Search Techniques
 384 | 
 385 | ### Primary API (search_yargitay_detailed)
 386 | ```
 387 | Simple search: "mülkiyet"
 388 | AND operator: "mülkiyet AND tapu"
 389 | OR operator: "mülkiyet OR tapu" 
 390 | NOT operator: "mülkiyet NOT satış"
 391 | Wildcard: "mülk*"
 392 | Exact phrase: "\"mülkiyet hakkı\""
 393 | ```
 394 | 
 395 | ### Bedesten API (search_bedesten_unified)
 396 | For detailed usage, see docs://tools/bedesten_unified
 397 | ```
 398 | Regular search: phrase="mülkiyet kararı", court_types=["YARGITAYKARARI"]
 399 | Exact phrase: phrase="\"mülkiyet kararı\"", court_types=["YARGITAYKARARI"]
 400 | Date filtering: kararTarihiStart="2024-01-01T00:00:00.000Z"
 401 | ```
 402 | 
 403 | ## Usage Scenarios
 404 | - **Precedent research**: Supreme court decisions on specific topics
 405 | - **Chamber-specific search**: Relevant chambers for specific legal areas  
 406 | - **Historical analysis**: Decision trends in specific periods
 407 | - **Jurisprudence tracking**: Changes in legal opinions
 408 | 
 409 | ## Best Practices
 410 | 1. **Use dual APIs**: Try both APIs for maximum coverage
 411 | 2. **Chamber filtering**: Select chambers based on relevant legal area
 412 | 3. **Exact phrases**: Use "\"term\"" for precise terms in Bedesten API
 413 | 4. **Date range**: Focus on last 2-3 years for recent developments
 414 | 
 415 | ## Common Civil Chambers
 416 | - **1st Civil**: Property, land registry, liens
 417 | - **4th Civil**: Labor law, collective agreements
 418 | - **11th Civil**: Insurance, social security
 419 | - **15th Civil**: Compensation, tort
 420 | - **21st Civil**: Execution and bankruptcy
 421 | 
 422 | ## Common Criminal Chambers  
 423 | - **1st Criminal**: General criminal offenses
 424 | - **8th Criminal**: Economic and commercial crimes
 425 | - **12th Criminal**: Official misconduct
 426 | """
 427 | 
 428 | @app.resource("docs://tools/danistay")  
 429 | async def get_danistay_tools_documentation() -> str:
 430 |     """Get document content as Markdown."""
 431 |     return """
 432 | # Danıştay (Council of State) Tools Documentation
 433 | 
 434 | ## Court Hierarchy and Position
 435 | Danıştay is Turkey's highest administrative court. It makes final decisions on administrative acts and actions.
 436 | 
 437 | **Triple API System:**
 438 | - **Keyword API (search_danistay_by_keyword)**: AND/OR/NOT logic
 439 | - **Detailed API (search_danistay_detailed)**: Comprehensive criteria  
 440 | - **Bedesten API (search_bedesten_unified)**: Unified access (see docs://tools/bedesten_unified)
 441 | 
 442 | ## Chamber Filtering Options (27 Total)
 443 | 
 444 | ### Main Councils
 445 | - **Grand General Assembly** (Büyük Gen.Kur.)
 446 | - **Administrative Cases Council** (İdare Dava Daireleri Kurulu)
 447 | - **Tax Cases Council** (Vergi Dava Daireleri Kurulu)
 448 | - **Precedents Unification Council** (İçtihatları Birleştirme Kurulu)
 449 | 
 450 | ### Chambers (1-17)
 451 | - **1st Chamber** through **17th Chamber** (Administrative case chambers)
 452 | 
 453 | ### Military Courts
 454 | - **Military High Administrative Court** (Askeri Yüksek İdare Mahkemesi)
 455 | - **Military High Administrative Court 1st-3rd Chambers**
 456 | 
 457 | ## Search Techniques
 458 | 
 459 | ### Keyword API
 460 | ```
 461 | AND logic: andKelimeler=["imar", "plan"]
 462 | OR logic: orKelimeler=["iptal", "yürütmeyi durdurma"]  
 463 | NOT logic: notKelimeler=["ceza"]
 464 | ```
 465 | 
 466 | ### Detailed API
 467 | ```
 468 | Chamber selection: daire="3. Daire"
 469 | Case year: esasYil="2024"
 470 | Decision date: kararTarihiBaslangic="01.01.2024"
 471 | Legislation: mevzuatId=123
 472 | ```
 473 | 
 474 | ### Bedesten API  
 475 | ```
 476 | Regular: phrase="idari işlem"
 477 | Exact: phrase="\"idari işlem\""
 478 | Date: kararTarihiStart="2024-01-01T00:00:00.000Z"
 479 | ```
 480 | 
 481 | ## Usage Scenarios
 482 | - **Administrative law research**: Public administration decisions
 483 | - **Tax law**: Financial matters and tax disputes
 484 | - **Urban planning law**: City planning and building permits
 485 | - **Personnel law**: Civil servant rights
 486 | 
 487 | ## Common Chamber Specializations
 488 | - **1st Chamber**: Municipal, urban planning, environment  
 489 | - **2nd Chamber**: Tax, customs, financial
 490 | - **3rd Chamber**: Personnel, personal rights
 491 | - **5th Chamber**: Administrative fines
 492 | - **8th Chamber**: Higher education, education
 493 | - **10th Chamber**: Health, social security
 494 | 
 495 | ## Best Practices
 496 | 1. **Triple API**: Use all three APIs for maximum coverage
 497 | 2. **Chamber selection**: Choose specialized chambers by subject area
 498 | 3. **Mevzuat bağlantısı**: İlgili kanun/tüzükle filtreleme
 499 | 4. **Kesin terim**: İdari hukuk terminolojisi için exact search
 500 | """
 501 | 
 502 | @app.resource("docs://tools/constitutional_court")
 503 | async def get_constitutional_court_tools_documentation() -> str:
 504 |     """Get document content as Markdown."""
 505 |     return """
 506 | # Anayasa Mahkemesi (Constitutional Court) Tools Documentation
 507 | 
 508 | ## Court Position
 509 | Constitutional Court is Turkey's highest judicial body. It has two main functions:
 510 | 
 511 | ### 1. Norm Control (Norm Control)
 512 | **Tool**: search_anayasa_norm_denetimi_decisions
 513 | - Reviews constitutional compliance of laws and regulations
 514 | - Abstract and concrete norm control
 515 | 
 516 | ### 2. Individual Application (Individual Application)  
 517 | **Tool**: search_anayasa_bireysel_basvuru_report
 518 | - Citizens' fundamental rights violation applications
 519 | - Turkey's human rights protection mechanism
 520 | 
 521 | ## Norm Control Features
 522 | 
 523 | ### Comprehensive Filtering
 524 | - **Application type**: Annulment, Objection, Other
 525 | - **Applicant**: President, Parliament, Courts
 526 | - **Legislation type**: Law, Decree, Regulation, Rules of procedure
 527 | - **Result type**: Annulment, Rejection, Partial annulment
 528 | 
 529 | ### Advanced Search
 530 | - **Member names**: Full names of participating justices
 531 | - **Rapporteur**: Case rapporteur 
 532 | - **Dissenting opinion**: Minority opinion, different view
 533 | - **Press release**: Important decisions
 534 | 
 535 | ## Bireysel Başvuru Özellikleri
 536 | 
 537 | ### Temel Haklar Kategorileri
 538 | - **Yaşam hakkı**: Ölüm olayları, güvenlik
 539 | - **Adil yargılanma**: Süre, tarafsızlık, duruşma hakkı  
 540 | - **İfade özgürlüğü**: Basın, düşünce, akademik özgürlük
 541 | - **Din özgürlüğü**: İbadet, vicdan özgürlüğü
 542 | - **Mülkiyet hakkı**: Kamulaştırma, tapu
 543 | - **Özel hayat**: Gizlilik, aile hayatı
 544 | 
 545 | ### Başvuru Süreci
 546 | - **Yurtiçi yollar**: Önce mahkeme kararı gerekli
 547 | - **Süre sınırı**: 30 gün (60 gün istisnai)
 548 | - **Kabul edilebilirlik**: Ön inceleme kriterleri
 549 | 
 550 | ## Paginated Content (5,000 characters)
 551 | Her iki tool da sayfalanmış Markdown döndürür:
 552 | - **page_number**: Sayfa numarası (1'den başlar)
 553 | - **total_pages**: Toplam sayfa sayısı
 554 | - **current_page**: Mevcut sayfa
 555 | 
 556 | ## Usage Scenarios
 557 | 
 558 | ### Norm Denetimi
 559 | - **Kanun anayasaya uygunluk**: Yeni çıkan kanunların kontrolü
 560 | - **Mahkeme iptali**: Kanunun belirli maddeleri
 561 | - **Mevzuat uyum**: Anayasa değişikliği sonrası
 562 | 
 563 | ### Bireysel Başvuru
 564 | - **İnsan hakları araştırması**: AİHM öncesi iç hukuk
 565 | - **Temel hak ihlalleri**: Sistematik ihlal tespiti
 566 | - **Emsal karar**: Benzer davalar için içtihat
 567 | 
 568 | ## Parameter Details
 569 | ### search_anayasa_norm_denetimi_decisions
 570 | - **keywords_all**: Keywords for AND logic (all must be present)
 571 | - **keywords_any**: Keywords for OR logic (any can be present) 
 572 | - **keywords_exclude**: Keywords to exclude from results
 573 | - **period**: Constitutional period - "ALL", "1" (1961 Constitution), "2" (1982 Constitution)
 574 | - **case_number_esas**: Case registry number (e.g., '2023/123')
 575 | - **decision_number_karar**: Decision number (e.g., '2023/456')
 576 | - **first_review_date_start/end**: First review date range (DD/MM/YYYY)
 577 | - **decision_date_start/end**: Decision date range (DD/MM/YYYY)
 578 | - **application_type**: "ALL", "1" (İptal), "2" (İtiraz), "3" (Diğer)
 579 | - **applicant_general_name**: General applicant name
 580 | - **applicant_specific_name**: Specific applicant name
 581 | - **official_gazette_date_start/end**: Official Gazette date range
 582 | - **official_gazette_number_start/end**: Official Gazette number range
 583 | - **has_press_release**: "ALL", "0" (No), "1" (Yes)
 584 | - **has_dissenting_opinion**: "ALL", "0" (No), "1" (Yes)
 585 | - **has_different_reasoning**: "ALL", "0" (No), "1" (Yes)
 586 | - **attending_members_names**: List of attending members' exact names
 587 | - **rapporteur_name**: Rapporteur's exact name
 588 | - **norm_type**: Type of reviewed norm (law, decree, regulation, etc.)
 589 | - **norm_id_or_name**: Number or name of the norm
 590 | - **norm_article**: Article number of the norm
 591 | - **review_outcomes**: List of review outcomes
 592 | - **reason_for_final_outcome**: Main reason for decision outcome
 593 | - **basis_constitution_article_numbers**: Supporting Constitution article numbers
 594 | - **results_per_page**: Results per page (10, 20, 30, 40, 50)
 595 | - **page_to_fetch**: Page number to fetch
 596 | - **sort_by_criteria**: Sort criteria ('KararTarihi', 'YayinTarihi', 'Toplam')
 597 | 
 598 | ### search_anayasa_bireysel_basvuru_report
 599 | - **keywords**: Keywords for AND logic (all must be present)
 600 | - **page_to_fetch**: Page number for the report (default: 1)
 601 | 
 602 | ### Document Tools
 603 | - **document_url**: URL path or full URL of the decision
 604 | - **page_number**: Page number for paginated content (1-indexed, default: 1)
 605 | 
 606 | ## Best Practices
 607 | 1. **Norm control önce**: Kanun iptal edilmiş mi kontrol
 608 | 2. **Bireysel başvuru ikinci**: Kişisel hak ihlalleri için
 609 | 3. **Tarih aralığı**: Anayasa değişiklikleri sonrası dönemler
 610 | 4. **Anahtar kelime kombinasyonu**: Temel hak + konu alanı
 611 | 5. **Sayfa yönetimi**: Uzun kararlarda sayfa sayfa okuyun
 612 | """
 613 | 
 614 | @app.resource("docs://tools/emsal")
 615 | async def get_emsal_tools_documentation() -> str:
 616 |     """Get document content as Markdown."""
 617 |     return """
 618 | # Emsal (UYAP Precedent System) Tools Documentation
 619 | 
 620 | ## System Position
 621 | Central precedent decision system providing access to all court decisions through the UYAP system.
 622 | 
 623 | ## Court Options
 624 | - **Yargıtay**: First and second instance courts
 625 | - **Danıştay**: Administrative court decisions
 626 | - **Other**: Regional courts of justice, civil courts
 627 | 
 628 | ## Advanced Filtering Features
 629 | - **Court type**: Civil, criminal, administrative
 630 | - **Case/Decision number**: File tracking system
 631 | - **Date range**: Flexible date selection
 632 | - **Content search**: Keyword search within decision text
 633 | 
 634 | ## Usage Scenarios
 635 | - **Kapsamlı emsal**: Tüm mahkeme seviyelerinden karar toplama
 636 | - **Güncel içtihat**: En son hukuki gelişmeler
 637 | - **Cross-reference**: Farklı mahkeme görüşlerini karşılaştırma
 638 | 
 639 | ## Best Practices
 640 | 1. **Spesifik terimler**: Hukuki terminoloji kullanın
 641 | 2. **Geniş arama**: Önce genel, sonra spesifik
 642 | 3. **Tarih stratejisi**: Mevzuat değişiklikleri dikkate alın
 643 | 4. **Cross-platform**: Aynı konuyu farklı mahkemelerde arayın
 644 | """
 645 | 
 646 | @app.resource("docs://tools/uyusmazlik")
 647 | async def get_uyusmazlik_tools_documentation() -> str:
 648 |     """Get document content as Markdown."""
 649 |     return """
 650 | # Uyuşmazlık Mahkemesi Tools Documentation
 651 | 
 652 | ## Court Position
 653 | Adli ve idari yargı arasındaki görev uyuşmazlıklarını çözen özel yetkili mahkeme.
 654 | 
 655 | ## Dispute Types
 656 | - **Görev uyuşmazlığı**: Hangi mahkeme bakacak konusunda anlaşmazlık
 657 | - **Hüküm uyuşmazlığı**: Çelişkili mahkeme kararları
 658 | - **Yetki uyuşmazlığı**: Yerel yetki sorunları
 659 | 
 660 | ## Form-Based Search Criteria
 661 | - **Karar türü**: Müspet, menfi, hüküm uyuşmazlığı
 662 | - **Taraf mahkemeler**: Adli-idari yargı organları
 663 | - **Konu alanı**: Hukuk dalı bazlı filtreleme
 664 | - **Tarih aralığı**: Karar tarihi seçimi
 665 | 
 666 | ## Usage Scenarios
 667 | - **Yargı türü belirleme**: Hangi mahkemenin yetkili olduğu
 668 | - **Çelişkili kararlar**: Farklı mahkeme kararları arasındaki uyuşmazlık
 669 | - **Yetki sorunları**: Mahkeme yetkisi tartışmaları
 670 | 
 671 | ## Best Practices
 672 | 1. **Net kriterler**: Arama kriterlerini spesifik tutun
 673 | 2. **Taraf bilgisi**: Uyuşmazlık taraflarını belirtin
 674 | 3. **Konu odaklı**: İlgili hukuk dalını seçin
 675 | """
 676 | 
 677 | @app.resource("docs://tools/kik")
 678 | async def get_kik_tools_documentation() -> str:
 679 |     """Get document content as Markdown."""
 680 |     return """
 681 | # KİK (Kamu İhale Kurumu) Tools Documentation
 682 | 
 683 | ## Kurum Konumu
 684 | Kamu ihale uyuşmazlıklarının ilk ve son merci çözüm organı. Kamu İhale Kanunu kapsamındaki tüm ihaleler için yetkili.
 685 | 
 686 | ## Decision Types
 687 | - **Uyuşmazlık**: İhale süreç itirazları
 688 | - **Düzenleyici**: Mevzuat ve uygulama kararları  
 689 | - **Mahkeme**: Mahkeme kararlarının uygulanması
 690 | 
 691 | ## Filtreleme Seçenekleri
 692 | - **Karar numarası**: 2024/UH.II-1766 formatında
 693 | - **Tarih aralığı**: Karar tarihi filtreleme
 694 | - **İhaleyi yapan idare**: Bakanlık, belediye, hastane, üniversite
 695 | - **Başvuru sahibi**: Şirket, firma adı
 696 | - **İhale konusu**: Mal, hizmet, yapım işi
 697 | 
 698 | ## Sayfalanmış İçerik Özelliği
 699 | 5.000 karakterlik sayfalar halinde Markdown formatında sunulur.
 700 | 
 701 | ## Usage Scenarios
 702 | - **İhale hukuku**: Kamu alımları, süreç kuralları
 703 | - **Başvuru hazırlığı**: Benzer davalar, emsal kararlar
 704 | - **Mevzuat yorumu**: Kamu İhale Kanunu uygulaması
 705 | - **İtiraz stratejisi**: Başarılı itiraz örnekleri
 706 | 
 707 | ## İhale Süreç Aşamaları
 708 | 1. **İhale öncesi**: İlan, şartname hazırlığı
 709 | 2. **İhale aşaması**: Teklif verme, değerlendirme
 710 | 3. **İhale sonrası**: Sonuç bildirimi, itirazlar
 711 | 4. **Sözleşme**: İmza, uygulama
 712 | 
 713 | ## Parameter Details
 714 | ### search_kik_decisions  
 715 | - **karar_tipi**: Decision type - "rbUyusmazlik" (disputes), "rbDuzenleyici" (regulatory), "rbMahkeme" (court)
 716 | - **karar_no**: Decision number (e.g., '2024/UH.II-1766')
 717 | - **karar_tarihi_baslangic**: Decision start date (DD.MM.YYYY format)
 718 | - **karar_tarihi_bitis**: Decision end date (DD.MM.YYYY format)
 719 | - **basvuru_sahibi**: Applicant name/company
 720 | - **ihaleyi_yapan_idare**: Procuring entity (ministry, municipality, etc.)
 721 | - **basvuru_konusu_ihale**: Tender subject/description
 722 | - **karar_metni**: Text search with operators: +word (AND), -word (exclude)
 723 | - **yil**: Decision year
 724 | - **resmi_gazete_tarihi**: Official Gazette date (DD.MM.YYYY)
 725 | - **resmi_gazete_sayisi**: Official Gazette number
 726 | - **page**: Results page number
 727 | 
 728 | ### get_kik_document_markdown
 729 | - **karar_id**: Base64 encoded decision identifier from search results
 730 | - **page_number**: Page number for paginated content (1-indexed, default: 1)
 731 | 
 732 | ## Best Practices
 733 | 1. **İhale türü**: Açık, belli istekliler arası, pazarlık
 734 | 2. **Süreç aşaması**: Hangi aşamada sorun olduğu
 735 | 3. **Hukuki dayanak**: İlgili KİK kanun maddesi
 736 | 4. **Sayfa yönetimi**: Uzun kararları bölümler halinde okuyun
 737 | """
 738 | 
 739 | @app.resource("docs://tools/rekabet")
 740 | async def get_rekabet_tools_documentation() -> str:
 741 |     """Get document content as Markdown."""
 742 |     return """
 743 | # Rekabet Kurumu (Competition Authority) Tools Documentation
 744 | 
 745 | ## Kurum Konumu  
 746 | Rekabet hukuku ihlallerini inceleyen ve ceza veren idari otorite. Rekabet Kanunu kapsamında yetkili.
 747 | 
 748 | ## Decision Types
 749 | - **Birleşme ve Devralma**: Şirket satın almaları, füzyonlar
 750 | - **Rekabet İhlali**: Anlaşma, hakim durum kötüye kullanımı
 751 | - **Menfi Tespit ve Muafiyet**: İhlal yok kararları, muafiyetler
 752 | - **Özelleştirme**: Kamu şirketleri satışı onayları
 753 | 
 754 | ## Filtreleme Özellikleri
 755 | - **PDF metin arama**: Tam metin içinde kelime arama
 756 | - **Karar türü**: Spesifik kategori seçimi
 757 | - **Tarih aralığı**: 1997'den günümüze karar arşivi
 758 | - **Sektör**: Telekomünikasyon, bankacılık, enerji, perakende
 759 | 
 760 | ## Rekabet Hukuku Temel Kavramları
 761 | - **Hakim durum**: Pazar gücü
 762 | - **Kartel**: Fiyat anlaşması
 763 | - **Dikey anlaşmalar**: Tedarikci-bayi ilişkileri
 764 | - **Konsantrasyon**: Birleşme işlemleri
 765 | 
 766 | ## Usage Scenarios
 767 | - **Antitrust araştırması**: Tekelleşme, kartel soruşturmaları  
 768 | - **Birleşme incelemesi**: M&A transaction değerlendirmesi
 769 | - **Sektör analizi**: Belirli pazarlardaki rekabet durumu
 770 | - **Ceza hesaplama**: İhlal cezası örnekleri
 771 | 
 772 | ## Sektörel Uzmanlık Alanları
 773 | 1. **Telekomünikasyon**: Operatör rekabeti
 774 | 2. **Enerji**: Elektrik, doğalgaz piyasası
 775 | 3. **Finans**: Bankacılık, sigorta
 776 | 4. **Perakende**: Zincir mağazalar
 777 | 5. **İnşaat**: Müteahhitlik sektörü
 778 | 
 779 | ## Parameter Details
 780 | ### search_rekabet_kurumu_decisions
 781 | - **sayfaAdi**: Search in decision title (Başlık)
 782 | - **YayinlanmaTarihi**: Publication date (DD.MM.YYYY format)
 783 | - **PdfText**: Search text. For exact phrases use double quotes: "vertical agreement"
 784 | - **KararTuru**: Decision type - "Birleşme ve Devralma", "Rekabet İhlali", etc.
 785 | - **KararSayisi**: Decision number (Karar Sayısı)
 786 | - **KararTarihi**: Decision date (DD.MM.YYYY format)
 787 | - **page**: Page number for results list
 788 | 
 789 | ### get_rekabet_kurumu_document
 790 | - **karar_id**: GUID from search results
 791 | - **page_number**: Requested page for Markdown content (1-indexed, default: 1)
 792 | 
 793 | ## Best Practices
 794 | 1. **Sektör odaklı**: İlgili sektörde arama yapın
 795 | 2. **Karar türü seçimi**: İhtiyacınıza uygun kategori
 796 | 3. **Güncel mevzuat**: Mevzuat değişiklikleri takibi
 797 | 4. **Sayfa yönetimi**: Uzun analizleri bölümler halinde
 798 | """
 799 | 
 800 | @app.resource("docs://tools/bedesten_unified")
 801 | async def get_bedesten_unified_documentation() -> str:
 802 |     """Get document content as Markdown."""
 803 |     return """
 804 | # Bedesten API Mahkemeleri Tools Documentation
 805 | 
 806 | ## Bedesten API Sistemi
 807 | bedesten.adalet.gov.tr üzerinden Türk adalet sistemi hiyerarşisindeki mahkemelere erişim.
 808 | 
 809 | ## Mahkeme Hiyerarşisi Kapsamı
 810 | 
 811 | ### 1. Yerel Hukuk Mahkemeleri (Local Civil Courts)
 812 | **Tool**: search_yerel_hukuk_bedesten
 813 | - **Konum**: İlk derece mahkemeler
 814 | - **Yetki**: Hukuki uyuşmazlıklar (sözleşme, tazminat, mülkiyet)
 815 | - **Önem**: Toplumun günlük hukuki sorunları
 816 | 
 817 | ### 2. İstinaf Hukuk Mahkemeleri (Civil Courts of Appeals)  
 818 | **Tool**: search_istinaf_hukuk_bedesten
 819 | - **Konum**: Orta derece (Yerel -> İstinaf -> Yargıtay)
 820 | - **Yetki**: Yerel mahkeme kararlarına itiraz
 821 | - **Önem**: Temyiz öncesi son kontrol
 822 | 
 823 | ### 3. Kanun Yararına Bozma (KYB)
 824 | **Tool**: search_kyb_bedesten  
 825 | - **Konum**: Olağanüstü kanun yolu
 826 | - **Başvuru sahibi**: Cumhuriyet Başsavcılığı
 827 | - **Amaç**: Hukuka aykırı kararları düzeltme
 828 | - **Özellik**: Sanık aleyhine olsa bile hukuk yararına
 829 | 
 830 | ## Ortak Bedesten API Özellikleri
 831 | 
 832 | ### Tarih Filtreleme (ISO 8601)
 833 | ```
 834 | Başlangıç: kararTarihiStart="2024-01-01T00:00:00.000Z"
 835 | Bitiş: kararTarihiEnd="2024-12-31T23:59:59.999Z"
 836 | Tek gün: "2024-06-25T00:00:00.000Z" - "2024-06-25T23:59:59.999Z"
 837 | ```
 838 | 
 839 | ### Kesin Cümle Arama
 840 | ```
 841 | Normal: phrase="sözleşme ihlali"  (kelimeler ayrı ayrı)
 842 | Kesin: phrase="\"sözleşme ihlali\""  (tam cümle)
 843 | ```
 844 | 
 845 | ### Sayfalama
 846 | - **pageSize**: 1-100 arası sonuç sayısı
 847 | - **pageNumber**: Sayfa numarası (1'den başlar)
 848 | 
 849 | ## Mahkeme Özellikleri
 850 | 
 851 | ### Yerel Hukuk Mahkemeleri
 852 | **Yaygın Dava Türleri**:
 853 | - Sözleşme ihlali davaları
 854 | - Tazminat talepleri  
 855 | - Mülkiyet uyuşmazlıkları
 856 | - Aile hukuku (boşanma, nafaka)
 857 | - Ticari uyuşmazlıklar (küçük-orta ölçek)
 858 | 
 859 | **Kullanım Senaryoları**:
 860 | - Günlük hukuki sorunlar
 861 | - Vatandaş hakları
 862 | - Ticaret hukuku temelleri
 863 | - İcra takipleri
 864 | 
 865 | ### İstinaf Hukuk Mahkemeleri  
 866 | **İnceleme Kapsamı**:
 867 | - Yerel mahkeme kararlarının kontrolü
 868 | - Hukuki ve maddi hata arayışı
 869 | - Yeniden yargılama (sınırlı)
 870 | 
 871 | **Kullanım Senaryoları**:
 872 | - Temyiz stratejisi gelişitirme
 873 | - İstinaf mahkemesi içtihatları
 874 | - Yerel-üst mahkeme uyumu analizi
 875 | 
 876 | ### Kanun Yararına Bozma (KYB)
 877 | **Başvuru Koşulları**:
 878 | - Kesinleşmiş mahkeme kararı
 879 | - Hukuka açık aykırılık
 880 | - Cumhuriyet Başsavcılığı başvurusu
 881 | - Sanık aleyhine sonuç doğurmama
 882 | 
 883 | **Kullanım Senaryoları**:
 884 | - Sistematik hukuki hatalar
 885 | - İçtihat birliğini sağlama
 886 | - Hukuk güvenliği
 887 | - Nadir ve özel hukuki durumlar
 888 | 
 889 | ## Arama Stratejileri
 890 | 
 891 | ### Hiyerarşik Arama
 892 | ```
 893 | 1. Yerel mahkeme -> Gündelik sorunlar
 894 | 2. İstinaf -> Kompleks yorumlar  
 895 | 3. KYB -> İstisnai hukuki durumlar
 896 | ```
 897 | 
 898 | ### Kesin Terim Kullanımı
 899 | ```
 900 | Yerel: "\"sözleşme ihlali\""
 901 | İstinaf: "\"temyiz incelemesi\""  
 902 | KYB: "\"kanun yararına bozma\""
 903 | ```
 904 | 
 905 | ### Tarih Stratejisi
 906 | - **Son 2 yıl**: Güncel içtihat
 907 | - **5-10 yıl**: Yerleşik görüşler
 908 | - **Mevzuat değişikliği sonrası**: Yeni uygulamalar
 909 | 
 910 | ## Best Practices
 911 | 1. **Hiyerarşi takibi**: Alt mahkemeden üst mahkemeye
 912 | 2. **Kesin cümle**: Hukuki terimler için "\"terim\""
 913 | 3. **Tarih aralığı**: İlgili mevzuat dönemleri
 914 | 4. **Cross-reference**: Aynı konuyu farklı seviyelerde
 915 | 5. **Minimal sonuç**: KYB çok nadir, az sonuç beklenir
 916 | 
 917 | ## Document ID Formatı
 918 | Tüm Bedesten mahkemeleri documentId döndürür:
 919 | - **Format**: Alfanumerik string
 920 | - **Kullanım**: get_*_bedesten_document_markdown fonksiyonları
 921 | - **İçerik**: HTML/PDF -> Markdown conversion
 922 | """
 923 | 
 924 | @app.resource("docs://tools/sayistay")
 925 | async def get_sayistay_tools_documentation() -> str:
 926 |     """Get document content as Markdown."""
 927 |     return """
 928 | # Sayıştay (Court of Accounts) Tools Documentation
 929 | 
 930 | ## Sayıştay'ın Konumu
 931 | Türkiye'nin en üst mali denetim organı. Kamu kaynaklarının kullanımını denetler ve mali disiplini sağlar.
 932 | 
 933 | ## Üç Tür Karar Sistemi
 934 | 
 935 | ### 1. Genel Kurul Kararları (Interpretive Rulings)
 936 | **Tool**: search_sayistay_genel_kurul
 937 | - **İşlev**: Mali mevzuat yorumlama
 938 | - **Kapsam**: 2006-2024 yılları arası
 939 | - **Özellik**: Bağlayıcı yorumlar
 940 | 
 941 | **Filtreleme Seçenekleri**:
 942 | - **Karar numarası**: Spesifik karar arama
 943 | - **Tarih aralığı**: Başlangıç-bitiş tarihleri
 944 | - **Karar tamamı**: Tam metin arama (400 karakter)
 945 | 
 946 | ### 2. Temyiz Kurulu Kararları (Appeals Board)
 947 | **Tool**: search_sayistay_temyiz_kurulu  
 948 | - **İşlev**: Daire kararlarına itiraz incelemesi
 949 | - **8 Daire Filtreleme**: Uzmanlık alanlarına göre
 950 | 
 951 | **Daire Uzmanlaşmaları**:
 952 | - **1. Daire**: Genel bütçeli idareler
 953 | - **2. Daire**: Mahalli idareler  
 954 | - **3. Daire**: Sosyal güvenlik kurumları
 955 | - **4. Daire**: KİT ve bağlı ortaklıklar
 956 | - **5. Daire**: Düzenleyici kuruluşlar
 957 | - **6. Daire**: Vakıflar, dernekler
 958 | - **7. Daire**: Üniversiteler, eğitim
 959 | - **8. Daire**: Yatırım projeleri
 960 | 
 961 | **Filtreleme Seçenekleri**:
 962 | - **İdare türü**: Bakanlık, belediye, üniversite, KİT
 963 | - **Temyiz karar**: Tam metin arama
 964 | - **Konu sınıflandırması**: Harcama, gelir, taşınır-taşınmaz
 965 | 
 966 | ### 3. Daire Kararları (Chamber Decisions)  
 967 | **Tool**: search_sayistay_daire
 968 | - **İşlev**: İlk derece denetim bulguları
 969 | - **8 Daire**: Aynı uzmanlaşma alanları
 970 | 
 971 | **Filtreleme Seçenekleri**:
 972 | - **Yargılama dairesi**: 1-8 arası daire seçimi
 973 | - **Hesap yılı**: Mali yıl bazlı
 974 | - **Web karar metni**: İçerik arama
 975 | 
 976 | ## Ortak Özellikler
 977 | 
 978 | ### Sayfalanmış Markdown
 979 | Tüm Sayıştay belgeleri sayfalanmış format:
 980 | - **5.000 karakter** per sayfa
 981 | - **page_number**: Sayfa numarası
 982 | - **total_pages**: Toplam sayfa
 983 | 
 984 | ### Tarih Aralığı Desteği
 985 | - **Genel Kurul**: 2006-2024 (18 yıl)
 986 | - **Temyiz/Daire**: Mevcut veriler üzerinde
 987 | 
 988 | ## Usage Scenarios
 989 | 
 990 | ### Mali Mevzuat Araştırması
 991 | ```
 992 | Genel Kurul -> Hukuki yorum
 993 | Temyiz -> Uygulama detayları  
 994 | Daire -> Spesifik örnekler
 995 | ```
 996 | 
 997 | ### Kamu Mali Yönetimi
 998 | - **Bütçe uygulama**: Harcama usulleri
 999 | - **İhale süreçleri**: Kamu alımları denetimi
1000 | - **Personel giderleri**: Özlük hakları mali boyutu
1001 | - **Yatırım projeleri**: Büyük ölçekli projeler
1002 | 
1003 | ### Kurumsal Denetim
1004 | - **KİT yönetimi**: Kamu iktisadi teşebbüsleri
1005 | - **Belediye maliyesi**: Yerel yönetim harcamaları  
1006 | - **Üniversite bütçesi**: Yükseköğretim mali yönetimi
1007 | - **Sosyal güvenlik**: SGK, Bağ-Kur mali işlemleri
1008 | 
1009 | ## Arama Stratejileri
1010 | 
1011 | ### Hiyerarşik Yaklaşım
1012 | 1. **Genel Kurul**: Konunun hukuki çerçevesi
1013 | 2. **Temyiz**: Tartışmalı uygulamalar
1014 | 3. **Daire**: Günlük uygulama örnekleri
1015 | 
1016 | ### Daire Bazlı Strateji
1017 | ```
1018 | Mali konu -> İlgili daire seçimi -> Derinlemesine arama
1019 | Örnek: KİT mali sorunları -> 4. Daire
1020 | ```
1021 | 
1022 | ### Tarih Odaklı Strateji
1023 | - **Son 2 yıl**: Güncel uygulamalar
1024 | - **5 yıl**: Yerleşik görüşler  
1025 | - **2006-2024**: Tarihsel gelişim
1026 | 
1027 | ## Best Practices
1028 | 1. **Daire uzmanlaşması**: İlgili kuruma uygun daire
1029 | 2. **Hiyerarşik sıralama**: Genel Kurul -> Temyiz -> Daire
1030 | 3. **Mali dönem**: Bütçe yılları bazında arama
1031 | 4. **Teknik terimler**: Mali mevzuat terminolojisi
1032 | 5. **Cross-reference**: Farklı seviyelerden görüş karşılaştırma
1033 | 
1034 | ## İdare Türü Kodları
1035 | - **1**: Genel bütçeli
1036 | - **2**: Özel bütçeli  
1037 | - **3**: Düzenleyici kuruluşlar
1038 | - **4**: Mahalli idareler
1039 | - **5**: Sosyal güvenlik
1040 | - **6**: KİT
1041 | - **7**: Vakıf/dernek
1042 | - **8**: Diğer kamu kuruluşları
1043 | """
1044 | 
1045 | @app.resource("docs://tools/kvkk")
1046 | async def get_kvkk_tools_documentation() -> str:
1047 |     """Get document content as Markdown."""
1048 |     return """
1049 | # KVKK (Personal Data Protection Authority) Tools
1050 | 
1051 | ## Overview
1052 | KVKK (Kişisel Verilerin Korunması Kurulu) - Turkey's GDPR equivalent authority.
1053 | 
1054 | ## Search Features
1055 | - **Brave Search API**: Searches kvkk.gov.tr with Turkish terms
1056 | - **Site-targeted**: Auto `site:kvkk.gov.tr "karar özeti"`
1057 | - **Pagination**: page and pageSize parameters
1058 | - **5,000-char pages**: Paginated Markdown documents
1059 | 
1060 | ## Common Search Terms
1061 | **Violations**: "veri ihlali", "açık rıza", "idari para cezası"
1062 | **Compliance**: "GDPR uyum", "veri koruma", "güvenlik tedbirleri"
1063 | **Sectors**: "e-ticaret", "bankacılık", "sağlık", "mobil uygulama"
1064 | 
1065 | ## Key Decision Types
1066 | - **Fines**: Data breaches, consent violations
1067 | - **Compliance**: GDPR alignment, corporate policies  
1068 | - **Breach notifications**: 24-hour rule violations
1069 | 
1070 | ## Usage Tips
1071 | 1. Use Turkish legal terms for best results
1072 | 2. Combine sector + violation type searches
1073 | 3. Use page_number for long decisions
1074 | 4. Focus on recent 2-3 years for current practices
1075 | """
1076 | 
1077 | 
1078 | # --- API Client Instances ---
1079 | yargitay_client_instance = YargitayOfficialApiClient()
1080 | danistay_client_instance = DanistayApiClient()
1081 | emsal_client_instance = EmsalApiClient()
1082 | uyusmazlik_client_instance = UyusmazlikApiClient()
1083 | anayasa_norm_client_instance = AnayasaMahkemesiApiClient()
1084 | anayasa_bireysel_client_instance = AnayasaBireyselBasvuruApiClient()
1085 | anayasa_unified_client_instance = AnayasaUnifiedClient()
1086 | kik_client_instance = KikApiClient()
1087 | rekabet_client_instance = RekabetKurumuApiClient()
1088 | bedesten_client_instance = BedestenApiClient()
1089 | sayistay_client_instance = SayistayApiClient()
1090 | sayistay_unified_client_instance = SayistayUnifiedClient()
1091 | kvkk_client_instance = KvkkApiClient()
1092 | bddk_client_instance = BddkApiClient()
1093 | 
1094 | 
1095 | KARAR_TURU_ADI_TO_GUID_ENUM_MAP = {
1096 |     "": RekabetKararTuruGuidEnum.TUMU,  # Keep for backward compatibility
1097 |     "ALL": RekabetKararTuruGuidEnum.TUMU,  # Map "ALL" to TUMU
1098 |     "Birleşme ve Devralma": RekabetKararTuruGuidEnum.BIRLESME_DEVRALMA,
1099 |     "Diğer": RekabetKararTuruGuidEnum.DIGER,
1100 |     "Menfi Tespit ve Muafiyet": RekabetKararTuruGuidEnum.MENFI_TESPIT_MUAFIYET,
1101 |     "Özelleştirme": RekabetKararTuruGuidEnum.OZELLESTIRME,
1102 |     "Rekabet İhlali": RekabetKararTuruGuidEnum.REKABET_IHLALI,
1103 | }
1104 | 
1105 | # --- MCP Tools for Yargitay ---
1106 | """
1107 | @app.tool(
1108 |     description="Search Yargıtay decisions with 52 chamber filtering and advanced operators",
1109 |     annotations={
1110 |         "readOnlyHint": True,
1111 |         "openWorldHint": True,
1112 |         "idempotentHint": True
1113 |     }
1114 | )
1115 | async def search_yargitay_detailed(
1116 |     arananKelime: str = Field("", description="Turkish search keyword. Supports +required -excluded \"exact phrase\" operators"),
1117 |     birimYrgKurulDaire: str = Field("ALL", description="Chamber selection (52 options: Civil/Criminal chambers, General Assemblies)"),
1118 |     esasYil: str = Field("", description="Case year for 'Esas No'."),
1119 |     esasIlkSiraNo: str = Field("", description="Starting sequence number for 'Esas No'."),
1120 |     esasSonSiraNo: str = Field("", description="Ending sequence number for 'Esas No'."),
1121 |     kararYil: str = Field("", description="Decision year for 'Karar No'."),
1122 |     kararIlkSiraNo: str = Field("", description="Starting sequence number for 'Karar No'."),
1123 |     kararSonSiraNo: str = Field("", description="Ending sequence number for 'Karar No'."),
1124 |     baslangicTarihi: str = Field("", description="Start date for decision search (DD.MM.YYYY)."),
1125 |     bitisTarihi: str = Field("", description="End date for decision search (DD.MM.YYYY)."),
1126 |     # pageSize: int = Field(10, ge=1, le=10, description="Number of results per page."),
1127 |     pageNumber: int = Field(1, ge=1, description="Page number to retrieve.")
1128 | ) -> CompactYargitaySearchResult:
1129 |     # Search Yargıtay decisions using primary API with 52 chamber filtering and advanced operators.
1130 |     
1131 |     # Convert "ALL" to empty string for API compatibility
1132 |     if birimYrgKurulDaire == "ALL":
1133 |         birimYrgKurulDaire = ""
1134 |     
1135 |     pageSize = 10  # Default value
1136 |     
1137 |     search_query = YargitayDetailedSearchRequest(
1138 |         arananKelime=arananKelime,
1139 |         birimYrgKurulDaire=birimYrgKurulDaire,
1140 |         esasYil=esasYil,
1141 |         esasIlkSiraNo=esasIlkSiraNo,
1142 |         esasSonSiraNo=esasSonSiraNo,
1143 |         kararYil=kararYil,
1144 |         kararIlkSiraNo=kararIlkSiraNo,
1145 |         kararSonSiraNo=kararSonSiraNo,
1146 |         baslangicTarihi=baslangicTarihi,
1147 |         bitisTarihi=bitisTarihi,
1148 |         siralama="3",
1149 |         siralamaDirection="desc",
1150 |         pageSize=pageSize,
1151 |         pageNumber=pageNumber
1152 |     )
1153 |     
1154 |     logger.info(f"Tool 'search_yargitay_detailed' called: {search_query.model_dump_json(exclude_none=True, indent=2)}")
1155 |     try:
1156 |         api_response = await yargitay_client_instance.search_detailed_decisions(search_query)
1157 |         if api_response and api_response.data and api_response.data.data:
1158 |             # Convert to clean decision entries without arananKelime field
1159 |             clean_decisions = [
1160 |                 CleanYargitayDecisionEntry(
1161 |                     id=decision.id,
1162 |                     daire=decision.daire,
1163 |                     esasNo=decision.esasNo,
1164 |                     kararNo=decision.kararNo,
1165 |                     kararTarihi=decision.kararTarihi,
1166 |                     document_url=decision.document_url
1167 |                 )
1168 |                 for decision in api_response.data.data
1169 |             ]
1170 |             return CompactYargitaySearchResult(
1171 |                 decisions=clean_decisions,
1172 |                 total_records=api_response.data.recordsTotal if api_response.data else 0,
1173 |                 requested_page=search_query.pageNumber,
1174 |                 page_size=search_query.pageSize)
1175 |         logger.warning("API response for Yargitay search did not contain expected data structure.")
1176 |         return CompactYargitaySearchResult(decisions=[], total_records=0, requested_page=search_query.pageNumber, page_size=search_query.pageSize)
1177 |     except Exception as e:
1178 |         logger.exception(f"Error in tool 'search_yargitay_detailed'.")
1179 |         raise
1180 | 
1181 | @app.tool(
1182 |     description="Get Yargıtay decision text in Markdown format",
1183 |     annotations={
1184 |         "readOnlyHint": True,
1185 |         "idempotentHint": True
1186 |     }
1187 | )
1188 | async def get_yargitay_document_markdown(id: str) -> YargitayDocumentMarkdown:
1189 |     # Get Yargıtay decision text as Markdown. Use ID from search results.
1190 |     logger.info(f"Tool 'get_yargitay_document_markdown' called for ID: {id}")
1191 |     if not id or not id.strip(): raise ValueError("Document ID must be a non-empty string.")
1192 |     try:
1193 |         return await yargitay_client_instance.get_decision_document_as_markdown(id)
1194 |     except Exception as e:
1195 |         logger.exception(f"Error in tool 'get_yargitay_document_markdown'.")
1196 |         raise
1197 | """
1198 | 
1199 | # --- MCP Tools for Danistay ---
1200 | """
1201 | @app.tool(
1202 |     description="Search Danıştay decisions with keyword logic (AND/OR/NOT operators)",
1203 |     annotations={
1204 |         "readOnlyHint": True,
1205 |         "openWorldHint": True,
1206 |         "idempotentHint": True
1207 |     }
1208 | )
1209 | async def search_danistay_by_keyword(
1210 |     andKelimeler: List[str] = Field(default_factory=list, description="Keywords for AND logic, e.g., ['word1', 'word2']"),
1211 |     orKelimeler: List[str] = Field(default_factory=list, description="Keywords for OR logic."),
1212 |     notAndKelimeler: List[str] = Field(default_factory=list, description="Keywords for NOT AND logic."),
1213 |     notOrKelimeler: List[str] = Field(default_factory=list, description="Keywords for NOT OR logic."),
1214 |     pageNumber: int = Field(1, ge=1, description="Page number."),
1215 |     # pageSize: int = Field(10, ge=1, le=10, description="Results per page.")
1216 | ) -> CompactDanistaySearchResult:
1217 |     # Search Danıştay decisions with keyword logic.
1218 |     
1219 |     pageSize = 10  # Default value
1220 |     
1221 |     search_query = DanistayKeywordSearchRequest(
1222 |         andKelimeler=andKelimeler,
1223 |         orKelimeler=orKelimeler,
1224 |         notAndKelimeler=notAndKelimeler,
1225 |         notOrKelimeler=notOrKelimeler,
1226 |         pageNumber=pageNumber,
1227 |         pageSize=pageSize
1228 |     )
1229 |     
1230 |     logger.info(f"Tool 'search_danistay_by_keyword' called.")
1231 |     try:
1232 |         api_response = await danistay_client_instance.search_keyword_decisions(search_query)
1233 |         if api_response.data:
1234 |             return CompactDanistaySearchResult(
1235 |                 decisions=api_response.data.data,
1236 |                 total_records=api_response.data.recordsTotal,
1237 |                 requested_page=search_query.pageNumber,
1238 |                 page_size=search_query.pageSize)
1239 |         logger.warning("API response for Danistay keyword search did not contain expected data structure.")
1240 |         return CompactDanistaySearchResult(decisions=[], total_records=0, requested_page=search_query.pageNumber, page_size=search_query.pageSize)
1241 |     except Exception as e:
1242 |         logger.exception(f"Error in tool 'search_danistay_by_keyword'.")
1243 |         raise
1244 | 
1245 | @app.tool(
1246 |     description="Search Danıştay decisions with detailed criteria (chamber selection, case numbers)",
1247 |     annotations={
1248 |         "readOnlyHint": True,
1249 |         "openWorldHint": True,
1250 |         "idempotentHint": True
1251 |     }
1252 | )
1253 | async def search_danistay_detailed(
1254 |     daire: str = Field("", description="Chamber/Department name (e.g., '1. Daire')."),
1255 |     esasYil: str = Field("", description="Case year for 'Esas No'."),
1256 |     esasIlkSiraNo: str = Field("", description="Starting sequence for 'Esas No'."),
1257 |     esasSonSiraNo: str = Field("", description="Ending sequence for 'Esas No'."),
1258 |     kararYil: str = Field("", description="Decision year for 'Karar No'."),
1259 |     kararIlkSiraNo: str = Field("", description="Starting sequence for 'Karar No'."),
1260 |     kararSonSiraNo: str = Field("", description="Ending sequence for 'Karar No'."),
1261 |     baslangicTarihi: str = Field("", description="Start date for decision (DD.MM.YYYY)."),
1262 |     bitisTarihi: str = Field("", description="End date for decision (DD.MM.YYYY)."),
1263 |     mevzuatNumarasi: str = Field("", description="Legislation number."),
1264 |     mevzuatAdi: str = Field("", description="Legislation name."),
1265 |     madde: str = Field("", description="Article number."),
1266 |     pageNumber: int = Field(1, ge=1, description="Page number."),
1267 |     # pageSize: int = Field(10, ge=1, le=10, description="Results per page.")
1268 | ) -> CompactDanistaySearchResult:
1269 |     # Search Danıştay decisions with detailed filtering.
1270 |     
1271 |     pageSize = 10  # Default value
1272 |     
1273 |     search_query = DanistayDetailedSearchRequest(
1274 |         daire=daire,
1275 |         esasYil=esasYil,
1276 |         esasIlkSiraNo=esasIlkSiraNo,
1277 |         esasSonSiraNo=esasSonSiraNo,
1278 |         kararYil=kararYil,
1279 |         kararIlkSiraNo=kararIlkSiraNo,
1280 |         kararSonSiraNo=kararSonSiraNo,
1281 |         baslangicTarihi=baslangicTarihi,
1282 |         bitisTarihi=bitisTarihi,
1283 |         mevzuatNumarasi=mevzuatNumarasi,
1284 |         mevzuatAdi=mevzuatAdi,
1285 |         madde=madde,
1286 |         siralama="3",
1287 |         siralamaDirection="desc",
1288 |         pageNumber=pageNumber,
1289 |         pageSize=pageSize
1290 |     )
1291 |     
1292 |     logger.info(f"Tool 'search_danistay_detailed' called.")
1293 |     try:
1294 |         api_response = await danistay_client_instance.search_detailed_decisions(search_query)
1295 |         if api_response.data:
1296 |             return CompactDanistaySearchResult(
1297 |                 decisions=api_response.data.data,
1298 |                 total_records=api_response.data.recordsTotal,
1299 |                 requested_page=search_query.pageNumber,
1300 |                 page_size=search_query.pageSize)
1301 |         logger.warning("API response for Danistay detailed search did not contain expected data structure.")
1302 |         return CompactDanistaySearchResult(decisions=[], total_records=0, requested_page=search_query.pageNumber, page_size=search_query.pageSize)
1303 |     except Exception as e:
1304 |         logger.exception(f"Error in tool 'search_danistay_detailed'.")
1305 |         raise
1306 | 
1307 | @app.tool(
1308 |     description="Get Danıştay decision text in Markdown format",
1309 |     annotations={
1310 |         "readOnlyHint": True,
1311 |         "idempotentHint": True
1312 |     }
1313 | )
1314 | async def get_danistay_document_markdown(id: str) -> DanistayDocumentMarkdown:
1315 |     # Get Danıştay decision text as Markdown. Use ID from search results.
1316 |     logger.info(f"Tool 'get_danistay_document_markdown' called for ID: {id}")
1317 |     if not id or not id.strip(): raise ValueError("Document ID must be a non-empty string for Danıştay.")
1318 |     try:
1319 |         return await danistay_client_instance.get_decision_document_as_markdown(id)
1320 |     except Exception as e:
1321 |         logger.exception(f"Error in tool 'get_danistay_document_markdown'.")
1322 |         raise
1323 | """
1324 | 
1325 | # --- MCP Tools for Emsal ---
1326 | @app.tool(
1327 |     description="Search Emsal precedent decisions with detailed criteria",
1328 |     annotations={
1329 |         "readOnlyHint": True,
1330 |         "openWorldHint": True,
1331 |         "idempotentHint": True
1332 |     }
1333 | )
1334 | async def search_emsal_detailed_decisions(
1335 |     keyword: str = Field("", description="Keyword to search."),
1336 |     selected_bam_civil_court: str = Field("", description="Selected BAM Civil Court."),
1337 |     selected_civil_court: str = Field("", description="Selected Civil Court."),
1338 |     selected_regional_civil_chambers: List[str] = Field(default_factory=list, description="Selected Regional Civil Chambers."),
1339 |     case_year_esas: str = Field("", description="Case year for 'Esas No'."),
1340 |     case_start_seq_esas: str = Field("", description="Starting sequence for 'Esas No'."),
1341 |     case_end_seq_esas: str = Field("", description="Ending sequence for 'Esas No'."),
1342 |     decision_year_karar: str = Field("", description="Decision year for 'Karar No'."),
1343 |     decision_start_seq_karar: str = Field("", description="Starting sequence for 'Karar No'."),
1344 |     decision_end_seq_karar: str = Field("", description="Ending sequence for 'Karar No'."),
1345 |     start_date: str = Field("", description="Start date for decision (DD.MM.YYYY)."),
1346 |     end_date: str = Field("", description="End date for decision (DD.MM.YYYY)."),
1347 |     sort_criteria: str = Field("1", description="Sorting criteria (e.g., 1: Esas No)."),
1348 |     sort_direction: str = Field("desc", description="Sorting direction ('asc' or 'desc')."),
1349 |     page_number: int = Field(1, ge=1, description="Page number (accepts int)."),
1350 |     # page_size: int = Field(10, ge=1, le=10, description="Results per page.")
1351 | ) -> CompactEmsalSearchResult:
1352 |     """Search Emsal precedent decisions with detailed criteria."""
1353 |     
1354 |     page_size = 10  # Default value
1355 |     
1356 |     search_query = EmsalSearchRequest(
1357 |         keyword=keyword,
1358 |         selected_bam_civil_court=selected_bam_civil_court,
1359 |         selected_civil_court=selected_civil_court,
1360 |         selected_regional_civil_chambers=selected_regional_civil_chambers,
1361 |         case_year_esas=case_year_esas,
1362 |         case_start_seq_esas=case_start_seq_esas,
1363 |         case_end_seq_esas=case_end_seq_esas,
1364 |         decision_year_karar=decision_year_karar,
1365 |         decision_start_seq_karar=decision_start_seq_karar,
1366 |         decision_end_seq_karar=decision_end_seq_karar,
1367 |         start_date=start_date,
1368 |         end_date=end_date,
1369 |         sort_criteria=sort_criteria,
1370 |         sort_direction=sort_direction,
1371 |         page_number=page_number,
1372 |         page_size=page_size
1373 |     )
1374 |     
1375 |     logger.info(f"Tool 'search_emsal_detailed_decisions' called.")
1376 |     try:
1377 |         api_response = await emsal_client_instance.search_detailed_decisions(search_query)
1378 |         if api_response.data:
1379 |             return CompactEmsalSearchResult(
1380 |                 decisions=api_response.data.data,
1381 |                 total_records=api_response.data.recordsTotal if api_response.data.recordsTotal is not None else 0,
1382 |                 requested_page=search_query.page_number,
1383 |                 page_size=search_query.page_size
1384 |             )
1385 |         logger.warning("API response for Emsal search did not contain expected data structure.")
1386 |         return CompactEmsalSearchResult(decisions=[], total_records=0, requested_page=search_query.page_number, page_size=search_query.page_size)
1387 |     except Exception as e:
1388 |         logger.exception(f"Error in tool 'search_emsal_detailed_decisions'.")
1389 |         raise
1390 | 
1391 | @app.tool(
1392 |     description="Get Emsal precedent decision text in Markdown format",
1393 |     annotations={
1394 |         "readOnlyHint": True,
1395 |         "idempotentHint": True
1396 |     }
1397 | )
1398 | async def get_emsal_document_markdown(id: str) -> EmsalDocumentMarkdown:
1399 |     """Get document as Markdown."""
1400 |     logger.info(f"Tool 'get_emsal_document_markdown' called for ID: {id}")
1401 |     if not id or not id.strip(): raise ValueError("Document ID required for Emsal.")
1402 |     try:
1403 |         return await emsal_client_instance.get_decision_document_as_markdown(id)
1404 |     except Exception as e:
1405 |         logger.exception(f"Error in tool 'get_emsal_document_markdown'.")
1406 |         raise
1407 | 
1408 | # --- MCP Tools for Uyusmazlik ---
1409 | @app.tool(
1410 |     description="Search Uyuşmazlık Mahkemesi decisions for jurisdictional disputes",
1411 |     annotations={
1412 |         "readOnlyHint": True,
1413 |         "openWorldHint": True,
1414 |         "idempotentHint": True
1415 |     }
1416 | )
1417 | async def search_uyusmazlik_decisions(
1418 |     icerik: str = Field("", description="Keyword or content for main text search."),
1419 |     bolum: Literal["ALL", "Ceza Bölümü", "Genel Kurul Kararları", "Hukuk Bölümü"] = Field("ALL", description="Select the department (Bölüm). Use 'ALL' for all departments."),
1420 |     uyusmazlik_turu: Literal["ALL", "Görev Uyuşmazlığı", "Hüküm Uyuşmazlığı"] = Field("ALL", description="Select the type of dispute. Use 'ALL' for all types."),
1421 |     karar_sonuclari: List[Literal["Hüküm Uyuşmazlığı Olmadığına Dair", "Hüküm Uyuşmazlığı Olduğuna Dair"]] = Field(default_factory=list, description="List of desired 'Karar Sonucu' types."),
1422 |     esas_yil: str = Field("", description="Case year ('Esas Yılı')."),
1423 |     esas_sayisi: str = Field("", description="Case number ('Esas Sayısı')."),
1424 |     karar_yil: str = Field("", description="Decision year ('Karar Yılı')."),
1425 |     karar_sayisi: str = Field("", description="Decision number ('Karar Sayısı')."),
1426 |     kanun_no: str = Field("", description="Relevant Law Number."),
1427 |     karar_date_begin: str = Field("", description="Decision start date (DD.MM.YYYY)."),
1428 |     karar_date_end: str = Field("", description="Decision end date (DD.MM.YYYY)."),
1429 |     resmi_gazete_sayi: str = Field("", description="Official Gazette number."),
1430 |     resmi_gazete_date: str = Field("", description="Official Gazette date (DD.MM.YYYY)."),
1431 |     tumce: str = Field("", description="Exact phrase search."),
1432 |     wild_card: str = Field("", description="Search for phrase and its inflections."),
1433 |     hepsi: str = Field("", description="Search for texts containing all specified words."),
1434 |     herhangi_birisi: str = Field("", description="Search for texts containing any of the specified words."),
1435 |     not_hepsi: str = Field("", description="Exclude texts containing these specified words.")
1436 | ) -> UyusmazlikSearchResponse:
1437 |     """Search Court of Jurisdictional Disputes decisions."""
1438 |     
1439 |     # Convert string literals to enums
1440 |     # Map "ALL" to TUMU for backward compatibility
1441 |     if bolum == "ALL":
1442 |         bolum_enum = UyusmazlikBolumEnum.TUMU
1443 |     else:
1444 |         bolum_enum = UyusmazlikBolumEnum(bolum) if bolum else UyusmazlikBolumEnum.TUMU
1445 |     
1446 |     if uyusmazlik_turu == "ALL":
1447 |         uyusmazlik_turu_enum = UyusmazlikTuruEnum.TUMU
1448 |     else:
1449 |         uyusmazlik_turu_enum = UyusmazlikTuruEnum(uyusmazlik_turu) if uyusmazlik_turu else UyusmazlikTuruEnum.TUMU
1450 |     karar_sonuclari_enums = [UyusmazlikKararSonucuEnum(ks) for ks in karar_sonuclari]
1451 |     
1452 |     search_params = UyusmazlikSearchRequest(
1453 |         icerik=icerik,
1454 |         bolum=bolum_enum,
1455 |         uyusmazlik_turu=uyusmazlik_turu_enum,
1456 |         karar_sonuclari=karar_sonuclari_enums,
1457 |         esas_yil=esas_yil,
1458 |         esas_sayisi=esas_sayisi,
1459 |         karar_yil=karar_yil,
1460 |         karar_sayisi=karar_sayisi,
1461 |         kanun_no=kanun_no,
1462 |         karar_date_begin=karar_date_begin,
1463 |         karar_date_end=karar_date_end,
1464 |         resmi_gazete_sayi=resmi_gazete_sayi,
1465 |         resmi_gazete_date=resmi_gazete_date,
1466 |         tumce=tumce,
1467 |         wild_card=wild_card,
1468 |         hepsi=hepsi,
1469 |         herhangi_birisi=herhangi_birisi,
1470 |         not_hepsi=not_hepsi
1471 |     )
1472 |     
1473 |     logger.info(f"Tool 'search_uyusmazlik_decisions' called.")
1474 |     try:
1475 |         return await uyusmazlik_client_instance.search_decisions(search_params)
1476 |     except Exception as e:
1477 |         logger.exception(f"Error in tool 'search_uyusmazlik_decisions'.")
1478 |         raise
1479 | 
1480 | @app.tool(
1481 |     description="Get Uyuşmazlık Mahkemesi decision text from URL in Markdown format",
1482 |     annotations={
1483 |         "readOnlyHint": True,
1484 |         "idempotentHint": True
1485 |     }
1486 | )
1487 | async def get_uyusmazlik_document_markdown_from_url(
1488 |     document_url: str = Field(..., description="Full URL to the Uyuşmazlık Mahkemesi decision document from search results")
1489 | ) -> UyusmazlikDocumentMarkdown:
1490 |     """Get Uyuşmazlık Mahkemesi decision as Markdown."""
1491 |     logger.info(f"Tool 'get_uyusmazlik_document_markdown_from_url' called for URL: {str(document_url)}")
1492 |     if not document_url:
1493 |         raise ValueError("Document URL (document_url) is required for Uyuşmazlık document retrieval.")
1494 |     try:
1495 |         return await uyusmazlik_client_instance.get_decision_document_as_markdown(str(document_url))
1496 |     except Exception as e:
1497 |         logger.exception(f"Error in tool 'get_uyusmazlik_document_markdown_from_url'.")
1498 |         raise
1499 | 
1500 | # --- DEACTIVATED: MCP Tools for Anayasa Mahkemesi (Individual Tools) ---
1501 | # Use search_anayasa_unified and get_anayasa_document_unified instead
1502 | 
1503 | """
1504 | @app.tool(
1505 |     description="Search Constitutional Court norm control decisions with comprehensive filtering",
1506 |     annotations={
1507 |         "readOnlyHint": True,
1508 |         "openWorldHint": True,
1509 |         "idempotentHint": True
1510 |     }
1511 | )
1512 | # DEACTIVATED TOOL - Use search_anayasa_unified instead
1513 | # @app.tool(
1514 | #     description="DEACTIVATED - Use search_anayasa_unified instead",
1515 | #     annotations={"readOnlyHint": True, "openWorldHint": False, "idempotentHint": True}
1516 | # )
1517 | # async def search_anayasa_norm_denetimi_decisions(...) -> AnayasaSearchResult:
1518 | #     raise ValueError("This tool is deactivated. Use search_anayasa_unified instead.")
1519 | 
1520 | # DEACTIVATED TOOL - Use get_anayasa_document_unified instead
1521 | # @app.tool(...)
1522 | # async def get_anayasa_norm_denetimi_document_markdown(...) -> AnayasaDocumentMarkdown:
1523 | #     raise ValueError("This tool is deactivated. Use get_anayasa_document_unified instead.")
1524 | 
1525 | # DEACTIVATED TOOL - Use search_anayasa_unified instead
1526 | # @app.tool(...)
1527 | # async def search_anayasa_bireysel_basvuru_report(...) -> AnayasaBireyselReportSearchResult:
1528 | #     raise ValueError("This tool is deactivated. Use search_anayasa_unified instead.")
1529 | 
1530 | # DEACTIVATED TOOL - Use get_anayasa_document_unified instead
1531 | # @app.tool(...)
1532 | # async def get_anayasa_bireysel_basvuru_document_markdown(...) -> AnayasaBireyselBasvuruDocumentMarkdown:
1533 | #     raise ValueError("This tool is deactivated. Use get_anayasa_document_unified instead.")
1534 | """
1535 | 
1536 | # --- Unified MCP Tools for Anayasa Mahkemesi ---
1537 | @app.tool(
1538 |     description="Unified search for Constitutional Court decisions: both norm control (normkararlarbilgibankasi) and individual applications (kararlarbilgibankasi) in one tool",
1539 |     annotations={
1540 |         "readOnlyHint": True,
1541 |         "openWorldHint": True,
1542 |         "idempotentHint": True
1543 |     }
1544 | )
1545 | async def search_anayasa_unified(
1546 |     decision_type: Literal["norm_denetimi", "bireysel_basvuru"] = Field(..., description="Decision type: norm_denetimi (norm control) or bireysel_basvuru (individual applications)"),
1547 |     keywords: List[str] = Field(default_factory=list, description="Keywords to search for (common parameter)"),
1548 |     page_to_fetch: int = Field(1, ge=1, le=100, description="Page number to fetch (1-100)"),
1549 |     # results_per_page: int = Field(10, ge=1, le=100, description="Results per page (1-100)"),
1550 |     
1551 |     # Norm Denetimi specific parameters (ignored for bireysel_basvuru)
1552 |     keywords_all: List[str] = Field(default_factory=list, description="All keywords must be present (norm_denetimi only)"),
1553 |     keywords_any: List[str] = Field(default_factory=list, description="Any of these keywords (norm_denetimi only)"),
1554 |     decision_type_norm: Literal["ALL", "1", "2", "3"] = Field("ALL", description="Decision type for norm denetimi"),
1555 |     application_date_start: str = Field("", description="Application start date (norm_denetimi only)"),
1556 |     application_date_end: str = Field("", description="Application end date (norm_denetimi only)"),
1557 |     
1558 |     # Bireysel Başvuru specific parameters (ignored for norm_denetimi)
1559 |     decision_start_date: str = Field("", description="Decision start date (bireysel_basvuru only)"),
1560 |     decision_end_date: str = Field("", description="Decision end date (bireysel_basvuru only)"),
1561 |     norm_type: Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "0"] = Field("ALL", description="Norm type (bireysel_basvuru only)"),
1562 |     subject_category: str = Field("", description="Subject category (bireysel_basvuru only)")
1563 | ) -> str:
1564 |     logger.info(f"Tool 'search_anayasa_unified' called for decision_type: {decision_type}")
1565 |     
1566 |     results_per_page = 10  # Default value
1567 |     
1568 |     try:
1569 |         request = AnayasaUnifiedSearchRequest(
1570 |             decision_type=decision_type,
1571 |             keywords=keywords,
1572 |             page_to_fetch=page_to_fetch,
1573 |             results_per_page=results_per_page,
1574 |             keywords_all=keywords_all,
1575 |             keywords_any=keywords_any,
1576 |             decision_type_norm=decision_type_norm,
1577 |             application_date_start=application_date_start,
1578 |             application_date_end=application_date_end,
1579 |             decision_start_date=decision_start_date,
1580 |             decision_end_date=decision_end_date,
1581 |             norm_type=norm_type,
1582 |             subject_category=subject_category
1583 |         )
1584 |         
1585 |         result = await anayasa_unified_client_instance.search_unified(request)
1586 |         return json.dumps(result.model_dump(), ensure_ascii=False, indent=2)
1587 |         
1588 |     except Exception as e:
1589 |         logger.exception(f"Error in tool 'search_anayasa_unified'.")
1590 |         raise
1591 | 
1592 | @app.tool(
1593 |     description="Unified document retrieval for Constitutional Court decisions: auto-detects norm control vs individual applications based on URL",
1594 |     annotations={
1595 |         "readOnlyHint": True,
1596 |         "openWorldHint": False,
1597 |         "idempotentHint": True
1598 |     }
1599 | )
1600 | async def get_anayasa_document_unified(
1601 |     document_url: str = Field(..., description="Document URL from search results"),
1602 |     page_number: int = Field(1, ge=1, description="Page number for paginated content (1-indexed)")
1603 | ) -> str:
1604 |     logger.info(f"Tool 'get_anayasa_document_unified' called for URL: {document_url}, Page: {page_number}")
1605 |     
1606 |     try:
1607 |         result = await anayasa_unified_client_instance.get_document_unified(document_url, page_number)
1608 |         return json.dumps(result.model_dump(mode='json'), ensure_ascii=False, indent=2)
1609 |         
1610 |     except Exception as e:
1611 |         logger.exception(f"Error in tool 'get_anayasa_document_unified'.")
1612 |         raise
1613 | 
1614 | # --- MCP Tools for KIK (Kamu İhale Kurulu) ---
1615 | @app.tool(
1616 |     description="Search Public Procurement Authority (KİK) decisions for procurement law disputes",
1617 |     annotations={
1618 |         "readOnlyHint": True,
1619 |         "openWorldHint": True,
1620 |         "idempotentHint": True
1621 |     }
1622 | )
1623 | async def search_kik_decisions(
1624 |     karar_tipi: Literal["rbUyusmazlik", "rbDuzenleyici", "rbMahkeme"] = Field("rbUyusmazlik", description="Type of KIK Decision."),
1625 |     karar_no: str = Field("", description="Decision Number (e.g., '2024/UH.II-1766')."),
1626 |     karar_tarihi_baslangic: str = Field("", description="Decision Date Start (DD.MM.YYYY)."),
1627 |     karar_tarihi_bitis: str = Field("", description="Decision Date End (DD.MM.YYYY)."),
1628 |     basvuru_sahibi: str = Field("", description="Applicant."),
1629 |     ihaleyi_yapan_idare: str = Field("", description="Procuring Entity."),
1630 |     basvuru_konusu_ihale: str = Field("", description="Tender subject of the application."),
1631 |     karar_metni: str = Field("", description="Decision text search. Supports: +word, -word, \"exact phrase\", OR/AND"),
1632 |     yil: str = Field("", description="Year of the decision."),
1633 |     resmi_gazete_tarihi: str = Field("", description="Official Gazette Date (DD.MM.YYYY)."),
1634 |     resmi_gazete_sayisi: str = Field("", description="Official Gazette Number."),
1635 |     page: int = Field(1, ge=1, description="Results page number.")
1636 | ) -> KikSearchResult:
1637 |     """Search Public Procurement Authority (KIK) decisions."""
1638 |     
1639 |     # Convert string literal to enum
1640 |     karar_tipi_enum = KikKararTipi(karar_tipi)
1641 |     
1642 |     search_query = KikSearchRequest(
1643 |         karar_tipi=karar_tipi_enum,
1644 |         karar_no=karar_no,
1645 |         karar_tarihi_baslangic=karar_tarihi_baslangic,
1646 |         karar_tarihi_bitis=karar_tarihi_bitis,
1647 |         basvuru_sahibi=basvuru_sahibi,
1648 |         ihaleyi_yapan_idare=ihaleyi_yapan_idare,
1649 |         basvuru_konusu_ihale=basvuru_konusu_ihale,
1650 |         karar_metni=karar_metni,
1651 |         yil=yil,
1652 |         resmi_gazete_tarihi=resmi_gazete_tarihi,
1653 |         resmi_gazete_sayisi=resmi_gazete_sayisi,
1654 |         page=page
1655 |     )
1656 |     
1657 |     logger.info(f"Tool 'search_kik_decisions' called.")
1658 |     try:
1659 |         api_response = await kik_client_instance.search_decisions(search_query)
1660 |         page_param_for_log = search_query.page if hasattr(search_query, 'page') else 1
1661 |         if not api_response.decisions and api_response.total_records == 0 and page_param_for_log == 1:
1662 |              logger.warning(f"KIK search returned no decisions for query.")
1663 |         return api_response
1664 |     except Exception as e:
1665 |         logger.exception(f"Error in KIK search tool 'search_kik_decisions'.")
1666 |         current_page_val = search_query.page if hasattr(search_query, 'page') else 1
1667 |         return KikSearchResult(decisions=[], total_records=0, current_page=current_page_val)
1668 | 
1669 | @app.tool(
1670 |     description="Get Public Procurement Authority (KİK) decision text in paginated Markdown format",
1671 |     annotations={
1672 |         "readOnlyHint": True,
1673 |         "idempotentHint": True
1674 |     }
1675 | )
1676 | async def get_kik_document_markdown(
1677 |     karar_id: str = Field(..., description="The Base64 encoded KIK decision identifier."),
1678 |     page_number: int = Field(1, ge=1, description="Page number for paginated Markdown content (1-indexed). Default is 1.")
1679 | ) -> KikDocumentMarkdown:
1680 |     """Get KIK decision as paginated Markdown."""
1681 |     logger.info(f"Tool 'get_kik_document_markdown' called for KIK karar_id: {karar_id}, Markdown Page: {page_number}")
1682 |     
1683 |     if not karar_id or not karar_id.strip():
1684 |         logger.error("KIK Document retrieval: karar_id cannot be empty.")
1685 |         return KikDocumentMarkdown( 
1686 |             retrieved_with_karar_id=karar_id,
1687 |             error_message="karar_id is required and must be a non-empty string.",
1688 |             current_page=page_number or 1,
1689 |             total_pages=1,
1690 |             is_paginated=False
1691 |             )
1692 | 
1693 |     current_page_to_fetch = page_number if page_number is not None and page_number >= 1 else 1
1694 | 
1695 |     try:
1696 |         return await kik_client_instance.get_decision_document_as_markdown(
1697 |             karar_id_b64=karar_id, 
1698 |             page_number=current_page_to_fetch
1699 |         )
1700 |     except Exception as e:
1701 |         logger.exception(f"Error in KIK document retrieval tool 'get_kik_document_markdown' for karar_id: {karar_id}")
1702 |         return KikDocumentMarkdown(
1703 |             retrieved_with_karar_id=karar_id,
1704 |             error_message=f"Tool-level error during KIK document retrieval: {str(e)}",
1705 |             current_page=current_page_to_fetch, 
1706 |             total_pages=1, 
1707 |             is_paginated=False
1708 |         )
1709 | @app.tool(
1710 |     description="Search Competition Authority (Rekabet Kurumu) decisions for competition law and antitrust",
1711 |     annotations={
1712 |         "readOnlyHint": True,
1713 |         "openWorldHint": True,
1714 |         "idempotentHint": True
1715 |     }
1716 | )
1717 | async def search_rekabet_kurumu_decisions(
1718 |     sayfaAdi: str = Field("", description="Search in decision title (Başlık)."),
1719 |     YayinlanmaTarihi: str = Field("", description="Publication date (Yayım Tarihi), e.g., DD.MM.YYYY."),
1720 |     PdfText: str = Field(
1721 |         "",
1722 |         description='Search in decision text. Use "\\"kesin cümle\\"" for precise matching.'
1723 |     ),
1724 |     KararTuru: Literal[ 
1725 |         "ALL", 
1726 |         "Birleşme ve Devralma",
1727 |         "Diğer",
1728 |         "Menfi Tespit ve Muafiyet",
1729 |         "Özelleştirme",
1730 |         "Rekabet İhlali"
1731 |     ] = Field("ALL", description="Parameter description"),
1732 |     KararSayisi: str = Field("", description="Decision number (Karar Sayısı)."),
1733 |     KararTarihi: str = Field("", description="Decision date (Karar Tarihi), e.g., DD.MM.YYYY."),
1734 |     page: int = Field(1, ge=1, description="Page number to fetch for the results list.")
1735 | ) -> RekabetSearchResult:
1736 |     """Search Competition Authority decisions."""
1737 |     
1738 |     karar_turu_guid_enum = KARAR_TURU_ADI_TO_GUID_ENUM_MAP.get(KararTuru)
1739 | 
1740 |     try:
1741 |         if karar_turu_guid_enum is None: 
1742 |             logger.warning(f"Invalid user-provided KararTuru: '{KararTuru}'. Defaulting to TUMU (all).")
1743 |             karar_turu_guid_enum = RekabetKararTuruGuidEnum.TUMU
1744 |     except Exception as e_map: 
1745 |         logger.error(f"Error mapping KararTuru '{KararTuru}': {e_map}. Defaulting to TUMU.")
1746 |         karar_turu_guid_enum = RekabetKararTuruGuidEnum.TUMU
1747 | 
1748 |     search_query = RekabetKurumuSearchRequest(
1749 |         sayfaAdi=sayfaAdi,
1750 |         YayinlanmaTarihi=YayinlanmaTarihi,
1751 |         PdfText=PdfText,
1752 |         KararTuruID=karar_turu_guid_enum, 
1753 |         KararSayisi=KararSayisi,
1754 |         KararTarihi=KararTarihi,
1755 |         page=page
1756 |     )
1757 |     logger.info(f"Tool 'search_rekabet_kurumu_decisions' called. Query: {search_query.model_dump_json(exclude_none=True, indent=2)}")
1758 |     try:
1759 |        
1760 |         return await rekabet_client_instance.search_decisions(search_query)
1761 |     except Exception as e:
1762 |         logger.exception("Error in tool 'search_rekabet_kurumu_decisions'.")
1763 |         return RekabetSearchResult(decisions=[], retrieved_page_number=page, total_records_found=0, total_pages=0)
1764 | 
1765 | @app.tool(
1766 |     description="Get Competition Authority decision text in paginated Markdown format",
1767 |     annotations={
1768 |         "readOnlyHint": True,
1769 |         "idempotentHint": True
1770 |     }
1771 | )
1772 | async def get_rekabet_kurumu_document(
1773 |     karar_id: str = Field(..., description="GUID (kararId) of the Rekabet Kurumu decision. This ID is obtained from search results."),
1774 |     page_number: int = Field(1, ge=1, description="Requested page number for the Markdown content converted from PDF (1-indexed, accepts int). Default is 1.")
1775 | ) -> RekabetDocument:
1776 |     """Get Competition Authority decision as paginated Markdown."""
1777 |     logger.info(f"Tool 'get_rekabet_kurumu_document' called. Karar ID: {karar_id}, Markdown Page: {page_number}")
1778 |     
1779 |     current_page_to_fetch = page_number if page_number >= 1 else 1
1780 |     
1781 |     try:
1782 |       
1783 |         return await rekabet_client_instance.get_decision_document(karar_id, page_number=current_page_to_fetch)
1784 |     except Exception as e:
1785 |         logger.exception(f"Error in tool 'get_rekabet_kurumu_document'. Karar ID: {karar_id}")
1786 |         raise 
1787 | 
1788 | # --- MCP Tools for Bedesten (Unified Search Across All Courts) ---
1789 | @app.tool(
1790 |     description="Search multiple Turkish courts (Yargıtay, Danıştay, Local Courts, Appeals Courts, KYB)",
1791 |     annotations={
1792 |         "readOnlyHint": True,
1793 |         "openWorldHint": True,
1794 |         "idempotentHint": True
1795 |     }
1796 | )
1797 | async def search_bedesten_unified(
1798 |     ctx: Context,
1799 |     phrase: str = Field(..., description="""Search query in Turkish. SUPPORTED OPERATORS:
1800 | • Simple: "mülkiyet hakkı" (finds both words)
1801 | • Exact phrase: "\"mülkiyet hakkı\"" (finds exact phrase)  
1802 | • Required term: "+mülkiyet hakkı" (must contain mülkiyet)
1803 | • Exclude term: "mülkiyet -kira" (contains mülkiyet but not kira)
1804 | • Boolean AND: "mülkiyet AND hak" (both terms required)
1805 | • Boolean OR: "mülkiyet OR tapu" (either term acceptable)
1806 | • Boolean NOT: "mülkiyet NOT satış" (contains mülkiyet but not satış)
1807 | NOTE: Wildcards (*,?), regex patterns (/regex/), fuzzy search (~), and proximity search are NOT supported.
1808 | For best results, use exact phrases with quotes for legal terms."""),
1809 |     court_types: List[BedestenCourtTypeEnum] = Field(
1810 |         default=["YARGITAYKARARI", "DANISTAYKARAR"], 
1811 |         description="Court types: YARGITAYKARARI, DANISTAYKARAR, YERELHUKUK, ISTINAFHUKUK, KYB"
1812 |     ),
1813 |     # pageSize: int = Field(10, ge=1, le=10, description="Results per page (1-10)"),
1814 |     pageNumber: int = Field(1, ge=1, description="Page number"),
1815 |     birimAdi: BirimAdiEnum = Field("ALL", description="""
1816 |         Chamber filter (optional). Abbreviated values with Turkish names:
1817 |         • Yargıtay: H1-H23 (1-23. Hukuk Dairesi), C1-C23 (1-23. Ceza Dairesi), HGK (Hukuk Genel Kurulu), CGK (Ceza Genel Kurulu), BGK (Büyük Genel Kurulu), HBK (Hukuk Daireleri Başkanlar Kurulu), CBK (Ceza Daireleri Başkanlar Kurulu)
1818 |         • Danıştay: D1-D17 (1-17. Daire), DBGK (Büyük Gen.Kur.), IDDK (İdare Dava Daireleri Kurulu), VDDK (Vergi Dava Daireleri Kurulu), IBK (İçtihatları Birleştirme Kurulu), IIK (İdari İşler Kurulu), DBK (Başkanlar Kurulu), AYIM (Askeri Yüksek İdare Mahkemesi), AYIM1-3 (Askeri Yüksek İdare Mahkemesi 1-3. Daire)
1819 |         """),
1820 |     kararTarihiStart: str = Field("", description="Start date (ISO 8601 format)"),
1821 |     kararTarihiEnd: str = Field("", description="End date (ISO 8601 format)")
1822 | ) -> dict:
1823 |     """Search Turkish legal databases via unified Bedesten API."""
1824 |     
1825 |     # Get Bearer token information for access control and logging
1826 |     try:
1827 |         access_token: AccessToken = get_access_token()
1828 |         user_id = access_token.client_id
1829 |         user_scopes = access_token.scopes
1830 |         
1831 |         # Check for required scopes
1832 |         if "yargi.read" not in user_scopes and "yargi.search" not in user_scopes:
1833 |             raise ToolError(f"Insufficient permissions: 'yargi.read' or 'yargi.search' scope required. Current scopes: {user_scopes}")
1834 |         
1835 |         logger.info(f"Tool 'search_bedesten_unified' called by user '{user_id}' with scopes {user_scopes}")
1836 |         
1837 |     except Exception as e:
1838 |         # Development mode fallback - allow access without strict token validation
1839 |         logger.warning(f"Bearer token validation failed, using development mode: {str(e)}")
1840 |         user_id = "dev-user"
1841 |         user_scopes = ["yargi.read", "yargi.search"]
1842 |     
1843 |     pageSize = 10  # Default value
1844 |     
1845 |     search_data = BedestenSearchData(
1846 |         pageSize=pageSize,
1847 |         pageNumber=pageNumber,
1848 |         itemTypeList=court_types,
1849 |         phrase=phrase,
1850 |         birimAdi=birimAdi,
1851 |         kararTarihiStart=kararTarihiStart,
1852 |         kararTarihiEnd=kararTarihiEnd
1853 |     )
1854 |     
1855 |     search_request = BedestenSearchRequest(data=search_data)
1856 |     
1857 |     logger.info(f"User '{user_id}' searching bedesten: phrase='{phrase}', court_types={court_types}, birimAdi='{birimAdi}', page={pageNumber}")
1858 |     
1859 |     try:
1860 |         response = await bedesten_client_instance.search_documents(search_request)
1861 |         
1862 |         if response.data is None:
1863 |             return {
1864 |                 "decisions": [],
1865 |                 "total_records": 0,
1866 |                 "requested_page": pageNumber,
1867 |                 "page_size": pageSize,
1868 |                 "searched_courts": court_types,
1869 |                 "error": "No data returned from Bedesten API"
1870 |             }
1871 |         
1872 |         return {
1873 |             "decisions": [d.model_dump() for d in response.data.emsalKararList],
1874 |             "total_records": response.data.total,
1875 |             "requested_page": pageNumber,
1876 |             "page_size": pageSize,
1877 |             "searched_courts": court_types
1878 |         }
1879 |     except Exception as e:
1880 |         logger.exception("Error in tool 'search_bedesten_unified'")
1881 |         raise
1882 | 
1883 | @app.tool(
1884 |     description="Get legal decision document from Bedesten API in Markdown format",
1885 |     annotations={
1886 |         "readOnlyHint": True,
1887 |         "idempotentHint": True
1888 |     }
1889 | )
1890 | async def get_bedesten_document_markdown(
1891 |     documentId: str = Field(..., description="Document ID from Bedesten search results")
1892 | ) -> BedestenDocumentMarkdown:
1893 |     """Get legal decision document as Markdown from Bedesten API."""
1894 |     logger.info(f"Tool 'get_bedesten_document_markdown' called for ID: {documentId}")
1895 |     
1896 |     if not documentId or not documentId.strip():
1897 |         raise ValueError("Document ID must be a non-empty string.")
1898 |     
1899 |     try:
1900 |         return await bedesten_client_instance.get_document_as_markdown(documentId)
1901 |     except Exception as e:
1902 |         logger.exception("Error in tool 'get_kyb_bedesten_document_markdown'")
1903 |         raise
1904 | 
1905 | # --- MCP Tools for Sayıştay (Turkish Court of Accounts) ---
1906 | 
1907 | # DEACTIVATED TOOL - Use search_sayistay_unified instead
1908 | # @app.tool(
1909 | #     description="Search Sayıştay Genel Kurul decisions for audit and accountability regulations",
1910 | #     annotations={
1911 | #         "readOnlyHint": True,
1912 | #         "openWorldHint": True,
1913 | #         "idempotentHint": True
1914 | #     }
1915 | # )
1916 | # async def search_sayistay_genel_kurul(
1917 | #     karar_no: str = Field("", description="Decision number to search for (e.g., '5415')"),
1918 | #     karar_ek: str = Field("", description="Decision appendix number (max 99, e.g., '1')"),
1919 | #     karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY)"),
1920 | #     karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY)"),
1921 | #     karar_tamami: str = Field("", description="Full text search"),
1922 | #     start: int = Field(0, description="Starting record for pagination (0-based)"),
1923 | #     length: int = Field(10, description="Number of records per page (1-100)")
1924 | # ) -> GenelKurulSearchResponse:
1925 | #     """Search Sayıştay General Assembly decisions."""
1926 | #     raise ValueError("This tool is deactivated. Use search_sayistay_unified instead.")
1927 | 
1928 | # DEACTIVATED TOOL - Use search_sayistay_unified instead
1929 | # @app.tool(
1930 | #     description="Search Sayıştay Temyiz Kurulu decisions with chamber filtering and comprehensive criteria",
1931 | #     annotations={
1932 | #         "readOnlyHint": True,
1933 | #         "openWorldHint": True,
1934 | #         "idempotentHint": True
1935 | #     }
1936 | # )
1937 | # async def search_sayistay_temyiz_kurulu(
1938 | #     ilam_dairesi: DaireEnum = Field("ALL", description="Audit chamber selection"),
1939 | #     yili: str = Field("", description="Year (YYYY)"),
1940 | #     karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY)"),
1941 | #     karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY)"),
1942 | #     kamu_idaresi_turu: KamuIdaresiTuruEnum = Field("ALL", description="Public admin type"),
1943 | #     ilam_no: str = Field("", description="Audit report number (İlam No, max 50 chars)"),
1944 | #     dosya_no: str = Field("", description="File number for the case"),
1945 | #     temyiz_tutanak_no: str = Field("", description="Appeals board meeting minutes number"),
1946 | #     temyiz_karar: str = Field("", description="Appeals decision text"),
1947 | #     web_karar_konusu: WebKararKonusuEnum = Field("ALL", description="Decision subject"),
1948 | #     start: int = Field(0, description="Starting record for pagination (0-based)"),
1949 | #     length: int = Field(10, description="Number of records per page (1-100)")
1950 | # ) -> TemyizKuruluSearchResponse:
1951 | #     """Search Sayıştay Appeals Board decisions."""
1952 | #     raise ValueError("This tool is deactivated. Use search_sayistay_unified instead.")
1953 | 
1954 | # DEACTIVATED TOOL - Use search_sayistay_unified instead
1955 | # @app.tool(
1956 | #     description="Search Sayıştay Daire decisions with chamber filtering and subject categorization",
1957 | #     annotations={
1958 | #         "readOnlyHint": True,
1959 | #         "openWorldHint": True,
1960 | #         "idempotentHint": True
1961 | #     }
1962 | # )
1963 | # async def search_sayistay_daire(
1964 | #     yargilama_dairesi: DaireEnum = Field("ALL", description="Chamber selection"),
1965 | #     karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY)"),
1966 | #     karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY)"),
1967 | #     ilam_no: str = Field("", description="Audit report number (İlam No, max 50 chars)"),
1968 | #     kamu_idaresi_turu: KamuIdaresiTuruEnum = Field("ALL", description="Public admin type"),
1969 | #     hesap_yili: str = Field("", description="Fiscal year"),
1970 | #     web_karar_konusu: WebKararKonusuEnum = Field("ALL", description="Decision subject"),
1971 | #     web_karar_metni: str = Field("", description="Decision text search"),
1972 | #     start: int = Field(0, description="Starting record for pagination (0-based)"),
1973 | #     length: int = Field(10, description="Number of records per page (1-100)")
1974 | # ) -> DaireSearchResponse:
1975 | #     """Search Sayıştay Chamber decisions."""
1976 | #     raise ValueError("This tool is deactivated. Use search_sayistay_unified instead.")
1977 | 
1978 | # DEACTIVATED TOOL - Use get_sayistay_document_unified instead
1979 | # @app.tool(
1980 | #     description="Get Sayıştay Genel Kurul decision document in Markdown format",
1981 | #     annotations={
1982 | #         "readOnlyHint": True,
1983 | #         "openWorldHint": False,
1984 | #         "idempotentHint": True
1985 | #     }
1986 | # )
1987 | # async def get_sayistay_genel_kurul_document_markdown(
1988 | #     decision_id: str = Field(..., description="Decision ID from search_sayistay_genel_kurul results")
1989 | # ) -> SayistayDocumentMarkdown:
1990 | #     """Get Sayıştay General Assembly decision as Markdown."""
1991 | #     raise ValueError("This tool is deactivated. Use get_sayistay_document_unified instead.")
1992 | 
1993 | # DEACTIVATED TOOL - Use get_sayistay_document_unified instead
1994 | # @app.tool(
1995 | #     description="Get Sayıştay Temyiz Kurulu decision document in Markdown format",
1996 | #     annotations={
1997 | #         "readOnlyHint": True,
1998 | #         "openWorldHint": False,
1999 | #         "idempotentHint": True
2000 | #     }
2001 | # )
2002 | # async def get_sayistay_temyiz_kurulu_document_markdown(
2003 | #     decision_id: str = Field(..., description="Decision ID from search_sayistay_temyiz_kurulu results")
2004 | # ) -> SayistayDocumentMarkdown:
2005 | #     """Get Sayıştay Appeals Board decision as Markdown."""
2006 | #     raise ValueError("This tool is deactivated. Use get_sayistay_document_unified instead.")
2007 | 
2008 | # DEACTIVATED TOOL - Use get_sayistay_document_unified instead
2009 | # @app.tool(
2010 | #     description="Get Sayıştay Daire decision document in Markdown format",
2011 | #     annotations={
2012 | #         "readOnlyHint": True,
2013 | #         "openWorldHint": False,
2014 | #         "idempotentHint": True
2015 | #     }
2016 | # )
2017 | # async def get_sayistay_daire_document_markdown(
2018 | #     decision_id: str = Field(..., description="Decision ID from search_sayistay_daire results")
2019 | # ) -> SayistayDocumentMarkdown:
2020 | #     """Get Sayıştay Chamber decision as Markdown."""
2021 | #     raise ValueError("This tool is deactivated. Use get_sayistay_document_unified instead.")
2022 | 
2023 | # --- UNIFIED MCP Tools for Sayıştay (Turkish Court of Accounts) ---
2024 | 
2025 | @app.tool(
2026 |     description="Search Sayıştay decisions unified across all three decision types (Genel Kurul, Temyiz Kurulu, Daire) with comprehensive filtering",
2027 |     annotations={
2028 |         "readOnlyHint": True,
2029 |         "openWorldHint": True,
2030 |         "idempotentHint": True
2031 |     }
2032 | )
2033 | async def search_sayistay_unified(
2034 |     decision_type: Literal["genel_kurul", "temyiz_kurulu", "daire"] = Field(..., description="Decision type: genel_kurul, temyiz_kurulu, or daire"),
2035 |     
2036 |     # Common pagination parameters
2037 |     start: int = Field(0, ge=0, description="Starting record for pagination (0-based)"),
2038 |     length: int = Field(10, ge=1, le=100, description="Number of records per page (1-100)"),
2039 |     
2040 |     # Common search parameters
2041 |     karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY format)"),
2042 |     karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY format)"),
2043 |     kamu_idaresi_turu: Literal["ALL", "Genel Bütçe Kapsamındaki İdareler", "Yüksek Öğretim Kurumları", "Diğer Özel Bütçeli İdareler", "Düzenleyici ve Denetleyici Kurumlar", "Sosyal Güvenlik Kurumları", "Özel İdareler", "Belediyeler ve Bağlı İdareler", "Diğer"] = Field("ALL", description="Public administration type filter"),
2044 |     ilam_no: str = Field("", description="Audit report number (İlam No, max 50 chars)"),
2045 |     web_karar_konusu: Literal["ALL", "Harcırah Mevzuatı", "İhale Mevzuatı", "İş Mevzuatı", "Personel Mevzuatı", "Sorumluluk ve Yargılama Usulleri", "Vergi Resmi Harç ve Diğer Gelirler", "Çeşitli Konular"] = Field("ALL", description="Decision subject category filter"),
2046 |     
2047 |     # Genel Kurul specific parameters (ignored for other types)
2048 |     karar_no: str = Field("", description="Decision number (genel_kurul only)"),
2049 |     karar_ek: str = Field("", description="Decision appendix number (genel_kurul only)"),
2050 |     karar_tamami: str = Field("", description="Full text search (genel_kurul only)"),
2051 |     
2052 |     # Temyiz Kurulu specific parameters (ignored for other types)
2053 |     ilam_dairesi: Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8"] = Field("ALL", description="Audit chamber selection (temyiz_kurulu only)"),
2054 |     yili: str = Field("", description="Year (YYYY format, temyiz_kurulu only)"),
2055 |     dosya_no: str = Field("", description="File number (temyiz_kurulu only)"),
2056 |     temyiz_tutanak_no: str = Field("", description="Appeals board meeting minutes number (temyiz_kurulu only)"),
2057 |     temyiz_karar: str = Field("", description="Appeals decision text search (temyiz_kurulu only)"),
2058 |     
2059 |     # Daire specific parameters (ignored for other types)
2060 |     yargilama_dairesi: Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8"] = Field("ALL", description="Chamber selection (daire only)"),
2061 |     hesap_yili: str = Field("", description="Account year (daire only)"),
2062 |     web_karar_metni: str = Field("", description="Decision text search (daire only)")
2063 | ) -> SayistayUnifiedSearchResult:
2064 |     """Search Sayıştay decisions across all three decision types with unified interface."""
2065 |     logger.info(f"Tool 'search_sayistay_unified' called with decision_type={decision_type}")
2066 |     
2067 |     try:
2068 |         search_request = SayistayUnifiedSearchRequest(
2069 |             decision_type=decision_type,
2070 |             start=start,
2071 |             length=length,
2072 |             karar_tarih_baslangic=karar_tarih_baslangic,
2073 |             karar_tarih_bitis=karar_tarih_bitis,
2074 |             kamu_idaresi_turu=kamu_idaresi_turu,
2075 |             ilam_no=ilam_no,
2076 |             web_karar_konusu=web_karar_konusu,
2077 |             karar_no=karar_no,
2078 |             karar_ek=karar_ek,
2079 |             karar_tamami=karar_tamami,
2080 |             ilam_dairesi=ilam_dairesi,
2081 |             yili=yili,
2082 |             dosya_no=dosya_no,
2083 |             temyiz_tutanak_no=temyiz_tutanak_no,
2084 |             temyiz_karar=temyiz_karar,
2085 |             yargilama_dairesi=yargilama_dairesi,
2086 |             hesap_yili=hesap_yili,
2087 |             web_karar_metni=web_karar_metni
2088 |         )
2089 |         return await sayistay_unified_client_instance.search_unified(search_request)
2090 |     except Exception as e:
2091 |         logger.exception("Error in tool 'search_sayistay_unified'")
2092 |         raise
2093 | 
2094 | @app.tool(
2095 |     description="Get Sayıştay decision document in Markdown format for any decision type",
2096 |     annotations={
2097 |         "readOnlyHint": True,
2098 |         "openWorldHint": False,
2099 |         "idempotentHint": True
2100 |     }
2101 | )
2102 | async def get_sayistay_document_unified(
2103 |     decision_id: str = Field(..., description="Decision ID from search_sayistay_unified results"),
2104 |     decision_type: Literal["genel_kurul", "temyiz_kurulu", "daire"] = Field(..., description="Decision type: genel_kurul, temyiz_kurulu, or daire")
2105 | ) -> SayistayUnifiedDocumentMarkdown:
2106 |     """Get Sayıştay decision document as Markdown for any decision type."""
2107 |     logger.info(f"Tool 'get_sayistay_document_unified' called for ID: {decision_id}, type: {decision_type}")
2108 |     
2109 |     if not decision_id or not decision_id.strip():
2110 |         raise ValueError("Decision ID must be a non-empty string.")
2111 |     
2112 |     try:
2113 |         return await sayistay_unified_client_instance.get_document_unified(decision_id, decision_type)
2114 |     except Exception as e:
2115 |         logger.exception("Error in tool 'get_sayistay_document_unified'")
2116 |         raise
2117 | 
2118 | # --- Application Shutdown Handling ---
2119 | def perform_cleanup():
2120 |     logger.info("MCP Server performing cleanup...")
2121 |     try:
2122 |         loop = asyncio.get_event_loop_policy().get_event_loop()
2123 |         if loop.is_closed(): 
2124 |             loop = asyncio.new_event_loop()
2125 |             asyncio.set_event_loop(loop)
2126 |     except RuntimeError: 
2127 |         loop = asyncio.new_event_loop()
2128 |         asyncio.set_event_loop(loop)
2129 |     clients_to_close = [
2130 |         globals().get('yargitay_client_instance'),
2131 |         globals().get('danistay_client_instance'),
2132 |         globals().get('emsal_client_instance'),
2133 |         globals().get('uyusmazlik_client_instance'),
2134 |         globals().get('anayasa_norm_client_instance'),
2135 |         globals().get('anayasa_bireysel_client_instance'),
2136 |         globals().get('anayasa_unified_client_instance'),
2137 |         globals().get('kik_client_instance'),
2138 |         globals().get('rekabet_client_instance'),
2139 |         globals().get('bedesten_client_instance'),
2140 |         globals().get('sayistay_client_instance'),
2141 |         globals().get('sayistay_unified_client_instance'),
2142 |         globals().get('kvkk_client_instance'),
2143 |         globals().get('bddk_client_instance')
2144 |     ]
2145 |     async def close_all_clients_async():
2146 |         tasks = []
2147 |         for client_instance in clients_to_close:
2148 |             if client_instance and hasattr(client_instance, 'close_client_session') and callable(client_instance.close_client_session):
2149 |                 logger.info(f"Scheduling close for client session: {client_instance.__class__.__name__}")
2150 |                 tasks.append(client_instance.close_client_session())
2151 |         if tasks:
2152 |             results = await asyncio.gather(*tasks, return_exceptions=True)
2153 |             for i, result in enumerate(results):
2154 |                 if isinstance(result, Exception):
2155 |                     client_name = "Unknown Client"
2156 |                     if i < len(clients_to_close) and clients_to_close[i] is not None:
2157 |                         client_name = clients_to_close[i].__class__.__name__
2158 |                     logger.error(f"Error closing client {client_name}: {result}")
2159 |     try:
2160 |         if loop.is_running(): 
2161 |             asyncio.ensure_future(close_all_clients_async(), loop=loop)
2162 |             logger.info("Client cleanup tasks scheduled on running event loop.")
2163 |         else:
2164 |             loop.run_until_complete(close_all_clients_async())
2165 |             logger.info("Client cleanup tasks completed via run_until_complete.")
2166 |     except Exception as e: 
2167 |         logger.error(f"Error during atexit cleanup execution: {e}", exc_info=True)
2168 |     logger.info("MCP Server atexit cleanup process finished.")
2169 | 
2170 | atexit.register(perform_cleanup)
2171 | 
2172 | # --- Health Check Tools ---
2173 | @app.tool(
2174 |     description="Check if Turkish government legal database servers are operational",
2175 |     annotations={
2176 |         "readOnlyHint": True,
2177 |         "idempotentHint": True
2178 |     }
2179 | )
2180 | async def check_government_servers_health() -> Dict[str, Any]:
2181 |     """Check health status of Turkish government legal database servers."""
2182 |     logger.info("Health check tool called for government servers")
2183 |     
2184 |     health_results = {}
2185 |     
2186 |     # Check Yargıtay server
2187 |     try:
2188 |         yargitay_payload = {
2189 |             "data": {
2190 |                 "aranan": "karar",
2191 |                 "arananKelime": "karar", 
2192 |                 "pageSize": 10,
2193 |                 "pageNumber": 1
2194 |             }
2195 |         }
2196 |         
2197 |         async with httpx.AsyncClient(
2198 |             headers={
2199 |                 "Accept": "*/*",
2200 |                 "Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7",
2201 |                 "Connection": "keep-alive",
2202 |                 "Content-Type": "application/json; charset=UTF-8",
2203 |                 "Origin": "https://karararama.yargitay.gov.tr",
2204 |                 "Referer": "https://karararama.yargitay.gov.tr/",
2205 |                 "Sec-Fetch-Dest": "empty",
2206 |                 "Sec-Fetch-Mode": "cors", 
2207 |                 "Sec-Fetch-Site": "same-origin",
2208 |                 "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",
2209 |                 "X-Requested-With": "XMLHttpRequest"
2210 |             },
2211 |             timeout=30.0,
2212 |             verify=False
2213 |         ) as client:
2214 |             response = await client.post(
2215 |                 "https://karararama.yargitay.gov.tr/aramalist",
2216 |                 json=yargitay_payload
2217 |             )
2218 |             
2219 |             if response.status_code == 200:
2220 |                 response_data = response.json()
2221 |                 records_total = response_data.get("data", {}).get("recordsTotal", 0)
2222 |                 
2223 |                 if records_total > 0:
2224 |                     health_results["yargitay"] = {
2225 |                         "status": "healthy",
2226 |                         "response_time_ms": response.elapsed.total_seconds() * 1000
2227 |                     }
2228 |                 else:
2229 |                     health_results["yargitay"] = {
2230 |                         "status": "unhealthy",
2231 |                         "reason": "recordsTotal is 0 or missing",
2232 |                         "response_time_ms": response.elapsed.total_seconds() * 1000
2233 |                     }
2234 |             else:
2235 |                 health_results["yargitay"] = {
2236 |                     "status": "unhealthy", 
2237 |                     "reason": f"HTTP {response.status_code}",
2238 |                     "response_time_ms": response.elapsed.total_seconds() * 1000
2239 |                 }
2240 |                 
2241 |     except Exception as e:
2242 |         health_results["yargitay"] = {
2243 |             "status": "unhealthy",
2244 |             "reason": f"Connection error: {str(e)}"
2245 |         }
2246 |     
2247 |     # Check Bedesten API server
2248 |     try:
2249 |         bedesten_payload = {
2250 |             "data": {
2251 |                 "pageSize": 5,
2252 |                 "pageNumber": 1,
2253 |                 "itemTypeList": ["YARGITAYKARARI"], 
2254 |                 "phrase": "karar",
2255 |                 "sortFields": ["KARAR_TARIHI"],
2256 |                 "sortDirection": "desc"
2257 |             },
2258 |             "applicationName": "UyapMevzuat",
2259 |             "paging": True
2260 |         }
2261 |         
2262 |         async with httpx.AsyncClient(
2263 |             headers={
2264 |                 "Content-Type": "application/json",
2265 |                 "Accept": "application/json",
2266 |                 "User-Agent": "Mozilla/5.0 Health Check"
2267 |             },
2268 |             timeout=30.0,
2269 |             verify=False
2270 |         ) as client:
2271 |             response = await client.post(
2272 |                 "https://bedesten.adalet.gov.tr/emsal-karar/searchDocuments",
2273 |                 json=bedesten_payload
2274 |             )
2275 |             
2276 |             if response.status_code == 200:
2277 |                 response_data = response.json()
2278 |                 logger.debug(f"Bedesten API response: {response_data}")
2279 |                 if response_data and isinstance(response_data, dict):
2280 |                     data_section = response_data.get("data")
2281 |                     if data_section and isinstance(data_section, dict):
2282 |                         total_found = data_section.get("total", 0)
2283 |                     else:
2284 |                         total_found = 0
2285 |                 else:
2286 |                     total_found = 0
2287 |                 
2288 |                 if total_found > 0:
2289 |                     health_results["bedesten"] = {
2290 |                         "status": "healthy", 
2291 |                         "response_time_ms": response.elapsed.total_seconds() * 1000
2292 |                     }
2293 |                 else:
2294 |                     health_results["bedesten"] = {
2295 |                         "status": "unhealthy",
2296 |                         "reason": "total is 0 or missing in data field",
2297 |                         "response_time_ms": response.elapsed.total_seconds() * 1000
2298 |                     }
2299 |             else:
2300 |                 health_results["bedesten"] = {
2301 |                     "status": "unhealthy",
2302 |                     "reason": f"HTTP {response.status_code}",
2303 |                     "response_time_ms": response.elapsed.total_seconds() * 1000
2304 |                 }
2305 |                 
2306 |     except Exception as e:
2307 |         health_results["bedesten"] = {
2308 |             "status": "unhealthy", 
2309 |             "reason": f"Connection error: {str(e)}"
2310 |         }
2311 |     
2312 |     # Overall health assessment
2313 |     healthy_servers = sum(1 for server in health_results.values() if server["status"] == "healthy")
2314 |     total_servers = len(health_results)
2315 |     
2316 |     overall_status = "healthy" if healthy_servers == total_servers else "degraded" if healthy_servers > 0 else "unhealthy"
2317 |     
2318 |     return {
2319 |         "overall_status": overall_status,
2320 |         "healthy_servers": healthy_servers,
2321 |         "total_servers": total_servers,
2322 |         "servers": health_results,
2323 |         "check_timestamp": f"{__import__('datetime').datetime.now().isoformat()}"
2324 |     }
2325 | 
2326 | # --- MCP Tools for KVKK ---
2327 | @app.tool(
2328 |     description="Search KVKK data protection authority decisions",
2329 |     annotations={
2330 |         "readOnlyHint": True,
2331 |         "openWorldHint": True,
2332 |         "idempotentHint": True
2333 |     }
2334 | )
2335 | async def search_kvkk_decisions(
2336 |     keywords: str = Field(..., description="Turkish keywords. Supports +required -excluded \"exact phrase\" operators"),
2337 |     page: int = Field(1, ge=1, le=50, description="Page number for results (1-50)."),
2338 |     # pageSize: int = Field(10, ge=1, le=20, description="Number of results per page (1-20).")
2339 | ) -> KvkkSearchResult:
2340 |     """Search function for legal decisions."""
2341 |     logger.info(f"KVKK search tool called with keywords: {keywords}")
2342 |     
2343 |     pageSize = 10  # Default value
2344 |     
2345 |     search_request = KvkkSearchRequest(
2346 |         keywords=keywords,
2347 |         page=page,
2348 |         pageSize=pageSize
2349 |     )
2350 |     
2351 |     try:
2352 |         result = await kvkk_client_instance.search_decisions(search_request)
2353 |         logger.info(f"KVKK search completed. Found {len(result.decisions)} decisions on page {page}")
2354 |         return result
2355 |     except Exception as e:
2356 |         logger.exception(f"Error in KVKK search: {e}")
2357 |         # Return empty result on error
2358 |         return KvkkSearchResult(
2359 |             decisions=[],
2360 |             total_results=0,
2361 |             page=page,
2362 |             pageSize=pageSize,
2363 |             query=keywords
2364 |         )
2365 | 
2366 | @app.tool(
2367 |     description="Get KVKK decision document in Markdown format with metadata extraction",
2368 |     annotations={
2369 |         "readOnlyHint": True,
2370 |         "openWorldHint": False,
2371 |         "idempotentHint": True
2372 |     }
2373 | )
2374 | async def get_kvkk_document_markdown(
2375 |     decision_url: str = Field(..., description="KVKK decision URL from search results"),
2376 |     page_number: int = Field(1, ge=1, description="Page number for paginated Markdown content (1-indexed, accepts int). Default is 1 (first 5,000 characters).")
2377 | ) -> KvkkDocumentMarkdown:
2378 |     """Get KVKK decision as paginated Markdown."""
2379 |     logger.info(f"KVKK document retrieval tool called for URL: {decision_url}")
2380 |     
2381 |     if not decision_url or not decision_url.strip():
2382 |         return KvkkDocumentMarkdown(
2383 |             source_url=HttpUrl("https://www.kvkk.gov.tr"),
2384 |             title=None,
2385 |             decision_date=None,
2386 |             decision_number=None,
2387 |             subject_summary=None,
2388 |             markdown_chunk=None,
2389 |             current_page=page_number or 1,
2390 |             total_pages=0,
2391 |             is_paginated=False,
2392 |             error_message="Decision URL is required and cannot be empty."
2393 |         )
2394 |     
2395 |     try:
2396 |         # Validate URL format
2397 |         if not decision_url.startswith("https://www.kvkk.gov.tr/"):
2398 |             return KvkkDocumentMarkdown(
2399 |                 source_url=HttpUrl(decision_url),
2400 |                 title=None,
2401 |                 decision_date=None,
2402 |                 decision_number=None,
2403 |                 subject_summary=None,
2404 |                 markdown_chunk=None,
2405 |                 current_page=page_number or 1,
2406 |                 total_pages=0,
2407 |                 is_paginated=False,
2408 |                 error_message="Invalid KVKK decision URL format. URL must start with https://www.kvkk.gov.tr/"
2409 |             )
2410 |         
2411 |         result = await kvkk_client_instance.get_decision_document(decision_url, page_number or 1)
2412 |         logger.info(f"KVKK document retrieved successfully. Page {result.current_page}/{result.total_pages}, Content length: {len(result.markdown_chunk) if result.markdown_chunk else 0}")
2413 |         return result
2414 |         
2415 |     except Exception as e:
2416 |         logger.exception(f"Error retrieving KVKK document: {e}")
2417 |         return KvkkDocumentMarkdown(
2418 |             source_url=HttpUrl(decision_url),
2419 |             title=None,
2420 |             decision_date=None,
2421 |             decision_number=None,
2422 |             subject_summary=None,
2423 |             markdown_chunk=None,
2424 |             current_page=page_number or 1,
2425 |             total_pages=0,
2426 |             is_paginated=False,
2427 |             error_message=f"Error retrieving KVKK document: {str(e)}"
2428 |         )
2429 | 
2430 | # --- MCP Tools for BDDK (Banking Regulation Authority) ---
2431 | @app.tool(
2432 |     description="Search BDDK banking regulation decisions",
2433 |     annotations={
2434 |         "readOnlyHint": True,
2435 |         "openWorldHint": True,
2436 |         "idempotentHint": True
2437 |     }
2438 | )
2439 | async def search_bddk_decisions(
2440 |     keywords: str = Field(..., description="Search keywords in Turkish"),
2441 |     page: int = Field(1, ge=1, description="Page number")
2442 |     # pageSize: int = Field(10, ge=1, le=50, description="Results per page")
2443 | ) -> dict:
2444 |     """Search BDDK banking regulation and supervision decisions."""
2445 |     logger.info(f"BDDK search tool called with keywords: {keywords}, page: {page}")
2446 |     
2447 |     pageSize = 10  # Default value
2448 |     
2449 |     try:
2450 |         search_request = BddkSearchRequest(
2451 |             keywords=keywords,
2452 |             page=page,
2453 |             pageSize=pageSize
2454 |         )
2455 |         
2456 |         result = await bddk_client_instance.search_decisions(search_request)
2457 |         logger.info(f"BDDK search completed. Found {len(result.decisions)} decisions on page {page}")
2458 |         
2459 |         return {
2460 |             "decisions": [
2461 |                 {
2462 |                     "title": dec.title,
2463 |                     "document_id": dec.document_id,
2464 |                     "content": dec.content
2465 |                 }
2466 |                 for dec in result.decisions
2467 |             ],
2468 |             "total_results": result.total_results,
2469 |             "page": result.page,
2470 |             "pageSize": result.pageSize
2471 |         }
2472 |     
2473 |     except Exception as e:
2474 |         logger.exception(f"Error searching BDDK decisions: {e}")
2475 |         return {
2476 |             "decisions": [],
2477 |             "total_results": 0,
2478 |             "page": page,
2479 |             "pageSize": pageSize,
2480 |             "error": str(e)
2481 |         }
2482 | 
2483 | @app.tool(
2484 |     description="Get BDDK decision document as Markdown",
2485 |     annotations={
2486 |         "readOnlyHint": True,
2487 |         "openWorldHint": False,
2488 |         "idempotentHint": True
2489 |     }
2490 | )
2491 | async def get_bddk_document_markdown(
2492 |     document_id: str = Field(..., description="BDDK document ID (e.g., '310')"),
2493 |     page_number: int = Field(1, ge=1, description="Page number")
2494 | ) -> dict:
2495 |     """Retrieve BDDK decision document in Markdown format."""
2496 |     logger.info(f"BDDK document retrieval tool called for ID: {document_id}, page: {page_number}")
2497 |     
2498 |     if not document_id or not document_id.strip():
2499 |         return {
2500 |             "document_id": document_id,
2501 |             "markdown_content": "",
2502 |             "page_number": page_number,
2503 |             "total_pages": 0,
2504 |             "error": "Document ID is required"
2505 |         }
2506 |     
2507 |     try:
2508 |         result = await bddk_client_instance.get_document_markdown(document_id, page_number)
2509 |         logger.info(f"BDDK document retrieved successfully. Page {result.page_number}/{result.total_pages}")
2510 |         
2511 |         return {
2512 |             "document_id": result.document_id,
2513 |             "markdown_content": result.markdown_content,
2514 |             "page_number": result.page_number,
2515 |             "total_pages": result.total_pages
2516 |         }
2517 |         
2518 |     except Exception as e:
2519 |         logger.exception(f"Error retrieving BDDK document: {e}")
2520 |         return {
2521 |             "document_id": document_id,
2522 |             "markdown_content": "",
2523 |             "page_number": page_number,
2524 |             "total_pages": 0,
2525 |             "error": str(e)
2526 |         }
2527 | 
2528 | # --- ChatGPT Deep Research Compatible Tools ---
2529 | 
2530 | def get_preview_text(markdown_content: str, skip_chars: int = 100, preview_chars: int = 200) -> str:
2531 |     """
2532 |     Extract a preview of document text by skipping headers and showing meaningful content.
2533 |     
2534 |     Args:
2535 |         markdown_content: Full document content in markdown format
2536 |         skip_chars: Number of characters to skip from the beginning (default: 100)
2537 |         preview_chars: Number of characters to show in preview (default: 200)
2538 |     
2539 |     Returns:
2540 |         Preview text suitable for ChatGPT Deep Research
2541 |     """
2542 |     if not markdown_content:
2543 |         return ""
2544 |     
2545 |     # Remove common markdown artifacts and clean up
2546 |     cleaned_content = markdown_content.strip()
2547 |     
2548 |     # Skip the first N characters (usually headers, metadata)
2549 |     if len(cleaned_content) > skip_chars:
2550 |         content_start = cleaned_content[skip_chars:]
2551 |     else:
2552 |         content_start = cleaned_content
2553 |     
2554 |     # Get the next N characters for preview
2555 |     if len(content_start) > preview_chars:
2556 |         preview = content_start[:preview_chars]
2557 |     else:
2558 |         preview = content_start
2559 |     
2560 |     # Clean up the preview - remove incomplete sentences at the end
2561 |     preview = preview.strip()
2562 |     
2563 |     # If preview ends mid-sentence, try to end at last complete sentence
2564 |     if preview and not preview.endswith('.'):
2565 |         last_period = preview.rfind('.')
2566 |         if last_period > 50:  # Only if there's a reasonable sentence
2567 |             preview = preview[:last_period + 1]
2568 |     
2569 |     # Add ellipsis if content was truncated
2570 |     if len(content_start) > preview_chars:
2571 |         preview += "..."
2572 |     
2573 |     return preview.strip()
2574 | 
2575 | @app.tool(
2576 |     description="DO NOT USE unless you are ChatGPT Deep Research. Search Turkish courts (Turkish keywords only). Supports: +term (must have), -term (exclude), \"exact phrase\", term1 OR term2",
2577 |     annotations={
2578 |         "readOnlyHint": True,
2579 |         "openWorldHint": True,
2580 |         "idempotentHint": True
2581 |     }
2582 | )
2583 | async def search(
2584 |     query: str = Field(..., description="Turkish search query")
2585 | ) -> Dict[str, List[Dict[str, str]]]:
2586 |     """
2587 |     Bedesten API search tool for ChatGPT Deep Research compatibility.
2588 |     
2589 |     This tool searches Turkish legal databases via the unified Bedesten API.
2590 |     It supports advanced search operators and covers all major court types.
2591 |     
2592 |     USAGE RESTRICTION: Only for ChatGPT Deep Research workflows.
2593 |     For regular legal research, use search_bedesten_unified with specific court types.
2594 |     
2595 |     Returns:
2596 |     Object with "results" field containing a list of documents with id, title, text preview, and url
2597 |     as required by ChatGPT Deep Research specification.
2598 |     """
2599 |     logger.info(f"ChatGPT Deep Research search tool called with query: {query}")
2600 |     
2601 |     results = []
2602 |     
2603 |     try:
2604 |         # Search all court types via unified Bedesten API
2605 |         court_types = [
2606 |             ("YARGITAYKARARI", "Yargıtay", "yargitay_bedesten"),
2607 |             ("DANISTAYKARAR", "Danıştay", "danistay_bedesten"), 
2608 |             ("YERELHUKUK", "Yerel Hukuk Mahkemesi", "yerel_hukuk_bedesten"),
2609 |             ("ISTINAFHUKUK", "İstinaf Hukuk Mahkemesi", "istinaf_hukuk_bedesten"),
2610 |             ("KYB", "Kanun Yararına Bozma", "kyb_bedesten")
2611 |         ]
2612 |         
2613 |         for item_type, court_name, id_prefix in court_types:
2614 |             try:
2615 |                 search_results = await bedesten_client_instance.search_documents(
2616 |                     BedestenSearchRequest(
2617 |                         data=BedestenSearchData(
2618 |                             phrase=query,  # Use query as-is to support both regular and exact phrase searches
2619 |                             itemTypeList=[item_type],
2620 |                             pageSize=10,
2621 |                             pageNumber=1
2622 |                         )
2623 |                     )
2624 |                 )
2625 |                 
2626 |                 # Handle potential None data
2627 |                 if search_results.data is None:
2628 |                     logger.warning(f"No data returned from Bedesten API for {court_name}")
2629 |                     continue
2630 |                 
2631 |                 # Add results from this court type (limit to top 5 per court)
2632 |                 for decision in search_results.data.emsalKararList[:5]:
2633 |                     # For ChatGPT Deep Research, fetch document content for preview
2634 |                     try:
2635 |                         # Fetch document content for preview
2636 |                         doc = await bedesten_client_instance.get_document_as_markdown(decision.documentId)
2637 |                         
2638 |                         # Generate preview text (skip first 100 chars, show next 200)
2639 |                         preview_text = get_preview_text(doc.markdown_content, skip_chars=100, preview_chars=200)
2640 |                         
2641 |                         # Build title from metadata
2642 |                         title_parts = []
2643 |                         if decision.birimAdi:
2644 |                             title_parts.append(decision.birimAdi)
2645 |                         if decision.esasNo:
2646 |                             title_parts.append(f"Esas: {decision.esasNo}")
2647 |                         if decision.kararNo:
2648 |                             title_parts.append(f"Karar: {decision.kararNo}")
2649 |                         if decision.kararTarihiStr:
2650 |                             title_parts.append(f"Tarih: {decision.kararTarihiStr}")
2651 |                         
2652 |                         if title_parts:
2653 |                             title = " - ".join(title_parts)
2654 |                         else:
2655 |                             title = f"{court_name} - Document {decision.documentId}"
2656 |                         
2657 |                         # Add to results in OpenAI format
2658 |                         results.append({
2659 |                             "id": decision.documentId,
2660 |                             "title": title,
2661 |                             "text": preview_text,
2662 |                             "url": f"https://mevzuat.adalet.gov.tr/ictihat/{decision.documentId}"
2663 |                         })
2664 |                         
2665 |                     except Exception as e:
2666 |                         logger.warning(f"Could not fetch preview for document {decision.documentId}: {e}")
2667 |                         # Add minimal result without preview
2668 |                         results.append({
2669 |                             "id": decision.documentId,
2670 |                             "title": f"{court_name} - Document {decision.documentId}",
2671 |                             "text": "Document preview not available",
2672 |                             "url": f"https://mevzuat.adalet.gov.tr/ictihat/{decision.documentId}"
2673 |                         })
2674 |                     
2675 |                 if search_results.data:
2676 |                     logger.info(f"Found {len(search_results.data.emsalKararList)} results from {court_name}")
2677 |                 else:
2678 |                     logger.info(f"Found 0 results from {court_name} (no data returned)")
2679 |                 
2680 |             except Exception as e:
2681 |                 logger.warning(f"Bedesten API search error for {court_name}: {e}")
2682 |         
2683 |         # Comment out other API implementations for ChatGPT Deep Research
2684 |         """
2685 |         # Other API implementations disabled for ChatGPT Deep Research
2686 |         # These are available through specific court tools:
2687 |         
2688 |         # Yargıtay Official API - use search_yargitay_detailed instead
2689 |         # Danıştay Official API - use search_danistay_by_keyword instead  
2690 |         # Constitutional Court - use search_anayasa_norm_denetimi_decisions instead
2691 |         # Competition Authority - use search_rekabet_kurumu_decisions instead
2692 |         # Public Procurement Authority - use search_kik_decisions instead
2693 |         # Court of Accounts - use search_sayistay_* tools instead
2694 |         # UYAP Emsal - use search_emsal_detailed_decisions instead
2695 |         # Jurisdictional Disputes Court - use search_uyusmazlik_decisions instead
2696 |         """
2697 |         
2698 |         logger.info(f"ChatGPT Deep Research search completed. Found {len(results)} results via Bedesten API.")
2699 |         return {"results": results}
2700 |         
2701 |     except Exception as e:
2702 |         logger.exception("Error in ChatGPT Deep Research search tool")
2703 |         # Return partial results if any were found
2704 |         if results:
2705 |             return {"results": results}
2706 |         raise
2707 | 
2708 | @app.tool(
2709 |     description="DO NOT USE unless you are ChatGPT Deep Research. Fetch document by ID. See docs for details",
2710 |     annotations={
2711 |         "readOnlyHint": True,
2712 |         "openWorldHint": False,  # Retrieves specific documents, not exploring
2713 |         "idempotentHint": True
2714 |     }
2715 | )
2716 | async def fetch(
2717 |     id: str = Field(..., description="Document identifier from search results (numeric only)")
2718 | ) -> Dict[str, Any]:
2719 |     """
2720 |     Bedesten API fetch tool for ChatGPT Deep Research compatibility.
2721 |     
2722 |     Retrieves the full text content of Turkish legal documents via unified Bedesten API.
2723 |     Converts documents from HTML/PDF to clean Markdown format.
2724 |     
2725 |     USAGE RESTRICTION: Only for ChatGPT Deep Research workflows.
2726 |     For regular legal research, use specific court document tools.
2727 |     
2728 |     Input Format:
2729 |     - id: Numeric document identifier from search results (e.g., "730113500", "71370900")
2730 |     
2731 |     Returns:
2732 |     Single object with numeric id, title, text (full Markdown content), mevzuat.adalet.gov.tr url, and metadata fields
2733 |     as required by ChatGPT Deep Research specification.
2734 |     """
2735 |     logger.info(f"ChatGPT Deep Research fetch tool called for document ID: {id}")
2736 |     
2737 |     if not id or not id.strip():
2738 |         raise ValueError("Document ID must be a non-empty string")
2739 |     
2740 |     try:
2741 |         # Use the numeric ID directly with Bedesten API
2742 |         doc = await bedesten_client_instance.get_document_as_markdown(id)
2743 |         
2744 |         # Try to get additional metadata by searching for this specific document
2745 |         title = f"Turkish Legal Document {id}"
2746 |         try:
2747 |             # Quick search to get metadata for better title
2748 |             search_results = await bedesten_client_instance.search_documents(
2749 |                 BedestenSearchRequest(
2750 |                     data=BedestenSearchData(
2751 |                         phrase=id,  # Search by document ID
2752 |                         pageSize=1,
2753 |                         pageNumber=1
2754 |                     )
2755 |                 )
2756 |             )
2757 |             
2758 |             if search_results.data and search_results.data.emsalKararList:
2759 |                 decision = search_results.data.emsalKararList[0]
2760 |                 if decision.documentId == id:
2761 |                     # Build a proper title from metadata
2762 |                     title_parts = []
2763 |                     if decision.birimAdi:
2764 |                         title_parts.append(decision.birimAdi)
2765 |                     if decision.esasNo:
2766 |                         title_parts.append(f"Esas: {decision.esasNo}")
2767 |                     if decision.kararNo:
2768 |                         title_parts.append(f"Karar: {decision.kararNo}")
2769 |                     if decision.kararTarihiStr:
2770 |                         title_parts.append(f"Tarih: {decision.kararTarihiStr}")
2771 |                     
2772 |                     if title_parts:
2773 |                         title = " - ".join(title_parts)
2774 |                     else:
2775 |                         title = f"Turkish Legal Decision {id}"
2776 |         except Exception as e:
2777 |             logger.warning(f"Could not fetch metadata for document {id}: {e}")
2778 |         
2779 |         return {
2780 |             "id": id,
2781 |             "title": title,
2782 |             "text": doc.markdown_content,
2783 |             "url": f"https://mevzuat.adalet.gov.tr/ictihat/{id}",
2784 |             "metadata": {
2785 |                 "database": "Turkish Legal Database via Bedesten API",
2786 |                 "document_id": id,
2787 |                 "source_url": doc.source_url,
2788 |                 "mime_type": doc.mime_type,
2789 |                 "api_source": "Bedesten Unified API",
2790 |                 "chatgpt_deep_research": True
2791 |             }
2792 |         }
2793 |         
2794 |         # Comment out other API implementations for ChatGPT Deep Research
2795 |         """
2796 |         # Other API implementations disabled for ChatGPT Deep Research
2797 |         # These are available through specific court document tools:
2798 |         
2799 |         elif id.startswith("yargitay_"):
2800 |             # Yargıtay Official API - use get_yargitay_document_markdown instead
2801 |             doc_id = id.replace("yargitay_", "")
2802 |             doc = await yargitay_client_instance.get_decision_document_as_markdown(doc_id)
2803 |             
2804 |         elif id.startswith("danistay_"):
2805 |             # Danıştay Official API - use get_danistay_document_markdown instead
2806 |             doc_id = id.replace("danistay_", "")
2807 |             doc = await danistay_client_instance.get_decision_document_as_markdown(doc_id)
2808 |             
2809 |         elif id.startswith("anayasa_"):
2810 |             # Constitutional Court - use get_anayasa_norm_denetimi_document_markdown instead
2811 |             doc_id = id.replace("anayasa_", "")
2812 |             doc = await anayasa_norm_client_instance.get_decision_document_as_markdown(...)
2813 |             
2814 |         elif id.startswith("rekabet_"):
2815 |             # Competition Authority - use get_rekabet_kurumu_document instead
2816 |             doc_id = id.replace("rekabet_", "")
2817 |             doc = await rekabet_client_instance.get_decision_document(...)
2818 |             
2819 |         elif id.startswith("kik_"):
2820 |             # Public Procurement Authority - use get_kik_decision_document_as_markdown instead
2821 |             doc_id = id.replace("kik_", "")
2822 |             doc = await kik_client_instance.get_decision_document_as_markdown(doc_id)
2823 |             
2824 |         elif id.startswith("local_"):
2825 |             # This was already using Bedesten API, but deprecated for ChatGPT Deep Research
2826 |             doc_id = id.replace("local_", "")
2827 |             doc = await bedesten_client_instance.get_document_as_markdown(doc_id)
2828 |         """
2829 |         
2830 |     except Exception as e:
2831 |         logger.exception(f"Error fetching ChatGPT Deep Research document {id}")
2832 |         raise
2833 | 
2834 | # --- Token Metrics Tool Removed for Optimization ---
2835 | 
2836 | def ensure_playwright_browsers():
2837 |     """Ensure Playwright browsers are installed for KIK tool functionality."""
2838 |     try:
2839 |         import subprocess
2840 |         import os
2841 |         
2842 |         # Check if chromium is already installed
2843 |         chromium_path = os.path.expanduser("~/Library/Caches/ms-playwright/chromium-1179")
2844 |         if os.path.exists(chromium_path):
2845 |             logger.info("Playwright Chromium browser already installed.")
2846 |             return
2847 |         
2848 |         logger.info("Installing Playwright Chromium browser for KIK tool...")
2849 |         result = subprocess.run(
2850 |             ["python", "-m", "playwright", "install", "chromium"],
2851 |             capture_output=True,
2852 |             text=True,
2853 |             timeout=300  # 5 minutes timeout
2854 |         )
2855 |         
2856 |         if result.returncode == 0:
2857 |             logger.info("Playwright Chromium browser installed successfully.")
2858 |         else:
2859 |             logger.warning(f"Failed to install Playwright browser: {result.stderr}")
2860 |             logger.warning("KIK tool may not work properly without Playwright browsers.")
2861 |             
2862 |     except Exception as e:
2863 |         logger.warning(f"Could not auto-install Playwright browsers: {e}")
2864 |         logger.warning("KIK tool may not work properly. Manual installation: 'playwright install chromium'")
2865 | 
2866 | def main():
2867 |     logger.info(f"Starting {app.name} server via main() function...")
2868 |     logger.info(f"Logs will be written to: {LOG_FILE_PATH}")
2869 |     
2870 |     # Ensure Playwright browsers are installed
2871 |     ensure_playwright_browsers()
2872 |     
2873 |     try:
2874 |         app.run()
2875 |     except KeyboardInterrupt: 
2876 |         logger.info("Server shut down by user (KeyboardInterrupt).")
2877 |     except Exception as e: 
2878 |         logger.exception("Server failed to start or crashed.")
2879 |     finally:
2880 |         logger.info(f"{app.name} server has shut down.")
2881 | 
2882 | if __name__ == "__main__": 
2883 |     main()
```
Page 11/11FirstPrevNextLast