---
title: "MCP: cos’è, come creare un server e quali strumenti scegliere"
source: "https://www.matteovincenti.com/blog/mcp-guida-server-tools-skills"
language: it
author: "Matteo Vincenti"
published: 2026-10-20
updated: 2026-10-24
category: "AI"
tags: ["MCP", "Integrazioni AI", "Automazione"]
---

# MCP: cos’è, come creare un server e quali strumenti scegliere

Cos’è MCP e quando adottarlo in azienda: benefici da verificare, costi, permessi e criteri di scelta, con un server di esempio completo.

![Un raccordo verde comune collega tre attacchi di forma diversa](https://cdn.sanity.io/images/xrqkwry7/production/cdfe9369457403ad4b8bddcff2412a83f5fa84c9-1727x910.png?w=1400&fit=max&auto=format)

Categoria: AI · Tag: MCP, Integrazioni AI, Automazione

Preparare una proposta partendo da un catalogo richiede di trovare le schede giuste, leggere le caratteristiche e capire quali informazioni mancano. Se l’assistente AI non può consultare il sistema, qualcuno deve esportare i dati, passarli alla chat e riportare il risultato nel posto giusto.

MCP, Model Context Protocol, dà a un’applicazione AI un’interfaccia per consultare dati e richiamare funzioni di altri software. Il server espone operazioni riconoscibili, come leggere una scheda o salvare una bozza; l’applicazione può proporle al modello e gestirne l’esecuzione. [Architettura MCP](https://modelcontextprotocol.io/docs/learn/architecture).

Ho raccontato un’applicazione ai dati prodotto nell’articolo [SaaS compatibile con MCP: come On Page® integra l’AI nel PIM](https://onpage.it/blog/saas-compatibile-con-mcp-come-on-page-integra-lai-nel-pim/). Un PIM raccoglie le informazioni di prodotto: collegarlo all’assistente permette di lavorare sulle schede rispettando i permessi dell’utente. Qui approfondisco la parte che rende utilizzabile quel collegamento: quali operazioni esporre, come provarle e dove applicare i controlli.

## Tre percorsi di lettura

Se devi **decidere se adottare MCP**, parti dal funzionamento e dal confronto con CLI e API. La selezione dei progetti GitHub ti aiuta poi a valutare un’integrazione esistente.

Per **costruire il primo server**, vai a «Creare un server MCP locale»: trovi prerequisiti, file completi e un client che verifica nove casi. Usa dati dimostrativi e non richiede un account esterno.

Per **progettare o mantenere un’integrazione**, entra in «Anatomia di un tool MCP». Nome, schemi, annotazioni, risposte, autorizzazioni e differenze tra host sono spiegati attraverso la stessa operazione di catalogo. Le sezioni finali collegano il contratto alle skills e al consumo dei token.

## Come funziona MCP

L’host è l’applicazione che usi per lavorare con il modello. Al suo interno un client MCP comunica con un server, che espone capacità accessibili. Il server può girare sul tuo computer o su un’infrastruttura remota.

Questa separazione consente di riusare un’integrazione da host compatibili. Non stabilisce, da sola, come l’applicazione scelga i tool o quanto materiale inserisca nel contesto del modello: sono responsabilità dell’host e del suo workflow. [Architettura MCP](https://modelcontextprotocol.io/docs/learn/architecture).

**Come funziona MCP**

1. Host: applicazione AI
2. Client MCP
3. Server MCP
4. Dati o servizio

_Skill: procedura del lavoro → Host: applicazione AI_

Nel disegno il protocollo riguarda il collegamento client–server. La skill guida il lavoro svolto dall’applicazione. Il servizio finale può continuare ad avere le proprie API: MCP può costituire un livello di accesso a quelle API.

### Tool, resource e prompt

Un tool è una funzione richiamabile, per esempio ottenere il riepilogo delle issue di un progetto. Una resource espone contenuti, come un documento o uno schema. Un prompt propone un modello di interazione riutilizzabile.

Sono capacità distinte. Per una prima integrazione conviene scegliere quella che rappresenta il bisogno, anziché trasformare qualsiasi informazione in un comando generico. La documentazione ufficiale dell’architettura distingue queste primitive. [Primitive MCP](https://modelcontextprotocol.io/docs/learn/architecture).

Per la prova locale useremo un riepilogo di issue: il tool riceverà l’identificativo di un progetto e restituirà pochi dati noti. È un esempio più piccolo del catalogo, scelto per provare chiamate ed errori senza collegare servizi o account esterni. Nell’anatomia torneremo alle schede prodotto e alle diverse conseguenze di lettura, salvataggio e invio.

## Quando sceglierei MCP, una CLI o un’API diretta

Se un’attività vive interamente in uno script locale, una CLI può già offrire l’interfaccia necessaria. Per leggere lo stato di Git, aggiungere un server intermedio richiede una ragione concreta: riuso da host diversi, controlli centralizzati o una capacità che il client non possiede.

Un’API diretta è una buona candidata quando l’applicazione è sotto il tuo controllo e la chiamata è semplice. Puoi verificare input e output senza introdurre la gestione di un server MCP.

MCP diventa interessante quando vuoi esporre capacità riconoscibili a più applicazioni compatibili o collegare un agente a un sistema esterno tramite un contratto esplicito. Devi comunque mantenere l’integrazione, gestire versioni e verificare il comportamento dei client.

_Quando sceglierei MCP, una CLI o un’API diretta_

| Bisogno | Primo candidato da valutare | Domanda decisiva |
| --- | --- | --- |
| Operazione locale deterministica | CLI o script | Serve davvero un’interfaccia aggiuntiva? |
| Poche chiamate dentro una mia applicazione | API o SDK diretto | Chi userà il contratto oltre a questa applicazione? |
| Capacità riusabile da diversi host AI | Server MCP | I client richiesti supportano le funzioni necessarie? |
| Procedura ricorrente con più passi | Skill | Quali strumenti deve coordinare? |

Per una procedura ricorrente puoi combinare queste opzioni: una skill descrive il lavoro e usa gli strumenti disponibili, anche CLI o MCP. La decisione sull’interfaccia riguarda invece chi deve richiamare l’operazione e quale contratto sei disposto a mantenere.

## Creare un server MCP locale: il caso completo

Costruiamo `get_project_summary`: riceve il nome di un progetto e restituisce il numero di issue aperte e chiuse. I dati sono una fixture, cioè un piccolo insieme scritto nel codice: per `demo` ci aspettiamo tre issue aperte e sette chiuse. Questo ci permette di verificare il risultato e gli errori senza collegare un account GitHub.

Servono un terminale, Node.js con npm e un editor di testo. La prova del 24 ottobre 2026 usa Node.js 22.23.4 e npm 10.9.9. Prima di iniziare controlla che siano disponibili:

```bash
node --version
npm --version
```

L’esempio usa JavaScript ESM, `@modelcontextprotocol/server@2.3.1`, `@modelcontextprotocol/client@2.3.1` e `zod@4.6.7`. Nella linea v2 dell’SDK client e server sono pacchetti distinti. Zod esprime gli schemi che l’SDK espone al client; l’SDK supporta anche altre librerie compatibili con Standard Schema. [Pacchetti dell’SDK v2](https://ts.sdk.modelcontextprotocol.io/v2/get-started/packages.html), [tool e schemi](https://ts.sdk.modelcontextprotocol.io/v2/servers/tools.html).

Il materiale riproducibile è disponibile qui, prima dei passaggi:

Scarica: [MCP v2: sorgenti, dipendenze fissate e nove controlli locali](https://cdn.sanity.io/files/xrqkwry7/production/0ef9ba22c144dc9e0fd1743616a3f934d2eafabd.json)

Nel JSON, la proprietà `files` contiene il testo dei quattro file: `server.mjs`, `client.mjs`, `package.json` e `package-lock.json`. Puoi seguire la creazione da zero qui sotto oppure copiare ogni testo nel file dal nome corrispondente, in una cartella vuota, e installare l’albero fissato con `npm ci`. In entrambi i casi, alla fine la cartella contiene:

```txt
mcp-project-demo/
  package.json
  package-lock.json
  server.mjs
  client.mjs
```

Nel percorso da zero, npm genera i primi due file; tu crei gli altri due con i contenuti delle sezioni successive. Non serve modificare il tipo del progetto: l’estensione `.mjs` abilita gli import ESM.

```bash
mkdir mcp-project-demo
cd mcp-project-demo
npm init -y
npm install --save-exact @modelcontextprotocol/server@2.3.1 @modelcontextprotocol/client@2.3.1 zod@4.6.7
```

### Il server

Salva questo contenuto in `server.mjs`:

```javascript
import { McpServer } from '@modelcontextprotocol/server';
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import { z } from 'zod/v4';

const projects = { demo: { open: 3, closed: 7, source: 'fixture-v1' } };

serveStdio(() => {
  const server = new McpServer({ name: 'project-summary', version: '2.0.0' });
  server.registerTool('get_project_summary', {
    description: 'Read issue counts from the local demo fixture. No live data.',
    inputSchema: z.object({ project: z.string().min(1).max(40) }),
    outputSchema: z.object({
      project: z.string(), open: z.number(), closed: z.number(), source: z.string()
    }),
    annotations: { readOnlyHint: true, openWorldHint: false }
  }, async ({ project }) => {
    if (!Object.hasOwn(projects, project)) {
      return { isError: true, content: [{ type: 'text', text: 'Unknown project. Available: demo.' }] };
    }
    const result = { project, ...projects[project] };
    return { structuredContent: result, content: [{ type: 'text', text: JSON.stringify(result) }] };
  });
  return server;
});
```

Il contratto limita l’identificativo a una stringa non vuota di massimo 40 caratteri. Il risultato dichiara progetto, conteggi e provenienza. La proprietà `source` permette al lettore di riconoscere che sta osservando una fixture.

Il controllo `Object.hasOwn` evita di trattare come progetti le proprietà ereditate dell’oggetto JavaScript. È una piccola scelta che rende esplicito quali identificativi il server accetta davvero.

La risposta di successo contiene sia dati strutturati sia una rappresentazione testuale per i client che la usano. Più avanti vedremo il payload del protocollo e la differenza rispetto al valore restituito da questo handler. [Risultati nell’SDK v2](https://ts.sdk.modelcontextprotocol.io/v2/servers/tools.html).

### Gli errori devono restare errori

Un progetto sconosciuto restituisce `isError: true` e un messaggio utile. Non restituisce conteggi a zero: zero significherebbe che il progetto esiste ed è privo di issue.

Anche l’input non valido deve fallire prima di essere interpretato come una richiesta lecita. Una stringa vuota, un numero e un identificativo troppo lungo sono casi diversi dal progetto mancante, ma nessuno deve produrre un riepilogo apparentemente valido.

Questo principio conta quando il risultato viene letto da un modello: una risposta ambigua può diventare la premessa di un’azione successiva.

### Il client di verifica

Salva il secondo file come `client.mjs`:

```javascript
import assert from 'node:assert/strict';
import { fileURLToPath } from 'node:url';
import { Client } from '@modelcontextprotocol/client';
import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';

const client = new Client({ name: 'article-check', version: '2.0.0' }, {
  versionNegotiation: { mode: { pin: '2026-07-28' } }
});
const transport = new StdioClientTransport({
  command: process.execPath,
  args: [fileURLToPath(new URL('./server.mjs', import.meta.url))]
});
let checks = 0;
try {
  await client.connect(transport);
  const { tools } = await client.listTools();
  assert.equal(tools.length, 1); checks++;
  assert.equal(tools[0].name, 'get_project_summary'); checks++;
  const demo = await client.callTool({ name: 'get_project_summary', arguments: { project: 'demo' } });
  assert.deepEqual(demo.structuredContent, { project: 'demo', open: 3, closed: 7, source: 'fixture-v1' }); checks++;
  const missing = await client.callTool({ name: 'get_project_summary', arguments: { project: 'missing' } });
  assert.equal(missing.isError, true); checks++;
  for (const project of ['', 'x'.repeat(41), 12, '__proto__']) {
    const result = await client.callTool({ name: 'get_project_summary', arguments: { project } });
    assert.equal(result.isError, true); checks++;
  }
  const again = await client.callTool({ name: 'get_project_summary', arguments: { project: 'demo' } });
  assert.equal(again.structuredContent.open, 3); checks++;
  console.log(JSON.stringify({ checks, result: 'PASS', demo: demo.structuredContent }));
} finally {
  await client.close();
}
```

Esegui:

```bash
node client.mjs
```

Nella verifica locale l’output è stato:

```json
{"checks":9,"result":"PASS","demo":{"project":"demo","open":3,"closed":7,"source":"fixture-v1"}}
```

I nove controlli riguardano il catalogo, il risultato atteso, il progetto mancante, quattro input problematici e una nuova chiamata valida dopo gli errori. Il client chiude la connessione nel blocco `finally`.

Il client fissa la revisione del protocollo `2026-07-28`: la prova fallisce se il server non la supporta. Il numero `2.3.1` identifica invece il pacchetto SDK. Sono due versioni da controllare separatamente. Per un client v2 costruito a mano, la documentazione distingue il default legacy, la modalità `auto` che può negoziare un percorso precedente e il pin usato qui, che impone questa revisione. [Compatibilità nell’SDK v2](https://ts.sdk.modelcontextprotocol.io/v2/migration/support-2026-07-28).

Nella prova del 24 ottobre, i nove controlli sono passati sia installando dal lockfile con `npm ci`, sia dal percorso `npm init` e `npm install`. Il passaggio successivo è collegare un host e osservare la chiamata effettiva.

## Perché il server usa stdio

Con stdio, client e server comunicano tramite gli stream del processo. È una scelta semplice per questo esempio locale. Il server non apre una porta HTTP e il client avvia il processo quando serve.

L’output standard è riservato ai messaggi del protocollo. Inserire un `console.log` di debug nel server può corrompere lo scambio. Per una diagnostica locale usa lo standard error e mantieni i log privi di informazioni riservate.

Per un servizio remoto valuta Streamable HTTP e progetta autenticazione, distribuzione e gestione delle connessioni insieme al trasporto. Il tutorial resta locale: il client avvia Node direttamente. [Stdio nell’SDK v2](https://ts.sdk.modelcontextprotocol.io/v2/serving/stdio.html).

### Collegarlo a Claude Code o Codex

Dopo il PASS locale puoi registrare lo stesso server in uno dei due client. Serve la relativa CLI già installata e Node disponibile nel suo ambiente. Dalla cartella della demo esegui solo i comandi del client scelto:

```bash
MCP_DEMO_SERVER="$PWD/server.mjs"

# Claude Code
claude mcp add --transport stdio project-summary -- node "$MCP_DEMO_SERVER"
claude mcp list

# Codex
codex mcp add project-summary -- node "$MCP_DEMO_SERVER"
codex mcp list
```

Apri una sessione del client, usa `/mcp` per controllare connessione e strumenti, poi chiedi di chiamare `get_project_summary` con `{"project":"demo"}`. Controlla la chiamata e la risposta: devono comparire `open: 3`, `closed: 7` e `source: "fixture-v1"`. Se compare solo il messaggio di registrazione, hai salvato la configurazione ma non ancora verificato il collegamento. Se il processo non parte, controlla prima il percorso di Node e quello assoluto del file; se parte ma la chiamata fallisce, confronta l’errore con il test locale. Il percorso assoluto salvato va aggiornato se sposti la demo.

Sono ricette basate sulla documentazione dei client, da verificare nel tuo ambiente; la prova eseguita sopra usa il client JavaScript. ChatGPT nel browser non legge la configurazione locale di Codex. [Configurazione Claude Code](https://code.claude.com/docs/en/mcp-quickstart), [MCP in Codex](https://learn.chatgpt.com/docs/extend/mcp?surface=cli).

## Quali strumenti MCP su GitHub prenderei in considerazione

Una selezione utile parte dallo scenario e dal manutentore. Le stelle non dicono quali permessi servano, se il client supporti una funzione o quanto sia grande una risposta.

### GitHub MCP Server: repository, issue e pull request

È il server ufficiale di GitHub, con funzionalità per leggere e operare sulla piattaforma. Lo prenderei in considerazione quando il workflow richiede informazioni aggiornate su repository, issue o PR da un host compatibile. Il progetto documenta modalità remote e locali. [github/github-mcp-server](https://github.com/github/github-mcp-server).

Per ottenere un dato in uno script confronterei prima `gh` e l’API. Se serve il server MCP, i toolset permettono di esporre gruppi di funzioni e la modalità read-only esclude le scritture. Questo filtro riduce le operazioni disponibili; i permessi delle credenziali restano da configurare sul servizio. [Toolset e modalità read-only](https://github.com/github/github-mcp-server), [gh api](https://cli.github.com/manual/gh_api).

### Playwright MCP: interazione con il browser

Il progetto Microsoft espone automazione del browser tramite Playwright. È un candidato quando l’agente deve esplorare un’interfaccia o interagire con elementi della pagina. [microsoft/playwright-mcp](https://github.com/microsoft/playwright-mcp).

I manutentori propongono anche [Playwright CLI con skills](https://github.com/microsoft/playwright-cli) per i coding agent. Confronta questa strada con MCP quando l’agente dispone di un terminale; per verificare con test di regressione una sequenza già definita, resta utile uno script Playwright con verifiche deterministiche. MCP è un candidato per l’esplorazione iterativa dell’interfaccia con gli strumenti esposti dal server. In tutti i casi conta la sessione del browser a cui dai accesso, inclusi account e dati. Il confronto dei manutentori sull’efficienza non è una misura del nostro workflow.

### Context7: documentazione nel workflow

Context7, mantenuto da Upstash, offre sia MCP sia CLI con skills per cercare documentazione pertinente a una libreria. La scelta dipende dagli strumenti disponibili nell’host e dal modo in cui vuoi recuperare i riferimenti. [upstash/context7](https://github.com/upstash/context7).

È ufficiale rispetto al progetto Context7; non diventa per questo il produttore di ogni libreria indicizzata. Verificherei versione richiesta, provenienza del frammento e sufficienza del contesto. Una risposta corta su una versione sbagliata rimane una risposta sbagliata.

### MCP Inspector: capire il contratto

Inspector è uno strumento del progetto MCP per esaminare catalogo, parametri e risposte prima di coinvolgere un modello. Offre interfacce interattive e una CLI utilizzabile negli script. Il client del tutorial contiene invece nove asserzioni fissate sul risultato della nostra fixture: puoi mantenerle come test del contratto e usare Inspector per esplorare altre chiamate. [modelcontextprotocol/inspector](https://github.com/modelcontextprotocol/inspector).

### Reference servers: studiare un’implementazione

Il repository dei server di riferimento raccoglie implementazioni didattiche del progetto MCP. I manutentori le distinguono dalle integrazioni da adottare in produzione: sono materiale utile per studiare SDK e protocollo. Per scegliere un servizio operativo controlla manutentore, licenza, issue pertinenti e compatibilità con il tuo ambiente. [modelcontextprotocol/servers](https://github.com/modelcontextprotocol/servers).

## Come valutare un server prima di adottarlo

Scegli un task reale abbastanza piccolo da poter controllare il risultato. Registra i tool selezionati e i dati che transitano. Un riepilogo richiede lettura; modificare una issue introduce un effetto diverso. Per un server remoto valuta anche il soggetto che riceve i dati ed esegue l’operazione.

Prova poi i fallimenti che cambiano la risposta: dati non trovati, accesso negato, timeout e ricerca incompleta. Un assistente che riceve un elenco vuoto per tutti questi casi non ha gli elementi per distinguerli. La progettazione dell’accesso e degli errori torna nell’anatomia del tool. [Autorizzazione MCP](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization).

## Anatomia di un tool MCP: dal nome all’autorizzazione

Nel caso del catalogo, “aiutami a preparare una proposta” nasconde operazioni con conseguenze diverse: leggere una scheda, salvare una bozza, inviarla a un destinatario. Esporrei queste azioni separatamente. Chi gestisce il processo può così stabilire quali automatizzare e dove serve una verifica; chi implementa il server può assegnare accessi e controlli coerenti.

Un tool ha un descrittore, che il client scopre attraverso `tools/list`, e un’implementazione sul server. Il descrittore spiega che cosa si può chiedere; il codice deve rendere vere quelle promesse. Una chiamata `tools/call` identifica il tool e i suoi argomenti. Il server valida la richiesta, controlla l’accesso ed esegue l’operazione. [Tools nella specifica MCP](https://modelcontextprotocol.io/specification/2026-07-28/server/tools).

### Nome, descrizione e schema sono parte dell’interfaccia

`get_catalog_item` indica un’azione più precisa di `manage_data`. Userei nomi stabili e riconoscibili, senza incorporarvi istruzioni come “chiamami sempre”. Il `title` serve a mostrare un’etichetta leggibile, mentre la `description` deve aiutare a scegliere: quando usare il tool, che cosa restituisce, quali effetti produce e quali limiti contano. Una descrizione utile potrebbe specificare che la ricerca riguarda soltanto il catalogo dell’account collegato e che non calcola disponibilità o prezzi personalizzati. È un criterio di progettazione coerente con le indicazioni di Anthropic sui tool. [Writing effective tools](https://www.anthropic.com/engineering/writing-tools-for-agents).

Nel descrittore MCP, `name` e `inputSchema` sono obbligatori; `title`, `description`, `outputSchema`, `annotations`, `icons` e `_meta` sono facoltativi. I client possono imporre requisiti ulteriori. `inputSchema` descrive un oggetto JSON; `outputSchema`, quando presente, descrive il risultato in `structuredContent`. Le icone aiutano la presentazione. `_meta` ospita metadati anche specifici del client: non esiste un significato universale per ogni chiave. [Schema Tool](https://modelcontextprotocol.io/specification/2026-07-28/schema#tool).

Torniamo al catalogo: il descrittore seguente progetta una lettura del solo account autenticato. Non è un secondo server da avviare; serve a leggere il contratto campo per campo.

```json
{
  "name": "get_catalog_item",
  "title": "Read a catalogue item",
  "description": "Read one item from the authenticated account's catalogue. Returns approved product details. Does not calculate stock or customer-specific prices.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "itemId": {
        "type": "string",
        "minLength": 1,
        "maxLength": 64,
        "description": "The catalogue item identifier."
      }
    },
    "required": ["itemId"],
    "additionalProperties": false
  },
  "outputSchema": {
    "type": "object",
    "properties": {
      "item": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "description": { "type": "string" }
        },
        "required": ["id", "name", "description"],
        "additionalProperties": false
      }
    },
    "required": ["item"],
    "additionalProperties": false
  },
  "annotations": {
    "readOnlyHint": true,
    "destructiveHint": false,
    "openWorldHint": false
  }
}
```

Lo schema vincola la forma dei dati. Per decidere se quell’utente possa leggere `itemId`, il server deve verificare le credenziali e l’appartenenza della scheda al suo account a ogni richiesta. Non accetterei un identificativo dell’account come prova di autorizzazione. Le API key non devono finire negli argomenti, nelle descrizioni o nei risultati. La guida OpenAI richiede di applicare l’autorizzazione sul server. [Autenticazione e autorizzazione](https://developers.openai.com/plugins/build/mcp-server#authenticate-and-authorize-requests).

Per un plugin OpenAI, `securitySchemes` può dichiarare il meccanismo di accesso, per esempio OAuth e i relativi scope. È una configurazione dell’integrazione, distinta dalle annotazioni MCP; il server deve comunque verificare il token e applicare i permessi. Le istruzioni del server possono descrivere regole condivise, come leggere una versione prima di aggiornarla. Non sostituiscono la descrizione di ciascun tool. [Guida del server MCP per OpenAI](https://developers.openai.com/plugins/build/mcp-server).

### Schemi di input: campi, vincoli e valori predefiniti

Nel JSON, `properties` definisce la validazione dei campi indicati, `required` impone la presenza di `itemId`, `minLength` e `maxLength` ne limitano la lunghezza. È `additionalProperties: false` a escludere campi extra: `properties` da solo non li vieta. Un campo assente e un campo con valore `null` sono casi diversi. [Oggetti in JSON Schema](https://json-schema.org/understanding-json-schema/reference/object).

Descriverei anche unità di misura, valori enumerati e significato degli identificativi dove necessari. Per esempio, una quantità deve chiarire se indica pezzi o confezioni; un importo deve avere una valuta. Il validatore non ricava questi vincoli da una descrizione vaga. Inoltre `default` documenta un valore, ma la validazione JSON Schema non lo inserisce automaticamente quando il campo manca. [Annotazioni di JSON Schema](https://json-schema.org/understanding-json-schema/reference/annotations).

Un SDK può offrire una sintassi più breve, come gli oggetti Zod del tutorial, e produrre il descrittore da esporre al client. Nel controllo finale guarderei quindi il risultato di `tools/list`, oltre al codice di registrazione: il client riceve quel contratto. Non aggiungerei un parametro “conferma: true” confidando che il modello lo compili: un argomento generato dal modello non prova il consenso di una persona.

Per la validità degli input, Claude Code documenta anche nomi delle proprietà al primo livello lunghi 1–64 caratteri, con lettere ASCII, cifre, `_`, `.` e `-`, e controlli dello schema 2020-12. L’esclusione dei tool non validi dipende dalle feature flag indicate nella documentazione; i combinatori alla radice hanno un adattamento separato. Per questo proverei anche lo schema nell’host effettivamente distribuito. [Validazione degli schemi in Claude Code](https://code.claude.com/docs/en/mcp#tools-with-invalid-input-schemas).

### Le quattro annotazioni: significato, valori predefiniti e limiti

Le annotazioni sono indicazioni sul comportamento. Il suffisso `Hint` conta: dichiarare una lettura non impedisce al codice di scrivere, né concede accesso a un documento. Il client deve applicare le proprie regole e valutare l’affidabilità del server. [Perché MCP ha introdotto le annotazioni](https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/).

_Le quattro annotazioni: significato, valori predefiniti e limiti_

| Annotazione | Che cosa descrive | Se viene omessa nella specifica |
| --- | --- | --- |
| readOnlyHint | true: il tool non modifica lo stato dell’ambiente. Leggere dati riservati resta un’operazione soggetta ad autorizzazione. | false |
| destructiveHint | Per un tool che scrive, true segnala possibili aggiornamenti distruttivi; false indica un comportamento soltanto additivo. | true |
| idempotentHint | Per un tool che scrive, true significa che ripetere la stessa chiamata con gli stessi argomenti non aggiunge altri effetti. | false |
| openWorldHint | true: interazione con entità aperte; false: attività limitata a un dominio chiuso. | true |

I default descrivono come interpretare un’annotazione omessa. `destructiveHint` e `idempotentHint` sono pertinenti alle scritture, quando `readOnlyHint` è `false`; per questo il tool di lettura della fixture non dichiara l’idempotenza. Per l’etichetta leggibile la precedenza è `title`, poi `annotations.title` e infine `name`. La politica di conferma resta una scelta dell’host, trattata nella sezione successiva. [Schema ToolAnnotations](https://modelcontextprotocol.io/specification/2026-07-28/schema#toolannotations).

L’idempotenza riguarda gli effetti: un secondo tentativo con gli stessi argomenti non deve creare un’altra bozza o un secondo invio. Le risposte possono cambiare, per esempio perché una lettura trova dati aggiornati. Per una creazione, una chiave di idempotenza permette al servizio di riconoscere una richiesta già completata dopo un timeout; il servizio deve conservarla e applicarla. Questo dettaglio ha conseguenze concrete nei client: Claude Code documenta un possibile tentativo dopo la riconnessione per chiamate HTTP o connector interrotte marcate `idempotentHint: true` oppure `readOnlyHint: true`. Dichiarare il flag senza gestire le ripetizioni espone quindi a effetti duplicati. [Retry in Claude Code](https://code.claude.com/docs/en/mcp).

“Esterno” non significa semplicemente “ospitato su un altro server”. Nelle indicazioni OpenAI un workspace privato e circoscritto può avere `openWorldHint: false` anche se è un SaaS remoto. Una ricerca sul web pubblico ha `openWorldHint: true` pur essendo una lettura. Il confine riguarda le entità raggiungibili. [Annotazioni nella guida OpenAI](https://developers.openai.com/plugins/build/mcp-server#tool-annotations-and-elicitation).

Per il processo esemplificativo sceglierei così:

_Le quattro annotazioni: significato, valori predefiniti e limiti_

| Operazione e confine ipotizzato | readOnlyHint / openWorldHint | destructiveHint |
| --- | --- | --- |
| Leggere una scheda dal solo catalogo privato collegato | true / false | false |
| Cercare informazioni nel web pubblico | true / true | false |
| Aggiungere una nuova bozza nel workspace, senza sovrascrivere | false / false | false |
| Inviare una proposta a un indirizzo esterno libero, con invio non revocabile | false / true | true |

La stessa etichetta può nascondere effetti diversi. Un “salva bozza” che sovrascrive la versione precedente cambia la valutazione di `destructiveHint`. Un “invia” limitato a una lista chiusa cambia quella di `openWorldHint`. Per `idempotentHint` conta invece che cosa accade quando il servizio riceve due volte gli stessi argomenti.

### Chi decide di chiedere approvazione: ChatGPT, Claude Code e Codex

Nessuna delle quattro annotazioni equivale a “chiedi sempre conferma”. Per i plugin OpenAI le linee guida richiedono valori booleani espliciti per `readOnlyHint`, `destructiveHint` e `openWorldHint`: un descrittore valido per il protocollo può quindi essere incompleto per quel percorso di integrazione. Le indicazioni insistono anche su nomi e descrizioni coerenti con l’operazione reale. [Linee guida dei plugin](https://developers.openai.com/plugins/plugin-guidelines).

Per i server MCP personalizzati in ChatGPT, la documentazione indica la conferma predefinita delle scritture e considera scrittura un tool privo di `readOnlyHint`. Le scelte ricordate nella conversazione possono cambiare l’esperienza successiva. Questo comportamento riguarda ChatGPT; non è una regola universale di MCP. [MCP personalizzati in ChatGPT](https://developers.openai.com/api/docs/guides/custom-mcp-server).

Claude Code espone regole di permesso per i tool identificati come `mcp__server__tool`: si possono consentire, sottoporre a richiesta o negare. Nel nostro esempio imposterei una richiesta per l’invio anziché consentire indistintamente tutto il server. È una scelta di policy del client, distinta dai permessi sui dati. [Permessi MCP in Claude Code](https://code.claude.com/docs/en/permissions#mcp).

Claude Code documenta anche `_meta["anthropic/requiresUserInteraction"]: true`, che richiede interazione umana a ogni chiamata; non è un’annotazione standard. `dontAsk` nega il tool. Nel caso Agent SDK, la callback `canUseTool` può approvare ed è responsabilità dell’applicazione mostrare la richiesta. Il nome completo con prefisso non deve superare 128 caratteri, altrimenti il tool viene omesso. [MCP in Claude Code](https://code.claude.com/docs/en/mcp).

Codex permette di impostare una politica per server e una diversa per singolo tool. Apri `~/.codex/config.toml` e cerca `[mcp_servers.project-summary]`, la tabella creata dalla registrazione precedente. Inserisci `default_tools_approval_mode` in quella tabella, conservando `command` e `args`; non duplicarne l’intestazione. Aggiungi poi la sottotabella del tool mostrata sotto. L’esempio chiede conferma come impostazione del server e approva separatamente la lettura nota della fixture.

```toml
[mcp_servers.project-summary]
default_tools_approval_mode = "prompt"

[mcp_servers.project-summary.tools.get_project_summary]
approval_mode = "approve"
```

Le modalità documentate per il server sono `auto`, `prompt`, `writes` e `approve`; `writes` richiede conferma per i tool non marcati read-only. La regola del singolo tool prevale su quella del server. `enabled_tools` e `disabled_tools` filtrano invece quali strumenti sono disponibili: consentire una chiamata e renderla disponibile sono controlli distinti. Per un futuro tool di invio terrei `prompt`. [MCP in Codex](https://learn.chatgpt.com/docs/extend/mcp?surface=cli), [riferimento della configurazione](https://learn.chatgpt.com/docs/config-file/config-reference).

Se costruisci una tua applicazione con la Responses API, il controllo `require_approval` è un’altra superficie ancora: la documentazione prevede richieste di approvazione prima di condividere dati con server remoti. Non eredita la configurazione di Codex o della conversazione ChatGPT. [MCP nella Responses API](https://developers.openai.com/api/docs/guides/tools-connectors-mcp#approvals).

Per l’invio consiglio una conferma che mostri destinatario e contenuto, legata all’azione che il server eseguirà. Autenticazione, autorizzazione sui dati e consenso alla singola azione rispondono a domande diverse: chi sei, che cosa puoi fare, quale operazione hai approvato.

### Il risultato e gli errori completano il contratto

`structuredContent` contiene i dati strutturati; `content` può contenere testo o altri contenuti MCP. Quando è dichiarato, `outputSchema` riguarda i dati strutturati restituiti dal tool, non il formato del testo finale scritto dal modello. Un errore dell’operazione può essere restituito con `isError: true`; gli errori di protocollo restano un caso distinto. [Risultati ed errori dei tool](https://modelcontextprotocol.io/specification/2026-07-28/server/tools).

Nel percorso dei plugin OpenAI, il riferimento richiede inoltre `outputSchema` quando il tool restituisce `structuredContent`. È un requisito dell’integrazione più restrittivo dell’opzionalità del campo nel protocollo. [Parametri del descrittore OpenAI](https://developers.openai.com/plugins/reference#tool-descriptor-parameters).

Nel catalogo, “nessuna scheda trovata” e “servizio non disponibile” richiedono risposte diverse. Non riempirei il secondo caso con un elenco vuoto: l’assistente potrebbe interpretarlo come assenza di prodotti. Restituirei identificativi stabili e, quando servono alla decisione, fonte, istante della lettura e confine dei risultati. Per una lista paginata deve essere chiaro se esistono altre pagine.

La guida OpenAI distingue inoltre il `_meta` del risultato, nascosto al modello, dai contenuti che il modello può leggere. Nascondere un campo al modello non lo rende un deposito sicuro per credenziali: il client può riceverlo. Non confonderei questo contenitore con il `_meta` del descrittore usato sopra per Claude Code. [Risultati senza interfaccia personalizzata](https://developers.openai.com/plugins/build/mcp-server#return-useful-results-without-ui).

### Scoperta, nomi e versioni: perché un tool può non comparire

La specifica raccomanda nomi unici nel server, di 1–128 caratteri, con lettere ASCII, cifre, `_`, `-` e `.`, senza spazi. Il titolo leggibile può essere una frase. Tieni il nome breve: un host può aggiungere un prefisso e applicare un limite proprio, come quello di Claude Code visto sopra. [Nomi dei tool MCP](https://modelcontextprotocol.io/specification/2026-07-28/server/tools#tool-names).

La lista dei tool può essere paginata: `nextCursor` serve a continuare la scoperta e il client deve trattarlo come opaco. Questa paginazione riguarda il catalogo delle funzioni. La paginazione dei prodotti restituiti da una nostra ricerca va progettata nel contratto di quella ricerca; non viene aggiunta automaticamente da MCP. [Paginazione del protocollo](https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/pagination).

Nella revisione `2026-07-28`, `tools/list` include anche `ttlMs` e `cacheScope`: regolano freschezza e condivisione della cache del catalogo. Se la lista dipende dall’utente, il confine della cache deve rispettarlo. Non sono un sistema di cache automatico per i risultati di `tools/call`, né equivalgono alla prompt cache del modello. [Cache MCP](https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/caching).

La revisione `2026-07-28` richiede `protocolVersion` e `clientCapabilities` nei metadati di ogni richiesta. `clientInfo` è facoltativo e raccomandato; è un’identità dichiarata dal client, inadatta ad autenticare chi chiama. Le revisioni precedenti basate su `initialize` usano invece un handshake. [Metadati di base](https://modelcontextprotocol.io/specification/2026-07-28/basic), [versioni e compatibilità](https://modelcontextprotocol.io/specification/2026-07-28/basic/versioning).

### Una risposta completa, con dati e contenuto leggibile

Per il descrittore del catalogo, un esempio di payload di successo è:

```json
{
  "resultType": "complete",
  "structuredContent": {
    "item": {
      "id": "item-42",
      "name": "Adjustable desk",
      "description": "Height-adjustable work surface."
    }
  },
  "content": [
    {
      "type": "text",
      "text": "{\"item\":{\"id\":\"item-42\",\"name\":\"Adjustable desk\",\"description\":\"Height-adjustable work surface.\"}}"
    }
  ],
  "isError": false
}
```

Questo è il payload del risultato sul filo, senza l’involucro JSON-RPC e l’identificativo della richiesta. `resultType: "complete"` appartiene alla revisione `2026-07-28`; l’SDK gestisce questi dettagli di trasporto, perciò non devi copiarlo nell’handler della fixture. Nella stessa revisione `structuredContent` può essere qualsiasi valore JSON ammesso dall’output schema. [Schema del risultato](https://modelcontextprotocol.io/specification/2026-07-28/schema#calltoolresult), [SDK e payload wire](https://ts.sdk.modelcontextprotocol.io/v2/migration/support-2026-07-28).

`content` può includere testo, immagini, audio, risorse incorporate e collegamenti a risorse; il supporto concreto va provato nel client. Per il nostro catalogo basta il testo. Un collegamento a una risorsa e il contenuto completo della risorsa comportano scelte diverse su ciò che trasferiamo: inviare tutto “per sicurezza” aumenta il materiale da gestire. [Tipi di contenuto MCP](https://modelcontextprotocol.io/specification/2026-07-28/schema#contentblock).

Attenzione a un’altra omonimia: le annotazioni dei contenuti, come `audience`, `priority` e `lastModified`, descrivono destinatari, priorità e aggiornamento. Non appartengono alle quattro annotazioni comportamentali del tool e non sono controlli d’accesso. [Annotazioni delle risorse e dei contenuti](https://modelcontextprotocol.io/specification/2026-07-28/server/resources#annotations).

### Accesso ai dati, richieste di informazioni e lavori lunghi

Quando adotti il meccanismo di autorizzazione HTTP previsto da MCP, il flusso è basato su OAuth. Il meccanismo è opzionale nel protocollo: questo non rende pubblici i dati di un servizio che richiede autenticazione. Per stdio la specifica indica credenziali recuperate dall’ambiente. Nel catalogo servono scope limitati alle operazioni necessarie e un controllo dell’account a ogni accesso, anche per un ID già restituito dal server. [Autorizzazione MCP](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization).

L’elicitation serve a raccogliere informazioni mancanti tramite il client: per esempio la lingua della proposta. Non equivale al permesso di inviarla. La modalità form non deve raccogliere password o token; per interazioni sensibili la specifica prevede la modalità URL, con supporto dichiarato dal client. [Elicitation MCP](https://modelcontextprotocol.io/specification/2026-07-28/client/elicitation).

Nella revisione corrente un risultato `input_required` può chiedere gli input necessari e permettere un nuovo tentativo della richiesta. Il server non può presumere che l’utente risponda; deve proteggere l’integrità di un eventuale `requestState` che influenzi autorizzazione o operazioni. [Richieste in più passaggi](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr).

Per un’elaborazione lunga, l’estensione Tasks può restituire un identificativo del lavoro e consentire di seguirlo, se supportata da entrambe le parti. L’annullamento è cooperativo: chiudere la chat non ferma necessariamente un invio o una modifica già partita. [Estensione Tasks](https://modelcontextprotocol.io/extensions/tasks/overview).

### Metadati dei client e trasporto: cosa resta fuori dalle quattro annotazioni

Un’interfaccia grafica è facoltativa. Per collegarla al tool, la documentazione OpenAI usa anche metadati MCP Apps come `_meta.ui.resourceUri` e `_meta.ui.visibility`; alcune chiavi `openai/...` servono a compatibilità o funzioni del prodotto. Nessuna di queste chiavi aggiunge automaticamente permessi sui dati. [Riferimento delle integrazioni OpenAI](https://developers.openai.com/plugins/reference#_meta-fields-on-tool-descriptor).

Esistono inoltre metadati della richiesta, del descrittore e del risultato: la loro posizione cambia il significato. Non sposterei una chiave da un contenitore all’altro perché ha lo stesso nome. Per esempio, il flag Claude Code di interazione umana sta nel descrittore; i dati riservati alla UI seguono il contratto del risultato.

Per Streamable HTTP, la revisione `2026-07-28` permette di annotare un parametro con `x-mcp-header` per copiarne il valore in un header `Mcp-Param-...`. È utile, per esempio, a un livello di routing. I vincoli riguardano nomi, tipi e posizione nello schema; su questo trasporto una definizione non valida impone l’esclusione del tool. Gli header sono visibili agli intermediari: non usare questo meccanismo per trasmettere segreti. [Parametri negli header HTTP](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http#custom-headers-from-tool-parameters).

### Verificare che il tool funzioni nel client scelto

Prima dell’adozione proverei il percorso completo: comparsa in `tools/list`, chiamata valida, input malformato, credenziali mancanti, accesso a una scheda di un altro account, servizio indisponibile e ripetizione dopo un timeout. Per le scritture aggiungerei il rifiuto dell’approvazione e il controllo che nessun effetto sia avvenuto. Confronterei infine la scelta del tool da richieste realistiche nei client da supportare.

Conserva l’output di `tools/list` insieme alla versione del server. Dopo un aggiornamento confronta anche descrizioni, schemi e annotazioni: un tool chiamato ancora “ricerca” potrebbe aver cambiato il significato dei filtri. Riprovare la stessa domanda con un risultato atteso ti permette di accorgertene prima di riusarlo nel processo.

## Un risultato parziale deve dichiarare il proprio confine

Immagina un tool che legga soltanto la prima pagina di issue. Il campo `items` dovrebbe essere accompagnato da `has_more` e dal cursore successivo; un conteggio totale va incluso soltanto se il servizio lo conosce. Anche l’istante della lettura conta quando il progetto cambia.

Per valutare un’ottimizzazione confronterei risposte complete e filtrate sulla stessa domanda, controllando che conservino le informazioni necessarie. Registrerei definizioni effettivamente caricate, output, retry, token e tempo fino al risultato accettato, includendo il lavoro dello script esterno. È il confronto da eseguire prima di attribuire un risparmio a MCP: qui la prova eseguita riguarda il funzionamento della fixture.

La provenienza aiuta anche a separare contenuto e istruzioni: una issue o una pagina esterna può contenere testo che tenta di dirigere l’agente. Non gli attribuirei i permessi di una skill approvata. I controlli applicativi devono limitare gli accessi anche quando il modello interpreta male il contenuto.

## Quando aggiungere una skill

Il server dimostrativo sa restituire conteggi. Non stabilisce come preparare un aggiornamento di progetto, quali fonti confrontare o come segnalare una lacuna. Una skill può descrivere quella procedura.

La aggiungerei quando il workflow ricorre e contiene decisioni da rendere coerenti: identificare il progetto, leggere il riepilogo, controllare se la fonte basta e presentare il risultato con la provenienza. La skill può usare MCP insieme ad altri strumenti; non richiede necessariamente un server.

Terrei la descrizione abbastanza precisa da evitare che venga richiamata per qualsiasi domanda sulle issue. Il progetto completo e le differenze tra client sono sviluppati nell’articolo dedicato alle skills.

Skills over MCP è un’estensione opzionale pubblicata per distribuire istruzioni e file di supporto; va verificato il supporto dell’host prima di sceglierla come canale di distribuzione. Il percorso documentato da OpenAI per i plugin è specifico: `Scan Tools` importa uno snapshot, usando `skills/list`, `skills/get` e `resources/read` con un manifest completo e digest SHA-256. Dopo una modifica serve una nuova scansione e una nuova versione del plugin. Lo snapshot di submission non si aggiorna leggendo il server a ogni conversazione. [Skills over MCP](https://modelcontextprotocol.io/extensions/skills/overview), [importazione OpenAI](https://developers.openai.com/plugins/build/mcp-server#import-skills-from-the-mcp-server).

## Come contenere il consumo dei token con MCP

La prima opportunità è migliorare la domanda al server. Se servono cinque issue bloccate, chiedere tutte le issue e filtrarle nella conversazione trasferisce materiale che potrebbe essere escluso prima.

La seconda è progettare risposte con un confine chiaro: campi pertinenti, limiti, paginazione e indicazione di eventuali risultati omessi. Una sintesi deve preservare i dati necessari alla decisione. Il lavoro di Anthropic sui tool sottolinea l’importanza di interfacce comprensibili e valutazioni sul compito. [Writing effective tools for agents](https://www.anthropic.com/engineering/writing-tools-for-agents).

Conta infine come l’host carica gli strumenti. Claude Code documenta una ricerca differita dei tool, che evita di caricare subito tutte le definizioni; configurazione, modello e disponibilità possono cambiare questo percorso. Un server installato non implica quindi che ogni descrizione entri per intero in ogni richiesta. L’elaborazione esterna dei dati è un’altra possibilità: il codice può filtrare un risultato prima di restituirlo al modello. [Tool search di Claude Code](https://code.claude.com/docs/en/mcp#scale-with-mcp-tool-search), [code execution con MCP](https://www.anthropic.com/engineering/code-execution-with-mcp).

Il nostro tool restituisce quattro campi perché bastano alla domanda scelta. Se domani servisse capire quali issue siano bloccate e perché, questi quattro campi non sarebbero sufficienti. L’obiettivo è fornire il contesto necessario con meno passaggi inutili.

## Da dove partire nel proprio progetto

Scegli un’operazione di cui puoi verificare l’esito, limita l’input e rendi controllabile la provenienza dell’output. Verifica successo ed errori con un client prima di collegare il modello. Poi confronta lo stesso task con l’alternativa diretta e conta l’intero percorso fino al risultato.

Se MCP semplifica il riuso e rende più chiari i confini, hai una ragione per adottarlo. Se aggiunge un processo senza migliorare il lavoro, lo script iniziale potrebbe essere ancora la soluzione adatta.
