MCP in JavaScript

MCP in JavaScript

MCP in JavaScript: Costruire Server e Client per l’Integrazione AI

Il Model Context Protocol (MCP) rappresenta una svolta nell’integrazione tra modelli di linguaggio e applicazioni esterne. Rilasciato da Anthropic nel novembre 2024, MCP è uno standard open-source che consente ai modelli AI di interagire con fonti di dati esterne attraverso un’interfaccia unificata. Il protocollo fornisce un modo standardizzato per connettere applicazioni LLM a fonti di dati esterne e strumenti, fungendo da ponte universale per l’ecosistema dell’AI.

Cosa è MCP e Perché è Importante – MCP in JavaScript

MCP è uno standard aperto che connette LLM come Claude alle tue fonti di dati, consentendo agli LLM di analizzare file locali (come log, PDF, CSV nel tuo file system). Il protocollo risolve un problema fondamentale: permettere ai modelli AI di accedere a dati specifici del dominio in modo sicuro e controllato, senza compromettere la sicurezza o la privacy.

Immaginate MCP come un “USB-C per l’AI” – un connettore universale che standardizza il modo in cui le applicazioni AI accedono a risorse esterne. Questo elimina la necessità di sviluppare integrazioni custom per ogni combinazione di LLM e fonte di dati.

MCP e i Linguaggi di Programmazione

MCP è progettato per essere language-agnostic, supportando implementazioni in diversi linguaggi:

Python: Il linguaggio più maturo per MCP, con SDK completi e una vasta community TypeScript/JavaScript: L’SDK TypeScript ufficiale implementa la specifica MCP completa, rendendo facile costruire client MCP che possono connettersi a qualsiasi server MCP e creare server MCP che espongono risorse, prompt e strumenti

Go: Implementazioni emergenti per applicazioni ad alte prestazioni

Rust: Per applicazioni system-level che richiedono massima efficienza

JavaScript e TypeScript: L’Ecosistema MCP

L’SDK TypeScript MCP fornisce tutti i building block necessari per creare un server MCP, gestendo i dettagli del protocollo, la gestione delle connessioni e i pattern di comunicazione. L’ecosistema JavaScript/TypeScript offre vantaggi unici:

  • Familiarità: La maggior parte degli sviluppatori web conosce JavaScript
  • Ecosistema ricco: NPM offre milioni di pacchetti riutilizzabili
  • Deployment versatile: Da Node.js a Edge Functions, le opzioni sono infinite
  • Tipo safety: TypeScript aggiunge robustezza senza sacrificare la produttività

Esempio 1: Server MCP con TypeScript

Iniziamo con un server MCP che espone un tool per l’analisi di dati meteorologici:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  McpError,
  ErrorCode,
} from "@modelcontextprotocol/sdk/types.js";

interface WeatherData {
  temperature: number;
  humidity: number;
  pressure: number;
  location: string;
  timestamp: Date;
}

class WeatherMCPServer {
  private server: Server;
  private weatherData: Map<string, WeatherData> = new Map();

  constructor() {
    this.server = new Server(
      {
        name: "weather-analysis-server",
        version: "1.0.0",
      },
      {
        capabilities: {
          tools: {},
        },
      }
    );

    this.setupToolHandlers();
    this.initializeMockData();
  }

  private initializeMockData() {
    // Simula dati meteorologici per diverse città
    const cities = ["Milano", "Roma", "Napoli", "Torino"];
    cities.forEach(city => {
      this.weatherData.set(city, {
        temperature: Math.round(Math.random() * 30 + 5),
        humidity: Math.round(Math.random() * 80 + 20),
        pressure: Math.round(Math.random() * 50 + 980),
        location: city,
        timestamp: new Date()
      });
    });
  }

