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

# Directory Structure

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

# Files

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

```python
  1 | #!/usr/bin/env python3
  2 | """
  3 | Standalone ASGI server runner for Yargı MCP
  4 | 
  5 | This script provides a simple way to run the Yargı MCP server
  6 | as a web service using uvicorn.
  7 | 
  8 | Usage:
  9 |     python run_asgi.py
 10 |     python run_asgi.py --host 0.0.0.0 --port 8080
 11 |     python run_asgi.py --reload  # For development
 12 | """
 13 | 
 14 | import os
 15 | import sys
 16 | import argparse
 17 | import logging
 18 | from pathlib import Path
 19 | 
 20 | # Add project root to Python path
 21 | sys.path.insert(0, str(Path(__file__).parent))
 22 | 
 23 | try:
 24 |     import uvicorn
 25 | except ImportError:
 26 |     print("Error: uvicorn is not installed.")
 27 |     print("Please install it with: pip install uvicorn")
 28 |     sys.exit(1)
 29 | 
 30 | # Configure logging
 31 | logging.basicConfig(
 32 |     level=logging.INFO,
 33 |     format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
 34 | )
 35 | 
 36 | def main():
 37 |     parser = argparse.ArgumentParser(
 38 |         description="Run Yargı MCP server as an ASGI web service"
 39 |     )
 40 |     parser.add_argument(
 41 |         "--host",
 42 |         type=str,
 43 |         default=os.getenv("HOST", "127.0.0.1"),
 44 |         help="Host to bind to (default: 127.0.0.1)"
 45 |     )
 46 |     parser.add_argument(
 47 |         "--port",
 48 |         type=int,
 49 |         default=int(os.getenv("PORT", "8000")),
 50 |         help="Port to bind to (default: 8000)"
 51 |     )
 52 |     parser.add_argument(
 53 |         "--reload",
 54 |         action="store_true",
 55 |         help="Enable auto-reload for development"
 56 |     )
 57 |     parser.add_argument(
 58 |         "--transport",
 59 |         choices=["http", "sse"],
 60 |         default="http",
 61 |         help="Transport type (default: http)"
 62 |     )
 63 |     parser.add_argument(
 64 |         "--log-level",
 65 |         choices=["debug", "info", "warning", "error"],
 66 |         default=os.getenv("LOG_LEVEL", "info").lower(),
 67 |         help="Log level (default: info)"
 68 |     )
 69 |     parser.add_argument(
 70 |         "--workers",
 71 |         type=int,
 72 |         default=1,
 73 |         help="Number of worker processes (default: 1)"
 74 |     )
 75 |     
 76 |     args = parser.parse_args()
 77 |     
 78 |     # Select app based on transport
 79 |     app_name = "asgi_app:app" if args.transport == "http" else "asgi_app:sse_app"
 80 |     
 81 |     # Configure uvicorn
 82 |     config = {
 83 |         "app": app_name,
 84 |         "host": args.host,
 85 |         "port": args.port,
 86 |         "log_level": args.log_level,
 87 |         "reload": args.reload,
 88 |         "access_log": True,
 89 |     }
 90 |     
 91 |     # Add workers only if not in reload mode
 92 |     if not args.reload and args.workers > 1:
 93 |         config["workers"] = args.workers
 94 |     
 95 |     # Print startup information
 96 |     print(f"Starting Yargı MCP server...")
 97 |     print(f"Host: {args.host}")
 98 |     print(f"Port: {args.port}")
 99 |     print(f"Transport: {args.transport}")
100 |     print(f"Log level: {args.log_level}")
101 |     if args.reload:
102 |         print("Auto-reload: enabled")
103 |     else:
104 |         print(f"Workers: {args.workers}")
105 |     print(f"\nServer will be available at: http://{args.host}:{args.port}")
106 |     print(f"MCP endpoint: http://{args.host}:{args.port}/mcp/")
107 |     print(f"Health check: http://{args.host}:{args.port}/health")
108 |     print(f"API status: http://{args.host}:{args.port}/status")
109 |     print("\nPress CTRL+C to stop the server\n")
110 |     
111 |     # Run uvicorn
112 |     try:
113 |         uvicorn.run(**config)
114 |     except KeyboardInterrupt:
115 |         print("\nShutting down server...")
116 |         sys.exit(0)
117 | 
118 | if __name__ == "__main__":
119 |     main()
```

--------------------------------------------------------------------------------
/rekabet_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
 1 | # rekabet_mcp_module/models.py
 2 | 
 3 | from pydantic import BaseModel, Field, HttpUrl
 4 | from typing import List, Optional, Any
 5 | from enum import Enum
 6 | 
 7 | # Enum for decision type GUIDs (used by the client and expected by the website)
 8 | class RekabetKararTuruGuidEnum(str, Enum):
 9 |     TUMU = "ALL"  # Represents "All" or "Select Decision Type"
10 |     BIRLESME_DEVRALMA = "2fff0979-9f9d-42d7-8c2e-a30705889542"  # Merger and Acquisition
11 |     DIGER = "dda8feaf-c919-405c-9da1-823f22b45ad9"  # Other
12 |     MENFI_TESPIT_MUAFIYET = "95ccd210-5304-49c5-b9e0-8ee53c50d4e8"  # Negative Clearance and Exemption
13 |     OZELLESTIRME = "e1f14505-842b-4af5-95d1-312d6de1a541"  # Privatization
14 |     REKABET_IHLALI = "720614bf-efd1-4dca-9785-b98eb65f2677"  # Competition Infringement
15 | 
16 | # Enum for user-friendly decision type names (for server tool parameters)
17 | # These correspond to the display names on the website's select dropdown.
18 | class RekabetKararTuruAdiEnum(str, Enum):
19 |     TUMU = "Tümü"  # Corresponds to the empty value "" for GUID, meaning "All"
20 |     BIRLESME_VE_DEVRALMA = "Birleşme ve Devralma"
21 |     DIGER = "Diğer"
22 |     MENFI_TESPIT_VE_MUAFIYET = "Menfi Tespit ve Muafiyet"
23 |     OZELLESTIRME = "Özelleştirme"
24 |     REKABET_IHLALI = "Rekabet İhlali"
25 | 
26 | class RekabetKurumuSearchRequest(BaseModel):
27 |     """Model for Rekabet Kurumu (Turkish Competition Authority) search request."""
28 |     sayfaAdi: str = Field("", description="Title")
29 |     YayinlanmaTarihi: str = Field("", description="Date")
30 |     PdfText: str = Field("", description="Text")
31 |     KararTuruID: RekabetKararTuruGuidEnum = Field(RekabetKararTuruGuidEnum.TUMU, description="Type")
32 |     KararSayisi: str = Field("", description="No")
33 |     KararTarihi: str = Field("", description="Date")
34 |     page: int = Field(1, ge=1, description="Page")
35 | 
36 | class RekabetDecisionSummary(BaseModel):
37 |     """Model for a single Rekabet Kurumu decision summary from search results."""
38 |     publication_date: str = Field("", description="Pub date")
39 |     decision_number: str = Field("", description="Number")
40 |     decision_date: str = Field("", description="Date")
41 |     decision_type_text: str = Field("", description="Type")
42 |     title: str = Field("", description="Title")
43 |     decision_url: str = Field("", description="URL")
44 |     karar_id: str = Field("", description="ID")
45 |     related_cases_url: str = Field("", description="Cases URL")
46 | 
47 | class RekabetSearchResult(BaseModel):
48 |     """Model for the overall search result for Rekabet Kurumu decisions."""
49 |     decisions: List[RekabetDecisionSummary]
50 |     total_records_found: int = Field(0, description="Total")
51 |     retrieved_page_number: int = Field(description="Page")
52 |     total_pages: int = Field(0, description="Pages")
53 | 
54 | class RekabetDocument(BaseModel):
55 |     """
56 |     Model for a Rekabet Kurumu decision document.
57 |     Contains metadata from the landing page, a link to the PDF,
58 |     and the PDF's content converted to paginated Markdown.
59 |     """
60 |     source_landing_page_url: HttpUrl = Field(description="Source URL")
61 |     karar_id: str = Field(description="ID")
62 |     
63 |     title_on_landing_page: Optional[str] = Field(None, description="Title")
64 |     pdf_url: Optional[HttpUrl] = Field(None, description="PDF URL")
65 |     
66 |     markdown_chunk: Optional[str] = Field(None, description="Content")
67 |     current_page: int = Field(1, description="Page")
68 |     total_pages: int = Field(1, description="Total pages")
69 |     is_paginated: bool = Field(False, description="Paginated")
70 |     
71 |     error_message: Optional[str] = Field(None, description="Error")
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/rekabet_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
 1 | # rekabet_mcp_module/models.py
 2 | 
 3 | from pydantic import BaseModel, Field, HttpUrl
 4 | from typing import List, Optional, Any
 5 | from enum import Enum
 6 | 
 7 | # Enum for decision type GUIDs (used by the client and expected by the website)
 8 | class RekabetKararTuruGuidEnum(str, Enum):
 9 |     TUMU = "ALL"  # Represents "All" or "Select Decision Type"
10 |     BIRLESME_DEVRALMA = "2fff0979-9f9d-42d7-8c2e-a30705889542"  # Merger and Acquisition
11 |     DIGER = "dda8feaf-c919-405c-9da1-823f22b45ad9"  # Other
12 |     MENFI_TESPIT_MUAFIYET = "95ccd210-5304-49c5-b9e0-8ee53c50d4e8"  # Negative Clearance and Exemption
13 |     OZELLESTIRME = "e1f14505-842b-4af5-95d1-312d6de1a541"  # Privatization
14 |     REKABET_IHLALI = "720614bf-efd1-4dca-9785-b98eb65f2677"  # Competition Infringement
15 | 
16 | # Enum for user-friendly decision type names (for server tool parameters)
17 | # These correspond to the display names on the website's select dropdown.
18 | class RekabetKararTuruAdiEnum(str, Enum):
19 |     TUMU = "Tümü"  # Corresponds to the empty value "" for GUID, meaning "All"
20 |     BIRLESME_VE_DEVRALMA = "Birleşme ve Devralma"
21 |     DIGER = "Diğer"
22 |     MENFI_TESPIT_VE_MUAFIYET = "Menfi Tespit ve Muafiyet"
23 |     OZELLESTIRME = "Özelleştirme"
24 |     REKABET_IHLALI = "Rekabet İhlali"
25 | 
26 | class RekabetKurumuSearchRequest(BaseModel):
27 |     """Model for Rekabet Kurumu (Turkish Competition Authority) search request."""
28 |     sayfaAdi: str = Field("", description="Title")
29 |     YayinlanmaTarihi: str = Field("", description="Date")
30 |     PdfText: str = Field("", description="Text")
31 |     KararTuruID: RekabetKararTuruGuidEnum = Field(RekabetKararTuruGuidEnum.TUMU, description="Type")
32 |     KararSayisi: str = Field("", description="No")
33 |     KararTarihi: str = Field("", description="Date")
34 |     page: int = Field(1, ge=1, description="Page")
35 | 
36 | class RekabetDecisionSummary(BaseModel):
37 |     """Model for a single Rekabet Kurumu decision summary from search results."""
38 |     publication_date: str = Field("", description="Pub date")
39 |     decision_number: str = Field("", description="Number")
40 |     decision_date: str = Field("", description="Date")
41 |     decision_type_text: str = Field("", description="Type")
42 |     title: str = Field("", description="Title")
43 |     decision_url: str = Field("", description="URL")
44 |     karar_id: str = Field("", description="ID")
45 |     related_cases_url: str = Field("", description="Cases URL")
46 | 
47 | class RekabetSearchResult(BaseModel):
48 |     """Model for the overall search result for Rekabet Kurumu decisions."""
49 |     decisions: List[RekabetDecisionSummary]
50 |     total_records_found: int = Field(0, description="Total")
51 |     retrieved_page_number: int = Field(description="Page")
52 |     total_pages: int = Field(0, description="Pages")
53 | 
54 | class RekabetDocument(BaseModel):
55 |     """
56 |     Model for a Rekabet Kurumu decision document.
57 |     Contains metadata from the landing page, a link to the PDF,
58 |     and the PDF's content converted to paginated Markdown.
59 |     """
60 |     source_landing_page_url: HttpUrl = Field(description="Source URL")
61 |     karar_id: str = Field(description="ID")
62 |     
63 |     title_on_landing_page: Optional[str] = Field(None, description="Title")
64 |     pdf_url: Optional[HttpUrl] = Field(None, description="PDF URL")
65 |     
66 |     markdown_chunk: Optional[str] = Field(None, description="Content")
67 |     current_page: int = Field(1, description="Page")
68 |     total_pages: int = Field(1, description="Total pages")
69 |     is_paginated: bool = Field(False, description="Paginated")
70 |     
71 |     error_message: Optional[str] = Field(None, description="Error")
```

--------------------------------------------------------------------------------
/kik_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
 1 | # kik_mcp_module/models.py
 2 | from pydantic import BaseModel, Field, HttpUrl, computed_field, ConfigDict
 3 | from typing import List, Optional
 4 | from enum import Enum
 5 | import base64 # Base64 encoding/decoding için
 6 | 
 7 | class KikKararTipi(str, Enum):
 8 |     """Enum for KIK (Public Procurement Authority) Decision Types."""
 9 |     UYUSMAZLIK = "rbUyusmazlik"
10 |     DUZENLEYICI = "rbDuzenleyici"
11 |     MAHKEME = "rbMahkeme"
12 | 
13 | class KikSearchRequest(BaseModel):
14 |     """Model for KIK Decision search criteria."""
15 |     karar_tipi: KikKararTipi = Field(KikKararTipi.UYUSMAZLIK, description="Type")
16 |     karar_no: str = Field("", description="No")
17 |     karar_tarihi_baslangic: str = Field("", description="Start", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$")
18 |     karar_tarihi_bitis: str = Field("", description="End", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$")
19 |     resmi_gazete_sayisi: str = Field("", description="Gazette")
20 |     resmi_gazete_tarihi: str = Field("", description="Date", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$")
21 |     basvuru_konusu_ihale: str = Field("", description="Subject")
22 |     basvuru_sahibi: str = Field("", description="Applicant")
23 |     ihaleyi_yapan_idare: str = Field("", description="Entity")
24 |     yil: str = Field("", description="Year")
25 |     karar_metni: str = Field("", description="Text")
26 |     page: int = Field(1, ge=1, description="Page")
27 | 
28 | class KikDecisionEntry(BaseModel):
29 |     """Represents a single decision entry from KIK search results."""
30 |     preview_event_target: str = Field(..., description="Event target")
31 |     karar_no_str: str = Field(..., alias="kararNo", description="Decision number")
32 |     karar_tipi: KikKararTipi = Field(..., description="Decision type")
33 |     
34 |     karar_tarihi_str: str = Field(..., alias="kararTarihi", description="Date")
35 |     idare_str: str = Field("", alias="idare", description="Entity")
36 |     basvuru_sahibi_str: str = Field("", alias="basvuruSahibi", description="Applicant")
37 |     ihale_konusu_str: str = Field("", alias="ihaleKonusu", description="Subject")
38 | 
39 |     @computed_field
40 |     @property
41 |     def karar_id(self) -> str:
42 |         """
43 |         A Base64 encoded unique ID for the decision, combining decision type and number.
44 |         Format before encoding: "{karar_tipi.value}|{karar_no_str}"
45 |         """
46 |         combined_key = f"{self.karar_tipi.value}|{self.karar_no_str}"
47 |         return base64.b64encode(combined_key.encode('utf-8')).decode('utf-8')
48 | 
49 |     model_config = ConfigDict(populate_by_name=True)
50 | 
51 | class KikSearchResult(BaseModel):
52 |     """Model for KIK search results."""
53 |     decisions: List[KikDecisionEntry]
54 |     total_records: int = 0
55 |     current_page: int = 1
56 | 
57 | class KikDocumentMarkdown(BaseModel):
58 |     """
59 |     KIK decision document, with Markdown content potentially paginated.
60 |     """
61 |     retrieved_with_karar_id: Optional[str] = Field(None, description="Request ID")
62 |     retrieved_karar_no: Optional[str] = Field(None, description="Decision number")
63 |     retrieved_karar_tipi: Optional[KikKararTipi] = Field(None, description="Decision type")
64 |     
65 |     karar_id_param_from_url: Optional[str] = Field(None, alias="kararIdParam", description="Internal ID")
66 |     markdown_chunk: Optional[str] = Field(None, description="Content")
67 |     source_url: Optional[str] = Field(None, description="Source URL")
68 |     error_message: Optional[str] = Field(None, description="Error")
69 |     current_page: int = Field(1, description="Page")
70 |     total_pages: int = Field(1, description="Total pages")
71 |     is_paginated: bool = Field(False, description="Paginated")
72 |     full_content_char_count: Optional[int] = Field(None, description="Char count")
73 | 
74 |     model_config = ConfigDict(populate_by_name=True)
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/kik_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
 1 | # kik_mcp_module/models.py
 2 | from pydantic import BaseModel, Field, HttpUrl, computed_field, ConfigDict
 3 | from typing import List, Optional
 4 | from enum import Enum
 5 | import base64 # Base64 encoding/decoding için
 6 | 
 7 | class KikKararTipi(str, Enum):
 8 |     """Enum for KIK (Public Procurement Authority) Decision Types."""
 9 |     UYUSMAZLIK = "rbUyusmazlik"
10 |     DUZENLEYICI = "rbDuzenleyici"
11 |     MAHKEME = "rbMahkeme"
12 | 
13 | class KikSearchRequest(BaseModel):
14 |     """Model for KIK Decision search criteria."""
15 |     karar_tipi: KikKararTipi = Field(KikKararTipi.UYUSMAZLIK, description="Type")
16 |     karar_no: str = Field("", description="No")
17 |     karar_tarihi_baslangic: str = Field("", description="Start", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$")
18 |     karar_tarihi_bitis: str = Field("", description="End", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$")
19 |     resmi_gazete_sayisi: str = Field("", description="Gazette")
20 |     resmi_gazete_tarihi: str = Field("", description="Date", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$")
21 |     basvuru_konusu_ihale: str = Field("", description="Subject")
22 |     basvuru_sahibi: str = Field("", description="Applicant")
23 |     ihaleyi_yapan_idare: str = Field("", description="Entity")
24 |     yil: str = Field("", description="Year")
25 |     karar_metni: str = Field("", description="Text")
26 |     page: int = Field(1, ge=1, description="Page")
27 | 
28 | class KikDecisionEntry(BaseModel):
29 |     """Represents a single decision entry from KIK search results."""
30 |     preview_event_target: str = Field(..., description="Event target")
31 |     karar_no_str: str = Field(..., alias="kararNo", description="Decision number")
32 |     karar_tipi: KikKararTipi = Field(..., description="Decision type")
33 |     
34 |     karar_tarihi_str: str = Field(..., alias="kararTarihi", description="Date")
35 |     idare_str: str = Field("", alias="idare", description="Entity")
36 |     basvuru_sahibi_str: str = Field("", alias="basvuruSahibi", description="Applicant")
37 |     ihale_konusu_str: str = Field("", alias="ihaleKonusu", description="Subject")
38 | 
39 |     @computed_field
40 |     @property
41 |     def karar_id(self) -> str:
42 |         """
43 |         A Base64 encoded unique ID for the decision, combining decision type and number.
44 |         Format before encoding: "{karar_tipi.value}|{karar_no_str}"
45 |         """
46 |         combined_key = f"{self.karar_tipi.value}|{self.karar_no_str}"
47 |         return base64.b64encode(combined_key.encode('utf-8')).decode('utf-8')
48 | 
49 |     model_config = ConfigDict(populate_by_name=True)
50 | 
51 | class KikSearchResult(BaseModel):
52 |     """Model for KIK search results."""
53 |     decisions: List[KikDecisionEntry]
54 |     total_records: int = 0
55 |     current_page: int = 1
56 | 
57 | class KikDocumentMarkdown(BaseModel):
58 |     """
59 |     KIK decision document, with Markdown content potentially paginated.
60 |     """
61 |     retrieved_with_karar_id: Optional[str] = Field(None, description="Request ID")
62 |     retrieved_karar_no: Optional[str] = Field(None, description="Decision number")
63 |     retrieved_karar_tipi: Optional[KikKararTipi] = Field(None, description="Decision type")
64 |     
65 |     karar_id_param_from_url: Optional[str] = Field(None, alias="kararIdParam", description="Internal ID")
66 |     markdown_chunk: Optional[str] = Field(None, description="Content")
67 |     source_url: Optional[str] = Field(None, description="Source URL")
68 |     error_message: Optional[str] = Field(None, description="Error")
69 |     current_page: int = Field(1, description="Page")
70 |     total_pages: int = Field(1, description="Total pages")
71 |     is_paginated: bool = Field(False, description="Paginated")
72 |     full_content_char_count: Optional[int] = Field(None, description="Char count")
73 | 
74 |     model_config = ConfigDict(populate_by_name=True)
```

