@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.
- package/LOG.md +32 -0
- package/dist/index.js +251 -214
- package/dist/worker.js +123 -103
- 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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
2376
|
-
|
|
2377
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
3172
|
-
const
|
|
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.
|
|
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
|
});
|