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

# Directory Structure

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

# Files

--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------

```markdown
   1 | # CLAUDE.md
   2 | 
   3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
   4 | 
   5 | ## Project Overview
   6 | 
   7 | This is a FastMCP server that provides programmatic access to Turkish legal databases through the Model Context Protocol (MCP). It integrates with 11 different Turkish legal institutions' databases including Yargıtay (Court of Cassation), Danıştay (Council of State), Constitutional Court, Competition Authority, Court of Accounts (Sayıştay), KVKK (Personal Data Protection Authority), BDDK (Banking Regulation and Supervision Agency), and others.
   8 | 
   9 | **🎯 HIGHLY OPTIMIZED**: This MCP server has been extensively optimized for token efficiency, achieving a **56.8% reduction** in MCP overhead (from 14,061 to 6,073 tokens) while maintaining full functionality.
  10 | 
  11 | **✅ PRODUCTION READY**: Fully deployed on Fly.io with OAuth 2.0 authentication, Bearer JWT token support, and Claude AI integration. Server successfully handles 21 Turkish legal database tools with cross-origin authentication.
  12 | 
  13 | ## Key Commands
  14 | 
  15 | ### Installation and Setup
  16 | 
  17 | #### PyPI Installation (Recommended)
  18 | ```bash
  19 | # Install from PyPI (no Git required)
  20 | pip install yargi-mcp
  21 | 
  22 | # Run the MCP server (stdio mode for Claude Desktop/5ire)
  23 | yargi-mcp
  24 | 
  25 | # Or use uvx for isolated execution
  26 | uvx yargi-mcp
  27 | ```
  28 | 
  29 | #### Development Installation
  30 | ```bash
  31 | # Install dependencies
  32 | uv sync
  33 | 
  34 | # Run the MCP server (stdio mode for Claude Desktop/5ire)
  35 | uv run mcp_server_main.py
  36 | 
  37 | # Or run via the entry point
  38 | yargi-mcp
  39 | 
  40 | # Run as web service (ASGI) with OAuth support
  41 | uv sync --extra saas    # Install OAuth dependencies (Clerk + Stripe)
  42 | cp .env.example .env    # Configure OAuth settings
  43 | uvicorn asgi_app:app --host 0.0.0.0 --port 8000
  44 | ```
  45 | 
  46 | ### Development Commands
  47 | ```bash
  48 | # IMPORTANT: Always use 'uv run' for Python files in this project
  49 | 
  50 | # Run tests (if any exist)
  51 | uv run -m pytest
  52 | 
  53 | # Run specific test file
  54 | uv run test_kik_client.py
  55 | 
  56 | # Debug specific modules
  57 | uv run debug_rekabet_arama.py
  58 | 
  59 | # Test individual components
  60 | uv run -c "import module; module.function()"
  61 | 
  62 | # ASGI Development
  63 | python run_asgi.py --reload --log-level debug  # Auto-reload for development
  64 | uvicorn fastapi_app:app --reload                # FastAPI with interactive docs
  65 | uvicorn starlette_app:app --reload             # Starlette with authentication
  66 | 
  67 | # Note: debug_*, test_*, mcp_overhead_*, and *.txt files are in .gitignore for temporary testing
  68 | 
  69 | # Testing with FastMCP Client
  70 | uv run test_core_tools_quick.py         # Quick test of all core tools (10 tests)
  71 | uv run simple_test.py                   # Simple individual tool test
  72 | uv run measure_mcp_directly.py          # Measure MCP token overhead
  73 | 
  74 | # Test KVKK module (uses fallback token if BRAVE_API_TOKEN not set)
  75 | python test_kvkv_module.py              # Test KVKK search and document retrieval
  76 | 
  77 | # Test KİK v2 comprehensive functionality
  78 | uv run test_kik_v2_comprehensive.py     # Test all three KİK v2 decision types (uyusmazlik, duzenleyici, mahkeme)
  79 | 
  80 | # MCP Optimization Testing
  81 | uv run test_core_tools_quick.py         # Verify all tools work after optimization
  82 | ```
  83 | 
  84 | ## MCP Token Optimization Achievement
  85 | 
  86 | **🚀 EXTRAORDINARY OPTIMIZATION SUCCESS**
  87 | 
  88 | This MCP server has undergone comprehensive optimization to minimize token overhead while maintaining full functionality:
  89 | 
  90 | ### Optimization Results
  91 | - **Baseline**: 14,061 tokens (original, 30 tools)
  92 | - **Phase 1-4**: 7,463 tokens (optimized, 30 tools, 46.9% reduction)
  93 | - **Phase 5**: 6,969 tokens (25 tools, 50.4% reduction)
  94 | - **Phase 6**: 6,608 tokens (23 tools, 53.0% reduction)
  95 | - **Phase 7**: 6,073 tokens (19 tools, 56.8% reduction)
  96 | - **Final Reduction**: 7,988 tokens saved (**56.8% decrease**)
  97 | - **Status**: **Exceeds 10,000 token target by 3,927+ tokens**
  98 | 
  99 | ### Optimization Phases Completed
 100 | 
 101 | #### Phase 1: Null Type Simplification ✅
 102 | - **Impact**: 5,913 token reduction (42.1%)
 103 | - **Method**: Converted `Optional[str] = Field(None)` → `str = Field("")`
 104 | - **Scope**: ~72 parameters across all tools
 105 | - **Result**: Eliminated most `anyOf` JSON schema patterns
 106 | 
 107 | #### Phase 2: Chamber Enum Compression ✅  
 108 | - **Impact**: 669 additional token reduction
 109 | - **Method**: Simplified `search_bedesten_unified` chamber parameter
 110 | - **Note**: Partially reverted to maintain API compatibility
 111 | 
 112 | #### Phase 3: Description Cleanup ✅
 113 | - **Impact**: Minimal change (as expected)
 114 | - **Method**: Replaced "See docs for details" with specific descriptions
 115 | - **Scope**: Sayıştay and other modules
 116 | 
 117 | #### Phase 4: Micro-optimizations ✅
 118 | - **Impact**: 25 token final reduction  
 119 | - **Method**: Shortened verbose parameter descriptions
 120 | - **Target**: Largest remaining tools
 121 | 
 122 | #### Phase 5: Tool Removal (Yargıtay & Danıştay) ✅
 123 | - **Impact**: 494 additional token reduction
 124 | - **Method**: Commented out 5 tools (Yargıtay and Danıştay primary APIs)
 125 | - **Tools Removed**: search_yargitay_detailed, get_yargitay_document_markdown, search_danistay_by_keyword, search_danistay_detailed, get_danistay_document_markdown
 126 | - **Alternative**: Bedesten unified API provides equivalent functionality
 127 | 
 128 | #### Phase 6: Anayasa Tool Unification ✅
 129 | - **Impact**: 361 additional token reduction
 130 | - **Method**: Unified 4 Constitutional Court tools into 2 unified tools
 131 | - **Tools Replaced**: 
 132 |   - ❌ search_anayasa_norm_denetimi_decisions (DEACTIVATED)
 133 |   - ❌ get_anayasa_norm_denetimi_document_markdown (DEACTIVATED) 
 134 |   - ❌ search_anayasa_bireysel_basvuru_report (DEACTIVATED)
 135 |   - ❌ get_anayasa_bireysel_basvuru_document_markdown (DEACTIVATED)
 136 | - **New Unified Tools**:
 137 |   - ✅ search_anayasa_unified (handles both norm control and individual applications)
 138 |   - ✅ get_anayasa_document_unified (auto-detects document type by URL)
 139 | - **Benefits**: Single interface, auto-detection, simplified usage
 140 | 
 141 | #### Phase 7: Sayıştay Tool Unification ✅
 142 | - **Impact**: 535 additional token reduction
 143 | - **Method**: Unified 6 Sayıştay tools into 2 unified tools
 144 | - **Tools Replaced**: 
 145 |   - ❌ search_sayistay_genel_kurul (DEACTIVATED)
 146 |   - ❌ search_sayistay_temyiz_kurulu (DEACTIVATED)
 147 |   - ❌ search_sayistay_daire (DEACTIVATED)
 148 |   - ❌ get_sayistay_genel_kurul_document_markdown (DEACTIVATED)
 149 |   - ❌ get_sayistay_temyiz_kurulu_document_markdown (DEACTIVATED)
 150 |   - ❌ get_sayistay_daire_document_markdown (DEACTIVATED)
 151 | - **New Unified Tools**:
 152 |   - ✅ search_sayistay_unified (handles all three decision types: Genel Kurul, Temyiz Kurulu, Daire)
 153 |   - ✅ get_sayistay_document_unified (unified document retrieval for all decision types)
 154 | - **Benefits**: Single interface, decision type parameter, comprehensive coverage
 155 | 
 156 | ### Current Token Distribution (Top 5 - 19 Tools Total)
 157 | 1. `search_bedesten_unified`: 1,262 tokens (enhanced with search operators)
 158 | 2. `search_sayistay_unified`: 983 tokens (NEW - replaces 3 search tools)
 159 | 3. `search_uyusmazlik_decisions`: 668 tokens (was 997)
 160 | 4. `search_anayasa_unified`: 652 tokens (NEW - replaces 2 search tools)
 161 | 5. `search_kik_decisions`: 560 tokens (was 997)
 162 | 
 163 | ### Testing & Verification ✅
 164 | - **Comprehensive testing**: All 19 active tools verified working
 165 | - **Empty string compatibility**: Confirmed APIs handle empty defaults correctly
 166 | - **Functionality preserved**: 100% test pass rate for active tools
 167 | - **Performance maintained**: Response times unaffected
 168 | - **Tool consolidation**: 11 tools successfully commented out, unified alternatives functional
 169 | - **Phase 6 unification**: Constitutional Court tools merged successfully
 170 | - **Phase 7 unification**: Sayıştay tools merged successfully
 171 | 
 172 | ## Architecture
 173 | 
 174 | ### Core Structure
 175 | - **mcp_server_main.py**: Main server entry point that defines all MCP tools and manages client instances
 176 | - **{module}_mcp_module/**: Each legal database has its own module with:
 177 |   - `client.py`: API client for interacting with the specific legal database
 178 |   - `models.py`: Pydantic models for request/response data structures
 179 |   - `__init__.py`: Module initialization
 180 | 
 181 | ### Legal Database Modules
 182 | 1. **yargitay_mcp_module**: Yargıtay (Court of Cassation) decisions - Primary API
 183 | 2. **bedesten_mcp_module**: Unified API for multiple courts (Yargıtay, Danıştay, Yerel Hukuk, İstinaf Hukuk, KYB) - Highly optimized
 184 | 3. **danistay_mcp_module**: Danıştay (Council of State) decisions  
 185 | 4. **emsal_mcp_module**: Emsal (UYAP precedent) decisions
 186 | 5. **uyusmazlik_mcp_module**: Uyuşmazlık Mahkemesi (Jurisdictional Disputes Court)
 187 | 6. **anayasa_mcp_module**: Constitutional Court (both norm control and individual applications)
 188 | 7. **kik_mcp_module**: KİK (Public Procurement Authority) decisions with v2 API support for three decision types (uyusmazlik, duzenleyici, mahkeme)
 189 | 8. **rekabet_mcp_module**: Competition Authority decisions
 190 | 9. **kvkk_mcp_module**: KVKK (Personal Data Protection Authority) decisions - Brave API integration
 191 | 10. **bddk_mcp_module**: BDDK (Banking Regulation and Supervision Agency) decisions - Tavily API integration
 192 | 11. **sayistay_mcp_module**: Sayıştay (Court of Accounts) decisions - Audit findings and appeals
 193 | 
 194 | ### Key Design Patterns
 195 | - **FastMCP Integration**: Uses FastMCP framework for MCP server implementation
 196 | - **Async HTTP Clients**: Each module uses httpx for async HTTP requests
 197 | - **Pydantic Models**: All data structures use Pydantic for validation
 198 | - **HTML to Markdown Conversion**: Uses MarkItDown library with BytesIO optimization for efficient conversion
 199 | - **BytesIO Optimization**: All HTML/PDF conversion uses in-memory streams instead of temp files
 200 | - **Paginated Content**: Long documents are paginated (5,000 character chunks)
 201 | - **Comprehensive Logging**: Structured logging to files and console
 202 | - **Token Optimization**: Extensively optimized for minimal MCP overhead (56.8% reduction)
 203 | - **Empty String Defaults**: All optional parameters use empty strings instead of null for efficiency
 204 | 
 205 | ### Client Session Management
 206 | - All API clients have `close_client_session()` methods
 207 | - Server handles cleanup via `atexit.register(perform_cleanup)`
 208 | - Proper async session management with error handling
 209 | 
 210 | ### MCP Tools Pattern
 211 | Each legal database exposes 2 main tools:
 212 | 1. **Search tool**: Searches decisions with various criteria
 213 | 2. **Document retrieval tool**: Gets full document content as Markdown
 214 | 
 215 | ### Yargıtay Search Tools (Dual API System)
 216 | 
 217 | The system provides TWO Yargıtay search tools for comprehensive coverage:
 218 | 
 219 | 1. **search_yargitay_detailed** - Primary API (karararama.yargitay.gov.tr) - **DEACTIVATED**
 220 |    - Advanced filtering options (chamber selection, esas/karar no, date range)
 221 |    - Complex search syntax (AND/OR/wildcards/exact phrases)
 222 |    - Chamber selection: 49 options including all Civil (1-23) and Criminal (1-23) chambers
 223 |    - Complete chamber list: Hukuk/Ceza Genel Kurulu, Individual chambers, Başkanlar Kurulu, Büyük Genel Kurulu
 224 |    - **Status**: Commented out for token optimization
 225 | 
 226 | 2. **search_bedesten_unified** - Unified Bedesten API (bedesten.adalet.gov.tr) - **ACTIVE**
 227 |    - Multi-court search with court type selection: `court_types=["YARGITAYKARARI"]` for Yargıtay only
 228 |    - **Exact phrase search**: Use double quotes for precise matching (e.g., `"\"mülkiyet kararı\""`)
 229 |    - **Regular phrase search**: Without quotes searches individual words separately
 230 |    - Chamber selection: Same 49 options as primary API (via birimAdi parameter)
 231 |    - Date filtering support: kararTarihiStart and kararTarihiEnd (ISO 8601 format)
 232 |    - Different data source with recent decisions
 233 |    - Returns documentId for full text retrieval
 234 |    - Supports both HTML and PDF document formats
 235 | 
 236 | **Important**: Use `search_bedesten_unified` with `court_types=["YARGITAYKARARI"]` for Yargıtay searches (primary API deactivated for optimization).
 237 | 
 238 | #### Yargıtay Chamber Selection (49 options)
 239 | 
 240 | **Civil Chambers (Hukuk):**
 241 | - `Hukuk Genel Kurulu` - Civil General Assembly
 242 | - `1. Hukuk Dairesi` through `23. Hukuk Dairesi` - Individual Civil Chambers (23 chambers)
 243 | - `Hukuk Daireleri Başkanlar Kurulu` - Civil Chambers Presidents Board
 244 | 
 245 | **Criminal Chambers (Ceza):**
 246 | - `Ceza Genel Kurulu` - Criminal General Assembly  
 247 | - `1. Ceza Dairesi` through `23. Ceza Dairesi` - Individual Criminal Chambers (23 chambers)
 248 | - `Ceza Daireleri Başkanlar Kurulu` - Criminal Chambers Presidents Board
 249 | 
 250 | **General Assembly:**
 251 | - `Büyük Genel Kurulu` - Grand General Assembly
 252 | 
 253 | **Usage:** Use empty string `""` for ALL chambers, or specify exact chamber name for targeted search.
 254 | 
 255 | ### Danıştay Search Tools (Triple API System)
 256 | 
 257 | The system provides THREE Danıştay search tools for comprehensive coverage:
 258 | 
 259 | 1. **search_danistay_by_keyword** - Primary API (keyword-based) - **DEACTIVATED**
 260 |    - AND/OR/NOT keyword logic
 261 |    - Multiple keyword combinations
 262 |    - **Status**: Commented out for token optimization
 263 | 
 264 | 2. **search_danistay_detailed** - Primary API (detailed criteria) - **DEACTIVATED**
 265 |    - Advanced filtering (daire, esas/karar no, date range, legislation)
 266 |    - Complex search parameters
 267 |    - **Status**: Commented out for token optimization
 268 | 
 269 | 3. **search_bedesten_unified** - Unified Bedesten API (bedesten.adalet.gov.tr) - **ACTIVE**
 270 |    - Multi-court search with court type selection: `court_types=["DANISTAYKARAR"]` for Danıştay only
 271 |    - **Exact phrase search**: Use double quotes for precise matching (e.g., `"\"idari işlem\""`)
 272 |    - **Regular phrase search**: Without quotes searches individual words separately
 273 |    - Chamber selection: 27 Danıştay options (via birimAdi parameter)
 274 |    - Date filtering support: kararTarihiStart and kararTarihiEnd (ISO 8601 format)
 275 |    - Different data source
 276 |    - Returns documentId for full text retrieval
 277 |    - Supports both HTML and PDF document formats
 278 | 
 279 | **Important**: Use `search_bedesten_unified` with `court_types=["DANISTAYKARAR"]` for Danıştay searches (primary APIs deactivated for optimization).
 280 | 
 281 | 
 282 | ### Bedesten Unified API Tools
 283 | 
 284 | The Bedesten API has been unified into a single interface that supports all court types:
 285 | 
 286 | **search_bedesten_unified** - Unified Multi-Court Search
 287 | - **Court type selection**: Use `court_types` parameter to specify which courts to search:
 288 |   - `["YARGITAYKARARI"]` - Yargıtay (Court of Cassation)
 289 |   - `["DANISTAYKARAR"]` - Danıştay (Council of State)
 290 |   - `["YERELHUKUK"]` - Local Civil Courts
 291 |   - `["ISTINAFHUKUK"]` - Civil Courts of Appeals
 292 |   - `["KYB"]` - Extraordinary Appeals (Kanun Yararına Bozma)
 293 |   - Multiple types: `["YARGITAYKARARI", "DANISTAYKARAR"]` for combined search
 294 | - **Search Operators Supported**:
 295 |   - **Simple terms**: `phrase="mülkiyet hakkı"` (searches for both words)
 296 |   - **Exact phrases**: `phrase="\"mülkiyet hakkı\""` (precise matching with quotes)
 297 |   - **Required terms**: `phrase="+mülkiyet hakkı"` (must contain mülkiyet)
 298 |   - **Exclude terms**: `phrase="mülkiyet -kira"` (contains mülkiyet but not kira)
 299 |   - **Boolean AND**: `phrase="mülkiyet AND hak"` (both terms required)
 300 |   - **Boolean OR**: `phrase="mülkiyet OR tapu"` (either term acceptable)
 301 |   - **Boolean NOT**: `phrase="mülkiyet NOT satış"` (contains mülkiyet but not satış)
 302 | - **❌ NOT Supported**: Wildcards (`*`, `?`), regex patterns (`/regex/`), fuzzy search (`~`), proximity search (`~N`)
 303 | - **Chamber filtering**: Available for Yargıtay (52 options) and Danıştay (27 options)
 304 | - **Date filtering**: ISO 8601 format support (kararTarihiStart/kararTarihiEnd)
 305 | - **Pagination**: Configurable page size (1-10) and page number
 306 | - Returns documentId for full text retrieval
 307 | - Supports both HTML and PDF document formats
 308 | 
 309 | **get_bedesten_document_markdown** - Universal Document Retrieval
 310 | - Single tool for retrieving documents from any Bedesten-supported court
 311 | - Auto-detects court type based on document ID
 312 | - Converts decisions to clean Markdown format
 313 | - Handles both HTML and PDF source documents
 314 | 
 315 | ### Sayıştay (Court of Accounts) Search Tools
 316 | 
 317 | The Sayıştay module provides access to audit findings and appeals decisions from Turkey's Court of Accounts:
 318 | 
 319 | **search_sayistay_genel_kurul** - General Assembly Decisions
 320 | - Searches precedent-setting rulings by the full assembly
 321 | - Addresses interpretation of audit and accountability regulations
 322 | - Search by decision number, date range, and full text
 323 | - Returns decision summaries with detailed abstracts
 324 | 
 325 | **search_sayistay_temyiz_kurulu** - Appeals Board Decisions  
 326 | - Reviews appeals against audit chamber decisions
 327 | - Higher-level review of audit findings and sanctions
 328 | - Filter by chamber (1-8), public administration type, decision subject
 329 | - Search by audit report number, file number, appeals decision
 330 | 
 331 | **search_sayistay_daire** - Chamber Decisions
 332 | - First-instance audit findings and sanctions
 333 | - Individual audit chambers before potential appeals
 334 | - Filter by chamber, account year, public administration type
 335 | - Search by audit report number and decision text
 336 | 
 337 | **get_sayistay_document_markdown** - Document Retrieval
 338 | - Retrieves full text of decisions from any Sayıştay decision type
 339 | - Converts decisions to clean Markdown format
 340 | - Supports Genel Kurul, Temyiz Kurulu, and Daire decisions
 341 | 
 342 | ### KVKK Search Tools (Brave API)
 343 | 
 344 | **search_kvkk_decisions** - Brave Search API (api.search.brave.com)
 345 | - Searches KVKK (Personal Data Protection Authority) decisions via Brave API
 346 | - **Turkish language search**: Use Turkish legal terms for best results
 347 | - **Site-targeted search**: Automatically includes `site:kvkk.gov.tr "karar özeti"`
 348 | - **Pagination support**: Page-based results with configurable page size
 349 | - Returns decision summaries with metadata extraction
 350 | - Supports all KVKK decision types (fines, compliance, data breaches, etc.)
 351 | 
 352 | **get_kvkk_document_markdown** - Document retrieval with pagination
 353 | - **Paginated Markdown conversion**: 5,000-character chunks for easier processing
 354 | - **Page parameter support**: `page_number` (1-indexed, default: 1)
 355 | - **BytesIO optimization**: Uses in-memory streams instead of temp files for conversion
 356 | - Extracts structured metadata (decision date, number, subject summary)
 357 | - Handles HTML content from KVKK website with robust error handling
 358 | - Preserves legal document structure and formatting
 359 | - Returns paginated decision text in clean Markdown format
 360 | - **Pagination info**: Includes `current_page`, `total_pages`, `is_paginated` fields
 361 | 
 362 | **KVKK Context**:
 363 | KVKK (Kişisel Verilerin Korunması Kanunu) is Turkey's Personal Data Protection Law, equivalent to GDPR. The Personal Data Protection Authority (KVKK) enforces data protection regulations, issues fines, and publishes decisions on data processing compliance, data breaches, consent requirements, and international data transfers.
 364 | 
 365 | ### BDDK Search Tools (Tavily API)
 366 | 
 367 | **search_bddk_decisions** - BDDK banking regulation decisions search
 368 | - Searches BDDK (Bankacılık Düzenleme ve Denetleme Kurumu) decisions via optimized search
 369 | - **Optimized targeting**: Uses "Karar Sayısı" keyword for precision
 370 | - **Specific URL filtering**: Targets `bddk.org.tr/Mevzuat/DokumanGetir` for decision documents
 371 | - **Pagination support**: Page-based results with configurable page size
 372 | - Returns decision summaries with metadata extraction
 373 | - Supports all BDDK decision types (banking licenses, electronic money, payment services, etc.)
 374 | 
 375 | **get_bddk_document_markdown** - Document retrieval with pagination
 376 | - **Paginated Markdown conversion**: 5,000-character chunks for easier processing
 377 | - **Page parameter support**: `page_number` (1-indexed, default: 1)
 378 | - **Multiple URL pattern support**: Automatic fallback for different BDDK URL formats
 379 | - Extracts structured metadata (decision date, number, subject summary)
 380 | - Handles both HTML and PDF content from BDDK website with robust error handling
 381 | - Preserves legal document structure and formatting
 382 | - Returns paginated decision text in clean Markdown format
 383 | - **Pagination info**: Includes `current_page`, `total_pages`, `is_paginated` fields
 384 | 
 385 | **BDDK Context**:
 386 | BDDK (Bankacılık Düzenleme ve Denetleme Kurumu) is Turkey's Banking Regulation and Supervision Agency responsible for banking licenses, supervision, electronic money institutions, payment services regulation, cryptocurrency guidance, and financial technology oversight.
 387 | 
 388 | Example workflows:
 389 | ```python
 390 | # Yargıtay search with both tools - chamber and date filtering available in both
 391 | results1 = await search_yargitay_detailed(arananKelime="mülkiyet", birimYrgKurulDaire="1. Hukuk Dairesi")
 392 | results2 = await search_bedesten_unified(phrase="mülkiyet", court_types=["YARGITAYKARARI"], birimAdi="1. Hukuk Dairesi", kararTarihiStart="2024-01-01T00:00:00.000Z", kararTarihiEnd="2024-12-31T23:59:59.999Z")
 393 | 
 394 | # Exact phrase search examples for precise matching
 395 | results2_exact = await search_bedesten_unified(phrase="\"mülkiyet kararı\"", court_types=["YARGITAYKARARI"], birimAdi="1. Hukuk Dairesi")  # Exact phrase
 396 | results2_regular = await search_bedesten_unified(phrase="mülkiyet kararı", court_types=["YARGITAYKARARI"], birimAdi="1. Hukuk Dairesi")   # Individual words
 397 | 
 398 | # Get Yargıtay documents
 399 | doc1 = await get_yargitay_document_markdown(id)  # From primary API
 400 | doc2 = await get_bedesten_document_markdown(documentId)  # From Bedesten
 401 | 
 402 | # Danıştay search with all three tools - chamber and date filtering available in Bedesten
 403 | results3 = await search_danistay_by_keyword(andKelimeler=["mülkiyet"])
 404 | results4 = await search_danistay_detailed(...)
 405 | results5 = await search_bedesten_unified(phrase="mülkiyet", court_types=["DANISTAYKARAR"], birimAdi="3. Daire", kararTarihiStart="2024-01-01T00:00:00.000Z", kararTarihiEnd="2024-12-31T23:59:59.999Z")
 406 | 
 407 | # Exact phrase search for Danıştay
 408 | results5_exact = await search_bedesten_unified(phrase="\"idari işlem\"", court_types=["DANISTAYKARAR"], birimAdi="3. Daire")  # Exact phrase
 409 | 
 410 | # Get Danıştay documents
 411 | doc3 = await get_danistay_document_markdown(id)  # From primary APIs
 412 | doc4 = await get_bedesten_document_markdown(documentId)  # From Bedesten
 413 | 
 414 | # Multi-court unified search - with date filtering and exact phrase search
 415 | results6 = await search_bedesten_unified(phrase="mülkiyet", court_types=["YERELHUKUK"], kararTarihiStart="2024-01-01T00:00:00.000Z", kararTarihiEnd="2024-12-31T23:59:59.999Z")
 416 | results6_exact = await search_bedesten_unified(phrase="\"sözleşme ihlali\"", court_types=["YERELHUKUK"])  # Exact phrase
 417 | 
 418 | # İstinaf Hukuk search - with date filtering and exact phrase search
 419 | results7 = await search_bedesten_unified(phrase="mülkiyet", court_types=["ISTINAFHUKUK"], kararTarihiStart="2024-01-01T00:00:00.000Z", kararTarihiEnd="2024-12-31T23:59:59.999Z")
 420 | results7_exact = await search_bedesten_unified(phrase="\"temyiz incelemesi\"", court_types=["ISTINAFHUKUK"])  # Exact phrase
 421 | 
 422 | # KYB search - with date filtering and exact phrase search
 423 | results8 = await search_bedesten_unified(phrase="mülkiyet", court_types=["KYB"], kararTarihiStart="2024-01-01T00:00:00.000Z", kararTarihiEnd="2024-12-31T23:59:59.999Z")
 424 | results8_exact = await search_bedesten_unified(phrase="\"kanun yararına bozma\"", court_types=["KYB"])  # Exact phrase
 425 | 
 426 | # Multi-court combined search
 427 | results9 = await search_bedesten_unified(phrase="mülkiyet", court_types=["YARGITAYKARARI", "DANISTAYKARAR", "YERELHUKUK"], pageSize=10)
 428 | 
 429 | # Universal document retrieval for all Bedesten courts
 430 | doc5 = await get_bedesten_document_markdown(documentId)  # Works for any court type
 431 | 
 432 | # Sayıştay (Court of Accounts) search - audit findings and appeals
 433 | results10 = await search_sayistay_genel_kurul(karar_no="5415", karar_tarih_baslangic="2023")
 434 | results11 = await search_sayistay_temyiz_kurulu(ilam_dairesi="1", yili="2023", kamu_idaresi_turu="Belediyeler ve Bağlı İdareler")
 435 | results12 = await search_sayistay_daire(yargilama_dairesi="1", hesap_yili="2023", web_karar_konusu="İhale Mevzuatı ile İlgili Kararlar")
 436 | 
 437 | # Get Sayıştay documents
 438 | doc8 = await get_sayistay_document_markdown(decision_id="12345", decision_type="genel_kurul")
 439 | 
 440 | # KVKK search (Brave API) - Turkish data protection decisions
 441 | results9 = await search_kvkk_decisions(keywords="açık rıza", page=1, pageSize=10)
 442 | results9_gdpr = await search_kvkk_decisions(keywords="GDPR uyum", page=1, pageSize=5)
 443 | results9_breach = await search_kvkk_decisions(keywords="veri ihlali bildirimi", page=1, pageSize=10)
 444 | 
 445 | # Get KVKK documents
 446 | doc8 = await get_kvkk_document_markdown(decision_url="https://www.kvkk.gov.tr/Icerik/7288/2021-1303")
 447 | ```
 448 | 
 449 | ## Configuration
 450 | 
 451 | ### Dependencies
 452 | - **httpx**: Async HTTP client for API requests
 453 | - **beautifulsoup4**: HTML parsing for preprocessing
 454 | - **markitdown[pdf]**: HTML/PDF to Markdown conversion
 455 | - **pydantic**: Data validation and serialization
 456 | - **fastmcp**: MCP server framework
 457 | - **playwright**: Browser automation for complex scraping
 458 | - **pypdf**: PDF processing capabilities
 459 | 
 460 | ### Environment
 461 | - Python 3.11+ required
 462 | - Supports both uvx and direct Python execution
 463 | - Logs written to `logs/mcp_server.log`
 464 | 
 465 | ### API Keys Configuration
 466 | - **BRAVE_API_TOKEN**: Optional for KVKK search functionality via Brave Search API
 467 |   - Get your API key from: https://brave.com/search/api/
 468 |   - Set environment variable: `export BRAVE_API_TOKEN=your_api_token_here`
 469 |   - **Fallback Token**: If not set, uses a limited free token automatically
 470 |   - KVKK search tools will work without configuration (with rate limits)
 471 | 
 472 | ### OAuth Authentication Configuration
 473 | 
 474 | The server uses **Clerk JWT tokens** for all authentication. **Cross-origin authentication** is implemented using Bearer JWT tokens as per Clerk's best practices.
 475 | 
 476 | #### Key Authentication Features ✅
 477 | - **Clerk JWT Tokens**: All authentication uses Clerk-generated JWT tokens
 478 | - **Bearer Authentication**: Primary method for direct API access (`Authorization: Bearer`)
 479 | - **Cross-Origin Support**: Works across different subdomains
 480 | - **Token Validation**: Uses Clerk's `authenticate_request` method
 481 | - **Email-Based Identity**: User identification via email field in JWT payload
 482 | - **Scopes Support**: Token-based permissions (`yargi.read`, etc.)
 483 | - **SSE & HTTP Support**: Identical authentication for both transports
 484 | - **No Custom Tokens**: Removed custom JWT generation in favor of Clerk tokens
 485 | 
 486 | #### Cross-Origin vs Same-Origin Authentication
 487 | 
 488 | **Current Setup**: Cross-Origin (Different Subdomains)
 489 | - Frontend: `yargimcp.com` 
 490 | - API Server: `api.yargimcp.com`
 491 | - **Status**: Different subdomains = Cross-origin requests
 492 | 
 493 | **Clerk's Authentication Approach**:
 494 | - **Same-Origin** (`foo.com` → `foo.com/api`): Cookies automatically included
 495 | - **Cross-Origin** (`foo.com` → `api.foo.com`): **Bearer tokens required**
 496 | 
 497 | **Important**: Subdomain cookie sharing does **NOT** work for cross-origin requests according to Clerk documentation. JWT tokens are the correct approach.
 498 | 
 499 | #### Setup OAuth Environment
 500 | ```bash
 501 | # Copy environment template
 502 | cp .env.example .env
 503 | 
 504 | # Configure OAuth settings in .env
 505 | ENABLE_AUTH=true                    # Enable/disable authentication
 506 | CLERK_SECRET_KEY=sk_test_xxx        # Clerk secret key from dashboard
 507 | CLERK_PUBLISHABLE_KEY=pk_test_xxx   # Clerk publishable key
 508 | CLERK_OAUTH_REDIRECT_URL=http://localhost:8000/auth/callback
 509 | CLERK_FRONTEND_URL=http://localhost:3000
 510 | ```
 511 | 
 512 | #### JWT Token Authentication Flow
 513 | 
 514 | **Primary Method**: JWT Token (Cross-Origin)
 515 | 
 516 | 1. **Frontend** (`yargimcp.com/sign-in`):
 517 |    ```javascript
 518 |    // User signs in with Clerk
 519 |    const { getToken } = useAuth();
 520 |    
 521 |    // Generate JWT token after login
 522 |    const token = await getToken();
 523 |    
 524 |    // Redirect to API with token parameter
 525 |    window.location.href = `${redirect_url}?clerk_token=${token}`;
 526 |    ```
 527 | 
 528 | 2. **Backend** (`api.yargimcp.com/auth/callback`):
 529 |    ```python
 530 |    # Hybrid authentication: JWT token + cookie fallback
 531 |    if clerk_token:
 532 |        # Validate JWT token with Clerk SDK
 533 |        jwt_claims = clerk.jwt_templates.verify_token(clerk_token)
 534 |        user_id = jwt_claims.get("email")
 535 |    else:
 536 |        # Fallback to cookie validation (for same-origin)
 537 |        clerk_session = request.cookies.get("__session")
 538 |    ```
 539 | 
 540 | #### Authentication Methods Hierarchy
 541 | 
 542 | 1. **Bearer JWT Token** (Primary - Direct API Access)
 543 |    - Standard Bearer authentication header
 544 |    - `Authorization: Bearer YOUR_CLERK_JWT_TOKEN`
 545 |    - Supports both `/mcp` and `/sse` endpoints
 546 |    - Uses Clerk's `authenticate_request` method
 547 |    - Works across different subdomains
 548 | 
 549 | 2. **JWT Token** (OAuth Flow - Cross-Origin)
 550 |    - Passed as `clerk_token` URL parameter during OAuth flow
 551 |    - Validated using Clerk SDK
 552 |    - Used for initial authentication setup
 553 | 
 554 | 3. **Cookie Validation** (Fallback - Same-Origin)
 555 |    - `__session` cookie from Clerk
 556 |    - Only works on same domain/subdomain
 557 |    - Automatic fallback if JWT token missing
 558 | 
 559 | 4. **Trusted Redirect** (Last Resort)
 560 |    - Assumes authentication if Clerk redirected to callback
 561 |    - Used when both JWT and cookie validation fail
 562 | 
 563 | #### Clerk Dashboard Setup
 564 | 1. Create account at [Clerk Dashboard](https://dashboard.clerk.com/)
 565 | 2. Create new application
 566 | 3. Go to **Social Connections** → Enable **Google** provider
 567 | 4. Configure Google OAuth:
 568 |    - Get Google OAuth credentials from [Google Console](https://console.developers.google.com/)
 569 |    - Add redirect URI: `http://localhost:8000/auth/callback`
 570 | 5. Copy API keys to `.env` file
 571 | 
 572 | #### OAuth Flow Endpoints
 573 | ```bash
 574 | # OAuth login (redirects to Clerk/Google)
 575 | GET /auth/login
 576 | 
 577 | # OAuth callback (handles OAuth response with JWT token support)
 578 | GET /auth/callback?clerk_token=JWT_TOKEN
 579 | 
 580 | # Google OAuth direct link
 581 | GET /auth/google/login
 582 | 
 583 | # Get current user info
 584 | GET /auth/user
 585 | 
 586 | # Validate session
 587 | GET /auth/session/validate
 588 | 
 589 | # Logout
 590 | POST /auth/logout
 591 | ```
 592 | 
 593 | #### Bearer JWT Token Usage
 594 | 
 595 | **Getting Clerk JWT Tokens**:
 596 | Clerk JWT tokens are automatically generated by Clerk after user authentication. 
 597 | 
 598 | **Current JWT Claim Configuration**:
 599 | ```json
 600 | {
 601 |   "aud": "pk_test_YXJ0aXN0aWMtc3dhbi04MS5jbGVyay5hY2NvdW50cy5kZXYk",
 602 |   "plan": "{{user.unsafe_metadata.plan}}",
 603 |   "email": "{{user.primary_email_address}}",
 604 |   "scopes": ["yargi.read"]
 605 | }
 606 | ```
 607 | 
 608 | **Recommended Best Practice JWT Claim**:
 609 | ```json
 610 | {
 611 |   "aud": "pk_test_YXJ0aXN0aWMtc3dhbi04MS5jbGVyay5hY2NvdW50cy5kZXYk",
 612 |   "email": "{{user.primary_email_address}}",
 613 |   "email_verified": "{{user.primary_email_address.verification.status}}",
 614 |   "name": "{{user.first_name}} {{user.last_name}}",
 615 |   "given_name": "{{user.first_name}}",
 616 |   "family_name": "{{user.last_name}}",
 617 |   "picture": "{{user.image_url}}",
 618 |   "scopes": ["yargi.read"],
 619 |   "plan": "{{user.public_metadata.plan}}",
 620 |   "role": "{{user.public_metadata.role}}"
 621 | }
 622 | ```
 623 | 
 624 | **JWT Standards Compliance**:
 625 | - ✅ **email**: User's primary email address
 626 | - ✅ **email_verified**: Email verification status
 627 | - ✅ **name**: Full name for display
 628 | - ✅ **given_name/family_name**: Standard name claims
 629 | - ✅ **picture**: User avatar URL
 630 | - ✅ **aud**: Audience (your app's publishable key)
 631 | - ✅ **iat/exp**: Issued at and expiration (automatic)
 632 | - ✅ **iss**: Issuer (Clerk domain)
 633 | 
 634 | **Custom Claims**:
 635 | - **scopes**: Application-specific permissions
 636 | - **plan**: User's subscription plan (from public_metadata)
 637 | - **role**: User's role (from public_metadata)
 638 | 
 639 | **Security Notes**:
 640 | - Use `public_metadata` instead of `unsafe_metadata` for sensitive data
 641 | - `unsafe_metadata` is accessible to frontend, `public_metadata` is read-only
 642 | - Private metadata should not be included in JWT tokens
 643 | 
 644 | **Using Clerk Bearer Tokens**:
 645 | ```bash
 646 | # HTTP Transport
 647 | curl -X POST https://api.yargimcp.com/mcp/ \
 648 |   -H "Authorization: Bearer YOUR_CLERK_JWT_TOKEN" \
 649 |   -H "Content-Type: application/json" \
 650 |   -H "Accept: application/json, text/event-stream" \
 651 |   -H "X-Session-ID: api-client-123" \
 652 |   -d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'
 653 | 
 654 | # SSE Transport
 655 | curl -X POST https://api.yargimcp.com/sse/ \
 656 |   -H "Authorization: Bearer YOUR_CLERK_JWT_TOKEN" \
 657 |   -H "Content-Type: application/json" \
 658 |   -H "Accept: application/json, text/event-stream" \
 659 |   -H "X-Session-ID: sse-client-123" \
 660 |   -d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'
 661 | ```
 662 | 
 663 | **Token Validation Implementation**:
 664 | - Uses Clerk's `authenticate_request` method
 665 | - Validates token signature against Clerk's JWKS endpoint
 666 | - Extracts user identity from `email` field (primary identifier)
 667 | - Supports scopes from token payload (`yargi.read`, etc.)
 668 | - Extracts user metadata: `role`, `plan`, `name`, `email_verified`
 669 | - Stores user information in request state for use by MCP tools
 670 | 
 671 | **Request State Variables**:
 672 | ```python
 673 | request.state.user_id = payload.get('email')
 674 | request.state.user_email = payload.get('email')
 675 | request.state.user_name = payload.get('name')
 676 | request.state.user_role = payload.get('role')
 677 | request.state.user_plan = payload.get('plan')
 678 | request.state.token_scopes = payload.get('scopes', ['yargi.read'])
 679 | ```
 680 | 
 681 | **Migration from Current Claim**:
 682 | 1. Update JWT template in Clerk Dashboard
 683 | 2. Change `unsafe_metadata` to `public_metadata`
 684 | 3. Add standard JWT claims (`name`, `given_name`, etc.)
 685 | 4. Test with new token format
 686 | 
 687 | #### Development vs Production
 688 | - **Development Mode**: Set `ENABLE_AUTH=false` to disable authentication
 689 | - **Production Mode**: Set `ENABLE_AUTH=true` with real Clerk credentials
 690 | - **Token Validation**: Uses Clerk SDK's `authenticate_request()` for all token validation
 691 | - **Bearer JWT Support**: Primary authentication method for direct API access
 692 | - **SSE Transport**: Server-Sent Events support with identical authentication
 693 | - **Custom Tokens**: Removed - all authentication now uses Clerk JWT tokens
 694 | 
 695 | #### Troubleshooting Cross-Origin Authentication
 696 | 
 697 | **Common Issues & Solutions** ✅:
 698 | 
 699 | 1. **MCP Connection Drops Immediately** ✅ **RESOLVED**
 700 |    
 701 |    **Previous Issue**: Claude AI connection dropping after OAuth flow
 702 |    **Root Cause**: Custom FastAPI POST handler conflicting with MCP Auth Toolkit
 703 |    **Solution Applied**: Removed interfering handler, proper request forwarding
 704 |    
 705 |    ```bash
 706 |    # Check backend logs for successful authentication
 707 |    fly logs --app yargi-mcp
 708 |    
 709 |    # Look for these SUCCESS entries:
 710 |    ✅ "OAuth authorize request - client_id: mcp-client-xxx"
 711 |    ✅ "Token exchange - grant_type: authorization_code"  
 712 |    ✅ "PKCE validation successful"
 713 |    ✅ "User authenticated: True, method: trusted_redirect"
 714 |    ```
 715 | 
 716 | 2. **Redis Connection Issues** ✅ **RESOLVED**
 717 |    
 718 |    **Previous Issue**: SSL connection errors with external Upstash Redis
 719 |    **Root Cause**: Using HTTPS URL instead of HTTP for Fly.io's internal Redis
 720 |    **Solution Applied**: 
 721 |    - Switched to Fly.io native Upstash Redis
 722 |    - Use `http://` URL (not `https://`)
 723 |    - Added connection timeout protection (5 seconds)
 724 |    - Automatic fallback to in-memory storage
 725 |    
 726 |    ```bash
 727 |    # Verify Redis is working
 728 |    fly logs --app yargi-mcp | grep "Redis"
 729 |    
 730 |    # Success indicators:
 731 |    ✅ "Upstash Redis client created"
 732 |    ✅ "Redis store initialized successfully"
 733 |    ✅ "Stored authorization code ... in Redis"
 734 |    ```
 735 | 
 736 | 2. **405 Method Not Allowed** ✅ **RESOLVED**
 737 |    
 738 |    **Previous Issue**: POST requests to `/mcp` returning 405 errors
 739 |    **Root Cause**: FastAPI mounting configuration issue
 740 |    **Solution Applied**: Custom route handler with proper ASGI forwarding
 741 |    
 742 |    ```bash
 743 |    # Test POST method acceptance (should return 400, not 405)
 744 |    curl -X POST -s -w "%{http_code}" -o /dev/null https://api.yargimcp.com/mcp
 745 |    # Expected: 400 (bad request due to missing headers)
 746 |    # Fixed: No longer 405 (method not allowed)
 747 |    ```
 748 | 
 749 | 3. **JWT Token Not Generated**
 750 |    ```javascript
 751 |    // Add debug logging in frontend (yargimcp.com/sign-in)
 752 |    console.log('isSignedIn:', isSignedIn);
 753 |    console.log('redirect_url:', redirect_url);
 754 |    console.log('JWT token generated:', token ? 'YES' : 'NO');
 755 |    ```
 756 | 
 757 | 4. **Browser DevTools Debug**
 758 |    ```
 759 |    Network Tab → Find callback request:
 760 |    ✅ URL should contain: ?state=xxx:yyy (state parameter)
 761 |    ✅ Request should go to: api.yargimcp.com/auth/callback
 762 |    ✅ Response: 307 Redirect to Claude AI with authorization code
 763 |    ```
 764 | 
 765 | 5. **Authentication Method Check**
 766 |    ```bash
 767 |    # Backend logs show which method was used:
 768 |    ✅ "User authenticated: True, method: trusted_redirect" (working)
 769 |    # "JWT token validation successful" (if JWT provided)
 770 |    # "Found Clerk session cookie" (fallback)
 771 |    ```
 772 | 
 773 | **Current Status Debug Commands**:
 774 | ```bash
 775 | # Test MCP endpoint availability (HTTP)
 776 | curl -X POST -s -w "%{http_code}" https://api.yargimcp.com/mcp
 777 | # Expected: 400 (accepts POST, but needs proper MCP headers)
 778 | 
 779 | # Test SSE endpoint availability
 780 | curl -X POST -s -w "%{http_code}" https://api.yargimcp.com/sse
 781 | # Expected: 400 (accepts POST, but needs proper MCP headers)
 782 | 
 783 | # Test OAuth discovery
 784 | curl https://api.yargimcp.com/.well-known/oauth-authorization-server | jq '.authorization_endpoint'
 785 | # Expected: "https://api.yargimcp.com/authorize"
 786 | 
 787 | # Test health status
 788 | curl https://api.yargimcp.com/health | jq '.auth_enabled'
 789 | # Expected: true
 790 | 
 791 | # Monitor live connection attempts
 792 | fly logs --app yargi-mcp
 793 | ```
 794 | 
 795 | **Domain Migration Verification**:
 796 | ```bash
 797 | # Verify all endpoints use api.yargimcp.com
 798 | curl -s https://api.yargimcp.com/.well-known/oauth-authorization-server | grep -o "api\.yargimcp\.com" | wc -l
 799 | # Expected: Multiple instances (all endpoints updated)
 800 | 
 801 | # Test cross-domain authentication flow
 802 | curl -I "https://api.yargimcp.com/auth/login"
 803 | # Expected: 30x redirect to accounts.yargimcp.com
 804 | ```
 805 | 
 806 | ## Development Notes
 807 | 
 808 | ### Testing
 809 | 
 810 | #### MCP Server Testing Strategy
 811 | 1. **Individual API Client Tests**: Test each legal database client independently
 812 | 2. **MCP Tool Integration Tests**: Test tools through FastMCP Client
 813 | 3. **Debug Scripts**: Test specific functionality during development
 814 | 
 815 | #### Testing Individual API Clients
 816 | ```bash
 817 | # Test specific database clients directly
 818 | uv run test_bedesten_api.py        # Bedesten API
 819 | uv run debug_rekabet_arama.py      # Competition Authority debug
 820 | uv run test_kik_client.py          # Public Procurement Authority
 821 | ```
 822 | 
 823 | #### Testing MCP Tools with FastMCP Client
 824 | **Recommended approach for testing MCP server functionality:**
 825 | 
 826 | ```python
 827 | from fastmcp import Client
 828 | from mcp_server_main import app
 829 | import json
 830 | 
 831 | async def test_mcp_server():
 832 |     # Create in-memory client (no network/process overhead)
 833 |     client = Client(app)
 834 |     
 835 |     async with client:
 836 |         # 1. List all available tools
 837 |         tools = await client.list_tools()
 838 |         print(f"Available tools: {[tool.name for tool in tools]}")
 839 |         
 840 |         # 2. Call specific tools
 841 |         result = await client.call_tool("search_yargitay_bedesten", {
 842 |             "phrase": "mülkiyet",
 843 |             "pageSize": 5
 844 |         })
 845 |         
 846 |         # 3. Parse FastMCP response format
 847 |         if isinstance(result, list) and len(result) > 0:
 848 |             json_data = json.loads(result[0].text)
 849 |             print(f"Results: {json_data}")
 850 | ```
 851 | 
 852 | #### Testing MCP Server via HTTP with curl
 853 | 
 854 | **Start the ASGI server:**
 855 | ```bash
 856 | # Run the server
 857 | uv run uvicorn asgi_app:app --host 127.0.0.1 --port 8000 --reload
 858 | 
 859 | # Check server health
 860 | curl -s http://127.0.0.1:8000/health | jq '.'
 861 | ```
 862 | 
 863 | **MCP over HTTP Testing:**
 864 | 
 865 | All MCP requests must include proper JSON-RPC format and headers:
 866 | 
 867 | ```bash
 868 | # Required headers for MCP over HTTP
 869 | -H "Content-Type: application/json"
 870 | -H "Accept: application/json, text/event-stream"
 871 | -H "Session-ID: test-session-123"
 872 | 
 873 | # JSON-RPC message format
 874 | {
 875 |   "jsonrpc": "2.0",
 876 |   "method": "METHOD_NAME",
 877 |   "params": {...},
 878 |   "id": 1
 879 | }
 880 | ```
 881 | 
 882 | **Basic MCP Tests:**
 883 | 
 884 | ```bash
 885 | # 1. List all available tools
 886 | curl -s -X POST http://127.0.0.1:8000/mcp/ \
 887 |   -H "Content-Type: application/json" \
 888 |   -H "Accept: application/json, text/event-stream" \
 889 |   -H "Session-ID: test-session-123" \
 890 |   -d '{
 891 |     "jsonrpc": "2.0",
 892 |     "method": "tools/list",
 893 |     "id": 1
 894 |   }' | jq '.result.tools | length'
 895 | 
 896 | # 2. Get specific tool schema
 897 | curl -s -X POST http://127.0.0.1:8000/mcp/ \
 898 |   -H "Content-Type: application/json" \
 899 |   -H "Accept: application/json, text/event-stream" \
 900 |   -H "Session-ID: test-session-123" \
 901 |   -d '{
 902 |     "jsonrpc": "2.0",
 903 |     "method": "tools/list",
 904 |     "id": 1
 905 |   }' | jq '.result.tools[] | select(.name=="search_yargitay_bedesten")'
 906 | 
 907 | # 3. Call a tool (Yargıtay search example)
 908 | curl -s -X POST http://127.0.0.1:8000/mcp/ \
 909 |   -H "Content-Type: application/json" \
 910 |   -H "Accept: application/json, text/event-stream" \
 911 |   -H "Session-ID: test-session-123" \
 912 |   -d '{
 913 |     "jsonrpc": "2.0",
 914 |     "method": "tools/call",
 915 |     "params": {
 916 |       "name": "search_yargitay_bedesten",
 917 |       "arguments": {
 918 |         "phrase": "mülkiyet hakkı",
 919 |         "pageSize": 3
 920 |       }
 921 |     },
 922 |     "id": 1
 923 |   }' | jq '.result.content[0].text | fromjson | .decisions | length'
 924 | 
 925 | # 4. Get document content (using documentId from search results)
 926 | curl -s -X POST http://127.0.0.1:8000/mcp/ \
 927 |   -H "Content-Type: application/json" \
 928 |   -H "Accept: application/json, text/event-stream" \
 929 |   -H "Session-ID: test-session-123" \
 930 |   -d '{
 931 |     "jsonrpc": "2.0",
 932 |     "method": "tools/call",
 933 |     "params": {
 934 |       "name": "get_yargitay_bedesten_document_markdown",
 935 |       "arguments": {
 936 |         "documentId": "DOCUMENT_ID_FROM_SEARCH"
 937 |       }
 938 |     },
 939 |     "id": 1
 940 |   }' | jq '.result.content[0].text' | head -20
 941 | ```
 942 | 
 943 | **Advanced MCP Tool Testing:**
 944 | 
 945 | ```bash
 946 | # Test Danıştay search with chamber filtering
 947 | curl -s -X POST http://127.0.0.1:8000/mcp/ \
 948 |   -H "Content-Type: application/json" \
 949 |   -H "Accept: application/json, text/event-stream" \
 950 |   -H "Session-ID: test-session-123" \
 951 |   -d '{
 952 |     "jsonrpc": "2.0",
 953 |     "method": "tools/call",
 954 |     "params": {
 955 |       "name": "search_danistay_bedesten",
 956 |       "arguments": {
 957 |         "phrase": "idari işlem",
 958 |         "birimAdi": "3. Daire",
 959 |         "pageSize": 5
 960 |       }
 961 |     },
 962 |     "id": 1
 963 |   }' | jq '.result.content[0].text | fromjson'
 964 | 
 965 | # Test Constitutional Court norm control search
 966 | curl -s -X POST http://127.0.0.1:8000/mcp/ \
 967 |   -H "Content-Type: application/json" \
 968 |   -H "Accept: application/json, text/event-stream" \
 969 |   -H "Session-ID: test-session-123" \
 970 |   -d '{
 971 |     "jsonrpc": "2.0",
 972 |     "method": "tools/call",
 973 |     "params": {
 974 |       "name": "search_anayasa_norm_denetimi_decisions",
 975 |       "arguments": {
 976 |         "keywords_all": ["eğitim hakkı"],
 977 |         "results_per_page": 3
 978 |       }
 979 |     },
 980 |     "id": 1
 981 |   }' | jq '.result.content[0].text | fromjson'
 982 | 
 983 | # Test Competition Authority search
 984 | curl -s -X POST http://127.0.0.1:8000/mcp/ \
 985 |   -H "Content-Type: application/json" \
 986 |   -H "Accept: application/json, text/event-stream" \
 987 |   -H "Session-ID: test-session-123" \
 988 |   -d '{
 989 |     "jsonrpc": "2.0",
 990 |     "method": "tools/call",
 991 |     "params": {
 992 |       "name": "search_rekabet_kurumu_decisions",
 993 |       "arguments": {
 994 |         "KararTuru": "Birleşme ve Devralma",
 995 |         "PdfText": "telekomünikasyon",
 996 |         "page": 1
 997 |       }
 998 |     },
 999 |     "id": 1
1000 |   }' | jq '.result.content[0].text | fromjson'
1001 | ```
1002 | 
1003 | **OAuth Authentication Testing:**
1004 | 
1005 | ```bash
1006 | # Test without authentication (development mode)
1007 | curl -s http://127.0.0.1:8000/auth/user | jq '.'
1008 | 
1009 | # Test with OAuth token (production mode)
1010 | curl -s -H "Authorization: Bearer YOUR_CLERK_TOKEN" \
1011 |   http://127.0.0.1:8000/auth/user | jq '.'
1012 | 
1013 | # MCP with authentication
1014 | curl -s -X POST http://127.0.0.1:8000/mcp/ \
1015 |   -H "Content-Type: application/json" \
1016 |   -H "Accept: application/json, text/event-stream" \
1017 |   -H "Session-ID: test-session-123" \
1018 |   -H "Authorization: Bearer YOUR_CLERK_TOKEN" \
1019 |   -d '{
1020 |     "jsonrpc": "2.0",
1021 |     "method": "tools/list",
1022 |     "id": 1
1023 |   }' | jq '.result.tools | length'
1024 | ```
1025 | 
1026 | **Common curl Testing Patterns:**
1027 | 
1028 | ```bash
1029 | # Create a reusable MCP test function
1030 | mcp_test() {
1031 |   local method=$1
1032 |   local params=${2:-"{}"}
1033 |   curl -s -X POST http://127.0.0.1:8000/mcp/ \
1034 |     -H "Content-Type: application/json" \
1035 |     -H "Accept: application/json, text/event-stream" \
1036 |     -H "Session-ID: test-session-123" \
1037 |     -d "{
1038 |       \"jsonrpc\": \"2.0\",
1039 |       \"method\": \"$method\",
1040 |       \"params\": $params,
1041 |       \"id\": 1
1042 |     }"
1043 | }
1044 | 
1045 | # Usage examples
1046 | mcp_test "tools/list" | jq '.result.tools | length'
1047 | mcp_test "tools/call" '{"name": "search_yargitay_bedesten", "arguments": {"phrase": "test", "pageSize": 1}}'
1048 | ```
1049 | 
1050 | 
1051 | 
1052 | #### FastMCP Client Response Format
1053 | **Important**: FastMCP Client returns tool results as `TextContent` objects:
1054 | ```python
1055 | # Response format: List[TextContent]
1056 | result = await client.call_tool("tool_name", params)
1057 | 
1058 | # Extract JSON data
1059 | if isinstance(result, list) and len(result) > 0:
1060 |     text_content = result[0].text
1061 |     parsed_data = json.loads(text_content)
1062 | ```
1063 | 
1064 | #### Running the MCP Server for External Testing
1065 | ```bash
1066 | # Start server for external MCP clients
1067 | uv run mcp_server_main.py
1068 | 
1069 | # Or use entry point
1070 | yargi-mcp
1071 | ```
1072 | 
1073 | 
1074 | ### Search Capabilities
1075 | - Complex search syntax support (AND, OR, NOT, wildcards, exact phrases)
1076 | - Multiple search criteria per legal database
1077 | - Pagination support for large result sets
1078 | 
1079 | 
1080 | 
1081 | 
1082 | ### Date Filtering Format (Bedesten API)
1083 | All Bedesten API tools support consistent date filtering with **automatic format conversion**:
1084 | - **Accepted Formats**: 
1085 |   - Simple format: `YYYY-MM-DD` (e.g., `2020-01-01`) - **automatically converted**
1086 |   - Full ISO 8601: `YYYY-MM-DDTHH:MM:SS.000Z` (e.g., `2020-01-01T00:00:00.000Z`)
1087 | - **Automatic Conversion**: ✅ **NEW FEATURE** - Simple dates are automatically converted to ISO 8601 format
1088 |   - Start dates: `2020-01-01` → `2020-01-01T00:00:00.000Z`
1089 |   - End dates: `2020-01-01` → `2020-01-01T23:59:59.999Z`
1090 | - **Parameters**: `kararTarihiStart` (start date) and `kararTarihiEnd` (end date)
1091 | - **Usage**: Both parameters are optional, use together for date ranges or single parameter for one-sided filtering
1092 | - **Examples**:
1093 |   - **Simple format**: `kararTarihiStart="2024-06-25", kararTarihiEnd="2024-06-25"` ✅ **Works automatically**
1094 |   - **Year range**: `kararTarihiStart="2024-01-01", kararTarihiEnd="2024-12-31"` ✅ **Auto-converted**
1095 |   - **Full ISO format**: `kararTarihiStart="2024-01-01T00:00:00.000Z", kararTarihiEnd="2024-12-31T23:59:59.999Z"`
1096 |   - **From date**: `kararTarihiStart="2024-01-01"` (no end date)
1097 |   - **Until date**: `kararTarihiEnd="2024-12-31"` (no start date)
1098 | 
1099 | ### Exact Phrase Search Format (Bedesten API)
1100 | All Bedesten API tools support two types of phrase searching:
1101 | - **Regular phrase search**: `phrase="mülkiyet kararı"` - searches for individual words separately
1102 | - **Exact phrase search**: `phrase="\"mülkiyet kararı\""` - searches for the exact phrase as a unit
1103 | - **Benefits of exact search**: More precise results, fewer false positives, better for specific legal terms
1104 | - **Usage examples**:
1105 |   - Legal concepts: `"\"idari işlem\""`, `"\"sözleşme ihlali\""`, `"\"kanun yararına bozma\""`
1106 |   - Complex phrases: `"\"temyiz incelemesi\""`, `"\"mülkiyet kararı\""`
1107 |   - Multiple word terms: `"\"kamu yararı\""`, `"\"hukuka aykırılık\""`
1108 | 
1109 | ## FastMCP Best Practices Implementation
1110 | 
1111 | This project implements comprehensive FastMCP best practices to maximize LLM compatibility and effectiveness. Based on official FastMCP documentation, LLMs can read and understand the following components:
1112 | 
1113 | ### LLM-Readable Components
1114 | 1. **Tool Names**: Clear, descriptive names that indicate functionality
1115 | 2. **Tool Descriptions**: Comprehensive explanations of what each tool does
1116 | 3. **Parameter Descriptions**: Detailed parameter documentation with examples
1117 | 4. **Annotations**: Hints that help LLMs understand tool behavior
1118 | 
1119 | ### Tool Description Best Practices ✅ IMPLEMENTED
1120 | 
1121 | All tools in this project follow FastMCP best practices:
1122 | 
1123 | #### 1. Comprehensive Tool Descriptions
1124 | **Format**: `@app.tool(description="Clear description of tool purpose and capabilities")`
1125 | 
1126 | **Implementation Examples**:
1127 | ```python
1128 | @app.tool(
1129 |     description="Search Yargıtay (Court of Cassation) decisions using Bedesten API with chamber filtering (52 options), date range filtering, and exact phrase search capabilities",
1130 |     annotations={...}
1131 | )
1132 | 
1133 | @app.tool(
1134 |     description="Search Danıştay (Council of State) decisions using Bedesten API with chamber filtering (27 options), date range filtering, and exact phrase search support",
1135 |     annotations={...}
1136 | )
1137 | ```
1138 | 
1139 | **Best Practices Applied**:
1140 | - ✅ **Specific functionality**: Clear explanation of what the tool searches
1141 | - ✅ **Key features highlighted**: Chamber count, filtering options, search types
1142 | - ✅ **Legal context**: Court names and their significance in Turkish legal system
1143 | - ✅ **Capability scope**: What makes each tool unique or powerful
1144 | 
1145 | #### 2. Enhanced Parameter Descriptions
1146 | **Format**: Detailed Field descriptions with examples and constraints
1147 | 
1148 | **Implementation Examples**:
1149 | ```python
1150 | phrase: str = Field(..., description="""
1151 |     Aranacak kavram/kelime. İki farklı arama türü desteklenir:
1152 |     • Normal arama: "mülkiyet kararı" - kelimeler ayrı ayrı aranır
1153 |     • Tam cümle arama: "\"mülkiyet kararı\"" - tırnak içindeki ifade aynen aranır
1154 |     Tam cümle aramalar daha kesin sonuçlar verir.
1155 | """)
1156 | 
1157 | birimAdi: Optional[Union[YargitayBirimEnum, DanistayBirimEnum]] = Field(None, description="""
1158 |     Chamber/Department filter (optional). Select specific court chamber:
1159 |     • Yargıtay: 52 options (1-23 Hukuk, 1-23 Ceza, Genel Kurullar, Başkanlar Kurulu)
1160 |     • Danıştay: 27 options (1-17 Daireler, İdare/Vergi Kurulları, Askeri Mahkemeler)
1161 |     Use None/empty for all chambers, or specify exact chamber name
1162 | """)
1163 | 
1164 | kararTarihiStart: Optional[str] = Field(None, description="""
1165 |     Decision start date filter (optional). Format: YYYY-MM-DDTHH:MM:SS.000Z
1166 |     Example: "2024-01-01T00:00:00.000Z" for decisions from Jan 1, 2024
1167 |     Use with kararTarihiEnd for date range filtering
1168 | """)
1169 | ```
1170 | 
1171 | **Best Practices Applied**:
1172 | - ✅ **Format specifications**: Exact format requirements with examples
1173 | - ✅ **Usage scenarios**: When and how to use each parameter
1174 | - ✅ **Option enumeration**: Clear listing of available choices
1175 | - ✅ **Relationship explanations**: How parameters work together
1176 | - ✅ **Practical examples**: Real-world usage patterns
1177 | 
1178 | #### 3. Tool Annotations for LLM Understanding
1179 | **Format**: Annotations provide behavioral hints to LLMs
1180 | 
1181 | **Implementation**:
1182 | ```python
1183 | @app.tool(
1184 |     description="...",
1185 |     annotations={
1186 |         "readOnlyHint": True,        # Tool doesn't modify system state
1187 |         "idempotentHint": True,      # Same inputs = same outputs
1188 |         "openWorldHint": True        # Explores open-ended databases (for search tools)
1189 |     }
1190 | )
1191 | ```
1192 | 
1193 | **Annotation Usage**:
1194 | - ✅ **readOnlyHint**: True for all tools (no system modifications)
1195 | - ✅ **idempotentHint**: True for all tools (deterministic behavior)
1196 | - ✅ **openWorldHint**: True for search tools, False for document retrieval tools
1197 | 
1198 | #### 4. Search vs Document Tool Differentiation
1199 | 
1200 | **Search Tools**:
1201 | - Return structured metadata (lists, summaries, IDs)
1202 | - Support filtering and pagination
1203 | - Have `openWorldHint: True` (explore databases)
1204 | - Names end with descriptive search terms
1205 | 
1206 | **Document Tools**:
1207 | - Return full text content in Markdown format
1208 | - Convert documents from source formats (HTML/PDF)
1209 | - Have `openWorldHint: False` (retrieve specific documents)
1210 | - Names clearly indicate document retrieval
1211 | 
1212 | ### Function-Level Documentation Best Practices ✅ IMPLEMENTED
1213 | 
1214 | #### 1. Comprehensive Docstrings
1215 | **Format**: Multi-line docstrings with structured information
1216 | 
1217 | **Implementation Example**:
1218 | ```python
1219 | async def search_yargitay_bedesten(...):
1220 |     """
1221 |     Searches Yargıtay (Court of Cassation) decisions using Bedesten API.
1222 |     
1223 |     Yargıtay is Turkey's highest court for civil and criminal matters, equivalent
1224 |     to a Supreme Court. This tool provides access to both recent and historical decisions
1225 |     with advanced filtering capabilities.
1226 |     
1227 |     Key Features:
1228 |     • Chamber filtering: 52 options (23 Civil + 23 Criminal + General Assemblies)
1229 |     • Date range filtering with ISO 8601 format (YYYY-MM-DDTHH:MM:SS.000Z)
1230 |     • Exact phrase search using double quotes: "\"legal term\""
1231 |     • Regular search for individual keywords
1232 |     • Pagination support (1-100 results per page)
1233 |     
1234 |     Use cases:
1235 |     • Research supreme court precedents
1236 |     • Find decisions from specific chambers
1237 |     • Search for recent interpretations of legal principles
1238 |     • Analyze court reasoning on specific topics
1239 |     
1240 |     Returns structured data with decision metadata including chamber, dates, case numbers,
1241 |     and summaries. Use get_yargitay_bedesten_document_markdown() to retrieve full texts.
1242 |     """
1243 | ```
1244 | 
1245 | **Best Practices Applied**:
1246 | - ✅ **Legal context**: Court's role in Turkish judicial system
1247 | - ✅ **Feature enumeration**: Key capabilities with technical details
1248 | - ✅ **Use case examples**: When and why to use the tool
1249 | - ✅ **Integration guidance**: How to use with related tools
1250 | - ✅ **Technical specifications**: Format requirements and constraints
1251 | 
1252 | #### 2. Document Tool Specialization
1253 | **Focus**: Clear differentiation and specialized descriptions
1254 | 
1255 | **Implementation Example**:
1256 | ```python
1257 | async def get_yargitay_bedesten_document_markdown(...):
1258 |     """
1259 |     Retrieves the full text of a Yargıtay decision document in Markdown format.
1260 |     
1261 |     This tool converts the original decision document (HTML or PDF) from Bedesten API
1262 |     into clean, readable Markdown format suitable for analysis and processing.
1263 |     
1264 |     Input Requirements:
1265 |     • documentId: Use the ID from search_yargitay_bedesten results
1266 |     • Document ID must be non-empty string
1267 |     
1268 |     Output Format:
1269 |     • Clean Markdown text with proper legal formatting
1270 |     • Preserves court structure (headers, reasoning sections, conclusions)
1271 |     • Removes technical artifacts from source documents
1272 |     
1273 |     Use for:
1274 |     • Reading full supreme court decision texts
1275 |     • Legal analysis of Yargıtay reasoning and precedents
1276 |     • Citation extraction and legal reference building
1277 |     • Content analysis and case summarization
1278 |     """
1279 | ```
1280 | 
1281 | ### Implementation Results
1282 | 
1283 | **Coverage**: Bedesten API tools unified and optimized:
1284 | - **Before**: 10 separate tools (5 search + 5 document tools)
1285 | - **After**: 2 unified tools (`search_bedesten_unified` + `get_bedesten_document_markdown`)
1286 | - **Court Types Supported**: YARGITAYKARARI, DANISTAYKARAR, YERELHUKUK, ISTINAFHUKUK, KYB
1287 | - **Benefits**: Single interface, multi-court search, simplified usage
1288 | 
1289 | **Quality Improvements**:
1290 | - ✅ **LLM Understanding**: Enhanced tool descriptions with legal context
1291 | - ✅ **Parameter Clarity**: Detailed format specifications with examples
1292 | - ✅ **Behavioral Hints**: Proper annotations for tool behavior
1293 | - ✅ **Functional Differentiation**: Clear search vs document tool purposes
1294 | - ✅ **Usage Guidance**: Practical examples and integration patterns
1295 | 
1296 | **Impact**: The Turkish legal database MCP server now provides one of the most comprehensive and LLM-friendly interfaces for accessing Turkish court decisions, with all tools optimized for AI understanding and effective usage.
1297 | 
1298 | ## ASGI Web Service Deployment
1299 | 
1300 | ### Overview
1301 | 
1302 | The Yargı MCP server now supports ASGI deployment, allowing it to run as a web service in addition to the traditional MCP protocol. This enables:
1303 | 
1304 | - **HTTP/REST API access** to all MCP tools
1305 | - **Server-Sent Events (SSE)** transport for real-time streaming
1306 | - **Bearer JWT token authentication** for direct API access
1307 | - **Cloud deployment** on platforms like Heroku, Railway, GCP, AWS
1308 | - **Web-based integration** with existing applications
1309 | - **Scalable architecture** with load balancing and multiple workers
1310 | - **Enhanced monitoring** with health checks and metrics
1311 | 
1312 | ### Quick Start Commands
1313 | 
1314 | ```bash
1315 | # Install ASGI dependencies
1316 | uv pip install -e .[asgi]
1317 | 
1318 | # Run basic ASGI server
1319 | python run_asgi.py
1320 | 
1321 | # Run with development auto-reload
1322 | python run_asgi.py --reload --log-level debug
1323 | 
1324 | # Run FastAPI integration (with interactive docs)
1325 | uvicorn fastapi_app:app --reload
1326 | 
1327 | # Run with custom configuration
1328 | python run_asgi.py --host 0.0.0.0 --port 8080 --workers 4
1329 | ```
1330 | 
1331 | ### Available ASGI Applications
1332 | 
1333 | #### 1. Basic ASGI (`asgi_app.py`)
1334 | - **Primary app**: `uvicorn asgi_app:app`
1335 | - **SSE support**: Built-in SSE transport at `/sse`
1336 | - **Endpoints**:
1337 |   - `/mcp/` - MCP endpoint (Streamable HTTP)
1338 |   - `/sse/` - MCP endpoint (Server-Sent Events)
1339 |   - `/health` - Health check
1340 |   - `/status` - Server status
1341 |   - `/` - Service information
1342 | - **Authentication**:
1343 |   - OAuth 2.0 via MCP Auth Toolkit
1344 |   - Bearer JWT token support (optional)
1345 |   - Hybrid authentication with fallbacks
1346 | 
1347 | #### 2. FastAPI Integration (`fastapi_app.py`)
1348 | - **Command**: `uvicorn fastapi_app:app`
1349 | - **Additional endpoints**:
1350 |   - `/docs` - Interactive API documentation
1351 |   - `/api/tools` - List all MCP tools
1352 |   - `/api/tools/{tool_name}` - Tool details
1353 |   - `/api/databases` - Database information
1354 |   - `/api/stats` - Server statistics
1355 |   - `/mcp-server/mcp/` - MCP endpoint
1356 | 
1357 | #### 3. Starlette with Authentication (`starlette_app.py`)
1358 | - **Command**: `uvicorn starlette_app:app`
1359 | - **Features**:
1360 |   - Token-based authentication
1361 |   - Custom middleware
1362 |   - Nested routing examples
1363 | 
1364 | ### Environment Configuration
1365 | 
1366 | Create `.env` file from template:
1367 | ```bash
1368 | cp .env.example .env
1369 | ```
1370 | 
1371 | Key variables:
1372 | ```bash
1373 | HOST=0.0.0.0                    # Server host
1374 | PORT=8000                       # Server port
1375 | ALLOWED_ORIGINS=*               # CORS origins (comma-separated)
1376 | LOG_LEVEL=info                  # Logging level
1377 | API_TOKEN=your-secret-token     # Optional authentication
1378 | ```
1379 | 
1380 | ### Docker Deployment
1381 | 
1382 | ```bash
1383 | # Build and run container
1384 | docker build -t yargi-mcp .
1385 | docker run -p 8000:8000 --env-file .env yargi-mcp
1386 | 
1387 | # Or use docker-compose
1388 | docker-compose up
1389 | 
1390 | # Production with Nginx
1391 | docker-compose --profile production up
1392 | ```
1393 | 
1394 | ### Cloud Deployment Examples
1395 | 
1396 | #### Heroku
1397 | ```bash
1398 | # Deploy to Heroku (uses Procfile)
1399 | heroku create your-app-name
1400 | git push heroku main
1401 | ```
1402 | 
1403 | #### Railway
1404 | ```bash
1405 | # Deploy to Railway (uses railway.json)
1406 | railway login
1407 | railway link
1408 | railway up
1409 | ```
1410 | 
1411 | #### Google Cloud Run
1412 | ```bash
1413 | # Build and deploy container
1414 | gcloud run deploy yargi-mcp \
1415 |   --source . \
1416 |   --platform managed \
1417 |   --region us-central1
1418 | ```
1419 | 
1420 | ### Production Considerations
1421 | 
1422 | #### 1. Multiple Workers
1423 | ```bash
1424 | # Use multiple workers for production
1425 | python run_asgi.py --workers 4
1426 | 
1427 | # Or with gunicorn
1428 | gunicorn asgi_app:app -w 4 -k uvicorn.workers.UvicornWorker
1429 | ```
1430 | 
1431 | #### 2. Reverse Proxy
1432 | - Nginx configuration provided in `nginx.conf`
1433 | - Includes rate limiting and SSL termination
1434 | - Load balancing support
1435 | 
1436 | #### 3. Health Monitoring
1437 | ```bash
1438 | # Health check endpoint
1439 | curl http://localhost:8000/health
1440 | 
1441 | # Response format
1442 | {
1443 |   "status": "healthy",
1444 |   "timestamp": "2024-12-26T10:00:00",
1445 |   "uptime_seconds": 3600,
1446 |   "tools_operational": true
1447 | }
1448 | ```
1449 | 
1450 | #### 4. Security Features
1451 | - CORS configuration
1452 | - Rate limiting (via Nginx)
1453 | - Token authentication support
1454 | - SSL/HTTPS ready
1455 | 
1456 | ### API Usage Examples
1457 | 
1458 | #### Using the REST API
1459 | ```bash
1460 | # List all tools
1461 | curl http://localhost:8000/api/tools
1462 | 
1463 | # Get tool details
1464 | curl http://localhost:8000/api/tools/search_yargitay_detailed
1465 | 
1466 | # Get database information
1467 | curl http://localhost:8000/api/databases
1468 | 
1469 | # Server statistics
1470 | curl http://localhost:8000/api/stats
1471 | ```
1472 | 
1473 | #### Using MCP over HTTP
1474 | ```bash
1475 | # Standard MCP request
1476 | curl -X POST http://localhost:8000/mcp/ \
1477 |   -H "Content-Type: application/json" \
1478 |   -H "Accept: application/json, text/event-stream" \
1479 |   -H "X-Session-ID: test-123" \
1480 |   -d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'
1481 | 
1482 | # With Bearer JWT token authentication
1483 | curl -X POST http://localhost:8000/mcp/ \
1484 |   -H "Content-Type: application/json" \
1485 |   -H "Accept: application/json, text/event-stream" \
1486 |   -H "Authorization: Bearer YOUR_JWT_TOKEN" \
1487 |   -H "X-Session-ID: test-123" \
1488 |   -d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'
1489 | ```
1490 | 
1491 | #### Using MCP over SSE
1492 | ```bash
1493 | # Server-Sent Events transport
1494 | curl -X POST http://localhost:8000/sse/ \
1495 |   -H "Content-Type: application/json" \
1496 |   -H "Accept: text/event-stream" \
1497 |   -H "Authorization: Bearer YOUR_JWT_TOKEN" \
1498 |   -H "X-Session-ID: sse-test-123" \
1499 |   -d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'
1500 | ```
1501 | 
1502 | ### Integration Examples
1503 | 
1504 | #### Web Application Integration
1505 | ```javascript
1506 | // JavaScript client example
1507 | const response = await fetch('/api/tools');
1508 | const tools = await response.json();
1509 | 
1510 | // Use specific tool
1511 | const searchResult = await fetch('/mcp-server/mcp/', {
1512 |   method: 'POST',
1513 |   headers: { 'Content-Type': 'application/json' },
1514 |   body: JSON.stringify({
1515 |     method: 'tools/call',
1516 |     params: {
1517 |       name: 'search_yargitay_detailed',
1518 |       arguments: {
1519 |         arananKelime: 'mülkiyet hakkı',
1520 |         pageSize: 10
1521 |       }
1522 |     }
1523 |   })
1524 | });
1525 | ```
1526 | 
1527 | #### Authentication Example
1528 | ```bash
1529 | # With API token
1530 | export API_TOKEN=your-secret-token
1531 | 
1532 | curl -H "Authorization: Bearer $API_TOKEN" \
1533 |   http://localhost:8000/api/tools
1534 | ```
1535 | 
1536 | ### Deployment Documentation
1537 | 
1538 | For comprehensive deployment instructions, see:
1539 | - **[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)** - Complete deployment guide (Turkish)
1540 | - Covers local development, production, cloud, Docker, security, monitoring
1541 | 
1542 | This ASGI support transforms the Yargı MCP server into a versatile web service while maintaining full compatibility with the MCP protocol.
1543 | 
1544 | ## Production Deployment (Fly.io) - Domain Migration Complete
1545 | 
1546 | ### Current Production Status ✅
1547 | 
1548 | **Production URL**: `https://api.yargimcp.com` (migrated from yargi-mcp.fly.dev)
1549 | **Status**: ✅ **FULLY OPERATIONAL** - All systems working perfectly
1550 | **Tools Available**: ✅ **21 Turkish Legal Database Tools** - Successfully integrated with Claude AI
1551 | **Authentication**: ✅ **OAuth 2.0 + Bearer JWT** - Cross-origin authentication working
1552 | **Last Updated**: 2025-01-21 - All critical issues resolved
1553 | 
1554 | **Free Deployment**: `https://yargi-mcp-free.fly.dev` - No authentication required
1555 | **Status**: ✅ **OPERATIONAL** - Authorization disabled for open access
1556 | **Use Case**: Development, testing, and open-source usage without OAuth setup
1557 | 
1558 | #### Live Production Endpoints
1559 | 
1560 | **Authenticated Deployment (api.yargimcp.com)**:
1561 | - **Health Check**: https://api.yargimcp.com/health
1562 | - **OAuth Login**: https://api.yargimcp.com/auth/login  
1563 | - **MCP Endpoint (HTTP)**: https://api.yargimcp.com/mcp/
1564 | - **MCP Endpoint (SSE)**: https://api.yargimcp.com/sse/
1565 | - **OAuth Discovery**: https://api.yargimcp.com/.well-known/oauth-authorization-server
1566 | 
1567 | **Free Deployment (yargi-mcp-free.fly.dev)**:
1568 | - **Health Check**: https://yargi-mcp-free.fly.dev/health
1569 | - **MCP Endpoint (HTTP)**: https://yargi-mcp-free.fly.dev/mcp/
1570 | - **MCP Endpoint (SSE)**: https://yargi-mcp-free.fly.dev/sse/
1571 | - **Direct Access**: No authentication required - immediate usage
1572 | 
1573 | ### Redis Configuration (Fly.io Native Upstash) ✅
1574 | 
1575 | The server uses Fly.io's native Upstash Redis integration for OAuth session storage:
1576 | 
1577 | #### Redis Setup
1578 | ```bash
1579 | # Check Redis status
1580 | fly redis status yargi-redis
1581 | 
1582 | # Get Redis connection info
1583 | fly redis status yargi-redis | grep "Private URL"
1584 | ```
1585 | 
1586 | #### Current Redis Configuration
1587 | - **Type**: Fly.io native Upstash Redis
1588 | - **Plan**: Pay-as-you-go with eviction enabled
1589 | - **Region**: fra (Frankfurt)
1590 | - **REST API URL**: `http://fly-yargi-redis.upstash.io:6379`
1591 | - **Connection**: Uses Upstash REST API (HTTP) instead of traditional Redis protocol
1592 | 
1593 | #### Redis Environment Variables
1594 | ```bash
1595 | UPSTASH_REDIS_REST_URL=http://fly-yargi-redis.upstash.io:6379
1596 | UPSTASH_REDIS_REST_TOKEN=<your-token>
1597 | ```
1598 | 
1599 | **Note**: Always use `http://` (not `https://`) for Fly.io's internal Upstash Redis REST API.
1600 | 
1601 | ### Domain Migration Architecture
1602 | 
1603 | **Cross-Domain Authentication System**:
1604 | - **Frontend**: `yargimcp.com` (sign-in interface)
1605 | - **Backend API**: `api.yargimcp.com` (MCP server)
1606 | - **Authentication Flow**: Cross-origin JWT token authentication
1607 | 
1608 | #### Authentication Resolution ✅
1609 | 
1610 | **Problem Solved**: Cross-domain cookie sharing between `yargimcp.com` and `api.yargimcp.com`
1611 | 
1612 | **Solution Implemented**: Hybrid Authentication System
1613 | 1. **JWT Token Authentication** (Primary - Cross-Origin)
1614 |    - Frontend generates JWT token after Clerk authentication
1615 |    - Token passed to backend via URL parameter
1616 |    - Backend validates token with Clerk SDK
1617 | 
1618 | 2. **Cookie Fallback** (Same-Origin compatibility)
1619 |    - Automatic fallback for same-domain requests
1620 |    - Maintains compatibility with existing flows
1621 | 
1622 | 3. **Trusted Redirect** (Last resort)
1623 |    - Final fallback for edge cases
1624 |    - Ensures authentication flow completion
1625 | 
1626 | #### MCP Integration Fix ✅
1627 | 
1628 | **Issue Resolved**: Claude AI MCP connection dropping after authentication
1629 | 
1630 | **Root Cause**: FastAPI route conflicts between custom handlers and MCP Auth Toolkit
1631 | 
1632 | **Solution Applied**: 
1633 | - Removed interfering custom POST handler for `/mcp`
1634 | - Proper request forwarding to mounted MCP application
1635 | - Let MCP Auth Toolkit handle authentication internally
1636 | 
1637 | ### Current Deployment Configuration
1638 | 
1639 | #### Production Environment Variables
1640 | ```bash
1641 | # Domain configuration (updated)
1642 | BASE_URL=https://api.yargimcp.com
1643 | CLERK_OAUTH_REDIRECT_URL=https://api.yargimcp.com/auth/callback
1644 | 
1645 | # Cross-domain authentication  
1646 | CLERK_SECRET_KEY=sk_live_production_key
1647 | CLERK_PUBLISHABLE_KEY=pk_live_production_key
1648 | CLERK_ISSUER=https://accounts.yargimcp.com
1649 | 
1650 | # Authentication status
1651 | ENABLE_AUTH=true
1652 | ```
1653 | 
1654 | #### MCP Connection Details for Claude AI
1655 | 
1656 | **Authenticated Production (api.yargimcp.com)**:
1657 | ```
1658 | MCP Server URL (HTTP): https://api.yargimcp.com/mcp/
1659 | MCP Server URL (SSE): https://api.yargimcp.com/sse/
1660 | OAuth Authorization: https://api.yargimcp.com/authorize
1661 | Token Exchange: https://api.yargimcp.com/token
1662 | Authentication: OAuth 2.0 with PKCE + JWT tokens + Bearer JWT (optional)
1663 | Transports: HTTP (Streamable) + SSE (Server-Sent Events)
1664 | ```
1665 | 
1666 | **Free Open Access (yargi-mcp-free.fly.dev)**:
1667 | ```
1668 | MCP Server URL (HTTP): https://yargi-mcp-free.fly.dev/mcp/
1669 | MCP Server URL (SSE): https://yargi-mcp-free.fly.dev/sse/
1670 | Authentication: None - Direct access
1671 | Transports: HTTP (Streamable) + SSE (Server-Sent Events)
1672 | Use Case: Development, testing, immediate usage without OAuth setup
1673 | ```
1674 | 
1675 | #### SSE Transport Implementation ✅
1676 | 
1677 | The server now supports **Server-Sent Events (SSE)** transport alongside HTTP:
1678 | 
1679 | **SSE Endpoint**: `https://api.yargimcp.com/sse/`
1680 | 
1681 | **Key Features**:
1682 | - Same MCP JSON-RPC protocol as HTTP endpoint
1683 | - Proper SSE headers for streaming compatibility
1684 | - Identical authentication (Clerk JWT Bearer tokens)
1685 | - Real-time streaming support for compatible clients
1686 | 
1687 | **SSE Headers Added**:
1688 | - `Content-Type: text/event-stream`
1689 | - `Cache-Control: no-cache`
1690 | - `Connection: keep-alive`
1691 | - `Access-Control-Allow-Origin: *`
1692 | 
1693 | **Usage Example**:
1694 | ```bash
1695 | # SSE Transport with Clerk JWT
1696 | curl -X POST https://api.yargimcp.com/sse/ \
1697 |   -H "Content-Type: application/json" \
1698 |   -H "Accept: application/json, text/event-stream" \
1699 |   -H "Authorization: Bearer YOUR_CLERK_JWT_TOKEN" \
1700 |   -H "X-Session-ID: sse-client-123" \
1701 |   -d '{"jsonrpc": "2.0", "method": "tools/list", "id": 1}'
1702 | ```
1703 | 
1704 | **Implementation**: 
1705 | - Reuses the same underlying MCP app for consistency
1706 | - Adds SSE headers without duplicating authentication logic
1707 | - Uses Clerk's `authenticate_request` for token validation
1708 | - Maintains full compatibility with existing HTTP transport
1709 | 
1710 | ### OAuth Authentication Issue Resolution ✅
1711 | 
1712 | **Issue Fixed**: Multi-machine load balancer causing OAuth token exchange failures
1713 | 
1714 | #### Problem Description
1715 | - **Root Cause**: In-memory authorization code storage with multi-machine deployment
1716 | - **Symptom**: Authorization codes stored on Machine A, token exchange requests load-balanced to Machine B
1717 | - **Error**: `"No stored data found for authorization code"` during token exchange
1718 | - **Impact**: OAuth flow would fail, preventing Claude AI from authenticating
1719 | 
1720 | #### Solution Applied
1721 | **Single Machine Deployment**: Scale down to single machine to ensure all OAuth operations happen on the same instance
1722 | 
1723 | ```bash
1724 | # Fix load balancer issue - scale to single machine
1725 | fly scale count 1 --app yargi-mcp --yes
1726 | 
1727 | # Verify single machine deployment
1728 | fly status --app yargi-mcp
1729 | ```
1730 | 
1731 | #### Results ✅
1732 | - OAuth flow now working end-to-end
1733 | - Real Clerk JWT tokens being generated and accepted  
1734 | - Claude AI successfully authenticating and accessing MCP tools
1735 | - Authentication is now completely mandatory as intended
1736 | - In-memory authorization code storage works correctly on single machine
1737 | 
1738 | #### Production Consideration
1739 | For high-availability production environments, consider:
1740 | - External storage for authorization codes (Redis, Database)
1741 | - Session affinity/sticky sessions for OAuth flows
1742 | - Stateless authentication using only JWT tokens
1743 | 
1744 | ### Quick Deploy to Fly.io (Updated)
1745 | 
1746 | #### Prerequisites
1747 | ```bash
1748 | # Install Fly.io CLI
1749 | brew install flyctl          # macOS
1750 | # or visit: https://fly.io/docs/flyctl/install/
1751 | 
1752 | # Login to Fly.io
1753 | fly auth login
1754 | ```
1755 | 
1756 | #### One-Command Deploy
1757 | ```bash
1758 | # Set required environment variables (updated domains)
1759 | export CLERK_SECRET_KEY="sk_live_your_production_key"
1760 | export CLERK_PUBLISHABLE_KEY="pk_live_your_production_key"
1761 | export CLERK_ISSUER="https://accounts.yargimcp.com"
1762 | 
1763 | # Deploy using automated script
1764 | ./scripts/deploy-flyio.sh
1765 | ```
1766 | 
1767 | #### Manual Deploy Steps
1768 | ```bash
1769 | # 1. App already exists: yargi-mcp
1770 | fly apps list | grep yargi-mcp
1771 | 
1772 | # 2. Set production secrets (updated URLs)
1773 | fly secrets set \
1774 |   CLERK_SECRET_KEY="sk_live_your_key" \
1775 |   CLERK_PUBLISHABLE_KEY="pk_live_your_key" \
1776 |   CLERK_OAUTH_REDIRECT_URL="https://api.yargimcp.com/auth/callback" \
1777 |   CLERK_ISSUER="https://accounts.yargimcp.com" \
1778 |   BASE_URL="https://api.yargimcp.com"
1779 | 
1780 | # 3. Deploy
1781 | fly deploy
1782 | 
1783 | # 4. Check status
1784 | fly status
1785 | curl https://api.yargimcp.com/health
1786 | ```
1787 | 
1788 | ### Clerk Production Setup (Updated)
1789 | 
1790 | #### 1. Clerk Dashboard Configuration ✅
1791 | 1. Go to https://dashboard.clerk.com/
1792 | 2. **API Keys**: Production keys configured (sk_live_... and pk_live_...)
1793 | 3. **Social Connections**: Google OAuth provider enabled
1794 | 4. **Domains**: `api.yargimcp.com` and `yargimcp.com` configured
1795 | 
1796 | #### 2. Google OAuth Setup ✅
1797 | 1. **Google Cloud Console**: https://console.cloud.google.com/apis/credentials
1798 | 2. **OAuth Client ID configured**:
1799 |    - Application type: Web application
1800 |    - Authorized redirect URIs:
1801 |      ```
1802 |      https://accounts.yargimcp.com/oauth/callback
1803 |      https://api.yargimcp.com/auth/callback
1804 |      ```
1805 | 3. **Client ID and Secret**: Configured in Clerk dashboard
1806 | 
1807 | #### 3. Current Production URLs ✅
1808 | - **Health Check**: https://api.yargimcp.com/health ✅ Operational
1809 | - **OAuth Login**: https://api.yargimcp.com/auth/login ✅ Working
1810 | - **User Info**: https://api.yargimcp.com/auth/user ✅ Protected
1811 | - **MCP Endpoint (HTTP)**: https://api.yargimcp.com/mcp/ ✅ Claude AI Compatible
1812 | - **MCP Endpoint (SSE)**: https://api.yargimcp.com/sse/ ✅ Server-Sent Events Transport
1813 | 
1814 | ### Complete Deployment Guide
1815 | 
1816 | For detailed step-by-step instructions, see **[docs/DEPLOYMENT_FLYIO.md](docs/DEPLOYMENT_FLYIO.md)** which covers:
1817 | - Clerk OAuth configuration
1818 | - Google Cloud Console setup
1819 | - Fly.io deployment process
1820 | - Production testing procedures
1821 | - Troubleshooting guide
1822 | - Security best practices
1823 | 
1824 | ### Production OAuth Testing
1825 | 
1826 | #### Test OAuth Flow
1827 | ```bash
1828 | # 1. Start OAuth flow in browser
1829 | open https://yargi-mcp.fly.dev/auth/login
1830 | 
1831 | # 2. Complete Google OAuth flow
1832 | 
1833 | # 3. Test authenticated endpoints
1834 | curl -H "Authorization: Bearer YOUR_TOKEN" \
1835 |      https://yargi-mcp.fly.dev/auth/user
1836 | 
1837 | # 4. Test MCP with authentication
1838 | curl -X POST \
1839 |   -H "Content-Type: application/json" \
1840 |   -H "Accept: application/json, text/event-stream" \
1841 |   -H "Authorization: Bearer YOUR_TOKEN" \
1842 |   -H "X-Session-ID: prod-session-123" \
1843 |   -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}' \
1844 |   https://yargi-mcp.fly.dev/mcp/
1845 | ```
1846 | 
1847 | #### Management Commands
1848 | ```bash
1849 | # View logs
1850 | fly logs --app yargi-mcp
1851 | 
1852 | # Scale up/down
1853 | fly scale count 2
1854 | 
1855 | # Restart app
1856 | fly apps restart yargi-mcp
1857 | 
1858 | # SSH into container
1859 | fly ssh console --app yargi-mcp
1860 | ```
1861 | 
1862 | ## ChatGPT Deep Research Integration
1863 | 
1864 | The MCP server now includes specialized tools for ChatGPT Deep Research compatibility:
1865 | 
1866 | ### Deep Research Tools
1867 | 
1868 | **search** - Universal search across all Turkish legal databases
1869 | - **Purpose**: Returns structured search results for Deep Research compatibility
1870 | - **Coverage**: All 9 supported Turkish legal databases in a single query
1871 | - **Output Format**: Array of objects with `id`, `title`, `text`, `url` fields as required by ChatGPT Deep Research specification
1872 | - **Databases Searched**:
1873 |   - Yargıtay (Court of Cassation) - Primary and Bedesten APIs
1874 |   - Danıştay (Council of State) - All 3 APIs
1875 |   - Anayasa Mahkemesi (Constitutional Court) - Norm control decisions
1876 |   - Rekabet Kurumu (Competition Authority) - Antitrust decisions
1877 |   - KİK (Public Procurement Authority) - Procurement disputes
1878 |   - Local Courts (Yerel Hukuk, İstinaf Hukuk) - Via Bedesten API
1879 |   - Kanun Yararına Bozma (KYB) - Extraordinary appeals
1880 | - **Usage**: `search(query="mülkiyet hakkı")` - Single query searches all databases
1881 | 
1882 | **fetch** - Document retrieval by ID
1883 | - **Purpose**: Retrieves complete legal document text for Deep Research analysis
1884 | - **Input**: Document identifier from search results (format: `database_documentid`)
1885 | - **Output Format**: Single object with `id`, `title`, `text`, `url`, `metadata` fields
1886 | - **Supported Databases**: All databases with automatic routing based on ID prefix
1887 | - **Usage**: `fetch(id="yargitay_12345")` - Returns full document content
1888 | 
1889 | ### ChatGPT Integration Setup
1890 | 
1891 | 1. **Add MCP Server in ChatGPT**:
1892 |    - Go to ChatGPT Settings → Connectors
1893 |    - Add new MCP server: `https://yargi-mcp.fly.dev/mcp`
1894 |    - Complete OAuth flow via Clerk
1895 |    - Server will appear in Deep Research sources
1896 | 
1897 | 2. **OAuth Authentication**:
1898 |    - Uses Clerk OAuth 2.0 with Google provider
1899 |    - Mock OAuth flow for test environment compatibility
1900 |    - Development tokens supported for testing
1901 | 
1902 | 3. **Deep Research Usage**:
1903 |    - Enable Deep Research mode in ChatGPT
1904 |    - Select "Yargı MCP Server" as data source
1905 |    - Ask legal research questions in Turkish or English
1906 |    - ChatGPT will automatically use `search` and `fetch` tools
1907 | 
1908 | ### Example Deep Research Queries
1909 | 
1910 | ```
1911 | "Türk hukukunda mülkiyet hakkının sınırları nelerdir?"
1912 | "What are the recent Constitutional Court decisions on freedom of expression?"
1913 | "Rekabet hukukunda hakim durumun kötüye kullanılması nasıl değerlendiriliyor?"
1914 | "Public procurement tender cancellation procedures in Turkish law"
1915 | ```
1916 | 
1917 | ### Tool Response Format
1918 | 
1919 | **Search Results Example**:
1920 | ```json
1921 | [
1922 |   {
1923 |     "id": "yargitay_12345",
1924 |     "title": "Yargıtay 1. Hukuk Dairesi - E.2024/123 K.2024/456",
1925 |     "text": "Supreme Court decision on property rights...",
1926 |     "url": "https://yargi-mcp.fly.dev/documents/yargitay/12345"
1927 |   }
1928 | ]
1929 | ```
1930 | 
1931 | **Fetch Results Example**:
1932 | ```json
1933 | {
1934 |   "id": "yargitay_12345",
1935 |   "title": "Yargıtay Supreme Court Decision - Document 12345",
1936 |   "text": "# Yargıtay 1. Hukuk Dairesi\n\nEsas No: 2024/123...",
1937 |   "url": "https://yargi-mcp.fly.dev/documents/yargitay/12345",
1938 |   "metadata": {
1939 |     "database": "Yargıtay (Court of Cassation)",
1940 |     "court_level": "Supreme Court",
1941 |     "jurisdiction": "Civil and Criminal Law",
1942 |     "document_id": "12345"
1943 |   }
1944 | }
1945 | ```
1946 | 
1947 | ### Testing Deep Research Tools
1948 | 
1949 | ```bash
1950 | # Test universal search tool
1951 | curl -X POST https://yargi-mcp.fly.dev/mcp/ \
1952 |   -H "Content-Type: application/json" \
1953 |   -H "Authorization: Bearer YOUR_TOKEN" \
1954 |   -H "X-Session-ID: deep-research-test" \
1955 |   -d '{
1956 |     "jsonrpc": "2.0",
1957 |     "id": 1,
1958 |     "method": "tools/call",
1959 |     "params": {
1960 |       "name": "search",
1961 |       "arguments": {
1962 |         "query": "mülkiyet hakkı"
1963 |       }
1964 |     }
1965 |   }'
1966 | 
1967 | # Test fetch tool with result ID
1968 | curl -X POST https://yargi-mcp.fly.dev/mcp/ \
1969 |   -H "Content-Type: application/json" \
1970 |   -H "Authorization: Bearer YOUR_TOKEN" \
1971 |   -H "X-Session-ID: deep-research-test" \
1972 |   -d '{
1973 |     "jsonrpc": "2.0", 
1974 |     "id": 2,
1975 |     "method": "tools/call",
1976 |     "params": {
1977 |       "name": "fetch",
1978 |       "arguments": {
1979 |         "id": "yargitay_12345"
1980 |       }
1981 |     }
1982 |   }'
1983 | ```
1984 | 
1985 | ## FastMCP Client Testing Results
1986 | 
1987 | 
1988 | ## PyPI Package Publishing
1989 | 
1990 | ### Overview
1991 | The project is published on PyPI as `yargi-mcp` for easy installation without Git dependencies.
1992 | 
1993 | **PyPI Package**: https://pypi.org/project/yargi-mcp/
1994 | 
1995 | ### User Installation
1996 | ```bash
1997 | # Install from PyPI (Recommended - no Git required)
1998 | pip install yargi-mcp
1999 | 
2000 | # Or use uvx for isolated execution
2001 | uvx yargi-mcp
2002 | 
2003 | # Claude Desktop/5ire configuration
2004 | {
2005 |   "mcpServers": {
2006 |     "yargi-mcp": {
2007 |       "command": "yargi-mcp"
2008 |     }
2009 |   }
2010 | }
2011 | ```
2012 | 
2013 | ### Automated Publishing Workflow
2014 | - **GitHub Actions**: `.github/workflows/publish.yml`
2015 | - **Trigger**: Release creation on GitHub
2016 | - **Process**: Build → Test → Publish to PyPI
2017 | - **Requirements**: `PYPI_API_TOKEN` secret in GitHub
2018 | 
2019 | ### Publishing Process
2020 | 1. **Update Version**: Bump version in `pyproject.toml`
2021 | 2. **Commit & Push**: Git commit and push changes
2022 | 3. **Create Release**: Use GitHub CLI or web interface
2023 |    ```bash
2024 |    # Via GitHub CLI
2025 |    gh release create v0.1.3 --title "v0.1.3" --notes ""
2026 |    
2027 |    # Via web: https://github.com/saidsurucu/yargi-mcp/releases/new
2028 |    ```
2029 | 4. **Automatic Publishing**: GitHub Actions publishes to PyPI
2030 | 
2031 | ### PyPI Configuration (pyproject.toml)
2032 | ```toml
2033 | [project]
2034 | name = "yargi-mcp"
2035 | version = "0.1.2"  # Update for each release
2036 | description = "MCP Server For Turkish Legal Databases"
2037 | license = {text = "MIT"}
2038 | authors = [{name = "Said Surucu", email = "[email protected]"}]
2039 | keywords = ["mcp", "turkish-law", "legal", "yargitay", "danistay"]
2040 | classifiers = [
2041 |     "Development Status :: 4 - Beta",
2042 |     "Intended Audience :: Legal Industry",
2043 |     "License :: OSI Approved :: MIT License",
2044 |     "Programming Language :: Python :: 3.11",
2045 |     "Programming Language :: Python :: 3.12",
2046 | ]
2047 | 
2048 | [build-system]
2049 | requires = ["setuptools>=65.0", "wheel"]
2050 | build-backend = "setuptools.build_meta"
2051 | ```
2052 | 
2053 | ## Summary
2054 | 
2055 | ### Current Tool Architecture (Updated)
2056 | 
2057 | **Total Tools**: 21 MCP tools across 9 legal institutions (Production Verified ✅)
2058 | 
2059 | **Legal Database Coverage**:
2060 | 1. **Yargıtay**: ❌ ~~2 tools~~ → Use Bedesten unified instead (DEACTIVATED)
2061 | 2. **Danıştay**: ❌ ~~3 tools~~ → Use Bedesten unified instead (DEACTIVATED)  
2062 | 3. **Bedesten Unified**: 2 tools (unified search + document retrieval) - **COVERS YARGITAY & DANISTAY**
2063 | 4. **Emsal**: 2 tools (search + document)
2064 | 5. **Uyuşmazlık**: 2 tools (search + document)
2065 | 6. **Constitutional Court**: ✅ 2 tools (unified norm control + individual applications) - **NEWLY UNIFIED**
2066 | 7. **KİK**: 2 tools (search + document) - **v2 API with three decision types: uyusmazlik, duzenleyici, mahkeme** ✅
2067 | 8. **Competition Authority**: 2 tools (search + document)
2068 | 9. **KVKK**: 2 tools (search + document)
2069 | 10. **Sayıştay**: 4 tools (3 search types + document)
2070 | 
2071 | ### Recent Updates
2072 | 
2073 | #### ✅ Bedesten API Unification (Completed)
2074 | - **Before**: 10 separate tools for different court types
2075 | - **After**: 2 unified tools supporting all court types
2076 | - **Benefits**: Simplified interface, multi-court search, better UX
2077 | - **Court Types**: YARGITAYKARARI, DANISTAYKARAR, YERELHUKUK, ISTINAFHUKUK, KYB
2078 | 
2079 | #### ✅ Constitutional Court Unification (Phase 6 - Completed)
2080 | - **Before**: 4 separate tools (2 norm control + 2 individual applications)
2081 | - **After**: 2 unified tools with decision type parameter
2082 | - **Benefits**: Single interface, auto-detection, simplified usage
2083 | - **Tools**: search_anayasa_unified + get_anayasa_document_unified
2084 | 
2085 | #### 🔄 Sayıştay Module (Available, Active)
2086 | - **Module**: `sayistay_mcp_module/` - Complete implementation  
2087 | - **Status**: 4 tools active and operational
2088 | - **Coverage**: General Assembly, Appeals Board, Chamber decisions
2089 | - **Tools**: search_sayistay_genel_kurul, search_sayistay_temyiz_kurulu, search_sayistay_daire, get_sayistay_document_markdown
2090 | 
2091 | #### ✅ Production Deployment & Claude AI Integration (Completed - Jan 21, 2025)
2092 | - **Status**: All systems fully operational on Fly.io production
2093 | - **Authentication**: OAuth 2.0 + Bearer JWT token authentication working
2094 | - **Tools Integration**: All 21 tools successfully integrated with Claude AI
2095 | - **Cross-Origin Auth**: Resolved subdomain authentication challenges
2096 | - **Issues Resolved**: 
2097 |   - ✅ Bearer token scope/audience validation
2098 |   - ✅ MCP tools initialization sequence
2099 |   - ✅ FastMCP app auth provider integration
2100 |   - ✅ 308 redirect for /mcp endpoint
2101 |   - ✅ Session management and tool discovery
2102 | - **Claude AI**: Successfully connects and uses all Turkish legal database tools
2103 | 
2104 | #### ✅ Bedesten Tools Null Safety Fixes (Completed - Jul 23, 2025)
2105 | - **Issue**: TypeError "cannot convert undefined or null to object" in Bedesten search and document tools
2106 | - **Root Cause**: API responses containing null/undefined fields without proper validation
2107 | - **Fixes Applied**:
2108 |   - ✅ **Search Function**: Added null safety checks for `response.data.emsalKararList` and `response.data.total`
2109 |   - ✅ **Document Function**: Added comprehensive validation for `doc_response.data`, `content`, and `mimeType` fields
2110 |   - ✅ **Error Handling**: Added descriptive error messages and graceful fallbacks
2111 |   - ✅ **Base64 Decoding**: Protected base64 operations with try-catch blocks
2112 | - **Result**: Bedesten tools now handle API edge cases gracefully without crashing
2113 | - **Production Status**: Deployed and operational on api.yargimcp.com
2114 | 
2115 | #### ✅ Automatic Date Format Conversion (Completed - Sep 2, 2025)
2116 | - **Issue**: Bedesten API requires ISO 8601 format with timezone, but users were providing simple dates
2117 | - **Problem**: Queries like `kararTarihiStart="2020-01-01"` returned "No data returned from Bedesten API"
2118 | - **Root Cause**: Simple date format `YYYY-MM-DD` not converted to required `YYYY-MM-DDTHH:MM:SS.000Z` format
2119 | - **Solution Applied**: ✅ **Automatic Date Format Conversion** in `search_bedesten_unified`
2120 |   - **Start dates**: `2020-01-01` → `2020-01-01T00:00:00.000Z` (beginning of day)
2121 |   - **End dates**: `2020-01-01` → `2020-01-01T23:59:59.999Z` (end of day)
2122 |   - **Backwards compatible**: Full ISO 8601 dates still work unchanged
2123 |   - **Smart detection**: Only converts if date doesn't already end with 'Z'
2124 | - **Benefits**: 
2125 |   - ✅ **User-friendly**: Simple date input now works seamlessly
2126 |   - ✅ **Inclusive ranges**: End dates include the entire specified day
2127 |   - ✅ **No breaking changes**: Existing ISO 8601 usage unaffected
2128 | - **Production Status**: Deployed on api.yargimcp.com - **Version 353**
2129 | 
2130 | #### ✅ KİK v2 MCP Implementation Testing (Completed - Sep 2, 2025)
2131 | - **Issue**: Test KİK v2 MCP implementation with all three decision types
2132 | - **Request**: "üç karar türü ile de mcpyi test et" (test the MCP with all three decision types)  
2133 | - **Decision Types Tested**:
2134 |   - ✅ **uyusmazlik** (dispute) - 500 decisions found and searchable
2135 |   - ✅ **duzenleyici** (regulatory) - 8 decisions found and searchable  
2136 |   - ✅ **mahkeme** (court) - 318 decisions found and searchable
2137 | - **Total Coverage**: 826 decisions across all three decision types
2138 | - **SSL Issues**: ✅ Resolved with legacy server connect configuration  
2139 | - **API Endpoints**: All three endpoints working correctly
2140 |   - `/api/KurulKararlari/GetKurulKararlari` (uyusmazlik)
2141 |   - `/api/KurulKararlari/GetKurulKararlariDk` (duzenleyici)  
2142 |   - `/api/KurulKararlari/GetKurulKararlariMk` (mahkeme)
2143 | - **Hash Analysis**: Comprehensive testing performed to understand document ID encryption
2144 |   - Tested various hash generation patterns (SHA256, HMAC, composite hashes)
2145 |   - Angular/cryptoService.encrypt() style approaches tested
2146 |   - Hash eşleşmesi bulunamadı - client-side session data veya farklı algoritma kullanılıyor olabilir
2147 | - **Result**: ✅ KİK v2 MCP implementation fully operational for all three decision types
2148 | - **Production Status**: All tests passing, search functionality working, ready for production deployment
2149 | 
2150 | ### Key Features
2151 | - **FastMCP Framework**: Modern MCP server implementation
2152 | - **Unified APIs**: Single interface for multiple court systems
2153 | - **Multi-format Support**: HTML and PDF document conversion
2154 | - **Advanced Search**: Chamber filtering, date ranges, exact phrases
2155 | - **Production Ready**: OAuth authentication, cloud deployment, monitoring
2156 | 
```

--------------------------------------------------------------------------------
/anayasa_mcp_module/__init__.py:
--------------------------------------------------------------------------------

```python
1 | 
```

--------------------------------------------------------------------------------
/danistay_mcp_module/__init__.py:
--------------------------------------------------------------------------------

```python
1 | 
```

--------------------------------------------------------------------------------
/emsal_mcp_module/__init__.py:
--------------------------------------------------------------------------------

```python
1 | 
```

--------------------------------------------------------------------------------
/kik_mcp_module/__init__.py:
--------------------------------------------------------------------------------

```python
1 | 
```

--------------------------------------------------------------------------------
/rekabet_mcp_module/__init__.py:
--------------------------------------------------------------------------------

```python
1 | 
```

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

```python
1 | 
```

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

```python
1 | 
```

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

```python
1 | 
```

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

```python
1 | 
```

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

```python
1 | 
```

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

```python
1 | 
```

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

```python
1 | 
```

--------------------------------------------------------------------------------
/uyusmazlik_mcp_module/__init__.py:
--------------------------------------------------------------------------------

```python
1 | 
```

--------------------------------------------------------------------------------
/yargitay_mcp_module/__init__.py:
--------------------------------------------------------------------------------

```python
1 | 
```

--------------------------------------------------------------------------------
/kvkk_mcp_module/__init__.py:
--------------------------------------------------------------------------------

```python
1 | # kvkk_mcp_module/__init__.py
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/kvkk_mcp_module/__init__.py:
--------------------------------------------------------------------------------

```python
1 | # kvkk_mcp_module/__init__.py
```

--------------------------------------------------------------------------------
/bedesten_mcp_module/__init__.py:
--------------------------------------------------------------------------------

```python
1 | # bedesten_mcp_module/__init__.py
```

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

```python
1 | # bedesten_mcp_module/__init__.py
```

--------------------------------------------------------------------------------
/__main__.py:
--------------------------------------------------------------------------------

```python
1 | #!/usr/bin/env python3
2 | """Entry point for yargi-mcp package."""
3 | 
4 | from mcp_server_main import main
5 | 
6 | if __name__ == "__main__":
7 |     main()
```

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

```python
1 | #!/usr/bin/env python3
2 | """Entry point for yargi-mcp package."""
3 | 
4 | from mcp_server_main import main
5 | 
6 | if __name__ == "__main__":
7 |     main()
```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
 1 | fastmcp
 2 | httpx
 3 | beautifulsoup4
 4 | markitdown[pdf]
 5 | pydantic
 6 | aiohttp
 7 | playwright
 8 | pypdf
 9 | fastapi>=0.115.14
10 | uvicorn[standard]>=0.30.0
11 | starlette>=0.37.0
```

--------------------------------------------------------------------------------
/bddk_mcp_module/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | # bddk_mcp_module/__init__.py
 2 | 
 3 | from .client import BddkApiClient
 4 | from .models import (
 5 |     BddkSearchRequest,
 6 |     BddkDecisionSummary,
 7 |     BddkSearchResult,
 8 |     BddkDocumentMarkdown
 9 | )
10 | 
11 | __all__ = [
12 |     "BddkApiClient",
13 |     "BddkSearchRequest",
14 |     "BddkDecisionSummary",
15 |     "BddkSearchResult",
16 |     "BddkDocumentMarkdown"
17 | ]
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/bddk_mcp_module/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | # bddk_mcp_module/__init__.py
 2 | 
 3 | from .client import BddkApiClient
 4 | from .models import (
 5 |     BddkSearchRequest,
 6 |     BddkDecisionSummary,
 7 |     BddkSearchResult,
 8 |     BddkDocumentMarkdown
 9 | )
10 | 
11 | __all__ = [
12 |     "BddkApiClient",
13 |     "BddkSearchRequest",
14 |     "BddkDecisionSummary",
15 |     "BddkSearchResult",
16 |     "BddkDocumentMarkdown"
17 | ]
```

--------------------------------------------------------------------------------
/railway.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "$schema": "https://railway.app/railway.schema.json",
 3 |   "build": {
 4 |     "builder": "NIXPACKS",
 5 |     "buildCommand": "pip install -e .[asgi]"
 6 |   },
 7 |   "deploy": {
 8 |     "startCommand": "uvicorn asgi_app:app --host 0.0.0.0 --port $PORT",
 9 |     "healthcheckPath": "/health",
10 |     "healthcheckTimeout": 30,
11 |     "restartPolicyType": "ON_FAILURE",
12 |     "restartPolicyMaxRetries": 3
13 |   },
14 |   "variables": {
15 |     "ALLOWED_ORIGINS": "*",
16 |     "LOG_LEVEL": "info"
17 |   }
18 | }
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/railway.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "$schema": "https://railway.app/railway.schema.json",
 3 |   "build": {
 4 |     "builder": "NIXPACKS",
 5 |     "buildCommand": "pip install -e .[asgi]"
 6 |   },
 7 |   "deploy": {
 8 |     "startCommand": "uvicorn asgi_app:app --host 0.0.0.0 --port $PORT",
 9 |     "healthcheckPath": "/health",
10 |     "healthcheckTimeout": 30,
11 |     "restartPolicyType": "ON_FAILURE",
12 |     "restartPolicyMaxRetries": 3
13 |   },
14 |   "variables": {
15 |     "ALLOWED_ORIGINS": "*",
16 |     "LOG_LEVEL": "info"
17 |   }
18 | }
```

--------------------------------------------------------------------------------
/mcp_auth/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | MCP Auth Toolkit - OAuth 2.1 + Authorization for Model Context Protocol Servers
 3 | Integrated with Clerk Authentication
 4 | """
 5 | 
 6 | from .middleware import (
 7 |     AuthContext,
 8 |     FastMCPAuthWrapper,
 9 |     MCPAuthMiddleware,
10 |     auth_required,
11 | )
12 | from .oauth import OAuthConfig, OAuthProvider
13 | from .policy import PolicyEngine, ToolPolicy, create_default_policies
14 | from .storage import PersistentStorage
15 | 
16 | __version__ = "0.1.0"
17 | __all__ = [
18 |     "OAuthProvider",
19 |     "OAuthConfig", 
20 |     "AuthContext",
21 |     "auth_required",
22 |     "create_default_policies",
23 |     "MCPAuthMiddleware",
24 |     "FastMCPAuthWrapper",
25 |     "PolicyEngine",
26 |     "ToolPolicy",
27 |     "PersistentStorage",
28 | ]
```

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

