uruguay-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. uruguay_mcp-0.1.0/.gitignore +28 -0
  2. uruguay_mcp-0.1.0/EXAMPLES.md +282 -0
  3. uruguay_mcp-0.1.0/LICENSE +21 -0
  4. uruguay_mcp-0.1.0/PKG-INFO +206 -0
  5. uruguay_mcp-0.1.0/README.es.md +182 -0
  6. uruguay_mcp-0.1.0/README.md +180 -0
  7. uruguay_mcp-0.1.0/pyproject.toml +65 -0
  8. uruguay_mcp-0.1.0/src/uruguay_mcp/__init__.py +3 -0
  9. uruguay_mcp-0.1.0/src/uruguay_mcp/__main__.py +4 -0
  10. uruguay_mcp-0.1.0/src/uruguay_mcp/cli.py +164 -0
  11. uruguay_mcp-0.1.0/src/uruguay_mcp/meta/__init__.py +1 -0
  12. uruguay_mcp-0.1.0/src/uruguay_mcp/meta/search.py +53 -0
  13. uruguay_mcp-0.1.0/src/uruguay_mcp/meta/tools.py +124 -0
  14. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/__init__.py +29 -0
  15. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/bcu/__init__.py +27 -0
  16. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/bcu/client.py +134 -0
  17. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/bcu/constants.py +27 -0
  18. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/bcu/prompts.py +57 -0
  19. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/bcu/resources.py +53 -0
  20. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/bcu/schemas.py +57 -0
  21. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/bcu/tools.py +137 -0
  22. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/catalogodatos/__init__.py +26 -0
  23. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/catalogodatos/client.py +49 -0
  24. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/catalogodatos/constants.py +17 -0
  25. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/catalogodatos/prompts.py +59 -0
  26. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/catalogodatos/resources.py +60 -0
  27. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/catalogodatos/schemas.py +42 -0
  28. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/catalogodatos/tools.py +159 -0
  29. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/datastore/__init__.py +27 -0
  30. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/datastore/client.py +255 -0
  31. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/datastore/constants.py +31 -0
  32. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/datastore/prompts.py +42 -0
  33. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/datastore/resources.py +46 -0
  34. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/datastore/schemas.py +44 -0
  35. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/datastore/tools.py +73 -0
  36. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/gubuy/__init__.py +25 -0
  37. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/gubuy/client.py +46 -0
  38. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/gubuy/constants.py +29 -0
  39. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/gubuy/prompts.py +71 -0
  40. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/gubuy/resources.py +67 -0
  41. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/gubuy/schemas.py +38 -0
  42. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/gubuy/tools.py +190 -0
  43. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/ine/__init__.py +26 -0
  44. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/ine/client.py +68 -0
  45. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/ine/constants.py +31 -0
  46. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/ine/prompts.py +68 -0
  47. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/ine/resources.py +68 -0
  48. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/ine/schemas.py +45 -0
  49. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/ine/tools.py +211 -0
  50. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/montevideo/__init__.py +28 -0
  51. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/montevideo/client.py +148 -0
  52. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/montevideo/constants.py +48 -0
  53. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/montevideo/prompts.py +83 -0
  54. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/montevideo/resources.py +77 -0
  55. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/montevideo/schemas.py +102 -0
  56. uruguay_mcp-0.1.0/src/uruguay_mcp/modules/montevideo/tools.py +412 -0
  57. uruguay_mcp-0.1.0/src/uruguay_mcp/server.py +74 -0
  58. uruguay_mcp-0.1.0/src/uruguay_mcp/shared/__init__.py +1 -0
  59. uruguay_mcp-0.1.0/src/uruguay_mcp/shared/cache.py +47 -0
  60. uruguay_mcp-0.1.0/src/uruguay_mcp/shared/config.py +40 -0
  61. uruguay_mcp-0.1.0/src/uruguay_mcp/shared/envelope.py +48 -0
  62. uruguay_mcp-0.1.0/src/uruguay_mcp/shared/errors.py +50 -0
  63. uruguay_mcp-0.1.0/src/uruguay_mcp/shared/http.py +85 -0
  64. uruguay_mcp-0.1.0/src/uruguay_mcp/shared/i18n.py +36 -0
  65. uruguay_mcp-0.1.0/src/uruguay_mcp/shared/rate_limiter.py +43 -0
  66. uruguay_mcp-0.1.0/src/uruguay_mcp/shared/registry.py +223 -0
  67. uruguay_mcp-0.1.0/tests/__init__.py +0 -0
  68. uruguay_mcp-0.1.0/tests/test_bcu.py +228 -0
  69. uruguay_mcp-0.1.0/tests/test_catalogodatos.py +114 -0
  70. uruguay_mcp-0.1.0/tests/test_cli.py +97 -0
  71. uruguay_mcp-0.1.0/tests/test_datastore.py +138 -0
  72. uruguay_mcp-0.1.0/tests/test_gubuy.py +198 -0
  73. uruguay_mcp-0.1.0/tests/test_ine.py +212 -0
  74. uruguay_mcp-0.1.0/tests/test_meta.py +54 -0
  75. uruguay_mcp-0.1.0/tests/test_montevideo.py +294 -0
  76. uruguay_mcp-0.1.0/tests/test_server.py +70 -0
  77. uruguay_mcp-0.1.0/uv.lock +2055 -0
