San Diego GIS MCP

San Diego GIS MCP

Serves SANDAG/SanGIS regional GIS data from SANDAG's ArcGIS Enterprise portal, enabling natural-language queries for parcels, floodplains, address points, transit, land use, and hundreds of other layers.

Category
访问服务器

README

OpenContext

<p align="center"> <img src="docs/opencontext_logo.png" alt="OpenContext Logo" width="400"> </p>

License: MIT Python 3.11+ MCP Compatible


San Diego Regional GIS MCP — a San Diego fork of OpenContext. It serves the SANDAG-hosted regional / SanGIS catalog from SANDAG's ArcGIS Enterprise portal (geo.sandag.org) through the built-in arcgis plugin: parcels, floodplains, address points, transit, regional land use, and hundreds of other regional layers.

Scope: two San Diego servers

San Diego coverage is split across two MCP servers. This server is the regional one.

Server Backing data Owns
San Diego Regional GIS (this repo) SANDAG/SanGIS ArcGIS Enterprise — geo.sandag.org Regional/county-wide layers: parcels, floodplain (FEMA NFHL), address points, roads, transit, regional land use & demographics
San Diego City (separate server) City of San Diego ArcGIS Server — webmaps.sandiego.gov Authoritative City municipal layers: MHPA, City base zoning, community-plan land use

If a question needs an authoritative City of San Diego municipal layer (MHPA boundaries, City base zones, community-plan land use), route it to the San Diego City server — this catalog does not hold those layers. The search_datasets tool description carries the same routing hint so models pick the right server on their own.

Data disclaimer & attribution. This server passes each layer's SanGIS/SANDAG attribution through in tool responses. Before using the data, review the SANDAG GIS Data Disclaimer (see SANDAG's Geographic Information Systems page), the SanGIS Legal Notice, and the SANDAG Open Data Terms of Use.


How it works

Discovery searches SANDAG's portal catalog anonymously (geo.sandag.org/portal/sharing/rest/search); if portal search is ever closed off, the plugin automatically falls back to walking the ArcGIS Server services directory (geo.sandag.org/server/rest/services), skipping auth-gated folders such as GeoDepot.

Coordinate contract: WGS84 in, WGS84 out. SANDAG stores its layers in EPSG:2230 (CA State Plane Zone VI, US feet), but that never leaks to callers: every /query request pins outSR=4326, every point/geometry input is declared as inSR=4326, and the geocoder returns outSR=4326 — so all coordinates in and out of every tool are plain WGS84 lon/lat (EPSG:4326). This matches the sibling San Diego City server, so results from the two servers compose without reprojection.

Tools exposed

Tool Purpose
arcgis__search_datasets Discover datasets by keyword (e.g. "parcels", "floodplain"). Supports a type filter — see below.
arcgis__get_dataset Fetch a dataset's metadata, service URL, and SanGIS attribution
arcgis__get_layer_schema List a dataset's fields (name, type, alias, coded values), optionally filtered by keyword
arcgis__get_distinct_values List the distinct values in a field (with optional like / where) to confirm exact codes
arcgis__query_data Query features from a dataset (supports where, out_fields, order_by, limit). Output leads with a TOTAL MATCHING count, so "how many X?" needs no paging. Pages with resultOffset when a layer's MaxRecordCount truncates a response.
arcgis__spatial_query_point Point-in-polygon: which polygon(s) contain a given point — by lon/lat or a street address
arcgis__geocode_address Convert a street address to lon/lat via the SANDAG composite locator (SANDAG_COMPOSITE_LOCATOR GeocodeServer)
arcgis__get_aggregations Facet counts across the catalog (e.g. by type, tags, owner), tallied over the top matching items

Finding queryable data: type filter

The portal catalog mixes queryable Feature Services with service definitions, web maps, and apps. search_datasets takes an optional type argument that restricts results to a single ArcGIS item type — pass type: "Feature Service" to see only data you can query or map.

search_datasets arguments:

Arg Required Description
q yes Full-text search query (single keywords match best; multi-word queries fall back to the most distinctive word if the phrase finds nothing)
type no Restrict to one item type. Use "Feature Service" for queryable data; other values: "Map Service", "Web Map", "Web Mapping Application"
limit no Max results, 1–100 (default 10)

For example, q: "parcels" alone returns 137 items of mixed types; with type: "Feature Service" it returns the 63 queryable layers.

Dataset IDs

Portal discovery returns 32-char hex item IDs (e.g. the SanGIS Parcels layer). When running in directory-fallback mode, IDs are service paths instead (e.g. Hosted/Parcels/FeatureServer). Both forms are accepted by every tool that takes a dataset/item ID.

Writing correct queries: schema → distinct values → query

ArcGIS field names are case-sensitive — and SANDAG's hosted layers use lowercase field names (apn, situs_address, …). Rather than guess, use the discovery tools first:

  1. get_layer_schema — see the real field names and types. keyword narrows a wide schema:
    { "item_id": "<parcels-id>", "keyword": "situs" }   // -> situs_address, situs_street, situs_zip, ...
    
  2. get_distinct_values — confirm the exact value to filter on:
    { "item_id": "<parcels-id>", "field": "situs_community" }
    
  3. query_data — now write the where clause with verified names and values.

