Open Emirates Intelligence
<p align="center">
<img src="docs/assets/hero.svg" alt="Open Emirates Intelligence" width="900">
</p>
<h1 align="center">Open Emirates Intelligence</h1>
<p align="center">
<a href="https://github.com/ahmedvnabil/uaemcp/actions/workflows/ci.yml"><img src="https://github.com/ahmedvnabil/uaemcp/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="https://www.npmjs.com/package/uaemcp"><img src="https://img.shields.io/npm/v/uaemcp?logo=npm" alt="npm version"></a>
<a href="https://www.npmjs.com/package/uaemcp"><img src="https://img.shields.io/npm/dm/uaemcp?logo=npm" alt="npm downloads"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
<a href="https://modelcontextprotocol.io"><img src="https://img.shields.io/badge/MCP-server-000000" alt="MCP"></a>
<a href="pyproject.toml"><img src="https://img.shields.io/badge/python-3.11%2B-3776AB?logo=python&logoColor=white" alt="Python"></a>
<a href="ts"><img src="https://img.shields.io/badge/TypeScript-3178C6?logo=typescript&logoColor=white" alt="TypeScript"></a>
</p>
<p align="center"><em>Official UAE open data, as MCP tools any agent can call — hosted or self-hosted.</em></p>
<p align="center">
<img src="docs/assets/demo.gif" alt="npx uaemcp — live terminal demo" width="760">
</p>
A **standalone MCP server** that gives any MCP client (Claude Desktop, Claude Code,
Cursor) source-cited access to **official UAE open data** — with direct-contact PII
redacted, every server-side fetch SSRF-guarded, and both **stdio** and **HTTP**
transports in one package.
It is self-contained: no dependency on any private or sibling service. Ships in two
flavors at **feature parity** — a **Python** implementation (this directory) and a
**TypeScript / npm** port ([`ts/`](ts)), published as
[`uaemcp`](https://www.npmjs.com/package/uaemcp) (**v0.2.0**). Both expose the same
11 MCP tools, resources, and prompts.
---
## Two ways to use it
Open source under the **MIT license** — use the instance we host, or run your own.
### 1. Use our hosted instance — zero setup
A public instance is live at **https://uaemcp.zad.tools**. Point any
MCP client at its endpoint — nothing to install:
```json
{
"mcpServers": {
"uae-intelligence": {
"type": "http",
"url": "https://uaemcp.zad.tools/mcp"
}
}
}
```
Or just open the dashboard: <https://uaemcp.zad.tools>
### 2. Self-host it — run your own, anywhere
- **npm, no install:** `npx uaemcp` (stdio) · `npx uaemcp http` (HTTP at `/mcp`)
- **Docker:** `docker build -t uaemcp . && docker run -p 8080:8080 uaemcp uaemcp http`
- **From source:** clone this repo (Python below, or the TypeScript port in [`ts/`](ts))
Self-hosting gives you full control: add your own sources, set a write token, and
keep every fetch on your own infrastructure.
---
## Quickstart
### Fastest — via npm (no install)
```bash
npx uaemcp # stdio MCP server
npx uaemcp http # Streamable-HTTP server at /mcp
```
> Latest release: **v0.2.0** — published to npm with [provenance](https://docs.npmjs.com/generating-provenance-statements)
> via GitHub Actions OIDC (no tokens). `npx uaemcp` always fetches it.
### Local (stdio) — for an MCP client
The published npm package is the easiest way to run it in a client:
```json
{
"mcpServers": {
"uae-intelligence": { "command": "npx", "args": ["-y", "uaemcp"] }
}
}
```
To run the **Python** implementation from source instead:
```bash
pip install -e .
uaemcp stdio
```
### Remote (HTTP) — landing page + REST + MCP endpoint
```bash
uaemcp http --host 0.0.0.0 --port 8080
```
- Landing page: `http://localhost:8080/`
- REST API: `http://localhost:8080/api/v1/...` — full [**API reference → APIs.md**](APIs.md)
- MCP (Streamable): `http://localhost:8080/mcp`
- Observability: `/health` · `/ready` · `/metrics` (Prometheus) — plus cheap `/healthz`, `/readyz`
---
## Showcase
The hosted dashboard is **bilingual (EN / العربية)** and styled with the official
**Dubai Font**:
<p align="center">
<img src="docs/assets/landing-en.png" alt="Landing page (English)" width="49%">
<img src="docs/assets/landing-ar.png" alt="Landing page (Arabic, RTL)" width="49%">
</p>
Interactive API docs — Dubai-Font-themed Swagger UI at [`/docs`](https://uaemcp.zad.tools/docs):
<p align="center">
<img src="docs/assets/swagger.png" alt="Dubai-Font Swagger UI" width="90%">
</p>
## Tools
| Tool | Kind | Description |
|------|------|-------------|
| `uae_sources_list` | read | List every registered official source |
| `uae_source_get` | read | Full metadata for one source |
| `uae_source_health` | read | Live, timeout-bounded health probe |
| `uae_source_datasets` | read | Discover datasets inside a portal (CKAN/ODS/ArcGIS) |
| `uae_source_records` | read | Live, redacted, cited records (per dataset) |
| `uae_search` | read | Federated bilingual search across the catalog |
| `uae_source_geo` | read | Spatially-filtered records as GeoJSON (maps) |
| `uae_source_aggregate` | read | group_by + count/sum/avg/min/max |
| `uae_market_snapshot` | read | Counts by emirate / area / product |
| `uae_dashboard_summary` | read | Concurrent, cached health across all sources |
| `uae_source_add_metadata` | **write** | Add a metadata source (token required) |
Every data-returning tool wraps results in `{ ok, data, error, meta }` and attaches
`source_id`, `license`, `citation`, `fetched_at`, and a `data_quality`
`{ confidence, warnings, validation }` block. Multi-dataset portals expose their
datasets via `uae_source_datasets`; pass a returned `id` as the `dataset`
argument to `uae_source_records`.
### Source status (honest by design)
**32 official sources** are registered. Data is **never fabricated** — each source
is typed by what it can actually return:
- **Live records, no key:** MOIAT industrial licenses (`http_json`) and Ajman
(`ods`, 211 datasets). Any ArcGIS FeatureServer you wire works too.
- **Key-gated official APIs** (`requires_api_key`): Dubai Pulse / DLD real-estate
transactions, Abu Dhabi Open Data — supply a developer key to enable them.
- **Metadata / discovery-only:** everything else — a portal with no reachable
machine API returns an actionable error, not empty-looking success.
## Usage examples
### Ask your MCP client (natural language)
Once connected, just ask — the client picks the right tool:
- *"List UAE industrial factories licensed in Abu Dhabi."* → `uae_source_records`
- *"Find official UAE datasets about real estate."* → `uae_search`
- *"Map licensed factories within 60 km of Abu Dhabi."* → `uae_source_geo`
- *"Break down industrial licenses by emirate."* → `uae_source_aggregate`
- *"Which official UAE open-data sources can you access?"* → `uae_sources_list`
- *"Is the Dubai Land Department data source up right now?"* → `uae_source_health`
### Call a tool over HTTP (Streamable-HTTP MCP)
```bash
# initialize → grab the mcp-session-id header, then call tools/list, tools/call
curl -s -X POST https://uaemcp.zad.tools/mcp/ \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"uae_market_snapshot","arguments":{"topic":"industry","limit":50}}}'
```
### Or hit the REST mirror directly
```bash
# 32 registered sources
curl -s https://uaemcp.zad.tools/api/v1/sources | jq '.meta.total'
# live, PII-redacted records from the MOIAT industrial-licenses API
curl -s 'https://uaemcp.zad.tools/api/v1/sources/moiat_industrial_licenses/records?limit=2'
# discover datasets in a multi-dataset portal (Ajman = OpenDataSoft, 211 datasets)
curl -s 'https://uaemcp.zad.tools/api/v1/sources/ajman_data_portal/datasets?limit=5'
# then fetch live records from one of those datasets
curl -s 'https://uaemcp.zad.tools/api/v1/sources/ajman_data_portal/records?dataset=developed-crossroads&limit=3'
# federated bilingual search (deep = also search live portal datasets)
curl -s 'https://uaemcp.zad.tools/api/v1/search?q=real%20estate&deep=true'
# GeoJSON for map apps — factories within 60 km of Abu Dhabi
curl -s 'https://uaemcp.zad.tools/api/v1/sources/moiat_industrial_licenses/geo?near=24.45,54.37,60'
# aggregate: licensed factories grouped by emirate
curl -s 'https://uaemcp.zad.tools/api/v1/sources/moiat_industrial_licenses/aggregate?group_by=EmirateNameEN'
# export records as csv | geojson | xlsx | json
curl -s -OJ 'https://uaemcp.zad.tools/api/v1/sources/moiat_industrial_licenses/export?format=geojson'
```
> Full parameter tables + real response examples for every endpoint are in the
> [**API reference (APIs.md)**](APIs.md).
```jsonc
// sample record (contact fields are redacted by default)
{
"CompanyName": "…",
"EmirateNameEN": "Abu Dhabi",
"AreaNameEN": "Musaffah Industrial",
"ContactPhone": "[redacted-open-data-contact]",
"ContactEmail": "[redacted-open-data-contact]"
}
```
Interactive API docs (Dubai-Font themed Swagger UI): <https://uaemcp.zad.tools/docs>
## Security model
- **Writes gated.** Reads are open; mutating tools require `UAEMCP_WRITE_TOKEN`.
No token set → writes are disabled entirely (safe default).
- **SSRF guard.** Every fetch resolves the host and rejects private, loopback,
link-local, and cloud-metadata addresses. See [`core/ssrf.py`](src/uaemcp/core/ssrf.py).
- **PII redaction.** Phone/email fields are redacted by name and by value pattern
before any record leaves the server.
- **Bounded fetches.** Browser-like User-Agent (so bot-mitigated portals don't hang),
strict timeouts, capped redirects and response size.
## Design notes — what this fixes vs. the prior version
- **No auth on writes** → token-gated writes, disabled by default.
- **SSRF surface** → mandatory `validate_url` on every egress.
- **stdio-only** → real remote MCP at `/mcp`.
- **`dashboard-summary` stall** → checks run **concurrently** with a strict
per-source timeout and a browser UA, served from cache, with DNS resolved
**off the event loop** so the fan-out truly overlaps. Worst case is the
slowest single source, not the sum of all.
- **Cold-start 502** → `/healthz` and `/readyz` do no upstream work.
## Development
```bash
pip install -e ".[dev]"
pytest -m "not network" # offline unit tests (ssrf, redaction, dashboard)
pytest # include live-endpoint integration tests
ruff check . && mypy src
```
## License
MIT. Data served is open government data — verify each source's terms before
redistribution.
TDQS
Scored across 11 tools
Each tool has a clearly distinct purpose: dashboard summary, market snapshot, search, source management (add, list, get, health), data retrieval (records, datasets, geo), and aggregation. No ambiguity between tools.
Tools consistently use the 'uae_' prefix. Most tools follow a 'uae_source_<action>' pattern, though 'uae_dashboard_summary' and 'uae_market_snapshot' deviate slightly. Overall pattern is predictable.
With 11 tools, the set is well-scoped for an open data intelligence server. Each tool serves a specific function without redundancy, and the count feels appropriate for the complexity of the domain.
The tool set covers core operations: search, catalog browsing, data retrieval, aggregation, and health checks. It lacks update/delete for sources, but for a read-heavy intelligence service, this is a minor gap.