@aborruso/ckan-mcp-server 0.4.25 → 0.4.26

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
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## 2026-01-29
4
4
 
5
+ ### Release v0.4.26
6
+
7
+ - Resolve portal hostname to API URL in CKAN requests
8
+ - Tests: add unit coverage for URL resolution
9
+ - Files: `src/utils/http.ts`, `tests/unit/http.test.ts`, `src/server.ts`, `src/worker.ts`, `package.json`, `package-lock.json`
10
+
5
11
  ### Release v0.4.25
6
12
 
7
13
  - Add ANAC open data portal entry and aliases
package/dist/index.js CHANGED
@@ -18,67 +18,6 @@ var CHARACTER_LIMIT = 5e4;
18
18
 
19
19
  // src/utils/http.ts
20
20
  import axios from "axios";
21
- async function makeCkanRequest(serverUrl, action, params = {}) {
22
- const baseUrl = serverUrl.replace(/\/$/, "");
23
- const url = `${baseUrl}/api/3/action/${action}`;
24
- try {
25
- const response = await axios.get(url, {
26
- params,
27
- timeout: 3e4,
28
- headers: {
29
- Accept: "application/json, text/plain, */*",
30
- "Accept-Language": "en-US,en;q=0.9,it;q=0.8",
31
- "User-Agent": "CKAN-MCP-Server/1.0"
32
- }
33
- });
34
- if (response.data && response.data.success === true) {
35
- return response.data.result;
36
- } else {
37
- throw new Error(`CKAN API returned success=false: ${JSON.stringify(response.data)}`);
38
- }
39
- } catch (error) {
40
- if (axios.isAxiosError(error)) {
41
- const axiosError = error;
42
- if (axiosError.response) {
43
- const status = axiosError.response.status;
44
- const data = axiosError.response.data;
45
- const errorMsg = data?.error?.message || data?.error || "Unknown error";
46
- throw new Error(`CKAN API error (${status}): ${errorMsg}`);
47
- } else if (axiosError.code === "ECONNABORTED") {
48
- throw new Error(`Request timeout connecting to ${serverUrl}`);
49
- } else if (axiosError.code === "ENOTFOUND") {
50
- throw new Error(`Server not found: ${serverUrl}`);
51
- } else {
52
- throw new Error(`Network error: ${axiosError.message}`);
53
- }
54
- }
55
- throw error;
56
- }
57
- }
58
-
59
- // src/utils/formatting.ts
60
- function truncateText(text, limit = CHARACTER_LIMIT) {
61
- if (text.length <= limit) {
62
- return text;
63
- }
64
- return text.substring(0, limit) + `
65
-
66
- ... [Response truncated at ${limit} characters]`;
67
- }
68
- function formatDate(dateStr) {
69
- try {
70
- if (!dateStr) {
71
- return "Invalid Date";
72
- }
73
- const date = new Date(dateStr);
74
- if (Number.isNaN(date.getTime())) {
75
- return "Invalid Date";
76
- }
77
- return date.toISOString().slice(0, 10);
78
- } catch {
79
- return "Invalid Date";
80
- }
81
- }
82
21
 
83
22
  // src/portals.json
84
23
  var portals_default = {
@@ -97,6 +36,16 @@ var portals_default = {
97
36
  },
98
37
  dataset_view_url: "https://www.dati.gov.it/view-dataset/dataset?id={id}",
99
38
  organization_view_url: "https://www.dati.gov.it/view-dataset?organization={name}"
39
+ },
40
+ {
41
+ id: "anac-opendata",
42
+ name: "dati.anticorruzione.it/opendata",
43
+ api_url: "https://dati.anticorruzione.it/opendata",
44
+ api_url_aliases: [
45
+ "http://dati.anticorruzione.it/opendata",
46
+ "https://dati.anticorruzione.it",
47
+ "http://dati.anticorruzione.it"
48
+ ]
100
49
  }
101
50
  ],
