gho-mcp
A Model Context Protocol (MCP) server that gives AI assistants like Claude direct access to the World Health Organization's Global Health Observatory (GHO) — purpose-built for comparative health systems research.
What it does
gho-mcp wraps the WHO GHO OData API (https://ghoapi.azureedge.net/api) in a small set of task-shaped MCP tools so an AI assistant can answer questions like:
- "Build me a comparative health systems profile for Peru."
- "Plot maternal mortality across the Andean countries since 2010."
- "What's the UHC service coverage gradient by World Bank income group?"
- "Does the tobacco indicator support sex disaggregation?"
Country names, ISO3 codes, WHO region codes (AFR, AMR, etc.), and World Bank income-group codes (WB_HI, WB_UMI, etc.) are all accepted interchangeably. CSV export is built in.
Why this exists
Raw API access for global health data is technically possible from any LLM — but in practice it's painful: indicator codes are cryptic (WHOSIS_000001 is "life expectancy at birth"), countries must be ISO3 codes, OData filter syntax is unforgiving, and every cross-country comparison becomes a loop. gho-mcp collapses the friction so an LLM can stay focused on the analysis.
The tool design reflects how comparative-health-systems researchers actually work: country profiles, regional and income-group benchmarks, sex-disaggregated time series, and metadata for citation.
Install
Requires Python 3.11 or newer. Check yours with python3 --version — on macOS, python3 from system Python is often 3.9, in which case install a current Python via brew install python (or pyenv) before continuing.
Install 0.7.0 from the versioned GitHub release asset. This route does not require the package to be available on PyPI:
python3 -m venv .venv
source .venv/bin/activate
python -m pip install "https://github.com/Decilion/gho-mcp/releases/download/v0.7.0/mcp_server_gho-0.7.0-py3-none-any.whl"
For an editable source installation:
git clone https://github.com/Decilion/gho-mcp.git
cd gho-mcp
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
Then register the server with your MCP client.
Claude Code:
claude mcp add gho /absolute/path/to/.venv/bin/gho-mcp
Claude Desktop: add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"gho": {
"command": "/absolute/path/to/.venv/bin/gho-mcp"
}
}
}
Codex CLI:
codex mcp add gho -- /absolute/path/to/.venv/bin/gho-mcp
This writes an entry to ~/.codex/config.toml. List or remove with codex mcp list / codex mcp remove gho.
Other MCP-compatible clients (Cursor, Cline, Continue, etc.): point them at the gho-mcp console script in your venv. The MCP protocol is the same across clients — only the registration UI differs.
Restart your client. The gho server should appear with all tools, the gho://topics/{topic_id} resource, and the compare_health_systems prompt available.
Updating an existing checkout
Version 0.7.0 includes the September 2026 correctness and reliability fixes listed in the changelog. Reinstall from the versioned GitHub wheel above, or update an editable source checkout as follows.
From your existing clone, with its virtual environment active:
git pull --ff-only
python -m pip install -e .
Restart the MCP client so its server process loads the updated code and tool
schemas. Reinstalling also updates dependencies: both servers require
mcp>=1.15.0,<2; GHED additionally requires filelock>=3.16,<4.
Response compatibility in 0.7.0
Comparisons and CSV exports retain the existing columns and append
spatial_dim_type, time_dim_type, dim1_type, dim1, dim2_type, dim2,
dim3_type, and dim3. Latest-year selection now preserves each complete
population combination; outputs can contain more rows than in 0.6.1. Include
those dimensions when grouping or pivoting, or choose a population with
dim_filters. sex is null when Dim1 represents a different dimension.
Profiles add indicator_dim_filters and per-indicator selection_status.
Ambiguous or incomplete latest-year populations return null headline values
with an explanation. Consumers should inspect this status before using a value.
Comparisons report unsupported group codes and missing requested countries.
For multi-chunk queries, source.params is empty and source.requests contains
the exact requests and continuation metadata. Unsupported identifiers and aliases
raise errors; failed dimension lookups preserve their upstream errors.
Tool reference
Tool signatures show the canonical parameter names — the server rejects unknown kwargs (since v0.5.2), so getting the names right matters. In particular: country is singular, countries is the list form, year filters are year_start / year_end (not year_from / year_to).
Discovery
| Tool | Signature | Purpose |
|---|---|---|
list_indicators |
(skip=0, top=50) |
Paginated list of all GHO indicators |
search_indicators |
(query, top=50) |
Case-insensitive substring search on indicator names |
topics_index |
() |
Curated map of comparative-health-systems topics → indicator codes |
list_dimensions |
() |
All GHO dimensions (COUNTRY, REGION, SEX, AGEGROUP, etc.) |
get_dimension_values |
(dimension_code) |
Allowed values for a dimension |
find_country_code |
(country) |
Resolve country name → ISO3 code |
list_income_groups |
() |
World Bank income-group spatial codes |
list_curated_country_groups |
() |
Decilion's curated groupings (LAC, LAC_TERRITORIES, OECD, LDC, SSA, MENA, etc.) — see Regional analysis below |
resolve_country_group_membership |
(group) |
Return the ISO3 list for one curated group |
Indicator detail
| Tool | Signature | Purpose |
|---|---|---|
get_indicator_metadata |
(indicator_code) |
Full metadata record for citation |
describe_indicator_dimensions |
(indicator_code, sample_size=200) |
What disaggregations (sex, age, residence) an indicator uses |
Data
| Tool | Signature | Purpose |
|---|---|---|
get_indicator_data |
(indicator_code, country=None, year_start=None, year_end=None, sex=None, dim_filters=None, top=100) |
Single indicator with optional spatial/temporal/sex/dim filters |
compare_countries |
(indicator_code, countries=None, country_group=None, year_start=None, year_end=None, sex=None, dim_filters=None, latest_only=False, top=1000, format="rows") |
One indicator × N spatial units × year range, tidy rows or CSV |
country_profile |
(country, year=None, indicator_codes=None, indicator_dim_filters=None) |
Parallel-fetched headline indicators for one country |
get_indicator_data_raw |
(indicator_code, filter=None, top=100, orderby=None, select=None) |
Expert escape-hatch — pass-through OData query for advanced disaggregations (wealth quintile, residence, education, custom Dim2 values) |
Resources and prompts
- Resource
gho://topics/{topic_id}— readable view of a curated topic and its indicator codes - Prompt
compare_health_systems(countries, topic)— guided template for cross-country comparative analysis
Population selection and query coverage
latest_only=True retains the latest year for each country and full dimension
combination. Age bands, residence areas and other subgroups remain separate;
sex is filled only when Dim1 represents sex. Discover dimensions first and
use dim_filters to compare a specific population. Indicators without a sex
dimension, such as physician density, must be queried without sex="both".
Dimension discovery samples data and reports its own truncation flags.
For an explicit custom profile population, pass per-indicator filters:
country_profile("Colombia", indicator_codes=["SDGPM25"],
indicator_dim_filters={"SDGPM25": {"Dim1": "RESIDENCEAREATYPE_TOTL"}})
Profiles select within the latest available year after applying filters. They prefer known WHO aggregate codes, otherwise report a unique labeled population. If several populations remain, no headline value is chosen. The profile query fetches up to 50 rows per indicator. When a capped page does not include an older year, the latest-year population set may be incomplete and no value is selected. Each profile indicator includes the exact query, truncation flags and selection status. Apply filters or use the data tools to investigate unresolved results.
Comparisons cap rows per chunk of up to 10 countries, not per country. An
alphabetically earlier country's observations can consume the entire allowance.
Check missing_requested_codes, truncated and possibly_truncated; split into
smaller country lists or narrow filters to establish coverage. A missing result
is not proof that WHO has no data. source.requests records each exact HTTP
query and its continuation link. For several chunks, filter is a readable
summary and source.params is empty, rather than containing an invented query.
Curated groups use exact ISO3 membership. unsupported_group_codes and a warning
report members absent from GHO dimensions. A group with no supported members
raises an error even when explicit countries are also supplied.
Regional analysis
Both gho-mcp and ghed-mcp expose the same curated country groupings beyond what WHO and the World Bank publish as built-in dimensions. Pass country_group="LAC" (or any of the codes below) on the data tools and the server resolves to the right ISO3 list — without you having to enumerate codes by hand.
Available groups
| Code | Definition | Members |
|---|---|---|
LAC |
33 sovereign Latin American & Caribbean states (PAHO/Decilion convention) | 33 |
LAC_TERRITORIES |
World Bank's 42-economy LAC region — sovereign states plus territories (Aruba, Cayman Islands, Curaçao, Puerto Rico, etc.) | 42 |
EAP |
World Bank East Asia & Pacific (FY2026) | 38 |
ECA |
World Bank Europe & Central Asia | 58 |
MENA |
World Bank "Middle East, North Africa, Afghanistan and Pakistan" (FY2026) | 23 |
MENA_EXCL_ISR_MLT |
MENA without Israel and Malta | 21 |
NAR |
World Bank North America (Bermuda, Canada, USA) | 3 |
SAS |
World Bank South Asia (FY2026 — without AFG and PAK, now in MENA) | 6 |
SSA |
World Bank Sub-Saharan Africa | 48 |
LDC |
UN Least Developed Countries | 44 |
OECD |
OECD member countries | 38 |
Aliases include natural-language ("Latin America and Caribbean", "Sub-Saharan Africa", "Least Developed Countries") and official codes (LCN, SSF, etc.). Two read-only tools — list_curated_country_groups and resolve_country_group_membership — let an assistant inspect or expand the lists at runtime.
Using country_group= on the data tools
country_group= merges (deduplicated) with any explicit countries= list. GHED additionally combines region / income filters using AND semantics. The following examples identify which server supports each call:
compare_countries(indicator_code="oops_che", country_group="LAC",
latest_only=True) # GHED
compare_countries(indicator_code="WHOSIS_000001", country_group="OECD",
year_start=2010, year_end=2023) # GHO
list_curated_country_groups() # both
resolve_country_group_membership("LAC") # both — returns 33 ISO3 codes
# ghed-mcp also exposes:
build_research_panel(indicator_codes=["che_gdp", "gghed_che"],
country_group="OECD", year_start=2000, year_end=2024)
summarize_country_group(indicator_code="ext_che", country_group="LDC",
latest_only=True)
list_countries(country_group="LAC", income="High") # LAC HICs
Curated members are validated as exact ISO3 codes, without fuzzy name matching. GHO reports unsupported members through unsupported_group_codes and warnings. An entirely unsupported group raises an error. Each source has different geographic and indicator coverage, so inspect the resolved and missing-country lists before comparing regional results. User-supplied countries= are resolved separately; unsupported three-letter identifiers and aliases whose target is absent from GHO dimensions raise an error. Exact country names take precedence over partial name matching.
Membership cadence
The lists are static Python data baked into each package — no runtime refresh, no separate cache. Users get updates by reinstalling the package.
The LAST_VERIFIED constant in country_groups.py records when each list was last cross-checked against:
- World Bank country and lending groups: https://datahelpdesk.worldbank.org/knowledgebase/articles/906519
- UN Least Developed Countries: https://www.un.org/development/desa/dpad/least-developed-country-category.html
- OECD members: https://www.oecd.org/about/document/list-oecd-member-countries.htm
Re-check annually. Known upcoming changes at the time of writing: Bangladesh, Lao PDR, and Nepal are scheduled to graduate from LDC status on 2026-11-24; Solomon Islands on 2027-12-13.
The same country_groups.py definitions live in both ghed-mcp and gho-mcp (canonical source: ghed-mcp). Their intended group membership matches, but supported countries and available observations can differ by source.
Examples
Each block below shows a natural-language prompt and a sketch of the underlying tool calls.
Country profile
"Give me a Colombia health systems profile."
The assistant calls country_profile(country="Colombia") and returns life expectancy, HALE, IMR, U5MR, MMR, UHC index, DTP3 coverage, skilled birth attendance, doctor and nurse density, premature NCD mortality, and TB incidence for the latest year, with both-sex, all-age and total-residence populations preferred when explicitly coded. Each selected value includes its actual population dimensions. Ambiguous or incomplete latest-year populations return selection_status="ambiguous" or "incomplete" and a null value, so a subgroup is never silently treated as a national total.
Comparative LAC analysis
"Compare maternal mortality across the Andean countries since 2010."
The assistant calls:
compare_countries(
indicator_code="MDG_0000000026",
countries=["Colombia", "Ecuador", "Peru", "Bolivia", "Venezuela"],
year_start=2010,
latest_only=False,
format="csv",
)
and gets a CSV ready to drop into any spreadsheet, statistical package, or charting tool.
Income-group gradient
"What's life expectancy by World Bank income group in 2021?"
The assistant calls compare_countries("WHOSIS_000001", countries=["WB_HI","WB_UMI","WB_LMI","WB_LI"], year_start=2021, year_end=2021, sex="both") and returns the textbook 16-year gap between high and low income groups.
From CSV output to analysis tools
compare_countries(..., format="csv") returns a CSV string under the csv key. Two common downstream paths:
To pandas — for time-series analysis or modelling:
import io, pandas as pd
# csv_text is the value of result["csv"] from compare_countries
df = pd.read_csv(io.StringIO(csv_text))
df["year"] = df["year"].astype(int)
df = df.dropna(subset=["value"]).pivot_table(
index="year", columns="country_name", values="value"
)
df.plot(title="Life expectancy at birth, LAC")
To any external tool (Excel, Google Sheets, R, Stata, Tableau, charting platforms, etc.):
with open("data.csv", "w") as f:
f.write(csv_text)
The columns country_code, country_name, year, sex, value, value_display, low, high, indicator_name, spatial_dim_type, time_dim_type, dim1_type, dim1, dim2_type, dim2, dim3_type, dim3 preserve population identity in both rows and CSV and are tidy-format-friendly and map cleanly into most analysis or visualization workflows. For wide-format / per-country columns, pivot first (pandas snippet above).
Advanced queries
The friendly tools cover spatial, temporal and sex filters, plus equality filters on Dim1/Dim2/Dim3 and their types through dim_filters. Use get_indicator_data_raw for custom OData expressions or selected output fields:
get_indicator_data_raw(
indicator_code="MDG_0000000026",
filter="SpatialDim eq 'BRA' and Dim2 eq 'WEALTHQUINTILE_QUINTILE5'",
select="SpatialDim,TimeDim,Dim2,NumericValue",
top=20,
)
Inspect what disaggregations an indicator supports with describe_indicator_dimensions(indicator_code) first; it samples observations and lists the Dim1/Dim2/Dim3 types and values seen in the sample.
Topics covered by topics_index
health_outcomes— life expectancy, HALE, IMR, U5MR, MMR, adult mortalityservice_coverage— UHC index, DTP3, measles, ANC, skilled birth attendancehealth_workforce— doctors, nurses & midwives, dentists, pharmacists per 10khealth_financing— health spending as % GDP, OOP share, per-capita CHEncd_burden— premature NCD mortality, hypertension, obesity, smokinginfectious_diseases— TB, HIV, malaria, hepatitis, water accesschild_health— stunting, wasting, exclusive breastfeeding, immunizationrisk_factors— tobacco, alcohol, obesity, physical inactivitymental_health— suicide, mental health workforceenvironment_climate— PM2.5, WASH (water/sanitation/hygiene)medicines_access— essential medicines availabilityquality_safety— antimicrobial resistance, hospital safety proxiesequity_sdg— SDG-aligned indicators for equity framing
Development
The 2026-09-14 regression suite contains 77 tests. GitHub Actions runs it on Python 3.11, 3.12, 3.13 and 3.14 against the minimum and latest compatible MCP SDK, then builds both distributions and checks wheel imports outside the checkout.
pip install -e ".[dev]"
pytest
Tests mock the WHO API; no network access required for the standard suite. A weekly GitHub Actions workflow validates that the curated indicator codes still resolve against the live API and opens an issue if anything rots.
Limitations
- The GHO OData feed is read-only and unauthenticated. Aggregate values (regional, income-group, global) are computed by WHO; this server does not recompute them.
- Detailed health expenditure data lives in the separate GHED database — a few GHED indicators are mirrored in GHO but not the full set.
- Some indicators are sparse for recent years; use
describe_indicator_dimensionsto inspect coverage before pulling. - The OData v3 endpoint is stable but ageing; if WHO migrates, this server will need a release.
About the WHO Global Health Observatory
The WHO Global Health Observatory (GHO) is the World Health Organization's central platform for global health statistics — what WHO describes as "a world of health data." It is the authoritative source of internationally comparable indicators on:
- Sustainable Development Goals health monitoring (SDG 3 and related targets)
- Universal Health Coverage — service coverage index, financial protection, essential medicines availability
- Major disease burden — HIV, tuberculosis, malaria, hepatitis, NCDs, mental health
- Health system inputs — workforce density, immunization coverage, health expenditure
- Risk factors — tobacco, alcohol, obesity, air pollution, WASH
- Equity dimensions — disaggregations by sex, age, residence, wealth quintile, and education where source data permits
The GHO underpins WHO's annual World Health Statistics report, regional health reviews, and country-level briefings used by ministries of health, multilateral institutions, and global health researchers worldwide. The data is free and openly published — through the OData API this server wraps, and through the official portal with its own visualizations, dashboards, and downloads.
This MCP server is plumbing. The data, the indicator definitions, the methodological work, and the country-level data validation are all WHO's. If you use values retrieved through this server, please:
- Cite WHO as the source. The
sourceblock on every data response includes the exact endpoint, parameters, and retrieval timestamp to make this straightforward. - Visit the GHO portal for indicator metadata, methodology notes, and the official visualizations. The MCP exposes the data; the portal provides the canonical context.
- Read the World Health Statistics annual report for WHO's curated narrative analysis of what the data shows.
The GHO is a public good. The most valuable contribution any user can make is to support and reference WHO's underlying data work.
Built by
Decilion — global health consulting across Latin America and the Caribbean, with an applied AI lens.
This server is one of Decilion's open-source contributions to the global health data community. If you use it in research, a brief acknowledgment is appreciated but not required.
License
MIT — see LICENSE.
Release files for mcp-server-gho 0.7.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mcp_server_gho-0.7.0.tar.gz | 72.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mcp_server_gho-0.7.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:106.3 kB
Release files / mcp_server_gho-0.7.0.tar.gz
| Download URL | mcp_server_gho-0.7.0.tar.gz |
|---|---|
| Size | 72.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0f5fae90dd40587b7acdacc606b02f84268b4d52037d4cbb52c3cc91f68e3eaf
|
|
BLAKE2b-256 checksum How to use checksums |
49b9474153c6526a39a33e9dfe5fddfddad537634d66f442c0d4e8f6c01210d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency logRelease files / mcp_server_gho-0.7.0-py3-none-any.whl
| Download URL | mcp_server_gho-0.7.0-py3-none-any.whl |
|---|---|
| Size | 33.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ae78f77be58c327c0f26ae9e11d8c71005d3add2426be2111774e235e6ac0b27
|
|
BLAKE2b-256 checksum How to use checksums |
acf196c5569ab40f7afc1dd9f73b85978f9ad74d40033546ada22b7c89817c42
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 15, 2026.
Transparency log