@aborruso/ckan-mcp-server 0.4.6 → 0.4.8
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 +10 -0
- package/LOG.md +57 -0
- package/PRD.md +339 -252
- package/README.md +39 -36
- package/dist/index.js +160 -21
- package/dist/worker.js +306 -74
- package/openspec/changes/update-search-parser-config/proposal.md +13 -0
- package/openspec/changes/update-search-parser-config/specs/ckan-insights/spec.md +11 -0
- package/openspec/changes/update-search-parser-config/specs/ckan-search/spec.md +11 -0
- package/openspec/changes/update-search-parser-config/tasks.md +6 -0
- package/openspec/project.md +9 -7
- package/openspec/specs/ckan-insights/spec.md +8 -1
- package/package.json +1 -1
- package/web-gui/PRD.md +158 -0
- package/web-gui/public/index.html +883 -0
- /package/openspec/changes/{add-ckan-find-relevant-datasets → archive/2026-01-10-add-ckan-find-relevant-datasets}/proposal.md +0 -0
- /package/openspec/changes/{add-ckan-find-relevant-datasets → archive/2026-01-10-add-ckan-find-relevant-datasets}/specs/ckan-insights/spec.md +0 -0
- /package/openspec/changes/{add-ckan-find-relevant-datasets → archive/2026-01-10-add-ckan-find-relevant-datasets}/tasks.md +0 -0
package/README.md
CHANGED
|
@@ -40,27 +40,11 @@ npm run build
|
|
|
40
40
|
npm test
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
## Usage
|
|
44
|
-
|
|
45
|
-
### Start with stdio (for local integration)
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
npm start
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Start with HTTP (for remote access)
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
TRANSPORT=http PORT=3000 npm start
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
The server will be available at `http://localhost:3000/mcp`
|
|
58
|
-
|
|
59
43
|
## Usage Options
|
|
60
44
|
|
|
61
45
|
### Option 1: Local Installation (stdio mode)
|
|
62
46
|
|
|
63
|
-
**Best for**: Personal use with
|
|
47
|
+
**Best for**: Personal use with local MCP clients
|
|
64
48
|
|
|
65
49
|
Install and run locally on your machine (see Installation section above).
|
|
66
50
|
|
|
@@ -90,9 +74,11 @@ Use the public Workers endpoint (no local install required):
|
|
|
90
74
|
}
|
|
91
75
|
```
|
|
92
76
|
|
|
77
|
+
**NOTE**: This service uses the Cloudflare Workers free tier which has a limit of 100,000 requests per month.
|
|
78
|
+
|
|
93
79
|
Want your own deployment? See [DEPLOYMENT.md](docs/DEPLOYMENT.md).
|
|
94
80
|
|
|
95
|
-
|
|
81
|
+
### Claude Desktop Configuration
|
|
96
82
|
|
|
97
83
|
Configuration file location:
|
|
98
84
|
|
|
@@ -100,7 +86,7 @@ Configuration file location:
|
|
|
100
86
|
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
101
87
|
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
|
|
102
88
|
|
|
103
|
-
|
|
89
|
+
#### Option 1: Global Installation (Recommended)
|
|
104
90
|
|
|
105
91
|
Install globally to use across all projects:
|
|
106
92
|
|
|
@@ -120,7 +106,7 @@ Then add to `claude_desktop_config.json`:
|
|
|
120
106
|
}
|
|
121
107
|
```
|
|
122
108
|
|
|
123
|
-
|
|
109
|
+
#### Option 2: Local Installation (Optional)
|
|
124
110
|
|
|
125
111
|
If you installed locally (see Installation), use this config:
|
|
126
112
|
|
|
@@ -137,7 +123,7 @@ If you installed locally (see Installation), use this config:
|
|
|
137
123
|
|
|
138
124
|
Replace `/absolute/path/to/project` with your actual project path.
|
|
139
125
|
|
|
140
|
-
|
|
126
|
+
#### Option 3: From Source
|
|
141
127
|
|
|
142
128
|
If you cloned the repository:
|
|
143
129
|
|
|
@@ -152,7 +138,7 @@ If you cloned the repository:
|
|
|
152
138
|
}
|
|
153
139
|
```
|
|
154
140
|
|
|
155
|
-
|
|
141
|
+
#### Option 4: Cloudflare Workers (HTTP transport)
|
|
156
142
|
|
|
157
143
|
Use the public Cloudflare Workers deployment (no local installation required):
|
|
158
144
|
|
|
@@ -166,6 +152,8 @@ Use the public Cloudflare Workers deployment (no local installation required):
|
|
|
166
152
|
}
|
|
167
153
|
```
|
|
168
154
|
|
|
155
|
+
**NOTE**: This service uses the Cloudflare Workers free tier which has a limit of 100,000 requests per month.
|
|
156
|
+
|
|
169
157
|
**Note**: This uses the public endpoint. You can also deploy your own Workers instance and use that URL instead.
|
|
170
158
|
|
|
171
159
|
## Available Tools
|
|
@@ -226,6 +214,17 @@ ckan_package_search({
|
|
|
226
214
|
})
|
|
227
215
|
```
|
|
228
216
|
|
|
217
|
+
### Force text-field parser for long OR queries
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
ckan_package_search({
|
|
221
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
222
|
+
q: "hotel OR alberghi OR \"strutture ricettive\" OR ospitalità OR ricettività",
|
|
223
|
+
query_parser: "text",
|
|
224
|
+
rows: 0
|
|
225
|
+
})
|
|
226
|
+
```
|
|
227
|
+
|
|
229
228
|
### Rank datasets by relevance
|
|
230
229
|
|
|
231
230
|
```typescript
|
|
@@ -477,28 +476,32 @@ ckan_package_search({
|
|
|
477
476
|
```
|
|
478
477
|
ckan-mcp-server/
|
|
479
478
|
├── src/
|
|
480
|
-
│ ├── index.ts
|
|
481
|
-
│ ├── server.ts
|
|
482
|
-
│ ├──
|
|
479
|
+
│ ├── index.ts # Entry point
|
|
480
|
+
│ ├── server.ts # MCP server setup
|
|
481
|
+
│ ├── worker.ts # Cloudflare Workers entry
|
|
482
|
+
│ ├── types.ts # Types & schemas
|
|
483
483
|
│ ├── utils/
|
|
484
|
-
│ │ ├── http.ts
|
|
485
|
-
│ │
|
|
484
|
+
│ │ ├── http.ts # CKAN API client
|
|
485
|
+
│ │ ├── formatting.ts # Output formatting
|
|
486
|
+
│ │ └── url-generator.ts
|
|
486
487
|
│ ├── tools/
|
|
487
|
-
│ │ ├── package.ts
|
|
488
|
+
│ │ ├── package.ts # Package search/show
|
|
488
489
|
│ │ ├── organization.ts # Organization tools
|
|
489
|
-
│ │ ├── datastore.ts
|
|
490
|
-
│ │
|
|
491
|
-
│ ├──
|
|
490
|
+
│ │ ├── datastore.ts # DataStore queries
|
|
491
|
+
│ │ ├── status.ts # Server status
|
|
492
|
+
│ │ ├── tag.ts # Tag tools
|
|
493
|
+
│ │ └── group.ts # Group tools
|
|
494
|
+
│ ├── resources/ # MCP Resource Templates
|
|
492
495
|
│ │ ├── index.ts
|
|
493
|
-
│ │ ├── uri.ts
|
|
496
|
+
│ │ ├── uri.ts # URI parsing
|
|
494
497
|
│ │ ├── dataset.ts
|
|
495
498
|
│ │ ├── resource.ts
|
|
496
499
|
│ │ └── organization.ts
|
|
497
500
|
│ └── transport/
|
|
498
|
-
│ ├── stdio.ts
|
|
499
|
-
│ └── http.ts
|
|
500
|
-
├── tests/
|
|
501
|
-
├── dist/
|
|
501
|
+
│ ├── stdio.ts # Stdio transport
|
|
502
|
+
│ └── http.ts # HTTP transport
|
|
503
|
+
├── tests/ # Test suite (120 tests)
|
|
504
|
+
├── dist/ # Compiled files (generated)
|
|
502
505
|
├── package.json
|
|
503
506
|
└── README.md
|
|
504
507
|
```
|
package/dist/index.js
CHANGED
|
@@ -90,41 +90,82 @@ var portals_default = {
|
|
|
90
90
|
"http://www.dati.gov.it/opendata",
|
|
91
91
|
"http://dati.gov.it/opendata"
|
|
92
92
|
],
|
|
93
|
+
search: {
|
|
94
|
+
force_text_field: true
|
|
95
|
+
},
|
|
93
96
|
dataset_view_url: "https://www.dati.gov.it/view-dataset/dataset?id={id}",
|
|
94
97
|
organization_view_url: "https://www.dati.gov.it/view-dataset?organization={name}"
|
|
95
98
|
}
|
|
96
99
|
],
|
|
97
100
|
defaults: {
|
|
98
101
|
dataset_view_url: "{server_url}/dataset/{name}",
|
|
99
|
-
organization_view_url: "{server_url}/organization/{name}"
|
|
102
|
+
organization_view_url: "{server_url}/organization/{name}",
|
|
103
|
+
search: {
|
|
104
|
+
force_text_field: false
|
|
105
|
+
}
|
|
100
106
|
}
|
|
101
107
|
};
|
|
102
108
|
|
|
103
|
-
// src/utils/
|
|
109
|
+
// src/utils/portal-config.ts
|
|
104
110
|
function normalizeUrl(url) {
|
|
105
111
|
return url.replace(/\/$/, "");
|
|
106
112
|
}
|
|
107
|
-
function
|
|
113
|
+
function getPortalConfig(serverUrl) {
|
|
108
114
|
const cleanServerUrl = normalizeUrl(serverUrl);
|
|
109
115
|
const portal = portals_default.portals.find((p) => {
|
|
110
116
|
const mainUrl = normalizeUrl(p.api_url);
|
|
111
117
|
const aliases = (p.api_url_aliases || []).map(normalizeUrl);
|
|
112
118
|
return mainUrl === cleanServerUrl || aliases.includes(cleanServerUrl);
|
|
113
119
|
});
|
|
120
|
+
return portal || null;
|
|
121
|
+
}
|
|
122
|
+
function getPortalSearchConfig(serverUrl) {
|
|
123
|
+
const portal = getPortalConfig(serverUrl);
|
|
124
|
+
const defaults = portals_default.defaults?.search || {};
|
|
125
|
+
return {
|
|
126
|
+
force_text_field: portal?.search?.force_text_field ?? defaults.force_text_field ?? false
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function normalizePortalUrl(serverUrl) {
|
|
130
|
+
return normalizeUrl(serverUrl);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/utils/url-generator.ts
|
|
134
|
+
function getDatasetViewUrl(serverUrl, pkg) {
|
|
135
|
+
const cleanServerUrl = normalizePortalUrl(serverUrl);
|
|
136
|
+
const portal = getPortalConfig(serverUrl);
|
|
114
137
|
const template = portal?.dataset_view_url || portals_default.defaults.dataset_view_url;
|
|
115
138
|
return template.replace("{server_url}", cleanServerUrl).replace("{id}", pkg.id).replace("{name}", pkg.name);
|
|
116
139
|
}
|
|
117
140
|
function getOrganizationViewUrl(serverUrl, org) {
|
|
118
|
-
const cleanServerUrl =
|
|
119
|
-
const portal =
|
|
120
|
-
const mainUrl = normalizeUrl(p.api_url);
|
|
121
|
-
const aliases = (p.api_url_aliases || []).map(normalizeUrl);
|
|
122
|
-
return mainUrl === cleanServerUrl || aliases.includes(cleanServerUrl);
|
|
123
|
-
});
|
|
141
|
+
const cleanServerUrl = normalizePortalUrl(serverUrl);
|
|
142
|
+
const portal = getPortalConfig(serverUrl);
|
|
124
143
|
const template = portal?.organization_view_url || portals_default.defaults.organization_view_url;
|
|
125
144
|
return template.replace("{server_url}", cleanServerUrl).replace("{id}", org.id).replace("{name}", org.name);
|
|
126
145
|
}
|
|
127
146
|
|
|
147
|
+
// src/utils/search.ts
|
|
148
|
+
var DEFAULT_SEARCH_QUERY = "*:*";
|
|
149
|
+
var FIELD_QUERY_PATTERN = /\b[a-zA-Z_][\w-]*:/;
|
|
150
|
+
function isFieldedQuery(query) {
|
|
151
|
+
return FIELD_QUERY_PATTERN.test(query);
|
|
152
|
+
}
|
|
153
|
+
function resolveSearchQuery(serverUrl, query, parserOverride) {
|
|
154
|
+
const portalSearchConfig = getPortalSearchConfig(serverUrl);
|
|
155
|
+
const portalForce = portalSearchConfig.force_text_field ?? false;
|
|
156
|
+
let forceTextField = false;
|
|
157
|
+
if (parserOverride === "text") {
|
|
158
|
+
forceTextField = true;
|
|
159
|
+
} else if (parserOverride === "default") {
|
|
160
|
+
forceTextField = false;
|
|
161
|
+
} else if (portalForce) {
|
|
162
|
+
const trimmedQuery = query.trim();
|
|
163
|
+
forceTextField = trimmedQuery !== DEFAULT_SEARCH_QUERY && !isFieldedQuery(trimmedQuery);
|
|
164
|
+
}
|
|
165
|
+
const effectiveQuery = forceTextField ? `text:(${query})` : query;
|
|
166
|
+
return { effectiveQuery, forcedTextField: forceTextField };
|
|
167
|
+
}
|
|
168
|
+
|
|
128
169
|
// src/tools/package.ts
|
|
129
170
|
var DEFAULT_RELEVANCE_WEIGHTS = {
|
|
130
171
|
title: 4,
|
|
@@ -221,6 +262,11 @@ function registerPackageTools(server2) {
|
|
|
221
262
|
Supports full Solr search capabilities including filters, facets, and sorting.
|
|
222
263
|
Use this to discover datasets matching specific criteria.
|
|
223
264
|
|
|
265
|
+
Note on parser behavior:
|
|
266
|
+
Some CKAN portals use a restrictive default query parser that can break long OR queries.
|
|
267
|
+
For those portals, this tool may force the query into 'text:(...)' based on per-portal config.
|
|
268
|
+
You can override with 'query_parser' to force or disable this behavior per request.
|
|
269
|
+
|
|
224
270
|
Args:
|
|
225
271
|
- server_url (string): Base URL of CKAN server (e.g., "https://dati.gov.it/opendata")
|
|
226
272
|
- q (string): Search query using Solr syntax (default: "*:*" for all)
|
|
@@ -231,6 +277,7 @@ Args:
|
|
|
231
277
|
- facet_field (array): Fields to facet on (e.g., ["organization", "tags"])
|
|
232
278
|
- facet_limit (number): Max facet values per field (default: 50)
|
|
233
279
|
- include_drafts (boolean): Include draft datasets (default: false)
|
|
280
|
+
- query_parser ('default' | 'text'): Override search parser behavior
|
|
234
281
|
- response_format ('markdown' | 'json'): Output format
|
|
235
282
|
|
|
236
283
|
Returns:
|
|
@@ -300,6 +347,7 @@ Examples:
|
|
|
300
347
|
facet_field: z2.array(z2.string()).optional().describe("Fields to facet on"),
|
|
301
348
|
facet_limit: z2.number().int().min(1).optional().default(50).describe("Maximum facet values per field"),
|
|
302
349
|
include_drafts: z2.boolean().optional().default(false).describe("Include draft datasets"),
|
|
350
|
+
query_parser: z2.enum(["default", "text"]).optional().describe("Override search parser ('text' forces text:(...) on non-fielded queries)"),
|
|
303
351
|
response_format: ResponseFormatSchema
|
|
304
352
|
}).strict(),
|
|
305
353
|
annotations: {
|
|
@@ -311,8 +359,13 @@ Examples:
|
|
|
311
359
|
},
|
|
312
360
|
async (params) => {
|
|
313
361
|
try {
|
|
362
|
+
const { effectiveQuery } = resolveSearchQuery(
|
|
363
|
+
params.server_url,
|
|
364
|
+
params.q,
|
|
365
|
+
params.query_parser
|
|
366
|
+
);
|
|
314
367
|
const apiParams = {
|
|
315
|
-
q:
|
|
368
|
+
q: effectiveQuery,
|
|
316
369
|
rows: params.rows,
|
|
317
370
|
start: params.start,
|
|
318
371
|
include_private: params.include_drafts
|
|
@@ -338,6 +391,8 @@ Examples:
|
|
|
338
391
|
|
|
339
392
|
**Server**: ${params.server_url}
|
|
340
393
|
**Query**: ${params.q}
|
|
394
|
+
${effectiveQuery !== params.q ? `**Effective Query**: ${effectiveQuery}
|
|
395
|
+
` : ""}
|
|
341
396
|
${params.fq ? `**Filter**: ${params.fq}
|
|
342
397
|
` : ""}
|
|
343
398
|
**Total Results**: ${result.count}
|
|
@@ -356,6 +411,15 @@ ${params.fq ? `**Filter**: ${params.fq}
|
|
|
356
411
|
const sorted = Object.entries(facetValues).sort((a, b) => b[1] - a[1]).slice(0, 10);
|
|
357
412
|
for (const [value, count] of sorted) {
|
|
358
413
|
markdown += `- **${value}**: ${count}
|
|
414
|
+
`;
|
|
415
|
+
}
|
|
416
|
+
if (Object.keys(facetValues).length > sorted.length) {
|
|
417
|
+
markdown += `
|
|
418
|
+
Note: showing top ${sorted.length} only. Use \`response_format: json\` or increase \`facet_limit\`.
|
|
419
|
+
`;
|
|
420
|
+
} else {
|
|
421
|
+
markdown += `
|
|
422
|
+
Note: showing top ${sorted.length} only. Use \`response_format: json\` for full list.
|
|
359
423
|
`;
|
|
360
424
|
}
|
|
361
425
|
markdown += "\n";
|
|
@@ -433,6 +497,7 @@ Args:
|
|
|
433
497
|
- query (string): Search query text
|
|
434
498
|
- limit (number): Number of datasets to return (default: 10)
|
|
435
499
|
- weights (object): Optional weights for title/notes/tags/organization
|
|
500
|
+
- query_parser ('default' | 'text'): Override search parser behavior
|
|
436
501
|
- response_format ('markdown' | 'json'): Output format
|
|
437
502
|
|
|
438
503
|
Returns:
|
|
@@ -451,6 +516,7 @@ Examples:
|
|
|
451
516
|
tags: z2.number().min(0).optional(),
|
|
452
517
|
organization: z2.number().min(0).optional()
|
|
453
518
|
}).optional().describe("Optional weights per field"),
|
|
519
|
+
query_parser: z2.enum(["default", "text"]).optional().describe("Override search parser ('text' forces text:(...) on non-fielded queries)"),
|
|
454
520
|
response_format: ResponseFormatSchema
|
|
455
521
|
}).strict(),
|
|
456
522
|
annotations: {
|
|
@@ -467,11 +533,16 @@ Examples:
|
|
|
467
533
|
...params.weights ?? {}
|
|
468
534
|
};
|
|
469
535
|
const rows = Math.min(Math.max(params.limit * 5, params.limit), 100);
|
|
536
|
+
const { effectiveQuery } = resolveSearchQuery(
|
|
537
|
+
params.server_url,
|
|
538
|
+
params.query,
|
|
539
|
+
params.query_parser
|
|
540
|
+
);
|
|
470
541
|
const searchResult = await makeCkanRequest(
|
|
471
542
|
params.server_url,
|
|
472
543
|
"package_search",
|
|
473
544
|
{
|
|
474
|
-
q:
|
|
545
|
+
q: effectiveQuery,
|
|
475
546
|
rows,
|
|
476
547
|
start: 0
|
|
477
548
|
}
|
|
@@ -835,16 +906,84 @@ Returns:
|
|
|
835
906
|
content: [{ type: "text", text: markdown2 }]
|
|
836
907
|
};
|
|
837
908
|
}
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
909
|
+
let result;
|
|
910
|
+
try {
|
|
911
|
+
result = await makeCkanRequest(
|
|
912
|
+
params.server_url,
|
|
913
|
+
"organization_list",
|
|
914
|
+
{
|
|
915
|
+
all_fields: params.all_fields,
|
|
916
|
+
sort: params.sort,
|
|
917
|
+
limit: params.limit,
|
|
918
|
+
offset: params.offset
|
|
919
|
+
}
|
|
920
|
+
);
|
|
921
|
+
} catch (error) {
|
|
922
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
923
|
+
if (message.includes("CKAN API error (500)")) {
|
|
924
|
+
const searchResult = await makeCkanRequest(
|
|
925
|
+
params.server_url,
|
|
926
|
+
"package_search",
|
|
927
|
+
{
|
|
928
|
+
rows: 0,
|
|
929
|
+
"facet.field": JSON.stringify(["organization"]),
|
|
930
|
+
"facet.limit": -1
|
|
931
|
+
}
|
|
932
|
+
);
|
|
933
|
+
const items = searchResult.search_facets?.organization?.items || [];
|
|
934
|
+
const sortValue = params.sort?.toLowerCase() ?? "name asc";
|
|
935
|
+
const sortedItems = [...items].sort((a, b) => {
|
|
936
|
+
if (sortValue.includes("package_count") || sortValue.includes("count")) {
|
|
937
|
+
return b.count - a.count;
|
|
938
|
+
}
|
|
939
|
+
if (sortValue.includes("name desc")) {
|
|
940
|
+
return String(b.name).localeCompare(String(a.name));
|
|
941
|
+
}
|
|
942
|
+
return String(a.name).localeCompare(String(b.name));
|
|
943
|
+
});
|
|
944
|
+
const pagedItems = sortedItems.slice(params.offset, params.offset + params.limit);
|
|
945
|
+
const organizations = pagedItems.map((item) => ({
|
|
946
|
+
id: item.name,
|
|
947
|
+
name: item.name,
|
|
948
|
+
title: item.display_name || item.name,
|
|
949
|
+
package_count: item.count
|
|
950
|
+
}));
|
|
951
|
+
if (params.response_format === "json" /* JSON */) {
|
|
952
|
+
const output = { count: items.length, organizations };
|
|
953
|
+
return {
|
|
954
|
+
content: [{ type: "text", text: truncateText(JSON.stringify(output, null, 2)) }],
|
|
955
|
+
structuredContent: output
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
let markdown2 = `# CKAN Organizations
|
|
959
|
+
|
|
960
|
+
`;
|
|
961
|
+
markdown2 += `**Server**: ${params.server_url}
|
|
962
|
+
`;
|
|
963
|
+
markdown2 += `**Total**: ${items.length}
|
|
964
|
+
`;
|
|
965
|
+
markdown2 += `
|
|
966
|
+
Note: organization_list returned 500; using package_search facets.
|
|
967
|
+
|
|
968
|
+
`;
|
|
969
|
+
for (const org of organizations) {
|
|
970
|
+
markdown2 += `## ${org.title || org.name}
|
|
971
|
+
|
|
972
|
+
`;
|
|
973
|
+
markdown2 += `- **Name**: \`${org.name}\`
|
|
974
|
+
`;
|
|
975
|
+
markdown2 += `- **Datasets**: ${org.package_count || 0}
|
|
976
|
+
`;
|
|
977
|
+
markdown2 += `- **Link**: ${getOrganizationViewUrl(params.server_url, org)}
|
|
978
|
+
|
|
979
|
+
`;
|
|
980
|
+
}
|
|
981
|
+
return {
|
|
982
|
+
content: [{ type: "text", text: truncateText(markdown2) }]
|
|
983
|
+
};
|
|
846
984
|
}
|
|
847
|
-
|
|
985
|
+
throw error;
|
|
986
|
+
}
|
|
848
987
|
if (params.response_format === "json" /* JSON */) {
|
|
849
988
|
const output = Array.isArray(result) ? { count: result.length, organizations: result } : result;
|
|
850
989
|
return {
|
|
@@ -2087,7 +2226,7 @@ function registerAllResources(server2) {
|
|
|
2087
2226
|
function createServer() {
|
|
2088
2227
|
return new McpServer({
|
|
2089
2228
|
name: "ckan-mcp-server",
|
|
2090
|
-
version: "0.4.
|
|
2229
|
+
version: "0.4.8"
|
|
2091
2230
|
});
|
|
2092
2231
|
}
|
|
2093
2232
|
function registerAll(server2) {
|