korter-mcp

A transparent, stateless MCP proxy over korter.ge — the Georgian new-build and secondary-market aggregator, which has no public API.

Streamable HTTP MCP open endpoint 1 request/second, shared stores nothing

One tool call fetches one korter page, live, parses the state korter inlines into it, filters the result in memory and hands it back with the URL it came from and how old korter admits the numbers are. There is no database behind this, no cache and no background collection — which is why every answer can say exactly where it came from, and why nothing here can answer a question about the past.

Personal research tool. Not a product, not a data service, not a way to bulk-export korter.

Connect

claude mcp add --transport http korter https://korter.prod.lh.jacum.com/mcp

No sign-in: /mcp is open. Once connected, call get_skill('apartment-search') — the server ships its own usage guidance rather than assuming you read this page.

Open, not unlimited. Every caller here shares one lane to korter at one request per second, because that pace is the whole basis on which this thing is allowed to read korter at all. You are not queueing behind a quota; you are queueing behind everyone else. See what happens when it is busy.

How one call works

The path of one tool call An MCP client calls one tool; the request passes a throttling edge proxy, then inside korter-mcp a robots.txt check, a shared one-request-per-second pacer with a bounded queue and a circuit breaker, then fetches one korter.ge page, parses its inline state, filters it in memory and returns it with a provenance envelope. Nothing is written to disk. MCP client Claude Code, Claude.ai one tool call = one korter page Traefik · Pomerium TLS · per-address throttle · body cap /mcp is open /api → 401 without a token korter-mcp one pod · no volume · no database robots.txt re-read daily unreadable → stop pacer 1 request/second, shared queue of 32, then refused, not queued circuit breaker 403/429 → opens and stays open GET /en/<slug> UA: korter-mcp/0.1 (personal research tool) korter.ge public HTML pages only 403 / 429 HTML with window.INITIAL_STATE parse · filter in memory · stamp provenance balanced-brace scan of the inline state — no HTML parser filters apply to THIS page’s cards, never across pages source_url · fetched_at · prices_as_of · staleness_days result, and the page is forgotten MCP client no database · no cache · no request log Nothing on this path writes to disk. There is no state between two calls.
One tool call, end to end. The three boxes inside the service are the only brakes on how much this thing asks of korter, so none of them is optional.

Tools

Each one reads exactly one korter page. They do not fan out, and none of them is a search across pages.

ToolReadsKey arguments
list_districts korter’s district taxonomy with their average $/m², and the slug to fetch next from_listing
get_listing one new-build listing page — project cards slug, min/max_price_per_m2, max_budget, construction_status, developer, q
get_project one project page — unit types (rooms, areas, prices), the completion timeline, korter’s own “prices updated” date, one prior $/m² slug
get_secondary one resale or rent page — rooms, area, floor, price, korter’s actualize date slug, rooms, min_rooms, min/max_area_m2, max_price
fetch_page any allowed korter path or a URL you pasted, parsed as whatever it turns out to be path
get_skill the server’s own guidance — no network name

Picking the page is the query

There is no criteria-first search here. District and market section come from the slug you pass, not from a filter argument — the slug is the last segment of a korter.ge/en/… URL. So a search is three steps, coarse to fine:

  1. list_districts turns “in Vake” into a listing slug (new-projects-tbilisi-vake-district).
  2. get_listing fetches that one page and filters its cards on price, status and developer.
  3. get_project on a shortlist — rooms, areas and completion dates exist only there. Keep the shortlist to about ten: each one is a real korter request.

A page is not a market. korter serves ~20 cards per page and tells you how many the filter really has. Every result reports matched against korter_total_count for that reason — a shortlist off one page is one page, not the district.

Reading what comes back

Freshness is korter’s, not ours

Every result carries source_url, fetched_at, prices_as_of and staleness_days. The staleness counts from korter’s own “prices updated” date, and on project pages it is routinely 30, 90, 400 days — korter displays those prices as current.

staleness_days: null means unknown, never 0. Listing cards and district averages carry no date at all. A card fetched one second ago may show a price korter last touched a year ago, so “we just fetched it” is not “the price is current”. For a dated number, fetch the project page.

Completion dates, where korter has them

get_project returns completion: the developer’s stated finish for each building as a year and a quarter ("2027-Q2"), plus completes_first, completes_last, houses_undated and is_past_due. Project pages only — listing cards carry no year or quarter, so “ready in 2027 or later” is a shortlist-then-check, not a filter.

Read it the way you read the prices. korter often shows no completion date at all, and completes_last: null is unknown — not “no deadline”, not “finished”. The target is itself undated, so it can already have passed while the building is still going up; is_past_due says when it has.

There is no price history

korter publishes none. Its pages carry a single earlier $/m² with no date attached, which get_project returns as price_per_m2_previous and a one-step price_change. It supports “it moved, this way, at some point” and nothing else. It is not a trend, and accumulating a real one would mean storing observations, which this server deliberately does not do.

Two smaller ones

When it is busy, and what it refuses

An open endpoint and a one-request-per-second lane only coexist if the server says no clearly, so it does. None of the following is a fault to retry hard against — each one is the server working:

You seeIt meansDo
a “too many callers are already queued” tool error up to 32 callers may wait for the lane; you would have been the 33rd. The wait can therefore never exceed ~32s, which is the point of refusing rather than queueing. wait a few seconds; shorten your shortlist, or fetch pages one at a time
HTTP 429 the edge throttled your address before the request reached the service slow down; this is per-address, not global
HTTP 413 the request body is larger than a tools/call could sensibly be check what your client is sending
HTTP 403 with “Origin not allowed” a browser origin that is not allowlisted — the DNS-rebinding defence the MCP spec requires. Server-to-server clients send no Origin and are unaffected. connect from an MCP client, not a web page
“the circuit breaker is open” korter answered 403 or 429. This one does not clear on its own. stop. It stays stopped until a human restarts the process, by design

Being open changes who can reach the service; it does not change what korter sees. The pace, the User-Agent, the robots checks and the breaker are the same as they were when the endpoint was closed, and the ceiling is still one request per second in total — not per caller.

How it behaves toward korter

Privacy

The service stores nothing about anyone who uses it: no profiles, no consent records, no search logs. Authentication checks that a caller got through and then forgets them. Tool arguments build one korter request, filter its response, and are gone when the call returns.