Skip to main content
Version: 1.1.0

Client Essentials

An MCP client connects to a server, discovers its capabilities, and invokes them - the mirror image of the server.

The ClientHandler traitโ€‹

Like ServerHandler, ClientHandler has default implementations for all methods. A minimal client needs zero overrides:

struct MyClient;
#[async_trait]
impl ClientHandler for MyClient {}

And a full-featured client can handle incoming requests from the server:

MethodWhat the server is asking
handle_create_message_request"Run an LLM completion for me" (sampling)
handle_list_roots_request"What filesystem roots do you have?"
handle_elicit_request"Ask the user for input"
handle_get_task_request"What's the status of this task?"
handle_progress_notification"Operation is at 50%"
handle_logging_message_notification"I logged something"

The McpClient traitโ€‹

The McpClient object is what you call to interact with the server:

client.request_tool_list(None).await?; // Discover tools
client.request_tool_call(params).await?; // Execute a tool
client.request_resource_list(None).await?; // Discover resources
client.request_resource_read(params).await?; // Read a resource
client.request_prompt_list(None).await?; // Discover prompts
client.request_prompt(params).await?; // Get a prompt
client.server_version().unwrap(); // Who is the server?
client.shut_down().await?; // Graceful disconnect

Connecting to a serverโ€‹

// Stdio - launch the server as a child process
let transport = StdioTransport::create_with_server_launch(
"npx",
vec!["-y".into(), "@modelcontextprotocol/server-everything@2026.8.18".into()],
None,
TransportOptions::default(),
)?;

// Streamable HTTP - connect to a remote server (no transport object needed)
let client = client_runtime::with_transport_options(
client_details,
StreamableTransportOptions {
mcp_url: "http://127.0.0.1:8080/mcp".into(),
request_options: RequestOptions { ..Default::default() },
},
handler,
None, // task_store
None, // server_task_store
None, // message_observer
);
client.clone().start().await?;

// Stdio - create the client with an options struct
let client = client_runtime::create_client(McpClientOptions {
client_details,
transport,
handler: handler.to_mcp_client_handler(),
task_store: None,
server_task_store: None,
message_observer: None,
});
client.clone().start().await?;

The discovery loopโ€‹

Client Server
โ”‚ โ”‚
โ”‚โ”€โ”€ initialize โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ "I'm a client that supports tools"
โ”‚โ—„โ”€โ”€ InitializeResult โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ "I'm weather-mcp v1.0, I have tools + resources"
โ”‚ โ”‚
โ”‚โ”€โ”€ tools/list โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚
โ”‚โ—„โ”€โ”€ ListToolsResult โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ 2 tools: current_weather, forecast
โ”‚ โ”‚
โ”‚โ”€โ”€ resources/list โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚
โ”‚โ—„โ”€โ”€ ListResourcesResult โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ 1 resource: greeting.txt
โ”‚ โ”‚
โ”‚โ”€โ”€ tools/call โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ current_weather{city:"London"}
โ”‚โ—„โ”€โ”€ CallToolResult โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ "Current weather: 24ยฐC, sunny"

Standard vs Core handlerโ€‹

  • ClientHandler - per-message-type methods. Use this in 90% of cases.
  • ClientHandlerCore - three raw methods. Use for full message-level control.

Use client_runtime::create_client() for standard, client_runtime_core::create_client() for core.

Nextโ€‹