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.
- 30book_mcp-0.1.0/.github/workflows/ci.yml +22 -0
- 30book_mcp-0.1.0/.github/workflows/publish.yml +51 -0
- 30book_mcp-0.1.0/.gitignore +6 -0
- 30book_mcp-0.1.0/CLAUDE.md +71 -0
- 30book_mcp-0.1.0/LICENSE +21 -0
- 30book_mcp-0.1.0/PKG-INFO +124 -0
- 30book_mcp-0.1.0/README.md +98 -0
- 30book_mcp-0.1.0/pyproject.toml +57 -0
- 30book_mcp-0.1.0/server.json +32 -0
- 30book_mcp-0.1.0/src/sibook_mcp/__init__.py +5 -0
- 30book_mcp-0.1.0/src/sibook_mcp/blazor.py +550 -0
- 30book_mcp-0.1.0/src/sibook_mcp/catalog.py +261 -0
- 30book_mcp-0.1.0/src/sibook_mcp/dom.py +120 -0
- 30book_mcp-0.1.0/src/sibook_mcp/http.py +152 -0
- 30book_mcp-0.1.0/src/sibook_mcp/parse.py +244 -0
- 30book_mcp-0.1.0/src/sibook_mcp/product.py +133 -0
- 30book_mcp-0.1.0/src/sibook_mcp/registry.py +16 -0
- 30book_mcp-0.1.0/src/sibook_mcp/server.py +52 -0
- 30book_mcp-0.1.0/tests/conftest.py +73 -0
- 30book_mcp-0.1.0/tests/fixtures/book_103736_soldout.html +837 -0
- 30book_mcp-0.1.0/tests/fixtures/book_167515.html +843 -0
- 30book_mcp-0.1.0/tests/fixtures/book_6426_translated.html +823 -0
- 30book_mcp-0.1.0/tests/fixtures/find_publisher.html +3 -0
- 30book_mcp-0.1.0/tests/fixtures/home_links.html +10 -0
- 30book_mcp-0.1.0/tests/fixtures/list_empty.html +91 -0
- 30book_mcp-0.1.0/tests/fixtures/list_publisher.html +785 -0
- 30book_mcp-0.1.0/tests/fixtures/list_search.html +657 -0
- 30book_mcp-0.1.0/tests/test_blazor.py +265 -0
- 30book_mcp-0.1.0/tests/test_http.py +107 -0
- 30book_mcp-0.1.0/tests/test_live.py +101 -0
- 30book_mcp-0.1.0/tests/test_tools.py +157 -0
- 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,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.
|
30book_mcp-0.1.0/LICENSE
ADDED
|
@@ -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
|
+
}
|