census-mcp-server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@census-mcp-serverGet median household income for all counties in Texas from ACS5 2022"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 |
| Browse available Census Bureau datasets (ACS5 and ACS1 with their profile, subject, and comparison tables, ACS Supplemental Estimates and Selected Population Profiles, Population Estimates, Decennial, County Business Patterns, Economic Census, Nonemployer Statistics) with vintage years and dataset codes. |
| List the geography levels supported by a dataset and year, with parent requirements and example FIPS values. |
| Keyword search across variable labels and concept groups. On ACS, returns estimate and margin-of-error codes together. |
| Fetch full metadata for one or more variable codes — label, concept, predicate type, universe, MOE sibling — including the annotation and flag columns the data tools accept. |
| List the codes a filter dimension accepts ( |
| Convert place names (e.g., "King County, WA"), ZIP codes, or street addresses to Census FIPS identifiers via TIGERweb and Census Geocoder. |
| Query a Census dataset for variables at a specific geography. Returns estimates with MOE, Census sentinel values and withheld business values resolved to their published meanings, and predicate filtering for the business datasets. |
| 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. |
Related MCP server: Census MCP Server
Capability reference
census_list_datasets tool
Returns dataset codes, names, descriptions, and available vintage years
Covers ACS5 with its Data Profiles, Subject Tables, and Comparison Profiles; ACS1 with its Data Profiles, Subject Tables, Comparison Profiles, and Selected Population Profiles (
acs/acs1/spp); ACS 1-Year Supplemental Estimates (acs/acsse); Population Estimates; the 2020 Decennial files — Redistricting (P.L. 94-171), DHC (dec/dhc), Demographic Profile (dec/dp), Supplemental DHC (dec/sdhc), and DDHC-A (dec/ddhca); 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. Every tool ignores their case and takes a two-part code by its last part alone (acs5isacs/acs5,plisdec/pl), echoing the resolved code; three-part codes such asacs/acs5/profilemust be given in full, and a bareprofilefails naming the codes that end in itavailable_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, thecbp/nonempvintages left out reject theNAMEcolumn every query here sends, and the Census API answersacs/acs1/spp2008 and 2010 with server errors
census_list_geographies tool
Returns one row per geography level —
geography_level, whether a parent is required,required_parent_levels, and an example FIPS valuegeography_levelvalues are the exact inputs togeography_levelincensus_query_dataandcensus_compare_geographiesyeardefaults to the dataset's latest available vintagedataset_not_foundwhen the dataset code is blank or unrecognized;year_not_availablewhen the dataset has no geography data for the requested year
census_search_variables tool
Whole-word search across label and concept: every query word must match (
ratenever matches "separated"), and when no variable contains them all, the variables with the most words come back with a notice saying soRanked by where the query appears — the label's last
!!segment or the whole concept equal to it first, then the phrase in label and concept, label only, concept only — then by fewer!!segments, shorter concept, and code, so a table total leads its breakdown rows and an estimate leads its margin of errorA column shared across tables, such as
GEO_ID, is matched on its label only and returned without a conceptOn ACS datasets, returns estimate (E suffix) and margin-of-error (M suffix) codes together so both can be requested in one query — the ACS comparison profiles and the other families publish no margins of error, and an E-final code there is an ordinary code. A margin's label is the one the Census publishes (
Margin of Error!!Median household income…), and search matches a margin on its estimate's label, so the two rank side by sideAlso surfaces the predicate codes a dataset filters on, such as
NAICS2017incbplimitis an integer from 1 to 100 (default 20) — out-of-range values are rejected, not clamped;totalMatchessays how many matched before the limitCache-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 (trimmed and matched regardless of case, then echoed in the dataset's own spelling) and returns metadata in the same order — label, concept, predicate type, and the table's universe when its
groups.jsonentry publishes oneResolves annotation and flag columns such as
B19013_001EAandEMP_Ffrom the Census per-variable endpoint, withattribute_ofnaming the column each belongs to andattribute_typeits kindA column shared across tables, such as
GEO_ID, carries no concept — its published one joins every table'sOn ACS datasets, returns
estimate_code/moe_codesibling references, and a margin-of-error code carries its published label withattribute_ofnaming its estimate andattribute_typeMARGIN_OF_ERROR; the comparison profiles and the other families publish no margins of error and carry none of theseAlso resolves predicate/filter dimension codes (e.g.,
NAICS2017,SEX) to confirm a dimension exists in a dataset —census_list_predicate_valueslists the values it acceptsdatasetdefaults 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 appearA 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 withheldKeyword
querymatches code and label; results are sorted by code and a truncated list is disclosed rather than passed off as complete (limitan integer from 1 to 500, default 50;totalCountsays how many matched)ecnbasicpublishesTAXSTATandTYPOPper industry, sowithin_naicsscopes the enumeration — and the notice says the result is complete for that industry aloneLive 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, with the address's
block_group_fipsand incorporatedplace_fipsalongsideThe place level covers incorporated places and census-designated places together ("Bethesda, MD" → Bethesda CDP, flagged
census_designated_place). A CDP answers a name only when no incorporated place or county has it exactly, so "Paradise, CA" is Paradise town and "Arlington, VA" Arlington County; a CDP's full name ("Arlington CDP, VA") orgeography_type: "place"reaches itA 5-digit ZIP (or ZIP+4) resolves to its ZIP Code Tabulation Area (
zip code tabulation area) — the ACS's ZIP-shaped area, notcbp'szip codelevel, which takes the ZIP itself with no resolutionThe state after a comma can be an abbreviation in either case, a full name ("Chatham County, Georgia"), or a hyphenated list ("NE-IA", scoped by its first state)
Auto-detects
geography_typefor state, county, place, tract, and ZIP; metropolitan/micropolitan statistical areas, combined statistical areas, consolidated cities, and economic places are never auto-detected and need an explicitgeography_type, since their names overlap city nameseconomic placereturns the 8-digit codeecnbasic2022 publishes a place under — its county, or000when it spans counties, then its place code (Seattle03363000, Auburn, WA00003180)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 addressMatching ignores case. A name no level matches is retried with Saint/St. respelled ("Saint Louis, MO") and with accents ignored ("Dona Ana County, NM"); a statistical area is also retried by its leading city, so a name from an earlier delineation ("Denver-Aurora-Lakewood, CO") still resolves
Prefers an exactly-named match over a partial one (e.g., "Kansas City, MO" does not resolve to North Kansas City), across levels too ("King, WA" is King County, not Kingston CDP)
A name matching more than one geography returns
ambiguous_name, with every candidate's FIPS code and the state that separates themReturns
state_fips(→parent_fips) andfips_summary(→geography_fips) ready to pass to other tools; a statistical area omitsstate_fipssince it can span several states, and a ZCTA omits it because its source layer carries no state
census_query_data tool
Requires FIPS codes (use
census_resolve_geographyfor place names);geography_fips: "*"returns every geography at the level within the parent, and each row carries bothgeography_fipsand the nationally-uniquegeography_geoidA wildcard returns up to
limitrows (default 50, max 500) in GEOID order, andoffsetpages through the rest;totalCountandtruncatedsay how many rows matched, and the notice names the range returned and the nextoffset. Every row counts, including eachpep/charvrecord and each category of a"*"predicateUp to 49 variable codes per call, fewer on datasets where label or record columns are added: the Census API accepts 50 columns per request and every query also sends
NAME.too_many_variablesstates the exact maximum before any request goes out. Codes are case-insensitive, and an unknown one isvariable_not_foundLevel 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 400Optional
tract_fips(exactly 6 digits, with a concretecounty_fips) scopes a block-group or decennial block query to one tract, so the block group around an address is one call:block group2in53/033/007101Optional
predicatesmap filters the business/pep/decdatasets andacs/acs1/spp(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. Keys are case-insensitive and a blank value counts as omitted;"*"returns one row per category, each labelled inrecordA dataset that publishes more than one record per geography (
pep/charv) returns multiple rows, each carrying arecordfield; pin one withpredicates(e.g.,{"MONTH": "7"})ACS sentinel values resolve to the Census's published meanings, a controlled estimate's margin of error reads as
0, and a median in an open-ended interval is flaggedopen_ended. Oncbp,ecnbasic, andnonemp, a value the Census withheld (stored as0beside a flag such asD) is reported as suppressed with the flag's meaning. A nullestimatemeans the value is either suppressed, a text cell (returned undervalue), or genuinely emptyRequires
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 comparisonRanks on one variable's value:
sort_by(default the first code; it must be one of the requested codes, or the call fails withsort_by_not_requested),sort_dir(defaultdesc), andlimit(an integer from 1 to 500, default 50);totalCountreports how many geographies matched before the limitA count ranks by size, not rate — rank a published percentage for a rate, e.g.
S1701_C03_001E(percent below poverty,acs/acs5/subject) orDP04_0047PE(percent renter-occupied,acs/acs5/profile), both available down to tractSame
predicatesmap, variable limit, geography validation, andapplied_filtersdefault-echoing ascensus_query_data, applied to every geography in the rankingA dataset that publishes more than one record per geography (
pep/charv), or a"*"predicate, fails withambiguous_rowsunlesspredicatespins one (e.g.,{"MONTH": "7"})Suppressed values carry the same reasons as
census_query_dataand sort to the end in either direction, withheld business values included; a text value has no ordering, so sorting on it leaves rows tied, and the notice says the rows are not rankedRequires
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 toolSuppression codes decoded — Census negative sentinel values (e.g.,
-666666666) and business-dataset withholding flags (e.g.,D) surfaced as their published meanings instead of raw numbers or false zerosRecovery 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/mcpPrerequisites
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.gitNavigate into the directory:
cd census-mcp-serverInstall dependencies:
bun installConfigure environment:
cp .env.example .env
# edit .env and set CENSUS_API_KEYConfiguration
Variable | Description | Default |
| Required for data queries. Register free at api.census.gov/data/key_signup.html. | — |
| Default vintage year when no year is specified. |
|
| Hours to cache variables.json per dataset+year in memory. |
|
| Transport: |
|
| HTTP session mode: |
|
| Port for HTTP server. |
|
| Auth mode: |
|
| Log level ( |
|
| Enable OpenTelemetry instrumentation. |
|
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:httpRun 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 specDocker
docker build -t census-mcp-server .
docker run --rm -e CENSUS_API_KEY=your-key -p 3010:3010 census-mcp-serverThe 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 |
|
|
| Census-specific env var parsing and validation with Zod. |
| Tool definitions ( |
| Census Data API client — data queries, suppression code mapping, retry logic. |
| Geography resolution — TIGERweb named-place lookup and Census Geocoder address-to-tract. |
| In-process variables.json cache with TTL and keyword search. |
| Vitest tests mirroring |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
Handlers throw, framework catches — no
try/catchin tool logicUse
ctx.logfor request-scoped logging,ctx.statefor tenant-scoped storageRegister new tools via the barrel in
src/mcp-server/tools/definitions/index.tsWrap 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 testLicense
Apache-2.0 — see LICENSE for details.
This server cannot be deployed
Maintenance
Related MCP Connectors
Census MCP — U.S. Census Bureau housing-relevant APIs.
FCC public geo/census APIs (geo.fcc.gov) MCP.
MCP server for US nursing facility search and ownership lookup (NursingHomeDatabase).
Hosted MCP servers for US federal data: 18 servers, 142 tools, one endpoint.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables access to U.S. Census Bureau data including demographics, population, income, and housing statistics. Users can query specific variables, search datasets, and retrieve geographic FIPS codes across various surveys like the American Community Survey and Decennial Census.1-
- AlicenseAqualityCmaintenanceNode.js MCP server enabling AI agents to access comprehensive U.S. Census Bureau demographic and economic data, including ACS, decennial census, and business patterns.1720 npmMIT
- FlicenseNot gradedqualityCmaintenanceA production-grade MCP server for querying U.S. Census Bureau data (ACS 5-Year and Decennial) with tools for geographic fuzzy matching, variable search, and batched data retrieval, backed by a PostgreSQL cache for performance.-
- AlicenseAqualityDmaintenanceAn MCP server that exposes the IPUMS API as LLM tools for browsing metadata, creating and downloading extracts, and generating reproducible R/Python code.23MIT