This is page 3 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/run_asgi.py:
--------------------------------------------------------------------------------
```python
1 | #!/usr/bin/env python3
2 | """
3 | Standalone ASGI server runner for Yargı MCP
4 |
5 | This script provides a simple way to run the Yargı MCP server
6 | as a web service using uvicorn.
7 |
8 | Usage:
9 | python run_asgi.py
10 | python run_asgi.py --host 0.0.0.0 --port 8080
11 | python run_asgi.py --reload # For development
12 | """
13 |
14 | import os
15 | import sys
16 | import argparse
17 | import logging
18 | from pathlib import Path
19 |
20 | # Add project root to Python path
21 | sys.path.insert(0, str(Path(__file__).parent))
22 |
23 | try:
24 | import uvicorn
25 | except ImportError:
26 | print("Error: uvicorn is not installed.")
27 | print("Please install it with: pip install uvicorn")
28 | sys.exit(1)
29 |
30 | # Configure logging
31 | logging.basicConfig(
32 | level=logging.INFO,
33 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
34 | )
35 |
36 | def main():
37 | parser = argparse.ArgumentParser(
38 | description="Run Yargı MCP server as an ASGI web service"
39 | )
40 | parser.add_argument(
41 | "--host",
42 | type=str,
43 | default=os.getenv("HOST", "127.0.0.1"),
44 | help="Host to bind to (default: 127.0.0.1)"
45 | )
46 | parser.add_argument(
47 | "--port",
48 | type=int,
49 | default=int(os.getenv("PORT", "8000")),
50 | help="Port to bind to (default: 8000)"
51 | )
52 | parser.add_argument(
53 | "--reload",
54 | action="store_true",
55 | help="Enable auto-reload for development"
56 | )
57 | parser.add_argument(
58 | "--transport",
59 | choices=["http", "sse"],
60 | default="http",
61 | help="Transport type (default: http)"
62 | )
63 | parser.add_argument(
64 | "--log-level",
65 | choices=["debug", "info", "warning", "error"],
66 | default=os.getenv("LOG_LEVEL", "info").lower(),
67 | help="Log level (default: info)"
68 | )
69 | parser.add_argument(
70 | "--workers",
71 | type=int,
72 | default=1,
73 | help="Number of worker processes (default: 1)"
74 | )
75 |
76 | args = parser.parse_args()
77 |
78 | # Select app based on transport
79 | app_name = "asgi_app:app" if args.transport == "http" else "asgi_app:sse_app"
80 |
81 | # Configure uvicorn
82 | config = {
83 | "app": app_name,
84 | "host": args.host,
85 | "port": args.port,
86 | "log_level": args.log_level,
87 | "reload": args.reload,
88 | "access_log": True,
89 | }
90 |
91 | # Add workers only if not in reload mode
92 | if not args.reload and args.workers > 1:
93 | config["workers"] = args.workers
94 |
95 | # Print startup information
96 | print(f"Starting Yargı MCP server...")
97 | print(f"Host: {args.host}")
98 | print(f"Port: {args.port}")
99 | print(f"Transport: {args.transport}")
100 | print(f"Log level: {args.log_level}")
101 | if args.reload:
102 | print("Auto-reload: enabled")
103 | else:
104 | print(f"Workers: {args.workers}")
105 | print(f"\nServer will be available at: http://{args.host}:{args.port}")
106 | print(f"MCP endpoint: http://{args.host}:{args.port}/mcp/")
107 | print(f"Health check: http://{args.host}:{args.port}/health")
108 | print(f"API status: http://{args.host}:{args.port}/status")
109 | print("\nPress CTRL+C to stop the server\n")
110 |
111 | # Run uvicorn
112 | try:
113 | uvicorn.run(**config)
114 | except KeyboardInterrupt:
115 | print("\nShutting down server...")
116 | sys.exit(0)
117 |
118 | if __name__ == "__main__":
119 | main()
```
--------------------------------------------------------------------------------
/rekabet_mcp_module/models.py:
--------------------------------------------------------------------------------
```python
1 | # rekabet_mcp_module/models.py
2 |
3 | from pydantic import BaseModel, Field, HttpUrl
4 | from typing import List, Optional, Any
5 | from enum import Enum
6 |
7 | # Enum for decision type GUIDs (used by the client and expected by the website)
8 | class RekabetKararTuruGuidEnum(str, Enum):
9 | TUMU = "ALL" # Represents "All" or "Select Decision Type"
10 | BIRLESME_DEVRALMA = "2fff0979-9f9d-42d7-8c2e-a30705889542" # Merger and Acquisition
11 | DIGER = "dda8feaf-c919-405c-9da1-823f22b45ad9" # Other
12 | MENFI_TESPIT_MUAFIYET = "95ccd210-5304-49c5-b9e0-8ee53c50d4e8" # Negative Clearance and Exemption
13 | OZELLESTIRME = "e1f14505-842b-4af5-95d1-312d6de1a541" # Privatization
14 | REKABET_IHLALI = "720614bf-efd1-4dca-9785-b98eb65f2677" # Competition Infringement
15 |
16 | # Enum for user-friendly decision type names (for server tool parameters)
17 | # These correspond to the display names on the website's select dropdown.
18 | class RekabetKararTuruAdiEnum(str, Enum):
19 | TUMU = "Tümü" # Corresponds to the empty value "" for GUID, meaning "All"
20 | BIRLESME_VE_DEVRALMA = "Birleşme ve Devralma"
21 | DIGER = "Diğer"
22 | MENFI_TESPIT_VE_MUAFIYET = "Menfi Tespit ve Muafiyet"
23 | OZELLESTIRME = "Özelleştirme"
24 | REKABET_IHLALI = "Rekabet İhlali"
25 |
26 | class RekabetKurumuSearchRequest(BaseModel):
27 | """Model for Rekabet Kurumu (Turkish Competition Authority) search request."""
28 | sayfaAdi: str = Field("", description="Title")
29 | YayinlanmaTarihi: str = Field("", description="Date")
30 | PdfText: str = Field("", description="Text")
31 | KararTuruID: RekabetKararTuruGuidEnum = Field(RekabetKararTuruGuidEnum.TUMU, description="Type")
32 | KararSayisi: str = Field("", description="No")
33 | KararTarihi: str = Field("", description="Date")
34 | page: int = Field(1, ge=1, description="Page")
35 |
36 | class RekabetDecisionSummary(BaseModel):
37 | """Model for a single Rekabet Kurumu decision summary from search results."""
38 | publication_date: str = Field("", description="Pub date")
39 | decision_number: str = Field("", description="Number")
40 | decision_date: str = Field("", description="Date")
41 | decision_type_text: str = Field("", description="Type")
42 | title: str = Field("", description="Title")
43 | decision_url: str = Field("", description="URL")
44 | karar_id: str = Field("", description="ID")
45 | related_cases_url: str = Field("", description="Cases URL")
46 |
47 | class RekabetSearchResult(BaseModel):
48 | """Model for the overall search result for Rekabet Kurumu decisions."""
49 | decisions: List[RekabetDecisionSummary]
50 | total_records_found: int = Field(0, description="Total")
51 | retrieved_page_number: int = Field(description="Page")
52 | total_pages: int = Field(0, description="Pages")
53 |
54 | class RekabetDocument(BaseModel):
55 | """
56 | Model for a Rekabet Kurumu decision document.
57 | Contains metadata from the landing page, a link to the PDF,
58 | and the PDF's content converted to paginated Markdown.
59 | """
60 | source_landing_page_url: HttpUrl = Field(description="Source URL")
61 | karar_id: str = Field(description="ID")
62 |
63 | title_on_landing_page: Optional[str] = Field(None, description="Title")
64 | pdf_url: Optional[HttpUrl] = Field(None, description="PDF URL")
65 |
66 | markdown_chunk: Optional[str] = Field(None, description="Content")
67 | current_page: int = Field(1, description="Page")
68 | total_pages: int = Field(1, description="Total pages")
69 | is_paginated: bool = Field(False, description="Paginated")
70 |
71 | error_message: Optional[str] = Field(None, description="Error")
```
--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/rekabet_mcp_module/models.py:
--------------------------------------------------------------------------------
```python
1 | # rekabet_mcp_module/models.py
2 |
3 | from pydantic import BaseModel, Field, HttpUrl
4 | from typing import List, Optional, Any
5 | from enum import Enum
6 |
7 | # Enum for decision type GUIDs (used by the client and expected by the website)
8 | class RekabetKararTuruGuidEnum(str, Enum):
9 | TUMU = "ALL" # Represents "All" or "Select Decision Type"
10 | BIRLESME_DEVRALMA = "2fff0979-9f9d-42d7-8c2e-a30705889542" # Merger and Acquisition
11 | DIGER = "dda8feaf-c919-405c-9da1-823f22b45ad9" # Other
12 | MENFI_TESPIT_MUAFIYET = "95ccd210-5304-49c5-b9e0-8ee53c50d4e8" # Negative Clearance and Exemption
13 | OZELLESTIRME = "e1f14505-842b-4af5-95d1-312d6de1a541" # Privatization
14 | REKABET_IHLALI = "720614bf-efd1-4dca-9785-b98eb65f2677" # Competition Infringement
15 |
16 | # Enum for user-friendly decision type names (for server tool parameters)
17 | # These correspond to the display names on the website's select dropdown.
18 | class RekabetKararTuruAdiEnum(str, Enum):
19 | TUMU = "Tümü" # Corresponds to the empty value "" for GUID, meaning "All"
20 | BIRLESME_VE_DEVRALMA = "Birleşme ve Devralma"
21 | DIGER = "Diğer"
22 | MENFI_TESPIT_VE_MUAFIYET = "Menfi Tespit ve Muafiyet"
23 | OZELLESTIRME = "Özelleştirme"
24 | REKABET_IHLALI = "Rekabet İhlali"
25 |
26 | class RekabetKurumuSearchRequest(BaseModel):
27 | """Model for Rekabet Kurumu (Turkish Competition Authority) search request."""
28 | sayfaAdi: str = Field("", description="Title")
29 | YayinlanmaTarihi: str = Field("", description="Date")
30 | PdfText: str = Field("", description="Text")
31 | KararTuruID: RekabetKararTuruGuidEnum = Field(RekabetKararTuruGuidEnum.TUMU, description="Type")
32 | KararSayisi: str = Field("", description="No")
33 | KararTarihi: str = Field("", description="Date")
34 | page: int = Field(1, ge=1, description="Page")
35 |
36 | class RekabetDecisionSummary(BaseModel):
37 | """Model for a single Rekabet Kurumu decision summary from search results."""
38 | publication_date: str = Field("", description="Pub date")
39 | decision_number: str = Field("", description="Number")
40 | decision_date: str = Field("", description="Date")
41 | decision_type_text: str = Field("", description="Type")
42 | title: str = Field("", description="Title")
43 | decision_url: str = Field("", description="URL")
44 | karar_id: str = Field("", description="ID")
45 | related_cases_url: str = Field("", description="Cases URL")
46 |
47 | class RekabetSearchResult(BaseModel):
48 | """Model for the overall search result for Rekabet Kurumu decisions."""
49 | decisions: List[RekabetDecisionSummary]
50 | total_records_found: int = Field(0, description="Total")
51 | retrieved_page_number: int = Field(description="Page")
52 | total_pages: int = Field(0, description="Pages")
53 |
54 | class RekabetDocument(BaseModel):
55 | """
56 | Model for a Rekabet Kurumu decision document.
57 | Contains metadata from the landing page, a link to the PDF,
58 | and the PDF's content converted to paginated Markdown.
59 | """
60 | source_landing_page_url: HttpUrl = Field(description="Source URL")
61 | karar_id: str = Field(description="ID")
62 |
63 | title_on_landing_page: Optional[str] = Field(None, description="Title")
64 | pdf_url: Optional[HttpUrl] = Field(None, description="PDF URL")
65 |
66 | markdown_chunk: Optional[str] = Field(None, description="Content")
67 | current_page: int = Field(1, description="Page")
68 | total_pages: int = Field(1, description="Total pages")
69 | is_paginated: bool = Field(False, description="Paginated")
70 |
71 | error_message: Optional[str] = Field(None, description="Error")
```
--------------------------------------------------------------------------------
/kik_mcp_module/models.py:
--------------------------------------------------------------------------------
```python
1 | # kik_mcp_module/models.py
2 | from pydantic import BaseModel, Field, HttpUrl, computed_field, ConfigDict
3 | from typing import List, Optional
4 | from enum import Enum
5 | import base64 # Base64 encoding/decoding için
6 |
7 | class KikKararTipi(str, Enum):
8 | """Enum for KIK (Public Procurement Authority) Decision Types."""
9 | UYUSMAZLIK = "rbUyusmazlik"
10 | DUZENLEYICI = "rbDuzenleyici"
11 | MAHKEME = "rbMahkeme"
12 |
13 | class KikSearchRequest(BaseModel):
14 | """Model for KIK Decision search criteria."""
15 | karar_tipi: KikKararTipi = Field(KikKararTipi.UYUSMAZLIK, description="Type")
16 | karar_no: str = Field("", description="No")
17 | karar_tarihi_baslangic: str = Field("", description="Start", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$")
18 | karar_tarihi_bitis: str = Field("", description="End", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$")
19 | resmi_gazete_sayisi: str = Field("", description="Gazette")
20 | resmi_gazete_tarihi: str = Field("", description="Date", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$")
21 | basvuru_konusu_ihale: str = Field("", description="Subject")
22 | basvuru_sahibi: str = Field("", description="Applicant")
23 | ihaleyi_yapan_idare: str = Field("", description="Entity")
24 | yil: str = Field("", description="Year")
25 | karar_metni: str = Field("", description="Text")
26 | page: int = Field(1, ge=1, description="Page")
27 |
28 | class KikDecisionEntry(BaseModel):
29 | """Represents a single decision entry from KIK search results."""
30 | preview_event_target: str = Field(..., description="Event target")
31 | karar_no_str: str = Field(..., alias="kararNo", description="Decision number")
32 | karar_tipi: KikKararTipi = Field(..., description="Decision type")
33 |
34 | karar_tarihi_str: str = Field(..., alias="kararTarihi", description="Date")
35 | idare_str: str = Field("", alias="idare", description="Entity")
36 | basvuru_sahibi_str: str = Field("", alias="basvuruSahibi", description="Applicant")
37 | ihale_konusu_str: str = Field("", alias="ihaleKonusu", description="Subject")
38 |
39 | @computed_field
40 | @property
41 | def karar_id(self) -> str:
42 | """
43 | A Base64 encoded unique ID for the decision, combining decision type and number.
44 | Format before encoding: "{karar_tipi.value}|{karar_no_str}"
45 | """
46 | combined_key = f"{self.karar_tipi.value}|{self.karar_no_str}"
47 | return base64.b64encode(combined_key.encode('utf-8')).decode('utf-8')
48 |
49 | model_config = ConfigDict(populate_by_name=True)
50 |
51 | class KikSearchResult(BaseModel):
52 | """Model for KIK search results."""
53 | decisions: List[KikDecisionEntry]
54 | total_records: int = 0
55 | current_page: int = 1
56 |
57 | class KikDocumentMarkdown(BaseModel):
58 | """
59 | KIK decision document, with Markdown content potentially paginated.
60 | """
61 | retrieved_with_karar_id: Optional[str] = Field(None, description="Request ID")
62 | retrieved_karar_no: Optional[str] = Field(None, description="Decision number")
63 | retrieved_karar_tipi: Optional[KikKararTipi] = Field(None, description="Decision type")
64 |
65 | karar_id_param_from_url: Optional[str] = Field(None, alias="kararIdParam", description="Internal ID")
66 | markdown_chunk: Optional[str] = Field(None, description="Content")
67 | source_url: Optional[str] = Field(None, description="Source URL")
68 | error_message: Optional[str] = Field(None, description="Error")
69 | current_page: int = Field(1, description="Page")
70 | total_pages: int = Field(1, description="Total pages")
71 | is_paginated: bool = Field(False, description="Paginated")
72 | full_content_char_count: Optional[int] = Field(None, description="Char count")
73 |
74 | model_config = ConfigDict(populate_by_name=True)
```
--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/kik_mcp_module/models.py:
--------------------------------------------------------------------------------
```python
1 | # kik_mcp_module/models.py
2 | from pydantic import BaseModel, Field, HttpUrl, computed_field, ConfigDict
3 | from typing import List, Optional
4 | from enum import Enum
5 | import base64 # Base64 encoding/decoding için
6 |
7 | class KikKararTipi(str, Enum):
8 | """Enum for KIK (Public Procurement Authority) Decision Types."""
9 | UYUSMAZLIK = "rbUyusmazlik"
10 | DUZENLEYICI = "rbDuzenleyici"
11 | MAHKEME = "rbMahkeme"
12 |
13 | class KikSearchRequest(BaseModel):
14 | """Model for KIK Decision search criteria."""
15 | karar_tipi: KikKararTipi = Field(KikKararTipi.UYUSMAZLIK, description="Type")
16 | karar_no: str = Field("", description="No")
17 | karar_tarihi_baslangic: str = Field("", description="Start", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$")
18 | karar_tarihi_bitis: str = Field("", description="End", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$")
19 | resmi_gazete_sayisi: str = Field("", description="Gazette")
20 | resmi_gazete_tarihi: str = Field("", description="Date", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$")
21 | basvuru_konusu_ihale: str = Field("", description="Subject")
22 | basvuru_sahibi: str = Field("", description="Applicant")
23 | ihaleyi_yapan_idare: str = Field("", description="Entity")
24 | yil: str = Field("", description="Year")
25 | karar_metni: str = Field("", description="Text")
26 | page: int = Field(1, ge=1, description="Page")
27 |
28 | class KikDecisionEntry(BaseModel):
29 | """Represents a single decision entry from KIK search results."""
30 | preview_event_target: str = Field(..., description="Event target")
31 | karar_no_str: str = Field(..., alias="kararNo", description="Decision number")
32 | karar_tipi: KikKararTipi = Field(..., description="Decision type")
33 |
34 | karar_tarihi_str: str = Field(..., alias="kararTarihi", description="Date")
35 | idare_str: str = Field("", alias="idare", description="Entity")
36 | basvuru_sahibi_str: str = Field("", alias="basvuruSahibi", description="Applicant")
37 | ihale_konusu_str: str = Field("", alias="ihaleKonusu", description="Subject")
38 |
39 | @computed_field
40 | @property
41 | def karar_id(self) -> str:
42 | """
43 | A Base64 encoded unique ID for the decision, combining decision type and number.
44 | Format before encoding: "{karar_tipi.value}|{karar_no_str}"
45 | """
46 | combined_key = f"{self.karar_tipi.value}|{self.karar_no_str}"
47 | return base64.b64encode(combined_key.encode('utf-8')).decode('utf-8')
48 |
49 | model_config = ConfigDict(populate_by_name=True)
50 |
51 | class KikSearchResult(BaseModel):
52 | """Model for KIK search results."""
53 | decisions: List[KikDecisionEntry]
54 | total_records: int = 0
55 | current_page: int = 1
56 |
57 | class KikDocumentMarkdown(BaseModel):
58 | """
59 | KIK decision document, with Markdown content potentially paginated.
60 | """
61 | retrieved_with_karar_id: Optional[str] = Field(None, description="Request ID")
62 | retrieved_karar_no: Optional[str] = Field(None, description="Decision number")
63 | retrieved_karar_tipi: Optional[KikKararTipi] = Field(None, description="Decision type")
64 |
65 | karar_id_param_from_url: Optional[str] = Field(None, alias="kararIdParam", description="Internal ID")
66 | markdown_chunk: Optional[str] = Field(None, description="Content")
67 | source_url: Optional[str] = Field(None, description="Source URL")
68 | error_message: Optional[str] = Field(None, description="Error")
69 | current_page: int = Field(1, description="Page")
70 | total_pages: int = Field(1, description="Total pages")
71 | is_paginated: bool = Field(False, description="Paginated")
72 | full_content_char_count: Optional[int] = Field(None, description="Char count")
73 |
74 | model_config = ConfigDict(populate_by_name=True)
```
--------------------------------------------------------------------------------
/bedesten_mcp_module/models.py:
--------------------------------------------------------------------------------
```python
1 | # bedesten_mcp_module/models.py
2 |
3 | from pydantic import BaseModel, Field
4 | from typing import List, Optional, Dict, Any, Literal, Union
5 | from datetime import datetime
6 |
7 | # Import compressed BirimAdiEnum for chamber filtering
8 | from .enums import BirimAdiEnum
9 |
10 | # Court Type Options for Unified Search
11 | BedestenCourtTypeEnum = Literal[
12 | "YARGITAYKARARI", # Yargıtay (Court of Cassation)
13 | "DANISTAYKARAR", # Danıştay (Council of State)
14 | "YERELHUKUK", # Local Civil Courts
15 | "ISTINAFHUKUK", # Civil Courts of Appeals
16 | "KYB" # Extraordinary Appeals (Kanun Yararına Bozma)
17 | ]
18 |
19 | # Search Request Models
20 | class BedestenSearchData(BaseModel):
21 | pageSize: int = Field(..., description="Results per page (1-10)")
22 | pageNumber: int = Field(..., description="Page number (1-indexed)")
23 | itemTypeList: List[str] = Field(..., description="Court type filter (YARGITAYKARARI/DANISTAYKARAR/YERELHUKUK/ISTINAFHUKUK/KYB)")
24 | phrase: str = Field(..., description="Search phrase. Supports: 'word', \"exact phrase\", +required, -exclude, AND/OR/NOT operators. No wildcards or regex.")
25 | birimAdi: BirimAdiEnum = Field("ALL", description="""
26 | Chamber filter (optional). Abbreviated values with Turkish names:
27 | • 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)
28 | • 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)
29 | """)
30 | kararTarihiStart: Optional[str] = Field(None, description="Start date (ISO 8601 format)")
31 | kararTarihiEnd: Optional[str] = Field(None, description="End date (ISO 8601 format)")
32 | sortFields: List[str] = Field(default=["KARAR_TARIHI"], description="Sort fields")
33 | sortDirection: str = Field(default="desc", description="Sort direction (asc/desc)")
34 |
35 | class BedestenSearchRequest(BaseModel):
36 | data: BedestenSearchData
37 | applicationName: str = "UyapMevzuat"
38 | paging: bool = True
39 |
40 | # Search Response Models
41 | class BedestenItemType(BaseModel):
42 | name: str
43 | description: str
44 |
45 | class BedestenDecisionEntry(BaseModel):
46 | documentId: str
47 | itemType: BedestenItemType
48 | birimId: Optional[str] = None
49 | birimAdi: Optional[str]
50 | esasNoYil: Optional[int] = None
51 | esasNoSira: Optional[int] = None
52 | kararNoYil: Optional[int] = None
53 | kararNoSira: Optional[int] = None
54 | kararTuru: Optional[str] = None
55 | kararTarihi: str
56 | kararTarihiStr: str
57 | kesinlesmeDurumu: Optional[str] = None
58 | kararNo: Optional[str] = None
59 | esasNo: Optional[str] = None
60 |
61 | class BedestenSearchDataResponse(BaseModel):
62 | emsalKararList: List[BedestenDecisionEntry]
63 | total: int
64 | start: int
65 |
66 | class BedestenSearchResponse(BaseModel):
67 | data: Optional[BedestenSearchDataResponse]
68 | metadata: Dict[str, Any]
69 |
70 | # Document Request/Response Models
71 | class BedestenDocumentRequestData(BaseModel):
72 | documentId: str
73 |
74 | class BedestenDocumentRequest(BaseModel):
75 | data: BedestenDocumentRequestData
76 | applicationName: str = "UyapMevzuat"
77 |
78 | class BedestenDocumentData(BaseModel):
79 | content: str # Base64 encoded HTML or PDF
80 | mimeType: str
81 | version: int
82 |
83 | class BedestenDocumentResponse(BaseModel):
84 | data: BedestenDocumentData
85 | metadata: Dict[str, Any]
86 |
87 | class BedestenDocumentMarkdown(BaseModel):
88 | documentId: str = Field(..., description="The document ID (Belge Kimliği) from Bedesten")
89 | markdown_content: Optional[str] = Field(None, description="The decision content (Karar İçeriği) converted to Markdown")
90 | source_url: str = Field(..., description="The source URL (Kaynak URL) of the document")
91 | mime_type: Optional[str] = Field(None, description="Original content type (İçerik Türü) (text/html or application/pdf)")
```
--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/models.py:
--------------------------------------------------------------------------------
```python
1 | # bedesten_mcp_module/models.py
2 |
3 | from pydantic import BaseModel, Field
4 | from typing import List, Optional, Dict, Any, Literal, Union
5 | from datetime import datetime
6 |
7 | # Import compressed BirimAdiEnum for chamber filtering
8 | from .enums import BirimAdiEnum
9 |
10 | # Court Type Options for Unified Search
11 | BedestenCourtTypeEnum = Literal[
12 | "YARGITAYKARARI", # Yargıtay (Court of Cassation)
13 | "DANISTAYKARAR", # Danıştay (Council of State)
14 | "YERELHUKUK", # Local Civil Courts
15 | "ISTINAFHUKUK", # Civil Courts of Appeals
16 | "KYB" # Extraordinary Appeals (Kanun Yararına Bozma)
17 | ]
18 |
19 | # Search Request Models
20 | class BedestenSearchData(BaseModel):
21 | pageSize: int = Field(..., description="Results per page (1-10)")
22 | pageNumber: int = Field(..., description="Page number (1-indexed)")
23 | itemTypeList: List[str] = Field(..., description="Court type filter (YARGITAYKARARI/DANISTAYKARAR/YERELHUKUK/ISTINAFHUKUK/KYB)")
24 | phrase: str = Field(..., description="Search phrase. Supports: 'word', \"exact phrase\", +required, -exclude, AND/OR/NOT operators. No wildcards or regex.")
25 | birimAdi: BirimAdiEnum = Field("ALL", description="""
26 | Chamber filter (optional). Abbreviated values with Turkish names:
27 | • 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)
28 | • 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)
29 | """)
30 | kararTarihiStart: Optional[str] = Field(None, description="Start date (ISO 8601 format)")
31 | kararTarihiEnd: Optional[str] = Field(None, description="End date (ISO 8601 format)")
32 | sortFields: List[str] = Field(default=["KARAR_TARIHI"], description="Sort fields")
33 | sortDirection: str = Field(default="desc", description="Sort direction (asc/desc)")
34 |
35 | class BedestenSearchRequest(BaseModel):
36 | data: BedestenSearchData
37 | applicationName: str = "UyapMevzuat"
38 | paging: bool = True
39 |
40 | # Search Response Models
41 | class BedestenItemType(BaseModel):
42 | name: str
43 | description: str
44 |
45 | class BedestenDecisionEntry(BaseModel):
46 | documentId: str
47 | itemType: BedestenItemType
48 | birimId: Optional[str] = None
49 | birimAdi: Optional[str]
50 | esasNoYil: Optional[int] = None
51 | esasNoSira: Optional[int] = None
52 | kararNoYil: Optional[int] = None
53 | kararNoSira: Optional[int] = None
54 | kararTuru: Optional[str] = None
55 | kararTarihi: str
56 | kararTarihiStr: str
57 | kesinlesmeDurumu: Optional[str] = None
58 | kararNo: Optional[str] = None
59 | esasNo: Optional[str] = None
60 |
61 | class BedestenSearchDataResponse(BaseModel):
62 | emsalKararList: List[BedestenDecisionEntry]
63 | total: int
64 | start: int
65 |
66 | class BedestenSearchResponse(BaseModel):
67 | data: Optional[BedestenSearchDataResponse]
68 | metadata: Dict[str, Any]
69 |
70 | # Document Request/Response Models
71 | class BedestenDocumentRequestData(BaseModel):
72 | documentId: str
73 |
74 | class BedestenDocumentRequest(BaseModel):
75 | data: BedestenDocumentRequestData
76 | applicationName: str = "UyapMevzuat"
77 |
78 | class BedestenDocumentData(BaseModel):
79 | content: str # Base64 encoded HTML or PDF
80 | mimeType: str
81 | version: int
82 |
83 | class BedestenDocumentResponse(BaseModel):
84 | data: BedestenDocumentData
85 | metadata: Dict[str, Any]
86 |
87 | class BedestenDocumentMarkdown(BaseModel):
88 | documentId: str = Field(..., description="The document ID (Belge Kimliği) from Bedesten")
89 | markdown_content: Optional[str] = Field(None, description="The decision content (Karar İçeriği) converted to Markdown")
90 | source_url: str = Field(..., description="The source URL (Kaynak URL) of the document")
91 | mime_type: Optional[str] = Field(None, description="Original content type (İçerik Türü) (text/html or application/pdf)")
```
--------------------------------------------------------------------------------
/mcp_auth/storage.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | Persistent storage for OAuth sessions and tokens
3 | """
4 |
5 | import json
6 | import os
7 | import tempfile
8 | import logging
9 | from datetime import datetime
10 | from typing import Dict, Any, Optional
11 |
12 | logger = logging.getLogger(__name__)
13 |
14 |
15 | class PersistentStorage:
16 | """File-based persistent storage for OAuth data"""
17 |
18 | def __init__(self, storage_dir: str = None):
19 | if storage_dir is None:
20 | # Use system temp directory or environment variable
21 | storage_dir = os.environ.get('TEMP', tempfile.gettempdir())
22 |
23 | self.storage_dir = os.path.join(storage_dir, 'mcp_oauth_storage')
24 | os.makedirs(self.storage_dir, exist_ok=True)
25 |
26 | self.sessions_file = os.path.join(self.storage_dir, 'oauth_sessions.json')
27 | self.tokens_file = os.path.join(self.storage_dir, 'oauth_tokens.json')
28 |
29 | logger.info(f"Persistent OAuth storage initialized at: {self.storage_dir}")
30 |
31 | def _load_json(self, filepath: str) -> Dict:
32 | """Load JSON data from file"""
33 | try:
34 | if os.path.exists(filepath):
35 | with open(filepath, 'r', encoding='utf-8') as f:
36 | return json.load(f)
37 | except Exception as e:
38 | logger.error(f"Error loading {filepath}: {e}")
39 | return {}
40 |
41 | def _save_json(self, filepath: str, data: Dict):
42 | """Save JSON data to file"""
43 | try:
44 | with open(filepath, 'w', encoding='utf-8') as f:
45 | json.dump(data, f, indent=2, default=str)
46 | except Exception as e:
47 | logger.error(f"Error saving {filepath}: {e}")
48 |
49 | def get_sessions(self) -> Dict[str, Dict[str, Any]]:
50 | """Get all OAuth sessions"""
51 | data = self._load_json(self.sessions_file)
52 | # Clean expired sessions
53 | now = datetime.utcnow().timestamp()
54 | valid_sessions = {k: v for k, v in data.items()
55 | if v.get('expires_at', 0) > now}
56 | if len(valid_sessions) != len(data):
57 | self._save_json(self.sessions_file, valid_sessions)
58 | return valid_sessions
59 |
60 | def set_session(self, session_id: str, data: Dict[str, Any]):
61 | """Set OAuth session data"""
62 | sessions = self.get_sessions()
63 | sessions[session_id] = data
64 | self._save_json(self.sessions_file, sessions)
65 |
66 | def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
67 | """Get specific OAuth session data"""
68 | sessions = self.get_sessions()
69 | return sessions.get(session_id)
70 |
71 | def delete_session(self, session_id: str):
72 | """Delete OAuth session"""
73 | sessions = self.get_sessions()
74 | if session_id in sessions:
75 | del sessions[session_id]
76 | self._save_json(self.sessions_file, sessions)
77 |
78 | def get_tokens(self) -> Dict[str, Dict[str, Any]]:
79 | """Get all OAuth tokens"""
80 | data = self._load_json(self.tokens_file)
81 | # Clean expired tokens
82 | now = datetime.utcnow().timestamp()
83 | valid_tokens = {k: v for k, v in data.items()
84 | if v.get('expires_at', 0) > now}
85 | if len(valid_tokens) != len(data):
86 | self._save_json(self.tokens_file, valid_tokens)
87 | return valid_tokens
88 |
89 | def set_token(self, token_id: str, token_data: Dict[str, Any]):
90 | """Set OAuth token data"""
91 | tokens = self.get_tokens()
92 | tokens[token_id] = token_data
93 | self._save_json(self.tokens_file, tokens)
94 |
95 | def get_token(self, token_id: str) -> Optional[Dict[str, Any]]:
96 | """Get specific OAuth token data"""
97 | tokens = self.get_tokens()
98 | return tokens.get(token_id)
99 |
100 | def delete_token(self, token_id: str):
101 | """Delete OAuth token"""
102 | tokens = self.get_tokens()
103 | if token_id in tokens:
104 | del tokens[token_id]
105 | self._save_json(self.tokens_file, tokens)
106 |
107 | def cleanup_expired_sessions(self):
108 | """Clean up expired sessions and tokens"""
109 | # This is handled automatically in get_sessions() and get_tokens()
110 | sessions = self.get_sessions()
111 | tokens = self.get_tokens()
112 | logger.debug(f"Cleanup: {len(sessions)} active sessions, {len(tokens)} active tokens")
```
--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/mcp_auth/storage.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | Persistent storage for OAuth sessions and tokens
3 | """
4 |
5 | import json
6 | import os
7 | import tempfile
8 | import logging
9 | from datetime import datetime
10 | from typing import Dict, Any, Optional
11 |
12 | logger = logging.getLogger(__name__)
13 |
14 |
15 | class PersistentStorage:
16 | """File-based persistent storage for OAuth data"""
17 |
18 | def __init__(self, storage_dir: str = None):
19 | if storage_dir is None:
20 | # Use system temp directory or environment variable
21 | storage_dir = os.environ.get('TEMP', tempfile.gettempdir())
22 |
23 | self.storage_dir = os.path.join(storage_dir, 'mcp_oauth_storage')
24 | os.makedirs(self.storage_dir, exist_ok=True)
25 |
26 | self.sessions_file = os.path.join(self.storage_dir, 'oauth_sessions.json')
27 | self.tokens_file = os.path.join(self.storage_dir, 'oauth_tokens.json')
28 |
29 | logger.info(f"Persistent OAuth storage initialized at: {self.storage_dir}")
30 |
31 | def _load_json(self, filepath: str) -> Dict:
32 | """Load JSON data from file"""
33 | try:
34 | if os.path.exists(filepath):
35 | with open(filepath, 'r', encoding='utf-8') as f:
36 | return json.load(f)
37 | except Exception as e:
38 | logger.error(f"Error loading {filepath}: {e}")
39 | return {}
40 |
41 | def _save_json(self, filepath: str, data: Dict):
42 | """Save JSON data to file"""
43 | try:
44 | with open(filepath, 'w', encoding='utf-8') as f:
45 | json.dump(data, f, indent=2, default=str)
46 | except Exception as e:
47 | logger.error(f"Error saving {filepath}: {e}")
48 |
49 | def get_sessions(self) -> Dict[str, Dict[str, Any]]:
50 | """Get all OAuth sessions"""
51 | data = self._load_json(self.sessions_file)
52 | # Clean expired sessions
53 | now = datetime.utcnow().timestamp()
54 | valid_sessions = {k: v for k, v in data.items()
55 | if v.get('expires_at', 0) > now}
56 | if len(valid_sessions) != len(data):
57 | self._save_json(self.sessions_file, valid_sessions)
58 | return valid_sessions
59 |
60 | def set_session(self, session_id: str, data: Dict[str, Any]):
61 | """Set OAuth session data"""
62 | sessions = self.get_sessions()
63 | sessions[session_id] = data
64 | self._save_json(self.sessions_file, sessions)
65 |
66 | def get_session(self, session_id: str) -> Optional[Dict[str, Any]]:
67 | """Get specific OAuth session data"""
68 | sessions = self.get_sessions()
69 | return sessions.get(session_id)
70 |
71 | def delete_session(self, session_id: str):
72 | """Delete OAuth session"""
73 | sessions = self.get_sessions()
74 | if session_id in sessions:
75 | del sessions[session_id]
76 | self._save_json(self.sessions_file, sessions)
77 |
78 | def get_tokens(self) -> Dict[str, Dict[str, Any]]:
79 | """Get all OAuth tokens"""
80 | data = self._load_json(self.tokens_file)
81 | # Clean expired tokens
82 | now = datetime.utcnow().timestamp()
83 | valid_tokens = {k: v for k, v in data.items()
84 | if v.get('expires_at', 0) > now}
85 | if len(valid_tokens) != len(data):
86 | self._save_json(self.tokens_file, valid_tokens)
87 | return valid_tokens
88 |
89 | def set_token(self, token_id: str, token_data: Dict[str, Any]):
90 | """Set OAuth token data"""
91 | tokens = self.get_tokens()
92 | tokens[token_id] = token_data
93 | self._save_json(self.tokens_file, tokens)
94 |
95 | def get_token(self, token_id: str) -> Optional[Dict[str, Any]]:
96 | """Get specific OAuth token data"""
97 | tokens = self.get_tokens()
98 | return tokens.get(token_id)
99 |
100 | def delete_token(self, token_id: str):
101 | """Delete OAuth token"""
102 | tokens = self.get_tokens()
103 | if token_id in tokens:
104 | del tokens[token_id]
105 | self._save_json(self.tokens_file, tokens)
106 |
107 | def cleanup_expired_sessions(self):
108 | """Clean up expired sessions and tokens"""
109 | # This is handled automatically in get_sessions() and get_tokens()
110 | sessions = self.get_sessions()
111 | tokens = self.get_tokens()
112 | logger.debug(f"Cleanup: {len(sessions)} active sessions, {len(tokens)} active tokens")
```
--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/uyusmazlik_mcp_module/models.py:
--------------------------------------------------------------------------------
```python
1 | # uyusmazlik_mcp_module/models.py
2 |
3 | from pydantic import BaseModel, Field, HttpUrl
4 | from typing import List, Optional
5 | from enum import Enum
6 |
7 | # Enum definitions for user-friendly input based on the provided HTML form
8 | class UyusmazlikBolumEnum(str, Enum):
9 | """User-friendly names for 'BolumId'."""
10 | TUMU = "ALL" # Represents "...Seçiniz..." or all
11 | CEZA_BOLUMU = "Ceza Bölümü"
12 | GENEL_KURUL_KARARLARI = "Genel Kurul Kararları"
13 | HUKUK_BOLUMU = "Hukuk Bölümü"
14 |
15 | class UyusmazlikTuruEnum(str, Enum):
16 | """User-friendly names for 'UyusmazlikId'."""
17 | TUMU = "ALL" # Represents "...Seçiniz..." or all
18 | GOREV_UYUSMAZLIGI = "Görev Uyuşmazlığı"
19 | HUKUM_UYUSMAZLIGI = "Hüküm Uyuşmazlığı"
20 |
21 | class UyusmazlikKararSonucuEnum(str, Enum): # Based on checkbox text in the form
22 | """User-friendly names for 'KararSonucuList' items."""
23 | HUKUM_UYUSMAZLIGI_OLMADIGINA_DAIR = "Hüküm Uyuşmazlığı Olmadığına Dair"
24 | HUKUM_UYUSMAZLIGI_OLDUGUNA_DAIR = "Hüküm Uyuşmazlığı Olduğuna Dair"
25 | # Add other "Karar Sonucu" options from the form's checkboxes as Enum members
26 | # Example: GOREVLI_YARGI_YERI_ADLI = "Görevli Yargı Yeri Belirlenmesine Dair (Adli Yargı)"
27 | # The client will map these enum values (which are strings) to their respective IDs.
28 |
29 | class UyusmazlikSearchRequest(BaseModel): # This is the model the MCP tool will accept
30 | """Model for Uyuşmazlık Mahkemesi search request using user-friendly terms."""
31 | icerik: Optional[str] = Field("", description="Search text")
32 |
33 | bolum: Optional[UyusmazlikBolumEnum] = Field(
34 | UyusmazlikBolumEnum.TUMU,
35 | description="Department"
36 | )
37 | uyusmazlik_turu: Optional[UyusmazlikTuruEnum] = Field(
38 | UyusmazlikTuruEnum.TUMU,
39 | description="Dispute type"
40 | )
41 |
42 | # User provides a list of user-friendly names for Karar Sonucu
43 | karar_sonuclari: Optional[List[UyusmazlikKararSonucuEnum]] = Field( # Changed to list of Enums
44 | default_factory=list,
45 | description="Decision types"
46 | )
47 |
48 | esas_yil: Optional[str] = Field("", description="Case year")
49 | esas_sayisi: Optional[str] = Field("", description="Case no")
50 | karar_yil: Optional[str] = Field("", description="Decision year")
51 | karar_sayisi: Optional[str] = Field("", description="Decision no")
52 | kanun_no: Optional[str] = Field("", description="Law no")
53 |
54 | karar_date_begin: Optional[str] = Field("", description="Start date (DD.MM.YYYY)")
55 | karar_date_end: Optional[str] = Field("", description="End date (DD.MM.YYYY)")
56 |
57 | resmi_gazete_sayi: Optional[str] = Field("", description="Gazette no")
58 | resmi_gazete_date: Optional[str] = Field("", description="Gazette date (DD.MM.YYYY)")
59 |
60 | # Detailed text search fields from the "icerikDetail" section of the form
61 | tumce: Optional[str] = Field("", description="Exact phrase")
62 | wild_card: Optional[str] = Field("", description="Wildcard search")
63 | hepsi: Optional[str] = Field("", description="All words")
64 | herhangi_birisi: Optional[str] = Field("", description="Any word")
65 | not_hepsi: Optional[str] = Field("", description="Exclude words")
66 |
67 | class UyusmazlikApiDecisionEntry(BaseModel):
68 | """Model for an individual decision entry parsed from Uyuşmazlık API's HTML search response."""
69 | karar_sayisi: Optional[str] = Field(None)
70 | esas_sayisi: Optional[str] = Field(None)
71 | bolum: Optional[str] = Field(None)
72 | uyusmazlik_konusu: Optional[str] = Field(None)
73 | karar_sonucu: Optional[str] = Field(None)
74 | popover_content: Optional[str] = Field(None, description="Summary")
75 | document_url: HttpUrl # Full URL to the decision document HTML page
76 | pdf_url: Optional[HttpUrl] = Field(None, description="PDF URL")
77 |
78 | class UyusmazlikSearchResponse(BaseModel): # This is what the MCP tool will return
79 | """Response model for Uyuşmazlık Mahkemesi search results for the MCP tool."""
80 | decisions: List[UyusmazlikApiDecisionEntry]
81 | total_records_found: Optional[int] = Field(None, description="Total number of records found for the query, if available.")
82 |
83 | class UyusmazlikDocumentMarkdown(BaseModel):
84 | """Model for an Uyuşmazlık decision document, containing only Markdown content."""
85 | source_url: HttpUrl # The URL from which the content was fetched
86 | markdown_content: Optional[str] = Field(None, description="The decision content converted to Markdown.")
```
--------------------------------------------------------------------------------
/uyusmazlik_mcp_module/models.py:
--------------------------------------------------------------------------------
```python
1 | # uyusmazlik_mcp_module/models.py
2 |
3 | from pydantic import BaseModel, Field, HttpUrl
4 | from typing import List, Optional
5 | from enum import Enum
6 |
7 | # Enum definitions for user-friendly input based on the provided HTML form
8 | class UyusmazlikBolumEnum(str, Enum):
9 | """User-friendly names for 'BolumId'."""
10 | TUMU = "ALL" # Represents "...Seçiniz..." or all
11 | CEZA_BOLUMU = "Ceza Bölümü"
12 | GENEL_KURUL_KARARLARI = "Genel Kurul Kararları"
13 | HUKUK_BOLUMU = "Hukuk Bölümü"
14 |
15 | class UyusmazlikTuruEnum(str, Enum):
16 | """User-friendly names for 'UyusmazlikId'."""
17 | TUMU = "ALL" # Represents "...Seçiniz..." or all
18 | GOREV_UYUSMAZLIGI = "Görev Uyuşmazlığı"
19 | HUKUM_UYUSMAZLIGI = "Hüküm Uyuşmazlığı"
20 |
21 | class UyusmazlikKararSonucuEnum(str, Enum): # Based on checkbox text in the form
22 | """User-friendly names for 'KararSonucuList' items."""
23 | HUKUM_UYUSMAZLIGI_OLMADIGINA_DAIR = "Hüküm Uyuşmazlığı Olmadığına Dair"
24 | HUKUM_UYUSMAZLIGI_OLDUGUNA_DAIR = "Hüküm Uyuşmazlığı Olduğuna Dair"
25 | # Add other "Karar Sonucu" options from the form's checkboxes as Enum members
26 | # Example: GOREVLI_YARGI_YERI_ADLI = "Görevli Yargı Yeri Belirlenmesine Dair (Adli Yargı)"
27 | # The client will map these enum values (which are strings) to their respective IDs.
28 |
29 | class UyusmazlikSearchRequest(BaseModel): # This is the model the MCP tool will accept
30 | """Model for Uyuşmazlık Mahkemesi search request using user-friendly terms."""
31 | icerik: Optional[str] = Field("", description="Search text")
32 |
33 | bolum: Optional[UyusmazlikBolumEnum] = Field(
34 | UyusmazlikBolumEnum.TUMU,
35 | description="Department"
36 | )
37 | uyusmazlik_turu: Optional[UyusmazlikTuruEnum] = Field(
38 | UyusmazlikTuruEnum.TUMU,
39 | description="Dispute type"
40 | )
41 |
42 | # User provides a list of user-friendly names for Karar Sonucu
43 | karar_sonuclari: Optional[List[UyusmazlikKararSonucuEnum]] = Field( # Changed to list of Enums
44 | default_factory=list,
45 | description="Decision types"
46 | )
47 |
48 | esas_yil: Optional[str] = Field("", description="Case year")
49 | esas_sayisi: Optional[str] = Field("", description="Case no")
50 | karar_yil: Optional[str] = Field("", description="Decision year")
51 | karar_sayisi: Optional[str] = Field("", description="Decision no")
52 | kanun_no: Optional[str] = Field("", description="Law no")
53 |
54 | karar_date_begin: Optional[str] = Field("", description="Start date (DD.MM.YYYY)")
55 | karar_date_end: Optional[str] = Field("", description="End date (DD.MM.YYYY)")
56 |
57 | resmi_gazete_sayi: Optional[str] = Field("", description="Gazette no")
58 | resmi_gazete_date: Optional[str] = Field("", description="Gazette date (DD.MM.YYYY)")
59 |
60 | # Detailed text search fields from the "icerikDetail" section of the form
61 | tumce: Optional[str] = Field("", description="Exact phrase")
62 | wild_card: Optional[str] = Field("", description="Wildcard search")
63 | hepsi: Optional[str] = Field("", description="All words")
64 | herhangi_birisi: Optional[str] = Field("", description="Any word")
65 | not_hepsi: Optional[str] = Field("", description="Exclude words")
66 |
67 | class UyusmazlikApiDecisionEntry(BaseModel):
68 | """Model for an individual decision entry parsed from Uyuşmazlık API's HTML search response."""
69 | karar_sayisi: Optional[str] = Field(None)
70 | esas_sayisi: Optional[str] = Field(None)
71 | bolum: Optional[str] = Field(None)
72 | uyusmazlik_konusu: Optional[str] = Field(None)
73 | karar_sonucu: Optional[str] = Field(None)
74 | popover_content: Optional[str] = Field(None, description="Summary")
75 | document_url: HttpUrl # Full URL to the decision document HTML page
76 | pdf_url: Optional[HttpUrl] = Field(None, description="PDF URL")
77 |
78 | class UyusmazlikSearchResponse(BaseModel): # This is what the MCP tool will return
79 | """Response model for Uyuşmazlık Mahkemesi search results for the MCP tool."""
80 | decisions: List[UyusmazlikApiDecisionEntry]
81 | total_records_found: Optional[int] = Field(None, description="Total number of records found for the query, if available.")
82 |
83 | class UyusmazlikDocumentMarkdown(BaseModel):
84 | """Model for an Uyuşmazlık decision document, containing only Markdown content."""
85 | source_url: HttpUrl # The URL from which the content was fetched
86 | markdown_content: Optional[str] = Field(None, description="The decision content converted to Markdown.")
```
--------------------------------------------------------------------------------
/emsal_mcp_module/models.py:
--------------------------------------------------------------------------------
```python
1 | # emsal_mcp_module/models.py
2 |
3 | from pydantic import BaseModel, Field, HttpUrl, ConfigDict
4 | from typing import List, Optional, Dict, Any
5 |
6 | class EmsalDetailedSearchRequestData(BaseModel):
7 | """
8 | Internal model for the 'data' object in the Emsal detailed search payload.
9 | Field names use aliases to match the exact keys in the API payload
10 | (e.g., "Bam Hukuk Mahkemeleri" with spaces).
11 | The API expects empty strings for None/omitted optional fields.
12 | """
13 | arananKelime: Optional[str] = ""
14 |
15 | Bam_Hukuk_Mahkemeleri: str = Field("", alias="Bam Hukuk Mahkemeleri")
16 | Hukuk_Mahkemeleri: str = Field("", alias="Hukuk Mahkemeleri")
17 | # Add other specific court type fields from the form if they are separate keys in payload
18 | # E.g., "Ceza Mahkemeleri", "İdari Mahkemeler" etc.
19 |
20 | birimHukukMah: Optional[str] = Field("", description="Regional chambers (+ separated)")
21 |
22 | esasYil: Optional[str] = ""
23 | esasIlkSiraNo: Optional[str] = ""
24 | esasSonSiraNo: Optional[str] = ""
25 | kararYil: Optional[str] = ""
26 | kararIlkSiraNo: Optional[str] = ""
27 | kararSonSiraNo: Optional[str] = ""
28 | baslangicTarihi: Optional[str] = ""
29 | bitisTarihi: Optional[str] = ""
30 | siralama: str # Mandatory in payload example
31 | siralamaDirection: str # Mandatory in payload example
32 | pageSize: int
33 | pageNumber: int
34 |
35 | model_config = ConfigDict(populate_by_name=True) # Enables use of alias in serialization (when dumping to dict for payload)
36 |
37 | class EmsalSearchRequest(BaseModel): # This is the model the MCP tool will accept
38 | """Model for Emsal detailed search request, with user-friendly field names."""
39 | keyword: str = Field("", description="Keyword")
40 |
41 | selected_bam_civil_court: str = Field("", description="BAM Civil Court")
42 | selected_civil_court: str = Field("", description="Civil Court")
43 | selected_regional_civil_chambers: List[str] = Field(default_factory=list, description="Regional chambers")
44 |
45 | case_year_esas: str = Field("", description="Case year")
46 | case_start_seq_esas: str = Field("", description="Start case no")
47 | case_end_seq_esas: str = Field("", description="End case no")
48 |
49 | decision_year_karar: str = Field("", description="Decision year")
50 | decision_start_seq_karar: str = Field("", description="Start decision no")
51 | decision_end_seq_karar: str = Field("", description="End decision no")
52 |
53 | start_date: str = Field("", description="Start date (DD.MM.YYYY)")
54 | end_date: str = Field("", description="End date (DD.MM.YYYY)")
55 |
56 | sort_criteria: str = Field("1", description="Sort by")
57 | sort_direction: str = Field("desc", description="Direction")
58 |
59 | page_number: int = Field(default=1, ge=1)
60 | page_size: int = Field(default=10, ge=1, le=10)
61 |
62 |
63 | class EmsalApiDecisionEntry(BaseModel):
64 | """Model for an individual decision entry from the Emsal API search response."""
65 | id: str
66 | daire: str = Field("", description="Chamber")
67 | esasNo: str = Field("", description="Case number")
68 | kararNo: str = Field("", description="Decision number")
69 | kararTarihi: str = Field("", description="Decision date")
70 | arananKelime: str = Field("", description="Keyword")
71 | durum: str = Field("", description="Status")
72 | # index: Optional[int] = None # Present in Emsal response, can be added if tool needs it
73 |
74 | document_url: Optional[HttpUrl] = Field(None, description="Document URL")
75 |
76 | model_config = ConfigDict(extra='ignore')
77 |
78 | class EmsalApiResponseInnerData(BaseModel):
79 | """Model for the inner 'data' object in the Emsal API search response."""
80 | data: List[EmsalApiDecisionEntry]
81 | recordsTotal: int
82 | recordsFiltered: int
83 | draw: int = Field(0, description="Draw counter (Çizim Sayıcısı) from API, usually for DataTables.")
84 |
85 | class EmsalApiResponse(BaseModel):
86 | """Model for the complete search response from the Emsal API."""
87 | data: EmsalApiResponseInnerData
88 | metadata: Optional[Dict[str, Any]] = Field(None, description="Optional metadata (Meta Veri) from API, if any.")
89 |
90 | class EmsalDocumentMarkdown(BaseModel):
91 | """Model for an Emsal decision document, containing only Markdown content."""
92 | id: str
93 | markdown_content: str = Field("", description="The decision content (Karar İçeriği) converted to Markdown.")
94 | source_url: HttpUrl
95 |
96 | class CompactEmsalSearchResult(BaseModel):
97 | """A compact search result model for the MCP tool to return."""
98 | decisions: List[EmsalApiDecisionEntry]
99 | total_records: int
100 | requested_page: int
101 | page_size: int
```
--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/emsal_mcp_module/models.py:
--------------------------------------------------------------------------------
```python
1 | # emsal_mcp_module/models.py
2 |
3 | from pydantic import BaseModel, Field, HttpUrl, ConfigDict
4 | from typing import List, Optional, Dict, Any
5 |
6 | class EmsalDetailedSearchRequestData(BaseModel):
7 | """
8 | Internal model for the 'data' object in the Emsal detailed search payload.
9 | Field names use aliases to match the exact keys in the API payload
10 | (e.g., "Bam Hukuk Mahkemeleri" with spaces).
11 | The API expects empty strings for None/omitted optional fields.
12 | """
13 | arananKelime: Optional[str] = ""
14 |
15 | Bam_Hukuk_Mahkemeleri: str = Field("", alias="Bam Hukuk Mahkemeleri")
16 | Hukuk_Mahkemeleri: str = Field("", alias="Hukuk Mahkemeleri")
17 | # Add other specific court type fields from the form if they are separate keys in payload
18 | # E.g., "Ceza Mahkemeleri", "İdari Mahkemeler" etc.
19 |
20 | birimHukukMah: Optional[str] = Field("", description="Regional chambers (+ separated)")
21 |
22 | esasYil: Optional[str] = ""
23 | esasIlkSiraNo: Optional[str] = ""
24 | esasSonSiraNo: Optional[str] = ""
25 | kararYil: Optional[str] = ""
26 | kararIlkSiraNo: Optional[str] = ""
27 | kararSonSiraNo: Optional[str] = ""
28 | baslangicTarihi: Optional[str] = ""
29 | bitisTarihi: Optional[str] = ""
30 | siralama: str # Mandatory in payload example
31 | siralamaDirection: str # Mandatory in payload example
32 | pageSize: int
33 | pageNumber: int
34 |
35 | model_config = ConfigDict(populate_by_name=True) # Enables use of alias in serialization (when dumping to dict for payload)
36 |
37 | class EmsalSearchRequest(BaseModel): # This is the model the MCP tool will accept
38 | """Model for Emsal detailed search request, with user-friendly field names."""
39 | keyword: str = Field("", description="Keyword")
40 |
41 | selected_bam_civil_court: str = Field("", description="BAM Civil Court")
42 | selected_civil_court: str = Field("", description="Civil Court")
43 | selected_regional_civil_chambers: List[str] = Field(default_factory=list, description="Regional chambers")
44 |
45 | case_year_esas: str = Field("", description="Case year")
46 | case_start_seq_esas: str = Field("", description="Start case no")
47 | case_end_seq_esas: str = Field("", description="End case no")
48 |
49 | decision_year_karar: str = Field("", description="Decision year")
50 | decision_start_seq_karar: str = Field("", description="Start decision no")
51 | decision_end_seq_karar: str = Field("", description="End decision no")
52 |
53 | start_date: str = Field("", description="Start date (DD.MM.YYYY)")
54 | end_date: str = Field("", description="End date (DD.MM.YYYY)")
55 |
56 | sort_criteria: str = Field("1", description="Sort by")
57 | sort_direction: str = Field("desc", description="Direction")
58 |
59 | page_number: int = Field(default=1, ge=1)
60 | page_size: int = Field(default=10, ge=1, le=10)
61 |
62 |
63 | class EmsalApiDecisionEntry(BaseModel):
64 | """Model for an individual decision entry from the Emsal API search response."""
65 | id: str
66 | daire: str = Field("", description="Chamber")
67 | esasNo: str = Field("", description="Case number")
68 | kararNo: str = Field("", description="Decision number")
69 | kararTarihi: str = Field("", description="Decision date")
70 | arananKelime: str = Field("", description="Keyword")
71 | durum: str = Field("", description="Status")
72 | # index: Optional[int] = None # Present in Emsal response, can be added if tool needs it
73 |
74 | document_url: Optional[HttpUrl] = Field(None, description="Document URL")
75 |
76 | model_config = ConfigDict(extra='ignore')
77 |
78 | class EmsalApiResponseInnerData(BaseModel):
79 | """Model for the inner 'data' object in the Emsal API search response."""
80 | data: List[EmsalApiDecisionEntry]
81 | recordsTotal: int
82 | recordsFiltered: int
83 | draw: int = Field(0, description="Draw counter (Çizim Sayıcısı) from API, usually for DataTables.")
84 |
85 | class EmsalApiResponse(BaseModel):
86 | """Model for the complete search response from the Emsal API."""
87 | data: EmsalApiResponseInnerData
88 | metadata: Optional[Dict[str, Any]] = Field(None, description="Optional metadata (Meta Veri) from API, if any.")
89 |
90 | class EmsalDocumentMarkdown(BaseModel):
91 | """Model for an Emsal decision document, containing only Markdown content."""
92 | id: str
93 | markdown_content: str = Field("", description="The decision content (Karar İçeriği) converted to Markdown.")
94 | source_url: HttpUrl
95 |
96 | class CompactEmsalSearchResult(BaseModel):
97 | """A compact search result model for the MCP tool to return."""
98 | decisions: List[EmsalApiDecisionEntry]
99 | total_records: int
100 | requested_page: int
101 | page_size: int
```
--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/starlette_app.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | Starlette integration example for Yargı MCP Server
3 |
4 | This module demonstrates how to integrate the Yargı MCP server
5 | with a Starlette application, including authentication middleware
6 | and custom routing.
7 |
8 | Usage:
9 | uvicorn starlette_app:app --host 0.0.0.0 --port 8000
10 | """
11 |
12 | import os
13 | from starlette.applications import Starlette
14 | from starlette.routing import Mount, Route
15 | from starlette.requests import Request
16 | from starlette.responses import JSONResponse, PlainTextResponse, RedirectResponse
17 | from starlette.middleware import Middleware
18 | from starlette.middleware.cors import CORSMiddleware
19 | from starlette.middleware.authentication import AuthenticationMiddleware
20 | from starlette.authentication import (
21 | AuthenticationBackend, AuthCredentials, SimpleUser, AuthenticationError
22 | )
23 |
24 | # Import the main MCP app
25 | from mcp_server_main import app as mcp_server
26 |
27 | # Simple token authentication backend
28 | class TokenAuthBackend(AuthenticationBackend):
29 | async def authenticate(self, request):
30 | auth_header = request.headers.get("Authorization")
31 | expected_token = os.getenv("API_TOKEN")
32 |
33 | # Skip auth for health check and public endpoints
34 | if request.url.path in ["/health", "/", "/login"]:
35 | return None
36 |
37 | if not expected_token:
38 | # No token configured, allow all
39 | return AuthCredentials(["authenticated"]), SimpleUser("anonymous")
40 |
41 | if not auth_header:
42 | raise AuthenticationError("Authorization header required")
43 |
44 | try:
45 | scheme, token = auth_header.split()
46 | if scheme.lower() != "bearer":
47 | raise AuthenticationError("Invalid authentication scheme")
48 |
49 | if token != expected_token:
50 | raise AuthenticationError("Invalid token")
51 |
52 | return AuthCredentials(["authenticated"]), SimpleUser("user")
53 | except ValueError:
54 | raise AuthenticationError("Invalid authorization header format")
55 |
56 | # Homepage
57 | async def homepage(request: Request):
58 | return JSONResponse({
59 | "service": "Yargı MCP Server",
60 | "version": "0.1.0",
61 | "endpoints": {
62 | "mcp": "/mcp-server/mcp/",
63 | "api": "/api/",
64 | "health": "/health"
65 | }
66 | })
67 |
68 | # API info endpoint
69 | async def api_info(request: Request):
70 | if not request.user.is_authenticated:
71 | return JSONResponse({"error": "Authentication required"}, status_code=401)
72 |
73 | return JSONResponse({
74 | "authenticated_as": request.user.display_name,
75 | "available_tools": len(mcp_server._tool_manager._tools),
76 | "databases": [
77 | "Yargıtay", "Danıştay", "Emsal", "Uyuşmazlık",
78 | "Anayasa", "KIK", "Rekabet", "Bedesten"
79 | ]
80 | })
81 |
82 | # Health check
83 | async def health_check(request: Request):
84 | return JSONResponse({
85 | "status": "healthy",
86 | "service": "Yargı MCP Server"
87 | })
88 |
89 | # Login example (returns token for demo)
90 | async def login(request: Request):
91 | token = os.getenv("API_TOKEN", "demo-token")
92 | return JSONResponse({
93 | "message": "Use this token in Authorization header",
94 | "example": f"Authorization: Bearer {token}",
95 | "note": "Set API_TOKEN environment variable to change token"
96 | })
97 |
98 | # Create MCP ASGI app
99 | mcp_app = mcp_server.http_app(path='/mcp')
100 |
101 | # Configure middleware
102 | middleware = [
103 | Middleware(
104 | CORSMiddleware,
105 | allow_origins=os.getenv("ALLOWED_ORIGINS", "*").split(","),
106 | allow_credentials=True,
107 | allow_methods=["*"],
108 | allow_headers=["*"],
109 | ),
110 | Middleware(AuthenticationMiddleware, backend=TokenAuthBackend()),
111 | ]
112 |
113 | # Create routes
114 | routes = [
115 | Route("/", homepage),
116 | Route("/health", health_check),
117 | Route("/login", login),
118 | Route("/api/info", api_info),
119 | Mount("/mcp-server", app=mcp_app),
120 | ]
121 |
122 | # Create Starlette app
123 | app = Starlette(
124 | routes=routes,
125 | middleware=middleware,
126 | lifespan=mcp_app.lifespan
127 | )
128 |
129 | # Nested mount example
130 | def create_nested_app():
131 | """Example of nested mounting for complex routing structures"""
132 |
133 | # Create inner app with MCP
134 | inner_app = Starlette(
135 | routes=[Mount("/services", app=mcp_app)],
136 | middleware=middleware
137 | )
138 |
139 | # Create outer app
140 | outer_app = Starlette(
141 | routes=[
142 | Route("/", homepage),
143 | Mount("/v1", app=inner_app),
144 | ],
145 | lifespan=mcp_app.lifespan
146 | )
147 |
148 | # MCP would be available at /v1/services/mcp/
149 | return outer_app
150 |
151 | # Export both apps
152 | nested_app = create_nested_app()
153 |
154 | if __name__ == "__main__":
155 | import uvicorn
156 | print("Starting Starlette app with authentication...")
157 | print("Set API_TOKEN environment variable to enable authentication")
158 | print("Example: API_TOKEN=secret-token python starlette_app.py")
159 | uvicorn.run(app, host="0.0.0.0", port=8000)
```
--------------------------------------------------------------------------------
/starlette_app.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | Starlette integration example for Yargı MCP Server
3 |
4 | This module demonstrates how to integrate the Yargı MCP server
5 | with a Starlette application, including authentication middleware
6 | and custom routing.
7 |
8 | Usage:
9 | uvicorn starlette_app:app --host 0.0.0.0 --port 8000
10 | """
11 |
12 | import os
13 | from starlette.applications import Starlette
14 | from starlette.routing import Mount, Route
15 | from starlette.requests import Request
16 | from starlette.responses import JSONResponse, PlainTextResponse, RedirectResponse
17 | from starlette.middleware import Middleware
18 | from starlette.middleware.cors import CORSMiddleware
19 | from starlette.middleware.authentication import AuthenticationMiddleware
20 | from starlette.authentication import (
21 | AuthenticationBackend, AuthCredentials, SimpleUser, AuthenticationError
22 | )
23 |
24 | # Import the main MCP app
25 | from mcp_server_main import app as mcp_server
26 |
27 | # Simple token authentication backend
28 | class TokenAuthBackend(AuthenticationBackend):
29 | async def authenticate(self, request):
30 | auth_header = request.headers.get("Authorization")
31 | expected_token = os.getenv("API_TOKEN")
32 |
33 | # Skip auth for health check and public endpoints
34 | if request.url.path in ["/health", "/", "/login"]:
35 | return None
36 |
37 | if not expected_token:
38 | # No token configured, allow all
39 | return AuthCredentials(["authenticated"]), SimpleUser("anonymous")
40 |
41 | if not auth_header:
42 | raise AuthenticationError("Authorization header required")
43 |
44 | try:
45 | scheme, token = auth_header.split()
46 | if scheme.lower() != "bearer":
47 | raise AuthenticationError("Invalid authentication scheme")
48 |
49 | if token != expected_token:
50 | raise AuthenticationError("Invalid token")
51 |
52 | return AuthCredentials(["authenticated"]), SimpleUser("user")
53 | except ValueError:
54 | raise AuthenticationError("Invalid authorization header format")
55 |
56 | # Homepage
57 | async def homepage(request: Request):
58 | return JSONResponse({
59 | "service": "Yargı MCP Server",
60 | "version": "0.1.0",
61 | "endpoints": {
62 | "mcp": "/mcp-server/mcp/",
63 | "api": "/api/",
64 | "health": "/health"
65 | }
66 | })
67 |
68 | # API info endpoint
69 | async def api_info(request: Request):
70 | if not request.user.is_authenticated:
71 | return JSONResponse({"error": "Authentication required"}, status_code=401)
72 |
73 | return JSONResponse({
74 | "authenticated_as": request.user.display_name,
75 | "available_tools": len(mcp_server._tool_manager._tools),
76 | "databases": [
77 | "Yargıtay", "Danıştay", "Emsal", "Uyuşmazlık",
78 | "Anayasa", "KIK", "Rekabet", "Bedesten"
79 | ]
80 | })
81 |
82 | # Health check
83 | async def health_check(request: Request):
84 | return JSONResponse({
85 | "status": "healthy",
86 | "service": "Yargı MCP Server"
87 | })
88 |
89 | # Login example (returns token for demo)
90 | async def login(request: Request):
91 | token = os.getenv("API_TOKEN", "demo-token")
92 | return JSONResponse({
93 | "message": "Use this token in Authorization header",
94 | "example": f"Authorization: Bearer {token}",
95 | "note": "Set API_TOKEN environment variable to change token"
96 | })
97 |
98 | # Create MCP ASGI app
99 | mcp_app = mcp_server.http_app(path='/mcp')
100 |
101 | # Configure middleware
102 | middleware = [
103 | Middleware(
104 | CORSMiddleware,
105 | allow_origins=os.getenv("ALLOWED_ORIGINS", "*").split(","),
106 | allow_credentials=True,
107 | allow_methods=["*"],
108 | allow_headers=["*"],
109 | ),
110 | Middleware(AuthenticationMiddleware, backend=TokenAuthBackend()),
111 | ]
112 |
113 | # Create routes
114 | routes = [
115 | Route("/", homepage),
116 | Route("/health", health_check),
117 | Route("/login", login),
118 | Route("/api/info", api_info),
119 | Mount("/mcp-server", app=mcp_app),
120 | ]
121 |
122 | # Create Starlette app
123 | app = Starlette(
124 | routes=routes,
125 | middleware=middleware,
126 | lifespan=mcp_app.lifespan
127 | )
128 |
129 | # Nested mount example
130 | def create_nested_app():
131 | """Example of nested mounting for complex routing structures"""
132 |
133 | # Create inner app with MCP
134 | inner_app = Starlette(
135 | routes=[Mount("/services", app=mcp_app)],
136 | middleware=middleware
137 | )
138 |
139 | # Create outer app
140 | outer_app = Starlette(
141 | routes=[
142 | Route("/", homepage),
143 | Mount("/v1", app=inner_app),
144 | ],
145 | lifespan=mcp_app.lifespan
146 | )
147 |
148 | # MCP would be available at /v1/services/mcp/
149 | return outer_app
150 |
151 | # Export both apps
152 | nested_app = create_nested_app()
153 |
154 | if __name__ == "__main__":
155 | import uvicorn
156 | print("Starting Starlette app with authentication...")
157 | print("Set API_TOKEN environment variable to enable authentication")
158 | print("Example: API_TOKEN=secret-token python starlette_app.py")
159 | uvicorn.run(app, host="0.0.0.0", port=8000)
```
--------------------------------------------------------------------------------
/bedesten_mcp_module/enums.py:
--------------------------------------------------------------------------------
```python
1 | # bedesten_mcp_module/enums.py
2 |
3 | from typing import Literal
4 |
5 | # Unified compressed enum for both Yargıtay and Danıştay chambers
6 | BirimAdiEnum = Literal[
7 | "ALL", # All chambers
8 |
9 | # Yargıtay (Court of Cassation) - Civil Chambers
10 | "H1", "H2", "H3", "H4", "H5", "H6", "H7", "H8", "H9", "H10",
11 | "H11", "H12", "H13", "H14", "H15", "H16", "H17", "H18", "H19", "H20",
12 | "H21", "H22", "H23",
13 |
14 | # Yargıtay - Criminal Chambers
15 | "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10",
16 | "C11", "C12", "C13", "C14", "C15", "C16", "C17", "C18", "C19", "C20",
17 | "C21", "C22", "C23",
18 |
19 | # Yargıtay - Councils and Assemblies
20 | "HGK", # Hukuk Genel Kurulu
21 | "CGK", # Ceza Genel Kurulu
22 | "BGK", # Büyük Genel Kurulu
23 | "HBK", # Hukuk Daireleri Başkanlar Kurulu
24 | "CBK", # Ceza Daireleri Başkanlar Kurulu
25 |
26 | # Danıştay (Council of State) - Chambers
27 | "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10",
28 | "D11", "D12", "D13", "D14", "D15", "D16", "D17",
29 |
30 | # Danıştay - Councils and Boards
31 | "DBGK", # Büyük Gen.Kur. (Grand General Assembly)
32 | "IDDK", # İdare Dava Daireleri Kurulu
33 | "VDDK", # Vergi Dava Daireleri Kurulu
34 | "IBK", # İçtihatları Birleştirme Kurulu
35 | "IIK", # İdari İşler Kurulu
36 | "DBK", # Başkanlar Kurulu
37 |
38 | # Military High Administrative Court
39 | "AYIM", # Askeri Yüksek İdare Mahkemesi
40 | "AYIMDK", # Askeri Yüksek İdare Mahkemesi Daireler Kurulu
41 | "AYIMB", # Askeri Yüksek İdare Mahkemesi Başsavcılığı
42 | "AYIM1", # Askeri Yüksek İdare Mahkemesi 1. Daire
43 | "AYIM2", # Askeri Yüksek İdare Mahkemesi 2. Daire
44 | "AYIM3" # Askeri Yüksek İdare Mahkemesi 3. Daire
45 | ]
46 |
47 | # Mapping from abbreviated values to full Turkish API values
48 | BIRIM_ADI_MAPPING = {
49 | "ALL": None, # Will be handled specially in client
50 |
51 | # Yargıtay Civil Chambers (1-23)
52 | "H1": "1. Hukuk Dairesi", "H2": "2. Hukuk Dairesi", "H3": "3. Hukuk Dairesi",
53 | "H4": "4. Hukuk Dairesi", "H5": "5. Hukuk Dairesi", "H6": "6. Hukuk Dairesi",
54 | "H7": "7. Hukuk Dairesi", "H8": "8. Hukuk Dairesi", "H9": "9. Hukuk Dairesi",
55 | "H10": "10. Hukuk Dairesi", "H11": "11. Hukuk Dairesi", "H12": "12. Hukuk Dairesi",
56 | "H13": "13. Hukuk Dairesi", "H14": "14. Hukuk Dairesi", "H15": "15. Hukuk Dairesi",
57 | "H16": "16. Hukuk Dairesi", "H17": "17. Hukuk Dairesi", "H18": "18. Hukuk Dairesi",
58 | "H19": "19. Hukuk Dairesi", "H20": "20. Hukuk Dairesi", "H21": "21. Hukuk Dairesi",
59 | "H22": "22. Hukuk Dairesi", "H23": "23. Hukuk Dairesi",
60 |
61 | # Yargıtay Criminal Chambers (1-23)
62 | "C1": "1. Ceza Dairesi", "C2": "2. Ceza Dairesi", "C3": "3. Ceza Dairesi",
63 | "C4": "4. Ceza Dairesi", "C5": "5. Ceza Dairesi", "C6": "6. Ceza Dairesi",
64 | "C7": "7. Ceza Dairesi", "C8": "8. Ceza Dairesi", "C9": "9. Ceza Dairesi",
65 | "C10": "10. Ceza Dairesi", "C11": "11. Ceza Dairesi", "C12": "12. Ceza Dairesi",
66 | "C13": "13. Ceza Dairesi", "C14": "14. Ceza Dairesi", "C15": "15. Ceza Dairesi",
67 | "C16": "16. Ceza Dairesi", "C17": "17. Ceza Dairesi", "C18": "18. Ceza Dairesi",
68 | "C19": "19. Ceza Dairesi", "C20": "20. Ceza Dairesi", "C21": "21. Ceza Dairesi",
69 | "C22": "22. Ceza Dairesi", "C23": "23. Ceza Dairesi",
70 |
71 | # Yargıtay Councils and Assemblies
72 | "HGK": "Hukuk Genel Kurulu",
73 | "CGK": "Ceza Genel Kurulu",
74 | "BGK": "Büyük Genel Kurulu",
75 | "HBK": "Hukuk Daireleri Başkanlar Kurulu",
76 | "CBK": "Ceza Daireleri Başkanlar Kurulu",
77 |
78 | # Danıştay Chambers (1-17)
79 | "D1": "1. Daire", "D2": "2. Daire", "D3": "3. Daire", "D4": "4. Daire",
80 | "D5": "5. Daire", "D6": "6. Daire", "D7": "7. Daire", "D8": "8. Daire",
81 | "D9": "9. Daire", "D10": "10. Daire", "D11": "11. Daire", "D12": "12. Daire",
82 | "D13": "13. Daire", "D14": "14. Daire", "D15": "15. Daire", "D16": "16. Daire",
83 | "D17": "17. Daire",
84 |
85 | # Danıştay Councils and Boards
86 | "DBGK": "Büyük Gen.Kur.",
87 | "IDDK": "İdare Dava Daireleri Kurulu",
88 | "VDDK": "Vergi Dava Daireleri Kurulu",
89 | "IBK": "İçtihatları Birleştirme Kurulu",
90 | "IIK": "İdari İşler Kurulu",
91 | "DBK": "Başkanlar Kurulu",
92 |
93 | # Military High Administrative Court
94 | "AYIM": "Askeri Yüksek İdare Mahkemesi",
95 | "AYIMDK": "Askeri Yüksek İdare Mahkemesi Daireler Kurulu",
96 | "AYIMB": "Askeri Yüksek İdare Mahkemesi Başsavcılığı",
97 | "AYIM1": "Askeri Yüksek İdare Mahkemesi 1. Daire",
98 | "AYIM2": "Askeri Yüksek İdare Mahkemesi 2. Daire",
99 | "AYIM3": "Askeri Yüksek İdare Mahkemesi 3. Daire"
100 | }
101 |
102 | # Helper function to get full Turkish name from abbreviated value
103 | def get_full_birim_adi(abbreviated_value: str) -> str:
104 | """Convert abbreviated birimAdi value to full Turkish name for API calls."""
105 | if abbreviated_value == "ALL" or not abbreviated_value:
106 | return "" # Empty string for ALL or None
107 |
108 | return BIRIM_ADI_MAPPING.get(abbreviated_value, abbreviated_value)
109 |
110 | # Helper function to validate abbreviated value
111 | def is_valid_birim_adi(abbreviated_value: str) -> bool:
112 | """Check if abbreviated birimAdi value is valid."""
113 | return abbreviated_value in BIRIM_ADI_MAPPING
```
--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/enums.py:
--------------------------------------------------------------------------------
```python
1 | # bedesten_mcp_module/enums.py
2 |
3 | from typing import Literal
4 |
5 | # Unified compressed enum for both Yargıtay and Danıştay chambers
6 | BirimAdiEnum = Literal[
7 | "ALL", # All chambers
8 |
9 | # Yargıtay (Court of Cassation) - Civil Chambers
10 | "H1", "H2", "H3", "H4", "H5", "H6", "H7", "H8", "H9", "H10",
11 | "H11", "H12", "H13", "H14", "H15", "H16", "H17", "H18", "H19", "H20",
12 | "H21", "H22", "H23",
13 |
14 | # Yargıtay - Criminal Chambers
15 | "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10",
16 | "C11", "C12", "C13", "C14", "C15", "C16", "C17", "C18", "C19", "C20",
17 | "C21", "C22", "C23",
18 |
19 | # Yargıtay - Councils and Assemblies
20 | "HGK", # Hukuk Genel Kurulu
21 | "CGK", # Ceza Genel Kurulu
22 | "BGK", # Büyük Genel Kurulu
23 | "HBK", # Hukuk Daireleri Başkanlar Kurulu
24 | "CBK", # Ceza Daireleri Başkanlar Kurulu
25 |
26 | # Danıştay (Council of State) - Chambers
27 | "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10",
28 | "D11", "D12", "D13", "D14", "D15", "D16", "D17",
29 |
30 | # Danıştay - Councils and Boards
31 | "DBGK", # Büyük Gen.Kur. (Grand General Assembly)
32 | "IDDK", # İdare Dava Daireleri Kurulu
33 | "VDDK", # Vergi Dava Daireleri Kurulu
34 | "IBK", # İçtihatları Birleştirme Kurulu
35 | "IIK", # İdari İşler Kurulu
36 | "DBK", # Başkanlar Kurulu
37 |
38 | # Military High Administrative Court
39 | "AYIM", # Askeri Yüksek İdare Mahkemesi
40 | "AYIMDK", # Askeri Yüksek İdare Mahkemesi Daireler Kurulu
41 | "AYIMB", # Askeri Yüksek İdare Mahkemesi Başsavcılığı
42 | "AYIM1", # Askeri Yüksek İdare Mahkemesi 1. Daire
43 | "AYIM2", # Askeri Yüksek İdare Mahkemesi 2. Daire
44 | "AYIM3" # Askeri Yüksek İdare Mahkemesi 3. Daire
45 | ]
46 |
47 | # Mapping from abbreviated values to full Turkish API values
48 | BIRIM_ADI_MAPPING = {
49 | "ALL": None, # Will be handled specially in client
50 |
51 | # Yargıtay Civil Chambers (1-23)
52 | "H1": "1. Hukuk Dairesi", "H2": "2. Hukuk Dairesi", "H3": "3. Hukuk Dairesi",
53 | "H4": "4. Hukuk Dairesi", "H5": "5. Hukuk Dairesi", "H6": "6. Hukuk Dairesi",
54 | "H7": "7. Hukuk Dairesi", "H8": "8. Hukuk Dairesi", "H9": "9. Hukuk Dairesi",
55 | "H10": "10. Hukuk Dairesi", "H11": "11. Hukuk Dairesi", "H12": "12. Hukuk Dairesi",
56 | "H13": "13. Hukuk Dairesi", "H14": "14. Hukuk Dairesi", "H15": "15. Hukuk Dairesi",
57 | "H16": "16. Hukuk Dairesi", "H17": "17. Hukuk Dairesi", "H18": "18. Hukuk Dairesi",
58 | "H19": "19. Hukuk Dairesi", "H20": "20. Hukuk Dairesi", "H21": "21. Hukuk Dairesi",
59 | "H22": "22. Hukuk Dairesi", "H23": "23. Hukuk Dairesi",
60 |
61 | # Yargıtay Criminal Chambers (1-23)
62 | "C1": "1. Ceza Dairesi", "C2": "2. Ceza Dairesi", "C3": "3. Ceza Dairesi",
63 | "C4": "4. Ceza Dairesi", "C5": "5. Ceza Dairesi", "C6": "6. Ceza Dairesi",
64 | "C7": "7. Ceza Dairesi", "C8": "8. Ceza Dairesi", "C9": "9. Ceza Dairesi",
65 | "C10": "10. Ceza Dairesi", "C11": "11. Ceza Dairesi", "C12": "12. Ceza Dairesi",
66 | "C13": "13. Ceza Dairesi", "C14": "14. Ceza Dairesi", "C15": "15. Ceza Dairesi",
67 | "C16": "16. Ceza Dairesi", "C17": "17. Ceza Dairesi", "C18": "18. Ceza Dairesi",
68 | "C19": "19. Ceza Dairesi", "C20": "20. Ceza Dairesi", "C21": "21. Ceza Dairesi",
69 | "C22": "22. Ceza Dairesi", "C23": "23. Ceza Dairesi",
70 |
71 | # Yargıtay Councils and Assemblies
72 | "HGK": "Hukuk Genel Kurulu",
73 | "CGK": "Ceza Genel Kurulu",
74 | "BGK": "Büyük Genel Kurulu",
75 | "HBK": "Hukuk Daireleri Başkanlar Kurulu",
76 | "CBK": "Ceza Daireleri Başkanlar Kurulu",
77 |
78 | # Danıştay Chambers (1-17)
79 | "D1": "1. Daire", "D2": "2. Daire", "D3": "3. Daire", "D4": "4. Daire",
80 | "D5": "5. Daire", "D6": "6. Daire", "D7": "7. Daire", "D8": "8. Daire",
81 | "D9": "9. Daire", "D10": "10. Daire", "D11": "11. Daire", "D12": "12. Daire",
82 | "D13": "13. Daire", "D14": "14. Daire", "D15": "15. Daire", "D16": "16. Daire",
83 | "D17": "17. Daire",
84 |
85 | # Danıştay Councils and Boards
86 | "DBGK": "Büyük Gen.Kur.",
87 | "IDDK": "İdare Dava Daireleri Kurulu",
88 | "VDDK": "Vergi Dava Daireleri Kurulu",
89 | "IBK": "İçtihatları Birleştirme Kurulu",
90 | "IIK": "İdari İşler Kurulu",
91 | "DBK": "Başkanlar Kurulu",
92 |
93 | # Military High Administrative Court
94 | "AYIM": "Askeri Yüksek İdare Mahkemesi",
95 | "AYIMDK": "Askeri Yüksek İdare Mahkemesi Daireler Kurulu",
96 | "AYIMB": "Askeri Yüksek İdare Mahkemesi Başsavcılığı",
97 | "AYIM1": "Askeri Yüksek İdare Mahkemesi 1. Daire",
98 | "AYIM2": "Askeri Yüksek İdare Mahkemesi 2. Daire",
99 | "AYIM3": "Askeri Yüksek İdare Mahkemesi 3. Daire"
100 | }
101 |
102 | # Helper function to get full Turkish name from abbreviated value
103 | def get_full_birim_adi(abbreviated_value: str) -> str:
104 | """Convert abbreviated birimAdi value to full Turkish name for API calls."""
105 | if abbreviated_value == "ALL" or not abbreviated_value:
106 | return "" # Empty string for ALL or None
107 |
108 | return BIRIM_ADI_MAPPING.get(abbreviated_value, abbreviated_value)
109 |
110 | # Helper function to validate abbreviated value
111 | def is_valid_birim_adi(abbreviated_value: str) -> bool:
112 | """Check if abbreviated birimAdi value is valid."""
113 | return abbreviated_value in BIRIM_ADI_MAPPING
```
--------------------------------------------------------------------------------
/anayasa_mcp_module/unified_client.py:
--------------------------------------------------------------------------------
```python
1 | # anayasa_mcp_module/unified_client.py
2 | # Unified client for both Norm Denetimi and Bireysel Başvuru
3 |
4 | import logging
5 | from typing import Optional
6 | from urllib.parse import urlparse
7 |
8 | from .models import (
9 | AnayasaUnifiedSearchRequest,
10 | AnayasaUnifiedSearchResult,
11 | AnayasaUnifiedDocumentMarkdown,
12 | # Removed AnayasaDecisionTypeEnum - now using string literals
13 | AnayasaNormDenetimiSearchRequest,
14 | AnayasaBireyselReportSearchRequest
15 | )
16 | from .client import AnayasaMahkemesiApiClient
17 | from .bireysel_client import AnayasaBireyselBasvuruApiClient
18 |
19 | logger = logging.getLogger(__name__)
20 |
21 | class AnayasaUnifiedClient:
22 | """Unified client that handles both Norm Denetimi and Bireysel Başvuru searches."""
23 |
24 | def __init__(self, request_timeout: float = 60.0):
25 | self.norm_client = AnayasaMahkemesiApiClient(request_timeout)
26 | self.bireysel_client = AnayasaBireyselBasvuruApiClient(request_timeout)
27 |
28 | async def search_unified(self, params: AnayasaUnifiedSearchRequest) -> AnayasaUnifiedSearchResult:
29 | """Unified search that routes to appropriate client based on decision_type."""
30 |
31 | if params.decision_type == "norm_denetimi":
32 | # Convert to norm denetimi request
33 | norm_params = AnayasaNormDenetimiSearchRequest(
34 | keywords_all=params.keywords_all or params.keywords,
35 | keywords_any=params.keywords_any,
36 | application_type=params.decision_type_norm,
37 | page_to_fetch=params.page_to_fetch,
38 | results_per_page=params.results_per_page
39 | )
40 |
41 | result = await self.norm_client.search_norm_denetimi_decisions(norm_params)
42 |
43 | # Convert to unified format
44 | decisions_list = [decision.model_dump() for decision in result.decisions]
45 |
46 | return AnayasaUnifiedSearchResult(
47 | decision_type="norm_denetimi",
48 | decisions=decisions_list,
49 | total_records_found=result.total_records_found,
50 | retrieved_page_number=result.retrieved_page_number
51 | )
52 |
53 | elif params.decision_type == "bireysel_basvuru":
54 | # Convert to bireysel başvuru request
55 | bireysel_params = AnayasaBireyselReportSearchRequest(
56 | keywords=params.keywords,
57 | decision_start_date=params.decision_start_date,
58 | decision_end_date=params.decision_end_date,
59 | norm_type=params.norm_type,
60 | subject_category=params.subject_category,
61 | page_to_fetch=params.page_to_fetch,
62 | results_per_page=params.results_per_page
63 | )
64 |
65 | result = await self.bireysel_client.search_bireysel_basvuru_report(bireysel_params)
66 |
67 | # Convert to unified format
68 | decisions_list = [decision.model_dump() for decision in result.decisions]
69 |
70 | return AnayasaUnifiedSearchResult(
71 | decision_type="bireysel_basvuru",
72 | decisions=decisions_list,
73 | total_records_found=result.total_records_found,
74 | retrieved_page_number=result.retrieved_page_number
75 | )
76 |
77 | else:
78 | raise ValueError(f"Unsupported decision type: {params.decision_type}")
79 |
80 | async def get_document_unified(self, document_url: str, page_number: int = 1) -> AnayasaUnifiedDocumentMarkdown:
81 | """Unified document retrieval that auto-detects the appropriate client."""
82 |
83 | # Auto-detect decision type based on URL
84 | parsed_url = urlparse(document_url)
85 |
86 | if "normkararlarbilgibankasi" in parsed_url.netloc or "/ND/" in document_url:
87 | # Norm Denetimi document
88 | result = await self.norm_client.get_decision_document_as_markdown(document_url, page_number)
89 |
90 | return AnayasaUnifiedDocumentMarkdown(
91 | decision_type="norm_denetimi",
92 | source_url=result.source_url,
93 | document_data=result.model_dump(),
94 | markdown_chunk=result.markdown_chunk,
95 | current_page=result.current_page,
96 | total_pages=result.total_pages,
97 | is_paginated=result.is_paginated
98 | )
99 |
100 | elif "kararlarbilgibankasi" in parsed_url.netloc or "/BB/" in document_url:
101 | # Bireysel Başvuru document
102 | result = await self.bireysel_client.get_decision_document_as_markdown(document_url, page_number)
103 |
104 | return AnayasaUnifiedDocumentMarkdown(
105 | decision_type="bireysel_basvuru",
106 | source_url=result.source_url,
107 | document_data=result.model_dump(),
108 | markdown_chunk=result.markdown_chunk,
109 | current_page=result.current_page,
110 | total_pages=result.total_pages,
111 | is_paginated=result.is_paginated
112 | )
113 |
114 | else:
115 | raise ValueError(f"Cannot determine document type from URL: {document_url}")
116 |
117 | async def close_client_session(self):
118 | """Close both client sessions."""
119 | if hasattr(self.norm_client, 'close_client_session'):
120 | await self.norm_client.close_client_session()
121 | if hasattr(self.bireysel_client, 'close_client_session'):
122 | await self.bireysel_client.close_client_session()
```
--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/unified_client.py:
--------------------------------------------------------------------------------
```python
1 | # anayasa_mcp_module/unified_client.py
2 | # Unified client for both Norm Denetimi and Bireysel Başvuru
3 |
4 | import logging
5 | from typing import Optional
6 | from urllib.parse import urlparse
7 |
8 | from .models import (
9 | AnayasaUnifiedSearchRequest,
10 | AnayasaUnifiedSearchResult,
11 | AnayasaUnifiedDocumentMarkdown,
12 | # Removed AnayasaDecisionTypeEnum - now using string literals
13 | AnayasaNormDenetimiSearchRequest,
14 | AnayasaBireyselReportSearchRequest
15 | )
16 | from .client import AnayasaMahkemesiApiClient
17 | from .bireysel_client import AnayasaBireyselBasvuruApiClient
18 |
19 | logger = logging.getLogger(__name__)
20 |
21 | class AnayasaUnifiedClient:
22 | """Unified client that handles both Norm Denetimi and Bireysel Başvuru searches."""
23 |
24 | def __init__(self, request_timeout: float = 60.0):
25 | self.norm_client = AnayasaMahkemesiApiClient(request_timeout)
26 | self.bireysel_client = AnayasaBireyselBasvuruApiClient(request_timeout)
27 |
28 | async def search_unified(self, params: AnayasaUnifiedSearchRequest) -> AnayasaUnifiedSearchResult:
29 | """Unified search that routes to appropriate client based on decision_type."""
30 |
31 | if params.decision_type == "norm_denetimi":
32 | # Convert to norm denetimi request
33 | norm_params = AnayasaNormDenetimiSearchRequest(
34 | keywords_all=params.keywords_all or params.keywords,
35 | keywords_any=params.keywords_any,
36 | application_type=params.decision_type_norm,
37 | page_to_fetch=params.page_to_fetch,
38 | results_per_page=params.results_per_page
39 | )
40 |
41 | result = await self.norm_client.search_norm_denetimi_decisions(norm_params)
42 |
43 | # Convert to unified format
44 | decisions_list = [decision.model_dump() for decision in result.decisions]
45 |
46 | return AnayasaUnifiedSearchResult(
47 | decision_type="norm_denetimi",
48 | decisions=decisions_list,
49 | total_records_found=result.total_records_found,
50 | retrieved_page_number=result.retrieved_page_number
51 | )
52 |
53 | elif params.decision_type == "bireysel_basvuru":
54 | # Convert to bireysel başvuru request
55 | bireysel_params = AnayasaBireyselReportSearchRequest(
56 | keywords=params.keywords,
57 | decision_start_date=params.decision_start_date,
58 | decision_end_date=params.decision_end_date,
59 | norm_type=params.norm_type,
60 | subject_category=params.subject_category,
61 | page_to_fetch=params.page_to_fetch,
62 | results_per_page=params.results_per_page
63 | )
64 |
65 | result = await self.bireysel_client.search_bireysel_basvuru_report(bireysel_params)
66 |
67 | # Convert to unified format
68 | decisions_list = [decision.model_dump() for decision in result.decisions]
69 |
70 | return AnayasaUnifiedSearchResult(
71 | decision_type="bireysel_basvuru",
72 | decisions=decisions_list,
73 | total_records_found=result.total_records_found,
74 | retrieved_page_number=result.retrieved_page_number
75 | )
76 |
77 | else:
78 | raise ValueError(f"Unsupported decision type: {params.decision_type}")
79 |
80 | async def get_document_unified(self, document_url: str, page_number: int = 1) -> AnayasaUnifiedDocumentMarkdown:
81 | """Unified document retrieval that auto-detects the appropriate client."""
82 |
83 | # Auto-detect decision type based on URL
84 | parsed_url = urlparse(document_url)
85 |
86 | if "normkararlarbilgibankasi" in parsed_url.netloc or "/ND/" in document_url:
87 | # Norm Denetimi document
88 | result = await self.norm_client.get_decision_document_as_markdown(document_url, page_number)
89 |
90 | return AnayasaUnifiedDocumentMarkdown(
91 | decision_type="norm_denetimi",
92 | source_url=result.source_url,
93 | document_data=result.model_dump(),
94 | markdown_chunk=result.markdown_chunk,
95 | current_page=result.current_page,
96 | total_pages=result.total_pages,
97 | is_paginated=result.is_paginated
98 | )
99 |
100 | elif "kararlarbilgibankasi" in parsed_url.netloc or "/BB/" in document_url:
101 | # Bireysel Başvuru document
102 | result = await self.bireysel_client.get_decision_document_as_markdown(document_url, page_number)
103 |
104 | return AnayasaUnifiedDocumentMarkdown(
105 | decision_type="bireysel_basvuru",
106 | source_url=result.source_url,
107 | document_data=result.model_dump(),
108 | markdown_chunk=result.markdown_chunk,
109 | current_page=result.current_page,
110 | total_pages=result.total_pages,
111 | is_paginated=result.is_paginated
112 | )
113 |
114 | else:
115 | raise ValueError(f"Cannot determine document type from URL: {document_url}")
116 |
117 | async def close_client_session(self):
118 | """Close both client sessions."""
119 | if hasattr(self.norm_client, 'close_client_session'):
120 | await self.norm_client.close_client_session()
121 | if hasattr(self.bireysel_client, 'close_client_session'):
122 | await self.bireysel_client.close_client_session()
```
--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/yargitay_mcp_module/models.py:
--------------------------------------------------------------------------------
```python
1 | # yargitay_mcp_module/models.py
2 |
3 | from pydantic import BaseModel, Field, HttpUrl, ConfigDict
4 | from typing import List, Optional, Dict, Any, Literal
5 |
6 | # Yargıtay Chamber/Board Options
7 | YargitayBirimEnum = Literal[
8 | "ALL", # "ALL" for all chambers
9 | # Hukuk (Civil) Chambers
10 | "Hukuk Genel Kurulu",
11 | "1. Hukuk Dairesi", "2. Hukuk Dairesi", "3. Hukuk Dairesi", "4. Hukuk Dairesi",
12 | "5. Hukuk Dairesi", "6. Hukuk Dairesi", "7. Hukuk Dairesi", "8. Hukuk Dairesi",
13 | "9. Hukuk Dairesi", "10. Hukuk Dairesi", "11. Hukuk Dairesi", "12. Hukuk Dairesi",
14 | "13. Hukuk Dairesi", "14. Hukuk Dairesi", "15. Hukuk Dairesi", "16. Hukuk Dairesi",
15 | "17. Hukuk Dairesi", "18. Hukuk Dairesi", "19. Hukuk Dairesi", "20. Hukuk Dairesi",
16 | "21. Hukuk Dairesi", "22. Hukuk Dairesi", "23. Hukuk Dairesi",
17 | "Hukuk Daireleri Başkanlar Kurulu",
18 | # Ceza (Criminal) Chambers
19 | "Ceza Genel Kurulu",
20 | "1. Ceza Dairesi", "2. Ceza Dairesi", "3. Ceza Dairesi", "4. Ceza Dairesi",
21 | "5. Ceza Dairesi", "6. Ceza Dairesi", "7. Ceza Dairesi", "8. Ceza Dairesi",
22 | "9. Ceza Dairesi", "10. Ceza Dairesi", "11. Ceza Dairesi", "12. Ceza Dairesi",
23 | "13. Ceza Dairesi", "14. Ceza Dairesi", "15. Ceza Dairesi", "16. Ceza Dairesi",
24 | "17. Ceza Dairesi", "18. Ceza Dairesi", "19. Ceza Dairesi", "20. Ceza Dairesi",
25 | "21. Ceza Dairesi", "22. Ceza Dairesi", "23. Ceza Dairesi",
26 | "Ceza Daireleri Başkanlar Kurulu",
27 | # General Assembly
28 | "Büyük Genel Kurulu"
29 | ]
30 |
31 | class YargitayDetailedSearchRequest(BaseModel):
32 | """
33 | Model for the 'data' object sent in the request payload
34 | to Yargitay's detailed search endpoint (e.g., /aramadetaylist).
35 | Based on the payload provided by the user.
36 | """
37 | arananKelime: Optional[str] = Field("", description="Turkish keywords (supports +word -word \"phrase\" operators)")
38 | # Department/Board selection - Complete Court of Cassation chamber hierarchy
39 | birimYrgKurulDaire: Optional[str] = Field("ALL", description="Chamber (ALL or specific chamber name)")
40 |
41 | esasYil: Optional[str] = Field("", description="Case year (YYYY)")
42 | esasIlkSiraNo: Optional[str] = Field("", description="Start case no")
43 | esasSonSiraNo: Optional[str] = Field("", description="End case no")
44 |
45 | kararYil: Optional[str] = Field("", description="Decision year (YYYY)")
46 | kararIlkSiraNo: Optional[str] = Field("", description="Start decision no")
47 | kararSonSiraNo: Optional[str] = Field("", description="End decision no")
48 |
49 | baslangicTarihi: Optional[str] = Field("", description="Start date (DD.MM.YYYY)")
50 | bitisTarihi: Optional[str] = Field("", description="End date (DD.MM.YYYY)")
51 |
52 |
53 | pageSize: int = Field(10, ge=1, le=10, description="Results per page (1-100)")
54 | pageNumber: int = Field(1, ge=1, description="Page number (1-indexed)")
55 |
56 | class YargitayApiDecisionEntry(BaseModel):
57 | """Model for an individual decision entry from the Yargitay API search response."""
58 | id: str # Unique system ID of the decision
59 | daire: Optional[str] = Field(None, description="Chamber")
60 | esasNo: Optional[str] = Field(None, alias="esasNo", description="Case no")
61 | kararNo: Optional[str] = Field(None, alias="kararNo", description="Decision no")
62 | kararTarihi: Optional[str] = Field(None, alias="kararTarihi", description="Date")
63 | # 'index' and 'siraNo' from API response are not critical for MCP tool, so omitted for brevity
64 |
65 | # This field will be populated by the client after fetching the search list
66 | document_url: Optional[HttpUrl] = Field(None, description="Document URL")
67 |
68 | model_config = ConfigDict(populate_by_name=True) # To allow populating by alias from API response
69 |
70 |
71 | class YargitayApiResponseInnerData(BaseModel):
72 | """Model for the inner 'data' object in the Yargitay API search response."""
73 | data: List[YargitayApiDecisionEntry] = Field(default_factory=list)
74 | # draw: Optional[int] = None # Typically used by DataTables, not essential for MCP
75 | recordsTotal: int = Field(default=0) # Total number of records matching the query
76 | recordsFiltered: int = Field(default=0) # Total number of records after filtering (usually same as recordsTotal)
77 |
78 | class YargitayApiSearchResponse(BaseModel):
79 | """Model for the complete search response from the Yargitay API."""
80 | data: Optional[YargitayApiResponseInnerData] = Field(default_factory=lambda: YargitayApiResponseInnerData())
81 | # metadata: Optional[Dict[str, Any]] = None # Optional metadata from API
82 |
83 | class YargitayDocumentMarkdown(BaseModel):
84 | """Model for a Yargitay decision document, containing only Markdown content."""
85 | id: str = Field(..., description="Document ID")
86 | markdown_content: Optional[str] = Field(None, description="Content")
87 | source_url: HttpUrl = Field(..., description="Source URL")
88 |
89 | class CleanYargitayDecisionEntry(BaseModel):
90 | """Clean decision entry without arananKelime field to reduce token usage."""
91 | id: str
92 | daire: Optional[str] = Field(None, description="Chamber")
93 | esasNo: Optional[str] = Field(None, description="Case no")
94 | kararNo: Optional[str] = Field(None, description="Decision no")
95 | kararTarihi: Optional[str] = Field(None, description="Date")
96 | document_url: Optional[HttpUrl] = Field(None, description="Document URL")
97 |
98 | class CompactYargitaySearchResult(BaseModel):
99 | """A more compact search result model for the MCP tool to return."""
100 | decisions: List[CleanYargitayDecisionEntry]
101 | total_records: int
102 | requested_page: int
103 | page_size: int
```
--------------------------------------------------------------------------------
/yargitay_mcp_module/models.py:
--------------------------------------------------------------------------------
```python
1 | # yargitay_mcp_module/models.py
2 |
3 | from pydantic import BaseModel, Field, HttpUrl, ConfigDict
4 | from typing import List, Optional, Dict, Any, Literal
5 |
6 | # Yargıtay Chamber/Board Options
7 | YargitayBirimEnum = Literal[
8 | "ALL", # "ALL" for all chambers
9 | # Hukuk (Civil) Chambers
10 | "Hukuk Genel Kurulu",
11 | "1. Hukuk Dairesi", "2. Hukuk Dairesi", "3. Hukuk Dairesi", "4. Hukuk Dairesi",
12 | "5. Hukuk Dairesi", "6. Hukuk Dairesi", "7. Hukuk Dairesi", "8. Hukuk Dairesi",
13 | "9. Hukuk Dairesi", "10. Hukuk Dairesi", "11. Hukuk Dairesi", "12. Hukuk Dairesi",
14 | "13. Hukuk Dairesi", "14. Hukuk Dairesi", "15. Hukuk Dairesi", "16. Hukuk Dairesi",
15 | "17. Hukuk Dairesi", "18. Hukuk Dairesi", "19. Hukuk Dairesi", "20. Hukuk Dairesi",
16 | "21. Hukuk Dairesi", "22. Hukuk Dairesi", "23. Hukuk Dairesi",
17 | "Hukuk Daireleri Başkanlar Kurulu",
18 | # Ceza (Criminal) Chambers
19 | "Ceza Genel Kurulu",
20 | "1. Ceza Dairesi", "2. Ceza Dairesi", "3. Ceza Dairesi", "4. Ceza Dairesi",
21 | "5. Ceza Dairesi", "6. Ceza Dairesi", "7. Ceza Dairesi", "8. Ceza Dairesi",
22 | "9. Ceza Dairesi", "10. Ceza Dairesi", "11. Ceza Dairesi", "12. Ceza Dairesi",
23 | "13. Ceza Dairesi", "14. Ceza Dairesi", "15. Ceza Dairesi", "16. Ceza Dairesi",
24 | "17. Ceza Dairesi", "18. Ceza Dairesi", "19. Ceza Dairesi", "20. Ceza Dairesi",
25 | "21. Ceza Dairesi", "22. Ceza Dairesi", "23. Ceza Dairesi",
26 | "Ceza Daireleri Başkanlar Kurulu",
27 | # General Assembly
28 | "Büyük Genel Kurulu"
29 | ]
30 |
31 | class YargitayDetailedSearchRequest(BaseModel):
32 | """
33 | Model for the 'data' object sent in the request payload
34 | to Yargitay's detailed search endpoint (e.g., /aramadetaylist).
35 | Based on the payload provided by the user.
36 | """
37 | arananKelime: Optional[str] = Field("", description="Turkish keywords (supports +word -word \"phrase\" operators)")
38 | # Department/Board selection - Complete Court of Cassation chamber hierarchy
39 | birimYrgKurulDaire: Optional[str] = Field("ALL", description="Chamber (ALL or specific chamber name)")
40 |
41 | esasYil: Optional[str] = Field("", description="Case year (YYYY)")
42 | esasIlkSiraNo: Optional[str] = Field("", description="Start case no")
43 | esasSonSiraNo: Optional[str] = Field("", description="End case no")
44 |
45 | kararYil: Optional[str] = Field("", description="Decision year (YYYY)")
46 | kararIlkSiraNo: Optional[str] = Field("", description="Start decision no")
47 | kararSonSiraNo: Optional[str] = Field("", description="End decision no")
48 |
49 | baslangicTarihi: Optional[str] = Field("", description="Start date (DD.MM.YYYY)")
50 | bitisTarihi: Optional[str] = Field("", description="End date (DD.MM.YYYY)")
51 |
52 |
53 | pageSize: int = Field(10, ge=1, le=10, description="Results per page (1-100)")
54 | pageNumber: int = Field(1, ge=1, description="Page number (1-indexed)")
55 |
56 | class YargitayApiDecisionEntry(BaseModel):
57 | """Model for an individual decision entry from the Yargitay API search response."""
58 | id: str # Unique system ID of the decision
59 | daire: Optional[str] = Field(None, description="Chamber")
60 | esasNo: Optional[str] = Field(None, alias="esasNo", description="Case no")
61 | kararNo: Optional[str] = Field(None, alias="kararNo", description="Decision no")
62 | kararTarihi: Optional[str] = Field(None, alias="kararTarihi", description="Date")
63 | # 'index' and 'siraNo' from API response are not critical for MCP tool, so omitted for brevity
64 |
65 | # This field will be populated by the client after fetching the search list
66 | document_url: Optional[HttpUrl] = Field(None, description="Document URL")
67 |
68 | model_config = ConfigDict(populate_by_name=True) # To allow populating by alias from API response
69 |
70 |
71 | class YargitayApiResponseInnerData(BaseModel):
72 | """Model for the inner 'data' object in the Yargitay API search response."""
73 | data: List[YargitayApiDecisionEntry] = Field(default_factory=list)
74 | # draw: Optional[int] = None # Typically used by DataTables, not essential for MCP
75 | recordsTotal: int = Field(default=0) # Total number of records matching the query
76 | recordsFiltered: int = Field(default=0) # Total number of records after filtering (usually same as recordsTotal)
77 |
78 | class YargitayApiSearchResponse(BaseModel):
79 | """Model for the complete search response from the Yargitay API."""
80 | data: Optional[YargitayApiResponseInnerData] = Field(default_factory=lambda: YargitayApiResponseInnerData())
81 | # metadata: Optional[Dict[str, Any]] = None # Optional metadata from API
82 |
83 | class YargitayDocumentMarkdown(BaseModel):
84 | """Model for a Yargitay decision document, containing only Markdown content."""
85 | id: str = Field(..., description="Document ID")
86 | markdown_content: Optional[str] = Field(None, description="Content")
87 | source_url: HttpUrl = Field(..., description="Source URL")
88 |
89 | class CleanYargitayDecisionEntry(BaseModel):
90 | """Clean decision entry without arananKelime field to reduce token usage."""
91 | id: str
92 | daire: Optional[str] = Field(None, description="Chamber")
93 | esasNo: Optional[str] = Field(None, description="Case no")
94 | kararNo: Optional[str] = Field(None, description="Decision no")
95 | kararTarihi: Optional[str] = Field(None, description="Date")
96 | document_url: Optional[HttpUrl] = Field(None, description="Document URL")
97 |
98 | class CompactYargitaySearchResult(BaseModel):
99 | """A more compact search result model for the MCP tool to return."""
100 | decisions: List[CleanYargitayDecisionEntry]
101 | total_records: int
102 | requested_page: int
103 | page_size: int
```
--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/sayistay_mcp_module/unified_client.py:
--------------------------------------------------------------------------------
```python
1 | # sayistay_mcp_module/unified_client.py
2 | # Unified client for all three Sayıştay decision types
3 |
4 | import logging
5 | from typing import Optional, Dict, Any
6 | from urllib.parse import urlparse
7 |
8 | from .models import (
9 | SayistayUnifiedSearchRequest,
10 | SayistayUnifiedSearchResult,
11 | SayistayUnifiedDocumentMarkdown,
12 | GenelKurulSearchRequest,
13 | TemyizKuruluSearchRequest,
14 | DaireSearchRequest
15 | )
16 | from .client import SayistayApiClient
17 |
18 | logger = logging.getLogger(__name__)
19 |
20 | class SayistayUnifiedClient:
21 | """Unified client that handles all three Sayıştay decision types."""
22 |
23 | def __init__(self, request_timeout: float = 60.0):
24 | self.client = SayistayApiClient(request_timeout)
25 |
26 | async def search_unified(self, params: SayistayUnifiedSearchRequest) -> SayistayUnifiedSearchResult:
27 | """Unified search that routes to appropriate search method based on decision_type."""
28 |
29 | if params.decision_type == "genel_kurul":
30 | # Convert to genel kurul request
31 | genel_kurul_params = GenelKurulSearchRequest(
32 | karar_no=params.karar_no,
33 | karar_ek=params.karar_ek,
34 | karar_tarih_baslangic=params.karar_tarih_baslangic,
35 | karar_tarih_bitis=params.karar_tarih_bitis,
36 | karar_tamami=params.karar_tamami,
37 | start=params.start,
38 | length=params.length
39 | )
40 |
41 | result = await self.client.search_genel_kurul_decisions(genel_kurul_params)
42 |
43 | # Convert to unified format
44 | decisions_list = [decision.model_dump() for decision in result.decisions]
45 |
46 | return SayistayUnifiedSearchResult(
47 | decision_type="genel_kurul",
48 | decisions=decisions_list,
49 | total_records=result.total_records,
50 | total_filtered=result.total_filtered,
51 | draw=result.draw
52 | )
53 |
54 | elif params.decision_type == "temyiz_kurulu":
55 | # Convert to temyiz kurulu request
56 | temyiz_params = TemyizKuruluSearchRequest(
57 | ilam_dairesi=params.ilam_dairesi,
58 | yili=params.yili,
59 | karar_tarih_baslangic=params.karar_tarih_baslangic,
60 | karar_tarih_bitis=params.karar_tarih_bitis,
61 | kamu_idaresi_turu=params.kamu_idaresi_turu,
62 | ilam_no=params.ilam_no,
63 | dosya_no=params.dosya_no,
64 | temyiz_tutanak_no=params.temyiz_tutanak_no,
65 | temyiz_karar=params.temyiz_karar,
66 | web_karar_konusu=params.web_karar_konusu,
67 | start=params.start,
68 | length=params.length
69 | )
70 |
71 | result = await self.client.search_temyiz_kurulu_decisions(temyiz_params)
72 |
73 | # Convert to unified format
74 | decisions_list = [decision.model_dump() for decision in result.decisions]
75 |
76 | return SayistayUnifiedSearchResult(
77 | decision_type="temyiz_kurulu",
78 | decisions=decisions_list,
79 | total_records=result.total_records,
80 | total_filtered=result.total_filtered,
81 | draw=result.draw
82 | )
83 |
84 | elif params.decision_type == "daire":
85 | # Convert to daire request
86 | daire_params = DaireSearchRequest(
87 | yargilama_dairesi=params.yargilama_dairesi,
88 | karar_tarih_baslangic=params.karar_tarih_baslangic,
89 | karar_tarih_bitis=params.karar_tarih_bitis,
90 | ilam_no=params.ilam_no,
91 | kamu_idaresi_turu=params.kamu_idaresi_turu,
92 | hesap_yili=params.hesap_yili,
93 | web_karar_konusu=params.web_karar_konusu,
94 | web_karar_metni=params.web_karar_metni,
95 | start=params.start,
96 | length=params.length
97 | )
98 |
99 | result = await self.client.search_daire_decisions(daire_params)
100 |
101 | # Convert to unified format
102 | decisions_list = [decision.model_dump() for decision in result.decisions]
103 |
104 | return SayistayUnifiedSearchResult(
105 | decision_type="daire",
106 | decisions=decisions_list,
107 | total_records=result.total_records,
108 | total_filtered=result.total_filtered,
109 | draw=result.draw
110 | )
111 |
112 | else:
113 | raise ValueError(f"Unsupported decision type: {params.decision_type}")
114 |
115 | async def get_document_unified(self, decision_id: str, decision_type: str) -> SayistayUnifiedDocumentMarkdown:
116 | """Unified document retrieval for all Sayıştay decision types."""
117 |
118 | # Use existing client method (decision_type is already a string)
119 | result = await self.client.get_document_as_markdown(decision_id, decision_type)
120 |
121 | return SayistayUnifiedDocumentMarkdown(
122 | decision_type=decision_type,
123 | decision_id=result.decision_id,
124 | source_url=result.source_url,
125 | document_data=result.model_dump(),
126 | markdown_content=result.markdown_content,
127 | error_message=result.error_message
128 | )
129 |
130 | async def close_client_session(self):
131 | """Close the underlying client session."""
132 | if hasattr(self.client, 'close_client_session'):
133 | await self.client.close_client_session()
```
--------------------------------------------------------------------------------
/sayistay_mcp_module/unified_client.py:
--------------------------------------------------------------------------------
```python
1 | # sayistay_mcp_module/unified_client.py
2 | # Unified client for all three Sayıştay decision types
3 |
4 | import logging
5 | from typing import Optional, Dict, Any
6 | from urllib.parse import urlparse
7 |
8 | from .models import (
9 | SayistayUnifiedSearchRequest,
10 | SayistayUnifiedSearchResult,
11 | SayistayUnifiedDocumentMarkdown,
12 | GenelKurulSearchRequest,
13 | TemyizKuruluSearchRequest,
14 | DaireSearchRequest
15 | )
16 | from .client import SayistayApiClient
17 |
18 | logger = logging.getLogger(__name__)
19 |
20 | class SayistayUnifiedClient:
21 | """Unified client that handles all three Sayıştay decision types."""
22 |
23 | def __init__(self, request_timeout: float = 60.0):
24 | self.client = SayistayApiClient(request_timeout)
25 |
26 | async def search_unified(self, params: SayistayUnifiedSearchRequest) -> SayistayUnifiedSearchResult:
27 | """Unified search that routes to appropriate search method based on decision_type."""
28 |
29 | if params.decision_type == "genel_kurul":
30 | # Convert to genel kurul request
31 | genel_kurul_params = GenelKurulSearchRequest(
32 | karar_no=params.karar_no,
33 | karar_ek=params.karar_ek,
34 | karar_tarih_baslangic=params.karar_tarih_baslangic,
35 | karar_tarih_bitis=params.karar_tarih_bitis,
36 | karar_tamami=params.karar_tamami,
37 | start=params.start,
38 | length=params.length
39 | )
40 |
41 | result = await self.client.search_genel_kurul_decisions(genel_kurul_params)
42 |
43 | # Convert to unified format
44 | decisions_list = [decision.model_dump() for decision in result.decisions]
45 |
46 | return SayistayUnifiedSearchResult(
47 | decision_type="genel_kurul",
48 | decisions=decisions_list,
49 | total_records=result.total_records,
50 | total_filtered=result.total_filtered,
51 | draw=result.draw
52 | )
53 |
54 | elif params.decision_type == "temyiz_kurulu":
55 | # Convert to temyiz kurulu request
56 | temyiz_params = TemyizKuruluSearchRequest(
57 | ilam_dairesi=params.ilam_dairesi,
58 | yili=params.yili,
59 | karar_tarih_baslangic=params.karar_tarih_baslangic,
60 | karar_tarih_bitis=params.karar_tarih_bitis,
61 | kamu_idaresi_turu=params.kamu_idaresi_turu,
62 | ilam_no=params.ilam_no,
63 | dosya_no=params.dosya_no,
64 | temyiz_tutanak_no=params.temyiz_tutanak_no,
65 | temyiz_karar=params.temyiz_karar,
66 | web_karar_konusu=params.web_karar_konusu,
67 | start=params.start,
68 | length=params.length
69 | )
70 |
71 | result = await self.client.search_temyiz_kurulu_decisions(temyiz_params)
72 |
73 | # Convert to unified format
74 | decisions_list = [decision.model_dump() for decision in result.decisions]
75 |
76 | return SayistayUnifiedSearchResult(
77 | decision_type="temyiz_kurulu",
78 | decisions=decisions_list,
79 | total_records=result.total_records,
80 | total_filtered=result.total_filtered,
81 | draw=result.draw
82 | )
83 |
84 | elif params.decision_type == "daire":
85 | # Convert to daire request
86 | daire_params = DaireSearchRequest(
87 | yargilama_dairesi=params.yargilama_dairesi,
88 | karar_tarih_baslangic=params.karar_tarih_baslangic,
89 | karar_tarih_bitis=params.karar_tarih_bitis,
90 | ilam_no=params.ilam_no,
91 | kamu_idaresi_turu=params.kamu_idaresi_turu,
92 | hesap_yili=params.hesap_yili,
93 | web_karar_konusu=params.web_karar_konusu,
94 | web_karar_metni=params.web_karar_metni,
95 | start=params.start,
96 | length=params.length
97 | )
98 |
99 | result = await self.client.search_daire_decisions(daire_params)
100 |
101 | # Convert to unified format
102 | decisions_list = [decision.model_dump() for decision in result.decisions]
103 |
104 | return SayistayUnifiedSearchResult(
105 | decision_type="daire",
106 | decisions=decisions_list,
107 | total_records=result.total_records,
108 | total_filtered=result.total_filtered,
109 | draw=result.draw
110 | )
111 |
112 | else:
113 | raise ValueError(f"Unsupported decision type: {params.decision_type}")
114 |
115 | async def get_document_unified(self, decision_id: str, decision_type: str) -> SayistayUnifiedDocumentMarkdown:
116 | """Unified document retrieval for all Sayıştay decision types."""
117 |
118 | # Use existing client method (decision_type is already a string)
119 | result = await self.client.get_document_as_markdown(decision_id, decision_type)
120 |
121 | return SayistayUnifiedDocumentMarkdown(
122 | decision_type=decision_type,
123 | decision_id=result.decision_id,
124 | source_url=result.source_url,
125 | document_data=result.model_dump(),
126 | markdown_content=result.markdown_content,
127 | error_message=result.error_message
128 | )
129 |
130 | async def close_client_session(self):
131 | """Close the underlying client session."""
132 | if hasattr(self.client, 'close_client_session'):
133 | await self.client.close_client_session()
```
--------------------------------------------------------------------------------
/.serena/project.yml:
--------------------------------------------------------------------------------
```yaml
1 | # list of languages for which language servers are started; choose from:
2 | # al bash clojure cpp csharp csharp_omnisharp
3 | # dart elixir elm erlang fortran go
4 | # haskell java julia kotlin lua markdown
5 | # nix perl php python python_jedi r
6 | # rego ruby ruby_solargraph rust scala swift
7 | # terraform typescript typescript_vts yaml zig
8 | # Note:
9 | # - For C, use cpp
10 | # - For JavaScript, use typescript
11 | # Special requirements:
12 | # - csharp: Requires the presence of a .sln file in the project folder.
13 | # When using multiple languages, the first language server that supports a given file will be used for that file.
14 | # The first language is the default language and the respective language server will be used as a fallback.
15 | # Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
16 | languages:
17 | - python
18 |
19 | # the encoding used by text files in the project
20 | # For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
21 | encoding: "utf-8"
22 |
23 | # whether to use the project's gitignore file to ignore files
24 | # Added on 2025-04-07
25 | ignore_all_files_in_gitignore: true
26 |
27 | # list of additional paths to ignore
28 | # same syntax as gitignore, so you can use * and **
29 | # Was previously called `ignored_dirs`, please update your config if you are using that.
30 | # Added (renamed) on 2025-04-07
31 | ignored_paths: []
32 |
33 | # whether the project is in read-only mode
34 | # If set to true, all editing tools will be disabled and attempts to use them will result in an error
35 | # Added on 2025-04-18
36 | read_only: false
37 |
38 | # list of tool names to exclude. We recommend not excluding any tools, see the readme for more details.
39 | # Below is the complete list of tools for convenience.
40 | # To make sure you have the latest list of tools, and to view their descriptions,
41 | # execute `uv run scripts/print_tool_overview.py`.
42 | #
43 | # * `activate_project`: Activates a project by name.
44 | # * `check_onboarding_performed`: Checks whether project onboarding was already performed.
45 | # * `create_text_file`: Creates/overwrites a file in the project directory.
46 | # * `delete_lines`: Deletes a range of lines within a file.
47 | # * `delete_memory`: Deletes a memory from Serena's project-specific memory store.
48 | # * `execute_shell_command`: Executes a shell command.
49 | # * `find_referencing_code_snippets`: Finds code snippets in which the symbol at the given location is referenced.
50 | # * `find_referencing_symbols`: Finds symbols that reference the symbol at the given location (optionally filtered by type).
51 | # * `find_symbol`: Performs a global (or local) search for symbols with/containing a given name/substring (optionally filtered by type).
52 | # * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
53 | # * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
54 | # * `initial_instructions`: Gets the initial instructions for the current project.
55 | # Should only be used in settings where the system prompt cannot be set,
56 | # e.g. in clients you have no control over, like Claude Desktop.
57 | # * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
58 | # * `insert_at_line`: Inserts content at a given line in a file.
59 | # * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
60 | # * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
61 | # * `list_memories`: Lists memories in Serena's project-specific memory store.
62 | # * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
63 | # * `prepare_for_new_conversation`: Provides instructions for preparing for a new conversation (in order to continue with the necessary context).
64 | # * `read_file`: Reads a file within the project directory.
65 | # * `read_memory`: Reads the memory with the given name from Serena's project-specific memory store.
66 | # * `remove_project`: Removes a project from the Serena configuration.
67 | # * `replace_lines`: Replaces a range of lines within a file with new content.
68 | # * `replace_symbol_body`: Replaces the full definition of a symbol.
69 | # * `restart_language_server`: Restarts the language server, may be necessary when edits not through Serena happen.
70 | # * `search_for_pattern`: Performs a search for a pattern in the project.
71 | # * `summarize_changes`: Provides instructions for summarizing the changes made to the codebase.
72 | # * `switch_modes`: Activates modes by providing a list of their names
73 | # * `think_about_collected_information`: Thinking tool for pondering the completeness of collected information.
74 | # * `think_about_task_adherence`: Thinking tool for determining whether the agent is still on track with the current task.
75 | # * `think_about_whether_you_are_done`: Thinking tool for determining whether the task is truly completed.
76 | # * `write_memory`: Writes a named memory (for future reference) to Serena's project-specific memory store.
77 | excluded_tools: []
78 |
79 | # initial prompt for the project. It will always be given to the LLM upon activating the project
80 | # (contrary to the memories, which are loaded on demand).
81 | initial_prompt: ""
82 |
83 | project_name: "yargi-mcp"
84 | included_optional_tools: []
85 |
```
--------------------------------------------------------------------------------
/danistay_mcp_module/models.py:
--------------------------------------------------------------------------------
```python
1 | # danistay_mcp_module/models.py
2 |
3 | from pydantic import BaseModel, Field, HttpUrl, ConfigDict
4 | from typing import List, Optional, Dict, Any
5 |
6 | class DanistayBaseSearchRequest(BaseModel):
7 | """Base model for common search parameters for Danistay."""
8 | pageSize: int = Field(default=10, ge=1, le=10)
9 | pageNumber: int = Field(default=1, ge=1)
10 | # siralama and siralamaDirection are part of detailed search, not necessarily keyword search
11 | # as per user's provided payloads.
12 |
13 | class DanistayKeywordSearchRequestData(BaseModel):
14 | """Internal data model for the keyword search payload's 'data' field."""
15 | andKelimeler: List[str] = Field(default_factory=list)
16 | orKelimeler: List[str] = Field(default_factory=list)
17 | notAndKelimeler: List[str] = Field(default_factory=list)
18 | notOrKelimeler: List[str] = Field(default_factory=list)
19 | pageSize: int
20 | pageNumber: int
21 |
22 | class DanistayKeywordSearchRequest(BaseModel): # This is the model the MCP tool will accept
23 | """Model for keyword-based search request for Danistay."""
24 | andKelimeler: List[str] = Field(default_factory=list, description="AND keywords")
25 | orKelimeler: List[str] = Field(default_factory=list, description="OR keywords")
26 | notAndKelimeler: List[str] = Field(default_factory=list, description="NOT AND keywords")
27 | notOrKelimeler: List[str] = Field(default_factory=list, description="NOT OR keywords")
28 | pageSize: int = Field(default=10, ge=1, le=10)
29 | pageNumber: int = Field(default=1, ge=1)
30 |
31 | class DanistayDetailedSearchRequestData(BaseModel): # Internal data model for detailed search payload
32 | """Internal data model for the detailed search payload's 'data' field."""
33 | daire: Optional[str] = "" # API expects empty string for None
34 | esasYil: Optional[str] = ""
35 | esasIlkSiraNo: Optional[str] = ""
36 | esasSonSiraNo: Optional[str] = ""
37 | kararYil: Optional[str] = ""
38 | kararIlkSiraNo: Optional[str] = ""
39 | kararSonSiraNo: Optional[str] = ""
40 | baslangicTarihi: Optional[str] = ""
41 | bitisTarihi: Optional[str] = ""
42 | mevzuatNumarasi: Optional[str] = ""
43 | mevzuatAdi: Optional[str] = ""
44 | madde: Optional[str] = ""
45 | siralama: str # Seems mandatory in detailed search payload
46 | siralamaDirection: str # Seems mandatory
47 | pageSize: int
48 | pageNumber: int
49 | # Note: 'arananKelime' is not in the detailed search payload example provided by user.
50 | # If it can be included, it should be added here.
51 |
52 | class DanistayDetailedSearchRequest(DanistayBaseSearchRequest): # MCP tool will accept this
53 | """Model for detailed search request for Danistay."""
54 | daire: str = Field("", description="Chamber")
55 | esasYil: str = Field("", description="Case year")
56 | esasIlkSiraNo: str = Field("", description="Start case no")
57 | esasSonSiraNo: str = Field("", description="End case no")
58 | kararYil: str = Field("", description="Decision year")
59 | kararIlkSiraNo: str = Field("", description="Start decision no")
60 | kararSonSiraNo: str = Field("", description="End decision no")
61 | baslangicTarihi: str = Field("", description="Start date")
62 | bitisTarihi: str = Field("", description="End date")
63 | mevzuatNumarasi: str = Field("", description="Law number")
64 | mevzuatAdi: str = Field("", description="Law name")
65 | madde: str = Field("", description="Article")
66 | # Add a general keyword field if detailed search also supports it
67 | # arananKelime: Optional[str] = Field(None, description="General keyword for detailed search.")
68 |
69 |
70 | class DanistayApiDecisionEntry(BaseModel):
71 | """Model for an individual decision entry from the Danistay API search response.
72 | Based on user-provided response samples for both keyword and detailed search.
73 | """
74 | id: str
75 | # The API response for keyword search uses "daireKurul", detailed search example uses "daire".
76 | # We use an alias to handle both and map to a consistent field name "chamber".
77 | chamber: str = Field("", alias="daire", description="Chamber")
78 | esasNo: str = Field("", description="Case number")
79 | kararNo: str = Field("", description="Decision number")
80 | kararTarihi: str = Field("", description="Decision date")
81 | arananKelime: str = Field("", description="Keyword")
82 | # index: Optional[int] = None # Present in response, can be added if needed by MCP tool
83 | # siraNo: Optional[int] = None # Present in detailed response, can be added
84 |
85 | document_url: Optional[HttpUrl] = Field(None, description="Document URL")
86 |
87 | model_config = ConfigDict(populate_by_name=True, extra='ignore') # Important for alias to work and ignore extra fields
88 |
89 | class DanistayApiResponseInnerData(BaseModel):
90 | """Model for the inner 'data' object in the Danistay API search response."""
91 | data: List[DanistayApiDecisionEntry]
92 | recordsTotal: int
93 | recordsFiltered: int
94 | draw: int = Field(0, description="Draw counter")
95 |
96 | class DanistayApiResponse(BaseModel):
97 | """Model for the complete search response from the Danistay API."""
98 | data: Optional[DanistayApiResponseInnerData] = Field(None, description="Response data, can be null when no results found")
99 | metadata: Optional[Dict[str, Any]] = Field(None, description="Optional metadata (Meta Veri) from API.")
100 |
101 | class DanistayDocumentMarkdown(BaseModel):
102 | """Model for a Danistay decision document, containing only Markdown content."""
103 | id: str
104 | markdown_content: str = Field("", description="The decision content (Karar İçeriği) converted to Markdown.")
105 | source_url: HttpUrl
106 |
107 | class CompactDanistaySearchResult(BaseModel):
108 | """A compact search result model for the MCP tool to return."""
109 | decisions: List[DanistayApiDecisionEntry]
110 | total_records: int
111 | requested_page: int
112 | page_size: int
```
--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/danistay_mcp_module/models.py:
--------------------------------------------------------------------------------
```python
1 | # danistay_mcp_module/models.py
2 |
3 | from pydantic import BaseModel, Field, HttpUrl, ConfigDict
4 | from typing import List, Optional, Dict, Any
5 |
6 | class DanistayBaseSearchRequest(BaseModel):
7 | """Base model for common search parameters for Danistay."""
8 | pageSize: int = Field(default=10, ge=1, le=10)
9 | pageNumber: int = Field(default=1, ge=1)
10 | # siralama and siralamaDirection are part of detailed search, not necessarily keyword search
11 | # as per user's provided payloads.
12 |
13 | class DanistayKeywordSearchRequestData(BaseModel):
14 | """Internal data model for the keyword search payload's 'data' field."""
15 | andKelimeler: List[str] = Field(default_factory=list)
16 | orKelimeler: List[str] = Field(default_factory=list)
17 | notAndKelimeler: List[str] = Field(default_factory=list)
18 | notOrKelimeler: List[str] = Field(default_factory=list)
19 | pageSize: int
20 | pageNumber: int
21 |
22 | class DanistayKeywordSearchRequest(BaseModel): # This is the model the MCP tool will accept
23 | """Model for keyword-based search request for Danistay."""
24 | andKelimeler: List[str] = Field(default_factory=list, description="AND keywords")
25 | orKelimeler: List[str] = Field(default_factory=list, description="OR keywords")
26 | notAndKelimeler: List[str] = Field(default_factory=list, description="NOT AND keywords")
27 | notOrKelimeler: List[str] = Field(default_factory=list, description="NOT OR keywords")
28 | pageSize: int = Field(default=10, ge=1, le=10)
29 | pageNumber: int = Field(default=1, ge=1)
30 |
31 | class DanistayDetailedSearchRequestData(BaseModel): # Internal data model for detailed search payload
32 | """Internal data model for the detailed search payload's 'data' field."""
33 | daire: Optional[str] = "" # API expects empty string for None
34 | esasYil: Optional[str] = ""
35 | esasIlkSiraNo: Optional[str] = ""
36 | esasSonSiraNo: Optional[str] = ""
37 | kararYil: Optional[str] = ""
38 | kararIlkSiraNo: Optional[str] = ""
39 | kararSonSiraNo: Optional[str] = ""
40 | baslangicTarihi: Optional[str] = ""
41 | bitisTarihi: Optional[str] = ""
42 | mevzuatNumarasi: Optional[str] = ""
43 | mevzuatAdi: Optional[str] = ""
44 | madde: Optional[str] = ""
45 | siralama: str # Seems mandatory in detailed search payload
46 | siralamaDirection: str # Seems mandatory
47 | pageSize: int
48 | pageNumber: int
49 | # Note: 'arananKelime' is not in the detailed search payload example provided by user.
50 | # If it can be included, it should be added here.
51 |
52 | class DanistayDetailedSearchRequest(DanistayBaseSearchRequest): # MCP tool will accept this
53 | """Model for detailed search request for Danistay."""
54 | daire: str = Field("", description="Chamber")
55 | esasYil: str = Field("", description="Case year")
56 | esasIlkSiraNo: str = Field("", description="Start case no")
57 | esasSonSiraNo: str = Field("", description="End case no")
58 | kararYil: str = Field("", description="Decision year")
59 | kararIlkSiraNo: str = Field("", description="Start decision no")
60 | kararSonSiraNo: str = Field("", description="End decision no")
61 | baslangicTarihi: str = Field("", description="Start date")
62 | bitisTarihi: str = Field("", description="End date")
63 | mevzuatNumarasi: str = Field("", description="Law number")
64 | mevzuatAdi: str = Field("", description="Law name")
65 | madde: str = Field("", description="Article")
66 | # Add a general keyword field if detailed search also supports it
67 | # arananKelime: Optional[str] = Field(None, description="General keyword for detailed search.")
68 |
69 |
70 | class DanistayApiDecisionEntry(BaseModel):
71 | """Model for an individual decision entry from the Danistay API search response.
72 | Based on user-provided response samples for both keyword and detailed search.
73 | """
74 | id: str
75 | # The API response for keyword search uses "daireKurul", detailed search example uses "daire".
76 | # We use an alias to handle both and map to a consistent field name "chamber".
77 | chamber: str = Field("", alias="daire", description="Chamber")
78 | esasNo: str = Field("", description="Case number")
79 | kararNo: str = Field("", description="Decision number")
80 | kararTarihi: str = Field("", description="Decision date")
81 | arananKelime: str = Field("", description="Keyword")
82 | # index: Optional[int] = None # Present in response, can be added if needed by MCP tool
83 | # siraNo: Optional[int] = None # Present in detailed response, can be added
84 |
85 | document_url: Optional[HttpUrl] = Field(None, description="Document URL")
86 |
87 | model_config = ConfigDict(populate_by_name=True, extra='ignore') # Important for alias to work and ignore extra fields
88 |
89 | class DanistayApiResponseInnerData(BaseModel):
90 | """Model for the inner 'data' object in the Danistay API search response."""
91 | data: List[DanistayApiDecisionEntry]
92 | recordsTotal: int
93 | recordsFiltered: int
94 | draw: int = Field(0, description="Draw counter")
95 |
96 | class DanistayApiResponse(BaseModel):
97 | """Model for the complete search response from the Danistay API."""
98 | data: Optional[DanistayApiResponseInnerData] = Field(None, description="Response data, can be null when no results found")
99 | metadata: Optional[Dict[str, Any]] = Field(None, description="Optional metadata (Meta Veri) from API.")
100 |
101 | class DanistayDocumentMarkdown(BaseModel):
102 | """Model for a Danistay decision document, containing only Markdown content."""
103 | id: str
104 | markdown_content: str = Field("", description="The decision content (Karar İçeriği) converted to Markdown.")
105 | source_url: HttpUrl
106 |
107 | class CompactDanistaySearchResult(BaseModel):
108 | """A compact search result model for the MCP tool to return."""
109 | decisions: List[DanistayApiDecisionEntry]
110 | total_records: int
111 | requested_page: int
112 | page_size: int
```
--------------------------------------------------------------------------------
/kik_mcp_module/models_v2.py:
--------------------------------------------------------------------------------
```python
1 | # kik_mcp_module/models_v2.py
2 | from pydantic import BaseModel, Field, ConfigDict
3 | from typing import List, Optional
4 | from datetime import datetime
5 | from enum import Enum
6 |
7 | # New KIK v2 API Models
8 |
9 | class KikV2DecisionType(str, Enum):
10 | """KIK v2 Decision Types with corresponding endpoints."""
11 | UYUSMAZLIK = "uyusmazlik" # Disputes - GetKurulKararlari
12 | DUZENLEYICI = "duzenleyici" # Regulatory - GetKurulKararlariDk
13 | MAHKEME = "mahkeme" # Court - GetKurulKararlariMk
14 |
15 | class KikV2SearchRequest(BaseModel):
16 | """Model for KIK v2 API search request."""
17 | KararMetni: str = Field("", description="Decision text search query")
18 | KararNo: str = Field("", description="Decision number (e.g., '2025/UH.II-1801')")
19 | BasvuranAdi: str = Field("", description="Applicant name")
20 | IdareAdi: str = Field("", description="Administration name")
21 | BaslangicTarihi: str = Field("", description="Start date (YYYY-MM-DD)")
22 | BitisTarihi: str = Field("", description="End date (YYYY-MM-DD)")
23 |
24 | class KikV2KeyValuePair(BaseModel):
25 | """Key-value pair for KIK v2 API request."""
26 | key: str
27 | value: str
28 |
29 | class KikV2QueryRequest(BaseModel):
30 | """Nested query structure for KIK v2 API."""
31 | keyValueOfstringanyType: List[KikV2KeyValuePair]
32 |
33 | class KikV2RequestData(BaseModel):
34 | """Main request data structure for KIK v2 API."""
35 | keyValuePairs: KikV2QueryRequest
36 |
37 | # Request Payloads for different decision types
38 | class KikV2SearchPayload(BaseModel):
39 | """Complete payload for KIK v2 API search - Uyuşmazlık (Disputes)."""
40 | sorgulaKurulKararlari: KikV2RequestData
41 |
42 | class KikV2SearchPayloadDk(BaseModel):
43 | """Complete payload for KIK v2 API search - Düzenleyici (Regulatory)."""
44 | sorgulaKurulKararlariDk: KikV2RequestData
45 |
46 | class KikV2SearchPayloadMk(BaseModel):
47 | """Complete payload for KIK v2 API search - Mahkeme (Court)."""
48 | sorgulaKurulKararlariMk: KikV2RequestData
49 |
50 | # Response Models
51 |
52 | class KikV2DecisionDetail(BaseModel):
53 | """Individual decision detail from KIK v2 API response."""
54 | resmiGazeteMukerrerSayi: str = Field("", description="Official Gazette duplicate number")
55 | itiraz: str = Field("", description="Objection")
56 | yayinlanmaTarihi: str = Field("", description="Publication date")
57 | idareAdi: str = Field("", description="Administration name")
58 | uzmanTCKN: str = Field("", description="Expert TCKN")
59 | resmiGazeteTarihi: str = Field("", description="Official Gazette date")
60 | basvuruKonusu: str = Field("", description="Application subject")
61 | kararTurKod: str = Field("", description="Decision type code")
62 | kararTurAciklama: str = Field("", description="Decision type description")
63 | karar: str = Field("", description="Decision text")
64 | kararNo: str = Field("", description="Decision number")
65 | resmiGazeteSayisi: str = Field("", description="Official Gazette number")
66 | inceleme: str = Field("", description="Review")
67 | basvuruTarihi: str = Field("", description="Application date")
68 | kararNitelikKod: str = Field("", description="Decision nature code")
69 | resmiGazeteMukerrer: str = Field("", description="Official Gazette duplicate")
70 | basvuruSayisi: str = Field("", description="Application number")
71 | basvuran: str = Field("", description="Applicant")
72 | kararNitelik: str = Field("", description="Decision nature")
73 | uyusmazlikKararNo: str = Field("", description="Dispute decision number")
74 | kurulNo: str = Field("", description="Board number")
75 | gundemMaddesiSiraNo: str = Field("", description="Agenda item sequence")
76 | kararTarihi: str = Field("", description="Decision date (ISO format)")
77 | dosyaBirimKodu: str = Field("", description="File unit code")
78 | gundemMaddesiId: str = Field("", description="Agenda item ID")
79 |
80 | class KikV2DecisionGroup(BaseModel):
81 | """Group of decision details."""
82 | KurulKararTutanakDetayi: List[KikV2DecisionDetail] = Field(alias="kurulKararTutanakDetayi")
83 |
84 | model_config = ConfigDict(populate_by_name=True)
85 |
86 | class KikV2SearchResultData(BaseModel):
87 | """Search result data structure."""
88 | hataKodu: str = Field("", description="Error code")
89 | hataMesaji: str = Field("", description="Error message")
90 | KurulKararTutanakDetayListesi: List[KikV2DecisionGroup]
91 |
92 | model_config = ConfigDict(populate_by_name=True)
93 |
94 | class KikV2SearchResultWrapper(BaseModel):
95 | """Wrapper for search result."""
96 | SorgulaKurulKararlariResult: KikV2SearchResultData
97 |
98 | # Base Response Models
99 | class KikV2SearchResponse(BaseModel):
100 | """Complete KIK v2 API search response for Uyuşmazlık (Disputes)."""
101 | SorgulaKurulKararlariResponse: KikV2SearchResultWrapper
102 |
103 | # Düzenleyici Kararlar (Regulatory Decisions) Response Models
104 | class KikV2SearchResultWrapperDk(BaseModel):
105 | """Wrapper for regulatory decisions search result."""
106 | SorgulaKurulKararlariDkResult: KikV2SearchResultData
107 |
108 | class KikV2SearchResponseDk(BaseModel):
109 | """Complete KIK v2 API search response for Düzenleyici (Regulatory) decisions."""
110 | SorgulaKurulKararlariDkResponse: KikV2SearchResultWrapperDk
111 |
112 | # Mahkeme Kararlar (Court Decisions) Response Models
113 | class KikV2SearchResultWrapperMk(BaseModel):
114 | """Wrapper for court decisions search result."""
115 | SorgulaKurulKararlariMkResult: KikV2SearchResultData
116 |
117 | class KikV2SearchResponseMk(BaseModel):
118 | """Complete KIK v2 API search response for Mahkeme (Court) decisions."""
119 | SorgulaKurulKararlariMkResponse: KikV2SearchResultWrapperMk
120 |
121 | # Simplified Models for MCP Tools
122 |
123 | class KikV2CompactDecision(BaseModel):
124 | """Compact decision format for MCP tool responses."""
125 | kararNo: str = Field("", description="Decision number")
126 | kararTarihi: str = Field("", description="Decision date")
127 | basvuran: str = Field("", description="Applicant")
128 | idareAdi: str = Field("", description="Administration")
129 | basvuruKonusu: str = Field("", description="Application subject")
130 | gundemMaddesiId: str = Field("", description="Document ID for retrieval")
131 | decision_type: str = Field("", description="Decision type (uyusmazlik/duzenleyici/mahkeme)")
132 |
133 | class KikV2SearchResult(BaseModel):
134 | """Compact search results for MCP tools."""
135 | decisions: List[KikV2CompactDecision]
136 | total_records: int = Field(0, description="Total number of decisions found")
137 | page: int = Field(1, description="Current page number")
138 | error_code: str = Field("", description="API error code")
139 | error_message: str = Field("", description="API error message")
140 |
141 | class KikV2DocumentMarkdown(BaseModel):
142 | """Document content in Markdown format."""
143 | document_id: str = Field("", description="Document ID")
144 | kararNo: str = Field("", description="Decision number")
145 | markdown_content: str = Field("", description="Decision content in Markdown")
146 | source_url: str = Field("", description="Source URL")
147 | error_message: str = Field("", description="Error message if retrieval failed")
```
--------------------------------------------------------------------------------
/mcp_auth_factory.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | Factory for creating FastMCP app with MCP Auth Toolkit integration
3 | """
4 |
5 | import logging
6 | import os
7 | from typing import Optional
8 |
9 | logger = logging.getLogger(__name__)
10 |
11 | try:
12 | from fastmcp import FastMCP
13 | FASTMCP_AVAILABLE = True
14 | except ImportError:
15 | FASTMCP_AVAILABLE = False
16 | FastMCP = None
17 |
18 | from mcp_auth import (
19 | OAuthProvider,
20 | PolicyEngine,
21 | FastMCPAuthWrapper,
22 | create_default_policies
23 | )
24 | from mcp_auth.clerk_config import create_mcp_server_config
25 |
26 |
27 | def create_auth_enabled_app(app_name: str = "Yargı MCP Server") -> FastMCP:
28 | """Create FastMCP app with authentication enabled"""
29 |
30 | if not FASTMCP_AVAILABLE:
31 | raise ImportError("FastMCP is required for authenticated MCP server")
32 |
33 | logger.info("Creating FastMCP app with MCP Auth Toolkit integration")
34 |
35 | # Create base FastMCP app
36 | app = FastMCP(app_name)
37 |
38 | # Check if authentication is enabled
39 | auth_enabled = os.getenv("ENABLE_AUTH", "true").lower() == "true"
40 |
41 | if not auth_enabled:
42 | logger.info("Authentication disabled, returning basic FastMCP app")
43 | return app
44 |
45 | try:
46 | # Get configuration
47 | logger.info("Getting MCP server configuration...")
48 | config = create_mcp_server_config()
49 | logger.info("Configuration loaded successfully")
50 |
51 | # Create OAuth provider with Clerk config
52 | logger.info("Creating OAuth provider...")
53 | oauth_provider = OAuthProvider(
54 | config=config["oauth_config"],
55 | jwt_secret=config["jwt_secret"]
56 | )
57 | logger.info("OAuth provider created successfully")
58 |
59 | # Create policy engine for Turkish legal database
60 | policy_engine = create_default_policies()
61 |
62 | # Store auth components for later wrapping (after tools are defined)
63 | app._oauth_provider = oauth_provider
64 | app._policy_engine = policy_engine
65 | app._auth_config = config
66 |
67 | # Add OAuth endpoints immediately
68 | @app.tool(
69 | description="Initiate OAuth 2.1 authorization flow with PKCE",
70 | annotations={"readOnlyHint": True, "idempotentHint": False}
71 | )
72 | async def oauth_authorize(redirect_uri: str, scopes: str = None):
73 | """OAuth authorization endpoint"""
74 | scope_list = scopes.split(" ") if scopes else ["mcp:tools:read", "mcp:tools:write"]
75 | auth_url, pkce = oauth_provider.generate_authorization_url(
76 | redirect_uri=redirect_uri, scopes=scope_list
77 | )
78 | logger.info(f"Generated authorization URL for redirect_uri: {redirect_uri}")
79 | return {
80 | "authorization_url": auth_url,
81 | "code_verifier": pkce.verifier,
82 | "code_challenge": pkce.challenge,
83 | "instructions": "Use the authorization_url to complete OAuth flow, then exchange the returned code using oauth_token tool"
84 | }
85 |
86 | @app.tool(
87 | description="Exchange OAuth authorization code for access token",
88 | annotations={"readOnlyHint": False, "idempotentHint": False}
89 | )
90 | async def oauth_token(code: str, state: str, redirect_uri: str):
91 | """OAuth token exchange endpoint"""
92 | try:
93 | result = await oauth_provider.exchange_code_for_token(
94 | code=code, state=state, redirect_uri=redirect_uri
95 | )
96 | logger.info("Successfully exchanged authorization code for token")
97 | return result
98 | except Exception as e:
99 | logger.error(f"Token exchange failed: {e}")
100 | raise
101 |
102 | @app.tool(
103 | description="Validate and introspect OAuth access token",
104 | annotations={"readOnlyHint": True, "idempotentHint": True}
105 | )
106 | async def oauth_introspect(token: str):
107 | """Token introspection endpoint"""
108 | result = oauth_provider.introspect_token(token)
109 | logger.debug(f"Token introspection: active={result.get('active', False)}")
110 | return result
111 |
112 | @app.tool(
113 | description="Revoke OAuth access token",
114 | annotations={"readOnlyHint": False, "idempotentHint": False}
115 | )
116 | async def oauth_revoke(token: str):
117 | """Token revocation endpoint"""
118 | success = oauth_provider.revoke_token(token)
119 | logger.info(f"Token revocation: success={success}")
120 | return {"revoked": success}
121 |
122 | logger.info("Successfully created authenticated FastMCP app")
123 |
124 | except Exception as e:
125 | logger.error(f"Failed to create authenticated app: {e}")
126 | logger.info("Falling back to non-authenticated FastMCP app")
127 | # Return basic app if auth setup fails
128 | return app
129 |
130 | return app
131 |
132 |
133 | def create_app() -> FastMCP:
134 | """Create FastMCP app (backwards compatible with mcp_factory.py)"""
135 | return create_auth_enabled_app()
136 |
137 |
138 | def get_auth_wrapper(app: FastMCP) -> Optional[FastMCPAuthWrapper]:
139 | """Get auth wrapper from app if available"""
140 | return getattr(app, '_auth_wrapper', None)
141 |
142 |
143 | def get_oauth_provider(app: FastMCP) -> Optional[OAuthProvider]:
144 | """Get OAuth provider from app if available"""
145 | return getattr(app, '_oauth_provider', None)
146 |
147 |
148 | def get_policy_engine(app: FastMCP) -> Optional[PolicyEngine]:
149 | """Get policy engine from app if available"""
150 | return getattr(app, '_policy_engine', None)
151 |
152 |
153 | def is_auth_enabled(app: FastMCP) -> bool:
154 | """Check if authentication is enabled for the app"""
155 | return hasattr(app, '_oauth_provider') or hasattr(app, '_auth_wrapper')
156 |
157 |
158 | def enable_tool_authentication(app: FastMCP):
159 | """Enable authentication on all existing tools (call after tools are defined)"""
160 | if not is_auth_enabled(app):
161 | logger.debug("Authentication not enabled, skipping tool authentication")
162 | return
163 |
164 | oauth_provider = get_oauth_provider(app)
165 | policy_engine = get_policy_engine(app)
166 |
167 | if not oauth_provider or not policy_engine:
168 | logger.warning("OAuth provider or policy engine not available")
169 | return
170 |
171 | try:
172 | # Create auth wrapper and wrap tools
173 | auth_wrapper = FastMCPAuthWrapper(
174 | mcp_server=app,
175 | oauth_provider=oauth_provider,
176 | policy_engine=policy_engine
177 | )
178 |
179 | # Store wrapper for reference
180 | app._auth_wrapper = auth_wrapper
181 |
182 | logger.info("Tool authentication enabled successfully")
183 |
184 | except Exception as e:
185 | logger.error(f"Failed to enable tool authentication: {e}")
186 |
187 |
188 | def cleanup_auth_sessions(app: FastMCP):
189 | """Clean up expired auth sessions and tokens"""
190 | oauth_provider = get_oauth_provider(app)
191 | if oauth_provider:
192 | oauth_provider.cleanup_expired_sessions()
193 | logger.debug("Cleaned up expired OAuth sessions")
```
--------------------------------------------------------------------------------
/saidsurucu-yargi-mcp-f5fa007/mcp_auth_factory.py:
--------------------------------------------------------------------------------
```python
1 | """
2 | Factory for creating FastMCP app with MCP Auth Toolkit integration
3 | """
4 |
5 | import logging
6 | import os
7 | from typing import Optional
8 |
9 | logger = logging.getLogger(__name__)
10 |
11 | try:
12 | from fastmcp import FastMCP
13 | FASTMCP_AVAILABLE = True
14 | except ImportError:
15 | FASTMCP_AVAILABLE = False
16 | FastMCP = None
17 |
18 | from mcp_auth import (
19 | OAuthProvider,
20 | PolicyEngine,
21 | FastMCPAuthWrapper,
22 | create_default_policies
23 | )
24 | from mcp_auth.clerk_config import create_mcp_server_config
25 |
26 |
27 | def create_auth_enabled_app(app_name: str = "Yargı MCP Server") -> FastMCP:
28 | """Create FastMCP app with authentication enabled"""
29 |
30 | if not FASTMCP_AVAILABLE:
31 | raise ImportError("FastMCP is required for authenticated MCP server")
32 |
33 | logger.info("Creating FastMCP app with MCP Auth Toolkit integration")
34 |
35 | # Create base FastMCP app
36 | app = FastMCP(app_name)
37 |
38 | # Check if authentication is enabled
39 | auth_enabled = os.getenv("ENABLE_AUTH", "true").lower() == "true"
40 |
41 | if not auth_enabled:
42 | logger.info("Authentication disabled, returning basic FastMCP app")
43 | return app
44 |
45 | try:
46 | # Get configuration
47 | logger.info("Getting MCP server configuration...")
48 | config = create_mcp_server_config()
49 | logger.info("Configuration loaded successfully")
50 |
51 | # Create OAuth provider with Clerk config
52 | logger.info("Creating OAuth provider...")
53 | oauth_provider = OAuthProvider(
54 | config=config["oauth_config"],
55 | jwt_secret=config["jwt_secret"]
56 | )
57 | logger.info("OAuth provider created successfully")
58 |
59 | # Create policy engine for Turkish legal database
60 | policy_engine = create_default_policies()
61 |
62 | # Store auth components for later wrapping (after tools are defined)
63 | app._oauth_provider = oauth_provider
64 | app._policy_engine = policy_engine
65 | app._auth_config = config
66 |
67 | # Add OAuth endpoints immediately
68 | @app.tool(
69 | description="Initiate OAuth 2.1 authorization flow with PKCE",
70 | annotations={"readOnlyHint": True, "idempotentHint": False}
71 | )
72 | async def oauth_authorize(redirect_uri: str, scopes: str = None):
73 | """OAuth authorization endpoint"""
74 | scope_list = scopes.split(" ") if scopes else ["mcp:tools:read", "mcp:tools:write"]
75 | auth_url, pkce = oauth_provider.generate_authorization_url(
76 | redirect_uri=redirect_uri, scopes=scope_list
77 | )
78 | logger.info(f"Generated authorization URL for redirect_uri: {redirect_uri}")
79 | return {
80 | "authorization_url": auth_url,
81 | "code_verifier": pkce.verifier,
82 | "code_challenge": pkce.challenge,
83 | "instructions": "Use the authorization_url to complete OAuth flow, then exchange the returned code using oauth_token tool"
84 | }
85 |
86 | @app.tool(
87 | description="Exchange OAuth authorization code for access token",
88 | annotations={"readOnlyHint": False, "idempotentHint": False}
89 | )
90 | async def oauth_token(code: str, state: str, redirect_uri: str):
91 | """OAuth token exchange endpoint"""
92 | try:
93 | result = await oauth_provider.exchange_code_for_token(
94 | code=code, state=state, redirect_uri=redirect_uri
95 | )
96 | logger.info("Successfully exchanged authorization code for token")
97 | return result
98 | except Exception as e:
99 | logger.error(f"Token exchange failed: {e}")
100 | raise
101 |
102 | @app.tool(
103 | description="Validate and introspect OAuth access token",
104 | annotations={"readOnlyHint": True, "idempotentHint": True}
105 | )
106 | async def oauth_introspect(token: str):
107 | """Token introspection endpoint"""
108 | result = oauth_provider.introspect_token(token)
109 | logger.debug(f"Token introspection: active={result.get('active', False)}")
110 | return result
111 |
112 | @app.tool(
113 | description="Revoke OAuth access token",
114 | annotations={"readOnlyHint": False, "idempotentHint": False}
115 | )
116 | async def oauth_revoke(token: str):
117 | """Token revocation endpoint"""
118 | success = oauth_provider.revoke_token(token)
119 | logger.info(f"Token revocation: success={success}")
120 | return {"revoked": success}
121 |
122 | logger.info("Successfully created authenticated FastMCP app")
123 |
124 | except Exception as e:
125 | logger.error(f"Failed to create authenticated app: {e}")
126 | logger.info("Falling back to non-authenticated FastMCP app")
127 | # Return basic app if auth setup fails
128 | return app
129 |
130 | return app
131 |
132 |
133 | def create_app() -> FastMCP:
134 | """Create FastMCP app (backwards compatible with mcp_factory.py)"""
135 | return create_auth_enabled_app()
136 |
137 |
138 | def get_auth_wrapper(app: FastMCP) -> Optional[FastMCPAuthWrapper]:
139 | """Get auth wrapper from app if available"""
140 | return getattr(app, '_auth_wrapper', None)
141 |
142 |
143 | def get_oauth_provider(app: FastMCP) -> Optional[OAuthProvider]:
144 | """Get OAuth provider from app if available"""
145 | return getattr(app, '_oauth_provider', None)
146 |
147 |
148 | def get_policy_engine(app: FastMCP) -> Optional[PolicyEngine]:
149 | """Get policy engine from app if available"""
150 | return getattr(app, '_policy_engine', None)
151 |
152 |
153 | def is_auth_enabled(app: FastMCP) -> bool:
154 | """Check if authentication is enabled for the app"""
155 | return hasattr(app, '_oauth_provider') or hasattr(app, '_auth_wrapper')
156 |
157 |
158 | def enable_tool_authentication(app: FastMCP):
159 | """Enable authentication on all existing tools (call after tools are defined)"""
160 | if not is_auth_enabled(app):
161 | logger.debug("Authentication not enabled, skipping tool authentication")
162 | return
163 |
164 | oauth_provider = get_oauth_provider(app)
165 | policy_engine = get_policy_engine(app)
166 |
167 | if not oauth_provider or not policy_engine:
168 | logger.warning("OAuth provider or policy engine not available")
169 | return
170 |
171 | try:
172 | # Create auth wrapper and wrap tools
173 | auth_wrapper = FastMCPAuthWrapper(
174 | mcp_server=app,
175 | oauth_provider=oauth_provider,
176 | policy_engine=policy_engine
177 | )
178 |
179 | # Store wrapper for reference
180 | app._auth_wrapper = auth_wrapper
181 |
182 | logger.info("Tool authentication enabled successfully")
183 |
184 | except Exception as e:
185 | logger.error(f"Failed to enable tool authentication: {e}")
186 |
187 |
188 | def cleanup_auth_sessions(app: FastMCP):
189 | """Clean up expired auth sessions and tokens"""
190 | oauth_provider = get_oauth_provider(app)
191 | if oauth_provider:
192 | oauth_provider.cleanup_expired_sessions()
193 | logger.debug("Cleaned up expired OAuth sessions")
```