@aborruso/ckan-mcp-server 0.4.67 → 0.4.69

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/LOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # LOG
2
2
 
3
+ ## 2026-03-04 (v0.4.69)
4
+
5
+ - feat: `ckan_package_search` and `ckan_package_show` JSON output now include `view_url` field pointing to the dataset page on the source portal
6
+
7
+ ## 2026-03-04 (v0.4.68)
8
+
9
+ - feat: SPARQL endpoint config in `portals.json` — `sparql.endpoint_url` + `sparql.method` per portal (Italy: `lod.dati.gov.it/sparql`, GET-only)
10
+ - feat: `ckan_status_show` now shows SPARQL endpoint when configured for the portal
11
+ - fix: `sparql_query` — GET fallback on 403/405 for endpoints that reject POST; User-Agent set to `Mozilla/5.0 (compatible; CKAN-MCP-Server/1.0)` (required by AWS WAF on lod.dati.gov.it)
12
+ - refactor: `portal-config.ts` — added `SparqlConfig` type, `getSparqlConfig(endpointUrl)`, `getPortalSparqlConfig(serverUrl)`; tool count 18→19
13
+
3
14
  ## 2026-03-04 (v0.4.67)
4
15
 
5
16
  - improve: `sparql_query` — validate SELECT-only, auto-inject LIMIT (default 25, max 1000), truncate output at CHARACTER_LIMIT; +11 tests (310 total)
package/dist/index.js CHANGED
@@ -37,6 +37,10 @@ var portals_default = {
37
37
  hvd: {
38
38
  category_field: "hvd_category"
39
39
  },
40
+ sparql: {
41
+ endpoint_url: "https://lod.dati.gov.it/sparql",
42
+ method: "GET"
43
+ },
40
44
  dataset_view_url: "https://www.dati.gov.it/view-dataset/dataset?id={id}",
41
45
  organization_view_url: "https://www.dati.gov.it/view-dataset?organization={name}"
42
46
  },
@@ -180,6 +184,17 @@ function getPortalHvdConfig(serverUrl) {
180
184
  const portal = getPortalConfig(serverUrl);
181
185
  return portal?.hvd ?? null;
182
186
  }
187
+ function getSparqlConfig(endpointUrl) {
188
+ const cleanUrl = normalizeUrl(endpointUrl);
189
+ const portal = portals_default.portals.find(
190
+ (p) => p.sparql && normalizeUrl(p.sparql.endpoint_url) === cleanUrl
191
+ );
192
+ return portal?.sparql ?? null;
193
+ }
194
+ function getPortalSparqlConfig(serverUrl) {
195
+ const portal = getPortalConfig(serverUrl);
196
+ return portal?.sparql ?? null;
197
+ }
183
198
  function getPortalApiPath(serverUrl) {
184
199
  const portal = getPortalConfig(serverUrl);
185
200
  return portal?.api_path || "/api/3/action";
@@ -857,7 +872,7 @@ function resolvePageParams(page, pageSize, start, rows) {
857
872
  }
858
873
  return { effectiveStart: start, effectiveRows: rows };
859
874
  }
860
- function compactSearchResult(result) {
875
+ function compactSearchResult(result, serverUrl) {
861
876
  return {
862
877
  count: result.count,
863
878
  results: (result.results || []).map((pkg) => ({
@@ -868,13 +883,14 @@ function compactSearchResult(result) {
868
883
  organization: pkg.organization?.title || pkg.organization?.name || null,
869
884
  tags: (pkg.tags || []).map((t) => t.name),
870
885
  num_resources: pkg.num_resources ?? 0,
871
- metadata_modified: pkg.metadata_modified
886
+ metadata_modified: pkg.metadata_modified,
887
+ ...serverUrl ? { view_url: getDatasetViewUrl(serverUrl, pkg) } : {}
872
888
  })),
873
889
  ...result.facets && Object.keys(result.facets).length > 0 ? { facets: result.facets } : {},
874
890
  ...result.search_facets && Object.keys(result.search_facets).length > 0 ? { search_facets: result.search_facets } : {}
875
891
  };
876
892
  }
877
- function compactPackageShow(result) {
893
+ function compactPackageShow(result, serverUrl) {
878
894
  return {
879
895
  id: result.id,
880
896
  name: result.name,
@@ -902,7 +918,8 @@ function compactPackageShow(result) {
902
918
  datastore_active: r.datastore_active ?? null,
903
919
  created: r.created || null,
904
920
  last_modified: r.last_modified || null
905
- }))
921
+ })),
922
+ ...serverUrl ? { view_url: getDatasetViewUrl(serverUrl, result) } : {}
906
923
  };
907
924
  }
