Un server MCP, due client: 40 tool, poi zero
Arvo espone un server MCP a ChatGPT e Claude: Connect lampeggia 40 tool e li azzera. Cinque fix in un giorno, una lezione: la tool surface è per client.
In breve. Arvo espone lo stesso server MCP a ChatGPT e a Claude. Il 21 settembre 2026 Claude Connect lampeggia 40 tool e poi li azzera, cinque volte per cinque motivi diversi. La lezione: il catalogo interno è uno solo, la superficie pubblicata è per client.
Il lato server — stdio, dry-run, exit code — sta in 5 lezioni da un MCP server vero; le definizioni nell'hub sui prodotti agent-accessibili. Qui il rovescio: cosa succede quando lo stesso server lo leggono due client diversi, e ognuno pretende la sua forma.
40 tool, poi zero
La frase che apre la PR #2169, 21 settembre 2026: «Claude Connect still flashes 40 tools then zeros the Tools step». Connect mostra i 40 tool e poi li azzera. Restano due superfici non-MCP: le chiavi widget di ChatGPT su _meta (ui, openai/*) e la GET su text/event-stream che rispondeva 405, letta da Claude come connessione morta.
Il fix (b529d6f82) fa due cose: pubblica le _meta widget solo quando l'Origin è un host OpenAI, e risponde 200 alla GET SSE. E chiude con una riga onesta: «Do not submit the Claude listing until Connect stays at 40 tools» — il listing si invia quando Connect resta a 40, non prima.
Cinque fix in un giorno
Quella del 21 settembre è una serie: cinque PR, cinque motivi diversi per lo stesso sintomo.
| PR | Sintomo | Causa | Fix |
|---|---|---|---|
| #2164 | Connect: `invalid_target`, 0 tool | Claude omette il `resource` RFC 8707, il server voleva il match esatto | resource mancante = URL MCP canonico |
| #2166 | 40 tool dopo OAuth, poi 0 | Claude manda `MCP-Protocol-Version: 2025-11-25`, il server accettava solo 2025-06-18, 2025-03-26 e 2024-11-05: `tools/list` rispondeva 400 | accetta 2025-11-25 e pubblica `title` su ogni tool |
| #2168 | flash 40 poi zero, ma `tools/list` diretto 40 | i Tool pubblicati portavano i `securitySchemes` di ChatGPT al top level: il parser di Claude scarta l'array | `securitySchemes` fuori dall'oggetto pubblicato |
| #2169 | ancora flash poi zero | `_meta` widget ChatGPT + GET SSE 405 | `_meta` solo per origin OpenAI, GET 200 (b529d6f82) |
| #2171 | 40 tool, 40 «Missing title annotation» | il form di Claude legge il nome da `annotations.title` e ignora il title top-level | title specchiato nelle annotations |
Ogni riga ha la sua prova: la #2166 registra il probe live — header 2025-11-25 → «Unsupported MCP-Protocol-Version», header 2025-06-18 → 40 tool. Non è teoria, è la risposta del server.
La regola: il catalogo è uno, la superficie è per client
Dentro, il catalogo resta uno: gli oggetti TOOLS tengono i securitySchemes per i controlli sugli scope. Fuori, la pubblicazione dipende dall'Origin: ChatGPT tiene le _meta widget, Claude riceve una tools/list pulita. Stesso server, due superfici.
È il contrario di come avevo disegnato il server: un oggetto Tool, pubblicato uguale a tutti. Il 21 settembre ha detto che l'oggetto pubblicato non è uno: è uno per client, e ogni client scarta l'array per un motivo suo.
Fatti e ipotesi
FACT. Il 21 settembre 2026 le richieste OAuth di Claude omettono il resource RFC 8707 e il server risponde invalid_target con 0 tool; il fix usa l'URL MCP canonico come default (PR #2164).
FACT. Il 21 settembre 2026 Claude manda MCP-Protocol-Version: 2025-11-25 dopo l'initialize e tools/list risponde 400; il probe live mostra errore con 2025-11-25 e 40 tool con 2025-06-18; il fix accetta 2025-11-25 e pubblica title su ogni tool (PR #2166).
FACT. Il 21 settembre 2026 i Tool pubblicati con i securitySchemes di ChatGPT al top level fanno scartare l'array al parser di Claude, mentre tools/list diretto resta a 40; il fix li tiene fuori dall'oggetto pubblicato (PR #2168).
FACT. Il 21 settembre 2026 Connect lampeggia ancora 40 tool e li azzera per le chiavi widget ui e openai/* su _meta e la GET SSE 405; il fix pubblica le _meta solo per origin OpenAI e risponde 200 (PR #2169, b529d6f82).
FACT. Il 21 settembre 2026 il form di Claude legge il nome da annotations.title e segnala 40 «Missing title annotation»; il fix specchia il title nelle annotations (PR #2171).
HYPOTHESIS. I client MCP diranno tutti «standard» e parseranno ognuno a modo suo: la superficie pubblicata per client è la forma stabile, non una toppa. È la mia lettura dopo il 21 settembre 2026, non un dato misurato.
Limiti: un server hosted (Arvo /api/mcp), due client, un giorno. Non so se dopo il 21 settembre Connect sia rimasto a 40: le PR dicono di non inviare il listing finché non resta tale, e non ho una prova successiva. Cinque fix in un giorno non sono una statistica sui client MCP.
Domande frequenti
Perché Claude azzerava i tool di Arvo?
Ogni volta per un motivo diverso: OAuth senza resource, versione di protocollo non accettata, securitySchemes di ChatGPT nell'oggetto, _meta widget e GET 405, title nelle annotations sbagliate. Cinque PR il 21 settembre 2026, una per causa.
Basta seguire lo standard MCP per funzionare con tutti i client?
No. Il server di Arvo seguiva lo standard e due client lo leggevano in due modi diversi: versioni di protocollo, parser e form di registrazione cambiano per client. Il catalogo interno resta uno, la superficie pubblicata va adattata a chi la legge.
Quanti tool deve esporre un server MCP?
Quaranta andavano bene: il numero non è mai stato il problema, la forma sì. Ogni azzeramento veniva da un campo che un client non digeriva, non dal conteggio. Prima la superficie giusta per ogni client, poi il budget sui tool.