30book-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.
Files changed (32) hide show
  1. 30book_mcp-0.1.0/.github/workflows/ci.yml +22 -0
  2. 30book_mcp-0.1.0/.github/workflows/publish.yml +51 -0
  3. 30book_mcp-0.1.0/.gitignore +6 -0
  4. 30book_mcp-0.1.0/CLAUDE.md +71 -0
  5. 30book_mcp-0.1.0/LICENSE +21 -0
  6. 30book_mcp-0.1.0/PKG-INFO +124 -0
  7. 30book_mcp-0.1.0/README.md +98 -0
  8. 30book_mcp-0.1.0/pyproject.toml +57 -0
  9. 30book_mcp-0.1.0/server.json +32 -0
  10. 30book_mcp-0.1.0/src/sibook_mcp/__init__.py +5 -0
  11. 30book_mcp-0.1.0/src/sibook_mcp/blazor.py +550 -0
  12. 30book_mcp-0.1.0/src/sibook_mcp/catalog.py +261 -0
  13. 30book_mcp-0.1.0/src/sibook_mcp/dom.py +120 -0
  14. 30book_mcp-0.1.0/src/sibook_mcp/http.py +152 -0
  15. 30book_mcp-0.1.0/src/sibook_mcp/parse.py +244 -0
  16. 30book_mcp-0.1.0/src/sibook_mcp/product.py +133 -0
  17. 30book_mcp-0.1.0/src/sibook_mcp/registry.py +16 -0
  18. 30book_mcp-0.1.0/src/sibook_mcp/server.py +52 -0
  19. 30book_mcp-0.1.0/tests/conftest.py +73 -0
  20. 30book_mcp-0.1.0/tests/fixtures/book_103736_soldout.html +837 -0
  21. 30book_mcp-0.1.0/tests/fixtures/book_167515.html +843 -0
  22. 30book_mcp-0.1.0/tests/fixtures/book_6426_translated.html +823 -0
  23. 30book_mcp-0.1.0/tests/fixtures/find_publisher.html +3 -0
  24. 30book_mcp-0.1.0/tests/fixtures/home_links.html +10 -0
  25. 30book_mcp-0.1.0/tests/fixtures/list_empty.html +91 -0
  26. 30book_mcp-0.1.0/tests/fixtures/list_publisher.html +785 -0
  27. 30book_mcp-0.1.0/tests/fixtures/list_search.html +657 -0
  28. 30book_mcp-0.1.0/tests/test_blazor.py +265 -0
  29. 30book_mcp-0.1.0/tests/test_http.py +107 -0
  30. 30book_mcp-0.1.0/tests/test_live.py +101 -0
  31. 30book_mcp-0.1.0/tests/test_tools.py +157 -0
  32. 30book_mcp-0.1.0/uv.lock +1173 -0