```python
 1 | """
 2 | MCP Auth Toolkit - OAuth 2.1 + Authorization for Model Context Protocol Servers
 3 | Integrated with Clerk Authentication
 4 | """
 5 | 
 6 | from .middleware import (
 7 |     AuthContext,
 8 |     FastMCPAuthWrapper,
 9 |     MCPAuthMiddleware,
10 |     auth_required,
11 | )
12 | from .oauth import OAuthConfig, OAuthProvider
13 | from .policy import PolicyEngine, ToolPolicy, create_default_policies
14 | from .storage import PersistentStorage
15 | 
16 | __version__ = "0.1.0"
17 | __all__ = [
18 |     "OAuthProvider",
19 |     "OAuthConfig", 
20 |     "AuthContext",
21 |     "auth_required",
22 |     "create_default_policies",
23 |     "MCPAuthMiddleware",
24 |     "FastMCPAuthWrapper",
25 |     "PolicyEngine",
26 |     "ToolPolicy",
27 |     "PersistentStorage",
28 | ]
```

--------------------------------------------------------------------------------
/check_response_format.py:
--------------------------------------------------------------------------------

```python
 1 | #!/usr/bin/env python3
 2 | from fastmcp import Client
 3 | from mcp_server_main import app
 4 | import json
 5 | import asyncio
 6 | 
 7 | async def check_response_format():
 8 |     client = Client(app)
 9 |     async with client:
10 |         result = await client.call_tool('search_bedesten_unified', {
11 |             'phrase': 'mülkiyet',
12 |             'court_types': ['YARGITAYKARARI'],
13 |             'birimAdi': 'H1',
14 |             'pageSize': 3
15 |         })
16 |         if result and result.content:
17 |             data = json.loads(result.content[0].text)
18 |             print('Response keys:', list(data.keys()))
19 |             print('Sample response:', json.dumps(data, indent=2, ensure_ascii=False)[:500])
20 | 
21 | asyncio.run(check_response_format())
```

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

