@aborruso/ckan-mcp-server 0.4.32 → 0.4.33

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/AGENTS.md CHANGED
@@ -45,6 +45,13 @@ Keep this managed block so 'openspec update' can refresh the instructions.
45
45
 
46
46
  **Single test**: `npm test -- tests/unit/http.test.ts` | `npm test -- -t "testName"`
47
47
 
48
+ ## Local MCP Client Build Test
49
+
50
+ Before deploying, you can test the current dev build by pointing your MCP client at the Node entrypoint in `dist/`:
51
+
52
+ 1. Build: `npm run build`
53
+ 2. Example absolute path: `/home/aborruso/git/idee/ckan-mcp-server/dist/index.js` (adjust to your local checkout)
54
+
48
55
  ## GitHub CLI Notes
49
56
 
50
57
  When creating issues with multi-line bodies, avoid literal `\n` in `--body`. Use a here-doc
package/LOG.md CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  ## 2026-02-01
4
4
 
5
+ ### Release v0.4.33
6
+
7
+ - Docs: clarify natural language date-field mapping for package search and document `content_recent` usage with example
8
+ - Files: `src/tools/package.ts`, `src/server.ts`, `src/worker.ts`, `package.json`, `package-lock.json`
9
+
5
10
  ### Release v0.4.32
6
11
 
7
12
  - Workers: align browser-like headers for fetch path to avoid 403 on dati.gov.it
package/dist/index.js CHANGED
@@ -142,12 +142,49 @@ function asBuffer(data) {
142
142
  }
143
143
  return void 0;
144
144
  }
145
+ function asArrayBuffer(data) {
146
+ if (!data) {
147
+ return void 0;
148
+ }
149
+ if (data instanceof ArrayBuffer) {
150
+ return data;
151
+ }
152
+ if (ArrayBuffer.isView(data)) {
153
+ return data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);
154
+ }
155
+ return void 0;
156
+ }
157
+ async function decodeArrayBufferText(buffer, encoding) {
158
+ if (encoding && typeof DecompressionStream !== "undefined") {
159
+ try {
160
+ const stream = new DecompressionStream(
161
+ encoding.includes("br") ? "br" : encoding.includes("deflate") ? "deflate" : "gzip"
162
+ );
163
+ const decompressed = await new Response(
164
+ new Blob([buffer]).stream().pipeThrough(stream)
165
+ ).arrayBuffer();
166
+ return new TextDecoder("utf-8").decode(decompressed).trim();
167
+ } catch {
168
+ }
169
+ }
170
+ return new TextDecoder("utf-8").decode(buffer).trim();
171
+ }
145
172
  async function decodePossiblyCompressed(data, headers) {
146
173
  if (data === null || data === void 0) {
147
174
  return data;
148
175
  }
149
- if (typeof data === "object" && !asBuffer(data)) {
150
- return data;
176
+ const arrayBuffer = asArrayBuffer(data);
177
+ if (arrayBuffer && typeof Buffer === "undefined") {
178
+ const encoding2 = getHeaderValue(headers, "content-encoding");
179
+ const text2 = await decodeArrayBufferText(arrayBuffer, encoding2);
180
+ if (!text2) {
181
+ return text2;
182
+ }
183
+ try {
184
+ return JSON.parse(text2);
185
+ } catch {
186
+ return text2;
187
+ }
151
188
  }
152
189
  if (typeof data === "string") {
153
190
  try {
@@ -158,6 +195,9 @@ async function decodePossiblyCompressed(data, headers) {
158
195
  }
159
196
  const buffer = asBuffer(data);
160
197
  if (!buffer) {
198
+ if (typeof data === "object") {
199
+ return data;
200
+ }
161
201
  return data;
162
202
  }
163
203
  const encoding = getHeaderValue(headers, "content-encoding");
@@ -241,7 +281,18 @@ async function makeCkanRequest(serverUrl, action, params = {}) {
241
281
  method: "GET",
242
282
  headers: {
243
283
  Accept: "application/json, text/plain, */*",
244
- "Accept-Language": "en-US,en;q=0.9,it;q=0.8"
284
+ "Accept-Language": "en-US,en;q=0.9,it;q=0.8",
285
+ "Accept-Encoding": "gzip, deflate, br",
286
+ Connection: "keep-alive",
287
+ Referer: `${baseUrl}/`,
288
+ "Sec-Fetch-Site": "same-origin",
289
+ "Sec-Fetch-Mode": "navigate",
290
+ "Sec-Fetch-Dest": "document",
291
+ "Upgrade-Insecure-Requests": "1",
292
+ "Sec-CH-UA": '"Chromium";v="120", "Not?A_Brand";v="24", "Google Chrome";v="120"',
293
+ "Sec-CH-UA-Mobile": "?0",
294
+ "Sec-CH-UA-Platform": '"Linux"',
295
+ "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
245
296
  }
246
297
  });
247
298
  if (!response.ok) {
@@ -668,6 +719,11 @@ Important - Date field semantics:
668
719
  - metadata_modified: CKAN record update timestamp (publish time on source portals,
669
720
  harvest time on aggregators; use for "updated/modified in last X")
670
721
 
722
+ Natural language mapping (important for tool callers):
723
+ - "created"/"published" -> prefer issued; fallback to metadata_created
724
+ - "updated"/"modified" -> prefer modified; fallback to metadata_modified
725
+ - For "recent in last X", consider using content_recent (issued with metadata_created fallback)
726
+
671
727
  Content-recent helper:
672
728
  - content_recent: if true, rewrites the query to use issued with a fallback to
673
729
  metadata_created when issued is missing.
@@ -743,6 +799,7 @@ Examples:
743
799
  - Date range: { q: "metadata_modified:[2024-01-01T00:00:00Z TO 2024-12-31T23:59:59Z]" }
744
800
  - Date math: { q: "metadata_modified:[NOW-6MONTHS TO *]" }
745
801
  - Date math (auto-converted): { q: "modified:[NOW-30DAYS TO NOW]" }
802
+ - Recent content (issued w/ fallback): { q: "*:*", content_recent: true, content_recent_days: 180 }
746
803
  - Field exists: { q: "organization:* AND num_resources:[1 TO *]" }
747
804
  - Boosting: { q: "title:climate^2 OR notes:climate" }
748
805
  - Filter org: { fq: "organization:regione-siciliana" }
@@ -3346,7 +3403,7 @@ var registerAllPrompts = (server2) => {
3346
3403
  function createServer() {
3347
3404
  return new McpServer({
3348
3405
  name: "ckan-mcp-server",
3349
- version: "0.4.27"
3406
+ version: "0.4.33"
3350
3407
  });
3351
3408
  }
3352
3409
  function registerAll(server2) {