# CLAUDE.md — mcp-query **Versione:** v3 (in produzione, collegato a un progetto Claude.ai dedicato, confermato funzionante) **Ultimo aggiornamento:** 09/08/2026 Eredita la Costituzione principale (`Dropbox/adrian/CLAUDE.md`). Solo contesto locale qui sotto. ## Cos'è MCP per far interrogare a Claude.ai i dati di business ICR (Emesso, Anagrafica, Diritti, Boxoffice, Cast, IMDb...) con query SQL generate da Claude.ai stesso, non pre-scritte. Nato da una discussione con Mauro il 09/08/2026 (mentre era in vacanza a Campomarino) dopo aver scoperto che la modalità live di Claude.ai legge bene il mirror Drive "SecondBrain" — la domanda naturale successiva è stata "potrebbe anche interrogare i dati veri, non solo le note?". **Decisioni prese in discussione, prima di scrivere codice**: - **No Supabase** (scartato esplicitamente da Mauro — sarebbe stato un parziale ripristino del vecchio vault smantellato il 29/07, per uno scopo diverso ma comunque non voluto). - **Sì a un MCP custom**, ma **puramente meccanico** — stesso principio "agnostico" di `gate-ufficio` (`PYTHON/gate-ufficio/CLAUDE.md`): il server esegue la query, non la interpreta, non conosce lo schema/le regole di business. - **La conoscenza di dominio non va duplicata qui** — Claude.ai la legge da `archivio/Adrian/agenti/data-expert.md` (già nel mirror Drive "SecondBrain", stessa fonte usata dal subagente `data-expert`). Se quella conoscenza cambia, non serve toccare questo server. **Autenticazione OAuth recuperata da git** — non scritta da zero: `vault-secondbrain/server.py` (vecchio MCP per Elon/Claude.ai, 479 righe, cancellato il 29/07/2026 con lo smantellamento del vault) è stato recuperato dal commit `5b461f6~1` (`git show 5b461f6~1:vault-secondbrain/server.py`). La parte di autenticazione (`OAuthAuthorizationServerProvider`, gestione token/refresh, client trust su redirect `claude.ai`/`claude.com`) era generica, non legata al contenuto del vault — riusata quasi as-is, solo adattata alla nuova versione dell'SDK (`mcp` 2.0.0, `MCPServer` al posto di `FastMCP` — stessa API di auth, verificato compatibile). **Bug reale trovato al primo collegamento vero (09/08/2026) e risolto subito**: `transport_security` non è sparito dal nuovo SDK come pensavo inizialmente — si è solo spostato: non più parametro del costruttore `MCPServer`, ma di `streamable_http_app()` (il metodo che genera l'app ASGI). Senza specificarlo lì, il default rifiuta ogni Host header diverso da `127.0.0.1`/`localhost` — risultato reale: OAuth passato correttamente (Mauro ha autorizzato l'account), ma ogni chiamata successiva a `/mcp` falliva con `Invalid Host header: query.privcloud.dev` → `421 Misdirected Request`, log leggibile via `journalctl -u mcp-query`. Fix: passare `transport_security=TransportSecuritySettings(...)` direttamente a `prod_server.streamable_http_app(...)` invece che al costruttore. **Verificato funzionante**: Mauro collegato con successo pochi minuti dopo il fix ("Collegato!!!"). ## Stato: esposto pubblicamente, verificato end-to-end (09/08/2026) **Fatto**: - `server.py` — due tool: `list_tables()` (elenco tabelle/righe) e `query_business_data(sql, limit=1000)` (esegue SELECT/WITH read-only sui parquet). Sicurezza query identica a `readonly_actions.py` del Frank-relay (solo SELECT/WITH, blacklist keyword di scrittura, niente `;` interno). - Tabelle esposte come view DuckDB con nome semplice (es. `emesso`, non il path del parquet) — una per ogni file in `PYTHON/MyICR_Suite/local_db/parquet/` (14 oggi). - **Due modalità nello stesso `server.py`**: `python server.py` (default, stdio, no auth — usata da `test_client.py` per i test locali) e `python server.py --real` (HTTP + OAuth, bind `10.0.0.2:8765` — usata in produzione dal servizio systemd). - **Verificato end-to-end tre volte**: (1) chiamata diretta alle funzioni Python, (2) client MCP reale via stdio (`test_client.py`), (3) client HTTP reale via internet (`curl https://query.privcloud.dev/...`) — discovery OAuth risponde correttamente, `/mcp` senza token → `401`. Query di test (film Canale 5 prima serata 2026) restituisce lo stesso risultato esatto trovato la mattina da `data-expert`: 18 trasmissioni, 17 titoli. **Infrastruttura pubblica (09/08/2026)**: - **Servizio systemd** `mcp-query.service` (Nave) — `ExecStart=.../venv/bin/python3 server.py --real`, `Restart=on-failure`, abilitato al boot. Stesso schema di `vault-mcp.service` (ormai rimosso). - **Firewall (ufw, Nave)**: `allow from 10.0.0.1 to any port 8765 proto tcp` — stesso pattern già in uso per voice-chat (porta 5060). Necessario perché Docker bypassa ufw ma un processo Python nudo no. - **Caddy (Avamposto, `/opt/caddy/Caddyfile`)**: blocco `query.privcloud.dev { reverse_proxy 10.0.0.2:8765 }`, aggiunto e verificato **dentro** il container (`docker exec caddy cat ...`), non solo fidandosi dell'esito di `reload` — stesso gotcha già noto su questo Caddy (bind mount su file singolo). - **DNS**: nessuna configurazione nuova — `*.privcloud.dev` è già wildcard, `query.privcloud.dev` risolveva già all'IP di Avamposto prima ancora di creare il blocco Caddy. - **Credenziali OAuth**: `.env` locale (`MCP_QUERY_OAUTH_CLIENT_ID`/`_SECRET`, generate con `secrets.token_urlsafe`), mai in git. `oauth_tokens.json` (persistenza token, locale al progetto) anch'esso gitignored — prima viveva in `adrian-ops/`, cartella non più esistente. **Registrato, collegato e VALIDATO (09/08/2026)**: Mauro ha aggiunto il connettore nelle impostazioni Claude.ai (nome + URL `https://query.privcloud.dev/mcp`), autorizzato via OAuth, collegamento riuscito dopo il fix `transport_security` sopra. Creato anche un **progetto Claude.ai dedicato** (prompt scritto insieme, vedi sotto) invece di usarlo come connettore libero — trasforma l'accesso ai dati in un agente specializzato con ambito ristretto. Prima domanda di business reale posta dalla modalità live: **"funziona benissimo!!!"** (Mauro) — ciclo completo confermato: Claude.ai legge `data-expert.md` da Drive, scrive la query giusta, la esegue via `mcp-query`, risponde correttamente. ### Prompt del progetto Claude.ai dedicato Non un connettore generico aggiunto al progetto "SecondBrain" esistente — un progetto a sé, con ambito ristretto (solo query di business ICR, non conoscenza personale su Mauro). Punti chiave del prompt: identità/missione ristretta; i due strumenti MCP (`list_tables`, `query_business_data`) descritti come puramente meccanici; istruzione esplicita di leggere `Adrian/agenti/data-expert.md` via Drive **prima** di ogni query non banale (stessa fonte di verità usata dal subagente `data-expert`, nessuna duplicazione di schema/regole nel prompt); disciplina sui tag `[CONFERMATO]`/`[IPOTESI]` di quel file (dichiarare quando la risposta si appoggia su un'ipotesi non validata); convenzioni di presentazione (sintesi prima del dettaglio, distinguere conteggi da elementi distinti); redirect esplicito verso il progetto "SecondBrain" per domande fuori ambito. Testo completo nella conversazione del 09/08/2026 (non duplicato qui — se va aggiornato, editarlo direttamente nel progetto Claude.ai). **(12/08/2026) La parte "cosa leggere e in che ordine" ora vive in `archivio/_bootstrap_query.md`** (mirror Drive), non più solo nel prompt — vedi convenzione multi-progetto in `archivio/Adrian/progetti/project_secondbrain_gdrive.md`. La parte identità/missione/tool resta nel prompt del progetto claude.ai, non duplicata qui. **NON fatto, deliberatamente non ancora**: - **Domanda strategica non risolta** (vedi `memory/MEMORY.md`): questo MCP dovrebbe restare sola-lettura (basso rischio) o allargarsi anche a scrittura/esecuzione (es. lanciare job `gate-ufficio` dalla voce)? Se si allarga, converrebbe estendere il dispatch già esistente di `gate-ufficio` invece di duplicare infrastruttura — non deciso. ## File - `server.py` — il server MCP (due modalità: stdio per test, `--real` per produzione HTTP+auth). - `test_client.py` — client di test locale via stdio (non tocca auth/rete). - `.env` — credenziali OAuth (gitignored, mai committato). - `oauth_tokens.json` — persistenza token, creato a runtime (gitignored). - `requirements.txt` — `mcp`, `duckdb`, `python-dotenv`. ## Come testarlo / gestirlo ``` # Setup python3 -m venv venv venv/bin/pip install -r requirements.txt # Test locale (stdio, no rete, no auth) venv/bin/python test_client.py # Gestione servizio reale (Nave) sudo systemctl status/restart/stop mcp-query # Verifica pubblica curl https://query.privcloud.dev/.well-known/oauth-authorization-server ``` ## Prossimo passo Mauro registra il connettore su Claude.ai (`https://query.privcloud.dev`) e prova una domanda reale dalla modalità live. Poi, con calma: richiudere il gap `transport_security`, e decidere se allargare l'ambito oltre la sola lettura.