IslamicCorpusMCP
by ayzekhdawy
README.md
# İslami Metin Korpusu — MCP Sunucusu
[](https://github.com/ayzekhdawy/IslamicCorpusMCP/actions/workflows/ci.yml)
[](https://github.com/ayzekhdawy/IslamicCorpusMCP/blob/main/LICENSE)
[](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ı)This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues