Skip to main content
Glama
ayzekhdawy

IslamicCorpusMCP

by ayzekhdawy
README.md
# İslami Metin Korpusu — MCP Sunucusu

[![CI](https://github.com/ayzekhdawy/IslamicCorpusMCP/actions/workflows/ci.yml/badge.svg)](https://github.com/ayzekhdawy/IslamicCorpusMCP/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/ayzekhdawy/IslamicCorpusMCP/blob/main/LICENSE)
[![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)

Kur'an + Kütüb-i Tis'a (hadis) + Tefsir kaynaklarını tek bir ilişkisel MySQL
veritabanında, ortak sayısal ayet anahtarı üzerinden sorgulanabilir hale
getirir. İki katman:

1. **Veri temeli (ingest)** — kaynak veriyi indir, normalize et, eşle, MySQL'e yükle.
2. **MCP sorgu katmanı** — `islamic_corpus_mcp/`, güvenli açık kaynak MCP sunucusu
   (stdio + SSE + streamable-http). Herhangi MCP-uyumlu araç (Claude, Cline,
   Continue, kendi istemciniz) üzerinden **Kur'an ile sohbet**.

---

# MCP Sorgu Katmanı (`islamic_corpus_mcp/`)

> Kur'an, Tefsir ve Hadis korpusunu **Model Context Protocol (MCP)** üzerinden
> sunan güvenli, açık kaynak sunucu. **Anti-hallucination** tasarım: her sonuç
> yapılandırılmış Pydantic modelleriyle doğrudan veritabanından gelir; model
> uydurmaz, araçları çağırır ve gerçek metni alır.

## İçerik

| Korpus | Kapsam |
|--------|--------|
| Kur'an | 114 sure, 6236 ayet (Osmani + S2 normalize) |
| Tefsir | 9 kaynak, ~218530 kayıt (%98.64 ayet eşleşme) |
| Hadis | Kutub-i Tis'a (9 kitap), ~40943 hadis (Arapça + İngilizce) |
| Meta | Sure adı çözümleme, Arapça normalizasyon, DB istatistikleri |

## Araçlar (16)

### Kur'an
- `quran_list_surahs` — 114 sure listesi
- `quran_fetch_ayah(surah_id, ayah_no)` — belirli ayet
- `quran_fetch_ayah_range(surah_id, start, end)` — ayet aralığı
- `quran_search(query, mode, surah_id?, limit, offset)` — `normalized`/`uthmani`
- `quran_get_surah_info(surah_id)` — sure detayı + ayet listesi

### Tefsir
- `tafsir_fetch(surah_id, ayah_no, source_book?, match_status?, limit, offset)`
- `tafsir_search(query, source_book?, match_status?, limit, offset)` — FULLTEXT
- `tafsir_list_books` — tefsir kaynakları + istatistik
- `tafsir_list_unmatched(source_book?, reason?, limit, offset)`

### Hadis
- `hadith_list_books` — 9 kitap
- `hadith_fetch(book_id, number)` — belirli hadis
- `hadith_search(query, lang, book_id?, limit, offset)` — `ar`/`en`
- `hadith_list_by_chapter(book_id, chapter_id, limit, offset)`

### Meta
- `resolve_surah(name)` — sure adı → surah_id (ar/tr/en)
- `normalize_arabic(text)` — S2 agresif normalizasyon
- `get_db_stats` — veritabanı istatistikleri

### Kaynaklar (Resources)
`quran://schema`, `quran://surahs`, `quran://stats`, `quran://tafsir-books`

### Prompt'lar
`Study Ayah`, `Compare Tafsirs`, `Surah Overview`, `Hadith Analysis`

## Kurulum

```bash
pip install -e ".[dev]"   # veya pip install -e .
```

Python ≥ 3.10. Çalıştırılabilir: `islamic-corpus-mcp`.

## Veritabanı bağlama

MySQL/MariaDB gerekir; şema `quran_surah`, `quran_ayah`,
`quran_ayah.text_normalized`, `tafsir`, `hadith`, `hadith_book`,
`surah_name_alias`, `tafsir_unmatched_raw` tablolarını içerir
(`quran://schema` kaynağı ile görülebilir). Şema kurulumu için aşağıdaki
**Veri temeli** bölümüne bakın.

**Güvenlik:** read-only kullanıcı oluşturun.

```sql
CREATE USER 'islam_readonly'@'%' IDENTIFIED BY '<parola>';
GRANT SELECT ON islam_corpus.* TO 'islam_readonly'@'%';
FLUSH PRIVILEGES;
```

`.env.example`'i `.env` olarak kopyalayıp doldurun:

```ini
ISLAM_DB_HOST=127.0.0.1
ISLAM_DB_PORT=3306
ISLAM_DB_USER=islam_readonly
ISLAM_DB_PASS=<parola>
ISLAM_DB_NAME=islam_corpus
```

### Railway public MySQL (production)

Railway'nin `mysql.railway.internal` adresi dışarıdan erişilemez. Railway
dashboard'ında **Connect** sekmesinden public proxy adresini alın ve
`ISLAM_DB_HOST` olarak kullanın.

## Ortam değişkenleri

| Değişken | Default | Açıklama |
|----------|---------|----------|
| `ISLAM_DB_HOST` | `127.0.0.1` | MySQL sunucu |
| `ISLAM_DB_PORT` | `3306` | MySQL port |
| `ISLAM_DB_USER` | `islam_readonly` | DB kullanıcı (root = uyarı) |
| `ISLAM_DB_PASS` | boş | DB parola |
| `ISLAM_DB_NAME` | `islam_corpus` | DB adı |
| `MCP_PUBLIC_URL` | — | **Remote zorunlu** public Railway domaini (OAuth issuer/resource) |
| `MCP_AUTH_TOKEN` | — | Opsiyonel statik Bearer (Claude Desktop/Cline fallback) |
| `MCP_CORS_ORIGINS` | boş (deny-all) | Virgülle ayrılmış izinli origin'ler |
| `MCP_RATE_LIMIT_REQUESTS` | `60` | IP başına pencere içi istek |
| `MCP_RATE_LIMIT_WINDOW` | `60` | Pencere (saniye) |
| `FASTMCP_HOST` | `0.0.0.0` | Uzak transport host |
| `FASTMCP_PORT` | `8080` | Uzak transport port |

## Transport modları

| Mod | Komut | Uç nokta | Auth | Kullanım |
|-----|-------|----------|------|----------|
| **stdio** (default) | `islamic-corpus-mcp` | stdin/stdout | yok | Claude Desktop, yerel |
| **SSE** | `MCP_PUBLIC_URL=… islamic-corpus-mcp --sse` | `/sse` + `/messages/` | OAuth/statik Bearer | eski MCP istemcileri |
| **streamable-http** | `MCP_PUBLIC_URL=… islamic-corpus-mcp --http` | `/mcp` | OAuth 2.1 (ChatGPT) | **önerilen** modern |

> **ChatGPT** streamable-http (`--http`) gerektirir; statik bearer token
> **gönderemez** — gerçek OAuth 2.1 akışı ister. Bu yüzden uzak transport artık
> tam bir OAuth 2.1 yetki sunucusu içerir (dinamik istemci kaydı + PKCE +
> `/authorize` `/token` `/register` `/revoke`). `MCP_PUBLIC_URL` public
> domaininize ayarlanmalı; aksi halde sahte issuer ChatGPT akışını bozar
> ("We couldn't connect your account").

Opsiyonel statik token (Claude Desktop/Cline gibi statik-bearer istemciler):

```bash
export MCP_AUTH_TOKEN=$(python -c "import secrets;print(secrets.token_urlsafe(32))")
```

> `MCP_PUBLIC_URL` olmadan `--sse`/`--http` başlamaz (`exit(1)`). stdio'da auth yoktur.

### TLS

Self-signed `--https` kaldırıldı. Üretimde bir reverse-proxy TLS sonlandırır.

**Caddy örneği** (`Caddyfile`):
```
mcp.example.com {
    reverse_proxy 127.0.0.1:8080
}
```
Caddy otomatik Let's Encrypt sertifikası alır; `X-Forwarded-For` gönderir
(rate limit için doğru IP kaynağı).

### CORS

Tarayıcı tabanlı MCP istemcisi için izin ver:

```bash
export MCP_CORS_ORIGINS=https://claude.ai,https://app.example.com
```

Boş bırakılırsa deny-all (hiçbir tarayıcı origin'ine header gönderilmez).

## İstemci yapılandırması

### Claude Desktop (stdio — yerel)

`claude_desktop_config.json`:
```json
{
  "mcpServers": {
    "islamic-corpus": {
      "command": "islamic-corpus-mcp",
      "env": {
        "ISLAM_DB_HOST": "127.0.0.1",
        "ISLAM_DB_USER": "islam_readonly",
        "ISLAM_DB_PASS": "<parola>",
        "ISLAM_DB_NAME": "islam_corpus"
      }
    }
  }
}
```

### Claude Desktop / Code (uzak SSE)

```json
{
  "mcpServers": {
    "islamic-corpus": {
      "url": "https://mcp.example.com/sse",
      "headers": { "Authorization": "Bearer <MCP_AUTH_TOKEN>" }
    }
  }
}
```

### streamable-http (önerilen)

```json
{
  "mcpServers": {
    "islamic-corpus": {
      "url": "https://mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer <MCP_AUTH_TOKEN>" },
      "type": "http"
    }
  }
}
```

### ChatGPT (özel connector — OAuth 2.1)

ChatGPT statik bearer **gönderemez**; gerçek OAuth 2.1 akışı kullanır. Sunucu
artık tam bir OAuth yetki sunucusudur, bu yüzden connector kurulumu basittir:

1. Railway/dağıtım env'inde **`MCP_PUBLIC_URL=https://mcp.example.com`**
   ayarlayın (kendi public domaininiz) ve `--http` ile çalıştırın (Dockerfile varsayılanı).
2. ChatGPT → Settings → Connectors → **Add custom connector**.
3. **Server URL**: `https://mcp.example.com/mcp`
4. **Authorization type**: **OAuth 2.0** (ChatGPT `/.well-known/oauth-authorization-server`
   metadatasını okuyup `/register` + `/authorize` + `/token` uç noktalarını otomatik keşfeder;
   client_id/secret girmeniz gerekmez — dinamik kayıt). "Sign in" tıklayın; sunucu
   tek-kullanıcı olduğu için otomatik onaylar (consent ekranı yok).
5. Token'lar bellek-içi 1 saat access / 30 gün refresh yaşar; yenileme otomatik.

> Tek process/replica için bellek-içi token deposu yeterlidir. Çok instance'a
> ölçeklemek için Redis/postgres-backed bir `OAuthAuthorizationServerProvider` gerekir.

## Dağıtım

### Docker

```bash
docker build -t islamic-corpus-mcp .
docker run -p 8080:8080 \
  -e MCP_PUBLIC_URL=https://mcp.example.com \
  -e ISLAM_DB_HOST=db.example.com \
  -e ISLAM_DB_USER=islam_readonly \
  -e ISLAM_DB_PASS=<parola> \
  -e ISLAM_DB_NAME=islam_corpus \
  -e MCP_CORS_ORIGINS=https://claude.ai \
  islamic-corpus-mcp
```

### Railway / fly.io

Kalıcı container (serverless değil) — streamable-http long-lived destekler.
Platformun `PORT` env'ini kullanır (Dockerfile `--http` ile başlatır). Platform
yönetilen TLS kullanır (reverse-proxy gerekmez). **`MCP_PUBLIC_URL`'i Railway
service değişkenlerine ekleyin** (public domain, örn. `https://mcp.example.com`)
— ChatGPT OAuth akışı için zorunlu.

## Test & lint

```bash
pytest tests/ -v      # 19 test (DB gerektirmez)
ruff check islamic_corpus_mcp/ tests/
```

## Güvenlik

Tüm güvenlik notları için [SECURITY.md](SECURITY.md). Özet:
- Remote = zorunlu Bearer token (sabit-zamanlı doğrulama)
- Tüm SQL parametrize (injection yok)
- DB read-only kullanıcı öner
- Pydantic girdi kısıtları (422)
- CORS deny-all varsayılan
- Rate limit IP bazlı (tek-process)
- TLS reverse-proxy ile

## Lisans

[MIT](LICENSE). Korpus verisinin kendisi ayrı lisans/lisanslara tabi olabilir;
kaynaklar için tefsir/hadis derlemelerinin orijinal yayın şartlarına uyun.

---

# Veri Temeli (Faz 1 — ingest)

Aşağıdaki bölüm kaynak veriyi indir, normalize et, eşle ve MySQL'e yükleme
sürecini anlatır. MCP sunucusunu kullanmadan önce veritabanını bu adımlarla
doldurun.

## Dizin yapısı
```
download_data.py         Kaynakları data/ içine indirir (SENIN makinede calisir)
db/schema.sql            Tam MySQL semasi (FK, index, FULLTEXT) — 11 tablo
ingest/
  arabic_normalize.py    Arapca normalize edici — S2 agresif (elif silme)
  db.py                  Ortak MySQL baglanti yardimcisi (PyMySQL)
  ingest_quran.py        Kur'an metni + ceviriler -> kanonik omurga
  ingest_hadith.py       Kutub-i Tis'a (hadith-json) -> hadith tablolari
  ingest_tafsir.py       Tefsir -> ayet esleme + iki gecisli (birebir+fuzzy)
verify_matching.py        MySQL gerektirmeden eslesme dogrulama (saf Python)
reports/
  normalization_report.md  Son eslesme raporu (tam veri, gercek sonuclar)
data/                    Ham veri (download_data.py doldurur; repoya konmaz)
```

## 0) Veriyi indir
```bash
pip install requests huggingface_hub pandas pyarrow
python download_data.py
```
Dosyalar `data/quran`, `data/hadith`, `data/tafsir` altına iner.

## 1) Kurulum
```bash
pip install pymysql pandas pyarrow
mysql -u root -p -e "CREATE DATABASE islam_corpus CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
mysql -u root -p islam_corpus < db/schema.sql
set DB_USER=root
set DB_PASS=***
set DB_NAME=islam_corpus
```

## 2) Eşleşme doğrulama (MySQL gerektirmez)
```bash
python verify_matching.py --sample 5000   # orneklem
python verify_matching.py --full           # tam veri
```

## 3) MySQL'e yükleme sırası
```bash
# Kanonik omurga (ONCE bu)
python ingest/ingest_quran.py --inspect   data/quran/Quran-Data/Quran-Data-main/Quran.json
python ingest/ingest_quran.py --sample 50 --json   data/quran/Quran-Data/Quran-Data-main/Quran.json
python ingest/ingest_quran.py --full --json        data/quran/Quran-Data/Quran-Data-main/Quran.json

# Hadis (kitap basina veya klasor)
python ingest/ingest_hadith.py --inspect  data/hadith/hadith-json/hadith-json-main/db/by_book/the_9_books/bukhari.json
python ingest/ingest_hadith.py --dir --full  data/hadith/hadith-json/hadith-json-main/db/by_book/the_9_books

# Tefsir — STOP & REPORT: once ornekle, orani kontrol et
python ingest/ingest_tafsir.py --inspect  data/tafsir
python ingest/ingest_tafsir.py --sample 2000  data/tafsir
#   -> reports/normalization_report.md'yi incele
python ingest/ingest_tafsir.py --full  data/tafsir
```

## Hangi kaynak hangi tabloya
| Kaynak | Tablo(lar) | Not |
|--------|-----------|-----|
| AbdullahGhanem/quran-database, rn0x/Quran-Data | quran_surah, quran_ayah, quran_translation | Sayisal omurga; kanonik anahtar buradan |
| AhmedBaset/hadith-json | hadith_book, hadith | ANA hadis kaynagi (id/chapterId/arabic/english) |
| LK-Hadith-Corpus | narrator, hadith_narrator | Ravi/isnad; doldurulabildigi kadar (ayri script — TODO) |
| MohamedRashad/Quran-Tafseer | tafsir, tafsir_unmatched_raw | Arapca ad + ayet metni; S2 + iki gecisli esleme |

## Eşleşme politikası (KARARLASTI)
- **Gecis 1 — Birebir (S2 agresif):** normalize_ayah_key() tum elif (ا) karakterlerini siler. Osmani ↔ standart imla farkini notrler. `match_status='matched'`, `confidence=1.000`
- **Gecis 2 — Bulanik (Jaccard >= 0.8):** birebir eslesmeyenler icin token Jaccard benzerligi >= 0.8 ile eslestirme. `match_status='fuzzy'`, `confidence=Jaccard skoru`
- **Eslesmeyen:** silinmez, tahmin edilmez. `match_status='unmatched'`, `surah_id=NULL`, ham hali `tafsir_unmatched_raw`'da

## Gerçek eşleşme sonuçları (tam veri, 218.530 satır)
| Durum | Sayı | Oran |
|-------|------|------|
| matched (birebir) | 179.330 | %82.06 |
| fuzzy (Jaccard>=0.8) | 28.407 | %13.00 |
| unmatched | 10.793 | %4.94 |
| **Toplam eşleşme** | **207.737** | **%95.06** |

Sûre adı eşlemesi: 113/113 = %100.
Sahte-eşleşme riski (S2): 6.141 kanonik anahtardan yalnızca 28'i farklı ayetlere aynı anahtara iniyor (%0.46).

## Bilinçli boş bırakılanlar (bu faz)
- hadith_ayah (hadis–ayet bağı): kaynakta hazır yok — sonraki faz.
- hadith.grade (derece): hadith-json'da yok — kaynak netleşince.
- text_tr (Türkçe tefsir/hadis): **Diyanet metinleri telifli, kullanılmaz.**
  Açık-lisanslı kaynak kararı kullanıcıda.

## Bilinen eksikler / sonraki fazlar
1. LK-Hadith-Corpus'tan narrator/hadith_narrator doldurma (ayrı script)
2. hadith_ayah: hadis–ayet bağı (NLP ile, sonraki faz)
3. Çeviri kaynakları (Türkçe Meal: açık lisanslı kaynak araştırması)
4. MCP sorgu katmanı — ✅ tamamlandı (`islamic_corpus_mcp/`)
5. Okuma arayüzü (`web/` — ayrı)