@aborruso/ckan-mcp-server 0.4.72 → 0.4.76

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # LOG
2
2
 
3
+ ## 2026-03-06 (v0.4.75)
4
+
5
+ - fix(`ckan_find_portals`): deduplicate portals by hostname, preferring https over http
6
+ - feat: new tool `ckan_find_portals` — discovers CKAN portals from datashades.info registry (~950 portals); filters by country, keyword, min_datasets, language, has_datastore; LLM translates country to English
7
+
8
+ ## 2026-03-06 (v0.4.74)
9
+
10
+ - fix: use `z.coerce.number()` for all numeric tool parameters — fixes validation errors when MCP clients pass numbers as strings (closes #16)
11
+
12
+ ## 2026-03-05 (v0.4.73)
13
+
14
+ - feat: `package_show` now includes `api_json_url` for dataset and each resource (direct CKAN API JSON link)
15
+ - fix: `api_json_url` uses portal-specific API path via `getPortalApiPath` instead of hardcoded `/api/3/action`
16
+
3
17
  ## 2026-03-05 (v0.4.72)
4
18
 
5
19
  - fix: `package_show` JSON now includes `hvd_category`, `applicable_legislation`, `frequency`, `language`, `publisher_name`, `holder_name`
package/dist/index.js CHANGED
@@ -729,6 +729,8 @@ var formatPackageShowMarkdown = (result, serverUrl) => {
729
729
  markdown += `**Server**: ${serverUrl}
730
730
  `;
731
731
  markdown += `**Link**: ${getDatasetViewUrl(serverUrl, result)}
732
+ `;
733
+ markdown += `**Full JSON metadata**: ${serverUrl.replace(/\/$/, "")}${getPortalApiPath(serverUrl)}/package_show?id=${result.id}
732
734
 
733
735
  `;
734
736
  markdown += `## Basic Information
@@ -851,6 +853,8 @@ ${result.notes}
851
853
  markdown += `- **DataStore**: \u2753 Not reported by portal
852
854
  `;
853
855
  }
856
+ markdown += `- **Full JSON metadata**: ${serverUrl.replace(/\/$/, "")}${getPortalApiPath(serverUrl)}/resource_show?id=${resource.id}
857
+ `;
854
858
  markdown += "\n";
855
859
  }
856
860
  }
@@ -923,9 +927,13 @@ function compactPackageShow(result, serverUrl) {
923
927
  size: r.size || null,
924
928
  datastore_active: r.datastore_active ?? null,
925
929
  created: r.created || null,
926
- last_modified: r.last_modified || null
930
+ last_modified: r.last_modified || null,
931
+ ...serverUrl ? { api_json_url: `${serverUrl.replace(/\/$/, "")}${getPortalApiPath(serverUrl)}/resource_show?id=${r.id}` } : {}
927
932
  })),
928
- ...serverUrl ? { view_url: getDatasetViewUrl(serverUrl, result) } : {}
933
+ ...serverUrl ? {
934
+ view_url: getDatasetViewUrl(serverUrl, result),
935
+ api_json_url: `${serverUrl.replace(/\/$/, "")}${getPortalApiPath(serverUrl)}/package_show?id=${result.id}`
936
+ } : {}
929
937
  };
930
938
  }
931
939
  function registerPackageTools(server2) {
@@ -1054,16 +1062,16 @@ Typical workflow: ckan_package_search \u2192 ckan_package_show (get full metadat
1054
1062
  server_url: z2.string().url("Must be a valid URL").describe("Base URL of the CKAN server"),
1055
1063
  q: z2.string().optional().default("*:*").describe("Search query in Solr syntax"),
1056
1064
  fq: z2.string().optional().describe(`Filter query in Solr syntax; applied after scoring, does not affect relevance. CKAN extras fields use prefix 'extras_' (e.g. extras_hvd_category). For OR on same field use field:(val1 OR val2), never field:val1 OR field:val2 (silently breaks). Examples: 'organization:comune-palermo', 'res_format:CSV', 'extras_hvd_category:("uri1" OR "uri2")'.`),
1057
- rows: z2.number().int().min(0).max(1e3).optional().default(10).describe("Number of results to return"),
1058
- start: z2.number().int().min(0).optional().default(0).describe("Offset for pagination"),
1065
+ rows: z2.coerce.number().int().min(0).max(1e3).optional().default(10).describe("Number of results to return"),
1066
+ start: z2.coerce.number().int().min(0).optional().default(0).describe("Offset for pagination"),
1059
1067
  sort: z2.string().optional().describe("Sort field and direction (e.g., 'metadata_modified desc')"),
1060
1068
  facet_field: z2.array(z2.string()).optional().describe("Fields to facet on"),
1061
- facet_limit: z2.number().int().min(1).optional().default(50).describe("Maximum facet values per field"),
1062
- page: z2.number().int().min(1).optional().describe("Page number (1-based); alias for start. Overrides start if provided."),
1063
- page_size: z2.number().int().min(1).max(1e3).optional().default(10).describe("Results per page when using page (default: 10)"),
1069
+ facet_limit: z2.coerce.number().int().min(1).optional().default(50).describe("Maximum facet values per field"),
1070
+ page: z2.coerce.number().int().min(1).optional().describe("Page number (1-based); alias for start. Overrides start if provided."),
1071
+ page_size: z2.coerce.number().int().min(1).max(1e3).optional().default(10).describe("Results per page when using page (default: 10)"),
1064
1072
  include_drafts: z2.boolean().optional().default(false).describe("Include draft datasets"),
1065
1073
  content_recent: z2.boolean().optional().default(false).describe("Use issued date with fallback to metadata_created for recent content"),
1066
- content_recent_days: z2.number().int().min(1).optional().default(30).describe("Day window for content_recent (default 30)"),
1074
+ content_recent_days: z2.coerce.number().int().min(1).optional().default(30).describe("Day window for content_recent (default 30)"),
1067
1075
  query_parser: z2.enum(["default", "text"]).optional().describe("Override search parser ('text' forces text:(...) on non-fielded queries)"),
1068
1076
  response_format: ResponseFormatSchema
1069
1077
  }).strict(),
@@ -1304,12 +1312,12 @@ Typical workflow: ckan_find_relevant_datasets \u2192 ckan_package_show (inspect
1304
1312
  inputSchema: z2.object({
1305
1313
  server_url: z2.string().url().describe("Base URL of the CKAN server (e.g., https://dati.gov.it/opendata)"),
1306
1314
  query: z2.string().min(2).describe("Natural language or keyword query to match against dataset title, notes, tags, and organization"),
1307
- limit: z2.number().int().min(1).max(50).optional().default(10).describe("Number of datasets to return"),
1315
+ limit: z2.coerce.number().int().min(1).max(50).optional().default(10).describe("Number of datasets to return"),
1308
1316
  weights: z2.object({
1309
- title: z2.number().min(0).optional().describe("Weight for title match (default 4)"),
1310
- notes: z2.number().min(0).optional().describe("Weight for description match (default 2)"),
1311
- tags: z2.number().min(0).optional().describe("Weight for tag match (default 3)"),
1312
- organization: z2.number().min(0).optional().describe("Weight for organization match (default 1)")
1317
+ title: z2.coerce.number().min(0).optional().describe("Weight for title match (default 4)"),
1318
+ notes: z2.coerce.number().min(0).optional().describe("Weight for description match (default 2)"),
1319
+ tags: z2.coerce.number().min(0).optional().describe("Weight for tag match (default 3)"),
1320
+ organization: z2.coerce.number().min(0).optional().describe("Weight for organization match (default 1)")
1313
1321
  }).optional().describe("Per-field scoring weights; unspecified fields use defaults"),
1314
1322
  query_parser: z2.enum(["default", "text"]).optional().describe("Override search parser ('text' forces text:(...) on non-fielded queries)"),
1315
1323
  response_format: ResponseFormatSchema
@@ -1486,8 +1494,8 @@ Returns (JSON format):
1486
1494
  author, maintainer,
1487
1495
  frequency, language, publisher_name, holder_name,
1488
1496
  hvd_category, applicable_legislation,
1489
- resources (id, name, format, url, size, datastore_active, created, last_modified),
1490
- view_url
1497
+ resources (id, name, format, url, size, datastore_active, created, last_modified, api_json_url),
1498
+ view_url, api_json_url
1491
1499
 
1492
1500
  Examples:
1493
1501
  - { server_url: "https://dati.gov.it/opendata", id: "dataset-name" }
@@ -1787,8 +1795,8 @@ Typical workflow: ckan_organization_list \u2192 ckan_organization_show (inspect
1787
1795
  server_url: z3.string().url().describe("Base URL of the CKAN server (e.g., https://dati.gov.it/opendata)"),
1788
1796
  all_fields: z3.boolean().optional().default(false).describe("Return full organization objects (true) or just name slugs (false)"),
1789
1797
  sort: z3.string().optional().default("name asc").describe("Sort field and direction (e.g., 'name asc', 'package_count desc')"),
1790
- limit: z3.number().int().min(0).optional().default(100).describe("Max organizations to return. Use 0 to get only the count via faceting"),
1791
- offset: z3.number().int().min(0).optional().default(0).describe("Pagination offset"),
1798
+ limit: z3.coerce.number().int().min(0).optional().default(100).describe("Max organizations to return. Use 0 to get only the count via faceting"),
1799
+ offset: z3.coerce.number().int().min(0).optional().default(0).describe("Pagination offset"),
1792
1800
  response_format: ResponseFormatSchema
1793
1801
  }).strict(),
1794
1802
  annotations: {
@@ -2293,8 +2301,8 @@ Typical workflow: ckan_package_search \u2192 ckan_package_show (find resource_id
2293
2301
  resource_id: z4.string().min(1).describe("UUID of the DataStore resource (from ckan_package_show resource.id where datastore_active is true)"),
2294
2302
  q: z4.string().optional().describe("Full-text search across all fields"),
2295
2303
  filters: z4.record(z4.any()).optional().describe('Key-value filters for exact matches (e.g., { "regione": "Sicilia", "anno": 2023 })'),
2296
- 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"),
2297
- offset: z4.number().int().min(0).optional().default(0).describe("Pagination offset"),
2304
+ limit: z4.coerce.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"),
2305
+ offset: z4.coerce.number().int().min(0).optional().default(0).describe("Pagination offset"),
2298
2306
  fields: z4.array(z4.string()).optional().describe("Specific field names to return; omit to return all fields"),
2299
2307
  sort: z4.string().optional().describe("Sort expression (e.g., 'anno desc', 'nome asc')"),
2300
2308
  distinct: z4.boolean().optional().default(false).describe("Return only distinct rows"),
@@ -4207,6 +4215,132 @@ ${error instanceof Error ? error.message : String(error)}`
4207
4215
  );
4208
4216
  }
