La necessità di questo sviluppo si è palesata lavorando in un contesto fortemente regolamentato come quello dell’AI nella PA, dove è importante fornire a ciascun assistente il set minimo di dati e documenti necessari alla risposta. Questo faceva sì che ogni nuovo assistente agentico per un cliente richiedesse tool dedicati, specialmente svariate tipologie di RAG che implementassero i filtri desiderati da ciascuno.
Le alternative scartate
Ne conseguivano sviluppo e manutenzione moltiplicati e superficie di vulnerabilità crescente. L’alternativa diametralmente opposta, ovvero conferire un accesso pienamente agentico a sistemi e dati con il solo prompt a fare da confine, non regge in un contesto regolamentato su auditabilità e sicurezza. Anche l’utilizzo di MCP esterni non risolveva del tutto il problema, perché a parità di risorsa (ad esempio una collezione Weaviate) utenti diversi del sistema dovevano vedere parti diverse della documentazione, e non altre. I filtri e le tipologie di accesso alle risorse erano troppi per riuscire a tramutarli in una specifica di MCP associata al sistema da fruire.
Primitive e composizione in YAML
Perciò, ho consolidato i tool frammentati in un unico server MCP interno (STDIO) che separa le primitive, mantenute centralmente (ricerca semantica, filtro esatto sui metadati, individuazione dei metadati, memoria conversazionale, chiamate API), dalla loro composizione in tool veri e propri, dichiarata in un file YAML: una decina di righe di configurazione per tool, senza scrivere codice. Il server consuma la configurazione e produce tool già calibrati sui confini del singolo agente.
Nel catalogo del runtime convivono tre famiglie di tool: quelli statici tipizzati in Python nel repository (l’eredità dell’approccio precedente), quelli instradati verso server MCP esterni e quelli del server MCP interno. Quando l’agente invoca un tool, il runtime lo cerca nel catalogo e capisce a chi passare la chiamata:
agente (tool call, formato OpenAI)
|
v
catalogo tool del runtime
| lookup: di che tipo è?
+-----------------+-----------------+
v v v
tool statici server MCP server MCP
(classi Python esterni interno (STDIO)
nel repo) |
v
binding dal YAML:
primitive + filtri
Per l’agente le tre famiglie sono indistinguibili: l’instradamento e i confini vivono sotto il catalogo.
Cosa vede il modello
Il modello vede solo nome, descrizione e argomenti di ciascun tool: la sezione di binding, quella che dichiara la primitiva da usare e i filtri da applicare, gli è invisibile. Ed è lì che sta il valore in un contesto regolamentato. Un filtro può prendere il valore da un argomento della tool call, ma anche da una costante fissata nella configurazione o dal runtime che ospita l’assistente (per esempio l’ente dell’utente autenticato): in questi due casi il confine vale per costruzione, senza doversi fidare che il modello passi il parametro giusto, perché il modello non può né vedere il filtro né alterarlo. Il principio è trasferire in artefatti statici e versionabili tutti i confini noti in anticipo, riducendo l’autonomia del modello dove non serve.
Tre tool, una primitiva
Un esempio rende l’idea: tre tool con nomi, argomenti e confini diversi per tre assistenti, la stessa primitiva sotto (i nomi sono di fantasia, la struttura invece è concettualmente quella giusta):
cerca_documenti_bando cerca_atti_ente cerca_faq
(bando_id, query) (query) (query)
| | |
v binding v binding v binding
filtro: id bando filtro: ente filtro: tipo doc
valore <- argomento valore <- runtime valore <- "faq"
(bando_id) (ente dell'utente) (costante)
| | |
+--------------------+------------------+
v
primitiva: ricerca semantica
(un'unica implementazione centralizzata)
Mentre i server MCP esterni usano il protocollo per l’interoperabilità, qui il punto è la riusabilità: tanti tool agli occhi degli agenti, un’unica implementazione da mantenere e revisionare.
L’effetto sullo sviluppo
Lo sviluppo di un agente per un nuovo cliente è passato da 4-5 giorni a mezza giornata, spesa sulla comprensione del dominio invece che sull’implementazione. La revisione di sicurezza si applica a un insieme ristretto di primitive centralizzate, le correzioni si propagano automaticamente a tutti i tool che le usano, e il collo di bottiglia si sposta dagli sviluppatori agli esperti di dominio, con una UI di back-office in arrivo per la composizione autonoma dei tool.
Indipendenza dall’orchestratore
L’utilizzo del protocollo MCP per sviluppare questo layer di configurabilità dei tool ci permette di renderlo indipendente dall’orchestratore che lo avvolge, che può essere un loop agentico puro, un sistema a grafi come LangGraph e così via. L’importante è che il sistema di orchestrazione abbia un connettore per MCP, che è un requisito sempre più standard visto il successo che sta riscuotendo il protocollo proposto da Anthropic.
Il dettaglio dell’architettura e delle alternative scartate è nell’articolo su Agenda Digitale.