@aborruso/ckan-mcp-server 0.4.113 → 0.4.115
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 +47 -0
- package/dist/index.js +26 -55
- package/package.json +5 -1
- package/dist/worker.js +0 -1319
package/LOG.md
CHANGED
|
@@ -1,7 +1,54 @@
|
|
|
1
1
|
# LOG
|
|
2
2
|
|
|
3
|
+
## 2026-08-03
|
|
4
|
+
|
|
5
|
+
### v0.4.115
|
|
6
|
+
|
|
7
|
+
First release published from CI. No functional change: this exists to exercise the release workflow added earlier today end to end — tag guard, OIDC authentication, provenance attestation — rather than discovering whether it works during a release that actually matters.
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### npm provenance: release workflow on tags
|
|
12
|
+
|
|
13
|
+
`npm view @aborruso/ckan-mcp-server@0.4.114 dist.attestations` came back empty — packages were published by hand from a local machine, with nothing binding a tarball to the commit that produced it. Adopters could check *which* version was current (see below) but not *where it came from*.
|
|
14
|
+
|
|
15
|
+
- New `.github/workflows/release.yml`: triggers on `v*` tags, verifies the tag matches `package.json` **before** anything else (npm publishes are irreversible after 72h), then `npm ci` → build → tests → `npm publish --provenance`. Guard tested both ways; `npm test -- --run` confirmed against the current 461-test suite.
|
|
16
|
+
- `package.json` had **no `repository` field** — a hard prerequisite for provenance, which would have failed the publish. Set to `ondata/ckan-mcp-server`.
|
|
17
|
+
- Release workflow in `CLAUDE.md` rewritten rather than extended: step 5 now warns that pushing the tag *is* the publish, step 9 says explicitly not to run `npm publish` by hand (two paths would collide on `EPUBLISHCONFLICT`), and step 10 must wait for the CI run to go green because the MCP Registry validates that the npm version exists.
|
|
18
|
+
- `.readme-full.md` added to `.gitignore`: the `prepack`/`postpack` pair swaps in the short npm README, so a local publish failing between the two hooks leaves the wrong `README.md` in the tree, one `git add .` away from being committed.
|
|
19
|
+
- Authentication is **trusted publishing (OIDC)**, not a secret. The first draft used an `NPM_TOKEN`; `npm profile get` then surfaced npm's own warning that "tokens that bypass 2FA are being restricted for direct publishing", which pointed at the mechanism npm now recommends instead. Trusted publishing needs no stored credential, emits provenance by default, and — because the trusted publisher names the GitHub repo explicitly — also settles the open question about the npm scope (`@aborruso`) differing from the GitHub org (`ondata`). The workflow upgrades npm to ≥ 11.5.1 explicitly: Node 22 ships npm 10.x, which falls back to token auth *silently* and would publish unattested. `--provenance` is kept although implied, so that degradation fails the job instead of passing quietly.
|
|
20
|
+
- **Not yet active**: the trusted publisher has to be registered by hand on npmjs.com (package → Settings → Trusted Publisher → GitHub Actions → `ondata` / `ckan-mcp-server` / `release.yml`). Until then the workflow runs and fails at the publish step. Note that the workflow filename is part of that identity.
|
|
21
|
+
|
|
22
|
+
### MCP Registry entry realigned to 0.4.114
|
|
23
|
+
|
|
24
|
+
Published and verified against the public endpoint: the registry now serves **0.4.114** with `isLatest: true` (updated 2026-08-03T06:01:07Z), and the old 0.4.83 record dropped to `isLatest: false`.
|
|
25
|
+
|
|
26
|
+
The official MCP Registry entry for `io.github.aborruso/ckan-mcp-server` had been stuck at **0.4.83 since 2026-03-12** — 31 patch releases and almost five months behind npm, while still flagged `isLatest: true`. Clients resolving the server through the registry were pointed at a build predating the v0.4.108 SSRF remediation, and 0.4.83 is still installable from npm.
|
|
27
|
+
|
|
28
|
+
- Root cause, not a one-off slip: the Release Workflow in `CLAUDE.md` listed the version bump for `package.json` and `manifest.json` but never `server.json`, and `npm publish` does not touch the registry. The drift was structural and would have kept growing.
|
|
29
|
+
- Fixed `server.json` (both `version` and `packages[0].version` — two fields, easy to half-update) and rewrote the release workflow: `server.json` added to step 1, a new step 10 for `mcp-publisher publish` placed after `npm publish` since the registry validates that the npm version exists, plus a `curl` one-liner to verify the published entry.
|
|
30
|
+
- Surfaced by an unsolicited vendor email selling a £395 "MCP Readiness Audit". The sales pitch was worthless — the remedy it offered has nothing to do with the defect — but the three technical claims all checked out under verification. Worth recording: the finding was real and cost the sender two `curl` calls, which is exactly how long it would have taken us to catch it ourselves with a check in the release procedure.
|
|
31
|
+
- Registry tokens (`.mcpregistry_*`) verified: gitignored, never committed.
|
|
32
|
+
|
|
3
33
|
## 2026-07-31
|
|
4
34
|
|
|
35
|
+
### v0.4.114
|
|
36
|
+
|
|
37
|
+
structuredContent capped like the text (closes #39).
|
|
38
|
+
|
|
39
|
+
The v0.4.113 cap applied to `content[].text` only, so any client reading the structured channel got the full payload — `ckan_tag_list limit=1000` on dati.gov.it: ~50K of text against 65,382 uncapped characters. The limit was a fiction for those clients.
|
|
40
|
+
|
|
41
|
+
- New `jsonToolResult()`: truncates once via `truncateJson` and parses the result back into `structuredContent`, so the two channels cannot disagree and `_truncated`/`_original_count` reach structured readers too. Applied to 13 call sites.
|
|
42
|
+
- `ckan_status_show` and `ckan_find_portals` pair structured output with Markdown text, so there is no truncated JSON to derive it from: they cap it on its own via the new `cappedStructured()`. My first pass left them uncapped calling them "bounded by shape" — wrong, as review pointed out: `status_show` is echoed straight from the portal, and the 50-result limit on `find_portals` bounds the number of entries, not the length of their titles and URLs. Only the `all_fields=false` count branches of `ckan_organization_list`/`ckan_group_list` stay outside the cap: a single integer.
|
|
43
|
+
- **Correction**: the deferral in v0.4.113 claimed capping would drop rows from `datastore-table-ui`. That was wrong and never verified — the UI resource is commented out in `src/resources/index.ts:18` and never registered, and `ckan_datastore_search` returns no `structuredContent` at all. No exception was needed. Same failure mode as the bug being fixed: a plausible claim about the code that nobody checked.
|
|
44
|
+
- 7 new tests (461 total), covering a payload long in a single field rather than in many, and a bounded-count list whose entries are individually oversized. Docs realigned: `README.md`, `docs/JSON-OUTPUT.md`, `docs/DECISIONS.md`, `CLAUDE.md` all asserted the uncapped behaviour.
|
|
45
|
+
- Released end to end: tag, GitHub release with DXT and skill, npm `0.4.114`, Cloudflare version `36b611fd`. Verified on the public endpoint after waiting for edge propagation — `tag_list limit=1000`: 49,938 characters on both channels, byte-identical, `_truncated` set. `status_show` and `find_portals` legitimately differ across channels: Markdown text against JSON structured output, two renderings of the same data.
|
|
46
|
+
- Process note: check edge propagation with an active probe, not a fixed wait. The v0.4.113 verification measured the old code because it ran seconds after deploy, and briefly looked like a broken release.
|
|
47
|
+
|
|
48
|
+
### v0.4.113 — release addendum
|
|
49
|
+
|
|
50
|
+
Released the same day: tag, GitHub release with DXT and skill, npm `0.4.113`, Cloudflare version `1cac40ea`. Also merged #38 (`docs: document user-facing limits`, contributed by `averyquinnhq`), which closed #37, and disabled the `claude-code-review` workflow: it cannot pass on fork PRs, since GitHub withholds secrets and the OIDC token for `pull_request` events from forks, so the job dies on token setup regardless of the diff. The file is kept in the repo. `CONTRIBUTING.md` now also states that `build:tsc` is not a gate — a contributor exhausted 8 GB of heap on it before reporting they could not pass it, because the warning lived only in `CLAUDE.md`.
|
|
51
|
+
|
|
5
52
|
### v0.4.113
|
|
6
53
|
|
|
7
54
|
JSON output always parseable (issue #39).
|
package/dist/index.js
CHANGED
|
@@ -1049,6 +1049,16 @@ function truncateJson(obj, limit = CHARACTER_LIMIT) {
|
|
|
1049
1049
|
const bare = JSON.stringify({ _truncated: true });
|
|
1050
1050
|
return bare.length <= limit ? bare : "{}";
|
|
1051
1051
|
}
|
|
1052
|
+
function jsonToolResult(obj, limit = CHARACTER_LIMIT) {
|
|
1053
|
+
const text = truncateJson(obj, limit);
|
|
1054
|
+
return {
|
|
1055
|
+
content: [{ type: "text", text }],
|
|
1056
|
+
structuredContent: JSON.parse(text)
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
function cappedStructured(obj, limit = CHARACTER_LIMIT) {
|
|
1060
|
+
return JSON.parse(truncateJson(obj, limit));
|
|
1061
|
+
}
|
|
1052
1062
|
function formatError(message, asJson) {
|
|
1053
1063
|
return asJson ? JSON.stringify({ error: message, _error: true }, null, 2) : message;
|
|
1054
1064
|
}
|
|
@@ -2158,10 +2168,7 @@ Typical workflow: ckan_find_relevant_datasets \u2192 ckan_package_show (inspect
|
|
|
2158
2168
|
results: top
|
|
2159
2169
|
};
|
|
2160
2170
|
if (params.response_format === "json" /* JSON */) {
|
|
2161
|
-
return
|
|
2162
|
-
content: [{ type: "text", text: truncateJson(payload) }],
|
|
2163
|
-
structuredContent: payload
|
|
2164
|
-
};
|
|
2171
|
+
return jsonToolResult(payload);
|
|
2165
2172
|
}
|
|
2166
2173
|
let markdown = `# Relevant CKAN Datasets
|
|
2167
2174
|
|
|
@@ -2305,10 +2312,7 @@ Typical workflow: ckan_package_show \u2192 pick a resource with datastore_active
|
|
|
2305
2312
|
);
|
|
2306
2313
|
if (params.response_format === "json" /* JSON */) {
|
|
2307
2314
|
const compact = compactPackageShow(enrichPackageShowResult(result), params.server_url);
|
|
2308
|
-
return
|
|
2309
|
-
content: [{ type: "text", text: truncateJson(compact) }],
|
|
2310
|
-
structuredContent: compact
|
|
2311
|
-
};
|
|
2315
|
+
return jsonToolResult(compact);
|
|
2312
2316
|
}
|
|
2313
2317
|
const markdown = formatPackageShowMarkdown(result, params.server_url);
|
|
2314
2318
|
return {
|
|
@@ -2417,10 +2421,7 @@ dataset's own resource URLs); set check_source_portal=true to enable it.`,
|
|
|
2417
2421
|
format_filter: formatFilter ?? null,
|
|
2418
2422
|
resources: summary
|
|
2419
2423
|
};
|
|
2420
|
-
return
|
|
2421
|
-
content: [{ type: "text", text: truncateJson(payload) }],
|
|
2422
|
-
structuredContent: payload
|
|
2423
|
-
};
|
|
2424
|
+
return jsonToolResult(payload);
|
|
2424
2425
|
}
|
|
2425
2426
|
let markdown = `# Resources: ${result.title || result.name}
|
|
2426
2427
|
|
|
@@ -2690,10 +2691,7 @@ Typical workflow: ckan_organization_list \u2192 ckan_organization_show (inspect
|
|
|
2690
2691
|
}));
|
|
2691
2692
|
if (params.response_format === "json" /* JSON */) {
|
|
2692
2693
|
const output = { count: items.length, organizations };
|
|
2693
|
-
return
|
|
2694
|
-
content: [{ type: "text", text: truncateJson(output) }],
|
|
2695
|
-
structuredContent: output
|
|
2696
|
-
};
|
|
2694
|
+
return jsonToolResult(output);
|
|
2697
2695
|
}
|
|
2698
2696
|
let markdown2 = `# CKAN Organizations
|
|
2699
2697
|
|
|
@@ -2726,10 +2724,7 @@ Note: organization_list returned 500; using package_search facets.
|
|
|
2726
2724
|
}
|
|
2727
2725
|
if (params.response_format === "json" /* JSON */) {
|
|
2728
2726
|
const compact = compactOrganizationList(result);
|
|
2729
|
-
return
|
|
2730
|
-
content: [{ type: "text", text: truncateJson(compact) }],
|
|
2731
|
-
structuredContent: compact
|
|
2732
|
-
};
|
|
2727
|
+
return jsonToolResult(compact);
|
|
2733
2728
|
}
|
|
2734
2729
|
let markdown = `# CKAN Organizations
|
|
2735
2730
|
|
|
@@ -2818,10 +2813,7 @@ Typical workflow: ckan_organization_show \u2192 ckan_package_show (inspect a dat
|
|
|
2818
2813
|
);
|
|
2819
2814
|
if (params.response_format === "json" /* JSON */) {
|
|
2820
2815
|
const compact = compactOrganizationShow(result, params.server_url);
|
|
2821
|
-
return
|
|
2822
|
-
content: [{ type: "text", text: truncateJson(compact) }],
|
|
2823
|
-
structuredContent: compact
|
|
2824
|
-
};
|
|
2816
|
+
return jsonToolResult(compact);
|
|
2825
2817
|
}
|
|
2826
2818
|
const markdown = formatOrganizationShowMarkdown(result, params.server_url);
|
|
2827
2819
|
return {
|
|
@@ -2895,10 +2887,7 @@ Typical workflow: ckan_organization_search \u2192 ckan_organization_show (get de
|
|
|
2895
2887
|
view_url: getOrganizationViewUrl(params.server_url, { name: item.name })
|
|
2896
2888
|
}))
|
|
2897
2889
|
};
|
|
2898
|
-
return
|
|
2899
|
-
content: [{ type: "text", text: truncateJson(jsonResult) }],
|
|
2900
|
-
structuredContent: jsonResult
|
|
2901
|
-
};
|
|
2890
|
+
return jsonToolResult(jsonResult);
|
|
2902
2891
|
}
|
|
2903
2892
|
let markdown = `# CKAN Organization Search Results
|
|
2904
2893
|
|
|
@@ -3199,10 +3188,7 @@ Security note: SQL queries are forwarded directly to the CKAN DataStore API. The
|
|
|
3199
3188
|
);
|
|
3200
3189
|
if (params.response_format === "json" /* JSON */) {
|
|
3201
3190
|
const compact = compactDatastoreResult(result);
|
|
3202
|
-
return
|
|
3203
|
-
content: [{ type: "text", text: truncateJson(compact) }],
|
|
3204
|
-
structuredContent: compact
|
|
3205
|
-
};
|
|
3191
|
+
return jsonToolResult(compact);
|
|
3206
3192
|
}
|
|
3207
3193
|
const markdown = formatDatastoreSqlMarkdown(result, params.server_url, params.sql);
|
|
3208
3194
|
return {
|
|
@@ -3274,7 +3260,7 @@ Typical workflow: ckan_status_show (verify server is up) \u2192 ckan_package_sea
|
|
|
3274
3260
|
const markdown = formatStatusMarkdown(result, params.server_url, hvdCount);
|
|
3275
3261
|
return {
|
|
3276
3262
|
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }],
|
|
3277
|
-
structuredContent: result
|
|
3263
|
+
structuredContent: cappedStructured(result)
|
|
3278
3264
|
};
|
|
3279
3265
|
} catch (error) {
|
|
3280
3266
|
return {
|
|
@@ -3384,10 +3370,7 @@ Typical workflow: ckan_tag_list \u2192 ckan_package_search with fq="tags:tag_nam
|
|
|
3384
3370
|
count: tags.length,
|
|
3385
3371
|
tags
|
|
3386
3372
|
};
|
|
3387
|
-
return
|
|
3388
|
-
content: [{ type: "text", text: truncateJson(output) }],
|
|
3389
|
-
structuredContent: output
|
|
3390
|
-
};
|
|
3373
|
+
return jsonToolResult(output);
|
|
3391
3374
|
}
|
|
3392
3375
|
let markdown = `# CKAN Tags
|
|
3393
3376
|
|
|
@@ -3620,10 +3603,7 @@ Typical workflow: ckan_group_list \u2192 ckan_group_show (inspect one) \u2192 ck
|
|
|
3620
3603
|
);
|
|
3621
3604
|
if (params.response_format === "json" /* JSON */) {
|
|
3622
3605
|
const compact = compactGroupList(result);
|
|
3623
|
-
return
|
|
3624
|
-
content: [{ type: "text", text: truncateJson(compact) }],
|
|
3625
|
-
structuredContent: compact
|
|
3626
|
-
};
|
|
3606
|
+
return jsonToolResult(compact);
|
|
3627
3607
|
}
|
|
3628
3608
|
let markdown = `# CKAN Groups
|
|
3629
3609
|
|
|
@@ -3709,10 +3689,7 @@ Typical workflow: ckan_group_show \u2192 ckan_package_show (inspect a dataset) \
|
|
|
3709
3689
|
);
|
|
3710
3690
|
if (params.response_format === "json" /* JSON */) {
|
|
3711
3691
|
const compact = compactGroupShow(result);
|
|
3712
|
-
return
|
|
3713
|
-
content: [{ type: "text", text: truncateJson(compact) }],
|
|
3714
|
-
structuredContent: compact
|
|
3715
|
-
};
|
|
3692
|
+
return jsonToolResult(compact);
|
|
3716
3693
|
}
|
|
3717
3694
|
const markdown = formatGroupShowMarkdown(result, params.server_url);
|
|
3718
3695
|
return {
|
|
@@ -3781,10 +3758,7 @@ Typical workflow: ckan_group_search \u2192 ckan_group_show (get details) \u2192
|
|
|
3781
3758
|
dataset_count: group.count
|
|
3782
3759
|
}))
|
|
3783
3760
|
};
|
|
3784
|
-
return
|
|
3785
|
-
content: [{ type: "text", text: truncateJson(jsonResult) }],
|
|
3786
|
-
structuredContent: jsonResult
|
|
3787
|
-
};
|
|
3761
|
+
return jsonToolResult(jsonResult);
|
|
3788
3762
|
}
|
|
3789
3763
|
let markdown = `# CKAN Group Search Results
|
|
3790
3764
|
|
|
@@ -4986,10 +4960,7 @@ Typical workflow: sparql_query (explore schema) \u2192 sparql_query (targeted qu
|
|
|
4986
4960
|
const data = await querySparqlEndpoint(params.endpoint_url, limitedQuery);
|
|
4987
4961
|
if (params.response_format === "json" /* JSON */) {
|
|
4988
4962
|
const result = formatSparqlJson(data);
|
|
4989
|
-
return
|
|
4990
|
-
content: [{ type: "text", text: truncateJson(result) }],
|
|
4991
|
-
structuredContent: result
|
|
4992
|
-
};
|
|
4963
|
+
return jsonToolResult(result);
|
|
4993
4964
|
}
|
|
4994
4965
|
return {
|
|
4995
4966
|
content: [{ type: "text", text: truncateText(formatSparqlMarkdown(data, params.endpoint_url)) }]
|
|
@@ -5111,7 +5082,7 @@ Typical workflow: ckan_find_portals (discover portal URL) \u2192 ckan_status_sho
|
|
|
5111
5082
|
const markdown = formatMarkdown(results, active.length, params.limit);
|
|
5112
5083
|
return {
|
|
5113
5084
|
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }],
|
|
5114
|
-
structuredContent: { portals: results.map((p) => ({
|
|
5085
|
+
structuredContent: cappedStructured({ portals: results.map((p) => ({
|
|
5115
5086
|
url: p.Href,
|
|
5116
5087
|
title: p.SiteInfo.site_title,
|
|
5117
5088
|
country: p.Coordinates.country_name,
|
|
@@ -5119,7 +5090,7 @@ Typical workflow: ckan_find_portals (discover portal URL) \u2192 ckan_status_sho
|
|
|
5119
5090
|
datasets: p.DatasetsNumber,
|
|
5120
5091
|
locale: p.SiteInfo.locale_default,
|
|
5121
5092
|
has_datastore: (p.Plugins || []).includes("datastore")
|
|
5122
|
-
})) }
|
|
5093
|
+
})) })
|
|
5123
5094
|
};
|
|
5124
5095
|
} catch (error) {
|
|
5125
5096
|
return {
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aborruso/ckan-mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.115",
|
|
4
4
|
"mcpName": "io.github.aborruso/ckan-mcp-server",
|
|
5
5
|
"description": "MCP server for interacting with CKAN open data portals",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/ondata/ckan-mcp-server.git"
|
|
9
|
+
},
|
|
6
10
|
"main": "dist/index.js",
|
|
7
11
|
"type": "module",
|
|
8
12
|
"bin": {
|