Salta al contenuto principale

02 - Primitive MCP: Tools, Resources e Prompts

Nel primo articolo di questa serie abbiamo introdotto il Model Context Protocol (MCP) e la sua architettura Host/Client/Server.

Cosa ne pensi di questo articolo?

02 - Primitive MCP: Tools, Resources e Prompts
MCPFoto:Brett SaylesPexels

In Breve

Nel primo articolo di questa serie abbiamo introdotto il Model Context Protocol (MCP) e la sua architettura Host/Client/Server.

Panoramica: Le Tre Primitive del Protocollo MCP

Nel primo articolo di questa serie abbiamo introdotto il Model Context Protocol (MCP) e la sua architettura Host/Client/Server. Ora e il momento di analizzare nel dettaglio le tre primitive che un server MCP può esporre: Tools, Resources e Prompts.

Ogni primitiva ha un ruolo distinto nell'interazione tra AI e mondo esterno. Comprendere le differenze, i casi d'uso e la struttura interna di ciascuna e fondamentale per progettare server MCP efficaci e ben strutturati. In questo articolo vedremo la teoria, gli schemi di validazione con Zod, i payload JSON-RPC e gli handler TypeScript, analizzando esempi concreti dal progetto Tech-MCP.

Cosa Imparerai in Questo Articolo

  • La differenza tra Tools, Resources e Prompts: ruolo, controllo e casi d'uso
  • Come definire un Tool con validazione Zod e handler asincrono
  • Come esporre Resources con URI template e dati read-only
  • Come creare Prompts parametrizzati e riutilizzabili
  • Il formato dei payload JSON-RPC per ogni primitiva
  • Il ciclo di vita di una sessione MCP dalla discovery alla chiusura
  • Best practice per le descrizioni dei tool e la naming convention

Le Tre Primitive a Confronto

Prima di entrare nel dettaglio di ciascuna primitiva, e utile avere una visione d'insieme delle loro differenze fondamentali. Ogni primitiva risponde a un'esigenza specifica nell'ecosistema MCP:

Riepilogo delle Primitive MCP

Primitiva Ruolo Chi Controlla Effetti Collaterali Esempio
Tools Funzioni invocabili dall'AI L'AI decide autonomamente Si (possono modificare stato) create-sprint, analyze-diff
Resources Dati accessibili in lettura L'applicazione host No (read-only) sprint://{id}, file:///config
Prompts Template predefiniti riutilizzabili L'utente seleziona No (generano messaggi) sprint-review, code-analysis

Questa distinzione e importante: i Tools sono il cuore operativo del protocollo, le Resources forniscono contesto e dati, i Prompts guidano l'AI in task strutturati. Vediamo ciascuna primitiva nel dettaglio.

1. Tools: Funzioni che l'AI Può Invocare

I Tools rappresentano il cuore dell'interazione MCP. Sono funzioni che l'AI può chiamare autonomamente durante una conversazione per eseguire azioni concrete nel mondo reale. A differenza di una semplice chiamata API, un tool MCP dichiara esplicitamente i suoi parametri con uno schema tipizzato, permettendo all'AI di capire quando e come invocarlo.

Caratteristiche Fondamentali dei Tools

  • L'AI decide quando chiamarli: basandosi sul contesto della conversazione, il modello linguistico sceglie autonomamente quale tool invocare e con quali parametri
  • Parametri tipizzati con Zod: ogni tool dichiara i suoi input con uno schema Zod, garantendo validazione automatica a runtime
  • Risultati strutturati: il risultato e sempre un array di content con tipo text, image o resource
  • Possono avere effetti collaterali: creare file, scrivere nel database, chiamare API esterne, inviare notifiche
  • Composabili: l'AI può combinare più tool in sequenza per completare task complessi

Anatomia di un Tool in TypeScript

La registrazione di un tool segue una struttura precisa con quattro elementi: nome univoco, descrizione per l'AI, schema dei parametri Zod e handler asincrono. Ecco un esempio completo dal progetto Tech-MCP:


import { z } from 'zod';