```python
 1 | #!/usr/bin/env python3
 2 | from fastmcp import Client
 3 | from mcp_server_main import app
 4 | import json
 5 | import asyncio
 6 | 
 7 | async def check_response_format():
 8 |     client = Client(app)
 9 |     async with client:
10 |         result = await client.call_tool('search_bedesten_unified', {
11 |             'phrase': 'mülkiyet',
12 |             'court_types': ['YARGITAYKARARI'],
13 |             'birimAdi': 'H1',
14 |             'pageSize': 3
15 |         })
16 |         if result and result.content:
17 |             data = json.loads(result.content[0].text)
18 |             print('Response keys:', list(data.keys()))
19 |             print('Sample response:', json.dumps(data, indent=2, ensure_ascii=False)[:500])
20 | 
21 | asyncio.run(check_response_format())
```

--------------------------------------------------------------------------------
/fly.toml:
--------------------------------------------------------------------------------

```toml
 1 | # fly.toml app configuration file generated for yargi-mcp on 2025-06-29T00:23:47+03:00
 2 | #
 3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file.
 4 | #
 5 | 
 6 | app = 'yargi-mcp'
 7 | primary_region = 'fra'
 8 | 
 9 | [env]
10 | ENABLE_AUTH = "true"
11 | HOST = "0.0.0.0"
12 | PORT = "8000"
13 | LOG_LEVEL = "info"
14 | 
15 | [build]
16 | 
17 | [http_service]
18 |   internal_port = 8000
19 |   force_https = true
20 |   auto_stop_machines = 'off'
21 |   auto_start_machines = true
22 |   min_machines_running = 1
23 |   processes = ['app']
24 |   
25 |   # Enable connection persistence for MCP sessions
26 |   [http_service.concurrency]
27 |     type = "connections"
28 |     hard_limit = 100
29 |     soft_limit = 80
30 | 
31 | [[vm]]
32 |   memory = '1gb'
33 |   cpu_kind = 'shared'
34 |   cpus = 1
35 | 
36 | [checks.http_health]             # keep MCP /health live
37 |   type     = "http"
38 |   interval = "30s"
39 |   timeout  = "10s"
40 |   path     = "/health"
41 | 
```

--------------------------------------------------------------------------------
/fly-no-auth.toml:
--------------------------------------------------------------------------------

```toml
 1 | # fly.toml app configuration file for yargi-mcp-noauth
 2 | #
 3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file.
 4 | #
 5 | 
 6 | app = 'yargi-mcp-free'
 7 | primary_region = 'fra'
 8 | 
 9 | [env]
10 | ENABLE_AUTH = "false"
11 | HOST = "0.0.0.0"
12 | PORT = "8000"
13 | LOG_LEVEL = "info"
14 | 
15 | [build]
16 | 
17 | [http_service]
18 |   internal_port = 8000
19 |   force_https = true
20 |   auto_stop_machines = 'off'
21 |   auto_start_machines = true
22 |   min_machines_running = 1
23 |   processes = ['app']
24 |   
25 |   # Enable connection persistence for MCP sessions
26 |   [http_service.concurrency]
27 |     type = "connections"
28 |     hard_limit = 100
29 |     soft_limit = 80
30 | 
31 | [[vm]]
32 |   memory = '1gb'
33 |   cpu_kind = 'shared'
34 |   cpus = 1
35 | 
36 | [deploy]
37 |   strategy = "immediate"
38 | 
39 | [processes]
40 |   app = "python asgi_app.py"
41 | 
42 | [checks.http_health]             # keep MCP /health live
43 |   type     = "http"
44 |   interval = "30s"
45 |   timeout  = "10s"
46 |   path     = "/health"
```