  private setupToolHandlers() {
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        {
          name: "get_weather",
          description: "Recupera dati meteorologici per una città specifica",
          inputSchema: {
            type: "object",
            properties: {
              city: {
                type: "string",
                description: "Nome della città"
              },
              format: {
                type: "string", 
                enum: ["json", "text"],
                description: "Formato di output"
              }
            },
            required: ["city"]
          }
        },
        {
          name: "analyze_weather_trend",
          description: "Analizza tendenze meteorologiche tra più città",
          inputSchema: {
            type: "object",
            properties: {
              cities: {
                type: "array",
                items: { type: "string" },
                description: "Lista di città da confrontare"
              }
            },
            required: ["cities"]
          }
        }
      ]
    }));

    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;

      try {
        switch (name) {
          case "get_weather":
            return this.getWeatherData(args as { city: string; format?: string });
          
          case "analyze_weather_trend":
            return this.analyzeWeatherTrend(args as { cities: string[] });

          default:
            throw new McpError(
              ErrorCode.MethodNotFound,
              `Tool ${name} non trovato`
            );
        }
      } catch (error) {
        throw new McpError(
          ErrorCode.InternalError,
          `Errore nell'esecuzione del tool: ${error}`
        );
      }
    });
  }

  private async getWeatherData(args: { city: string; format?: string }) {
    const { city, format = "json" } = args;
    const data = this.weatherData.get(city);

    if (!data) {
      throw new McpError(
        ErrorCode.InvalidParams,
        `Dati non disponibili per ${city}`
      );
    }

    if (format === "text") {
      return {
        content: [{
          type: "text",
          text: `Meteo per ${data.location}:\n` +
                `Temperatura: ${data.temperature}°C\n` +
                `Umidità: ${data.humidity}%\n` +
                `Pressione: ${data.pressure} hPa\n` +
                `Ultimo aggiornamento: ${data.timestamp.toLocaleString()}`
        }]
      };
    }

    return {
      content: [{
        type: "text",
        text: JSON.stringify(data, null, 2)
      }]
    };
  }

  private async analyzeWeatherTrend(args: { cities: string[] }) {
    const { cities } = args;
    const validData: WeatherData[] = [];

    cities.forEach(city => {
      const data = this.weatherData.get(city);
      if (data) validData.push(data);
    });

    if (validData.length === 0) {
      throw new McpError(
        ErrorCode.InvalidParams,
        "Nessuna città valida fornita"
      );
    }

    const avgTemp = validData.reduce((sum, d) => sum + d.temperature, 0) / validData.length;
    const avgHumidity = validData.reduce((sum, d) => sum + d.humidity, 0) / validData.length;
    const maxTemp = Math.max(...validData.map(d => d.temperature));
    const minTemp = Math.min(...validData.map(d => d.temperature));

    const analysis = {
      cities_analyzed: validData.map(d => d.location),
      statistics: {
        average_temperature: Math.round(avgTemp * 10) / 10,
        average_humidity: Math.round(avgHumidity * 10) / 10,
        temperature_range: { min: minTemp, max: maxTemp },
        temperature_variance: maxTemp - minTemp
      },
      recommendations: this.generateRecommendations(avgTemp, avgHumidity)
    };

    return {
      content: [{
        type: "text",
        text: JSON.stringify(analysis, null, 2)
      }]
    };
  }

  private generateRecommendations(avgTemp: number, avgHumidity: number): string[] {
    const recommendations: string[] = [];
    
    if (avgTemp > 25) {
      recommendations.push("Temperature elevate: considerare sistemi di raffreddamento");
    }
    if (avgTemp < 10) {
      recommendations.push("Temperature basse: attenzione al riscaldamento");
    }
    if (avgHumidity > 70) {
      recommendations.push("Umidità alta: rischio di muffe, considerare deumidificazione");
    }
    if (avgHumidity < 30) {
      recommendations.push("Umidità bassa: considerare umidificazione");
    }

    return recommendations;
  }

  async start() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
  }
}

// Avvio del server
const server = new WeatherMCPServer();
server.start().catch(console.error);

Esempio 2: Client MCP in JavaScript

Ora creiamo un client che interagisce con il nostro server meteorologico:

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { spawn } from "child_process";

