Salta al contenuto principale

03 - Architettura Monorepo e Pattern di Progetto

Organizzare un progetto MCP con monorepo Turborepo, pnpm workspaces, pacchetti condivisi e pattern architetturali per gestire 30+ server.

Cosa ne pensi di questo articolo?

In Breve

Organizzare un progetto MCP con monorepo Turborepo, pnpm workspaces, pacchetti condivisi e pattern architetturali per gestire 30+ server.

Introduzione: Organizzare un Progetto MCP su Larga Scala

Quando si sviluppa un singolo server MCP, la struttura del progetto e semplice: un package.json, qualche file TypeScript, e si e pronti a partire. Ma quando il numero di server cresce, la complessità organizzativa esplode. Come condividere codice comune senza pubblicare su npm? Come garantire che tutti i server si compilino nell'ordine corretto? Come mantenere coerenza nella struttura e nei pattern?

La risposta a queste domande e il monorepo: un singolo repository che contiene tutti i server e i pacchetti condivisi. In questo articolo analizziamo l'architettura monorepo del progetto Tech-MCP, le scelte tecnologiche alla base (TypeScript, SQLite, STDIO/HTTP) e il pattern a 4 strati che ogni server segue per garantire uniformita e manutenibilità.

Cosa Imparerai in Questo Articolo

  • Come strutturare un monorepo MCP con pnpm workspaces e Turborepo
  • La directory structure completa con packages/ e servers/
  • perchè TypeScript, SQLite e STDIO/HTTP sono le scelte ottimali per server MCP
  • Il pattern architetturale a 4 strati: index.ts, server.ts, tools/, services/
  • L'interfaccia McpSuiteServer e la factory createMcpServer()
  • La pipeline di build con Turborepo e il grafo delle dipendenze

perchè un Monorepo per Server MCP?

Un monorepo raccoglie tutti i componenti di un progetto in un unico repository Git. Per una suite di server MCP, i vantaggi sono significativi:

  • Condivisione codice senza pubblicazione: i pacchetti in packages/ sono disponibili a tutti i server tramite il protocollo workspace: di pnpm, senza bisogno di pubblicarli su npm
  • Build atomiche e ordinate: Turborepo analizza il grafo delle dipendenze e compila i pacchetti nell'ordine corretto, parallelizzando dove possibile
  • Versioning unificato: tutti i pacchetti e server si evolvono insieme, eliminando problemi di compatibilità tra versioni
  • Developer Experience superiore: un singolo pnpm install e un singolo pnpm build per l'intero progetto
  • Refactoring sicuro: modificare un'interfaccia in @mcp-suite/core evidenzia immediatamente tutti i server che devono essere aggiornati

Struttura Completa del Monorepo

Il progetto Tech-MCP e organizzato in tre directory principali: la radice con i file di configurazione, packages/ per le librerie condivise e servers/ per i server MCP indipendenti.


mcp-suite/
├── package.json              # Root: script globali, engine constraints
├── pnpm-workspace.yaml       # Definisce packages/* e servers/* come workspace
├── turbo.json                # Pipeline di build con Turborepo
├── tsconfig.base.json        # Configurazione TypeScript condivisa
│
├── packages/                 # Librerie condivise (6 pacchetti)
│   ├── core/                 #   Factory server, config, logger, errori, tipi
│   ├── event-bus/            #   EventBus tipizzato con 29 eventi
│   ├── database/             #   Connessione SQLite + migrazioni
│   ├── testing/              #   Harness di test + MockEventBus
│   ├── cli/                  #   CLI per gestire i server
│   └── client-manager/       #   Pool di client MCP per comunicazione server-to-server
│
├── servers/                  # 22 MCP server indipendenti
│   ├── scrum-board/          #   Gestione sprint, storie, task
│   ├── standup-notes/        #   Note per gli standup giornalieri
│   ├── time-tracking/        #   Tracciamento tempo
│   ├── agile-metrics/        #   Metriche agili (velocity, burndown)
│   ├── code-review/          #   Analisi e review del codice
│   ├── test-generator/       #   Generazione test automatici
│   ├── cicd-monitor/         #   Monitoraggio pipeline CI/CD
│   ├── docker-compose/       #   Gestione Docker Compose
│   ├── db-schema-explorer/   #   Esplorazione schemi database
│   ├── dependency-manager/   #   Gestione dipendenze progetto
│   ├── api-documentation/    #   Documentazione API
│   ├── codebase-knowledge/   #   Knowledge base del codice
│   ├── data-mock-generator/  #   Generazione dati mock
│   ├── environment-manager/  #   Gestione variabili d'ambiente
│   ├── http-client/          #   Client HTTP per test API
│   ├── log-analyzer/         #   Analisi log applicativi
│   ├── performance-profiler/ #   Profilazione performance
│   ├── project-economics/    #   Economia di progetto (budget, costi)
│   ├── project-scaffolding/  #   Scaffolding nuovi progetti
│   ├── regex-builder/        #   Costruzione espressioni regolari
│   ├── retrospective-manager/#   Gestione retrospettive agili
│   └── snippet-manager/      #   Libreria di snippet di codice
│
└── docs/                     # Documentazione del progetto
    

