@opendata.cat/mcp-server 0.0.9 → 0.0.10
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/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 +21 -10
- package/package.json +1 -1
|
@@ -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.10",
|
|
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 () => {
|
|
@@ -179,7 +190,7 @@ async function main() {
|
|
|
179
190
|
// Health check
|
|
180
191
|
if (req.url === "/health") {
|
|
181
192
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
182
|
-
res.end(JSON.stringify({ status: "ok", name: "opendata-cat", version: "0.0.
|
|
193
|
+
res.end(JSON.stringify({ status: "ok", name: "opendata-cat", version: "0.0.10" }));
|
|
183
194
|
return;
|
|
184
195
|
}
|
|
185
196
|
// MCP endpoint
|