yemek-com-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- yemek_com_mcp-0.1.0/.github/workflows/publish.yml +20 -0
- yemek_com_mcp-0.1.0/.github/workflows/registry.yml +61 -0
- yemek_com_mcp-0.1.0/PKG-INFO +84 -0
- yemek_com_mcp-0.1.0/README-fa.md +40 -0
- yemek_com_mcp-0.1.0/README-tr.md +42 -0
- yemek_com_mcp-0.1.0/README.md +72 -0
- yemek_com_mcp-0.1.0/pyproject.toml +23 -0
- yemek_com_mcp-0.1.0/server.json +1 -0
- yemek_com_mcp-0.1.0/src/yemek_com_mcp/__init__.py +2 -0
- yemek_com_mcp-0.1.0/src/yemek_com_mcp/http.py +101 -0
- yemek_com_mcp-0.1.0/src/yemek_com_mcp/recipes.py +242 -0
- yemek_com_mcp-0.1.0/src/yemek_com_mcp/server.py +157 -0
- yemek_com_mcp-0.1.0/tests/__init__.py +0 -0
- yemek_com_mcp-0.1.0/tests/fixtures/bugun.html +17 -0
- yemek_com_mcp-0.1.0/tests/fixtures/home.html +2273 -0
- yemek_com_mcp-0.1.0/tests/fixtures/recipe.html +3 -0
- yemek_com_mcp-0.1.0/tests/fixtures/search.html +14 -0
- yemek_com_mcp-0.1.0/tests/fixtures/search.json +137 -0
- yemek_com_mcp-0.1.0/tests/test_live.py +69 -0
- yemek_com_mcp-0.1.0/tests/test_recipes.py +119 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write # required for PyPI trusted publishing (OIDC)
|
|
12
|
+
contents: read
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
- run: pip install build
|
|
19
|
+
- run: python -m build
|
|
20
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
name: Publish to MCP Registry
|
|
2
|
+
|
|
3
|
+
# Official MCP Registry (registry.modelcontextprotocol.io) via GitHub OIDC.
|
|
4
|
+
# No secret, no interactive login: the runner's OIDC token proves ownership
|
|
5
|
+
# of the io.github.mohammad-abbasi-sh/* namespace.
|
|
6
|
+
# Refs: https://github.com/modelcontextprotocol/registry
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
push:
|
|
10
|
+
tags: ["v*"]
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
publish:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
permissions:
|
|
17
|
+
id-token: write # required for OIDC -> MCP Registry
|
|
18
|
+
contents: read
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Sync server.json version to tag
|
|
23
|
+
# Only sync when running on a version tag (v*). On workflow_dispatch
|
|
24
|
+
# (ref = refs/heads/main) keep the version committed in server.json.
|
|
25
|
+
run: |
|
|
26
|
+
VER=""
|
|
27
|
+
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
|
|
28
|
+
TAG="${GITHUB_REF#refs/tags/}"
|
|
29
|
+
VER="${TAG#v}"
|
|
30
|
+
fi
|
|
31
|
+
if [ -n "$VER" ]; then
|
|
32
|
+
python3 - "$VER" << 'EOF'
|
|
33
|
+
import json, sys
|
|
34
|
+
ver = sys.argv[1]
|
|
35
|
+
d = json.load(open('server.json'))
|
|
36
|
+
d['version'] = ver
|
|
37
|
+
for pkg in d.get('packages', []):
|
|
38
|
+
pkg['version'] = ver
|
|
39
|
+
json.dump(d, open('server.json', 'w'), indent=2, ensure_ascii=False)
|
|
40
|
+
print('server.json version ->', ver)
|
|
41
|
+
EOF
|
|
42
|
+
else
|
|
43
|
+
echo "No version tag ($GITHUB_REF); keeping server.json version as-is"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
- name: Install mcp-publisher
|
|
47
|
+
run: |
|
|
48
|
+
curl -sL "https://github.com/modelcontextprotocol/registry/releases/download/v1.8.7/mcp-publisher_linux_amd64.tar.gz" -o /tmp/mcp-publisher.tar.gz
|
|
49
|
+
tar -xzf /tmp/mcp-publisher.tar.gz -C /tmp
|
|
50
|
+
sudo mv /tmp/mcp-publisher /usr/local/bin/mcp-publisher
|
|
51
|
+
mcp-publisher --version
|
|
52
|
+
|
|
53
|
+
- name: Validate server.json
|
|
54
|
+
run: mcp-publisher validate server.json
|
|
55
|
+
|
|
56
|
+
- name: Publish to MCP Registry (OIDC)
|
|
57
|
+
run: |
|
|
58
|
+
mcp-publisher login github-oidc
|
|
59
|
+
mcp-publisher publish server.json
|
|
60
|
+
# A registry outage must never break the release; re-run from Actions if needed.
|
|
61
|
+
continue-on-error: true
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: yemek-com-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial read-only MCP server for Yemek.com Turkish recipes (search, full recipes, categories, daily picks)
|
|
5
|
+
Author: Mohammad Abbasi
|
|
6
|
+
License: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Requires-Dist: curl-cffi>=0.7
|
|
9
|
+
Requires-Dist: mcp<2
|
|
10
|
+
Requires-Dist: requests>=2.32
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# yemek-com-mcp 🇹🇷
|
|
14
|
+
|
|
15
|
+
mcp-name: io.github.mohammad-abbasi-sh/yemek-com-mcp
|
|
16
|
+
|
|
17
|
+
Unofficial **read-only** MCP server for **Yemek.com** — Türkiye's recipe site: search recipes, full recipe details (ingredients, steps, times, servings, ratings), featured categories, the daily "Bugün Ne Pişirsem?" picks, video recipes and the most popular recipes.
|
|
18
|
+
|
|
19
|
+
> Part of the "Turkey MCP factory" (Sepehr-style pipeline). Not affiliated with Yemek.com.
|
|
20
|
+
|
|
21
|
+
## For AI agents
|
|
22
|
+
|
|
23
|
+
Add to your MCP client config (stdio):
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"yemek-com": {
|
|
29
|
+
"command": "uvx",
|
|
30
|
+
"args": ["yemek-com-mcp"]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or run locally: `python -m yemek_com_mcp.server` (needs `mcp<2`, `curl-cffi`, `requests`).
|
|
37
|
+
|
|
38
|
+
## Tools
|
|
39
|
+
|
|
40
|
+
| Tool | Description |
|
|
41
|
+
|------|-------------|
|
|
42
|
+
| `yemek_search_recipes` | Search recipes by dish/ingredient — `query`, `limit` (1..30). Uses the site's public search JSON |
|
|
43
|
+
| `yemek_get_recipe` | Full recipe — `slug` (e.g. `mercimek-corbasi`) or a full yemek.com/tarif/... URL |
|
|
44
|
+
| `yemek_list_categories` | Featured recipe categories + recipe counts |
|
|
45
|
+
| `yemek_list_trending` | Today's "Bugün Ne Pişirsem?" picks (15 recipes) |
|
|
46
|
+
| `yemek_list_videos` | Video recipes from the homepage ("Videolu Tarifler") |
|
|
47
|
+
| `yemek_list_popular` | Most popular recipes ("Popüler Yemek Tarifleri") |
|
|
48
|
+
|
|
49
|
+
Example output (`yemek_search_recipes` / "mercimek"):
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{"query": "mercimek", "total_recipes": 533, "total_posts": 822,
|
|
53
|
+
"recipes": [{"title": "Lokanta Usulünde: 4 Kişilik Mercimek Çorbası",
|
|
54
|
+
"slug": "4-kisilik-mercimek-corbasi", "prep_time": "15dk",
|
|
55
|
+
"category": "ÇORBA", "author": "filizssweetbakery"}]}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Example output (`yemek_get_recipe` / "mercimek-corbasi"):
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{"name": "Mercimek Çorbası (Lokanta Usulü)", "servings": "6 kişilik",
|
|
62
|
+
"total_time": "PT60M", "rating": 4.3, "rating_count": 230,
|
|
63
|
+
"ingredients": ["3 yemek kaşığı ayçiçek yağı", "1,5 su bardağı kırmızı ya da sarı mercimek", "..."],
|
|
64
|
+
"instructions": ["Mercimek çorbası için derin bir tencereye ...", "..."]}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Notes:
|
|
68
|
+
- Search results page in 3 recipes per request — the server paginates automatically.
|
|
69
|
+
- Times are ISO 8601 durations (`PT60M`) or Turkish shorthand (`15dk`); servings are Turkish (`6 kişilik`).
|
|
70
|
+
- Data: yemek.com public pages + public search JSON (`/ara/sonuclar/`) — no key, no login, read-only.
|
|
71
|
+
|
|
72
|
+
## Development
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
python3 -m venv .venv && .venv/bin/pip install "mcp<2" curl-cffi requests pytest
|
|
76
|
+
.venv/bin/python -m pytest tests/ -q
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- `tests/test_recipes.py` — offline, parses real captured fixtures
|
|
80
|
+
- `tests/test_live.py` — 5 cautious live requests against yemek.com
|
|
81
|
+
|
|
82
|
+
## License
|
|
83
|
+
|
|
84
|
+
MIT
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# yemek-com-mcp 🇹🇷
|
|
2
|
+
|
|
3
|
+
سرور MCP غیررسمی و **فقطخواندنی** برای **Yemek.com** — سایت دستور غذای ترکیه: جستوجوی دستورها، جزئیات کامل دستور (مواد، مراحل، زمان، تعداد نفرات، امتیاز)، دستههای ویژه، انتخابهای روزانهی «Bugün Ne Pişirsem?» (امروز چی بپزم؟)، دستورهای ویدیویی و محبوبترین دستورها.
|
|
4
|
+
|
|
5
|
+
> بخشی از «کارخانهی MCP ترکیه» (پایپلاین به سبک سپهر). هیچ وابستگی به Yemek.com ندارد.
|
|
6
|
+
|
|
7
|
+
## برای ایجنتهای هوش مصنوعی
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"mcpServers": {
|
|
12
|
+
"yemek-com": {
|
|
13
|
+
"command": "uvx",
|
|
14
|
+
"args": ["yemek-com-mcp"]
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## ابزارها
|
|
21
|
+
|
|
22
|
+
| ابزار | توضیح |
|
|
23
|
+
|------|-------|
|
|
24
|
+
| `yemek_search_recipes` | جستوجوی دستور با نام غذا یا ماده — `query`، `limit` (۱ تا ۳۰) |
|
|
25
|
+
| `yemek_get_recipe` | دستور کامل — `slug` (مثلاً `mercimek-corbasi`) یا URL کامل yemek.com/tarif/... |
|
|
26
|
+
| `yemek_list_categories` | دستههای ویژهی دستورها + تعداد دستور هر دسته |
|
|
27
|
+
| `yemek_list_trending` | انتخابهای امروز «Bugün Ne Pişirsem؟» (۱۵ دستور) |
|
|
28
|
+
| `yemek_list_videos` | دستورهای ویدیویی («Videolu Tarifler») |
|
|
29
|
+
| `yemek_list_popular` | محبوبترین دستورها («Popüler Yemek Tarifleri») |
|
|
30
|
+
|
|
31
|
+
مثال (`yemek_search_recipes` / «mercimek»): ۵۳۳ دستور پیدا شد؛ اولین نتیجه «Lokanta Usulünde: 4 Kişilik Mercimek Çorbası» (آمادهسازی ۱۵ دقیقه). مثال (`yemek_get_recipe` / «mercimek-corbasi»): ۶ نفره، مجموع ۶۰ دقیقه، امتیاز ۴٫۳ (۲۳۰ رأی)، ۱۲ ماده، ۸ مرحله.
|
|
32
|
+
|
|
33
|
+
نکتهها:
|
|
34
|
+
- نتایج جستوجو در هر درخواست ۳ دستور برمیگردانند — سرور خودکار صفحهبندی میکند.
|
|
35
|
+
- زمانها ISO 8601 (`PT60M`) یا مخفف ترکی (`15dk`) هستند؛ تعداد نفرات به ترکی است (`6 kişilik`).
|
|
36
|
+
- داده: صفحات عمومی yemek.com + JSON عمومی جستوجو — بدون لاگین، بدون کلید، فقطخواندنی.
|
|
37
|
+
|
|
38
|
+
## قوانین
|
|
39
|
+
|
|
40
|
+
غیررسمی و فقطخواندنی — بدون لاگین، بدون کلید، بدون نوشتن. تستها: آفلاین با فیکسچر + تست زنده (۲۰/۲۰ سبز).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# yemek-com-mcp 🇹🇷
|
|
2
|
+
|
|
3
|
+
**Yemek.com** için gayriresmî, **salt-okunur** MCP sunucusu — Türkiye'nin tarif sitesi: tarif arama, tam tarif detayları (malzemeler, adımlar, süreler, porsiyon, puan), öne çıkan kategoriler, günün "Bugün Ne Pişirsem?" seçimleri, videolu tarifler ve en popüler tarifler.
|
|
4
|
+
|
|
5
|
+
> "Türkiye MCP fabrikası" parçası (Sepehr tarzı pipeline). Yemek.com ile bağlantısı yoktur.
|
|
6
|
+
|
|
7
|
+
## Yapay zekâ ajanları için
|
|
8
|
+
|
|
9
|
+
MCP istemcinize ekleyin (stdio):
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"yemek-com": {
|
|
15
|
+
"command": "uvx",
|
|
16
|
+
"args": ["yemek-com-mcp"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Araçlar
|
|
23
|
+
|
|
24
|
+
| Araç | Açıklama |
|
|
25
|
+
|------|----------|
|
|
26
|
+
| `yemek_search_recipes` | Yemek/malzeme ile tarif ara — `query`, `limit` (1..30). Sitenin herkese açık arama JSON'unu kullanır |
|
|
27
|
+
| `yemek_get_recipe` | Tam tarif — `slug` (örn. `mercimek-corbasi`) veya tam yemek.com/tarif/... URL'si |
|
|
28
|
+
| `yemek_list_categories` | Öne çıkan tarif kategorileri + tarif sayıları |
|
|
29
|
+
| `yemek_list_trending` | Günün "Bugün Ne Pişirsem?" seçimleri (15 tarif) |
|
|
30
|
+
| `yemek_list_videos` | Videolu tarifler ("Videolu Tarifler") |
|
|
31
|
+
| `yemek_list_popular` | En popüler tarifler ("Popüler Yemek Tarifleri") |
|
|
32
|
+
|
|
33
|
+
Örnek (`yemek_search_recipes` / "mercimek"): 533 tarif bulundu; ilk sonuç "Lokanta Usulünde: 4 Kişilik Mercimek Çorbası" (hazırlık 15dk). Örnek (`yemek_get_recipe` / "mercimek-corbasi"): 6 kişilik, toplam 60dk, puan 4.3 (230 oy), 12 malzeme, 8 adım.
|
|
34
|
+
|
|
35
|
+
Notlar:
|
|
36
|
+
- Arama sonuçları istek başına 3 tarif döner — sunucu otomatik sayfalar.
|
|
37
|
+
- Süreler ISO 8601 (`PT60M`) veya Türkçe kısaltma (`15dk`); porsiyon Türkçe (`6 kişilik`).
|
|
38
|
+
- Veri: yemek.com herkese açık sayfalar + arama JSON'u — giriş yok, API anahtarı yok, salt-okunur.
|
|
39
|
+
|
|
40
|
+
## Kurallar
|
|
41
|
+
|
|
42
|
+
Gayriresmî ve salt-okunur — giriş yok, API anahtarı yok, yazma yok. Testler: çevrimdışı fixture + canlı testler (20/20 yeşil).
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# yemek-com-mcp 🇹🇷
|
|
2
|
+
|
|
3
|
+
mcp-name: io.github.mohammad-abbasi-sh/yemek-com-mcp
|
|
4
|
+
|
|
5
|
+
Unofficial **read-only** MCP server for **Yemek.com** — Türkiye's recipe site: search recipes, full recipe details (ingredients, steps, times, servings, ratings), featured categories, the daily "Bugün Ne Pişirsem?" picks, video recipes and the most popular recipes.
|
|
6
|
+
|
|
7
|
+
> Part of the "Turkey MCP factory" (Sepehr-style pipeline). Not affiliated with Yemek.com.
|
|
8
|
+
|
|
9
|
+
## For AI agents
|
|
10
|
+
|
|
11
|
+
Add to your MCP client config (stdio):
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"yemek-com": {
|
|
17
|
+
"command": "uvx",
|
|
18
|
+
"args": ["yemek-com-mcp"]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or run locally: `python -m yemek_com_mcp.server` (needs `mcp<2`, `curl-cffi`, `requests`).
|
|
25
|
+
|
|
26
|
+
## Tools
|
|
27
|
+
|
|
28
|
+
| Tool | Description |
|
|
29
|
+
|------|-------------|
|
|
30
|
+
| `yemek_search_recipes` | Search recipes by dish/ingredient — `query`, `limit` (1..30). Uses the site's public search JSON |
|
|
31
|
+
| `yemek_get_recipe` | Full recipe — `slug` (e.g. `mercimek-corbasi`) or a full yemek.com/tarif/... URL |
|
|
32
|
+
| `yemek_list_categories` | Featured recipe categories + recipe counts |
|
|
33
|
+
| `yemek_list_trending` | Today's "Bugün Ne Pişirsem?" picks (15 recipes) |
|
|
34
|
+
| `yemek_list_videos` | Video recipes from the homepage ("Videolu Tarifler") |
|
|
35
|
+
| `yemek_list_popular` | Most popular recipes ("Popüler Yemek Tarifleri") |
|
|
36
|
+
|
|
37
|
+
Example output (`yemek_search_recipes` / "mercimek"):
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{"query": "mercimek", "total_recipes": 533, "total_posts": 822,
|
|
41
|
+
"recipes": [{"title": "Lokanta Usulünde: 4 Kişilik Mercimek Çorbası",
|
|
42
|
+
"slug": "4-kisilik-mercimek-corbasi", "prep_time": "15dk",
|
|
43
|
+
"category": "ÇORBA", "author": "filizssweetbakery"}]}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Example output (`yemek_get_recipe` / "mercimek-corbasi"):
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{"name": "Mercimek Çorbası (Lokanta Usulü)", "servings": "6 kişilik",
|
|
50
|
+
"total_time": "PT60M", "rating": 4.3, "rating_count": 230,
|
|
51
|
+
"ingredients": ["3 yemek kaşığı ayçiçek yağı", "1,5 su bardağı kırmızı ya da sarı mercimek", "..."],
|
|
52
|
+
"instructions": ["Mercimek çorbası için derin bir tencereye ...", "..."]}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Notes:
|
|
56
|
+
- Search results page in 3 recipes per request — the server paginates automatically.
|
|
57
|
+
- Times are ISO 8601 durations (`PT60M`) or Turkish shorthand (`15dk`); servings are Turkish (`6 kişilik`).
|
|
58
|
+
- Data: yemek.com public pages + public search JSON (`/ara/sonuclar/`) — no key, no login, read-only.
|
|
59
|
+
|
|
60
|
+
## Development
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python3 -m venv .venv && .venv/bin/pip install "mcp<2" curl-cffi requests pytest
|
|
64
|
+
.venv/bin/python -m pytest tests/ -q
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- `tests/test_recipes.py` — offline, parses real captured fixtures
|
|
68
|
+
- `tests/test_live.py` — 5 cautious live requests against yemek.com
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "yemek-com-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Unofficial read-only MCP server for Yemek.com Turkish recipes (search, full recipes, categories, daily picks)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"mcp<2",
|
|
14
|
+
"curl-cffi>=0.7",
|
|
15
|
+
"requests>=2.32",
|
|
16
|
+
]
|
|
17
|
+
authors = [{ name = "Mohammad Abbasi" }]
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
yemek-com-mcp = "yemek_com_mcp.server:main"
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
packages = ["src/yemek_com_mcp"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json","description":"Unofficial read-only MCP server for Yemek.com Turkish recipes — search, full recipes, categories.","name":"io.github.mohammad-abbasi-sh/yemek-com-mcp","packages":[{"identifier":"yemek-com-mcp","registryBaseUrl":"https://pypi.org","registryType":"pypi","transport":{"type":"stdio"},"version":"0.1.0"}],"repository":{"source":"github","url":"https://github.com/mohammad-abbasi-sh/yemek-com-mcp"},"title":"Yemek.com Recipes","version":"0.1.0","websiteUrl":"https://github.com/mohammad-abbasi-sh/yemek-com-mcp"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""HTTP layer for yemek_com_mcp.
|
|
2
|
+
|
|
3
|
+
yemek.com serves its pages server-rendered with no login and no API
|
|
4
|
+
key. Search is a public JSON endpoint the site's own search page
|
|
5
|
+
calls (``/ara/sonuclar/?q=...&rows=...``) — no key, no session.
|
|
6
|
+
Transport uses curl-cffi chrome120 impersonation (the proven
|
|
7
|
+
Turkey-factory pattern) with a plain-requests fallback, so one
|
|
8
|
+
transport failing does not break the server. Requests are kept
|
|
9
|
+
gentle (one at a time, ~0.8s apart) and read-only.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import time
|
|
14
|
+
from urllib.parse import quote
|
|
15
|
+
|
|
16
|
+
from curl_cffi import requests as _creq
|
|
17
|
+
import requests as _plain
|
|
18
|
+
|
|
19
|
+
BASE = "https://yemek.com"
|
|
20
|
+
TIMEOUT = 30.0
|
|
21
|
+
MIN_INTERVAL = 0.8 # seconds between requests — be polite
|
|
22
|
+
|
|
23
|
+
_HEADERS = {
|
|
24
|
+
"Accept-Language": "tr-TR,tr;q=0.9,en;q=0.8",
|
|
25
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_UA = (
|
|
29
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
|
30
|
+
"(KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
_last_fetch = 0.0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class YemekError(RuntimeError):
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _throttle() -> None:
|
|
41
|
+
global _last_fetch
|
|
42
|
+
wait = MIN_INTERVAL - (time.monotonic() - _last_fetch)
|
|
43
|
+
if wait > 0:
|
|
44
|
+
time.sleep(wait)
|
|
45
|
+
_last_fetch = time.monotonic()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _get(url: str, accept_json: bool = False) -> str:
|
|
49
|
+
_throttle()
|
|
50
|
+
headers = dict(_HEADERS)
|
|
51
|
+
if accept_json:
|
|
52
|
+
headers["Accept"] = "application/json, text/plain, */*"
|
|
53
|
+
try:
|
|
54
|
+
resp = _creq.get(url, impersonate="chrome120", headers=headers, timeout=TIMEOUT)
|
|
55
|
+
status, text = resp.status_code, resp.text
|
|
56
|
+
except Exception:
|
|
57
|
+
# Fallback transport: plain requests with a browser UA.
|
|
58
|
+
try:
|
|
59
|
+
resp = _plain.get(url, headers={**headers, "User-Agent": _UA}, timeout=TIMEOUT)
|
|
60
|
+
status, text = resp.status_code, resp.text
|
|
61
|
+
except Exception as e:
|
|
62
|
+
raise YemekError(f"network error fetching {url}: {e}") from e
|
|
63
|
+
if status == 403:
|
|
64
|
+
raise YemekError("yemek.com refused the request (HTTP 403, bot wall) — try again later")
|
|
65
|
+
if status == 429:
|
|
66
|
+
raise YemekError("yemek.com rate-limited the request (HTTP 429) — try again later")
|
|
67
|
+
if status == 404:
|
|
68
|
+
raise YemekError(f"yemek.com has no page at {url} (HTTP 404)")
|
|
69
|
+
if status != 200:
|
|
70
|
+
raise YemekError(f"yemek.com returned HTTP {status} for {url}")
|
|
71
|
+
return text
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def fetch_search_json(query: str, rows: int = 12, recipes_start: int = 0, posts_start: int = 0) -> str:
|
|
75
|
+
"""Public search JSON: /ara/sonuclar/?q=<query>&rows=<n>.
|
|
76
|
+
Recipes come back 3 per page — paginate with recipes_start."""
|
|
77
|
+
url = (
|
|
78
|
+
f"{BASE}/ara/sonuclar/?q={quote(query)}&rows={rows}"
|
|
79
|
+
f"&recipes_start={recipes_start}&posts_start={posts_start}"
|
|
80
|
+
)
|
|
81
|
+
return _get(url, accept_json=True)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def fetch_recipe_page(slug: str) -> str:
|
|
85
|
+
return _get(f"{BASE}/tarif/{slug}/")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def fetch_home() -> str:
|
|
89
|
+
return _get(f"{BASE}/")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def fetch_bugun() -> str:
|
|
93
|
+
return _get(f"{BASE}/bugun-ne-pisirsem/")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def fetch_tarif_list(query: str | None = None) -> str:
|
|
97
|
+
"""Server-rendered /tarif/ listing, optionally filtered by ?q=."""
|
|
98
|
+
url = f"{BASE}/tarif/"
|
|
99
|
+
if query:
|
|
100
|
+
url += f"?q={quote(query)}"
|
|
101
|
+
return _get(url)
|