AI

MCP Server con Node.js: da un sistema di note su file a MySQL

Dario Fadda Maggio 12, 2026

Cos’è il Model Context Protocol e perché interessa ai developer Node.js

I modelli di linguaggio sono bravi a ragionare e conversare, ma da soli non possono eseguire operazioni reali sui tuoi sistemi. Possono suggerire query SQL o chiamate API, ma non possono farle girare concretamente. Il Model Context Protocol (MCP) risolve questo limite: fornisce ai modelli AI un modo strutturato per interagire con i tuoi strumenti, dai database ai file, fino alle API esterne. Invece di generare testo su ciò che dovrebbe accadere, il modello può invocare funzioni che lo fanno davvero accadere.

In pratica, questo apre la strada a strumenti come chatbot che creano e cercano voci nel database, assistenti AI che interrogano tool interni o attivano workflow, e agenti che leggono file, eseguono comandi e restituiscono risultati reali.

In questo tutorial imparerai a costruire il tuo primo MCP server da zero con Node.js e TypeScript: partiremo da un sistema di note basato su file per capire i concetti fondamentali, poi passeremo a un backend MySQL per mostrare come un LLM possa guidare operazioni deterministiche. Entro la fine avrai un server MCP funzionante pronto per essere collegato al client AI che preferisci.

Come funziona MCP

MCP segue un modello client-server: l’applicazione AI fa da client, il tuo codice gira come server. In una configurazione tipica, il client (Claude Desktop, Claude Code, Cursor, ecc.) si interpone tra l’utente e il tuo server, inoltrandogli le richieste e restituendogli i risultati. Il modello stesso non chiama mai direttamente il tuo server: quando l’utente manda un messaggio, il client condivide col modello la lista dei tool esposti dal tuo server. Il modello decide quale tool chiamare (e con quali argomenti), il client esegue la chiamata e rimanda il risultato al modello.

Utente → Client MCP → Modello AI → Tool selezionato → Server MCP → Risposta

Prerequisiti

Per seguire questo tutorial ti serviranno:

  • Node.js 18+
  • Familiarità di base con TypeScript
  • Un client compatibile con MCP per il test (Claude Desktop, Claude Code, Cursor, ecc.)
  • MySQL installato localmente (solo per la sezione avanzata con database)

Costruire il server MCP: sistema di note su file

Iniziamo creando un nuovo progetto Node.js. Questo sarà un sistema di note basato su file, utile per comprendere i concetti prima di introdurre un database.

mkdir mcp-notes && cd mcp-notes
npm init -y

Installa le dipendenze necessarie: l’SDK MCP per costruire il server, Zod per la validazione degli input e TypeScript per la type safety:

npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node

Apri il package.json e aggiungi "type": "module" (l’SDK MCP usa i moduli ES) e gli script di build e start:

{
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js"
  }
}

Crea un file tsconfig.json nella root del progetto:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

Scrivere il server

Crea il file src/index.ts con il codice base del server:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import fs from "fs/promises";
import path from "path";
import { z } from "zod";

const NOTES_DIR = path.join(process.cwd(), "notes");
await fs.mkdir(NOTES_DIR, { recursive: true });

const server = new McpServer({
  name: "mcp-notes",
  version: "1.0.0",
});

// I tool verranno aggiunti qui

const transport = new StdioServerTransport();
await server.connect(transport);

Questo è un MCP server completo e funzionante: crea la directory delle note, inizializza il server e lo connette tramite stdio per comunicare con un client MCP.

Aggiungere i tool

Ogni tool definisce una singola azione che il modello può compiere. Include nome, descrizione, schema di input e una funzione handler. Aggiungiamo i tre tool fondamentali: creazione, lettura e lista delle note.

server.tool(
  "create_note",
  "Create a new note with a given title and content",
  {
    title: z.string().min(1).describe("The note title"),
    content: z.string().min(1).describe("The body of the note"),
  },
  async ({ title, content }) => {
    const filename = `${title.replace(/[^a-z0-9_-]/gi, "_")}.txt`;
    const filepath = path.join(NOTES_DIR, filename);
    try {
      await fs.access(filepath);
      return {
        content: [{ type: "text", text: `Error: note "${title}" already exists.` }],
        isError: true,
      };
    } catch {}
    await fs.writeFile(filepath, content, "utf-8");
    return { content: [{ type: "text", text: `Note "${title}" created.` }] };
  }
);

server.tool(
  "read_note",
  "Read the content of a note by its title",
  { title: z.string().min(1).describe("The title of the note to read") },
  async ({ title }) => {
    const filename = `${title.replace(/[^a-z0-9_-]/gi, "_")}.txt`;
    try {
      const content = await fs.readFile(path.join(NOTES_DIR, filename), "utf-8");
      return { content: [{ type: "text", text: content }] };
    } catch {
      return {
        content: [{ type: "text", text: `Error: note "${title}" not found.` }],
        isError: true,
      };
    }
  }
);

