A small Model Context Protocol server that lets an LLM ask grounded questions about WHO public-health data: indicator timeseries from the Global Health Observatory (GHO) and recent stories from Disease Outbreak News (DON).
Not affiliated with the World Health Organization. This is an independent open-source project that consumes WHO's public APIs. WHO content is used under CC BY 4.0 and every tool response carries an attribution line.
Asking an LLM about an outbreak in country X tends to produce confident-sounding case counts and stale baselines. This server gives the model a small, opinionated set of tools so it can:
- look up an indicator code instead of guessing one,
- pull the country's actual GHO timeseries,
- compare the latest value against a prior-years baseline,
- surface recent DON items with a real public URL,
- pull regex/heuristic case-and-CFR hints out of DON narratives — with confidence flags so the model knows when not to quote them,
- read the published INFORM Risk Index for a country (UN OCHA / EC JRC, via HDX HAPI) instead of inventing a composite.
Intended for research, teaching, and desk analysis.
Requires uv and Python 3.10+ (the repo pins 3.11 via .python-version).
uv syncuv run who-sentinel-mcpThe process speaks MCP over stdio, so don't run it interactively in a terminal — point an MCP host at it.
{
"mcpServers": {
"who-sentinel": {
"command": "uv",
"args": ["run", "who-sentinel-mcp"],
"cwd": "/absolute/path/to/who-sentinel-mcp"
}
}
}docker build -t who-sentinel-mcp .
docker run -i --rm who-sentinel-mcp| Tool | What it does |
|---|---|
search_gho_indicators |
Find IndicatorCode values by keyword. |
list_gho_countries |
Full WHO RegionCountry table, optional substring filter. |
resolve_gho_country |
Resolve a free-text country / ISO3 to a RegionCountry row. |
list_gho_indicator_dimensions |
Dimensions for one indicator (e.g. COUNTRY, YEAR). |
list_gho_entity_sets |
Browse the GHO /api catalog (~3000 entity sets). |
get_latest_outbreaks |
Recent DON items, optional disease keyword filter. |
get_full_report |
Full DON record by Id, plus public_url. |
extract_outbreak_metadata |
Regex/heuristic case & CFR hints from DON text. |
surveillance_synthesis |
DON narrative + GHO baseline for one disease/country. Refuses by default if the picked IndicatorCode shares no keywords with the disease query (code_validation); pass confirm_indicator=true to override. |
compare_gho_countries |
Same indicator across two countries. |
country_risk_index |
Latest INFORM Risk Index scores (UN OCHA / EC JRC, via HDX HAPI) for a country. |
get_server_limits |
Effective cache TTLs, rate-limit budget, in-process metrics. |
Resources (for agent context):
who-sentinel://docs/gho-basicswho-sentinel://docs/limitationswho-sentinel://docs/trust-and-use
Prompt: who_sentinel_workflow — a short suggested workflow for agents.
| Variable | Default | Meaning |
|---|---|---|
WHO_SENTINEL_CACHE_TTL |
86400 |
GHO series / indicator cache TTL (seconds). |
WHO_SENTINEL_COUNTRY_REGISTRY_TTL |
604800 |
TTL for the RegionCountry + /api catalog. |
WHO_SENTINEL_LOG_LEVEL |
WARNING |
Stdlib log level. DEBUG includes Python tracebacks in tool error payloads. |
WHO_SENTINEL_MAX_HTTP_PER_MINUTE |
120 |
Token-bucket cap on outbound GETs. 0 = unlimited. |
WHO_SENTINEL_GHO_BASE |
https://ghoapi.azureedge.net/api |
GHO OData base URL. |
WHO_SENTINEL_DON_BASE |
https://www.who.int/api/news/diseaseoutbreaknews |
DON OData base URL. |
WHO_SENTINEL_WHO_WEB_BASE |
https://www.who.int |
Base used when building DON public_urls. |
WHO_SENTINEL_HDX_HAPI_BASE |
https://hapi.humdata.org |
HDX HAPI base URL for country_risk_index. |
WHO_SENTINEL_HDX_APP_ID |
(unset) | Required for country_risk_index. Generate one at the HAPI sandbox. |
GETs retry on 408, 429, 502, 503, 504 with exponential backoff and respect Retry-After.
- Resolve the country via
resolve_gho_country/list_gho_countries. - If
indicator_codeis omitted, the response carriesindicator_candidates(curated hints first, then live GHO search) so the model can pick one and call back. - With an
indicator_code, the default baseline is latest year vs the mean of up to 10 prior years in that GHO series. Override withprior_years_for_baseline(1–20) and/orbaseline_latest_yearto anchor the "latest" point. - The
latestblock surfacesgho_last_updated,period_begin/period_end, andvalue_low/value_highwhen WHO publishes them. - DON excerpts include a real
public_url(https://www.who.int+ItemDefaultUrl). - Every payload carries
who_sentinel_meta.disclaimernext to the CC BY attribution line.
- GHO uses per-indicator entity sets.
surveillance_synthesisadds acode_validationfield and refuses by default when the IndicatorName shares no keywords with the disease query, but a high-confidence pick can still be wrong —code_provenanceshows where the code came from; verify viasearch_gho_indicatorsandlist_gho_indicator_dimensions. - Disease hints come from
data/disease_hints.json— a small editorial overlay merged with an auto-generated snapshot of the live GHOIndicatorcatalog (refreshed weekly byscripts/refresh_disease_hints.py). Still starting points, not authoritative. - DON country matching is text-based. We expand each country into pycountry names + an alias overlay (e.g.
DRC,Burma,Côte d'Ivoire), and ISO3 codes use word boundaries, but narratives can still use subnational place names. extract_outbreak_metadatauses regex heuristics. Inspectconfidenceandcfr_source(explicit_textvsderived_or_none) before quoting numbers.country_risk_indexis a thin passthrough of the published INFORM Risk Index (CC BY 4.0); we don't compute composites of our own. SetWHO_SENTINEL_HDX_APP_IDto enable it.
uv sync --all-groups
uv run ruff check .
uv run pytestCI runs uv sync --locked, ruff, and pytest. Integration-style tests use mocked HTTP only — no live WHO calls in CI.
Code is released under the MIT License. WHO data is used under CC BY 4.0; see the attribution line prefixed on every tool response.