# Audit 01 - Uso memoria MCP: contro-analisi e stato

**Documento verificato:** [`20260923_audit_01_memory_usage.md`](20260923_audit_01_memory_usage.md)
**Baseline Git:** `c6f09d9c253a12b710c4a61bd0dc519585dc98e3`
**Data verifica:** 2026-09-23, Windows 11 x64, Node 24.13

Il documento di audit non va trattato come autorevole: ogni affermazione e' stata verificata sul codice e, dove possibile, con misure. Questo file registra esiti, correzioni e stato di implementazione.

## 1. Affermazioni verificate

| # | Affermazione dell'audit | Esito | Evidenza |
|---|---|---|---|
| 2.1 | I server Sophia usano `StdioServerTransport`: un processo per connessione/sessione client | Confermata, con correzione | Sono **11** server, non 10: l'elenco dell'audit omette `docs-node`. Registry in `scripts/runtime/state-manager.js` |
| 2.2 | Playwright non avvia Chromium al boot | Confermata | `ensureBrowser()` / `ensureCdpBrowser()` sono on-demand |
| 2.3 | `import { chromium } from "playwright"` e' eager | Confermata e misurata | 5 run: **+50 MB RSS, +39 MB heap, ~400 ms** per processo idle |
| 2.4 | `suggestOnly` non governa il lifecycle | Confermata | `suggestOnly: true` su tutti i server; nessun campo di lifecycle |
| 2.5 | Selezioni per-MCP `<runtime>_mcp_<server>` gia' presenti | Confermata | `scripts/install-user-runtime.js`; su clean install tutte `checked` |
| 2.6 | Manifest `~/.mcp-servers/mcp-availability.json` | Confermata | `buildMcpAvailabilityManifest()` |
| 2.7 | Codex supporta `enabled = false` e override di progetto | **Confermata** su codex-cli 0.145.0 | `codex mcp list --json` con `CODEX_HOME` temporaneo: `enabled = false` rispettato; in un progetto *trusted* `.codex/config.toml` con `[mcp_servers.<id>] enabled = true` riattiva il server globale (merge a livello di campo); nei progetti non trusted la config di progetto viene ignorata |
| 2.8 | Cleanup Playwright asincrono anche su `exit`, con `process.exit(0)` | Confermata, ma **non era la causa principale degli orfani** | Vedi N1-N4 |

## 2. Finding nuovi o corretti

- **N1 - Orfani da EOF di stdin (causa principale, verificata).** Lo `StdioServerTransport` dell'SDK (1.26.0 in `playwright-node`) ascolta solo `data`/`error` su stdin: se il client termina o va in crash, `transport.onclose` non viene mai chiamato. Prova: dopo una qualsiasi tool call il timer di hibernation da 10 minuti (non `unref`) teneva vivo il processo, e con un browser lanciato l'handle Chromium lo teneva vivo fino alla hibernation. Sonda: `stdin.end()` dopo `browser_get_capabilities`, processo ancora vivo dopo 5 s. Gli altri 10 server, testati allo stesso modo, terminano correttamente all'EOF.
- **N2 - `transport.onclose` sovrascritto.** Era assegnato dopo `server.connect()`, sostituendo l'handler di chiusura del `Protocol` dell'SDK invece di concatenarsi.
- **N3 - Exit code mascherato.** `process.exit(0)` dentro l'handler `exit` forzava a 0 qualunque codice di uscita, anche dopo un errore fatale.
- **N4 - Timer di hibernation** non cancellato nel cleanup e non `unref()`.
- **N5 - Il manifest di availability tratta come `configured` le entry Codex con `enabled = false`.** Corretto: una definizione Codex disabilitata vale `unknown`, perche' il server puo' essere attivo per singolo progetto.
- **N6 - `inject-user-mcp-settings.js` considera `checked` una selezione assente** (`selections[...] || 'checked'`). Gestito: l'installer scrive sempre selezioni esplicite per ogni server.
- **N8 - `check-user-runtime` segnalava come errore ogni server assente dagli `mcp.json`.** Con il profilo lean produrrebbe falsi errori. Corretto: l'assenza di un server `project`/`manual` e' informativa (sezione "MCP Startup Profile"); resta errore solo l'assenza di un server `always`.
- **N9 - Installazioni senza `installation-state` ma con server gia' configurati.** Esistono (config scritte a mano o da versioni precedenti). Il default lean li considerava da non gestire, e i test end-to-end lo hanno rilevato. Regola adottata: un server gia' presente nella config del client resta `checked`; lean evita solo di *aggiungerne* di nuovi.
- **N10 - Il flag `enabled` entrava nell'hash degli snapshot.** La GUI avrebbe mostrato per sempre "Modificato" sui server disabilitati. Ora `enabled` e' escluso dall'hash: e' stato di ciclo di vita, non contenuto della definizione.
- **N7 - Moltiplicazione per thread in Codex.** Sulla macchina di verifica un solo processo `codex.exe` ospitava **100** processi MCP: 10 set completi, avviati a orari diversi nell'arco di 138 minuti. Il fattore di moltiplicazione e' quindi per thread o sessione dentro lo stesso host, non per istanza del client. Questo collega l'audit 01 all'audit 02: ogni thread aggiuntivo, subagent compresi se usano lo stesso meccanismo, puo' avviare un set completo di MCP. Il collegamento e' da verificare con lo script di audit durante uno spawn.

## 3. Baseline misurata (sanitizzata)

Strumento: `node scripts/runtime/mcp-process-audit.js`. Fotografia di un solo istante con client reali aperti: non e' ancora il benchmark 1/2/3/6 sessioni della Fase 6.

| Host | Istanze host | Processi MCP | RSS | Private WS |
|---|---:|---:|---:|---:|
| `codex.exe` (sotto ChatGPT desktop) | 1 | 100 | 4972 MB | 704 MB |
| `claude.exe` | 1 | 10 | 739 MB | 346 MB |
| **Totale** | | **110** | **5711 MB** | **1050 MB** |

- La media e' di circa **9,5 MB privati** e circa **52 MB RSS** per processo. L'RSS include pagine condivise (immagine Node, DLL), quindi sommarlo sovrastima il costo reale: il KPI da usare e' il private working set.
- `office-mcp-server` e' il server piu' pesante (circa 19,5 MB privati per processo, il doppio della media).
- `playwright-mcp-server` non era configurato su questa macchina, quindi il risparmio del lazy import (circa 50 MB RSS per processo) non compare in questa fotografia.
- Nessun sospetto orfano rilevato al momento della misura.

## 4. Stato di implementazione

| Fase | Stato | Note |
|---|---|---|
| 0 - Audit processi/memoria | **Fatto** (script + test) | Posizione `scripts/runtime/mcp-process-audit.js` invece di `scripts/audit-mcp-memory.mjs`: i file nuovi in `scripts/` root non sono coperti dal planner `test:affected --strict`. Benchmark 1/2/3/6 sessioni: vedi Fase 6 |
| 1 - `startupPolicy` nel registry | **Fatto** | Classificazione iniziale dell'audit applicata. Il manifest resta `version: 1` con campo additivo: i reader esistenti ignorano i campi extra, mentre un bump a v2 li avrebbe fatti degradare a `unknown`. Nessun cambio alle config generate |
| 2 - Profili lean/full/custom | **Fatto** | `--mcp-profile lean\|full\|custom`, `--optimize-mcp-startup`; regole in `scripts/runtime/mcp-startup-profile.js`. Clean install: default lean (non-`always` mai aggiunti). Installazione esistente: invariata senza flag esplicito. Lean *restringe soltanto*: non riattiva voci `unchecked`/`remove`. Il profilo e' registrato in `installation-state.json` (`mcpProfile`). GUI: badge `avvio: sempre/progetto/manuale`, stato del profilo, pulsante "Ottimizza avvio MCP" con conferma che elenca i server coinvolti; con un profilo esplicito la GUI non passa `--no-backup` |
| 3 - Traduzione per-client | **Fatto** per Codex e Claude Code; fallback conservativo per gli altri client | Codex: server gestiti non globali mantenuti con `enabled = false` solo nella tabella radice (mai `.env`/`.tools`). Claude/Copilot/Antigravity/Cursor: rimossi dallo scope globale (`remove`, con backup). Capability per client centralizzate (`CLIENT_MCP_CAPABILITIES`, valori non verificati = `unknown`). Attivazione per progetto con `scripts/runtime/project-mcp.js`: Codex scrive `enabled = true` nel `.codex/config.toml` di progetto e aggiunge la definizione globale disabilitata se manca; segnala la trust senza modificarla. Claude usa lo scope `local` privato, mai il file condiviso `.mcp.json`. Verificato con i CLI reali: `codex mcp list` (server attivo solo nel progetto) e `claude mcp get` (`Local config`, `Connected` solo nel progetto) |
| 4 - Lazy import Playwright | **Fatto** | Gate quantitativo superato (-50 MB RSS, -400 ms di startup per processo idle); latenza spostata sulla prima azione browser |
| 5 - Shutdown Playwright | **Fatto** | N1-N4 corretti; smoke `tests/smoke/playwright-node-lifecycle.smoke.mjs` (scenario SIGTERM solo POSIX: su Windows `child.kill()` non consegna un segnale intercettabile) |
| 6 - Benchmark full vs lean | **Fatto** (benchmark sintetico riproducibile); misura con client reali dopo l'ottimizzazione dell'utente | `scripts/runtime/mcp-startup-benchmark.js`, risultati sotto |

## 5. Benchmark full vs lean (Fase 6)

Comando: `node scripts/runtime/mcp-startup-benchmark.js` (Windows 11, Node 24.13). Per ogni sessione simulata avvia il set globale del profilo con le stesse definizioni command/args/env scritte dall'installer. Completa l'handshake MCP, misura con l'audit, poi chiude stdin come fa un client che esce e conta i server ancora vivi dopo 10 s.

| Sessioni | Processi full | Processi lean | Private full | Private lean | RSS full | RSS lean | Orfani |
|---:|---:|---:|---:|---:|---:|---:|---:|
| 1 | 11 | 4 | 548 MB | 166 MB | 964 MB | 309 MB | 0 |
| 2 | 22 | 8 | 1095 MB | 328 MB | 1927 MB | 614 MB | 0 |
| 3 | 33 | 12 | 1639 MB | 496 MB | 2888 MB | 925 MB | 0 |
| 6 | 66 | 24 | 3062 MB | 988 MB | 5138 MB | 1846 MB | 0 |

- La pendenza per sessione passa da 11 a 4 processi (-64%) e da circa 510 a circa 165 MB privati (circa -68%): `slope lean << slope full`, come richiesto dall'audit.
- La crescita resta lineare nel numero di sessioni: e' normale per `stdio` (N sessioni = N copie) e non indica un leak.
- I processi appena avviati hanno un private working set piu' alto di quelli vecchi di ore (circa 50 MB contro i 9,5 MB della fotografia della sezione 3, dove Windows aveva gia' ridotto i working set inattivi). Il confronto full/lean va quindi fatto a parita' di eta' dei processi, come nel benchmark.
- Resta da fare la misura con client reali (Codex e Claude Code a 1/3/6 sessioni), che richiede di applicare `--optimize-mcp-startup` alla configurazione dell'utente. E' una scelta dell'utente, non applicata automaticamente.