server.tool(
  'create-sprint',                    // Nome univoco del tool
  'Create a new sprint with a name, date range, and goals. ' +
  'Returns the created sprint object with id, status, and dates. ' +
  'Use this when the user wants to start planning a new iteration.',
  {                                   // Schema parametri (Zod)
    name: z.string().describe('Sprint name, e.g. Sprint-15'),
    startDate: z.string().describe('Start date in ISO format'),
    endDate: z.string().describe('End date in ISO format'),
    goals: z.array(z.string()).describe('List of sprint goals'),
  },
  async ({ name, startDate, endDate, goals }) => {  // Handler
    const sprint = store.createSprint({
      name,
      startDate,
      endDate,
      goals
    });
    return {
      content: [{
        type: 'text',
        text: JSON.stringify(sprint, null, 2)
      }]
    };
  }
);
    

perchè Zod per la Validazione?

Zod e una libreria di validazione TypeScript-first che offre type-safety completa a compile-time e validazione a runtime. L'SDK MCP converte automaticamente gli schemi Zod in JSON Schema, il formato che l'AI usa per comprendere i parametri di un tool. Questo significa che dichiarando uno schema Zod si ottiene: validazione automatica degli input, generazione della documentazione per l'AI e type inference in TypeScript, tutto in un'unica definizione.

Il Formato della Risposta di un Tool

Ogni tool handler deve ritornare un oggetto con un array content. Ogni elemento dell'array ha un type che indica il formato del contenuto:


// Risposta testuale (più comune)
return {
  content: [{
    type: 'text',
    text: 'Sprint creato con successo: Sprint-15 (ID: 42)'
  }]
};

// Risposta con immagine
return {
  content: [{
    type: 'image',
    data: base64EncodedImage,
    mimeType: 'image/png'
  }]
};

// Risposta con errore
return {
  content: [{
    type: 'text',
    text: 'Errore: sprint non trovato'
  }],
  isError: true
};
    

Categorie di Tool nel Progetto Tech-MCP

Nel progetto Tech-MCP, gli 85+ tool sono organizzati in categorie funzionali in base al tipo di operazione che eseguono:

Classificazione dei Tool per Tipo di Operazione

Categoria Esempio Tool Effetti Descrizione
Creazione create-sprint, save-snippet Scrivono nel database Creano nuove entità persistenti
Lettura get-sprint, search-snippets Solo lettura Recuperano dati esistenti
Analisi analyze-diff, find-bottlenecks Nessun side effect Elaborano input e generano insight
Generazione generate-unit-tests, generate-compose Producono output Generano codice, configurazioni, template
Monitoraggio list-pipelines, get-budget-status Leggono stato esterno Verificano lo stato di sistemi esterni

2. Resources: Dati Accessibili in Lettura

Le Resources sono la seconda primitiva MCP. Rappresentano dati che l'applicazione host può leggere e fornire come contesto all'AI. A differenza dei Tool, le Resources non vengono chiamate direttamente dall'AI: e l'applicazione host (Claude Desktop, Cursor, ecc.) che decide quando e quali risorse caricare nel contesto della conversazione.

Caratteristiche Fondamentali delle Resources

  • Identificate da URI: ogni resource ha un identificatore univoco, come file:///path/to/file, db://schema/table o sprint://{id}
  • L'applicazione le richiede: a differenza dei tool dove l'AI decide, per le resources e l'applicazione host a controllare l'accesso
  • Read-only: le resources non modificano lo stato del server, servono esclusivamente a fornire dati
  • Supportano URI Template: per risorse parametriche si possono definire template come sprint://{id}
  • MIME type esplicito: ogni resource dichiara il tipo di contenuto (application/json, text/plain, ecc.)

Definizione di una Resource con URI Template

Ecco come si definisce una resource parametrica che espone i dettagli di uno sprint identificato tramite il suo ID:


server.resource(
  'sprint://{id}',                     // URI template
  'Get sprint details by ID',         // Descrizione
  async (uri) => {                    // Handler
    const id = extractIdFromUri(uri);
    const sprint = store.getSprint(id);

    if (!sprint) {
      throw new Error(`Sprint ${id} not found`);
    }

    return {
      contents: [{
        uri: uri.href,
        mimeType: 'application/json',
        text: JSON.stringify(sprint, null, 2)
      }]
    };
  }
);
    

Resource Statiche vs Dinamiche

Le resources MCP si dividono in due categorie: statiche e dinamiche. Le resources statiche hanno un URI fisso e restituiscono sempre dati dallo stesso endpoint. Le resources dinamiche usano URI template per accettare parametri.