@@ -0,0 +1,28 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ .venv/
5
+ venv/
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+
10
+ # uv
11
+ .uv/
12
+
13
+ # Tooling
14
+ .pytest_cache/
15
+ .ruff_cache/
16
+ .coverage
17
+ htmlcov/
18
+ .pyright/
19
+
20
+ # Local data / cache
21
+ *.sqlite
22
+ *.db
23
+ .cache/
24
+
25
+ # Editors / OS
26
+ .DS_Store
27
+ .idea/
28
+ .vscode/
@@ -0,0 +1,282 @@
1
+ # Ejemplos de uso de uruguay_mcp
2
+
3
+ `uruguay_mcp` expone solo **5 meta-herramientas**. Todo dato concreto se alcanza a
4
+ travΓ©s de ellas: primero `discover_tools` (o `plan_query`) para encontrar la
5
+ herramienta adecuada y su esquema, luego `call_tool` para ejecutarla, o
6
+ `execute_batch` para correr varias en paralelo.
7
+
8
+ Meta-tools:
9
+
10
+ - `list_modules()` β€” mΓ³dulos disponibles y cuΓ‘ntas tools ofrece cada uno.
11
+ - `discover_tools(query, module=None, limit=8)` β€” busca tools por necesidad.
12
+ - `plan_query(goal)` β€” candidatos de varias fuentes para un objetivo amplio.
13
+ - `call_tool(name, arguments)` β€” ejecuta una tool por nombre.
14
+ - `execute_batch(calls)` β€” ejecuta `[{name, arguments}, ...]` en paralelo.
15
+
16
+ MΓ³dulos cargados por defecto: `catalogodatos`, `bcu`, `ine`, `gubuy`,
17
+ `montevideo`, `datastore` (31 data tools). Los nombres de tools usados abajo son
18
+ los reales registrados en cada mΓ³dulo.
19
+
20
+ ---
21
+
22
+ ## 1. CotizaciΓ³n del dΓ³lar (BCU)
23
+
24
+ **Objetivo:** ΒΏA cuΓ‘nto cerrΓ³ el dΓ³lar en el ΓΊltimo dΓ­a hΓ‘bil?
25
+
26
+ ```text
27
+ discover_tools(query="cotizaciΓ³n del dΓ³lar BCU", module="bcu")
28
+ -> sugiere bcu_cotizacion_usd
29
+
30
+ call_tool("bcu_cotizacion_usd", {})
31
+ ```
32
+
33
+ Sin `fecha`, usa el ΓΊltimo cierre. Devuelve `compra` (TCC) y `venta` (TCV) del
34
+ dΓ³lar billete (moneda 2225) envueltos en el sobre estΓ‘ndar (`data`, `api`,
35
+ `url`, `cached`).
36
+
37
+ ---
38
+
39
+ ## 2. Serie de tipos de cambio en un rango (BCU)
40
+
41
+ **Objetivo:** Tipo de cambio del dΓ³lar y el real entre dos fechas.
42
+
43
+ ```text
44
+ call_tool("bcu_listar_monedas", {"grupo": 2})
45
+ -> tomar los cΓ³digos (ej. 2225 = dΓ³lar billete, 1001 = real)
46
+
47
+ call_tool("bcu_cotizaciones", {
48
+ "monedas": [2225, 1001],
49
+ "fecha_desde": "2025-01-02",
50
+ "fecha_hasta": "2025-01-31",
51
+ "grupo": 2
52
+ })
53
+ ```
54
+
55
+ Primero `bcu_listar_monedas` para conocer los cΓ³digos numΓ©ricos, luego
56
+ `bcu_cotizaciones` con el rango. Devuelve una fila por moneda y fecha con
57
+ `compra`/`venta`/`arbitraje`.
58
+
59
+ ---
60
+
61
+ ## 3. Buscar y abrir un dataset del catΓ‘logo nacional (catalogodatos)
62
+
63
+ **Objetivo:** Encontrar datos abiertos sobre presupuesto pΓΊblico.
64
+
65
+ ```text
66
+ discover_tools(query="datasets de presupuesto datos abiertos")
67
+ -> catalogo_search_datasets
68
+
69
+ call_tool("catalogo_search_datasets", {"query": "presupuesto", "rows": 10})
70
+ -> elegir un dataset y tomar su 'name'/'id'
71
+
72
+ call_tool("catalogo_get_dataset", {"id": "presupuesto-nacional"})
73
+ ```
74
+
75
+ `catalogo_get_dataset` devuelve los `resources`; quedate con el `resource_id`
76
+ de uno que tenga datastore activo para el ejemplo 4.
77
+
78
+ ---
79
+
80
+ ## 4. Consultar registros tabulares de un recurso CKAN (catalogodatos)
81
+
82
+ **Objetivo:** Leer filas de un recurso con datastore, filtrando por texto.
83
+
84
+ ```text
85
+ call_tool("catalogo_query_datastore", {
86
+ "resource_id": "a1b2c3d4-0000-1111-2222-333344445555",
87
+ "query": "Montevideo",
88
+ "limit": 50,
89
+ "offset": 0
90
+ })
91
+ ```
92
+
93
+ `resource_id` sale del paso 3 (`catalogo_get_dataset`). Solo funciona sobre
94
+ recursos con datastore activo; si no lo tienen, descargΓ‘ el archivo desde su
95
+ `url`.
96
+
97
+ ---
98
+
99
+ ## 5. Estudios estadΓ­sticos del INE (INE / ANDA)
100
+
101
+ **Objetivo:** Hallar el estudio del censo y abrir su ficha.
102
+
103
+ ```text
104
+ discover_tools(query="censo de poblaciΓ³n INE", module="ine")
105
+ -> ine_search_studies
106
+
107
+ call_tool("ine_search_studies", {
108
+ "query": "censo", "rows": 10, "sort_by": "year", "sort_order": "desc"
109
+ })
110
+ -> copiar el 'idno' ANDA (NO el id numΓ©rico)
111
+
112
+ call_tool("ine_get_study", {"idno": "URY-INE-CENSO-2023-v01"})
113
+ ```
114
+
115
+ Ojo: `ine_get_study` requiere el `idno` (cadena ANDA), no el id numΓ©rico que
116
+ aparece en la bΓΊsqueda.
117
+
118
+ ---
119
+
120
+ ## 6. Datasets CKAN del INE
121
+
122
+ **Objetivo:** Listar los datasets de datos abiertos publicados por el INE.
123
+
124
+ ```text
125
+ call_tool("ine_list_ckan_datasets", {"query": "precios", "rows": 20})
126
+ ```
127
+
128
+ Devuelve los datasets CKAN del INE (complementa a los estudios ANDA del
129
+ ejemplo 5). Para leer las filas de uno de sus recursos, seguΓ­ con
130
+ `catalogo_query_datastore` usando el `resource_id` correspondiente.
131
+
132
+ ---
133
+
134
+ ## 7. Servicios y APIs de gub.uy (gubuy)
135
+
136
+ **Objetivo:** Ver quΓ© servicios/APIs de gobierno existen sobre transporte.
137
+
138
+ ```text
139
+ discover_tools(query="APIs de gobierno transporte gub.uy", module="gubuy")
140
+ -> gubuy_search_apis, gubuy_list_servicios
141
+
142
+ execute_batch([
143
+ {"name": "gubuy_search_apis", "arguments": {"query": "transporte", "rows": 10}},
144
+ {"name": "gubuy_list_servicios", "arguments": {"query": "transporte", "limit": 10}}
145
+ ])
146
+ ```
147
+
148
+ `execute_batch` corre ambas en paralelo con aislamiento de errores. TomΓ‘ un
149
+ `id`/`showcase_id` y profundizΓ‘ con `gubuy_get_servicio` o
150
+ `gubuy_servicio_datasets`.
151
+
152
+ ---
153
+
154
+ ## 8. PrΓ³ximo Γ³mnibus en una parada (montevideo, transporte)
155
+
156
+ **Objetivo:** ΒΏCuΓ‘ndo llega el 103 a una parada?
157
+
158
+ ```text
159
+ discover_tools(query="cuΓ‘ndo llega el prΓ³ximo bus a una parada", module="montevideo")
160
+ -> montevideo_list_busstops, montevideo_busstop_lines, montevideo_bus_eta
161
+
162
+ call_tool("montevideo_list_busstops", {"query": "18 de Julio", "limit": 20})
163
+ -> tomar el busstop_id
164
+
165
+ call_tool("montevideo_busstop_lines", {"busstop_id": 4567})
166
+ -> confirmar quΓ© lΓ­neas pasan
167
+
168
+ call_tool("montevideo_bus_eta", {"busstop_id": 4567, "lines": ["103"], "amount_per_line": 2})
169
+ ```
170
+
171
+ `montevideo_bus_eta` exige `busstop_id` y al menos una lΓ­nea. La unidad de
172
+ `eta` no estΓ‘ documentada (segundos o minutos): se devuelve sin transformar.
173
+
174
+ ---
175
+
176
+ ## 9. Buses cerca de un punto en tiempo real (montevideo, transporte)
177
+
178
+ **Objetivo:** Ver buses circulando cerca de una ubicaciΓ³n.
179
+
180
+ ```text
181
+ call_tool("montevideo_buses_near", {
182
+ "lat": -34.9011, "lng": -56.1645, "radius_m": 500
183
+ })
184
+ ```
185
+
186
+ Devuelve posiciones GPS en tiempo real dentro del radio. Alternativa filtrada:
187
+ `montevideo_bus_positions` por `lines`, `company` o `busstop_id`.
188
+
189
+ ---
190
+
191
+ ## 10. CRUZADO β€” Transporte + multas de trΓ‘nsito de Montevideo
192
+
193
+ **Objetivo:** Para un punto del centro, mostrar quΓ© buses pasan cerca y, de
194
+ paso, el panorama de multas de trΓ‘nsito (SUCIVE) de ese aΓ±o.
195
+
196
+ ```text
197
+ discover_tools(query="buses cerca y multas de trΓ‘nsito Montevideo", module="montevideo")
198
+ -> montevideo_buses_near, montevideo_multas_transito
199
+
200
+ execute_batch([
201
+ {"name": "montevideo_buses_near",
202
+ "arguments": {"lat": -34.9061, "lng": -56.1914, "radius_m": 400}},
203
+ {"name": "montevideo_multas_transito",
204
+ "arguments": {"year": 2018}}
205
+ ])
206
+ ```
207
+
208
+ Dos fuentes distintas dentro del mismo mΓ³dulo, resueltas en paralelo:
209
+ tiempo real de transporte + el Γ­ndice estadΓ­stico de multas. `multas_transito`
210
+ devuelve archivos anuales descargables y tablas de referencia (ordenanzas,
211
+ tipos de vehΓ­culo): son datos AGREGADOS, no una consulta de deuda por
212
+ matrΓ­cula.
213
+
214
+ ---
215
+
216
+ ## 11. CRUZADO β€” BCU + dataset del catΓ‘logo (tipo de cambio para contextualizar)
217
+
218
+ **Objetivo:** Cruzar la cotizaciΓ³n oficial del dΓ³lar con un dataset de precios
219
+ o presupuesto del catΓ‘logo nacional para anΓ‘lisis monetario.
220
+
221
+ ```text
222
+ plan_query(goal="contextualizar precios en pesos con el tipo de cambio del dΓ³lar")
223
+ -> candidatos: bcu_cotizacion_usd, catalogo_search_datasets, catalogo_query_datastore
224
+
225
+ execute_batch([
226
+ {"name": "bcu_cotizacion_usd", "arguments": {}},
227
+ {"name": "catalogo_search_datasets","arguments": {"query": "Γ­ndice de precios", "rows": 5}}
228
+ ])
229
+ -> con un resource_id del resultado del catΓ‘logo:
230
+
231
+ call_tool("catalogo_query_datastore", {
232
+ "resource_id": "ipc-0000-1111-2222-333344445555",
233
+ "limit": 100
234
+ })
235
+ ```
236
+
237
+ `plan_query` propone tools de **dos mΓ³dulos** (`bcu` + `catalogodatos`). Se trae
238
+ el dΓ³lar y el dataset en paralelo con `execute_batch`, y luego se leen las filas
239
+ del recurso para convertir/contextualizar montos.
240
+
241
+ ---
242
+
243
+ ## 12. CRUZADO β€” Dos tablas CKAN para un JOIN lΓ³gico
244
+
245
+ **Objetivo:** Combinar dos recursos CKAN (uno del catΓ‘logo nacional, otro del
246
+ portal de Montevideo) para relacionar registros β€” un JOIN hecho del lado del
247
+ cliente con los resultados de dos datastores.
248
+
249
+ ```text
250
+ discover_tools(query="consultar registros de un recurso CKAN", limit=8)
251
+ -> catalogo_query_datastore, montevideo_query_datastore
252
+
253
+ # Tabla A: recurso del catΓ‘logo nacional
254
+ call_tool("catalogo_query_datastore", {
255
+ "resource_id": "padron-organismos-0000-1111-2222-3333",
256
+ "limit": 500
257
+ })
258
+
259
+ # Tabla B: recurso del portal de Montevideo
260
+ call_tool("montevideo_query_datastore", {
261
+ "resource_id": "arbolado-publico-aaaa-bbbb-cccc-dddd",
262
+ "limit": 500
263
+ })
264
+ ```
265
+
266
+ Cada `*_query_datastore` devuelve registros de su portal CKAN respectivo;
267
+ el JOIN (por una clave comΓΊn, ej. `organismo` o `barrio`) se arma combinando
268
+ los dos resultados. Para empujar el JOIN a un motor SQL real usΓ‘ el mΓ³dulo
269
+ `datastore` (cargado por defecto): `datastore_load_ckan_resource` para subir
270
+ cada recurso a una tabla SQLite y `datastore_sql` para el JOIN con SELECT.
271
+
272
+ ---
273
+
274
+ ## Notas
275
+
276
+ - Toda respuesta de data tool viene en el sobre estΓ‘ndar: `data`, `api`, `url`,
277
+ `cached` (y `error` ante fallos).
278
+ - `discover_tools` devuelve el esquema de argumentos de cada candidato: leelo
279
+ antes de armar el `arguments` de `call_tool`.
280
+ - `execute_batch` aΓ­sla errores por llamada: una falla no aborta el resto.
281
+ - Los `resource_id` y `busstop_id` de los ejemplos son ilustrativos; obtenelos
282
+ siempre del paso de bΓΊsqueda/listado previo.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 uruguay-mcp contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,206 @@
1
+ Metadata-Version: 2.4
2
+ Name: uruguay-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for Uruguay's open government data (national catalog, BCU, INE, Montevideo, gub.uy)
5
+ Author: uruguay-mcp contributors
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: bcu,ckan,ine,mcp,montevideo,open-data,uruguay
9
+ Requires-Python: >=3.11
10
+ Requires-Dist: aiocache>=0.12
11
+ Requires-Dist: fastmcp>=2.3.0
12
+ Requires-Dist: httpx>=0.27
13
+ Requires-Dist: pydantic-settings>=2.3
14
+ Requires-Dist: pydantic>=2.7
15
+ Requires-Dist: structlog>=24.1
16
+ Requires-Dist: tenacity>=8.3
17
+ Requires-Dist: zeep>=4.2
18
+ Provides-Extra: dev
19
+ Requires-Dist: pyright>=1.1; extra == 'dev'
20
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
21
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
22
+ Requires-Dist: pytest>=8.2; extra == 'dev'
23
+ Requires-Dist: respx>=0.21; extra == 'dev'
24
+ Requires-Dist: ruff>=0.5; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ <div align="center">
28
+
29
+ <img src="https://flagcdn.com/w160/uy.png" alt="Bandera de Uruguay" width="120" />
30
+
31
+ # πŸ‡ΊπŸ‡Ύ uruguay-mcp
32
+
33
+ **Structured AI-agent access to Uruguay's open government data**
34
+ <br>
35
+ *Acceso estructurado de agentes de IA a los datos abiertos del Estado uruguayo*
36
+
37
+ [![PyPI](https://img.shields.io/pypi/v/uruguay-mcp?color=blue&label=PyPI)](https://pypi.org/project/uruguay-mcp/)
38
+ [![Python](https://img.shields.io/badge/python-3.11%2B-blue)](https://www.python.org/)
39
+ [![MCP](https://img.shields.io/badge/MCP-Model%20Context%20Protocol-7C3AED)](https://modelcontextprotocol.io/)
40
+ [![Tests](https://img.shields.io/badge/tests-69%20passing-brightgreen)](#development)
41
+ [![Coverage](https://img.shields.io/badge/coverage-86%25-brightgreen)](#development)
42
+ [![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
43
+
44
+ 🌎 **[Español](README.es.md)** · **English**
45
+
46
+ </div>
47
+
48
+ ---
49
+
50
+ An [MCP](https://modelcontextprotocol.io/) server that gives AI agents structured
51
+ access to **Uruguay's open government data** β€” the national data catalog, the
52
+ Central Bank, the statistics institute, Montevideo's city data & realtime
53
+ transport, and the gub.uy service catalog β€” behind a single **meta-discovery**
54
+ layer.
55
+
56
+ ## ✨ Why a meta-discovery layer?
57
+
58
+ Instead of flooding the model with hundreds of tool definitions, the server
59
+ exposes **five meta-tools**. The model searches for what it needs, then invokes
60
+ the matching data tool by name. The prompt-visible surface stays constant no
61
+ matter how many data sources are added.
62
+
63
+ | Meta-tool | Purpose |
64
+ |---|---|
65
+ | `discover_tools(query, module?, limit?)` | Rank data tools relevant to a natural-language need (returns their argument schemas) |
66
+ | `call_tool(name, arguments)` | Invoke a data tool by name (validates arguments) |
67
+ | `list_modules()` | List data-source modules and their tool counts |
68
+ | `plan_query(goal)` | Surface candidate tools for a multi-step goal |
69
+ | `execute_batch(calls)` | Run several calls concurrently with per-call error isolation |
70
+
71
+ Every tool returns a unified envelope: `{ "_meta": { source, cached, lang, timestamp }, "data": ... }`.
72
+
73
+ > At a glance: **5 meta-tools + 31 data tools across 6 modules**, plus **17
74
+ > prompts** and **11 resources**.
75
+
76
+ ## πŸ“š Data sources (modules)
77
+
78
+ | | Module | Source | Protocol | Tools |
79
+ |---|---|---|---|:--:|
80
+ | πŸ›οΈ | `catalogodatos` | [catalogodatos.gub.uy](https://catalogodatos.gub.uy) β€” national CKAN catalog (~2680 datasets, 72 orgs) | CKAN REST | 5 |
81
+ | πŸ’΅ | `bcu` | Banco Central del Uruguay β€” exchange rates | SOAP (`zeep`) | 4 |
82
+ | πŸ“Š | `ine` | Instituto Nacional de EstadΓ­stica β€” ANDA / microdata | REST | 3 |
83
+ | 🌐 | `gubuy` | gub.uy public API / service catalog | CKAN REST | 4 |
84
+ | 🚌 | `montevideo` | Intendencia de Montevideo β€” city CKAN + realtime transport | CKAN + REST | 11 |
85
+ | πŸ—„οΈ | `datastore` | Cross-source SQLite workspace β€” load CSV/CKAN data, run read-only SQL JOINs | local SQLite | 4 |
86
+
87
+ The transport surface of `montevideo` needs OAuth2 credentials
88
+ (`URUGUAY_MCP_MVD_CLIENT_ID` / `URUGUAY_MCP_MVD_CLIENT_SECRET`); without them the
89
+ transport tools return a typed `validation_error` while the CKAN tools work
90
+ unauthenticated.
91
+
92
+ ## 🧩 Prompts & Resources
93
+
94
+ Each module also registers reusable **prompts** (parameterized Spanish
95
+ instruction templates) and **resources** (static reference docs under the
96
+ `uru://<module>/<path>` URI scheme), exposed natively through FastMCP.
97
+
98
+ - **17 prompts** β€” e.g. `bcu_cotizacion_dolar_hoy`, `catalogo_buscar_por_tema`,
99
+ `ine_buscar_estudios`, `montevideo_proximo_bus`, `datastore_unir_dos_fuentes`.
100
+ - **11 resources** β€” e.g. `uru://bcu/codigos-moneda`,
101
+ `uru://catalogodatos/guia-de-uso`, `uru://montevideo/credenciales-transporte`.
102
+
103
+ See **[EXAMPLES.md](EXAMPLES.md)** for end-to-end usage scenarios, including
104
+ cross-source ones via `plan_query` / `execute_batch` and SQL JOINs through the
105
+ `datastore` module.
106
+
107
+ ## πŸš€ Quick start
108
+
109
+ ```bash
110
+ # Run directly from PyPI (once published)
111
+ uvx uruguay-mcp
112
+
113
+ # …or install it
114
+ pip install uruguay-mcp # or: uv pip install uruguay-mcp
115
+ uruguay-mcp
116
+ ```
117
+
118
+ ### One-command install into Claude
119
+
120
+ ```bash
121
+ uruguay-mcp install
122
+ ```
123
+
124
+ Merges the server into Claude Desktop's config (preserving existing
125
+ `mcpServers` and unrelated keys) and prints a ready-to-paste snippet for Claude
126
+ Code / Cursor. Restart the client afterwards.
127
+
128
+ ### Claude Desktop config (manual)
129
+
130
+ ```json
131
+ {
132
+ "mcpServers": {
133
+ "uruguay-mcp": { "command": "uruguay-mcp" }
134
+ }
135
+ }
136
+ ```
137
+
138
+ ### Run options
139
+
140
+ ```bash
141
+ uruguay-mcp # stdio (default)
142
+ uruguay-mcp --transport sse --port 8000
143
+ uruguay-mcp --modules catalogodatos,bcu # load only some modules
144
+ uruguay-mcp --verbose # INFO logs (--debug for DEBUG)
145
+ ```
146
+
147
+ ## βš™οΈ Configuration
148
+
149
+ All via `URUGUAY_MCP_*` environment variables:
150
+
151
+ | Variable | Default | Meaning |
152
+ |---|---|---|
153
+ | `URUGUAY_MCP_LANG` | `es` | Language for human-facing strings (`es`/`en`) |
154
+ | `URUGUAY_MCP_HTTP_TIMEOUT` | `30` | HTTP timeout (seconds) |
155
+ | `URUGUAY_MCP_CACHE_TTL` | `900` | Response cache TTL (seconds) |
156
+ | `URUGUAY_MCP_RATE_LIMIT_RPS` | `5` | Max requests/sec per host |
157
+ | `URUGUAY_MCP_MODULES` | _(all)_ | Comma-separated module allowlist |
158
+ | `URUGUAY_MCP_MVD_CLIENT_ID` | _(unset)_ | OAuth2 client id for the Montevideo transport API |
159
+ | `URUGUAY_MCP_MVD_CLIENT_SECRET` | _(unset)_ | OAuth2 client secret for the Montevideo transport API |
160
+
161
+ ## πŸ—οΈ Architecture
162
+
163
+ ```
164
+ src/uruguay_mcp/
165
+ β”œβ”€β”€ server.py # FastMCP wiring; meta-tools + registered prompts + resources
166
+ β”œβ”€β”€ cli.py # `uruguay-mcp` / `uruguay-mcp install`; -v/--debug logging
167
+ β”œβ”€β”€ meta/ # discovery layer
168
+ β”‚ β”œβ”€β”€ tools.py # the 5 meta-tools
169
+ β”‚ └── search.py # BM25-lite ranking over the registry
170
+ β”œβ”€β”€ shared/ # reused by every module
171
+ β”‚ β”œβ”€β”€ config.py # env-driven settings (URUGUAY_MCP_*)
172
+ β”‚ β”œβ”€β”€ http.py # async client: retries (tenacity) + per-host rate limit
173
+ β”‚ β”œβ”€β”€ cache.py # async TTL cache
174
+ β”‚ β”œβ”€β”€ envelope.py # unified {_meta, data} response (+ UTC timestamp)
175
+ β”‚ β”œβ”€β”€ i18n.py # es/en messages
176
+ β”‚ β”œβ”€β”€ errors.py # typed, localized errors
177
+ β”‚ └── registry.py # tool/prompt/resource registry; @tool/@prompt/@resource
178
+ └── modules/ # one self-contained package per data source
179
+ β”œβ”€β”€ catalogodatos/ β”œβ”€β”€ bcu/ β”œβ”€β”€ ine/
180
+ β”œβ”€β”€ gubuy/ β”œβ”€β”€ montevideo/ └── datastore/
181
+ ```
182
+
183
+ Each module package is independent (`constants` Β· `schemas` Β· `client` Β·
184
+ `tools` Β· optional `prompts`/`resources`). Importing the package self-registers
185
+ everything it offers.
186
+
187
+ ## πŸ› οΈ Development
188
+
189
+ ```bash
190
+ uv venv && uv pip install -e ".[dev]"
191
+
192
+ uv run pytest # 69 unit tests (HTTP mocked, offline) Β· 86% coverage
193
+ uv run pytest -m integration # hits live government APIs
194
+ uv run ruff check src tests
195
+ uv run pyright
196
+ ```
197
+
198
+ ## πŸ™Œ Acknowledgements
199
+
200
+ Built on data published by **AGESIC**, **BCU**, **INE** and the **Intendencia de
201
+ Montevideo** under Uruguay's open-data law (NΒΊ 18.381). This project is an
202
+ independent client and is not affiliated with those institutions.
203
+
204
+ ## πŸ“„ License
205
+
206
+ [MIT](LICENSE)