@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.
- package/.claude/commands/openspec/apply.md +23 -0
- package/.claude/commands/openspec/archive.md +27 -0
- package/.claude/commands/openspec/proposal.md +28 -0
- package/.claude/settings.local.json +31 -0
- package/.gemini/commands/openspec/apply.toml +21 -0
- package/.gemini/commands/openspec/archive.toml +25 -0
- package/.gemini/commands/openspec/proposal.toml +26 -0
- package/.mcp.json +12 -0
- package/.opencode/command/openspec-apply.md +24 -0
- package/.opencode/command/openspec-archive.md +27 -0
- package/.opencode/command/openspec-proposal.md +29 -0
- package/AGENTS.md +18 -0
- package/CLAUDE.md +320 -0
- package/EXAMPLES.md +707 -0
- package/LICENSE.txt +21 -0
- package/LOG.md +154 -0
- package/PRD.md +912 -0
- package/README.md +468 -0
- package/REFACTORING.md +237 -0
- package/dist/index.js +1277 -0
- package/openspec/AGENTS.md +456 -0
- package/openspec/changes/archive/2026-01-08-add-mcp-resources/design.md +115 -0
- package/openspec/changes/archive/2026-01-08-add-mcp-resources/proposal.md +52 -0
- package/openspec/changes/archive/2026-01-08-add-mcp-resources/specs/mcp-resources/spec.md +92 -0
- package/openspec/changes/archive/2026-01-08-add-mcp-resources/tasks.md +56 -0
- package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/design.md +355 -0
- package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/proposal.md +161 -0
- package/openspec/changes/archive/2026-01-08-expand-test-coverage-specs/tasks.md +162 -0
- package/openspec/changes/archive/2026-01-08-translate-project-to-english/proposal.md +115 -0
- package/openspec/changes/archive/2026-01-08-translate-project-to-english/specs/documentation-language/spec.md +32 -0
- package/openspec/changes/archive/2026-01-08-translate-project-to-english/tasks.md +115 -0
- package/openspec/changes/archive/add-automated-tests/design.md +324 -0
- package/openspec/changes/archive/add-automated-tests/proposal.md +167 -0
- package/openspec/changes/archive/add-automated-tests/specs/automated-testing/spec.md +143 -0
- package/openspec/changes/archive/add-automated-tests/tasks.md +132 -0
- package/openspec/project.md +113 -0
- package/openspec/specs/documentation-language/spec.md +32 -0
- package/openspec/specs/mcp-resources/spec.md +94 -0
- package/package.json +46 -0
- package/spunti.md +19 -0
- package/tasks/todo.md +124 -0
- package/test-urls.js +18 -0
- package/tmp/test-org-search.js +55 -0
package/PRD.md
ADDED
|
@@ -0,0 +1,912 @@
|
|
|
1
|
+
# Product Requirements Document (PRD)
|
|
2
|
+
|
|
3
|
+
## CKAN MCP Server
|
|
4
|
+
|
|
5
|
+
**Version**: 1.0.0
|
|
6
|
+
**Last Updated**: 2026-01-08
|
|
7
|
+
**Author**: onData
|
|
8
|
+
**Status**: Implemented
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 1. Executive Summary
|
|
13
|
+
|
|
14
|
+
CKAN MCP Server è un server Model Context Protocol (MCP) che permette a AI agent (come Claude Desktop) di interagire con oltre 500 portali di dati aperti basati su CKAN in tutto il mondo. Il server espone strumenti MCP per ricercare dataset, esplorare organizzazioni, interrogare dati tabulari e accedere a metadati completi.
|
|
15
|
+
|
|
16
|
+
### 1.1 Problem Statement
|
|
17
|
+
|
|
18
|
+
Gli AI agent non hanno un modo nativo per:
|
|
19
|
+
- Scoprire e cercare dataset negli open data portal
|
|
20
|
+
- Interrogare metadati strutturati di dataset governativi
|
|
21
|
+
- Eseguire query su dati tabulari pubblicati su portali CKAN
|
|
22
|
+
- Esplorare organizzazioni pubbliche e la loro produzione di dati aperti
|
|
23
|
+
|
|
24
|
+
### 1.2 Solution
|
|
25
|
+
|
|
26
|
+
Un server MCP che espone tool per interagire con le API CKAN v3, permettendo agli AI agent di:
|
|
27
|
+
- Cercare dataset con query Solr avanzate
|
|
28
|
+
- Ottenere metadati completi di dataset e risorse
|
|
29
|
+
- Esplorare organizzazioni e gruppi
|
|
30
|
+
- Interrogare il DataStore con filtri e sorting
|
|
31
|
+
- Analizzare statistiche tramite faceting
|
|
32
|
+
|
|
33
|
+
**Distribution Strategy**: Pubblicazione su npm registry per installazione semplice e universale (come PyPI per Python), permettendo a chiunque di installare il server con un singolo comando senza bisogno di clonare repository o compilare codice.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 2. Target Audience
|
|
38
|
+
|
|
39
|
+
### 2.1 Primary Users
|
|
40
|
+
|
|
41
|
+
- **Data Scientist & Analyst**: Ricerca e analisi di dataset pubblici
|
|
42
|
+
- **Civic Hacker & Developer**: Sviluppo applicazioni su dati aperti
|
|
43
|
+
- **Researcher & Journalist**: Investigazione e analisi dati governativi
|
|
44
|
+
- **Public Administration**: Esplorazione cataloghi dati aperti
|
|
45
|
+
|
|
46
|
+
### 2.2 AI Agent Use Cases
|
|
47
|
+
|
|
48
|
+
- **Claude Desktop**: Integrazione nativa tramite configurazione MCP
|
|
49
|
+
- **Altri client MCP**: Qualsiasi client compatibile con MCP protocol
|
|
50
|
+
- **Automazioni**: Script e workflow che necessitano accesso a CKAN
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## 3. Core Requirements
|
|
55
|
+
|
|
56
|
+
### 3.1 Functional Requirements
|
|
57
|
+
|
|
58
|
+
#### FR-1: Dataset Search
|
|
59
|
+
- **Priority**: High
|
|
60
|
+
- **Description**: Cercare dataset su qualsiasi server CKAN usando sintassi Solr
|
|
61
|
+
- **Acceptance Criteria**:
|
|
62
|
+
- Supporto query full-text (q parameter)
|
|
63
|
+
- Filtri avanzati (fq parameter)
|
|
64
|
+
- Faceting per statistiche (organization, tags, formats)
|
|
65
|
+
- Paginazione (start/rows)
|
|
66
|
+
- Ordinamento (sort parameter)
|
|
67
|
+
- Output in formato Markdown o JSON
|
|
68
|
+
- **Implementation Status**: ✅ Implemented (`ckan_package_search`)
|
|
69
|
+
|
|
70
|
+
#### FR-2: Dataset Details
|
|
71
|
+
- **Priority**: High
|
|
72
|
+
- **Description**: Ottenere metadati completi di un dataset specifico
|
|
73
|
+
- **Acceptance Criteria**:
|
|
74
|
+
- Ricerca per ID o name
|
|
75
|
+
- Metadati base (title, description, author, license)
|
|
76
|
+
- Lista risorse con dettagli (format, size, URL, DataStore status)
|
|
77
|
+
- Organizzazione e tag
|
|
78
|
+
- Campi extra custom
|
|
79
|
+
- Tracking statistics opzionale
|
|
80
|
+
- **Implementation Status**: ✅ Implemented (`ckan_package_show`)
|
|
81
|
+
|
|
82
|
+
#### FR-3: Organization Discovery
|
|
83
|
+
- **Priority**: Medium
|
|
84
|
+
- **Description**: Esplorare organizzazioni che pubblicano dataset
|
|
85
|
+
- **Acceptance Criteria**:
|
|
86
|
+
- Lista tutte le organizzazioni (con/senza dettagli completi)
|
|
87
|
+
- Ricerca per pattern nel nome
|
|
88
|
+
- Ordinamento e paginazione
|
|
89
|
+
- Conteggio dataset per organizzazione
|
|
90
|
+
- Dettagli completi organizzazione con lista dataset
|
|
91
|
+
- **Implementation Status**: ✅ Implemented (`ckan_organization_list`, `ckan_organization_show`, `ckan_organization_search`)
|
|
92
|
+
|
|
93
|
+
#### FR-4: DataStore Query
|
|
94
|
+
- **Priority**: Medium
|
|
95
|
+
- **Description**: Interrogare dati tabulari nel CKAN DataStore
|
|
96
|
+
- **Acceptance Criteria**:
|
|
97
|
+
- Query per resource_id
|
|
98
|
+
- Filtri chiave-valore
|
|
99
|
+
- Full-text search (q parameter)
|
|
100
|
+
- Ordinamento
|
|
101
|
+
- Selezione campi specifici
|
|
102
|
+
- Paginazione (limit/offset)
|
|
103
|
+
- Valori distinct
|
|
104
|
+
- **Implementation Status**: ✅ Implemented (`ckan_datastore_search`)
|
|
105
|
+
|
|
106
|
+
#### FR-5: Server Status Check
|
|
107
|
+
- **Priority**: Low
|
|
108
|
+
- **Description**: Verificare disponibilità e versione di un server CKAN
|
|
109
|
+
- **Acceptance Criteria**:
|
|
110
|
+
- Verifica connessione server
|
|
111
|
+
- Informazioni versione CKAN
|
|
112
|
+
- Site title e URL
|
|
113
|
+
- **Implementation Status**: ✅ Implemented (`ckan_status_show`)
|
|
114
|
+
|
|
115
|
+
#### FR-6: Package List
|
|
116
|
+
- **Priority**: Low
|
|
117
|
+
- **Description**: Lista semplice di tutti i dataset (solo nomi)
|
|
118
|
+
- **Acceptance Criteria**:
|
|
119
|
+
- Lista nomi dataset
|
|
120
|
+
- Paginazione
|
|
121
|
+
- **Implementation Status**: ❌ Not implemented (menzionato nel README ma non presente nel codice)
|
|
122
|
+
|
|
123
|
+
### 3.2 Non-Functional Requirements
|
|
124
|
+
|
|
125
|
+
#### NFR-1: Performance
|
|
126
|
+
- **Response Time**: Timeout HTTP a 30 secondi
|
|
127
|
+
- **Throughput**: Limitato dalle API CKAN del server remoto
|
|
128
|
+
- **Scalability**: Stateless, può gestire richieste multiple in parallelo
|
|
129
|
+
|
|
130
|
+
#### NFR-2: Reliability
|
|
131
|
+
- **Error Handling**:
|
|
132
|
+
- Gestione errori HTTP (404, 500, timeout)
|
|
133
|
+
- Validazione input con Zod strict schemas
|
|
134
|
+
- Messaggi errore descrittivi
|
|
135
|
+
- **Availability**: Dipende dalla disponibilità dei server CKAN remoti
|
|
136
|
+
|
|
137
|
+
#### NFR-3: Usability
|
|
138
|
+
- **Output Format**:
|
|
139
|
+
- Markdown per leggibilità umana (default)
|
|
140
|
+
- JSON per elaborazione programmatica
|
|
141
|
+
- **Character Limit**: Troncamento automatico a 50.000 caratteri
|
|
142
|
+
- **Documentation**: README completo con esempi
|
|
143
|
+
|
|
144
|
+
#### NFR-4: Compatibility
|
|
145
|
+
- **CKAN Versions**: API v3 (compatibile con CKAN 2.x e 3.x)
|
|
146
|
+
- **Node.js**: Richiede Node.js >= 18.0.0
|
|
147
|
+
- **Transport Modes**:
|
|
148
|
+
- stdio (default) per integrazione locale
|
|
149
|
+
- HTTP per accesso remoto
|
|
150
|
+
|
|
151
|
+
#### NFR-5: Security
|
|
152
|
+
- **Authentication**: Non supportata (solo endpoint pubblici)
|
|
153
|
+
- **Read-Only**: Tutti i tool sono read-only, nessuna modifica ai dati
|
|
154
|
+
- **Input Validation**: Strict schema validation con Zod
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## 4. Technical Architecture
|
|
159
|
+
|
|
160
|
+
### 4.1 Technology Stack
|
|
161
|
+
|
|
162
|
+
**Runtime**:
|
|
163
|
+
- Node.js >= 18.0.0
|
|
164
|
+
- TypeScript (ES2022)
|
|
165
|
+
|
|
166
|
+
**Dependencies**:
|
|
167
|
+
- `@modelcontextprotocol/sdk@^1.0.4` - MCP protocol implementation
|
|
168
|
+
- `axios@^1.7.2` - HTTP client
|
|
169
|
+
- `zod@^3.23.8` - Schema validation
|
|
170
|
+
- `express@^4.19.2` - HTTP server (modalità HTTP)
|
|
171
|
+
|
|
172
|
+
**Build Tools**:
|
|
173
|
+
- `esbuild@^0.27.2` - Bundler ultra-veloce (~4ms)
|
|
174
|
+
- `typescript@^5.4.5` - Type checking e editor support
|
|
175
|
+
|
|
176
|
+
### 4.2 Architecture Diagram
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
┌─────────────────────────────────────────────────────┐
|
|
180
|
+
│ MCP Client │
|
|
181
|
+
│ (Claude Desktop, etc.) │
|
|
182
|
+
└─────────────┬───────────────────────────────────────┘
|
|
183
|
+
│
|
|
184
|
+
│ MCP Protocol (stdio or HTTP)
|
|
185
|
+
│
|
|
186
|
+
┌─────────────▼───────────────────────────────────────┐
|
|
187
|
+
│ CKAN MCP Server │
|
|
188
|
+
│ ┌───────────────────────────────────────────────┐ │
|
|
189
|
+
│ │ MCP Tool Registry │ │
|
|
190
|
+
│ │ - ckan_package_search │ │
|
|
191
|
+
│ │ - ckan_package_show │ │
|
|
192
|
+
│ │ - ckan_organization_list/show/search │ │
|
|
193
|
+
│ │ - ckan_datastore_search │ │
|
|
194
|
+
│ │ - ckan_status_show │ │
|
|
195
|
+
│ └───────────┬───────────────────────────────────┘ │
|
|
196
|
+
│ │ │
|
|
197
|
+
│ ┌───────────▼───────────────────────────────────┐ │
|
|
198
|
+
│ │ HTTP Client (axios) │ │
|
|
199
|
+
│ │ - Timeout: 30s │ │
|
|
200
|
+
│ │ - User-Agent: CKAN-MCP-Server/1.0 │ │
|
|
201
|
+
│ └───────────┬───────────────────────────────────┘ │
|
|
202
|
+
└──────────────┼───────────────────────────────────────┘
|
|
203
|
+
│
|
|
204
|
+
│ HTTPS
|
|
205
|
+
│
|
|
206
|
+
┌──────────────▼───────────────────────────────────────┐
|
|
207
|
+
│ CKAN Servers (worldwide) │
|
|
208
|
+
│ - dati.gov.it (IT) │
|
|
209
|
+
│ - data.gov (US) │
|
|
210
|
+
│ - open.canada.ca (CA) │
|
|
211
|
+
│ - data.gov.uk (UK) │
|
|
212
|
+
│ - data.europa.eu (EU) │
|
|
213
|
+
│ - 500+ altri portali CKAN │
|
|
214
|
+
└──────────────────────────────────────────────────────┘
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 4.3 Component Description
|
|
218
|
+
|
|
219
|
+
#### MCP Tool Registry
|
|
220
|
+
Registra i tool MCP disponibili con:
|
|
221
|
+
- Input schema (Zod validation)
|
|
222
|
+
- Output format (Markdown/JSON)
|
|
223
|
+
- MCP annotations (readonly, idempotent, etc.)
|
|
224
|
+
- Handler function
|
|
225
|
+
|
|
226
|
+
#### HTTP Client Layer
|
|
227
|
+
- Normalizza URL server (rimuove trailing slash)
|
|
228
|
+
- Costruisce endpoint API: `{server_url}/api/3/action/{action}`
|
|
229
|
+
- Gestisce timeout e errori
|
|
230
|
+
- Valida response (`success: true`)
|
|
231
|
+
|
|
232
|
+
#### Output Formatter
|
|
233
|
+
- Markdown: Tabelle, sezioni, formatting per leggibilità
|
|
234
|
+
- JSON: Structured output con `structuredContent`
|
|
235
|
+
- Truncation: Limita output a CHARACTER_LIMIT (50000)
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## 5. MCP Tools Specification
|
|
240
|
+
|
|
241
|
+
### 5.1 ckan_package_search
|
|
242
|
+
|
|
243
|
+
**Purpose**: Ricerca dataset con query Solr avanzate
|
|
244
|
+
|
|
245
|
+
**Input Parameters**:
|
|
246
|
+
```typescript
|
|
247
|
+
{
|
|
248
|
+
server_url: string (required) // Base URL server CKAN
|
|
249
|
+
q: string (default: "*:*") // Query Solr
|
|
250
|
+
fq?: string // Filter query
|
|
251
|
+
rows: number (default: 10) // Risultati per pagina (max 1000)
|
|
252
|
+
start: number (default: 0) // Offset paginazione
|
|
253
|
+
sort?: string // Es: "metadata_modified desc"
|
|
254
|
+
facet_field?: string[] // Campi per faceting
|
|
255
|
+
facet_limit: number (default: 50) // Max valori per facet
|
|
256
|
+
include_drafts: boolean (default: false)
|
|
257
|
+
response_format: "markdown" | "json" (default: "markdown")
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Output**:
|
|
262
|
+
- Conteggio totale risultati
|
|
263
|
+
- Array di dataset con metadati base
|
|
264
|
+
- Facets (se richiesti)
|
|
265
|
+
- Link paginazione
|
|
266
|
+
|
|
267
|
+
**Solr Query Examples**:
|
|
268
|
+
- `q: "popolazione"` - Full-text search
|
|
269
|
+
- `q: "title:covid"` - Search in field
|
|
270
|
+
- `q: "tags:sanità"` - Tag filter
|
|
271
|
+
- `fq: "organization:comune-palermo"` - Organization filter
|
|
272
|
+
- `fq: "res_format:CSV"` - Resource format filter
|
|
273
|
+
|
|
274
|
+
### 5.2 ckan_package_show
|
|
275
|
+
|
|
276
|
+
**Purpose**: Dettagli completi di un dataset
|
|
277
|
+
|
|
278
|
+
**Input Parameters**:
|
|
279
|
+
```typescript
|
|
280
|
+
{
|
|
281
|
+
server_url: string (required)
|
|
282
|
+
id: string (required) // Dataset ID o name
|
|
283
|
+
include_tracking: boolean (default: false)
|
|
284
|
+
response_format: "markdown" | "json"
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Output**:
|
|
289
|
+
- Metadati completi (title, description, author, license)
|
|
290
|
+
- Organizzazione
|
|
291
|
+
- Tags e gruppi
|
|
292
|
+
- Lista risorse con dettagli (format, size, URL, DataStore status)
|
|
293
|
+
- Extra fields custom
|
|
294
|
+
|
|
295
|
+
### 5.3 ckan_organization_list
|
|
296
|
+
|
|
297
|
+
**Purpose**: Lista organizzazioni
|
|
298
|
+
|
|
299
|
+
**Input Parameters**:
|
|
300
|
+
```typescript
|
|
301
|
+
{
|
|
302
|
+
server_url: string (required)
|
|
303
|
+
all_fields: boolean (default: false)
|
|
304
|
+
sort: string (default: "name asc")
|
|
305
|
+
limit: number (default: 100) // 0 per solo count
|
|
306
|
+
offset: number (default: 0)
|
|
307
|
+
response_format: "markdown" | "json"
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Output**:
|
|
312
|
+
- Array di organizzazioni (nomi o oggetti completi)
|
|
313
|
+
- Se `limit=0`: conteggio organizzazioni con dataset
|
|
314
|
+
|
|
315
|
+
### 5.4 ckan_organization_show
|
|
316
|
+
|
|
317
|
+
**Purpose**: Dettagli organizzazione specifica
|
|
318
|
+
|
|
319
|
+
**Input Parameters**:
|
|
320
|
+
```typescript
|
|
321
|
+
{
|
|
322
|
+
server_url: string (required)
|
|
323
|
+
id: string (required) // Organization ID o name
|
|
324
|
+
include_datasets: boolean (default: true)
|
|
325
|
+
include_users: boolean (default: false)
|
|
326
|
+
response_format: "markdown" | "json"
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Output**:
|
|
331
|
+
- Dettagli organizzazione
|
|
332
|
+
- Lista dataset (opzionale)
|
|
333
|
+
- Lista utenti con ruoli (opzionale)
|
|
334
|
+
|
|
335
|
+
### 5.5 ckan_organization_search
|
|
336
|
+
|
|
337
|
+
**Purpose**: Ricerca organizzazioni per pattern
|
|
338
|
+
|
|
339
|
+
**Input Parameters**:
|
|
340
|
+
```typescript
|
|
341
|
+
{
|
|
342
|
+
server_url: string (required)
|
|
343
|
+
pattern: string (required) // Pattern (wildcards automatici)
|
|
344
|
+
response_format: "markdown" | "json"
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**Output**:
|
|
349
|
+
- Lista organizzazioni matchanti
|
|
350
|
+
- Conteggio dataset per organizzazione
|
|
351
|
+
- Totale dataset
|
|
352
|
+
|
|
353
|
+
**Implementation**: Usa `package_search` con `organization:*{pattern}*` e faceting
|
|
354
|
+
|
|
355
|
+
### 5.6 ckan_datastore_search
|
|
356
|
+
|
|
357
|
+
**Purpose**: Query dati tabulari nel DataStore
|
|
358
|
+
|
|
359
|
+
**Input Parameters**:
|
|
360
|
+
```typescript
|
|
361
|
+
{
|
|
362
|
+
server_url: string (required)
|
|
363
|
+
resource_id: string (required)
|
|
364
|
+
q?: string // Full-text search
|
|
365
|
+
filters?: Record<string, any> // Filtri chiave-valore
|
|
366
|
+
limit: number (default: 100) // Max 32000
|
|
367
|
+
offset: number (default: 0)
|
|
368
|
+
fields?: string[] // Campi da restituire
|
|
369
|
+
sort?: string // Es: "anno desc"
|
|
370
|
+
distinct: boolean (default: false)
|
|
371
|
+
response_format: "markdown" | "json"
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
**Output**:
|
|
376
|
+
- Total records count
|
|
377
|
+
- Fields metadata (type, id)
|
|
378
|
+
- Records (max 50 in markdown per leggibilità)
|
|
379
|
+
- Paginazione info
|
|
380
|
+
|
|
381
|
+
**Limitations**:
|
|
382
|
+
- Non tutte le risorse hanno DataStore attivo
|
|
383
|
+
- Max 32000 record per query
|
|
384
|
+
|
|
385
|
+
### 5.7 ckan_status_show
|
|
386
|
+
|
|
387
|
+
**Purpose**: Verifica stato server CKAN
|
|
388
|
+
|
|
389
|
+
**Input Parameters**:
|
|
390
|
+
```typescript
|
|
391
|
+
{
|
|
392
|
+
server_url: string (required)
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
**Output**:
|
|
397
|
+
- Online status
|
|
398
|
+
- CKAN version
|
|
399
|
+
- Site title
|
|
400
|
+
- Site URL
|
|
401
|
+
|
|
402
|
+
---
|
|
403
|
+
|
|
404
|
+
## 6. Supported CKAN Portals
|
|
405
|
+
|
|
406
|
+
Il server può connettersi a **qualsiasi server CKAN pubblico**. Principali portali:
|
|
407
|
+
|
|
408
|
+
| Country | Portal | URL |
|
|
409
|
+
|---------|--------|-----|
|
|
410
|
+
| 🇮🇹 Italia | Portale Nazionale Dati Aperti | https://www.dati.gov.it/opendata |
|
|
411
|
+
| 🇺🇸 USA | Data.gov | https://catalog.data.gov |
|
|
412
|
+
| 🇨🇦 Canada | Open Government | https://open.canada.ca/data |
|
|
413
|
+
| 🇬🇧 UK | Data.gov.uk | https://data.gov.uk |
|
|
414
|
+
| 🇪🇺 EU | European Data Portal | https://data.europa.eu |
|
|
415
|
+
| 🌍 Demo | CKAN Official Demo | https://demo.ckan.org |
|
|
416
|
+
|
|
417
|
+
**Compatibilità**:
|
|
418
|
+
- CKAN API v3 (CKAN 2.x e 3.x)
|
|
419
|
+
- Oltre 500 portali nel mondo
|
|
420
|
+
|
|
421
|
+
---
|
|
422
|
+
|
|
423
|
+
## 7. Installation & Deployment
|
|
424
|
+
|
|
425
|
+
### 7.1 Prerequisites
|
|
426
|
+
|
|
427
|
+
- Node.js >= 18.0.0
|
|
428
|
+
- npm o yarn
|
|
429
|
+
|
|
430
|
+
### 7.2 Installation
|
|
431
|
+
|
|
432
|
+
#### Option 1: npm Package (Recommended - PLANNED)
|
|
433
|
+
|
|
434
|
+
**Global Installation**:
|
|
435
|
+
```bash
|
|
436
|
+
npm install -g ckan-mcp-server
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
**Local Installation**:
|
|
440
|
+
```bash
|
|
441
|
+
npm install ckan-mcp-server
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
**npx (No Installation)**:
|
|
445
|
+
```bash
|
|
446
|
+
npx ckan-mcp-server
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
> **Note**: La pubblicazione su npm registry è pianificata per permettere installazione semplice come PyPI in Python. Attualmente richiede installazione da repository.
|
|
450
|
+
|
|
451
|
+
#### Option 2: From Source (Current)
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
git clone https://github.com/ondata/ckan-mcp-server
|
|
455
|
+
cd ckan-mcp-server
|
|
456
|
+
npm install
|
|
457
|
+
npm run build
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### 7.3 Usage Modes
|
|
461
|
+
|
|
462
|
+
#### stdio Mode (Default)
|
|
463
|
+
Per integrazione con Claude Desktop e altri client MCP locali:
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
npm start
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
**Claude Desktop Configuration** (`claude_desktop_config.json`):
|
|
470
|
+
|
|
471
|
+
*After npm publication*:
|
|
472
|
+
```json
|
|
473
|
+
{
|
|
474
|
+
"mcpServers": {
|
|
475
|
+
"ckan": {
|
|
476
|
+
"command": "npx",
|
|
477
|
+
"args": ["ckan-mcp-server"]
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
*Current (from source)*:
|
|
484
|
+
```json
|
|
485
|
+
{
|
|
486
|
+
"mcpServers": {
|
|
487
|
+
"ckan": {
|
|
488
|
+
"command": "node",
|
|
489
|
+
"args": ["/path/to/ckan-mcp-server/dist/index.js"]
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
#### HTTP Mode
|
|
496
|
+
Per accesso remoto via HTTP:
|
|
497
|
+
|
|
498
|
+
```bash
|
|
499
|
+
TRANSPORT=http PORT=3000 npm start
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Server disponibile su: `http://localhost:3000/mcp`
|
|
503
|
+
|
|
504
|
+
**Test HTTP endpoint**:
|
|
505
|
+
```bash
|
|
506
|
+
curl -X POST http://localhost:3000/mcp \
|
|
507
|
+
-H "Content-Type: application/json" \
|
|
508
|
+
-d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
### 7.4 Build System
|
|
512
|
+
|
|
513
|
+
Il progetto usa **esbuild** (non tsc) per:
|
|
514
|
+
- Build ultra-veloce (~4ms vs minuti con tsc)
|
|
515
|
+
- Minimo utilizzo memoria (importante in WSL)
|
|
516
|
+
- Bundle automatico con tree-shaking
|
|
517
|
+
|
|
518
|
+
```bash
|
|
519
|
+
npm run build # Build con esbuild
|
|
520
|
+
npm run watch # Watch mode
|
|
521
|
+
npm run dev # Build + run
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
---
|
|
525
|
+
|
|
526
|
+
## 8. Use Cases & Examples
|
|
527
|
+
|
|
528
|
+
### 8.1 Use Case 1: Dataset Discovery
|
|
529
|
+
|
|
530
|
+
**Scenario**: Un data scientist cerca dataset su popolazione italiana
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
// Step 1: Cerca dataset
|
|
534
|
+
ckan_package_search({
|
|
535
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
536
|
+
q: "popolazione",
|
|
537
|
+
rows: 20,
|
|
538
|
+
sort: "metadata_modified desc"
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
// Step 2: Ottieni dettagli dataset interessante
|
|
542
|
+
ckan_package_show({
|
|
543
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
544
|
+
id: "popolazione-residente-2023"
|
|
545
|
+
})
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### 8.2 Use Case 2: Organization Analysis
|
|
549
|
+
|
|
550
|
+
**Scenario**: Analizzare la produzione di dati aperti regionali
|
|
551
|
+
|
|
552
|
+
```typescript
|
|
553
|
+
// Step 1: Cerca organizzazioni regionali
|
|
554
|
+
ckan_organization_search({
|
|
555
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
556
|
+
pattern: "regione"
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
// Step 2: Analizza dataset di una regione
|
|
560
|
+
ckan_organization_show({
|
|
561
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
562
|
+
id: "regione-siciliana",
|
|
563
|
+
include_datasets: true
|
|
564
|
+
})
|
|
565
|
+
|
|
566
|
+
// Step 3: Cerca dataset specifici dell'organizzazione
|
|
567
|
+
ckan_package_search({
|
|
568
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
569
|
+
fq: "organization:regione-siciliana",
|
|
570
|
+
facet_field: ["tags", "res_format"],
|
|
571
|
+
rows: 50
|
|
572
|
+
})
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
### 8.3 Use Case 3: Data Analysis with DataStore
|
|
576
|
+
|
|
577
|
+
**Scenario**: Analizzare dati tabulari COVID-19
|
|
578
|
+
|
|
579
|
+
```typescript
|
|
580
|
+
// Step 1: Cerca dataset COVID
|
|
581
|
+
ckan_package_search({
|
|
582
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
583
|
+
q: "covid",
|
|
584
|
+
fq: "res_format:CSV"
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
// Step 2: Ottieni dettagli e resource_id
|
|
588
|
+
ckan_package_show({
|
|
589
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
590
|
+
id: "covid-19-italia"
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
// Step 3: Query DataStore
|
|
594
|
+
ckan_datastore_search({
|
|
595
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
596
|
+
resource_id: "abc-123-def",
|
|
597
|
+
filters: { "regione": "Sicilia" },
|
|
598
|
+
sort: "data desc",
|
|
599
|
+
limit: 100
|
|
600
|
+
})
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
### 8.4 Use Case 4: Statistical Analysis with Faceting
|
|
604
|
+
|
|
605
|
+
**Scenario**: Analizzare distribuzione dataset per formato e organizzazione
|
|
606
|
+
|
|
607
|
+
```typescript
|
|
608
|
+
// Statistiche formati
|
|
609
|
+
ckan_package_search({
|
|
610
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
611
|
+
facet_field: ["res_format"],
|
|
612
|
+
facet_limit: 100,
|
|
613
|
+
rows: 0 // Solo facets, no results
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
// Statistiche organizzazioni
|
|
617
|
+
ckan_package_search({
|
|
618
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
619
|
+
facet_field: ["organization"],
|
|
620
|
+
facet_limit: 50,
|
|
621
|
+
rows: 0
|
|
622
|
+
})
|
|
623
|
+
|
|
624
|
+
// Distribuzione tag
|
|
625
|
+
ckan_package_search({
|
|
626
|
+
server_url: "https://www.dati.gov.it/opendata",
|
|
627
|
+
facet_field: ["tags"],
|
|
628
|
+
facet_limit: 50,
|
|
629
|
+
rows: 0
|
|
630
|
+
})
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
---
|
|
634
|
+
|
|
635
|
+
## 9. Limitations & Constraints
|
|
636
|
+
|
|
637
|
+
### 9.1 Current Limitations
|
|
638
|
+
|
|
639
|
+
1. **Read-Only**:
|
|
640
|
+
- Non supporta creazione/modifica dataset
|
|
641
|
+
- Solo endpoint pubblici (no autenticazione)
|
|
642
|
+
|
|
643
|
+
2. **Character Limit**:
|
|
644
|
+
- Output troncato a 50.000 caratteri
|
|
645
|
+
- Hardcoded, non configurabile
|
|
646
|
+
|
|
647
|
+
3. **No Caching**:
|
|
648
|
+
- Ogni richiesta fa chiamata HTTP fresca
|
|
649
|
+
- Nessuna cache locale
|
|
650
|
+
|
|
651
|
+
4. **DataStore Limitations**:
|
|
652
|
+
- Non tutte le risorse hanno DataStore attivo
|
|
653
|
+
- Max 32.000 record per query
|
|
654
|
+
- Dipende dalla configurazione del server CKAN
|
|
655
|
+
|
|
656
|
+
5. **No SQL Support**:
|
|
657
|
+
- Tool `ckan_datastore_search_sql` menzionato come "in sviluppo"
|
|
658
|
+
- Non implementato nel codice attuale
|
|
659
|
+
|
|
660
|
+
6. **Timeout**:
|
|
661
|
+
- 30 secondi fissi per HTTP request
|
|
662
|
+
- Non configurabile
|
|
663
|
+
|
|
664
|
+
7. **Locale**:
|
|
665
|
+
- Date formattate in 'it-IT'
|
|
666
|
+
- Non parametrizzato
|
|
667
|
+
|
|
668
|
+
### 9.2 External Dependencies
|
|
669
|
+
|
|
670
|
+
- **Network**: Richiede connessione internet
|
|
671
|
+
- **CKAN Server Availability**: Dipende dalla disponibilità dei server remoti
|
|
672
|
+
- **CKAN API Compatibility**: Richiede CKAN API v3
|
|
673
|
+
|
|
674
|
+
### 9.3 Known Issues
|
|
675
|
+
|
|
676
|
+
- Tool `ckan_package_list` documentato nel README ma non implementato
|
|
677
|
+
- Nessun test automatizzato
|
|
678
|
+
- Error messages potrebbero essere più specifici
|
|
679
|
+
|
|
680
|
+
---
|
|
681
|
+
|
|
682
|
+
## 10. Future Enhancements
|
|
683
|
+
|
|
684
|
+
### 10.1 Planned Features
|
|
685
|
+
|
|
686
|
+
#### High Priority
|
|
687
|
+
|
|
688
|
+
- [ ] **npm Package Publication**
|
|
689
|
+
- Pubblicare su npm registry pubblico
|
|
690
|
+
- Installazione globale/locale senza build
|
|
691
|
+
- CLI eseguibile: `ckan-mcp-server`
|
|
692
|
+
- npx support per uso senza installazione
|
|
693
|
+
- **Goal**: Semplicità di installazione come `pip install` in Python
|
|
694
|
+
|
|
695
|
+
- [ ] **Authentication Support**
|
|
696
|
+
- API key per endpoint privati
|
|
697
|
+
- OAuth per portali che lo supportano
|
|
698
|
+
|
|
699
|
+
- [ ] **SQL Query Support**
|
|
700
|
+
- Implementare `ckan_datastore_search_sql`
|
|
701
|
+
- Supporto query SQL complete
|
|
702
|
+
|
|
703
|
+
- [ ] **Caching Layer**
|
|
704
|
+
- Cache risultati frequenti
|
|
705
|
+
- TTL configurabile
|
|
706
|
+
- Invalidation strategy
|
|
707
|
+
|
|
708
|
+
#### Medium Priority
|
|
709
|
+
|
|
710
|
+
- [ ] **Advanced DataStore Features**
|
|
711
|
+
- Support for aggregations
|
|
712
|
+
- JOIN tra risorse
|
|
713
|
+
- Computed fields
|
|
714
|
+
|
|
715
|
+
- [ ] **Batch Operations**
|
|
716
|
+
- Query multiple in parallelo
|
|
717
|
+
- Bulk export
|
|
718
|
+
|
|
719
|
+
- [ ] **Configuration**
|
|
720
|
+
- Timeout configurabile
|
|
721
|
+
- Character limit configurabile
|
|
722
|
+
- Locale configurabile
|
|
723
|
+
|
|
724
|
+
#### Low Priority
|
|
725
|
+
|
|
726
|
+
- [ ] **Write Operations** (se richiesto)
|
|
727
|
+
- Create/update dataset
|
|
728
|
+
- Upload risorse
|
|
729
|
+
- Requires authentication
|
|
730
|
+
|
|
731
|
+
- [ ] **Advanced Filtering**
|
|
732
|
+
- Spatial filters (geo queries)
|
|
733
|
+
- Temporal filters (date ranges)
|
|
734
|
+
|
|
735
|
+
- [ ] **Export Formats**
|
|
736
|
+
- CSV export
|
|
737
|
+
- Excel export
|
|
738
|
+
- Graph visualization data
|
|
739
|
+
|
|
740
|
+
### 10.2 Distribution & Installation
|
|
741
|
+
|
|
742
|
+
- [ ] **npm Package Publication**
|
|
743
|
+
- Pubblicare su npm registry (come PyPI per Python)
|
|
744
|
+
- Installazione globale: `npm install -g ckan-mcp-server`
|
|
745
|
+
- Installazione locale: `npm install ckan-mcp-server`
|
|
746
|
+
- Versioning semantico (semver)
|
|
747
|
+
- Changelog automatico
|
|
748
|
+
- Pre-built binaries per evitare build locale
|
|
749
|
+
|
|
750
|
+
- [ ] **Executable CLI**
|
|
751
|
+
- Comando globale: `ckan-mcp-server`
|
|
752
|
+
- npx support: `npx ckan-mcp-server`
|
|
753
|
+
- Configurazione via flags o file
|
|
754
|
+
|
|
755
|
+
- [ ] **Distribution Channels**
|
|
756
|
+
- npm registry (principale)
|
|
757
|
+
- GitHub Releases con assets
|
|
758
|
+
- Docker image (opzionale)
|
|
759
|
+
|
|
760
|
+
### 10.3 Testing & Quality
|
|
761
|
+
|
|
762
|
+
- [ ] Unit tests per utility functions
|
|
763
|
+
- [ ] Integration tests per tool handlers
|
|
764
|
+
- [ ] E2E tests con server CKAN demo
|
|
765
|
+
- [ ] Performance benchmarks
|
|
766
|
+
- [ ] Error scenario coverage
|
|
767
|
+
|
|
768
|
+
### 10.4 Documentation
|
|
769
|
+
|
|
770
|
+
- [ ] OpenAPI/Swagger spec per HTTP mode
|
|
771
|
+
- [ ] Video tutorial
|
|
772
|
+
- [ ] More real-world examples
|
|
773
|
+
- [ ] Best practices guide
|
|
774
|
+
|
|
775
|
+
---
|
|
776
|
+
|
|
777
|
+
## 11. Success Metrics
|
|
778
|
+
|
|
779
|
+
### 11.1 Technical Metrics
|
|
780
|
+
|
|
781
|
+
- **Build Time**: < 5ms (esbuild)
|
|
782
|
+
- **Bundle Size**: < 500KB
|
|
783
|
+
- **Memory Usage**: < 100MB runtime
|
|
784
|
+
- **Response Time**: < 30s (CKAN API timeout)
|
|
785
|
+
|
|
786
|
+
### 11.2 Distribution Metrics
|
|
787
|
+
|
|
788
|
+
- **npm Downloads**: Weekly/monthly downloads from npm registry
|
|
789
|
+
- **Installation Success Rate**: % of successful installations
|
|
790
|
+
- **Installation Time**: < 2 minutes from `npm install` to running
|
|
791
|
+
- **Global vs Local**: Ratio di installazioni globali vs locali
|
|
792
|
+
- **npx Usage**: Utilizzo via npx senza installazione
|
|
793
|
+
|
|
794
|
+
### 11.3 Usage Metrics
|
|
795
|
+
|
|
796
|
+
- Number of MCP tool calls per session
|
|
797
|
+
- Most used tools
|
|
798
|
+
- Average results per search
|
|
799
|
+
- Error rate by tool
|
|
800
|
+
- Server coverage (unique CKAN servers used)
|
|
801
|
+
|
|
802
|
+
### 11.4 Quality Metrics
|
|
803
|
+
|
|
804
|
+
- Zero known security vulnerabilities
|
|
805
|
+
- Error messages clarity
|
|
806
|
+
- Documentation completeness
|
|
807
|
+
- User satisfaction (GitHub issues/feedback)
|
|
808
|
+
|
|
809
|
+
---
|
|
810
|
+
|
|
811
|
+
## 12. References
|
|
812
|
+
|
|
813
|
+
### 12.1 Documentation
|
|
814
|
+
|
|
815
|
+
- [CKAN API Documentation](https://docs.ckan.org/en/latest/api/)
|
|
816
|
+
- [MCP Protocol Specification](https://modelcontextprotocol.io/)
|
|
817
|
+
- [Apache Solr Query Syntax](https://solr.apache.org/guide/solr/latest/query-guide/standard-query-parser.html)
|
|
818
|
+
|
|
819
|
+
### 12.2 Related Resources
|
|
820
|
+
|
|
821
|
+
- [CKAN Official Site](https://ckan.org/)
|
|
822
|
+
- [onData Community](https://www.ondata.it/)
|
|
823
|
+
- [dati.gov.it](https://www.dati.gov.it/opendata/)
|
|
824
|
+
|
|
825
|
+
### 12.3 Code Repository
|
|
826
|
+
|
|
827
|
+
- **GitHub**: https://github.com/ondata/ckan-mcp-server (presumed)
|
|
828
|
+
- **License**: MIT License
|
|
829
|
+
- **Contact**: onData community
|
|
830
|
+
|
|
831
|
+
---
|
|
832
|
+
|
|
833
|
+
## 13. Appendix
|
|
834
|
+
|
|
835
|
+
### 13.1 Glossary
|
|
836
|
+
|
|
837
|
+
- **CKAN**: Comprehensive Knowledge Archive Network - piattaforma open source per portali dati aperti
|
|
838
|
+
- **MCP**: Model Context Protocol - protocollo per integrare AI agent con strumenti esterni
|
|
839
|
+
- **Solr**: Apache Solr - motore di ricerca full-text usato da CKAN
|
|
840
|
+
- **DataStore**: Feature CKAN per query SQL-like su dati tabulari
|
|
841
|
+
- **Faceting**: Aggregazioni statistiche per analisi distributiva
|
|
842
|
+
- **Package**: Termine CKAN per "dataset"
|
|
843
|
+
- **Resource**: File o API endpoint associato a un dataset
|
|
844
|
+
|
|
845
|
+
### 13.2 Solr Query Syntax Quick Reference
|
|
846
|
+
|
|
847
|
+
```
|
|
848
|
+
# Full-text search
|
|
849
|
+
q: "popolazione"
|
|
850
|
+
|
|
851
|
+
# Field search
|
|
852
|
+
q: "title:covid"
|
|
853
|
+
q: "notes:sanità"
|
|
854
|
+
|
|
855
|
+
# Boolean operators
|
|
856
|
+
q: "popolazione AND sicilia"
|
|
857
|
+
q: "popolazione OR abitanti"
|
|
858
|
+
q: "popolazione NOT censimento"
|
|
859
|
+
|
|
860
|
+
# Wildcard
|
|
861
|
+
q: "popola*"
|
|
862
|
+
q: "*salute*"
|
|
863
|
+
|
|
864
|
+
# Filter query (no score impact)
|
|
865
|
+
fq: "organization:comune-palermo"
|
|
866
|
+
fq: "res_format:CSV"
|
|
867
|
+
|
|
868
|
+
# Date range
|
|
869
|
+
fq: "metadata_modified:[2023-01-01T00:00:00Z TO *]"
|
|
870
|
+
fq: "metadata_created:[NOW-7DAYS TO NOW]"
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
### 13.3 Response Format Examples
|
|
874
|
+
|
|
875
|
+
**Markdown Output** (human-readable):
|
|
876
|
+
```markdown
|
|
877
|
+
# CKAN Package Search Results
|
|
878
|
+
|
|
879
|
+
**Server**: https://www.dati.gov.it/opendata
|
|
880
|
+
**Query**: popolazione
|
|
881
|
+
**Total Results**: 1234
|
|
882
|
+
|
|
883
|
+
## Datasets
|
|
884
|
+
|
|
885
|
+
### Popolazione Residente 2023
|
|
886
|
+
- **ID**: `abc-123-def`
|
|
887
|
+
- **Organization**: ISTAT
|
|
888
|
+
- **Tags**: popolazione, demografia, censimento
|
|
889
|
+
...
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
**JSON Output** (machine-readable):
|
|
893
|
+
```json
|
|
894
|
+
{
|
|
895
|
+
"count": 1234,
|
|
896
|
+
"results": [
|
|
897
|
+
{
|
|
898
|
+
"id": "abc-123-def",
|
|
899
|
+
"name": "popolazione-residente-2023",
|
|
900
|
+
"title": "Popolazione Residente 2023",
|
|
901
|
+
"organization": { "name": "istat", "title": "ISTAT" }
|
|
902
|
+
}
|
|
903
|
+
]
|
|
904
|
+
}
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
---
|
|
908
|
+
|
|
909
|
+
**Document Version**: 1.0.0
|
|
910
|
+
**Created**: 2026-01-08
|
|
911
|
+
**Status**: Approved
|
|
912
|
+
**Next Review**: 2026-04-08
|