An interactive MCP App that displays Paris real estate prices per arrondissement, directly inside Claude.
Built with the MCP Apps SDK (@modelcontextprotocol/ext-apps).
Ask Claude about Paris real estate prices and get an interactive widget with:
- Fullscreen mode (v0.8): split layout with map (60%) and info panel (40%), interactive search
- Interactive map (Leaflet + OpenStreetMap) highlighting the arrondissement
- Choropleth sections (v0.6): cadastral sections colored by median price, clickable for details
- Price stats: average price/m², median price/m², number of sales
- Apartments / Houses toggle
- Comparison mode: compare two arrondissements side-by-side with a bar chart
- Address search (v0.5): search by address and get stats for the cadastral section
- UI-driven search (v0.8): search addresses and browse arrondissements directly from the widget
Data source: DVF (Demandes de Valeurs Foncieres) from data.gouv.fr.
Claude MCP Server UI (iframe)
│ │ │
│── "prix Paris 11" ──────>│ │
│ │── get-dvf-stats ───────>│
│ │ structuredContent │── Map + Stats
│ │ │
│── "compare 6e vs 11e" ──>│ │
│ │── compare-dvf-stats ───>│
│ │ mode: "compare" │── Map + Bar chart
│ │ │
│── "prix rue Roquette" ──>│ │
│ │── search-dvf-address ──>│
│ │ mode: "address" │── Map + Marker + Compare
- Claude calls
get-dvf-stats,compare-dvf-statsorsearch-dvf-address - The tool returns data + a reference to
ui://dvf/mcp-app.html - Claude fetches the resource and displays it in a sandboxed iframe
- The UI receives data via
app.ontoolresult
| Tool | Input | Output |
|---|---|---|
get-dvf-stats |
arrondissement (1-20) |
Price stats for one arrondissement |
compare-dvf-stats |
arrondissement_1, arrondissement_2 (1-20) |
Side-by-side comparison with bar chart |
search-dvf-address |
adresse (string) |
Stats for cadastral section + comparison with arrondissement |
| Component | Technology |
|---|---|
| MCP Apps SDK | @modelcontextprotocol/ext-apps |
| MCP Server | @modelcontextprotocol/sdk |
| Build | Vite + TypeScript |
| UI | Vanilla JS |
| Map | Leaflet + OpenStreetMap (no API key needed) |
| Charts | Pure SVG (no dependencies) |
| Transport | stdio (Claude Desktop) or Streamable HTTP |
| API | Usage |
|---|---|
| MCP data.gouv | Real-time DVF stats |
| API Géoplateforme | Address geocoding |
| cadastre.data.gouv.fr | Cadastral section geometries (GeoJSON) |
npm install
npm run buildAdd to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"dvf-paris": {
"command": "bash",
"args": ["-c", "cd /path/to/dvf-mcp-app && npx tsx main.ts --stdio"]
}
}
}Then restart Claude Desktop.
# Watch mode (UI hot reload + server)
npm run dev
# Build only
npm run build
# Run server (Streamable HTTP on port 3001)
npm run servedvf-mcp-app/
├── server.ts # MCP Server + Tools + Resource
├── main.ts # Entry point (stdio + HTTP)
├── mcp-app.html # UI shell (HTML)
├── src/
│ ├── mcp-app.ts # UI logic (map, chart, host communication)
│ ├── mcp-app.css # Widget styles
│ ├── api/ # External API clients
│ │ ├── cadastre.ts # Cadastre GeoJSON client
│ │ ├── data-gouv.ts # MCP data.gouv client
│ │ └── geoplateforme.ts # Geocoding client
│ └── data/
│ ├── dvf-paris.json # Pre-computed stats (fallback)
│ └── arrondissements.geojson.json # GeoJSON boundaries
├── package.json
├── tsconfig.json
└── vite.config.ts
- v0.1 — Basic stats widget
- v0.2 — Interactive map with Leaflet
- v0.3 — Comparison mode (2 arrondissements)
- v0.4 — Real-time data via data.gouv.fr API (with JSON fallback)
- v0.5 — Address search with cadastral section stats
- v0.6 — Choropleth cadastral sections (clickable, color-coded by price)
- v0.7 — Link to recent transactions (optional)
- v0.8 — Fullscreen mode with interactive search (split layout, callServerTool)
MIT