DEV Community

Cover image for Quanti token costa a Codex la lista di tool MCP di osFoundry e come ridurla per progetto?
BBT for BBT Italiano

Posted on

Quanti token costa a Codex la lista di tool MCP di osFoundry e come ridurla per progetto?

Questa è la chiamata che un client MCP fa subito dopo l'handshake, inviata con curl al server di osFoundry da un Mac a Tokyo il 2026-11-06. Non serve nessun token:

$ curl -s -D - -X POST https://api.osfoundry.io/api/v1/external-mcp \
    -H 'content-type: application/json' \
    -H 'accept: application/json, text/event-stream' \
    -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'

HTTP/2 200
content-type: application/json
content-length: 45368

{"jsonrpc":"2.0","id":2,"result":{"tools":[ …59 tools… ]}}
Enter fullscreen mode Exit fullscreen mode

Una delle 59 voci:

{"name": "read_note_tab",
 "title": "Read one Note tab",
 "description": "Read a selected Note, Markdown, Slides, Sheet, Mermaid, Board, Whiteboard, Design, or Artifacts tab by instance ID. Use list_note_attachments for Files and Sources.",
 "inputSchema": {"type": "object",
                 "properties": {"note_id": {"type": "string"}, "tab_id": {"type": "string"}},
                 "required": ["note_id", "tab_id"], "additionalProperties": false},
 "annotations": {"title": "Read one Note tab", "readOnlyHint": true,
                 "openWorldHint": false, "destructiveHint": false},
 "securitySchemes": [{"type": "oauth2", "scopes": ["osfoundry:read"]}]}
Enter fullscreen mode Exit fullscreen mode

Codex CLI si collega a questo server con una tabella [mcp_servers.osfoundry] e un url. A meno che il client non ne rimandi alcune, ogni richiesta che Codex fa poi al modello si porta dietro queste 59 dichiarazioni in qualche forma. Le domande qui sotto sono quelle in cui ti imbatti quando decidi quante dovrebbe portarsene dietro.

Cosa scarica Codex prima del primo turno?

Tre POST allo stesso URL: initialize, la notifica notifications/initialized e tools/list. Il server in produzione risponde così:

Richiesta Stato Body
initialize 200 308 bytes: protocollo 2025-06-18, osfoundry 0.1.0, tools.listChanged: false, instructions
notifications/initialized 202 vuoto
tools/list 200 45,368 bytes, 59 tool
tools/call senza token 401 WWW-Authenticate con resource_metadata

listChanged: false vuol dire che il server non annuncerà modifiche alla lista a metà sessione. La lista che Codex riceve all'avvio è quella di tutta la sessione.

La stringa instructions è di 125 caratteri: "Use list_granted_resources first. Every Note edit must use the exact tab ID. Local-only data is unavailable to cloud clients." La documentazione di Codex di OpenAI dice che Codex legge questo campo come indicazione valida per tutto il server e che i primi 512 caratteri dovrebbero reggersi da soli. Questa ci sta con ampio margine.

Il 401 è il punto in cui parte OAuth. Quella parte, e il motivo per cui osFoundry controlla l'accesso a ogni chiamata, è trattata in un altro post sullo stesso endpoint dal lato di Claude. Qui conta solo il catalogo.

Quanti token sono 59 tool?

Al modello non arriva il JSON grezzo. Codex trasforma ogni tool MCP in una dichiarazione di funzione. Nome, descrizione e schema di input sono ciò che serve al modello. title, annotations e securitySchemes sono metadati di protocollo che usa il client stesso. OpenAI non pubblica come vengono contate le dichiarazioni dei tool dalla sua parte, quindi i numeri qui sotto contano il payload, non la fattura.

Misurato con tiktoken 0.14.0 e l'encoding o200k_base, su JSON compatto:

Cosa Token
Risultato grezzo di tools/list, tutti i campi 10,494
59 dichiarazioni (nome con prefisso mcp__osfoundry__, descrizione, schema) 7,609
di cui descrizioni 2,194
di cui schemi di input 4,118
di cui i prefissi mcp__osfoundry__ 413
Metadati che restano nel client (title, annotations, securitySchemes) 3,590

La dichiarazione mediana è di 116 token. La più grande è edit_note_tab con 336, poi send_channel_message (237) e upload_linked_chat_artifact (233). La più piccola è list_granted_resources con 57.

Per scope: i 30 tool osfoundry:read arrivano a 2,906 token, i 26 tool osfoundry:write a 4,378 e i 3 tool osfoundry:run a 329. Quelli di scrittura costano di più per tool perché si portano dietro expected_version, idempotency_key e il contenuto da scrivere.

Circa 7,600 token stanno davanti a ogni richiesta, che il modello chiami osFoundry o no. I prefissi di prompt in cache lo rendono più economico di quanto sembri. Occupa comunque contesto, e 59 nomi si contendono l'attenzione del modello quando decide quale tool è quello giusto.

