├── ornek.png ├── .gitattributes ├── requirements.txt ├── hello.py ├── pyproject.toml ├── LICENSE ├── .gitignore ├── README.md ├── yokatlas_pdf_generator.py └── yokatlas_mcp_server.py /ornek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/saidsurucu/yokatlas-mcp/HEAD/ornek.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | fastmcp 2 | yokatlas-py 3 | beautifulsoup4 4 | setuptools 5 | reportlab -------------------------------------------------------------------------------- /hello.py: -------------------------------------------------------------------------------- 1 | def main(): 2 | print("Hello from yokatlas-mcp!") 3 | 4 | 5 | if __name__ == "__main__": 6 | main() 7 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools>=61.0", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | [project] 6 | name = "yokatlas-mcp" 7 | version = "0.1.0" 8 | description = "MCP server for YOKATLAS API - Turkish Higher Education Atlas" 9 | readme = "README.md" 10 | requires-python = ">=3.12" 11 | dependencies = [ 12 | "beautifulsoup4>=4.12.3", 13 | "fastmcp>=2.10.5", 14 | "setuptools>=80.9.0", 15 | "yokatlas-py==0.5.3", 16 | ] 17 | 18 | [project.scripts] 19 | yokatlas-mcp = "yokatlas_mcp_server:main" 20 | 21 | [tool.setuptools] 22 | py-modules = ["yokatlas_mcp_server"] 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 saidsurucu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .nox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | *.py,cover 48 | .hypothesis/ 49 | .pytest_cache/ 50 | cover/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | db.sqlite3-journal 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | .pybuilder/ 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | Pipfile.lock 88 | 89 | # poetry 90 | poetry.lock 91 | 92 | # pdm 93 | .pdm.toml 94 | 95 | # PEP 582 96 | __pypackages__/ 97 | 98 | # Celery stuff 99 | celerybeat-schedule 100 | celerybeat.pid 101 | 102 | # SageMath parsed files 103 | *.sage.py 104 | 105 | # Environments 106 | .env 107 | .venv 108 | env/ 109 | venv/ 110 | ENV/ 111 | env.bak/ 112 | venv.bak/ 113 | 114 | # Spyder project settings 115 | .spyderproject 116 | .spyproject 117 | 118 | # Rope project settings 119 | .ropeproject 120 | 121 | # mkdocs documentation 122 | /site 123 | 124 | # mypy 125 | .mypy_cache/ 126 | .dmypy.json 127 | dmypy.json 128 | 129 | # Pyre type checker 130 | .pyre/ 131 | 132 | # pytype static type analyzer 133 | .pytype/ 134 | 135 | # Cython debug symbols 136 | cython_debug/ 137 | 138 | # PyCharm 139 | .idea/ 140 | 141 | # VS Code 142 | .vscode/ 143 | 144 | # macOS 145 | .DS_Store 146 | 147 | # Claude specific 148 | CLAUDE.md 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # YOKATLAS API MCP Sunucusu 2 | 3 | [![Star History Chart](https://api.star-history.com/svg?repos=saidsurucu/yokatlas-mcp&type=Date)](https://www.star-history.com/#saidsurucu/yokatlas-mcp&Date) 4 | 5 | Bu proje, [YÖKATLAS](https://yokatlas.yok.gov.tr/) verilerine erişimi sağlayan `yokatlas-py` Python kütüphanesini kullanarak bir [FastMCP](https://www.gofastmcp.com/) sunucusu oluşturur. Bu sayede, YÖKATLAS API fonksiyonları, Model Context Protocol (MCP) destekleyen LLM (Büyük Dil Modeli) uygulamaları ve diğer istemciler tarafından araç (tool) olarak kullanılabilir hale gelir. 6 | 7 | ![örnek](./ornek.png) 8 | 9 | ## 🎯 Temel Özellikler 10 | 11 | * YÖKATLAS verilerine programatik erişim için standart bir MCP arayüzü. 12 | * Lisans ve Önlisans program detaylarını getirme. 13 | * Lisans ve Önlisans programları için kapsamlı arama yapabilme (Tercih Sihirbazı). 14 | * Claude Desktop uygulaması ile kolay entegrasyon. 15 | 16 | ## 📋 Ön Gereksinimler 17 | 18 | * **Python Sürümü:** Python 3.12 veya daha yeni bir sürümünün sisteminizde kurulu olması gerekmektedir. Python'ı [python.org](https://www.python.org/downloads/) adresinden indirebilirsiniz. 19 | * **pip:** Python ile birlikte gelen `pip` paket yöneticisinin çalışır durumda olması gerekir. 20 | 21 |
22 | ⚙️ Kurulum Adımları 23 | 24 | ### Hızlı Kurulum (Önerilen) 25 | 26 | Claude Desktop'a entegre etmek için sadece `uv` kurulumuna ihtiyacınız var: 27 | 28 | #### 1. `uv` Kurulumu 29 | `uv`, hızlı bir Python paket yöneticisidir. 30 | 31 | * **macOS ve Linux:** 32 | ```bash 33 | curl -LsSf https://astral.sh/uv/install.sh | sh 34 | ``` 35 | 36 | * **Windows (PowerShell):** 37 | ```bash 38 | powershell -c "irm https://astral.sh/uv/install.ps1 | iex" 39 | ``` 40 | 41 | * **pip ile kurulum:** 42 | ```bash 43 | pip install uv 44 | ``` 45 | 46 | Kurulumu doğrulayın: `uv --version` 47 | 48 | #### 2. Claude Desktop'a Ekleme 49 | 50 | Claude Desktop ayarlarından (Settings > Developer > Edit Config) yapılandırma dosyasına aşağıdaki girdiyi ekleyin: 51 | 52 | ```json 53 | { 54 | "mcpServers": { 55 | "YOKATLAS API Servisi": { 56 | "command": "uvx", 57 | "args": [ 58 | "--from", 59 | "git+https://github.com/saidsurucu/yokatlas-mcp", 60 | "yokatlas-mcp" 61 | ] 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | Başarılı bir kurulumdan sonra, Claude Desktop uygulamasında YOKATLAS API araçlarını kullanabilirsiniz. 68 | 69 |
70 | 71 |
72 | 🚀 Claude Haricindeki Modellerle Kullanmak İçin Çok Kolay Kurulum (Örnek: 5ire için) 73 | 74 | Bu bölüm, YOKATLAS MCP aracını 5ire gibi Claude Desktop dışındaki MCP istemcileriyle kullanmak isteyenler içindir. 75 | 76 | 1. **Python Kurulumu:** Sisteminizde Python 3.12 veya üzeri kurulu olmalıdır. Kurulum sırasında "Add Python to PATH" (Python'ı PATH'e ekle) seçeneğini işaretlemeyi unutmayın. [Buradan indirebilirsiniz](https://www.python.org/downloads/). 77 | 78 | 2. **Git Kurulumu (Windows):** Bilgisayarınıza git yazılımını [indirip kurun](https://git-scm.com/download/win). "Git for Windows/x64 Setup" seçeneğini indirmelisiniz. 79 | 80 | 3. **uv Kurulumu:** 81 | - **Windows Kullanıcıları (PowerShell):** Bir CMD ekranı açın ve bu kodu çalıştırın: `powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"` 82 | - **Mac/Linux Kullanıcıları (Terminal):** Bir Terminal ekranı açın ve bu kodu çalıştırın: `curl -LsSf https://astral.sh/uv/install.sh | sh` 83 | 84 | 4. **Microsoft Visual C++ Redistributable (Windows):** Bazı Python paketlerinin doğru çalışması için gereklidir. [Buradan indirip kurun](https://aka.ms/vs/17/release/vc_redist.x64.exe). 85 | 86 | 5. İşletim sisteminize uygun 5ire MCP istemcisini indirip kurun. 87 | 88 | 6. 5ire'ı açın. **Workspace → Providers** menüsünden kullanmak istediğiniz LLM servisinin API anahtarını girin. 89 | 90 | 7. **Tools** menüsüne girin. **+Local** veya **New** yazan butona basın. 91 | 92 | 8. Aşağıdaki bilgileri girin: 93 | - **Tool Key:** `yokatlasmcp` 94 | - **Name:** `YOKATLAS MCP` 95 | - **Command:** 96 | ``` 97 | uvx --from git+https://github.com/saidsurucu/yokatlas-mcp yokatlas-mcp 98 | ``` 99 | 100 | 9. **Save** butonuna basarak kaydedin. 101 | 102 | 10. Şimdi **Tools** altında **YOKATLAS MCP**'yi görüyor olmalısınız. Üstüne geldiğinizde sağda çıkan butona tıklayıp etkinleştirin (yeşil ışık yanmalı). 103 | 104 | 11. Artık YOKATLAS MCP ile konuşabilirsiniz. 105 | 106 |
107 | 108 |
109 | 🔧 Gemini CLI ile Kullanmak İçin Kurulum 110 | 111 | **Video Rehber:** [Gemini CLI MCP Kurulum Videosu](https://youtu.be/mP_4ulb81zw) 112 | 113 | **Ön Gereksinimler:** Python, uv, (Windows için) Microsoft Visual C++ Redistributable'ın sisteminizde kurulu olduğundan emin olun. Detaylı bilgi için yukarıdaki "5ire için Kurulum" bölümündeki ilgili adımlara bakabilirsiniz. 114 | 115 | 1. **Gemini CLI ayarlarını yapılandırın:** 116 | 117 | Gemini CLI'ın ayar dosyasını düzenleyin: 118 | - **macOS/Linux:** `~/.gemini/settings.json` 119 | - **Windows:** `%USERPROFILE%\.gemini\settings.json` 120 | 121 | 2. **Aşağıdaki mcpServers bloğunu ekleyin:** 122 | 123 | ```json 124 | { 125 | "theme": "Default", 126 | "selectedAuthType": "oauth-personal", 127 | "mcpServers": { 128 | "yokatlas_mcp": { 129 | "command": "uvx", 130 | "args": [ 131 | "--from", 132 | "git+https://github.com/saidsurucu/yokatlas-mcp", 133 | "yokatlas-mcp" 134 | ] 135 | } 136 | } 137 | } 138 | ``` 139 | 140 | 3. **Yapılandırma açıklamaları:** 141 | - `"yokatlas_mcp"`: Sunucunuz için yerel bir isim 142 | - `"command"`: uvx komutu (uv'nin paket çalıştırma aracı) 143 | - `"args"`: GitHub'dan doğrudan YOKATLAS MCP'yi çalıştırmak için gerekli argümanlar 144 | 145 | 4. **Kullanım:** 146 | - Gemini CLI'ı başlatın 147 | - YOKATLAS MCP araçları otomatik olarak kullanılabilir olacaktır 148 | - **Örnek komutlar:** 149 | - "İstanbul'daki tıp fakültelerinin 2024 taban puanlarını getir" 150 | - "Boğaziçi Üniversitesi Bilgisayar Mühendisliği programının detaylarını ara" 151 | - "SAY puan türünde 400-500 bin sıralama aralığındaki mühendislik programlarını listele" 152 | 153 |
154 | 155 | ## 🛠️ Kullanılabilir Araçlar (MCP Tools) 156 | 157 | Bu FastMCP sunucusu aşağıdaki araçları sunar: 158 | 159 | ### 🔍 Akıllı Arama Araçları (Smart Search Tools) 160 | 161 | 1. **`search_bachelor_degree_programs`** ⭐ **YENİ Smart Search** 162 | * **Açıklama:** Lisans programları için akıllı arama (Fuzzy matching ile) 163 | * **Özellikler:** 164 | - 🧠 **Fuzzy Matching:** "boğaziçi" → "BOĞAZİÇİ ÜNİVERSİTESİ" 165 | - 🔎 **Kısmi Eşleştirme:** "bilgisayar" → tüm bilgisayar programları 166 | - 📝 **Kullanıcı Dostu Parametreler:** `university`, `program`, `city` 167 | - ✅ **Type-Safe Validation:** Pydantic modelleri ile 168 | * **Parametreler:** 169 | - `university` (str): Üniversite adı (fuzzy matching) 170 | - `program` (str): Program adı (kısmi eşleştirme) 171 | - `city` (str): Şehir adı 172 | - `score_type` (str): Puan türü (SAY, EA, SOZ, DIL) 173 | - `university_type` (str): Üniversite türü (Devlet, Vakıf) 174 | - `fee_type` (str): Ücret durumu 175 | - `education_type` (str): Öğretim türü 176 | - `results_limit` (int): Sonuç sayısı (varsayılan: 50) 177 | 178 | 2. **`search_associate_degree_programs`** ⭐ **YENİ Smart Search** 179 | * **Açıklama:** Önlisans programları için akıllı arama (Fuzzy matching ile) 180 | * **Özellikler:** 181 | - 🧠 **Fuzzy Matching:** "anadolu" → "ANADOLU ÜNİVERSİTESİ" 182 | - 🔎 **Kısmi Eşleştirme:** "turizm" → tüm turizm programları 183 | - 📝 **Kullanıcı Dostu Parametreler:** `university`, `program`, `city` 184 | - ⚡ **TYT Puan Sistemi:** Önlisans için özel puan sistemi 185 | * **Parametreler:** 186 | - `university` (str): Üniversite adı (fuzzy matching) 187 | - `program` (str): Program adı (kısmi eşleştirme) 188 | - `city` (str): Şehir adı 189 | - `university_type` (str): Üniversite türü 190 | - `fee_type` (str): Ücret durumu 191 | - `education_type` (str): Öğretim türü 192 | - `results_limit` (int): Sonuç sayısı (varsayılan: 50) 193 | 194 | ### 📊 Atlas Detay Araçları 195 | 196 | 3. **`get_bachelor_degree_atlas_details`** 197 | * **Açıklama:** Belirli bir lisans programının YOKATLAS Atlas'tan kapsamlı detaylarını getirir 198 | * **Parametreler:** 199 | - `yop_kodu` (str): Program YÖP kodu (örn: '102210277') 200 | - `year` (int): Veri yılı (örn: 2024, 2023) 201 | * **Döndürülen Veriler:** 202 | - Genel program bilgileri ve istatistikleri 203 | - Kontenjan, yerleşme ve puan verileri 204 | - Öğrenci demografik dağılımları 205 | - Akademik kadro ve tesis bilgileri 206 | - Geçmiş yerleşme trendleri 207 | 208 | 4. **`get_associate_degree_atlas_details`** 209 | * **Açıklama:** Belirli bir önlisans programının YOKATLAS Atlas'tan kapsamlı detaylarını getirir 210 | * **Parametreler:** 211 | - `yop_kodu` (str): Program YÖP kodu (örn: '120910060') 212 | - `year` (int): Veri yılı (örn: 2024, 2023) 213 | * **Döndürülen Veriler:** 214 | - Genel program bilgileri ve istatistikleri 215 | - Kontenjan, yerleşme ve puan verileri 216 | - Öğrenci demografik dağılımları 217 | - Akademik kadro ve tesis bilgileri 218 | - Geçmiş yerleşme trendleri 219 | 220 | ### 🚀 Kullanım Örnekleri 221 | 222 | ```python 223 | # Claude Desktop'ta kullanım örnekleri: 224 | 225 | # 1. Fuzzy matching ile üniversite arama 226 | "Boğaziçi üniversitesinin bilgisayar mühendisliği programlarını bul" 227 | # → "boğaziçi" otomatik olarak "BOĞAZİÇİ ÜNİVERSİTESİ" ile eşleşir 228 | 229 | # 2. Kısmi program adı ile arama 230 | "İstanbul'daki tüm mühendislik programlarını listele" 231 | # → "mühendislik" kelimesi ile başlayan tüm programları bulur 232 | 233 | # 3. Şehir bazlı arama 234 | "Ankara'daki devlet üniversitelerindeki tıp programlarını göster" 235 | # → Şehir, üniversite türü ve program filtresi ile arama 236 | 237 | # 4. Önlisans programları 238 | "Anadolu üniversitesinin turizm ile ilgili önlisans programlarını bul" 239 | # → Fuzzy matching + kısmi eşleştirme ile önlisans arama 240 | 241 | # 5. Atlas detayları 242 | "102210277 YÖP kodlu programın 2024 yılı detaylarını getir" 243 | # → Program atlas detayları: kontenjan, yerleşme, puan istatistikleri 244 | 245 | # 6. Program kodu bulma ve atlas detayları 246 | "Boğaziçi bilgisayar mühendisliğini bul, sonra atlas detaylarını getir" 247 | # → Önce arama ile YÖP kodunu bul, sonra atlas detaylarını çek 248 | ``` 249 | 250 | 251 | ## 📜 Lisans 252 | 253 | Bu proje MIT Lisansı altında lisanslanmıştır. -------------------------------------------------------------------------------- /yokatlas_pdf_generator.py: -------------------------------------------------------------------------------- 1 | import os 2 | import datetime 3 | import tempfile 4 | from io import BytesIO 5 | import base64 6 | 7 | from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle 8 | from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle 9 | from reportlab.lib.pagesizes import A4 10 | from reportlab.lib import colors 11 | from reportlab.lib.units import cm 12 | 13 | 14 | def generate_pdf( 15 | data: dict, 16 | report_type: str, 17 | title: str = "YOKATLAS Raporu", 18 | language: str = "tr", # 'tr' for Turkish, 'en' for English (future support) 19 | save_to_local: bool = False, 20 | output_dir: str = None, 21 | ) -> dict: 22 | """ 23 | Generates a formatted PDF report from YOKATLAS data. 24 | 25 | Parameters: 26 | - data: The data dictionary from one of the YOKATLAS search or details functions 27 | - report_type: Type of report ('bachelor_search', 'associate_search', 'bachelor_details', 'associate_details') 28 | - title: Custom title for the report 29 | - language: Report language ('tr' for Turkish, 'en' for English - future) 30 | - save_to_local: If True, saves PDF to local file system 31 | - output_dir: Directory to save the PDF (if None, uses system temp directory) 32 | 33 | Returns: 34 | - A dictionary containing the Base64 encoded PDF content, filename, and local path if saved 35 | """ 36 | try: 37 | buffer = BytesIO() 38 | 39 | doc = SimpleDocTemplate( 40 | buffer, 41 | pagesize=A4, 42 | rightMargin=2 * cm, 43 | leftMargin=2 * cm, 44 | topMargin=2 * cm, 45 | bottomMargin=2 * cm, 46 | ) 47 | 48 | styles = getSampleStyleSheet() 49 | title_style = styles["Title"] 50 | heading_style = styles["Heading2"] 51 | normal_style = styles["Normal"] 52 | 53 | turkish_style = ParagraphStyle( 54 | "TurkishStyle", 55 | parent=normal_style, 56 | fontName="Helvetica", 57 | fontSize=10, 58 | leading=12, 59 | ) 60 | 61 | elements = [] 62 | 63 | elements.append(Paragraph(title, title_style)) 64 | current_date = datetime.datetime.now().strftime("%d.%m.%Y %H:%M") 65 | elements.append(Paragraph(f"Oluşturulma Tarihi: {current_date}", turkish_style)) 66 | elements.append(Spacer(1, 0.5 * cm)) 67 | 68 | # Process data based on report type 69 | if report_type == "bachelor_search" or report_type == "associate_search": 70 | if "results" in data and isinstance(data["results"], list): 71 | total_programs = len(data["results"]) 72 | elements.append( 73 | Paragraph(f"Toplam Program Sayısı: {total_programs}", heading_style) 74 | ) 75 | elements.append(Spacer(1, 0.3 * cm)) 76 | 77 | table_data = [] 78 | 79 | if report_type == "bachelor_search": 80 | headers = [ 81 | "Program Kodu", 82 | "Üniversite", 83 | "Program", 84 | "Şehir", 85 | "Puan Türü", 86 | "Taban Puanı", 87 | "Başarı Sırası", 88 | ] 89 | table_data.append(headers) 90 | 91 | for program in data["results"]: 92 | row = [ 93 | program.get("program_kodu", ""), 94 | program.get("universite", ""), 95 | program.get("program_adi", ""), 96 | program.get("sehir", ""), 97 | program.get("puan_turu", "").upper(), 98 | program.get("taban_puani", ""), 99 | program.get("basari_sirasi", ""), 100 | ] 101 | table_data.append(row) 102 | else: # associate_search 103 | headers = [ 104 | "Program Kodu", 105 | "Üniversite", 106 | "Program", 107 | "Şehir", 108 | "Taban Puanı", 109 | ] 110 | table_data.append(headers) 111 | 112 | for program in data["results"]: 113 | row = [ 114 | program.get("program_kodu", ""), 115 | program.get("universite", ""), 116 | program.get("program_adi", ""), 117 | program.get("sehir", ""), 118 | program.get("taban_puani", ""), 119 | ] 120 | table_data.append(row) 121 | 122 | table = Table(table_data, repeatRows=1) 123 | table.setStyle( 124 | TableStyle( 125 | [ 126 | ("BACKGROUND", (0, 0), (-1, 0), colors.lightblue), 127 | ("TEXTCOLOR", (0, 0), (-1, 0), colors.black), 128 | ("ALIGN", (0, 0), (-1, -1), "CENTER"), 129 | ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"), 130 | ("BOTTOMPADDING", (0, 0), (-1, 0), 12), 131 | ("GRID", (0, 0), (-1, -1), 1, colors.black), 132 | ( 133 | "ROWBACKGROUNDS", 134 | (0, 1), 135 | (-1, -1), 136 | [colors.white, colors.lightgrey], 137 | ), 138 | ] 139 | ) 140 | ) 141 | 142 | elements.append(table) 143 | 144 | elif report_type == "bachelor_details" or report_type == "associate_details": 145 | if "genel" in data and isinstance(data["genel"], dict): 146 | genel = data["genel"] 147 | program_name = genel.get("program_adi", "Program Bilgisi") 148 | university = genel.get("universite_adi", "") 149 | 150 | elements.append( 151 | Paragraph(f"{university} - {program_name}", heading_style) 152 | ) 153 | elements.append(Spacer(1, 0.3 * cm)) 154 | 155 | elements.append(Paragraph("Genel Bilgiler", styles["Heading3"])) 156 | 157 | general_info = [ 158 | ["Üniversite", university], 159 | ["Program", genel.get("program_adi", "")], 160 | ["Fakülte", genel.get("fakulte", "")], 161 | ["Şehir", genel.get("sehir", "")], 162 | ["Program Türü", genel.get("program_turu", "")], 163 | ["Öğretim Türü", genel.get("ogretim_turu", "")], 164 | ["Burs/Ücret", genel.get("ucret_burs", "")], 165 | ] 166 | 167 | gen_table = Table(general_info) 168 | gen_table.setStyle( 169 | TableStyle( 170 | [ 171 | ("BACKGROUND", (0, 0), (0, -1), colors.lightblue), 172 | ("TEXTCOLOR", (0, 0), (0, -1), colors.black), 173 | ("ALIGN", (0, 0), (-1, -1), "LEFT"), 174 | ("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"), 175 | ("GRID", (0, 0), (-1, -1), 1, colors.black), 176 | ] 177 | ) 178 | ) 179 | elements.append(gen_table) 180 | elements.append(Spacer(1, 0.5 * cm)) 181 | 182 | if "kontenjan_yerlesen" in data: 183 | elements.append( 184 | Paragraph("Kontenjan ve Yerleşme Bilgileri", styles["Heading3"]) 185 | ) 186 | kontenjan = data["kontenjan_yerlesen"] 187 | 188 | if isinstance(kontenjan, dict): 189 | kontenjan_info = [ 190 | ["Kontenjan", kontenjan.get("kontenjan", "")], 191 | ["Yerleşen", kontenjan.get("yerlesen", "")], 192 | ["Doluluk Oranı", f"{kontenjan.get('doluluk_orani', '')}%"], 193 | ] 194 | 195 | kon_table = Table(kontenjan_info) 196 | kon_table.setStyle( 197 | TableStyle( 198 | [ 199 | ("BACKGROUND", (0, 0), (0, -1), colors.lightblue), 200 | ("TEXTCOLOR", (0, 0), (0, -1), colors.black), 201 | ("ALIGN", (0, 0), (-1, -1), "LEFT"), 202 | ("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"), 203 | ("GRID", (0, 0), (-1, -1), 1, colors.black), 204 | ] 205 | ) 206 | ) 207 | elements.append(kon_table) 208 | elements.append(Spacer(1, 0.5 * cm)) 209 | 210 | if "taban_puan_bilgileri" in data: 211 | elements.append( 212 | Paragraph("Taban Puan ve Başarı Sırası", styles["Heading3"]) 213 | ) 214 | taban = data["taban_puan_bilgileri"] 215 | 216 | if isinstance(taban, dict): 217 | puan_info = [ 218 | ["Puan Türü", taban.get("puan_turu", "").upper()], 219 | ["Taban Puanı", taban.get("taban_puani", "")], 220 | ["Tavan Puanı", taban.get("tavan_puani", "")], 221 | ["Başarı Sırası", taban.get("basari_sirasi", "")], 222 | ] 223 | 224 | puan_table = Table(puan_info) 225 | puan_table.setStyle( 226 | TableStyle( 227 | [ 228 | ("BACKGROUND", (0, 0), (0, -1), colors.lightblue), 229 | ("TEXTCOLOR", (0, 0), (0, -1), colors.black), 230 | ("ALIGN", (0, 0), (-1, -1), "LEFT"), 231 | ("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"), 232 | ("GRID", (0, 0), (-1, -1), 1, colors.black), 233 | ] 234 | ) 235 | ) 236 | elements.append(puan_table) 237 | 238 | doc.build(elements) 239 | 240 | pdf_data = buffer.getvalue() 241 | 242 | timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") 243 | filename = f"YOKATLAS_Rapor_{timestamp}.pdf" 244 | 245 | local_path = None 246 | if save_to_local: 247 | if output_dir is None: 248 | output_dir = tempfile.gettempdir() 249 | 250 | os.makedirs(output_dir, exist_ok=True) 251 | 252 | local_path = os.path.join(output_dir, filename) 253 | with open(local_path, "wb") as f: 254 | f.write(pdf_data) 255 | 256 | pdf_base64 = base64.b64encode(pdf_data).decode("utf-8") 257 | buffer.close() 258 | 259 | return { 260 | "success": True, 261 | "filename": filename, 262 | "pdf_base64": pdf_base64, 263 | "local_path": local_path, 264 | "message": "PDF rapor başarıyla oluşturuldu" 265 | + (f" ve {local_path} konumuna kaydedildi." if local_path else "."), 266 | } 267 | 268 | except Exception as e: 269 | error_msg = str(e) 270 | print(f"Error in generate_pdf: {error_msg}") 271 | return { 272 | "success": False, 273 | "error": error_msg, 274 | "message": "PDF rapor oluşturma sırasında hata oluştu.", 275 | } 276 | 277 | 278 | def get_downloads_folder(): 279 | """Returns the downloads folder path for the current user.""" 280 | home = os.path.expanduser("~") 281 | if os.name == "nt": 282 | return os.path.join(home, "Downloads") 283 | elif os.name == "posix": 284 | return os.path.join(home, "Downloads") 285 | else: 286 | return home 287 | -------------------------------------------------------------------------------- /yokatlas_mcp_server.py: -------------------------------------------------------------------------------- 1 | import asyncio # Required for async yokatlas_py functions 2 | from typing import Literal, Annotated, Optional 3 | from pydantic import Field 4 | 5 | from fastmcp import FastMCP 6 | 7 | # Import the new smart search functions (v0.4.3+) 8 | try: 9 | from yokatlas_py import search_lisans_programs, search_onlisans_programs 10 | from yokatlas_py import YOKATLASLisansAtlasi, YOKATLASOnlisansAtlasi 11 | from yokatlas_py.models import SearchParams, ProgramInfo 12 | NEW_SMART_API = True 13 | except ImportError: 14 | # Fallback to older API structure 15 | try: 16 | from yokatlas_py import YOKATLASLisansTercihSihirbazi, YOKATLASOnlisansTercihSihirbazi 17 | from yokatlas_py import YOKATLASLisansAtlasi, YOKATLASOnlisansAtlasi 18 | from yokatlas_py.models import SearchParams, ProgramInfo 19 | NEW_SMART_API = False 20 | except ImportError: 21 | # Final fallback to very old structure 22 | from yokatlas_py.lisansatlasi import YOKATLASLisansAtlasi 23 | from yokatlas_py.lisanstercihsihirbazi import YOKATLASLisansTercihSihirbazi 24 | from yokatlas_py.onlisansatlasi import YOKATLASOnlisansAtlasi 25 | from yokatlas_py.onlisanstercihsihirbazi import YOKATLASOnlisansTercihSihirbazi 26 | NEW_SMART_API = False 27 | 28 | # Create a FastMCP server instance 29 | mcp = FastMCP("YOKATLAS API Server") 30 | 31 | # Tool for YOKATLAS Onlisans Atlasi 32 | @mcp.tool() 33 | async def get_associate_degree_atlas_details( 34 | yop_kodu: Annotated[str, Field(description="Program YÖP code (e.g., '120910060') - unique identifier for the associate degree program")], 35 | year: Annotated[int, Field(description="Data year for statistics (e.g., 2024, 2023)", ge=2020, le=2030)] 36 | ) -> dict: 37 | """ 38 | Get comprehensive details for a specific associate degree program from YOKATLAS Atlas. 39 | 40 | Parameters: 41 | - yop_kodu (str): Program YÖP code (e.g., '120910060') 42 | - year (int): Data year (e.g., 2024, 2023) 43 | 44 | Returns detailed information including: 45 | - General program information and statistics 46 | - Quota, placement, and score data 47 | - Student demographics and distribution 48 | - Academic staff and facility information 49 | - Historical placement trends 50 | """ 51 | try: 52 | onlisans_atlasi = YOKATLASOnlisansAtlasi({'program_id': yop_kodu, 'year': year}) 53 | result = await onlisans_atlasi.fetch_all_details() 54 | return result 55 | except Exception as e: 56 | # Log error or handle it as appropriate for your MCP server 57 | # For now, re-raising or returning an error structure 58 | print(f"Error in get_associate_degree_atlas_details: {e}") 59 | return {"error": str(e), "program_id": yop_kodu, "year": year} 60 | 61 | # Tool for YOKATLAS Lisans Atlasi 62 | @mcp.tool() 63 | async def get_bachelor_degree_atlas_details( 64 | yop_kodu: Annotated[str, Field(description="Program YÖP code (e.g., '102210277') - unique identifier for the bachelor's degree program")], 65 | year: Annotated[int, Field(description="Data year for statistics (e.g., 2024, 2023)", ge=2020, le=2030)] 66 | ) -> dict: 67 | """ 68 | Get comprehensive details for a specific bachelor's degree program from YOKATLAS Atlas. 69 | 70 | Parameters: 71 | - yop_kodu (str): Program YÖP code (e.g., '102210277') 72 | - year (int): Data year (e.g., 2024, 2023) 73 | 74 | Returns detailed information including: 75 | - General program information and statistics 76 | - Quota, placement, and score data 77 | - Student demographics and distribution 78 | - Academic staff and facility information 79 | - Historical placement trends 80 | """ 81 | try: 82 | lisans_atlasi = YOKATLASLisansAtlasi({'program_id': yop_kodu, 'year': year}) 83 | result = await lisans_atlasi.fetch_all_details() 84 | return result 85 | except Exception as e: 86 | print(f"Error in get_bachelor_degree_atlas_details: {e}") 87 | return {"error": str(e), "program_id": yop_kodu, "year": year} 88 | 89 | # Tool for YOKATLAS Lisans Search with Smart Features 90 | @mcp.tool() 91 | def search_bachelor_degree_programs( 92 | # User-friendly parameters with fuzzy matching 93 | university: Annotated[str, Field(description="University name with fuzzy matching support (e.g., 'boğaziçi' → 'BOĞAZİÇİ ÜNİVERSİTESİ')")] = '', 94 | program: Annotated[str, Field(description="Program/department name with partial matching (e.g., 'bilgisayar' finds all computer programs)")] = '', 95 | city: Annotated[str, Field(description="City name where the university is located")] = '', 96 | score_type: Annotated[Optional[Literal['SAY', 'EA', 'SOZ', 'DIL']], Field(description="Score type")] = None, 97 | university_type: Annotated[Optional[Literal['Devlet', 'Vakıf', 'KKTC', 'Yurt Dışı']], Field(description="University type")] = None, 98 | fee_type: Annotated[Optional[Literal['Ücretsiz', 'Ücretli', 'İÖ-Ücretli', 'Burslu', '%50 İndirimli', '%25 İndirimli', 'AÖ-Ücretli', 'UÖ-Ücretli']], Field(description="Fee status: Ücretsiz (Free), Ücretli (Paid), İÖ-Ücretli (Evening-Paid), Burslu (Scholarship), İndirimli (Discounted), AÖ-Ücretli (Open Education-Paid), UÖ-Ücretli (Distance Learning-Paid)")] = None, 99 | education_type: Annotated[Optional[Literal['Örgün', 'İkinci', 'Açıköğretim', 'Uzaktan']], Field(description="Education type: Örgün (Regular), İkinci (Evening), Açıköğretim (Open Education), Uzaktan (Distance Learning)")] = None, 100 | availability: Annotated[Optional[Literal['Doldu', 'Doldu#', 'Dolmadı', 'Yeni']], Field(description="Program availability: Doldu (Filled), Doldu# (Filled with conditions), Dolmadı (Not filled), Yeni (New program)")] = None, 101 | results_limit: Annotated[int, Field(description="Maximum number of results to return", ge=1, le=500)] = 50, 102 | # Legacy parameter support for backward compatibility 103 | uni_adi: str = '', 104 | program_adi: str = '', 105 | sehir: str = '', 106 | puan_turu: str = '', 107 | universite_turu: str = '', 108 | ucret_burs: str = '', 109 | ogretim_turu: str = '', 110 | length: int = 0 111 | ) -> dict: 112 | """ 113 | Search for bachelor's degree programs with smart fuzzy matching and user-friendly parameters. 114 | 115 | Smart Features: 116 | - Fuzzy university name matching (e.g., "boğaziçi" → "BOĞAZİÇİ ÜNİVERSİTESİ") 117 | - Partial program name matching (e.g., "bilgisayar" finds all computer programs) 118 | - Intelligent parameter normalization 119 | - Type-safe validation 120 | 121 | Parameters: 122 | - university: University name (fuzzy matching supported) 123 | - program: Program/department name (partial matching supported) 124 | - city: City name 125 | - score_type: Score type (SAY, EA, SOZ, DIL) 126 | - university_type: Type of university (Devlet, Vakıf, etc.) 127 | - fee_type: Fee/scholarship information 128 | - education_type: Type of education (Örgün, İkinci, etc.) 129 | - results_limit: Maximum number of results to return 130 | """ 131 | try: 132 | if NEW_SMART_API: 133 | # Use new smart search functions (v0.4.3+) 134 | search_params = {} 135 | 136 | # Map user-friendly parameters to API parameters 137 | if university or uni_adi: 138 | search_params['uni_adi'] = university or uni_adi 139 | if program or program_adi: 140 | search_params['program_adi'] = program or program_adi 141 | if city or sehir: 142 | search_params['city'] = city or sehir 143 | if score_type or puan_turu: 144 | search_params['score_type'] = score_type or puan_turu 145 | if university_type or universite_turu: 146 | search_params['university_type'] = university_type or universite_turu 147 | if fee_type or ucret_burs: 148 | search_params['fee_type'] = fee_type or ucret_burs 149 | if education_type or ogretim_turu: 150 | search_params['education_type'] = education_type or ogretim_turu 151 | if results_limit or length: 152 | search_params['length'] = results_limit or length 153 | 154 | # Use smart search with fuzzy matching 155 | results = search_lisans_programs(search_params, smart_search=True) 156 | 157 | # Validate and format results 158 | validated_results = [] 159 | for program_data in results: 160 | try: 161 | program = ProgramInfo(**program_data) 162 | validated_results.append(program.model_dump()) 163 | except Exception: 164 | # Include unvalidated data if validation fails 165 | validated_results.append(program_data) 166 | 167 | return { 168 | "programs": validated_results, 169 | "total_found": len(validated_results), 170 | "search_method": "smart_search_v0.4.3", 171 | "fuzzy_matching": True 172 | } 173 | 174 | else: 175 | # Fallback to legacy API 176 | params = { 177 | 'uni_adi': university or uni_adi, 178 | 'program_adi': program or program_adi, 179 | 'sehir_adi': city or sehir, 180 | 'puan_turu': (score_type or puan_turu).lower() if (score_type or puan_turu) else 'say', 181 | 'universite_turu': university_type or universite_turu, 182 | 'ucret_burs': fee_type or ucret_burs, 183 | 'ogretim_turu': education_type or ogretim_turu, 184 | 'page': 1 185 | } 186 | 187 | # Remove empty parameters 188 | params = {k: v for k, v in params.items() if v} 189 | 190 | lisans_tercih = YOKATLASLisansTercihSihirbazi(params) 191 | result = lisans_tercih.search() 192 | 193 | return { 194 | "programs": result[:results_limit] if isinstance(result, list) else result, 195 | "total_found": len(result) if isinstance(result, list) else 0, 196 | "search_method": "legacy_api", 197 | "fuzzy_matching": False 198 | } 199 | 200 | except Exception as e: 201 | print(f"Error in search_bachelor_degree_programs: {e}") 202 | return { 203 | "error": str(e), 204 | "search_method": "smart_search" if NEW_SMART_API else "legacy_api", 205 | "parameters_used": { 206 | "university": university or uni_adi, 207 | "program": program or program_adi, 208 | "city": city or sehir 209 | } 210 | } 211 | 212 | # Tool for YOKATLAS Onlisans Search with Smart Features 213 | @mcp.tool() 214 | def search_associate_degree_programs( 215 | # User-friendly parameters with fuzzy matching 216 | university: Annotated[str, Field(description="University name with fuzzy matching support (e.g., 'anadolu' → 'ANADOLU ÜNİVERSİTESİ')")] = '', 217 | program: Annotated[str, Field(description="Program name with partial matching (e.g., 'turizm' finds all tourism programs)")] = '', 218 | city: Annotated[str, Field(description="City name where the university is located")] = '', 219 | university_type: Annotated[Optional[Literal['Devlet', 'Vakıf', 'KKTC', 'Yurt Dışı']], Field(description="University type: Devlet (State), Vakıf (Foundation), KKTC (TRNC), Yurt Dışı (International)")] = None, 220 | fee_type: Annotated[Optional[Literal['Ücretsiz', 'Ücretli', 'İÖ-Ücretli', 'Burslu', '%50 İndirimli', '%25 İndirimli', 'AÖ-Ücretli', 'UÖ-Ücretli']], Field(description="Fee status: Ücretsiz (Free), Ücretli (Paid), İÖ-Ücretli (Evening-Paid), Burslu (Scholarship), İndirimli (Discounted), AÖ-Ücretli (Open Education-Paid), UÖ-Ücretli (Distance Learning-Paid)")] = None, 221 | education_type: Annotated[Optional[Literal[ 'Örgün', 'İkinci', 'Açıköğretim', 'Uzaktan']], Field(description="Education type: Örgün (Regular), İkinci (Evening), Açıköğretim (Open Education), Uzaktan (Distance Learning)")] = None, 222 | availability: Annotated[Optional[Literal['Doldu', 'Doldu#', 'Dolmadı', 'Yeni']], Field(description="Program availability: Doldu (Filled), Doldu# (Filled with conditions), Dolmadı (Not filled), Yeni (New program)")] = None, 223 | results_limit: Annotated[int, Field(description="Maximum number of results to return", ge=1, le=500)] = 50, 224 | # Legacy parameter support for backward compatibility 225 | yop_kodu: str = '', 226 | uni_adi: str = '', 227 | program_adi: str = '', 228 | sehir_adi: str = '', 229 | universite_turu: str = '', 230 | ucret_burs: str = '', 231 | ogretim_turu: str = '', 232 | doluluk: str = '', 233 | ust_puan: float = 550.0, 234 | alt_puan: float = 150.0, 235 | page: int = 1 236 | ) -> dict: 237 | """ 238 | Search for associate degree (önlisans) programs with smart fuzzy matching and user-friendly parameters. 239 | 240 | Smart Features: 241 | - Fuzzy university name matching (e.g., "anadolu" → "ANADOLU ÜNİVERSİTESİ") 242 | - Partial program name matching (e.g., "turizm" finds all tourism programs) 243 | - Intelligent parameter normalization 244 | - Type-safe validation 245 | 246 | Parameters: 247 | - university: University name (fuzzy matching supported) 248 | - program: Program/department name (partial matching supported) 249 | - city: City name 250 | - university_type: Type of university (Devlet, Vakıf, etc.) 251 | - fee_type: Fee/scholarship information 252 | - education_type: Type of education (Örgün, İkinci, etc.) 253 | - results_limit: Maximum number of results to return 254 | 255 | Note: Associate degree programs use TYT scores, not SAY/EA/SOZ/DIL like bachelor programs. 256 | """ 257 | try: 258 | if NEW_SMART_API: 259 | # Use new smart search functions (v0.4.3+) 260 | search_params = {} 261 | 262 | # Map user-friendly parameters to API parameters 263 | if university or uni_adi: 264 | search_params['uni_adi'] = university or uni_adi 265 | if program or program_adi: 266 | search_params['program_adi'] = program or program_adi 267 | if city or sehir_adi: 268 | search_params['city'] = city or sehir_adi 269 | if university_type or universite_turu: 270 | search_params['university_type'] = university_type or universite_turu 271 | if fee_type or ucret_burs: 272 | search_params['fee_type'] = fee_type or ucret_burs 273 | if education_type or ogretim_turu: 274 | search_params['education_type'] = education_type or ogretim_turu 275 | if results_limit: 276 | search_params['length'] = results_limit 277 | 278 | # Use smart search with fuzzy matching 279 | results = search_onlisans_programs(search_params, smart_search=True) 280 | 281 | # Format results consistently 282 | return { 283 | "programs": results, 284 | "total_found": len(results), 285 | "search_method": "smart_search_v0.4.3", 286 | "fuzzy_matching": True, 287 | "program_type": "associate_degree" 288 | } 289 | 290 | else: 291 | # Fallback to legacy API 292 | params = { 293 | 'yop_kodu': yop_kodu, 294 | 'uni_adi': university or uni_adi, 295 | 'program_adi': program or program_adi, 296 | 'sehir_adi': city or sehir_adi, 297 | 'universite_turu': university_type or universite_turu, 298 | 'ucret_burs': fee_type or ucret_burs, 299 | 'ogretim_turu': education_type or ogretim_turu, 300 | 'doluluk': doluluk, 301 | 'ust_puan': ust_puan, 302 | 'alt_puan': alt_puan, 303 | 'page': page 304 | } 305 | 306 | # Remove empty parameters 307 | params = {k: v for k, v in params.items() if v or isinstance(v, (int, float))} 308 | 309 | onlisans_tercih = YOKATLASOnlisansTercihSihirbazi(params) 310 | result = onlisans_tercih.search() 311 | 312 | return { 313 | "programs": result[:results_limit] if isinstance(result, list) else result, 314 | "total_found": len(result) if isinstance(result, list) else 0, 315 | "search_method": "legacy_api", 316 | "fuzzy_matching": False, 317 | "program_type": "associate_degree" 318 | } 319 | 320 | except Exception as e: 321 | print(f"Error in search_associate_degree_programs: {e}") 322 | return { 323 | "error": str(e), 324 | "search_method": "smart_search" if NEW_SMART_API else "legacy_api", 325 | "parameters_used": { 326 | "university": university or uni_adi, 327 | "program": program or program_adi, 328 | "city": city or sehir_adi 329 | }, 330 | "program_type": "associate_degree" 331 | } 332 | 333 | def main(): 334 | """Main entry point for the YOKATLAS MCP server.""" 335 | import sys 336 | 337 | # Default to stdio transport for MCP compatibility 338 | transport = "stdio" 339 | 340 | # Check if running in development mode 341 | if "--dev" in sys.argv: 342 | transport = "sse" 343 | print("Starting YOKATLAS API MCP Server in development mode...") 344 | print("Server will be available at http://127.0.0.1:8000") 345 | mcp.run(transport=transport, host="127.0.0.1", port=8000) 346 | else: 347 | # Run with stdio transport for Claude Desktop and other MCP clients 348 | mcp.run(transport=transport) 349 | 350 | if __name__ == "__main__": 351 | main() 352 | --------------------------------------------------------------------------------