@aborruso/ckan-mcp-server 0.4.2 → 0.4.4
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/EXAMPLES.md +27 -0
- package/LOG.md +13 -0
- package/PRD.md +6 -6
- package/README.md +54 -42
- package/dist/index.js +498 -1
- package/dist/worker.js +147 -36
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1059,6 +1059,501 @@ ${error instanceof Error ? error.message : String(error)}`
|
|
|
1059
1059
|
);
|
|
1060
1060
|
}
|
|
1061
1061
|
|
|
1062
|
+
// src/tools/tag.ts
|
|
1063
|
+
import { z as z6 } from "zod";
|
|
1064
|
+
function normalizeTagFacets(result) {
|
|
1065
|
+
const searchItems = result?.search_facets?.tags?.items;
|
|
1066
|
+
if (Array.isArray(searchItems)) {
|
|
1067
|
+
return searchItems.map((item) => ({
|
|
1068
|
+
name: item?.name || item?.display_name || String(item),
|
|
1069
|
+
count: typeof item?.count === "number" ? item.count : 0,
|
|
1070
|
+
display_name: item?.display_name
|
|
1071
|
+
}));
|
|
1072
|
+
}
|
|
1073
|
+
const facets = result?.facets?.tags;
|
|
1074
|
+
if (Array.isArray(facets)) {
|
|
1075
|
+
if (facets.length > 0 && typeof facets[0] === "object") {
|
|
1076
|
+
return facets.map((item) => ({
|
|
1077
|
+
name: item?.name || item?.display_name || String(item),
|
|
1078
|
+
count: typeof item?.count === "number" ? item.count : 0,
|
|
1079
|
+
display_name: item?.display_name
|
|
1080
|
+
}));
|
|
1081
|
+
}
|
|
1082
|
+
return facets.map((name) => ({ name, count: 0 }));
|
|
1083
|
+
}
|
|
1084
|
+
if (facets && typeof facets === "object") {
|
|
1085
|
+
return Object.entries(facets).map(([name, count]) => ({
|
|
1086
|
+
name,
|
|
1087
|
+
count: typeof count === "number" ? count : Number(count) || 0
|
|
1088
|
+
}));
|
|
1089
|
+
}
|
|
1090
|
+
return [];
|
|
1091
|
+
}
|
|
1092
|
+
function registerTagTools(server2) {
|
|
1093
|
+
server2.registerTool(
|
|
1094
|
+
"ckan_tag_list",
|
|
1095
|
+
{
|
|
1096
|
+
title: "List CKAN Tags",
|
|
1097
|
+
description: `List tags from a CKAN server using faceting.
|
|
1098
|
+
|
|
1099
|
+
This returns tag names with counts, optionally filtered by dataset query or tag substring.
|
|
1100
|
+
|
|
1101
|
+
Args:
|
|
1102
|
+
- server_url (string): Base URL of CKAN server
|
|
1103
|
+
- q (string): Dataset search query (default: "*:*")
|
|
1104
|
+
- fq (string): Filter query (optional)
|
|
1105
|
+
- tag_query (string): Filter tags by substring (optional)
|
|
1106
|
+
- limit (number): Max tags to return (default: 100, max: 1000)
|
|
1107
|
+
- response_format ('markdown' | 'json'): Output format
|
|
1108
|
+
|
|
1109
|
+
Returns:
|
|
1110
|
+
List of tags with counts (from faceting)`,
|
|
1111
|
+
inputSchema: z6.object({
|
|
1112
|
+
server_url: z6.string().url(),
|
|
1113
|
+
q: z6.string().optional().default("*:*"),
|
|
1114
|
+
fq: z6.string().optional(),
|
|
1115
|
+
tag_query: z6.string().optional(),
|
|
1116
|
+
limit: z6.number().int().min(1).max(1e3).optional().default(100),
|
|
1117
|
+
response_format: ResponseFormatSchema
|
|
1118
|
+
}).strict(),
|
|
1119
|
+
annotations: {
|
|
1120
|
+
readOnlyHint: true,
|
|
1121
|
+
destructiveHint: false,
|
|
1122
|
+
idempotentHint: true,
|
|
1123
|
+
openWorldHint: true
|
|
1124
|
+
}
|
|
1125
|
+
},
|
|
1126
|
+
async (params) => {
|
|
1127
|
+
try {
|
|
1128
|
+
const apiParams = {
|
|
1129
|
+
q: params.q,
|
|
1130
|
+
rows: 0,
|
|
1131
|
+
"facet.field": JSON.stringify(["tags"]),
|
|
1132
|
+
"facet.limit": params.limit
|
|
1133
|
+
};
|
|
1134
|
+
if (params.fq) apiParams.fq = params.fq;
|
|
1135
|
+
const result = await makeCkanRequest(
|
|
1136
|
+
params.server_url,
|
|
1137
|
+
"package_search",
|
|
1138
|
+
apiParams
|
|
1139
|
+
);
|
|
1140
|
+
let tags = normalizeTagFacets(result);
|
|
1141
|
+
if (params.tag_query) {
|
|
1142
|
+
const needle = params.tag_query.toLowerCase();
|
|
1143
|
+
tags = tags.filter((tag) => tag.name.toLowerCase().includes(needle));
|
|
1144
|
+
}
|
|
1145
|
+
tags = tags.sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)).slice(0, params.limit);
|
|
1146
|
+
if (params.response_format === "json" /* JSON */) {
|
|
1147
|
+
const output = {
|
|
1148
|
+
count: tags.length,
|
|
1149
|
+
tags
|
|
1150
|
+
};
|
|
1151
|
+
return {
|
|
1152
|
+
content: [{ type: "text", text: truncateText(JSON.stringify(output, null, 2)) }],
|
|
1153
|
+
structuredContent: output
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
let markdown = `# CKAN Tags
|
|
1157
|
+
|
|
1158
|
+
`;
|
|
1159
|
+
markdown += `**Server**: ${params.server_url}
|
|
1160
|
+
`;
|
|
1161
|
+
markdown += `**Query**: ${params.q}
|
|
1162
|
+
`;
|
|
1163
|
+
if (params.fq) markdown += `**Filter**: ${params.fq}
|
|
1164
|
+
`;
|
|
1165
|
+
if (params.tag_query) markdown += `**Tag Query**: ${params.tag_query}
|
|
1166
|
+
`;
|
|
1167
|
+
markdown += `**Count**: ${tags.length}
|
|
1168
|
+
|
|
1169
|
+
`;
|
|
1170
|
+
if (tags.length === 0) {
|
|
1171
|
+
markdown += `No tags found.
|
|
1172
|
+
`;
|
|
1173
|
+
} else {
|
|
1174
|
+
for (const tag of tags) {
|
|
1175
|
+
markdown += `- **${tag.name}**: ${tag.count}
|
|
1176
|
+
`;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return {
|
|
1180
|
+
content: [{ type: "text", text: truncateText(markdown) }]
|
|
1181
|
+
};
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
return {
|
|
1184
|
+
content: [{
|
|
1185
|
+
type: "text",
|
|
1186
|
+
text: `Error listing tags: ${error instanceof Error ? error.message : String(error)}`
|
|
1187
|
+
}],
|
|
1188
|
+
isError: true
|
|
1189
|
+
};
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// src/tools/group.ts
|
|
1196
|
+
import { z as z7 } from "zod";
|
|
1197
|
+
function getGroupViewUrl(serverUrl, group) {
|
|
1198
|
+
const cleanServerUrl = serverUrl.replace(/\/$/, "");
|
|
1199
|
+
return `${cleanServerUrl}/group/${group.name}`;
|
|
1200
|
+
}
|
|
1201
|
+
function normalizeGroupFacets(result) {
|
|
1202
|
+
const items = result?.search_facets?.groups?.items;
|
|
1203
|
+
if (Array.isArray(items)) {
|
|
1204
|
+
return items.map((item) => ({
|
|
1205
|
+
name: item?.name || item?.display_name || String(item),
|
|
1206
|
+
display_name: item?.display_name,
|
|
1207
|
+
count: typeof item?.count === "number" ? item.count : 0
|
|
1208
|
+
}));
|
|
1209
|
+
}
|
|
1210
|
+
const facets = result?.facets?.groups;
|
|
1211
|
+
if (Array.isArray(facets)) {
|
|
1212
|
+
if (facets.length > 0 && typeof facets[0] === "object") {
|
|
1213
|
+
return facets.map((item) => ({
|
|
1214
|
+
name: item?.name || item?.display_name || String(item),
|
|
1215
|
+
display_name: item?.display_name,
|
|
1216
|
+
count: typeof item?.count === "number" ? item.count : 0
|
|
1217
|
+
}));
|
|
1218
|
+
}
|
|
1219
|
+
return facets.map((name) => ({ name, count: 0 }));
|
|
1220
|
+
}
|
|
1221
|
+
if (facets && typeof facets === "object") {
|
|
1222
|
+
return Object.entries(facets).map(([name, count]) => ({
|
|
1223
|
+
name,
|
|
1224
|
+
count: typeof count === "number" ? count : Number(count) || 0
|
|
1225
|
+
}));
|
|
1226
|
+
}
|
|
1227
|
+
return [];
|
|
1228
|
+
}
|
|
1229
|
+
function registerGroupTools(server2) {
|
|
1230
|
+
server2.registerTool(
|
|
1231
|
+
"ckan_group_list",
|
|
1232
|
+
{
|
|
1233
|
+
title: "List CKAN Groups",
|
|
1234
|
+
description: `List all groups on a CKAN server.
|
|
1235
|
+
|
|
1236
|
+
Groups are thematic collections of datasets.
|
|
1237
|
+
|
|
1238
|
+
Args:
|
|
1239
|
+
- server_url (string): Base URL of CKAN server
|
|
1240
|
+
- all_fields (boolean): Return full objects vs just names (default: false)
|
|
1241
|
+
- sort (string): Sort field (default: "name asc")
|
|
1242
|
+
- limit (number): Maximum results (default: 100). Use 0 to get only the count via faceting
|
|
1243
|
+
- offset (number): Pagination offset (default: 0)
|
|
1244
|
+
- response_format ('markdown' | 'json'): Output format
|
|
1245
|
+
|
|
1246
|
+
Returns:
|
|
1247
|
+
List of groups with metadata. When limit=0, returns only the count of groups with datasets.`,
|
|
1248
|
+
inputSchema: z7.object({
|
|
1249
|
+
server_url: z7.string().url(),
|
|
1250
|
+
all_fields: z7.boolean().optional().default(false),
|
|
1251
|
+
sort: z7.string().optional().default("name asc"),
|
|
1252
|
+
limit: z7.number().int().min(0).optional().default(100),
|
|
1253
|
+
offset: z7.number().int().min(0).optional().default(0),
|
|
1254
|
+
response_format: ResponseFormatSchema
|
|
1255
|
+
}).strict(),
|
|
1256
|
+
annotations: {
|
|
1257
|
+
readOnlyHint: true,
|
|
1258
|
+
destructiveHint: false,
|
|
1259
|
+
idempotentHint: true,
|
|
1260
|
+
openWorldHint: false
|
|
1261
|
+
}
|
|
1262
|
+
},
|
|
1263
|
+
async (params) => {
|
|
1264
|
+
try {
|
|
1265
|
+
if (params.limit === 0) {
|
|
1266
|
+
const searchResult = await makeCkanRequest(
|
|
1267
|
+
params.server_url,
|
|
1268
|
+
"package_search",
|
|
1269
|
+
{
|
|
1270
|
+
rows: 0,
|
|
1271
|
+
"facet.field": JSON.stringify(["groups"]),
|
|
1272
|
+
"facet.limit": -1
|
|
1273
|
+
}
|
|
1274
|
+
);
|
|
1275
|
+
const groupCount = searchResult.search_facets?.groups?.items?.length || 0;
|
|
1276
|
+
if (params.response_format === "json" /* JSON */) {
|
|
1277
|
+
return {
|
|
1278
|
+
content: [{ type: "text", text: JSON.stringify({ count: groupCount }, null, 2) }],
|
|
1279
|
+
structuredContent: { count: groupCount }
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
const markdown2 = `# CKAN Groups Count
|
|
1283
|
+
|
|
1284
|
+
**Server**: ${params.server_url}
|
|
1285
|
+
**Total groups (with datasets)**: ${groupCount}
|
|
1286
|
+
`;
|
|
1287
|
+
return {
|
|
1288
|
+
content: [{ type: "text", text: markdown2 }]
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
const result = await makeCkanRequest(
|
|
1292
|
+
params.server_url,
|
|
1293
|
+
"group_list",
|
|
1294
|
+
{
|
|
1295
|
+
all_fields: params.all_fields,
|
|
1296
|
+
sort: params.sort,
|
|
1297
|
+
limit: params.limit,
|
|
1298
|
+
offset: params.offset
|
|
1299
|
+
}
|
|
1300
|
+
);
|
|
1301
|
+
if (params.response_format === "json" /* JSON */) {
|
|
1302
|
+
const output = Array.isArray(result) ? { count: result.length, groups: result } : result;
|
|
1303
|
+
return {
|
|
1304
|
+
content: [{ type: "text", text: truncateText(JSON.stringify(output, null, 2)) }],
|
|
1305
|
+
structuredContent: output
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
let markdown = `# CKAN Groups
|
|
1309
|
+
|
|
1310
|
+
`;
|
|
1311
|
+
markdown += `**Server**: ${params.server_url}
|
|
1312
|
+
`;
|
|
1313
|
+
markdown += `**Total**: ${Array.isArray(result) ? result.length : "Unknown"}
|
|
1314
|
+
|
|
1315
|
+
`;
|
|
1316
|
+
if (Array.isArray(result)) {
|
|
1317
|
+
if (params.all_fields) {
|
|
1318
|
+
for (const group of result) {
|
|
1319
|
+
markdown += `## ${group.title || group.name}
|
|
1320
|
+
|
|
1321
|
+
`;
|
|
1322
|
+
markdown += `- **ID**: \`${group.id}\`
|
|
1323
|
+
`;
|
|
1324
|
+
markdown += `- **Name**: \`${group.name}\`
|
|
1325
|
+
`;
|
|
1326
|
+
if (group.description) markdown += `- **Description**: ${group.description.substring(0, 200)}
|
|
1327
|
+
`;
|
|
1328
|
+
markdown += `- **Datasets**: ${group.package_count || 0}
|
|
1329
|
+
`;
|
|
1330
|
+
markdown += `- **Created**: ${formatDate(group.created)}
|
|
1331
|
+
`;
|
|
1332
|
+
markdown += `- **Link**: ${getGroupViewUrl(params.server_url, group)}
|
|
1333
|
+
|
|
1334
|
+
`;
|
|
1335
|
+
}
|
|
1336
|
+
} else {
|
|
1337
|
+
markdown += result.map((name) => `- ${name}`).join("\n");
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
return {
|
|
1341
|
+
content: [{ type: "text", text: truncateText(markdown) }]
|
|
1342
|
+
};
|
|
1343
|
+
} catch (error) {
|
|
1344
|
+
return {
|
|
1345
|
+
content: [{
|
|
1346
|
+
type: "text",
|
|
1347
|
+
text: `Error listing groups: ${error instanceof Error ? error.message : String(error)}`
|
|
1348
|
+
}],
|
|
1349
|
+
isError: true
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
);
|
|
1354
|
+
server2.registerTool(
|
|
1355
|
+
"ckan_group_show",
|
|
1356
|
+
{
|
|
1357
|
+
title: "Show CKAN Group Details",
|
|
1358
|
+
description: `Get details of a specific group.
|
|
1359
|
+
|
|
1360
|
+
Args:
|
|
1361
|
+
- server_url (string): Base URL of CKAN server
|
|
1362
|
+
- id (string): Group ID or name
|
|
1363
|
+
- include_datasets (boolean): Include list of datasets (default: true)
|
|
1364
|
+
- response_format ('markdown' | 'json'): Output format
|
|
1365
|
+
|
|
1366
|
+
Returns:
|
|
1367
|
+
Group details with optional datasets`,
|
|
1368
|
+
inputSchema: z7.object({
|
|
1369
|
+
server_url: z7.string().url(),
|
|
1370
|
+
id: z7.string().min(1),
|
|
1371
|
+
include_datasets: z7.boolean().optional().default(true),
|
|
1372
|
+
response_format: ResponseFormatSchema
|
|
1373
|
+
}).strict(),
|
|
1374
|
+
annotations: {
|
|
1375
|
+
readOnlyHint: true,
|
|
1376
|
+
destructiveHint: false,
|
|
1377
|
+
idempotentHint: true,
|
|
1378
|
+
openWorldHint: false
|
|
1379
|
+
}
|
|
1380
|
+
},
|
|
1381
|
+
async (params) => {
|
|
1382
|
+
try {
|
|
1383
|
+
const result = await makeCkanRequest(
|
|
1384
|
+
params.server_url,
|
|
1385
|
+
"group_show",
|
|
1386
|
+
{
|
|
1387
|
+
id: params.id,
|
|
1388
|
+
include_datasets: params.include_datasets
|
|
1389
|
+
}
|
|
1390
|
+
);
|
|
1391
|
+
if (params.response_format === "json" /* JSON */) {
|
|
1392
|
+
return {
|
|
1393
|
+
content: [{ type: "text", text: truncateText(JSON.stringify(result, null, 2)) }],
|
|
1394
|
+
structuredContent: result
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
let markdown = `# Group: ${result.title || result.name}
|
|
1398
|
+
|
|
1399
|
+
`;
|
|
1400
|
+
markdown += `**Server**: ${params.server_url}
|
|
1401
|
+
`;
|
|
1402
|
+
markdown += `**Link**: ${getGroupViewUrl(params.server_url, result)}
|
|
1403
|
+
|
|
1404
|
+
`;
|
|
1405
|
+
markdown += `## Details
|
|
1406
|
+
|
|
1407
|
+
`;
|
|
1408
|
+
markdown += `- **ID**: \`${result.id}\`
|
|
1409
|
+
`;
|
|
1410
|
+
markdown += `- **Name**: \`${result.name}\`
|
|
1411
|
+
`;
|
|
1412
|
+
markdown += `- **Datasets**: ${result.package_count || 0}
|
|
1413
|
+
`;
|
|
1414
|
+
markdown += `- **Created**: ${formatDate(result.created)}
|
|
1415
|
+
`;
|
|
1416
|
+
markdown += `- **State**: ${result.state}
|
|
1417
|
+
|
|
1418
|
+
`;
|
|
1419
|
+
if (result.description) {
|
|
1420
|
+
markdown += `## Description
|
|
1421
|
+
|
|
1422
|
+
${result.description}
|
|
1423
|
+
|
|
1424
|
+
`;
|
|
1425
|
+
}
|
|
1426
|
+
if (result.packages && result.packages.length > 0) {
|
|
1427
|
+
markdown += `## Datasets (${result.packages.length})
|
|
1428
|
+
|
|
1429
|
+
`;
|
|
1430
|
+
for (const pkg of result.packages.slice(0, 20)) {
|
|
1431
|
+
markdown += `- **${pkg.title || pkg.name}** (\`${pkg.name}\`)
|
|
1432
|
+
`;
|
|
1433
|
+
}
|
|
1434
|
+
if (result.packages.length > 20) {
|
|
1435
|
+
markdown += `
|
|
1436
|
+
... and ${result.packages.length - 20} more datasets
|
|
1437
|
+
`;
|
|
1438
|
+
}
|
|
1439
|
+
markdown += "\n";
|
|
1440
|
+
}
|
|
1441
|
+
return {
|
|
1442
|
+
content: [{ type: "text", text: truncateText(markdown) }]
|
|
1443
|
+
};
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
return {
|
|
1446
|
+
content: [{
|
|
1447
|
+
type: "text",
|
|
1448
|
+
text: `Error fetching group: ${error instanceof Error ? error.message : String(error)}`
|
|
1449
|
+
}],
|
|
1450
|
+
isError: true
|
|
1451
|
+
};
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
);
|
|
1455
|
+
server2.registerTool(
|
|
1456
|
+
"ckan_group_search",
|
|
1457
|
+
{
|
|
1458
|
+
title: "Search CKAN Groups by Name",
|
|
1459
|
+
description: `Search for groups by name pattern.
|
|
1460
|
+
|
|
1461
|
+
This tool provides a simpler interface than package_search for finding groups.
|
|
1462
|
+
Wildcards are automatically added around the search pattern.
|
|
1463
|
+
|
|
1464
|
+
Args:
|
|
1465
|
+
- server_url (string): Base URL of CKAN server
|
|
1466
|
+
- pattern (string): Search pattern (e.g., "energia", "salute")
|
|
1467
|
+
- response_format ('markdown' | 'json'): Output format
|
|
1468
|
+
|
|
1469
|
+
Returns:
|
|
1470
|
+
List of matching groups with dataset counts`,
|
|
1471
|
+
inputSchema: z7.object({
|
|
1472
|
+
server_url: z7.string().url(),
|
|
1473
|
+
pattern: z7.string().min(1).describe("Search pattern (wildcards added automatically)"),
|
|
1474
|
+
response_format: ResponseFormatSchema
|
|
1475
|
+
}).strict(),
|
|
1476
|
+
annotations: {
|
|
1477
|
+
readOnlyHint: true,
|
|
1478
|
+
destructiveHint: false,
|
|
1479
|
+
idempotentHint: true,
|
|
1480
|
+
openWorldHint: true
|
|
1481
|
+
}
|
|
1482
|
+
},
|
|
1483
|
+
async (params) => {
|
|
1484
|
+
try {
|
|
1485
|
+
const query = `groups:*${params.pattern}*`;
|
|
1486
|
+
const result = await makeCkanRequest(
|
|
1487
|
+
params.server_url,
|
|
1488
|
+
"package_search",
|
|
1489
|
+
{
|
|
1490
|
+
q: query,
|
|
1491
|
+
rows: 0,
|
|
1492
|
+
"facet.field": JSON.stringify(["groups"]),
|
|
1493
|
+
"facet.limit": 500
|
|
1494
|
+
}
|
|
1495
|
+
);
|
|
1496
|
+
const groupFacets = normalizeGroupFacets(result);
|
|
1497
|
+
const totalDatasets = result.count || 0;
|
|
1498
|
+
if (params.response_format === "json" /* JSON */) {
|
|
1499
|
+
const jsonResult = {
|
|
1500
|
+
count: groupFacets.length,
|
|
1501
|
+
total_datasets: totalDatasets,
|
|
1502
|
+
groups: groupFacets.map((group) => ({
|
|
1503
|
+
name: group.name,
|
|
1504
|
+
display_name: group.display_name,
|
|
1505
|
+
dataset_count: group.count
|
|
1506
|
+
}))
|
|
1507
|
+
};
|
|
1508
|
+
return {
|
|
1509
|
+
content: [{ type: "text", text: truncateText(JSON.stringify(jsonResult, null, 2)) }],
|
|
1510
|
+
structuredContent: jsonResult
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
let markdown = `# CKAN Group Search Results
|
|
1514
|
+
|
|
1515
|
+
`;
|
|
1516
|
+
markdown += `**Server**: ${params.server_url}
|
|
1517
|
+
`;
|
|
1518
|
+
markdown += `**Pattern**: "${params.pattern}"
|
|
1519
|
+
`;
|
|
1520
|
+
markdown += `**Groups Found**: ${groupFacets.length}
|
|
1521
|
+
`;
|
|
1522
|
+
markdown += `**Total Datasets**: ${totalDatasets}
|
|
1523
|
+
|
|
1524
|
+
`;
|
|
1525
|
+
if (groupFacets.length === 0) {
|
|
1526
|
+
markdown += `No groups found matching pattern "${params.pattern}".
|
|
1527
|
+
`;
|
|
1528
|
+
} else {
|
|
1529
|
+
markdown += `## Matching Groups
|
|
1530
|
+
|
|
1531
|
+
`;
|
|
1532
|
+
markdown += `| Group | Datasets |
|
|
1533
|
+
`;
|
|
1534
|
+
markdown += `|-------|----------|
|
|
1535
|
+
`;
|
|
1536
|
+
for (const group of groupFacets) {
|
|
1537
|
+
markdown += `| ${group.display_name || group.name} | ${group.count} |
|
|
1538
|
+
`;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
return {
|
|
1542
|
+
content: [{ type: "text", text: truncateText(markdown) }]
|
|
1543
|
+
};
|
|
1544
|
+
} catch (error) {
|
|
1545
|
+
return {
|
|
1546
|
+
content: [{
|
|
1547
|
+
type: "text",
|
|
1548
|
+
text: `Error searching groups: ${error instanceof Error ? error.message : String(error)}`
|
|
1549
|
+
}],
|
|
1550
|
+
isError: true
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1062
1557
|
// src/resources/dataset.ts
|
|
1063
1558
|
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1064
1559
|
|
|
@@ -1232,7 +1727,7 @@ function registerAllResources(server2) {
|
|
|
1232
1727
|
function createServer() {
|
|
1233
1728
|
return new McpServer({
|
|
1234
1729
|
name: "ckan-mcp-server",
|
|
1235
|
-
version: "0.4.
|
|
1730
|
+
version: "0.4.3"
|
|
1236
1731
|
});
|
|
1237
1732
|
}
|
|
1238
1733
|
function registerAll(server2) {
|
|
@@ -1240,6 +1735,8 @@ function registerAll(server2) {
|
|
|
1240
1735
|
registerOrganizationTools(server2);
|
|
1241
1736
|
registerDatastoreTools(server2);
|
|
1242
1737
|
registerStatusTools(server2);
|
|
1738
|
+
registerTagTools(server2);
|
|
1739
|
+
registerGroupTools(server2);
|
|
1243
1740
|
registerAllResources(server2);
|
|
1244
1741
|
}
|
|
1245
1742
|
|