Clientes são aplicativos ou scripts personalizados que se comunicam diretamente com um servidor MCP para solicitar recursos, ferramentas e prompts. Diferentemente do uso da ferramenta de inspeção, que fornece uma interface gráfica para interagir com o servidor, escrever seu próprio cliente permite interações programáticas e automatizadas. Isso possibilita que os desenvolvedores integrem as capacidades do MCP em seus próprios fluxos de trabalho, automatizem tarefas e criem soluções personalizadas adaptadas às necessidades específicas.
Esta lição introduz o conceito de clientes dentro do ecossistema do Model Context Protocol (MCP). Você aprenderá como escrever seu próprio cliente e conectá-lo a um servidor MCP.
Ao final desta lição, você será capaz de:
- Entender o que um cliente pode fazer.
- Escrever seu próprio cliente.
- Conectar e testar o cliente com um servidor MCP para garantir que este funcione conforme esperado.
Para escrever um cliente, você precisará fazer o seguinte:
- Importar as bibliotecas corretas. Você usará a mesma biblioteca de antes, apenas com diferentes construções.
- Instanciar um cliente. Isso envolverá criar uma instância de cliente e conectá-la ao método de transporte escolhido.
- Decidir quais recursos listar. Seu servidor MCP vem com recursos, ferramentas e prompts; você precisa decidir quais listar.
- Integrar o cliente a um aplicativo host. Depois de conhecer as capacidades do servidor, você precisa integrá-lo ao seu aplicativo host para que, se um usuário digitar um prompt ou outro comando, o recurso correspondente do servidor seja invocado.
Agora que entendemos em alto nível o que estamos prestes a fazer, vamos olhar um exemplo a seguir.
Vamos dar uma olhada neste exemplo de cliente:
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);
// List prompts
const prompts = await client.listPrompts();
// Get a prompt
const prompt = await client.getPrompt({
name: "example-prompt",
arguments: {
arg1: "value"
}
});
// List resources
const resources = await client.listResources();
// Read a resource
const resource = await client.readResource({
uri: "file:///example.txt"
});
// Call a tool
const result = await client.callTool({
name: "example-tool",
arguments: {
arg1: "value"
}
});No código anterior, nós:
- Importamos as bibliotecas.
- Criamos uma instância de cliente e a conectamos usando stdio como transporte.
- Listamos prompts, recursos e ferramentas e os invocamos.
Pronto, você tem um cliente que pode se comunicar com um servidor MCP.
Vamos dedicar nosso tempo na próxima seção de exercícios para analisar cada trecho de código e explicar o que está acontecendo.
Como mencionado acima, vamos dedicar nosso tempo explicando o código e, se quiser, acompanhe codificando.
Vamos importar as bibliotecas necessárias. Precisaremos de referências a um cliente e ao protocolo de transporte escolhido, stdio. stdio é um protocolo para coisas que devem ser executadas na sua máquina local. SSE é outro protocolo de transporte que mostraremos em capítulos futuros, mas essa é sua outra opção. Por enquanto, vamos continuar com 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;
using ModelContextProtocol.Protocol.Transport;Para Java, você criará um cliente que se conecta ao servidor MCP do exercício anterior. Usando a mesma estrutura de projeto Java Spring Boot de Introdução ao Servidor MCP, crie uma nova classe Java chamada SDKClient na pasta src/main/java/com/microsoft/mcp/sample/client/ e adicione as seguintes importações:
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;Você precisará adicionar as seguintes dependências ao seu arquivo 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"] }A partir daí, você pode importar as bibliotecas necessárias no código do cliente.
use rmcp::{
RmcpError,
model::CallToolRequestParam,
service::ServiceExt,
transport::{ConfigureCommandExt, TokioChildProcess},
};
use tokio::process::Command;Vamos avançar para a instância.
Precisaremos criar uma instância do transporte e outra do cliente:
const transport = new StdioClientTransport({
command: "node",
args: ["server.js"]
});
const client = new Client(
{
name: "example-client",
version: "1.0.0"
}
);
await client.connect(transport);No código anterior, nós:
-
Criamos uma instância de transporte stdio. Note como ela especifica o comando e os argumentos para localizar e iniciar o servidor, pois isso é algo que precisaremos fazer ao criar o cliente.
const transport = new StdioClientTransport({ command: "node", args: ["server.js"] });
-
Instanciamos um cliente fornecendo um nome e uma versão.
const client = new Client( { name: "example-client", version: "1.0.0" });
-
Conectamos o cliente ao transporte escolhido.
await client.connect(transport);
from mcp import ClientSession, StdioServerParameters, types
from mcp.client.stdio import stdio_client
# Create server parameters for stdio connection
server_params = StdioServerParameters(
command="mcp", # Executable
args=["run", "server.py"], # Optional command line arguments
env=None, # Optional environment variables
)
async def run():
async with stdio_client(server_params) as (read, write):
async with ClientSession(
read, write
) as session:
# Initialize the connection
await session.initialize()
if __name__ == "__main__":
import asyncio
asyncio.run(run())No código anterior, nós:
- Importamos as bibliotecas necessárias.
- Instanciamos um objeto de parâmetros do servidor, pois usaremos isso para executar o servidor e conectá-lo ao nosso cliente.
- Definimos um método
runque, por sua vez, chamastdio_client, iniciando uma sessão de cliente. - Criamos um ponto de entrada onde fornecemos o método
runparaasyncio.run.
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Transport;
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 McpClientFactory.CreateAsync(clientTransport);
No código anterior, nós:
- Importamos as bibliotecas necessárias.
- Criamos um transporte stdio e um cliente
mcpClient. Este último será usado para listar e invocar recursos no servidor MCP.
Nota: em "Arguments", você pode apontar para o arquivo .csproj ou para o executável.
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();
// Your client logic goes here
}
}No código anterior, nós:
- Criamos um método principal que configura um transporte SSE apontando para
http://localhost:8080, onde nosso servidor MCP estará em execução. - Criamos uma classe de cliente que recebe o transporte como parâmetro do construtor.
- No método
run, criamos um cliente MCP síncrono usando o transporte e inicializamos a conexão. - Usamos o transporte SSE (Server-Sent Events), que é adequado para comunicação baseada em HTTP com servidores MCP Java Spring Boot.
Este cliente Rust assume que o servidor é um projeto irmão chamado "calculator-server" no mesmo diretório. O código abaixo iniciará o servidor e se conectará a ele.
async fn main() -> Result<(), RmcpError> {
// Assume the server is a sibling project named "calculator-server" in the same 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?;
// TODO: Initialize
// TODO: List tools
// TODO: Call add tool with arguments = {"a": 3, "b": 2}
client.cancel().await?;
Ok(())
}Agora, temos um cliente que pode se conectar caso o programa seja executado. No entanto, ele não lista seus recursos, então vamos fazer isso a seguir:
// List prompts
const prompts = await client.listPrompts();
// List resources
const resources = await client.listResources();
// list tools
const tools = await client.listTools();# List available resources
resources = await session.list_resources()
print("LISTING RESOURCES")
for resource in resources:
print("Resource: ", resource)
# List available tools
tools = await session.list_tools()
print("LISTING TOOLS")
for tool in tools.tools:
print("Tool: ", tool.name)Aqui, listamos os recursos disponíveis, list_resources() e ferramentas, list_tools, e os imprimimos.
foreach (var tool in await client.ListToolsAsync())
{
Console.WriteLine($"{tool.Name} ({tool.Description})");
}
Acima está um exemplo de como podemos listar as ferramentas no servidor. Para cada ferramenta, imprimimos seu nome.
// List and demonstrate tools
ListToolsResult toolsList = client.listTools();
System.out.println("Available Tools = " + toolsList);
// You can also ping the server to verify connection
client.ping();No código anterior, nós:
- Chamamos
listTools()para obter todas as ferramentas disponíveis no servidor MCP. - Usamos
ping()para verificar se a conexão com o servidor está funcionando. - O
ListToolsResultcontém informações sobre todas as ferramentas, incluindo seus nomes, descrições e esquemas de entrada.
Ótimo, agora capturamos todos os recursos. A questão agora é: quando os usamos? Bem, este cliente é bastante simples, no sentido de que precisaremos chamar explicitamente os recursos quando quisermos. No próximo capítulo, criaremos um cliente mais avançado que terá acesso ao seu próprio modelo de linguagem grande, LLM. Por enquanto, vamos ver como podemos invocar os recursos no servidor:
Na função principal, após inicializar o cliente, podemos inicializar o servidor e listar alguns de seus recursos.
// Initialize
let server_info = client.peer_info();
println!("Server info: {:?}", server_info);
// List tools
let tools = client.list_tools(Default::default()).await?;
println!("Available tools: {:?}", tools);Para invocar os recursos, precisamos garantir que especificamos os argumentos corretos e, em alguns casos, o nome do que estamos tentando invocar.
// Read a resource
const resource = await client.readResource({
uri: "file:///example.txt"
});
// Call a tool
const result = await client.callTool({
name: "example-tool",
arguments: {
arg1: "value"
}
});
// call prompt
const promptResult = await client.getPrompt({
name: "review-code",
arguments: {
code: "console.log(\"Hello world\")"
}
})No código anterior, nós:
-
Lemos um recurso, chamamos o recurso usando
readResource()especificandouri. Veja como isso provavelmente se parece no lado do servidor:server.resource( "readFile", new ResourceTemplate("file://{name}", { list: undefined }), async (uri, { name }) => ({ contents: [{ uri: uri.href, text: `Hello, ${name}!` }] }) );
Nosso valor
urifile://example.txtcorresponde afile://{name}no servidor.example.txtserá mapeado paraname. -
Chamamos uma ferramenta, especificamos seu
namee seusargumentsassim:const result = await client.callTool({ name: "example-tool", arguments: { arg1: "value" } });
-
Obtemos um prompt, para obter um prompt, chamamos
getPrompt()comnameearguments. O código do servidor se parece com isso:server.prompt( "review-code", { code: z.string() }, ({ code }) => ({ messages: [{ role: "user", content: { type: "text", text: `Please review this code:\n\n${code}` } }] }) );
E, portanto, o código do cliente resultante se parece com isso para corresponder ao que foi declarado no servidor:
const promptResult = await client.getPrompt({ name: "review-code", arguments: { code: "console.log(\"Hello world\")" } })
# Read a resource
print("READING RESOURCE")
content, mime_type = await session.read_resource("greeting://hello")
# Call a tool
print("CALL TOOL")
result = await session.call_tool("add", arguments={"a": 1, "b": 7})
print(result.content)No código anterior, nós:
- Chamamos um recurso chamado
greetingusandoread_resource. - Invocamos uma ferramenta chamada
addusandocall_tool.
- Vamos adicionar algum código para chamar uma ferramenta:
var result = await mcpClient.CallToolAsync(
"Add",
new Dictionary<string, object?>() { ["a"] = 1, ["b"] = 3 },
cancellationToken:CancellationToken.None);- Para imprimir o resultado, aqui está um código para lidar com isso:
Console.WriteLine(result.Content.First(c => c.Type == "text").Text);
// Sum 4// Call various calculator tools
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);No código anterior, nós:
- Chamamos várias ferramentas de calculadora usando o método
callTool()com objetosCallToolRequest. - Cada chamada de ferramenta especifica o nome da ferramenta e um
Mapde argumentos necessários para essa ferramenta. - As ferramentas do servidor esperam nomes de parâmetros específicos (como "a", "b" para operações matemáticas).
- Os resultados são retornados como objetos
CallToolResultcontendo a resposta do servidor.
// Call add tool with arguments = {"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);Para executar o cliente, digite o seguinte comando no terminal:
Adicione a seguinte entrada à seção "scripts" em package.json:
"client": "tsc && node build/client.js"npm run clientChame o cliente com o seguinte comando:
python client.pydotnet runPrimeiro, certifique-se de que seu servidor MCP está em execução em http://localhost:8080. Em seguida, execute o cliente:
# Build you project
./mvnw clean compile
# Run the client
./mvnw exec:java -Dexec.mainClass="com.microsoft.mcp.sample.client.SDKClient"Alternativamente, você pode executar o projeto completo do cliente fornecido na pasta de solução 03-GettingStarted\02-client\solution\java:
# Navigate to the solution directory
cd 03-GettingStarted/02-client/solution/java
# Build and run the JAR
./mvnw clean package
java -jar target/calculator-client-0.0.1-SNAPSHOT.jarcargo fmt
cargo runNesta tarefa, você usará o que aprendeu ao criar um cliente, mas criará um cliente próprio.
Aqui está um servidor que você pode usar e que precisa ser chamado via seu código de cliente. Veja se consegue adicionar mais recursos ao servidor para torná-lo mais interessante.
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// Create an MCP server
const server = new McpServer({
name: "Demo",
version: "1.0.0"
});
// Add an addition tool
server.tool("add",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }]
})
);
// Add a dynamic greeting resource
server.resource(
"greeting",
new ResourceTemplate("greeting://{name}", { list: undefined }),
async (uri, { name }) => ({
contents: [{
uri: uri.href,
text: `Hello, ${name}!`
}]
})
);
// Start receiving messages on stdin and sending messages on 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
# Create an MCP server
mcp = FastMCP("Demo")
# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
# Add a dynamic greeting resource
@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}";
}Veja este projeto para saber como adicionar prompts e recursos.
Além disso, confira este link para saber como invocar prompts e recursos.
Na seção anterior, você aprendeu como criar um servidor MCP simples com Rust. Você pode continuar a construir sobre isso ou verificar este link para mais exemplos de servidores MCP baseados em Rust: Exemplos de Servidores MCP
A pasta de solução contém implementações completas e prontas para uso de clientes que demonstram todos os conceitos abordados neste tutorial. Cada solução inclui código de cliente e servidor organizado em projetos separados e autossuficientes.
O diretório de solução está organizado por linguagem de programação:
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
Cada solução específica de linguagem fornece:
- Implementação completa do cliente com todos os recursos do tutorial.
- Estrutura de projeto funcional com dependências e configuração adequadas.
- Scripts de construção e execução para configuração e execução fáceis.
- README detalhado com instruções específicas para cada linguagem.
- Exemplos de tratamento de erros e processamento de resultados.
-
Navegue até a pasta da sua linguagem preferida:
cd solution/typescript/ # For TypeScript cd solution/java/ # For Java cd solution/python/ # For Python cd solution/dotnet/ # For .NET
-
Siga as instruções do README em cada pasta para:
- Instalar dependências.
- Construir o projeto.
- Executar o cliente.
-
Exemplo de saída que você deve ver:
Prompt: Please review this code: console.log("hello"); Resource template: file Tool result: { content: [ { type: 'text', text: '9' } ] }
Para documentação completa e instruções passo a passo, veja: 📖 Documentação da Solução
Fornecemos implementações completas e funcionais de clientes para todas as linguagens de programação abordadas neste tutorial. Esses exemplos demonstram toda a funcionalidade descrita acima e podem ser usados como implementações de referência ou pontos de partida para seus próprios projetos.
| Linguagem | Arquivo | Descrição |
|---|---|---|
| Java | client_example_java.java |
Cliente Java completo usando transporte SSE com tratamento abrangente de erros |
| C# | client_example_csharp.cs |
Cliente C# completo usando transporte stdio com inicialização automática do servidor |
| TypeScript | client_example_typescript.ts |
Cliente TypeScript completo com suporte total ao protocolo MCP |
| Python | client_example_python.py |
Cliente Python completo usando padrões async/await |
| Rust | client_example_rust.rs |
Cliente Rust completo usando Tokio para operações assíncronas |
| Cada exemplo completo inclui: |
- ✅ Estabelecimento de conexão e tratamento de erros
- ✅ Descoberta do servidor (ferramentas, recursos, prompts, onde aplicável)
- ✅ Operações da calculadora (somar, subtrair, multiplicar, dividir, ajuda)
- ✅ Processamento de resultados e saída formatada
- ✅ Tratamento abrangente de erros
- ✅ Código limpo e documentado com comentários passo a passo
- Escolha seu idioma preferido na tabela acima
- Revise o arquivo de exemplo completo para entender a implementação completa
- Execute o exemplo seguindo as instruções em
complete_examples.md - Modifique e amplie o exemplo para atender ao seu caso de uso específico
Para documentação detalhada sobre como executar e personalizar esses exemplos, veja: 📖 Documentação de Exemplos Completos
| Pasta de Solução | Exemplos Completos |
|---|---|
| Estrutura completa do projeto com arquivos de build | Implementações em arquivo único |
| Pronto para executar com dependências | Exemplos de código focados |
| Configuração semelhante à produção | Referência educacional |
| Ferramentas específicas para cada linguagem | Comparação entre linguagens |
Ambas as abordagens são valiosas - use a pasta de solução para projetos completos e os exemplos completos para aprendizado e referência.
Os principais pontos deste capítulo sobre clientes são os seguintes:
- Podem ser usados tanto para descobrir quanto para invocar funcionalidades no servidor.
- Podem iniciar um servidor enquanto se iniciam (como neste capítulo), mas também podem se conectar a servidores já em execução.
- São uma ótima maneira de testar as capacidades do servidor, além de alternativas como o Inspector, descrito no capítulo anterior.
- Calculadora em Java
- Calculadora em .Net
- Calculadora em JavaScript
- Calculadora em TypeScript
- Calculadora em Python
- Calculadora em Rust
- Próximo: Criando um cliente com um LLM
Aviso Legal:
Este documento foi traduzido utilizando o serviço de tradução por IA Co-op Translator. Embora nos esforcemos para garantir a precisão, esteja ciente de que traduções automáticas podem conter erros ou imprecisões. O documento original em seu idioma nativo deve ser considerado a fonte oficial. Para informações críticas, recomenda-se a tradução profissional feita por humanos. Não nos responsabilizamos por quaisquer mal-entendidos ou interpretações equivocadas decorrentes do uso desta tradução.