@aborruso/ckan-mcp-server 0.4.39 → 0.4.40
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 +27 -5
- package/README.md +2 -9
- package/dist/index.js +386 -20
- package/dist/worker.js +374 -74
- package/package.json +1 -1
package/LOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# LOG
|
|
2
2
|
|
|
3
|
+
## 2026-02-20
|
|
4
|
+
|
|
5
|
+
- Add DataStore Table Viewer (MCP Apps interactive UI)
|
|
6
|
+
- New MCP resource `ckan-ui://datastore-table` serves self-contained HTML table viewer
|
|
7
|
+
- `ckan_datastore_search` now returns `_meta.ui.resourceUri` + `_meta.ui.data` for MCP Apps clients
|
|
8
|
+
- Interactive features: sortable columns (numeric/date/string type-aware), text filter, pagination (10/25/50/100 rows/page)
|
|
9
|
+
- Works in Node.js and Cloudflare Workers (HTML inlined in TypeScript module, no fs dependency)
|
|
10
|
+
- Non-breaking: text/markdown output unchanged; non-MCP-Apps clients ignore `_meta`
|
|
11
|
+
- Files: `src/ui/datastore-table.html`, `src/resources/datastore-table-ui.ts`, updated `src/tools/datastore.ts` and `src/resources/index.ts`
|
|
12
|
+
- Tests: 228 passing (7 new)
|
|
13
|
+
- Ideas: `docs/future-ideas.md` updated with MCP Apps section
|
|
14
|
+
- OpenSpec: `add-datastore-table-viewer` created and implemented
|
|
15
|
+
|
|
16
|
+
## 2026-02-09
|
|
17
|
+
|
|
18
|
+
### Feature Request - One-click Installation
|
|
19
|
+
|
|
20
|
+
- Created issue #11 for one-click MCP server installation support
|
|
21
|
+
- Proposes `claude://install-mcp-server` protocol integration
|
|
22
|
+
- Based on Anthropic Desktop Extensions announcement
|
|
23
|
+
- **Updated proposal**: Two one-click installers to serve different use cases
|
|
24
|
+
- 🚀 **"Try it now"** → HTTP Worker (instant, zero install, shared quota)
|
|
25
|
+
- 💪 **"Install locally"** → npx (unlimited, requires Node.js)
|
|
26
|
+
- User journey: try demo first, install locally when ready for production
|
|
27
|
+
- Files: GitHub issue #11 + comment
|
|
28
|
+
|
|
3
29
|
## 2026-02-02
|
|
4
30
|
|
|
5
31
|
### Release v0.4.39 - Local Install Promotion
|
|
@@ -17,7 +43,7 @@
|
|
|
17
43
|
|
|
18
44
|
**Footer shown to Workers users**:
|
|
19
45
|
```
|
|
20
|
-
ℹ️ Demo instance (100k requests/
|
|
46
|
+
ℹ️ Demo instance (100k requests/day shared quota). For unlimited access: https://github.com/ondata/ckan-mcp-server#installation
|
|
21
47
|
```
|
|
22
48
|
|
|
23
49
|
**Files modified**:
|
|
@@ -478,10 +504,6 @@
|
|
|
478
504
|
- **Fix**: Normalize natural-language queries before search
|
|
479
505
|
- **Gemini**: Added API key input and NL→Solr query call
|
|
480
506
|
|
|
481
|
-
### Web GUI landing + Pages deploy
|
|
482
|
-
- **Web GUI**: Added static landing page in `web-gui/public`
|
|
483
|
-
- **CI**: Added GitHub Pages workflow for auto deploy on HTML changes
|
|
484
|
-
|
|
485
507
|
## 2026-01-10
|
|
486
508
|
|
|
487
509
|
### Version 0.4.7 - Portal search parser override
|
package/README.md
CHANGED
|
@@ -24,19 +24,12 @@ MCP (Model Context Protocol) server for interacting with CKAN-based open data po
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
-
>
|
|
28
|
-
>
|
|
27
|
+
> **💡 Local installation available** for unlimited access and better performance:
|
|
29
28
|
> ```bash
|
|
30
29
|
> npm install -g @aborruso/ckan-mcp-server
|
|
31
30
|
> ```
|
|
32
31
|
>
|
|
33
|
-
>
|
|
34
|
-
> - ✅ **No request limits** - unlimited queries
|
|
35
|
-
> - ✅ **Faster** - no network latency
|
|
36
|
-
> - ✅ **Always available** - no shared quota
|
|
37
|
-
> - ✅ **Free** - open source, no costs
|
|
38
|
-
>
|
|
39
|
-
> The Cloudflare Workers endpoint is only for quick testing (100k requests/month shared globally across all users).
|
|
32
|
+
> The Cloudflare Workers endpoint has 100k requests/day shared quota - sufficient for most users, but local installation is recommended for heavy usage.
|
|
40
33
|
|
|
41
34
|
---
|
|
42
35
|
|
package/dist/index.js
CHANGED
|
@@ -57,8 +57,8 @@ var portals_default = {
|
|
|
57
57
|
"http://www.data.gov.uk"
|
|
58
58
|
],
|
|
59
59
|
api_path: "/api/action",
|
|
60
|
-
dataset_view_url: "https://data.gov.uk/dataset/{name}",
|
|
61
|
-
organization_view_url: "https://data.gov.uk/
|
|
60
|
+
dataset_view_url: "https://www.data.gov.uk/dataset/{id}/{name}",
|
|
61
|
+
organization_view_url: "https://www.data.gov.uk/organization/{name}"
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
id: "catalog-data-gov",
|
|
@@ -86,7 +86,9 @@ var portals_default = {
|
|
|
86
86
|
"http://data.gov.au",
|
|
87
87
|
"https://www.data.gov.au",
|
|
88
88
|
"http://www.data.gov.au"
|
|
89
|
-
]
|
|
89
|
+
],
|
|
90
|
+
dataset_view_url: "https://data.gov.au/data/dataset/{name}",
|
|
91
|
+
organization_view_url: "https://data.gov.au/data/organization/{name}"
|
|
90
92
|
},
|
|
91
93
|
{
|
|
92
94
|
id: "opendata-swiss",
|
|
@@ -98,6 +100,20 @@ var portals_default = {
|
|
|
98
100
|
"https://www.opendata.swiss",
|
|
99
101
|
"http://www.opendata.swiss"
|
|
100
102
|
]
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "govdata-de",
|
|
106
|
+
name: "govdata.de",
|
|
107
|
+
api_url: "https://ckan.govdata.de",
|
|
108
|
+
api_url_aliases: [
|
|
109
|
+
"https://www.govdata.de",
|
|
110
|
+
"https://govdata.de",
|
|
111
|
+
"https://www.govdata.de/daten",
|
|
112
|
+
"https://data.gov.de",
|
|
113
|
+
"https://www.data.gov.de"
|
|
114
|
+
],
|
|
115
|
+
dataset_view_url: "https://ckan.govdata.de/dataset/{name}",
|
|
116
|
+
organization_view_url: "https://ckan.govdata.de/organization/{name}"
|
|
101
117
|
}
|
|
102
118
|
],
|
|
103
119
|
defaults: {
|
|
@@ -412,6 +428,20 @@ function formatDate(dateStr) {
|
|
|
412
428
|
return "Invalid Date";
|
|
413
429
|
}
|
|
414
430
|
}
|
|
431
|
+
function isWorkers() {
|
|
432
|
+
try {
|
|
433
|
+
return typeof WorkerGlobalScope !== "undefined";
|
|
434
|
+
} catch {
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function addDemoFooter(text) {
|
|
439
|
+
if (!isWorkers()) {
|
|
440
|
+
return text;
|
|
441
|
+
}
|
|
442
|
+
const footer = "\n\n---\n\u2139\uFE0F Demo instance (100k requests/day shared quota). For unlimited access: https://github.com/ondata/ckan-mcp-server#installation";
|
|
443
|
+
return text + footer;
|
|
444
|
+
}
|
|
415
445
|
|
|
416
446
|
// src/utils/url-generator.ts
|
|
417
447
|
function getDatasetViewUrl(serverUrl, pkg) {
|
|
@@ -1008,7 +1038,7 @@ Note: showing top ${sorted.length} only. Use \`response_format: json\` for full
|
|
|
1008
1038
|
`;
|
|
1009
1039
|
}
|
|
1010
1040
|
return {
|
|
1011
|
-
content: [{ type: "text", text: truncateText(markdown) }]
|
|
1041
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
1012
1042
|
};
|
|
1013
1043
|
} catch (error) {
|
|
1014
1044
|
return {
|
|
@@ -1186,7 +1216,7 @@ Examples:
|
|
|
1186
1216
|
});
|
|
1187
1217
|
}
|
|
1188
1218
|
return {
|
|
1189
|
-
content: [{ type: "text", text: truncateText(markdown) }]
|
|
1219
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
1190
1220
|
};
|
|
1191
1221
|
} catch (error) {
|
|
1192
1222
|
return {
|
|
@@ -1257,7 +1287,7 @@ Examples:
|
|
|
1257
1287
|
}
|
|
1258
1288
|
const markdown = formatPackageShowMarkdown(result, params.server_url);
|
|
1259
1289
|
return {
|
|
1260
|
-
content: [{ type: "text", text: truncateText(markdown) }]
|
|
1290
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
1261
1291
|
};
|
|
1262
1292
|
} catch (error) {
|
|
1263
1293
|
return {
|
|
@@ -1409,7 +1439,7 @@ Note: organization_list returned 500; using package_search facets.
|
|
|
1409
1439
|
`;
|
|
1410
1440
|
}
|
|
1411
1441
|
return {
|
|
1412
|
-
content: [{ type: "text", text: truncateText(markdown2) }]
|
|
1442
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown2)) }]
|
|
1413
1443
|
};
|
|
1414
1444
|
}
|
|
1415
1445
|
throw error;
|
|
@@ -1454,7 +1484,7 @@ Note: organization_list returned 500; using package_search facets.
|
|
|
1454
1484
|
}
|
|
1455
1485
|
}
|
|
1456
1486
|
return {
|
|
1457
|
-
content: [{ type: "text", text: truncateText(markdown) }]
|
|
1487
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
1458
1488
|
};
|
|
1459
1489
|
} catch (error) {
|
|
1460
1490
|
return {
|
|
@@ -1568,7 +1598,7 @@ ${result.description}
|
|
|
1568
1598
|
markdown += "\n";
|
|
1569
1599
|
}
|
|
1570
1600
|
return {
|
|
1571
|
-
content: [{ type: "text", text: truncateText(markdown) }]
|
|
1601
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
1572
1602
|
};
|
|
1573
1603
|
} catch (error) {
|
|
1574
1604
|
return {
|
|
@@ -1672,7 +1702,7 @@ Examples:
|
|
|
1672
1702
|
}
|
|
1673
1703
|
}
|
|
1674
1704
|
return {
|
|
1675
|
-
content: [{ type: "text", text: truncateText(markdown) }]
|
|
1705
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
1676
1706
|
};
|
|
1677
1707
|
} catch (error) {
|
|
1678
1708
|
return {
|
|
@@ -1810,7 +1840,19 @@ Examples:
|
|
|
1810
1840
|
`;
|
|
1811
1841
|
}
|
|
1812
1842
|
return {
|
|
1813
|
-
content: [{ type: "text", text: truncateText(markdown) }]
|
|
1843
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }],
|
|
1844
|
+
_meta: {
|
|
1845
|
+
ui: {
|
|
1846
|
+
resourceUri: "ckan-ui://datastore-table",
|
|
1847
|
+
data: {
|
|
1848
|
+
server_url: params.server_url,
|
|
1849
|
+
resource_id: params.resource_id,
|
|
1850
|
+
total: result.total || 0,
|
|
1851
|
+
fields: result.fields || [],
|
|
1852
|
+
records: result.records || []
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1814
1856
|
};
|
|
1815
1857
|
} catch (error) {
|
|
1816
1858
|
return {
|
|
@@ -1914,7 +1956,7 @@ Examples:
|
|
|
1914
1956
|
markdown += "No records returned by the SQL query.\n";
|
|
1915
1957
|
}
|
|
1916
1958
|
return {
|
|
1917
|
-
content: [{ type: "text", text: truncateText(markdown) }]
|
|
1959
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
1918
1960
|
};
|
|
1919
1961
|
} catch (error) {
|
|
1920
1962
|
return {
|
|
@@ -1971,7 +2013,7 @@ Returns:
|
|
|
1971
2013
|
**Site URL**: ${result.site_url || "N/A"}
|
|
1972
2014
|
`;
|
|
1973
2015
|
return {
|
|
1974
|
-
content: [{ type: "text", text: markdown }],
|
|
2016
|
+
content: [{ type: "text", text: addDemoFooter(markdown) }],
|
|
1975
2017
|
structuredContent: result
|
|
1976
2018
|
};
|
|
1977
2019
|
} catch (error) {
|
|
@@ -2106,7 +2148,7 @@ Returns:
|
|
|
2106
2148
|
}
|
|
2107
2149
|
}
|
|
2108
2150
|
return {
|
|
2109
|
-
content: [{ type: "text", text: truncateText(markdown) }]
|
|
2151
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
2110
2152
|
};
|
|
2111
2153
|
} catch (error) {
|
|
2112
2154
|
return {
|
|
@@ -2267,7 +2309,7 @@ Returns:
|
|
|
2267
2309
|
}
|
|
2268
2310
|
}
|
|
2269
2311
|
return {
|
|
2270
|
-
content: [{ type: "text", text: truncateText(markdown) }]
|
|
2312
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
2271
2313
|
};
|
|
2272
2314
|
} catch (error) {
|
|
2273
2315
|
return {
|
|
@@ -2368,7 +2410,7 @@ ${result.description}
|
|
|
2368
2410
|
markdown += "\n";
|
|
2369
2411
|
}
|
|
2370
2412
|
return {
|
|
2371
|
-
content: [{ type: "text", text: truncateText(markdown) }]
|
|
2413
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
2372
2414
|
};
|
|
2373
2415
|
} catch (error) {
|
|
2374
2416
|
return {
|
|
@@ -2468,7 +2510,7 @@ Returns:
|
|
|
2468
2510
|
}
|
|
2469
2511
|
}
|
|
2470
2512
|
return {
|
|
2471
|
-
content: [{ type: "text", text: truncateText(markdown) }]
|
|
2513
|
+
content: [{ type: "text", text: truncateText(addDemoFooter(markdown)) }]
|
|
2472
2514
|
};
|
|
2473
2515
|
} catch (error) {
|
|
2474
2516
|
return {
|
|
@@ -3130,7 +3172,7 @@ The MQA (Metadata Quality Assurance) system is operated by data.europa.eu and on
|
|
|
3130
3172
|
return {
|
|
3131
3173
|
content: [{
|
|
3132
3174
|
type: "text",
|
|
3133
|
-
text: output
|
|
3175
|
+
text: addDemoFooter(output)
|
|
3134
3176
|
}]
|
|
3135
3177
|
};
|
|
3136
3178
|
} catch (error) {
|
|
@@ -3170,7 +3212,7 @@ The MQA (Metadata Quality Assurance) system is operated by data.europa.eu and on
|
|
|
3170
3212
|
return {
|
|
3171
3213
|
content: [{
|
|
3172
3214
|
type: "text",
|
|
3173
|
-
text: output
|
|
3215
|
+
text: addDemoFooter(output)
|
|
3174
3216
|
}]
|
|
3175
3217
|
};
|
|
3176
3218
|
} catch (error) {
|
|
@@ -3443,6 +3485,329 @@ function registerFormatDatasetsResource(server2) {
|
|
|
3443
3485
|
});
|
|
3444
3486
|
}
|
|
3445
3487
|
|
|
3488
|
+
// src/resources/datastore-table-ui.ts
|
|
3489
|
+
import { ResourceTemplate as ResourceTemplate5 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3490
|
+
var DATASTORE_TABLE_HTML = `<!DOCTYPE html>
|
|
3491
|
+
<html lang="en">
|
|
3492
|
+
<head>
|
|
3493
|
+
<meta charset="UTF-8">
|
|
3494
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
3495
|
+
<title>DataStore Table Viewer</title>
|
|
3496
|
+
<style>
|
|
3497
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
3498
|
+
body {
|
|
3499
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
3500
|
+
font-size: 13px;
|
|
3501
|
+
background: #fff;
|
|
3502
|
+
color: #1a1a1a;
|
|
3503
|
+
padding: 12px;
|
|
3504
|
+
}
|
|
3505
|
+
.header {
|
|
3506
|
+
display: flex;
|
|
3507
|
+
align-items: center;
|
|
3508
|
+
gap: 8px;
|
|
3509
|
+
margin-bottom: 10px;
|
|
3510
|
+
flex-wrap: wrap;
|
|
3511
|
+
}
|
|
3512
|
+
.title { font-size: 14px; font-weight: 600; color: #333; }
|
|
3513
|
+
.badge {
|
|
3514
|
+
font-size: 11px;
|
|
3515
|
+
color: #555;
|
|
3516
|
+
background: #f0f0f0;
|
|
3517
|
+
border-radius: 10px;
|
|
3518
|
+
padding: 2px 8px;
|
|
3519
|
+
}
|
|
3520
|
+
.controls {
|
|
3521
|
+
display: flex;
|
|
3522
|
+
gap: 8px;
|
|
3523
|
+
margin-bottom: 10px;
|
|
3524
|
+
align-items: center;
|
|
3525
|
+
flex-wrap: wrap;
|
|
3526
|
+
}
|
|
3527
|
+
.filter-input {
|
|
3528
|
+
flex: 1;
|
|
3529
|
+
min-width: 140px;
|
|
3530
|
+
max-width: 300px;
|
|
3531
|
+
padding: 5px 10px;
|
|
3532
|
+
border: 1px solid #d0d0d0;
|
|
3533
|
+
border-radius: 4px;
|
|
3534
|
+
font-size: 12px;
|
|
3535
|
+
outline: none;
|
|
3536
|
+
}
|
|
3537
|
+
.filter-input:focus { border-color: #0066cc; }
|
|
3538
|
+
.page-size-select {
|
|
3539
|
+
padding: 5px 8px;
|
|
3540
|
+
border: 1px solid #d0d0d0;
|
|
3541
|
+
border-radius: 4px;
|
|
3542
|
+
font-size: 12px;
|
|
3543
|
+
background: #fff;
|
|
3544
|
+
}
|
|
3545
|
+
.count-info { font-size: 11px; color: #666; margin-left: auto; }
|
|
3546
|
+
.table-wrapper {
|
|
3547
|
+
overflow-x: auto;
|
|
3548
|
+
border: 1px solid #e0e0e0;
|
|
3549
|
+
border-radius: 4px;
|
|
3550
|
+
}
|
|
3551
|
+
table { width: 100%; border-collapse: collapse; font-size: 12px; }
|
|
3552
|
+
th {
|
|
3553
|
+
background: #f5f5f5;
|
|
3554
|
+
padding: 7px 10px;
|
|
3555
|
+
text-align: left;
|
|
3556
|
+
font-weight: 600;
|
|
3557
|
+
white-space: nowrap;
|
|
3558
|
+
cursor: pointer;
|
|
3559
|
+
user-select: none;
|
|
3560
|
+
border-bottom: 2px solid #e0e0e0;
|
|
3561
|
+
}
|
|
3562
|
+
th:hover { background: #ebebeb; }
|
|
3563
|
+
th.sort-asc::after { content: ' \\25B2'; font-size: 9px; color: #0066cc; }
|
|
3564
|
+
th.sort-desc::after { content: ' \\25BC'; font-size: 9px; color: #0066cc; }
|
|
3565
|
+
td {
|
|
3566
|
+
padding: 5px 10px;
|
|
3567
|
+
border-bottom: 1px solid #f0f0f0;
|
|
3568
|
+
vertical-align: top;
|
|
3569
|
+
max-width: 220px;
|
|
3570
|
+
overflow: hidden;
|
|
3571
|
+
text-overflow: ellipsis;
|
|
3572
|
+
white-space: nowrap;
|
|
3573
|
+
}
|
|
3574
|
+
tr:hover td { background: #fafafa; }
|
|
3575
|
+
tr:last-child td { border-bottom: none; }
|
|
3576
|
+
td.null-val { color: #bbb; font-style: italic; }
|
|
3577
|
+
.pagination {
|
|
3578
|
+
display: flex;
|
|
3579
|
+
align-items: center;
|
|
3580
|
+
gap: 3px;
|
|
3581
|
+
margin-top: 10px;
|
|
3582
|
+
flex-wrap: wrap;
|
|
3583
|
+
}
|
|
3584
|
+
.page-btn {
|
|
3585
|
+
padding: 4px 9px;
|
|
3586
|
+
border: 1px solid #d0d0d0;
|
|
3587
|
+
border-radius: 3px;
|
|
3588
|
+
background: #fff;
|
|
3589
|
+
cursor: pointer;
|
|
3590
|
+
font-size: 12px;
|
|
3591
|
+
line-height: 1.4;
|
|
3592
|
+
}
|
|
3593
|
+
.page-btn:hover:not(:disabled) { background: #f0f0f0; }
|
|
3594
|
+
.page-btn.active { background: #0066cc; color: #fff; border-color: #0066cc; }
|
|
3595
|
+
.page-btn:disabled { opacity: 0.4; cursor: default; }
|
|
3596
|
+
.ellipsis { padding: 4px 4px; font-size: 12px; color: #999; }
|
|
3597
|
+
.notice {
|
|
3598
|
+
font-size: 11px;
|
|
3599
|
+
color: #888;
|
|
3600
|
+
margin-top: 8px;
|
|
3601
|
+
padding: 5px 10px;
|
|
3602
|
+
background: #fafafa;
|
|
3603
|
+
border-left: 3px solid #ddd;
|
|
3604
|
+
border-radius: 0 3px 3px 0;
|
|
3605
|
+
}
|
|
3606
|
+
.state-msg { text-align: center; color: #aaa; padding: 40px 20px; font-size: 13px; }
|
|
3607
|
+
</style>
|
|
3608
|
+
</head>
|
|
3609
|
+
<body>
|
|
3610
|
+
<div id="app"><div class="state-msg">Waiting for data from MCP client…</div></div>
|
|
3611
|
+
<script>
|
|
3612
|
+
(function () {
|
|
3613
|
+
var allRecords = [], fields = [], total = 0;
|
|
3614
|
+
var filteredRecords = [], currentPage = 1, pageSize = 25;
|
|
3615
|
+
var sortCol = null, sortDir = 1;
|
|
3616
|
+
var filterText = '';
|
|
3617
|
+
var colTypes = {};
|
|
3618
|
+
function escHtml(s) {
|
|
3619
|
+
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
3620
|
+
}
|
|
3621
|
+
function range(a, b) { var r=[]; for(var i=a;i<=b;i++) r.push(i); return r; }
|
|
3622
|
+
function pagesToShow(cur, tot) {
|
|
3623
|
+
if (tot<=7) return range(1,tot);
|
|
3624
|
+
if (cur<=4) return range(1,5).concat([tot]);
|
|
3625
|
+
if (cur>=tot-3) return [1].concat(range(tot-4,tot));
|
|
3626
|
+
return [1,cur-1,cur,cur+1,tot];
|
|
3627
|
+
}
|
|
3628
|
+
var dateRe = /^\\d{4}-\\d{2}-\\d{2}/;
|
|
3629
|
+
function inferType(samples) {
|
|
3630
|
+
var numOk=0,dateOk=0,valid=0;
|
|
3631
|
+
for(var i=0;i<samples.length;i++){
|
|
3632
|
+
var v=samples[i];
|
|
3633
|
+
if(v===null||v===undefined||v==='') continue;
|
|
3634
|
+
valid++;
|
|
3635
|
+
var s=String(v);
|
|
3636
|
+
if(s!==''&&!isNaN(Number(s))) numOk++;
|
|
3637
|
+
else if(dateRe.test(s)) dateOk++;
|
|
3638
|
+
}
|
|
3639
|
+
if(valid===0) return 'string';
|
|
3640
|
+
if(numOk/valid>0.8) return 'number';
|
|
3641
|
+
if(dateOk/valid>0.8) return 'date';
|
|
3642
|
+
return 'string';
|
|
3643
|
+
}
|
|
3644
|
+
function detectColTypes(records,fieldList) {
|
|
3645
|
+
var types={};
|
|
3646
|
+
for(var i=0;i<fieldList.length;i++){
|
|
3647
|
+
var fid=fieldList[i].id;
|
|
3648
|
+
var samples=records.slice(0,20).map(function(r){return r[fid];});
|
|
3649
|
+
types[fid]=inferType(samples);
|
|
3650
|
+
}
|
|
3651
|
+
return types;
|
|
3652
|
+
}
|
|
3653
|
+
function cmpValue(a,b,type){
|
|
3654
|
+
if(a===null||a===undefined) return 1;
|
|
3655
|
+
if(b===null||b===undefined) return -1;
|
|
3656
|
+
if(type==='number') return Number(a)-Number(b);
|
|
3657
|
+
if(type==='date') return new Date(a)-new Date(b);
|
|
3658
|
+
return String(a).localeCompare(String(b));
|
|
3659
|
+
}
|
|
3660
|
+
function sortRecords(records){
|
|
3661
|
+
if(!sortCol) return records;
|
|
3662
|
+
var type=colTypes[sortCol]||'string';
|
|
3663
|
+
return records.slice().sort(function(a,b){ return cmpValue(a[sortCol],b[sortCol],type)*sortDir; });
|
|
3664
|
+
}
|
|
3665
|
+
function applyFilter(records){
|
|
3666
|
+
if(!filterText) return records;
|
|
3667
|
+
var q=filterText.toLowerCase();
|
|
3668
|
+
return records.filter(function(r){
|
|
3669
|
+
return fields.some(function(f){
|
|
3670
|
+
var v=r[f.id];
|
|
3671
|
+
return v!==null&&v!==undefined&&String(v).toLowerCase().indexOf(q)!==-1;
|
|
3672
|
+
});
|
|
3673
|
+
});
|
|
3674
|
+
}
|
|
3675
|
+
function render(){
|
|
3676
|
+
var app=document.getElementById('app');
|
|
3677
|
+
filteredRecords=sortRecords(applyFilter(allRecords));
|
|
3678
|
+
var totalFiltered=filteredRecords.length;
|
|
3679
|
+
var totalPages=Math.ceil(totalFiltered/pageSize)||1;
|
|
3680
|
+
if(currentPage>totalPages) currentPage=totalPages;
|
|
3681
|
+
var start=(currentPage-1)*pageSize;
|
|
3682
|
+
var pageRecords=filteredRecords.slice(start,start+pageSize);
|
|
3683
|
+
var html='';
|
|
3684
|
+
html+='<div class="header">';
|
|
3685
|
+
html+='<span class="title">DataStore Table</span>';
|
|
3686
|
+
html+='<span class="badge">'+total.toLocaleString()+' total on server</span>';
|
|
3687
|
+
if(allRecords.length<total){
|
|
3688
|
+
html+='<span class="badge" style="background:#fff3cd;color:#856404">batch: '+allRecords.length.toLocaleString()+'</span>';
|
|
3689
|
+
}
|
|
3690
|
+
html+='</div>';
|
|
3691
|
+
var countLabel=(filterText&&totalFiltered<allRecords.length)
|
|
3692
|
+
?totalFiltered.toLocaleString()+' match'
|
|
3693
|
+
:allRecords.length.toLocaleString()+' record'+(allRecords.length!==1?'s':'');
|
|
3694
|
+
html+='<div class="controls">';
|
|
3695
|
+
html+='<input class="filter-input" id="filter" type="text" placeholder="Filter all columns\\u2026" value="'+escHtml(filterText)+'">';
|
|
3696
|
+
html+='<select class="page-size-select" id="pageSz">';
|
|
3697
|
+
[10,25,50,100].forEach(function(n){
|
|
3698
|
+
html+='<option value="'+n+'"'+(n===pageSize?' selected':'')+'>'+n+' rows</option>';
|
|
3699
|
+
});
|
|
3700
|
+
html+='</select>';
|
|
3701
|
+
html+='<span class="count-info">'+countLabel+'</span></div>';
|
|
3702
|
+
html+='<div class="table-wrapper"><table><thead><tr>';
|
|
3703
|
+
fields.forEach(function(f){
|
|
3704
|
+
var cls=(sortCol===f.id)?(sortDir===1?' class="sort-asc"':' class="sort-desc"'):'';
|
|
3705
|
+
html+='<th'+cls+' data-col="'+escHtml(f.id)+'">'+escHtml(f.id)+'</th>';
|
|
3706
|
+
});
|
|
3707
|
+
html+='</tr></thead><tbody>';
|
|
3708
|
+
if(pageRecords.length===0){
|
|
3709
|
+
html+='<tr><td colspan="'+fields.length+'" class="state-msg">No records found</td></tr>';
|
|
3710
|
+
} else {
|
|
3711
|
+
pageRecords.forEach(function(rec){
|
|
3712
|
+
html+='<tr>';
|
|
3713
|
+
fields.forEach(function(f){
|
|
3714
|
+
var v=rec[f.id];
|
|
3715
|
+
if(v===null||v===undefined){
|
|
3716
|
+
html+='<td class="null-val">\\u2014</td>';
|
|
3717
|
+
} else {
|
|
3718
|
+
var s=String(v);
|
|
3719
|
+
var display=s.length>80?s.slice(0,77)+'\\u2026':s;
|
|
3720
|
+
html+='<td title="'+escHtml(s)+'">'+escHtml(display)+'</td>';
|
|
3721
|
+
}
|
|
3722
|
+
});
|
|
3723
|
+
html+='</tr>';
|
|
3724
|
+
});
|
|
3725
|
+
}
|
|
3726
|
+
html+='</tbody></table></div>';
|
|
3727
|
+
if(totalPages>1){
|
|
3728
|
+
html+='<div class="pagination">';
|
|
3729
|
+
html+='<button class="page-btn" id="prevBtn"'+(currentPage===1?' disabled':'')+'>\\u2039 Prev</button>';
|
|
3730
|
+
var pages=pagesToShow(currentPage,totalPages);
|
|
3731
|
+
var prev=null;
|
|
3732
|
+
pages.forEach(function(p){
|
|
3733
|
+
if(prev!==null&&p-prev>1) html+='<span class="ellipsis">\\u2026</span>';
|
|
3734
|
+
html+='<button class="page-btn'+(p===currentPage?' active':'')+'" data-page="'+p+'">'+p+'</button>';
|
|
3735
|
+
prev=p;
|
|
3736
|
+
});
|
|
3737
|
+
html+='<button class="page-btn" id="nextBtn"'+(currentPage===totalPages?' disabled':'')+'>Next \\u203a</button>';
|
|
3738
|
+
html+='</div>';
|
|
3739
|
+
}
|
|
3740
|
+
if(total>allRecords.length){
|
|
3741
|
+
html+='<div class="notice">Showing batch of '+allRecords.length.toLocaleString()+' records (server total: '+total.toLocaleString()+'). Re-run the tool with a different <code>offset</code> to access more records.</div>';
|
|
3742
|
+
}
|
|
3743
|
+
app.innerHTML=html;
|
|
3744
|
+
bindEvents();
|
|
3745
|
+
}
|
|
3746
|
+
function bindEvents(){
|
|
3747
|
+
var filterEl=document.getElementById('filter');
|
|
3748
|
+
if(filterEl) filterEl.addEventListener('input',function(e){ filterText=e.target.value; currentPage=1; render(); });
|
|
3749
|
+
var pageSzEl=document.getElementById('pageSz');
|
|
3750
|
+
if(pageSzEl) pageSzEl.addEventListener('change',function(e){ pageSize=parseInt(e.target.value,10); currentPage=1; render(); });
|
|
3751
|
+
document.querySelectorAll('th[data-col]').forEach(function(th){
|
|
3752
|
+
th.addEventListener('click',function(){
|
|
3753
|
+
var col=th.getAttribute('data-col');
|
|
3754
|
+
if(sortCol===col){ sortDir=-sortDir; } else { sortCol=col; sortDir=1; }
|
|
3755
|
+
render();
|
|
3756
|
+
});
|
|
3757
|
+
});
|
|
3758
|
+
var p=document.getElementById('prevBtn');
|
|
3759
|
+
if(p) p.addEventListener('click',function(){ currentPage--; render(); });
|
|
3760
|
+
var n=document.getElementById('nextBtn');
|
|
3761
|
+
if(n) n.addEventListener('click',function(){ currentPage++; render(); });
|
|
3762
|
+
document.querySelectorAll('.page-btn[data-page]').forEach(function(btn){
|
|
3763
|
+
btn.addEventListener('click',function(){ currentPage=parseInt(btn.getAttribute('data-page'),10); render(); });
|
|
3764
|
+
});
|
|
3765
|
+
}
|
|
3766
|
+
function initTable(data){
|
|
3767
|
+
fields=data.fields||[];
|
|
3768
|
+
allRecords=data.records||[];
|
|
3769
|
+
total=data.total||allRecords.length;
|
|
3770
|
+
colTypes=detectColTypes(allRecords,fields);
|
|
3771
|
+
currentPage=1; sortCol=null; sortDir=1; filterText='';
|
|
3772
|
+
render();
|
|
3773
|
+
}
|
|
3774
|
+
window.addEventListener('message',function(event){
|
|
3775
|
+
var msg=event.data;
|
|
3776
|
+
if(!msg||typeof msg!=='object') return;
|
|
3777
|
+
var data=
|
|
3778
|
+
(msg._meta&&msg._meta.ui&&msg._meta.ui.data)||
|
|
3779
|
+
(msg.toolResult&&msg.toolResult._meta&&msg.toolResult._meta.ui&&msg.toolResult._meta.ui.data)||
|
|
3780
|
+
(msg.result&&msg.result._meta&&msg.result._meta.ui&&msg.result._meta.ui.data)||
|
|
3781
|
+
(msg.fields&&msg.records?msg:null);
|
|
3782
|
+
if(data&&(data.fields||data.records)) initTable(data);
|
|
3783
|
+
});
|
|
3784
|
+
}());
|
|
3785
|
+
</script>
|
|
3786
|
+
</body>
|
|
3787
|
+
</html>`;
|
|
3788
|
+
function registerDatastoreTableUiResource(server2) {
|
|
3789
|
+
server2.registerResource(
|
|
3790
|
+
"ckan-datastore-table-ui",
|
|
3791
|
+
new ResourceTemplate5("ckan-ui://datastore-table", { list: void 0 }),
|
|
3792
|
+
{
|
|
3793
|
+
title: "CKAN DataStore Table Viewer",
|
|
3794
|
+
description: "Interactive sortable/filterable/paginated table for DataStore query results (MCP Apps UI)",
|
|
3795
|
+
mimeType: "text/html"
|
|
3796
|
+
},
|
|
3797
|
+
async (uri) => {
|
|
3798
|
+
return {
|
|
3799
|
+
contents: [
|
|
3800
|
+
{
|
|
3801
|
+
uri: uri.href,
|
|
3802
|
+
mimeType: "text/html",
|
|
3803
|
+
text: DATASTORE_TABLE_HTML
|
|
3804
|
+
}
|
|
3805
|
+
]
|
|
3806
|
+
};
|
|
3807
|
+
}
|
|
3808
|
+
);
|
|
3809
|
+
}
|
|
3810
|
+
|
|
3446
3811
|
// src/resources/index.ts
|
|
3447
3812
|
function registerAllResources(server2) {
|
|
3448
3813
|
registerDatasetResource(server2);
|
|
@@ -3452,6 +3817,7 @@ function registerAllResources(server2) {
|
|
|
3452
3817
|
registerOrganizationDatasetsResource(server2);
|
|
3453
3818
|
registerTagDatasetsResource(server2);
|
|
3454
3819
|
registerFormatDatasetsResource(server2);
|
|
3820
|
+
registerDatastoreTableUiResource(server2);
|
|
3455
3821
|
}
|
|
3456
3822
|
|
|
3457
3823
|
// src/prompts/theme.ts
|
|
@@ -3725,7 +4091,7 @@ var registerAllPrompts = (server2) => {
|
|
|
3725
4091
|
function createServer() {
|
|
3726
4092
|
return new McpServer({
|
|
3727
4093
|
name: "ckan-mcp-server",
|
|
3728
|
-
version: "0.4.
|
|
4094
|
+
version: "0.4.40"
|
|
3729
4095
|
});
|
|
3730
4096
|
}
|
|
3731
4097
|
function registerAll(server2) {
|