WHO Global Health Observatory — 3,059 indicators across 194 member states.
Query WHO Global Health Observatory data — 3,059 indicators across 194 member states with country, region, year, and sex filters via MCP. STDIO or Streamable HTTP.
WHO Global Health Observatory (GHO) data — 3,059 indicators across 194 member states. Search the indicator catalog, discover country, region, income-group, and sex filter dimensions, and query data rows from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
| Tool | Description |
|---|---|
who_search_indicators | Search the GHO indicator catalog by keyword in indicator names |
who_list_indicators | Browse the full indicator catalog with pagination |
who_get_indicator_metadata | Fetch indicator names and supported filter dimensions for up to 10 codes |
who_list_dimensions | List all dimension type codes available in the GHO API |
who_list_dimension_values | List valid codes and labels for a dimension type (COUNTRY, REGION, SEX, etc.) |
who_query_indicator_data | Query data rows for an indicator with spatial, temporal, and dimension filters |
| Resource | Description |
|---|---|
who://indicator/{indicatorCode}/metadata | Indicator name and supported filter dimensions for a single code |
who://dimension/{dimensionCode}/values | First 100 values for a dimension type |
who://dimension/{dimensionCode}/values{?limit,offset} | One explicit page of a dimension type's values |
who://dimension/{dimensionCode}/values{?limit,offset,parentCode} | One explicit page, narrowed to a parent code |
Each mirrors data also reachable via who_get_indicator_metadata and who_list_dimension_values — useful for clients that inject resources as context but don't call tools.
who_search_indicators tool"life expectancy", "immunization", "mortality", "diabetes", or "HIV"who_query_indicator_dataoffset, default 0) with limit default 20, max 100; reports totalCount, hasMore, pageInfo, nextOffsettotalCount returns an empty page; a no_results error is raised only when nothing matches at allwho_list_indicators toollimit (default 50, max 500) and offsettotalCount and hasMore for iterationwho_get_indicator_metadata toolCOUNTRY, SEX, REGION, AGEGROUP) for each resolved codedimensions: [] plus a dimensionsNote pointing at a sample data row's dim1Type/dim2Type, not a not-foundnotFound rather than raising an error; the call fails only when none of the requested codes resolvewho_list_dimensions toolCOUNTRY, REGION, SEX, WORLDBANKINCOMEGROUP, AGEGROUPwho_list_dimension_valueswho_list_dimension_values toolparentCode, parentLabel, parentDimension)parent_code narrows hierarchical dimensions — dimension: "COUNTRY" with parent_code: "EUR" returns the 58 countries in the WHO European RegionCode; offset-based pagination (offset) with limit default 100, max 500 — GHO (3,103 values) and DHSMICSGEOREGION (4,932) need pagingparent_code that matches nothing returns an empty page, not an error; only an unfiltered empty result means the dimension itself does not existdimension fails as a typed malformed_identifier validation errorwho_query_indicator_data toolcountry_codes (ISO 3166-1 alpha-3), region_codes (WHO regions), or income_group_codes (World Bank groups) — supplying more than one is a validation erroryear_from / year_to time range; sex (SEX_BTSX, SEX_FMLE, SEX_MLE) applies only when the indicator's first cross-cutting dimension is SEX, otherwise use dim1_valueinclude_uncertainty (default true) adds low/high bounds; sort (year_desc default or year_asc) with a total row ordering so paging never repeats or drops rowslimit default 200, max 1000; reports totalRows, hasMore, pageInfo, nextOffsetindicator_not_found, no_data, ambiguous_spatial_filter, invalid_year_range, invalid_query, malformed_identifierwho://indicator/{indicatorCode}/metadata resourceapplication/jsonindicatorCode comes from who_search_indicators or who_list_indicatorsdimensions carries a dimensionsNote when the upstream dimension table lists none for the code, rather than reporting a missing indicatorwho://dimension/{dimensionCode}/values resourcedimensionCode from who_list_dimensions), as application/jsonwho://dimension/{dimensionCode}/values{?limit,offset} resourcelimit (1–500) and offset must both be present in the URI — the query variables are required, not optionaltotalCount, hasMore, nextOffset, and an optional noticetotalCount returns an empty page, not an errorwho://dimension/{dimensionCode}/values{?limit,offset,parentCode} resourceparentCode to narrow to one parent value, e.g. parentCode="EUR" for countries in the WHO European Region; limit, offset, and parentCode must all be present in the URIparentCode that matches nothing returns an empty page, not an error — read the unfiltered URI to confirm the dimension itself existsdimension, values, totalCount, hasMore, nextOffset, noticeBuilt 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.
WHO GHO-specific:
GHO_BASE_URL, GHO_REQUEST_TIMEOUT_MS) for custom or mirrored deploymentsAgent-friendly output:
hasMore, nextOffset, pageInfo, and truncated on the data-query tool) so agents can decide whether to page furtherrecovery hint on every failure pathA public instance is available at https://who-gho.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"who-gho-mcp-server": {
"type": "streamable-http",
"url": "https://who-gho.caseyjhand.com/mcp"
}
}
}
Add the following to your MCP client configuration file.
{
"mcpServers": {
"who-gho-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/who-gho-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"who-gho-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/who-gho-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"who-gho-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/who-gho-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/who-gho-mcp-server.git
cd who-gho-mcp-server
bun install
cp .env.example .env
# edit .env and set optional overrides
| Variable | Description | Default |
|---|---|---|
MCP_TRANSPORT_TYPE | Transport: stdio or http | stdio |
MCP_HTTP_PORT | HTTP server port | 3010 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path where the MCP server is mounted | /mcp |
MCP_SESSION_MODE | HTTP session posture: stateless, stateful, or auto. No tool asks the caller for input mid-handler, so the server declares stateless; set this only to override. | stateless |
MCP_PUBLIC_URL | Public origin override for TLS-terminating reverse-proxy deployments | none |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.) | info |
MCP_GC_PRESSURE_INTERVAL_MS | Opt-in Bun-only forced-GC pressure loop (ms). Try 60000 if RSS grows under sustained HTTP load. | 0 (disabled) |
LOGS_DIR | Directory for log files (Node.js only) | <project-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1 | in-memory |
GHO_BASE_URL | WHO GHO OData API base URL (override for custom/mirrored deployments) | https://ghoapi.azureedge.net/api/ |
GHO_REQUEST_TIMEOUT_MS | HTTP request timeout in milliseconds | 30000 |
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 who-gho-mcp-server .
docker run --rm -p 3010:3010 who-gho-mcp-server
The Dockerfile defaults to HTTP transport, stateless session mode, and logs to /var/log/who-gho-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 and inits the GHO service. |
src/config | Server-specific environment variable parsing and validation with Zod. |
src/mcp-server/tools | Tool definitions (*.tool.ts). Six tools across indicator discovery, dimension lookup, and data queries. |
src/mcp-server/resources | Resource definitions. Indicator metadata and dimension values resources. |
src/services/gho | WHO GHO OData API service layer — HTTP client, query builder, types. |
src/utils | wellFormed() — repairs unpaired UTF-16 surrogates in caller-supplied strings before they reach output, enrichment, or failure data. |
tests/ | Unit and integration tests, mirroring the src/ structure. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for logging, ctx.state for storagecreateApp() arraysIssues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
Apache-2.0 — see LICENSE for details.
Source-derived launch command. Check the maintainer’s required arguments and credentials before running:
npx -y @cyanheads/who-gho-mcp-serverMerge this template into ~/Library/Application Support/Claude/claude_desktop_config.json. Keep existing servers. Add any arguments, credentials, and permissions required by the maintainer; this template has not been install-tested.
{
"mcpServers": {
"io-github-cyanheads-who-gho-mcp-server": {
"command": "npx",
"args": [
"-y",
"@cyanheads/who-gho-mcp-server"
]
}
}
}Restart Claude Desktop completely for changes to take effect. Confirm the server appears connected in the client’s tool list, then try a read-only example from its documentation.
Claude Desktop setup referenceio.github.cyanheads/who-gho-mcp-server works with any MCP-compatible client. Copy the config snippet from the Configuration section above and add it to the file shown for your client, then restart the application.
~/Library/Application Support/Claude/claude_desktop_config.jsonRestart Claude Desktop completely for changes to take effect.~/.cursor/mcp.jsonRestart Cursor for changes to take effect..vscode/mcp.jsonReload VS Code window for changes to take effect.~/.codeium/windsurf/mcp_config.jsonRestart Windsurf for changes to take effect..mcp.jsonSave at the project root, then start Claude Code in that project and review the MCP server approval prompt. Keep real credentials out of shared files.