class WeatherMCPClient {
  constructor() {
    this.client = new Client(
      {
        name: "weather-client",
        version: "1.0.0",
      },
      {
        capabilities: {},
      }
    );
  }

  async connect() {
    // Spawna il processo del server MCP
    const serverProcess = spawn("node", ["weather-server.js"], {
      stdio: ["pipe", "pipe", "inherit"],
    });

    const transport = new StdioClientTransport({
      readable: serverProcess.stdout,
      writable: serverProcess.stdin,
    });

    await this.client.connect(transport);
    console.log("Connesso al server MCP meteorologico");
  }

  async listAvailableTools() {
    try {
      const tools = await this.client.listTools();
      console.log("Tool disponibili:");
      tools.tools.forEach(tool => {
        console.log(`- ${tool.name}: ${tool.description}`);
      });
      return tools.tools;
    } catch (error) {
      console.error("Errore nel recupero dei tool:", error);
      throw error;
    }
  }

  async getWeatherForCity(city, format = "json") {
    try {
      const result = await this.client.callTool({
        name: "get_weather",
        arguments: { city, format }
      });

      console.log(`\n=== Meteo per ${city} ===`);
      result.content.forEach(content => {
        if (content.type === "text") {
          console.log(content.text);
        }
      });

      return result;
    } catch (error) {
      console.error(`Errore nel recupero meteo per ${city}:`, error);
      throw error;
    }
  }

  async analyzeMultipleCities(cities) {
    try {
      const result = await this.client.callTool({
        name: "analyze_weather_trend",
        arguments: { cities }
      });

      console.log(`\n=== Analisi trend per ${cities.join(", ")} ===`);
      result.content.forEach(content => {
        if (content.type === "text") {
          const analysis = JSON.parse(content.text);
          console.log("Statistiche:");
          console.log(`- Temperatura media: ${analysis.statistics.average_temperature}°C`);
          console.log(`- Umidità media: ${analysis.statistics.average_humidity}%`);
          console.log(`- Range temperature: ${analysis.statistics.temperature_range.min}°C - ${analysis.statistics.temperature_range.max}°C`);
          
          if (analysis.recommendations.length > 0) {
            console.log("\nRaccomandazioni:");
            analysis.recommendations.forEach(rec => console.log(`- ${rec}`));
          }
        }
      });

      return result;
    } catch (error) {
      console.error("Errore nell'analisi delle città:", error);
      throw error;
    }
  }

  async runDemo() {
    try {
      await this.connect();
      await this.listAvailableTools();

      // Test singola città
      await this.getWeatherForCity("Milano", "text");
      await this.getWeatherForCity("Roma");

      // Test analisi multiple città
      await this.analyzeMultipleCities(["Milano", "Roma", "Napoli"]);

    } catch (error) {
      console.error("Errore nella demo:", error);
    }
  }
}

// Esecuzione della demo
const client = new WeatherMCPClient();
client.runDemo();

Esempio 3: Integrazione Avanzata con Express.js

Per scenario più complessi, possiamo integrare MCP in un’applicazione web:

import express from 'express';
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import cors from 'cors';

interface WeatherApiResponse {
  success: boolean;
  data?: any;
  error?: string;
  timestamp: string;
}

class WeatherAPIGateway {
  private app: express.Application;
  private mcpClient: Client;
  private isConnected: boolean = false;

  constructor() {
    this.app = express();
    this.mcpClient = new Client(
      { name: "weather-api-gateway", version: "1.0.0" },
      { capabilities: {} }
    );
    
    this.setupMiddleware();
    this.setupRoutes();
  }

  private setupMiddleware() {
    this.app.use(cors());
    this.app.use(express.json());
    this.app.use(express.urlencoded({ extended: true }));
    
    // Middleware per logging
    this.app.use((req, res, next) => {
      console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
      next();
    });
  }

