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.
@@ -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,2 @@
1
+ """yemek_com_mcp — unofficial read-only MCP server for Yemek.com recipes."""
2
+ __version__ = "0.1.0"
@@ -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)