--------------------------------------------------------------------------------
/bedesten_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
 1 | # bedesten_mcp_module/models.py
 2 | 
 3 | from pydantic import BaseModel, Field
 4 | from typing import List, Optional, Dict, Any, Literal, Union
 5 | from datetime import datetime
 6 | 
 7 | # Import compressed BirimAdiEnum for chamber filtering
 8 | from .enums import BirimAdiEnum
 9 | 
10 | # Court Type Options for Unified Search
11 | BedestenCourtTypeEnum = Literal[
12 |     "YARGITAYKARARI",  # Yargıtay (Court of Cassation)
13 |     "DANISTAYKARAR",   # Danıştay (Council of State)
14 |     "YERELHUKUK",      # Local Civil Courts
15 |     "ISTINAFHUKUK",    # Civil Courts of Appeals
16 |     "KYB"              # Extraordinary Appeals (Kanun Yararına Bozma)
17 | ]
18 | 
19 | # Search Request Models
20 | class BedestenSearchData(BaseModel):
21 |     pageSize: int = Field(..., description="Results per page (1-10)")
22 |     pageNumber: int = Field(..., description="Page number (1-indexed)")
23 |     itemTypeList: List[str] = Field(..., description="Court type filter (YARGITAYKARARI/DANISTAYKARAR/YERELHUKUK/ISTINAFHUKUK/KYB)")
24 |     phrase: str = Field(..., description="Search phrase. Supports: 'word', \"exact phrase\", +required, -exclude, AND/OR/NOT operators. No wildcards or regex.")
25 |     birimAdi: BirimAdiEnum = Field("ALL", description="""
26 |         Chamber filter (optional). Abbreviated values with Turkish names:
27 |         • 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)
28 |         • 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)
29 |         """)
30 |     kararTarihiStart: Optional[str] = Field(None, description="Start date (ISO 8601 format)")
31 |     kararTarihiEnd: Optional[str] = Field(None, description="End date (ISO 8601 format)")
32 |     sortFields: List[str] = Field(default=["KARAR_TARIHI"], description="Sort fields")
33 |     sortDirection: str = Field(default="desc", description="Sort direction (asc/desc)")
34 | 
35 | class BedestenSearchRequest(BaseModel):
36 |     data: BedestenSearchData
37 |     applicationName: str = "UyapMevzuat"
38 |     paging: bool = True
39 | 
40 | # Search Response Models
41 | class BedestenItemType(BaseModel):
42 |     name: str
43 |     description: str
44 | 
45 | class BedestenDecisionEntry(BaseModel):
46 |     documentId: str
47 |     itemType: BedestenItemType
48 |     birimId: Optional[str] = None
49 |     birimAdi: Optional[str]
50 |     esasNoYil: Optional[int] = None
51 |     esasNoSira: Optional[int] = None
52 |     kararNoYil: Optional[int] = None
53 |     kararNoSira: Optional[int] = None
54 |     kararTuru: Optional[str] = None
55 |     kararTarihi: str
56 |     kararTarihiStr: str
57 |     kesinlesmeDurumu: Optional[str] = None
58 |     kararNo: Optional[str] = None
59 |     esasNo: Optional[str] = None
60 | 
61 | class BedestenSearchDataResponse(BaseModel):
62 |     emsalKararList: List[BedestenDecisionEntry]
63 |     total: int
64 |     start: int
65 | 
66 | class BedestenSearchResponse(BaseModel):
67 |     data: Optional[BedestenSearchDataResponse]
68 |     metadata: Dict[str, Any]
69 | 
70 | # Document Request/Response Models
71 | class BedestenDocumentRequestData(BaseModel):
72 |     documentId: str
73 | 
74 | class BedestenDocumentRequest(BaseModel):
75 |     data: BedestenDocumentRequestData
76 |     applicationName: str = "UyapMevzuat"
77 | 
78 | class BedestenDocumentData(BaseModel):
79 |     content: str  # Base64 encoded HTML or PDF
80 |     mimeType: str
81 |     version: int
82 | 
83 | class BedestenDocumentResponse(BaseModel):
84 |     data: BedestenDocumentData
85 |     metadata: Dict[str, Any]
86 | 
87 | class BedestenDocumentMarkdown(BaseModel):
88 |     documentId: str = Field(..., description="The document ID (Belge Kimliği) from Bedesten")
89 |     markdown_content: Optional[str] = Field(None, description="The decision content (Karar İçeriği) converted to Markdown")
90 |     source_url: str = Field(..., description="The source URL (Kaynak URL) of the document")
91 |     mime_type: Optional[str] = Field(None, description="Original content type (İçerik Türü) (text/html or application/pdf)")
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
 1 | # bedesten_mcp_module/models.py
 2 | 
 3 | from pydantic import BaseModel, Field
 4 | from typing import List, Optional, Dict, Any, Literal, Union
 5 | from datetime import datetime
 6 | 
 7 | # Import compressed BirimAdiEnum for chamber filtering
 8 | from .enums import BirimAdiEnum
 9 | 
10 | # Court Type Options for Unified Search
11 | BedestenCourtTypeEnum = Literal[
12 |     "YARGITAYKARARI",  # Yargıtay (Court of Cassation)
13 |     "DANISTAYKARAR",   # Danıştay (Council of State)
14 |     "YERELHUKUK",      # Local Civil Courts
15 |     "ISTINAFHUKUK",    # Civil Courts of Appeals
16 |     "KYB"              # Extraordinary Appeals (Kanun Yararına Bozma)
17 | ]
18 | 
19 | # Search Request Models
20 | class BedestenSearchData(BaseModel):
21 |     pageSize: int = Field(..., description="Results per page (1-10)")
22 |     pageNumber: int = Field(..., description="Page number (1-indexed)")
23 |     itemTypeList: List[str] = Field(..., description="Court type filter (YARGITAYKARARI/DANISTAYKARAR/YERELHUKUK/ISTINAFHUKUK/KYB)")
24 |     phrase: str = Field(..., description="Search phrase. Supports: 'word', \"exact phrase\", +required, -exclude, AND/OR/NOT operators. No wildcards or regex.")
25 |     birimAdi: BirimAdiEnum = Field("ALL", description="""
26 |         Chamber filter (optional). Abbreviated values with Turkish names:
27 |         • 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)
28 |         • 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)
29 |         """)
30 |     kararTarihiStart: Optional[str] = Field(None, description="Start date (ISO 8601 format)")
31 |     kararTarihiEnd: Optional[str] = Field(None, description="End date (ISO 8601 format)")
32 |     sortFields: List[str] = Field(default=["KARAR_TARIHI"], description="Sort fields")
33 |     sortDirection: str = Field(default="desc", description="Sort direction (asc/desc)")
34 | 
35 | class BedestenSearchRequest(BaseModel):
36 |     data: BedestenSearchData
37 |     applicationName: str = "UyapMevzuat"
38 |     paging: bool = True
39 | 
40 | # Search Response Models
41 | class BedestenItemType(BaseModel):
42 |     name: str
43 |     description: str
44 | 
45 | class BedestenDecisionEntry(BaseModel):
46 |     documentId: str
47 |     itemType: BedestenItemType
48 |     birimId: Optional[str] = None
49 |     birimAdi: Optional[str]
50 |     esasNoYil: Optional[int] = None
51 |     esasNoSira: Optional[int] = None
52 |     kararNoYil: Optional[int] = None
53 |     kararNoSira: Optional[int] = None
54 |     kararTuru: Optional[str] = None
55 |     kararTarihi: str
56 |     kararTarihiStr: str
57 |     kesinlesmeDurumu: Optional[str] = None
58 |     kararNo: Optional[str] = None
59 |     esasNo: Optional[str] = None
60 | 
61 | class BedestenSearchDataResponse(BaseModel):
62 |     emsalKararList: List[BedestenDecisionEntry]
63 |     total: int
64 |     start: int
65 | 
66 | class BedestenSearchResponse(BaseModel):
67 |     data: Optional[BedestenSearchDataResponse]
68 |     metadata: Dict[str, Any]
69 | 
70 | # Document Request/Response Models
71 | class BedestenDocumentRequestData(BaseModel):
72 |     documentId: str
73 | 
74 | class BedestenDocumentRequest(BaseModel):
75 |     data: BedestenDocumentRequestData
76 |     applicationName: str = "UyapMevzuat"
77 | 
78 | class BedestenDocumentData(BaseModel):
79 |     content: str  # Base64 encoded HTML or PDF
80 |     mimeType: str
81 |     version: int
82 | 
83 | class BedestenDocumentResponse(BaseModel):
84 |     data: BedestenDocumentData
85 |     metadata: Dict[str, Any]
86 | 
87 | class BedestenDocumentMarkdown(BaseModel):
88 |     documentId: str = Field(..., description="The document ID (Belge Kimliği) from Bedesten")
89 |     markdown_content: Optional[str] = Field(None, description="The decision content (Karar İçeriği) converted to Markdown")
90 |     source_url: str = Field(..., description="The source URL (Kaynak URL) of the document")
91 |     mime_type: Optional[str] = Field(None, description="Original content type (İçerik Türü) (text/html or application/pdf)")
```

--------------------------------------------------------------------------------
/mcp_auth/storage.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Persistent storage for OAuth sessions and tokens
  3 | """
  4 | 
  5 | import json
  6 | import os
  7 | import tempfile
  8 | import logging
  9 | from datetime import datetime
 10 | from typing import Dict, Any, Optional
 11 | 
 12 | logger = logging.getLogger(__name__)
 13 | 
 14 | 
 15 | class PersistentStorage:
 16 |     """File-based persistent storage for OAuth data"""
 17 |     
 18 |     def __init__(self, storage_dir: str = None):
 19 |         if storage_dir is None:
 20 |             # Use system temp directory or environment variable
 21 |             storage_dir = os.environ.get('TEMP', tempfile.gettempdir())
 22 |         
 23 |         self.storage_dir = os.path.join(storage_dir, 'mcp_oauth_storage')
 24 |         os.makedirs(self.storage_dir, exist_ok=True)
 25 |         
 26 |         self.sessions_file = os.path.join(self.storage_dir, 'oauth_sessions.json')
 27 |         self.tokens_file = os.path.join(self.storage_dir, 'oauth_tokens.json')
 28 |         
 29 |         logger.info(f"Persistent OAuth storage initialized at: {self.storage_dir}")
 30 |     
 31 |     def _load_json(self, filepath: str) -> Dict:
 32 |         """Load JSON data from file"""
 33 |         try:
 34 |             if os.path.exists(filepath):
 35 |                 with open(filepath, 'r', encoding='utf-8') as f:
 36 |                     return json.load(f)
 37 |         except Exception as e:
 38 |             logger.error(f"Error loading {filepath}: {e}")
 39 |         return {}
 40 |     
 41 |     def _save_json(self, filepath: str, data: Dict):
 42 |         """Save JSON data to file"""
 43 |         try:
 44 |             with open(filepath, 'w', encoding='utf-8') as f:
 45 |                 json.dump(data, f, indent=2, default=str)
 46 |         except Exception as e:
 47 |             logger.error(f"Error saving {filepath}: {e}")
 48 |     
 49 |     def get_sessions(self) -> Dict[str, Dict[str, Any]]:
 50 |         """Get all OAuth sessions"""
 51 |         data = self._load_json(self.sessions_file)
 52 |         # Clean expired sessions
 53 |         now = datetime.utcnow().timestamp()
 54 |         valid_sessions = {k: v for k, v in data.items() 
 55 |                          if v.get('expires_at', 0) > now}
 56 |         if len(valid_sessions) != len(data):
 57 |             self._save_json(self.sessions_file, valid_sessions)
 58 |         return valid_sessions
 59 |     
 60 |     def set_session(self, session_id: str, data: Dict[str, Any]):
 61 |         """Set OAuth session data"""
 62 |         sessions = self.get_sessions()
 63 |         sessions[session_id] = data
 64 |         self._save_json(self.sessions_file, sessions)
 65 |     
 66 |     def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
 67 |         """Get specific OAuth session data"""
 68 |         sessions = self.get_sessions()
 69 |         return sessions.get(session_id)
 70 |     
 71 |     def delete_session(self, session_id: str):
 72 |         """Delete OAuth session"""
 73 |         sessions = self.get_sessions()
 74 |         if session_id in sessions:
 75 |             del sessions[session_id]
 76 |             self._save_json(self.sessions_file, sessions)
 77 |     
 78 |     def get_tokens(self) -> Dict[str, Dict[str, Any]]:
 79 |         """Get all OAuth tokens"""
 80 |         data = self._load_json(self.tokens_file)
 81 |         # Clean expired tokens
 82 |         now = datetime.utcnow().timestamp()
 83 |         valid_tokens = {k: v for k, v in data.items() 
 84 |                        if v.get('expires_at', 0) > now}
 85 |         if len(valid_tokens) != len(data):
 86 |             self._save_json(self.tokens_file, valid_tokens)
 87 |         return valid_tokens
 88 |     
 89 |     def set_token(self, token_id: str, token_data: Dict[str, Any]):
 90 |         """Set OAuth token data"""
 91 |         tokens = self.get_tokens()
 92 |         tokens[token_id] = token_data
 93 |         self._save_json(self.tokens_file, tokens)
 94 |     
 95 |     def get_token(self, token_id: str) -> Optional[Dict[str, Any]]:
 96 |         """Get specific OAuth token data"""
 97 |         tokens = self.get_tokens()
 98 |         return tokens.get(token_id)
 99 |     
100 |     def delete_token(self, token_id: str):
101 |         """Delete OAuth token"""
102 |         tokens = self.get_tokens()
103 |         if token_id in tokens:
104 |             del tokens[token_id]
105 |             self._save_json(self.tokens_file, tokens)
106 |     
107 |     def cleanup_expired_sessions(self):
108 |         """Clean up expired sessions and tokens"""
109 |         # This is handled automatically in get_sessions() and get_tokens()
110 |         sessions = self.get_sessions()
111 |         tokens = self.get_tokens()
112 |         logger.debug(f"Cleanup: {len(sessions)} active sessions, {len(tokens)} active tokens")
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/mcp_auth/storage.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Persistent storage for OAuth sessions and tokens
  3 | """
  4 | 
  5 | import json
  6 | import os
  7 | import tempfile
  8 | import logging
  9 | from datetime import datetime
 10 | from typing import Dict, Any, Optional
 11 | 
 12 | logger = logging.getLogger(__name__)
 13 | 
 14 | 
 15 | class PersistentStorage:
 16 |     """File-based persistent storage for OAuth data"""
 17 |     
 18 |     def __init__(self, storage_dir: str = None):
 19 |         if storage_dir is None:
 20 |             # Use system temp directory or environment variable
 21 |             storage_dir = os.environ.get('TEMP', tempfile.gettempdir())
 22 |         
 23 |         self.storage_dir = os.path.join(storage_dir, 'mcp_oauth_storage')
 24 |         os.makedirs(self.storage_dir, exist_ok=True)
 25 |         
 26 |         self.sessions_file = os.path.join(self.storage_dir, 'oauth_sessions.json')
 27 |         self.tokens_file = os.path.join(self.storage_dir, 'oauth_tokens.json')
 28 |         
 29 |         logger.info(f"Persistent OAuth storage initialized at: {self.storage_dir}")
 30 |     
 31 |     def _load_json(self, filepath: str) -> Dict:
 32 |         """Load JSON data from file"""
 33 |         try:
 34 |             if os.path.exists(filepath):
 35 |                 with open(filepath, 'r', encoding='utf-8') as f:
 36 |                     return json.load(f)
 37 |         except Exception as e:
 38 |             logger.error(f"Error loading {filepath}: {e}")
 39 |         return {}
 40 |     
 41 |     def _save_json(self, filepath: str, data: Dict):
 42 |         """Save JSON data to file"""
 43 |         try:
 44 |             with open(filepath, 'w', encoding='utf-8') as f:
 45 |                 json.dump(data, f, indent=2, default=str)
 46 |         except Exception as e:
 47 |             logger.error(f"Error saving {filepath}: {e}")
 48 |     
 49 |     def get_sessions(self) -> Dict[str, Dict[str, Any]]:
 50 |         """Get all OAuth sessions"""
 51 |         data = self._load_json(self.sessions_file)
 52 |         # Clean expired sessions
 53 |         now = datetime.utcnow().timestamp()
 54 |         valid_sessions = {k: v for k, v in data.items() 
 55 |                          if v.get('expires_at', 0) > now}
 56 |         if len(valid_sessions) != len(data):
 57 |             self._save_json(self.sessions_file, valid_sessions)
 58 |         return valid_sessions
 59 |     
 60 |     def set_session(self, session_id: str, data: Dict[str, Any]):
 61 |         """Set OAuth session data"""
 62 |         sessions = self.get_sessions()
 63 |         sessions[session_id] = data
 64 |         self._save_json(self.sessions_file, sessions)
 65 |     
 66 |     def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
 67 |         """Get specific OAuth session data"""
 68 |         sessions = self.get_sessions()
 69 |         return sessions.get(session_id)
 70 |     
 71 |     def delete_session(self, session_id: str):
 72 |         """Delete OAuth session"""
 73 |         sessions = self.get_sessions()
 74 |         if session_id in sessions:
 75 |             del sessions[session_id]
 76 |             self._save_json(self.sessions_file, sessions)
 77 |     
 78 |     def get_tokens(self) -> Dict[str, Dict[str, Any]]:
 79 |         """Get all OAuth tokens"""
 80 |         data = self._load_json(self.tokens_file)
 81 |         # Clean expired tokens
 82 |         now = datetime.utcnow().timestamp()
 83 |         valid_tokens = {k: v for k, v in data.items() 
 84 |                        if v.get('expires_at', 0) > now}
 85 |         if len(valid_tokens) != len(data):
 86 |             self._save_json(self.tokens_file, valid_tokens)
 87 |         return valid_tokens
 88 |     
 89 |     def set_token(self, token_id: str, token_data: Dict[str, Any]):
 90 |         """Set OAuth token data"""
 91 |         tokens = self.get_tokens()
 92 |         tokens[token_id] = token_data
 93 |         self._save_json(self.tokens_file, tokens)
 94 |     
 95 |     def get_token(self, token_id: str) -> Optional[Dict[str, Any]]:
 96 |         """Get specific OAuth token data"""
 97 |         tokens = self.get_tokens()
 98 |         return tokens.get(token_id)
 99 |     
100 |     def delete_token(self, token_id: str):
101 |         """Delete OAuth token"""
102 |         tokens = self.get_tokens()
103 |         if token_id in tokens:
104 |             del tokens[token_id]
105 |             self._save_json(self.tokens_file, tokens)
106 |     
107 |     def cleanup_expired_sessions(self):
108 |         """Clean up expired sessions and tokens"""
109 |         # This is handled automatically in get_sessions() and get_tokens()
110 |         sessions = self.get_sessions()
111 |         tokens = self.get_tokens()
112 |         logger.debug(f"Cleanup: {len(sessions)} active sessions, {len(tokens)} active tokens")
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/uyusmazlik_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
 1 | # uyusmazlik_mcp_module/models.py
 2 | 
 3 | from pydantic import BaseModel, Field, HttpUrl
 4 | from typing import List, Optional
 5 | from enum import Enum
 6 | 
 7 | # Enum definitions for user-friendly input based on the provided HTML form
 8 | class UyusmazlikBolumEnum(str, Enum):
 9 |     """User-friendly names for 'BolumId'."""
10 |     TUMU = "ALL" # Represents "...Seçiniz..." or all
11 |     CEZA_BOLUMU = "Ceza Bölümü"
12 |     GENEL_KURUL_KARARLARI = "Genel Kurul Kararları"
13 |     HUKUK_BOLUMU = "Hukuk Bölümü"
14 | 
15 | class UyusmazlikTuruEnum(str, Enum):
16 |     """User-friendly names for 'UyusmazlikId'."""
17 |     TUMU = "ALL" # Represents "...Seçiniz..." or all
18 |     GOREV_UYUSMAZLIGI = "Görev Uyuşmazlığı"
19 |     HUKUM_UYUSMAZLIGI = "Hüküm Uyuşmazlığı"
20 | 
21 | class UyusmazlikKararSonucuEnum(str, Enum): # Based on checkbox text in the form
22 |     """User-friendly names for 'KararSonucuList' items."""
23 |     HUKUM_UYUSMAZLIGI_OLMADIGINA_DAIR = "Hüküm Uyuşmazlığı Olmadığına Dair"
24 |     HUKUM_UYUSMAZLIGI_OLDUGUNA_DAIR = "Hüküm Uyuşmazlığı Olduğuna Dair"
25 |     # Add other "Karar Sonucu" options from the form's checkboxes as Enum members
26 |     # Example: GOREVLI_YARGI_YERI_ADLI = "Görevli Yargı Yeri Belirlenmesine Dair (Adli Yargı)"
27 |     # The client will map these enum values (which are strings) to their respective IDs.
28 | 
29 | class UyusmazlikSearchRequest(BaseModel): # This is the model the MCP tool will accept
30 |     """Model for Uyuşmazlık Mahkemesi search request using user-friendly terms."""
31 |     icerik: Optional[str] = Field("", description="Search text")
32 |     
33 |     bolum: Optional[UyusmazlikBolumEnum] = Field(
34 |         UyusmazlikBolumEnum.TUMU, 
35 |         description="Department"
36 |     )
37 |     uyusmazlik_turu: Optional[UyusmazlikTuruEnum] = Field(
38 |         UyusmazlikTuruEnum.TUMU, 
39 |         description="Dispute type"
40 |     )
41 |     
42 |     # User provides a list of user-friendly names for Karar Sonucu
43 |     karar_sonuclari: Optional[List[UyusmazlikKararSonucuEnum]] = Field( # Changed to list of Enums
44 |         default_factory=list, 
45 |         description="Decision types"
46 |     )
47 |     
48 |     esas_yil: Optional[str] = Field("", description="Case year")
49 |     esas_sayisi: Optional[str] = Field("", description="Case no")
50 |     karar_yil: Optional[str] = Field("", description="Decision year")
51 |     karar_sayisi: Optional[str] = Field("", description="Decision no")
52 |     kanun_no: Optional[str] = Field("", description="Law no")
53 |     
54 |     karar_date_begin: Optional[str] = Field("", description="Start date (DD.MM.YYYY)")
55 |     karar_date_end: Optional[str] = Field("", description="End date (DD.MM.YYYY)")
56 |     
57 |     resmi_gazete_sayi: Optional[str] = Field("", description="Gazette no")
58 |     resmi_gazete_date: Optional[str] = Field("", description="Gazette date (DD.MM.YYYY)")
59 |     
60 |     # Detailed text search fields from the "icerikDetail" section of the form
61 |     tumce: Optional[str] = Field("", description="Exact phrase")
62 |     wild_card: Optional[str] = Field("", description="Wildcard search")
63 |     hepsi: Optional[str] = Field("", description="All words")
64 |     herhangi_birisi: Optional[str] = Field("", description="Any word")
65 |     not_hepsi: Optional[str] = Field("", description="Exclude words")
66 | 
67 | class UyusmazlikApiDecisionEntry(BaseModel):
68 |     """Model for an individual decision entry parsed from Uyuşmazlık API's HTML search response."""
69 |     karar_sayisi: Optional[str] = Field(None)
70 |     esas_sayisi: Optional[str] = Field(None)
71 |     bolum: Optional[str] = Field(None)
72 |     uyusmazlik_konusu: Optional[str] = Field(None)
73 |     karar_sonucu: Optional[str] = Field(None)
74 |     popover_content: Optional[str] = Field(None, description="Summary")
75 |     document_url: HttpUrl # Full URL to the decision document HTML page
76 |     pdf_url: Optional[HttpUrl] = Field(None, description="PDF URL")
77 | 
78 | class UyusmazlikSearchResponse(BaseModel): # This is what the MCP tool will return
79 |     """Response model for Uyuşmazlık Mahkemesi search results for the MCP tool."""
80 |     decisions: List[UyusmazlikApiDecisionEntry]
81 |     total_records_found: Optional[int] = Field(None, description="Total number of records found for the query, if available.")
82 | 
83 | class UyusmazlikDocumentMarkdown(BaseModel):
84 |     """Model for an Uyuşmazlık decision document, containing only Markdown content."""
85 |     source_url: HttpUrl # The URL from which the content was fetched
86 |     markdown_content: Optional[str] = Field(None, description="The decision content converted to Markdown.")
```

