humanitarian-mcp
v0.6.0
Published
Model Context Protocol server for humanitarian open data — UNHCR today, ReliefWeb/HDX/IOM tomorrow. Semantic tools, resources and prompts over trusted public datasets.
Maintainers
Readme
Humanitarian MCP
Trusted humanitarian data — refugees, conflict, hunger, funding — as one clean, citable interface for AI assistants and research code.

"How many refugees does Jordan host right now?"
"Top refugee-hosting countries per capita — not absolute numbers."
"Relate Sudan's displacement to conflict fatalities since 2018."
"Export that as citation-ready CSV with a codebook." ← also works in Arabic: «قارن بين مصر والأردن»What is Humanitarian MCP?
Humanitarian MCP is an open-source server that gives AI assistants and analysis scripts reliable, normalized, read-only access to trusted humanitarian datasets: 75 years of UNHCR displacement statistics, World Bank context indicators, and HDX crisis data (conflict events, food security, humanitarian funding, internal displacement).
MCP in one paragraph: the Model Context Protocol is an open standard — think "USB for AI tools" — that lets any AI application (Claude Desktop, Claude Code, Cursor, VS Code, Windsurf, custom agents) plug into external systems through one protocol. A server like this one exposes tools the model can call, resources it can read, and prompts it can reuse. Connect it once, and every question your assistant answers about displacement is grounded in the real numbers instead of its training data. New to MCP? Read docs/how-mcp-works.md.
Humanitarian data is public but hostile to programmatic use — different country-code schemes, different schemas, silent failure modes (details below). This project normalizes multiple humanitarian datasets into one consistent record shape, one country-code scheme (ISO3), one interface, with the citation attached to every payload.
Why this project exists
Every trap below is real, encoded in this codebase, and covered by tests:
| Problem | Example | What this server does |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
| Country-code chaos | UNHCR's internal codes disagree with ISO3 for 99 of 232 countries. Egypt is ARE in UNHCR-speak — which is the UAE's ISO code. Ask a raw API "refugees in ARE?" and you silently get the wrong country. | Everything speaks ISO3. Names resolve fuzzily in English and Arabic («مصر», «الأردن», «السودان»). |
| Schema babel | UNHCR returns {items: [...]}, the World Bank returns [meta, rows], HAPI returns {data: [...]} — each with different field names, pagination and update cadence. | One NormalizedRecord shape across all sources: country_code, year, population, metrics, source, dataset. |
| Dirty cells | Numeric values arrive as numbers, numeric strings, or "-". | Cleaned once, at the provider layer. Missing stays missing — never silently zero. |
| Silent empties | Most query mistakes return an empty list, not an error. An AI confidently reports "no data". | Errors come back as actionable text: "No country matched 'Atlantis'. Try search_country first." |
| Aggregation traps | IDP assessment rounds must not be summed (double-counts people); funding coverage must be recomputed, never averaged; IPC projections must not be mixed with current analyses. | Each dataset's aggregation semantics are encoded and unit-tested. |
| Misleading absolute numbers | Lebanon and Germany host similar refugee counts. Per 1,000 residents, they are worlds apart. | normalize_by: "population" \| "gdp" — denominators matched per year, denominator year disclosed on every row. |
| Missing citations | Models paraphrase numbers with no provenance. | Every payload carries its source; exports attach a reproducible extraction manifest and optional codebook. |
Without a trusted middleware, an LLM pointed at raw humanitarian APIs re-discovers these traps every session — and the failure mode is not a crash, it is a plausible-looking wrong number. This server exists so that never happens.
Architecture
flowchart TD
U["You"] --> C["Claude Desktop · Claude Code · Cursor · VS Code · Windsurf<br/>or your own MCP agent"]
C -->|"MCP (stdio or Streamable HTTP)"| S["Humanitarian MCP<br/>21 tools · 11+ resources · 8 prompts"]
S --> R["Provider registry<br/>(tools never see provider internals)"]
R --> P1["UNHCR provider<br/>displacement · demographics · asylum"]
R --> P2["World Bank provider<br/>population · GDP · poverty"]
R --> P3["HDX/HAPI provider<br/>conflict · food security · funding · IDPs"]
R --> P4["ReliefWeb provider<br/>situation reports · narrative context"]
P1 & P2 & P3 & P4 --> H["Shared HTTP layer<br/>retry · backoff · rate limit · ETag · cache · offline mode"]
H --> A1[("api.unhcr.org")]
H --> A2[("api.worldbank.org")]
H --> A3[("hapi.humdata.org")]
H --> A4[("api.reliefweb.int")]Three invariants hold everywhere: (1) nothing provider-specific leaks outside src/providers/<id>/; (2) every tool is read-only and annotated as such; (3) errors reach the model as actionable text, never stack traces. Deep dive: docs/architecture.md.
Two ways to use it
TL;DR — researchers and organizations who care about control: self-host. Everyone who just wants answers: use the hosted endpoint.
| | 🖥️ Self-hosted | ☁️ Hosted endpoint |
| ------------------- | ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Setup | Install Node/Docker, run the server | None — paste one URL |
| Who runs it | You, on your machine or infra | Maintainer-operated at humanitarian-mcp.zad.tools |
| Privacy | Queries never leave your machine (except to the public data APIs) | Queries pass through the hosted server |
| Offline / fieldwork | ✅ full offline mode with a warmed cache | ❌ needs internet |
| Configuration | Every knob: providers, cache, rate limits | Fixed server-side |
| Updates | You pull releases | Updated for you (caching, normalization, rate limiting, source fixes, monitoring handled) |
| Version | Always the latest release | Rolling; may briefly lag the newest release — check /api/status |
| Guarantees | Yours to make | Best-effort community service, no SLA today |
| Cost | Free (MIT) | Free today; free and paid plans may be introduced later |
| Best for | Researchers, NGOs with data policies, enterprises, air-gapped fieldwork | Quick starts, demos, journalists, students |
Mode 1 — Self-hosted
You install and run the server yourself. You own everything: the process, the cache, the configuration. Your MCP client talks to it over stdio (desktop) or HTTP (remote/containers). Full instructions in Installation below.
// Claude Desktop — claude_desktop_config.json
{
"mcpServers": {
"humanitarian": {
"command": "node",
"args": ["/absolute/path/to/humanitarian-mcp/dist/index.js"],
},
},
}Mode 2 — Hosted endpoint
No infrastructure. Connect any Streamable-HTTP-capable MCP client to:
https://humanitarian-mcp.zad.tools/mcp- claude.ai / Claude Desktop (remote connector): add a custom connector with that URL.
- Claude Code:
claude mcp add --transport http humanitarian https://humanitarian-mcp.zad.tools/mcp - Anything that speaks HTTP: see examples/http-client.md — the endpoint is stateless JSON-RPC, no session juggling.
No API key is required today. The service is operated on a best-effort basis by the maintainer; free and paid tiers may be introduced in the future — nothing beyond what you see here is promised. If you need guarantees, self-host: it is the same code.
Features
Data access
- 20 semantic, read-only tools — full reference: country profiles, comparisons, yearly series, demographics, asylum applications/decisions with recognition rates, conflict events, food security (IPC), humanitarian funding, rankings, trend analysis with anomaly detection, (loudly caveated) naive forecasts
- 11+ MCP resources (
country://EGY,report://SDN,chart://UGA,metadata://providers…) with URI autocompletion - 7 built-in prompts (situation summary, donor briefing, anomaly hunt…)
Country intelligence
- Fuzzy name resolution in English and Arabic — «مصر», "egypt",
EGY, "DRC", "ivory coast" all land correctly; Arabic matching folds hamza/alef forms, taa marbuta and the definite article («الأردن» = «الاردن» = «اردن») - The UNHCR↔ISO3 code mismatch (99/232 countries) handled invisibly
Analytics
normalize_by: per-capita (per 1,000 residents) and per-GDP (per US$1bn) comparisons, rankings and charts, with per-year denominator matching- Regression, year-over-year, CAGR, z-score anomaly detection
- Charts as Chart.js / Vega-Lite / Mermaid / SVG; maps as GeoJSON
Research reproducibility
- Extraction manifest on every export: exact arguments, timestamp, server version, citation — a repeatable recipe for a paper's appendix
- Optional variable-level codebook matching exactly the exported columns
- CSV / JSON / Markdown / GeoJSON export; CSV manifests ride in
#comment lines (pd.read_csv(..., comment="#")) - Runnable Python & R notebooks reproducing four research workflows
- CITATION.cff + JOSS paper draft in paper/
Operations
- Two transports: stdio (desktop) and stateless Streamable HTTP (remote) + a built-in dashboard with a query playground
- Serious caching: memory or SQLite (zero native deps), TTL + ETag revalidation, stale-while-revalidate, full offline mode for fieldwork
- Polite by design: token-bucket rate limiting, retries with backoff, identified User-Agent, strictly read-only
- Docker image + compose for organizational self-hosting
Live data sources
| Provider | Datasets | What it contributes | Key | | -------------------------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | | UNHCR Refugee Statistics (default) | population, demographics, asylum-applications, asylum-decisions | The displacement backbone: refugees, asylum-seekers, IDPs, stateless and others of concern, 1951–present, by origin and asylum country; age/sex breakdowns; asylum decisions with recognition rates | none | | World Bank Indicators (default) | context-indicators | The denominators: national population, GDP, GDP per capita, extreme-poverty rates — what turns "how many" into "how heavy a burden" | none | | HDX HAPI (opt-in) | conflict-events, food-security, humanitarian-funding, idps | The crisis context, citing original producers: conflict events & fatalities (ACLED), IPC food-insecurity phases (IPC), appeal requirements vs funding (OCHA FTS), IDP stocks (IOM DTM) | free app identifier (.env.example) | | ReliefWeb (opt-in) | situation-reports | The narrative context: situation-report counts per country-year plus the latest report titles, publishers and links (UN OCHA / ReliefWeb) — what grounds trends and anomalies in published reporting | pre-approved appname (.env.example) |
Example questions
Real prompts, real production numbers (extracted 2026-07-10 — figures are revised upstream over time):
- "What are the top refugee-hosting countries per capita?" → Lebanon 130.7 per 1,000 residents, Chad 63.0, Moldova 56.6, Jordan 55.7 — a very different list than the absolute ranking.
- "Was there anything unusual in Sudan's displacement data?" → 2023 flagged as an anomaly (z ≈ +2.6, +78.8% YoY), coinciding with the April 2023 war.
- "Relate Sudan's conflict to its displacement since 2022." → conflict fatalities 2,770 → 21,020 → 22,987 (2022–2024, ACLED) alongside IDPs 3.78M → 9.05M → 11.56M (IOM DTM).
- "How well is Sudan's humanitarian response funded?" → 58.5% of requirements in 2023, 76.4% in 2024 (OCHA FTS).
- "«قارن بين عدد اللاجئين في مصر والأردن»" → works — country resolution is bilingual.
- "Export Jordan's series as citation-ready CSV with a codebook." →
export_data({..., include_codebook: true}).
More worked conversations with tool traces: examples/conversations.md.
Why researchers like it
- Citable: GitHub's Cite this repository button (CITATION.cff); a JOSS paper is drafted in paper/.
- Reproducible: every export carries a manifest (exact call, timestamp, server version, source citation) — paste it in your appendix and anyone can re-run the extraction.
- Documented data:
include_codebook: truegenerates variable-level documentation (meaning, unit, derivation — including how recognition rates and funding coverage are computed) for exactly the columns you exported. - Join-ready: every row carries ISO3, so merges against World Bank / V-Dem / UCDP panels need no country-name crosswalk.
- Honest methods: end-year stock semantics, denominator years, truncation and forecast naivety are stated in the output, not hidden.
- Fieldwork-ready: warm the SQLite cache once, then
HMCP_OFFLINE=1gives the full toolset with zero connectivity.
Start here: docs/for-researchers.md · notebooks.
Installation
Requires Node.js ≥ 20 for source installs (SQLite cache uses built-in node:sqlite on Node ≥ 22.5; older Nodes fall back to memory automatically). Docker route needs only Docker.
From npm — no clone needed
npx humanitarian-mcp --version # → humanitarian-mcp 0.5.1Register it with Claude Code in one line:
claude mcp add humanitarian -- npx -y humanitarian-mcpClaude Desktop — one click (no terminal)
Download humanitarian-mcp.mcpb from the latest release and double-click it. Done.
From source
git clone https://github.com/ahmedvnabil/humanitarian-mcp
cd humanitarian-mcp
npm install
npm run build
node dist/index.js --version # → humanitarian-mcp 0.5.1Docker
docker run -p 8642:8642 -v hmcp-cache:/data ghcr.io/ahmedvnabil/humanitarian-mcp
# → MCP endpoint at http://localhost:8642/mcp + dashboard at http://localhost:8642Or docker compose up -d with the provided compose.yaml.
Connect your client (self-hosted, stdio)
{
"mcpServers": {
"humanitarian": {
"command": "node",
"args": ["/absolute/path/to/humanitarian-mcp/dist/index.js"]
}
}
}Sample with cache tuning: examples/claude-desktop-config.json.
claude mcp add humanitarian -- node /absolute/path/to/humanitarian-mcp/dist/index.js{
"mcpServers": {
"humanitarian": {
"command": "node",
"args": ["/absolute/path/to/humanitarian-mcp/dist/index.js"]
}
}
}{
"servers": {
"humanitarian": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/humanitarian-mcp/dist/index.js"]
}
}
}{
"mcpServers": {
"humanitarian": {
"command": "node",
"args": ["/absolute/path/to/humanitarian-mcp/dist/index.js"]
}
}
}Then ask: "What are the top refugee-hosting countries this year?"
Enable the HDX crisis datasets (optional)
# one-time: generate a free identifier (base64 of app-name:email — not a secret)
curl 'https://hapi.humdata.org/api/v2/encode_app_identifier?application=<your-app>&email=<your-email>'
HMCP_PROVIDERS=unhcr,worldbank,hdx HMCP_HDX_APP_ID=<identifier> node dist/index.jsEnable ReliefWeb situation reports (optional)
# one-time: request a pre-approved appname (short form, reviewed by ReliefWeb)
# https://apidoc.reliefweb.int/parameters#appname
HMCP_PROVIDERS=unhcr,worldbank,reliefweb HMCP_RELIEFWEB_APPNAME=<appname> node dist/index.jsAll configuration knobs: .env.example.
Verify without any client
npm run dashboard # → http://localhost:8642 — providers, health, live logs, query playground
npm run inspect # → official MCP InspectorIn --http mode, GET /health is a dependency-free liveness probe for uptime
monitors, and every other route is rate limited per client IP
(HMCP_HTTP_RATE_LIMIT_RPM, default 120/min) so a public endpoint cannot be
used to exhaust the upstream quotas all providers share.
Repository tour
| Path | What lives there |
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| src/ | The server. providers/ (one directory per data source — the only place provider quirks may exist), tools/, resources/, prompts/, shared/ (HTTP, cache, rate limiting, stats, country matching), viz/, http/ (Streamable HTTP + dashboard) |
| docs/ | tools.md (tool reference) · architecture.md · for-researchers.md · how-mcp-works.md (MCP primer) · adding-providers.md · development.md · index.html (Arabic landing page, served at humanitarian-mcp.zad.tools) |
| examples/ | Client configs, worked conversations with tool traces, HTTP client recipes, runnable Python/R notebooks |
| tests/ | 125 tests: MCP compliance suite (official SDK client ↔ real server), fixture-based provider suites (no network), unit tests |
| paper/ | JOSS paper draft (paper.md + paper.bib) |
| marketing/ | Launch kit: platform-native announcement drafts |
| Dockerfile / compose.yaml | Organizational self-hosting (image published to GHCR on every release) |
| .github/ | CI (Node 20/22/24), release automation (npm + .mcpb + Docker on tag), issue templates incl. a provider-request template |
Development
npm run dev # stdio server via tsx
npm run dev:http # HTTP + dashboard on :8642
npm test # vitest — unit + integration + MCP compliance
npm run check # typecheck + lint + format + tests (run before pushing)
npm run build # emit dist/Releases: bump the version, tag v*, push — CI publishes the GitHub release with the .mcpb bundle, the Docker image to GHCR, and (once the npm token is configured) the npm package. Full guide: docs/development.md · adding a data source: docs/adding-providers.md.
Roadmap
✅ Completed
- v0.1.0 — UNHCR provider, 17 tools, resources, prompts, caching/offline, dashboard, compliance suite
- v0.2.0 — release automation (npm workflow +
.mcpb), CITATION.cff, Arabic country names, reproducible extraction manifests - v0.3.0 — World Bank provider +
normalize_byper-capita / per-GDP analytics - v0.4.0 — HDX/HAPI provider (conflict, food security, funding, IDPs) + 3 crisis tools + Docker/GHCR
- v0.5.0 — codebooks, Python/R notebooks, JOSS paper draft
- v0.5.1 — HDX fixes from the first live verification round (per-theme admin levels, server-side year windows, pagination)
- Published to npm —
npx humanitarian-mcpis live
🚧 In progress
- Zenodo DOI per release
- JOSS paper review & submission
- npm provenance signing on future releases (via the release workflow once its token is configured)
🔭 Future (contributions welcome)
- ReliefWeb disasters dataset (the situation-reports pipeline already shipped)
- Full Arabic report generation (
locale: "ar") - UNHCR Operational Data Portal situations
- Sturdier statistics (confidence intervals, changepoint detection)
- Redis cache backend
FAQ
No. MCP (Model Context Protocol) is the plumbing standard that connects AI apps to tools. You paste a config or double-click the .mcpb file; your assistant does the rest. Curious anyway? docs/how-mcp-works.md.
No — UNHCR and World Bank need none. Only the optional HDX datasets need a free "app identifier" (base64 of your app name + email, generated in one curl; not a secret).
No. It is an independent open-source project. All data is © its original producers (UNHCR, World Bank, ACLED, IPC, OCHA, IOM via HDX), and every payload carries that attribution.
Because the failure mode is silent wrong answers: UNHCR's ARE is Egypt, but ISO's ARE is the UAE; empty results look like "no data"; "-" cells break math. This server encodes those traps once, with 125 tests. See Why this project exists.
It is as fresh as the sources: UNHCR publishes end-year (and mid-year) statistics, the World Bank annual indicators, HDX themes on their producers' cadence. The server caches responses (default 1 h fresh, stale-while-revalidate after) — it never makes data older than the source.
Usually one of: news quotes flows ("X arrived this month") while these are stocks ("X present at year-end"); different population categories (refugees vs all people of concern); or upstream revisions — UNHCR revises series retroactively. Record your extraction date (the manifest does it for you).
Each category is a separate metric (refugees, idps, asylum_seekers, stateless…), and the codebook defines every one. The population headline per dataset is documented in docs/tools.md and in generated codebooks.
asylum = people hosted in the country (default); origin = people displaced from it. Mixing them up is the most common analysis error — the server's instructions teach connected models this convention up front.
normalize_by: "population" divides by the country's population of the same year (World Bank), scales per 1,000 residents, re-sorts the ranking, and discloses the denominator year on every row. "gdp" does the same per US$1bn. Countries lacking denominator data are counted, never silently dropped.
Yes — country resolution covers official UN Arabic names for 100% of countries served, plus common variants, with spelling-variant folding («الأردن» = «الاردن» = «اردن»). Tool output is currently English; full Arabic report generation is on the roadmap.
Yes. npm run dashboard gives you a query playground and a plain HTTP bridge (POST /api/call) — the Python/R notebooks use exactly that. The /mcp endpoint is also plain JSON-RPC (examples/http-client.md).
Self-host if you need control, privacy guarantees, offline mode, or custom configuration (it's the same MIT-licensed code). Use the hosted endpoint to be running in 30 seconds. Full comparison: Two ways to use it.
The hosted instance runs this open-source code, which keeps in-memory usage counters (per-tool call counts/latency for its dashboard) and standard server logs. It has no accounts, no API keys, and stores no personal data by design. If that is still too much, self-host.
Yes (self-hosted): run once online with HMCP_CACHE=sqlite to warm the cache, then HMCP_OFFLINE=1 serves everything from cache and fails loudly on misses.
Anything that speaks MCP: Claude Desktop & claude.ai, Claude Code, Cursor, VS Code, Windsurf, and custom agents via the official SDKs (stdio or Streamable HTTP). Config snippets are in Installation.
All three — plain Node with zero native dependencies (the SQLite cache uses Node's built-in node:sqlite). The Docker image covers anything that runs containers.
Two runtime dependencies (the MCP SDK and zod), no database server required, and the Docker image is a slim multi-stage Node build. It runs comfortably on the smallest VPS tier.
They are deliberately naive (OLS extrapolation) and say so in their own output. Use them as a baseline sanity check, never as planning figures — UNHCR publishes no such projections through this API.
The server rate-limits itself (token bucket, default 4 req/s per provider), retries with backoff, and serves stale cache when a source is down — your session degrades gracefully instead of erroring.
See Citation below — BibTeX/APA/Chicago provided, plus GitHub's "Cite this repository" button. Cite the data as its original producers (the manifests include the right citation string per dataset).
One directory, one interface, fixture-based tests — the full worked guide is docs/adding-providers.md, and there's a provider-request issue template. IOM DTM and UNHCR ODP situations are the most-wanted next providers.
Yes — that's what the Docker image and compose file are for. MIT license, commercial use fine. Keep the HTTP endpoint behind your reverse proxy/VPN (it is unauthenticated by design — see SECURITY.md).
No. Everything is aggregate national statistics from public sources. Still: these numbers represent people — present them with the care they deserve (see Data & responsibility).
Contributing
Contributions welcome — most wanted: new providers (IOM DTM, UNHCR ODP situations), country-alias corrections, and documentation in more languages. Start with CONTRIBUTING.md; the golden rules: read-only always, provider isolation, no network in tests, attribution is not optional.
Citation
Software (also available via GitHub's "Cite this repository" button — CITATION.cff; a Zenodo DOI per release is being set up):
BibTeX
@software{nabil_humanitarian_mcp_2026,
author = {Nabil, Ahmed},
title = {humanitarian-mcp: a Model Context Protocol server for humanitarian open data},
year = {2026},
version = {0.5.1},
url = {https://github.com/ahmedvnabil/humanitarian-mcp},
license = {MIT}
}APA — Nabil, A. (2026). humanitarian-mcp: A Model Context Protocol server for humanitarian open data (Version 0.5.1) [Computer software]. https://github.com/ahmedvnabil/humanitarian-mcp
Chicago — Nabil, Ahmed. humanitarian-mcp: A Model Context Protocol Server for Humanitarian Open Data. V. 0.5.1. Computer software, 2026. https://github.com/ahmedvnabil/humanitarian-mcp.
Cite the data as its producers: UNHCR Refugee Data Finder; World Bank World Development Indicators (CC BY 4.0); ACLED / IPC / OCHA FTS / IOM DTM via HDX HAPI — every export's manifest carries the exact citation string for its dataset. Method notes: docs/for-researchers.md.
Data, attribution & responsibility
- Data © its original producers: UNHCR (Refugee Data Finder), World Bank (CC BY 4.0), and via HDX HAPI: ACLED, IPC, OCHA FTS, IOM DTM. This project is unofficial and unaffiliated.
- The server is strictly read-only and respects upstream rate limits.
- Figures are end-year stocks; recent years may be preliminary; series get revised — record extraction dates (manifests do).
- Forecasts are naive extrapolations, clearly labelled.
- These numbers represent people. Present them with the care they deserve.
License
MIT — free for research, NGO, commercial and government use.
