Books Mandala — Find a Book
Purpose
Search, browse, and look up books from Books Mandala — Nepal's largest dedicated online bookstore (50,000+ titles, with deep coverage of Nepali / Hindi / Sanskrit / South-Asian literature and international bestsellers). Returns title, author(s), ISBN, formatted NPR price + numeric value, stock status (in / out), genres, description, and a canonical booksmandala.com/books/{slug}-{id} purchase link for each match. Read-only — never adds to cart, places an order, or hits authenticated endpoints.
When to Use
- A user (anywhere in the world) is looking for a specific Nepali or South-Asian title and wants to know whether Books Mandala stocks it, at what price, and where to buy.
- Discovery flows: "show me bestsellers", "what's new", "books in the
nepali-literaturegenre", "everything by Buddhisagar". - Agentic shopping where you need a stable, structured catalog surface — Books Mandala publishes a public MCP server and a documented REST API specifically so AI agents can browse the catalog programmatically.
- Anywhere you'd otherwise scrape
booksmandala.comHTML — the MCP / API path is ~100× cheaper and structurally more reliable than rendering the Next.js front-end.
Workflow
Books Mandala ships three machine-friendly surfaces; pick in this order of preference:
- MCP server (recommended, no auth) —
https://bm-agent-mcp.booksmandala.workers.dev/mcp. Public, no API key required, 7 tools covering every browse / lookup use case. Streamable HTTP, MCP protocol2025-03-26. - REST API (requires key) —
https://booksmandala.com/api/agent/v1. Same 7 operations as the MCP, returns clean JSON, but every endpoint except/healthand/docsrequires anX-API-Keyheader. Keys are issued by emailingdev@mandalatech.io. Rate limit 100 req/min per key. - Browser fallback — only when MCP is down and you have no API key. The site is Next.js SSR + JSON-LD; works without stealth or proxies.
1. Recommended path — MCP (Streamable HTTP, no auth)
If your agent host (Claude Desktop, Cursor, etc.) supports MCP config, just add it:
{
"mcpServers": {
"books-mandala": {
"url": "https://bm-agent-mcp.booksmandala.workers.dev/mcp"
}
}
}
If you're a code-driven agent without a built-in MCP client, talk to it over plain HTTP with two POSTs (initialize + tools/call). The server returns SSE-framed JSON-RPC responses (event: message\ndata: {...}\n\n):
# 1. Initialize → server returns an mcp-session-id header you must echo on subsequent calls
INIT=$(curl -s -D - -X POST https://bm-agent-mcp.booksmandala.workers.dev/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"my-agent","version":"1.0"}}}')
MCP_SID=$(printf '%s' "$INIT" | grep -i '^mcp-session-id:' | awk '{print $2}' | tr -d '\r')
# 2. (optional but spec-compliant) — fire-and-forget initialized notification
curl -s -X POST https://bm-agent-mcp.booksmandala.workers.dev/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $MCP_SID" \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
# 3. Call a tool — search_books, get_book, list_genres, browse_genre, bestsellers, new_arrivals, get_author
curl -s -X POST https://bm-agent-mcp.booksmandala.workers.dev/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "mcp-session-id: $MCP_SID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search_books","arguments":{"query":"palpasa cafe","limit":3}}}'
The 7 MCP tools and when to use each:
| Tool | Required args | Use for |
|---|---|---|
search_books | query (min 2 chars) | Lookup by title / author / ISBN. Optional: page (default 1), limit (default 20, max 50). |
get_book | identifier (ISBN or slug) | Full details for one book — publisher, pages, edition, weight, rating, full description. Slug is the part of the URL between /books/ and the trailing -{id} (e.g. atomic-habits for /books/atomic-habits-15626) — though either the bare slug or the full slug-id resolves. ISBN-13 also works. |
list_genres | — | All 177 genres with their parent category (e.g. nepali-literature → parent nepali). Returns slugs you feed into browse_genre. |
browse_genre | genre_slug | Paged list of books in a genre. |
bestsellers | — | Current top sellers, paged. |
new_arrivals | — | Books added in the last 45 days, paged. |
get_author | author_slug | Author bio + their books. Slugs are kebab-case author names (e.g. james-clear, narayan-wagle, buddhisagar); the same slug appears in https://booksmandala.com/author/{slug} URLs. |
Response shape. Every tool returns {"content": [{"type": "text", "text": "..."}]} — a single Markdown-formatted text block, NOT structured JSON book objects. Each book entry looks like:
**Palpasa Cafe**
by Narayan Wagle
Price: NPR 595 | In Stock
ISBN: 9789937905855
Genres: Nepali, Nepali Literature
<full description>
Buy: https://booksmandala.com/palpasa-cafe-2739
If your downstream system needs structured fields, parse with a per-entry block split on \n---\n and a regex pass per line (^by\s+(.+)$, ^Price:\s+(NPR \d[\d,]*)\s*\|\s*(In Stock|Out of Stock)$, ^ISBN:\s+(\d+)$, etc.). For machine-clean JSON, use the REST API instead.
2. Alternative — REST API (key required, returns clean JSON)
Same operations as the MCP but with structured Book objects. Request a key from dev@mandalatech.io, then:
curl -s -H "X-API-Key: $BM_KEY" \
"https://booksmandala.com/api/agent/v1/search?q=palpasa+cafe&limit=3"
Endpoints (all GET, all under https://booksmandala.com/api/agent/v1):
| Path | Notes |
|---|---|
/search?q=<text> | min q length 2; page, limit (max 50) |
/books/{isbn-or-slug} | full BookDetail |
/genres | flat list |
/genres/{slug}/books | paged |
/bestsellers | paged |
/new-arrivals | paged |
/authors/{slug} | author + books |
/health | no auth |
/docs | no auth — full OpenAPI 3.0 spec |
The OpenAPI spec at /docs defines the canonical Book / BookDetail schemas — fetch it once and use it as your typed contract. Without X-API-Key, every non-/health / non-/docs endpoint returns HTTP 401 {"error":"API key required...","code":"AUTH_REQUIRED"}.
3. Browser fallback (when no MCP + no API key)
Bare session is fine (no Akamai, no proxies, no stealth needed):
sid=$(browse cloud sessions create --keep-alive | jq -r .id)
export BROWSE_SESSION="$sid"
Three browser sub-flows depending on intent:
Search — the homepage has no ?q= URL param. /search?q=... is a 404. Search is a Ctrl+K modal that fires inline typeahead results:
browse open "https://booksmandala.com/" --remote
browse wait load --remote
# Click the "What do you want to read ? Ctrl + K" button (top right of nav)
browse click "button:What do you want to read*" --remote
# Type into the searchbox (don't press Enter — autocomplete renders as you type)
browse fill "searchbox:Search By Title*" "palpasa cafe" --remote
browse wait timeout 2000 --remote
browse snapshot --remote
# urlMap entries shaped like https://booksmandala.com/books/{slug}-{id} are the matches
Genre browse — direct URL works: https://booksmandala.com/books/genres/{genre-slug} (e.g. /books/genres/nepali, /books/genres/fiction-and-literature). Add ?sub_genres={sub} to scope to a sub-genre (e.g. /books/genres/self-improvement-and-relationships?sub_genres=self-help). Genre slugs match list_genres output exactly.
Book detail — https://booksmandala.com/books/{slug}-{id}. Either browse get markdown body to get clean Markdown of the page, or grep the raw HTML for "@type":"Book" — the Next.js hydration payload embeds JSON-LD with the full schema.org Book object (title, image, ISBN, description, publisher, price NPR, availability, aggregateRating). Note the JSON-LD is embedded inside a <script>self.__next_f.push(...)</script> Next.js streaming chunk, not a clean <script type="application/ld+json"> tag — use a substring search for "@type":"Book" then carve from the surrounding object literal.
Release the session when done: browse cloud sessions update "$sid" --status REQUEST_RELEASE.
Site-Specific Gotchas
- The MCP server is genuinely public — don't bother passing API keys.
bm-agent-mcp.booksmandala.workers.devrequires no auth header, no client registration, no OAuth. Verified 2026-05-20: a coldinitializefrom a fresh Browserbase IP returnedprotocolVersion 2025-03-26and a session id in the first response. The REST API at/api/agent/v1is the surface that requiresX-API-Key; the MCP is the unauthenticated mirror. - MCP returns Markdown text, not JSON book objects. Despite being a structured tool-calling protocol, every Books Mandala tool returns a single
{"type":"text","text":"..."}block formatted for human/LLM reading. If you need typed fields, either (a) regex-parse the text block, or (b) switch to the REST API which returns proper{"data": Book, "meta": {...}}JSON. - MCP transport is Streamable HTTP with SSE framing. Responses look like
event: message\ndata: <json>\n\n. If your HTTP client treats the body as plain JSON it will fail to parse — strip theevent:/data:prefixes first, or use a real MCP client. Also: you MUST sendAccept: application/json, text/event-stream(both) on every POST; sending onlyapplication/jsonis rejected. - MCP session id is REQUIRED on every call after
initialize. The server setsmcp-session-id: <hex>in the response headers of the initialize call; echo it back as themcp-session-idrequest header on every subsequenttools/call/tools/list. Calls without it return an MCP protocol error. - REST API rate limit is per endpoint and SHARED with the docs endpoint. Observed:
X-Ratelimit-Limit: 20on the/docsendpoint, and the documented per-key rate is 100/min. Don't loop on/docs— fetch it once, cache it. - URL paths use
/books/{slug}(plural), not/book/{slug}(singular). Thellms.txtathttps://booksmandala.com/llms.txtdocuments the singular form; the live site serves the plural. All MCPBuy:links and the front-end'surlMapuse plural. - Slugs carry a numeric id suffix. Canonical URL shape is
/books/{kebab-title}-{id}(e.g./books/palpasa-cafe-2739,/books/atomic-habits-15626). Theget_booktool accepts either ISBN-13, the bare slug, or the full slug-with-id. - Prices are in NPR (Nepalese Rupees) in the API, but the front-end auto-converts based on browser geolocation. Same book on the rendered homepage shows
$1.56whilebestsellersMCP returnsNPR 209. If you're routing the user to the front-end for purchase, surface both — the user may see a different display currency. /search?q=...is a 404 page. The site has no URL-routed search. Browser fallback for search MUST go through the Ctrl+K modal (see workflow). The search button has accessible name "What do you want to read ? Ctrl + K".- The Ctrl+K modal does NOT navigate on Enter. Results render inline in the modal as a typeahead. Use
browse snapshotafterfill(with a 1–2s wait for the debounce) and read result anchors from theurlMap. Pressing Enter just submits without going anywhere useful. - JSON-LD on book pages is embedded inside Next.js hydration chunks, not in clean
<script type="application/ld+json">tags. A naivegrep '<script[^>]*ld+json'finds nothing parseable. Search for the literal string"@type":"Book"and carve the enclosing{...}— or just usebrowse get markdown body, which gives a clean prose version of the same data. - No anti-bot, no stealth needed. Cloudflare in front of the site (
cf-rayheader present) but no challenges, no captcha, no IP-based blocking observed. A bare Browserbase session loads the homepage and any deep link cleanly.--proxiesand--verifiedare NOT required for any of the three paths. get_bookdescription fields can contain Nepali (Devanagari) script and the MCP / API both preserve UTF-8 correctly. Don't assume ASCII-only output when displaying descriptions.- Phase 1 only. The MCP and REST API are documented as Phase 1 (discovery / browsing). Real-time stock-by-ISBN, shipping-cost-by-city, and order placement are advertised as Phase 2+ and are NOT in the current tool set. Don't attempt to place an order through any of these surfaces.
llms.txtandllms-full.txtare the canonical reference.https://booksmandala.com/llms.txtis the short overview;https://booksmandala.com/llms-full.txtis the full catalog + taxonomy + tech doc. Both update; pull them at runtime if you need the genre tree or the latest endpoint list.
Expected Output
When the caller asks "find {query} on Books Mandala", normalize to this shape regardless of which path you used:
{
"query": "palpasa cafe",
"method": "mcp",
"total_results": 3,
"page": 1,
"books": [
{
"title": "Palpasa Cafe",
"authors": ["Narayan Wagle"],
"isbn": "9789937905855",
"price": "NPR 595",
"price_value": 595,
"currency": "NPR",
"in_stock": true,
"genres": ["Nepali", "Nepali Literature"],
"description": "Felicitated by Madan Purashkar in the year 2005…",
"url": "https://booksmandala.com/books/palpasa-cafe-2739"
}
]
}
For a single-book detail lookup (get_book / /books/{id}):
{
"method": "mcp",
"book": {
"title": "Palpasa Cafe",
"alternate_title": "पल्पसा क्याफे",
"authors": ["Narayan Wagle"],
"isbn": "9789937905855",
"publisher": "Nepalaya",
"pages": 286,
"cover_type": "paper back",
"edition": "1",
"weight_grams": 240,
"languages": ["Nepali"],
"genres": ["Nepali", "Nepali Literature"],
"average_rating": 5,
"reviews_count": 1,
"price": "NPR 595",
"price_value": 595,
"currency": "NPR",
"in_stock": true,
"description": "…full text…",
"url": "https://booksmandala.com/books/palpasa-cafe-2739"
}
}
Empty / not-found result:
{
"query": "definitely not a real book xyz",
"method": "mcp",
"total_results": 0,
"books": []
}
Always include the canonical https://booksmandala.com/books/{slug}-{id} URL on every result so the caller has a one-click path to the purchase page. Prices are NPR; surface conversion to the user's locale on the caller side if needed.