Salta al contenuto principale

13 - Testing, Best Practice e Produzione

Nei dodici articoli precedenti di questa serie abbiamo costruito un ecosistema MCP completo: server con tool, risorse, persistenza SQLite…

Cosa ne pensi di questo articolo?

In Breve

Nei dodici articoli precedenti di questa serie abbiamo costruito un ecosistema MCP completo: server con tool, risorse, persistenza SQLite…

Introduzione: Dalla Scrittura del Codice alla Produzione

Nei dodici articoli precedenti di questa serie abbiamo costruito un ecosistema MCP completo: server con tool, risorse, persistenza SQLite, comunicazione inter-server tramite EventBus e ClientManager. Ma un server MCP professionale non e completo senza una strategia di testing rigorosa e una preparazione adeguata per la produzione.

Testare un server MCP presenta sfide specifiche: i tool comunicano attraverso il protocollo JSON-RPC, gli eventi viaggiano tramite bus asincroni, e la comunicazione cross-server coinvolge trasporti in-memory. Il pacchetto @mcp-suite/testing del progetto Tech-MCP risolve queste complessità fornendo utility dedicate come TestHarness, InMemoryTransport e MockEventBus.

Cosa Imparerai in Questo Articolo

  • La piramide dei test MCP: unit, integration e wiring test
  • Come testare lo store in isolamento con database in-memory
  • Il pacchetto @mcp-suite/testing: TestHarness, InMemoryTransport, MockEventBus
  • Integration test end-to-end dei tool con createTestHarness()
  • Verifica degli eventi pubblicati con MockEventBus
  • Wiring test per la comunicazione cross-server
  • Best practice di sicurezza: validazione input, prepared statement, rate limiting
  • Performance: WAL mode SQLite, indici, transazioni batch
  • Deploy in produzione: logging strutturato, graceful shutdown, Docker

La Piramide dei Test MCP

Un server MCP professionale richiede tre livelli di testing, organizzati in una piramide che bilancia velocità di esecuzione e copertura funzionale. Alla base troviamo i test più veloci e numerosi, al vertice quelli più complessi ma meno frequenti:


                    /\
                   /  \
                  / W  \        Wiring Test
                 / I  R \       (cross-server con InMemoryTransport)
                /  I  I  \
               / N  N  G  \
              /────────────\
             /  INTEGRATION \   Tool Test
            / (test harness) \  (tool → store → risultato)
           /──────────────────\
          /                    \
         /      UNIT TEST       \  Store Test
        /    (store in-memory)   \ (logica pura, nessun MCP)
       /──────────────────────────\
    

I Tre Livelli di Test MCP

Livello Cosa Testa Velocita Setup Richiesto
Unit Metodi dello store (logica di persistenza) Velocissimo Solo store in-memory
Integration Tool end-to-end tramite protocollo MCP Veloce TestHarness + InMemoryTransport
Wiring Comunicazione cross-server reale Medio Più server + ClientManager

Unit Test dello Store

Il primo livello della piramide testa la logica di persistenza in isolamento, senza coinvolgere il protocollo MCP. Lo store viene istanziato con un database in-memory (inMemory: true), garantendo che ogni test parta da uno stato pulito e che l'esecuzione sia estremamente veloce.

Gli unit test dello store verificano tutte le operazioni CRUD (Create, Read, Update, Delete), i vincoli di integrita (UNIQUE constraints), i filtri, le ricerche e la serializzazione/deserializzazione dei dati JSON.


import { describe, it, expect, beforeEach } from "vitest";
import { NotesStore } from "../../src/services/notes-store.js";