// Resource statica: URI fisso, dati globali
server.resource(
  'config://app-settings',
  'Application configuration settings',
  async (uri) => {
    return {
      contents: [{
        uri: uri.href,
        mimeType: 'application/json',
        text: JSON.stringify(config.getAll())
      }]
    };
  }
);

// Resource dinamica: URI template con parametro
server.resource(
  'user://{userId}/profile',
  'User profile data',
  async (uri) => {
    const userId = extractParam(uri, 'userId');
    const profile = await db.getProfile(userId);
    return {
      contents: [{
        uri: uri.href,
        mimeType: 'application/json',
        text: JSON.stringify(profile)
      }]
    };
  }
);
    

Il Payload JSON-RPC per le Resources

Quando l'applicazione host richiede una resource, il protocollo MCP usa due metodi JSON-RPC: resources/list per scoprire le risorse disponibili e resources/read per leggerne il contenuto.


// Richiesta: elenco delle resources disponibili
{ "method": "resources/list" }

// Risposta del server
{
  "resources": [
    {
      "uri": "config://app-settings",
      "name": "App Settings",
      "mimeType": "application/json"
    }
  ],
  "resourceTemplates": [
    {
      "uriTemplate": "sprint://{id}",
      "name": "Sprint Details",
      "mimeType": "application/json"
    }
  ]
}

// Richiesta: lettura di una resource specifica
{ "method": "resources/read",
  "params": { "uri": "sprint://42" } }

// Risposta del server
{
  "contents": [{
    "uri": "sprint://42",
    "mimeType": "application/json",
    "text": "{ \"id\": 42, \"name\": \"Sprint-15\" }"
  }]
}
    

Nota sul Progetto Tech-MCP

Nel progetto Tech-MCP, l'interazione con i dati avviene principalmente attraverso i Tools. Le Resources sono previste per sviluppi futuri, ad esempio per esporre configurazioni di progetto, schemi di database o documentazione API come contesto read-only per l'AI.

3. Prompts: Template Predefiniti Riutilizzabili

I Prompts sono la terza primitiva MCP. Rappresentano template predefiniti che guidano l'AI nell'esecuzione di task complessi e strutturati. A differenza dei tool (dove l'AI decide) e delle resources (dove l'applicazione decide), i prompts sono selezionati dall'utente per avviare flussi di lavoro specifici.

Caratteristiche Fondamentali dei Prompts

  • Guidano l'AI: forniscono istruzioni strutturate che indirizzano il modello linguistico verso un obiettivo specifico
  • Accettano argomenti: possono essere parametrizzati con input dell'utente
  • Combinabili con i tool: un prompt può suggerire all'AI una sequenza di tool da chiamare in ordine
  • Riutilizzabili: una volta definiti, possono essere usati in conversazioni diverse con parametri diversi
  • Selezionati dall'utente: l'utente sceglie quale prompt attivare, non l'AI

Definizione di un Prompt con Argomenti

Un prompt MCP si definisce con un nome, una descrizione, una lista di argomenti e una funzione che genera i messaggi da inviare all'AI:


server.prompt(
  'sprint-review',                              // Nome univoco
  'Generate a comprehensive sprint review report', // Descrizione
  [                                              // Argomenti
    {
      name: 'sprintId',
      description: 'ID of the sprint to review',
      required: true
    },
    {
      name: 'format',
      description: 'Output format: summary, detailed, or metrics-only',
      required: false
    }
  ],
  async ({ sprintId, format }) => ({            // Handler
    messages: [{
      role: 'user',
      content: {
        type: 'text',
        text: `Analizza lo sprint ${sprintId} con i seguenti passaggi:
1. Usa il tool get-sprint per recuperare i dati dello sprint
2. Usa calculate-velocity per calcolare la velocity del team
3. Usa get-sprint-stories per l'elenco delle storie completate
4. Genera un report in formato ${format || 'detailed'} con:
   - Obiettivi raggiunti vs pianificati
   - Velocity e trend rispetto agli sprint precedenti
   - Storie completate e non completate
   - Raccomandazioni per il prossimo sprint`
      }
    }]
  }))
);
    

Il Payload JSON-RPC per i Prompts

Come per le altre primitive, i prompts utilizzano metodi JSON-RPC dedicati per discovery e invocazione:


// Discovery: elenco dei prompts disponibili
{ "method": "prompts/list" }

