# 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) │ └───────┬─────────────────────────────┬───────────────────────┘ │ │ ▼ ▼ ┌───────────────┐ ┌─────────────────────┐ │ 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` | | 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` | Embedding locale | `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` | | `THERMAL_TEMP_HARD_C` | Pausa embedding se CPU DS920 ≥ °C | `70` | | `THERMAL_TEMP_SOFT_C` | Rallenta embedding sopra °C | `58` | | `THERMAL_CPU_TARGET_PCT` | Cap load1 (nproc × %) | `40` | | `OLLAMA_NUM_THREAD` | Thread CPU per richiesta embed | `1` | | `OLLAMA_EMBED_DELAY_S` | Pausa tra embed (duty-cycle lento) | `10` | | `DS920_THERMAL_URL` | Probe temp/load DS920 | `http://192.168.128.100:9191/thermal` | --- ## 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 **solo** i propri progetti in `/mnt/ha-apps/mcp/context/{username}/` - 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 | | `create_project` | context:write | Nuovo progetto | | `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}/ meta.json # titolo, tag, date, stato, gitea_repo (opz.) context.md # memoria persistente sessions/ # snapshot conversazioni artifacts/ # file generati dagli agenti ``` --- ## 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 DS920) → upsert `kb_*` 2. Gitea (repo configurati in `GITEA_INDEX_REPOS_*`) → file `.md`/`.py`/`.sh` → chunk → upsert `gitea_*` 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` per embedding locali 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)