describe("NotesStore", () => {
  let store: NotesStore;

  beforeEach(() => {
    // Ogni test ha un database fresco in-memory
    store = new NotesStore({ inMemory: true });
  });

  it("should add and retrieve a note", () => {
    const note = store.addNote({
      title: "Test",
      content: "Contenuto di test",
      tags: ["tag1", "tag2"],
    });

    expect(note.id).toBe(1);
    expect(note.title).toBe("Test");
    expect(note.tags).toEqual(["tag1", "tag2"]);

    const retrieved = store.getNote(1);
    expect(retrieved).toEqual(note);
  });

  it("should return undefined for non-existent note", () => {
    const note = store.getNote(999);
    expect(note).toBeUndefined();
  });

  it("should list notes ordered by updatedAt desc", () => {
    store.addNote({ title: "Prima", content: "A" });
    store.addNote({ title: "Seconda", content: "B" });
    store.addNote({ title: "Terza", content: "C" });

    const notes = store.listNotes();
    expect(notes).toHaveLength(3);
    expect(notes[0].title).toBe("Terza");
  });

  it("should handle UNIQUE constraint on title", () => {
    store.addNote({ title: "Unico", content: "A" });
    expect(() => store.addNote({ title: "Unico", content: "B" }))
      .toThrow();
  });

  it("should search notes by content", () => {
    store.addNote({ title: "JS", content: "Arrow functions e closures" });
    store.addNote({ title: "TS", content: "Tipi generici e interfacce" });

    const results = store.searchNotes("functions");
    expect(results).toHaveLength(1);
    expect(results[0].title).toBe("JS");
  });

  it("should delete a note and return true", () => {
    store.addNote({ title: "Da cancellare", content: "X" });
    expect(store.deleteNote(1)).toBe(true);
    expect(store.getNote(1)).toBeUndefined();
  });
});
    

Best Practice per Unit Test dello Store

  • Usa beforeEach con inMemory: true per garantire isolamento totale tra i test
  • Testa l'intero ciclo CRUD: create, read, update, delete
  • Verifica gli edge case: record inesistente, constraint violati, filtri vuoti
  • Testa la serializzazione/deserializzazione JSON (array di tag, oggetti annidati)
  • Non testare le query SQL direttamente: testa il comportamento osservabile dello store

Integration Test dei Tool con TestHarness

Il secondo livello della piramide testa i tool end-to-end attraverso il protocollo MCP. Il pacchetto @mcp-suite/testing fornisce la funzione createTestHarness() che crea una coppia client-server collegata in-memory, permettendo di invocare i tool esattamente come farebbe un client MCP reale.

Come Funziona createTestHarness()

La funzione crea un InMemoryTransport pair, collega il server e il client, e restituisce un oggetto TestHarness con il client pronto per invocare i tool:


export async function createTestHarness(server: McpServer): Promise<TestHarness> {
  const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
  const client = new Client({ name: "test-client", version: "1.0.0" });

  await server.connect(serverTransport);   // Server PRIMA
  await client.connect(clientTransport);    // Client DOPO

  return {
    client,
    close: async () => {
      await client.close();
      await server.close();
    },
  };
}
    

L'ordine di connessione e fondamentale: il server deve connettersi prima del client. Questo perchè il server deve essere pronto a gestire la negoziazione delle capabilities che il client avvia immediatamente dopo la connessione.

Test Completo di un Tool

Ecco un esempio completo che testa il tool add-note verificando il formato della risposta MCP, il contenuto del risultato e la gestione degli errori:


import { describe, it, expect, afterEach } from "vitest";
import { createTestHarness, MockEventBus, type TestHarness } from "@mcp-suite/testing";
import { createNotesServer } from "../../src/server.js";

describe("add-note tool", () => {
  let harness: TestHarness;

  afterEach(async () => {
    if (harness) await harness.close();
  });

  it("should add a note and return it as JSON", async () => {
    const { server } = createNotesServer({ storeOptions: { inMemory: true } });
    harness = await createTestHarness(server);

    const result = await harness.client.callTool({
      name: "add-note",
      arguments: {
        title: "Test Note",
        content: "Hello World",
        tags: ["test"],
      },
    });

    // Verifica formato risultato MCP
    const content = result.content as Array<{ type: string; text: string }>;
    expect(content[0].type).toBe("text");

    // Verifica contenuto
    const note = JSON.parse(content[0].text);
    expect(note.title).toBe("Test Note");
    expect(note.content).toBe("Hello World");
    expect(note.tags).toEqual(["test"]);
    expect(note.id).toBeDefined();
  });

  it("should return error for duplicate title", async () => {
    const { server } = createNotesServer({ storeOptions: { inMemory: true } });
    harness = await createTestHarness(server);

    await harness.client.callTool({
      name: "add-note",
      arguments: { title: "Duplicato", content: "Primo" },
    });

    const result = await harness.client.callTool({
      name: "add-note",
      arguments: { title: "Duplicato", content: "Secondo" },
    });

    expect(result.isError).toBe(true);
  });
});
    

MockEventBus: Verifica della Pubblicazione Eventi

