@aborruso/ckan-mcp-server 0.4.113 → 0.4.114
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 +11 -0
- package/dist/index.js +26 -55
- package/dist/worker.js +112 -112
- package/package.json +1 -1
package/LOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## 2026-07-31
|
|
4
4
|
|
|
5
|
+
### v0.4.114
|
|
6
|
+
|
|
7
|
+
structuredContent capped like the text (closes #39).
|
|
8
|
+
|
|
9
|
+
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.
|
|
10
|
+
|
|
11
|
+
- 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.
|
|
12
|
+
- `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.
|
|
13
|
+
- **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.
|
|
14
|
+
- 4 new tests (458 total). Docs realigned: `README.md`, `docs/JSON-OUTPUT.md`, `docs/DECISIONS.md`, `CLAUDE.md` all asserted the uncapped behaviour.
|
|
15
|
+
|
|
5
16
|
### v0.4.113
|
|
6
17
|
|
|
7
18
|
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 {
|