Fix tools visibility - revert to v0.1.6 authentication approach
- Disable issuer validation in BearerAuthProvider (issuer=None) - Simplify authentication condition (remove auth_enabled check) - Revert CORS middleware to simple configuration - Fix OAuth metadata endpoint to match v0.1.6 - Apply conditional auth only to MCP server creation Critical fixes for Claude AI tools discovery
This commit is contained in:
@@ -0,0 +1 @@
|
||||
# bedesten_mcp_module/__init__.py
|
||||
@@ -0,0 +1,181 @@
|
||||
# bedesten_mcp_module/client.py
|
||||
|
||||
import httpx
|
||||
import base64
|
||||
from typing import Optional
|
||||
import logging
|
||||
from markitdown import MarkItDown
|
||||
import io
|
||||
|
||||
from .models import (
|
||||
BedestenSearchRequest, BedestenSearchResponse,
|
||||
BedestenDocumentRequest, BedestenDocumentResponse,
|
||||
BedestenDocumentMarkdown, BedestenDocumentRequestData
|
||||
)
|
||||
from .enums import get_full_birim_adi
|
||||
|
||||
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}")
|
||||
|
||||
# Map abbreviated birimAdi to full Turkish name before sending to API
|
||||
original_birim_adi = search_request.data.birimAdi
|
||||
mapped_birim_adi = get_full_birim_adi(original_birim_adi)
|
||||
search_request.data.birimAdi = mapped_birim_adi
|
||||
if original_birim_adi != "ALL":
|
||||
logger.info(f"BedestenApiClient: Mapped birimAdi '{original_birim_adi}' to '{mapped_birim_adi}'")
|
||||
|
||||
try:
|
||||
# Create request dict and remove birimAdi if empty
|
||||
request_dict = search_request.model_dump()
|
||||
if not request_dict["data"]["birimAdi"]: # Remove if empty string
|
||||
del request_dict["data"]["birimAdi"]
|
||||
|
||||
response = await self.http_client.post(
|
||||
self.SEARCH_ENDPOINT,
|
||||
json=request_dict
|
||||
)
|
||||
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
|
||||
|
||||
try:
|
||||
# Convert HTML string to bytes and create BytesIO stream
|
||||
html_bytes = html_content.encode('utf-8')
|
||||
html_stream = io.BytesIO(html_bytes)
|
||||
|
||||
# Pass BytesIO stream to MarkItDown to avoid temp file creation
|
||||
md_converter = MarkItDown()
|
||||
result = md_converter.convert(html_stream)
|
||||
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)}"
|
||||
|
||||
def _convert_pdf_to_markdown(self, pdf_bytes: bytes) -> Optional[str]:
|
||||
"""Convert PDF to Markdown using MarkItDown"""
|
||||
if not pdf_bytes:
|
||||
return None
|
||||
|
||||
try:
|
||||
# Create BytesIO stream from PDF bytes
|
||||
pdf_stream = io.BytesIO(pdf_bytes)
|
||||
|
||||
# Pass BytesIO stream to MarkItDown to avoid temp file creation
|
||||
md_converter = MarkItDown()
|
||||
result = md_converter.convert(pdf_stream)
|
||||
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."
|
||||
|
||||
async def close_client_session(self):
|
||||
"""Close HTTP client session"""
|
||||
await self.http_client.aclose()
|
||||
logger.info("BedestenApiClient: HTTP client session closed.")
|
||||
@@ -0,0 +1,113 @@
|
||||
# bedesten_mcp_module/enums.py
|
||||
|
||||
from typing import Literal
|
||||
|
||||
# Unified compressed enum for both Yargıtay and Danıştay chambers
|
||||
BirimAdiEnum = Literal[
|
||||
"ALL", # All chambers
|
||||
|
||||
# Yargıtay (Court of Cassation) - Civil Chambers
|
||||
"H1", "H2", "H3", "H4", "H5", "H6", "H7", "H8", "H9", "H10",
|
||||
"H11", "H12", "H13", "H14", "H15", "H16", "H17", "H18", "H19", "H20",
|
||||
"H21", "H22", "H23",
|
||||
|
||||
# Yargıtay - Criminal Chambers
|
||||
"C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10",
|
||||
"C11", "C12", "C13", "C14", "C15", "C16", "C17", "C18", "C19", "C20",
|
||||
"C21", "C22", "C23",
|
||||
|
||||
# Yargıtay - Councils and Assemblies
|
||||
"HGK", # Hukuk Genel Kurulu
|
||||
"CGK", # Ceza Genel Kurulu
|
||||
"BGK", # Büyük Genel Kurulu
|
||||
"HBK", # Hukuk Daireleri Başkanlar Kurulu
|
||||
"CBK", # Ceza Daireleri Başkanlar Kurulu
|
||||
|
||||
# Danıştay (Council of State) - Chambers
|
||||
"D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10",
|
||||
"D11", "D12", "D13", "D14", "D15", "D16", "D17",
|
||||
|
||||
# Danıştay - Councils and Boards
|
||||
"DBGK", # Büyük Gen.Kur. (Grand General Assembly)
|
||||
"IDDK", # İdare Dava Daireleri Kurulu
|
||||
"VDDK", # Vergi Dava Daireleri Kurulu
|
||||
"IBK", # İçtihatları Birleştirme Kurulu
|
||||
"IIK", # İdari İşler Kurulu
|
||||
"DBK", # Başkanlar Kurulu
|
||||
|
||||
# Military High Administrative Court
|
||||
"AYIM", # Askeri Yüksek İdare Mahkemesi
|
||||
"AYIMDK", # Askeri Yüksek İdare Mahkemesi Daireler Kurulu
|
||||
"AYIMB", # Askeri Yüksek İdare Mahkemesi Başsavcılığı
|
||||
"AYIM1", # Askeri Yüksek İdare Mahkemesi 1. Daire
|
||||
"AYIM2", # Askeri Yüksek İdare Mahkemesi 2. Daire
|
||||
"AYIM3" # Askeri Yüksek İdare Mahkemesi 3. Daire
|
||||
]
|
||||
|
||||
# Mapping from abbreviated values to full Turkish API values
|
||||
BIRIM_ADI_MAPPING = {
|
||||
"ALL": None, # Will be handled specially in client
|
||||
|
||||
# Yargıtay Civil Chambers (1-23)
|
||||
"H1": "1. Hukuk Dairesi", "H2": "2. Hukuk Dairesi", "H3": "3. Hukuk Dairesi",
|
||||
"H4": "4. Hukuk Dairesi", "H5": "5. Hukuk Dairesi", "H6": "6. Hukuk Dairesi",
|
||||
"H7": "7. Hukuk Dairesi", "H8": "8. Hukuk Dairesi", "H9": "9. Hukuk Dairesi",
|
||||
"H10": "10. Hukuk Dairesi", "H11": "11. Hukuk Dairesi", "H12": "12. Hukuk Dairesi",
|
||||
"H13": "13. Hukuk Dairesi", "H14": "14. Hukuk Dairesi", "H15": "15. Hukuk Dairesi",
|
||||
"H16": "16. Hukuk Dairesi", "H17": "17. Hukuk Dairesi", "H18": "18. Hukuk Dairesi",
|
||||
"H19": "19. Hukuk Dairesi", "H20": "20. Hukuk Dairesi", "H21": "21. Hukuk Dairesi",
|
||||
"H22": "22. Hukuk Dairesi", "H23": "23. Hukuk Dairesi",
|
||||
|
||||
# Yargıtay Criminal Chambers (1-23)
|
||||
"C1": "1. Ceza Dairesi", "C2": "2. Ceza Dairesi", "C3": "3. Ceza Dairesi",
|
||||
"C4": "4. Ceza Dairesi", "C5": "5. Ceza Dairesi", "C6": "6. Ceza Dairesi",
|
||||
"C7": "7. Ceza Dairesi", "C8": "8. Ceza Dairesi", "C9": "9. Ceza Dairesi",
|
||||
"C10": "10. Ceza Dairesi", "C11": "11. Ceza Dairesi", "C12": "12. Ceza Dairesi",
|
||||
"C13": "13. Ceza Dairesi", "C14": "14. Ceza Dairesi", "C15": "15. Ceza Dairesi",
|
||||
"C16": "16. Ceza Dairesi", "C17": "17. Ceza Dairesi", "C18": "18. Ceza Dairesi",
|
||||
"C19": "19. Ceza Dairesi", "C20": "20. Ceza Dairesi", "C21": "21. Ceza Dairesi",
|
||||
"C22": "22. Ceza Dairesi", "C23": "23. Ceza Dairesi",
|
||||
|
||||
# Yargıtay Councils and Assemblies
|
||||
"HGK": "Hukuk Genel Kurulu",
|
||||
"CGK": "Ceza Genel Kurulu",
|
||||
"BGK": "Büyük Genel Kurulu",
|
||||
"HBK": "Hukuk Daireleri Başkanlar Kurulu",
|
||||
"CBK": "Ceza Daireleri Başkanlar Kurulu",
|
||||
|
||||
# Danıştay Chambers (1-17)
|
||||
"D1": "1. Daire", "D2": "2. Daire", "D3": "3. Daire", "D4": "4. Daire",
|
||||
"D5": "5. Daire", "D6": "6. Daire", "D7": "7. Daire", "D8": "8. Daire",
|
||||
"D9": "9. Daire", "D10": "10. Daire", "D11": "11. Daire", "D12": "12. Daire",
|
||||
"D13": "13. Daire", "D14": "14. Daire", "D15": "15. Daire", "D16": "16. Daire",
|
||||
"D17": "17. Daire",
|
||||
|
||||
# Danıştay Councils and Boards
|
||||
"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",
|
||||
|
||||
# Military High Administrative Court
|
||||
"AYIM": "Askeri Yüksek İdare Mahkemesi",
|
||||
"AYIMDK": "Askeri Yüksek İdare Mahkemesi Daireler Kurulu",
|
||||
"AYIMB": "Askeri Yüksek İdare Mahkemesi Başsavcılığı",
|
||||
"AYIM1": "Askeri Yüksek İdare Mahkemesi 1. Daire",
|
||||
"AYIM2": "Askeri Yüksek İdare Mahkemesi 2. Daire",
|
||||
"AYIM3": "Askeri Yüksek İdare Mahkemesi 3. Daire"
|
||||
}
|
||||
|
||||
# Helper function to get full Turkish name from abbreviated value
|
||||
def get_full_birim_adi(abbreviated_value: str) -> str:
|
||||
"""Convert abbreviated birimAdi value to full Turkish name for API calls."""
|
||||
if abbreviated_value == "ALL" or not abbreviated_value:
|
||||
return "" # Empty string for ALL or None
|
||||
|
||||
return BIRIM_ADI_MAPPING.get(abbreviated_value, abbreviated_value)
|
||||
|
||||
# Helper function to validate abbreviated value
|
||||
def is_valid_birim_adi(abbreviated_value: str) -> bool:
|
||||
"""Check if abbreviated birimAdi value is valid."""
|
||||
return abbreviated_value in BIRIM_ADI_MAPPING
|
||||
@@ -0,0 +1,91 @@
|
||||
# bedesten_mcp_module/models.py
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import List, Optional, Dict, Any, Literal, Union
|
||||
from datetime import datetime
|
||||
|
||||
# Import compressed BirimAdiEnum for chamber filtering
|
||||
from .enums import BirimAdiEnum
|
||||
|
||||
# Court Type Options for Unified Search
|
||||
BedestenCourtTypeEnum = Literal[
|
||||
"YARGITAYKARARI", # Yargıtay (Court of Cassation)
|
||||
"DANISTAYKARAR", # Danıştay (Council of State)
|
||||
"YERELHUKUK", # Local Civil Courts
|
||||
"ISTINAFHUKUK", # Civil Courts of Appeals
|
||||
"KYB" # Extraordinary Appeals (Kanun Yararına Bozma)
|
||||
]
|
||||
|
||||
# Search Request Models
|
||||
class BedestenSearchData(BaseModel):
|
||||
pageSize: int = Field(..., description="Results per page (1-10)")
|
||||
pageNumber: int = Field(..., description="Page number (1-indexed)")
|
||||
itemTypeList: List[str] = Field(..., description="Court type filter (YARGITAYKARARI/DANISTAYKARAR/YERELHUKUK/ISTINAFHUKUK/KYB)")
|
||||
phrase: str = Field(..., description="Search phrase. Supports: 'word', \"exact phrase\", +required, -exclude, AND/OR/NOT operators. No wildcards or regex.")
|
||||
birimAdi: BirimAdiEnum = Field("ALL", description="""
|
||||
Chamber filter (optional). Abbreviated values with Turkish names:
|
||||
• Yargıtay: H1-H23 (1-23. Hukuk Dairesi), C1-C23 (1-23. Ceza Dairesi), HGK (Hukuk Genel Kurulu), CGK (Ceza Genel Kurulu), BGK (Büyük Genel Kurulu), HBK (Hukuk Daireleri Başkanlar Kurulu), CBK (Ceza Daireleri Başkanlar Kurulu)
|
||||
• Danıştay: D1-D17 (1-17. Daire), DBGK (Büyük Gen.Kur.), IDDK (İdare Dava Daireleri Kurulu), VDDK (Vergi Dava Daireleri Kurulu), IBK (İçtihatları Birleştirme Kurulu), IIK (İdari İşler Kurulu), DBK (Başkanlar Kurulu), AYIM (Askeri Yüksek İdare Mahkemesi), AYIM1-3 (Askeri Yüksek İdare Mahkemesi 1-3. Daire)
|
||||
""")
|
||||
kararTarihiStart: Optional[str] = Field(None, description="Start date (ISO 8601 format)")
|
||||
kararTarihiEnd: Optional[str] = Field(None, description="End date (ISO 8601 format)")
|
||||
sortFields: List[str] = Field(default=["KARAR_TARIHI"], description="Sort fields")
|
||||
sortDirection: str = Field(default="desc", description="Sort direction (asc/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: Optional[int] = None
|
||||
esasNoSira: Optional[int] = None
|
||||
kararNoYil: Optional[int] = None
|
||||
kararNoSira: Optional[int] = None
|
||||
kararTuru: Optional[str] = None
|
||||
kararTarihi: str
|
||||
kararTarihiStr: str
|
||||
kesinlesmeDurumu: Optional[str] = None
|
||||
kararNo: Optional[str] = None
|
||||
esasNo: Optional[str] = None
|
||||
|
||||
class BedestenSearchDataResponse(BaseModel):
|
||||
emsalKararList: List[BedestenDecisionEntry]
|
||||
total: int
|
||||
start: int
|
||||
|
||||
class BedestenSearchResponse(BaseModel):
|
||||
data: Optional[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 (Belge Kimliği) from Bedesten")
|
||||
markdown_content: Optional[str] = Field(None, description="The decision content (Karar İçeriği) converted to Markdown")
|
||||
source_url: str = Field(..., description="The source URL (Kaynak URL) of the document")
|
||||
mime_type: Optional[str] = Field(None, description="Original content type (İçerik Türü) (text/html or application/pdf)")
|
||||
Reference in New Issue
Block a user