Il MockEventBus e una implementazione di test dell'interfaccia EventBus che registra tutti gli eventi pubblicati senza propagarli. Fornisce due metodi chiave per le asserzioni:

  • wasPublished(eventName): verifica se un evento specifico e stato pubblicato
  • getPublishedEvents(eventName): restituisce l'array di tutti gli eventi pubblicati con quel nome, incluso il payload

it("should publish event when note is added", async () => {
  const eventBus = new MockEventBus();
  const { server } = createNotesServer({
    eventBus,
    storeOptions: { inMemory: true },
  });
  harness = await createTestHarness(server);

  await harness.client.callTool({
    name: "add-note",
    arguments: { title: "Evento", content: "Test" },
  });

  // Verifica che l'evento sia stato pubblicato
  expect(eventBus.wasPublished("notes:created")).toBe(true);

  // Verifica il payload dell'evento
  const events = eventBus.getPublishedEvents("notes:created");
  expect(events[0].payload).toMatchObject({ title: "Evento" });
});
    

Pattern di Test con MockEventBus

Il MockEventBus permette di verificare che i tool pubblichino gli eventi corretti senza bisogno di configurare un bus reale o sottoscrivere handler. Questo approccio isola il test del tool dalla logica di collaborazione, seguendo il principio di responsabilità singola anche nei test.

Wiring Test Cross-Server

Il terzo livello della piramide testa la comunicazione reale tra server MCP. I wiring test verificano che un server possa invocare tool su un altro server attraverso il McpClientManager, simulando uno scenario di produzione completo.


import { describe, it, expect, afterEach } from "vitest";
import { createTestHarness, type TestHarness } from "@mcp-suite/testing";
import { McpClientManager } from "@mcp-suite/client-manager";
import { createInsightEngineServer } from "../../src/server.js";
import { createAgileMetricsServer } from "../../../agile-metrics/src/server.js";

describe("insight-engine -> agile-metrics wiring", () => {
  let callerHarness: TestHarness;
  let clientManager: McpClientManager;

  afterEach(async () => {
    if (callerHarness) await callerHarness.close();
    if (clientManager) await clientManager.disconnectAll();
  });

  it("should fetch velocity from agile-metrics", async () => {
    // 1. Crea il server target (quello che viene chiamato)
    const targetSuite = createAgileMetricsServer({
      storeOptions: { inMemory: true },
    });

    // 2. Configura la connessione in-memory tramite ClientManager
    clientManager = new McpClientManager();
    const [ct, st] = McpClientManager.createInMemoryPair();
    await targetSuite.server.connect(st);
    await clientManager.connectInMemoryWithTransport("agile-metrics", ct);

    // 3. Crea il server caller (quello che chiama)
    const callerSuite = createInsightEngineServer({
      clientManager,
      storeOptions: { inMemory: true },
    });
    callerHarness = await createTestHarness(callerSuite.server);

    // 4. Invoca il tool che effettua chiamata cross-server
    const result = await callerHarness.client.callTool({
      name: "health-dashboard",
      arguments: {},
    });

    // 5. Verifica il risultato
    const content = result.content as Array<{ type: string; text: string }>;
    const dashboard = JSON.parse(content[0].text);
    expect(dashboard.dataSources["agile-metrics"]).toBe("available");
  });
});
    

Struttura di un Wiring Test

Ogni wiring test segue un flusso preciso in 5 fasi:

  1. Crea il server target in-memory: il server che verrà chiamato dal caller
  2. Configura il transport: crea un InMemoryTransport pair e collega il target al ClientManager
  3. Crea il server caller con il ClientManager: il server che effettua la chiamata cross-server
  4. Crea il TestHarness per il caller: permette di invocare i tool del caller come se fosse un client MCP
  5. Verifica il risultato: assicura che i dati del server target siano stati recuperati correttamente

Organizzazione dei File di Test

Nel progetto Tech-MCP, i file di test seguono una struttura standardizzata per ogni server:


servers/my-server/
  tests/
    services/
      my-store.test.ts           # Unit test dello store
    tools/
      add-item.test.ts           # Integration test del tool
      get-stats.test.ts          # Integration test del tool
      get-stats-wiring.test.ts   # Wiring test cross-server
    server.test.ts               # Test della factory (opzionale)
    

La configurazione di Vitest e minimale e condivisa tramite il workspace:


// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    globals: true,
    include: ["tests/**/*.test.ts"],
  },
});
    

Best Practice di Sicurezza

