Skip to main content
Glama
Lavoiedavidw

Charlotte-Mecklenburg MCP Server

by Lavoiedavidw

Charlotte-Mecklenburg MCP Server

An MCP (Model Context Protocol) server that exposes Charlotte, NC and Mecklenburg County open data as tools for an LLM agent. Ask about trash pickup, parcels and zoning, crime and 311 requests, planning areas and rezonings, bike lanes and ADA infrastructure, or environmental data (watersheds, tree canopy, brownfields) at any address in the city/county, and the server geocodes the address and queries the relevant live ArcGIS layer for you.

Core motion behind almost every tool: geocode an address → spatial or attribute query against an ArcGIS REST layer → structured result + a human-readable summary.

Data sources

Everything is queried live, at call time, from public ArcGIS REST endpoints — there is no local copy of the underlying data (a small local registry of endpoint metadata, not the data itself, lives in data/registry.json; see "Endpoint discovery" below). Four backends are in play:

Source

What it is

Base URL

City of Charlotte on-prem ArcGIS Server

City departmental data (CMPD, sanitation, 311, zoning)

https://gis.charlottenc.gov/arcgis/rest/services

Mecklenburg County on-prem ArcGIS Server

County data (parcels, tax foreclosures)

https://meckags.mecklenburgcountync.gov/server/rest/services

City of Charlotte ArcGIS Online (AGOL) org

Solid waste routes, bike lanes, ADA infrastructure, CATS transit, watersheds, tree canopy

https://services.arcgis.com/9Nl857LBlQVyzq54/arcgis/rest/services

Mecklenburg County ArcGIS Online (AGOL) org

Capital Improvement Projects, brownfields, battery/HHW recycling sites

https://services.arcgis.com/BWD3gDuaqc7SQmy7/arcgis/rest/services

All endpoints are public and require no authentication/token. Geocoding uses Esri's public World geocoder (https://geocode.arcgis.com/...), also token-free for non-stored results.

An offline registry of ~1,875 discovered service/layer entries across all four sources (crawled via scripts/build_registry.py) backs the list_datasets tool, so an agent can search for "what layer has X" before calling a specific tool.

Related MCP server: Dilix MCP

Install

git clone <this repo> charlotte-mcp
cd charlotte-mcp
uv sync

Register with Claude Code:

claude mcp add charlotte -- uv --directory /Users/dwlavoie/charlotte-mcp run charlotte-mcp

(Substitute your actual clone path for /Users/dwlavoie/charlotte-mcp.) The server runs over stdio — no ports, no API keys, no config needed.

You can also run it directly for local testing:

uv run charlotte-mcp

Tools

21 tools total: 1 geocoder, 1 dataset-discovery tool, and 19 domain tools across six tool packs. Every location-based tool accepts either address: str or lat: float, lon: float and returns a dict with a summary field.

Tool

Pack

Description

geocode

core

Geocode a free-form address to lat/lon via the Esri World geocoder

list_datasets

datasets

Search the ~1,875-entry endpoint registry by keyword/source

lookup_parcel

property

Look up a parcel by address, point, PID, or NC-PIN (ParcelStatus)

get_zoning

property

Get zoning code and rezone date for a parcel (Parcel_Zoning_Lookup)

search_tax_foreclosures

property

Search county tax foreclosures by neighborhood/zip/status (TaxForeclosures)

get_trash_schedule

sanitation

Garbage/recycling/yard-waste day, provider, route, and GREEN/ORANGE recycling week

crime_near

safety

CMPD patrol + domestic-violence calls-for-service near a point (NPA-aggregate)

violent_crime_near

safety

CMPD violent-offense counts (homicide/rape/robbery/assault) near a point (NPA-aggregate)

homicides_near

safety

Point-level homicide incidents near a point, with weapon/clearance detail

service_requests_near

safety

311 service requests near a point

get_planning_area

planning

Named community planning area containing a location

rezonings_near

planning

Approved rezonings (since 2016) near a point

area_plans_at

planning

