booli-mcp
One-line summary: Read-only access to Booli.se Swedish property data — active listings, sold prices, area lookup, and market statistics — through your signed-in browser session.
Resolve areas (
booli_search_areas): turn a place name (e.g. "Nacka", "Södermalm") into Booliarea_ids for municipalities, districts, and streets.Search for-sale listings (
booli_search_listings): scope byarea_idor free-textlocation, filter by price, SEK/m², rooms, living/plot area, object type (Lägenhet, Villa, etc.), new-construction status, and construction year; sort and paginate viapage/total_count.Get one property (
booli_get_listing): full detail for a residence id (from a/bostad/<id>URL or search result), for active or sold properties.Search sold listings / slutpriser (
booli_search_sold): comparables with achieved final price and over/under-asking %, filterable additionally by sold price and sold date (YYYYMMDD).Compute market stats (
booli_market_stats): median/average sold price, price per m², and average over/under-asking % for an area, with asample_sizewarning for thin data.Diagnose the data path (
booli_healthcheck): reportstransport(direct vs fetchproxy), bridge role/port/version, extension link state (linked / pair pending / disconnected), round-trip time, and a next-step hint.All tools are read-only (no Booli login required), with
compact/fullresponse views; money in SEK, areas in m².
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., "@booli-mcpShow me sold prices in Göteborg"
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.
booli-mcp
An MCP server for Booli, the Swedish property portal — search active for-sale listings, sold prices (slutpriser), resolve areas, and compute market statistics, all from Claude.
Developed and maintained by AI (Claude Code). Use at your own discretion and within booli.se's terms of use.
How it works
Booli fronts www.booli.se — including its GraphQL API — with a Cloudflare bot wall that blocks server-side clients. booli-mcp therefore reads Booli's consumer GraphQL API by routing each query through your own signed-in www.booli.se browser tab via the fetchproxy bridge (the ContextMint Bridge browser extension), reusing your Cloudflare-cleared session. No Booli login is required — just a normal page view. All tools are read-only.
BOOLI_TRANSPORT selects the path: auto (default — direct fetch first,
browser-bridge fallback when walled), fetchproxy (always the bridge), or
direct. The fetchproxy fleet shares WS port 37149 (BOOLI_WS_PORT).
Related MCP server: Idealista7 MCP Server
Setup
Install ContextMint Bridge from its releases page — Chrome: download the chrome zip, unzip it, and load it unpacked at
chrome://extensions(Developer mode). Safari isn't available yet (it will ship inside the ContextMint app, which has no public download), so use Chrome for now. Keep a www.booli.se tab open.ContextMint Bridge is the fetchproxy browser extension under its new name, from the same maintainer — fetchproxy's own README points to it. Its source is public at nullnet-app/contextmint-bridge: build it yourself, or check a release zip against the
.sha256file published beside it (shasum -a 256 -c contextmint-bridge-chrome-<version>.zip.sha256).On the first request, approve the one-time pairing prompt in ContextMint Bridge.
Run
booli_healthcheckto confirm the path is working. Itstransportfield says which leg served the probe (directorfetchproxy) and, once the bridge exists,bridge.session_statesays whether the extension islinked,pair_pending(approve the pair code it names), orextension_disconnected.
Install
// mcp config
{
"mcpServers": {
"booli": {
"command": "npx",
"args": ["-y", "@chrischall/booli-mcp"]
}
}
}Tools
Tool | What it does |
| Resolve a place name to Booli area ids |
| Search active for-sale listings by area + filters |
| Full detail for one property (active or sold) by residence id |
| Search sold listings (slutpriser) with final prices |
| Median/average sold-price statistics for an area |
| Probe the data path and report |
Searches scope by area_id (from booli_search_areas) or a free-text
location. Money is SEK, areas m². See
docs/BOOLI-API.md for the underlying GraphQL API.
Development
npm install
npm test # vitest, no network
npm run build # tsc + esbuild bundleLicense
MIT
Available Tools
6 toolsbooli_get_listingGet a Booli propertyARead-onlyIdempotent
Full detail for one property by its Booli residence id — the number in a booli.se/bostad/ URL, or the residence_id from a search result. Works for both active and sold properties. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| view | No | Response shape: "compact" (default) drops fields the response already carries elsewhere; "full" returns every field this server understands. compact returns the slim PropertySummary/detail projection; "full" returns Booli's whole GraphQL node. | |
| residence_id | Yes | The property's residence id (e.g. "4370936" from /bostad/4370936). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, and openWorldHint, so the safety profile is covered. The description reinforces 'Read-only' and adds the useful behavioral detail that both active and sold properties are supported, but it does not add substantial new behavioral context beyond that.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three short sentences with no filler. The core purpose is front-loaded, and every sentence earns its place by conveying scope, input origin, or safety.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple single-resource getter, this is complete: it names the required identifier, defines its provenance, notes optional response shape via the view parameter, and states applicability to active and sold properties. An output schema is absent, but the description's 'full detail' plus the view parameter documentation is sufficient for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description adds a small but real semantic cue by explaining that residence_id can come from either a booli.se/bostad/<id> URL or a search result's residence_id field, which helps the agent source the value correctly.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'Full detail for one property by its Booli residence id.' It clearly identifies the lookup key and distinguishes this from the sibling search/stats/health tools, which do different jobs.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It tells the agent the precondition for use: the agent must already have a Booli residence id, either from a URL or a search result. It also clarifies that the tool works for both active and sold properties. It does not explicitly name alternative tools for cases where no id is available, but the context and sibling names make this reasonably clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
booli_healthcheckVerify the ContextMint Bridge connection end-to-endARead-onlyIdempotent
Round-trips a small public www.booli.se URL (/graphql) through ContextMint Bridge (your signed-in browser tab) and returns diagnostics: the bridge's role (host/peer/null), port, version, the extension link (linked / pair pending / not attached / never answered), the elapsed round-trip time, and a plain-English hint distinguishing 'bridge never came up' from 'extension not connected' from 'this browser can't serve a capability' from 'real www.booli.se-side problem'. Read-only, no auth required. Call this when a real tool fails and you want to know which hop broke.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the annotations by enumerating the returned diagnostics (bridge role, port, version, extension link state, elapsed time) and enumerating the failure classes the hint distinguishes (bridge never came up / extension not connected / browser can't serve capability / server-side problem). It also confirms 'no auth required'. This is real behavioral context an agent cannot infer from readOnlyHint/idempotentHint alone.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single dense sentence plus a short routing instruction, front-loaded with the action and ending with the failure-mode enumeration. The list of diagnostics is long but each item earns its place; the only minor cost is one very overloaded sentence.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the full burden of explaining what comes back, and it does so comprehensively – listing every diagnostic field and the interpretive hint. Nothing needed to call or interpret this tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the baseline is 4. There is nothing for the description to disambiguate beyond confirming the call is parameter-free, which it implicitly does by describing a self-contained round-trip.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific action (round-trips a public booli.se URL through ContextMint Bridge), the exact resource (/graphql), and the nature of the result (diagnostics). It is unmistakably a health/diagnostic tool, distinct from the listing/search/stats siblings, without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says when to call it: 'Call this when a real tool fails and you want to know which hop broke.' That is a clear trigger condition and implicitly positions it as a fallback to the other booli_* tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
booli_market_statsBooli sold-price market statisticsARead-only
Aggregate sold-price statistics (median/average final price, price per m², average over/under-asking %) for an area on booli.se. Takes the same scope and filters as booli_search_sold, over one page of sold results. Check sample_size before trusting a thin median. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | 1-based page (default 1). | |
| sort | No | Sort key (default: soldDate — most recent sales first with ascending false). | |
| view | No | Response shape: "compact" (default) drops fields the response already carries elsewhere; "full" returns every field this server understands. compact returns the slim PropertySummary/detail projection; "full" returns Booli's whole GraphQL node. | |
| area_id | No | Booli area id from booli_search_areas. Provide this OR `location`. | |
| location | No | Free-text place name (e.g. "Nacka", "Södermalm") resolved to its top Booli area. Ignored when `area_id` is set. | |
| ascending | No | Sort ascending (default false). | |
| max_rooms | No | ||
| min_rooms | No | ||
| object_type | No | Property type(s), comma-separated, from: Lägenhet, Villa, Kedjehus-Parhus-Radhus, Fritidshus, Gård, Tomt/Mark. | |
| max_plot_area | No | m² | |
| max_sold_date | No | Latest sold date, YYYYMMDD. | |
| min_plot_area | No | m² | |
| min_sold_date | No | Earliest sold date, YYYYMMDD (e.g. "20240101"). | |
| max_sold_price | No | SEK | |
| min_sold_price | No | SEK | |
| max_living_area | No | m² | |
| min_living_area | No | m² | |
| max_sold_sqm_price | No | SEK/m² | |
| min_sold_sqm_price | No | SEK/m² | |
| is_new_construction | No | true = only new production; false = exclude new production. | |
| max_construction_year | No | ||
| min_construction_year | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark readOnlyHint=true, so the description's 'Read-only' is redundant. However, it adds valuable behavioral context: the tool operates over exactly one page of results and warns about sample_size affecting median reliability. These are not derivable from annotations or schema, so they meaningfully disclose behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences, front-loads the purpose, and includes the key caveat about sample_size. Every sentence carries meaning with no filler or repetition of schema details. It is appropriately concise for the tool's complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool has 22 parameters, no output schema, and is a read-only aggregation, the description is sufficient. It explains what it returns, its relationship to booli_search_sold, and a practical trust caveat. It does not detail return structure or pagination beyond one page, but that is reasonably implied. For an aggregation tool with this complexity, it is well-rounded.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is high (82%), so the schema already documents most parameters. The description adds no per-parameter detail, but it references that the tool shares the same filters as booli_search_sold, which implies parameter reuse. Since the schema covers the parameters, a baseline of 3 is appropriate; the description adds marginal value beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb ('Aggregate') and a clear resource ('sold-price statistics'), and explicitly lists the derived metrics (median/average final price, price per m², average over/under-asking %). It also differentiates from sibling tools by referencing booli_search_sold as the scope basis, so an agent can distinguish it from listing retrieval.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states the tool takes 'the same scope and filters as booli_search_sold', giving clear context on when to use it (for aggregate stats on a page of sold results). It also adds a practical tip ('Check sample_size before trusting a thin median'). It does not explicitly name alternatives or exclusions, but the reference to booli_search_sold implicitly routes away from listing tools.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
booli_search_areasResolve a Booli areaARead-onlyIdempotent
Resolve a place name to Booli areas — municipalities, districts, streets — each with its area_id to pass as area_id into booli_search_listings / booli_search_sold. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Max results (default 10). | |
| query | Yes | Place-name search string (e.g. "Nacka", "Södermalm"). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already cover safety with readOnlyHint, idempotentHint, and openWorldHint, lowering the bar. The description adds some behavioral context by specifying that it returns municipalities, districts, and streets each with an `area_id`, but 'Read-only' merely repeats what readOnlyHint already declares. It does not disclose ambiguity handling, multiple match behavior, or other runtime quirks.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The core sentence is dense, front-loaded, and contains the essential purpose, output types, and downstream usage in one line. The trailing 'Read-only.' is redundant with the annotations but is harmless; the overall definition is compact and efficient.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple two-parameter resolver with safe annotations, the description is largely complete: it explains the input, the output types, and how the output is consumed by sibling tools. Because there is no output schema, it would be slightly stronger to state what happens on no match or how many areas may be returned, but the essential calling context is present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already explains both `query` and `limit`. The description reinforces that `query` is a place name and that the result carries `area_id`, but it does not add meaningful parameter-level detail beyond what the input schema provides, so the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description uses a specific verb ('Resolve') plus a clear resource ('place name to Booli areas') and names the concrete output types: municipalities, districts, streets. It also differentiates the tool by stating that the resulting `area_id` feeds into booli_search_listings / booli_search_sold, so an agent can distinguish it from its siblings without opening their schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear context for when to use the tool: resolving a place name before searching listings or sold data via the returned `area_id`. It names the downstream tools, which implies the intended workflow, but it does not explicitly state when not to use this tool or name a direct alternative.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
booli_search_listingsSearch Booli for-sale listingsARead-only
Search active for-sale property listings on booli.se. Scope by area_id (from booli_search_areas) or a free-text location, and filter by price, rooms, living area, plot, object type, construction year. Paginated by page; check total_count/pages. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | 1-based page (default 1). | |
| sort | No | Sort key (default: newest published). | |
| view | No | Response shape: "compact" (default) drops fields the response already carries elsewhere; "full" returns every field this server understands. compact returns the slim PropertySummary/detail projection; "full" returns Booli's whole GraphQL node. | |
| area_id | No | Booli area id from booli_search_areas. Provide this OR `location`. | |
| location | No | Free-text place name (e.g. "Nacka", "Södermalm") resolved to its top Booli area. Ignored when `area_id` is set. | |
| ascending | No | Sort ascending (default false). | |
| max_rooms | No | ||
| min_rooms | No | ||
| object_type | No | Property type(s), comma-separated, from: Lägenhet, Villa, Kedjehus-Parhus-Radhus, Fritidshus, Gård, Tomt/Mark. | |
| max_plot_area | No | m² | |
| min_plot_area | No | m² | |
| max_list_price | No | SEK | |
| min_list_price | No | SEK | |
| max_living_area | No | m² | |
| min_living_area | No | m² | |
| max_list_sqm_price | No | SEK/m² | |
| min_list_sqm_price | No | SEK/m² | |
| is_new_construction | No | true = only new production; false = exclude new production. | |
| max_construction_year | No | ||
| min_construction_year | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, and the description reinforces this with 'Read-only.' It adds behavioral context beyond annotations by describing pagination ('Paginated by `page`; check `total_count`/`pages`') and the scoping rule ('area_id ... OR location'). No contradictions; the description complements the annotations with operational detail.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, no wasted words. The purpose is stated first, then scoping, then filters, then pagination. Every clause earns its place. The structure is front-loaded with the most important information and reads naturally.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 20-parameter search tool with no output schema, the description covers the core behaviors: what it searches, how to scope, what filters exist, and how pagination works. It references a sibling for area_id resolution. It doesn't mention authentication or rate limits, but those are not essential for selection. The description is sufficient for an agent to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 80%, so the baseline is 3. The description adds value by explaining the relationship between area_id and location (location ignored when area_id is set), and by grouping filters by category (price, rooms, living area, etc.). It also clarifies that area_id comes from booli_search_areas, which is not in the schema. This goes beyond the schema's per-parameter descriptions, warranting a 4.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a precise verb+resource: 'Search active for-sale property listings on booli.se.' It distinguishes itself from siblings by the 'active for-sale' qualifier, which sets it apart from booli_search_sold (sold listings), booli_get_listing (single listing), and the others. An agent can tell exactly what this tool does and when to reach for it.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear context: it's for active for-sale listings, and it even references booli_search_areas for obtaining area_id. It implies the 'when' (when you need active listings) but does not explicitly state exclusions like 'use booli_search_sold for sold listings.' However, the 'active' qualifier and the sibling names make the usage boundary evident, so it's clear enough without being exhaustive.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
booli_search_soldSearch Booli sold listings (slutpriser)ARead-only
Search sold properties (slutpriser) on booli.se with the achieved final price and over/under-asking % — the comparables for valuation. Scope by area_id or free-text location, filter by sold price, sold date, rooms, area, object type. Paginated by page. Read-only.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | 1-based page (default 1). | |
| sort | No | Sort key (default: soldDate — most recent sales first with ascending false). | |
| view | No | Response shape: "compact" (default) drops fields the response already carries elsewhere; "full" returns every field this server understands. compact returns the slim PropertySummary/detail projection; "full" returns Booli's whole GraphQL node. | |
| area_id | No | Booli area id from booli_search_areas. Provide this OR `location`. | |
| location | No | Free-text place name (e.g. "Nacka", "Södermalm") resolved to its top Booli area. Ignored when `area_id` is set. | |
| ascending | No | Sort ascending (default false). | |
| max_rooms | No | ||
| min_rooms | No | ||
| object_type | No | Property type(s), comma-separated, from: Lägenhet, Villa, Kedjehus-Parhus-Radhus, Fritidshus, Gård, Tomt/Mark. | |
| max_plot_area | No | m² | |
| max_sold_date | No | Latest sold date, YYYYMMDD. | |
| min_plot_area | No | m² | |
| min_sold_date | No | Earliest sold date, YYYYMMDD (e.g. "20240101"). | |
| max_sold_price | No | SEK | |
| min_sold_price | No | SEK | |
| max_living_area | No | m² | |
| min_living_area | No | m² | |
| max_sold_sqm_price | No | SEK/m² | |
| min_sold_sqm_price | No | SEK/m² | |
| is_new_construction | No | true = only new production; false = exclude new production. | |
| max_construction_year | No | ||
| min_construction_year | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already provide readOnlyHint and openWorldHint, and the description reinforces safety with 'Read-only.' It adds useful behavioral context beyond annotations by specifying the result includes achieved final price and over/under-asking %, plus pagination by page. No contradictions with annotations are present.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded with the core purpose before filter details. It earns its place overall, though 'Read-only' repeats the annotation and the single long sentence could be split for slightly easier parsing.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite 22 optional parameters and no output schema, the description plus schema covers scope selection, filter dimensions, pagination, and key return fields. An agent can determine how to invoke the tool, though deeper output-shape details are left unspecified.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is high at 82%, so the baseline is 3. The description adds a helpful high-level grouping of scope by area_id/location and filter categories, but it does not add substantial new parameter meaning beyond what the schema already documents for the remaining parameters.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: searching sold properties (slutpriser) on booli.se. It also adds distinguishing scope by mentioning achieved final price and over/under-asking %, positioning it as the comparables tool next to siblings like booli_search_listings and booli_market_stats.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'the comparables for valuation' gives clear context for when this tool is the right choice. It does not explicitly name alternatives or exclusion conditions, but the sold-property framing strongly separates it from active listing or stats siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v2.1.3- Changed
booli_market_stats2 fields changed- changed
Input schema / properties / sort / descriptionPrevious value: -"Sort key (default: newest published)."New value: +"Sort key (default: soldDate — most recent sales first with ascending false)." - changed
Input schema / properties / sort / enumPrevious value: -[ - "published", - "listPrice", - "listSqmPrice", - "rooms", - "livingArea", - "rent", - "plotArea" -]New value: +[ + "published", + "listPrice", + "listSqmPrice", + "rooms", + "livingArea", + "rent", + "plotArea", + "soldDate", + "soldPrice" +]
- Changed
booli_search_sold2 fields changed- changed
Input schema / properties / sort / descriptionPrevious value: -"Sort key (default: newest published)."New value: +"Sort key (default: soldDate — most recent sales first with ascending false)." - changed
Input schema / properties / sort / enumPrevious value: -[ - "published", - "listPrice", - "listSqmPrice", - "rooms", - "livingArea", - "rent", - "plotArea" -]New value: +[ + "published", + "listPrice", + "listSqmPrice", + "rooms", + "livingArea", + "rent", + "plotArea", + "soldDate", + "soldPrice" +]
6 tool updates
v2.0.0- Changed
booli_get_listing1 field changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
- Changed
booli_healthcheck1 field changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
- Changed
booli_market_stats1 field changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
- Changed
booli_search_areas1 field changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
- Changed
booli_search_listings1 field changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
- Changed
booli_search_sold1 field changed- changed
Input schema / $schemaPrevious value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
4 tool updates
v1.3.1- Changed
booli_get_listing2 fields changed- removed
Input schema / properties / compactRemoved value: -{ - "description": "Return a slim summary instead of the full raw record (default false).", - "type": "boolean" -} - added
Input schema / properties / viewAdded value: +{ + "description": "Response shape: \"compact\" (default) drops fields the response already carries elsewhere; \"full\" returns every field this server understands. compact returns the slim PropertySummary/detail projection; \"full\" returns Booli's whole GraphQL node.", + "enum": [ + "compact", + "full" + ], + "type": "string" +}
- Changed
booli_market_stats2 fields changed- removed
Input schema / properties / compactRemoved value: -{ - "description": "Return slim summary records (default true). Set false for full raw fields.", - "type": "boolean" -} - added
Input schema / properties / viewAdded value: +{ + "description": "Response shape: \"compact\" (default) drops fields the response already carries elsewhere; \"full\" returns every field this server understands. compact returns the slim PropertySummary/detail projection; \"full\" returns Booli's whole GraphQL node.", + "enum": [ + "compact", + "full" + ], + "type": "string" +}
- Changed
booli_search_listings2 fields changed- removed
Input schema / properties / compactRemoved value: -{ - "description": "Return slim summary records (default true). Set false for full raw fields.", - "type": "boolean" -} - added
Input schema / properties / viewAdded value: +{ + "description": "Response shape: \"compact\" (default) drops fields the response already carries elsewhere; \"full\" returns every field this server understands. compact returns the slim PropertySummary/detail projection; \"full\" returns Booli's whole GraphQL node.", + "enum": [ + "compact", + "full" + ], + "type": "string" +}
- Changed
booli_search_sold2 fields changed- removed
Input schema / properties / compactRemoved value: -{ - "description": "Return slim summary records (default true). Set false for full raw fields.", - "type": "boolean" -} - added
Input schema / properties / viewAdded value: +{ + "description": "Response shape: \"compact\" (default) drops fields the response already carries elsewhere; \"full\" returns every field this server understands. compact returns the slim PropertySummary/detail projection; \"full\" returns Booli's whole GraphQL node.", + "enum": [ + "compact", + "full" + ], + "type": "string" +}
6 tool updates
v1.0.0- Changed
booli_get_listing3 fields changed- removed
Input schema / properties / booli_idRemoved value: -{ - "description": "The listing's Booli id (e.g. \"1579812\").", - "type": "string" -} - added
Input schema / properties / residence_idAdded value: +{ + "description": "The property's residence id (e.g. \"4370936\" from /bostad/4370936).", + "type": "string" +} - changed
Input schema / requiredPrevious value: -[ - "booli_id" -]New value: +[ + "residence_id" +]
- Removed
booli_get_sold - Changed
booli_market_stats13 fields changed- changed
Input schema / properties / area_id / descriptionPrevious value: -"Booli area id(s) from booli_search_areas, comma-separated for several (e.g. \"76,16\")."New value: +"Booli area id from booli_search_areas. Provide this OR `location`." - added
Input schema / properties / ascendingAdded value: +{ + "description": "Sort ascending (default false).", + "type": "boolean" +} - removed
Input schema / properties / bboxRemoved value: -{ - "description": "Bounding box \"lat_lo,lng_lo,lat_hi,lng_hi\" (SW then NE corner).", - "type": "string" -} - removed
Input schema / properties / centerRemoved value: -{ - "description": "Coordinate \"lat,lng\" (e.g. \"59.34674,18.0603\"); use with `dim`.", - "type": "string" -} - removed
Input schema / properties / dimRemoved value: -{ - "description": "Rectangle size in metres \"w,h\" (e.g. \"400,500\"), used with `center`.", - "type": "string" -} - removed
Input schema / properties / limitRemoved value: -{ - "description": "Default 30, max 100.", - "maximum": 100, - "minimum": 1, - "type": "integer" -} - added
Input schema / properties / locationAdded value: +{ + "description": "Free-text place name (e.g. \"Nacka\", \"Södermalm\") resolved to its top Booli area. Ignored when `area_id` is set.", + "type": "string" +} - removed
Input schema / properties / max_rentRemoved value: -{ - "description": "SEK/month", - "minimum": 0, - "type": "number" -} - changed
Input schema / properties / object_type / descriptionPrevious value: -"Property type(s), comma-separated, from: villa, lägenhet, gård, tomt-mark, fritidshus, parhus, radhus, kedjehus."New value: +"Property type(s), comma-separated, from: Lägenhet, Villa, Kedjehus-Parhus-Radhus, Fritidshus, Gård, Tomt/Mark." - removed
Input schema / properties / offsetRemoved value: -{ - "description": "Pagination offset.", - "maximum": 9007199254740991, - "minimum": 0, - "type": "integer" -} - added
Input schema / properties / pageAdded value: +{ + "description": "1-based page (default 1).", + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer" +} - removed
Input schema / properties / qRemoved value: -{ - "description": "Free-text area search (e.g. \"Nacka\", \"Södermalm\"). One of q / area_id / center / bbox scopes the search.", - "type": "string" -} - added
Input schema / properties / sortAdded value: +{ + "description": "Sort key (default: newest published).", + "enum": [ + "published", + "listPrice", + "listSqmPrice", + "rooms", + "livingArea", + "rent", + "plotArea" + ], + "type": "string" +}
- Changed
booli_search_areas8 fields changed- removed
Input schema / properties / latRemoved value: -{ - "description": "Latitude; use together with `lng`.", - "type": "number" -} - changed
Input schema / properties / limit / descriptionPrevious value: -"Default 10, max 50."New value: +"Max results (default 10)." - removed
Input schema / properties / lngRemoved value: -{ - "description": "Longitude; use together with `lat`.", - "type": "number" -} - removed
Input schema / properties / only_with_listingsRemoved value: -{ - "description": "Only return areas that currently have listings for sale.", - "type": "boolean" -} - removed
Input schema / properties / only_with_soldRemoved value: -{ - "description": "Only return areas that have sold listings.", - "type": "boolean" -} - removed
Input schema / properties / qRemoved value: -{ - "description": "Place-name search string (e.g. \"Nacka\").", - "type": "string" -} - added
Input schema / properties / queryAdded value: +{ + "description": "Place-name search string (e.g. \"Nacka\", \"Södermalm\").", + "type": "string" +} - added
Input schema / requiredAdded value: +[ + "query" +]
- Changed
booli_search_listings13 fields changed- changed
Input schema / properties / area_id / descriptionPrevious value: -"Booli area id(s) from booli_search_areas, comma-separated for several (e.g. \"76,16\")."New value: +"Booli area id from booli_search_areas. Provide this OR `location`." - added
Input schema / properties / ascendingAdded value: +{ + "description": "Sort ascending (default false).", + "type": "boolean" +} - removed
Input schema / properties / bboxRemoved value: -{ - "description": "Bounding box \"lat_lo,lng_lo,lat_hi,lng_hi\" (SW then NE corner).", - "type": "string" -} - removed
Input schema / properties / centerRemoved value: -{ - "description": "Coordinate \"lat,lng\" (e.g. \"59.34674,18.0603\"); use with `dim`.", - "type": "string" -} - removed
Input schema / properties / dimRemoved value: -{ - "description": "Rectangle size in metres \"w,h\" (e.g. \"400,500\"), used with `center`.", - "type": "string" -} - removed
Input schema / properties / limitRemoved value: -{ - "description": "Default 30, max 100.", - "maximum": 100, - "minimum": 1, - "type": "integer" -} - added
Input schema / properties / locationAdded value: +{ + "description": "Free-text place name (e.g. \"Nacka\", \"Södermalm\") resolved to its top Booli area. Ignored when `area_id` is set.", + "type": "string" +} - removed
Input schema / properties / max_rentRemoved value: -{ - "description": "SEK/month", - "minimum": 0, - "type": "number" -} - changed
Input schema / properties / object_type / descriptionPrevious value: -"Property type(s), comma-separated, from: villa, lägenhet, gård, tomt-mark, fritidshus, parhus, radhus, kedjehus."New value: +"Property type(s), comma-separated, from: Lägenhet, Villa, Kedjehus-Parhus-Radhus, Fritidshus, Gård, Tomt/Mark." - removed
Input schema / properties / offsetRemoved value: -{ - "description": "Pagination offset.", - "maximum": 9007199254740991, - "minimum": 0, - "type": "integer" -} - added
Input schema / properties / pageAdded value: +{ + "description": "1-based page (default 1).", + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer" +} - removed
Input schema / properties / qRemoved value: -{ - "description": "Free-text area search (e.g. \"Nacka\", \"Södermalm\"). One of q / area_id / center / bbox scopes the search.", - "type": "string" -} - added
Input schema / properties / sortAdded value: +{ + "description": "Sort key (default: newest published).", + "enum": [ + "published", + "listPrice", + "listSqmPrice", + "rooms", + "livingArea", + "rent", + "plotArea" + ], + "type": "string" +}
- Changed
booli_search_sold13 fields changed- changed
Input schema / properties / area_id / descriptionPrevious value: -"Booli area id(s) from booli_search_areas, comma-separated for several (e.g. \"76,16\")."New value: +"Booli area id from booli_search_areas. Provide this OR `location`." - added
Input schema / properties / ascendingAdded value: +{ + "description": "Sort ascending (default false).", + "type": "boolean" +} - removed
Input schema / properties / bboxRemoved value: -{ - "description": "Bounding box \"lat_lo,lng_lo,lat_hi,lng_hi\" (SW then NE corner).", - "type": "string" -} - removed
Input schema / properties / centerRemoved value: -{ - "description": "Coordinate \"lat,lng\" (e.g. \"59.34674,18.0603\"); use with `dim`.", - "type": "string" -} - removed
Input schema / properties / dimRemoved value: -{ - "description": "Rectangle size in metres \"w,h\" (e.g. \"400,500\"), used with `center`.", - "type": "string" -} - removed
Input schema / properties / limitRemoved value: -{ - "description": "Default 30, max 100.", - "maximum": 100, - "minimum": 1, - "type": "integer" -} - added
Input schema / properties / locationAdded value: +{ + "description": "Free-text place name (e.g. \"Nacka\", \"Södermalm\") resolved to its top Booli area. Ignored when `area_id` is set.", + "type": "string" +} - removed
Input schema / properties / max_rentRemoved value: -{ - "description": "SEK/month", - "minimum": 0, - "type": "number" -} - changed
Input schema / properties / object_type / descriptionPrevious value: -"Property type(s), comma-separated, from: villa, lägenhet, gård, tomt-mark, fritidshus, parhus, radhus, kedjehus."New value: +"Property type(s), comma-separated, from: Lägenhet, Villa, Kedjehus-Parhus-Radhus, Fritidshus, Gård, Tomt/Mark." - removed
Input schema / properties / offsetRemoved value: -{ - "description": "Pagination offset.", - "maximum": 9007199254740991, - "minimum": 0, - "type": "integer" -} - added
Input schema / properties / pageAdded value: +{ + "description": "1-based page (default 1).", + "maximum": 9007199254740991, + "minimum": 1, + "type": "integer" +} - removed
Input schema / properties / qRemoved value: -{ - "description": "Free-text area search (e.g. \"Nacka\", \"Södermalm\"). One of q / area_id / center / bbox scopes the search.", - "type": "string" -} - added
Input schema / properties / sortAdded value: +{ + "description": "Sort key (default: newest published).", + "enum": [ + "published", + "listPrice", + "listSqmPrice", + "rooms", + "livingArea", + "rent", + "plotArea" + ], + "type": "string" +}
7 tool updates
v0.0.0- First observed
booli_get_listing - First observed
booli_get_sold - First observed
booli_healthcheck - First observed
booli_market_stats - First observed
booli_search_areas - First observed
booli_search_listings - First observed
booli_search_sold
TDQS
Scored across 6 tools
Each tool has a distinct role: diagnostics (healthcheck), ID lookup (get_listing), area resolution (search_areas), active search (search_listings), sold search (search_sold), and aggregates (market_stats). The sold-search vs market-stats boundary could be adjacent, but descriptions explicitly clarify that one returns listings and the other returns aggregate statistics.
All tools use a predictable booli_ prefix and snake_case. Most follow a verb_noun pattern (get_listing, search_areas, search_listings, search_sold), with minor deviations like healthcheck and market_stats that are still readable and idiomatic.
Six tools is well-scoped for a read-only property data API: one diagnostic, one area resolver, two search endpoints, one detail endpoint, and one aggregate stats endpoint. Each earns its place without redundancy.
The surface covers the core read-only lifecycle: area lookup, active/sold search, listing detail, and market aggregates. Minor gaps exist (e.g., no bulk listing fetch or comparable-listing helper), but agents can work around them by composing existing tools.
Maintenance
Related MCP Connectors
Redfin listings, sale-comps, and neighborhood market data via natural-language queries.
Pull property listings, prices, and details from real-estate sites as structured JSON.
Booking.com stays by destination and dates, and full property details, as structured JSON.
Zillow for-sale, for-rent and sold listings, and full property details, as structured JSON.
Related MCP Servers
- FlicenseBqualityNot gradedmaintenanceEnables AI assistants to search and analyze Swedish real estate data from Booli.se using natural language queries. Supports property searches with comprehensive filtering options and location discovery through GraphQL API integration.31-
- AlicenseCqualityDmaintenanceEnables access to Idealista API for searching and retrieving property listings across Spain, Portugal, and Italy. Supports various property types including homes, apartments, garages, commercial properties, offices, and land with detailed filtering options.143MIT
- AlicenseNot gradedqualityFmaintenanceProvides real-time access to Zillow real estate data, enabling property search, details, Zestimates, market trends, and mortgage calculations via natural language.9 npm50MIT
- AlicenseAqualityCmaintenanceEnables searching Korean apartment listings, market prices, and recent transactions via Naver Real Estate through natural language.6MIT