Portare un server MCP in produzione richiede attenzione alla sicurezza su più livelli. Ogni tool e un potenziale punto di ingresso per input malevoli, e ogni endpoint HTTP esposto necessità di protezione adeguata.

Validazione Input con Zod

L'SDK MCP gestisce automaticamente la validazione degli argomenti tramite gli schemi Zod dichiarati nella definizione del tool. Usare vincoli precisi e la prima linea di difesa:


import { z } from "zod";

server.tool(
  "create-project",
  "Crea un nuovo progetto",
  {
    name: z.string().min(1).max(100),
    budget: z.number().positive().optional(),
    priority: z.enum(["low", "medium", "high"]).default("medium"),
    tags: z.array(z.string()).max(10).optional(),
    startDate: z.string()
      .regex(/^\d{4}-\d{2}-\d{2}$/, "Formato: YYYY-MM-DD")
      .optional(),
  },
  async (args) => {
    // args e già validato e tipizzato da Zod
    // ...
  },
);
    

Prepared Statement e SQL Injection

Lo store non deve mai eseguire SQL costruito da stringhe concatenate. La libreria better-sqlite3 usa prepared statement per default, eliminando il rischio di SQL injection:


// CORRETTO: prepared statement con parametri posizionali
const stmt = this.db.prepare("SELECT * FROM items WHERE title = ?");
const item = stmt.get(title);

// SBAGLIATO: SQL injection vulnerabile
const item = this.db.exec(`SELECT * FROM items WHERE title = '${title}'`);
    

Autenticazione HTTP

Per server MCP esposti in rete tramite transport HTTP, proteggi l'endpoint con autenticazione Bearer token:


import express from "express";

const app = express();
const API_KEY = process.env.MCP_API_KEY;

app.use("/mcp", (req, res, next) => {
  if (!API_KEY) return next(); // Dev mode: nessuna autenticazione
  const auth = req.headers.authorization;
  if (auth !== `Bearer ${API_KEY}`) {
    return res.status(401).json({ error: "Non autorizzato" });
  }
  next();
});
    

Principio del Minimo Privilegio

  • Esponi solo le operazioni strettamente necessarie
  • I tool di lettura non devono poter scrivere
  • Separa tool pericolosi (delete, purge) da tool sicuri (list, get)
  • In produzione, considera tool separati per operazioni distruttive
  • Non esporre stack trace negli errori: solo messaggi leggibili per l'AI

Gestione degli Errori nei Tool

Ogni tool deve catturare le eccezioni e restituire errori strutturati nel formato MCP. Il flag isError: true segnala all'AI che l'operazione e fallita, permettendo al modello linguistico di decidere se riprovare o informare l'utente:


server.tool(
  "my-tool",
  "Descrizione del tool",
  { param: z.string() },
  async ({ param }) => {
    try {
      const result = store.doSomething(param);
      return {
        content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
      };
    } catch (error) {
      return {
        content: [{
          type: "text",
          text: `Errore: ${error instanceof Error ? error.message : String(error)}`,
        }],
        isError: true,
      };
    }
  },
);
    

Regole per la Gestione degli Errori

  • Mai lanciare eccezioni non gestite: il tool DEVE sempre ritornare un risultato
  • isError: true: segnala al modello AI che l'operazione e fallita
  • Messaggio leggibile: l'AI legge il messaggio di errore per decidere l'azione successiva
  • No stack trace in produzione: non esporre dettagli interni del server

Performance SQLite

La performance del database SQLite e fondamentale per un server MCP reattivo. Il progetto Tech-MCP adotta tre strategie principali per ottimizzare le performance.

WAL Mode (Write-Ahead Logging)

WAL mode migliora significativamente le performance in lettura concorrente, permettendo a più lettori di accedere al database simultaneamente senza bloccare le scritture:


constructor(options?: { inMemory?: boolean }) {
  this.db = new Database(
    options?.inMemory ? ":memory:" : "data/my-server.db"
  );
  this.db.pragma("journal_mode = WAL");
  this.db.pragma("foreign_keys = ON");
  this.migrate();
}
    

Transazioni per Operazioni Batch

Le transazioni migliorano le performance di ordini di grandezza per inserimenti multipli e garantiscono atomicita (tutto o niente):


