
Connects Claude to the CDC's Socrata SODA API across 1,487+ public health datasets covering mortality, vaccinations, NNDSS surveillance, and behavioral risk factors. Three tools handle the full workflow: discover datasets by keyword or category, fetch column schemas with row counts and types, then execute SoQL queries with filtering, aggregation, and full-text search. All responses return as strings per SODA v2.1 spec, so you'll need the schema metadata to parse correctly. Ships with a guided five-step prompt for trend analysis and two resources for quick dataset orientation. No auth required for basic use, optional app token bumps rate limits. Available as a public hosted instance or self-hosted via stdio, Docker, or Streamable HTTP.
claude mcp add --transport http cdc-health https://cdc.caseyjhand.com/mcpRun in your terminal. Add --scope user to make it available in every project.
Review the command, arguments, and environment values before installing — MCP servers run with your local permissions.
Verified live against the running server on Jun 10, 2026.
cdc_discover_datasetsSearch the CDC dataset catalog by keyword, category, or tag. Returns dataset IDs, names, descriptions, column lists, and update timestamps.5 paramsSearch the CDC dataset catalog by keyword, category, or tag. Returns dataset IDs, names, descriptions, column lists, and update timestamps.
categorystringlimitintegeroffsetintegerquerystringtagsarraycdc_get_dataset_schemaFetch the full column schema for a CDC dataset — names, data types, descriptions, row count, and last-updated timestamp. Get dataset IDs from cdc_discover_datasets.1 paramsFetch the full column schema for a CDC dataset — names, data types, descriptions, row count, and last-updated timestamp. Get dataset IDs from cdc_discover_datasets.
datasetId*stringcdc_query_datasetExecute a SoQL query against any CDC dataset. Supports filtering, aggregation, sorting, full-text search, and field selection. Use cdc_discover_datasets to find dataset IDs and cdc_get_dataset_schema to inspect columns before querying.9 paramsExecute a SoQL query against any CDC dataset. Supports filtering, aggregation, sorting, full-text search, and field selection. Use cdc_discover_datasets to find dataset IDs and cdc_get_dataset_schema to inspect columns before querying.
datasetId*stringgroupstringhavingstringlimitintegeroffsetintegerorderstringsearchstringselectstringwherestringSearch and query CDC public health data — mortality, vaccinations, surveillance, behavioral risk (Socrata SODA API) via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://cdc.caseyjhand.com/mcp
CDC public health data from two systems: the Socrata-based CDC Open Data portal and CDC WONDER, a separate CDC system for national mortality statistics. Search the catalog, inspect dataset schemas, and run SoQL queries across vaccination, surveillance, and behavioral-risk data, or query WONDER for deaths, population, and death rates by year, age, sex, and race. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
cdc_discover_datasets | Search the CDC dataset catalog by keyword, category, or tag |
cdc_get_dataset_schema | Fetch column schema, row count, and metadata for a dataset |
cdc_list_catalog_vocabulary | List the catalog's category and tag values with their entry counts |
cdc_query_dataset | Execute SoQL queries — filter, aggregate, sort, full-text search, select fields |
cdc_query_wonder | Query CDC WONDER for national mortality, population, and death rates across five databases |
| Resource | Description |
|---|---|
cdc://datasets | Top 50 most-viewed catalog entries, for orientation |
cdc://datasets/{datasetId} | Dataset metadata and the first 100 columns for a specific dataset |
Both resources mirror data also reachable via cdc_discover_datasets and cdc_get_dataset_schema, for clients that surface resources but not tools.
| Prompt | Description |
|---|---|
analyze_health_trend | Guided workflow for investigating a public health question across CDC data |
cdc_discover_datasets toolquery, category, and tags filters (tags union, the other two intersect with them); up to 100 results per page (default 10; a larger limit, up to 10,000, is capped at 100 and the notice says so with the offset to continue from), with offset + limit capped at 10,000 (page_out_of_range past it); order is dataset_id (default, stable paging) or relevanceassetType, columnCount (0 marks a chart, map, story, file, or link that yields no data), an 8-name columnSample, and a description cut to 300 characters (cdc_get_dataset_schema with full_description: true returns the whole text); enrichment adds totalCount and appliedFilterscategory or tags value matches nothing, the notice names the closest catalog values with their entry counts, e.g. Vaccination → "Vaccinations" (89 datasets)cdc_get_dataset_schema tooldatasetId (e.g. bi63-dtpu); column_limit (default 100, any larger value up to the whole schema) and column_offset page wide schemas, reporting totalCount, truncated, and nextOffsetdescription is capped at 4,000 characters by default, ending in … with descriptionTruncated and descriptionLength set when cut; full_description: true returns the whole text. Column descriptions are never shortenedfieldName, dataType, and description; rowCountSource says whether rowCount is a live count or Socrata's cached figure, which can lag an active dataset. A non-tabular asset fails with not_queryablecdc_list_catalog_vocabulary tooltag_limit (default 50; a value at or above the tag count returns them all) and tag_offset. filter (3+ characters) narrows both by word containment in either direction, not fuzzy matchingdatasetCount; enrichment reports vocabularySize, categoryCount, tagCount, truncated, and nextOffset, with truncationCeiling bounding the count of every tag after the page — the tags before tag_offset rank at or above the first tag showncdc_query_dataset toolselect, where, group, having, order, and full-text search; up to 5,000 rows per call (default 100; a larger limit is capped at 5,000 and says so), offset up to 1,000,000. Pair offset paging with an order, set from the first page, ending in a per-row tie-breaker: :id on ungrouped rows, the grouped columns on a grouped query. When the order lacks one, the notice says to restart from offset 0 with iteffectiveQuery echoes the SoQL clauses as sent, unencoded; truncated and nextOffset come from a one-row over-fetch, with no totalCount. A chart or map ID fails with not_queryablecdc_query_wonder tooldatabase | CDC database | Years | Race groups | mcd_icd10 |
|---|---|---|---|---|
underlying_1999_2020 (default) | D76 — Underlying Cause of Death | 1999–2020 | 4 bridged | — |
provisional | D176 — Provisional Mortality Statistics | 2018 → current year | 6 single-race | yes |
underlying_2018_2024 | D158 — Underlying Cause of Death, Single Race | 2018–2024 | 6 single-race | — |
multiple_1999_2020 | D77 — Multiple Cause of Death | 1999–2020 | 4 bridged | yes |
multiple_2018_2024 | D157 — Multiple Cause of Death, Single Race | 2018–2024 | 6 single-race | yes |
group_by takes 1–4 of year, age_group, sex, race, national only. cause_icd10 and mcd_icd10 take an ICD-10 code, a chapter or block range (X40-X49), or a list of up to 50 matched as one union (an empty list reads as all causes). age_groups takes NS (age not stated) alone or with all eleven known groups, never beside only some of them, which CDC refuses. limit (any positive integer; the response budget bounds the page) and offset (max 10,000) page the table, and an empty page's notice says whether nothing matched or the offset ran past the enddeaths, population, crude_rate, and age_adjusted_rate where age can be standardized. Cells CDC returns as Suppressed, Unreliable, or Not Applicable read null and are listed in cellNotes; messages reports whole rows CDC hid, and totalCount is exactcdc://datasets resourceid, name, assetType, category, columnCount, and updatedAt, plus the catalog totalCountcolumnCount: 0 marks a non-tabular entry; cdc_discover_datasets searches and pages the full catalogcdc://datasets/{datasetId} resourceapplication/json, with columnCount and truncated; the description is capped at 4,000 characters, with descriptionTruncated and descriptionLength set when cutcdc_get_dataset_schema and column_offset, and read a cut description whole with its full_description: true. The notice names both when they applyanalyze_health_trend prompttopic required; timeRange and geography optionalcdc_query_wonder (national mortality) or the Socrata tools (everything else), then walks discover → inspect → baseline → compare → synthesizeBuilt 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.
CDC-specific:
domain input, allowlisted to data.cdc.gov (default) and chronicdata.cdc.gov. Both front the same catalog, so it picks the host that answers, not the datasets you can reachcategory and tags match the catalog's controlled vocabularies (categories case-sensitively, tags case-insensitively), so a near miss returns nothing; cdc_list_catalog_vocabulary lists the real valuesyear is numeric in some datasets and text in others. Match WHERE literals to the column's dataType from the schema: numbers bare, strings single-quotedAgent-friendly output:
totalCount, truncated, nextOffset, and shown/cap. Both query tools bound each page by a 200,000-character budget over structuredContent and content[] together, so a wide page can end short of limitnot_queryable, page_out_of_range, and invalid_querycellNotes, hidden-row notices in messages, and rowCountSource on schema row countseffectiveQuery echoes the SoQL clauses or a WONDER query summary, so a result can be reproducedA public instance is available at https://cdc.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"cdc-health-mcp-server": {
"type": "streamable-http",
"url": "https://cdc.caseyjhand.com/mcp"
}
}
}
Add the following to your MCP client configuration file.
{
"mcpServers": {
"cdc-health-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/cdc-health-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"cdc-health-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/cdc-health-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"cdc-health-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/cdc-health-mcp-server:latest"]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
git clone https://github.com/cyanheads/cdc-health-mcp-server.git
cd cdc-health-mcp-server
bun install
cp .env.example .env
# optionally set CDC_APP_TOKEN
| Variable | Description | Default |
|---|---|---|
CDC_APP_TOKEN | Socrata app token; raises rate limits. | none |
CDC_BASE_URL | SODA host for requests that name no domain, in practice only the cdc://datasets/{datasetId} resource. The Socrata tools choose their host with domain. | https://data.cdc.gov |
CDC_CATALOG_URL | Socrata Discovery API base URL. | https://api.us.socrata.com/api/catalog/v1 |
MCP_TRANSPORT_TYPE | Transport: stdio or http. | stdio |
MCP_HTTP_PORT | HTTP server port. | 3010 |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto. | stateless |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth. | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.). | info |
OTEL_ENABLED | Enable OpenTelemetry. | false |
See .env.example for the full list of optional overrides.
Build and run the production version:
# One-time build
bun run rebuild
# Run the built server
bun run start:http
# or
bun run start:stdio
Run checks and tests:
bun run devcheck # Lints, formats, type-checks, and more
bun run test # Runs the test suite
docker build -t cdc-health-mcp-server .
docker run --rm -p 3010:3010 cdc-health-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/cdc-health-mcp-server. OpenTelemetry peer dependencies are installed by default; build with --build-arg OTEL_ENABLED=false to omit them.
| Directory | Purpose |
|---|---|
src/index.ts | createApp() entry point — registers tools/resources/prompts and inits services. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Four Socrata tools and one WONDER tool. |
src/mcp-server/resources | Resource definitions. Catalog overview and dataset detail. |
src/mcp-server/prompts | Prompt definitions. Health trend analysis workflow. |
src/services/socrata | Socrata service — catalog search, vocabularies, metadata, SoQL queries. |
src/services/wonder | CDC WONDER service — XML request builder, response parser, request spacing. |
src/utils | Shared helpers — HTML-to-text, table-cell escaping, vocabulary matching, response budget. |
tests/ | Unit and integration tests mirroring src/. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for logging, ctx.state for storagecreateApp() arrays in src/index.tsIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
CDC_APP_TOKENSocrata app token for higher rate limits.
MCP_LOG_LEVELdefault: infoSets the minimum log level for output (e.g., 'debug', 'info', 'warn').
MCP_HTTP_HOSTdefault: 127.0.0.1The hostname for the HTTP server.
MCP_HTTP_PORTdefault: 3010The port to run the HTTP server on.
MCP_HTTP_ENDPOINT_PATHdefault: /mcpThe endpoint path for the MCP server.
MCP_AUTH_MODEdefault: noneAuthentication mode to use: 'none', 'jwt', or 'oauth'.