Listed from the MCP registry; nobody on Odel publishes it.
Runs on your machine from its package; the steps are below.
At most: only reads or looks up information.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
npx -y @betanyc/ny-data-registry-mcp
MCP client config
{
"mcpServers": {
"ny-data-registry": {
"command": "npx",
"args": [
"-y",
"@betanyc/ny-data-registry-mcp"
]
}
}
}An MCP (Model Context Protocol) server that finds open data across every New York data portal in BetaNYC's NY data portal registry: which portal covers a place or topic, how to query it, and, with the harvested index, which datasets and columns exist and in what formats.
It complements the socrata MCP rather than replacing it. This server knows where data is (140 sources across state, county, city, agency, library and nonprofit portals) and hands the agent the right Socrata domain or ArcGIS service. Querying the data stays with socrata and the other BetaNYC MCPs.
Built by BetaNYC. Status: 0.1.0. Package: @betanyc/ny-data-registry-mcp.
Nine tools, in two groups.
Registry tools (work out of the box, no index needed):
| Tool | What it answers |
|---|---|
find_sources | Which portals cover this topic or place? Filters: place, tier, platform family, has API, key required, status (default live) |
list_sources_for_place | Everything for one county, city, NYC borough or region. Says whether the place has a portal, was checked and has none, or is not in the registry (unchecked, not absent) |
how_to_query | Endpoint (verbatim from the registry, never guessed), API type, key and env var, example request, and which MCP to hand off to. Flags UNCONFIRMED, undocumented, gated, dead and no-portal rows |
check_source | One live request to a portal URL: status, redirect, response time, and whether that matches the registry |
registry_info | Row counts, registry date and origin, unmapped platform strings, index size and last harvest |
Index tools (need the harvested SQLite index):
| Tool | What it answers |
|---|---|
search_datasets | Dataset titles, descriptions, tags and column names across Socrata and ArcGIS portals. Filters: place, portal, platform family, format (csv, geojson, shapefile, kml, api, xlsx …), updated since |
get_dataset_columns | Fields and types, every distribution (format + URL), license, publisher, extent, and per-field provenance |
find_joinable | Datasets sharing a join key (BBL, BIN, borough, community district, council district, census tract, SWIS, FIPS, ZIP), from a curated key-column map, not raw name equality |
whats_new | Datasets added, changed or removed since a date |
All tool inputs are strict: an unknown parameter is rejected by name, never silently dropped (same rule as nyc-311-mcp).
Ask your AI assistant these once the server is connected. Where we ran a question on 2026-11-05, what it found is noted. Portals change, so your results may differ.
Which datasets match my words? (search_datasets)
Who has data, and for where? (find_sources, list_sources_for_place)
How do I use it? (how_to_query, get_dataset_columns, find_joinable)
Is it current? (whats_new, check_source, registry_info)
Tips
socrata MCP. For an ArcGIS layer, open or download it from the portal.Requires Node 22.16 or later (it uses Node's built-in node:sqlite, so there is nothing native to compile; 22.16 is the first official Node whose SQLite includes FTS5 full-text search).
Add it to your MCP client's config (Claude Desktop, Claude Code, Cursor, VS Code and others use this shape):
"ny-data-registry-mcp": {
"command": "npx",
"args": ["-y", "@betanyc/ny-data-registry-mcp"]
}
That's all. The first time it starts, it downloads the dataset index by itself (about 19 MB, unzipped to about 110 MB). This takes a few seconds on a normal connection. The five registry tools work right away. If you ask a dataset question while the download is still running, the tool waits up to 45 seconds and then says "try again in a minute".
From source instead:
git clone https://github.com/BetaNYC/ny-data-registry-mcp && cd ny-data-registry-mcp
npm install # also builds
npm test # offline; no network
and point your client at node /path/to/ny-data-registry-mcp/dist/index.js.
The four index tools (search_datasets, get_dataset_columns, find_joinable, whats_new) read a prebuilt SQLite index. It holds metadata only: titles, descriptions, column names and types, formats and links. No dataset contents.
Where it comes from: the index-latest release, at a fixed URL: https://github.com/BetaNYC/ny-data-registry-mcp/releases/download/index-latest/ny-data-index.sqlite.gz. Dated snapshots are kept as index-YYYY-MM-DD releases.
Checked before use: the server checks both SHA-256 sums in the release's SHA256SUMS file (the .gz and the unzipped .sqlite) and the schema version. A file that fails any check is never installed.
Where it goes: ${HOME}/.cache/betanyc/ny-data-registry/ny-data-index.sqlite (change with NY_DATA_INDEX).
When: only when the file is missing. An existing index is never replaced on its own.
Get or refresh it by hand:
npx -y -p @betanyc/ny-data-registry-mcp ny-data-registry download # if missing
npx -y -p @betanyc/ny-data-registry-mcp ny-data-registry download --force # replace with the newest
Turn the download off: set NY_DATA_INDEX_AUTO_DOWNLOAD=0. Then the dataset tools reply with these steps instead. Or build your own index with ny-data-registry harvest (see "Refresh").
| Variable | Default | Purpose |
|---|---|---|
NY_DATA_INDEX | ${HOME}/.cache/betanyc/ny-data-registry/ny-data-index.sqlite | Where the index lives |
NY_DATA_INDEX_AUTO_DOWNLOAD | on | 0 = never download the index on first run |
NY_DATA_INDEX_URL | the index-latest release | Download the index from somewhere else (it must have a SHA256SUMS beside it) |
NY_DATA_CACHE_DIR | ${HOME}/.cache/betanyc/ny-data-registry | Registry refresh cache and HTTP cache |
NY_DATA_REGISTRY_REFRESH | on | 0 = never fetch the published registry; use the bundled copy |
NY_DATA_OFFLINE | off | 1 = refuse every network call, including the first-run download (tests use this) |
NY_DATA_MAX_LAYERS | 250 | Per-portal cap on ArcGIS field requests per harvest |
NY_DATA_MIN_INTERVAL_MS | 1000 | Minimum gap between requests to one host (cannot be set below 1000) |
SOCRATA_APP_TOKEN | none | Sent as X-App-Token to the Socrata Discovery API |
BetaNYC/grounding-ai-with-ny-open-data resources/ny-data-portal-registry.json (140 rows). A copy is bundled in data/. On startup the server makes a conditional request (ETag) for the published copy on GitHub Pages, caches it under ${HOME}/.cache/betanyc/, and falls back to the bundled copy on any failure. This server never edits the registry.data/:
platform-families.json: maps each of the registry's 101 free-text platform strings to one family (socrata, arcgis-hub, arcgis-enterprise, arcgis-server, ckan, geoblacklight, custom-api, none). A string not in the table is reported as unmapped, never guessed; 7 strings are deliberately null because the registry marks them UNCONFIRMED.ny-places.json: 62 counties, 62 cities, 5 boroughs, 8 regions.key-columns.json: the curated join keys.handoffs.json: which BetaNYC MCP serves which registry row.harvest-config.json: skips (ANHD, Rockland, Columbia GeoBlacklight) and caps.synonyms.json: a short civic synonym list ("property" → parcels, tax lots, BBL).node dist/cli.js harvest reads every live registry row and harvests the ones a documented catalog API covers:
| Platform | Adapter | Columns from |
|---|---|---|
| Socrata | Discovery API (api.us.socrata.com/api/catalog/v1, scroll_id paging) | the search response itself |
| ArcGIS Hub | OGC API - Records (/api/search/v1/collections/dataset/items), plus the DCAT-US 1.1 feed for formats; falls back to the all collection, then DCAT only, on older sites | FeatureServer?f=json, then each layer ?f=json |
| ArcGIS Enterprise | <portal>/sharing/rest/search, filtered to the portal's own org (portals/self), or to its own service host when the org id is hidden | same second hop |
| ArcGIS Online org (agency REST directories such as DEC, Parks, DCP) | www.arcgis.com/sharing/rest/search?q=orgid:<id> | same second hop |
Everything else (custom APIs, CKAN, GeoBlacklight, standalone ArcGIS Server directories, pages with no API) is described by the registry tools but not harvested in this version.
Guards
esri…, org P3ePLMYs2RVChkJx). For Enterprise portals and ArcGIS Online orgs, items from another org are not counted under that portal; this is what turns Monroe's 108 search hits into its own content. Hub sites are not filtered by org, because a Hub's catalog is curated by its owner and aggregators like the State GIS Clearinghouse deliberately list other agencies' layers.[0,0,0,0]) and projected coordinates (State Plane) count as unknown, never as outside.id. A listing that comes back with fewer unique items than the portal's own total is treated as incomplete, so nothing is marked removed from a shaky listing.The index stores metadata only (titles, descriptions, field names, links). It never downloads data.
Incremental: datasets carry first_seen, last_seen and a content hash. A layer whose modified date has not changed keeps its stored columns and is not re-requested. A dataset is marked removed only after a complete harvest of its portal.
node dist/cli.js harvest. .github/workflows/harvest.yml is written to do this nightly in GitHub Actions and publish the SQLite file as a release asset, but it is not enabled (schedule commented out). Index releases are published by hand for now; the first is index-2026-11-05.npm test: offline tests from recorded fixtures (test/fixtures/, each says which request produced it), including a stdio smoke test that starts the server and calls all nine tools.node dist/cli.js eval: a fixed set of real questions (eval/questions.json) scored against the real index. Results: eval/runs/.Latest results (2026-11-05, office-mac), all in eval/runs/:
npm test: 44 / 44 pass.erm2-nwe9) joins: BBL 426, borough 1,073, community district 510, council district 426 datasets (Scout, name equality only: 331 / 512 / 286).downloads and update_frequency exist only for Socrata.columns_status: not-fetched and fill in on later runs.Read before any adapter was written; details and quotes in docs/terms.md.
docs/scout-mapping.mdplatform_family): docs/upstream/MIT