census-mcp-server
census-mcp-server
Production-grade Model Context Protocol server for the U.S. Census Bureau Data API, backed by a PostgreSQL cache layer for FIPS geography mappings and variable metadata.
Tools
Tool | Purpose |
| Fetch ACS 5-Year / Decennial data with multi-variable chunked batching and geographic nesting (e.g. all tracts in a county). |
| Full-text search over cached variable metadata (labels, concepts, table codes). |
| Fuzzy-resolve place names → FIPS codes via a lazily hydrated |
Live deployment: https://census-mcp-server-production-4b30.up.railway.app/mcp (streamable HTTP,
Railway, bearer-token protected — see Deployed on Railway below).
All tools support response_format: 'markdown' | 'json', return structured content alongside text,
and are annotated read-only/idempotent.
Supported datasets
id | Dataset | Vintages |
| ACS 5-Year Detailed Tables | 2009–2023 |
| ACS 5-Year Data Profiles | 2010–2023 |
| ACS 5-Year Subject Tables | 2010–2023 |
| Decennial Redistricting (P.L. 94-171) | 2000, 2010, 2020 |
| Decennial DHC | 2020 |
| Decennial Summary File 1 | 2000, 2010 |
Architecture
src/
├── index.ts Bootstrap + transports (stdio / streamable HTTP)
├── server.ts McpServer factory (composition root)
├── config/env.ts Zod-validated environment config (fail-fast)
├── constants.ts Limits, sentinels, state abbreviations
├── errors/census-error.ts CensusError taxonomy + remediation hints
├── http/census-http-client.ts fetch wrapper: retries, backoff+jitter, timeouts
├── logging/logger.ts pino → stderr (stdout reserved for MCP frames)
├── schemas/tool-schemas.ts Zod input/output schemas for every tool
├── tools/ Controllers: schema ↔ service ↔ response shaping
├── services/ CensusApi / CensusData / Geography / VariableSearch / Database
├── repositories/ SQL access: geographies, variables, ingestions
├── db/ Embedded migrations + advisory-locked runner + CLI
└── utils/ Bounded concurrency, name normalization, MarkdownDesign notes:
Resilience — upstream 429/5xx/timeouts retry with exponential backoff + full jitter, honoring
Retry-After. Errors map onto a typedCensusErrortaxonomy (rate limits, invalid FIPS, invalid variables, missing API key, ...) with agent-actionable hints.Chunked batching — variable lists split into API-sized chunks (48/request), fetched with bounded concurrency, and merged on geography keys. Cache hydration writes in 1,000-row batched
unnest()upserts. Responses are row-capped (max_rows) and character-capped with explicit truncation flags.Cache layer — geography names (~32k places) and variable catalogs (~28k entries) hydrate lazily on first use, then serve locally: trigram-indexed fuzzy search for names, GIN-indexed full-text search for variables. Migrations are embedded, idempotent, and serialized with a PostgreSQL advisory lock.
Quickstart (local, stdio)
Requires Node 20+ and a reachable PostgreSQL 14+ instance.
npm install # also generates package-lock.json (needed for Docker builds)
cp .env.example .env # set CENSUS_API_KEY and DATABASE_URL
npm run build
npm run db:migrate # optional; the server also migrates at startup
npm run start:local # loads .env via node --env-file(npm start runs without loading .env — it expects the environment to be
provided externally, as in Docker or an MCP client config.)
Get a free API key at https://api.census.gov/data/key_signup.html.
Claude Desktop / Claude Code registration
{
"mcpServers": {
"census": {
"command": "node",
"args": ["/absolute/path/to/census-mcp-server/dist/index.js"],
"env": {
"CENSUS_API_KEY": "your-key",
"DATABASE_URL": "postgres://census:change_me@localhost:5432/census_cache"
}
}
}
}Docker deployment (streamable HTTP)
npm install # generate package-lock.json before the first image build
cp .env.example .env # set CENSUS_API_KEY and POSTGRES_PASSWORD
docker compose up --build -d
curl http://localhost:3000/healthzMulti-stage build: TypeScript compiles in a build stage; the runtime stage ships only production
node_modules+dist, running as a non-rootmcpuser.Healthchecks:
pg_isreadyfor PostgreSQL; a Node probe against/healthz(which verifies database connectivity) for the server. The server only starts after PostgreSQL is healthy.Network isolation: PostgreSQL lives on an internal-only network with no published ports; data persists in the
census_pgdatavolume.MCP endpoint:
POST http://localhost:3000/mcp(stateless JSON mode).
Scripts
Command | Description |
| Compile TypeScript to |
| Run the compiled server (env provided externally) |
| Run the compiled server, loading |
| Watch-mode development server (tsx) |
| ESLint (strict: no |
|
|
| Apply pending schema migrations and exit |
Example workflow
resolve_geography—{ "name": "San Francisco", "level": "county", "state": "CA" }→state_fips: "06",geo_code: "075".search_census_variables—{ "query": "median household income" }→B19013_001E.get_census_data—{ "variables": ["B19013_001E", "B01001_001E"], "geography": { "level": "tract", "codes": ["*"], "within": [ { "level": "state", "code": "06" }, { "level": "county", "code": "075" } ] } }
Deployed on Railway
The server is deployed at project census-mcp-server in Railway (workspace: Greg Peters's
Projects), built directly from this repo's Dockerfile on every push to master:
App service
census-mcp-server— env varsTRANSPORT=http,PORT=3000,LOG_LEVEL=info,CENSUS_API_KEY,MCP_AUTH_TOKEN, andDATABASE_URLset as a Railway reference variable (${{Postgres.DATABASE_URL}}) pointing at the sibling Postgres service — so the cache survives redeploys and the connection string is never duplicated.Postgres service — managed Railway PostgreSQL; migrations run automatically at container startup (see
src/db/migration-runner.ts).Public domain — a generated
*.up.railway.appdomain targets container port 3000./mcprequiresAuthorization: Bearer <MCP_AUTH_TOKEN>;/healthzis open for Railway's health probes.
To redeploy: push to master (Railway auto-builds), or run railway redeploy --service census-mcp-server --yes --from-source to force a fresh pull. To rotate secrets: railway variable set CENSUS_API_KEY=<new-key> --service census-mcp-server (a set triggers a redeploy
unless --skip-deploys is passed).
Security notes
The API key is only read from the environment, appended per-request, and redacted from all log output.
Every tool input is validated by Zod schemas (strict bounds and patterns) before any I/O.
Internal errors are logged server-side and never leaked to MCP clients verbatim.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/zenlyticgreg/census-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server