  private setupRoutes() {
    // Health check
    this.app.get('/health', (req, res) => {
      res.json({
        status: 'healthy',
        mcp_connected: this.isConnected,
        timestamp: new Date().toISOString()
      });
    });

    // Endpoint per recuperare meteo singola città
    this.app.get('/weather/:city', async (req, res) => {
      try {
        if (!this.isConnected) {
          throw new Error('MCP client non connesso');
        }

        const { city } = req.params;
        const { format = 'json' } = req.query;

        const result = await this.mcpClient.callTool({
          name: 'get_weather',
          arguments: { city, format }
        });

        const response: WeatherApiResponse = {
          success: true,
          data: this.parseToolResult(result),
          timestamp: new Date().toISOString()
        };

        res.json(response);
      } catch (error) {
        const response: WeatherApiResponse = {
          success: false,
          error: error instanceof Error ? error.message : 'Errore sconosciuto',
          timestamp: new Date().toISOString()
        };
        res.status(500).json(response);
      }
    });

    // Endpoint per analisi multiple città
    this.app.post('/weather/analyze', async (req, res) => {
      try {
        if (!this.isConnected) {
          throw new Error('MCP client non connesso');
        }

        const { cities } = req.body;

        if (!Array.isArray(cities) || cities.length === 0) {
          return res.status(400).json({
            success: false,
            error: 'Fornire un array di città non vuoto',
            timestamp: new Date().toISOString()
          });
        }

        const result = await this.mcpClient.callTool({
          name: 'analyze_weather_trend',
          arguments: { cities }
        });

        const response: WeatherApiResponse = {
          success: true,
          data: this.parseToolResult(result),
          timestamp: new Date().toISOString()
        };

        res.json(response);
      } catch (error) {
        const response: WeatherApiResponse = {
          success: false,
          error: error instanceof Error ? error.message : 'Errore sconosciuto',
          timestamp: new Date().toISOString()
        };
        res.status(500).json(response);
      }
    });

    // Endpoint per ottenere tool disponibili
    this.app.get('/tools', async (req, res) => {
      try {
        if (!this.isConnected) {
          throw new Error('MCP client non connesso');
        }

        const tools = await this.mcpClient.listTools();
        res.json({
          success: true,
          data: tools.tools,
          timestamp: new Date().toISOString()
        });
      } catch (error) {
        res.status(500).json({
          success: false,
          error: error instanceof Error ? error.message : 'Errore sconosciuto',
          timestamp: new Date().toISOString()
        });
      }
    });

    // Endpoint per batch processing
    this.app.post('/weather/batch', async (req, res) => {
      try {
        if (!this.isConnected) {
          throw new Error('MCP client non connesso');
        }

        const { requests } = req.body;

        if (!Array.isArray(requests)) {
          return res.status(400).json({
            success: false,
            error: 'Fornire un array di richieste',
            timestamp: new Date().toISOString()
          });
        }

        const results = await Promise.allSettled(
          requests.map(async (request: any) => {
            if (request.type === 'single' && request.city) {
              return await this.mcpClient.callTool({
                name: 'get_weather',
                arguments: { city: request.city, format: request.format || 'json' }
              });
            } else if (request.type === 'analyze' && request.cities) {
              return await this.mcpClient.callTool({
                name: 'analyze_weather_trend',
                arguments: { cities: request.cities }
              });
            }
            throw new Error('Tipo di richiesta non valido');
          })
        );

        const processedResults = results.map((result, index) => ({
          index,
          success: result.status === 'fulfilled',
          data: result.status === 'fulfilled' ? this.parseToolResult(result.value) : null,
          error: result.status === 'rejected' ? result.reason.message : null
        }));

        res.json({
          success: true,
          data: processedResults,
          timestamp: new Date().toISOString()
        });
      } catch (error) {
        res.status(500).json({
          success: false,
          error: error instanceof Error ? error.message : 'Errore sconosciuto',
          timestamp: new Date().toISOString()
        });
      }
    });
  }

  private parseToolResult(result: any): any {
    if (result.content && result.content[0] && result.content[0].type === 'text') {
      try {
        return JSON.parse(result.content[0].text);
      } catch {
        return result.content[0].text;
      }
    }
    return result;
  }

