@opendata.cat/mcp-server 0.0.11 → 0.0.13
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.
- package/README.md +33 -2
- package/dist/index.js +140 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -154,6 +154,12 @@ Prompts predefinits que guien l'LLM pas a pas per fer analisis completes:
|
|
|
154
154
|
| `compara_municipis` | Compara dos municipis en totes les dades disponibles | `municipi_a`, `municipi_b` |
|
|
155
155
|
| `descobreix_dades` | Mapa complet de dades obertes sobre un tema | `tema` |
|
|
156
156
|
| `analisi_bombers` | Actuacions dels Bombers: emergencies, distribucio, tendencies | `comarca` (opcional) |
|
|
157
|
+
| `novetats` | Datasets actualitzats mes recentment | `portal` (opcional) |
|
|
158
|
+
| `datasets_populars` | Datasets mes consultats pels usuaris | — |
|
|
159
|
+
| `explorar_portal` | Guia completa d'un portal: categories, exemples, destacats | `portal` |
|
|
160
|
+
| `dades_municipi` | Fitxa completa d'un municipi amb totes les dades disponibles | `municipi` |
|
|
161
|
+
| `datasets_temps_real` | Datasets amb dades en temps real o actualitzacio frequent | — |
|
|
162
|
+
| `resum_portals` | Visio panoramica de tots els portals de dades obertes | — |
|
|
157
163
|
|
|
158
164
|
## Exemples d'us
|
|
159
165
|
|
|
@@ -167,6 +173,12 @@ Un cop configurat, pots fer preguntes al teu LLM com:
|
|
|
167
173
|
- *"Quines dades obertes hi ha sobre educacio a Catalunya?"* → prompt `descobreix_dades`
|
|
168
174
|
- *"Dona'm les ultimes dades de pressupostos de Reus"* → prompt `pressupostos_municipals`
|
|
169
175
|
- *"Analitza les actuacions dels Bombers al Valles"* → prompt `analisi_bombers`
|
|
176
|
+
- *"Quines novetats hi ha en dades obertes?"* → prompt `novetats`
|
|
177
|
+
- *"Quins son els datasets mes consultats?"* → prompt `datasets_populars`
|
|
178
|
+
- *"Explora'm el portal de la Generalitat"* → prompt `explorar_portal`
|
|
179
|
+
- *"Quines dades obertes te Sabadell?"* → prompt `dades_municipi`
|
|
180
|
+
- *"Quines dades en temps real hi ha?"* → prompt `datasets_temps_real`
|
|
181
|
+
- *"Dona'm un resum de tots els portals"* → prompt `resum_portals`
|
|
170
182
|
|
|
171
183
|
## Com funciona
|
|
172
184
|
|
|
@@ -181,6 +193,24 @@ Usuari → LLM → MCP opendata.cat → API opendata.cat (cataleg)
|
|
|
181
193
|
|
|
182
194
|
No emmagatzema ni fa de proxy de dades. Cada consulta va directament a la font oficial.
|
|
183
195
|
|
|
196
|
+
## API REST
|
|
197
|
+
|
|
198
|
+
A mes del servidor MCP, opendata.cat ofereix una API REST publica per accedir al cataleg de datasets:
|
|
199
|
+
|
|
200
|
+
| Endpoint | Descripcio |
|
|
201
|
+
|----------|-----------|
|
|
202
|
+
| `GET /api/datasets.php?q=...` | Cerca datasets per text lliure |
|
|
203
|
+
| `GET /api/dataset.php?id=...` | Detall complet d'un dataset |
|
|
204
|
+
| `GET /api/categories.php` | Categories i portals amb comptadors |
|
|
205
|
+
| `GET /api/portals.php` | Portals de transparencia (1.769) |
|
|
206
|
+
| `GET /api/stats.php` | Estadistiques agregades |
|
|
207
|
+
| `GET /api/mcp-stats.php` | Metriques d'us del MCP |
|
|
208
|
+
| `POST /api/mcp` | Servidor MCP (Streamable HTTP) |
|
|
209
|
+
|
|
210
|
+
Documentacio interactiva (Swagger): **[opendata.cat/api/docs.html](https://opendata.cat/api/docs.html)**
|
|
211
|
+
|
|
212
|
+
Especificacio OpenAPI: [`opendata.cat/api/openapi.json`](https://opendata.cat/api/openapi.json)
|
|
213
|
+
|
|
184
214
|
## Sobre opendata.cat
|
|
185
215
|
|
|
186
216
|
[opendata.cat](https://opendata.cat) es una associacio catalana sense anim de lucre fundada el 2012 (registre 47468) dedicada a promoure la transparencia i l'acces a la informacio publica. Treballa en tres eixos: **estandarditzacio** de formats i protocols, **formacio** especialitzada per a professionals i administracions, i **collaboracio** publico-privada per a l'obertura de dades.
|
|
@@ -194,8 +224,9 @@ Les contribucions son benvingudes! Per afegir un nou portal de dades obertes:
|
|
|
194
224
|
|
|
195
225
|
## Changelog
|
|
196
226
|
|
|
197
|
-
### v0.0.
|
|
198
|
-
-
|
|
227
|
+
### v0.0.12 (2026-04-13)
|
|
228
|
+
- 14 prompts predefinits (8 analisi + 6 descobriment)
|
|
229
|
+
- Nous prompts: novetats, datasets_populars, explorar_portal, dades_municipi, datasets_temps_real, resum_portals
|
|
199
230
|
- Crawler incremental: UPSERT en lloc de TRUNCATE, no perd dades si un portal falla
|
|
200
231
|
- Backup automatic despres de cada carrega
|
|
201
232
|
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { queryCido } from "./clients/cido.js";
|
|
|
12
12
|
import { queryOpendatasoft } from "./clients/opendatasoft.js";
|
|
13
13
|
const server = new McpServer({
|
|
14
14
|
name: "opendata-cat",
|
|
15
|
-
version: "0.0.
|
|
15
|
+
version: "0.0.12",
|
|
16
16
|
});
|
|
17
17
|
// Tool 1: search_datasets
|
|
18
18
|
server.tool("search_datasets", "Cerca datasets de dades obertes catalanes per text lliure. Retorna nom, descripció, portal i formats.", {
|
|
@@ -92,6 +92,26 @@ server.tool("query_dataset", "Executa una consulta contra un dataset i retorna f
|
|
|
92
92
|
}
|
|
93
93
|
else if (dataset.api_type === "opendatasoft") {
|
|
94
94
|
const data = await queryOpendatasoft(dataset.api_endpoint, filters, search, limit, offset);
|
|
95
|
+
// Detect GTFS-RT protobuf files
|
|
96
|
+
const first = data.records[0];
|
|
97
|
+
const fileField = first?.file;
|
|
98
|
+
if (fileField?.filename?.endsWith(".pb") || fileField?.filename?.endsWith(".pbf")) {
|
|
99
|
+
const dsMatch = dataset.api_endpoint.match(/dataset=([^&]+)/);
|
|
100
|
+
const baseMatch = dataset.api_endpoint.match(/(https?:\/\/[^/]+)/);
|
|
101
|
+
const infoUrl = baseMatch ? `${baseMatch[1]}/explore/dataset/${dsMatch?.[1] ?? ""}/information/` : dataset.api_endpoint;
|
|
102
|
+
return {
|
|
103
|
+
content: [{
|
|
104
|
+
type: "text",
|
|
105
|
+
text: JSON.stringify({
|
|
106
|
+
dataset: dataset.name,
|
|
107
|
+
format: "Protocol Buffers (GTFS-RT)",
|
|
108
|
+
message: "Aquest dataset conté dades en format Protocol Buffers (.pb), un format binari per a transport públic en temps real (GTFS Realtime). No es pot llegir directament com a text/JSON. Per usar-lo cal un decoder GTFS-RT (ex: gtfs-realtime-bindings).",
|
|
109
|
+
filename: fileField.filename,
|
|
110
|
+
download_url: infoUrl,
|
|
111
|
+
}, null, 2),
|
|
112
|
+
}],
|
|
113
|
+
};
|
|
114
|
+
}
|
|
95
115
|
results = data.records;
|
|
96
116
|
}
|
|
97
117
|
else {
|
|
@@ -315,6 +335,124 @@ server.prompt("analisi_bombers", "Analitza les actuacions dels Bombers de la Gen
|
|
|
315
335
|
}],
|
|
316
336
|
};
|
|
317
337
|
});
|
|
338
|
+
// ===== PROMPTS DE DESCOBRIMENT =====
|
|
339
|
+
server.prompt("novetats", "Mostra els datasets actualitzats més recentment als portals de dades obertes de Catalunya.", { portal: z.string().optional().describe("Filtrar per portal: generalitat, barcelona, diba, aoc, reus, girona, fgc") }, ({ portal }) => {
|
|
340
|
+
const filtreText = portal ? ` al portal ${portal}` : "";
|
|
341
|
+
return {
|
|
342
|
+
messages: [{
|
|
343
|
+
role: "user",
|
|
344
|
+
content: {
|
|
345
|
+
type: "text",
|
|
346
|
+
text: `Mostra els datasets de dades obertes de Catalunya actualitzats més recentment${filtreText}.\n\n`
|
|
347
|
+
+ "1. Usa list_portals per veure els portals disponibles\n"
|
|
348
|
+
+ `2. Usa search_datasets amb termes generals${portal ? ` i portal '${portal}'` : ""} per obtenir datasets\n`
|
|
349
|
+
+ "3. Per als primers 10 resultats, usa get_dataset_info per veure la data d'última actualització (last_updated)\n"
|
|
350
|
+
+ "4. Ordena per data d'actualització (més recent primer)\n"
|
|
351
|
+
+ "5. Presenta una taula amb: nom, portal, categoria, última actualització, formats\n"
|
|
352
|
+
+ "6. Destaca els que s'han actualitzat en els últims 7 dies\n\n"
|
|
353
|
+
+ "L'objectiu és descobrir quines dades es mantenen actives i actualitzades.",
|
|
354
|
+
},
|
|
355
|
+
}],
|
|
356
|
+
};
|
|
357
|
+
});
|
|
358
|
+
server.prompt("datasets_populars", "Mostra els datasets més consultats pels usuaris del MCP.", () => ({
|
|
359
|
+
messages: [{
|
|
360
|
+
role: "user",
|
|
361
|
+
content: {
|
|
362
|
+
type: "text",
|
|
363
|
+
text: "Mostra els datasets de dades obertes de Catalunya més consultats pels usuaris.\n\n"
|
|
364
|
+
+ "1. Usa search_datasets amb termes populars: 'embassament', 'qualitat aire', 'transport', 'pressupost', 'població'\n"
|
|
365
|
+
+ "2. Per cada cerca, agafa el primer resultat i usa get_dataset_info per obtenir detalls\n"
|
|
366
|
+
+ "3. Presenta un rànquing dels datasets més rellevants amb:\n"
|
|
367
|
+
+ " - Nom i portal\n"
|
|
368
|
+
+ " - Descripció breu\n"
|
|
369
|
+
+ " - Camps disponibles\n"
|
|
370
|
+
+ " - Última actualització\n"
|
|
371
|
+
+ "4. Per al top 3, fes una consulta amb query_dataset (limit: 3) per mostrar una mostra de dades reals\n"
|
|
372
|
+
+ "5. Suggereix preguntes interessants que es podrien fer a cada dataset\n\n"
|
|
373
|
+
+ "L'objectiu és inspirar l'usuari amb les possibilitats de les dades obertes.",
|
|
374
|
+
},
|
|
375
|
+
}],
|
|
376
|
+
}));
|
|
377
|
+
server.prompt("explorar_portal", "Explora un portal de dades obertes: quants datasets té, categories, exemples de cada tipus.", { portal: z.string().describe("Portal a explorar: generalitat, barcelona, diba, aoc, reus, girona, fgc") }, ({ portal }) => ({
|
|
378
|
+
messages: [{
|
|
379
|
+
role: "user",
|
|
380
|
+
content: {
|
|
381
|
+
type: "text",
|
|
382
|
+
text: `Fes una exploració completa del portal de dades obertes '${portal}'.\n\n`
|
|
383
|
+
+ "1. Usa list_portals per obtenir el nombre total de datasets\n"
|
|
384
|
+
+ "2. Usa list_categories per veure les categories disponibles al portal\n"
|
|
385
|
+
+ `3. Usa search_datasets amb portal '${portal}' i limit 50 per veure tots els datasets\n`
|
|
386
|
+
+ "4. Agrupa-los per categoria i presenta una taula resum\n"
|
|
387
|
+
+ "5. Per a cada categoria, tria el dataset més interessant i usa get_dataset_info per mostrar-ne els camps\n"
|
|
388
|
+
+ "6. Destaca:\n"
|
|
389
|
+
+ " - Datasets amb dades en temps real o actualització freqüent\n"
|
|
390
|
+
+ " - Datasets amb molts camps (rics en dades)\n"
|
|
391
|
+
+ " - Datasets únics que no es troben a altres portals\n\n"
|
|
392
|
+
+ "Presenta el portal com una guia completa per a un nou usuari.",
|
|
393
|
+
},
|
|
394
|
+
}],
|
|
395
|
+
}));
|
|
396
|
+
server.prompt("dades_municipi", "Descobreix totes les dades obertes disponibles sobre un municipi concret de Catalunya.", { municipi: z.string().describe("Nom del municipi (ex: 'Sabadell', 'Girona', 'Manresa')") }, ({ municipi }) => ({
|
|
397
|
+
messages: [{
|
|
398
|
+
role: "user",
|
|
399
|
+
content: {
|
|
400
|
+
type: "text",
|
|
401
|
+
text: `Descobreix totes les dades obertes disponibles sobre el municipi de ${municipi}.\n\n`
|
|
402
|
+
+ `1. Usa search_datasets amb '${municipi}' (limit: 50) per trobar tots els datasets\n`
|
|
403
|
+
+ "2. Agrupa per portal i categoria\n"
|
|
404
|
+
+ "3. Per als datasets més rellevants, usa get_dataset_info per veure detalls\n"
|
|
405
|
+
+ "4. Fes query_dataset (limit: 3) als 2-3 datasets més interessants per mostrar dades reals\n"
|
|
406
|
+
+ "5. Usa related_datasets per trobar dades complementàries d'altres portals\n"
|
|
407
|
+
+ "6. Presenta un resum en format fitxa municipal:\n"
|
|
408
|
+
+ " - Població (si hi ha dades)\n"
|
|
409
|
+
+ " - Pressupost (si hi ha dades)\n"
|
|
410
|
+
+ " - Equipaments, transport, medi ambient...\n"
|
|
411
|
+
+ ` - Què falta: quins temes no tenen dades obertes\n\n`
|
|
412
|
+
+ `L'objectiu és donar un retrat complet de ${municipi} a través de les dades obertes.`,
|
|
413
|
+
},
|
|
414
|
+
}],
|
|
415
|
+
}));
|
|
416
|
+
server.prompt("datasets_temps_real", "Llista els datasets que ofereixen dades en temps real o actualització freqüent.", () => ({
|
|
417
|
+
messages: [{
|
|
418
|
+
role: "user",
|
|
419
|
+
content: {
|
|
420
|
+
type: "text",
|
|
421
|
+
text: "Descobreix quins datasets de dades obertes de Catalunya ofereixen dades en temps real o actualització molt freqüent.\n\n"
|
|
422
|
+
+ "1. Usa search_datasets amb termes com 'temps real', 'GTFS', 'realtime' per trobar datasets en viu\n"
|
|
423
|
+
+ "2. Usa search_datasets amb portal 'fgc' per trobar dades de transport en temps real\n"
|
|
424
|
+
+ "3. Usa search_datasets amb 'qualitat aire estacions' per trobar mesures en directe\n"
|
|
425
|
+
+ "4. Usa search_datasets amb 'embassament' i 'cabal' per trobar dades hídriques en viu\n"
|
|
426
|
+
+ "5. Per cada dataset trobat, usa get_dataset_info per verificar la freqüència d'actualització\n"
|
|
427
|
+
+ "6. Presenta una llista organitzada per tema:\n"
|
|
428
|
+
+ " - Transport: trens FGC, trànsit, bicing...\n"
|
|
429
|
+
+ " - Medi ambient: aire, aigua, meteorologia...\n"
|
|
430
|
+
+ " - Altres en temps real\n"
|
|
431
|
+
+ "7. Per als 3 més interessants, fes query_dataset per mostrar les últimes dades\n\n"
|
|
432
|
+
+ "L'objectiu és que l'usuari sàpiga quines dades pot consultar 'ara mateix'.",
|
|
433
|
+
},
|
|
434
|
+
}],
|
|
435
|
+
}));
|
|
436
|
+
server.prompt("resum_portals", "Resum general de tots els portals: quants datasets, quins temes, quins formats.", () => ({
|
|
437
|
+
messages: [{
|
|
438
|
+
role: "user",
|
|
439
|
+
content: {
|
|
440
|
+
type: "text",
|
|
441
|
+
text: "Fes un resum complet de tots els portals de dades obertes de Catalunya.\n\n"
|
|
442
|
+
+ "1. Usa list_portals per obtenir la llista amb comptadors\n"
|
|
443
|
+
+ "2. Usa list_categories per veure les categories de cada portal\n"
|
|
444
|
+
+ "3. Presenta una taula comparativa:\n"
|
|
445
|
+
+ " - Nom del portal, URL, nombre de datasets\n"
|
|
446
|
+
+ " - Tipus d'API (Socrata, CKAN, REST, Opendatasoft)\n"
|
|
447
|
+
+ " - Categories principals\n"
|
|
448
|
+
+ " - Tipus de dades destacades\n"
|
|
449
|
+
+ "4. Per cada portal, destaca el dataset més singular o interessant\n"
|
|
450
|
+
+ "5. Indica quins portals tenen dades en temps real\n"
|
|
451
|
+
+ "6. Suggereix per a cada portal una pregunta interessant que es podria respondre amb les seves dades\n\n"
|
|
452
|
+
+ "L'objectiu és donar una visió panoràmica de l'ecosistema de dades obertes català.",
|
|
453
|
+
},
|
|
454
|
+
}],
|
|
455
|
+
}));
|
|
318
456
|
async function main() {
|
|
319
457
|
const mode = process.argv.includes("--http") ? "http" : "stdio";
|
|
320
458
|
const port = parseInt(process.env.MCP_PORT || "3100", 10);
|
|
@@ -333,7 +471,7 @@ async function main() {
|
|
|
333
471
|
// Health check
|
|
334
472
|
if (req.url === "/health") {
|
|
335
473
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
336
|
-
res.end(JSON.stringify({ status: "ok", name: "opendata-cat", version: "0.0.
|
|
474
|
+
res.end(JSON.stringify({ status: "ok", name: "opendata-cat", version: "0.0.12" }));
|
|
337
475
|
return;
|
|
338
476
|
}
|
|
339
477
|
// MCP endpoint
|