--------------------------------------------------------------------------------
/uyusmazlik_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
 1 | # uyusmazlik_mcp_module/models.py
 2 | 
 3 | from pydantic import BaseModel, Field, HttpUrl
 4 | from typing import List, Optional
 5 | from enum import Enum
 6 | 
 7 | # Enum definitions for user-friendly input based on the provided HTML form
 8 | class UyusmazlikBolumEnum(str, Enum):
 9 |     """User-friendly names for 'BolumId'."""
10 |     TUMU = "ALL" # Represents "...Seçiniz..." or all
11 |     CEZA_BOLUMU = "Ceza Bölümü"
12 |     GENEL_KURUL_KARARLARI = "Genel Kurul Kararları"
13 |     HUKUK_BOLUMU = "Hukuk Bölümü"
14 | 
15 | class UyusmazlikTuruEnum(str, Enum):
16 |     """User-friendly names for 'UyusmazlikId'."""
17 |     TUMU = "ALL" # Represents "...Seçiniz..." or all
18 |     GOREV_UYUSMAZLIGI = "Görev Uyuşmazlığı"
19 |     HUKUM_UYUSMAZLIGI = "Hüküm Uyuşmazlığı"
20 | 
21 | class UyusmazlikKararSonucuEnum(str, Enum): # Based on checkbox text in the form
22 |     """User-friendly names for 'KararSonucuList' items."""
23 |     HUKUM_UYUSMAZLIGI_OLMADIGINA_DAIR = "Hüküm Uyuşmazlığı Olmadığına Dair"
24 |     HUKUM_UYUSMAZLIGI_OLDUGUNA_DAIR = "Hüküm Uyuşmazlığı Olduğuna Dair"
25 |     # Add other "Karar Sonucu" options from the form's checkboxes as Enum members
26 |     # Example: GOREVLI_YARGI_YERI_ADLI = "Görevli Yargı Yeri Belirlenmesine Dair (Adli Yargı)"
27 |     # The client will map these enum values (which are strings) to their respective IDs.
28 | 
29 | class UyusmazlikSearchRequest(BaseModel): # This is the model the MCP tool will accept
30 |     """Model for Uyuşmazlık Mahkemesi search request using user-friendly terms."""
31 |     icerik: Optional[str] = Field("", description="Search text")
32 |     
33 |     bolum: Optional[UyusmazlikBolumEnum] = Field(
34 |         UyusmazlikBolumEnum.TUMU, 
35 |         description="Department"
36 |     )
37 |     uyusmazlik_turu: Optional[UyusmazlikTuruEnum] = Field(
38 |         UyusmazlikTuruEnum.TUMU, 
39 |         description="Dispute type"
40 |     )
41 |     
42 |     # User provides a list of user-friendly names for Karar Sonucu
43 |     karar_sonuclari: Optional[List[UyusmazlikKararSonucuEnum]] = Field( # Changed to list of Enums
44 |         default_factory=list, 
45 |         description="Decision types"
46 |     )
47 |     
48 |     esas_yil: Optional[str] = Field("", description="Case year")
49 |     esas_sayisi: Optional[str] = Field("", description="Case no")
50 |     karar_yil: Optional[str] = Field("", description="Decision year")
51 |     karar_sayisi: Optional[str] = Field("", description="Decision no")
52 |     kanun_no: Optional[str] = Field("", description="Law no")
53 |     
54 |     karar_date_begin: Optional[str] = Field("", description="Start date (DD.MM.YYYY)")
55 |     karar_date_end: Optional[str] = Field("", description="End date (DD.MM.YYYY)")
56 |     
57 |     resmi_gazete_sayi: Optional[str] = Field("", description="Gazette no")
58 |     resmi_gazete_date: Optional[str] = Field("", description="Gazette date (DD.MM.YYYY)")
59 |     
60 |     # Detailed text search fields from the "icerikDetail" section of the form
61 |     tumce: Optional[str] = Field("", description="Exact phrase")
62 |     wild_card: Optional[str] = Field("", description="Wildcard search")
63 |     hepsi: Optional[str] = Field("", description="All words")
64 |     herhangi_birisi: Optional[str] = Field("", description="Any word")
65 |     not_hepsi: Optional[str] = Field("", description="Exclude words")
66 | 
67 | class UyusmazlikApiDecisionEntry(BaseModel):
68 |     """Model for an individual decision entry parsed from Uyuşmazlık API's HTML search response."""
69 |     karar_sayisi: Optional[str] = Field(None)
70 |     esas_sayisi: Optional[str] = Field(None)
71 |     bolum: Optional[str] = Field(None)
72 |     uyusmazlik_konusu: Optional[str] = Field(None)
73 |     karar_sonucu: Optional[str] = Field(None)
74 |     popover_content: Optional[str] = Field(None, description="Summary")
75 |     document_url: HttpUrl # Full URL to the decision document HTML page
76 |     pdf_url: Optional[HttpUrl] = Field(None, description="PDF URL")
77 | 
78 | class UyusmazlikSearchResponse(BaseModel): # This is what the MCP tool will return
79 |     """Response model for Uyuşmazlık Mahkemesi search results for the MCP tool."""
80 |     decisions: List[UyusmazlikApiDecisionEntry]
81 |     total_records_found: Optional[int] = Field(None, description="Total number of records found for the query, if available.")
82 | 
83 | class UyusmazlikDocumentMarkdown(BaseModel):
84 |     """Model for an Uyuşmazlık decision document, containing only Markdown content."""
85 |     source_url: HttpUrl # The URL from which the content was fetched
86 |     markdown_content: Optional[str] = Field(None, description="The decision content converted to Markdown.")
```

--------------------------------------------------------------------------------
/emsal_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
  1 | # emsal_mcp_module/models.py
  2 | 
  3 | from pydantic import BaseModel, Field, HttpUrl, ConfigDict
  4 | from typing import List, Optional, Dict, Any
  5 | 
  6 | class EmsalDetailedSearchRequestData(BaseModel):
  7 |     """
  8 |     Internal model for the 'data' object in the Emsal detailed search payload.
  9 |     Field names use aliases to match the exact keys in the API payload
 10 |     (e.g., "Bam Hukuk Mahkemeleri" with spaces).
 11 |     The API expects empty strings for None/omitted optional fields.
 12 |     """
 13 |     arananKelime: Optional[str] = ""
 14 |     
 15 |     Bam_Hukuk_Mahkemeleri: str = Field("", alias="Bam Hukuk Mahkemeleri")
 16 |     Hukuk_Mahkemeleri: str = Field("", alias="Hukuk Mahkemeleri")
 17 |     # Add other specific court type fields from the form if they are separate keys in payload
 18 |     # E.g., "Ceza Mahkemeleri", "İdari Mahkemeler" etc.
 19 |     
 20 |     birimHukukMah: Optional[str] = Field("", description="Regional chambers (+ separated)") 
 21 | 
 22 |     esasYil: Optional[str] = ""
 23 |     esasIlkSiraNo: Optional[str] = ""
 24 |     esasSonSiraNo: Optional[str] = ""
 25 |     kararYil: Optional[str] = ""
 26 |     kararIlkSiraNo: Optional[str] = ""
 27 |     kararSonSiraNo: Optional[str] = ""
 28 |     baslangicTarihi: Optional[str] = ""
 29 |     bitisTarihi: Optional[str] = ""
 30 |     siralama: str # Mandatory in payload example
 31 |     siralamaDirection: str # Mandatory in payload example
 32 |     pageSize: int
 33 |     pageNumber: int
 34 |     
 35 |     model_config = ConfigDict(populate_by_name=True)  # Enables use of alias in serialization (when dumping to dict for payload)
 36 | 
 37 | class EmsalSearchRequest(BaseModel): # This is the model the MCP tool will accept
 38 |     """Model for Emsal detailed search request, with user-friendly field names."""
 39 |     keyword: str = Field("", description="Keyword")
 40 |     
 41 |     selected_bam_civil_court: str = Field("", description="BAM Civil Court")
 42 |     selected_civil_court: str = Field("", description="Civil Court")
 43 |     selected_regional_civil_chambers: List[str] = Field(default_factory=list, description="Regional chambers")
 44 | 
 45 |     case_year_esas: str = Field("", description="Case year")
 46 |     case_start_seq_esas: str = Field("", description="Start case no")
 47 |     case_end_seq_esas: str = Field("", description="End case no")
 48 |     
 49 |     decision_year_karar: str = Field("", description="Decision year")
 50 |     decision_start_seq_karar: str = Field("", description="Start decision no")
 51 |     decision_end_seq_karar: str = Field("", description="End decision no")
 52 |     
 53 |     start_date: str = Field("", description="Start date (DD.MM.YYYY)")
 54 |     end_date: str = Field("", description="End date (DD.MM.YYYY)")
 55 |     
 56 |     sort_criteria: str = Field("1", description="Sort by")
 57 |     sort_direction: str = Field("desc", description="Direction")
 58 |     
 59 |     page_number: int = Field(default=1, ge=1)
 60 |     page_size: int = Field(default=10, ge=1, le=10)
 61 | 
 62 | 
 63 | class EmsalApiDecisionEntry(BaseModel):
 64 |     """Model for an individual decision entry from the Emsal API search response."""
 65 |     id: str
 66 |     daire: str = Field("", description="Chamber")
 67 |     esasNo: str = Field("", description="Case number")
 68 |     kararNo: str = Field("", description="Decision number")
 69 |     kararTarihi: str = Field("", description="Decision date")
 70 |     arananKelime: str = Field("", description="Keyword")
 71 |     durum: str = Field("", description="Status")
 72 |     # index: Optional[int] = None # Present in Emsal response, can be added if tool needs it
 73 | 
 74 |     document_url: Optional[HttpUrl] = Field(None, description="Document URL")
 75 | 
 76 |     model_config = ConfigDict(extra='ignore')
 77 | 
 78 | class EmsalApiResponseInnerData(BaseModel):
 79 |     """Model for the inner 'data' object in the Emsal API search response."""
 80 |     data: List[EmsalApiDecisionEntry]
 81 |     recordsTotal: int
 82 |     recordsFiltered: int
 83 |     draw: int = Field(0, description="Draw counter (Çizim Sayıcısı) from API, usually for DataTables.")
 84 | 
 85 | class EmsalApiResponse(BaseModel):
 86 |     """Model for the complete search response from the Emsal API."""
 87 |     data: EmsalApiResponseInnerData
 88 |     metadata: Optional[Dict[str, Any]] = Field(None, description="Optional metadata (Meta Veri) from API, if any.")
 89 | 
 90 | class EmsalDocumentMarkdown(BaseModel):
 91 |     """Model for an Emsal decision document, containing only Markdown content."""
 92 |     id: str
 93 |     markdown_content: str = Field("", description="The decision content (Karar İçeriği) converted to Markdown.")
 94 |     source_url: HttpUrl
 95 | 
 96 | class CompactEmsalSearchResult(BaseModel):
 97 |     """A compact search result model for the MCP tool to return."""
 98 |     decisions: List[EmsalApiDecisionEntry]
 99 |     total_records: int
100 |     requested_page: int
101 |     page_size: int
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/emsal_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
  1 | # emsal_mcp_module/models.py
  2 | 
  3 | from pydantic import BaseModel, Field, HttpUrl, ConfigDict
  4 | from typing import List, Optional, Dict, Any
  5 | 
  6 | class EmsalDetailedSearchRequestData(BaseModel):
  7 |     """
  8 |     Internal model for the 'data' object in the Emsal detailed search payload.
  9 |     Field names use aliases to match the exact keys in the API payload
 10 |     (e.g., "Bam Hukuk Mahkemeleri" with spaces).
 11 |     The API expects empty strings for None/omitted optional fields.
 12 |     """
 13 |     arananKelime: Optional[str] = ""
 14 |     
 15 |     Bam_Hukuk_Mahkemeleri: str = Field("", alias="Bam Hukuk Mahkemeleri")
 16 |     Hukuk_Mahkemeleri: str = Field("", alias="Hukuk Mahkemeleri")
 17 |     # Add other specific court type fields from the form if they are separate keys in payload
 18 |     # E.g., "Ceza Mahkemeleri", "İdari Mahkemeler" etc.
 19 |     
 20 |     birimHukukMah: Optional[str] = Field("", description="Regional chambers (+ separated)") 
 21 | 
 22 |     esasYil: Optional[str] = ""
 23 |     esasIlkSiraNo: Optional[str] = ""
 24 |     esasSonSiraNo: Optional[str] = ""
 25 |     kararYil: Optional[str] = ""
 26 |     kararIlkSiraNo: Optional[str] = ""
 27 |     kararSonSiraNo: Optional[str] = ""
 28 |     baslangicTarihi: Optional[str] = ""
 29 |     bitisTarihi: Optional[str] = ""
 30 |     siralama: str # Mandatory in payload example
 31 |     siralamaDirection: str # Mandatory in payload example
 32 |     pageSize: int
 33 |     pageNumber: int
 34 |     
 35 |     model_config = ConfigDict(populate_by_name=True)  # Enables use of alias in serialization (when dumping to dict for payload)
 36 | 
 37 | class EmsalSearchRequest(BaseModel): # This is the model the MCP tool will accept
 38 |     """Model for Emsal detailed search request, with user-friendly field names."""
 39 |     keyword: str = Field("", description="Keyword")
 40 |     
 41 |     selected_bam_civil_court: str = Field("", description="BAM Civil Court")
 42 |     selected_civil_court: str = Field("", description="Civil Court")
 43 |     selected_regional_civil_chambers: List[str] = Field(default_factory=list, description="Regional chambers")
 44 | 
 45 |     case_year_esas: str = Field("", description="Case year")
 46 |     case_start_seq_esas: str = Field("", description="Start case no")
 47 |     case_end_seq_esas: str = Field("", description="End case no")
 48 |     
 49 |     decision_year_karar: str = Field("", description="Decision year")
 50 |     decision_start_seq_karar: str = Field("", description="Start decision no")
 51 |     decision_end_seq_karar: str = Field("", description="End decision no")
 52 |     
 53 |     start_date: str = Field("", description="Start date (DD.MM.YYYY)")
 54 |     end_date: str = Field("", description="End date (DD.MM.YYYY)")
 55 |     
 56 |     sort_criteria: str = Field("1", description="Sort by")
 57 |     sort_direction: str = Field("desc", description="Direction")
 58 |     
 59 |     page_number: int = Field(default=1, ge=1)
 60 |     page_size: int = Field(default=10, ge=1, le=10)
 61 | 
 62 | 
 63 | class EmsalApiDecisionEntry(BaseModel):
 64 |     """Model for an individual decision entry from the Emsal API search response."""
 65 |     id: str
 66 |     daire: str = Field("", description="Chamber")
 67 |     esasNo: str = Field("", description="Case number")
 68 |     kararNo: str = Field("", description="Decision number")
 69 |     kararTarihi: str = Field("", description="Decision date")
 70 |     arananKelime: str = Field("", description="Keyword")
 71 |     durum: str = Field("", description="Status")
 72 |     # index: Optional[int] = None # Present in Emsal response, can be added if tool needs it
 73 | 
 74 |     document_url: Optional[HttpUrl] = Field(None, description="Document URL")
 75 | 
 76 |     model_config = ConfigDict(extra='ignore')
 77 | 
 78 | class EmsalApiResponseInnerData(BaseModel):
 79 |     """Model for the inner 'data' object in the Emsal API search response."""
 80 |     data: List[EmsalApiDecisionEntry]
 81 |     recordsTotal: int
 82 |     recordsFiltered: int
 83 |     draw: int = Field(0, description="Draw counter (Çizim Sayıcısı) from API, usually for DataTables.")
 84 | 
 85 | class EmsalApiResponse(BaseModel):
 86 |     """Model for the complete search response from the Emsal API."""
 87 |     data: EmsalApiResponseInnerData
 88 |     metadata: Optional[Dict[str, Any]] = Field(None, description="Optional metadata (Meta Veri) from API, if any.")
 89 | 
 90 | class EmsalDocumentMarkdown(BaseModel):
 91 |     """Model for an Emsal decision document, containing only Markdown content."""
 92 |     id: str
 93 |     markdown_content: str = Field("", description="The decision content (Karar İçeriği) converted to Markdown.")
 94 |     source_url: HttpUrl
 95 | 
 96 | class CompactEmsalSearchResult(BaseModel):
 97 |     """A compact search result model for the MCP tool to return."""
 98 |     decisions: List[EmsalApiDecisionEntry]
 99 |     total_records: int
100 |     requested_page: int
101 |     page_size: int
```

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

