Files
loogle-scripts/services/loogle-mcp/docs/ARCHITETTURA.md
T

323 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Loogle MCP Hub — Architettura e parametri
Documento di riferimento per la piattaforma MCP domestica LOOGLE.IT.
Ultimo aggiornamento: 2026-08-22
---
## Scopo
**Loogle MCP Hub** espone agli assistenti AI (Claude, ChatGPT, Gemini, Cursor) due capacità principali:
1. **Archivio contesto** — memoria persistente per progetti e task, isolata per utente
2. **Knowledge base RAG** — ricerca semantica su documenti Paperless e sui contesti salvati
Tutti i dati restano in LAN/NAS; l’accesso esterno avviene solo via HTTPS + OAuth.
---
## Architettura logica
```
┌─────────────────────────────────────────────────────────────┐
│ Client AI (ChatGPT / Claude / Gemini / Cursor) │
└───────────────────────────┬─────────────────────────────────┘
│ HTTPS + OAuth Bearer
▼
┌─────────────────────────────────────────────────────────────┐
│ NPM VIP 192.168.128.85 → mcp.loogle.it:443 │
└───────────────────────────┬─────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Pi-1 :8700 loogle-mcp (FastAPI) │
│ ├── /mcp JSON-RPC MCP │
│ ├── /oauth/* OAuth 2.1 PKCE │
│ └── /dashboard UI famiglia │
│ Pi-1 :8700 loogle-mcp-indexer (worker Paperless→vector) │
│ Pi-1 loogle-mcp-ollama (embedding query + indexer) │
└───────┬─────────────────────────────┬───────────────────────┘
│ │
▼ ▼
┌───────────────┐ ┌─────────────────────┐
│ SQLite │ │ Vector store │
│ /data/*.db │ │ Qdrant DS920 :6333 │
│ utenti OAuth │ │ o fallback SQLite │
└───────────────┘ └─────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ NFS /mnt/ha-apps/mcp/context/{utente}/projects/... │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Paperless docs.loogle.it (sorgente documenti + OCR) │
└─────────────────────────────────────────────────────────────┘
```
---
## Componenti e percorsi
| Componente | Host | Porta | Percorso / container |
|------------|------|-------|----------------------|
| MCP Gateway | Pi-1 | 8700 | `loogle-mcp` |
| Indexer | Pi-1 | — | `loogle-mcp-indexer` |
| Embedding | Pi-1 | 11434 (solo rete compose) | `loogle-mcp-ollama`, volume `loogle-mcp_ollama-models` |
| Ollama di riserva | DS920 | 11434 | solo query se l'Ollama di Pi-1 non risponde; embedding per extrema |
| Qdrant (primario) | DS920 | 6333 | opzionale, compose extrema |
| Vector fallback | Pi-1 | — | `/data/vector_fallback.db` |
| Context NFS | NAS | — | `/mnt/ha-apps/mcp/context/` |
| DB SQLite | Pi-1 | — | `/home/daniely/docker/loogle-mcp/data/loogle_mcp.db` |
| Compose locale | Pi-1 | — | `/home/daniely/docker/loogle-mcp/` |
| Compose failover | Pi-1/Pi-2 | — | `/home/daniely/rete/compose/failover/loogle-mcp-compose.yml` |
| Runbook | — | — | `/home/daniely/rete/ha/RUNBOOK-mcp.md` |
---
## URL e endpoint
| Servizio | URL |
|----------|-----|
| MCP (client AI) | `https://mcp.loogle.it/mcp` |
| Dashboard web | `https://mcp.loogle.it/dashboard` |
| Health | `https://mcp.loogle.it/health` |
| OAuth authorize | `https://mcp.loogle.it/oauth/authorize` |
| OAuth token | `https://mcp.loogle.it/oauth/token` |
| Metadata OAuth | `https://mcp.loogle.it/.well-known/oauth-authorization-server` |
| Metadata risorsa MCP | `https://mcp.loogle.it/.well-known/oauth-protected-resource` |
---
## DNS
| Record | Tipo | Valore | Dove configurato |
|--------|------|--------|------------------|
| `mcp.loogle.it` | A | `192.168.128.85` (VIP NPM) | Pi-hole `/etc/pihole/pihole.toml` → hosts |
| Risoluzione LAN | — | Pi-hole → Unbound → split-horizon | Come `docs.loogle.it`, `pass.loogle.it` |
**Script setup:** `/home/daniely/rete/scripts/setup-mcp-dns-npm.sh`
**Verifica:**
```bash
dig +short mcp.loogle.it @127.0.0.1 # → 192.168.128.85
curl -sf https://mcp.loogle.it/health
```
---
## NPM (reverse proxy)
| Parametro | Valore |
|-----------|--------|
| Proxy host ID | 30 |
| Dominio | `mcp.loogle.it` |
| Upstream normale | `192.168.128.80:8700` |
| Certificato TLS | `npm-65` (Let's Encrypt) |
| Failover route | `rete/ha/npm-routes.conf` riga `30\|mcp.loogle.it\|8700\|...` |
| Websocket | ON |
| Force SSL | ON |
**Failover:** `npm-failover-route.sh` aggiorna upstream su Pi-2 / DS920 in base allo scenario HA.
---
## Variabili d'ambiente (`.env`)
File: `/home/daniely/docker/loogle-mcp/.env`
| Variabile | Descrizione | Esempio |
|-----------|-------------|---------|
| `MCP_BASE_URL` | URL pubblico (issuer JWT) | `https://mcp.loogle.it` |
| `MCP_JWT_SECRET` | Segreto firma token OAuth | *(stringa lunga casuale)* |
| `MCP_OAUTH_CLIENT_ID` | Client OAuth pre-registrato | `loogle-mcp-public` |
| `PAPERLESS_URL` | API documenti | `https://docs.loogle.it` |
| `PAPERLESS_API_TOKEN` | Token API admin (fallback) | vedi [PAPERLESS-TOKEN.md](PAPERLESS-TOKEN.md) |
| `PAPERLESS_API_TOKEN_{USER}` | Token API per utente | `DANIELE`, `LUCIA`, `DAVIDE`, `LUCA` |
| `QDRANT_URL` | Vector DB remoto | `http://192.168.128.100:6333` |
| `OLLAMA_URL` | Ollama DS920: riserva per le query, embedding in extrema | `http://192.168.128.100:11434` |
| `OLLAMA_EMBED_MODEL` | Modello embedding | `nomic-embed-text` |
| `OPENAI_API_KEY` | Fallback embedding cloud | *(opzionale)* |
| `INDEXER_INTERVAL_MINUTES` | Intervallo sync Paperless | `30` |
| `MCP_TOOL_TIMEOUT_S` | Tempo massimo di una chiamata MCP | `45` |
| `MCP_THREAD_LIMIT` | Thread per i tool (eseguiti in parallelo) | `40` |
| `QDRANT_RETRY_S` | Riprova Qdrant ogni N s mentre usa il fallback SQLite | `60` |
| `OLLAMA_QUERY_TIMEOUT_S` | Timeout embedding query sull'Ollama DS920 | `20` |
Impostate in `docker-compose.yml` (sovrascrivono `.env`) per usare l'Ollama di Pi-1:
| Variabile | Valore | Note |
|-----------|--------|------|
| `OLLAMA_QUERY_URL` / `OLLAMA_INDEX_URL` | `http://ollama:11434` | query (~0,1 s) e indicizzazione (~0,9 s/chunk) |
| `OLLAMA_QUERY_NUM_THREAD` | `2` | uguale a `cpus: 2` del container; stesse options per query e indexer, altrimenti Ollama ricarica il modello |
| `THERMAL_SOURCE` | `local` | thermal gate su temperatura e carico di Pi-1 (`ds920` = sonda `DS920_THERMAL_URL`) |
| `THERMAL_TEMP_SOFT_C` / `_HARD_C` | `70` / `78` | soglie Pi 5 |
| `OLLAMA_EMBED_DELAY_S` | `0.5` | pausa tra un chunk e l'altro |
---
## Utenti e isolamento
| Persona | Username | Ruolo | Password iniziale |
|---------|----------|-------|-------------------|
| Daniele | `daniele` | admin | `daniele` *(cambiarla)* |
| Lucia | `lucia` | user | `lucia` |
| Davide | `davide` | user | `davide` |
| Luca | `luca` | user | `luca` |
- Ogni utente vede i propri progetti in `/mnt/ha-apps/mcp/context/{username}/` più i
**contesti condivisi** con la famiglia in `/mnt/ha-apps/mcp/context/_shared/`
- Contesti condivisi: tutti li leggono e li aggiornano; archiviazione e ritorno a personale
solo per il proprietario (`owner`) o l'admin. Ogni salvataggio registra `updated_by` e una
sessione `AAAAMMGG-HHMMSS-{utente}.md`; `revision` aumenta a ogni salvataggio
(`save_context if_revision` evita di sovrascrivere modifiche concorrenti)
- I token OAuth JWT contengono `sub` = username; ogni tool filtra per utente
- L’admin può `reindex_document`, vedere audit completo, revocare refresh token
**Scope OAuth:** `context:read`, `context:write`, `knowledge:read`, `knowledge:write`, `gitea:read`, `gitea:write`, `home:read`, `irrigation:read`, `turni:read`, `admin` (solo daniele)
---
## Tool MCP esposti
| Tool | Scope minimo | Funzione |
|------|--------------|----------|
| `ping` | — | Test connettività |
| `whoami` | — | Utente e scope attivi |
| `list_projects` | context:read | Elenco progetti personali + condivisi (`shared`) |
| `create_project` | context:write | Nuovo progetto (`shared=true`: condiviso con la famiglia) |
| `share_project` | context:write | Condivide un proprio progetto / lo riporta personale |
| `get_project_context` | context:read | Legge memoria progetto |
| `save_context` | context:write | Salva/appende memoria |
| `archive_project` | context:write | Archivia progetto |
| `search_context` | context:read | Ricerca semantica contesti |
| `search_knowledge` | knowledge:read | RAG Paperless + contesti + Gitea + app |
| `search_gitea_knowledge` | knowledge:read | RAG solo Gitea |
| `search_apps_knowledge` | knowledge:read | RAG Irrigazione + Turni |
| `list_apps_indexed` | knowledge:read | Stato indicizzazione app |
| `reindex_apps` | admin | Re-sync RAG app |
| `get_document` | knowledge:read | Testo documento Paperless |
| `list_recent_documents` | knowledge:read | Ultimi doc indicizzati |
| `reindex_document` | admin | Re-indicizza documento |
| `get_home_dashboard` | home:read | Dashboard Loogle Casa |
| `get_home_weather` | home:read | Meteo casa |
| `get_network_overview` | home:read | Panoramica rete LAN |
| `get_network_failover_status` | home:read | Stato failover cluster |
| `get_ha_entity` | home:read | Entità Home Assistant |
| `list_ha_entities` | home:read | Elenco entità HA |
| `search_ha_entities` | home:read | Cerca entità HA |
| `get_irrigation_status` | irrigation:read | Stato irrigazione |
| `get_irrigation_zones` | irrigation:read | Zone irrigazione |
| `get_irrigation_history` | irrigation:read | Storico irrigazioni |
| `get_turni_status` | — | Stato servizio Turni |
| `get_my_shifts` | turni:read | Turni utente corrente |
| `list_turni_doctors` | turni:read | Medici Turni-Live |
Vedi anche tool Gitea in `docs/GITEA-TOKEN.md` e app homelab in `docs/APPS-INTEGRATION.md`.
---
## Storage contesto (per progetto)
```
/mnt/ha-apps/mcp/context/{username}/projects/{project-id}/ # personali
/mnt/ha-apps/mcp/context/_shared/projects/{project-id}/ # condivisi con la famiglia
meta.json # titolo, tag, date, stato, revision, gitea_repo (opz.)
# condivisi: shared, shared_with=family, owner, updated_by
context.md # memoria persistente
sessions/ # snapshot conversazioni (condivisi: suffisso -{utente})
artifacts/ # file generati dagli agenti
```
Gli ID dei condivisi sono univoci anche rispetto ai progetti personali di tutti gli utenti.
Nel vector store ogni progetto è indicizzato con il `context.md` **completo** (sostituito a
ogni salvataggio): `ctx_{utente}` per i personali, `ctx_shared_family` per i condivisi.
---
## Vector store / RAG
| Collection Qdrant | Contenuto |
|-------------------|-----------|
| `kb_shared_family` | Documenti famiglia (tag non personali) |
| `kb_personal_{user}` | Documenti personali |
| `ctx_{user}` | Embedding dei contesti agente |
| `gitea_shared_family` | File testo da repo Gitea non privati |
| `gitea_personal_{user}` | File testo da repo Gitea privati dell'owner |
| `apps_shared_family` | Export Irrigazione + Turni (P7) |
**Nota Pi 5:** Qdrant locale su ARM (page size 16K) non è supportato. Default: Qdrant su DS920 o fallback SQLite in `/data/vector_fallback.db`.
**Pipeline indexer:** ogni 30 min:
1. Paperless → chunk → embedding (Ollama Pi-1) → upsert `kb_*`
2. Gitea (repo configurati in `GITEA_INDEX_REPOS_*`) → file `.md`/`.py`/`.sh` → segreti mascherati
(`auth_pass`, `password=`, `token:`, chiavi private) → chunk → upsert `gitea_*`. I file
`.env*` (tranne `.env.example`), chiavi e certificati non vengono mai indicizzati.
3. Irrigazione + Turni → snapshot/storico → upsert `apps_shared_family` (vedi `docs/APPS-INTEGRATION.md`)
---
## Sicurezza
- TLS terminato su NPM (Let's Encrypt)
- OAuth 2.1 Authorization Code + PKCE
- JWT access token 1h + refresh token 30 giorni (revocabili)
- Audit log: `{timestamp, user, tool, resource_id}` in SQLite
- CrowdSec attivo su NPM
- Nessun accesso cross-user ai dati
---
## Operazioni comuni
```bash
# Stato servizi
cd /home/daniely/docker/loogle-mcp && docker compose ps
# Log
docker logs -f loogle-mcp
docker logs -f loogle-mcp-indexer
# Redeploy
./scripts/deploy.sh
# DNS + NPM (se reinstall)
sudo /home/daniely/rete/scripts/setup-mcp-dns-npm.sh
# Failover route
/home/daniely/rete/scripts/npm-failover-route.sh normal
# Backup dati MCP
/home/daniely/rete/infra-monitor/mcp-restic-backup.sh
```
---
## Failover (tier-b)
| Scenario | Comportamento |
|----------|---------------|
| Pi-1 down, Pi-2 up | Stack MCP su Pi-2, NPM upstream → `.81:8700` |
| Extrema DS920 | NPM3 + upstream DS920 |
| Monitoraggio | Probe `mcp_ok` in `failover-status.sh` |
---
## Da completare (post-install)
1. **`PAPERLESS_API_TOKEN_*`** in `.env` — vedi [PAPERLESS-TOKEN.md](PAPERLESS-TOKEN.md)
2. **Ollama su DS920** con `nomic-embed-text`: riserva per le query ed embedding in extrema
3. **Qdrant su DS920** (opzionale): `rete/compose/extrema-ds920/loogle-mcp-compose.yml`
4. **Cambio password** per tutti gli utenti dal dashboard
5. **`MCP_JWT_SECRET`** — impostare valore robusto in produzione
---
## Riferimenti
- [Guida utenti famiglia](docs/GUIDA-UTENTI.md)
- [Onboarding tecnico client AI](docs/ONBOARDING.md)
- [Runbook operativo](/home/daniely/rete/ha/RUNBOOK-mcp.md)
- [README progetto](README.md)