Spatial lookup: spatial_query_point (by address or coordinates)

"Which polygon contains this location?" — against a polygon Feature Service (parcels, floodplains, districts, …). Pass either a street address (geocoded automatically via the SANDAG composite locator) or a WGS84 lon/lat (longitude first):

// arcgis__spatial_query_point — by address (202 C St = San Diego City Administration Building)
{ "item_id": "<parcels-id>", "address": "202 C St, San Diego, CA",
  "out_fields": "apn,situs_address,situs_street" }

// ...or by coordinates
{ "item_id": "<parcels-id>", "lon": -117.1626, "lat": 32.7170 }

Returns the attributes of every polygon containing the point (no geometry); when an address is used, the matched address is shown. You can also geocode on its own with geocode_address.


Try asking

Once the connector is added, just ask Claude in plain English — it picks the right tools:

Discovery

  • "What GIS layers does SANDAG publish about flooding?" (type-filtered discovery)
  • "Break down the regional GIS catalog by type." (aggregations)

Counts & records

  • "How many parcels are in the 92101 ZIP code?" (answered from TOTAL MATCHING, no paging)
  • "What communities appear in the parcels layer?" (get_distinct_values)

Schema

  • "What fields does the parcels layer have?" (get_layer_schema)

Spatial

  • "Which parcel is San Diego City Hall (202 C St) on?" (address geocoded automatically, then point-in-polygon)
  • "What flood zone is at latitude 32.717, longitude -117.163?" (coordinates also work)

Run locally

config.yaml is already committed for San Diego (the arcgis plugin pointed at geo.sandag.org), so no setup is needed to run the server locally:

pip install aiohttp pyyaml
python3 scripts/local_server.py      # serves http://localhost:8000/mcp

On startup it connects to the live portal and registers the eight arcgis__* tools. SANDAG's portal search and Hosted services are public, so no API token is required. (On a Windows console you may need PYTHONUTF8=1 for the startup banner's emoji.)

Verify the upstream endpoints directly (services directory, portal search, a live parcel query, and a geocode) with:

python3 scripts/smoke_sandag_live.py

And smoke-test a running deployment end-to-end (defaults to http://localhost:8000/mcp):

python3 scripts/smoke_prod.py http://localhost:8000/mcp

See Getting Started for the generic OpenContext setup.

Connect to Claude

The server is live. Add it as a custom connector in Claude (same steps on Claude.ai and Claude Desktop):

  1. Settings → Connectors (or Customize → Connectors on claude.ai)

  2. Add custom connector

  3. Name it e.g. San Diego GIS and paste the URL:

    https://sandiego-gis.codeforanchorage.org/mcp
    

Quick health check from a terminal:

curl -sS -X POST https://sandiego-gis.codeforanchorage.org/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"ping"}'
# → {"jsonrpc":"2.0","id":1,"result":{"status":"ok"}}

Deploy & operate

Production runs on AWS Lambda + API Gateway behind sandiego-gis.codeforanchorage.org, in us-west-2. DNS for codeforanchorage.org is managed externally at DreamHost: the ACM validation CNAME and the sandiego-gis CNAME (→ terraform output -raw custom_domain_target) are created there.

First-time bootstrap (state backend, once per account):

cd terraform/bootstrap
terraform init
terraform apply \
  -var="aws_region=us-west-2" \
  -var="state_bucket_name=<your-tfstate-bucket>" \
  -var="lock_table_name=terraform-state-lock"

These values must match terraform/aws/backend.tf.

Deploy / redeploy:

./scripts/deploy.sh --environment prod

For a code-only change, that single command is all you need. The first stand-up of a new environment also creates an ACM certificate and an API Gateway custom domain — DNS is managed externally (no Route53), so on the first deploy you must:

  1. Validate the cert. The first apply errors on CreateDomainName ("Certificate is not in an ISSUED state") — expected. Create the ACM validation CNAME (terraform output acm_validation_cname_name/_value), wait for ISSUED, then re-run the deploy.
  2. Point the endpoint. Create a CNAME for your domain → terraform output -raw custom_domain_target.

Documentation

Doc Description
Getting Started Setup and usage
Architecture System design and plugins
Deployment AWS, Terraform, monitoring
Testing Local testing (Terminal, Claude, MCP Inspector)

Examples


Contributing

Pre-commit hooks (optional):

pip install pre-commit
pre-commit install

Hooks: Ruff, yamllint, gofmt, plus detect-private-key and gitleaks secret scanning. Run manually: pre-commit run --all-files.

Never commit secrets. config.yaml is tracked, so any API token belongs in an environment variable referenced via ${ENV_VAR} (e.g. token: "${ARCGIS_TOKEN}"), never inline. The gitleaks hook will block accidental commits of keys/tokens.


License

MIT — see LICENSE.

Author: Srihari Raman, City of Boston Department of Innovation and Technology

San Diego fork adapted from the Worcester, MA fork of OpenContext. OpenContext is MIT-licensed; this fork retains the original attribution above. GIS data © SanGIS/SANDAG — see the disclaimer links at the top of this README.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选