#
tokens: 41555/50000 2/145 files (page 7/8)
lines: off (toggle) GitHub
raw markdown copy
This is page 7 of 8. Use http://codebase.md/saidsurucu/yargi-mcp?lines=false&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/example_fastapi_app.py:
--------------------------------------------------------------------------------

```python
"""
FastAPI Comprehensive Endpoints with Complete MCP Documentation
This is the complete version with all descriptions and docstrings from MCP server.
"""

import os
from typing import List, Dict, Any, Optional
from datetime import datetime

from fastapi import FastAPI, HTTPException, Query, Depends, Body
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
import json

# Import the main MCP app
from mcp_server_main import app as mcp_server

# Create MCP ASGI app
mcp_asgi_app = mcp_server.http_app(path="/mcp")

# Create FastAPI app with MCP lifespan
app = FastAPI(
    title="Yargı MCP API - Turkish Legal Database REST API",
    description="""
    Comprehensive REST API for Turkish Legal Databases with complete MCP tool coverage.
    
    This API provides access to 8 major Turkish legal institutions including:
    • Yargıtay (Court of Cassation) - Supreme civil/criminal court
    • Danıştay (Council of State) - Supreme administrative court  
    • Constitutional Court - Constitutional review and individual applications
    • Competition Authority - Antitrust and merger decisions
    • Public Procurement Authority - Government contracting disputes
    • Court of Accounts - Public audit and accountability
    • Emsal (UYAP Precedents) - Cross-court precedent database
    • Local and Appellate Courts - First and second instance decisions
    
    Features complete coverage of 33 MCP tools with enhanced documentation,
    typed request models, and comprehensive legal context.
    
    Tool Properties (from MCP annotations):
    • Read-only: All tools are read-only and do not modify system state
    • Idempotent: Same inputs produce same outputs for reliable research
    • Open-world search: Search tools explore comprehensive legal databases
    • Deterministic document retrieval: Document tools return consistent content
    """,
    version="1.0.0",
    lifespan=mcp_asgi_app.lifespan
)

# Add CORS middleware
cors_origins = os.getenv("ALLOWED_ORIGINS", "*").split(",")
app.add_middleware(
    CORSMiddleware,
    allow_origins=cors_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Mount MCP server
app.mount("/mcp-server", mcp_asgi_app)

# Response models (keeping from original)
class ToolInfo(BaseModel):
    name: str
    description: str
    parameters: Dict[str, Any]

class ServerInfo(BaseModel):
    name: str
    version: str
    description: str
    tools_count: int
    databases: List[str]
    mcp_endpoint: str
    api_docs: str

class HealthCheck(BaseModel):
    status: str
    timestamp: datetime
    uptime_seconds: Optional[float] = None
    tools_operational: bool

# Track server start time
SERVER_START_TIME = datetime.now()

# MCP tool caller helper
async def call_mcp_tool(tool_name: str, arguments: Dict[str, Any]):
    """Call an MCP tool with given arguments"""
    try:
        tool = mcp_server._tool_manager._tools.get(tool_name)
        if not tool:
            raise HTTPException(status_code=404, detail=f"Tool '{tool_name}' not found")
        result = await tool.fn(**arguments)
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Tool execution failed: {str(e)}")

# ============================================================================
# COMPREHENSIVE REQUEST MODELS WITH FULL MCP DOCUMENTATION
# ============================================================================

class YargitaySearchRequest(BaseModel):
    """
    Search request for Court of Cassation (Yargıtay) decisions using primary official API.
    
    The Court of Cassation is Turkey's highest court for civil and criminal matters,
    equivalent to a Supreme Court. Provides access to comprehensive supreme court precedents.
    """
    arananKelime: str = Field(
        ..., 
        description="""Keyword to search for with advanced operators:
        • Space between words = OR logic (arsa payı → "arsa" OR "payı")
        • "exact phrase" = Exact match ("arsa payı" → exact phrase)
        • word1+word2 = AND logic (arsa+payı → both words required)
        • word* = Wildcard (bozma* → bozma, bozması, bozmanın, etc.)
        • +"phrase1" +"phrase2" = Multiple required phrases
        • +"required" -"excluded" = Include and exclude
        
        Turkish Examples:
        • Simple OR: arsa payı (~523K results)
        • Exact phrase: "arsa payı" (~22K results)
        • Multiple AND: +"arsa payı" +"bozma sebebi" (~234 results)
        • Wildcard: bozma* (bozma, bozması, bozmanın, etc.)
        • Exclude: +"arsa payı" -"kira sözleşmesi"
        """,
        example='+"mülkiyet hakkı" +"iptal"'
    )
    birimYrgKurulDaire: Optional[str] = Field(
        "", 
        description="""Chamber/board selection (52 options):
        Civil Chambers: 1-23. Hukuk Dairesi
        Criminal Chambers: 1-23. Ceza Dairesi
        General Assemblies: Hukuk Genel Kurulu, Ceza Genel Kurulu
        Special Boards: Hukuk/Ceza Daireleri Başkanlar Kurulu, Büyük Genel Kurulu
        
        Use "" for ALL chambers or specify exact chamber name.
        """,
        example="1. Hukuk Dairesi"
    )
    baslangicTarihi: Optional[str] = Field(None, description="Start date (DD.MM.YYYY)", example="01.01.2020")
    bitisTarihi: Optional[str] = Field(None, description="End date (DD.MM.YYYY)", example="31.12.2024")
    pageSize: int = Field(20, description="Results per page (1-100)", ge=1, le=100, example=20)

class YargitayBedestenSearchRequest(BaseModel):
    """
    Search request for Court of Cassation using Bedesten API (alternative source).
    Complements primary API with different search capabilities and recent decisions.
    """
    phrase: str = Field(
        ..., 
        description="""Aranacak kavram/kelime. İki farklı arama türü desteklenir:
        • Normal arama: "mülkiyet hakkı" - kelimeler ayrı ayrı aranır
        • Tam cümle arama: "\"mülkiyet hakkı\"" - tırnak içindeki ifade aynen aranır
        Tam cümle aramalar daha kesin sonuçlar verir.
        
        Search phrase with exact matching support:
        • Regular search: "mülkiyet hakkı" - searches individual words separately
        • Exact phrase search: "\"mülkiyet hakkı\"" - searches for exact phrase as unit
        Exact phrase search provides more precise results with fewer false positives.
        """,
        example="\"mülkiyet hakkı\""
    )
    birimAdi: Optional[str] = Field(
        None, 
        description="""Daire/Kurul seçimi (52 seçenek - ana API ile aynı):
        • Hukuk daireleri: 1. Hukuk Dairesi - 23. Hukuk Dairesi
        • Ceza daireleri: 1. Ceza Dairesi - 23. Ceza Dairesi
        • Genel kurullar: Hukuk Genel Kurulu, Ceza Genel Kurulu
        • Özel kurullar: Hukuk/Ceza Daireleri Başkanlar Kurulu, Büyük Genel Kurulu
        
        Chamber filtering (52 options - same as primary API):
        • Civil chambers: 1. Hukuk Dairesi through 23. Hukuk Dairesi
        • Criminal chambers: 1. Ceza Dairesi through 23. Ceza Dairesi
        • General assemblies: Hukuk Genel Kurulu, Ceza Genel Kurulu
        • Special boards: Hukuk/Ceza Daireleri Başkanlar Kurulu, Büyük Genel Kurulu
        
        Use None for ALL chambers, or specify exact chamber name.
        """,
        example="1. Hukuk Dairesi"
    )
    kararTarihiStart: Optional[str] = Field(
        None, 
        description="""Karar başlangıç tarihi (ISO 8601 formatı):
        Format: YYYY-MM-DDTHH:MM:SS.000Z
        Örnek: "2024-01-01T00:00:00.000Z" - 1 Ocak 2024'ten itibaren kararlar
        kararTarihiEnd ile birlikte tarih aralığı filtrelemesi için kullanılır.
        
        Decision start date filter (ISO 8601 format):
        Format: YYYY-MM-DDTHH:MM:SS.000Z
        Example: "2024-01-01T00:00:00.000Z" for decisions from Jan 1, 2024
        Use with kararTarihiEnd for date range filtering.
        """,
        example="2024-01-01T00:00:00.000Z"
    )
    kararTarihiEnd: Optional[str] = Field(
        None, 
        description="""Karar bitiş tarihi (ISO 8601 formatı):
        Format: YYYY-MM-DDTHH:MM:SS.000Z
        Örnek: "2024-12-31T23:59:59.999Z" - 31 Aralık 2024'e kadar kararlar
        kararTarihiStart ile birlikte tarih aralığı filtrelemesi için kullanılır.
        
        Decision end date filter (ISO 8601 format):
        Format: YYYY-MM-DDTHH:MM:SS.000Z
        Example: "2024-12-31T23:59:59.999Z" for decisions until Dec 31, 2024
        Use with kararTarihiStart for date range filtering.
        """,
        example="2024-12-31T23:59:59.999Z"
    )
    pageSize: int = Field(20, description="Results per page (1-100)", ge=1, le=100)

class DanistayKeywordSearchRequest(BaseModel):
    """
    Keyword-based search for Council of State (Danıştay) decisions with Boolean logic.
    
    The Council of State is Turkey's highest administrative court, providing final
    rulings on administrative law matters with Boolean keyword operators.
    """
    andKelimeler: List[str] = Field(
        ..., 
        description="ALL keywords must be present (AND logic)",
        example=["idari işlem", "iptal"]
    )
    orKelimeler: Optional[List[str]] = Field(
        None, 
        description="ANY keyword can be present (OR logic)",
        example=["ruhsat", "izin", "lisans"]
    )
    notKelimeler: Optional[List[str]] = Field(
        None, 
        description="EXCLUDE if keywords present (NOT logic)",
        example=["vergi"]
    )
    pageSize: int = Field(20, description="Results per page (1-100)", ge=1, le=100)

class DanistayDetailedSearchRequest(BaseModel):
    """
    Detailed search for Council of State decisions with comprehensive filtering.
    Provides the most comprehensive search capabilities for administrative court decisions.
    """
    daire: Optional[str] = Field(
        None, 
        description="Chamber/Department filter (1. Daire through 17. Daire, special councils)",
        example="3. Daire"
    )
    baslangicTarihi: Optional[str] = Field(None, description="Start date (DD.MM.YYYY)", example="01.01.2020")
    bitisTarihi: Optional[str] = Field(None, description="End date (DD.MM.YYYY)", example="31.12.2024")
    esas: Optional[str] = Field(None, description="Case number (Esas No)", example="2024/123")
    karar: Optional[str] = Field(None, description="Decision number (Karar No)", example="2024/456")

class DanistayBedestenSearchRequest(BaseModel):
    """
    Council of State search using Bedesten API with chamber filtering and exact phrase search.
    Provides access to administrative court decisions with 27 chamber options.
    """
    phrase: str = Field(..., description="Search phrase (supports exact matching with quotes)")
    birimAdi: Optional[str] = Field(
        None, 
        description="""Chamber filtering (27 options):
        Main Councils: Büyük Gen.Kur., İdare Dava Daireleri Kurulu, Vergi Dava Daireleri Kurulu
        Chambers: 1. Daire through 17. Daire
        Military: Askeri Yüksek İdare Mahkemesi chambers
        """,
        example="3. Daire"
    )
    kararTarihiStart: Optional[str] = Field(None, description="Start date (ISO 8601)")
    kararTarihiEnd: Optional[str] = Field(None, description="End date (ISO 8601)")
    pageSize: int = Field(20, description="Results per page", ge=1, le=100)

class EmsalSearchRequest(BaseModel):
    """
    Search Precedent (Emsal) decisions from UYAP system across multiple court levels.
    Provides access to precedent decisions from various Turkish courts.
    """
    keyword: str = Field(..., description="Search keyword across decision texts")
    decision_year_karar: Optional[str] = Field(None, description="Decision year filter", example="2024")
    results_per_page: int = Field(20, description="Results per page", ge=1, le=100)

class UyusmazlikSearchRequest(BaseModel):
    """
    Search Court of Jurisdictional Disputes decisions.
    Resolves jurisdictional disputes between different court systems.
    """
    keywords: List[str] = Field(..., description="Search keywords", example=["görev", "uyuşmazlık"])
    page_to_fetch: int = Field(1, description="Page number", ge=1)

class AnayasaNormSearchRequest(BaseModel):
    """
    Search Constitutional Court norm control (judicial review) decisions.
    Turkey's highest constitutional authority for reviewing law constitutionality.
    """
    keywords_all: List[str] = Field(..., description="All required keywords", example=["eğitim hakkı", "anayasa"])
    period: Optional[str] = Field(None, description="Constitutional period (1=1961, 2=1982)", example="2")
    application_type: Optional[str] = Field(None, description="Application type (1=İptal)", example="1")
    results_per_page: int = Field(20, description="Results per page", ge=1, le=100)

class AnayasaBireyselSearchRequest(BaseModel):
    """
    Search Constitutional Court individual application decisions.
    Human rights violation cases through individual citizen petitions.
    """
    keywords: List[str] = Field(..., description="Search keywords", example=["ifade özgürlüğü", "basın"])
    page_to_fetch: int = Field(1, description="Page number", ge=1)

class KikSearchRequest(BaseModel):
    """
    Search Public Procurement Authority (KİK) decisions.
    Government procurement disputes and regulatory interpretations.
    """
    karar_tipi: Optional[str] = Field(
        None, 
        description="Decision type (rbUyusmazlik=Disputes, rbDuzenleyici=Regulatory, rbMahkeme=Court)",
        example="rbUyusmazlik"
    )
    karar_metni: Optional[str] = Field(None, description="Decision text search", example="ihale iptali")
    basvuru_konusu_ihale: Optional[str] = Field(None, description="Tender subject", example="danışmanlık")
    karar_tarihi_baslangic: Optional[str] = Field(None, description="Start date", example="01.01.2023")

class RekabetSearchRequest(BaseModel):
    """
    Search Competition Authority decisions.
    Antitrust, merger control, and competition law enforcement.
    """
    KararTuru: Optional[str] = Field(
        None, 
        description="Decision type (Birleşme ve Devralma, Rekabet İhlali, Muafiyet, etc.)",
        example="Birleşme ve Devralma"
    )
    PdfText: Optional[str] = Field(
        None, 
        description="Full-text search in decisions. Use quotes for exact phrases.",
        example="\"market definition\" telecommunications"
    )
    YayinlanmaTarihi: Optional[str] = Field(None, description="Publication date", example="01.01.2020")
    page: int = Field(1, description="Page number", ge=1)

class BedestenSearchRequest(BaseModel):
    """
    Generic search request for Bedesten API courts (Yerel Hukuk, İstinaf Hukuk, KYB).
    Supports exact phrase search and date filtering.
    """
    phrase: str = Field(
        ..., 
        description="Search phrase. Use quotes for exact matching: \"legal term\"",
        example="\"sözleşme ihlali\""
    )
    kararTarihiStart: Optional[str] = Field(None, description="Start date (ISO 8601)")
    kararTarihiEnd: Optional[str] = Field(None, description="End date (ISO 8601)")
    pageSize: int = Field(20, description="Results per page", ge=1, le=100)

class SayistaySearchRequest(BaseModel):
    """
    Search Court of Accounts (Sayıştay) decisions.
    Public audit, accountability, and financial oversight decisions.
    """
    keywords: List[str] = Field(..., description="Search keywords", example=["mali sorumluluk", "denetim"])
    page_to_fetch: int = Field(1, description="Page number", ge=1)

# ============================================================================
# BASIC SERVER ENDPOINTS (keeping from original)
# ============================================================================

@app.get("/", response_model=ServerInfo)
async def root():
    """Get comprehensive server information with database coverage"""
    return ServerInfo(
        name="Yargı MCP Server - Turkish Legal Database API",
        version="1.0.0", 
        description="Complete REST API for Turkish legal databases with 33 MCP tools",
        tools_count=len(mcp_server._tool_manager._tools),
        databases=[
            "Yargıtay (Court of Cassation) - 4 tools",
            "Danıştay (Council of State) - 5 tools", 
            "Emsal (UYAP Precedents) - 2 tools",
            "Uyuşmazlık Mahkemesi (Jurisdictional Disputes) - 2 tools",
            "Anayasa Mahkemesi (Constitutional Court) - 4 tools",
            "Kamu İhale Kurulu (Public Procurement) - 2 tools",
            "Rekabet Kurumu (Competition Authority) - 2 tools",
            "Sayıştay (Court of Accounts) - 6 tools",
            "Bedesten API Courts (Local/Appellate/KYB) - 6 tools"
        ],
        mcp_endpoint="/mcp-server/mcp/",
        api_docs="/docs"
    )

@app.get("/health", response_model=HealthCheck)
async def health_check():
    """Health check with comprehensive system status"""
    uptime = (datetime.now() - SERVER_START_TIME).total_seconds()
    return HealthCheck(
        status="healthy",
        timestamp=datetime.now(),
        uptime_seconds=uptime,
        tools_operational=len(mcp_server._tool_manager._tools) == 33
    )

@app.get("/api/tools", response_model=List[ToolInfo])
async def list_tools(
    search: Optional[str] = Query(None, description="Search tools by name or description"),
    database: Optional[str] = Query(None, description="Filter by database name")
):
    """List all 33 MCP tools with filtering capabilities"""
    tools = []
    for tool in mcp_server._tool_manager._tools.values():
        if search and search.lower() not in tool.name.lower() and search.lower() not in tool.description.lower():
            continue
        if database:
            db_lower = database.lower()
            if db_lower not in tool.name.lower() and db_lower not in tool.description.lower():
                continue
        
        params = {}
        if hasattr(tool, 'schema') and tool.schema:
            if hasattr(tool.schema, 'parameters'):
                params = tool.schema.parameters
            elif hasattr(tool.schema, '__annotations__'):
                params = {k: str(v) for k, v in tool.schema.__annotations__.items()}
        
        tools.append(ToolInfo(
            name=tool.name,
            description=tool.description,
            parameters=params
        ))
    return tools

# ============================================================================
# YARGITAY (COURT OF CASSATION) ENDPOINTS - 4 TOOLS
# ============================================================================

@app.post(
    "/api/yargitay/search", 
    tags=["Yargıtay"],
    summary="Search Court of Cassation (Primary API)",
    description="""Search Turkey's Supreme Court for civil and criminal precedents using advanced operators.

Key Features:
• Advanced search: AND (+), OR (space), NOT (-), wildcards (*), exact phrases ("")
• 52 chamber options (23 Civil + 23 Criminal + General Assemblies)
• Date range filtering • Case/decision number filtering • Pagination

Search Examples:
• OR search: property share (finds ANY words)
• Exact phrase: "property share" (finds exact phrase)
• AND required: +"property share" +"annulment reason"
• Wildcard: construct* (construction, constructive, etc.)
• Exclude terms: +"property share" -"construction contract"

Use for supreme court precedent research and legal principle analysis."""
)
async def search_yargitay(request: YargitaySearchRequest):
    """
    Searches Court of Cassation (Yargıtay) decisions using the primary official API.

    The Court of Cassation (Yargıtay) is Turkey's highest court for civil and criminal matters,
    equivalent to a Supreme Court. This tool provides access to the most comprehensive database
    of supreme court precedents with advanced search capabilities and filtering options.

    Key Features:
    • Advanced search operators (AND, OR, wildcards, exclusions)
    • Chamber filtering: 52 options (23 Civil (Hukuk) + 23 Criminal (Ceza) + General Assemblies (Genel Kurullar))
    • Date range filtering with DD.MM.YYYY format
    • Case number filtering (Case No (Esas No) and Decision No (Karar No))
    • Pagination support (1-100 results per page)
    • Multiple sorting options (by case number, decision number, date)

    SEARCH SYNTAX GUIDE:
    • Words with spaces: OR search ("property share" finds ANY of the words)
    • "Quotes": Exact phrase search ("property share" finds exact phrase)
    • Plus sign (+): AND search (property+share requires both words)
    • Asterisk (*): Wildcard (construct* matches variations)
    • Minus sign (-): Exclude terms (avoid unwanted results)

    Common Search Patterns:
    • Simple OR: property share (finds ~523K results)
    • Exact phrase: "property share" (finds ~22K results)
    • Multiple required: +"property share" +"annulment reason (bozma sebebi)" (finds ~234 results)
    • Wildcard expansion: construct* (matches construction, constructive, etc.)
    • Exclude unwanted: +"property share" -"construction contract"

    Use cases:
    • Research supreme court precedents and legal principles
    • Find decisions from specific chambers (Civil (Hukuk) vs Criminal (Ceza))
    • Search for interpretations of specific legal concepts
    • Analyze court reasoning on complex legal issues
    • Track legal developments over time periods

    Returns structured search results with decision metadata. Use get_yargitay_document_markdown()
    to retrieve full decision texts for detailed analysis.
    """
    args = {
        "arananKelime": request.arananKelime,
        "birimYrgKurulDaire": request.birimYrgKurulDaire, 
        "pageSize": request.pageSize
    }
    if request.baslangicTarihi:
        args["baslangicTarihi"] = request.baslangicTarihi
    if request.bitisTarihi:
        args["bitisTarihi"] = request.bitisTarihi
    return await call_mcp_tool("search_yargitay_detailed", args)

@app.post(
    "/api/yargitay/search-bedesten",
    tags=["Yargıtay"], 
    summary="Search Court of Cassation (Bedesten API)",
    description="""Alternative Court of Cassation search with exact phrase matching and recent decisions.

Key Features:
• Exact phrase search: "\"legal term\"" for precise matching
• Regular search: "legal term" for individual word matching  
• 52 chamber filtering options (same as primary API)
• ISO 8601 date filtering • Recent decision coverage

Use alongside primary search for comprehensive coverage. Exact phrase search provides
higher precision with fewer false positives."""
)
async def search_yargitay_bedesten(request: YargitayBedestenSearchRequest):
    """Search Court of Cassation using Bedesten API. Complements primary API for complete coverage."""
    args = {"phrase": request.phrase, "pageSize": request.pageSize}
    if request.birimAdi:
        args["birimAdi"] = request.birimAdi
    if request.kararTarihiStart:
        args["kararTarihiStart"] = request.kararTarihiStart
    if request.kararTarihiEnd:
        args["kararTarihiEnd"] = request.kararTarihiEnd
    return await call_mcp_tool("search_yargitay_bedesten", args)

@app.get(
    "/api/yargitay/document/{decision_id}",
    tags=["Yargıtay"],
    summary="Get Court of Cassation Document (Primary API)", 
    description="""Retrieve complete Court of Cassation decision in Markdown format.

Content includes:
• Complete legal reasoning and precedent analysis
• Detailed examination of lower court decisions  
• Citations of laws, regulations, and prior cases
• Final ruling with legal justification

Perfect for detailed legal analysis, precedent research, and citation building."""
)
async def get_yargitay_document(decision_id: str):
    """Get full Court of Cassation decision text in clean Markdown format."""
    return await call_mcp_tool("get_yargitay_document_markdown", {"id": decision_id})

@app.get(
    "/api/yargitay/bedesten-document/{document_id}",
    tags=["Yargıtay"],
    summary="Get Court of Cassation Document (Bedesten API)",
    description="""Retrieve Court of Cassation decision from Bedesten API in Markdown format.

Features:
• Supports both HTML and PDF source documents
• Clean Markdown conversion with legal structure preserved
• Removes technical artifacts for easy reading
• Compatible with documentId from Bedesten search results"""
)
async def get_yargitay_bedesten_document(document_id: str):
    """Get Court of Cassation document from Bedesten API in Markdown format."""
    return await call_mcp_tool("get_yargitay_bedesten_document_markdown", {"documentId": document_id})

# ============================================================================
# DANISTAY (COUNCIL OF STATE) ENDPOINTS - 5 TOOLS  
# ============================================================================

@app.post(
    "/api/danistay/search-keyword",
    tags=["Danıştay"],
    summary="Search Council of State (Keyword Logic)",
    description="""Search Turkey's highest administrative court using Boolean keyword logic.

Boolean Operators:
• AND keywords: ALL must be present (required terms)
• OR keywords: ANY can be present (alternative terms)  
• NOT keywords: EXCLUDE if present (unwanted terms)

Examples:
• Administrative acts: andKelimeler=["idari işlem", "iptal"]
• Permits/licenses: orKelimeler=["ruhsat", "izin", "lisans"]
• Exclude tax cases: notKelimeler=["vergi"]

Perfect for administrative law research and government action reviews."""
)
async def search_danistay_keyword(request: DanistayKeywordSearchRequest):
    """
    Searches Council of State (Danıştay) decisions using keyword-based logic.

    The Council of State (Danıştay) is Turkey's highest administrative court, responsible for
    reviewing administrative actions and providing administrative law precedents. This tool
    provides flexible keyword-based searching with Boolean logic operators.

    Key Features:
    • Boolean logic operators: AND, OR, NOT combinations
    • Multiple keyword lists for complex search strategies
    • Pagination support (1-100 results per page)
    • Administrative law focus (permits, licenses, public administration)
    • Complement to search_danistay_detailed for comprehensive coverage

    Keyword Logic:
    • andKelimeler: ALL keywords must be present (AND logic)
    • orKelimeler: ANY keyword can be present (OR logic)
    • notAndKelimeler: EXCLUDE if ALL keywords present (NOT AND)
    • notOrKelimeler: EXCLUDE if ANY keyword present (NOT OR)

    Administrative Law Use Cases:
    • Research administrative court precedents
    • Find decisions on specific government agencies
    • Search for rulings on permits (ruhsat) and licenses (izin)
    • Analyze administrative procedure interpretations
    • Study public administration legal principles

    Examples:
    • Simple AND: andKelimeler=["administrative act (idari işlem)", "annulment (iptal)"]
    • OR search: orKelimeler=["permit (ruhsat)", "permission (izin)", "license (lisans)"]
    • Complex: andKelimeler=["municipality (belediye)"], notOrKelimeler=["tax (vergi)"]

    Returns structured search results. Use get_danistay_document_markdown() for full texts.
    For comprehensive Council of State (Danıştay) research, also use search_danistay_detailed and search_danistay_bedesten.
    """
    args = {"andKelimeler": request.andKelimeler, "pageSize": request.pageSize}
    if request.orKelimeler:
        args["orKelimeler"] = request.orKelimeler
    if request.notKelimeler:
        args["notKelimeler"] = request.notKelimeler
    return await call_mcp_tool("search_danistay_by_keyword", args)

@app.post(
    "/api/danistay/search-detailed",
    tags=["Danıştay"],
    summary="Search Council of State (Detailed Criteria)",
    description="""Most comprehensive Council of State search with advanced filtering.

Advanced Filtering:
• Chamber targeting (1. Daire through 17. Daire, special councils)
• Case/decision number ranges • Date range filtering
• Legislation cross-referencing • Multiple sorting options

Use for specialized administrative law research, chamber-specific decisions,
and regulatory compliance analysis."""
)
async def search_danistay_detailed(request: DanistayDetailedSearchRequest):
    """Search Council of State with comprehensive filtering for specialized administrative law research."""
    args = {}
    for field in ["daire", "baslangicTarihi", "bitisTarihi", "esas", "karar"]:
        if getattr(request, field):
            args[field] = getattr(request, field)
    return await call_mcp_tool("search_danistay_detailed", args)

@app.post(
    "/api/danistay/search-bedesten",
    tags=["Danıştay"],
    summary="Search Council of State (Bedesten API)",
    description="""Council of State search via Bedesten API with 27 chamber options and exact phrase search.

Key Features:
• 27 chamber options (Main Councils, 17 Chambers, Military courts)
• Exact phrase search with double quotes for precision
• ISO 8601 date filtering • Alternative data source

Use with other Danıştay tools for complete administrative law coverage."""
)
async def search_danistay_bedesten(request: DanistayBedestenSearchRequest):
    """Search Council of State via Bedesten API. Use with other Danıştay tools for complete coverage."""
    args = {"phrase": request.phrase, "pageSize": request.pageSize}
    for field in ["birimAdi", "kararTarihiStart", "kararTarihiEnd"]:
        if getattr(request, field):
            args[field] = getattr(request, field)
    return await call_mcp_tool("search_danistay_bedesten", args)

@app.get(
    "/api/danistay/document/{decision_id}",
    tags=["Danıştay"],
    summary="Get Council of State Document (Primary API)",
    description="""Retrieve complete administrative court decision in Markdown format.

Content includes:
• Complete administrative law reasoning and precedent analysis
• Review of administrative actions and government decisions
• Citations of administrative laws and regulations
• Final administrative ruling with legal justification

Essential for administrative law research and government compliance analysis."""
)
async def get_danistay_document(decision_id: str):
    """Get full Council of State decision text in clean Markdown format."""
    return await call_mcp_tool("get_danistay_document_markdown", {"id": decision_id})

@app.get(
    "/api/danistay/bedesten-document/{document_id}",
    tags=["Danıştay"],
    summary="Get Council of State Document (Bedesten API)",
    description="""Retrieve Council of State decision from Bedesten API in Markdown format."""
)
async def get_danistay_bedesten_document(document_id: str):
    """Get Council of State document from Bedesten API in Markdown format."""
    return await call_mcp_tool("get_danistay_bedesten_document_markdown", {"documentId": document_id})

# ============================================================================
# BEDESTEN API COURTS (LOCAL/APPELLATE/KYB) - 6 TOOLS
# ============================================================================

@app.post(
    "/api/yerel-hukuk/search",
    tags=["Yerel Hukuk"],
    summary="Search Local Civil Courts",
    description="""Search first-instance civil court decisions using Bedesten API.

Local Civil Courts handle:
• Contract disputes • Property rights • Family law • Tort claims
• Commercial disputes • Consumer protection

Only available tool for local court decisions. Supports exact phrase search
and date filtering for precise legal research."""
)
async def search_yerel_hukuk(request: BedestenSearchRequest):
    """
    Searches Yerel Hukuk Mahkemesi (Local Civil Court) decisions using Bedesten API.
    
    This provides access to local court decisions that are not available through other APIs.
    Currently the only available tool for searching Yerel Hukuk Mahkemesi decisions.
    Local civil courts represent the first instance of civil litigation in Turkey.

    Local Civil Courts handle:
    • Contract disputes and commercial litigation
    • Property rights and real estate disputes
    • Family law matters (divorce, custody, inheritance)
    • Tort claims and compensation cases
    • Consumer protection issues
    • Employment disputes
    
    Returns structured search results with decision metadata. Use get_yerel_hukuk_bedesten_document_markdown()
    to retrieve full decision texts for detailed analysis.
    """
    args = {"phrase": request.phrase, "pageSize": request.pageSize}
    for field in ["kararTarihiStart", "kararTarihiEnd"]:
        if getattr(request, field):
            args[field] = getattr(request, field)
    return await call_mcp_tool("search_yerel_hukuk_bedesten", args)

@app.get("/api/yerel-hukuk/document/{document_id}", tags=["Yerel Hukuk"])
async def get_yerel_hukuk_document(document_id: str):
    """
    Retrieves a Yerel Hukuk Mahkemesi decision document from Bedesten API and converts to Markdown.
    
    This tool fetches complete local court decision texts using documentId from search results.
    Perfect for detailed analysis of first-instance civil court rulings.
    
    Supports both HTML and PDF content types, automatically converting to clean Markdown format.
    Use documentId from search_yerel_hukuk_bedesten results.
    """
    return await call_mcp_tool("get_yerel_hukuk_bedesten_document_markdown", {"documentId": document_id})

@app.post(
    "/api/istinaf-hukuk/search",
    tags=["İstinaf Hukuk"],
    summary="Search Civil Courts of Appeals",
    description="""Search intermediate appellate court decisions using Bedesten API.

İstinaf Courts are intermediate appellate courts handling appeals from local civil courts
before cases reach the Court of Cassation. Only available tool for İstinaf decisions."""
)
async def search_istinaf_hukuk(request: BedestenSearchRequest):
    """
    Searches İstinaf Hukuk Mahkemesi (Civil Court of Appeals) decisions using Bedesten API.

    İstinaf courts are intermediate appellate courts in the Turkish judicial system that handle
    appeals from local civil courts before cases reach Yargıtay (Court of Cassation).
    This is the only available tool for accessing İstinaf Hukuk Mahkemesi decisions.

    Key Features:
    • Date range filtering with ISO 8601 format (YYYY-MM-DDTHH:MM:SS.000Z)
    • Exact phrase search using double quotes: "\"legal term\"" 
    • Regular search for individual keywords
    • Pagination support (1-100 results per page)

    Use cases:
    • Research appellate court precedents
    • Track appeals from specific lower courts
    • Find decisions on specific legal issues at appellate level
    • Analyze intermediate court reasoning before supreme court review

    Returns structured data with decision metadata including dates, case numbers, and summaries.
    Use get_istinaf_hukuk_bedesten_document_markdown() to retrieve full decision texts.
    """
    args = {"phrase": request.phrase, "pageSize": request.pageSize}
    for field in ["kararTarihiStart", "kararTarihiEnd"]:
        if getattr(request, field):
            args[field] = getattr(request, field)
    return await call_mcp_tool("search_istinaf_hukuk_bedesten", args)

@app.get("/api/istinaf-hukuk/document/{document_id}", tags=["İstinaf Hukuk"])
async def get_istinaf_hukuk_document(document_id: str):
    """
    Retrieves the full text of an İstinaf Hukuk Mahkemesi decision document in Markdown format.

    This tool converts the original decision document (HTML or PDF) from Bedesten API
    into clean, readable Markdown format suitable for analysis and processing.

    Input Requirements:
    • documentId: Use the ID from search_istinaf_hukuk_bedesten results
    • Document ID must be non-empty string

    Output Format:
    • Clean Markdown text with proper formatting
    • Preserves legal structure (headers, paragraphs, citations)
    • Removes extraneous HTML/PDF artifacts

    Use for:
    • Reading full appellate court decision texts
    • Legal analysis of İstinaf court reasoning
    • Citation extraction and reference building
    • Content analysis and summarization
    """
    return await call_mcp_tool("get_istinaf_hukuk_bedesten_document_markdown", {"documentId": document_id})

@app.post(
    "/api/kyb/search",
    tags=["KYB"],
    summary="Search Extraordinary Appeals (KYB)",
    description="""Search Kanun Yararına Bozma (Extraordinary Appeal) decisions.

KYB is an extraordinary legal remedy where the Public Prosecutor's Office requests
review of finalized decisions in favor of law and defendants. Very rare but important
legal precedents. Only available tool for KYB decisions."""
)
async def search_kyb(request: BedestenSearchRequest):
    """
    Searches Kanun Yararına Bozma (KYB - Extraordinary Appeal) decisions using Bedesten API.

    KYB is an extraordinary legal remedy in the Turkish judicial system where the
    Public Prosecutor's Office can request review of finalized decisions in favor of
    the law and defendants. This is the only available tool for accessing KYB decisions.

    Key Features:
    • Date range filtering with ISO 8601 format (YYYY-MM-DDTHH:MM:SS.000Z)
    • Exact phrase search using double quotes: "\"extraordinary appeal\""
    • Regular search for individual keywords
    • Pagination support (1-100 results per page)

    Legal Significance:
    • Extraordinary remedy beyond regular appeals
    • Initiated by Public Prosecutor's Office
    • Reviews finalized decisions for legal errors
    • Can benefit defendants retroactively
    • Rare but important legal precedents

    Use cases:
    • Research extraordinary appeal precedents
    • Study prosecutorial challenges to final decisions
    • Analyze legal errors in finalized cases
    • Track KYB success rates and patterns

    Returns structured data with decision metadata. Use get_kyb_bedesten_document_markdown()
    to retrieve full decision texts for detailed analysis.
    """
    args = {"phrase": request.phrase, "pageSize": request.pageSize}
    for field in ["kararTarihiStart", "kararTarihiEnd"]:
        if getattr(request, field):
            args[field] = getattr(request, field)
    return await call_mcp_tool("search_kyb_bedesten", args)

@app.get("/api/kyb/document/{document_id}", tags=["KYB"])
async def get_kyb_document(document_id: str):
    """
    Retrieves the full text of a Kanun Yararına Bozma (KYB) decision document in Markdown format.

    This tool converts the original extraordinary appeal decision document (HTML or PDF)
    from Bedesten API into clean, readable Markdown format for analysis.

    Input Requirements:
    • documentId: Use the ID from search_kyb_bedesten results
    • Document ID must be non-empty string

    Output Format:
    • Clean Markdown text with legal formatting preserved
    • Structured content with headers and citations
    • Removes technical artifacts from source documents

    Special Value for KYB Documents:
    • Contains rare extraordinary appeal reasoning
    • Shows prosecutorial arguments for legal review
    • Documents correction of finalized legal errors
    • Provides precedent for similar extraordinary circumstances

    Use for:
    • Analyzing extraordinary appeal legal reasoning
    • Understanding prosecutorial review criteria
    • Research on legal error correction mechanisms
    • Studying retroactive benefit applications
    """
    return await call_mcp_tool("get_kyb_bedesten_document_markdown", {"documentId": document_id})

# ============================================================================
# ADDITIONAL COURTS - 12 TOOLS
# ============================================================================

@app.post("/api/emsal/search", tags=["Emsal"], summary="Search UYAP Precedents")
async def search_emsal(request: EmsalSearchRequest):
    """
    Searches for Precedent (Emsal) decisions using detailed criteria.

    The Precedent (Emsal) database contains precedent decisions from various Turkish courts
    integrated through the UYAP (National Judiciary Informatics System). This tool provides
    access to a comprehensive collection of court decisions that serve as legal precedents.

    Key Features:
    • Multi-court coverage (BAM, Civil courts, Regional chambers)
    • Keyword-based search across decision texts
    • Court-specific filtering for targeted research
    • Case number filtering (Case No (Esas No) and Decision No (Karar No) with ranges)
    • Date range filtering with DD.MM.YYYY format
    • Multiple sorting options and pagination support

    Court Selection Options:
    • BAM Civil Courts: Higher regional civil courts
    • Civil Courts: Local and first-instance civil courts
    • Regional Civil Chambers: Specialized civil court departments

    Precedent Research Use Cases:
    • Find precedent (emsal) decisions across multiple court levels
    • Research court interpretations of specific legal concepts
    • Analyze consistent legal reasoning patterns
    • Study regional variations in legal decisions
    • Track precedent development over time
    • Compare decisions from different court types

    Returns structured precedent data with court information and decision metadata.
    Use get_emsal_document_markdown() to retrieve full precedent decision texts.
    """
    args = {"keyword": request.keyword, "results_per_page": request.results_per_page}
    if request.decision_year_karar:
        args["decision_year_karar"] = request.decision_year_karar
    return await call_mcp_tool("search_emsal_detailed_decisions", args)

@app.get("/api/emsal/document/{decision_id}", tags=["Emsal"])
async def get_emsal_document(decision_id: str):
    """
    Retrieves the full text of a specific Emsal (UYAP Precedent) decision in Markdown format.

    This tool fetches complete precedent decision documents from the UYAP system and converts
    them to clean, readable Markdown format suitable for legal precedent analysis.

    Input Requirements:
    • decision_id: Decision ID from search_emsal_detailed_decisions results
    • ID must be non-empty string from UYAP Emsal database

    Output Format:
    • Clean Markdown text with legal precedent structure preserved
    • Organized sections: court info, case facts, legal reasoning, conclusion
    • Proper formatting for legal citations and cross-references
    • Removes technical artifacts from source documents

    Precedent Decision Content:
    • Complete court reasoning and legal analysis
    • Detailed examination of legal principles applied
    • Citation of relevant laws, regulations, and prior precedents
    • Final ruling with precedent-setting reasoning
    • Court-specific interpretations and legal standards

    Use for legal precedent research, citation building, and comparative legal analysis.
    """
    return await call_mcp_tool("get_emsal_document_markdown", {"decision_id": decision_id})

@app.post("/api/uyusmazlik/search", tags=["Uyuşmazlık"], summary="Search Jurisdictional Disputes")
async def search_uyusmazlik(request: UyusmazlikSearchRequest):
    """
    Searches for Court of Jurisdictional Disputes (Uyuşmazlık Mahkemesi) decisions.

    The Court of Jurisdictional Disputes (Uyuşmazlık Mahkemesi) resolves jurisdictional disputes between different court systems
    in Turkey, determining which court has jurisdiction over specific cases. This specialized
    court handles conflicts between civil, criminal, and administrative jurisdictions.

    Key Features:
    • Department filtering (Criminal, Civil, General Assembly decisions)
    • Dispute type classification (Jurisdiction vs Judgment disputes)
    • Decision outcome filtering (dispute resolution results)
    • Case number and date range filtering
    • Advanced text search with Boolean logic operators
    • Official Gazette reference search

    Dispute Types:
    • Jurisdictional Disputes (Görev Uyuşmazlığı): Which court has authority
    • Judgment Disputes (Hüküm Uyuşmazlığı): Conflicting final decisions

    Departments:
    • Criminal Section (Ceza Bölümü): Criminal section decisions
    • Civil Section (Hukuk Bölümü): Civil section decisions  
    • General Assembly Decisions (Genel Kurul Kararları): General Assembly decisions

    Use cases:
    • Research jurisdictional precedents
    • Understand court system boundaries
    • Analyze dispute resolution patterns
    • Study inter-court conflict resolution
    • Legal procedure and jurisdiction research

    Returns structured search results with dispute resolution information.
    """
    return await call_mcp_tool("search_uyusmazlik_decisions", {
        "keywords": request.keywords,
        "page_to_fetch": request.page_to_fetch
    })

@app.get("/api/uyusmazlik/document", tags=["Uyuşmazlık"])
async def get_uyusmazlik_document(document_url: str):
    """
    Retrieves the full text of a specific Uyuşmazlık Mahkemesi decision from its URL in Markdown format.

    This tool fetches complete jurisdictional dispute resolution decisions and converts them
    to clean, readable Markdown format suitable for legal analysis of inter-court disputes.

    Input Requirements:
    • document_url: Full URL to the decision document from search_uyusmazlik_decisions results
    • URL must be valid HttpUrl format from official Uyuşmazlık Mahkemesi database

    Output Format:
    • Clean Markdown text with jurisdictional dispute structure preserved
    • Organized sections: dispute facts, jurisdictional analysis, resolution ruling
    • Proper formatting for legal citations and court system references
    • Removes technical artifacts from source documents

    Jurisdictional Dispute Decision Content:
    • Complete analysis of jurisdictional conflicts between court systems
    • Detailed examination of applicable jurisdictional rules
    • Citation of relevant procedural laws and court organization statutes
    • Final resolution determining proper court jurisdiction
    • Reasoning for jurisdictional boundaries and court authority

    Use for understanding court system boundaries, analyzing jurisdictional precedents,
    and legal practice guidance on proper court selection.
    """
    return await call_mcp_tool("get_uyusmazlik_document_markdown_from_url", {"document_url": document_url})

@app.post("/api/anayasa/search-norm", tags=["Anayasa"], summary="Search Constitutional Court (Norm Control)")
async def search_anayasa_norm(request: AnayasaNormSearchRequest):
    """
    Searches Constitutional Court (Anayasa Mahkemesi) norm control decisions with comprehensive filtering.

    The Constitutional Court is Turkey's highest constitutional authority, responsible for judicial
    review of laws, regulations, and constitutional amendments. Norm control (Norm Denetimi) is the
    court's power to review the constitutionality of legal norms.

    Key Features:
    • Boolean keyword search (AND, OR, NOT logic)
    • Constitutional period filtering (1961 vs 1982 Constitution)
    • Case and decision number filtering
    • Date range filtering for review and decision dates
    • Application type classification (İptal, İtiraz, etc.)
    • Applicant filtering (government entities, opposition parties)
    • Official Gazette publication filtering
    • Judicial opinion analysis (dissents, different reasoning)
    • Court member and rapporteur filtering
    • Norm type classification (laws, regulations, decrees)
    • Review outcome filtering (constitutionality determinations)
    • Constitutional basis article referencing

    Constitutional Review Types:
    • Abstract review: Ex ante constitutional control
    • Concrete review: Constitutional questions during litigation
    • Legislative review: Parliamentary acts and government decrees
    • Regulatory review: Administrative regulations and bylaws

    Use cases:
    • Constitutional law research and analysis
    • Legislative drafting constitutional compliance
    • Academic constitutional law study
    • Legal precedent analysis for constitutional questions
    • Government policy constitutional assessment

    Returns structured constitutional court data with comprehensive metadata.
    """
    args = {"keywords_all": request.keywords_all, "results_per_page": request.results_per_page}
    for field in ["period", "application_type"]:
        if getattr(request, field):
            args[field] = getattr(request, field)
    return await call_mcp_tool("search_anayasa_norm_denetimi_decisions", args)

@app.get("/api/anayasa/norm-document", tags=["Anayasa"])
async def get_anayasa_norm_document(document_url: str, page_number: int = 1):
    """Get Constitutional Court norm control decision in paginated Markdown format."""
    return await call_mcp_tool("get_anayasa_norm_denetimi_document_markdown", {
        "document_url": document_url, "page_number": page_number
    })

@app.post("/api/anayasa/search-bireysel", tags=["Anayasa"], summary="Search Constitutional Court (Individual Applications)")
async def search_anayasa_bireysel(request: AnayasaBireyselSearchRequest):
    """
    Search Constitutional Court individual application (Bireysel Başvuru) decisions for human rights violation reports with keyword filtering.
    
    Individual applications allow citizens to petition the Constitutional Court directly for
    violations of fundamental rights and freedoms. This tool generates decision search reports
    that help identify relevant human rights violation cases.
    
    Key Features:
    • Keyword-based search with AND logic
    • Human rights violation case identification
    • Individual petition decision analysis
    • Fundamental rights and freedoms research
    • Pagination support for large result sets
    
    Individual Application System:
    • Direct citizen access to Constitutional Court
    • Human rights and fundamental freedoms protection
    • Alternative to European Court of Human Rights
    • Domestic remedy for constitutional violations
    • Individual justice and rights enforcement
    
    Human Rights Categories:
    • Right to life and personal liberty
    • Right to fair trial and due process
    • Freedom of expression and press
    • Freedom of religion and conscience
    • Property rights and economic freedoms
    • Right to privacy and family life
    • Political rights and democratic participation
    
    Use cases:
    • Human rights violation research
    • Individual petition precedent analysis
    • Constitutional rights interpretation study
    • Legal remedies for rights violations
    • Academic human rights law research
    • Civil society and NGO legal research
    
    Returns search report with case summaries and violation categories.
    Use get_anayasa_bireysel_document for full decision texts.
    """
    return await call_mcp_tool("search_anayasa_bireysel_basvuru_report", {
        "keywords": request.keywords, "page_to_fetch": request.page_to_fetch
    })

@app.get("/api/anayasa/bireysel-document", tags=["Anayasa"])
async def get_anayasa_bireysel_document(document_url: str, page_number: int = 1):
    """
    Retrieve the full text of a Constitutional Court individual application decision in paginated Markdown format.
    
    This tool fetches complete human rights violation decisions from individual applications
    and converts them to clean, readable Markdown format. Content is paginated into
    5,000-character chunks for easier processing.
    
    Input Requirements:
    • document_url: URL path (e.g., /BB/YYYY/NNNN) from search results
    • page_number: Page number for pagination (1-indexed, default: 1)
    
    Output Format:
    • Clean Markdown text with human rights case structure preserved
    • Organized sections: applicant info, violation claims, court analysis, ruling
    • Proper formatting for human rights law citations and references
    • Paginated content with navigation information
    
    Individual Application Decision Content:
    • Complete human rights violation analysis
    • Detailed examination of fundamental rights claims
    • Citation of constitutional articles and international human rights law
    • Final determination on rights violations with remedies
    • Analysis of domestic court proceedings and their adequacy
    • Individual remedy recommendations and compensation
    
    Use for:
    • Reading full human rights violation decisions
    • Human rights law research and precedent analysis
    • Understanding constitutional rights protection standards
    • Individual petition strategy development
    • Academic human rights and constitutional law study
    • Civil society monitoring of rights violations
    """
    return await call_mcp_tool("get_anayasa_bireysel_basvuru_document_markdown", {
        "document_url": document_url, "page_number": page_number
    })

@app.post("/api/kik/search", tags=["KİK"], summary="Search Public Procurement Authority")
async def search_kik(request: KikSearchRequest):
    """
    Search Public Procurement Authority (Kamu İhale Kurulu - KIK) decisions with comprehensive filtering for public procurement law and administrative dispute research.
    
    The Public Procurement Authority (KIK) is Turkey's procurement oversight body, responsible for
    regulating public procurement processes, resolving procurement disputes, and issuing interpretive
    decisions on public contracting laws. This tool provides access to official procurement decisions
    and regulatory guidance.
    
    Key Features:
    • Decision type filtering (Disputes, Regulatory, Court decisions)
    • Decision date range filtering for temporal analysis
    • Applicant and procuring entity filtering
    • Tender subject and content-based search
    • Decision number and reference tracking
    • Comprehensive metadata extraction
    
    Public Procurement Decision Types:
    • Uyuşmazlık Kararları: Dispute resolution decisions
    • Düzenleyici Kararlar: Regulatory and interpretive decisions
    • Mahkeme Kararları: Court decisions and judicial precedents
    
    Procurement Law Areas:
    • Tender procedure compliance and violations
    • Bid evaluation and award criteria disputes
    • Contractor qualification and eligibility
    • Contract modification and scope changes
    • Performance guarantees and penalty applications
    • Public procurement ethics and transparency
    • Emergency procurement and exceptional procedures
    
    Use cases:
    • Public procurement law research and compliance guidance
    • Tender dispute resolution precedent analysis
    • Government contracting risk assessment
    • Procurement policy and regulatory interpretation
    • Academic public administration and law study
    • Legal strategy development for procurement disputes
    
    Returns structured procurement authority data with comprehensive case metadata.
    Use get_kik_document for full decision texts with detailed reasoning.
    """
    args = {}
    for field in ["karar_tipi", "karar_metni", "basvuru_konusu_ihale", "karar_tarihi_baslangic"]:
        if getattr(request, field):
            args[field] = getattr(request, field)
    return await call_mcp_tool("search_kik_decisions", args)

@app.get("/api/kik/document/{decision_id}", tags=["KİK"])
async def get_kik_document(decision_id: str):
    """
    Retrieve the full text of a Public Procurement Authority (KIK) decision in paginated Markdown format.
    
    This tool fetches complete public procurement decisions and converts them from PDF to clean,
    readable Markdown format. Content is paginated into manageable chunks for processing lengthy
    procurement law decisions and regulatory interpretations.
    
    Input Requirements:
    • decision_id: KIK decision ID (base64 encoded karar_id) from search results
    • Decision ID must be non-empty string
    
    Output Format:
    • Clean Markdown text converted from original PDF documents
    • Organized sections: case summary, legal analysis, regulatory interpretation, decision
    • Proper formatting for procurement law citations and regulatory references
    • Paginated content with navigation information
    • Metadata including PDF source link and document information
    
    Public Procurement Decision Content:
    • Complete procurement dispute analysis and resolution
    • Detailed examination of tender procedures and compliance
    • Citation of procurement laws, regulations, and precedents
    • Final determination on procurement violations with corrective measures
    • Regulatory guidance and policy interpretations
    • Contractor liability and penalty determinations
    
    Use for:
    • Reading full public procurement authority decisions
    • Procurement law research and precedent analysis
    • Government contracting compliance and risk assessment
    • Tender dispute resolution strategy development
    • Academic public administration and procurement law study
    • Policy analysis and regulatory interpretation
    """
    return await call_mcp_tool("get_kik_document_markdown", {"decision_id": decision_id})

@app.post("/api/rekabet/search", tags=["Rekabet"], summary="Search Competition Authority")
async def search_rekabet(request: RekabetSearchRequest):
    """
    Search Competition Authority (Rekabet Kurumu) decisions with comprehensive filtering for competition law and antitrust research.
    
    The Competition Authority (Rekabet Kurumu) is Turkey's competition authority, responsible for enforcing antitrust laws,
    preventing anti-competitive practices, and regulating mergers and acquisitions. This tool
    provides access to official competition law decisions and regulatory interpretations.
    
    Key Features:
    • Decision type filtering (Mergers, Violations, Exemptions, etc.)
    • Title and content-based text search with exact phrase matching
    • Publication date filtering
    • Case year and decision number filtering
    • Pagination support for large result sets
    
    Competition Law Decision Types:
    • Birleşme ve Devralma: Merger and acquisition approvals
    • Rekabet İhlali: Competition violation investigations
    • Muafiyet: Exemption and negative clearance decisions
    • Geçici Tedbir: Interim measures and emergency orders
    • Sektör İncelemesi: Sector inquiry and market studies
    • Diğer: Other regulatory and interpretive decisions
    
    Competition Law Areas:
    • Anti-competitive agreements and cartels
    • Abuse of dominant market position
    • Merger control and market concentration
    • Vertical agreements and distribution restrictions
    • Unfair competition and consumer protection
    • Market definition and economic analysis
    
    Advanced Search:
    • Exact phrase matching with double quotes for precise legal terms
    • Content search across full decision texts (PdfText parameter)
    • Title search for specific case names or topics
    • Date range filtering for temporal analysis
    
    Example for exact phrase search: PdfText=\"tender process\" consultancy
    
    Use cases:
    • Competition law research and precedent analysis
    • Merger and acquisition due diligence
    • Antitrust compliance and risk assessment
    • Market analysis and competitive intelligence
    • Academic competition economics study
    • Legal strategy development for competition cases
    
    Returns structured competition authority data with comprehensive metadata.
    Use get_rekabet_document for full decision texts (paginated PDF conversion).
    """
    args = {"page": request.page}
    for field in ["KararTuru", "PdfText", "YayinlanmaTarihi"]:
        if getattr(request, field):
            args[field] = getattr(request, field)
    return await call_mcp_tool("search_rekabet_kurumu_decisions", args)

@app.get("/api/rekabet/document/{karar_id}", tags=["Rekabet"])
async def get_rekabet_document(karar_id: str, page_number: int = 1):
    """
    Retrieve the full text of a Competition Authority (Rekabet Kurumu) decision in paginated Markdown format converted from PDF.
    
    This tool fetches complete competition authority decisions and converts them from PDF to clean,
    readable Markdown format. Content is paginated for easier processing of lengthy competition law decisions.
    
    Input Requirements:
    • karar_id: GUID (kararId) from search_rekabet_kurumu_decisions results
    • page_number: Page number for pagination (1-indexed, default: 1)
    
    Output Format:
    • Clean Markdown text converted from original PDF documents
    • Organized sections: case summary, market analysis, legal reasoning, decision
    • Proper formatting for competition law citations and references
    • Paginated content with navigation information
    • Metadata including PDF source link and document information
    
    Competition Authority Decision Content:
    • Complete competition law analysis and market assessment
    • Detailed examination of anti-competitive practices
    • Economic analysis and market definition studies
    • Citation of competition laws, regulations, and precedents
    • Final determination on competition violations with remedies
    • Merger and acquisition approval conditions
    • Regulatory guidance and policy interpretations
    
    Use for:
    • Reading full competition authority decisions
    • Competition law research and precedent analysis
    • Market analysis and economic impact assessment
    • Antitrust compliance and risk evaluation
    • Academic competition economics and law study
    • Legal strategy development for competition cases
    """
    return await call_mcp_tool("get_rekabet_kurumu_document", {
        "karar_id": karar_id, "page_number": page_number
    })

# ============================================================================
# SAYISTAY (COURT OF ACCOUNTS) ENDPOINTS - 6 TOOLS
# ============================================================================

@app.post("/api/sayistay/search-genel-kurul", tags=["Sayıştay"], summary="Search Court of Accounts (General Assembly)")
async def search_sayistay_genel_kurul(request: SayistaySearchRequest):
    """
    Search Sayıştay Genel Kurul (General Assembly) decisions - highest-level audit precedents and interpretive rulings with keyword-based filtering.
    
    The General Assembly represents the highest decision-making body of Turkey's Court of Accounts,
    issuing authoritative interpretations of audit laws, precedential rulings on complex accountability
    issues, and binding guidance for audit practice. These decisions establish fundamental principles
    for public financial oversight and accountability standards.
    
    Key Features:
    • Keyword-based search with AND logic for precise case finding
    • Highest-level audit precedent identification
    • Interpretive ruling analysis and legal guidance extraction
    • Complex accountability issue resolution tracking
    • Pagination support for comprehensive result sets
    
    General Assembly Decision Authority:
    • Ultimate audit law interpretation and clarification
    • Precedential rulings binding on all audit chambers
    • Complex inter-chamber jurisdiction dispute resolution
    • Policy guidance for audit methodology and standards
    • Final determination on constitutional audit questions
    
    Public Audit Areas:
    • Government budget execution and compliance
    • Public institution financial management
    • State-owned enterprise oversight and accountability
    • Local government and municipal audit standards
    • Public procurement oversight and compliance monitoring
    • Performance audit methodology and effectiveness standards
    • Public revenue collection and tax administration audit
    
    Use Cases:
    • Research authoritative audit law interpretations
    • Find binding precedents for complex audit questions
    • Study evolution of public accountability standards
    • Analyze audit methodology development and refinement
    • Academic public administration and audit research
    • Government policy and accountability framework analysis
    
    Returns structured General Assembly data with comprehensive legal precedent metadata.
    Use get_sayistay_genel_kurul_document for full decision texts with detailed reasoning.
    """
    return await call_mcp_tool("search_sayistay_genel_kurul", {
        "keywords": request.keywords, "page_to_fetch": request.page_to_fetch
    })

@app.get("/api/sayistay/genel-kurul-document", tags=["Sayıştay"])
async def get_sayistay_genel_kurul_document(document_url: str, page_number: int = 1):
    """
    Retrieve the full text of a Sayıştay Genel Kurul decision document in Markdown format for detailed analysis.
    
    This tool converts the original General Assembly decision document into clean,
    readable Markdown format suitable for legal analysis and research.
    
    Input Requirements:
    • decision_id: Use the ID from search_sayistay_genel_kurul results
    • Decision ID must be non-empty string
    
    Output Format:
    • Clean Markdown text with legal formatting preserved
    • Structured content with reasoning and conclusions
    • Removes technical artifacts from source documents
    
    Use for:
    • Detailed analysis of audit precedents
    • Research on public accountability standards
    • Citation and reference building
    • Legal interpretation and case study development
    """
    return await call_mcp_tool("get_sayistay_genel_kurul_document_markdown", {
        "document_url": document_url, "page_number": page_number
    })

@app.post("/api/sayistay/search-temyiz-kurulu", tags=["Sayıştay"], summary="Search Court of Accounts (Appeals Board)")
async def search_sayistay_temyiz_kurulu(request: SayistaySearchRequest):
    """
    Search Sayıştay Temyiz Kurulu (Appeals Board) decisions - appellate review of audit chamber findings with advanced filtering and institutional analysis.
    
    The Appeals Board serves as the intermediate appellate authority in Turkey's audit system,
    reviewing first-instance chamber decisions on audit findings, liability determinations,
    and sanctions. Appeals Board decisions refine audit standards and ensure consistency
    across different audit chambers.
    
    Key Features:
    • Chamber-specific filtering for targeted appeals analysis
    • Institutional type categorization for audit pattern analysis
    • Decision and account year filtering for temporal trends
    • Audit subject matter classification and content search
    • Appeal outcome tracking and precedent identification
    • Pagination support for comprehensive coverage
    
    Appeals Board Authority:
    • Review and modification of chamber audit findings
    • Standardization of audit liability determinations
    • Consistency enforcement across audit chambers
    • Intermediate precedent development for audit practice
    • Quality control for first-instance audit decisions
    
    Audit Review Areas:
    • Government agency financial accountability appeals
    • Municipality and local government audit review
    • State enterprise oversight and performance audit appeals
    • Educational institution audit finding review
    • Healthcare system financial accountability appeals
    • Public procurement oversight and compliance review
    • Tax administration and revenue audit appeals
    
    Use Cases:
    • Research audit appeals patterns and outcomes
    • Study chamber decision consistency and standards
    • Analyze audit liability determination evolution
    • Find precedents for specific audit finding types
    • Track institutional audit patterns and compliance
    • Academic public accountability and audit law research
    
    Returns structured Appeals Board data with comprehensive appellate analysis metadata.
    Use get_sayistay_temyiz_kurulu_document for full appeals decisions with detailed reasoning.
    """
    return await call_mcp_tool("search_sayistay_temyiz_kurulu", {
        "keywords": request.keywords, "page_to_fetch": request.page_to_fetch
    })

@app.get("/api/sayistay/temyiz-kurulu-document", tags=["Sayıştay"])
async def get_sayistay_temyiz_kurulu_document(document_url: str, page_number: int = 1):
    """
    Retrieve the full text of a Sayıştay Temyiz Kurulu decision document in Markdown format for detailed appeals analysis.
    
    This tool converts the original Appeals Board decision document into clean,
    readable Markdown format for analysis of appellate reasoning and standards.
    
    Input Requirements:
    • decision_id: Use the ID from search_sayistay_temyiz_kurulu results
    • Decision ID must be non-empty string
    
    Output Format:
    • Clean Markdown text with appellate reasoning preserved
    • Structured content with original findings and appeals analysis
    • Removes technical artifacts from source documents
    
    Use for:
    • Analysis of appeals board reasoning and standards
    • Research on audit liability determination evolution
    • Understanding chamber decision review criteria
    • Precedent analysis for audit appeal cases
    """
    return await call_mcp_tool("get_sayistay_temyiz_kurulu_document_markdown", {
        "document_url": document_url, "page_number": page_number
    })

@app.post("/api/sayistay/search-daire", tags=["Sayıştay"], summary="Search Court of Accounts (Chambers)")
async def search_sayistay_daire(request: SayistaySearchRequest):
    """
    Search Sayıştay Daire (Chamber) decisions - first-instance audit findings and sanctions from individual audit chambers with comprehensive filtering and subject categorization.
    
    Chamber decisions represent first-instance audit findings, sanctions, and
    liability determinations issued by specialized audit chambers. These form
    the foundation of Turkey's public financial accountability system.
    
    Key Features:
    • Chamber-specific filtering (8 specialized audit chambers)
    • Decision and account year filtering (2012-2025)
    • Public administration type categorization
    • Subject matter classification and full-text search
    • Audit report tracking and institutional analysis
    
    Use Cases:
    • Research specific audit findings and sanctions
    • Study chamber specialization and jurisdiction
    • Analyze audit patterns by institution type
    • Find precedents for financial irregularities
    • Track audit evolution across fiscal years
    """
    return await call_mcp_tool("search_sayistay_daire", {
        "keywords": request.keywords, "page_to_fetch": request.page_to_fetch
    })

@app.get("/api/sayistay/daire-document", tags=["Sayıştay"])
async def get_sayistay_daire_document(document_url: str, page_number: int = 1):
    """
    Retrieve the full text of a Sayıştay Daire decision document in Markdown format for detailed audit findings analysis.
    
    This tool converts the original chamber decision document into clean,
    readable Markdown format for analysis of first-instance audit findings.
    
    Input Requirements:
    • decision_id: Use the ID from search_sayistay_daire results  
    • Decision ID must be non-empty string
    
    Output Format:
    • Clean Markdown text with audit findings preserved
    • Structured content with violations and sanctions
    • Removes technical artifacts from source documents
    
    Use for:
    • Detailed analysis of audit findings and methodology
    • Research on specific types of financial irregularities
    • Understanding chamber jurisdiction and specialization
    • Case study development for audit training and compliance
    """
    return await call_mcp_tool("get_sayistay_daire_document_markdown", {
        "document_url": document_url, "page_number": page_number
    })

# ============================================================================
# ADDITIONAL API ENDPOINTS
# ============================================================================

@app.get("/api/databases")
async def list_databases():
    """Comprehensive database information with tool mappings"""
    return {
        "total_tools": 33,
        "databases": {
            "yargitay": {
                "name": "Yargıtay (Court of Cassation)",
                "description": "Turkey's Supreme Court for civil and criminal matters",
                "tools": 4,
                "chambers": 52,
                "search_tools": ["search_yargitay", "search_yargitay_bedesten"],
                "document_tools": ["get_yargitay_document", "get_yargitay_bedesten_document"]
            },
            "danistay": {
                "name": "Danıştay (Council of State)", 
                "description": "Turkey's Supreme Administrative Court",
                "tools": 5,
                "chambers": 27,
                "search_tools": ["search_danistay_keyword", "search_danistay_detailed", "search_danistay_bedesten"],
                "document_tools": ["get_danistay_document", "get_danistay_bedesten_document"]
            },
            "anayasa": {
                "name": "Anayasa Mahkemesi (Constitutional Court)",
                "description": "Constitutional review and individual applications",
                "tools": 4,
                "features": ["norm_control", "individual_applications", "human_rights"]
            },
            "rekabet": {
                "name": "Rekabet Kurumu (Competition Authority)",
                "description": "Antitrust and merger control",
                "tools": 2,
                "coverage": ["mergers", "cartels", "market_abuse", "sector_inquiries"]
            },
            "kik": {
                "name": "Kamu İhale Kurulu (Public Procurement Authority)",
                "description": "Government procurement disputes",
                "tools": 2,
                "coverage": ["procurement_disputes", "regulatory_decisions", "tender_violations"]
            },
            "sayistay": {
                "name": "Sayıştay (Court of Accounts)",
                "description": "Public audit and financial accountability", 
                "tools": 6,
                "levels": ["general_assembly", "appeals_board", "audit_chambers"]
            },
            "emsal": {
                "name": "Emsal (UYAP Precedents)",
                "description": "Cross-court precedent database",
                "tools": 2,
                "coverage": ["multi_court", "precedent_analysis"]
            },
            "uyusmazlik": {
                "name": "Uyuşmazlık Mahkemesi (Jurisdictional Disputes)",
                "description": "Inter-court jurisdiction disputes",
                "tools": 2,
                "specialization": "jurisdictional_conflicts"
            },
            "bedesten_courts": {
                "name": "Bedesten API Courts (Local/Appellate/KYB)",
                "description": "First instance, appellate, and extraordinary appeal courts",
                "tools": 6,
                "courts": ["yerel_hukuk", "istinaf_hukuk", "kyb"],
                "coverage": "complete_court_hierarchy"
            }
        }
    }

@app.get("/api/stats")
async def get_statistics():
    """Comprehensive API statistics and capabilities"""
    uptime = (datetime.now() - SERVER_START_TIME).total_seconds()
    return {
        "server": {
            "uptime_seconds": uptime,
            "start_time": SERVER_START_TIME.isoformat(),
            "version": "1.0.0",
            "status": "operational"
        },
        "coverage": {
            "total_tools": 33,
            "total_databases": 9,
            "total_chambers": 79,  # 52 Yargıtay + 27 Danıştay
            "search_tools": 16,
            "document_tools": 17
        },
        "capabilities": {
            "advanced_search_operators": True,
            "exact_phrase_search": True,
            "date_range_filtering": True,
            "chamber_filtering": True,
            "boolean_logic": True,
            "wildcard_search": True,
            "pagination": True,
            "markdown_conversion": True,
            "pdf_processing": True,
            "dual_api_support": True
        },
        "legal_coverage": {
            "supreme_courts": ["Yargıtay", "Danıştay"],
            "constitutional_law": "Anayasa Mahkemesi",
            "administrative_law": "Full coverage",
            "competition_law": "Rekabet Kurumu",
            "public_procurement": "KİK",
            "public_audit": "Sayıştay",
            "court_hierarchy": "Complete (Local → Appellate → Supreme)",
            "specialized_courts": ["Uyuşmazlık", "Constitutional", "Administrative"]
        }
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

```

