@aborruso/ckan-mcp-server 0.3.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.
Files changed (43) hide show
  1. package/.claude/commands/openspec/apply.md +23 -0
  2. package/.claude/commands/openspec/archive.md +27 -0
  3. package/.claude/commands/openspec/proposal.md +28 -0
  4. package/.claude/settings.local.json +31 -0
  5. package/.gemini/commands/openspec/apply.toml +21 -0
  6. package/.gemini/commands/openspec/archive.toml +25 -0
  7. package/.gemini/commands/openspec/proposal.toml +26 -0
  8. package/.mcp.json +12 -0
  9. package/.opencode/command/openspec-apply.md +24 -0
  10. package/.opencode/command/openspec-archive.md +27 -0
  11. package/.opencode/command/openspec-proposal.md +29 -0
  12. package/AGENTS.md +18 -0
  13. package/CLAUDE.md +320 -0
  14. package/EXAMPLES.md +707 -0
  15. package/LICENSE.txt +21 -0
  16. package/LOG.md +154 -0
  17. package/PRD.md +912 -0
  18. package/README.md +468 -0
  19. package/REFACTORING.md +237 -0
  20. package/dist/index.js +1277 -0
  21. package/openspec/AGENTS.md +456 -0
  22. package/openspec/changes/archive/2026-01-08-add-mcp-resources/design.md +115 -0
  23. package/openspec/changes/archive/2026-01-08-add-mcp-resources/proposal.md +52 -0
  24. package/openspec/changes/archive/2026-01-08-add-mcp-resources/specs/mcp-resources/spec.md +92 -0
  25. package/openspec/changes/archive/2026-01-08-add-mcp-resources/tasks.md +56 -0
  26. package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/design.md +355 -0
  27. package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/proposal.md +161 -0
  28. package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/tasks.md +162 -0
  29. package/openspec/changes/archive/2026-01-08-translate-project-to-english/proposal.md +115 -0
  30. package/openspec/changes/archive/2026-01-08-translate-project-to-english/specs/documentation-language/spec.md +32 -0
  31. package/openspec/changes/archive/2026-01-08-translate-project-to-english/tasks.md +115 -0
  32. package/openspec/changes/archive/add-automated-tests/design.md +324 -0
  33. package/openspec/changes/archive/add-automated-tests/proposal.md +167 -0
  34. package/openspec/changes/archive/add-automated-tests/specs/automated-testing/spec.md +143 -0
  35. package/openspec/changes/archive/add-automated-tests/tasks.md +132 -0
  36. package/openspec/project.md +113 -0
  37. package/openspec/specs/documentation-language/spec.md +32 -0
  38. package/openspec/specs/mcp-resources/spec.md +94 -0
  39. package/package.json +46 -0
  40. package/spunti.md +19 -0
  41. package/tasks/todo.md +124 -0
  42. package/test-urls.js +18 -0
  43. package/tmp/test-org-search.js +55 -0