// Risposta del server
{
  "prompts": [
    {
      "name": "sprint-review",
      "description": "Generate a comprehensive sprint review report",
      "arguments": [
        {
          "name": "sprintId",
          "description": "ID of the sprint to review",
          "required": true
        },
        {
          "name": "format",
          "description": "Output format: summary, detailed, or metrics-only",
          "required": false
        }
      ]
    }
  ]
}

// Invocazione di un prompt
{ "method": "prompts/get",
  "params": {
    "name": "sprint-review",
    "arguments": { "sprintId": "42", "format": "detailed" }
  }
}

// Risposta: messaggi generati dal prompt
{
  "messages": [{
    "role": "user",
    "content": {
      "type": "text",
      "text": "Analizza lo sprint 42 con i seguenti passaggi..."
    }
  }]
}
    

Prompt che Orchestrano Tool

Il vero potere dei prompts emerge quando vengono usati per orchestrare sequenze complesse di tool. Un prompt ben progettato può guidare l'AI attraverso un flusso di lavoro multi-step:


server.prompt(
  'full-code-review',
  'Perform a comprehensive code review workflow',
  [
    { name: 'filePath', description: 'Path to the file to review', required: true },
    { name: 'language', description: 'Programming language', required: true }
  ],
  async ({ filePath, language }) => ({
    messages: [{
      role: 'user',
      content: {
        type: 'text',
        text: `Esegui una code review completa del file ${filePath} (${language}):

STEP 1 - Analisi statica:
  Usa analyze-diff per identificare problemi nel codice

STEP 2 - Generazione test:
  Usa generate-unit-tests per creare test per le funzioni trovate

STEP 3 - Verifica stile:
  Controlla che il codice segua le convenzioni del progetto

STEP 4 - Report finale:
  Genera un report con: problemi trovati, test suggeriti,
  miglioramenti proposti, severity di ogni issue`
      }
    }]
  }))
);
    

Nota: Prompts nel Progetto Tech-MCP

Come per le Resources, il progetto Tech-MCP utilizza attualmente i Tools come primitiva principale. I Prompts sono previsti per sviluppi futuri, con l'obiettivo di creare workflow predefiniti per scenari ricorrenti come sprint review, onboarding di nuovi sviluppatori e analisi di performance del team.

Come l'AI Sceglie i Tool: Il Ruolo delle Descrizioni

Quando un server MCP si registra con un client, espone la lista dei tool disponibili tramite il metodo tools/list. Per ogni tool, l'AI riceve tre informazioni chiave:

  1. Nome: identificativo univoco (es. create-sprint)
  2. Descrizione: testo in linguaggio naturale che spiega cosa fa il tool
  3. Schema parametri: struttura dei parametri di input con tipi e descrizioni

La descrizione e l'elemento più critico: una descrizione ben scritta permette all'AI di capire quando usare il tool, cosa aspettarsi come risultato e in quali scenari e appropriato invocarlo.

Best Practice per le Descrizioni dei Tool


OTTIMO (spiega cosa fa, cosa ritorna, quando usarlo):
"Create a new sprint with a name, date range, and goals.
 Returns the created sprint object with id, name, dates, status.
 Use this when the user wants to start planning a new iteration."

BUONO (spiega cosa fa e gli input):
"Create a new sprint with a name, date range, and goals"