908
925
  function registerPackageTools(server2) {
@@ -1096,7 +1113,7 @@ Typical workflow: ckan_package_search \u2192 ckan_package_show (get full metadat
1096
1113
  }
1097
1114
  }
1098
1115
  if (params.response_format === "json" /* JSON */) {
1099
- const compact = compactSearchResult(result);
1116
+ const compact = compactSearchResult(result, params.server_url);
1100
1117
  return {
1101
1118
  content: [{ type: "text", text: truncateJson(compact) }]
1102
1119
  };
@@ -1481,7 +1498,7 @@ Typical workflow: ckan_package_show \u2192 pick a resource with datastore_active
1481
1498
  }
1482
1499
  );
1483
1500
  if (params.response_format === "json" /* JSON */) {
1484
- const compact = compactPackageShow(enrichPackageShowResult(result));
1501
+ const compact = compactPackageShow(enrichPackageShowResult(result), params.server_url);
1485
1502
  return {
1486
1503
  content: [{ type: "text", text: truncateJson(compact) }],
1487
1504
  structuredContent: compact
@@ -2375,6 +2392,9 @@ Typical workflow: ckan_package_show (get resource_id) \u2192 ckan_datastore_sear
2375
2392
  // src/tools/status.ts
2376
2393
  import { z as z5 } from "zod";
2377
2394
  function formatStatusMarkdown(result, serverUrl) {
2395
+ const sparql = getPortalSparqlConfig(serverUrl);
2396
+ const sparqlLine = sparql ? `**SPARQL Endpoint**: ${sparql.endpoint_url}
2397
+ ` : "";
2378
2398
  return `# CKAN Server Status
2379
2399
 
2380
2400
  **Server**: ${serverUrl}
@@ -2382,7 +2402,7 @@ function formatStatusMarkdown(result, serverUrl) {
2382
2402
  **CKAN Version**: ${result.ckan_version || "Unknown"}
2383
2403
  **Site Title**: ${result.site_title || "N/A"}
2384
2404
  **Site URL**: ${result.site_url || "N/A"}
2385
- `;
2405
+ ` + sparqlLine;
2386
2406
  }
2387
2407
  function registerStatusTools(server2) {
2388
2408
  server2.registerTool(
@@ -4004,19 +4024,41 @@ async function querySparqlEndpoint(endpointUrl, query) {
4004
4024
  if (url.protocol !== "https:") {
4005
4025
  throw new Error("Only HTTPS endpoints are allowed");
4006
4026
  }
4027
+ const sparqlConfig = getSparqlConfig(endpointUrl);
4028
+ const method = sparqlConfig?.method ?? "POST";
4029
+ const commonHeaders = {
4030
+ "Accept": "application/sparql-results+json",
4031
+ "User-Agent": "Mozilla/5.0 (compatible; CKAN-MCP-Server/1.0)"
4032
+ };
4007
4033
  const controller = new AbortController();
4008
4034
  const timeoutId = setTimeout(() => controller.abort(), 15e3);
4009
4035
  let response;
4010
4036
  try {
4011
- response = await fetch(endpointUrl, {
4012
- method: "POST",
4013
- signal: controller.signal,
4014
- headers: {
4015
- "Content-Type": "application/sparql-query",
4016
- "Accept": "application/sparql-results+json"
4017
- },
4018
- body: query
4019
- });
4037
+ if (method === "GET") {
4038
+ const getUrl = new URL(endpointUrl);
4039
+ getUrl.searchParams.set("query", query);
4040
+ response = await fetch(getUrl.toString(), {
4041
+ method: "GET",
4042
+ signal: controller.signal,
4043
+ headers: commonHeaders
4044
+ });
4045
+ } else {
4046
+ response = await fetch(endpointUrl, {
4047
+ method: "POST",
4048
+ signal: controller.signal,
4049
+ headers: { ...commonHeaders, "Content-Type": "application/sparql-query" },
4050
+ body: query
4051
+ });
4052
+ if (response.status === 403 || response.status === 405) {
4053
+ const getUrl = new URL(endpointUrl);
4054
+ getUrl.searchParams.set("query", query);
4055
+ response = await fetch(getUrl.toString(), {
4056
+ method: "GET",
4057
+ signal: controller.signal,
4058
+ headers: commonHeaders
4059
+ });
4060
+ }
4061
+ }
4020
4062
  } finally {
4021
4063
  clearTimeout(timeoutId);
4022
4064
  }
@@ -4681,7 +4723,7 @@ var registerAllPrompts = (server2) => {
4681
4723
  function createServer() {
4682
4724
  return new McpServer({
4683
4725
  name: "ckan-mcp-server",
4684
- version: "0.4.67"
4726
+ version: "0.4.68"
4685
4727
  });
4686
4728
  }
4687
4729
  function registerAll(server2) {