server.tool(
  "list_notes",
  "List all available notes",
  {},
  async () => {
    const files = await fs.readdir(NOTES_DIR);
    const notes = files.filter(f => f.endsWith(".txt")).map(f => f.replace(".txt", ""));
    if (notes.length === 0) return { content: [{ type: "text", text: "No notes found." }] };
    return { content: [{ type: "text", text: notes.join("\n") }] };
  }
);

Testare il server con Claude Desktop

Compila il progetto con npm run build. Poi apri il file di configurazione di Claude Desktop:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Aggiungi il server sotto la chiave mcpServers:

{
  "mcpServers": {
    "mcp-notes": {
      "command": "node",
      "args": ["/percorso/del/progetto/mcp-notes/dist/index.js"],
      "cwd": "/percorso/del/progetto/mcp-notes"
    }
  }
}

Riavvia Claude Desktop e prova con prompt come: “Crea una nota chiamata standup con gli aggiornamenti di oggi” oppure “Elenca tutte le mie note”.

Passare a MySQL: dati strutturati per uso reale

Il sistema basato su file funziona bene per comprendere i fondamentali, ma ha limiti evidenti. Passare a MySQL mette in luce un pattern importante nel design MCP: il modello decide quale azione intraprendere, ma il tuo codice rimane responsabile di come quella azione viene eseguita. Quando il modello chiama search_notes, non genera né esegue SQL da solo: il tuo handler gestisce l’operazione in modo controllato, con query parametrizzate.

Installa il driver MySQL:

npm install mysql2

Crea il database e la tabella:

CREATE DATABASE mcp_notes;
USE mcp_notes;
CREATE TABLE notes (
  id INT AUTO_INCREMENT PRIMARY KEY,
  title VARCHAR(255) UNIQUE NOT NULL,
  content TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

La versione MySQL del tool create_note inserisce una riga invece di scrivere un file e gestisce i duplicati intercettando l’errore ER_DUP_ENTRY. La versione di search_notes permette ricerche full-text su titoli e contenuti — una funzionalità che con i file richiederebbe un codice molto più complesso.

server.tool(
  "search_notes",
  "Search notes by keyword across titles and content",
  { query: z.string().min(1).describe("Keyword or phrase to search for") },
  async ({ query }) => {
    const like = `%${query}%`;
    const [rows] = await pool.execute<mysql.RowDataPacket[]>(
      "SELECT title, created_at FROM notes WHERE title LIKE ? OR content LIKE ? ORDER BY created_at DESC",
      [like, like]
    );
    if (rows.length === 0) {
      return { content: [{ type: "text", text: `No notes found matching "${query}".` }] };
    }
    return { content: [{ type: "text", text: rows.map(r => `- ${r.title} (${r.created_at})`).join("\n") }] };
  }
);

Principi per progettare buoni tool MCP

Un tool MCP che funziona in fase di test può fallire in produzione se il modello fraintende quando o come usarlo. Alcune regole pratiche:

  • Descrizioni esplicite: frasi come “Gestisce le note” sono troppo vaghe. Usa descrizioni che spiegano chiaramente cosa fa il tool e quando va usato.
  • Singola responsabilità: ogni tool deve fare una sola cosa. Strumenti troppo “jolly” costringono il modello a indovinare l’intento.
  • Errori azionabili: usa isError: true con messaggi che guidano il modello su come riprovare: "Note non trovata. Usa list_notes per vedere quelle disponibili."
  • Boundary sicuri: mai interpolazione diretta dell’input utente in SQL o comandi shell. Usa sempre query parametrizzate.

Conclusione

Costruire un MCP server con Node.js e TypeScript è sorprendentemente accessibile grazie all’SDK ufficiale. Il pattern che hai imparato in questo tutorial — definire tool con schema Zod, gestire gli errori con isError e connettere il server via stdio — si scala facilmente a scenari più complessi: integrazione di API REST, automazione di workflow, connessione di agenti AI a sistemi legacy.

Il codice completo del tutorial è disponibile su GitHub.

Fonte: How to build your first MCP server with Node.js — LogRocket Blog (Elijah Asaolu, 5 maggio 2026)

💬 Unisciti alla discussione!


Questo è un blog del Fediverso: puoi trovare quindi questo articolo ovunque con @blog@spcnet.it e ogni commento/risposta apparirà qui sotto.

Se vuoi commentare su MCP Server con Node.js: da un sistema di note su file a MySQL, utilizza la discussione sul Forum.
Condividi la tua esperienza, confrontati con altri professionisti e approfondisci i dettagli tecnici nel nostro 👉 forum community