add asgi support

This commit is contained in:
saidsurucu
2025-06-26 16:22:27 +03:00
parent ad0c9834e7
commit 262ef5bef5
14 changed files with 1446 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
# Yargı MCP Server Environment Configuration
# Copy this file to .env and customize as needed
# Server Configuration
HOST=0.0.0.0
PORT=8000
LOG_LEVEL=info
# CORS Configuration
# Comma-separated list of allowed origins
# Use * to allow all origins (not recommended for production)
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080,https://yourdomain.com
# Authentication (optional)
# Uncomment and set to enable token-based authentication
# API_TOKEN=your-secret-token-here
# Worker Configuration
# Number of worker processes (for production)
# WORKERS=4
# SSL Configuration (optional)
# SSL_CERT_FILE=/path/to/cert.pem
# SSL_KEY_FILE=/path/to/key.pem
# Database Timeouts (seconds)
# Adjust based on your network conditions
YARGITAY_TIMEOUT=60
DANISTAY_TIMEOUT=60
BEDESTEN_TIMEOUT=60
ANAYASA_TIMEOUT=90
KIK_TIMEOUT=45
REKABET_TIMEOUT=45
UYUSMAZLIK_TIMEOUT=30
EMSAL_TIMEOUT=60
# Development Settings
# Enable debug mode (not for production)
# DEBUG=false
# Monitoring (optional)
# Sentry DSN for error tracking
# SENTRY_DSN=https://your-sentry-dsn@sentry.io/project-id
# OpenTelemetry Configuration (optional)
# OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
# OTEL_SERVICE_NAME=yargi-mcp-server
+12
View File
@@ -168,3 +168,15 @@ fast-mcp-docs.md
debug_*
test_*
CLAUDE.md
# ASGI/Deployment files
ssl/
*.pem
*.key
*.crt
# Docker volumes
redis-data/
# Production logs
logs/*.log.*
+86
View File
@@ -0,0 +1,86 @@
# Multi-stage Dockerfile for Yargı MCP Server
# Build stage
FROM python:3.12-slim as builder
# Install build dependencies
RUN apt-get update && apt-get install -y \
gcc \
g++ \
git \
&& rm -rf /var/lib/apt/lists/*
# Set working directory
WORKDIR /app
# Copy requirements first for better caching
COPY pyproject.toml ./
COPY README.md ./
# Install dependencies
RUN pip install --no-cache-dir uv && \
uv pip install --system --no-cache-dir .
# Runtime stage
FROM python:3.12-slim
# Install runtime dependencies
RUN apt-get update && apt-get install -y \
# Required for Playwright
libnss3 \
libnspr4 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdrm2 \
libdbus-1-3 \
libatspi2.0-0 \
libx11-6 \
libxcomposite1 \
libxdamage1 \
libxext6 \
libxfixes3 \
libxrandr2 \
libgbm1 \
libxcb1 \
libxkbcommon0 \
libpango-1.0-0 \
libcairo2 \
libasound2 \
&& rm -rf /var/lib/apt/lists/*
# Create non-root user
RUN useradd -m -u 1000 mcp && \
mkdir -p /app && \
chown -R mcp:mcp /app
# Set working directory
WORKDIR /app
# Copy from builder
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
# Copy application code
COPY --chown=mcp:mcp . .
# Install Playwright browsers
RUN playwright install chromium
# Switch to non-root user
USER mcp
# Expose port
EXPOSE 8000
# Set environment variables
ENV PYTHONUNBUFFERED=1
ENV HOST=0.0.0.0
ENV PORT=8000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD python -c "import httpx; httpx.get('http://localhost:8000/health').raise_for_status()"
# Run the ASGI server
CMD ["uvicorn", "asgi_app:app", "--host", "0.0.0.0", "--port", "8000"]
+1
View File
@@ -0,0 +1 @@
web: uvicorn asgi_app:app --host 0.0.0.0 --port $PORT
+26
View File
@@ -166,6 +166,32 @@ Bedesten API Bedesten API Dual/Triple API Norm+Bireysel API
- Kesin arama: `"\"mülkiyet kararı\""` (tam cümle olarak)
- Daha kesin sonuçlar için hukuki terimler ve kavramlar
---
🌐 **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.
+102
View File
@@ -0,0 +1,102 @@
"""
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.
Usage:
uvicorn asgi_app:app --host 0.0.0.0 --port 8000
Or with custom transport:
uvicorn asgi_app:sse_app --host 0.0.0.0 --port 8000
"""
import os
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse, PlainTextResponse
# Import the main MCP app
from mcp_server_main import app as mcp_server
# Add a health check endpoint
@mcp_server.custom_route("/health", methods=["GET"])
async def health_check(request: Request) -> JSONResponse:
"""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)
})
@mcp_server.custom_route("/", methods=["GET"])
async def root(request: Request) -> JSONResponse:
"""Root endpoint with service information"""
return JSONResponse({
"service": "Yargı MCP Server",
"description": "MCP server for Turkish legal databases",
"endpoints": {
"mcp": "/mcp/",
"health": "/health",
"status": "/status"
},
"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)",
"Bedesten API (Multiple courts)"
]
})
@mcp_server.custom_route("/status", methods=["GET"])
async def status(request: Request) -> JSONResponse:
"""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"
})
# Configure CORS middleware
cors_origins = os.getenv("ALLOWED_ORIGINS", "*").split(",")
custom_middleware = [
Middleware(
CORSMiddleware,
allow_origins=cors_origins,
allow_credentials=True,
allow_methods=["GET", "POST", "OPTIONS"],
allow_headers=["Content-Type", "Authorization", "X-Request-ID"],
),
]
# Create ASGI apps with different transports
# Recommended: Streamable HTTP transport
app = mcp_server.http_app(
path="/mcp",
middleware=custom_middleware
)
# Alternative: SSE transport (for compatibility)
sse_app = mcp_server.http_app(
path="/sse",
transport="sse",
middleware=custom_middleware
)
# Export for uvicorn
__all__ = ["app", "sse_app"]
+66
View File
@@ -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:
+428
View File
@@ -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 <PID>
```
### İ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
+274
View File
@@ -0,0 +1,274 @@
"""
FastAPI integration for Yargı MCP Server
This module demonstrates how to integrate the Yargı MCP server
with a FastAPI application, providing additional REST API endpoints
alongside the MCP functionality.
Usage:
uvicorn fastapi_app:app --host 0.0.0.0 --port 8000
"""
import os
from typing import List, Dict, Any, Optional
from datetime import datetime
from fastapi import FastAPI, HTTPException, Query, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from pydantic import BaseModel
# Import the main MCP app
from mcp_server_main import app as mcp_server
from asgi_app import custom_middleware
# 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",
description="Turkish Legal Database MCP Server with REST API",
version="0.1.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
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()
@app.get("/", response_model=ServerInfo)
async def root():
"""Get server information"""
return ServerInfo(
name="Yargı MCP Server",
version="0.1.0",
description="MCP server for Turkish legal databases",
tools_count=len(mcp_server._tool_manager.tools),
databases=[
"Yargıtay (Court of Cassation)",
"Danıştay (Council of State)",
"Emsal (Precedent)",
"Uyuşmazlık Mahkemesi (Court of Jurisdictional Disputes)",
"Anayasa Mahkemesi (Constitutional Court)",
"Kamu İhale Kurulu (Public Procurement Authority)",
"Rekabet Kurumu (Competition Authority)",
"Bedesten API (Multiple courts)"
],
mcp_endpoint="/mcp-server/mcp/",
api_docs="/docs"
)
@app.get("/health", response_model=HealthCheck)
async def health_check():
"""Health check endpoint"""
uptime = (datetime.now() - SERVER_START_TIME).total_seconds()
return HealthCheck(
status="healthy",
timestamp=datetime.now(),
uptime_seconds=uptime,
tools_operational=len(mcp_server._tool_manager.tools) > 0
)
@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 available MCP tools"""
tools = []
for tool in mcp_server._tool_manager.tools.values():
# Apply filters if provided
if search and search.lower() not in tool.name.lower() and search.lower() not in tool.description.lower():
continue
if database:
db_lower = database.lower()
if db_lower not in tool.name.lower() and db_lower not in tool.description.lower():
continue
# Extract parameter schema
params = {}
if hasattr(tool, 'schema') and tool.schema:
if hasattr(tool.schema, 'parameters'):
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
@app.get("/api/tools/{tool_name}", response_model=ToolInfo)
async def get_tool(tool_name: str):
"""Get detailed information about a specific tool"""
tool = mcp_server._tool_manager.tools.get(tool_name)
if not tool:
raise HTTPException(status_code=404, detail=f"Tool '{tool_name}' not found")
# Extract parameter schema
params = {}
if hasattr(tool, 'schema') and tool.schema:
if hasattr(tool.schema, 'parameters'):
params = tool.schema.parameters
elif hasattr(tool.schema, '__annotations__'):
params = {k: str(v) for k, v in tool.schema.__annotations__.items()}
return ToolInfo(
name=tool.name,
description=tool.description,
parameters=params
)
@app.get("/api/databases")
async def list_databases():
"""List all supported legal databases"""
databases = {
"yargitay": {
"name": "Yargıtay (Court of Cassation)",
"description": "Supreme court for civil and criminal cases",
"tools": ["search_yargitay_detailed", "get_yargitay_document_markdown",
"search_yargitay_bedesten", "get_yargitay_bedesten_document_markdown"],
"chambers": 52
},
"danistay": {
"name": "Danıştay (Council of State)",
"description": "Supreme administrative court",
"tools": ["search_danistay_by_keyword", "search_danistay_detailed",
"get_danistay_document_markdown", "search_danistay_bedesten",
"get_danistay_bedesten_document_markdown"],
"chambers": 27
},
"emsal": {
"name": "Emsal (Precedent)",
"description": "Precedent decisions from various courts",
"tools": ["search_emsal_detailed_decisions", "get_emsal_document_markdown"]
},
"uyusmazlik": {
"name": "Uyuşmazlık Mahkemesi",
"description": "Court of Jurisdictional Disputes",
"tools": ["search_uyusmazlik_decisions", "get_uyusmazlik_document_markdown_from_url"]
},
"anayasa": {
"name": "Anayasa Mahkemesi (Constitutional Court)",
"description": "Constitutional review and individual applications",
"tools": ["search_anayasa_norm_denetimi_decisions",
"get_anayasa_norm_denetimi_document_markdown",
"search_anayasa_bireysel_basvuru_report",
"get_anayasa_bireysel_basvuru_document_markdown"]
},
"kik": {
"name": "Kamu İhale Kurulu",
"description": "Public Procurement Authority",
"tools": ["search_kik_decisions", "get_kik_document_markdown"]
},
"rekabet": {
"name": "Rekabet Kurumu",
"description": "Competition Authority",
"tools": ["search_rekabet_kurumu_decisions", "get_rekabet_kurumu_document"]
},
"bedesten": {
"name": "Bedesten API",
"description": "Unified API for multiple courts",
"tools": ["search_yerel_hukuk_bedesten", "get_yerel_hukuk_bedesten_document_markdown",
"search_istinaf_hukuk_bedesten", "get_istinaf_hukuk_bedesten_document_markdown",
"search_kyb_bedesten", "get_kyb_bedesten_document_markdown"]
}
}
return JSONResponse(content=databases)
@app.get("/api/stats")
async def get_statistics():
"""Get server statistics"""
uptime = (datetime.now() - SERVER_START_TIME).total_seconds()
# Count tools by database
tool_counts = {}
for tool in mcp_server._tool_manager.tools.values():
for db in ["yargitay", "danistay", "emsal", "uyusmazlik", "anayasa", "kik", "rekabet", "bedesten"]:
if db in tool.name.lower():
tool_counts[db] = tool_counts.get(db, 0) + 1
break
return JSONResponse({
"server": {
"uptime_seconds": uptime,
"start_time": SERVER_START_TIME.isoformat(),
"version": "0.1.0"
},
"tools": {
"total": len(mcp_server._tool_manager.tools),
"by_database": tool_counts
},
"capabilities": {
"total_chambers": 79, # 52 Yargıtay + 27 Danıştay
"date_filtering": True,
"exact_phrase_search": True,
"dual_api_support": True
}
})
# Add a simple authentication example (optional)
# Uncomment to enable basic token authentication
"""
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
security = HTTPBearer()
async def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
token = credentials.credentials
expected_token = os.getenv("API_TOKEN")
if expected_token and token != expected_token:
raise HTTPException(status_code=401, detail="Invalid authentication token")
return token
# Then add Depends(verify_token) to any endpoint that needs protection
# Example: async def list_tools(..., token: str = Depends(verify_token)):
"""
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
+94
View File
@@ -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
# }
}
+14
View File
@@ -15,6 +15,20 @@ dependencies = [
"pypdf>=5.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",
]
[project.scripts]
yargi-mcp = "mcp_server_main:main"
+18
View File
@@ -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"
}
}
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""
Standalone ASGI server runner for Yargı MCP
This script provides a simple way to run the Yargı MCP server
as a web service using uvicorn.
Usage:
python run_asgi.py
python run_asgi.py --host 0.0.0.0 --port 8080
python run_asgi.py --reload # For development
"""
import os
import sys
import argparse
import logging
from pathlib import Path
# Add project root to Python path
sys.path.insert(0, str(Path(__file__).parent))
try:
import uvicorn
except ImportError:
print("Error: uvicorn is not installed.")
print("Please install it with: pip install uvicorn")
sys.exit(1)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
def main():
parser = argparse.ArgumentParser(
description="Run Yargı MCP server as an ASGI web service"
)
parser.add_argument(
"--host",
type=str,
default=os.getenv("HOST", "127.0.0.1"),
help="Host to bind to (default: 127.0.0.1)"
)
parser.add_argument(
"--port",
type=int,
default=int(os.getenv("PORT", "8000")),
help="Port to bind to (default: 8000)"
)
parser.add_argument(
"--reload",
action="store_true",
help="Enable auto-reload for development"
)
parser.add_argument(
"--transport",
choices=["http", "sse"],
default="http",
help="Transport type (default: http)"
)
parser.add_argument(
"--log-level",
choices=["debug", "info", "warning", "error"],
default=os.getenv("LOG_LEVEL", "info").lower(),
help="Log level (default: info)"
)
parser.add_argument(
"--workers",
type=int,
default=1,
help="Number of worker processes (default: 1)"
)
args = parser.parse_args()
# Select app based on transport
app_name = "asgi_app:app" if args.transport == "http" else "asgi_app:sse_app"
# Configure uvicorn
config = {
"app": app_name,
"host": args.host,
"port": args.port,
"log_level": args.log_level,
"reload": args.reload,
"access_log": True,
}
# Add workers only if not in reload mode
if not args.reload and args.workers > 1:
config["workers"] = args.workers
# Print startup information
print(f"Starting Yargı MCP server...")
print(f"Host: {args.host}")
print(f"Port: {args.port}")
print(f"Transport: {args.transport}")
print(f"Log level: {args.log_level}")
if args.reload:
print("Auto-reload: enabled")
else:
print(f"Workers: {args.workers}")
print(f"\nServer will be available at: http://{args.host}:{args.port}")
print(f"MCP endpoint: http://{args.host}:{args.port}/mcp/")
print(f"Health check: http://{args.host}:{args.port}/health")
print(f"API status: http://{args.host}:{args.port}/status")
print("\nPress CTRL+C to stop the server\n")
# Run uvicorn
try:
uvicorn.run(**config)
except KeyboardInterrupt:
print("\nShutting down server...")
sys.exit(0)
if __name__ == "__main__":
main()
+159
View File
@@ -0,0 +1,159 @@
"""
Starlette integration example for Yargı MCP Server
This module demonstrates how to integrate the Yargı MCP server
with a Starlette application, including authentication middleware
and custom routing.
Usage:
uvicorn starlette_app:app --host 0.0.0.0 --port 8000
"""
import os
from starlette.applications import Starlette
from starlette.routing import Mount, Route
from starlette.requests import Request
from starlette.responses import JSONResponse, PlainTextResponse, RedirectResponse
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.authentication import (
AuthenticationBackend, AuthCredentials, SimpleUser, AuthenticationError
)
# Import the main MCP app
from mcp_server_main import app as mcp_server
# Simple token authentication backend
class TokenAuthBackend(AuthenticationBackend):
async def authenticate(self, request):
auth_header = request.headers.get("Authorization")
expected_token = os.getenv("API_TOKEN")
# Skip auth for health check and public endpoints
if request.url.path in ["/health", "/", "/login"]:
return None
if not expected_token:
# No token configured, allow all
return AuthCredentials(["authenticated"]), SimpleUser("anonymous")
if not auth_header:
raise AuthenticationError("Authorization header required")
try:
scheme, token = auth_header.split()
if scheme.lower() != "bearer":
raise AuthenticationError("Invalid authentication scheme")
if token != expected_token:
raise AuthenticationError("Invalid token")
return AuthCredentials(["authenticated"]), SimpleUser("user")
except ValueError:
raise AuthenticationError("Invalid authorization header format")
# Homepage
async def homepage(request: Request):
return JSONResponse({
"service": "Yargı MCP Server",
"version": "0.1.0",
"endpoints": {
"mcp": "/mcp-server/mcp/",
"api": "/api/",
"health": "/health"
}
})
# API info endpoint
async def api_info(request: Request):
if not request.user.is_authenticated:
return JSONResponse({"error": "Authentication required"}, status_code=401)
return JSONResponse({
"authenticated_as": request.user.display_name,
"available_tools": len(mcp_server._tool_manager.tools),
"databases": [
"Yargıtay", "Danıştay", "Emsal", "Uyuşmazlık",
"Anayasa", "KIK", "Rekabet", "Bedesten"
]
})
# Health check
async def health_check(request: Request):
return JSONResponse({
"status": "healthy",
"service": "Yargı MCP Server"
})
# Login example (returns token for demo)
async def login(request: Request):
token = os.getenv("API_TOKEN", "demo-token")
return JSONResponse({
"message": "Use this token in Authorization header",
"example": f"Authorization: Bearer {token}",
"note": "Set API_TOKEN environment variable to change token"
})
# Create MCP ASGI app
mcp_app = mcp_server.http_app(path='/mcp')
# Configure middleware
middleware = [
Middleware(
CORSMiddleware,
allow_origins=os.getenv("ALLOWED_ORIGINS", "*").split(","),
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
),
Middleware(AuthenticationMiddleware, backend=TokenAuthBackend()),
]
# Create routes
routes = [
Route("/", homepage),
Route("/health", health_check),
Route("/login", login),
Route("/api/info", api_info),
Mount("/mcp-server", app=mcp_app),
]
# Create Starlette app
app = Starlette(
routes=routes,
middleware=middleware,
lifespan=mcp_app.lifespan
)
# Nested mount example
def create_nested_app():
"""Example of nested mounting for complex routing structures"""
# Create inner app with MCP
inner_app = Starlette(
routes=[Mount("/services", app=mcp_app)],
middleware=middleware
)
# Create outer app
outer_app = Starlette(
routes=[
Route("/", homepage),
Mount("/v1", app=inner_app),
],
lifespan=mcp_app.lifespan
)
# MCP would be available at /v1/services/mcp/
return outer_app
# Export both apps
nested_app = create_nested_app()
if __name__ == "__main__":
import uvicorn
print("Starting Starlette app with authentication...")
print("Set API_TOKEN environment variable to enable authentication")
print("Example: API_TOKEN=secret-token python starlette_app.py")
uvicorn.run(app, host="0.0.0.0", port=8000)