@aborruso/ckan-mcp-server 0.4.0 → 0.4.1
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/CLAUDE.md +2 -2
- package/EXAMPLES.md +50 -0
- package/LOG.md +18 -0
- package/PRD.md +1 -1
- package/REFACTORING.md +2 -1
- package/dist/index.js +30 -19
- package/package.json +1 -1
- package/dist/worker.js +0 -387
package/CLAUDE.md
CHANGED
|
@@ -348,7 +348,7 @@ To test with Claude Desktop, add the MCP configuration to the config file.
|
|
|
348
348
|
### Known Limitations
|
|
349
349
|
|
|
350
350
|
- **Output limit**: 50,000 characters hardcoded in `types.ts` (could be configurable)
|
|
351
|
-
- **Date formatting**: Uses
|
|
351
|
+
- **Date formatting**: Uses fixed ISO `YYYY-MM-DD` in `utils/formatting.ts` (could be configurable)
|
|
352
352
|
- **Read-only**: All tools are read-only (no data modification on CKAN)
|
|
353
353
|
- **No caching**: Every request makes fresh HTTP call to CKAN APIs
|
|
354
354
|
- **No authentication**: Uses only public CKAN endpoints
|
|
@@ -358,7 +358,7 @@ To test with Claude Desktop, add the MCP configuration to the config file.
|
|
|
358
358
|
|
|
359
359
|
1. Create new file in `src/tools/`
|
|
360
360
|
2. Export `registerXxxTools(server: McpServer)` function
|
|
361
|
-
3.
|
|
361
|
+
3. Add to `registerAll()` in `src/server.ts`
|
|
362
362
|
4. Add tests in `tests/integration/`
|
|
363
363
|
5. Build and test: `npm run build && npm test`
|
|
364
364
|
|
package/EXAMPLES.md
CHANGED
|
@@ -289,6 +289,56 @@ ckan_package_search({
|
|
|
289
289
|
|
|
290
290
|
CKAN uses Apache Solr for search. The `q` parameter supports advanced Solr query syntax including fuzzy matching, proximity search, boosting, and complex boolean logic.
|
|
291
291
|
|
|
292
|
+
### Understanding Solr Field Types: Exact vs Fuzzy Search
|
|
293
|
+
|
|
294
|
+
**Important**: CKAN's Solr schema defines two main field types that behave differently in searches:
|
|
295
|
+
|
|
296
|
+
#### String Fields (type="string")
|
|
297
|
+
- **Behavior**: Exact match, case-sensitive, no normalization
|
|
298
|
+
- **Fields**: `res_format`, `tags`, `organization`, `license`, `license_id`, `state`, `name`
|
|
299
|
+
- **Example**: `res_format:CSV` finds 43,836 results, but `res_format:csv` finds 0 results
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
// Works - exact match
|
|
303
|
+
ckan_package_search({
|
|
304
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
305
|
+
fq: "res_format:CSV",
|
|
306
|
+
rows: 10
|
|
307
|
+
})
|
|
308
|
+
// → 43,836 results
|
|
309
|
+
|
|
310
|
+
// Fails - wrong case
|
|
311
|
+
ckan_package_search({
|
|
312
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
313
|
+
fq: "res_format:csv",
|
|
314
|
+
rows: 10
|
|
315
|
+
})
|
|
316
|
+
// → 0 results
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
#### Text Fields (type="text")
|
|
320
|
+
- **Behavior**: Fuzzy search enabled, normalized (accents/punctuation removed), tokenized
|
|
321
|
+
- **Fields**: `title`, `notes`, `author`, `maintainer`, `res_name`, `res_description`
|
|
322
|
+
- **Example**: `title:sanità` also finds "sanita", "sanità", "Sanità" (variations)
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// Fuzzy search automatically applied on text fields
|
|
326
|
+
ckan_package_search({
|
|
327
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
328
|
+
q: "title:sanità~2", // Finds variations with up to 2 character differences
|
|
329
|
+
rows: 20
|
|
330
|
+
})
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
#### Accessing the Schema
|
|
334
|
+
|
|
335
|
+
You can view CKAN's Solr schema to see all field types:
|
|
336
|
+
|
|
337
|
+
1. **GitHub**: https://github.com/ckan/ckan/blob/master/ckan/config/solr/schema.xml
|
|
338
|
+
2. **Solr API** (if available): `http://your-ckan-server:8983/solr/ckan/schema`
|
|
339
|
+
|
|
340
|
+
Note: Public CKAN portals (like dati.gov.it) do not expose the Solr endpoint directly for security reasons - only the CKAN API is public.
|
|
341
|
+
|
|
292
342
|
### Fuzzy Search
|
|
293
343
|
|
|
294
344
|
Find terms with similar spelling (edit distance matching). Useful for typos or variations.
|
package/LOG.md
CHANGED
|
@@ -2,6 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
## 2026-01-10
|
|
4
4
|
|
|
5
|
+
### Version 0.4.1 - Maintenance
|
|
6
|
+
- **Date formatting**: ISO `YYYY-MM-DD` output, tests aligned
|
|
7
|
+
- **HTTP transport**: Single shared transport per process
|
|
8
|
+
- **Registration**: Centralized tool/resource setup via `registerAll()`
|
|
9
|
+
- **Docs**: Updated CLAUDE/PRD/REFACTORING notes
|
|
10
|
+
|
|
11
|
+
## 2026-01-10
|
|
12
|
+
|
|
13
|
+
### Documentation Enhancement - Solr Field Types
|
|
14
|
+
- **New section in EXAMPLES.md**: "Understanding Solr Field Types: Exact vs Fuzzy Search"
|
|
15
|
+
- Documents difference between `type=string` (exact match) and `type=text` (fuzzy)
|
|
16
|
+
- String fields: res_format, tags, organization, license, state, name (case-sensitive)
|
|
17
|
+
- Text fields: title, notes, author, maintainer (normalized, fuzzy enabled)
|
|
18
|
+
- Practical example: `res_format:CSV` (43,836 results) vs `res_format:csv` (0 results)
|
|
19
|
+
- Links to CKAN Solr schema on GitHub
|
|
20
|
+
- Explains why some searches are exact and others are fuzzy
|
|
21
|
+
- **Impact**: Users understand when exact matching is required vs when fuzzy search works
|
|
22
|
+
|
|
5
23
|
### Version 0.4.0 - Cloudflare Workers Deployment ⭐
|
|
6
24
|
|
|
7
25
|
- **Production deployment**: Server now live on Cloudflare Workers
|
package/PRD.md
CHANGED
package/REFACTORING.md
CHANGED
|
@@ -70,7 +70,7 @@ src/
|
|
|
70
70
|
|
|
71
71
|
**`utils/formatting.ts`** (Output Formatting)
|
|
72
72
|
- `truncateText()` - Limita output a CHARACTER_LIMIT
|
|
73
|
-
- `formatDate()` - Format date in
|
|
73
|
+
- `formatDate()` - Format date in ISO `YYYY-MM-DD`
|
|
74
74
|
- `formatBytes()` - Human-readable file sizes
|
|
75
75
|
|
|
76
76
|
### Tools
|
|
@@ -98,6 +98,7 @@ src/
|
|
|
98
98
|
|
|
99
99
|
**`transport/http.ts`** (HTTP Transport)
|
|
100
100
|
- `runHTTP()` - HTTP server on configurable port
|
|
101
|
+
- Single shared transport per process
|
|
101
102
|
- For remote access via HTTP POST
|
|
102
103
|
|
|
103
104
|
## Build Configuration
|
package/dist/index.js
CHANGED
|
@@ -2,12 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
// src/server.ts
|
|
4
4
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
-
function createServer() {
|
|
6
|
-
return new McpServer({
|
|
7
|
-
name: "ckan-mcp-server",
|
|
8
|
-
version: "0.1.0"
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
5
|
|
|
12
6
|
// src/tools/package.ts
|
|
13
7
|
import { z as z2 } from "zod";
|
|
@@ -71,9 +65,16 @@ function truncateText(text, limit = CHARACTER_LIMIT) {
|
|
|
71
65
|
}
|
|
72
66
|
function formatDate(dateStr) {
|
|
73
67
|
try {
|
|
74
|
-
|
|
68
|
+
if (!dateStr) {
|
|
69
|
+
return "Invalid Date";
|
|
70
|
+
}
|
|
71
|
+
const date = new Date(dateStr);
|
|
72
|
+
if (Number.isNaN(date.getTime())) {
|
|
73
|
+
return "Invalid Date";
|
|
74
|
+
}
|
|
75
|
+
return date.toISOString().slice(0, 10);
|
|
75
76
|
} catch {
|
|
76
|
-
return
|
|
77
|
+
return "Invalid Date";
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
|
|
@@ -1227,6 +1228,21 @@ function registerAllResources(server2) {
|
|
|
1227
1228
|
registerOrganizationResource(server2);
|
|
1228
1229
|
}
|
|
1229
1230
|
|
|
1231
|
+
// src/server.ts
|
|
1232
|
+
function createServer() {
|
|
1233
|
+
return new McpServer({
|
|
1234
|
+
name: "ckan-mcp-server",
|
|
1235
|
+
version: "0.4.1"
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
function registerAll(server2) {
|
|
1239
|
+
registerPackageTools(server2);
|
|
1240
|
+
registerOrganizationTools(server2);
|
|
1241
|
+
registerDatastoreTools(server2);
|
|
1242
|
+
registerStatusTools(server2);
|
|
1243
|
+
registerAllResources(server2);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1230
1246
|
// src/transport/stdio.ts
|
|
1231
1247
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1232
1248
|
async function runStdio(server2) {
|
|
@@ -1241,13 +1257,12 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
1241
1257
|
async function runHTTP(server2) {
|
|
1242
1258
|
const app = express();
|
|
1243
1259
|
app.use(express.json());
|
|
1260
|
+
const transport2 = new StreamableHTTPServerTransport({
|
|
1261
|
+
sessionIdGenerator: void 0,
|
|
1262
|
+
enableJsonResponse: true
|
|
1263
|
+
});
|
|
1264
|
+
await server2.connect(transport2);
|
|
1244
1265
|
app.post("/mcp", async (req, res) => {
|
|
1245
|
-
const transport2 = new StreamableHTTPServerTransport({
|
|
1246
|
-
sessionIdGenerator: void 0,
|
|
1247
|
-
enableJsonResponse: true
|
|
1248
|
-
});
|
|
1249
|
-
res.on("close", () => transport2.close());
|
|
1250
|
-
await server2.connect(transport2);
|
|
1251
1266
|
await transport2.handleRequest(req, res, req.body);
|
|
1252
1267
|
});
|
|
1253
1268
|
const port = parseInt(process.env.PORT || "3000");
|
|
@@ -1258,11 +1273,7 @@ async function runHTTP(server2) {
|
|
|
1258
1273
|
|
|
1259
1274
|
// src/index.ts
|
|
1260
1275
|
var server = createServer();
|
|
1261
|
-
|
|
1262
|
-
registerOrganizationTools(server);
|
|
1263
|
-
registerDatastoreTools(server);
|
|
1264
|
-
registerStatusTools(server);
|
|
1265
|
-
registerAllResources(server);
|
|
1276
|
+
registerAll(server);
|
|
1266
1277
|
var transport = process.env.TRANSPORT || "stdio";
|
|
1267
1278
|
if (transport === "http") {
|
|
1268
1279
|
runHTTP(server).catch((error) => {
|