--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Publish to PyPI
 2 | 
 3 | on:
 4 |   release:
 5 |     types: [published]
 6 |   workflow_dispatch:  # Manual trigger for testing
 7 | 
 8 | jobs:
 9 |   pypi-publish:
10 |     name: Upload release to PyPI
11 |     runs-on: ubuntu-latest
12 |     environment:
13 |       name: pypi
14 |       url: https://pypi.org/p/yargi-mcp
15 |     permissions:
16 |       id-token: write  # IMPORTANT: this permission is mandatory for trusted publishing
17 |     steps:
18 |     - uses: actions/checkout@v4
19 |     
20 |     - name: Set up Python
21 |       uses: actions/setup-python@v5
22 |       with:
23 |         python-version: '3.11'
24 |     
25 |     - name: Install dependencies
26 |       run: |
27 |         python -m pip install --upgrade pip
28 |         pip install build
29 |     
30 |     - name: Build package
31 |       run: python -m build
32 |     
33 |     - name: Publish package to PyPI
34 |       uses: pypa/gh-action-pypi-publish@release/v1
35 |       with:
36 |         password: ${{ secrets.PYPI_API_TOKEN }}
37 |         skip-existing: true
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/.github/workflows/publish.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Publish to PyPI
 2 | 
 3 | on:
 4 |   release:
 5 |     types: [published]
 6 |   workflow_dispatch:  # Manual trigger for testing
 7 | 
 8 | jobs:
 9 |   pypi-publish:
