Struct McpClient

Source
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>

Source

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 communication
  • sampling_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))
);
Source

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()?;
Source

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()?;
Source

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 the McpCommand trait, which defines:
    • The command name (COMMAND)
    • The request type (Request)
    • The response type (Response)
§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);
Source

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);
}
Source

pub fn server_version(&self) -> Result<String, McpError>

Returns the protocol version supported by the server.

§Errors

Returns McpError::ClientNotInitialized if called before the client is started.

§Examples
let version = client.server_version()?;
println!("Server supports MCP version: {}", version);
Source

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);
Source

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());
}
Source

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());
}
Source

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());
}
Source

pub fn supports_logging(&self) -> bool

Checks if the server supports logging capabilities.

§Returns

Returns true if the server supports logging, false otherwise.

§Examples
if client.supports_logging() {
    println!("Server supports logging capabilities");
}
Source

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");
}
Source

pub fn supports_resource_list_changed(&self) -> bool

Checks if the server supports resource list change notifications.

Source

pub fn supports_prompt_list_changed(&self) -> bool

Checks if the server supports prompt list change notifications.

Source

pub fn supports_experimental_feature(&self, feature: &str) -> bool

Checks if the server supports a specific experimental feature.

§Arguments
  • feature - The name of the experimental feature to check
§Examples
if client.supports_experimental_feature("my_feature") {
    println!("Server supports experimental feature 'my_feature'");
}
Source

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);
}
Source

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 call
  • arguments - 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."
}
Source

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)
            );
        }
    }
}
Source

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 retrieve
  • arguments - 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);
        }
    }
}
Source

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);
    }
}
Source

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);
            }
        }
    }
}
Source

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)?;
}
Source

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.

Trait Implementations§

Source§

impl<T: Transport + 'static> Drop for McpClient<T>

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more

Auto Trait Implementations§

§

impl<T> Freeze for McpClient<T>

§

impl<T> !RefUnwindSafe for McpClient<T>

§

impl<T> Send for McpClient<T>

§

impl<T> Sync for McpClient<T>

§

impl<T> Unpin for McpClient<T>

§

impl<T> !UnwindSafe for McpClient<T>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,