```python
  1 | """
  2 | Starlette integration example for Yargı MCP Server
  3 | 
  4 | This module demonstrates how to integrate the Yargı MCP server
  5 | with a Starlette application, including authentication middleware
  6 | and custom routing.
  7 | 
  8 | Usage:
  9 |     uvicorn starlette_app:app --host 0.0.0.0 --port 8000
 10 | """
 11 | 
 12 | import os
 13 | from starlette.applications import Starlette
 14 | from starlette.routing import Mount, Route
 15 | from starlette.requests import Request
 16 | from starlette.responses import JSONResponse, PlainTextResponse, RedirectResponse
 17 | from starlette.middleware import Middleware
 18 | from starlette.middleware.cors import CORSMiddleware
 19 | from starlette.middleware.authentication import AuthenticationMiddleware
 20 | from starlette.authentication import (
 21 |     AuthenticationBackend, AuthCredentials, SimpleUser, AuthenticationError
 22 | )
 23 | 
 24 | # Import the main MCP app
 25 | from mcp_server_main import app as mcp_server
 26 | 
 27 | # Simple token authentication backend
 28 | class TokenAuthBackend(AuthenticationBackend):
 29 |     async def authenticate(self, request):
 30 |         auth_header = request.headers.get("Authorization")
 31 |         expected_token = os.getenv("API_TOKEN")
 32 |         
 33 |         # Skip auth for health check and public endpoints
 34 |         if request.url.path in ["/health", "/", "/login"]:
 35 |             return None
 36 |             
 37 |         if not expected_token:
 38 |             # No token configured, allow all
 39 |             return AuthCredentials(["authenticated"]), SimpleUser("anonymous")
 40 |             
 41 |         if not auth_header:
 42 |             raise AuthenticationError("Authorization header required")
 43 |             
 44 |         try:
 45 |             scheme, token = auth_header.split()
 46 |             if scheme.lower() != "bearer":
 47 |                 raise AuthenticationError("Invalid authentication scheme")
 48 |                 
 49 |             if token != expected_token:
 50 |                 raise AuthenticationError("Invalid token")
 51 |                 
 52 |             return AuthCredentials(["authenticated"]), SimpleUser("user")
 53 |         except ValueError:
 54 |             raise AuthenticationError("Invalid authorization header format")
 55 | 
 56 | # Homepage
 57 | async def homepage(request: Request):
 58 |     return JSONResponse({
 59 |         "service": "Yargı MCP Server",
 60 |         "version": "0.1.0",
 61 |         "endpoints": {
 62 |             "mcp": "/mcp-server/mcp/",
 63 |             "api": "/api/",
 64 |             "health": "/health"
 65 |         }
 66 |     })
 67 | 
 68 | # API info endpoint
 69 | async def api_info(request: Request):
 70 |     if not request.user.is_authenticated:
 71 |         return JSONResponse({"error": "Authentication required"}, status_code=401)
 72 |         
 73 |     return JSONResponse({
 74 |         "authenticated_as": request.user.display_name,
 75 |         "available_tools": len(mcp_server._tool_manager._tools),
 76 |         "databases": [
 77 |             "Yargıtay", "Danıştay", "Emsal", "Uyuşmazlık",
 78 |             "Anayasa", "KIK", "Rekabet", "Bedesten"
 79 |         ]
 80 |     })
 81 | 
 82 | # Health check
 83 | async def health_check(request: Request):
 84 |     return JSONResponse({
 85 |         "status": "healthy",
 86 |         "service": "Yargı MCP Server"
 87 |     })
 88 | 
 89 | # Login example (returns token for demo)
 90 | async def login(request: Request):
 91 |     token = os.getenv("API_TOKEN", "demo-token")
 92 |     return JSONResponse({
 93 |         "message": "Use this token in Authorization header",
 94 |         "example": f"Authorization: Bearer {token}",
 95 |         "note": "Set API_TOKEN environment variable to change token"
 96 |     })
 97 | 
 98 | # Create MCP ASGI app
 99 | mcp_app = mcp_server.http_app(path='/mcp')
100 | 
101 | # Configure middleware
102 | middleware = [
103 |     Middleware(
104 |         CORSMiddleware,
105 |         allow_origins=os.getenv("ALLOWED_ORIGINS", "*").split(","),
106 |         allow_credentials=True,
107 |         allow_methods=["*"],
108 |         allow_headers=["*"],
109 |     ),
110 |     Middleware(AuthenticationMiddleware, backend=TokenAuthBackend()),
111 | ]
112 | 
113 | # Create routes
114 | routes = [
115 |     Route("/", homepage),
116 |     Route("/health", health_check),
117 |     Route("/login", login),
118 |     Route("/api/info", api_info),
119 |     Mount("/mcp-server", app=mcp_app),
120 | ]
121 | 
122 | # Create Starlette app
123 | app = Starlette(
124 |     routes=routes,
125 |     middleware=middleware,
126 |     lifespan=mcp_app.lifespan
127 | )
128 | 
129 | # Nested mount example
130 | def create_nested_app():
131 |     """Example of nested mounting for complex routing structures"""
132 |     
133 |     # Create inner app with MCP
134 |     inner_app = Starlette(
135 |         routes=[Mount("/services", app=mcp_app)],
136 |         middleware=middleware
137 |     )
138 |     
139 |     # Create outer app
140 |     outer_app = Starlette(
141 |         routes=[
142 |             Route("/", homepage),
143 |             Mount("/v1", app=inner_app),
144 |         ],
145 |         lifespan=mcp_app.lifespan
146 |     )
147 |     
148 |     # MCP would be available at /v1/services/mcp/
149 |     return outer_app
150 | 
151 | # Export both apps
152 | nested_app = create_nested_app()
153 | 
154 | if __name__ == "__main__":
155 |     import uvicorn
156 |     print("Starting Starlette app with authentication...")
157 |     print("Set API_TOKEN environment variable to enable authentication")
158 |     print("Example: API_TOKEN=secret-token python starlette_app.py")
159 |     uvicorn.run(app, host="0.0.0.0", port=8000)
```