10 |     name: Upload release to PyPI
11 |     runs-on: ubuntu-latest
12 |     environment:
13 |       name: pypi
14 |       url: https://pypi.org/p/yargi-mcp
15 |     permissions:
16 |       id-token: write  # IMPORTANT: this permission is mandatory for trusted publishing
17 |     steps:
18 |     - uses: actions/checkout@v4
19 |     
20 |     - name: Set up Python
21 |       uses: actions/setup-python@v5
22 |       with:
23 |         python-version: '3.11'
24 |     
25 |     - name: Install dependencies
26 |       run: |
27 |         python -m pip install --upgrade pip
28 |         pip install build
29 |     
30 |     - name: Build package
31 |       run: python -m build
32 |     
33 |     - name: Publish package to PyPI
34 |       uses: pypa/gh-action-pypi-publish@release/v1
35 |       with:
36 |         password: ${{ secrets.PYPI_API_TOKEN }}
37 |         skip-existing: true
```

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

```python
 1 | import os, stripe
 2 | from clerk_backend_api import Clerk                         # Clerk backend SDK
 3 | from fastapi import APIRouter, Request, HTTPException
 4 | 
 5 | router = APIRouter()
 6 | stripe.api_key = os.getenv("STRIPE_SECRET")
 7 | clerk         = Clerk(bearer_auth=os.getenv("CLERK_SECRET_KEY"))
 8 | 
 9 | @router.post("/stripe/webhook")
10 | async def stripe_hook(req: Request):
11 |     payload, sig = await req.body(), req.headers["stripe-signature"]
12 |     try:
13 |         event = stripe.Webhook.construct_event(             # Stripe-recommended verify
14 |             payload, sig, os.getenv("STRIPE_WEBHOOK_SECRET"))
15 |     except stripe.error.SignatureVerificationError:
16 |         raise HTTPException(400, "Bad sig")
17 | 
18 |     if event["type"] == "customer.subscription.updated":
19 |         item   = event["data"]["object"]["items"]["data"][0]
20 |         plan   = item["price"]["nickname"]                  # "Pro", "Enterprise"…
21 |         userID = event["data"]["object"]["metadata"]["clerk_user_id"]
22 |         clerk.users.update_user_metadata(                   # merge into unsafe_metadata
23 |             userID, unsafe_metadata={"plan": plan})
24 |     return {"ok": True}
25 |     
26 | 
```

--------------------------------------------------------------------------------
/stripe_webhook.py:
--------------------------------------------------------------------------------

```python
 1 | import os, stripe
 2 | from clerk_backend_api import Clerk                         # Clerk backend SDK
 3 | from fastapi import APIRouter, Request, HTTPException
 4 | 
 5 | router = APIRouter()
 6 | stripe.api_key = os.getenv("STRIPE_SECRET")
 7 | clerk         = Clerk(bearer_auth=os.getenv("CLERK_SECRET_KEY"))
 8 | 
 9 | @router.post("/stripe/webhook")
10 | async def stripe_hook(req: Request):
11 |     payload, sig = await req.body(), req.headers["stripe-signature"]
12 |     try:
13 |         event = stripe.Webhook.construct_event(             # Stripe-recommended verify
14 |             payload, sig, os.getenv("STRIPE_WEBHOOK_SECRET"))
15 |     except stripe.error.SignatureVerificationError:
16 |         raise HTTPException(400, "Bad sig")
17 | 
18 |     if event["type"] == "customer.subscription.updated":
19 |         item   = event["data"]["object"]["items"]["data"][0]
20 |         plan   = item["price"]["nickname"]                  # "Pro", "Enterprise"…
21 |         userID = event["data"]["object"]["metadata"]["clerk_user_id"]
22 |         clerk.users.update_user_metadata(                   # merge into unsafe_metadata
23 |             userID, unsafe_metadata={"plan": plan})
24 |     return {"ok": True}
25 |     
26 | 
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # -------- BASE IMAGE (includes Chromium & deps) ----------------------------
 2 | FROM mcr.microsoft.com/playwright/python:v1.53.0-noble
 3 | 
 4 | # -------- Runtime setup ----------------------------------------------------
 5 | WORKDIR /app
 6 | 
 7 | # Copy dependency manifests first for layer-cache
 8 | COPY pyproject.toml poetry.lock* requirements*.txt* ./
 9 | 
10 | # Fast, deterministic install with `uv`
11 | RUN pip install --no-cache-dir uv && \
12 |     uv pip install --system --no-cache-dir .[asgi,saas]
13 | 
14 | # Copy application source
15 | COPY . .
16 | 
17 | # -------- Environment ------------------------------------------------------
18 | ENV PYTHONUNBUFFERED=1
19 | ENV ENABLE_AUTH=true
20 | ENV PORT=8000
21 | 
22 | # -------- Health check -----------------------------------------------------
23 | HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
24 |   CMD python -c "import httpx, os, sys; r=httpx.get(f'http://localhost:{os.getenv(\"PORT\",\"8000\")}/health'); sys.exit(0 if r.status_code==200 else 1)"
25 | 
26 | EXPOSE 8000
27 | 
28 | # -------- Entrypoint -------------------------------------------------------
29 | CMD ["uvicorn", "asgi_app:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"]
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # -------- BASE IMAGE (includes Chromium & deps) ----------------------------
 2 | FROM mcr.microsoft.com/playwright/python:v1.53.0-noble
 3 | 
 4 | # -------- Runtime setup ----------------------------------------------------
 5 | WORKDIR /app
 6 | 
 7 | # Copy dependency manifests first for layer-cache
 8 | COPY pyproject.toml poetry.lock* requirements*.txt* ./
 9 | 
10 | # Fast, deterministic install with `uv`
11 | RUN pip install --no-cache-dir uv && \
12 |     uv pip install --system --no-cache-dir . && \
13 |     uv pip install --system --no-cache-dir .[asgi,saas]
14 | 
15 | # Cache buster - force rebuild
16 | ARG CACHE_BUST=202510061202
17 | RUN echo "Cache bust: $CACHE_BUST"
18 | 
19 | # Copy application source
20 | COPY . .
21 | 
22 | # -------- Environment ------------------------------------------------------
23 | ENV PYTHONUNBUFFERED=1
24 | ENV ENABLE_AUTH=true
25 | ENV PORT=8000
26 | 
27 | # -------- Health check -----------------------------------------------------
28 | HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
29 |   CMD python -c "import httpx, os, sys; r=httpx.get(f'http://localhost:{os.getenv(\"PORT\",\"8000\")}/health'); sys.exit(0 if r.status_code==200 else 1)"
30 | 
31 | EXPOSE 8000
32 | 
33 | # -------- Entrypoint -------------------------------------------------------
34 | CMD ["uvicorn", "asgi_app:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"]
```

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

```python
 1 | # sayistay_mcp_module/__init__.py
 2 | 
 3 | """
 4 | Sayıştay (Turkish Court of Accounts) MCP Module
 5 | 
 6 | This module provides access to three types of Sayıştay decisions:
 7 | - Genel Kurul (General Assembly) decisions
 8 | - Temyiz Kurulu (Appeals Board) decisions  
 9 | - Daire (Chamber) decisions
10 | 
11 | The module handles ASP.NET WebForms authentication with CSRF tokens
12 | and DataTables-based pagination for comprehensive decision search.
13 | """
14 | 
15 | from .client import SayistayApiClient
16 | from .models import (
17 |     # Genel Kurul models
18 |     GenelKurulSearchRequest,
19 |     GenelKurulSearchResponse,
20 |     GenelKurulDecision,
21 |     
22 |     # Temyiz Kurulu models
23 |     TemyizKuruluSearchRequest, 
24 |     TemyizKuruluSearchResponse,
25 |     TemyizKuruluDecision,
26 |     
27 |     # Daire models
28 |     DaireSearchRequest,
29 |     DaireSearchResponse,
30 |     DaireDecision,
31 |     
32 |     # Document models
33 |     SayistayDocumentMarkdown
34 | )
35 | from .enums import (
36 |     DaireEnum,
37 |     KamuIdaresiTuruEnum,
38 |     WebKararKonusuEnum
39 | )
40 | 
41 | __all__ = [
42 |     "SayistayApiClient",
43 |     "GenelKurulSearchRequest",
44 |     "GenelKurulSearchResponse", 
45 |     "GenelKurulDecision",
46 |     "TemyizKuruluSearchRequest",
47 |     "TemyizKuruluSearchResponse",
48 |     "TemyizKuruluDecision",
49 |     "DaireSearchRequest",
50 |     "DaireSearchResponse",
51 |     "DaireDecision",
52 |     "SayistayDocumentMarkdown",
53 |     "DaireEnum",
54 |     "KamuIdaresiTuruEnum",
55 |     "WebKararKonusuEnum"
56 | ]
```

--------------------------------------------------------------------------------
/sayistay_mcp_module/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | # sayistay_mcp_module/__init__.py
 2 | 
 3 | """
 4 | Sayıştay (Turkish Court of Accounts) MCP Module
 5 | 
 6 | This module provides access to three types of Sayıştay decisions:
 7 | - Genel Kurul (General Assembly) decisions
 8 | - Temyiz Kurulu (Appeals Board) decisions  
 9 | - Daire (Chamber) decisions
10 | 
11 | The module handles ASP.NET WebForms authentication with CSRF tokens
12 | and DataTables-based pagination for comprehensive decision search.
13 | """
14 | 
15 | from .client import SayistayApiClient
16 | from .models import (
17 |     # Genel Kurul models
18 |     GenelKurulSearchRequest,
19 |     GenelKurulSearchResponse,
20 |     GenelKurulDecision,
21 |     
22 |     # Temyiz Kurulu models
23 |     TemyizKuruluSearchRequest, 
24 |     TemyizKuruluSearchResponse,
25 |     TemyizKuruluDecision,
26 |     
27 |     # Daire models
28 |     DaireSearchRequest,
29 |     DaireSearchResponse,
30 |     DaireDecision,
31 |     
32 |     # Document models
33 |     SayistayDocumentMarkdown
34 | )
35 | from .enums import (
36 |     DaireEnum,
37 |     KamuIdaresiTuruEnum,
38 |     WebKararKonusuEnum
39 | )
40 | 
41 | __all__ = [
42 |     "SayistayApiClient",
43 |     "GenelKurulSearchRequest",
44 |     "GenelKurulSearchResponse", 
45 |     "GenelKurulDecision",
46 |     "TemyizKuruluSearchRequest",
47 |     "TemyizKuruluSearchResponse",
48 |     "TemyizKuruluDecision",
49 |     "DaireSearchRequest",
50 |     "DaireSearchResponse",
51 |     "DaireDecision",
52 |     "SayistayDocumentMarkdown",
53 |     "DaireEnum",
54 |     "KamuIdaresiTuruEnum",
55 |     "WebKararKonusuEnum"
56 | ]
```

