Skip to main content

urban-mcp

PyPI Python CI License: MIT

A Model Context Protocol (MCP) server for urban planning, mobility and spatial statistics. It gives AI agents (Claude Desktop, Claude Code and any other MCP client) 24 tools for accessibility (2SFCA, gravity, 15-minute city, PTAL), multi-criteria siting (AHP, TOPSIS, MCLP), space syntax, landscape metrics, seismic screening, slope-aware routing and isochrones, and spatial statistics (Moran's I, LISA, Getis-Ord Gi*, GWR, Huff).

The methods come from three published, tested SDKs; this package is a thin, validated MCP layer over them:

SDK Import Used for
planx-sdk planx accessibility, PTAL, MCDA, MCLP, space syntax, landscape metrics, Spacemate, seismic chain
zero2route3d-sdk zero2route3d slope/energy-aware routing, isochrones, hiking time, ramp audit, OSM streets
geoai2analytics-sdk geoai2analytics Moran's I, LISA, Gi*, GWR, Huff model

Generic GIS MCP servers (e.g. gis-mcp, mcp-geo) cover geometry operations and file I/O; urban-mcp focuses on planning indicators and spatial statistics and returns analysis-ready summaries.


Installation

pip install urban-mcp          # Python >= 3.10
urban-mcp --list-tools         # check the install

Or run it without installing, with uv:

uvx urban-mcp --list-tools

Dependencies: mcp (official Python SDK, 2.x), planx-sdk, zero2route3d-sdk, geoai2analytics-sdk and NumPy/SciPy. No GDAL, GeoPandas or Shapely is needed.

Connecting a client

Claude Code

claude mcp add urban-mcp -- uvx urban-mcp
# with file output and OpenStreetMap downloads enabled:
claude mcp add urban-mcp -- uvx urban-mcp --output-dir ./urban_outputs --allow-network

Claude Desktop

Add to claude_desktop_config.json (Settings > Developer > Edit Config):

{
  "mcpServers": {
    "urban-mcp": {
      "command": "uvx",
      "args": ["urban-mcp"]
    }
  }
}

With a pip install, use "command": "urban-mcp", "args": [] instead. To let the server save results, add "--output-dir", "/path/to/folder" to args.

Other clients / HTTP

Any MCP client that launches a stdio server can run urban-mcp. For a network service use streamable HTTP (bound to localhost by default):

urban-mcp --transport streamable-http --host 127.0.0.1 --port 8000   # endpoint: /mcp

Options

Option Environment variable Default Meaning
--output-dir DIR URBAN_MCP_OUTPUT_DIR none the only folder save_geojson may write to; without it, writing is disabled
--allow-network URBAN_MCP_ALLOW_NETWORK=1 off enables fetch_osm_street_network
--max-output-features N URBAN_MCP_MAX_OUTPUT_FEATURES 500 features kept in a GeoJSON result (the rest is dropped and flagged truncated)
--max-input-features N URBAN_MCP_MAX_INPUT_FEATURES 2000 largest layer accepted by the quadratic tools
--transport stdio stdio or streamable-http
--list-tools [--json] print the tools and exit

Tools

Tool What it does SDK function
accessibility_2sfca 2SFCA / E2SFCA supply-per-head of demand points to facilities, with Gini planx.spatial.enhanced_2sfca, spatial_equity_gini
accessibility_gravity Hansen gravity accessibility planx.spatial.gravity_accessibility
accessibility_2sfca_matrix 2SFCA on your own travel-time matrix planx.engine.accessibility.two_step_fca
fifteen_minute_city_score 0-100 score, per-category coverage, missing categories per origin planx.spatial.calculate_15m_city_score
ptal_score TfL PTAL index and band for a site planx.engine.ptal.accessibility_index, band
ahp_weights AHP weights and consistency ratio planx.suitability.ahp_weights
topsis_ranking TOPSIS ranking (given, AHP or equal weights) planx.suitability.topsis_method
facility_siting_mclp Maximal covering location (greedy), with existing facilities planx.suitability.greedy_mclp
space_syntax_segment_analysis Angular integration (NAIN) and choice (NACH) per street segment planx.engine.graphs.build_segment_graph, planx.engine.syntax.segment_angular_analysis
landscape_metrics FRAGSTATS-style class and landscape metrics of a land-cover grid planx.engine.landscape.metrics
spacemate_density FSI/GSI/OSR/L and density typology per block planx.urban_morphology.spacemate_density_matrix
seismic_damage_screening ASB2014 PGA, Hazus damage states and casualties per building (screening) planx.engine.gmpe.predict, fragility.*, impact.casualties
route_3d Slope/energy-aware route for a mobility profile zero2route3d.solve_3d_route
isochrones_3d Slope-aware isochrone polygons zero2route3d.solve_3d_isochrones
hiking_time DIN 33466, Naismith-Langmuir and Tobler walking times zero2route3d.estimate_hiking_time
wheelchair_ramp_audit ADA ramp slope / rise / landing audit of a 3D path zero2route3d.audit_wheelchair_ramp_compliance
list_mobility_profiles The 15 mobility profiles (adult, senior, wheelchair, bicycle ...) zero2route3d.PROFILES
global_moran_i Global Moran's I with permutation test geoai2analytics.global_moran
local_moran_lisa LISA clusters and outliers (FDR-corrected) geoai2analytics.local_moran
hotspot_gi_star Getis-Ord Gi* hot/cold spots with 99/95/90 % bins geoai2analytics.local_getis_ord_gi_star
gwr_regression Geographically weighted regression, local coefficients and R2 geoai2analytics.GWR
huff_market_share Huff retail model: probabilities and market shares geoai2analytics.huff_model
save_geojson Save a FeatureCollection into --output-dir (off by default) (urban-mcp)
fetch_osm_street_network OSM streets in a bbox via Overpass (off by default) zero2route3d.NetworkSourceManager.fetch_osm_network_bbox

Inputs are GeoJSON FeatureCollections (points; polygons are reduced to their area-weighted centroid), plain arrays and numbers, each described in the tool's JSON schema. Coordinates may be WGS84 lon/lat or projected metres: coordinate_system="auto" treats a layer as lon/lat when every coordinate lies within +-180/+-90 and projects it to a local plane in metres. Pass coordinate_system="projected" for projected data with small coordinate values (e.g. a local grid in metres near the origin). Routing tools need lon/lat.

Outputs are JSON objects with the SDK function used, a summary (counts, min/mean/max, class counts, top items) and, where it makes sense, a GeoJSON result that pairs each input geometry with the computed properties. Results are capped at --max-output-features and marked truncated: true with total_features.

Distances in the point-based tools (2SFCA, gravity, 15-minute city, MCLP, Huff) are straight-line. Use route_3d / isochrones_3d, or pass a network travel-time matrix to accessibility_2sfca_matrix, for network distances.

Resources and prompts

Resource Content
urban://tools Markdown catalogue of all tools and the SDK functions behind them
urban://methods Index of method notes
urban://methods/{method} Formula, interpretation and references: 2sfca, gravity, 15-minute-city, ptal, ahp, topsis, mclp, space-syntax, landscape-metrics, spacemate, seismic-screening, routing-3d, hiking-time, ramp-audit, morans-i, lisa, gi-star, gwr, huff
urban://samples/{name} Small WGS84 sample layers: street-network, neighbourhoods, clinics
urban://about Package versions and the active safety configuration
Prompt Arguments Workflow
fifteen_minute_city_audit area_name, amenity_categories, max_walk_m 15-minute score, 2SFCA equity, profile isochrones, optional siting
hot_spot_analysis layer_description, value_field, weights Moran's I, sensitivity to weights, LISA and Gi*
site_selection_mcda facility_type, criteria AHP weights with consistency check, TOPSIS ranking, robustness

Example conversations

You: Here are our neighbourhood centroids with population and the GP clinics with doctor counts (two GeoJSON files). Who is underserved within 1.5 km?

Assistant calls accessibility_2sfca(demand_points=..., supply_points=..., demand_field="population", supply_field="doctors", cutoff_m=1500) and answers: "Median access is 0.42 doctors per 1 000 residents; 3 of 41 neighbourhoods (6 900 people) have no clinic within 1.5 km - all on the eastern edge. The population-weighted Gini is 0.38."

You: Is the rent level clustered? Use the census tracts layer.

Assistant runs the hot_spot_analysis prompt: global_moran_i (I = 0.41, p = 0.001), then local_moran_lisa and hotspot_gi_star, and reports 12 high-high tracts around the waterfront and 9 low-low tracts in the north, noting the FDR correction.

You: How much harder is the walk from the station to the hospital for a wheelchair user?

Assistant calls route_3d twice (profile="adult" and profile="wheelchair") on the street network and compares: "The direct route climbs 40 m over a steep hill street; the wheelchair route avoids it, 0.44 km longer (12.5 vs 8.1 min) and ADA-compliant."

You: We have 120 candidate parcels; pick 5 shelter sites that cover most residents within 500 m.

Assistant calls facility_siting_mclp(k=5, max_distance_m=500, ...), lists the five parcels with marginal coverage, and offers save_geojson to export them for QGIS.

Python use

The tool handlers are plain functions and can be called without MCP:

from urban_mcp.tools.accessibility import ptal_score

print(ptal_score([
    {"stop_id": "s1", "walk_minutes": 4,
     "routes": [{"route_id": "B1", "mode": "bus", "frequency_per_hour": 6},
                {"route_id": "B2", "mode": "bus", "frequency_per_hour": 12}]},
    {"stop_id": "s2", "walk_minutes": 10,
     "routes": [{"route_id": "R1", "mode": "rail", "frequency_per_hour": 4}]},
])["ptal_band"])   # 2

urban_mcp.server.build_server(ServerConfig(...)) returns the configured MCPServer, for embedding or in-process testing with mcp.Client.

Security notes

  • No arbitrary code execution. Tools only accept data (GeoJSON, arrays, numbers, enums) validated against JSON schemas and again in the handler; nothing is evaluated or imported from user input, and no tool reads files by path.
  • File writes are opt-in and confined. Only save_geojson writes, only when the server was started with --output-dir, only plain file names inside that folder (no paths, no ..), and it never overwrites unless overwrite=true. Note: zero2route3d-sdk creates an empty zero2route3d_cache folder in the system temp directory when it parses a network; nothing else is written there by the routing tools.
  • No network by default. Only fetch_osm_street_network goes online, only with --allow-network, with a bounded bbox (about 5 x 5 km) and 12 s timeouts; its cache goes to --output-dir/osm_cache or a temporary folder that is deleted afterwards. The routing tools never fetch elevation online (fetch_online_dem=False).
  • Bounded work. Input layers for the quadratic tools are capped (--max-input-features, 4 million matrix cells, 1 500 features for GWR), outputs are truncated, and long computations run in a worker thread so the server stays responsive.
  • Errors are tool errors. Invalid input returns an MCP tool result with isError: true and a message that says what to fix; the server does not crash.
  • HTTP transport binds to 127.0.0.1 by default and has no authentication; put it behind an authenticating proxy before exposing it.
  • Model outputs are screening results. The seismic tool in particular uses uncalibrated design-level mapping (see urban://methods/seismic-screening).

Development

git clone https://github.com/YusufEminoglu/urban_mcp_sdk
cd urban_mcp_sdk
pip install -e ".[dev]"
python -m pytest --cov=urban_mcp     # handler tests + in-process MCP client round trips
python -m ruff check . && python -m ruff format --check src tests
python -m mypy src

Citation

See CITATION.cff. Please also cite the underlying SDKs and the original method papers listed in the urban://methods/* resources.

License

MIT (c) Yusuf Eminoğlu. OpenStreetMap data fetched by the OSM tool is (c) OpenStreetMap contributors, ODbL 1.0.

Metadata

Release files for urban-mcp 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for urban-mcp 0.1.0
File Size Uploaded Provenance Actions
urban_mcp-0.1.0.tar.gz 61.4 kB With provenance Details for urban_mcp-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for urban-mcp 0.1.0
File Interpreter ABI Platform Actions
urban_mcp-0.1.0-py3-none-any.whl Python 3 none any Details for urban_mcp-0.1.0-py3-none-any.whl

Total release size: 113.4 kB

Release files / urban_mcp-0.1.0.tar.gz

Download URL urban_mcp-0.1.0.tar.gz
Size 61.4 kB
Tags Source
SHA-256 checksum
How to use checksums
991eb79826e61ac92e20858451603b97c8d228b3d923622cd9a647a93825d217
BLAKE2b-256 checksum
How to use checksums
454c5735515643267d3786f3fdf3376b8486d2992bf2d2fb1718dc4a44abe152
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 8, 2026.

Transparency log

Release files / urban_mcp-0.1.0-py3-none-any.whl

Download URL urban_mcp-0.1.0-py3-none-any.whl
Size 52.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
82f70696c7456f941591fe1b7a245ae884110a5b23ff8d0b810a928d3b37b851
BLAKE2b-256 checksum
How to use checksums
832d29a0fc350983c1a83e5b6a4468f95322156c90498e810bc9ff941d4e50a2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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 Nov 8, 2026.

Transparency log

Security for urban-mcp 0.1.0

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

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for urban_mcp-0.1.0.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 Nov 8, 2026.

Transparency log

A 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.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page