Skip to main content
Glama
dataciviclab

dataciviclab

Official
by dataciviclab
README.md
# dcl-mcp — il MCP del DataCivicLab

Server MCP unico per partecipare all'ecosistema del Lab: contesto
org-wide, GitHub Discussions e query read-only sui dati pubblici.

```
pip install "lab-connectors[mcp,duckdb] @ git+https://github.com/dataciviclab/lab-connectors.git"
pip install "dcl-mcp @ git+https://github.com/dataciviclab/dcl-mcp.git"
```

## Cosa espone

Un solo server, nome `dataciviclab` — 11 tool in tre famiglie:

| Famiglia | Tool | Cosa fa |
|---|---|---|
| **Context** | `session_bootstrap` | Orientamento rapido (markdown) |
| | `lab_map(domain=)` | Albero domains → repos → sources; con `domain=` i `totals` sono quelli del dominio (dominio sconosciuto → elenco domini validi) |
| | `find(query, type=, domain=)` | Ricerca ranked cross-entity; `total_catalog` (match totali) + `returned` (quanti ne vedi) + `total_external` (+ `github_errors` se `include_github` fallisce). `type=` sconosciuto → `ok:false` coi tipi validi |
| | `explore(ref)` | Deep-dive di dataset/repo/domain/source/analysis — ref tipizzabili (`analysis:slug`, `repo:slug`, …) |
| | `workspace_triage(section=)` | Stato Lab: radar, PR, issues, discussions… |
| **Discussions** | `github_list_discussions(query=, category_name=)` | Elenca (cursor) o cerca full-text/categoria server-side; `repo_full_name` default hub |
| | `github_list_discussion_categories` | Categorie valide per la creazione (nome + slug) |
| | `github_get_discussion(include_comments=)` | Legge una discussion; con `include_comments=true` aggiunge commenti e reply in una sola chiamata |
| | `github_create_discussion` / `github_add_discussion_comment` | Partecipa (scrittura) |
| **GCS** | `gcs_query(slug, sql, layer=, limit=)` | SELECT/CTE read-only sui parquet pubblici |

### Flusso tipico (onboarding)

```
find("rifiuti urbani")            # scopri dataset/analisi/repo
  → explore("ispra_ru_base")      # card: description, anni, colonne,
                                  # clean_rows, marts, gcs_path, query_hint
  → gcs_query(slug=…, sql=…)      # SELECT o WITH…SELECT su `_data`,
                                  # layer=mart per le tabelle mart
  → find(…, include_github=true)  # issue/PR aperte + discussioni open del tema
  → github_get_discussion(number=…, include_comments=True)
  → github_list_discussion_categories → github_create_discussion
```

### Routing tra MCP del Lab

- **dcl-mcp** (questo) — org-wide: dove cerco, cosa è successo, come partecipo, dati pubblicati
- **toolkit** — repo locale: pipeline, contratti, SQL su raw/clean/mart del workspace
- **source-observatory** — scouting fonti e inventory
- **github** (server generico) — leggere/scrivere un issue o una PR
  specifica. `find(include_github=true)` qui è solo *scoperta* Lab-scoped:
  cerca issue/PR aperte i cui titoli contengono i token della query, non
  sostituisce il server generico.

## Configurazione

```json
{
  "mcpServers": {
    "dataciviclab": {
      "command": "dcl-mcp"
    }
  }
}
```