4209
4217
 
4218
+ // src/tools/portal-discovery.ts
4219
+ import { z as z11 } from "zod";
4220
+ import axios3 from "axios";
4221
+ var DATASHADES_URL = "https://datashades.info/api/portal/list";
4222
+ async function fetchPortals() {
4223
+ const resp = await axios3.get(DATASHADES_URL, {
4224
+ timeout: 15e3,
4225
+ headers: { "User-Agent": "CKAN-MCP-Server/1.0" }
4226
+ });
4227
+ return resp.data.portals;
4228
+ }
4229
+ function deduplicateByHostname(portals) {
4230
+ const seen = /* @__PURE__ */ new Map();
4231
+ for (const p of portals) {
4232
+ try {
4233
+ const hostname = new URL(p.Href).hostname;
4234
+ const existing = seen.get(hostname);
4235
+ if (!existing || p.Href.startsWith("https://")) {
4236
+ seen.set(hostname, p);
4237
+ }
4238
+ } catch {
4239
+ }
4240
+ }
4241
+ return Array.from(seen.values());
4242
+ }
4243
+ function filterPortals(portals, params) {
4244
+ const filtered = portals.filter((p) => p.status === "active" && p.Href).filter((p) => !params.country || p.Coordinates.country_name.toLowerCase().includes(params.country.toLowerCase())).filter((p) => !params.query || p.SiteInfo.site_title.toLowerCase().includes(params.query.toLowerCase())).filter((p) => params.min_datasets === void 0 || p.DatasetsNumber >= params.min_datasets).filter((p) => !params.language || p.SiteInfo.locale_default.toLowerCase().startsWith(params.language.toLowerCase())).filter((p) => !params.has_datastore || (p.Plugins || []).includes("datastore"));
4245
+ return deduplicateByHostname(filtered).sort((a, b) => b.DatasetsNumber - a.DatasetsNumber).slice(0, params.limit);
4246
+ }
4247
+ function formatMarkdown(portals, total, limit) {
4248
+ if (portals.length === 0) return "No CKAN portals found matching the given filters.";
4249
+ const rows = portals.map(
4250
+ (p) => `| [${p.SiteInfo.site_title || p.Href}](${p.Href}) | ${p.Coordinates.country_name} | ${p.Version} | ${p.DatasetsNumber.toLocaleString()} | ${p.SiteInfo.locale_default} | ${(p.Plugins || []).includes("datastore") ? "\u2705" : "\u274C"} |`
4251
+ ).join("\n");
4252
+ return `# CKAN Portals
4253
+
4254
+ **Source**: [datashades.info](https://datashades.info/portals) \u2014 live registry of ${total} active portals
4255
+ **Showing**: ${portals.length} of ${total} (filtered, sorted by dataset count)
4256
+
4257
+ | Portal | Country | CKAN | Datasets | Locale | DataStore |
4258
+ |--------|---------|------|----------|--------|-----------|
4259
+ ${rows}
4260
+
4261
+ ---
4262
+ \u{1F4A1} Use the portal URL as \`server_url\` in any CKAN tool.`;
4263
+ }
4264
+ function registerPortalDiscoveryTools(server2) {
4265
+ server2.registerTool(
4266
+ "ckan_find_portals",
4267
+ {
4268
+ title: "Find CKAN Portals",
4269
+ description: `Search the live datashades.info registry of ~950 CKAN portals worldwide.
4270
+
4271
+ Use this tool to discover which CKAN portals exist for a country, language, or topic
4272
+ before querying them with other CKAN tools.
4273
+
4274
+ **IMPORTANT \u2014 country parameter**: always pass country name in English.
4275
+ If the user writes in another language (e.g. "Italia", "Espa\xF1a", "Brasil"),
4276
+ translate to English ("Italy", "Spain", "Brazil") before calling this tool.
4277
+
4278
+ Args:
4279
+ - country (string): Country name in English (e.g. "Italy", "Brazil", "France")
4280
+ - query (string): Keyword to match against portal title (e.g. "transport", "health")
4281
+ - min_datasets (number): Minimum number of datasets (e.g. 100)
4282
+ - language (string): Portal default locale code (e.g. "it", "en", "pt_BR", "fr")
4283
+ - has_datastore (boolean): If true, return only portals with DataStore enabled (supports SQL queries)
4284
+ - limit (number): Max results to return (default 10, max 50)
4285
+
4286
+ Returns:
4287
+ Ranked list of matching portals with URL, country, CKAN version, dataset count, and DataStore status.
4288
+
4289
+ Typical workflow: ckan_find_portals (discover portal URL) \u2192 ckan_status_show (verify) \u2192 ckan_package_search (search datasets)`,
4290
+ inputSchema: z11.object({
4291
+ country: z11.string().optional().describe("Country name in English (e.g. 'Italy', 'Brazil'). Translate from any language before passing."),
4292
+ query: z11.string().optional().describe("Keyword matched against portal title (case-insensitive)"),
4293
+ min_datasets: z11.coerce.number().int().min(0).optional().describe("Minimum number of datasets"),
4294
+ language: z11.string().optional().describe("Portal default locale code (e.g. 'it', 'en', 'pt_BR')"),
4295
+ has_datastore: z11.boolean().optional().describe("If true, return only portals with DataStore plugin (required for SQL queries)"),
4296
+ limit: z11.coerce.number().int().min(1).max(50).optional().default(10).describe("Max results (default 10, max 50)")
4297
+ }).strict(),
4298
+ annotations: {
4299
+ readOnlyHint: true,
4300
+ destructiveHint: false,
4301
+ idempotentHint: true,
4302
+ openWorldHint: true
4303
+ }
4304
+ },
4305
+ async (params) => {
4306
+ try {
4307
+ const all = await fetchPortals();
4308
+ const active = all.filter((p) => p.status === "active");
4309
+ const results = filterPortals(all, {
4310
+ country: params.country,
4311
+ query: params.query,
4312
+ min_datasets: params.min_datasets,
4313
+ language: params.language,
4314
+ has_datastore: params.has_datastore,
4315
+ limit: params.limit
4316
+ });
4317
+ const markdown = formatMarkdown(results, active.length, params.limit);
4318
+ return {
4319
+ content: [{ type: "text", text: addDemoFooter(markdown) }],
4320
+ structuredContent: { portals: results.map((p) => ({
4321
+ url: p.Href,
4322
+ title: p.SiteInfo.site_title,
4323
+ country: p.Coordinates.country_name,
4324
+ version: p.Version,
4325
+ datasets: p.DatasetsNumber,
4326
+ locale: p.SiteInfo.locale_default,
4327
+ has_datastore: (p.Plugins || []).includes("datastore")
4328
+ })) }
4329
+ };
4330
+ } catch (error) {
4331
+ return {
4332
+ content: [{
4333
+ type: "text",
4334
+ text: `Could not fetch portal list from datashades.info:
4335
+ ${error instanceof Error ? error.message : String(error)}`
4336
+ }],
4337
+ isError: true
4338
+ };
4339
+ }
4340
+ }
4341
+ );
4342
+ }
4343
+
4210
4344
  // src/resources/dataset.ts
