@aborruso/ckan-mcp-server 0.4.17 → 0.4.19

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.
Files changed (87) hide show
  1. package/LOG.md +64 -0
  2. package/README.md +104 -34
  3. package/dist/index.js +161 -45
  4. package/dist/worker.js +42 -42
  5. package/package.json +12 -1
  6. package/.devin/wiki.json +0 -273
  7. package/CLAUDE.md +0 -398
  8. package/PRD.md +0 -999
  9. package/REFACTORING.md +0 -238
  10. package/examples/langgraph/01_basic_workflow.py +0 -277
  11. package/examples/langgraph/02_data_exploration.py +0 -366
  12. package/examples/langgraph/README.md +0 -719
  13. package/examples/langgraph/metadata_quality.py +0 -299
  14. package/examples/langgraph/requirements.txt +0 -12
  15. package/examples/langgraph/setup.sh +0 -32
  16. package/examples/langgraph/test_setup.py +0 -106
  17. package/openspec/AGENTS.md +0 -456
  18. package/openspec/changes/add-ckan-analyze-dataset-structure/proposal.md +0 -17
  19. package/openspec/changes/add-ckan-analyze-dataset-structure/specs/ckan-insights/spec.md +0 -7
  20. package/openspec/changes/add-ckan-analyze-dataset-structure/tasks.md +0 -6
  21. package/openspec/changes/add-ckan-analyze-dataset-updates/proposal.md +0 -17
  22. package/openspec/changes/add-ckan-analyze-dataset-updates/specs/ckan-insights/spec.md +0 -7
  23. package/openspec/changes/add-ckan-analyze-dataset-updates/tasks.md +0 -6
  24. package/openspec/changes/add-ckan-audit-tool/proposal.md +0 -17
  25. package/openspec/changes/add-ckan-audit-tool/specs/ckan-insights/spec.md +0 -7
  26. package/openspec/changes/add-ckan-audit-tool/tasks.md +0 -6
  27. package/openspec/changes/add-ckan-dataset-insights/proposal.md +0 -17
  28. package/openspec/changes/add-ckan-dataset-insights/specs/ckan-insights/spec.md +0 -7
  29. package/openspec/changes/add-ckan-dataset-insights/tasks.md +0 -6
  30. package/openspec/changes/add-ckan-host-allowlist-env/design.md +0 -38
  31. package/openspec/changes/add-ckan-host-allowlist-env/proposal.md +0 -16
  32. package/openspec/changes/add-ckan-host-allowlist-env/specs/ckan-request-allowlist/spec.md +0 -15
  33. package/openspec/changes/add-ckan-host-allowlist-env/specs/cloudflare-deployment/spec.md +0 -11
  34. package/openspec/changes/add-ckan-host-allowlist-env/tasks.md +0 -12
  35. package/openspec/changes/add-escape-text-query/proposal.md +0 -12
  36. package/openspec/changes/add-escape-text-query/specs/ckan-search/spec.md +0 -11
  37. package/openspec/changes/add-escape-text-query/tasks.md +0 -8
  38. package/openspec/changes/add-mqa-quality-tool/proposal.md +0 -21
  39. package/openspec/changes/add-mqa-quality-tool/specs/ckan-quality/spec.md +0 -71
  40. package/openspec/changes/add-mqa-quality-tool/tasks.md +0 -29
  41. package/openspec/changes/archive/2026-01-08-add-mcp-resources/design.md +0 -115
  42. package/openspec/changes/archive/2026-01-08-add-mcp-resources/proposal.md +0 -52
  43. package/openspec/changes/archive/2026-01-08-add-mcp-resources/specs/mcp-resources/spec.md +0 -92
  44. package/openspec/changes/archive/2026-01-08-add-mcp-resources/tasks.md +0 -56
  45. package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/design.md +0 -355
  46. package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/proposal.md +0 -161
  47. package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/tasks.md +0 -162
  48. package/openspec/changes/archive/2026-01-08-translate-project-to-english/proposal.md +0 -115
  49. package/openspec/changes/archive/2026-01-08-translate-project-to-english/specs/documentation-language/spec.md +0 -32
  50. package/openspec/changes/archive/2026-01-08-translate-project-to-english/tasks.md +0 -115
  51. package/openspec/changes/archive/2026-01-10-add-ckan-find-relevant-datasets/proposal.md +0 -17
  52. package/openspec/changes/archive/2026-01-10-add-ckan-find-relevant-datasets/specs/ckan-insights/spec.md +0 -7
  53. package/openspec/changes/archive/2026-01-10-add-ckan-find-relevant-datasets/tasks.md +0 -6
  54. package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/design.md +0 -734
  55. package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/proposal.md +0 -183
  56. package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/specs/cloudflare-deployment/spec.md +0 -389
  57. package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/tasks.md +0 -519
  58. package/openspec/changes/archive/2026-01-15-add-mcp-prompts/proposal.md +0 -13
  59. package/openspec/changes/archive/2026-01-15-add-mcp-prompts/specs/mcp-prompts/spec.md +0 -22
  60. package/openspec/changes/archive/2026-01-15-add-mcp-prompts/tasks.md +0 -10
  61. package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/proposal.md +0 -13
  62. package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/specs/mcp-resources/spec.md +0 -38
  63. package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/tasks.md +0 -10
  64. package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/proposal.md +0 -13
  65. package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/specs/repository-metadata/spec.md +0 -14
  66. package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/tasks.md +0 -12
  67. package/openspec/changes/archive/2026-01-19-update-search-parser-config/proposal.md +0 -13
  68. package/openspec/changes/archive/2026-01-19-update-search-parser-config/specs/ckan-insights/spec.md +0 -11
  69. package/openspec/changes/archive/2026-01-19-update-search-parser-config/specs/ckan-search/spec.md +0 -11
  70. package/openspec/changes/archive/2026-01-19-update-search-parser-config/tasks.md +0 -6
  71. package/openspec/changes/archive/add-automated-tests/design.md +0 -324
  72. package/openspec/changes/archive/add-automated-tests/proposal.md +0 -167
  73. package/openspec/changes/archive/add-automated-tests/specs/automated-testing/spec.md +0 -143
  74. package/openspec/changes/archive/add-automated-tests/tasks.md +0 -132
  75. package/openspec/project.md +0 -115
  76. package/openspec/specs/ckan-insights/spec.md +0 -23
  77. package/openspec/specs/ckan-search/spec.md +0 -16
  78. package/openspec/specs/cloudflare-deployment/spec.md +0 -344
  79. package/openspec/specs/documentation-language/spec.md +0 -32
  80. package/openspec/specs/mcp-prompts/spec.md +0 -26
  81. package/openspec/specs/mcp-resources/spec.md +0 -120
  82. package/openspec/specs/repository-metadata/spec.md +0 -19
  83. package/private/commenti-privati.yaml +0 -14
  84. package/testo.md +0 -12
  85. package/web-gui/PRD.md +0 -158
  86. package/web-gui/public/index.html +0 -883
  87. package/wrangler.toml +0 -6
