@opendata.cat/mcp-server 0.0.9 → 0.0.11
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 +42 -11
- package/dist/clients/opendatasoft.d.ts +4 -0
- package/dist/clients/opendatasoft.js +20 -0
- package/dist/clients/socrata.js +6 -2
- package/dist/index.js +164 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
# Opendata.cat MCP Server
|
|
14
14
|
|
|
15
|
-
Servidor [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) que connecta els models de llenguatge (Claude, ChatGPT, Gemini...) amb les **dades obertes publiques de Catalunya**. Cerca datasets, explora metadades i consulta dades reals de
|
|
15
|
+
Servidor [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) que connecta els models de llenguatge (Claude, ChatGPT, Gemini...) amb les **dades obertes publiques de Catalunya**. Cerca datasets, explora metadades i consulta dades reals de 7 portals catalans directament des del teu assistent d'IA.
|
|
16
16
|
|
|
17
17
|
Un projecte d'**[opendata.cat](https://opendata.cat)** — associacio sense anim de lucre fundada el 2012 que promou la transparencia, la difusio i l'estandarditzacio de les dades obertes a Catalunya. Inspirat en el projecte [datagouv-mcp](https://github.com/datagouv/datagouv-mcp) del govern frances.
|
|
18
18
|
|
|
@@ -26,18 +26,20 @@ Un projecte d'**[opendata.cat](https://opendata.cat)** — associacio sense anim
|
|
|
26
26
|
| [Consorci AOC](https://dadesobertes.seu-e.cat) | ~893 | CKAN datastore |
|
|
27
27
|
| [Ajuntament de Reus](https://opendata.reus.cat) | 119 | CKAN datastore |
|
|
28
28
|
| [Ajuntament de Girona](https://www.girona.cat/opendata/) | 53 | CKAN datastore |
|
|
29
|
+
| [FGC (Ferrocarrils)](https://dadesobertes.fgc.cat) | 50 | Opendatasoft |
|
|
29
30
|
|
|
30
31
|
El Consorci AOC inclou datasets de les **diputacions de Tarragona, Girona i Lleida**, ajuntaments, consells comarcals i altres organismes publics catalans.
|
|
31
32
|
|
|
32
|
-
**+2.
|
|
33
|
+
**+2.800 datasets** de 7 portals. La majoria queryables amb filtres, cerca i paginacio.
|
|
33
34
|
|
|
34
35
|
El cataleg s'actualitza automaticament cada setmana. Cada endpoint es valida per assegurar que funciona.
|
|
35
36
|
|
|
36
37
|
**Tipus d'acces:**
|
|
37
38
|
- **Socrata**: consulta SoQL amb filtres i cerca (Generalitat)
|
|
38
|
-
- **CKAN**: datastore_search amb filtres i cerca (Barcelona, AOC)
|
|
39
|
+
- **CKAN**: datastore_search amb filtres i cerca (Barcelona, AOC, Reus, Girona)
|
|
39
40
|
- **Diba REST**: API do.diba.cat amb paginacio i filtres (Diputacio BCN)
|
|
40
41
|
- **CIDO JSON:API**: api.diba.cat per contractacions, normatives, subvencions, oposicions, convenis (Diputacio BCN)
|
|
42
|
+
- **Opendatasoft**: API records amb filtres i cerca (FGC — horaris GTFS, trens temps real, estacions esqui)
|
|
41
43
|
- **File download**: descarrega directa de CSV, JSON, XLSX o fitxers GIS
|
|
42
44
|
- **Restricted**: requereix token d'autenticacio (4 datasets BSM)
|
|
43
45
|
|
|
@@ -89,6 +91,7 @@ Afegeix al fitxer `.vscode/mcp.json` del teu projecte:
|
|
|
89
91
|
| `query_dataset` | Consulta dades reals directament al portal origen |
|
|
90
92
|
| `list_portals` | Llista els portals disponibles amb estadistiques |
|
|
91
93
|
| `list_categories` | Llista categories i temes disponibles amb comptadors |
|
|
94
|
+
| `related_datasets` | Retorna datasets relacionats d'altres portals |
|
|
92
95
|
|
|
93
96
|
### search_datasets
|
|
94
97
|
|
|
@@ -96,7 +99,7 @@ Cerca datasets per text lliure.
|
|
|
96
99
|
|
|
97
100
|
```
|
|
98
101
|
query: "qualitat aire"
|
|
99
|
-
portal: "barcelona" # opcional: generalitat, barcelona, diba
|
|
102
|
+
portal: "barcelona" # opcional: generalitat, barcelona, diba, aoc, reus, girona, fgc
|
|
100
103
|
category: "Medi Ambient" # opcional
|
|
101
104
|
limit: 20 # opcional (defecte: 20)
|
|
102
105
|
```
|
|
@@ -137,17 +140,33 @@ Llista els portals disponibles amb el nombre de datasets de cadascun. No requere
|
|
|
137
140
|
|
|
138
141
|
Llista totes les categories i temes de datasets disponibles amb comptadors per portal. Ideal per descobrir quins tipus de dades hi ha.
|
|
139
142
|
|
|
143
|
+
## Prompts disponibles
|
|
144
|
+
|
|
145
|
+
Prompts predefinits que guien l'LLM pas a pas per fer analisis completes:
|
|
146
|
+
|
|
147
|
+
| Prompt | Descripcio | Arguments |
|
|
148
|
+
|--------|-----------|-----------|
|
|
149
|
+
| `estat_embassaments` | Estat actual dels embassaments amb grafiques d'evolucio | — |
|
|
150
|
+
| `trens_fgc_temps_real` | Retards, alertes i posicions dels trens FGC en temps real | — |
|
|
151
|
+
| `qualitat_aire` | Analisi de la qualitat de l'aire amb comparativa OMS/UE | `lloc` (opcional) |
|
|
152
|
+
| `accidents_transit` | Analisi d'accidents de transit amb punts negres i tendencies | `municipi` (opcional) |
|
|
153
|
+
| `pressupostos_municipals` | Pressupostos municipals amb desglossament per partides | `municipi` (opcional) |
|
|
154
|
+
| `compara_municipis` | Compara dos municipis en totes les dades disponibles | `municipi_a`, `municipi_b` |
|
|
155
|
+
| `descobreix_dades` | Mapa complet de dades obertes sobre un tema | `tema` |
|
|
156
|
+
| `analisi_bombers` | Actuacions dels Bombers: emergencies, distribucio, tendencies | `comarca` (opcional) |
|
|
157
|
+
|
|
140
158
|
## Exemples d'us
|
|
141
159
|
|
|
142
160
|
Un cop configurat, pots fer preguntes al teu LLM com:
|
|
143
161
|
|
|
144
|
-
- *"
|
|
145
|
-
- *"
|
|
146
|
-
- *"
|
|
147
|
-
- *"
|
|
148
|
-
- *"
|
|
149
|
-
- *"Quines dades obertes hi ha sobre educacio a Catalunya?"*
|
|
150
|
-
- *"
|
|
162
|
+
- *"Quin es l'estat dels embassaments de Catalunya?"* → prompt `estat_embassaments`
|
|
163
|
+
- *"Hi ha algun tren de FGC amb retard ara mateix?"* → prompt `trens_fgc_temps_real`
|
|
164
|
+
- *"Analitza la qualitat de l'aire a Terrassa"* → prompt `qualitat_aire`
|
|
165
|
+
- *"Fes unes grafiques amb l'evolucio dels accidents de transit a Barcelona"* → prompt `accidents_transit`
|
|
166
|
+
- *"Compara Girona i Tarragona en dades obertes"* → prompt `compara_municipis`
|
|
167
|
+
- *"Quines dades obertes hi ha sobre educacio a Catalunya?"* → prompt `descobreix_dades`
|
|
168
|
+
- *"Dona'm les ultimes dades de pressupostos de Reus"* → prompt `pressupostos_municipals`
|
|
169
|
+
- *"Analitza les actuacions dels Bombers al Valles"* → prompt `analisi_bombers`
|
|
151
170
|
|
|
152
171
|
## Com funciona
|
|
153
172
|
|
|
@@ -175,6 +194,18 @@ Les contribucions son benvingudes! Per afegir un nou portal de dades obertes:
|
|
|
175
194
|
|
|
176
195
|
## Changelog
|
|
177
196
|
|
|
197
|
+
### v0.0.11 (2026-04-13)
|
|
198
|
+
- 8 prompts predefinits: embassaments, trens FGC temps real, qualitat aire, accidents, pressupostos, compara municipis, descobreix dades, bombers
|
|
199
|
+
- Crawler incremental: UPSERT en lloc de TRUNCATE, no perd dades si un portal falla
|
|
200
|
+
- Backup automatic despres de cada carrega
|
|
201
|
+
|
|
202
|
+
### v0.0.10 (2026-04-13)
|
|
203
|
+
- Afegeix portal FGC (Ferrocarrils de la Generalitat de Catalunya) — 50 datasets via Opendatasoft
|
|
204
|
+
- Nou client Opendatasoft per consultar dades de transport, esqui, meteorologia, GTFS
|
|
205
|
+
- list_portals ara llista els 7 portals (abans nomes 3)
|
|
206
|
+
- Fix seguretat: validacio de claus de filtre SoQL contra injeccio
|
|
207
|
+
- Cobertura total: ~2.800 datasets de 7 portals
|
|
208
|
+
|
|
178
209
|
### v0.0.7 (2026-04-13)
|
|
179
210
|
- Afegeix portal Consorci AOC (~893 datasets de diputacions, ajuntaments, consells comarcals)
|
|
180
211
|
- Cobertura total: ~2.600 datasets, ~2.400 queryables
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export async function queryOpendatasoft(endpoint, filters, search, limit = 20, offset = 0) {
|
|
2
|
+
const url = new URL(endpoint);
|
|
3
|
+
if (filters && Object.keys(filters).length > 0) {
|
|
4
|
+
for (const [key, value] of Object.entries(filters)) {
|
|
5
|
+
url.searchParams.set(`refine.${key}`, value);
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
if (search)
|
|
9
|
+
url.searchParams.set("q", search);
|
|
10
|
+
url.searchParams.set("rows", String(Math.min(limit, 100)));
|
|
11
|
+
url.searchParams.set("start", String(offset));
|
|
12
|
+
const resp = await fetch(url.toString());
|
|
13
|
+
if (!resp.ok)
|
|
14
|
+
throw new Error(`Opendatasoft error ${resp.status}: ${resp.statusText}`);
|
|
15
|
+
const data = (await resp.json());
|
|
16
|
+
return {
|
|
17
|
+
records: data.records.map((r) => r.fields),
|
|
18
|
+
total: data.nhits,
|
|
19
|
+
};
|
|
20
|
+
}
|
package/dist/clients/socrata.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
export async function querySocrata(endpoint, filters, search, limit = 20, offset = 0) {
|
|
2
2
|
const url = new URL(endpoint);
|
|
3
3
|
if (filters && Object.keys(filters).length > 0) {
|
|
4
|
-
const clauses = Object.entries(filters)
|
|
5
|
-
|
|
4
|
+
const clauses = Object.entries(filters)
|
|
5
|
+
.filter(([key]) => /^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(key))
|
|
6
|
+
.map(([key, value]) => `\`${key}\`='${value.replace(/'/g, "''")}'`);
|
|
7
|
+
if (clauses.length > 0) {
|
|
8
|
+
url.searchParams.set("$where", clauses.join(" AND "));
|
|
9
|
+
}
|
|
6
10
|
}
|
|
7
11
|
if (search)
|
|
8
12
|
url.searchParams.set("$q", search);
|
package/dist/index.js
CHANGED
|
@@ -9,14 +9,15 @@ import { querySocrata } from "./clients/socrata.js";
|
|
|
9
9
|
import { queryCkan } from "./clients/ckan.js";
|
|
10
10
|
import { queryDiba } from "./clients/diba.js";
|
|
11
11
|
import { queryCido } from "./clients/cido.js";
|
|
12
|
+
import { queryOpendatasoft } from "./clients/opendatasoft.js";
|
|
12
13
|
const server = new McpServer({
|
|
13
14
|
name: "opendata-cat",
|
|
14
|
-
version: "0.0.
|
|
15
|
+
version: "0.0.11",
|
|
15
16
|
});
|
|
16
17
|
// Tool 1: search_datasets
|
|
17
18
|
server.tool("search_datasets", "Cerca datasets de dades obertes catalanes per text lliure. Retorna nom, descripció, portal i formats.", {
|
|
18
19
|
query: z.string().describe("Text de cerca (ex: 'qualitat aire', 'pressupostos')"),
|
|
19
|
-
portal: z.string().optional().describe("Filtrar per portal: 'generalitat', 'barcelona'
|
|
20
|
+
portal: z.string().optional().describe("Filtrar per portal: 'generalitat', 'barcelona', 'diba', 'aoc', 'reus', 'girona', 'fgc'"),
|
|
20
21
|
category: z.string().optional().describe("Filtrar per categoria"),
|
|
21
22
|
limit: z.number().optional().default(20).describe("Nombre màxim de resultats (defecte: 20)"),
|
|
22
23
|
}, async ({ query, portal, category, limit }) => {
|
|
@@ -89,6 +90,10 @@ server.tool("query_dataset", "Executa una consulta contra un dataset i retorna f
|
|
|
89
90
|
const data = await queryCkan(dataset.api_endpoint, filters, search, limit, offset);
|
|
90
91
|
results = data.records;
|
|
91
92
|
}
|
|
93
|
+
else if (dataset.api_type === "opendatasoft") {
|
|
94
|
+
const data = await queryOpendatasoft(dataset.api_endpoint, filters, search, limit, offset);
|
|
95
|
+
results = data.records;
|
|
96
|
+
}
|
|
92
97
|
else {
|
|
93
98
|
return {
|
|
94
99
|
content: [{
|
|
@@ -116,15 +121,21 @@ server.tool("query_dataset", "Executa una consulta contra un dataset i retorna f
|
|
|
116
121
|
// Tool 5: list_portals
|
|
117
122
|
server.tool("list_portals", "Llista els portals de dades obertes catalans disponibles amb estadístiques.", {}, async () => {
|
|
118
123
|
const portals = [
|
|
119
|
-
{ id: "generalitat", name: "Generalitat de Catalunya", url: "https://analisi.transparenciacatalunya.cat",
|
|
120
|
-
{ id: "barcelona", name: "Ajuntament de Barcelona", url: "https://opendata-ajuntament.barcelona.cat",
|
|
121
|
-
{ id: "diba", name: "Diputació de Barcelona", url: "https://dadesobertes.diba.cat",
|
|
124
|
+
{ id: "generalitat", name: "Generalitat de Catalunya", url: "https://analisi.transparenciacatalunya.cat", api: "Socrata" },
|
|
125
|
+
{ id: "barcelona", name: "Ajuntament de Barcelona", url: "https://opendata-ajuntament.barcelona.cat", api: "CKAN" },
|
|
126
|
+
{ id: "diba", name: "Diputació de Barcelona", url: "https://dadesobertes.diba.cat", api: "CKAN" },
|
|
127
|
+
{ id: "aoc", name: "Consorci AOC (diputacions, ajuntaments, consells comarcals)", url: "https://dadesobertes.seu-e.cat", api: "CKAN" },
|
|
128
|
+
{ id: "reus", name: "Ajuntament de Reus", url: "https://opendata.reus.cat", api: "CKAN" },
|
|
129
|
+
{ id: "girona", name: "Ajuntament de Girona", url: "https://www.girona.cat/opendata/", api: "CKAN" },
|
|
130
|
+
{ id: "fgc", name: "Ferrocarrils de la Generalitat de Catalunya", url: "https://dadesobertes.fgc.cat", api: "Opendatasoft" },
|
|
122
131
|
];
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
132
|
+
const cats = await getCategories();
|
|
133
|
+
const portalCounts = new Map(cats.portals.map((p) => [p.portal_id, p.total]));
|
|
134
|
+
const result = portals.map((p) => ({
|
|
135
|
+
...p,
|
|
136
|
+
dataset_count: portalCounts.get(p.id) ?? 0,
|
|
126
137
|
}));
|
|
127
|
-
return { content: [{ type: "text", text: JSON.stringify(
|
|
138
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
128
139
|
});
|
|
129
140
|
// Tool 6: list_categories
|
|
130
141
|
server.tool("list_categories", "Llista totes les categories i temes de datasets disponibles amb comptadors per portal. Útil per saber quins tipus de dades hi ha.", {}, async () => {
|
|
@@ -161,6 +172,149 @@ server.tool("related_datasets", "Retorna datasets relacionats d'ALTRES portals.
|
|
|
161
172
|
}],
|
|
162
173
|
};
|
|
163
174
|
});
|
|
175
|
+
// ===== PROMPTS =====
|
|
176
|
+
server.prompt("estat_embassaments", "Analitza l'estat actual dels embassaments de Catalunya amb gràfiques d'evolució.", () => ({
|
|
177
|
+
messages: [{
|
|
178
|
+
role: "user",
|
|
179
|
+
content: {
|
|
180
|
+
type: "text",
|
|
181
|
+
text: "Consulta l'estat actual dels embassaments de les Conques Internes de Catalunya.\n\n"
|
|
182
|
+
+ "1. Usa search_datasets amb 'embassament' per trobar el dataset rellevant\n"
|
|
183
|
+
+ "2. Usa query_dataset per obtenir les últimes dades\n"
|
|
184
|
+
+ "3. Presenta una taula amb cada embassament: nom, volum actual (hm³), percentatge ple, i variació\n"
|
|
185
|
+
+ "4. Genera un gràfic ASCII o Markdown amb l'evolució dels nivells\n"
|
|
186
|
+
+ "5. Destaca embassaments en situació crítica (< 40%) i els que estan millor\n"
|
|
187
|
+
+ "6. Compara amb el dataset d'estat de sequera si n'hi ha\n\n"
|
|
188
|
+
+ "Mostra les dades de forma visual i fàcil d'entendre.",
|
|
189
|
+
},
|
|
190
|
+
}],
|
|
191
|
+
}));
|
|
192
|
+
server.prompt("trens_fgc_temps_real", "Consulta l'estat dels trens de FGC en temps real: retards, alertes i posicions.", () => ({
|
|
193
|
+
messages: [{
|
|
194
|
+
role: "user",
|
|
195
|
+
content: {
|
|
196
|
+
type: "text",
|
|
197
|
+
text: "Consulta l'estat en temps real dels trens de Ferrocarrils de la Generalitat de Catalunya (FGC).\n\n"
|
|
198
|
+
+ "1. Usa search_datasets amb portal 'fgc' per trobar els datasets GTFS Realtime\n"
|
|
199
|
+
+ "2. Consulta 'trip-updates' per veure retards actuals\n"
|
|
200
|
+
+ "3. Consulta 'vehicle-positions' per veure on són els trens\n"
|
|
201
|
+
+ "4. Consulta 'alerts' per veure si hi ha alertes de servei\n\n"
|
|
202
|
+
+ "Presenta un resum clar:\n"
|
|
203
|
+
+ "- Trens amb retard (quants minuts, quina línia)\n"
|
|
204
|
+
+ "- Alertes actives de servei\n"
|
|
205
|
+
+ "- Estat general: normal / amb incidències / interromput",
|
|
206
|
+
},
|
|
207
|
+
}],
|
|
208
|
+
}));
|
|
209
|
+
server.prompt("qualitat_aire", "Analitza la qualitat de l'aire a una estació o municipi de Catalunya.", { lloc: z.string().optional().describe("Nom del municipi o estació (ex: 'Barcelona', 'Sabadell')") }, ({ lloc }) => {
|
|
210
|
+
const filtreText = lloc ? ` a ${lloc}` : " a les principals estacions";
|
|
211
|
+
return {
|
|
212
|
+
messages: [{
|
|
213
|
+
role: "user",
|
|
214
|
+
content: {
|
|
215
|
+
type: "text",
|
|
216
|
+
text: `Analitza la qualitat de l'aire${filtreText}.\n\n`
|
|
217
|
+
+ "1. Usa search_datasets amb 'qualitat aire contaminació' per trobar els datasets rellevants\n"
|
|
218
|
+
+ "2. Consulta les últimes mesures disponibles"
|
|
219
|
+
+ (lloc ? ` filtrant per '${lloc}'` : "") + "\n"
|
|
220
|
+
+ "3. Presenta els nivells de: NO₂, PM10, PM2.5, O₃, SO₂ (els que hi hagi)\n"
|
|
221
|
+
+ "4. Compara amb els llindars de l'OMS i la normativa UE\n"
|
|
222
|
+
+ "5. Dona una valoració global: bona / acceptable / dolenta / molt dolenta\n"
|
|
223
|
+
+ "6. Si hi ha dades històriques, mostra la tendència recent\n\n"
|
|
224
|
+
+ "Usa taules i indicadors visuals per fer-ho entenedor.",
|
|
225
|
+
},
|
|
226
|
+
}],
|
|
227
|
+
};
|
|
228
|
+
});
|
|
229
|
+
server.prompt("accidents_transit", "Analitza les dades d'accidents de trànsit a Catalunya o a un municipi concret.", { municipi: z.string().optional().describe("Nom del municipi (ex: 'Barcelona', 'Hospitalet')") }, ({ municipi }) => {
|
|
230
|
+
const filtreText = municipi ? ` a ${municipi}` : " a Catalunya";
|
|
231
|
+
return {
|
|
232
|
+
messages: [{
|
|
233
|
+
role: "user",
|
|
234
|
+
content: {
|
|
235
|
+
type: "text",
|
|
236
|
+
text: `Analitza les dades d'accidents de trànsit${filtreText}.\n\n`
|
|
237
|
+
+ "1. Usa search_datasets amb 'accidents trànsit" + (municipi ? ` ${municipi}` : "") + "'\n"
|
|
238
|
+
+ "2. Consulta les dades més recents\n"
|
|
239
|
+
+ "3. Presenta: nombre total d'accidents, distribució per gravetat (mortals, ferits greus, lleus)\n"
|
|
240
|
+
+ "4. Si hi ha dades geolocalitzades, identifica els punts negres\n"
|
|
241
|
+
+ "5. Analitza tendències: augmenten o disminueixen?\n"
|
|
242
|
+
+ "6. Busca datasets relacionats amb related_datasets per completar l'anàlisi\n\n"
|
|
243
|
+
+ "Presenta conclusions clares amb dades concretes.",
|
|
244
|
+
},
|
|
245
|
+
}],
|
|
246
|
+
};
|
|
247
|
+
});
|
|
248
|
+
server.prompt("pressupostos_municipals", "Explora i compara els pressupostos municipals d'ajuntaments catalans.", { municipi: z.string().optional().describe("Nom del municipi") }, ({ municipi }) => {
|
|
249
|
+
const filtreText = municipi ? ` de ${municipi}` : "";
|
|
250
|
+
return {
|
|
251
|
+
messages: [{
|
|
252
|
+
role: "user",
|
|
253
|
+
content: {
|
|
254
|
+
type: "text",
|
|
255
|
+
text: `Explora els pressupostos municipals${filtreText}.\n\n`
|
|
256
|
+
+ "1. Usa search_datasets amb 'pressupost" + (municipi ? ` ${municipi}` : " municipal") + "'\n"
|
|
257
|
+
+ "2. Consulta les últimes dades de pressupost disponibles\n"
|
|
258
|
+
+ "3. Desglossa: ingressos vs despeses, partides principals\n"
|
|
259
|
+
+ "4. Si hi ha dades multi-any, mostra l'evolució\n"
|
|
260
|
+
+ "5. Destaca les partides més grans i les variacions significatives\n\n"
|
|
261
|
+
+ "Presenta les xifres en format comprensible (milions €) amb taules.",
|
|
262
|
+
},
|
|
263
|
+
}],
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
server.prompt("compara_municipis", "Compara dos municipis catalans en totes les dades obertes disponibles.", {
|
|
267
|
+
municipi_a: z.string().describe("Primer municipi"),
|
|
268
|
+
municipi_b: z.string().describe("Segon municipi"),
|
|
269
|
+
}, ({ municipi_a, municipi_b }) => ({
|
|
270
|
+
messages: [{
|
|
271
|
+
role: "user",
|
|
272
|
+
content: {
|
|
273
|
+
type: "text",
|
|
274
|
+
text: `Compara els municipis de ${municipi_a} i ${municipi_b} amb totes les dades obertes disponibles.\n\n`
|
|
275
|
+
+ `1. Usa search_datasets per trobar datasets que incloguin '${municipi_a}'\n`
|
|
276
|
+
+ `2. Usa search_datasets per trobar datasets que incloguin '${municipi_b}'\n`
|
|
277
|
+
+ "3. Per cada tema comú (població, pressupost, equipaments, transport...), consulta les dades dels dos municipis\n"
|
|
278
|
+
+ "4. Presenta una taula comparativa amb les dades clau\n"
|
|
279
|
+
+ "5. Destaca les diferències més significatives\n\n"
|
|
280
|
+
+ "Organitza la comparativa per temes i indica la font de cada dada.",
|
|
281
|
+
},
|
|
282
|
+
}],
|
|
283
|
+
}));
|
|
284
|
+
server.prompt("descobreix_dades", "Explora quines dades obertes hi ha disponibles sobre un tema a Catalunya.", { tema: z.string().describe("Tema a explorar (ex: 'educació', 'medi ambient', 'turisme')") }, ({ tema }) => ({
|
|
285
|
+
messages: [{
|
|
286
|
+
role: "user",
|
|
287
|
+
content: {
|
|
288
|
+
type: "text",
|
|
289
|
+
text: `Explora totes les dades obertes disponibles sobre '${tema}' a Catalunya.\n\n`
|
|
290
|
+
+ `1. Usa search_datasets amb '${tema}' (limit: 50)\n`
|
|
291
|
+
+ "2. Agrupa els resultats per portal i categoria\n"
|
|
292
|
+
+ "3. Per als 3-5 datasets més rellevants, usa get_dataset_info per mostrar detalls (camps, tipus, actualització)\n"
|
|
293
|
+
+ "4. Usa related_datasets per descobrir dades complementàries\n"
|
|
294
|
+
+ "5. Suggereix 3 anàlisis interessants que es podrien fer creuant aquests datasets\n\n"
|
|
295
|
+
+ "L'objectiu és donar un mapa complet de quines dades existeixen i què es pot fer amb elles.",
|
|
296
|
+
},
|
|
297
|
+
}],
|
|
298
|
+
}));
|
|
299
|
+
server.prompt("analisi_bombers", "Analitza les actuacions dels Bombers de la Generalitat: tipus d'emergències, distribució territorial i tendències.", { comarca: z.string().optional().describe("Filtrar per comarca (ex: 'Barcelonès', 'Vallès Occidental')") }, ({ comarca }) => {
|
|
300
|
+
const filtreText = comarca ? ` a la comarca de ${comarca}` : "";
|
|
301
|
+
return {
|
|
302
|
+
messages: [{
|
|
303
|
+
role: "user",
|
|
304
|
+
content: {
|
|
305
|
+
type: "text",
|
|
306
|
+
text: `Analitza les actuacions dels Bombers de la Generalitat${filtreText}.\n\n`
|
|
307
|
+
+ "1. Usa search_datasets amb 'bombers actuacions emergències'\n"
|
|
308
|
+
+ "2. Consulta els datasets d'actuacions, GRAF i EAIC\n"
|
|
309
|
+
+ "3. Presenta: nombre total d'actuacions, distribució per tipus (incendis, rescats, inundacions...)\n"
|
|
310
|
+
+ "4. Si hi ha dades temporals, mostra estacionalitat (estiu = incendis?)\n"
|
|
311
|
+
+ "5. Identifica les zones amb més actuacions\n"
|
|
312
|
+
+ (comarca ? `6. Filtra específicament per la comarca de ${comarca}\n` : "")
|
|
313
|
+
+ "\nFes una anàlisi visual amb taules i percentatges.",
|
|
314
|
+
},
|
|
315
|
+
}],
|
|
316
|
+
};
|
|
317
|
+
});
|
|
164
318
|
async function main() {
|
|
165
319
|
const mode = process.argv.includes("--http") ? "http" : "stdio";
|
|
166
320
|
const port = parseInt(process.env.MCP_PORT || "3100", 10);
|
|
@@ -179,7 +333,7 @@ async function main() {
|
|
|
179
333
|
// Health check
|
|
180
334
|
if (req.url === "/health") {
|
|
181
335
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
182
|
-
res.end(JSON.stringify({ status: "ok", name: "opendata-cat", version: "0.0.
|
|
336
|
+
res.end(JSON.stringify({ status: "ok", name: "opendata-cat", version: "0.0.11" }));
|
|
183
337
|
return;
|
|
184
338
|
}
|
|
185
339
|
// MCP endpoint
|