├── 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 | [](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 | 
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 |
--------------------------------------------------------------------------------