5 lezioni da un MCP server vero: cosa ho imparato da Accordo

Cosa insegna un MCP server vero: stdout come protocollo, due server, dry-run di default, exit code e limiti dichiarati. Da Accordo.

In breve. Un MCP server vero insegna cinque cose: lo stdout è il protocollo, servono due server e non uno, il dry-run è il default, gli exit code sono un contratto, i limiti sono output di prima classe. Le ho imparate sul server MCP di Accordo, e ogni lezione sotto cita la fonte. Le definizioni stanno nell'hub MCP, le regole di progettazione in 1/3; qui solo ciò che gira davvero. Pubblicato il 6 ottobre 2026.

1. Lo stdout è il protocollo

Il server parla JSON-RPC delimitato da newline su stdio, e la diagnostica va solo su stderr (docs/MCP.md, verificato il 6 ottobre 2026). Il motivo è scritto nella pagina di installazione: stdout è riservato al protocollo, quindi tutto il resto esce su stderr. Una riga di log nel posto sbagliato non è rumore: corrompe il flusso che il client sta parsando.

È la lezione zero perché non perdona: sul filo non esiste "quasi valido". O il client parsa ogni riga, o niente funziona. Da qui anche la forma dei report — JSON deterministico, niente path assoluti — che ho già raccontato in 1/3: se l'output è il contratto, deve essere diffabile e committabile.

2. Due server, non uno

Ne girano due. Il server applicativo espone nove tool, cinque risorse e due prompt sul progetto del cliente, solo via stdio e solo in locale. Il secondo è un server documentazione con tre tool — search_docs, get_capability, check_job — sopra indice docs, claims ledger e indice job (pagina di installazione, 6 ottobre 2026).

E qui la sorpresa: lo stesso server documentazione è esposto anche read-only via HTTP come endpoint Streamable su https://accordo.dev/api/mcp, senza autenticazione: punti un client MCP a quell'URL e i tre tool rispondono dal corpus pubblicato, sotto il data boundary dichiarato nella pagina privacy. Il server applicativo resta stdio-only; quello documentazione no. La lezione: separa ciò che muta da ciò che informa, e solo il secondo può viaggiare in rete.

3. Dry-run di default, apply esplicito

Tutto ciò che genera codice o distrugge stato è dry-run a meno che apply non sia esplicitamente vero: crm_scaffold_module dichiara apply con default false, e le istruzioni del server enunciano la regola al client (pagina di installazione, 6 ottobre 2026). Il claim C-18 del ledger dice la stessa cosa in una riga: il server MCP espone contesto e tool di scrittura stretti, e la generazione è dry-run senza flag esplicito.

I tool di scrittura restano stretti, le mutazioni non scavalcano mai servizi o workflow, e la mutazione remota resta vincolata a lavoro di produzione che non esiste. La lezione: il server non si fida del client — nemmeno quando il client sono io. Ogni scrittura è una richiesta che il default rifiuta.

4. Gli exit code sono un contratto

0 valido, 1 problemi con report completo comunque stampato, 2 illeggibile del tutto — identici sulle due superfici, skill e MCP (pagina di installazione, 6 ottobre 2026). Così un harness che distingue solo zero da non-zero si comporta comunque bene, e uno più fine legge il report completo anche nel caso 1.

La lezione: il codice di uscita fa parte dell'API, non è un dettaglio operativo. Un agente decide sui numeri prima che sulle parole, e 1-con-report batte 1-senza-report perché lascia all'agente qualcosa su cui lavorare invece di un vicolo cieco.

5. I limiti sono output di prima classe

Ogni report porta limitations[], e le skill lo leggono come confine rigido su ciò che si può affermare. Claim e limitazioni sono stampati parola per parola da site/claims.json; un job senza pagina propria è elencato con il suo stato invece che linkato (pagina di installazione e contratti CLI e MCP, 6 ottobre 2026).

Il claim C-18 porta il suo limite attaccato: stdio-only, locale-only, nessuna autenticazione, autorità ereditata dal processo locale. La lezione: un server onesto non pubblica solo capacità, pubblica il confine delle capacità — e lo pubblica in un formato che l'agente legge prima di agire, non in una pagina che non aprirà mai.

Fatti e limiti, separati

Fatti (pagine verificate il 6 ottobre 2026):

  • JSON-RPC su stdio, diagnostica solo su stderr (docs/MCP.md).
  • Nove tool, cinque risorse, due prompt sul server applicativo; search_docs, get_capability, check_job sul documentazione (pagina di installazione).
  • Endpoint read-only https://accordo.dev/api/mcp per il server documentazione (stessa pagina).
  • apply default false, claim C-18 con limite stdio-only locale-only (stessa pagina).
  • Exit code 0/1/2 identici sulle due superfici (stessa pagina).
  • Claim e limiti stampati da site/claims.json, stati job da jobs.json (stessa pagina e contratti).
  • Superficie budgettata con controllo che rompe la build oltre il tetto (stessa pagina).

Limiti, dalle stesse fonti:

  • Nessun endpoint MCP applicativo hosted o autenticato; il server eredita l'autorità del processo che lo avvia.
  • Nessuna autenticazione spedita dal framework; in locale un header attore è un'asserzione, non un'identità.
  • Cinque lezioni da un server costruito — Accordo — non un benchmark tra implementazioni.

Domande frequenti

Quanti tool deve esporre un MCP server?

Pochi e contati: nove sul server applicativo di Accordo, tre sul documentazione, con un controllo che rompe la build oltre il budget. Ogni tool in più consuma contesto prima ancora di lavorare.

MCP via rete o solo locale?

Dipende da cosa fa il server: quello applicativo di Accordo è stdio-only e locale-only, quello documentazione è anche read-only via HTTP. Ciò che muta resta locale; ciò che informa può viaggiare.

Come fa l'agente a sapere cosa non può fare?

Leggendo limitations[] in ogni report e i limiti attaccati a ogni claim, prima di agire. Il confine delle capacità è output di prima classe, non documentazione separata.