DVF Paris — MCP App
An interactive MCP App that displays Paris real estate prices per arrondissement, directly inside Claude.
Built with the MCP Apps SDK (@modelcontextprotocol/ext-apps).
What it does
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.
Architecture
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
MCP Tools
| 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 |
Stack
| 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 |
External APIs
| API | Usage | |-----|-------| | MCP data.gouv | Real-time DVF stats | | API Géoplateforme | Address geocoding | | cadastre.data.gouv.fr | Cadastral section geometries (GeoJSON) |
Setup
npm install
npm run build
Claude Desktop configuration
Add 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.
Development
# Watch mode (UI hot reload + server)
npm run dev
# Build only
npm run build
# Run server (Streamable HTTP on port 3001)
npm run serve
Project structure
dvf-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
Roadmap
- [x] v0.1 — Basic stats widget
- [x] v0.2 — Interactive map with Leaflet
- [x] v0.3 — Comparison mode (2 arrondissements)
- [x] v0.4 — Real-time data via data.gouv.fr API (with JSON fallback)
- [x] v0.5 — Address search with cadastral section stats
- [x] v0.6 — Choropleth cadastral sections (clickable, color-coded by price)
- [ ] v0.7 — Link to recent transactions (optional)
- [x] v0.8 — Fullscreen mode with interactive search (split layout, callServerTool)
License
MIT