--------------------------------------------------------------------------------
/starlette_app.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Starlette integration example for Yargı MCP Server
  3 | 
  4 | This module demonstrates how to integrate the Yargı MCP server
  5 | with a Starlette application, including authentication middleware
  6 | and custom routing.
  7 | 
  8 | Usage:
  9 |     uvicorn starlette_app:app --host 0.0.0.0 --port 8000
 10 | """
 11 | 
 12 | import os
 13 | from starlette.applications import Starlette
 14 | from starlette.routing import Mount, Route
 15 | from starlette.requests import Request
 16 | from starlette.responses import JSONResponse, PlainTextResponse, RedirectResponse
 17 | from starlette.middleware import Middleware
 18 | from starlette.middleware.cors import CORSMiddleware
 19 | from starlette.middleware.authentication import AuthenticationMiddleware
 20 | from starlette.authentication import (
 21 |     AuthenticationBackend, AuthCredentials, SimpleUser, AuthenticationError
 22 | )
 23 | 
 24 | # Import the main MCP app
 25 | from mcp_server_main import app as mcp_server
 26 | 
 27 | # Simple token authentication backend
 28 | class TokenAuthBackend(AuthenticationBackend):
 29 |     async def authenticate(self, request):
 30 |         auth_header = request.headers.get("Authorization")
 31 |         expected_token = os.getenv("API_TOKEN")
 32 |         
 33 |         # Skip auth for health check and public endpoints
 34 |         if request.url.path in ["/health", "/", "/login"]:
 35 |             return None
 36 |             
 37 |         if not expected_token:
 38 |             # No token configured, allow all
 39 |             return AuthCredentials(["authenticated"]), SimpleUser("anonymous")
 40 |             
 41 |         if not auth_header:
 42 |             raise AuthenticationError("Authorization header required")
 43 |             
 44 |         try:
 45 |             scheme, token = auth_header.split()
 46 |             if scheme.lower() != "bearer":
 47 |                 raise AuthenticationError("Invalid authentication scheme")
 48 |                 
 49 |             if token != expected_token:
 50 |                 raise AuthenticationError("Invalid token")
 51 |                 
 52 |             return AuthCredentials(["authenticated"]), SimpleUser("user")
 53 |         except ValueError:
 54 |             raise AuthenticationError("Invalid authorization header format")
 55 | 
 56 | # Homepage
 57 | async def homepage(request: Request):
 58 |     return JSONResponse({
 59 |         "service": "Yargı MCP Server",
 60 |         "version": "0.1.0",
 61 |         "endpoints": {
 62 |             "mcp": "/mcp-server/mcp/",
 63 |             "api": "/api/",
 64 |             "health": "/health"
 65 |         }
 66 |     })
 67 | 
 68 | # API info endpoint
 69 | async def api_info(request: Request):
 70 |     if not request.user.is_authenticated:
 71 |         return JSONResponse({"error": "Authentication required"}, status_code=401)
 72 |         
 73 |     return JSONResponse({
 74 |         "authenticated_as": request.user.display_name,
 75 |         "available_tools": len(mcp_server._tool_manager._tools),
 76 |         "databases": [
 77 |             "Yargıtay", "Danıştay", "Emsal", "Uyuşmazlık",
 78 |             "Anayasa", "KIK", "Rekabet", "Bedesten"
 79 |         ]
 80 |     })
 81 | 
 82 | # Health check
 83 | async def health_check(request: Request):
 84 |     return JSONResponse({
 85 |         "status": "healthy",
 86 |         "service": "Yargı MCP Server"
 87 |     })
 88 | 
 89 | # Login example (returns token for demo)
 90 | async def login(request: Request):
 91 |     token = os.getenv("API_TOKEN", "demo-token")
 92 |     return JSONResponse({
 93 |         "message": "Use this token in Authorization header",
 94 |         "example": f"Authorization: Bearer {token}",
 95 |         "note": "Set API_TOKEN environment variable to change token"
 96 |     })
 97 | 
 98 | # Create MCP ASGI app
 99 | mcp_app = mcp_server.http_app(path='/mcp')
100 | 
101 | # Configure middleware
102 | middleware = [
103 |     Middleware(
104 |         CORSMiddleware,
105 |         allow_origins=os.getenv("ALLOWED_ORIGINS", "*").split(","),
106 |         allow_credentials=True,
107 |         allow_methods=["*"],
108 |         allow_headers=["*"],
109 |     ),
110 |     Middleware(AuthenticationMiddleware, backend=TokenAuthBackend()),
111 | ]
112 | 
113 | # Create routes
114 | routes = [
115 |     Route("/", homepage),
116 |     Route("/health", health_check),
117 |     Route("/login", login),
118 |     Route("/api/info", api_info),
119 |     Mount("/mcp-server", app=mcp_app),
120 | ]
121 | 
122 | # Create Starlette app
123 | app = Starlette(
124 |     routes=routes,
125 |     middleware=middleware,
126 |     lifespan=mcp_app.lifespan
127 | )
128 | 
129 | # Nested mount example
130 | def create_nested_app():
131 |     """Example of nested mounting for complex routing structures"""
132 |     
133 |     # Create inner app with MCP
134 |     inner_app = Starlette(
135 |         routes=[Mount("/services", app=mcp_app)],
136 |         middleware=middleware
137 |     )
138 |     
139 |     # Create outer app
140 |     outer_app = Starlette(
141 |         routes=[
142 |             Route("/", homepage),
143 |             Mount("/v1", app=inner_app),
144 |         ],
145 |         lifespan=mcp_app.lifespan
146 |     )
147 |     
148 |     # MCP would be available at /v1/services/mcp/
149 |     return outer_app
150 | 
151 | # Export both apps
152 | nested_app = create_nested_app()
153 | 
154 | if __name__ == "__main__":
155 |     import uvicorn
156 |     print("Starting Starlette app with authentication...")
157 |     print("Set API_TOKEN environment variable to enable authentication")
158 |     print("Example: API_TOKEN=secret-token python starlette_app.py")
159 |     uvicorn.run(app, host="0.0.0.0", port=8000)
```

--------------------------------------------------------------------------------
/bedesten_mcp_module/enums.py:
--------------------------------------------------------------------------------

```python
  1 | # bedesten_mcp_module/enums.py
  2 | 
  3 | from typing import Literal
  4 | 
  5 | # Unified compressed enum for both Yargıtay and Danıştay chambers
  6 | BirimAdiEnum = Literal[
  7 |     "ALL",  # All chambers
  8 |     
  9 |     # Yargıtay (Court of Cassation) - Civil Chambers
 10 |     "H1", "H2", "H3", "H4", "H5", "H6", "H7", "H8", "H9", "H10",
 11 |     "H11", "H12", "H13", "H14", "H15", "H16", "H17", "H18", "H19", "H20",
 12 |     "H21", "H22", "H23",
 13 |     
 14 |     # Yargıtay - Criminal Chambers
 15 |     "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10",
 16 |     "C11", "C12", "C13", "C14", "C15", "C16", "C17", "C18", "C19", "C20",
 17 |     "C21", "C22", "C23",
 18 |     
 19 |     # Yargıtay - Councils and Assemblies
 20 |     "HGK",   # Hukuk Genel Kurulu
 21 |     "CGK",   # Ceza Genel Kurulu
 22 |     "BGK",   # Büyük Genel Kurulu
 23 |     "HBK",   # Hukuk Daireleri Başkanlar Kurulu
 24 |     "CBK",   # Ceza Daireleri Başkanlar Kurulu
 25 |     
 26 |     # Danıştay (Council of State) - Chambers
 27 |     "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10",
 28 |     "D11", "D12", "D13", "D14", "D15", "D16", "D17",
 29 |     
 30 |     # Danıştay - Councils and Boards
 31 |     "DBGK",  # Büyük Gen.Kur. (Grand General Assembly)
 32 |     "IDDK",  # İdare Dava Daireleri Kurulu
 33 |     "VDDK",  # Vergi Dava Daireleri Kurulu
 34 |     "IBK",   # İçtihatları Birleştirme Kurulu
 35 |     "IIK",   # İdari İşler Kurulu
 36 |     "DBK",   # Başkanlar Kurulu
 37 |     
 38 |     # Military High Administrative Court
 39 |     "AYIM",   # Askeri Yüksek İdare Mahkemesi
 40 |     "AYIMDK", # Askeri Yüksek İdare Mahkemesi Daireler Kurulu
 41 |     "AYIMB",  # Askeri Yüksek İdare Mahkemesi Başsavcılığı
 42 |     "AYIM1",  # Askeri Yüksek İdare Mahkemesi 1. Daire
 43 |     "AYIM2",  # Askeri Yüksek İdare Mahkemesi 2. Daire
 44 |     "AYIM3"   # Askeri Yüksek İdare Mahkemesi 3. Daire
 45 | ]
 46 | 
 47 | # Mapping from abbreviated values to full Turkish API values
 48 | BIRIM_ADI_MAPPING = {
 49 |     "ALL": None,  # Will be handled specially in client
 50 |     
 51 |     # Yargıtay Civil Chambers (1-23)
 52 |     "H1": "1. Hukuk Dairesi", "H2": "2. Hukuk Dairesi", "H3": "3. Hukuk Dairesi",
 53 |     "H4": "4. Hukuk Dairesi", "H5": "5. Hukuk Dairesi", "H6": "6. Hukuk Dairesi",
 54 |     "H7": "7. Hukuk Dairesi", "H8": "8. Hukuk Dairesi", "H9": "9. Hukuk Dairesi",
 55 |     "H10": "10. Hukuk Dairesi", "H11": "11. Hukuk Dairesi", "H12": "12. Hukuk Dairesi",
 56 |     "H13": "13. Hukuk Dairesi", "H14": "14. Hukuk Dairesi", "H15": "15. Hukuk Dairesi",
 57 |     "H16": "16. Hukuk Dairesi", "H17": "17. Hukuk Dairesi", "H18": "18. Hukuk Dairesi",
 58 |     "H19": "19. Hukuk Dairesi", "H20": "20. Hukuk Dairesi", "H21": "21. Hukuk Dairesi",
 59 |     "H22": "22. Hukuk Dairesi", "H23": "23. Hukuk Dairesi",
 60 |     
 61 |     # Yargıtay Criminal Chambers (1-23)
 62 |     "C1": "1. Ceza Dairesi", "C2": "2. Ceza Dairesi", "C3": "3. Ceza Dairesi",
 63 |     "C4": "4. Ceza Dairesi", "C5": "5. Ceza Dairesi", "C6": "6. Ceza Dairesi",
 64 |     "C7": "7. Ceza Dairesi", "C8": "8. Ceza Dairesi", "C9": "9. Ceza Dairesi",
 65 |     "C10": "10. Ceza Dairesi", "C11": "11. Ceza Dairesi", "C12": "12. Ceza Dairesi",
 66 |     "C13": "13. Ceza Dairesi", "C14": "14. Ceza Dairesi", "C15": "15. Ceza Dairesi",
 67 |     "C16": "16. Ceza Dairesi", "C17": "17. Ceza Dairesi", "C18": "18. Ceza Dairesi",
 68 |     "C19": "19. Ceza Dairesi", "C20": "20. Ceza Dairesi", "C21": "21. Ceza Dairesi",
 69 |     "C22": "22. Ceza Dairesi", "C23": "23. Ceza Dairesi",
 70 |     
 71 |     # Yargıtay Councils and Assemblies
 72 |     "HGK": "Hukuk Genel Kurulu",
 73 |     "CGK": "Ceza Genel Kurulu",
 74 |     "BGK": "Büyük Genel Kurulu",
 75 |     "HBK": "Hukuk Daireleri Başkanlar Kurulu",
 76 |     "CBK": "Ceza Daireleri Başkanlar Kurulu",
 77 |     
 78 |     # Danıştay Chambers (1-17)
 79 |     "D1": "1. Daire", "D2": "2. Daire", "D3": "3. Daire", "D4": "4. Daire",
 80 |     "D5": "5. Daire", "D6": "6. Daire", "D7": "7. Daire", "D8": "8. Daire",
 81 |     "D9": "9. Daire", "D10": "10. Daire", "D11": "11. Daire", "D12": "12. Daire",
 82 |     "D13": "13. Daire", "D14": "14. Daire", "D15": "15. Daire", "D16": "16. Daire",
 83 |     "D17": "17. Daire",
 84 |     
 85 |     # Danıştay Councils and Boards
 86 |     "DBGK": "Büyük Gen.Kur.",
 87 |     "IDDK": "İdare Dava Daireleri Kurulu",
 88 |     "VDDK": "Vergi Dava Daireleri Kurulu",
 89 |     "IBK": "İçtihatları Birleştirme Kurulu",
 90 |     "IIK": "İdari İşler Kurulu",
 91 |     "DBK": "Başkanlar Kurulu",
 92 |     
 93 |     # Military High Administrative Court
 94 |     "AYIM": "Askeri Yüksek İdare Mahkemesi",
 95 |     "AYIMDK": "Askeri Yüksek İdare Mahkemesi Daireler Kurulu",
 96 |     "AYIMB": "Askeri Yüksek İdare Mahkemesi Başsavcılığı",
 97 |     "AYIM1": "Askeri Yüksek İdare Mahkemesi 1. Daire",
 98 |     "AYIM2": "Askeri Yüksek İdare Mahkemesi 2. Daire",
 99 |     "AYIM3": "Askeri Yüksek İdare Mahkemesi 3. Daire"
100 | }
101 | 
102 | # Helper function to get full Turkish name from abbreviated value
103 | def get_full_birim_adi(abbreviated_value: str) -> str:
104 |     """Convert abbreviated birimAdi value to full Turkish name for API calls."""
105 |     if abbreviated_value == "ALL" or not abbreviated_value:
106 |         return ""  # Empty string for ALL or None
107 |     
108 |     return BIRIM_ADI_MAPPING.get(abbreviated_value, abbreviated_value)
109 | 
110 | # Helper function to validate abbreviated value
111 | def is_valid_birim_adi(abbreviated_value: str) -> bool:
112 |     """Check if abbreviated birimAdi value is valid."""
113 |     return abbreviated_value in BIRIM_ADI_MAPPING
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/enums.py:
--------------------------------------------------------------------------------

```python
  1 | # bedesten_mcp_module/enums.py
  2 | 
  3 | from typing import Literal
  4 | 
  5 | # Unified compressed enum for both Yargıtay and Danıştay chambers
  6 | BirimAdiEnum = Literal[
  7 |     "ALL",  # All chambers
  8 |     
  9 |     # Yargıtay (Court of Cassation) - Civil Chambers
 10 |     "H1", "H2", "H3", "H4", "H5", "H6", "H7", "H8", "H9", "H10",
 11 |     "H11", "H12", "H13", "H14", "H15", "H16", "H17", "H18", "H19", "H20",
 12 |     "H21", "H22", "H23",
 13 |     
 14 |     # Yargıtay - Criminal Chambers
 15 |     "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10",
 16 |     "C11", "C12", "C13", "C14", "C15", "C16", "C17", "C18", "C19", "C20",
 17 |     "C21", "C22", "C23",
 18 |     
 19 |     # Yargıtay - Councils and Assemblies
 20 |     "HGK",   # Hukuk Genel Kurulu
 21 |     "CGK",   # Ceza Genel Kurulu
 22 |     "BGK",   # Büyük Genel Kurulu
 23 |     "HBK",   # Hukuk Daireleri Başkanlar Kurulu
 24 |     "CBK",   # Ceza Daireleri Başkanlar Kurulu
 25 |     
 26 |     # Danıştay (Council of State) - Chambers
 27 |     "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10",
 28 |     "D11", "D12", "D13", "D14", "D15", "D16", "D17",
 29 |     
 30 |     # Danıştay - Councils and Boards
 31 |     "DBGK",  # Büyük Gen.Kur. (Grand General Assembly)
 32 |     "IDDK",  # İdare Dava Daireleri Kurulu
 33 |     "VDDK",  # Vergi Dava Daireleri Kurulu
 34 |     "IBK",   # İçtihatları Birleştirme Kurulu
 35 |     "IIK",   # İdari İşler Kurulu
 36 |     "DBK",   # Başkanlar Kurulu
 37 |     
 38 |     # Military High Administrative Court
 39 |     "AYIM",   # Askeri Yüksek İdare Mahkemesi
 40 |     "AYIMDK", # Askeri Yüksek İdare Mahkemesi Daireler Kurulu
 41 |     "AYIMB",  # Askeri Yüksek İdare Mahkemesi Başsavcılığı
 42 |     "AYIM1",  # Askeri Yüksek İdare Mahkemesi 1. Daire
 43 |     "AYIM2",  # Askeri Yüksek İdare Mahkemesi 2. Daire
 44 |     "AYIM3"   # Askeri Yüksek İdare Mahkemesi 3. Daire
 45 | ]
 46 | 
 47 | # Mapping from abbreviated values to full Turkish API values
 48 | BIRIM_ADI_MAPPING = {
 49 |     "ALL": None,  # Will be handled specially in client
 50 |     
 51 |     # Yargıtay Civil Chambers (1-23)
 52 |     "H1": "1. Hukuk Dairesi", "H2": "2. Hukuk Dairesi", "H3": "3. Hukuk Dairesi",
 53 |     "H4": "4. Hukuk Dairesi", "H5": "5. Hukuk Dairesi", "H6": "6. Hukuk Dairesi",
 54 |     "H7": "7. Hukuk Dairesi", "H8": "8. Hukuk Dairesi", "H9": "9. Hukuk Dairesi",
 55 |     "H10": "10. Hukuk Dairesi", "H11": "11. Hukuk Dairesi", "H12": "12. Hukuk Dairesi",
 56 |     "H13": "13. Hukuk Dairesi", "H14": "14. Hukuk Dairesi", "H15": "15. Hukuk Dairesi",
 57 |     "H16": "16. Hukuk Dairesi", "H17": "17. Hukuk Dairesi", "H18": "18. Hukuk Dairesi",
 58 |     "H19": "19. Hukuk Dairesi", "H20": "20. Hukuk Dairesi", "H21": "21. Hukuk Dairesi",
 59 |     "H22": "22. Hukuk Dairesi", "H23": "23. Hukuk Dairesi",
 60 |     
 61 |     # Yargıtay Criminal Chambers (1-23)
 62 |     "C1": "1. Ceza Dairesi", "C2": "2. Ceza Dairesi", "C3": "3. Ceza Dairesi",
 63 |     "C4": "4. Ceza Dairesi", "C5": "5. Ceza Dairesi", "C6": "6. Ceza Dairesi",
 64 |     "C7": "7. Ceza Dairesi", "C8": "8. Ceza Dairesi", "C9": "9. Ceza Dairesi",
 65 |     "C10": "10. Ceza Dairesi", "C11": "11. Ceza Dairesi", "C12": "12. Ceza Dairesi",
 66 |     "C13": "13. Ceza Dairesi", "C14": "14. Ceza Dairesi", "C15": "15. Ceza Dairesi",
 67 |     "C16": "16. Ceza Dairesi", "C17": "17. Ceza Dairesi", "C18": "18. Ceza Dairesi",
 68 |     "C19": "19. Ceza Dairesi", "C20": "20. Ceza Dairesi", "C21": "21. Ceza Dairesi",
 69 |     "C22": "22. Ceza Dairesi", "C23": "23. Ceza Dairesi",
 70 |     
 71 |     # Yargıtay Councils and Assemblies
 72 |     "HGK": "Hukuk Genel Kurulu",
 73 |     "CGK": "Ceza Genel Kurulu",
 74 |     "BGK": "Büyük Genel Kurulu",
 75 |     "HBK": "Hukuk Daireleri Başkanlar Kurulu",
 76 |     "CBK": "Ceza Daireleri Başkanlar Kurulu",
 77 |     
 78 |     # Danıştay Chambers (1-17)
 79 |     "D1": "1. Daire", "D2": "2. Daire", "D3": "3. Daire", "D4": "4. Daire",
 80 |     "D5": "5. Daire", "D6": "6. Daire", "D7": "7. Daire", "D8": "8. Daire",
 81 |     "D9": "9. Daire", "D10": "10. Daire", "D11": "11. Daire", "D12": "12. Daire",
 82 |     "D13": "13. Daire", "D14": "14. Daire", "D15": "15. Daire", "D16": "16. Daire",
 83 |     "D17": "17. Daire",
 84 |     
 85 |     # Danıştay Councils and Boards
 86 |     "DBGK": "Büyük Gen.Kur.",
 87 |     "IDDK": "İdare Dava Daireleri Kurulu",
 88 |     "VDDK": "Vergi Dava Daireleri Kurulu",
 89 |     "IBK": "İçtihatları Birleştirme Kurulu",
 90 |     "IIK": "İdari İşler Kurulu",
 91 |     "DBK": "Başkanlar Kurulu",
 92 |     
 93 |     # Military High Administrative Court
 94 |     "AYIM": "Askeri Yüksek İdare Mahkemesi",
 95 |     "AYIMDK": "Askeri Yüksek İdare Mahkemesi Daireler Kurulu",
 96 |     "AYIMB": "Askeri Yüksek İdare Mahkemesi Başsavcılığı",
 97 |     "AYIM1": "Askeri Yüksek İdare Mahkemesi 1. Daire",
 98 |     "AYIM2": "Askeri Yüksek İdare Mahkemesi 2. Daire",
 99 |     "AYIM3": "Askeri Yüksek İdare Mahkemesi 3. Daire"
100 | }
101 | 
102 | # Helper function to get full Turkish name from abbreviated value
103 | def get_full_birim_adi(abbreviated_value: str) -> str:
104 |     """Convert abbreviated birimAdi value to full Turkish name for API calls."""
105 |     if abbreviated_value == "ALL" or not abbreviated_value:
106 |         return ""  # Empty string for ALL or None
107 |     
108 |     return BIRIM_ADI_MAPPING.get(abbreviated_value, abbreviated_value)
109 | 
110 | # Helper function to validate abbreviated value
111 | def is_valid_birim_adi(abbreviated_value: str) -> bool:
112 |     """Check if abbreviated birimAdi value is valid."""
113 |     return abbreviated_value in BIRIM_ADI_MAPPING
```

--------------------------------------------------------------------------------
/anayasa_mcp_module/unified_client.py:
--------------------------------------------------------------------------------

```python
  1 | # anayasa_mcp_module/unified_client.py
  2 | # Unified client for both Norm Denetimi and Bireysel Başvuru
  3 | 
  4 | import logging
  5 | from typing import Optional
  6 | from urllib.parse import urlparse
  7 | 
  8 | from .models import (
  9 |     AnayasaUnifiedSearchRequest,
 10 |     AnayasaUnifiedSearchResult, 
 11 |     AnayasaUnifiedDocumentMarkdown,
 12 |     # Removed AnayasaDecisionTypeEnum - now using string literals
 13 |     AnayasaNormDenetimiSearchRequest,
 14 |     AnayasaBireyselReportSearchRequest
 15 | )
 16 | from .client import AnayasaMahkemesiApiClient
 17 | from .bireysel_client import AnayasaBireyselBasvuruApiClient
 18 | 
 19 | logger = logging.getLogger(__name__)
 20 | 
 21 | class AnayasaUnifiedClient:
 22 |     """Unified client that handles both Norm Denetimi and Bireysel Başvuru searches."""
 23 |     
 24 |     def __init__(self, request_timeout: float = 60.0):
 25 |         self.norm_client = AnayasaMahkemesiApiClient(request_timeout)
 26 |         self.bireysel_client = AnayasaBireyselBasvuruApiClient(request_timeout)
 27 |     
 28 |     async def search_unified(self, params: AnayasaUnifiedSearchRequest) -> AnayasaUnifiedSearchResult:
 29 |         """Unified search that routes to appropriate client based on decision_type."""
 30 |         
 31 |         if params.decision_type == "norm_denetimi":
 32 |             # Convert to norm denetimi request
 33 |             norm_params = AnayasaNormDenetimiSearchRequest(
 34 |                 keywords_all=params.keywords_all or params.keywords,
 35 |                 keywords_any=params.keywords_any,
 36 |                 application_type=params.decision_type_norm,
 37 |                 page_to_fetch=params.page_to_fetch,
 38 |                 results_per_page=params.results_per_page
 39 |             )
 40 |             
 41 |             result = await self.norm_client.search_norm_denetimi_decisions(norm_params)
 42 |             
 43 |             # Convert to unified format
 44 |             decisions_list = [decision.model_dump() for decision in result.decisions]
 45 |             
 46 |             return AnayasaUnifiedSearchResult(
 47 |                 decision_type="norm_denetimi",
 48 |                 decisions=decisions_list,
 49 |                 total_records_found=result.total_records_found,
 50 |                 retrieved_page_number=result.retrieved_page_number
 51 |             )
 52 |             
 53 |         elif params.decision_type == "bireysel_basvuru":
 54 |             # Convert to bireysel başvuru request
 55 |             bireysel_params = AnayasaBireyselReportSearchRequest(
 56 |                 keywords=params.keywords,
 57 |                 decision_start_date=params.decision_start_date,
 58 |                 decision_end_date=params.decision_end_date,
 59 |                 norm_type=params.norm_type,
 60 |                 subject_category=params.subject_category,
 61 |                 page_to_fetch=params.page_to_fetch,
 62 |                 results_per_page=params.results_per_page
 63 |             )
 64 |             
 65 |             result = await self.bireysel_client.search_bireysel_basvuru_report(bireysel_params)
 66 |             
 67 |             # Convert to unified format
 68 |             decisions_list = [decision.model_dump() for decision in result.decisions]
 69 |             
 70 |             return AnayasaUnifiedSearchResult(
 71 |                 decision_type="bireysel_basvuru",
 72 |                 decisions=decisions_list,
 73 |                 total_records_found=result.total_records_found,
 74 |                 retrieved_page_number=result.retrieved_page_number
 75 |             )
 76 |         
 77 |         else:
 78 |             raise ValueError(f"Unsupported decision type: {params.decision_type}")
 79 |     
 80 |     async def get_document_unified(self, document_url: str, page_number: int = 1) -> AnayasaUnifiedDocumentMarkdown:
 81 |         """Unified document retrieval that auto-detects the appropriate client."""
 82 |         
 83 |         # Auto-detect decision type based on URL
 84 |         parsed_url = urlparse(document_url)
 85 |         
 86 |         if "normkararlarbilgibankasi" in parsed_url.netloc or "/ND/" in document_url:
 87 |             # Norm Denetimi document
 88 |             result = await self.norm_client.get_decision_document_as_markdown(document_url, page_number)
 89 |             
 90 |             return AnayasaUnifiedDocumentMarkdown(
 91 |                 decision_type="norm_denetimi",
 92 |                 source_url=result.source_url,
 93 |                 document_data=result.model_dump(),
 94 |                 markdown_chunk=result.markdown_chunk,
 95 |                 current_page=result.current_page,
 96 |                 total_pages=result.total_pages,
 97 |                 is_paginated=result.is_paginated
 98 |             )
 99 |             
100 |         elif "kararlarbilgibankasi" in parsed_url.netloc or "/BB/" in document_url:
101 |             # Bireysel Başvuru document
102 |             result = await self.bireysel_client.get_decision_document_as_markdown(document_url, page_number)
103 |             
104 |             return AnayasaUnifiedDocumentMarkdown(
105 |                 decision_type="bireysel_basvuru",
106 |                 source_url=result.source_url,
107 |                 document_data=result.model_dump(),
108 |                 markdown_chunk=result.markdown_chunk,
109 |                 current_page=result.current_page,
110 |                 total_pages=result.total_pages,
111 |                 is_paginated=result.is_paginated
112 |             )
113 |         
114 |         else:
115 |             raise ValueError(f"Cannot determine document type from URL: {document_url}")
116 |     
117 |     async def close_client_session(self):
118 |         """Close both client sessions."""
119 |         if hasattr(self.norm_client, 'close_client_session'):
120 |             await self.norm_client.close_client_session()
121 |         if hasattr(self.bireysel_client, 'close_client_session'):
122 |             await self.bireysel_client.close_client_session()
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/unified_client.py:
--------------------------------------------------------------------------------

```python
  1 | # anayasa_mcp_module/unified_client.py
  2 | # Unified client for both Norm Denetimi and Bireysel Başvuru
  3 | 
  4 | import logging
  5 | from typing import Optional
  6 | from urllib.parse import urlparse
  7 | 
  8 | from .models import (
  9 |     AnayasaUnifiedSearchRequest,
 10 |     AnayasaUnifiedSearchResult, 
 11 |     AnayasaUnifiedDocumentMarkdown,
 12 |     # Removed AnayasaDecisionTypeEnum - now using string literals
 13 |     AnayasaNormDenetimiSearchRequest,
 14 |     AnayasaBireyselReportSearchRequest
 15 | )
 16 | from .client import AnayasaMahkemesiApiClient
 17 | from .bireysel_client import AnayasaBireyselBasvuruApiClient
 18 | 
 19 | logger = logging.getLogger(__name__)
 20 | 
 21 | class AnayasaUnifiedClient:
 22 |     """Unified client that handles both Norm Denetimi and Bireysel Başvuru searches."""
 23 |     
 24 |     def __init__(self, request_timeout: float = 60.0):
 25 |         self.norm_client = AnayasaMahkemesiApiClient(request_timeout)
 26 |         self.bireysel_client = AnayasaBireyselBasvuruApiClient(request_timeout)
 27 |     
 28 |     async def search_unified(self, params: AnayasaUnifiedSearchRequest) -> AnayasaUnifiedSearchResult:
 29 |         """Unified search that routes to appropriate client based on decision_type."""
 30 |         
 31 |         if params.decision_type == "norm_denetimi":
 32 |             # Convert to norm denetimi request
 33 |             norm_params = AnayasaNormDenetimiSearchRequest(
 34 |                 keywords_all=params.keywords_all or params.keywords,
 35 |                 keywords_any=params.keywords_any,
 36 |                 application_type=params.decision_type_norm,
 37 |                 page_to_fetch=params.page_to_fetch,
 38 |                 results_per_page=params.results_per_page
 39 |             )
 40 |             
 41 |             result = await self.norm_client.search_norm_denetimi_decisions(norm_params)
 42 |             
 43 |             # Convert to unified format
 44 |             decisions_list = [decision.model_dump() for decision in result.decisions]
 45 |             
 46 |             return AnayasaUnifiedSearchResult(
 47 |                 decision_type="norm_denetimi",
 48 |                 decisions=decisions_list,
 49 |                 total_records_found=result.total_records_found,
 50 |                 retrieved_page_number=result.retrieved_page_number
 51 |             )
 52 |             
 53 |         elif params.decision_type == "bireysel_basvuru":
 54 |             # Convert to bireysel başvuru request
 55 |             bireysel_params = AnayasaBireyselReportSearchRequest(
 56 |                 keywords=params.keywords,
 57 |                 decision_start_date=params.decision_start_date,
 58 |                 decision_end_date=params.decision_end_date,
 59 |                 norm_type=params.norm_type,
 60 |                 subject_category=params.subject_category,
 61 |                 page_to_fetch=params.page_to_fetch,
 62 |                 results_per_page=params.results_per_page
 63 |             )
 64 |             
 65 |             result = await self.bireysel_client.search_bireysel_basvuru_report(bireysel_params)
 66 |             
 67 |             # Convert to unified format
 68 |             decisions_list = [decision.model_dump() for decision in result.decisions]
 69 |             
 70 |             return AnayasaUnifiedSearchResult(
 71 |                 decision_type="bireysel_basvuru",
 72 |                 decisions=decisions_list,
 73 |                 total_records_found=result.total_records_found,
 74 |                 retrieved_page_number=result.retrieved_page_number
 75 |             )
 76 |         
 77 |         else:
 78 |             raise ValueError(f"Unsupported decision type: {params.decision_type}")
 79 |     
 80 |     async def get_document_unified(self, document_url: str, page_number: int = 1) -> AnayasaUnifiedDocumentMarkdown:
 81 |         """Unified document retrieval that auto-detects the appropriate client."""
 82 |         
 83 |         # Auto-detect decision type based on URL
 84 |         parsed_url = urlparse(document_url)
 85 |         
 86 |         if "normkararlarbilgibankasi" in parsed_url.netloc or "/ND/" in document_url:
 87 |             # Norm Denetimi document
 88 |             result = await self.norm_client.get_decision_document_as_markdown(document_url, page_number)
 89 |             
 90 |             return AnayasaUnifiedDocumentMarkdown(
 91 |                 decision_type="norm_denetimi",
 92 |                 source_url=result.source_url,
 93 |                 document_data=result.model_dump(),
 94 |                 markdown_chunk=result.markdown_chunk,
 95 |                 current_page=result.current_page,
 96 |                 total_pages=result.total_pages,
 97 |                 is_paginated=result.is_paginated
 98 |             )
 99 |             
100 |         elif "kararlarbilgibankasi" in parsed_url.netloc or "/BB/" in document_url:
101 |             # Bireysel Başvuru document
102 |             result = await self.bireysel_client.get_decision_document_as_markdown(document_url, page_number)
103 |             
104 |             return AnayasaUnifiedDocumentMarkdown(
105 |                 decision_type="bireysel_basvuru",
106 |                 source_url=result.source_url,
107 |                 document_data=result.model_dump(),
108 |                 markdown_chunk=result.markdown_chunk,
109 |                 current_page=result.current_page,
110 |                 total_pages=result.total_pages,
111 |                 is_paginated=result.is_paginated
112 |             )
113 |         
114 |         else:
115 |             raise ValueError(f"Cannot determine document type from URL: {document_url}")
116 |     
117 |     async def close_client_session(self):
118 |         """Close both client sessions."""
119 |         if hasattr(self.norm_client, 'close_client_session'):
120 |             await self.norm_client.close_client_session()
121 |         if hasattr(self.bireysel_client, 'close_client_session'):
122 |             await self.bireysel_client.close_client_session()
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/yargitay_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
  1 | # yargitay_mcp_module/models.py
  2 | 
  3 | from pydantic import BaseModel, Field, HttpUrl, ConfigDict
  4 | from typing import List, Optional, Dict, Any, Literal
  5 | 
  6 | # Yargıtay Chamber/Board Options
  7 | YargitayBirimEnum = Literal[
  8 |     "ALL",  # "ALL" for all chambers
  9 |     # Hukuk (Civil) Chambers
 10 |     "Hukuk Genel Kurulu",
 11 |     "1. Hukuk Dairesi", "2. Hukuk Dairesi", "3. Hukuk Dairesi", "4. Hukuk Dairesi",
 12 |     "5. Hukuk Dairesi", "6. Hukuk Dairesi", "7. Hukuk Dairesi", "8. Hukuk Dairesi",
 13 |     "9. Hukuk Dairesi", "10. Hukuk Dairesi", "11. Hukuk Dairesi", "12. Hukuk Dairesi",
 14 |     "13. Hukuk Dairesi", "14. Hukuk Dairesi", "15. Hukuk Dairesi", "16. Hukuk Dairesi",
 15 |     "17. Hukuk Dairesi", "18. Hukuk Dairesi", "19. Hukuk Dairesi", "20. Hukuk Dairesi",
 16 |     "21. Hukuk Dairesi", "22. Hukuk Dairesi", "23. Hukuk Dairesi",
 17 |     "Hukuk Daireleri Başkanlar Kurulu",
 18 |     # Ceza (Criminal) Chambers
 19 |     "Ceza Genel Kurulu", 
 20 |     "1. Ceza Dairesi", "2. Ceza Dairesi", "3. Ceza Dairesi", "4. Ceza Dairesi",
 21 |     "5. Ceza Dairesi", "6. Ceza Dairesi", "7. Ceza Dairesi", "8. Ceza Dairesi",
 22 |     "9. Ceza Dairesi", "10. Ceza Dairesi", "11. Ceza Dairesi", "12. Ceza Dairesi",
 23 |     "13. Ceza Dairesi", "14. Ceza Dairesi", "15. Ceza Dairesi", "16. Ceza Dairesi",
 24 |     "17. Ceza Dairesi", "18. Ceza Dairesi", "19. Ceza Dairesi", "20. Ceza Dairesi",
 25 |     "21. Ceza Dairesi", "22. Ceza Dairesi", "23. Ceza Dairesi",
 26 |     "Ceza Daireleri Başkanlar Kurulu",
 27 |     # General Assembly
 28 |     "Büyük Genel Kurulu"
 29 | ]
 30 | 
 31 | class YargitayDetailedSearchRequest(BaseModel):
 32 |     """
 33 |     Model for the 'data' object sent in the request payload
 34 |     to Yargitay's detailed search endpoint (e.g., /aramadetaylist).
 35 |     Based on the payload provided by the user.
 36 |     """
 37 |     arananKelime: Optional[str] = Field("", description="Turkish keywords (supports +word -word \"phrase\" operators)")
 38 |     # Department/Board selection - Complete Court of Cassation chamber hierarchy
 39 |     birimYrgKurulDaire: Optional[str] = Field("ALL", description="Chamber (ALL or specific chamber name)")
 40 |     
 41 |     esasYil: Optional[str] = Field("", description="Case year (YYYY)")
 42 |     esasIlkSiraNo: Optional[str] = Field("", description="Start case no")
 43 |     esasSonSiraNo: Optional[str] = Field("", description="End case no")
 44 |     
 45 |     kararYil: Optional[str] = Field("", description="Decision year (YYYY)")
 46 |     kararIlkSiraNo: Optional[str] = Field("", description="Start decision no")
 47 |     kararSonSiraNo: Optional[str] = Field("", description="End decision no")
 48 |     
 49 |     baslangicTarihi: Optional[str] = Field("", description="Start date (DD.MM.YYYY)")
 50 |     bitisTarihi: Optional[str] = Field("", description="End date (DD.MM.YYYY)")
 51 |     
 52 |     
 53 |     pageSize: int = Field(10, ge=1, le=10, description="Results per page (1-100)")
 54 |     pageNumber: int = Field(1, ge=1, description="Page number (1-indexed)")
 55 | 
 56 | class YargitayApiDecisionEntry(BaseModel):
 57 |     """Model for an individual decision entry from the Yargitay API search response."""
 58 |     id: str # Unique system ID of the decision
 59 |     daire: Optional[str] = Field(None, description="Chamber")
 60 |     esasNo: Optional[str] = Field(None, alias="esasNo", description="Case no")
 61 |     kararNo: Optional[str] = Field(None, alias="kararNo", description="Decision no")
 62 |     kararTarihi: Optional[str] = Field(None, alias="kararTarihi", description="Date")
 63 |     # 'index' and 'siraNo' from API response are not critical for MCP tool, so omitted for brevity
 64 |     
 65 |     # This field will be populated by the client after fetching the search list
 66 |     document_url: Optional[HttpUrl] = Field(None, description="Document URL")
 67 | 
 68 |     model_config = ConfigDict(populate_by_name=True)  # To allow populating by alias from API response
 69 | 
 70 | 
 71 | class YargitayApiResponseInnerData(BaseModel):
 72 |     """Model for the inner 'data' object in the Yargitay API search response."""
 73 |     data: List[YargitayApiDecisionEntry] = Field(default_factory=list)
 74 |     # draw: Optional[int] = None # Typically used by DataTables, not essential for MCP
 75 |     recordsTotal: int = Field(default=0) # Total number of records matching the query
 76 |     recordsFiltered: int = Field(default=0) # Total number of records after filtering (usually same as recordsTotal)
 77 | 
 78 | class YargitayApiSearchResponse(BaseModel):
 79 |     """Model for the complete search response from the Yargitay API."""
 80 |     data: Optional[YargitayApiResponseInnerData] = Field(default_factory=lambda: YargitayApiResponseInnerData())
 81 |     # metadata: Optional[Dict[str, Any]] = None # Optional metadata from API
 82 | 
 83 | class YargitayDocumentMarkdown(BaseModel):
 84 |     """Model for a Yargitay decision document, containing only Markdown content."""
 85 |     id: str = Field(..., description="Document ID")
 86 |     markdown_content: Optional[str] = Field(None, description="Content")
 87 |     source_url: HttpUrl = Field(..., description="Source URL")
 88 | 
 89 | class CleanYargitayDecisionEntry(BaseModel):
 90 |     """Clean decision entry without arananKelime field to reduce token usage."""
 91 |     id: str
 92 |     daire: Optional[str] = Field(None, description="Chamber")
 93 |     esasNo: Optional[str] = Field(None, description="Case no")
 94 |     kararNo: Optional[str] = Field(None, description="Decision no")
 95 |     kararTarihi: Optional[str] = Field(None, description="Date")
 96 |     document_url: Optional[HttpUrl] = Field(None, description="Document URL")
 97 | 
 98 | class CompactYargitaySearchResult(BaseModel):
 99 |     """A more compact search result model for the MCP tool to return."""
100 |     decisions: List[CleanYargitayDecisionEntry]
101 |     total_records: int
102 |     requested_page: int
103 |     page_size: int
```

--------------------------------------------------------------------------------
/yargitay_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
  1 | # yargitay_mcp_module/models.py
  2 | 
  3 | from pydantic import BaseModel, Field, HttpUrl, ConfigDict
  4 | from typing import List, Optional, Dict, Any, Literal
  5 | 
  6 | # Yargıtay Chamber/Board Options
  7 | YargitayBirimEnum = Literal[
  8 |     "ALL",  # "ALL" for all chambers
  9 |     # Hukuk (Civil) Chambers
 10 |     "Hukuk Genel Kurulu",
 11 |     "1. Hukuk Dairesi", "2. Hukuk Dairesi", "3. Hukuk Dairesi", "4. Hukuk Dairesi",
 12 |     "5. Hukuk Dairesi", "6. Hukuk Dairesi", "7. Hukuk Dairesi", "8. Hukuk Dairesi",
 13 |     "9. Hukuk Dairesi", "10. Hukuk Dairesi", "11. Hukuk Dairesi", "12. Hukuk Dairesi",
 14 |     "13. Hukuk Dairesi", "14. Hukuk Dairesi", "15. Hukuk Dairesi", "16. Hukuk Dairesi",
 15 |     "17. Hukuk Dairesi", "18. Hukuk Dairesi", "19. Hukuk Dairesi", "20. Hukuk Dairesi",
 16 |     "21. Hukuk Dairesi", "22. Hukuk Dairesi", "23. Hukuk Dairesi",
 17 |     "Hukuk Daireleri Başkanlar Kurulu",
 18 |     # Ceza (Criminal) Chambers
 19 |     "Ceza Genel Kurulu", 
 20 |     "1. Ceza Dairesi", "2. Ceza Dairesi", "3. Ceza Dairesi", "4. Ceza Dairesi",
 21 |     "5. Ceza Dairesi", "6. Ceza Dairesi", "7. Ceza Dairesi", "8. Ceza Dairesi",
 22 |     "9. Ceza Dairesi", "10. Ceza Dairesi", "11. Ceza Dairesi", "12. Ceza Dairesi",
 23 |     "13. Ceza Dairesi", "14. Ceza Dairesi", "15. Ceza Dairesi", "16. Ceza Dairesi",
 24 |     "17. Ceza Dairesi", "18. Ceza Dairesi", "19. Ceza Dairesi", "20. Ceza Dairesi",
 25 |     "21. Ceza Dairesi", "22. Ceza Dairesi", "23. Ceza Dairesi",
 26 |     "Ceza Daireleri Başkanlar Kurulu",
 27 |     # General Assembly
 28 |     "Büyük Genel Kurulu"
 29 | ]
 30 | 
 31 | class YargitayDetailedSearchRequest(BaseModel):
 32 |     """
 33 |     Model for the 'data' object sent in the request payload
 34 |     to Yargitay's detailed search endpoint (e.g., /aramadetaylist).
 35 |     Based on the payload provided by the user.
 36 |     """
 37 |     arananKelime: Optional[str] = Field("", description="Turkish keywords (supports +word -word \"phrase\" operators)")
 38 |     # Department/Board selection - Complete Court of Cassation chamber hierarchy
 39 |     birimYrgKurulDaire: Optional[str] = Field("ALL", description="Chamber (ALL or specific chamber name)")
 40 |     
 41 |     esasYil: Optional[str] = Field("", description="Case year (YYYY)")
 42 |     esasIlkSiraNo: Optional[str] = Field("", description="Start case no")
 43 |     esasSonSiraNo: Optional[str] = Field("", description="End case no")
 44 |     
 45 |     kararYil: Optional[str] = Field("", description="Decision year (YYYY)")
 46 |     kararIlkSiraNo: Optional[str] = Field("", description="Start decision no")
 47 |     kararSonSiraNo: Optional[str] = Field("", description="End decision no")
 48 |     
 49 |     baslangicTarihi: Optional[str] = Field("", description="Start date (DD.MM.YYYY)")
 50 |     bitisTarihi: Optional[str] = Field("", description="End date (DD.MM.YYYY)")
 51 |     
 52 |     
 53 |     pageSize: int = Field(10, ge=1, le=10, description="Results per page (1-100)")
 54 |     pageNumber: int = Field(1, ge=1, description="Page number (1-indexed)")
 55 | 
 56 | class YargitayApiDecisionEntry(BaseModel):
 57 |     """Model for an individual decision entry from the Yargitay API search response."""
 58 |     id: str # Unique system ID of the decision
 59 |     daire: Optional[str] = Field(None, description="Chamber")
 60 |     esasNo: Optional[str] = Field(None, alias="esasNo", description="Case no")
 61 |     kararNo: Optional[str] = Field(None, alias="kararNo", description="Decision no")
 62 |     kararTarihi: Optional[str] = Field(None, alias="kararTarihi", description="Date")
 63 |     # 'index' and 'siraNo' from API response are not critical for MCP tool, so omitted for brevity
 64 |     
 65 |     # This field will be populated by the client after fetching the search list
 66 |     document_url: Optional[HttpUrl] = Field(None, description="Document URL")
 67 | 
 68 |     model_config = ConfigDict(populate_by_name=True)  # To allow populating by alias from API response
 69 | 
 70 | 
 71 | class YargitayApiResponseInnerData(BaseModel):
 72 |     """Model for the inner 'data' object in the Yargitay API search response."""
 73 |     data: List[YargitayApiDecisionEntry] = Field(default_factory=list)
 74 |     # draw: Optional[int] = None # Typically used by DataTables, not essential for MCP
 75 |     recordsTotal: int = Field(default=0) # Total number of records matching the query
 76 |     recordsFiltered: int = Field(default=0) # Total number of records after filtering (usually same as recordsTotal)
 77 | 
 78 | class YargitayApiSearchResponse(BaseModel):
 79 |     """Model for the complete search response from the Yargitay API."""
 80 |     data: Optional[YargitayApiResponseInnerData] = Field(default_factory=lambda: YargitayApiResponseInnerData())
 81 |     # metadata: Optional[Dict[str, Any]] = None # Optional metadata from API
 82 | 
 83 | class YargitayDocumentMarkdown(BaseModel):
 84 |     """Model for a Yargitay decision document, containing only Markdown content."""
 85 |     id: str = Field(..., description="Document ID")
 86 |     markdown_content: Optional[str] = Field(None, description="Content")
 87 |     source_url: HttpUrl = Field(..., description="Source URL")
 88 | 
 89 | class CleanYargitayDecisionEntry(BaseModel):
 90 |     """Clean decision entry without arananKelime field to reduce token usage."""
 91 |     id: str
 92 |     daire: Optional[str] = Field(None, description="Chamber")
 93 |     esasNo: Optional[str] = Field(None, description="Case no")
 94 |     kararNo: Optional[str] = Field(None, description="Decision no")
 95 |     kararTarihi: Optional[str] = Field(None, description="Date")
 96 |     document_url: Optional[HttpUrl] = Field(None, description="Document URL")
 97 | 
 98 | class CompactYargitaySearchResult(BaseModel):
 99 |     """A more compact search result model for the MCP tool to return."""
100 |     decisions: List[CleanYargitayDecisionEntry]
101 |     total_records: int
102 |     requested_page: int
103 |     page_size: int
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/sayistay_mcp_module/unified_client.py:
--------------------------------------------------------------------------------

```python
  1 | # sayistay_mcp_module/unified_client.py
  2 | # Unified client for all three Sayıştay decision types
  3 | 
  4 | import logging
  5 | from typing import Optional, Dict, Any
  6 | from urllib.parse import urlparse
  7 | 
  8 | from .models import (
  9 |     SayistayUnifiedSearchRequest,
 10 |     SayistayUnifiedSearchResult,
 11 |     SayistayUnifiedDocumentMarkdown,
 12 |     GenelKurulSearchRequest,
 13 |     TemyizKuruluSearchRequest,
 14 |     DaireSearchRequest
 15 | )
 16 | from .client import SayistayApiClient
 17 | 
 18 | logger = logging.getLogger(__name__)
 19 | 
 20 | class SayistayUnifiedClient:
 21 |     """Unified client that handles all three Sayıştay decision types."""
 22 |     
 23 |     def __init__(self, request_timeout: float = 60.0):
 24 |         self.client = SayistayApiClient(request_timeout)
 25 |     
 26 |     async def search_unified(self, params: SayistayUnifiedSearchRequest) -> SayistayUnifiedSearchResult:
 27 |         """Unified search that routes to appropriate search method based on decision_type."""
 28 |         
 29 |         if params.decision_type == "genel_kurul":
 30 |             # Convert to genel kurul request
 31 |             genel_kurul_params = GenelKurulSearchRequest(
 32 |                 karar_no=params.karar_no,
 33 |                 karar_ek=params.karar_ek,
 34 |                 karar_tarih_baslangic=params.karar_tarih_baslangic,
 35 |                 karar_tarih_bitis=params.karar_tarih_bitis,
 36 |                 karar_tamami=params.karar_tamami,
 37 |                 start=params.start,
 38 |                 length=params.length
 39 |             )
 40 |             
 41 |             result = await self.client.search_genel_kurul_decisions(genel_kurul_params)
 42 |             
 43 |             # Convert to unified format
 44 |             decisions_list = [decision.model_dump() for decision in result.decisions]
 45 |             
 46 |             return SayistayUnifiedSearchResult(
 47 |                 decision_type="genel_kurul",
 48 |                 decisions=decisions_list,
 49 |                 total_records=result.total_records,
 50 |                 total_filtered=result.total_filtered,
 51 |                 draw=result.draw
 52 |             )
 53 |             
 54 |         elif params.decision_type == "temyiz_kurulu":
 55 |             # Convert to temyiz kurulu request
 56 |             temyiz_params = TemyizKuruluSearchRequest(
 57 |                 ilam_dairesi=params.ilam_dairesi,
 58 |                 yili=params.yili,
 59 |                 karar_tarih_baslangic=params.karar_tarih_baslangic,
 60 |                 karar_tarih_bitis=params.karar_tarih_bitis,
 61 |                 kamu_idaresi_turu=params.kamu_idaresi_turu,
 62 |                 ilam_no=params.ilam_no,
 63 |                 dosya_no=params.dosya_no,
 64 |                 temyiz_tutanak_no=params.temyiz_tutanak_no,
 65 |                 temyiz_karar=params.temyiz_karar,
 66 |                 web_karar_konusu=params.web_karar_konusu,
 67 |                 start=params.start,
 68 |                 length=params.length
 69 |             )
 70 |             
 71 |             result = await self.client.search_temyiz_kurulu_decisions(temyiz_params)
 72 |             
 73 |             # Convert to unified format
 74 |             decisions_list = [decision.model_dump() for decision in result.decisions]
 75 |             
 76 |             return SayistayUnifiedSearchResult(
 77 |                 decision_type="temyiz_kurulu",
 78 |                 decisions=decisions_list,
 79 |                 total_records=result.total_records,
 80 |                 total_filtered=result.total_filtered,
 81 |                 draw=result.draw
 82 |             )
 83 |             
 84 |         elif params.decision_type == "daire":
 85 |             # Convert to daire request
 86 |             daire_params = DaireSearchRequest(
 87 |                 yargilama_dairesi=params.yargilama_dairesi,
 88 |                 karar_tarih_baslangic=params.karar_tarih_baslangic,
 89 |                 karar_tarih_bitis=params.karar_tarih_bitis,
 90 |                 ilam_no=params.ilam_no,
 91 |                 kamu_idaresi_turu=params.kamu_idaresi_turu,
 92 |                 hesap_yili=params.hesap_yili,
 93 |                 web_karar_konusu=params.web_karar_konusu,
 94 |                 web_karar_metni=params.web_karar_metni,
 95 |                 start=params.start,
 96 |                 length=params.length
 97 |             )
 98 |             
 99 |             result = await self.client.search_daire_decisions(daire_params)
100 |             
101 |             # Convert to unified format
102 |             decisions_list = [decision.model_dump() for decision in result.decisions]
103 |             
104 |             return SayistayUnifiedSearchResult(
105 |                 decision_type="daire",
106 |                 decisions=decisions_list,
107 |                 total_records=result.total_records,
108 |                 total_filtered=result.total_filtered,
109 |                 draw=result.draw
110 |             )
111 |         
112 |         else:
113 |             raise ValueError(f"Unsupported decision type: {params.decision_type}")
114 |     
115 |     async def get_document_unified(self, decision_id: str, decision_type: str) -> SayistayUnifiedDocumentMarkdown:
116 |         """Unified document retrieval for all Sayıştay decision types."""
117 |         
118 |         # Use existing client method (decision_type is already a string)
119 |         result = await self.client.get_document_as_markdown(decision_id, decision_type)
120 |         
121 |         return SayistayUnifiedDocumentMarkdown(
122 |             decision_type=decision_type,
123 |             decision_id=result.decision_id,
124 |             source_url=result.source_url,
125 |             document_data=result.model_dump(),
126 |             markdown_content=result.markdown_content,
127 |             error_message=result.error_message
128 |         )
129 |     
130 |     async def close_client_session(self):
131 |         """Close the underlying client session."""
132 |         if hasattr(self.client, 'close_client_session'):
133 |             await self.client.close_client_session()
```

--------------------------------------------------------------------------------
/sayistay_mcp_module/unified_client.py:
--------------------------------------------------------------------------------

```python
  1 | # sayistay_mcp_module/unified_client.py
  2 | # Unified client for all three Sayıştay decision types
  3 | 
  4 | import logging
  5 | from typing import Optional, Dict, Any
  6 | from urllib.parse import urlparse
  7 | 
  8 | from .models import (
  9 |     SayistayUnifiedSearchRequest,
 10 |     SayistayUnifiedSearchResult,
 11 |     SayistayUnifiedDocumentMarkdown,
 12 |     GenelKurulSearchRequest,
 13 |     TemyizKuruluSearchRequest,
 14 |     DaireSearchRequest
 15 | )
 16 | from .client import SayistayApiClient
 17 | 
 18 | logger = logging.getLogger(__name__)
 19 | 
 20 | class SayistayUnifiedClient:
 21 |     """Unified client that handles all three Sayıştay decision types."""
 22 |     
 23 |     def __init__(self, request_timeout: float = 60.0):
 24 |         self.client = SayistayApiClient(request_timeout)
 25 |     
 26 |     async def search_unified(self, params: SayistayUnifiedSearchRequest) -> SayistayUnifiedSearchResult:
 27 |         """Unified search that routes to appropriate search method based on decision_type."""
 28 |         
 29 |         if params.decision_type == "genel_kurul":
 30 |             # Convert to genel kurul request
 31 |             genel_kurul_params = GenelKurulSearchRequest(
 32 |                 karar_no=params.karar_no,
 33 |                 karar_ek=params.karar_ek,
 34 |                 karar_tarih_baslangic=params.karar_tarih_baslangic,
 35 |                 karar_tarih_bitis=params.karar_tarih_bitis,
 36 |                 karar_tamami=params.karar_tamami,
 37 |                 start=params.start,
 38 |                 length=params.length
 39 |             )
 40 |             
 41 |             result = await self.client.search_genel_kurul_decisions(genel_kurul_params)
 42 |             
 43 |             # Convert to unified format
 44 |             decisions_list = [decision.model_dump() for decision in result.decisions]
 45 |             
 46 |             return SayistayUnifiedSearchResult(
 47 |                 decision_type="genel_kurul",
 48 |                 decisions=decisions_list,
 49 |                 total_records=result.total_records,
 50 |                 total_filtered=result.total_filtered,
 51 |                 draw=result.draw
 52 |             )
 53 |             
 54 |         elif params.decision_type == "temyiz_kurulu":
 55 |             # Convert to temyiz kurulu request
 56 |             temyiz_params = TemyizKuruluSearchRequest(
 57 |                 ilam_dairesi=params.ilam_dairesi,
 58 |                 yili=params.yili,
 59 |                 karar_tarih_baslangic=params.karar_tarih_baslangic,
 60 |                 karar_tarih_bitis=params.karar_tarih_bitis,
 61 |                 kamu_idaresi_turu=params.kamu_idaresi_turu,
 62 |                 ilam_no=params.ilam_no,
 63 |                 dosya_no=params.dosya_no,
 64 |                 temyiz_tutanak_no=params.temyiz_tutanak_no,
 65 |                 temyiz_karar=params.temyiz_karar,
 66 |                 web_karar_konusu=params.web_karar_konusu,
 67 |                 start=params.start,
 68 |                 length=params.length
 69 |             )
 70 |             
 71 |             result = await self.client.search_temyiz_kurulu_decisions(temyiz_params)
 72 |             
 73 |             # Convert to unified format
 74 |             decisions_list = [decision.model_dump() for decision in result.decisions]
 75 |             
 76 |             return SayistayUnifiedSearchResult(
 77 |                 decision_type="temyiz_kurulu",
 78 |                 decisions=decisions_list,
 79 |                 total_records=result.total_records,
 80 |                 total_filtered=result.total_filtered,
 81 |                 draw=result.draw
 82 |             )
 83 |             
 84 |         elif params.decision_type == "daire":
 85 |             # Convert to daire request
 86 |             daire_params = DaireSearchRequest(
 87 |                 yargilama_dairesi=params.yargilama_dairesi,
 88 |                 karar_tarih_baslangic=params.karar_tarih_baslangic,
 89 |                 karar_tarih_bitis=params.karar_tarih_bitis,
 90 |                 ilam_no=params.ilam_no,
 91 |                 kamu_idaresi_turu=params.kamu_idaresi_turu,
 92 |                 hesap_yili=params.hesap_yili,
 93 |                 web_karar_konusu=params.web_karar_konusu,
 94 |                 web_karar_metni=params.web_karar_metni,
 95 |                 start=params.start,
 96 |                 length=params.length
 97 |             )
 98 |             
 99 |             result = await self.client.search_daire_decisions(daire_params)
100 |             
101 |             # Convert to unified format
102 |             decisions_list = [decision.model_dump() for decision in result.decisions]
103 |             
104 |             return SayistayUnifiedSearchResult(
105 |                 decision_type="daire",
106 |                 decisions=decisions_list,
107 |                 total_records=result.total_records,
108 |                 total_filtered=result.total_filtered,
109 |                 draw=result.draw
110 |             )
111 |         
112 |         else:
113 |             raise ValueError(f"Unsupported decision type: {params.decision_type}")
114 |     
115 |     async def get_document_unified(self, decision_id: str, decision_type: str) -> SayistayUnifiedDocumentMarkdown:
116 |         """Unified document retrieval for all Sayıştay decision types."""
117 |         
118 |         # Use existing client method (decision_type is already a string)
119 |         result = await self.client.get_document_as_markdown(decision_id, decision_type)
120 |         
121 |         return SayistayUnifiedDocumentMarkdown(
122 |             decision_type=decision_type,
123 |             decision_id=result.decision_id,
124 |             source_url=result.source_url,
125 |             document_data=result.model_dump(),
126 |             markdown_content=result.markdown_content,
127 |             error_message=result.error_message
128 |         )
129 |     
130 |     async def close_client_session(self):
131 |         """Close the underlying client session."""
132 |         if hasattr(self.client, 'close_client_session'):
133 |             await self.client.close_client_session()
```

--------------------------------------------------------------------------------
/.serena/project.yml:
--------------------------------------------------------------------------------

```yaml
 1 | # list of languages for which language servers are started; choose from:
 2 | #   al               bash             clojure          cpp              csharp           csharp_omnisharp
 3 | #   dart             elixir           elm              erlang           fortran          go
 4 | #   haskell          java             julia            kotlin           lua              markdown
 5 | #   nix              perl             php              python           python_jedi      r
 6 | #   rego             ruby             ruby_solargraph  rust             scala            swift
 7 | #   terraform        typescript       typescript_vts   yaml             zig
 8 | # Note:
 9 | #   - For C, use cpp
10 | #   - For JavaScript, use typescript
11 | # Special requirements:
12 | #   - csharp: Requires the presence of a .sln file in the project folder.
13 | # When using multiple languages, the first language server that supports a given file will be used for that file.
14 | # The first language is the default language and the respective language server will be used as a fallback.
15 | # Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
16 | languages:
17 | - python
18 | 
19 | # the encoding used by text files in the project
20 | # For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
21 | encoding: "utf-8"
22 | 
23 | # whether to use the project's gitignore file to ignore files
24 | # Added on 2025-04-07
25 | ignore_all_files_in_gitignore: true
26 | 
27 | # list of additional paths to ignore
28 | # same syntax as gitignore, so you can use * and **
29 | # Was previously called `ignored_dirs`, please update your config if you are using that.
30 | # Added (renamed) on 2025-04-07
31 | ignored_paths: []
32 | 
33 | # whether the project is in read-only mode
34 | # If set to true, all editing tools will be disabled and attempts to use them will result in an error
35 | # Added on 2025-04-18
36 | read_only: false
37 | 
38 | # list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
39 | # Below is the complete list of tools for convenience.
40 | # To make sure you have the latest list of tools, and to view their descriptions, 
41 | # execute `uv run scripts/print_tool_overview.py`.
42 | #
43 | #  * `activate_project`: Activates a project by name.
44 | #  * `check_onboarding_performed`: Checks whether project onboarding was already performed.
45 | #  * `create_text_file`: Creates/overwrites a file in the project directory.
46 | #  * `delete_lines`: Deletes a range of lines within a file.
47 | #  * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
48 | #  * `execute_shell_command`: Executes a shell command.
49 | #  * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
50 | #  * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
51 | #  * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
52 | #  * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
53 | #  * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
54 | #  * `initial_instructions`: Gets the initial instructions for the current project.
55 | #     Should only be used in settings where the system prompt cannot be set,
56 | #     e.g. in clients you have no control over, like Claude Desktop.
57 | #  * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
58 | #  * `insert_at_line`: Inserts content at a given line in a file.
59 | #  * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
60 | #  * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
61 | #  * `list_memories`: Lists memories in Serena's project-specific memory store.
62 | #  * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
63 | #  * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
64 | #  * `read_file`: Reads a file within the project directory.
65 | #  * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
66 | #  * `remove_project`: Removes a project from the Serena configuration.
67 | #  * `replace_lines`: Replaces a range of lines within a file with new content.
68 | #  * `replace_symbol_body`: Replaces the full definition of a symbol.
69 | #  * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
70 | #  * `search_for_pattern`: Performs a search for a pattern in the project.
71 | #  * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
72 | #  * `switch_modes`: Activates modes by providing a list of their names
73 | #  * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
74 | #  * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
75 | #  * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
76 | #  * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
77 | excluded_tools: []
78 | 
79 | # initial prompt for the project. It will always be given to the LLM upon activating the project
80 | # (contrary to the memories, which are loaded on demand).
81 | initial_prompt: ""
82 | 
83 | project_name: "yargi-mcp"
84 | included_optional_tools: []
85 | 
```

--------------------------------------------------------------------------------
/danistay_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
  1 | # danistay_mcp_module/models.py
  2 | 
  3 | from pydantic import BaseModel, Field, HttpUrl, ConfigDict
  4 | from typing import List, Optional, Dict, Any
  5 | 
  6 | class DanistayBaseSearchRequest(BaseModel):
  7 |     """Base model for common search parameters for Danistay."""
  8 |     pageSize: int = Field(default=10, ge=1, le=10)
  9 |     pageNumber: int = Field(default=1, ge=1)
 10 |     # siralama and siralamaDirection are part of detailed search, not necessarily keyword search
 11 |     # as per user's provided payloads.
 12 | 
 13 | class DanistayKeywordSearchRequestData(BaseModel):
 14 |     """Internal data model for the keyword search payload's 'data' field."""
 15 |     andKelimeler: List[str] = Field(default_factory=list)
 16 |     orKelimeler: List[str] = Field(default_factory=list)
 17 |     notAndKelimeler: List[str] = Field(default_factory=list)
 18 |     notOrKelimeler: List[str] = Field(default_factory=list)
 19 |     pageSize: int
 20 |     pageNumber: int
 21 | 
 22 | class DanistayKeywordSearchRequest(BaseModel): # This is the model the MCP tool will accept
 23 |     """Model for keyword-based search request for Danistay."""
 24 |     andKelimeler: List[str] = Field(default_factory=list, description="AND keywords")
 25 |     orKelimeler: List[str] = Field(default_factory=list, description="OR keywords")
 26 |     notAndKelimeler: List[str] = Field(default_factory=list, description="NOT AND keywords")
 27 |     notOrKelimeler: List[str] = Field(default_factory=list, description="NOT OR keywords")
 28 |     pageSize: int = Field(default=10, ge=1, le=10)
 29 |     pageNumber: int = Field(default=1, ge=1)
 30 | 
 31 | class DanistayDetailedSearchRequestData(BaseModel): # Internal data model for detailed search payload
 32 |     """Internal data model for the detailed search payload's 'data' field."""
 33 |     daire: Optional[str] = "" # API expects empty string for None
 34 |     esasYil: Optional[str] = ""
 35 |     esasIlkSiraNo: Optional[str] = ""
 36 |     esasSonSiraNo: Optional[str] = ""
 37 |     kararYil: Optional[str] = ""
 38 |     kararIlkSiraNo: Optional[str] = ""
 39 |     kararSonSiraNo: Optional[str] = ""
 40 |     baslangicTarihi: Optional[str] = ""
 41 |     bitisTarihi: Optional[str] = ""
 42 |     mevzuatNumarasi: Optional[str] = ""
 43 |     mevzuatAdi: Optional[str] = ""
 44 |     madde: Optional[str] = ""
 45 |     siralama: str # Seems mandatory in detailed search payload
 46 |     siralamaDirection: str # Seems mandatory
 47 |     pageSize: int
 48 |     pageNumber: int
 49 |     # Note: 'arananKelime' is not in the detailed search payload example provided by user.
 50 |     # If it can be included, it should be added here.
 51 | 
 52 | class DanistayDetailedSearchRequest(DanistayBaseSearchRequest): # MCP tool will accept this
 53 |     """Model for detailed search request for Danistay."""
 54 |     daire: str = Field("", description="Chamber")
 55 |     esasYil: str = Field("", description="Case year")
 56 |     esasIlkSiraNo: str = Field("", description="Start case no")
 57 |     esasSonSiraNo: str = Field("", description="End case no")
 58 |     kararYil: str = Field("", description="Decision year")
 59 |     kararIlkSiraNo: str = Field("", description="Start decision no")
 60 |     kararSonSiraNo: str = Field("", description="End decision no")
 61 |     baslangicTarihi: str = Field("", description="Start date")
 62 |     bitisTarihi: str = Field("", description="End date")
 63 |     mevzuatNumarasi: str = Field("", description="Law number")
 64 |     mevzuatAdi: str = Field("", description="Law name")
 65 |     madde: str = Field("", description="Article")
 66 |     # Add a general keyword field if detailed search also supports it
 67 |     # arananKelime: Optional[str] = Field(None, description="General keyword for detailed search.")
 68 | 
 69 | 
 70 | class DanistayApiDecisionEntry(BaseModel):
 71 |     """Model for an individual decision entry from the Danistay API search response.
 72 |        Based on user-provided response samples for both keyword and detailed search.
 73 |     """
 74 |     id: str
 75 |     # The API response for keyword search uses "daireKurul", detailed search example uses "daire".
 76 |     # We use an alias to handle both and map to a consistent field name "chamber".
 77 |     chamber: str = Field("", alias="daire", description="Chamber")
 78 |     esasNo: str = Field("", description="Case number")
 79 |     kararNo: str = Field("", description="Decision number")
 80 |     kararTarihi: str = Field("", description="Decision date")
 81 |     arananKelime: str = Field("", description="Keyword")
 82 |     # index: Optional[int] = None # Present in response, can be added if needed by MCP tool
 83 |     # siraNo: Optional[int] = None # Present in detailed response, can be added
 84 | 
 85 |     document_url: Optional[HttpUrl] = Field(None, description="Document URL")
 86 | 
 87 |     model_config = ConfigDict(populate_by_name=True, extra='ignore')  # Important for alias to work and ignore extra fields
 88 | 
 89 | class DanistayApiResponseInnerData(BaseModel):
 90 |     """Model for the inner 'data' object in the Danistay API search response."""
 91 |     data: List[DanistayApiDecisionEntry]
 92 |     recordsTotal: int
 93 |     recordsFiltered: int
 94 |     draw: int = Field(0, description="Draw counter")
 95 | 
 96 | class DanistayApiResponse(BaseModel):
 97 |     """Model for the complete search response from the Danistay API."""
 98 |     data: Optional[DanistayApiResponseInnerData] = Field(None, description="Response data, can be null when no results found")
 99 |     metadata: Optional[Dict[str, Any]] = Field(None, description="Optional metadata (Meta Veri) from API.")
