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.
Metadata
Release files for woning-mcp 0.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| woning_mcp-0.1.1.tar.gz | 145.8 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| woning_mcp-0.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 198.9 kB
Release files / woning_mcp-0.1.1.tar.gz
| Download URL | woning_mcp-0.1.1.tar.gz |
|---|---|
| Size | 145.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
806d4a1a07b6bdb3755075c16f92bc0fdb5715bd81bc68b68531826eedb924f0
|
|
BLAKE2b-256 checksum How to use checksums |
5e30fa548aae7f1ef738ba9ba527e66f00b07738ee8437420a643e386251bc14
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 26, 2026.
Transparency logRelease files / woning_mcp-0.1.1-py3-none-any.whl
| Download URL | woning_mcp-0.1.1-py3-none-any.whl |
|---|---|
| Size | 53.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e5d75923c818311b72b33d99693bc3857d5e0a41a2b189458471df510acc7c70
|
|
BLAKE2b-256 checksum How to use checksums |
f22d2bdf315ae31ebab82c72655a3a0d696b19fdad67ffc2f951131e5f3e6f3b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 26, 2026.
Transparency logSecurity for woning-mcp 0.1.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for woning_mcp-0.1.1.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 26, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.