@aborruso/ckan-mcp-server 0.4.49 → 0.4.51
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/AGENTS.md +37 -0
- package/LOG.md +35 -0
- package/README.md +20 -1
- package/dist/index.js +331 -223
- package/dist/worker.js +123 -103
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -489,6 +489,24 @@ function convertDateMathForUnsupportedFields(query) {
|
|
|
489
489
|
return `${field}:[${startIso} TO ${nowIso}]`;
|
|
490
490
|
});
|
|
491
491
|
}
|
|
492
|
+
var EXPLICIT_BOOL_PATTERN = /\b(AND|OR|NOT)\b|[+\-!]/;
|
|
493
|
+
function isPlainMultiTermQuery(query) {
|
|
494
|
+
const trimmed = query.trim();
|
|
495
|
+
if (trimmed === "*:*" || trimmed === "") return false;
|
|
496
|
+
if (FIELD_QUERY_PATTERN.test(trimmed)) return false;
|
|
497
|
+
if (EXPLICIT_BOOL_PATTERN.test(trimmed)) return false;
|
|
498
|
+
const words = trimmed.split(/\s+/).filter(Boolean);
|
|
499
|
+
return words.length > 1;
|
|
500
|
+
}
|
|
501
|
+
function buildOrQuery(query) {
|
|
502
|
+
return query.trim().split(/\s+/).filter(Boolean).join(" OR ");
|
|
503
|
+
}
|
|
504
|
+
function stripAccents(text) {
|
|
505
|
+
return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
|
506
|
+
}
|
|
507
|
+
function hasAccents(text) {
|
|
508
|
+
return text !== stripAccents(text);
|
|
509
|
+
}
|
|
492
510
|
function resolveSearchQuery(serverUrl, query, parserOverride) {
|
|
493
511
|
const portalSearchConfig = getPortalSearchConfig(serverUrl);
|
|
494
512
|
const portalForce = portalSearchConfig.force_text_field ?? false;
|
|
@@ -761,8 +779,14 @@ ${result.notes}
|
|
|
761
779
|
`;
|
|
762
780
|
if (resource.last_modified) markdown += `- **Modified**: ${formatDate(resource.last_modified)}
|
|
763
781
|
`;
|
|
764
|
-
if (resource.datastore_active
|
|
765
|
-
markdown += `- **DataStore**:
|
|
782
|
+
if (resource.datastore_active === true) {
|
|
783
|
+
markdown += `- **DataStore**: \u2705 Available
|
|
784
|
+
`;
|
|
785
|
+
} else if (resource.datastore_active === false) {
|
|
786
|
+
markdown += `- **DataStore**: \u274C Not available
|
|
787
|
+
`;
|
|
788
|
+
} else {
|
|
789
|
+
markdown += `- **DataStore**: \u2753 Not reported by portal
|
|
766
790
|
`;
|
|
767
791
|
}
|
|
768
792
|
markdown += "\n";
|
|
@@ -780,6 +804,12 @@ ${result.notes}
|
|
|
780
804
|
}
|
|
781
805
|
return markdown;
|
|
782
806
|
};
|
|
807
|
+
function resolvePageParams(page, pageSize, start, rows) {
|
|
808
|
+
if (page !== void 0) {
|
|
809
|
+
return { effectiveStart: (page - 1) * pageSize, effectiveRows: pageSize };
|
|
810
|
+
}
|
|
811
|
+
return { effectiveStart: start, effectiveRows: rows };
|
|
812
|
+
}
|
|
783
813
|
function registerPackageTools(server2) {
|
|
784
814
|
server2.registerTool(
|
|
785
815
|
"ckan_package_search",
|
|
@@ -819,6 +849,8 @@ Args:
|
|
|
819
849
|
- fq (string): Filter query (e.g., "organization:comune-palermo")
|
|
820
850
|
- rows (number): Number of results to return (default: 10, max: 1000)
|
|
821
851
|
- start (number): Offset for pagination (default: 0)
|
|
852
|
+
- page (number): Page number (1-based); alias for start. Overrides start if provided.
|
|
853
|
+
- page_size (number): Results per page when using page (default: 10, max: 1000)
|
|
822
854
|
- sort (string): Sort field and direction (e.g., "metadata_modified desc")
|
|
823
855
|
- facet_field (array): Fields to facet on (e.g., ["organization", "tags"])
|
|
824
856
|
- facet_limit (number): Max facet values per field (default: 50)
|
|
@@ -899,6 +931,8 @@ Typical workflow: ckan_package_search \u2192 ckan_package_show (get full metadat
|
|
|
899
931
|
sort: z2.string().optional().describe("Sort field and direction (e.g., 'metadata_modified desc')"),
|
|
900
932
|
facet_field: z2.array(z2.string()).optional().describe("Fields to facet on"),
|
|
901
933
|
facet_limit: z2.number().int().min(1).optional().default(50).describe("Maximum facet values per field"),
|
|
934
|
+
page: z2.number().int().min(1).optional().describe("Page number (1-based); alias for start. Overrides start if provided."),
|
|
935
|
+
page_size: z2.number().int().min(1).max(1e3).optional().default(10).describe("Results per page when using page (default: 10)"),
|
|
902
936
|
include_drafts: z2.boolean().optional().default(false).describe("Include draft datasets"),
|
|
903
937
|
content_recent: z2.boolean().optional().default(false).describe("Use issued date with fallback to metadata_created for recent content"),
|
|
904
938
|
content_recent_days: z2.number().int().min(1).optional().default(30).describe("Day window for content_recent (default 30)"),
|
|
@@ -928,10 +962,11 @@ Typical workflow: ckan_package_search \u2192 ckan_package_show (get full metadat
|
|
|
928
962
|
query,
|
|
929
963
|
params.query_parser
|
|
930
964
|
);
|
|
965
|
+
const { effectiveRows, effectiveStart } = resolvePageParams(params.page, params.page_size, params.start, params.rows);
|
|
931
966
|
const apiParams = {
|
|
932
967
|
q: effectiveQuery,
|
|
933
|
-
rows:
|
|
934
|
-
start:
|
|
968
|
+
rows: effectiveRows,
|
|
969
|
+
start: effectiveStart,
|
|
935
970
|
include_private: params.include_drafts
|
|
936
971
|
};
|
|
937
972
|
if (params.fq) apiParams.fq = params.fq;
|
|
@@ -940,11 +975,29 @@ Typical workflow: ckan_package_search \u2192 ckan_package_show (get full metadat
|
|
|
940
975
|
apiParams["facet.field"] = JSON.stringify(params.facet_field);
|
|
941
976
|
apiParams["facet.limit"] = params.facet_limit;
|
|
942
977
|
}
|
|
943
|
-
|
|
978
|
+
let result = await makeCkanRequest(
|
|
944
979
|
params.server_url,
|
|
945
980
|
"package_search",
|
|
946
981
|
apiParams
|
|
947
982
|
);
|
|
983
|
+
let accentFallbackUsed = false;
|
|
984
|
+
if (result.count === 0 && hasAccents(params.q)) {
|
|
985
|
+
const strippedQuery = stripAccents(params.q);
|
|
986
|
+
const { effectiveQuery: strippedEffective } = resolveSearchQuery(
|
|
987
|
+
params.server_url,
|
|
988
|
+
strippedQuery,
|
|
989
|
+
params.query_parser
|
|
990
|
+
);
|
|
991
|
+
const fallbackResult = await makeCkanRequest(
|
|
992
|
+
params.server_url,
|
|
993
|
+
"package_search",
|
|
994
|
+
{ ...apiParams, q: strippedEffective }
|
|
995
|
+
);
|
|
996
|
+
if (fallbackResult.count > 0) {
|
|
997
|
+
result = fallbackResult;
|
|
998
|
+
accentFallbackUsed = true;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
948
1001
|
if (params.response_format === "json" /* JSON */) {
|
|
949
1002
|
return {
|
|
950
1003
|
content: [{ type: "text", text: truncateText(JSON.stringify(result, null, 2)) }]
|
|
@@ -958,10 +1011,12 @@ ${params.content_recent ? `**Content Recent**: last ${params.content_recent_days
|
|
|
958
1011
|
` : ""}
|
|
959
1012
|
${effectiveQuery !== userQuery ? `**Effective Query**: ${effectiveQuery}
|
|
960
1013
|
` : ""}
|
|
1014
|
+
${accentFallbackUsed ? `**Note**: Original query returned 0 results; retried with accent-stripped query "${stripAccents(params.q)}".
|
|
1015
|
+
` : ""}
|
|
961
1016
|
${params.fq ? `**Filter**: ${params.fq}
|
|
962
1017
|
` : ""}
|
|
963
1018
|
**Total Results**: ${result.count}
|
|
964
|
-
**Showing**: ${result.results.length} results (from ${
|
|
1019
|
+
**Showing**: ${result.results.length} results (from ${effectiveStart})
|
|
965
1020
|
|
|
966
1021
|
`;
|
|
967
1022
|
if (result.facets && Object.keys(result.facets).length > 0) {
|
|
@@ -1027,13 +1082,27 @@ Note: showing top ${sorted.length} only. Use \`response_format: json\` for full
|
|
|
1027
1082
|
} else {
|
|
1028
1083
|
markdown += `No datasets found matching your query.
|
|
1029
1084
|
`;
|
|
1085
|
+
if (isPlainMultiTermQuery(params.q)) {
|
|
1086
|
+
markdown += `
|
|
1087
|
+
> **Tip**: Multi-term queries use AND by default (all terms must match). Try OR to broaden the search:
|
|
1088
|
+
`;
|
|
1089
|
+
markdown += `> \`q: "${buildOrQuery(params.q)}"\`
|
|
1090
|
+
`;
|
|
1091
|
+
}
|
|
1030
1092
|
}
|
|
1031
|
-
if (result.count >
|
|
1032
|
-
|
|
1033
|
-
|
|
1093
|
+
if (result.count > effectiveStart + effectiveRows) {
|
|
1094
|
+
if (params.page !== void 0) {
|
|
1095
|
+
markdown += `
|
|
1096
|
+
---
|
|
1097
|
+
**More results available**: Use \`page: ${params.page + 1}\` to see next page.
|
|
1098
|
+
`;
|
|
1099
|
+
} else {
|
|
1100
|
+
const nextStart = effectiveStart + effectiveRows;
|
|
1101
|
+
markdown += `
|
|
1034
1102
|
---
|
|
1035
1103
|
**More results available**: Use \`start: ${nextStart}\` to see next page.
|
|
1036
1104
|
`;
|
|
1105
|
+
}
|
|
1037
1106
|
}
|
|
1038
1107
|
return {
|
|
1039
1108
|
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
@@ -1319,6 +1388,7 @@ Use this to quickly assess what files a dataset contains before deciding how to
|
|
|
1319
1388
|
Args:
|
|
1320
1389
|
- server_url (string): Base URL of CKAN server
|
|
1321
1390
|
- id (string): Dataset ID or name
|
|
1391
|
+
- format_filter (string): Filter resources by format, case-insensitive (e.g., "CSV", "json", "XLSX")
|
|
1322
1392
|
- response_format ('markdown' | 'json'): Output format
|
|
1323
1393
|
|
|
1324
1394
|
Returns:
|
|
@@ -1326,11 +1396,13 @@ Returns:
|
|
|
1326
1396
|
|
|
1327
1397
|
Examples:
|
|
1328
1398
|
- { server_url: "https://dati.gov.it/opendata", id: "dataset-name" }
|
|
1399
|
+
- { server_url: "...", id: "dataset-name", format_filter: "CSV" }
|
|
1329
1400
|
|
|
1330
1401
|
Typical workflow: ckan_package_search \u2192 ckan_list_resources (assess available files) \u2192 ckan_datastore_search (for resources with DataStore=true)`,
|
|
1331
1402
|
inputSchema: z2.object({
|
|
1332
1403
|
server_url: z2.string().url().describe("Base URL of the CKAN server"),
|
|
1333
1404
|
id: z2.string().min(1).describe("Dataset ID or name"),
|
|
1405
|
+
format_filter: z2.string().optional().describe("Filter resources by format, case-insensitive (e.g., 'CSV', 'json', 'XLSX')"),
|
|
1334
1406
|
response_format: ResponseFormatSchema
|
|
1335
1407
|
}).strict(),
|
|
1336
1408
|
annotations: {
|
|
@@ -1348,7 +1420,8 @@ Typical workflow: ckan_package_search \u2192 ckan_list_resources (assess availab
|
|
|
1348
1420
|
{ id: params.id }
|
|
1349
1421
|
);
|
|
1350
1422
|
const resources = Array.isArray(result.resources) ? result.resources : [];
|
|
1351
|
-
const
|
|
1423
|
+
const formatFilter = params.format_filter?.toUpperCase();
|
|
1424
|
+
const summary = resources.filter((r) => !formatFilter || (r.format || "").toUpperCase() === formatFilter).map((r) => {
|
|
1352
1425
|
const effectiveUrl = resolveDownloadUrl(r);
|
|
1353
1426
|
return {
|
|
1354
1427
|
name: r.name || "Unnamed Resource",
|
|
@@ -1364,7 +1437,9 @@ Typical workflow: ckan_package_search \u2192 ckan_list_resources (assess availab
|
|
|
1364
1437
|
dataset_id: result.id,
|
|
1365
1438
|
dataset_name: result.name,
|
|
1366
1439
|
dataset_title: result.title || result.name,
|
|
1367
|
-
total_resources:
|
|
1440
|
+
total_resources: resources.length,
|
|
1441
|
+
filtered_resources: summary.length,
|
|
1442
|
+
format_filter: formatFilter ?? null,
|
|
1368
1443
|
resources: summary
|
|
1369
1444
|
};
|
|
1370
1445
|
return {
|
|
@@ -1379,7 +1454,11 @@ Typical workflow: ckan_package_search \u2192 ckan_list_resources (assess availab
|
|
|
1379
1454
|
`;
|
|
1380
1455
|
markdown += `**Dataset**: \`${result.name}\` (\`${result.id}\`)
|
|
1381
1456
|
`;
|
|
1382
|
-
markdown += `**Total Resources**: ${
|
|
1457
|
+
markdown += `**Total Resources**: ${resources.length}`;
|
|
1458
|
+
if (formatFilter) {
|
|
1459
|
+
markdown += ` (showing ${summary.length} ${formatFilter})`;
|
|
1460
|
+
}
|
|
1461
|
+
markdown += `
|
|
1383
1462
|
|
|
1384
1463
|
`;
|
|
1385
1464
|
if (summary.length === 0) {
|
|
@@ -1426,6 +1505,65 @@ Typical workflow: ckan_package_search \u2192 ckan_list_resources (assess availab
|
|
|
1426
1505
|
|
|
1427
1506
|
// src/tools/organization.ts
|
|
1428
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
|
+
}
|
|
1429
1567
|
function registerOrganizationTools(server2) {
|
|
1430
1568
|
server2.registerTool(
|
|
1431
1569
|
"ckan_organization_list",
|
|
@@ -1669,60 +1807,7 @@ Typical workflow: ckan_organization_show \u2192 ckan_package_show (inspect a dat
|
|
|
1669
1807
|
structuredContent: result
|
|
1670
1808
|
};
|
|
1671
1809
|
}
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
`;
|
|
1675
|
-
markdown += `**Server**: ${params.server_url}
|
|
1676
|
-
`;
|
|
1677
|
-
markdown += `**Link**: ${getOrganizationViewUrl(params.server_url, result)}
|
|
1678
|
-
|
|
1679
|
-
`;
|
|
1680
|
-
markdown += `## Details
|
|
1681
|
-
|
|
1682
|
-
`;
|
|
1683
|
-
markdown += `- **ID**: \`${result.id}\`
|
|
1684
|
-
`;
|
|
1685
|
-
markdown += `- **Name**: \`${result.name}\`
|
|
1686
|
-
`;
|
|
1687
|
-
markdown += `- **Datasets**: ${result.package_count || 0}
|
|
1688
|
-
`;
|
|
1689
|
-
markdown += `- **Created**: ${formatDate(result.created)}
|
|
1690
|
-
`;
|
|
1691
|
-
markdown += `- **State**: ${result.state}
|
|
1692
|
-
|
|
1693
|
-
`;
|
|
1694
|
-
if (result.description) {
|
|
1695
|
-
markdown += `## Description
|
|
1696
|
-
|
|
1697
|
-
${result.description}
|
|
1698
|
-
|
|
1699
|
-
`;
|
|
1700
|
-
}
|
|
1701
|
-
if (result.packages && result.packages.length > 0) {
|
|
1702
|
-
markdown += `## Datasets (${result.packages.length})
|
|
1703
|
-
|
|
1704
|
-
`;
|
|
1705
|
-
for (const pkg of result.packages.slice(0, 20)) {
|
|
1706
|
-
markdown += `- **${pkg.title || pkg.name}** (\`${pkg.name}\`)
|
|
1707
|
-
`;
|
|
1708
|
-
}
|
|
1709
|
-
if (result.packages.length > 20) {
|
|
1710
|
-
markdown += `
|
|
1711
|
-
... and ${result.packages.length - 20} more datasets
|
|
1712
|
-
`;
|
|
1713
|
-
}
|
|
1714
|
-
markdown += "\n";
|
|
1715
|
-
}
|
|
1716
|
-
if (result.users && result.users.length > 0) {
|
|
1717
|
-
markdown += `## Users (${result.users.length})
|
|
1718
|
-
|
|
1719
|
-
`;
|
|
1720
|
-
for (const user of result.users) {
|
|
1721
|
-
markdown += `- **${user.name}** (${user.capacity})
|
|
1722
|
-
`;
|
|
1723
|
-
}
|
|
1724
|
-
markdown += "\n";
|
|
1725
|
-
}
|
|
1810
|
+
const markdown = formatOrganizationShowMarkdown(result, params.server_url);
|
|
1726
1811
|
return {
|
|
1727
1812
|
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
1728
1813
|
};
|
|
@@ -1847,6 +1932,108 @@ Typical workflow: ckan_organization_search \u2192 ckan_organization_show (get de
|
|
|
1847
1932
|
|
|
1848
1933
|
// src/tools/datastore.ts
|
|
1849
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
|
+
}
|
|
1850
2037
|
function registerDatastoreTools(server2) {
|
|
1851
2038
|
server2.registerTool(
|
|
1852
2039
|
"ckan_datastore_search",
|
|
@@ -1918,56 +2105,7 @@ Typical workflow: ckan_package_search \u2192 ckan_package_show (find resource_id
|
|
|
1918
2105
|
content: [{ type: "text", text: truncateText(JSON.stringify(result, null, 2)) }]
|
|
1919
2106
|
};
|
|
1920
2107
|
}
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
`;
|
|
1924
|
-
markdown += `**Server**: ${params.server_url}
|
|
1925
|
-
`;
|
|
1926
|
-
markdown += `**Resource ID**: \`${params.resource_id}\`
|
|
1927
|
-
`;
|
|
1928
|
-
markdown += `**Total Records**: ${result.total || 0}
|
|
1929
|
-
`;
|
|
1930
|
-
markdown += `**Returned**: ${result.records ? result.records.length : 0} records
|
|
1931
|
-
|
|
1932
|
-
`;
|
|
1933
|
-
if (result.fields && result.fields.length > 0) {
|
|
1934
|
-
markdown += `## Fields
|
|
1935
|
-
|
|
1936
|
-
`;
|
|
1937
|
-
markdown += result.fields.map((f) => `- **${f.id}** (${f.type})`).join("\n") + "\n\n";
|
|
1938
|
-
}
|
|
1939
|
-
if (result.records && result.records.length > 0) {
|
|
1940
|
-
markdown += `## Records
|
|
1941
|
-
|
|
1942
|
-
`;
|
|
1943
|
-
const fields = result.fields.map((f) => f.id);
|
|
1944
|
-
const displayFields = fields.slice(0, 8);
|
|
1945
|
-
markdown += `| ${displayFields.join(" | ")} |
|
|
1946
|
-
`;
|
|
1947
|
-
markdown += `| ${displayFields.map(() => "---").join(" | ")} |
|
|
1948
|
-
`;
|
|
1949
|
-
for (const record of result.records.slice(0, 50)) {
|
|
1950
|
-
const values = displayFields.map((field) => {
|
|
1951
|
-
const val = record[field];
|
|
1952
|
-
if (val === null || val === void 0) return "-";
|
|
1953
|
-
const str = String(val);
|
|
1954
|
-
return str.length > 50 ? str.substring(0, 47) + "..." : str;
|
|
1955
|
-
});
|
|
1956
|
-
markdown += `| ${values.join(" | ")} |
|
|
1957
|
-
`;
|
|
1958
|
-
}
|
|
1959
|
-
if (result.records.length > 50) {
|
|
1960
|
-
markdown += `
|
|
1961
|
-
... and ${result.records.length - 50} more records
|
|
1962
|
-
`;
|
|
1963
|
-
}
|
|
1964
|
-
markdown += "\n";
|
|
1965
|
-
}
|
|
1966
|
-
if (result.total && result.total > params.offset + (result.records?.length || 0)) {
|
|
1967
|
-
const nextOffset = params.offset + params.limit;
|
|
1968
|
-
markdown += `**More results available**: Use \`offset: ${nextOffset}\` for next page.
|
|
1969
|
-
`;
|
|
1970
|
-
}
|
|
2108
|
+
const markdown = formatDatastoreSearchMarkdown(result, params.server_url, params.resource_id, params.offset, params.limit);
|
|
1971
2109
|
return {
|
|
1972
2110
|
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
1973
2111
|
};
|
|
@@ -2028,52 +2166,7 @@ Typical workflow: ckan_package_show (get resource_id) \u2192 ckan_datastore_sear
|
|
|
2028
2166
|
structuredContent: result
|
|
2029
2167
|
};
|
|
2030
2168
|
}
|
|
2031
|
-
const
|
|
2032
|
-
const fieldIds = result.fields?.map((field) => field.id) || Object.keys(records[0] || {});
|
|
2033
|
-
let markdown = `# DataStore SQL Results
|
|
2034
|
-
|
|
2035
|
-
`;
|
|
2036
|
-
markdown += `**Server**: ${params.server_url}
|
|
2037
|
-
`;
|
|
2038
|
-
markdown += `**SQL**: \`${params.sql}\`
|
|
2039
|
-
`;
|
|
2040
|
-
markdown += `**Returned**: ${records.length} records
|
|
2041
|
-
|
|
2042
|
-
`;
|
|
2043
|
-
if (result.fields && result.fields.length > 0) {
|
|
2044
|
-
markdown += `## Fields
|
|
2045
|
-
|
|
2046
|
-
`;
|
|
2047
|
-
markdown += result.fields.map((field) => `- **${field.id}** (${field.type})`).join("\n") + "\n\n";
|
|
2048
|
-
}
|
|
2049
|
-
if (records.length > 0 && fieldIds.length > 0) {
|
|
2050
|
-
markdown += `## Records
|
|
2051
|
-
|
|
2052
|
-
`;
|
|
2053
|
-
const displayFields = fieldIds.slice(0, 8);
|
|
2054
|
-
markdown += `| ${displayFields.join(" | ")} |
|
|
2055
|
-
`;
|
|
2056
|
-
markdown += `| ${displayFields.map(() => "---").join(" | ")} |
|
|
2057
|
-
`;
|
|
2058
|
-
for (const record of records.slice(0, 50)) {
|
|
2059
|
-
const values = displayFields.map((field) => {
|
|
2060
|
-
const value = record[field];
|
|
2061
|
-
if (value === null || value === void 0) return "-";
|
|
2062
|
-
const text = String(value);
|
|
2063
|
-
return text.length > 50 ? text.substring(0, 47) + "..." : text;
|
|
2064
|
-
});
|
|
2065
|
-
markdown += `| ${values.join(" | ")} |
|
|
2066
|
-
`;
|
|
2067
|
-
}
|
|
2068
|
-
if (records.length > 50) {
|
|
2069
|
-
markdown += `
|
|
2070
|
-
... and ${records.length - 50} more records
|
|
2071
|
-
`;
|
|
2072
|
-
}
|
|
2073
|
-
markdown += "\n";
|
|
2074
|
-
} else {
|
|
2075
|
-
markdown += "No records returned by the SQL query.\n";
|
|
2076
|
-
}
|
|
2169
|
+
const markdown = formatDatastoreSqlMarkdown(result, params.server_url, params.sql);
|
|
2077
2170
|
return {
|
|
2078
2171
|
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
2079
2172
|
};
|
|
@@ -2092,6 +2185,16 @@ Typical workflow: ckan_package_show (get resource_id) \u2192 ckan_datastore_sear
|
|
|
2092
2185
|
|
|
2093
2186
|
// src/tools/status.ts
|
|
2094
2187
|
import { z as z5 } from "zod";
|
|
2188
|
+
function formatStatusMarkdown(result, serverUrl) {
|
|
2189
|
+
return `# CKAN Server Status
|
|
2190
|
+
|
|
2191
|
+
**Server**: ${serverUrl}
|
|
2192
|
+
**Status**: \u2705 Online
|
|
2193
|
+
**CKAN Version**: ${result.ckan_version || "Unknown"}
|
|
2194
|
+
**Site Title**: ${result.site_title || "N/A"}
|
|
2195
|
+
**Site URL**: ${result.site_url || "N/A"}
|
|
2196
|
+
`;
|
|
2197
|
+
}
|
|
2095
2198
|
function registerStatusTools(server2) {
|
|
2096
2199
|
server2.registerTool(
|
|
2097
2200
|
"ckan_status_show",
|
|
@@ -2125,14 +2228,7 @@ Typical workflow: ckan_status_show (verify server is up) \u2192 ckan_package_sea
|
|
|
2125
2228
|
"status_show",
|
|
2126
2229
|
{}
|
|
2127
2230
|
);
|
|
2128
|
-
const markdown =
|
|
2129
|
-
|
|
2130
|
-
**Server**: ${params.server_url}
|
|
2131
|
-
**Status**: \u2705 Online
|
|
2132
|
-
**CKAN Version**: ${result.ckan_version || "Unknown"}
|
|
2133
|
-
**Site Title**: ${result.site_title || "N/A"}
|
|
2134
|
-
**Site URL**: ${result.site_url || "N/A"}
|
|
2135
|
-
`;
|
|
2231
|
+
const markdown = formatStatusMarkdown(result, params.server_url);
|
|
2136
2232
|
return {
|
|
2137
2233
|
content: [{ type: "text", text: addDemoFooter(markdown) }],
|
|
2138
2234
|
structuredContent: result
|
|
@@ -2154,7 +2250,10 @@ ${error instanceof Error ? error.message : String(error)}`
|
|
|
2154
2250
|
// src/tools/tag.ts
|
|
2155
2251
|
import { z as z6 } from "zod";
|
|
2156
2252
|
function normalizeTagFacets(result) {
|
|
2157
|
-
const
|
|
2253
|
+
const r = result;
|
|
2254
|
+
const searchFacets = r?.search_facets;
|
|
2255
|
+
const tagsGroup = searchFacets?.tags;
|
|
2256
|
+
const searchItems = tagsGroup?.items;
|
|
2158
2257
|
if (Array.isArray(searchItems)) {
|
|
2159
2258
|
return searchItems.map((item) => ({
|
|
2160
2259
|
name: item?.name || item?.display_name || String(item),
|
|
@@ -2162,7 +2261,7 @@ function normalizeTagFacets(result) {
|
|
|
2162
2261
|
display_name: item?.display_name
|
|
2163
2262
|
}));
|
|
2164
2263
|
}
|
|
2165
|
-
const facets =
|
|
2264
|
+
const facets = r?.facets?.tags;
|
|
2166
2265
|
if (Array.isArray(facets)) {
|
|
2167
2266
|
if (facets.length > 0 && typeof facets[0] === "object") {
|
|
2168
2267
|
return facets.map((item) => ({
|
|
@@ -2292,16 +2391,67 @@ function getGroupViewUrl(serverUrl, group) {
|
|
|
2292
2391
|
const cleanServerUrl = serverUrl.replace(/\/$/, "");
|
|
2293
2392
|
return `${cleanServerUrl}/group/${group.name}`;
|
|
2294
2393
|
}
|
|
2394
|
+
function formatGroupShowMarkdown(result, serverUrl) {
|
|
2395
|
+
let markdown = `# Group: ${result.title || result.name}
|
|
2396
|
+
|
|
2397
|
+
`;
|
|
2398
|
+
markdown += `**Server**: ${serverUrl}
|
|
2399
|
+
`;
|
|
2400
|
+
markdown += `**Link**: ${getGroupViewUrl(serverUrl, result)}
|
|
2401
|
+
|
|
2402
|
+
`;
|
|
2403
|
+
markdown += `## Details
|
|
2404
|
+
|
|
2405
|
+
`;
|
|
2406
|
+
markdown += `- **ID**: \`${result.id}\`
|
|
2407
|
+
`;
|
|
2408
|
+
markdown += `- **Name**: \`${result.name}\`
|
|
2409
|
+
`;
|
|
2410
|
+
markdown += `- **Datasets**: ${result.package_count || 0}
|
|
2411
|
+
`;
|
|
2412
|
+
markdown += `- **Created**: ${formatDate(result.created)}
|
|
2413
|
+
`;
|
|
2414
|
+
markdown += `- **State**: ${result.state}
|
|
2415
|
+
|
|
2416
|
+
`;
|
|
2417
|
+
if (result.description) {
|
|
2418
|
+
markdown += `## Description
|
|
2419
|
+
|
|
2420
|
+
${result.description}
|
|
2421
|
+
|
|
2422
|
+
`;
|
|
2423
|
+
}
|
|
2424
|
+
if (result.packages && result.packages.length > 0) {
|
|
2425
|
+
const displayed = Math.min(result.packages.length, 20);
|
|
2426
|
+
const totalHint = result.package_count && result.package_count !== result.packages.length ? ` \u2014 ${result.package_count} total` : "";
|
|
2427
|
+
markdown += `## Datasets (showing ${displayed} of ${result.packages.length} returned${totalHint})
|
|
2428
|
+
|
|
2429
|
+
`;
|
|
2430
|
+
for (const pkg of result.packages.slice(0, 20)) {
|
|
2431
|
+
markdown += `- **${pkg.title || pkg.name}** (\`${pkg.name}\`)
|
|
2432
|
+
`;
|
|
2433
|
+
}
|
|
2434
|
+
if (result.packages.length > 20) {
|
|
2435
|
+
markdown += `
|
|
2436
|
+
... and ${result.packages.length - 20} more datasets
|
|
2437
|
+
`;
|
|
2438
|
+
}
|
|
2439
|
+
markdown += "\n";
|
|
2440
|
+
}
|
|
2441
|
+
return markdown;
|
|
2442
|
+
}
|
|
2295
2443
|
function normalizeGroupFacets(result) {
|
|
2296
|
-
const
|
|
2297
|
-
|
|
2298
|
-
|
|
2444
|
+
const r = result;
|
|
2445
|
+
const items = r?.search_facets?.groups;
|
|
2446
|
+
const itemsArr = items?.items;
|
|
2447
|
+
if (Array.isArray(itemsArr)) {
|
|
2448
|
+
return itemsArr.map((item) => ({
|
|
2299
2449
|
name: item?.name || item?.display_name || String(item),
|
|
2300
2450
|
display_name: item?.display_name,
|
|
2301
2451
|
count: typeof item?.count === "number" ? item.count : 0
|
|
2302
2452
|
}));
|
|
2303
2453
|
}
|
|
2304
|
-
const facets =
|
|
2454
|
+
const facets = r?.facets?.groups;
|
|
2305
2455
|
if (Array.isArray(facets)) {
|
|
2306
2456
|
if (facets.length > 0 && typeof facets[0] === "object") {
|
|
2307
2457
|
return facets.map((item) => ({
|
|
@@ -2492,50 +2642,7 @@ Typical workflow: ckan_group_show \u2192 ckan_package_show (inspect a dataset) \
|
|
|
2492
2642
|
structuredContent: result
|
|
2493
2643
|
};
|
|
2494
2644
|
}
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
`;
|
|
2498
|
-
markdown += `**Server**: ${params.server_url}
|
|
2499
|
-
`;
|
|
2500
|
-
markdown += `**Link**: ${getGroupViewUrl(params.server_url, result)}
|
|
2501
|
-
|
|
2502
|
-
`;
|
|
2503
|
-
markdown += `## Details
|
|
2504
|
-
|
|
2505
|
-
`;
|
|
2506
|
-
markdown += `- **ID**: \`${result.id}\`
|
|
2507
|
-
`;
|
|
2508
|
-
markdown += `- **Name**: \`${result.name}\`
|
|
2509
|
-
`;
|
|
2510
|
-
markdown += `- **Datasets**: ${result.package_count || 0}
|
|
2511
|
-
`;
|
|
2512
|
-
markdown += `- **Created**: ${formatDate(result.created)}
|
|
2513
|
-
`;
|
|
2514
|
-
markdown += `- **State**: ${result.state}
|
|
2515
|
-
|
|
2516
|
-
`;
|
|
2517
|
-
if (result.description) {
|
|
2518
|
-
markdown += `## Description
|
|
2519
|
-
|
|
2520
|
-
${result.description}
|
|
2521
|
-
|
|
2522
|
-
`;
|
|
2523
|
-
}
|
|
2524
|
-
if (result.packages && result.packages.length > 0) {
|
|
2525
|
-
markdown += `## Datasets (${result.packages.length})
|
|
2526
|
-
|
|
2527
|
-
`;
|
|
2528
|
-
for (const pkg of result.packages.slice(0, 20)) {
|
|
2529
|
-
markdown += `- **${pkg.title || pkg.name}** (\`${pkg.name}\`)
|
|
2530
|
-
`;
|
|
2531
|
-
}
|
|
2532
|
-
if (result.packages.length > 20) {
|
|
2533
|
-
markdown += `
|
|
2534
|
-
... and ${result.packages.length - 20} more datasets
|
|
2535
|
-
`;
|
|
2536
|
-
}
|
|
2537
|
-
markdown += "\n";
|
|
2538
|
-
}
|
|
2645
|
+
const markdown = formatGroupShowMarkdown(result, params.server_url);
|
|
2539
2646
|
return {
|
|
2540
2647
|
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
2541
2648
|
};
|
|
@@ -3089,8 +3196,9 @@ function metricAvailability(section, availabilityKey, statusKey) {
|
|
|
3089
3196
|
return void 0;
|
|
3090
3197
|
}
|
|
3091
3198
|
function normalizeQualityData(data) {
|
|
3092
|
-
const
|
|
3093
|
-
const
|
|
3199
|
+
const d = data;
|
|
3200
|
+
const mqaData = d?.mqa ?? data;
|
|
3201
|
+
const breakdown = d?.breakdown;
|
|
3094
3202
|
const resultEntry = mqaData?.result?.results?.[0];
|
|
3095
3203
|
if (!resultEntry || typeof resultEntry !== "object") {
|
|
3096
3204
|
return { ...data, breakdown };
|
|
@@ -3896,7 +4004,7 @@ var registerAllPrompts = (server2) => {
|
|
|
3896
4004
|
function createServer() {
|
|
3897
4005
|
return new McpServer({
|
|
3898
4006
|
name: "ckan-mcp-server",
|
|
3899
|
-
version: "0.4.
|
|
4007
|
+
version: "0.4.50"
|
|
3900
4008
|
});
|
|
3901
4009
|
}
|
|
3902
4010
|
function registerAll(server2) {
|