@cyanheads/census-mcp-server
MCP serverEverything elseQuery U.S. Census Bureau data, variables, and geography via MCP.
Available today. Use it from your connected AI after setup.
Needs your own Census account. Credentials stay encrypted.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use @cyanheads/census-mcp-server
From the project's README
As published by cyanheads/census-mcp-server in README.md.
Public Hosted Server: https://census.caseyjhand.com/mcp
Overview
U.S. Census Bureau data — datasets, variables, and geography — via the Census Data API, TIGERweb, and the Census Geocoder. Discover datasets and variables, resolve place names or addresses to FIPS codes, and query or rank demographic, economic, and housing estimates across geographies from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
Tools
| Tool | Description |
|---|---|
census_list_datasets | Browse available Census Bureau datasets (ACS5, ACS1, Population Estimates, Decennial, County Business Patterns, Economic Census, Nonemployer Statistics) with vintage years and dataset codes. |
census_list_geographies | List the geography levels supported by a dataset and year, with parent requirements and example FIPS values. |
census_search_variables | Keyword search across variable labels and concept groups. On ACS, returns estimate and margin-of-error codes together. |
census_get_variable | Fetch full metadata for one or more variable codes — label, concept, predicate type, universe, MOE sibling. |
census_list_predicate_values | List the codes a filter dimension accepts (EMPSZES, LFO, POPGROUP, NAICS2017…), from the dataset dictionary or a live wildcard enumeration. |
census_resolve_geography | Convert place names (e.g., "King County, WA") or street addresses to Census FIPS identifiers via TIGERweb and Census Geocoder. |
census_query_data | Query a Census dataset for variables at a specific geography. Returns estimates with MOE, suppression codes resolved to readable reasons, and predicate filtering for the business datasets. |
census_compare_geographies | Rank and compare variables across multiple geographies — all counties in a state, all states nationally, or a named set. Sorted table output, with the same predicate filtering. |
Capability reference
census_list_datasets tool
- Returns dataset codes, names, descriptions, and available vintage years
- Covers ACS5, ACS5 Data Profiles, ACS5 Subject Tables, ACS1, ACS1 Data Profiles, Population Estimates, Decennial Redistricting (P.L. 94-171), Decennial DHC, County Business Patterns (
cbp), Economic Census (ecnbasic), and Nonemployer Statistics (nonemp) - Each description names the filter predicates the dataset requires and the geography levels it publishes — both vary by dataset
- Accepts an optional keyword filter
- Dataset codes (e.g.,
acs/acs5) are the values to pass to other tools available_yearsis exhaustive, not a sample: any other year fails withyear_not_availablebefore a request goes out, naming the years that do work. It is narrower than what the Census API hosts —pep/charvreaches its 2020-2022 estimates through theYEARfilter inside the 2023 vintage, and thecbp/nonempvintages left out reject theNAMEcolumn every query here sends
census_list_geographies tool
- Returns one row per geography level —
geography_level, whether a parent is required,required_parent_levels, and an example FIPS value geography_levelvalues are the exact inputs togeography_levelincensus_query_dataandcensus_compare_geographiesyeardefaults to the dataset's latest available vintagedataset_not_foundwhen the dataset code is unrecognized;year_not_availablewhen the dataset has no geography data for the requested year
census_search_variables tool
- Full-text search across label and concept fields with relevance scoring (exact concept match > label match > partial)
- On ACS datasets, returns estimate (E suffix) and margin-of-error (M suffix) codes together so both can be requested in one query — no other family publishes margins of error, and an E-final code there is an ordinary code
- Also surfaces the predicate codes a dataset filters on, such as
NAICS2017incbp - Configurable limit (default 20, max 100);
total_matchesindicates how many matched before the limit - Cache-backed: variables.json is fetched once per dataset+year with a configurable TTL (default 24h)
census_get_variable tool
- Accepts one or more variable codes (case-sensitive) and returns metadata in the same order — label, concept, predicate type, and universe when the dataset publishes one
- On ACS datasets, returns
estimate_code/moe_codesibling references; other families publish no margins of error and carry neither field - Also resolves predicate/filter dimension codes (e.g.,
NAICS2017,SEX) to confirm a dimension exists in a dataset —census_list_predicate_valueslists the values it accepts datasetdefaults toacs/acs5,yeardefaults to the dataset's latest available vintagevariable_not_foundwhen a code isn't defined in the dataset and year
census_list_predicate_values tool
- Two routes, picked by where the answer lives: a dimension with a published value list is read from the dataset dictionary, one without is enumerated live by wildcarding it on the data endpoint.
NAICS*andPOPGROUPalways publish one (thousands of codes — narrow them withquery); on the current vintagesEMPSZES,LFO,RCPSZES,TAXSTAT, andTYPOPpublish none, so the live route is the only place their codes appear - A dictionary value list is a classification shared across Census products, not a record of what one dataset serves —
dec/ddhcadeclares 5,543POPGROUPcodes and publishes 2,996,cbpdeclares 6,694NAICS2017codes and publishes 2,003. The declared list is checked against the dataset's own published rows and the dead codes are dropped;sourcesays whether that check ran and the notice says how many were withheld - Keyword
querymatches code and label; results are sorted by code and a truncated list is disclosed rather than passed off as complete (default limit 50, max 500) ecnbasicpublishesTAXSTATandTYPOPper industry, sowithin_naicsscopes the enumeration — and the notice says the result is complete for that industry alone- Live enumerations are cached per dataset, year, dimension, industry scope, and probe measure
census_resolve_geography tool
- Named places (e.g., "King County, WA") resolve via TIGERweb; street addresses resolve to tract level via Census Geocoder
- Auto-detects
geography_typefor state, county, place, and tract; metropolitan/micropolitan statistical areas, combined statistical areas, and consolidated cities are never auto-detected and need an explicitgeography_type, since their names overlap city names - Optional
county_fipsscopes resolution to the county and tract levels only — required when a tract name matches more than one county;county_scope_unsupportedwhen paired with any other level or a street address - Prefers an exactly-named match over a partial one (e.g., "Kansas City, MO" does not resolve to North Kansas City)
- A name matching more than one geography returns
ambiguous_name, with every candidate's FIPS code and the state that separates them - Returns
state_fips(→parent_fips) andfips_summary(→geography_fips) ready to pass to other tools; a statistical area omitsstate_fipssince it can span several states
census_query_data tool
- Requires FIPS codes (use
census_resolve_geographyfor place names) and up to 50 variable codes per call;geography_fips: "*"returns every geography at the level within the parent, and each row carries bothgeography_fipsand the nationally-uniquegeography_geoid - Level and parent are checked against the dataset's own geography metadata before querying —
parent_requiredandparent_not_acceptedname what's missing or unaccepted rather than surfacing a raw Census 400 - Optional
predicatesmap filters the business/pep/decdatasets (e.g.,{"NAICS2017": "5112"}); a dimension left unset applies a Census-chosen default — an all-categories total on some datasets, a single category on others — echoed per row inapplied_filters - A dataset that publishes more than one record per geography (
pep/charv) returns multiple rows, each carrying arecordfield; pin one withpredicates(e.g.,{"MONTH": "7"}) - Suppression codes resolve to human-readable reasons; a null
estimatemeans the value is either suppressed, a text cell (returned undervalue), or genuinely empty - Requires
CENSUS_API_KEY
census_compare_geographies tool
- Ranks all geographies at a level, or a named
geographieslist of GEOIDs/bare level codes, in one call;within/within_countyscope to a state/county, omit for a national comparison - Configurable
sort_byvariable,sort_dir(defaultdesc), andlimit(default 50, max 500);total_countreports how many geographies matched before the limit - Same
predicatesmap, geography validation, andapplied_filtersdefault-echoing ascensus_query_data, applied to every geography in the ranking - A dataset that publishes more than one record per geography (
pep/charv) fails withambiguous_rowsunlesspredicatespins one (e.g.,{"MONTH": "7"}) - Suppressed values are labeled and sorted to the end rather than passed through as raw sentinels; a text value has no ordering, so sorting on it leaves rows tied
- Requires
CENSUS_API_KEY
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
Census-specific:
- In-process variable cache with configurable TTL — variables.json fetched once per dataset+year, searched client-side
- Three-API backend: Census Data API for data queries, TIGERweb for named-place resolution, Census Geocoder for address-to-tract
- Automatic retry with backoff on all external API calls
- FIPS formatting helpers — zero-padded state, county, and tract codes ready to pass between tools
Agent-friendly output:
- Workflow-oriented tool surface —
fips_summaryandstate_fipsreturn values are ready to pass asgeography_fipsandparent_fipsto the next tool - Suppression codes decoded — Census negative sentinel values (e.g.,
-666666666) surfaced as human-readable reasons instead of raw numbers - Recovery hints on errors — ambiguous geography names include candidate lists; missing API key errors include registration URL
Getting started
Public Hosted Instance
A public instance is available at https://census.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"census-mcp-server": {
"type": "streamable-http",
"url": "https://census.caseyjhand.com/mcp"
}
}
}
Self-Hosted / Local
API key: Register a free key at api.census.gov/data/key_signup.html. Variable search and geography resolution work without a key; data queries (
census_query_data,census_compare_geographies) require one.
Add the following to your MCP client configuration file:
{
"mcpServers": {
"census-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/census-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"CENSUS_API_KEY": "your-census-api-key"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"census-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/census-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"CENSUS_API_KEY": "your-census-api-key"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"census-mcp-server": {
"type": "stdio",
"command": "docker",
"args": [
"run", "-i", "--rm",
"-e", "MCP_TRANSPORT_TYPE=stdio",
"-e", "CENSUS_API_KEY=your-census-api-key",
"ghcr.io/cyanheads/census-mcp-server:latest"
]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 CENSUS_API_KEY=... bun run start:http
# Server listens at http://localhost:3010/mcp
Prerequisites
- Bun v1.4.0 or higher (or Node.js v24+).
- A Census API key — register free at api.census.gov/data/key_signup.html. Required for
census_query_dataandcensus_compare_geographies; other tools work without it.
Installation
- Clone the repository:
git clone https://github.com/cyanheads/census-mcp-server.git
- Navigate into the directory:
cd census-mcp-server
- Install dependencies:
bun install
- Configure environment:
cp .env.example .env
# edit .env and set CENSUS_API_KEY
Configuration
| Variable | Description | Default |
|---|---|---|
CENSUS_API_KEY | Required for data queries. Register free at api.census.gov/data/key_signup.html. | — |
CENSUS_DEFAULT_YEAR | Default vintage year when no year is specified. | 2024 |
CENSUS_VARIABLE_CACHE_TTL_HOURS | Hours to cache variables.json per dataset+year in memory. | 24 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_SESSION_MODE | HTTP session mode: stateful, stateless, or auto. The server declares stateless in src/index.ts; set this only to override it. | stateless |
MCP_HTTP_PORT | Port for HTTP server. | 3010 |
MCP_AUTH_MODE | Auth mode: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, notice, warning, error). | info |
OTEL_ENABLED | Enable OpenTelemetry instrumentation. | false |
See .env.example for the full list of optional overrides.
Running the server
Local development
# One-time build
bun run rebuild
# Run the built server
bun run start:stdio
# or
bun run start:http
Run checks and tests:
bun run devcheck # Lint, format, typecheck, security audit
bun run test # Vitest test suite
bun run lint:mcp # Validate MCP definitions against spec
Docker
docker build -t census-mcp-server .
docker run --rm -e CENSUS_API_KEY=your-key -p 3010:3010 census-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/census-mcp-server. OpenTelemetry peer dependencies are installed by default — build with --build-arg OTEL_ENABLED=false to omit them.
Project structure
| Path | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools and initializes services. |
src/config/server-config.ts | Census-specific env var parsing and validation with Zod. |
src/mcp-server/tools/definitions/ | Tool definitions (*.tool.ts). |
src/services/census-api/ | Census Data API client — data queries, suppression code mapping, retry logic. |
src/services/geography/ | Geography resolution — TIGERweb named-place lookup and Census Geocoder address-to-tract. |
src/services/variable-cache/ | In-process variables.json cache with TTL and keyword search. |
tests/ | Vitest tests mirroring src/ structure. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storage - Register new tools via the barrel in
src/mcp-server/tools/definitions/index.ts - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
Apache-2.0 — see LICENSE for details.
Advanced
- Delivery
- census-mcp-server MCP server → your ahel gateway (mcp.ahel.ai) → every connected AI client.
- Catalog kind
- mcp-server
- Gateway key
io-github-cyanheads-census-mcp-server- Source
- github.com/cyanheads/census-mcp-server
- Hosted endpoint
https://census.caseyjhand.com/mcp