Questa struttura realizza il principio di indipendenza dei server: ogni server e una cartella autonoma con il proprio package.json, tsconfig.json e codice sorgente. Allo stesso tempo, la collaborazione opzionale e garantita dai pacchetti condivisi in packages/.

I 6 Pacchetti Condivisi

I pacchetti in packages/ forniscono le fondamenta comuni a tutti i server:

Pacchetti Condivisi e le Loro Responsabilità

Pacchetto Responsabilità Dipendenze
@mcp-suite/core Factory server, configurazione, logger, errori, tipi condivisi event-bus
@mcp-suite/event-bus EventBus tipizzato con 29 eventi e pattern pub/sub Nessuna
@mcp-suite/database Connessione SQLite con WAL mode e sistema di migrazioni core
@mcp-suite/testing Harness di test e MockEventBus per unit testing core, event-bus
@mcp-suite/cli CLI per gestire, avviare e monitorare i server core, event-bus, client-manager
@mcp-suite/client-manager Pool di client MCP per comunicazione server-to-server core

Decisioni Progettuali: perchè TypeScript, SQLite e STDIO

Le tecnologie alla base del progetto non sono state scelte casualmente. Ogni decisione risponde a requisiti specifici di un sistema MCP distribuito.

perchè TypeScript?

TypeScript e il linguaggio ideale per server MCP per quattro ragioni fondamentali:

  • Type-safety end-to-end: i tipi definiti in @mcp-suite/core sono condivisi tra tutti i server, garantendo coerenza a compile-time. Un cambio di interfaccia si propaga immediatamente come errore in tutti i server che la utilizzano
  • ESM nativo: target ES2022 con "module": "Node16" per compatibilità nativa con Node.js moderno, senza bisogno di bundler o transpiler aggiuntivi
  • Declaration maps: ogni pacchetto genera .d.ts e .d.ts.map, permettendo il "Go to Definition" attraverso tutto il monorepo direttamente nell'IDE
  • Strict mode: "strict": true in tsconfig.base.json per massima sicurezza sui tipi e prevenzione di errori runtime

perchè SQLite (better-sqlite3)?

Per la persistenza locale di ogni server, SQLite e la scelta ottimale:

  • Zero configurazione: nessun server database da installare o gestire. Il file .db viene creato automaticamente alla prima esecuzione
  • File-based: ogni server ha il proprio file database in ~/.mcp-suite/data/, completamente indipendente dagli altri server
  • Sincrono: better-sqlite3 usa binding C++ sincroni, ideale per operazioni locali veloci senza la complessità delle Promise per operazioni di I/O locale
  • WAL mode: Write-Ahead Logging abilitato per performance ottimali in lettura concorrente, fondamentale quando più tool accedono contemporaneamente allo store
  • Portabile: il database si sposta semplicemente copiando il file, facilitando backup e migrazione

Trasporti: STDIO e HTTP

MCP Suite supporta due trasporti, selezionabili via variabile d'ambiente MCP_SUITE_TRANSPORT:

Confronto tra Trasporti MCP

Caratteristica STDIO (default) HTTP (Streamable HTTP)
Caso d'uso Uso locale con Claude Desktop, Cursor, VS Code Deployment remoti, comunicazione inter-server
Configurazione Nessuna porta, nessun conflitto di rete Porta dedicata per ogni server
Sicurezza Comunicazione locale, nessuna esposizione Protocollo stateful con session UUID
Scalabilità Un processo per connessione Container, scaling orizzontale
Route stdin/stdout POST/GET/DELETE /mcp + GET /health

STDIO e il trasporto predefinito, ideale per lo sviluppo locale. HTTP diventa necessario quando i server devono comunicare tra loro tramite il Client Manager o quando si effettua il deploy su server remoti.

Configurazione del Monorepo

