I client sono applicazioni personalizzate o script che comunicano direttamente con un Server MCP per richiedere risorse, strumenti e prompt. A differenza dell'uso dello strumento inspector, che fornisce un'interfaccia grafica per interagire con il server, scrivere il proprio client consente interazioni programmatiche e automatizzate. Ciò permette agli sviluppatori di integrare le funzionalità MCP nei propri flussi di lavoro, automatizzare attività e costruire soluzioni personalizzate su misura per esigenze specifiche.
Questa lezione introduce il concetto di client all'interno dell'ecosistema Model Context Protocol (MCP). Imparerai come scrivere il tuo client e farlo connettere a un Server MCP.
Alla fine di questa lezione, sarai in grado di:
- Comprendere cosa può fare un client.
- Scrivere il tuo client.
- Connettere e testare il client con un server MCP per assicurarti che questo funzioni come previsto.
Per scrivere un client, dovrai fare quanto segue:
- Importare le librerie corrette. Userai la stessa libreria di prima, solo costrutti diversi.
- Istanziare un client. Ciò comporterà creare un'istanza client e connetterla al metodo di trasporto scelto.
- Decidere quali risorse elencare. Il tuo server MCP include risorse, strumenti e prompt, devi decidere quali elencare.
- Integrare il client in un'applicazione host. Una volta che conosci le capacità del server, devi integrare questo nel tuo host in modo che se un utente inserisce un prompt o altro comando, venga invocata la funzionalità corrispondente del server.
Ora che abbiamo una visione di alto livello di cosa stiamo per fare, vediamo un esempio.
Diamo un'occhiata a questo esempio di client:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
const transport = new StdioClientTransport({
command: "node",
args: ["server.js"]
});
const client = new Client(
{
name: "example-client",
version: "1.0.0"
}
);
await client.connect(transport);
// Elenca i prompt
const prompts = await client.listPrompts();
// Ottieni un prompt
const prompt = await client.getPrompt({
name: "example-prompt",
arguments: {
arg1: "value"
}
});
// Elenca le risorse
const resources = await client.listResources();
// Leggi una risorsa
const resource = await client.readResource({
uri: "file:///example.txt"
});
// Chiama uno strumento
const result = await client.callTool({
name: "example-tool",
arguments: {
arg1: "value"
}
});Nel codice precedente abbiamo:
- Importato le librerie
- Creato un'istanza di un client e connesso utilizzando stdio come trasporto.
- Elencato prompt, risorse e strumenti e li abbiamo invocati tutti.
Ecco fatto, un client che può comunicare con un Server MCP.
Prendiamoci il tempo nella prossima sezione esercizi per analizzare ogni frammento di codice e spiegare cosa succede.
Come detto sopra, prendiamoci il tempo per spiegare il codice, e ovviamente programmare insieme se vuoi.
Importiamo le librerie necessarie; ci serviranno riferimenti a un client e al protocollo di trasporto scelto, stdio. stdio è un protocollo per cose che devono girare sulla tua macchina locale. SSE è un altro protocollo di trasporto che mostreremo nei capitoli successivi, ma questa è la tua altra opzione. Per ora, continuiamo con stdio.
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";from mcp import ClientSession, StdioServerParameters, types
from mcp.client.stdio import stdio_clientusing Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Client;Per Java, creerai un client che si connette al server MCP dell'esercizio precedente. Usando la stessa struttura di progetto Java Spring Boot di Getting Started with MCP Server, crea una nuova classe Java chiamata SDKClient nella cartella src/main/java/com/microsoft/mcp/sample/client/ e aggiungi i seguenti import:
import java.util.Map;
import org.springframework.web.reactive.function.client.WebClient;
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.client.transport.WebFluxSseClientTransport;
import io.modelcontextprotocol.spec.McpClientTransport;
import io.modelcontextprotocol.spec.McpSchema.CallToolRequest;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import io.modelcontextprotocol.spec.McpSchema.ListToolsResult;Dovrai aggiungere le seguenti dipendenze al tuo file Cargo.toml.
[package]
name = "calculator-client"
version = "0.1.0"
edition = "2024"
[dependencies]
rmcp = { version = "0.5.0", features = ["client", "transport-child-process"] }
serde_json = "1.0.141"
tokio = { version = "1.46.1", features = ["rt-multi-thread"] }Da lì, puoi importare le librerie necessarie nel codice del client.
use rmcp::{
RmcpError,
model::CallToolRequestParam,
service::ServiceExt,
transport::{ConfigureCommandExt, TokioChildProcess},
};
use tokio::process::Command;Passiamo all'istanza.
Dovremo creare un'istanza del trasporto e un'istanza del client:
const transport = new StdioClientTransport({
command: "node",
args: ["server.js"]
});
const client = new Client(
{
name: "example-client",
version: "1.0.0"
}
);
await client.connect(transport);Nel codice precedente abbiamo:
-
Creato un'istanza del trasporto stdio. Nota come vengono specificati comando e argomenti per trovare e avviare il server, poiché è qualcosa che dovremo fare mentre creiamo il client.
const transport = new StdioClientTransport({ command: "node", args: ["server.js"] });
-
Istanziato un client assegnandogli un nome e versione.
const client = new Client( { name: "example-client", version: "1.0.0" });
-
Connesso il client al trasporto scelto.
await client.connect(transport);
from mcp import ClientSession, StdioServerParameters, types
from mcp.client.stdio import stdio_client
# Crea i parametri del server per la connessione stdio
server_params = StdioServerParameters(
command="mcp", # Eseguibile
args=["run", "server.py"], # Argomenti opzionali della linea di comando
env=None, # Variabili d'ambiente opzionali
)
async def run():
async with stdio_client(server_params) as (read, write):
async with ClientSession(
read, write
) as session:
# Inizializza la connessione
await session.initialize()
if __name__ == "__main__":
import asyncio
asyncio.run(run())Nel codice precedente abbiamo:
- Importato le librerie necessarie
- Istanziato un oggetto parametri server poiché lo useremo per avviare il server e connetterci con il client.
- Definito un metodo
runche a sua volta chiamastdio_clientche avvia una sessione client. - Creato un punto d'ingresso in cui forniamo il metodo
runaasyncio.run.
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Client;
var builder = Host.CreateApplicationBuilder(args);
builder.Configuration
.AddEnvironmentVariables()
.AddUserSecrets<Program>();
var clientTransport = new StdioClientTransport(new()
{
Name = "Demo Server",
Command = "dotnet",
Arguments = ["run", "--project", "path/to/file.csproj"],
});
await using var mcpClient = await McpClient.CreateAsync(clientTransport);
Nel codice precedente abbiamo:
- Importato le librerie necessarie.
- Creato un trasporto stdio e un client
mcpClient. Quest'ultimo sarà usato per elencare e invocare funzionalità sul Server MCP.
Nota, in "Arguments", puoi puntare sia al .csproj che all'eseguibile.
public class SDKClient {
public static void main(String[] args) {
var transport = new WebFluxSseClientTransport(WebClient.builder().baseUrl("http://localhost:8080"));
new SDKClient(transport).run();
}
private final McpClientTransport transport;
public SDKClient(McpClientTransport transport) {
this.transport = transport;
}
public void run() {
var client = McpClient.sync(this.transport).build();
client.initialize();
// La logica del tuo client va qui
}
}Nel codice precedente abbiamo:
- Creato un metodo main che imposta un trasporto SSE puntando a
http://localhost:8080dove il server MCP sarà in esecuzione. - Creato una classe client che prende il trasporto come parametro del costruttore.
- Nel metodo
run, creiamo un client MCP sincrono usando il trasporto e inizializziamo la connessione. - Usato il trasporto SSE (Server-Sent Events) adatto per comunicazione HTTP con server MCP Java Spring Boot.
Nota che questo client Rust presume che il server sia un progetto sibling chiamato "calculator-server" nella stessa directory. Il codice sotto avvierà il server e si connetterà ad esso.
async fn main() -> Result<(), RmcpError> {
// Assumere che il server sia un progetto parallelo chiamato "calculator-server" nella stessa directory
let server_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("failed to locate workspace root")
.join("calculator-server");
let client = ()
.serve(
TokioChildProcess::new(Command::new("cargo").configure(|cmd| {
cmd.arg("run").current_dir(server_dir);
}))
.map_err(RmcpError::transport_creation::<TokioChildProcess>)?,
)
.await?;
// DA FARE: Inizializzare
// DA FARE: Elencare gli strumenti
// DA FARE: Chiamare lo strumento add con argomenti = {"a": 3, "b": 2}
client.cancel().await?;
Ok(())
}Ora, abbiamo un client che può connettersi se il programma viene eseguito. Tuttavia, non elenca effettivamente le funzionalità quindi facciamolo ora:
// Elenca i prompt
const prompts = await client.listPrompts();
// Elenca le risorse
const resources = await client.listResources();
// elenca gli strumenti
const tools = await client.listTools();# Elenca le risorse disponibili
resources = await session.list_resources()
print("LISTING RESOURCES")
for resource in resources:
print("Resource: ", resource)
# Elenca gli strumenti disponibili
tools = await session.list_tools()
print("LISTING TOOLS")
for tool in tools.tools:
print("Tool: ", tool.name)Qui elenchiamo le risorse disponibili, list_resources() e gli strumenti, list_tools e li stampiamo.
foreach (var tool in await client.ListToolsAsync())
{
Console.WriteLine($"{tool.Name} ({tool.Description})");
}
Sopra è mostrato un esempio di come possiamo elencare gli strumenti sul server. Per ogni strumento, stampiamo il suo nome.
// Elenca e dimostra gli strumenti
ListToolsResult toolsList = client.listTools();
System.out.println("Available Tools = " + toolsList);
// Puoi anche eseguire il ping del server per verificare la connessione
client.ping();Nel codice precedente abbiamo:
- Chiamato
listTools()per ottenere tutti gli strumenti disponibili dal server MCP. - Usato
ping()per verificare che la connessione al server funzioni. ListToolsResultcontiene informazioni su tutti gli strumenti incluse nome, descrizione e schemi input.
Ottimo, abbiamo catturato tutte le funzionalità. Ora la domanda è quando usiamo queste funzionalità? Beh, questo client è piuttosto semplice, nel senso che dovremo chiamare esplicitamente le funzionalità quando le vogliamo. Nel prossimo capitolo, creeremo un client più avanzato che ha accesso al proprio large language model, LLM. Per ora, vediamo come possiamo invocare le funzionalità sul server:
Nella funzione main, dopo aver inizializzato il client, possiamo inizializzare il server ed elencare alcune sue funzionalità.
// Inizializza
let server_info = client.peer_info();
println!("Server info: {:?}", server_info);
// Elenca gli strumenti
let tools = client.list_tools(Default::default()).await?;
println!("Available tools: {:?}", tools);Per invocare le funzionalità dobbiamo assicurarci di specificare gli argomenti corretti e in alcuni casi il nome di ciò che vogliamo invocare.
// Leggi una risorsa
const resource = await client.readResource({
uri: "file:///example.txt"
});
// Chiama uno strumento
const result = await client.callTool({
name: "example-tool",
arguments: {
arg1: "value"
}
});
// chiama prompt
const promptResult = await client.getPrompt({
name: "review-code",
arguments: {
code: "console.log(\"Hello world\")"
}
})Nel codice precedente abbiamo:
-
Letto una risorsa, la chiamiamo con
readResource()specificandouri. Ecco cosa probabilmente accade lato server:server.resource( "readFile", new ResourceTemplate("file://{name}", { list: undefined }), async (uri, { name }) => ({ contents: [{ uri: uri.href, text: `Hello, ${name}!` }] }) );
Il valore
urinostrofile://example.txtcorrisponde afile://{name}sul server.example.txtsarà mappato aname. -
Chiamato uno strumento, lo chiamiamo specificandone il
namee gliargumentscosì:const result = await client.callTool({ name: "example-tool", arguments: { arg1: "value" } });
-
Ottenuto un prompt, per ottenere un prompt, chiami
getPrompt()connameearguments. Il codice server è così:server.prompt( "review-code", { code: z.string() }, ({ code }) => ({ messages: [{ role: "user", content: { type: "text", text: `Please review this code:\n\n${code}` } }] }) );
e di conseguenza il codice resultante del client è così per corrispondere a quanto dichiarato sul server:
const promptResult = await client.getPrompt({ name: "review-code", arguments: { code: "console.log(\"Hello world\")" } })
# Leggi una risorsa
print("READING RESOURCE")
content, mime_type = await session.read_resource("greeting://hello")
# Chiama uno strumento
print("CALL TOOL")
result = await session.call_tool("add", arguments={"a": 1, "b": 7})
print(result.content)Nel codice precedente abbiamo:
- Chiamato una risorsa chiamata
greetingusandoread_resource. - Invocato uno strumento chiamato
addusandocall_tool.
- Aggiungiamo del codice per chiamare uno strumento:
var result = await mcpClient.CallToolAsync(
"Add",
new Dictionary<string, object?>() { ["a"] = 1, ["b"] = 3 },
cancellationToken:CancellationToken.None);- Per stampare il risultato, ecco del codice che gestisce questo:
Console.WriteLine(result.Content.First(c => c.Type == "text").Text);
// Sum 4// Chiama vari strumenti di calcolatrice
CallToolResult resultAdd = client.callTool(new CallToolRequest("add", Map.of("a", 5.0, "b", 3.0)));
System.out.println("Add Result = " + resultAdd);
CallToolResult resultSubtract = client.callTool(new CallToolRequest("subtract", Map.of("a", 10.0, "b", 4.0)));
System.out.println("Subtract Result = " + resultSubtract);
CallToolResult resultMultiply = client.callTool(new CallToolRequest("multiply", Map.of("a", 6.0, "b", 7.0)));
System.out.println("Multiply Result = " + resultMultiply);
CallToolResult resultDivide = client.callTool(new CallToolRequest("divide", Map.of("a", 20.0, "b", 4.0)));
System.out.println("Divide Result = " + resultDivide);
CallToolResult resultHelp = client.callTool(new CallToolRequest("help", Map.of()));
System.out.println("Help = " + resultHelp);Nel codice precedente abbiamo:
- Chiamato più strumenti calcolatrice usando il metodo
callTool()con oggettiCallToolRequest. - Ogni chiamata strumento specifica il nome dello strumento e una
Mapdi argomenti richiesti da quello strumento. - Gli strumenti server si aspettano nomi parametri specifici (come "a", "b" per operazioni matematiche).
- I risultati sono restituiti come oggetti
CallToolResultcontenenti la risposta dal server.
// Chiamare lo strumento add con argomenti = {"a": 3, "b": 2}
let a = 3;
let b = 2;
let tool_result = client
.call_tool(CallToolRequestParam {
name: "add".into(),
arguments: serde_json::json!({ "a": a, "b": b }).as_object().cloned(),
})
.await?;
println!("Result of {:?} + {:?}: {:?}", a, b, tool_result);Per eseguire il client, digita il seguente comando nel terminale:
Aggiungi la seguente voce nella sezione "scripts" del tuo package.json:
"client": "tsc && node build/client.js"npm run clientLancia il client con il seguente comando:
python client.pydotnet runPrima, assicurati che il tuo server MCP sia in esecuzione su http://localhost:8080. Poi esegui il client:
# Compila il tuo progetto
./mvnw clean compile
# Esegui il client
./mvnw exec:java -Dexec.mainClass="com.microsoft.mcp.sample.client.SDKClient"In alternativa, puoi eseguire il progetto completo del client fornito nella cartella soluzione 03-GettingStarted\02-client\solution\java:
# Naviga nella directory della soluzione
cd 03-GettingStarted/02-client/solution/java
# Compila ed esegui il JAR
./mvnw clean package
java -jar target/calculator-client-0.0.1-SNAPSHOT.jarcargo fmt
cargo runIn questo compito, utilizzerai ciò che hai imparato nella creazione di un client, ma creerai un client tutto tuo.
Ecco un server che puoi usare e che devi chiamare tramite il codice del tuo client, vedi se riesci ad aggiungere altre funzionalità al server per renderlo più interessante.
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Crea un server MCP
const server = new McpServer({
name: "Demo",
version: "1.0.0"
});
// Aggiungi uno strumento di addizione
server.tool("add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }]
})
);
// Aggiungi una risorsa di saluto dinamico
server.resource(
"greeting",
new ResourceTemplate("greeting://{name}", { list: undefined }),
async (uri, { name }) => ({
contents: [{
uri: uri.href,
text: `Hello, ${name}!`
}]
})
);
// Inizia a ricevere messaggi su stdin e inviare messaggi su stdout
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCPServer started on stdin/stdout");
}
main().catch((error) => {
console.error("Fatal error: ", error);
process.exit(1);
});# server.py
from mcp.server.fastmcp import FastMCP
# Crea un server MCP
mcp = FastMCP("Demo")
# Aggiungi uno strumento di addizione
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
# Aggiungi una risorsa di saluto dinamica
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
"""Get a personalized greeting"""
return f"Hello, {name}!"using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
using System.ComponentModel;
var builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(consoleLogOptions =>
{
// Configure all logs to go to stderr
consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
await builder.Build().RunAsync();
[McpServerToolType]
public static class CalculatorTool
{
[McpServerTool, Description("Adds two numbers")]
public static string Add(int a, int b) => $"Sum {a + b}";
}Guarda questo progetto per vedere come puoi aggiungere prompt e risorse.
Inoltre, controlla questo link per come invocare prompt e risorse.
Nella sezione precedente, hai imparato come creare un semplice server MCP con Rust. Puoi continuare a costruire su quello o consultare questo link per più esempi di server MCP basati su Rust: Esempi Server MCP
La cartella soluzione contiene implementazioni client complete e pronte all'uso che dimostrano tutti i concetti trattati in questo tutorial. Ogni soluzione include sia codice client che server organizzati in progetti separati e indipendenti.
La directory della soluzione è organizzata per linguaggio di programmazione:
solution/
├── typescript/ # TypeScript client with npm/Node.js setup
│ ├── package.json # Dependencies and scripts
│ ├── tsconfig.json # TypeScript configuration
│ └── src/ # Source code
├── java/ # Java Spring Boot client project
│ ├── pom.xml # Maven configuration
│ ├── src/ # Java source files
│ └── mvnw # Maven wrapper
├── python/ # Python client implementation
│ ├── client.py # Main client code
│ ├── server.py # Compatible server
│ └── README.md # Python-specific instructions
├── dotnet/ # .NET client project
│ ├── dotnet.csproj # Project configuration
│ ├── Program.cs # Main client code
│ └── dotnet.sln # Solution file
├── rust/ # Rust client implementation
| ├── Cargo.lock # Cargo lock file
| ├── Cargo.toml # Project configuration and dependencies
| ├── src # Source code
| │ └── main.rs # Main client code
└── server/ # Additional .NET server implementation
├── Program.cs # Server code
└── server.csproj # Server project file
Ogni soluzione specifica per linguaggio fornisce:
- Implementazione client completa con tutte le funzionalità del tutorial
- Struttura progetto funzionante con dipendenze e configurazioni appropriate
- Script di build e esecuzione per facile configurazione ed esecuzione
- README dettagliato con istruzioni specifiche per linguaggio
- Esempi di gestione errori e elaborazione risultati
-
Naviga nella cartella del linguaggio preferito:
cd solution/typescript/ # Per TypeScript cd solution/java/ # Per Java cd solution/python/ # Per Python cd solution/dotnet/ # Per .NET
-
Segui le istruzioni nel README di ogni cartella per:
- Installare le dipendenze
- Compilare il progetto
- Eseguire il client
-
Output di esempio che dovresti vedere:
Prompt: Please review this code: console.log("hello"); Resource template: file Tool result: { content: [ { type: 'text', text: '9' } ] }
Per documentazione completa e istruzioni passo-passo, consulta: 📖 Documentazione Soluzione
Abbiamo fornito implementazioni client complete e funzionanti per tutti i linguaggi di programmazione trattati in questo tutorial. Questi esempi mostrano tutte le funzionalità descritte sopra e possono essere usati come riferimento o punto di partenza per i tuoi progetti.
| Linguaggio | File | Descrizione |
|---|---|---|
| Java | client_example_java.java |
Client Java completo usando trasporto SSE con gestione errori approfondita |
| C# | client_example_csharp.cs |
Client C# completo usando trasporto stdio con avvio automatico del server |
| TypeScript | client_example_typescript.ts |
Client TypeScript completo con pieno supporto al protocollo MCP |
| Python | client_example_python.py |
Client Python completo usando pattern async/await |
| Rust | client_example_rust.rs |
Client Rust completo usando Tokio per operazioni async |
Ogni esempio completo include:
- ✅ Stabilire la connessione e gestione degli errori
- ✅ Scoperta del server (strumenti, risorse, prompt dove applicabile)
- ✅ Operazioni della calcolatrice (somma, sottrazione, moltiplicazione, divisione, aiuto)
- ✅ Elaborazione dei risultati e output formattato
- ✅ Gestione completa degli errori
- ✅ Codice pulito e documentato con commenti passo-passo
- Scegli il linguaggio preferito dalla tabella sopra
- Esamina il file di esempio completo per comprendere l'implementazione intera
- Esegui l’esempio seguendo le istruzioni in
complete_examples.md - Modifica ed estendi l’esempio per il tuo caso d’uso specifico
Per documentazione dettagliata su come eseguire e personalizzare questi esempi, consulta: 📖 Documentazione Esempi Completi
| Cartella Soluzione | Esempi Completi |
|---|---|
| Struttura completa del progetto con file di build | Implementazioni in singolo file |
| Pronto per l’esecuzione con dipendenze | Esempi di codice mirati |
| Configurazione simile a produzione | Riferimento educativo |
| Strumenti specifici per il linguaggio | Confronto tra linguaggi |
Entrambi gli approcci sono preziosi - usa la cartella soluzione per progetti completi e gli esempi completi per apprendimento e riferimento.
I punti chiave di questo capitolo riguardo ai client sono:
- Possono essere usati sia per scoprire che per invocare funzionalità sul server.
- Possono avviare un server mentre si avviano da soli (come in questo capitolo), ma i client possono anche connettersi a server già attivi.
- Sono un ottimo modo per testare le capacità del server accanto ad alternative come l’Inspector, come descritto nel capitolo precedente.
- Calcolatrice Java
- Calcolatrice .Net
- Calcolatrice JavaScript
- Calcolatrice TypeScript
- Calcolatrice Python
- Calcolatrice Rust
- Successivo: Creare un client con un LLM
Disclaimer:
Questo documento è stato tradotto utilizzando il servizio di traduzione automatica AI Co-op Translator. Pur impegnandoci per garantire precisione, si prega di essere consapevoli che le traduzioni automatizzate possono contenere errori o inesattezze. Il documento originale nella sua lingua nativa deve essere considerato la fonte autorevole. Per informazioni critiche, si consiglia una traduzione professionale eseguita da un umano. Non siamo responsabili per eventuali fraintendimenti o interpretazioni errate derivanti dall’uso di questa traduzione.