Skip to main content
Glama

Bolha

CI npm license node

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 the research/ 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 /avto-oglasi) are listed and browsable

Listings

Full detail: description, all image sizes, seller, category path, Bolha's attribute table, schema.org data

Sorting

relevance (search only), new, old, cheap, expensive, distance

Pagination

Multi-page walking, de-duplicated, bounded for safety

Sellers

Private sellers (/uporabnik/) and business stores (/trgovina/) with their current listings

Diagnostics

bolha doctor — nine live checks so silent breakage is visible

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 build

Run the CLI directly, or link it onto your PATH:

node dist/cli/index.js --help

# or, globally:
npm link
bolha --help

CLI 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 diagnostics

Examples

# 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 --verbose

Options

Option

Meaning

--limit <n>

Max listings (default 25, cap 500)

--page <n>

1-based page (max 100)

--sort <order>

relevance | new | old | cheap | expensive | distance

--category <slug> / --category-id <n>

Restrict to a category

--min-price / --max-price

Price range in EUR

--condition <c>

Repeatable: new | used | defective

--lat --lng --radius

Radius search

--location <id>

Repeatable Bolha location id

--ads-with-images

Only listings with a photo

--param <k=v>

Any other Bolha URL parameter (repeatable)

--json

Pure JSON on stdout

--verbose / -v

Log every HTTP request to stderr

--timeout --concurrency --retries

Network tuning

--no-cache / --cache-dir

Cache control

--help, --version

Exit codes

Code

Meaning

0

Success

2

Usage error (bad argument, limit above cap)

3

Not found / no results / removed listing / parent-category page

4

Network, HTTP or parse failure

130

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

search(options)

ListingPage

browseCategory(slug, options)

ListingPage

getListing(urlOrPath)

ListingDetail

getTopCategories()

Category[]

getCategory(slug)

CategoryDetail

getFilters({ categorySlug })

FilterDefinition[]

getSortOrders({ categorySlug })

SortOption[]

getSeller(ref, options)

SellerProfile

resolveSellerFromListing(url)

string | null

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

bolha_search

Text search with filters and sorting

bolha_browse_category

Listings in one category, no query needed

bolha_get_categories

Discover category slugs

bolha_get_category

One category's subcategories, filters and sort orders

bolha_get_facets

A category's facet sub-pages (vehicle makes, brand tags)

bolha_get_locations

Slovenian localities (regions and towns) for a category or free-text search

bolha_get_listing

Full detail of one listing

bolha_get_regions

The eleven Slovenian statistical regions

bolha_get_location_categories

Which categories support locality filtering

bolha_search_in_location

Text search scoped to a region or town

bolha_get_seller

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

BOLHA_CACHE_DIR

Cache location (default ~/.bolha/cache)

BOLHA_NO_CACHE=1

Disable the cache

BOLHA_USER_AGENT

Override the User-Agent

BOLHA_VERBOSE=1

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

BOLHA_USER_AGENT

descriptive default

Identify your client to Bolha

BOLHA_CACHE_DIR

~/.bolha/cache

Cache location

BOLHA_NO_CACHE

unset

Disable caching

BOLHA_VERBOSE

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 build

Quick check that it works before wiring it into anything:

npm run doctor

Claude Code (terminal agent)

Run this once:

claude mcp add bolha --scope user -- node D:\marketplace\bolha\dist\mcp\server.js

Then verify:

claude mcp list          # should show: bolha: node D:\marketplace\bolha\dist\mcp\server.js - √ Connected

Or add it interactively from inside a claude session with /mcp.

Scopes

Scope

Flag

Where it is stored

user (all your projects)

--scope user

~/.claude.json

project (this repo only)

--scope project

.mcp.json in the repo

local (this one project, you only)

--scope local

~/.claude.json, project-scoped

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.js

Claude 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

BOLHA_USER_AGENT

Identify yourself to bolha.com (please do set this)

BOLHA_CACHE_DIR

Cache location (default ~/.bolha/cache)

BOLHA_NO_CACHE=1

Disable caching

BOLHA_VERBOSE=1

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.js

Verifying 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_CHANGED instead 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

User-agent: *

AI crawler groups

/search, /hitro-iskanje, /brza-pretraga

Disallowed

mixed

/uporabnik/, /*-oglas-, /objava-oglasa

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/seller all 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-Agent you can override via BOLHA_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         # everything

Tests 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

AGENTS.md

Project contract: rules, confirmed findings, decisions, status

BUGS.md

Every bug found, with cause, fix and live evidence

STATUS.md

What works and what has actually been tested

TODO.md

Remaining work

COMPLETION_REPORT.md

Final test report: every feature with PASS/FAIL/LIMITATION

research/INDEX.md

Knowledge-base entry point

CONTRIBUTING.md

How to contribute, and the project's ground rules

CODE_OF_CONDUCT.md

Code of conduct

SECURITY.md

What counts as a security issue, and how to report one

CHANGELOG.md

Release history


Licence

MIT.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables 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
  • A
    license
    A
    quality
    A
    maintenance
    Enables AI agents to search and monitor Dutch and Belgian classifieds (Marktplaats and 2dehands) for listings, seller profiles, and categories.
    8
    32 PyPI
    4
    MIT
  • F
    license
    A
    quality
    C
    maintenance
    Exposes 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
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP 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 npm
    4
    MIT