This is page 11 of 11. Use http://codebase.md/saidsurucu/yargi-mcp?lines=true&page={x} to view the full context.
# Directory Structure
```
├── __main__.py
├── .dockerignore
├── .env.example
├── .gitattributes
├── .github
│ └── workflows
│ └── publish.yml
├── .gitignore
├── .serena
│ ├── .gitignore
│ └── project.yml
├── 5ire-settings.png
├── analyze_kik_hash_generation.py
├── anayasa_mcp_module
│ ├── __init__.py
│ ├── bireysel_client.py
│ ├── client.py
│ ├── models.py
│ └── unified_client.py
├── asgi_app.py
├── bddk_mcp_module
│ ├── __init__.py
│ ├── client.py
│ └── models.py
├── bedesten_mcp_module
│ ├── __init__.py
│ ├── client.py
│ ├── enums.py
│ └── models.py
├── check_response_format.py
├── CLAUDE.md
├── danistay_mcp_module
│ ├── __init__.py
│ ├── client.py
│ └── models.py
├── docker-compose.yml
├── Dockerfile
├── docs
│ └── DEPLOYMENT.md
├── emsal_mcp_module
│ ├── __init__.py
│ ├── client.py
│ └── models.py
├── example_fastapi_app.py
├── fly-no-auth.toml
├── fly.toml
├── kik_mcp_module
│ ├── __init__.py
│ ├── client_v2.py
│ ├── client.py
│ ├── models_v2.py
│ └── models.py
├── kvkk_mcp_module
│ ├── __init__.py
│ ├── client.py
│ └── models.py
├── LICENSE
├── mcp_auth
│ ├── __init__.py
│ ├── clerk_config.py
│ ├── middleware.py
│ ├── oauth.py
│ ├── policy.py
│ └── storage.py
├── mcp_auth_factory.py
├── mcp_auth_http_adapter.py
├── mcp_auth_http_simple.py
├── mcp_server_main.py
├── nginx.conf
├── ornek.png
├── Procfile
├── pyproject.toml
├── railway.json
├── README.md
├── redis_session_store.py
├── rekabet_mcp_module
│ ├── __init__.py
│ ├── client.py
│ └── models.py
├── requirements.txt
├── run_asgi.py
├── saidsurucu-yargi-mcp-f5fa007
│ ├── __main__.py
│ ├── .dockerignore
│ ├── .env.example
│ ├── .gitattributes
│ ├── .github
│ │ └── workflows
│ │ └── publish.yml
│ ├── .gitignore
│ ├── 5ire-settings.png
│ ├── anayasa_mcp_module
│ │ ├── __init__.py
│ │ ├── bireysel_client.py
│ │ ├── client.py
│ │ ├── models.py
│ │ └── unified_client.py
│ ├── asgi_app.py
│ ├── bddk_mcp_module
│ │ ├── __init__.py
│ │ ├── client.py
│ │ └── models.py
│ ├── bedesten_mcp_module
│ │ ├── __init__.py
│ │ ├── client.py
│ │ ├── enums.py
│ │ └── models.py
│ ├── check_response_format.py
│ ├── danistay_mcp_module
│ │ ├── __init__.py
│ │ ├── client.py
│ │ └── models.py
│ ├── docker-compose.yml
│ ├── Dockerfile
│ ├── docs
│ │ └── DEPLOYMENT.md
│ ├── emsal_mcp_module
│ │ ├── __init__.py
│ │ ├── client.py
│ │ └── models.py
│ ├── example_fastapi_app.py
│ ├── kik_mcp_module
│ │ ├── __init__.py
│ │ ├── client.py
│ │ └── models.py
│ ├── kvkk_mcp_module
│ │ ├── __init__.py
│ │ ├── client.py
│ │ └── models.py
│ ├── LICENSE
│ ├── mcp_auth
│ │ ├── __init__.py
│ │ ├── clerk_config.py
│ │ ├── middleware.py
│ │ ├── oauth.py
│ │ ├── policy.py
│ │ └── storage.py
│ ├── mcp_auth_factory.py
│ ├── mcp_auth_http_adapter.py
│ ├── mcp_auth_http_simple.py
│ ├── mcp_server_main.py
│ ├── nginx.conf
│ ├── ornek.png
│ ├── Procfile
│ ├── pyproject.toml
│ ├── railway.json
│ ├── README.md
│ ├── redis_session_store.py
│ ├── rekabet_mcp_module
│ │ ├── __init__.py
│ │ ├── client.py
│ │ └── models.py
│ ├── run_asgi.py
│ ├── sayistay_mcp_module
│ │ ├── __init__.py
│ │ ├── client.py
│ │ ├── enums.py
│ │ ├── models.py
│ │ └── unified_client.py
│ ├── starlette_app.py
│ ├── stripe_webhook.py
│ ├── uyusmazlik_mcp_module
│ │ ├── __init__.py
│ │ ├── client.py
│ │ └── models.py
│ └── yargitay_mcp_module
│ ├── __init__.py
│ ├── client.py
│ └── models.py
├── sayistay_mcp_module
│ ├── __init__.py
│ ├── client.py
│ ├── enums.py
│ ├── models.py
│ └── unified_client.py
├── starlette_app.py
├── stripe_webhook.py
├── uv.lock
├── uyusmazlik_mcp_module
│ ├── __init__.py
│ ├── client.py
│ └── models.py
└── yargitay_mcp_module
├── __init__.py
├── client.py
└── models.py
```
# Files
--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/mcp_server_main.py:
--------------------------------------------------------------------------------
```python
1 | # mcp_server_main.py
2 | import asyncio
3 | import atexit
4 | import logging
5 | import os
6 | import httpx
7 | import json
8 | import time
9 | from collections import defaultdict
10 | from pydantic import HttpUrl, Field
11 | from typing import Optional, Dict, List, Literal, Any, Union
12 | import urllib.parse
13 | import tiktoken
14 | from fastmcp.server.middleware import Middleware, MiddlewareContext
15 | from fastmcp.server.dependencies import get_access_token, AccessToken
16 | from fastmcp import Context
17 |
18 | # Use standard exception for tool errors
19 | class ToolError(Exception):
20 | """Tool execution error"""
21 | pass
22 |
23 | # --- Logging Configuration Start ---
24 | LOG_DIRECTORY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logs")
25 | if not os.path.exists(LOG_DIRECTORY):
26 | os.makedirs(LOG_DIRECTORY)
27 | LOG_FILE_PATH = os.path.join(LOG_DIRECTORY, "mcp_server.log")
28 |
29 | root_logger = logging.getLogger()
30 | root_logger.setLevel(logging.DEBUG)
31 |
32 | log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(threadName)s - %(message)s')
33 |
34 | file_handler = logging.FileHandler(LOG_FILE_PATH, mode='a', encoding='utf-8')
35 | file_handler.setFormatter(log_formatter)
36 | file_handler.setLevel(logging.DEBUG)
37 | root_logger.addHandler(file_handler)
38 |
39 | console_handler = logging.StreamHandler()
40 | console_handler.setFormatter(log_formatter)
41 | console_handler.setLevel(logging.INFO)
42 | root_logger.addHandler(console_handler)
43 |
44 | logger = logging.getLogger(__name__)
45 | # --- Logging Configuration End ---
46 |
47 | # --- Token Counting Middleware ---
48 | class TokenCountingMiddleware(Middleware):
49 | """Middleware for counting input/output tokens using tiktoken."""
50 |
51 | def __init__(self, model: str = "cl100k_base"):
52 | """Initialize token counting middleware.
53 |
54 | Args:
55 | model: Tiktoken model name (cl100k_base for GPT-4/Claude compatibility)
56 | """
57 | self.encoder = tiktoken.get_encoding(model)
58 | self.model = model
59 | self.token_stats = defaultdict(lambda: {"input": 0, "output": 0, "calls": 0})
60 | self.logger = logging.getLogger("token_counter")
61 |
62 | # Create separate log file for token metrics
63 | token_log_path = os.path.join(LOG_DIRECTORY, "token_metrics.log")
64 | token_handler = logging.FileHandler(token_log_path, mode='a', encoding='utf-8')
65 | token_formatter = logging.Formatter('%(asctime)s - %(message)s')
66 | token_handler.setFormatter(token_formatter)
67 | token_handler.setLevel(logging.INFO)
68 | self.logger.addHandler(token_handler)
69 | self.logger.setLevel(logging.INFO)
70 |
71 | def count_tokens(self, text: str) -> int:
72 | """Count tokens in text using tiktoken."""
73 | if not text:
74 | return 0
75 | try:
76 | return len(self.encoder.encode(str(text)))
77 | except Exception as e:
78 | logger.warning(f"Token counting failed: {e}")
79 | return 0
80 |
81 | def extract_text_content(self, data: Any) -> str:
82 | """Extract text content from various data types."""
83 | if isinstance(data, str):
84 | return data
85 | elif isinstance(data, dict):
86 | # Extract text from common response fields
87 | text_parts = []
88 | for key, value in data.items():
89 | if isinstance(value, str):
90 | text_parts.append(value)
91 | elif isinstance(value, list):
92 | for item in value:
93 | if isinstance(item, str):
94 | text_parts.append(item)
95 | elif isinstance(item, dict) and 'text' in item:
96 | text_parts.append(str(item['text']))
97 | return ' '.join(text_parts)
98 | elif isinstance(data, list):
99 | text_parts = []
100 | for item in data:
101 | text_parts.append(self.extract_text_content(item))
102 | return ' '.join(text_parts)
103 | else:
104 | return str(data)
105 |
106 | def log_token_usage(self, operation: str, input_tokens: int, output_tokens: int,
107 | tool_name: str = None, duration_ms: float = None):
108 | """Log token usage with structured format."""
109 | log_data = {
110 | "operation": operation,
111 | "tool_name": tool_name,
112 | "input_tokens": input_tokens,
113 | "output_tokens": output_tokens,
114 | "total_tokens": input_tokens + output_tokens,
115 | "duration_ms": duration_ms,
116 | "timestamp": time.time()
117 | }
118 |
119 | # Update statistics
120 | key = tool_name if tool_name else operation
121 | self.token_stats[key]["input"] += input_tokens
122 | self.token_stats[key]["output"] += output_tokens
123 | self.token_stats[key]["calls"] += 1
124 |
125 | # Log as JSON for easy parsing
126 | self.logger.info(json.dumps(log_data))
127 |
128 | # Also log human-readable format to main logger
129 | logger.info(f"Token Usage - {operation}" +
130 | (f" ({tool_name})" if tool_name else "") +
131 | f": {input_tokens} in + {output_tokens} out = {input_tokens + output_tokens} total")
132 |
133 | async def on_call_tool(self, context: MiddlewareContext, call_next):
134 | """Count tokens for tool calls."""
135 | start_time = time.perf_counter()
136 |
137 | # Extract tool name and arguments
138 | tool_name = getattr(context.message, 'name', 'unknown_tool')
139 | tool_args = getattr(context.message, 'arguments', {})
140 |
141 | # Count input tokens (tool arguments)
142 | input_text = self.extract_text_content(tool_args)
143 | input_tokens = self.count_tokens(input_text)
144 |
145 | try:
146 | # Execute the tool
147 | result = await call_next(context)
148 |
149 | # Count output tokens (tool result)
150 | output_text = self.extract_text_content(result)
151 | output_tokens = self.count_tokens(output_text)
152 |
153 | # Calculate duration
154 | duration_ms = (time.perf_counter() - start_time) * 1000
155 |
156 | # Log token usage
157 | self.log_token_usage("tool_call", input_tokens, output_tokens,
158 | tool_name, duration_ms)
159 |
160 | return result
161 |
162 | except Exception as e:
163 | duration_ms = (time.perf_counter() - start_time) * 1000
164 | self.log_token_usage("tool_call_error", input_tokens, 0,
165 | tool_name, duration_ms)
166 | raise
167 |
168 | async def on_read_resource(self, context: MiddlewareContext, call_next):
169 | """Count tokens for resource reads."""
170 | start_time = time.perf_counter()
171 |
172 | # Extract resource URI
173 | resource_uri = getattr(context.message, 'uri', 'unknown_resource')
174 |
175 | try:
176 | # Execute the resource read
177 | result = await call_next(context)
178 |
179 | # Count output tokens (resource content)
180 | output_text = self.extract_text_content(result)
181 | output_tokens = self.count_tokens(output_text)
182 |
183 | # Calculate duration
184 | duration_ms = (time.perf_counter() - start_time) * 1000
185 |
186 | # Log token usage (no input tokens for resource reads)
187 | self.log_token_usage("resource_read", 0, output_tokens,
188 | resource_uri, duration_ms)
189 |
190 | return result
191 |
192 | except Exception as e:
193 | duration_ms = (time.perf_counter() - start_time) * 1000
194 | self.log_token_usage("resource_read_error", 0, 0,
195 | resource_uri, duration_ms)
196 | raise
197 |
198 | async def on_get_prompt(self, context: MiddlewareContext, call_next):
199 | """Count tokens for prompt retrievals."""
200 | start_time = time.perf_counter()
201 |
202 | # Extract prompt name
203 | prompt_name = getattr(context.message, 'name', 'unknown_prompt')
204 |
205 | try:
206 | # Execute the prompt retrieval
207 | result = await call_next(context)
208 |
209 | # Count output tokens (prompt content)
210 | output_text = self.extract_text_content(result)
211 | output_tokens = self.count_tokens(output_text)
212 |
213 | # Calculate duration
214 | duration_ms = (time.perf_counter() - start_time) * 1000
215 |
216 | # Log token usage
217 | self.log_token_usage("prompt_get", 0, output_tokens,
218 | prompt_name, duration_ms)
219 |
220 | return result
221 |
222 | except Exception as e:
223 | duration_ms = (time.perf_counter() - start_time) * 1000
224 | self.log_token_usage("prompt_get_error", 0, 0,
225 | prompt_name, duration_ms)
226 | raise
227 |
228 | def get_token_stats(self) -> Dict[str, Any]:
229 | """Get current token usage statistics."""
230 | return dict(self.token_stats)
231 |
232 | def reset_token_stats(self):
233 | """Reset token usage statistics."""
234 | self.token_stats.clear()
235 |
236 | # --- End Token Counting Middleware ---
237 |
238 | # Create FastMCP app directly without authentication wrapper
239 | from fastmcp import FastMCP
240 |
241 | def create_app(auth=None):
242 | """Create FastMCP app with standard capabilities and optional auth."""
243 | global app
244 | if auth:
245 | # Set auth on existing app instead of creating new one
246 | app.auth = auth
247 | app.name = "Yargı MCP Server"
248 | logger.info("MCP server created with Bearer authentication enabled")
249 | else:
250 | # Update placeholder app name only
251 | app.name = "Yargı MCP Server"
252 | logger.info("MCP server created with standard capabilities (FastMCP handles tools.listChanged automatically)")
253 |
254 | # Add token counting middleware
255 | token_counter = TokenCountingMiddleware()
256 | app.add_middleware(token_counter)
257 | logger.info("Token counting middleware added to MCP server")
258 |
259 | return app
260 |
261 | # --- Module Imports ---
262 | from yargitay_mcp_module.client import YargitayOfficialApiClient
263 | from yargitay_mcp_module.models import (
264 | YargitayDetailedSearchRequest, YargitayDocumentMarkdown, CompactYargitaySearchResult,
265 | YargitayBirimEnum, CleanYargitayDecisionEntry
266 | )
267 | from bedesten_mcp_module.client import BedestenApiClient
268 | from bedesten_mcp_module.models import (
269 | BedestenSearchRequest, BedestenSearchData,
270 | BedestenDocumentMarkdown, BedestenCourtTypeEnum
271 | )
272 | from bedesten_mcp_module.enums import BirimAdiEnum
273 | from danistay_mcp_module.client import DanistayApiClient
274 | from danistay_mcp_module.models import (
275 | DanistayKeywordSearchRequest, DanistayDetailedSearchRequest,
276 | DanistayDocumentMarkdown, CompactDanistaySearchResult
277 | )
278 | from emsal_mcp_module.client import EmsalApiClient
279 | from emsal_mcp_module.models import (
280 | EmsalSearchRequest, EmsalDocumentMarkdown, CompactEmsalSearchResult
281 | )
282 | from uyusmazlik_mcp_module.client import UyusmazlikApiClient
283 | from uyusmazlik_mcp_module.models import (
284 | UyusmazlikSearchRequest, UyusmazlikSearchResponse, UyusmazlikDocumentMarkdown,
285 | UyusmazlikBolumEnum, UyusmazlikTuruEnum, UyusmazlikKararSonucuEnum
286 | )
287 | from anayasa_mcp_module.client import AnayasaMahkemesiApiClient
288 | from anayasa_mcp_module.bireysel_client import AnayasaBireyselBasvuruApiClient
289 | from anayasa_mcp_module.unified_client import AnayasaUnifiedClient
290 | from anayasa_mcp_module.models import (
291 | AnayasaNormDenetimiSearchRequest,
292 | AnayasaSearchResult,
293 | AnayasaDocumentMarkdown,
294 | AnayasaBireyselReportSearchRequest,
295 | AnayasaBireyselReportSearchResult,
296 | AnayasaBireyselBasvuruDocumentMarkdown,
297 | AnayasaUnifiedSearchRequest,
298 | AnayasaUnifiedSearchResult,
299 | AnayasaUnifiedDocumentMarkdown,
300 | # Removed enum imports - now using Literal strings in models
301 | )
302 | # KIK Module Imports
303 | from kik_mcp_module.client import KikApiClient
304 | from kik_mcp_module.models import (
305 | KikKararTipi,
306 | KikSearchRequest,
307 | KikSearchResult,
308 | KikDocumentMarkdown
309 | )
310 |
311 | from rekabet_mcp_module.client import RekabetKurumuApiClient
312 | from rekabet_mcp_module.models import (
313 | RekabetKurumuSearchRequest,
314 | RekabetSearchResult,
315 | RekabetDocument,
316 | RekabetKararTuruGuidEnum
317 | )
318 |
319 | from sayistay_mcp_module.client import SayistayApiClient
320 | from sayistay_mcp_module.models import (
321 | GenelKurulSearchRequest, GenelKurulSearchResponse,
322 | TemyizKuruluSearchRequest, TemyizKuruluSearchResponse,
323 | DaireSearchRequest, DaireSearchResponse,
324 | SayistayDocumentMarkdown,
325 | SayistayUnifiedSearchRequest, SayistayUnifiedSearchResult,
326 | SayistayUnifiedDocumentMarkdown
327 | )
328 | from sayistay_mcp_module.enums import DaireEnum, KamuIdaresiTuruEnum, WebKararKonusuEnum
329 | from sayistay_mcp_module.unified_client import SayistayUnifiedClient
330 |
331 | # KVKK Module Imports
332 | from kvkk_mcp_module.client import KvkkApiClient
333 | from kvkk_mcp_module.models import (
334 | KvkkSearchRequest,
335 | KvkkSearchResult,
336 | KvkkDocumentMarkdown
337 | )
338 |
339 | # BDDK Module Imports
340 | from bddk_mcp_module.client import BddkApiClient
341 | from bddk_mcp_module.models import (
342 | BddkSearchRequest,
343 | BddkSearchResult,
344 | BddkDocumentMarkdown
345 | )
346 |
347 |
348 | # Create a placeholder app that will be properly initialized after tools are defined
349 | from fastmcp import FastMCP
350 |
351 | # Placeholder app for decorators - will be replaced in create_app() after all tools are defined
352 | app = FastMCP("Yargı MCP Server Placeholder")
353 |
354 | # --- Tool Documentation Resources ---
355 | @app.resource("docs://tools/yargitay")
356 | async def get_yargitay_tools_documentation() -> str:
357 | """Get document content as Markdown."""
358 | return """
359 | # Yargıtay (Court of Cassation) Tools Documentation
360 |
361 | ## Court Hierarchy and Position
362 | Yargıtay is Turkey's highest civil and criminal court. It serves as the final appellate authority and establishes legal precedents for civil and criminal cases.
363 |
364 | **Dual API System:**
365 | - **Primary API (search_yargitay_detailed)**: Official karararama.yargitay.gov.tr
366 | - **Bedesten API (search_bedesten_unified)**: Unified access to bedesten.adalet.gov.tr (see docs://tools/bedesten_unified)
367 |
368 | ## Chamber Filtering Options (52 Total)
369 |
370 | ### Civil Chambers (Hukuk Daireleri)
371 | - **Civil General Assembly** (Hukuk Genel Kurulu)
372 | - **1st Civil Chamber** through **23rd Civil Chamber** (23 civil chambers)
373 | - **Civil Chambers Presidents Board** (Hukuk Daireleri Başkanlar Kurulu)
374 |
375 | ### Criminal Chambers (Ceza Daireleri)
376 | - **Criminal General Assembly** (Ceza Genel Kurulu)
377 | - **1st Criminal Chamber** through **23rd Criminal Chamber** (23 criminal chambers)
378 | - **Criminal Chambers Presidents Board** (Ceza Daireleri Başkanlar Kurulu)
379 |
380 | ### General Assemblies
381 | - **Grand General Assembly** (Büyük Genel Kurulu)
382 |
383 | ## Search Techniques
384 |
385 | ### Primary API (search_yargitay_detailed)
386 | ```
387 | Simple search: "mülkiyet"
388 | AND operator: "mülkiyet AND tapu"
389 | OR operator: "mülkiyet OR tapu"
390 | NOT operator: "mülkiyet NOT satış"
391 | Wildcard: "mülk*"
392 | Exact phrase: "\"mülkiyet hakkı\""
393 | ```
394 |
395 | ### Bedesten API (search_bedesten_unified)
396 | For detailed usage, see docs://tools/bedesten_unified
397 | ```
398 | Regular search: phrase="mülkiyet kararı", court_types=["YARGITAYKARARI"]
399 | Exact phrase: phrase="\"mülkiyet kararı\"", court_types=["YARGITAYKARARI"]
400 | Date filtering: kararTarihiStart="2024-01-01T00:00:00.000Z"
401 | ```
402 |
403 | ## Usage Scenarios
404 | - **Precedent research**: Supreme court decisions on specific topics
405 | - **Chamber-specific search**: Relevant chambers for specific legal areas
406 | - **Historical analysis**: Decision trends in specific periods
407 | - **Jurisprudence tracking**: Changes in legal opinions
408 |
409 | ## Best Practices
410 | 1. **Use dual APIs**: Try both APIs for maximum coverage
411 | 2. **Chamber filtering**: Select chambers based on relevant legal area
412 | 3. **Exact phrases**: Use "\"term\"" for precise terms in Bedesten API
413 | 4. **Date range**: Focus on last 2-3 years for recent developments
414 |
415 | ## Common Civil Chambers
416 | - **1st Civil**: Property, land registry, liens
417 | - **4th Civil**: Labor law, collective agreements
418 | - **11th Civil**: Insurance, social security
419 | - **15th Civil**: Compensation, tort
420 | - **21st Civil**: Execution and bankruptcy
421 |
422 | ## Common Criminal Chambers
423 | - **1st Criminal**: General criminal offenses
424 | - **8th Criminal**: Economic and commercial crimes
425 | - **12th Criminal**: Official misconduct
426 | """
427 |
428 | @app.resource("docs://tools/danistay")
429 | async def get_danistay_tools_documentation() -> str:
430 | """Get document content as Markdown."""
431 | return """
432 | # Danıştay (Council of State) Tools Documentation
433 |
434 | ## Court Hierarchy and Position
435 | Danıştay is Turkey's highest administrative court. It makes final decisions on administrative acts and actions.
436 |
437 | **Triple API System:**
438 | - **Keyword API (search_danistay_by_keyword)**: AND/OR/NOT logic
439 | - **Detailed API (search_danistay_detailed)**: Comprehensive criteria
440 | - **Bedesten API (search_bedesten_unified)**: Unified access (see docs://tools/bedesten_unified)
441 |
442 | ## Chamber Filtering Options (27 Total)
443 |
444 | ### Main Councils
445 | - **Grand General Assembly** (Büyük Gen.Kur.)
446 | - **Administrative Cases Council** (İdare Dava Daireleri Kurulu)
447 | - **Tax Cases Council** (Vergi Dava Daireleri Kurulu)
448 | - **Precedents Unification Council** (İçtihatları Birleştirme Kurulu)
449 |
450 | ### Chambers (1-17)
451 | - **1st Chamber** through **17th Chamber** (Administrative case chambers)
452 |
453 | ### Military Courts
454 | - **Military High Administrative Court** (Askeri Yüksek İdare Mahkemesi)
455 | - **Military High Administrative Court 1st-3rd Chambers**
456 |
457 | ## Search Techniques
458 |
459 | ### Keyword API
460 | ```
461 | AND logic: andKelimeler=["imar", "plan"]
462 | OR logic: orKelimeler=["iptal", "yürütmeyi durdurma"]
463 | NOT logic: notKelimeler=["ceza"]
464 | ```
465 |
466 | ### Detailed API
467 | ```
468 | Chamber selection: daire="3. Daire"
469 | Case year: esasYil="2024"
470 | Decision date: kararTarihiBaslangic="01.01.2024"
471 | Legislation: mevzuatId=123
472 | ```
473 |
474 | ### Bedesten API
475 | ```
476 | Regular: phrase="idari işlem"
477 | Exact: phrase="\"idari işlem\""
478 | Date: kararTarihiStart="2024-01-01T00:00:00.000Z"
479 | ```
480 |
481 | ## Usage Scenarios
482 | - **Administrative law research**: Public administration decisions
483 | - **Tax law**: Financial matters and tax disputes
484 | - **Urban planning law**: City planning and building permits
485 | - **Personnel law**: Civil servant rights
486 |
487 | ## Common Chamber Specializations
488 | - **1st Chamber**: Municipal, urban planning, environment
489 | - **2nd Chamber**: Tax, customs, financial
490 | - **3rd Chamber**: Personnel, personal rights
491 | - **5th Chamber**: Administrative fines
492 | - **8th Chamber**: Higher education, education
493 | - **10th Chamber**: Health, social security
494 |
495 | ## Best Practices
496 | 1. **Triple API**: Use all three APIs for maximum coverage
497 | 2. **Chamber selection**: Choose specialized chambers by subject area
498 | 3. **Mevzuat bağlantısı**: İlgili kanun/tüzükle filtreleme
499 | 4. **Kesin terim**: İdari hukuk terminolojisi için exact search
500 | """
501 |
502 | @app.resource("docs://tools/constitutional_court")
503 | async def get_constitutional_court_tools_documentation() -> str:
504 | """Get document content as Markdown."""
505 | return """
506 | # Anayasa Mahkemesi (Constitutional Court) Tools Documentation
507 |
508 | ## Court Position
509 | Constitutional Court is Turkey's highest judicial body. It has two main functions:
510 |
511 | ### 1. Norm Control (Norm Control)
512 | **Tool**: search_anayasa_norm_denetimi_decisions
513 | - Reviews constitutional compliance of laws and regulations
514 | - Abstract and concrete norm control
515 |
516 | ### 2. Individual Application (Individual Application)
517 | **Tool**: search_anayasa_bireysel_basvuru_report
518 | - Citizens' fundamental rights violation applications
519 | - Turkey's human rights protection mechanism
520 |
521 | ## Norm Control Features
522 |
523 | ### Comprehensive Filtering
524 | - **Application type**: Annulment, Objection, Other
525 | - **Applicant**: President, Parliament, Courts
526 | - **Legislation type**: Law, Decree, Regulation, Rules of procedure
527 | - **Result type**: Annulment, Rejection, Partial annulment
528 |
529 | ### Advanced Search
530 | - **Member names**: Full names of participating justices
531 | - **Rapporteur**: Case rapporteur
532 | - **Dissenting opinion**: Minority opinion, different view
533 | - **Press release**: Important decisions
534 |
535 | ## Bireysel Başvuru Özellikleri
536 |
537 | ### Temel Haklar Kategorileri
538 | - **Yaşam hakkı**: Ölüm olayları, güvenlik
539 | - **Adil yargılanma**: Süre, tarafsızlık, duruşma hakkı
540 | - **İfade özgürlüğü**: Basın, düşünce, akademik özgürlük
541 | - **Din özgürlüğü**: İbadet, vicdan özgürlüğü
542 | - **Mülkiyet hakkı**: Kamulaştırma, tapu
543 | - **Özel hayat**: Gizlilik, aile hayatı
544 |
545 | ### Başvuru Süreci
546 | - **Yurtiçi yollar**: Önce mahkeme kararı gerekli
547 | - **Süre sınırı**: 30 gün (60 gün istisnai)
548 | - **Kabul edilebilirlik**: Ön inceleme kriterleri
549 |
550 | ## Paginated Content (5,000 characters)
551 | Her iki tool da sayfalanmış Markdown döndürür:
552 | - **page_number**: Sayfa numarası (1'den başlar)
553 | - **total_pages**: Toplam sayfa sayısı
554 | - **current_page**: Mevcut sayfa
555 |
556 | ## Usage Scenarios
557 |
558 | ### Norm Denetimi
559 | - **Kanun anayasaya uygunluk**: Yeni çıkan kanunların kontrolü
560 | - **Mahkeme iptali**: Kanunun belirli maddeleri
561 | - **Mevzuat uyum**: Anayasa değişikliği sonrası
562 |
563 | ### Bireysel Başvuru
564 | - **İnsan hakları araştırması**: AİHM öncesi iç hukuk
565 | - **Temel hak ihlalleri**: Sistematik ihlal tespiti
566 | - **Emsal karar**: Benzer davalar için içtihat
567 |
568 | ## Parameter Details
569 | ### search_anayasa_norm_denetimi_decisions
570 | - **keywords_all**: Keywords for AND logic (all must be present)
571 | - **keywords_any**: Keywords for OR logic (any can be present)
572 | - **keywords_exclude**: Keywords to exclude from results
573 | - **period**: Constitutional period - "ALL", "1" (1961 Constitution), "2" (1982 Constitution)
574 | - **case_number_esas**: Case registry number (e.g., '2023/123')
575 | - **decision_number_karar**: Decision number (e.g., '2023/456')
576 | - **first_review_date_start/end**: First review date range (DD/MM/YYYY)
577 | - **decision_date_start/end**: Decision date range (DD/MM/YYYY)
578 | - **application_type**: "ALL", "1" (İptal), "2" (İtiraz), "3" (Diğer)
579 | - **applicant_general_name**: General applicant name
580 | - **applicant_specific_name**: Specific applicant name
581 | - **official_gazette_date_start/end**: Official Gazette date range
582 | - **official_gazette_number_start/end**: Official Gazette number range
583 | - **has_press_release**: "ALL", "0" (No), "1" (Yes)
584 | - **has_dissenting_opinion**: "ALL", "0" (No), "1" (Yes)
585 | - **has_different_reasoning**: "ALL", "0" (No), "1" (Yes)
586 | - **attending_members_names**: List of attending members' exact names
587 | - **rapporteur_name**: Rapporteur's exact name
588 | - **norm_type**: Type of reviewed norm (law, decree, regulation, etc.)
589 | - **norm_id_or_name**: Number or name of the norm
590 | - **norm_article**: Article number of the norm
591 | - **review_outcomes**: List of review outcomes
592 | - **reason_for_final_outcome**: Main reason for decision outcome
593 | - **basis_constitution_article_numbers**: Supporting Constitution article numbers
594 | - **results_per_page**: Results per page (10, 20, 30, 40, 50)
595 | - **page_to_fetch**: Page number to fetch
596 | - **sort_by_criteria**: Sort criteria ('KararTarihi', 'YayinTarihi', 'Toplam')
597 |
598 | ### search_anayasa_bireysel_basvuru_report
599 | - **keywords**: Keywords for AND logic (all must be present)
600 | - **page_to_fetch**: Page number for the report (default: 1)
601 |
602 | ### Document Tools
603 | - **document_url**: URL path or full URL of the decision
604 | - **page_number**: Page number for paginated content (1-indexed, default: 1)
605 |
606 | ## Best Practices
607 | 1. **Norm control önce**: Kanun iptal edilmiş mi kontrol
608 | 2. **Bireysel başvuru ikinci**: Kişisel hak ihlalleri için
609 | 3. **Tarih aralığı**: Anayasa değişiklikleri sonrası dönemler
610 | 4. **Anahtar kelime kombinasyonu**: Temel hak + konu alanı
611 | 5. **Sayfa yönetimi**: Uzun kararlarda sayfa sayfa okuyun
612 | """
613 |
614 | @app.resource("docs://tools/emsal")
615 | async def get_emsal_tools_documentation() -> str:
616 | """Get document content as Markdown."""
617 | return """
618 | # Emsal (UYAP Precedent System) Tools Documentation
619 |
620 | ## System Position
621 | Central precedent decision system providing access to all court decisions through the UYAP system.
622 |
623 | ## Court Options
624 | - **Yargıtay**: First and second instance courts
625 | - **Danıştay**: Administrative court decisions
626 | - **Other**: Regional courts of justice, civil courts
627 |
628 | ## Advanced Filtering Features
629 | - **Court type**: Civil, criminal, administrative
630 | - **Case/Decision number**: File tracking system
631 | - **Date range**: Flexible date selection
632 | - **Content search**: Keyword search within decision text
633 |
634 | ## Usage Scenarios
635 | - **Kapsamlı emsal**: Tüm mahkeme seviyelerinden karar toplama
636 | - **Güncel içtihat**: En son hukuki gelişmeler
637 | - **Cross-reference**: Farklı mahkeme görüşlerini karşılaştırma
638 |
639 | ## Best Practices
640 | 1. **Spesifik terimler**: Hukuki terminoloji kullanın
641 | 2. **Geniş arama**: Önce genel, sonra spesifik
642 | 3. **Tarih stratejisi**: Mevzuat değişiklikleri dikkate alın
643 | 4. **Cross-platform**: Aynı konuyu farklı mahkemelerde arayın
644 | """
645 |
646 | @app.resource("docs://tools/uyusmazlik")
647 | async def get_uyusmazlik_tools_documentation() -> str:
648 | """Get document content as Markdown."""
649 | return """
650 | # Uyuşmazlık Mahkemesi Tools Documentation
651 |
652 | ## Court Position
653 | Adli ve idari yargı arasındaki görev uyuşmazlıklarını çözen özel yetkili mahkeme.
654 |
655 | ## Dispute Types
656 | - **Görev uyuşmazlığı**: Hangi mahkeme bakacak konusunda anlaşmazlık
657 | - **Hüküm uyuşmazlığı**: Çelişkili mahkeme kararları
658 | - **Yetki uyuşmazlığı**: Yerel yetki sorunları
659 |
660 | ## Form-Based Search Criteria
661 | - **Karar türü**: Müspet, menfi, hüküm uyuşmazlığı
662 | - **Taraf mahkemeler**: Adli-idari yargı organları
663 | - **Konu alanı**: Hukuk dalı bazlı filtreleme
664 | - **Tarih aralığı**: Karar tarihi seçimi
665 |
666 | ## Usage Scenarios
667 | - **Yargı türü belirleme**: Hangi mahkemenin yetkili olduğu
668 | - **Çelişkili kararlar**: Farklı mahkeme kararları arasındaki uyuşmazlık
669 | - **Yetki sorunları**: Mahkeme yetkisi tartışmaları
670 |
671 | ## Best Practices
672 | 1. **Net kriterler**: Arama kriterlerini spesifik tutun
673 | 2. **Taraf bilgisi**: Uyuşmazlık taraflarını belirtin
674 | 3. **Konu odaklı**: İlgili hukuk dalını seçin
675 | """
676 |
677 | @app.resource("docs://tools/kik")
678 | async def get_kik_tools_documentation() -> str:
679 | """Get document content as Markdown."""
680 | return """
681 | # KİK (Kamu İhale Kurumu) Tools Documentation
682 |
683 | ## Kurum Konumu
684 | Kamu ihale uyuşmazlıklarının ilk ve son merci çözüm organı. Kamu İhale Kanunu kapsamındaki tüm ihaleler için yetkili.
685 |
686 | ## Decision Types
687 | - **Uyuşmazlık**: İhale süreç itirazları
688 | - **Düzenleyici**: Mevzuat ve uygulama kararları
689 | - **Mahkeme**: Mahkeme kararlarının uygulanması
690 |
691 | ## Filtreleme Seçenekleri
692 | - **Karar numarası**: 2024/UH.II-1766 formatında
693 | - **Tarih aralığı**: Karar tarihi filtreleme
694 | - **İhaleyi yapan idare**: Bakanlık, belediye, hastane, üniversite
695 | - **Başvuru sahibi**: Şirket, firma adı
696 | - **İhale konusu**: Mal, hizmet, yapım işi
697 |
698 | ## Sayfalanmış İçerik Özelliği
699 | 5.000 karakterlik sayfalar halinde Markdown formatında sunulur.
700 |
701 | ## Usage Scenarios
702 | - **İhale hukuku**: Kamu alımları, süreç kuralları
703 | - **Başvuru hazırlığı**: Benzer davalar, emsal kararlar
704 | - **Mevzuat yorumu**: Kamu İhale Kanunu uygulaması
705 | - **İtiraz stratejisi**: Başarılı itiraz örnekleri
706 |
707 | ## İhale Süreç Aşamaları
708 | 1. **İhale öncesi**: İlan, şartname hazırlığı
709 | 2. **İhale aşaması**: Teklif verme, değerlendirme
710 | 3. **İhale sonrası**: Sonuç bildirimi, itirazlar
711 | 4. **Sözleşme**: İmza, uygulama
712 |
713 | ## Parameter Details
714 | ### search_kik_decisions
715 | - **karar_tipi**: Decision type - "rbUyusmazlik" (disputes), "rbDuzenleyici" (regulatory), "rbMahkeme" (court)
716 | - **karar_no**: Decision number (e.g., '2024/UH.II-1766')
717 | - **karar_tarihi_baslangic**: Decision start date (DD.MM.YYYY format)
718 | - **karar_tarihi_bitis**: Decision end date (DD.MM.YYYY format)
719 | - **basvuru_sahibi**: Applicant name/company
720 | - **ihaleyi_yapan_idare**: Procuring entity (ministry, municipality, etc.)
721 | - **basvuru_konusu_ihale**: Tender subject/description
722 | - **karar_metni**: Text search with operators: +word (AND), -word (exclude)
723 | - **yil**: Decision year
724 | - **resmi_gazete_tarihi**: Official Gazette date (DD.MM.YYYY)
725 | - **resmi_gazete_sayisi**: Official Gazette number
726 | - **page**: Results page number
727 |
728 | ### get_kik_document_markdown
729 | - **karar_id**: Base64 encoded decision identifier from search results
730 | - **page_number**: Page number for paginated content (1-indexed, default: 1)
731 |
732 | ## Best Practices
733 | 1. **İhale türü**: Açık, belli istekliler arası, pazarlık
734 | 2. **Süreç aşaması**: Hangi aşamada sorun olduğu
735 | 3. **Hukuki dayanak**: İlgili KİK kanun maddesi
736 | 4. **Sayfa yönetimi**: Uzun kararları bölümler halinde okuyun
737 | """
738 |
739 | @app.resource("docs://tools/rekabet")
740 | async def get_rekabet_tools_documentation() -> str:
741 | """Get document content as Markdown."""
742 | return """
743 | # Rekabet Kurumu (Competition Authority) Tools Documentation
744 |
745 | ## Kurum Konumu
746 | Rekabet hukuku ihlallerini inceleyen ve ceza veren idari otorite. Rekabet Kanunu kapsamında yetkili.
747 |
748 | ## Decision Types
749 | - **Birleşme ve Devralma**: Şirket satın almaları, füzyonlar
750 | - **Rekabet İhlali**: Anlaşma, hakim durum kötüye kullanımı
751 | - **Menfi Tespit ve Muafiyet**: İhlal yok kararları, muafiyetler
752 | - **Özelleştirme**: Kamu şirketleri satışı onayları
753 |
754 | ## Filtreleme Özellikleri
755 | - **PDF metin arama**: Tam metin içinde kelime arama
756 | - **Karar türü**: Spesifik kategori seçimi
757 | - **Tarih aralığı**: 1997'den günümüze karar arşivi
758 | - **Sektör**: Telekomünikasyon, bankacılık, enerji, perakende
759 |
760 | ## Rekabet Hukuku Temel Kavramları
761 | - **Hakim durum**: Pazar gücü
762 | - **Kartel**: Fiyat anlaşması
763 | - **Dikey anlaşmalar**: Tedarikci-bayi ilişkileri
764 | - **Konsantrasyon**: Birleşme işlemleri
765 |
766 | ## Usage Scenarios
767 | - **Antitrust araştırması**: Tekelleşme, kartel soruşturmaları
768 | - **Birleşme incelemesi**: M&A transaction değerlendirmesi
769 | - **Sektör analizi**: Belirli pazarlardaki rekabet durumu
770 | - **Ceza hesaplama**: İhlal cezası örnekleri
771 |
772 | ## Sektörel Uzmanlık Alanları
773 | 1. **Telekomünikasyon**: Operatör rekabeti
774 | 2. **Enerji**: Elektrik, doğalgaz piyasası
775 | 3. **Finans**: Bankacılık, sigorta
776 | 4. **Perakende**: Zincir mağazalar
777 | 5. **İnşaat**: Müteahhitlik sektörü
778 |
779 | ## Parameter Details
780 | ### search_rekabet_kurumu_decisions
781 | - **sayfaAdi**: Search in decision title (Başlık)
782 | - **YayinlanmaTarihi**: Publication date (DD.MM.YYYY format)
783 | - **PdfText**: Search text. For exact phrases use double quotes: "vertical agreement"
784 | - **KararTuru**: Decision type - "Birleşme ve Devralma", "Rekabet İhlali", etc.
785 | - **KararSayisi**: Decision number (Karar Sayısı)
786 | - **KararTarihi**: Decision date (DD.MM.YYYY format)
787 | - **page**: Page number for results list
788 |
789 | ### get_rekabet_kurumu_document
790 | - **karar_id**: GUID from search results
791 | - **page_number**: Requested page for Markdown content (1-indexed, default: 1)
792 |
793 | ## Best Practices
794 | 1. **Sektör odaklı**: İlgili sektörde arama yapın
795 | 2. **Karar türü seçimi**: İhtiyacınıza uygun kategori
796 | 3. **Güncel mevzuat**: Mevzuat değişiklikleri takibi
797 | 4. **Sayfa yönetimi**: Uzun analizleri bölümler halinde
798 | """
799 |
800 | @app.resource("docs://tools/bedesten_unified")
801 | async def get_bedesten_unified_documentation() -> str:
802 | """Get document content as Markdown."""
803 | return """
804 | # Bedesten API Mahkemeleri Tools Documentation
805 |
806 | ## Bedesten API Sistemi
807 | bedesten.adalet.gov.tr üzerinden Türk adalet sistemi hiyerarşisindeki mahkemelere erişim.
808 |
809 | ## Mahkeme Hiyerarşisi Kapsamı
810 |
811 | ### 1. Yerel Hukuk Mahkemeleri (Local Civil Courts)
812 | **Tool**: search_yerel_hukuk_bedesten
813 | - **Konum**: İlk derece mahkemeler
814 | - **Yetki**: Hukuki uyuşmazlıklar (sözleşme, tazminat, mülkiyet)
815 | - **Önem**: Toplumun günlük hukuki sorunları
816 |
817 | ### 2. İstinaf Hukuk Mahkemeleri (Civil Courts of Appeals)
818 | **Tool**: search_istinaf_hukuk_bedesten
819 | - **Konum**: Orta derece (Yerel -> İstinaf -> Yargıtay)
820 | - **Yetki**: Yerel mahkeme kararlarına itiraz
821 | - **Önem**: Temyiz öncesi son kontrol
822 |
823 | ### 3. Kanun Yararına Bozma (KYB)
824 | **Tool**: search_kyb_bedesten
825 | - **Konum**: Olağanüstü kanun yolu
826 | - **Başvuru sahibi**: Cumhuriyet Başsavcılığı
827 | - **Amaç**: Hukuka aykırı kararları düzeltme
828 | - **Özellik**: Sanık aleyhine olsa bile hukuk yararına
829 |
830 | ## Ortak Bedesten API Özellikleri
831 |
832 | ### Tarih Filtreleme (ISO 8601)
833 | ```
834 | Başlangıç: kararTarihiStart="2024-01-01T00:00:00.000Z"
835 | Bitiş: kararTarihiEnd="2024-12-31T23:59:59.999Z"
836 | Tek gün: "2024-06-25T00:00:00.000Z" - "2024-06-25T23:59:59.999Z"
837 | ```
838 |
839 | ### Kesin Cümle Arama
840 | ```
841 | Normal: phrase="sözleşme ihlali" (kelimeler ayrı ayrı)
842 | Kesin: phrase="\"sözleşme ihlali\"" (tam cümle)
843 | ```
844 |
845 | ### Sayfalama
846 | - **pageSize**: 1-100 arası sonuç sayısı
847 | - **pageNumber**: Sayfa numarası (1'den başlar)
848 |
849 | ## Mahkeme Özellikleri
850 |
851 | ### Yerel Hukuk Mahkemeleri
852 | **Yaygın Dava Türleri**:
853 | - Sözleşme ihlali davaları
854 | - Tazminat talepleri
855 | - Mülkiyet uyuşmazlıkları
856 | - Aile hukuku (boşanma, nafaka)
857 | - Ticari uyuşmazlıklar (küçük-orta ölçek)
858 |
859 | **Kullanım Senaryoları**:
860 | - Günlük hukuki sorunlar
861 | - Vatandaş hakları
862 | - Ticaret hukuku temelleri
863 | - İcra takipleri
864 |
865 | ### İstinaf Hukuk Mahkemeleri
866 | **İnceleme Kapsamı**:
867 | - Yerel mahkeme kararlarının kontrolü
868 | - Hukuki ve maddi hata arayışı
869 | - Yeniden yargılama (sınırlı)
870 |
871 | **Kullanım Senaryoları**:
872 | - Temyiz stratejisi gelişitirme
873 | - İstinaf mahkemesi içtihatları
874 | - Yerel-üst mahkeme uyumu analizi
875 |
876 | ### Kanun Yararına Bozma (KYB)
877 | **Başvuru Koşulları**:
878 | - Kesinleşmiş mahkeme kararı
879 | - Hukuka açık aykırılık
880 | - Cumhuriyet Başsavcılığı başvurusu
881 | - Sanık aleyhine sonuç doğurmama
882 |
883 | **Kullanım Senaryoları**:
884 | - Sistematik hukuki hatalar
885 | - İçtihat birliğini sağlama
886 | - Hukuk güvenliği
887 | - Nadir ve özel hukuki durumlar
888 |
889 | ## Arama Stratejileri
890 |
891 | ### Hiyerarşik Arama
892 | ```
893 | 1. Yerel mahkeme -> Gündelik sorunlar
894 | 2. İstinaf -> Kompleks yorumlar
895 | 3. KYB -> İstisnai hukuki durumlar
896 | ```
897 |
898 | ### Kesin Terim Kullanımı
899 | ```
900 | Yerel: "\"sözleşme ihlali\""
901 | İstinaf: "\"temyiz incelemesi\""
902 | KYB: "\"kanun yararına bozma\""
903 | ```
904 |
905 | ### Tarih Stratejisi
906 | - **Son 2 yıl**: Güncel içtihat
907 | - **5-10 yıl**: Yerleşik görüşler
908 | - **Mevzuat değişikliği sonrası**: Yeni uygulamalar
909 |
910 | ## Best Practices
911 | 1. **Hiyerarşi takibi**: Alt mahkemeden üst mahkemeye
912 | 2. **Kesin cümle**: Hukuki terimler için "\"terim\""
913 | 3. **Tarih aralığı**: İlgili mevzuat dönemleri
914 | 4. **Cross-reference**: Aynı konuyu farklı seviyelerde
915 | 5. **Minimal sonuç**: KYB çok nadir, az sonuç beklenir
916 |
917 | ## Document ID Formatı
918 | Tüm Bedesten mahkemeleri documentId döndürür:
919 | - **Format**: Alfanumerik string
920 | - **Kullanım**: get_*_bedesten_document_markdown fonksiyonları
921 | - **İçerik**: HTML/PDF -> Markdown conversion
922 | """
923 |
924 | @app.resource("docs://tools/sayistay")
925 | async def get_sayistay_tools_documentation() -> str:
926 | """Get document content as Markdown."""
927 | return """
928 | # Sayıştay (Court of Accounts) Tools Documentation
929 |
930 | ## Sayıştay'ın Konumu
931 | Türkiye'nin en üst mali denetim organı. Kamu kaynaklarının kullanımını denetler ve mali disiplini sağlar.
932 |
933 | ## Üç Tür Karar Sistemi
934 |
935 | ### 1. Genel Kurul Kararları (Interpretive Rulings)
936 | **Tool**: search_sayistay_genel_kurul
937 | - **İşlev**: Mali mevzuat yorumlama
938 | - **Kapsam**: 2006-2024 yılları arası
939 | - **Özellik**: Bağlayıcı yorumlar
940 |
941 | **Filtreleme Seçenekleri**:
942 | - **Karar numarası**: Spesifik karar arama
943 | - **Tarih aralığı**: Başlangıç-bitiş tarihleri
944 | - **Karar tamamı**: Tam metin arama (400 karakter)
945 |
946 | ### 2. Temyiz Kurulu Kararları (Appeals Board)
947 | **Tool**: search_sayistay_temyiz_kurulu
948 | - **İşlev**: Daire kararlarına itiraz incelemesi
949 | - **8 Daire Filtreleme**: Uzmanlık alanlarına göre
950 |
951 | **Daire Uzmanlaşmaları**:
952 | - **1. Daire**: Genel bütçeli idareler
953 | - **2. Daire**: Mahalli idareler
954 | - **3. Daire**: Sosyal güvenlik kurumları
955 | - **4. Daire**: KİT ve bağlı ortaklıklar
956 | - **5. Daire**: Düzenleyici kuruluşlar
957 | - **6. Daire**: Vakıflar, dernekler
958 | - **7. Daire**: Üniversiteler, eğitim
959 | - **8. Daire**: Yatırım projeleri
960 |
961 | **Filtreleme Seçenekleri**:
962 | - **İdare türü**: Bakanlık, belediye, üniversite, KİT
963 | - **Temyiz karar**: Tam metin arama
964 | - **Konu sınıflandırması**: Harcama, gelir, taşınır-taşınmaz
965 |
966 | ### 3. Daire Kararları (Chamber Decisions)
967 | **Tool**: search_sayistay_daire
968 | - **İşlev**: İlk derece denetim bulguları
969 | - **8 Daire**: Aynı uzmanlaşma alanları
970 |
971 | **Filtreleme Seçenekleri**:
972 | - **Yargılama dairesi**: 1-8 arası daire seçimi
973 | - **Hesap yılı**: Mali yıl bazlı
974 | - **Web karar metni**: İçerik arama
975 |
976 | ## Ortak Özellikler
977 |
978 | ### Sayfalanmış Markdown
979 | Tüm Sayıştay belgeleri sayfalanmış format:
980 | - **5.000 karakter** per sayfa
981 | - **page_number**: Sayfa numarası
982 | - **total_pages**: Toplam sayfa
983 |
984 | ### Tarih Aralığı Desteği
985 | - **Genel Kurul**: 2006-2024 (18 yıl)
986 | - **Temyiz/Daire**: Mevcut veriler üzerinde
987 |
988 | ## Usage Scenarios
989 |
990 | ### Mali Mevzuat Araştırması
991 | ```
992 | Genel Kurul -> Hukuki yorum
993 | Temyiz -> Uygulama detayları
994 | Daire -> Spesifik örnekler
995 | ```
996 |
997 | ### Kamu Mali Yönetimi
998 | - **Bütçe uygulama**: Harcama usulleri
999 | - **İhale süreçleri**: Kamu alımları denetimi
1000 | - **Personel giderleri**: Özlük hakları mali boyutu
1001 | - **Yatırım projeleri**: Büyük ölçekli projeler
1002 |
1003 | ### Kurumsal Denetim
1004 | - **KİT yönetimi**: Kamu iktisadi teşebbüsleri
1005 | - **Belediye maliyesi**: Yerel yönetim harcamaları
1006 | - **Üniversite bütçesi**: Yükseköğretim mali yönetimi
1007 | - **Sosyal güvenlik**: SGK, Bağ-Kur mali işlemleri
1008 |
1009 | ## Arama Stratejileri
1010 |
1011 | ### Hiyerarşik Yaklaşım
1012 | 1. **Genel Kurul**: Konunun hukuki çerçevesi
1013 | 2. **Temyiz**: Tartışmalı uygulamalar
1014 | 3. **Daire**: Günlük uygulama örnekleri
1015 |
1016 | ### Daire Bazlı Strateji
1017 | ```
1018 | Mali konu -> İlgili daire seçimi -> Derinlemesine arama
1019 | Örnek: KİT mali sorunları -> 4. Daire
1020 | ```
1021 |
1022 | ### Tarih Odaklı Strateji
1023 | - **Son 2 yıl**: Güncel uygulamalar
1024 | - **5 yıl**: Yerleşik görüşler
1025 | - **2006-2024**: Tarihsel gelişim
1026 |
1027 | ## Best Practices
1028 | 1. **Daire uzmanlaşması**: İlgili kuruma uygun daire
1029 | 2. **Hiyerarşik sıralama**: Genel Kurul -> Temyiz -> Daire
1030 | 3. **Mali dönem**: Bütçe yılları bazında arama
1031 | 4. **Teknik terimler**: Mali mevzuat terminolojisi
1032 | 5. **Cross-reference**: Farklı seviyelerden görüş karşılaştırma
1033 |
1034 | ## İdare Türü Kodları
1035 | - **1**: Genel bütçeli
1036 | - **2**: Özel bütçeli
1037 | - **3**: Düzenleyici kuruluşlar
1038 | - **4**: Mahalli idareler
1039 | - **5**: Sosyal güvenlik
1040 | - **6**: KİT
1041 | - **7**: Vakıf/dernek
1042 | - **8**: Diğer kamu kuruluşları
1043 | """
1044 |
1045 | @app.resource("docs://tools/kvkk")
1046 | async def get_kvkk_tools_documentation() -> str:
1047 | """Get document content as Markdown."""
1048 | return """
1049 | # KVKK (Personal Data Protection Authority) Tools
1050 |
1051 | ## Overview
1052 | KVKK (Kişisel Verilerin Korunması Kurulu) - Turkey's GDPR equivalent authority.
1053 |
1054 | ## Search Features
1055 | - **Brave Search API**: Searches kvkk.gov.tr with Turkish terms
1056 | - **Site-targeted**: Auto `site:kvkk.gov.tr "karar özeti"`
1057 | - **Pagination**: page and pageSize parameters
1058 | - **5,000-char pages**: Paginated Markdown documents
1059 |
1060 | ## Common Search Terms
1061 | **Violations**: "veri ihlali", "açık rıza", "idari para cezası"
1062 | **Compliance**: "GDPR uyum", "veri koruma", "güvenlik tedbirleri"
1063 | **Sectors**: "e-ticaret", "bankacılık", "sağlık", "mobil uygulama"
1064 |
1065 | ## Key Decision Types
1066 | - **Fines**: Data breaches, consent violations
1067 | - **Compliance**: GDPR alignment, corporate policies
1068 | - **Breach notifications**: 24-hour rule violations
1069 |
1070 | ## Usage Tips
1071 | 1. Use Turkish legal terms for best results
1072 | 2. Combine sector + violation type searches
1073 | 3. Use page_number for long decisions
1074 | 4. Focus on recent 2-3 years for current practices
1075 | """
1076 |
1077 |
1078 | # --- API Client Instances ---
1079 | yargitay_client_instance = YargitayOfficialApiClient()
1080 | danistay_client_instance = DanistayApiClient()
1081 | emsal_client_instance = EmsalApiClient()
1082 | uyusmazlik_client_instance = UyusmazlikApiClient()
1083 | anayasa_norm_client_instance = AnayasaMahkemesiApiClient()
1084 | anayasa_bireysel_client_instance = AnayasaBireyselBasvuruApiClient()
1085 | anayasa_unified_client_instance = AnayasaUnifiedClient()
1086 | kik_client_instance = KikApiClient()
1087 | rekabet_client_instance = RekabetKurumuApiClient()
1088 | bedesten_client_instance = BedestenApiClient()
1089 | sayistay_client_instance = SayistayApiClient()
1090 | sayistay_unified_client_instance = SayistayUnifiedClient()
1091 | kvkk_client_instance = KvkkApiClient()
1092 | bddk_client_instance = BddkApiClient()
1093 |
1094 |
1095 | KARAR_TURU_ADI_TO_GUID_ENUM_MAP = {
1096 | "": RekabetKararTuruGuidEnum.TUMU, # Keep for backward compatibility
1097 | "ALL": RekabetKararTuruGuidEnum.TUMU, # Map "ALL" to TUMU
1098 | "Birleşme ve Devralma": RekabetKararTuruGuidEnum.BIRLESME_DEVRALMA,
1099 | "Diğer": RekabetKararTuruGuidEnum.DIGER,
1100 | "Menfi Tespit ve Muafiyet": RekabetKararTuruGuidEnum.MENFI_TESPIT_MUAFIYET,
1101 | "Özelleştirme": RekabetKararTuruGuidEnum.OZELLESTIRME,
1102 | "Rekabet İhlali": RekabetKararTuruGuidEnum.REKABET_IHLALI,
1103 | }
1104 |
1105 | # --- MCP Tools for Yargitay ---
1106 | """
1107 | @app.tool(
1108 | description="Search Yargıtay decisions with 52 chamber filtering and advanced operators",
1109 | annotations={
1110 | "readOnlyHint": True,
1111 | "openWorldHint": True,
1112 | "idempotentHint": True
1113 | }
1114 | )
1115 | async def search_yargitay_detailed(
1116 | arananKelime: str = Field("", description="Turkish search keyword. Supports +required -excluded \"exact phrase\" operators"),
1117 | birimYrgKurulDaire: str = Field("ALL", description="Chamber selection (52 options: Civil/Criminal chambers, General Assemblies)"),
1118 | esasYil: str = Field("", description="Case year for 'Esas No'."),
1119 | esasIlkSiraNo: str = Field("", description="Starting sequence number for 'Esas No'."),
1120 | esasSonSiraNo: str = Field("", description="Ending sequence number for 'Esas No'."),
1121 | kararYil: str = Field("", description="Decision year for 'Karar No'."),
1122 | kararIlkSiraNo: str = Field("", description="Starting sequence number for 'Karar No'."),
1123 | kararSonSiraNo: str = Field("", description="Ending sequence number for 'Karar No'."),
1124 | baslangicTarihi: str = Field("", description="Start date for decision search (DD.MM.YYYY)."),
1125 | bitisTarihi: str = Field("", description="End date for decision search (DD.MM.YYYY)."),
1126 | # pageSize: int = Field(10, ge=1, le=10, description="Number of results per page."),
1127 | pageNumber: int = Field(1, ge=1, description="Page number to retrieve.")
1128 | ) -> CompactYargitaySearchResult:
1129 | # Search Yargıtay decisions using primary API with 52 chamber filtering and advanced operators.
1130 |
1131 | # Convert "ALL" to empty string for API compatibility
1132 | if birimYrgKurulDaire == "ALL":
1133 | birimYrgKurulDaire = ""
1134 |
1135 | pageSize = 10 # Default value
1136 |
1137 | search_query = YargitayDetailedSearchRequest(
1138 | arananKelime=arananKelime,
1139 | birimYrgKurulDaire=birimYrgKurulDaire,
1140 | esasYil=esasYil,
1141 | esasIlkSiraNo=esasIlkSiraNo,
1142 | esasSonSiraNo=esasSonSiraNo,
1143 | kararYil=kararYil,
1144 | kararIlkSiraNo=kararIlkSiraNo,
1145 | kararSonSiraNo=kararSonSiraNo,
1146 | baslangicTarihi=baslangicTarihi,
1147 | bitisTarihi=bitisTarihi,
1148 | siralama="3",
1149 | siralamaDirection="desc",
1150 | pageSize=pageSize,
1151 | pageNumber=pageNumber
1152 | )
1153 |
1154 | logger.info(f"Tool 'search_yargitay_detailed' called: {search_query.model_dump_json(exclude_none=True, indent=2)}")
1155 | try:
1156 | api_response = await yargitay_client_instance.search_detailed_decisions(search_query)
1157 | if api_response and api_response.data and api_response.data.data:
1158 | # Convert to clean decision entries without arananKelime field
1159 | clean_decisions = [
1160 | CleanYargitayDecisionEntry(
1161 | id=decision.id,
1162 | daire=decision.daire,
1163 | esasNo=decision.esasNo,
1164 | kararNo=decision.kararNo,
1165 | kararTarihi=decision.kararTarihi,
1166 | document_url=decision.document_url
1167 | )
1168 | for decision in api_response.data.data
1169 | ]
1170 | return CompactYargitaySearchResult(
1171 | decisions=clean_decisions,
1172 | total_records=api_response.data.recordsTotal if api_response.data else 0,
1173 | requested_page=search_query.pageNumber,
1174 | page_size=search_query.pageSize)
1175 | logger.warning("API response for Yargitay search did not contain expected data structure.")
1176 | return CompactYargitaySearchResult(decisions=[], total_records=0, requested_page=search_query.pageNumber, page_size=search_query.pageSize)
1177 | except Exception as e:
1178 | logger.exception(f"Error in tool 'search_yargitay_detailed'.")
1179 | raise
1180 |
1181 | @app.tool(
1182 | description="Get Yargıtay decision text in Markdown format",
1183 | annotations={
1184 | "readOnlyHint": True,
1185 | "idempotentHint": True
1186 | }
1187 | )
1188 | async def get_yargitay_document_markdown(id: str) -> YargitayDocumentMarkdown:
1189 | # Get Yargıtay decision text as Markdown. Use ID from search results.
1190 | logger.info(f"Tool 'get_yargitay_document_markdown' called for ID: {id}")
1191 | if not id or not id.strip(): raise ValueError("Document ID must be a non-empty string.")
1192 | try:
1193 | return await yargitay_client_instance.get_decision_document_as_markdown(id)
1194 | except Exception as e:
1195 | logger.exception(f"Error in tool 'get_yargitay_document_markdown'.")
1196 | raise
1197 | """
1198 |
1199 | # --- MCP Tools for Danistay ---
1200 | """
1201 | @app.tool(
1202 | description="Search Danıştay decisions with keyword logic (AND/OR/NOT operators)",
1203 | annotations={
1204 | "readOnlyHint": True,
1205 | "openWorldHint": True,
1206 | "idempotentHint": True
1207 | }
1208 | )
1209 | async def search_danistay_by_keyword(
1210 | andKelimeler: List[str] = Field(default_factory=list, description="Keywords for AND logic, e.g., ['word1', 'word2']"),
1211 | orKelimeler: List[str] = Field(default_factory=list, description="Keywords for OR logic."),
1212 | notAndKelimeler: List[str] = Field(default_factory=list, description="Keywords for NOT AND logic."),
1213 | notOrKelimeler: List[str] = Field(default_factory=list, description="Keywords for NOT OR logic."),
1214 | pageNumber: int = Field(1, ge=1, description="Page number."),
1215 | # pageSize: int = Field(10, ge=1, le=10, description="Results per page.")
1216 | ) -> CompactDanistaySearchResult:
1217 | # Search Danıştay decisions with keyword logic.
1218 |
1219 | pageSize = 10 # Default value
1220 |
1221 | search_query = DanistayKeywordSearchRequest(
1222 | andKelimeler=andKelimeler,
1223 | orKelimeler=orKelimeler,
1224 | notAndKelimeler=notAndKelimeler,
1225 | notOrKelimeler=notOrKelimeler,
1226 | pageNumber=pageNumber,
1227 | pageSize=pageSize
1228 | )
1229 |
1230 | logger.info(f"Tool 'search_danistay_by_keyword' called.")
1231 | try:
1232 | api_response = await danistay_client_instance.search_keyword_decisions(search_query)
1233 | if api_response.data:
1234 | return CompactDanistaySearchResult(
1235 | decisions=api_response.data.data,
1236 | total_records=api_response.data.recordsTotal,
1237 | requested_page=search_query.pageNumber,
1238 | page_size=search_query.pageSize)
1239 | logger.warning("API response for Danistay keyword search did not contain expected data structure.")
1240 | return CompactDanistaySearchResult(decisions=[], total_records=0, requested_page=search_query.pageNumber, page_size=search_query.pageSize)
1241 | except Exception as e:
1242 | logger.exception(f"Error in tool 'search_danistay_by_keyword'.")
1243 | raise
1244 |
1245 | @app.tool(
1246 | description="Search Danıştay decisions with detailed criteria (chamber selection, case numbers)",
1247 | annotations={
1248 | "readOnlyHint": True,
1249 | "openWorldHint": True,
1250 | "idempotentHint": True
1251 | }
1252 | )
1253 | async def search_danistay_detailed(
1254 | daire: str = Field("", description="Chamber/Department name (e.g., '1. Daire')."),
1255 | esasYil: str = Field("", description="Case year for 'Esas No'."),
1256 | esasIlkSiraNo: str = Field("", description="Starting sequence for 'Esas No'."),
1257 | esasSonSiraNo: str = Field("", description="Ending sequence for 'Esas No'."),
1258 | kararYil: str = Field("", description="Decision year for 'Karar No'."),
1259 | kararIlkSiraNo: str = Field("", description="Starting sequence for 'Karar No'."),
1260 | kararSonSiraNo: str = Field("", description="Ending sequence for 'Karar No'."),
1261 | baslangicTarihi: str = Field("", description="Start date for decision (DD.MM.YYYY)."),
1262 | bitisTarihi: str = Field("", description="End date for decision (DD.MM.YYYY)."),
1263 | mevzuatNumarasi: str = Field("", description="Legislation number."),
1264 | mevzuatAdi: str = Field("", description="Legislation name."),
1265 | madde: str = Field("", description="Article number."),
1266 | pageNumber: int = Field(1, ge=1, description="Page number."),
1267 | # pageSize: int = Field(10, ge=1, le=10, description="Results per page.")
1268 | ) -> CompactDanistaySearchResult:
1269 | # Search Danıştay decisions with detailed filtering.
1270 |
1271 | pageSize = 10 # Default value
1272 |
1273 | search_query = DanistayDetailedSearchRequest(
1274 | daire=daire,
1275 | esasYil=esasYil,
1276 | esasIlkSiraNo=esasIlkSiraNo,
1277 | esasSonSiraNo=esasSonSiraNo,
1278 | kararYil=kararYil,
1279 | kararIlkSiraNo=kararIlkSiraNo,
1280 | kararSonSiraNo=kararSonSiraNo,
1281 | baslangicTarihi=baslangicTarihi,
1282 | bitisTarihi=bitisTarihi,
1283 | mevzuatNumarasi=mevzuatNumarasi,
1284 | mevzuatAdi=mevzuatAdi,
1285 | madde=madde,
1286 | siralama="3",
1287 | siralamaDirection="desc",
1288 | pageNumber=pageNumber,
1289 | pageSize=pageSize
1290 | )
1291 |
1292 | logger.info(f"Tool 'search_danistay_detailed' called.")
1293 | try:
1294 | api_response = await danistay_client_instance.search_detailed_decisions(search_query)
1295 | if api_response.data:
1296 | return CompactDanistaySearchResult(
1297 | decisions=api_response.data.data,
1298 | total_records=api_response.data.recordsTotal,
1299 | requested_page=search_query.pageNumber,
1300 | page_size=search_query.pageSize)
1301 | logger.warning("API response for Danistay detailed search did not contain expected data structure.")
1302 | return CompactDanistaySearchResult(decisions=[], total_records=0, requested_page=search_query.pageNumber, page_size=search_query.pageSize)
1303 | except Exception as e:
1304 | logger.exception(f"Error in tool 'search_danistay_detailed'.")
1305 | raise
1306 |
1307 | @app.tool(
1308 | description="Get Danıştay decision text in Markdown format",
1309 | annotations={
1310 | "readOnlyHint": True,
1311 | "idempotentHint": True
1312 | }
1313 | )
1314 | async def get_danistay_document_markdown(id: str) -> DanistayDocumentMarkdown:
1315 | # Get Danıştay decision text as Markdown. Use ID from search results.
1316 | logger.info(f"Tool 'get_danistay_document_markdown' called for ID: {id}")
1317 | if not id or not id.strip(): raise ValueError("Document ID must be a non-empty string for Danıştay.")
1318 | try:
1319 | return await danistay_client_instance.get_decision_document_as_markdown(id)
1320 | except Exception as e:
1321 | logger.exception(f"Error in tool 'get_danistay_document_markdown'.")
1322 | raise
1323 | """
1324 |
1325 | # --- MCP Tools for Emsal ---
1326 | @app.tool(
1327 | description="Search Emsal precedent decisions with detailed criteria",
1328 | annotations={
1329 | "readOnlyHint": True,
1330 | "openWorldHint": True,
1331 | "idempotentHint": True
1332 | }
1333 | )
1334 | async def search_emsal_detailed_decisions(
1335 | keyword: str = Field("", description="Keyword to search."),
1336 | selected_bam_civil_court: str = Field("", description="Selected BAM Civil Court."),
1337 | selected_civil_court: str = Field("", description="Selected Civil Court."),
1338 | selected_regional_civil_chambers: List[str] = Field(default_factory=list, description="Selected Regional Civil Chambers."),
1339 | case_year_esas: str = Field("", description="Case year for 'Esas No'."),
1340 | case_start_seq_esas: str = Field("", description="Starting sequence for 'Esas No'."),
1341 | case_end_seq_esas: str = Field("", description="Ending sequence for 'Esas No'."),
1342 | decision_year_karar: str = Field("", description="Decision year for 'Karar No'."),
1343 | decision_start_seq_karar: str = Field("", description="Starting sequence for 'Karar No'."),
1344 | decision_end_seq_karar: str = Field("", description="Ending sequence for 'Karar No'."),
1345 | start_date: str = Field("", description="Start date for decision (DD.MM.YYYY)."),
1346 | end_date: str = Field("", description="End date for decision (DD.MM.YYYY)."),
1347 | sort_criteria: str = Field("1", description="Sorting criteria (e.g., 1: Esas No)."),
1348 | sort_direction: str = Field("desc", description="Sorting direction ('asc' or 'desc')."),
1349 | page_number: int = Field(1, ge=1, description="Page number (accepts int)."),
1350 | # page_size: int = Field(10, ge=1, le=10, description="Results per page.")
1351 | ) -> CompactEmsalSearchResult:
1352 | """Search Emsal precedent decisions with detailed criteria."""
1353 |
1354 | page_size = 10 # Default value
1355 |
1356 | search_query = EmsalSearchRequest(
1357 | keyword=keyword,
1358 | selected_bam_civil_court=selected_bam_civil_court,
1359 | selected_civil_court=selected_civil_court,
1360 | selected_regional_civil_chambers=selected_regional_civil_chambers,
1361 | case_year_esas=case_year_esas,
1362 | case_start_seq_esas=case_start_seq_esas,
1363 | case_end_seq_esas=case_end_seq_esas,
1364 | decision_year_karar=decision_year_karar,
1365 | decision_start_seq_karar=decision_start_seq_karar,
1366 | decision_end_seq_karar=decision_end_seq_karar,
1367 | start_date=start_date,
1368 | end_date=end_date,
1369 | sort_criteria=sort_criteria,
1370 | sort_direction=sort_direction,
1371 | page_number=page_number,
1372 | page_size=page_size
1373 | )
1374 |
1375 | logger.info(f"Tool 'search_emsal_detailed_decisions' called.")
1376 | try:
1377 | api_response = await emsal_client_instance.search_detailed_decisions(search_query)
1378 | if api_response.data:
1379 | return CompactEmsalSearchResult(
1380 | decisions=api_response.data.data,
1381 | total_records=api_response.data.recordsTotal if api_response.data.recordsTotal is not None else 0,
1382 | requested_page=search_query.page_number,
1383 | page_size=search_query.page_size
1384 | )
1385 | logger.warning("API response for Emsal search did not contain expected data structure.")
1386 | return CompactEmsalSearchResult(decisions=[], total_records=0, requested_page=search_query.page_number, page_size=search_query.page_size)
1387 | except Exception as e:
1388 | logger.exception(f"Error in tool 'search_emsal_detailed_decisions'.")
1389 | raise
1390 |
1391 | @app.tool(
1392 | description="Get Emsal precedent decision text in Markdown format",
1393 | annotations={
1394 | "readOnlyHint": True,
1395 | "idempotentHint": True
1396 | }
1397 | )
1398 | async def get_emsal_document_markdown(id: str) -> EmsalDocumentMarkdown:
1399 | """Get document as Markdown."""
1400 | logger.info(f"Tool 'get_emsal_document_markdown' called for ID: {id}")
1401 | if not id or not id.strip(): raise ValueError("Document ID required for Emsal.")
1402 | try:
1403 | return await emsal_client_instance.get_decision_document_as_markdown(id)
1404 | except Exception as e:
1405 | logger.exception(f"Error in tool 'get_emsal_document_markdown'.")
1406 | raise
1407 |
1408 | # --- MCP Tools for Uyusmazlik ---
1409 | @app.tool(
1410 | description="Search Uyuşmazlık Mahkemesi decisions for jurisdictional disputes",
1411 | annotations={
1412 | "readOnlyHint": True,
1413 | "openWorldHint": True,
1414 | "idempotentHint": True
1415 | }
1416 | )
1417 | async def search_uyusmazlik_decisions(
1418 | icerik: str = Field("", description="Keyword or content for main text search."),
1419 | bolum: Literal["ALL", "Ceza Bölümü", "Genel Kurul Kararları", "Hukuk Bölümü"] = Field("ALL", description="Select the department (Bölüm). Use 'ALL' for all departments."),
1420 | uyusmazlik_turu: Literal["ALL", "Görev Uyuşmazlığı", "Hüküm Uyuşmazlığı"] = Field("ALL", description="Select the type of dispute. Use 'ALL' for all types."),
1421 | karar_sonuclari: List[Literal["Hüküm Uyuşmazlığı Olmadığına Dair", "Hüküm Uyuşmazlığı Olduğuna Dair"]] = Field(default_factory=list, description="List of desired 'Karar Sonucu' types."),
1422 | esas_yil: str = Field("", description="Case year ('Esas Yılı')."),
1423 | esas_sayisi: str = Field("", description="Case number ('Esas Sayısı')."),
1424 | karar_yil: str = Field("", description="Decision year ('Karar Yılı')."),
1425 | karar_sayisi: str = Field("", description="Decision number ('Karar Sayısı')."),
1426 | kanun_no: str = Field("", description="Relevant Law Number."),
1427 | karar_date_begin: str = Field("", description="Decision start date (DD.MM.YYYY)."),
1428 | karar_date_end: str = Field("", description="Decision end date (DD.MM.YYYY)."),
1429 | resmi_gazete_sayi: str = Field("", description="Official Gazette number."),
1430 | resmi_gazete_date: str = Field("", description="Official Gazette date (DD.MM.YYYY)."),
1431 | tumce: str = Field("", description="Exact phrase search."),
1432 | wild_card: str = Field("", description="Search for phrase and its inflections."),
1433 | hepsi: str = Field("", description="Search for texts containing all specified words."),
1434 | herhangi_birisi: str = Field("", description="Search for texts containing any of the specified words."),
1435 | not_hepsi: str = Field("", description="Exclude texts containing these specified words.")
1436 | ) -> UyusmazlikSearchResponse:
1437 | """Search Court of Jurisdictional Disputes decisions."""
1438 |
1439 | # Convert string literals to enums
1440 | # Map "ALL" to TUMU for backward compatibility
1441 | if bolum == "ALL":
1442 | bolum_enum = UyusmazlikBolumEnum.TUMU
1443 | else:
1444 | bolum_enum = UyusmazlikBolumEnum(bolum) if bolum else UyusmazlikBolumEnum.TUMU
1445 |
1446 | if uyusmazlik_turu == "ALL":
1447 | uyusmazlik_turu_enum = UyusmazlikTuruEnum.TUMU
1448 | else:
1449 | uyusmazlik_turu_enum = UyusmazlikTuruEnum(uyusmazlik_turu) if uyusmazlik_turu else UyusmazlikTuruEnum.TUMU
1450 | karar_sonuclari_enums = [UyusmazlikKararSonucuEnum(ks) for ks in karar_sonuclari]
1451 |
1452 | search_params = UyusmazlikSearchRequest(
1453 | icerik=icerik,
1454 | bolum=bolum_enum,
1455 | uyusmazlik_turu=uyusmazlik_turu_enum,
1456 | karar_sonuclari=karar_sonuclari_enums,
1457 | esas_yil=esas_yil,
1458 | esas_sayisi=esas_sayisi,
1459 | karar_yil=karar_yil,
1460 | karar_sayisi=karar_sayisi,
1461 | kanun_no=kanun_no,
1462 | karar_date_begin=karar_date_begin,
1463 | karar_date_end=karar_date_end,
1464 | resmi_gazete_sayi=resmi_gazete_sayi,
1465 | resmi_gazete_date=resmi_gazete_date,
1466 | tumce=tumce,
1467 | wild_card=wild_card,
1468 | hepsi=hepsi,
1469 | herhangi_birisi=herhangi_birisi,
1470 | not_hepsi=not_hepsi
1471 | )
1472 |
1473 | logger.info(f"Tool 'search_uyusmazlik_decisions' called.")
1474 | try:
1475 | return await uyusmazlik_client_instance.search_decisions(search_params)
1476 | except Exception as e:
1477 | logger.exception(f"Error in tool 'search_uyusmazlik_decisions'.")
1478 | raise
1479 |
1480 | @app.tool(
1481 | description="Get Uyuşmazlık Mahkemesi decision text from URL in Markdown format",
1482 | annotations={
1483 | "readOnlyHint": True,
1484 | "idempotentHint": True
1485 | }
1486 | )
1487 | async def get_uyusmazlik_document_markdown_from_url(
1488 | document_url: str = Field(..., description="Full URL to the Uyuşmazlık Mahkemesi decision document from search results")
1489 | ) -> UyusmazlikDocumentMarkdown:
1490 | """Get Uyuşmazlık Mahkemesi decision as Markdown."""
1491 | logger.info(f"Tool 'get_uyusmazlik_document_markdown_from_url' called for URL: {str(document_url)}")
1492 | if not document_url:
1493 | raise ValueError("Document URL (document_url) is required for Uyuşmazlık document retrieval.")
1494 | try:
1495 | return await uyusmazlik_client_instance.get_decision_document_as_markdown(str(document_url))
1496 | except Exception as e:
1497 | logger.exception(f"Error in tool 'get_uyusmazlik_document_markdown_from_url'.")
1498 | raise
1499 |
1500 | # --- DEACTIVATED: MCP Tools for Anayasa Mahkemesi (Individual Tools) ---
1501 | # Use search_anayasa_unified and get_anayasa_document_unified instead
1502 |
1503 | """
1504 | @app.tool(
1505 | description="Search Constitutional Court norm control decisions with comprehensive filtering",
1506 | annotations={
1507 | "readOnlyHint": True,
1508 | "openWorldHint": True,
1509 | "idempotentHint": True
1510 | }
1511 | )
1512 | # DEACTIVATED TOOL - Use search_anayasa_unified instead
1513 | # @app.tool(
1514 | # description="DEACTIVATED - Use search_anayasa_unified instead",
1515 | # annotations={"readOnlyHint": True, "openWorldHint": False, "idempotentHint": True}
1516 | # )
1517 | # async def search_anayasa_norm_denetimi_decisions(...) -> AnayasaSearchResult:
1518 | # raise ValueError("This tool is deactivated. Use search_anayasa_unified instead.")
1519 |
1520 | # DEACTIVATED TOOL - Use get_anayasa_document_unified instead
1521 | # @app.tool(...)
1522 | # async def get_anayasa_norm_denetimi_document_markdown(...) -> AnayasaDocumentMarkdown:
1523 | # raise ValueError("This tool is deactivated. Use get_anayasa_document_unified instead.")
1524 |
1525 | # DEACTIVATED TOOL - Use search_anayasa_unified instead
1526 | # @app.tool(...)
1527 | # async def search_anayasa_bireysel_basvuru_report(...) -> AnayasaBireyselReportSearchResult:
1528 | # raise ValueError("This tool is deactivated. Use search_anayasa_unified instead.")
1529 |
1530 | # DEACTIVATED TOOL - Use get_anayasa_document_unified instead
1531 | # @app.tool(...)
1532 | # async def get_anayasa_bireysel_basvuru_document_markdown(...) -> AnayasaBireyselBasvuruDocumentMarkdown:
1533 | # raise ValueError("This tool is deactivated. Use get_anayasa_document_unified instead.")
1534 | """
1535 |
1536 | # --- Unified MCP Tools for Anayasa Mahkemesi ---
1537 | @app.tool(
1538 | description="Unified search for Constitutional Court decisions: both norm control (normkararlarbilgibankasi) and individual applications (kararlarbilgibankasi) in one tool",
1539 | annotations={
1540 | "readOnlyHint": True,
1541 | "openWorldHint": True,
1542 | "idempotentHint": True
1543 | }
1544 | )
1545 | async def search_anayasa_unified(
1546 | decision_type: Literal["norm_denetimi", "bireysel_basvuru"] = Field(..., description="Decision type: norm_denetimi (norm control) or bireysel_basvuru (individual applications)"),
1547 | keywords: List[str] = Field(default_factory=list, description="Keywords to search for (common parameter)"),
1548 | page_to_fetch: int = Field(1, ge=1, le=100, description="Page number to fetch (1-100)"),
1549 | # results_per_page: int = Field(10, ge=1, le=100, description="Results per page (1-100)"),
1550 |
1551 | # Norm Denetimi specific parameters (ignored for bireysel_basvuru)
1552 | keywords_all: List[str] = Field(default_factory=list, description="All keywords must be present (norm_denetimi only)"),
1553 | keywords_any: List[str] = Field(default_factory=list, description="Any of these keywords (norm_denetimi only)"),
1554 | decision_type_norm: Literal["ALL", "1", "2", "3"] = Field("ALL", description="Decision type for norm denetimi"),
1555 | application_date_start: str = Field("", description="Application start date (norm_denetimi only)"),
1556 | application_date_end: str = Field("", description="Application end date (norm_denetimi only)"),
1557 |
1558 | # Bireysel Başvuru specific parameters (ignored for norm_denetimi)
1559 | decision_start_date: str = Field("", description="Decision start date (bireysel_basvuru only)"),
1560 | decision_end_date: str = Field("", description="Decision end date (bireysel_basvuru only)"),
1561 | norm_type: Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "0"] = Field("ALL", description="Norm type (bireysel_basvuru only)"),
1562 | subject_category: str = Field("", description="Subject category (bireysel_basvuru only)")
1563 | ) -> str:
1564 | logger.info(f"Tool 'search_anayasa_unified' called for decision_type: {decision_type}")
1565 |
1566 | results_per_page = 10 # Default value
1567 |
1568 | try:
1569 | request = AnayasaUnifiedSearchRequest(
1570 | decision_type=decision_type,
1571 | keywords=keywords,
1572 | page_to_fetch=page_to_fetch,
1573 | results_per_page=results_per_page,
1574 | keywords_all=keywords_all,
1575 | keywords_any=keywords_any,
1576 | decision_type_norm=decision_type_norm,
1577 | application_date_start=application_date_start,
1578 | application_date_end=application_date_end,
1579 | decision_start_date=decision_start_date,
1580 | decision_end_date=decision_end_date,
1581 | norm_type=norm_type,
1582 | subject_category=subject_category
1583 | )
1584 |
1585 | result = await anayasa_unified_client_instance.search_unified(request)
1586 | return json.dumps(result.model_dump(), ensure_ascii=False, indent=2)
1587 |
1588 | except Exception as e:
1589 | logger.exception(f"Error in tool 'search_anayasa_unified'.")
1590 | raise
1591 |
1592 | @app.tool(
1593 | description="Unified document retrieval for Constitutional Court decisions: auto-detects norm control vs individual applications based on URL",
1594 | annotations={
1595 | "readOnlyHint": True,
1596 | "openWorldHint": False,
1597 | "idempotentHint": True
1598 | }
1599 | )
1600 | async def get_anayasa_document_unified(
1601 | document_url: str = Field(..., description="Document URL from search results"),
1602 | page_number: int = Field(1, ge=1, description="Page number for paginated content (1-indexed)")
1603 | ) -> str:
1604 | logger.info(f"Tool 'get_anayasa_document_unified' called for URL: {document_url}, Page: {page_number}")
1605 |
1606 | try:
1607 | result = await anayasa_unified_client_instance.get_document_unified(document_url, page_number)
1608 | return json.dumps(result.model_dump(mode='json'), ensure_ascii=False, indent=2)
1609 |
1610 | except Exception as e:
1611 | logger.exception(f"Error in tool 'get_anayasa_document_unified'.")
1612 | raise
1613 |
1614 | # --- MCP Tools for KIK (Kamu İhale Kurulu) ---
1615 | @app.tool(
1616 | description="Search Public Procurement Authority (KİK) decisions for procurement law disputes",
1617 | annotations={
1618 | "readOnlyHint": True,
1619 | "openWorldHint": True,
1620 | "idempotentHint": True
1621 | }
1622 | )
1623 | async def search_kik_decisions(
1624 | karar_tipi: Literal["rbUyusmazlik", "rbDuzenleyici", "rbMahkeme"] = Field("rbUyusmazlik", description="Type of KIK Decision."),
1625 | karar_no: str = Field("", description="Decision Number (e.g., '2024/UH.II-1766')."),
1626 | karar_tarihi_baslangic: str = Field("", description="Decision Date Start (DD.MM.YYYY)."),
1627 | karar_tarihi_bitis: str = Field("", description="Decision Date End (DD.MM.YYYY)."),
1628 | basvuru_sahibi: str = Field("", description="Applicant."),
1629 | ihaleyi_yapan_idare: str = Field("", description="Procuring Entity."),
1630 | basvuru_konusu_ihale: str = Field("", description="Tender subject of the application."),
1631 | karar_metni: str = Field("", description="Decision text search. Supports: +word, -word, \"exact phrase\", OR/AND"),
1632 | yil: str = Field("", description="Year of the decision."),
1633 | resmi_gazete_tarihi: str = Field("", description="Official Gazette Date (DD.MM.YYYY)."),
1634 | resmi_gazete_sayisi: str = Field("", description="Official Gazette Number."),
1635 | page: int = Field(1, ge=1, description="Results page number.")
1636 | ) -> KikSearchResult:
1637 | """Search Public Procurement Authority (KIK) decisions."""
1638 |
1639 | # Convert string literal to enum
1640 | karar_tipi_enum = KikKararTipi(karar_tipi)
1641 |
1642 | search_query = KikSearchRequest(
1643 | karar_tipi=karar_tipi_enum,
1644 | karar_no=karar_no,
1645 | karar_tarihi_baslangic=karar_tarihi_baslangic,
1646 | karar_tarihi_bitis=karar_tarihi_bitis,
1647 | basvuru_sahibi=basvuru_sahibi,
1648 | ihaleyi_yapan_idare=ihaleyi_yapan_idare,
1649 | basvuru_konusu_ihale=basvuru_konusu_ihale,
1650 | karar_metni=karar_metni,
1651 | yil=yil,
1652 | resmi_gazete_tarihi=resmi_gazete_tarihi,
1653 | resmi_gazete_sayisi=resmi_gazete_sayisi,
1654 | page=page
1655 | )
1656 |
1657 | logger.info(f"Tool 'search_kik_decisions' called.")
1658 | try:
1659 | api_response = await kik_client_instance.search_decisions(search_query)
1660 | page_param_for_log = search_query.page if hasattr(search_query, 'page') else 1
1661 | if not api_response.decisions and api_response.total_records == 0 and page_param_for_log == 1:
1662 | logger.warning(f"KIK search returned no decisions for query.")
1663 | return api_response
1664 | except Exception as e:
1665 | logger.exception(f"Error in KIK search tool 'search_kik_decisions'.")
1666 | current_page_val = search_query.page if hasattr(search_query, 'page') else 1
1667 | return KikSearchResult(decisions=[], total_records=0, current_page=current_page_val)
1668 |
1669 | @app.tool(
1670 | description="Get Public Procurement Authority (KİK) decision text in paginated Markdown format",
1671 | annotations={
1672 | "readOnlyHint": True,
1673 | "idempotentHint": True
1674 | }
1675 | )
1676 | async def get_kik_document_markdown(
1677 | karar_id: str = Field(..., description="The Base64 encoded KIK decision identifier."),
1678 | page_number: int = Field(1, ge=1, description="Page number for paginated Markdown content (1-indexed). Default is 1.")
1679 | ) -> KikDocumentMarkdown:
1680 | """Get KIK decision as paginated Markdown."""
1681 | logger.info(f"Tool 'get_kik_document_markdown' called for KIK karar_id: {karar_id}, Markdown Page: {page_number}")
1682 |
1683 | if not karar_id or not karar_id.strip():
1684 | logger.error("KIK Document retrieval: karar_id cannot be empty.")
1685 | return KikDocumentMarkdown(
1686 | retrieved_with_karar_id=karar_id,
1687 | error_message="karar_id is required and must be a non-empty string.",
1688 | current_page=page_number or 1,
1689 | total_pages=1,
1690 | is_paginated=False
1691 | )
1692 |
1693 | current_page_to_fetch = page_number if page_number is not None and page_number >= 1 else 1
1694 |
1695 | try:
1696 | return await kik_client_instance.get_decision_document_as_markdown(
1697 | karar_id_b64=karar_id,
1698 | page_number=current_page_to_fetch
1699 | )
1700 | except Exception as e:
1701 | logger.exception(f"Error in KIK document retrieval tool 'get_kik_document_markdown' for karar_id: {karar_id}")
1702 | return KikDocumentMarkdown(
1703 | retrieved_with_karar_id=karar_id,
1704 | error_message=f"Tool-level error during KIK document retrieval: {str(e)}",
1705 | current_page=current_page_to_fetch,
1706 | total_pages=1,
1707 | is_paginated=False
1708 | )
1709 | @app.tool(
1710 | description="Search Competition Authority (Rekabet Kurumu) decisions for competition law and antitrust",
1711 | annotations={
1712 | "readOnlyHint": True,
1713 | "openWorldHint": True,
1714 | "idempotentHint": True
1715 | }
1716 | )
1717 | async def search_rekabet_kurumu_decisions(
1718 | sayfaAdi: str = Field("", description="Search in decision title (Başlık)."),
1719 | YayinlanmaTarihi: str = Field("", description="Publication date (Yayım Tarihi), e.g., DD.MM.YYYY."),
1720 | PdfText: str = Field(
1721 | "",
1722 | description='Search in decision text. Use "\\"kesin cümle\\"" for precise matching.'
1723 | ),
1724 | KararTuru: Literal[
1725 | "ALL",
1726 | "Birleşme ve Devralma",
1727 | "Diğer",
1728 | "Menfi Tespit ve Muafiyet",
1729 | "Özelleştirme",
1730 | "Rekabet İhlali"
1731 | ] = Field("ALL", description="Parameter description"),
1732 | KararSayisi: str = Field("", description="Decision number (Karar Sayısı)."),
1733 | KararTarihi: str = Field("", description="Decision date (Karar Tarihi), e.g., DD.MM.YYYY."),
1734 | page: int = Field(1, ge=1, description="Page number to fetch for the results list.")
1735 | ) -> RekabetSearchResult:
1736 | """Search Competition Authority decisions."""
1737 |
1738 | karar_turu_guid_enum = KARAR_TURU_ADI_TO_GUID_ENUM_MAP.get(KararTuru)
1739 |
1740 | try:
1741 | if karar_turu_guid_enum is None:
1742 | logger.warning(f"Invalid user-provided KararTuru: '{KararTuru}'. Defaulting to TUMU (all).")
1743 | karar_turu_guid_enum = RekabetKararTuruGuidEnum.TUMU
1744 | except Exception as e_map:
1745 | logger.error(f"Error mapping KararTuru '{KararTuru}': {e_map}. Defaulting to TUMU.")
1746 | karar_turu_guid_enum = RekabetKararTuruGuidEnum.TUMU
1747 |
1748 | search_query = RekabetKurumuSearchRequest(
1749 | sayfaAdi=sayfaAdi,
1750 | YayinlanmaTarihi=YayinlanmaTarihi,
1751 | PdfText=PdfText,
1752 | KararTuruID=karar_turu_guid_enum,
1753 | KararSayisi=KararSayisi,
1754 | KararTarihi=KararTarihi,
1755 | page=page
1756 | )
1757 | logger.info(f"Tool 'search_rekabet_kurumu_decisions' called. Query: {search_query.model_dump_json(exclude_none=True, indent=2)}")
1758 | try:
1759 |
1760 | return await rekabet_client_instance.search_decisions(search_query)
1761 | except Exception as e:
1762 | logger.exception("Error in tool 'search_rekabet_kurumu_decisions'.")
1763 | return RekabetSearchResult(decisions=[], retrieved_page_number=page, total_records_found=0, total_pages=0)
1764 |
1765 | @app.tool(
1766 | description="Get Competition Authority decision text in paginated Markdown format",
1767 | annotations={
1768 | "readOnlyHint": True,
1769 | "idempotentHint": True
1770 | }
1771 | )
1772 | async def get_rekabet_kurumu_document(
1773 | karar_id: str = Field(..., description="GUID (kararId) of the Rekabet Kurumu decision. This ID is obtained from search results."),
1774 | page_number: int = Field(1, ge=1, description="Requested page number for the Markdown content converted from PDF (1-indexed, accepts int). Default is 1.")
1775 | ) -> RekabetDocument:
1776 | """Get Competition Authority decision as paginated Markdown."""
1777 | logger.info(f"Tool 'get_rekabet_kurumu_document' called. Karar ID: {karar_id}, Markdown Page: {page_number}")
1778 |
1779 | current_page_to_fetch = page_number if page_number >= 1 else 1
1780 |
1781 | try:
1782 |
1783 | return await rekabet_client_instance.get_decision_document(karar_id, page_number=current_page_to_fetch)
1784 | except Exception as e:
1785 | logger.exception(f"Error in tool 'get_rekabet_kurumu_document'. Karar ID: {karar_id}")
1786 | raise
1787 |
1788 | # --- MCP Tools for Bedesten (Unified Search Across All Courts) ---
1789 | @app.tool(
1790 | description="Search multiple Turkish courts (Yargıtay, Danıştay, Local Courts, Appeals Courts, KYB)",
1791 | annotations={
1792 | "readOnlyHint": True,
1793 | "openWorldHint": True,
1794 | "idempotentHint": True
1795 | }
1796 | )
1797 | async def search_bedesten_unified(
1798 | ctx: Context,
1799 | phrase: str = Field(..., description="""Search query in Turkish. SUPPORTED OPERATORS:
1800 | • Simple: "mülkiyet hakkı" (finds both words)
1801 | • Exact phrase: "\"mülkiyet hakkı\"" (finds exact phrase)
1802 | • Required term: "+mülkiyet hakkı" (must contain mülkiyet)
1803 | • Exclude term: "mülkiyet -kira" (contains mülkiyet but not kira)
1804 | • Boolean AND: "mülkiyet AND hak" (both terms required)
1805 | • Boolean OR: "mülkiyet OR tapu" (either term acceptable)
1806 | • Boolean NOT: "mülkiyet NOT satış" (contains mülkiyet but not satış)
1807 | NOTE: Wildcards (*,?), regex patterns (/regex/), fuzzy search (~), and proximity search are NOT supported.
1808 | For best results, use exact phrases with quotes for legal terms."""),
1809 | court_types: List[BedestenCourtTypeEnum] = Field(
1810 | default=["YARGITAYKARARI", "DANISTAYKARAR"],
1811 | description="Court types: YARGITAYKARARI, DANISTAYKARAR, YERELHUKUK, ISTINAFHUKUK, KYB"
1812 | ),
1813 | # pageSize: int = Field(10, ge=1, le=10, description="Results per page (1-10)"),
1814 | pageNumber: int = Field(1, ge=1, description="Page number"),
1815 | birimAdi: BirimAdiEnum = Field("ALL", description="""
1816 | Chamber filter (optional). Abbreviated values with Turkish names:
1817 | • Yargıtay: H1-H23 (1-23. Hukuk Dairesi), C1-C23 (1-23. Ceza Dairesi), HGK (Hukuk Genel Kurulu), CGK (Ceza Genel Kurulu), BGK (Büyük Genel Kurulu), HBK (Hukuk Daireleri Başkanlar Kurulu), CBK (Ceza Daireleri Başkanlar Kurulu)
1818 | • Danıştay: D1-D17 (1-17. Daire), DBGK (Büyük Gen.Kur.), IDDK (İdare Dava Daireleri Kurulu), VDDK (Vergi Dava Daireleri Kurulu), IBK (İçtihatları Birleştirme Kurulu), IIK (İdari İşler Kurulu), DBK (Başkanlar Kurulu), AYIM (Askeri Yüksek İdare Mahkemesi), AYIM1-3 (Askeri Yüksek İdare Mahkemesi 1-3. Daire)
1819 | """),
1820 | kararTarihiStart: str = Field("", description="Start date (ISO 8601 format)"),
1821 | kararTarihiEnd: str = Field("", description="End date (ISO 8601 format)")
1822 | ) -> dict:
1823 | """Search Turkish legal databases via unified Bedesten API."""
1824 |
1825 | # Get Bearer token information for access control and logging
1826 | try:
1827 | access_token: AccessToken = get_access_token()
1828 | user_id = access_token.client_id
1829 | user_scopes = access_token.scopes
1830 |
1831 | # Check for required scopes
1832 | if "yargi.read" not in user_scopes and "yargi.search" not in user_scopes:
1833 | raise ToolError(f"Insufficient permissions: 'yargi.read' or 'yargi.search' scope required. Current scopes: {user_scopes}")
1834 |
1835 | logger.info(f"Tool 'search_bedesten_unified' called by user '{user_id}' with scopes {user_scopes}")
1836 |
1837 | except Exception as e:
1838 | # Development mode fallback - allow access without strict token validation
1839 | logger.warning(f"Bearer token validation failed, using development mode: {str(e)}")
1840 | user_id = "dev-user"
1841 | user_scopes = ["yargi.read", "yargi.search"]
1842 |
1843 | pageSize = 10 # Default value
1844 |
1845 | search_data = BedestenSearchData(
1846 | pageSize=pageSize,
1847 | pageNumber=pageNumber,
1848 | itemTypeList=court_types,
1849 | phrase=phrase,
1850 | birimAdi=birimAdi,
1851 | kararTarihiStart=kararTarihiStart,
1852 | kararTarihiEnd=kararTarihiEnd
1853 | )
1854 |
1855 | search_request = BedestenSearchRequest(data=search_data)
1856 |
1857 | logger.info(f"User '{user_id}' searching bedesten: phrase='{phrase}', court_types={court_types}, birimAdi='{birimAdi}', page={pageNumber}")
1858 |
1859 | try:
1860 | response = await bedesten_client_instance.search_documents(search_request)
1861 |
1862 | if response.data is None:
1863 | return {
1864 | "decisions": [],
1865 | "total_records": 0,
1866 | "requested_page": pageNumber,
1867 | "page_size": pageSize,
1868 | "searched_courts": court_types,
1869 | "error": "No data returned from Bedesten API"
1870 | }
1871 |
1872 | return {
1873 | "decisions": [d.model_dump() for d in response.data.emsalKararList],
1874 | "total_records": response.data.total,
1875 | "requested_page": pageNumber,
1876 | "page_size": pageSize,
1877 | "searched_courts": court_types
1878 | }
1879 | except Exception as e:
1880 | logger.exception("Error in tool 'search_bedesten_unified'")
1881 | raise
1882 |
1883 | @app.tool(
1884 | description="Get legal decision document from Bedesten API in Markdown format",
1885 | annotations={
1886 | "readOnlyHint": True,
1887 | "idempotentHint": True
1888 | }
1889 | )
1890 | async def get_bedesten_document_markdown(
1891 | documentId: str = Field(..., description="Document ID from Bedesten search results")
1892 | ) -> BedestenDocumentMarkdown:
1893 | """Get legal decision document as Markdown from Bedesten API."""
1894 | logger.info(f"Tool 'get_bedesten_document_markdown' called for ID: {documentId}")
1895 |
1896 | if not documentId or not documentId.strip():
1897 | raise ValueError("Document ID must be a non-empty string.")
1898 |
1899 | try:
1900 | return await bedesten_client_instance.get_document_as_markdown(documentId)
1901 | except Exception as e:
1902 | logger.exception("Error in tool 'get_kyb_bedesten_document_markdown'")
1903 | raise
1904 |
1905 | # --- MCP Tools for Sayıştay (Turkish Court of Accounts) ---
1906 |
1907 | # DEACTIVATED TOOL - Use search_sayistay_unified instead
1908 | # @app.tool(
1909 | # description="Search Sayıştay Genel Kurul decisions for audit and accountability regulations",
1910 | # annotations={
1911 | # "readOnlyHint": True,
1912 | # "openWorldHint": True,
1913 | # "idempotentHint": True
1914 | # }
1915 | # )
1916 | # async def search_sayistay_genel_kurul(
1917 | # karar_no: str = Field("", description="Decision number to search for (e.g., '5415')"),
1918 | # karar_ek: str = Field("", description="Decision appendix number (max 99, e.g., '1')"),
1919 | # karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY)"),
1920 | # karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY)"),
1921 | # karar_tamami: str = Field("", description="Full text search"),
1922 | # start: int = Field(0, description="Starting record for pagination (0-based)"),
1923 | # length: int = Field(10, description="Number of records per page (1-100)")
1924 | # ) -> GenelKurulSearchResponse:
1925 | # """Search Sayıştay General Assembly decisions."""
1926 | # raise ValueError("This tool is deactivated. Use search_sayistay_unified instead.")
1927 |
1928 | # DEACTIVATED TOOL - Use search_sayistay_unified instead
1929 | # @app.tool(
1930 | # description="Search Sayıştay Temyiz Kurulu decisions with chamber filtering and comprehensive criteria",
1931 | # annotations={
1932 | # "readOnlyHint": True,
1933 | # "openWorldHint": True,
1934 | # "idempotentHint": True
1935 | # }
1936 | # )
1937 | # async def search_sayistay_temyiz_kurulu(
1938 | # ilam_dairesi: DaireEnum = Field("ALL", description="Audit chamber selection"),
1939 | # yili: str = Field("", description="Year (YYYY)"),
1940 | # karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY)"),
1941 | # karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY)"),
1942 | # kamu_idaresi_turu: KamuIdaresiTuruEnum = Field("ALL", description="Public admin type"),
1943 | # ilam_no: str = Field("", description="Audit report number (İlam No, max 50 chars)"),
1944 | # dosya_no: str = Field("", description="File number for the case"),
1945 | # temyiz_tutanak_no: str = Field("", description="Appeals board meeting minutes number"),
1946 | # temyiz_karar: str = Field("", description="Appeals decision text"),
1947 | # web_karar_konusu: WebKararKonusuEnum = Field("ALL", description="Decision subject"),
1948 | # start: int = Field(0, description="Starting record for pagination (0-based)"),
1949 | # length: int = Field(10, description="Number of records per page (1-100)")
1950 | # ) -> TemyizKuruluSearchResponse:
1951 | # """Search Sayıştay Appeals Board decisions."""
1952 | # raise ValueError("This tool is deactivated. Use search_sayistay_unified instead.")
1953 |
1954 | # DEACTIVATED TOOL - Use search_sayistay_unified instead
1955 | # @app.tool(
1956 | # description="Search Sayıştay Daire decisions with chamber filtering and subject categorization",
1957 | # annotations={
1958 | # "readOnlyHint": True,
1959 | # "openWorldHint": True,
1960 | # "idempotentHint": True
1961 | # }
1962 | # )
1963 | # async def search_sayistay_daire(
1964 | # yargilama_dairesi: DaireEnum = Field("ALL", description="Chamber selection"),
1965 | # karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY)"),
1966 | # karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY)"),
1967 | # ilam_no: str = Field("", description="Audit report number (İlam No, max 50 chars)"),
1968 | # kamu_idaresi_turu: KamuIdaresiTuruEnum = Field("ALL", description="Public admin type"),
1969 | # hesap_yili: str = Field("", description="Fiscal year"),
1970 | # web_karar_konusu: WebKararKonusuEnum = Field("ALL", description="Decision subject"),
1971 | # web_karar_metni: str = Field("", description="Decision text search"),
1972 | # start: int = Field(0, description="Starting record for pagination (0-based)"),
1973 | # length: int = Field(10, description="Number of records per page (1-100)")
1974 | # ) -> DaireSearchResponse:
1975 | # """Search Sayıştay Chamber decisions."""
1976 | # raise ValueError("This tool is deactivated. Use search_sayistay_unified instead.")
1977 |
1978 | # DEACTIVATED TOOL - Use get_sayistay_document_unified instead
1979 | # @app.tool(
1980 | # description="Get Sayıştay Genel Kurul decision document in Markdown format",
1981 | # annotations={
1982 | # "readOnlyHint": True,
1983 | # "openWorldHint": False,
1984 | # "idempotentHint": True
1985 | # }
1986 | # )
1987 | # async def get_sayistay_genel_kurul_document_markdown(
1988 | # decision_id: str = Field(..., description="Decision ID from search_sayistay_genel_kurul results")
1989 | # ) -> SayistayDocumentMarkdown:
1990 | # """Get Sayıştay General Assembly decision as Markdown."""
1991 | # raise ValueError("This tool is deactivated. Use get_sayistay_document_unified instead.")
1992 |
1993 | # DEACTIVATED TOOL - Use get_sayistay_document_unified instead
1994 | # @app.tool(
1995 | # description="Get Sayıştay Temyiz Kurulu decision document in Markdown format",
1996 | # annotations={
1997 | # "readOnlyHint": True,
1998 | # "openWorldHint": False,
1999 | # "idempotentHint": True
2000 | # }
2001 | # )
2002 | # async def get_sayistay_temyiz_kurulu_document_markdown(
2003 | # decision_id: str = Field(..., description="Decision ID from search_sayistay_temyiz_kurulu results")
2004 | # ) -> SayistayDocumentMarkdown:
2005 | # """Get Sayıştay Appeals Board decision as Markdown."""
2006 | # raise ValueError("This tool is deactivated. Use get_sayistay_document_unified instead.")
2007 |
2008 | # DEACTIVATED TOOL - Use get_sayistay_document_unified instead
2009 | # @app.tool(
2010 | # description="Get Sayıştay Daire decision document in Markdown format",
2011 | # annotations={
2012 | # "readOnlyHint": True,
2013 | # "openWorldHint": False,
2014 | # "idempotentHint": True
2015 | # }
2016 | # )
2017 | # async def get_sayistay_daire_document_markdown(
2018 | # decision_id: str = Field(..., description="Decision ID from search_sayistay_daire results")
2019 | # ) -> SayistayDocumentMarkdown:
2020 | # """Get Sayıştay Chamber decision as Markdown."""
2021 | # raise ValueError("This tool is deactivated. Use get_sayistay_document_unified instead.")
2022 |
2023 | # --- UNIFIED MCP Tools for Sayıştay (Turkish Court of Accounts) ---
2024 |
2025 | @app.tool(
2026 | description="Search Sayıştay decisions unified across all three decision types (Genel Kurul, Temyiz Kurulu, Daire) with comprehensive filtering",
2027 | annotations={
2028 | "readOnlyHint": True,
2029 | "openWorldHint": True,
2030 | "idempotentHint": True
2031 | }
2032 | )
2033 | async def search_sayistay_unified(
2034 | decision_type: Literal["genel_kurul", "temyiz_kurulu", "daire"] = Field(..., description="Decision type: genel_kurul, temyiz_kurulu, or daire"),
2035 |
2036 | # Common pagination parameters
2037 | start: int = Field(0, ge=0, description="Starting record for pagination (0-based)"),
2038 | length: int = Field(10, ge=1, le=100, description="Number of records per page (1-100)"),
2039 |
2040 | # Common search parameters
2041 | karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY format)"),
2042 | karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY format)"),
2043 | kamu_idaresi_turu: Literal["ALL", "Genel Bütçe Kapsamındaki İdareler", "Yüksek Öğretim Kurumları", "Diğer Özel Bütçeli İdareler", "Düzenleyici ve Denetleyici Kurumlar", "Sosyal Güvenlik Kurumları", "Özel İdareler", "Belediyeler ve Bağlı İdareler", "Diğer"] = Field("ALL", description="Public administration type filter"),
2044 | ilam_no: str = Field("", description="Audit report number (İlam No, max 50 chars)"),
2045 | web_karar_konusu: Literal["ALL", "Harcırah Mevzuatı", "İhale Mevzuatı", "İş Mevzuatı", "Personel Mevzuatı", "Sorumluluk ve Yargılama Usulleri", "Vergi Resmi Harç ve Diğer Gelirler", "Çeşitli Konular"] = Field("ALL", description="Decision subject category filter"),
2046 |
2047 | # Genel Kurul specific parameters (ignored for other types)
2048 | karar_no: str = Field("", description="Decision number (genel_kurul only)"),
2049 | karar_ek: str = Field("", description="Decision appendix number (genel_kurul only)"),
2050 | karar_tamami: str = Field("", description="Full text search (genel_kurul only)"),
2051 |
2052 | # Temyiz Kurulu specific parameters (ignored for other types)
2053 | ilam_dairesi: Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8"] = Field("ALL", description="Audit chamber selection (temyiz_kurulu only)"),
2054 | yili: str = Field("", description="Year (YYYY format, temyiz_kurulu only)"),
2055 | dosya_no: str = Field("", description="File number (temyiz_kurulu only)"),
2056 | temyiz_tutanak_no: str = Field("", description="Appeals board meeting minutes number (temyiz_kurulu only)"),
2057 | temyiz_karar: str = Field("", description="Appeals decision text search (temyiz_kurulu only)"),
2058 |
2059 | # Daire specific parameters (ignored for other types)
2060 | yargilama_dairesi: Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8"] = Field("ALL", description="Chamber selection (daire only)"),
2061 | hesap_yili: str = Field("", description="Account year (daire only)"),
2062 | web_karar_metni: str = Field("", description="Decision text search (daire only)")
2063 | ) -> SayistayUnifiedSearchResult:
2064 | """Search Sayıştay decisions across all three decision types with unified interface."""
2065 | logger.info(f"Tool 'search_sayistay_unified' called with decision_type={decision_type}")
2066 |
2067 | try:
2068 | search_request = SayistayUnifiedSearchRequest(
2069 | decision_type=decision_type,
2070 | start=start,
2071 | length=length,
2072 | karar_tarih_baslangic=karar_tarih_baslangic,
2073 | karar_tarih_bitis=karar_tarih_bitis,
2074 | kamu_idaresi_turu=kamu_idaresi_turu,
2075 | ilam_no=ilam_no,
2076 | web_karar_konusu=web_karar_konusu,
2077 | karar_no=karar_no,
2078 | karar_ek=karar_ek,
2079 | karar_tamami=karar_tamami,
2080 | ilam_dairesi=ilam_dairesi,
2081 | yili=yili,
2082 | dosya_no=dosya_no,
2083 | temyiz_tutanak_no=temyiz_tutanak_no,
2084 | temyiz_karar=temyiz_karar,
2085 | yargilama_dairesi=yargilama_dairesi,
2086 | hesap_yili=hesap_yili,
2087 | web_karar_metni=web_karar_metni
2088 | )
2089 | return await sayistay_unified_client_instance.search_unified(search_request)
2090 | except Exception as e:
2091 | logger.exception("Error in tool 'search_sayistay_unified'")
2092 | raise
2093 |
2094 | @app.tool(
2095 | description="Get Sayıştay decision document in Markdown format for any decision type",
2096 | annotations={
2097 | "readOnlyHint": True,
2098 | "openWorldHint": False,
2099 | "idempotentHint": True
2100 | }
2101 | )
2102 | async def get_sayistay_document_unified(
2103 | decision_id: str = Field(..., description="Decision ID from search_sayistay_unified results"),
2104 | decision_type: Literal["genel_kurul", "temyiz_kurulu", "daire"] = Field(..., description="Decision type: genel_kurul, temyiz_kurulu, or daire")
2105 | ) -> SayistayUnifiedDocumentMarkdown:
2106 | """Get Sayıştay decision document as Markdown for any decision type."""
2107 | logger.info(f"Tool 'get_sayistay_document_unified' called for ID: {decision_id}, type: {decision_type}")
2108 |
2109 | if not decision_id or not decision_id.strip():
2110 | raise ValueError("Decision ID must be a non-empty string.")
2111 |
2112 | try:
2113 | return await sayistay_unified_client_instance.get_document_unified(decision_id, decision_type)
2114 | except Exception as e:
2115 | logger.exception("Error in tool 'get_sayistay_document_unified'")
2116 | raise
2117 |
2118 | # --- Application Shutdown Handling ---
2119 | def perform_cleanup():
2120 | logger.info("MCP Server performing cleanup...")
2121 | try:
2122 | loop = asyncio.get_event_loop_policy().get_event_loop()
2123 | if loop.is_closed():
2124 | loop = asyncio.new_event_loop()
2125 | asyncio.set_event_loop(loop)
2126 | except RuntimeError:
2127 | loop = asyncio.new_event_loop()
2128 | asyncio.set_event_loop(loop)
2129 | clients_to_close = [
2130 | globals().get('yargitay_client_instance'),
2131 | globals().get('danistay_client_instance'),
2132 | globals().get('emsal_client_instance'),
2133 | globals().get('uyusmazlik_client_instance'),
2134 | globals().get('anayasa_norm_client_instance'),
2135 | globals().get('anayasa_bireysel_client_instance'),
2136 | globals().get('anayasa_unified_client_instance'),
2137 | globals().get('kik_client_instance'),
2138 | globals().get('rekabet_client_instance'),
2139 | globals().get('bedesten_client_instance'),
2140 | globals().get('sayistay_client_instance'),
2141 | globals().get('sayistay_unified_client_instance'),
2142 | globals().get('kvkk_client_instance'),
2143 | globals().get('bddk_client_instance')
2144 | ]
2145 | async def close_all_clients_async():
2146 | tasks = []
2147 | for client_instance in clients_to_close:
2148 | if client_instance and hasattr(client_instance, 'close_client_session') and callable(client_instance.close_client_session):
2149 | logger.info(f"Scheduling close for client session: {client_instance.__class__.__name__}")
2150 | tasks.append(client_instance.close_client_session())
2151 | if tasks:
2152 | results = await asyncio.gather(*tasks, return_exceptions=True)
2153 | for i, result in enumerate(results):
2154 | if isinstance(result, Exception):
2155 | client_name = "Unknown Client"
2156 | if i < len(clients_to_close) and clients_to_close[i] is not None:
2157 | client_name = clients_to_close[i].__class__.__name__
2158 | logger.error(f"Error closing client {client_name}: {result}")
2159 | try:
2160 | if loop.is_running():
2161 | asyncio.ensure_future(close_all_clients_async(), loop=loop)
2162 | logger.info("Client cleanup tasks scheduled on running event loop.")
2163 | else:
2164 | loop.run_until_complete(close_all_clients_async())
2165 | logger.info("Client cleanup tasks completed via run_until_complete.")
2166 | except Exception as e:
2167 | logger.error(f"Error during atexit cleanup execution: {e}", exc_info=True)
2168 | logger.info("MCP Server atexit cleanup process finished.")
2169 |
2170 | atexit.register(perform_cleanup)
2171 |
2172 | # --- Health Check Tools ---
2173 | @app.tool(
2174 | description="Check if Turkish government legal database servers are operational",
2175 | annotations={
2176 | "readOnlyHint": True,
2177 | "idempotentHint": True
2178 | }
2179 | )
2180 | async def check_government_servers_health() -> Dict[str, Any]:
2181 | """Check health status of Turkish government legal database servers."""
2182 | logger.info("Health check tool called for government servers")
2183 |
2184 | health_results = {}
2185 |
2186 | # Check Yargıtay server
2187 | try:
2188 | yargitay_payload = {
2189 | "data": {
2190 | "aranan": "karar",
2191 | "arananKelime": "karar",
2192 | "pageSize": 10,
2193 | "pageNumber": 1
2194 | }
2195 | }
2196 |
2197 | async with httpx.AsyncClient(
2198 | headers={
2199 | "Accept": "*/*",
2200 | "Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7",
2201 | "Connection": "keep-alive",
2202 | "Content-Type": "application/json; charset=UTF-8",
2203 | "Origin": "https://karararama.yargitay.gov.tr",
2204 | "Referer": "https://karararama.yargitay.gov.tr/",
2205 | "Sec-Fetch-Dest": "empty",
2206 | "Sec-Fetch-Mode": "cors",
2207 | "Sec-Fetch-Site": "same-origin",
2208 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
2209 | "X-Requested-With": "XMLHttpRequest"
2210 | },
2211 | timeout=30.0,
2212 | verify=False
2213 | ) as client:
2214 | response = await client.post(
2215 | "https://karararama.yargitay.gov.tr/aramalist",
2216 | json=yargitay_payload
2217 | )
2218 |
2219 | if response.status_code == 200:
2220 | response_data = response.json()
2221 | records_total = response_data.get("data", {}).get("recordsTotal", 0)
2222 |
2223 | if records_total > 0:
2224 | health_results["yargitay"] = {
2225 | "status": "healthy",
2226 | "response_time_ms": response.elapsed.total_seconds() * 1000
2227 | }
2228 | else:
2229 | health_results["yargitay"] = {
2230 | "status": "unhealthy",
2231 | "reason": "recordsTotal is 0 or missing",
2232 | "response_time_ms": response.elapsed.total_seconds() * 1000
2233 | }
2234 | else:
2235 | health_results["yargitay"] = {
2236 | "status": "unhealthy",
2237 | "reason": f"HTTP {response.status_code}",
2238 | "response_time_ms": response.elapsed.total_seconds() * 1000
2239 | }
2240 |
2241 | except Exception as e:
2242 | health_results["yargitay"] = {
2243 | "status": "unhealthy",
2244 | "reason": f"Connection error: {str(e)}"
2245 | }
2246 |
2247 | # Check Bedesten API server
2248 | try:
2249 | bedesten_payload = {
2250 | "data": {
2251 | "pageSize": 5,
2252 | "pageNumber": 1,
2253 | "itemTypeList": ["YARGITAYKARARI"],
2254 | "phrase": "karar",
2255 | "sortFields": ["KARAR_TARIHI"],
2256 | "sortDirection": "desc"
2257 | },
2258 | "applicationName": "UyapMevzuat",
2259 | "paging": True
2260 | }
2261 |
2262 | async with httpx.AsyncClient(
2263 | headers={
2264 | "Content-Type": "application/json",
2265 | "Accept": "application/json",
2266 | "User-Agent": "Mozilla/5.0 Health Check"
2267 | },
2268 | timeout=30.0,
2269 | verify=False
2270 | ) as client:
2271 | response = await client.post(
2272 | "https://bedesten.adalet.gov.tr/emsal-karar/searchDocuments",
2273 | json=bedesten_payload
2274 | )
2275 |
2276 | if response.status_code == 200:
2277 | response_data = response.json()
2278 | logger.debug(f"Bedesten API response: {response_data}")
2279 | if response_data and isinstance(response_data, dict):
2280 | data_section = response_data.get("data")
2281 | if data_section and isinstance(data_section, dict):
2282 | total_found = data_section.get("total", 0)
2283 | else:
2284 | total_found = 0
2285 | else:
2286 | total_found = 0
2287 |
2288 | if total_found > 0:
2289 | health_results["bedesten"] = {
2290 | "status": "healthy",
2291 | "response_time_ms": response.elapsed.total_seconds() * 1000
2292 | }
2293 | else:
2294 | health_results["bedesten"] = {
2295 | "status": "unhealthy",
2296 | "reason": "total is 0 or missing in data field",
2297 | "response_time_ms": response.elapsed.total_seconds() * 1000
2298 | }
2299 | else:
2300 | health_results["bedesten"] = {
2301 | "status": "unhealthy",
2302 | "reason": f"HTTP {response.status_code}",
2303 | "response_time_ms": response.elapsed.total_seconds() * 1000
2304 | }
2305 |
2306 | except Exception as e:
2307 | health_results["bedesten"] = {
2308 | "status": "unhealthy",
2309 | "reason": f"Connection error: {str(e)}"
2310 | }
2311 |
2312 | # Overall health assessment
2313 | healthy_servers = sum(1 for server in health_results.values() if server["status"] == "healthy")
2314 | total_servers = len(health_results)
2315 |
2316 | overall_status = "healthy" if healthy_servers == total_servers else "degraded" if healthy_servers > 0 else "unhealthy"
2317 |
2318 | return {
2319 | "overall_status": overall_status,
2320 | "healthy_servers": healthy_servers,
2321 | "total_servers": total_servers,
2322 | "servers": health_results,
2323 | "check_timestamp": f"{__import__('datetime').datetime.now().isoformat()}"
2324 | }
2325 |
2326 | # --- MCP Tools for KVKK ---
2327 | @app.tool(
2328 | description="Search KVKK data protection authority decisions",
2329 | annotations={
2330 | "readOnlyHint": True,
2331 | "openWorldHint": True,
2332 | "idempotentHint": True
2333 | }
2334 | )
2335 | async def search_kvkk_decisions(
2336 | keywords: str = Field(..., description="Turkish keywords. Supports +required -excluded \"exact phrase\" operators"),
2337 | page: int = Field(1, ge=1, le=50, description="Page number for results (1-50)."),
2338 | # pageSize: int = Field(10, ge=1, le=20, description="Number of results per page (1-20).")
2339 | ) -> KvkkSearchResult:
2340 | """Search function for legal decisions."""
2341 | logger.info(f"KVKK search tool called with keywords: {keywords}")
2342 |
2343 | pageSize = 10 # Default value
2344 |
2345 | search_request = KvkkSearchRequest(
2346 | keywords=keywords,
2347 | page=page,
2348 | pageSize=pageSize
2349 | )
2350 |
2351 | try:
2352 | result = await kvkk_client_instance.search_decisions(search_request)
2353 | logger.info(f"KVKK search completed. Found {len(result.decisions)} decisions on page {page}")
2354 | return result
2355 | except Exception as e:
2356 | logger.exception(f"Error in KVKK search: {e}")
2357 | # Return empty result on error
2358 | return KvkkSearchResult(
2359 | decisions=[],
2360 | total_results=0,
2361 | page=page,
2362 | pageSize=pageSize,
2363 | query=keywords
2364 | )
2365 |
2366 | @app.tool(
2367 | description="Get KVKK decision document in Markdown format with metadata extraction",
2368 | annotations={
2369 | "readOnlyHint": True,
2370 | "openWorldHint": False,
2371 | "idempotentHint": True
2372 | }
2373 | )
2374 | async def get_kvkk_document_markdown(
2375 | decision_url: str = Field(..., description="KVKK decision URL from search results"),
2376 | page_number: int = Field(1, ge=1, description="Page number for paginated Markdown content (1-indexed, accepts int). Default is 1 (first 5,000 characters).")
2377 | ) -> KvkkDocumentMarkdown:
2378 | """Get KVKK decision as paginated Markdown."""
2379 | logger.info(f"KVKK document retrieval tool called for URL: {decision_url}")
2380 |
2381 | if not decision_url or not decision_url.strip():
2382 | return KvkkDocumentMarkdown(
2383 | source_url=HttpUrl("https://www.kvkk.gov.tr"),
2384 | title=None,
2385 | decision_date=None,
2386 | decision_number=None,
2387 | subject_summary=None,
2388 | markdown_chunk=None,
2389 | current_page=page_number or 1,
2390 | total_pages=0,
2391 | is_paginated=False,
2392 | error_message="Decision URL is required and cannot be empty."
2393 | )
2394 |
2395 | try:
2396 | # Validate URL format
2397 | if not decision_url.startswith("https://www.kvkk.gov.tr/"):
2398 | return KvkkDocumentMarkdown(
2399 | source_url=HttpUrl(decision_url),
2400 | title=None,
2401 | decision_date=None,
2402 | decision_number=None,
2403 | subject_summary=None,
2404 | markdown_chunk=None,
2405 | current_page=page_number or 1,
2406 | total_pages=0,
2407 | is_paginated=False,
2408 | error_message="Invalid KVKK decision URL format. URL must start with https://www.kvkk.gov.tr/"
2409 | )
2410 |
2411 | result = await kvkk_client_instance.get_decision_document(decision_url, page_number or 1)
2412 | logger.info(f"KVKK document retrieved successfully. Page {result.current_page}/{result.total_pages}, Content length: {len(result.markdown_chunk) if result.markdown_chunk else 0}")
2413 | return result
2414 |
2415 | except Exception as e:
2416 | logger.exception(f"Error retrieving KVKK document: {e}")
2417 | return KvkkDocumentMarkdown(
2418 | source_url=HttpUrl(decision_url),
2419 | title=None,
2420 | decision_date=None,
2421 | decision_number=None,
2422 | subject_summary=None,
2423 | markdown_chunk=None,
2424 | current_page=page_number or 1,
2425 | total_pages=0,
2426 | is_paginated=False,
2427 | error_message=f"Error retrieving KVKK document: {str(e)}"
2428 | )
2429 |
2430 | # --- MCP Tools for BDDK (Banking Regulation Authority) ---
2431 | @app.tool(
2432 | description="Search BDDK banking regulation decisions",
2433 | annotations={
2434 | "readOnlyHint": True,
2435 | "openWorldHint": True,
2436 | "idempotentHint": True
2437 | }
2438 | )
2439 | async def search_bddk_decisions(
2440 | keywords: str = Field(..., description="Search keywords in Turkish"),
2441 | page: int = Field(1, ge=1, description="Page number")
2442 | # pageSize: int = Field(10, ge=1, le=50, description="Results per page")
2443 | ) -> dict:
2444 | """Search BDDK banking regulation and supervision decisions."""
2445 | logger.info(f"BDDK search tool called with keywords: {keywords}, page: {page}")
2446 |
2447 | pageSize = 10 # Default value
2448 |
2449 | try:
2450 | search_request = BddkSearchRequest(
2451 | keywords=keywords,
2452 | page=page,
2453 | pageSize=pageSize
2454 | )
2455 |
2456 | result = await bddk_client_instance.search_decisions(search_request)
2457 | logger.info(f"BDDK search completed. Found {len(result.decisions)} decisions on page {page}")
2458 |
2459 | return {
2460 | "decisions": [
2461 | {
2462 | "title": dec.title,
2463 | "document_id": dec.document_id,
2464 | "content": dec.content
2465 | }
2466 | for dec in result.decisions
2467 | ],
2468 | "total_results": result.total_results,
2469 | "page": result.page,
2470 | "pageSize": result.pageSize
2471 | }
2472 |
2473 | except Exception as e:
2474 | logger.exception(f"Error searching BDDK decisions: {e}")
2475 | return {
2476 | "decisions": [],
2477 | "total_results": 0,
2478 | "page": page,
2479 | "pageSize": pageSize,
2480 | "error": str(e)
2481 | }
2482 |
2483 | @app.tool(
2484 | description="Get BDDK decision document as Markdown",
2485 | annotations={
2486 | "readOnlyHint": True,
2487 | "openWorldHint": False,
2488 | "idempotentHint": True
2489 | }
2490 | )
2491 | async def get_bddk_document_markdown(
2492 | document_id: str = Field(..., description="BDDK document ID (e.g., '310')"),
2493 | page_number: int = Field(1, ge=1, description="Page number")
2494 | ) -> dict:
2495 | """Retrieve BDDK decision document in Markdown format."""
2496 | logger.info(f"BDDK document retrieval tool called for ID: {document_id}, page: {page_number}")
2497 |
2498 | if not document_id or not document_id.strip():
2499 | return {
2500 | "document_id": document_id,
2501 | "markdown_content": "",
2502 | "page_number": page_number,
2503 | "total_pages": 0,
2504 | "error": "Document ID is required"
2505 | }
2506 |
2507 | try:
2508 | result = await bddk_client_instance.get_document_markdown(document_id, page_number)
2509 | logger.info(f"BDDK document retrieved successfully. Page {result.page_number}/{result.total_pages}")
2510 |
2511 | return {
2512 | "document_id": result.document_id,
2513 | "markdown_content": result.markdown_content,
2514 | "page_number": result.page_number,
2515 | "total_pages": result.total_pages
2516 | }
2517 |
2518 | except Exception as e:
2519 | logger.exception(f"Error retrieving BDDK document: {e}")
2520 | return {
2521 | "document_id": document_id,
2522 | "markdown_content": "",
2523 | "page_number": page_number,
2524 | "total_pages": 0,
2525 | "error": str(e)
2526 | }
2527 |
2528 | # --- ChatGPT Deep Research Compatible Tools ---
2529 |
2530 | def get_preview_text(markdown_content: str, skip_chars: int = 100, preview_chars: int = 200) -> str:
2531 | """
2532 | Extract a preview of document text by skipping headers and showing meaningful content.
2533 |
2534 | Args:
2535 | markdown_content: Full document content in markdown format
2536 | skip_chars: Number of characters to skip from the beginning (default: 100)
2537 | preview_chars: Number of characters to show in preview (default: 200)
2538 |
2539 | Returns:
2540 | Preview text suitable for ChatGPT Deep Research
2541 | """
2542 | if not markdown_content:
2543 | return ""
2544 |
2545 | # Remove common markdown artifacts and clean up
2546 | cleaned_content = markdown_content.strip()
2547 |
2548 | # Skip the first N characters (usually headers, metadata)
2549 | if len(cleaned_content) > skip_chars:
2550 | content_start = cleaned_content[skip_chars:]
2551 | else:
2552 | content_start = cleaned_content
2553 |
2554 | # Get the next N characters for preview
2555 | if len(content_start) > preview_chars:
2556 | preview = content_start[:preview_chars]
2557 | else:
2558 | preview = content_start
2559 |
2560 | # Clean up the preview - remove incomplete sentences at the end
2561 | preview = preview.strip()
2562 |
2563 | # If preview ends mid-sentence, try to end at last complete sentence
2564 | if preview and not preview.endswith('.'):
2565 | last_period = preview.rfind('.')
2566 | if last_period > 50: # Only if there's a reasonable sentence
2567 | preview = preview[:last_period + 1]
2568 |
2569 | # Add ellipsis if content was truncated
2570 | if len(content_start) > preview_chars:
2571 | preview += "..."
2572 |
2573 | return preview.strip()
2574 |
2575 | @app.tool(
2576 | description="DO NOT USE unless you are ChatGPT Deep Research. Search Turkish courts (Turkish keywords only). Supports: +term (must have), -term (exclude), \"exact phrase\", term1 OR term2",
2577 | annotations={
2578 | "readOnlyHint": True,
2579 | "openWorldHint": True,
2580 | "idempotentHint": True
2581 | }
2582 | )
2583 | async def search(
2584 | query: str = Field(..., description="Turkish search query")
2585 | ) -> Dict[str, List[Dict[str, str]]]:
2586 | """
2587 | Bedesten API search tool for ChatGPT Deep Research compatibility.
2588 |
2589 | This tool searches Turkish legal databases via the unified Bedesten API.
2590 | It supports advanced search operators and covers all major court types.
2591 |
2592 | USAGE RESTRICTION: Only for ChatGPT Deep Research workflows.
2593 | For regular legal research, use search_bedesten_unified with specific court types.
2594 |
2595 | Returns:
2596 | Object with "results" field containing a list of documents with id, title, text preview, and url
2597 | as required by ChatGPT Deep Research specification.
2598 | """
2599 | logger.info(f"ChatGPT Deep Research search tool called with query: {query}")
2600 |
2601 | results = []
2602 |
2603 | try:
2604 | # Search all court types via unified Bedesten API
2605 | court_types = [
2606 | ("YARGITAYKARARI", "Yargıtay", "yargitay_bedesten"),
2607 | ("DANISTAYKARAR", "Danıştay", "danistay_bedesten"),
2608 | ("YERELHUKUK", "Yerel Hukuk Mahkemesi", "yerel_hukuk_bedesten"),
2609 | ("ISTINAFHUKUK", "İstinaf Hukuk Mahkemesi", "istinaf_hukuk_bedesten"),
2610 | ("KYB", "Kanun Yararına Bozma", "kyb_bedesten")
2611 | ]
2612 |
2613 | for item_type, court_name, id_prefix in court_types:
2614 | try:
2615 | search_results = await bedesten_client_instance.search_documents(
2616 | BedestenSearchRequest(
2617 | data=BedestenSearchData(
2618 | phrase=query, # Use query as-is to support both regular and exact phrase searches
2619 | itemTypeList=[item_type],
2620 | pageSize=10,
2621 | pageNumber=1
2622 | )
2623 | )
2624 | )
2625 |
2626 | # Handle potential None data
2627 | if search_results.data is None:
2628 | logger.warning(f"No data returned from Bedesten API for {court_name}")
2629 | continue
2630 |
2631 | # Add results from this court type (limit to top 5 per court)
2632 | for decision in search_results.data.emsalKararList[:5]:
2633 | # For ChatGPT Deep Research, fetch document content for preview
2634 | try:
2635 | # Fetch document content for preview
2636 | doc = await bedesten_client_instance.get_document_as_markdown(decision.documentId)
2637 |
2638 | # Generate preview text (skip first 100 chars, show next 200)
2639 | preview_text = get_preview_text(doc.markdown_content, skip_chars=100, preview_chars=200)
2640 |
2641 | # Build title from metadata
2642 | title_parts = []
2643 | if decision.birimAdi:
2644 | title_parts.append(decision.birimAdi)
2645 | if decision.esasNo:
2646 | title_parts.append(f"Esas: {decision.esasNo}")
2647 | if decision.kararNo:
2648 | title_parts.append(f"Karar: {decision.kararNo}")
2649 | if decision.kararTarihiStr:
2650 | title_parts.append(f"Tarih: {decision.kararTarihiStr}")
2651 |
2652 | if title_parts:
2653 | title = " - ".join(title_parts)
2654 | else:
2655 | title = f"{court_name} - Document {decision.documentId}"
2656 |
2657 | # Add to results in OpenAI format
2658 | results.append({
2659 | "id": decision.documentId,
2660 | "title": title,
2661 | "text": preview_text,
2662 | "url": f"https://mevzuat.adalet.gov.tr/ictihat/{decision.documentId}"
2663 | })
2664 |
2665 | except Exception as e:
2666 | logger.warning(f"Could not fetch preview for document {decision.documentId}: {e}")
2667 | # Add minimal result without preview
2668 | results.append({
2669 | "id": decision.documentId,
2670 | "title": f"{court_name} - Document {decision.documentId}",
2671 | "text": "Document preview not available",
2672 | "url": f"https://mevzuat.adalet.gov.tr/ictihat/{decision.documentId}"
2673 | })
2674 |
2675 | if search_results.data:
2676 | logger.info(f"Found {len(search_results.data.emsalKararList)} results from {court_name}")
2677 | else:
2678 | logger.info(f"Found 0 results from {court_name} (no data returned)")
2679 |
2680 | except Exception as e:
2681 | logger.warning(f"Bedesten API search error for {court_name}: {e}")
2682 |
2683 | # Comment out other API implementations for ChatGPT Deep Research
2684 | """
2685 | # Other API implementations disabled for ChatGPT Deep Research
2686 | # These are available through specific court tools:
2687 |
2688 | # Yargıtay Official API - use search_yargitay_detailed instead
2689 | # Danıştay Official API - use search_danistay_by_keyword instead
2690 | # Constitutional Court - use search_anayasa_norm_denetimi_decisions instead
2691 | # Competition Authority - use search_rekabet_kurumu_decisions instead
2692 | # Public Procurement Authority - use search_kik_decisions instead
2693 | # Court of Accounts - use search_sayistay_* tools instead
2694 | # UYAP Emsal - use search_emsal_detailed_decisions instead
2695 | # Jurisdictional Disputes Court - use search_uyusmazlik_decisions instead
2696 | """
2697 |
2698 | logger.info(f"ChatGPT Deep Research search completed. Found {len(results)} results via Bedesten API.")
2699 | return {"results": results}
2700 |
2701 | except Exception as e:
2702 | logger.exception("Error in ChatGPT Deep Research search tool")
2703 | # Return partial results if any were found
2704 | if results:
2705 | return {"results": results}
2706 | raise
2707 |
2708 | @app.tool(
2709 | description="DO NOT USE unless you are ChatGPT Deep Research. Fetch document by ID. See docs for details",
2710 | annotations={
2711 | "readOnlyHint": True,
2712 | "openWorldHint": False, # Retrieves specific documents, not exploring
2713 | "idempotentHint": True
2714 | }
2715 | )
2716 | async def fetch(
2717 | id: str = Field(..., description="Document identifier from search results (numeric only)")
2718 | ) -> Dict[str, Any]:
2719 | """
2720 | Bedesten API fetch tool for ChatGPT Deep Research compatibility.
2721 |
2722 | Retrieves the full text content of Turkish legal documents via unified Bedesten API.
2723 | Converts documents from HTML/PDF to clean Markdown format.
2724 |
2725 | USAGE RESTRICTION: Only for ChatGPT Deep Research workflows.
2726 | For regular legal research, use specific court document tools.
2727 |
2728 | Input Format:
2729 | - id: Numeric document identifier from search results (e.g., "730113500", "71370900")
2730 |
2731 | Returns:
2732 | Single object with numeric id, title, text (full Markdown content), mevzuat.adalet.gov.tr url, and metadata fields
2733 | as required by ChatGPT Deep Research specification.
2734 | """
2735 | logger.info(f"ChatGPT Deep Research fetch tool called for document ID: {id}")
2736 |
2737 | if not id or not id.strip():
2738 | raise ValueError("Document ID must be a non-empty string")
2739 |
2740 | try:
2741 | # Use the numeric ID directly with Bedesten API
2742 | doc = await bedesten_client_instance.get_document_as_markdown(id)
2743 |
2744 | # Try to get additional metadata by searching for this specific document
2745 | title = f"Turkish Legal Document {id}"
2746 | try:
2747 | # Quick search to get metadata for better title
2748 | search_results = await bedesten_client_instance.search_documents(
2749 | BedestenSearchRequest(
2750 | data=BedestenSearchData(
2751 | phrase=id, # Search by document ID
2752 | pageSize=1,
2753 | pageNumber=1
2754 | )
2755 | )
2756 | )
2757 |
2758 | if search_results.data and search_results.data.emsalKararList:
2759 | decision = search_results.data.emsalKararList[0]
2760 | if decision.documentId == id:
2761 | # Build a proper title from metadata
2762 | title_parts = []
2763 | if decision.birimAdi:
2764 | title_parts.append(decision.birimAdi)
2765 | if decision.esasNo:
2766 | title_parts.append(f"Esas: {decision.esasNo}")
2767 | if decision.kararNo:
2768 | title_parts.append(f"Karar: {decision.kararNo}")
2769 | if decision.kararTarihiStr:
2770 | title_parts.append(f"Tarih: {decision.kararTarihiStr}")
2771 |
2772 | if title_parts:
2773 | title = " - ".join(title_parts)
2774 | else:
2775 | title = f"Turkish Legal Decision {id}"
2776 | except Exception as e:
2777 | logger.warning(f"Could not fetch metadata for document {id}: {e}")
2778 |
2779 | return {
2780 | "id": id,
2781 | "title": title,
2782 | "text": doc.markdown_content,
2783 | "url": f"https://mevzuat.adalet.gov.tr/ictihat/{id}",
2784 | "metadata": {
2785 | "database": "Turkish Legal Database via Bedesten API",
2786 | "document_id": id,
2787 | "source_url": doc.source_url,
2788 | "mime_type": doc.mime_type,
2789 | "api_source": "Bedesten Unified API",
2790 | "chatgpt_deep_research": True
2791 | }
2792 | }
2793 |
2794 | # Comment out other API implementations for ChatGPT Deep Research
2795 | """
2796 | # Other API implementations disabled for ChatGPT Deep Research
2797 | # These are available through specific court document tools:
2798 |
2799 | elif id.startswith("yargitay_"):
2800 | # Yargıtay Official API - use get_yargitay_document_markdown instead
2801 | doc_id = id.replace("yargitay_", "")
2802 | doc = await yargitay_client_instance.get_decision_document_as_markdown(doc_id)
2803 |
2804 | elif id.startswith("danistay_"):
2805 | # Danıştay Official API - use get_danistay_document_markdown instead
2806 | doc_id = id.replace("danistay_", "")
2807 | doc = await danistay_client_instance.get_decision_document_as_markdown(doc_id)
2808 |
2809 | elif id.startswith("anayasa_"):
2810 | # Constitutional Court - use get_anayasa_norm_denetimi_document_markdown instead
2811 | doc_id = id.replace("anayasa_", "")
2812 | doc = await anayasa_norm_client_instance.get_decision_document_as_markdown(...)
2813 |
2814 | elif id.startswith("rekabet_"):
2815 | # Competition Authority - use get_rekabet_kurumu_document instead
2816 | doc_id = id.replace("rekabet_", "")
2817 | doc = await rekabet_client_instance.get_decision_document(...)
2818 |
2819 | elif id.startswith("kik_"):
2820 | # Public Procurement Authority - use get_kik_decision_document_as_markdown instead
2821 | doc_id = id.replace("kik_", "")
2822 | doc = await kik_client_instance.get_decision_document_as_markdown(doc_id)
2823 |
2824 | elif id.startswith("local_"):
2825 | # This was already using Bedesten API, but deprecated for ChatGPT Deep Research
2826 | doc_id = id.replace("local_", "")
2827 | doc = await bedesten_client_instance.get_document_as_markdown(doc_id)
2828 | """
2829 |
2830 | except Exception as e:
2831 | logger.exception(f"Error fetching ChatGPT Deep Research document {id}")
2832 | raise
2833 |
2834 | # --- Token Metrics Tool Removed for Optimization ---
2835 |
2836 | def ensure_playwright_browsers():
2837 | """Ensure Playwright browsers are installed for KIK tool functionality."""
2838 | try:
2839 | import subprocess
2840 | import os
2841 |
2842 | # Check if chromium is already installed
2843 | chromium_path = os.path.expanduser("~/Library/Caches/ms-playwright/chromium-1179")
2844 | if os.path.exists(chromium_path):
2845 | logger.info("Playwright Chromium browser already installed.")
2846 | return
2847 |
2848 | logger.info("Installing Playwright Chromium browser for KIK tool...")
2849 | result = subprocess.run(
2850 | ["python", "-m", "playwright", "install", "chromium"],
2851 | capture_output=True,
2852 | text=True,
2853 | timeout=300 # 5 minutes timeout
2854 | )
2855 |
2856 | if result.returncode == 0:
2857 | logger.info("Playwright Chromium browser installed successfully.")
2858 | else:
2859 | logger.warning(f"Failed to install Playwright browser: {result.stderr}")
2860 | logger.warning("KIK tool may not work properly without Playwright browsers.")
2861 |
2862 | except Exception as e:
2863 | logger.warning(f"Could not auto-install Playwright browsers: {e}")
2864 | logger.warning("KIK tool may not work properly. Manual installation: 'playwright install chromium'")
2865 |
2866 | def main():
2867 | logger.info(f"Starting {app.name} server via main() function...")
2868 | logger.info(f"Logs will be written to: {LOG_FILE_PATH}")
2869 |
2870 | # Ensure Playwright browsers are installed
2871 | ensure_playwright_browsers()
2872 |
2873 | try:
2874 | app.run()
2875 | except KeyboardInterrupt:
2876 | logger.info("Server shut down by user (KeyboardInterrupt).")
2877 | except Exception as e:
2878 | logger.exception("Server failed to start or crashed.")
2879 | finally:
2880 | logger.info(f"{app.name} server has shut down.")
2881 |
2882 | if __name__ == "__main__":
2883 | main()
```