CityDPC-MCP
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., "@CityDPC-MCP @CityDPC-MCP list the available CityJSON files"
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.
CityDPC-MCP
A Model Context Protocol (MCP) server that lets LLM agents such as Claude Code or Codex inspect, analyse and edit CityJSON and CityGML building models. It is built on CityDPC, the Python library for 3D city models maintained by RWTH-E3D.
Companion repository to the paper CityDPC-MCP: Enabling 3D city model processing via Model Context Protocol. It contains the server, the benchmark used in the paper and the per-run results.
Installation
You need uv. It fetches Python 3.12+ and all dependencies, CityDPC included.
uv tool install git+https://github.com/HamerCode/CityDPC-MCPThis puts a citydpc-mcp command on your PATH. The server communicates over stdio and works with the *.json / *.gml files in one dataset directory, which you set with --dataset-dir (default: the current directory).
Note:
save_datasetoverwrites files in that directory, so work on copies of important data.
Claude Code
claude mcp add citydpc -- citydpc-mcp --dataset-dir /absolute/path/to/your/data
claude mcp list # should show: citydpc: citydpc-mcp ... - ✔ ConnectedAdd --scope user to make the server available in all projects. If you leave out --dataset-dir, the server uses the project directory Claude Code was started in.
Codex
codex mcp add citydpc -- citydpc-mcp --dataset-dir /absolute/path/to/your/dataOr add it to ~/.codex/config.toml yourself:
[mcp_servers.citydpc]
command = "citydpc-mcp"
args = ["--dataset-dir", "/absolute/path/to/your/data"]Other MCP clients (Claude Desktop, Cursor, …)
{
"mcpServers": {
"citydpc": {
"command": "citydpc-mcp",
"args": ["--dataset-dir", "/absolute/path/to/your/data"]
}
}
}Desktop apps often don't search ~/.local/bin. If yours can't find the command, use the absolute path that which citydpc-mcp prints. To run the server without installing it, use uvx --from git+https://github.com/HamerCode/CityDPC-MCP citydpc-mcp as the command. The first start takes a while because uv installs the dependencies then.
Try it
Point the server at the sample dataset (evaluation/data/evaluation.city.json, 36 LoD2 buildings) and ask something like:
Load evaluation.city.json. Which building has the highest measuredHeight, and how many party walls does the dataset have?
Related MCP server: ifc-geometry-mcp
Tools
Group | Tools |
Datasets |
|
Analysis |
|
Editing |
|
Versioning |
|
All edits happen in memory until save_dataset is called. The paper evaluated the other 18 tools; create_dataset was added afterwards for quick testing. Their names and (German) descriptions are unchanged, including the historical spelling get_buiding_Id_list. See src/citydpc_mcp/server.py for signatures.
Evaluation
The benchmark asks an LLM to solve five tasks on the 36-building sample, in three setups:
MCP: the model uses this server.
No Tools: the dataset is pasted into the prompt.
Sandbox: the dataset is in the prompt and the model also has a Python code sandbox.
Task | Category | Checked against |
| query | all 36 building IDs and the total |
| query | the ID and height of the tallest building |
| analytic | the number of party walls (CityDPC: 10) |
| state change | the saved file: measuredHeight +2 m |
| state change | the saved file: new building, attributes and valid geometry, importable by CityDPC |
The paper tested GPT-OSS-120B, Mistral Small 4 and GPT-5.4 Mini, all with high reasoning effort. Each combination of model, setup and task ran 100 times, for 4,500 runs in total.
Results
Averages across the five tasks (full tables):
Setup | GPT-OSS success | Mistral success | GPT-5.4 Mini success | Avg. tokens (GPT-OSS / Mistral / GPT-5.4 Mini) |
MCP | 93% | 80% | 97% | 14,865 / 25,755 / 15,429 |
No Tools | 40% | 34% | 61% | 62,188 / 80,633 / 71,879 |
Sandbox | 34% | 30% | 64% | 236,803 / 83,517 / 117,703 |
evaluation/results/paper_runs.csv has one row per run: tokens, tool calls, the sequence of tool calls, duration and success. The paper's Tables 2 and 3 are computed from this file, and all 300 numbers match the paper:
git clone https://github.com/HamerCode/CityDPC-MCP && cd CityDPC-MCP
uv sync --extra eval
uv run python evaluation/analyze.py tablesRunning the benchmark yourself
cp evaluation/.env.example evaluation/.env # add your endpoint and API key
uv run python evaluation/run.py --setup mcp,no_tools --model gpt-oss-120b \
--api completions --reasoning-effort high --runs 100
uv run python evaluation/analyze.py extract evaluation/logs/<run> -o my_runs.csv
uv run python evaluation/analyze.py tables my_runs.csvThe runner works with any OpenAI-compatible endpoint, through either the Responses or the Chat Completions API. The paper ran GPT-OSS and Mistral through KI:Connect NRW Chat Completions, and GPT-5.4 Mini through the Responses API of ChatGPT's Codex backend. Prompts, task specifications, validation and success criteria are the ones used for the paper. Current models will not reproduce the historical runs exactly.
The code_interpreter (Sandbox) setup also needs a Docker code-sandbox MCP server (CODE_SANDBOX_MCP_BINARY, e.g. code-sandbox-mcp). Leave that setup out if you don't have one.
The following check needs no API key. It exercises the server over stdio, solves all five tasks with the reference tool calls and confirms that the evaluation's validation accepts each solution:
uv run python tests/smoke_test.pyRepository layout
src/citydpc_mcp/server.py MCP server (19 tools)
evaluation/
cases.py, schemas.py task prompts and answer schemas
run.py, client.py benchmark runner and OpenAI-compatible tool-calling client
validation.py checks answers and saved datasets
analyze.py logs -> per-run CSV -> result tables
ground_truth.py regenerates data/ground_truth.json with CityDPC
data/ sample dataset and ground truth
results/ per-run results and tables of the paper
tests/smoke_test.py offline end-to-end checkCredits and license
CityDPC is developed by Maxim Shamovich and Simon Raming at RWTH-E3D under the Apache 2.0 license. This server installs CityDPC v0.1.22 pinned to commit 4ede6d7. This repository is licensed under Apache 2.0 as well (LICENSE).
If you use this work, please cite the paper CityDPC-MCP: Enabling 3D city model processing via Model Context Protocol and link a specific commit of this repository.
Available Tools
19 toolsanalyse_datasetB
Analysiert das Dataset und gibt detaillierte Informationen über die Gebäude zurück.
Returns: dict: Analyse-Ergebnisse mit Informationen über GML-Version, CRS, LoD, Anzahl Gebäude etc.
Inputs: no named parameters. Returns: object
calculate_roof_volume_by_idB
Berechnet das Dachvolumen für ein spezifisches Gebäude.
Args: building_id: Die gml:id des Gebäudes
Returns: float: Das berechnete Dachvolumen in Kubikmetern
Inputs: building_id (required). Returns: Generic wrapper for non-object return types.
create_buildingA
Erstellt ein neues Gebäude mit wählbarem Detaillierungsgrad (LoD 0/1/2).
LoD: 0=2D-Grundfläche | 1=3D-Quader | 2=3D mit Dachgeometrie (Standard)
Das Gebäude wird direkt zum aktiven Dataset hinzugefügt (Single Source of Truth). Verwende take_snapshot() vor dem Erstellen und save_dataset() zum Speichern.
Args: id: Eindeutige Gebäude-ID groundsCoordinates: 2D-Grundriss als Liste von Koordinaten [[x1,y1], [x2,y2], ..., [x1,y1]] groundSurfaceHeight: Bodenhöhe in Metern geometryHeight: Gebäudehöhe in Metern (erforderlich für LoD 1+2) roofType: Dachtyp (für LoD 2): "1000"=Flach, "1010"=Pult, "1020"=Pult versetzt, "1030"=Sattel, "1040"=Walm, "1070"=Zelt roofHeight: Dachhöhe in Metern (für LoD 2, außer "1000") roofOrientation: Dachausrichtung als Koordinaten-Index (für "1010","1020","1030") lod: Detaillierungsgrad 0-2 (Standard: 2) isRoofEdge: Für LoD 0, ob Koordinaten Dachkante sind (Standard: False)
Returns: dict: Das erstellte Gebäude mit allen Attributen und Bounding Box
Inputs: id (required), groundsCoordinates (required), groundSurfaceHeight (required), lod, +5 more. Returns: object
create_datasetA
Erstellt ein neues, leeres Dataset als GML- oder JSON-Datei und lädt es.
Praktisch zum Testen: Danach können direkt mit create_building() Gebäude hinzugefügt und mit save_dataset() gespeichert werden. Bestehende Dateien werden nicht überschrieben.
Args: filename: Name der neuen Datei, Endung '.json' (CityJSON) oder '.gml' (CityGML), z.B. 'test.city.json' title: Optionaler Titel des Datasets epsg: EPSG-Code des Koordinatenreferenzsystems (Standard: 25832 = ETRS89 / UTM 32N)
Returns: str: Erfolgsmeldung oder Fehlermeldung
Inputs: filename (required), epsg, title. Returns: Generic wrapper for non-object return types.
enrich_buildingA
Reichert ein Gebäude mit semantischen Informationen an.
Die Änderungen werden direkt im Dataset vorgenommen (Single Source of Truth). Verwende take_snapshot() vor wichtigen Änderungen und save_dataset() zum Speichern.
Args: building_id: Die gml:id des Gebäudes measured_height: Gemessene Gebäudehöhe in Metern roof_type: Dachtyp-Code (z.B. "1000"=Flach, "1030"=Sattel, "1040"=Walm, "3100"=andere) roof_height: Dachhöhe in Metern function: Gebäudefunktion-Code nach CityGML (z.B. "31001_1010"=Wohngebäude, "31001_2000"=Gewerbe) usage: Nutzung des Gebäudes (z.B. "residential", "commercial", "industrial") year_of_construction: Baujahr (z.B. 2020) storeys_above_ground: Anzahl der Stockwerke über dem Boden storeys_below_ground: Anzahl der Stockwerke unter dem Boden (Keller) creation_date: Erstellungsdatum des Gebäudes (ISO-Format: "2020-01-15") Returns: dict: Das angereicherte Gebäude mit allen Attributen
Inputs: building_id (required), usage, function, roof_type, +6 more. Returns: object
filter_datasetA
Filtert das Dataset nach Adressen oder Koordinaten und setzt das gefilterte Dataset als aktives Dataset.
Das gefilterte Dataset wird zum neuen aktiven Dataset. Alle nachfolgenden Tool-Aufrufe arbeiten dann nur mit den gefilterten Gebäuden. Mit load_dataset() kann jederzeit das Original neu geladen werden.
WICHTIG: Nach dem Filtern können keine Änderungen mehr gespeichert werden, um das Original zu schützen. Die History wird geleert!
Args: addressRestriciton: Dictionary mit Adressbeschränkungen (z.B. {"thoroughfareName": "Stakenholt"}) borderCoordinates: Liste von Koordinaten für einen rechteckigen Bereich (z.B. [[x1,y1], [x2,y2], [x3,y3], [x4,y4]])
Returns: dict: Informationen zum gefilterten Dataset (building_count, minimum, maximum, filepath, title)
Inputs: borderCoordinates, addressRestriciton. Returns: object
get_all_buildingsB
Gibt Informationen zu allen Gebäuden im Dataset zurück.
Returns: list[dict]: Liste mit Dictionaries aller Gebäude und deren Attributen
Inputs: no named parameters. Returns: Generic wrapper for non-object return types.
get_buiding_Id_listB
Gibt eine Liste aller Gebäude-IDs im Dataset zurück.
Returns: list[str]: Liste aller gml:id Werte der Gebäude
Inputs: no named parameters. Returns: Generic wrapper for non-object return types.
get_building_by_idB
Gibt alle Informationen zu einem spezifischen Gebäude zurück.
Args: building_id: Die gml:id des Gebäudes
Returns: dict: Dictionary mit allen Gebäude-Attributen
Inputs: building_id (required). Returns: object
get_dataset_historyB
Zeigt die Historie aller Snapshots und Speicherungen.
Returns: dict: Übersicht über Snapshots, Speicherungen und aktuellen Zustand
Inputs: no named parameters. Returns: object
get_party_wallsB
Findet alle angrenzenden Wände (Party Walls) zwischen Gebäuden im Dataset.
Returns: list: Liste aller erkannten Party Walls als [id of b0, id of w0, id of b1, id of w1, area, collision coordinates]
Inputs: no named parameters. Returns: Generic wrapper for non-object return types.
list_datasetsB
Listet alle verfügbaren GML- oder JSON-Datasets im Data-Verzeichnis auf.
Returns: list[str]: Liste aller verfügbaren Dataset-Dateinamen
Inputs: no named parameters. Returns: Generic wrapper for non-object return types.
load_datasetB
Lädt ein Dataset aus einer GML- oder JSON-Datei.
Erstellt automatisch einen Snapshot des ursprünglichen Zustands.
Args: filename: Der Name der GML- oder JSON-Datei (z.B. 'EssenExample.gml')
Returns: str: Erfolgsmeldung oder Fehlermeldung
Inputs: filename (required). Returns: Generic wrapper for non-object return types.
number_of_buildingsA
Gibt die Anzahl der Gebäude im aktuellen Dataset zurück.
Returns: int: Anzahl der Gebäude
Inputs: no named parameters. Returns: Generic wrapper for non-object return types.
remove_building_attributesA
Entfernt Attribute eines Gebäudes (setzt sie auf None).
Args: building_id: Die gml:id des Gebäudes attributes: Liste von Attributnamen (gleiche Namen wie in enrich_building, z.B. "measuredHeight", "roofType"). Unbekannte Namen werden ignoriert.
Returns: dict: Das aktualisierte Gebäude mit allen Attributen
Inputs: attributes (required), building_id (required). Returns: object
remove_building_from_datasetA
Entfernt ein Gebäude aus dem Dataset.
Die Änderung wird direkt im Dataset vorgenommen (Single Source of Truth). Verwende take_snapshot() vor wichtigen Änderungen und save_dataset() zum Speichern.
Args: building_id: Die gml:id des zu entfernenden Gebäudes
Returns: dict: Bestätigungsmeldung mit Anzahl verbleibender Gebäude
Inputs: building_id (required). Returns: object
rollback_to_snapshotA
Stellt einen früheren Dataset-Zustand wieder her.
Wie Git-Reset: Kehrt zu einem gespeicherten Zustand zurück. Alle Änderungen nach diesem Snapshot gehen verloren!
Args: snapshot_index: Index des Snapshots (von get_dataset_history() erhalten)
Returns: dict: Informationen über den Rollback
Inputs: snapshot_index (required). Returns: object
save_datasetB
Speichert das Dataset dauerhaft in die GML/JSON-Datei.
Übernimmt alle aktuellen Änderungen permanent in die Datei.
Args: description: Beschreibung des Speicherns target_format: Optionales Zielformat ("gml" oder "json"). Wenn None, wird das ursprüngliche Format beibehalten. Returns: dict: Informationen über den Speichervorgang
Inputs: description, target_format. Returns: object
take_snapshotA
Erstellt einen Snapshot des aktuellen Dataset-Zustands.
Wie ein Git-Commit: Speichert den aktuellen Zustand für spätere Rollbacks.
Args: description: Beschreibung des Snapshots
Returns: dict: Informationen über den erstellten Snapshot
Inputs: description. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 19 tools
Most tools have distinct resource/action boundaries: dataset lifecycle, building CRUD, semantic enrichment, spatial analysis, and snapshot history are clearly separated. However, number_of_buildings and analyse_dataset overlap somewhat, and get_all_buildings/get_buiding_Id_list/get_building_by_id require reading descriptions to distinguish intent.
Tool names mostly follow a snake_case, verb-oriented pattern (list_datasets, load_dataset, create_building, remove_building_attributes). Minor deviations include the noun-only number_of_buildings and the typo/camelCase segment in get_buiding_Id_list.
At 19 tools, the set is borderline heavy for the domain. Most tools earn their place, but there is some redundancy around dataset/building summaries that could be consolidated.
The surface covers dataset loading, creation, filtering, saving, history, and building CRUD, enrichment, removal, and analysis. Minor gaps remain, such as no explicit delete/update dataset operation and no general building geometry update tool.
Related MCP Connectors
Create, inspect, validate, and save editable 3D building scenes with Pascal's MCP server.
MCP server for progressive tool usage at any scale (see https://klavis.ai)
Official MCP server for Softhills 3D/2D Visualization SDKs, API documentation, and code examples.
MCP server for the Seline Analytics API
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceBrowser-based geometry processing server that enables AI agents to create geometry, run analysis and operators, inspect results, and take screenshots via MCP.23 npm6MIT
- AlicenseAqualityDmaintenanceMCP server for IFC geometric audit, providing tools to detect space clashes, extract space inventories, compute surface losses, check boundaries, and verify opening correspondences in BIM models.6Apache 2.0
- AlicenseCqualityDmaintenanceMCP server for COBie Excel validation, updates, and PDF extraction.631MIT
- AlicenseBqualityBmaintenanceMCP server for reading, viewing, and editing IFC models using IfcOpenShell and Bonsai. Enables querying model data, viewing via Blender's viewport, and making IFC-semantic edits without raw mesh operations.28MIT