pub struct McpClient<T: Transport + 'static> { /* private fields */ }
Expand description
A client implementation of the Model Context Protocol (MCP).
The MCP client provides a synchronous interface for interacting with MCP servers, allowing operations like tool execution, prompt management, and resource handling.
§Examples
use sovran_mcp::{McpClient, transport::StdioTransport};
// Create a client using stdio transport
let transport = StdioTransport::new("npx", &["-y", "@modelcontextprotocol/server-everything"])?;
let mut client = McpClient::new(transport, None, None);
// Start the client (initializes connection and protocol)
client.start()?;
// Use the client...
let tools = client.list_tools()?;
// Clean up when done
client.stop()?;
Implementations§
Source§impl<T: Transport + 'static> McpClient<T>
impl<T: Transport + 'static> McpClient<T>
Sourcepub fn new(
transport: T,
sampling_handler: Option<Box<dyn SamplingHandler + Send>>,
notification_handler: Option<Box<dyn NotificationHandler + Send>>,
) -> Self
pub fn new( transport: T, sampling_handler: Option<Box<dyn SamplingHandler + Send>>, notification_handler: Option<Box<dyn NotificationHandler + Send>>, ) -> Self
Creates a new MCP client with the specified transport and optional handlers.
§Arguments
transport
- The transport implementation to use for communicationsampling_handler
- Optional handler for LLM completion requests from the server. This enables the server to request AI completions through the client while maintaining security boundaries.notification_handler
- Optional handler for receiving one-way messages from the server, such as resource updates.
§Examples
Basic client without handlers:
use sovran_mcp::{McpClient, transport::StdioTransport};
let transport = StdioTransport::new("npx", &["-y", "server-name"])?;
let client = McpClient::new(transport, None, None);
Client with sampling and notification handlers:
use sovran_mcp::{McpClient, transport::StdioTransport, types::*, McpError};
use std::sync::Arc;
use serde_json::Value;
use url::Url; ///
use sovran_mcp::messaging::{LogLevel, NotificationMethod};
// Handler for LLM completion requests
struct MySamplingHandler;
impl SamplingHandler for MySamplingHandler {
fn handle_message(&self, request: CreateMessageRequest) -> Result<CreateMessageResponse, McpError> {
// Process the completion request and return response
Ok(CreateMessageResponse {
content: MessageContent::Text(TextContent {
text: "AI response".to_string()
}),
model: "test-model".to_string(),
role: Role::Assistant,
stop_reason: Some("complete".to_string()),
meta: None,
})
}
}
// Handler for server notifications
struct MyNotificationHandler;
impl NotificationHandler for MyNotificationHandler {
fn handle_resource_update(&self, uri: &Url) -> Result<(), McpError> {
println!("Resource updated: {}", uri);
Ok(())
}
fn handle_initialized(&self) {
todo!()
}
fn handle_log_message(&self, level: &LogLevel, data: &Value, logger: &Option<String>) {
todo!()
}
fn handle_progress_update(&self, token: &String, progress: &f64, total: &Option<f64>) {
todo!()
}
fn handle_list_changed(&self, method: &NotificationMethod) {
todo!()
}
}
// Create client with handlers
let transport = StdioTransport::new("npx", &["-y", "server-name"])?;
let client = McpClient::new(
transport,
Some(Box::new(MySamplingHandler)),
Some(Box::new(MyNotificationHandler))
);
Sourcepub fn start(&mut self) -> Result<(), McpError>
pub fn start(&mut self) -> Result<(), McpError>
Starts the client, establishing the transport connection and initializing the MCP protocol.
This method must be called before using any other client operations. It:
- Opens the transport connection
- Starts the message handling thread
- Performs protocol initialization
§Errors
Returns McpError
if:
- Transport connection fails
- Protocol initialization fails
- Message handling thread cannot be started
§Examples
use sovran_mcp::{McpClient, transport::StdioTransport};
let transport = StdioTransport::new("npx", &["-y", "server-name"])?;
let mut client = McpClient::new(transport, None, None);
// Start the client
client.start()?;
Sourcepub fn stop(&mut self) -> Result<(), McpError>
pub fn stop(&mut self) -> Result<(), McpError>
Stops the client, cleaning up resources and closing the transport connection.
This method:
- Signals the message handling thread to stop
- Closes the transport connection
- Cleans up any pending requests
§Errors
Returns McpError
if:
- The transport close operation fails
- The message handling thread cannot be properly stopped
§Examples
// Use the client...
// Clean up when done
client.stop()?;
Sourcepub fn execute<C: McpCommand>(
&self,
request: C::Request,
) -> Result<C::Response, McpError>
pub fn execute<C: McpCommand>( &self, request: C::Request, ) -> Result<C::Response, McpError>
Executes a generic MCP command on the server.
This is a lower-level method used internally by the specific command methods (list_tools, get_prompt, etc). It can be used to implement custom commands when extending the protocol.
§Type Parameters
C
- A type implementing theMcpCommand
trait, which defines:- The command name (
COMMAND
) - The request type (
Request
) - The response type (
Response
)
- The command name (
§Arguments
request
- The command-specific request data
§Errors
Returns McpError
if:
- The command execution fails on the server
- The request times out
- The response cannot be deserialized
§Examples
use serde::{Serialize, Deserialize};
// Define a custom command
#[derive(Debug, Clone)]
pub struct MyCommand;
impl McpCommand for MyCommand {
const COMMAND: &'static str = "custom/operation";
type Request = MyRequest;
type Response = MyResponse;
}
#[derive(Debug, Serialize)]
pub struct MyRequest {
data: String,
}
#[derive(Debug, Deserialize)]
pub struct MyResponse {
result: String,
}
// Execute the custom command
let request = MyRequest {
data: "test".to_string(),
};
let response = client.execute::<MyCommand>(request)?;
println!("Got result: {}", response.result);
Sourcepub fn server_capabilities(&self) -> Result<ServerCapabilities, McpError>
pub fn server_capabilities(&self) -> Result<ServerCapabilities, McpError>
Returns the server’s capabilities as reported during initialization.
§Errors
Returns McpError::ClientNotInitialized
if called before the client is started.
§Examples
let capabilities = client.server_capabilities()?;
// Check specific capabilities
if let Some(prompts) = capabilities.prompts {
println!("Server supports prompts with list_changed: {:?}",
prompts.list_changed);
}
if let Some(resources) = capabilities.resources {
println!("Server supports resources with subscribe: {:?}",
resources.subscribe);
}
Sourcepub fn server_version(&self) -> Result<String, McpError>
pub fn server_version(&self) -> Result<String, McpError>
Sourcepub fn has_capability<F>(&self, check: F) -> bool
pub fn has_capability<F>(&self, check: F) -> bool
Checks if the server has a specific capability using a custom predicate.
This is a lower-level method used by the specific capability checks. It allows for custom capability testing logic.
§Arguments
check
- A closure that takes a reference to ServerCapabilities and returns a boolean
§Examples
// Check if server supports prompt list change notifications
let has_prompt_notifications = client.has_capability(|caps| {
caps.prompts
.as_ref()
.and_then(|p| p.list_changed)
.unwrap_or(false)
});
println!("Prompt notifications supported: {}", has_prompt_notifications);
Sourcepub fn supports_resources(&self) -> bool
pub fn supports_resources(&self) -> bool
Checks if the server supports resource operations.
Resources are server-managed content that can be read and monitored for changes.
§Returns
Returns true
if the server supports resources, false
otherwise.
§Examples
if client.supports_resources() {
let resources = client.list_resources()?;
println!("Available resources: {}", resources.resources.len());
}
Sourcepub fn supports_prompts(&self) -> bool
pub fn supports_prompts(&self) -> bool
Checks if the server supports prompt operations.
Prompts are server-managed message templates that can be retrieved and processed.
§Returns
Returns true
if the server supports prompts, false
otherwise.
§Examples
if client.supports_prompts() {
let prompts = client.list_prompts()?;
println!("Available prompts: {}", prompts.prompts.len());
}
Sourcepub fn supports_tools(&self) -> bool
pub fn supports_tools(&self) -> bool
Checks if the server supports tool operations.
Tools are server-provided functions that can be called by the client.
§Returns
Returns true
if the server supports tools, false
otherwise.
§Examples
if client.supports_tools() {
let tools = client.list_tools()?;
println!("Available tools: {}", tools.tools.len());
}
Sourcepub fn supports_logging(&self) -> bool
pub fn supports_logging(&self) -> bool
Sourcepub fn supports_resource_subscription(&self) -> bool
pub fn supports_resource_subscription(&self) -> bool
Checks if the server supports resource subscriptions.
Resource subscriptions allow the client to receive notifications when resources change.
§Returns
Returns true
if the server supports resource subscriptions, false
otherwise.
§Examples
if client.supports_resource_subscription() {
println!("Server supports resource change notifications");
}
Sourcepub fn supports_resource_list_changed(&self) -> bool
pub fn supports_resource_list_changed(&self) -> bool
Checks if the server supports resource list change notifications.
Sourcepub fn supports_prompt_list_changed(&self) -> bool
pub fn supports_prompt_list_changed(&self) -> bool
Checks if the server supports prompt list change notifications.
Sourcepub fn supports_experimental_feature(&self, feature: &str) -> bool
pub fn supports_experimental_feature(&self, feature: &str) -> bool
Sourcepub fn list_tools(&self) -> Result<ListToolsResponse, McpError>
pub fn list_tools(&self) -> Result<ListToolsResponse, McpError>
Lists all available tools provided by the server.
§Errors
Returns McpError
if:
- The server doesn’t support tools (
UnsupportedCapability
) - The request fails or times out
- The response cannot be deserialized
§Examples
let tools = client.list_tools()?;
for tool in tools.tools {
println!("Tool: {} - {:?}", tool.name, tool.description);
}
Sourcepub fn call_tool(
&self,
name: String,
arguments: Option<Value>,
) -> Result<CallToolResponse, McpError>
pub fn call_tool( &self, name: String, arguments: Option<Value>, ) -> Result<CallToolResponse, McpError>
Calls a tool on the server with the specified arguments.
§Arguments
name
- The name of the tool to callarguments
- Optional JSON-formatted arguments for the tool
§Errors
Returns McpError
if:
- The server doesn’t support tools (
UnsupportedCapability
) - The specified tool doesn’t exist
- The arguments are invalid for the tool
- The request fails or times out
- The response cannot be deserialized
§Examples
Simple tool call (echo):
let response = client.call_tool(
"echo".to_string(),
Some(serde_json::json!({
"message": "Hello, MCP!"
}))
)?;
// Handle the response
if let Some(content) = response.content.first() {
match content {
ToolResponseContent::Text { text } => println!("Got response: {}", text),
_ => println!("Got non-text response"),
}
}
Tool with numeric arguments:
// Call an "add" tool that sums two numbers
let response = client.call_tool(
"add".to_string(),
Some(serde_json::json!({
"a": 2,
"b": 3
}))
)?;
// Process the response
if let Some(ToolResponseContent::Text { text }) = response.content.first() {
println!("Result: {}", text); // "The sum of 2 and 3 is 5."
}
Sourcepub fn list_prompts(&self) -> Result<ListPromptsResponse, McpError>
pub fn list_prompts(&self) -> Result<ListPromptsResponse, McpError>
Lists all available prompts from the server.
§Errors
Returns McpError
if:
- The server doesn’t support prompts (
UnsupportedCapability
) - The request fails or times out
- The response cannot be deserialized
§Examples
let prompts = client.list_prompts()?;
for prompt in prompts.prompts {
println!("Prompt: {} - {:?}", prompt.name, prompt.description);
// Check for required arguments
if let Some(args) = prompt.arguments {
for arg in args {
println!(" Argument: {} (required: {})",
arg.name,
arg.required.unwrap_or(false)
);
}
}
}
Sourcepub fn get_prompt(
&self,
name: String,
arguments: Option<HashMap<String, String>>,
) -> Result<GetPromptResponse, McpError>
pub fn get_prompt( &self, name: String, arguments: Option<HashMap<String, String>>, ) -> Result<GetPromptResponse, McpError>
Retrieves a specific prompt from the server with optional arguments.
§Arguments
name
- The name of the prompt to retrievearguments
- Optional key-value pairs of arguments for the prompt
§Errors
Returns McpError
if:
- The server doesn’t support prompts (
UnsupportedCapability
) - The specified prompt doesn’t exist
- Required arguments are missing
- The request fails or times out
- The response cannot be deserialized
§Examples
Simple prompt without arguments:
let response = client.get_prompt("simple_prompt".to_string(), None)?;
// Process the messages
for message in response.messages {
match message.role {
Role::System => println!("System: {:?}", message.content),
Role::User => println!("User: {:?}", message.content),
Role::Assistant => println!("Assistant: {:?}", message.content),
}
}
Prompt with arguments:
let mut args = HashMap::new();
args.insert("temperature".to_string(), "0.7".to_string());
args.insert("style".to_string(), "formal".to_string());
let response = client.get_prompt("complex_prompt".to_string(), Some(args))?;
// Process different content types
for message in response.messages {
match &message.content {
PromptContent::Text(text) => {
println!("{}: {}", message.role, text.text);
}
PromptContent::Image(img) => {
println!("{}: Image ({:?})", message.role, img.mime_type);
}
PromptContent::Resource(res) => {
println!("{}: Resource at {}", message.role, res.resource.uri);
}
}
}
Sourcepub fn list_resources(&self) -> Result<ListResourcesResponse, McpError>
pub fn list_resources(&self) -> Result<ListResourcesResponse, McpError>
Lists all available resources from the server.
§Errors
Returns McpError
if:
- The server doesn’t support resources (
UnsupportedCapability
) - The request fails or times out
- The response cannot be deserialized
§Examples
let resources = client.list_resources()?;
for resource in resources.resources {
println!("Resource: {} ({})", resource.name, resource.uri);
if let Some(mime_type) = resource.mime_type {
println!(" Type: {}", mime_type);
}
if let Some(description) = resource.description {
println!(" Description: {}", description);
}
}
Sourcepub fn read_resource(&self, uri: &Url) -> Result<ReadResourceResponse, McpError>
pub fn read_resource(&self, uri: &Url) -> Result<ReadResourceResponse, McpError>
Reads the content of a specific resource.
§Arguments
uri
- The URI of the resource to read
§Errors
Returns McpError
if:
- The server doesn’t support resources (
UnsupportedCapability
) - The specified resource doesn’t exist
- The request fails or times out
- The response cannot be deserialized
§Examples
// Read a resource's contents
let content = client.read_resource(&resource.uri)?;
// Handle different content types
for item in content.contents {
match item {
ResourceContent::Text(text) => {
println!("Text content: {}", text.text);
if let Some(mime) = text.mime_type {
println!("MIME type: {}", mime);
}
}
ResourceContent::Blob(blob) => {
println!("Binary content ({} bytes)", blob.blob.len());
if let Some(mime) = blob.mime_type {
println!("MIME type: {}", mime);
}
}
}
}
Sourcepub fn subscribe(&self, uri: &Url) -> Result<EmptyResult, McpError>
pub fn subscribe(&self, uri: &Url) -> Result<EmptyResult, McpError>
Subscribes to changes for a specific resource.
When subscribed, the client will receive notifications through the NotificationHandler
whenever the resource changes.
§Arguments
uri
- The URI of the resource to subscribe to
§Errors
Returns McpError
if:
- The server doesn’t support resource subscriptions (
UnsupportedCapability
) - The specified resource doesn’t exist
- The request fails or times out
§Examples
// Create a notification handler
struct MyNotificationHandler;
impl NotificationHandler for MyNotificationHandler {
fn handle_resource_update(&self, uri: &Url) -> Result<(), McpError> {
println!("Resource updated: {}", uri);
Ok(())
}
fn handle_log_message(&self, level: &LogLevel, data: &Value, logger: &Option<String>) {
todo!()
}
fn handle_progress_update(&self, token: &String, progress: &f64, total: &Option<f64>) {
todo!()
}
fn handle_initialized(&self) {
todo!()
}
fn handle_list_changed(&self, method: &NotificationMethod) {
todo!()
}
}
// Subscribe to a resource
if client.supports_resource_subscription() {
client.subscribe(&resource.uri)?;
println!("Subscribed to {}", resource.uri);
// ... wait for notifications through handler ...
// Unsubscribe when done
client.unsubscribe(&resource.uri)?;
}
Sourcepub fn unsubscribe(&self, uri: &Url) -> Result<EmptyResult, McpError>
pub fn unsubscribe(&self, uri: &Url) -> Result<EmptyResult, McpError>
Unsubscribes from changes for a specific resource.
§Arguments
uri
- The URI of the resource to unsubscribe from
§Errors
Returns McpError
if:
- The server doesn’t support resource subscriptions (
UnsupportedCapability
) - The specified resource doesn’t exist
- The request fails or times out
§Examples
See the subscribe
method for a complete example including unsubscribe.