Creo applicazioni web moderne e strumenti digitali personalizzati per aiutare le attività a crescere attraverso l'innovazione tecnologica. La mia passione è unire informatica ed economia per generare valore reale.
La mia passione per l'informatica è nata tra i banchi dell'Istituto Tecnico Commerciale di Maglie, dove ho scoperto il potere della programmazione e il fascino di creare soluzioni digitali. Fin da subito, ho capito che l'informatica non era solo codice, ma uno strumento straordinario per trasformare idee in realtà.
Durante gli studi superiori in Sistemi Informativi Aziendali, ho iniziato a intrecciare informatica ed economia, comprendendo come la tecnologia possa essere il motore della crescita per qualsiasi attività. Questa visione mi ha accompagnato all'Università degli Studi di Bari, dove ho conseguito la Laurea in Informatica, approfondendo le mie competenze tecniche e la mia passione per lo sviluppo software.
Oggi metto questa esperienza al servizio di imprese, professionisti e startup, creando soluzioni digitali su misura che automatizzano processi, ottimizzano risorse e aprono nuove opportunità di business. Perché la vera innovazione inizia quando la tecnologia incontra le esigenze reali delle persone.
Le Mie Competenze
Analisi Dati & Modelli Previsionali
Trasformo i dati in insights strategici con analisi approfondite e modelli predittivi per decisioni informate
Automazione Processi
Creo strumenti personalizzati che automatizzano operazioni ripetitive e liberano tempo per attività a valore aggiunto
Sistemi Custom
Sviluppo sistemi software su misura, dalle integrazioni tra piattaforme alle dashboard personalizzate
Credo fermamente che l'informatica sia lo strumento più potente per trasformare le idee in realtà e migliorare la vita delle persone.
Democratizzare la Tecnologia
La mia missione è rendere l'informatica accessibile a tutti: dalle piccole imprese locali alle startup innovative, fino ai professionisti che vogliono digitalizzare la propria attività. Ogni realtà merita di sfruttare le potenzialità del digitale.
Unire Informatica ed Economia
Non è solo questione di scrivere codice: è capire come la tecnologia possa generare valore reale. Intrecciando competenze informatiche e visione economica, aiuto le attività a crescere, ottimizzare processi e raggiungere nuovi traguardi di efficienza e redditività.
Creare Soluzioni su Misura
Ogni attività è unica, e così devono esserlo le soluzioni. Sviluppo strumenti personalizzati che rispondono alle esigenze specifiche di ciascun cliente, automatizzando processi ripetitivi e liberando tempo per ciò che conta davvero: far crescere il business.
Trasforma la Tua Attività con la Tecnologia
Che tu gestisca un negozio, uno studio professionale o un'azienda, posso aiutarti a sfruttare le potenzialità dell'informatica per lavorare meglio, più velocemente e in modo più intelligente.
Il mio percorso accademico e le tecnologie che padroneggio
Certificazioni Professionali
8 certificazioni conseguite
Nuovo
Visualizza
Reinvention With Agentic AI Learning Program
Anthropic
Dicembre 2024
Nuovo
Visualizza
Agentic AI Fluency
Anthropic
Dicembre 2024
Nuovo
Visualizza
AI Fluency for Students
Anthropic
Dicembre 2024
Nuovo
Visualizza
AI Fluency: Framework and Foundations
Anthropic
Dicembre 2024
Nuovo
Visualizza
Claude with the Anthropic API
Anthropic
Dicembre 2024
Visualizza
Master SQL
RoadMap.sh
Novembre 2024
Visualizza
Oracle Certified Foundations Associate
Oracle
Ottobre 2024
Visualizza
People Leadership Credential
Connect
Settembre 2024
Linguaggi & Tecnologie
Java
Python
JavaScript
Angular
React
TypeScript
SQL
PHP
CSS/SCSS
Node.js
Docker
Git
💼
12/2024 - Presente
Custom Software Engineering Analyst
Accenture
Bari, Puglia, Italia · Ibrida
Analisi e sviluppo di sistemi informatici attraverso l'utilizzo di Java e Quarkus in Health and Public Sector. Formazione continua su tecnologie moderne per la creazione di soluzioni software personalizzate ed efficienti e sugli agenti.
💼
06/2022 - 12/2024
Analista software e Back End Developer Associate Consultant
Links Management and Technology SpA
Esperienza nell'analisi di sistemi software as-is e flussi ETL utilizzando PowerCenter. Formazione completata su Spring Boot per lo sviluppo di applicazioni backend moderne e scalabili. Sviluppatore Backend specializzato in Spring Boot, con esperienza in progettazione di database, analisi, sviluppo e testing dei task assegnati.
💼
02/2021 - 10/2021
Programmatore software
Adesso.it (prima era WebScience srl)
Esperienza nell'analisi AS-IS e TO-BE, evoluzioni SEO ed evoluzioni website per migliorare le performance e l'engagement degli utenti.
🎓
2018 - 2025
Laurea in Informatica
Università degli Studi di Bari Aldo Moro
Bachelor's degree in Computer Science, focusing on software engineering, algorithms, and modern development practices.
📚
2013 - 2018
Diploma - Sistemi Informativi Aziendali
Istituto Tecnico Commerciale di Maglie
Technical diploma specializing in Business Information Systems, combining IT knowledge with business management.
Contattami
Hai un progetto in mente? Parliamone! Compila il form qui sotto e ti risponderò al più presto.
* Campi obbligatori. I tuoi dati saranno utilizzati solo per rispondere alla tua richiesta.
Introduzione: Dal Server al Client MCP
Negli articoli precedenti abbiamo costruito server MCP che espongono tool, risorse e prompt. Ma chi
consuma queste funzionalità? Il Client MCP e il componente software che si
connette a uno o più server, scopre i tool disponibili e li invoca per conto di un modello linguistico.
In questo articolo esploreremo come costruire un client MCP programmatico in TypeScript, confronteremo i
due principali meccanismi di trasporto (STDIO e Streamable HTTP),
analizzeremo la gestione delle sessioni e vedremo come integrare un server MCP in un'applicazione web
Express. Tutto il codice e disponibile nel repository
Tech-MCP.
Cosa Imparerai in Questo Articolo
Come creare un client MCP con l'SDK ufficiale TypeScript
Il ciclo tool-use: query, decisione dell'AI, invocazione, risposta
Transport STDIO: connessione locale via processo figlio
Transport Streamable HTTP: server indipendente accessibile via rete
Gestione delle sessioni con session ID e riconnessione
Pattern multi-server e InMemoryTransport per testing
Sicurezza del transport HTTP: Origin validation, autenticazione Bearer
Setup del Progetto Client
Per creare un client MCP da zero, iniziamo con il setup del progetto TypeScript. L'SDK
@modelcontextprotocol/sdk fornisce tutto il necessario per il lato client,
mentre @anthropic-ai/sdk permette di integrare Claude come modello decisionale.
Il transport STDIO e il meccanismo più semplice per connettere un client a un server MCP.
Il client lancia il server come processo figlio e comunica tramite standard input/output.
Ogni messaggio e un oggetto JSON-RPC separato da newline, mentre stderr resta disponibile
per il logging.
Architettura del Client STDIO
Il flusso di una connessione STDIO segue questi passaggi:
Il client crea un StdioClientTransport specificando comando e argomenti del server
Il metodo connect() lancia il processo figlio ed esegue l'handshake initialize / initialized
Il client scopre i tool disponibili con listTools()
A ogni query utente, il client invoca Claude con la lista dei tool e gestisce il ciclo tool-use
Implementazione Completa del Client STDIO
Ecco il codice completo di un client CLI interattivo che usa Claude come modello decisionale:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import Anthropic from "@anthropic-ai/sdk";
import { config } from "dotenv";
import * as readline from "node:readline";
config(); // Carica variabili da .env
// Tipo per i tool compatibili con l'API Anthropic
interface AnthropicTool {
name: string;
description: string | undefined;
input_schema: Record<string, unknown>;
}
class McpChatClient {
private client: Client;
private anthropic: Anthropic;
private tools: AnthropicTool[] = [];
constructor() {
this.client = new Client({
name: "mcp-chat-client",
version: "1.0.0",
});
this.anthropic = new Anthropic();
}
/**
* Connessione al server MCP via STDIO.
* Il client lancia il server come processo figlio.
*/
async connect(serverCommand: string, serverArgs: string[]): Promise<void> {
const transport = new StdioClientTransport({
command: serverCommand,
args: serverArgs,
});
// connect() esegue il lifecycle: initialize -> initialized
await this.client.connect(transport);
// Scopri i tool disponibili
const result = await this.client.listTools();
this.tools = result.tools.map((tool) => ({
name: tool.name,
description: tool.description,
input_schema: tool.inputSchema as Record<string, unknown>,
}));
console.error(
`Connesso. Tool disponibili:
#123;this.tools.map((t) => t.name).join(", ")}`
);
}
/**
* Elabora una query utente con il ciclo tool-use di Claude.
*/
async chat(userMessage: string): Promise<string> {
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: userMessage },
];
let response = await this.anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
messages,
tools: this.tools as Anthropic.Tool[],
});
// Ciclo tool-use: Claude può richiedere più tool in sequenza
while (response.stop_reason === "tool_use") {
const assistantContent = response.content;
messages.push({ role: "assistant", content: assistantContent });
const toolResults: Anthropic.ToolResultBlockParam[] = [];
for (const block of assistantContent) {
if (block.type === "tool_use") {
console.error(` -> Invocazione: #123;block.name}`);
// Chiama il tool sul server MCP
const result = await this.client.callTool({
name: block.name,
arguments: block.input as Record<string, unknown>,
});
const text = (result.content as Array<{ type: string; text: string }>)
.map((c) => c.text)
.join("\n");
toolResults.push({
type: "tool_result",
tool_use_id: block.id,
content: text,
});
}
}
messages.push({ role: "user", content: toolResults });
response = await this.anthropic.messages.create({
model: "claude-sonnet-4-20250514",
max_tokens: 4096,
messages,
tools: this.tools as Anthropic.Tool[],
});
}
return response.content
.filter((block): block is Anthropic.TextBlock => block.type === "text")
.map((block) => block.text)
.join("\n");
}
async disconnect(): Promise<void> {
await this.client.close();
}
}
Il Flusso Tool-Use in Dettaglio
Il cuore del client MCP e il ciclo tool-use, un meccanismo iterativo che permette all'AI
di decidere autonomamente quali tool invocare per rispondere a una query. Ecco il flusso completo:
Il client invia la query utente a Claude insieme alla lista dei tool (nomi, descrizioni, schemi JSON)
Claude analizza la query e decide se usare un tool (risponde con stop_reason: "tool_use")
Il client invoca il tool sul server MCP con client.callTool()
Il risultato torna a Claude come tool_result
Claude può richiedere altri tool o generare la risposta finale (stop_reason: "end_turn")
Transport STDIO vs Streamable HTTP
L'SDK MCP supporta due meccanismi di trasporto fondamentalmente diversi. La scelta tra i due dipende
dal caso d'uso, dall'architettura del sistema e dai requisiti di deployment.
Confronto STDIO vs Streamable HTTP
Aspetto
STDIO
Streamable HTTP
Architettura
Il client lancia il server come processo figlio
Il server e un servizio indipendente
Connessioni
1:1 (un client per processo)
N:1 (molti client, un server)
Deployment
Solo locale, stesso host
Locale o remoto, qualsiasi rete
Sessioni
Implicita (vita del processo)
Esplicita (session ID nell'header)
Autenticazione
Non necessaria (stesso host)
Necessaria (Bearer token, OAuth)
Scalabilità
Limitata (un processo per client)
Alta (un server, molti client)
Caso d'uso
Claude Desktop, IDE, sviluppo locale
Microservizi, API condivise, produzione
Complessità setup
Minima
Richiede server HTTP (Express, Fastify)
In generale, STDIO e la scelta migliore per lo sviluppo locale e l'integrazione con
applicazioni desktop come Claude Desktop e Cursor. Streamable HTTP e necessario quando
il server deve essere accessibile da più client, da host remoti, o in ambienti di produzione containerizzati.
Server MCP con Transport HTTP (Express)
Per esporre un server MCP via HTTP, l'SDK fornisce StreamableHTTPServerTransport che si
integra nativamente con Express. Il server diventa un servizio indipendente che accetta connessioni
da qualsiasi client compatibile.
Setup delle Dipendenze
npm install express
npm install -D @types/express
Implementazione del Server HTTP
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport }
from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
import { randomUUID } from "node:crypto";
import { z } from "zod";
// Crea il server MCP (stessa API di STDIO)
const server = new McpServer({
name: "notes-http-server",
version: "1.0.0",
});
// Registra i tool
const notes: Map<string, string> = new Map();
server.tool(
"add-note",
"Aggiunge una nuova nota",
{
title: z.string(),
content: z.string(),
},
async ({ title, content }) => {
notes.set(title, content);
return {
content: [{ type: "text", text: `Nota "#123;title}" salvata.` }],
};
},
);
// --- Transport HTTP con Express ---
const app = express();
app.use(express.json());
// Crea transport con generatore di sessioni
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
});
// Connetti il server MCP al transport
await server.connect(transport);
// Endpoint MCP: POST (messaggi client -> server)
app.post("/mcp", async (req, res) => {
await transport.handleRequest(req, res, req.body);
});
// Endpoint MCP: GET (SSE stream server -> client)
app.get("/mcp", async (req, res) => {
await transport.handleRequest(req, res);
});
// Endpoint MCP: DELETE (terminazione sessione)
app.delete("/mcp", async (req, res) => {
await transport.handleRequest(req, res);
});
// Health check
app.get("/health", (_req, res) => {
res.json({ status: "ok", server: "notes-http-server" });
});
const PORT = process.env.PORT ?? 3000;
app.listen(PORT, () => {
console.log(`Server MCP HTTP su http://localhost:#123;PORT}/mcp`);
});
Endpoint e Protocollo HTTP
Il server espone un singolo path /mcp con tre metodi HTTP, ognuno con una funzione specifica
nel protocollo MCP:
POST /mcp - Il client invia messaggi JSON-RPC (request, notification, response). Questo e il canale principale per l'invocazione dei tool.
GET /mcp - Il client apre un SSE (Server-Sent Events) stream per ricevere notifiche push dal server in tempo reale.
DELETE /mcp - Il client termina esplicitamente la sessione, liberando le risorse lato server.
Differenza Fondamentale rispetto a STDIO
Con STDIO, ogni connessione crea un nuovo processo server. Con HTTP, il server si connette al transport
una sola volta con server.connect(transport) e gestisce tutte le sessioni
concorrentemente. Il sessionIdGenerator assegna un UUID univoco a ogni client che si connette.
Client MCP con Transport HTTP
Per connettersi a un server HTTP, l'SDK fornisce StreamableHTTPClientTransport. L'API
del client rimane identica a quella STDIO: una volta stabilità la connessione, listTools(),
callTool() e close() funzionano esattamente allo stesso modo.
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport }
from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const client = new Client({
name: "http-client",
version: "1.0.0",
});
const transport = new StreamableHTTPClientTransport(
new URL("http://localhost:3000/mcp"),
);
await client.connect(transport);
// Da qui in poi, stessa API di STDIO
const tools = await client.listTools();
console.log("Tool disponibili:", tools.tools.map((t) => t.name));
const result = await client.callTool({
name: "add-note",
arguments: { title: "Test", content: "Contenuto di prova" },
});
console.log("Risultato:", result);
await client.close();
L'aspetto fondamentale e che il codice applicativo e identico indipendentemente dal transport.
La scelta tra STDIO e HTTP e una decisione infrastrutturale, non architetturale: la logica di business
del client resta invariata.
Gestione delle Sessioni
La gestione delle sessioni e uno degli aspetti più importanti del transport HTTP. Mentre in STDIO la sessione
e implicita (coincide con la vita del processo), in HTTP ogni connessione client riceve un
session ID univoco che deve essere incluso in tutte le richieste successive.
Come Funzionano le Sessioni
Il client invia la prima richiesta initialize senza session ID
Il server genera un UUID con sessionIdGenerator e lo restituisce nell'header Mcp-Session-Id
Il client include Mcp-Session-Id in tutte le richieste successive
Il server associa ogni richiesta alla sessione corretta e mantiene lo stato
Alla disconnessione, il client invia DELETE /mcp per terminare la sessione
Il StreamableHTTPClientTransport gestisce automaticamente:
L'invio dell'header Mcp-Session-Id dopo l'inizializzazione
L'invio dell'header MCP-Protocol-Version in tutte le richieste
La riconnessione in caso di sessione scaduta (HTTP 404)
Pattern Stateful: Stato per Sessione
In produzione, ogni sessione può avere il proprio stato isolato. Ecco come implementarlo:
import { randomUUID } from "node:crypto";
// Mappa sessioni -> stato isolato
const sessions = new Map<string, { notes: Map<string, string> }>();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => {
const sessionId = randomUUID();
sessions.set(sessionId, { notes: new Map() });
return sessionId;
},
});
Connessione a Server Multipli
Un client MCP può connettersi a più server contemporaneamente, aggregando i tool
disponibili in un'unica lista. Questo pattern e fondamentale in architetture complesse dove i tool
sono distribuiti su server specializzati.
class MultiServerClient {
private clients: Map<string, Client> = new Map();
private allTools: AnthropicTool[] = [];
async addServer(
name: string, command: string, args: string[]
): Promise<void> {
const client = new Client({
name: `client-#123;name}`,
version: "1.0.0"
});
const transport = new StdioClientTransport({ command, args });
await client.connect(transport);
const result = await client.listTools();
for (const tool of result.tools) {
this.allTools.push({
// Prefisso per evitare collisioni di nomi
name: `#123;name}__#123;tool.name}`,
description: `[#123;name}] #123;tool.description}`,
input_schema: tool.inputSchema as Record<string, unknown>,
});
}
this.clients.set(name, client);
}
async callTool(
prefixedName: string, args: Record<string, unknown>
): Promise<unknown> {
const [serverName, toolName] = prefixedName.split("__");
const client = this.clients.get(serverName);
if (!client) throw new Error(`Server #123;serverName} non connesso`);
return client.callTool({ name: toolName, arguments: args });
}
}
Il prefisso serverName__toolName e essenziale per evitare collisioni quando server diversi
espongono tool con lo stesso nome. Claude riceve la lista completa e il client si occupa del routing.
Dual Transport: Supportare STDIO e HTTP
Un server MCP professionale dovrebbe supportare entrambi i transport, decidendo quale
usare in base alla configurazione. Questo permette di utilizzare lo stesso codice sia con Claude Desktop
(STDIO) che in produzione (HTTP).
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { StreamableHTTPServerTransport }
from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
import { randomUUID } from "node:crypto";
function createServer(): McpServer {
const server = new McpServer({ name: "my-server", version: "1.0.0" });
// ... registra tool, resource, prompt ...
return server;
}
// Avvio condizionale basato su variabile d'ambiente
const mode = process.env.MCP_TRANSPORT ?? "stdio";
if (mode === "http") {
const server = createServer();
const app = express();
app.use(express.json());
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(),
});
await server.connect(transport);
app.post("/mcp", async (req, res) =>
await transport.handleRequest(req, res, req.body));
app.get("/mcp", async (req, res) =>
await transport.handleRequest(req, res));
app.delete("/mcp", async (req, res) =>
await transport.handleRequest(req, res));
app.get("/health", (_, res) => res.json({ status: "ok" }));
const port = process.env.PORT ?? 3000;
app.listen(port, () => console.log(`HTTP su porta #123;port}`));
} else {
const server = createServer();
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Server avviato su STDIO");
}
Configurazione del Transport
MCP_TRANSPORT=stdio (default) - Per Claude Desktop e IDE locali
MCP_TRANSPORT=http PORT=3000 - Per deployment HTTP e microservizi
InMemoryTransport per il Testing
Per i test unitari non serve lanciare processi o server HTTP. L'SDK fornisce
InMemoryTransport che collega direttamente client e server in memoria:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
async function testInMemory() {
const server = new McpServer({ name: "test-server", version: "1.0.0" });
server.tool("ping", "Test ping", {}, async () => ({
content: [{ type: "text", text: "pong" }],
}));
// Crea coppia di transport collegati
const [clientTransport, serverTransport] =
InMemoryTransport.createLinkedPair();
// IMPORTANTE: il server si connette PRIMA del client
await server.connect(serverTransport);
const client = new Client({ name: "test-client", version: "1.0.0" });
await client.connect(clientTransport);
const result = await client.callTool({ name: "ping", arguments: {} });
console.log(result);
// { content: [{ type: "text", text: "pong" }] }
await client.close();
await server.close();
}
Ordine Critico di Connessione
Con InMemoryTransport, server.connect() deve essere chiamato
prima di client.connect(). Il client invia il messaggio
initialize immediatamente al connect(), e il server deve gia
essere in ascolto per rispondere.
Sicurezza del Transport HTTP
Quando il server MCP e esposto via HTTP, la sicurezza diventa un aspetto critico. Ecco le
misure fondamentali da implementare.
Validazione dell'Origin
Per prevenire attacchi di DNS rebinding, valida l'header Origin di ogni richiesta:
Per server locali, binda solo su 127.0.0.1. Per server remoti, implementa autenticazione
con Bearer token:
// Binding solo locale
app.listen(port, "127.0.0.1", () => {
console.log(`Server solo su localhost:#123;port}`);
});
// Autenticazione per server remoti
app.use("/mcp", (req, res, next) => {
const auth = req.headers.authorization;
if (!auth?.startsWith("Bearer ")) {
res.status(401).json({ error: "Token mancante" });
return;
}
const token = auth.slice(7);
if (!isValidToken(token)) {
res.status(403).json({ error: "Token non valido" });
return;
}
next();
});
Riepilogo
In questo articolo abbiamo esplorato in profondità il lato client del protocollo MCP
e i due meccanismi di trasporto fondamentali. Ecco i concetti chiave:
Un client MCP si connette a uno o più server, scopre i tool e li invoca per conto di un modello linguistico
Il ciclo tool-use permette all'AI di decidere autonomamente quali tool chiamare, con il client che funge da intermediario
STDIO e ideale per lo sviluppo locale e le integrazioni desktop (Claude Desktop, Cursor)
Streamable HTTP e necessario per server remoti, connessioni multiple e ambienti di produzione
La gestione delle sessioni con session ID garantisce l'isolamento dello stato tra client diversi
Il pattern dual transport permette di supportare entrambe le modalità con lo stesso codice
InMemoryTransport semplifica il testing senza processi o server HTTP
Nel prossimo articolo approfondiremo i Pacchetti Condivisi del monorepo Tech-MCP:
il pacchetto Core con utility comuni, l'EventBus per la comunicazione inter-server e i moduli
di accesso al database. Vedremo come strutturare codice riutilizzabile in un monorepo TypeScript
professionale.
Il codice completo di tutti gli esempi e disponibile nel repository
Tech-MCP su GitHub.