@aborruso/ckan-mcp-server 0.4.93 → 0.4.95

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,17 @@
1
1
  # LOG
2
2
 
3
+ ## 2026-03-26
4
+
5
+ ### v0.4.95
6
+
7
+ - Show `Portal Locale` (`locale_default`) in `ckan_status_show` markdown output
8
+ - Add query language hint to `ckan_package_search` description: check locale before searching
9
+ - Update skill query construction rule: dynamic locale check via `ckan_status_show` instead of hardcoded portal table
10
+
11
+ ### v0.4.94
12
+
13
+ - Add `data.gov.ua` (Ukraine open data portal) to `portals.json` with explicit `force_text_field: false` to prevent auto-detection from incorrectly wrapping Solr queries in `text:(...)`, which caused 0 results
14
+
3
15
  ## 2026-03-24
4
16
 
5
17
  ### v0.4.93
package/dist/index.js CHANGED
@@ -141,6 +141,17 @@ var portals_default = {
141
141
  ],
142
142
  organization_view_url: "https://ckan-prod.zurich.datopian.com/organization/{name}"
143
143
  },
144
+ {
145
+ id: "data-gov-ua",
146
+ name: "data.gov.ua",
147
+ api_url: "https://data.gov.ua",
148
+ api_url_aliases: [
149
+ "http://data.gov.ua"
150
+ ],
151
+ search: {
152
+ force_text_field: false
153
+ }
154
+ },
144
155
  {
145
156
  id: "govdata-de",
146
157
  name: "govdata.de",
@@ -1215,7 +1226,13 @@ Examples:
1215
1226
  - Filter extras OR (correct): { fq: "extras_hvd_category:("http://data.europa.eu/bna/c_ac64a52d" OR "http://data.europa.eu/bna/c_dd313021")" }
1216
1227
  - Get facets: { facet_field: ["organization"], rows: 0 }
1217
1228
 
1218
- Typical workflow: ckan_package_search \u2192 ckan_package_show (get full metadata + resource IDs) \u2192 ckan_datastore_search (query tabular data)`,
1229
+ Query language:
1230
+ Before searching a portal, check its locale via ckan_status_show (field: "Portal Locale" / locale_default).
1231
+ Translate query terms to the portal's language \u2014 searching in English on a non-English portal returns 0 results.
1232
+ Examples: locale "it" \u2192 Italian terms; "uk_UA" \u2192 Ukrainian (Cyrillic); "fr_FR" \u2192 French.
1233
+ Exception: multilingual portals (e.g. data.europa.eu, open.canada.ca) accept EN + native terms joined with OR.
1234
+
1235
+ Typical workflow: ckan_status_show (check locale) \u2192 ckan_package_search (query in portal's language) \u2192 ckan_package_show (get full metadata + resource IDs) \u2192 ckan_datastore_search (query tabular data)`,
1219
1236
  inputSchema: z2.object({
1220
1237
  server_url: z2.string().url("Must be a valid URL").describe("Base URL of the CKAN server"),
1221
1238
  q: z2.string().optional().default("*:*").describe("Search query in Solr syntax"),
@@ -2597,6 +2614,8 @@ function formatStatusMarkdown(result, serverUrl, hvdCount) {
2597
2614
  const sparqlLine = sparql ? `**SPARQL Endpoint**: ${sparql.endpoint_url}
2598
2615
  ` : "";
2599
2616
  const hvdLine = hvdCount !== void 0 ? `**HVD Datasets**: ${hvdCount}
2617
+ ` : "";
2618
+ const localeLine = result.locale_default ? `**Portal Locale**: ${result.locale_default}
2600
2619
  ` : "";
2601
2620
  return `# CKAN Server Status
2602
2621
 
@@ -2605,7 +2624,7 @@ function formatStatusMarkdown(result, serverUrl, hvdCount) {
2605
2624
  **CKAN Version**: ${result.ckan_version || "Unknown"}
2606
2625
  **Site Title**: ${result.site_title || "N/A"}
2607
2626
  **Site URL**: ${result.site_url || "N/A"}
2608
- ` + sparqlLine + hvdLine;
2627
+ ` + localeLine + sparqlLine + hvdLine;
2609
2628
  }
2610
2629
  function registerStatusTools(server) {
2611
2630
  server.registerTool(
@@ -5129,7 +5148,7 @@ var registerAllPrompts = (server) => {
5129
5148
  function createServer() {
5130
5149
  return new McpServer({
5131
5150
  name: "ckan-mcp-server",
5132
- version: "0.4.93"
5151
+ version: "0.4.95"
5133
5152
  });
5134
5153
  }
5135
5154
  function registerAll(server) {
package/dist/worker.js CHANGED
@@ -1086,7 +1086,7 @@ ckan_package_search({
1086
1086
  })
1087
1087
  \`\`\`
1088
1088
 
1089
- If the portal supports HVD classification, look for datasets with fields like \`hvd_category\` or \`applicable_legislation\`.`,Sb=t=>{t.registerPrompt(sC,{title:"Search High-Value Datasets (HVD)",description:"Guided prompt to find High-Value Datasets (HVD) on a CKAN portal. Automatically uses the correct filter field from portal configuration.",argsSchema:{server_url:v.string().url().describe("Base URL of the CKAN server"),rows:v.coerce.number().int().positive().default(10).describe("Max results to return")}},async({server_url:e,rows:r})=>{let n=co(e);return ht(iC(e,r,n?.category_field??null))})};var $b=t=>{vb(t),bb(t),wb(t),xb(t),kb(t),Sb(t)};function zb(){return new ga({name:"ckan-mcp-server",version:"0.4.92"})}function Tb(t){Kv(t),Jv(t),Gv(t),Qv(t),Yv(t),eb(t),ib(t),ab(t),cb(t),ub(t),lb(t),_b(t),$b(t)}var qa=class{constructor(e={}){this._started=!1,this._hasHandledRequest=!1,this._streamMapping=new Map,this._requestToStreamMapping=new Map,this._requestResponseMap=new Map,this._initialized=!1,this._enableJsonResponse=!1,this._standaloneSseStreamId="_GET_stream",this.sessionIdGenerator=e.sessionIdGenerator,this._enableJsonResponse=e.enableJsonResponse??!1,this._eventStore=e.eventStore,this._onsessioninitialized=e.onsessioninitialized,this._onsessionclosed=e.onsessionclosed,this._allowedHosts=e.allowedHosts,this._allowedOrigins=e.allowedOrigins,this._enableDnsRebindingProtection=e.enableDnsRebindingProtection??!1,this._retryInterval=e.retryInterval}async start(){if(this._started)throw new Error("Transport already started");this._started=!0}createJsonErrorResponse(e,r,n,o){let s={code:r,message:n};return o?.data!==void 0&&(s.data=o.data),new Response(JSON.stringify({jsonrpc:"2.0",error:s,id:null}),{status:e,headers:{"Content-Type":"application/json",...o?.headers}})}validateRequestHeaders(e){if(this._enableDnsRebindingProtection){if(this._allowedHosts&&this._allowedHosts.length>0){let r=e.headers.get("host");if(!r||!this._allowedHosts.includes(r)){let n=`Invalid Host header: ${r}`;return this.onerror?.(new Error(n)),this.createJsonErrorResponse(403,-32e3,n)}}if(this._allowedOrigins&&this._allowedOrigins.length>0){let r=e.headers.get("origin");if(r&&!this._allowedOrigins.includes(r)){let n=`Invalid Origin header: ${r}`;return this.onerror?.(new Error(n)),this.createJsonErrorResponse(403,-32e3,n)}}}}async handleRequest(e,r){if(!this.sessionIdGenerator&&this._hasHandledRequest)throw new Error("Stateless transport cannot be reused across requests. Create a new transport per request.");this._hasHandledRequest=!0;let n=this.validateRequestHeaders(e);if(n)return n;switch(e.method){case"POST":return this.handlePostRequest(e,r);case"GET":return this.handleGetRequest(e);case"DELETE":return this.handleDeleteRequest(e);default:return this.handleUnsupportedRequest()}}async writePrimingEvent(e,r,n,o){if(!this._eventStore||o<"2025-11-25")return;let s=await this._eventStore.storeEvent(n,{}),i=`id: ${s}
1089
+ If the portal supports HVD classification, look for datasets with fields like \`hvd_category\` or \`applicable_legislation\`.`,Sb=t=>{t.registerPrompt(sC,{title:"Search High-Value Datasets (HVD)",description:"Guided prompt to find High-Value Datasets (HVD) on a CKAN portal. Automatically uses the correct filter field from portal configuration.",argsSchema:{server_url:v.string().url().describe("Base URL of the CKAN server"),rows:v.coerce.number().int().positive().default(10).describe("Max results to return")}},async({server_url:e,rows:r})=>{let n=co(e);return ht(iC(e,r,n?.category_field??null))})};var $b=t=>{vb(t),bb(t),wb(t),xb(t),kb(t),Sb(t)};function zb(){return new ga({name:"ckan-mcp-server",version:"0.4.93"})}function Tb(t){Kv(t),Jv(t),Gv(t),Qv(t),Yv(t),eb(t),ib(t),ab(t),cb(t),ub(t),lb(t),_b(t),$b(t)}var qa=class{constructor(e={}){this._started=!1,this._hasHandledRequest=!1,this._streamMapping=new Map,this._requestToStreamMapping=new Map,this._requestResponseMap=new Map,this._initialized=!1,this._enableJsonResponse=!1,this._standaloneSseStreamId="_GET_stream",this.sessionIdGenerator=e.sessionIdGenerator,this._enableJsonResponse=e.enableJsonResponse??!1,this._eventStore=e.eventStore,this._onsessioninitialized=e.onsessioninitialized,this._onsessionclosed=e.onsessionclosed,this._allowedHosts=e.allowedHosts,this._allowedOrigins=e.allowedOrigins,this._enableDnsRebindingProtection=e.enableDnsRebindingProtection??!1,this._retryInterval=e.retryInterval}async start(){if(this._started)throw new Error("Transport already started");this._started=!0}createJsonErrorResponse(e,r,n,o){let s={code:r,message:n};return o?.data!==void 0&&(s.data=o.data),new Response(JSON.stringify({jsonrpc:"2.0",error:s,id:null}),{status:e,headers:{"Content-Type":"application/json",...o?.headers}})}validateRequestHeaders(e){if(this._enableDnsRebindingProtection){if(this._allowedHosts&&this._allowedHosts.length>0){let r=e.headers.get("host");if(!r||!this._allowedHosts.includes(r)){let n=`Invalid Host header: ${r}`;return this.onerror?.(new Error(n)),this.createJsonErrorResponse(403,-32e3,n)}}if(this._allowedOrigins&&this._allowedOrigins.length>0){let r=e.headers.get("origin");if(r&&!this._allowedOrigins.includes(r)){let n=`Invalid Origin header: ${r}`;return this.onerror?.(new Error(n)),this.createJsonErrorResponse(403,-32e3,n)}}}}async handleRequest(e,r){if(!this.sessionIdGenerator&&this._hasHandledRequest)throw new Error("Stateless transport cannot be reused across requests. Create a new transport per request.");this._hasHandledRequest=!0;let n=this.validateRequestHeaders(e);if(n)return n;switch(e.method){case"POST":return this.handlePostRequest(e,r);case"GET":return this.handleGetRequest(e);case"DELETE":return this.handleDeleteRequest(e);default:return this.handleUnsupportedRequest()}}async writePrimingEvent(e,r,n,o){if(!this._eventStore||o<"2025-11-25")return;let s=await this._eventStore.storeEvent(n,{}),i=`id: ${s}
1090
1090
  data:
1091
1091
 
1092
1092
  `;this._retryInterval!==void 0&&(i=`id: ${s}
@@ -1280,4 +1280,4 @@ data:
1280
1280
  </div>
1281
1281
  </div>
1282
1282
  </body>
1283
- </html>`,{headers:{"Content-Type":"text/html; charset=utf-8","Access-Control-Allow-Origin":"*"}});if(t.method==="GET"&&e.pathname==="/health")return new Response(JSON.stringify({status:"ok",version:"0.4.92",tools:20,resources:7,prompts:6,runtime:"cloudflare-workers"}),{headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}});if(e.pathname==="/mcp"){if(t.method!=="POST"&&t.method!=="OPTIONS")return new Response("Method Not Allowed",{status:405,headers:{Allow:"POST, OPTIONS","Access-Control-Allow-Origin":"*"}});if(t.method==="OPTIONS")return new Response(null,{status:204,headers:{"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET, POST, DELETE, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Accept, Authorization, Mcp-Session-Id","Access-Control-Max-Age":"86400"}});try{let r=zb();Tb(r);let n=new qa({sessionIdGenerator:void 0,enableJsonResponse:!0});await r.connect(n);let o=t.clone();try{let a=await o.json();if(a?.method==="tools/call"&&a?.params?.name){let c=a.params.name,u=a.params.arguments??{},l={tool:c,server:u.server_url??u.endpoint_url??""};u.q!==void 0&&(l.q=u.q),u.fq!==void 0&&(l.fq=u.fq),u.query!==void 0&&(l.query=u.query),u.id!==void 0&&(l.id=u.id),u.name!==void 0&&(l.name=u.name),u.pattern!==void 0&&(l.pattern=u.pattern),u.resource_id!==void 0&&(l.resource_id=u.resource_id),u.format_filter!==void 0&&(l.format_filter=u.format_filter),u.sort!==void 0&&(l.sort=u.sort),u.rows!==void 0&&(l.rows=u.rows),u.limit!==void 0&&(l.limit=u.limit),u.sql!==void 0&&(l.sql=String(u.sql).slice(0,200)),u.country!==void 0&&(l.country=u.country),u.language!==void 0&&(l.language=u.language),u.has_datastore!==void 0&&(l.has_datastore=u.has_datastore),u.min_datasets!==void 0&&(l.min_datasets=u.min_datasets),console.log(JSON.stringify(l))}}catch{}let s=await n.handleRequest(t),i=new Headers(s.headers);return i.set("Access-Control-Allow-Origin","*"),i.set("X-Service-Notice","Demo instance - 100k requests/day shared quota"),i.set("X-Recommendation","https://github.com/ondata/ckan-mcp-server#installation"),new Response(s.body,{status:s.status,statusText:s.statusText,headers:i})}catch(r){return console.error("Worker error:",r),new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32603,message:"Internal error",data:r instanceof Error?r.message:String(r)},id:null}),{status:500,headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}})}}return new Response("Not Found",{status:404,headers:{"Access-Control-Allow-Origin":"*"}})}};export{nF as default};
1283
+ </html>`,{headers:{"Content-Type":"text/html; charset=utf-8","Access-Control-Allow-Origin":"*"}});if(t.method==="GET"&&e.pathname==="/health")return new Response(JSON.stringify({status:"ok",version:"0.4.93",tools:20,resources:7,prompts:6,runtime:"cloudflare-workers"}),{headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}});if(e.pathname==="/mcp"){if(t.method!=="POST"&&t.method!=="OPTIONS")return new Response("Method Not Allowed",{status:405,headers:{Allow:"POST, OPTIONS","Access-Control-Allow-Origin":"*"}});if(t.method==="OPTIONS")return new Response(null,{status:204,headers:{"Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"GET, POST, DELETE, OPTIONS","Access-Control-Allow-Headers":"Content-Type, Accept, Authorization, Mcp-Session-Id","Access-Control-Max-Age":"86400"}});try{let r=zb();Tb(r);let n=new qa({sessionIdGenerator:void 0,enableJsonResponse:!0});await r.connect(n);let o=t.clone();try{let a=await o.json();if(a?.method==="tools/call"&&a?.params?.name){let c=a.params.name,u=a.params.arguments??{},l={tool:c,server:u.server_url??u.endpoint_url??""};u.q!==void 0&&(l.q=u.q),u.fq!==void 0&&(l.fq=u.fq),u.query!==void 0&&(l.query=u.query),u.id!==void 0&&(l.id=u.id),u.name!==void 0&&(l.name=u.name),u.pattern!==void 0&&(l.pattern=u.pattern),u.resource_id!==void 0&&(l.resource_id=u.resource_id),u.format_filter!==void 0&&(l.format_filter=u.format_filter),u.sort!==void 0&&(l.sort=u.sort),u.rows!==void 0&&(l.rows=u.rows),u.limit!==void 0&&(l.limit=u.limit),u.sql!==void 0&&(l.sql=String(u.sql).slice(0,200)),u.country!==void 0&&(l.country=u.country),u.language!==void 0&&(l.language=u.language),u.has_datastore!==void 0&&(l.has_datastore=u.has_datastore),u.min_datasets!==void 0&&(l.min_datasets=u.min_datasets),console.log(JSON.stringify(l))}}catch{}let s=await n.handleRequest(t),i=new Headers(s.headers);return i.set("Access-Control-Allow-Origin","*"),i.set("X-Service-Notice","Demo instance - 100k requests/day shared quota"),i.set("X-Recommendation","https://github.com/ondata/ckan-mcp-server#installation"),new Response(s.body,{status:s.status,statusText:s.statusText,headers:i})}catch(r){return console.error("Worker error:",r),new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32603,message:"Internal error",data:r instanceof Error?r.message:String(r)},id:null}),{status:500,headers:{"Content-Type":"application/json","Access-Control-Allow-Origin":"*"}})}}return new Response("Not Found",{status:404,headers:{"Access-Control-Allow-Origin":"*"}})}};export{nF as default};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aborruso/ckan-mcp-server",
3
- "version": "0.4.93",
3
+ "version": "0.4.95",
4
4
  "mcpName": "io.github.aborruso/ckan-mcp-server",
5
5
  "description": "MCP server for interacting with CKAN open data portals",
6
6
  "main": "dist/index.js",