QueensEstate ♔⛫
An MCP server that lets an LLM answer Charlotte residents' everyday questions with official public data from the City of Charlotte Open Data Portal — https://data.charlottenc.gov.
The portal lives at
data.charlottenc.gov. It is an ArcGIS Hub site; its ~380 datasets are ArcGIS REST layers run by the City of Charlotte and Mecklenburg County. No API key is required.
What's on the portal
A survey of the catalog (/api/feed/dcat-us/1.1.json) shows these main themes:
Tools
The tools are chosen for the questions residents actually ask. Every tool is read-only, and
every location parameter accepts either an address ("600 E 4th St, Charlotte") or
"latitude,longitude".
My address
Safety
City services and neighborhood change
Places and transit
Government transparency
Anything else in the catalog
Setup
Requires Python 3.14 (developed on 3.14.7).
~/.pyenv/versions/3.14.7/bin/python -m venv .venv
.venv/bin/python -m pip install -e . --group dev
Use with Claude Code
claude mcp add queensestate -- /absolute/path/to/queensestate/.venv/bin/queensestate
Use with Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"queensestate": {
"command": "/absolute/path/to/queensestate/.venv/bin/queensestate"
}
}
}
Other transports and debugging
.venv/bin/queensestate --transport http --port 8000 # http://127.0.0.1:8000/mcp
npx @modelcontextprotocol/inspector .venv/bin/queensestate # interactive inspector
The http transport authenticates its callers with Google OAuth and refuses to start
without a public URL, a Google client, and an allow list — see Hosting. To try
it locally:
QUEENSESTATE_PUBLIC_URL=http://localhost:8000 \
QUEENSESTATE_GOOGLE_CLIENT_ID=... QUEENSESTATE_GOOGLE_CLIENT_SECRET=... \
[email protected] \
.venv/bin/queensestate --transport http --port 8000
Hosting
The hosted server runs on Google Cloud Run at https://queensestate.adamwanninger.com/mcp, and is its own OAuth 2.1 authorization server: it delegates the sign-in to Google purely to learn which account is calling, then checks that address against an allow list and mints its own tokens. Google's tokens are never passed through to the MCP client.
claude mcp add --transport http queensestate https://queensestate.adamwanninger.com/mcp
scripts/deploy-gcp.sh provisions and deploys the whole thing, and re-running it is also
how you update. See deploy/GCP.md.
Hosting is only for the HTTP transport. Running the server locally over stdio needs no Google account, no allow list, and none of the cloud dependencies.
Development
.venv/bin/pytest # unit + in-memory MCP tests; no network access
.venv/bin/ruff format --check . && .venv/bin/ruff check .
.venv/bin/mypy
Layout:
src/queensestate/arcgis.py: async ArcGIS REST / Hub client (timeouts, bounded retries, host allowlist)src/queensestate/address.py: address matching against the county Master Address layersrc/queensestate/catalog.py: the curated layers behind the purpose-built toolssrc/queensestate/server.py: the MCP tools
Data notes and limitations
- Geocoding uses Mecklenburg County's Master Address layer, so only addresses inside the county resolve. Coordinates outside the county are rejected.
- Freshness varies by dataset: CMPD incidents, 311 requests, and code enforcement are current within days; the Budget Report dataset currently runs FY2018–FY2023. Tools report what the portal publishes.
- CMPD locations are generalized by the department for privacy.
- 311 data records when requests were received, not when they were resolved.
- Timestamps are ISO 8601 in Charlotte local time with the UTC offset
(e.g.
2026-09-06T00:00:00-04:00). The underlying services store UTC. - Budget amounts include large negative lines (e.g. "00 Non Department", Charlotte Water),
so
get_city_budgetreports both the net total and the sum of positive lines. - Statistics limits: some city map services ignore
resultRecordCounton aggregate queries, so the client enforces row limits itself. - Live bus arrivals are not on the portal; pair this server with a CATS real-time source.
Bus stop IDs from
find_nearby_placesmatch CATS stop IDs. - The generic tools only reach hosts that serve portal datasets (
gis.charlottenc.gov,meckgis.mecklenburgcountync.gov,services.arcgis.com,gis.ci.charlotte.nc.us). - Mecklenburg County's server rejects default Python user agents, so the client sends its own.
Ideas for more tools
- Neighborhood Quality of Life indicators by NPA (income, age, housing, amenities)
- Charlotte Fire incident reports by address block
- CMPD officer traffic-stop statistics
- Sidewalk and bike network gaps near an address
- Tree canopy and land surface temperature by neighborhood
License
MIT - see LICENSE.