Guida completa al Client MCP: costruisci un chatbot AI con Model Context Protocol

ai-integration - 08/05/2025

9 min read

Introduzione

Il Model Context Protocol (MCP) è uno standard aperto che permette di collegare in modo sicuro modelli AI e servizi esterni. In questa guida pratica imparerai a creare da zero un client MCP in Python capace di:

  1. Avviare o connettersi a un server MCP locale o remoto.
  2. Passare i tool esposti dal server a Claude (o un altro LLM compatibile) — che deciderà se e come usarli.
  3. Gestire le chiamate ai tool, restituendo all’utente una risposta naturale.

Seguiremo il quickstart ufficiale per client pubblicato sul sito di Model Context Protocol e ne estrarremo i passaggi fondamentali.(modelcontextprotocol.io)


Cos’è MCP e perché serve un client

MCP definisce messaggi strutturati (JSON) per descrivere tool, invocarli e restituire risultati. Un client MCP fa da ponte tra il modello linguistico (Claude, GPT, ecc.) e i server MCP che custodiscono dati o funzionalità:

  1. Scoperta tool – il client chiede list_tools al server.
  2. Conversazione – passa la tua richiesta e la lista tool al modello.
  3. Esecuzione tool – se il modello decide di usare un tool, il client chiama call_tool sul server e rimanda il risultato al modello.

Questa architettura disaccoppia UI / logica AI dal backend che ospita le risorse. Risultato: più sicurezza, riuso dei tool ed evoluzione indipendente dei componenti.(modelcontextprotocol.io)


Requisiti e setup dell’ambiente

Prerequisiti

  • Python (versione recente)
  • uv per gestire ambiente e dipendenze
  • Chiave API Anthropic (Claude)

1. Creazione progetto e virtualenv

# Creazione cartella
uv init mcp-client
cd mcp-client

# Virtual environment
uv venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate

# Dipendenze
uv add mcp anthropic python-dotenv

2. Gestione API Key

touch .env
echo "ANTHROPIC_API_KEY=<la_tua_chiave>" >> .env
echo ".env" >> .gitignore

Struttura base del client

Crea client.py e incolla il boilerplate seguente.

import asyncio
import os
import sys
from typing import Optional
from contextlib import AsyncExitStack

from dotenv import load_dotenv
from anthropic import Anthropic
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

load_dotenv()

class MCPClient:
    """Client MCP minimale con supporto a Claude."""

    def __init__(self):
        self.session: Optional[ClientSession] = None
        self.exit_stack = AsyncExitStack()
        self.anthropic = Anthropic()

Connessione al server MCP

    async def connect(self, server_script: str):
        is_py = server_script.endswith(".py")
        is_js = server_script.endswith(".js")
        if not (is_py or is_js):
            raise ValueError("Il server deve essere .py o .js")

        cmd = "python" if is_py else "node"
        params = StdioServerParameters(command=cmd, args=[server_script])

        stdio = await self.exit_stack.enter_async_context(stdio_client(params))
        self.read, self.write = stdio
        self.session = await self.exit_stack.enter_async_context(ClientSession(self.read, self.write))
        await self.session.initialize()

        tools = (await self.session.list_tools()).tools
        self.tools = [
            {"name": t.name, "description": t.description, "input_schema": t.inputSchema}
            for t in tools
        ]
        print("Connesso al server. Tool disponibili:", [t["name"] for t in self.tools])

Elaborazione delle query e chiamate tool

    async def process_query(self, query: str) -> str:
        messages = [{"role": "user", "content": query}]

        # Primo round con i tool a disposizione
        response = self.anthropic.messages.create(
            model="claude-3-5-sonnet-20241022",
            max_tokens=1000,
            messages=messages,
            tools=self.tools,
        )

        final = []
        for block in response.content:
            if block.type == "text":
                final.append(block.text)
            elif block.type == "tool_use":
                result = await self.session.call_tool(block.name, block.input)
                messages += [
                    {"role": "assistant", "content": [block]},
                    {
                        "role": "user",
                        "content": [
                            {
                                "type": "tool_result",
                                "tool_use_id": block.id,
                                "content": result.content,
                            }
                        ],
                    },
                ]
                # Secondo round dopo il tool
                follow_up = self.anthropic.messages.create(
                    model="claude-3-5-sonnet-20241022",
                    max_tokens=1000,
                    messages=messages,
                )
                final.append(follow_up.content[0].text)
        return "\n".join(final)

Ciclo chat interattivo & pulizia

    async def chat(self):
        print("\nMCP Client pronto! ('quit' per uscire)")
        while True:
            q = input("\nQuery: ").strip()
            if q.lower() == "quit":
                break
            print(await self.process_query(q))

    async def close(self):
        await self.exit_stack.aclose()

async def main():
    if len(sys.argv) < 2:
        print("Uso: python client.py <path_server.py|js>")
        sys.exit(1)
    client = MCPClient()
    try:
        await client.connect(sys.argv[1])
        await client.chat()
    finally:
        await client.close()

if __name__ == "__main__":
    asyncio.run(main())

Esecuzione del client

uv run client.py path/to/weather_server.py  # server Python
uv run client.py path/to/build/index.js    # server Node

Il flusso sarà:

  1. Connessione e discovery dei tool.
  2. Conversazione con Claude.
  3. Tool call automatiche.
  4. Risposta all’utente.(modelcontextprotocol.io)

Best practice e personalizzazioni

  1. Gestione errori – wrappa ogni call_tool in try/except e logga gli errori.
  2. Cleanup risorse – usa AsyncExitStack per chiudere trasporti e sessioni.
  3. Sicurezza – mantieni le chiavi API in .env e valida l’input proveniente dai server.
  4. UI avanzata – sostituisci il loop CLI con una GUI (Tkinter) o un frontend web usando websockets.
  5. Caching – se un tool è costoso, memorizza i risultati per richieste successive identiche.

Conclusione

Hai ora un client MCP funzionante che può parlare con qualsiasi server MCP e arricchire le risposte di Claude con tool personalizzati. Espandi il progetto aggiungendo nuovi tool server‑side o integrando modelli diversi dal solito LLM.


Ti serve aiuto per integrare MCP nel tuo stack?

👉 Contattami ora