--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
 1 | version: '3.8'
 2 | 
 3 | services:
 4 |   yargi-mcp:
 5 |     build: .
 6 |     image: yargi-mcp:latest
 7 |     container_name: yargi-mcp-server
 8 |     ports:
 9 |       - "${PORT:-8000}:8000"
10 |     environment:
11 |       - HOST=0.0.0.0
12 |       - PORT=8000
13 |       - LOG_LEVEL=${LOG_LEVEL:-info}
14 |       - ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-*}
15 |       - API_TOKEN=${API_TOKEN:-}
16 |       - PYTHONUNBUFFERED=1
17 |     volumes:
18 |       # Mount logs directory
19 |       - ./logs:/app/logs
20 |       # Mount .env file if it exists
21 |       - ./.env:/app/.env:ro
22 |     restart: unless-stopped
23 |     healthcheck:
24 |       test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8000/health').raise_for_status()"]
25 |       interval: 30s
26 |       timeout: 10s
27 |       retries: 3
28 |       start_period: 10s
29 |     networks:
30 |       - yargi-network
31 | 
32 |   # Optional: Nginx reverse proxy
33 |   nginx:
34 |     image: nginx:alpine
35 |     container_name: yargi-nginx
36 |     ports:
37 |       - "80:80"
38 |       - "443:443"
39 |     volumes:
40 |       - ./nginx.conf:/etc/nginx/nginx.conf:ro
41 |       - ./ssl:/etc/nginx/ssl:ro
42 |     depends_on:
43 |       - yargi-mcp
44 |     networks:
45 |       - yargi-network
46 |     profiles:
47 |       - production
48 | 
49 |   # Optional: Redis for caching (future enhancement)
50 |   redis:
51 |     image: redis:alpine
52 |     container_name: yargi-redis
53 |     command: redis-server --appendonly yes
54 |     volumes:
55 |       - redis-data:/data
56 |     networks:
57 |       - yargi-network
58 |     profiles:
59 |       - with-cache
60 | 
61 | networks:
62 |   yargi-network:
63 |     driver: bridge
64 | 
65 | volumes:
66 |   redis-data:
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
 1 | version: '3.8'
 2 | 
 3 | services:
 4 |   yargi-mcp:
 5 |     build: .
 6 |     image: yargi-mcp:latest
 7 |     container_name: yargi-mcp-server
 8 |     ports:
 9 |       - "${PORT:-8000}:8000"
10 |     environment:
11 |       - HOST=0.0.0.0
12 |       - PORT=8000
13 |       - LOG_LEVEL=${LOG_LEVEL:-info}
14 |       - ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-*}
15 |       - API_TOKEN=${API_TOKEN:-}
16 |       - PYTHONUNBUFFERED=1
17 |     volumes:
18 |       # Mount logs directory
19 |       - ./logs:/app/logs
20 |       # Mount .env file if it exists
21 |       - ./.env:/app/.env:ro
22 |     restart: unless-stopped
23 |     healthcheck:
24 |       test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8000/health').raise_for_status()"]
25 |       interval: 30s
26 |       timeout: 10s
27 |       retries: 3
28 |       start_period: 10s
29 |     networks:
30 |       - yargi-network
31 | 
32 |   # Optional: Nginx reverse proxy
33 |   nginx:
34 |     image: nginx:alpine
35 |     container_name: yargi-nginx
36 |     ports:
37 |       - "80:80"
38 |       - "443:443"
39 |     volumes:
40 |       - ./nginx.conf:/etc/nginx/nginx.conf:ro
41 |       - ./ssl:/etc/nginx/ssl:ro
42 |     depends_on:
43 |       - yargi-mcp
44 |     networks:
45 |       - yargi-network
46 |     profiles:
47 |       - production
48 | 
49 |   # Optional: Redis for caching (future enhancement)
50 |   redis:
51 |     image: redis:alpine
52 |     container_name: yargi-redis
53 |     command: redis-server --appendonly yes
54 |     volumes:
55 |       - redis-data:/data
56 |     networks:
57 |       - yargi-network
58 |     profiles:
59 |       - with-cache
60 | 
61 | networks:
62 |   yargi-network:
63 |     driver: bridge
64 | 
65 | volumes:
66 |   redis-data:
```

--------------------------------------------------------------------------------
/bddk_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
 1 | # bddk_mcp_module/models.py
 2 | 
 3 | from pydantic import BaseModel, Field
 4 | from typing import List, Optional
 5 | 
 6 | class BddkSearchRequest(BaseModel):
 7 |     """
 8 |     Request model for searching BDDK decisions via Tavily API.
 9 |     
10 |     BDDK (Bankacılık Düzenleme ve Denetleme Kurumu) is Turkey's Banking
11 |     Regulation and Supervision Agency responsible for banking licenses,
12 |     electronic money institutions, and financial regulations.
13 |     """
14 |     keywords: str = Field(..., description="Search keywords in Turkish")
15 |     page: int = Field(1, ge=1, description="Page number (1-indexed)")
16 |     pageSize: int = Field(10, ge=1, le=50, description="Results per page (1-50)")
17 | 
18 | class BddkDecisionSummary(BaseModel):
19 |     """Summary of a BDDK decision from search results."""
20 |     title: str = Field(..., description="Decision title")
21 |     document_id: str = Field(..., description="BDDK document ID (e.g., '310')")
22 |     content: str = Field(..., description="Decision summary/excerpt")
23 | 
24 | class BddkSearchResult(BaseModel):
25 |     """Response model for BDDK decision search results."""
26 |     decisions: List[BddkDecisionSummary] = Field(
27 |         default_factory=list, 
28 |         description="List of matching BDDK decisions"
29 |     )
30 |     total_results: int = Field(0, description="Total number of results")
31 |     page: int = Field(1, description="Current page number")
32 |     pageSize: int = Field(10, description="Results per page")
33 | 
34 | class BddkDocumentMarkdown(BaseModel):
35 |     """
36 |     BDDK decision document converted to Markdown format.
37 |     
38 |     Supports paginated content for long documents (5000 chars per page).
39 |     """
40 |     document_id: str = Field(..., description="BDDK document ID")
41 |     markdown_content: str = Field("", description="Document content in Markdown")
42 |     page_number: int = Field(1, description="Current page number")
43 |     total_pages: int = Field(1, description="Total number of pages")
```

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

```python
 1 | # bddk_mcp_module/models.py
 2 | 
 3 | from pydantic import BaseModel, Field
 4 | from typing import List, Optional
 5 | 
 6 | class BddkSearchRequest(BaseModel):
 7 |     """
 8 |     Request model for searching BDDK decisions via Tavily API.
 9 |     
10 |     BDDK (Bankacılık Düzenleme ve Denetleme Kurumu) is Turkey's Banking
11 |     Regulation and Supervision Agency responsible for banking licenses,
12 |     electronic money institutions, and financial regulations.
13 |     """
14 |     keywords: str = Field(..., description="Search keywords in Turkish")
15 |     page: int = Field(1, ge=1, description="Page number (1-indexed)")
16 |     pageSize: int = Field(10, ge=1, le=50, description="Results per page (1-50)")
17 | 
18 | class BddkDecisionSummary(BaseModel):
19 |     """Summary of a BDDK decision from search results."""
20 |     title: str = Field(..., description="Decision title")
21 |     document_id: str = Field(..., description="BDDK document ID (e.g., '310')")
22 |     content: str = Field(..., description="Decision summary/excerpt")
23 | 
24 | class BddkSearchResult(BaseModel):
25 |     """Response model for BDDK decision search results."""
26 |     decisions: List[BddkDecisionSummary] = Field(
27 |         default_factory=list, 
28 |         description="List of matching BDDK decisions"
29 |     )
30 |     total_results: int = Field(0, description="Total number of results")
31 |     page: int = Field(1, description="Current page number")
32 |     pageSize: int = Field(10, description="Results per page")
33 | 
34 | class BddkDocumentMarkdown(BaseModel):
35 |     """
36 |     BDDK decision document converted to Markdown format.
37 |     
38 |     Supports paginated content for long documents (5000 chars per page).
39 |     """
40 |     document_id: str = Field(..., description="BDDK document ID")
41 |     markdown_content: str = Field("", description="Document content in Markdown")
42 |     page_number: int = Field(1, description="Current page number")
43 |     total_pages: int = Field(1, description="Total number of pages")
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "yargi-mcp"
 3 | version = "0.1.8"
 4 | description = "MCP Server For Turkish Legal Databases"
 5 | readme = "README.md"
 6 | requires-python = ">=3.11"
 7 | license = {text = "MIT"}
 8 | authors = [{name = "Said Surucu", email = "[email protected]"}]
 9 | keywords = ["mcp", "turkish-law", "legal", "yargitay", "danistay", "bddk", "kvkk", "turkish", "law", "court", "decisions"]
10 | classifiers = [
11 |     "Development Status :: 4 - Beta",
12 |     "Intended Audience :: Legal Industry",
13 |     "Intended Audience :: Developers",
14 |     "License :: OSI Approved :: MIT License",
15 |     "Programming Language :: Python :: 3.11",
16 |     "Programming Language :: Python :: 3.12",
17 |     "Topic :: Software Development :: Libraries :: Python Modules",
18 |     "Topic :: Text Processing :: Markup :: Markdown",
19 |     "Operating System :: OS Independent",
20 | ]
21 | urls = {Homepage = "https://github.com/saidsurucu/yargi-mcp", Issues = "https://github.com/saidsurucu/yargi-mcp/issues"}
22 | dependencies = [
23 |     "beautifulsoup4>=4.13.4",
24 |     "httpx>=0.28.1",
25 |     "markitdown[pdf]>=0.1.1",
26 |     "pydantic>=2.11.4",
27 |     "aiohttp>=3.11.18",
28 |     "playwright>=1.52.0",
29 |     "fastmcp>=2.10.5",
30 |     "pypdf>=5.5.0",
31 |     "fastapi>=0.115.14",
32 | ]
33 | 
34 | [project.optional-dependencies]
35 | asgi = [
36 |     "uvicorn[standard]>=0.30.0",
37 |     "starlette>=0.37.0",
38 | ]
39 | api = [
40 |     "fastapi>=0.115.0",
41 |     "uvicorn[standard]>=0.30.0",
42 | ]
43 | production = [
44 |     "gunicorn>=22.0.0",
45 |     "uvicorn[standard]>=0.30.0",
46 | ]
47 | saas = [
48 |     "clerk-backend-api>=3.0.0",
49 |     "stripe>=9.1.0",
50 |     "upstash-redis>=1.1.0",
51 |     "tiktoken>=0.5.0",
52 |     "PyJWT>=2.8.0",
53 | ]
54 | 
55 | [project.scripts]
56 | yargi-mcp = "mcp_server_main:main"
57 | 
58 | [tool.setuptools]
59 | py-modules = ["mcp_server_main", "mcp_auth_factory", "mcp_auth_http_adapter", "asgi_app", "fastapi_app", "starlette_app", "run_asgi", "stripe_webhook"]
60 | 
61 | [tool.setuptools.packages.find]
62 | include = ["*_mcp_module", "mcp_auth"]
63 | 
64 | [build-system]
65 | requires = ["setuptools>=65.0", "wheel"]
66 | build-backend = "setuptools.build_meta"
67 | 
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "yargi-mcp"
 3 | version = "0.1.6"
 4 | description = "MCP Server For Turkish Legal Databases"
 5 | readme = "README.md"
 6 | requires-python = ">=3.11"
 7 | license = {text = "MIT"}
 8 | authors = [{name = "Said Surucu", email = "[email protected]"}]
 9 | keywords = ["mcp", "turkish-law", "legal", "yargitay", "danistay", "bddk", "kvkk", "turkish", "law", "court", "decisions"]
10 | classifiers = [
11 |     "Development Status :: 4 - Beta",
12 |     "Intended Audience :: Legal Industry",
13 |     "Intended Audience :: Developers",
14 |     "License :: OSI Approved :: MIT License",
15 |     "Programming Language :: Python :: 3.11",
16 |     "Programming Language :: Python :: 3.12",
17 |     "Topic :: Software Development :: Libraries :: Python Modules",
18 |     "Topic :: Text Processing :: Markup :: Markdown",
19 |     "Operating System :: OS Independent",
20 | ]
21 | urls = {Homepage = "https://github.com/saidsurucu/yargi-mcp", Issues = "https://github.com/saidsurucu/yargi-mcp/issues"}
22 | dependencies = [
23 |     "beautifulsoup4>=4.13.4",
24 |     "httpx>=0.28.1",
25 |     "markitdown[pdf]>=0.1.1",
26 |     "pydantic>=2.11.4",
27 |     "aiohttp>=3.11.18",
28 |     "playwright>=1.52.0",
29 |     "fastmcp>=2.10.5",
30 |     "pypdf>=5.5.0",
31 |     "fastapi>=0.115.14",
32 |     "PyJWT>=2.8.0",
33 |     "tiktoken>=0.5.0",
34 | ]
35 | 
36 | [project.optional-dependencies]
37 | asgi = [
38 |     "uvicorn[standard]>=0.30.0",
39 |     "starlette>=0.37.0",
40 | ]
41 | api = [
42 |     "fastapi>=0.115.0",
43 |     "uvicorn[standard]>=0.30.0",
44 | ]
45 | production = [
46 |     "gunicorn>=22.0.0",
47 |     "uvicorn[standard]>=0.30.0",
48 | ]
49 | saas = [
50 |     "clerk-backend-api>=3.0.0",
51 |     "stripe>=9.1.0",
52 |     "upstash-redis>=1.1.0",
53 | ]
54 | 
55 | [project.scripts]
56 | yargi-mcp = "mcp_server_main:main"
57 | 
58 | [tool.setuptools]
59 | py-modules = ["mcp_server_main", "mcp_auth_factory", "mcp_auth_http_adapter", "asgi_app", "fastapi_app", "starlette_app", "run_asgi", "stripe_webhook"]
60 | 
61 | [tool.setuptools.packages.find]
62 | include = ["*_mcp_module", "mcp_auth"]
63 | 
64 | [build-system]
65 | requires = ["setuptools>=65.0", "wheel"]
66 | build-backend = "setuptools.build_meta"
67 | 
```

--------------------------------------------------------------------------------
/mcp_auth/clerk_config.py:
--------------------------------------------------------------------------------

```python
 1 | """
 2 | Clerk OAuth configuration for MCP Auth Toolkit
 3 | """
 4 | 
 5 | import os
 6 | import logging
 7 | from .oauth import OAuthConfig
 8 | 
 9 | logger = logging.getLogger(__name__)
10 | 
11 | 
12 | def create_clerk_oauth_config() -> OAuthConfig:
13 |     """Create OAuth configuration for Clerk integration using SDK"""
14 |     
15 |     # Get Clerk configuration from environment
16 |     clerk_domain = os.getenv("CLERK_DOMAIN", "accounts.yargimcp.com")
17 |     clerk_publishable_key = os.getenv("CLERK_PUBLISHABLE_KEY")
18 |     clerk_secret_key = os.getenv("CLERK_SECRET_KEY")
19 |     
20 |     if not clerk_publishable_key or not clerk_secret_key:
21 |         raise ValueError("CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY are required")
22 |     
23 |     # For Clerk with custom domains, we use our adapter endpoints
24 |     # This allows us to handle the custom domain flow properly
25 |     base_url = os.getenv("BASE_URL", "https://yargimcp.com")
26 |     
27 |     config = OAuthConfig(
28 |         client_id=clerk_publishable_key,
29 |         client_secret=clerk_secret_key,
30 |         # Use our adapter endpoints instead of Clerk's direct endpoints
31 |         authorization_endpoint=f"{base_url}/authorize",
32 |         token_endpoint=f"{base_url}/token",
33 |         # Keep Clerk's JWKS for token validation
34 |         jwks_uri=f"https://{clerk_domain}/.well-known/jwks.json",
35 |         issuer=base_url,  # We're the issuer for MCP tokens
36 |         scopes=["mcp:tools:read", "mcp:tools:write", "openid", "profile", "email"]
37 |     )
38 |     
39 |     logger.info(f"Created Clerk OAuth config with adapter endpoints")
40 |     logger.info(f"Clerk domain: {clerk_domain}")
41 |     logger.debug(f"Authorization endpoint: {config.authorization_endpoint}")
42 |     logger.debug(f"Token endpoint: {config.token_endpoint}")
43 |     
44 |     return config
45 | 
46 | 
47 | def get_jwt_secret() -> str:
48 |     """Get JWT secret for token signing"""
49 |     jwt_secret = os.getenv("JWT_SECRET_KEY")
50 |     
51 |     if not jwt_secret:
52 |         raise ValueError("JWT_SECRET_KEY environment variable is required")
53 |     
54 |     return jwt_secret
55 | 
56 | 
57 | def create_mcp_server_config():
58 |     """Create complete MCP server configuration for Clerk integration"""
59 |     
60 |     try:
61 |         oauth_config = create_clerk_oauth_config()
62 |         jwt_secret = get_jwt_secret()
63 |         
64 |         return {
65 |             "oauth_config": oauth_config,
66 |             "jwt_secret": jwt_secret,
67 |             "base_url": os.getenv("BASE_URL", "https://yargi-mcp.fly.dev"),
68 |             "auth_enabled": os.getenv("ENABLE_AUTH", "true").lower() == "true"
69 |         }
70 |         
71 |     except Exception as e:
72 |         logger.error(f"Failed to create MCP server config: {e}")
73 |         raise
```

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

```python
 1 | """
 2 | Clerk OAuth configuration for MCP Auth Toolkit
 3 | """
 4 | 
 5 | import os
 6 | import logging
 7 | from .oauth import OAuthConfig
 8 | 
 9 | logger = logging.getLogger(__name__)
10 | 
11 | 
12 | def create_clerk_oauth_config() -> OAuthConfig:
13 |     """Create OAuth configuration for Clerk integration using SDK"""
14 |     
15 |     # Get Clerk configuration from environment
16 |     clerk_domain = os.getenv("CLERK_DOMAIN", "accounts.yargimcp.com")
17 |     clerk_publishable_key = os.getenv("CLERK_PUBLISHABLE_KEY")
18 |     clerk_secret_key = os.getenv("CLERK_SECRET_KEY")
19 |     
20 |     if not clerk_publishable_key or not clerk_secret_key:
21 |         raise ValueError("CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY are required")
22 |     
23 |     # For Clerk with custom domains, we use our adapter endpoints
24 |     # This allows us to handle the custom domain flow properly
25 |     base_url = os.getenv("BASE_URL", "https://yargimcp.com")
26 |     
27 |     config = OAuthConfig(
28 |         client_id=clerk_publishable_key,
29 |         client_secret=clerk_secret_key,
30 |         # Use our adapter endpoints instead of Clerk's direct endpoints
31 |         authorization_endpoint=f"{base_url}/authorize",
32 |         token_endpoint=f"{base_url}/token",
33 |         # Keep Clerk's JWKS for token validation
34 |         jwks_uri=f"https://{clerk_domain}/.well-known/jwks.json",
35 |         issuer=base_url,  # We're the issuer for MCP tokens
36 |         scopes=["mcp:tools:read", "mcp:tools:write", "openid", "profile", "email"]
37 |     )
38 |     
39 |     logger.info(f"Created Clerk OAuth config with adapter endpoints")
40 |     logger.info(f"Clerk domain: {clerk_domain}")
41 |     logger.debug(f"Authorization endpoint: {config.authorization_endpoint}")
42 |     logger.debug(f"Token endpoint: {config.token_endpoint}")
43 |     
44 |     return config
45 | 
46 | 
47 | def get_jwt_secret() -> str:
48 |     """Get JWT secret for token signing"""
49 |     jwt_secret = os.getenv("JWT_SECRET_KEY")
50 |     
51 |     if not jwt_secret:
52 |         raise ValueError("JWT_SECRET_KEY environment variable is required")
53 |     
54 |     return jwt_secret
55 | 
56 | 
57 | def create_mcp_server_config():
58 |     """Create complete MCP server configuration for Clerk integration"""
59 |     
60 |     try:
61 |         oauth_config = create_clerk_oauth_config()
62 |         jwt_secret = get_jwt_secret()
63 |         
64 |         return {
65 |             "oauth_config": oauth_config,
66 |             "jwt_secret": jwt_secret,
67 |             "base_url": os.getenv("BASE_URL", "https://yargi-mcp.fly.dev"),
68 |             "auth_enabled": os.getenv("ENABLE_AUTH", "true").lower() == "true"
69 |         }
70 |         
71 |     except Exception as e:
72 |         logger.error(f"Failed to create MCP server config: {e}")
73 |         raise
```

