MCP tools for inspecting, validating, transforming, and querying CityJSON models.
331
CityJSON MCP provides a web chat application and an MCP server for CityJSON files. It supports inspection, queries, validation, transformations, export, CityGML conversion, and cjdb/PostGIS operations.
The Docker image includes:
cjio for CityJSON transformations and export.cjval for syntax, schema, and structural validation.val3dity for 3D geometry validation.citygml-tools for CityGML and CityJSON conversion.cjdb for PostgreSQL/PostGIS import and export.Transformations do not overwrite the source dataset. Each transformation returns a new dataset_id.
Datum is the main application in this repository. It accepts CityJSON attachments in the browser, imports them through MCP, and lets a configured model call the CityJSON tools.
npm install
.envCopy the example file:
cp .env.example .env
Set these values before starting Datum:
MODEL_PROVIDER=openai
MODEL_NAME=gemini-3.7-flash
MODEL_API_KEY=replace-with-your-api-key
MODEL_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai
MODEL_TEMPERATURE=0.1
The model settings mean:
| Variable | Required | Description |
|---|---|---|
MODEL_PROVIDER | yes | API request format. Use openai for OpenAI-compatible Chat Completions or anthropic for Anthropic Messages. This value describes the API format, not the model company. |
MODEL_NAME | yes | Exact model identifier sent to the provider, for example gemini-3.7-flash. The model must support tool calls. |
MODEL_API_KEY | yes | API credential issued by the model provider. Do not commit .env. |
MODEL_BASE_URL | yes | Base URL for the provider API. |
MODEL_TEMPERATURE | no | Sampling temperature. Datum defaults to 0.1. |
The .env model is the default model in Datum. Users can add other models from the model menu. Models added through the interface remain in server memory for up to eight hours. Their API keys are not returned to the browser or passed to MCP tools.
Gemini 3.7 Flash is the recommended model for initial testing. Google lists free input and output tokens for this model on the Gemini API free tier. The free tier has rate limits, availability depends on region, and Google states that free-tier content may be used to improve its products. Do not send confidential datasets through a free-tier account without reviewing the provider's data terms.
Create and configure a Gemini API key:
Open the Google AI Studio API Keys page. Google's API key guide explains project and key management.
Sign in and accept the Gemini API terms if prompted.
Select Create API key. New keys created in AI Studio are restricted to the Gemini API.
Copy the key.
Create .env from .env.example and set:
MODEL_PROVIDER=openai
MODEL_NAME=gemini-3.7-flash
MODEL_API_KEY=paste-your-gemini-api-key-here
MODEL_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai
MODEL_TEMPERATURE=0.1
Run npm run chat, open http://127.0.0.1:3000, import a CityJSON file, and select one of the suggested questions.
The base URL above is Google's documented OpenAI compatibility endpoint, which is why MODEL_PROVIDER remains openai.
Check the Gemini API pricing page and rate-limit documentation because free-tier quotas can change.
Datum can use any model that supports tool calls through one of its two API formats. Examples:
| Service | MODEL_PROVIDER | Example model | MODEL_BASE_URL |
|---|---|---|---|
| Google Gemini | openai | gemini-3.7-flash | https://generativelanguage.googleapis.com/v1beta/openai |
| DeepSeek | openai | deepseek-v4-pro | https://api.deepseek.com |
| OpenAI GPT | openai | a current GPT model with Chat Completions tool calling | https://api.openai.com/v1 |
| Anthropic Claude | anthropic | a current Claude model with tool use | https://api.anthropic.com |
Model names and availability change. Confirm the exact model identifier in the provider documentation:
npm run chat
Open http://127.0.0.1:3000.
The command starts the container in detached mode and returns to the terminal. Use these commands to view logs or stop the application:
npm run chat:logs
npm run chat:stop
npm run chat uses this image-selection sequence:
yarroudh/cityjson-mcp:latest exists locally.The application binds to 127.0.0.1:3000. Docker volumes store imported files and derived datasets.
Build the image before npm run chat when you want to run local source changes:
npm run docker:build
npm run docker:doctor
npm run chat
The val3dity and cjval build stages take the most time. They can be cached separately:
npm run docker:cache:val3dity
npm run docker:cache:cjval
npm run docker:build
Set CITYJSON_MCP_IMAGE to use another image name:
CITYJSON_MCP_IMAGE=example/cityjson-mcp:tag npm run chat
The MCP server can run separately in Claude Desktop, Claude Code, Cursor, VS Code, or another client that supports local stdio MCP servers. A separate model API key is not required by the MCP server because the client supplies the model.
docker pull yarroudh/cityjson-mcp:latest
docker run --rm --entrypoint node yarroudh/cityjson-mcp:latest /app/scripts/doctor.mjs
The doctor command should report OK for cjio, cjval, val3dity, citygml-tools, and cjdb.
MCP tool calls do not contain ordinary chat attachments. Mount a host folder as /input for files that must be available to the MCP server. Replace /absolute/path/to/cityjson-files with an existing absolute path:
{
"mcpServers": {
"cityjson": {
"command": "docker",
"args": [
"run",
"--rm",
"-i",
"--mount",
"type=bind,source=/absolute/path/to/cityjson-files,target=/input,readonly",
"--env",
"CITYJSON_MCP_ALLOWED_ROOTS=/input:/data",
"--env",
"CITYJSON_MCP_INPUT=/input",
"yarroudh/cityjson-mcp:latest"
]
}
}
}
Place model.city.json in the mounted folder, then ask the client:
Import
model.city.jsonand summarize it.
The model should call cityjson_import with the filename. It should not send the full file through cityjson_import_text.
Paths created by a chat client, such as /mnt/user-data/..., do not automatically exist in the MCP container. Use the mounted inbox or a client that implements attachment handling, such as Datum.
The template is config/claude-desktop.json. Add the inbox mount from the previous example when working with files.
Claude Desktop configuration locations:
~/Library/Application Support/Claude/claude_desktop_config.json%APPDATA%\Claude\claude_desktop_config.jsonMerge the mcpServers.cityjson entry into the existing file. Fully quit and reopen Claude Desktop. In a chat, enable the cityjson connector and allow its tools.
Copy the template into the project where Claude Code runs:
cp config/claude-code.json .mcp.json
Add the inbox mount to .mcp.json when required, then restart or reconnect the MCP server.
The template is config/cursor-mcp.json.
Use one of these locations:
.cursor/mcp.json~/.cursor/mcp.jsonRestart the MCP server after changing the file.
The template is config/vscode-mcp.json. Copy its servers.cityjson entry into .vscode/mcp.json, then start or restart the server from the MCP server-management commands in VS Code.
Use this command for any client that supports a local stdio MCP server:
docker run --rm -i yarroudh/cityjson-mcp:latest
Add the /input mount shown above when the server must read local files.
cityjson_import automatically./input directory.cityjson_import copies a source file into the managed workspace and returns a dataset_id.cityjson_open opens an authorized server-visible path.cityjson_import_text accepts small CityJSON documents supplied as text.cityjson_download returns a source or derived dataset.The server exposes 37 tools.
| Tool | Backend | Purpose |
|---|---|---|
cityjson_backend_status | native | Report backend availability and path settings. |
cityjson_list_imports | native | List JSON files in the input directory. |
cityjson_import | native | Import an input file and return a dataset_id. |
cityjson_import_text | native | Import a small CityJSON document supplied as text. |
cityjson_open | native | Open a CityJSON file from an authorized path. |
cityjson_download | native | Return a source or derived CityJSON file. |
cityjson_save | native | Copy a dataset to an authorized destination. |
cityjson_info | native | Return version, counts, LoDs, attributes, metadata, transform, and extensions. |
cityjson_list_objects | native | List CityObjects with pagination and type filters. |
cityjson_get_object | native | Return one CityObject and its computed bounding box. |
cityjson_query | native | Query by IDs, types, bounding box, and attribute predicates. |
| Tool | Backend | Purpose |
|---|---|---|
cityjson_validate_schema | cjval | Validate JSON syntax, schemas, extensions, and structural consistency. |
cityjson_validate_geometry | val3dity | Validate supported 3D geometry primitives. |
cityjson_validate | cjval + val3dity | Run both validators and return one result. |
Each tool in this table returns a new dataset handle.
| Tool | Backend | Purpose |
|---|---|---|
cityjson_subset | cjio | Select or exclude objects by IDs, type, bounding box, radius, or random count. |
cityjson_filter_lod | cjio | Keep one level of detail. |
cityjson_reproject | cjio | Transform coordinates to a target EPSG CRS. |
cityjson_assign_crs | cjio | Assign an EPSG CRS without changing coordinates. |
cityjson_translate | cjio | Translate coordinates. |
cityjson_clean_vertices | cjio | Remove duplicate and unused vertices. |
cityjson_triangulate | cjio | Triangulate surfaces. |
cityjson_merge | cjio | Merge two or more datasets. |
cityjson_attribute_rename | cjio | Rename an attribute. |
cityjson_attribute_remove | cjio | Remove an attribute. |
cityjson_remove_textures | cjio | Remove textures. |
cityjson_remove_materials | cjio | Remove materials. |
cityjson_upgrade | cjio | Upgrade an older supported CityJSON version. |
| Tool | Backend | Purpose |
|---|---|---|
cityjson_export | cjio | Export to JSONL, OBJ, STL, GLB, or B3DM. |
citygml_to_cityjson | citygml-tools | Convert CityGML to CityJSON or CityJSONSeq. |
cityjson_to_citygml | citygml-tools | Convert CityJSON to CityGML. |
cityjson_db_import | cjio + cjdb | Import a dataset into PostgreSQL/PostGIS. |
cityjson_db_export | cjdb + cjio | Export all or selected objects from cjdb. |
| Tool | Source | Purpose |
|---|---|---|
cityjson_spec_outline | bundled index | Return the CityJSON specification outline and schema names. |
cityjson_spec_read | cityjson.org | Read part of the CityJSON specification. |
cityjson_schema_read | cityjson.org | Read a CityJSON JSON Schema. |
cityjson_extensions_registry | CityJSON registry | List or search registered extensions. |
cityjson_extension_schema | CityJSON registry | Read a registered extension schema. |
Import
tile.city.json. Report the version, CRS, object counts by type, LoDs, attributes, and extensions. Do not modify the dataset.
Expected tools: cityjson_import, cityjson_info.
Import
tile.city.json. Run structural and geometric validation. Separate cjval findings from val3dity findings and list affected object IDs.
Expected tools: cityjson_import, cityjson_validate.
Import
city.city.json. Keep Building and BuildingPart objects inside bbox[85000, 446000, 86000, 447000], keep LoD 2.2, reproject to EPSG:28992, validate the result, and give me the resulting file.
Expected tools: cityjson_import, cityjson_subset, cityjson_filter_lod, cityjson_reproject, cityjson_validate, cityjson_download.
Import
tile.city.json. Remove duplicate and unused vertices, validate the derived dataset, and return it astile-clean.city.json. Do not overwrite the source.
Expected tools: cityjson_import, cityjson_clean_vertices, cityjson_validate, cityjson_download.
| Variable | Default | Purpose |
|---|---|---|
MODEL_PROVIDER | anthropic | Model API format: anthropic or openai. |
MODEL_NAME | none | Default Datum model identifier. |
MODEL_API_KEY | none | Default Datum model API key. |
MODEL_BASE_URL | provider default | Model API base URL. |
MODEL_MAX_OUTPUT_TOKENS | 4096 | Maximum output tokens per model call. |
MODEL_TEMPERATURE | 0.1 | Model sampling temperature. |
CHAT_HOST | 127.0.0.1 | Datum bind address outside Docker. |
CHAT_PORT | 3000 | Datum port. |
CHAT_MAX_UPLOAD_BYTES | 1073741824 | Maximum upload size. |
CHAT_MAX_UPLOAD_FILES | 5 | Maximum files per upload. |
CHAT_MAX_TOOL_ROUNDS | 12 | Maximum tool-call rounds per response. |
CITYJSON_MCP_ALLOWED_ROOTS | current directory | Authorized filesystem roots. Use : on macOS/Linux and ; on Windows. |
CITYJSON_MCP_INPUT | ./input | Input directory used by cityjson_import. |
CITYJSON_MCP_WORKSPACE | ./.cityjson-mcp-workspace | Managed source and derived datasets. |
CITYJSON_MCP_COMMAND_TIMEOUT_MS | 120000 | External command timeout. |
CITYJSON_MCP_MAX_DOWNLOAD_BYTES | 26214400 | Maximum inline MCP download size. Datum streams downloads directly. |
CJIO_BIN | cjio | Optional cjio executable override. |
CJVAL_BIN | cjval | Optional cjval executable override. |
VAL3DITY_BIN | val3dity | Optional val3dity executable override. |
CITYGML_TOOLS_BIN | citygml-tools | Optional citygml-tools executable override. |
CJDB_BIN | cjdb | Optional cjdb executable override. |
Set PGPASSWORD in the process environment for cjdb. Database tool arguments do not accept a password.
Running without Docker requires Node.js and the backend executables used by the requested tools.
npm install
npm run doctor
npm test
npm run check
npm start
Backend installation sources:
Use npm run chat:host only when all required backends are installed locally. Datum checks backend availability during startup. CHAT_ALLOW_PARTIAL_BACKENDS=true allows startup with missing backends for development tests.
CITYJSON_MCP_ALLOWED_ROOTS, the input directory, and the managed workspace.shell: false.SELECT statement, but this check is not a database security boundary. Use a database role with limited permissions.Fork the repository and create a branch for one change.
Install dependencies with npm install.
Make the change. Keep MCP tool inputs typed and do not add shell-string execution.
Add or update tests for changed behavior.
Run:
npm test
npm run check
If the change affects a Docker backend, build the image and run npm run docker:doctor.
Update the README and .env.example when configuration or user-visible behavior changes.
Open a pull request that states what changed, why it changed, and how it was tested.
Do not include API keys, database passwords, private CityJSON datasets, generated workspaces, or .env files in a contribution.
Run the test suite:
npm test
Run syntax checks for project .mjs files:
npm run check
Verify every executable in the Docker image:
npm run docker:doctor
dataset_id values.cityjson_query computes bounding boxes from geometry stored directly on each object. It does not combine all child geometry into a parent bounding box.cityjson_spec_outline.val3dity is GPL-3.0 software and runs as a separate executable. Review upstream licenses before redistributing a modified image.This repository uses the MIT License. See LICENSE.
The bundled image invokes external programs under their own licenses. This repository does not relicense those programs.
Content type
Image
Digest
sha256:d37d0933a…
Size
456.6 MB
Last updated
24 days ago
docker pull yarroudh/cityjson-mcp