# Datalake-MCP
**Server MCP** (Model Context Protocol) che espone un Azure Data Lake Storage Gen2 a Claude,
consentendo di navigare i file, leggerne il contenuto in formato CSV, Parquet, testo (.txt),
markdown (.md) o XML, e di salvare file (testo, markdown, CSV, Parquet, PDF) con un indice ricercabile.
[](https://mcpservers.org/servers/alex1976/datalake-mcp)
## Come funziona
### Architettura
```mermaid
flowchart LR
U([Utente]) <--> C["Claude
(Desktop / Code)"]
C <-->|"MCP via stdio"| T
subgraph S["Datalake.Mcp.Server (.NET 9)"]
direction TB
T["Tools/DataLakeTools
tool MCP"]
subgraph R["Lettura (streaming)"]
direction LR
CSV[CsvReaderService]
PQ[ParquetReaderService]
TXT[TextReaderService]
XML[XmlReaderService]
F[RowFilter]
end
subgraph W["Scrittura"]
direction LR
FW[FileWriterService]
IDX[FileIndex]
end
DL[DataLakeService]
T --> R
T --> W
R --> DL
W --> DL
R -.-> F
end
DL <-->|"Azure.Storage.Files.DataLake
AccountName + AccountKey"| ADLS[("Azure Data Lake
Storage Gen2")]
IDX -.->|"saved-files-index.csv
(ETag)"| ADLS
```
### Flusso di lettura e interrogazione
```mermaid
sequenceDiagram
actor U as Utente
participant C as Claude
participant M as Server MCP
participant L as Data Lake
U->>C: "Mostrami gli ordini APERTI > 1000"
C->>M: list_filesystems / list_directory
M->>L: navigazione
L-->>C: filesystem, cartelle, file
C->>M: get_parquet_schema (colonne e tipi)
C->>M: read_parquet(filter, columns, maxRows, offset)
M->>L: OpenReadAsync (download lazy)
loop un row-group / riga alla volta
L-->>M: byte necessari
M->>M: filtro → offset → maxRows
end
M-->>C: righe + truncated / nextOffset
C-->>U: risposta in linguaggio naturale
Note over C,M: se truncated=true si richiama con offset=nextOffset
```
### Flusso di salvataggio e ricerca
```mermaid
sequenceDiagram
actor U as Utente
participant C as Claude
participant M as Server MCP
participant L as Data Lake
U->>C: "Salva il report in report/2026"
C->>M: save_csv / save_parquet / save_pdf / ...
M->>M: validazione formato ed estensione
M->>L: upload condizionale (If-None-Match: *)
alt esiste già e overwrite=false
L-->>M: errore, nessuna sovrascrittura
else
L-->>M: file salvato
loop fino a 5 tentativi
M->>L: leggi indice (ETag)
M->>L: scrivi indice (If-Match: ETag)
end
end
M-->>C: esito
U->>C: "Trova il report"
C->>M: search_file(nomeFile, tipoFile)
M->>L: legge l'indice
M-->>C: voci trovate (+ contenuto se un solo file)
```
## Struttura della soluzione
```
Datalake-MCP.sln
src/Datalake.Mcp.Server/ Server MCP (host .NET Generic Host, trasporto stdio)
Configuration/DataLakeOptions.cs Opzioni di connessione (AccountName/AccountKey/DefaultFileSystem)
Services/DataLakeService.cs Wrapper su Azure.Storage.Files.DataLake (navigazione, streaming)
Services/CsvReaderService.cs Lettura CSV con limite di righe (streaming)
Services/ParquetReaderService.cs Lettura Parquet a row-group, con proiezione colonne
Services/TextReaderService.cs Lettura file di testo/markdown riga per riga (streaming)
Services/XmlReaderService.cs Lettura XML tabellare elemento per elemento, con proiezione colonne
Services/FileWriterService.cs Costruzione/validazione dei byte da salvare (testo, CSV, Parquet, PDF)
Services/FileIndex.cs Indice CSV dei file salvati (upsert, parsing, ricerca)
Tools/DataLakeTools.cs Tool MCP esposti al client (list_filesystems, read_csv, ...)
tests/Datalake.Mcp.Server.Tests/ Test xUnit per reader CSV/Parquet/testo/XML, writer e indice
```
## Tool MCP esposti
| Tool | Descrizione |
|---|---|
| `list_filesystems` | Elenca i filesystem (container) dell'account |
| `list_directory` | Elenca file e directory in un percorso (opzionalmente ricorsivo) |
| `get_file_info` | Metadati di un file (dimensione, data ultima modifica) |
| `read_csv` | Legge un CSV, con limite `maxRows`, delimitatore configurabile e `offset` per la paginazione; filtro opzionale `filter` |
| `get_parquet_schema` | Restituisce lo schema (colonne/tipi) di un Parquet senza leggerne i dati |
| `read_parquet` | Legge un Parquet, con `maxRows`, proiezione opzionale delle colonne e `offset` per la paginazione; filtro opzionale `filter` |
| `read_text` | Legge un file di testo (.txt), con `maxLines` e `offset` per la paginazione; filtro opzionale `contains` |
| `read_markdown` | Legge un file markdown (.md), con `maxLines` e `offset` per la paginazione; filtro opzionale `contains` |
| `get_xml_schema` | Restituisce lo schema (colonne, tra attributi ed elementi) dell'elemento record di un XML tabellare senza leggerne i dati |
| `read_xml` | Legge un XML tabellare (radice con elementi record ripetuti), con `maxRows`, elemento record e proiezione colonne opzionali, e `offset` per la paginazione; filtro opzionale `filter` |
| `create_folder` | Crea una cartella nella radice del filesystem o come sottocartella di una cartella esistente (il padre deve esistere; errore se la cartella esiste già) |
| `save_text` | Salva un file `.txt` in una cartella/sottocartella del datalake |
| `save_markdown` | Salva un file `.md` |
| `save_csv` | Salva un file `.csv` da testo CSV (validato: stesso numero di campi per riga) |
| `save_parquet` | Salva un file `.parquet` da un elenco di righe (oggetti colonna → valore); i tipi sono dedotti (bool, intero, decimale, stringa) |
| `save_pdf` | Salva un file `.pdf` da contenuto base64 (verificata l'intestazione `%PDF-`) |
| `search_file` | Cerca nell'indice dei file salvati per `nomeFile` (contiene, case-insensitive) e/o `tipoFile` (csv, parquet, pdf, md, txt) e restituisce le voci trovate (`path` utilizzabile con i tool `read_*`). Se il risultato è un solo file, ne restituisce anche il contenuto (csv, parquet, txt, md; primi `maxRows` righe, poi si prosegue con `read_*` e `nextOffset`; per i pdf solo i metadati) |
Le letture sono progettate per non caricare interi file in memoria: CSV, testo/markdown e XML
vengono letti riga/elemento per riga/elemento fermandosi al limite richiesto, mentre Parquet viene
letto un row-group alla volta. I file remoti vengono aperti con streaming a caricamento differito
(`DataLakeFileClient.OpenReadAsync`), che scarica solo i byte effettivamente necessari. La lettura
XML disabilita il DTD processing e la risoluzione di entità esterne per evitare attacchi XXE.
### Filtri
`read_csv`, `read_parquet` e `read_xml` accettano un parametro opzionale `filter`: un elenco di
condizioni `{ column, operator, value }` combinate in AND, ad esempio:
```json
[{"column": "stato", "operator": "eq", "value": "APERTO"},
{"column": "importo", "operator": "gt", "value": "1000"}]
```
Operatori: `eq` (`=`), `ne` (`!=`), `gt` (`>`), `gte` (`>=`), `lt` (`<`), `lte` (`<=`), `contains`,
`startswith`, `endswith`, `isnull`, `isnotnull` (questi ultimi due non richiedono `value`; una stringa
vuota conta come null). I confronti sono numerici quando cella e valore sono numeri (formato
invariante, es. `12.5`), sui timestamp Parquet quando il valore è una data, altrimenti testuali
case-insensitive. Il filtro può riferirsi anche a colonne non proiettate; una colonna inesistente o un
operatore non valido producono un errore che elenca i valori ammessi.
Il filtro viene applicato durante la lettura in streaming, **prima** di `offset` e `maxRows`: la
paginazione (`truncated`/`nextOffset`) conta quindi solo le righe che soddisfano il filtro.
`read_text` e `read_markdown` accettano invece `contains`, che seleziona le righe contenenti il testo
indicato (case-insensitive).
### Paginazione
Le risposte di `read_csv`, `read_parquet`, `read_text`, `read_markdown` e `read_xml` sono soggette
al limite di dimensione dei tool call (circa 1 MB). Per file che eccedono `maxRows`/`maxLines` (o
il limite di dimensione), il risultato riporta
`truncated: true` e un `nextOffset`: richiamando nuovamente il tool con `offset = nextOffset` si
ottiene la pagina successiva, saltando le righe già restituite senza doverle rileggere per intero.
Quando non c'è altro da leggere, `truncated` è `false` e `nextOffset` è `null`.
### Salvataggio file e indice
I tool `save_*` accettano `path` (cartelle e sottocartelle vengono create se mancanti), `fileSystem`,
`description` e `overwrite`. Senza `overwrite=true` un file esistente non viene mai sostituito (upload
condizionale `If-None-Match: *`). L'estensione del percorso deve corrispondere al formato.
Ogni salvataggio registra il file nell'**indice** `DataLake:SavedFilesIndexPath` (default
`saved-files-index.csv`, nella radice del filesystem), un CSV leggibile con `read_csv` con colonne
`path,name,format,sizeBytes,savedAt,description`. Salvare di nuovo lo stesso percorso aggiorna la riga
esistente. L'indice è aggiornato con concurrency ottimistica (ETag, fino a 5 tentativi).
## Configurazione
Le credenziali dell'account (`accountName` e `accountKey`) non vanno mai committate. In sviluppo,
usa i .NET user-secrets dalla cartella `src/Datalake.Mcp.Server`:
```powershell
dotnet user-secrets init
dotnet user-secrets set "DataLake:AccountName" ""
dotnet user-secrets set "DataLake:AccountKey" ""
dotnet user-secrets set "DataLake:DefaultFileSystem" ""
```
In alternativa, tramite variabili d'ambiente (utile per il deploy):
```powershell
$env:DataLake__AccountName = ""
$env:DataLake__AccountKey = ""
$env:DataLake__DefaultFileSystem = ""
```
`appsettings.json` contiene solo i placeholder vuoti e può restare nel repository.
## Build, test ed esecuzione
```powershell
dotnet build
dotnet test
dotnet run --project src/Datalake.Mcp.Server
```
Il server comunica via stdio secondo il protocollo MCP: va quindi registrato come server MCP nel
client (es. Claude Desktop / Claude Code), puntando all'eseguibile del progetto
`Datalake.Mcp.Server`, non lanciato manualmente in un terminale interattivo.
## Registrazione in Claude Desktop e utilizzo dell'MCP
Un esempio di configurazione è disponibile in
[`claude_desktop_config.example.json`](claude_desktop_config.example.json). Per usarlo:
1. Esegui `dotnet build src/Datalake.Mcp.Server -c Release` per generare il DLL in `bin/Release/net9.0`. Oppure usa il contenuto della cartella se già compilato.
2. Copia il contenuto del file di esempio nel file di configurazione di Claude Desktop
(`%APPDATA%\Claude\claude_desktop_config.json`), unendolo alla chiave `mcpServers` se già presente.
3. Aggiorna il percorso in `args` se la repository si trova altrove, e valorizza `AccountName`/`AccountKey`/`DefaultFileSystem` in `env` con le credenziali reali (non committarle mai nel repository).
4. Riavvia Claude Desktop: il server "datalake" comparirà tra i tool disponibili.