@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.
- package/LOG.md +64 -0
- package/README.md +104 -34
- package/dist/index.js +161 -45
- package/dist/worker.js +42 -42
- package/package.json +12 -1
- package/.devin/wiki.json +0 -273
- package/CLAUDE.md +0 -398
- package/PRD.md +0 -999
- package/REFACTORING.md +0 -238
- package/examples/langgraph/01_basic_workflow.py +0 -277
- package/examples/langgraph/02_data_exploration.py +0 -366
- package/examples/langgraph/README.md +0 -719
- package/examples/langgraph/metadata_quality.py +0 -299
- package/examples/langgraph/requirements.txt +0 -12
- package/examples/langgraph/setup.sh +0 -32
- package/examples/langgraph/test_setup.py +0 -106
- package/openspec/AGENTS.md +0 -456
- package/openspec/changes/add-ckan-analyze-dataset-structure/proposal.md +0 -17
- package/openspec/changes/add-ckan-analyze-dataset-structure/specs/ckan-insights/spec.md +0 -7
- package/openspec/changes/add-ckan-analyze-dataset-structure/tasks.md +0 -6
- package/openspec/changes/add-ckan-analyze-dataset-updates/proposal.md +0 -17
- package/openspec/changes/add-ckan-analyze-dataset-updates/specs/ckan-insights/spec.md +0 -7
- package/openspec/changes/add-ckan-analyze-dataset-updates/tasks.md +0 -6
- package/openspec/changes/add-ckan-audit-tool/proposal.md +0 -17
- package/openspec/changes/add-ckan-audit-tool/specs/ckan-insights/spec.md +0 -7
- package/openspec/changes/add-ckan-audit-tool/tasks.md +0 -6
- package/openspec/changes/add-ckan-dataset-insights/proposal.md +0 -17
- package/openspec/changes/add-ckan-dataset-insights/specs/ckan-insights/spec.md +0 -7
- package/openspec/changes/add-ckan-dataset-insights/tasks.md +0 -6
- package/openspec/changes/add-ckan-host-allowlist-env/design.md +0 -38
- package/openspec/changes/add-ckan-host-allowlist-env/proposal.md +0 -16
- package/openspec/changes/add-ckan-host-allowlist-env/specs/ckan-request-allowlist/spec.md +0 -15
- package/openspec/changes/add-ckan-host-allowlist-env/specs/cloudflare-deployment/spec.md +0 -11
- package/openspec/changes/add-ckan-host-allowlist-env/tasks.md +0 -12
- package/openspec/changes/add-escape-text-query/proposal.md +0 -12
- package/openspec/changes/add-escape-text-query/specs/ckan-search/spec.md +0 -11
- package/openspec/changes/add-escape-text-query/tasks.md +0 -8
- package/openspec/changes/add-mqa-quality-tool/proposal.md +0 -21
- package/openspec/changes/add-mqa-quality-tool/specs/ckan-quality/spec.md +0 -71
- package/openspec/changes/add-mqa-quality-tool/tasks.md +0 -29
- package/openspec/changes/archive/2026-01-08-add-mcp-resources/design.md +0 -115
- package/openspec/changes/archive/2026-01-08-add-mcp-resources/proposal.md +0 -52
- package/openspec/changes/archive/2026-01-08-add-mcp-resources/specs/mcp-resources/spec.md +0 -92
- package/openspec/changes/archive/2026-01-08-add-mcp-resources/tasks.md +0 -56
- package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/design.md +0 -355
- package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/proposal.md +0 -161
- package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/tasks.md +0 -162
- package/openspec/changes/archive/2026-01-08-translate-project-to-english/proposal.md +0 -115
- package/openspec/changes/archive/2026-01-08-translate-project-to-english/specs/documentation-language/spec.md +0 -32
- package/openspec/changes/archive/2026-01-08-translate-project-to-english/tasks.md +0 -115
- package/openspec/changes/archive/2026-01-10-add-ckan-find-relevant-datasets/proposal.md +0 -17
- package/openspec/changes/archive/2026-01-10-add-ckan-find-relevant-datasets/specs/ckan-insights/spec.md +0 -7
- package/openspec/changes/archive/2026-01-10-add-ckan-find-relevant-datasets/tasks.md +0 -6
- package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/design.md +0 -734
- package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/proposal.md +0 -183
- package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/specs/cloudflare-deployment/spec.md +0 -389
- package/openspec/changes/archive/2026-01-10-add-cloudflare-workers/tasks.md +0 -519
- package/openspec/changes/archive/2026-01-15-add-mcp-prompts/proposal.md +0 -13
- package/openspec/changes/archive/2026-01-15-add-mcp-prompts/specs/mcp-prompts/spec.md +0 -22
- package/openspec/changes/archive/2026-01-15-add-mcp-prompts/tasks.md +0 -10
- package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/proposal.md +0 -13
- package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/specs/mcp-resources/spec.md +0 -38
- package/openspec/changes/archive/2026-01-15-add-mcp-resource-filters/tasks.md +0 -10
- package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/proposal.md +0 -13
- package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/specs/repository-metadata/spec.md +0 -14
- package/openspec/changes/archive/2026-01-19-update-repo-owner-ondata/tasks.md +0 -12
- package/openspec/changes/archive/2026-01-19-update-search-parser-config/proposal.md +0 -13
- package/openspec/changes/archive/2026-01-19-update-search-parser-config/specs/ckan-insights/spec.md +0 -11
- package/openspec/changes/archive/2026-01-19-update-search-parser-config/specs/ckan-search/spec.md +0 -11
- package/openspec/changes/archive/2026-01-19-update-search-parser-config/tasks.md +0 -6
- package/openspec/changes/archive/add-automated-tests/design.md +0 -324
- package/openspec/changes/archive/add-automated-tests/proposal.md +0 -167
- package/openspec/changes/archive/add-automated-tests/specs/automated-testing/spec.md +0 -143
- package/openspec/changes/archive/add-automated-tests/tasks.md +0 -132
- package/openspec/project.md +0 -115
- package/openspec/specs/ckan-insights/spec.md +0 -23
- package/openspec/specs/ckan-search/spec.md +0 -16
- package/openspec/specs/cloudflare-deployment/spec.md +0 -344
- package/openspec/specs/documentation-language/spec.md +0 -32
- package/openspec/specs/mcp-prompts/spec.md +0 -26
- package/openspec/specs/mcp-resources/spec.md +0 -120
- package/openspec/specs/repository-metadata/spec.md +0 -19
- package/private/commenti-privati.yaml +0 -14
- package/testo.md +0 -12
- package/web-gui/PRD.md +0 -158
- package/web-gui/public/index.html +0 -883
- 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()
|