bolha
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@bolhasearch for a used iPhone under 500 EUR in Ljubljana"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Bolha
A client for bolha.com, Slovenia's largest classifieds marketplace — as a reusable library, a CLI, and an MCP server, so that humans and AI agents can use Bolha reliably without the web UI.
Bolha website → bolha core client → ┌────────────┐
(HTML/JSON) (src/core) │ CLI + MCP │
└────────────┘One core library, two front-ends. No duplicated scraping logic.
Unofficial. Not affiliated with or endorsed by Styria digital marketplaces d.o.o., the operator of bolha.com. Read-only by design: it never writes to the site.
Publishing this? See PREPARE.md — three things need your input first.
Why this exists
Bolha has no public API. api.bolha.com 301-redirects to the homepage, and there is no
Bolha MCP server (both verified — see research/EXISTING_PROJECTS.md).
The site is nonetheless fully machine-readable: every page embeds a complete JSON payload in
window.__INITIAL_STATE__. This project reads that, rather than scraping CSS selectors,
which makes it both simpler and far more resistant to layout changes.
Research-first. Nothing here was written from memory of the site. Every URL, parameter, id and field was verified against the live site — see
AGENTS.md§3 and theresearch/knowledge base.
Related MCP server: marktplaats-mcp
Features
Area | Supported |
Search | Text search, category restriction, price range, condition, image-only, online-payment, sort, pagination |
Categories | Top-level tree, category detail with subcategories, category-specific filters, listings counts |
Filters | Discovered dynamically from the site, so vehicle/real-estate filters work without special-casing |
Locations | 238 distinct locality slugs from Bolha's public sitemap: 11 Slovenian regions + 184 towns/settlements |
Region filter | Works in every category — exact locality path in real estate, measured radius everywhere else |
Facets | Category facet pages (e.g. all 60 vehicle makes under |
Listings | Full detail: description, all image sizes, seller, category path, Bolha's attribute table, schema.org data |
Sorting |
|
Pagination | Multi-page walking, de-duplicated, bounded for safety |
Sellers | Private sellers ( |
Diagnostics |
|
MCP | Six tools over stdio with validated structured output |
Not supported (deliberately): posting, editing, deleting, favouriting, messaging, or any account change. Those require authentication and are intentionally out of scope — see Authentication.
Requirements
Node.js ≥ 20 (the MCP SDK requires it)
Installation
git clone <this-repo> bolha
cd bolha
npm install
npm run buildRun the CLI directly, or link it onto your PATH:
node dist/cli/index.js --help
# or, globally:
npm link
bolha --helpCLI usage
bolha search <query> # search listings
bolha browse <category> # browse a category by slug or URL
bolha listing <url> # full detail for one listing
bolha categories # top-level categories
bolha category <slug> # category detail + its filters
bolha facets <category> # facet sub-pages (makes, brands, …)
bolha locations [category] # localities: regions and towns
bolha regions [category] # the eleven Slovenian regions
bolha towns [category] # towns and settlements
bolha location-categories # which categories support locality filtering
bolha filters [category] # filters the site advertises
bolha sort [category] # sort orders available
bolha seller <slug|url> # a private seller or business store
bolha doctor # live diagnosticsExamples
# Plain search
bolha search "RTX 5090"
# Machine-readable output — nothing but JSON on stdout
bolha search "iPhone" --json | jq '.listings[] | {id, title, price: .price.formatted}'
# Filtered + sorted
bolha search "iPhone" --condition used --min-price 100 --max-price 900 --sort cheap
# Restrict to a category
bolha search "kolo" --category rekreacija-sport --limit 50
# Browse a category by price
bolha browse avto-oglasi --min-price 5000 --max-price 20000 --sort cheap
# Radius search around a city
bolha browse nepremicnine --lat 46.0511 --lng 14.5051 --radius 20
# Category-specific filter (vehicles) via the escape hatch
bolha browse avto-oglasi --param "yearManufactured[min]=2020" --param "condition[used]=1"
# Narrow a category by make: /avto-oglasi links every make as a facet page
bolha facets avto-oglasi
bolha browse avto-oglasi/audi --sort cheap
# Filter by place. Localities are ordinary category path segments.
bolha locations prodaja-hise --query maribor
bolha browse prodaja-hise/maribor --sort cheap
# Region filter — works in any category, not just property.
bolha regions # the 11 Slovenian regions
bolha towns prodaja-hise # 184 towns and settlements
bolha browse avto-oglasi --region koroska # vehicles in Koroška
bolha browse racunalnistvo --region gorenjska # computers in Gorenjska
bolha search "hiša" --region koroska --category prodaja-hise
# Inspect one listing (copy the URL from any search)
bolha listing /apple-iphone/prodam-apple-iphone-14-oglas-16445305
# Who is this seller?
bolha seller _borut_007
bolha seller https://www.bolha.com/trgovina/primer-trgovina
# Check everything still works
bolha doctor --verboseOptions
Option | Meaning |
| Max listings (default 25, cap 500) |
| 1-based page (max 100) |
|
|
| Restrict to a category |
| Price range in EUR |
| Repeatable: |
| Radius search |
| Repeatable Bolha location id |
| Only listings with a photo |
| Any other Bolha URL parameter (repeatable) |
| Pure JSON on stdout |
| Log every HTTP request to stderr |
| Network tuning |
| Cache control |
|
Exit codes
Code | Meaning |
| Success |
| Usage error (bad argument, limit above cap) |
| Not found / no results / removed listing / parent-category page |
| Network, HTTP or parse failure |
| Interrupted |
Library
import { BolhaClient } from 'bolha';
const bolha = new BolhaClient();
const page = await bolha.search({ query: 'iPhone', maxPrice: 900, limit: 10 });
console.log(page.totalCount, page.listings[0]?.title);
const listing = await bolha.getListing(page.listings[0]!.url);
console.log(listing.price.formatted, listing.images.length, listing.attributes);Main methods:
Method | Returns |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Every method returns typed, structured data — never raw HTML. Errors are BolhaErrors with a
stable code (NOT_FOUND, PARENT_CATEGORY, SITE_CHANGED, LIMIT_EXCEEDED, …).
MCP server
Six tools, each with a strict input schema and validated structured output:
Tool | Purpose |
| Text search with filters and sorting |
| Listings in one category, no query needed |
| Discover category slugs |
| One category's subcategories, filters and sort orders |
| A category's facet sub-pages (vehicle makes, brand tags) |
| Slovenian localities (regions and towns) for a category or free-text search |
| Full detail of one listing |
| The eleven Slovenian statistical regions |
| Which categories support locality filtering |
| Text search scoped to a region or town |
| Seller or store profile |
Configuration
Codex / Claude Desktop / any stdio MCP client:
{
"mcpServers": {
"bolha": {
"command": "node",
"args": ["/absolute/path/to/bolha/dist/mcp/server.js"]
}
}
}Environment variables (all optional):
Variable | Effect |
| Cache location (default |
| Disable the cache |
| Override the |
| Log requests to stderr |
Context discipline
Search results are compact summaries — id, title, price, location, date, seller, URL — with
no descriptions. An agent can choose which listings matter, then call bolha_get_listing for the
full text. Listings default to at most 100 per call and descriptions are truncated at 4000
characters, so a search cannot flood a model's context.
The tools speak human terms (new/used/defective, cheap/new), never Bolha's internal
numeric codes.
Configuration
No configuration is required. Optional environment variables:
Variable | Default | Purpose |
| descriptive default | Identify your client to Bolha |
|
| Cache location |
| unset | Disable caching |
| unset | Request logging to stderr |
The cache stores only public page data, is keyed by URL, honours per-kind TTLs (categories 6 h,
searches 1 min) and is invalidated automatically when the library's output shape changes. Session
state and credentials live under ~/.bolha/ and are never written to the repository.
Setup: Claude Code and Claude Desktop
The MCP server is dist/mcp/server.js. It speaks stdio, needs no API key, and
reads only public bolha.com pages.
Build it once first (it is already built in this checkout, but you need it after
any git pull or code change):
cd D:\marketplace\bolha
npm install
npm run buildQuick check that it works before wiring it into anything:
npm run doctorClaude Code (terminal agent)
Run this once:
claude mcp add bolha --scope user -- node D:\marketplace\bolha\dist\mcp\server.jsThen verify:
claude mcp list # should show: bolha: node D:\marketplace\bolha\dist\mcp\server.js - √ ConnectedOr add it interactively from inside a claude session with /mcp.
Scopes
Scope | Flag | Where it is stored |
user (all your projects) |
|
|
project (this repo only) |
|
|
local (this one project, you only) |
|
|
Prefer user so it follows you across directories. Use project if you want the
repo to declare the dependency for your whole team — but then commit .mcp.json.
Windows note. If claude is not on your PATH in the terminal Claude Code runs
from, replace node with the absolute interpreter path:
claude mcp add bolha --scope user -- "C:\Program Files\nodejs\node.exe" D:\marketplace\bolha\dist\mcp\server.jsClaude Desktop (GUI app)
Desktop only reads MCP servers from its own JSON file — the claude mcp add
command above does not configure it.
Edit:
%APPDATA%\Claude\claude_desktop_config.json(on your machine: C:\Users\vilis\AppData\Roaming\Claude\claude_desktop_config.json)
Add a bolha entry alongside the servers already there, keeping the existing
ones intact:
{
"mcpServers": {
"davinci-resolve": { "...": "keep what is already there" },
"FaceMCP": { "...": "keep what is already there" },
"secondhand-facebook": { "...": "keep what is already there" },
"bolha": {
"command": "node",
"args": ["D:\\marketplace\\bolha\\dist\\mcp\\server.js"]
}
}
}Then fully quit and restart Claude Desktop (close the tray icon too — closing
the window is not enough on Windows). The server appears in the tools menu
(🔨 icon) as bolha_search, bolha_get_listing, and so on.
To undo: delete the bolha block from that file and restart Desktop.
Optional environment variables
None are required. Add them under "env" in the config block, or pass
--env KEY=value to claude mcp add:
Variable | Effect |
| Identify yourself to bolha.com (please do set this) |
| Cache location (default |
| Disable caching |
| Log requests to stderr |
Example with a custom user agent:
claude mcp add bolha --scope user \
--env BOLHA_USER_AGENT="janez/my-bolha-mcp" \
-- node D:\marketplace\bolha\dist\mcp\server.jsVerifying from inside Claude
Ask it something that forces a live call, for example:
Search bolha for "iPhone" under 500 EUR, cheapest first, and show me the listing URLs.
or, to check the connection specifically:
List the bolha categories.
If the tools are not offered, the server did not start — check
claude mcp list for a connection error, and confirm dist/mcp/server.js
exists (run npm run build).
Reliability
Polite by default — descriptive
User-Agent, ≤3 concurrent requests, ≥350 ms between requests, retries with exponential backoff, per-request timeouts.Breaks loudly — if Bolha reports listings but none can be parsed, the client raises
SITE_CHANGEDinstead of quietly returning[].Bounded — 500 listings and 100 pages per call, so an agent cannot accidentally trigger a crawl.
Honest about limits — see [
research/Known Limitations.md](research/Known Limitations.md).
robots.txt and ethics
Bolha's robots.txt defines 39 separate user-agent groups, and the details are easy to
misread. Checked directly against the live file:
Path |
| AI crawler groups |
| Disallowed | mixed |
| not listed | Disallowed (GPTBot-style) |
So /search is off-limits to every crawler, while listing and profile URLs are only restricted
for training-oriented AI crawlers.
How this project treats that. It is a user-operated client: it runs on your machine, at human pace, and returns results to your own terminal or agent. It does not train models, does not systematically crawl, and does not re-publish content. Where an equivalent route exists, the client prefers the allowed surface:
browse/category/filters/sellerall use category URLs, which are not disallowed;public sitemaps (
/sitemap-index.xml) are the intended discovery surface;requests are paced (≥350 ms apart), capped at 3 concurrent, cached, and sent with a descriptive
User-Agentyou can override viaBOLHA_USER_AGENT;if Bolha serves a CAPTCHA or block, the client reports it and stops — it never tries to defeat it.
Please keep it that way. If you need bulk discovery, the sitemaps are the right tool.
Development
npm run build # compile TypeScript to dist/
npm test # unit tests (offline, 40 tests)
npm run test:mcp # MCP tests against the live site (23 checks)
npm run test:integration # live integration tests (21 tests)
npm run test:all # everythingTests are real: unit tests run offline against sanitised fixtures, while integration and MCP tests hit the live site read-only and are discovery-based (they search, then open whatever came back) rather than depending on hardcoded listing ids that might expire.
Project layout
src/
types/ public types
parsing/ __INITIAL_STATE__ extraction + normalisers
core/ BolhaClient, HTTP, URLs, cache, errors
cli/ the bolha command
mcp/ MCP stdio server
research/ Obsidian knowledge base (start at research/INDEX.md)
tests/ unit (offline) · integration (live) · mcp (live)Documentation
Document | Purpose |
Project contract: rules, confirmed findings, decisions, status | |
Every bug found, with cause, fix and live evidence | |
What works and what has actually been tested | |
Remaining work | |
Final test report: every feature with PASS/FAIL/LIMITATION | |
Knowledge-base entry point | |
How to contribute, and the project's ground rules | |
Code of conduct | |
What counts as a security issue, and how to report one | |
Release history |
Licence
MIT.
This server cannot be deployed
Maintenance
Related MCP Connectors
AI-agent-first offers directory: search and publish listings via MCP.
Pay-per-use tool marketplace for AI agents. Search, price-check, and call APIs via MCP.
Search and browse global classifieds across 80 markets. No auth required for read-only access.
Federated listings from personal humanMCP servers. Search offers, trades by humans.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceEnables AI assistants to search and consult Leboncoin classified ads through the MCP protocol, with tools for ad search, detail retrieval, user profiles, and category/region listings.MIT
- AlicenseAqualityAmaintenanceEnables AI agents to search and monitor Dutch and Belgian classifieds (Marktplaats and 2dehands) for listings, seller profiles, and categories.832 PyPI4MIT
- FlicenseAqualityCmaintenanceExposes the OLX.ba marketplace API as MCP tools, enabling LLM clients to authenticate, browse and manage listings, read categories/attributes/locations, and run sponsorship/discount actions.35-
- AlicenseNot gradedqualityDmaintenanceMCP server for OLX marketplace. Enables AI assistants to search listings, get offer details, track prices over time, and compare offers across OLX Poland and other supported countries.60 npm4MIT