4211
4345
  import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
4212
4346
 
@@ -4476,7 +4610,7 @@ function registerAllResources(server2) {
4476
4610
  }
4477
4611
 
4478
4612
  // src/prompts/theme.ts
4479
- import { z as z11 } from "zod";
4613
+ import { z as z12 } from "zod";
4480
4614
 
4481
4615
  // src/prompts/types.ts
4482
4616
  var createTextPrompt = (text) => ({
@@ -4537,9 +4671,9 @@ var registerThemePrompt = (server2) => {
4537
4671
  title: "Search datasets by theme",
4538
4672
  description: "Guided prompt to discover a theme and search datasets under it.",
4539
4673
  argsSchema: {
4540
- server_url: z11.string().url().describe("Base URL of the CKAN server"),
4541
- theme: z11.string().min(1).describe("Theme or group name to search"),
4542
- rows: z11.coerce.number().int().positive().default(10).describe("Max results to return")
4674
+ server_url: z12.string().url().describe("Base URL of the CKAN server"),
4675
+ theme: z12.string().min(1).describe("Theme or group name to search"),
4676
+ rows: z12.coerce.number().int().positive().default(10).describe("Max results to return")
4543
4677
  }
4544
4678
  },
4545
4679
  async ({ server_url, theme, rows }) => createTextPrompt(buildThemePromptText(server_url, theme, rows))
@@ -4547,7 +4681,7 @@ var registerThemePrompt = (server2) => {
4547
4681
  };
4548
4682
 
4549
4683
  // src/prompts/organization.ts
4550
- import { z as z12 } from "zod";
4684
+ import { z as z13 } from "zod";
4551
4685
  var ORGANIZATION_PROMPT_NAME = "ckan-search-by-organization";
4552
4686
  var buildOrganizationPromptText = (serverUrl, organization, rows) => `# Guided search: datasets by organization
4553
4687
 
@@ -4582,9 +4716,9 @@ var registerOrganizationPrompt = (server2) => {
4582
4716
  title: "Search datasets by organization",
4583
4717
  description: "Guided prompt to find a publisher and list its datasets.",
4584
4718
  argsSchema: {
4585
- server_url: z12.string().url().describe("Base URL of the CKAN server"),
4586
- organization: z12.string().min(1).describe("Organization name or keyword"),
4587
- rows: z12.coerce.number().int().positive().default(10).describe("Max results to return")
4719
+ server_url: z13.string().url().describe("Base URL of the CKAN server"),
4720
+ organization: z13.string().min(1).describe("Organization name or keyword"),
4721
+ rows: z13.coerce.number().int().positive().default(10).describe("Max results to return")
4588
4722
  }
4589
4723
  },
4590
4724
  async ({ server_url, organization, rows }) => createTextPrompt(buildOrganizationPromptText(server_url, organization, rows))
@@ -4592,7 +4726,7 @@ var registerOrganizationPrompt = (server2) => {
4592
4726
  };
4593
4727
 
4594
4728
  // src/prompts/format.ts
4595
- import { z as z13 } from "zod";
4729
+ import { z as z14 } from "zod";
4596
4730
  var FORMAT_PROMPT_NAME = "ckan-search-by-format";
4597
4731
  var buildFormatPromptText = (serverUrl, format, rows) => `# Guided search: datasets by resource format
4598
4732
 
@@ -4616,9 +4750,9 @@ var registerFormatPrompt = (server2) => {
4616
4750
  title: "Search datasets by resource format",
4617
4751
  description: "Guided prompt to find datasets with a given resource format.",
4618
4752
  argsSchema: {
4619
- server_url: z13.string().url().describe("Base URL of the CKAN server"),
4620
- format: z13.string().min(1).describe("Resource format (e.g., CSV, JSON)"),
4621
- rows: z13.coerce.number().int().positive().default(10).describe("Max results to return")
4753
+ server_url: z14.string().url().describe("Base URL of the CKAN server"),
4754
+ format: z14.string().min(1).describe("Resource format (e.g., CSV, JSON)"),
4755
+ rows: z14.coerce.number().int().positive().default(10).describe("Max results to return")
4622
4756
  }
4623
4757
  },
4624
4758
  async ({ server_url, format, rows }) => createTextPrompt(buildFormatPromptText(server_url, format, rows))
@@ -4626,7 +4760,7 @@ var registerFormatPrompt = (server2) => {
4626
4760
  };
4627
4761
 
4628
4762
  // src/prompts/recent.ts
4629
- import { z as z14 } from "zod";
4763
+ import { z as z15 } from "zod";
4630
4764
  var RECENT_PROMPT_NAME = "ckan-recent-datasets";
4631
4765
  var buildRecentPromptText = (serverUrl, rows) => `# Guided search: recent datasets
4632
4766
 
@@ -4674,8 +4808,8 @@ var registerRecentPrompt = (server2) => {
4674
4808
  title: "Find recently updated datasets",
4675
4809
  description: "Guided prompt to list recently updated datasets on a CKAN portal.",
4676
4810
  argsSchema: {
4677
- server_url: z14.string().url().describe("Base URL of the CKAN server"),
4678
- rows: z14.coerce.number().int().positive().default(10).describe("Max results to return")
4811
+ server_url: z15.string().url().describe("Base URL of the CKAN server"),
4812
+ rows: z15.coerce.number().int().positive().default(10).describe("Max results to return")
4679
4813
  }
4680
4814
  },
4681
4815
  async ({ server_url, rows }) => createTextPrompt(buildRecentPromptText(server_url, rows))
@@ -4683,7 +4817,7 @@ var registerRecentPrompt = (server2) => {
4683
4817
  };
4684
4818
 
4685
4819
  // src/prompts/dataset-analysis.ts
4686
- import { z as z15 } from "zod";
4820
+ import { z as z16 } from "zod";
4687
4821
  var DATASET_ANALYSIS_PROMPT_NAME = "ckan-analyze-dataset";
4688
4822
  var buildDatasetAnalysisPromptText = (serverUrl, id) => `# Guided analysis: dataset
4689
4823
 
@@ -4725,8 +4859,8 @@ var registerDatasetAnalysisPrompt = (server2) => {
4725
4859
  title: "Analyze a dataset",
4726
4860
  description: "Guided prompt to inspect dataset metadata and explore DataStore tables.",
4727
4861
  argsSchema: {
4728
- server_url: z15.string().url().describe("Base URL of the CKAN server"),
4729
- id: z15.string().min(1).describe("Dataset id or name (CKAN package id)")
4862
+ server_url: z16.string().url().describe("Base URL of the CKAN server"),
4863
+ id: z16.string().min(1).describe("Dataset id or name (CKAN package id)")
4730
4864
  }
4731
4865
  },
4732
4866
  async ({ server_url, id }) => createTextPrompt(buildDatasetAnalysisPromptText(server_url, id))
@@ -4734,7 +4868,7 @@ var registerDatasetAnalysisPrompt = (server2) => {
4734
4868
  };
4735
4869
 
4736
4870
  // src/prompts/hvd.ts
4737
- import { z as z16 } from "zod";
4871
+ import { z as z17 } from "zod";
4738
4872
  var HVD_PROMPT_NAME = "ckan-search-hvd";
4739
4873
  var buildHvdPromptText = (serverUrl, rows, categoryField) => {
4740
4874
  if (!categoryField) {
@@ -4796,8 +4930,8 @@ var registerHvdPrompt = (server2) => {
4796
4930
  title: "Search High-Value Datasets (HVD)",
4797
4931
  description: "Guided prompt to find High-Value Datasets (HVD) on a CKAN portal. Automatically uses the correct filter field from portal configuration.",
4798
4932
  argsSchema: {
4799
- server_url: z16.string().url().describe("Base URL of the CKAN server"),
4800
- rows: z16.coerce.number().int().positive().default(10).describe("Max results to return")
4933
+ server_url: z17.string().url().describe("Base URL of the CKAN server"),
4934
+ rows: z17.coerce.number().int().positive().default(10).describe("Max results to return")
4801
4935
  }
4802
4936
  },
4803
4937
  async ({ server_url, rows }) => {
@@ -4821,7 +4955,7 @@ var registerAllPrompts = (server2) => {
4821
4955
  function createServer() {
4822
4956
  return new McpServer({
4823
4957
  name: "ckan-mcp-server",
4824
- version: "0.4.72"
4958
+ version: "0.4.76"
4825
4959
  });
4826
4960
  }
4827
4961
  function registerAll(server2) {
@@ -4835,6 +4969,7 @@ function registerAll(server2) {
4835
4969
  registerAnalyzeTools(server2);
4836
4970
  registerCatalogStatsTools(server2);
4837
4971
  registerSparqlTools(server2);
4972
+ registerPortalDiscoveryTools(server2);
4838
4973
  registerAllResources(server2);
4839
4974
  registerAllPrompts(server2);
4840
4975
  }