@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.
Files changed (4) hide show
  1. package/LOG.md +13 -0
  2. package/dist/index.js +335 -32
  3. package/dist/worker.js +150 -145
  4. 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(1).max(32e3).optional().default(100).describe("Max rows to return (default 100, max 32000)"),
2077
+ limit: z4.number().int().min(0).max(32e3).optional().default(100).describe("Max rows to return (default 100, max 32000); use 0 to get only column names without data"),
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.tool(
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
- server_url: z8.string().url().describe("Base URL of dati.gov.it (e.g., https://www.dati.gov.it/opendata)"),
3391
- dataset_id: z8.string().describe("Dataset ID or name"),
3392
- response_format: ResponseFormatSchema.optional()
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.tool(
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
- server_url: z8.string().url().describe("Base URL of dati.gov.it (e.g., https://www.dati.gov.it/opendata)"),
3431
- dataset_id: z8.string().describe("Dataset ID or name"),
3432
- response_format: ResponseFormatSchema.optional()
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 z9 } from "zod";
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: z9.string().url().describe("Base URL of the CKAN server"),
3799
- theme: z9.string().min(1).describe("Theme or group name to search"),
3800
- rows: z9.coerce.number().int().positive().default(10).describe("Max results to return")
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 z10 } from "zod";
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: z10.string().url().describe("Base URL of the CKAN server"),
3844
- organization: z10.string().min(1).describe("Organization name or keyword"),
3845
- rows: z10.coerce.number().int().positive().default(10).describe("Max results to return")
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 z11 } from "zod";
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: z11.string().url().describe("Base URL of the CKAN server"),
3878
- format: z11.string().min(1).describe("Resource format (e.g., CSV, JSON)"),
3879
- rows: z11.coerce.number().int().positive().default(10).describe("Max results to return")
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 z12 } from "zod";
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: z12.string().url().describe("Base URL of the CKAN server"),
3936
- rows: z12.coerce.number().int().positive().default(10).describe("Max results to return")
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 z13 } from "zod";
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: z13.string().url().describe("Base URL of the CKAN server"),
3987
- id: z13.string().min(1).describe("Dataset id or name (CKAN package 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.50"
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
  });