woning — MCP server for Dutch house hunting: funda.nl search, rental sources, and Dutch mortgage borrowing capacity
Project description
woning-mcp
Your AI makelaar (real estate agent) for the Dutch housing market. Search funda.nl, compare listings, calculate Dutch mortgages, and estimate buying costs — all from Claude.
Quick start
Prerequisites: Install uv (Python package runner) if you don't have it:
curl -LsSf https://astral.sh/uv/install.sh | sh
Claude Code
From a local checkout (use this while developing — it picks up your edits):
claude mcp add makelaar -- uv run --project /ABSOLUTE/PATH/TO/woning-mcp woning-mcp
Or the released version from PyPI:
claude mcp add makelaar -- uvx woning-mcp
Note: the PyPI release is pinned to an older
pyfunda. funda's search API now rejects those requests with HTTP 400, so install from a local checkout until a new version is published.
Verify it registered:
claude mcp list # should show: makelaar
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"makelaar": {
"command": "FULL_PATH_TO_UV",
"args": [
"run",
"--project",
"/ABSOLUTE/PATH/TO/woning-mcp",
"woning-mcp"
]
}
}
}
Replace FULL_PATH_TO_UV with the output of which uv (e.g. /Users/you/.local/bin/uv). Claude Desktop doesn't inherit your shell PATH, so the full path is required.
Then restart Claude Desktop.
What you can ask
Search for apartments in Amsterdam under €400k with energy label A or better
I earn €80,000/year, my partner earns €50,000. Can we afford this €500,000 house? We're first-time buyers, both 29. Include NHG.
Compare these two listings: https://www.funda.nl/detail/koop/amsterdam/appartement-rapenburgerstraat-103-f/43363178/ and https://www.funda.nl/detail/koop/amsterdam/huis-arendonksingel-54/43363912/
What are the total buying costs for a €350,000 apartment? I'm a starter, age 27.
Show me the price history for this listing: https://www.funda.nl/detail/koop/amsterdam/huis-arendonksingel-54/43363912/
Tools
search_listings
Search funda.nl with filters for location, price, area, property type, energy label, and more. Supports multi-city search and pagination (15 results/page).
get_listing
Full property details by listing ID or funda.nl URL.
get_price_history
Historical asking prices, sale prices, and WOZ values for a listing.
compare_listings
Side-by-side comparison of 2–10 properties: price, area, price/m², bedrooms, year built, energy label, garden.
calculate_dutch_mortgage
Dutch-specific mortgage calculator covering:
- Annuity & linear mortgage types
- NHG (Nationale Hypotheek Garantie) — eligibility, premium, interest discount
- Hypotheekrenteaftrek — mortgage interest deduction (36.97%) minus eigenwoningforfait
- Startersvrijstelling — 0% transfer tax for first-time buyers age 18–34
- NIBUD max mortgage — income-based borrowing limit with partner income and student debt
calculate_total_cost
Itemized breakdown of bijkomende kosten (additional buying costs):
| Cost | Typical amount |
|---|---|
| Overdrachtsbelasting (transfer tax) | 0% / 2% / 10.4% |
| Notariskosten (notary) | €1,500 – €2,500 |
| Taxatiekosten (appraisal) | ~€600 |
| Hypotheekadviseur (mortgage advisor) | ~€2,500 |
| Bankgarantie (bank guarantee) | ~€500 |
| Kadaster (land registry) | ~€150 |
| NHG premium (optional) | 0.6% of mortgage |
| Aankoopmakelaar (optional) | ~1.5% of price |
These costs must come from savings — you can't borrow them.
Disclaimer
This project is not affiliated with, endorsed by, or connected to funda.nl or Funda B.V. Property data is sourced from third-party services and may be incomplete or outdated.
Mortgage calculations and cost estimates are approximations for informational purposes only — not financial advice. Dutch mortgage rules, tax rates, and NHG limits change annually. Always consult a licensed mortgage advisor (hypotheekadviseur) before making financial decisions.
See LICENSE for the full MIT license.
Acknowledgements
Built on pyfunda by 0xMH — the Python library that makes funda.nl data accessible.
Development
git clone https://github.com/victorlane/woning-mcp.git
cd woning-mcp
uv sync
uv run pytest # run tests
uv run ruff check src/ tests/ # lint
uv run mcp dev src/woning/server.py # MCP Inspector
Listing monitor
A standalone script that watches funda.nl on a schedule and appends only listings it has never seen before to a CSV, ready for review by you or an agent. It does not need Claude or the MCP server — it is plain Python and safe to run from cron.
uv run woning-monitor # one pass
uv run woning-monitor --dry-run # show what would be written, touch nothing
Each run:
- searches every enabled stream across its locations,
- compares each listing's ID against the seen-IDs state file,
- fetches one detail page per genuinely new listing (for coordinates, construction year and photo URL, which funda's search results omit),
- appends the new ones to
new_listings.csvand records their IDs.
Configure
Edit monitor_config.toml. Three streams ship pre-configured — rent (≤ €1.300), nieuwbouw (new build ≤ €400k) and koop (resale ≤ €400k) — each with its own locations, price ceiling, filters and enabled toggle.
[monitor]
csv_path = "new_listings.csv"
state_path = "monitor_state.json"
request_delay = 1.0 # seconds between requests
seed_on_first_run = true # see "First run" below
fetch_details = true
detail_fetch_limit = 40
[streams.koop]
enabled = true
locations = ["utrecht", "amersfoort", "woerden", "den-haag", "amstelveen", "nieuwegein", "ijsselstein"]
category = "buy" # "buy" | "rent" | "sold"
construction_type = "resale"
max_price = 400000
sort = "newest"
max_pages = 3 # 15 listings per page, per location
Location slugs must be lowercase and hyphenated: den-haag, not den haag. A wrong slug is not an error — funda returns an empty result set — so any location yielding 0 listings is logged as a WARNING.
Valid filters: category, status, min_price/max_price, min_area/max_area, min_plot/max_plot, min_rooms/max_rooms, min_bedrooms/max_bedrooms, min_construction_year/max_construction_year, object_type, energy_label, construction_type, radius_km, sort. Anything else is rejected at startup.
First run
On a virgin state file the monitor seeds: it records everything currently listed as already seen and writes nothing to the CSV, so you don't get a few hundred pre-existing listings dumped on you. From then on only genuinely new listings are reported. Use --no-seed if you do want the current market written out on the first run.
CSV output
Appended in place, header written once. Columns: first_seen (UTC), stream, listing_id, global_id, address, postcode, city, neighbourhood, price, living_area, plot_area, price_per_m2, rooms, bedrooms, object_type, construction_year, energy_label, status, publication_date, latitude, longitude, project, broker, url, photo_url, and a blank reviewed column for you or an agent to fill in.
Cron
Write your own crontab entry (crontab -e). Every 30 minutes, with output appended to a log:
*/30 * * * * cd /Users/victorbrinkhorst/Personal/Projects/woning-mcp && /Users/victorbrinkhorst/.local/bin/uv run woning-monitor >> monitor.log 2>&1
Both paths must be absolute — cron does not inherit your shell PATH or working directory. Use the output of which uv for the binary. Overlapping runs are prevented by a lock file, so a slow run can never double-write.
CLI
| Flag | Effect |
|---|---|
--config PATH |
Config file (default: monitor_config.toml in the working directory) |
--csv PATH |
Override CSV output path |
--state PATH |
Override state file path |
--stream NAME |
Run only this stream; repeatable. Also runs disabled streams |
--dry-run |
Report findings without writing CSV or state |
--seed / --no-seed |
Override seed_on_first_run |
--verbose |
Debug logging |
Logs go to stderr with timestamps. Exit code is 0 on success, 1 if every stream failed, 2 on a bad config.
Unattended-safety
- One failing stream or location never aborts the run; the rest continues.
- A stream that had any location fail does not get its IDs marked as seen, so nothing is silently lost.
- The state file is written atomically; a corrupt state file is backed up (
monitor_state.json.corrupt.<ts>) rather than wiped. - A lock file guards against overlapping cron invocations, and a lock left by a killed process is detected and reclaimed.
- Pagination is capped per location, so a broken filter can't paginate forever.
Rental sources beyond funda
funda's free-sector rental supply below ~€1300 is thin, and in some towns it is empty — Woerden and Amersfoort both return zero rentals on funda but have listings elsewhere. So rentals are searched across more than one site.
| Source | Access | Reliability |
|---|---|---|
| funda.nl | pyfunda (mobile API) |
Reliable |
| ikwilhuren.nu | server-rendered HTML | Reliable — no bot protection |
| Pararius | pypararius |
Best-effort; Cloudflare returns intermittent HTTP 403 |
Use the search_rentals_all_sources tool to query them together. Pararius is
off by default because it is frequently blocked; enable it with
include_pararius=true and expect it to sometimes return nothing.
Not integrated, and why:
- Huurwoningen.nl — serves a Cloudflare managed challenge site-wide
(
cf-mitigated: challenge), including/sitemap.xml. There is no sanctioned automated path, so it is not scraped. Check it manually. - Kamernet — rooms only.
- WoningNet / sociale huur — login-gated, and irrelevant above the ~€48.6k/yr income ceiling.
All adapters are read-only. They never contact an agent, request a viewing,
or submit an application — which is also what these sites' robots.txt files
disallow (/contact/, /plan-een-bezichtiging, /request-details/).
Monitoring ikwilhuren
Set source = "ikwilhuren" on a stream in monitor_config.toml. Because
ikwilhuren ignores query-string filters, such a stream sweeps the national feed
(~40 pages) and filters locally, so it accepts min_price/max_price only.
Keep min_price set — the feed also carries parking spaces and garages, some as
low as €97/mnd.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file woning_mcp-0.1.1.tar.gz.
File metadata
- Download URL: woning_mcp-0.1.1.tar.gz
- Upload date:
- Size: 145.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
806d4a1a07b6bdb3755075c16f92bc0fdb5715bd81bc68b68531826eedb924f0
|
|
| MD5 |
132e2ebe03511e6f3a57f13f0840dc07
|
|
| BLAKE2b-256 |
5e30fa548aae7f1ef738ba9ba527e66f00b07738ee8437420a643e386251bc14
|
Provenance
The following attestation bundles were made for woning_mcp-0.1.1.tar.gz:
Publisher:
publish.yml on victorlane/woning-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
woning_mcp-0.1.1.tar.gz -
Subject digest:
806d4a1a07b6bdb3755075c16f92bc0fdb5715bd81bc68b68531826eedb924f0 - Sigstore transparency entry: 2256515267
- Sigstore integration time:
-
Permalink:
victorlane/woning-mcp@7c9d02d4fb35d831d2b90e426077369903f9abfa -
Branch / Tag:
refs/tags/0.1.0 - Owner: https://github.com/victorlane
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7c9d02d4fb35d831d2b90e426077369903f9abfa -
Trigger Event:
release
-
Statement type:
File details
Details for the file woning_mcp-0.1.1-py3-none-any.whl.
File metadata
- Download URL: woning_mcp-0.1.1-py3-none-any.whl
- Upload date:
- Size: 53.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e5d75923c818311b72b33d99693bc3857d5e0a41a2b189458471df510acc7c70
|
|
| MD5 |
c58f9af17e8fd7d99d0b302ddd8fac69
|
|
| BLAKE2b-256 |
f22d2bdf315ae31ebab82c72655a3a0d696b19fdad67ffc2f951131e5f3e6f3b
|
Provenance
The following attestation bundles were made for woning_mcp-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on victorlane/woning-mcp
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
woning_mcp-0.1.1-py3-none-any.whl -
Subject digest:
e5d75923c818311b72b33d99693bc3857d5e0a41a2b189458471df510acc7c70 - Sigstore transparency entry: 2256515272
- Sigstore integration time:
-
Permalink:
victorlane/woning-mcp@7c9d02d4fb35d831d2b90e426077369903f9abfa -
Branch / Tag:
refs/tags/0.1.0 - Owner: https://github.com/victorlane
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7c9d02d4fb35d831d2b90e426077369903f9abfa -
Trigger Event:
release
-
Statement type: