ImotAI — Bulgarian real estate
RemoteAbout ImotAI — Bulgarian real estate
ImotAI is a read-only MCP server that gives agents access to Bulgarian property listings and market data from imotai.bg. It supports property searches, district statistics, and asking-price estimates based on active listings rather than completed transactions.
Key features
- 01List cities and their price districts
- 02Search sale and rental listings
- 03View listings with district market context
- 04Get monthly reports and market statistics
- 05Estimate prices from comparable listings
Use cases
- 01Find listings by city and budget
- 02Compare asking prices across districts
- 03Review monthly changes in a city market
- 04Estimate a property's price from comparable listings
MCP support
The MCP server provides read-only property search, market statistics, reports, and valuation tools over Streamable HTTP or local stdio, with no API key required. Output fields are allowlisted and exclude broker contact details, descriptions, and exact coordinates.
Configuration
Remote servers need no local install. Pick your client, then copy the config or install in one click.
https://imotai.bg/mcpclaude mcp add --transport http bg-imotai-imotai https://imotai.bg/mcp{"mcpServers":{"bg-imotai-imotai":{"type":"http","url":"https://imotai.bg/mcp"}}}Open in Cursor{"servers":{"bg-imotai-imotai":{"type":"http","url":"https://imotai.bg/mcp"}}}Open in VS Code{"mcpServers":{"bg-imotai-imotai":{"type":"http","url":"https://imotai.bg/mcp"}}}{"mcpServers":{"bg-imotai-imotai":{"serverUrl":"https://imotai.bg/mcp"}}}Run commands in a terminal; paste JSON into the client’s MCP config and restart. Servers that need sign-in will prompt on first connect.
Tools
Details
From the project READMEImotAI MCP Server
A Model Context Protocol server that gives AI agents (Claude Desktop, Claude Code, …) a read-only window onto Bulgarian real-estate data from imotai.bg.
It is a thin client over the site's public API — no API keys, no writes, no personal data (broker names, phones and free-text descriptions never leave the API). Numbers are based on current asking prices of active listings, not on closed transactions.
Use it online (no install)
The server runs at https://imotai.bg/mcp (Streamable HTTP, stateless, no key).
- Claude (claude.ai / desktop app): Settings → Connectors → Add custom connector →
https://imotai.bg/mcp - Claude Code:
claude mcp add --transport http imotai https://imotai.bg/mcp - Cursor / VS Code / other clients (
mcp.json):
{ "mcpServers": { "imotai": { "url": "https://imotai.bg/mcp" } } }
- ChatGPT: add a custom connector with
https://imotai.bg/mcp(developer mode, depending on plan).
Instructions in Bulgarian: https://imotai.bg/ii-asistenti. Online limits: 30 requests/min per client.
Status
v0.2.0. Six tools (list_districts, search_properties, get_property,
get_market_stats, get_market_report, estimate_value), each with an explicit output
allowlist; the property tools carry a marketContext block. Two transports: stdio
(dist/index.js, local) and Streamable HTTP (src/http.ts, the hosted endpoint above).
get_market_report returns the monthly ImotAI market report for a city (median asking
price per m², month-over-month change, districts, new listings).
Data: CC BY 4.0 — credit ImotAI (imotai.bg).
Install
git clone https://github.com/pdimiev-prog/imotai-mcp.git
cd imotai-mcp
npm install
npm run build # produces dist/index.js
Use it in Claude Desktop
Add this to claude_desktop_config.json (use the absolute path to dist/index.js):
{
"mcpServers": {
"imotai": {
"command": "node",
"args": ["/absolute/path/to/imotai-mcp/dist/index.js"],
"env": { "IMOTAI_API_URL": "https://imotai.bg/api" }
}
}
}
Restart Claude Desktop, then ask something like:
Which districts does ImotAI track in Sofia, and how many active listings are in each?
Claude will call list_districts and answer from the result.
Tools
list_districts
Input: { city: string } — a city slug ("sofia") or its Bulgarian name ("София").
Output:
{
"city": { "slug": "sofia", "name": "София" },
"districts": [
{ "name": "Лозенец", "slug": "lozenets", "activeListings": 64 },
{ "name": "Витоша", "slug": "vitosha", "activeListings": null }
],
"note": "activeListings is filled only for districts with a public price page (>= 8 offers); a null count does not mean zero."
}
An unknown city returns { "error": "Unknown city \"…\". Recognised city slugs: …" }.
Call this first — the slugs it returns are the valid district values for the
other tools.
search_properties
Input:
| Field | Type | Notes |
|---|---|---|
city | string, required | Slug or Bulgarian name |
transactionType | "sale" | "rent", required | |
district | string | Slug from list_districts |
propertyType | enum | apartment, house, villa, studio, office, shop, warehouse, land, garage, other |
minPrice / maxPrice | number | EUR |
minArea / maxArea | number | m² |
rooms | string | CSV, e.g. "2,3"; "5+" for 5 or more |
limit | integer 1–25 | Default 10 |
Output: { results: [...], total, shown }. Each result has structured fields
plus a url to the listing. No contact details, no free-text description, no
coordinates.
get_property
Input: { id: string } — the listing's UUID or its URL slug.
Output: { found: true, url, ...structured fields, features: [] }, or
{ found: false, reason } if there is no such listing. Never returns the broker
description, phone/name, or exact coordinates.
get_market_stats
Input: { city: string, district?: string } — slugs or Bulgarian names from
list_districts. Omit district for city-level stats.
Output: { available: true, city, district, count, avgPrice, avgPricePerSqm, price: {p25,p50,p75}, pricePerSqm: {p25,p50,p75}, byRooms: [...], neighbouringDistricts: [...], basis, url }. Sale offers, all residential types.
Below 8 offers → { available: false, count, reason }. Unknown city/district →
{ error } pointing at list_districts.
Market context
Both property tools attach a marketContext — "is this listing priced above or
below its district":
get_property.marketContextis the authoritative per-listing figure from ImotAI's own calculation:districtAveragePricePerSqm,districtMedianPricePerSqm,deviationPercent(+ = pricier than the district),comparableActiveListings,basedOnCount,pricePageUrl.search_properties[].marketContextis a lighter district-level approximation for scanning (one price-page lookup per distinct district):districtAveragePricePerSqm,deviationPercent,comparableListings,basis.
Both are null when the district has fewer than 8 offers or no price page. The
two levels use slightly different denominators, so their numbers are close but
not identical — the get_property figure is the precise one.
estimate_value
Input:
| Field | Type | Notes |
|---|---|---|
city | string, required | Slug or Bulgarian name from list_districts |
district | string, required | Slug or Bulgarian name from list_districts |
propertyType | enum, required | apartment, house, villa, studio, land |
area | number, required | Living area in m² |
rooms | integer | Accepted but not used for filtering |
Output:
{
"available": true,
"estimate": 208000,
"rangeLow": 184000,
"rangeHigh": 240000,
"basedOnCount": 40,
"confidence": "medium", // from sample size; forced "low" for the market blend
"disclaimer": "…", // ALWAYS present — show verbatim
"referenceUrl": "https://imotai.bg/ceni/sofia/lozenets"
}
Below 5 comparable offers it returns { "available": false, "reason": "…", "disclaimer": "…" } — still with a disclaimer. An unknown city or district
returns { "error": "…" } pointing at list_districts (no disclaimer there).
confidence is high at ≥ 50 comparable offers, medium at ≥ 15, otherwise
low; it is always low when external market data
was blended into the figure, and the disclaimer then says so.
⚠️ This is positioning against the CURRENT ASKING PRICES of comparable active listings — not an appraisal, not an official valuation, not based on closed transactions. The
disclaimerfield is present on everyavailableresult; show it to the user verbatim whenever you cite any number from this tool.
Rate limiting
The server allows 60 downstream requests per minute per process (token bucket,
refills continuously). Every call to imotai.bg/api counts — a search_properties
that resolves three distinct districts spends four requests (one search + three price
pages).
When the bucket is empty a tool returns, without throwing:
{ "error": "Твърде много заявки за кратко — изчакайте минута." }
Wait about a minute and retry. The limit is per running server process; a single
Claude Desktop client is one process. Override it with IMOTAI_RATE_LIMIT_PER_MIN
if you run your own instance.
Configuration
| Env var | Default | Meaning |
|---|---|---|
IMOTAI_API_URL | https://imotai.bg/api | Base URL of the ImotAI public API |
IMOTAI_HTTP_TIMEOUT_MS | 15000 | Per-request timeout |
IMOTAI_RATE_LIMIT_PER_MIN | 60 | Max downstream requests per minute (token bucket, per process) |
How it works
stdio MCP server (Node ≥ 20, ESM). Each tool maps to one or two calls against
the public imotai.bg/api endpoints and reshapes the response through an
explicit field allowlist. No state, no cache, no credentials.
Licence
MIT.
Comments
New to MCP?
From what the protocol is to installing your first server, these answers cover most first questions.
An open protocol that defines how AI applications connect to external tools and data in a uniform way. Clients talk to the model; MCP servers provide tools, resources and prompt templates. They exchange standard messages, so one server can be reused by many clients.