Dove applica Codex enabled_tools?

Le chiavi sono documentate nella pagina MCP di Codex di OpenAI: enabled_tools è una allow list, disabled_tools una deny list "applied after enabled_tools". L'implementazione sta in codex-rs/codex-mcp/src/tools.rs nel repository openai/codex:

pub(crate) fn allows(&self, tool_name: &str) -> bool {
    if let Some(enabled) = &self.enabled
        && !enabled.contains(tool_name)
    {
        return false;
    }

    !self.disabled.contains(tool_name)
}

pub(crate) fn filter_tools(tools: Vec<ToolInfo>, filter: &ToolFilter) -> Vec<ToolInfo> {
    tools
        .into_iter()
        .filter(|tool| filter.allows(&tool.tool.name))
        .collect()
}
Enter fullscreen mode Exit fullscreen mode

Due conseguenze. Il filtro confronta tool.tool.name, il nome MCP grezzo, quindi in config.toml scrivi read_note_tab, non mcp__osfoundry__read_note_tab. Il prefisso viene aggiunto dopo, nello stesso file, quando si costruiscono i nomi visibili al modello. E una ricerca in un set non fallisce mai. Un nome che non esiste in tools/list non corrisponde a niente e non dice niente.

Il filtro gira nel client dopo che tools/list è arrivato. Accorcia il prompt. Non accorcia il download, e il server non ne sa nulla.

Ricostruire il lato client

Sul Mac dove sono state fatte queste misure (Apple M1) non c'è nessun binario codex installato. catalog.py fa le veci dell'avvio di Codex. Sono 92 righe e si lancia con uv run --python 3.12 --with tiktoken. Legge le tabelle [mcp_servers.*] da un file TOML, invia le tre richieste all'endpoint reale tramite http.client, applica ai nomi grezzi la stessa regola "prima consenti, poi nega", costruisce le dichiarazioni mcp__osfoundry__<tool> e le conta:

kept = [t for t in tools if (allow is None or t["name"] in allow) and t["name"] not in deny]
decls = [model_decl("osfoundry", t) for t in kept]
unknown = sorted(((allow or set()) | deny) - names)
Enter fullscreen mode Exit fullscreen mode

Il file dei profili ha una tabella per configurazione. Un config.toml vero avrebbe una sola tabella [mcp_servers.osfoundry], globale o per repository:

[mcp_servers.notes_kb_records]
url = "https://api.osfoundry.io/api/v1/external-mcp"
enabled_tools = [
  "list_granted_resources", "list_project_resources", "list_note_tabs",
  "read_note_tab", "read_sheet_range", "search_knowledge_base",
  "read_knowledge_article", "list_record_items", "read_record_item",
]

[mcp_servers.deny_list]
url = "https://api.osfoundry.io/api/v1/external-mcp"
disabled_tools = ["request_session_run", "run_graph_session", "cancel_graph_run",
                  "propose_gitnest_work", "…the other propose_ tools…",
                  "join_agent_network", "send_agent_message", "wait_for_messages",
                  "list_agents", "set_status", "claim_connected_profile"]

[mcp_servers.typo]
url = "https://api.osfoundry.io/api/v1/external-mcp"
enabled_tools = ["list_granted_resources", "list_project_resources",
                 "read_note", "search_knowledge_base"]
Enter fullscreen mode Exit fullscreen mode

Output:

$ uv run --python 3.12 --with tiktoken python catalog.py profiles.toml --runs 10
status (200, 202, 200)  initialize 308 B  tools/list 45368 B  59 tools  instructions 125 chars
raw tools/list result: 10494 tokens (o200k_base)

profile                  tools   bytes  tokens  unknown names
all_59                      59   33559    7609  -
read_only                   30   13181    2906  -
notes_kb_records             9    3937     881  -
coding_project              16    7245    1604  -
deny_list                   40   20943    4693  -
typo                         3    1449     309  read_note
Enter fullscreen mode Exit fullscreen mode

read_only sono tutti i tool con readOnlyHint: true. coding_project sono i nove lettori Note/KB/Record più cinque lettori GitNest, edit_record_item e post_session_note. deny_list toglie i 19 tool che a un assistente di coding con un checkout locale servono a poco: i tre tool run, i dieci tool propose_… e sei tool di rete degli agenti e di profilo.

Passare da 59 tool ai nove lettori fa scendere le dichiarazioni da 7,609 a 881 token, l'88% in meno. La deny list arriva solo a 4,693, perché i tool di scrittura sono quelli costosi e lei ne tiene la maggior parte. Una allow list è la scelta più prevedibile. Resta anche corta quando il server aggiunge tool, ed è questo il motivo per preferirla.

