@aborruso/ckan-mcp-server 0.4.131 → 0.4.133
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 +36 -1
- package/dist/index.js +57 -18
- package/package.json +1 -1
package/LOG.md
CHANGED
|
@@ -1,10 +1,45 @@
|
|
|
1
1
|
# LOG
|
|
2
2
|
|
|
3
|
+
## 2026-10-28
|
|
4
|
+
|
|
5
|
+
### v0.4.133
|
|
6
|
+
|
|
7
|
+
Ships #568. For callers: `ckan_find_portals` works again on the hosted Worker (it failed on every call since at least 2026-09-05); `ckan_status_show` on a site that is not a CKAN API now says so and points at `ckan_find_portals`, instead of telling the caller to call `ckan_status_show`; `open.toronto.ca` works as a server URL in every tool (routed to its CKAN host). Tests 616 passed. Smoke 16/17: same MQA outage as v0.4.132 (per-dataset `/api/mqa/cache/*/{id}` still not answering, `/global` 0.2 s), tagged with `SKIP_RELEASE_GATE=1` on the user's decision.
|
|
8
|
+
|
|
9
|
+
### Telemetry errors: find_portals fixed on Workers, error messages logged, non-CKAN hints
|
|
10
|
+
|
|
11
|
+
From the error analysis of `worker_events_flat.jsonl` (app errors are logged only since 2026-09-05: 754 of 3,752 calls, 537 of them invalid input from scanners).
|
|
12
|
+
|
|
13
|
+
- `ckan_find_portals` failed on every Worker call (96/96 since 2026-09-05): it was the only tool calling axios directly, and axios's fetch adapter breaks on Workers ("The 'cache' field on 'RequestInitializerDict' is not implemented"). Now uses `safeFetch`, like MQA; SSRF checks on redirects come with it. Checked with `wrangler dev`: 945 portals, about 5 s (the datashades.info list is 22 MB).
|
|
14
|
+
- The worker log line of a failed call now has `error_message` (first 200 chars, one line) and `error_kind` (`validation`, `not_allowed`, `timeout`, `upstream_http`, `unreachable`, `other`, `src/utils/error-telemetry.ts`); the archiver carries both into the flat file. The find_portals failure stayed hidden for weeks because the log only said `error`.
|
|
15
|
+
- `ckan_status_show` on a site that is not the CKAN API (47% failure on real URLs: open.toronto.ca, open.ottawa.ca, data.europa.eu/data, ...) told the caller to call `ckan_status_show`. It now says the site may not run CKAN or keep the API on another host, and points at `ckan_find_portals`. `open.toronto.ca` is routed to its CKAN host `ckan0.cf.opendata.inter.prod-toronto.ca` via `portals.json`.
|
|
16
|
+
- Tests 615 passed (+14, each new test fails without its fix), Python tests 5.
|
|
17
|
+
|
|
18
|
+
## 2026-10-27
|
|
19
|
+
|
|
20
|
+
### Archiver: empty result lists for zero-result searches
|
|
21
|
+
|
|
22
|
+
Manual archiver run on a scratchpad copy (7 live events): search fields reach `worker_events_flat.jsonl`, but a zero-result search came in with `result_ids`/`result_names` null, since the Observability API drops empty arrays from `source`. `flatten()` now restores `[]` whenever `result_count` is present, so "0 results" stays distinct from "not a search". Rechecked on a fresh copy: `zzqxkwvnonesiste` → `result_count` 0, lists `[]`. Tests in `tests/python/test_worker_telemetry_archiver.py` (stdlib `unittest`: zero count with omitted lists, non-empty lists, non-search event, field set); the zero-count case fails on the previous `flatten()`. They run in `update-telemetry.yml` before each archiver run (Greptile review).
|
|
23
|
+
|
|
24
|
+
### v0.4.132
|
|
25
|
+
|
|
26
|
+
Ships #565: worker telemetry logs returned search results. Tests 601 passed. Smoke 16/17: the MQA case times out because data.europa.eu `/api/mqa/cache/datasets/{id}` (and its successor `/resources/{id}`) hang for every dataset id tried, while `/global`, `/countries`, `/catalogues/dati-gov-it` answer in 0.2 s; released anyway on the user's decision. Live on npm (provenance), the MCP Registry (`isLatest`), the Worker (`/health` 0.4.132) and the GitHub release (DXT + skill attached). Observability API checked read-only: `result_ids` arrives as a list and `flatten()` maps it. The official spec (`openapi.yaml` 6.0.1-SNAPSHOT) marks `/datasets/{id}` deprecated, alias of `/resources/{id}`: switching is a separate change.
|
|
27
|
+
|
|
28
|
+
### Worker telemetry: returned search results
|
|
29
|
+
|
|
30
|
+
`ckan_package_search` and `ckan_find_relevant_datasets` now record the datasets they return (ids and names in rank order, max 20), the total count (zero included) and the start offset. The worker adds them to its log line (`result_count`, `result_start`, `result_ids`, `result_names`), and the archiver carries them, plus `fq` and `sort`, into `data/worker_events_flat.jsonl`. Goal: query → result pairs for search fine-tuning. No session id exists (stateless worker): a later `package_show` on an id in `result_ids` links to the search. Side channel keyed by the per-request `McpServer` (`src/utils/search-telemetry.ts`), so concurrent requests cannot mix. Checked with `wrangler dev` on dati.gov.it, zero-result case included.
|
|
31
|
+
|
|
32
|
+
## 2026-10-26
|
|
33
|
+
|
|
34
|
+
### Update Worker Telemetry: DuckDB pinned to v2.0.0
|
|
35
|
+
|
|
36
|
+
The workflow had failed twice a day since 2026-10-21: DuckDB v2.0.0 (released that day) renamed the CLI asset from `duckdb_cli-linux-amd64.zip` to `duckdb-cli-linux-amd64.tar.gz`, so `releases/latest/download/...zip` returned 404. Now downloads the v2.0.0 tarball explicitly (pinned, so a future rename does not break it again). Checked locally: `scripts/worker_daily_stats.sh` with v2.0.0 and v1.5.6 in UTC gives output identical to the committed `data/worker_daily_calls.jsonl`. Note: v2.0.0 reads `...Z` timestamps as `TIMESTAMPTZ`, so `::TIMESTAMP` follows the machine time zone; on the UTC runner nothing changes, on a non-UTC machine the daily counts shift.
|
|
37
|
+
|
|
3
38
|
## 2026-10-22
|
|
4
39
|
|
|
5
40
|
### v0.4.131
|
|
6
41
|
|
|
7
|
-
Ships #563: Anubis challenge retry with an honest User-Agent (#7). Tests 595 passed, smoke 17/17.
|
|
42
|
+
Ships #563: Anubis challenge retry with an honest User-Agent (#7). Tests 595 passed, smoke 17/17. Live on npm (with provenance; the registry took ~5 min to serve it after the CI publish), the MCP Registry (`isLatest`), the Worker (`/health` 0.4.131) and the GitHub release (DXT + skill attached).
|
|
8
43
|
|
|
9
44
|
### Anubis-fronted portals: retry once with an honest User-Agent (#7)
|
|
10
45
|
|
package/dist/index.js
CHANGED
|
@@ -96,6 +96,16 @@ var portals_default = {
|
|
|
96
96
|
"http://open.canada.ca"
|
|
97
97
|
]
|
|
98
98
|
},
|
|
99
|
+
{
|
|
100
|
+
id: "open-toronto",
|
|
101
|
+
name: "open.toronto.ca",
|
|
102
|
+
api_url: "https://ckan0.cf.opendata.inter.prod-toronto.ca",
|
|
103
|
+
api_url_aliases: [
|
|
104
|
+
"https://open.toronto.ca",
|
|
105
|
+
"http://open.toronto.ca"
|
|
106
|
+
],
|
|
107
|
+
dataset_view_url: "https://open.toronto.ca/dataset/{name}/"
|
|
108
|
+
},
|
|
99
109
|
{
|
|
100
110
|
id: "data-gov-au",
|
|
101
111
|
name: "data.gov.au",
|
|
@@ -1122,6 +1132,23 @@ async function makeCkanRequest(serverUrl, action, params = {}, opts = {}) {
|
|
|
1122
1132
|
}
|
|
1123
1133
|
}
|
|
1124
1134
|
|
|
1135
|
+
// src/utils/search-telemetry.ts
|
|
1136
|
+
var SEARCH_TELEMETRY_MAX_RESULTS = 20;
|
|
1137
|
+
var MAX_FIELD_LENGTH = 100;
|
|
1138
|
+
var lastResults = /* @__PURE__ */ new WeakMap();
|
|
1139
|
+
function recordSearchResults(server, count, start, datasets, replyText) {
|
|
1140
|
+
const shown = datasets.filter(
|
|
1141
|
+
(d) => d.id && replyText.includes(d.id) || d.name && replyText.includes(d.name)
|
|
1142
|
+
);
|
|
1143
|
+
const kept = shown.slice(0, SEARCH_TELEMETRY_MAX_RESULTS);
|
|
1144
|
+
lastResults.set(server, {
|
|
1145
|
+
result_count: count,
|
|
1146
|
+
result_start: start,
|
|
1147
|
+
result_ids: kept.map((d) => String(d.id ?? "").slice(0, MAX_FIELD_LENGTH)),
|
|
1148
|
+
result_names: kept.map((d) => String(d.name || d.id || "").slice(0, MAX_FIELD_LENGTH))
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1125
1152
|
// src/utils/formatting.ts
|
|
1126
1153
|
function truncateText(text, limit = CHARACTER_LIMIT) {
|
|
1127
1154
|
if (text.length <= limit) {
|
|
@@ -2248,8 +2275,10 @@ Typical workflow: ckan_status_show (check locale) \u2192 ckan_package_search (qu
|
|
|
2248
2275
|
params.server_url,
|
|
2249
2276
|
effectiveQuery !== params.q ? effectiveQuery : void 0
|
|
2250
2277
|
);
|
|
2278
|
+
const text2 = truncateJson(compact);
|
|
2279
|
+
recordSearchResults(server, result.count ?? 0, effectiveStart, result.results ?? [], text2);
|
|
2251
2280
|
return {
|
|
2252
|
-
content: [{ type: "text", text:
|
|
2281
|
+
content: [{ type: "text", text: text2 }]
|
|
2253
2282
|
};
|
|
2254
2283
|
}
|
|
2255
2284
|
let hvdNote = "";
|
|
@@ -2379,8 +2408,10 @@ Note: showing top ${sorted.length} only. Use \`response_format: json\` for full
|
|
|
2379
2408
|
`;
|
|
2380
2409
|
}
|
|
2381
2410
|
}
|
|
2411
|
+
const text = truncateText(addDemoFooter(markdown));
|
|
2412
|
+
recordSearchResults(server, result.count ?? 0, effectiveStart, result.results ?? [], text);
|
|
2382
2413
|
return {
|
|
2383
|
-
content: [{ type: "text", text
|
|
2414
|
+
content: [{ type: "text", text }]
|
|
2384
2415
|
};
|
|
2385
2416
|
} catch (error) {
|
|
2386
2417
|
return {
|
|
@@ -2508,7 +2539,9 @@ Typical workflow: ckan_find_relevant_datasets \u2192 ckan_package_show (inspect
|
|
|
2508
2539
|
results: top
|
|
2509
2540
|
};
|
|
2510
2541
|
if (params.response_format === "json" /* JSON */) {
|
|
2511
|
-
|
|
2542
|
+
const jsonResult = jsonToolResult(payload);
|
|
2543
|
+
recordSearchResults(server, payload.total_results, 0, top, jsonResult.content[0].text);
|
|
2544
|
+
return jsonResult;
|
|
2512
2545
|
}
|
|
2513
2546
|
let markdown = `# Relevant CKAN Datasets
|
|
2514
2547
|
|
|
@@ -2584,8 +2617,10 @@ Typical workflow: ckan_find_relevant_datasets \u2192 ckan_package_show (inspect
|
|
|
2584
2617
|
`;
|
|
2585
2618
|
});
|
|
2586
2619
|
}
|
|
2620
|
+
const text = truncateText(addDemoFooter(markdown));
|
|
2621
|
+
recordSearchResults(server, payload.total_results, 0, top, text);
|
|
2587
2622
|
return {
|
|
2588
|
-
content: [{ type: "text", text
|
|
2623
|
+
content: [{ type: "text", text }]
|
|
2589
2624
|
};
|
|
2590
2625
|
} catch (error) {
|
|
2591
2626
|
return {
|
|
@@ -3623,14 +3658,15 @@ Typical workflow: ckan_status_show (verify server is up) \u2192 ckan_package_sea
|
|
|
3623
3658
|
structuredContent: cappedStructured(result)
|
|
3624
3659
|
};
|
|
3625
3660
|
} catch (error) {
|
|
3626
|
-
const migrated = error instanceof CkanApiError && error.serverUrl && getPortalMigration(error.serverUrl);
|
|
3627
|
-
|
|
3661
|
+
const migrated = (error instanceof CkanApiError || error instanceof CkanTransportError) && error.serverUrl && getPortalMigration(error.serverUrl);
|
|
3662
|
+
if (migrated) {
|
|
3663
|
+
return { content: [{ type: "text", text: formatCkanError(error, "ckan_status_show") }], isError: true };
|
|
3664
|
+
}
|
|
3665
|
+
const detail = error instanceof CkanApiError || error instanceof CkanTransportError ? `${error.message}
|
|
3666
|
+
\u2192 No CKAN API answered at this address. The site may be up but not run CKAN, or keep its CKAN API on another host. Use \`ckan_find_portals\` to look up the portal's CKAN API URL; retry later only if this address usually works.` : formatCkanError(error, "ckan_status_show");
|
|
3628
3667
|
return {
|
|
3629
|
-
content: [{
|
|
3630
|
-
|
|
3631
|
-
text: migrated ? detail : `Server appears to be offline or not a valid CKAN instance:
|
|
3632
|
-
${detail}`
|
|
3633
|
-
}],
|
|
3668
|
+
content: [{ type: "text", text: `Server appears to be offline or not a valid CKAN instance:
|
|
3669
|
+
${detail}` }],
|
|
3634
3670
|
isError: true
|
|
3635
3671
|
};
|
|
3636
3672
|
}
|
|
@@ -5382,14 +5418,17 @@ ${error instanceof Error ? error.message : String(error)}`;
|
|
|
5382
5418
|
|
|
5383
5419
|
// src/tools/portal-discovery.ts
|
|
5384
5420
|
import { z as z11 } from "zod";
|
|
5385
|
-
import axios2 from "axios";
|
|
5386
5421
|
var DATASHADES_URL = "https://datashades.info/api/portal/list";
|
|
5387
5422
|
async function fetchPortals() {
|
|
5388
|
-
const resp = await
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
});
|
|
5392
|
-
|
|
5423
|
+
const resp = await safeFetch(DATASHADES_URL, {
|
|
5424
|
+
headers: { "User-Agent": "CKAN-MCP-Server/1.0" },
|
|
5425
|
+
signal: AbortSignal.timeout(15e3)
|
|
5426
|
+
}, { httpsOnly: true });
|
|
5427
|
+
if (!resp.ok) {
|
|
5428
|
+
throw new Error(`datashades.info returned ${resp.status} ${resp.statusText}`);
|
|
5429
|
+
}
|
|
5430
|
+
const data = await resp.json();
|
|
5431
|
+
return data.portals;
|
|
5393
5432
|
}
|
|
5394
5433
|
function deduplicateByHostname(portals) {
|
|
5395
5434
|
const seen = /* @__PURE__ */ new Map();
|
|
@@ -6117,7 +6156,7 @@ var registerAllPrompts = (server) => {
|
|
|
6117
6156
|
function createServer() {
|
|
6118
6157
|
return new McpServer({
|
|
6119
6158
|
name: "ckan-mcp-server",
|
|
6120
|
-
version: "0.4.
|
|
6159
|
+
version: "0.4.133"
|
|
6121
6160
|
});
|
|
6122
6161
|
}
|
|
6123
6162
|
function registerAll(server) {
|