102
51
  defaults: {
@@ -146,6 +95,88 @@ function getPortalApiUrlForHostname(hostname) {
146
95
  return portal ? normalizeUrl(portal.api_url) : null;
147
96
  }
148
97
 
98
+ // src/utils/http.ts
99
+ async function makeCkanRequest(serverUrl, action, params = {}) {
100
+ let resolvedServerUrl = serverUrl;
101
+ try {
102
+ const hostname = new URL(serverUrl).hostname;
103
+ const portalApiUrl = getPortalApiUrlForHostname(hostname);
104
+ if (portalApiUrl) {
105
+ resolvedServerUrl = portalApiUrl;
106
+ }
107
+ } catch {
108
+ }
109
+ const baseUrl = resolvedServerUrl.replace(/\/$/, "");
110
+ const url = `${baseUrl}/api/3/action/${action}`;
111
+ try {
112
+ const response = await axios.get(url, {
113
+ params,
114
+ timeout: 3e4,
115
+ headers: {
116
+ Accept: "application/json, text/plain, */*",
117
+ "Accept-Language": "en-US,en;q=0.9,it;q=0.8",
118
+ "Accept-Encoding": "gzip, deflate, br",
119
+ Connection: "keep-alive",
120
+ Referer: `${baseUrl}/`,
121
+ "Sec-Fetch-Site": "same-origin",
122
+ "Sec-Fetch-Mode": "navigate",
123
+ "Sec-Fetch-Dest": "document",
124
+ "Upgrade-Insecure-Requests": "1",
125
+ "Sec-CH-UA": '"Chromium";v="120", "Not?A_Brand";v="24", "Google Chrome";v="120"',
126
+ "Sec-CH-UA-Mobile": "?0",
127
+ "Sec-CH-UA-Platform": '"Linux"',
128
+ "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
129
+ }
130
+ });
131
+ if (response.data && response.data.success === true) {
132
+ return response.data.result;
133
+ } else {
134
+ throw new Error(`CKAN API returned success=false: ${JSON.stringify(response.data)}`);
135
+ }
136
+ } catch (error) {
137
+ if (axios.isAxiosError(error)) {
138
+ const axiosError = error;
139
+ if (axiosError.response) {
140
+ const status = axiosError.response.status;
141
+ const data = axiosError.response.data;
142
+ const errorMsg = data?.error?.message || data?.error || "Unknown error";
143
+ throw new Error(`CKAN API error (${status}): ${errorMsg}`);
144
+ } else if (axiosError.code === "ECONNABORTED") {
145
+ throw new Error(`Request timeout connecting to ${serverUrl}`);
146
+ } else if (axiosError.code === "ENOTFOUND") {
147
+ throw new Error(`Server not found: ${serverUrl}`);
148
+ } else {
149
+ throw new Error(`Network error: ${axiosError.message}`);
150
+ }
151
+ }
152
+ throw error;
153
+ }
154
+ }
155
+
156
+ // src/utils/formatting.ts
157
+ function truncateText(text, limit = CHARACTER_LIMIT) {
158
+ if (text.length <= limit) {
159
+ return text;
160
+ }
161
+ return text.substring(0, limit) + `
162
+
163
+ ... [Response truncated at ${limit} characters]`;
164
+ }
165
+ function formatDate(dateStr) {
166
+ try {
167
+ if (!dateStr) {
168
+ return "Invalid Date";
169
+ }
170
+ const date = new Date(dateStr);
171
+ if (Number.isNaN(date.getTime())) {
172
+ return "Invalid Date";
173
+ }
174
+ return date.toISOString().slice(0, 10);
175
+ } catch {
176
+ return "Invalid Date";
177
+ }
178
+ }
179
+
149
180
  // src/utils/url-generator.ts
150
181
  function getDatasetViewUrl(serverUrl, pkg) {
151
182
  const cleanServerUrl = normalizePortalUrl(serverUrl);
@@ -3049,7 +3080,7 @@ var registerAllPrompts = (server2) => {
3049
3080
  function createServer() {
3050
3081
  return new McpServer({
3051
3082
  name: "ckan-mcp-server",
3052
- version: "0.4.23"
3083
+ version: "0.4.26"
3053
3084
  });
3054
3085
  }
3055
3086
  function registerAll(server2) {