@@ -1,299 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Metadata Quality Scoring for CKAN Datasets
4
-
5
- Advanced quality scoring system based on:
6
- - Completeness (required and recommended fields)
7
- - Richness (descriptions, tags, temporal coverage)
8
- - Resources quality (formats, accessibility)
9
- - Temporal freshness
10
-
11
- Score: 0-100 points
12
- """
13
-
14
- from datetime import datetime
15
- from typing import Any
16
-
17
-
18
- class MetadataQualityScorer:
19
- """Calculate metadata quality score for CKAN datasets."""
20
-
21
- # Quality thresholds
22
- EXCELLENT = 80
23
- GOOD = 60
24
- ACCEPTABLE = 40
25
- POOR = 0
26
-
27
- @classmethod
28
- def score_dataset(cls, dataset: dict[str, Any]) -> dict[str, Any]:
29
- """
30
- Calculate comprehensive quality score.
31
-
32
- Returns:
33
- {
34
- "score": 75, # Total score 0-100
35
- "level": "good", # excellent/good/acceptable/poor
36
- "breakdown": {
37
- "completeness": 20, # out of 30
38
- "richness": 15, # out of 30
39
- "resources": 25, # out of 30
40
- "freshness": 8 # out of 10
41
- },
42
- "issues": ["Missing license", ...]
43
- }
44
- """
45
- issues = []
46
- breakdown = {
47
- "completeness": cls._score_completeness(dataset, issues),
48
- "richness": cls._score_richness(dataset, issues),
49
- "resources": cls._score_resources(dataset, issues),
50
- "freshness": cls._score_freshness(dataset, issues),
51
- }
52
-
53
- total_score = sum(breakdown.values())
54
- level = cls._get_level(total_score)
55
-
56
- return {
57
- "score": total_score,
58
- "level": level,
59
- "breakdown": breakdown,
60
- "issues": issues,
61
- }
62
-
63
- @classmethod
64
- def _score_completeness(cls, dataset: dict, issues: list) -> int:
65
- """Score 0-30: Required and recommended fields."""
66
- score = 0
67
-
68
- # Required fields (15 points)
69
- if dataset.get("title"):
70
- score += 5
71
- else:
72
- issues.append("Missing title")
73
-
74
- if dataset.get("notes"): # Description
75
- score += 5
76
- else:
77
- issues.append("Missing description")
78
-
79
- if dataset.get("name"): # Identifier
80
- score += 5
81
- else:
82
- issues.append("Missing identifier")
83
-
84
- # Recommended fields (15 points)
85
- if dataset.get("license_id"):
86
- score += 3
87
- else:
88
- issues.append("Missing license")
89
-
90
- if dataset.get("author") or dataset.get("maintainer"):
91
- score += 3
92
- else:
93
- issues.append("Missing author/maintainer")
94
-
95
- if dataset.get("author_email") or dataset.get("maintainer_email"):
96
- score += 3
97
- else:
98
- issues.append("Missing contact email")
99
-
100
- # Organization
101
- if dataset.get("organization"):
102
- score += 3
103
- else:
104
- issues.append("Not assigned to organization")
105
-
106
- # Geographical coverage
107
- if dataset.get("extras"):
108
- has_geo = any(
109
- e.get("key") in ["spatial", "geographic_coverage"]
110
- for e in dataset.get("extras", [])
111
- )
112
- if has_geo:
113
- score += 3
114
-
115
- return score
116
-
117
- @classmethod
118
- def _score_richness(cls, dataset: dict, issues: list) -> int:
119
- """Score 0-30: Richness of metadata."""
120
- score = 0
121
-
122
- # Description quality (10 points)
123
- notes = dataset.get("notes", "")
124
- if len(notes) > 200:
125
- score += 10
126
- elif len(notes) > 100:
127
- score += 5
128
- elif len(notes) > 0:
129
- score += 2
130
- else:
131
- issues.append("Very short or missing description")
132
-
133
- # Tags (10 points)
134
- tags = dataset.get("tags", [])
135
- num_tags = len(tags)
136
- if num_tags >= 5:
137
- score += 10
138
- elif num_tags >= 3:
139
- score += 6
140
- elif num_tags >= 1:
141
- score += 3
142
- else:
143
- issues.append("No tags")
144
-
145
- # Temporal coverage (5 points)
146
- extras = {e.get("key"): e.get("value") for e in dataset.get("extras", [])}
147
- if "temporal_start" in extras or "temporal_end" in extras:
148
- score += 3
149
-
150
- # Frequency/update schedule (5 points)
151
- if extras.get("frequency") or extras.get("update_frequency"):
152
- score += 2
153
-
154
- return score
155
-
156
- @classmethod
157
- def _score_resources(cls, dataset: dict, issues: list) -> int:
158
- """Score 0-30: Resources quality."""
159
- score = 0
160
- resources = dataset.get("resources", [])
161
-
162
- if not resources:
163
- issues.append("No resources")
164
- return 0
165
-
166
- # At least one resource (5 points)
167
- score += 5
168
-
169
- # Check formats (10 points)
170
- formats = {r.get("format", "").upper() for r in resources}
171
- open_formats = {"CSV", "JSON", "GEOJSON", "XML", "RDF", "JSONLD"}
172
- if formats & open_formats:
173
- score += 10
174
- if "CSV" in formats:
175
- score += 2 # Bonus for CSV
176
- else:
177
- issues.append("No open formats (CSV/JSON/XML)")
178
-
179
- # Resource descriptions (5 points)
180
- described = sum(1 for r in resources if r.get("description"))
181
- if described == len(resources):
182
- score += 5
183
- elif described > 0:
184
- score += 2
185
-
186
- # DataStore availability (5 points)
187
- has_datastore = any(r.get("datastore_active") for r in resources)
188
- if has_datastore:
189
- score += 5
190
-
191
- # URLs validity (5 points)
192
- valid_urls = sum(
193
- 1 for r in resources if r.get("url") and r["url"].startswith("http")
194
- )
195
- if valid_urls == len(resources):
196
- score += 5
197
- elif valid_urls > 0:
198
- score += 2
199
- else:
200
- issues.append("Invalid or missing resource URLs")
201
-
202
- return score
203
-
204
- @classmethod
205
- def _score_freshness(cls, dataset: dict, issues: list) -> int:
206
- """Score 0-10: Temporal freshness."""
207
- score = 0
208
-
209
- # Check metadata_modified
210
- modified_str = dataset.get("metadata_modified")
211
- if not modified_str:
212
- issues.append("No last modified date")
213
- return 0
214
-
215
- try:
216
- modified = datetime.fromisoformat(modified_str.replace("Z", "+00:00"))
217
- now = datetime.now(modified.tzinfo)
218
- days_old = (now - modified).days
219
-
220
- if days_old < 90: # < 3 months
221
- score = 10
222
- elif days_old < 180: # < 6 months
223
- score = 7
224
- elif days_old < 365: # < 1 year
225
- score = 5
226
- elif days_old < 730: # < 2 years
227
- score = 3
228
- else:
229
- score = 1
230
- issues.append(f"Last updated {days_old} days ago")
231
-
232
- except (ValueError, AttributeError):
233
- issues.append("Invalid date format")
234
-
235
- return score
236
-
237
- @classmethod
238
- def _get_level(cls, score: int) -> str:
239
- """Convert score to quality level."""
240
- if score >= cls.EXCELLENT:
241
- return "excellent"
242
- elif score >= cls.GOOD:
243
- return "good"
244
- elif score >= cls.ACCEPTABLE:
245
- return "acceptable"
246
- else:
247
- return "poor"
248
-
249
-
250
- # Example usage
251
- if __name__ == "__main__":
252
- # Sample dataset
253
- sample_dataset = {
254
- "title": "Sample Dataset",
255
- "name": "sample-dataset",
256
- "notes": "This is a sample dataset with a detailed description " * 5,
257
- "license_id": "cc-by-4.0",
258
- "author": "Mario Rossi",
259
- "author_email": "mario@example.com",
260
- "organization": {"name": "comune-roma"},
261
- "tags": [
262
- {"name": "environment"},
263
- {"name": "air-quality"},
264
- {"name": "open-data"},
265
- ],
266
- "resources": [
267
- {
268
- "format": "CSV",
269
- "url": "https://example.com/data.csv",
270
- "description": "Data in CSV format",
271
- "datastore_active": True,
272
- },
273
- {
274
- "format": "JSON",
275
- "url": "https://example.com/data.json",
276
- "description": "Data in JSON format",
277
- },
278
- ],
279
- "metadata_modified": "2025-01-15T10:00:00Z",
280
- }
281
-
282
- scorer = MetadataQualityScorer()
283
- result = scorer.score_dataset(sample_dataset)
284
-
285
- print("Metadata Quality Assessment")
286
- print("=" * 50)
287
- print(f"Overall Score: {result['score']}/100")
288
- print(f"Quality Level: {result['level'].upper()}")
289
- print(f"\nBreakdown:")
290
- for category, score in result["breakdown"].items():
291
- print(
292
- f" {category.capitalize():15} {score:2}/30"
293
- if category != "freshness"
294
- else f" {category.capitalize():15} {score:2}/10"
295
- )
296
- if result["issues"]:
297
- print(f"\nIssues ({len(result['issues'])}):")
298
- for issue in result["issues"]:
299
- print(f" - {issue}")
@@ -1,12 +0,0 @@
1
- # LangGraph Examples - Python Dependencies
2
-
3
- # Core dependencies
4
- langgraph>=0.2.0
5
- langchain-core>=0.3.0
6
-
7
- # MCP Python SDK for client connection
8
- mcp>=1.0.0
9
-
10
- # Optional: LangSmith for debugging/tracing
11
- # Uncomment if you want to use LangSmith
12
- # langsmith>=0.1.0
@@ -1,32 +0,0 @@
1
- #!/bin/bash
2
- # Setup script for LangGraph examples
3
-
4
- set -e
5
-
6
- echo "Setting up LangGraph examples environment..."
7
-
8
- # Check Python version
9
- python3 --version
10
-
11
- # Create virtual environment if it doesn't exist
12
- if [ ! -d "venv" ]; then
13
- echo "Creating virtual environment..."
14
- python3 -m venv venv
15
- fi
16
-
17
- # Activate virtual environment
18
- source venv/bin/activate
19
-
20
- # Install dependencies
21
- echo "Installing dependencies..."
22
- pip install -r requirements.txt
23
-
24
- echo ""
25
- echo "✓ Setup complete!"
26
- echo ""
27
- echo "To activate the environment:"
28
- echo " source venv/bin/activate"
29
- echo ""
30
- echo "To run examples:"
31
- echo " python 01_basic_workflow.py"
32
- echo " python 02_data_exploration.py"
@@ -1,106 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Quick test to verify LangGraph + MCP setup
4
-
5
- Run:
6
- uvx --with langgraph --with mcp --with langchain-core python test_setup.py
7
- """
8
-
9
- import sys
10
-
11
-
12
- def test_imports():
13
- """Test that all required packages are available."""
14
- print("Testing imports...")
15
- errors = []
16
-
17
- try:
18
- import langgraph # noqa: F401
19
-
20
- print("✓ langgraph")
21
- except ImportError as e:
22
- errors.append(f"✗ langgraph: {e}")
23
-
24
- try:
25
- import mcp # noqa: F401
26
-
27
- print("✓ mcp")
28
- except ImportError as e:
29
- errors.append(f"✗ mcp: {e}")
30
-
31
- try:
32
- import langchain_core # noqa: F401
33
-
34
- print("✓ langchain_core")
35
- except ImportError as e:
36
- errors.append(f"✗ langchain_core: {e}")
37
-
38
- return errors
39
-
40
-
41
- def test_mcp_server():
42
- """Test that MCP server file exists."""
43
- import os
44
-
45
- print("\nTesting MCP server...")
46
- server_path = os.path.join(os.path.dirname(__file__), "../../dist/index.js")
47
-
48
- if os.path.exists(server_path):
49
- print(f"✓ MCP server found: {server_path}")
50
- return []
51
- else:
52
- return [
53
- f"✗ MCP server not found: {server_path}",
54
- " Run: cd ../.. && npm run build",
55
- ]
56
-
57
-
58
- def test_node():
59
- """Test that Node.js is available."""
60
- import subprocess
61
-
62
- print("\nTesting Node.js...")
63
- try:
64
- result = subprocess.run(
65
- ["node", "--version"], capture_output=True, text=True, check=True
66
- )
67
- version = result.stdout.strip()
68
- print(f"✓ Node.js {version}")
69
- return []
70
- except (subprocess.CalledProcessError, FileNotFoundError):
71
- return ["✗ Node.js not found or not in PATH"]
72
-
73
-
74
- def main():
75
- """Run all tests."""
76
- print("=" * 60)
77
- print("LangGraph + CKAN MCP Setup Test")
78
- print("=" * 60)
79
-
80
- all_errors = []
81
-
82
- # Run tests
83
- all_errors.extend(test_imports())
84
- all_errors.extend(test_node())
85
- all_errors.extend(test_mcp_server())
86
-
87
- # Summary
88
- print("\n" + "=" * 60)
89
- if all_errors:
90
- print("SETUP INCOMPLETE")
91
- print("=" * 60)
92
- for error in all_errors:
93
- print(error)
94
- print("\nSee README.md for setup instructions")
95
- sys.exit(1)
96
- else:
97
- print("✓ ALL TESTS PASSED")
98
- print("=" * 60)
99
- print("\nYou can now run:")
100
- print(" python 01_basic_workflow.py")
101
- print(" python 02_data_exploration.py")
102
- sys.exit(0)
103
-
104
-
105
- if __name__ == "__main__":
106
- main()