addBulkItems(items: NewItem[]): Item[] {
  const insert = this.db.prepare(
    "INSERT INTO items (title, content) VALUES (?, ?)"
  );

  const addMany = this.db.transaction((items: NewItem[]) => {
    return items.map(item => {
      const result = insert.run(item.title, item.content);
      return { id: Number(result.lastInsertRowid), ...item };
    });
  });

  return addMany(items);
}
    

Indici Strategici

Aggiungi indici sulle colonne utilizzate frequentemente nei filtri e negli ordinamenti:


CREATE INDEX IF NOT EXISTS idx_items_created ON items(createdAt);
CREATE INDEX IF NOT EXISTS idx_items_status ON items(status);
    

Logging Strutturato per Produzione

MCP su STDIO usa stdout per i messaggi JSON-RPC. Il logging DEVE usare stderr per non corrompere il protocollo di comunicazione:


// CORRETTO: log su stderr (non interferisce con MCP)
console.error("[INFO] Server avviato sulla porta 3000");
console.error("[ERROR] Connessione database fallita:", error.message);

// SBAGLIATO: stdout corrompe il protocollo JSON-RPC
console.log("Server avviato");  // ROMPE MCP!
    

Logger Strutturato JSON

Per ambienti di produzione, un logger strutturato che produce output JSON su stderr facilità l'integrazione con sistemi di monitoraggio come ELK Stack o Datadog:


type LogLevel = "debug" | "info" | "warn" | "error";

function createLogger(name: string, level: LogLevel = "info") {
  const levels: Record<LogLevel, number> = {
    debug: 0, info: 1, warn: 2, error: 3,
  };
  const minLevel = levels[level];

  return {
    debug: (msg: string, data?: unknown) => {
      if (minLevel <= 0) console.error(JSON.stringify({
        level: "debug", server: name, msg, data,
        ts: new Date().toISOString()
      }));
    },
    info: (msg: string, data?: unknown) => {
      if (minLevel <= 1) console.error(JSON.stringify({
        level: "info", server: name, msg, data,
        ts: new Date().toISOString()
      }));
    },
    warn: (msg: string, data?: unknown) => {
      if (minLevel <= 2) console.error(JSON.stringify({
        level: "warn", server: name, msg, data,
        ts: new Date().toISOString()
      }));
    },
    error: (msg: string, data?: unknown) => {
      if (minLevel <= 3) console.error(JSON.stringify({
        level: "error", server: name, msg, data,
        ts: new Date().toISOString()
      }));
    },
  };
}

const logger = createLogger("my-server");
logger.info("Tool invocato", { tool: "add-item", args: { title: "Test" } });
    

Graceful Shutdown

In produzione, il server deve gestire i segnali di terminazione in modo pulito, chiudendo le connessioni e rilasciando le risorse prima di uscire:


// index.ts - Entry point del server

import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createMyServer } from "./server.js";

const { server } = createMyServer();
const transport = new StdioServerTransport();

await server.connect(transport);
console.error("[INFO] Server avviato");

// Gestione segnale SIGINT (Ctrl+C)
process.on("SIGINT", async () => {
  console.error("[INFO] Chiusura in corso...");
  await server.close();
  process.exit(0);
});

// Gestione segnale SIGTERM (container orchestration)
process.on("SIGTERM", async () => {
  console.error("[INFO] Terminazione in corso...");
  await server.close();
  process.exit(0);
});
    

Deploy con Docker

Per un deploy containerizzato, ecco un Dockerfile ottimizzato per un server MCP con build multi-stage e immagine finale minimale:


# Stage 1: Build
FROM node:20-alpine AS builder
WORKDIR /app

# Installa pnpm
RUN corepack enable && corepack prepare pnpm@latest --activate

# Copia solo i file necessari per le dipendenze
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY packages/ ./packages/
COPY servers/my-server/package.json ./servers/my-server/

# Installa le dipendenze
RUN pnpm install --frozen-lockfile

# Copia il codice sorgente
COPY servers/my-server/ ./servers/my-server/
COPY tsconfig.json ./

# Build
RUN pnpm --filter @mcp-suite/server-my-server build

# Stage 2: Produzione
FROM node:20-alpine AS production
WORKDIR /app

RUN corepack enable && corepack prepare pnpm@latest --activate

# Copia solo i file compilati e le dipendenze di produzione
COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml ./
COPY --from=builder /app/packages/ ./packages/
COPY --from=builder /app/servers/my-server/dist/ ./servers/my-server/dist/
COPY --from=builder /app/servers/my-server/package.json ./servers/my-server/