VAGO (l'AI potrebbe non capire quando usarlo):
"Sprint creation tool"
    

Checklist per Descrizioni Efficaci

Elemento Domanda Esempio
Azione Cosa fa il tool? "Create a new sprint..."
Input Quali parametri servono? "...with a name, date range, and goals"
Output Cosa ritorna? "Returns the created sprint object with id..."
Contesto Quando usarlo? "Use when the user wants to plan a new iteration"

Il Ciclo di Vita di una Sessione MCP

Ogni interazione MCP segue un ciclo di vita strutturato in quattro fasi. Comprendere questo flusso e essenziale per progettare server che si comportano correttamente in ogni scenario:


[1] INIZIALIZZAZIONE
    Client --> Server: initialize (versione protocollo, capabilities)
    Server --> Client: capabilities (tool list, resource templates, prompts)

[2] DISCOVERY
    Client --> Server: tools/list
    Server --> Client: [{ name, description, inputSchema }, ...]

    Client --> Server: resources/list
    Server --> Client: [{ uri, name, mimeType }, ...]

    Client --> Server: prompts/list
    Server --> Client: [{ name, description, arguments }, ...]

[3] UTILIZZO (ripetuto N volte durante la sessione)
    Client --> Server: tools/call { name, arguments }
    Server --> Client: { content: [...] }

    Client --> Server: resources/read { uri }
    Server --> Client: { contents: [...] }

    Client --> Server: prompts/get { name, arguments }
    Server --> Client: { messages: [...] }

[4] CHIUSURA
    Client --> Server: close
    

Dettaglio delle Fasi

  1. Inizializzazione: il client invia la versione del protocollo e le proprie capabilities. Il server risponde dichiarando quali primitive supporta (tools, resources, prompts) e le proprie capabilities (notifiche, logging, ecc.).
  2. Discovery: il client interroga il server per scoprire tutte le primitive disponibili. Questa fase e fondamentale perchè fornisce all'AI le informazioni necessarie per decidere quali tool utilizzare.
  3. Utilizzo: la fase operativa, ripetuta N volte durante la sessione. Il client può invocare tool, leggere resources e attivare prompts in qualsiasi ordine e combinazione.
  4. Chiusura: il client segnala la fine della sessione. Il server rilascia le risorse e chiude le connessioni.

Validazione dei Parametri con Zod: Esempi Avanzati

Zod offre una vasta gamma di validatori che permettono di definire schemi precisi per i parametri dei tool. Ecco alcuni pattern avanzati utilizzati nel progetto Tech-MCP:


import { z } from 'zod';

// Schema con enum e valori opzionali
const createTaskSchema = {
  title: z.string().min(1).max(200)
    .describe('Task title'),
  priority: z.enum(['low', 'medium', 'high', 'critical'])
    .describe('Task priority level'),
  assignee: z.string().optional()
    .describe('Username of the assignee'),
  labels: z.array(z.string()).default([])
    .describe('List of labels to apply'),
  estimate: z.number().positive().optional()
    .describe('Estimated hours to complete'),
};

// Schema con oggetti nested
const deployConfigSchema = {
  environment: z.enum(['staging', 'production']),
  version: z.string().regex(/^\d+\.\d+\.\d+$/),
  options: z.object({
    rollback: z.boolean().default(true),
    healthCheck: z.boolean().default(true),
    notifySlack: z.boolean().default(false),
  }).optional(),
};

// Schema con union types
const searchSchema = {
  query: z.string().min(1),
  scope: z.union([
    z.literal('code'),
    z.literal('docs'),
    z.literal('issues'),
    z.literal('all'),
  ]).default('all'),
  limit: z.number().int().min(1).max(100).default(20),
};
    

Interazione tra le Primitive: Un Esempio Completo

Le tre primitive MCP non operano in isolamento. In uno scenario reale, un flusso di lavoro può combinare tutte e tre le primitive in modo sinergico:

  1. L'utente seleziona un Prompt: "Sprint Review" con parametro sprintId: 42
  2. Il Prompt genera istruzioni: il messaggio suggerisce all'AI di usare specifici tool
  3. L'AI chiama i Tool: invoca get-sprint, calculate-velocity, get-sprint-stories
  4. L'applicazione carica Resources: aggiunge come contesto config://team-settings
  5. L'AI genera il report: combina tutti i dati raccolti in un report strutturato

Questa composabilità e uno dei punti di forza del protocollo MCP: ogni primitiva contribuisce con il suo ruolo specifico a flussi di lavoro complessi e articolati.

Conclusioni

Le tre primitive MCP -- Tools, Resources e Prompts -- costituiscono il vocabolario fondamentale del protocollo. Ogni primitiva ha un ruolo distinto: i Tool eseguono azioni, le Resources forniscono dati, i Prompts guidano l'AI in task strutturati.

La validazione con Zod garantisce type-safety e documentazione automatica, mentre il ciclo di vita della sessione MCP assicura un'interazione ordinata e prevedibile tra client e server. La composabilità tra le primitive permette di costruire flussi di lavoro complessi partendo da building block semplici e ben definiti.

Nel prossimo articolo approfondiremo l'architettura monorepo e i pattern di progetto utilizzati in Tech-MCP: come organizzare 31 server MCP in un singolo repository, gestire le dipendenze condivise con Turborepo e pnpm, e strutturare il codice per massima riutilizzabilita e manutenibilità.

Il codice completo di tutti gli esempi e disponibile nel repository Tech-MCP su GitHub.

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti