add asgi support
This commit is contained in:
@@ -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
@@ -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
@@ -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"]
|
||||
@@ -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
@@ -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"]
|
||||
@@ -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:
|
||||
@@ -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
@@ -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
@@ -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
|
||||
# }
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
@@ -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()
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user