From 2a24f92702399f1898ac2eafdbe27122aeb6f93a Mon Sep 17 00:00:00 2001 From: saidsurucu Date: Tue, 24 Jun 2025 18:24:19 +0300 Subject: [PATCH] add bedesten module --- README.md | 62 +++++- bedesten_mcp_module/__init__.py | 1 + bedesten_mcp_module/client.py | 183 +++++++++++++++++ bedesten_mcp_module/models.py | 115 +++++++++++ mcp_server_main.py | 354 +++++++++++++++++++++++++++++++- yargitay_mcp_module/models.py | 43 +++- 6 files changed, 738 insertions(+), 20 deletions(-) create mode 100644 bedesten_mcp_module/__init__.py create mode 100644 bedesten_mcp_module/client.py create mode 100644 bedesten_mcp_module/models.py diff --git a/README.md b/README.md index 55b2907..8b6d776 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,14 @@ Bu proje, çeşitli Türk hukuk kaynaklarına (Yargıtay, Danıştay, Emsal Kara 🎯 **Temel Özellikler** * Çeşitli Türk hukuk veritabanlarına programatik erişim için standart bir MCP arayüzü. +* **Kapsamlı Mahkeme Daire/Kurul Filtreleme:** 79 farklı daire/kurul filtreleme seçeneği +* **Dual/Triple API Desteği:** Her mahkeme için birden fazla API kaynağı ile maksimum kapsama * Aşağıdaki kurumların kararlarını arama ve getirme yeteneği: - * **Yargıtay:** Detaylı kriterlerle karar arama ve karar metinlerini Markdown formatında getirme. - * **Danıştay:** Anahtar kelime bazlı ve detaylı kriterlerle karar arama; karar metinlerini Markdown formatında getirme. + * **Yargıtay:** Detaylı kriterlerle karar arama ve karar metinlerini Markdown formatında getirme. **Dual API** (Ana + Bedesten) + **52 Daire/Kurul Filtreleme** (Hukuk/Ceza Daireleri, Genel Kurullar) + * **Danıştay:** Anahtar kelime bazlı ve detaylı kriterlerle karar arama; karar metinlerini Markdown formatında getirme. **Triple API** (Keyword + Detailed + Bedesten) + **27 Daire/Kurul Filtreleme** (İdari Daireler, Vergi/İdare Kurulları, Askeri Yüksek İdare Mahkemesi) + * **Yerel Hukuk Mahkemeleri:** Bedesten API ile yerel hukuk mahkemesi kararlarına erişim + * **İstinaf Hukuk Mahkemeleri:** Bedesten API ile istinaf mahkemesi kararlarına erişim + * **Kanun Yararına Bozma (KYB):** Bedesten API ile olağanüstü kanun yoluna erişim * **Emsal (UYAP):** Detaylı kriterlerle emsal karar arama ve karar metinlerini Markdown formatında getirme. * **Uyuşmazlık Mahkemesi:** Form tabanlı kriterlerle karar arama ve karar metinlerini (URL ile erişilen) Markdown formatında getirme. * **Anayasa Mahkemesi (Norm Denetimi):** Kapsamlı kriterlerle norm denetimi kararlarını arama; uzun karar metinlerini (5.000 karakterlik) sayfalanmış Markdown formatında getirme. @@ -75,15 +80,33 @@ Bu bölüm, Yargı MCP aracını 5ire gibi Claude Desktop dışındaki MCP istem Bu FastMCP sunucusu aşağıdaki temel araçları sunar: +### **Yargıtay Araçları (Dual API + 52 Daire Filtreleme)** +* **Ana API:** + * `search_yargitay_detailed(arananKelime, birimYrgKurulDaire, ...)`: Yargıtay kararlarını detaylı kriterlerle arar. **52 daire/kurul seçeneği** (Hukuk/Ceza Daireleri 1-23, Genel Kurullar, Başkanlar Kurulu) + * `get_yargitay_document_markdown(id: str)`: Belirli bir Yargıtay kararının metnini Markdown formatında getirir. +* **Bedesten API (Alternatif):** + * `search_yargitay_bedesten(phrase, birimAdi, ...)`: Bedesten API ile Yargıtay kararlarını arar. **Aynı 52 daire filtreleme** + * `get_yargitay_bedesten_document_markdown(documentId: str)`: Bedesten'den karar metni (HTML/PDF → Markdown) -* **Yargıtay Araçları:** - * `search_yargitay_detailed(search_query: YargitayDetailedSearchRequest) -> CompactYargitaySearchResult`: Yargıtay kararlarını detaylı kriterlerle arar. - * `get_yargitay_document_markdown(id: str) -> YargitayDocumentMarkdown`: Belirli bir Yargıtay kararının metnini Markdown formatında getirir. +### **Danıştay Araçları (Triple API + 27 Daire Filtreleme)** +* **Ana API'lar:** + * `search_danistay_by_keyword(andKelimeler, orKelimeler, ...)`: Danıştay kararlarını anahtar kelimelerle arar. + * `search_danistay_detailed(daire, esasYil, ...)`: Danıştay kararlarını detaylı kriterlerle arar. + * `get_danistay_document_markdown(id: str)`: Belirli bir Danıştay kararının metnini Markdown formatında getirir. +* **Bedesten API (Alternatif):** + * `search_danistay_bedesten(phrase, birimAdi, ...)`: Bedesten API ile Danıştay kararlarını arar. **27 daire/kurul seçeneği** (1-17. Daireler, Vergi/İdare Kurulları, Askeri Mahkemeler) + * `get_danistay_bedesten_document_markdown(documentId: str)`: Bedesten'den karar metni -* **Danıştay Araçları:** - * `search_danistay_by_keyword(search_query: DanistayKeywordSearchRequest) -> CompactDanistaySearchResult`: Danıştay kararlarını anahtar kelimelerle arar. - * `search_danistay_detailed(search_query: DanistayDetailedSearchRequest) -> CompactDanistaySearchResult`: Danıştay kararlarını detaylı kriterlerle arar. - * `get_danistay_document_markdown(id: str) -> DanistayDocumentMarkdown`: Belirli bir Danıştay kararının metnini Markdown formatında getirir. +### **Diğer Mahkemeler (Bedesten API)** +* **Yerel Hukuk Mahkemeleri:** + * `search_yerel_hukuk_bedesten(phrase, pageSize, ...)`: Yerel hukuk mahkemesi kararlarını arar + * `get_yerel_hukuk_bedesten_document_markdown(documentId: str)`: Karar metni +* **İstinaf Hukuk Mahkemeleri:** + * `search_istinaf_hukuk_bedesten(phrase, pageSize, ...)`: İstinaf mahkemesi kararlarını arar + * `get_istinaf_hukuk_bedesten_document_markdown(documentId: str)`: Karar metni +* **Kanun Yararına Bozma (KYB):** + * `search_kyb_bedesten(phrase, pageSize, ...)`: Olağanüstü kanun yolu kararlarını arar + * `get_kyb_bedesten_document_markdown(documentId: str)`: Karar metni * **Emsal Karar Araçları:** * `search_emsal_detailed_decisions(search_query: EmsalSearchRequest) -> CompactEmsalSearchResult`: Emsal (UYAP) kararlarını detaylı kriterlerle arar. @@ -109,6 +132,27 @@ Bu FastMCP sunucusu aşağıdaki temel araçları sunar:     * `get_rekabet_kurumu_document(karar_id: str, page_number: Optional[int] = 1) -> RekabetDocument`: Belirli bir Rekabet Kurumu kararını `karar_id` ile alır. Kararın PDF formatındaki orijinalinden istenen sayfayı ayıklar ve Markdown formatında döndürür. +--- + +### **📊 Kapsamlı İstatistikler** +- **Toplam Mahkeme/Kurum:** 11 farklı hukuki kurum +- **Toplam MCP Tool:** 30+ arama ve belge getirme aracı +- **Daire/Kurul Filtreleme:** 79 farklı seçenek (52 Yargıtay + 27 Danıştay) +- **API Kaynağı:** Dual/Triple API desteği ile maksimum kapsama +- **Tam Türk Adalet Sistemi:** Yerel mahkemelerden en yüksek mahkemelere kadar + +**🏛️ Desteklenen Mahkeme Hiyerarşisi:** +``` +Yerel Mahkemeler → İstinaf → Yargıtay/Danıştay → Anayasa Mahkemesi + ↓ ↓ ↓ ↓ +Bedesten API Bedesten API Dual/Triple API Norm+Bireysel API +``` + +**⚖️ Kapsamlı Daire Filtreleme:** +- **Yargıtay:** 52 seçenek (1-23 Hukuk, 1-23 Ceza, Genel Kurullar, Başkanlar Kurulu) +- **Danıştay:** 27 seçenek (1-17 Daireler, İdare/Vergi Kurulları, Askeri Mahkemeler) +- **Toplam:** 79 farklı mahkeme dairesi/kurulu + 📜 **Lisans** Bu proje MIT Lisansı altında lisanslanmıştır. Detaylar için `LICENSE` dosyasına bakınız. diff --git a/bedesten_mcp_module/__init__.py b/bedesten_mcp_module/__init__.py new file mode 100644 index 0000000..90b95d3 --- /dev/null +++ b/bedesten_mcp_module/__init__.py @@ -0,0 +1 @@ +# bedesten_mcp_module/__init__.py \ No newline at end of file diff --git a/bedesten_mcp_module/client.py b/bedesten_mcp_module/client.py new file mode 100644 index 0000000..3dae096 --- /dev/null +++ b/bedesten_mcp_module/client.py @@ -0,0 +1,183 @@ +# bedesten_mcp_module/client.py + +import httpx +import base64 +from typing import Optional +import logging +from markitdown import MarkItDown +import tempfile +import os + +from .models import ( + BedestenSearchRequest, BedestenSearchResponse, + BedestenDocumentRequest, BedestenDocumentResponse, + BedestenDocumentMarkdown, BedestenDocumentRequestData +) + +logger = logging.getLogger(__name__) + +class BedestenApiClient: + """ + API Client for Bedesten (bedesten.adalet.gov.tr) - Alternative legal decision search system. + Currently used for Yargıtay decisions, but can be extended for other court types. + """ + BASE_URL = "https://bedesten.adalet.gov.tr" + SEARCH_ENDPOINT = "/emsal-karar/searchDocuments" + DOCUMENT_ENDPOINT = "/emsal-karar/getDocumentContent" + + def __init__(self, request_timeout: float = 60.0): + self.http_client = httpx.AsyncClient( + base_url=self.BASE_URL, + headers={ + "Accept": "*/*", + "Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7", + "AdaletApplicationName": "UyapMevzuat", + "Content-Type": "application/json; charset=utf-8", + "Origin": "https://mevzuat.adalet.gov.tr", + "Referer": "https://mevzuat.adalet.gov.tr/", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-site", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36" + }, + timeout=request_timeout + ) + + async def search_documents(self, search_request: BedestenSearchRequest) -> BedestenSearchResponse: + """ + Search for documents using Bedesten API. + Currently supports: YARGITAYKARARI, DANISTAYKARARI, YERELHUKMAHKARARI, etc. + """ + logger.info(f"BedestenApiClient: Searching documents with phrase: {search_request.data.phrase}") + + try: + response = await self.http_client.post( + self.SEARCH_ENDPOINT, + json=search_request.model_dump() + ) + response.raise_for_status() + response_json = response.json() + + # Parse and return the response + return BedestenSearchResponse(**response_json) + + except httpx.RequestError as e: + logger.error(f"BedestenApiClient: HTTP request error during search: {e}") + raise + except Exception as e: + logger.error(f"BedestenApiClient: Error processing search response: {e}") + raise + + async def get_document_as_markdown(self, document_id: str) -> BedestenDocumentMarkdown: + """ + Get document content and convert to markdown. + Handles both HTML (text/html) and PDF (application/pdf) content types. + """ + logger.info(f"BedestenApiClient: Fetching document for markdown conversion (ID: {document_id})") + + try: + # Prepare request + doc_request = BedestenDocumentRequest( + data=BedestenDocumentRequestData(documentId=document_id) + ) + + # Get document + response = await self.http_client.post( + self.DOCUMENT_ENDPOINT, + json=doc_request.model_dump() + ) + response.raise_for_status() + response_json = response.json() + doc_response = BedestenDocumentResponse(**response_json) + + # Decode base64 content + content_bytes = base64.b64decode(doc_response.data.content) + mime_type = doc_response.data.mimeType + + logger.info(f"BedestenApiClient: Document mime type: {mime_type}") + + # Convert to markdown based on mime type + if mime_type == "text/html": + html_content = content_bytes.decode('utf-8') + markdown_content = self._convert_html_to_markdown(html_content) + elif mime_type == "application/pdf": + markdown_content = self._convert_pdf_to_markdown(content_bytes) + else: + logger.warning(f"Unsupported mime type: {mime_type}") + markdown_content = f"Unsupported content type: {mime_type}. Unable to convert to markdown." + + return BedestenDocumentMarkdown( + documentId=document_id, + markdown_content=markdown_content, + source_url=f"{self.BASE_URL}/document/{document_id}", + mime_type=mime_type + ) + + except httpx.RequestError as e: + logger.error(f"BedestenApiClient: HTTP error fetching document {document_id}: {e}") + raise + except Exception as e: + logger.error(f"BedestenApiClient: Error processing document {document_id}: {e}") + raise + + def _convert_html_to_markdown(self, html_content: str) -> Optional[str]: + """Convert HTML to Markdown using MarkItDown""" + if not html_content: + return None + + temp_file_path = None + try: + md_converter = MarkItDown() + + # Write HTML to temp file + with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".html", encoding="utf-8") as tmp: + tmp.write(html_content) + temp_file_path = tmp.name + + # Convert + result = md_converter.convert(temp_file_path) + markdown_content = result.text_content + + logger.info("Successfully converted HTML to Markdown") + return markdown_content + + except Exception as e: + logger.error(f"Error converting HTML to Markdown: {e}") + return f"Error converting HTML content: {str(e)}" + finally: + if temp_file_path and os.path.exists(temp_file_path): + os.remove(temp_file_path) + + def _convert_pdf_to_markdown(self, pdf_bytes: bytes) -> Optional[str]: + """Convert PDF to Markdown using MarkItDown""" + if not pdf_bytes: + return None + + temp_file_path = None + try: + # MarkItDown supports PDF with markitdown[pdf] + md_converter = MarkItDown() + + # Write PDF to temp file + with tempfile.NamedTemporaryFile(mode="wb", delete=False, suffix=".pdf") as tmp: + tmp.write(pdf_bytes) + temp_file_path = tmp.name + + # Convert + result = md_converter.convert(temp_file_path) + markdown_content = result.text_content + + logger.info("Successfully converted PDF to Markdown") + return markdown_content + + except Exception as e: + logger.error(f"Error converting PDF to Markdown: {e}") + return f"Error converting PDF content: {str(e)}. The document may be corrupted or in an unsupported format." + finally: + if temp_file_path and os.path.exists(temp_file_path): + os.remove(temp_file_path) + + async def close_client_session(self): + """Close HTTP client session""" + await self.http_client.aclose() + logger.info("BedestenApiClient: HTTP client session closed.") \ No newline at end of file diff --git a/bedesten_mcp_module/models.py b/bedesten_mcp_module/models.py new file mode 100644 index 0000000..5fe7d90 --- /dev/null +++ b/bedesten_mcp_module/models.py @@ -0,0 +1,115 @@ +# bedesten_mcp_module/models.py + +from pydantic import BaseModel, Field +from typing import List, Optional, Dict, Any, Literal, Union + +# Import YargitayBirimEnum for chamber filtering +from yargitay_mcp_module.models import YargitayBirimEnum + +# Danıştay Chamber/Board Options +DanistayBirimEnum = Literal[ + "", # Empty string for "All" chambers + # Main Councils + "Büyük Gen.Kur.", # Grand General Assembly + "İdare Dava Daireleri Kurulu", # Administrative Cases Chambers Council + "Vergi Dava Daireleri Kurulu", # Tax Cases Chambers Council + "İçtihatları Birleştirme Kurulu", # Precedents Unification Council + "İdari İşler Kurulu", # Administrative Affairs Council + "Başkanlar Kurulu", # Presidents Council + # Chambers + "1. Daire", "2. Daire", "3. Daire", "4. Daire", "5. Daire", + "6. Daire", "7. Daire", "8. Daire", "9. Daire", "10. Daire", + "11. Daire", "12. Daire", "13. Daire", "14. Daire", "15. Daire", + "16. Daire", "17. Daire", + # Military High Administrative Court + "Askeri Yüksek İdare Mahkemesi", + "Askeri Yüksek İdare Mahkemesi Daireler Kurulu", + "Askeri Yüksek İdare Mahkemesi Başsavcılığı", + "Askeri Yüksek İdare Mahkemesi 1. Daire", + "Askeri Yüksek İdare Mahkemesi 2. Daire", + "Askeri Yüksek İdare Mahkemesi 3. Daire" +] + +# Search Request Models +class BedestenSearchData(BaseModel): + pageSize: int + pageNumber: int + itemTypeList: List[str] + phrase: str + birimAdi: Optional[Union[YargitayBirimEnum, DanistayBirimEnum]] = Field(None, description=""" + Chamber/Board filter (optional). Available options depend on itemTypeList: + + For YARGITAYKARARI (52 options): + - None/null for ALL chambers + - 'Hukuk Genel Kurulu', '1. Hukuk Dairesi' through '23. Hukuk Dairesi' + - 'Ceza Genel Kurulu', '1. Ceza Dairesi' through '23. Ceza Dairesi' + - 'Hukuk Daireleri Başkanlar Kurulu', 'Ceza Daireleri Başkanlar Kurulu' + - 'Büyük Genel Kurulu' + + For DANISTAYKARAR (27 options): + - None/null for ALL chambers + - 'Büyük Gen.Kur.', 'İdare Dava Daireleri Kurulu', 'Vergi Dava Daireleri Kurulu' + - '1. Daire' through '17. Daire' + - 'İçtihatları Birleştirme Kurulu', 'İdari İşler Kurulu', 'Başkanlar Kurulu' + - Military courts: 'Askeri Yüksek İdare Mahkemesi' variants + """) + sortFields: List[str] = ["KARAR_TARIHI"] + sortDirection: str = "desc" + +class BedestenSearchRequest(BaseModel): + data: BedestenSearchData + applicationName: str = "UyapMevzuat" + paging: bool = True + +# Search Response Models +class BedestenItemType(BaseModel): + name: str + description: str + +class BedestenDecisionEntry(BaseModel): + documentId: str + itemType: BedestenItemType + birimId: Optional[str] = None + birimAdi: Optional[str] + esasNoYil: int + esasNoSira: int + kararNoYil: int + kararNoSira: int + kararTuru: Optional[str] = None + kararTarihi: str + kararTarihiStr: str + kesinlesmeDurumu: Optional[str] = None + kararNo: str + esasNo: str + +class BedestenSearchDataResponse(BaseModel): + emsalKararList: List[BedestenDecisionEntry] + total: int + start: int + +class BedestenSearchResponse(BaseModel): + data: BedestenSearchDataResponse + metadata: Dict[str, Any] + +# Document Request/Response Models +class BedestenDocumentRequestData(BaseModel): + documentId: str + +class BedestenDocumentRequest(BaseModel): + data: BedestenDocumentRequestData + applicationName: str = "UyapMevzuat" + +class BedestenDocumentData(BaseModel): + content: str # Base64 encoded HTML or PDF + mimeType: str + version: int + +class BedestenDocumentResponse(BaseModel): + data: BedestenDocumentData + metadata: Dict[str, Any] + +class BedestenDocumentMarkdown(BaseModel): + documentId: str = Field(..., description="The document ID from Bedesten") + markdown_content: Optional[str] = Field(None, description="The decision content converted to Markdown") + source_url: str = Field(..., description="The source URL of the document") + mime_type: Optional[str] = Field(None, description="Original content type (text/html or application/pdf)") \ No newline at end of file diff --git a/mcp_server_main.py b/mcp_server_main.py index 0762a9a..4e61d46 100644 --- a/mcp_server_main.py +++ b/mcp_server_main.py @@ -36,7 +36,13 @@ from fastmcp import FastMCP # --- Module Imports --- from yargitay_mcp_module.client import YargitayOfficialApiClient from yargitay_mcp_module.models import ( - YargitayDetailedSearchRequest, YargitayDocumentMarkdown, CompactYargitaySearchResult + YargitayDetailedSearchRequest, YargitayDocumentMarkdown, CompactYargitaySearchResult, + YargitayBirimEnum +) +from bedesten_mcp_module.client import BedestenApiClient +from bedesten_mcp_module.models import ( + BedestenSearchRequest, BedestenSearchData, + BedestenDocumentMarkdown, DanistayBirimEnum ) from danistay_mcp_module.client import DanistayApiClient from danistay_mcp_module.models import ( @@ -97,6 +103,7 @@ anayasa_norm_client_instance = AnayasaMahkemesiApiClient() anayasa_bireysel_client_instance = AnayasaBireyselBasvuruApiClient() kik_client_instance = KikApiClient() rekabet_client_instance = RekabetKurumuApiClient() +bedesten_client_instance = BedestenApiClient() KARAR_TURU_ADI_TO_GUID_ENUM_MAP = { @@ -120,9 +127,20 @@ async def search_yargitay_detailed( • +"phrase1" +"phrase2" = Multiple required phrases • +"required" -"excluded" = Include and exclude Examples: arsa payı | "arsa payı" | +"arsa payı" +"bozma sebebi" | bozma*"""), - birimYrgKurulDaire: str = Field("", description="Yargitay Board Unit (e.g., 'Hukuk Genel Kurulu')."), - birimYrgHukukDaire: str = Field("", description="Yargitay Civil Chamber (e.g., '1. Hukuk Dairesi')."), - birimYrgCezaDaire: str = Field("", description="Yargitay Criminal Chamber."), + birimYrgKurulDaire: YargitayBirimEnum = Field("", description=""" + Yargıtay chamber/board selection. Available options: + • Empty string ('') for ALL chambers + • Hukuk Genel Kurulu (Civil General Assembly) + • 1. Hukuk Dairesi through 23. Hukuk Dairesi (Civil Chambers 1-23) + • Hukuk Daireleri Başkanlar Kurulu (Civil Chambers Presidents Board) + • Ceza Genel Kurulu (Criminal General Assembly) + • 1. Ceza Dairesi through 23. Ceza Dairesi (Criminal Chambers 1-23) + • Ceza Daireleri Başkanlar Kurulu (Criminal Chambers Presidents Board) + • Büyük Genel Kurulu (Grand General Assembly) + Total: 49 possible values + """), + birimYrgHukukDaire: str = Field("", description="Legacy field - use birimYrgKurulDaire instead"), + birimYrgCezaDaire: str = Field("", description="Legacy field - use birimYrgKurulDaire instead"), esasYil: str = Field("", description="Case year for 'Esas No'."), esasIlkSiraNo: str = Field("", description="Starting sequence number for 'Esas No'."), esasSonSiraNo: str = Field("", description="Ending sequence number for 'Esas No'."), @@ -756,6 +774,331 @@ async def get_rekabet_kurumu_document( logger.exception(f"Error in tool 'get_rekabet_kurumu_document'. Karar ID: {karar_id}") raise +# --- MCP Tools for Bedesten (Alternative Yargitay Search) --- +@app.tool() +async def search_yargitay_bedesten( + phrase: str = Field(..., description="Aranacak kavram/kelime"), + pageSize: int = Field(10, ge=1, le=100, description="Sayfa başına sonuç sayısı"), + pageNumber: int = Field(1, ge=1, description="Sayfa numarası"), + birimAdi: Optional[YargitayBirimEnum] = Field(None, description=""" + Yargıtay chamber/board filter (optional). Available options: + • None for ALL chambers + • 'Hukuk Genel Kurulu' (Civil General Assembly) + • '1. Hukuk Dairesi' through '23. Hukuk Dairesi' (Civil Chambers 1-23) + • 'Hukuk Daireleri Başkanlar Kurulu' (Civil Chambers Presidents Board) + • 'Ceza Genel Kurulu' (Criminal General Assembly) + • '1. Ceza Dairesi' through '23. Ceza Dairesi' (Criminal Chambers 1-23) + • 'Ceza Daireleri Başkanlar Kurulu' (Criminal Chambers Presidents Board) + • 'Büyük Genel Kurulu' (Grand General Assembly) + Total: 49 chamber options + """) +) -> dict: + """ + Searches Yargıtay decisions using Bedesten API (alternative source). + This complements search_yargitay_detailed for comprehensive coverage. + Always use BOTH Yargıtay search tools for complete results. + + Returns a simplified response with decision list and metadata. + """ + search_data = BedestenSearchData( + pageSize=pageSize, + pageNumber=pageNumber, + itemTypeList=["YARGITAYKARARI"], # Only Yargıtay decisions + phrase=phrase, + birimAdi=birimAdi + ) + + search_request = BedestenSearchRequest(data=search_data) + + logger.info(f"Tool 'search_yargitay_bedesten' called: phrase='{phrase}', birimAdi='{birimAdi}', page={pageNumber}") + + try: + response = await bedesten_client_instance.search_documents(search_request) + + # Return simplified response format + return { + "decisions": [d.model_dump() for d in response.data.emsalKararList], + "total_records": response.data.total, + "requested_page": pageNumber, + "page_size": pageSize + } + except Exception as e: + logger.exception("Error in tool 'search_yargitay_bedesten'") + raise + +@app.tool() +async def get_yargitay_bedesten_document_markdown( + documentId: str = Field(..., description="Document ID from Bedesten search results") +) -> BedestenDocumentMarkdown: + """ + Retrieves a Yargıtay decision document from Bedesten API and converts to Markdown. + Supports both HTML and PDF content types. + Use documentId from search_yargitay_bedesten results. + """ + logger.info(f"Tool 'get_yargitay_bedesten_document_markdown' called for ID: {documentId}") + + if not documentId or not documentId.strip(): + raise ValueError("Document ID must be a non-empty string.") + + try: + return await bedesten_client_instance.get_document_as_markdown(documentId) + except Exception as e: + logger.exception("Error in tool 'get_yargitay_bedesten_document_markdown'") + raise + +# --- MCP Tools for Bedesten (Alternative Danıştay Search) --- +@app.tool() +async def search_danistay_bedesten( + phrase: str = Field(..., description="Aranacak kavram/kelime"), + pageSize: int = Field(10, ge=1, le=100, description="Sayfa başına sonuç sayısı"), + pageNumber: int = Field(1, ge=1, description="Sayfa numarası"), + birimAdi: Optional[DanistayBirimEnum] = Field(None, description=""" + Danıştay chamber/board filter (optional). Available options: + • None for ALL chambers + • Main Councils: 'Büyük Gen.Kur.', 'İdare Dava Daireleri Kurulu', 'Vergi Dava Daireleri Kurulu' + • Chambers: '1. Daire' through '17. Daire' (17 administrative chambers) + • Special Councils: 'İçtihatları Birleştirme Kurulu', 'İdari İşler Kurulu', 'Başkanlar Kurulu' + • Military: 'Askeri Yüksek İdare Mahkemesi' and its chambers/councils + Total: 27 chamber options + """) +) -> dict: + """ + Searches Danıştay decisions using Bedesten API (alternative source). + This complements existing Danıştay search tools (search_danistay_by_keyword, search_danistay_detailed) + for comprehensive coverage. Always use ALL Danıştay search tools for complete results. + + Returns a simplified response with decision list and metadata. + """ + search_data = BedestenSearchData( + pageSize=pageSize, + pageNumber=pageNumber, + itemTypeList=["DANISTAYKARAR"], # Only Danıştay decisions + phrase=phrase, + birimAdi=birimAdi + ) + + search_request = BedestenSearchRequest(data=search_data) + + logger.info(f"Tool 'search_danistay_bedesten' called: phrase='{phrase}', birimAdi='{birimAdi}', page={pageNumber}") + + try: + response = await bedesten_client_instance.search_documents(search_request) + + # Return simplified response format + return { + "decisions": [d.model_dump() for d in response.data.emsalKararList], + "total_records": response.data.total, + "requested_page": pageNumber, + "page_size": pageSize + } + except Exception as e: + logger.exception("Error in tool 'search_danistay_bedesten'") + raise + +@app.tool() +async def get_danistay_bedesten_document_markdown( + documentId: str = Field(..., description="Document ID from Bedesten search results") +) -> BedestenDocumentMarkdown: + """ + Retrieves a Danıştay decision document from Bedesten API and converts to Markdown. + Supports both HTML and PDF content types. + Use documentId from search_danistay_bedesten results. + """ + logger.info(f"Tool 'get_danistay_bedesten_document_markdown' called for ID: {documentId}") + + if not documentId or not documentId.strip(): + raise ValueError("Document ID must be a non-empty string.") + + try: + return await bedesten_client_instance.get_document_as_markdown(documentId) + except Exception as e: + logger.exception("Error in tool 'get_danistay_bedesten_document_markdown'") + raise + +# --- MCP Tools for Bedesten (Yerel Hukuk Mahkemesi Search) --- +@app.tool() +async def search_yerel_hukuk_bedesten( + phrase: str = Field(..., description="Aranacak kavram/kelime"), + pageSize: int = Field(10, ge=1, le=100, description="Sayfa başına sonuç sayısı"), + pageNumber: int = Field(1, ge=1, description="Sayfa numarası") +) -> dict: + """ + Searches Yerel Hukuk Mahkemesi (Local Civil Court) decisions using Bedesten API. + This provides access to local court decisions that are not available through other APIs. + Currently the only available tool for searching Yerel Hukuk Mahkemesi decisions. + + Returns a simplified response with decision list and metadata. + """ + search_data = BedestenSearchData( + pageSize=pageSize, + pageNumber=pageNumber, + itemTypeList=["YERELHUKUK"], # Local Civil Court decisions + phrase=phrase + ) + + search_request = BedestenSearchRequest(data=search_data) + + logger.info(f"Tool 'search_yerel_hukuk_bedesten' called: phrase='{phrase}', page={pageNumber}") + + try: + response = await bedesten_client_instance.search_documents(search_request) + + # Return simplified response format + return { + "decisions": [d.model_dump() for d in response.data.emsalKararList], + "total_records": response.data.total, + "requested_page": pageNumber, + "page_size": pageSize + } + except Exception as e: + logger.exception("Error in tool 'search_yerel_hukuk_bedesten'") + raise + +@app.tool() +async def get_yerel_hukuk_bedesten_document_markdown( + documentId: str = Field(..., description="Document ID from Bedesten search results") +) -> BedestenDocumentMarkdown: + """ + Retrieves a Yerel Hukuk Mahkemesi decision document from Bedesten API and converts to Markdown. + Supports both HTML and PDF content types. + Use documentId from search_yerel_hukuk_bedesten results. + """ + logger.info(f"Tool 'get_yerel_hukuk_bedesten_document_markdown' called for ID: {documentId}") + + if not documentId or not documentId.strip(): + raise ValueError("Document ID must be a non-empty string.") + + try: + return await bedesten_client_instance.get_document_as_markdown(documentId) + except Exception as e: + logger.exception("Error in tool 'get_yerel_hukuk_bedesten_document_markdown'") + raise + +# --- MCP Tools for Bedesten (İstinaf Hukuk Mahkemesi Search) --- +@app.tool() +async def search_istinaf_hukuk_bedesten( + phrase: str = Field(..., description="Aranacak kavram/kelime"), + pageSize: int = Field(10, ge=1, le=100, description="Sayfa başına sonuç sayısı"), + pageNumber: int = Field(1, ge=1, description="Sayfa numarası") +) -> dict: + """ + Searches İstinaf Hukuk Mahkemesi (Civil Court of Appeals) decisions using Bedesten API. + This provides access to appellate court decisions that are not available through other APIs. + Currently the only available tool for searching İstinaf Hukuk Mahkemesi decisions. + + İstinaf courts are intermediate appellate courts in the Turkish judicial system, + handling appeals from local civil courts before cases reach Yargıtay. + + Returns a simplified response with decision list and metadata. + """ + search_data = BedestenSearchData( + pageSize=pageSize, + pageNumber=pageNumber, + itemTypeList=["ISTINAFHUKUK"], # Civil Court of Appeals decisions + phrase=phrase + ) + + search_request = BedestenSearchRequest(data=search_data) + + logger.info(f"Tool 'search_istinaf_hukuk_bedesten' called: phrase='{phrase}', page={pageNumber}") + + try: + response = await bedesten_client_instance.search_documents(search_request) + + # Return simplified response format + return { + "decisions": [d.model_dump() for d in response.data.emsalKararList], + "total_records": response.data.total, + "requested_page": pageNumber, + "page_size": pageSize + } + except Exception as e: + logger.exception("Error in tool 'search_istinaf_hukuk_bedesten'") + raise + +@app.tool() +async def get_istinaf_hukuk_bedesten_document_markdown( + documentId: str = Field(..., description="Document ID from Bedesten search results") +) -> BedestenDocumentMarkdown: + """ + Retrieves an İstinaf Hukuk Mahkemesi decision document from Bedesten API and converts to Markdown. + Supports both HTML and PDF content types. + Use documentId from search_istinaf_hukuk_bedesten results. + """ + logger.info(f"Tool 'get_istinaf_hukuk_bedesten_document_markdown' called for ID: {documentId}") + + if not documentId or not documentId.strip(): + raise ValueError("Document ID must be a non-empty string.") + + try: + return await bedesten_client_instance.get_document_as_markdown(documentId) + except Exception as e: + logger.exception("Error in tool 'get_istinaf_hukuk_bedesten_document_markdown'") + raise + +# --- MCP Tools for Bedesten (Kanun Yararına Bozma Search) --- +@app.tool() +async def search_kyb_bedesten( + phrase: str = Field(..., description="Aranacak kavram/kelime"), + pageSize: int = Field(10, ge=1, le=100, description="Sayfa başına sonuç sayısı"), + pageNumber: int = Field(1, ge=1, description="Sayfa numarası") +) -> dict: + """ + Searches Kanun Yararına Bozma (KYB - Extraordinary Appeal) decisions using Bedesten API. + This provides access to extraordinary appeal decisions that are not available through other APIs. + Currently the only available tool for searching KYB decisions. + + KYB decisions are extraordinary appeals in the Turkish judicial system, + where the Public Prosecutor's Office can request review of finalized decisions + in favor of the law and defendants. + + Returns a simplified response with decision list and metadata. + """ + search_data = BedestenSearchData( + pageSize=pageSize, + pageNumber=pageNumber, + itemTypeList=["KYB"], # Kanun Yararına Bozma decisions + phrase=phrase + ) + + search_request = BedestenSearchRequest(data=search_data) + + logger.info(f"Tool 'search_kyb_bedesten' called: phrase='{phrase}', page={pageNumber}") + + try: + response = await bedesten_client_instance.search_documents(search_request) + + # Return simplified response format + return { + "decisions": [d.model_dump() for d in response.data.emsalKararList], + "total_records": response.data.total, + "requested_page": pageNumber, + "page_size": pageSize + } + except Exception as e: + logger.exception("Error in tool 'search_kyb_bedesten'") + raise + +@app.tool() +async def get_kyb_bedesten_document_markdown( + documentId: str = Field(..., description="Document ID from Bedesten search results") +) -> BedestenDocumentMarkdown: + """ + Retrieves a Kanun Yararına Bozma (KYB) decision document from Bedesten API and converts to Markdown. + Supports both HTML and PDF content types. + Use documentId from search_kyb_bedesten results. + """ + logger.info(f"Tool 'get_kyb_bedesten_document_markdown' called for ID: {documentId}") + + if not documentId or not documentId.strip(): + raise ValueError("Document ID must be a non-empty string.") + + try: + return await bedesten_client_instance.get_document_as_markdown(documentId) + except Exception as e: + logger.exception("Error in tool 'get_kyb_bedesten_document_markdown'") + raise + # --- Application Shutdown Handling --- def perform_cleanup(): logger.info("MCP Server performing cleanup...") @@ -775,7 +1118,8 @@ def perform_cleanup(): globals().get('anayasa_norm_client_instance'), globals().get('anayasa_bireysel_client_instance'), globals().get('kik_client_instance'), - globals().get('rekabet_client_instance') + globals().get('rekabet_client_instance'), + globals().get('bedesten_client_instance') ] async def close_all_clients_async(): tasks = [] diff --git a/yargitay_mcp_module/models.py b/yargitay_mcp_module/models.py index f24d6b2..da63bca 100644 --- a/yargitay_mcp_module/models.py +++ b/yargitay_mcp_module/models.py @@ -1,7 +1,32 @@ # yargitay_mcp_module/models.py from pydantic import BaseModel, Field, HttpUrl -from typing import List, Optional, Dict, Any +from typing import List, Optional, Dict, Any, Literal + +# Yargıtay Chamber/Board Options +YargitayBirimEnum = Literal[ + "", # Empty string for "All" chambers + # Hukuk (Civil) Chambers + "Hukuk Genel Kurulu", + "1. Hukuk Dairesi", "2. Hukuk Dairesi", "3. Hukuk Dairesi", "4. Hukuk Dairesi", + "5. Hukuk Dairesi", "6. Hukuk Dairesi", "7. Hukuk Dairesi", "8. Hukuk Dairesi", + "9. Hukuk Dairesi", "10. Hukuk Dairesi", "11. Hukuk Dairesi", "12. Hukuk Dairesi", + "13. Hukuk Dairesi", "14. Hukuk Dairesi", "15. Hukuk Dairesi", "16. Hukuk Dairesi", + "17. Hukuk Dairesi", "18. Hukuk Dairesi", "19. Hukuk Dairesi", "20. Hukuk Dairesi", + "21. Hukuk Dairesi", "22. Hukuk Dairesi", "23. Hukuk Dairesi", + "Hukuk Daireleri Başkanlar Kurulu", + # Ceza (Criminal) Chambers + "Ceza Genel Kurulu", + "1. Ceza Dairesi", "2. Ceza Dairesi", "3. Ceza Dairesi", "4. Ceza Dairesi", + "5. Ceza Dairesi", "6. Ceza Dairesi", "7. Ceza Dairesi", "8. Ceza Dairesi", + "9. Ceza Dairesi", "10. Ceza Dairesi", "11. Ceza Dairesi", "12. Ceza Dairesi", + "13. Ceza Dairesi", "14. Ceza Dairesi", "15. Ceza Dairesi", "16. Ceza Dairesi", + "17. Ceza Dairesi", "18. Ceza Dairesi", "19. Ceza Dairesi", "20. Ceza Dairesi", + "21. Ceza Dairesi", "22. Ceza Dairesi", "23. Ceza Dairesi", + "Ceza Daireleri Başkanlar Kurulu", + # General Assembly + "Büyük Genel Kurulu" +] class YargitayDetailedSearchRequest(BaseModel): """ @@ -10,11 +35,17 @@ class YargitayDetailedSearchRequest(BaseModel): Based on the payload provided by the user. """ arananKelime: Optional[str] = Field("", description="Keyword to search for.") - # Department/Board selection. Based on user provided payload. - # birimYrg* fields seem to be the ones used for filtering. - birimYrgKurulDaire: Optional[str] = Field("", description="Yargitay Board Unit (e.g., 'Hukuk Genel Kurulu').") - birimYrgHukukDaire: Optional[str] = Field("", description="Yargitay Civil Chamber (e.g., '1. Hukuk Dairesi').") - birimYrgCezaDaire: Optional[str] = Field("", description="Yargitay Criminal Chamber.") + # Department/Board selection - Complete Yargıtay chamber hierarchy + birimYrgKurulDaire: YargitayBirimEnum = Field("", description=""" + Yargıtay chamber/board selection. Options include: + - Empty string ('') for ALL chambers + - Civil: 'Hukuk Genel Kurulu', '1. Hukuk Dairesi' through '23. Hukuk Dairesi', 'Hukuk Daireleri Başkanlar Kurulu' + - Criminal: 'Ceza Genel Kurulu', '1. Ceza Dairesi' through '23. Ceza Dairesi', 'Ceza Daireleri Başkanlar Kurulu' + - General: 'Büyük Genel Kurulu' + Total: 49 possible values (including empty string for all chambers) + """) + birimYrgHukukDaire: Optional[str] = Field("", description="Legacy field - use birimYrgKurulDaire instead") + birimYrgCezaDaire: Optional[str] = Field("", description="Legacy field - use birimYrgKurulDaire instead") esasYil: Optional[str] = Field("", description="Case year for 'Esas No'.") esasIlkSiraNo: Optional[str] = Field("", description="Starting sequence number for 'Esas No'.")