@@ -0,0 +1,22 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python: ["3.10", "3.11", "3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v7
16
+ - uses: astral-sh/setup-uv@v10.2.0
17
+ with:
18
+ python-version: ${{ matrix.python }}
19
+ - run: uv sync --locked
20
+ - run: uv run ruff check .
21
+ - run: uv run ruff format --check .
22
+ - run: uv run pytest -q
@@ -0,0 +1,51 @@
1
+ name: Publish
2
+
3
+ # Tag a release (git tag v0.1.0 && git push --tags) after bumping the version in
4
+ # pyproject.toml and server.json.
5
+ on:
6
+ push:
7
+ tags: ["v*"]
8
+
9
+ jobs:
10
+ pypi:
11
+ runs-on: ubuntu-latest
12
+ environment: pypi
13
+ permissions:
14
+ id-token: write # PyPI trusted publishing
15
+ contents: write # GitHub Release
16
+ steps:
17
+ - uses: actions/checkout@v7
18
+ - uses: astral-sh/setup-uv@v10.2.0
19
+ - name: Check versions match the tag
20
+ run: |
21
+ TAG=${GITHUB_REF_NAME#v}
22
+ grep -q "^version = \"$TAG\"" pyproject.toml
23
+ test "$(jq -r .version server.json)" = "$TAG"
24
+ test "$(jq -r '.packages[0].version' server.json)" = "$TAG"
25
+ - run: uv sync --locked && uv run pytest -q
26
+ - run: uv build
27
+ - uses: pypa/gh-action-pypi-publish@release/v1
28
+ - run: gh release create "$GITHUB_REF_NAME" dist/* --generate-notes --title "$GITHUB_REF_NAME"
29
+ env:
30
+ GH_TOKEN: ${{ github.token }}
31
+
32
+ mcp-registry:
33
+ needs: pypi
34
+ runs-on: ubuntu-latest
35
+ permissions:
36
+ id-token: write # MCP Registry login via GitHub OIDC
37
+ contents: read
38
+ steps:
39
+ - uses: actions/checkout@v7
40
+ - name: Wait for PyPI to serve the new version
41
+ run: |
42
+ for i in $(seq 1 30); do
43
+ curl -sf "https://pypi.org/pypi/30book-mcp/${GITHUB_REF_NAME#v}/json" >/dev/null && exit 0
44
+ sleep 10
45
+ done
46
+ exit 1
47
+ - name: Install mcp-publisher
48
+ run: |
49
+ curl -L "https://github.com/modelcontextprotocol/registry/releases/download/v1.8.1/mcp-publisher_$(uname -s | tr '[:upper:]' '[:lower:]')_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" | tar xz mcp-publisher
50
+ - run: ./mcp-publisher login github-oidc
51
+ - run: ./mcp-publisher publish
@@ -0,0 +1,6 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ dist/
5
+ .pytest_cache/
6
+ .ruff_cache/
@@ -0,0 +1,71 @@
1
+ # 30book-mcp
2
+
3
+ Unofficial, read-only MCP server for 30book (30book.com), an Iranian online bookstore with about 200,000 printed books.
4
+ Intended GitHub `sepehr071/30book-mcp`, PyPI `30book-mcp`, MCP Registry `io.github.sepehr071/30book-mcp` (not published yet,
5
+ no git repo yet). Python package `sibook_mcp` (a module name cannot start with a digit). Sibling of `../shopino-mcp`
6
+ and `../masterkala-mcp`; same layout and idioms, keep them in step.
7
+
8
+ API map this server is built on (pages, circuit transport, quirks, test report): `../30book/`.
9
+
10
+ ## Layout
11
+
12
+ - `src/sibook_mcp/http.py`: the only HTTP code. One shared `httpx.AsyncClient` (`trust_env=False`, proxy only from
13
+ `SIBOOK_MCP_PROXY`), `html(path)` for plain pages, `render(path, ready, type_into=)` for list pages (opens a Blazor
14
+ circuit, up to 3 attempts), `ApiError(ToolError)`, `set_transport()` for tests, `query()` builds `?a=1&b=True`.
15
+ - `blazor.py`: the Blazor Server client (MessagePack + SignalR long polling + render-batch DOM). `dom.py`: tiny DOM and
16
+ HTML parser. `parse.py`: cards, book page, reviews to dicts (Toman ints). `registry.py` / `server.py`: tool registry,
17
+ `INSTRUCTIONS`.
18
+ - Tools: `catalog.py` (search, browse, publisher, author, best sellers, new releases, deals, find publisher / person),
19
+ `product.py` (book, similar, reviews, categories, collections). 14 tools, all `sb_*`.
20
+ - `tests/`: `test_tools.py` (tools on trimmed real pages in `tests/fixtures/`, `http.html` / `http.render` patched),
21
+ `test_blazor.py` (codecs, a synthetic render-batch writer, a scripted fake SignalR server), `test_http.py` (client,
22
+ errors, README tables), `test_live.py` (`@pytest.mark.live`, one test per tool, about 2 minutes).
23
+
24
+ ## Commands
25
+
26
+ uv needs the local proxy to download packages; the server and tests call 30book directly (env proxies are ignored).
27
+
28
+ ```bash
29
+ export HTTPS_PROXY=http://127.0.0.1:10898 HTTP_PROXY=http://127.0.0.1:10898
30
+ uv sync
31
+ uv run ruff check . && uv run ruff format --check .
32
+ uv run pytest -q # offline
33
+ uv run pytest -m live -q # real site
34
+ uv build
35
+ ```
36
+
37
+ Git Bash mangles `/path` arguments: prefix with `MSYS_NO_PATHCONV=1` when passing site paths to scripts or curl.
38
+
39
+ ## Rules for changes
40
+
41
+ - Read-only only. Never log in, never touch `/login`, `/basket`, `/profile`, the like / want-to-read / recommend buttons,
42
+ the comment form, contact forms or the analytics hosts. The only events ever fired are `input` and `click` on the
43
+ name box of `/publisher` and `/person`.
44
+ - Output units: prices in **Toman**. HTML prices are Toman; the JSON-LD on book pages is Rial (x10) and only fills the
45
+ price of sold-out books. `price` = list price, `final_price` = to pay, `discount_pct` computed; `in_stock` false when
46
+ the page shows no price («ناموجود»). Stock is yes/no, never a count.
47
+ - New tool: `@tool("Title")` async function, every param `Annotated[..., Field(...)]` with constraints and an example,
48
+ docstring = what it returns + when to use + what to call next, compact dict output. Add a unit test on a real trimmed
49
+ fixture, a live test, a README tool-table row (a test checks the tables) and an `INSTRUCTIONS` mention.
50
+ - Deps stay `mcp` + `httpx`. Write ZWNJ as `‌`, never the invisible character. Keep `Field(pattern=...)` small.
51
+
52
+ ## 30book quirks (keep them)
53
+
54
+ - No JSON API. Book page / home / landing are plain HTML (cards present). Every list page is an empty shell until a
55
+ Blazor circuit draws it, so `http.render` is used; one list costs 2-5 s and about 10 requests. About 1 in 20 circuits
56
+ fails to render: `render` retries. Keep at most 2 circuits at once (`MAX_CIRCUITS`).
57
+ - A markup frame counts as ONE logical child (the browser wraps its nodes in a container); components are logical
58
+ containers too. Getting this wrong shows up as a `stepIn` into a text node. Handler attributes are kept as `@click`.
59
+ - Query params are the state: `pg` (0-based), `sort` 1-7, `qt=True`, `pub`, `wtr`, `inp`, `stp=True`. Dummy slugs redirect
60
+ (301) to the canonical URL and the redirect drops the query: resolve first, then append (done in `http.render`).
61
+ - Category: `bts-N` section pages are landing pages only (no list); lists work for `bta-N-M` / `btf-N-M-K` (1 / 2 / 3
62
+ dummy slug segments). Award pages use a different layout (`loose` ready test, about 22 cards per page, sort ignored).
63
+ - `/special-offers` is mostly stationery (`/product/..` links); `sb_deals` uses best sellers or a subject with `sort=7`.
64
+ - Person / publisher name boxes match name fragments and miss some people (Arabic yeh / kaf in stored names).
65
+ - `sort=4/5` follow the list price. No price-range filter, no result count (only the last page number).
66
+ - `robots.txt` disallows `*/search` for crawlers; keep request rates low.
67
+
68
+ ## Release
69
+
70
+ Bump `version` in `pyproject.toml` and both versions in `server.json`, `uv lock`, commit, `git tag vX.Y.Z && git push --tags`
71
+ once a publish workflow is added (copy `.github/workflows` from `../shopino-mcp`). This file is local only.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sepehr
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.
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.5
2
+ Name: 30book-mcp
3
+ Version: 0.1.0
4
+ Summary: Unofficial read-only MCP server for 30book (30book.com): search Iranian printed books, compare editions and publishers, prices in Toman, discounts, authors and bestsellers.
5
+ Project-URL: Homepage, https://github.com/sepehr071/30book-mcp
6
+ Project-URL: Source, https://github.com/sepehr071/30book-mcp
7
+ Project-URL: Changelog, https://github.com/sepehr071/30book-mcp/releases
8
+ Project-URL: Issues, https://github.com/sepehr071/30book-mcp/issues
9
+ Author-email: Sepehr <sepehr@nextofx.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: 30book,books,e-commerce,iran,mcp,model-context-protocol,persian,shopping
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: httpx>=0.27
24
+ Requires-Dist: mcp<3,>=2.2
25
+ Description-Content-Type: text/markdown
26
+
27
+ <!-- mcp-name: io.github.sepehr071/30book-mcp -->
28
+
29
+ # 30book-mcp
30
+
31
+ **Let your AI agent search Iran's 30book bookstore.** Unofficial, read-only MCP server for [30book](https://www.30book.com)
32
+ (about 200,000 printed books, Persian and foreign): search by title, compare editions and publishers, see list and
33
+ discounted price in Toman, stock, pages, cover type, ISBN, print run, authors, translators, reviews, best sellers and
34
+ discounts. No account, no API key, no login, no cart.
35
+
36
+ [Quick start](#quick-start) · [Tools](#tools) · [How it works](#how-it-works) · [FAQ](#faq) · [فارسی](#فارسی)
37
+
38
+ ## Why
39
+
40
+ 30book has no public API and its list pages are drawn by a live Blazor Server session, so a plain scraper sees empty
41
+ pages. This server speaks that protocol (SignalR long polling, no browser needed) and parses the book pages, so an
42
+ agent can answer "cheapest edition of Little Prince with a hard cover", "everything by this translator under
43
+ 300,000 Toman" or "what are the best sellers in history" in a few calls.
44
+
45
+ ## Quick start
46
+
47
+ You need [uv](https://docs.astral.sh/uv/getting-started/installation/).
48
+
49
+ ```bash
50
+ claude mcp add 30book -- uvx 30book-mcp
51
+ ```
52
+
53
+ Other clients (Claude Desktop, Cursor, VS Code): run the command `uvx` with argument `30book-mcp`.
54
+ Behind a proxy, set `SIBOOK_MCP_PROXY=http://host:port` (system proxy variables are ignored on purpose).
55
+
56
+ ## Tools
57
+
58
+ All tools are read-only and prefixed `sb_`. Prices are Toman.
59
+
60
+ ### Find books
61
+
62
+ | Tool | What it does |
63
+ |---|---|
64
+ | `sb_search` | Search by title (exact phrase or word by word) with sort, in-stock, publisher, author and translator filters; 20 per page |
65
+ | `sb_browse` | Books of a category (section, subject, topic), fiction genre, national literature, literary award or tag |
66
+ | `sb_publisher` | One publisher's catalogue with a short description |
67
+ | `sb_author` | Everything by one author or translator |
68
+ | `sb_find_publisher` | Publisher name to id |
69
+ | `sb_find_person` | Author / translator name to id |
70
+ | `sb_categories` | Category sections and their subjects (codes for `sb_browse`) |
71
+ | `sb_collections` | Genres, national literatures, award lists and current tags |
72
+
73
+ ### One book
74
+
75
+ | Tool | What it does |
76
+ |---|---|
77
+ | `sb_book` | Price, final price, discount %, stock, authors, translators, publisher, pages, cover, size, ISBN, print run, year, categories, rating, description |
78
+ | `sb_similar` | Similar books, more by the author, more by the translator |
79
+ | `sb_reviews` | Reader comments with stars, plus the rating summary |
80
+
81
+ ### Popular and cheap
82
+
83
+ | Tool | What it does |
84
+ |---|---|
85
+ | `sb_bestsellers` | Site-wide best sellers |
86
+ | `sb_new_releases` | Newest books |
87
+ | `sb_deals` | The discounted-books page, biggest discount first on request |
88
+
89
+ ## How it works
90
+
91
+ - Book pages, the home page and landing pages are plain server-rendered HTML and are parsed directly.
92
+ - Search, category, publisher, author, genre and tag pages are built after load by a Blazor Server circuit. The server
93
+ opens one over SignalR long polling, applies the binary render batches to a small DOM (`blazor.py`) and reads the
94
+ cards. Paging, sort and filters are URL query parameters (`pg`, `sort`, `qt`, `pub`, `wtr`, `inp`, `stp`), so no
95
+ clicks are simulated; only the name boxes on the publisher and author index pages use a fired event.
96
+ - HTML prices are Toman; the JSON-LD on book pages is Rial (x10) and is only used for sold-out books.
97
+ - List pages take 2 to 5 seconds, book pages about 1 second. Stock is yes/no only.
98
+
99
+ ## Development
100
+
101
+ ```bash
102
+ export HTTPS_PROXY=http://127.0.0.1:10898 HTTP_PROXY=http://127.0.0.1:10898 # uv downloads only
103
+ uv sync
104
+ uv run ruff check . && uv run ruff format --check .
105
+ uv run pytest -q # offline, recorded pages
106
+ uv run pytest -m live -q # real site, one test per tool
107
+ ```
108
+
109
+ API map and test report: `../30book/`.
110
+
111
+ ## FAQ
112
+
113
+ **Does it buy anything?** No. It never logs in and never calls cart, checkout, wishlist or review endpoints.
114
+
115
+ **Why is a search sometimes empty?** The site stores some names with Arabic yeh / kaf; try a shorter word or `extended=true`.
116
+
117
+ **Is it affiliated with 30book?** No. Unofficial; keep the request rate low.
118
+
119
+ ## فارسی
120
+
121
+ سرور MCP غیررسمی و فقط‌خواندنی برای فروشگاه کتاب ۳۰بوک: جستجوی کتاب، مقایسهٔ چاپ‌ها و ناشران، قیمت (تومان) و
122
+ تخفیف، موجودی، مشخصات کتاب، نویسنده و مترجم، نظرات، پرفروش‌ها و تخفیف‌ها. نیازی به حساب یا کلید ندارد.
123
+
124
+ نصب: `claude mcp add 30book -- uvx 30book-mcp`
@@ -0,0 +1,98 @@
1
+ <!-- mcp-name: io.github.sepehr071/30book-mcp -->
2
+
3
+ # 30book-mcp
4
+
5
+ **Let your AI agent search Iran's 30book bookstore.** Unofficial, read-only MCP server for [30book](https://www.30book.com)
6
+ (about 200,000 printed books, Persian and foreign): search by title, compare editions and publishers, see list and
7
+ discounted price in Toman, stock, pages, cover type, ISBN, print run, authors, translators, reviews, best sellers and
8
+ discounts. No account, no API key, no login, no cart.
9
+
10
+ [Quick start](#quick-start) · [Tools](#tools) · [How it works](#how-it-works) · [FAQ](#faq) · [فارسی](#فارسی)
11
+
12
+ ## Why
13
+
14
+ 30book has no public API and its list pages are drawn by a live Blazor Server session, so a plain scraper sees empty
15
+ pages. This server speaks that protocol (SignalR long polling, no browser needed) and parses the book pages, so an
16
+ agent can answer "cheapest edition of Little Prince with a hard cover", "everything by this translator under
17
+ 300,000 Toman" or "what are the best sellers in history" in a few calls.
18
+
19
+ ## Quick start
20
+
21
+ You need [uv](https://docs.astral.sh/uv/getting-started/installation/).
22
+
23
+ ```bash
24
+ claude mcp add 30book -- uvx 30book-mcp
25
+ ```
26
+
27
+ Other clients (Claude Desktop, Cursor, VS Code): run the command `uvx` with argument `30book-mcp`.
28
+ Behind a proxy, set `SIBOOK_MCP_PROXY=http://host:port` (system proxy variables are ignored on purpose).
29
+
30
+ ## Tools
31
+
32
+ All tools are read-only and prefixed `sb_`. Prices are Toman.
33
+
34
+ ### Find books
35
+
36
+ | Tool | What it does |
37
+ |---|---|
38
+ | `sb_search` | Search by title (exact phrase or word by word) with sort, in-stock, publisher, author and translator filters; 20 per page |
39
+ | `sb_browse` | Books of a category (section, subject, topic), fiction genre, national literature, literary award or tag |
40
+ | `sb_publisher` | One publisher's catalogue with a short description |
41
+ | `sb_author` | Everything by one author or translator |
42
+ | `sb_find_publisher` | Publisher name to id |
43
+ | `sb_find_person` | Author / translator name to id |
44
+ | `sb_categories` | Category sections and their subjects (codes for `sb_browse`) |
45
+ | `sb_collections` | Genres, national literatures, award lists and current tags |
46
+
47
+ ### One book
48
+
49
+ | Tool | What it does |
50
+ |---|---|
51
+ | `sb_book` | Price, final price, discount %, stock, authors, translators, publisher, pages, cover, size, ISBN, print run, year, categories, rating, description |
52
+ | `sb_similar` | Similar books, more by the author, more by the translator |
53
+ | `sb_reviews` | Reader comments with stars, plus the rating summary |
54
+
55
+ ### Popular and cheap
56
+
57
+ | Tool | What it does |
58
+ |---|---|
59
+ | `sb_bestsellers` | Site-wide best sellers |
60
+ | `sb_new_releases` | Newest books |
61
+ | `sb_deals` | The discounted-books page, biggest discount first on request |
62
+
63
+ ## How it works
64
+
65
+ - Book pages, the home page and landing pages are plain server-rendered HTML and are parsed directly.
66
+ - Search, category, publisher, author, genre and tag pages are built after load by a Blazor Server circuit. The server
67
+ opens one over SignalR long polling, applies the binary render batches to a small DOM (`blazor.py`) and reads the
68
+ cards. Paging, sort and filters are URL query parameters (`pg`, `sort`, `qt`, `pub`, `wtr`, `inp`, `stp`), so no
69
+ clicks are simulated; only the name boxes on the publisher and author index pages use a fired event.
70
+ - HTML prices are Toman; the JSON-LD on book pages is Rial (x10) and is only used for sold-out books.
71
+ - List pages take 2 to 5 seconds, book pages about 1 second. Stock is yes/no only.
72
+
73
+ ## Development
74
+
75
+ ```bash
76
+ export HTTPS_PROXY=http://127.0.0.1:10898 HTTP_PROXY=http://127.0.0.1:10898 # uv downloads only
77
+ uv sync
78
+ uv run ruff check . && uv run ruff format --check .
79
+ uv run pytest -q # offline, recorded pages
80
+ uv run pytest -m live -q # real site, one test per tool
81
+ ```
82
+
83
+ API map and test report: `../30book/`.
84
+
85
+ ## FAQ
86
+
87
+ **Does it buy anything?** No. It never logs in and never calls cart, checkout, wishlist or review endpoints.
88
+
89
+ **Why is a search sometimes empty?** The site stores some names with Arabic yeh / kaf; try a shorter word or `extended=true`.
90
+
91
+ **Is it affiliated with 30book?** No. Unofficial; keep the request rate low.
92
+
93
+ ## فارسی
94
+
95
+ سرور MCP غیررسمی و فقط‌خواندنی برای فروشگاه کتاب ۳۰بوک: جستجوی کتاب، مقایسهٔ چاپ‌ها و ناشران، قیمت (تومان) و
96
+ تخفیف، موجودی، مشخصات کتاب، نویسنده و مترجم، نظرات، پرفروش‌ها و تخفیف‌ها. نیازی به حساب یا کلید ندارد.
97
+
98
+ نصب: `claude mcp add 30book -- uvx 30book-mcp`
@@ -0,0 +1,57 @@
1
+ [project]
2
+ name = "30book-mcp"
3
+ version = "0.1.0"
4
+ description = "Unofficial read-only MCP server for 30book (30book.com): search Iranian printed books, compare editions and publishers, prices in Toman, discounts, authors and bestsellers."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "Sepehr", email = "sepehr@nextofx.com" }]
9
+ requires-python = ">=3.10"
10
+ keywords = ["mcp", "model-context-protocol", "30book", "iran", "books", "e-commerce", "shopping", "persian"]
11
+ classifiers = [
12
+ "Development Status :: 4 - Beta",
13
+ "Intended Audience :: Developers",
14
+ "Operating System :: OS Independent",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.13",
20
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
21
+ ]
22
+ dependencies = [
23
+ "mcp>=2.2,<3",
24
+ "httpx>=0.27",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/sepehr071/30book-mcp"
29
+ Source = "https://github.com/sepehr071/30book-mcp"
30
+ Changelog = "https://github.com/sepehr071/30book-mcp/releases"
31
+ Issues = "https://github.com/sepehr071/30book-mcp/issues"
32
+
33
+ [project.scripts]
34
+ 30book-mcp = "sibook_mcp.server:main"
35
+
36
+ [dependency-groups]
37
+ dev = ["pytest>=8", "ruff>=0.6"]
38
+
39
+ [build-system]
40
+ requires = ["hatchling"]
41
+ build-backend = "hatchling.build"
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/sibook_mcp"]
45
+
46
+ [tool.pytest.ini_options]
47
+ testpaths = ["tests"]
48
+ addopts = "-m 'not live'"
49
+ markers = ["live: calls the real 30book site (run with -m live)"]
50
+
51
+ [tool.ruff]
52
+ line-length = 120
53
+ target-version = "py310"
54
+
55
+ [tool.ruff.lint]
56
+ select = ["E", "F", "I", "B", "UP"]
57
+ ignore = ["E501"] # long Field(description=...) strings read better on one line
@@ -0,0 +1,32 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.sepehr071/30book-mcp",
4
+ "title": "30book",
5
+ "description": "Unofficial read-only 30book: search Iranian printed books, compare editions and prices.",
6
+ "repository": {
7
+ "url": "https://github.com/sepehr071/30book-mcp",
8
+ "source": "github"
9
+ },
10
+ "websiteUrl": "https://github.com/sepehr071/30book-mcp",
11
+ "version": "0.1.0",
12
+ "packages": [
13
+ {
14
+ "registryType": "pypi",
15
+ "registryBaseUrl": "https://pypi.org",
16
+ "identifier": "30book-mcp",
17
+ "version": "0.1.0",
18
+ "runtimeHint": "uvx",
19
+ "transport": {
20
+ "type": "stdio"
21
+ },
22
+ "environmentVariables": [
23
+ {
24
+ "name": "SIBOOK_MCP_PROXY",
25
+ "description": "Optional HTTP proxy URL for requests to 30book.com; system proxy settings are ignored.",
26
+ "isRequired": false,
27
+ "isSecret": false
28
+ }
29
+ ]
30
+ }
31
+ ]
32
+ }
@@ -0,0 +1,5 @@
1
+ """Unofficial MCP server for 30book."""
2
+
3
+ from importlib.metadata import version
4
+
5
+ __version__ = version("30book-mcp")