Tre file nella directory radice definiscono la struttura del monorepo: il workspace pnpm, la pipeline Turborepo e il package.json root.

pnpm-workspace.yaml

Questo file dichiara quali directory contengono i workspace del monorepo:


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

Con questa configurazione, pnpm tratta ogni sottocartella di packages/ e servers/ come un workspace indipendente. Le dipendenze interne vengono risolte tramite il protocollo workspace:* invece di cercarle su npm.

turbo.json

Turborepo gestisce la pipeline di build, definendo l'ordine di compilazione e le dipendenze tra task:


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

La direttiva chiave e "dependsOn": ["^build"]. Il simbolo ^ indica che il task build di ogni workspace dipende dal build delle sue dipendenze interne. Questo garantisce che @mcp-suite/core venga compilato prima di qualsiasi server che lo utilizza.

package.json del Server con Dipendenze Interne

Ogni server dichiara le proprie dipendenze, sia esterne (npm) sia interne (workspace):


{
  "name": "@mcp-suite/scrum-board",
  "version": "0.1.0",
  "type": "module",
  "scripts": {
    "build": "tsc -p tsconfig.json",
    "start": "node dist/index.js",
    "clean": "rm -rf dist"
  },
  "dependencies": {
    "@mcp-suite/core": "workspace:*",
    "@mcp-suite/event-bus": "workspace:*",
    "@mcp-suite/database": "workspace:*",
    "@modelcontextprotocol/sdk": "^1.0.0",
    "better-sqlite3": "^11.0.0",
    "zod": "^3.23.0"
  },
  "devDependencies": {
    "@types/better-sqlite3": "^7.6.0",
    "typescript": "^5.7.0"
  }
}
    

La sintassi "workspace:*" indica a pnpm di risolvere la dipendenza dal workspace locale invece di cercarla su npm. Questo significa che le modifiche ai pacchetti condivisi sono immediatamente disponibili a tutti i server dopo una ricompilazione.

La Pipeline di Build

Quando si esegue pnpm build dalla radice del monorepo, Turborepo analizza il grafo delle dipendenze e pianifica la build ottimale:


pnpm build
    │
    ├── @mcp-suite/event-bus      (nessuna dipendenza interna)
    ├── @mcp-suite/core           (dipende da event-bus)
    ├── @mcp-suite/database       (dipende da core)
    ├── @mcp-suite/testing        (dipende da core, event-bus)
    ├── @mcp-suite/client-manager (dipende da core)
    ├── @mcp-suite/cli            (dipende da core, event-bus, client-manager)
    │
    └── servers/* (tutti dipendono da core, event-bus, database)
        ├── scrum-board
        ├── standup-notes
        ├── time-tracking
        └── ... (altri 19 server compilati in parallelo)
    

Turborepo compila prima event-bus (nessuna dipendenza), poi core (dipende da event-bus), poi database e gli altri pacchetti, e infine tutti i server in parallelo. La cache integrata di Turborepo salta la ricompilazione dei pacchetti che non sono cambiati, rendendo le build incrementali estremamente veloci.

Il Pattern Server a 4 Strati

Ogni server MCP nella suite segue un pattern architetturale rigoroso a 4 strati. Questo pattern garantisce uniformita, manutenibilità e testabilità in tutti i 22 server. Non si tratta di una convenzione opzionale, ma di un contratto architetturale: ogni nuovo server deve aderire a questa struttura.


servers/nome-server/
├── package.json        # Dipendenze e script
├── tsconfig.json       # Estende tsconfig.base.json
└── src/
    ├── index.ts        # Strato 1: Entry point, bootstrap
    ├── server.ts       # Strato 2: Factory, registrazione tool
    ├── tools/          # Strato 3: Un file per tool
    │   ├── create-sprint.ts
    │   ├── get-sprint.ts
    │   └── ...
    ├── services/       # Strato 4: Store SQLite, logica di business
    │   └── scrum-store.ts
    └── collaboration.ts  # Handler eventi cross-server (opzionale)
    

I 4 Strati e le Loro Responsabilità

Strato File Responsabilità Dipendenze
1. Entry Point index.ts Bootstrap: crea EventBus, chiama factory, avvia trasporto @mcp-suite/core, @mcp-suite/event-bus
2. Factory server.ts Crea McpSuiteServer, istanzia store, registra tool @mcp-suite/core, services, tools
3. Tools tools/*.ts Definizione singolo tool: schema Zod + handler async @modelcontextprotocol/sdk, services
4. Services services/*.ts Persistenza SQLite, migrazioni, logica di dominio @mcp-suite/database

Strato 1: Entry Point (index.ts)

L'entry point e il file più semplice dell'intero server. Ha esattamente tre responsabilità: creare un'istanza di LocalEventBus, chiamare la factory del server, e avviare il trasporto.


#!/usr/bin/env node

import { startServer } from '@mcp-suite/core';
import { LocalEventBus } from '@mcp-suite/event-bus';
import { createScrumBoardServer } from './server.js';

const eventBus = new LocalEventBus();
const suite = createScrumBoardServer(eventBus);
startServer(suite).catch((error) => {
  console.error('Failed to start scrum-board server:', error);
  process.exit(1);
});
    

Lo shebang #!/usr/bin/env node permette l'esecuzione diretta come binario. L'EventBus viene creato qui e iniettato nel server tramite Dependency Injection. La funzione startServer() sceglie automaticamente il trasporto (STDIO o HTTP) in base alla configurazione. Gli errori fatali terminano il processo con process.exit(1).

Strato 2: Factory (server.ts)

La factory e il cuore del server. Crea l'oggetto McpSuiteServer, istanzia i servizi di persistenza, registra tutti i tool e configura la collaborazione con altri server.


import { createMcpServer, type McpSuiteServer, type EventBus } from '@mcp-suite/core';
import { ScrumStore } from './services/scrum-store.js';
import { registerCreateSprint } from './tools/create-sprint.js';
import { registerGetSprint } from './tools/get-sprint.js';
import { registerCreateStory } from './tools/create-story.js';
import { registerCreateTask } from './tools/create-task.js';
import { registerUpdateTaskStatus } from './tools/update-task-status.js';
import { registerSprintBoard } from './tools/sprint-board.js';
import { registerGetBacklog } from './tools/get-backlog.js';
import { setupCollaborationHandlers } from './collaboration.js';

export function createScrumBoardServer(eventBus?: EventBus): McpSuiteServer {
  const suite = createMcpServer({
    name: 'scrum-board',
    version: '0.1.0',
    description: 'MCP server for managing sprints, user stories, tasks',
    eventBus,
  });

  const store = new ScrumStore();

  // Registra tutti i tool
  registerCreateSprint(suite.server, store, suite.eventBus);
  registerGetSprint(suite.server, store);
  registerCreateStory(suite.server, store);
  registerCreateTask(suite.server, store);
  registerUpdateTaskStatus(suite.server, store, suite.eventBus);
  registerSprintBoard(suite.server, store);
  registerGetBacklog(suite.server, store);

  // Collaborazione cross-server (solo se EventBus presente)
  if (suite.eventBus) {
    setupCollaborationHandlers(suite.eventBus, store);
  }

  suite.logger.info('All scrum-board tools registered');

  return suite;
}
    

Notare come createMcpServer() restituisce un oggetto McpSuiteServer già configurato. Lo store viene creato una sola volta e condiviso tra tutti i tool tramite le funzioni di registrazione. L'eventBus e opzionale: se non viene passato, il server funziona in modalità standalone.

Strato 3: Tool Registration (tools/)

Ogni tool e un file separato che esporta una funzione registerXxx(). Questa funzione riceve il McpServer, lo store e opzionalmente l'EventBus, e registra un singolo tool con il suo schema di validazione Zod e il handler asincrono.


import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import type { EventBus } from '@mcp-suite/core';
import type { ScrumStore } from '../services/scrum-store.js';

export function registerCreateSprint(
  server: McpServer,
  store: ScrumStore,
  eventBus?: EventBus,
): void {
  server.tool(
    'create-sprint',                                         // Nome del tool
    'Create a new sprint with a name, date range, and goals',  // Descrizione per l'LLM
    {                                                         // Schema Zod
      name: z.string().describe('Sprint name (e.g. "Sprint 12")'),
      startDate: z.string().describe('Start date ISO (YYYY-MM-DD)'),
      endDate: z.string().describe('End date ISO (YYYY-MM-DD)'),
      goals: z.array(z.string()).describe('Sprint goals'),
    },
    async ({ name, startDate, endDate, goals }) => {          // Handler
      try {
        const sprint = store.createSprint({ name, startDate, endDate, goals });

        // Pubblicazione evento fire-and-forget
        eventBus?.publish('scrum:sprint-started', {
          sprintId: String(sprint.id),
          name: sprint.name,
          startDate: sprint.startDate,
          endDate: sprint.endDate,
        });

        return {
          content: [{ type: 'text' as const, text: JSON.stringify(sprint, null, 2) }],
        };
      } catch (error) {
        return {
          content: [{
            type: 'text' as const,
            text: `Failed to create sprint: ${error instanceof Error ? error.message : String(error)}`,
          }],
          isError: true,
        };
      }
    },
  );
}
    

Il pattern di registrazione segue una struttura precisa: nome univoco del tool, descrizione leggibile dall'LLM per capire quando utilizzarlo, schema Zod per la validazione automatica dei parametri di input, e handler asincrono con gestione errori. L'EventBus viene utilizzato con l'operatore optional chaining eventBus?.publish(...): se non esiste, la chiamata semplicemente non fa nulla.

Strato 4: Services e Store (services/)

I servizi contengono la logica di business e la persistenza. Lo Store e la classe principale che gestisce le operazioni CRUD sul database SQLite, utilizzando il sistema di migrazioni di @mcp-suite/database.


import type Database from 'better-sqlite3';
import { createDatabase } from '@mcp-suite/database';
import { runMigrations, type Migration } from '@mcp-suite/database';

const migrations: Migration[] = [
  {
    version: 1,
    description: 'Create sprints, stories, and tasks tables',
    up: `
      CREATE TABLE IF NOT EXISTS sprints (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        startDate TEXT NOT NULL,
        endDate TEXT NOT NULL,
        goals TEXT NOT NULL DEFAULT '[]',
        status TEXT NOT NULL DEFAULT 'planning',
        createdAt TEXT NOT NULL DEFAULT (datetime('now'))
      );
    `,
  },
];

export class ScrumStore {
  private db: Database.Database;

  constructor(options?: { inMemory?: boolean; dataDir?: string }) {
    this.db = createDatabase({
      serverName: 'scrum-board',
      inMemory: options?.inMemory,
      dataDir: options?.dataDir,
    });
    runMigrations(this.db, migrations);
  }

  createSprint(input: {
    name: string; startDate: string; endDate: string; goals: string[]
  }): Sprint {
    const stmt = this.db.prepare(
      'INSERT INTO sprints (name, startDate, endDate, goals) VALUES (?, ?, ?, ?)',
    );
    const result = stmt.run(
      input.name, input.startDate, input.endDate,
      JSON.stringify(input.goals)
    );
    return this.getSprint(Number(result.lastInsertRowid))!;
  }

  // ... altri metodi CRUD ...
}
    

Lo Store utilizza createDatabase() da @mcp-suite/database per creare il database SQLite con WAL mode abilitato. Le migrazioni vengono definite come array di oggetti Migration e applicate automaticamente al costruttore. Il costruttore supporta inMemory: true per i test, permettendo di eseguire test unitari senza toccare il filesystem.

L'Interfaccia McpSuiteServer

Al centro dell'architettura c'è l'interfaccia McpSuiteServer, il contratto che ogni server deve rispettare. Questa interfaccia viene creata dalla factory createMcpServer() e passata a tutte le funzioni di registrazione tool.


export interface CreateServerOptions {
  name: string;
  version: string;
  description?: string;
  config?: Partial<ServerConfig>;
  eventBus?: EventBus;
}

export interface McpSuiteServer {
  server: McpServer;      // Istanza del server MCP (SDK ufficiale)
  config: ServerConfig;   // Configurazione caricata e validata
  logger: Logger;         // Logger strutturato su stderr
  eventBus?: EventBus;    // EventBus opzionale per collaborazione
}

export function createMcpServer(options: CreateServerOptions): McpSuiteServer {
  const config = loadConfig(options.name, options.config);
  const logger = new Logger(options.name, config.logLevel);

  logger.info(`Initializing ${options.name} v${options.version}`);

  const server = new McpServer({
    name: options.name,
    version: options.version,
  });

  return { server, config, logger, eventBus: options.eventBus };
}
    

Il flusso interno della factory e lineare: carica la configurazione dalle variabili d'ambiente (loadConfig), crea il logger con il livello configurato, istanzia il McpServer dall'SDK ufficiale, e restituisce il bundle completo. Il campo eventBus e opzionale: se presente, i tool possono pubblicare eventi; se assente, i tool funzionano in modalità standalone senza alcun effetto collaterale.

Diagramma Architetturale Completo

Il seguente diagramma mostra come tutti i componenti si relazionano tra loro, dai client AI fino alle dipendenze esterne:


┌────────────────────────────────────────────────────────────────────┐
│             CLIENT (Claude Desktop, Cursor, VS Code)               │
│          Comunica via STDIO o HTTP (JSON-RPC / Streamable)         │
└──────────────┬─────────────────────────────────┬───────────────────┘
               │                                 │
               v                                 v
┌──────────────────────┐           ┌──────────────────────┐
│   scrum-board        │           │   time-tracking      │
│   ┌──────────────┐   │ EventBus  │   ┌──────────────┐   │
│   │  tools/      │   │<────────>│   │  tools/      │   │
│   │  services/   │   │(opzionale)│   │  services/   │   │
│   │  server.ts   │   │           │   │  server.ts   │   │
│   │  index.ts    │   │           │   │  index.ts    │   │
│   └──────────────┘   │           │   └──────────────┘   │
└──────────┬───────────┘           └──────────┬───────────┘
           │                                  │
           v                                  v
┌────────────────────────────────────────────────────────────────────┐
│                   PACKAGES (librerie condivise)                    │
│                                                                    │
│  ┌────────────┐  ┌────────────┐  ┌──────────┐  ┌────────────┐     │
│  │ @mcp-suite│  │ @mcp-suite│  │@mcp-suite│  │ @mcp-suite│     │
│  │ /core     │  │ /event-bus │  │/database │  │ /testing   │     │
│  └────────────┘  └────────────┘  └──────────┘  └────────────┘     │
└────────────────────────────────────────────────────────────────────┘
                                │
                                v
┌────────────────────────────────────────────────────────────────────┐
│  @modelcontextprotocol/sdk       better-sqlite3          zod      │
│  (protocollo MCP ufficiale)     (database nativo)     (validaz.)  │
└────────────────────────────────────────────────────────────────────┘
    

Flusso di Iniezione dell'EventBus

Un aspetto fondamentale del pattern e come l'EventBus viene iniettato attraverso i vari strati. Il design segue il principio fire-and-forget: se l'EventBus non esiste, le chiamate eventBus?.publish(...) non fanno nulla. Se esiste ma nessuno e in ascolto, l'evento viene semplicemente ignorato. Il tool non attende mai la risposta dell'evento.


index.ts                   server.ts                      tools/xxx.ts
────────                   ─────────                      ────────────

const eventBus =           function createXxxServer(      function registerXxx(
  new LocalEventBus();       eventBus?: EventBus            server, store,
                           ) {                              eventBus?: EventBus
const suite =                const suite = createMcpServer( ) {
  createXxxServer(             { eventBus }                  // uso:
    eventBus                 );                              eventBus?.publish(...)
  );                                                       }
                             registerXxx(
                               suite.server,
                               store,
                               suite.eventBus  // passato ai tool
                             );
                           }
    

Questo pattern di Dependency Injection garantisce che ogni componente riceva solo le dipendenze di cui ha bisogno. L'EventBus e sempre opzionale, quindi aggiungere o rimuovere la collaborazione non richiede modifiche strutturali al server.

Vantaggi del Pattern a 4 Strati

  • Separazione delle responsabilità: ogni strato ha un compito ben definito, rendendo il codice facile da leggere e mantenere
  • Testabilità: i servizi possono essere testati con database in-memory, i tool possono essere testati con mock dello store
  • Scalabilità: aggiungere un nuovo tool richiede solo un nuovo file in tools/ e la registrazione nella factory
  • Uniformita: tutti i 22 server seguono la stessa struttura, rendendo facile navigare e comprendere qualsiasi server
  • Indipendenza: ogni server e deployabile singolarmente, con le proprie dipendenze e il proprio database

Conclusioni

L'architettura monorepo con pnpm workspaces e Turborepo, combinata con il pattern server a 4 strati, fornisce le fondamenta solide per una suite MCP scalabile e manutenibile. Le scelte tecnologiche (TypeScript per la type-safety, SQLite per la persistenza locale, STDIO/HTTP per i trasporti) sono state guidate dalle esigenze specifiche di un ecosistema di server MCP.

L'interfaccia McpSuiteServer e la factory createMcpServer() standardizzano la creazione di nuovi server, mentre la separazione in strati (entry point, factory, tools, services) garantisce che ogni componente abbia una responsabilità chiara e limitata.

Nel prossimo articolo passeremo dalla teoria alla pratica: creeremo il nostro primo server MCP in TypeScript da zero, seguendo il pattern descritto in questo articolo. Vedremo come configurare il progetto, definire i tool con Zod, implementare la persistenza con SQLite, e testare il server con Claude Desktop.

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