Skip to main content
Glama
zenlyticgreg

census-mcp-server

by zenlyticgreg

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

get_census_data

Fetch ACS 5-Year / Decennial data with multi-variable chunked batching and geographic nesting (e.g. all tracts in a county).

search_census_variables

Full-text search over cached variable metadata (labels, concepts, table codes).

resolve_geography

Fuzzy-resolve place names → FIPS codes via a lazily hydrated pg_trgm cache.

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

acs5

ACS 5-Year Detailed Tables

2009–2023

acs5_profile

ACS 5-Year Data Profiles

2010–2023

acs5_subject

ACS 5-Year Subject Tables

2010–2023

dec_pl

Decennial Redistricting (P.L. 94-171)

2000, 2010, 2020

dec_dhc

Decennial DHC

2020

dec_sf1

Decennial Summary File 1

2000, 2010

Related MCP server: Eddie MCP Server

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, Markdown

Design notes:

  • Resilience — upstream 429/5xx/timeouts retry with exponential backoff + full jitter, honoring Retry-After. Errors map onto a typed CensusError taxonomy (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/healthz
  • Multi-stage build: TypeScript compiles in a build stage; the runtime stage ships only production node_modules + dist, running as a non-root mcp user.

  • Healthchecks: pg_isready for 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_pgdata volume.

  • MCP endpoint: POST http://localhost:3000/mcp (stateless JSON mode).

Scripts

Command

Description

npm run build

Compile TypeScript to dist/

npm start

Run the compiled server (env provided externally)

npm run start:local

Run the compiled server, loading .env

npm run dev

Watch-mode development server (tsx)

npm run lint

ESLint (strict: no any, explicit return types)

npm run typecheck

tsc --noEmit

npm run db:migrate

Apply pending schema migrations and exit

Example workflow

  1. resolve_geography{ "name": "San Francisco", "level": "county", "state": "CA" }state_fips: "06", geo_code: "075".

  2. search_census_variables{ "query": "median household income" }B19013_001E.

  3. 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 vars TRANSPORT=http, PORT=3000, LOG_LEVEL=info, CENSUS_API_KEY, MCP_AUTH_TOKEN, and DATABASE_URL set 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.app domain targets container port 3000. /mcp requires Authorization: Bearer <MCP_AUTH_TOKEN>; /healthz is 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.

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

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