n11-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.
- n11_mcp-0.1.0/.github/workflows/publish.yml +20 -0
- n11_mcp-0.1.0/.github/workflows/registry.yml +61 -0
- n11_mcp-0.1.0/LICENSE +21 -0
- n11_mcp-0.1.0/PKG-INFO +118 -0
- n11_mcp-0.1.0/README-fa.md +85 -0
- n11_mcp-0.1.0/README-tr.md +85 -0
- n11_mcp-0.1.0/README.md +106 -0
- n11_mcp-0.1.0/pyproject.toml +22 -0
- n11_mcp-0.1.0/server.json +1 -0
- n11_mcp-0.1.0/src/n11_mcp/__init__.py +1 -0
- n11_mcp-0.1.0/src/n11_mcp/http.py +88 -0
- n11_mcp-0.1.0/src/n11_mcp/products.py +317 -0
- n11_mcp-0.1.0/src/n11_mcp/server.py +139 -0
- n11_mcp-0.1.0/tests/__init__.py +0 -0
- n11_mcp-0.1.0/tests/fixtures/home-categories.html +25 -0
- n11_mcp-0.1.0/tests/fixtures/product-iphone17.html +5 -0
- n11_mcp-0.1.0/tests/fixtures/search-iphone.html +3 -0
- n11_mcp-0.1.0/tests/test_live.py +120 -0
- n11_mcp-0.1.0/tests/test_products.py +187 -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
|
n11_mcp-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mohammad Abbasi
|
|
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.
|
n11_mcp-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: n11-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Unofficial read-only MCP server for n11.com shopping (search, product details, reviews, categories, price stats)
|
|
5
|
+
Author: Mohammad Abbasi
|
|
6
|
+
License: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Requires-Dist: curl-cffi>=0.7
|
|
10
|
+
Requires-Dist: mcp<2
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
# n11-mcp 🇹🇷
|
|
14
|
+
|
|
15
|
+
mcp-name: io.github.mohammad-abbasi-sh/n11-mcp
|
|
16
|
+
|
|
17
|
+
Unofficial **read-only** MCP server for **n11.com**, Türkiye's #3 marketplace — search millions of products, read product details (price, list price, stock, rating, seller, category), read customer reviews, browse the 11 top categories, and get price statistics (min / median / mean / max) for any query.
|
|
18
|
+
|
|
19
|
+
> Built by Mohammad Abbasi as part of the "Turkey MCP factory" (Sepehr-style pipeline). Not affiliated with n11.com.
|
|
20
|
+
|
|
21
|
+
## For AI agents
|
|
22
|
+
|
|
23
|
+
Add to your MCP client config (stdio):
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"n11": {
|
|
29
|
+
"command": "uvx",
|
|
30
|
+
"args": ["n11-mcp"]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or run locally: `python -m n11_mcp.server` (needs `mcp<2`, `curl-cffi`).
|
|
37
|
+
|
|
38
|
+
## Tools
|
|
39
|
+
|
|
40
|
+
| Tool | Description |
|
|
41
|
+
|------|-------------|
|
|
42
|
+
| `n11_search` | Search products by keyword — `query` ("iphone", "kablosuz kulaklık"), `sort` ("relevance" / "price_asc" / "price_desc"), `limit` 1..24, `page` 1-based; returns title, price in TRY, list price, brand, SKU, URL, image, stock status |
|
|
43
|
+
| `n11_product_detail` | Detail of one product (`product` = full URL or path with "/urun/…"): name, brand, price in TRY, list price, stock status, rating, review count, seller (company, store name, authorized-store badge, grade, shipment city), category path, description, images |
|
|
44
|
+
| `n11_list_categories` | The 11 top categories: Turkish name, URL, slug |
|
|
45
|
+
| `n11_price_stats` | Price statistics for a query: count, min / median / mean / max price in TRY across the first page |
|
|
46
|
+
| `n11_product_reviews` | Recent customer reviews of one product: reviewer and star rating (n11 does not server-render review bodies) |
|
|
47
|
+
|
|
48
|
+
Example output (`n11_search` / "iphone"):
|
|
49
|
+
|
|
50
|
+
```json
|
|
51
|
+
{"query": "iphone", "sort": "relevance", "page": 1, "count": 10, "products": [
|
|
52
|
+
{"title": "Apple iPhone 17 Pro Max 256 GB (Apple Türkiye Garantili)",
|
|
53
|
+
"url": "https://www.n11.com/urun/apple-iphone-17-pro-max-256-gb-apple-turkiye-garantili-100816929",
|
|
54
|
+
"sku": "127340433485", "brand": "Apple", "price_try": 112434.28,
|
|
55
|
+
"original_price_try": 117119.04, "price_currency": "TRY",
|
|
56
|
+
"in_stock": true, "image": "https://n11scdn.akamaized.net/…"}, …]}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Example output (`n11_product_detail` / iPhone 17 Pro Max):
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
{"name": "Apple iPhone 17 Pro Max 256 GB (Apple Türkiye Garantili) Turuncu 256 GB Fiyatları ve Özellikleri",
|
|
63
|
+
"sku": "127340433485", "brand": "Apple", "price_try": 112434.28,
|
|
64
|
+
"original_price_try": 117119.04, "price_currency": "TRY", "in_stock": true,
|
|
65
|
+
"rating": 5.0, "review_count": 227,
|
|
66
|
+
"category": "Elektronik > Telefon & Aksesuarları > Cep Telefonu > Apple Cep Telefonu",
|
|
67
|
+
"seller_company": "SALİM TEKEMEN", "seller": "TKMNBILISIM",
|
|
68
|
+
"seller_badge": "Yetkili Mağaza", "seller_grade": 100,
|
|
69
|
+
"shipment_city": "Antalya", "shipment_condition": "Ücretsiz Kargo", …}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Example output (`n11_price_stats` / "iphone"):
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{"query": "iphone", "sort": "relevance", "count": 10, "min_try": 57199.0,
|
|
76
|
+
"median_try": 106099.49, "mean_try": 100880.09, "max_try": 150999.55}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## How it works
|
|
80
|
+
|
|
81
|
+
n11.com answers plain HTTP clients, and the transport uses Chrome TLS
|
|
82
|
+
impersonation (`curl-cffi`, `chrome120`) so a stricter bot check does not
|
|
83
|
+
break the server. There is no public JSON API — data comes from the
|
|
84
|
+
site's server-rendered pages: search pages carry a schema.org ItemList
|
|
85
|
+
JSON-LD block (20 products per page), product pages carry a schema.org
|
|
86
|
+
Product JSON-LD block plus a breadcrumb category path and an embedded
|
|
87
|
+
seller JSON object, and the homepage carries the 11 top categories.
|
|
88
|
+
Requests are spaced ~0.8s apart; everything is read-only, no login,
|
|
89
|
+
no API key.
|
|
90
|
+
|
|
91
|
+
## Install
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
uvx n11-mcp # run directly (needs uv)
|
|
95
|
+
# or
|
|
96
|
+
pip install n11-mcp
|
|
97
|
+
n11-mcp # starts the MCP server on stdio
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Tests
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
python -m venv .venv && .venv/bin/pip install -e .
|
|
104
|
+
.venv/bin/python -m unittest discover -s tests -v
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Offline tests run on recorded fixtures (real pages fetched 2026-11-06);
|
|
108
|
+
live tests hit n11.com.
|
|
109
|
+
|
|
110
|
+
## Roadmap
|
|
111
|
+
|
|
112
|
+
- Category-scoped search (search within one category slug)
|
|
113
|
+
- Other sellers of the same product (compare prices per seller)
|
|
114
|
+
- Price history (track a SKU over days)
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# n11-mcp 🇹🇷
|
|
2
|
+
|
|
3
|
+
mcp-name: io.github.mohammad-abbasi-sh/n11-mcp
|
|
4
|
+
|
|
5
|
+
سرور MCP غیررسمی و **فقطخواندنی** برای **n11.com**، مارکتپلیس شمارهی
|
|
6
|
+
۳ ترکیه — جستوجو در میلیونها کالا، خواندن جزئیات کالا (قیمت، قیمت
|
|
7
|
+
لیست، موجودی، امتیاز، فروشنده، دسته)، خواندن نظرهای مشتریان، مرور ۱۱
|
|
8
|
+
دستهی اصلی، و گرفتن آمار قیمت (کمترین / میانه / میانگین / بیشترین)
|
|
9
|
+
برای هر جستوجو.
|
|
10
|
+
|
|
11
|
+
> ساختهی محمد عباسی در چارچوب «کارخانهی MCP ترکیه» (به سبک سپهر). هیچ
|
|
12
|
+
> وابستگیای به n11.com ندارد.
|
|
13
|
+
|
|
14
|
+
## برای ایجنتهای هوش مصنوعی
|
|
15
|
+
|
|
16
|
+
به پیکربندی MCP client خود اضافه کنید (stdio):
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"n11": {
|
|
22
|
+
"command": "uvx",
|
|
23
|
+
"args": ["n11-mcp"]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
یا بهصورت محلی اجرا کنید: `python -m n11_mcp.server`
|
|
30
|
+
(نیازمند `mcp<2` و `curl-cffi`).
|
|
31
|
+
|
|
32
|
+
## ابزارها
|
|
33
|
+
|
|
34
|
+
| ابزار | توضیح |
|
|
35
|
+
|------|-------|
|
|
36
|
+
| `n11_search` | جستوجوی کالا با کلیدواژه — `query` (مثل «iphone» یا «kablosuz kulaklık»)، `sort` («relevance» / «price_asc» / «price_desc»)، `limit` از ۱ تا ۲۴، `page` از ۱؛ برمیگرداند: عنوان، قیمت به لیر، قیمت لیست، برند، SKU، آدرس، عکس، وضعیت موجودی |
|
|
37
|
+
| `n11_product_detail` | جزئیات یک کالا (`product` = آدرس کامل یا مسیر حاوی «/urun/…»): نام، برند، قیمت به لیر، قیمت لیست، وضعیت موجودی، امتیاز، تعداد نظر، فروشنده (شرکت، نام فروشگاه، نشان فروشگاه مجاز، امتیاز فروشنده، شهر ارسال)، مسیر دسته، توضیح، عکسها |
|
|
38
|
+
| `n11_list_categories` | ۱۱ دستهی اصلی: نام ترکی، آدرس، اسلاگ |
|
|
39
|
+
| `n11_price_stats` | آمار قیمت برای یک جستوجو: تعداد، کمترین / میانه / میانگین / بیشترین قیمت (لیر) |
|
|
40
|
+
| `n11_product_reviews` | نظرهای اخیر مشتریان برای یک کالا: نویسنده و امتیاز ستارهای (سایت n11 متن نظرها را سمت سرور رندر نمیکند) |
|
|
41
|
+
|
|
42
|
+
نمونهی خروجی (`n11_search` / «iphone»):
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{"query": "iphone", "count": 10, "products": [
|
|
46
|
+
{"title": "Apple iPhone 17 Pro Max 256 GB (Apple Türkiye Garantili)",
|
|
47
|
+
"url": "https://www.n11.com/urun/apple-iphone-17-pro-max-256-gb-apple-turkiye-garantili-100816929",
|
|
48
|
+
"sku": "127340433485", "brand": "Apple", "price_try": 112434.28,
|
|
49
|
+
"original_price_try": 117119.04, "in_stock": true}, …]}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## چطور کار میکند؟
|
|
53
|
+
|
|
54
|
+
سایت n11.com به کلاینتهای HTTP ساده جواب میدهد؛ با این حال لایهی
|
|
55
|
+
انتقال از جعل TLS کروم (`curl-cffi`، `chrome120`) استفاده میکند تا
|
|
56
|
+
یک بررسی ربات سختگیرانهتر سرور را از کار نیندازد. API عمومی JSON
|
|
57
|
+
ندارد — دادهها از HTML رندرشدهی سمت سرور میآیند: صفحههای جستوجو
|
|
58
|
+
بلوک ItemList از نوع JSON-LD دارند (۲۰ کالا در هر صفحه)، صفحههای کالا
|
|
59
|
+
JSON-LD از نوع Product بههمراه مسیر دسته (breadcrumb) و آبجکت JSON
|
|
60
|
+
فروشنده، و صفحهی اصلی ۱۱ دستهی اصلی را دارد. درخواستها با فاصلهی
|
|
61
|
+
حدود ۰٫۸ ثانیه فرستاده میشوند؛ همهچیز فقطخواندنی است، بدون لاگین و
|
|
62
|
+
بدون کلید API.
|
|
63
|
+
|
|
64
|
+
## نصب
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
uvx n11-mcp # اجرای مستقیم (نیازمند uv)
|
|
68
|
+
# یا
|
|
69
|
+
pip install n11-mcp
|
|
70
|
+
n11-mcp # اجرای سرور MCP روی stdio
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## تستها
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
python -m venv .venv && .venv/bin/pip install -e .
|
|
77
|
+
.venv/bin/python -m unittest discover -s tests -v
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
تستهای آفلاین روی نمونههای ضبطشده از صفحههای واقعی اجرا میشوند
|
|
81
|
+
(دریافتشده در ۲۰۲۶-۱۱-۰۶)؛ تستهای زنده به n11.com وصل میشوند.
|
|
82
|
+
|
|
83
|
+
## لایسنس
|
|
84
|
+
|
|
85
|
+
MIT — ببینید [LICENSE](LICENSE).
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# n11-mcp 🇹🇷
|
|
2
|
+
|
|
3
|
+
mcp-name: io.github.mohammad-abbasi-sh/n11-mcp
|
|
4
|
+
|
|
5
|
+
Türkiye'nin 3 numaralı pazaryeri **n11.com** için resmi olmayan,
|
|
6
|
+
**salt okunur** MCP sunucusu — milyonlarca üründe arama yapın, ürün
|
|
7
|
+
detaylarını okuyun (fiyat, liste fiyatı, stok, puan, satıcı, kategori),
|
|
8
|
+
müşteri yorumlarını okuyun, 11 ana kategoriye göz atın ve herhangi bir
|
|
9
|
+
sorgu için fiyat istatistikleri (min / medyan / ortalama / maks) alın.
|
|
10
|
+
|
|
11
|
+
> Mohammad Abbasi tarafından "Turkey MCP factory" (Sepehr tarzı süreç)
|
|
12
|
+
> kapsamında geliştirildi. n11.com ile bağlantısı yoktur.
|
|
13
|
+
|
|
14
|
+
## Yapay zekâ ajanları için
|
|
15
|
+
|
|
16
|
+
MCP istemci yapılandırmanıza ekleyin (stdio):
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"n11": {
|
|
22
|
+
"command": "uvx",
|
|
23
|
+
"args": ["n11-mcp"]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
veya yerel çalıştırın: `python -m n11_mcp.server`
|
|
30
|
+
(`mcp<2`, `curl-cffi` gerekir).
|
|
31
|
+
|
|
32
|
+
## Araçlar
|
|
33
|
+
|
|
34
|
+
| Araç | Açıklama |
|
|
35
|
+
|------|----------|
|
|
36
|
+
| `n11_search` | Anahtar kelimeyle ürün arama — `query` ("iphone", "kablosuz kulaklık"), `sort` ("relevance" / "price_asc" / "price_desc"), `limit` 1..24, `page` 1'den başlar; başlık, TL fiyat, liste fiyatı, marka, SKU, URL, görsel, stok durumu döndürür |
|
|
37
|
+
| `n11_product_detail` | Bir ürünün detayı (`product` = "/urun/…" içeren URL veya yol): ad, marka, TL fiyat, liste fiyatı, stok durumu, puan, yorum sayısı, satıcı (şirket, mağaza adı, yetkili mağaza rozeti, satıcı puanı, gönderim şehri), kategori yolu, açıklama, görseller |
|
|
38
|
+
| `n11_list_categories` | 11 ana kategori: Türkçe ad, URL, slug |
|
|
39
|
+
| `n11_price_stats` | Bir sorgunun fiyat istatistikleri: adet, min / medyan / ortalama / maks fiyat (TL) |
|
|
40
|
+
| `n11_product_reviews` | Bir ürünün son müşteri yorumları: yorumcu ve yıldız puanı (n11 yorum metinlerini sunucu taraflı işlemez) |
|
|
41
|
+
|
|
42
|
+
Örnek çıktı (`n11_search` / "iphone"):
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{"query": "iphone", "count": 10, "products": [
|
|
46
|
+
{"title": "Apple iPhone 17 Pro Max 256 GB (Apple Türkiye Garantili)",
|
|
47
|
+
"url": "https://www.n11.com/urun/apple-iphone-17-pro-max-256-gb-apple-turkiye-garantili-100816929",
|
|
48
|
+
"sku": "127340433485", "brand": "Apple", "price_try": 112434.28,
|
|
49
|
+
"original_price_try": 117119.04, "in_stock": true}, …]}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Nasıl çalışır?
|
|
53
|
+
|
|
54
|
+
n11.com düz HTTP istemcilerine yanıt verir; aktarım yine de Chrome TLS
|
|
55
|
+
taklidi (`curl-cffi`, `chrome120`) kullanır, böylece daha katı bir bot
|
|
56
|
+
kontrolü sunucuyu bozmaz. Herkese açık bir JSON API yoktur — veriler
|
|
57
|
+
sitenin sunucu taraflı işlenmiş HTML sayfalarından gelir: arama
|
|
58
|
+
sayfaları schema.org ItemList JSON-LD bloğu (sayfa başına 20 ürün),
|
|
59
|
+
ürün sayfaları schema.org Product JSON-LD + breadcrumb kategori yolu +
|
|
60
|
+
gömülü satıcı JSON nesnesi, ana sayfa ise 11 ana kategoriyi taşır.
|
|
61
|
+
İstekler ~0.8 sn arayla yapılır; her şey salt okunurdur, giriş
|
|
62
|
+
gerekmez, API anahtarı gerekmez.
|
|
63
|
+
|
|
64
|
+
## Kurulum
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
uvx n11-mcp # doğrudan çalıştır (uv gerekir)
|
|
68
|
+
# veya
|
|
69
|
+
pip install n11-mcp
|
|
70
|
+
n11-mcp # MCP sunucusunu stdio üzerinde başlatır
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Testler
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
python -m venv .venv && .venv/bin/pip install -e .
|
|
77
|
+
.venv/bin/python -m unittest discover -s tests -v
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Çevrimdışı testler kayıtlı örnek sayfalarla çalışır (gerçek sayfalar,
|
|
81
|
+
2026-11-06); canlı testler n11.com'a bağlanır.
|
|
82
|
+
|
|
83
|
+
## Lisans
|
|
84
|
+
|
|
85
|
+
MIT — bkz. [LICENSE](LICENSE).
|
n11_mcp-0.1.0/README.md
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# n11-mcp 🇹🇷
|
|
2
|
+
|
|
3
|
+
mcp-name: io.github.mohammad-abbasi-sh/n11-mcp
|
|
4
|
+
|
|
5
|
+
Unofficial **read-only** MCP server for **n11.com**, Türkiye's #3 marketplace — search millions of products, read product details (price, list price, stock, rating, seller, category), read customer reviews, browse the 11 top categories, and get price statistics (min / median / mean / max) for any query.
|
|
6
|
+
|
|
7
|
+
> Built by Mohammad Abbasi as part of the "Turkey MCP factory" (Sepehr-style pipeline). Not affiliated with n11.com.
|
|
8
|
+
|
|
9
|
+
## For AI agents
|
|
10
|
+
|
|
11
|
+
Add to your MCP client config (stdio):
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"mcpServers": {
|
|
16
|
+
"n11": {
|
|
17
|
+
"command": "uvx",
|
|
18
|
+
"args": ["n11-mcp"]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or run locally: `python -m n11_mcp.server` (needs `mcp<2`, `curl-cffi`).
|
|
25
|
+
|
|
26
|
+
## Tools
|
|
27
|
+
|
|
28
|
+
| Tool | Description |
|
|
29
|
+
|------|-------------|
|
|
30
|
+
| `n11_search` | Search products by keyword — `query` ("iphone", "kablosuz kulaklık"), `sort` ("relevance" / "price_asc" / "price_desc"), `limit` 1..24, `page` 1-based; returns title, price in TRY, list price, brand, SKU, URL, image, stock status |
|
|
31
|
+
| `n11_product_detail` | Detail of one product (`product` = full URL or path with "/urun/…"): name, brand, price in TRY, list price, stock status, rating, review count, seller (company, store name, authorized-store badge, grade, shipment city), category path, description, images |
|
|
32
|
+
| `n11_list_categories` | The 11 top categories: Turkish name, URL, slug |
|
|
33
|
+
| `n11_price_stats` | Price statistics for a query: count, min / median / mean / max price in TRY across the first page |
|
|
34
|
+
| `n11_product_reviews` | Recent customer reviews of one product: reviewer and star rating (n11 does not server-render review bodies) |
|
|
35
|
+
|
|
36
|
+
Example output (`n11_search` / "iphone"):
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{"query": "iphone", "sort": "relevance", "page": 1, "count": 10, "products": [
|
|
40
|
+
{"title": "Apple iPhone 17 Pro Max 256 GB (Apple Türkiye Garantili)",
|
|
41
|
+
"url": "https://www.n11.com/urun/apple-iphone-17-pro-max-256-gb-apple-turkiye-garantili-100816929",
|
|
42
|
+
"sku": "127340433485", "brand": "Apple", "price_try": 112434.28,
|
|
43
|
+
"original_price_try": 117119.04, "price_currency": "TRY",
|
|
44
|
+
"in_stock": true, "image": "https://n11scdn.akamaized.net/…"}, …]}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Example output (`n11_product_detail` / iPhone 17 Pro Max):
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{"name": "Apple iPhone 17 Pro Max 256 GB (Apple Türkiye Garantili) Turuncu 256 GB Fiyatları ve Özellikleri",
|
|
51
|
+
"sku": "127340433485", "brand": "Apple", "price_try": 112434.28,
|
|
52
|
+
"original_price_try": 117119.04, "price_currency": "TRY", "in_stock": true,
|
|
53
|
+
"rating": 5.0, "review_count": 227,
|
|
54
|
+
"category": "Elektronik > Telefon & Aksesuarları > Cep Telefonu > Apple Cep Telefonu",
|
|
55
|
+
"seller_company": "SALİM TEKEMEN", "seller": "TKMNBILISIM",
|
|
56
|
+
"seller_badge": "Yetkili Mağaza", "seller_grade": 100,
|
|
57
|
+
"shipment_city": "Antalya", "shipment_condition": "Ücretsiz Kargo", …}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Example output (`n11_price_stats` / "iphone"):
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{"query": "iphone", "sort": "relevance", "count": 10, "min_try": 57199.0,
|
|
64
|
+
"median_try": 106099.49, "mean_try": 100880.09, "max_try": 150999.55}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## How it works
|
|
68
|
+
|
|
69
|
+
n11.com answers plain HTTP clients, and the transport uses Chrome TLS
|
|
70
|
+
impersonation (`curl-cffi`, `chrome120`) so a stricter bot check does not
|
|
71
|
+
break the server. There is no public JSON API — data comes from the
|
|
72
|
+
site's server-rendered pages: search pages carry a schema.org ItemList
|
|
73
|
+
JSON-LD block (20 products per page), product pages carry a schema.org
|
|
74
|
+
Product JSON-LD block plus a breadcrumb category path and an embedded
|
|
75
|
+
seller JSON object, and the homepage carries the 11 top categories.
|
|
76
|
+
Requests are spaced ~0.8s apart; everything is read-only, no login,
|
|
77
|
+
no API key.
|
|
78
|
+
|
|
79
|
+
## Install
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
uvx n11-mcp # run directly (needs uv)
|
|
83
|
+
# or
|
|
84
|
+
pip install n11-mcp
|
|
85
|
+
n11-mcp # starts the MCP server on stdio
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Tests
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
python -m venv .venv && .venv/bin/pip install -e .
|
|
92
|
+
.venv/bin/python -m unittest discover -s tests -v
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Offline tests run on recorded fixtures (real pages fetched 2026-11-06);
|
|
96
|
+
live tests hit n11.com.
|
|
97
|
+
|
|
98
|
+
## Roadmap
|
|
99
|
+
|
|
100
|
+
- Category-scoped search (search within one category slug)
|
|
101
|
+
- Other sellers of the same product (compare prices per seller)
|
|
102
|
+
- Price history (track a SKU over days)
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "n11-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Unofficial read-only MCP server for n11.com shopping (search, product details, reviews, categories, price stats)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"mcp<2",
|
|
14
|
+
"curl-cffi>=0.7",
|
|
15
|
+
]
|
|
16
|
+
authors = [{ name = "Mohammad Abbasi" }]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
n11-mcp = "n11_mcp.server:main"
|
|
20
|
+
|
|
21
|
+
[tool.hatch.build.targets.wheel]
|
|
22
|
+
packages = ["src/n11_mcp"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"$schema":"https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json","description":"Unofficial read-only MCP server for n11.com shopping: search, details, reviews, prices.","name":"io.github.mohammad-abbasi-sh/n11-mcp","packages":[{"identifier":"n11-mcp","registryBaseUrl":"https://pypi.org","registryType":"pypi","transport":{"type":"stdio"},"version":"0.1.0"}],"repository":{"source":"github","url":"https://github.com/mohammad-abbasi-sh/n11-mcp"},"title":"n11.com Shopping","version":"0.1.0","websiteUrl":"https://github.com/mohammad-abbasi-sh/n11-mcp"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""n11_mcp — unofficial read-only MCP server for n11.com."""
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""HTTP layer for n11_mcp.
|
|
2
|
+
|
|
3
|
+
n11.com answers HTTP 200 to plain HTTP clients with a browser UA
|
|
4
|
+
(verified live 2026-11-06: homepage, search, product pages). The
|
|
5
|
+
transport still uses curl-cffi chrome120 impersonation, the same proven
|
|
6
|
+
pattern as the other Turkey-factory servers, so a stricter bot check
|
|
7
|
+
does not break the server overnight. Requests are kept gentle (one at
|
|
8
|
+
a time, ~0.8s apart) and read-only.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import time
|
|
13
|
+
from urllib.parse import quote_plus, urlparse
|
|
14
|
+
|
|
15
|
+
from curl_cffi import requests as _r
|
|
16
|
+
|
|
17
|
+
BASE = "https://www.n11.com"
|
|
18
|
+
TIMEOUT = 30.0
|
|
19
|
+
MIN_INTERVAL = 0.8 # seconds between requests — be polite
|
|
20
|
+
|
|
21
|
+
_HEADERS = {
|
|
22
|
+
"Accept-Language": "tr-TR,tr;q=0.9,en;q=0.8",
|
|
23
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
_last_fetch = 0.0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class N11Error(RuntimeError):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _get(url: str) -> str:
|
|
34
|
+
global _last_fetch
|
|
35
|
+
wait = MIN_INTERVAL - (time.monotonic() - _last_fetch)
|
|
36
|
+
if wait > 0:
|
|
37
|
+
time.sleep(wait)
|
|
38
|
+
try:
|
|
39
|
+
resp = _r.get(url, impersonate="chrome120", headers=_HEADERS, timeout=TIMEOUT)
|
|
40
|
+
except Exception as e:
|
|
41
|
+
raise N11Error(f"network error fetching {url}: {e}") from e
|
|
42
|
+
finally:
|
|
43
|
+
_last_fetch = time.monotonic()
|
|
44
|
+
if resp.status_code == 403:
|
|
45
|
+
raise N11Error(
|
|
46
|
+
"n11.com refused the request (HTTP 403, bot wall) — try again later"
|
|
47
|
+
)
|
|
48
|
+
if resp.status_code == 429:
|
|
49
|
+
raise N11Error(
|
|
50
|
+
"n11.com rate-limited the request (HTTP 429) — try again later"
|
|
51
|
+
)
|
|
52
|
+
if resp.status_code == 404:
|
|
53
|
+
raise N11Error(f"page not found (HTTP 404): {url}")
|
|
54
|
+
if resp.status_code != 200:
|
|
55
|
+
raise N11Error(f"unexpected HTTP {resp.status_code} fetching {url}")
|
|
56
|
+
return resp.text
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def fetch_search_html(query: str, sort_param: str | None = None, page: int = 1) -> str:
|
|
60
|
+
"""Search page HTML. sort_param: 'PRICE_LOW' (price asc) or 'PRICE_HIGH'."""
|
|
61
|
+
url = f"{BASE}/arama?q={quote_plus(query)}"
|
|
62
|
+
if sort_param:
|
|
63
|
+
url += f"&srt={sort_param}"
|
|
64
|
+
if page > 1:
|
|
65
|
+
url += f"&pg={page}"
|
|
66
|
+
return _get(url)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def fetch_product_html(product: str) -> str:
|
|
70
|
+
"""Product page HTML. product: full URL or site path '/urun/<slug>-<id>'."""
|
|
71
|
+
path = product.strip()
|
|
72
|
+
if path.startswith("http"):
|
|
73
|
+
parsed = urlparse(path)
|
|
74
|
+
if "n11.com" not in parsed.netloc:
|
|
75
|
+
raise N11Error("product must be an n11.com URL or path")
|
|
76
|
+
path = parsed.path
|
|
77
|
+
if not path.startswith("/"):
|
|
78
|
+
path = "/" + path
|
|
79
|
+
if "/urun/" not in path:
|
|
80
|
+
raise N11Error(
|
|
81
|
+
"product must be a product URL/path containing '/urun/…' "
|
|
82
|
+
"(find one with n11_search first)"
|
|
83
|
+
)
|
|
84
|
+
return _get(f"{BASE}{path}")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def fetch_home_html() -> str:
|
|
88
|
+
return _get(f"{BASE}/")
|