| Env var | Ruolo | Obbligatoria |
|---|---|---|
| `GITHUB_TOKEN` (o `GITHUB_PERSONAL_ACCESS_TOKEN` / `GH_TOKEN`) | Discussions e `find(include_github=true)` — GraphQL richiede auth anche in lettura. Unica regola di risoluzione, in ordine di fallback | sì, per discussions/write |
| `DCL_MCP_ENV_FILE` | Path a un file `.env` (fallback: `.env` cercato verso l'alto dal cwd) | no |
| `ACB_REPO` / `ACB_BRANCH` | Sorgente artefatti contesto (default: ACB, branch `context`) | no |

## Note di design

- **Context** legge da remoto gli artefatti prodotti dalla CI di
  [agent-context-builder](https://github.com/dataciviclab/agent-context-builder)
  sul branch `context` (raw.githubusercontent, cache TTL 120s). Nessuna
  checkout locale richiesta. `explore` sulle card dataset espone anche
  description, periodo (`year_min`/`year_max`), `clean_rows`, lista `marts`
  (da `mart_refs`), colonne compatte (name/type/role), `gcs_path` e
  `query_hint` pronto.
- **Una card = un layer**: `gcs_path` e `query_hint` puntano sempre al
  layer della card (clean per un dataset, mart per uno slug
  `dataset__table`). La card mart non eredita `clean_rows` né la lista
  `marts` del dataset padre; la lista `marts` sulla card dataset resta
  dato, non hint — per l'hint del mart si fa `explore(<mart_slug>)`.
  Il `name` di un mart viene dal registry del producer e, se il registry
  non lo committa, coincide con lo slug `dataset__table` (mai col nome del
  dataset padre, che rendeva indistinguibili i mart dello stesso dataset).
- **`clean_rows`** è il conteggio dell'ultimo snapshot clean pubblicato dal
  producer: **non è garantito** sia la serie completa. Per numeri citabili
  usa `query_hint` (`count(*)`) via `gcs_query`.
- **GCS** è read-only: solo bucket `dataciviclab-clean` e
  `dataciviclab-mart`, una sola istruzione per chiamata (SELECT o
  WITH…SELECT, niente DML/PRAGMA), limite 500 righe (clamp dichiarato).
  La query deve leggere la tabella `_data` (view sul parquet). La
  validazione gira sull'**AST** che DuckDB produce con
  `json_serialize_sql`: ogni sorgente di righe (`BASE_TABLE`) deve essere
  `_data` o una CTE della query — **incluse le sorgenti scritte come
  stringa** (`FROM '/tmp/x.csv'`, sintassi DuckDB valida che legge il
  file) — e nessuna funzione di lettura (`read_*`, `*_scan`, `glob`, …)
  può comparire, né in `FROM` né come scalare in `WHERE`. Le sole table
  function ammesse sono quelle in memoria (`unnest`, `range`,
  `generate_series`). È un guardrail, non un sandbox: non eseguire query
  di terzi non fidati.
  Per i mart usa `layer=mart` e lo slug `dataset__table` (in `marts`
  della card, o dal registry del repo produttore).
- **Ref tipizzati**: in caso di collisione di slug (es. repo e analysis
  con lo stesso nome) `explore("analysis:slug")` forza il tipo; senza
  prefisso l'ordine è dataset → repo → domain → source → analysis.
- **Rimozioni (v0.2)**: `github_get_discussion_summary` →
  `github_get_discussion(mode="summary")`;
  `github_get_discussion_comments` →
  `github_get_discussion(include_comments=True)`;
  `github_search_discussions` → `github_list_discussions(query=…)`.
  `refresh_context` è stato rimosso: il rebuild degli artefatti è
  un'azione operativa da CLI/CI di ACB, non da un MCP per esterni.
- **`find.total`** = `total_catalog` + `total_external` (quest'ultimo solo
  con `include_github=true`). `total_catalog` conta tutti i match nel
  catalogo; `returned` dice quanti ne stai vedendo (`limit`). Con
  `include_github=true` la risposta ha anche `github_ok` e, se qualcosa
  fallisce, `github_errors`: un token assente/scaduto non viene più
  confuso con "nessun risultato".
- **Envelope `ok`**: ogni risposta ha `ok` (anche gli errori, con
  `error`/`message`). I 5 tool Discussions hanno `repo_full_name` con
  default `dataciviclab/dataciviclab`.
- **Un solo trasporto HTTP**: context, `find(include_github=true)` e
  Discussions passano dallo stesso `HttpClient` di lab-connectors (retry,
  circuit breaker, un solo pool) e dalla stessa risoluzione del token.
  La ricerca discussioni org-wide usa la query GraphQL di
  `discussions.client` — non esiste una seconda copia in `context/`.
- **Cache**: schema dei parquet mart (`DESCRIBE`) cachato 10 min come il
  registry — `explore` non ripete il round-trip GCS.
- I nomi dei tool sono contratti cross-repo (skill, docs, config utente):
  non vanno cambiati senza migrazione.

## Modifiche (v0.4)

Cambi di contratto rispetto alla v0.3 — i **nomi dei tool** non cambiano:

- `gcs_query` e la card mart di `explore` espongono `gcs_path` (era
  `gs_path`): un solo nome per il path in tutto il server.
- Rimossi `marts_hint` (card dataset) e `marts_query_hint` (card mart):
  resta `query_hint`, sempre del layer della card.
- La card mart non eredita più `clean_rows`, `clean_rows_note` e `marts`
  dal dataset padre, e il `name` è quello del mart (non del padre).
- `find` espone `returned`; `total_catalog` è il totale dei match e non
  più `len(results)` (era limitato da `limit`).
- `find(type=…)` con un tipo sconosciuto risponde `ok:false` con
  `valid_types` (prima: zero risultati silenziosi).
- `lab_map(domain=…)` riporta `totals` del dominio, non globali.
- `gcs_query` valida la SQL sull'AST di DuckDB: sorgenti `FROM`/`JOIN`
  limitate a `_data`/CTE (anche quando scritte come stringa), funzioni di
  lettura rifiutate ovunque compaiano.
- Un solo client HTTP (lab-connectors) e una sola risoluzione del token
  per context, `find(include_github)` e Discussions. `httpx` e
  `python-dotenv` non sono più dipendenze.

## Sviluppo

```bash
pip install -e ".[dev]"
pytest tests/ -q -m "not smoke"
pytest tests/test_smoke.py -q -m smoke   # golden path, rete richiesta
ruff check src/ tests/
mypy src/dcl_mcp/
```

Storia: questo server nasce dalla migrazione di `dataciviclab-context`
(ACB) e `github-discussions` (lab-connectors) + il nuovo `gcs_query`,
modellato su tre passate di audit con agenti newcomer. Il builder degli
artefatti resta in ACB; le librerie condivise in
[lab-connectors](https://github.com/dataciviclab/lab-connectors).

## Licenza

MIT — vedi [LICENSE](LICENSE).

TDQS

A3.6/5.0

Scored across 11 tools

Disambiguation4/5

Most tools target clearly distinct actions: the four github_* discussion tools are cleanly separated, and gcs_query vs find vs explore have well-drawn boundaries (query data vs search catalog vs deep-dive a ref). The only soft spot is the trio session_bootstrap / workspace_triage / lab_map, which all return 'state of the Lab' in different formats and could be confused on first use, though the descriptions differentiate them (markdown overview vs machine-readable sections vs domain→repo→source map).

Naming Consistency3/5

The GitHub subset follows a clean, consistent github_verb_noun pattern (list/get/create/add). The rest mixes bare verbs (find, explore), noun phrases (session_bootstrap, workspace_triage, lab_map), and one verb_noun (gcs_query), so there is no single predictable pattern across the whole set. Names remain readable and snake_case is used uniformly, so it is mixed rather than chaotic.

Tool Count5/5

11 tools is well within a comfortable range and each one earns its place: orientation, catalog search/inspection, one read-only SQL endpoint, and a five-tool discussion workflow. Nothing looks redundant enough to be padding.

Completeness4/5

The discussion lifecycle is largely covered (list, categories, get with comments/replies, create, comment), and catalog coverage via find/explore/gcs_query is solid. Gaps remain: no update/delete of discussions and no way to reply to a specific comment even though nested replies are returned, and the surface is read-only for issues/PRs and raw/pipeline data (explicitly deferred to another toolkit).