  async connectToMCP(serverUrl: string) {
    try {
      // Per questo esempio, assumiamo un server SSE
      // In produzione, potresti usare altri transport
      const transport = new SSEClientTransport(new URL(serverUrl));
      await this.mcpClient.connect(transport);
      this.isConnected = true;
      console.log('Connesso al server MCP via SSE');
    } catch (error) {
      console.error('Errore nella connessione MCP:', error);
      throw error;
    }
  }

  start(port: number = 3000) {
    this.app.listen(port, () => {
      console.log(`Weather API Gateway avviato sulla porta ${port}`);
      console.log(`Health check: http://localhost:${port}/health`);
    });
  }
}

// Configurazione e avvio
const gateway = new WeatherAPIGateway();

// In un ambiente reale, configureresti l'URL del server MCP
// gateway.connectToMCP('http://localhost:3001/mcp').catch(console.error);

gateway.start(3000);

export default WeatherAPIGateway;

Passaggi per Sviluppare con MCP – MCP in JavaScript

Setup del Progetto

  1. Inizializzazione:
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node ts-node
  1. Configurazione TypeScript:
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true
  }
}

Sviluppo Server MCP

  1. Definire le capabilities: Stabilire se il server esporrà tools, resources, o prompts
  2. Implementare i handler: Creare handler per ListTools, CallTool, etc.
  3. Gestione errori: Implementare error handling robusto con McpError
  4. Testing: Testare con client di esempio

Sviluppo Client MCP

  1. Scegliere il transport: stdio, SSE, o HTTP
  2. Gestire la connessione: Implementare riconnessione automatica
  3. Cache intelligente: Implementare caching per migliorare le performance
  4. Monitoraggio: Aggiungere logging e metriche

API Principali e Best Practices – MCP in JavaScript

Server API

  • server.setRequestHandler(): Per gestire richieste specifiche
  • ListToolsRequestSchema: Schema per listing dei tool
  • CallToolRequestSchema: Schema per chiamate ai tool
  • McpError: Gestione errori standardizzata

Client API

  • client.connect(): Connessione al server
  • client.listTools(): Elenco tool disponibili
  • client.callTool(): Invocazione tool
  • Transport layers: stdio, SSE, HTTP

Best Practices – MCP in JavaScript

  1. Validazione input: Sempre validare parametri in input
  2. Error handling: Usare McpError per errori specifici del protocollo
  3. Performance: Implementare caching e connection pooling
  4. Security: Validare permessi e sanitizzare input
  5. Documentation: Documentare schema degli input accuratamente

Conclusione – MCP in JavaScript

MCP rappresenta un protocollo che consente l’integrazione tra modelli di linguaggio e strumenti, spesso come parte di un agente. L’ecosistema JavaScript/TypeScript offre un’implementazione matura e ben documentata, ideale per sviluppatori che vogliono integrare rapidamente le proprie applicazioni con l’AI.

Gli esempi mostrati dimostrano la versatilità di MCP: da semplici server di dati a gateway API complessi. La standardizzazione offerta da MCP elimina la complessità dell’integrazione diretta con diversi LLM, permettendo agli sviluppatori di concentrarsi sulla logica business.

Con il continuo sviluppo dell’ecosistema AI, MCP si posiziona come protocollo fondamentale per il futuro dell’integrazione AI-application, e l’implementazione JavaScript/TypeScript ne facilita l’adozione in contesti web e enterprise.

(fonte) (fonte) (fonte)

Innovaformazione, scuola informatica specialistica segue costantemente il mercato IT ed affianca le aziende nella formazione dei team di sviluppatori. Nell’offerta formativa trovate il Corso Agenti AI ed MCP, mentre l’elenco corsi completo è presente sul nostro sito al seguente LINK.

INFO: info@innovaformazione.net – Tel. 3471012275 (Dario Carrassi)

Ti potrebbe interessare

Articoli correlati