100 | 
101 | class DanistayDocumentMarkdown(BaseModel):
102 |     """Model for a Danistay decision document, containing only Markdown content."""
103 |     id: str
104 |     markdown_content: str = Field("", description="The decision content (Karar İçeriği) converted to Markdown.")
105 |     source_url: HttpUrl
106 | 
107 | class CompactDanistaySearchResult(BaseModel):
108 |     """A compact search result model for the MCP tool to return."""
109 |     decisions: List[DanistayApiDecisionEntry]
110 |     total_records: int
111 |     requested_page: int
112 |     page_size: int
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/danistay_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
  1 | # danistay_mcp_module/models.py
  2 | 
  3 | from pydantic import BaseModel, Field, HttpUrl, ConfigDict
  4 | from typing import List, Optional, Dict, Any
  5 | 
  6 | class DanistayBaseSearchRequest(BaseModel):
  7 |     """Base model for common search parameters for Danistay."""
  8 |     pageSize: int = Field(default=10, ge=1, le=10)
  9 |     pageNumber: int = Field(default=1, ge=1)
 10 |     # siralama and siralamaDirection are part of detailed search, not necessarily keyword search
 11 |     # as per user's provided payloads.
 12 | 
 13 | class DanistayKeywordSearchRequestData(BaseModel):
 14 |     """Internal data model for the keyword search payload's 'data' field."""
 15 |     andKelimeler: List[str] = Field(default_factory=list)
 16 |     orKelimeler: List[str] = Field(default_factory=list)
 17 |     notAndKelimeler: List[str] = Field(default_factory=list)
 18 |     notOrKelimeler: List[str] = Field(default_factory=list)
 19 |     pageSize: int
 20 |     pageNumber: int
 21 | 
 22 | class DanistayKeywordSearchRequest(BaseModel): # This is the model the MCP tool will accept
 23 |     """Model for keyword-based search request for Danistay."""
 24 |     andKelimeler: List[str] = Field(default_factory=list, description="AND keywords")
 25 |     orKelimeler: List[str] = Field(default_factory=list, description="OR keywords")
 26 |     notAndKelimeler: List[str] = Field(default_factory=list, description="NOT AND keywords")
 27 |     notOrKelimeler: List[str] = Field(default_factory=list, description="NOT OR keywords")
 28 |     pageSize: int = Field(default=10, ge=1, le=10)
 29 |     pageNumber: int = Field(default=1, ge=1)
 30 | 
 31 | class DanistayDetailedSearchRequestData(BaseModel): # Internal data model for detailed search payload
 32 |     """Internal data model for the detailed search payload's 'data' field."""
 33 |     daire: Optional[str] = "" # API expects empty string for None
 34 |     esasYil: Optional[str] = ""
 35 |     esasIlkSiraNo: Optional[str] = ""
 36 |     esasSonSiraNo: Optional[str] = ""
 37 |     kararYil: Optional[str] = ""
 38 |     kararIlkSiraNo: Optional[str] = ""
 39 |     kararSonSiraNo: Optional[str] = ""
 40 |     baslangicTarihi: Optional[str] = ""
 41 |     bitisTarihi: Optional[str] = ""
 42 |     mevzuatNumarasi: Optional[str] = ""
 43 |     mevzuatAdi: Optional[str] = ""
 44 |     madde: Optional[str] = ""
 45 |     siralama: str # Seems mandatory in detailed search payload
 46 |     siralamaDirection: str # Seems mandatory
 47 |     pageSize: int
 48 |     pageNumber: int
 49 |     # Note: 'arananKelime' is not in the detailed search payload example provided by user.
 50 |     # If it can be included, it should be added here.
 51 | 
 52 | class DanistayDetailedSearchRequest(DanistayBaseSearchRequest): # MCP tool will accept this
 53 |     """Model for detailed search request for Danistay."""
 54 |     daire: str = Field("", description="Chamber")
 55 |     esasYil: str = Field("", description="Case year")
 56 |     esasIlkSiraNo: str = Field("", description="Start case no")
 57 |     esasSonSiraNo: str = Field("", description="End case no")
 58 |     kararYil: str = Field("", description="Decision year")
 59 |     kararIlkSiraNo: str = Field("", description="Start decision no")
 60 |     kararSonSiraNo: str = Field("", description="End decision no")
 61 |     baslangicTarihi: str = Field("", description="Start date")
 62 |     bitisTarihi: str = Field("", description="End date")
 63 |     mevzuatNumarasi: str = Field("", description="Law number")
 64 |     mevzuatAdi: str = Field("", description="Law name")
 65 |     madde: str = Field("", description="Article")
 66 |     # Add a general keyword field if detailed search also supports it
 67 |     # arananKelime: Optional[str] = Field(None, description="General keyword for detailed search.")
 68 | 
 69 | 
 70 | class DanistayApiDecisionEntry(BaseModel):
 71 |     """Model for an individual decision entry from the Danistay API search response.
 72 |        Based on user-provided response samples for both keyword and detailed search.
 73 |     """
 74 |     id: str
 75 |     # The API response for keyword search uses "daireKurul", detailed search example uses "daire".
 76 |     # We use an alias to handle both and map to a consistent field name "chamber".
 77 |     chamber: str = Field("", alias="daire", description="Chamber")
 78 |     esasNo: str = Field("", description="Case number")
 79 |     kararNo: str = Field("", description="Decision number")
 80 |     kararTarihi: str = Field("", description="Decision date")
 81 |     arananKelime: str = Field("", description="Keyword")
 82 |     # index: Optional[int] = None # Present in response, can be added if needed by MCP tool
 83 |     # siraNo: Optional[int] = None # Present in detailed response, can be added
 84 | 
 85 |     document_url: Optional[HttpUrl] = Field(None, description="Document URL")
 86 | 
 87 |     model_config = ConfigDict(populate_by_name=True, extra='ignore')  # Important for alias to work and ignore extra fields
 88 | 
 89 | class DanistayApiResponseInnerData(BaseModel):
 90 |     """Model for the inner 'data' object in the Danistay API search response."""
 91 |     data: List[DanistayApiDecisionEntry]
 92 |     recordsTotal: int
 93 |     recordsFiltered: int
 94 |     draw: int = Field(0, description="Draw counter")
 95 | 
 96 | class DanistayApiResponse(BaseModel):
 97 |     """Model for the complete search response from the Danistay API."""
 98 |     data: Optional[DanistayApiResponseInnerData] = Field(None, description="Response data, can be null when no results found")
 99 |     metadata: Optional[Dict[str, Any]] = Field(None, description="Optional metadata (Meta Veri) from API.")