La riga typo è l'errore che vedrai davvero. read_note non è un tool (read_note_tab sì), quindi il profilo finisce con tre tool, e nessuno si lamenta. Codex elencherà tranquillamente la Note e poi non avrà modo di aprirla. Il simulatore stampa i nomi sconosciuti. Codex no, per quanto mostrano la documentazione e il codice del filtro.

Quanto tempo costa l'handshake?

catalog.py --runs 10 ha cronometrato le tre richieste dall'inizio alla fine, due volte: una su un'unica connessione TLS e una con una connessione nuova per ogni richiesta:

Batch (n=10 ciascuno) mediana initialize mediana tools/list mediana avvio completo min–max
una connessione, batch 1 482 ms 826 ms 1,515 ms 1,034–2,899 ms
connessione nuova, batch 1 521 ms 704 ms 1,715 ms 1,122–3,107 ms
una connessione, batch 2 454 ms 828 ms 1,441 ms 741–1,915 ms
connessione nuova, batch 2 432 ms 478 ms 1,257 ms 1,088–1,557 ms

Riutilizzare la connessione non ha fatto una differenza costante. La variazione tra un run e l'altro era più grande dell'effetto. Filtrare non cambia per niente questo numero, perché Codex scarica comunque la lista completa.

Tre impostazioni documentate di Codex si scontrano con questo numero. startup_timeout_sec vale 10 secondi di default, e il run più lento qui ha impiegato 3.1 s. mcp_optional_startup_grace_ms vale 1,000 ms di default e, secondo la documentazione, controlla quanto Codex aspetta i server MCP opzionali quando costruisce il catalogo iniziale dei tool. Ogni avvio misurato qui ha impiegato di più. Se i tool di osFoundry devono esserci dal primo turno, le leve documentate sono required = true, che fa aspettare il server all'avvio e lo fa fallire se non è pronto, e startup_readiness = "catalog", che lascia che un catalogo in cache valido soddisfi l'avvio mentre la connessione reale si stabilisce. Le chiamate ai tool hanno comunque bisogno della connessione reale.

Cosa ti danno le annotazioni?

default_tools_approval_mode accetta auto, prompt, writes o approve, e "the writes mode prompts for tools that aren't marked read-only". osFoundry marca 30 dei suoi 59 tool con readOnlyHint: true. Con writes, Codex esegue quelli senza chiedere e chiede conferma per gli altri 29, che è proprio la divisione che faresti a mano. Per un singolo tool, tools.<tool>.approval_mode sovrascrive il default, e tools.<tool>.output_token_limit limita quanto il risultato di un tool può aggiungere al contesto.

Lo scope funziona allo stesso modo a livello OAuth. Codex usa scopes dalla configurazione se è impostato. Altrimenti prende gli scope dalla challenge del server o dai suoi metadati della risorsa protetta, e quelli di osFoundry elencano osfoundry:read, osfoundry:write, osfoundry:run e osfoundry:models. Con scopes = ["osfoundry:read"] il token viene emesso solo per la lettura, e ogni tool di scrittura dichiara osfoundry:write nei suoi securitySchemes, qualunque cosa dica enabled_tools.

Qual è il filtro che conta davvero?

Ce ne sono tre, e solo uno è un confine di sicurezza.

  • enabled_tools / disabled_tools in config.toml decidono cosa viene offerto al modello. Chiunque possa modificare il file può cambiarlo.
  • La checklist Tools nella scheda Connected di osFoundry è salvata sulla connessione come allowed_tools. null significa "tutti i tool supportati, attuali e futuri". La imposta il proprietario, e un file locale non può allargarla.
  • L'appartenenza ai progetti e il controllo di accesso a ogni chiamata decidono cosa può toccare qualsiasi tool. Una lista di tool completa su una connessione senza progetti non legge nulla.

Il filtro del client è igiene del prompt. Usalo per i token e per le scelte del modello, e usa gli altri due per i permessi.

Se lo configuri da solo

  • Per un server con decine di tool, preferisci enabled_tools a disabled_tools. Costa meno token e resta stabile quando il server aggiunge tool.
  • Scrivi i nomi MCP grezzi, e confronta la tua lista con un tools/list appena scaricato quando il server si aggiorna. I nomi sconosciuti falliscono in silenzio.
  • Imposta scopes in modo esplicito, così il token non porta più di quanto prevede la configurazione.
  • Abbina default_tools_approval_mode = "writes" a un server che imposta readOnlyHint onestamente. La divisione delle conferme vale quanto le annotazioni.
  • Se il server sta a un secondo o più di distanza, scegli tra required = true e startup_readiness = "catalog" invece di lasciare tutto al margine di default di 1,000 ms.
  • Tieni le instructions del server sotto i 512 caratteri e comprensibili da sole. Vengono inviate a ogni sessione, come la lista dei tool.

Come si lega la lista dei tool al budget di contesto in generale è spiegato nella pagina del glossario sulla context window di osFoundry.

Top comments (0)