uruguay-mcp
<div align="center">
<img src="https://flagcdn.com/w160/uy.png" alt="Bandera de Uruguay" width="120" />
# πΊπΎ uruguay-mcp
**Structured AI-agent access to Uruguay's open government data**
<br>
*Acceso estructurado de agentes de IA a los datos abiertos del Estado uruguayo*
[](https://pypi.org/project/uruguay-mcp/)
[](https://www.python.org/)
[](https://modelcontextprotocol.io/)
[](https://github.com/Ellweb3/uruguay-mcp/actions/workflows/ci.yml)
[](#development)
[](#development)
[](LICENSE)
π **[EspaΓ±ol](README.es.md)** Β· **English**
</div>
---
An [MCP](https://modelcontextprotocol.io/) server that gives AI agents structured
access to **Uruguay's open government data** β the national data catalog, the
Central Bank, the statistics institute, Montevideo's city data & realtime
transport, spatial data (IDE), education, health, social programs, government
social-security statistics (BPS), tax reference values (DGI), news, and the
gub.uy service catalog β behind a single **meta-discovery**
layer.
## β¨ Why a meta-discovery layer?
Instead of flooding the model with hundreds of tool definitions, the server
exposes **five meta-tools**. The model searches for what it needs, then invokes
the matching data tool by name. The prompt-visible surface stays constant no
matter how many data sources are added.
| Meta-tool | Purpose |
|---|---|
| `discover_tools(query, module?, limit?)` | Rank data tools relevant to a natural-language need (returns their argument schemas) |
| `call_tool(name, arguments)` | Invoke a data tool by name (validates arguments) |
| `list_modules()` | List data-source modules and their tool counts |
| `plan_query(goal)` | Surface candidate tools for a multi-step goal |
| `execute_batch(calls)` | Run several calls concurrently with per-call error isolation |
Every tool returns a unified envelope: `{ "_meta": { source, cached, lang, timestamp }, "data": ... }`.
> At a glance: **5 meta-tools + 84 data tools across 17 modules**, plus **54
> prompts** and **36 resources**.
## π Data sources (modules)
| | Module | Source | Protocol | Tools |
|---|---|---|---|:--:|
| ποΈ | `catalogodatos` | [catalogodatos.gub.uy](https://catalogodatos.gub.uy) β national CKAN catalog (~2680 datasets, 72 orgs) + DataStore SQL | CKAN REST | 9 |
| π΅ | `bcu` | Banco Central del Uruguay β exchange rates | SOAP (`zeep`) | 4 |
| π | `ine` | Instituto Nacional de EstadΓstica β ANDA studies + national CKAN DataStore queries | REST | 7 |
| π | `gubuy` | gub.uy public API / service catalog | CKAN REST | 4 |
| π | `montevideo` | Intendencia de Montevideo β city CKAN + realtime transport | CKAN + REST | 11 |
| ποΈ | `datastore` | Cross-source SQLite workspace β load CSV/CKAN data, run read-only SQL JOINs | local SQLite | 4 |
| π | `acce` | Agencia de Compras y Contrataciones del Estado β public procurement (OCDS) | OCDS REST/RSS + CKAN | 4 |
| βοΈ | `impo` | IMPO β legislation, normativa & Diario Oficial | REST (JSON) | 6 |
| π§Ύ | `dgi` | DGI (tax authority) β reference values (UI, IPC, ITP & late-payment rates) as `.ods` + statistical bulletins | scrape + ODS/PDF | 4 |
| π¦οΈ | `inumet` | Instituto Uruguayo de MeteorologΓa β stations, forecast & alerts | REST + HTML | 3 |
| ποΈ | `parlamento` | Parlamento del Uruguay β datasets, attendance & activity (CKAN-backed) | CKAN REST | 4 |
| πΊοΈ | `ide` | IDE Uruguay (AGESIC) β spatial data: WFS layers, cadastral parcels & geocoding | WFS 2.0 + REST | 5 |
| π | `educacion` | ANEP / education β datasets & school directories (national CKAN, org=anep) | CKAN REST | 3 |
| π₯ | `salud` | Salud (MSP / FNR) β health datasets, clinics & medication spending | CKAN REST | 5 |
| π€ | `mides` | MIDES β social programs & the *GuΓa de Recursos* service directory | CKAN + HTML | 4 |
| π§ | `bps` | Banco de PrevisiΓ³n Social β "BPS en Cifras" observatory: pensions, benefits & contributors (live indicators) | REST (JSON) | 5 |
| π° | `noticias` | gub.uy government news β latest releases & full-text search | HTML scrape | 2 |
The transport surface of `montevideo` needs OAuth2 credentials
(`URUGUAY_MCP_MVD_CLIENT_ID` / `URUGUAY_MCP_MVD_CLIENT_SECRET`); without them the
transport tools return a typed `validation_error` while the CKAN tools work
unauthenticated.
## π§© Prompts & Resources
Each module also registers reusable **prompts** (parameterized Spanish
instruction templates) and **resources** (static reference docs under the
`uru://<module>/<path>` URI scheme), exposed natively through FastMCP.
- **54 prompts** β e.g. `bcu_cotizacion_dolar_hoy`, `catalogo_buscar_por_tema`,
`bps_pasividades_actuales`, `dgi_valor_referencia`,
`ine_buscar_estudios`, `montevideo_proximo_bus`, `datastore_unir_dos_fuentes`,
`acce_analizar_compra`, `impo_consultar_norma`, `inumet_clima_actual`,
`ide_consultar_catastro`, `salud_consultar_medicamentos`, `noticias_ultimas`.
- **36 resources** β e.g. `uru://bcu/codigos-moneda`, `uru://bps/catalogo-indicadores`,
`uru://dgi/catalogo-valores`,
`uru://catalogodatos/guia-de-uso`, `uru://montevideo/credenciales-transporte`,
`uru://acce/glosario-ocds`, `uru://impo/esquema`, `uru://inumet/variables`,
`uru://ide/capas-destacadas`, `uru://salud/fuentes`, `uru://mides/guia-recursos`.
See **[EXAMPLES.md](EXAMPLES.md)** for end-to-end usage scenarios, including
cross-source ones via `plan_query` / `execute_batch` and SQL JOINs through the
`datastore` module.
## π Quick start
```bash
# Run directly from PyPI (once published)
uvx uruguay-mcp
# β¦or install it
pip install uruguay-mcp # or: uv pip install uruguay-mcp
uruguay-mcp
```
### One-command install into Claude
```bash
uruguay-mcp install
```
Merges the server into Claude Desktop's config (preserving existing
`mcpServers` and unrelated keys) and prints a ready-to-paste snippet for Claude
Code / Cursor. Restart the client afterwards.
### Claude Desktop config (manual)
```json
{
"mcpServers": {
"uruguay-mcp": { "command": "uruguay-mcp" }
}
}
```
### Run options
```bash
uruguay-mcp # stdio (default)
uruguay-mcp --transport sse --port 8000
uruguay-mcp --modules catalogodatos,bcu # load only some modules
uruguay-mcp --verbose # INFO logs (--debug for DEBUG)
```
## βοΈ Configuration
All via `URUGUAY_MCP_*` environment variables:
| Variable | Default | Meaning |
|---|---|---|
| `URUGUAY_MCP_LANG` | `es` | Language for human-facing strings (`es`/`en`) |
| `URUGUAY_MCP_HTTP_TIMEOUT` | `30` | HTTP timeout (seconds) |
| `URUGUAY_MCP_CACHE_TTL` | `900` | Response cache TTL (seconds) |
| `URUGUAY_MCP_RATE_LIMIT_RPS` | `5` | Max requests/sec per host |
| `URUGUAY_MCP_MODULES` | _(all)_ | Comma-separated module allowlist |
| `URUGUAY_MCP_MVD_CLIENT_ID` | _(unset)_ | OAuth2 client id for the Montevideo transport API |
| `URUGUAY_MCP_MVD_CLIENT_SECRET` | _(unset)_ | OAuth2 client secret for the Montevideo transport API |
## ποΈ Architecture
```
src/uruguay_mcp/
βββ server.py # FastMCP wiring; meta-tools + registered prompts + resources
βββ cli.py # `uruguay-mcp` / `uruguay-mcp install`; -v/--debug logging
βββ meta/ # discovery layer
β βββ tools.py # the 5 meta-tools
β βββ search.py # BM25-lite ranking over the registry
βββ shared/ # reused by every module
β βββ config.py # env-driven settings (URUGUAY_MCP_*)
β βββ http.py # async client: retries (tenacity) + per-host rate limit
β βββ cache.py # async TTL cache
β βββ envelope.py # unified {_meta, data} response (+ UTC timestamp)
β βββ i18n.py # es/en messages
β βββ errors.py # typed, localized errors
β βββ registry.py # tool/prompt/resource registry; @tool/@prompt/@resource
βββ modules/ # one self-contained package per data source
βββ catalogodatos/ βββ bcu/ βββ ine/
βββ gubuy/ βββ montevideo/ βββ datastore/
βββ acce/ βββ impo/ βββ inumet/
βββ parlamento/ βββ ide/ βββ educacion/
βββ salud/ βββ mides/ βββ noticias/
βββ bps/ βββ dgi/
```
Each module package is independent (`constants` Β· `schemas` Β· `client` Β·
`tools` Β· optional `prompts`/`resources`). Importing the package self-registers
everything it offers.
## π οΈ Development
```bash
uv venv && uv pip install -e ".[dev]"
uv run pytest # 252 unit tests (HTTP mocked, offline) Β· 86% coverage
uv run pytest -m integration # hits live government APIs
uv run ruff check src tests
uv run pyright
```
## π Acknowledgements
Built on data published by **AGESIC**, **BCU**, **INE** and the **Intendencia de
Montevideo** under Uruguay's open-data law (NΒΊ 18.381). This project is an
independent client and is not affiliated with those institutions.
## π License
[MIT](LICENSE)
TDQS
Scored across 5 tools
Each tool has a clearly distinct purpose: call_tool for invocation, discover_tools for natural language search, execute_batch for concurrent calls, list_modules for module overview, and plan_query for multi-step planning. No overlap in functionality.
All tool names follow a consistent verb_noun pattern in snake_case (call_tool, discover_tools, execute_batch, list_modules, plan_query). The naming is predictable and uniform.
With 5 tools, the server is well-scoped for a meta-tool that discovers and invokes other data tools. The number feels complete without being excessive.
The tool surface covers the full workflow: discovering tools (discover_tools), planning multi-step queries (plan_query), invoking individual tools (call_tool), batch execution (execute_batch), and listing available modules (list_modules). No obvious gaps for its stated purpose.