100 | 
101 | class DanistayDocumentMarkdown(BaseModel):
102 |     """Model for a Danistay decision document, containing only Markdown content."""
103 |     id: str
104 |     markdown_content: str = Field("", description="The decision content (Karar İçeriği) converted to Markdown.")
105 |     source_url: HttpUrl
106 | 
107 | class CompactDanistaySearchResult(BaseModel):
108 |     """A compact search result model for the MCP tool to return."""
109 |     decisions: List[DanistayApiDecisionEntry]
110 |     total_records: int
111 |     requested_page: int
112 |     page_size: int
```

--------------------------------------------------------------------------------
/kik_mcp_module/models_v2.py:
--------------------------------------------------------------------------------

```python
  1 | # kik_mcp_module/models_v2.py
  2 | from pydantic import BaseModel, Field, ConfigDict
  3 | from typing import List, Optional
  4 | from datetime import datetime
  5 | from enum import Enum
  6 | 
  7 | # New KIK v2 API Models
  8 | 
  9 | class KikV2DecisionType(str, Enum):
 10 |     """KIK v2 Decision Types with corresponding endpoints."""
 11 |     UYUSMAZLIK = "uyusmazlik"        # Disputes - GetKurulKararlari
 12 |     DUZENLEYICI = "duzenleyici"      # Regulatory - GetKurulKararlariDk  
 13 |     MAHKEME = "mahkeme"              # Court - GetKurulKararlariMk
 14 | 
 15 | class KikV2SearchRequest(BaseModel):
 16 |     """Model for KIK v2 API search request."""
 17 |     KararMetni: str = Field("", description="Decision text search query")
 18 |     KararNo: str = Field("", description="Decision number (e.g., '2025/UH.II-1801')")
 19 |     BasvuranAdi: str = Field("", description="Applicant name")
 20 |     IdareAdi: str = Field("", description="Administration name")
 21 |     BaslangicTarihi: str = Field("", description="Start date (YYYY-MM-DD)")
 22 |     BitisTarihi: str = Field("", description="End date (YYYY-MM-DD)")
 23 |     
 24 | class KikV2KeyValuePair(BaseModel):
 25 |     """Key-value pair for KIK v2 API request."""
 26 |     key: str
 27 |     value: str
 28 | 
 29 | class KikV2QueryRequest(BaseModel):
 30 |     """Nested query structure for KIK v2 API."""
 31 |     keyValueOfstringanyType: List[KikV2KeyValuePair]
 32 | 
 33 | class KikV2RequestData(BaseModel):
 34 |     """Main request data structure for KIK v2 API."""
 35 |     keyValuePairs: KikV2QueryRequest
 36 | 
 37 | # Request Payloads for different decision types
 38 | class KikV2SearchPayload(BaseModel):
 39 |     """Complete payload for KIK v2 API search - Uyuşmazlık (Disputes)."""
 40 |     sorgulaKurulKararlari: KikV2RequestData
 41 | 
 42 | class KikV2SearchPayloadDk(BaseModel):
 43 |     """Complete payload for KIK v2 API search - Düzenleyici (Regulatory)."""
 44 |     sorgulaKurulKararlariDk: KikV2RequestData
 45 | 
 46 | class KikV2SearchPayloadMk(BaseModel):
 47 |     """Complete payload for KIK v2 API search - Mahkeme (Court)."""
 48 |     sorgulaKurulKararlariMk: KikV2RequestData
 49 | 
 50 | # Response Models
 51 | 
 52 | class KikV2DecisionDetail(BaseModel):
 53 |     """Individual decision detail from KIK v2 API response."""
 54 |     resmiGazeteMukerrerSayi: str = Field("", description="Official Gazette duplicate number")
 55 |     itiraz: str = Field("", description="Objection")
 56 |     yayinlanmaTarihi: str = Field("", description="Publication date")
 57 |     idareAdi: str = Field("", description="Administration name")
 58 |     uzmanTCKN: str = Field("", description="Expert TCKN")
 59 |     resmiGazeteTarihi: str = Field("", description="Official Gazette date")
 60 |     basvuruKonusu: str = Field("", description="Application subject")
 61 |     kararTurKod: str = Field("", description="Decision type code")
 62 |     kararTurAciklama: str = Field("", description="Decision type description")
 63 |     karar: str = Field("", description="Decision text")
 64 |     kararNo: str = Field("", description="Decision number")
 65 |     resmiGazeteSayisi: str = Field("", description="Official Gazette number")
 66 |     inceleme: str = Field("", description="Review")
 67 |     basvuruTarihi: str = Field("", description="Application date")
 68 |     kararNitelikKod: str = Field("", description="Decision nature code")
 69 |     resmiGazeteMukerrer: str = Field("", description="Official Gazette duplicate")
 70 |     basvuruSayisi: str = Field("", description="Application number")
 71 |     basvuran: str = Field("", description="Applicant")
 72 |     kararNitelik: str = Field("", description="Decision nature")
 73 |     uyusmazlikKararNo: str = Field("", description="Dispute decision number")
 74 |     kurulNo: str = Field("", description="Board number")
 75 |     gundemMaddesiSiraNo: str = Field("", description="Agenda item sequence")
 76 |     kararTarihi: str = Field("", description="Decision date (ISO format)")
 77 |     dosyaBirimKodu: str = Field("", description="File unit code")
 78 |     gundemMaddesiId: str = Field("", description="Agenda item ID")
 79 | 
 80 | class KikV2DecisionGroup(BaseModel):
 81 |     """Group of decision details."""
 82 |     KurulKararTutanakDetayi: List[KikV2DecisionDetail] = Field(alias="kurulKararTutanakDetayi")
 83 |     
 84 |     model_config = ConfigDict(populate_by_name=True)
 85 | 
 86 | class KikV2SearchResultData(BaseModel):
 87 |     """Search result data structure."""
 88 |     hataKodu: str = Field("", description="Error code")
 89 |     hataMesaji: str = Field("", description="Error message") 
 90 |     KurulKararTutanakDetayListesi: List[KikV2DecisionGroup]
 91 |     
 92 |     model_config = ConfigDict(populate_by_name=True)
 93 | 
 94 | class KikV2SearchResultWrapper(BaseModel):
 95 |     """Wrapper for search result."""
 96 |     SorgulaKurulKararlariResult: KikV2SearchResultData
 97 | 
 98 | # Base Response Models
 99 | class KikV2SearchResponse(BaseModel):
