@aborruso/ckan-mcp-server 0.4.51 → 0.4.53
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 +13 -0
- package/dist/index.js +335 -32
- package/dist/worker.js +150 -145
- package/package.json +1 -1
package/LOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# LOG
|
|
2
2
|
|
|
3
|
+
## 2026-02-28 (v0.4.53)
|
|
4
|
+
|
|
5
|
+
- feat: new tool `ckan_analyze_datasets` — search + DataStore schema introspection in one call; includes `info.label`/`info.notes` from DataStore Dictionary when available
|
|
6
|
+
- feat: new tool `ckan_catalog_stats` — portal overview (total datasets, categories, formats, organizations) via single faceted query
|
|
7
|
+
- refactor: `quality.ts` tools migrated from deprecated `server.tool()` to `registerTool()` with full annotations
|
|
8
|
+
- tests: 287 passing (+15 new tests)
|
|
9
|
+
|
|
10
|
+
## 2026-02-28 (v0.4.52)
|
|
11
|
+
|
|
12
|
+
- fix: HTTP transport — `/.well-known/oauth-authorization-server` now returns JSON 404 instead of HTML; fixes Claude Code HTTP transport connection failure
|
|
13
|
+
- fix: `ckan_datastore_search` — `limit` min changed 1→0; allows column discovery without fetching data
|
|
14
|
+
- docs: `ckan_datastore_search` description updated — fields always returned, `limit=0` pattern documented
|
|
15
|
+
|
|
3
16
|
## 2026-02-27 (v0.4.51)
|
|
4
17
|
|
|
5
18
|
- refactor: domain types for all tool files — `CkanTag`, `CkanResource`, `CkanPackage`, `CkanOrganization`, `CkanField`, `CkanDatastoreResult` in `src/types.ts`; `any` reduced 32 → 1
|
package/dist/index.js
CHANGED
|
@@ -2043,6 +2043,10 @@ function registerDatastoreTools(server2) {
|
|
|
2043
2043
|
|
|
2044
2044
|
The DataStore allows SQL-like queries on tabular data. Not all resources have DataStore enabled.
|
|
2045
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
|
+
|
|
2046
2050
|
Args:
|
|
2047
2051
|
- server_url (string): Base URL of CKAN server
|
|
2048
2052
|
- resource_id (string): ID of the DataStore resource
|
|
@@ -2056,20 +2060,21 @@ Args:
|
|
|
2056
2060
|
- response_format ('markdown' | 'json'): Output format
|
|
2057
2061
|
|
|
2058
2062
|
Returns:
|
|
2059
|
-
DataStore records matching query
|
|
2063
|
+
DataStore records matching query, always including available column names and types
|
|
2060
2064
|
|
|
2061
2065
|
Examples:
|
|
2066
|
+
- { server_url: "...", resource_id: "abc-123", limit: 0 } \u2190 discover columns first
|
|
2062
2067
|
- { server_url: "...", resource_id: "abc-123", limit: 50 }
|
|
2063
2068
|
- { server_url: "...", resource_id: "...", filters: { "regione": "Sicilia" } }
|
|
2064
2069
|
- { server_url: "...", resource_id: "...", sort: "anno desc", limit: 100 }
|
|
2065
2070
|
|
|
2066
|
-
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)`,
|
|
2067
2072
|
inputSchema: z4.object({
|
|
2068
2073
|
server_url: z4.string().url().describe("Base URL of the CKAN server (e.g., https://dati.gov.it/opendata)"),
|
|
2069
2074
|
resource_id: z4.string().min(1).describe("UUID of the DataStore resource (from ckan_package_show resource.id where datastore_active is true)"),
|
|
2070
2075
|
q: z4.string().optional().describe("Full-text search across all fields"),
|
|
2071
2076
|
filters: z4.record(z4.any()).optional().describe('Key-value filters for exact matches (e.g., { "regione": "Sicilia", "anno": 2023 })'),
|
|
2072
|
-
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"),
|
|
2073
2078
|
offset: z4.number().int().min(0).optional().default(0).describe("Pagination offset"),
|
|
2074
2079
|
fields: z4.array(z4.string()).optional().describe("Specific field names to return; omit to return all fields"),
|
|
2075
2080
|
sort: z4.string().optional().describe("Sort expression (e.g., 'anno desc', 'nome asc')"),
|
|
@@ -3383,13 +3388,22 @@ function formatQualityDetailsMarkdown(data, datasetId) {
|
|
|
3383
3388
|
return lines.join("\n");
|
|
3384
3389
|
}
|
|
3385
3390
|
function registerQualityTools(server2) {
|
|
3386
|
-
server2.
|
|
3391
|
+
server2.registerTool(
|
|
3387
3392
|
"ckan_get_mqa_quality",
|
|
3388
|
-
"Get MQA (Metadata Quality Assurance) quality metrics for a dataset on dati.gov.it. Returns quality score and detailed metrics (accessibility, reusability, interoperability, findability, contextuality) from data.europa.eu. Only works with dati.gov.it server. Typical workflow: ckan_package_show (get dataset ID) \u2192 ckan_get_mqa_quality \u2192 ckan_get_mqa_quality_details (for non-max dimensions)",
|
|
3389
3393
|
{
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3394
|
+
title: "Get MQA Quality Score",
|
|
3395
|
+
description: "Get MQA (Metadata Quality Assurance) quality metrics for a dataset on dati.gov.it. Returns quality score and detailed metrics (accessibility, reusability, interoperability, findability, contextuality) from data.europa.eu. Only works with dati.gov.it server. Typical workflow: ckan_package_show (get dataset ID) \u2192 ckan_get_mqa_quality \u2192 ckan_get_mqa_quality_details (for non-max dimensions)",
|
|
3396
|
+
inputSchema: z8.object({
|
|
3397
|
+
server_url: z8.string().url().describe("Base URL of dati.gov.it (e.g., https://www.dati.gov.it/opendata)"),
|
|
3398
|
+
dataset_id: z8.string().describe("Dataset ID or name"),
|
|
3399
|
+
response_format: ResponseFormatSchema.optional()
|
|
3400
|
+
}).strict(),
|
|
3401
|
+
annotations: {
|
|
3402
|
+
readOnlyHint: true,
|
|
3403
|
+
destructiveHint: false,
|
|
3404
|
+
idempotentHint: true,
|
|
3405
|
+
openWorldHint: true
|
|
3406
|
+
}
|
|
3393
3407
|
},
|
|
3394
3408
|
async ({ server_url, dataset_id, response_format }) => {
|
|
3395
3409
|
if (!isValidMqaServer(server_url)) {
|
|
@@ -3423,13 +3437,22 @@ The MQA (Metadata Quality Assurance) system is operated by data.europa.eu and on
|
|
|
3423
3437
|
}
|
|
3424
3438
|
}
|
|
3425
3439
|
);
|
|
3426
|
-
server2.
|
|
3440
|
+
server2.registerTool(
|
|
3427
3441
|
"ckan_get_mqa_quality_details",
|
|
3428
|
-
"Get detailed MQA (Metadata Quality Assurance) quality reasons for a dataset on dati.gov.it. Returns dimension scores, non-max reasons, and raw MQA flags from data.europa.eu. Only works with dati.gov.it server. Typical workflow: ckan_get_mqa_quality (get overview scores) \u2192 ckan_get_mqa_quality_details (inspect failing metrics)",
|
|
3429
3442
|
{
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3443
|
+
title: "Get MQA Quality Details",
|
|
3444
|
+
description: "Get detailed MQA (Metadata Quality Assurance) quality reasons for a dataset on dati.gov.it. Returns dimension scores, non-max reasons, and raw MQA flags from data.europa.eu. Only works with dati.gov.it server. Typical workflow: ckan_get_mqa_quality (get overview scores) \u2192 ckan_get_mqa_quality_details (inspect failing metrics)",
|
|
3445
|
+
inputSchema: z8.object({
|
|
3446
|
+
server_url: z8.string().url().describe("Base URL of dati.gov.it (e.g., https://www.dati.gov.it/opendata)"),
|
|
3447
|
+
dataset_id: z8.string().describe("Dataset ID or name"),
|
|
3448
|
+
response_format: ResponseFormatSchema.optional()
|
|
3449
|
+
}).strict(),
|
|
3450
|
+
annotations: {
|
|
3451
|
+
readOnlyHint: true,
|
|
3452
|
+
destructiveHint: false,
|
|
3453
|
+
idempotentHint: true,
|
|
3454
|
+
openWorldHint: true
|
|
3455
|
+
}
|
|
3433
3456
|
},
|
|
3434
3457
|
async ({ server_url, dataset_id, response_format }) => {
|
|
3435
3458
|
if (!isValidMqaServer(server_url)) {
|
|
@@ -3465,6 +3488,281 @@ The MQA (Metadata Quality Assurance) system is operated by data.europa.eu and on
|
|
|
3465
3488
|
);
|
|
3466
3489
|
}
|
|
3467
3490
|
|
|
3491
|
+
// src/tools/analyze.ts
|
|
3492
|
+
import { z as z9 } from "zod";
|
|
3493
|
+
function formatAnalyzeDatasetsMarkdown(serverUrl, query, total, datasets) {
|
|
3494
|
+
let md = `# Dataset Analysis
|
|
3495
|
+
|
|
3496
|
+
`;
|
|
3497
|
+
md += `**Server**: ${serverUrl}
|
|
3498
|
+
`;
|
|
3499
|
+
md += `**Query**: ${query}
|
|
3500
|
+
`;
|
|
3501
|
+
md += `**Total datasets found**: ${total}
|
|
3502
|
+
`;
|
|
3503
|
+
md += `**Datasets analyzed**: ${datasets.length}
|
|
3504
|
+
|
|
3505
|
+
`;
|
|
3506
|
+
for (const { dataset, datastoreResources, nonDatastoreResources } of datasets) {
|
|
3507
|
+
md += `---
|
|
3508
|
+
|
|
3509
|
+
`;
|
|
3510
|
+
md += `## ${dataset.title || dataset.name}
|
|
3511
|
+
|
|
3512
|
+
`;
|
|
3513
|
+
md += `- **ID**: \`${dataset.id}\`
|
|
3514
|
+
`;
|
|
3515
|
+
md += `- **Name**: \`${dataset.name}\`
|
|
3516
|
+
`;
|
|
3517
|
+
if (dataset.organization) {
|
|
3518
|
+
md += `- **Organization**: ${dataset.organization.title || dataset.organization.name}
|
|
3519
|
+
`;
|
|
3520
|
+
}
|
|
3521
|
+
if (datastoreResources.length > 0) {
|
|
3522
|
+
md += `
|
|
3523
|
+
### DataStore Resources
|
|
3524
|
+
|
|
3525
|
+
`;
|
|
3526
|
+
for (const { resource, schema, error } of datastoreResources) {
|
|
3527
|
+
md += `#### ${resource.name || resource.id}
|
|
3528
|
+
|
|
3529
|
+
`;
|
|
3530
|
+
md += `- **Resource ID**: \`${resource.id}\`
|
|
3531
|
+
`;
|
|
3532
|
+
if (resource.format) md += `- **Format**: ${resource.format}
|
|
3533
|
+
`;
|
|
3534
|
+
if (error) {
|
|
3535
|
+
md += `- **Error**: ${error}
|
|
3536
|
+
`;
|
|
3537
|
+
} else if (schema) {
|
|
3538
|
+
md += `- **Total Records**: ${schema.total}
|
|
3539
|
+
`;
|
|
3540
|
+
const fields = schema.fields.filter((f) => f.id !== "_id");
|
|
3541
|
+
if (fields.length > 0) {
|
|
3542
|
+
md += `
|
|
3543
|
+
**Fields** (${fields.length}):
|
|
3544
|
+
|
|
3545
|
+
`;
|
|
3546
|
+
for (const f of fields) {
|
|
3547
|
+
let line = `- \`${f.id}\` (${f.type})`;
|
|
3548
|
+
if (f.info?.label) line += ` \u2014 ${f.info.label}`;
|
|
3549
|
+
if (f.info?.notes) line += `: ${f.info.notes}`;
|
|
3550
|
+
md += line + "\n";
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
md += "\n";
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
if (nonDatastoreResources.length > 0) {
|
|
3558
|
+
md += `### Other Resources (not queryable)
|
|
3559
|
+
|
|
3560
|
+
`;
|
|
3561
|
+
for (const r of nonDatastoreResources) {
|
|
3562
|
+
md += `- ${r.name || "(unnamed)"}${r.format ? ` (${r.format})` : ""}
|
|
3563
|
+
`;
|
|
3564
|
+
}
|
|
3565
|
+
md += "\n";
|
|
3566
|
+
}
|
|
3567
|
+
if (datastoreResources.length === 0 && nonDatastoreResources.length === 0) {
|
|
3568
|
+
md += `_No resources available._
|
|
3569
|
+
|
|
3570
|
+
`;
|
|
3571
|
+
}
|
|
3572
|
+
}
|
|
3573
|
+
return md;
|
|
3574
|
+
}
|
|
3575
|
+
function registerAnalyzeTools(server2) {
|
|
3576
|
+
server2.registerTool(
|
|
3577
|
+
"ckan_analyze_datasets",
|
|
3578
|
+
{
|
|
3579
|
+
title: "Analyze CKAN Datasets and DataStore Schema",
|
|
3580
|
+
description: `Search datasets and inspect the DataStore schema of queryable resources.
|
|
3581
|
+
|
|
3582
|
+
For each dataset found, lists all resources. For DataStore-enabled resources, fetches the full
|
|
3583
|
+
field schema (name, type, and label/notes when available) plus total record count \u2014 all in one call.
|
|
3584
|
+
|
|
3585
|
+
Use this before ckan_datastore_search to understand what fields are available and what data to expect.
|
|
3586
|
+
|
|
3587
|
+
Args:
|
|
3588
|
+
- server_url (string): Base URL of CKAN server
|
|
3589
|
+
- q (string): Solr search query (e.g. "incidenti", "title:ambiente")
|
|
3590
|
+
- rows (number): Max datasets to analyze (default 5, max 20)
|
|
3591
|
+
- response_format ('markdown' | 'json'): Output format
|
|
3592
|
+
|
|
3593
|
+
Returns:
|
|
3594
|
+
For each dataset: title, ID, organization, and per DataStore resource: field schema with
|
|
3595
|
+
label/notes (when available from DataStore Dictionary) and record count.
|
|
3596
|
+
|
|
3597
|
+
Typical workflow: ckan_analyze_datasets \u2192 ckan_datastore_search (with known field names)`,
|
|
3598
|
+
inputSchema: z9.object({
|
|
3599
|
+
server_url: z9.string().url().describe("Base URL of the CKAN server (e.g., https://dati.comune.messina.it)"),
|
|
3600
|
+
q: z9.string().min(1).describe("Solr search query (e.g. 'incidenti', 'title:ambiente')"),
|
|
3601
|
+
rows: z9.number().int().min(1).max(20).optional().default(5).describe("Max datasets to analyze (default 5, max 20)"),
|
|
3602
|
+
response_format: ResponseFormatSchema
|
|
3603
|
+
}).strict(),
|
|
3604
|
+
annotations: {
|
|
3605
|
+
readOnlyHint: true,
|
|
3606
|
+
destructiveHint: false,
|
|
3607
|
+
idempotentHint: true,
|
|
3608
|
+
openWorldHint: true
|
|
3609
|
+
}
|
|
3610
|
+
},
|
|
3611
|
+
async (params) => {
|
|
3612
|
+
try {
|
|
3613
|
+
const searchResult = await makeCkanRequest(
|
|
3614
|
+
params.server_url,
|
|
3615
|
+
"package_search",
|
|
3616
|
+
{ q: params.q, rows: params.rows }
|
|
3617
|
+
);
|
|
3618
|
+
const datasets = searchResult.results || [];
|
|
3619
|
+
const analyzed = await Promise.all(datasets.map(async (dataset) => {
|
|
3620
|
+
const resources = dataset.resources || [];
|
|
3621
|
+
const datastoreResources = [];
|
|
3622
|
+
const nonDatastoreResources = [];
|
|
3623
|
+
for (const resource of resources) {
|
|
3624
|
+
if (resource.datastore_active === true) {
|
|
3625
|
+
try {
|
|
3626
|
+
const schema = await makeCkanRequest(
|
|
3627
|
+
params.server_url,
|
|
3628
|
+
"datastore_search",
|
|
3629
|
+
{ resource_id: resource.id, limit: 0 }
|
|
3630
|
+
);
|
|
3631
|
+
datastoreResources.push({
|
|
3632
|
+
resource: { id: resource.id, name: resource.name, format: resource.format },
|
|
3633
|
+
schema
|
|
3634
|
+
});
|
|
3635
|
+
} catch (err) {
|
|
3636
|
+
datastoreResources.push({
|
|
3637
|
+
resource: { id: resource.id, name: resource.name, format: resource.format },
|
|
3638
|
+
schema: null,
|
|
3639
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3640
|
+
});
|
|
3641
|
+
}
|
|
3642
|
+
} else {
|
|
3643
|
+
nonDatastoreResources.push({ name: resource.name, format: resource.format });
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
return { dataset, datastoreResources, nonDatastoreResources };
|
|
3647
|
+
}));
|
|
3648
|
+
if (params.response_format === "json" /* JSON */) {
|
|
3649
|
+
return {
|
|
3650
|
+
content: [{ type: "text", text: truncateText(JSON.stringify({ total: searchResult.count, datasets: analyzed }, null, 2)) }]
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
const markdown = formatAnalyzeDatasetsMarkdown(
|
|
3654
|
+
params.server_url,
|
|
3655
|
+
params.q,
|
|
3656
|
+
searchResult.count,
|
|
3657
|
+
analyzed
|
|
3658
|
+
);
|
|
3659
|
+
return {
|
|
3660
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
3661
|
+
};
|
|
3662
|
+
} catch (error) {
|
|
3663
|
+
return {
|
|
3664
|
+
content: [{
|
|
3665
|
+
type: "text",
|
|
3666
|
+
text: `Error analyzing datasets: ${error instanceof Error ? error.message : String(error)}`
|
|
3667
|
+
}],
|
|
3668
|
+
isError: true
|
|
3669
|
+
};
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
);
|
|
3673
|
+
}
|
|
3674
|
+
function formatCatalogStatsMarkdown(serverUrl, total, facets) {
|
|
3675
|
+
const LABELS = {
|
|
3676
|
+
groups: "Categories",
|
|
3677
|
+
res_format: "Formats",
|
|
3678
|
+
organization: "Organizations"
|
|
3679
|
+
};
|
|
3680
|
+
let md = `# CKAN Portal Statistics
|
|
3681
|
+
|
|
3682
|
+
`;
|
|
3683
|
+
md += `**Server**: ${serverUrl}
|
|
3684
|
+
`;
|
|
3685
|
+
md += `**Total datasets**: ${total}
|
|
3686
|
+
`;
|
|
3687
|
+
for (const [field, label] of Object.entries(LABELS)) {
|
|
3688
|
+
const values = facets[field];
|
|
3689
|
+
if (!values || Object.keys(values).length === 0) continue;
|
|
3690
|
+
const sorted = Object.entries(values).sort((a, b) => b[1] - a[1]);
|
|
3691
|
+
md += `
|
|
3692
|
+
## ${label}
|
|
3693
|
+
|
|
3694
|
+
`;
|
|
3695
|
+
for (const [name, count] of sorted) {
|
|
3696
|
+
md += `- **${name}**: ${count}
|
|
3697
|
+
`;
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
return md;
|
|
3701
|
+
}
|
|
3702
|
+
function registerCatalogStatsTools(server2) {
|
|
3703
|
+
server2.registerTool(
|
|
3704
|
+
"ckan_catalog_stats",
|
|
3705
|
+
{
|
|
3706
|
+
title: "Get CKAN Portal Statistics",
|
|
3707
|
+
description: `Get a statistical overview of a CKAN portal: total dataset count and breakdown by category, format, and organization.
|
|
3708
|
+
|
|
3709
|
+
Single CKAN call (package_search with rows=0 and facets). No query needed.
|
|
3710
|
+
|
|
3711
|
+
Args:
|
|
3712
|
+
- server_url (string): Base URL of the CKAN server
|
|
3713
|
+
- facet_limit (number): Max entries per facet section (default 20)
|
|
3714
|
+
- response_format ('markdown' | 'json'): Output format
|
|
3715
|
+
|
|
3716
|
+
Returns:
|
|
3717
|
+
Total dataset count, categories ranked by count, file formats ranked by count, organizations ranked by count.
|
|
3718
|
+
|
|
3719
|
+
Typical workflow: ckan_catalog_stats (understand the portal) \u2192 ckan_package_search (query specific data)`,
|
|
3720
|
+
inputSchema: z9.object({
|
|
3721
|
+
server_url: z9.string().url().describe("Base URL of the CKAN server (e.g., https://dati.comune.messina.it)"),
|
|
3722
|
+
facet_limit: z9.number().int().min(1).max(100).optional().default(20).describe("Max entries per facet section (default 20)"),
|
|
3723
|
+
response_format: ResponseFormatSchema
|
|
3724
|
+
}).strict(),
|
|
3725
|
+
annotations: {
|
|
3726
|
+
readOnlyHint: true,
|
|
3727
|
+
destructiveHint: false,
|
|
3728
|
+
idempotentHint: true,
|
|
3729
|
+
openWorldHint: false
|
|
3730
|
+
}
|
|
3731
|
+
},
|
|
3732
|
+
async (params) => {
|
|
3733
|
+
try {
|
|
3734
|
+
const result = await makeCkanRequest(
|
|
3735
|
+
params.server_url,
|
|
3736
|
+
"package_search",
|
|
3737
|
+
{
|
|
3738
|
+
q: "*:*",
|
|
3739
|
+
rows: 0,
|
|
3740
|
+
"facet.field": JSON.stringify(["groups", "res_format", "organization"]),
|
|
3741
|
+
"facet.limit": params.facet_limit
|
|
3742
|
+
}
|
|
3743
|
+
);
|
|
3744
|
+
if (params.response_format === "json" /* JSON */) {
|
|
3745
|
+
return {
|
|
3746
|
+
content: [{ type: "text", text: truncateText(JSON.stringify({ total: result.count, facets: result.facets }, null, 2)) }]
|
|
3747
|
+
};
|
|
3748
|
+
}
|
|
3749
|
+
const markdown = formatCatalogStatsMarkdown(params.server_url, result.count, result.facets);
|
|
3750
|
+
return {
|
|
3751
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
3752
|
+
};
|
|
3753
|
+
} catch (error) {
|
|
3754
|
+
return {
|
|
3755
|
+
content: [{
|
|
3756
|
+
type: "text",
|
|
3757
|
+
text: `Error retrieving catalog stats: ${error instanceof Error ? error.message : String(error)}`
|
|
3758
|
+
}],
|
|
3759
|
+
isError: true
|
|
3760
|
+
};
|
|
3761
|
+
}
|
|
3762
|
+
}
|
|
3763
|
+
);
|
|
3764
|
+
}
|
|
3765
|
+
|
|
3468
3766
|
// src/resources/dataset.ts
|
|
3469
3767
|
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3470
3768
|
|
|
@@ -3734,7 +4032,7 @@ function registerAllResources(server2) {
|
|
|
3734
4032
|
}
|
|
3735
4033
|
|
|
3736
4034
|
// src/prompts/theme.ts
|
|
3737
|
-
import { z as
|
|
4035
|
+
import { z as z10 } from "zod";
|
|
3738
4036
|
|
|
3739
4037
|
// src/prompts/types.ts
|
|
3740
4038
|
var createTextPrompt = (text) => ({
|
|
@@ -3795,9 +4093,9 @@ var registerThemePrompt = (server2) => {
|
|
|
3795
4093
|
title: "Search datasets by theme",
|
|
3796
4094
|
description: "Guided prompt to discover a theme and search datasets under it.",
|
|
3797
4095
|
argsSchema: {
|
|
3798
|
-
server_url:
|
|
3799
|
-
theme:
|
|
3800
|
-
rows:
|
|
4096
|
+
server_url: z10.string().url().describe("Base URL of the CKAN server"),
|
|
4097
|
+
theme: z10.string().min(1).describe("Theme or group name to search"),
|
|
4098
|
+
rows: z10.coerce.number().int().positive().default(10).describe("Max results to return")
|
|
3801
4099
|
}
|
|
3802
4100
|
},
|
|
3803
4101
|
async ({ server_url, theme, rows }) => createTextPrompt(buildThemePromptText(server_url, theme, rows))
|
|
@@ -3805,7 +4103,7 @@ var registerThemePrompt = (server2) => {
|
|
|
3805
4103
|
};
|
|
3806
4104
|
|
|
3807
4105
|
// src/prompts/organization.ts
|
|
3808
|
-
import { z as
|
|
4106
|
+
import { z as z11 } from "zod";
|
|
3809
4107
|
var ORGANIZATION_PROMPT_NAME = "ckan-search-by-organization";
|
|
3810
4108
|
var buildOrganizationPromptText = (serverUrl, organization, rows) => `# Guided search: datasets by organization
|
|
3811
4109
|
|
|
@@ -3840,9 +4138,9 @@ var registerOrganizationPrompt = (server2) => {
|
|
|
3840
4138
|
title: "Search datasets by organization",
|
|
3841
4139
|
description: "Guided prompt to find a publisher and list its datasets.",
|
|
3842
4140
|
argsSchema: {
|
|
3843
|
-
server_url:
|
|
3844
|
-
organization:
|
|
3845
|
-
rows:
|
|
4141
|
+
server_url: z11.string().url().describe("Base URL of the CKAN server"),
|
|
4142
|
+
organization: z11.string().min(1).describe("Organization name or keyword"),
|
|
4143
|
+
rows: z11.coerce.number().int().positive().default(10).describe("Max results to return")
|
|
3846
4144
|
}
|
|
3847
4145
|
},
|
|
3848
4146
|
async ({ server_url, organization, rows }) => createTextPrompt(buildOrganizationPromptText(server_url, organization, rows))
|
|
@@ -3850,7 +4148,7 @@ var registerOrganizationPrompt = (server2) => {
|
|
|
3850
4148
|
};
|
|
3851
4149
|
|
|
3852
4150
|
// src/prompts/format.ts
|
|
3853
|
-
import { z as
|
|
4151
|
+
import { z as z12 } from "zod";
|
|
3854
4152
|
var FORMAT_PROMPT_NAME = "ckan-search-by-format";
|
|
3855
4153
|
var buildFormatPromptText = (serverUrl, format, rows) => `# Guided search: datasets by resource format
|
|
3856
4154
|
|
|
@@ -3874,9 +4172,9 @@ var registerFormatPrompt = (server2) => {
|
|
|
3874
4172
|
title: "Search datasets by resource format",
|
|
3875
4173
|
description: "Guided prompt to find datasets with a given resource format.",
|
|
3876
4174
|
argsSchema: {
|
|
3877
|
-
server_url:
|
|
3878
|
-
format:
|
|
3879
|
-
rows:
|
|
4175
|
+
server_url: z12.string().url().describe("Base URL of the CKAN server"),
|
|
4176
|
+
format: z12.string().min(1).describe("Resource format (e.g., CSV, JSON)"),
|
|
4177
|
+
rows: z12.coerce.number().int().positive().default(10).describe("Max results to return")
|
|
3880
4178
|
}
|
|
3881
4179
|
},
|
|
3882
4180
|
async ({ server_url, format, rows }) => createTextPrompt(buildFormatPromptText(server_url, format, rows))
|
|
@@ -3884,7 +4182,7 @@ var registerFormatPrompt = (server2) => {
|
|
|
3884
4182
|
};
|
|
3885
4183
|
|
|
3886
4184
|
// src/prompts/recent.ts
|
|
3887
|
-
import { z as
|
|
4185
|
+
import { z as z13 } from "zod";
|
|
3888
4186
|
var RECENT_PROMPT_NAME = "ckan-recent-datasets";
|
|
3889
4187
|
var buildRecentPromptText = (serverUrl, rows) => `# Guided search: recent datasets
|
|
3890
4188
|
|
|
@@ -3932,8 +4230,8 @@ var registerRecentPrompt = (server2) => {
|
|
|
3932
4230
|
title: "Find recently updated datasets",
|
|
3933
4231
|
description: "Guided prompt to list recently updated datasets on a CKAN portal.",
|
|
3934
4232
|
argsSchema: {
|
|
3935
|
-
server_url:
|
|
3936
|
-
rows:
|
|
4233
|
+
server_url: z13.string().url().describe("Base URL of the CKAN server"),
|
|
4234
|
+
rows: z13.coerce.number().int().positive().default(10).describe("Max results to return")
|
|
3937
4235
|
}
|
|
3938
4236
|
},
|
|
3939
4237
|
async ({ server_url, rows }) => createTextPrompt(buildRecentPromptText(server_url, rows))
|
|
@@ -3941,7 +4239,7 @@ var registerRecentPrompt = (server2) => {
|
|
|
3941
4239
|
};
|
|
3942
4240
|
|
|
3943
4241
|
// src/prompts/dataset-analysis.ts
|
|
3944
|
-
import { z as
|
|
4242
|
+
import { z as z14 } from "zod";
|
|
3945
4243
|
var DATASET_ANALYSIS_PROMPT_NAME = "ckan-analyze-dataset";
|
|
3946
4244
|
var buildDatasetAnalysisPromptText = (serverUrl, id) => `# Guided analysis: dataset
|
|
3947
4245
|
|
|
@@ -3983,8 +4281,8 @@ var registerDatasetAnalysisPrompt = (server2) => {
|
|
|
3983
4281
|
title: "Analyze a dataset",
|
|
3984
4282
|
description: "Guided prompt to inspect dataset metadata and explore DataStore tables.",
|
|
3985
4283
|
argsSchema: {
|
|
3986
|
-
server_url:
|
|
3987
|
-
id:
|
|
4284
|
+
server_url: z14.string().url().describe("Base URL of the CKAN server"),
|
|
4285
|
+
id: z14.string().min(1).describe("Dataset id or name (CKAN package id)")
|
|
3988
4286
|
}
|
|
3989
4287
|
},
|
|
3990
4288
|
async ({ server_url, id }) => createTextPrompt(buildDatasetAnalysisPromptText(server_url, id))
|
|
@@ -4004,7 +4302,7 @@ var registerAllPrompts = (server2) => {
|
|
|
4004
4302
|
function createServer() {
|
|
4005
4303
|
return new McpServer({
|
|
4006
4304
|
name: "ckan-mcp-server",
|
|
4007
|
-
version: "0.4.
|
|
4305
|
+
version: "0.4.53"
|
|
4008
4306
|
});
|
|
4009
4307
|
}
|
|
4010
4308
|
function registerAll(server2) {
|
|
@@ -4015,6 +4313,8 @@ function registerAll(server2) {
|
|
|
4015
4313
|
registerTagTools(server2);
|
|
4016
4314
|
registerGroupTools(server2);
|
|
4017
4315
|
registerQualityTools(server2);
|
|
4316
|
+
registerAnalyzeTools(server2);
|
|
4317
|
+
registerCatalogStatsTools(server2);
|
|
4018
4318
|
registerAllResources(server2);
|
|
4019
4319
|
registerAllPrompts(server2);
|
|
4020
4320
|
}
|
|
@@ -4038,6 +4338,9 @@ async function runHTTP(server2) {
|
|
|
4038
4338
|
enableJsonResponse: true
|
|
4039
4339
|
});
|
|
4040
4340
|
await server2.connect(transport2);
|
|
4341
|
+
app.get("/.well-known/oauth-authorization-server", (_req, res) => {
|
|
4342
|
+
res.status(404).json({ error: "authorization_not_supported", error_description: "This server does not require authentication" });
|
|
4343
|
+
});
|
|
4041
4344
|
app.post("/mcp", async (req, res) => {
|
|
4042
4345
|
await transport2.handleRequest(req, res, req.body);
|
|
4043
4346
|
});
|