@aborruso/ckan-mcp-server 0.4.50 → 0.4.52

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.
Files changed (4) hide show
  1. package/LOG.md +32 -0
  2. package/dist/index.js +251 -214
  3. package/dist/worker.js +123 -103
  4. package/package.json +1 -1
package/LOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # LOG
2
2
 
3
+ ## 2026-02-28 (v0.4.52)
4
+
5
+ - fix: HTTP transport — `/.well-known/oauth-authorization-server` now returns JSON 404 instead of HTML; fixes Claude Code HTTP transport connection failure
6
+ - fix: `ckan_datastore_search` — `limit` min changed 1→0; allows column discovery without fetching data
7
+ - docs: `ckan_datastore_search` description updated — fields always returned, `limit=0` pattern documented
8
+
9
+ ## 2026-02-27 (v0.4.51)
10
+
11
+ - refactor: domain types for all tool files — `CkanTag`, `CkanResource`, `CkanPackage`, `CkanOrganization`, `CkanField`, `CkanDatastoreResult` in `src/types.ts`; `any` reduced 32 → 1
12
+ - refactor: extract rendering functions from handler closures → named exports in `datastore.ts`, `organization.ts`, `group.ts`, `status.ts`; +26 unit tests
13
+ - fix: datastore table — skip `_id` column, increase cell truncation 50 → 80 chars
14
+ - fix: org/group show — dataset heading now shows `showing M of N returned — T total`
15
+ - tests: 191 → 272; all passing
16
+
17
+ ## 2026-02-27 (continued 2)
18
+
19
+ - fix: datastore table — skip `_id` column, increase cell truncation 50→80 chars
20
+ - fix: org/group show — `## Datasets (N)` → `## Datasets (showing M of N returned — T total)`
21
+ - tests: 270 → 272; all passing
22
+
23
+ ## 2026-02-27 (continued)
24
+
25
+ - refactor: extract markdown rendering from handler closures into exported functions in `datastore.ts`, `organization.ts`, `group.ts`, `status.ts`
26
+ - add 26 unit tests across 4 new test files (`datastore-formatting`, `organization-formatting`, `group-formatting`, `status-formatting`)
27
+ - test count: 244 → 270; all passing
28
+
29
+ ## 2026-02-27
30
+
31
+ - refactor: add CKAN domain types (`CkanTag`, `CkanResource`, `CkanPackage`, `CkanOrganization`, `CkanField`, `CkanDatastoreResult`) to `src/types.ts`
32
+ - replace `any` in exported tool functions across `package.ts`, `datastore.ts`, `organization.ts`, `group.ts`, `quality.ts`, `tag.ts` — 32 → 1 remaining (internal handler variable)
33
+ - no behavioral change; 244 tests passing
34
+
3
35
  ## 2026-02-26 (v0.4.50)
4
36
 
5
37
  - `ckan_list_resources`: add `format_filter` param (case-insensitive, client-side) — e.g. 72 resources → 8 CSV; header shows "Total: 72 (showing 8 CSV)"
package/dist/index.js CHANGED
@@ -1505,6 +1505,65 @@ Typical workflow: ckan_package_search \u2192 ckan_list_resources (assess availab
1505
1505
 
1506
1506
  // src/tools/organization.ts
1507
1507
  import { z as z3 } from "zod";
1508
+ function formatOrganizationShowMarkdown(result, serverUrl) {
1509
+ let markdown = `# Organization: ${result.title || result.name}
1510
+
1511
+ `;
1512
+ markdown += `**Server**: ${serverUrl}
1513
+ `;
1514
+ markdown += `**Link**: ${getOrganizationViewUrl(serverUrl, result)}
1515
+
1516
+ `;
1517
+ markdown += `## Details
1518
+
1519
+ `;
1520
+ markdown += `- **ID**: \`${result.id}\`
1521
+ `;
1522
+ markdown += `- **Name**: \`${result.name}\`
1523
+ `;
1524
+ markdown += `- **Datasets**: ${result.package_count || 0}
1525
+ `;
1526
+ markdown += `- **Created**: ${formatDate(result.created)}
1527
+ `;
1528
+ markdown += `- **State**: ${result.state}
1529
+
1530
+ `;
1531
+ if (result.description) {
1532
+ markdown += `## Description
1533
+
1534
+ ${result.description}
1535
+
1536
+ `;
1537
+ }
1538
+ if (result.packages && result.packages.length > 0) {
1539
+ const displayed = Math.min(result.packages.length, 20);
1540
+ const totalHint = result.package_count && result.package_count !== result.packages.length ? ` \u2014 ${result.package_count} total` : "";
1541
+ markdown += `## Datasets (showing ${displayed} of ${result.packages.length} returned${totalHint})
1542
+
1543
+ `;
1544
+ for (const pkg of result.packages.slice(0, 20)) {
1545
+ markdown += `- **${pkg.title || pkg.name}** (\`${pkg.name}\`)
1546
+ `;
1547
+ }
1548
+ if (result.packages.length > 20) {
1549
+ markdown += `
1550
+ ... and ${result.packages.length - 20} more datasets
1551
+ `;
1552
+ }
1553
+ markdown += "\n";
1554
+ }
1555
+ if (result.users && result.users.length > 0) {
1556
+ markdown += `## Users (${result.users.length})
1557
+
1558
+ `;
1559
+ for (const user of result.users) {
1560
+ markdown += `- **${user.name}** (${user.capacity})
1561
+ `;
1562
+ }
1563
+ markdown += "\n";
1564
+ }
1565
+ return markdown;
1566
+ }
1508
1567
  function registerOrganizationTools(server2) {
1509
1568
  server2.registerTool(
1510
1569
  "ckan_organization_list",
@@ -1748,60 +1807,7 @@ Typical workflow: ckan_organization_show \u2192 ckan_package_show (inspect a dat
1748
1807
  structuredContent: result
1749
1808
  };
1750
1809
  }
1751
- let markdown = `# Organization: ${result.title || result.name}
1752
-
1753
- `;
1754
- markdown += `**Server**: ${params.server_url}
1755
- `;
1756
- markdown += `**Link**: ${getOrganizationViewUrl(params.server_url, result)}
1757
-
1758
- `;
1759
- markdown += `## Details
1760
-
1761
- `;
1762
- markdown += `- **ID**: \`${result.id}\`
1763
- `;
1764
- markdown += `- **Name**: \`${result.name}\`
1765
- `;
1766
- markdown += `- **Datasets**: ${result.package_count || 0}
1767
- `;
1768
- markdown += `- **Created**: ${formatDate(result.created)}
1769
- `;
1770
- markdown += `- **State**: ${result.state}
1771
-
1772
- `;
1773
- if (result.description) {
1774
- markdown += `## Description
1775
-
1776
- ${result.description}
1777
-
1778
- `;
1779
- }
1780
- if (result.packages && result.packages.length > 0) {
1781
- markdown += `## Datasets (${result.packages.length})
1782
-
1783
- `;
1784
- for (const pkg of result.packages.slice(0, 20)) {
1785
- markdown += `- **${pkg.title || pkg.name}** (\`${pkg.name}\`)
1786
- `;
1787
- }
1788
- if (result.packages.length > 20) {
1789
- markdown += `
1790
- ... and ${result.packages.length - 20} more datasets
1791
- `;
1792
- }
1793
- markdown += "\n";
1794
- }
1795
- if (result.users && result.users.length > 0) {
1796
- markdown += `## Users (${result.users.length})
1797
-
1798
- `;
1799
- for (const user of result.users) {
1800
- markdown += `- **${user.name}** (${user.capacity})
1801
- `;
1802
- }
1803
- markdown += "\n";
1804
- }
1810
+ const markdown = formatOrganizationShowMarkdown(result, params.server_url);
1805
1811
  return {
1806
1812
  content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
1807
1813
  };
@@ -1926,6 +1932,108 @@ Typical workflow: ckan_organization_search \u2192 ckan_organization_show (get de
1926
1932
 
1927
1933
  // src/tools/datastore.ts
1928
1934
  import { z as z4 } from "zod";
1935
+ function formatDatastoreSearchMarkdown(result, serverUrl, resourceId, offset, limit) {
1936
+ let markdown = `# DataStore Query Results
1937
+
1938
+ `;
1939
+ markdown += `**Server**: ${serverUrl}
1940
+ `;
1941
+ markdown += `**Resource ID**: \`${resourceId}\`
1942
+ `;
1943
+ markdown += `**Total Records**: ${result.total || 0}
1944
+ `;
1945
+ markdown += `**Returned**: ${result.records ? result.records.length : 0} records
1946
+
1947
+ `;
1948
+ if (result.fields && result.fields.length > 0) {
1949
+ markdown += `## Fields
1950
+
1951
+ `;
1952
+ markdown += result.fields.map((f) => `- **${f.id}** (${f.type})`).join("\n") + "\n\n";
1953
+ }
1954
+ if (result.records && result.records.length > 0) {
1955
+ markdown += `## Records
1956
+
1957
+ `;
1958
+ const fields = result.fields ? result.fields.map((f) => f.id).filter((id) => id !== "_id") : [];
1959
+ const displayFields = fields.slice(0, 8);
1960
+ markdown += `| ${displayFields.join(" | ")} |
1961
+ `;
1962
+ markdown += `| ${displayFields.map(() => "---").join(" | ")} |
1963
+ `;
1964
+ for (const record of result.records.slice(0, 50)) {
1965
+ const values = displayFields.map((field) => {
1966
+ const val = record[field];
1967
+ if (val === null || val === void 0) return "-";
1968
+ const str = String(val);
1969
+ return str.length > 80 ? str.substring(0, 77) + "..." : str;
1970
+ });
1971
+ markdown += `| ${values.join(" | ")} |
1972
+ `;
1973
+ }
1974
+ if (result.records.length > 50) {
1975
+ markdown += `
1976
+ ... and ${result.records.length - 50} more records
1977
+ `;
1978
+ }
1979
+ markdown += "\n";
1980
+ }
1981
+ if (result.total && result.total > offset + (result.records?.length || 0)) {
1982
+ const nextOffset = offset + limit;
1983
+ markdown += `**More results available**: Use \`offset: ${nextOffset}\` for next page.
1984
+ `;
1985
+ }
1986
+ return markdown;
1987
+ }
1988
+ function formatDatastoreSqlMarkdown(result, serverUrl, sql) {
1989
+ const records = result.records || [];
1990
+ const fieldIds = (result.fields?.map((field) => field.id) || Object.keys(records[0] || {})).filter((id) => id !== "_id");
1991
+ let markdown = `# DataStore SQL Results
1992
+
1993
+ `;
1994
+ markdown += `**Server**: ${serverUrl}
1995
+ `;
1996
+ markdown += `**SQL**: \`${sql}\`
1997
+ `;
1998
+ markdown += `**Returned**: ${records.length} records
1999
+
2000
+ `;
2001
+ if (result.fields && result.fields.length > 0) {
2002
+ markdown += `## Fields
2003
+
2004
+ `;
2005
+ markdown += result.fields.map((field) => `- **${field.id}** (${field.type})`).join("\n") + "\n\n";
2006
+ }
2007
+ if (records.length > 0 && fieldIds.length > 0) {
2008
+ markdown += `## Records
2009
+
2010
+ `;
2011
+ const displayFields = fieldIds.slice(0, 8);
2012
+ markdown += `| ${displayFields.join(" | ")} |
2013
+ `;
2014
+ markdown += `| ${displayFields.map(() => "---").join(" | ")} |
2015
+ `;
2016
+ for (const record of records.slice(0, 50)) {
2017
+ const values = displayFields.map((field) => {
2018
+ const value = record[field];
2019
+ if (value === null || value === void 0) return "-";
2020
+ const text = String(value);
2021
+ return text.length > 80 ? text.substring(0, 77) + "..." : text;
2022
+ });
2023
+ markdown += `| ${values.join(" | ")} |
2024
+ `;
2025
+ }
2026
+ if (records.length > 50) {
2027
+ markdown += `
2028
+ ... and ${records.length - 50} more records
2029
+ `;
2030
+ }
2031
+ markdown += "\n";
2032
+ } else {
2033
+ markdown += "No records returned by the SQL query.\n";
2034
+ }
2035
+ return markdown;
2036
+ }
1929
2037
  function registerDatastoreTools(server2) {
1930
2038
  server2.registerTool(
1931
2039
  "ckan_datastore_search",
@@ -1935,6 +2043,10 @@ function registerDatastoreTools(server2) {
1935
2043
 
1936
2044
  The DataStore allows SQL-like queries on tabular data. Not all resources have DataStore enabled.
1937
2045
 
2046
+ The response always includes a Fields section listing all available column names and types.
2047
+ Use limit=0 to discover column names without fetching data \u2014 do this before using filters
2048
+ to avoid guessing column names and getting HTTP 400 errors.
2049
+
1938
2050
  Args:
1939
2051
  - server_url (string): Base URL of CKAN server
1940
2052
  - resource_id (string): ID of the DataStore resource
@@ -1948,20 +2060,21 @@ Args:
1948
2060
  - response_format ('markdown' | 'json'): Output format
1949
2061
 
1950
2062
  Returns:
1951
- DataStore records matching query
2063
+ DataStore records matching query, always including available column names and types
1952
2064
 
1953
2065
  Examples:
2066
+ - { server_url: "...", resource_id: "abc-123", limit: 0 } \u2190 discover columns first
1954
2067
  - { server_url: "...", resource_id: "abc-123", limit: 50 }
1955
2068
  - { server_url: "...", resource_id: "...", filters: { "regione": "Sicilia" } }
1956
2069
  - { server_url: "...", resource_id: "...", sort: "anno desc", limit: 100 }
1957
2070
 
1958
- Typical workflow: ckan_package_search \u2192 ckan_package_show (find resource_id with datastore_active=true) \u2192 ckan_datastore_search`,
2071
+ Typical workflow: ckan_package_search \u2192 ckan_package_show (find resource_id with datastore_active=true) \u2192 ckan_datastore_search (limit=0 to get columns) \u2192 ckan_datastore_search (with filters)`,
1959
2072
  inputSchema: z4.object({
1960
2073
  server_url: z4.string().url().describe("Base URL of the CKAN server (e.g., https://dati.gov.it/opendata)"),
1961
2074
  resource_id: z4.string().min(1).describe("UUID of the DataStore resource (from ckan_package_show resource.id where datastore_active is true)"),
1962
2075
  q: z4.string().optional().describe("Full-text search across all fields"),
1963
2076
  filters: z4.record(z4.any()).optional().describe('Key-value filters for exact matches (e.g., { "regione": "Sicilia", "anno": 2023 })'),
1964
- limit: z4.number().int().min(1).max(32e3).optional().default(100).describe("Max rows to return (default 100, max 32000)"),
2077
+ limit: z4.number().int().min(0).max(32e3).optional().default(100).describe("Max rows to return (default 100, max 32000); use 0 to get only column names without data"),
1965
2078
  offset: z4.number().int().min(0).optional().default(0).describe("Pagination offset"),
1966
2079
  fields: z4.array(z4.string()).optional().describe("Specific field names to return; omit to return all fields"),
1967
2080
  sort: z4.string().optional().describe("Sort expression (e.g., 'anno desc', 'nome asc')"),
@@ -1997,56 +2110,7 @@ Typical workflow: ckan_package_search \u2192 ckan_package_show (find resource_id
1997
2110
  content: [{ type: "text", text: truncateText(JSON.stringify(result, null, 2)) }]
1998
2111
  };
1999
2112
  }
2000
- let markdown = `# DataStore Query Results
2001
-
2002
- `;
2003
- markdown += `**Server**: ${params.server_url}
2004
- `;
2005
- markdown += `**Resource ID**: \`${params.resource_id}\`
2006
- `;
2007
- markdown += `**Total Records**: ${result.total || 0}
2008
- `;
2009
- markdown += `**Returned**: ${result.records ? result.records.length : 0} records
2010
-
2011
- `;
2012
- if (result.fields && result.fields.length > 0) {
2013
- markdown += `## Fields
2014
-
2015
- `;
2016
- markdown += result.fields.map((f) => `- **${f.id}** (${f.type})`).join("\n") + "\n\n";
2017
- }
2018
- if (result.records && result.records.length > 0) {
2019
- markdown += `## Records
2020
-
2021
- `;
2022
- const fields = result.fields.map((f) => f.id);
2023
- const displayFields = fields.slice(0, 8);
2024
- markdown += `| ${displayFields.join(" | ")} |
2025
- `;
2026
- markdown += `| ${displayFields.map(() => "---").join(" | ")} |
2027
- `;
2028
- for (const record of result.records.slice(0, 50)) {
2029
- const values = displayFields.map((field) => {
2030
- const val = record[field];
2031
- if (val === null || val === void 0) return "-";
2032
- const str = String(val);
2033
- return str.length > 50 ? str.substring(0, 47) + "..." : str;
2034
- });
2035
- markdown += `| ${values.join(" | ")} |
2036
- `;
2037
- }
2038
- if (result.records.length > 50) {
2039
- markdown += `
2040
- ... and ${result.records.length - 50} more records
2041
- `;
2042
- }
2043
- markdown += "\n";
2044
- }
2045
- if (result.total && result.total > params.offset + (result.records?.length || 0)) {
2046
- const nextOffset = params.offset + params.limit;
2047
- markdown += `**More results available**: Use \`offset: ${nextOffset}\` for next page.
2048
- `;
2049
- }
2113
+ const markdown = formatDatastoreSearchMarkdown(result, params.server_url, params.resource_id, params.offset, params.limit);
2050
2114
  return {
2051
2115
  content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
2052
2116
  };
@@ -2107,52 +2171,7 @@ Typical workflow: ckan_package_show (get resource_id) \u2192 ckan_datastore_sear
2107
2171
  structuredContent: result
2108
2172
  };
2109
2173
  }
2110
- const records = result.records || [];
2111
- const fieldIds = result.fields?.map((field) => field.id) || Object.keys(records[0] || {});
2112
- let markdown = `# DataStore SQL Results
2113
-
2114
- `;
2115
- markdown += `**Server**: ${params.server_url}
2116
- `;
2117
- markdown += `**SQL**: \`${params.sql}\`
2118
- `;
2119
- markdown += `**Returned**: ${records.length} records
2120
-
2121
- `;
2122
- if (result.fields && result.fields.length > 0) {
2123
- markdown += `## Fields
2124
-
2125
- `;
2126
- markdown += result.fields.map((field) => `- **${field.id}** (${field.type})`).join("\n") + "\n\n";
2127
- }
2128
- if (records.length > 0 && fieldIds.length > 0) {
2129
- markdown += `## Records
2130
-
2131
- `;
2132
- const displayFields = fieldIds.slice(0, 8);
2133
- markdown += `| ${displayFields.join(" | ")} |
2134
- `;
2135
- markdown += `| ${displayFields.map(() => "---").join(" | ")} |
2136
- `;
2137
- for (const record of records.slice(0, 50)) {
2138
- const values = displayFields.map((field) => {
2139
- const value = record[field];
2140
- if (value === null || value === void 0) return "-";
2141
- const text = String(value);
2142
- return text.length > 50 ? text.substring(0, 47) + "..." : text;
2143
- });
2144
- markdown += `| ${values.join(" | ")} |
2145
- `;
2146
- }
2147
- if (records.length > 50) {
2148
- markdown += `
2149
- ... and ${records.length - 50} more records
2150
- `;
2151
- }
2152
- markdown += "\n";
2153
- } else {
2154
- markdown += "No records returned by the SQL query.\n";
2155
- }
2174
+ const markdown = formatDatastoreSqlMarkdown(result, params.server_url, params.sql);
2156
2175
  return {
2157
2176
  content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
2158
2177
  };
@@ -2171,6 +2190,16 @@ Typical workflow: ckan_package_show (get resource_id) \u2192 ckan_datastore_sear
2171
2190
 
2172
2191
  // src/tools/status.ts
2173
2192
  import { z as z5 } from "zod";
2193
+ function formatStatusMarkdown(result, serverUrl) {
2194
+ return `# CKAN Server Status
2195
+
2196
+ **Server**: ${serverUrl}
2197
+ **Status**: \u2705 Online
2198
+ **CKAN Version**: ${result.ckan_version || "Unknown"}
2199
+ **Site Title**: ${result.site_title || "N/A"}
2200
+ **Site URL**: ${result.site_url || "N/A"}
2201
+ `;
2202
+ }
2174
2203
  function registerStatusTools(server2) {
2175
2204
  server2.registerTool(
2176
2205
  "ckan_status_show",
@@ -2204,14 +2233,7 @@ Typical workflow: ckan_status_show (verify server is up) \u2192 ckan_package_sea
2204
2233
  "status_show",
2205
2234
  {}
2206
2235
  );
2207
- const markdown = `# CKAN Server Status
2208
-
2209
- **Server**: ${params.server_url}
2210
- **Status**: \u2705 Online
2211
- **CKAN Version**: ${result.ckan_version || "Unknown"}
2212
- **Site Title**: ${result.site_title || "N/A"}
2213
- **Site URL**: ${result.site_url || "N/A"}
2214
- `;
2236
+ const markdown = formatStatusMarkdown(result, params.server_url);
2215
2237
  return {
2216
2238
  content: [{ type: "text", text: addDemoFooter(markdown) }],
2217
2239
  structuredContent: result
@@ -2233,7 +2255,10 @@ ${error instanceof Error ? error.message : String(error)}`
2233
2255
  // src/tools/tag.ts
2234
2256
  import { z as z6 } from "zod";
2235
2257
  function normalizeTagFacets(result) {
2236
- const searchItems = result?.search_facets?.tags?.items;
2258
+ const r = result;
2259
+ const searchFacets = r?.search_facets;
2260
+ const tagsGroup = searchFacets?.tags;
2261
+ const searchItems = tagsGroup?.items;
2237
2262
  if (Array.isArray(searchItems)) {
2238
2263
  return searchItems.map((item) => ({
2239
2264
  name: item?.name || item?.display_name || String(item),
@@ -2241,7 +2266,7 @@ function normalizeTagFacets(result) {
2241
2266
  display_name: item?.display_name
2242
2267
  }));
2243
2268
  }
2244
- const facets = result?.facets?.tags;
2269
+ const facets = r?.facets?.tags;
2245
2270
  if (Array.isArray(facets)) {
2246
2271
  if (facets.length > 0 && typeof facets[0] === "object") {
2247
2272
  return facets.map((item) => ({
@@ -2371,16 +2396,67 @@ function getGroupViewUrl(serverUrl, group) {
2371
2396
  const cleanServerUrl = serverUrl.replace(/\/$/, "");
2372
2397
  return `${cleanServerUrl}/group/${group.name}`;
2373
2398
  }
2399
+ function formatGroupShowMarkdown(result, serverUrl) {
2400
+ let markdown = `# Group: ${result.title || result.name}
2401
+
2402
+ `;
2403
+ markdown += `**Server**: ${serverUrl}
2404
+ `;
2405
+ markdown += `**Link**: ${getGroupViewUrl(serverUrl, result)}
2406
+
2407
+ `;
2408
+ markdown += `## Details
2409
+
2410
+ `;
2411
+ markdown += `- **ID**: \`${result.id}\`
2412
+ `;
2413
+ markdown += `- **Name**: \`${result.name}\`
2414
+ `;
2415
+ markdown += `- **Datasets**: ${result.package_count || 0}
2416
+ `;
2417
+ markdown += `- **Created**: ${formatDate(result.created)}
2418
+ `;
2419
+ markdown += `- **State**: ${result.state}
2420
+
2421
+ `;
2422
+ if (result.description) {
2423
+ markdown += `## Description
2424
+
2425
+ ${result.description}
2426
+
2427
+ `;
2428
+ }
2429
+ if (result.packages && result.packages.length > 0) {
2430
+ const displayed = Math.min(result.packages.length, 20);
2431
+ const totalHint = result.package_count && result.package_count !== result.packages.length ? ` \u2014 ${result.package_count} total` : "";
2432
+ markdown += `## Datasets (showing ${displayed} of ${result.packages.length} returned${totalHint})
2433
+
2434
+ `;
2435
+ for (const pkg of result.packages.slice(0, 20)) {
2436
+ markdown += `- **${pkg.title || pkg.name}** (\`${pkg.name}\`)
2437
+ `;
2438
+ }
2439
+ if (result.packages.length > 20) {
2440
+ markdown += `
2441
+ ... and ${result.packages.length - 20} more datasets
2442
+ `;
2443
+ }
2444
+ markdown += "\n";
2445
+ }
2446
+ return markdown;
2447
+ }
2374
2448
  function normalizeGroupFacets(result) {
2375
- const items = result?.search_facets?.groups?.items;
2376
- if (Array.isArray(items)) {
2377
- return items.map((item) => ({
2449
+ const r = result;
2450
+ const items = r?.search_facets?.groups;
2451
+ const itemsArr = items?.items;
2452
+ if (Array.isArray(itemsArr)) {
2453
+ return itemsArr.map((item) => ({
2378
2454
  name: item?.name || item?.display_name || String(item),
2379
2455
  display_name: item?.display_name,
2380
2456
  count: typeof item?.count === "number" ? item.count : 0
2381
2457
  }));
2382
2458
  }
2383
- const facets = result?.facets?.groups;
2459
+ const facets = r?.facets?.groups;
2384
2460
  if (Array.isArray(facets)) {
2385
2461
  if (facets.length > 0 && typeof facets[0] === "object") {
2386
2462
  return facets.map((item) => ({
@@ -2571,50 +2647,7 @@ Typical workflow: ckan_group_show \u2192 ckan_package_show (inspect a dataset) \
2571
2647
  structuredContent: result
2572
2648
  };
2573
2649
  }
2574
- let markdown = `# Group: ${result.title || result.name}
2575
-
2576
- `;
2577
- markdown += `**Server**: ${params.server_url}
2578
- `;
2579
- markdown += `**Link**: ${getGroupViewUrl(params.server_url, result)}
2580
-
2581
- `;
2582
- markdown += `## Details
2583
-
2584
- `;
2585
- markdown += `- **ID**: \`${result.id}\`
2586
- `;
2587
- markdown += `- **Name**: \`${result.name}\`
2588
- `;
2589
- markdown += `- **Datasets**: ${result.package_count || 0}
2590
- `;
2591
- markdown += `- **Created**: ${formatDate(result.created)}
2592
- `;
2593
- markdown += `- **State**: ${result.state}
2594
-
2595
- `;
2596
- if (result.description) {
2597
- markdown += `## Description
2598
-
2599
- ${result.description}
2600
-
2601
- `;
2602
- }
2603
- if (result.packages && result.packages.length > 0) {
2604
- markdown += `## Datasets (${result.packages.length})
2605
-
2606
- `;
2607
- for (const pkg of result.packages.slice(0, 20)) {
2608
- markdown += `- **${pkg.title || pkg.name}** (\`${pkg.name}\`)
2609
- `;
2610
- }
2611
- if (result.packages.length > 20) {
2612
- markdown += `
2613
- ... and ${result.packages.length - 20} more datasets
2614
- `;
2615
- }
2616
- markdown += "\n";
2617
- }
2650
+ const markdown = formatGroupShowMarkdown(result, params.server_url);
2618
2651
  return {
2619
2652
  content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
2620
2653
  };
@@ -3168,8 +3201,9 @@ function metricAvailability(section, availabilityKey, statusKey) {
3168
3201
  return void 0;
3169
3202
  }
3170
3203
  function normalizeQualityData(data) {
3171
- const mqaData = data?.mqa ?? data;
3172
- const breakdown = data?.breakdown;
3204
+ const d = data;
3205
+ const mqaData = d?.mqa ?? data;
3206
+ const breakdown = d?.breakdown;
3173
3207
  const resultEntry = mqaData?.result?.results?.[0];
3174
3208
  if (!resultEntry || typeof resultEntry !== "object") {
3175
3209
  return { ...data, breakdown };
@@ -3975,7 +4009,7 @@ var registerAllPrompts = (server2) => {
3975
4009
  function createServer() {
3976
4010
  return new McpServer({
3977
4011
  name: "ckan-mcp-server",
3978
- version: "0.4.50"
4012
+ version: "0.4.52"
3979
4013
  });
3980
4014
  }
3981
4015
  function registerAll(server2) {
@@ -4009,6 +4043,9 @@ async function runHTTP(server2) {
4009
4043
  enableJsonResponse: true
4010
4044
  });
4011
4045
  await server2.connect(transport2);
4046
+ app.get("/.well-known/oauth-authorization-server", (_req, res) => {
4047
+ res.status(404).json({ error: "authorization_not_supported", error_description: "This server does not require authentication" });
4048
+ });
4012
4049
  app.post("/mcp", async (req, res) => {
4013
4050
  await transport2.handleRequest(req, res, req.body);
4014
4051
  });