--------------------------------------------------------------------------------
/mcp_server_main.py:
--------------------------------------------------------------------------------

```python
# mcp_server_main.py
import asyncio
import atexit
import logging
import os
import httpx
import json
import time
from collections import defaultdict
from pydantic import BaseModel, HttpUrl, Field 
from typing import Optional, Dict, List, Literal, Any, Union
import urllib.parse
from fastmcp.server.middleware import Middleware, MiddlewareContext

# Optional tiktoken import for token counting
try:
    import tiktoken
    TIKTOKEN_AVAILABLE = True
except ImportError:
    TIKTOKEN_AVAILABLE = False
    tiktoken = None
from fastmcp.server.dependencies import get_access_token, AccessToken
from fastmcp import Context

# Use standard exception for tool errors
class ToolError(Exception):
    """Tool execution error"""
    pass

# --- Logging Configuration Start ---
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)

console_handler = logging.StreamHandler()
log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(log_formatter)
console_handler.setLevel(logging.INFO)
root_logger.addHandler(console_handler)

logger = logging.getLogger(__name__)
# --- Logging Configuration End ---

# --- Token Counting Middleware ---
class TokenCountingMiddleware(Middleware):
    """Middleware for counting input/output tokens using tiktoken."""
    
    def __init__(self, model: str = "cl100k_base"):
        """Initialize token counting middleware.

        Args:
            model: Tiktoken model name (cl100k_base for GPT-4/Claude compatibility)
        """
        if not TIKTOKEN_AVAILABLE:
            raise ImportError("tiktoken is required for token counting. Install with: pip install tiktoken")

        self.encoder = tiktoken.get_encoding(model)
        self.model = model
        self.token_stats = defaultdict(lambda: {"input": 0, "output": 0, "calls": 0})
        self.logger = logging.getLogger("token_counter")
        self.logger.setLevel(logging.INFO)
    
    def count_tokens(self, text: str) -> int:
        """Count tokens in text using tiktoken."""
        if not text:
            return 0
        try:
            return len(self.encoder.encode(str(text)))
        except Exception as e:
            logger.warning(f"Token counting failed: {e}")
            return 0
    
    def extract_text_content(self, data: Any) -> str:
        """Extract text content from various data types."""
        if isinstance(data, str):
            return data
        elif isinstance(data, dict):
            # Extract text from common response fields
            text_parts = []
            for key, value in data.items():
                if isinstance(value, str):
                    text_parts.append(value)
                elif isinstance(value, list):
                    for item in value:
                        if isinstance(item, str):
                            text_parts.append(item)
                        elif isinstance(item, dict) and 'text' in item:
                            text_parts.append(str(item['text']))
            return ' '.join(text_parts)
        elif isinstance(data, list):
            text_parts = []
            for item in data:
                text_parts.append(self.extract_text_content(item))
            return ' '.join(text_parts)
        else:
            return str(data)
    
    def log_token_usage(self, operation: str, input_tokens: int, output_tokens: int, 
                       tool_name: str = None, duration_ms: float = None):
        """Log token usage with structured format."""
        log_data = {
            "operation": operation,
            "tool_name": tool_name,
            "input_tokens": input_tokens,
            "output_tokens": output_tokens,
            "total_tokens": input_tokens + output_tokens,
            "duration_ms": duration_ms,
            "timestamp": time.time()
        }
        
        # Update statistics
        key = tool_name if tool_name else operation
        self.token_stats[key]["input"] += input_tokens
        self.token_stats[key]["output"] += output_tokens
        self.token_stats[key]["calls"] += 1
        
        # Log as JSON for easy parsing
        self.logger.info(json.dumps(log_data))
        
        # Also log human-readable format to main logger
        logger.info(f"Token Usage - {operation}" + 
                   (f" ({tool_name})" if tool_name else "") +
                   f": {input_tokens} in + {output_tokens} out = {input_tokens + output_tokens} total")
    
    async def on_call_tool(self, context: MiddlewareContext, call_next):
        """Count tokens for tool calls."""
        start_time = time.perf_counter()
        
        # Extract tool name and arguments
        tool_name = getattr(context.message, 'name', 'unknown_tool')
        tool_args = getattr(context.message, 'arguments', {})
        
        # Count input tokens (tool arguments)
        input_text = self.extract_text_content(tool_args)
        input_tokens = self.count_tokens(input_text)
        
        try:
            # Execute the tool
            result = await call_next(context)
            
            # Count output tokens (tool result)
            output_text = self.extract_text_content(result)
            output_tokens = self.count_tokens(output_text)
            
            # Calculate duration
            duration_ms = (time.perf_counter() - start_time) * 1000
            
            # Log token usage
            self.log_token_usage("tool_call", input_tokens, output_tokens, 
                               tool_name, duration_ms)
            
            return result
            
        except Exception as e:
            duration_ms = (time.perf_counter() - start_time) * 1000
            self.log_token_usage("tool_call_error", input_tokens, 0, 
                               tool_name, duration_ms)
            raise
    
    async def on_read_resource(self, context: MiddlewareContext, call_next):
        """Count tokens for resource reads."""
        start_time = time.perf_counter()
        
        # Extract resource URI
        resource_uri = getattr(context.message, 'uri', 'unknown_resource')
        
        try:
            # Execute the resource read
            result = await call_next(context)
            
            # Count output tokens (resource content)
            output_text = self.extract_text_content(result)
            output_tokens = self.count_tokens(output_text)
            
            # Calculate duration
            duration_ms = (time.perf_counter() - start_time) * 1000
            
            # Log token usage (no input tokens for resource reads)
            self.log_token_usage("resource_read", 0, output_tokens, 
                               resource_uri, duration_ms)
            
            return result
            
        except Exception as e:
            duration_ms = (time.perf_counter() - start_time) * 1000
            self.log_token_usage("resource_read_error", 0, 0, 
                               resource_uri, duration_ms)
            raise
    
    async def on_get_prompt(self, context: MiddlewareContext, call_next):
        """Count tokens for prompt retrievals."""
        start_time = time.perf_counter()
        
        # Extract prompt name
        prompt_name = getattr(context.message, 'name', 'unknown_prompt')
        
        try:
            # Execute the prompt retrieval
            result = await call_next(context)
            
            # Count output tokens (prompt content)
            output_text = self.extract_text_content(result)
            output_tokens = self.count_tokens(output_text)
            
            # Calculate duration
            duration_ms = (time.perf_counter() - start_time) * 1000
            
            # Log token usage
            self.log_token_usage("prompt_get", 0, output_tokens, 
                               prompt_name, duration_ms)
            
            return result
            
        except Exception as e:
            duration_ms = (time.perf_counter() - start_time) * 1000
            self.log_token_usage("prompt_get_error", 0, 0, 
                               prompt_name, duration_ms)
            raise
    
    def get_token_stats(self) -> Dict[str, Any]:
        """Get current token usage statistics."""
        return dict(self.token_stats)
    
    def reset_token_stats(self):
        """Reset token usage statistics."""
        self.token_stats.clear()

# --- End Token Counting Middleware ---

# Create FastMCP app directly without authentication wrapper
from fastmcp import FastMCP

def create_app(auth=None):
    """Create FastMCP app with standard capabilities and optional auth."""
    global app
    if auth:
        app.auth = auth
        logger.info("MCP server created with Bearer authentication enabled")
    else:
        logger.info("MCP server created with standard capabilities...")
    
    # Add token counting middleware only if tiktoken is available
    if TIKTOKEN_AVAILABLE:
        try:
            token_counter = TokenCountingMiddleware()
            app.add_middleware(token_counter)
            logger.info("Token counting middleware added to MCP server")
        except Exception as e:
            logger.warning(f"Failed to add token counting middleware: {e}")
    
    return app

# --- Module Imports ---
from yargitay_mcp_module.client import YargitayOfficialApiClient
from yargitay_mcp_module.models import (
    YargitayDetailedSearchRequest, YargitayDocumentMarkdown, CompactYargitaySearchResult,
    YargitayBirimEnum, CleanYargitayDecisionEntry
)
from bedesten_mcp_module.client import BedestenApiClient
from bedesten_mcp_module.models import (
    BedestenSearchRequest, BedestenSearchData,
    BedestenDocumentMarkdown, BedestenCourtTypeEnum
)
from bedesten_mcp_module.enums import BirimAdiEnum
from danistay_mcp_module.client import DanistayApiClient
from danistay_mcp_module.models import (
    DanistayKeywordSearchRequest, DanistayDetailedSearchRequest,
    DanistayDocumentMarkdown, CompactDanistaySearchResult
)
from emsal_mcp_module.client import EmsalApiClient
from emsal_mcp_module.models import (
    EmsalSearchRequest, EmsalDocumentMarkdown, CompactEmsalSearchResult
)
from uyusmazlik_mcp_module.client import UyusmazlikApiClient
from uyusmazlik_mcp_module.models import (
    UyusmazlikSearchRequest, UyusmazlikSearchResponse, UyusmazlikDocumentMarkdown,
    UyusmazlikBolumEnum, UyusmazlikTuruEnum, UyusmazlikKararSonucuEnum
)
from anayasa_mcp_module.client import AnayasaMahkemesiApiClient
from anayasa_mcp_module.bireysel_client import AnayasaBireyselBasvuruApiClient
from anayasa_mcp_module.unified_client import AnayasaUnifiedClient
from anayasa_mcp_module.models import (
    AnayasaNormDenetimiSearchRequest,
    AnayasaSearchResult,
    AnayasaDocumentMarkdown,
    AnayasaBireyselReportSearchRequest,
    AnayasaBireyselReportSearchResult,
    AnayasaBireyselBasvuruDocumentMarkdown,
    AnayasaUnifiedSearchRequest,
    AnayasaUnifiedSearchResult,
    AnayasaUnifiedDocumentMarkdown,
    # Removed enum imports - now using Literal strings in models
)
# KIK v2 Module Imports (New API)
from kik_mcp_module.client_v2 import KikV2ApiClient
from kik_mcp_module.models_v2 import KikV2DecisionType
from kik_mcp_module.models_v2 import (
    KikV2SearchResult,
    KikV2DocumentMarkdown
)

from rekabet_mcp_module.client import RekabetKurumuApiClient
from rekabet_mcp_module.models import (
    RekabetKurumuSearchRequest,
    RekabetSearchResult,
    RekabetDocument,
    RekabetKararTuruGuidEnum
)

from sayistay_mcp_module.client import SayistayApiClient
from sayistay_mcp_module.models import (
    GenelKurulSearchRequest, GenelKurulSearchResponse,
    TemyizKuruluSearchRequest, TemyizKuruluSearchResponse,
    DaireSearchRequest, DaireSearchResponse,
    SayistayDocumentMarkdown,
    SayistayUnifiedSearchRequest, SayistayUnifiedSearchResult,
    SayistayUnifiedDocumentMarkdown
)
from sayistay_mcp_module.enums import DaireEnum, KamuIdaresiTuruEnum, WebKararKonusuEnum
from sayistay_mcp_module.unified_client import SayistayUnifiedClient

# KVKK Module Imports
from kvkk_mcp_module.client import KvkkApiClient
from kvkk_mcp_module.models import (
    KvkkSearchRequest,
    KvkkSearchResult,
    KvkkDocumentMarkdown
)

# BDDK Module Imports
from bddk_mcp_module.client import BddkApiClient
from bddk_mcp_module.models import (
    BddkSearchRequest,
    BddkSearchResult,
    BddkDocumentMarkdown
)


# Create a placeholder app that will be properly initialized after tools are defined
from fastmcp import FastMCP

# MCP app for Turkish legal databases with explicit capabilities
app = FastMCP(
    name="Yargı MCP Server",
    version="0.1.6"
)

# --- Health Check Functions (using individual clients) ---

# --- API Client Instances ---
yargitay_client_instance = YargitayOfficialApiClient()
danistay_client_instance = DanistayApiClient()
emsal_client_instance = EmsalApiClient()
uyusmazlik_client_instance = UyusmazlikApiClient()
anayasa_norm_client_instance = AnayasaMahkemesiApiClient()
anayasa_bireysel_client_instance = AnayasaBireyselBasvuruApiClient()
anayasa_unified_client_instance = AnayasaUnifiedClient()
kik_v2_client_instance = KikV2ApiClient()
rekabet_client_instance = RekabetKurumuApiClient()
bedesten_client_instance = BedestenApiClient()
sayistay_client_instance = SayistayApiClient()
sayistay_unified_client_instance = SayistayUnifiedClient()
kvkk_client_instance = KvkkApiClient()
bddk_client_instance = BddkApiClient()


KARAR_TURU_ADI_TO_GUID_ENUM_MAP = {
    "": RekabetKararTuruGuidEnum.TUMU,  # Keep for backward compatibility
    "ALL": RekabetKararTuruGuidEnum.TUMU,  # Map "ALL" to TUMU
    "Birleşme ve Devralma": RekabetKararTuruGuidEnum.BIRLESME_DEVRALMA,
    "Diğer": RekabetKararTuruGuidEnum.DIGER,
    "Menfi Tespit ve Muafiyet": RekabetKararTuruGuidEnum.MENFI_TESPIT_MUAFIYET,
    "Özelleştirme": RekabetKararTuruGuidEnum.OZELLESTIRME,
    "Rekabet İhlali": RekabetKararTuruGuidEnum.REKABET_IHLALI,
}

# --- MCP Tools for Yargitay ---
"""
@app.tool(
    description="Search Yargıtay decisions with 52 chamber filtering and advanced operators",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": True,
        "idempotentHint": True
    }
)
async def search_yargitay_detailed(
    arananKelime: str = Field("", description="Turkish search keyword. Supports +required -excluded \"exact phrase\" operators"),
    birimYrgKurulDaire: str = Field("ALL", description="Chamber selection (52 options: Civil/Criminal chambers, General Assemblies)"),
    esasYil: str = Field("", description="Case year for 'Esas No'."),
    esasIlkSiraNo: str = Field("", description="Starting sequence number for 'Esas No'."),
    esasSonSiraNo: str = Field("", description="Ending sequence number for 'Esas No'."),
    kararYil: str = Field("", description="Decision year for 'Karar No'."),
    kararIlkSiraNo: str = Field("", description="Starting sequence number for 'Karar No'."),
    kararSonSiraNo: str = Field("", description="Ending sequence number for 'Karar No'."),
    baslangicTarihi: str = Field("", description="Start date for decision search (DD.MM.YYYY)."),
    bitisTarihi: str = Field("", description="End date for decision search (DD.MM.YYYY)."),
    # pageSize: int = Field(10, ge=1, le=10, description="Number of results per page."),
    pageNumber: int = Field(1, ge=1, description="Page number to retrieve.")
) -> CompactYargitaySearchResult:
    # Search Yargıtay decisions using primary API with 52 chamber filtering and advanced operators.
    
    # Convert "ALL" to empty string for API compatibility
    if birimYrgKurulDaire == "ALL":
        birimYrgKurulDaire = ""
    
    pageSize = 10  # Default value
    
    search_query = YargitayDetailedSearchRequest(
        arananKelime=arananKelime,
        birimYrgKurulDaire=birimYrgKurulDaire,
        esasYil=esasYil,
        esasIlkSiraNo=esasIlkSiraNo,
        esasSonSiraNo=esasSonSiraNo,
        kararYil=kararYil,
        kararIlkSiraNo=kararIlkSiraNo,
        kararSonSiraNo=kararSonSiraNo,
        baslangicTarihi=baslangicTarihi,
        bitisTarihi=bitisTarihi,
        siralama="3",
        siralamaDirection="desc",
        pageSize=pageSize,
        pageNumber=pageNumber
    )
    
    logger.info(f"Tool 'search_yargitay_detailed' called: {search_query.model_dump_json(exclude_none=True, indent=2)}")
    try:
        api_response = await yargitay_client_instance.search_detailed_decisions(search_query)
        if api_response and api_response.data and api_response.data.data:
            # Convert to clean decision entries without arananKelime field
            clean_decisions = [
                CleanYargitayDecisionEntry(
                    id=decision.id,
                    daire=decision.daire,
                    esasNo=decision.esasNo,
                    kararNo=decision.kararNo,
                    kararTarihi=decision.kararTarihi,
                    document_url=decision.document_url
                )
                for decision in api_response.data.data
            ]
            return CompactYargitaySearchResult(
                decisions=clean_decisions,
                total_records=api_response.data.recordsTotal if api_response.data else 0,
                requested_page=search_query.pageNumber,
                page_size=search_query.pageSize)
        logger.warning("API response for Yargitay search did not contain expected data structure.")
        return CompactYargitaySearchResult(decisions=[], total_records=0, requested_page=search_query.pageNumber, page_size=search_query.pageSize)
    except Exception as e:
        logger.exception(f"Error in tool 'search_yargitay_detailed'.")
        raise

@app.tool(
    description="Get Yargıtay decision text in Markdown format",
    annotations={
        "readOnlyHint": True,
        "idempotentHint": True
    }
)
async def get_yargitay_document_markdown(id: str) -> YargitayDocumentMarkdown:
    # Get Yargıtay decision text as Markdown. Use ID from search results.
    logger.info(f"Tool 'get_yargitay_document_markdown' called for ID: {id}")
    if not id or not id.strip(): raise ValueError("Document ID must be a non-empty string.")
    try:
        return await yargitay_client_instance.get_decision_document_as_markdown(id)
    except Exception as e:
        logger.exception(f"Error in tool 'get_yargitay_document_markdown'.")
        raise
"""

# --- MCP Tools for Danistay ---
"""
@app.tool(
    description="Search Danıştay decisions with keyword logic (AND/OR/NOT operators)",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": True,
        "idempotentHint": True
    }
)
async def search_danistay_by_keyword(
    andKelimeler: List[str] = Field(default_factory=list, description="Keywords for AND logic, e.g., ['word1', 'word2']"),
    orKelimeler: List[str] = Field(default_factory=list, description="Keywords for OR logic."),
    notAndKelimeler: List[str] = Field(default_factory=list, description="Keywords for NOT AND logic."),
    notOrKelimeler: List[str] = Field(default_factory=list, description="Keywords for NOT OR logic."),
    pageNumber: int = Field(1, ge=1, description="Page number."),
    # pageSize: int = Field(10, ge=1, le=10, description="Results per page.")
) -> CompactDanistaySearchResult:
    # Search Danıştay decisions with keyword logic.
    
    pageSize = 10  # Default value
    
    search_query = DanistayKeywordSearchRequest(
        andKelimeler=andKelimeler,
        orKelimeler=orKelimeler,
        notAndKelimeler=notAndKelimeler,
        notOrKelimeler=notOrKelimeler,
        pageNumber=pageNumber,
        pageSize=pageSize
    )
    
    logger.info(f"Tool 'search_danistay_by_keyword' called.")
    try:
        api_response = await danistay_client_instance.search_keyword_decisions(search_query)
        if api_response.data:
            return CompactDanistaySearchResult(
                decisions=api_response.data.data,
                total_records=api_response.data.recordsTotal,
                requested_page=search_query.pageNumber,
                page_size=search_query.pageSize)
        logger.warning("API response for Danistay keyword search did not contain expected data structure.")
        return CompactDanistaySearchResult(decisions=[], total_records=0, requested_page=search_query.pageNumber, page_size=search_query.pageSize)
    except Exception as e:
        logger.exception(f"Error in tool 'search_danistay_by_keyword'.")
        raise

@app.tool(
    description="Search Danıştay decisions with detailed criteria (chamber selection, case numbers)",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": True,
        "idempotentHint": True
    }
)
async def search_danistay_detailed(
    daire: str = Field("", description="Chamber/Department name (e.g., '1. Daire')."),
    esasYil: str = Field("", description="Case year for 'Esas No'."),
    esasIlkSiraNo: str = Field("", description="Starting sequence for 'Esas No'."),
    esasSonSiraNo: str = Field("", description="Ending sequence for 'Esas No'."),
    kararYil: str = Field("", description="Decision year for 'Karar No'."),
    kararIlkSiraNo: str = Field("", description="Starting sequence for 'Karar No'."),
    kararSonSiraNo: str = Field("", description="Ending sequence for 'Karar No'."),
    baslangicTarihi: str = Field("", description="Start date for decision (DD.MM.YYYY)."),
    bitisTarihi: str = Field("", description="End date for decision (DD.MM.YYYY)."),
    mevzuatNumarasi: str = Field("", description="Legislation number."),
    mevzuatAdi: str = Field("", description="Legislation name."),
    madde: str = Field("", description="Article number."),
    pageNumber: int = Field(1, ge=1, description="Page number."),
    # pageSize: int = Field(10, ge=1, le=10, description="Results per page.")
) -> CompactDanistaySearchResult:
    # Search Danıştay decisions with detailed filtering.
    
    pageSize = 10  # Default value
    
    search_query = DanistayDetailedSearchRequest(
        daire=daire,
        esasYil=esasYil,
        esasIlkSiraNo=esasIlkSiraNo,
        esasSonSiraNo=esasSonSiraNo,
        kararYil=kararYil,
        kararIlkSiraNo=kararIlkSiraNo,
        kararSonSiraNo=kararSonSiraNo,
        baslangicTarihi=baslangicTarihi,
        bitisTarihi=bitisTarihi,
        mevzuatNumarasi=mevzuatNumarasi,
        mevzuatAdi=mevzuatAdi,
        madde=madde,
        siralama="3",
        siralamaDirection="desc",
        pageNumber=pageNumber,
        pageSize=pageSize
    )
    
    logger.info(f"Tool 'search_danistay_detailed' called.")
    try:
        api_response = await danistay_client_instance.search_detailed_decisions(search_query)
        if api_response.data:
            return CompactDanistaySearchResult(
                decisions=api_response.data.data,
                total_records=api_response.data.recordsTotal,
                requested_page=search_query.pageNumber,
                page_size=search_query.pageSize)
        logger.warning("API response for Danistay detailed search did not contain expected data structure.")
        return CompactDanistaySearchResult(decisions=[], total_records=0, requested_page=search_query.pageNumber, page_size=search_query.pageSize)
    except Exception as e:
        logger.exception(f"Error in tool 'search_danistay_detailed'.")
        raise

@app.tool(
    description="Get Danıştay decision text in Markdown format",
    annotations={
        "readOnlyHint": True,
        "idempotentHint": True
    }
)
async def get_danistay_document_markdown(id: str) -> DanistayDocumentMarkdown:
    # Get Danıştay decision text as Markdown. Use ID from search results.
    logger.info(f"Tool 'get_danistay_document_markdown' called for ID: {id}")
    if not id or not id.strip(): raise ValueError("Document ID must be a non-empty string for Danıştay.")
    try:
        return await danistay_client_instance.get_decision_document_as_markdown(id)
    except Exception as e:
        logger.exception(f"Error in tool 'get_danistay_document_markdown'.")
        raise
"""

# --- MCP Tools for Emsal ---
@app.tool(
    description="Search Emsal precedent decisions with detailed criteria",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": True,
        "idempotentHint": True
    }
)
async def search_emsal_detailed_decisions(
    keyword: str = Field("", description="Keyword to search."),
    selected_bam_civil_court: str = Field("", description="Selected BAM Civil Court."),
    selected_civil_court: str = Field("", description="Selected Civil Court."),
    selected_regional_civil_chambers: List[str] = Field(default_factory=list, description="Selected Regional Civil Chambers."),
    case_year_esas: str = Field("", description="Case year for 'Esas No'."),
    case_start_seq_esas: str = Field("", description="Starting sequence for 'Esas No'."),
    case_end_seq_esas: str = Field("", description="Ending sequence for 'Esas No'."),
    decision_year_karar: str = Field("", description="Decision year for 'Karar No'."),
    decision_start_seq_karar: str = Field("", description="Starting sequence for 'Karar No'."),
    decision_end_seq_karar: str = Field("", description="Ending sequence for 'Karar No'."),
    start_date: str = Field("", description="Start date for decision (DD.MM.YYYY)."),
    end_date: str = Field("", description="End date for decision (DD.MM.YYYY)."),
    sort_criteria: str = Field("1", description="Sorting criteria (e.g., 1: Esas No)."),
    sort_direction: str = Field("desc", description="Sorting direction ('asc' or 'desc')."),
    page_number: int = Field(1, ge=1, description="Page number (accepts int)."),
    # page_size: int = Field(10, ge=1, le=10, description="Results per page.")
) -> Dict[str, Any]:
    """Search Emsal precedent decisions with detailed criteria."""
    
    page_size = 10  # Default value
    
    search_query = EmsalSearchRequest(
        keyword=keyword,
        selected_bam_civil_court=selected_bam_civil_court,
        selected_civil_court=selected_civil_court,
        selected_regional_civil_chambers=selected_regional_civil_chambers,
        case_year_esas=case_year_esas,
        case_start_seq_esas=case_start_seq_esas,
        case_end_seq_esas=case_end_seq_esas,
        decision_year_karar=decision_year_karar,
        decision_start_seq_karar=decision_start_seq_karar,
        decision_end_seq_karar=decision_end_seq_karar,
        start_date=start_date,
        end_date=end_date,
        sort_criteria=sort_criteria,
        sort_direction=sort_direction,
        page_number=page_number,
        page_size=page_size
    )
    
    logger.info(f"Tool 'search_emsal_detailed_decisions' called.")
    try:
        api_response = await emsal_client_instance.search_detailed_decisions(search_query)
        if api_response.data:
            return CompactEmsalSearchResult(
                decisions=api_response.data.data,
                total_records=api_response.data.recordsTotal if api_response.data.recordsTotal is not None else 0,
                requested_page=search_query.page_number,
                page_size=search_query.page_size
            ).model_dump()
        logger.warning("API response for Emsal search did not contain expected data structure.")
        return CompactEmsalSearchResult(decisions=[], total_records=0, requested_page=search_query.page_number, page_size=search_query.page_size).model_dump()
    except Exception as e:
        logger.exception(f"Error in tool 'search_emsal_detailed_decisions'.")
        raise

@app.tool(
    description="Get Emsal precedent decision text in Markdown format",
    annotations={
        "readOnlyHint": True,
        "idempotentHint": True
    }
)
async def get_emsal_document_markdown(id: str) -> Dict[str, Any]:
    """Get document as Markdown."""
    logger.info(f"Tool 'get_emsal_document_markdown' called for ID: {id}")
    if not id or not id.strip(): raise ValueError("Document ID required for Emsal.")
    try:
        result = await emsal_client_instance.get_decision_document_as_markdown(id)
        return result.model_dump()
    except Exception as e:
        logger.exception(f"Error in tool 'get_emsal_document_markdown'.")
        raise

# --- MCP Tools for Uyusmazlik ---
@app.tool(
    description="Search Uyuşmazlık Mahkemesi decisions for jurisdictional disputes",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": True,
        "idempotentHint": True
    }
)
async def search_uyusmazlik_decisions(
    icerik: str = Field("", description="Keyword or content for main text search."),
    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."),
    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."),
    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."),
    esas_yil: str = Field("", description="Case year ('Esas Yılı')."),
    esas_sayisi: str = Field("", description="Case number ('Esas Sayısı')."),
    karar_yil: str = Field("", description="Decision year ('Karar Yılı')."),
    karar_sayisi: str = Field("", description="Decision number ('Karar Sayısı')."),
    kanun_no: str = Field("", description="Relevant Law Number."),
    karar_date_begin: str = Field("", description="Decision start date (DD.MM.YYYY)."),
    karar_date_end: str = Field("", description="Decision end date (DD.MM.YYYY)."),
    resmi_gazete_sayi: str = Field("", description="Official Gazette number."),
    resmi_gazete_date: str = Field("", description="Official Gazette date (DD.MM.YYYY)."),
    tumce: str = Field("", description="Exact phrase search."),
    wild_card: str = Field("", description="Search for phrase and its inflections."),
    hepsi: str = Field("", description="Search for texts containing all specified words."),
    herhangi_birisi: str = Field("", description="Search for texts containing any of the specified words."),
    not_hepsi: str = Field("", description="Exclude texts containing these specified words.")
) -> Dict[str, Any]:
    """Search Court of Jurisdictional Disputes decisions."""
    
    # Convert string literals to enums
    # Map "ALL" to TUMU for backward compatibility
    if bolum == "ALL":
        bolum_enum = UyusmazlikBolumEnum.TUMU
    else:
        bolum_enum = UyusmazlikBolumEnum(bolum) if bolum else UyusmazlikBolumEnum.TUMU
    
    if uyusmazlik_turu == "ALL":
        uyusmazlik_turu_enum = UyusmazlikTuruEnum.TUMU
    else:
        uyusmazlik_turu_enum = UyusmazlikTuruEnum(uyusmazlik_turu) if uyusmazlik_turu else UyusmazlikTuruEnum.TUMU
    karar_sonuclari_enums = [UyusmazlikKararSonucuEnum(ks) for ks in karar_sonuclari]
    
    search_params = UyusmazlikSearchRequest(
        icerik=icerik,
        bolum=bolum_enum,
        uyusmazlik_turu=uyusmazlik_turu_enum,
        karar_sonuclari=karar_sonuclari_enums,
        esas_yil=esas_yil,
        esas_sayisi=esas_sayisi,
        karar_yil=karar_yil,
        karar_sayisi=karar_sayisi,
        kanun_no=kanun_no,
        karar_date_begin=karar_date_begin,
        karar_date_end=karar_date_end,
        resmi_gazete_sayi=resmi_gazete_sayi,
        resmi_gazete_date=resmi_gazete_date,
        tumce=tumce,
        wild_card=wild_card,
        hepsi=hepsi,
        herhangi_birisi=herhangi_birisi,
        not_hepsi=not_hepsi
    )
    
    logger.info(f"Tool 'search_uyusmazlik_decisions' called.")
    try:
        result = await uyusmazlik_client_instance.search_decisions(search_params)
        return result.model_dump()
    except Exception as e:
        logger.exception(f"Error in tool 'search_uyusmazlik_decisions'.")
        raise

@app.tool(
    description="Get Uyuşmazlık Mahkemesi decision text from URL in Markdown format",
    annotations={
        "readOnlyHint": True,
        "idempotentHint": True
    }
)
async def get_uyusmazlik_document_markdown_from_url(
    document_url: str = Field(..., description="Full URL to the Uyuşmazlık Mahkemesi decision document from search results")
) -> Dict[str, Any]:
    """Get Uyuşmazlık Mahkemesi decision as Markdown."""
    logger.info(f"Tool 'get_uyusmazlik_document_markdown_from_url' called for URL: {str(document_url)}")
    if not document_url:
        raise ValueError("Document URL (document_url) is required for Uyuşmazlık document retrieval.")
    try:
        result = await uyusmazlik_client_instance.get_decision_document_as_markdown(str(document_url))
        return result.model_dump()
    except Exception as e:
        logger.exception(f"Error in tool 'get_uyusmazlik_document_markdown_from_url'.")
        raise

# --- DEACTIVATED: MCP Tools for Anayasa Mahkemesi (Individual Tools) ---
# Use search_anayasa_unified and get_anayasa_document_unified instead

"""
@app.tool(
    description="Search Constitutional Court norm control decisions with comprehensive filtering",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": True,
        "idempotentHint": True
    }
)
# DEACTIVATED TOOL - Use search_anayasa_unified instead
# @app.tool(
#     description="DEACTIVATED - Use search_anayasa_unified instead",
#     annotations={"readOnlyHint": True, "openWorldHint": False, "idempotentHint": True}
# )
# async def search_anayasa_norm_denetimi_decisions(...) -> AnayasaSearchResult:
#     raise ValueError("This tool is deactivated. Use search_anayasa_unified instead.")

# DEACTIVATED TOOL - Use get_anayasa_document_unified instead
# @app.tool(...)
# async def get_anayasa_norm_denetimi_document_markdown(...) -> AnayasaDocumentMarkdown:
#     raise ValueError("This tool is deactivated. Use get_anayasa_document_unified instead.")

# DEACTIVATED TOOL - Use search_anayasa_unified instead
# @app.tool(...)
# async def search_anayasa_bireysel_basvuru_report(...) -> AnayasaBireyselReportSearchResult:
#     raise ValueError("This tool is deactivated. Use search_anayasa_unified instead.")

# DEACTIVATED TOOL - Use get_anayasa_document_unified instead
# @app.tool(...)
# async def get_anayasa_bireysel_basvuru_document_markdown(...) -> AnayasaBireyselBasvuruDocumentMarkdown:
#     raise ValueError("This tool is deactivated. Use get_anayasa_document_unified instead.")
"""

# --- Unified MCP Tools for Anayasa Mahkemesi ---
@app.tool(
    description="Unified search for Constitutional Court decisions: both norm control (normkararlarbilgibankasi) and individual applications (kararlarbilgibankasi) in one tool",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": True,
        "idempotentHint": True
    }
)
async def search_anayasa_unified(
    decision_type: Literal["norm_denetimi", "bireysel_basvuru"] = Field(..., description="Decision type: norm_denetimi (norm control) or bireysel_basvuru (individual applications)"),
    keywords: List[str] = Field(default_factory=list, description="Keywords to search for (common parameter)"),
    page_to_fetch: int = Field(1, ge=1, le=100, description="Page number to fetch (1-100)"),
    # results_per_page: int = Field(10, ge=1, le=100, description="Results per page (1-100)"),
    
    # Norm Denetimi specific parameters (ignored for bireysel_basvuru)
    keywords_all: List[str] = Field(default_factory=list, description="All keywords must be present (norm_denetimi only)"),
    keywords_any: List[str] = Field(default_factory=list, description="Any of these keywords (norm_denetimi only)"),
    decision_type_norm: Literal["ALL", "1", "2", "3"] = Field("ALL", description="Decision type for norm denetimi"),
    application_date_start: str = Field("", description="Application start date (norm_denetimi only)"),
    application_date_end: str = Field("", description="Application end date (norm_denetimi only)"),
    
    # Bireysel Başvuru specific parameters (ignored for norm_denetimi)
    decision_start_date: str = Field("", description="Decision start date (bireysel_basvuru only)"),
    decision_end_date: str = Field("", description="Decision end date (bireysel_basvuru only)"),
    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)"),
    subject_category: str = Field("", description="Subject category (bireysel_basvuru only)")
) -> str:
    logger.info(f"Tool 'search_anayasa_unified' called for decision_type: {decision_type}")
    
    results_per_page = 10  # Default value
    
    try:
        request = AnayasaUnifiedSearchRequest(
            decision_type=decision_type,
            keywords=keywords,
            page_to_fetch=page_to_fetch,
            results_per_page=results_per_page,
            keywords_all=keywords_all,
            keywords_any=keywords_any,
            decision_type_norm=decision_type_norm,
            application_date_start=application_date_start,
            application_date_end=application_date_end,
            decision_start_date=decision_start_date,
            decision_end_date=decision_end_date,
            norm_type=norm_type,
            subject_category=subject_category
        )
        
        result = await anayasa_unified_client_instance.search_unified(request)
        return json.dumps(result.model_dump(), ensure_ascii=False, indent=2)
        
    except Exception as e:
        logger.exception(f"Error in tool 'search_anayasa_unified'.")
        raise

@app.tool(
    description="Unified document retrieval for Constitutional Court decisions: auto-detects norm control vs individual applications based on URL",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": False,
        "idempotentHint": True
    }
)
async def get_anayasa_document_unified(
    document_url: str = Field(..., description="Document URL from search results"),
    page_number: int = Field(1, ge=1, description="Page number for paginated content (1-indexed)")
) -> str:
    logger.info(f"Tool 'get_anayasa_document_unified' called for URL: {document_url}, Page: {page_number}")
    
    try:
        result = await anayasa_unified_client_instance.get_document_unified(document_url, page_number)
        return json.dumps(result.model_dump(mode='json'), ensure_ascii=False, indent=2)
        
    except Exception as e:
        logger.exception(f"Error in tool 'get_anayasa_document_unified'.")
        raise

# --- MCP Tools for KIK v2 (Kamu İhale Kurulu - New API) ---
@app.tool(
    description="Search Public Procurement Authority (KİK) decisions using the new v2 API with JSON responses. Supports all three decision types: disputes (uyusmazlik), regulatory (duzenleyici), and court decisions (mahkeme)",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": True,
        "idempotentHint": True
    }
)
async def search_kik_v2_decisions(
    decision_type: str = Field("uyusmazlik", description="Decision type: 'uyusmazlik' (disputes), 'duzenleyici' (regulatory), or 'mahkeme' (court decisions)"),
    karar_metni: str = Field("", description="Decision text search query"),
    karar_no: str = Field("", description="Decision number (e.g., '2025/UH.II-1801')"),
    basvuran: str = Field("", description="Applicant name"),
    idare_adi: str = Field("", description="Administration/procuring entity name"),
    baslangic_tarihi: str = Field("", description="Start date (YYYY-MM-DD format, e.g., '2025-01-01')"),
    bitis_tarihi: str = Field("", description="End date (YYYY-MM-DD format, e.g., '2025-12-31')")
) -> dict:
    """Search Public Procurement Authority (KİK) decisions using the new v2 API.
    
    This tool supports all three KİK decision types:
    - uyusmazlik: Disputes and conflicts in public procurement
    - duzenleyici: Regulatory decisions and guidelines  
    - mahkeme: Court decisions and legal interpretations
    
    Each decision type uses its respective endpoint (GetKurulKararlari, GetKurulKararlariDk, GetKurulKararlariMk)
    and returns results with the decision_type field populated for identification.
    """
    
    logger.info(f"Tool 'search_kik_v2_decisions' called with decision_type='{decision_type}', karar_metni='{karar_metni}', karar_no='{karar_no}'")
    
    try:
        # Validate and convert decision type
        try:
            kik_decision_type = KikV2DecisionType(decision_type)
        except ValueError:
            return {
                "decisions": [],
                "total_records": 0,
                "page": 1,
                "error_code": "INVALID_DECISION_TYPE",
                "error_message": f"Invalid decision type: {decision_type}. Valid options: uyusmazlik, duzenleyici, mahkeme"
            }
        
        api_response = await kik_v2_client_instance.search_decisions(
            decision_type=kik_decision_type,
            karar_metni=karar_metni,
            karar_no=karar_no,
            basvuran=basvuran,
            idare_adi=idare_adi,
            baslangic_tarihi=baslangic_tarihi,
            bitis_tarihi=bitis_tarihi
        )
        
        # Convert to dictionary for MCP tool response
        result = {
            "decisions": [decision.model_dump() for decision in api_response.decisions],
            "total_records": api_response.total_records,
            "page": api_response.page,
            "error_code": api_response.error_code,
            "error_message": api_response.error_message
        }
        
        logger.info(f"KİK v2 {decision_type} search completed. Found {len(api_response.decisions)} decisions")
        return result
        
    except Exception as e:
        logger.exception(f"Error in KİK v2 {decision_type} search tool 'search_kik_v2_decisions'.")
        return {
            "decisions": [],
            "total_records": 0,
            "page": 1,
            "error_code": "TOOL_ERROR",
            "error_message": str(e)
        }

@app.tool(
    description="Get Public Procurement Authority (KİK) decision document using the new v2 API (placeholder - full implementation pending)",
    annotations={
        "readOnlyHint": True,
        "idempotentHint": True
    }
)
async def get_kik_v2_document_markdown(
    document_id: str = Field(..., description="Document ID (gundemMaddesiId from search results)")
) -> dict:
    """Get KİK decision document using the v2 API."""
    
    logger.info(f"Tool 'get_kik_v2_document_markdown' called for document ID: {document_id}")
    
    if not document_id or not document_id.strip():
        return {
            "document_id": document_id,
            "kararNo": "",
            "markdown_content": "",
            "source_url": "",
            "error_message": "Document ID is required and must be a non-empty string"
        }
    
    try:
        api_response = await kik_v2_client_instance.get_document_markdown(document_id)
        
        return {
            "document_id": api_response.document_id,
            "kararNo": api_response.kararNo,
            "markdown_content": api_response.markdown_content,
            "source_url": api_response.source_url,
            "error_message": api_response.error_message
        }
        
    except Exception as e:
        logger.exception(f"Error in KİK v2 document retrieval tool for document ID: {document_id}")
        return {
            "document_id": document_id,
            "kararNo": "",
            "markdown_content": "",
            "source_url": "",
            "error_message": f"Tool-level error during document retrieval: {str(e)}"
        }
@app.tool(
    description="Search Competition Authority (Rekabet Kurumu) decisions for competition law and antitrust",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": True,
        "idempotentHint": True
    }
)
async def search_rekabet_kurumu_decisions(
    sayfaAdi: str = Field("", description="Search in decision title (Başlık)."),
    YayinlanmaTarihi: str = Field("", description="Publication date (Yayım Tarihi), e.g., DD.MM.YYYY."),
    PdfText: str = Field(
        "",
        description='Search in decision text. Use "\\"kesin cümle\\"" for precise matching.'
    ),
    KararTuru: Literal[ 
        "ALL", 
        "Birleşme ve Devralma",
        "Diğer",
        "Menfi Tespit ve Muafiyet",
        "Özelleştirme",
        "Rekabet İhlali"
    ] = Field("ALL", description="Parameter description"),
    KararSayisi: str = Field("", description="Decision number (Karar Sayısı)."),
    KararTarihi: str = Field("", description="Decision date (Karar Tarihi), e.g., DD.MM.YYYY."),
    page: int = Field(1, ge=1, description="Page number to fetch for the results list.")
) -> Dict[str, Any]:
    """Search Competition Authority decisions."""
    
    karar_turu_guid_enum = KARAR_TURU_ADI_TO_GUID_ENUM_MAP.get(KararTuru)

    try:
        if karar_turu_guid_enum is None: 
            logger.warning(f"Invalid user-provided KararTuru: '{KararTuru}'. Defaulting to TUMU (all).")
            karar_turu_guid_enum = RekabetKararTuruGuidEnum.TUMU
    except Exception as e_map: 
        logger.error(f"Error mapping KararTuru '{KararTuru}': {e_map}. Defaulting to TUMU.")
        karar_turu_guid_enum = RekabetKararTuruGuidEnum.TUMU

    search_query = RekabetKurumuSearchRequest(
        sayfaAdi=sayfaAdi,
        YayinlanmaTarihi=YayinlanmaTarihi,
        PdfText=PdfText,
        KararTuruID=karar_turu_guid_enum, 
        KararSayisi=KararSayisi,
        KararTarihi=KararTarihi,
        page=page
    )
    logger.info(f"Tool 'search_rekabet_kurumu_decisions' called. Query: {search_query.model_dump_json(exclude_none=True, indent=2)}")
    try:
       
        result = await rekabet_client_instance.search_decisions(search_query)
        return result.model_dump()
    except Exception as e:
        logger.exception("Error in tool 'search_rekabet_kurumu_decisions'.")
        return RekabetSearchResult(decisions=[], retrieved_page_number=page, total_records_found=0, total_pages=0).model_dump()

@app.tool(
    description="Get Competition Authority decision text in paginated Markdown format",
    annotations={
        "readOnlyHint": True,
        "idempotentHint": True
    }
)
async def get_rekabet_kurumu_document(
    karar_id: str = Field(..., description="GUID (kararId) of the Rekabet Kurumu decision. This ID is obtained from search results."),
    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.")
) -> Dict[str, Any]:
    """Get Competition Authority decision as paginated Markdown."""
    logger.info(f"Tool 'get_rekabet_kurumu_document' called. Karar ID: {karar_id}, Markdown Page: {page_number}")
    
    current_page_to_fetch = page_number if page_number >= 1 else 1
    
    try:
        result = await rekabet_client_instance.get_decision_document(karar_id, page_number=current_page_to_fetch)
        return result.model_dump()
    except Exception as e:
        logger.exception(f"Error in tool 'get_rekabet_kurumu_document'. Karar ID: {karar_id}")
        raise 

# --- MCP Tools for Bedesten (Unified Search Across All Courts) ---
@app.tool(
    description="Search multiple Turkish courts (Yargıtay, Danıştay, Local Courts, Appeals Courts, KYB)",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": True,
        "idempotentHint": True
    }
)
async def search_bedesten_unified(
    ctx: Context,
    phrase: str = Field(..., description="""Search query in Turkish. SUPPORTED OPERATORS:
• Simple: "mülkiyet hakkı" (finds both words)
• Exact phrase: "\"mülkiyet hakkı\"" (finds exact phrase)  
• Required term: "+mülkiyet hakkı" (must contain mülkiyet)
• Exclude term: "mülkiyet -kira" (contains mülkiyet but not kira)
• Boolean AND: "mülkiyet AND hak" (both terms required)
• Boolean OR: "mülkiyet OR tapu" (either term acceptable)
• Boolean NOT: "mülkiyet NOT satış" (contains mülkiyet but not satış)
NOTE: Wildcards (*,?), regex patterns (/regex/), fuzzy search (~), and proximity search are NOT supported.
For best results, use exact phrases with quotes for legal terms."""),
    court_types: List[BedestenCourtTypeEnum] = Field(
        default=["YARGITAYKARARI", "DANISTAYKARAR"], 
        description="Court types: YARGITAYKARARI, DANISTAYKARAR, YERELHUKUK, ISTINAFHUKUK, KYB"
    ),
    # pageSize: int = Field(10, ge=1, le=10, description="Results per page (1-10)"),
    pageNumber: int = Field(1, ge=1, description="Page number"),
    birimAdi: BirimAdiEnum = Field("ALL", description="""
        Chamber filter (optional). Abbreviated values with Turkish names:
        • 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)
        • 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)
        """),
    kararTarihiStart: str = Field("", description="Start date (ISO 8601 format)"),
    kararTarihiEnd: str = Field("", description="End date (ISO 8601 format)")
) -> dict:
    """Search Turkish legal databases via unified Bedesten API."""
    
    # Get Bearer token information for access control and logging
    try:
        access_token: AccessToken = get_access_token()
        user_id = access_token.client_id
        user_scopes = access_token.scopes
        
        # Check for required scopes - DISABLED: Already handled by Bearer auth provider
        # if "yargi.read" not in user_scopes and "yargi.search" not in user_scopes:
        #     raise ToolError(f"Insufficient permissions: 'yargi.read' or 'yargi.search' scope required. Current scopes: {user_scopes}")
        
        logger.info(f"Tool 'search_bedesten_unified' called by user '{user_id}' with scopes {user_scopes}")
        
    except Exception as e:
        # Development mode fallback - allow access without strict token validation
        logger.warning(f"Bearer token validation failed, using development mode: {str(e)}")
        user_id = "dev-user"
        user_scopes = ["yargi.read", "yargi.search"]
    
    pageSize = 10  # Default value
    
    # Convert date formats if provided
    # Accept formats: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS.000Z
    if kararTarihiStart and not kararTarihiStart.endswith('Z'):
        # Convert simple date format to ISO 8601 with timezone
        if 'T' not in kararTarihiStart:
            kararTarihiStart = f"{kararTarihiStart}T00:00:00.000Z"
    
    if kararTarihiEnd and not kararTarihiEnd.endswith('Z'):
        # Convert simple date format to ISO 8601 with timezone
        if 'T' not in kararTarihiEnd:
            kararTarihiEnd = f"{kararTarihiEnd}T23:59:59.999Z"
    
    search_data = BedestenSearchData(
        pageSize=pageSize,
        pageNumber=pageNumber,
        itemTypeList=court_types,
        phrase=phrase,
        birimAdi=birimAdi,
        kararTarihiStart=kararTarihiStart,
        kararTarihiEnd=kararTarihiEnd
    )
    
    search_request = BedestenSearchRequest(data=search_data)
    
    logger.info(f"User '{user_id}' searching bedesten: phrase='{phrase}', court_types={court_types}, birimAdi='{birimAdi}', page={pageNumber}")
    
    try:
        response = await bedesten_client_instance.search_documents(search_request)
        
        if response.data is None:
            return {
                "decisions": [],
                "total_records": 0,
                "requested_page": pageNumber,
                "page_size": pageSize,
                "searched_courts": court_types,
                "error": "No data returned from Bedesten API"
            }
        
        # Add null safety checks for response.data fields
        emsal_karar_list = response.data.emsalKararList if hasattr(response.data, 'emsalKararList') and response.data.emsalKararList is not None else []
        total_records = response.data.total if hasattr(response.data, 'total') and response.data.total is not None else 0
        
        return {
            "decisions": [d.model_dump() for d in emsal_karar_list],
            "total_records": total_records,
            "requested_page": pageNumber,
            "page_size": pageSize,
            "searched_courts": court_types
        }
    except Exception as e:
        logger.exception("Error in tool 'search_bedesten_unified'")
        raise

@app.tool(
    description="Get legal decision document from Bedesten API in Markdown format",
    annotations={
        "readOnlyHint": True,
        "idempotentHint": True
    }
)
async def get_bedesten_document_markdown(
    documentId: str = Field(..., description="Document ID from Bedesten search results")
) -> BedestenDocumentMarkdown:
    """Get legal decision document as Markdown from Bedesten API."""
    logger.info(f"Tool 'get_bedesten_document_markdown' called for ID: {documentId}")
    
    if not documentId or not documentId.strip():
        raise ValueError("Document ID must be a non-empty string.")
    
    try:
        return await bedesten_client_instance.get_document_as_markdown(documentId)
    except Exception as e:
        logger.exception("Error in tool 'get_kyb_bedesten_document_markdown'")
        raise

# --- MCP Tools for Sayıştay (Turkish Court of Accounts) ---

# DEACTIVATED TOOL - Use search_sayistay_unified instead
# @app.tool(
#     description="Search Sayıştay Genel Kurul decisions for audit and accountability regulations",
#     annotations={
#         "readOnlyHint": True,
#         "openWorldHint": True,
#         "idempotentHint": True
#     }
# )
# async def search_sayistay_genel_kurul(
#     karar_no: str = Field("", description="Decision number to search for (e.g., '5415')"),
#     karar_ek: str = Field("", description="Decision appendix number (max 99, e.g., '1')"),
#     karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY)"),
#     karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY)"),
#     karar_tamami: str = Field("", description="Full text search"),
#     start: int = Field(0, description="Starting record for pagination (0-based)"),
#     length: int = Field(10, description="Number of records per page (1-100)")
# ) -> GenelKurulSearchResponse:
#     """Search Sayıştay General Assembly decisions."""
#     raise ValueError("This tool is deactivated. Use search_sayistay_unified instead.")

# DEACTIVATED TOOL - Use search_sayistay_unified instead
# @app.tool(
#     description="Search Sayıştay Temyiz Kurulu decisions with chamber filtering and comprehensive criteria",
#     annotations={
#         "readOnlyHint": True,
#         "openWorldHint": True,
#         "idempotentHint": True
#     }
# )
# async def search_sayistay_temyiz_kurulu(
#     ilam_dairesi: DaireEnum = Field("ALL", description="Audit chamber selection"),
#     yili: str = Field("", description="Year (YYYY)"),
#     karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY)"),
#     karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY)"),
#     kamu_idaresi_turu: KamuIdaresiTuruEnum = Field("ALL", description="Public admin type"),
#     ilam_no: str = Field("", description="Audit report number (İlam No, max 50 chars)"),
#     dosya_no: str = Field("", description="File number for the case"),
#     temyiz_tutanak_no: str = Field("", description="Appeals board meeting minutes number"),
#     temyiz_karar: str = Field("", description="Appeals decision text"),
#     web_karar_konusu: WebKararKonusuEnum = Field("ALL", description="Decision subject"),
#     start: int = Field(0, description="Starting record for pagination (0-based)"),
#     length: int = Field(10, description="Number of records per page (1-100)")
# ) -> TemyizKuruluSearchResponse:
#     """Search Sayıştay Appeals Board decisions."""
#     raise ValueError("This tool is deactivated. Use search_sayistay_unified instead.")

# DEACTIVATED TOOL - Use search_sayistay_unified instead
# @app.tool(
#     description="Search Sayıştay Daire decisions with chamber filtering and subject categorization",
#     annotations={
#         "readOnlyHint": True,
#         "openWorldHint": True,
#         "idempotentHint": True
#     }
# )
# async def search_sayistay_daire(
#     yargilama_dairesi: DaireEnum = Field("ALL", description="Chamber selection"),
#     karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY)"),
#     karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY)"),
#     ilam_no: str = Field("", description="Audit report number (İlam No, max 50 chars)"),
#     kamu_idaresi_turu: KamuIdaresiTuruEnum = Field("ALL", description="Public admin type"),
#     hesap_yili: str = Field("", description="Fiscal year"),
#     web_karar_konusu: WebKararKonusuEnum = Field("ALL", description="Decision subject"),
#     web_karar_metni: str = Field("", description="Decision text search"),
#     start: int = Field(0, description="Starting record for pagination (0-based)"),
#     length: int = Field(10, description="Number of records per page (1-100)")
# ) -> DaireSearchResponse:
#     """Search Sayıştay Chamber decisions."""
#     raise ValueError("This tool is deactivated. Use search_sayistay_unified instead.")

# DEACTIVATED TOOL - Use get_sayistay_document_unified instead
# @app.tool(
#     description="Get Sayıştay Genel Kurul decision document in Markdown format",
#     annotations={
#         "readOnlyHint": True,
#         "openWorldHint": False,
#         "idempotentHint": True
#     }
# )
# async def get_sayistay_genel_kurul_document_markdown(
#     decision_id: str = Field(..., description="Decision ID from search_sayistay_genel_kurul results")
# ) -> SayistayDocumentMarkdown:
#     """Get Sayıştay General Assembly decision as Markdown."""
#     raise ValueError("This tool is deactivated. Use get_sayistay_document_unified instead.")

# DEACTIVATED TOOL - Use get_sayistay_document_unified instead
# @app.tool(
#     description="Get Sayıştay Temyiz Kurulu decision document in Markdown format",
#     annotations={
#         "readOnlyHint": True,
#         "openWorldHint": False,
#         "idempotentHint": True
#     }
# )
# async def get_sayistay_temyiz_kurulu_document_markdown(
#     decision_id: str = Field(..., description="Decision ID from search_sayistay_temyiz_kurulu results")
# ) -> SayistayDocumentMarkdown:
#     """Get Sayıştay Appeals Board decision as Markdown."""
#     raise ValueError("This tool is deactivated. Use get_sayistay_document_unified instead.")

# DEACTIVATED TOOL - Use get_sayistay_document_unified instead
# @app.tool(
#     description="Get Sayıştay Daire decision document in Markdown format",
#     annotations={
#         "readOnlyHint": True,
#         "openWorldHint": False,
#         "idempotentHint": True
#     }
# )
# async def get_sayistay_daire_document_markdown(
#     decision_id: str = Field(..., description="Decision ID from search_sayistay_daire results")
# ) -> SayistayDocumentMarkdown:
#     """Get Sayıştay Chamber decision as Markdown."""
#     raise ValueError("This tool is deactivated. Use get_sayistay_document_unified instead.")

# --- UNIFIED MCP Tools for Sayıştay (Turkish Court of Accounts) ---

@app.tool(
    description="Search Sayıştay decisions unified across all three decision types (Genel Kurul, Temyiz Kurulu, Daire) with comprehensive filtering",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": True,
        "idempotentHint": True
    }
)
async def search_sayistay_unified(
    decision_type: Literal["genel_kurul", "temyiz_kurulu", "daire"] = Field(..., description="Decision type: genel_kurul, temyiz_kurulu, or daire"),
    
    # Common pagination parameters
    start: int = Field(0, ge=0, description="Starting record for pagination (0-based)"),
    length: int = Field(10, ge=1, le=100, description="Number of records per page (1-100)"),
    
    # Common search parameters
    karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY format)"),
    karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY format)"),
    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"),
    ilam_no: str = Field("", description="Audit report number (İlam No, max 50 chars)"),
    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"),
    
    # Genel Kurul specific parameters (ignored for other types)
    karar_no: str = Field("", description="Decision number (genel_kurul only)"),
    karar_ek: str = Field("", description="Decision appendix number (genel_kurul only)"),
    karar_tamami: str = Field("", description="Full text search (genel_kurul only)"),
    
    # Temyiz Kurulu specific parameters (ignored for other types)
    ilam_dairesi: Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8"] = Field("ALL", description="Audit chamber selection (temyiz_kurulu only)"),
    yili: str = Field("", description="Year (YYYY format, temyiz_kurulu only)"),
    dosya_no: str = Field("", description="File number (temyiz_kurulu only)"),
    temyiz_tutanak_no: str = Field("", description="Appeals board meeting minutes number (temyiz_kurulu only)"),
    temyiz_karar: str = Field("", description="Appeals decision text search (temyiz_kurulu only)"),
    
    # Daire specific parameters (ignored for other types)
    yargilama_dairesi: Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8"] = Field("ALL", description="Chamber selection (daire only)"),
    hesap_yili: str = Field("", description="Account year (daire only)"),
    web_karar_metni: str = Field("", description="Decision text search (daire only)")
) -> Dict[str, Any]:
    """Search Sayıştay decisions across all three decision types with unified interface."""
    logger.info(f"Tool 'search_sayistay_unified' called with decision_type={decision_type}")

    try:
        search_request = SayistayUnifiedSearchRequest(
            decision_type=decision_type,
            start=start,
            length=length,
            karar_tarih_baslangic=karar_tarih_baslangic,
            karar_tarih_bitis=karar_tarih_bitis,
            kamu_idaresi_turu=kamu_idaresi_turu,
            ilam_no=ilam_no,
            web_karar_konusu=web_karar_konusu,
            karar_no=karar_no,
            karar_ek=karar_ek,
            karar_tamami=karar_tamami,
            ilam_dairesi=ilam_dairesi,
            yili=yili,
            dosya_no=dosya_no,
            temyiz_tutanak_no=temyiz_tutanak_no,
            temyiz_karar=temyiz_karar,
            yargilama_dairesi=yargilama_dairesi,
            hesap_yili=hesap_yili,
            web_karar_metni=web_karar_metni
        )
        result = await sayistay_unified_client_instance.search_unified(search_request)
        return result.model_dump()
    except Exception as e:
        logger.exception("Error in tool 'search_sayistay_unified'")
        raise

@app.tool(
    description="Get Sayıştay decision document in Markdown format for any decision type",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": False,
        "idempotentHint": True
    }
)
async def get_sayistay_document_unified(
    decision_id: str = Field(..., description="Decision ID from search_sayistay_unified results"),
    decision_type: Literal["genel_kurul", "temyiz_kurulu", "daire"] = Field(..., description="Decision type: genel_kurul, temyiz_kurulu, or daire")
) -> Dict[str, Any]:
    """Get Sayıştay decision document as Markdown for any decision type."""
    logger.info(f"Tool 'get_sayistay_document_unified' called for ID: {decision_id}, type: {decision_type}")

    if not decision_id or not decision_id.strip():
        raise ValueError("Decision ID must be a non-empty string.")

    try:
        result = await sayistay_unified_client_instance.get_document_unified(decision_id, decision_type)
        return result.model_dump()
    except Exception as e:
        logger.exception("Error in tool 'get_sayistay_document_unified'")
        raise

# --- Application Shutdown Handling ---
def perform_cleanup():
    logger.info("MCP Server performing cleanup...")
    try:
        loop = asyncio.get_event_loop_policy().get_event_loop()
        if loop.is_closed(): 
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
    except RuntimeError: 
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
    clients_to_close = [
        globals().get('yargitay_client_instance'),
        globals().get('danistay_client_instance'),
        globals().get('emsal_client_instance'),
        globals().get('uyusmazlik_client_instance'),
        globals().get('anayasa_norm_client_instance'),
        globals().get('anayasa_bireysel_client_instance'),
        globals().get('anayasa_unified_client_instance'),
        globals().get('kik_v2_client_instance'),
        globals().get('rekabet_client_instance'),
        globals().get('bedesten_client_instance'),
        globals().get('sayistay_client_instance'),
        globals().get('sayistay_unified_client_instance'),
        globals().get('kvkk_client_instance'),
        globals().get('bddk_client_instance')
    ]
    async def close_all_clients_async():
        tasks = []
        for client_instance in clients_to_close:
            if client_instance and hasattr(client_instance, 'close_client_session') and callable(client_instance.close_client_session):
                logger.info(f"Scheduling close for client session: {client_instance.__class__.__name__}")
                tasks.append(client_instance.close_client_session())
        if tasks:
            results = await asyncio.gather(*tasks, return_exceptions=True)
            for i, result in enumerate(results):
                if isinstance(result, Exception):
                    client_name = "Unknown Client"
                    if i < len(clients_to_close) and clients_to_close[i] is not None:
                        client_name = clients_to_close[i].__class__.__name__
                    logger.error(f"Error closing client {client_name}: {result}")
    try:
        if loop.is_running(): 
            asyncio.ensure_future(close_all_clients_async(), loop=loop)
            logger.info("Client cleanup tasks scheduled on running event loop.")
        else:
            loop.run_until_complete(close_all_clients_async())
            logger.info("Client cleanup tasks completed via run_until_complete.")
    except Exception as e: 
        logger.error(f"Error during atexit cleanup execution: {e}", exc_info=True)
    logger.info("MCP Server atexit cleanup process finished.")

atexit.register(perform_cleanup)

# --- Health Check Tools ---
@app.tool(
    description="Check if Turkish government legal database servers are operational",
    annotations={
        "readOnlyHint": True,
        "idempotentHint": True
    }
)
async def check_government_servers_health() -> Dict[str, Any]:
    """Check health status of Turkish government legal database servers."""
    logger.info("Health check tool called for government servers")
    
    health_results = {}
    
    # Check Yargıtay server
    try:
        yargitay_payload = {
            "data": {
                "aranan": "karar",
                "arananKelime": "karar", 
                "pageSize": 10,
                "pageNumber": 1
            }
        }
        
        async with httpx.AsyncClient(
            headers={
                "Accept": "*/*",
                "Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7",
                "Connection": "keep-alive",
                "Content-Type": "application/json; charset=UTF-8",
                "Origin": "https://karararama.yargitay.gov.tr",
                "Referer": "https://karararama.yargitay.gov.tr/",
                "Sec-Fetch-Dest": "empty",
                "Sec-Fetch-Mode": "cors", 
                "Sec-Fetch-Site": "same-origin",
                "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
                "X-Requested-With": "XMLHttpRequest"
            },
            timeout=30.0,
            verify=False
        ) as client:
            response = await client.post(
                "https://karararama.yargitay.gov.tr/aramalist",
                json=yargitay_payload
            )
        
        if response.status_code == 200:
            response_data = response.json()
            records_total = response_data.get("data", {}).get("recordsTotal", 0)
            
            if records_total > 0:
                health_results["yargitay"] = {
                    "status": "healthy",
                    "response_time_ms": response.elapsed.total_seconds() * 1000
                }
            else:
                health_results["yargitay"] = {
                    "status": "unhealthy", 
                    "reason": "recordsTotal is 0 or missing",
                    "response_time_ms": response.elapsed.total_seconds() * 1000
                }
        else:
            health_results["yargitay"] = {
                "status": "unhealthy", 
                "reason": f"HTTP {response.status_code}",
                "response_time_ms": response.elapsed.total_seconds() * 1000
            }
        
    except Exception as e:
        health_results["yargitay"] = {
            "status": "unhealthy",
            "reason": f"Connection error: {str(e)}"
        }
    
    # Check Bedesten API server
    try:
        bedesten_payload = {
            "data": {
                "pageSize": 5,
                "pageNumber": 1,
                "itemTypeList": ["YARGITAYKARARI"], 
                "phrase": "karar",
                "sortFields": ["KARAR_TARIHI"],
                "sortDirection": "desc"
            },
            "applicationName": "UyapMevzuat",
            "paging": True
        }
        
        client = get_or_create_health_check_client()
        headers = {
            "Content-Type": "application/json",
            "Accept": "application/json",
            "User-Agent": "Mozilla/5.0 Health Check"
        }
        
        response = await client.post(
            "https://bedesten.adalet.gov.tr/emsal-karar/searchDocuments",
            json=bedesten_payload,
            headers=headers
        )
        
        if response.status_code == 200:
            response_data = response.json()
            logger.debug(f"Bedesten API response: {response_data}")
            if response_data and isinstance(response_data, dict):
                data_section = response_data.get("data")
                if data_section and isinstance(data_section, dict):
                    total_found = data_section.get("total", 0)
                else:
                    total_found = 0
            else:
                total_found = 0
            
            if total_found > 0:
                health_results["bedesten"] = {
                    "status": "healthy", 
                    "response_time_ms": response.elapsed.total_seconds() * 1000
                }
            else:
                health_results["bedesten"] = {
                    "status": "unhealthy",
                    "reason": "total is 0 or missing in data field",
                    "response_time_ms": response.elapsed.total_seconds() * 1000
                }
        else:
            health_results["bedesten"] = {
                "status": "unhealthy",
                "reason": f"HTTP {response.status_code}",
                "response_time_ms": response.elapsed.total_seconds() * 1000
            }
        
    except Exception as e:
        health_results["bedesten"] = {
            "status": "unhealthy", 
            "reason": f"Connection error: {str(e)}"
        }
    
    # Overall health assessment
    healthy_servers = sum(1 for server in health_results.values() if server["status"] == "healthy")
    total_servers = len(health_results)
    
    overall_status = "healthy" if healthy_servers == total_servers else "degraded" if healthy_servers > 0 else "unhealthy"
    
    return {
        "overall_status": overall_status,
        "healthy_servers": healthy_servers,
        "total_servers": total_servers,
        "servers": health_results,
        "check_timestamp": f"{__import__('datetime').datetime.now().isoformat()}"
    }

# --- MCP Tools for KVKK ---
@app.tool(
    description="Search KVKK data protection authority decisions",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": True,
        "idempotentHint": True
    }
)
async def search_kvkk_decisions(
    keywords: str = Field(..., description="Turkish keywords. Supports +required -excluded \"exact phrase\" operators"),
    page: int = Field(1, ge=1, le=50, description="Page number for results (1-50)."),
    # pageSize: int = Field(10, ge=1, le=20, description="Number of results per page (1-20).")
) -> Dict[str, Any]:
    """Search function for legal decisions."""
    logger.info(f"KVKK search tool called with keywords: {keywords}")

    pageSize = 10  # Default value

    search_request = KvkkSearchRequest(
        keywords=keywords,
        page=page,
        pageSize=pageSize
    )

    try:
        result = await kvkk_client_instance.search_decisions(search_request)
        logger.info(f"KVKK search completed. Found {len(result.decisions)} decisions on page {page}")
        return result.model_dump()
    except Exception as e:
        logger.exception(f"Error in KVKK search: {e}")
        # Return empty result on error
        return KvkkSearchResult(
            decisions=[],
            total_results=0,
            page=page,
            pageSize=pageSize,
            query=keywords
        ).model_dump()

@app.tool(
    description="Get KVKK decision document in Markdown format with metadata extraction",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": False,
        "idempotentHint": True
    }
)
async def get_kvkk_document_markdown(
    decision_url: str = Field(..., description="KVKK decision URL from search results"),
    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).")
) -> Dict[str, Any]:
    """Get KVKK decision as paginated Markdown."""
    logger.info(f"KVKK document retrieval tool called for URL: {decision_url}")

    if not decision_url or not decision_url.strip():
        return KvkkDocumentMarkdown(
            source_url=HttpUrl("https://www.kvkk.gov.tr"),
            title=None,
            decision_date=None,
            decision_number=None,
            subject_summary=None,
            markdown_chunk=None,
            current_page=page_number or 1,
            total_pages=0,
            is_paginated=False,
            error_message="Decision URL is required and cannot be empty."
        ).model_dump()
    
    try:
        # Validate URL format
        if not decision_url.startswith("https://www.kvkk.gov.tr/"):
            return KvkkDocumentMarkdown(
                source_url=HttpUrl(decision_url),
                title=None,
                decision_date=None,
                decision_number=None,
                subject_summary=None,
                markdown_chunk=None,
                current_page=page_number or 1,
                total_pages=0,
                is_paginated=False,
                error_message="Invalid KVKK decision URL format. URL must start with https://www.kvkk.gov.tr/"
            ).model_dump()

        result = await kvkk_client_instance.get_decision_document(decision_url, page_number or 1)
        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}")
        return result.model_dump()
        
    except Exception as e:
        logger.exception(f"Error retrieving KVKK document: {e}")
        return KvkkDocumentMarkdown(
            source_url=HttpUrl(decision_url),
            title=None,
            decision_date=None,
            decision_number=None,
            subject_summary=None,
            markdown_chunk=None,
            current_page=page_number or 1,
            total_pages=0,
            is_paginated=False,
            error_message=f"Error retrieving KVKK document: {str(e)}"
        ).model_dump()

# --- MCP Tools for BDDK (Banking Regulation Authority) ---
@app.tool(
    description="Search BDDK banking regulation decisions",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": True,
        "idempotentHint": True
    }
)
async def search_bddk_decisions(
    keywords: str = Field(..., description="Search keywords in Turkish"),
    page: int = Field(1, ge=1, description="Page number")
    # pageSize: int = Field(10, ge=1, le=50, description="Results per page")
) -> dict:
    """Search BDDK banking regulation and supervision decisions."""
    logger.info(f"BDDK search tool called with keywords: {keywords}, page: {page}")
    
    pageSize = 10  # Default value
    
    try:
        search_request = BddkSearchRequest(
            keywords=keywords,
            page=page,
            pageSize=pageSize
        )
        
        result = await bddk_client_instance.search_decisions(search_request)
        logger.info(f"BDDK search completed. Found {len(result.decisions)} decisions on page {page}")
        
        return {
            "decisions": [
                {
                    "title": dec.title,
                    "document_id": dec.document_id,
                    "content": dec.content
                }
                for dec in result.decisions
            ],
            "total_results": result.total_results,
            "page": result.page,
            "pageSize": result.pageSize
        }
    
    except Exception as e:
        logger.exception(f"Error searching BDDK decisions: {e}")
        return {
            "decisions": [],
            "total_results": 0,
            "page": page,
            "pageSize": pageSize,
            "error": str(e)
        }

@app.tool(
    description="Get BDDK decision document as Markdown",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": False,
        "idempotentHint": True
    }
)
async def get_bddk_document_markdown(
    document_id: str = Field(..., description="BDDK document ID (e.g., '310')"),
    page_number: int = Field(1, ge=1, description="Page number")
) -> dict:
    """Retrieve BDDK decision document in Markdown format."""
    logger.info(f"BDDK document retrieval tool called for ID: {document_id}, page: {page_number}")
    
    if not document_id or not document_id.strip():
        return {
            "document_id": document_id,
            "markdown_content": "",
            "page_number": page_number,
            "total_pages": 0,
            "error": "Document ID is required"
        }
    
    try:
        result = await bddk_client_instance.get_document_markdown(document_id, page_number)
        logger.info(f"BDDK document retrieved successfully. Page {result.page_number}/{result.total_pages}")
        
        return {
            "document_id": result.document_id,
            "markdown_content": result.markdown_content,
            "page_number": result.page_number,
            "total_pages": result.total_pages
        }
        
    except Exception as e:
        logger.exception(f"Error retrieving BDDK document: {e}")
        return {
            "document_id": document_id,
            "markdown_content": "",
            "page_number": page_number,
            "total_pages": 0,
            "error": str(e)
        }

# --- ChatGPT Deep Research Compatible Tools ---

def get_preview_text(markdown_content: str, skip_chars: int = 100, preview_chars: int = 200) -> str:
    """
    Extract a preview of document text by skipping headers and showing meaningful content.
    
    Args:
        markdown_content: Full document content in markdown format
        skip_chars: Number of characters to skip from the beginning (default: 100)
        preview_chars: Number of characters to show in preview (default: 200)
    
    Returns:
        Preview text suitable for ChatGPT Deep Research
    """
    if not markdown_content:
        return ""
    
    # Remove common markdown artifacts and clean up
    cleaned_content = markdown_content.strip()
    
    # Skip the first N characters (usually headers, metadata)
    if len(cleaned_content) > skip_chars:
        content_start = cleaned_content[skip_chars:]
    else:
        content_start = cleaned_content
    
    # Get the next N characters for preview
    if len(content_start) > preview_chars:
        preview = content_start[:preview_chars]
    else:
        preview = content_start
    
    # Clean up the preview - remove incomplete sentences at the end
    preview = preview.strip()
    
    # If preview ends mid-sentence, try to end at last complete sentence
    if preview and not preview.endswith('.'):
        last_period = preview.rfind('.')
        if last_period > 50:  # Only if there's a reasonable sentence
            preview = preview[:last_period + 1]
    
    # Add ellipsis if content was truncated
    if len(content_start) > preview_chars:
        preview += "..."
    
    return preview.strip()


@app.tool(
    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",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": True,
        "idempotentHint": True
    }
)
async def search(
    query: str = Field(..., description="Turkish search query")
) -> Dict[str, Any]:
    """
    Bedesten API search tool for ChatGPT Deep Research compatibility.
    
    This tool searches Turkish legal databases via the unified Bedesten API.
    It supports advanced search operators and covers all major court types.
    
    USAGE RESTRICTION: Only for ChatGPT Deep Research workflows.
    For regular legal research, use search_bedesten_unified with specific court types.
    
    Returns:
    Object with "results" field containing a list of documents with id, title, text preview, and url
    as required by ChatGPT Deep Research specification.
    """
    logger.info(f"ChatGPT Deep Research search tool called with query: {query}")
    
    results = []
    
    try:
        # Search all court types via unified Bedesten API
        court_types = [
            ("YARGITAYKARARI", "Yargıtay", "yargitay_bedesten"),
            ("DANISTAYKARAR", "Danıştay", "danistay_bedesten"), 
            ("YERELHUKUK", "Yerel Hukuk Mahkemesi", "yerel_hukuk_bedesten"),
            ("ISTINAFHUKUK", "İstinaf Hukuk Mahkemesi", "istinaf_hukuk_bedesten"),
            ("KYB", "Kanun Yararına Bozma", "kyb_bedesten")
        ]
        
        for item_type, court_name, id_prefix in court_types:
            try:
                search_results = await bedesten_client_instance.search_documents(
                    BedestenSearchRequest(
                        data=BedestenSearchData(
                            phrase=query,  # Use query as-is to support both regular and exact phrase searches
                            itemTypeList=[item_type],
                            pageSize=10,
                            pageNumber=1
                        )
                    )
                )
                
                # Handle potential None data
                if search_results.data is None:
                    logger.warning(f"No data returned from Bedesten API for {court_name}")
                    continue
                
                # Add results from this court type (limit to top 5 per court)
                for decision in search_results.data.emsalKararList[:5]:
                    # For ChatGPT Deep Research, fetch document content for preview
                    try:
                        # Fetch document content for preview
                        doc = await bedesten_client_instance.get_document_as_markdown(decision.documentId)
                        
                        # Generate preview text (skip first 100 chars, show next 200)
                        preview_text = get_preview_text(doc.markdown_content, skip_chars=100, preview_chars=200)
                        
                        # Build title from metadata
                        title_parts = []
                        if decision.birimAdi:
                            title_parts.append(decision.birimAdi)
                        if decision.esasNo:
                            title_parts.append(f"Esas: {decision.esasNo}")
                        if decision.kararNo:
                            title_parts.append(f"Karar: {decision.kararNo}")
                        if decision.kararTarihiStr:
                            title_parts.append(f"Tarih: {decision.kararTarihiStr}")
                        
                        if title_parts:
                            title = " - ".join(title_parts)
                        else:
                            title = f"{court_name} - Document {decision.documentId}"
                        
                        # Add to results in OpenAI format
                        results.append({
                            "id": decision.documentId,
                            "title": title,
                            "text": preview_text,
                            "url": f"https://mevzuat.adalet.gov.tr/ictihat/{decision.documentId}"
                        })
                        
                    except Exception as e:
                        logger.warning(f"Could not fetch preview for document {decision.documentId}: {e}")
                        # Add minimal result without preview
                        results.append({
                            "id": decision.documentId,
                            "title": f"{court_name} - Document {decision.documentId}",
                            "text": "Document preview not available",
                            "url": f"https://mevzuat.adalet.gov.tr/ictihat/{decision.documentId}"
                        })
                    
                if search_results.data:
                    logger.info(f"Found {len(search_results.data.emsalKararList)} results from {court_name}")
                else:
                    logger.info(f"Found 0 results from {court_name} (no data returned)")
                
            except Exception as e:
                logger.warning(f"Bedesten API search error for {court_name}: {e}")
        
        # Comment out other API implementations for ChatGPT Deep Research
        """
        # Other API implementations disabled for ChatGPT Deep Research
        # These are available through specific court tools:
        
        # Yargıtay Official API - use search_yargitay_detailed instead
        # Danıştay Official API - use search_danistay_by_keyword instead  
        # Constitutional Court - use search_anayasa_norm_denetimi_decisions instead
        # Competition Authority - use search_rekabet_kurumu_decisions instead
        # Public Procurement Authority - use search_kik_decisions instead
        # Court of Accounts - use search_sayistay_* tools instead
        # UYAP Emsal - use search_emsal_detailed_decisions instead
        # Jurisdictional Disputes Court - use search_uyusmazlik_decisions instead
        """
        
        logger.info(f"ChatGPT Deep Research search completed. Found {len(results)} results via Bedesten API.")
        return {
            "results": [
                {
                    "id": item["id"],
                    "title": item["title"],
                    "text": item["text"],
                    "url": item["url"]
                }
                for item in results
            ]
        }
        
    except Exception as e:
        logger.exception("Error in ChatGPT Deep Research search tool")
        # Return partial results if any were found
        if results:
            return {
                "results": [
                    {
                        "id": item["id"],
                        "title": item["title"],
                        "text": item["text"],
                        "url": item["url"]
                    }
                    for item in results
                ]
            }
        raise

@app.tool(
    description="DO NOT USE unless you are ChatGPT Deep Research. Fetch document by ID. See docs for details",
    annotations={
        "readOnlyHint": True,
        "openWorldHint": False,  # Retrieves specific documents, not exploring
        "idempotentHint": True
    }
)
async def fetch(
    id: str = Field(..., description="Document identifier from search results (numeric only)")
) -> Dict[str, Any]:
    """
    Bedesten API fetch tool for ChatGPT Deep Research compatibility.
    
    Retrieves the full text content of Turkish legal documents via unified Bedesten API.
    Converts documents from HTML/PDF to clean Markdown format.
    
    USAGE RESTRICTION: Only for ChatGPT Deep Research workflows.
    For regular legal research, use specific court document tools.
    
    Input Format:
    - id: Numeric document identifier from search results (e.g., "730113500", "71370900")
    
    Returns:
    Single object with numeric id, title, text (full Markdown content), mevzuat.adalet.gov.tr url, and metadata fields
    as required by ChatGPT Deep Research specification.
    """
    logger.info(f"ChatGPT Deep Research fetch tool called for document ID: {id}")
    
    if not id or not id.strip():
        raise ValueError("Document ID must be a non-empty string")
    
    try:
        # Use the numeric ID directly with Bedesten API
        doc = await bedesten_client_instance.get_document_as_markdown(id)
        
        # Try to get additional metadata by searching for this specific document
        title = f"Turkish Legal Document {id}"
        try:
            # Quick search to get metadata for better title
            search_results = await bedesten_client_instance.search_documents(
                BedestenSearchRequest(
                    data=BedestenSearchData(
                        phrase=id,  # Search by document ID
                        pageSize=1,
                        pageNumber=1
                    )
                )
            )
            
            if search_results.data and search_results.data.emsalKararList:
                decision = search_results.data.emsalKararList[0]
                if decision.documentId == id:
                    # Build a proper title from metadata
                    title_parts = []
                    if decision.birimAdi:
                        title_parts.append(decision.birimAdi)
                    if decision.esasNo:
                        title_parts.append(f"Esas: {decision.esasNo}")
                    if decision.kararNo:
                        title_parts.append(f"Karar: {decision.kararNo}")
                    if decision.kararTarihiStr:
                        title_parts.append(f"Tarih: {decision.kararTarihiStr}")
                    
                    if title_parts:
                        title = " - ".join(title_parts)
                    else:
                        title = f"Turkish Legal Decision {id}"
        except Exception as e:
            logger.warning(f"Could not fetch metadata for document {id}: {e}")
        
        return {
            "id": id,
            "title": title,
            "text": doc.markdown_content,
            "url": f"https://mevzuat.adalet.gov.tr/ictihat/{id}",
            "metadata": {
                "database": "Turkish Legal Database via Bedesten API",
                "document_id": id,
                "source_url": doc.source_url,
                "mime_type": doc.mime_type,
                "api_source": "Bedesten Unified API",
                "chatgpt_deep_research": True
            }
        }
        
        # Comment out other API implementations for ChatGPT Deep Research
        """
        # Other API implementations disabled for ChatGPT Deep Research
        # These are available through specific court document tools:
        
        elif id.startswith("yargitay_"):
            # Yargıtay Official API - use get_yargitay_document_markdown instead
            doc_id = id.replace("yargitay_", "")
            doc = await yargitay_client_instance.get_decision_document_as_markdown(doc_id)
            
        elif id.startswith("danistay_"):
            # Danıştay Official API - use get_danistay_document_markdown instead
            doc_id = id.replace("danistay_", "")
            doc = await danistay_client_instance.get_decision_document_as_markdown(doc_id)
            
        elif id.startswith("anayasa_"):
            # Constitutional Court - use get_anayasa_norm_denetimi_document_markdown instead
            doc_id = id.replace("anayasa_", "")
            doc = await anayasa_norm_client_instance.get_decision_document_as_markdown(...)
            
        elif id.startswith("rekabet_"):
            # Competition Authority - use get_rekabet_kurumu_document instead
            doc_id = id.replace("rekabet_", "")
            doc = await rekabet_client_instance.get_decision_document(...)
            
        elif id.startswith("kik_"):
            # Public Procurement Authority - use get_kik_decision_document_as_markdown instead
            doc_id = id.replace("kik_", "")
            doc = await kik_client_instance.get_decision_document_as_markdown(doc_id)
            
        elif id.startswith("local_"):
            # This was already using Bedesten API, but deprecated for ChatGPT Deep Research
            doc_id = id.replace("local_", "")
            doc = await bedesten_client_instance.get_document_as_markdown(doc_id)
        """
        
    except Exception as e:
        logger.exception(f"Error fetching ChatGPT Deep Research document {id}")
        raise

# --- Token Metrics Tool Removed for Optimization ---

def ensure_playwright_browsers():
    """Ensure Playwright browsers are installed for KIK tool functionality."""
    try:
        import subprocess
        import os
        
        # Check if chromium is already installed
        chromium_path = os.path.expanduser("~/Library/Caches/ms-playwright/chromium-1179")
        if os.path.exists(chromium_path):
            logger.info("Playwright Chromium browser already installed.")
            return
        
        logger.info("Installing Playwright Chromium browser for KIK tool...")
        result = subprocess.run(
            ["python", "-m", "playwright", "install", "chromium"],
            capture_output=True,
            text=True,
            timeout=300  # 5 minutes timeout
        )
        
        if result.returncode == 0:
            logger.info("Playwright Chromium browser installed successfully.")
        else:
            logger.warning(f"Failed to install Playwright browser: {result.stderr}")
            logger.warning("KIK tool may not work properly without Playwright browsers.")
            
    except Exception as e:
        logger.warning(f"Could not auto-install Playwright browsers: {e}")
        logger.warning("KIK tool may not work properly. Manual installation: 'playwright install chromium'")

def main():
    # Initialize the app properly with create_app()
    global app
    app = create_app()
    
    logger.info(f"Starting {app.name} server via main() function...")
    # logger.info(f"Logs will be written to: {LOG_FILE_PATH}")  # File logging disabled
    
    # Ensure Playwright browsers are installed
    ensure_playwright_browsers()
    
    try:
        app.run()
    except KeyboardInterrupt: 
        logger.info("Server shut down by user (KeyboardInterrupt).")
    except Exception as e: 
        logger.exception("Server failed to start or crashed.")
    finally:
        logger.info(f"{app.name} server has shut down.")

if __name__ == "__main__": 
    main()
```
Page 7/8FirstPrevNextLast