@@ -0,0 +1,132 @@
1
+ # Tasks: Add Automated Testing
2
+
3
+ Ordered list of work items to add automated tests to the project.
4
+
5
+ ## Phase 1: Foundation
6
+
7
+ - [x] Install Vitest and related dependencies
8
+ - [x] Create vitest.config.ts with TypeScript support
9
+ - [x] Create test directory structure: `tests/unit/`, `tests/integration/`, `tests/fixtures/`
10
+ - [x] Add test scripts to package.json (test, test:watch, test:coverage)
11
+ - [x] Create .gitignore entry for coverage directory
12
+ - [x] Configure coverage settings (threshold: 80%)
13
+
14
+ ## Phase 2: Create Mock Fixtures
15
+
16
+ - [x] Create `tests/fixtures/responses/status-success.json`
17
+ - [x] Create `tests/fixtures/responses/package-search-success.json`
18
+ - [x] Create `tests/fixtures/responses/package-show-success.json`
19
+ - [x] Create `tests/fixtures/responses/organization-list-success.json`
20
+ - [x] Create `tests/fixtures/responses/organization-show-success.json`
21
+ - [x] Create `tests/fixtures/responses/organization-search-success.json`
22
+ - [x] Create `tests/fixtures/responses/datastore-search-success.json`
23
+ - [x] Create `tests/fixtures/errors/timeout.json`
24
+ - [x] Create `tests/fixtures/errors/not-found.json`
25
+ - [x] Create `tests/fixtures/errors/server-error.json`
26
+ - [x] Document fixture structure in tests/README.md
27
+
28
+ ## Phase 3: Unit Tests for Utils
29
+
30
+ - [x] Create `tests/unit/formatting.test.ts`
31
+ - [x] Write test for truncateText() with normal text
32
+ - [x] Write test for truncateText() with empty string
33
+ - [x] Write test for truncateText() with text under limit
34
+ - [x] Write test for truncateText() with text over limit
35
+ - [x] Write test for formatDate() with ISO date string
36
+ - [x] Write test for formatDate() with null/undefined
37
+ - [x] Write test for formatBytes() with bytes
38
+ - [x] Write test for formatBytes() with kilobytes
39
+ - [x] Write test for formatBytes() with megabytes
40
+ - [x] Write test for formatBytes() with gigabytes
41
+ - [x] Run tests and verify all pass
42
+
43
+ - [x] Create `tests/unit/http.test.ts`
44
+ - [x] Write test for makeCkanRequest() successful response
45
+ - [x] Write test for makeCkanRequest() with error (404)
46
+ - [x] Write test for makeCkanRequest() with error (500)
47
+ - [x] Write test for makeCkanRequest() with timeout
48
+ - [x] Write test for makeCkanRequest() URL normalization (trailing slash)
49
+ - [x] Write test for makeCkanRequest() User-Agent header
50
+ - [x] Run tests and verify all pass
51
+
52
+ ## Phase 4: Integration Tests for Tools
53
+
54
+ ### Status Tool
55
+ - [x] Create `tests/integration/status.test.ts`
56
+ - [x] Write test for ckan_status_show with valid server URL
57
+ - [x] Write test for ckan_status_show with invalid URL
58
+ - [ ] Write test for ckan_status_show with server error
59
+ - [ ] Write test for markdown output format
60
+ - [ ] Write test for JSON output format
61
+ - [x] Run tests and verify all pass
62
+
63
+ ### Package Tools
64
+ - [ ] Create `tests/integration/package.test.ts`
65
+ - [ ] Write test for ckan_package_search with basic query
66
+ - [ ] Write test for ckan_package_search with facets
67
+ - [ ] Write test for ckan_package_search with pagination
68
+ - [ ] Write test for ckan_package_search with sorting
69
+ - [ ] Write test for ckan_package_search with filter query
70
+ - [ ] Write test for ckan_package_show with valid package ID
71
+ - [ ] Write test for ckan_package_show with invalid package ID
72
+ - [ ] Write test for markdown output format
73
+ - [ ] Write test for JSON output format
74
+ - [ ] Run tests and verify all pass
75
+
76
+ ### Organization Tools
77
+ - [ ] Create `tests/integration/organization.test.ts`
78
+ - [ ] Write test for ckan_organization_list
79
+ - [ ] Write test for ckan_organization_list with sorting
80
+ - [ ] Write test for ckan_organization_show
81
+ - [ ] Write test for ckan_organization_search
82
+ - [ ] Write test for markdown output format
83
+ - [ ] Write test for JSON output format
84
+ - [ ] Run tests and verify all pass
85
+
86
+ ### DataStore Tool
87
+ - [ ] Create `tests/integration/datastore.test.ts`
88
+ - [ ] Write test for ckan_datastore_search with basic query
89
+ - [ ] Write test for ckan_datastore_search with filters
90
+ - [ ] Write test for ckan_datastore_search with sorting
91
+ - [ ] Write test for ckan_datastore_search with pagination
92
+ - [ ] Write test for markdown output format
93
+ - [ ] Write test for JSON output format
94
+ - [ ] Run tests and verify all pass
95
+
96
+ ## Phase 5: Validation and Documentation
97
+
98
+ - [x] Run `npm test` and verify all tests pass
99
+ - [x] Run `npm run test:coverage` and check coverage report
100
+ - [ ] Verify coverage meets 80% threshold
101
+ - [ ] Update CLAUDE.md with testing instructions
102
+ - [ ] Update README.md with test section
103
+ - [x] Create test writing guide in tests/README.md
104
+ - [ ] Add example test in test writing guide
105
+
106
+ ## Phase 6: Continuous Integration
107
+
108
+ - [ ] Check if CI/CD exists (GitHub Actions, etc.)
109
+ - [ ] Add test step to CI pipeline if exists
110
+ - [ ] Add coverage reporting to CI if exists
111
+ - [ ] Verify tests run in CI environment
112
+ - [ ] Configure coverage upload to service (optional)
113
+
114
+ ## Dependencies
115
+
116
+ - Phase 1 must complete before Phase 2, 3, 4
117
+ - Phase 2 must complete before Phase 4 (fixtures needed)
118
+ - Phase 3 and Phase 4 can be done in parallel after Phase 2
119
+ - Phase 5 depends on completion of Phase 3 and Phase 4
120
+ - Phase 6 is optional and depends on CI infrastructure
121
+
122
+ ## Notes
123
+
124
+ - Use vi.fn() for mocking axios
125
+ - Keep tests focused and readable
126
+ - Mock realistic CKAN API responses from actual documentation
127
+ - One assertion per test when possible
128
+ - Describe expected behavior clearly in test names
129
+ - Use describe blocks for grouping related tests
130
+ - Use test.only sparingly and remove before committing
131
+ - Run tests in watch mode during development: `npm run test:watch`
132
+ - Generate coverage report: `npm run test:coverage`
@@ -0,0 +1,113 @@
1
+ # Project Context
2
+
3
+ ## Purpose
4
+ CKAN MCP Server - MCP (Model Context Protocol) server for interacting with open data portals based on CKAN (dati.gov.it, data.gov, demo.ckan.org, etc.).
5
+
6
+ Exposes MCP tools for:
7
+ - Advanced dataset search with Solr syntax
8
+ - DataStore queries for tabular data analysis
9
+ - Organization and group exploration
10
+ - Complete metadata access
11
+
12
+ ## Tech Stack
13
+ - **TypeScript** (ES2022 target)
14
+ - **Node.js**
15
+ - **esbuild** - ultra-fast build system (preferred over tsc for memory efficiency)
16
+ - **@modelcontextprotocol/sdk** - official MCP SDK
17
+ - **axios** - HTTP client for CKAN API calls
18
+ - **zod** - schema validation for tool inputs
19
+ - **express** - HTTP server (for HTTP transport mode)
20
+
21
+ ## Project Conventions
22
+
23
+ ### Code Style
24
+ - TypeScript strict mode enabled
25
+ - Single-file architecture (src/index.ts)
26
+ - Utility functions for reusable operations (makeCkanRequest, truncateText, formatDate, formatBytes)
27
+ - Zod schemas for strict input validation (reject extra parameters)
28
+ - Italian locale for date formatting (it-IT)
29
+ - Markdown output optimized for human readability; JSON available for programmatic use
30
+
31
+ ### Architecture Patterns
32
+ - **Single-file MCP server** - all logic in src/index.ts
33
+ - **Tool-based architecture** - 7 registered MCP tools for CKAN operations
34
+ - **Dual transport modes**:
35
+ - stdio (default) - for local MCP client integration
36
+ - HTTP - for remote access via POST /mcp endpoint
37
+ - **Read-only operations** - no data modification on CKAN
38
+ - **No caching** - fresh API calls for each request
39
+ - **Error handling** - HTTP errors, timeouts, server validation
40
+
41
+ ### Testing Strategy
42
+ - **No automated tests currently** (consider adding for critical tools)
43
+ - Manual testing:
44
+ - stdio mode: direct integration testing with Claude Desktop
45
+ - HTTP mode: curl-based testing against localhost endpoint
46
+ - Build validation via `npm run build` before deployment
47
+
48
+ ### Git Workflow
49
+ - **Main branch**: `main`
50
+ - **Commit style**: extremely concise, prioritize brevity over grammar
51
+ - **LOG.md**: date-based entries (YYYY-MM-DD format), most recent at top
52
+ - **GitHub operations**: use `gh` CLI exclusively
53
+ - Update LOG.md for any significant change
54
+
55
+ ## Domain Context
56
+
57
+ ### CKAN Knowledge
58
+ - **CKAN API v3** - standard API across all CKAN portals
59
+ - **Apache Solr** - powers search functionality
60
+ - Supports field:value syntax, AND/OR/NOT operators, wildcards, ranges
61
+ - Filter queries (fq) for filtering without affecting score
62
+ - Facets for statistical aggregations
63
+ - Sort and pagination (start/rows)
64
+
65
+ ### Output Conventions
66
+ - All tools support `format` parameter: `markdown` (default) or `json`
67
+ - Output truncated to 50000 characters max (hardcoded)
68
+ - Dates formatted in Italian locale
69
+ - File sizes in human-readable format
70
+
71
+ ## Important Constraints
72
+
73
+ ### Environment
74
+ - **WSL considerations** - memory constraints require esbuild instead of tsc
75
+ - Always use `npm run build` (esbuild) not `npm run build:tsc`
76
+ - esbuild bundles internal modules but keeps external deps as-is
77
+
78
+ ### Technical Limits
79
+ - 50000 character output limit (hardcoded, could be configurable)
80
+ - 30-second timeout for CKAN API calls
81
+ - Read-only operations only (security by design)
82
+ - No authentication (public endpoints only)
83
+ - No caching (trade-off: freshness vs performance)
84
+
85
+ ### Build System
86
+ - esbuild config: bundles TypeScript, externalizes runtime deps
87
+ - External deps must be in node_modules: @modelcontextprotocol/sdk, axios, express, zod
88
+ - tsconfig.json mainly for IDE support (esbuild ignores most options)
89
+
90
+ ## External Dependencies
91
+
92
+ ### CKAN Portals
93
+ Major supported portals:
94
+ - 🇮🇹 https://dati.gov.it (Italia)
95
+ - 🇺🇸 https://catalog.data.gov (United States)
96
+ - 🇨🇦 https://open.canada.ca/data (Canada)
97
+ - 🇬🇧 https://data.gov.uk (United Kingdom)
98
+ - 🇪🇺 https://data.europa.eu (European Union)
99
+ - 🌍 https://demo.ckan.org (Official CKAN demo)
100
+
101
+ ### CKAN API Integration
102
+ - All requests use axios with 30s timeout
103
+ - User-Agent: `CKAN-MCP-Server/1.0`
104
+ - URL normalization (remove trailing slash)
105
+ - Response validation (success === true)
106
+ - Error handling: HTTP errors, timeouts, unreachable servers
107
+
108
+ ### Portal Variations
109
+ Each CKAN portal may differ in:
110
+ - DataStore availability
111
+ - Custom dataset fields
112
+ - Available organizations and tags
113
+ - Supported resource formats
@@ -0,0 +1,32 @@
1
+ # documentation-language Specification
2
+
3
+ ## Purpose
4
+ TBD - created by archiving change translate-project-to-english. Update Purpose after archive.
5
+ ## Requirements
6
+ ### Requirement: Project documentation in English
7
+
8
+ The project SHALL provide all user-facing documentation in English to ensure accessibility for the international open data community.
9
+
10
+ #### Scenario: English documentation
11
+ Given a CKAN MCP Server project
12
+ When a user reads the documentation
13
+ Then the documentation is in English
14
+ And code examples are in English
15
+ And technical terms follow CKAN API terminology
16
+
17
+ ### Requirement: Preserve multilingual examples
18
+
19
+ The project SHALL preserve non-English portal names, organization titles, or data values in their original language to accurately reflect real-world CKAN portals.
20
+
21
+ #### Scenario: Italian portal example
22
+ Given an example using dati.gov.it
23
+ When the example references Italian organizations
24
+ Then organization names remain in Italian (e.g., "Regione Siciliana")
25
+ And descriptions are in English
26
+
27
+ #### Scenario: Non-English data values
28
+ Given an example query with data filters
29
+ When the data contains non-English values
30
+ Then those values are preserved in their original language
31
+ And the surrounding documentation explains the context in English
32
+
@@ -0,0 +1,94 @@
1
+ # mcp-resources Specification
2
+
3
+ ## Purpose
4
+ TBD - created by archiving change add-mcp-resources. Update Purpose after archive.
5
+ ## Requirements
6
+ ### Requirement: Dataset Resource Template
7
+
8
+ The system SHALL expose a resource template for accessing CKAN dataset metadata via the URI pattern `ckan://{server}/dataset/{id}`.
9
+
10
+ #### Scenario: Read dataset metadata successfully
11
+
12
+ - **WHEN** client reads `ckan://dati.gov.it/dataset/vaccini-covid`
13
+ - **THEN** server returns JSON with complete dataset metadata including title, description, resources, organization, tags
14
+
15
+ #### Scenario: Dataset not found
16
+
17
+ - **WHEN** client reads `ckan://demo.ckan.org/dataset/nonexistent-id`
18
+ - **THEN** server returns error indicating dataset not found
19
+
20
+ #### Scenario: Server unreachable
21
+
22
+ - **WHEN** client reads `ckan://invalid-server.example/dataset/test`
23
+ - **THEN** server returns error indicating server unreachable
24
+
25
+ ### Requirement: Resource Resource Template
26
+
27
+ The system SHALL expose a resource template for accessing CKAN resource metadata via the URI pattern `ckan://{server}/resource/{id}`.
28
+
29
+ #### Scenario: Read resource metadata successfully
30
+
31
+ - **WHEN** client reads `ckan://dati.gov.it/resource/abc-123-def`
32
+ - **THEN** server returns JSON with resource metadata including name, format, URL, size, and download link
33
+
34
+ #### Scenario: Resource not found
35
+
36
+ - **WHEN** client reads `ckan://demo.ckan.org/resource/invalid-id`
37
+ - **THEN** server returns error indicating resource not found
38
+
39
+ ### Requirement: Organization Resource Template
40
+
41
+ The system SHALL expose a resource template for accessing CKAN organization metadata via the URI pattern `ckan://{server}/organization/{name}`.
42
+
43
+ #### Scenario: Read organization metadata successfully
44
+
45
+ - **WHEN** client reads `ckan://dati.gov.it/organization/regione-toscana`
46
+ - **THEN** server returns JSON with organization metadata including title, description, and dataset count
47
+
48
+ #### Scenario: Organization not found
49
+
50
+ - **WHEN** client reads `ckan://demo.ckan.org/organization/nonexistent-org`
51
+ - **THEN** server returns error indicating organization not found
52
+
53
+ ### Requirement: URI Scheme Parsing
54
+
55
+ The system SHALL parse `ckan://` URIs extracting server hostname and path components.
56
+
57
+ #### Scenario: Parse standard URI
58
+
59
+ - **WHEN** URI is `ckan://dati.gov.it/dataset/test-id`
60
+ - **THEN** server extracts: server=`https://dati.gov.it`, type=`dataset`, id=`test-id`
61
+
62
+ #### Scenario: Parse URI with www prefix
63
+
64
+ - **WHEN** URI is `ckan://www.dati.gov.it/dataset/test-id`
65
+ - **THEN** server extracts: server=`https://www.dati.gov.it`, type=`dataset`, id=`test-id`
66
+
67
+ #### Scenario: Reject malformed URI
68
+
69
+ - **WHEN** URI is `ckan://invalid` (missing path)
70
+ - **THEN** server returns validation error
71
+
72
+ ### Requirement: Resource Response Format
73
+
74
+ The system SHALL return resource content as JSON with standard MCP resource response structure.
75
+
76
+ #### Scenario: JSON response structure
77
+
78
+ - **WHEN** client reads any valid resource URI
79
+ - **THEN** response contains `contents` array with `uri`, `mimeType` (application/json), and `text` (JSON string)
80
+
81
+ #### Scenario: Large response truncation
82
+
83
+ - **WHEN** resource content exceeds CHARACTER_LIMIT
84
+ - **THEN** response is truncated to CHARACTER_LIMIT with truncation indicator
85
+
86
+ ### Requirement: Resource Discovery
87
+
88
+ The system SHALL list available resource templates when client requests resource list.
89
+
90
+ #### Scenario: List resource templates
91
+
92
+ - **WHEN** client calls `resources/listTemplates`
93
+ - **THEN** server returns list of available URI templates with descriptions
94
+
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@aborruso/ckan-mcp-server",
3
+ "version": "0.3.1",
4
+ "description": "MCP server for interacting with CKAN open data portals",
5
+ "main": "dist/index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "build": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --external:@modelcontextprotocol/sdk --external:axios --external:express --external:zod",
9
+ "build:tsc": "node --max-old-space-size=8192 ./node_modules/typescript/bin/tsc",
10
+ "start": "node dist/index.js",
11
+ "dev": "npm run build && node dist/index.js",
12
+ "watch": "esbuild src/index.ts --bundle --platform=node --format=esm --outfile=dist/index.js --external:@modelcontextprotocol/sdk --external:axios --external:express --external:zod --watch",
13
+ "test": "vitest",
14
+ "test:watch": "vitest --watch",
15
+ "test:coverage": "vitest --coverage"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "ckan",
20
+ "open-data",
21
+ "api",
22
+ "data-portal"
23
+ ],
24
+ "author": "Andrea Borruso <aborruso@gmail.com>",
25
+ "license": "MIT",
26
+ "dependencies": {
27
+ "@modelcontextprotocol/sdk": "^1.0.4",
28
+ "axios": "^1.7.2",
29
+ "express": "^4.19.2",
30
+ "zod": "^3.23.8"
31
+ },
32
+ "devDependencies": {
33
+ "@types/express": "^4.17.21",
34
+ "@types/node": "^20.12.12",
35
+ "@vitest/coverage-v8": "^4.0.16",
36
+ "esbuild": "^0.27.2",
37
+ "typescript": "^5.4.5",
38
+ "vitest": "^4.0.16"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "engines": {
44
+ "node": ">=18.0.0"
45
+ }
46
+ }
package/spunti.md ADDED
@@ -0,0 +1,19 @@
1
+ # Intro
2
+
3
+ Spunti vari
4
+
5
+ ## Da fare
6
+
7
+ - farlo totalmente in inglese
8
+ - documentarne l'uso per chatGPT e Claude Code
9
+ - aggiunere agli esempi e al tool poter cercare per frequenza di aggiornamento
10
+ - il tema del peso del ranking dei risultati andrebbe approfondito. Prendere spunto da ckan canada
11
+
12
+ ## Tool con coi confrontarsi
13
+
14
+ - https://skywork.ai/skypage/en/unlocking-government-data-mcp-server/1980445961155629056
15
+ - https://github.com/ondics/ckan-mcp-server
16
+
17
+ ## Temi aperti
18
+
19
+ - la data di modifica di una risorsa sembra non esserci realmente, salvo non cambiare i metadti. Se si aggiorna soltanto il csv non ce ne è traccia?
package/tasks/todo.md ADDED
@@ -0,0 +1,124 @@
1
+ # Task: Aggiungere tool ckan_organization_search
2
+
3
+ ## Obiettivo
4
+
5
+ Implementare un nuovo tool MCP dedicato alla ricerca di organizzazioni per nome, con interfaccia più semplice rispetto a `package_search` con sintassi Solr.
6
+
7
+ ## Fasi
8
+
9
+ ### Fase 1: Implementazione tool
10
+ - [ ] Aggiungere `ckan_organization_search` in `src/index.ts` (prima riga 872)
11
+ - [ ] Schema input: `server_url`, `pattern`, `response_format`
12
+ - [ ] Costruire query Solr automatica: `organization:*{pattern}*`
13
+ - [ ] Chiamare `package_search` con `rows=0` e `facet.field=organization`
14
+ - [ ] Output JSON: `{ count: N, organizations: [...] }`
15
+ - [ ] Output markdown: tabella organizzazioni con conteggio dataset
16
+
17
+ ### Fase 2: Testing
18
+ - [ ] Build: `npm run build`
19
+ - [ ] Test con pattern "toscana"
20
+ - [ ] Verificare output JSON
21
+ - [ ] Verificare output markdown
22
+
23
+ ### Fase 3: Documentazione
24
+ - [ ] Aggiungere esempi in `EXAMPLES.md`
25
+ - [ ] Aggiornare `LOG.md` con data e modifiche
26
+
27
+ ## Dettagli Implementazione
28
+
29
+ **Posizione**: Dopo tool `ckan_organization_show` (circa riga 700) e prima di `ckan_datastore_search`
30
+
31
+ **Firma tool**:
32
+ ```typescript
33
+ server.registerTool(
34
+ "ckan_organization_search",
35
+ {
36
+ title: "Search CKAN Organizations by Name",
37
+ description: "...",
38
+ inputSchema: z.object({
39
+ server_url: z.string().url(),
40
+ pattern: z.string().min(1).describe("Search pattern (wildcards added automatically)"),
41
+ response_format: ResponseFormatSchema
42
+ }).strict(),
43
+ annotations: {
44
+ readOnlyHint: true,
45
+ destructiveHint: false,
46
+ idempotentHint: true,
47
+ openWorldHint: true
48
+ }
49
+ },
50
+ async (params) => { ... }
51
+ )
52
+ ```
53
+
54
+ **Logica**:
55
+ 1. Query Solr: `organization:*${params.pattern}*`
56
+ 2. API call: `package_search` con `rows=0`, `facet.field=["organization"]`, `facet.limit=500`
57
+ 3. Parsing facets per estrarre organizzazioni matchate
58
+ 4. Output formattato
59
+
60
+ ## Note
61
+
62
+ - Molto più semplice di costruire query manualmente
63
+ - Risparmio token: restituisce solo organizzazioni, non dataset
64
+ - Pattern matching automatico (l'utente non deve ricordare wildcard Solr)
65
+
66
+ ---
67
+
68
+ ## Review
69
+
70
+ ### Modifiche Completate
71
+
72
+ **File modificati**:
73
+ 1. `src/index.ts` - Aggiunto tool `ckan_organization_search` (righe 680-785)
74
+ 2. `EXAMPLES.md` - Aggiunti esempi di utilizzo (righe 54-73)
75
+ 3. `LOG.md` - Documentata la nuova feature
76
+
77
+ **Implementazione**:
78
+ - Tool registrato tra `ckan_organization_show` e `ckan_datastore_search`
79
+ - Schema input: `server_url`, `pattern`, `response_format`
80
+ - Query Solr automatica: `organization:*{pattern}*`
81
+ - Utilizza `package_search` con `rows=0` per efficienza massima
82
+ - Output JSON: `{ count, total_datasets, organizations: [...] }`
83
+ - Output markdown: tabella formattata con org e conteggio dataset
84
+
85
+ **Testing**:
86
+ - Build completato con successo (47ms, esbuild)
87
+ - Test manuale con pattern "toscana": ✅
88
+ - 2 organizzazioni trovate
89
+ - 11.000 dataset totali
90
+ - Output JSON corretto
91
+ - Zero dataset scaricati (solo facet)
92
+
93
+ **Vantaggi**:
94
+ - **UX migliorata**: API semplice vs sintassi Solr
95
+ - **Efficienza**: filtraggio lato server, zero dataset trasmessi
96
+ - **Token saving**: solo metadata organizzazioni
97
+ - **Performance**: wildcard automatici, no costruzione query manuale
98
+
99
+ ### Prossimi Passi
100
+
101
+ Per utilizzare il tool:
102
+ 1. Riavviare Claude Code per caricare il nuovo server MCP
103
+ 2. Il tool sarà disponibile come `ckan_organization_search`
104
+
105
+ ### Esempio Output
106
+
107
+ ```json
108
+ {
109
+ "count": 2,
110
+ "total_datasets": 11000,
111
+ "organizations": [
112
+ {
113
+ "name": "regione-toscana",
114
+ "display_name": "Regione Toscana",
115
+ "dataset_count": 10988
116
+ },
117
+ {
118
+ "name": "autorita-idrica-toscana",
119
+ "display_name": "Autorità Idrica Toscana",
120
+ "dataset_count": 12
121
+ }
122
+ ]
123
+ }
124
+ ```
package/test-urls.js ADDED
@@ -0,0 +1,18 @@
1
+ import { getDatasetViewUrl, getOrganizationViewUrl } from './src/utils/url-generator.js';
2
+
3
+ const serverUrl = 'https://www.dati.gov.it/opendata';
4
+ const pkg = {
5
+ id: 'f20b37d0cd56764f77e4c707cc62b528eeb48ff24cdb415174a0dd31b63cad0c',
6
+ name: 'test-dataset'
7
+ };
8
+
9
+ const org = {
10
+ id: 'org-id',
11
+ name: 'comune-di-bari'
12
+ };
13
+
14
+ console.log('Dataset URL:', getDatasetViewUrl(serverUrl, pkg));
15
+ console.log('Organization URL:', getOrganizationViewUrl(serverUrl, org));
16
+
17
+ const otherServer = 'https://demo.ckan.org';
18
+ console.log('Other Dataset URL:', getDatasetViewUrl(otherServer, pkg));
@@ -0,0 +1,55 @@
1
+ // Test manuale del tool ckan_organization_search
2
+ import axios from 'axios';
3
+
4
+ async function testOrgSearch() {
5
+ const serverUrl = 'https://www.dati.gov.it/opendata';
6
+ const pattern = 'toscana';
7
+ const query = `organization:*${pattern}*`;
8
+
9
+ console.log('Testing organization search...');
10
+ console.log('Pattern:', pattern);
11
+ console.log('Query:', query);
12
+ console.log('');
13
+
14
+ try {
15
+ const url = `${serverUrl}/api/3/action/package_search`;
16
+ const response = await axios.get(url, {
17
+ params: {
18
+ q: query,
19
+ rows: 0,
20
+ 'facet.field': JSON.stringify(['organization']),
21
+ 'facet.limit': 500
22
+ },
23
+ timeout: 30000,
24
+ headers: {
25
+ 'User-Agent': 'CKAN-MCP-Server/1.0'
26
+ }
27
+ });
28
+
29
+ const result = response.data.result;
30
+ const orgFacets = result.search_facets?.organization?.items || [];
31
+ const totalDatasets = result.count || 0;
32
+
33
+ console.log('Organizations found:', orgFacets.length);
34
+ console.log('Total datasets:', totalDatasets);
35
+ console.log('');
36
+
37
+ const jsonResult = {
38
+ count: orgFacets.length,
39
+ total_datasets: totalDatasets,
40
+ organizations: orgFacets.map((item) => ({
41
+ name: item.name,
42
+ display_name: item.display_name,
43
+ dataset_count: item.count
44
+ }))
45
+ };
46
+
47
+ console.log(JSON.stringify(jsonResult, null, 2));
48
+
49
+ } catch (error) {
50
+ console.error('Error:', error.message);
51
+ process.exit(1);
52
+ }
53
+ }
54
+
55
+ testOrgSearch();