RUN pnpm install --frozen-lockfile --prod

# Directory per i database SQLite
RUN mkdir -p /app/data
VOLUME ["/app/data"]

# Variabili d'ambiente
ENV NODE_ENV=production

# Entry point
CMD ["node", "servers/my-server/dist/index.js"]
    

Configurazione Claude Desktop per la Produzione

Per utilizzare un server MCP con Claude Desktop, aggiungi la configurazione nel file claude_desktop_config.json:

Server STDIO Locale


{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["/percorso/al/server/dist/index.js"],
      "env": {
        "NODE_ENV": "production"
      }
    }
  }
}
    

Server HTTP Remoto


{
  "mcpServers": {
    "my-server": {
      "url": "http://localhost:3000/mcp",
      "headers": {
        "Authorization": "Bearer il-tuo-token"
      }
    }
  }
}
    

Checklist Pre-Produzione

Prima di deployare un server MCP, verifica sistematicamente ogni punto di questa checklist:

Checklist Completa per il Deploy

Area Requisito Verifica
Funzionalità Validazione Zod completa su tutti i tool Schemi con min(), max(), enum()
Funzionalità Gestione errori con try/catch e isError: true Nessuna eccezione non gestita
Funzionalità Test copertura happy path e casi di errore Unit + integration test passano
Sicurezza Nessun console.log su stdout Solo console.error per logging
Sicurezza Prepared statement per tutte le query SQL Nessuna concatenazione di stringhe SQL
Sicurezza Autenticazione su endpoint HTTP esposti Bearer token o API key
Performance WAL mode abilitato su SQLite pragma("journal_mode = WAL")
Performance Indici sulle colonne filtrate CREATE INDEX per colonne WHERE/ORDER BY
Deploy Graceful shutdown con SIGINT e SIGTERM Server chiude connessioni correttamente
Architettura Server factory con parametri opzionali eventBus, clientManager, storeOptions

Monorepo con Workspaces

Per progetti con più server MCP, un monorepo con pnpm workspaces e Turborepo semplifica la gestione delle dipendenze, la compilazione e il testing:


# pnpm-workspace.yaml
packages:
  - "packages/*"
  - "servers/*"
    

// turbo.json
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "test": {
      "dependsOn": ["build"]
    }
  }
}
    

La direttiva "dependsOn": ["^build"] indica a Turborepo di compilare prima le dipendenze e poi il pacchetto corrente. Il build system risolve automaticamente l'ordine e parallelizza le compilazioni indipendenti, riducendo significativamente i tempi di build.

Percorso Evolutivo: Da Zero a Produzione

In questa serie abbiamo percorso un cammino completo attraverso quattro livelli di complessità crescente:


  Livello 1 (Base)
  ─────────────────────────────────
  Tool in-memory + STDIO
  Client singolo + Validazione Zod

         |
         v

  Livello 2 (Intermedio)
  ─────────────────────────────────
  Resources + Prompts
  HTTP transport + SQLite store
  Migrazioni versionabili

         |
         v

  Livello 3 (Avanzato)
  ─────────────────────────────────
  EventBus (fire-and-forget)
  ClientManager (cross-server)
  Collaboration handlers

         |
         v

  Livello 4 (Produzione)
  ─────────────────────────────────
  Piramide dei test completa
  Logging strutturato
  Sicurezza + Performance
  Monorepo + CI/CD + Docker
    

Conclusioni

Il testing e la preparazione alla produzione sono fasi fondamentali nello sviluppo di server MCP professionali. La piramide dei test -- unit, integration e wiring -- garantisce copertura a ogni livello dell'architettura, dalla logica di persistenza alla comunicazione cross-server.

Il pacchetto @mcp-suite/testing con TestHarness, InMemoryTransport e MockEventBus semplifica enormemente il setup dei test, permettendo di verificare il comportamento dei tool attraverso il protocollo MCP reale senza dipendenze esterne.

Le best practice di sicurezza (validazione Zod, prepared statement, autenticazione), performance (WAL mode, indici, transazioni) e deploy (logging strutturato, graceful shutdown, Docker) completano il percorso verso un server MCP production-ready.

Nel prossimo e ultimo articolo della serie presenteremo una panoramica completa del progetto Tech-MCP: il catalogo di tutti i 31 server, le statistiche del progetto, le lezioni apprese e come contribuire.

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