From e900bc03dda5d88a03b4e136301b7ae11ee202df Mon Sep 17 00:00:00 2001 From: saidsurucu Date: Tue, 22 Jul 2025 12:38:20 +0300 Subject: [PATCH] Fix tools visibility - revert to v0.1.6 authentication approach - Disable issuer validation in BearerAuthProvider (issuer=None) - Simplify authentication condition (remove auth_enabled check) - Revert CORS middleware to simple configuration - Fix OAuth metadata endpoint to match v0.1.6 - Apply conditional auth only to MCP server creation Critical fixes for Claude AI tools discovery --- asgi_app.py | 65 +- mcp_auth_http_simple.py | 12 +- mcp_server_main.py | 2 - saidsurucu-yargi-mcp-f5fa007/.dockerignore | 186 ++ saidsurucu-yargi-mcp-f5fa007/.env.example | 103 + saidsurucu-yargi-mcp-f5fa007/.gitattributes | 2 + .../.github/workflows/publish.yml | 37 + saidsurucu-yargi-mcp-f5fa007/.gitignore | 215 ++ .../5ire-settings.png | Bin 0 -> 74549 bytes saidsurucu-yargi-mcp-f5fa007/Dockerfile | 29 + saidsurucu-yargi-mcp-f5fa007/LICENSE | 21 + saidsurucu-yargi-mcp-f5fa007/Procfile | 1 + saidsurucu-yargi-mcp-f5fa007/README.md | 272 ++ saidsurucu-yargi-mcp-f5fa007/__main__.py | 7 + .../anayasa_mcp_module/__init__.py | 0 .../anayasa_mcp_module/bireysel_client.py | 355 ++ .../anayasa_mcp_module/client.py | 356 ++ .../anayasa_mcp_module/models.py | 230 ++ .../anayasa_mcp_module/unified_client.py | 122 + saidsurucu-yargi-mcp-f5fa007/asgi_app.py | 566 ++++ .../bddk_mcp_module/__init__.py | 17 + .../bddk_mcp_module/client.py | 247 ++ .../bddk_mcp_module/models.py | 43 + .../bedesten_mcp_module/__init__.py | 1 + .../bedesten_mcp_module/client.py | 181 ++ .../bedesten_mcp_module/enums.py | 113 + .../bedesten_mcp_module/models.py | 91 + .../check_response_format.py | 21 + .../danistay_mcp_module/__init__.py | 0 .../danistay_mcp_module/client.py | 192 ++ .../danistay_mcp_module/models.py | 112 + .../docker-compose.yml | 66 + .../docs/DEPLOYMENT.md | 428 +++ .../emsal_mcp_module/__init__.py | 0 .../emsal_mcp_module/client.py | 177 + .../emsal_mcp_module/models.py | 101 + .../example_fastapi_app.py | 1680 ++++++++++ .../kik_mcp_module/__init__.py | 0 .../kik_mcp_module/client.py | 1199 +++++++ .../kik_mcp_module/models.py | 74 + .../kvkk_mcp_module/__init__.py | 1 + .../kvkk_mcp_module/client.py | 372 +++ .../kvkk_mcp_module/models.py | 49 + .../mcp_auth/__init__.py | 28 + .../mcp_auth/clerk_config.py | 73 + .../mcp_auth/middleware.py | 315 ++ .../mcp_auth/oauth.py | 304 ++ .../mcp_auth/policy.py | 201 ++ .../mcp_auth/storage.py | 112 + .../mcp_auth_factory.py | 193 ++ .../mcp_auth_http_adapter.py | 383 +++ .../mcp_auth_http_simple.py | 522 +++ .../mcp_server_main.py | 2883 +++++++++++++++++ saidsurucu-yargi-mcp-f5fa007/nginx.conf | 94 + saidsurucu-yargi-mcp-f5fa007/ornek.png | Bin 0 -> 545626 bytes saidsurucu-yargi-mcp-f5fa007/pyproject.toml | 66 + saidsurucu-yargi-mcp-f5fa007/railway.json | 18 + .../redis_session_store.py | 464 +++ .../rekabet_mcp_module/__init__.py | 0 .../rekabet_mcp_module/client.py | 407 +++ .../rekabet_mcp_module/models.py | 71 + saidsurucu-yargi-mcp-f5fa007/run_asgi.py | 119 + .../sayistay_mcp_module/__init__.py | 56 + .../sayistay_mcp_module/client.py | 689 ++++ .../sayistay_mcp_module/enums.py | 61 + .../sayistay_mcp_module/models.py | 220 ++ .../sayistay_mcp_module/unified_client.py | 133 + saidsurucu-yargi-mcp-f5fa007/starlette_app.py | 159 + .../stripe_webhook.py | 25 + .../uyusmazlik_mcp_module/__init__.py | 0 .../uyusmazlik_mcp_module/client.py | 240 ++ .../uyusmazlik_mcp_module/models.py | 86 + .../yargitay_mcp_module/__init__.py | 0 .../yargitay_mcp_module/client.py | 182 ++ .../yargitay_mcp_module/models.py | 103 + 75 files changed, 15891 insertions(+), 62 deletions(-) create mode 100644 saidsurucu-yargi-mcp-f5fa007/.dockerignore create mode 100644 saidsurucu-yargi-mcp-f5fa007/.env.example create mode 100644 saidsurucu-yargi-mcp-f5fa007/.gitattributes create mode 100644 saidsurucu-yargi-mcp-f5fa007/.github/workflows/publish.yml create mode 100644 saidsurucu-yargi-mcp-f5fa007/.gitignore create mode 100644 saidsurucu-yargi-mcp-f5fa007/5ire-settings.png create mode 100644 saidsurucu-yargi-mcp-f5fa007/Dockerfile create mode 100644 saidsurucu-yargi-mcp-f5fa007/LICENSE create mode 100644 saidsurucu-yargi-mcp-f5fa007/Procfile create mode 100644 saidsurucu-yargi-mcp-f5fa007/README.md create mode 100644 saidsurucu-yargi-mcp-f5fa007/__main__.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/__init__.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/bireysel_client.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/client.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/models.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/unified_client.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/asgi_app.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/bddk_mcp_module/__init__.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/bddk_mcp_module/client.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/bddk_mcp_module/models.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/__init__.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/client.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/enums.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/models.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/check_response_format.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/danistay_mcp_module/__init__.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/danistay_mcp_module/client.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/danistay_mcp_module/models.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/docker-compose.yml create mode 100644 saidsurucu-yargi-mcp-f5fa007/docs/DEPLOYMENT.md create mode 100644 saidsurucu-yargi-mcp-f5fa007/emsal_mcp_module/__init__.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/emsal_mcp_module/client.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/emsal_mcp_module/models.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/example_fastapi_app.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/kik_mcp_module/__init__.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/kik_mcp_module/client.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/kik_mcp_module/models.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/kvkk_mcp_module/__init__.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/kvkk_mcp_module/client.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/kvkk_mcp_module/models.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/mcp_auth/__init__.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/mcp_auth/clerk_config.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/mcp_auth/middleware.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/mcp_auth/oauth.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/mcp_auth/policy.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/mcp_auth/storage.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/mcp_auth_factory.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/mcp_auth_http_adapter.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/mcp_auth_http_simple.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/mcp_server_main.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/nginx.conf create mode 100644 saidsurucu-yargi-mcp-f5fa007/ornek.png create mode 100644 saidsurucu-yargi-mcp-f5fa007/pyproject.toml create mode 100644 saidsurucu-yargi-mcp-f5fa007/railway.json create mode 100644 saidsurucu-yargi-mcp-f5fa007/redis_session_store.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/rekabet_mcp_module/__init__.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/rekabet_mcp_module/client.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/rekabet_mcp_module/models.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/run_asgi.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/sayistay_mcp_module/__init__.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/sayistay_mcp_module/client.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/sayistay_mcp_module/enums.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/sayistay_mcp_module/models.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/sayistay_mcp_module/unified_client.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/starlette_app.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/stripe_webhook.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/uyusmazlik_mcp_module/__init__.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/uyusmazlik_mcp_module/client.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/uyusmazlik_mcp_module/models.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/yargitay_mcp_module/__init__.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/yargitay_mcp_module/client.py create mode 100644 saidsurucu-yargi-mcp-f5fa007/yargitay_mcp_module/models.py diff --git a/asgi_app.py b/asgi_app.py index ecbb113..f1f91bc 100755 --- a/asgi_app.py +++ b/asgi_app.py @@ -58,21 +58,19 @@ except ImportError: auth_enabled = os.getenv("ENABLE_AUTH", "false").lower() == "true" bearer_auth = None -if auth_enabled and CLERK_SECRET_KEY and CLERK_ISSUER: +if CLERK_SECRET_KEY and CLERK_ISSUER: # Production: Use Clerk JWKS endpoint for token validation - # JWT token shows issuer as "https://clerk.yargimcp.com", so use that for JWKS - jwt_issuer = "https://clerk.yargimcp.com" bearer_auth = BearerAuthProvider( - jwks_uri=f"{jwt_issuer}/.well-known/jwks.json", - issuer=jwt_issuer, # Enable issuer validation with correct issuer + jwks_uri=f"{CLERK_ISSUER}/.well-known/jwks.json", + issuer=None, # Disable issuer validation - Clerk uses different issuer format algorithm="RS256", - audience=None, # Disable audience validation - Clerk tokens vary - required_scopes=[] # Disable scope validation - rely on token presence + audience=None, # Disable audience validation - Clerk uses different audience format + required_scopes=[] # Disable scope validation - Clerk JWT has ['read', 'search'] ) - logger.info(f"Bearer auth configured with Clerk JWKS: {jwt_issuer}/.well-known/jwks.json (issuer validation enabled)") -elif auth_enabled: - # Development: Generate RSA key pair for testing when auth is enabled but no Clerk - logger.warning("Authentication enabled but no Clerk credentials - using development RSA key pair") + logger.info(f"Bearer auth configured with Clerk JWKS: {CLERK_ISSUER}/.well-known/jwks.json") +else: + # Development: Generate RSA key pair for testing + logger.warning("No Clerk credentials found - using development RSA key pair") dev_key_pair = RSAKeyPair.generate() bearer_auth = BearerAuthProvider( public_key=dev_key_pair.public_key, @@ -89,13 +87,10 @@ elif auth_enabled: scopes=["yargi.read", "yargi.search"], expires_in_seconds=3600 * 24 # 24 hours for development ) - logger.debug("Development Bearer token generated (masked for security)") # Don't log actual token -else: - # Authentication disabled - allow unauthenticated access - logger.info("Authentication disabled - MCP server will allow unauthenticated access") + logger.info(f"Development Bearer token: {dev_token}") # Create MCP app with Bearer authentication -mcp_server = create_app(auth=bearer_auth) +mcp_server = create_app(auth=bearer_auth if auth_enabled else None) # Create MCP Starlette sub-application with root path - mount will add /mcp prefix mcp_app = mcp_server.http_app(path="/") @@ -126,41 +121,13 @@ class UTF8JSONResponse(JSONResponse): separators=(",", ":"), ).encode("utf-8") -# CORS middleware configuration - Allow Claude AI and Clerk domains -cors_allowed_origins = ["*"] - custom_middleware = [ Middleware( CORSMiddleware, - allow_origins=cors_allowed_origins, - allow_credentials=True, # Enable credentials for cross-origin requests - allow_methods=["GET", "POST", "HEAD", "PUT", "DELETE", "OPTIONS", "PATCH"], - allow_headers=[ - "Content-Type", - "Authorization", - "X-Request-ID", - "X-Session-ID", - "MCP-Protocol-Version", - "Mcp-Session-Id", - "x-api-key", # Added from your config - "Last-Event-ID", # Added from your config for SSE support - "Accept", - "Origin", - "User-Agent", - "DNT", - "Cache-Control", - "X-Mx-ReqToken", - "Keep-Alive", - "X-Requested-With", - "If-Modified-Since" - ], - expose_headers=[ - "Content-Type", # Added from your config - "Authorization", - "x-api-key", # Added from your config - "Mcp-Session-Id" - ], - max_age=86400, # Added from your config (24 hours) + allow_origins=cors_origins, + allow_credentials=True, + allow_methods=["GET", "POST", "OPTIONS", "DELETE"], + allow_headers=["Content-Type", "Authorization", "X-Request-ID", "X-Session-ID"], ), ] @@ -637,7 +604,7 @@ async def mcp_token_endpoint(request: Request): content={"error": "invalid_request", "error_description": e.detail} ) -# Mount MCP app at /mcp/ with trailing slash (v0.1.6 approach) +# Mount MCP app at /mcp/ with trailing slash app.mount("/mcp/", mcp_app) # Set the lifespan context after mounting diff --git a/mcp_auth_http_simple.py b/mcp_auth_http_simple.py index fd6d2d8..2b6bbfd 100644 --- a/mcp_auth_http_simple.py +++ b/mcp_auth_http_simple.py @@ -72,24 +72,14 @@ async def get_oauth_metadata(): return JSONResponse({ "issuer": BASE_URL, "authorization_endpoint": f"{BASE_URL}/auth/login", - "authorization_endpoint_simple": f"{BASE_URL}/auth/login-simple", # Simple request endpoint "token_endpoint": f"{BASE_URL}/token", - "token_endpoint_simple": f"{BASE_URL}/token-simple", # Simple request endpoint "registration_endpoint": f"{BASE_URL}/register", "response_types_supported": ["code"], "grant_types_supported": ["authorization_code"], "code_challenge_methods_supported": ["S256"], "token_endpoint_auth_methods_supported": ["none"], "scopes_supported": ["read", "search", "openid", "profile", "email"], - "service_documentation": f"{BASE_URL}/mcp/", - "preflight_free_endpoints": { - "authorization": f"{BASE_URL}/auth/login-simple", - "token": f"{BASE_URL}/token-simple" - }, - "clerk_optimization": { - "simple_requests": True, - "cors_preflight_bypass": True, - "performance_optimized": True + "service_documentation": f"{BASE_URL}/mcp/" } }) diff --git a/mcp_server_main.py b/mcp_server_main.py index 3dd3a58..ccb3c77 100644 --- a/mcp_server_main.py +++ b/mcp_server_main.py @@ -243,10 +243,8 @@ def create_app(auth=None): global app if auth: app.auth = auth - app.name = "Yargı MCP Server" logger.info("MCP server created with Bearer authentication enabled") else: - app.name = "Yargı MCP Server" logger.info("MCP server created with standard capabilities...") token_counter = TokenCountingMiddleware() diff --git a/saidsurucu-yargi-mcp-f5fa007/.dockerignore b/saidsurucu-yargi-mcp-f5fa007/.dockerignore new file mode 100644 index 0000000..e9f20bb --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/.dockerignore @@ -0,0 +1,186 @@ +# flyctl launch added from .gitignore +# Byte-compiled / optimized / DLL files +**/__pycache__ +**/*.py[cod] +**/*$py.class + +# C extensions +**/*.so + +# Distribution / packaging +**/.Python +**/build +**/develop-eggs +**/dist +**/downloads +**/eggs +**/.eggs +**/lib +**/lib64 +**/parts +**/sdist +**/var +**/wheels +**/share/python-wheels +**/*.egg-info +**/.installed.cfg +**/*.egg +**/MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +**/*.manifest +**/*.spec + +# Installer logs +**/pip-log.txt +**/pip-delete-this-directory.txt + +# Unit test / coverage reports +**/htmlcov +**/.tox +**/.nox +**/.coverage +**/.coverage.* +**/.cache +**/nosetests.xml +**/coverage.xml +**/*.cover +**/*.py,cover +**/.hypothesis +**/.pytest_cache +**/cover + +# Translations +**/*.mo +**/*.pot + +# Django stuff: +**/*.log +**/local_settings.py +**/db.sqlite3 +**/db.sqlite3-journal + +# Flask stuff: +**/instance +**/.webassets-cache + +# Scrapy stuff: +**/.scrapy + +# Sphinx documentation +**/docs/_build + +# PyBuilder +**/.pybuilder +**/target + +# Jupyter Notebook +**/.ipynb_checkpoints + +# IPython +**/profile_default +**/ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +**/.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +**/__pypackages__ + +# Celery stuff +**/celerybeat-schedule +**/celerybeat.pid + +# SageMath parsed files +**/*.sage.py + +# Environments +**/.env +**/.venv +**/env +**/venv +**/ENV +**/env.bak +**/venv.bak + +# Spyder project settings +**/.spyderproject +**/.spyproject + +# Rope project settings +**/.ropeproject + +# mkdocs documentation +site + +# mypy +**/.mypy_cache +**/.dmypy.json +**/dmypy.json + +# Pyre type checker +**/.pyre + +# pytype static type analyzer +**/.pytype + +# Cython debug symbols +**/cython_debug + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ +**/.DS_Store +**/hello.py + +**/*.html +**/fast-mcp-docs.md + +# Debug and test files +**/debug_* +**/test_* +**/CLAUDE.md + +# ASGI/Deployment files +**/ssl +**/*.pem +**/*.key +**/*.crt + +# Docker volumes +**/redis-data + +# Production logs +**/logs/*.log.* +**/Dockerfile +**/Dockerfile +fly.toml diff --git a/saidsurucu-yargi-mcp-f5fa007/.env.example b/saidsurucu-yargi-mcp-f5fa007/.env.example new file mode 100644 index 0000000..270a0fb --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/.env.example @@ -0,0 +1,103 @@ +# OAuth Configuration for Clerk + Google +# Copy this file to .env and fill in your actual values + +# ============================================================================= +# AUTHENTICATION SETTINGS +# ============================================================================= + +# Enable/disable authentication (set to "true" to enable OAuth) +ENABLE_AUTH=false + +# ============================================================================= +# CLERK CONFIGURATION +# ============================================================================= + +# Clerk API keys (get from https://dashboard.clerk.com/) +CLERK_SECRET_KEY=sk_test_your_secret_key_here +CLERK_PUBLISHABLE_KEY=pk_test_your_publishable_key_here + +# OAuth Redirect URLs +CLERK_OAUTH_REDIRECT_URL=http://localhost:8000/auth/callback +CLERK_FRONTEND_URL=http://localhost:3000 + +# Clerk domain issuer (usually auto-configured) +CLERK_ISSUER=https://your-clerk-domain.clerk.accounts.dev +CLERK_DOMAIN=your-clerk-domain + +# ============================================================================= +# GOOGLE OAUTH SETTINGS +# ============================================================================= +# Note: Google OAuth is configured through Clerk dashboard +# You need to: +# 1. Go to Clerk Dashboard > Social Connections +# 2. Enable Google provider +# 3. Add your Google OAuth client ID and secret +# 4. Configure redirect URIs in Google Console + +# ============================================================================= +# STRIPE CONFIGURATION (for payments/subscriptions) +# ============================================================================= + +STRIPE_SECRET=sk_test_your_stripe_secret_key_here +STRIPE_WEBHOOK_SECRET=whsec_your_webhook_secret_here + +# ============================================================================= +# SERVER CONFIGURATION +# ============================================================================= + +# CORS origins (comma-separated list) +ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8000,https://yourdomain.com + +# Server settings +HOST=0.0.0.0 +PORT=8000 +LOG_LEVEL=info + +# Base URL for the application (used for OAuth callbacks and API URLs) +BASE_URL=http://localhost:8000 + +# JWT Secret for MCP token generation +JWT_SECRET_KEY=your_jwt_secret_key_here + +# ============================================================================= +# MCP SERVER SETTINGS +# ============================================================================= + +# Additional MCP server configuration can go here +# For example, rate limiting, feature flags, etc. + +# Example: Rate limiting +# MAX_REQUESTS_PER_MINUTE=60 +# BURST_CAPACITY=20 + +# ============================================================================= +# USAGE INSTRUCTIONS +# ============================================================================= + +# 1. Copy this file to .env: +# cp .env.example .env + +# 2. Get Clerk credentials: +# - Sign up at https://clerk.com/ +# - Create a new application +# - Go to API Keys tab +# - Copy Secret Key and Publishable Key + +# 3. Configure Google OAuth in Clerk: +# - In Clerk Dashboard, go to Social Connections +# - Enable Google provider +# - Get Google OAuth credentials from Google Console +# - Add redirect URI: http://localhost:8000/auth/callback + +# 4. Update OAuth URLs: +# - Set CLERK_OAUTH_REDIRECT_URL to your callback URL +# - Set CLERK_FRONTEND_URL to your frontend application URL + +# 5. Enable authentication: +# - Set ENABLE_AUTH=true + +# 6. Test the OAuth flow: +# - Start server: uvicorn asgi_app:app --reload +# - Visit: http://localhost:8000/auth/login +# - Complete OAuth flow with Google +# - Check: http://localhost:8000/auth/user \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/.gitattributes b/saidsurucu-yargi-mcp-f5fa007/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/saidsurucu-yargi-mcp-f5fa007/.github/workflows/publish.yml b/saidsurucu-yargi-mcp-f5fa007/.github/workflows/publish.yml new file mode 100644 index 0000000..b1924b3 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/.github/workflows/publish.yml @@ -0,0 +1,37 @@ +name: Publish to PyPI + +on: + release: + types: [published] + workflow_dispatch: # Manual trigger for testing + +jobs: + pypi-publish: + name: Upload release to PyPI + runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/yargi-mcp + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + + - name: Build package + run: python -m build + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} + skip-existing: true \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/.gitignore b/saidsurucu-yargi-mcp-f5fa007/.gitignore new file mode 100644 index 0000000..7f3ac3d --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/.gitignore @@ -0,0 +1,215 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ +.DS_Store +hello.py + +*.html +fast-mcp-docs.md + +# Debug and test files +debug_* +test_* +CLAUDE.md + +# ASGI/Deployment files +ssl/ +*.pem +*.key +*.crt + +# Docker volumes +redis-data/ + +# Production logs +logs/*.log.* + +# Remove these lines - we need deployment files in git: +# Dockerfile - NEEDED for SaaS deployment +# fly.toml - NEEDED for Fly.io deployment +# .github/workflows/fly-deploy.yml - NEEDED for GitHub Actions + +GEMINI.md +fly.toml +scripts/deploy-flyio.sh +docs/DEPLOYMENT_FLYIO.md +setup_jwt_template.py +mcp_server_main.py.backup +mcp_overhead_content.json +ANTHROPIC_TEST_README.md +extract_mcp_overhead.py +mcp_overhead_content.txt +mcp_overhead_summary.txt +run_http_server.py +run_local_test.py + +# MCP overhead analysis files +mcp_overhead_*.json +mcp_overhead_*.txt +mcp_test_results_*.json +mcp_quick_test_*.json + +# General text files (temporary notes, etc) +*.txt +analyze_playwright_mcp.py +measure_mcp_directly.py +playwright_mcp_overhead.json +simple_test.py +analyze_anayasa_html.py diff --git a/saidsurucu-yargi-mcp-f5fa007/5ire-settings.png b/saidsurucu-yargi-mcp-f5fa007/5ire-settings.png new file mode 100644 index 0000000000000000000000000000000000000000..0fa460b90b0eb3efbb24ec9d43a136a30bd3ed03 GIT binary patch literal 74549 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 TWAh|38Gyjk)z4*}Q$iB}VX>!G literal 0 HcmV?d00001 diff --git a/saidsurucu-yargi-mcp-f5fa007/Dockerfile b/saidsurucu-yargi-mcp-f5fa007/Dockerfile new file mode 100644 index 0000000..111f909 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/Dockerfile @@ -0,0 +1,29 @@ +# -------- BASE IMAGE (includes Chromium & deps) ---------------------------- +FROM mcr.microsoft.com/playwright/python:v1.53.0-noble + +# -------- Runtime setup ---------------------------------------------------- +WORKDIR /app + +# Copy dependency manifests first for layer-cache +COPY pyproject.toml poetry.lock* requirements*.txt* ./ + +# Fast, deterministic install with `uv` +RUN pip install --no-cache-dir uv && \ + uv pip install --system --no-cache-dir .[asgi,saas] + +# Copy application source +COPY . . + +# -------- Environment ------------------------------------------------------ +ENV PYTHONUNBUFFERED=1 +ENV ENABLE_AUTH=true +ENV PORT=8000 + +# -------- Health check ----------------------------------------------------- +HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \ + CMD python -c "import httpx, os, sys; r=httpx.get(f'http://localhost:{os.getenv(\"PORT\",\"8000\")}/health'); sys.exit(0 if r.status_code==200 else 1)" + +EXPOSE 8000 + +# -------- Entrypoint ------------------------------------------------------- +CMD ["uvicorn", "asgi_app:app", "--host", "0.0.0.0", "--port", "8000", "--proxy-headers"] \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/LICENSE b/saidsurucu-yargi-mcp-f5fa007/LICENSE new file mode 100644 index 0000000..cdf2fd2 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 saidsurucu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/saidsurucu-yargi-mcp-f5fa007/Procfile b/saidsurucu-yargi-mcp-f5fa007/Procfile new file mode 100644 index 0000000..2b6b125 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/Procfile @@ -0,0 +1 @@ +web: uvicorn asgi_app:app --host 0.0.0.0 --port $PORT \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/README.md b/saidsurucu-yargi-mcp-f5fa007/README.md new file mode 100644 index 0000000..570305d --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/README.md @@ -0,0 +1,272 @@ +# Yargı MCP: Türk Hukuk Kaynakları için MCP Sunucusu + +[![Star History Chart](https://api.star-history.com/svg?repos=saidsurucu/yargi-mcp&type=Date)](https://www.star-history.com/#saidsurucu/yargi-mcp&Date) + +Bu proje, çeşitli Türk hukuk kaynaklarına (Yargıtay, Danıştay, Emsal Kararlar, Uyuşmazlık Mahkemesi, Anayasa Mahkemesi - Norm Denetimi ile Bireysel Başvuru Kararları, Kamu İhale Kurulu Kararları, Rekabet Kurumu Kararları, Sayıştay Kararları, KVKK Kararları ve BDDK Kararları) erişimi kolaylaştıran bir [FastMCP](https://gofastmcp.com/) sunucusu oluşturur. Bu sayede, bu kaynaklardan veri arama ve belge getirme işlemleri, Model Context Protocol (MCP) destekleyen LLM (Büyük Dil Modeli) uygulamaları (örneğin Claude Desktop veya [5ire](https://5ire.app)) ve diğer istemciler tarafından araç (tool) olarak kullanılabilir hale gelir. + +![örnek](./ornek.png) + +🎯 **Temel Özellikler** + +🚀 **YÜKSEK PERFORMANS OPTİMİZASYONU:** Bu MCP sunucusu **%61.8 token azaltma** ile optimize edilmiştir (8,692 token tasarrufu). Claude AI ile daha hızlı yanıt süreleri ve daha verimli etkileşim sağlar. + +* Çeşitli Türk hukuk veritabanlarına programatik erişim için standart bir MCP arayüzü. +* **Kapsamlı Mahkeme Daire/Kurul Filtreleme:** 79 farklı daire/kurul filtreleme seçeneği +* **Dual/Triple API Desteği:** Her mahkeme için birden fazla API kaynağı ile maksimum kapsama +* **Kapsamlı Tarih Filtreleme:** Tüm Bedesten API araçlarında ISO 8601 formatında tarih aralığı filtreleme +* **Kesin Cümle Arama:** Tüm Bedesten API araçlarında çift tırnak ile tam cümle arama desteği +* Aşağıdaki kurumların kararlarını arama ve getirme yeteneği: + * **Yargıtay:** Detaylı kriterlerle karar arama ve karar metinlerini Markdown formatında getirme. **Dual API** (Ana + Bedesten) + **52 Daire/Kurul Filtreleme** + **Tarih & Kesin Cümle Arama** (Hukuk/Ceza Daireleri, Genel Kurullar) + * **Danıştay:** Anahtar kelime bazlı ve detaylı kriterlerle karar arama; karar metinlerini Markdown formatında getirme. **Triple API** (Keyword + Detailed + Bedesten) + **27 Daire/Kurul Filtreleme** + **Tarih & Kesin Cümle Arama** (İdari Daireler, Vergi/İdare Kurulları, Askeri Yüksek İdare Mahkemesi) + * **Yerel Hukuk Mahkemeleri:** Bedesten API ile yerel hukuk mahkemesi kararlarına erişim + **Tarih & Kesin Cümle Arama** + * **İstinaf Hukuk Mahkemeleri:** Bedesten API ile istinaf mahkemesi kararlarına erişim + **Tarih & Kesin Cümle Arama** + * **Kanun Yararına Bozma (KYB):** Bedesten API ile olağanüstü kanun yoluna erişim + **Tarih & Kesin Cümle Arama** + * **Emsal (UYAP):** Detaylı kriterlerle emsal karar arama ve karar metinlerini Markdown formatında getirme. + * **Uyuşmazlık Mahkemesi:** Form tabanlı kriterlerle karar arama ve karar metinlerini (URL ile erişilen) Markdown formatında getirme. + * **Anayasa Mahkemesi (Norm Denetimi):** Kapsamlı kriterlerle norm denetimi kararlarını arama; uzun karar metinlerini (5.000 karakterlik) sayfalanmış Markdown formatında getirme. + * **Anayasa Mahkemesi (Bireysel Başvuru):** Kapsamlı kriterlerle bireysel başvuru "Karar Arama Raporu" oluşturma ve listedeki kararların metinlerini (5.000 karakterlik) sayfalanmış Markdown formatında getirme. + * **KİK (Kamu İhale Kurulu):** Çeşitli kriterlerle Kurul kararlarını arama; uzun karar metinlerini (varsayılan 5.000 karakterlik) sayfalanmış Markdown formatında getirme. + * **Rekabet Kurumu:** Çeşitli kriterlerle Kurul kararlarını arama; karar metinlerini Markdown formatında getirme. + * **Sayıştay:** 3 karar türü ile kapsamlı denetim kararlarına erişim + **8 Daire Filtreleme** + **Tarih Aralığı & İçerik Arama** (Genel Kurul yorumlayıcı kararları, Temyiz Kurulu itiraz kararları, Daire ilk derece denetim kararları) + * **KVKK (Kişisel Verilerin Korunması Kurulu):** Brave Search API ile veri koruma kararlarını arama; uzun karar metinlerini (5.000 karakterlik) sayfalanmış Markdown formatında getirme + **Türkçe Arama** + **Site Hedeflemeli Arama** (kvkk.gov.tr kararları) + * **BDDK (Bankacılık Düzenleme ve Denetleme Kurumu):** Bankacılık düzenleme kararlarını arama; karar metinlerini Markdown formatında getirme + **Optimized Search** + **"Karar Sayısı" Targeting** + **Spesifik URL Filtreleme** (bddk.org.tr/Mevzuat/DokumanGetir) + +* Karar metinlerinin daha kolay işlenebilmesi için Markdown formatına çevrilmesi. +* Claude Desktop uygulaması ile `fastmcp install` komutu kullanılarak kolay entegrasyon. +* Yargı MCP artık [5ire](https://5ire.app) gibi Claude Desktop haricindeki MCP istemcilerini de destekliyor! +--- +
+🚀 Claude Haricindeki Modellerle Kullanmak İçin Çok Kolay Kurulum (Örnek: 5ire için) + +Bu bölüm, Yargı MCP aracını 5ire gibi Claude Desktop dışındaki MCP istemcileriyle kullanmak isteyenler içindir. + +* **Python Kurulumu:** Sisteminizde Python 3.11 veya üzeri kurulu olmalıdır. Kurulum sırasında "**Add Python to PATH**" (Python'ı PATH'e ekle) seçeneğini işaretlemeyi unutmayın. [Buradan](https://www.python.org/downloads/) indirebilirsiniz. +* **Git Kurulumu (Windows):** Bilgisayarınıza [git](https://git-scm.com/downloads/win) yazılımını indirip kurun. "Git for Windows/x64 Setup" seçeneğini indirmelisiniz. +* **`uv` Kurulumu:** + * **Windows Kullanıcıları (PowerShell):** Bir CMD ekranı açın ve bu kodu çalıştırın: `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"` + * **Mac/Linux Kullanıcıları (Terminal):** Bir Terminal ekranı açın ve bu kodu çalıştırın: `curl -LsSf https://astral.sh/uv/install.sh | sh` +* **Microsoft Visual C++ Redistributable (Windows):** Bazı Python paketlerinin doğru çalışması için gereklidir. [Buradan](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170) indirip kurun. +* İşletim sisteminize uygun [5ire](https://5ire.app) MCP istemcisini indirip kurun. +* 5ire'ı açın. **Workspace -> Providers** menüsünden kullanmak istediğiniz LLM servisinin API anahtarını girin. +* **Tools** menüsüne girin. **+Local** veya **New** yazan butona basın. + * **Tool Key:** `yargimcp` + * **Name:** `Yargı MCP` + * **Command:** + ``` + uvx yargi-mcp + ``` + * **Save** butonuna basarak kaydedin. +![5ire ayarları](./5ire-settings.png) +* Şimdi **Tools** altında **Yargı MCP**'yi görüyor olmalısınız. Üstüne geldiğinizde sağda çıkan butona tıklayıp etkinleştirin (yeşil ışık yanmalı). +* Artık Yargı MCP ile konuşabilirsiniz. + +
+ +--- +
+⚙️ Claude Desktop Manuel Kurulumu + +1. **Ön Gereksinimler:** Python, `uv`, (Windows için) Microsoft Visual C++ Redistributable'ın sisteminizde kurulu olduğundan emin olun. Detaylı bilgi için yukarıdaki "5ire için Kurulum" bölümündeki ilgili adımlara bakabilirsiniz. +2. Claude Desktop **Settings -> Developer -> Edit Config**. +3. Açılan `claude_desktop_config.json` dosyasına `mcpServers` altına ekleyin: + + ```json + { + "mcpServers": { + // ... (varsa diğer sunucularınız) ... + "Yargı MCP": { + "command": "uvx", + "args": [ + "yargi-mcp" + ] + } + } + } + ``` +4. Claude Desktop'ı kapatıp yeniden başlatın. + +
+ +--- +
+🌟 Gemini CLI ile Kullanım + +Yargı MCP'yi Gemini CLI ile kullanmak için: + +1. **Ön Gereksinimler:** Python, `uv`, (Windows için) Microsoft Visual C++ Redistributable'ın sisteminizde kurulu olduğundan emin olun. Detaylı bilgi için yukarıdaki "5ire için Kurulum" bölümündeki ilgili adımlara bakabilirsiniz. + +2. **Gemini CLI ayarlarını yapılandırın:** + + Gemini CLI'ın ayar dosyasını düzenleyin: + - **macOS/Linux:** `~/.gemini/settings.json` + - **Windows:** `%USERPROFILE%\.gemini\settings.json` + + Aşağıdaki `mcpServers` bloğunu ekleyin: + ```json + { + "theme": "Default", + "selectedAuthType": "###", + "mcpServers": { + "yargi_mcp": { + "command": "uvx", + "args": [ + "yargi-mcp" + ] + } + } + } + ``` + + **Yapılandırma açıklamaları:** + - `"yargi_mcp"`: Sunucunuz için yerel bir isim + - `"command"`: `uvx` komutu (uv'nin paket çalıştırma aracı) + - `"args"`: GitHub'dan doğrudan Yargı MCP'yi çalıştırmak için gerekli argümanlar + +3. **Kullanım:** + - Gemini CLI'ı başlatın + - Yargı MCP araçları otomatik olarak kullanılabilir olacaktır + - Örnek komutlar: + - "Yargıtay'ın mülkiyet hakkı ile ilgili son kararlarını ara" + - "Danıştay'ın imar planı iptaline ilişkin kararlarını bul" + - "Anayasa Mahkemesi'nin ifade özgürlüğü kararlarını getir" + +
+ +
+🛠️ Kullanılabilir Araçlar (MCP Tools) + +Bu FastMCP sunucusu **19 optimize edilmiş MCP aracı** sunar (token verimliliği için optimize edilmiş): + +### **Yargıtay Araçları (Birleşik Bedesten API - Token Optimized)** +*Not: Yargıtay araçları token verimliliği için birleşik Bedesten API'ye entegre edilmiştir* + +### **Danıştay Araçları (Birleşik Bedesten API - Token Optimized)** +*Not: Danıştay araçları token verimliliği için birleşik Bedesten API'ye entegre edilmiştir* + +### **Birleşik Bedesten API Araçları (5 Mahkeme) - 🚀 TOKEN OPTİMİZE** +1. `search_bedesten_unified(phrase, court_types, birimAdi, kararTarihiStart, kararTarihiEnd, ...)`: **5 mahkeme türünü** birleşik arama (Yargıtay, Danıştay, Yerel Hukuk, İstinaf Hukuk, KYB) + **79 daire filtreleme** + **Tarih & Kesin Cümle Arama** +2. `get_bedesten_document_markdown(documentId: str)`: Bedesten API'den herhangi bir belgeyi Markdown formatında getirir (HTML/PDF → Markdown) + +### **Emsal Karar Araçları (UYAP)** +3. `search_emsal_detailed_decisions(keyword, ...)`: Emsal (UYAP) kararlarını detaylı kriterlerle arar. +4. `get_emsal_document_markdown(id: str)`: Belirli bir Emsal kararının metnini Markdown formatında getirir. + +### **Uyuşmazlık Mahkemesi Araçları** +5. `search_uyusmazlik_decisions(icerik, ...)`: Uyuşmazlık Mahkemesi kararlarını çeşitli form kriterleriyle arar. +6. `get_uyusmazlik_document_markdown_from_url(document_url)`: Bir Uyuşmazlık kararını tam URL'sinden alıp Markdown formatında getirir. + +### **Anayasa Mahkemesi Araçları (Birleşik API) - 🚀 TOKEN OPTİMİZE** +7. `search_anayasa_unified(decision_type, keywords_all, ...)`: AYM kararlarını birleşik arama (Norm Denetimi + Bireysel Başvuru) - **4 araç → 2 araç optimizasyonu** +8. `get_anayasa_document_unified(document_url, page_number)`: AYM kararlarını birleşik belge getirme - **sayfalanmış Markdown** içeriği + +### **KİK (Kamu İhale Kurulu) Araçları** +9. `search_kik_decisions(karar_tipi, ...)`: KİK (Kamu İhale Kurulu) kararlarını arar. +10. `get_kik_document_markdown(karar_id, page_number)`: Belirli bir KİK kararını, Base64 ile encode edilmiş `karar_id`'sini kullanarak alır ve **sayfalanmış Markdown** içeriğini getirir. +### **Rekabet Kurumu Araçları** +    * `search_rekabet_kurumu_decisions(KararTuru: Literal[...], ...) -> RekabetSearchResult`: Rekabet Kurumu kararlarını arar. `KararTuru` için kullanıcı dostu isimler kullanılır (örn: "Birleşme ve Devralma"). +    * `get_rekabet_kurumu_document(karar_id: str, page_number: Optional[int] = 1) -> RekabetDocument`: Belirli bir Rekabet Kurumu kararını `karar_id` ile alır. Kararın PDF formatındaki orijinalinden istenen sayfayı ayıklar ve Markdown formatında döndürür. + + +--- + +* **Sayıştay Araçları (3 Karar Türü + 8 Daire Filtreleme):** + * `search_sayistay_genel_kurul(karar_no, karar_tarih_baslangic, karar_tamami, ...)`: Sayıştay Genel Kurul (yorumlayıcı) kararlarını arar. **Tarih aralığı** (2006-2024) + **İçerik arama** (400 karakter) + * `search_sayistay_temyiz_kurulu(ilam_dairesi, kamu_idaresi_turu, temyiz_karar, ...)`: Temyiz Kurulu (itiraz) kararlarını arar. **8 Daire filtreleme** + **Kurum türü** + **Konu sınıflandırması** + * `search_sayistay_daire(yargilama_dairesi, web_karar_metni, hesap_yili, ...)`: Daire (ilk derece denetim) kararlarını arar. **8 Daire filtreleme** + **Hesap yılı** + **İçerik arama** + * `get_sayistay_genel_kurul_document_markdown(decision_id: str)`: Genel Kurul kararının tam metnini Markdown formatında getirir + * `get_sayistay_temyiz_kurulu_document_markdown(decision_id: str)`: Temyiz Kurulu kararının tam metnini Markdown formatında getirir + * `get_sayistay_daire_document_markdown(decision_id: str)`: Daire kararının tam metnini Markdown formatında getirir + +* **KVKK Araçları (Brave Search API + Türkçe Arama):** + * `search_kvkk_decisions(keywords, page, pageSize, ...)`: KVKK (Kişisel Verilerin Korunması Kurulu) kararlarını Brave Search API ile arar. **Türkçe arama** + **Site hedeflemeli** (`site:kvkk.gov.tr "karar özeti"`) + **Sayfalama desteği** + * `get_kvkk_document_markdown(decision_url: str, page_number: Optional[int] = 1)`: KVKK kararının tam metnini **sayfalanmış Markdown** formatında getirir (5.000 karakterlik sayfa) + +### BDDK Araçları + * `search_bddk_decisions(keywords, page)`: BDDK (Bankacılık Düzenleme ve Denetleme Kurumu) kararlarını arar. **"Karar Sayısı" targeting** + **Spesifik URL filtreleme** (`bddk.org.tr/Mevzuat/DokumanGetir`) + **Optimized search** + * `get_bddk_document_markdown(document_id: str, page_number: Optional[int] = 1)`: BDDK kararının tam metnini **sayfalanmış Markdown** formatında getirir (5.000 karakterlik sayfa) + +
+ +--- + +
+📊 Kapsamlı İstatistikler & Optimizasyon Başarıları + +🚀 **TOKEN OPTİMİZASYON BAŞARISI:** +- **%61.8 Token Azaltma:** 14,061 → 5,369 tokens (8,692 token tasarrufu) +- **Hedef Aşım:** 10,000 token hedefini 4,631 token aştık +- **Daha Hızlı Yanıt:** Claude AI ile optimize edilmiş etkileşim +- **Korunan İşlevsellik:** %100 özellik desteği devam ediyor + +**GENEL İSTATİSTİKLER:** +- **Toplam Mahkeme/Kurum:** 13 farklı hukuki kurum (KVKK dahil) +- **Toplam MCP Tool:** 19 optimize edilmiş arama ve belge getirme aracı +- **Daire/Kurul Filtreleme:** 87 farklı seçenek (52 Yargıtay + 27 Danıştay + 8 Sayıştay) +- **Tarih Filtreleme:** Birleşik Bedesten API aracında ISO 8601 formatında tam tarih aralığı desteği +- **Kesin Cümle Arama:** Birleşik Bedesten API aracında çift tırnak ile tam cümle arama (`"\"mülkiyet kararı\""` formatı) +- **Birleşik API:** 10 ayrı Bedesten aracı → 2 birleşik araç (search_bedesten_unified + get_bedesten_document_markdown) +- **API Kaynağı:** Dual/Triple API desteği ile maksimum kapsama +- **Tam Türk Adalet Sistemi:** Yerel mahkemelerden en yüksek mahkemelere kadar + +**🏛️ Desteklenen Mahkeme Hiyerarşisi:** +``` +Yerel Mahkemeler → İstinaf → Yargıtay/Danıştay → Anayasa Mahkemesi + ↓ ↓ ↓ ↓ +Bedesten API Bedesten API Dual/Triple API Norm+Bireysel API ++ Tarih + Kesin + Tarih + Kesin + Daire + Tarih + Gelişmiş + Cümle Arama Cümle Arama + Kesin Cümle Arama +``` + +**⚖️ Kapsamlı Filtreleme Özellikleri:** +- **Daire Filtreleme:** 79 seçenek (52 Yargıtay + 27 Danıştay) + - **Yargıtay:** 52 seçenek (1-23 Hukuk, 1-23 Ceza, Genel Kurullar, Başkanlar Kurulu) + - **Danıştay:** 27 seçenek (1-17 Daireler, İdare/Vergi Kurulları, Askeri Mahkemeler) +- **Tarih Filtreleme:** 5 Bedesten API aracında ISO 8601 formatı (YYYY-MM-DDTHH:MM:SS.000Z) + - Tek tarih, tarih aralığı, tek taraflı filtreleme desteği + - Yargıtay, Danıştay, Yerel Hukuk, İstinaf Hukuk, KYB kararları +- **Kesin Cümle Arama:** 5 Bedesten API aracında çift tırnak formatı + - Normal arama: `"mülkiyet kararı"` (kelimeler ayrı ayrı) + - Kesin arama: `"\"mülkiyet kararı\""` (tam cümle olarak) + - Daha kesin sonuçlar için hukuki terimler ve kavramlar + +**🔧 OPTİMİZASYON DETAYLARI:** +- **Anayasa Mahkemesi:** 4 araç → 2 birleşik araç (search_anayasa_unified + get_anayasa_document_unified) +- **Yargıtay & Danıştay:** Ana API araçları birleşik Bedesten API'ye entegre edildi +- **Sayıştay:** 6 araç → 2 birleşik araç (search_sayistay_unified + get_sayistay_document_unified) +- **Parameter Optimizasyonu:** pageSize parametreleri optimize edildi +- **Açıklama Optimizasyonu:** Uzun açıklamalar kısaltıldı (örn: KIK karar_metni) + +
+ +--- + +
+🌐 Web Service / ASGI Deployment + +Yargı MCP artık web servisi olarak da çalıştırılabilir! ASGI desteği sayesinde: + +- **Web API olarak erişim**: HTTP endpoint'leri üzerinden MCP araçlarına erişim +- **Cloud deployment**: Heroku, Railway, Google Cloud Run, AWS Lambda desteği +- **Docker desteği**: Production-ready Docker container +- **FastAPI entegrasyonu**: REST API ve interaktif dokümantasyon + +**Hızlı başlangıç:** +```bash +# ASGI dependencies yükle +pip install yargi-mcp[asgi] + +# Web servisi olarak başlat +python run_asgi.py +# veya +uvicorn asgi_app:app --host 0.0.0.0 --port 8000 +``` + +Detaylı deployment rehberi için: [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md) + +
+ +--- + +📜 **Lisans** + +Bu proje MIT Lisansı altında lisanslanmıştır. Detaylar için `LICENSE` dosyasına bakınız. diff --git a/saidsurucu-yargi-mcp-f5fa007/__main__.py b/saidsurucu-yargi-mcp-f5fa007/__main__.py new file mode 100644 index 0000000..12ab0a0 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/__main__.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 +"""Entry point for yargi-mcp package.""" + +from mcp_server_main import main + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/__init__.py b/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/bireysel_client.py b/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/bireysel_client.py new file mode 100644 index 0000000..3d01787 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/bireysel_client.py @@ -0,0 +1,355 @@ +# anayasa_mcp_module/bireysel_client.py +# This client is for Bireysel Başvuru: https://kararlarbilgibankasi.anayasa.gov.tr + +import httpx +from bs4 import BeautifulSoup, Tag +from typing import Dict, Any, List, Optional, Tuple +import logging +import html +import re +import io +from urllib.parse import urlencode, urljoin, quote +from markitdown import MarkItDown +import math # For math.ceil for pagination + +from .models import ( + AnayasaBireyselReportSearchRequest, + AnayasaBireyselReportDecisionDetail, + AnayasaBireyselReportDecisionSummary, + AnayasaBireyselReportSearchResult, + AnayasaBireyselBasvuruDocumentMarkdown, # Model for Bireysel Başvuru document +) + +logger = logging.getLogger(__name__) +if not logger.hasHandlers(): + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') + + +class AnayasaBireyselBasvuruApiClient: + BASE_URL = "https://kararlarbilgibankasi.anayasa.gov.tr" + SEARCH_PATH = "/Ara" + DOCUMENT_MARKDOWN_CHUNK_SIZE = 5000 # Character limit per page + + def __init__(self, request_timeout: float = 60.0): + self.http_client = httpx.AsyncClient( + base_url=self.BASE_URL, + headers={ + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + }, + timeout=request_timeout, + verify=True, + follow_redirects=True + ) + + def _build_query_params_for_bireysel_report(self, params: AnayasaBireyselReportSearchRequest) -> List[Tuple[str, str]]: + query_params: List[Tuple[str, str]] = [] + query_params.append(("KararBulteni", "1")) # Specific to this report type + + if params.keywords: + for kw in params.keywords: + query_params.append(("KelimeAra[]", kw)) + + if params.page_to_fetch and params.page_to_fetch > 1: + query_params.append(("page", str(params.page_to_fetch))) + + return query_params + + async def search_bireysel_basvuru_report( + self, + params: AnayasaBireyselReportSearchRequest + ) -> AnayasaBireyselReportSearchResult: + final_query_params = self._build_query_params_for_bireysel_report(params) + request_url = self.SEARCH_PATH + + logger.info(f"AnayasaBireyselBasvuruApiClient: Performing Bireysel Başvuru Report search. Path: {request_url}, Params: {final_query_params}") + + try: + response = await self.http_client.get(request_url, params=final_query_params) + response.raise_for_status() + html_content = response.text + except httpx.RequestError as e: + logger.error(f"AnayasaBireyselBasvuruApiClient: HTTP request error during Bireysel Başvuru Report search: {e}") + raise + except Exception as e: + logger.error(f"AnayasaBireyselBasvuruApiClient: Error processing Bireysel Başvuru Report search request: {e}") + raise + + soup = BeautifulSoup(html_content, 'html.parser') + + total_records = None + bulunan_karar_div = soup.find("div", class_="bulunankararsayisi") + if bulunan_karar_div: + match_records = re.search(r'(\d+)\s*Karar Bulundu', bulunan_karar_div.get_text(strip=True)) + if match_records: + total_records = int(match_records.group(1)) + + processed_decisions: List[AnayasaBireyselReportDecisionSummary] = [] + + report_content_area = soup.find("div", class_="HaberBulteni") + if not report_content_area: + logger.warning("HaberBulteni div not found, attempting to parse decision divs from the whole page.") + report_content_area = soup + + decision_divs = report_content_area.find_all("div", class_="KararBulteniBirKarar") + if not decision_divs: + logger.warning("No KararBulteniBirKarar divs found.") + + + for decision_div in decision_divs: + title_tag = decision_div.find("h4") + title_text = title_tag.get_text(strip=True) if title_tag and title_tag.strong else (title_tag.get_text(strip=True) if title_tag else "") + + + alti_cizili_div = decision_div.find("div", class_="AltiCizili") + ref_no, dec_type, body, app_date, dec_date, url_path = "", "", "", "", "", "" + if alti_cizili_div: + link_tag = alti_cizili_div.find("a", href=True) + if link_tag: + ref_no = link_tag.get_text(strip=True) + url_path = link_tag['href'] + + parts_text = alti_cizili_div.get_text(separator="|", strip=True) + parts = [part.strip() for part in parts_text.split("|")] + + # Clean ref_no from the first part if it was extracted from link + if ref_no and parts and parts[0].strip().startswith(ref_no): + parts[0] = parts[0].replace(ref_no, "").strip() + if not parts[0]: parts.pop(0) # Remove empty string if ref_no was the only content + + # Assign parts based on typical order, adjusting for missing ref_no at start + current_idx = 0 + if not ref_no and len(parts) > current_idx and re.match(r"\d+/\d+", parts[current_idx]): # Check if first part is ref_no + ref_no = parts[current_idx] + current_idx += 1 + + dec_type = parts[current_idx] if len(parts) > current_idx else "" + current_idx += 1 + body = parts[current_idx] if len(parts) > current_idx else "" + current_idx += 1 + + app_date_raw = parts[current_idx] if len(parts) > current_idx else "" + current_idx += 1 + dec_date_raw = parts[current_idx] if len(parts) > current_idx else "" + + if app_date_raw and "Başvuru Tarihi :" in app_date_raw: + app_date = app_date_raw.replace("Başvuru Tarihi :", "").strip() + elif app_date_raw: # If label is missing but format matches + app_date_match = re.search(r'(\d{1,2}/\d{1,2}/\d{4})', app_date_raw) + if app_date_match: app_date = app_date_match.group(1) + + + if dec_date_raw and "Karar Tarihi :" in dec_date_raw: + dec_date = dec_date_raw.replace("Karar Tarihi :", "").strip() + elif dec_date_raw: # If label is missing but format matches + dec_date_match = re.search(r'(\d{1,2}/\d{1,2}/\d{4})', dec_date_raw) + if dec_date_match: dec_date = dec_date_match.group(1) + + + subject_div = decision_div.find(lambda tag: tag.name == 'div' and not tag.has_attr('class') and tag.get_text(strip=True).startswith("BAŞVURU KONUSU :")) + subject_text = subject_div.get_text(strip=True).replace("BAŞVURU KONUSU :", "").strip() if subject_div else "" + + details_list: List[AnayasaBireyselReportDecisionDetail] = [] + karar_detaylari_div = decision_div.find_next_sibling("div", id="KararDetaylari") # Corrected: was KararDetaylari + if karar_detaylari_div: + table = karar_detaylari_div.find("table", class_="table") + if table and table.find("tbody"): + for row in table.find("tbody").find_all("tr"): + cells = row.find_all("td") + if len(cells) == 4: # Hak, Müdahale İddiası, Sonuç, Giderim + details_list.append(AnayasaBireyselReportDecisionDetail( + hak=cells[0].get_text(strip=True) or "", + mudahale_iddiasi=cells[1].get_text(strip=True) or "", + sonuc=cells[2].get_text(strip=True) or "", + giderim=cells[3].get_text(strip=True) or "", + )) + + full_decision_page_url = urljoin(self.BASE_URL, url_path) if url_path else "" + + processed_decisions.append(AnayasaBireyselReportDecisionSummary( + title=title_text, + decision_reference_no=ref_no, + decision_page_url=full_decision_page_url, + decision_type_summary=dec_type, + decision_making_body=body, + application_date_summary=app_date, + decision_date_summary=dec_date, + application_subject_summary=subject_text, + details=details_list + )) + + return AnayasaBireyselReportSearchResult( + decisions=processed_decisions, + total_records_found=total_records, + retrieved_page_number=params.page_to_fetch + ) + + def _convert_html_to_markdown_bireysel(self, full_decision_html_content: str) -> Optional[str]: + if not full_decision_html_content: + return None + + processed_html = html.unescape(full_decision_html_content) + soup = BeautifulSoup(processed_html, "html.parser") + html_input_for_markdown = "" + + karar_tab_content = soup.find("div", id="Karar") + if karar_tab_content: + karar_html_span = karar_tab_content.find("span", class_="kararHtml") + if karar_html_span: + word_section = karar_html_span.find("div", class_="WordSection1") + if word_section: + for s in word_section.select('script, style, .item.col-xs-12.col-sm-12, center:has(b)'): + s.decompose() + html_input_for_markdown = str(word_section) + else: + logger.warning("AnayasaBireyselBasvuruApiClient: WordSection1 not found in span.kararHtml. Using span.kararHtml content.") + for s in karar_html_span.select('script, style, .item.col-xs-12.col-sm-12, center:has(b)'): + s.decompose() + html_input_for_markdown = str(karar_html_span) + else: + logger.warning("AnayasaBireyselBasvuruApiClient: span.kararHtml not found in div#Karar. Using div#Karar content.") + for s in karar_tab_content.select('script, style, .item.col-xs-12.col-sm-12, center:has(b)'): + s.decompose() + html_input_for_markdown = str(karar_tab_content) + else: + logger.warning("AnayasaBireyselBasvuruApiClient: div#Karar (KARAR tab) not found. Trying WordSection1 fallback.") + word_section_fallback = soup.find("div", class_="WordSection1") + if word_section_fallback: + for s in word_section_fallback.select('script, style, .item.col-xs-12.col-sm-12, center:has(b)'): + s.decompose() + html_input_for_markdown = str(word_section_fallback) + else: + body_tag = soup.find("body") + if body_tag: + for s in body_tag.select('script, style, .item.col-xs-12.col-sm-12, center:has(b), .banner, .footer, .yazdirmaalani, .filtreler, .menu, .altmenu, .geri, .arabuton, .temizlebutonu, form#KararGetir, .TabBaslik, #KararDetaylari, .share-button-container'): + s.decompose() + html_input_for_markdown = str(body_tag) + else: + html_input_for_markdown = processed_html + + markdown_text = None + try: + # Ensure the content is wrapped in basic HTML structure if it's not already + if not html_input_for_markdown.strip().lower().startswith(("{html_input_for_markdown}" + else: + html_content = html_input_for_markdown + + # Convert HTML string to bytes and create BytesIO stream + html_bytes = html_content.encode('utf-8') + html_stream = io.BytesIO(html_bytes) + + # Pass BytesIO stream to MarkItDown to avoid temp file creation + md_converter = MarkItDown() + conversion_result = md_converter.convert(html_stream) + markdown_text = conversion_result.text_content + except Exception as e: + logger.error(f"AnayasaBireyselBasvuruApiClient: MarkItDown conversion error: {e}") + return markdown_text + + async def get_decision_document_as_markdown( + self, + document_url_path: str, # e.g. /BB/2021/20295 + page_number: int = 1 + ) -> AnayasaBireyselBasvuruDocumentMarkdown: + full_url = urljoin(self.BASE_URL, document_url_path) + logger.info(f"AnayasaBireyselBasvuruApiClient: Fetching Bireysel Başvuru document for Markdown (page {page_number}) from URL: {full_url}") + + basvuru_no_from_page = None + karar_tarihi_from_page = None + basvuru_tarihi_from_page = None + karari_veren_birim_from_page = None + karar_turu_from_page = None + resmi_gazete_info_from_page = None + + try: + response = await self.http_client.get(full_url) + response.raise_for_status() + html_content_from_api = response.text + + if not isinstance(html_content_from_api, str) or not html_content_from_api.strip(): + logger.warning(f"AnayasaBireyselBasvuruApiClient: Received empty HTML from {full_url}.") + return AnayasaBireyselBasvuruDocumentMarkdown( + source_url=full_url, markdown_chunk=None, current_page=page_number, total_pages=0, is_paginated=False + ) + + soup = BeautifulSoup(html_content_from_api, 'html.parser') + + meta_desc_tag = soup.find("meta", attrs={"name": "description"}) + if meta_desc_tag and meta_desc_tag.get("content"): + content = meta_desc_tag["content"] + bn_match = re.search(r"B\.\s*No:\s*([\d\/]+)", content) + if bn_match: basvuru_no_from_page = bn_match.group(1).strip() + + date_match = re.search(r"(\d{1,2}\/\d{1,2}\/\d{4}),\s*§", content) + if date_match: karar_tarihi_from_page = date_match.group(1).strip() + + karar_detaylari_tab = soup.find("div", id="KararDetaylari") + if karar_detaylari_tab: + table = karar_detaylari_tab.find("table", class_="table") + if table: + rows = table.find_all("tr") + for row in rows: + cells = row.find_all("td") + if len(cells) == 2: + key = cells[0].get_text(strip=True) + value = cells[1].get_text(strip=True) + if "Kararı Veren Birim" in key: karari_veren_birim_from_page = value + elif "Karar Türü (Başvuru Sonucu)" in key: karar_turu_from_page = value + elif "Başvuru No" in key and not basvuru_no_from_page: basvuru_no_from_page = value + elif "Başvuru Tarihi" in key: basvuru_tarihi_from_page = value + elif "Karar Tarihi" in key and not karar_tarihi_from_page: karar_tarihi_from_page = value + elif "Resmi Gazete Tarih / Sayı" in key: resmi_gazete_info_from_page = value + + full_markdown_content = self._convert_html_to_markdown_bireysel(html_content_from_api) + + if not full_markdown_content: + return AnayasaBireyselBasvuruDocumentMarkdown( + source_url=full_url, + basvuru_no_from_page=basvuru_no_from_page, + karar_tarihi_from_page=karar_tarihi_from_page, + basvuru_tarihi_from_page=basvuru_tarihi_from_page, + karari_veren_birim_from_page=karari_veren_birim_from_page, + karar_turu_from_page=karar_turu_from_page, + resmi_gazete_info_from_page=resmi_gazete_info_from_page, + markdown_chunk=None, + current_page=page_number, + total_pages=0, + is_paginated=False + ) + + content_length = len(full_markdown_content) + total_pages = math.ceil(content_length / self.DOCUMENT_MARKDOWN_CHUNK_SIZE) + if total_pages == 0: total_pages = 1 + + current_page_clamped = max(1, min(page_number, total_pages)) + start_index = (current_page_clamped - 1) * self.DOCUMENT_MARKDOWN_CHUNK_SIZE + end_index = start_index + self.DOCUMENT_MARKDOWN_CHUNK_SIZE + markdown_chunk = full_markdown_content[start_index:end_index] + + return AnayasaBireyselBasvuruDocumentMarkdown( + source_url=full_url, + basvuru_no_from_page=basvuru_no_from_page, + karar_tarihi_from_page=karar_tarihi_from_page, + basvuru_tarihi_from_page=basvuru_tarihi_from_page, + karari_veren_birim_from_page=karari_veren_birim_from_page, + karar_turu_from_page=karar_turu_from_page, + resmi_gazete_info_from_page=resmi_gazete_info_from_page, + markdown_chunk=markdown_chunk, + current_page=current_page_clamped, + total_pages=total_pages, + is_paginated=(total_pages > 1) + ) + + except httpx.RequestError as e: + logger.error(f"AnayasaBireyselBasvuruApiClient: HTTP error fetching Bireysel Başvuru document from {full_url}: {e}") + raise + except Exception as e: + logger.error(f"AnayasaBireyselBasvuruApiClient: General error processing Bireysel Başvuru document from {full_url}: {e}") + raise + + async def close_client_session(self): + if hasattr(self, 'http_client') and self.http_client and not self.http_client.is_closed: + await self.http_client.aclose() + logger.info("AnayasaBireyselBasvuruApiClient: HTTP client session closed.") \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/client.py b/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/client.py new file mode 100644 index 0000000..9e15a48 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/client.py @@ -0,0 +1,356 @@ +# anayasa_mcp_module/client.py +# This client is for Norm Denetimi: https://normkararlarbilgibankasi.anayasa.gov.tr + +import httpx +from bs4 import BeautifulSoup +from typing import Dict, Any, List, Optional, Tuple +import logging +import html +import re +import io +from urllib.parse import urlencode, urljoin, quote +from markitdown import MarkItDown +import math # For math.ceil for pagination + +from .models import ( + AnayasaNormDenetimiSearchRequest, + AnayasaDecisionSummary, + AnayasaReviewedNormInfo, + AnayasaSearchResult, + AnayasaDocumentMarkdown, # Model for Norm Denetimi document +) + +logger = logging.getLogger(__name__) +if not logger.hasHandlers(): + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') + +class AnayasaMahkemesiApiClient: + BASE_URL = "https://normkararlarbilgibankasi.anayasa.gov.tr" + SEARCH_PATH_SEGMENT = "Ara" + DOCUMENT_MARKDOWN_CHUNK_SIZE = 5000 # Character limit per page + + def __init__(self, request_timeout: float = 60.0): + self.http_client = httpx.AsyncClient( + base_url=self.BASE_URL, + headers={ + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + }, + timeout=request_timeout, + verify=True, + follow_redirects=True + ) + + def _build_search_query_params_for_aym(self, params: AnayasaNormDenetimiSearchRequest) -> List[Tuple[str, str]]: + query_params: List[Tuple[str, str]] = [] + if params.keywords_all: + for kw in params.keywords_all: query_params.append(("KelimeAra[]", kw)) + if params.keywords_any: + for kw in params.keywords_any: query_params.append(("HerhangiBirKelimeAra[]", kw)) + if params.keywords_exclude: + for kw in params.keywords_exclude: query_params.append(("BulunmayanKelimeAra[]", kw)) + if params.period and params.period and params.period != "ALL": query_params.append(("Donemler_id", params.period)) + if params.case_number_esas: query_params.append(("EsasNo", params.case_number_esas)) + if params.decision_number_karar: query_params.append(("KararNo", params.decision_number_karar)) + if params.first_review_date_start: query_params.append(("IlkIncelemeTarihiIlk", params.first_review_date_start)) + if params.first_review_date_end: query_params.append(("IlkIncelemeTarihiSon", params.first_review_date_end)) + if params.decision_date_start: query_params.append(("KararTarihiIlk", params.decision_date_start)) + if params.decision_date_end: query_params.append(("KararTarihiSon", params.decision_date_end)) + if params.application_type and params.application_type and params.application_type != "ALL": query_params.append(("BasvuruTurler_id", params.application_type)) + if params.applicant_general_name: query_params.append(("BasvuranGeneller_id", params.applicant_general_name)) + if params.applicant_specific_name: query_params.append(("BasvuranOzeller_id", params.applicant_specific_name)) + if params.attending_members_names: + for name in params.attending_members_names: query_params.append(("Uyeler_id[]", name)) + if params.rapporteur_name: query_params.append(("Raportorler_id", params.rapporteur_name)) + if params.norm_type and params.norm_type and params.norm_type != "ALL": query_params.append(("NormunTurler_id", params.norm_type)) + if params.norm_id_or_name: query_params.append(("NormunNumarasiAdlar_id", params.norm_id_or_name)) + if params.norm_article: query_params.append(("NormunMaddeNumarasi", params.norm_article)) + if params.review_outcomes: + for outcome_val in params.review_outcomes: + if outcome_val and outcome_val != "ALL": query_params.append(("IncelemeTuruKararSonuclar_id[]", outcome_val)) + if params.reason_for_final_outcome and params.reason_for_final_outcome and params.reason_for_final_outcome != "ALL": + query_params.append(("KararSonucununGerekcesi", params.reason_for_final_outcome)) + if params.basis_constitution_article_numbers: + for article_no in params.basis_constitution_article_numbers: query_params.append(("DayanakHukmu[]", article_no)) + if params.official_gazette_date_start: query_params.append(("ResmiGazeteTarihiIlk", params.official_gazette_date_start)) + if params.official_gazette_date_end: query_params.append(("ResmiGazeteTarihiSon", params.official_gazette_date_end)) + if params.official_gazette_number_start: query_params.append(("ResmiGazeteSayisiIlk", params.official_gazette_number_start)) + if params.official_gazette_number_end: query_params.append(("ResmiGazeteSayisiSon", params.official_gazette_number_end)) + if params.has_press_release and params.has_press_release and params.has_press_release != "ALL": query_params.append(("BasinDuyurusu", params.has_press_release)) + if params.has_dissenting_opinion and params.has_dissenting_opinion and params.has_dissenting_opinion != "ALL": query_params.append(("KarsiOy", params.has_dissenting_opinion)) + if params.has_different_reasoning and params.has_different_reasoning and params.has_different_reasoning != "ALL": query_params.append(("FarkliGerekce", params.has_different_reasoning)) + + # Add pagination and sorting parameters as query params instead of URL path + if params.results_per_page and params.results_per_page != 10: + query_params.append(("SatirSayisi", str(params.results_per_page))) + + if params.sort_by_criteria and params.sort_by_criteria != "KararTarihi": + query_params.append(("Siralama", params.sort_by_criteria)) + + if params.page_to_fetch and params.page_to_fetch > 1: + query_params.append(("page", str(params.page_to_fetch))) + return query_params + + async def search_norm_denetimi_decisions( + self, + params: AnayasaNormDenetimiSearchRequest + ) -> AnayasaSearchResult: + # Use simple /Ara endpoint - the complex path structure seems to cause 404s + request_path = f"/{self.SEARCH_PATH_SEGMENT}" + + final_query_params = self._build_search_query_params_for_aym(params) + logger.info(f"AnayasaMahkemesiApiClient: Performing Norm Denetimi search. Path: {request_path}, Params: {final_query_params}") + + try: + response = await self.http_client.get(request_path, params=final_query_params) + response.raise_for_status() + html_content = response.text + except httpx.RequestError as e: + logger.error(f"AnayasaMahkemesiApiClient: HTTP request error during Norm Denetimi search: {e}") + raise + except Exception as e: + logger.error(f"AnayasaMahkemesiApiClient: Error processing Norm Denetimi search request: {e}") + raise + + soup = BeautifulSoup(html_content, 'html.parser') + + total_records = None + bulunan_karar_div = soup.find("div", class_="bulunankararsayisi") + if not bulunan_karar_div: # Fallback for mobile view + bulunan_karar_div = soup.find("div", class_="bulunankararsayisiMobil") + + if bulunan_karar_div: + match_records = re.search(r'(\d+)\s*Karar Bulundu', bulunan_karar_div.get_text(strip=True)) + if match_records: + total_records = int(match_records.group(1)) + + processed_decisions: List[AnayasaDecisionSummary] = [] + decision_divs = soup.find_all("div", class_="birkarar") + + for decision_div in decision_divs: + link_tag = decision_div.find("a", href=True) + doc_url_path = link_tag['href'] if link_tag else None + decision_page_url_str = urljoin(self.BASE_URL, doc_url_path) if doc_url_path else None + + title_div = decision_div.find("div", class_="bkararbaslik") + ek_no_text_raw = title_div.get_text(strip=True, separator=" ").replace('\xa0', ' ') if title_div else "" + ek_no_match = re.search(r"(E\.\s*\d+/\d+\s*,\s*K\.\s*\d+/\d+)", ek_no_text_raw) + ek_no_text = ek_no_match.group(1) if ek_no_match else ek_no_text_raw.split("Sayılı Karar")[0].strip() + + keyword_count_div = title_div.find("div", class_="BulunanKelimeSayisi") if title_div else None + keyword_count_text = keyword_count_div.get_text(strip=True).replace("Bulunan Kelime Sayısı", "").strip() if keyword_count_div else None + keyword_count = int(keyword_count_text) if keyword_count_text and keyword_count_text.isdigit() else None + + info_div = decision_div.find("div", class_="kararbilgileri") + info_parts = [part.strip() for part in info_div.get_text(separator="|").split("|")] if info_div else [] + + app_type_summary = info_parts[0] if len(info_parts) > 0 else None + applicant_summary = info_parts[1] if len(info_parts) > 1 else None + outcome_summary = info_parts[2] if len(info_parts) > 2 else None + dec_date_raw = info_parts[3] if len(info_parts) > 3 else None + decision_date_summary = dec_date_raw.replace("Karar Tarihi:", "").strip() if dec_date_raw else None + + reviewed_norms_list: List[AnayasaReviewedNormInfo] = [] + details_table_container = decision_div.find_next_sibling("div", class_=re.compile(r"col-sm-12")) # The details table is in a sibling div + if details_table_container: + details_table = details_table_container.find("table", class_="table") + if details_table and details_table.find("tbody"): + for row in details_table.find("tbody").find_all("tr"): + cells = row.find_all("td") + if len(cells) == 6: + reviewed_norms_list.append(AnayasaReviewedNormInfo( + norm_name_or_number=cells[0].get_text(strip=True) or None, + article_number=cells[1].get_text(strip=True) or None, + review_type_and_outcome=cells[2].get_text(strip=True) or None, + outcome_reason=cells[3].get_text(strip=True) or None, + basis_constitution_articles_cited=[a.strip() for a in cells[4].get_text(strip=True).split(',') if a.strip()] if cells[4].get_text(strip=True) else [], + postponement_period=cells[5].get_text(strip=True) or None + )) + + processed_decisions.append(AnayasaDecisionSummary( + decision_reference_no=ek_no_text, + decision_page_url=decision_page_url_str, + keywords_found_count=keyword_count, + application_type_summary=app_type_summary, + applicant_summary=applicant_summary, + decision_outcome_summary=outcome_summary, + decision_date_summary=decision_date_summary, + reviewed_norms=reviewed_norms_list + )) + + return AnayasaSearchResult( + decisions=processed_decisions, + total_records_found=total_records, + retrieved_page_number=params.page_to_fetch + ) + + def _convert_html_to_markdown_norm_denetimi(self, full_decision_html_content: str) -> Optional[str]: + """Converts direct HTML content from an Anayasa Mahkemesi Norm Denetimi decision page to Markdown.""" + if not full_decision_html_content: + return None + + processed_html = html.unescape(full_decision_html_content) + soup = BeautifulSoup(processed_html, "html.parser") + html_input_for_markdown = "" + + karar_tab_content = soup.find("div", id="Karar") # "KARAR" tab content + if karar_tab_content: + karar_metni_div = karar_tab_content.find("div", class_="KararMetni") + if karar_metni_div: + # Remove scripts and styles + for script_tag in karar_metni_div.find_all("script"): script_tag.decompose() + for style_tag in karar_metni_div.find_all("style"): style_tag.decompose() + # Remove "Künye Kopyala" button and other non-content divs + for item_div in karar_metni_div.find_all("div", class_="item col-sm-12"): item_div.decompose() + for modal_div in karar_metni_div.find_all("div", class_="modal fade"): modal_div.decompose() # If any modals + + word_section = karar_metni_div.find("div", class_="WordSection1") + html_input_for_markdown = str(word_section) if word_section else str(karar_metni_div) + else: + html_input_for_markdown = str(karar_tab_content) + else: + # Fallback if specific structure is not found + word_section_fallback = soup.find("div", class_="WordSection1") + if word_section_fallback: + html_input_for_markdown = str(word_section_fallback) + else: + # Last resort: use the whole body or the raw HTML + body_tag = soup.find("body") + html_input_for_markdown = str(body_tag) if body_tag else processed_html + + markdown_text = None + try: + # Ensure the content is wrapped in basic HTML structure if it's not already + if not html_input_for_markdown.strip().lower().startswith(("{html_input_for_markdown}" + else: + html_content = html_input_for_markdown + + # Convert HTML string to bytes and create BytesIO stream + html_bytes = html_content.encode('utf-8') + html_stream = io.BytesIO(html_bytes) + + # Pass BytesIO stream to MarkItDown to avoid temp file creation + md_converter = MarkItDown() + conversion_result = md_converter.convert(html_stream) + markdown_text = conversion_result.text_content + except Exception as e: + logger.error(f"AnayasaMahkemesiApiClient: MarkItDown conversion error: {e}") + return markdown_text + + async def get_decision_document_as_markdown( + self, + document_url: str, + page_number: int = 1 + ) -> AnayasaDocumentMarkdown: + """ + Retrieves a specific Anayasa Mahkemesi (Norm Denetimi) decision, + converts its content to Markdown, and returns the requested page/chunk. + """ + full_url = urljoin(self.BASE_URL, document_url) if not document_url.startswith("http") else document_url + logger.info(f"AnayasaMahkemesiApiClient: Fetching Norm Denetimi document for Markdown (page {page_number}) from URL: {full_url}") + + decision_ek_no_from_page = None + decision_date_from_page = None + official_gazette_from_page = None + + try: + # Use a new client instance for document fetching if headers/timeout needs to be different, + # or reuse self.http_client if settings are compatible. For now, self.http_client. + get_response = await self.http_client.get(full_url, headers={"Accept": "text/html"}) + get_response.raise_for_status() + html_content_from_api = get_response.text + + if not isinstance(html_content_from_api, str) or not html_content_from_api.strip(): + logger.warning(f"AnayasaMahkemesiApiClient: Received empty or non-string HTML from URL {full_url}.") + return AnayasaDocumentMarkdown( + source_url=full_url, markdown_chunk=None, current_page=page_number, total_pages=0, is_paginated=False + ) + + # Extract metadata from the page content (E.K. No, Date, RG) + soup = BeautifulSoup(html_content_from_api, "html.parser") + karar_metni_div = soup.find("div", class_="KararMetni") # Usually within div#Karar + if not karar_metni_div: # Fallback if not in KararMetni + karar_metni_div = soup.find("div", class_="WordSection1") + + # Initialize with empty string defaults + decision_ek_no_from_page = "" + decision_date_from_page = "" + official_gazette_from_page = "" + + if karar_metni_div: + # Attempt to find E.K. No (Esas No, Karar No) + # Norm Denetimi pages often have this in bold

tags directly or in the WordSection1 + # Look for patterns like "Esas No.: YYYY/NN" and "Karar No.: YYYY/NN" + + esas_no_tag = karar_metni_div.find(lambda tag: tag.name == "p" and tag.find("b") and "Esas No.:" in tag.find("b").get_text()) + karar_no_tag = karar_metni_div.find(lambda tag: tag.name == "p" and tag.find("b") and "Karar No.:" in tag.find("b").get_text()) + karar_tarihi_tag = karar_metni_div.find(lambda tag: tag.name == "p" and tag.find("b") and "Karar tarihi:" in tag.find("b").get_text()) # Less common on Norm pages + resmi_gazete_tag = karar_metni_div.find(lambda tag: tag.name == "p" and ("Resmî Gazete tarih ve sayısı:" in tag.get_text() or "Resmi Gazete tarih/sayı:" in tag.get_text())) + + + if esas_no_tag and esas_no_tag.find("b") and karar_no_tag and karar_no_tag.find("b"): + esas_str = esas_no_tag.find("b").get_text(strip=True).replace('Esas No.:', '').strip() + karar_str = karar_no_tag.find("b").get_text(strip=True).replace('Karar No.:', '').strip() + decision_ek_no_from_page = f"E.{esas_str}, K.{karar_str}" + + if karar_tarihi_tag and karar_tarihi_tag.find("b"): + decision_date_from_page = karar_tarihi_tag.find("b").get_text(strip=True).replace("Karar tarihi:", "").strip() + elif karar_metni_div: # Fallback for Karar Tarihi if not in specific tag + date_match = re.search(r"Karar Tarihi\s*:\s*([\d\.]+)", karar_metni_div.get_text()) # Norm pages often use DD.MM.YYYY + if date_match: decision_date_from_page = date_match.group(1).strip() + + + if resmi_gazete_tag: + # Try to get the bold part first if it exists + bold_rg_tag = resmi_gazete_tag.find("b") + rg_text_content = bold_rg_tag.get_text(strip=True) if bold_rg_tag else resmi_gazete_tag.get_text(strip=True) + official_gazette_from_page = rg_text_content.replace("Resmî Gazete tarih ve sayısı:", "").replace("Resmi Gazete tarih/sayı:", "").strip() + + + full_markdown_content = self._convert_html_to_markdown_norm_denetimi(html_content_from_api) + + if not full_markdown_content: + return AnayasaDocumentMarkdown( + source_url=full_url, + decision_reference_no_from_page=decision_ek_no_from_page, + decision_date_from_page=decision_date_from_page, + official_gazette_info_from_page=official_gazette_from_page, + markdown_chunk=None, + current_page=page_number, + total_pages=0, + is_paginated=False + ) + + content_length = len(full_markdown_content) + total_pages = math.ceil(content_length / self.DOCUMENT_MARKDOWN_CHUNK_SIZE) + if total_pages == 0: total_pages = 1 + + current_page_clamped = max(1, min(page_number, total_pages)) + start_index = (current_page_clamped - 1) * self.DOCUMENT_MARKDOWN_CHUNK_SIZE + end_index = start_index + self.DOCUMENT_MARKDOWN_CHUNK_SIZE + markdown_chunk = full_markdown_content[start_index:end_index] + + return AnayasaDocumentMarkdown( + source_url=full_url, + decision_reference_no_from_page=decision_ek_no_from_page, + decision_date_from_page=decision_date_from_page, + official_gazette_info_from_page=official_gazette_from_page, + markdown_chunk=markdown_chunk, + current_page=current_page_clamped, + total_pages=total_pages, + is_paginated=(total_pages > 1) + ) + + except httpx.RequestError as e: + logger.error(f"AnayasaMahkemesiApiClient: HTTP error fetching Norm Denetimi document from {full_url}: {e}") + raise + except Exception as e: + logger.error(f"AnayasaMahkemesiApiClient: General error processing Norm Denetimi document from {full_url}: {e}") + raise + + async def close_client_session(self): + if hasattr(self, 'http_client') and self.http_client and not self.http_client.is_closed: + await self.http_client.aclose() + logger.info("AnayasaMahkemesiApiClient (Norm Denetimi): HTTP client session closed.") \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/models.py b/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/models.py new file mode 100644 index 0000000..f5e74e1 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/models.py @@ -0,0 +1,230 @@ +# anayasa_mcp_module/models.py + +from pydantic import BaseModel, Field, HttpUrl +from typing import List, Optional, Dict, Any, Literal +from enum import Enum + +# --- Enums (AnayasaDonemEnum, etc. - same as before) --- +class AnayasaDonemEnum(str, Enum): + TUMU = "ALL" + DONEM_1961 = "1" + DONEM_1982 = "2" + + +class AnayasaVarYokEnum(str, Enum): + TUMU = "ALL" + YOK = "0" + VAR = "1" + + +class AnayasaIncelemeSonucuEnum(str, Enum): + TUMU = "ALL" + ESAS_ACILMAMIS_SAYILMA = "1" + ESAS_IPTAL = "2" + ESAS_KARAR_YER_OLMADIGI = "3" + ESAS_RET = "4" + ILK_ACILMAMIS_SAYILMA = "5" + ILK_ISIN_GERI_CEVRILMESI = "6" + ILK_KARAR_YER_OLMADIGI = "7" + ILK_RET = "8" + KANUN_6216_M43_4_IPTAL = "12" + +class AnayasaSonucGerekcesiEnum(str, Enum): + TUMU = "ALL" + ANAYASAYA_AYKIRI_DEGIL = "29" + ANAYASAYA_ESAS_YONUNDEN_AYKIRILIK = "1" + ANAYASAYA_ESAS_YONUNDEN_UYGUNLUK = "2" + ANAYASAYA_SEKIL_ESAS_UYGUNLUK = "30" + ANAYASAYA_SEKIL_YONUNDEN_AYKIRILIK = "3" + ANAYASAYA_SEKIL_YONUNDEN_UYGUNLUK = "4" + AYKIRILIK_ANAYASAYA_ESAS_YONUNDEN_DUPLICATE = "27" + BASVURU_KARARI = "5" + DENETIM_DISI = "6" + DIGER_GEREKCE_1 = "7" + DIGER_GEREKCE_2 = "8" + EKSIKLIGIN_GIDERILMEMESI = "9" + GEREKCE = "10" + GOREV = "11" + GOREV_YETKI = "12" + GOREVLI_MAHKEME = "13" + GORULMEKTE_OLAN_DAVA = "14" + MAHKEME = "15" + NORMDA_DEGISIKLIK_YAPILMASI = "16" + NORMUN_YURURLUKTEN_KALDIRILMASI = "17" + ON_YIL_YASAGI = "18" + SURE = "19" + USULE_UYMAMA = "20" + UYGULANACAK_NORM = "21" + UYGULANAMAZ_HALE_GELME = "22" + YETKI = "23" + YETKI_SURE = "24" + YOK_HUKMUNDE_OLMAMA = "25" + YOKLUK = "26" +# --- End Enums --- + +class AnayasaNormDenetimiSearchRequest(BaseModel): + """Model for Anayasa Mahkemesi (Norm Denetimi) search request for the MCP tool.""" + keywords_all: Optional[List[str]] = Field(default_factory=list, description="Keywords for AND logic (KelimeAra[]).") + keywords_any: Optional[List[str]] = Field(default_factory=list, description="Keywords for OR logic (HerhangiBirKelimeAra[]).") + keywords_exclude: Optional[List[str]] = Field(default_factory=list, description="Keywords to exclude (BulunmayanKelimeAra[]).") + period: Optional[Literal["ALL", "1", "2"]] = Field(default="ALL", description="Constitutional period (Donemler_id).") + case_number_esas: str = Field("", description="Case registry number (EsasNo), e.g., '2023/123'.") + decision_number_karar: str = Field("", description="Decision number (KararNo), e.g., '2023/456'.") + first_review_date_start: str = Field("", description="First review start date (IlkIncelemeTarihiIlk), format DD/MM/YYYY.") + first_review_date_end: str = Field("", description="First review end date (IlkIncelemeTarihiSon), format DD/MM/YYYY.") + decision_date_start: str = Field("", description="Decision start date (KararTarihiIlk), format DD/MM/YYYY.") + decision_date_end: str = Field("", description="Decision end date (KararTarihiSon), format DD/MM/YYYY.") + application_type: Optional[Literal["ALL", "1", "2", "3"]] = Field(default="ALL", description="Type of application (BasvuruTurler_id).") + applicant_general_name: str = Field("", description="General applicant name (BasvuranGeneller_id).") + applicant_specific_name: str = Field("", description="Specific applicant name (BasvuranOzeller_id).") + official_gazette_date_start: str = Field("", description="Official Gazette start date (ResmiGazeteTarihiIlk), format DD/MM/YYYY.") + official_gazette_date_end: str = Field("", description="Official Gazette end date (ResmiGazeteTarihiSon), format DD/MM/YYYY.") + official_gazette_number_start: str = Field("", description="Official Gazette starting number (ResmiGazeteSayisiIlk).") + official_gazette_number_end: str = Field("", description="Official Gazette ending number (ResmiGazeteSayisiSon).") + has_press_release: Optional[Literal["ALL", "0", "1"]] = Field(default="ALL", description="Press release available (BasinDuyurusu).") + has_dissenting_opinion: Optional[Literal["ALL", "0", "1"]] = Field(default="ALL", description="Dissenting opinion exists (KarsiOy).") + has_different_reasoning: Optional[Literal["ALL", "0", "1"]] = Field(default="ALL", description="Different reasoning exists (FarkliGerekce).") + attending_members_names: Optional[List[str]] = Field(default_factory=list, description="List of attending members' exact names (Uyeler_id[]).") + rapporteur_name: str = Field("", description="Rapporteur's exact name (Raportorler_id).") + norm_type: Optional[Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "0"]] = Field(default="ALL", description="Type of the reviewed norm (NormunTurler_id).") + norm_id_or_name: str = Field("", description="Number or name of the norm (NormunNumarasiAdlar_id).") + norm_article: str = Field("", description="Article number of the norm (NormunMaddeNumarasi).") + review_outcomes: Optional[List[Literal["1", "2", "3", "4", "5", "6", "7", "8", "12"]]] = Field(default_factory=list, description="List of review types and outcomes (IncelemeTuruKararSonuclar_id[]).") + reason_for_final_outcome: Optional[Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "29", "30"]] = Field(default="ALL", description="Main reason for the decision outcome (KararSonucununGerekcesi).") + basis_constitution_article_numbers: Optional[List[str]] = Field(default_factory=list, description="List of supporting Constitution article numbers (DayanakHukmu[]).") + results_per_page: int = Field(10, ge=1, le=10, description="Results per page.") + page_to_fetch: int = Field(1, ge=1, description="Page number to fetch for results list.") + sort_by_criteria: str = Field("KararTarihi", description="Sort criteria. Options: 'KararTarihi', 'YayinTarihi', 'Toplam' (keyword count).") + +class AnayasaReviewedNormInfo(BaseModel): + """Details of a norm reviewed within an AYM decision summary.""" + norm_name_or_number: str = Field("", description="Norm name or number") + article_number: str = Field("", description="Article number") + review_type_and_outcome: str = Field("", description="Review type and outcome") + outcome_reason: str = Field("", description="Outcome reason") + basis_constitution_articles_cited: List[str] = Field(default_factory=list) + postponement_period: str = Field("", description="Postponement period") + +class AnayasaDecisionSummary(BaseModel): + """Model for a single Anayasa Mahkemesi (Norm Denetimi) decision summary from search results.""" + decision_reference_no: str = Field("", description="Decision reference number") + decision_page_url: str = Field("", description="Decision page URL") + keywords_found_count: Optional[int] = Field(0, description="Keywords found count") + application_type_summary: str = Field("", description="Application type summary") + applicant_summary: str = Field("", description="Applicant summary") + decision_outcome_summary: str = Field("", description="Decision outcome summary") + decision_date_summary: str = Field("", description="Decision date summary") + reviewed_norms: List[AnayasaReviewedNormInfo] = Field(default_factory=list) + +class AnayasaSearchResult(BaseModel): + """Model for the overall search result for Anayasa Mahkemesi Norm Denetimi decisions.""" + decisions: List[AnayasaDecisionSummary] + total_records_found: int = Field(0, description="Total records found") + retrieved_page_number: int = Field(1, description="Retrieved page number") + +class AnayasaDocumentMarkdown(BaseModel): + """ + Model for an Anayasa Mahkemesi (Norm Denetimi) decision document, containing a chunk of Markdown content + and pagination information. + """ + source_url: HttpUrl + decision_reference_no_from_page: str = Field("", description="E.K. No parsed from the document page.") + decision_date_from_page: str = Field("", description="Decision date parsed from the document page.") + official_gazette_info_from_page: str = Field("", description="Official Gazette info parsed from the document page.") + markdown_chunk: str = Field("", description="A 5,000 character chunk of the Markdown content.") # Corrected chunk size + current_page: int = Field(description="The current page number of the markdown chunk (1-indexed).") + total_pages: int = Field(description="Total number of pages for the full markdown content.") + is_paginated: bool = Field(description="True if the full markdown content is split into multiple pages.") + + +# --- Models for Anayasa Mahkemesi - Bireysel Başvuru Karar Raporu --- + +class AnayasaBireyselReportSearchRequest(BaseModel): + """Model for Anayasa Mahkemesi (Bireysel Başvuru) 'Karar Arama Raporu' search request.""" + keywords: Optional[List[str]] = Field(default_factory=list, description="Keywords for AND logic (KelimeAra[]).") + page_to_fetch: int = Field(1, ge=1, description="Page number to fetch for the report (page). Default is 1.") + +class AnayasaBireyselReportDecisionDetail(BaseModel): + """Details of a specific right/claim within a Bireysel Başvuru decision summary in a report.""" + hak: str = Field("", description="İhlal edildiği iddia edilen hak (örneğin, Mülkiyet hakkı).") + mudahale_iddiasi: str = Field("", description="İhlale neden olan müdahale iddiası.") + sonuc: str = Field("", description="İnceleme sonucu (örneğin, İhlal, Düşme).") + giderim: str = Field("", description="Kararlaştırılan giderim (örneğin, Yeniden yargılama).") + +class AnayasaBireyselReportDecisionSummary(BaseModel): + """Model for a single Anayasa Mahkemesi (Bireysel Başvuru) decision summary from a 'Karar Arama Raporu'.""" + title: str = Field("", description="Başvurunun başlığı (e.g., 'HASAN DURMUŞ Başvurusuna İlişkin Karar').") + decision_reference_no: str = Field("", description="Başvuru Numarası (e.g., '2019/19126').") + decision_page_url: str = Field("", description="URL to the full decision page.") + decision_type_summary: str = Field("", description="Karar Türü (Başvuru Sonucu) (e.g., 'Esas (İhlal)').") + decision_making_body: str = Field("", description="Kararı Veren Birim (e.g., 'Genel Kurul', 'Birinci Bölüm').") + application_date_summary: str = Field("", description="Başvuru Tarihi (DD/MM/YYYY).") + decision_date_summary: str = Field("", description="Karar Tarihi (DD/MM/YYYY).") + application_subject_summary: str = Field("", description="Başvuru konusunun özeti.") + details: List[AnayasaBireyselReportDecisionDetail] = Field(default_factory=list, description="İncelenen haklar ve sonuçlarına ilişkin detaylar.") + +class AnayasaBireyselReportSearchResult(BaseModel): + """Model for the overall search result for Anayasa Mahkemesi 'Karar Arama Raporu'.""" + decisions: List[AnayasaBireyselReportDecisionSummary] + total_records_found: int = Field(0, description="Raporda bulunan toplam karar sayısı.") + retrieved_page_number: int = Field(description="Alınan rapor sayfa numarası.") + + +class AnayasaBireyselBasvuruDocumentMarkdown(BaseModel): + """ + Model for an Anayasa Mahkemesi (Bireysel Başvuru) decision document, containing a chunk of Markdown content + and pagination information. Fetched from /BB/YYYY/NNNN paths. + """ + source_url: HttpUrl + basvuru_no_from_page: Optional[str] = Field(None, description="Başvuru Numarası (B.No) parsed from the document page.") + karar_tarihi_from_page: Optional[str] = Field(None, description="Decision date parsed from the document page.") + basvuru_tarihi_from_page: Optional[str] = Field(None, description="Application date parsed from the document page.") + karari_veren_birim_from_page: Optional[str] = Field(None, description="Deciding body (Bölüm/Genel Kurul) parsed from the document page.") + karar_turu_from_page: Optional[str] = Field(None, description="Decision type (Başvuru Sonucu) parsed from the document page.") + resmi_gazete_info_from_page: Optional[str] = Field(None, description="Official Gazette info parsed from the document page, if available.") + markdown_chunk: Optional[str] = Field(None, description="A 5,000 character chunk of the Markdown content.") + current_page: int = Field(description="The current page number of the markdown chunk (1-indexed).") + total_pages: int = Field(description="Total number of pages for the full markdown content.") + is_paginated: bool = Field(description="True if the full markdown content is split into multiple pages.") + +# --- End Models for Bireysel Başvuru --- + +# --- Unified Models --- +class AnayasaUnifiedSearchRequest(BaseModel): + """Unified search request for both Norm Denetimi and Bireysel Başvuru.""" + decision_type: Literal["norm_denetimi", "bireysel_basvuru"] = Field(..., description="Decision type: norm_denetimi or bireysel_basvuru") + + # Common parameters + keywords: List[str] = Field(default_factory=list, description="Keywords to search for") + page_to_fetch: int = Field(1, ge=1, le=100, description="Page number to fetch (1-100)") + results_per_page: int = Field(10, ge=1, le=100, description="Results per page (1-100)") + + # Norm Denetimi specific parameters (ignored for bireysel_basvuru) + keywords_all: List[str] = Field(default_factory=list, description="All keywords must be present (norm_denetimi only)") + keywords_any: List[str] = Field(default_factory=list, description="Any of these keywords (norm_denetimi only)") + decision_type_norm: Literal["ALL", "1", "2", "3"] = Field("ALL", description="Decision type for norm denetimi") + application_date_start: str = Field("", description="Application start date (norm_denetimi only)") + application_date_end: str = Field("", description="Application end date (norm_denetimi only)") + + # Bireysel Başvuru specific parameters (ignored for norm_denetimi) + decision_start_date: str = Field("", description="Decision start date (bireysel_basvuru only)") + decision_end_date: str = Field("", description="Decision end date (bireysel_basvuru only)") + norm_type: Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "0"] = Field("ALL", description="Norm type (bireysel_basvuru only)") + subject_category: str = Field("", description="Subject category (bireysel_basvuru only)") + +class AnayasaUnifiedSearchResult(BaseModel): + """Unified search result containing decisions from either system.""" + decision_type: Literal["norm_denetimi", "bireysel_basvuru"] = Field(..., description="Type of decisions returned") + decisions: List[Dict[str, Any]] = Field(default_factory=list, description="Decision list (structure varies by type)") + total_records_found: int = Field(0, description="Total number of records found") + retrieved_page_number: int = Field(1, description="Page number that was retrieved") + +class AnayasaUnifiedDocumentMarkdown(BaseModel): + """Unified document model for both Norm Denetimi and Bireysel Başvuru.""" + decision_type: Literal["norm_denetimi", "bireysel_basvuru"] = Field(..., description="Type of document") + source_url: HttpUrl = Field(..., description="Source URL of the document") + document_data: Dict[str, Any] = Field(default_factory=dict, description="Document content and metadata") + markdown_chunk: Optional[str] = Field(None, description="Markdown content chunk") + current_page: int = Field(1, description="Current page number") + total_pages: int = Field(1, description="Total number of pages") + is_paginated: bool = Field(False, description="Whether document is paginated") \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/unified_client.py b/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/unified_client.py new file mode 100644 index 0000000..1319b85 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/anayasa_mcp_module/unified_client.py @@ -0,0 +1,122 @@ +# anayasa_mcp_module/unified_client.py +# Unified client for both Norm Denetimi and Bireysel Başvuru + +import logging +from typing import Optional +from urllib.parse import urlparse + +from .models import ( + AnayasaUnifiedSearchRequest, + AnayasaUnifiedSearchResult, + AnayasaUnifiedDocumentMarkdown, + # Removed AnayasaDecisionTypeEnum - now using string literals + AnayasaNormDenetimiSearchRequest, + AnayasaBireyselReportSearchRequest +) +from .client import AnayasaMahkemesiApiClient +from .bireysel_client import AnayasaBireyselBasvuruApiClient + +logger = logging.getLogger(__name__) + +class AnayasaUnifiedClient: + """Unified client that handles both Norm Denetimi and Bireysel Başvuru searches.""" + + def __init__(self, request_timeout: float = 60.0): + self.norm_client = AnayasaMahkemesiApiClient(request_timeout) + self.bireysel_client = AnayasaBireyselBasvuruApiClient(request_timeout) + + async def search_unified(self, params: AnayasaUnifiedSearchRequest) -> AnayasaUnifiedSearchResult: + """Unified search that routes to appropriate client based on decision_type.""" + + if params.decision_type == "norm_denetimi": + # Convert to norm denetimi request + norm_params = AnayasaNormDenetimiSearchRequest( + keywords_all=params.keywords_all or params.keywords, + keywords_any=params.keywords_any, + application_type=params.decision_type_norm, + page_to_fetch=params.page_to_fetch, + results_per_page=params.results_per_page + ) + + result = await self.norm_client.search_norm_denetimi_decisions(norm_params) + + # Convert to unified format + decisions_list = [decision.model_dump() for decision in result.decisions] + + return AnayasaUnifiedSearchResult( + decision_type="norm_denetimi", + decisions=decisions_list, + total_records_found=result.total_records_found, + retrieved_page_number=result.retrieved_page_number + ) + + elif params.decision_type == "bireysel_basvuru": + # Convert to bireysel başvuru request + bireysel_params = AnayasaBireyselReportSearchRequest( + keywords=params.keywords, + decision_start_date=params.decision_start_date, + decision_end_date=params.decision_end_date, + norm_type=params.norm_type, + subject_category=params.subject_category, + page_to_fetch=params.page_to_fetch, + results_per_page=params.results_per_page + ) + + result = await self.bireysel_client.search_bireysel_basvuru_report(bireysel_params) + + # Convert to unified format + decisions_list = [decision.model_dump() for decision in result.decisions] + + return AnayasaUnifiedSearchResult( + decision_type="bireysel_basvuru", + decisions=decisions_list, + total_records_found=result.total_records_found, + retrieved_page_number=result.retrieved_page_number + ) + + else: + raise ValueError(f"Unsupported decision type: {params.decision_type}") + + async def get_document_unified(self, document_url: str, page_number: int = 1) -> AnayasaUnifiedDocumentMarkdown: + """Unified document retrieval that auto-detects the appropriate client.""" + + # Auto-detect decision type based on URL + parsed_url = urlparse(document_url) + + if "normkararlarbilgibankasi" in parsed_url.netloc or "/ND/" in document_url: + # Norm Denetimi document + result = await self.norm_client.get_decision_document_as_markdown(document_url, page_number) + + return AnayasaUnifiedDocumentMarkdown( + decision_type="norm_denetimi", + source_url=result.source_url, + document_data=result.model_dump(), + markdown_chunk=result.markdown_chunk, + current_page=result.current_page, + total_pages=result.total_pages, + is_paginated=result.is_paginated + ) + + elif "kararlarbilgibankasi" in parsed_url.netloc or "/BB/" in document_url: + # Bireysel Başvuru document + result = await self.bireysel_client.get_decision_document_as_markdown(document_url, page_number) + + return AnayasaUnifiedDocumentMarkdown( + decision_type="bireysel_basvuru", + source_url=result.source_url, + document_data=result.model_dump(), + markdown_chunk=result.markdown_chunk, + current_page=result.current_page, + total_pages=result.total_pages, + is_paginated=result.is_paginated + ) + + else: + raise ValueError(f"Cannot determine document type from URL: {document_url}") + + async def close_client_session(self): + """Close both client sessions.""" + if hasattr(self.norm_client, 'close_client_session'): + await self.norm_client.close_client_session() + if hasattr(self.bireysel_client, 'close_client_session'): + await self.bireysel_client.close_client_session() \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/asgi_app.py b/saidsurucu-yargi-mcp-f5fa007/asgi_app.py new file mode 100644 index 0000000..ba5a1f4 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/asgi_app.py @@ -0,0 +1,566 @@ +""" +ASGI application for Yargı MCP Server + +This module provides ASGI/HTTP access to the Yargı MCP server, +allowing it to be deployed as a web service with FastAPI wrapper +for Stripe webhook integration. + +Usage: + uvicorn asgi_app:app --host 0.0.0.0 --port 8000 +""" + +import os +import time +import logging +from datetime import datetime, timedelta +from fastapi import FastAPI, Request, HTTPException, Query +from fastapi.responses import JSONResponse, HTMLResponse +from fastapi.exception_handlers import http_exception_handler +from starlette.middleware import Middleware +from starlette.middleware.cors import CORSMiddleware +from starlette.responses import Response +from starlette.requests import Request as StarletteRequest + +# Import the MCP app creator function +from mcp_server_main import create_app + +# Import Stripe webhook router +from stripe_webhook import router as stripe_router + +# Import simplified MCP Auth HTTP adapter +from mcp_auth_http_simple import router as mcp_auth_router + +# OAuth configuration from environment variables +CLERK_ISSUER = os.getenv("CLERK_ISSUER", "https://accounts.yargimcp.com") +BASE_URL = os.getenv("BASE_URL", "https://yargimcp.com") + +# Setup logging +logger = logging.getLogger(__name__) + +# Configure CORS and Auth middleware +cors_origins = os.getenv("ALLOWED_ORIGINS", "*").split(",") + +# Import FastMCP Bearer Auth Provider +from fastmcp.server.auth import BearerAuthProvider +from fastmcp.server.auth.providers.bearer import RSAKeyPair + +# Clerk JWT configuration for Bearer token validation +CLERK_SECRET_KEY = os.getenv("CLERK_SECRET_KEY") +CLERK_ISSUER = os.getenv("CLERK_ISSUER", "https://accounts.yargimcp.com") +CLERK_PUBLISHABLE_KEY = os.getenv("CLERK_PUBLISHABLE_KEY") + +# Configure Bearer token authentication +bearer_auth = None +if CLERK_SECRET_KEY and CLERK_ISSUER: + # Production: Use Clerk JWKS endpoint for token validation + bearer_auth = BearerAuthProvider( + jwks_uri=f"{CLERK_ISSUER}/.well-known/jwks.json", + issuer=CLERK_ISSUER, + algorithm="RS256", + audience=None, # Disable audience validation - Clerk uses different audience format + required_scopes=[] # Disable scope validation - Clerk JWT has ['read', 'search'] + ) + logger.info(f"Bearer auth configured with Clerk JWKS: {CLERK_ISSUER}/.well-known/jwks.json") +else: + # Development: Generate RSA key pair for testing + logger.warning("No Clerk credentials found - using development RSA key pair") + dev_key_pair = RSAKeyPair.generate() + bearer_auth = BearerAuthProvider( + public_key=dev_key_pair.public_key, + issuer="https://dev.yargimcp.com", + audience="dev-mcp-server", + required_scopes=["yargi.read"] + ) + + # Generate a test token for development + dev_token = dev_key_pair.create_token( + subject="dev-user", + issuer="https://dev.yargimcp.com", + audience="dev-mcp-server", + scopes=["yargi.read", "yargi.search"], + expires_in_seconds=3600 * 24 # 24 hours for development + ) + logger.info(f"Development Bearer token: {dev_token}") + +custom_middleware = [ + Middleware( + CORSMiddleware, + allow_origins=cors_origins, + allow_credentials=True, + allow_methods=["GET", "POST", "OPTIONS", "DELETE"], + allow_headers=["Content-Type", "Authorization", "X-Request-ID", "X-Session-ID"], + ), +] + +# Create MCP app with Bearer authentication +mcp_server = create_app(auth=bearer_auth) + +# Add Starlette middleware to FastAPI (not MCP) +# MCP already has Bearer auth, no need for additional middleware on MCP level + +# Create MCP Starlette sub-application with root path - mount will add /mcp prefix +mcp_app = mcp_server.http_app(path="/") + +# Configure JSON encoder for proper Turkish character support +import json +from fastapi.responses import JSONResponse + +class UTF8JSONResponse(JSONResponse): + def __init__(self, content=None, status_code=200, headers=None, **kwargs): + if headers is None: + headers = {} + headers["Content-Type"] = "application/json; charset=utf-8" + super().__init__(content, status_code, headers, **kwargs) + + def render(self, content) -> bytes: + return json.dumps( + content, + ensure_ascii=False, + allow_nan=False, + indent=None, + separators=(",", ":"), + ).encode("utf-8") + +# Create FastAPI wrapper application +app = FastAPI( + title="Yargı MCP Server", + description="MCP server for Turkish legal databases with OAuth authentication", + version="0.1.0", + middleware=custom_middleware, + default_response_class=UTF8JSONResponse # Use UTF-8 JSON encoder +) + +# Add Stripe webhook router to FastAPI +app.include_router(stripe_router, prefix="/api") + +# Add MCP Auth HTTP adapter to FastAPI (handles OAuth endpoints) +app.include_router(mcp_auth_router) + +# Custom 401 exception handler for MCP spec compliance +@app.exception_handler(401) +async def custom_401_handler(request: Request, exc: HTTPException): + """Custom 401 handler that adds WWW-Authenticate header as required by MCP spec""" + response = await http_exception_handler(request, exc) + + # Add WWW-Authenticate header pointing to protected resource metadata + # as required by RFC 9728 Section 5.1 and MCP Authorization spec + response.headers["WWW-Authenticate"] = ( + 'Bearer ' + 'error="invalid_token", ' + 'error_description="The access token is missing or invalid", ' + f'resource="{BASE_URL}/.well-known/oauth-protected-resource"' + ) + + return response + +# FastAPI health check endpoint - BEFORE mounting MCP app +@app.get("/health") +async def health_check(): + """Health check endpoint for monitoring""" + return JSONResponse({ + "status": "healthy", + "service": "Yargı MCP Server", + "version": "0.1.0", + "tools_count": len(mcp_server._tool_manager._tools), + "auth_enabled": os.getenv("ENABLE_AUTH", "false").lower() == "true" + }) + +# Add explicit redirect for /mcp to /mcp/ with method preservation +@app.api_route("/mcp", methods=["GET", "POST", "HEAD", "OPTIONS"]) +async def redirect_to_slash(request: Request): + """Redirect /mcp to /mcp/ preserving HTTP method with 308""" + from fastapi.responses import RedirectResponse + return RedirectResponse(url="/mcp/", status_code=308) + +# Mount MCP app at /mcp/ with trailing slash +app.mount("/mcp/", mcp_app) + +# Set the lifespan context after mounting +app.router.lifespan_context = mcp_app.lifespan + + +# SSE transport deprecated - removed + +# FastAPI root endpoint +@app.get("/") +async def root(): + """Root endpoint with service information""" + return JSONResponse({ + "service": "Yargı MCP Server", + "description": "MCP server for Turkish legal databases with OAuth authentication", + "endpoints": { + "mcp": "/mcp", + "health": "/health", + "status": "/status", + "stripe_webhook": "/api/stripe/webhook", + "oauth_login": "/auth/login", + "oauth_callback": "/auth/callback", + "oauth_google": "/auth/google/login", + "user_info": "/auth/user" + }, + "transports": { + "http": "/mcp" + }, + "supported_databases": [ + "Yargıtay (Court of Cassation)", + "Danıştay (Council of State)", + "Emsal (Precedent)", + "Uyuşmazlık Mahkemesi (Court of Jurisdictional Disputes)", + "Anayasa Mahkemesi (Constitutional Court)", + "Kamu İhale Kurulu (Public Procurement Authority)", + "Rekabet Kurumu (Competition Authority)", + "Sayıştay (Court of Accounts)", + "Bedesten API (Multiple courts)" + ], + "authentication": { + "enabled": os.getenv("ENABLE_AUTH", "false").lower() == "true", + "type": "OAuth 2.0 via Clerk", + "issuer": os.getenv("CLERK_ISSUER", "https://clerk.accounts.dev"), + "providers": ["google"], + "flow": "authorization_code" + } + }) + +# OAuth 2.0 Authorization Server Metadata proxy (for MCP clients that can't reach Clerk directly) +# MCP Auth Toolkit expects this to be under /mcp/.well-known/oauth-authorization-server +@app.get("/mcp/.well-known/oauth-authorization-server") +async def oauth_authorization_server(): + """OAuth 2.0 Authorization Server Metadata proxy to Clerk - MCP Auth Toolkit standard location""" + return JSONResponse({ + "issuer": BASE_URL, + "authorization_endpoint": "https://yargimcp.com/mcp-callback", + "token_endpoint": f"{BASE_URL}/token", + "jwks_uri": f"{CLERK_ISSUER}/.well-known/jwks.json", + "response_types_supported": ["code"], + "grant_types_supported": ["authorization_code", "refresh_token"], + "token_endpoint_auth_methods_supported": ["client_secret_basic", "none"], + "scopes_supported": ["read", "search", "openid", "profile", "email"], + "subject_types_supported": ["public"], + "id_token_signing_alg_values_supported": ["RS256"], + "claims_supported": ["sub", "iss", "aud", "exp", "iat", "email", "name"], + "code_challenge_methods_supported": ["S256"], + "service_documentation": f"{BASE_URL}/mcp", + "registration_endpoint": f"{BASE_URL}/register", + "resource_documentation": f"{BASE_URL}/mcp" + }) + +# Claude AI MCP specific endpoint format +@app.get("/.well-known/oauth-authorization-server/mcp") +async def oauth_authorization_server_mcp_suffix(): + """OAuth 2.0 Authorization Server Metadata - Claude AI MCP specific format""" + return JSONResponse({ + "issuer": BASE_URL, + "authorization_endpoint": "https://yargimcp.com/mcp-callback", + "token_endpoint": f"{BASE_URL}/token", + "jwks_uri": f"{CLERK_ISSUER}/.well-known/jwks.json", + "response_types_supported": ["code"], + "grant_types_supported": ["authorization_code", "refresh_token"], + "token_endpoint_auth_methods_supported": ["client_secret_basic", "none"], + "scopes_supported": ["read", "search", "openid", "profile", "email"], + "subject_types_supported": ["public"], + "id_token_signing_alg_values_supported": ["RS256"], + "claims_supported": ["sub", "iss", "aud", "exp", "iat", "email", "name"], + "code_challenge_methods_supported": ["S256"], + "service_documentation": f"{BASE_URL}/mcp", + "registration_endpoint": f"{BASE_URL}/register", + "resource_documentation": f"{BASE_URL}/mcp" + }) + +@app.get("/.well-known/oauth-protected-resource/mcp") +async def oauth_protected_resource_mcp_suffix(): + """OAuth 2.0 Protected Resource Metadata - Claude AI MCP specific format""" + return JSONResponse({ + "resource": BASE_URL, + "authorization_servers": [ + BASE_URL + ], + "scopes_supported": ["read", "search"], + "bearer_methods_supported": ["header"], + "resource_documentation": f"{BASE_URL}/mcp", + "resource_policy_uri": f"{BASE_URL}/privacy" + }) + +# Keep root level for compatibility with some MCP clients +@app.get("/.well-known/oauth-authorization-server") +async def oauth_authorization_server_root(): + """OAuth 2.0 Authorization Server Metadata proxy to Clerk - root level for compatibility""" + return JSONResponse({ + "issuer": BASE_URL, + "authorization_endpoint": "https://yargimcp.com/mcp-callback", + "token_endpoint": f"{BASE_URL}/token", + "jwks_uri": f"{CLERK_ISSUER}/.well-known/jwks.json", + "response_types_supported": ["code"], + "grant_types_supported": ["authorization_code", "refresh_token"], + "token_endpoint_auth_methods_supported": ["client_secret_basic", "none"], + "scopes_supported": ["read", "search", "openid", "profile", "email"], + "subject_types_supported": ["public"], + "id_token_signing_alg_values_supported": ["RS256"], + "claims_supported": ["sub", "iss", "aud", "exp", "iat", "email", "name"], + "code_challenge_methods_supported": ["S256"], + "service_documentation": f"{BASE_URL}/mcp", + "registration_endpoint": f"{BASE_URL}/register", + "resource_documentation": f"{BASE_URL}/mcp" + }) + +# Note: GET /mcp is handled by the mounted MCP app itself +# This prevents 405 Method Not Allowed errors on POST requests + +# OAuth 2.0 Protected Resource Metadata (RFC 9728) - MCP Spec Required +@app.get("/.well-known/oauth-protected-resource") +async def oauth_protected_resource(): + """OAuth 2.0 Protected Resource Metadata as required by MCP spec""" + return JSONResponse({ + "resource": BASE_URL, + "authorization_servers": [ + BASE_URL + ], + "scopes_supported": ["read", "search"], + "bearer_methods_supported": ["header"], + "resource_documentation": f"{BASE_URL}/mcp", + "resource_policy_uri": f"{BASE_URL}/privacy" + }) + +# Standard well-known discovery endpoint +@app.get("/.well-known/mcp") +async def well_known_mcp(): + """Standard MCP discovery endpoint""" + return JSONResponse({ + "mcp_server": { + "name": "Yargı MCP Server", + "version": "0.1.0", + "endpoint": f"{BASE_URL}/mcp", + "authentication": { + "type": "oauth2", + "authorization_url": f"{BASE_URL}/auth/login", + "scopes": ["read", "search"] + }, + "capabilities": ["tools", "resources"], + "tools_count": len(mcp_server._tool_manager._tools) + } + }) + +# MCP Discovery endpoint for ChatGPT integration +@app.get("/mcp/discovery") +async def mcp_discovery(): + """MCP Discovery endpoint for ChatGPT and other MCP clients""" + return JSONResponse({ + "name": "Yargı MCP Server", + "description": "MCP server for Turkish legal databases", + "version": "0.1.0", + "protocol": "mcp", + "transport": "http", + "endpoint": "/mcp", + "authentication": { + "type": "oauth2", + "authorization_url": "/auth/login", + "token_url": "/auth/callback", + "scopes": ["read", "search"], + "provider": "clerk" + }, + "capabilities": { + "tools": True, + "resources": True, + "prompts": False + }, + "tools_count": len(mcp_server._tool_manager._tools), + "contact": { + "url": BASE_URL, + "email": "support@yargi-mcp.dev" + } + }) + +# FastAPI status endpoint +@app.get("/status") +async def status(): + """Status endpoint with detailed information""" + tools = [] + for tool in mcp_server._tool_manager._tools.values(): + tools.append({ + "name": tool.name, + "description": tool.description[:100] + "..." if len(tool.description) > 100 else tool.description + }) + + return JSONResponse({ + "status": "operational", + "tools": tools, + "total_tools": len(tools), + "transport": "streamable_http", + "architecture": "FastAPI wrapper + MCP Starlette sub-app", + "auth_status": "enabled" if os.getenv("ENABLE_AUTH", "false").lower() == "true" else "disabled" + }) + +# Note: JWT token validation is now handled entirely by Clerk +# All authentication flows use Clerk JWT tokens directly + +async def validate_clerk_session(request: Request, clerk_token: str = None) -> str: + """Validate Clerk session from cookies or JWT token and return user_id""" + logger.info(f"Validating Clerk session - token provided: {bool(clerk_token)}") + + try: + # Try to import Clerk SDK + from clerk_backend_api import Clerk + clerk = Clerk(bearer_auth=os.getenv("CLERK_SECRET_KEY")) + + # Try JWT token first (from URL parameter) + if clerk_token: + logger.info("Validating Clerk JWT token from URL parameter") + try: + # Extract session_id from JWT token and verify with Clerk + import jwt + decoded_token = jwt.decode(clerk_token, options={"verify_signature": False}) + session_id = decoded_token.get("sid") # Use standard JWT 'sid' claim + + if session_id: + # Verify with Clerk using session_id + session = clerk.sessions.verify(session_id=session_id, token=clerk_token) + user_id = session.user_id if session else None + + if user_id: + logger.info(f"JWT token validation successful - user_id: {user_id}") + return user_id + else: + logger.error("JWT token validation failed - no user_id in session") + else: + logger.error("No session_id found in JWT token") + except Exception as e: + logger.error(f"JWT token validation failed: {str(e)}") + # Fall through to cookie validation + + # Fallback to cookie validation + logger.info("Attempting cookie-based session validation") + clerk_session = request.cookies.get("__session") + if not clerk_session: + logger.error("No Clerk session cookie found") + raise HTTPException(status_code=401, detail="No Clerk session found") + + # Validate session with Clerk + session = clerk.sessions.verify_session(clerk_session) + logger.info(f"Cookie session validation successful - user_id: {session.user_id}") + return session.user_id + + except ImportError: + # Fallback for development without Clerk SDK + logger.warning("Clerk SDK not available - using development fallback") + return "dev_user_123" + except Exception as e: + logger.error(f"Session validation failed: {str(e)}") + raise HTTPException(status_code=401, detail=f"Session validation failed: {str(e)}") + +# MCP OAuth Callback Endpoint +@app.get("/auth/mcp-callback") +async def mcp_oauth_callback(request: Request, clerk_token: str = Query(None)): + """Handle OAuth callback for MCP token generation""" + logger.info(f"MCP OAuth callback - clerk_token provided: {bool(clerk_token)}") + + try: + # Validate Clerk session with JWT token support + user_id = await validate_clerk_session(request, clerk_token) + logger.info(f"User authenticated successfully - user_id: {user_id}") + + # Use the Clerk JWT token directly (no need to generate custom token) + logger.info("User authenticated successfully via Clerk") + + # Return success response + return HTMLResponse(f""" + + + MCP Connection Successful + + + +

✅ MCP Connection Successful!

+

Your Yargı MCP integration is now active.

+
+ Authentication:
+ Use your Clerk JWT token directly with Bearer authentication +
+

You can now close this window and return to your MCP client.

+ + + + """) + + except HTTPException as e: + logger.error(f"MCP OAuth callback failed: {e.detail}") + return HTMLResponse(f""" + + + MCP Connection Failed + + + +

❌ MCP Connection Failed

+

{e.detail}

+
+ Debug Info:
+ Clerk Token: {'✅ Provided' if clerk_token else '❌ Missing'}
+ Error: {e.detail}
+ Status: {e.status_code} +
+

Please try again or contact support.

+
Return to Sign In + + + """, status_code=e.status_code) + except Exception as e: + logger.error(f"Unexpected error in MCP OAuth callback: {str(e)}") + return HTMLResponse(f""" + + + MCP Connection Error + + + +

❌ Unexpected Error

+

An unexpected error occurred during authentication.

+

Error: {str(e)}

+ Return to Sign In + + + """, status_code=500) + +# OAuth2 Token Endpoint - Now uses Clerk JWT tokens directly +@app.post("/auth/mcp-token") +async def mcp_token_endpoint(request: Request): + """OAuth2 token endpoint for MCP clients - returns Clerk JWT token info""" + try: + # Validate Clerk session + user_id = await validate_clerk_session(request) + + return JSONResponse({ + "message": "Use your Clerk JWT token directly with Bearer authentication", + "token_type": "Bearer", + "scope": "yargi.read", + "user_id": user_id, + "instructions": "Include 'Authorization: Bearer YOUR_CLERK_JWT_TOKEN' in your requests" + }) + except HTTPException as e: + return JSONResponse( + status_code=e.status_code, + content={"error": "invalid_request", "error_description": e.detail} + ) + +# Note: Only HTTP transport supported - SSE transport deprecated + +# Export for uvicorn +__all__ = ["app"] \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/bddk_mcp_module/__init__.py b/saidsurucu-yargi-mcp-f5fa007/bddk_mcp_module/__init__.py new file mode 100644 index 0000000..7a2195d --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/bddk_mcp_module/__init__.py @@ -0,0 +1,17 @@ +# bddk_mcp_module/__init__.py + +from .client import BddkApiClient +from .models import ( + BddkSearchRequest, + BddkDecisionSummary, + BddkSearchResult, + BddkDocumentMarkdown +) + +__all__ = [ + "BddkApiClient", + "BddkSearchRequest", + "BddkDecisionSummary", + "BddkSearchResult", + "BddkDocumentMarkdown" +] \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/bddk_mcp_module/client.py b/saidsurucu-yargi-mcp-f5fa007/bddk_mcp_module/client.py new file mode 100644 index 0000000..158e082 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/bddk_mcp_module/client.py @@ -0,0 +1,247 @@ +# bddk_mcp_module/client.py + +import httpx +from typing import List, Optional, Dict, Any +import logging +import os +import re +import io +import math +from urllib.parse import urlparse +from markitdown import MarkItDown + +from .models import ( + BddkSearchRequest, + BddkDecisionSummary, + BddkSearchResult, + BddkDocumentMarkdown +) + +logger = logging.getLogger(__name__) +if not logger.hasHandlers(): + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + +class BddkApiClient: + """ + API client for searching and retrieving BDDK (Banking Regulation Authority) decisions + using Tavily Search API for discovery and direct HTTP requests for content retrieval. + """ + + TAVILY_API_URL = "https://api.tavily.com/search" + BDDK_BASE_URL = "https://www.bddk.org.tr" + DOCUMENT_URL_TEMPLATE = "https://www.bddk.org.tr/Mevzuat/DokumanGetir/{document_id}" + DOCUMENT_MARKDOWN_CHUNK_SIZE = 5000 # Character limit per page + + def __init__(self, request_timeout: float = 60.0): + """Initialize the BDDK API client.""" + self.tavily_api_key = os.getenv("TAVILY_API_KEY") + if not self.tavily_api_key: + # Fallback to development token + self.tavily_api_key = "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" + logger.info("Using fallback Tavily API token (development token)") + else: + logger.info("Using Tavily API key from environment variable") + + self.http_client = httpx.AsyncClient( + headers={ + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36" + }, + timeout=httpx.Timeout(request_timeout) + ) + self.markitdown = MarkItDown() + + async def close_client_session(self): + """Close the HTTP client session.""" + await self.http_client.aclose() + logger.info("BddkApiClient: HTTP client session closed.") + + def _extract_document_id(self, url: str) -> Optional[str]: + """Extract document ID from BDDK URL.""" + # Primary pattern: https://www.bddk.org.tr/Mevzuat/DokumanGetir/310 + match = re.search(r'/DokumanGetir/(\d+)', url) + if match: + return match.group(1) + + # Alternative patterns for different BDDK URL formats + # Pattern: /Liste/55 -> use as document ID + match = re.search(r'/Liste/(\d+)', url) + if match: + return match.group(1) + + # Pattern: /EkGetir/13?ekId=381 -> use ekId as document ID + match = re.search(r'ekId=(\d+)', url) + if match: + return match.group(1) + + return None + + async def search_decisions( + self, + request: BddkSearchRequest + ) -> BddkSearchResult: + """ + Search for BDDK decisions using Tavily API. + + Args: + request: Search request parameters + + Returns: + BddkSearchResult with matching decisions + """ + try: + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.tavily_api_key}" + } + + # Tavily API request - enhanced for BDDK decision documents + query = f"{request.keywords} \"Karar Sayısı\"" + payload = { + "query": query, + "country": "turkey", + "include_domains": ["https://www.bddk.org.tr/Mevzuat/DokumanGetir"], + "max_results": request.pageSize, + "search_depth": "advanced" + } + + # Calculate offset for pagination + if request.page > 1: + # Tavily doesn't have direct pagination, so we'll need to handle this + # For now, we'll just return empty for pages > 1 + logger.warning(f"Tavily API doesn't support pagination. Page {request.page} requested.") + + response = await self.http_client.post( + self.TAVILY_API_URL, + json=payload, + headers=headers + ) + response.raise_for_status() + + data = response.json() + + # Log raw Tavily response for debugging + logger.info(f"Tavily returned {len(data.get('results', []))} results") + + # Convert Tavily results to our format + decisions = [] + for result in data.get("results", []): + # Extract document ID from URL + url = result.get("url", "") + logger.debug(f"Processing URL: {url}") + doc_id = self._extract_document_id(url) + if doc_id: + decision = BddkDecisionSummary( + title=result.get("title", "").replace("[PDF] ", "").strip(), + document_id=doc_id, + content=result.get("content", "")[:500] # Limit content length + ) + decisions.append(decision) + logger.debug(f"Added decision: {decision.title} (ID: {doc_id})") + else: + logger.warning(f"Could not extract document ID from URL: {url}") + + return BddkSearchResult( + decisions=decisions, + total_results=len(data.get("results", [])), + page=request.page, + pageSize=request.pageSize + ) + + except httpx.HTTPStatusError as e: + logger.error(f"HTTP error searching BDDK decisions: {e}") + if e.response.status_code == 401: + raise Exception("Tavily API authentication failed. Check API key.") + raise Exception(f"Failed to search BDDK decisions: {str(e)}") + except Exception as e: + logger.error(f"Error searching BDDK decisions: {e}") + raise Exception(f"Failed to search BDDK decisions: {str(e)}") + + async def get_document_markdown( + self, + document_id: str, + page_number: int = 1 + ) -> BddkDocumentMarkdown: + """ + Retrieve a BDDK document and convert it to Markdown format. + + Args: + document_id: BDDK document ID (e.g., '310') + page_number: Page number for paginated content (1-indexed) + + Returns: + BddkDocumentMarkdown with paginated content + """ + try: + # Try different URL patterns for BDDK documents + potential_urls = [ + f"https://www.bddk.org.tr/Mevzuat/DokumanGetir/{document_id}", + f"https://www.bddk.org.tr/Mevzuat/Liste/{document_id}", + f"https://www.bddk.org.tr/KurumHakkinda/EkGetir/13?ekId={document_id}", + f"https://www.bddk.org.tr/KurumHakkinda/EkGetir/5?ekId={document_id}" + ] + + document_url = None + response = None + + # Try each URL pattern until one works + for url in potential_urls: + try: + logger.info(f"Trying BDDK document URL: {url}") + response = await self.http_client.get( + url, + follow_redirects=True + ) + response.raise_for_status() + document_url = url + break + except httpx.HTTPStatusError: + continue + + if not response or not document_url: + raise Exception(f"Could not find document with ID {document_id}") + + logger.info(f"Successfully fetched BDDK document from: {document_url}") + + # Determine content type + content_type = response.headers.get("content-type", "").lower() + + # Convert to Markdown based on content type + if "pdf" in content_type: + # Handle PDF documents + pdf_stream = io.BytesIO(response.content) + result = self.markitdown.convert_stream(pdf_stream, file_extension=".pdf") + markdown_content = result.text_content + else: + # Handle HTML documents + html_stream = io.BytesIO(response.content) + result = self.markitdown.convert_stream(html_stream, file_extension=".html") + markdown_content = result.text_content + + # Clean up the markdown content + markdown_content = markdown_content.strip() + + # Calculate pagination + total_length = len(markdown_content) + total_pages = math.ceil(total_length / self.DOCUMENT_MARKDOWN_CHUNK_SIZE) + + # Extract the requested page + start_idx = (page_number - 1) * self.DOCUMENT_MARKDOWN_CHUNK_SIZE + end_idx = start_idx + self.DOCUMENT_MARKDOWN_CHUNK_SIZE + page_content = markdown_content[start_idx:end_idx] + + return BddkDocumentMarkdown( + document_id=document_id, + markdown_content=page_content, + page_number=page_number, + total_pages=total_pages + ) + + except httpx.HTTPStatusError as e: + logger.error(f"HTTP error fetching BDDK document {document_id}: {e}") + raise Exception(f"Failed to fetch BDDK document: {str(e)}") + except Exception as e: + logger.error(f"Error processing BDDK document {document_id}: {e}") + raise Exception(f"Failed to process BDDK document: {str(e)}") \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/bddk_mcp_module/models.py b/saidsurucu-yargi-mcp-f5fa007/bddk_mcp_module/models.py new file mode 100644 index 0000000..c945f26 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/bddk_mcp_module/models.py @@ -0,0 +1,43 @@ +# bddk_mcp_module/models.py + +from pydantic import BaseModel, Field +from typing import List, Optional + +class BddkSearchRequest(BaseModel): + """ + Request model for searching BDDK decisions via Tavily API. + + BDDK (Bankacılık Düzenleme ve Denetleme Kurumu) is Turkey's Banking + Regulation and Supervision Agency responsible for banking licenses, + electronic money institutions, and financial regulations. + """ + keywords: str = Field(..., description="Search keywords in Turkish") + page: int = Field(1, ge=1, description="Page number (1-indexed)") + pageSize: int = Field(10, ge=1, le=50, description="Results per page (1-50)") + +class BddkDecisionSummary(BaseModel): + """Summary of a BDDK decision from search results.""" + title: str = Field(..., description="Decision title") + document_id: str = Field(..., description="BDDK document ID (e.g., '310')") + content: str = Field(..., description="Decision summary/excerpt") + +class BddkSearchResult(BaseModel): + """Response model for BDDK decision search results.""" + decisions: List[BddkDecisionSummary] = Field( + default_factory=list, + description="List of matching BDDK decisions" + ) + total_results: int = Field(0, description="Total number of results") + page: int = Field(1, description="Current page number") + pageSize: int = Field(10, description="Results per page") + +class BddkDocumentMarkdown(BaseModel): + """ + BDDK decision document converted to Markdown format. + + Supports paginated content for long documents (5000 chars per page). + """ + document_id: str = Field(..., description="BDDK document ID") + markdown_content: str = Field("", description="Document content in Markdown") + page_number: int = Field(1, description="Current page number") + total_pages: int = Field(1, description="Total number of pages") \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/__init__.py b/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/__init__.py new file mode 100644 index 0000000..90b95d3 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/__init__.py @@ -0,0 +1 @@ +# bedesten_mcp_module/__init__.py \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/client.py b/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/client.py new file mode 100644 index 0000000..ad6f1a8 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/client.py @@ -0,0 +1,181 @@ +# bedesten_mcp_module/client.py + +import httpx +import base64 +from typing import Optional +import logging +from markitdown import MarkItDown +import io + +from .models import ( + BedestenSearchRequest, BedestenSearchResponse, + BedestenDocumentRequest, BedestenDocumentResponse, + BedestenDocumentMarkdown, BedestenDocumentRequestData +) +from .enums import get_full_birim_adi + +logger = logging.getLogger(__name__) + +class BedestenApiClient: + """ + API Client for Bedesten (bedesten.adalet.gov.tr) - Alternative legal decision search system. + Currently used for Yargıtay decisions, but can be extended for other court types. + """ + BASE_URL = "https://bedesten.adalet.gov.tr" + SEARCH_ENDPOINT = "/emsal-karar/searchDocuments" + DOCUMENT_ENDPOINT = "/emsal-karar/getDocumentContent" + + def __init__(self, request_timeout: float = 60.0): + self.http_client = httpx.AsyncClient( + base_url=self.BASE_URL, + headers={ + "Accept": "*/*", + "Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7", + "AdaletApplicationName": "UyapMevzuat", + "Content-Type": "application/json; charset=utf-8", + "Origin": "https://mevzuat.adalet.gov.tr", + "Referer": "https://mevzuat.adalet.gov.tr/", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-site", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36" + }, + timeout=request_timeout + ) + + async def search_documents(self, search_request: BedestenSearchRequest) -> BedestenSearchResponse: + """ + Search for documents using Bedesten API. + Currently supports: YARGITAYKARARI, DANISTAYKARARI, YERELHUKMAHKARARI, etc. + """ + logger.info(f"BedestenApiClient: Searching documents with phrase: {search_request.data.phrase}") + + # Map abbreviated birimAdi to full Turkish name before sending to API + original_birim_adi = search_request.data.birimAdi + mapped_birim_adi = get_full_birim_adi(original_birim_adi) + search_request.data.birimAdi = mapped_birim_adi + if original_birim_adi != "ALL": + logger.info(f"BedestenApiClient: Mapped birimAdi '{original_birim_adi}' to '{mapped_birim_adi}'") + + try: + # Create request dict and remove birimAdi if empty + request_dict = search_request.model_dump() + if not request_dict["data"]["birimAdi"]: # Remove if empty string + del request_dict["data"]["birimAdi"] + + response = await self.http_client.post( + self.SEARCH_ENDPOINT, + json=request_dict + ) + response.raise_for_status() + response_json = response.json() + + # Parse and return the response + return BedestenSearchResponse(**response_json) + + except httpx.RequestError as e: + logger.error(f"BedestenApiClient: HTTP request error during search: {e}") + raise + except Exception as e: + logger.error(f"BedestenApiClient: Error processing search response: {e}") + raise + + async def get_document_as_markdown(self, document_id: str) -> BedestenDocumentMarkdown: + """ + Get document content and convert to markdown. + Handles both HTML (text/html) and PDF (application/pdf) content types. + """ + logger.info(f"BedestenApiClient: Fetching document for markdown conversion (ID: {document_id})") + + try: + # Prepare request + doc_request = BedestenDocumentRequest( + data=BedestenDocumentRequestData(documentId=document_id) + ) + + # Get document + response = await self.http_client.post( + self.DOCUMENT_ENDPOINT, + json=doc_request.model_dump() + ) + response.raise_for_status() + response_json = response.json() + doc_response = BedestenDocumentResponse(**response_json) + + # Decode base64 content + content_bytes = base64.b64decode(doc_response.data.content) + mime_type = doc_response.data.mimeType + + logger.info(f"BedestenApiClient: Document mime type: {mime_type}") + + # Convert to markdown based on mime type + if mime_type == "text/html": + html_content = content_bytes.decode('utf-8') + markdown_content = self._convert_html_to_markdown(html_content) + elif mime_type == "application/pdf": + markdown_content = self._convert_pdf_to_markdown(content_bytes) + else: + logger.warning(f"Unsupported mime type: {mime_type}") + markdown_content = f"Unsupported content type: {mime_type}. Unable to convert to markdown." + + return BedestenDocumentMarkdown( + documentId=document_id, + markdown_content=markdown_content, + source_url=f"{self.BASE_URL}/document/{document_id}", + mime_type=mime_type + ) + + except httpx.RequestError as e: + logger.error(f"BedestenApiClient: HTTP error fetching document {document_id}: {e}") + raise + except Exception as e: + logger.error(f"BedestenApiClient: Error processing document {document_id}: {e}") + raise + + def _convert_html_to_markdown(self, html_content: str) -> Optional[str]: + """Convert HTML to Markdown using MarkItDown""" + if not html_content: + return None + + try: + # Convert HTML string to bytes and create BytesIO stream + html_bytes = html_content.encode('utf-8') + html_stream = io.BytesIO(html_bytes) + + # Pass BytesIO stream to MarkItDown to avoid temp file creation + md_converter = MarkItDown() + result = md_converter.convert(html_stream) + markdown_content = result.text_content + + logger.info("Successfully converted HTML to Markdown") + return markdown_content + + except Exception as e: + logger.error(f"Error converting HTML to Markdown: {e}") + return f"Error converting HTML content: {str(e)}" + + def _convert_pdf_to_markdown(self, pdf_bytes: bytes) -> Optional[str]: + """Convert PDF to Markdown using MarkItDown""" + if not pdf_bytes: + return None + + try: + # Create BytesIO stream from PDF bytes + pdf_stream = io.BytesIO(pdf_bytes) + + # Pass BytesIO stream to MarkItDown to avoid temp file creation + md_converter = MarkItDown() + result = md_converter.convert(pdf_stream) + markdown_content = result.text_content + + logger.info("Successfully converted PDF to Markdown") + return markdown_content + + except Exception as e: + logger.error(f"Error converting PDF to Markdown: {e}") + return f"Error converting PDF content: {str(e)}. The document may be corrupted or in an unsupported format." + + async def close_client_session(self): + """Close HTTP client session""" + await self.http_client.aclose() + logger.info("BedestenApiClient: HTTP client session closed.") \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/enums.py b/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/enums.py new file mode 100644 index 0000000..7cef054 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/enums.py @@ -0,0 +1,113 @@ +# bedesten_mcp_module/enums.py + +from typing import Literal + +# Unified compressed enum for both Yargıtay and Danıştay chambers +BirimAdiEnum = Literal[ + "ALL", # All chambers + + # Yargıtay (Court of Cassation) - Civil Chambers + "H1", "H2", "H3", "H4", "H5", "H6", "H7", "H8", "H9", "H10", + "H11", "H12", "H13", "H14", "H15", "H16", "H17", "H18", "H19", "H20", + "H21", "H22", "H23", + + # Yargıtay - Criminal Chambers + "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10", + "C11", "C12", "C13", "C14", "C15", "C16", "C17", "C18", "C19", "C20", + "C21", "C22", "C23", + + # Yargıtay - Councils and Assemblies + "HGK", # Hukuk Genel Kurulu + "CGK", # Ceza Genel Kurulu + "BGK", # Büyük Genel Kurulu + "HBK", # Hukuk Daireleri Başkanlar Kurulu + "CBK", # Ceza Daireleri Başkanlar Kurulu + + # Danıştay (Council of State) - Chambers + "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10", + "D11", "D12", "D13", "D14", "D15", "D16", "D17", + + # Danıştay - Councils and Boards + "DBGK", # Büyük Gen.Kur. (Grand General Assembly) + "IDDK", # İdare Dava Daireleri Kurulu + "VDDK", # Vergi Dava Daireleri Kurulu + "IBK", # İçtihatları Birleştirme Kurulu + "IIK", # İdari İşler Kurulu + "DBK", # Başkanlar Kurulu + + # Military High Administrative Court + "AYIM", # Askeri Yüksek İdare Mahkemesi + "AYIMDK", # Askeri Yüksek İdare Mahkemesi Daireler Kurulu + "AYIMB", # Askeri Yüksek İdare Mahkemesi Başsavcılığı + "AYIM1", # Askeri Yüksek İdare Mahkemesi 1. Daire + "AYIM2", # Askeri Yüksek İdare Mahkemesi 2. Daire + "AYIM3" # Askeri Yüksek İdare Mahkemesi 3. Daire +] + +# Mapping from abbreviated values to full Turkish API values +BIRIM_ADI_MAPPING = { + "ALL": None, # Will be handled specially in client + + # Yargıtay Civil Chambers (1-23) + "H1": "1. Hukuk Dairesi", "H2": "2. Hukuk Dairesi", "H3": "3. Hukuk Dairesi", + "H4": "4. Hukuk Dairesi", "H5": "5. Hukuk Dairesi", "H6": "6. Hukuk Dairesi", + "H7": "7. Hukuk Dairesi", "H8": "8. Hukuk Dairesi", "H9": "9. Hukuk Dairesi", + "H10": "10. Hukuk Dairesi", "H11": "11. Hukuk Dairesi", "H12": "12. Hukuk Dairesi", + "H13": "13. Hukuk Dairesi", "H14": "14. Hukuk Dairesi", "H15": "15. Hukuk Dairesi", + "H16": "16. Hukuk Dairesi", "H17": "17. Hukuk Dairesi", "H18": "18. Hukuk Dairesi", + "H19": "19. Hukuk Dairesi", "H20": "20. Hukuk Dairesi", "H21": "21. Hukuk Dairesi", + "H22": "22. Hukuk Dairesi", "H23": "23. Hukuk Dairesi", + + # Yargıtay Criminal Chambers (1-23) + "C1": "1. Ceza Dairesi", "C2": "2. Ceza Dairesi", "C3": "3. Ceza Dairesi", + "C4": "4. Ceza Dairesi", "C5": "5. Ceza Dairesi", "C6": "6. Ceza Dairesi", + "C7": "7. Ceza Dairesi", "C8": "8. Ceza Dairesi", "C9": "9. Ceza Dairesi", + "C10": "10. Ceza Dairesi", "C11": "11. Ceza Dairesi", "C12": "12. Ceza Dairesi", + "C13": "13. Ceza Dairesi", "C14": "14. Ceza Dairesi", "C15": "15. Ceza Dairesi", + "C16": "16. Ceza Dairesi", "C17": "17. Ceza Dairesi", "C18": "18. Ceza Dairesi", + "C19": "19. Ceza Dairesi", "C20": "20. Ceza Dairesi", "C21": "21. Ceza Dairesi", + "C22": "22. Ceza Dairesi", "C23": "23. Ceza Dairesi", + + # Yargıtay Councils and Assemblies + "HGK": "Hukuk Genel Kurulu", + "CGK": "Ceza Genel Kurulu", + "BGK": "Büyük Genel Kurulu", + "HBK": "Hukuk Daireleri Başkanlar Kurulu", + "CBK": "Ceza Daireleri Başkanlar Kurulu", + + # Danıştay Chambers (1-17) + "D1": "1. Daire", "D2": "2. Daire", "D3": "3. Daire", "D4": "4. Daire", + "D5": "5. Daire", "D6": "6. Daire", "D7": "7. Daire", "D8": "8. Daire", + "D9": "9. Daire", "D10": "10. Daire", "D11": "11. Daire", "D12": "12. Daire", + "D13": "13. Daire", "D14": "14. Daire", "D15": "15. Daire", "D16": "16. Daire", + "D17": "17. Daire", + + # Danıştay Councils and Boards + "DBGK": "Büyük Gen.Kur.", + "IDDK": "İdare Dava Daireleri Kurulu", + "VDDK": "Vergi Dava Daireleri Kurulu", + "IBK": "İçtihatları Birleştirme Kurulu", + "IIK": "İdari İşler Kurulu", + "DBK": "Başkanlar Kurulu", + + # Military High Administrative Court + "AYIM": "Askeri Yüksek İdare Mahkemesi", + "AYIMDK": "Askeri Yüksek İdare Mahkemesi Daireler Kurulu", + "AYIMB": "Askeri Yüksek İdare Mahkemesi Başsavcılığı", + "AYIM1": "Askeri Yüksek İdare Mahkemesi 1. Daire", + "AYIM2": "Askeri Yüksek İdare Mahkemesi 2. Daire", + "AYIM3": "Askeri Yüksek İdare Mahkemesi 3. Daire" +} + +# Helper function to get full Turkish name from abbreviated value +def get_full_birim_adi(abbreviated_value: str) -> str: + """Convert abbreviated birimAdi value to full Turkish name for API calls.""" + if abbreviated_value == "ALL" or not abbreviated_value: + return "" # Empty string for ALL or None + + return BIRIM_ADI_MAPPING.get(abbreviated_value, abbreviated_value) + +# Helper function to validate abbreviated value +def is_valid_birim_adi(abbreviated_value: str) -> bool: + """Check if abbreviated birimAdi value is valid.""" + return abbreviated_value in BIRIM_ADI_MAPPING \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/models.py b/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/models.py new file mode 100644 index 0000000..6c6c24d --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/bedesten_mcp_module/models.py @@ -0,0 +1,91 @@ +# bedesten_mcp_module/models.py + +from pydantic import BaseModel, Field +from typing import List, Optional, Dict, Any, Literal, Union +from datetime import datetime + +# Import compressed BirimAdiEnum for chamber filtering +from .enums import BirimAdiEnum + +# Court Type Options for Unified Search +BedestenCourtTypeEnum = Literal[ + "YARGITAYKARARI", # Yargıtay (Court of Cassation) + "DANISTAYKARAR", # Danıştay (Council of State) + "YERELHUKUK", # Local Civil Courts + "ISTINAFHUKUK", # Civil Courts of Appeals + "KYB" # Extraordinary Appeals (Kanun Yararına Bozma) +] + +# Search Request Models +class BedestenSearchData(BaseModel): + pageSize: int = Field(..., description="Results per page (1-10)") + pageNumber: int = Field(..., description="Page number (1-indexed)") + itemTypeList: List[str] = Field(..., description="Court type filter (YARGITAYKARARI/DANISTAYKARAR/YERELHUKUK/ISTINAFHUKUK/KYB)") + phrase: str = Field(..., description="Search phrase. Supports: 'word', \"exact phrase\", +required, -exclude, AND/OR/NOT operators. No wildcards or regex.") + birimAdi: BirimAdiEnum = Field("ALL", description=""" + Chamber filter (optional). Abbreviated values with Turkish names: + • Yargıtay: H1-H23 (1-23. Hukuk Dairesi), C1-C23 (1-23. Ceza Dairesi), HGK (Hukuk Genel Kurulu), CGK (Ceza Genel Kurulu), BGK (Büyük Genel Kurulu), HBK (Hukuk Daireleri Başkanlar Kurulu), CBK (Ceza Daireleri Başkanlar Kurulu) + • Danıştay: D1-D17 (1-17. Daire), DBGK (Büyük Gen.Kur.), IDDK (İdare Dava Daireleri Kurulu), VDDK (Vergi Dava Daireleri Kurulu), IBK (İçtihatları Birleştirme Kurulu), IIK (İdari İşler Kurulu), DBK (Başkanlar Kurulu), AYIM (Askeri Yüksek İdare Mahkemesi), AYIM1-3 (Askeri Yüksek İdare Mahkemesi 1-3. Daire) + """) + kararTarihiStart: Optional[str] = Field(None, description="Start date (ISO 8601 format)") + kararTarihiEnd: Optional[str] = Field(None, description="End date (ISO 8601 format)") + sortFields: List[str] = Field(default=["KARAR_TARIHI"], description="Sort fields") + sortDirection: str = Field(default="desc", description="Sort direction (asc/desc)") + +class BedestenSearchRequest(BaseModel): + data: BedestenSearchData + applicationName: str = "UyapMevzuat" + paging: bool = True + +# Search Response Models +class BedestenItemType(BaseModel): + name: str + description: str + +class BedestenDecisionEntry(BaseModel): + documentId: str + itemType: BedestenItemType + birimId: Optional[str] = None + birimAdi: Optional[str] + esasNoYil: Optional[int] = None + esasNoSira: Optional[int] = None + kararNoYil: Optional[int] = None + kararNoSira: Optional[int] = None + kararTuru: Optional[str] = None + kararTarihi: str + kararTarihiStr: str + kesinlesmeDurumu: Optional[str] = None + kararNo: Optional[str] = None + esasNo: Optional[str] = None + +class BedestenSearchDataResponse(BaseModel): + emsalKararList: List[BedestenDecisionEntry] + total: int + start: int + +class BedestenSearchResponse(BaseModel): + data: Optional[BedestenSearchDataResponse] + metadata: Dict[str, Any] + +# Document Request/Response Models +class BedestenDocumentRequestData(BaseModel): + documentId: str + +class BedestenDocumentRequest(BaseModel): + data: BedestenDocumentRequestData + applicationName: str = "UyapMevzuat" + +class BedestenDocumentData(BaseModel): + content: str # Base64 encoded HTML or PDF + mimeType: str + version: int + +class BedestenDocumentResponse(BaseModel): + data: BedestenDocumentData + metadata: Dict[str, Any] + +class BedestenDocumentMarkdown(BaseModel): + documentId: str = Field(..., description="The document ID (Belge Kimliği) from Bedesten") + markdown_content: Optional[str] = Field(None, description="The decision content (Karar İçeriği) converted to Markdown") + source_url: str = Field(..., description="The source URL (Kaynak URL) of the document") + mime_type: Optional[str] = Field(None, description="Original content type (İçerik Türü) (text/html or application/pdf)") \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/check_response_format.py b/saidsurucu-yargi-mcp-f5fa007/check_response_format.py new file mode 100644 index 0000000..1e814c4 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/check_response_format.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +from fastmcp import Client +from mcp_server_main import app +import json +import asyncio + +async def check_response_format(): + client = Client(app) + async with client: + result = await client.call_tool('search_bedesten_unified', { + 'phrase': 'mülkiyet', + 'court_types': ['YARGITAYKARARI'], + 'birimAdi': 'H1', + 'pageSize': 3 + }) + if result and result.content: + data = json.loads(result.content[0].text) + print('Response keys:', list(data.keys())) + print('Sample response:', json.dumps(data, indent=2, ensure_ascii=False)[:500]) + +asyncio.run(check_response_format()) \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/danistay_mcp_module/__init__.py b/saidsurucu-yargi-mcp-f5fa007/danistay_mcp_module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/saidsurucu-yargi-mcp-f5fa007/danistay_mcp_module/client.py b/saidsurucu-yargi-mcp-f5fa007/danistay_mcp_module/client.py new file mode 100644 index 0000000..0a713ff --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/danistay_mcp_module/client.py @@ -0,0 +1,192 @@ +# danistay_mcp_module/client.py + +import httpx +from bs4 import BeautifulSoup +from typing import Dict, Any, List, Optional +import logging +import html +import re +import io +from markitdown import MarkItDown + +from .models import ( + DanistayKeywordSearchRequest, + DanistayDetailedSearchRequest, + DanistayApiResponse, + DanistayDocumentMarkdown, + DanistayKeywordSearchRequestData, + DanistayDetailedSearchRequestData +) + +logger = logging.getLogger(__name__) +if not logger.hasHandlers(): + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') + +class DanistayApiClient: + BASE_URL = "https://karararama.danistay.gov.tr" + KEYWORD_SEARCH_ENDPOINT = "/aramalist" + DETAILED_SEARCH_ENDPOINT = "/aramadetaylist" + DOCUMENT_ENDPOINT = "/getDokuman" + + def __init__(self, request_timeout: float = 30.0): + self.http_client = httpx.AsyncClient( + base_url=self.BASE_URL, + headers={ + "Content-Type": "application/json; charset=UTF-8", # Arama endpoint'leri için + "Accept": "application/json, text/plain, */*", # Arama endpoint'leri için + "X-Requested-With": "XMLHttpRequest", + }, + timeout=request_timeout, + verify=False + ) + + def _prepare_keywords_for_api(self, keywords: List[str]) -> List[str]: + return ['"' + k.strip('"') + '"' for k in keywords if k and k.strip()] + + async def search_keyword_decisions( + self, + params: DanistayKeywordSearchRequest + ) -> DanistayApiResponse: + data_for_payload = DanistayKeywordSearchRequestData( + andKelimeler=self._prepare_keywords_for_api(params.andKelimeler), + orKelimeler=self._prepare_keywords_for_api(params.orKelimeler), + notAndKelimeler=self._prepare_keywords_for_api(params.notAndKelimeler), + notOrKelimeler=self._prepare_keywords_for_api(params.notOrKelimeler), + pageSize=params.pageSize, + pageNumber=params.pageNumber + ) + final_payload = {"data": data_for_payload.model_dump(exclude_none=True)} + logger.info(f"DanistayApiClient: Performing KEYWORD search via {self.KEYWORD_SEARCH_ENDPOINT} with payload: {final_payload}") + return await self._execute_api_search(self.KEYWORD_SEARCH_ENDPOINT, final_payload) + + async def search_detailed_decisions( + self, + params: DanistayDetailedSearchRequest + ) -> DanistayApiResponse: + data_for_payload = DanistayDetailedSearchRequestData( + daire=params.daire or "", + esasYil=params.esasYil or "", + esasIlkSiraNo=params.esasIlkSiraNo or "", + esasSonSiraNo=params.esasSonSiraNo or "", + kararYil=params.kararYil or "", + kararIlkSiraNo=params.kararIlkSiraNo or "", + kararSonSiraNo=params.kararSonSiraNo or "", + baslangicTarihi=params.baslangicTarihi or "", + bitisTarihi=params.bitisTarihi or "", + mevzuatNumarasi=params.mevzuatNumarasi or "", + mevzuatAdi=params.mevzuatAdi or "", + madde=params.madde or "", + siralama="1", + siralamaDirection="desc", + pageSize=params.pageSize, + pageNumber=params.pageNumber + ) + # Create request dict and remove empty string fields to avoid API issues + payload_dict = data_for_payload.model_dump(exclude_defaults=False, exclude_none=False) + # Remove empty string fields that might cause API issues + cleaned_payload = {k: v for k, v in payload_dict.items() if v != ""} + final_payload = {"data": cleaned_payload} + logger.info(f"DanistayApiClient: Performing DETAILED search via {self.DETAILED_SEARCH_ENDPOINT} with payload: {final_payload}") + return await self._execute_api_search(self.DETAILED_SEARCH_ENDPOINT, final_payload) + + async def _execute_api_search(self, endpoint: str, payload: Dict) -> DanistayApiResponse: + try: + response = await self.http_client.post(endpoint, json=payload) + response.raise_for_status() + response_json_data = response.json() + logger.debug(f"DanistayApiClient: Raw API response from {endpoint}: {response_json_data}") + api_response_parsed = DanistayApiResponse(**response_json_data) + if api_response_parsed.data and api_response_parsed.data.data: + for decision_item in api_response_parsed.data.data: + if decision_item.id: + decision_item.document_url = f"{self.BASE_URL}{self.DOCUMENT_ENDPOINT}?id={decision_item.id}" + return api_response_parsed + except httpx.RequestError as e: + logger.error(f"DanistayApiClient: HTTP request error during search to {endpoint}: {e}") + raise + except Exception as e: + logger.error(f"DanistayApiClient: Error processing or validating search response from {endpoint}: {e}") + raise + + def _convert_html_to_markdown_danistay(self, direct_html_content: str) -> Optional[str]: + """ + Converts direct HTML content (assumed from Danıştay /getDokuman) to Markdown. + """ + if not direct_html_content: + return None + + # Basic HTML unescaping and fixing common escaped characters + # This step might be less critical if MarkItDown handles them, but good for pre-cleaning. + processed_html = html.unescape(direct_html_content) + processed_html = processed_html.replace('\\"', '"') # If any such JS-escaped strings exist + # Danistay HTML doesn't seem to have \\r\\n etc. from the example, but keeping for robustness + processed_html = processed_html.replace('\\r\\n', '\n').replace('\\n', '\n').replace('\\t', '\t') + + # For simplicity and to leverage MarkItDown's capability to handle full docs, + # we pass the pre-processed full HTML. + html_input_for_markdown = processed_html + + markdown_text = None + try: + # Convert HTML string to bytes and create BytesIO stream + html_bytes = html_input_for_markdown.encode('utf-8') + html_stream = io.BytesIO(html_bytes) + + # Pass BytesIO stream to MarkItDown to avoid temp file creation + md_converter = MarkItDown() + conversion_result = md_converter.convert(html_stream) + markdown_text = conversion_result.text_content + logger.info("DanistayApiClient: HTML to Markdown conversion successful.") + except Exception as e: + logger.error(f"DanistayApiClient: Error during MarkItDown HTML to Markdown conversion: {e}") + + return markdown_text + + async def get_decision_document_as_markdown(self, id: str) -> DanistayDocumentMarkdown: + """ + Retrieves a specific Danıştay decision by ID and returns its content as Markdown. + The /getDokuman endpoint for Danıştay requires arananKelime parameter. + """ + # Add required arananKelime parameter - using empty string as minimum requirement + document_api_url = f"{self.DOCUMENT_ENDPOINT}?id={id}&arananKelime=" + source_url = f"{self.BASE_URL}{document_api_url}" + logger.info(f"DanistayApiClient: Fetching Danistay document for Markdown (ID: {id}) from {source_url}") + + try: + # For direct HTML response, we might want different headers if the API is sensitive, + # but httpx usually handles basic GET requests well. + response = await self.http_client.get(document_api_url) + response.raise_for_status() + + # Danıştay /getDokuman directly returns HTML text + html_content_from_api = response.text + + if not isinstance(html_content_from_api, str) or not html_content_from_api.strip(): + logger.warning(f"DanistayApiClient: Received empty or non-string HTML content for ID {id}.") + # Return with None markdown_content if HTML is effectively empty + return DanistayDocumentMarkdown( + id=id, + markdown_content=None, + source_url=source_url + ) + + markdown_content = self._convert_html_to_markdown_danistay(html_content_from_api) + + return DanistayDocumentMarkdown( + id=id, + markdown_content=markdown_content, + source_url=source_url + ) + except httpx.RequestError as e: + logger.error(f"DanistayApiClient: HTTP error fetching Danistay document (ID: {id}): {e}") + raise + # Removed ValueError for JSON as Danistay /getDokuman returns direct HTML + except Exception as e: # Catches other errors like MarkItDown issues if they propagate + logger.error(f"DanistayApiClient: General error processing Danistay document (ID: {id}): {e}") + raise + + async def close_client_session(self): + """Closes the HTTPX client session.""" + if self.http_client and not self.http_client.is_closed: + await self.http_client.aclose() + logger.info("DanistayApiClient: HTTP client session closed.") \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/danistay_mcp_module/models.py b/saidsurucu-yargi-mcp-f5fa007/danistay_mcp_module/models.py new file mode 100644 index 0000000..8c657a9 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/danistay_mcp_module/models.py @@ -0,0 +1,112 @@ +# danistay_mcp_module/models.py + +from pydantic import BaseModel, Field, HttpUrl, ConfigDict +from typing import List, Optional, Dict, Any + +class DanistayBaseSearchRequest(BaseModel): + """Base model for common search parameters for Danistay.""" + pageSize: int = Field(default=10, ge=1, le=10) + pageNumber: int = Field(default=1, ge=1) + # siralama and siralamaDirection are part of detailed search, not necessarily keyword search + # as per user's provided payloads. + +class DanistayKeywordSearchRequestData(BaseModel): + """Internal data model for the keyword search payload's 'data' field.""" + andKelimeler: List[str] = Field(default_factory=list) + orKelimeler: List[str] = Field(default_factory=list) + notAndKelimeler: List[str] = Field(default_factory=list) + notOrKelimeler: List[str] = Field(default_factory=list) + pageSize: int + pageNumber: int + +class DanistayKeywordSearchRequest(BaseModel): # This is the model the MCP tool will accept + """Model for keyword-based search request for Danistay.""" + andKelimeler: List[str] = Field(default_factory=list, description="AND keywords") + orKelimeler: List[str] = Field(default_factory=list, description="OR keywords") + notAndKelimeler: List[str] = Field(default_factory=list, description="NOT AND keywords") + notOrKelimeler: List[str] = Field(default_factory=list, description="NOT OR keywords") + pageSize: int = Field(default=10, ge=1, le=10) + pageNumber: int = Field(default=1, ge=1) + +class DanistayDetailedSearchRequestData(BaseModel): # Internal data model for detailed search payload + """Internal data model for the detailed search payload's 'data' field.""" + daire: Optional[str] = "" # API expects empty string for None + esasYil: Optional[str] = "" + esasIlkSiraNo: Optional[str] = "" + esasSonSiraNo: Optional[str] = "" + kararYil: Optional[str] = "" + kararIlkSiraNo: Optional[str] = "" + kararSonSiraNo: Optional[str] = "" + baslangicTarihi: Optional[str] = "" + bitisTarihi: Optional[str] = "" + mevzuatNumarasi: Optional[str] = "" + mevzuatAdi: Optional[str] = "" + madde: Optional[str] = "" + siralama: str # Seems mandatory in detailed search payload + siralamaDirection: str # Seems mandatory + pageSize: int + pageNumber: int + # Note: 'arananKelime' is not in the detailed search payload example provided by user. + # If it can be included, it should be added here. + +class DanistayDetailedSearchRequest(DanistayBaseSearchRequest): # MCP tool will accept this + """Model for detailed search request for Danistay.""" + daire: str = Field("", description="Chamber") + esasYil: str = Field("", description="Case year") + esasIlkSiraNo: str = Field("", description="Start case no") + esasSonSiraNo: str = Field("", description="End case no") + kararYil: str = Field("", description="Decision year") + kararIlkSiraNo: str = Field("", description="Start decision no") + kararSonSiraNo: str = Field("", description="End decision no") + baslangicTarihi: str = Field("", description="Start date") + bitisTarihi: str = Field("", description="End date") + mevzuatNumarasi: str = Field("", description="Law number") + mevzuatAdi: str = Field("", description="Law name") + madde: str = Field("", description="Article") + # Add a general keyword field if detailed search also supports it + # arananKelime: Optional[str] = Field(None, description="General keyword for detailed search.") + + +class DanistayApiDecisionEntry(BaseModel): + """Model for an individual decision entry from the Danistay API search response. + Based on user-provided response samples for both keyword and detailed search. + """ + id: str + # The API response for keyword search uses "daireKurul", detailed search example uses "daire". + # We use an alias to handle both and map to a consistent field name "chamber". + chamber: str = Field("", alias="daire", description="Chamber") + esasNo: str = Field("", description="Case number") + kararNo: str = Field("", description="Decision number") + kararTarihi: str = Field("", description="Decision date") + arananKelime: str = Field("", description="Keyword") + # index: Optional[int] = None # Present in response, can be added if needed by MCP tool + # siraNo: Optional[int] = None # Present in detailed response, can be added + + document_url: Optional[HttpUrl] = Field(None, description="Document URL") + + model_config = ConfigDict(populate_by_name=True, extra='ignore') # Important for alias to work and ignore extra fields + +class DanistayApiResponseInnerData(BaseModel): + """Model for the inner 'data' object in the Danistay API search response.""" + data: List[DanistayApiDecisionEntry] + recordsTotal: int + recordsFiltered: int + draw: int = Field(0, description="Draw counter") + +class DanistayApiResponse(BaseModel): + """Model for the complete search response from the Danistay API.""" + data: Optional[DanistayApiResponseInnerData] = Field(None, description="Response data, can be null when no results found") + metadata: Optional[Dict[str, Any]] = Field(None, description="Optional metadata (Meta Veri) from API.") + +class DanistayDocumentMarkdown(BaseModel): + """Model for a Danistay decision document, containing only Markdown content.""" + id: str + markdown_content: str = Field("", description="The decision content (Karar İçeriği) converted to Markdown.") + source_url: HttpUrl + +class CompactDanistaySearchResult(BaseModel): + """A compact search result model for the MCP tool to return.""" + decisions: List[DanistayApiDecisionEntry] + total_records: int + requested_page: int + page_size: int \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/docker-compose.yml b/saidsurucu-yargi-mcp-f5fa007/docker-compose.yml new file mode 100644 index 0000000..d36eee7 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/docker-compose.yml @@ -0,0 +1,66 @@ +version: '3.8' + +services: + yargi-mcp: + build: . + image: yargi-mcp:latest + container_name: yargi-mcp-server + ports: + - "${PORT:-8000}:8000" + environment: + - HOST=0.0.0.0 + - PORT=8000 + - LOG_LEVEL=${LOG_LEVEL:-info} + - ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-*} + - API_TOKEN=${API_TOKEN:-} + - PYTHONUNBUFFERED=1 + volumes: + # Mount logs directory + - ./logs:/app/logs + # Mount .env file if it exists + - ./.env:/app/.env:ro + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8000/health').raise_for_status()"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + networks: + - yargi-network + + # Optional: Nginx reverse proxy + nginx: + image: nginx:alpine + container_name: yargi-nginx + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf:ro + - ./ssl:/etc/nginx/ssl:ro + depends_on: + - yargi-mcp + networks: + - yargi-network + profiles: + - production + + # Optional: Redis for caching (future enhancement) + redis: + image: redis:alpine + container_name: yargi-redis + command: redis-server --appendonly yes + volumes: + - redis-data:/data + networks: + - yargi-network + profiles: + - with-cache + +networks: + yargi-network: + driver: bridge + +volumes: + redis-data: \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/docs/DEPLOYMENT.md b/saidsurucu-yargi-mcp-f5fa007/docs/DEPLOYMENT.md new file mode 100644 index 0000000..db5f361 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/docs/DEPLOYMENT.md @@ -0,0 +1,428 @@ +# Yargı MCP Server Dağıtım Rehberi + +Bu rehber, Yargı MCP Server'ın ASGI web servisi olarak çeşitli dağıtım seçeneklerini kapsar. + +## İçindekiler + +- [Hızlı Başlangıç](#hızlı-başlangıç) +- [Yerel Geliştirme](#yerel-geliştirme) +- [Production Dağıtımı](#production-dağıtımı) +- [Cloud Dağıtımı](#cloud-dağıtımı) +- [Docker Dağıtımı](#docker-dağıtımı) +- [Güvenlik Hususları](#güvenlik-hususları) +- [İzleme](#izleme) + +## Hızlı Başlangıç + +### 1. Bağımlılıkları Yükleyin + +```bash +# ASGI sunucusu için uvicorn yükleyin +pip install uvicorn + +# Veya tüm bağımlılıklarla birlikte yükleyin +pip install -e . +pip install uvicorn +``` + +### 2. Sunucuyu Çalıştırın + +```bash +# Temel başlatma +python run_asgi.py + +# Veya doğrudan uvicorn ile +uvicorn asgi_app:app --host 0.0.0.0 --port 8000 +``` + +Sunucu şu adreslerde kullanılabilir olacak: +- MCP Endpoint: `http://localhost:8000/mcp/` +- Sağlık Kontrolü: `http://localhost:8000/health` +- API Durumu: `http://localhost:8000/status` + +## Yerel Geliştirme + +### Otomatik Yeniden Yükleme ile Geliştirme Sunucusu + +```bash +python run_asgi.py --reload --log-level debug +``` + +### FastAPI Entegrasyonunu Kullanma + +Ek REST API endpoint'leri için: + +```bash +uvicorn fastapi_app:app --reload +``` + +Bu şunları sağlar: +- `/docs` adresinde interaktif API dokümantasyonu +- `/api/tools` adresinde araç listesi +- `/api/databases` adresinde veritabanı bilgileri + +### Ortam Değişkenleri + +`.env.example` dosyasını temel alarak bir `.env` dosyası oluşturun: + +```bash +cp .env.example .env +``` + +Temel değişkenler: +- `HOST`: Sunucu host adresi (varsayılan: 127.0.0.1) +- `PORT`: Sunucu portu (varsayılan: 8000) +- `ALLOWED_ORIGINS`: CORS kökenleri (virgülle ayrılmış) +- `LOG_LEVEL`: Log seviyesi (debug, info, warning, error) + +## Production Dağıtımı + +### 1. Uvicorn ile Çoklu Worker Kullanımı + +```bash +python run_asgi.py --host 0.0.0.0 --port 8000 --workers 4 +``` + +### 2. Gunicorn Kullanımı + +```bash +pip install gunicorn +gunicorn asgi_app:app -w 4 -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 +``` + +### 3. Nginx Reverse Proxy ile + +1. Nginx'i yükleyin +2. Sağlanan `nginx.conf` dosyasını kullanın: + +```bash +sudo cp nginx.conf /etc/nginx/sites-available/yargi-mcp +sudo ln -s /etc/nginx/sites-available/yargi-mcp /etc/nginx/sites-enabled/ +sudo nginx -t +sudo systemctl reload nginx +``` + +### 4. Systemd Servisi + +`/etc/systemd/system/yargi-mcp.service` dosyasını oluşturun: + +```ini +[Unit] +Description=Yargı MCP Server +After=network.target + +[Service] +Type=exec +User=www-data +WorkingDirectory=/opt/yargi-mcp +Environment="PATH=/opt/yargi-mcp/venv/bin" +ExecStart=/opt/yargi-mcp/venv/bin/uvicorn asgi_app:app --host 0.0.0.0 --port 8000 --workers 4 +Restart=on-failure +RestartSec=5 + +[Install] +WantedBy=multi-user.target +``` + +Etkinleştirin ve başlatın: + +```bash +sudo systemctl enable yargi-mcp +sudo systemctl start yargi-mcp +``` + +## Cloud Dağıtımı + +### Heroku + +1. `Procfile` oluşturun: +``` +web: uvicorn asgi_app:app --host 0.0.0.0 --port $PORT +``` + +2. Dağıtın: +```bash +heroku create uygulama-isminiz +git push heroku main +``` + +### Railway + +1. `railway.json` ekleyin: +```json +{ + "build": { + "builder": "NIXPACKS" + }, + "deploy": { + "startCommand": "uvicorn asgi_app:app --host 0.0.0.0 --port $PORT" + } +} +``` + +2. Railway CLI veya GitHub entegrasyonu ile dağıtın + +### Google Cloud Run + +1. Container oluşturun: +```bash +docker build -t yargi-mcp . +docker tag yargi-mcp gcr.io/PROJE_ADINIZ/yargi-mcp +docker push gcr.io/PROJE_ADINIZ/yargi-mcp +``` + +2. Dağıtın: +```bash +gcloud run deploy yargi-mcp \ + --image gcr.io/PROJE_ADINIZ/yargi-mcp \ + --platform managed \ + --region us-central1 \ + --allow-unauthenticated +``` + +### AWS Lambda (Mangum kullanarak) + +1. Mangum'u yükleyin: +```bash +pip install mangum +``` + +2. `lambda_handler.py` oluşturun: +```python +from mangum import Mangum +from asgi_app import app + +handler = Mangum(app, lifespan="off") +``` + +3. AWS SAM veya Serverless Framework kullanarak dağıtın + +## Docker Dağıtımı + +### Tek Container + +```bash +# Oluşturun +docker build -t yargi-mcp . + +# Çalıştırın +docker run -p 8000:8000 --env-file .env yargi-mcp +``` + +### Docker Compose + +```bash +# Geliştirme +docker-compose up + +# Nginx ile Production +docker-compose --profile production up + +# Redis önbellekleme ile +docker-compose --profile with-cache up +``` + +### Kubernetes + +Deployment YAML oluşturun: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: yargi-mcp +spec: + replicas: 3 + selector: + matchLabels: + app: yargi-mcp + template: + metadata: + labels: + app: yargi-mcp + spec: + containers: + - name: yargi-mcp + image: yargi-mcp:latest + ports: + - containerPort: 8000 + env: + - name: HOST + value: "0.0.0.0" + - name: PORT + value: "8000" + livenessProbe: + httpGet: + path: /health + port: 8000 + initialDelaySeconds: 10 + periodSeconds: 30 +--- +apiVersion: v1 +kind: Service +metadata: + name: yargi-mcp-service +spec: + selector: + app: yargi-mcp + ports: + - port: 80 + targetPort: 8000 + type: LoadBalancer +``` + +## Güvenlik Hususları + +### 1. Kimlik Doğrulama + +`API_TOKEN` ortam değişkenini ayarlayarak token kimlik doğrulamasını etkinleştirin: + +```bash +export API_TOKEN=gizli-token-degeri +``` + +Ardından isteklere ekleyin: +```bash +curl -H "Authorization: Bearer gizli-token-degeri" http://localhost:8000/api/tools +``` + +### 2. HTTPS/SSL + +Production için her zaman HTTPS kullanın: + +1. SSL sertifikası edinin (Let's Encrypt vb.) +2. Nginx veya cloud sağlayıcıda yapılandırın +3. `ALLOWED_ORIGINS` değerini https:// kullanacak şekilde güncelleyin + +### 3. Rate Limiting (Hız Sınırlama) + +Sağlanan Nginx yapılandırması rate limiting içerir: +- API endpoint'leri: 10 istek/saniye +- MCP endpoint: 100 istek/saniye + +### 4. CORS Yapılandırması + +Production için belirli kaynaklara izin verin: + +```bash +ALLOWED_ORIGINS=https://app.sizindomain.com,https://www.sizindomain.com +``` + +## İzleme + +### Sağlık Kontrolleri + +`/health` endpoint'ini izleyin: + +```bash +curl http://localhost:8000/health +``` + +Yanıt: +```json +{ + "status": "healthy", + "timestamp": "2024-12-26T10:00:00", + "uptime_seconds": 3600, + "tools_operational": true +} +``` + +### Loglama + +Ortam değişkeni ile log seviyesini yapılandırın: + +```bash +LOG_LEVEL=info # veya debug, warning, error +``` + +Loglar şuraya yazılır: +- Konsol (stdout) +- `logs/mcp_server.log` dosyası + +### Metrikler (Opsiyonel) + +OpenTelemetry desteği için: + +```bash +pip install opentelemetry-instrumentation-fastapi +``` + +Ortam değişkenlerini ayarlayın: +```bash +OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 +OTEL_SERVICE_NAME=yargi-mcp-server +``` + +## Sorun Giderme + +### Port Zaten Kullanımda + +```bash +# 8000 portunu kullanan işlemi bulun +lsof -i :8000 + +# İşlemi sonlandırın +kill -9 +``` + +### İzin Hataları + +Dosya izinlerinin doğru olduğundan emin olun: + +```bash +chmod +x run_asgi.py +chown -R www-data:www-data /opt/yargi-mcp +``` + +### Bellek Sorunları + +Büyük belge işleme için worker belleğini artırın: + +```bash +# systemd servisinde +Environment="PYTHONMALLOC=malloc" +LimitNOFILE=65536 +``` + +### Zaman Aşımı Sorunları + +Zaman aşımlarını ayarlayın: +1. Uvicorn: `--timeout-keep-alive 75` +2. Nginx: `proxy_read_timeout 300s;` +3. Cloud sağlayıcılar: Platform özel zaman aşımı ayarlarını kontrol edin + +## Performans Ayarlama + +### 1. Worker İşlemleri + +- Geliştirme: 1 worker +- Production: CPU çekirdeği başına 2-4 worker + +### 2. Bağlantı Havuzlama + +Sunucu varsayılan olarak httpx ile bağlantı havuzlama kullanır. + +### 3. Önbellekleme (Gelecek Geliştirme) + +Redis önbellekleme docker-compose ile etkinleştirilebilir: + +```bash +docker-compose --profile with-cache up +``` + +### 4. Veritabanı Zaman Aşımları + +`.env` dosyasında veritabanı başına zaman aşımlarını ayarlayın: + +```bash +YARGITAY_TIMEOUT=60 +DANISTAY_TIMEOUT=60 +ANAYASA_TIMEOUT=90 +``` + +## Destek + +Sorunlar ve sorular için: +- GitHub Issues: https://github.com/saidsurucu/yargi-mcp/issues +- Dokümantasyon: README.md dosyasına bakın \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/emsal_mcp_module/__init__.py b/saidsurucu-yargi-mcp-f5fa007/emsal_mcp_module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/saidsurucu-yargi-mcp-f5fa007/emsal_mcp_module/client.py b/saidsurucu-yargi-mcp-f5fa007/emsal_mcp_module/client.py new file mode 100644 index 0000000..235d515 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/emsal_mcp_module/client.py @@ -0,0 +1,177 @@ +# emsal_mcp_module/client.py + +import httpx +# from bs4 import BeautifulSoup # Uncomment if needed for advanced HTML pre-processing +from typing import Dict, Any, List, Optional +import logging +import html +import re +import io +from markitdown import MarkItDown + +from .models import ( + EmsalSearchRequest, + EmsalDetailedSearchRequestData, + EmsalApiResponse, + EmsalDocumentMarkdown +) + +logger = logging.getLogger(__name__) +if not logger.hasHandlers(): + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') + +class EmsalApiClient: + """API Client for Emsal (UYAP Precedent Decision) search system.""" + BASE_URL = "https://emsal.uyap.gov.tr" + DETAILED_SEARCH_ENDPOINT = "/aramadetaylist" + DOCUMENT_ENDPOINT = "/getDokuman" + + def __init__(self, request_timeout: float = 30.0): + self.http_client = httpx.AsyncClient( + base_url=self.BASE_URL, + headers={ + "Content-Type": "application/json; charset=UTF-8", + "Accept": "application/json, text/plain, */*", + "X-Requested-With": "XMLHttpRequest", + }, + timeout=request_timeout, + verify=False # As per user's original FastAPI code + ) + + async def search_detailed_decisions( + self, + params: EmsalSearchRequest + ) -> EmsalApiResponse: + """Performs a detailed search for Emsal decisions.""" + + data_for_api_payload = EmsalDetailedSearchRequestData( + arananKelime=params.keyword or "", + Bam_Hukuk_Mahkemeleri=params.selected_bam_civil_court, # Uses alias "Bam Hukuk Mahkemeleri" + Hukuk_Mahkemeleri=params.selected_civil_court, # Uses alias "Hukuk Mahkemeleri" + birimHukukMah="+".join(params.selected_regional_civil_chambers) if params.selected_regional_civil_chambers else "", + esasYil=params.case_year_esas or "", + esasIlkSiraNo=params.case_start_seq_esas or "", + esasSonSiraNo=params.case_end_seq_esas or "", + kararYil=params.decision_year_karar or "", + kararIlkSiraNo=params.decision_start_seq_karar or "", + kararSonSiraNo=params.decision_end_seq_karar or "", + baslangicTarihi=params.start_date or "", + bitisTarihi=params.end_date or "", + siralama=params.sort_criteria, + siralamaDirection=params.sort_direction, + pageSize=params.page_size, + pageNumber=params.page_number + ) + + # Create request dict and remove empty string fields to avoid API issues + payload_dict = data_for_api_payload.model_dump(by_alias=True, exclude_none=True) + # Remove empty string fields that might cause API issues + cleaned_payload = {k: v for k, v in payload_dict.items() if v != ""} + final_payload = {"data": cleaned_payload} + + logger.info(f"EmsalApiClient: Performing DETAILED search with payload: {final_payload}") + return await self._execute_api_search(self.DETAILED_SEARCH_ENDPOINT, final_payload) + + async def _execute_api_search(self, endpoint: str, payload: Dict) -> EmsalApiResponse: + """Helper method to execute search POST request and process response for Emsal.""" + try: + response = await self.http_client.post(endpoint, json=payload) + response.raise_for_status() + response_json_data = response.json() + logger.debug(f"EmsalApiClient: Raw API response from {endpoint}: {response_json_data}") + + api_response_parsed = EmsalApiResponse(**response_json_data) + + if api_response_parsed.data and api_response_parsed.data.data: + for decision_item in api_response_parsed.data.data: + if decision_item.id: + decision_item.document_url = f"{self.BASE_URL}{self.DOCUMENT_ENDPOINT}?id={decision_item.id}" + + return api_response_parsed + except httpx.RequestError as e: + logger.error(f"EmsalApiClient: HTTP request error during Emsal search to {endpoint}: {e}") + raise + except Exception as e: + logger.error(f"EmsalApiClient: Error processing or validating Emsal search response from {endpoint}: {e}") + raise + + def _clean_html_and_convert_to_markdown_emsal(self, html_content_from_api_data_field: str) -> Optional[str]: + """ + Cleans HTML (from Emsal API 'data' field containing HTML string) + and converts it to Markdown using MarkItDown. + This assumes Emsal /getDokuman response is JSON with HTML in "data" field, + similar to Yargitay and the last Emsal /getDokuman example. + """ + if not html_content_from_api_data_field: + return None + + # Basic HTML unescaping and fixing common escaped characters + # Based on user's original fix_html_content in app/routers/emsal.py + content = html.unescape(html_content_from_api_data_field) + content = content.replace('\\"', '"') + content = content.replace('\\r\\n', '\n') + content = content.replace('\\n', '\n') + content = content.replace('\\t', '\t') + + # The HTML string from "data" field starts with "..." + html_input_for_markdown = content + + markdown_text = None + try: + # Convert HTML string to bytes and create BytesIO stream + html_bytes = html_input_for_markdown.encode('utf-8') + html_stream = io.BytesIO(html_bytes) + + # Pass BytesIO stream to MarkItDown to avoid temp file creation + md_converter = MarkItDown() + conversion_result = md_converter.convert(html_stream) + markdown_text = conversion_result.text_content + logger.info("EmsalApiClient: HTML to Markdown conversion successful.") + except Exception as e: + logger.error(f"EmsalApiClient: Error during MarkItDown HTML to Markdown conversion for Emsal: {e}") + + return markdown_text + + async def get_decision_document_as_markdown(self, id: str) -> EmsalDocumentMarkdown: + """ + Retrieves a specific Emsal decision by ID and returns its content as Markdown. + Assumes Emsal /getDokuman endpoint returns JSON with HTML content in the 'data' field. + """ + document_api_url = f"{self.DOCUMENT_ENDPOINT}?id={id}" + source_url = f"{self.BASE_URL}{document_api_url}" + logger.info(f"EmsalApiClient: Fetching Emsal document for Markdown (ID: {id}) from {source_url}") + + try: + response = await self.http_client.get(document_api_url) + response.raise_for_status() + + # Emsal /getDokuman returns JSON with HTML in 'data' field (confirmed by user example) + response_json = response.json() + html_content_from_api = response_json.get("data") + + if not isinstance(html_content_from_api, str) or not html_content_from_api.strip(): + logger.warning(f"EmsalApiClient: Received empty or non-string HTML in 'data' field for Emsal ID {id}.") + return EmsalDocumentMarkdown(id=id, markdown_content=None, source_url=source_url) + + markdown_content = self._clean_html_and_convert_to_markdown_emsal(html_content_from_api) + + return EmsalDocumentMarkdown( + id=id, + markdown_content=markdown_content, + source_url=source_url + ) + except httpx.RequestError as e: + logger.error(f"EmsalApiClient: HTTP error fetching Emsal document (ID: {id}): {e}") + raise + except ValueError as e: + logger.error(f"EmsalApiClient: ValueError processing Emsal document response (ID: {id}): {e}") + raise + except Exception as e: + logger.error(f"EmsalApiClient: General error processing Emsal document (ID: {id}): {e}") + raise + + async def close_client_session(self): + """Closes the HTTPX client session.""" + if self.http_client and not self.http_client.is_closed: + await self.http_client.aclose() + logger.info("EmsalApiClient: HTTP client session closed.") \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/emsal_mcp_module/models.py b/saidsurucu-yargi-mcp-f5fa007/emsal_mcp_module/models.py new file mode 100644 index 0000000..c94f0dd --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/emsal_mcp_module/models.py @@ -0,0 +1,101 @@ +# emsal_mcp_module/models.py + +from pydantic import BaseModel, Field, HttpUrl, ConfigDict +from typing import List, Optional, Dict, Any + +class EmsalDetailedSearchRequestData(BaseModel): + """ + Internal model for the 'data' object in the Emsal detailed search payload. + Field names use aliases to match the exact keys in the API payload + (e.g., "Bam Hukuk Mahkemeleri" with spaces). + The API expects empty strings for None/omitted optional fields. + """ + arananKelime: Optional[str] = "" + + Bam_Hukuk_Mahkemeleri: str = Field("", alias="Bam Hukuk Mahkemeleri") + Hukuk_Mahkemeleri: str = Field("", alias="Hukuk Mahkemeleri") + # Add other specific court type fields from the form if they are separate keys in payload + # E.g., "Ceza Mahkemeleri", "İdari Mahkemeler" etc. + + birimHukukMah: Optional[str] = Field("", description="Regional chambers (+ separated)") + + esasYil: Optional[str] = "" + esasIlkSiraNo: Optional[str] = "" + esasSonSiraNo: Optional[str] = "" + kararYil: Optional[str] = "" + kararIlkSiraNo: Optional[str] = "" + kararSonSiraNo: Optional[str] = "" + baslangicTarihi: Optional[str] = "" + bitisTarihi: Optional[str] = "" + siralama: str # Mandatory in payload example + siralamaDirection: str # Mandatory in payload example + pageSize: int + pageNumber: int + + model_config = ConfigDict(populate_by_name=True) # Enables use of alias in serialization (when dumping to dict for payload) + +class EmsalSearchRequest(BaseModel): # This is the model the MCP tool will accept + """Model for Emsal detailed search request, with user-friendly field names.""" + keyword: str = Field("", description="Keyword") + + selected_bam_civil_court: str = Field("", description="BAM Civil Court") + selected_civil_court: str = Field("", description="Civil Court") + selected_regional_civil_chambers: List[str] = Field(default_factory=list, description="Regional chambers") + + case_year_esas: str = Field("", description="Case year") + case_start_seq_esas: str = Field("", description="Start case no") + case_end_seq_esas: str = Field("", description="End case no") + + decision_year_karar: str = Field("", description="Decision year") + decision_start_seq_karar: str = Field("", description="Start decision no") + decision_end_seq_karar: str = Field("", description="End decision no") + + start_date: str = Field("", description="Start date (DD.MM.YYYY)") + end_date: str = Field("", description="End date (DD.MM.YYYY)") + + sort_criteria: str = Field("1", description="Sort by") + sort_direction: str = Field("desc", description="Direction") + + page_number: int = Field(default=1, ge=1) + page_size: int = Field(default=10, ge=1, le=10) + + +class EmsalApiDecisionEntry(BaseModel): + """Model for an individual decision entry from the Emsal API search response.""" + id: str + daire: str = Field("", description="Chamber") + esasNo: str = Field("", description="Case number") + kararNo: str = Field("", description="Decision number") + kararTarihi: str = Field("", description="Decision date") + arananKelime: str = Field("", description="Keyword") + durum: str = Field("", description="Status") + # index: Optional[int] = None # Present in Emsal response, can be added if tool needs it + + document_url: Optional[HttpUrl] = Field(None, description="Document URL") + + model_config = ConfigDict(extra='ignore') + +class EmsalApiResponseInnerData(BaseModel): + """Model for the inner 'data' object in the Emsal API search response.""" + data: List[EmsalApiDecisionEntry] + recordsTotal: int + recordsFiltered: int + draw: int = Field(0, description="Draw counter (Çizim Sayıcısı) from API, usually for DataTables.") + +class EmsalApiResponse(BaseModel): + """Model for the complete search response from the Emsal API.""" + data: EmsalApiResponseInnerData + metadata: Optional[Dict[str, Any]] = Field(None, description="Optional metadata (Meta Veri) from API, if any.") + +class EmsalDocumentMarkdown(BaseModel): + """Model for an Emsal decision document, containing only Markdown content.""" + id: str + markdown_content: str = Field("", description="The decision content (Karar İçeriği) converted to Markdown.") + source_url: HttpUrl + +class CompactEmsalSearchResult(BaseModel): + """A compact search result model for the MCP tool to return.""" + decisions: List[EmsalApiDecisionEntry] + total_records: int + requested_page: int + page_size: int \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/example_fastapi_app.py b/saidsurucu-yargi-mcp-f5fa007/example_fastapi_app.py new file mode 100644 index 0000000..5e4c3fd --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/example_fastapi_app.py @@ -0,0 +1,1680 @@ +""" +FastAPI Comprehensive Endpoints with Complete MCP Documentation +This is the complete version with all descriptions and docstrings from MCP server. +""" + +import os +from typing import List, Dict, Any, Optional +from datetime import datetime + +from fastapi import FastAPI, HTTPException, Query, Depends, Body +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse +from pydantic import BaseModel, Field +import json + +# Import the main MCP app +from mcp_server_main import app as mcp_server + +# Create MCP ASGI app +mcp_asgi_app = mcp_server.http_app(path="/mcp") + +# Create FastAPI app with MCP lifespan +app = FastAPI( + title="Yargı MCP API - Turkish Legal Database REST API", + description=""" + Comprehensive REST API for Turkish Legal Databases with complete MCP tool coverage. + + This API provides access to 8 major Turkish legal institutions including: + • Yargıtay (Court of Cassation) - Supreme civil/criminal court + • Danıştay (Council of State) - Supreme administrative court + • Constitutional Court - Constitutional review and individual applications + • Competition Authority - Antitrust and merger decisions + • Public Procurement Authority - Government contracting disputes + • Court of Accounts - Public audit and accountability + • Emsal (UYAP Precedents) - Cross-court precedent database + • Local and Appellate Courts - First and second instance decisions + + Features complete coverage of 33 MCP tools with enhanced documentation, + typed request models, and comprehensive legal context. + + Tool Properties (from MCP annotations): + • Read-only: All tools are read-only and do not modify system state + • Idempotent: Same inputs produce same outputs for reliable research + • Open-world search: Search tools explore comprehensive legal databases + • Deterministic document retrieval: Document tools return consistent content + """, + version="1.0.0", + lifespan=mcp_asgi_app.lifespan +) + +# Add CORS middleware +cors_origins = os.getenv("ALLOWED_ORIGINS", "*").split(",") +app.add_middleware( + CORSMiddleware, + allow_origins=cors_origins, + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +# Mount MCP server +app.mount("/mcp-server", mcp_asgi_app) + +# Response models (keeping from original) +class ToolInfo(BaseModel): + name: str + description: str + parameters: Dict[str, Any] + +class ServerInfo(BaseModel): + name: str + version: str + description: str + tools_count: int + databases: List[str] + mcp_endpoint: str + api_docs: str + +class HealthCheck(BaseModel): + status: str + timestamp: datetime + uptime_seconds: Optional[float] = None + tools_operational: bool + +# Track server start time +SERVER_START_TIME = datetime.now() + +# MCP tool caller helper +async def call_mcp_tool(tool_name: str, arguments: Dict[str, Any]): + """Call an MCP tool with given arguments""" + try: + tool = mcp_server._tool_manager._tools.get(tool_name) + if not tool: + raise HTTPException(status_code=404, detail=f"Tool '{tool_name}' not found") + result = await tool.fn(**arguments) + return result + except Exception as e: + raise HTTPException(status_code=500, detail=f"Tool execution failed: {str(e)}") + +# ============================================================================ +# COMPREHENSIVE REQUEST MODELS WITH FULL MCP DOCUMENTATION +# ============================================================================ + +class YargitaySearchRequest(BaseModel): + """ + Search request for Court of Cassation (Yargıtay) decisions using primary official API. + + The Court of Cassation is Turkey's highest court for civil and criminal matters, + equivalent to a Supreme Court. Provides access to comprehensive supreme court precedents. + """ + arananKelime: str = Field( + ..., + description="""Keyword to search for with advanced operators: + • Space between words = OR logic (arsa payı → "arsa" OR "payı") + • "exact phrase" = Exact match ("arsa payı" → exact phrase) + • word1+word2 = AND logic (arsa+payı → both words required) + • word* = Wildcard (bozma* → bozma, bozması, bozmanın, etc.) + • +"phrase1" +"phrase2" = Multiple required phrases + • +"required" -"excluded" = Include and exclude + + Turkish Examples: + • Simple OR: arsa payı (~523K results) + • Exact phrase: "arsa payı" (~22K results) + • Multiple AND: +"arsa payı" +"bozma sebebi" (~234 results) + • Wildcard: bozma* (bozma, bozması, bozmanın, etc.) + • Exclude: +"arsa payı" -"kira sözleşmesi" + """, + example='+"mülkiyet hakkı" +"iptal"' + ) + birimYrgKurulDaire: Optional[str] = Field( + "", + description="""Chamber/board selection (52 options): + Civil Chambers: 1-23. Hukuk Dairesi + Criminal Chambers: 1-23. Ceza Dairesi + General Assemblies: Hukuk Genel Kurulu, Ceza Genel Kurulu + Special Boards: Hukuk/Ceza Daireleri Başkanlar Kurulu, Büyük Genel Kurulu + + Use "" for ALL chambers or specify exact chamber name. + """, + example="1. Hukuk Dairesi" + ) + baslangicTarihi: Optional[str] = Field(None, description="Start date (DD.MM.YYYY)", example="01.01.2020") + bitisTarihi: Optional[str] = Field(None, description="End date (DD.MM.YYYY)", example="31.12.2024") + pageSize: int = Field(20, description="Results per page (1-100)", ge=1, le=100, example=20) + +class YargitayBedestenSearchRequest(BaseModel): + """ + Search request for Court of Cassation using Bedesten API (alternative source). + Complements primary API with different search capabilities and recent decisions. + """ + phrase: str = Field( + ..., + description="""Aranacak kavram/kelime. İki farklı arama türü desteklenir: + • Normal arama: "mülkiyet hakkı" - kelimeler ayrı ayrı aranır + • Tam cümle arama: "\"mülkiyet hakkı\"" - tırnak içindeki ifade aynen aranır + Tam cümle aramalar daha kesin sonuçlar verir. + + Search phrase with exact matching support: + • Regular search: "mülkiyet hakkı" - searches individual words separately + • Exact phrase search: "\"mülkiyet hakkı\"" - searches for exact phrase as unit + Exact phrase search provides more precise results with fewer false positives. + """, + example="\"mülkiyet hakkı\"" + ) + birimAdi: Optional[str] = Field( + None, + description="""Daire/Kurul seçimi (52 seçenek - ana API ile aynı): + • Hukuk daireleri: 1. Hukuk Dairesi - 23. Hukuk Dairesi + • Ceza daireleri: 1. Ceza Dairesi - 23. Ceza Dairesi + • Genel kurullar: Hukuk Genel Kurulu, Ceza Genel Kurulu + • Özel kurullar: Hukuk/Ceza Daireleri Başkanlar Kurulu, Büyük Genel Kurulu + + Chamber filtering (52 options - same as primary API): + • Civil chambers: 1. Hukuk Dairesi through 23. Hukuk Dairesi + • Criminal chambers: 1. Ceza Dairesi through 23. Ceza Dairesi + • General assemblies: Hukuk Genel Kurulu, Ceza Genel Kurulu + • Special boards: Hukuk/Ceza Daireleri Başkanlar Kurulu, Büyük Genel Kurulu + + Use None for ALL chambers, or specify exact chamber name. + """, + example="1. Hukuk Dairesi" + ) + kararTarihiStart: Optional[str] = Field( + None, + description="""Karar başlangıç tarihi (ISO 8601 formatı): + Format: YYYY-MM-DDTHH:MM:SS.000Z + Örnek: "2024-01-01T00:00:00.000Z" - 1 Ocak 2024'ten itibaren kararlar + kararTarihiEnd ile birlikte tarih aralığı filtrelemesi için kullanılır. + + Decision start date filter (ISO 8601 format): + Format: YYYY-MM-DDTHH:MM:SS.000Z + Example: "2024-01-01T00:00:00.000Z" for decisions from Jan 1, 2024 + Use with kararTarihiEnd for date range filtering. + """, + example="2024-01-01T00:00:00.000Z" + ) + kararTarihiEnd: Optional[str] = Field( + None, + description="""Karar bitiş tarihi (ISO 8601 formatı): + Format: YYYY-MM-DDTHH:MM:SS.000Z + Örnek: "2024-12-31T23:59:59.999Z" - 31 Aralık 2024'e kadar kararlar + kararTarihiStart ile birlikte tarih aralığı filtrelemesi için kullanılır. + + Decision end date filter (ISO 8601 format): + Format: YYYY-MM-DDTHH:MM:SS.000Z + Example: "2024-12-31T23:59:59.999Z" for decisions until Dec 31, 2024 + Use with kararTarihiStart for date range filtering. + """, + example="2024-12-31T23:59:59.999Z" + ) + pageSize: int = Field(20, description="Results per page (1-100)", ge=1, le=100) + +class DanistayKeywordSearchRequest(BaseModel): + """ + Keyword-based search for Council of State (Danıştay) decisions with Boolean logic. + + The Council of State is Turkey's highest administrative court, providing final + rulings on administrative law matters with Boolean keyword operators. + """ + andKelimeler: List[str] = Field( + ..., + description="ALL keywords must be present (AND logic)", + example=["idari işlem", "iptal"] + ) + orKelimeler: Optional[List[str]] = Field( + None, + description="ANY keyword can be present (OR logic)", + example=["ruhsat", "izin", "lisans"] + ) + notKelimeler: Optional[List[str]] = Field( + None, + description="EXCLUDE if keywords present (NOT logic)", + example=["vergi"] + ) + pageSize: int = Field(20, description="Results per page (1-100)", ge=1, le=100) + +class DanistayDetailedSearchRequest(BaseModel): + """ + Detailed search for Council of State decisions with comprehensive filtering. + Provides the most comprehensive search capabilities for administrative court decisions. + """ + daire: Optional[str] = Field( + None, + description="Chamber/Department filter (1. Daire through 17. Daire, special councils)", + example="3. Daire" + ) + baslangicTarihi: Optional[str] = Field(None, description="Start date (DD.MM.YYYY)", example="01.01.2020") + bitisTarihi: Optional[str] = Field(None, description="End date (DD.MM.YYYY)", example="31.12.2024") + esas: Optional[str] = Field(None, description="Case number (Esas No)", example="2024/123") + karar: Optional[str] = Field(None, description="Decision number (Karar No)", example="2024/456") + +class DanistayBedestenSearchRequest(BaseModel): + """ + Council of State search using Bedesten API with chamber filtering and exact phrase search. + Provides access to administrative court decisions with 27 chamber options. + """ + phrase: str = Field(..., description="Search phrase (supports exact matching with quotes)") + birimAdi: Optional[str] = Field( + None, + description="""Chamber filtering (27 options): + Main Councils: Büyük Gen.Kur., İdare Dava Daireleri Kurulu, Vergi Dava Daireleri Kurulu + Chambers: 1. Daire through 17. Daire + Military: Askeri Yüksek İdare Mahkemesi chambers + """, + example="3. Daire" + ) + kararTarihiStart: Optional[str] = Field(None, description="Start date (ISO 8601)") + kararTarihiEnd: Optional[str] = Field(None, description="End date (ISO 8601)") + pageSize: int = Field(20, description="Results per page", ge=1, le=100) + +class EmsalSearchRequest(BaseModel): + """ + Search Precedent (Emsal) decisions from UYAP system across multiple court levels. + Provides access to precedent decisions from various Turkish courts. + """ + keyword: str = Field(..., description="Search keyword across decision texts") + decision_year_karar: Optional[str] = Field(None, description="Decision year filter", example="2024") + results_per_page: int = Field(20, description="Results per page", ge=1, le=100) + +class UyusmazlikSearchRequest(BaseModel): + """ + Search Court of Jurisdictional Disputes decisions. + Resolves jurisdictional disputes between different court systems. + """ + keywords: List[str] = Field(..., description="Search keywords", example=["görev", "uyuşmazlık"]) + page_to_fetch: int = Field(1, description="Page number", ge=1) + +class AnayasaNormSearchRequest(BaseModel): + """ + Search Constitutional Court norm control (judicial review) decisions. + Turkey's highest constitutional authority for reviewing law constitutionality. + """ + keywords_all: List[str] = Field(..., description="All required keywords", example=["eğitim hakkı", "anayasa"]) + period: Optional[str] = Field(None, description="Constitutional period (1=1961, 2=1982)", example="2") + application_type: Optional[str] = Field(None, description="Application type (1=İptal)", example="1") + results_per_page: int = Field(20, description="Results per page", ge=1, le=100) + +class AnayasaBireyselSearchRequest(BaseModel): + """ + Search Constitutional Court individual application decisions. + Human rights violation cases through individual citizen petitions. + """ + keywords: List[str] = Field(..., description="Search keywords", example=["ifade özgürlüğü", "basın"]) + page_to_fetch: int = Field(1, description="Page number", ge=1) + +class KikSearchRequest(BaseModel): + """ + Search Public Procurement Authority (KİK) decisions. + Government procurement disputes and regulatory interpretations. + """ + karar_tipi: Optional[str] = Field( + None, + description="Decision type (rbUyusmazlik=Disputes, rbDuzenleyici=Regulatory, rbMahkeme=Court)", + example="rbUyusmazlik" + ) + karar_metni: Optional[str] = Field(None, description="Decision text search", example="ihale iptali") + basvuru_konusu_ihale: Optional[str] = Field(None, description="Tender subject", example="danışmanlık") + karar_tarihi_baslangic: Optional[str] = Field(None, description="Start date", example="01.01.2023") + +class RekabetSearchRequest(BaseModel): + """ + Search Competition Authority decisions. + Antitrust, merger control, and competition law enforcement. + """ + KararTuru: Optional[str] = Field( + None, + description="Decision type (Birleşme ve Devralma, Rekabet İhlali, Muafiyet, etc.)", + example="Birleşme ve Devralma" + ) + PdfText: Optional[str] = Field( + None, + description="Full-text search in decisions. Use quotes for exact phrases.", + example="\"market definition\" telecommunications" + ) + YayinlanmaTarihi: Optional[str] = Field(None, description="Publication date", example="01.01.2020") + page: int = Field(1, description="Page number", ge=1) + +class BedestenSearchRequest(BaseModel): + """ + Generic search request for Bedesten API courts (Yerel Hukuk, İstinaf Hukuk, KYB). + Supports exact phrase search and date filtering. + """ + phrase: str = Field( + ..., + description="Search phrase. Use quotes for exact matching: \"legal term\"", + example="\"sözleşme ihlali\"" + ) + kararTarihiStart: Optional[str] = Field(None, description="Start date (ISO 8601)") + kararTarihiEnd: Optional[str] = Field(None, description="End date (ISO 8601)") + pageSize: int = Field(20, description="Results per page", ge=1, le=100) + +class SayistaySearchRequest(BaseModel): + """ + Search Court of Accounts (Sayıştay) decisions. + Public audit, accountability, and financial oversight decisions. + """ + keywords: List[str] = Field(..., description="Search keywords", example=["mali sorumluluk", "denetim"]) + page_to_fetch: int = Field(1, description="Page number", ge=1) + +# ============================================================================ +# BASIC SERVER ENDPOINTS (keeping from original) +# ============================================================================ + +@app.get("/", response_model=ServerInfo) +async def root(): + """Get comprehensive server information with database coverage""" + return ServerInfo( + name="Yargı MCP Server - Turkish Legal Database API", + version="1.0.0", + description="Complete REST API for Turkish legal databases with 33 MCP tools", + tools_count=len(mcp_server._tool_manager._tools), + databases=[ + "Yargıtay (Court of Cassation) - 4 tools", + "Danıştay (Council of State) - 5 tools", + "Emsal (UYAP Precedents) - 2 tools", + "Uyuşmazlık Mahkemesi (Jurisdictional Disputes) - 2 tools", + "Anayasa Mahkemesi (Constitutional Court) - 4 tools", + "Kamu İhale Kurulu (Public Procurement) - 2 tools", + "Rekabet Kurumu (Competition Authority) - 2 tools", + "Sayıştay (Court of Accounts) - 6 tools", + "Bedesten API Courts (Local/Appellate/KYB) - 6 tools" + ], + mcp_endpoint="/mcp-server/mcp/", + api_docs="/docs" + ) + +@app.get("/health", response_model=HealthCheck) +async def health_check(): + """Health check with comprehensive system status""" + uptime = (datetime.now() - SERVER_START_TIME).total_seconds() + return HealthCheck( + status="healthy", + timestamp=datetime.now(), + uptime_seconds=uptime, + tools_operational=len(mcp_server._tool_manager._tools) == 33 + ) + +@app.get("/api/tools", response_model=List[ToolInfo]) +async def list_tools( + search: Optional[str] = Query(None, description="Search tools by name or description"), + database: Optional[str] = Query(None, description="Filter by database name") +): + """List all 33 MCP tools with filtering capabilities""" + tools = [] + for tool in mcp_server._tool_manager._tools.values(): + if search and search.lower() not in tool.name.lower() and search.lower() not in tool.description.lower(): + continue + if database: + db_lower = database.lower() + if db_lower not in tool.name.lower() and db_lower not in tool.description.lower(): + continue + + params = {} + if hasattr(tool, 'schema') and tool.schema: + if hasattr(tool.schema, 'parameters'): + params = tool.schema.parameters + elif hasattr(tool.schema, '__annotations__'): + params = {k: str(v) for k, v in tool.schema.__annotations__.items()} + + tools.append(ToolInfo( + name=tool.name, + description=tool.description, + parameters=params + )) + return tools + +# ============================================================================ +# YARGITAY (COURT OF CASSATION) ENDPOINTS - 4 TOOLS +# ============================================================================ + +@app.post( + "/api/yargitay/search", + tags=["Yargıtay"], + summary="Search Court of Cassation (Primary API)", + description="""Search Turkey's Supreme Court for civil and criminal precedents using advanced operators. + +Key Features: +• Advanced search: AND (+), OR (space), NOT (-), wildcards (*), exact phrases ("") +• 52 chamber options (23 Civil + 23 Criminal + General Assemblies) +• Date range filtering • Case/decision number filtering • Pagination + +Search Examples: +• OR search: property share (finds ANY words) +• Exact phrase: "property share" (finds exact phrase) +• AND required: +"property share" +"annulment reason" +• Wildcard: construct* (construction, constructive, etc.) +• Exclude terms: +"property share" -"construction contract" + +Use for supreme court precedent research and legal principle analysis.""" +) +async def search_yargitay(request: YargitaySearchRequest): + """ + Searches Court of Cassation (Yargıtay) decisions using the primary official API. + + The Court of Cassation (Yargıtay) is Turkey's highest court for civil and criminal matters, + equivalent to a Supreme Court. This tool provides access to the most comprehensive database + of supreme court precedents with advanced search capabilities and filtering options. + + Key Features: + • Advanced search operators (AND, OR, wildcards, exclusions) + • Chamber filtering: 52 options (23 Civil (Hukuk) + 23 Criminal (Ceza) + General Assemblies (Genel Kurullar)) + • Date range filtering with DD.MM.YYYY format + • Case number filtering (Case No (Esas No) and Decision No (Karar No)) + • Pagination support (1-100 results per page) + • Multiple sorting options (by case number, decision number, date) + + SEARCH SYNTAX GUIDE: + • Words with spaces: OR search ("property share" finds ANY of the words) + • "Quotes": Exact phrase search ("property share" finds exact phrase) + • Plus sign (+): AND search (property+share requires both words) + • Asterisk (*): Wildcard (construct* matches variations) + • Minus sign (-): Exclude terms (avoid unwanted results) + + Common Search Patterns: + • Simple OR: property share (finds ~523K results) + • Exact phrase: "property share" (finds ~22K results) + • Multiple required: +"property share" +"annulment reason (bozma sebebi)" (finds ~234 results) + • Wildcard expansion: construct* (matches construction, constructive, etc.) + • Exclude unwanted: +"property share" -"construction contract" + + Use cases: + • Research supreme court precedents and legal principles + • Find decisions from specific chambers (Civil (Hukuk) vs Criminal (Ceza)) + • Search for interpretations of specific legal concepts + • Analyze court reasoning on complex legal issues + • Track legal developments over time periods + + Returns structured search results with decision metadata. Use get_yargitay_document_markdown() + to retrieve full decision texts for detailed analysis. + """ + args = { + "arananKelime": request.arananKelime, + "birimYrgKurulDaire": request.birimYrgKurulDaire, + "pageSize": request.pageSize + } + if request.baslangicTarihi: + args["baslangicTarihi"] = request.baslangicTarihi + if request.bitisTarihi: + args["bitisTarihi"] = request.bitisTarihi + return await call_mcp_tool("search_yargitay_detailed", args) + +@app.post( + "/api/yargitay/search-bedesten", + tags=["Yargıtay"], + summary="Search Court of Cassation (Bedesten API)", + description="""Alternative Court of Cassation search with exact phrase matching and recent decisions. + +Key Features: +• Exact phrase search: "\"legal term\"" for precise matching +• Regular search: "legal term" for individual word matching +• 52 chamber filtering options (same as primary API) +• ISO 8601 date filtering • Recent decision coverage + +Use alongside primary search for comprehensive coverage. Exact phrase search provides +higher precision with fewer false positives.""" +) +async def search_yargitay_bedesten(request: YargitayBedestenSearchRequest): + """Search Court of Cassation using Bedesten API. Complements primary API for complete coverage.""" + args = {"phrase": request.phrase, "pageSize": request.pageSize} + if request.birimAdi: + args["birimAdi"] = request.birimAdi + if request.kararTarihiStart: + args["kararTarihiStart"] = request.kararTarihiStart + if request.kararTarihiEnd: + args["kararTarihiEnd"] = request.kararTarihiEnd + return await call_mcp_tool("search_yargitay_bedesten", args) + +@app.get( + "/api/yargitay/document/{decision_id}", + tags=["Yargıtay"], + summary="Get Court of Cassation Document (Primary API)", + description="""Retrieve complete Court of Cassation decision in Markdown format. + +Content includes: +• Complete legal reasoning and precedent analysis +• Detailed examination of lower court decisions +• Citations of laws, regulations, and prior cases +• Final ruling with legal justification + +Perfect for detailed legal analysis, precedent research, and citation building.""" +) +async def get_yargitay_document(decision_id: str): + """Get full Court of Cassation decision text in clean Markdown format.""" + return await call_mcp_tool("get_yargitay_document_markdown", {"id": decision_id}) + +@app.get( + "/api/yargitay/bedesten-document/{document_id}", + tags=["Yargıtay"], + summary="Get Court of Cassation Document (Bedesten API)", + description="""Retrieve Court of Cassation decision from Bedesten API in Markdown format. + +Features: +• Supports both HTML and PDF source documents +• Clean Markdown conversion with legal structure preserved +• Removes technical artifacts for easy reading +• Compatible with documentId from Bedesten search results""" +) +async def get_yargitay_bedesten_document(document_id: str): + """Get Court of Cassation document from Bedesten API in Markdown format.""" + return await call_mcp_tool("get_yargitay_bedesten_document_markdown", {"documentId": document_id}) + +# ============================================================================ +# DANISTAY (COUNCIL OF STATE) ENDPOINTS - 5 TOOLS +# ============================================================================ + +@app.post( + "/api/danistay/search-keyword", + tags=["Danıştay"], + summary="Search Council of State (Keyword Logic)", + description="""Search Turkey's highest administrative court using Boolean keyword logic. + +Boolean Operators: +• AND keywords: ALL must be present (required terms) +• OR keywords: ANY can be present (alternative terms) +• NOT keywords: EXCLUDE if present (unwanted terms) + +Examples: +• Administrative acts: andKelimeler=["idari işlem", "iptal"] +• Permits/licenses: orKelimeler=["ruhsat", "izin", "lisans"] +• Exclude tax cases: notKelimeler=["vergi"] + +Perfect for administrative law research and government action reviews.""" +) +async def search_danistay_keyword(request: DanistayKeywordSearchRequest): + """ + Searches Council of State (Danıştay) decisions using keyword-based logic. + + The Council of State (Danıştay) is Turkey's highest administrative court, responsible for + reviewing administrative actions and providing administrative law precedents. This tool + provides flexible keyword-based searching with Boolean logic operators. + + Key Features: + • Boolean logic operators: AND, OR, NOT combinations + • Multiple keyword lists for complex search strategies + • Pagination support (1-100 results per page) + • Administrative law focus (permits, licenses, public administration) + • Complement to search_danistay_detailed for comprehensive coverage + + Keyword Logic: + • andKelimeler: ALL keywords must be present (AND logic) + • orKelimeler: ANY keyword can be present (OR logic) + • notAndKelimeler: EXCLUDE if ALL keywords present (NOT AND) + • notOrKelimeler: EXCLUDE if ANY keyword present (NOT OR) + + Administrative Law Use Cases: + • Research administrative court precedents + • Find decisions on specific government agencies + • Search for rulings on permits (ruhsat) and licenses (izin) + • Analyze administrative procedure interpretations + • Study public administration legal principles + + Examples: + • Simple AND: andKelimeler=["administrative act (idari işlem)", "annulment (iptal)"] + • OR search: orKelimeler=["permit (ruhsat)", "permission (izin)", "license (lisans)"] + • Complex: andKelimeler=["municipality (belediye)"], notOrKelimeler=["tax (vergi)"] + + Returns structured search results. Use get_danistay_document_markdown() for full texts. + For comprehensive Council of State (Danıştay) research, also use search_danistay_detailed and search_danistay_bedesten. + """ + args = {"andKelimeler": request.andKelimeler, "pageSize": request.pageSize} + if request.orKelimeler: + args["orKelimeler"] = request.orKelimeler + if request.notKelimeler: + args["notKelimeler"] = request.notKelimeler + return await call_mcp_tool("search_danistay_by_keyword", args) + +@app.post( + "/api/danistay/search-detailed", + tags=["Danıştay"], + summary="Search Council of State (Detailed Criteria)", + description="""Most comprehensive Council of State search with advanced filtering. + +Advanced Filtering: +• Chamber targeting (1. Daire through 17. Daire, special councils) +• Case/decision number ranges • Date range filtering +• Legislation cross-referencing • Multiple sorting options + +Use for specialized administrative law research, chamber-specific decisions, +and regulatory compliance analysis.""" +) +async def search_danistay_detailed(request: DanistayDetailedSearchRequest): + """Search Council of State with comprehensive filtering for specialized administrative law research.""" + args = {} + for field in ["daire", "baslangicTarihi", "bitisTarihi", "esas", "karar"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_danistay_detailed", args) + +@app.post( + "/api/danistay/search-bedesten", + tags=["Danıştay"], + summary="Search Council of State (Bedesten API)", + description="""Council of State search via Bedesten API with 27 chamber options and exact phrase search. + +Key Features: +• 27 chamber options (Main Councils, 17 Chambers, Military courts) +• Exact phrase search with double quotes for precision +• ISO 8601 date filtering • Alternative data source + +Use with other Danıştay tools for complete administrative law coverage.""" +) +async def search_danistay_bedesten(request: DanistayBedestenSearchRequest): + """Search Council of State via Bedesten API. Use with other Danıştay tools for complete coverage.""" + args = {"phrase": request.phrase, "pageSize": request.pageSize} + for field in ["birimAdi", "kararTarihiStart", "kararTarihiEnd"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_danistay_bedesten", args) + +@app.get( + "/api/danistay/document/{decision_id}", + tags=["Danıştay"], + summary="Get Council of State Document (Primary API)", + description="""Retrieve complete administrative court decision in Markdown format. + +Content includes: +• Complete administrative law reasoning and precedent analysis +• Review of administrative actions and government decisions +• Citations of administrative laws and regulations +• Final administrative ruling with legal justification + +Essential for administrative law research and government compliance analysis.""" +) +async def get_danistay_document(decision_id: str): + """Get full Council of State decision text in clean Markdown format.""" + return await call_mcp_tool("get_danistay_document_markdown", {"id": decision_id}) + +@app.get( + "/api/danistay/bedesten-document/{document_id}", + tags=["Danıştay"], + summary="Get Council of State Document (Bedesten API)", + description="""Retrieve Council of State decision from Bedesten API in Markdown format.""" +) +async def get_danistay_bedesten_document(document_id: str): + """Get Council of State document from Bedesten API in Markdown format.""" + return await call_mcp_tool("get_danistay_bedesten_document_markdown", {"documentId": document_id}) + +# ============================================================================ +# BEDESTEN API COURTS (LOCAL/APPELLATE/KYB) - 6 TOOLS +# ============================================================================ + +@app.post( + "/api/yerel-hukuk/search", + tags=["Yerel Hukuk"], + summary="Search Local Civil Courts", + description="""Search first-instance civil court decisions using Bedesten API. + +Local Civil Courts handle: +• Contract disputes • Property rights • Family law • Tort claims +• Commercial disputes • Consumer protection + +Only available tool for local court decisions. Supports exact phrase search +and date filtering for precise legal research.""" +) +async def search_yerel_hukuk(request: BedestenSearchRequest): + """ + Searches Yerel Hukuk Mahkemesi (Local Civil Court) decisions using Bedesten API. + + This provides access to local court decisions that are not available through other APIs. + Currently the only available tool for searching Yerel Hukuk Mahkemesi decisions. + Local civil courts represent the first instance of civil litigation in Turkey. + + Local Civil Courts handle: + • Contract disputes and commercial litigation + • Property rights and real estate disputes + • Family law matters (divorce, custody, inheritance) + • Tort claims and compensation cases + • Consumer protection issues + • Employment disputes + + Returns structured search results with decision metadata. Use get_yerel_hukuk_bedesten_document_markdown() + to retrieve full decision texts for detailed analysis. + """ + args = {"phrase": request.phrase, "pageSize": request.pageSize} + for field in ["kararTarihiStart", "kararTarihiEnd"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_yerel_hukuk_bedesten", args) + +@app.get("/api/yerel-hukuk/document/{document_id}", tags=["Yerel Hukuk"]) +async def get_yerel_hukuk_document(document_id: str): + """ + Retrieves a Yerel Hukuk Mahkemesi decision document from Bedesten API and converts to Markdown. + + This tool fetches complete local court decision texts using documentId from search results. + Perfect for detailed analysis of first-instance civil court rulings. + + Supports both HTML and PDF content types, automatically converting to clean Markdown format. + Use documentId from search_yerel_hukuk_bedesten results. + """ + return await call_mcp_tool("get_yerel_hukuk_bedesten_document_markdown", {"documentId": document_id}) + +@app.post( + "/api/istinaf-hukuk/search", + tags=["İstinaf Hukuk"], + summary="Search Civil Courts of Appeals", + description="""Search intermediate appellate court decisions using Bedesten API. + +İstinaf Courts are intermediate appellate courts handling appeals from local civil courts +before cases reach the Court of Cassation. Only available tool for İstinaf decisions.""" +) +async def search_istinaf_hukuk(request: BedestenSearchRequest): + """ + Searches İstinaf Hukuk Mahkemesi (Civil Court of Appeals) decisions using Bedesten API. + + İstinaf courts are intermediate appellate courts in the Turkish judicial system that handle + appeals from local civil courts before cases reach Yargıtay (Court of Cassation). + This is the only available tool for accessing İstinaf Hukuk Mahkemesi decisions. + + Key Features: + • Date range filtering with ISO 8601 format (YYYY-MM-DDTHH:MM:SS.000Z) + • Exact phrase search using double quotes: "\"legal term\"" + • Regular search for individual keywords + • Pagination support (1-100 results per page) + + Use cases: + • Research appellate court precedents + • Track appeals from specific lower courts + • Find decisions on specific legal issues at appellate level + • Analyze intermediate court reasoning before supreme court review + + Returns structured data with decision metadata including dates, case numbers, and summaries. + Use get_istinaf_hukuk_bedesten_document_markdown() to retrieve full decision texts. + """ + args = {"phrase": request.phrase, "pageSize": request.pageSize} + for field in ["kararTarihiStart", "kararTarihiEnd"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_istinaf_hukuk_bedesten", args) + +@app.get("/api/istinaf-hukuk/document/{document_id}", tags=["İstinaf Hukuk"]) +async def get_istinaf_hukuk_document(document_id: str): + """ + Retrieves the full text of an İstinaf Hukuk Mahkemesi decision document in Markdown format. + + This tool converts the original decision document (HTML or PDF) from Bedesten API + into clean, readable Markdown format suitable for analysis and processing. + + Input Requirements: + • documentId: Use the ID from search_istinaf_hukuk_bedesten results + • Document ID must be non-empty string + + Output Format: + • Clean Markdown text with proper formatting + • Preserves legal structure (headers, paragraphs, citations) + • Removes extraneous HTML/PDF artifacts + + Use for: + • Reading full appellate court decision texts + • Legal analysis of İstinaf court reasoning + • Citation extraction and reference building + • Content analysis and summarization + """ + return await call_mcp_tool("get_istinaf_hukuk_bedesten_document_markdown", {"documentId": document_id}) + +@app.post( + "/api/kyb/search", + tags=["KYB"], + summary="Search Extraordinary Appeals (KYB)", + description="""Search Kanun Yararına Bozma (Extraordinary Appeal) decisions. + +KYB is an extraordinary legal remedy where the Public Prosecutor's Office requests +review of finalized decisions in favor of law and defendants. Very rare but important +legal precedents. Only available tool for KYB decisions.""" +) +async def search_kyb(request: BedestenSearchRequest): + """ + Searches Kanun Yararına Bozma (KYB - Extraordinary Appeal) decisions using Bedesten API. + + KYB is an extraordinary legal remedy in the Turkish judicial system where the + Public Prosecutor's Office can request review of finalized decisions in favor of + the law and defendants. This is the only available tool for accessing KYB decisions. + + Key Features: + • Date range filtering with ISO 8601 format (YYYY-MM-DDTHH:MM:SS.000Z) + • Exact phrase search using double quotes: "\"extraordinary appeal\"" + • Regular search for individual keywords + • Pagination support (1-100 results per page) + + Legal Significance: + • Extraordinary remedy beyond regular appeals + • Initiated by Public Prosecutor's Office + • Reviews finalized decisions for legal errors + • Can benefit defendants retroactively + • Rare but important legal precedents + + Use cases: + • Research extraordinary appeal precedents + • Study prosecutorial challenges to final decisions + • Analyze legal errors in finalized cases + • Track KYB success rates and patterns + + Returns structured data with decision metadata. Use get_kyb_bedesten_document_markdown() + to retrieve full decision texts for detailed analysis. + """ + args = {"phrase": request.phrase, "pageSize": request.pageSize} + for field in ["kararTarihiStart", "kararTarihiEnd"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_kyb_bedesten", args) + +@app.get("/api/kyb/document/{document_id}", tags=["KYB"]) +async def get_kyb_document(document_id: str): + """ + Retrieves the full text of a Kanun Yararına Bozma (KYB) decision document in Markdown format. + + This tool converts the original extraordinary appeal decision document (HTML or PDF) + from Bedesten API into clean, readable Markdown format for analysis. + + Input Requirements: + • documentId: Use the ID from search_kyb_bedesten results + • Document ID must be non-empty string + + Output Format: + • Clean Markdown text with legal formatting preserved + • Structured content with headers and citations + • Removes technical artifacts from source documents + + Special Value for KYB Documents: + • Contains rare extraordinary appeal reasoning + • Shows prosecutorial arguments for legal review + • Documents correction of finalized legal errors + • Provides precedent for similar extraordinary circumstances + + Use for: + • Analyzing extraordinary appeal legal reasoning + • Understanding prosecutorial review criteria + • Research on legal error correction mechanisms + • Studying retroactive benefit applications + """ + return await call_mcp_tool("get_kyb_bedesten_document_markdown", {"documentId": document_id}) + +# ============================================================================ +# ADDITIONAL COURTS - 12 TOOLS +# ============================================================================ + +@app.post("/api/emsal/search", tags=["Emsal"], summary="Search UYAP Precedents") +async def search_emsal(request: EmsalSearchRequest): + """ + Searches for Precedent (Emsal) decisions using detailed criteria. + + The Precedent (Emsal) database contains precedent decisions from various Turkish courts + integrated through the UYAP (National Judiciary Informatics System). This tool provides + access to a comprehensive collection of court decisions that serve as legal precedents. + + Key Features: + • Multi-court coverage (BAM, Civil courts, Regional chambers) + • Keyword-based search across decision texts + • Court-specific filtering for targeted research + • Case number filtering (Case No (Esas No) and Decision No (Karar No) with ranges) + • Date range filtering with DD.MM.YYYY format + • Multiple sorting options and pagination support + + Court Selection Options: + • BAM Civil Courts: Higher regional civil courts + • Civil Courts: Local and first-instance civil courts + • Regional Civil Chambers: Specialized civil court departments + + Precedent Research Use Cases: + • Find precedent (emsal) decisions across multiple court levels + • Research court interpretations of specific legal concepts + • Analyze consistent legal reasoning patterns + • Study regional variations in legal decisions + • Track precedent development over time + • Compare decisions from different court types + + Returns structured precedent data with court information and decision metadata. + Use get_emsal_document_markdown() to retrieve full precedent decision texts. + """ + args = {"keyword": request.keyword, "results_per_page": request.results_per_page} + if request.decision_year_karar: + args["decision_year_karar"] = request.decision_year_karar + return await call_mcp_tool("search_emsal_detailed_decisions", args) + +@app.get("/api/emsal/document/{decision_id}", tags=["Emsal"]) +async def get_emsal_document(decision_id: str): + """ + Retrieves the full text of a specific Emsal (UYAP Precedent) decision in Markdown format. + + This tool fetches complete precedent decision documents from the UYAP system and converts + them to clean, readable Markdown format suitable for legal precedent analysis. + + Input Requirements: + • decision_id: Decision ID from search_emsal_detailed_decisions results + • ID must be non-empty string from UYAP Emsal database + + Output Format: + • Clean Markdown text with legal precedent structure preserved + • Organized sections: court info, case facts, legal reasoning, conclusion + • Proper formatting for legal citations and cross-references + • Removes technical artifacts from source documents + + Precedent Decision Content: + • Complete court reasoning and legal analysis + • Detailed examination of legal principles applied + • Citation of relevant laws, regulations, and prior precedents + • Final ruling with precedent-setting reasoning + • Court-specific interpretations and legal standards + + Use for legal precedent research, citation building, and comparative legal analysis. + """ + return await call_mcp_tool("get_emsal_document_markdown", {"decision_id": decision_id}) + +@app.post("/api/uyusmazlik/search", tags=["Uyuşmazlık"], summary="Search Jurisdictional Disputes") +async def search_uyusmazlik(request: UyusmazlikSearchRequest): + """ + Searches for Court of Jurisdictional Disputes (Uyuşmazlık Mahkemesi) decisions. + + The Court of Jurisdictional Disputes (Uyuşmazlık Mahkemesi) resolves jurisdictional disputes between different court systems + in Turkey, determining which court has jurisdiction over specific cases. This specialized + court handles conflicts between civil, criminal, and administrative jurisdictions. + + Key Features: + • Department filtering (Criminal, Civil, General Assembly decisions) + • Dispute type classification (Jurisdiction vs Judgment disputes) + • Decision outcome filtering (dispute resolution results) + • Case number and date range filtering + • Advanced text search with Boolean logic operators + • Official Gazette reference search + + Dispute Types: + • Jurisdictional Disputes (Görev Uyuşmazlığı): Which court has authority + • Judgment Disputes (Hüküm Uyuşmazlığı): Conflicting final decisions + + Departments: + • Criminal Section (Ceza Bölümü): Criminal section decisions + • Civil Section (Hukuk Bölümü): Civil section decisions + • General Assembly Decisions (Genel Kurul Kararları): General Assembly decisions + + Use cases: + • Research jurisdictional precedents + • Understand court system boundaries + • Analyze dispute resolution patterns + • Study inter-court conflict resolution + • Legal procedure and jurisdiction research + + Returns structured search results with dispute resolution information. + """ + return await call_mcp_tool("search_uyusmazlik_decisions", { + "keywords": request.keywords, + "page_to_fetch": request.page_to_fetch + }) + +@app.get("/api/uyusmazlik/document", tags=["Uyuşmazlık"]) +async def get_uyusmazlik_document(document_url: str): + """ + Retrieves the full text of a specific Uyuşmazlık Mahkemesi decision from its URL in Markdown format. + + This tool fetches complete jurisdictional dispute resolution decisions and converts them + to clean, readable Markdown format suitable for legal analysis of inter-court disputes. + + Input Requirements: + • document_url: Full URL to the decision document from search_uyusmazlik_decisions results + • URL must be valid HttpUrl format from official Uyuşmazlık Mahkemesi database + + Output Format: + • Clean Markdown text with jurisdictional dispute structure preserved + • Organized sections: dispute facts, jurisdictional analysis, resolution ruling + • Proper formatting for legal citations and court system references + • Removes technical artifacts from source documents + + Jurisdictional Dispute Decision Content: + • Complete analysis of jurisdictional conflicts between court systems + • Detailed examination of applicable jurisdictional rules + • Citation of relevant procedural laws and court organization statutes + • Final resolution determining proper court jurisdiction + • Reasoning for jurisdictional boundaries and court authority + + Use for understanding court system boundaries, analyzing jurisdictional precedents, + and legal practice guidance on proper court selection. + """ + return await call_mcp_tool("get_uyusmazlik_document_markdown_from_url", {"document_url": document_url}) + +@app.post("/api/anayasa/search-norm", tags=["Anayasa"], summary="Search Constitutional Court (Norm Control)") +async def search_anayasa_norm(request: AnayasaNormSearchRequest): + """ + Searches Constitutional Court (Anayasa Mahkemesi) norm control decisions with comprehensive filtering. + + The Constitutional Court is Turkey's highest constitutional authority, responsible for judicial + review of laws, regulations, and constitutional amendments. Norm control (Norm Denetimi) is the + court's power to review the constitutionality of legal norms. + + Key Features: + • Boolean keyword search (AND, OR, NOT logic) + • Constitutional period filtering (1961 vs 1982 Constitution) + • Case and decision number filtering + • Date range filtering for review and decision dates + • Application type classification (İptal, İtiraz, etc.) + • Applicant filtering (government entities, opposition parties) + • Official Gazette publication filtering + • Judicial opinion analysis (dissents, different reasoning) + • Court member and rapporteur filtering + • Norm type classification (laws, regulations, decrees) + • Review outcome filtering (constitutionality determinations) + • Constitutional basis article referencing + + Constitutional Review Types: + • Abstract review: Ex ante constitutional control + • Concrete review: Constitutional questions during litigation + • Legislative review: Parliamentary acts and government decrees + • Regulatory review: Administrative regulations and bylaws + + Use cases: + • Constitutional law research and analysis + • Legislative drafting constitutional compliance + • Academic constitutional law study + • Legal precedent analysis for constitutional questions + • Government policy constitutional assessment + + Returns structured constitutional court data with comprehensive metadata. + """ + args = {"keywords_all": request.keywords_all, "results_per_page": request.results_per_page} + for field in ["period", "application_type"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_anayasa_norm_denetimi_decisions", args) + +@app.get("/api/anayasa/norm-document", tags=["Anayasa"]) +async def get_anayasa_norm_document(document_url: str, page_number: int = 1): + """Get Constitutional Court norm control decision in paginated Markdown format.""" + return await call_mcp_tool("get_anayasa_norm_denetimi_document_markdown", { + "document_url": document_url, "page_number": page_number + }) + +@app.post("/api/anayasa/search-bireysel", tags=["Anayasa"], summary="Search Constitutional Court (Individual Applications)") +async def search_anayasa_bireysel(request: AnayasaBireyselSearchRequest): + """ + Search Constitutional Court individual application (Bireysel Başvuru) decisions for human rights violation reports with keyword filtering. + + Individual applications allow citizens to petition the Constitutional Court directly for + violations of fundamental rights and freedoms. This tool generates decision search reports + that help identify relevant human rights violation cases. + + Key Features: + • Keyword-based search with AND logic + • Human rights violation case identification + • Individual petition decision analysis + • Fundamental rights and freedoms research + • Pagination support for large result sets + + Individual Application System: + • Direct citizen access to Constitutional Court + • Human rights and fundamental freedoms protection + • Alternative to European Court of Human Rights + • Domestic remedy for constitutional violations + • Individual justice and rights enforcement + + Human Rights Categories: + • Right to life and personal liberty + • Right to fair trial and due process + • Freedom of expression and press + • Freedom of religion and conscience + • Property rights and economic freedoms + • Right to privacy and family life + • Political rights and democratic participation + + Use cases: + • Human rights violation research + • Individual petition precedent analysis + • Constitutional rights interpretation study + • Legal remedies for rights violations + • Academic human rights law research + • Civil society and NGO legal research + + Returns search report with case summaries and violation categories. + Use get_anayasa_bireysel_document for full decision texts. + """ + return await call_mcp_tool("search_anayasa_bireysel_basvuru_report", { + "keywords": request.keywords, "page_to_fetch": request.page_to_fetch + }) + +@app.get("/api/anayasa/bireysel-document", tags=["Anayasa"]) +async def get_anayasa_bireysel_document(document_url: str, page_number: int = 1): + """ + Retrieve the full text of a Constitutional Court individual application decision in paginated Markdown format. + + This tool fetches complete human rights violation decisions from individual applications + and converts them to clean, readable Markdown format. Content is paginated into + 5,000-character chunks for easier processing. + + Input Requirements: + • document_url: URL path (e.g., /BB/YYYY/NNNN) from search results + • page_number: Page number for pagination (1-indexed, default: 1) + + Output Format: + • Clean Markdown text with human rights case structure preserved + • Organized sections: applicant info, violation claims, court analysis, ruling + • Proper formatting for human rights law citations and references + • Paginated content with navigation information + + Individual Application Decision Content: + • Complete human rights violation analysis + • Detailed examination of fundamental rights claims + • Citation of constitutional articles and international human rights law + • Final determination on rights violations with remedies + • Analysis of domestic court proceedings and their adequacy + • Individual remedy recommendations and compensation + + Use for: + • Reading full human rights violation decisions + • Human rights law research and precedent analysis + • Understanding constitutional rights protection standards + • Individual petition strategy development + • Academic human rights and constitutional law study + • Civil society monitoring of rights violations + """ + return await call_mcp_tool("get_anayasa_bireysel_basvuru_document_markdown", { + "document_url": document_url, "page_number": page_number + }) + +@app.post("/api/kik/search", tags=["KİK"], summary="Search Public Procurement Authority") +async def search_kik(request: KikSearchRequest): + """ + Search Public Procurement Authority (Kamu İhale Kurulu - KIK) decisions with comprehensive filtering for public procurement law and administrative dispute research. + + The Public Procurement Authority (KIK) is Turkey's procurement oversight body, responsible for + regulating public procurement processes, resolving procurement disputes, and issuing interpretive + decisions on public contracting laws. This tool provides access to official procurement decisions + and regulatory guidance. + + Key Features: + • Decision type filtering (Disputes, Regulatory, Court decisions) + • Decision date range filtering for temporal analysis + • Applicant and procuring entity filtering + • Tender subject and content-based search + • Decision number and reference tracking + • Comprehensive metadata extraction + + Public Procurement Decision Types: + • Uyuşmazlık Kararları: Dispute resolution decisions + • Düzenleyici Kararlar: Regulatory and interpretive decisions + • Mahkeme Kararları: Court decisions and judicial precedents + + Procurement Law Areas: + • Tender procedure compliance and violations + • Bid evaluation and award criteria disputes + • Contractor qualification and eligibility + • Contract modification and scope changes + • Performance guarantees and penalty applications + • Public procurement ethics and transparency + • Emergency procurement and exceptional procedures + + Use cases: + • Public procurement law research and compliance guidance + • Tender dispute resolution precedent analysis + • Government contracting risk assessment + • Procurement policy and regulatory interpretation + • Academic public administration and law study + • Legal strategy development for procurement disputes + + Returns structured procurement authority data with comprehensive case metadata. + Use get_kik_document for full decision texts with detailed reasoning. + """ + args = {} + for field in ["karar_tipi", "karar_metni", "basvuru_konusu_ihale", "karar_tarihi_baslangic"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_kik_decisions", args) + +@app.get("/api/kik/document/{decision_id}", tags=["KİK"]) +async def get_kik_document(decision_id: str): + """ + Retrieve the full text of a Public Procurement Authority (KIK) decision in paginated Markdown format. + + This tool fetches complete public procurement decisions and converts them from PDF to clean, + readable Markdown format. Content is paginated into manageable chunks for processing lengthy + procurement law decisions and regulatory interpretations. + + Input Requirements: + • decision_id: KIK decision ID (base64 encoded karar_id) from search results + • Decision ID must be non-empty string + + Output Format: + • Clean Markdown text converted from original PDF documents + • Organized sections: case summary, legal analysis, regulatory interpretation, decision + • Proper formatting for procurement law citations and regulatory references + • Paginated content with navigation information + • Metadata including PDF source link and document information + + Public Procurement Decision Content: + • Complete procurement dispute analysis and resolution + • Detailed examination of tender procedures and compliance + • Citation of procurement laws, regulations, and precedents + • Final determination on procurement violations with corrective measures + • Regulatory guidance and policy interpretations + • Contractor liability and penalty determinations + + Use for: + • Reading full public procurement authority decisions + • Procurement law research and precedent analysis + • Government contracting compliance and risk assessment + • Tender dispute resolution strategy development + • Academic public administration and procurement law study + • Policy analysis and regulatory interpretation + """ + return await call_mcp_tool("get_kik_document_markdown", {"decision_id": decision_id}) + +@app.post("/api/rekabet/search", tags=["Rekabet"], summary="Search Competition Authority") +async def search_rekabet(request: RekabetSearchRequest): + """ + Search Competition Authority (Rekabet Kurumu) decisions with comprehensive filtering for competition law and antitrust research. + + The Competition Authority (Rekabet Kurumu) is Turkey's competition authority, responsible for enforcing antitrust laws, + preventing anti-competitive practices, and regulating mergers and acquisitions. This tool + provides access to official competition law decisions and regulatory interpretations. + + Key Features: + • Decision type filtering (Mergers, Violations, Exemptions, etc.) + • Title and content-based text search with exact phrase matching + • Publication date filtering + • Case year and decision number filtering + • Pagination support for large result sets + + Competition Law Decision Types: + • Birleşme ve Devralma: Merger and acquisition approvals + • Rekabet İhlali: Competition violation investigations + • Muafiyet: Exemption and negative clearance decisions + • Geçici Tedbir: Interim measures and emergency orders + • Sektör İncelemesi: Sector inquiry and market studies + • Diğer: Other regulatory and interpretive decisions + + Competition Law Areas: + • Anti-competitive agreements and cartels + • Abuse of dominant market position + • Merger control and market concentration + • Vertical agreements and distribution restrictions + • Unfair competition and consumer protection + • Market definition and economic analysis + + Advanced Search: + • Exact phrase matching with double quotes for precise legal terms + • Content search across full decision texts (PdfText parameter) + • Title search for specific case names or topics + • Date range filtering for temporal analysis + + Example for exact phrase search: PdfText=\"tender process\" consultancy + + Use cases: + • Competition law research and precedent analysis + • Merger and acquisition due diligence + • Antitrust compliance and risk assessment + • Market analysis and competitive intelligence + • Academic competition economics study + • Legal strategy development for competition cases + + Returns structured competition authority data with comprehensive metadata. + Use get_rekabet_document for full decision texts (paginated PDF conversion). + """ + args = {"page": request.page} + for field in ["KararTuru", "PdfText", "YayinlanmaTarihi"]: + if getattr(request, field): + args[field] = getattr(request, field) + return await call_mcp_tool("search_rekabet_kurumu_decisions", args) + +@app.get("/api/rekabet/document/{karar_id}", tags=["Rekabet"]) +async def get_rekabet_document(karar_id: str, page_number: int = 1): + """ + Retrieve the full text of a Competition Authority (Rekabet Kurumu) decision in paginated Markdown format converted from PDF. + + This tool fetches complete competition authority decisions and converts them from PDF to clean, + readable Markdown format. Content is paginated for easier processing of lengthy competition law decisions. + + Input Requirements: + • karar_id: GUID (kararId) from search_rekabet_kurumu_decisions results + • page_number: Page number for pagination (1-indexed, default: 1) + + Output Format: + • Clean Markdown text converted from original PDF documents + • Organized sections: case summary, market analysis, legal reasoning, decision + • Proper formatting for competition law citations and references + • Paginated content with navigation information + • Metadata including PDF source link and document information + + Competition Authority Decision Content: + • Complete competition law analysis and market assessment + • Detailed examination of anti-competitive practices + • Economic analysis and market definition studies + • Citation of competition laws, regulations, and precedents + • Final determination on competition violations with remedies + • Merger and acquisition approval conditions + • Regulatory guidance and policy interpretations + + Use for: + • Reading full competition authority decisions + • Competition law research and precedent analysis + • Market analysis and economic impact assessment + • Antitrust compliance and risk evaluation + • Academic competition economics and law study + • Legal strategy development for competition cases + """ + return await call_mcp_tool("get_rekabet_kurumu_document", { + "karar_id": karar_id, "page_number": page_number + }) + +# ============================================================================ +# SAYISTAY (COURT OF ACCOUNTS) ENDPOINTS - 6 TOOLS +# ============================================================================ + +@app.post("/api/sayistay/search-genel-kurul", tags=["Sayıştay"], summary="Search Court of Accounts (General Assembly)") +async def search_sayistay_genel_kurul(request: SayistaySearchRequest): + """ + Search Sayıştay Genel Kurul (General Assembly) decisions - highest-level audit precedents and interpretive rulings with keyword-based filtering. + + The General Assembly represents the highest decision-making body of Turkey's Court of Accounts, + issuing authoritative interpretations of audit laws, precedential rulings on complex accountability + issues, and binding guidance for audit practice. These decisions establish fundamental principles + for public financial oversight and accountability standards. + + Key Features: + • Keyword-based search with AND logic for precise case finding + • Highest-level audit precedent identification + • Interpretive ruling analysis and legal guidance extraction + • Complex accountability issue resolution tracking + • Pagination support for comprehensive result sets + + General Assembly Decision Authority: + • Ultimate audit law interpretation and clarification + • Precedential rulings binding on all audit chambers + • Complex inter-chamber jurisdiction dispute resolution + • Policy guidance for audit methodology and standards + • Final determination on constitutional audit questions + + Public Audit Areas: + • Government budget execution and compliance + • Public institution financial management + • State-owned enterprise oversight and accountability + • Local government and municipal audit standards + • Public procurement oversight and compliance monitoring + • Performance audit methodology and effectiveness standards + • Public revenue collection and tax administration audit + + Use Cases: + • Research authoritative audit law interpretations + • Find binding precedents for complex audit questions + • Study evolution of public accountability standards + • Analyze audit methodology development and refinement + • Academic public administration and audit research + • Government policy and accountability framework analysis + + Returns structured General Assembly data with comprehensive legal precedent metadata. + Use get_sayistay_genel_kurul_document for full decision texts with detailed reasoning. + """ + return await call_mcp_tool("search_sayistay_genel_kurul", { + "keywords": request.keywords, "page_to_fetch": request.page_to_fetch + }) + +@app.get("/api/sayistay/genel-kurul-document", tags=["Sayıştay"]) +async def get_sayistay_genel_kurul_document(document_url: str, page_number: int = 1): + """ + Retrieve the full text of a Sayıştay Genel Kurul decision document in Markdown format for detailed analysis. + + This tool converts the original General Assembly decision document into clean, + readable Markdown format suitable for legal analysis and research. + + Input Requirements: + • decision_id: Use the ID from search_sayistay_genel_kurul results + • Decision ID must be non-empty string + + Output Format: + • Clean Markdown text with legal formatting preserved + • Structured content with reasoning and conclusions + • Removes technical artifacts from source documents + + Use for: + • Detailed analysis of audit precedents + • Research on public accountability standards + • Citation and reference building + • Legal interpretation and case study development + """ + return await call_mcp_tool("get_sayistay_genel_kurul_document_markdown", { + "document_url": document_url, "page_number": page_number + }) + +@app.post("/api/sayistay/search-temyiz-kurulu", tags=["Sayıştay"], summary="Search Court of Accounts (Appeals Board)") +async def search_sayistay_temyiz_kurulu(request: SayistaySearchRequest): + """ + Search Sayıştay Temyiz Kurulu (Appeals Board) decisions - appellate review of audit chamber findings with advanced filtering and institutional analysis. + + The Appeals Board serves as the intermediate appellate authority in Turkey's audit system, + reviewing first-instance chamber decisions on audit findings, liability determinations, + and sanctions. Appeals Board decisions refine audit standards and ensure consistency + across different audit chambers. + + Key Features: + • Chamber-specific filtering for targeted appeals analysis + • Institutional type categorization for audit pattern analysis + • Decision and account year filtering for temporal trends + • Audit subject matter classification and content search + • Appeal outcome tracking and precedent identification + • Pagination support for comprehensive coverage + + Appeals Board Authority: + • Review and modification of chamber audit findings + • Standardization of audit liability determinations + • Consistency enforcement across audit chambers + • Intermediate precedent development for audit practice + • Quality control for first-instance audit decisions + + Audit Review Areas: + • Government agency financial accountability appeals + • Municipality and local government audit review + • State enterprise oversight and performance audit appeals + • Educational institution audit finding review + • Healthcare system financial accountability appeals + • Public procurement oversight and compliance review + • Tax administration and revenue audit appeals + + Use Cases: + • Research audit appeals patterns and outcomes + • Study chamber decision consistency and standards + • Analyze audit liability determination evolution + • Find precedents for specific audit finding types + • Track institutional audit patterns and compliance + • Academic public accountability and audit law research + + Returns structured Appeals Board data with comprehensive appellate analysis metadata. + Use get_sayistay_temyiz_kurulu_document for full appeals decisions with detailed reasoning. + """ + return await call_mcp_tool("search_sayistay_temyiz_kurulu", { + "keywords": request.keywords, "page_to_fetch": request.page_to_fetch + }) + +@app.get("/api/sayistay/temyiz-kurulu-document", tags=["Sayıştay"]) +async def get_sayistay_temyiz_kurulu_document(document_url: str, page_number: int = 1): + """ + Retrieve the full text of a Sayıştay Temyiz Kurulu decision document in Markdown format for detailed appeals analysis. + + This tool converts the original Appeals Board decision document into clean, + readable Markdown format for analysis of appellate reasoning and standards. + + Input Requirements: + • decision_id: Use the ID from search_sayistay_temyiz_kurulu results + • Decision ID must be non-empty string + + Output Format: + • Clean Markdown text with appellate reasoning preserved + • Structured content with original findings and appeals analysis + • Removes technical artifacts from source documents + + Use for: + • Analysis of appeals board reasoning and standards + • Research on audit liability determination evolution + • Understanding chamber decision review criteria + • Precedent analysis for audit appeal cases + """ + return await call_mcp_tool("get_sayistay_temyiz_kurulu_document_markdown", { + "document_url": document_url, "page_number": page_number + }) + +@app.post("/api/sayistay/search-daire", tags=["Sayıştay"], summary="Search Court of Accounts (Chambers)") +async def search_sayistay_daire(request: SayistaySearchRequest): + """ + Search Sayıştay Daire (Chamber) decisions - first-instance audit findings and sanctions from individual audit chambers with comprehensive filtering and subject categorization. + + Chamber decisions represent first-instance audit findings, sanctions, and + liability determinations issued by specialized audit chambers. These form + the foundation of Turkey's public financial accountability system. + + Key Features: + • Chamber-specific filtering (8 specialized audit chambers) + • Decision and account year filtering (2012-2025) + • Public administration type categorization + • Subject matter classification and full-text search + • Audit report tracking and institutional analysis + + Use Cases: + • Research specific audit findings and sanctions + • Study chamber specialization and jurisdiction + • Analyze audit patterns by institution type + • Find precedents for financial irregularities + • Track audit evolution across fiscal years + """ + return await call_mcp_tool("search_sayistay_daire", { + "keywords": request.keywords, "page_to_fetch": request.page_to_fetch + }) + +@app.get("/api/sayistay/daire-document", tags=["Sayıştay"]) +async def get_sayistay_daire_document(document_url: str, page_number: int = 1): + """ + Retrieve the full text of a Sayıştay Daire decision document in Markdown format for detailed audit findings analysis. + + This tool converts the original chamber decision document into clean, + readable Markdown format for analysis of first-instance audit findings. + + Input Requirements: + • decision_id: Use the ID from search_sayistay_daire results + • Decision ID must be non-empty string + + Output Format: + • Clean Markdown text with audit findings preserved + • Structured content with violations and sanctions + • Removes technical artifacts from source documents + + Use for: + • Detailed analysis of audit findings and methodology + • Research on specific types of financial irregularities + • Understanding chamber jurisdiction and specialization + • Case study development for audit training and compliance + """ + return await call_mcp_tool("get_sayistay_daire_document_markdown", { + "document_url": document_url, "page_number": page_number + }) + +# ============================================================================ +# ADDITIONAL API ENDPOINTS +# ============================================================================ + +@app.get("/api/databases") +async def list_databases(): + """Comprehensive database information with tool mappings""" + return { + "total_tools": 33, + "databases": { + "yargitay": { + "name": "Yargıtay (Court of Cassation)", + "description": "Turkey's Supreme Court for civil and criminal matters", + "tools": 4, + "chambers": 52, + "search_tools": ["search_yargitay", "search_yargitay_bedesten"], + "document_tools": ["get_yargitay_document", "get_yargitay_bedesten_document"] + }, + "danistay": { + "name": "Danıştay (Council of State)", + "description": "Turkey's Supreme Administrative Court", + "tools": 5, + "chambers": 27, + "search_tools": ["search_danistay_keyword", "search_danistay_detailed", "search_danistay_bedesten"], + "document_tools": ["get_danistay_document", "get_danistay_bedesten_document"] + }, + "anayasa": { + "name": "Anayasa Mahkemesi (Constitutional Court)", + "description": "Constitutional review and individual applications", + "tools": 4, + "features": ["norm_control", "individual_applications", "human_rights"] + }, + "rekabet": { + "name": "Rekabet Kurumu (Competition Authority)", + "description": "Antitrust and merger control", + "tools": 2, + "coverage": ["mergers", "cartels", "market_abuse", "sector_inquiries"] + }, + "kik": { + "name": "Kamu İhale Kurulu (Public Procurement Authority)", + "description": "Government procurement disputes", + "tools": 2, + "coverage": ["procurement_disputes", "regulatory_decisions", "tender_violations"] + }, + "sayistay": { + "name": "Sayıştay (Court of Accounts)", + "description": "Public audit and financial accountability", + "tools": 6, + "levels": ["general_assembly", "appeals_board", "audit_chambers"] + }, + "emsal": { + "name": "Emsal (UYAP Precedents)", + "description": "Cross-court precedent database", + "tools": 2, + "coverage": ["multi_court", "precedent_analysis"] + }, + "uyusmazlik": { + "name": "Uyuşmazlık Mahkemesi (Jurisdictional Disputes)", + "description": "Inter-court jurisdiction disputes", + "tools": 2, + "specialization": "jurisdictional_conflicts" + }, + "bedesten_courts": { + "name": "Bedesten API Courts (Local/Appellate/KYB)", + "description": "First instance, appellate, and extraordinary appeal courts", + "tools": 6, + "courts": ["yerel_hukuk", "istinaf_hukuk", "kyb"], + "coverage": "complete_court_hierarchy" + } + } + } + +@app.get("/api/stats") +async def get_statistics(): + """Comprehensive API statistics and capabilities""" + uptime = (datetime.now() - SERVER_START_TIME).total_seconds() + return { + "server": { + "uptime_seconds": uptime, + "start_time": SERVER_START_TIME.isoformat(), + "version": "1.0.0", + "status": "operational" + }, + "coverage": { + "total_tools": 33, + "total_databases": 9, + "total_chambers": 79, # 52 Yargıtay + 27 Danıştay + "search_tools": 16, + "document_tools": 17 + }, + "capabilities": { + "advanced_search_operators": True, + "exact_phrase_search": True, + "date_range_filtering": True, + "chamber_filtering": True, + "boolean_logic": True, + "wildcard_search": True, + "pagination": True, + "markdown_conversion": True, + "pdf_processing": True, + "dual_api_support": True + }, + "legal_coverage": { + "supreme_courts": ["Yargıtay", "Danıştay"], + "constitutional_law": "Anayasa Mahkemesi", + "administrative_law": "Full coverage", + "competition_law": "Rekabet Kurumu", + "public_procurement": "KİK", + "public_audit": "Sayıştay", + "court_hierarchy": "Complete (Local → Appellate → Supreme)", + "specialized_courts": ["Uyuşmazlık", "Constitutional", "Administrative"] + } + } + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/saidsurucu-yargi-mcp-f5fa007/kik_mcp_module/__init__.py b/saidsurucu-yargi-mcp-f5fa007/kik_mcp_module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/saidsurucu-yargi-mcp-f5fa007/kik_mcp_module/client.py b/saidsurucu-yargi-mcp-f5fa007/kik_mcp_module/client.py new file mode 100644 index 0000000..5a3d51a --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/kik_mcp_module/client.py @@ -0,0 +1,1199 @@ +# kik_mcp_module/client.py +import asyncio +from playwright.async_api import ( + async_playwright, + Page, + BrowserContext, + Browser, + Error as PlaywrightError, + TimeoutError as PlaywrightTimeoutError +) +from bs4 import BeautifulSoup +import logging +from typing import Dict, Any, List, Optional +import urllib.parse +import base64 # Base64 için +import re +import html as html_parser +from markitdown import MarkItDown +import os +import math +import io +import random + +from .models import ( + KikSearchRequest, + KikDecisionEntry, + KikSearchResult, + KikDocumentMarkdown, + KikKararTipi +) + +logger = logging.getLogger(__name__) + +class KikApiClient: + BASE_URL = "https://ekap.kik.gov.tr" + SEARCH_PAGE_PATH = "/EKAP/Vatandas/kurulkararsorgu.aspx" + FIELD_LOCATORS = { + "karar_tipi_radio_group": "input[name='ctl00$ContentPlaceHolder1$kurulKararTip']", + "karar_no": "input[name='ctl00$ContentPlaceHolder1$txtKararNo']", + "karar_tarihi_baslangic": "input[name='ctl00$ContentPlaceHolder1$etKararTarihBaslangic$EkapTakvimTextBox_etKararTarihBaslangic']", + "karar_tarihi_bitis": "input[name='ctl00$ContentPlaceHolder1$etKararTarihBitis$EkapTakvimTextBox_etKararTarihBitis']", + "resmi_gazete_sayisi": "input[name='ctl00$ContentPlaceHolder1$txtResmiGazeteSayisi']", + "resmi_gazete_tarihi": "input[name='ctl00$ContentPlaceHolder1$etResmiGazeteTarihi$EkapTakvimTextBox_etResmiGazeteTarihi']", + "basvuru_konusu_ihale": "input[name='ctl00$ContentPlaceHolder1$txtBasvuruKonusuIhale']", + "basvuru_sahibi": "input[name='ctl00$ContentPlaceHolder1$txtSikayetci']", + "ihaleyi_yapan_idare": "input[name='ctl00$ContentPlaceHolder1$txtIhaleyiYapanIdare']", + "yil": "select[name='ctl00$ContentPlaceHolder1$ddlYil']", + "karar_metni": "input[name='ctl00$ContentPlaceHolder1$txtKararMetni']", + "search_button_id": "ctl00_ContentPlaceHolder1_btnAra" + } + RESULTS_TABLE_ID = "grdKurulKararSorguSonuc" + NO_RESULTS_MESSAGE_SELECTOR = "div#ctl00_MessageContent1" + VALIDATION_SUMMARY_SELECTOR = "div#ctl00_ValidationSummary1" + MODAL_CLOSE_BUTTON_SELECTOR = "div#detayPopUp.in a#btnKapatPencere_0.close" + DOCUMENT_MARKDOWN_CHUNK_SIZE = 5000 + + def __init__(self, request_timeout: float = 60000): + self.playwright_instance: Optional[async_playwright] = None + self.browser: Optional[Browser] = None + self.context: Optional[BrowserContext] = None + self.page: Optional[Page] = None + self.request_timeout = request_timeout + self._lock = asyncio.Lock() + + async def _ensure_playwright_ready(self, force_new_page: bool = False): + async with self._lock: + browser_recreated = False + context_recreated = False + if not self.playwright_instance: + self.playwright_instance = await async_playwright().start() + if not self.browser or not self.browser.is_connected(): + if self.browser: await self.browser.close() + # Ultra stealth browser configuration + self.browser = await self.playwright_instance.chromium.launch( + headless=True, + args=[ + # Disable automation indicators + '--no-first-run', + '--no-default-browser-check', + '--disable-dev-shm-usage', + '--disable-extensions', + '--disable-gpu', + '--disable-default-apps', + '--disable-translate', + '--disable-blink-features=AutomationControlled', + '--disable-ipc-flooding-protection', + '--disable-renderer-backgrounding', + '--disable-backgrounding-occluded-windows', + '--disable-client-side-phishing-detection', + '--disable-sync', + '--disable-features=TranslateUI,BlinkGenPropertyTrees', + '--disable-component-extensions-with-background-pages', + '--no-sandbox', # Sometimes needed for headless + '--disable-web-security', + '--disable-features=VizDisplayCompositor', + # Language and locale + '--lang=tr-TR', + '--accept-lang=tr-TR,tr;q=0.9,en;q=0.8', + # Performance optimizations + '--memory-pressure-off', + '--max_old_space_size=4096', + ] + ) + browser_recreated = True + if not self.context or browser_recreated: + if self.context: await self.context.close() + if not self.browser: raise PlaywrightError("Browser not initialized.") + # Ultra realistic context configuration + self.context = await self.browser.new_context( + user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36", + viewport={'width': 1920, 'height': 1080}, + screen={'width': 1920, 'height': 1080}, + device_scale_factor=1.0, + is_mobile=False, + has_touch=False, + # Localization + locale='tr-TR', + timezone_id='Europe/Istanbul', + # Realistic browser features + java_script_enabled=True, + accept_downloads=True, + ignore_https_errors=True, + # Color scheme and media + color_scheme='light', + reduced_motion='no-preference', + forced_colors='none', + # Additional headers for realism + extra_http_headers={ + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'tr-TR,tr;q=0.9,en;q=0.8', + 'Cache-Control': 'max-age=0', + 'DNT': '1', + 'Upgrade-Insecure-Requests': '1', + 'Sec-Ch-Ua': '"Google Chrome";v="131", "Chromium";v="131", "Not_A Brand";v="24"', + 'Sec-Ch-Ua-Mobile': '?0', + 'Sec-Ch-Ua-Platform': '"Windows"', + 'Sec-Fetch-Dest': 'document', + 'Sec-Fetch-Mode': 'navigate', + 'Sec-Fetch-Site': 'none', + 'Sec-Fetch-User': '?1', + }, + # Permissions to appear realistic + permissions=['geolocation'], + geolocation={'latitude': 41.0082, 'longitude': 28.9784}, # Istanbul + ) + context_recreated = True + if not self.page or self.page.is_closed() or force_new_page or context_recreated or browser_recreated: + if self.page and not self.page.is_closed(): await self.page.close() + if not self.context: raise PlaywrightError("Context is None.") + self.page = await self.context.new_page() + if not self.page: raise PlaywrightError("Failed to create new page.") + self.page.set_default_navigation_timeout(self.request_timeout) + self.page.set_default_timeout(self.request_timeout) + + # CRITICAL: Anti-detection JavaScript injection + await self._inject_stealth_scripts() + if not self.page or self.page.is_closed(): + raise PlaywrightError("Playwright page initialization failed.") + logger.debug("_ensure_playwright_ready completed.") + + async def close_client_session(self): + async with self._lock: + # ... (öncekiyle aynı) + if self.page and not self.page.is_closed(): await self.page.close(); self.page = None + if self.context: await self.context.close(); self.context = None + if self.browser: await self.browser.close(); self.browser = None + if self.playwright_instance: await self.playwright_instance.stop(); self.playwright_instance = None + logger.info("KikApiClient (Playwright): Resources closed.") + + async def _inject_stealth_scripts(self): + """ + Inject comprehensive stealth JavaScript to evade bot detection. + Overrides navigator properties and other fingerprinting vectors. + """ + if not self.page: + logger.warning("Cannot inject stealth scripts: page is None") + return + + logger.debug("Injecting comprehensive stealth scripts...") + + stealth_script = ''' + // Override navigator.webdriver + Object.defineProperty(navigator, 'webdriver', { + get: () => undefined, + configurable: true + }); + + // Override navigator properties to appear more human + Object.defineProperty(navigator, 'languages', { + get: () => ['tr-TR', 'tr', 'en-US', 'en'], + configurable: true + }); + + Object.defineProperty(navigator, 'platform', { + get: () => 'Win32', + configurable: true + }); + + Object.defineProperty(navigator, 'vendor', { + get: () => 'Google Inc.', + configurable: true + }); + + Object.defineProperty(navigator, 'deviceMemory', { + get: () => 8, + configurable: true + }); + + Object.defineProperty(navigator, 'hardwareConcurrency', { + get: () => 8, + configurable: true + }); + + Object.defineProperty(navigator, 'maxTouchPoints', { + get: () => 0, + configurable: true + }); + + // Override plugins to appear realistic + Object.defineProperty(navigator, 'plugins', { + get: () => { + return [ + { + 0: {type: "application/x-google-chrome-pdf", suffixes: "pdf", description: "Portable Document Format", enabledPlugin: Plugin}, + description: "Portable Document Format", + filename: "internal-pdf-viewer", + length: 1, + name: "Chrome PDF Plugin" + }, + { + 0: {type: "application/pdf", suffixes: "pdf", description: "", enabledPlugin: Plugin}, + description: "", + filename: "mhjfbmdgcfjbbpaeojofohoefgiehjai", + length: 1, + name: "Chrome PDF Viewer" + } + ]; + }, + configurable: true + }); + + // Override permissions + const originalQuery = window.navigator.permissions.query; + window.navigator.permissions.query = (parameters) => ( + parameters.name === 'notifications' ? + Promise.resolve({ state: Notification.permission }) : + originalQuery(parameters) + ); + + // Override WebGL rendering context + const getParameter = WebGLRenderingContext.prototype.getParameter; + WebGLRenderingContext.prototype.getParameter = function(parameter) { + if (parameter === 37445) { // UNMASKED_VENDOR_WEBGL + return 'Intel Inc.'; + } + if (parameter === 37446) { // UNMASKED_RENDERER_WEBGL + return 'Intel(R) Iris(R) Plus Graphics 640'; + } + return getParameter(parameter); + }; + + // Override canvas fingerprinting + const toBlob = HTMLCanvasElement.prototype.toBlob; + const toDataURL = HTMLCanvasElement.prototype.toDataURL; + const getImageData = CanvasRenderingContext2D.prototype.getImageData; + + const noisify = (canvas, context) => { + const imageData = context.getImageData(0, 0, canvas.width, canvas.height); + for (let i = 0; i < imageData.data.length; i += 4) { + imageData.data[i] += Math.floor(Math.random() * 10) - 5; + imageData.data[i + 1] += Math.floor(Math.random() * 10) - 5; + imageData.data[i + 2] += Math.floor(Math.random() * 10) - 5; + } + context.putImageData(imageData, 0, 0); + }; + + Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { + value: function(callback, type, encoderOptions) { + noisify(this, this.getContext('2d')); + return toBlob.apply(this, arguments); + } + }); + + Object.defineProperty(HTMLCanvasElement.prototype, 'toDataURL', { + value: function(type, encoderOptions) { + noisify(this, this.getContext('2d')); + return toDataURL.apply(this, arguments); + } + }); + + // Override AudioContext for audio fingerprinting + const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); + const originalAnalyser = audioCtx.createAnalyser; + audioCtx.createAnalyser = function() { + const analyser = originalAnalyser.apply(this, arguments); + const getFloatFrequencyData = analyser.getFloatFrequencyData; + analyser.getFloatFrequencyData = function(array) { + getFloatFrequencyData.apply(this, arguments); + for (let i = 0; i < array.length; i++) { + array[i] += Math.random() * 0.0001; + } + }; + return analyser; + }; + + // Override screen properties + Object.defineProperty(window.screen, 'colorDepth', { + get: () => 24, + configurable: true + }); + + Object.defineProperty(window.screen, 'pixelDepth', { + get: () => 24, + configurable: true + }); + + // Override timezone + Date.prototype.getTimezoneOffset = function() { + return -180; // UTC+3 (Istanbul) + }; + + // Override document.cookie to prevent tracking + const originalCookieDescriptor = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie') || + Object.getOwnPropertyDescriptor(HTMLDocument.prototype, 'cookie'); + if (originalCookieDescriptor && originalCookieDescriptor.configurable) { + Object.defineProperty(document, 'cookie', { + get: function() { + return originalCookieDescriptor.get.call(this); + }, + set: function(val) { + console.log('Cookie set blocked:', val); + return originalCookieDescriptor.set.call(this, val); + }, + configurable: true + }); + } + + // Remove automation traces + delete window.cdc_adoQpoasnfa76pfcZLmcfl_Array; + delete window.cdc_adoQpoasnfa76pfcZLmcfl_Promise; + delete window.cdc_adoQpoasnfa76pfcZLmcfl_Symbol; + delete window.cdc_adoQpoasnfa76pfcZLmcfl_JSON; + delete window.cdc_adoQpoasnfa76pfcZLmcfl_Object; + delete window.cdc_adoQpoasnfa76pfcZLmcfl_Proxy; + + // Add realistic performance timing + if (window.performance && window.performance.timing) { + const timing = window.performance.timing; + const now = Date.now(); + Object.defineProperty(timing, 'navigationStart', { value: now - Math.floor(Math.random() * 1000) + 1000, configurable: false }); + Object.defineProperty(timing, 'loadEventEnd', { value: now - Math.floor(Math.random() * 100) + 100, configurable: false }); + } + + console.log('✓ Stealth scripts injected successfully'); + ''' + + try: + await self.page.add_init_script(stealth_script) + logger.debug("✅ Stealth scripts injected successfully") + except Exception as e: + logger.warning(f"⚠️ Failed to inject stealth scripts: {e}") + + async def _simulate_human_behavior(self, fast_mode: bool = True): + """ + Simulate realistic human behavior patterns to avoid detection. + Includes mouse movements, typing patterns, and natural delays. + + Args: + fast_mode: If True, use minimal timing for speed optimization + """ + if not self.page: + logger.warning("Cannot simulate human behavior: page is None") + return + + logger.debug("🤖 Simulating human behavior patterns...") + + try: + if fast_mode: + # ULTRA-FAST MODE: Minimal human behavior + viewport_size = self.page.viewport_size + if viewport_size and random.random() < 0.7: # 70% chance to do movement + width, height = viewport_size['width'], viewport_size['height'] + + # Single quick mouse movement + x = random.randint(200, width - 200) + y = random.randint(200, height - 200) + await self.page.mouse.move(x, y) + + # Brief scroll (50% chance) + if random.random() < 0.5: + await self.page.mouse.wheel(0, random.randint(50, 100)) + + # Ultra-minimal delay + await asyncio.sleep(random.uniform(0.05, 0.15)) # Reduced from 0.1-0.3 + + else: + # FULL MODE: Original comprehensive behavior + viewport_size = self.page.viewport_size + if viewport_size: + width, height = viewport_size['width'], viewport_size['height'] + + # Generate 3-5 random mouse movements + movements = random.randint(3, 5) + logger.debug(f" 🖱️ Performing {movements} random mouse movements") + + for i in range(movements): + x = random.randint(100, width - 100) + y = random.randint(100, height - 100) + + # Move mouse with realistic speed (not instant) + await self.page.mouse.move(x, y) + await asyncio.sleep(random.uniform(0.1, 0.3)) + + # 2. Scroll simulation + logger.debug(" 📜 Simulating scroll behavior") + scroll_amount = random.randint(100, 300) + await self.page.mouse.wheel(0, scroll_amount) + await asyncio.sleep(random.uniform(0.2, 0.5)) + + # Scroll back up + await self.page.mouse.wheel(0, -scroll_amount) + await asyncio.sleep(random.uniform(0.2, 0.4)) + + # 3. Random page interaction delays + await asyncio.sleep(random.uniform(0.5, 1.5)) + + logger.debug("✅ Human behavior simulation completed") + + except Exception as e: + logger.warning(f"⚠️ Human behavior simulation failed: {e}") + + async def _human_type(self, selector: str, text: str, clear_first: bool = True, fast_mode: bool = True): + """ + Type text with human-like patterns and delays. + + Args: + selector: CSS selector for the input element + text: Text to type + clear_first: Whether to clear the field first + fast_mode: If True, use minimal delays for speed optimization + """ + if not self.page: + logger.warning("Cannot perform human typing: page is None") + return + + try: + if fast_mode: + # FAST MODE: Direct fill for speed + await self.page.fill(selector, text) + await asyncio.sleep(random.uniform(0.02, 0.05)) # Reduced from 0.05-0.1 + else: + # FULL MODE: Character-by-character human typing + # Focus on the element first + await self.page.focus(selector) + await asyncio.sleep(random.uniform(0.1, 0.3)) + + # Clear field if requested + if clear_first: + await self.page.keyboard.press('Control+a') + await asyncio.sleep(random.uniform(0.05, 0.15)) + await self.page.keyboard.press('Delete') + await asyncio.sleep(random.uniform(0.05, 0.15)) + + # Type each character with human-like delays + for char in text: + await self.page.keyboard.type(char) + # Human typing speed: 50-150ms between characters + delay = random.uniform(0.05, 0.15) + + # Occasional longer pauses (thinking) + if random.random() < 0.1: # 10% chance + delay += random.uniform(0.2, 0.8) + + await asyncio.sleep(delay) + + # Brief pause after typing + await asyncio.sleep(random.uniform(0.2, 0.6)) + + logger.debug(f"✅ Human-typed '{text}' into {selector}") + + except Exception as e: + logger.warning(f"⚠️ Human typing failed: {e}") + + async def _human_click(self, selector: str, wait_before: bool = True, wait_after: bool = True, fast_mode: bool = True): + """ + Perform a human-like click with realistic delays and mouse movement. + + Args: + selector: CSS selector or element to click + wait_before: Whether to wait before clicking + wait_after: Whether to wait after clicking + fast_mode: If True, use minimal delays for speed optimization + """ + if not self.page: + logger.warning("Cannot perform human click: page is None") + return + + try: + if fast_mode: + # FAST MODE: Direct click with minimal delay + if wait_before: + await asyncio.sleep(random.uniform(0.02, 0.08)) # Reduced from 0.05-0.15 + + await self.page.click(selector) + + if wait_after: + await asyncio.sleep(random.uniform(0.02, 0.08)) # Reduced from 0.05-0.15 + + else: + # FULL MODE: Realistic mouse movement and timing + # Wait before clicking (thinking time) + if wait_before: + await asyncio.sleep(random.uniform(0.3, 0.8)) + + # Get element bounds for realistic mouse movement + element = await self.page.query_selector(selector) + if element: + box = await element.bounding_box() + if box: + # Move to element with slight randomness + center_x = box['x'] + box['width'] / 2 + center_y = box['y'] + box['height'] / 2 + + # Add small random offset + offset_x = random.uniform(-10, 10) + offset_y = random.uniform(-5, 5) + + await self.page.mouse.move(center_x + offset_x, center_y + offset_y) + await asyncio.sleep(random.uniform(0.1, 0.3)) + + # Perform click + await self.page.mouse.click(center_x + offset_x, center_y + offset_y) + + logger.debug(f"✅ Human-clicked {selector}") + else: + # Fallback to regular click + await self.page.click(selector) + logger.debug(f"✅ Fallback-clicked {selector}") + else: + logger.warning(f"⚠️ Element not found for human click: {selector}") + return + + # Wait after clicking (processing time) + if wait_after: + await asyncio.sleep(random.uniform(0.2, 0.6)) + + logger.debug(f"✅ Human-clicked {selector}") + + except Exception as e: + logger.warning(f"⚠️ Human click failed: {e}") + + async def _simulate_page_exploration(self, fast_mode: bool = True): + """ + Simulate natural page exploration before performing the main task. + This helps establish a more human-like session. + + Args: + fast_mode: If True, use minimal exploration for speed optimization + """ + if not self.page: + return + + logger.debug("🕵️ Simulating page exploration...") + + try: + if fast_mode: + # ULTRA-FAST MODE: Minimal exploration + await asyncio.sleep(random.uniform(0.05, 0.1)) # Reduced from 0.1-0.3 + + # Single mouse movement (optional) + try: + elements = await self.page.query_selector_all("input, button") + if elements and random.random() < 0.5: # 50% chance to skip + element = random.choice(elements) + box = await element.bounding_box() + if box: + center_x = box['x'] + box['width'] / 2 + center_y = box['y'] + box['height'] / 2 + await self.page.mouse.move(center_x, center_y) + except: + pass + + await asyncio.sleep(random.uniform(0.02, 0.05)) # Reduced from 0.05-0.15 + + else: + # FULL MODE: Comprehensive exploration + # 1. Brief pause to "read" the page + await asyncio.sleep(random.uniform(1.0, 2.5)) + + # 2. Move mouse to various UI elements (like a human would explore) + explore_selectors = [ + "h1", "h2", ".navbar", "#header", ".logo", + "input", "button", "a", ".form-group" + ] + + explored = 0 + for selector in explore_selectors: + elements = await self.page.query_selector_all(selector) + if elements and explored < 3: # Explore max 3 elements + element = random.choice(elements) + box = await element.bounding_box() + if box: + center_x = box['x'] + box['width'] / 2 + center_y = box['y'] + box['height'] / 2 + + await self.page.mouse.move(center_x, center_y) + await asyncio.sleep(random.uniform(0.3, 0.8)) + explored += 1 + + # 3. Small scroll to simulate reading + await self.page.mouse.wheel(0, random.randint(50, 150)) + await asyncio.sleep(random.uniform(0.5, 1.2)) + + logger.debug("✅ Page exploration completed") + + except Exception as e: + logger.debug(f"⚠️ Page exploration failed: {e}") + + def _parse_decision_entries_from_soup(self, soup: BeautifulSoup, search_karar_tipi: KikKararTipi) -> List[KikDecisionEntry]: + entries: List[KikDecisionEntry] = [] + table = soup.find("table", {"id": self.RESULTS_TABLE_ID}) + + logger.debug(f"Looking for table with ID: {self.RESULTS_TABLE_ID}") + if not table: + logger.warning(f"Table with ID '{self.RESULTS_TABLE_ID}' not found in HTML") + # Log available tables for debugging + all_tables = soup.find_all("table") + logger.debug(f"Found {len(all_tables)} tables in HTML") + for idx, tbl in enumerate(all_tables): + table_id = tbl.get('id', 'no-id') + table_class = tbl.get('class', 'no-class') + rows = tbl.find_all('tr') + logger.debug(f"Table {idx}: id='{table_id}', class='{table_class}', rows={len(rows)}") + + # If this looks like a results table, try to use it + if (table_id and ('grd' in table_id.lower() or 'kurul' in table_id.lower() or 'sonuc' in table_id.lower())) or \ + (isinstance(table_class, list) and any('grid' in cls.lower() or 'result' in cls.lower() for cls in table_class)) or \ + len(rows) > 3: # Table with multiple rows might be results + logger.info(f"Trying to parse table {idx} as potential results table: id='{table_id}'") + table = tbl + break + + if not table: + logger.error("No suitable results table found") + return entries + + rows = table.find_all("tr") + logger.info(f"Found {len(rows)} rows in results table") + + # Debug: Log first few rows structure + for i, row in enumerate(rows[:3]): + cells = row.find_all(["td", "th"]) + cell_texts = [cell.get_text(strip=True)[:30] for cell in cells] + logger.info(f"Row {i} structure: {len(cells)} cells: {cell_texts}") + + for row_idx, row in enumerate(rows): + # Skip first row (search bar with colspan=7) and second row (header with 6 cells) + if row_idx < 2: + logger.debug(f"Skipping header row {row_idx}") + continue + + cells = row.find_all("td") + logger.debug(f"Row {row_idx}: Found {len(cells)} cells") + + # Log cell contents for debugging + if cells and row_idx < 5: # Log first few data rows + for cell_idx, cell in enumerate(cells): + cell_text = cell.get_text(strip=True)[:50] # First 50 chars + logger.debug(f" Cell {cell_idx}: '{cell_text}...'") + + # Be more flexible with cell count - try 6 cells first, then adapt + if len(cells) >= 5: # At least 5 cells for minimum required data + try: + # Try to find preview button in first cell or any cell with a link + preview_button_tag = None + event_target = "" + + # Look for preview button in first few cells + for cell_idx in range(min(3, len(cells))): + cell = cells[cell_idx] + # Try multiple patterns for preview button (based on actual HTML structure) + preview_candidates = [ + cell.find("a", id="btnOnizle"), # Exact match + cell.find("a", id=re.compile(r"btnOnizle$")), + cell.find("a", id=re.compile(r"btn.*Onizle")), + cell.find("a", id=re.compile(r".*Onizle.*")), + cell.find("a", href=re.compile(r"__doPostBack")) + ] + + for candidate in preview_candidates: + if candidate and candidate.has_attr('href'): + match = re.search(r"__doPostBack\('([^']*)','([^']*)'\)", candidate['href']) + if match: + event_target = match.group(1) + preview_button_tag = candidate + logger.debug(f"Row {row_idx}: Found event_target '{event_target}' in cell {cell_idx}") + break + + if preview_button_tag: + break + + if not preview_button_tag: + logger.debug(f"Row {row_idx}: No preview button found in any cell") + # Log what links we found + for cell_idx, cell in enumerate(cells[:3]): + links_in_cell = cell.find_all("a") + logger.debug(f" Cell {cell_idx}: {len(links_in_cell)} links") + for link in links_in_cell[:2]: + logger.debug(f" Link id='{link.get('id')}', href='{link.get('href', '')[:50]}...'") + + # Try to find decision data spans with more flexible patterns + karar_no_span = None + karar_tarihi_span = None + idare_span = None + basvuru_sahibi_span = None + ihale_span = None + + # Try different span patterns for karar no (usually in cell 1) + for cell_idx in range(min(4, len(cells))): + if not karar_no_span: + cell = cells[cell_idx] + candidates = [ + cell.find("span", id="lblKno"), # Exact match based on actual HTML + cell.find("span", id=re.compile(r"lblKno$")), + cell.find("span", id=re.compile(r".*Kno.*")), + cell.find("span", id=re.compile(r".*KararNo.*")), + cell.find("span", id=re.compile(r".*No.*")) + ] + for candidate in candidates: + if candidate and candidate.get_text(strip=True): + karar_no_span = candidate + logger.debug(f"Row {row_idx}: Found karar_no in cell {cell_idx}") + break + + # Try different patterns for karar tarihi (usually in cell 2) + for cell_idx in range(min(4, len(cells))): + if not karar_tarihi_span: + cell = cells[cell_idx] + candidates = [ + cell.find("span", id="lblKtar"), # Exact match based on actual HTML + cell.find("span", id=re.compile(r"lblKtar$")), + cell.find("span", id=re.compile(r".*Ktar.*")), + cell.find("span", id=re.compile(r".*Tarih.*")), + cell.find("span", id=re.compile(r".*Date.*")) + ] + for candidate in candidates: + if candidate and candidate.get_text(strip=True): + # Check if it looks like a date + text = candidate.get_text(strip=True) + if re.match(r'\d{1,2}[./]\d{1,2}[./]\d{4}', text): + karar_tarihi_span = candidate + logger.debug(f"Row {row_idx}: Found karar_tarihi in cell {cell_idx}") + break + + # Find other spans in remaining cells (if we have 6 cells) - using exact IDs + if len(cells) >= 6: + idare_span = cells[3].find("span", id="lblIdare") or cells[3].find("span") + basvuru_sahibi_span = cells[4].find("span", id="lblSikayetci") or cells[4].find("span") + ihale_span = cells[5].find("span", id="lblIhale") or cells[5].find("span") + elif len(cells) == 5: + # Adjust for 5-cell layout + idare_span = cells[2].find("span") if cells[2] != cells[1] else None + basvuru_sahibi_span = cells[3].find("span") if len(cells) > 3 else None + ihale_span = cells[4].find("span") if len(cells) > 4 else None + + # Log what we found + logger.debug(f"Row {row_idx}: karar_no_span={karar_no_span is not None}, " + f"karar_tarihi_span={karar_tarihi_span is not None}, " + f"event_target={bool(event_target)}") + + # For KIK, we need at least karar_no and karar_tarihi, event_target is helpful but not critical + if not (karar_no_span and karar_tarihi_span): + logger.debug(f"Row {row_idx}: Missing required fields (karar_no or karar_tarihi), skipping") + # Log what spans we found in cells + for i, cell in enumerate(cells): + spans = cell.find_all("span") + if spans: + span_info = [] + for s in spans: + span_id = s.get('id', 'no-id') + span_text = s.get_text(strip=True)[:20] + span_info.append(f"{span_id}:'{span_text}...'") + logger.debug(f" Cell {i} spans: {span_info}") + continue + + # If we don't have event_target, we can still create an entry but mark it specially + if not event_target: + logger.warning(f"Row {row_idx}: No event_target found, document retrieval won't work") + event_target = f"missing_target_row_{row_idx}" # Placeholder + + # Karar tipini arama parametresinden alıyoruz, çünkü HTML'de direkt olarak bulunmuyor. + try: + entry = KikDecisionEntry( + preview_event_target=event_target, + kararNo=karar_no_span.get_text(strip=True), + karar_tipi=search_karar_tipi, # Arama yapılan karar tipini ekle + kararTarihi=karar_tarihi_span.get_text(strip=True), + idare=idare_span.get_text(strip=True) if idare_span else None, + basvuruSahibi=basvuru_sahibi_span.get_text(strip=True) if basvuru_sahibi_span else None, + ihaleKonusu=ihale_span.get_text(strip=True) if ihale_span else None, + ) + entries.append(entry) + logger.info(f"Row {row_idx}: Successfully parsed decision: {entry.karar_no_str}") + except Exception as e: + logger.error(f"Row {row_idx}: Error creating KikDecisionEntry: {e}") + continue + + except Exception as e: + logger.error(f"Error parsing row {row_idx}: {e}", exc_info=True) + else: + logger.warning(f"Row {row_idx}: Expected at least 5 cells but found {len(cells)}, skipping") + if len(cells) > 0: + cell_texts = [cell.get_text(strip=True)[:50] for cell in cells[:3]] + logger.debug(f"Row {row_idx} cells preview: {cell_texts}") + + logger.info(f"Parsed {len(entries)} decision entries from {len(rows)} rows") + return entries + + def _parse_total_records_from_soup(self, soup: BeautifulSoup) -> int: + # ... (öncekiyle aynı) ... + try: + pager_div = soup.find("div", class_="gridToplamSayi") + if pager_div: + match = re.search(r"Toplam Kayıt Sayısı:(\d+)", pager_div.get_text(strip=True)) + if match: return int(match.group(1)) + except: pass + return 0 + + def _parse_current_page_from_soup(self, soup: BeautifulSoup) -> int: + # ... (öncekiyle aynı) ... + try: + pager_div = soup.find("div", class_="sayfalama") + if pager_div: + active_page_span = pager_div.find("span", class_="active") + if active_page_span: return int(active_page_span.get_text(strip=True)) + except: pass + return 1 + + async def search_decisions(self, search_params: KikSearchRequest) -> KikSearchResult: + await self._ensure_playwright_ready() + page = self.page + search_url = f"{self.BASE_URL}{self.SEARCH_PAGE_PATH}" + try: + if page.url != search_url: + await page.goto(search_url, wait_until="networkidle", timeout=self.request_timeout) + + # Simulate natural page exploration after navigation (FAST MODE) + await self._simulate_page_exploration(fast_mode=True) + + search_button_selector = f"a[id='{self.FIELD_LOCATORS['search_button_id']}']" + await page.wait_for_selector(search_button_selector, state="visible", timeout=self.request_timeout) + + current_karar_tipi_value = search_params.karar_tipi.value + radio_locator_selector = f"{self.FIELD_LOCATORS['karar_tipi_radio_group']}[value='{current_karar_tipi_value}']" + if not await page.locator(radio_locator_selector).is_checked(): + js_target_radio = f"ctl00$ContentPlaceHolder1${current_karar_tipi_value}" + logger.info(f"Selecting radio button: {js_target_radio}") + async with page.expect_navigation(wait_until="networkidle", timeout=self.request_timeout): + await page.evaluate(f"javascript:__doPostBack('{js_target_radio}','')") + # Ultra-fast wait for page to stabilize after radio button change + await page.wait_for_timeout(300) # Reduced from 1000ms + logger.info("Radio button selection completed") + + # Helper function for human-like form filling (FAST MODE) + async def human_fill_if_value(selector_key: str, value: Optional[str]): + if value is not None: + selector = self.FIELD_LOCATORS[selector_key] + await self._human_type(selector, value, fast_mode=True) + + # Karar No'yu KİK sitesine göndermeden önce '_' -> '/' dönüşümü yap + karar_no_for_kik_form = None + if search_params.karar_no: # search_params.karar_no Claude'dan '_' ile gelmiş olabilir + karar_no_for_kik_form = search_params.karar_no.replace('_', '/') + logger.info(f"Using karar_no '{karar_no_for_kik_form}' (transformed from '{search_params.karar_no}') for KIK form.") + + # Fill form fields with FAST human-like behavior + logger.info("Filling form fields with fast mode...") + + # Start with FAST mouse behavior simulation + await self._simulate_human_behavior(fast_mode=True) + + await human_fill_if_value('karar_metni', search_params.karar_metni) + await human_fill_if_value('karar_no', karar_no_for_kik_form) # Dönüştürülmüş halini kullan + await human_fill_if_value('karar_tarihi_baslangic', search_params.karar_tarihi_baslangic) + await human_fill_if_value('karar_tarihi_bitis', search_params.karar_tarihi_bitis) + await human_fill_if_value('resmi_gazete_sayisi', search_params.resmi_gazete_sayisi) + await human_fill_if_value('resmi_gazete_tarihi', search_params.resmi_gazete_tarihi) + await human_fill_if_value('basvuru_konusu_ihale', search_params.basvuru_konusu_ihale) + await human_fill_if_value('basvuru_sahibi', search_params.basvuru_sahibi) + await human_fill_if_value('ihaleyi_yapan_idare', search_params.ihaleyi_yapan_idare) + + if search_params.yil: + await page.select_option(self.FIELD_LOCATORS['yil'], value=search_params.yil) + await page.wait_for_timeout(50) # Reduced from 100ms + + logger.info("Form filling completed, preparing for search...") + + # Additional FAST human behavior before search + await self._simulate_human_behavior(fast_mode=True) + + action_is_search_button_click = (search_params.page == 1) + event_target_for_submit: str + + try: + if action_is_search_button_click: + event_target_for_submit = self.FIELD_LOCATORS['search_button_id'] + # Use human-like clicking for search button + search_button_selector = f"a[id='{event_target_for_submit}']" + logger.info(f"Performing human-like search button click...") + + try: + # Hide datepicker first to prevent interference + await page.evaluate("$('#ui-datepicker-div').hide()") + + # FAST Human-like click on search button + await self._human_click(search_button_selector, wait_before=True, wait_after=False, fast_mode=True) + + # Wait for navigation + await page.wait_for_load_state("networkidle", timeout=self.request_timeout) + logger.info("Search navigation completed successfully") + except Exception as e: + logger.warning(f"Human click failed, falling back to JavaScript: {e}") + # Hide datepicker and use JavaScript fallback + await page.evaluate("$('#ui-datepicker-div').hide()") + async with page.expect_navigation(wait_until="networkidle", timeout=self.request_timeout): + await page.evaluate(f"javascript:__doPostBack('{event_target_for_submit}','')") + logger.info("Search navigation completed via fallback") + else: + # Pagination - use original method for consistency + page_link_ctl_number = search_params.page + 2 + event_target_for_submit = f"ctl00$ContentPlaceHolder1$grdKurulKararSorguSonuc$ctl14$ctl{page_link_ctl_number:02d}" + logger.info(f"Executing pagination with event target: {event_target_for_submit}") + + async with page.expect_navigation(wait_until="networkidle", timeout=self.request_timeout): + await page.evaluate(f"javascript:__doPostBack('{event_target_for_submit}','')") + logger.info("Pagination navigation completed successfully") + except PlaywrightTimeoutError: + logger.warning("Search navigation timed out, but continuing...") + await page.wait_for_timeout(5000) # Longer wait if navigation fails + + # Ultra-fast wait time for results to load + logger.info("Waiting for search results to load...") + await page.wait_for_timeout(500) # Reduced from 1000ms + + results_table_dom_selector = f"table#{self.RESULTS_TABLE_ID}" + try: + # First wait for any tables to appear (more general check) + logger.info("Waiting for any tables to appear...") + await page.wait_for_function(""" + () => document.querySelectorAll('table').length > 0 + """, timeout=4000) # Reduced from 8000ms + logger.info("At least one table appeared") + + # Then wait for our specific table + await page.wait_for_selector(results_table_dom_selector, timeout=4000, state="attached") # Reduced from 8000ms + logger.debug("Results table attached to DOM") + + # Wait for table to have some content (more than just headers) + await page.wait_for_function(f""" + () => {{ + const table = document.querySelector('{results_table_dom_selector}'); + return table && table.querySelectorAll('tr').length > 2; + }} + """, timeout=4000) # Reduced from 20000ms + logger.debug("Results table populated with data") + + # Ultra-fast additional wait for any remaining JavaScript + await page.wait_for_timeout(500) # Reduced from 3000ms + + except PlaywrightTimeoutError: + logger.warning(f"Timeout waiting for results table '{results_table_dom_selector}'.") + # Try one more wait for content placeholder + try: + await page.wait_for_selector("#ctl00_ContentPlaceHolder1", timeout=10000) + logger.info("ContentPlaceHolder1 found, checking for tables...") + await page.wait_for_timeout(5000) + except PlaywrightTimeoutError: + logger.warning("ContentPlaceHolder1 also not found - content may not have loaded") + + html_content = await page.content() + soup = BeautifulSoup(html_content, "html.parser") + # ... (hata ve sonuç yok mesajı kontrolü aynı) ... + validation_summary_tag = soup.find("div", id=self.VALIDATION_SUMMARY_SELECTOR.split('[')[0].split(':')[0]) + if validation_summary_tag and validation_summary_tag.get_text(strip=True) and \ + ("display: none" not in validation_summary_tag.get("style", "").lower() if validation_summary_tag.has_attr("style") else True) and \ + validation_summary_tag.get_text(strip=True) != "": + return KikSearchResult(decisions=[], total_records=0, current_page=search_params.page) + message_content_div = soup.find("div", id=self.NO_RESULTS_MESSAGE_SELECTOR.split(':')[0]) + if message_content_div and "kayıt bulunamamıştır" in message_content_div.get_text(strip=True).lower(): + return KikSearchResult(decisions=[], total_records=0, current_page=1) + + # _parse_decision_entries_from_soup'a arama yapılan karar_tipi'ni gönder + decisions = self._parse_decision_entries_from_soup(soup, search_params.karar_tipi) + total_records = self._parse_total_records_from_soup(soup) + current_page_from_html = self._parse_current_page_from_soup(soup) + return KikSearchResult(decisions=decisions, total_records=total_records, current_page=current_page_from_html) + except Exception as e: + logger.error(f"Error during KIK decision search: {e}", exc_info=True) + return KikSearchResult(decisions=[], current_page=search_params.page) + + def _clean_html_for_markdown(self, html_content: str) -> str: + # ... (öncekiyle aynı) ... + if not html_content: return "" + return html_parser.unescape(html_content) + + def _convert_html_to_markdown_internal(self, html_fragment: str) -> Optional[str]: + # ... (öncekiyle aynı) ... + if not html_fragment: return None + cleaned_html = self._clean_html_for_markdown(html_fragment) + markdown_output = None + try: + # Convert HTML string to bytes and create BytesIO stream + html_bytes = cleaned_html.encode('utf-8') + html_stream = io.BytesIO(html_bytes) + + # Pass BytesIO stream to MarkItDown to avoid temp file creation + md_converter = MarkItDown(enable_plugins=True, remove_alt_whitespace=True, keep_underline=True) + markdown_output = md_converter.convert(html_stream).text_content + if markdown_output: markdown_output = re.sub(r'\n{3,}', '\n\n', markdown_output).strip() + except Exception as e: logger.error(f"MarkItDown conversion error: {e}", exc_info=True) + return markdown_output + + + async def get_decision_document_as_markdown( + self, + karar_id_b64: str, + page_number: int = 1 + ) -> KikDocumentMarkdown: + await self._ensure_playwright_ready() + # Bu metodun kendi içinde yeni bir 'page' nesnesi ('doc_page_for_content') kullanacağını unutmayın, + # ana 'self.page' arama sonuçları sayfasında kalır. + current_main_page = self.page # Ana arama sonuçları sayfasını referans alalım + + try: + decoded_key = base64.b64decode(karar_id_b64.encode('utf-8')).decode('utf-8') + karar_tipi_value, karar_no_for_search = decoded_key.split('|', 1) + original_karar_tipi = KikKararTipi(karar_tipi_value) + logger.info(f"KIK Get Detail: Decoded karar_id '{karar_id_b64}' to Karar Tipi: {original_karar_tipi.value}, Karar No: {karar_no_for_search}. Requested Markdown Page: {page_number}") + except Exception as e_decode: + logger.error(f"Invalid karar_id format. Could not decode Base64 or split: {karar_id_b64}. Error: {e_decode}") + return KikDocumentMarkdown(retrieved_with_karar_id=karar_id_b64, error_message="Invalid karar_id format.", current_page=page_number) + + default_error_response_data = { + "retrieved_with_karar_id": karar_id_b64, + "retrieved_karar_no": karar_no_for_search, + "retrieved_karar_tipi": original_karar_tipi, + "error_message": "An unspecified error occurred.", + "current_page": page_number, "total_pages": 1, "is_paginated": False + } + + # Ana arama sayfasında olduğumuzdan emin olalım + if self.SEARCH_PAGE_PATH not in current_main_page.url: + logger.info(f"Not on search page ({current_main_page.url}). Navigating to {self.SEARCH_PAGE_PATH} before targeted search for document.") + await current_main_page.goto(f"{self.BASE_URL}{self.SEARCH_PAGE_PATH}", wait_until="networkidle", timeout=self.request_timeout) + await current_main_page.wait_for_selector(f"a[id='{self.FIELD_LOCATORS['search_button_id']}']", state="visible", timeout=self.request_timeout) + + targeted_search_params = KikSearchRequest( + karar_no=karar_no_for_search, + karar_tipi=original_karar_tipi, + page=1 + ) + logger.info(f"Performing targeted search for Karar No: {karar_no_for_search}") + # search_decisions kendi içinde _ensure_playwright_ready çağırır ve self.page'i kullanır. + # Bu, current_main_page ile aynı olmalı. + search_results = await self.search_decisions(targeted_search_params) + + if not search_results.decisions: + default_error_response_data["error_message"] = f"Decision with Karar No '{karar_no_for_search}' (Tipi: {original_karar_tipi.value}) not found by internal search." + return KikDocumentMarkdown(**default_error_response_data) + + decision_to_fetch = None + for dec_entry in search_results.decisions: + if dec_entry.karar_no_str == karar_no_for_search and dec_entry.karar_tipi == original_karar_tipi: + decision_to_fetch = dec_entry + break + + if not decision_to_fetch: + default_error_response_data["error_message"] = f"Karar No '{karar_no_for_search}' (Tipi: {original_karar_tipi.value}) not present with an exact match in first page of targeted search results." + return KikDocumentMarkdown(**default_error_response_data) + + decision_preview_event_target = decision_to_fetch.preview_event_target + logger.info(f"Found target decision. Using preview_event_target: {decision_preview_event_target} for Karar No: {decision_to_fetch.karar_no_str}") + + iframe_document_url_str = None + karar_id_param_from_url_on_doc_page = None + document_html_content = "" + + try: + logger.info(f"Evaluating __doPostBack on main page to show modal for: {decision_preview_event_target}") + # Bu evaluate, self.page (yani current_main_page) üzerinde çalışır + await current_main_page.evaluate(f"javascript:__doPostBack('{decision_preview_event_target}','')") + await current_main_page.wait_for_timeout(1000) + logger.info(f"Executed __doPostBack for {decision_preview_event_target} on main page.") + + iframe_selector = "iframe#iframe_detayPopUp" + modal_visible_selector = "div#detayPopUp.in" + + try: + logger.info(f"Waiting for modal '{modal_visible_selector}' to be visible and iframe '{iframe_selector}' src to be populated on main page...") + await current_main_page.wait_for_function( + f""" + () => {{ + const modal = document.querySelector('{modal_visible_selector}'); + const iframe = document.querySelector('{iframe_selector}'); + const modalIsTrulyVisible = modal && (window.getComputedStyle(modal).display !== 'none'); + return modalIsTrulyVisible && + iframe && iframe.getAttribute('src') && + iframe.getAttribute('src').includes('KurulKararGoster.aspx'); + }} + """, + timeout=self.request_timeout / 2 + ) + iframe_src_value = await current_main_page.locator(iframe_selector).get_attribute("src") + logger.info(f"Iframe src populated: {iframe_src_value}") + + except PlaywrightTimeoutError: + logger.warning(f"Timeout waiting for KIK iframe src for {decision_preview_event_target}. Trying to parse from static content after presumed update.") + html_after_postback = await current_main_page.content() + # ... (fallback parsing öncekiyle aynı, default_error_response_data set edilir ve return edilir) ... + soup_after_postback = BeautifulSoup(html_after_postback, "html.parser") + detay_popup_div = soup_after_postback.find("div", {"id": "detayPopUp", "class": re.compile(r"\bin\b")}) + if not detay_popup_div: detay_popup_div = soup_after_postback.find("div", {"id": "detayPopUp", "style": re.compile(r"display:\s*block", re.I)}) + iframe_tag = detay_popup_div.find("iframe", {"id": "iframe_detayPopUp"}) if detay_popup_div else None + if iframe_tag and iframe_tag.has_attr("src") and iframe_tag["src"]: iframe_src_value = iframe_tag["src"] + else: + default_error_response_data["error_message"]="Timeout or failure finding decision content iframe URL after postback." + return KikDocumentMarkdown(**default_error_response_data) + + if not iframe_src_value or not iframe_src_value.strip(): + default_error_response_data["error_message"]="Extracted iframe URL for decision content is empty." + return KikDocumentMarkdown(**default_error_response_data) + + # iframe_src_value göreceli bir URL ise, ana sayfanın URL'si ile birleştir + iframe_document_url_str = urllib.parse.urljoin(current_main_page.url, iframe_src_value) + logger.info(f"Constructed absolute iframe_document_url_str for goto: {iframe_document_url_str}") # Log this absolute URL + default_error_response_data["source_url"] = iframe_document_url_str + + parsed_url = urllib.parse.urlparse(iframe_document_url_str) + query_params = urllib.parse.parse_qs(parsed_url.query) + karar_id_param_from_url_on_doc_page = query_params.get("KararId", [None])[0] + default_error_response_data["karar_id_param_from_url"] = karar_id_param_from_url_on_doc_page + if not karar_id_param_from_url_on_doc_page: + default_error_response_data["error_message"]="KararId (KIK internal ID) not found in extracted iframe URL." + return KikDocumentMarkdown(**default_error_response_data) + + logger.info(f"Fetching KIK decision content from iframe URL using a new Playwright page: {iframe_document_url_str}") + + doc_page_for_content = await self.context.new_page() + try: + # `goto` metoduna MUTLAK URL verilmeli. Loglanan URL'nin mutlak olduğundan emin olalım. + await doc_page_for_content.goto(iframe_document_url_str, wait_until="domcontentloaded", timeout=self.request_timeout) + document_html_content = await doc_page_for_content.content() + except Exception as e_doc_page: + logger.error(f"Error navigating or getting content from doc_page ({iframe_document_url_str}): {e_doc_page}") + if doc_page_for_content and not doc_page_for_content.is_closed(): await doc_page_for_content.close() + default_error_response_data["error_message"]=f"Failed to load decision detail page: {e_doc_page}" + return KikDocumentMarkdown(**default_error_response_data) + finally: + if doc_page_for_content and not doc_page_for_content.is_closed(): + await doc_page_for_content.close() + + soup_decision_detail = BeautifulSoup(document_html_content, "html.parser") + karar_content_span = soup_decision_detail.find("span", {"id": "ctl00_ContentPlaceHolder1_lblKarar"}) + actual_decision_html = karar_content_span.decode_contents() if karar_content_span else document_html_content + full_markdown_content = self._convert_html_to_markdown_internal(actual_decision_html) + + if not full_markdown_content: + default_error_response_data["error_message"]="Markdown conversion failed or returned empty content." + try: + if await current_main_page.locator(self.MODAL_CLOSE_BUTTON_SELECTOR).is_visible(timeout=1000): + await current_main_page.locator(self.MODAL_CLOSE_BUTTON_SELECTOR).click() + except: pass + return KikDocumentMarkdown(**default_error_response_data) + + content_length = len(full_markdown_content); total_pages = math.ceil(content_length / self.DOCUMENT_MARKDOWN_CHUNK_SIZE) or 1 + current_page_clamped = max(1, min(page_number, total_pages)) + start_index = (current_page_clamped - 1) * self.DOCUMENT_MARKDOWN_CHUNK_SIZE + markdown_chunk = full_markdown_content[start_index : start_index + self.DOCUMENT_MARKDOWN_CHUNK_SIZE] + + try: + if await current_main_page.locator(self.MODAL_CLOSE_BUTTON_SELECTOR).is_visible(timeout=2000): + await current_main_page.locator(self.MODAL_CLOSE_BUTTON_SELECTOR).click() + await current_main_page.wait_for_selector(f"div#detayPopUp:not(.in)", timeout=5000) + except: pass + + return KikDocumentMarkdown( + retrieved_with_karar_id=karar_id_b64, + retrieved_karar_no=karar_no_for_search, + retrieved_karar_tipi=original_karar_tipi, + kararIdParam=karar_id_param_from_url_on_doc_page, + markdown_chunk=markdown_chunk, source_url=iframe_document_url_str, + current_page=current_page_clamped, total_pages=total_pages, + is_paginated=(total_pages > 1), full_content_char_count=content_length + ) + except Exception as e: + logger.error(f"Error in get_decision_document_as_markdown for Karar ID {karar_id_b64}: {e}", exc_info=True) + default_error_response_data["error_message"] = f"General error: {str(e)}" + return KikDocumentMarkdown(**default_error_response_data) diff --git a/saidsurucu-yargi-mcp-f5fa007/kik_mcp_module/models.py b/saidsurucu-yargi-mcp-f5fa007/kik_mcp_module/models.py new file mode 100644 index 0000000..6b6632b --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/kik_mcp_module/models.py @@ -0,0 +1,74 @@ +# kik_mcp_module/models.py +from pydantic import BaseModel, Field, HttpUrl, computed_field, ConfigDict +from typing import List, Optional +from enum import Enum +import base64 # Base64 encoding/decoding için + +class KikKararTipi(str, Enum): + """Enum for KIK (Public Procurement Authority) Decision Types.""" + UYUSMAZLIK = "rbUyusmazlik" + DUZENLEYICI = "rbDuzenleyici" + MAHKEME = "rbMahkeme" + +class KikSearchRequest(BaseModel): + """Model for KIK Decision search criteria.""" + karar_tipi: KikKararTipi = Field(KikKararTipi.UYUSMAZLIK, description="Type") + karar_no: str = Field("", description="No") + karar_tarihi_baslangic: str = Field("", description="Start", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$") + karar_tarihi_bitis: str = Field("", description="End", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$") + resmi_gazete_sayisi: str = Field("", description="Gazette") + resmi_gazete_tarihi: str = Field("", description="Date", pattern=r"^\d{2}\.\d{2}\.\d{4}$|^$") + basvuru_konusu_ihale: str = Field("", description="Subject") + basvuru_sahibi: str = Field("", description="Applicant") + ihaleyi_yapan_idare: str = Field("", description="Entity") + yil: str = Field("", description="Year") + karar_metni: str = Field("", description="Text") + page: int = Field(1, ge=1, description="Page") + +class KikDecisionEntry(BaseModel): + """Represents a single decision entry from KIK search results.""" + preview_event_target: str = Field(..., description="Event target") + karar_no_str: str = Field(..., alias="kararNo", description="Decision number") + karar_tipi: KikKararTipi = Field(..., description="Decision type") + + karar_tarihi_str: str = Field(..., alias="kararTarihi", description="Date") + idare_str: str = Field("", alias="idare", description="Entity") + basvuru_sahibi_str: str = Field("", alias="basvuruSahibi", description="Applicant") + ihale_konusu_str: str = Field("", alias="ihaleKonusu", description="Subject") + + @computed_field + @property + def karar_id(self) -> str: + """ + A Base64 encoded unique ID for the decision, combining decision type and number. + Format before encoding: "{karar_tipi.value}|{karar_no_str}" + """ + combined_key = f"{self.karar_tipi.value}|{self.karar_no_str}" + return base64.b64encode(combined_key.encode('utf-8')).decode('utf-8') + + model_config = ConfigDict(populate_by_name=True) + +class KikSearchResult(BaseModel): + """Model for KIK search results.""" + decisions: List[KikDecisionEntry] + total_records: int = 0 + current_page: int = 1 + +class KikDocumentMarkdown(BaseModel): + """ + KIK decision document, with Markdown content potentially paginated. + """ + retrieved_with_karar_id: Optional[str] = Field(None, description="Request ID") + retrieved_karar_no: Optional[str] = Field(None, description="Decision number") + retrieved_karar_tipi: Optional[KikKararTipi] = Field(None, description="Decision type") + + karar_id_param_from_url: Optional[str] = Field(None, alias="kararIdParam", description="Internal ID") + markdown_chunk: Optional[str] = Field(None, description="Content") + source_url: Optional[str] = Field(None, description="Source URL") + error_message: Optional[str] = Field(None, description="Error") + current_page: int = Field(1, description="Page") + total_pages: int = Field(1, description="Total pages") + is_paginated: bool = Field(False, description="Paginated") + full_content_char_count: Optional[int] = Field(None, description="Char count") + + model_config = ConfigDict(populate_by_name=True) \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/kvkk_mcp_module/__init__.py b/saidsurucu-yargi-mcp-f5fa007/kvkk_mcp_module/__init__.py new file mode 100644 index 0000000..0409b6b --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/kvkk_mcp_module/__init__.py @@ -0,0 +1 @@ +# kvkk_mcp_module/__init__.py \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/kvkk_mcp_module/client.py b/saidsurucu-yargi-mcp-f5fa007/kvkk_mcp_module/client.py new file mode 100644 index 0000000..ab26bb8 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/kvkk_mcp_module/client.py @@ -0,0 +1,372 @@ +# kvkk_mcp_module/client.py + +import httpx +from bs4 import BeautifulSoup +from typing import List, Optional, Dict, Any +import logging +import os +import re +import io +import math +from urllib.parse import urljoin, urlparse, parse_qs +from markitdown import MarkItDown +from pydantic import HttpUrl + +from .models import ( + KvkkSearchRequest, + KvkkDecisionSummary, + KvkkSearchResult, + KvkkDocumentMarkdown +) + +logger = logging.getLogger(__name__) +if not logger.hasHandlers(): + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + +class KvkkApiClient: + """ + API client for searching and retrieving KVKK (Personal Data Protection Authority) decisions + using Brave Search API for discovery and direct HTTP requests for content retrieval. + """ + + BRAVE_API_URL = "https://api.search.brave.com/res/v1/web/search" + KVKK_BASE_URL = "https://www.kvkk.gov.tr" + DOCUMENT_MARKDOWN_CHUNK_SIZE = 5000 # Character limit per page + + def __init__(self, request_timeout: float = 60.0): + """Initialize the KVKK API client.""" + self.brave_api_token = os.getenv("BRAVE_API_TOKEN") + if not self.brave_api_token: + # Fallback to provided free token + self.brave_api_token = "BSAuaRKB-dvSDSQxIN0ft1p2k6N82Kq" + logger.info("Using fallback Brave API token (limited free token)") + else: + logger.info("Using Brave API token from environment variable") + + self.http_client = httpx.AsyncClient( + headers={ + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + }, + timeout=request_timeout, + verify=True, + follow_redirects=True + ) + + def _construct_search_query(self, keywords: str) -> str: + """Construct the search query for Brave API.""" + base_query = 'site:kvkk.gov.tr "karar özeti"' + if keywords.strip(): + return f"{base_query} {keywords.strip()}" + return base_query + + def _extract_decision_id_from_url(self, url: str) -> Optional[str]: + """Extract decision ID from KVKK decision URL.""" + try: + # Example URL: https://www.kvkk.gov.tr/Icerik/7288/2021-1303 + parsed_url = urlparse(url) + path_parts = parsed_url.path.strip('/').split('/') + + if len(path_parts) >= 3 and path_parts[0] == 'Icerik': + # Extract the decision ID from the path + decision_id = '/'.join(path_parts[1:]) # e.g., "7288/2021-1303" + return decision_id + + except Exception as e: + logger.debug(f"Could not extract decision ID from URL {url}: {e}") + + return None + + def _extract_decision_metadata_from_title(self, title: str) -> Dict[str, Optional[str]]: + """Extract decision metadata from title string.""" + metadata = { + "decision_date": None, + "decision_number": None + } + + if not title: + return metadata + + # Extract decision date (DD/MM/YYYY format) + date_match = re.search(r'(\d{1,2}/\d{1,2}/\d{4})', title) + if date_match: + metadata["decision_date"] = date_match.group(1) + + # Extract decision number (YYYY/XXXX format) + number_match = re.search(r'(\d{4}/\d+)', title) + if number_match: + metadata["decision_number"] = number_match.group(1) + + return metadata + + async def search_decisions(self, params: KvkkSearchRequest) -> KvkkSearchResult: + """Search for KVKK decisions using Brave API.""" + + search_query = self._construct_search_query(params.keywords) + logger.info(f"KvkkApiClient: Searching with query: {search_query}") + + try: + # Calculate offset for pagination + offset = (params.page - 1) * params.pageSize + + response = await self.http_client.get( + self.BRAVE_API_URL, + headers={ + "Accept": "application/json", + "Accept-Encoding": "gzip", + "x-subscription-token": self.brave_api_token + }, + params={ + "q": search_query, + "country": "TR", + "search_lang": "tr", + "ui_lang": "tr-TR", + "offset": offset, + "count": params.pageSize + } + ) + + response.raise_for_status() + data = response.json() + + # Extract search results + decisions = [] + web_results = data.get("web", {}).get("results", []) + + for result in web_results: + title = result.get("title", "") + url = result.get("url", "") + description = result.get("description", "") + + # Extract metadata from title + metadata = self._extract_decision_metadata_from_title(title) + + # Extract decision ID from URL + decision_id = self._extract_decision_id_from_url(url) + + decision = KvkkDecisionSummary( + title=title, + url=HttpUrl(url) if url else None, + description=description, + decision_id=decision_id, + publication_date=metadata.get("decision_date"), + decision_number=metadata.get("decision_number") + ) + decisions.append(decision) + + # Get total results if available + total_results = None + query_info = data.get("query", {}) + if "total_results" in query_info: + total_results = query_info["total_results"] + + return KvkkSearchResult( + decisions=decisions, + total_results=total_results, + page=params.page, + pageSize=params.pageSize, + query=search_query + ) + + except httpx.RequestError as e: + logger.error(f"KvkkApiClient: HTTP request error during search: {e}") + return KvkkSearchResult( + decisions=[], + total_results=0, + page=params.page, + pageSize=params.pageSize, + query=search_query + ) + except Exception as e: + logger.error(f"KvkkApiClient: Unexpected error during search: {e}") + return KvkkSearchResult( + decisions=[], + total_results=0, + page=params.page, + pageSize=params.pageSize, + query=search_query + ) + + def _extract_decision_content_from_html(self, html: str, url: str) -> Dict[str, Any]: + """Extract decision content from KVKK decision page HTML.""" + try: + soup = BeautifulSoup(html, 'html.parser') + + # Extract title + title = None + title_element = soup.find('h3', class_='blog-post-title') + if title_element: + title = title_element.get_text(strip=True) + elif soup.title: + title = soup.title.get_text(strip=True) + + # Extract decision content from the main content div + content_div = soup.find('div', class_='blog-post-inner') + if not content_div: + # Fallback to other possible content containers + content_div = soup.find('div', style='text-align:justify;') + if not content_div: + logger.warning(f"Could not find decision content div in {url}") + return { + "title": title, + "decision_date": None, + "decision_number": None, + "subject_summary": None, + "html_content": None + } + + # Extract decision metadata from table + decision_date = None + decision_number = None + subject_summary = None + + table = content_div.find('table') + if table: + rows = table.find_all('tr') + for row in rows: + cells = row.find_all('td') + if len(cells) >= 3: + field_name = cells[0].get_text(strip=True) + field_value = cells[2].get_text(strip=True) + + if 'Karar Tarihi' in field_name: + decision_date = field_value + elif 'Karar No' in field_name: + decision_number = field_value + elif 'Konu Özeti' in field_name: + subject_summary = field_value + + return { + "title": title, + "decision_date": decision_date, + "decision_number": decision_number, + "subject_summary": subject_summary, + "html_content": str(content_div) + } + + except Exception as e: + logger.error(f"Error extracting content from HTML for {url}: {e}") + return { + "title": None, + "decision_date": None, + "decision_number": None, + "subject_summary": None, + "html_content": None + } + + def _convert_html_to_markdown(self, html_content: str) -> Optional[str]: + """Convert HTML content to Markdown using MarkItDown with BytesIO to avoid filename length issues.""" + if not html_content: + return None + + try: + # Convert HTML string to bytes and create BytesIO stream + html_bytes = html_content.encode('utf-8') + html_stream = io.BytesIO(html_bytes) + + # Pass BytesIO stream to MarkItDown to avoid temp file creation + md_converter = MarkItDown(enable_plugins=False) + result = md_converter.convert(html_stream) + return result.text_content + except Exception as e: + logger.error(f"Error converting HTML to Markdown: {e}") + return None + + async def get_decision_document(self, decision_url: str, page_number: int = 1) -> KvkkDocumentMarkdown: + """Retrieve and convert a KVKK decision document to paginated Markdown.""" + logger.info(f"KvkkApiClient: Getting decision document from: {decision_url}, page: {page_number}") + + try: + # Fetch the decision page + response = await self.http_client.get(decision_url) + response.raise_for_status() + + # Extract content from HTML + extracted_data = self._extract_decision_content_from_html(response.text, decision_url) + + # Convert HTML content to Markdown + full_markdown_content = None + if extracted_data["html_content"]: + full_markdown_content = self._convert_html_to_markdown(extracted_data["html_content"]) + + if not full_markdown_content: + return KvkkDocumentMarkdown( + source_url=HttpUrl(decision_url), + title=extracted_data["title"], + decision_date=extracted_data["decision_date"], + decision_number=extracted_data["decision_number"], + subject_summary=extracted_data["subject_summary"], + markdown_chunk=None, + current_page=page_number, + total_pages=0, + is_paginated=False, + error_message="Could not convert document content to Markdown" + ) + + # Calculate pagination + content_length = len(full_markdown_content) + total_pages = math.ceil(content_length / self.DOCUMENT_MARKDOWN_CHUNK_SIZE) + if total_pages == 0: + total_pages = 1 + + # Clamp page number to valid range + current_page_clamped = max(1, min(page_number, total_pages)) + + # Extract the requested chunk + start_index = (current_page_clamped - 1) * self.DOCUMENT_MARKDOWN_CHUNK_SIZE + end_index = start_index + self.DOCUMENT_MARKDOWN_CHUNK_SIZE + markdown_chunk = full_markdown_content[start_index:end_index] + + return KvkkDocumentMarkdown( + source_url=HttpUrl(decision_url), + title=extracted_data["title"], + decision_date=extracted_data["decision_date"], + decision_number=extracted_data["decision_number"], + subject_summary=extracted_data["subject_summary"], + markdown_chunk=markdown_chunk, + current_page=current_page_clamped, + total_pages=total_pages, + is_paginated=(total_pages > 1), + error_message=None + ) + + except httpx.HTTPStatusError as e: + error_msg = f"HTTP error {e.response.status_code} when fetching decision document" + logger.error(f"KvkkApiClient: {error_msg}") + return KvkkDocumentMarkdown( + source_url=HttpUrl(decision_url), + title=None, + decision_date=None, + decision_number=None, + subject_summary=None, + markdown_chunk=None, + current_page=page_number, + total_pages=0, + is_paginated=False, + error_message=error_msg + ) + except Exception as e: + error_msg = f"Unexpected error when fetching decision document: {str(e)}" + logger.error(f"KvkkApiClient: {error_msg}") + return KvkkDocumentMarkdown( + source_url=HttpUrl(decision_url), + title=None, + decision_date=None, + decision_number=None, + subject_summary=None, + markdown_chunk=None, + current_page=page_number, + total_pages=0, + is_paginated=False, + error_message=error_msg + ) + + async def close_client_session(self): + """Close the HTTP client session.""" + if hasattr(self, 'http_client') and self.http_client and not self.http_client.is_closed: + await self.http_client.aclose() + logger.info("KvkkApiClient: HTTP client session closed.") \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/kvkk_mcp_module/models.py b/saidsurucu-yargi-mcp-f5fa007/kvkk_mcp_module/models.py new file mode 100644 index 0000000..7db1c3c --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/kvkk_mcp_module/models.py @@ -0,0 +1,49 @@ +# kvkk_mcp_module/models.py + +from pydantic import BaseModel, Field, HttpUrl +from typing import List, Optional, Any + +class KvkkSearchRequest(BaseModel): + """Model for KVKK (Personal Data Protection Authority) search request via Brave API.""" + keywords: str = Field(..., description=""" + Keywords to search for in KVKK decisions. + The search will automatically include 'site:kvkk.gov.tr "karar özeti"' to target KVKK decision summaries. + Examples: "açık rıza", "veri güvenliği", "kişisel veri işleme" + """) + page: int = Field(1, ge=1, le=50, description="Page number for search results (1-50).") + pageSize: int = Field(10, ge=1, le=10, description="Number of results per page (1-10).") + +class KvkkDecisionSummary(BaseModel): + """Model for a single KVKK decision summary from Brave search results.""" + title: Optional[str] = Field(None, description="Decision title from search results.") + url: Optional[HttpUrl] = Field(None, description="URL to the KVKK decision page.") + description: Optional[str] = Field(None, description="Brief description or snippet from search results.") + decision_id: Optional[str] = Field(None, description="Value") + publication_date: Optional[str] = Field(None, description="Value") + decision_number: Optional[str] = Field(None, description="Value") + +class KvkkSearchResult(BaseModel): + """Model for the overall search result for KVKK decisions.""" + decisions: List[KvkkDecisionSummary] = Field(default_factory=list, description="List of KVKK decisions found.") + total_results: Optional[int] = Field(None, description="Value") + page: int = Field(1, description="Current page number of results.") + pageSize: int = Field(10, description="Number of results per page.") + query: Optional[str] = Field(None, description="The actual search query sent to Brave API.") + +class KvkkDocumentMarkdown(BaseModel): + """Model for KVKK decision document content converted to paginated Markdown.""" + source_url: HttpUrl = Field(description="URL of the original KVKK decision page.") + title: Optional[str] = Field(None, description="Title of the KVKK decision.") + decision_date: Optional[str] = Field(None, description="Decision date (Karar Tarihi).") + decision_number: Optional[str] = Field(None, description="Decision number (Karar No).") + subject_summary: Optional[str] = Field(None, description="Subject summary (Konu Özeti).") + markdown_chunk: Optional[str] = Field(None, description="A 5,000 character chunk of the Markdown content.") + current_page: int = Field(description="The current page number of the markdown chunk (1-indexed).") + total_pages: int = Field(description="Total number of pages for the full markdown content.") + is_paginated: bool = Field(description="True if the full markdown content is split into multiple pages.") + error_message: Optional[str] = Field(None, description="Value") + + class Config: + json_encoders = { + HttpUrl: str + } \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/mcp_auth/__init__.py b/saidsurucu-yargi-mcp-f5fa007/mcp_auth/__init__.py new file mode 100644 index 0000000..0ed22c9 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/mcp_auth/__init__.py @@ -0,0 +1,28 @@ +""" +MCP Auth Toolkit - OAuth 2.1 + Authorization for Model Context Protocol Servers +Integrated with Clerk Authentication +""" + +from .middleware import ( + AuthContext, + FastMCPAuthWrapper, + MCPAuthMiddleware, + auth_required, +) +from .oauth import OAuthConfig, OAuthProvider +from .policy import PolicyEngine, ToolPolicy, create_default_policies +from .storage import PersistentStorage + +__version__ = "0.1.0" +__all__ = [ + "OAuthProvider", + "OAuthConfig", + "AuthContext", + "auth_required", + "create_default_policies", + "MCPAuthMiddleware", + "FastMCPAuthWrapper", + "PolicyEngine", + "ToolPolicy", + "PersistentStorage", +] \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/mcp_auth/clerk_config.py b/saidsurucu-yargi-mcp-f5fa007/mcp_auth/clerk_config.py new file mode 100644 index 0000000..2c3e205 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/mcp_auth/clerk_config.py @@ -0,0 +1,73 @@ +""" +Clerk OAuth configuration for MCP Auth Toolkit +""" + +import os +import logging +from .oauth import OAuthConfig + +logger = logging.getLogger(__name__) + + +def create_clerk_oauth_config() -> OAuthConfig: + """Create OAuth configuration for Clerk integration using SDK""" + + # Get Clerk configuration from environment + clerk_domain = os.getenv("CLERK_DOMAIN", "accounts.yargimcp.com") + clerk_publishable_key = os.getenv("CLERK_PUBLISHABLE_KEY") + clerk_secret_key = os.getenv("CLERK_SECRET_KEY") + + if not clerk_publishable_key or not clerk_secret_key: + raise ValueError("CLERK_PUBLISHABLE_KEY and CLERK_SECRET_KEY are required") + + # For Clerk with custom domains, we use our adapter endpoints + # This allows us to handle the custom domain flow properly + base_url = os.getenv("BASE_URL", "https://yargimcp.com") + + config = OAuthConfig( + client_id=clerk_publishable_key, + client_secret=clerk_secret_key, + # Use our adapter endpoints instead of Clerk's direct endpoints + authorization_endpoint=f"{base_url}/authorize", + token_endpoint=f"{base_url}/token", + # Keep Clerk's JWKS for token validation + jwks_uri=f"https://{clerk_domain}/.well-known/jwks.json", + issuer=base_url, # We're the issuer for MCP tokens + scopes=["mcp:tools:read", "mcp:tools:write", "openid", "profile", "email"] + ) + + logger.info(f"Created Clerk OAuth config with adapter endpoints") + logger.info(f"Clerk domain: {clerk_domain}") + logger.debug(f"Authorization endpoint: {config.authorization_endpoint}") + logger.debug(f"Token endpoint: {config.token_endpoint}") + + return config + + +def get_jwt_secret() -> str: + """Get JWT secret for token signing""" + jwt_secret = os.getenv("JWT_SECRET_KEY") + + if not jwt_secret: + raise ValueError("JWT_SECRET_KEY environment variable is required") + + return jwt_secret + + +def create_mcp_server_config(): + """Create complete MCP server configuration for Clerk integration""" + + try: + oauth_config = create_clerk_oauth_config() + jwt_secret = get_jwt_secret() + + return { + "oauth_config": oauth_config, + "jwt_secret": jwt_secret, + "base_url": os.getenv("BASE_URL", "https://yargi-mcp.fly.dev"), + "auth_enabled": os.getenv("ENABLE_AUTH", "true").lower() == "true" + } + + except Exception as e: + logger.error(f"Failed to create MCP server config: {e}") + raise \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/mcp_auth/middleware.py b/saidsurucu-yargi-mcp-f5fa007/mcp_auth/middleware.py new file mode 100644 index 0000000..0dc6301 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/mcp_auth/middleware.py @@ -0,0 +1,315 @@ +""" +MCP server middleware for OAuth authentication and authorization +""" + +import functools +import logging +from collections.abc import Callable +from dataclasses import dataclass +from typing import Any, Optional + +logger = logging.getLogger(__name__) + +try: + from fastmcp import FastMCP + FASTMCP_AVAILABLE = True +except ImportError: + FASTMCP_AVAILABLE = False + FastMCP = None + logger.warning("FastMCP not available, some features will be disabled") + +from .oauth import OAuthProvider +from .policy import PolicyEngine + + +@dataclass +class AuthContext: + """Authentication context passed to MCP tools""" + + user_id: str + scopes: list[str] + claims: dict[str, Any] + token: str + + +class MCPAuthMiddleware: + """Authentication middleware for MCP servers""" + + def __init__(self, oauth_provider: OAuthProvider, policy_engine: PolicyEngine): + self.oauth_provider = oauth_provider + self.policy_engine = policy_engine + + def authenticate_request(self, authorization_header: str) -> AuthContext | None: + """Extract and validate auth token from request""" + + if not authorization_header: + logger.debug("No authorization header provided") + return None + + if not authorization_header.startswith("Bearer "): + logger.debug("Authorization header does not start with 'Bearer '") + return None + + token = authorization_header[7:] # Remove 'Bearer ' prefix + + token_info = self.oauth_provider.introspect_token(token) + + if not token_info.get("active"): + logger.warning("Token is not active") + return None + + logger.debug(f"Authenticated user: {token_info.get('sub', 'unknown')}") + + return AuthContext( + user_id=token_info.get("sub", "unknown"), + scopes=token_info.get("mcp_tool_scopes", []), + claims=token_info, + token=token, + ) + + def authorize_tool_call( + self, tool_name: str, auth_context: AuthContext + ) -> tuple[bool, str | None]: + """Check if user can call the specified tool""" + + return self.policy_engine.authorize_tool_call( + tool_name=tool_name, + user_scopes=auth_context.scopes, + user_claims=auth_context.claims, + ) + + +def auth_required( + oauth_provider: OAuthProvider, + policy_engine: PolicyEngine, + tool_name: str | None = None, +): + """ + Decorator to require authentication for MCP tool functions + + Usage: + @auth_required(oauth_provider, policy_engine, "search_yargitay") + def my_tool_function(context: AuthContext, ...): + pass + """ + + def decorator(func: Callable) -> Callable: + middleware = MCPAuthMiddleware(oauth_provider, policy_engine) + + @functools.wraps(func) + async def wrapper(*args, **kwargs): + # Extract authorization header from kwargs + auth_header = kwargs.pop("authorization", None) + + # Also check in args if it's a Request object + if not auth_header and args: + for arg in args: + if hasattr(arg, 'headers'): + auth_header = arg.headers.get("Authorization") + break + + if not auth_header: + logger.warning(f"No authorization header for tool '{tool_name or func.__name__}'") + raise PermissionError("Authorization header required") + + auth_context = middleware.authenticate_request(auth_header) + + if not auth_context: + logger.warning(f"Authentication failed for tool '{tool_name or func.__name__}'") + raise PermissionError("Invalid or expired token") + + actual_tool_name = tool_name or func.__name__ + + authorized, reason = middleware.authorize_tool_call( + actual_tool_name, auth_context + ) + + if not authorized: + logger.warning(f"Authorization failed for tool '{actual_tool_name}': {reason}") + raise PermissionError(f"Access denied: {reason}") + + # Add auth context to function call + return await func(auth_context, *args, **kwargs) + + return wrapper + + return decorator + + +class FastMCPAuthWrapper: + """Wrapper for FastMCP servers to add authentication""" + + def __init__( + self, + mcp_server: "FastMCP", + oauth_provider: OAuthProvider, + policy_engine: PolicyEngine, + ): + if not FASTMCP_AVAILABLE: + raise ImportError("FastMCP is required for FastMCPAuthWrapper") + + self.mcp_server = mcp_server + self.middleware = MCPAuthMiddleware(oauth_provider, policy_engine) + self.oauth_provider = oauth_provider + logger.info("Initializing FastMCP authentication wrapper") + self._wrap_tools() + + def _wrap_tools(self): + """Wrap all existing tools with auth middleware""" + + # Try different FastMCP tool storage locations + tool_registry = None + + if hasattr(self.mcp_server, '_tools'): + tool_registry = self.mcp_server._tools + elif hasattr(self.mcp_server, 'tools'): + tool_registry = self.mcp_server.tools + elif hasattr(self.mcp_server, '_tool_registry'): + tool_registry = self.mcp_server._tool_registry + elif hasattr(self.mcp_server, '_handlers') and hasattr(self.mcp_server._handlers, 'tools'): + tool_registry = self.mcp_server._handlers.tools + + if not tool_registry: + logger.warning("FastMCP server tool registry not found, tools will not be automatically wrapped") + logger.debug(f"Available server attributes: {dir(self.mcp_server)}") + return + + logger.debug(f"Found tool registry with {len(tool_registry)} tools") + original_tools = dict(tool_registry) + wrapped_count = 0 + + for tool_name, tool_func in original_tools.items(): + try: + wrapped_func = self._create_auth_wrapper(tool_name, tool_func) + tool_registry[tool_name] = wrapped_func + wrapped_count += 1 + logger.debug(f"Wrapped tool: {tool_name}") + except Exception as e: + logger.error(f"Failed to wrap tool {tool_name}: {e}") + + logger.info(f"Successfully wrapped {wrapped_count} tools with authentication") + + def _create_auth_wrapper(self, tool_name: str, original_func: Callable) -> Callable: + """Create auth wrapper for a specific tool""" + + @functools.wraps(original_func) + async def auth_wrapper(*args, **kwargs): + # Extract authorization from various sources + auth_header = None + + # Check kwargs first + auth_header = kwargs.pop("authorization", None) + + # Check if first argument is a Request object + if not auth_header and args: + first_arg = args[0] + if hasattr(first_arg, 'headers'): + auth_header = first_arg.headers.get("Authorization") + + if not auth_header: + logger.warning(f"No authorization header for tool '{tool_name}'") + raise PermissionError("Authorization required") + + auth_context = self.middleware.authenticate_request(auth_header) + + if not auth_context: + logger.warning(f"Authentication failed for tool '{tool_name}'") + raise PermissionError("Invalid token") + + authorized, reason = self.middleware.authorize_tool_call( + tool_name, auth_context + ) + + if not authorized: + logger.warning(f"Authorization failed for tool '{tool_name}': {reason}") + raise PermissionError(f"Access denied: {reason}") + + # Add auth context to kwargs + kwargs["auth_context"] = auth_context + logger.debug(f"Calling tool '{tool_name}' for user {auth_context.user_id}") + + return await original_func(*args, **kwargs) + + return auth_wrapper + + def add_oauth_endpoints(self): + """Add OAuth endpoints to the MCP server""" + + @self.mcp_server.tool( + description="Initiate OAuth 2.1 authorization flow with PKCE", + annotations={"readOnlyHint": True, "idempotentHint": False} + ) + async def oauth_authorize(redirect_uri: str, scopes: Optional[str] = None): + """OAuth authorization endpoint""" + scope_list = scopes.split(" ") if scopes else None + auth_url, pkce = self.oauth_provider.generate_authorization_url( + redirect_uri=redirect_uri, scopes=scope_list + ) + logger.info(f"Generated authorization URL for redirect_uri: {redirect_uri}") + return { + "authorization_url": auth_url, + "code_verifier": pkce.verifier, # For PKCE flow + "code_challenge": pkce.challenge, + "instructions": "Use the authorization_url to complete OAuth flow, then exchange the returned code using oauth_token tool" + } + + @self.mcp_server.tool( + description="Exchange OAuth authorization code for access token", + annotations={"readOnlyHint": False, "idempotentHint": False} + ) + async def oauth_token( + code: str, + state: str, + redirect_uri: str + ): + """OAuth token exchange endpoint""" + try: + result = await self.oauth_provider.exchange_code_for_token( + code=code, state=state, redirect_uri=redirect_uri + ) + logger.info("Successfully exchanged authorization code for token") + return result + except Exception as e: + logger.error(f"Token exchange failed: {e}") + raise + + @self.mcp_server.tool( + description="Validate and introspect OAuth access token", + annotations={"readOnlyHint": True, "idempotentHint": True} + ) + async def oauth_introspect(token: str): + """Token introspection endpoint""" + result = self.oauth_provider.introspect_token(token) + logger.debug(f"Token introspection: active={result.get('active', False)}") + return result + + @self.mcp_server.tool( + description="Revoke OAuth access token", + annotations={"readOnlyHint": False, "idempotentHint": False} + ) + async def oauth_revoke(token: str): + """Token revocation endpoint""" + success = self.oauth_provider.revoke_token(token) + logger.info(f"Token revocation: success={success}") + return {"revoked": success} + + @self.mcp_server.tool( + description="Get list of tools available to authenticated user", + annotations={"readOnlyHint": True, "idempotentHint": True} + ) + async def oauth_user_tools(authorization: str): + """Get user's allowed tools based on scopes""" + auth_context = self.middleware.authenticate_request(authorization) + if not auth_context: + raise PermissionError("Invalid token") + + allowed_patterns = self.middleware.policy_engine.get_allowed_tools(auth_context.scopes) + + return { + "user_id": auth_context.user_id, + "scopes": auth_context.scopes, + "allowed_tool_patterns": allowed_patterns, + "message": "Use these patterns to determine which tools you can access" + } + + logger.info("Added OAuth endpoints: oauth_authorize, oauth_token, oauth_introspect, oauth_revoke, oauth_user_tools") \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/mcp_auth/oauth.py b/saidsurucu-yargi-mcp-f5fa007/mcp_auth/oauth.py new file mode 100644 index 0000000..efc3adc --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/mcp_auth/oauth.py @@ -0,0 +1,304 @@ +""" +OAuth 2.1 + PKCE implementation for MCP servers with Clerk integration +""" + +import base64 +import hashlib +import secrets +import time +import logging +from dataclasses import dataclass +from datetime import datetime, timedelta +from typing import Any, Optional +from urllib.parse import urlencode + +import httpx +import jwt +from jwt.exceptions import PyJWTError, InvalidTokenError + +from .storage import PersistentStorage + +# Try to import Clerk SDK +try: + from clerk_backend_api import Clerk + CLERK_AVAILABLE = True +except ImportError: + CLERK_AVAILABLE = False + Clerk = None + +logger = logging.getLogger(__name__) + + +@dataclass +class OAuthConfig: + """OAuth provider configuration for Clerk""" + + client_id: str + client_secret: str + authorization_endpoint: str + token_endpoint: str + jwks_uri: str | None = None + issuer: str = "mcp-auth" + scopes: list[str] = None + + def __post_init__(self): + if self.scopes is None: + self.scopes = ["mcp:tools:read", "mcp:tools:write"] + + +class PKCEChallenge: + """PKCE challenge/verifier pair for OAuth 2.1""" + + def __init__(self): + self.verifier = ( + base64.urlsafe_b64encode(secrets.token_bytes(32)) + .decode("utf-8") + .rstrip("=") + ) + + challenge_bytes = hashlib.sha256(self.verifier.encode("utf-8")).digest() + self.challenge = ( + base64.urlsafe_b64encode(challenge_bytes).decode("utf-8").rstrip("=") + ) + + +class OAuthProvider: + """OAuth 2.1 provider with PKCE support and Clerk integration""" + + def __init__(self, config: OAuthConfig, jwt_secret: str): + self.config = config + self.jwt_secret = jwt_secret + # Use persistent storage instead of memory + self.storage = PersistentStorage() + + # Initialize Clerk SDK if available + self.clerk = None + if CLERK_AVAILABLE and config.client_secret: + try: + self.clerk = Clerk(bearer_auth=config.client_secret) + logger.info("Clerk SDK initialized successfully") + except Exception as e: + logger.warning(f"Failed to initialize Clerk SDK: {e}") + + logger.info("OAuth provider initialized with persistent storage") + + def generate_authorization_url( + self, + redirect_uri: str, + state: str | None = None, + scopes: list[str] | None = None, + ) -> tuple[str, PKCEChallenge]: + """Generate OAuth authorization URL with PKCE for Clerk""" + + pkce = PKCEChallenge() + session_id = secrets.token_urlsafe(32) + + if state is None: + state = secrets.token_urlsafe(16) + + if scopes is None: + scopes = self.config.scopes + + # Store session data with expiration + session_data = { + "pkce_verifier": pkce.verifier, + "state": state, + "redirect_uri": redirect_uri, + "scopes": scopes, + "created_at": time.time(), + "expires_at": (datetime.utcnow() + timedelta(minutes=10)).timestamp(), + } + self.storage.set_session(session_id, session_data) + + # Build Clerk OAuth URL + # Check if this is a custom domain (sign-in endpoint) + if self.config.authorization_endpoint.endswith('/sign-in'): + # For custom domains, Clerk expects redirect_url parameter + params = { + "redirect_url": redirect_uri, + "state": f"{state}:{session_id}", + } + auth_url = f"{self.config.authorization_endpoint}?{urlencode(params)}" + else: + # Standard OAuth flow with PKCE + params = { + "response_type": "code", + "client_id": self.config.client_id, + "redirect_uri": redirect_uri, + "scope": " ".join(scopes), + "state": f"{state}:{session_id}", # Combine state with session ID + "code_challenge": pkce.challenge, + "code_challenge_method": "S256", + } + auth_url = f"{self.config.authorization_endpoint}?{urlencode(params)}" + + logger.info(f"Generated OAuth URL with session {session_id[:8]}...") + logger.debug(f"Auth URL: {auth_url}") + return auth_url, pkce + + async def exchange_code_for_token( + self, code: str, state: str, redirect_uri: str + ) -> dict[str, Any]: + """Exchange authorization code for access token with Clerk""" + + try: + original_state, session_id = state.split(":", 1) + except ValueError as e: + logger.error(f"Invalid state format: {state}") + raise ValueError("Invalid state format") from e + + session = self.storage.get_session(session_id) + if not session: + logger.error(f"Session {session_id} not found") + raise ValueError("Invalid session") + + # Check session expiration + if datetime.utcnow().timestamp() > session.get("expires_at", 0): + self.storage.delete_session(session_id) + logger.error(f"Session {session_id} expired") + raise ValueError("Session expired") + + if session["state"] != original_state: + logger.error(f"State mismatch: expected {session['state']}, got {original_state}") + raise ValueError("State mismatch") + + if session["redirect_uri"] != redirect_uri: + logger.error(f"Redirect URI mismatch: expected {session['redirect_uri']}, got {redirect_uri}") + raise ValueError("Redirect URI mismatch") + + # Prepare token exchange request for Clerk + token_data = { + "grant_type": "authorization_code", + "client_id": self.config.client_id, + "client_secret": self.config.client_secret, + "code": code, + "redirect_uri": redirect_uri, + "code_verifier": session["pkce_verifier"], + } + + logger.info(f"Exchanging code with Clerk for session {session_id[:8]}...") + + async with httpx.AsyncClient() as client: + response = await client.post( + self.config.token_endpoint, + data=token_data, + headers={"Content-Type": "application/x-www-form-urlencoded"}, + timeout=30.0, + ) + + if response.status_code != 200: + logger.error(f"Clerk token exchange failed: {response.status_code} - {response.text}") + raise ValueError(f"Token exchange failed: {response.text}") + + token_response = response.json() + logger.info("Successfully exchanged code for Clerk token") + + # Create MCP-scoped JWT token + access_token = self._create_mcp_token( + session["scopes"], token_response.get("access_token"), session_id + ) + + # Store token for introspection + token_id = secrets.token_urlsafe(16) + token_data = { + "access_token": access_token, + "scopes": session["scopes"], + "created_at": time.time(), + "expires_at": (datetime.utcnow() + timedelta(hours=1)).timestamp(), + "session_id": session_id, + "clerk_token": token_response.get("access_token"), + } + self.storage.set_token(token_id, token_data) + + # Clean up session + self.storage.delete_session(session_id) + + return { + "access_token": access_token, + "token_type": "bearer", + "expires_in": 3600, + "scope": " ".join(session["scopes"]), + } + + def validate_pkce(self, code_verifier: str, code_challenge: str) -> bool: + """Validate PKCE code challenge (RFC 7636)""" + # S256 method + verifier_hash = hashlib.sha256(code_verifier.encode()).digest() + expected_challenge = base64.urlsafe_b64encode(verifier_hash).decode().rstrip('=') + return expected_challenge == code_challenge + + def _create_mcp_token( + self, scopes: list[str], upstream_token: str, session_id: str + ) -> str: + """Create MCP-scoped JWT token with Clerk token embedded""" + + now = int(time.time()) + payload = { + "iss": self.config.issuer, + "sub": session_id, + "aud": "mcp-server", + "iat": now, + "exp": now + 3600, # 1 hour expiration + "mcp_tool_scopes": scopes, + "upstream_token": upstream_token, + "clerk_integration": True, + } + + return jwt.encode(payload, self.jwt_secret, algorithm="HS256") + + def introspect_token(self, token: str) -> dict[str, Any]: + """Introspect and validate MCP token""" + + try: + payload = jwt.decode(token, self.jwt_secret, algorithms=["HS256"]) + + # Check if token is expired + if payload.get("exp", 0) < time.time(): + return {"active": False, "error": "token_expired"} + + return { + "active": True, + "sub": payload.get("sub"), + "aud": payload.get("aud"), + "iss": payload.get("iss"), + "exp": payload.get("exp"), + "iat": payload.get("iat"), + "mcp_tool_scopes": payload.get("mcp_tool_scopes", []), + "upstream_token": payload.get("upstream_token"), + "clerk_integration": payload.get("clerk_integration", False), + } + + except PyJWTError as e: + logger.warning(f"Token validation failed: {e}") + return {"active": False, "error": "invalid_token"} + + def revoke_token(self, token: str) -> bool: + """Revoke a token""" + + try: + payload = jwt.decode(token, self.jwt_secret, algorithms=["HS256"]) + session_id = payload.get("sub") + + # Remove all tokens associated with this session + all_tokens = self.storage.get_tokens() + tokens_to_remove = [ + token_id + for token_id, token_data in all_tokens.items() + if token_data.get("session_id") == session_id + ] + + for token_id in tokens_to_remove: + self.storage.delete_token(token_id) + + logger.info(f"Revoked {len(tokens_to_remove)} tokens for session {session_id}") + return True + + except InvalidTokenError as e: + logger.warning(f"Token revocation failed: {e}") + return False + + def cleanup_expired_sessions(self): + """Clean up expired sessions and tokens""" + # This is now handled automatically by persistent storage + self.storage.cleanup_expired_sessions() + logger.debug("Cleanup completed via persistent storage") \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/mcp_auth/policy.py b/saidsurucu-yargi-mcp-f5fa007/mcp_auth/policy.py new file mode 100644 index 0000000..e947af3 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/mcp_auth/policy.py @@ -0,0 +1,201 @@ +""" +Authorization policy engine for MCP tools +""" + +import re +import logging +from dataclasses import dataclass +from enum import Enum +from typing import Any + +logger = logging.getLogger(__name__) + + +class PolicyAction(Enum): + ALLOW = "allow" + DENY = "deny" + + +@dataclass +class ToolPolicy: + """Policy rule for MCP tool access""" + + tool_pattern: str # regex pattern for tool names + required_scopes: list[str] + action: PolicyAction = PolicyAction.ALLOW + conditions: dict[str, Any] | None = None + + def matches_tool(self, tool_name: str) -> bool: + """Check if the policy applies to given tool""" + return bool(re.match(self.tool_pattern, tool_name)) + + def evaluate_scopes(self, user_scopes: list[str]) -> bool: + """Check if user has required scopes""" + return all(scope in user_scopes for scope in self.required_scopes) + + +class PolicyEngine: + """Authorization policy engine for Turkish legal database tools""" + + def __init__(self): + self.policies: list[ToolPolicy] = [] + self.default_action = PolicyAction.DENY + + def add_policy(self, policy: ToolPolicy): + """Add a policy rule""" + self.policies.append(policy) + logger.debug(f"Added policy: {policy.tool_pattern} -> {policy.required_scopes}") + + def add_tool_scope_policy( + self, + tool_pattern: str, + required_scopes: str | list[str], + action: PolicyAction = PolicyAction.ALLOW, + ): + """Convenience method to add tool-scope policy""" + if isinstance(required_scopes, str): + required_scopes = [required_scopes] + + policy = ToolPolicy( + tool_pattern=tool_pattern, required_scopes=required_scopes, action=action + ) + self.add_policy(policy) + + def authorize_tool_call( + self, + tool_name: str, + user_scopes: list[str], + user_claims: dict[str, Any] | None = None, + ) -> tuple[bool, str | None]: + """ + Authorize a tool call + + Returns: + (authorized: bool, reason: Optional[str]) + """ + + logger.debug(f"Authorizing tool '{tool_name}' for user with scopes: {user_scopes}") + + matching_policies = [ + policy for policy in self.policies if policy.matches_tool(tool_name) + ] + + if not matching_policies: + if self.default_action == PolicyAction.ALLOW: + logger.debug(f"No policies found for '{tool_name}', allowing by default") + return True, None + else: + logger.warning(f"No policies found for '{tool_name}', denying by default") + return False, f"No policy found for tool '{tool_name}', default deny" + + # Check for explicit deny policies first + for policy in matching_policies: + if policy.action == PolicyAction.DENY: + if policy.evaluate_scopes(user_scopes): + logger.warning(f"Explicit deny policy matched for '{tool_name}'") + return False, f"Explicit deny policy for tool '{tool_name}'" + + # Check allow policies + allow_policies = [ + p for p in matching_policies if p.action == PolicyAction.ALLOW + ] + + if not allow_policies: + logger.warning(f"No allow policies found for '{tool_name}'") + return False, f"No allow policies found for tool '{tool_name}'" + + for policy in allow_policies: + if policy.evaluate_scopes(user_scopes): + if self._evaluate_conditions(policy.conditions, user_claims): + logger.debug(f"Authorization granted for '{tool_name}'") + return True, None + + logger.warning(f"Insufficient scopes for '{tool_name}'. Required: {[p.required_scopes for p in allow_policies]}, User has: {user_scopes}") + return False, f"Insufficient scopes for tool '{tool_name}'" + + def _evaluate_conditions( + self, + conditions: dict[str, Any] | None, + user_claims: dict[str, Any] | None, + ) -> bool: + """Evaluate additional policy conditions""" + + if not conditions: + return True + + if not user_claims: + logger.debug("No user claims provided, conditions evaluation failed") + return False + + for key, expected_value in conditions.items(): + user_value = user_claims.get(key) + + if isinstance(expected_value, list): + if user_value not in expected_value: + logger.debug(f"Condition failed: {key} = {user_value} not in {expected_value}") + return False + elif user_value != expected_value: + logger.debug(f"Condition failed: {key} = {user_value} != {expected_value}") + return False + + return True + + def get_allowed_tools(self, user_scopes: list[str]) -> list[str]: + """Get list of tool patterns user is allowed to call""" + + allowed_tools = [] + + for policy in self.policies: + if policy.action == PolicyAction.ALLOW and policy.evaluate_scopes( + user_scopes + ): + allowed_tools.append(policy.tool_pattern) + + return allowed_tools + + +def create_turkish_legal_policies() -> PolicyEngine: + """Create policy set for Turkish legal database MCP server""" + + engine = PolicyEngine() + + # Administrative tools (full access) + engine.add_tool_scope_policy(".*", ["mcp:tools:admin"]) + + # Search tools - require read access + engine.add_tool_scope_policy("search.*", ["mcp:tools:read"]) + + # Fetch/get document tools - require read access + engine.add_tool_scope_policy("get_.*", ["mcp:tools:read"]) + engine.add_tool_scope_policy("fetch.*", ["mcp:tools:read"]) + + # Specific Turkish legal database tools + engine.add_tool_scope_policy("search_yargitay.*", ["mcp:tools:read"]) + engine.add_tool_scope_policy("search_danistay.*", ["mcp:tools:read"]) + engine.add_tool_scope_policy("search_anayasa.*", ["mcp:tools:read"]) + engine.add_tool_scope_policy("search_rekabet.*", ["mcp:tools:read"]) + engine.add_tool_scope_policy("search_kik.*", ["mcp:tools:read"]) + engine.add_tool_scope_policy("search_emsal.*", ["mcp:tools:read"]) + engine.add_tool_scope_policy("search_uyusmazlik.*", ["mcp:tools:read"]) + engine.add_tool_scope_policy("search_sayistay.*", ["mcp:tools:read"]) + engine.add_tool_scope_policy("search_.*_bedesten", ["mcp:tools:read"]) + engine.add_tool_scope_policy("search_yerel_hukuk.*", ["mcp:tools:read"]) + engine.add_tool_scope_policy("search_istinaf_hukuk.*", ["mcp:tools:read"]) + engine.add_tool_scope_policy("search_kyb.*", ["mcp:tools:read"]) + + # Document retrieval tools + engine.add_tool_scope_policy("get_.*_document.*", ["mcp:tools:read"]) + engine.add_tool_scope_policy("get_.*_markdown", ["mcp:tools:read"]) + + # Write operations (if any future tools need them) + engine.add_tool_scope_policy("create_.*", ["mcp:tools:write"]) + engine.add_tool_scope_policy("update_.*", ["mcp:tools:write"]) + engine.add_tool_scope_policy("delete_.*", ["mcp:tools:write"]) + + logger.info("Created Turkish legal database policy engine") + return engine + + +def create_default_policies() -> PolicyEngine: + """Create a default policy set for MCP servers (backwards compatibility)""" + return create_turkish_legal_policies() \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/mcp_auth/storage.py b/saidsurucu-yargi-mcp-f5fa007/mcp_auth/storage.py new file mode 100644 index 0000000..ea1ad8f --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/mcp_auth/storage.py @@ -0,0 +1,112 @@ +""" +Persistent storage for OAuth sessions and tokens +""" + +import json +import os +import tempfile +import logging +from datetime import datetime +from typing import Dict, Any, Optional + +logger = logging.getLogger(__name__) + + +class PersistentStorage: + """File-based persistent storage for OAuth data""" + + def __init__(self, storage_dir: str = None): + if storage_dir is None: + # Use system temp directory or environment variable + storage_dir = os.environ.get('TEMP', tempfile.gettempdir()) + + self.storage_dir = os.path.join(storage_dir, 'mcp_oauth_storage') + os.makedirs(self.storage_dir, exist_ok=True) + + self.sessions_file = os.path.join(self.storage_dir, 'oauth_sessions.json') + self.tokens_file = os.path.join(self.storage_dir, 'oauth_tokens.json') + + logger.info(f"Persistent OAuth storage initialized at: {self.storage_dir}") + + def _load_json(self, filepath: str) -> Dict: + """Load JSON data from file""" + try: + if os.path.exists(filepath): + with open(filepath, 'r', encoding='utf-8') as f: + return json.load(f) + except Exception as e: + logger.error(f"Error loading {filepath}: {e}") + return {} + + def _save_json(self, filepath: str, data: Dict): + """Save JSON data to file""" + try: + with open(filepath, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, default=str) + except Exception as e: + logger.error(f"Error saving {filepath}: {e}") + + def get_sessions(self) -> Dict[str, Dict[str, Any]]: + """Get all OAuth sessions""" + data = self._load_json(self.sessions_file) + # Clean expired sessions + now = datetime.utcnow().timestamp() + valid_sessions = {k: v for k, v in data.items() + if v.get('expires_at', 0) > now} + if len(valid_sessions) != len(data): + self._save_json(self.sessions_file, valid_sessions) + return valid_sessions + + def set_session(self, session_id: str, data: Dict[str, Any]): + """Set OAuth session data""" + sessions = self.get_sessions() + sessions[session_id] = data + self._save_json(self.sessions_file, sessions) + + def get_session(self, session_id: str) -> Optional[Dict[str, Any]]: + """Get specific OAuth session data""" + sessions = self.get_sessions() + return sessions.get(session_id) + + def delete_session(self, session_id: str): + """Delete OAuth session""" + sessions = self.get_sessions() + if session_id in sessions: + del sessions[session_id] + self._save_json(self.sessions_file, sessions) + + def get_tokens(self) -> Dict[str, Dict[str, Any]]: + """Get all OAuth tokens""" + data = self._load_json(self.tokens_file) + # Clean expired tokens + now = datetime.utcnow().timestamp() + valid_tokens = {k: v for k, v in data.items() + if v.get('expires_at', 0) > now} + if len(valid_tokens) != len(data): + self._save_json(self.tokens_file, valid_tokens) + return valid_tokens + + def set_token(self, token_id: str, token_data: Dict[str, Any]): + """Set OAuth token data""" + tokens = self.get_tokens() + tokens[token_id] = token_data + self._save_json(self.tokens_file, tokens) + + def get_token(self, token_id: str) -> Optional[Dict[str, Any]]: + """Get specific OAuth token data""" + tokens = self.get_tokens() + return tokens.get(token_id) + + def delete_token(self, token_id: str): + """Delete OAuth token""" + tokens = self.get_tokens() + if token_id in tokens: + del tokens[token_id] + self._save_json(self.tokens_file, tokens) + + def cleanup_expired_sessions(self): + """Clean up expired sessions and tokens""" + # This is handled automatically in get_sessions() and get_tokens() + sessions = self.get_sessions() + tokens = self.get_tokens() + logger.debug(f"Cleanup: {len(sessions)} active sessions, {len(tokens)} active tokens") \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/mcp_auth_factory.py b/saidsurucu-yargi-mcp-f5fa007/mcp_auth_factory.py new file mode 100644 index 0000000..a5760f6 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/mcp_auth_factory.py @@ -0,0 +1,193 @@ +""" +Factory for creating FastMCP app with MCP Auth Toolkit integration +""" + +import logging +import os +from typing import Optional + +logger = logging.getLogger(__name__) + +try: + from fastmcp import FastMCP + FASTMCP_AVAILABLE = True +except ImportError: + FASTMCP_AVAILABLE = False + FastMCP = None + +from mcp_auth import ( + OAuthProvider, + PolicyEngine, + FastMCPAuthWrapper, + create_default_policies +) +from mcp_auth.clerk_config import create_mcp_server_config + + +def create_auth_enabled_app(app_name: str = "Yargı MCP Server") -> FastMCP: + """Create FastMCP app with authentication enabled""" + + if not FASTMCP_AVAILABLE: + raise ImportError("FastMCP is required for authenticated MCP server") + + logger.info("Creating FastMCP app with MCP Auth Toolkit integration") + + # Create base FastMCP app + app = FastMCP(app_name) + + # Check if authentication is enabled + auth_enabled = os.getenv("ENABLE_AUTH", "true").lower() == "true" + + if not auth_enabled: + logger.info("Authentication disabled, returning basic FastMCP app") + return app + + try: + # Get configuration + logger.info("Getting MCP server configuration...") + config = create_mcp_server_config() + logger.info("Configuration loaded successfully") + + # Create OAuth provider with Clerk config + logger.info("Creating OAuth provider...") + oauth_provider = OAuthProvider( + config=config["oauth_config"], + jwt_secret=config["jwt_secret"] + ) + logger.info("OAuth provider created successfully") + + # Create policy engine for Turkish legal database + policy_engine = create_default_policies() + + # Store auth components for later wrapping (after tools are defined) + app._oauth_provider = oauth_provider + app._policy_engine = policy_engine + app._auth_config = config + + # Add OAuth endpoints immediately + @app.tool( + description="Initiate OAuth 2.1 authorization flow with PKCE", + annotations={"readOnlyHint": True, "idempotentHint": False} + ) + async def oauth_authorize(redirect_uri: str, scopes: str = None): + """OAuth authorization endpoint""" + scope_list = scopes.split(" ") if scopes else ["mcp:tools:read", "mcp:tools:write"] + auth_url, pkce = oauth_provider.generate_authorization_url( + redirect_uri=redirect_uri, scopes=scope_list + ) + logger.info(f"Generated authorization URL for redirect_uri: {redirect_uri}") + return { + "authorization_url": auth_url, + "code_verifier": pkce.verifier, + "code_challenge": pkce.challenge, + "instructions": "Use the authorization_url to complete OAuth flow, then exchange the returned code using oauth_token tool" + } + + @app.tool( + description="Exchange OAuth authorization code for access token", + annotations={"readOnlyHint": False, "idempotentHint": False} + ) + async def oauth_token(code: str, state: str, redirect_uri: str): + """OAuth token exchange endpoint""" + try: + result = await oauth_provider.exchange_code_for_token( + code=code, state=state, redirect_uri=redirect_uri + ) + logger.info("Successfully exchanged authorization code for token") + return result + except Exception as e: + logger.error(f"Token exchange failed: {e}") + raise + + @app.tool( + description="Validate and introspect OAuth access token", + annotations={"readOnlyHint": True, "idempotentHint": True} + ) + async def oauth_introspect(token: str): + """Token introspection endpoint""" + result = oauth_provider.introspect_token(token) + logger.debug(f"Token introspection: active={result.get('active', False)}") + return result + + @app.tool( + description="Revoke OAuth access token", + annotations={"readOnlyHint": False, "idempotentHint": False} + ) + async def oauth_revoke(token: str): + """Token revocation endpoint""" + success = oauth_provider.revoke_token(token) + logger.info(f"Token revocation: success={success}") + return {"revoked": success} + + logger.info("Successfully created authenticated FastMCP app") + + except Exception as e: + logger.error(f"Failed to create authenticated app: {e}") + logger.info("Falling back to non-authenticated FastMCP app") + # Return basic app if auth setup fails + return app + + return app + + +def create_app() -> FastMCP: + """Create FastMCP app (backwards compatible with mcp_factory.py)""" + return create_auth_enabled_app() + + +def get_auth_wrapper(app: FastMCP) -> Optional[FastMCPAuthWrapper]: + """Get auth wrapper from app if available""" + return getattr(app, '_auth_wrapper', None) + + +def get_oauth_provider(app: FastMCP) -> Optional[OAuthProvider]: + """Get OAuth provider from app if available""" + return getattr(app, '_oauth_provider', None) + + +def get_policy_engine(app: FastMCP) -> Optional[PolicyEngine]: + """Get policy engine from app if available""" + return getattr(app, '_policy_engine', None) + + +def is_auth_enabled(app: FastMCP) -> bool: + """Check if authentication is enabled for the app""" + return hasattr(app, '_oauth_provider') or hasattr(app, '_auth_wrapper') + + +def enable_tool_authentication(app: FastMCP): + """Enable authentication on all existing tools (call after tools are defined)""" + if not is_auth_enabled(app): + logger.debug("Authentication not enabled, skipping tool authentication") + return + + oauth_provider = get_oauth_provider(app) + policy_engine = get_policy_engine(app) + + if not oauth_provider or not policy_engine: + logger.warning("OAuth provider or policy engine not available") + return + + try: + # Create auth wrapper and wrap tools + auth_wrapper = FastMCPAuthWrapper( + mcp_server=app, + oauth_provider=oauth_provider, + policy_engine=policy_engine + ) + + # Store wrapper for reference + app._auth_wrapper = auth_wrapper + + logger.info("Tool authentication enabled successfully") + + except Exception as e: + logger.error(f"Failed to enable tool authentication: {e}") + + +def cleanup_auth_sessions(app: FastMCP): + """Clean up expired auth sessions and tokens""" + oauth_provider = get_oauth_provider(app) + if oauth_provider: + oauth_provider.cleanup_expired_sessions() + logger.debug("Cleaned up expired OAuth sessions") \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/mcp_auth_http_adapter.py b/saidsurucu-yargi-mcp-f5fa007/mcp_auth_http_adapter.py new file mode 100644 index 0000000..442b540 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/mcp_auth_http_adapter.py @@ -0,0 +1,383 @@ +""" +HTTP adapter for MCP Auth Toolkit OAuth endpoints +Exposes MCP OAuth tools as HTTP endpoints for Claude.ai integration +""" + +import os +import logging +import secrets +import time +from typing import Optional +from urllib.parse import urlencode, quote +from datetime import datetime, timedelta + +from fastapi import APIRouter, Request, Query, HTTPException +from fastapi.responses import RedirectResponse, JSONResponse + +# Try to import Clerk SDK +try: + from clerk_backend_api import Clerk + CLERK_AVAILABLE = True +except ImportError as e: + CLERK_AVAILABLE = False + Clerk = None + +logger = logging.getLogger(__name__) + +router = APIRouter() + +# OAuth configuration +BASE_URL = os.getenv("BASE_URL", "https://yargimcp.com") + + +@router.get("/.well-known/oauth-authorization-server") +async def get_oauth_metadata(): + """OAuth 2.0 Authorization Server Metadata (RFC 8414)""" + return JSONResponse({ + "issuer": BASE_URL, + "authorization_endpoint": f"{BASE_URL}/authorize", + "token_endpoint": f"{BASE_URL}/token", + "registration_endpoint": f"{BASE_URL}/register", + "response_types_supported": ["code"], + "grant_types_supported": ["authorization_code", "refresh_token"], + "code_challenge_methods_supported": ["S256"], + "token_endpoint_auth_methods_supported": ["none"], + "scopes_supported": ["mcp:tools:read", "mcp:tools:write", "openid", "profile", "email"], + "service_documentation": f"{BASE_URL}/mcp/" + }) + + +@router.get("/.well-known/oauth-protected-resource") +async def get_protected_resource_metadata(): + """OAuth Protected Resource Metadata (RFC 9728)""" + return JSONResponse({ + "resource": BASE_URL, + "authorization_servers": [BASE_URL], + "bearer_methods_supported": ["header"], + "scopes_supported": ["mcp:tools:read", "mcp:tools:write"], + "resource_documentation": f"{BASE_URL}/docs" + }) + + +@router.get("/authorize") +async def authorize_endpoint( + response_type: str = Query(...), + client_id: str = Query(...), + redirect_uri: str = Query(...), + code_challenge: str = Query(...), + code_challenge_method: str = Query("S256"), + state: Optional[str] = Query(None), + scope: Optional[str] = Query(None) +): + """OAuth 2.1 Authorization Endpoint - Uses Clerk SDK for custom domains""" + + logger.info(f"OAuth authorize request - client_id: {client_id}, redirect_uri: {redirect_uri}") + + if not CLERK_AVAILABLE: + logger.error("Clerk SDK not available") + raise HTTPException(status_code=500, detail="Clerk SDK not available") + + # Store OAuth session for later validation + try: + from mcp_server_main import app as mcp_app + from mcp_auth_factory import get_oauth_provider + + oauth_provider = get_oauth_provider(mcp_app) + if not oauth_provider: + raise HTTPException(status_code=500, detail="OAuth provider not configured") + + # Generate session and store PKCE + session_id = secrets.token_urlsafe(32) + if state is None: + state = secrets.token_urlsafe(16) + + # Create PKCE challenge + from mcp_auth.oauth import PKCEChallenge + pkce = PKCEChallenge() + + # Store session data + session_data = { + "pkce_verifier": pkce.verifier, + "pkce_challenge": code_challenge, # Store the client's challenge + "state": state, + "redirect_uri": redirect_uri, + "client_id": client_id, + "scopes": scope.split(" ") if scope else ["mcp:tools:read", "mcp:tools:write"], + "created_at": time.time(), + "expires_at": (datetime.utcnow() + timedelta(minutes=10)).timestamp(), + } + oauth_provider.storage.set_session(session_id, session_data) + + # For Clerk with custom domains, we need to use their hosted sign-in page + # We'll pass our callback URL and session info in the state + callback_url = f"{BASE_URL}/auth/callback" + + # Encode session info in state for retrieval after Clerk auth + combined_state = f"{state}:{session_id}" + + # Use Clerk's sign-in URL with proper parameters + clerk_domain = os.getenv("CLERK_DOMAIN", "accounts.yargimcp.com") + sign_in_params = { + "redirect_url": f"{callback_url}?state={quote(combined_state)}", + } + + sign_in_url = f"https://{clerk_domain}/sign-in?{urlencode(sign_in_params)}" + + logger.info(f"Redirecting to Clerk sign-in: {sign_in_url}") + + return RedirectResponse(url=sign_in_url) + + except Exception as e: + logger.exception(f"Authorization failed: {e}") + raise HTTPException(status_code=500, detail=str(e)) + + +@router.get("/auth/callback") +async def oauth_callback( + request: Request, + state: Optional[str] = Query(None), + clerk_token: Optional[str] = Query(None) +): + """Handle OAuth callback from Clerk - supports both JWT token and cookie auth""" + + logger.info(f"OAuth callback received - state: {state}") + logger.info(f"Query params: {dict(request.query_params)}") + logger.info(f"Cookies: {dict(request.cookies)}") + logger.info(f"Clerk JWT token provided: {bool(clerk_token)}") + + # Support both JWT token (for cross-domain) and cookie auth (for subdomain) + + try: + if not state: + logger.error("No state parameter provided") + return JSONResponse( + status_code=400, + content={"error": "invalid_request", "error_description": "Missing state parameter"} + ) + + # Parse state to get original state and session ID + try: + if ":" in state: + original_state, session_id = state.rsplit(":", 1) + else: + original_state = state + session_id = state # Fallback + except ValueError: + logger.error(f"Invalid state format: {state}") + return JSONResponse( + status_code=400, + content={"error": "invalid_request", "error_description": "Invalid state format"} + ) + + # Get OAuth provider + from mcp_server_main import app as mcp_app + from mcp_auth_factory import get_oauth_provider + + oauth_provider = get_oauth_provider(mcp_app) + if not oauth_provider: + raise HTTPException(status_code=500, detail="OAuth provider not configured") + + # Get stored session + oauth_session = oauth_provider.storage.get_session(session_id) + + if not oauth_session: + logger.error(f"OAuth session not found for ID: {session_id}") + return JSONResponse( + status_code=400, + content={"error": "invalid_request", "error_description": "OAuth session expired or not found"} + ) + + # Check if we have a JWT token (for cross-domain auth) + user_authenticated = False + auth_method = "none" + + if clerk_token: + logger.info("Attempting JWT token validation") + try: + # Validate JWT token with Clerk + from clerk_backend_api import Clerk + clerk = Clerk(bearer_auth=os.getenv("CLERK_SECRET_KEY")) + + # Extract session_id from JWT token and verify with Clerk + import jwt + decoded_token = jwt.decode(clerk_token, options={"verify_signature": False}) + session_id = decoded_token.get("sid") or decoded_token.get("session_id") + + if session_id: + # Verify with Clerk using session_id + session = clerk.sessions.verify(session_id=session_id, token=clerk_token) + user_id = session.user_id if session else None + else: + user_id = None + + if user_id: + logger.info(f"JWT token validation successful - user_id: {user_id}") + user_authenticated = True + auth_method = "jwt_token" + # Store user info in session for token exchange + oauth_session["user_id"] = user_id + oauth_session["auth_method"] = "jwt_token" + else: + logger.error("JWT token validation failed - no user_id in claims") + except Exception as e: + logger.error(f"JWT token validation failed: {str(e)}") + # Fall through to cookie validation + + # If no JWT token or validation failed, check cookies + if not user_authenticated: + logger.info("Checking for Clerk session cookies") + # Check for Clerk session cookies (for subdomain auth) + clerk_session_cookie = request.cookies.get("__session") + if clerk_session_cookie: + logger.info("Found Clerk session cookie, assuming authenticated") + user_authenticated = True + auth_method = "cookie" + oauth_session["auth_method"] = "cookie" + else: + logger.info("No Clerk session cookie found") + + # For custom domains, we'll also trust that Clerk redirected here + if not user_authenticated: + logger.info("Trusting Clerk redirect for custom domain flow") + user_authenticated = True + auth_method = "trusted_redirect" + oauth_session["auth_method"] = "trusted_redirect" + + logger.info(f"User authenticated: {user_authenticated}, method: {auth_method}") + + # Generate simple authorization code for custom domain flow + auth_code = f"clerk_custom_{session_id}_{int(time.time())}" + + # Store the code mapping for token exchange + code_data = { + "session_id": session_id, + "clerk_authenticated": user_authenticated, + "auth_method": auth_method, + "custom_domain_flow": True, + "created_at": time.time(), + "expires_at": (datetime.utcnow() + timedelta(minutes=5)).timestamp(), + } + if "user_id" in oauth_session: + code_data["user_id"] = oauth_session["user_id"] + + oauth_provider.storage.set_session(f"code_{auth_code}", code_data) + + # Build redirect URL back to Claude + redirect_params = { + "code": auth_code, + "state": original_state + } + + redirect_url = f"{oauth_session['redirect_uri']}?{urlencode(redirect_params)}" + logger.info(f"Redirecting back to Claude: {redirect_url}") + + return RedirectResponse(url=redirect_url) + + except Exception as e: + logger.exception(f"Callback processing failed: {e}") + return JSONResponse( + status_code=500, + content={"error": "server_error", "error_description": str(e)} + ) + + +@router.post("/register") +async def register_client(request: Request): + """Dynamic Client Registration (RFC 7591)""" + + data = await request.json() + logger.info(f"Client registration request: {data}") + + # Simple dynamic registration - accept any client + client_id = f"mcp-client-{os.urandom(8).hex()}" + + return JSONResponse({ + "client_id": client_id, + "client_secret": None, # Public client + "redirect_uris": data.get("redirect_uris", []), + "grant_types": ["authorization_code", "refresh_token"], + "response_types": ["code"], + "client_name": data.get("client_name", "MCP Client"), + "token_endpoint_auth_method": "none", + "client_id_issued_at": int(datetime.now().timestamp()) + }) + + +@router.post("/token") +async def token_endpoint(request: Request): + """OAuth 2.1 Token Endpoint""" + + # Parse form data + form_data = await request.form() + grant_type = form_data.get("grant_type") + code = form_data.get("code") + redirect_uri = form_data.get("redirect_uri") + client_id = form_data.get("client_id") + code_verifier = form_data.get("code_verifier") + + logger.info(f"Token exchange - grant_type: {grant_type}, code: {code[:20] if code else 'None'}...") + + if grant_type != "authorization_code": + return JSONResponse( + status_code=400, + content={"error": "unsupported_grant_type"} + ) + + try: + # OAuth token exchange - validate code and return Clerk JWT + # This supports proper OAuth flow while using Clerk JWT tokens + + if not code or not redirect_uri: + logger.error("Missing required parameters: code or redirect_uri") + return JSONResponse( + status_code=400, + content={"error": "invalid_request", "error_description": "Missing code or redirect_uri"} + ) + + # Validate OAuth code with Clerk + if CLERK_AVAILABLE: + try: + clerk = Clerk(bearer_auth=os.getenv("CLERK_SECRET_KEY")) + + # In a real implementation, you'd validate the code with Clerk + # For now, we'll assume the code is valid if it looks like a Clerk code + if len(code) > 10: # Basic validation + # Create a mock session with the code + # In practice, this would be validated with Clerk's OAuth flow + + # Return Clerk JWT token format + # This should be the actual Clerk JWT token from the OAuth flow + return JSONResponse({ + "access_token": f"mock_clerk_jwt_{code}", + "token_type": "Bearer", + "expires_in": 3600, + "scope": "yargi.read yargi.search" + }) + else: + logger.error(f"Invalid code format: {code}") + return JSONResponse( + status_code=400, + content={"error": "invalid_grant", "error_description": "Invalid authorization code"} + ) + + except Exception as e: + logger.error(f"Clerk validation failed: {e}") + return JSONResponse( + status_code=400, + content={"error": "invalid_grant", "error_description": "Authorization code validation failed"} + ) + else: + logger.warning("Clerk SDK not available, using mock response") + return JSONResponse({ + "access_token": "mock_jwt_token_for_development", + "token_type": "Bearer", + "expires_in": 3600, + "scope": "yargi.read yargi.search" + }) + + except Exception as e: + logger.exception(f"Token exchange failed: {e}") + return JSONResponse( + status_code=500, + content={"error": "server_error", "error_description": str(e)} + ) \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/mcp_auth_http_simple.py b/saidsurucu-yargi-mcp-f5fa007/mcp_auth_http_simple.py new file mode 100644 index 0000000..4311ef9 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/mcp_auth_http_simple.py @@ -0,0 +1,522 @@ +""" +Simplified MCP OAuth HTTP adapter - only Clerk JWT based authentication +Uses Redis for authorization code storage to support multi-machine deployment +""" + +import os +import logging +from typing import Optional +from urllib.parse import urlencode, quote + +from fastapi import APIRouter, Request, Query, HTTPException +from fastapi.responses import RedirectResponse, JSONResponse + +# Import Redis session store +from redis_session_store import get_redis_store + +# Try to import Clerk SDK +try: + from clerk_backend_api import Clerk + CLERK_AVAILABLE = True +except ImportError: + CLERK_AVAILABLE = False + Clerk = None + +logger = logging.getLogger(__name__) + +router = APIRouter() + +# OAuth configuration +BASE_URL = os.getenv("BASE_URL", "https://api.yargimcp.com") +CLERK_DOMAIN = os.getenv("CLERK_DOMAIN", "accounts.yargimcp.com") + +# Initialize Redis store +redis_store = None + +def get_redis_session_store(): + """Get Redis store instance with lazy initialization.""" + global redis_store + if redis_store is None: + try: + import concurrent.futures + import functools + + # Use thread pool with timeout to prevent hanging + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: + future = executor.submit(get_redis_store) + try: + # 5 second timeout for Redis initialization + redis_store = future.result(timeout=5.0) + if redis_store: + logger.info("Redis session store initialized for OAuth handler") + else: + logger.warning("Redis store initialization returned None") + except concurrent.futures.TimeoutError: + logger.error("Redis initialization timed out after 5 seconds") + redis_store = None + future.cancel() # Try to cancel the hanging operation + + except Exception as e: + logger.error(f"Failed to initialize Redis store: {e}") + redis_store = None + + if redis_store is None: + # Fall back to in-memory storage with warning + logger.warning("Falling back to in-memory storage - multi-machine deployment will not work") + + return redis_store + +@router.get("/.well-known/oauth-authorization-server") +async def get_oauth_metadata(): + """OAuth 2.0 Authorization Server Metadata (RFC 8414)""" + return JSONResponse({ + "issuer": BASE_URL, + "authorization_endpoint": "https://yargimcp.com/mcp-callback", + "token_endpoint": f"{BASE_URL}/token", + "registration_endpoint": f"{BASE_URL}/register", + "response_types_supported": ["code"], + "grant_types_supported": ["authorization_code"], + "code_challenge_methods_supported": ["S256"], + "token_endpoint_auth_methods_supported": ["none"], + "scopes_supported": ["read", "search", "openid", "profile", "email"], + "service_documentation": f"{BASE_URL}/mcp/" + }) + +@router.get("/auth/login") +async def oauth_authorize( + request: Request, + client_id: str = Query(...), + redirect_uri: str = Query(...), + response_type: str = Query("code"), + scope: Optional[str] = Query("read search"), + state: Optional[str] = Query(None), + code_challenge: Optional[str] = Query(None), + code_challenge_method: Optional[str] = Query(None) +): + """OAuth 2.1 Authorization Endpoint - redirects to Clerk""" + + logger.info(f"OAuth authorize request - client_id: {client_id}") + logger.info(f"Redirect URI: {redirect_uri}") + logger.info(f"State: {state}") + logger.info(f"PKCE Challenge: {bool(code_challenge)}") + + try: + # Build callback URL with all necessary parameters + callback_url = f"{BASE_URL}/auth/callback" + callback_params = { + "client_id": client_id, + "redirect_uri": redirect_uri, + "state": state or "", + "scope": scope or "read search" + } + + # Add PKCE parameters if present + if code_challenge: + callback_params["code_challenge"] = code_challenge + callback_params["code_challenge_method"] = code_challenge_method or "S256" + + # Encode callback URL as redirect_url for Clerk + callback_with_params = f"{callback_url}?{urlencode(callback_params)}" + + # Build Clerk sign-in URL - use yargimcp.com frontend for JWT token generation + clerk_params = { + "redirect_url": callback_with_params + } + + # Use frontend sign-in page that handles JWT token generation + clerk_signin_url = f"https://yargimcp.com/sign-in?{urlencode(clerk_params)}" + + logger.info(f"Redirecting to Clerk: {clerk_signin_url}") + + return RedirectResponse(url=clerk_signin_url) + + except Exception as e: + logger.exception(f"Authorization failed: {e}") + raise HTTPException(status_code=500, detail=str(e)) + +@router.get("/auth/callback") +async def oauth_callback( + request: Request, + client_id: str = Query(...), + redirect_uri: str = Query(...), + state: Optional[str] = Query(None), + scope: Optional[str] = Query("read search"), + code_challenge: Optional[str] = Query(None), + code_challenge_method: Optional[str] = Query(None), + clerk_token: Optional[str] = Query(None) +): + """OAuth callback from Clerk - generates authorization code""" + + logger.info(f"OAuth callback - client_id: {client_id}") + logger.info(f"Clerk token provided: {bool(clerk_token)}") + + try: + # Validate user with Clerk and generate real JWT token + user_authenticated = False + user_id = None + session_id = None + real_jwt_token = None + + if clerk_token and CLERK_AVAILABLE: + try: + # Extract user info from JWT token (no Clerk session verification needed) + import jwt + decoded_token = jwt.decode(clerk_token, options={"verify_signature": False}) + user_id = decoded_token.get("user_id") or decoded_token.get("sub") + user_email = decoded_token.get("email") + token_scopes = decoded_token.get("scopes", ["read", "search"]) + + logger.info(f"JWT token claims - user_id: {user_id}, email: {user_email}, scopes: {token_scopes}") + + if user_id and user_email: + # JWT token is already signed by Clerk and contains valid user info + user_authenticated = True + logger.info(f"User authenticated via JWT token - user_id: {user_id}") + + # Use the JWT token directly as the real token (it's already from Clerk template) + real_jwt_token = clerk_token + logger.info("Using Clerk JWT token directly (already real token)") + + else: + logger.error(f"Missing required fields in JWT token - user_id: {bool(user_id)}, email: {bool(user_email)}") + + except Exception as e: + logger.error(f"JWT validation failed: {e}") + + # Fallback to cookie validation + if not user_authenticated: + clerk_session = request.cookies.get("__session") + if clerk_session: + user_authenticated = True + logger.info("User authenticated via cookie") + + # Try to get session from cookie and generate JWT + if CLERK_AVAILABLE: + try: + clerk = Clerk(bearer_auth=os.getenv("CLERK_SECRET_KEY")) + # Note: sessions.verify_session is deprecated, but we'll try + # In practice, you'd need to extract session_id from cookie + logger.info("Cookie authentication - JWT generation not implemented yet") + except Exception as e: + logger.warning(f"Failed to generate JWT from cookie: {e}") + + # Only generate authorization code if we have a real JWT token + if user_authenticated and real_jwt_token: + # Generate authorization code + auth_code = f"clerk_auth_{os.urandom(16).hex()}" + + # Prepare code data + import time + code_data = { + "user_id": user_id, + "session_id": session_id, + "real_jwt_token": real_jwt_token, + "user_authenticated": user_authenticated, + "client_id": client_id, + "redirect_uri": redirect_uri, + "scope": scope or "read search" + } + + # Try to store in Redis, fall back to in-memory if Redis unavailable + store = get_redis_session_store() + if store: + # Store in Redis with automatic expiration + success = store.set_oauth_code(auth_code, code_data) + if success: + logger.info(f"Stored authorization code {auth_code[:10]}... in Redis with real JWT token") + else: + logger.error(f"Failed to store authorization code in Redis, falling back to in-memory") + # Fall back to in-memory storage + if not hasattr(oauth_callback, '_code_storage'): + oauth_callback._code_storage = {} + oauth_callback._code_storage[auth_code] = code_data + else: + # Fall back to in-memory storage + logger.warning("Redis not available, using in-memory storage") + if not hasattr(oauth_callback, '_code_storage'): + oauth_callback._code_storage = {} + oauth_callback._code_storage[auth_code] = code_data + logger.info(f"Stored authorization code in memory (fallback)") + + # Redirect back to client with authorization code + redirect_params = { + "code": auth_code, + "state": state or "" + } + + final_redirect_url = f"{redirect_uri}?{urlencode(redirect_params)}" + logger.info(f"Redirecting back to client: {final_redirect_url}") + + return RedirectResponse(url=final_redirect_url) + else: + # No JWT token yet - redirect back to sign-in page to wait for authentication + logger.info("No JWT token provided - redirecting back to sign-in to complete authentication") + + # Keep the same redirect URL so the flow continues + sign_in_params = { + "redirect_url": f"{request.url._url}" # Current callback URL with all params + } + + sign_in_url = f"https://yargimcp.com/sign-in?{urlencode(sign_in_params)}" + logger.info(f"Redirecting back to sign-in: {sign_in_url}") + + return RedirectResponse(url=sign_in_url) + + except Exception as e: + logger.exception(f"Callback processing failed: {e}") + return JSONResponse( + status_code=500, + content={"error": "server_error", "error_description": str(e)} + ) + +@router.post("/auth/register") +async def register_client(request: Request): + """Dynamic Client Registration (RFC 7591)""" + + data = await request.json() + logger.info(f"Client registration request: {data}") + + # Simple dynamic registration - accept any client + client_id = f"mcp-client-{os.urandom(8).hex()}" + + return JSONResponse({ + "client_id": client_id, + "client_secret": None, # Public client + "redirect_uris": data.get("redirect_uris", []), + "grant_types": ["authorization_code"], + "response_types": ["code"], + "client_name": data.get("client_name", "MCP Client"), + "token_endpoint_auth_method": "none" + }) + +@router.post("/auth/callback") +async def oauth_callback_post(request: Request): + """OAuth callback POST endpoint for token exchange""" + + # Parse form data (standard OAuth token exchange format) + form_data = await request.form() + grant_type = form_data.get("grant_type") + code = form_data.get("code") + redirect_uri = form_data.get("redirect_uri") + client_id = form_data.get("client_id") + code_verifier = form_data.get("code_verifier") + + logger.info(f"OAuth callback POST - grant_type: {grant_type}") + logger.info(f"Code: {code[:20] if code else 'None'}...") + logger.info(f"Client ID: {client_id}") + logger.info(f"PKCE verifier: {bool(code_verifier)}") + + if grant_type != "authorization_code": + return JSONResponse( + status_code=400, + content={"error": "unsupported_grant_type"} + ) + + if not code or not redirect_uri: + return JSONResponse( + status_code=400, + content={"error": "invalid_request", "error_description": "Missing code or redirect_uri"} + ) + + try: + # Validate authorization code + if not code.startswith("clerk_auth_"): + return JSONResponse( + status_code=400, + content={"error": "invalid_grant", "error_description": "Invalid authorization code"} + ) + + # Retrieve stored JWT token using authorization code from Redis or in-memory fallback + stored_code_data = None + + # Try to get from Redis first, then fall back to in-memory + store = get_redis_session_store() + if store: + stored_code_data = store.get_oauth_code(code, delete_after_use=True) + if stored_code_data: + logger.info(f"Retrieved authorization code {code[:10]}... from Redis") + else: + logger.warning(f"Authorization code {code[:10]}... not found in Redis") + + # Fall back to in-memory storage if Redis unavailable or code not found + if not stored_code_data and hasattr(oauth_callback, '_code_storage'): + stored_code_data = oauth_callback._code_storage.get(code) + if stored_code_data: + # Clean up in-memory storage + oauth_callback._code_storage.pop(code, None) + logger.info(f"Retrieved authorization code {code[:10]}... from in-memory storage") + + if not stored_code_data: + logger.error(f"No stored data found for authorization code: {code}") + return JSONResponse( + status_code=400, + content={"error": "invalid_grant", "error_description": "Authorization code not found or expired"} + ) + + # Note: Redis TTL handles expiration automatically, but check for manual expiration for in-memory fallback + import time + expires_at = stored_code_data.get("expires_at", 0) + if expires_at and time.time() > expires_at: + logger.error(f"Authorization code expired: {code}") + return JSONResponse( + status_code=400, + content={"error": "invalid_grant", "error_description": "Authorization code expired"} + ) + + # Get the real JWT token + real_jwt_token = stored_code_data.get("real_jwt_token") + + if real_jwt_token: + logger.info("Returning real Clerk JWT token") + # Note: Code already deleted from Redis, clean up in-memory fallback if used + if hasattr(oauth_callback, '_code_storage'): + oauth_callback._code_storage.pop(code, None) + + return JSONResponse({ + "access_token": real_jwt_token, + "token_type": "Bearer", + "expires_in": 3600, + "scope": "read search" + }) + else: + logger.warning("No real JWT token found, generating mock token") + # Fallback to mock token for testing + mock_token = f"mock_clerk_jwt_{code}" + return JSONResponse({ + "access_token": mock_token, + "token_type": "Bearer", + "expires_in": 3600, + "scope": "read search" + }) + + except Exception as e: + logger.exception(f"OAuth callback POST failed: {e}") + return JSONResponse( + status_code=500, + content={"error": "server_error", "error_description": str(e)} + ) + +@router.post("/register") +async def register_client(request: Request): + """Dynamic Client Registration (RFC 7591)""" + + data = await request.json() + logger.info(f"Client registration request: {data}") + + # Simple dynamic registration - accept any client + client_id = f"mcp-client-{os.urandom(8).hex()}" + + return JSONResponse({ + "client_id": client_id, + "client_secret": None, # Public client + "redirect_uris": data.get("redirect_uris", []), + "grant_types": ["authorization_code"], + "response_types": ["code"], + "client_name": data.get("client_name", "MCP Client"), + "token_endpoint_auth_method": "none" + }) + +@router.post("/token") +async def token_endpoint(request: Request): + """OAuth 2.1 Token Endpoint - exchanges code for Clerk JWT""" + + # Parse form data + form_data = await request.form() + grant_type = form_data.get("grant_type") + code = form_data.get("code") + redirect_uri = form_data.get("redirect_uri") + client_id = form_data.get("client_id") + code_verifier = form_data.get("code_verifier") + + logger.info(f"Token exchange - grant_type: {grant_type}") + logger.info(f"Code: {code[:20] if code else 'None'}...") + + if grant_type != "authorization_code": + return JSONResponse( + status_code=400, + content={"error": "unsupported_grant_type"} + ) + + if not code or not redirect_uri: + return JSONResponse( + status_code=400, + content={"error": "invalid_request", "error_description": "Missing code or redirect_uri"} + ) + + try: + # Validate authorization code + if not code.startswith("clerk_auth_"): + return JSONResponse( + status_code=400, + content={"error": "invalid_grant", "error_description": "Invalid authorization code"} + ) + + # Retrieve stored JWT token using authorization code from Redis or in-memory fallback + stored_code_data = None + + # Try to get from Redis first, then fall back to in-memory + store = get_redis_session_store() + if store: + stored_code_data = store.get_oauth_code(code, delete_after_use=True) + if stored_code_data: + logger.info(f"Retrieved authorization code {code[:10]}... from Redis (/token endpoint)") + else: + logger.warning(f"Authorization code {code[:10]}... not found in Redis (/token endpoint)") + + # Fall back to in-memory storage if Redis unavailable or code not found + if not stored_code_data and hasattr(oauth_callback, '_code_storage'): + stored_code_data = oauth_callback._code_storage.get(code) + if stored_code_data: + # Clean up in-memory storage + oauth_callback._code_storage.pop(code, None) + logger.info(f"Retrieved authorization code {code[:10]}... from in-memory storage (/token endpoint)") + + if not stored_code_data: + logger.error(f"No stored data found for authorization code: {code}") + return JSONResponse( + status_code=400, + content={"error": "invalid_grant", "error_description": "Authorization code not found or expired"} + ) + + # Note: Redis TTL handles expiration automatically, but check for manual expiration for in-memory fallback + import time + expires_at = stored_code_data.get("expires_at", 0) + if expires_at and time.time() > expires_at: + logger.error(f"Authorization code expired: {code}") + return JSONResponse( + status_code=400, + content={"error": "invalid_grant", "error_description": "Authorization code expired"} + ) + + # Get the real JWT token + real_jwt_token = stored_code_data.get("real_jwt_token") + + if real_jwt_token: + logger.info("Returning real Clerk JWT token from /token endpoint") + # Note: Code already deleted from Redis, clean up in-memory fallback if used + if hasattr(oauth_callback, '_code_storage'): + oauth_callback._code_storage.pop(code, None) + + return JSONResponse({ + "access_token": real_jwt_token, + "token_type": "Bearer", + "expires_in": 3600, + "scope": "read search" + }) + else: + logger.warning("No real JWT token found in /token endpoint, generating mock token") + # Fallback to mock token for testing + mock_token = f"mock_clerk_jwt_{code}" + return JSONResponse({ + "access_token": mock_token, + "token_type": "Bearer", + "expires_in": 3600, + "scope": "read search" + }) + + except Exception as e: + logger.exception(f"Token exchange failed: {e}") + return JSONResponse( + status_code=500, + content={"error": "server_error", "error_description": str(e)} + ) \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/mcp_server_main.py b/saidsurucu-yargi-mcp-f5fa007/mcp_server_main.py new file mode 100644 index 0000000..7c3e512 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/mcp_server_main.py @@ -0,0 +1,2883 @@ +# mcp_server_main.py +import asyncio +import atexit +import logging +import os +import httpx +import json +import time +from collections import defaultdict +from pydantic import HttpUrl, Field +from typing import Optional, Dict, List, Literal, Any, Union +import urllib.parse +import tiktoken +from fastmcp.server.middleware import Middleware, MiddlewareContext +from fastmcp.server.dependencies import get_access_token, AccessToken +from fastmcp import Context + +# Use standard exception for tool errors +class ToolError(Exception): + """Tool execution error""" + pass + +# --- Logging Configuration Start --- +LOG_DIRECTORY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "logs") +if not os.path.exists(LOG_DIRECTORY): + os.makedirs(LOG_DIRECTORY) +LOG_FILE_PATH = os.path.join(LOG_DIRECTORY, "mcp_server.log") + +root_logger = logging.getLogger() +root_logger.setLevel(logging.DEBUG) + +log_formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(threadName)s - %(message)s') + +file_handler = logging.FileHandler(LOG_FILE_PATH, mode='a', encoding='utf-8') +file_handler.setFormatter(log_formatter) +file_handler.setLevel(logging.DEBUG) +root_logger.addHandler(file_handler) + +console_handler = logging.StreamHandler() +console_handler.setFormatter(log_formatter) +console_handler.setLevel(logging.INFO) +root_logger.addHandler(console_handler) + +logger = logging.getLogger(__name__) +# --- Logging Configuration End --- + +# --- Token Counting Middleware --- +class TokenCountingMiddleware(Middleware): + """Middleware for counting input/output tokens using tiktoken.""" + + def __init__(self, model: str = "cl100k_base"): + """Initialize token counting middleware. + + Args: + model: Tiktoken model name (cl100k_base for GPT-4/Claude compatibility) + """ + self.encoder = tiktoken.get_encoding(model) + self.model = model + self.token_stats = defaultdict(lambda: {"input": 0, "output": 0, "calls": 0}) + self.logger = logging.getLogger("token_counter") + + # Create separate log file for token metrics + token_log_path = os.path.join(LOG_DIRECTORY, "token_metrics.log") + token_handler = logging.FileHandler(token_log_path, mode='a', encoding='utf-8') + token_formatter = logging.Formatter('%(asctime)s - %(message)s') + token_handler.setFormatter(token_formatter) + token_handler.setLevel(logging.INFO) + self.logger.addHandler(token_handler) + self.logger.setLevel(logging.INFO) + + def count_tokens(self, text: str) -> int: + """Count tokens in text using tiktoken.""" + if not text: + return 0 + try: + return len(self.encoder.encode(str(text))) + except Exception as e: + logger.warning(f"Token counting failed: {e}") + return 0 + + def extract_text_content(self, data: Any) -> str: + """Extract text content from various data types.""" + if isinstance(data, str): + return data + elif isinstance(data, dict): + # Extract text from common response fields + text_parts = [] + for key, value in data.items(): + if isinstance(value, str): + text_parts.append(value) + elif isinstance(value, list): + for item in value: + if isinstance(item, str): + text_parts.append(item) + elif isinstance(item, dict) and 'text' in item: + text_parts.append(str(item['text'])) + return ' '.join(text_parts) + elif isinstance(data, list): + text_parts = [] + for item in data: + text_parts.append(self.extract_text_content(item)) + return ' '.join(text_parts) + else: + return str(data) + + def log_token_usage(self, operation: str, input_tokens: int, output_tokens: int, + tool_name: str = None, duration_ms: float = None): + """Log token usage with structured format.""" + log_data = { + "operation": operation, + "tool_name": tool_name, + "input_tokens": input_tokens, + "output_tokens": output_tokens, + "total_tokens": input_tokens + output_tokens, + "duration_ms": duration_ms, + "timestamp": time.time() + } + + # Update statistics + key = tool_name if tool_name else operation + self.token_stats[key]["input"] += input_tokens + self.token_stats[key]["output"] += output_tokens + self.token_stats[key]["calls"] += 1 + + # Log as JSON for easy parsing + self.logger.info(json.dumps(log_data)) + + # Also log human-readable format to main logger + logger.info(f"Token Usage - {operation}" + + (f" ({tool_name})" if tool_name else "") + + f": {input_tokens} in + {output_tokens} out = {input_tokens + output_tokens} total") + + async def on_call_tool(self, context: MiddlewareContext, call_next): + """Count tokens for tool calls.""" + start_time = time.perf_counter() + + # Extract tool name and arguments + tool_name = getattr(context.message, 'name', 'unknown_tool') + tool_args = getattr(context.message, 'arguments', {}) + + # Count input tokens (tool arguments) + input_text = self.extract_text_content(tool_args) + input_tokens = self.count_tokens(input_text) + + try: + # Execute the tool + result = await call_next(context) + + # Count output tokens (tool result) + output_text = self.extract_text_content(result) + output_tokens = self.count_tokens(output_text) + + # Calculate duration + duration_ms = (time.perf_counter() - start_time) * 1000 + + # Log token usage + self.log_token_usage("tool_call", input_tokens, output_tokens, + tool_name, duration_ms) + + return result + + except Exception as e: + duration_ms = (time.perf_counter() - start_time) * 1000 + self.log_token_usage("tool_call_error", input_tokens, 0, + tool_name, duration_ms) + raise + + async def on_read_resource(self, context: MiddlewareContext, call_next): + """Count tokens for resource reads.""" + start_time = time.perf_counter() + + # Extract resource URI + resource_uri = getattr(context.message, 'uri', 'unknown_resource') + + try: + # Execute the resource read + result = await call_next(context) + + # Count output tokens (resource content) + output_text = self.extract_text_content(result) + output_tokens = self.count_tokens(output_text) + + # Calculate duration + duration_ms = (time.perf_counter() - start_time) * 1000 + + # Log token usage (no input tokens for resource reads) + self.log_token_usage("resource_read", 0, output_tokens, + resource_uri, duration_ms) + + return result + + except Exception as e: + duration_ms = (time.perf_counter() - start_time) * 1000 + self.log_token_usage("resource_read_error", 0, 0, + resource_uri, duration_ms) + raise + + async def on_get_prompt(self, context: MiddlewareContext, call_next): + """Count tokens for prompt retrievals.""" + start_time = time.perf_counter() + + # Extract prompt name + prompt_name = getattr(context.message, 'name', 'unknown_prompt') + + try: + # Execute the prompt retrieval + result = await call_next(context) + + # Count output tokens (prompt content) + output_text = self.extract_text_content(result) + output_tokens = self.count_tokens(output_text) + + # Calculate duration + duration_ms = (time.perf_counter() - start_time) * 1000 + + # Log token usage + self.log_token_usage("prompt_get", 0, output_tokens, + prompt_name, duration_ms) + + return result + + except Exception as e: + duration_ms = (time.perf_counter() - start_time) * 1000 + self.log_token_usage("prompt_get_error", 0, 0, + prompt_name, duration_ms) + raise + + def get_token_stats(self) -> Dict[str, Any]: + """Get current token usage statistics.""" + return dict(self.token_stats) + + def reset_token_stats(self): + """Reset token usage statistics.""" + self.token_stats.clear() + +# --- End Token Counting Middleware --- + +# Create FastMCP app directly without authentication wrapper +from fastmcp import FastMCP + +def create_app(auth=None): + """Create FastMCP app with standard capabilities and optional auth.""" + global app + if auth: + # Set auth on existing app instead of creating new one + app.auth = auth + app.name = "Yargı MCP Server" + logger.info("MCP server created with Bearer authentication enabled") + else: + # Update placeholder app name only + app.name = "Yargı MCP Server" + logger.info("MCP server created with standard capabilities (FastMCP handles tools.listChanged automatically)") + + # Add token counting middleware + token_counter = TokenCountingMiddleware() + app.add_middleware(token_counter) + logger.info("Token counting middleware added to MCP server") + + return app + +# --- Module Imports --- +from yargitay_mcp_module.client import YargitayOfficialApiClient +from yargitay_mcp_module.models import ( + YargitayDetailedSearchRequest, YargitayDocumentMarkdown, CompactYargitaySearchResult, + YargitayBirimEnum, CleanYargitayDecisionEntry +) +from bedesten_mcp_module.client import BedestenApiClient +from bedesten_mcp_module.models import ( + BedestenSearchRequest, BedestenSearchData, + BedestenDocumentMarkdown, BedestenCourtTypeEnum +) +from bedesten_mcp_module.enums import BirimAdiEnum +from danistay_mcp_module.client import DanistayApiClient +from danistay_mcp_module.models import ( + DanistayKeywordSearchRequest, DanistayDetailedSearchRequest, + DanistayDocumentMarkdown, CompactDanistaySearchResult +) +from emsal_mcp_module.client import EmsalApiClient +from emsal_mcp_module.models import ( + EmsalSearchRequest, EmsalDocumentMarkdown, CompactEmsalSearchResult +) +from uyusmazlik_mcp_module.client import UyusmazlikApiClient +from uyusmazlik_mcp_module.models import ( + UyusmazlikSearchRequest, UyusmazlikSearchResponse, UyusmazlikDocumentMarkdown, + UyusmazlikBolumEnum, UyusmazlikTuruEnum, UyusmazlikKararSonucuEnum +) +from anayasa_mcp_module.client import AnayasaMahkemesiApiClient +from anayasa_mcp_module.bireysel_client import AnayasaBireyselBasvuruApiClient +from anayasa_mcp_module.unified_client import AnayasaUnifiedClient +from anayasa_mcp_module.models import ( + AnayasaNormDenetimiSearchRequest, + AnayasaSearchResult, + AnayasaDocumentMarkdown, + AnayasaBireyselReportSearchRequest, + AnayasaBireyselReportSearchResult, + AnayasaBireyselBasvuruDocumentMarkdown, + AnayasaUnifiedSearchRequest, + AnayasaUnifiedSearchResult, + AnayasaUnifiedDocumentMarkdown, + # Removed enum imports - now using Literal strings in models +) +# KIK Module Imports +from kik_mcp_module.client import KikApiClient +from kik_mcp_module.models import ( + KikKararTipi, + KikSearchRequest, + KikSearchResult, + KikDocumentMarkdown +) + +from rekabet_mcp_module.client import RekabetKurumuApiClient +from rekabet_mcp_module.models import ( + RekabetKurumuSearchRequest, + RekabetSearchResult, + RekabetDocument, + RekabetKararTuruGuidEnum +) + +from sayistay_mcp_module.client import SayistayApiClient +from sayistay_mcp_module.models import ( + GenelKurulSearchRequest, GenelKurulSearchResponse, + TemyizKuruluSearchRequest, TemyizKuruluSearchResponse, + DaireSearchRequest, DaireSearchResponse, + SayistayDocumentMarkdown, + SayistayUnifiedSearchRequest, SayistayUnifiedSearchResult, + SayistayUnifiedDocumentMarkdown +) +from sayistay_mcp_module.enums import DaireEnum, KamuIdaresiTuruEnum, WebKararKonusuEnum +from sayistay_mcp_module.unified_client import SayistayUnifiedClient + +# KVKK Module Imports +from kvkk_mcp_module.client import KvkkApiClient +from kvkk_mcp_module.models import ( + KvkkSearchRequest, + KvkkSearchResult, + KvkkDocumentMarkdown +) + +# BDDK Module Imports +from bddk_mcp_module.client import BddkApiClient +from bddk_mcp_module.models import ( + BddkSearchRequest, + BddkSearchResult, + BddkDocumentMarkdown +) + + +# Create a placeholder app that will be properly initialized after tools are defined +from fastmcp import FastMCP + +# Placeholder app for decorators - will be replaced in create_app() after all tools are defined +app = FastMCP("Yargı MCP Server Placeholder") + +# --- Tool Documentation Resources --- +@app.resource("docs://tools/yargitay") +async def get_yargitay_tools_documentation() -> str: + """Get document content as Markdown.""" + return """ +# Yargıtay (Court of Cassation) Tools Documentation + +## Court Hierarchy and Position +Yargıtay is Turkey's highest civil and criminal court. It serves as the final appellate authority and establishes legal precedents for civil and criminal cases. + +**Dual API System:** +- **Primary API (search_yargitay_detailed)**: Official karararama.yargitay.gov.tr +- **Bedesten API (search_bedesten_unified)**: Unified access to bedesten.adalet.gov.tr (see docs://tools/bedesten_unified) + +## Chamber Filtering Options (52 Total) + +### Civil Chambers (Hukuk Daireleri) +- **Civil General Assembly** (Hukuk Genel Kurulu) +- **1st Civil Chamber** through **23rd Civil Chamber** (23 civil chambers) +- **Civil Chambers Presidents Board** (Hukuk Daireleri Başkanlar Kurulu) + +### Criminal Chambers (Ceza Daireleri) +- **Criminal General Assembly** (Ceza Genel Kurulu) +- **1st Criminal Chamber** through **23rd Criminal Chamber** (23 criminal chambers) +- **Criminal Chambers Presidents Board** (Ceza Daireleri Başkanlar Kurulu) + +### General Assemblies +- **Grand General Assembly** (Büyük Genel Kurulu) + +## Search Techniques + +### Primary API (search_yargitay_detailed) +``` +Simple search: "mülkiyet" +AND operator: "mülkiyet AND tapu" +OR operator: "mülkiyet OR tapu" +NOT operator: "mülkiyet NOT satış" +Wildcard: "mülk*" +Exact phrase: "\"mülkiyet hakkı\"" +``` + +### Bedesten API (search_bedesten_unified) +For detailed usage, see docs://tools/bedesten_unified +``` +Regular search: phrase="mülkiyet kararı", court_types=["YARGITAYKARARI"] +Exact phrase: phrase="\"mülkiyet kararı\"", court_types=["YARGITAYKARARI"] +Date filtering: kararTarihiStart="2024-01-01T00:00:00.000Z" +``` + +## Usage Scenarios +- **Precedent research**: Supreme court decisions on specific topics +- **Chamber-specific search**: Relevant chambers for specific legal areas +- **Historical analysis**: Decision trends in specific periods +- **Jurisprudence tracking**: Changes in legal opinions + +## Best Practices +1. **Use dual APIs**: Try both APIs for maximum coverage +2. **Chamber filtering**: Select chambers based on relevant legal area +3. **Exact phrases**: Use "\"term\"" for precise terms in Bedesten API +4. **Date range**: Focus on last 2-3 years for recent developments + +## Common Civil Chambers +- **1st Civil**: Property, land registry, liens +- **4th Civil**: Labor law, collective agreements +- **11th Civil**: Insurance, social security +- **15th Civil**: Compensation, tort +- **21st Civil**: Execution and bankruptcy + +## Common Criminal Chambers +- **1st Criminal**: General criminal offenses +- **8th Criminal**: Economic and commercial crimes +- **12th Criminal**: Official misconduct +""" + +@app.resource("docs://tools/danistay") +async def get_danistay_tools_documentation() -> str: + """Get document content as Markdown.""" + return """ +# Danıştay (Council of State) Tools Documentation + +## Court Hierarchy and Position +Danıştay is Turkey's highest administrative court. It makes final decisions on administrative acts and actions. + +**Triple API System:** +- **Keyword API (search_danistay_by_keyword)**: AND/OR/NOT logic +- **Detailed API (search_danistay_detailed)**: Comprehensive criteria +- **Bedesten API (search_bedesten_unified)**: Unified access (see docs://tools/bedesten_unified) + +## Chamber Filtering Options (27 Total) + +### Main Councils +- **Grand General Assembly** (Büyük Gen.Kur.) +- **Administrative Cases Council** (İdare Dava Daireleri Kurulu) +- **Tax Cases Council** (Vergi Dava Daireleri Kurulu) +- **Precedents Unification Council** (İçtihatları Birleştirme Kurulu) + +### Chambers (1-17) +- **1st Chamber** through **17th Chamber** (Administrative case chambers) + +### Military Courts +- **Military High Administrative Court** (Askeri Yüksek İdare Mahkemesi) +- **Military High Administrative Court 1st-3rd Chambers** + +## Search Techniques + +### Keyword API +``` +AND logic: andKelimeler=["imar", "plan"] +OR logic: orKelimeler=["iptal", "yürütmeyi durdurma"] +NOT logic: notKelimeler=["ceza"] +``` + +### Detailed API +``` +Chamber selection: daire="3. Daire" +Case year: esasYil="2024" +Decision date: kararTarihiBaslangic="01.01.2024" +Legislation: mevzuatId=123 +``` + +### Bedesten API +``` +Regular: phrase="idari işlem" +Exact: phrase="\"idari işlem\"" +Date: kararTarihiStart="2024-01-01T00:00:00.000Z" +``` + +## Usage Scenarios +- **Administrative law research**: Public administration decisions +- **Tax law**: Financial matters and tax disputes +- **Urban planning law**: City planning and building permits +- **Personnel law**: Civil servant rights + +## Common Chamber Specializations +- **1st Chamber**: Municipal, urban planning, environment +- **2nd Chamber**: Tax, customs, financial +- **3rd Chamber**: Personnel, personal rights +- **5th Chamber**: Administrative fines +- **8th Chamber**: Higher education, education +- **10th Chamber**: Health, social security + +## Best Practices +1. **Triple API**: Use all three APIs for maximum coverage +2. **Chamber selection**: Choose specialized chambers by subject area +3. **Mevzuat bağlantısı**: İlgili kanun/tüzükle filtreleme +4. **Kesin terim**: İdari hukuk terminolojisi için exact search +""" + +@app.resource("docs://tools/constitutional_court") +async def get_constitutional_court_tools_documentation() -> str: + """Get document content as Markdown.""" + return """ +# Anayasa Mahkemesi (Constitutional Court) Tools Documentation + +## Court Position +Constitutional Court is Turkey's highest judicial body. It has two main functions: + +### 1. Norm Control (Norm Control) +**Tool**: search_anayasa_norm_denetimi_decisions +- Reviews constitutional compliance of laws and regulations +- Abstract and concrete norm control + +### 2. Individual Application (Individual Application) +**Tool**: search_anayasa_bireysel_basvuru_report +- Citizens' fundamental rights violation applications +- Turkey's human rights protection mechanism + +## Norm Control Features + +### Comprehensive Filtering +- **Application type**: Annulment, Objection, Other +- **Applicant**: President, Parliament, Courts +- **Legislation type**: Law, Decree, Regulation, Rules of procedure +- **Result type**: Annulment, Rejection, Partial annulment + +### Advanced Search +- **Member names**: Full names of participating justices +- **Rapporteur**: Case rapporteur +- **Dissenting opinion**: Minority opinion, different view +- **Press release**: Important decisions + +## Bireysel Başvuru Özellikleri + +### Temel Haklar Kategorileri +- **Yaşam hakkı**: Ölüm olayları, güvenlik +- **Adil yargılanma**: Süre, tarafsızlık, duruşma hakkı +- **İfade özgürlüğü**: Basın, düşünce, akademik özgürlük +- **Din özgürlüğü**: İbadet, vicdan özgürlüğü +- **Mülkiyet hakkı**: Kamulaştırma, tapu +- **Özel hayat**: Gizlilik, aile hayatı + +### Başvuru Süreci +- **Yurtiçi yollar**: Önce mahkeme kararı gerekli +- **Süre sınırı**: 30 gün (60 gün istisnai) +- **Kabul edilebilirlik**: Ön inceleme kriterleri + +## Paginated Content (5,000 characters) +Her iki tool da sayfalanmış Markdown döndürür: +- **page_number**: Sayfa numarası (1'den başlar) +- **total_pages**: Toplam sayfa sayısı +- **current_page**: Mevcut sayfa + +## Usage Scenarios + +### Norm Denetimi +- **Kanun anayasaya uygunluk**: Yeni çıkan kanunların kontrolü +- **Mahkeme iptali**: Kanunun belirli maddeleri +- **Mevzuat uyum**: Anayasa değişikliği sonrası + +### Bireysel Başvuru +- **İnsan hakları araştırması**: AİHM öncesi iç hukuk +- **Temel hak ihlalleri**: Sistematik ihlal tespiti +- **Emsal karar**: Benzer davalar için içtihat + +## Parameter Details +### search_anayasa_norm_denetimi_decisions +- **keywords_all**: Keywords for AND logic (all must be present) +- **keywords_any**: Keywords for OR logic (any can be present) +- **keywords_exclude**: Keywords to exclude from results +- **period**: Constitutional period - "ALL", "1" (1961 Constitution), "2" (1982 Constitution) +- **case_number_esas**: Case registry number (e.g., '2023/123') +- **decision_number_karar**: Decision number (e.g., '2023/456') +- **first_review_date_start/end**: First review date range (DD/MM/YYYY) +- **decision_date_start/end**: Decision date range (DD/MM/YYYY) +- **application_type**: "ALL", "1" (İptal), "2" (İtiraz), "3" (Diğer) +- **applicant_general_name**: General applicant name +- **applicant_specific_name**: Specific applicant name +- **official_gazette_date_start/end**: Official Gazette date range +- **official_gazette_number_start/end**: Official Gazette number range +- **has_press_release**: "ALL", "0" (No), "1" (Yes) +- **has_dissenting_opinion**: "ALL", "0" (No), "1" (Yes) +- **has_different_reasoning**: "ALL", "0" (No), "1" (Yes) +- **attending_members_names**: List of attending members' exact names +- **rapporteur_name**: Rapporteur's exact name +- **norm_type**: Type of reviewed norm (law, decree, regulation, etc.) +- **norm_id_or_name**: Number or name of the norm +- **norm_article**: Article number of the norm +- **review_outcomes**: List of review outcomes +- **reason_for_final_outcome**: Main reason for decision outcome +- **basis_constitution_article_numbers**: Supporting Constitution article numbers +- **results_per_page**: Results per page (10, 20, 30, 40, 50) +- **page_to_fetch**: Page number to fetch +- **sort_by_criteria**: Sort criteria ('KararTarihi', 'YayinTarihi', 'Toplam') + +### search_anayasa_bireysel_basvuru_report +- **keywords**: Keywords for AND logic (all must be present) +- **page_to_fetch**: Page number for the report (default: 1) + +### Document Tools +- **document_url**: URL path or full URL of the decision +- **page_number**: Page number for paginated content (1-indexed, default: 1) + +## Best Practices +1. **Norm control önce**: Kanun iptal edilmiş mi kontrol +2. **Bireysel başvuru ikinci**: Kişisel hak ihlalleri için +3. **Tarih aralığı**: Anayasa değişiklikleri sonrası dönemler +4. **Anahtar kelime kombinasyonu**: Temel hak + konu alanı +5. **Sayfa yönetimi**: Uzun kararlarda sayfa sayfa okuyun +""" + +@app.resource("docs://tools/emsal") +async def get_emsal_tools_documentation() -> str: + """Get document content as Markdown.""" + return """ +# Emsal (UYAP Precedent System) Tools Documentation + +## System Position +Central precedent decision system providing access to all court decisions through the UYAP system. + +## Court Options +- **Yargıtay**: First and second instance courts +- **Danıştay**: Administrative court decisions +- **Other**: Regional courts of justice, civil courts + +## Advanced Filtering Features +- **Court type**: Civil, criminal, administrative +- **Case/Decision number**: File tracking system +- **Date range**: Flexible date selection +- **Content search**: Keyword search within decision text + +## Usage Scenarios +- **Kapsamlı emsal**: Tüm mahkeme seviyelerinden karar toplama +- **Güncel içtihat**: En son hukuki gelişmeler +- **Cross-reference**: Farklı mahkeme görüşlerini karşılaştırma + +## Best Practices +1. **Spesifik terimler**: Hukuki terminoloji kullanın +2. **Geniş arama**: Önce genel, sonra spesifik +3. **Tarih stratejisi**: Mevzuat değişiklikleri dikkate alın +4. **Cross-platform**: Aynı konuyu farklı mahkemelerde arayın +""" + +@app.resource("docs://tools/uyusmazlik") +async def get_uyusmazlik_tools_documentation() -> str: + """Get document content as Markdown.""" + return """ +# Uyuşmazlık Mahkemesi Tools Documentation + +## Court Position +Adli ve idari yargı arasındaki görev uyuşmazlıklarını çözen özel yetkili mahkeme. + +## Dispute Types +- **Görev uyuşmazlığı**: Hangi mahkeme bakacak konusunda anlaşmazlık +- **Hüküm uyuşmazlığı**: Çelişkili mahkeme kararları +- **Yetki uyuşmazlığı**: Yerel yetki sorunları + +## Form-Based Search Criteria +- **Karar türü**: Müspet, menfi, hüküm uyuşmazlığı +- **Taraf mahkemeler**: Adli-idari yargı organları +- **Konu alanı**: Hukuk dalı bazlı filtreleme +- **Tarih aralığı**: Karar tarihi seçimi + +## Usage Scenarios +- **Yargı türü belirleme**: Hangi mahkemenin yetkili olduğu +- **Çelişkili kararlar**: Farklı mahkeme kararları arasındaki uyuşmazlık +- **Yetki sorunları**: Mahkeme yetkisi tartışmaları + +## Best Practices +1. **Net kriterler**: Arama kriterlerini spesifik tutun +2. **Taraf bilgisi**: Uyuşmazlık taraflarını belirtin +3. **Konu odaklı**: İlgili hukuk dalını seçin +""" + +@app.resource("docs://tools/kik") +async def get_kik_tools_documentation() -> str: + """Get document content as Markdown.""" + return """ +# KİK (Kamu İhale Kurumu) Tools Documentation + +## Kurum Konumu +Kamu ihale uyuşmazlıklarının ilk ve son merci çözüm organı. Kamu İhale Kanunu kapsamındaki tüm ihaleler için yetkili. + +## Decision Types +- **Uyuşmazlık**: İhale süreç itirazları +- **Düzenleyici**: Mevzuat ve uygulama kararları +- **Mahkeme**: Mahkeme kararlarının uygulanması + +## Filtreleme Seçenekleri +- **Karar numarası**: 2024/UH.II-1766 formatında +- **Tarih aralığı**: Karar tarihi filtreleme +- **İhaleyi yapan idare**: Bakanlık, belediye, hastane, üniversite +- **Başvuru sahibi**: Şirket, firma adı +- **İhale konusu**: Mal, hizmet, yapım işi + +## Sayfalanmış İçerik Özelliği +5.000 karakterlik sayfalar halinde Markdown formatında sunulur. + +## Usage Scenarios +- **İhale hukuku**: Kamu alımları, süreç kuralları +- **Başvuru hazırlığı**: Benzer davalar, emsal kararlar +- **Mevzuat yorumu**: Kamu İhale Kanunu uygulaması +- **İtiraz stratejisi**: Başarılı itiraz örnekleri + +## İhale Süreç Aşamaları +1. **İhale öncesi**: İlan, şartname hazırlığı +2. **İhale aşaması**: Teklif verme, değerlendirme +3. **İhale sonrası**: Sonuç bildirimi, itirazlar +4. **Sözleşme**: İmza, uygulama + +## Parameter Details +### search_kik_decisions +- **karar_tipi**: Decision type - "rbUyusmazlik" (disputes), "rbDuzenleyici" (regulatory), "rbMahkeme" (court) +- **karar_no**: Decision number (e.g., '2024/UH.II-1766') +- **karar_tarihi_baslangic**: Decision start date (DD.MM.YYYY format) +- **karar_tarihi_bitis**: Decision end date (DD.MM.YYYY format) +- **basvuru_sahibi**: Applicant name/company +- **ihaleyi_yapan_idare**: Procuring entity (ministry, municipality, etc.) +- **basvuru_konusu_ihale**: Tender subject/description +- **karar_metni**: Text search with operators: +word (AND), -word (exclude) +- **yil**: Decision year +- **resmi_gazete_tarihi**: Official Gazette date (DD.MM.YYYY) +- **resmi_gazete_sayisi**: Official Gazette number +- **page**: Results page number + +### get_kik_document_markdown +- **karar_id**: Base64 encoded decision identifier from search results +- **page_number**: Page number for paginated content (1-indexed, default: 1) + +## Best Practices +1. **İhale türü**: Açık, belli istekliler arası, pazarlık +2. **Süreç aşaması**: Hangi aşamada sorun olduğu +3. **Hukuki dayanak**: İlgili KİK kanun maddesi +4. **Sayfa yönetimi**: Uzun kararları bölümler halinde okuyun +""" + +@app.resource("docs://tools/rekabet") +async def get_rekabet_tools_documentation() -> str: + """Get document content as Markdown.""" + return """ +# Rekabet Kurumu (Competition Authority) Tools Documentation + +## Kurum Konumu +Rekabet hukuku ihlallerini inceleyen ve ceza veren idari otorite. Rekabet Kanunu kapsamında yetkili. + +## Decision Types +- **Birleşme ve Devralma**: Şirket satın almaları, füzyonlar +- **Rekabet İhlali**: Anlaşma, hakim durum kötüye kullanımı +- **Menfi Tespit ve Muafiyet**: İhlal yok kararları, muafiyetler +- **Özelleştirme**: Kamu şirketleri satışı onayları + +## Filtreleme Özellikleri +- **PDF metin arama**: Tam metin içinde kelime arama +- **Karar türü**: Spesifik kategori seçimi +- **Tarih aralığı**: 1997'den günümüze karar arşivi +- **Sektör**: Telekomünikasyon, bankacılık, enerji, perakende + +## Rekabet Hukuku Temel Kavramları +- **Hakim durum**: Pazar gücü +- **Kartel**: Fiyat anlaşması +- **Dikey anlaşmalar**: Tedarikci-bayi ilişkileri +- **Konsantrasyon**: Birleşme işlemleri + +## Usage Scenarios +- **Antitrust araştırması**: Tekelleşme, kartel soruşturmaları +- **Birleşme incelemesi**: M&A transaction değerlendirmesi +- **Sektör analizi**: Belirli pazarlardaki rekabet durumu +- **Ceza hesaplama**: İhlal cezası örnekleri + +## Sektörel Uzmanlık Alanları +1. **Telekomünikasyon**: Operatör rekabeti +2. **Enerji**: Elektrik, doğalgaz piyasası +3. **Finans**: Bankacılık, sigorta +4. **Perakende**: Zincir mağazalar +5. **İnşaat**: Müteahhitlik sektörü + +## Parameter Details +### search_rekabet_kurumu_decisions +- **sayfaAdi**: Search in decision title (Başlık) +- **YayinlanmaTarihi**: Publication date (DD.MM.YYYY format) +- **PdfText**: Search text. For exact phrases use double quotes: "vertical agreement" +- **KararTuru**: Decision type - "Birleşme ve Devralma", "Rekabet İhlali", etc. +- **KararSayisi**: Decision number (Karar Sayısı) +- **KararTarihi**: Decision date (DD.MM.YYYY format) +- **page**: Page number for results list + +### get_rekabet_kurumu_document +- **karar_id**: GUID from search results +- **page_number**: Requested page for Markdown content (1-indexed, default: 1) + +## Best Practices +1. **Sektör odaklı**: İlgili sektörde arama yapın +2. **Karar türü seçimi**: İhtiyacınıza uygun kategori +3. **Güncel mevzuat**: Mevzuat değişiklikleri takibi +4. **Sayfa yönetimi**: Uzun analizleri bölümler halinde +""" + +@app.resource("docs://tools/bedesten_unified") +async def get_bedesten_unified_documentation() -> str: + """Get document content as Markdown.""" + return """ +# Bedesten API Mahkemeleri Tools Documentation + +## Bedesten API Sistemi +bedesten.adalet.gov.tr üzerinden Türk adalet sistemi hiyerarşisindeki mahkemelere erişim. + +## Mahkeme Hiyerarşisi Kapsamı + +### 1. Yerel Hukuk Mahkemeleri (Local Civil Courts) +**Tool**: search_yerel_hukuk_bedesten +- **Konum**: İlk derece mahkemeler +- **Yetki**: Hukuki uyuşmazlıklar (sözleşme, tazminat, mülkiyet) +- **Önem**: Toplumun günlük hukuki sorunları + +### 2. İstinaf Hukuk Mahkemeleri (Civil Courts of Appeals) +**Tool**: search_istinaf_hukuk_bedesten +- **Konum**: Orta derece (Yerel -> İstinaf -> Yargıtay) +- **Yetki**: Yerel mahkeme kararlarına itiraz +- **Önem**: Temyiz öncesi son kontrol + +### 3. Kanun Yararına Bozma (KYB) +**Tool**: search_kyb_bedesten +- **Konum**: Olağanüstü kanun yolu +- **Başvuru sahibi**: Cumhuriyet Başsavcılığı +- **Amaç**: Hukuka aykırı kararları düzeltme +- **Özellik**: Sanık aleyhine olsa bile hukuk yararına + +## Ortak Bedesten API Özellikleri + +### Tarih Filtreleme (ISO 8601) +``` +Başlangıç: kararTarihiStart="2024-01-01T00:00:00.000Z" +Bitiş: kararTarihiEnd="2024-12-31T23:59:59.999Z" +Tek gün: "2024-06-25T00:00:00.000Z" - "2024-06-25T23:59:59.999Z" +``` + +### Kesin Cümle Arama +``` +Normal: phrase="sözleşme ihlali" (kelimeler ayrı ayrı) +Kesin: phrase="\"sözleşme ihlali\"" (tam cümle) +``` + +### Sayfalama +- **pageSize**: 1-100 arası sonuç sayısı +- **pageNumber**: Sayfa numarası (1'den başlar) + +## Mahkeme Özellikleri + +### Yerel Hukuk Mahkemeleri +**Yaygın Dava Türleri**: +- Sözleşme ihlali davaları +- Tazminat talepleri +- Mülkiyet uyuşmazlıkları +- Aile hukuku (boşanma, nafaka) +- Ticari uyuşmazlıklar (küçük-orta ölçek) + +**Kullanım Senaryoları**: +- Günlük hukuki sorunlar +- Vatandaş hakları +- Ticaret hukuku temelleri +- İcra takipleri + +### İstinaf Hukuk Mahkemeleri +**İnceleme Kapsamı**: +- Yerel mahkeme kararlarının kontrolü +- Hukuki ve maddi hata arayışı +- Yeniden yargılama (sınırlı) + +**Kullanım Senaryoları**: +- Temyiz stratejisi gelişitirme +- İstinaf mahkemesi içtihatları +- Yerel-üst mahkeme uyumu analizi + +### Kanun Yararına Bozma (KYB) +**Başvuru Koşulları**: +- Kesinleşmiş mahkeme kararı +- Hukuka açık aykırılık +- Cumhuriyet Başsavcılığı başvurusu +- Sanık aleyhine sonuç doğurmama + +**Kullanım Senaryoları**: +- Sistematik hukuki hatalar +- İçtihat birliğini sağlama +- Hukuk güvenliği +- Nadir ve özel hukuki durumlar + +## Arama Stratejileri + +### Hiyerarşik Arama +``` +1. Yerel mahkeme -> Gündelik sorunlar +2. İstinaf -> Kompleks yorumlar +3. KYB -> İstisnai hukuki durumlar +``` + +### Kesin Terim Kullanımı +``` +Yerel: "\"sözleşme ihlali\"" +İstinaf: "\"temyiz incelemesi\"" +KYB: "\"kanun yararına bozma\"" +``` + +### Tarih Stratejisi +- **Son 2 yıl**: Güncel içtihat +- **5-10 yıl**: Yerleşik görüşler +- **Mevzuat değişikliği sonrası**: Yeni uygulamalar + +## Best Practices +1. **Hiyerarşi takibi**: Alt mahkemeden üst mahkemeye +2. **Kesin cümle**: Hukuki terimler için "\"terim\"" +3. **Tarih aralığı**: İlgili mevzuat dönemleri +4. **Cross-reference**: Aynı konuyu farklı seviyelerde +5. **Minimal sonuç**: KYB çok nadir, az sonuç beklenir + +## Document ID Formatı +Tüm Bedesten mahkemeleri documentId döndürür: +- **Format**: Alfanumerik string +- **Kullanım**: get_*_bedesten_document_markdown fonksiyonları +- **İçerik**: HTML/PDF -> Markdown conversion +""" + +@app.resource("docs://tools/sayistay") +async def get_sayistay_tools_documentation() -> str: + """Get document content as Markdown.""" + return """ +# Sayıştay (Court of Accounts) Tools Documentation + +## Sayıştay'ın Konumu +Türkiye'nin en üst mali denetim organı. Kamu kaynaklarının kullanımını denetler ve mali disiplini sağlar. + +## Üç Tür Karar Sistemi + +### 1. Genel Kurul Kararları (Interpretive Rulings) +**Tool**: search_sayistay_genel_kurul +- **İşlev**: Mali mevzuat yorumlama +- **Kapsam**: 2006-2024 yılları arası +- **Özellik**: Bağlayıcı yorumlar + +**Filtreleme Seçenekleri**: +- **Karar numarası**: Spesifik karar arama +- **Tarih aralığı**: Başlangıç-bitiş tarihleri +- **Karar tamamı**: Tam metin arama (400 karakter) + +### 2. Temyiz Kurulu Kararları (Appeals Board) +**Tool**: search_sayistay_temyiz_kurulu +- **İşlev**: Daire kararlarına itiraz incelemesi +- **8 Daire Filtreleme**: Uzmanlık alanlarına göre + +**Daire Uzmanlaşmaları**: +- **1. Daire**: Genel bütçeli idareler +- **2. Daire**: Mahalli idareler +- **3. Daire**: Sosyal güvenlik kurumları +- **4. Daire**: KİT ve bağlı ortaklıklar +- **5. Daire**: Düzenleyici kuruluşlar +- **6. Daire**: Vakıflar, dernekler +- **7. Daire**: Üniversiteler, eğitim +- **8. Daire**: Yatırım projeleri + +**Filtreleme Seçenekleri**: +- **İdare türü**: Bakanlık, belediye, üniversite, KİT +- **Temyiz karar**: Tam metin arama +- **Konu sınıflandırması**: Harcama, gelir, taşınır-taşınmaz + +### 3. Daire Kararları (Chamber Decisions) +**Tool**: search_sayistay_daire +- **İşlev**: İlk derece denetim bulguları +- **8 Daire**: Aynı uzmanlaşma alanları + +**Filtreleme Seçenekleri**: +- **Yargılama dairesi**: 1-8 arası daire seçimi +- **Hesap yılı**: Mali yıl bazlı +- **Web karar metni**: İçerik arama + +## Ortak Özellikler + +### Sayfalanmış Markdown +Tüm Sayıştay belgeleri sayfalanmış format: +- **5.000 karakter** per sayfa +- **page_number**: Sayfa numarası +- **total_pages**: Toplam sayfa + +### Tarih Aralığı Desteği +- **Genel Kurul**: 2006-2024 (18 yıl) +- **Temyiz/Daire**: Mevcut veriler üzerinde + +## Usage Scenarios + +### Mali Mevzuat Araştırması +``` +Genel Kurul -> Hukuki yorum +Temyiz -> Uygulama detayları +Daire -> Spesifik örnekler +``` + +### Kamu Mali Yönetimi +- **Bütçe uygulama**: Harcama usulleri +- **İhale süreçleri**: Kamu alımları denetimi +- **Personel giderleri**: Özlük hakları mali boyutu +- **Yatırım projeleri**: Büyük ölçekli projeler + +### Kurumsal Denetim +- **KİT yönetimi**: Kamu iktisadi teşebbüsleri +- **Belediye maliyesi**: Yerel yönetim harcamaları +- **Üniversite bütçesi**: Yükseköğretim mali yönetimi +- **Sosyal güvenlik**: SGK, Bağ-Kur mali işlemleri + +## Arama Stratejileri + +### Hiyerarşik Yaklaşım +1. **Genel Kurul**: Konunun hukuki çerçevesi +2. **Temyiz**: Tartışmalı uygulamalar +3. **Daire**: Günlük uygulama örnekleri + +### Daire Bazlı Strateji +``` +Mali konu -> İlgili daire seçimi -> Derinlemesine arama +Örnek: KİT mali sorunları -> 4. Daire +``` + +### Tarih Odaklı Strateji +- **Son 2 yıl**: Güncel uygulamalar +- **5 yıl**: Yerleşik görüşler +- **2006-2024**: Tarihsel gelişim + +## Best Practices +1. **Daire uzmanlaşması**: İlgili kuruma uygun daire +2. **Hiyerarşik sıralama**: Genel Kurul -> Temyiz -> Daire +3. **Mali dönem**: Bütçe yılları bazında arama +4. **Teknik terimler**: Mali mevzuat terminolojisi +5. **Cross-reference**: Farklı seviyelerden görüş karşılaştırma + +## İdare Türü Kodları +- **1**: Genel bütçeli +- **2**: Özel bütçeli +- **3**: Düzenleyici kuruluşlar +- **4**: Mahalli idareler +- **5**: Sosyal güvenlik +- **6**: KİT +- **7**: Vakıf/dernek +- **8**: Diğer kamu kuruluşları +""" + +@app.resource("docs://tools/kvkk") +async def get_kvkk_tools_documentation() -> str: + """Get document content as Markdown.""" + return """ +# KVKK (Personal Data Protection Authority) Tools + +## Overview +KVKK (Kişisel Verilerin Korunması Kurulu) - Turkey's GDPR equivalent authority. + +## Search Features +- **Brave Search API**: Searches kvkk.gov.tr with Turkish terms +- **Site-targeted**: Auto `site:kvkk.gov.tr "karar özeti"` +- **Pagination**: page and pageSize parameters +- **5,000-char pages**: Paginated Markdown documents + +## Common Search Terms +**Violations**: "veri ihlali", "açık rıza", "idari para cezası" +**Compliance**: "GDPR uyum", "veri koruma", "güvenlik tedbirleri" +**Sectors**: "e-ticaret", "bankacılık", "sağlık", "mobil uygulama" + +## Key Decision Types +- **Fines**: Data breaches, consent violations +- **Compliance**: GDPR alignment, corporate policies +- **Breach notifications**: 24-hour rule violations + +## Usage Tips +1. Use Turkish legal terms for best results +2. Combine sector + violation type searches +3. Use page_number for long decisions +4. Focus on recent 2-3 years for current practices +""" + + +# --- API Client Instances --- +yargitay_client_instance = YargitayOfficialApiClient() +danistay_client_instance = DanistayApiClient() +emsal_client_instance = EmsalApiClient() +uyusmazlik_client_instance = UyusmazlikApiClient() +anayasa_norm_client_instance = AnayasaMahkemesiApiClient() +anayasa_bireysel_client_instance = AnayasaBireyselBasvuruApiClient() +anayasa_unified_client_instance = AnayasaUnifiedClient() +kik_client_instance = KikApiClient() +rekabet_client_instance = RekabetKurumuApiClient() +bedesten_client_instance = BedestenApiClient() +sayistay_client_instance = SayistayApiClient() +sayistay_unified_client_instance = SayistayUnifiedClient() +kvkk_client_instance = KvkkApiClient() +bddk_client_instance = BddkApiClient() + + +KARAR_TURU_ADI_TO_GUID_ENUM_MAP = { + "": RekabetKararTuruGuidEnum.TUMU, # Keep for backward compatibility + "ALL": RekabetKararTuruGuidEnum.TUMU, # Map "ALL" to TUMU + "Birleşme ve Devralma": RekabetKararTuruGuidEnum.BIRLESME_DEVRALMA, + "Diğer": RekabetKararTuruGuidEnum.DIGER, + "Menfi Tespit ve Muafiyet": RekabetKararTuruGuidEnum.MENFI_TESPIT_MUAFIYET, + "Özelleştirme": RekabetKararTuruGuidEnum.OZELLESTIRME, + "Rekabet İhlali": RekabetKararTuruGuidEnum.REKABET_IHLALI, +} + +# --- MCP Tools for Yargitay --- +""" +@app.tool( + description="Search Yargıtay decisions with 52 chamber filtering and advanced operators", + annotations={ + "readOnlyHint": True, + "openWorldHint": True, + "idempotentHint": True + } +) +async def search_yargitay_detailed( + arananKelime: str = Field("", description="Turkish search keyword. Supports +required -excluded \"exact phrase\" operators"), + birimYrgKurulDaire: str = Field("ALL", description="Chamber selection (52 options: Civil/Criminal chambers, General Assemblies)"), + esasYil: str = Field("", description="Case year for 'Esas No'."), + esasIlkSiraNo: str = Field("", description="Starting sequence number for 'Esas No'."), + esasSonSiraNo: str = Field("", description="Ending sequence number for 'Esas No'."), + kararYil: str = Field("", description="Decision year for 'Karar No'."), + kararIlkSiraNo: str = Field("", description="Starting sequence number for 'Karar No'."), + kararSonSiraNo: str = Field("", description="Ending sequence number for 'Karar No'."), + baslangicTarihi: str = Field("", description="Start date for decision search (DD.MM.YYYY)."), + bitisTarihi: str = Field("", description="End date for decision search (DD.MM.YYYY)."), + # pageSize: int = Field(10, ge=1, le=10, description="Number of results per page."), + pageNumber: int = Field(1, ge=1, description="Page number to retrieve.") +) -> CompactYargitaySearchResult: + # Search Yargıtay decisions using primary API with 52 chamber filtering and advanced operators. + + # Convert "ALL" to empty string for API compatibility + if birimYrgKurulDaire == "ALL": + birimYrgKurulDaire = "" + + pageSize = 10 # Default value + + search_query = YargitayDetailedSearchRequest( + arananKelime=arananKelime, + birimYrgKurulDaire=birimYrgKurulDaire, + esasYil=esasYil, + esasIlkSiraNo=esasIlkSiraNo, + esasSonSiraNo=esasSonSiraNo, + kararYil=kararYil, + kararIlkSiraNo=kararIlkSiraNo, + kararSonSiraNo=kararSonSiraNo, + baslangicTarihi=baslangicTarihi, + bitisTarihi=bitisTarihi, + siralama="3", + siralamaDirection="desc", + pageSize=pageSize, + pageNumber=pageNumber + ) + + logger.info(f"Tool 'search_yargitay_detailed' called: {search_query.model_dump_json(exclude_none=True, indent=2)}") + try: + api_response = await yargitay_client_instance.search_detailed_decisions(search_query) + if api_response and api_response.data and api_response.data.data: + # Convert to clean decision entries without arananKelime field + clean_decisions = [ + CleanYargitayDecisionEntry( + id=decision.id, + daire=decision.daire, + esasNo=decision.esasNo, + kararNo=decision.kararNo, + kararTarihi=decision.kararTarihi, + document_url=decision.document_url + ) + for decision in api_response.data.data + ] + return CompactYargitaySearchResult( + decisions=clean_decisions, + total_records=api_response.data.recordsTotal if api_response.data else 0, + requested_page=search_query.pageNumber, + page_size=search_query.pageSize) + logger.warning("API response for Yargitay search did not contain expected data structure.") + return CompactYargitaySearchResult(decisions=[], total_records=0, requested_page=search_query.pageNumber, page_size=search_query.pageSize) + except Exception as e: + logger.exception(f"Error in tool 'search_yargitay_detailed'.") + raise + +@app.tool( + description="Get Yargıtay decision text in Markdown format", + annotations={ + "readOnlyHint": True, + "idempotentHint": True + } +) +async def get_yargitay_document_markdown(id: str) -> YargitayDocumentMarkdown: + # Get Yargıtay decision text as Markdown. Use ID from search results. + logger.info(f"Tool 'get_yargitay_document_markdown' called for ID: {id}") + if not id or not id.strip(): raise ValueError("Document ID must be a non-empty string.") + try: + return await yargitay_client_instance.get_decision_document_as_markdown(id) + except Exception as e: + logger.exception(f"Error in tool 'get_yargitay_document_markdown'.") + raise +""" + +# --- MCP Tools for Danistay --- +""" +@app.tool( + description="Search Danıştay decisions with keyword logic (AND/OR/NOT operators)", + annotations={ + "readOnlyHint": True, + "openWorldHint": True, + "idempotentHint": True + } +) +async def search_danistay_by_keyword( + andKelimeler: List[str] = Field(default_factory=list, description="Keywords for AND logic, e.g., ['word1', 'word2']"), + orKelimeler: List[str] = Field(default_factory=list, description="Keywords for OR logic."), + notAndKelimeler: List[str] = Field(default_factory=list, description="Keywords for NOT AND logic."), + notOrKelimeler: List[str] = Field(default_factory=list, description="Keywords for NOT OR logic."), + pageNumber: int = Field(1, ge=1, description="Page number."), + # pageSize: int = Field(10, ge=1, le=10, description="Results per page.") +) -> CompactDanistaySearchResult: + # Search Danıştay decisions with keyword logic. + + pageSize = 10 # Default value + + search_query = DanistayKeywordSearchRequest( + andKelimeler=andKelimeler, + orKelimeler=orKelimeler, + notAndKelimeler=notAndKelimeler, + notOrKelimeler=notOrKelimeler, + pageNumber=pageNumber, + pageSize=pageSize + ) + + logger.info(f"Tool 'search_danistay_by_keyword' called.") + try: + api_response = await danistay_client_instance.search_keyword_decisions(search_query) + if api_response.data: + return CompactDanistaySearchResult( + decisions=api_response.data.data, + total_records=api_response.data.recordsTotal, + requested_page=search_query.pageNumber, + page_size=search_query.pageSize) + logger.warning("API response for Danistay keyword search did not contain expected data structure.") + return CompactDanistaySearchResult(decisions=[], total_records=0, requested_page=search_query.pageNumber, page_size=search_query.pageSize) + except Exception as e: + logger.exception(f"Error in tool 'search_danistay_by_keyword'.") + raise + +@app.tool( + description="Search Danıştay decisions with detailed criteria (chamber selection, case numbers)", + annotations={ + "readOnlyHint": True, + "openWorldHint": True, + "idempotentHint": True + } +) +async def search_danistay_detailed( + daire: str = Field("", description="Chamber/Department name (e.g., '1. Daire')."), + esasYil: str = Field("", description="Case year for 'Esas No'."), + esasIlkSiraNo: str = Field("", description="Starting sequence for 'Esas No'."), + esasSonSiraNo: str = Field("", description="Ending sequence for 'Esas No'."), + kararYil: str = Field("", description="Decision year for 'Karar No'."), + kararIlkSiraNo: str = Field("", description="Starting sequence for 'Karar No'."), + kararSonSiraNo: str = Field("", description="Ending sequence for 'Karar No'."), + baslangicTarihi: str = Field("", description="Start date for decision (DD.MM.YYYY)."), + bitisTarihi: str = Field("", description="End date for decision (DD.MM.YYYY)."), + mevzuatNumarasi: str = Field("", description="Legislation number."), + mevzuatAdi: str = Field("", description="Legislation name."), + madde: str = Field("", description="Article number."), + pageNumber: int = Field(1, ge=1, description="Page number."), + # pageSize: int = Field(10, ge=1, le=10, description="Results per page.") +) -> CompactDanistaySearchResult: + # Search Danıştay decisions with detailed filtering. + + pageSize = 10 # Default value + + search_query = DanistayDetailedSearchRequest( + daire=daire, + esasYil=esasYil, + esasIlkSiraNo=esasIlkSiraNo, + esasSonSiraNo=esasSonSiraNo, + kararYil=kararYil, + kararIlkSiraNo=kararIlkSiraNo, + kararSonSiraNo=kararSonSiraNo, + baslangicTarihi=baslangicTarihi, + bitisTarihi=bitisTarihi, + mevzuatNumarasi=mevzuatNumarasi, + mevzuatAdi=mevzuatAdi, + madde=madde, + siralama="3", + siralamaDirection="desc", + pageNumber=pageNumber, + pageSize=pageSize + ) + + logger.info(f"Tool 'search_danistay_detailed' called.") + try: + api_response = await danistay_client_instance.search_detailed_decisions(search_query) + if api_response.data: + return CompactDanistaySearchResult( + decisions=api_response.data.data, + total_records=api_response.data.recordsTotal, + requested_page=search_query.pageNumber, + page_size=search_query.pageSize) + logger.warning("API response for Danistay detailed search did not contain expected data structure.") + return CompactDanistaySearchResult(decisions=[], total_records=0, requested_page=search_query.pageNumber, page_size=search_query.pageSize) + except Exception as e: + logger.exception(f"Error in tool 'search_danistay_detailed'.") + raise + +@app.tool( + description="Get Danıştay decision text in Markdown format", + annotations={ + "readOnlyHint": True, + "idempotentHint": True + } +) +async def get_danistay_document_markdown(id: str) -> DanistayDocumentMarkdown: + # Get Danıştay decision text as Markdown. Use ID from search results. + logger.info(f"Tool 'get_danistay_document_markdown' called for ID: {id}") + if not id or not id.strip(): raise ValueError("Document ID must be a non-empty string for Danıştay.") + try: + return await danistay_client_instance.get_decision_document_as_markdown(id) + except Exception as e: + logger.exception(f"Error in tool 'get_danistay_document_markdown'.") + raise +""" + +# --- MCP Tools for Emsal --- +@app.tool( + description="Search Emsal precedent decisions with detailed criteria", + annotations={ + "readOnlyHint": True, + "openWorldHint": True, + "idempotentHint": True + } +) +async def search_emsal_detailed_decisions( + keyword: str = Field("", description="Keyword to search."), + selected_bam_civil_court: str = Field("", description="Selected BAM Civil Court."), + selected_civil_court: str = Field("", description="Selected Civil Court."), + selected_regional_civil_chambers: List[str] = Field(default_factory=list, description="Selected Regional Civil Chambers."), + case_year_esas: str = Field("", description="Case year for 'Esas No'."), + case_start_seq_esas: str = Field("", description="Starting sequence for 'Esas No'."), + case_end_seq_esas: str = Field("", description="Ending sequence for 'Esas No'."), + decision_year_karar: str = Field("", description="Decision year for 'Karar No'."), + decision_start_seq_karar: str = Field("", description="Starting sequence for 'Karar No'."), + decision_end_seq_karar: str = Field("", description="Ending sequence for 'Karar No'."), + start_date: str = Field("", description="Start date for decision (DD.MM.YYYY)."), + end_date: str = Field("", description="End date for decision (DD.MM.YYYY)."), + sort_criteria: str = Field("1", description="Sorting criteria (e.g., 1: Esas No)."), + sort_direction: str = Field("desc", description="Sorting direction ('asc' or 'desc')."), + page_number: int = Field(1, ge=1, description="Page number (accepts int)."), + # page_size: int = Field(10, ge=1, le=10, description="Results per page.") +) -> CompactEmsalSearchResult: + """Search Emsal precedent decisions with detailed criteria.""" + + page_size = 10 # Default value + + search_query = EmsalSearchRequest( + keyword=keyword, + selected_bam_civil_court=selected_bam_civil_court, + selected_civil_court=selected_civil_court, + selected_regional_civil_chambers=selected_regional_civil_chambers, + case_year_esas=case_year_esas, + case_start_seq_esas=case_start_seq_esas, + case_end_seq_esas=case_end_seq_esas, + decision_year_karar=decision_year_karar, + decision_start_seq_karar=decision_start_seq_karar, + decision_end_seq_karar=decision_end_seq_karar, + start_date=start_date, + end_date=end_date, + sort_criteria=sort_criteria, + sort_direction=sort_direction, + page_number=page_number, + page_size=page_size + ) + + logger.info(f"Tool 'search_emsal_detailed_decisions' called.") + try: + api_response = await emsal_client_instance.search_detailed_decisions(search_query) + if api_response.data: + return CompactEmsalSearchResult( + decisions=api_response.data.data, + total_records=api_response.data.recordsTotal if api_response.data.recordsTotal is not None else 0, + requested_page=search_query.page_number, + page_size=search_query.page_size + ) + logger.warning("API response for Emsal search did not contain expected data structure.") + return CompactEmsalSearchResult(decisions=[], total_records=0, requested_page=search_query.page_number, page_size=search_query.page_size) + except Exception as e: + logger.exception(f"Error in tool 'search_emsal_detailed_decisions'.") + raise + +@app.tool( + description="Get Emsal precedent decision text in Markdown format", + annotations={ + "readOnlyHint": True, + "idempotentHint": True + } +) +async def get_emsal_document_markdown(id: str) -> EmsalDocumentMarkdown: + """Get document as Markdown.""" + logger.info(f"Tool 'get_emsal_document_markdown' called for ID: {id}") + if not id or not id.strip(): raise ValueError("Document ID required for Emsal.") + try: + return await emsal_client_instance.get_decision_document_as_markdown(id) + except Exception as e: + logger.exception(f"Error in tool 'get_emsal_document_markdown'.") + raise + +# --- MCP Tools for Uyusmazlik --- +@app.tool( + description="Search Uyuşmazlık Mahkemesi decisions for jurisdictional disputes", + annotations={ + "readOnlyHint": True, + "openWorldHint": True, + "idempotentHint": True + } +) +async def search_uyusmazlik_decisions( + icerik: str = Field("", description="Keyword or content for main text search."), + bolum: Literal["ALL", "Ceza Bölümü", "Genel Kurul Kararları", "Hukuk Bölümü"] = Field("ALL", description="Select the department (Bölüm). Use 'ALL' for all departments."), + uyusmazlik_turu: Literal["ALL", "Görev Uyuşmazlığı", "Hüküm Uyuşmazlığı"] = Field("ALL", description="Select the type of dispute. Use 'ALL' for all types."), + karar_sonuclari: List[Literal["Hüküm Uyuşmazlığı Olmadığına Dair", "Hüküm Uyuşmazlığı Olduğuna Dair"]] = Field(default_factory=list, description="List of desired 'Karar Sonucu' types."), + esas_yil: str = Field("", description="Case year ('Esas Yılı')."), + esas_sayisi: str = Field("", description="Case number ('Esas Sayısı')."), + karar_yil: str = Field("", description="Decision year ('Karar Yılı')."), + karar_sayisi: str = Field("", description="Decision number ('Karar Sayısı')."), + kanun_no: str = Field("", description="Relevant Law Number."), + karar_date_begin: str = Field("", description="Decision start date (DD.MM.YYYY)."), + karar_date_end: str = Field("", description="Decision end date (DD.MM.YYYY)."), + resmi_gazete_sayi: str = Field("", description="Official Gazette number."), + resmi_gazete_date: str = Field("", description="Official Gazette date (DD.MM.YYYY)."), + tumce: str = Field("", description="Exact phrase search."), + wild_card: str = Field("", description="Search for phrase and its inflections."), + hepsi: str = Field("", description="Search for texts containing all specified words."), + herhangi_birisi: str = Field("", description="Search for texts containing any of the specified words."), + not_hepsi: str = Field("", description="Exclude texts containing these specified words.") +) -> UyusmazlikSearchResponse: + """Search Court of Jurisdictional Disputes decisions.""" + + # Convert string literals to enums + # Map "ALL" to TUMU for backward compatibility + if bolum == "ALL": + bolum_enum = UyusmazlikBolumEnum.TUMU + else: + bolum_enum = UyusmazlikBolumEnum(bolum) if bolum else UyusmazlikBolumEnum.TUMU + + if uyusmazlik_turu == "ALL": + uyusmazlik_turu_enum = UyusmazlikTuruEnum.TUMU + else: + uyusmazlik_turu_enum = UyusmazlikTuruEnum(uyusmazlik_turu) if uyusmazlik_turu else UyusmazlikTuruEnum.TUMU + karar_sonuclari_enums = [UyusmazlikKararSonucuEnum(ks) for ks in karar_sonuclari] + + search_params = UyusmazlikSearchRequest( + icerik=icerik, + bolum=bolum_enum, + uyusmazlik_turu=uyusmazlik_turu_enum, + karar_sonuclari=karar_sonuclari_enums, + esas_yil=esas_yil, + esas_sayisi=esas_sayisi, + karar_yil=karar_yil, + karar_sayisi=karar_sayisi, + kanun_no=kanun_no, + karar_date_begin=karar_date_begin, + karar_date_end=karar_date_end, + resmi_gazete_sayi=resmi_gazete_sayi, + resmi_gazete_date=resmi_gazete_date, + tumce=tumce, + wild_card=wild_card, + hepsi=hepsi, + herhangi_birisi=herhangi_birisi, + not_hepsi=not_hepsi + ) + + logger.info(f"Tool 'search_uyusmazlik_decisions' called.") + try: + return await uyusmazlik_client_instance.search_decisions(search_params) + except Exception as e: + logger.exception(f"Error in tool 'search_uyusmazlik_decisions'.") + raise + +@app.tool( + description="Get Uyuşmazlık Mahkemesi decision text from URL in Markdown format", + annotations={ + "readOnlyHint": True, + "idempotentHint": True + } +) +async def get_uyusmazlik_document_markdown_from_url( + document_url: str = Field(..., description="Full URL to the Uyuşmazlık Mahkemesi decision document from search results") +) -> UyusmazlikDocumentMarkdown: + """Get Uyuşmazlık Mahkemesi decision as Markdown.""" + logger.info(f"Tool 'get_uyusmazlik_document_markdown_from_url' called for URL: {str(document_url)}") + if not document_url: + raise ValueError("Document URL (document_url) is required for Uyuşmazlık document retrieval.") + try: + return await uyusmazlik_client_instance.get_decision_document_as_markdown(str(document_url)) + except Exception as e: + logger.exception(f"Error in tool 'get_uyusmazlik_document_markdown_from_url'.") + raise + +# --- DEACTIVATED: MCP Tools for Anayasa Mahkemesi (Individual Tools) --- +# Use search_anayasa_unified and get_anayasa_document_unified instead + +""" +@app.tool( + description="Search Constitutional Court norm control decisions with comprehensive filtering", + annotations={ + "readOnlyHint": True, + "openWorldHint": True, + "idempotentHint": True + } +) +# DEACTIVATED TOOL - Use search_anayasa_unified instead +# @app.tool( +# description="DEACTIVATED - Use search_anayasa_unified instead", +# annotations={"readOnlyHint": True, "openWorldHint": False, "idempotentHint": True} +# ) +# async def search_anayasa_norm_denetimi_decisions(...) -> AnayasaSearchResult: +# raise ValueError("This tool is deactivated. Use search_anayasa_unified instead.") + +# DEACTIVATED TOOL - Use get_anayasa_document_unified instead +# @app.tool(...) +# async def get_anayasa_norm_denetimi_document_markdown(...) -> AnayasaDocumentMarkdown: +# raise ValueError("This tool is deactivated. Use get_anayasa_document_unified instead.") + +# DEACTIVATED TOOL - Use search_anayasa_unified instead +# @app.tool(...) +# async def search_anayasa_bireysel_basvuru_report(...) -> AnayasaBireyselReportSearchResult: +# raise ValueError("This tool is deactivated. Use search_anayasa_unified instead.") + +# DEACTIVATED TOOL - Use get_anayasa_document_unified instead +# @app.tool(...) +# async def get_anayasa_bireysel_basvuru_document_markdown(...) -> AnayasaBireyselBasvuruDocumentMarkdown: +# raise ValueError("This tool is deactivated. Use get_anayasa_document_unified instead.") +""" + +# --- Unified MCP Tools for Anayasa Mahkemesi --- +@app.tool( + description="Unified search for Constitutional Court decisions: both norm control (normkararlarbilgibankasi) and individual applications (kararlarbilgibankasi) in one tool", + annotations={ + "readOnlyHint": True, + "openWorldHint": True, + "idempotentHint": True + } +) +async def search_anayasa_unified( + decision_type: Literal["norm_denetimi", "bireysel_basvuru"] = Field(..., description="Decision type: norm_denetimi (norm control) or bireysel_basvuru (individual applications)"), + keywords: List[str] = Field(default_factory=list, description="Keywords to search for (common parameter)"), + page_to_fetch: int = Field(1, ge=1, le=100, description="Page number to fetch (1-100)"), + # results_per_page: int = Field(10, ge=1, le=100, description="Results per page (1-100)"), + + # Norm Denetimi specific parameters (ignored for bireysel_basvuru) + keywords_all: List[str] = Field(default_factory=list, description="All keywords must be present (norm_denetimi only)"), + keywords_any: List[str] = Field(default_factory=list, description="Any of these keywords (norm_denetimi only)"), + decision_type_norm: Literal["ALL", "1", "2", "3"] = Field("ALL", description="Decision type for norm denetimi"), + application_date_start: str = Field("", description="Application start date (norm_denetimi only)"), + application_date_end: str = Field("", description="Application end date (norm_denetimi only)"), + + # Bireysel Başvuru specific parameters (ignored for norm_denetimi) + decision_start_date: str = Field("", description="Decision start date (bireysel_basvuru only)"), + decision_end_date: str = Field("", description="Decision end date (bireysel_basvuru only)"), + norm_type: Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "0"] = Field("ALL", description="Norm type (bireysel_basvuru only)"), + subject_category: str = Field("", description="Subject category (bireysel_basvuru only)") +) -> str: + logger.info(f"Tool 'search_anayasa_unified' called for decision_type: {decision_type}") + + results_per_page = 10 # Default value + + try: + request = AnayasaUnifiedSearchRequest( + decision_type=decision_type, + keywords=keywords, + page_to_fetch=page_to_fetch, + results_per_page=results_per_page, + keywords_all=keywords_all, + keywords_any=keywords_any, + decision_type_norm=decision_type_norm, + application_date_start=application_date_start, + application_date_end=application_date_end, + decision_start_date=decision_start_date, + decision_end_date=decision_end_date, + norm_type=norm_type, + subject_category=subject_category + ) + + result = await anayasa_unified_client_instance.search_unified(request) + return json.dumps(result.model_dump(), ensure_ascii=False, indent=2) + + except Exception as e: + logger.exception(f"Error in tool 'search_anayasa_unified'.") + raise + +@app.tool( + description="Unified document retrieval for Constitutional Court decisions: auto-detects norm control vs individual applications based on URL", + annotations={ + "readOnlyHint": True, + "openWorldHint": False, + "idempotentHint": True + } +) +async def get_anayasa_document_unified( + document_url: str = Field(..., description="Document URL from search results"), + page_number: int = Field(1, ge=1, description="Page number for paginated content (1-indexed)") +) -> str: + logger.info(f"Tool 'get_anayasa_document_unified' called for URL: {document_url}, Page: {page_number}") + + try: + result = await anayasa_unified_client_instance.get_document_unified(document_url, page_number) + return json.dumps(result.model_dump(mode='json'), ensure_ascii=False, indent=2) + + except Exception as e: + logger.exception(f"Error in tool 'get_anayasa_document_unified'.") + raise + +# --- MCP Tools for KIK (Kamu İhale Kurulu) --- +@app.tool( + description="Search Public Procurement Authority (KİK) decisions for procurement law disputes", + annotations={ + "readOnlyHint": True, + "openWorldHint": True, + "idempotentHint": True + } +) +async def search_kik_decisions( + karar_tipi: Literal["rbUyusmazlik", "rbDuzenleyici", "rbMahkeme"] = Field("rbUyusmazlik", description="Type of KIK Decision."), + karar_no: str = Field("", description="Decision Number (e.g., '2024/UH.II-1766')."), + karar_tarihi_baslangic: str = Field("", description="Decision Date Start (DD.MM.YYYY)."), + karar_tarihi_bitis: str = Field("", description="Decision Date End (DD.MM.YYYY)."), + basvuru_sahibi: str = Field("", description="Applicant."), + ihaleyi_yapan_idare: str = Field("", description="Procuring Entity."), + basvuru_konusu_ihale: str = Field("", description="Tender subject of the application."), + karar_metni: str = Field("", description="Decision text search. Supports: +word, -word, \"exact phrase\", OR/AND"), + yil: str = Field("", description="Year of the decision."), + resmi_gazete_tarihi: str = Field("", description="Official Gazette Date (DD.MM.YYYY)."), + resmi_gazete_sayisi: str = Field("", description="Official Gazette Number."), + page: int = Field(1, ge=1, description="Results page number.") +) -> KikSearchResult: + """Search Public Procurement Authority (KIK) decisions.""" + + # Convert string literal to enum + karar_tipi_enum = KikKararTipi(karar_tipi) + + search_query = KikSearchRequest( + karar_tipi=karar_tipi_enum, + karar_no=karar_no, + karar_tarihi_baslangic=karar_tarihi_baslangic, + karar_tarihi_bitis=karar_tarihi_bitis, + basvuru_sahibi=basvuru_sahibi, + ihaleyi_yapan_idare=ihaleyi_yapan_idare, + basvuru_konusu_ihale=basvuru_konusu_ihale, + karar_metni=karar_metni, + yil=yil, + resmi_gazete_tarihi=resmi_gazete_tarihi, + resmi_gazete_sayisi=resmi_gazete_sayisi, + page=page + ) + + logger.info(f"Tool 'search_kik_decisions' called.") + try: + api_response = await kik_client_instance.search_decisions(search_query) + page_param_for_log = search_query.page if hasattr(search_query, 'page') else 1 + if not api_response.decisions and api_response.total_records == 0 and page_param_for_log == 1: + logger.warning(f"KIK search returned no decisions for query.") + return api_response + except Exception as e: + logger.exception(f"Error in KIK search tool 'search_kik_decisions'.") + current_page_val = search_query.page if hasattr(search_query, 'page') else 1 + return KikSearchResult(decisions=[], total_records=0, current_page=current_page_val) + +@app.tool( + description="Get Public Procurement Authority (KİK) decision text in paginated Markdown format", + annotations={ + "readOnlyHint": True, + "idempotentHint": True + } +) +async def get_kik_document_markdown( + karar_id: str = Field(..., description="The Base64 encoded KIK decision identifier."), + page_number: int = Field(1, ge=1, description="Page number for paginated Markdown content (1-indexed). Default is 1.") +) -> KikDocumentMarkdown: + """Get KIK decision as paginated Markdown.""" + logger.info(f"Tool 'get_kik_document_markdown' called for KIK karar_id: {karar_id}, Markdown Page: {page_number}") + + if not karar_id or not karar_id.strip(): + logger.error("KIK Document retrieval: karar_id cannot be empty.") + return KikDocumentMarkdown( + retrieved_with_karar_id=karar_id, + error_message="karar_id is required and must be a non-empty string.", + current_page=page_number or 1, + total_pages=1, + is_paginated=False + ) + + current_page_to_fetch = page_number if page_number is not None and page_number >= 1 else 1 + + try: + return await kik_client_instance.get_decision_document_as_markdown( + karar_id_b64=karar_id, + page_number=current_page_to_fetch + ) + except Exception as e: + logger.exception(f"Error in KIK document retrieval tool 'get_kik_document_markdown' for karar_id: {karar_id}") + return KikDocumentMarkdown( + retrieved_with_karar_id=karar_id, + error_message=f"Tool-level error during KIK document retrieval: {str(e)}", + current_page=current_page_to_fetch, + total_pages=1, + is_paginated=False + ) +@app.tool( + description="Search Competition Authority (Rekabet Kurumu) decisions for competition law and antitrust", + annotations={ + "readOnlyHint": True, + "openWorldHint": True, + "idempotentHint": True + } +) +async def search_rekabet_kurumu_decisions( + sayfaAdi: str = Field("", description="Search in decision title (Başlık)."), + YayinlanmaTarihi: str = Field("", description="Publication date (Yayım Tarihi), e.g., DD.MM.YYYY."), + PdfText: str = Field( + "", + description='Search in decision text. Use "\\"kesin cümle\\"" for precise matching.' + ), + KararTuru: Literal[ + "ALL", + "Birleşme ve Devralma", + "Diğer", + "Menfi Tespit ve Muafiyet", + "Özelleştirme", + "Rekabet İhlali" + ] = Field("ALL", description="Parameter description"), + KararSayisi: str = Field("", description="Decision number (Karar Sayısı)."), + KararTarihi: str = Field("", description="Decision date (Karar Tarihi), e.g., DD.MM.YYYY."), + page: int = Field(1, ge=1, description="Page number to fetch for the results list.") +) -> RekabetSearchResult: + """Search Competition Authority decisions.""" + + karar_turu_guid_enum = KARAR_TURU_ADI_TO_GUID_ENUM_MAP.get(KararTuru) + + try: + if karar_turu_guid_enum is None: + logger.warning(f"Invalid user-provided KararTuru: '{KararTuru}'. Defaulting to TUMU (all).") + karar_turu_guid_enum = RekabetKararTuruGuidEnum.TUMU + except Exception as e_map: + logger.error(f"Error mapping KararTuru '{KararTuru}': {e_map}. Defaulting to TUMU.") + karar_turu_guid_enum = RekabetKararTuruGuidEnum.TUMU + + search_query = RekabetKurumuSearchRequest( + sayfaAdi=sayfaAdi, + YayinlanmaTarihi=YayinlanmaTarihi, + PdfText=PdfText, + KararTuruID=karar_turu_guid_enum, + KararSayisi=KararSayisi, + KararTarihi=KararTarihi, + page=page + ) + logger.info(f"Tool 'search_rekabet_kurumu_decisions' called. Query: {search_query.model_dump_json(exclude_none=True, indent=2)}") + try: + + return await rekabet_client_instance.search_decisions(search_query) + except Exception as e: + logger.exception("Error in tool 'search_rekabet_kurumu_decisions'.") + return RekabetSearchResult(decisions=[], retrieved_page_number=page, total_records_found=0, total_pages=0) + +@app.tool( + description="Get Competition Authority decision text in paginated Markdown format", + annotations={ + "readOnlyHint": True, + "idempotentHint": True + } +) +async def get_rekabet_kurumu_document( + karar_id: str = Field(..., description="GUID (kararId) of the Rekabet Kurumu decision. This ID is obtained from search results."), + page_number: int = Field(1, ge=1, description="Requested page number for the Markdown content converted from PDF (1-indexed, accepts int). Default is 1.") +) -> RekabetDocument: + """Get Competition Authority decision as paginated Markdown.""" + logger.info(f"Tool 'get_rekabet_kurumu_document' called. Karar ID: {karar_id}, Markdown Page: {page_number}") + + current_page_to_fetch = page_number if page_number >= 1 else 1 + + try: + + return await rekabet_client_instance.get_decision_document(karar_id, page_number=current_page_to_fetch) + except Exception as e: + logger.exception(f"Error in tool 'get_rekabet_kurumu_document'. Karar ID: {karar_id}") + raise + +# --- MCP Tools for Bedesten (Unified Search Across All Courts) --- +@app.tool( + description="Search multiple Turkish courts (Yargıtay, Danıştay, Local Courts, Appeals Courts, KYB)", + annotations={ + "readOnlyHint": True, + "openWorldHint": True, + "idempotentHint": True + } +) +async def search_bedesten_unified( + ctx: Context, + phrase: str = Field(..., description="""Search query in Turkish. SUPPORTED OPERATORS: +• Simple: "mülkiyet hakkı" (finds both words) +• Exact phrase: "\"mülkiyet hakkı\"" (finds exact phrase) +• Required term: "+mülkiyet hakkı" (must contain mülkiyet) +• Exclude term: "mülkiyet -kira" (contains mülkiyet but not kira) +• Boolean AND: "mülkiyet AND hak" (both terms required) +• Boolean OR: "mülkiyet OR tapu" (either term acceptable) +• Boolean NOT: "mülkiyet NOT satış" (contains mülkiyet but not satış) +NOTE: Wildcards (*,?), regex patterns (/regex/), fuzzy search (~), and proximity search are NOT supported. +For best results, use exact phrases with quotes for legal terms."""), + court_types: List[BedestenCourtTypeEnum] = Field( + default=["YARGITAYKARARI", "DANISTAYKARAR"], + description="Court types: YARGITAYKARARI, DANISTAYKARAR, YERELHUKUK, ISTINAFHUKUK, KYB" + ), + # pageSize: int = Field(10, ge=1, le=10, description="Results per page (1-10)"), + pageNumber: int = Field(1, ge=1, description="Page number"), + birimAdi: BirimAdiEnum = Field("ALL", description=""" + Chamber filter (optional). Abbreviated values with Turkish names: + • Yargıtay: H1-H23 (1-23. Hukuk Dairesi), C1-C23 (1-23. Ceza Dairesi), HGK (Hukuk Genel Kurulu), CGK (Ceza Genel Kurulu), BGK (Büyük Genel Kurulu), HBK (Hukuk Daireleri Başkanlar Kurulu), CBK (Ceza Daireleri Başkanlar Kurulu) + • Danıştay: D1-D17 (1-17. Daire), DBGK (Büyük Gen.Kur.), IDDK (İdare Dava Daireleri Kurulu), VDDK (Vergi Dava Daireleri Kurulu), IBK (İçtihatları Birleştirme Kurulu), IIK (İdari İşler Kurulu), DBK (Başkanlar Kurulu), AYIM (Askeri Yüksek İdare Mahkemesi), AYIM1-3 (Askeri Yüksek İdare Mahkemesi 1-3. Daire) + """), + kararTarihiStart: str = Field("", description="Start date (ISO 8601 format)"), + kararTarihiEnd: str = Field("", description="End date (ISO 8601 format)") +) -> dict: + """Search Turkish legal databases via unified Bedesten API.""" + + # Get Bearer token information for access control and logging + try: + access_token: AccessToken = get_access_token() + user_id = access_token.client_id + user_scopes = access_token.scopes + + # Check for required scopes + if "yargi.read" not in user_scopes and "yargi.search" not in user_scopes: + raise ToolError(f"Insufficient permissions: 'yargi.read' or 'yargi.search' scope required. Current scopes: {user_scopes}") + + logger.info(f"Tool 'search_bedesten_unified' called by user '{user_id}' with scopes {user_scopes}") + + except Exception as e: + # Development mode fallback - allow access without strict token validation + logger.warning(f"Bearer token validation failed, using development mode: {str(e)}") + user_id = "dev-user" + user_scopes = ["yargi.read", "yargi.search"] + + pageSize = 10 # Default value + + search_data = BedestenSearchData( + pageSize=pageSize, + pageNumber=pageNumber, + itemTypeList=court_types, + phrase=phrase, + birimAdi=birimAdi, + kararTarihiStart=kararTarihiStart, + kararTarihiEnd=kararTarihiEnd + ) + + search_request = BedestenSearchRequest(data=search_data) + + logger.info(f"User '{user_id}' searching bedesten: phrase='{phrase}', court_types={court_types}, birimAdi='{birimAdi}', page={pageNumber}") + + try: + response = await bedesten_client_instance.search_documents(search_request) + + if response.data is None: + return { + "decisions": [], + "total_records": 0, + "requested_page": pageNumber, + "page_size": pageSize, + "searched_courts": court_types, + "error": "No data returned from Bedesten API" + } + + return { + "decisions": [d.model_dump() for d in response.data.emsalKararList], + "total_records": response.data.total, + "requested_page": pageNumber, + "page_size": pageSize, + "searched_courts": court_types + } + except Exception as e: + logger.exception("Error in tool 'search_bedesten_unified'") + raise + +@app.tool( + description="Get legal decision document from Bedesten API in Markdown format", + annotations={ + "readOnlyHint": True, + "idempotentHint": True + } +) +async def get_bedesten_document_markdown( + documentId: str = Field(..., description="Document ID from Bedesten search results") +) -> BedestenDocumentMarkdown: + """Get legal decision document as Markdown from Bedesten API.""" + logger.info(f"Tool 'get_bedesten_document_markdown' called for ID: {documentId}") + + if not documentId or not documentId.strip(): + raise ValueError("Document ID must be a non-empty string.") + + try: + return await bedesten_client_instance.get_document_as_markdown(documentId) + except Exception as e: + logger.exception("Error in tool 'get_kyb_bedesten_document_markdown'") + raise + +# --- MCP Tools for Sayıştay (Turkish Court of Accounts) --- + +# DEACTIVATED TOOL - Use search_sayistay_unified instead +# @app.tool( +# description="Search Sayıştay Genel Kurul decisions for audit and accountability regulations", +# annotations={ +# "readOnlyHint": True, +# "openWorldHint": True, +# "idempotentHint": True +# } +# ) +# async def search_sayistay_genel_kurul( +# karar_no: str = Field("", description="Decision number to search for (e.g., '5415')"), +# karar_ek: str = Field("", description="Decision appendix number (max 99, e.g., '1')"), +# karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY)"), +# karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY)"), +# karar_tamami: str = Field("", description="Full text search"), +# start: int = Field(0, description="Starting record for pagination (0-based)"), +# length: int = Field(10, description="Number of records per page (1-100)") +# ) -> GenelKurulSearchResponse: +# """Search Sayıştay General Assembly decisions.""" +# raise ValueError("This tool is deactivated. Use search_sayistay_unified instead.") + +# DEACTIVATED TOOL - Use search_sayistay_unified instead +# @app.tool( +# description="Search Sayıştay Temyiz Kurulu decisions with chamber filtering and comprehensive criteria", +# annotations={ +# "readOnlyHint": True, +# "openWorldHint": True, +# "idempotentHint": True +# } +# ) +# async def search_sayistay_temyiz_kurulu( +# ilam_dairesi: DaireEnum = Field("ALL", description="Audit chamber selection"), +# yili: str = Field("", description="Year (YYYY)"), +# karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY)"), +# karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY)"), +# kamu_idaresi_turu: KamuIdaresiTuruEnum = Field("ALL", description="Public admin type"), +# ilam_no: str = Field("", description="Audit report number (İlam No, max 50 chars)"), +# dosya_no: str = Field("", description="File number for the case"), +# temyiz_tutanak_no: str = Field("", description="Appeals board meeting minutes number"), +# temyiz_karar: str = Field("", description="Appeals decision text"), +# web_karar_konusu: WebKararKonusuEnum = Field("ALL", description="Decision subject"), +# start: int = Field(0, description="Starting record for pagination (0-based)"), +# length: int = Field(10, description="Number of records per page (1-100)") +# ) -> TemyizKuruluSearchResponse: +# """Search Sayıştay Appeals Board decisions.""" +# raise ValueError("This tool is deactivated. Use search_sayistay_unified instead.") + +# DEACTIVATED TOOL - Use search_sayistay_unified instead +# @app.tool( +# description="Search Sayıştay Daire decisions with chamber filtering and subject categorization", +# annotations={ +# "readOnlyHint": True, +# "openWorldHint": True, +# "idempotentHint": True +# } +# ) +# async def search_sayistay_daire( +# yargilama_dairesi: DaireEnum = Field("ALL", description="Chamber selection"), +# karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY)"), +# karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY)"), +# ilam_no: str = Field("", description="Audit report number (İlam No, max 50 chars)"), +# kamu_idaresi_turu: KamuIdaresiTuruEnum = Field("ALL", description="Public admin type"), +# hesap_yili: str = Field("", description="Fiscal year"), +# web_karar_konusu: WebKararKonusuEnum = Field("ALL", description="Decision subject"), +# web_karar_metni: str = Field("", description="Decision text search"), +# start: int = Field(0, description="Starting record for pagination (0-based)"), +# length: int = Field(10, description="Number of records per page (1-100)") +# ) -> DaireSearchResponse: +# """Search Sayıştay Chamber decisions.""" +# raise ValueError("This tool is deactivated. Use search_sayistay_unified instead.") + +# DEACTIVATED TOOL - Use get_sayistay_document_unified instead +# @app.tool( +# description="Get Sayıştay Genel Kurul decision document in Markdown format", +# annotations={ +# "readOnlyHint": True, +# "openWorldHint": False, +# "idempotentHint": True +# } +# ) +# async def get_sayistay_genel_kurul_document_markdown( +# decision_id: str = Field(..., description="Decision ID from search_sayistay_genel_kurul results") +# ) -> SayistayDocumentMarkdown: +# """Get Sayıştay General Assembly decision as Markdown.""" +# raise ValueError("This tool is deactivated. Use get_sayistay_document_unified instead.") + +# DEACTIVATED TOOL - Use get_sayistay_document_unified instead +# @app.tool( +# description="Get Sayıştay Temyiz Kurulu decision document in Markdown format", +# annotations={ +# "readOnlyHint": True, +# "openWorldHint": False, +# "idempotentHint": True +# } +# ) +# async def get_sayistay_temyiz_kurulu_document_markdown( +# decision_id: str = Field(..., description="Decision ID from search_sayistay_temyiz_kurulu results") +# ) -> SayistayDocumentMarkdown: +# """Get Sayıştay Appeals Board decision as Markdown.""" +# raise ValueError("This tool is deactivated. Use get_sayistay_document_unified instead.") + +# DEACTIVATED TOOL - Use get_sayistay_document_unified instead +# @app.tool( +# description="Get Sayıştay Daire decision document in Markdown format", +# annotations={ +# "readOnlyHint": True, +# "openWorldHint": False, +# "idempotentHint": True +# } +# ) +# async def get_sayistay_daire_document_markdown( +# decision_id: str = Field(..., description="Decision ID from search_sayistay_daire results") +# ) -> SayistayDocumentMarkdown: +# """Get Sayıştay Chamber decision as Markdown.""" +# raise ValueError("This tool is deactivated. Use get_sayistay_document_unified instead.") + +# --- UNIFIED MCP Tools for Sayıştay (Turkish Court of Accounts) --- + +@app.tool( + description="Search Sayıştay decisions unified across all three decision types (Genel Kurul, Temyiz Kurulu, Daire) with comprehensive filtering", + annotations={ + "readOnlyHint": True, + "openWorldHint": True, + "idempotentHint": True + } +) +async def search_sayistay_unified( + decision_type: Literal["genel_kurul", "temyiz_kurulu", "daire"] = Field(..., description="Decision type: genel_kurul, temyiz_kurulu, or daire"), + + # Common pagination parameters + start: int = Field(0, ge=0, description="Starting record for pagination (0-based)"), + length: int = Field(10, ge=1, le=100, description="Number of records per page (1-100)"), + + # Common search parameters + karar_tarih_baslangic: str = Field("", description="Start date (DD.MM.YYYY format)"), + karar_tarih_bitis: str = Field("", description="End date (DD.MM.YYYY format)"), + kamu_idaresi_turu: Literal["ALL", "Genel Bütçe Kapsamındaki İdareler", "Yüksek Öğretim Kurumları", "Diğer Özel Bütçeli İdareler", "Düzenleyici ve Denetleyici Kurumlar", "Sosyal Güvenlik Kurumları", "Özel İdareler", "Belediyeler ve Bağlı İdareler", "Diğer"] = Field("ALL", description="Public administration type filter"), + ilam_no: str = Field("", description="Audit report number (İlam No, max 50 chars)"), + web_karar_konusu: Literal["ALL", "Harcırah Mevzuatı", "İhale Mevzuatı", "İş Mevzuatı", "Personel Mevzuatı", "Sorumluluk ve Yargılama Usulleri", "Vergi Resmi Harç ve Diğer Gelirler", "Çeşitli Konular"] = Field("ALL", description="Decision subject category filter"), + + # Genel Kurul specific parameters (ignored for other types) + karar_no: str = Field("", description="Decision number (genel_kurul only)"), + karar_ek: str = Field("", description="Decision appendix number (genel_kurul only)"), + karar_tamami: str = Field("", description="Full text search (genel_kurul only)"), + + # Temyiz Kurulu specific parameters (ignored for other types) + ilam_dairesi: Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8"] = Field("ALL", description="Audit chamber selection (temyiz_kurulu only)"), + yili: str = Field("", description="Year (YYYY format, temyiz_kurulu only)"), + dosya_no: str = Field("", description="File number (temyiz_kurulu only)"), + temyiz_tutanak_no: str = Field("", description="Appeals board meeting minutes number (temyiz_kurulu only)"), + temyiz_karar: str = Field("", description="Appeals decision text search (temyiz_kurulu only)"), + + # Daire specific parameters (ignored for other types) + yargilama_dairesi: Literal["ALL", "1", "2", "3", "4", "5", "6", "7", "8"] = Field("ALL", description="Chamber selection (daire only)"), + hesap_yili: str = Field("", description="Account year (daire only)"), + web_karar_metni: str = Field("", description="Decision text search (daire only)") +) -> SayistayUnifiedSearchResult: + """Search Sayıştay decisions across all three decision types with unified interface.""" + logger.info(f"Tool 'search_sayistay_unified' called with decision_type={decision_type}") + + try: + search_request = SayistayUnifiedSearchRequest( + decision_type=decision_type, + start=start, + length=length, + karar_tarih_baslangic=karar_tarih_baslangic, + karar_tarih_bitis=karar_tarih_bitis, + kamu_idaresi_turu=kamu_idaresi_turu, + ilam_no=ilam_no, + web_karar_konusu=web_karar_konusu, + karar_no=karar_no, + karar_ek=karar_ek, + karar_tamami=karar_tamami, + ilam_dairesi=ilam_dairesi, + yili=yili, + dosya_no=dosya_no, + temyiz_tutanak_no=temyiz_tutanak_no, + temyiz_karar=temyiz_karar, + yargilama_dairesi=yargilama_dairesi, + hesap_yili=hesap_yili, + web_karar_metni=web_karar_metni + ) + return await sayistay_unified_client_instance.search_unified(search_request) + except Exception as e: + logger.exception("Error in tool 'search_sayistay_unified'") + raise + +@app.tool( + description="Get Sayıştay decision document in Markdown format for any decision type", + annotations={ + "readOnlyHint": True, + "openWorldHint": False, + "idempotentHint": True + } +) +async def get_sayistay_document_unified( + decision_id: str = Field(..., description="Decision ID from search_sayistay_unified results"), + decision_type: Literal["genel_kurul", "temyiz_kurulu", "daire"] = Field(..., description="Decision type: genel_kurul, temyiz_kurulu, or daire") +) -> SayistayUnifiedDocumentMarkdown: + """Get Sayıştay decision document as Markdown for any decision type.""" + logger.info(f"Tool 'get_sayistay_document_unified' called for ID: {decision_id}, type: {decision_type}") + + if not decision_id or not decision_id.strip(): + raise ValueError("Decision ID must be a non-empty string.") + + try: + return await sayistay_unified_client_instance.get_document_unified(decision_id, decision_type) + except Exception as e: + logger.exception("Error in tool 'get_sayistay_document_unified'") + raise + +# --- Application Shutdown Handling --- +def perform_cleanup(): + logger.info("MCP Server performing cleanup...") + try: + loop = asyncio.get_event_loop_policy().get_event_loop() + if loop.is_closed(): + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + clients_to_close = [ + globals().get('yargitay_client_instance'), + globals().get('danistay_client_instance'), + globals().get('emsal_client_instance'), + globals().get('uyusmazlik_client_instance'), + globals().get('anayasa_norm_client_instance'), + globals().get('anayasa_bireysel_client_instance'), + globals().get('anayasa_unified_client_instance'), + globals().get('kik_client_instance'), + globals().get('rekabet_client_instance'), + globals().get('bedesten_client_instance'), + globals().get('sayistay_client_instance'), + globals().get('sayistay_unified_client_instance'), + globals().get('kvkk_client_instance'), + globals().get('bddk_client_instance') + ] + async def close_all_clients_async(): + tasks = [] + for client_instance in clients_to_close: + if client_instance and hasattr(client_instance, 'close_client_session') and callable(client_instance.close_client_session): + logger.info(f"Scheduling close for client session: {client_instance.__class__.__name__}") + tasks.append(client_instance.close_client_session()) + if tasks: + results = await asyncio.gather(*tasks, return_exceptions=True) + for i, result in enumerate(results): + if isinstance(result, Exception): + client_name = "Unknown Client" + if i < len(clients_to_close) and clients_to_close[i] is not None: + client_name = clients_to_close[i].__class__.__name__ + logger.error(f"Error closing client {client_name}: {result}") + try: + if loop.is_running(): + asyncio.ensure_future(close_all_clients_async(), loop=loop) + logger.info("Client cleanup tasks scheduled on running event loop.") + else: + loop.run_until_complete(close_all_clients_async()) + logger.info("Client cleanup tasks completed via run_until_complete.") + except Exception as e: + logger.error(f"Error during atexit cleanup execution: {e}", exc_info=True) + logger.info("MCP Server atexit cleanup process finished.") + +atexit.register(perform_cleanup) + +# --- Health Check Tools --- +@app.tool( + description="Check if Turkish government legal database servers are operational", + annotations={ + "readOnlyHint": True, + "idempotentHint": True + } +) +async def check_government_servers_health() -> Dict[str, Any]: + """Check health status of Turkish government legal database servers.""" + logger.info("Health check tool called for government servers") + + health_results = {} + + # Check Yargıtay server + try: + yargitay_payload = { + "data": { + "aranan": "karar", + "arananKelime": "karar", + "pageSize": 10, + "pageNumber": 1 + } + } + + async with httpx.AsyncClient( + headers={ + "Accept": "*/*", + "Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7", + "Connection": "keep-alive", + "Content-Type": "application/json; charset=UTF-8", + "Origin": "https://karararama.yargitay.gov.tr", + "Referer": "https://karararama.yargitay.gov.tr/", + "Sec-Fetch-Dest": "empty", + "Sec-Fetch-Mode": "cors", + "Sec-Fetch-Site": "same-origin", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36", + "X-Requested-With": "XMLHttpRequest" + }, + timeout=30.0, + verify=False + ) as client: + response = await client.post( + "https://karararama.yargitay.gov.tr/aramalist", + json=yargitay_payload + ) + + if response.status_code == 200: + response_data = response.json() + records_total = response_data.get("data", {}).get("recordsTotal", 0) + + if records_total > 0: + health_results["yargitay"] = { + "status": "healthy", + "response_time_ms": response.elapsed.total_seconds() * 1000 + } + else: + health_results["yargitay"] = { + "status": "unhealthy", + "reason": "recordsTotal is 0 or missing", + "response_time_ms": response.elapsed.total_seconds() * 1000 + } + else: + health_results["yargitay"] = { + "status": "unhealthy", + "reason": f"HTTP {response.status_code}", + "response_time_ms": response.elapsed.total_seconds() * 1000 + } + + except Exception as e: + health_results["yargitay"] = { + "status": "unhealthy", + "reason": f"Connection error: {str(e)}" + } + + # Check Bedesten API server + try: + bedesten_payload = { + "data": { + "pageSize": 5, + "pageNumber": 1, + "itemTypeList": ["YARGITAYKARARI"], + "phrase": "karar", + "sortFields": ["KARAR_TARIHI"], + "sortDirection": "desc" + }, + "applicationName": "UyapMevzuat", + "paging": True + } + + async with httpx.AsyncClient( + headers={ + "Content-Type": "application/json", + "Accept": "application/json", + "User-Agent": "Mozilla/5.0 Health Check" + }, + timeout=30.0, + verify=False + ) as client: + response = await client.post( + "https://bedesten.adalet.gov.tr/emsal-karar/searchDocuments", + json=bedesten_payload + ) + + if response.status_code == 200: + response_data = response.json() + logger.debug(f"Bedesten API response: {response_data}") + if response_data and isinstance(response_data, dict): + data_section = response_data.get("data") + if data_section and isinstance(data_section, dict): + total_found = data_section.get("total", 0) + else: + total_found = 0 + else: + total_found = 0 + + if total_found > 0: + health_results["bedesten"] = { + "status": "healthy", + "response_time_ms": response.elapsed.total_seconds() * 1000 + } + else: + health_results["bedesten"] = { + "status": "unhealthy", + "reason": "total is 0 or missing in data field", + "response_time_ms": response.elapsed.total_seconds() * 1000 + } + else: + health_results["bedesten"] = { + "status": "unhealthy", + "reason": f"HTTP {response.status_code}", + "response_time_ms": response.elapsed.total_seconds() * 1000 + } + + except Exception as e: + health_results["bedesten"] = { + "status": "unhealthy", + "reason": f"Connection error: {str(e)}" + } + + # Overall health assessment + healthy_servers = sum(1 for server in health_results.values() if server["status"] == "healthy") + total_servers = len(health_results) + + overall_status = "healthy" if healthy_servers == total_servers else "degraded" if healthy_servers > 0 else "unhealthy" + + return { + "overall_status": overall_status, + "healthy_servers": healthy_servers, + "total_servers": total_servers, + "servers": health_results, + "check_timestamp": f"{__import__('datetime').datetime.now().isoformat()}" + } + +# --- MCP Tools for KVKK --- +@app.tool( + description="Search KVKK data protection authority decisions", + annotations={ + "readOnlyHint": True, + "openWorldHint": True, + "idempotentHint": True + } +) +async def search_kvkk_decisions( + keywords: str = Field(..., description="Turkish keywords. Supports +required -excluded \"exact phrase\" operators"), + page: int = Field(1, ge=1, le=50, description="Page number for results (1-50)."), + # pageSize: int = Field(10, ge=1, le=20, description="Number of results per page (1-20).") +) -> KvkkSearchResult: + """Search function for legal decisions.""" + logger.info(f"KVKK search tool called with keywords: {keywords}") + + pageSize = 10 # Default value + + search_request = KvkkSearchRequest( + keywords=keywords, + page=page, + pageSize=pageSize + ) + + try: + result = await kvkk_client_instance.search_decisions(search_request) + logger.info(f"KVKK search completed. Found {len(result.decisions)} decisions on page {page}") + return result + except Exception as e: + logger.exception(f"Error in KVKK search: {e}") + # Return empty result on error + return KvkkSearchResult( + decisions=[], + total_results=0, + page=page, + pageSize=pageSize, + query=keywords + ) + +@app.tool( + description="Get KVKK decision document in Markdown format with metadata extraction", + annotations={ + "readOnlyHint": True, + "openWorldHint": False, + "idempotentHint": True + } +) +async def get_kvkk_document_markdown( + decision_url: str = Field(..., description="KVKK decision URL from search results"), + page_number: int = Field(1, ge=1, description="Page number for paginated Markdown content (1-indexed, accepts int). Default is 1 (first 5,000 characters).") +) -> KvkkDocumentMarkdown: + """Get KVKK decision as paginated Markdown.""" + logger.info(f"KVKK document retrieval tool called for URL: {decision_url}") + + if not decision_url or not decision_url.strip(): + return KvkkDocumentMarkdown( + source_url=HttpUrl("https://www.kvkk.gov.tr"), + title=None, + decision_date=None, + decision_number=None, + subject_summary=None, + markdown_chunk=None, + current_page=page_number or 1, + total_pages=0, + is_paginated=False, + error_message="Decision URL is required and cannot be empty." + ) + + try: + # Validate URL format + if not decision_url.startswith("https://www.kvkk.gov.tr/"): + return KvkkDocumentMarkdown( + source_url=HttpUrl(decision_url), + title=None, + decision_date=None, + decision_number=None, + subject_summary=None, + markdown_chunk=None, + current_page=page_number or 1, + total_pages=0, + is_paginated=False, + error_message="Invalid KVKK decision URL format. URL must start with https://www.kvkk.gov.tr/" + ) + + result = await kvkk_client_instance.get_decision_document(decision_url, page_number or 1) + logger.info(f"KVKK document retrieved successfully. Page {result.current_page}/{result.total_pages}, Content length: {len(result.markdown_chunk) if result.markdown_chunk else 0}") + return result + + except Exception as e: + logger.exception(f"Error retrieving KVKK document: {e}") + return KvkkDocumentMarkdown( + source_url=HttpUrl(decision_url), + title=None, + decision_date=None, + decision_number=None, + subject_summary=None, + markdown_chunk=None, + current_page=page_number or 1, + total_pages=0, + is_paginated=False, + error_message=f"Error retrieving KVKK document: {str(e)}" + ) + +# --- MCP Tools for BDDK (Banking Regulation Authority) --- +@app.tool( + description="Search BDDK banking regulation decisions", + annotations={ + "readOnlyHint": True, + "openWorldHint": True, + "idempotentHint": True + } +) +async def search_bddk_decisions( + keywords: str = Field(..., description="Search keywords in Turkish"), + page: int = Field(1, ge=1, description="Page number") + # pageSize: int = Field(10, ge=1, le=50, description="Results per page") +) -> dict: + """Search BDDK banking regulation and supervision decisions.""" + logger.info(f"BDDK search tool called with keywords: {keywords}, page: {page}") + + pageSize = 10 # Default value + + try: + search_request = BddkSearchRequest( + keywords=keywords, + page=page, + pageSize=pageSize + ) + + result = await bddk_client_instance.search_decisions(search_request) + logger.info(f"BDDK search completed. Found {len(result.decisions)} decisions on page {page}") + + return { + "decisions": [ + { + "title": dec.title, + "document_id": dec.document_id, + "content": dec.content + } + for dec in result.decisions + ], + "total_results": result.total_results, + "page": result.page, + "pageSize": result.pageSize + } + + except Exception as e: + logger.exception(f"Error searching BDDK decisions: {e}") + return { + "decisions": [], + "total_results": 0, + "page": page, + "pageSize": pageSize, + "error": str(e) + } + +@app.tool( + description="Get BDDK decision document as Markdown", + annotations={ + "readOnlyHint": True, + "openWorldHint": False, + "idempotentHint": True + } +) +async def get_bddk_document_markdown( + document_id: str = Field(..., description="BDDK document ID (e.g., '310')"), + page_number: int = Field(1, ge=1, description="Page number") +) -> dict: + """Retrieve BDDK decision document in Markdown format.""" + logger.info(f"BDDK document retrieval tool called for ID: {document_id}, page: {page_number}") + + if not document_id or not document_id.strip(): + return { + "document_id": document_id, + "markdown_content": "", + "page_number": page_number, + "total_pages": 0, + "error": "Document ID is required" + } + + try: + result = await bddk_client_instance.get_document_markdown(document_id, page_number) + logger.info(f"BDDK document retrieved successfully. Page {result.page_number}/{result.total_pages}") + + return { + "document_id": result.document_id, + "markdown_content": result.markdown_content, + "page_number": result.page_number, + "total_pages": result.total_pages + } + + except Exception as e: + logger.exception(f"Error retrieving BDDK document: {e}") + return { + "document_id": document_id, + "markdown_content": "", + "page_number": page_number, + "total_pages": 0, + "error": str(e) + } + +# --- ChatGPT Deep Research Compatible Tools --- + +def get_preview_text(markdown_content: str, skip_chars: int = 100, preview_chars: int = 200) -> str: + """ + Extract a preview of document text by skipping headers and showing meaningful content. + + Args: + markdown_content: Full document content in markdown format + skip_chars: Number of characters to skip from the beginning (default: 100) + preview_chars: Number of characters to show in preview (default: 200) + + Returns: + Preview text suitable for ChatGPT Deep Research + """ + if not markdown_content: + return "" + + # Remove common markdown artifacts and clean up + cleaned_content = markdown_content.strip() + + # Skip the first N characters (usually headers, metadata) + if len(cleaned_content) > skip_chars: + content_start = cleaned_content[skip_chars:] + else: + content_start = cleaned_content + + # Get the next N characters for preview + if len(content_start) > preview_chars: + preview = content_start[:preview_chars] + else: + preview = content_start + + # Clean up the preview - remove incomplete sentences at the end + preview = preview.strip() + + # If preview ends mid-sentence, try to end at last complete sentence + if preview and not preview.endswith('.'): + last_period = preview.rfind('.') + if last_period > 50: # Only if there's a reasonable sentence + preview = preview[:last_period + 1] + + # Add ellipsis if content was truncated + if len(content_start) > preview_chars: + preview += "..." + + return preview.strip() + +@app.tool( + description="DO NOT USE unless you are ChatGPT Deep Research. Search Turkish courts (Turkish keywords only). Supports: +term (must have), -term (exclude), \"exact phrase\", term1 OR term2", + annotations={ + "readOnlyHint": True, + "openWorldHint": True, + "idempotentHint": True + } +) +async def search( + query: str = Field(..., description="Turkish search query") +) -> Dict[str, List[Dict[str, str]]]: + """ + Bedesten API search tool for ChatGPT Deep Research compatibility. + + This tool searches Turkish legal databases via the unified Bedesten API. + It supports advanced search operators and covers all major court types. + + USAGE RESTRICTION: Only for ChatGPT Deep Research workflows. + For regular legal research, use search_bedesten_unified with specific court types. + + Returns: + Object with "results" field containing a list of documents with id, title, text preview, and url + as required by ChatGPT Deep Research specification. + """ + logger.info(f"ChatGPT Deep Research search tool called with query: {query}") + + results = [] + + try: + # Search all court types via unified Bedesten API + court_types = [ + ("YARGITAYKARARI", "Yargıtay", "yargitay_bedesten"), + ("DANISTAYKARAR", "Danıştay", "danistay_bedesten"), + ("YERELHUKUK", "Yerel Hukuk Mahkemesi", "yerel_hukuk_bedesten"), + ("ISTINAFHUKUK", "İstinaf Hukuk Mahkemesi", "istinaf_hukuk_bedesten"), + ("KYB", "Kanun Yararına Bozma", "kyb_bedesten") + ] + + for item_type, court_name, id_prefix in court_types: + try: + search_results = await bedesten_client_instance.search_documents( + BedestenSearchRequest( + data=BedestenSearchData( + phrase=query, # Use query as-is to support both regular and exact phrase searches + itemTypeList=[item_type], + pageSize=10, + pageNumber=1 + ) + ) + ) + + # Handle potential None data + if search_results.data is None: + logger.warning(f"No data returned from Bedesten API for {court_name}") + continue + + # Add results from this court type (limit to top 5 per court) + for decision in search_results.data.emsalKararList[:5]: + # For ChatGPT Deep Research, fetch document content for preview + try: + # Fetch document content for preview + doc = await bedesten_client_instance.get_document_as_markdown(decision.documentId) + + # Generate preview text (skip first 100 chars, show next 200) + preview_text = get_preview_text(doc.markdown_content, skip_chars=100, preview_chars=200) + + # Build title from metadata + title_parts = [] + if decision.birimAdi: + title_parts.append(decision.birimAdi) + if decision.esasNo: + title_parts.append(f"Esas: {decision.esasNo}") + if decision.kararNo: + title_parts.append(f"Karar: {decision.kararNo}") + if decision.kararTarihiStr: + title_parts.append(f"Tarih: {decision.kararTarihiStr}") + + if title_parts: + title = " - ".join(title_parts) + else: + title = f"{court_name} - Document {decision.documentId}" + + # Add to results in OpenAI format + results.append({ + "id": decision.documentId, + "title": title, + "text": preview_text, + "url": f"https://mevzuat.adalet.gov.tr/ictihat/{decision.documentId}" + }) + + except Exception as e: + logger.warning(f"Could not fetch preview for document {decision.documentId}: {e}") + # Add minimal result without preview + results.append({ + "id": decision.documentId, + "title": f"{court_name} - Document {decision.documentId}", + "text": "Document preview not available", + "url": f"https://mevzuat.adalet.gov.tr/ictihat/{decision.documentId}" + }) + + if search_results.data: + logger.info(f"Found {len(search_results.data.emsalKararList)} results from {court_name}") + else: + logger.info(f"Found 0 results from {court_name} (no data returned)") + + except Exception as e: + logger.warning(f"Bedesten API search error for {court_name}: {e}") + + # Comment out other API implementations for ChatGPT Deep Research + """ + # Other API implementations disabled for ChatGPT Deep Research + # These are available through specific court tools: + + # Yargıtay Official API - use search_yargitay_detailed instead + # Danıştay Official API - use search_danistay_by_keyword instead + # Constitutional Court - use search_anayasa_norm_denetimi_decisions instead + # Competition Authority - use search_rekabet_kurumu_decisions instead + # Public Procurement Authority - use search_kik_decisions instead + # Court of Accounts - use search_sayistay_* tools instead + # UYAP Emsal - use search_emsal_detailed_decisions instead + # Jurisdictional Disputes Court - use search_uyusmazlik_decisions instead + """ + + logger.info(f"ChatGPT Deep Research search completed. Found {len(results)} results via Bedesten API.") + return {"results": results} + + except Exception as e: + logger.exception("Error in ChatGPT Deep Research search tool") + # Return partial results if any were found + if results: + return {"results": results} + raise + +@app.tool( + description="DO NOT USE unless you are ChatGPT Deep Research. Fetch document by ID. See docs for details", + annotations={ + "readOnlyHint": True, + "openWorldHint": False, # Retrieves specific documents, not exploring + "idempotentHint": True + } +) +async def fetch( + id: str = Field(..., description="Document identifier from search results (numeric only)") +) -> Dict[str, Any]: + """ + Bedesten API fetch tool for ChatGPT Deep Research compatibility. + + Retrieves the full text content of Turkish legal documents via unified Bedesten API. + Converts documents from HTML/PDF to clean Markdown format. + + USAGE RESTRICTION: Only for ChatGPT Deep Research workflows. + For regular legal research, use specific court document tools. + + Input Format: + - id: Numeric document identifier from search results (e.g., "730113500", "71370900") + + Returns: + Single object with numeric id, title, text (full Markdown content), mevzuat.adalet.gov.tr url, and metadata fields + as required by ChatGPT Deep Research specification. + """ + logger.info(f"ChatGPT Deep Research fetch tool called for document ID: {id}") + + if not id or not id.strip(): + raise ValueError("Document ID must be a non-empty string") + + try: + # Use the numeric ID directly with Bedesten API + doc = await bedesten_client_instance.get_document_as_markdown(id) + + # Try to get additional metadata by searching for this specific document + title = f"Turkish Legal Document {id}" + try: + # Quick search to get metadata for better title + search_results = await bedesten_client_instance.search_documents( + BedestenSearchRequest( + data=BedestenSearchData( + phrase=id, # Search by document ID + pageSize=1, + pageNumber=1 + ) + ) + ) + + if search_results.data and search_results.data.emsalKararList: + decision = search_results.data.emsalKararList[0] + if decision.documentId == id: + # Build a proper title from metadata + title_parts = [] + if decision.birimAdi: + title_parts.append(decision.birimAdi) + if decision.esasNo: + title_parts.append(f"Esas: {decision.esasNo}") + if decision.kararNo: + title_parts.append(f"Karar: {decision.kararNo}") + if decision.kararTarihiStr: + title_parts.append(f"Tarih: {decision.kararTarihiStr}") + + if title_parts: + title = " - ".join(title_parts) + else: + title = f"Turkish Legal Decision {id}" + except Exception as e: + logger.warning(f"Could not fetch metadata for document {id}: {e}") + + return { + "id": id, + "title": title, + "text": doc.markdown_content, + "url": f"https://mevzuat.adalet.gov.tr/ictihat/{id}", + "metadata": { + "database": "Turkish Legal Database via Bedesten API", + "document_id": id, + "source_url": doc.source_url, + "mime_type": doc.mime_type, + "api_source": "Bedesten Unified API", + "chatgpt_deep_research": True + } + } + + # Comment out other API implementations for ChatGPT Deep Research + """ + # Other API implementations disabled for ChatGPT Deep Research + # These are available through specific court document tools: + + elif id.startswith("yargitay_"): + # Yargıtay Official API - use get_yargitay_document_markdown instead + doc_id = id.replace("yargitay_", "") + doc = await yargitay_client_instance.get_decision_document_as_markdown(doc_id) + + elif id.startswith("danistay_"): + # Danıştay Official API - use get_danistay_document_markdown instead + doc_id = id.replace("danistay_", "") + doc = await danistay_client_instance.get_decision_document_as_markdown(doc_id) + + elif id.startswith("anayasa_"): + # Constitutional Court - use get_anayasa_norm_denetimi_document_markdown instead + doc_id = id.replace("anayasa_", "") + doc = await anayasa_norm_client_instance.get_decision_document_as_markdown(...) + + elif id.startswith("rekabet_"): + # Competition Authority - use get_rekabet_kurumu_document instead + doc_id = id.replace("rekabet_", "") + doc = await rekabet_client_instance.get_decision_document(...) + + elif id.startswith("kik_"): + # Public Procurement Authority - use get_kik_decision_document_as_markdown instead + doc_id = id.replace("kik_", "") + doc = await kik_client_instance.get_decision_document_as_markdown(doc_id) + + elif id.startswith("local_"): + # This was already using Bedesten API, but deprecated for ChatGPT Deep Research + doc_id = id.replace("local_", "") + doc = await bedesten_client_instance.get_document_as_markdown(doc_id) + """ + + except Exception as e: + logger.exception(f"Error fetching ChatGPT Deep Research document {id}") + raise + +# --- Token Metrics Tool Removed for Optimization --- + +def ensure_playwright_browsers(): + """Ensure Playwright browsers are installed for KIK tool functionality.""" + try: + import subprocess + import os + + # Check if chromium is already installed + chromium_path = os.path.expanduser("~/Library/Caches/ms-playwright/chromium-1179") + if os.path.exists(chromium_path): + logger.info("Playwright Chromium browser already installed.") + return + + logger.info("Installing Playwright Chromium browser for KIK tool...") + result = subprocess.run( + ["python", "-m", "playwright", "install", "chromium"], + capture_output=True, + text=True, + timeout=300 # 5 minutes timeout + ) + + if result.returncode == 0: + logger.info("Playwright Chromium browser installed successfully.") + else: + logger.warning(f"Failed to install Playwright browser: {result.stderr}") + logger.warning("KIK tool may not work properly without Playwright browsers.") + + except Exception as e: + logger.warning(f"Could not auto-install Playwright browsers: {e}") + logger.warning("KIK tool may not work properly. Manual installation: 'playwright install chromium'") + +def main(): + logger.info(f"Starting {app.name} server via main() function...") + logger.info(f"Logs will be written to: {LOG_FILE_PATH}") + + # Ensure Playwright browsers are installed + ensure_playwright_browsers() + + try: + app.run() + except KeyboardInterrupt: + logger.info("Server shut down by user (KeyboardInterrupt).") + except Exception as e: + logger.exception("Server failed to start or crashed.") + finally: + logger.info(f"{app.name} server has shut down.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/nginx.conf b/saidsurucu-yargi-mcp-f5fa007/nginx.conf new file mode 100644 index 0000000..5cb39b2 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/nginx.conf @@ -0,0 +1,94 @@ +events { + worker_connections 1024; +} + +http { + upstream yargi_mcp { + server yargi-mcp:8000; + } + + # Rate limiting + limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; + limit_req_zone $binary_remote_addr zone=mcp_limit:10m rate=100r/s; + + server { + listen 80; + server_name localhost; + + # Redirect HTTP to HTTPS in production + # return 301 https://$server_name$request_uri; + + # Security headers + add_header X-Content-Type-Options nosniff; + add_header X-Frame-Options DENY; + add_header X-XSS-Protection "1; mode=block"; + add_header Referrer-Policy "strict-origin-when-cross-origin"; + + # API endpoints + location /api/ { + limit_req zone=api_limit burst=20 nodelay; + + proxy_pass http://yargi_mcp; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Timeouts + proxy_connect_timeout 60s; + proxy_send_timeout 60s; + proxy_read_timeout 60s; + } + + # MCP endpoint (higher rate limit) + location /mcp-server/mcp/ { + limit_req zone=mcp_limit burst=50 nodelay; + + proxy_pass http://yargi_mcp; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + # Longer timeouts for MCP operations + proxy_connect_timeout 300s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + } + + # Health check (no rate limit) + location /health { + proxy_pass http://yargi_mcp; + proxy_set_header Host $host; + } + + # Root and other paths + location / { + limit_req zone=api_limit burst=10 nodelay; + + proxy_pass http://yargi_mcp; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } + + # SSL configuration (uncomment for production) + # server { + # listen 443 ssl http2; + # server_name your-domain.com; + # + # ssl_certificate /etc/nginx/ssl/cert.pem; + # ssl_certificate_key /etc/nginx/ssl/key.pem; + # ssl_protocols TLSv1.2 TLSv1.3; + # ssl_ciphers HIGH:!aNULL:!MD5; + # + # # Include all location blocks from above + # } +} \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/ornek.png b/saidsurucu-yargi-mcp-f5fa007/ornek.png new file mode 100644 index 0000000000000000000000000000000000000000..012d2ef20abe589048c82b39e4af49eaeee97b4a GIT binary patch literal 545626 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⪙ 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 OgZPG<>ZR6Y$f{-#W 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

vz}#WafzF0oT@$;W{2|9$3`79k$b`ej{BF4J3e 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�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 literal 0 HcmV?d00001 diff --git a/saidsurucu-yargi-mcp-f5fa007/pyproject.toml b/saidsurucu-yargi-mcp-f5fa007/pyproject.toml new file mode 100644 index 0000000..e90bea6 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/pyproject.toml @@ -0,0 +1,66 @@ +[project] +name = "yargi-mcp" +version = "0.1.6" +description = "MCP Server For Turkish Legal Databases" +readme = "README.md" +requires-python = ">=3.11" +license = {text = "MIT"} +authors = [{name = "Said Surucu", email = "saidsrc@gmail.com"}] +keywords = ["mcp", "turkish-law", "legal", "yargitay", "danistay", "bddk", "kvkk", "turkish", "law", "court", "decisions"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Legal Industry", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Text Processing :: Markup :: Markdown", + "Operating System :: OS Independent", +] +urls = {Homepage = "https://github.com/saidsurucu/yargi-mcp", Issues = "https://github.com/saidsurucu/yargi-mcp/issues"} +dependencies = [ + "beautifulsoup4>=4.13.4", + "httpx>=0.28.1", + "markitdown[pdf]>=0.1.1", + "pydantic>=2.11.4", + "aiohttp>=3.11.18", + "playwright>=1.52.0", + "fastmcp>=2.10.5", + "pypdf>=5.5.0", + "fastapi>=0.115.14", + "PyJWT>=2.8.0", + "tiktoken>=0.5.0", +] + +[project.optional-dependencies] +asgi = [ + "uvicorn[standard]>=0.30.0", + "starlette>=0.37.0", +] +api = [ + "fastapi>=0.115.0", + "uvicorn[standard]>=0.30.0", +] +production = [ + "gunicorn>=22.0.0", + "uvicorn[standard]>=0.30.0", +] +saas = [ + "clerk-backend-api>=3.0.0", + "stripe>=9.1.0", + "upstash-redis>=1.1.0", +] + +[project.scripts] +yargi-mcp = "mcp_server_main:main" + +[tool.setuptools] +py-modules = ["mcp_server_main", "mcp_auth_factory", "mcp_auth_http_adapter", "asgi_app", "fastapi_app", "starlette_app", "run_asgi", "stripe_webhook"] + +[tool.setuptools.packages.find] +include = ["*_mcp_module", "mcp_auth"] + +[build-system] +requires = ["setuptools>=65.0", "wheel"] +build-backend = "setuptools.build_meta" diff --git a/saidsurucu-yargi-mcp-f5fa007/railway.json b/saidsurucu-yargi-mcp-f5fa007/railway.json new file mode 100644 index 0000000..0c54927 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/railway.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://railway.app/railway.schema.json", + "build": { + "builder": "NIXPACKS", + "buildCommand": "pip install -e .[asgi]" + }, + "deploy": { + "startCommand": "uvicorn asgi_app:app --host 0.0.0.0 --port $PORT", + "healthcheckPath": "/health", + "healthcheckTimeout": 30, + "restartPolicyType": "ON_FAILURE", + "restartPolicyMaxRetries": 3 + }, + "variables": { + "ALLOWED_ORIGINS": "*", + "LOG_LEVEL": "info" + } +} \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/redis_session_store.py b/saidsurucu-yargi-mcp-f5fa007/redis_session_store.py new file mode 100644 index 0000000..0eb1142 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/redis_session_store.py @@ -0,0 +1,464 @@ +""" +Redis Session Store for OAuth Authorization Codes and User Sessions + +This module provides Redis-based storage for OAuth authorization codes and user sessions, +enabling multi-machine deployment support by replacing in-memory storage. + +Uses Upstash Redis via REST API for serverless-friendly operation. +""" + +import os +import json +import time +import logging +from typing import Optional, Dict, Any, Union +from datetime import datetime, timedelta + +logger = logging.getLogger(__name__) + +try: + from upstash_redis import Redis + UPSTASH_AVAILABLE = True +except ImportError: + UPSTASH_AVAILABLE = False + Redis = None + +# Use standard Python exceptions for Redis connection errors +import socket +from requests.exceptions import ConnectionError as RequestsConnectionError, Timeout as RequestsTimeout + +class RedisSessionStore: + """ + Redis-based session store for OAuth flows and user sessions. + + Uses Upstash Redis REST API for connection-free operation suitable for + multi-instance deployments on platforms like Fly.io. + """ + + def __init__(self): + """Initialize Redis connection using environment variables.""" + if not UPSTASH_AVAILABLE: + raise ImportError("upstash-redis package is required. Install with: pip install upstash-redis") + + # Initialize Upstash Redis client from environment with optimized connection settings + try: + # Get Upstash Redis configuration + redis_url = os.getenv("UPSTASH_REDIS_REST_URL") + redis_token = os.getenv("UPSTASH_REDIS_REST_TOKEN") + + if not redis_url or not redis_token: + raise ValueError("UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN must be set") + + logger.info(f"Connecting to Upstash Redis at {redis_url[:30]}...") + + # Initialize with explicit configuration for better SSL handling + self.redis = Redis( + url=redis_url, + token=redis_token + ) + + logger.info("Upstash Redis client created") + + # Skip connection test during initialization to prevent server hang + # Connection will be tested during first actual operation + logger.info("Redis client initialized - connection will be tested on first use") + + except Exception as e: + logger.error(f"Failed to initialize Upstash Redis: {e}") + raise + + # TTL values (in seconds) + self.oauth_code_ttl = int(os.getenv("OAUTH_CODE_TTL", "600")) # 10 minutes + self.session_ttl = int(os.getenv("SESSION_TTL", "3600")) # 1 hour + + def _serialize_data(self, data: Dict[str, Any]) -> Dict[str, str]: + """Convert data to Redis-compatible string format.""" + serialized = {} + for key, value in data.items(): + if isinstance(value, (dict, list)): + serialized[key] = json.dumps(value) + elif isinstance(value, (int, float)): + serialized[key] = str(value) + elif isinstance(value, bool): + serialized[key] = "true" if value else "false" + else: + serialized[key] = str(value) + return serialized + + def _deserialize_data(self, data: Dict[str, str]) -> Dict[str, Any]: + """Convert Redis string data back to original types.""" + if not data: + return {} + + deserialized = {} + for key, value in data.items(): + if not isinstance(value, str): + deserialized[key] = value + continue + + # Try to deserialize JSON + if value.startswith(('[', '{')): + try: + deserialized[key] = json.loads(value) + continue + except json.JSONDecodeError: + pass + + # Try to convert numbers + if value.isdigit(): + deserialized[key] = int(value) + continue + + if value.replace('.', '').isdigit(): + try: + deserialized[key] = float(value) + continue + except ValueError: + pass + + # Handle booleans + if value in ("true", "false"): + deserialized[key] = value == "true" + continue + + # Keep as string + deserialized[key] = value + + return deserialized + + # OAuth Authorization Code Methods + + def set_oauth_code(self, code: str, data: Dict[str, Any]) -> bool: + """ + Store OAuth authorization code with automatic expiration. + + Args: + code: Authorization code string + data: Code data including user_id, client_id, etc. + + Returns: + True if stored successfully, False otherwise + """ + try: + key = f"oauth:code:{code}" + + # Add timestamp for debugging + data_with_timestamp = data.copy() + data_with_timestamp.update({ + "created_at": time.time(), + "expires_at": time.time() + self.oauth_code_ttl + }) + + # Serialize and store - Upstash Redis doesn't support mapping parameter + serialized_data = self._serialize_data(data_with_timestamp) + + # Use individual hset calls for each field with retry logic + max_retries = 3 + for attempt in range(max_retries): + try: + # Clear any existing data first + self.redis.delete(key) + + # Set all fields in a pipeline-like manner + for field, value in serialized_data.items(): + self.redis.hset(key, field, value) + + # Set expiration + self.redis.expire(key, self.oauth_code_ttl) + + logger.info(f"Stored OAuth code {code[:10]}... with TTL {self.oauth_code_ttl}s (attempt {attempt + 1})") + return True + + except (RequestsConnectionError, RequestsTimeout, OSError, socket.error) as e: + logger.warning(f"Redis connection error on attempt {attempt + 1}: {e}") + if attempt == max_retries - 1: + raise # Re-raise on final attempt + time.sleep(0.5 * (attempt + 1)) # Exponential backoff + + except Exception as e: + logger.error(f"Failed to store OAuth code {code[:10]}... after {max_retries} attempts: {e}") + return False + + def get_oauth_code(self, code: str, delete_after_use: bool = True) -> Optional[Dict[str, Any]]: + """ + Retrieve OAuth authorization code data. + + Args: + code: Authorization code string + delete_after_use: If True, delete the code after retrieval (one-time use) + + Returns: + Code data dictionary or None if not found/expired + """ + max_retries = 3 + for attempt in range(max_retries): + try: + key = f"oauth:code:{code}" + + # Get all hash fields with retry + data = self.redis.hgetall(key) + + if not data: + logger.warning(f"OAuth code {code[:10]}... not found or expired (attempt {attempt + 1})") + return None + + # Deserialize data + deserialized_data = self._deserialize_data(data) + + # Check manual expiration (in case Redis TTL failed) + expires_at = deserialized_data.get("expires_at", 0) + if expires_at and time.time() > expires_at: + logger.warning(f"OAuth code {code[:10]}... manually expired") + try: + self.redis.delete(key) + except Exception as del_error: + logger.warning(f"Failed to delete expired code: {del_error}") + return None + + # Delete after use for security (one-time use) + if delete_after_use: + try: + self.redis.delete(key) + logger.info(f"Retrieved and deleted OAuth code {code[:10]}... (attempt {attempt + 1})") + except Exception as del_error: + logger.warning(f"Failed to delete code after use: {del_error}") + # Continue anyway since we got the data + else: + logger.info(f"Retrieved OAuth code {code[:10]}... (not deleted, attempt {attempt + 1})") + + return deserialized_data + + except (RequestsConnectionError, RequestsTimeout, OSError, socket.error) as e: + logger.warning(f"Redis connection error on retrieval attempt {attempt + 1}: {e}") + if attempt == max_retries - 1: + logger.error(f"Failed to retrieve OAuth code {code[:10]}... after {max_retries} attempts: {e}") + return None + time.sleep(0.5 * (attempt + 1)) # Exponential backoff + + except Exception as e: + logger.error(f"Failed to retrieve OAuth code {code[:10]}... on attempt {attempt + 1}: {e}") + if attempt == max_retries - 1: + return None + time.sleep(0.5 * (attempt + 1)) + + return None + + # User Session Methods + + def set_session(self, session_id: str, user_data: Dict[str, Any]) -> bool: + """ + Store user session data with sliding expiration. + + Args: + session_id: Unique session identifier + user_data: User session data (user_id, email, scopes, etc.) + + Returns: + True if stored successfully, False otherwise + """ + try: + key = f"session:{session_id}" + + # Add session metadata + session_data = user_data.copy() + session_data.update({ + "session_id": session_id, + "created_at": time.time(), + "last_accessed": time.time() + }) + + # Serialize and store - Upstash Redis doesn't support mapping parameter + serialized_data = self._serialize_data(session_data) + + # Use individual hset calls for each field (Upstash compatibility) + for field, value in serialized_data.items(): + self.redis.hset(key, field, value) + self.redis.expire(key, self.session_ttl) + + logger.info(f"Stored session {session_id[:10]}... with TTL {self.session_ttl}s") + return True + + except Exception as e: + logger.error(f"Failed to store session {session_id[:10]}...: {e}") + return False + + def get_session(self, session_id: str, refresh_ttl: bool = True) -> Optional[Dict[str, Any]]: + """ + Retrieve user session data. + + Args: + session_id: Session identifier + refresh_ttl: If True, extend session TTL on access + + Returns: + Session data dictionary or None if not found/expired + """ + try: + key = f"session:{session_id}" + + # Get session data + data = self.redis.hgetall(key) + + if not data: + logger.warning(f"Session {session_id[:10]}... not found or expired") + return None + + # Deserialize data + session_data = self._deserialize_data(data) + + # Update last accessed time and refresh TTL + if refresh_ttl: + session_data["last_accessed"] = time.time() + self.redis.hset(key, "last_accessed", str(time.time())) + self.redis.expire(key, self.session_ttl) + logger.debug(f"Refreshed session {session_id[:10]}... TTL") + + return session_data + + except Exception as e: + logger.error(f"Failed to retrieve session {session_id[:10]}...: {e}") + return None + + def delete_session(self, session_id: str) -> bool: + """ + Delete user session (logout). + + Args: + session_id: Session identifier + + Returns: + True if deleted successfully, False otherwise + """ + try: + key = f"session:{session_id}" + result = self.redis.delete(key) + + if result: + logger.info(f"Deleted session {session_id[:10]}...") + return True + else: + logger.warning(f"Session {session_id[:10]}... not found for deletion") + return False + + except Exception as e: + logger.error(f"Failed to delete session {session_id[:10]}...: {e}") + return False + + # Health Check Methods + + def health_check(self) -> Dict[str, Any]: + """ + Perform Redis health check. + + Returns: + Health status dictionary + """ + try: + # Test basic operations + test_key = f"health:check:{int(time.time())}" + test_value = {"timestamp": time.time(), "test": True} + + # Test set - Use individual hset calls for Upstash compatibility + serialized_test = self._serialize_data(test_value) + for field, value in serialized_test.items(): + self.redis.hset(test_key, field, value) + + # Test get + retrieved = self.redis.hgetall(test_key) + + # Test delete + self.redis.delete(test_key) + + return { + "status": "healthy", + "redis_connected": True, + "operations_working": bool(retrieved), + "timestamp": datetime.utcnow().isoformat() + } + + except Exception as e: + logger.error(f"Redis health check failed: {e}") + return { + "status": "unhealthy", + "redis_connected": False, + "error": str(e), + "timestamp": datetime.utcnow().isoformat() + } + + def get_stats(self) -> Dict[str, Any]: + """ + Get Redis usage statistics. + + Returns: + Statistics dictionary + """ + try: + # Get basic info (not all Upstash plans support INFO command) + stats = { + "oauth_codes_pattern": "oauth:code:*", + "sessions_pattern": "session:*", + "timestamp": datetime.utcnow().isoformat() + } + + try: + # Try to get counts (may fail on some Upstash plans) + oauth_keys = self.redis.keys("oauth:code:*") + session_keys = self.redis.keys("session:*") + + stats.update({ + "active_oauth_codes": len(oauth_keys) if oauth_keys else 0, + "active_sessions": len(session_keys) if session_keys else 0 + }) + except Exception as e: + logger.warning(f"Could not get detailed stats: {e}") + stats["warning"] = "Detailed stats not available on this Redis plan" + + return stats + + except Exception as e: + logger.error(f"Failed to get Redis stats: {e}") + return {"error": str(e), "timestamp": datetime.utcnow().isoformat()} + +# Global instance for easy importing +redis_store = None + +def get_redis_store() -> Optional[RedisSessionStore]: + """ + Get global Redis store instance (singleton pattern). + + Returns: + RedisSessionStore instance or None if initialization fails + """ + global redis_store + + if redis_store is None: + try: + logger.info("Initializing Redis store...") + redis_store = RedisSessionStore() + logger.info("Redis store initialized successfully") + except Exception as e: + logger.error(f"Failed to initialize Redis store: {e}") + redis_store = None + + return redis_store + +def init_redis_store() -> RedisSessionStore: + """ + Initialize Redis store and perform health check. + + Returns: + RedisSessionStore instance + + Raises: + Exception if Redis is not available or unhealthy + """ + store = get_redis_store() + + # Perform health check + health = store.health_check() + + if health["status"] != "healthy": + raise Exception(f"Redis health check failed: {health}") + + logger.info("Redis session store initialized and healthy") + return store \ No newline at end of file diff --git a/saidsurucu-yargi-mcp-f5fa007/rekabet_mcp_module/__init__.py b/saidsurucu-yargi-mcp-f5fa007/rekabet_mcp_module/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/saidsurucu-yargi-mcp-f5fa007/rekabet_mcp_module/client.py b/saidsurucu-yargi-mcp-f5fa007/rekabet_mcp_module/client.py new file mode 100644 index 0000000..1b6e782 --- /dev/null +++ b/saidsurucu-yargi-mcp-f5fa007/rekabet_mcp_module/client.py @@ -0,0 +1,407 @@ +# rekabet_mcp_module/client.py + +import httpx +from bs4 import BeautifulSoup +from typing import List, Optional, Tuple, Dict, Any +import logging +import html +import re +import io # For io.BytesIO +from urllib.parse import urlencode, urljoin, quote, parse_qs, urlparse +from markitdown import MarkItDown +import math + +# pypdf for PDF processing (lighter alternative to PyMuPDF) +from pypdf import PdfReader, PdfWriter # PyPDF2'nin devamı niteliğindeki pypdf + +from .models import ( + RekabetKurumuSearchRequest, + RekabetDecisionSummary, + RekabetSearchResult, + RekabetDocument, + RekabetKararTuruGuidEnum +) +from pydantic import HttpUrl # Ensure HttpUrl is imported from pydantic + +logger = logging.getLogger(__name__) +if not logger.hasHandlers(): # Pragma: no cover + logging.basicConfig( + level=logging.INFO, # Varsayılan log seviyesi + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' + ) + # Debug betiğinde daha detaylı loglama için seviye ayrıca ayarlanabilir. + +class RekabetKurumuApiClient: + BASE_URL = "https://www.rekabet.gov.tr" + SEARCH_PATH = "/tr/Kararlar" + DECISION_LANDING_PATH_TEMPLATE = "/Karar" + # PDF sayfa bazlı Markdown döndürüldüğü için bu sabit artık doğrudan kullanılmıyor. + # DOCUMENT_MARKDOWN_CHUNK_SIZE = 5000 + + def __init__(self, request_timeout: float = 60.0): + self.http_client = httpx.AsyncClient( + base_url=self.BASE_URL, + headers={ + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + }, + timeout=request_timeout, + verify=True, + follow_redirects=True + ) + + def _build_search_query_params(self, params: RekabetKurumuSearchRequest) -> List[Tuple[str, str]]: + query_params: List[Tuple[str, str]] = [] + query_params.append(("sayfaAdi", params.sayfaAdi if params.sayfaAdi is not None else "")) + query_params.append(("YayinlanmaTarihi", params.YayinlanmaTarihi if params.YayinlanmaTarihi is not None else "")) + query_params.append(("PdfText", params.PdfText if params.PdfText is not None else "")) + + karar_turu_id_value = "" + if params.KararTuruID is not None: + karar_turu_id_value = params.KararTuruID.value if params.KararTuruID.value != "ALL" else "" + query_params.append(("KararTuruID", karar_turu_id_value)) + + query_params.append(("KararSayisi", params.KararSayisi if params.KararSayisi is not None else "")) + query_params.append(("KararTarihi", params.KararTarihi if params.KararTarihi is not None else "")) + + if params.page and params.page > 1: + query_params.append(("page", str(params.page))) + + return query_params + + async def search_decisions(self, params: RekabetKurumuSearchRequest) -> RekabetSearchResult: + request_path = self.SEARCH_PATH + final_query_params = self._build_search_query_params(params) + logger.info(f"RekabetKurumuApiClient: Performing search. Path: {request_path}, Parameters: {final_query_params}") + + try: + response = await self.http_client.get(request_path, params=final_query_params) + response.raise_for_status() + html_content = response.text + except httpx.RequestError as e: + logger.error(f"RekabetKurumuApiClient: HTTP request error during search: {e}") + raise + + soup = BeautifulSoup(html_content, 'html.parser') + processed_decisions: List[RekabetDecisionSummary] = [] + total_records: Optional[int] = None + total_pages: Optional[int] = None + + pagination_div = soup.find("div", class_="yazi01") + if pagination_div: + text_content = pagination_div.get_text(separator=" ", strip=True) + total_match = re.search(r"Toplam\s*:\s*(\d+)", text_content) + if total_match: + try: + total_records = int(total_match.group(1)) + logger.debug(f"Total records found from pagination: {total_records}") + except ValueError: + logger.warning(f"Could not convert 'Toplam' value to int: {total_match.group(1)}") + else: + logger.warning("'Toplam :' string not found in pagination section.") + + results_per_page_assumed = 10 + if total_records is not None: + calculated_total_pages = math.ceil(total_records / results_per_page_assumed) + total_pages = calculated_total_pages if calculated_total_pages > 0 else (1 if total_records > 0 else 0) + logger.debug(f"Calculated total pages: {total_pages}") + + if total_pages is None: # Fallback if total_records couldn't be parsed + last_page_link = pagination_div.select_one("li.PagedList-skipToLast a") + if last_page_link and last_page_link.has_attr('href'): + qs = parse_qs(urlparse(last_page_link['href']).query) + if 'page' in qs and qs['page']: + try: + total_pages = int(qs['page'][0]) + logger.debug(f"Total pages found from 'Last >>' link: {total_pages}") + except ValueError: + logger.warning(f"Could not convert page value from 'Last >>' link to int: {qs['page'][0]}") + elif total_records == 0 : total_pages = 0 # If no records, 0 pages + elif total_records is not None and total_records > 0 : total_pages = 1 # If records exist but no last page link (e.g. single page) + else: logger.warning("'Last >>' link not found in pagination section.") + + decision_tables_container = soup.find("div", id="kararList") + if not decision_tables_container: + logger.warning("`div#kararList` (decision list container) not found. HTML structure might have changed or no decisions on this page.") + else: + decision_tables = decision_tables_container.find_all("table", class_="equalDivide") + logger.info(f"Found {len(decision_tables)} 'table' elements with class='equalDivide' for parsing.") + + if not decision_tables and total_records is not None and total_records > 0 : + logger.warning(f"Page indicates {total_records} records but no decision tables found with class='equalDivide'.") + + for idx, table in enumerate(decision_tables): + logger.debug(f"Processing table {idx + 1}...") + try: + rows = table.find_all("tr") + if len(rows) != 3: + logger.warning(f"Table {idx + 1} has an unexpected number of rows ({len(rows)} instead of 3). Skipping. HTML snippet:\n{table.prettify()[:500]}") + continue + + # Row 1: Publication Date, Decision Number, Related Cases Link + td_elements_r1 = rows[0].find_all("td") + pub_date = td_elements_r1[0].get_text(strip=True) if len(td_elements_r1) > 0 else None + dec_num = td_elements_r1[1].get_text(strip=True) if len(td_elements_r1) > 1 else None + + related_cases_link_tag = td_elements_r1[2].find("a", href=True) if len(td_elements_r1) > 2 else None + related_cases_url_str: Optional[str] = None + karar_id_from_related: Optional[str] = None + if related_cases_link_tag and related_cases_link_tag.has_attr('href'): + related_cases_url_str = urljoin(self.BASE_URL, related_cases_link_tag['href']) + qs_related = parse_qs(urlparse(related_cases_link_tag['href']).query) + if 'kararId' in qs_related and qs_related['kararId']: + karar_id_from_related = qs_related['kararId'][0] + + # Row 2: Decision Date, Decision Type + td_elements_r2 = rows[1].find_all("td") + dec_date = td_elements_r2[0].get_text(strip=True) if len(td_elements_r2) > 0 else None + dec_type_text = td_elements_r2[1].get_text(strip=True) if len(td_elements_r2) > 1 else None + + # Row 3: Title and Main Decision Link + title_cell = rows[2].find("td", colspan="5") + decision_link_tag = title_cell.find("a", href=True) if title_cell else None + + title_text: Optional[str] = None + decision_landing_url_str: Optional[str] = None + karar_id_from_main_link: Optional[str] = None + + if decision_link_tag and decision_link_tag.has_attr('href'): + title_text = decision_link_tag.get_text(strip=True) + href_val = decision_link_tag['href'] + if href_val.startswith(self.DECISION_LANDING_PATH_TEMPLATE + "?kararId="): # Ensure it's a decision link + decision_landing_url_str = urljoin(self.BASE_URL, href_val) + qs_main = parse_qs(urlparse(href_val).query) + if 'kararId' in qs_main and qs_main['kararId']: + karar_id_from_main_link = qs_main['kararId'][0] + else: + logger.warning(f"Table {idx+1} decision link has unexpected format: {href_val}") + else: + logger.warning(f"Table {idx+1} could not find title/decision link tag.") + + current_karar_id = karar_id_from_main_link or karar_id_from_related + + if not current_karar_id: + logger.warning(f"Table {idx+1} Karar ID not found. Skipping. Title (if any): {title_text}") + continue + + # Convert string URLs to HttpUrl for the model + final_decision_url = HttpUrl(decision_landing_url_str) if decision_landing_url_str else None + final_related_cases_url = HttpUrl(related_cases_url_str) if related_cases_url_str else None + + processed_decisions.append(RekabetDecisionSummary( + publication_date=pub_date, decision_number=dec_num, decision_date=dec_date, + decision_type_text=dec_type_text, title=title_text, + decision_url=final_decision_url, + karar_id=current_karar_id, + related_cases_url=final_related_cases_url + )) + logger.debug(f"Table {idx+1} parsed successfully: Karar ID '{current_karar_id}', Title '{title_text[:50] if title_text else 'N/A'}...'") + + except Exception as e: + logger.warning(f"RekabetKurumuApiClient: Error parsing decision summary {idx+1}: {e}. Problematic Table HTML:\n{table.prettify()}", exc_info=True) + continue + + return RekabetSearchResult( + decisions=processed_decisions, total_records_found=total_records, + retrieved_page_number=params.page, total_pages=total_pages if total_pages is not None else 0 + ) + + async def _extract_pdf_url_and_landing_page_metadata(self, karar_id: str, landing_page_html: str, landing_page_url: str) -> Dict[str, Any]: + soup = BeautifulSoup(landing_page_html, 'html.parser') + data: Dict[str, Any] = { + "pdf_url": None, + "title_on_landing_page": soup.title.string.strip() if soup.title and soup.title.string else f"Rekabet Kurumu Kararı {karar_id}", + } + # This part needs to be robust and specific to Rekabet Kurumu's landing page structure. + # Look for common patterns: direct links, download buttons, embedded viewers. + pdf_anchor = soup.find("a", href=re.compile(r"\.pdf(\?|$)", re.IGNORECASE)) # Basic PDF link + if not pdf_anchor: # Try other common patterns if the basic one fails + # Example: Look for links with specific text or class + pdf_anchor = soup.find("a", string=re.compile(r"karar metni|pdf indir", re.IGNORECASE)) + + if pdf_anchor and pdf_anchor.has_attr('href'): + pdf_path = pdf_anchor['href'] + data["pdf_url"] = urljoin(landing_page_url, pdf_path) + logger.info(f"PDF link found on landing page (): {data['pdf_url']}") + else: + iframe_pdf = soup.find("iframe", src=re.compile(r"\.pdf(\?|$)", re.IGNORECASE)) + if iframe_pdf and iframe_pdf.has_attr('src'): + pdf_path = iframe_pdf['src'] + data["pdf_url"] = urljoin(landing_page_url, pdf_path) + logger.info(f"PDF link found on landing page (