@aborruso/ckan-mcp-server 0.4.125 → 0.4.126
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 +10 -0
- package/dist/index.js +65 -6
- package/package.json +1 -1
package/LOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## 2026-09-23
|
|
4
4
|
|
|
5
|
+
### v0.4.126 - why MQA URL tests fail
|
|
6
|
+
|
|
7
|
+
Ships #552. For a caller: `ckan_get_mqa_quality_details` now says why URL tests fail (`HTTP test: 1100 timeout ×21, no status recorded ×3`). For the project: `npm run smoke` checks the MQA tool live against data.europa.eu, so a format change there fails the release gate.
|
|
8
|
+
|
|
9
|
+
### MQA: smoke gate and HTTP status of failing URL tests
|
|
10
|
+
|
|
11
|
+
- `npm run smoke` gains a live MQA case (Roma `czrm-…-2023`: `methodology` v2, `maxScore` 7.5, 0 ≤ `score` ≤ 7.5) and a generic `field_max`. v0.4.124 shipped broken MQA tools with green tests because the tests read stored fixtures; the gate now asks data.europa.eu. Negative check: expecting `v1` fails the case.
|
|
12
|
+
- `ckan_get_mqa_quality_details` says why URL tests fail: `HTTP test: 1100 timeout ×21, no status recorded ×3`. Codes come from the metrics graph (`http:statusCodeValue` in v2, `dqv:value` in v1; ≥1000 are piveau's own, 1100 = timeout), latest per distribution, 2xx excluded. Fetched only by the details tool and only when a URL test fails (the graph is ~570 KB); best effort, a failed fetch leaves the details intact.
|
|
13
|
+
- Bug caught by the tests: boolean result nodes share the metric id, and `Number(false)` read as status code 0.
|
|
14
|
+
|
|
5
15
|
### v0.4.125 - MQA scores on the new data.europa.eu methodology
|
|
6
16
|
|
|
7
17
|
Ships #549 (closes #548). For a caller: `ckan_get_mqa_quality` and `ckan_get_mqa_quality_details` report the MQA v2 score (0-7.5, band, failing metrics by gain) instead of printing v2 numbers on the old 405 scale or failing with "identifier not aligned"; datasets not yet re-evaluated get the previous-methodology score, labelled. The MQA tools work again on the Cloudflare Worker. **BREAKING** for JSON consumers of the two MQA tools: compact shape with `methodology`, `metricsVersion`, `score`, `maxScore`.
|
package/dist/index.js
CHANGED
|
@@ -4497,7 +4497,55 @@ async function fetchMqaV1(europeanId, links) {
|
|
|
4497
4497
|
...links
|
|
4498
4498
|
};
|
|
4499
4499
|
}
|
|
4500
|
-
|
|
4500
|
+
var STATUS_METRICS = /* @__PURE__ */ new Set(["accessUrlStatusCode", "downloadUrlStatusCode"]);
|
|
4501
|
+
var HTTP_STATUS_CODE = "http://www.w3.org/2011/http#statusCodeValue";
|
|
4502
|
+
function extractStatusCodes(metricsData) {
|
|
4503
|
+
const latest = /* @__PURE__ */ new Map();
|
|
4504
|
+
const graph = decodeMetricsPayload(metricsData)?.["@graph"];
|
|
4505
|
+
for (const node of Array.isArray(graph) ? graph : []) {
|
|
4506
|
+
if (!node || typeof node !== "object") continue;
|
|
4507
|
+
const record = node;
|
|
4508
|
+
const metricRef = record["dqv:isMeasurementOf"];
|
|
4509
|
+
const metricId = typeof metricRef === "string" ? metricRef : metricRef?.["@id"];
|
|
4510
|
+
if (typeof metricId !== "string") continue;
|
|
4511
|
+
const metric = metricKeyFromId(metricId);
|
|
4512
|
+
if (!STATUS_METRICS.has(metric)) continue;
|
|
4513
|
+
const raw = record[HTTP_STATUS_CODE] !== void 0 ? Number(record[HTTP_STATUS_CODE]) : parseMetricValue(record["dqv:value"]);
|
|
4514
|
+
if (typeof raw !== "number" || !Number.isInteger(raw)) continue;
|
|
4515
|
+
const code = raw;
|
|
4516
|
+
const target = record["dqv:computedOn"]?.["@id"];
|
|
4517
|
+
const time = String(record["prov:generatedAtTime"]?.["@value"] ?? "");
|
|
4518
|
+
const key = `${metric} ${typeof target === "string" ? target : ""}`;
|
|
4519
|
+
const current = latest.get(key);
|
|
4520
|
+
if (current && current.time > time) continue;
|
|
4521
|
+
const description = typeof record["dct:description"] === "string" ? record["dct:description"] : "";
|
|
4522
|
+
const label = code >= 1e3 ? `${code} ${/timeout/i.test(description) ? "timeout" : "connection error"}` : String(code);
|
|
4523
|
+
latest.set(key, { metric, code, label, time });
|
|
4524
|
+
}
|
|
4525
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4526
|
+
for (const { metric, code, label } of latest.values()) {
|
|
4527
|
+
if (code >= 200 && code < 300) continue;
|
|
4528
|
+
const perMetric = counts.get(metric) ?? {};
|
|
4529
|
+
perMetric[label] = (perMetric[label] ?? 0) + 1;
|
|
4530
|
+
counts.set(metric, perMetric);
|
|
4531
|
+
}
|
|
4532
|
+
return counts;
|
|
4533
|
+
}
|
|
4534
|
+
async function addHttpStatus(result) {
|
|
4535
|
+
const urlTests = result.failing.filter((item) => item.entity === "distribution" && STATUS_METRICS.has(item.metric));
|
|
4536
|
+
if (urlTests.length === 0) return;
|
|
4537
|
+
let counts;
|
|
4538
|
+
try {
|
|
4539
|
+
counts = extractStatusCodes(await fetchMetricsGraph(`${MQA_METRICS_BASE}/${result.portalId}/metrics`));
|
|
4540
|
+
} catch {
|
|
4541
|
+
return;
|
|
4542
|
+
}
|
|
4543
|
+
for (const item of urlTests) {
|
|
4544
|
+
const perCode = counts.get(item.metric);
|
|
4545
|
+
if (perCode) item.httpStatus = perCode;
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4548
|
+
async function getMqaQuality(serverUrl, datasetId, opts = {}) {
|
|
4501
4549
|
const dataset = await makeCkanRequest(
|
|
4502
4550
|
serverUrl,
|
|
4503
4551
|
"package_show",
|
|
@@ -4534,7 +4582,11 @@ async function getMqaQuality(serverUrl, datasetId) {
|
|
|
4534
4582
|
if (!entry || typeof entry !== "object") {
|
|
4535
4583
|
throw new Error("Unexpected MQA payload: no result");
|
|
4536
4584
|
}
|
|
4537
|
-
|
|
4585
|
+
const result = parseMqaV2(entry, links);
|
|
4586
|
+
if (opts.httpStatus) {
|
|
4587
|
+
await addHttpStatus(result);
|
|
4588
|
+
}
|
|
4589
|
+
return result;
|
|
4538
4590
|
}
|
|
4539
4591
|
throw new Error(
|
|
4540
4592
|
`No MQA record on data.europa.eu for this dataset (identifier may not be aligned). Tried: ${candidates.join(", ")}. Check the dataset quality page on data.europa.eu to confirm the identifier (it may include a '~~1' suffix) or verify alignment on dati.gov.it (quality may be marked as 'Non disponibile o identificativo non allineato').`
|
|
@@ -4545,7 +4597,14 @@ function formatNumber(value) {
|
|
|
4545
4597
|
}
|
|
4546
4598
|
function describeFailing(item) {
|
|
4547
4599
|
const scope = item.entity === "dataset" ? "dataset" : `${item.failed} of ${item.total} ${item.entity === "distribution" ? "distributions" : "data services"}`;
|
|
4548
|
-
|
|
4600
|
+
let http = "";
|
|
4601
|
+
if (item.httpStatus) {
|
|
4602
|
+
const codes = Object.entries(item.httpStatus).sort((a, b) => b[1] - a[1]).map(([code, n]) => `${code} \xD7${n}`);
|
|
4603
|
+
const unexplained = item.failed - Object.values(item.httpStatus).reduce((sum, n) => sum + n, 0);
|
|
4604
|
+
if (unexplained > 0) codes.push(`no status recorded \xD7${unexplained}`);
|
|
4605
|
+
http = `; HTTP test: ${codes.join(", ")}`;
|
|
4606
|
+
}
|
|
4607
|
+
return `\`${item.property}\` (${item.metric}, ${item.importance.toLowerCase()} ${item.weight}) - ${scope}, +${formatNumber(item.gain)}${http}`;
|
|
4549
4608
|
}
|
|
4550
4609
|
function pushV2Scores(lines, result) {
|
|
4551
4610
|
lines.push(`**Score**: ${formatNumber(result.score)}/${result.maxScore} (${result.band})`);
|
|
@@ -4704,7 +4763,7 @@ The MQA (Metadata Quality Assurance) system is operated by data.europa.eu and on
|
|
|
4704
4763
|
"ckan_get_mqa_quality_details",
|
|
4705
4764
|
{
|
|
4706
4765
|
title: "Get MQA Quality Details",
|
|
4707
|
-
description: "Get detailed MQA (Metadata Quality Assessment) quality reasons for a dataset on dati.gov.it. Lists every failing metric grouped by FAIR dimension, with DCAT-AP property, weight, how many distributions fail it and its gain on the final score (methodology v2); previous-methodology datasets get non-max reasons. Only works with dati.gov.it server. Typical workflow: ckan_get_mqa_quality (get overview scores) \u2192 ckan_get_mqa_quality_details (inspect failing metrics)",
|
|
4766
|
+
description: "Get detailed MQA (Metadata Quality Assessment) quality reasons for a dataset on dati.gov.it. Lists every failing metric grouped by FAIR dimension, with DCAT-AP property, weight, how many distributions fail it and its gain on the final score, plus the HTTP status of failing URL tests (e.g. 1100 timeout, 404) (methodology v2); previous-methodology datasets get non-max reasons. Only works with dati.gov.it server. Typical workflow: ckan_get_mqa_quality (get overview scores) \u2192 ckan_get_mqa_quality_details (inspect failing metrics)",
|
|
4708
4767
|
inputSchema: z8.object({
|
|
4709
4768
|
server_url: z8.string().url().describe("Base URL of dati.gov.it (e.g., https://www.dati.gov.it/opendata)"),
|
|
4710
4769
|
dataset_id: z8.string().describe("Dataset ID or name"),
|
|
@@ -4733,7 +4792,7 @@ The MQA (Metadata Quality Assurance) system is operated by data.europa.eu and on
|
|
|
4733
4792
|
};
|
|
4734
4793
|
}
|
|
4735
4794
|
try {
|
|
4736
|
-
const details = await getMqaQuality(server_url, dataset_id);
|
|
4795
|
+
const details = await getMqaQuality(server_url, dataset_id, { httpStatus: true });
|
|
4737
4796
|
const format = response_format || "markdown" /* MARKDOWN */;
|
|
4738
4797
|
const output = format === "json" /* JSON */ ? truncateJson(details) : truncateText(addDemoFooter(formatQualityDetailsMarkdown(details, dataset_id)));
|
|
4739
4798
|
return {
|
|
@@ -5935,7 +5994,7 @@ var registerAllPrompts = (server) => {
|
|
|
5935
5994
|
function createServer() {
|
|
5936
5995
|
return new McpServer({
|
|
5937
5996
|
name: "ckan-mcp-server",
|
|
5938
|
-
version: "0.4.
|
|
5997
|
+
version: "0.4.126"
|
|
5939
5998
|
});
|
|
5940
5999
|
}
|
|
5941
6000
|
function registerAll(server) {
|