15 KiB
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:
- Archivio contesto — memoria persistente per progetti e task, isolata per utente
- 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:
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_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 registraupdated_bye una sessioneAAAAMMGG-HHMMSS-{utente}.md;revisionaumenta a ogni salvataggio (save_context if_revisionevita 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:
- Paperless → chunk → embedding (Ollama Pi-1) → upsert
kb_* - Gitea (repo configurati in
GITEA_INDEX_REPOS_*) → file.md/.py/.sh→ segreti mascherati (auth_pass,password=,token:, chiavi private) → chunk → upsertgitea_*. I file.env*(tranne.env.example), chiavi e certificati non vengono mai indicizzati. - Irrigazione + Turni → snapshot/storico → upsert
apps_shared_family(vedidocs/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
# 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)
PAPERLESS_API_TOKEN_*in.env— vedi PAPERLESS-TOKEN.md- Ollama su DS920 con
nomic-embed-text: riserva per le query ed embedding in extrema - Qdrant su DS920 (opzionale):
rete/compose/extrema-ds920/loogle-mcp-compose.yml - Cambio password per tutti gli utenti dal dashboard
MCP_JWT_SECRET— impostare valore robusto in produzione