@aborruso/ckan-mcp-server 0.4.115 → 0.4.116
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 +24 -0
- package/dist/index.js +30 -13
- package/package.json +1 -1
package/LOG.md
CHANGED
|
@@ -1,11 +1,35 @@
|
|
|
1
1
|
# LOG
|
|
2
2
|
|
|
3
|
+
## 2026-08-04
|
|
4
|
+
|
|
5
|
+
### v0.4.116
|
|
6
|
+
|
|
7
|
+
Ships the two DataStore output fixes below, plus `.greptile/rules.md`: the automated review on #43 produced one confident false positive, so the invariants a generic reviewer cannot know are now stated in the repo.
|
|
8
|
+
|
|
9
|
+
### DataStore tables no longer hide columns silently
|
|
10
|
+
|
|
11
|
+
Both markdown renderers in `src/tools/datastore.ts` cut the record table at 8 columns with no notice. On the Messina electoral-lists resource (14 columns) the table stopped right before `cognome`, `nome`, `sesso` and `voti`: a model reading it saw an election dataset with no votes and no candidate names, and nothing told it anything was missing. Row truncation was already handled properly (`... and N more records`, `Total Records`); columns were not.
|
|
12
|
+
|
|
13
|
+
Both renderers now append a note naming the omitted columns and pointing at the way to retrieve them (`fields` parameter for search, an explicit SELECT list for SQL, or `response_format: "json"`). The JSON path never had the bug — `compactDatastoreResult` passes every column through.
|
|
14
|
+
|
|
15
|
+
Found while checking what the GovInsider piece on the OKFN Brazil/Uruguay pilot added to `docs/future-ideas.md` (nothing new — it covers the same pilot already recorded on 2026-06-11), but its failure mode is exactly this: the model fills a gap it cannot see.
|
|
16
|
+
|
|
17
|
+
### `_full_text` no longer eats a table slot
|
|
18
|
+
|
|
19
|
+
Surfaced by the end-to-end check above: `datastore_search_sql` on `SELECT *` returns CKAN's internal `_full_text` column, which repeats the whole row as one concatenated string. It was taking the first table slot and pushing out a real column, and the same query reported 15 columns via SQL against 14 via `datastore_search`. `_id` was already filtered; `_full_text` now is too, in both renderers and in the JSON output (where it was pure token waste). The two tools now agree on the column count.
|
|
20
|
+
|
|
21
|
+
465 tests pass; verified end to end against `dati.comune.messina.it`.
|
|
22
|
+
|
|
3
23
|
## 2026-08-03
|
|
4
24
|
|
|
5
25
|
### v0.4.115
|
|
6
26
|
|
|
7
27
|
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
28
|
|
|
29
|
+
Worked on the first try: the job went green in 34s and the attestation binds the tarball to `ondata/ckan-mcp-server`, workflow `release.yml`, ref `refs/tags/v0.4.115`, on a GitHub-hosted runner. `npm view @aborruso/ckan-mcp-server@0.4.115 dist.attestations` returns an SLSA v1 provenance; the same query on the hand-published 0.4.114 returns nothing.
|
|
30
|
+
|
|
31
|
+
One snag worth remembering: `mcp-publisher` failed with an expired JWT, and re-running `login github` then died with `incorrect_device_code` before the browser step. Neither error named the real cause — the local binary was **1.5.0 from 6 March**, and the device-auth flow changed by **1.8.0**. Updating the binary fixed it. When the registry token expires, check the publisher version too: both were installed the same day and go stale together.
|
|
32
|
+
|
|
9
33
|
|
|
10
34
|
|
|
11
35
|
### npm provenance: release workflow on tags
|
package/dist/index.js
CHANGED
|
@@ -2936,6 +2936,20 @@ Typical workflow: ckan_organization_search \u2192 ckan_organization_show (get de
|
|
|
2936
2936
|
|
|
2937
2937
|
// src/tools/datastore.ts
|
|
2938
2938
|
import { z as z4 } from "zod";
|
|
2939
|
+
var MAX_TABLE_COLUMNS = 8;
|
|
2940
|
+
var INTERNAL_FIELDS = ["_id", "_full_text"];
|
|
2941
|
+
var isInternalField = (id) => INTERNAL_FIELDS.includes(id);
|
|
2942
|
+
var MAX_LISTED_OMITTED_COLUMNS = 15;
|
|
2943
|
+
var sanitizeFieldName = (id) => id.replace(/[\r\n]+/g, " ").replace(/\|/g, "\\|");
|
|
2944
|
+
function formatOmittedColumnsNote(allFields, hint) {
|
|
2945
|
+
if (allFields.length <= MAX_TABLE_COLUMNS) return "";
|
|
2946
|
+
const omitted = allFields.slice(MAX_TABLE_COLUMNS);
|
|
2947
|
+
const listed = omitted.slice(0, MAX_LISTED_OMITTED_COLUMNS).map(sanitizeFieldName).join(", ");
|
|
2948
|
+
const rest = omitted.length > MAX_LISTED_OMITTED_COLUMNS ? ` and ${omitted.length - MAX_LISTED_OMITTED_COLUMNS} more` : "";
|
|
2949
|
+
return `
|
|
2950
|
+
> **Note**: the table above shows only the first ${MAX_TABLE_COLUMNS} of ${allFields.length} columns. Columns not shown: ${listed}${rest}. They do hold values \u2014 ${hint}
|
|
2951
|
+
`;
|
|
2952
|
+
}
|
|
2939
2953
|
function formatDatastoreSearchMarkdown(result, serverUrl, resourceId, offset, limit) {
|
|
2940
2954
|
let markdown = `# DataStore Query Results
|
|
2941
2955
|
|
|
@@ -2949,18 +2963,19 @@ function formatDatastoreSearchMarkdown(result, serverUrl, resourceId, offset, li
|
|
|
2949
2963
|
markdown += `**Returned**: ${result.records ? result.records.length : 0} records
|
|
2950
2964
|
|
|
2951
2965
|
`;
|
|
2952
|
-
|
|
2966
|
+
const declaredFields = (result.fields || []).filter((f) => f.id !== "_full_text");
|
|
2967
|
+
if (declaredFields.length > 0) {
|
|
2953
2968
|
markdown += `## Fields
|
|
2954
2969
|
|
|
2955
2970
|
`;
|
|
2956
|
-
markdown +=
|
|
2971
|
+
markdown += declaredFields.map((f) => `- **${f.id}** (${f.type})`).join("\n") + "\n\n";
|
|
2957
2972
|
}
|
|
2958
2973
|
if (result.records && result.records.length > 0) {
|
|
2959
2974
|
markdown += `## Records
|
|
2960
2975
|
|
|
2961
2976
|
`;
|
|
2962
|
-
const fields = result.fields ? result.fields.map((f) => f.id).filter((id) => id
|
|
2963
|
-
const displayFields = fields.slice(0,
|
|
2977
|
+
const fields = result.fields ? result.fields.map((f) => f.id).filter((id) => !isInternalField(id)) : [];
|
|
2978
|
+
const displayFields = fields.slice(0, MAX_TABLE_COLUMNS);
|
|
2964
2979
|
markdown += `| ${displayFields.join(" | ")} |
|
|
2965
2980
|
`;
|
|
2966
2981
|
markdown += `| ${displayFields.map(() => "---").join(" | ")} |
|
|
@@ -2975,6 +2990,7 @@ function formatDatastoreSearchMarkdown(result, serverUrl, resourceId, offset, li
|
|
|
2975
2990
|
markdown += `| ${values.join(" | ")} |
|
|
2976
2991
|
`;
|
|
2977
2992
|
}
|
|
2993
|
+
markdown += formatOmittedColumnsNote(fields, 'use the `fields` parameter to select them, or `response_format: "json"` to get every column.');
|
|
2978
2994
|
if (result.records.length > 50) {
|
|
2979
2995
|
markdown += `
|
|
2980
2996
|
... and ${result.records.length - 50} more records
|
|
@@ -2994,7 +3010,7 @@ function formatDatastoreSearchMarkdown(result, serverUrl, resourceId, offset, li
|
|
|
2994
3010
|
}
|
|
2995
3011
|
function formatDatastoreSqlMarkdown(result, serverUrl, sql) {
|
|
2996
3012
|
const records = result.records || [];
|
|
2997
|
-
const fieldIds = (result.fields?.map((field) => field.id) || Object.keys(records[0] || {})).filter((id) => id
|
|
3013
|
+
const fieldIds = (result.fields?.map((field) => field.id) || Object.keys(records[0] || {})).filter((id) => !isInternalField(id));
|
|
2998
3014
|
let markdown = `# DataStore SQL Results
|
|
2999
3015
|
|
|
3000
3016
|
`;
|
|
@@ -3005,17 +3021,18 @@ function formatDatastoreSqlMarkdown(result, serverUrl, sql) {
|
|
|
3005
3021
|
markdown += `**Returned**: ${records.length} records
|
|
3006
3022
|
|
|
3007
3023
|
`;
|
|
3008
|
-
|
|
3024
|
+
const declaredFields = (result.fields || []).filter((field) => field.id !== "_full_text");
|
|
3025
|
+
if (declaredFields.length > 0) {
|
|
3009
3026
|
markdown += `## Fields
|
|
3010
3027
|
|
|
3011
3028
|
`;
|
|
3012
|
-
markdown +=
|
|
3029
|
+
markdown += declaredFields.map((field) => `- **${field.id}** (${field.type})`).join("\n") + "\n\n";
|
|
3013
3030
|
}
|
|
3014
3031
|
if (records.length > 0 && fieldIds.length > 0) {
|
|
3015
3032
|
markdown += `## Records
|
|
3016
3033
|
|
|
3017
3034
|
`;
|
|
3018
|
-
const displayFields = fieldIds.slice(0,
|
|
3035
|
+
const displayFields = fieldIds.slice(0, MAX_TABLE_COLUMNS);
|
|
3019
3036
|
markdown += `| ${displayFields.join(" | ")} |
|
|
3020
3037
|
`;
|
|
3021
3038
|
markdown += `| ${displayFields.map(() => "---").join(" | ")} |
|
|
@@ -3030,6 +3047,7 @@ function formatDatastoreSqlMarkdown(result, serverUrl, sql) {
|
|
|
3030
3047
|
markdown += `| ${values.join(" | ")} |
|
|
3031
3048
|
`;
|
|
3032
3049
|
}
|
|
3050
|
+
markdown += formatOmittedColumnsNote(fieldIds, 'name them explicitly in the SELECT clause, or use `response_format: "json"` to get every column.');
|
|
3033
3051
|
if (records.length > 50) {
|
|
3034
3052
|
markdown += `
|
|
3035
3053
|
... and ${records.length - 50} more records
|
|
@@ -3043,11 +3061,10 @@ function formatDatastoreSqlMarkdown(result, serverUrl, sql) {
|
|
|
3043
3061
|
return markdown;
|
|
3044
3062
|
}
|
|
3045
3063
|
function compactDatastoreResult(result) {
|
|
3046
|
-
const fields = (result.fields || []).filter((f) => f.id
|
|
3047
|
-
const records = (result.records || []).map(
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
});
|
|
3064
|
+
const fields = (result.fields || []).filter((f) => !isInternalField(f.id));
|
|
3065
|
+
const records = (result.records || []).map(
|
|
3066
|
+
(record) => Object.fromEntries(Object.entries(record).filter(([key]) => !isInternalField(key)))
|
|
3067
|
+
);
|
|
3051
3068
|
return {
|
|
3052
3069
|
resource_id: result.resource_id || null,
|
|
3053
3070
|
fields,
|