Area plan(s) covering a location

bike_lanes_near

transport

Bike lane segments near a point (street, lane type, width, year built)

ada_features_near

transport

ADA curb ramps, accessible parking, and pedestrian signals near a point

cip_projects_near

transport

County Capital Improvement (stormwater) projects near a point

cats_projects_near

transport

CATS transit capital projects (stations + corridors) near a point

brownfields_near

environment

Recorded NC Brownfields Program sites near a point

watershed_at

environment

Named watershed/basin containing a location

tree_canopy_at

environment

Tree canopy / vegetation / impervious-surface stats for a location

battery_recycling_sites_near

environment

Household hazardous waste / battery recycling drop-off sites near a point

Example prompts

  • "When is trash pickup at 2437 Remount Rd?"

  • "What's the zoning for 2437 Remount Rd, and has it been rezoned recently?"

  • "Are there any tax foreclosures in zip code 28208?"

  • "What watershed is 2437 Remount Rd in, and what's the tree canopy like there?"

  • "Any 311 requests or CMPD calls for service near 600 E 4th St in the last 90 days?"

  • "Find bike lanes and ADA curb ramps within 400 meters of 600 E 4th St."

  • "What capital improvement or CATS transit projects are planned near uptown?"

  • "Search the dataset registry for anything related to 'waste'."

See docs/demo.md for a full worked transcript of the trash-pickup question.

Known data quirks

  • City SWS/AddressLocator geocoder is broken — it returns roughly (0, 0) in its native spatial reference (wkid 2264) instead of a real match. The server does not use it; geocode and every tool's address resolution go through Esri's World geocoder instead.

  • ParcelStatus (county on-prem) rejects outFields=* — the layer's query operation returns an HTTP 200 with an ArcGIS {"error": {"code": 400, ...}} body whenever outFields=* or resultRecordCount is present, regardless of the where/point/envelope filter. lookup_parcel always passes an explicit field list and never a page-size hint to work around it. This is isolated to this layer.

  • TaxForeclosures.status is null on every row in the current dataset (629 total rows checked). The status filter is still exposed by search_tax_foreclosures as documented — it just won't match anything until the county populates that column. cde_symbology_val and bpo_status are the actually-populated status-like fields.

  • CMPD_Calls_for_Service and ViolentCrimeData are aggregate-only tables, not incident-level data — no geometry, no per-incident rows, just monthly counts by Neighborhood Profile Area (NPA). crime_near and violent_crime_near proxy "near this point" by sampling nearby NPAs from the spatial ServiceRequests311 layer, then filtering the aggregate tables to those NPAs — a best-effort approximation, not a true point-in-polygon match. homicides_near and service_requests_near, by contrast, query real point-level layers directly.

  • ArcGIS distance + units=esriSRUnit_Meter buffer overshoot, on some AGOL layers. During development the environment tool pack found Brownfields and Battery_Recycling_Sites (both hosted on layers with a native NC State Plane feet spatial reference) returning features far outside the requested meter radius — consistent with the server silently treating the requested distance as the layer's native unit (feet) instead of converting from meters, a roughly 3.28x overshoot. environment.py's brownfields_near and battery_recycling_sites_near work around this by fetching the full (small: 170 and 5 records respectively) layer once and filtering with a client-side haversine calculation instead of relying on the server-side buffer. A follow-up audit of every other radius-based tool (all of safety.py and transport.py, plus a re-probe of the two originally-flagged layers) found the meter buffer behaving correctly everywhere else, including on a fresh re-test of Brownfields/Battery_Recycling_Sites — see the "Task 8 distance-buffer regression check" note in PLAN.md for the full methodology and a caution for future maintainers about re-verifying before trusting (or removing a workaround for) the buffer on any new layer.

Development

uv run pytest -m live   # live smoke tests against real endpoints, one file per tool pack

Tests are marked live because they hit real network services; expect occasional transient failures from upstream ArcGIS endpoints rather than code bugs.

F
license - not found
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Lavoiedavidw/Charlotte-City-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server