100 |     """Complete KIK v2 API search response for Uyuşmazlık (Disputes)."""
101 |     SorgulaKurulKararlariResponse: KikV2SearchResultWrapper
102 | 
103 | # Düzenleyici Kararlar (Regulatory Decisions) Response Models
104 | class KikV2SearchResultWrapperDk(BaseModel):
105 |     """Wrapper for regulatory decisions search result."""
106 |     SorgulaKurulKararlariDkResult: KikV2SearchResultData
107 | 
108 | class KikV2SearchResponseDk(BaseModel):
109 |     """Complete KIK v2 API search response for Düzenleyici (Regulatory) decisions."""
110 |     SorgulaKurulKararlariDkResponse: KikV2SearchResultWrapperDk
111 | 
112 | # Mahkeme Kararlar (Court Decisions) Response Models  
113 | class KikV2SearchResultWrapperMk(BaseModel):
114 |     """Wrapper for court decisions search result."""
115 |     SorgulaKurulKararlariMkResult: KikV2SearchResultData
116 | 
117 | class KikV2SearchResponseMk(BaseModel):
118 |     """Complete KIK v2 API search response for Mahkeme (Court) decisions."""
119 |     SorgulaKurulKararlariMkResponse: KikV2SearchResultWrapperMk
120 | 
121 | # Simplified Models for MCP Tools
122 | 
123 | class KikV2CompactDecision(BaseModel):
124 |     """Compact decision format for MCP tool responses."""
125 |     kararNo: str = Field("", description="Decision number")
126 |     kararTarihi: str = Field("", description="Decision date")
127 |     basvuran: str = Field("", description="Applicant")
128 |     idareAdi: str = Field("", description="Administration")
129 |     basvuruKonusu: str = Field("", description="Application subject")
130 |     gundemMaddesiId: str = Field("", description="Document ID for retrieval")
131 |     decision_type: str = Field("", description="Decision type (uyusmazlik/duzenleyici/mahkeme)")
132 |     
133 | class KikV2SearchResult(BaseModel):
134 |     """Compact search results for MCP tools."""
135 |     decisions: List[KikV2CompactDecision]
136 |     total_records: int = Field(0, description="Total number of decisions found")
137 |     page: int = Field(1, description="Current page number")
138 |     error_code: str = Field("", description="API error code")
139 |     error_message: str = Field("", description="API error message")
140 | 
141 | class KikV2DocumentMarkdown(BaseModel):
142 |     """Document content in Markdown format."""
143 |     document_id: str = Field("", description="Document ID")
144 |     kararNo: str = Field("", description="Decision number")
145 |     markdown_content: str = Field("", description="Decision content in Markdown")
146 |     source_url: str = Field("", description="Source URL")
147 |     error_message: str = Field("", description="Error message if retrieval failed")
```

--------------------------------------------------------------------------------
/mcp_auth_factory.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Factory for creating FastMCP app with MCP Auth Toolkit integration
  3 | """
  4 | 
  5 | import logging
  6 | import os
  7 | from typing import Optional
  8 | 
  9 | logger = logging.getLogger(__name__)
 10 | 
 11 | try:
 12 |     from fastmcp import FastMCP
 13 |     FASTMCP_AVAILABLE = True
 14 | except ImportError:
 15 |     FASTMCP_AVAILABLE = False
 16 |     FastMCP = None
 17 | 
 18 | from mcp_auth import (
 19 |     OAuthProvider, 
 20 |     PolicyEngine, 
 21 |     FastMCPAuthWrapper,
 22 |     create_default_policies
 23 | )
 24 | from mcp_auth.clerk_config import create_mcp_server_config
 25 | 
 26 | 
 27 | def create_auth_enabled_app(app_name: str = "Yargı MCP Server") -> FastMCP:
 28 |     """Create FastMCP app with authentication enabled"""
 29 |     
 30 |     if not FASTMCP_AVAILABLE:
 31 |         raise ImportError("FastMCP is required for authenticated MCP server")
 32 |     
 33 |     logger.info("Creating FastMCP app with MCP Auth Toolkit integration")
 34 |     
 35 |     # Create base FastMCP app
 36 |     app = FastMCP(app_name)
 37 |     
 38 |     # Check if authentication is enabled
 39 |     auth_enabled = os.getenv("ENABLE_AUTH", "true").lower() == "true"
 40 |     
 41 |     if not auth_enabled:
 42 |         logger.info("Authentication disabled, returning basic FastMCP app")
 43 |         return app
 44 |     
 45 |     try:
 46 |         # Get configuration
 47 |         logger.info("Getting MCP server configuration...")
 48 |         config = create_mcp_server_config()
 49 |         logger.info("Configuration loaded successfully")
 50 |         
 51 |         # Create OAuth provider with Clerk config
 52 |         logger.info("Creating OAuth provider...")
 53 |         oauth_provider = OAuthProvider(
 54 |             config=config["oauth_config"],
 55 |             jwt_secret=config["jwt_secret"]
 56 |         )
 57 |         logger.info("OAuth provider created successfully")
 58 |         
 59 |         # Create policy engine for Turkish legal database
 60 |         policy_engine = create_default_policies()
 61 |         
 62 |         # Store auth components for later wrapping (after tools are defined)
 63 |         app._oauth_provider = oauth_provider
 64 |         app._policy_engine = policy_engine
 65 |         app._auth_config = config
 66 |         
 67 |         # Add OAuth endpoints immediately
 68 |         @app.tool(
 69 |             description="Initiate OAuth 2.1 authorization flow with PKCE",
 70 |             annotations={"readOnlyHint": True, "idempotentHint": False}
 71 |         )
 72 |         async def oauth_authorize(redirect_uri: str, scopes: str = None):
 73 |             """OAuth authorization endpoint"""
 74 |             scope_list = scopes.split(" ") if scopes else ["mcp:tools:read", "mcp:tools:write"]
 75 |             auth_url, pkce = oauth_provider.generate_authorization_url(
 76 |                 redirect_uri=redirect_uri, scopes=scope_list
 77 |             )
 78 |             logger.info(f"Generated authorization URL for redirect_uri: {redirect_uri}")
 79 |             return {
 80 |                 "authorization_url": auth_url,
 81 |                 "code_verifier": pkce.verifier,
 82 |                 "code_challenge": pkce.challenge,
 83 |                 "instructions": "Use the authorization_url to complete OAuth flow, then exchange the returned code using oauth_token tool"
 84 |             }
 85 | 
 86 |         @app.tool(
 87 |             description="Exchange OAuth authorization code for access token",
 88 |             annotations={"readOnlyHint": False, "idempotentHint": False}
 89 |         )
 90 |         async def oauth_token(code: str, state: str, redirect_uri: str):
 91 |             """OAuth token exchange endpoint"""
 92 |             try:
 93 |                 result = await oauth_provider.exchange_code_for_token(
 94 |                     code=code, state=state, redirect_uri=redirect_uri
 95 |                 )
 96 |                 logger.info("Successfully exchanged authorization code for token")
 97 |                 return result
 98 |             except Exception as e:
 99 |                 logger.error(f"Token exchange failed: {e}")
100 |                 raise
101 | 
102 |         @app.tool(
103 |             description="Validate and introspect OAuth access token",
104 |             annotations={"readOnlyHint": True, "idempotentHint": True}
105 |         )
106 |         async def oauth_introspect(token: str):
107 |             """Token introspection endpoint"""
108 |             result = oauth_provider.introspect_token(token)
109 |             logger.debug(f"Token introspection: active={result.get('active', False)}")
110 |             return result
111 | 
112 |         @app.tool(
113 |             description="Revoke OAuth access token",
114 |             annotations={"readOnlyHint": False, "idempotentHint": False}
115 |         )
116 |         async def oauth_revoke(token: str):
117 |             """Token revocation endpoint"""
118 |             success = oauth_provider.revoke_token(token)
119 |             logger.info(f"Token revocation: success={success}")
120 |             return {"revoked": success}
121 |         
122 |         logger.info("Successfully created authenticated FastMCP app")
123 |         
124 |     except Exception as e:
125 |         logger.error(f"Failed to create authenticated app: {e}")
126 |         logger.info("Falling back to non-authenticated FastMCP app")
127 |         # Return basic app if auth setup fails
128 |         return app
129 |     
130 |     return app
131 | 
132 | 
133 | def create_app() -> FastMCP:
134 |     """Create FastMCP app (backwards compatible with mcp_factory.py)"""
135 |     return create_auth_enabled_app()
136 | 
137 | 
138 | def get_auth_wrapper(app: FastMCP) -> Optional[FastMCPAuthWrapper]:
139 |     """Get auth wrapper from app if available"""
140 |     return getattr(app, '_auth_wrapper', None)
141 | 
142 | 
143 | def get_oauth_provider(app: FastMCP) -> Optional[OAuthProvider]:
144 |     """Get OAuth provider from app if available"""
145 |     return getattr(app, '_oauth_provider', None)
146 | 
147 | 
148 | def get_policy_engine(app: FastMCP) -> Optional[PolicyEngine]:
149 |     """Get policy engine from app if available"""
150 |     return getattr(app, '_policy_engine', None)
151 | 
152 | 
153 | def is_auth_enabled(app: FastMCP) -> bool:
154 |     """Check if authentication is enabled for the app"""
155 |     return hasattr(app, '_oauth_provider') or hasattr(app, '_auth_wrapper')
156 | 
157 | 
158 | def enable_tool_authentication(app: FastMCP):
159 |     """Enable authentication on all existing tools (call after tools are defined)"""
160 |     if not is_auth_enabled(app):
161 |         logger.debug("Authentication not enabled, skipping tool authentication")
162 |         return
163 |     
164 |     oauth_provider = get_oauth_provider(app)
165 |     policy_engine = get_policy_engine(app)
166 |     
167 |     if not oauth_provider or not policy_engine:
168 |         logger.warning("OAuth provider or policy engine not available")
169 |         return
170 |     
171 |     try:
172 |         # Create auth wrapper and wrap tools
173 |         auth_wrapper = FastMCPAuthWrapper(
174 |             mcp_server=app,
175 |             oauth_provider=oauth_provider,
176 |             policy_engine=policy_engine
177 |         )
178 |         
179 |         # Store wrapper for reference
180 |         app._auth_wrapper = auth_wrapper
181 |         
182 |         logger.info("Tool authentication enabled successfully")
183 |         
184 |     except Exception as e:
185 |         logger.error(f"Failed to enable tool authentication: {e}")
186 | 
187 | 
188 | def cleanup_auth_sessions(app: FastMCP):
189 |     """Clean up expired auth sessions and tokens"""
190 |     oauth_provider = get_oauth_provider(app)
191 |     if oauth_provider:
192 |         oauth_provider.cleanup_expired_sessions()
193 |         logger.debug("Cleaned up expired OAuth sessions")
```

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

```python
  1 | """
  2 | Factory for creating FastMCP app with MCP Auth Toolkit integration
  3 | """
  4 | 
  5 | import logging
  6 | import os
  7 | from typing import Optional
  8 | 
  9 | logger = logging.getLogger(__name__)
 10 | 
 11 | try:
 12 |     from fastmcp import FastMCP
 13 |     FASTMCP_AVAILABLE = True
 14 | except ImportError:
 15 |     FASTMCP_AVAILABLE = False
 16 |     FastMCP = None
 17 | 
 18 | from mcp_auth import (
 19 |     OAuthProvider, 
 20 |     PolicyEngine, 
 21 |     FastMCPAuthWrapper,
 22 |     create_default_policies
 23 | )
 24 | from mcp_auth.clerk_config import create_mcp_server_config
 25 | 
 26 | 
 27 | def create_auth_enabled_app(app_name: str = "Yargı MCP Server") -> FastMCP:
 28 |     """Create FastMCP app with authentication enabled"""
 29 |     
 30 |     if not FASTMCP_AVAILABLE:
 31 |         raise ImportError("FastMCP is required for authenticated MCP server")
 32 |     
 33 |     logger.info("Creating FastMCP app with MCP Auth Toolkit integration")
 34 |     
 35 |     # Create base FastMCP app
 36 |     app = FastMCP(app_name)
 37 |     
 38 |     # Check if authentication is enabled
 39 |     auth_enabled = os.getenv("ENABLE_AUTH", "true").lower() == "true"
 40 |     
 41 |     if not auth_enabled:
 42 |         logger.info("Authentication disabled, returning basic FastMCP app")
 43 |         return app
 44 |     
 45 |     try:
 46 |         # Get configuration
 47 |         logger.info("Getting MCP server configuration...")
 48 |         config = create_mcp_server_config()
 49 |         logger.info("Configuration loaded successfully")
 50 |         
 51 |         # Create OAuth provider with Clerk config
 52 |         logger.info("Creating OAuth provider...")
 53 |         oauth_provider = OAuthProvider(
 54 |             config=config["oauth_config"],
 55 |             jwt_secret=config["jwt_secret"]
 56 |         )
 57 |         logger.info("OAuth provider created successfully")
 58 |         
 59 |         # Create policy engine for Turkish legal database
 60 |         policy_engine = create_default_policies()
 61 |         
 62 |         # Store auth components for later wrapping (after tools are defined)
 63 |         app._oauth_provider = oauth_provider
 64 |         app._policy_engine = policy_engine
 65 |         app._auth_config = config
 66 |         
 67 |         # Add OAuth endpoints immediately
 68 |         @app.tool(
 69 |             description="Initiate OAuth 2.1 authorization flow with PKCE",
 70 |             annotations={"readOnlyHint": True, "idempotentHint": False}
 71 |         )
 72 |         async def oauth_authorize(redirect_uri: str, scopes: str = None):
 73 |             """OAuth authorization endpoint"""
 74 |             scope_list = scopes.split(" ") if scopes else ["mcp:tools:read", "mcp:tools:write"]
 75 |             auth_url, pkce = oauth_provider.generate_authorization_url(
 76 |                 redirect_uri=redirect_uri, scopes=scope_list
 77 |             )
 78 |             logger.info(f"Generated authorization URL for redirect_uri: {redirect_uri}")
 79 |             return {
 80 |                 "authorization_url": auth_url,
 81 |                 "code_verifier": pkce.verifier,
 82 |                 "code_challenge": pkce.challenge,
 83 |                 "instructions": "Use the authorization_url to complete OAuth flow, then exchange the returned code using oauth_token tool"
 84 |             }
 85 | 
 86 |         @app.tool(
 87 |             description="Exchange OAuth authorization code for access token",
 88 |             annotations={"readOnlyHint": False, "idempotentHint": False}
 89 |         )
 90 |         async def oauth_token(code: str, state: str, redirect_uri: str):
 91 |             """OAuth token exchange endpoint"""
 92 |             try:
 93 |                 result = await oauth_provider.exchange_code_for_token(
 94 |                     code=code, state=state, redirect_uri=redirect_uri
 95 |                 )
 96 |                 logger.info("Successfully exchanged authorization code for token")
 97 |                 return result
 98 |             except Exception as e:
 99 |                 logger.error(f"Token exchange failed: {e}")
100 |                 raise
101 | 
102 |         @app.tool(
103 |             description="Validate and introspect OAuth access token",
104 |             annotations={"readOnlyHint": True, "idempotentHint": True}
105 |         )
106 |         async def oauth_introspect(token: str):
107 |             """Token introspection endpoint"""
108 |             result = oauth_provider.introspect_token(token)
109 |             logger.debug(f"Token introspection: active={result.get('active', False)}")
110 |             return result
111 | 
112 |         @app.tool(
113 |             description="Revoke OAuth access token",
114 |             annotations={"readOnlyHint": False, "idempotentHint": False}
115 |         )
116 |         async def oauth_revoke(token: str):
117 |             """Token revocation endpoint"""
118 |             success = oauth_provider.revoke_token(token)
119 |             logger.info(f"Token revocation: success={success}")
120 |             return {"revoked": success}
121 |         
122 |         logger.info("Successfully created authenticated FastMCP app")
123 |         
124 |     except Exception as e:
125 |         logger.error(f"Failed to create authenticated app: {e}")
126 |         logger.info("Falling back to non-authenticated FastMCP app")
127 |         # Return basic app if auth setup fails
128 |         return app
129 |     
130 |     return app
131 | 
132 | 
133 | def create_app() -> FastMCP:
134 |     """Create FastMCP app (backwards compatible with mcp_factory.py)"""
135 |     return create_auth_enabled_app()
136 | 
137 | 
138 | def get_auth_wrapper(app: FastMCP) -> Optional[FastMCPAuthWrapper]:
139 |     """Get auth wrapper from app if available"""
140 |     return getattr(app, '_auth_wrapper', None)
141 | 
142 | 
143 | def get_oauth_provider(app: FastMCP) -> Optional[OAuthProvider]:
144 |     """Get OAuth provider from app if available"""
145 |     return getattr(app, '_oauth_provider', None)
146 | 
147 | 
148 | def get_policy_engine(app: FastMCP) -> Optional[PolicyEngine]:
149 |     """Get policy engine from app if available"""
150 |     return getattr(app, '_policy_engine', None)
151 | 
152 | 
153 | def is_auth_enabled(app: FastMCP) -> bool:
154 |     """Check if authentication is enabled for the app"""
155 |     return hasattr(app, '_oauth_provider') or hasattr(app, '_auth_wrapper')
156 | 
157 | 
158 | def enable_tool_authentication(app: FastMCP):
159 |     """Enable authentication on all existing tools (call after tools are defined)"""
160 |     if not is_auth_enabled(app):
161 |         logger.debug("Authentication not enabled, skipping tool authentication")
162 |         return
163 |     
164 |     oauth_provider = get_oauth_provider(app)
165 |     policy_engine = get_policy_engine(app)
166 |     
167 |     if not oauth_provider or not policy_engine:
168 |         logger.warning("OAuth provider or policy engine not available")
169 |         return
170 |     
171 |     try:
172 |         # Create auth wrapper and wrap tools
173 |         auth_wrapper = FastMCPAuthWrapper(
174 |             mcp_server=app,
175 |             oauth_provider=oauth_provider,
176 |             policy_engine=policy_engine
177 |         )
178 |         
179 |         # Store wrapper for reference
180 |         app._auth_wrapper = auth_wrapper
181 |         
182 |         logger.info("Tool authentication enabled successfully")
183 |         
184 |     except Exception as e:
185 |         logger.error(f"Failed to enable tool authentication: {e}")
186 | 
187 | 
188 | def cleanup_auth_sessions(app: FastMCP):
189 |     """Clean up expired auth sessions and tokens"""
190 |     oauth_provider = get_oauth_provider(app)
191 |     if oauth_provider:
192 |         oauth_provider.cleanup_expired_sessions()
193 |         logger.debug("Cleaned up expired OAuth sessions")
```
Page 3/11FirstPrevNextLast