--------------------------------------------------------------------------------
/nginx.conf:
--------------------------------------------------------------------------------

```
 1 | events {
 2 |     worker_connections 1024;
 3 | }
 4 | 
 5 | http {
 6 |     upstream yargi_mcp {
 7 |         server yargi-mcp:8000;
 8 |     }
 9 | 
10 |     # Rate limiting
11 |     limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
12 |     limit_req_zone $binary_remote_addr zone=mcp_limit:10m rate=100r/s;
13 | 
14 |     server {
15 |         listen 80;
16 |         server_name localhost;
17 | 
18 |         # Redirect HTTP to HTTPS in production
19 |         # return 301 https://$server_name$request_uri;
20 | 
21 |         # Security headers
22 |         add_header X-Content-Type-Options nosniff;
23 |         add_header X-Frame-Options DENY;
24 |         add_header X-XSS-Protection "1; mode=block";
25 |         add_header Referrer-Policy "strict-origin-when-cross-origin";
26 | 
27 |         # API endpoints
28 |         location /api/ {
29 |             limit_req zone=api_limit burst=20 nodelay;
30 |             
31 |             proxy_pass http://yargi_mcp;
32 |             proxy_set_header Host $host;
33 |             proxy_set_header X-Real-IP $remote_addr;
34 |             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
35 |             proxy_set_header X-Forwarded-Proto $scheme;
36 |             
37 |             # Timeouts
38 |             proxy_connect_timeout 60s;
39 |             proxy_send_timeout 60s;
40 |             proxy_read_timeout 60s;
41 |         }
42 | 
43 |         # MCP endpoint (higher rate limit)
44 |         location /mcp-server/mcp/ {
45 |             limit_req zone=mcp_limit burst=50 nodelay;
46 |             
47 |             proxy_pass http://yargi_mcp;
48 |             proxy_set_header Host $host;
49 |             proxy_set_header X-Real-IP $remote_addr;
50 |             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
51 |             proxy_set_header X-Forwarded-Proto $scheme;
52 |             
53 |             # WebSocket support
54 |             proxy_http_version 1.1;
55 |             proxy_set_header Upgrade $http_upgrade;
56 |             proxy_set_header Connection "upgrade";
57 |             
58 |             # Longer timeouts for MCP operations
59 |             proxy_connect_timeout 300s;
60 |             proxy_send_timeout 300s;
61 |             proxy_read_timeout 300s;
62 |         }
63 | 
64 |         # Health check (no rate limit)
65 |         location /health {
66 |             proxy_pass http://yargi_mcp;
67 |             proxy_set_header Host $host;
68 |         }
69 | 
70 |         # Root and other paths
71 |         location / {
72 |             limit_req zone=api_limit burst=10 nodelay;
73 |             
74 |             proxy_pass http://yargi_mcp;
75 |             proxy_set_header Host $host;
76 |             proxy_set_header X-Real-IP $remote_addr;
77 |             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
78 |             proxy_set_header X-Forwarded-Proto $scheme;
79 |         }
80 |     }
81 | 
82 |     # SSL configuration (uncomment for production)
83 |     # server {
84 |     #     listen 443 ssl http2;
85 |     #     server_name your-domain.com;
86 |     #
87 |     #     ssl_certificate /etc/nginx/ssl/cert.pem;
88 |     #     ssl_certificate_key /etc/nginx/ssl/key.pem;
89 |     #     ssl_protocols TLSv1.2 TLSv1.3;
90 |     #     ssl_ciphers HIGH:!aNULL:!MD5;
91 |     #
92 |     #     # Include all location blocks from above
93 |     # }
94 | }
```

--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/nginx.conf:
--------------------------------------------------------------------------------

```
 1 | events {
 2 |     worker_connections 1024;
 3 | }
 4 | 
 5 | http {
 6 |     upstream yargi_mcp {
 7 |         server yargi-mcp:8000;
 8 |     }
 9 | 
10 |     # Rate limiting
11 |     limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
12 |     limit_req_zone $binary_remote_addr zone=mcp_limit:10m rate=100r/s;
13 | 
14 |     server {
15 |         listen 80;
16 |         server_name localhost;
17 | 
18 |         # Redirect HTTP to HTTPS in production
19 |         # return 301 https://$server_name$request_uri;
20 | 
21 |         # Security headers
22 |         add_header X-Content-Type-Options nosniff;
23 |         add_header X-Frame-Options DENY;
24 |         add_header X-XSS-Protection "1; mode=block";
25 |         add_header Referrer-Policy "strict-origin-when-cross-origin";
26 | 
27 |         # API endpoints
28 |         location /api/ {
29 |             limit_req zone=api_limit burst=20 nodelay;
30 |             
31 |             proxy_pass http://yargi_mcp;
32 |             proxy_set_header Host $host;
33 |             proxy_set_header X-Real-IP $remote_addr;
34 |             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
35 |             proxy_set_header X-Forwarded-Proto $scheme;
36 |             
37 |             # Timeouts
38 |             proxy_connect_timeout 60s;
39 |             proxy_send_timeout 60s;
40 |             proxy_read_timeout 60s;
41 |         }
42 | 
43 |         # MCP endpoint (higher rate limit)
44 |         location /mcp-server/mcp/ {
45 |             limit_req zone=mcp_limit burst=50 nodelay;
46 |             
47 |             proxy_pass http://yargi_mcp;
48 |             proxy_set_header Host $host;
49 |             proxy_set_header X-Real-IP $remote_addr;
50 |             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
51 |             proxy_set_header X-Forwarded-Proto $scheme;
52 |             
53 |             # WebSocket support
54 |             proxy_http_version 1.1;
55 |             proxy_set_header Upgrade $http_upgrade;
56 |             proxy_set_header Connection "upgrade";
57 |             
58 |             # Longer timeouts for MCP operations
59 |             proxy_connect_timeout 300s;
60 |             proxy_send_timeout 300s;
61 |             proxy_read_timeout 300s;
62 |         }
63 | 
64 |         # Health check (no rate limit)
65 |         location /health {
66 |             proxy_pass http://yargi_mcp;
67 |             proxy_set_header Host $host;
68 |         }
69 | 
70 |         # Root and other paths
71 |         location / {
72 |             limit_req zone=api_limit burst=10 nodelay;
73 |             
74 |             proxy_pass http://yargi_mcp;
75 |             proxy_set_header Host $host;
76 |             proxy_set_header X-Real-IP $remote_addr;
77 |             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
78 |             proxy_set_header X-Forwarded-Proto $scheme;
79 |         }
80 |     }
81 | 
82 |     # SSL configuration (uncomment for production)
83 |     # server {
84 |     #     listen 443 ssl http2;
85 |     #     server_name your-domain.com;
86 |     #
87 |     #     ssl_certificate /etc/nginx/ssl/cert.pem;
88 |     #     ssl_certificate_key /etc/nginx/ssl/key.pem;
89 |     #     ssl_protocols TLSv1.2 TLSv1.3;
90 |     #     ssl_ciphers HIGH:!aNULL:!MD5;
91 |     #
92 |     #     # Include all location blocks from above
93 |     # }
94 | }
```

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

```python
 1 | # sayistay_mcp_module/enums.py
 2 | 
 3 | from typing import Literal
 4 | 
 5 | # Chamber/Daire options for Temyiz Kurulu and Daire endpoints (1-8 + All)
 6 | DaireEnum = Literal[
 7 |     "ALL",           # All chambers/departments
 8 |     "1",             # 1. Daire
 9 |     "2",             # 2. Daire  
10 |     "3",             # 3. Daire
11 |     "4",             # 4. Daire
12 |     "5",             # 5. Daire
13 |     "6",             # 6. Daire
14 |     "7",             # 7. Daire
15 |     "8"              # 8. Daire
16 | ]
17 | 
18 | # Public Administration Types (Kamu İdaresi Türü)
19 | KamuIdaresiTuruEnum = Literal[
20 |     "ALL",                                      # All institutions
21 |     "Genel Bütçe Kapsamındaki İdareler",       # General Budget Administrations
22 |     "Yüksek Öğretim Kurumları",                # Higher Education Institutions
23 |     "Diğer Özel Bütçeli İdareler",             # Other Special Budget Administrations
24 |     "Düzenleyici ve Denetleyici Kurumlar",     # Regulatory and Supervisory Institutions
25 |     "Sosyal Güvenlik Kurumları",               # Social Security Institutions
26 |     "Özel İdareler",                           # Special Administrations
27 |     "Belediyeler ve Bağlı İdareler",           # Municipalities and Affiliated Administrations
28 |     "Diğer"                                    # Other
29 | ]
30 | 
31 | # Decision Subject Categories (Web Karar Konusu) - Shortened for token efficiency
32 | WebKararKonusuEnum = Literal[
33 |     "ALL",                                          # All subjects
34 |     "Harcırah Mevzuatı",                           # Travel Allowance Legislation
35 |     "İhale Mevzuatı",                              # Procurement Legislation
36 |     "İş Mevzuatı",                                 # Labor Legislation
37 |     "Personel Mevzuatı",                           # Personnel Legislation
38 |     "Sorumluluk ve Yargılama Usulleri",            # Liability and Trial Procedures
39 |     "Vergi Resmi Harç ve Diğer Gelirler",         # Tax, Official Fee and Other Revenue
40 |     "Çeşitli Konular"                              # Various Topics
41 | ]
42 | 
43 | # Mapping from shortened enum values to full API values
44 | WEB_KARAR_KONUSU_MAPPING = {
45 |     "ALL": "ALL",
46 |     "Harcırah Mevzuatı": "Harcırah Mevzuatı ile İlgili Kararlar",
47 |     "İhale Mevzuatı": "İhale Mevzuatı ile İlgili Kararlar",
48 |     "İş Mevzuatı": "İş Mevzuatı ile İlgili Kararlar",
49 |     "Personel Mevzuatı": "Personel Mevzuatı ile İlgili Kararlar",
50 |     "Sorumluluk ve Yargılama Usulleri": "Sorumluluk ve Yargılama Usulleri ile İlgili Kararlar",
51 |     "Vergi Resmi Harç ve Diğer Gelirler": "Vergi Resmi Harç ve Diğer Gelirlerle İlgili Kararlar",
52 |     "Çeşitli Konular": "Çeşitli Konuları İlgilendiren Kararlar"
53 | }
54 | 
55 | # Year ranges for different endpoints
56 | GENEL_KURUL_YEARS = [str(year) for year in range(2006, 2025)]  # 2006-2024
57 | TEMYIZ_KURULU_YEARS = [str(year) for year in range(1993, 2023)]  # 1993-2022
58 | DAIRE_YEARS = [str(year) for year in range(2012, 2026)]  # 2012-2025
59 | 
60 | # Account years for Temyiz Kurulu and Daire endpoints
61 | HESAP_YILLARI = [str(year) for year in range(1993, 2024)]  # 1993-2023
```

--------------------------------------------------------------------------------
/sayistay_mcp_module/enums.py:
--------------------------------------------------------------------------------

```python
 1 | # sayistay_mcp_module/enums.py
 2 | 
 3 | from typing import Literal
 4 | 
 5 | # Chamber/Daire options for Temyiz Kurulu and Daire endpoints (1-8 + All)
 6 | DaireEnum = Literal[
 7 |     "ALL",           # All chambers/departments
 8 |     "1",             # 1. Daire
 9 |     "2",             # 2. Daire  
10 |     "3",             # 3. Daire
11 |     "4",             # 4. Daire
12 |     "5",             # 5. Daire
13 |     "6",             # 6. Daire
14 |     "7",             # 7. Daire
15 |     "8"              # 8. Daire
16 | ]
17 | 
18 | # Public Administration Types (Kamu İdaresi Türü)
19 | KamuIdaresiTuruEnum = Literal[
20 |     "ALL",                                      # All institutions
21 |     "Genel Bütçe Kapsamındaki İdareler",       # General Budget Administrations
22 |     "Yüksek Öğretim Kurumları",                # Higher Education Institutions
23 |     "Diğer Özel Bütçeli İdareler",             # Other Special Budget Administrations
24 |     "Düzenleyici ve Denetleyici Kurumlar",     # Regulatory and Supervisory Institutions
25 |     "Sosyal Güvenlik Kurumları",               # Social Security Institutions
26 |     "Özel İdareler",                           # Special Administrations
27 |     "Belediyeler ve Bağlı İdareler",           # Municipalities and Affiliated Administrations
28 |     "Diğer"                                    # Other
29 | ]
30 | 
31 | # Decision Subject Categories (Web Karar Konusu) - Shortened for token efficiency
32 | WebKararKonusuEnum = Literal[
33 |     "ALL",                                          # All subjects
34 |     "Harcırah Mevzuatı",                           # Travel Allowance Legislation
35 |     "İhale Mevzuatı",                              # Procurement Legislation
36 |     "İş Mevzuatı",                                 # Labor Legislation
37 |     "Personel Mevzuatı",                           # Personnel Legislation
38 |     "Sorumluluk ve Yargılama Usulleri",            # Liability and Trial Procedures
39 |     "Vergi Resmi Harç ve Diğer Gelirler",         # Tax, Official Fee and Other Revenue
40 |     "Çeşitli Konular"                              # Various Topics
41 | ]
42 | 
43 | # Mapping from shortened enum values to full API values
44 | WEB_KARAR_KONUSU_MAPPING = {
45 |     "ALL": "ALL",
46 |     "Harcırah Mevzuatı": "Harcırah Mevzuatı ile İlgili Kararlar",
47 |     "İhale Mevzuatı": "İhale Mevzuatı ile İlgili Kararlar",
48 |     "İş Mevzuatı": "İş Mevzuatı ile İlgili Kararlar",
49 |     "Personel Mevzuatı": "Personel Mevzuatı ile İlgili Kararlar",
50 |     "Sorumluluk ve Yargılama Usulleri": "Sorumluluk ve Yargılama Usulleri ile İlgili Kararlar",
51 |     "Vergi Resmi Harç ve Diğer Gelirler": "Vergi Resmi Harç ve Diğer Gelirlerle İlgili Kararlar",
52 |     "Çeşitli Konular": "Çeşitli Konuları İlgilendiren Kararlar"
53 | }
54 | 
55 | # Year ranges for different endpoints
56 | GENEL_KURUL_YEARS = [str(year) for year in range(2006, 2025)]  # 2006-2024
57 | TEMYIZ_KURULU_YEARS = [str(year) for year in range(1993, 2023)]  # 1993-2022
58 | DAIRE_YEARS = [str(year) for year in range(2012, 2026)]  # 2012-2025
59 | 
60 | # Account years for Temyiz Kurulu and Daire endpoints
61 | HESAP_YILLARI = [str(year) for year in range(1993, 2024)]  # 1993-2023
```

--------------------------------------------------------------------------------
/kvkk_mcp_module/models.py:
--------------------------------------------------------------------------------

```python
 1 | # kvkk_mcp_module/models.py
 2 | 
 3 | from pydantic import BaseModel, Field, HttpUrl
 4 | from typing import List, Optional, Any
 5 | 
 6 | class KvkkSearchRequest(BaseModel):
 7 |     """Model for KVKK (Personal Data Protection Authority) search request via Brave API."""
 8 |     keywords: str = Field(..., description="""
 9 |         Keywords to search for in KVKK decisions. 
10 |         The search will automatically include 'site:kvkk.gov.tr "karar özeti"' to target KVKK decision summaries.
11 |         Examples: "açık rıza", "veri güvenliği", "kişisel veri işleme"
12 |     """)
13 |     page: int = Field(1, ge=1, le=50, description="Page number for search results (1-50).")
14 |     pageSize: int = Field(10, ge=1, le=10, description="Number of results per page (1-10).")
15 | 
16 | class KvkkDecisionSummary(BaseModel):
17 |     """Model for a single KVKK decision summary from Brave search results."""
18 |     title: Optional[str] = Field(None, description="Decision title from search results.")
19 |     url: Optional[HttpUrl] = Field(None, description="URL to the KVKK decision page.")
20 |     description: Optional[str] = Field(None, description="Brief description or snippet from search results.")
21 |     decision_id: Optional[str] = Field(None, description="Value")
22 |     publication_date: Optional[str] = Field(None, description="Value")
23 |     decision_number: Optional[str] = Field(None, description="Value")
24 | 
25 | class KvkkSearchResult(BaseModel):
26 |     """Model for the overall search result for KVKK decisions."""
27 |     decisions: List[KvkkDecisionSummary] = Field(default_factory=list, description="List of KVKK decisions found.")
28 |     total_results: Optional[int] = Field(None, description="Value")
29 |     page: int = Field(1, description="Current page number of results.")
30 |     pageSize: int = Field(10, description="Number of results per page.")
31 |     query: Optional[str] = Field(None, description="The actual search query sent to Brave API.")
32 | 
33 | class KvkkDocumentMarkdown(BaseModel):
34 |     """Model for KVKK decision document content converted to paginated Markdown."""
35 |     source_url: HttpUrl = Field(description="URL of the original KVKK decision page.")
36 |     title: Optional[str] = Field(None, description="Title of the KVKK decision.")
37 |     decision_date: Optional[str] = Field(None, description="Decision date (Karar Tarihi).")
38 |     decision_number: Optional[str] = Field(None, description="Decision number (Karar No).")
39 |     subject_summary: Optional[str] = Field(None, description="Subject summary (Konu Özeti).")
40 |     markdown_chunk: Optional[str] = Field(None, description="A 5,000 character chunk of the Markdown content.")
41 |     current_page: int = Field(description="The current page number of the markdown chunk (1-indexed).")
42 |     total_pages: int = Field(description="Total number of pages for the full markdown content.")
43 |     is_paginated: bool = Field(description="True if the full markdown content is split into multiple pages.")
44 |     error_message: Optional[str] = Field(None, description="Value")
45 |     
46 |     class Config:
47 |         json_encoders = {
48 |             HttpUrl: str
49 |         }
```

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

```python
 1 | # kvkk_mcp_module/models.py
 2 | 
 3 | from pydantic import BaseModel, Field, HttpUrl
 4 | from typing import List, Optional, Any
 5 | 
 6 | class KvkkSearchRequest(BaseModel):
 7 |     """Model for KVKK (Personal Data Protection Authority) search request via Brave API."""
 8 |     keywords: str = Field(..., description="""
 9 |         Keywords to search for in KVKK decisions. 
10 |         The search will automatically include 'site:kvkk.gov.tr "karar özeti"' to target KVKK decision summaries.
11 |         Examples: "açık rıza", "veri güvenliği", "kişisel veri işleme"
12 |     """)
13 |     page: int = Field(1, ge=1, le=50, description="Page number for search results (1-50).")
14 |     pageSize: int = Field(10, ge=1, le=10, description="Number of results per page (1-10).")
15 | 
16 | class KvkkDecisionSummary(BaseModel):
17 |     """Model for a single KVKK decision summary from Brave search results."""
18 |     title: Optional[str] = Field(None, description="Decision title from search results.")
19 |     url: Optional[HttpUrl] = Field(None, description="URL to the KVKK decision page.")
20 |     description: Optional[str] = Field(None, description="Brief description or snippet from search results.")
21 |     decision_id: Optional[str] = Field(None, description="Value")
22 |     publication_date: Optional[str] = Field(None, description="Value")
23 |     decision_number: Optional[str] = Field(None, description="Value")
24 | 
25 | class KvkkSearchResult(BaseModel):
26 |     """Model for the overall search result for KVKK decisions."""
27 |     decisions: List[KvkkDecisionSummary] = Field(default_factory=list, description="List of KVKK decisions found.")
28 |     total_results: Optional[int] = Field(None, description="Value")
29 |     page: int = Field(1, description="Current page number of results.")
30 |     pageSize: int = Field(10, description="Number of results per page.")
31 |     query: Optional[str] = Field(None, description="The actual search query sent to Brave API.")
32 | 
33 | class KvkkDocumentMarkdown(BaseModel):
34 |     """Model for KVKK decision document content converted to paginated Markdown."""
35 |     source_url: HttpUrl = Field(description="URL of the original KVKK decision page.")
36 |     title: Optional[str] = Field(None, description="Title of the KVKK decision.")
37 |     decision_date: Optional[str] = Field(None, description="Decision date (Karar Tarihi).")
38 |     decision_number: Optional[str] = Field(None, description="Decision number (Karar No).")
39 |     subject_summary: Optional[str] = Field(None, description="Subject summary (Konu Özeti).")
40 |     markdown_chunk: Optional[str] = Field(None, description="A 5,000 character chunk of the Markdown content.")
41 |     current_page: int = Field(description="The current page number of the markdown chunk (1-indexed).")
42 |     total_pages: int = Field(description="Total number of pages for the full markdown content.")
43 |     is_paginated: bool = Field(description="True if the full markdown content is split into multiple pages.")
44 |     error_message: Optional[str] = Field(None, description="Value")
45 |     
46 |     class Config:
47 |         json_encoders = {
48 |             HttpUrl: str
49 |         }
```

--------------------------------------------------------------------------------
/run_asgi.py:
--------------------------------------------------------------------------------

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