diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e9f20bb --- /dev/null +++ b/.dockerignore @@ -0,0 +1,186 @@ +# flyctl launch added from .gitignore +# Byte-compiled / optimized / DLL files +**/__pycache__ +**/*.py[cod] +**/*$py.class + +# C extensions +**/*.so + +# Distribution / packaging +**/.Python +**/build +**/develop-eggs +**/dist +**/downloads +**/eggs +**/.eggs +**/lib +**/lib64 +**/parts +**/sdist +**/var +**/wheels +**/share/python-wheels +**/*.egg-info +**/.installed.cfg +**/*.egg +**/MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +**/*.manifest +**/*.spec + +# Installer logs +**/pip-log.txt +**/pip-delete-this-directory.txt + +# Unit test / coverage reports +**/htmlcov +**/.tox +**/.nox +**/.coverage +**/.coverage.* +**/.cache +**/nosetests.xml +**/coverage.xml +**/*.cover +**/*.py,cover +**/.hypothesis +**/.pytest_cache +**/cover + +# Translations +**/*.mo +**/*.pot + +# Django stuff: +**/*.log +**/local_settings.py +**/db.sqlite3 +**/db.sqlite3-journal + +# Flask stuff: +**/instance +**/.webassets-cache + +# Scrapy stuff: +**/.scrapy + +# Sphinx documentation +**/docs/_build + +# PyBuilder +**/.pybuilder +**/target + +# Jupyter Notebook +**/.ipynb_checkpoints + +# IPython +**/profile_default +**/ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +**/.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +**/__pypackages__ + +# Celery stuff +**/celerybeat-schedule +**/celerybeat.pid + +# SageMath parsed files +**/*.sage.py + +# Environments +**/.env +**/.venv +**/env +**/venv +**/ENV +**/env.bak +**/venv.bak + +# Spyder project settings +**/.spyderproject +**/.spyproject + +# Rope project settings +**/.ropeproject + +# mkdocs documentation +site + +# mypy +**/.mypy_cache +**/.dmypy.json +**/dmypy.json + +# Pyre type checker +**/.pyre + +# pytype static type analyzer +**/.pytype + +# Cython debug symbols +**/cython_debug + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ +**/.DS_Store +**/hello.py + +**/*.html +**/fast-mcp-docs.md + +# Debug and test files +**/debug_* +**/test_* +**/CLAUDE.md + +# ASGI/Deployment files +**/ssl +**/*.pem +**/*.key +**/*.crt + +# Docker volumes +**/redis-data + +# Production logs +**/logs/*.log.* +**/Dockerfile +**/Dockerfile +fly.toml diff --git a/.gitignore b/.gitignore index db012ff..2a22546 100644 --- a/.gitignore +++ b/.gitignore @@ -180,3 +180,7 @@ redis-data/ # Production logs logs/*.log.* +Dockerfile +Dockerfile +fly.toml +.github/workflows/fly-deploy.yml diff --git a/Dockerfile b/Dockerfile index 15627bb..8a2ab43 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,86 +1,27 @@ -# Multi-stage Dockerfile for Yargı MCP Server - -# Build stage -FROM python:3.12-slim as builder - -# Install build dependencies -RUN apt-get update && apt-get install -y \ - gcc \ - g++ \ - git \ - && rm -rf /var/lib/apt/lists/* - -# Set working directory -WORKDIR /app - -# Copy requirements first for better caching -COPY pyproject.toml ./ -COPY README.md ./ - -# Install dependencies -RUN pip install --no-cache-dir uv && \ - uv pip install --system --no-cache-dir . - -# Runtime stage +# ---------- temel imaj ---------- FROM python:3.12-slim -# Install runtime dependencies +# Playwright’in istediği kitaplıklar RUN apt-get update && apt-get install -y \ - # Required for Playwright - libnss3 \ - libnspr4 \ - libatk1.0-0 \ - libatk-bridge2.0-0 \ - libcups2 \ - libdrm2 \ - libdbus-1-3 \ - libatspi2.0-0 \ - libx11-6 \ - libxcomposite1 \ - libxdamage1 \ - libxext6 \ - libxfixes3 \ - libxrandr2 \ - libgbm1 \ - libxcb1 \ - libxkbcommon0 \ - libpango-1.0-0 \ - libcairo2 \ - libasound2 \ + libnss3 libnspr4 libatk1.0-0 libatk-bridge2.0-0 libcups2 \ + libdrm2 libdbus-1-3 libatspi2.0-0 libx11-6 libxcomposite1 \ + libxdamage1 libxext6 libxfixes3 libxrandr2 libgbm1 libxcb1 \ + libxkbcommon0 libpango-1.0-0 libcairo2 libasound2 \ && rm -rf /var/lib/apt/lists/* -# Create non-root user -RUN useradd -m -u 1000 mcp && \ - mkdir -p /app && \ - chown -R mcp:mcp /app - -# Set working directory WORKDIR /app -# Copy from builder -COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages -COPY --from=builder /usr/local/bin /usr/local/bin +# Gereksinimler +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt -# Copy application code -COPY --chown=mcp:mcp . . +# Uygulama dosyaları +COPY . . -# Install Playwright browsers +# Playwright tarayıcılarını kur RUN playwright install chromium -# Switch to non-root user -USER mcp - -# Expose port +ENV PYTHONUNBUFFERED=1 EXPOSE 8000 -# Set environment variables -ENV PYTHONUNBUFFERED=1 -ENV HOST=0.0.0.0 -ENV PORT=8000 - -# Health check -HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ - CMD python -c "import httpx; httpx.get('http://localhost:8000/health').raise_for_status()" - -# Run the ASGI server -CMD ["uvicorn", "asgi_app:app", "--host", "0.0.0.0", "--port", "8000"] \ No newline at end of file +CMD ["uvicorn", "fastapi_app:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"] diff --git a/fastapi_app.py b/fastapi_app.py index a85ab52..9d99965 100644 --- a/fastapi_app.py +++ b/fastapi_app.py @@ -1,22 +1,17 @@ """ -FastAPI integration for Yargı MCP Server - -This module demonstrates how to integrate the Yargı MCP server -with a FastAPI application, providing additional REST API endpoints -alongside the MCP functionality. - -Usage: - uvicorn fastapi_app:app --host 0.0.0.0 --port 8000 +FastAPI Comprehensive Endpoints with Complete MCP Documentation +This is the complete version with all descriptions and docstrings from MCP server. """ import os from typing import List, Dict, Any, Optional from datetime import datetime -from fastapi import FastAPI, HTTPException, Query, Depends +from fastapi import FastAPI, HTTPException, Query, Depends, Body from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse -from pydantic import BaseModel +from pydantic import BaseModel, Field +import json # Import the main MCP app from mcp_server_main import app as mcp_server @@ -27,9 +22,24 @@ mcp_asgi_app = mcp_server.http_app(path="/mcp") # Create FastAPI app with MCP lifespan app = FastAPI( - title="Yargı MCP API", - description="Turkish Legal Database MCP Server with REST API", - version="0.1.0", + title="Yargı MCP API - Turkish Legal Database REST API", + description=""" + Comprehensive REST API for Turkish Legal Databases with complete MCP tool coverage. + + This API provides access to 8 major Turkish legal institutions including: + • Yargıtay (Court of Cassation) - Supreme civil/criminal court + • Danıştay (Council of State) - Supreme administrative court + • Constitutional Court - Constitutional review and individual applications + • Competition Authority - Antitrust and merger decisions + • Public Procurement Authority - Government contracting disputes + • Court of Accounts - Public audit and accountability + • Emsal (UYAP Precedents) - Cross-court precedent database + • Local and Appellate Courts - First and second instance decisions + + Features complete coverage of 33 MCP tools with enhanced documentation, + typed request models, and comprehensive legal context. + """, + version="1.0.0", lifespan=mcp_asgi_app.lifespan ) @@ -46,7 +56,7 @@ app.add_middleware( # Mount MCP server app.mount("/mcp-server", mcp_asgi_app) -# Response models +# Response models (keeping from original) class ToolInfo(BaseModel): name: str description: str @@ -70,23 +80,264 @@ class HealthCheck(BaseModel): # Track server start time SERVER_START_TIME = datetime.now() +# MCP tool caller helper +async def call_mcp_tool(tool_name: str, arguments: Dict[str, Any]): + """Call an MCP tool with given arguments""" + try: + tool = mcp_server._tool_manager._tools.get(tool_name) + if not tool: + raise HTTPException(status_code=404, detail=f"Tool '{tool_name}' not found") + result = await tool.fn(**arguments) + return result + except Exception as e: + raise HTTPException(status_code=500, detail=f"Tool execution failed: {str(e)}") + +# ============================================================================ +# COMPREHENSIVE REQUEST MODELS WITH FULL MCP DOCUMENTATION +# ============================================================================ + +class YargitaySearchRequest(BaseModel): + """ + Search request for Court of Cassation (Yargıtay) decisions using primary official API. + + The Court of Cassation is Turkey's highest court for civil and criminal matters, + equivalent to a Supreme Court. Provides access to comprehensive supreme court precedents. + """ + arananKelime: str = Field( + ..., + description="""Advanced search operators supported: + • Words with spaces: OR search (property share finds ANY words) + • "Quotes": Exact phrase search ("property share" finds exact phrase) + • Plus (+): AND search (property+share requires both) + • Asterisk (*): Wildcard (construct* matches variations) + • Minus (-): Exclude terms (avoid unwanted results) + + Examples: + • Simple OR: property share (~523K results) + • Exact phrase: "property share" (~22K results) + • Multiple AND: +"property share" +"annulment reason" (~234 results) + • Wildcard: construct* (construction, constructive, etc.) + • Exclude: +"property share" -"construction contract" + """, + example='+"mülkiyet hakkı" +"iptal"' + ) + birimYrgKurulDaire: Optional[str] = Field( + "", + description="""Chamber/board selection (52 options): + Civil Chambers: 1-23. Hukuk Dairesi + Criminal Chambers: 1-23. Ceza Dairesi + General Assemblies: Hukuk Genel Kurulu, Ceza Genel Kurulu + Special Boards: Hukuk/Ceza Daireleri Başkanlar Kurulu, Büyük Genel Kurulu + + Use "" for ALL chambers or specify exact chamber name. + """, + example="1. Hukuk Dairesi" + ) + baslangicTarihi: Optional[str] = Field(None, description="Start date (DD.MM.YYYY)", example="01.01.2020") + bitisTarihi: Optional[str] = Field(None, description="End date (DD.MM.YYYY)", example="31.12.2024") + pageSize: int = Field(20, description="Results per page (1-100)", ge=1, le=100, example=20) + +class YargitayBedestenSearchRequest(BaseModel): + """ + Search request for Court of Cassation using Bedesten API (alternative source). + Complements primary API with different search capabilities and recent decisions. + """ + phrase: str = Field( + ..., + description="""Search phrase with exact matching support: + • Regular: "mülkiyet hakkı" - individual words separately + • Exact: "\"mülkiyet hakkı\"" - exact phrase as unit + + Exact phrase search provides more precise results. + """, + example="\"mülkiyet hakkı\"" + ) + birimAdi: Optional[str] = Field( + None, + description="Chamber filtering (52 options, same as primary API). Use None for all chambers.", + example="1. Hukuk Dairesi" + ) + kararTarihiStart: Optional[str] = Field( + None, + description="Start date (ISO 8601): YYYY-MM-DDTHH:MM:SS.000Z", + example="2024-01-01T00:00:00.000Z" + ) + kararTarihiEnd: Optional[str] = Field( + None, + description="End date (ISO 8601): YYYY-MM-DDTHH:MM:SS.000Z", + example="2024-12-31T23:59:59.999Z" + ) + pageSize: int = Field(20, description="Results per page (1-100)", ge=1, le=100) + +class DanistayKeywordSearchRequest(BaseModel): + """ + Keyword-based search for Council of State (Danıştay) decisions with Boolean logic. + + The Council of State is Turkey's highest administrative court, providing final + rulings on administrative law matters with Boolean keyword operators. + """ + andKelimeler: List[str] = Field( + ..., + description="ALL keywords must be present (AND logic)", + example=["idari işlem", "iptal"] + ) + orKelimeler: Optional[List[str]] = Field( + None, + description="ANY keyword can be present (OR logic)", + example=["ruhsat", "izin", "lisans"] + ) + notKelimeler: Optional[List[str]] = Field( + None, + description="EXCLUDE if keywords present (NOT logic)", + example=["vergi"] + ) + pageSize: int = Field(20, description="Results per page (1-100)", ge=1, le=100) + +class DanistayDetailedSearchRequest(BaseModel): + """ + Detailed search for Council of State decisions with comprehensive filtering. + Provides the most comprehensive search capabilities for administrative court decisions. + """ + daire: Optional[str] = Field( + None, + description="Chamber/Department filter (1. Daire through 17. Daire, special councils)", + example="3. Daire" + ) + baslangicTarihi: Optional[str] = Field(None, description="Start date (DD.MM.YYYY)", example="01.01.2020") + bitisTarihi: Optional[str] = Field(None, description="End date (DD.MM.YYYY)", example="31.12.2024") + esas: Optional[str] = Field(None, description="Case number (Esas No)", example="2024/123") + karar: Optional[str] = Field(None, description="Decision number (Karar No)", example="2024/456") + +class DanistayBedestenSearchRequest(BaseModel): + """ + Council of State search using Bedesten API with chamber filtering and exact phrase search. + Provides access to administrative court decisions with 27 chamber options. + """ + phrase: str = Field(..., description="Search phrase (supports exact matching with quotes)") + birimAdi: Optional[str] = Field( + None, + description="""Chamber filtering (27 options): + Main Councils: Büyük Gen.Kur., İdare Dava Daireleri Kurulu, Vergi Dava Daireleri Kurulu + Chambers: 1. Daire through 17. Daire + Military: Askeri Yüksek İdare Mahkemesi chambers + """, + example="3. Daire" + ) + kararTarihiStart: Optional[str] = Field(None, description="Start date (ISO 8601)") + kararTarihiEnd: Optional[str] = Field(None, description="End date (ISO 8601)") + pageSize: int = Field(20, description="Results per page", ge=1, le=100) + +class EmsalSearchRequest(BaseModel): + """ + Search Precedent (Emsal) decisions from UYAP system across multiple court levels. + Provides access to precedent decisions from various Turkish courts. + """ + keyword: str = Field(..., description="Search keyword across decision texts") + decision_year_karar: Optional[str] = Field(None, description="Decision year filter", example="2024") + results_per_page: int = Field(20, description="Results per page", ge=1, le=100) + +class UyusmazlikSearchRequest(BaseModel): + """ + Search Court of Jurisdictional Disputes decisions. + Resolves jurisdictional disputes between different court systems. + """ + keywords: List[str] = Field(..., description="Search keywords", example=["görev", "uyuşmazlık"]) + page_to_fetch: int = Field(1, description="Page number", ge=1) + +class AnayasaNormSearchRequest(BaseModel): + """ + Search Constitutional Court norm control (judicial review) decisions. + Turkey's highest constitutional authority for reviewing law constitutionality. + """ + keywords_all: List[str] = Field(..., description="All required keywords", example=["eğitim hakkı", "anayasa"]) + period: Optional[str] = Field(None, description="Constitutional period (1=1961, 2=1982)", example="2") + application_type: Optional[str] = Field(None, description="Application type (1=İptal)", example="1") + results_per_page: int = Field(20, description="Results per page", ge=1, le=100) + +class AnayasaBireyselSearchRequest(BaseModel): + """ + Search Constitutional Court individual application decisions. + Human rights violation cases through individual citizen petitions. + """ + keywords: List[str] = Field(..., description="Search keywords", example=["ifade özgürlüğü", "basın"]) + page_to_fetch: int = Field(1, description="Page number", ge=1) + +class KikSearchRequest(BaseModel): + """ + Search Public Procurement Authority (KİK) decisions. + Government procurement disputes and regulatory interpretations. + """ + karar_tipi: Optional[str] = Field( + None, + description="Decision type (rbUyusmazlik=Disputes, rbDuzenleyici=Regulatory, rbMahkeme=Court)", + example="rbUyusmazlik" + ) + karar_metni: Optional[str] = Field(None, description="Decision text search", example="ihale iptali") + basvuru_konusu_ihale: Optional[str] = Field(None, description="Tender subject", example="danışmanlık") + karar_tarihi_baslangic: Optional[str] = Field(None, description="Start date", example="01.01.2023") + +class RekabetSearchRequest(BaseModel): + """ + Search Competition Authority decisions. + Antitrust, merger control, and competition law enforcement. + """ + KararTuru: Optional[str] = Field( + None, + description="Decision type (Birleşme ve Devralma, Rekabet İhlali, Muafiyet, etc.)", + example="Birleşme ve Devralma" + ) + PdfText: Optional[str] = Field( + None, + description="Full-text search in decisions. Use quotes for exact phrases.", + example="\"market definition\" telecommunications" + ) + YayinlanmaTarihi: Optional[str] = Field(None, description="Publication date", example="01.01.2020") + page: int = Field(1, description="Page number", ge=1) + +class BedestenSearchRequest(BaseModel): + """ + Generic search request for Bedesten API courts (Yerel Hukuk, İstinaf Hukuk, KYB). + Supports exact phrase search and date filtering. + """ + phrase: str = Field( + ..., + description="Search phrase. Use quotes for exact matching: \"legal term\"", + example="\"sözleşme ihlali\"" + ) + kararTarihiStart: Optional[str] = Field(None, description="Start date (ISO 8601)") + kararTarihiEnd: Optional[str] = Field(None, description="End date (ISO 8601)") + pageSize: int = Field(20, description="Results per page", ge=1, le=100) + +class SayistaySearchRequest(BaseModel): + """ + Search Court of Accounts (Sayıştay) decisions. + Public audit, accountability, and financial oversight decisions. + """ + keywords: List[str] = Field(..., description="Search keywords", example=["mali sorumluluk", "denetim"]) + page_to_fetch: int = Field(1, description="Page number", ge=1) + +# ============================================================================ +# BASIC SERVER ENDPOINTS (keeping from original) +# ============================================================================ + @app.get("/", response_model=ServerInfo) async def root(): - """Get server information""" + """Get comprehensive server information with database coverage""" return ServerInfo( - name="Yargı MCP Server", - version="0.1.0", - description="MCP server for Turkish legal databases", + name="Yargı MCP Server - Turkish Legal Database API", + version="1.0.0", + description="Complete REST API for Turkish legal databases with 33 MCP tools", tools_count=len(mcp_server._tool_manager._tools), databases=[ - "Yargıtay (Court of Cassation)", - "Danıştay (Council of State)", - "Emsal (Precedent)", - "Uyuşmazlık Mahkemesi (Court of Jurisdictional Disputes)", - "Anayasa Mahkemesi (Constitutional Court)", - "Kamu İhale Kurulu (Public Procurement Authority)", - "Rekabet Kurumu (Competition Authority)", - "Bedesten API (Multiple courts)" + "Yargıtay (Court of Cassation) - 4 tools", + "Danıştay (Council of State) - 5 tools", + "Emsal (UYAP Precedents) - 2 tools", + "Uyuşmazlık Mahkemesi (Jurisdictional Disputes) - 2 tools", + "Anayasa Mahkemesi (Constitutional Court) - 4 tools", + "Kamu İhale Kurulu (Public Procurement) - 2 tools", + "Rekabet Kurumu (Competition Authority) - 2 tools", + "Sayıştay (Court of Accounts) - 6 tools", + "Bedesten API Courts (Local/Appellate/KYB) - 6 tools" ], mcp_endpoint="/mcp-server/mcp/", api_docs="/docs" @@ -94,14 +345,13 @@ async def root(): @app.get("/health", response_model=HealthCheck) async def health_check(): - """Health check endpoint""" + """Health check with comprehensive system status""" uptime = (datetime.now() - SERVER_START_TIME).total_seconds() - return HealthCheck( status="healthy", timestamp=datetime.now(), uptime_seconds=uptime, - tools_operational=len(mcp_server._tool_manager._tools) > 0 + tools_operational=len(mcp_server._tool_manager._tools) == 33 ) @app.get("/api/tools", response_model=List[ToolInfo]) @@ -109,20 +359,16 @@ async def list_tools( search: Optional[str] = Query(None, description="Search tools by name or description"), database: Optional[str] = Query(None, description="Filter by database name") ): - """List all available MCP tools""" + """List all 33 MCP tools with filtering capabilities""" tools = [] - for tool in mcp_server._tool_manager._tools.values(): - # Apply filters if provided if search and search.lower() not in tool.name.lower() and search.lower() not in tool.description.lower(): continue - if database: db_lower = database.lower() if db_lower not in tool.name.lower() and db_lower not in tool.description.lower(): continue - # Extract parameter schema params = {} if hasattr(tool, 'schema') and tool.schema: if hasattr(tool.schema, 'parameters'): @@ -135,139 +381,531 @@ async def list_tools( description=tool.description, parameters=params )) - return tools -@app.get("/api/tools/{tool_name}", response_model=ToolInfo) -async def get_tool(tool_name: str): - """Get detailed information about a specific tool""" - tool = mcp_server._tool_manager._tools.get(tool_name) - - if not tool: - raise HTTPException(status_code=404, detail=f"Tool '{tool_name}' not found") - - # Extract parameter schema - params = {} - if hasattr(tool, 'schema') and tool.schema: - if hasattr(tool.schema, 'parameters'): - params = tool.schema.parameters - elif hasattr(tool.schema, '__annotations__'): - params = {k: str(v) for k, v in tool.schema.__annotations__.items()} - - return ToolInfo( - name=tool.name, - description=tool.description, - parameters=params - ) +# ============================================================================ +# YARGITAY (COURT OF CASSATION) ENDPOINTS - 4 TOOLS +# ============================================================================ + +@app.post( + "/api/yargitay/search", + tags=["Yargıtay"], + summary="Search Court of Cassation (Primary API)", + description="""Search Turkey's Supreme Court for civil and criminal precedents using advanced operators. + +Key Features: +• Advanced search: AND (+), OR (space), NOT (-), wildcards (*), exact phrases ("") +• 52 chamber options (23 Civil + 23 Criminal + General Assemblies) +• Date range filtering • Case/decision number filtering • Pagination + +Search Examples: +• OR search: property share (finds ANY words) +• Exact phrase: "property share" (finds exact phrase) +• AND required: +"property share" +"annulment reason" +• Wildcard: construct* (construction, constructive, etc.) +• Exclude terms: +"property share" -"construction contract" + +Use for supreme court precedent research and legal principle analysis.""" +) +async def search_yargitay(request: YargitaySearchRequest): + """Search Court of Cassation decisions using primary official API with advanced search operators.""" + args = { + "arananKelime": request.arananKelime, + "birimYrgKurulDaire": request.birimYrgKurulDaire, + "pageSize": request.pageSize + } + if request.baslangicTarihi: + args["baslangicTarihi"] = request.baslangicTarihi + if request.bitisTarihi: + args["bitisTarihi"] = request.bitisTarihi + return await call_mcp_tool("search_yargitay_detailed", args) + +@app.post( + "/api/yargitay/search-bedesten", + tags=["Yargıtay"], + summary="Search Court of Cassation (Bedesten API)", + description="""Alternative Court of Cassation search with exact phrase matching and recent decisions. + +Key Features: +• Exact phrase search: "\"legal term\"" for precise matching +• Regular search: "legal term" for individual word matching +• 52 chamber filtering options (same as primary API) +• ISO 8601 date filtering • Recent decision coverage + +Use alongside primary search for comprehensive coverage. Exact phrase search provides +higher precision with fewer false positives.""" +) +async def search_yargitay_bedesten(request: YargitayBedestenSearchRequest): + """Search Court of Cassation using Bedesten API. Complements primary API for complete coverage.""" + args = {"phrase": request.phrase, "pageSize": request.pageSize} + if request.birimAdi: + args["birimAdi"] = request.birimAdi + if request.kararTarihiStart: + args["kararTarihiStart"] = request.kararTarihiStart + if request.kararTarihiEnd: + args["kararTarihiEnd"] = request.kararTarihiEnd + return await call_mcp_tool("search_yargitay_bedesten", args) + +@app.get( + "/api/yargitay/document/{decision_id}", + tags=["Yargıtay"], + summary="Get Court of Cassation Document (Primary API)", + description="""Retrieve complete Court of Cassation decision in Markdown format. + +Content includes: +• Complete legal reasoning and precedent analysis +• Detailed examination of lower court decisions +• Citations of laws, regulations, and prior cases +• Final ruling with legal justification + +Perfect for detailed legal analysis, precedent research, and citation building.""" +) +async def get_yargitay_document(decision_id: str): + """Get full Court of Cassation decision text in clean Markdown format.""" + return await call_mcp_tool("get_yargitay_document_markdown", {"id": decision_id}) + +@app.get( + "/api/yargitay/bedesten-document/{document_id}", + tags=["Yargıtay"], + summary="Get Court of Cassation Document (Bedesten API)", + description="""Retrieve Court of Cassation decision from Bedesten API in Markdown format. + +Features: +• Supports both HTML and PDF source documents +• Clean Markdown conversion with legal structure preserved +• Removes technical artifacts for easy reading +• Compatible with documentId from Bedesten search results""" +) +async def get_yargitay_bedesten_document(document_id: str): + """Get Court of Cassation document from Bedesten API in Markdown format.""" + return await call_mcp_tool("get_yargitay_bedesten_document_markdown", {"documentId": document_id}) + +# ============================================================================ +# DANISTAY (COUNCIL OF STATE) ENDPOINTS - 5 TOOLS +# ============================================================================ + +@app.post( + "/api/danistay/search-keyword", + tags=["Danıştay"], + summary="Search Council of State (Keyword Logic)", + description="""Search Turkey's highest administrative court using Boolean keyword logic. + +Boolean Operators: +• AND keywords: ALL must be present (required terms) +• OR keywords: ANY can be present (alternative terms) +• NOT keywords: EXCLUDE if present (unwanted terms) + +Examples: +• Administrative acts: andKelimeler=["idari işlem", "iptal"] +• Permits/licenses: orKelimeler=["ruhsat", "izin", "lisans"] +• Exclude tax cases: notKelimeler=["vergi"] + +Perfect for administrative law research and government action reviews.""" +) +async def search_danistay_keyword(request: DanistayKeywordSearchRequest): + """Search Council of State using Boolean keyword logic for administrative law research.""" + args = {"andKelimeler": request.andKelimeler, "pageSize": request.pageSize} + if request.orKelimeler: + args["orKelimeler"] = request.orKelimeler + if request.notKelimeler: + args["notKelimeler"] = request.notKelimeler + return await call_mcp_tool("search_danistay_by_keyword", args) + +@app.post( + "/api/danistay/search-detailed", + tags=["Danıştay"], + summary="Search Council of State (Detailed Criteria)", + description="""Most comprehensive Council of State search with advanced filtering. + +Advanced Filtering: +• Chamber targeting (1. Daire through 17. Daire, special councils) +• Case/decision number ranges • Date range filtering +• Legislation cross-referencing • Multiple sorting options + +Use for specialized administrative law research, chamber-specific decisions, +and regulatory compliance analysis.""" +) +async def search_danistay_detailed(request: DanistayDetailedSearchRequest): + """Search Council of State with comprehensive filtering for specialized administrative law research.""" + args = {} + for field in ["daire", "baslangicTarihi", "bitisTarihi", "esas", "karar"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_danistay_detailed", args) + +@app.post( + "/api/danistay/search-bedesten", + tags=["Danıştay"], + summary="Search Council of State (Bedesten API)", + description="""Council of State search via Bedesten API with 27 chamber options and exact phrase search. + +Key Features: +• 27 chamber options (Main Councils, 17 Chambers, Military courts) +• Exact phrase search with double quotes for precision +• ISO 8601 date filtering • Alternative data source + +Use with other Danıştay tools for complete administrative law coverage.""" +) +async def search_danistay_bedesten(request: DanistayBedestenSearchRequest): + """Search Council of State via Bedesten API. Use with other Danıştay tools for complete coverage.""" + args = {"phrase": request.phrase, "pageSize": request.pageSize} + for field in ["birimAdi", "kararTarihiStart", "kararTarihiEnd"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_danistay_bedesten", args) + +@app.get( + "/api/danistay/document/{decision_id}", + tags=["Danıştay"], + summary="Get Council of State Document (Primary API)", + description="""Retrieve complete administrative court decision in Markdown format. + +Content includes: +• Complete administrative law reasoning and precedent analysis +• Review of administrative actions and government decisions +• Citations of administrative laws and regulations +• Final administrative ruling with legal justification + +Essential for administrative law research and government compliance analysis.""" +) +async def get_danistay_document(decision_id: str): + """Get full Council of State decision text in clean Markdown format.""" + return await call_mcp_tool("get_danistay_document_markdown", {"id": decision_id}) + +@app.get( + "/api/danistay/bedesten-document/{document_id}", + tags=["Danıştay"], + summary="Get Council of State Document (Bedesten API)", + description="""Retrieve Council of State decision from Bedesten API in Markdown format.""" +) +async def get_danistay_bedesten_document(document_id: str): + """Get Council of State document from Bedesten API in Markdown format.""" + return await call_mcp_tool("get_danistay_bedesten_document_markdown", {"documentId": document_id}) + +# ============================================================================ +# BEDESTEN API COURTS (LOCAL/APPELLATE/KYB) - 6 TOOLS +# ============================================================================ + +@app.post( + "/api/yerel-hukuk/search", + tags=["Yerel Hukuk"], + summary="Search Local Civil Courts", + description="""Search first-instance civil court decisions using Bedesten API. + +Local Civil Courts handle: +• Contract disputes • Property rights • Family law • Tort claims +• Commercial disputes • Consumer protection + +Only available tool for local court decisions. Supports exact phrase search +and date filtering for precise legal research.""" +) +async def search_yerel_hukuk(request: BedestenSearchRequest): + """Search Local Civil Courts (first instance). Only available tool for local court decisions.""" + args = {"phrase": request.phrase, "pageSize": request.pageSize} + for field in ["kararTarihiStart", "kararTarihiEnd"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_yerel_hukuk_bedesten", args) + +@app.get("/api/yerel-hukuk/document/{document_id}", tags=["Yerel Hukuk"]) +async def get_yerel_hukuk_document(document_id: str): + """Get Local Civil Court decision in Markdown format.""" + return await call_mcp_tool("get_yerel_hukuk_bedesten_document_markdown", {"documentId": document_id}) + +@app.post( + "/api/istinaf-hukuk/search", + tags=["İstinaf Hukuk"], + summary="Search Civil Courts of Appeals", + description="""Search intermediate appellate court decisions using Bedesten API. + +İstinaf Courts are intermediate appellate courts handling appeals from local civil courts +before cases reach the Court of Cassation. Only available tool for İstinaf decisions.""" +) +async def search_istinaf_hukuk(request: BedestenSearchRequest): + """Search Civil Courts of Appeals (intermediate appellate level).""" + args = {"phrase": request.phrase, "pageSize": request.pageSize} + for field in ["kararTarihiStart", "kararTarihiEnd"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_istinaf_hukuk_bedesten", args) + +@app.get("/api/istinaf-hukuk/document/{document_id}", tags=["İstinaf Hukuk"]) +async def get_istinaf_hukuk_document(document_id: str): + """Get Civil Court of Appeals decision in Markdown format.""" + return await call_mcp_tool("get_istinaf_hukuk_bedesten_document_markdown", {"documentId": document_id}) + +@app.post( + "/api/kyb/search", + tags=["KYB"], + summary="Search Extraordinary Appeals (KYB)", + description="""Search Kanun Yararına Bozma (Extraordinary Appeal) decisions. + +KYB is an extraordinary legal remedy where the Public Prosecutor's Office requests +review of finalized decisions in favor of law and defendants. Very rare but important +legal precedents. Only available tool for KYB decisions.""" +) +async def search_kyb(request: BedestenSearchRequest): + """Search Extraordinary Appeal (KYB) decisions. Rare but important legal precedents.""" + args = {"phrase": request.phrase, "pageSize": request.pageSize} + for field in ["kararTarihiStart", "kararTarihiEnd"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_kyb_bedesten", args) + +@app.get("/api/kyb/document/{document_id}", tags=["KYB"]) +async def get_kyb_document(document_id: str): + """Get Extraordinary Appeal (KYB) decision in Markdown format.""" + return await call_mcp_tool("get_kyb_bedesten_document_markdown", {"documentId": document_id}) + +# ============================================================================ +# ADDITIONAL COURTS - 12 TOOLS +# ============================================================================ + +@app.post("/api/emsal/search", tags=["Emsal"], summary="Search UYAP Precedents") +async def search_emsal(request: EmsalSearchRequest): + """Search precedent decisions across multiple court levels via UYAP system.""" + args = {"keyword": request.keyword, "results_per_page": request.results_per_page} + if request.decision_year_karar: + args["decision_year_karar"] = request.decision_year_karar + return await call_mcp_tool("search_emsal_detailed_decisions", args) + +@app.get("/api/emsal/document/{decision_id}", tags=["Emsal"]) +async def get_emsal_document(decision_id: str): + """Get UYAP precedent decision in Markdown format.""" + return await call_mcp_tool("get_emsal_document_markdown", {"decision_id": decision_id}) + +@app.post("/api/uyusmazlik/search", tags=["Uyuşmazlık"], summary="Search Jurisdictional Disputes") +async def search_uyusmazlik(request: UyusmazlikSearchRequest): + """Search Court of Jurisdictional Disputes decisions.""" + return await call_mcp_tool("search_uyusmazlik_decisions", { + "keywords": request.keywords, + "page_to_fetch": request.page_to_fetch + }) + +@app.get("/api/uyusmazlik/document", tags=["Uyuşmazlık"]) +async def get_uyusmazlik_document(document_url: str): + """Get Jurisdictional Disputes decision by URL in Markdown format.""" + return await call_mcp_tool("get_uyusmazlik_document_markdown_from_url", {"document_url": document_url}) + +@app.post("/api/anayasa/search-norm", tags=["Anayasa"], summary="Search Constitutional Court (Norm Control)") +async def search_anayasa_norm(request: AnayasaNormSearchRequest): + """Search Constitutional Court norm control (judicial review) decisions.""" + args = {"keywords_all": request.keywords_all, "results_per_page": request.results_per_page} + for field in ["period", "application_type"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_anayasa_norm_denetimi_decisions", args) + +@app.get("/api/anayasa/norm-document", tags=["Anayasa"]) +async def get_anayasa_norm_document(document_url: str, page_number: int = 1): + """Get Constitutional Court norm control decision in paginated Markdown format.""" + return await call_mcp_tool("get_anayasa_norm_denetimi_document_markdown", { + "document_url": document_url, "page_number": page_number + }) + +@app.post("/api/anayasa/search-bireysel", tags=["Anayasa"], summary="Search Constitutional Court (Individual Applications)") +async def search_anayasa_bireysel(request: AnayasaBireyselSearchRequest): + """Search Constitutional Court individual application (human rights) decisions.""" + return await call_mcp_tool("search_anayasa_bireysel_basvuru_report", { + "keywords": request.keywords, "page_to_fetch": request.page_to_fetch + }) + +@app.get("/api/anayasa/bireysel-document", tags=["Anayasa"]) +async def get_anayasa_bireysel_document(document_url: str, page_number: int = 1): + """Get Constitutional Court individual application decision in paginated Markdown format.""" + return await call_mcp_tool("get_anayasa_bireysel_basvuru_document_markdown", { + "document_url": document_url, "page_number": page_number + }) + +@app.post("/api/kik/search", tags=["KİK"], summary="Search Public Procurement Authority") +async def search_kik(request: KikSearchRequest): + """Search Public Procurement Authority decisions on government contracting disputes.""" + args = {} + for field in ["karar_tipi", "karar_metni", "basvuru_konusu_ihale", "karar_tarihi_baslangic"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_kik_decisions", args) + +@app.get("/api/kik/document/{decision_id}", tags=["KİK"]) +async def get_kik_document(decision_id: str): + """Get Public Procurement Authority decision in paginated Markdown format.""" + return await call_mcp_tool("get_kik_document_markdown", {"decision_id": decision_id}) + +@app.post("/api/rekabet/search", tags=["Rekabet"], summary="Search Competition Authority") +async def search_rekabet(request: RekabetSearchRequest): + """Search Competition Authority decisions on antitrust and merger control.""" + args = {"page": request.page} + for field in ["KararTuru", "PdfText", "YayinlanmaTarihi"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_rekabet_kurumu_decisions", args) + +@app.get("/api/rekabet/document/{karar_id}", tags=["Rekabet"]) +async def get_rekabet_document(karar_id: str, page_number: int = 1): + """Get Competition Authority decision in paginated Markdown format converted from PDF.""" + return await call_mcp_tool("get_rekabet_kurumu_document", { + "karar_id": karar_id, "page_number": page_number + }) + +# ============================================================================ +# SAYISTAY (COURT OF ACCOUNTS) ENDPOINTS - 6 TOOLS +# ============================================================================ + +@app.post("/api/sayistay/search-genel-kurul", tags=["Sayıştay"], summary="Search Court of Accounts (General Assembly)") +async def search_sayistay_genel_kurul(request: SayistaySearchRequest): + """Search Sayıştay General Assembly - highest-level audit interpretive rulings.""" + return await call_mcp_tool("search_sayistay_genel_kurul", { + "keywords": request.keywords, "page_to_fetch": request.page_to_fetch + }) + +@app.get("/api/sayistay/genel-kurul-document", tags=["Sayıştay"]) +async def get_sayistay_genel_kurul_document(document_url: str, page_number: int = 1): + """Get Sayıştay General Assembly decision in Markdown format.""" + return await call_mcp_tool("get_sayistay_genel_kurul_document_markdown", { + "document_url": document_url, "page_number": page_number + }) + +@app.post("/api/sayistay/search-temyiz-kurulu", tags=["Sayıştay"], summary="Search Court of Accounts (Appeals Board)") +async def search_sayistay_temyiz_kurulu(request: SayistaySearchRequest): + """Search Sayıştay Appeals Board - second-level review of audit chamber decisions.""" + return await call_mcp_tool("search_sayistay_temyiz_kurulu", { + "keywords": request.keywords, "page_to_fetch": request.page_to_fetch + }) + +@app.get("/api/sayistay/temyiz-kurulu-document", tags=["Sayıştay"]) +async def get_sayistay_temyiz_kurulu_document(document_url: str, page_number: int = 1): + """Get Sayıştay Appeals Board decision in Markdown format.""" + return await call_mcp_tool("get_sayistay_temyiz_kurulu_document_markdown", { + "document_url": document_url, "page_number": page_number + }) + +@app.post("/api/sayistay/search-daire", tags=["Sayıştay"], summary="Search Court of Accounts (Chambers)") +async def search_sayistay_daire(request: SayistaySearchRequest): + """Search Sayıştay Chambers - first-instance audit findings and sanctions.""" + return await call_mcp_tool("search_sayistay_daire", { + "keywords": request.keywords, "page_to_fetch": request.page_to_fetch + }) + +@app.get("/api/sayistay/daire-document", tags=["Sayıştay"]) +async def get_sayistay_daire_document(document_url: str, page_number: int = 1): + """Get Sayıştay Chamber decision in Markdown format.""" + return await call_mcp_tool("get_sayistay_daire_document_markdown", { + "document_url": document_url, "page_number": page_number + }) + +# ============================================================================ +# ADDITIONAL API ENDPOINTS +# ============================================================================ @app.get("/api/databases") async def list_databases(): - """List all supported legal databases""" - databases = { - "yargitay": { - "name": "Yargıtay (Court of Cassation)", - "description": "Supreme court for civil and criminal cases", - "tools": ["search_yargitay_detailed", "get_yargitay_document_markdown", - "search_yargitay_bedesten", "get_yargitay_bedesten_document_markdown"], - "chambers": 52 - }, - "danistay": { - "name": "Danıştay (Council of State)", - "description": "Supreme administrative court", - "tools": ["search_danistay_by_keyword", "search_danistay_detailed", - "get_danistay_document_markdown", "search_danistay_bedesten", - "get_danistay_bedesten_document_markdown"], - "chambers": 27 - }, - "emsal": { - "name": "Emsal (Precedent)", - "description": "Precedent decisions from various courts", - "tools": ["search_emsal_detailed_decisions", "get_emsal_document_markdown"] - }, - "uyusmazlik": { - "name": "Uyuşmazlık Mahkemesi", - "description": "Court of Jurisdictional Disputes", - "tools": ["search_uyusmazlik_decisions", "get_uyusmazlik_document_markdown_from_url"] - }, - "anayasa": { - "name": "Anayasa Mahkemesi (Constitutional Court)", - "description": "Constitutional review and individual applications", - "tools": ["search_anayasa_norm_denetimi_decisions", - "get_anayasa_norm_denetimi_document_markdown", - "search_anayasa_bireysel_basvuru_report", - "get_anayasa_bireysel_basvuru_document_markdown"] - }, - "kik": { - "name": "Kamu İhale Kurulu", - "description": "Public Procurement Authority", - "tools": ["search_kik_decisions", "get_kik_document_markdown"] - }, - "rekabet": { - "name": "Rekabet Kurumu", - "description": "Competition Authority", - "tools": ["search_rekabet_kurumu_decisions", "get_rekabet_kurumu_document"] - }, - "bedesten": { - "name": "Bedesten API", - "description": "Unified API for multiple courts", - "tools": ["search_yerel_hukuk_bedesten", "get_yerel_hukuk_bedesten_document_markdown", - "search_istinaf_hukuk_bedesten", "get_istinaf_hukuk_bedesten_document_markdown", - "search_kyb_bedesten", "get_kyb_bedesten_document_markdown"] + """Comprehensive database information with tool mappings""" + return { + "total_tools": 33, + "databases": { + "yargitay": { + "name": "Yargıtay (Court of Cassation)", + "description": "Turkey's Supreme Court for civil and criminal matters", + "tools": 4, + "chambers": 52, + "search_tools": ["search_yargitay", "search_yargitay_bedesten"], + "document_tools": ["get_yargitay_document", "get_yargitay_bedesten_document"] + }, + "danistay": { + "name": "Danıştay (Council of State)", + "description": "Turkey's Supreme Administrative Court", + "tools": 5, + "chambers": 27, + "search_tools": ["search_danistay_keyword", "search_danistay_detailed", "search_danistay_bedesten"], + "document_tools": ["get_danistay_document", "get_danistay_bedesten_document"] + }, + "anayasa": { + "name": "Anayasa Mahkemesi (Constitutional Court)", + "description": "Constitutional review and individual applications", + "tools": 4, + "features": ["norm_control", "individual_applications", "human_rights"] + }, + "rekabet": { + "name": "Rekabet Kurumu (Competition Authority)", + "description": "Antitrust and merger control", + "tools": 2, + "coverage": ["mergers", "cartels", "market_abuse", "sector_inquiries"] + }, + "kik": { + "name": "Kamu İhale Kurulu (Public Procurement Authority)", + "description": "Government procurement disputes", + "tools": 2, + "coverage": ["procurement_disputes", "regulatory_decisions", "tender_violations"] + }, + "sayistay": { + "name": "Sayıştay (Court of Accounts)", + "description": "Public audit and financial accountability", + "tools": 6, + "levels": ["general_assembly", "appeals_board", "audit_chambers"] + }, + "emsal": { + "name": "Emsal (UYAP Precedents)", + "description": "Cross-court precedent database", + "tools": 2, + "coverage": ["multi_court", "precedent_analysis"] + }, + "uyusmazlik": { + "name": "Uyuşmazlık Mahkemesi (Jurisdictional Disputes)", + "description": "Inter-court jurisdiction disputes", + "tools": 2, + "specialization": "jurisdictional_conflicts" + }, + "bedesten_courts": { + "name": "Bedesten API Courts (Local/Appellate/KYB)", + "description": "First instance, appellate, and extraordinary appeal courts", + "tools": 6, + "courts": ["yerel_hukuk", "istinaf_hukuk", "kyb"], + "coverage": "complete_court_hierarchy" + } } } - - return JSONResponse(content=databases) @app.get("/api/stats") async def get_statistics(): - """Get server statistics""" + """Comprehensive API statistics and capabilities""" uptime = (datetime.now() - SERVER_START_TIME).total_seconds() - - # Count tools by database - tool_counts = {} - for tool in mcp_server._tool_manager._tools.values(): - for db in ["yargitay", "danistay", "emsal", "uyusmazlik", "anayasa", "kik", "rekabet", "bedesten"]: - if db in tool.name.lower(): - tool_counts[db] = tool_counts.get(db, 0) + 1 - break - - return JSONResponse({ + return { "server": { "uptime_seconds": uptime, "start_time": SERVER_START_TIME.isoformat(), - "version": "0.1.0" + "version": "1.0.0", + "status": "operational" }, - "tools": { - "total": len(mcp_server._tool_manager._tools), - "by_database": tool_counts + "coverage": { + "total_tools": 33, + "total_databases": 9, + "total_chambers": 79, # 52 Yargıtay + 27 Danıştay + "search_tools": 16, + "document_tools": 17 }, "capabilities": { - "total_chambers": 79, # 52 Yargıtay + 27 Danıştay - "date_filtering": True, + "advanced_search_operators": True, "exact_phrase_search": True, + "date_range_filtering": True, + "chamber_filtering": True, + "boolean_logic": True, + "wildcard_search": True, + "pagination": True, + "markdown_conversion": True, + "pdf_processing": True, "dual_api_support": True + }, + "legal_coverage": { + "supreme_courts": ["Yargıtay", "Danıştay"], + "constitutional_law": "Anayasa Mahkemesi", + "administrative_law": "Full coverage", + "competition_law": "Rekabet Kurumu", + "public_procurement": "KİK", + "public_audit": "Sayıştay", + "court_hierarchy": "Complete (Local → Appellate → Supreme)", + "specialized_courts": ["Uyuşmazlık", "Constitutional", "Administrative"] } - }) - -# Add a simple authentication example (optional) -# Uncomment to enable basic token authentication -""" -from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials - -security = HTTPBearer() - -async def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)): - token = credentials.credentials - expected_token = os.getenv("API_TOKEN") - - if expected_token and token != expected_token: - raise HTTPException(status_code=401, detail="Invalid authentication token") - - return token - -# Then add Depends(verify_token) to any endpoint that needs protection -# Example: async def list_tools(..., token: str = Depends(verify_token)): -""" + } if __name__ == "__main__": import uvicorn