Puoi facilmente creare flussi di lavoro AI con Anakin AI senza alcuna conoscenza di programmazione. Collegati alle API LLM come: GPT-4, Claude 3.5 Sonnet, Uncensored Dolphin-Mixtral, Stable Diffusion, DALLE, Web Scraping.... in un unico flusso di lavoro!
Dimentica la programmazione complicata, automatizza il tuo lavoro quotidiano con Anakin AI!
Per un tempo limitato, puoi anche utilizzare Google Gemini 1.5 e Stable Diffusion gratuitamente!

Introduzione
Il Protocollo di Contesto del Modello (MCP) rappresenta un notevole progresso nell'ecosistema AI, offrendo un modo standardizzato per comunicare con grandi modelli di linguaggio. Invece che ogni piattaforma AI implementi la propria formattazione unica per i messaggi, MCP mira a fornire un'interfaccia coerente per richieste, risposte e chiamate a funzioni attraverso vari modelli e piattaforme.
Sebbene il protocollo stesso sia in evoluzione, costruire un server di base compatibile con MCP può essere semplice. In questa guida, ti guiderò nella creazione di un server semplice, ma funzionale, che aderisca ai principi fondamentali della gestione dei messaggi nei moderni sistemi AI. La nostra implementazione si concentrerà sulla creazione di una base che potrai successivamente ampliare con funzionalità più avanzate.
Dieci minuti sono sufficienti? Sicuramente non per un sistema pronto per la produzione. Ma per un prototipo funzionante che dimostri i concetti chiave? Assolutamente. Cominciamo!
Requisiti
Prima di cominciare, avrai bisogno di:
- Node.js (v16+) installato sul tuo sistema
- Conoscenze di base di JavaScript/TypeScript
- Familiarità con Express.js o framework web simili
- Un editor di codice (VS Code consigliato)
- Accesso al terminale/riga di comando
- npm o yarn come gestore di pacchetti
Passaggio 1: Configurare il tuo progetto (2 minuti)
Prima di tutto, creiamo una nuova directory e inizializziamo il nostro progetto:
mkdir mcp-server
cd mcp-server
npm init -y
Ora, installa le dipendenze necessarie:
npm install express cors typescript ts-node @types/node @types/express @types/cors
npm install --save-dev nodemon
Crea un file di configurazione TypeScript:
npx tsc --init
Modifica il file generato tsconfig.json
per includere queste impostazioni essenziali:
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
},
"include": ["src/**/*"]
}
Aggiorna la sezione scripts del tuo package.json
:
"scripts": {
"start": "node dist/index.js",
"dev": "nodemon --exec ts-node src/index.ts",
"build": "tsc"
}
Passaggio 2: Creare il server core (3 minuti)
Crea la tua directory sorgente e il file principale del server:
mkdir -p src/handlers
touch src/index.ts
touch src/handlers/messageHandler.ts
touch src/types.ts
Definiamo prima i nostri tipi in src/types.ts
:
// Struttura di messaggio di base
export interface Content {
type: string;
text?: string;
}
export interface Message {
role: "user" | "assistant" | "system";
content: Content[] | string;
}
// Strutture di richiesta e risposta
export interface ModelRequest {
messages: Message[];
max_tokens?: number;
temperature?: number;
stream?: boolean;
}
export interface ModelResponse {
message: Message;
}
// Interfacce di chiamata agli strumenti
export interface Tool {
name: string;
description: string;
input_schema: {
type: string;
properties: Record;
required?: string[];
};
}
export interface ToolCall {
type: "tool_call";
id: string;
name: string;
input: Record;
}
export interface ToolResult {
type: "tool_result";
tool_call_id: string;
content: string;
}
Ora implementa il server di base in src/index.ts
:
import express from 'express';
import cors from 'cors';
import { handleMessageRequest } from './handlers/messageHandler';
const app = express();
const PORT = process.env.PORT || 3000;
// Middleware
app.use(cors());
app.use(express.json({ limit: '10mb' }));
// Endpoint principale per l'elaborazione dei messaggi
app.post('/v1/messages', handleMessageRequest);
// Endpoint di controllo dello stato
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok' });
});
// Avvia il server
app.listen(PORT, () => {
console.log(`Server in esecuzione sulla porta ${PORT}`);
console.log(`Controllo stato: http://localhost:${PORT}/health`);
console.log(`Endpoint messaggi: http://localhost:${PORT}/v1/messages`);
});
Successivamente, implementa il gestore dei messaggi in src/handlers/messageHandler.ts
:
import { Request, Response } from 'express';
import { ModelRequest, ModelResponse, Message, Content } from '../types';
export async function handleMessageRequest(req: Request, res: Response) {
try {
const request = req.body as ModelRequest;
// Validazione di base
if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
return res.status(400).json({ error: 'Formato di richiesta non valido. L\'array di messaggi è obbligatorio.' });
}
// Registra la richiesta in arrivo (per il debug)
console.log('Richiesta ricevuta con', request.messages.length, 'messaggi');
// Elabora i messaggi
const response = processMessages(request.messages);
// Restituisci la risposta
return res.status(200).json(response);
} catch (error) {
console.error('Errore durante l\'elaborazione della richiesta:', error);
return res.status(500).json({ error: 'Errore interno del server' });
}
}
function processMessages(messages: Message[]): ModelResponse {
// Estrai l'ultimo messaggio dell'utente
const lastUserMessage = findLastUserMessage(messages);
if (!lastUserMessage) {
return createErrorResponse("Nessun messaggio dell'utente trovato nella conversazione");
}
const userQuery = extractTextContent(lastUserMessage);
// Logica semplice di generazione della risposta
let responseText = "";
if (userQuery.toLowerCase().includes('ciao') || userQuery.toLowerCase().includes('salve')) {
responseText = "Ciao! Come posso aiutarti oggi?";
} else if (userQuery.toLowerCase().includes('meteo')) {
responseText = "Non ho accesso ai dati meteorologici in tempo reale, ma posso aiutarti a capire i modelli meteorologici in generale.";
} else if (userQuery.toLowerCase().includes('ora')) {
responseText = `L'ora attuale del server è ${new Date().toLocaleTimeString()}.`;
} else {
responseText = "Ho ricevuto il tuo messaggio. Questa è una risposta del server modello semplice.";
}
// Costruisci e restituisci la risposta
return {
message: {
role: "assistant",
content: [{
type: "text",
text: responseText
}]
}
};
}
function findLastUserMessage(messages: Message[]): Message | undefined {
// Trova l'ultimo messaggio con ruolo 'user'
for (let i = messages.length - 1; i >= 0; i--) {
if (messages[i].role === 'user') {
return messages[i];
}
}
return undefined;
}
function extractTextContent(message: Message): string {
if (typeof message.content === 'string') {
return message.content;
} else if (Array.isArray(message.content)) {
return message.content
.filter(item => item.type === 'text' && item.text)
.map(item => item.text)
.join(' ');
}
return '';
}
function createErrorResponse(errorMessage: string): ModelResponse {
return {
message: {
role: "assistant",
content: [{
type: "text",
text: `Errore: ${errorMessage}`
}]
}
};
}
Passaggio 3: Aggiungere la capacità di chiamata agli strumenti (3 minuti)
Crea un nuovo file per le definizioni e implementazioni degli strumenti:
touch src/tools.ts
Implementa alcuni strumenti di base in src/tools.ts
:
import { Tool } from './types';
// Definizioni degli strumenti
export const availableTools: Tool[] = [
{
name: "get_current_time",
description: "Ottieni l'ora attuale del server",
input_schema: {
type: "object",
properties: {
timezone: {
type: "string",
description: "Fuso orario opzionale (predefinito al fuso orario del server)"
}
}
}
},
{
name: "calculate",
description: "Esegui un calcolo matematico",
input_schema: {
type: "object",
properties: {
expression: {
type: "string",
description: "Espressione matematica da valutare"
}
},
required: ["expression"]
}
}
];
// Implementazioni degli strumenti
export function executeToolCall(name: string, params: Record): string {
switch (name) {
case "get_current_time":
return getTime(params.timezone);
case "calculate":
return calculate(params.expression);
default:
throw new Error(`Strumento sconosciuto: ${name}`);
}
}
function getTime(timezone?: string): string {
const options: Intl.DateTimeFormatOptions = {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
timeZoneName: 'short'
};
try {
if (timezone) {
options.timeZone = timezone;
}
return new Date().toLocaleTimeString('it-IT', options);
} catch (error) {
return `${new Date().toLocaleTimeString()} (Ora del server)`;
}
}
function calculate(expression: string): string {
try {
// ATTENZIONE: In un'applicazione reale, dovresti utilizzare un metodo di valutazione più sicuro
// Questo è un esempio semplificato solo per dimostrazione
const sanitizedExpression = expression.replace(/[^0-9+\-*/().\s]/g, '');
const result = eval(sanitizedExpression);
return `${expression} = ${result}`;
} catch (error) {
return `Errore nel calcolo di ${expression}: ${error instanceof Error ? error.message : String(error)}`;
}
}
Ora, aggiorna il gestore dei messaggi per supportare la chiamata agli strumenti modificando src/handlers/messageHandler.ts
:
// Aggiungi queste importazioni in cima
import { availableTools, executeToolCall } from '../tools';
import { ToolCall, ToolResult } from '../types';
// Aggiorna la funzione handleMessageRequest
export async function handleMessageRequest(req: Request, res: Response) {
try {
const request = req.body as ModelRequest;
// Validazione di base
if (!request.messages || !Array.isArray(request.messages) || request.messages.length === 0) {
return res.status(400).json({ error: 'Formato di richiesta non valido. L\'array di messaggi è obbligatorio.' });
}
// Controlla se si tratta di una richiesta con risultati degli strumenti
const lastMessage = request.messages[request.messages.length - 1];
if (lastMessage.role === 'assistant' && Array.isArray(lastMessage.content)) {
const toolCalls = lastMessage.content.filter(item =>
item.type === 'tool_call') as ToolCall[];
if (toolCalls.length > 0) {
// Questa è una continuazione con risultati degli strumenti
return handleToolResultsResponse(request, res);
}
}
// Elabora come un messaggio normale
const response = processMessages(request.messages);
// Restituisci la risposta
return res.status(200).json(response);
} catch (error) {
console.error('Errore durante l\'elaborazione della richiesta:', error);
return res.status(500).json({ error: 'Errore interno del server' });
}
}
// Aggiungi questa funzione per gestire le chiamate agli strumenti
function handleToolResultsResponse(request: ModelRequest, res: Response) {
const messages = request.messages;
const lastAssistantMessage = messages[messages.length - 1];
if (lastAssistantMessage.role !== 'assistant' || !Array.isArray(lastAssistantMessage.content)) {
return res.status(400).json({ error: 'Formato dei risultati dello strumento non valido' });
}
// Trova le chiamate e i risultati degli strumenti
const toolCalls = lastAssistantMessage.content.filter(
item => item.type === 'tool_call'
) as ToolCall[];
const toolResults = lastAssistantMessage.content.filter(
item => item.type === 'tool_result'
) as ToolResult[];
// Elabora i risultati
let finalResponse = "Ho elaborato le seguenti informazioni:\n\n";
toolResults.forEach(result => {
const relatedCall = toolCalls.find(call => call.id === result.tool_call_id);
if (relatedCall) {
finalResponse += `- ${relatedCall.name}: ${result.content}\n`;
}
});
return res.status(200).json({
message: {
role: "assistant",
content: [{ type: "text", text: finalResponse }]
}
});
}
// Modifica la funzione processMessages per gestire potenziali chiamate agli strumenti
function processMessages(messages: Message[]): ModelResponse {
const lastUserMessage = findLastUserMessage(messages);
if (!lastUserMessage) {
return createErrorResponse("Nessun messaggio dell'utente trovato nella conversazione");
}
const userQuery = extractTextContent(lastUserMessage);
// Cerca parole chiave che potrebbero attivare l'uso degli strumenti
if (userQuery.toLowerCase().includes('ora')) {
return {
message: {
role: "assistant",
content: [
{ type: "tool_call", id: "call_001", name: "get_current_time", input: {} },
{
type: "text",
text: "Controllo l'ora attuale per te."
}
]
}
};
} else if (userQuery.toLowerCase().match(/calcola|calcolare|che cos'è \d+[\+\-\*\/]/)) {
// Estrai un potenziale calcolo
const expression = userQuery.match(/(\d+[\+\-\*\/\(\)\.]*\d+)/)?.[0] || "1+1";
return {
message: {
role: "assistant",
content: [
{
type: "tool_call",
id: "call_002",
name: "calculate",
input: { expression }
},
{
type: "text",
text: `Calcolerò ${expression} per te.`
}
]
}
};
}
// Risposta predefinita per altre richieste
return {
message: {
role: "assistant",
content: [{
type: "text",
text: `Ho ricevuto il tuo messaggio: "${userQuery}". Come posso aiutarti ulteriormente?`
}]
}
};
}
Passaggio 4: Test e distribuzione (2 minuti)
Creiamo un semplice script di test per controllare il nostro server. Crea un file test.js
nella directory principale:
const fetch = require('node-fetch');
async function testServer() {
const url = 'http://localhost:3000/v1/messages';
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
messages: [
{
role: "user",
content: [
{
type: "text",
text: "Che ora è adesso?"
}
]
}
]
})
});
const data = await response.json();
console.log(JSON.stringify(data, null, 2));
}
testServer().catch(console.error);
Per testare il tuo server:
# Avvia il server
npm run dev
# In un altro terminale, esegui il test
node test.js
Per una distribuzione rapida, vediamo di creare un semplice Dockerfile:
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]
Costruisci e avvia il contenitore:
docker build -t mcp-server .
docker run -p 3000:3000 mcp-server
Conclusione
In soli 10 minuti, abbiamo costruito un server di base che implementa i concetti chiave dei moderni protocolli di messaggistica AI. Il nostro server può:
- Elaborare richieste di messaggi strutturati
- Rispondere in un formato standardizzato
- Gestire funzionalità di chiamata agli strumenti di base
- Elaborare e rispondere ai risultati degli strumenti
Sebbene questa implementazione sia semplificata, fornisce una solida base per ulteriori sviluppi. Per estendere questo server per l'uso in produzione, considera:
- Aggiungere autenticazione e limitazione della larghezza di banda
- Implementare una corretta gestione degli errori e convalida
- Collegarsi a modelli AI reali per l'elaborazione
- Aggiungere strumenti più sofisticati
- Implementare risposte in streaming
- Aggiungere registrazione e monitoraggio completo
Ricorda che, mentre la nostra implementazione segue i principi generali dei moderni protocolli di messaggistica AI, implementazioni specifiche come l'API di OpenAI o l'API Claude di Anthropic potrebbero avere requisiti aggiuntivi o lievi variazioni nei loro formati attesi. Consulta sempre la documentazione ufficiale per il servizio specifico con cui stai integrando.
Il campo dei protocolli di messaggistica AI è in rapida evoluzione, quindi rimani aggiornato con gli sviluppi più recenti e preparati ad adattare la tua implementazione man mano che gli standard evolvono. Comprendendo i concetti fondamentali dimostrati in questa guida, sarai ben equipaggiato per lavorare con qualsiasi nuovo sviluppo emerga in questo entusiasmante campo.