turbomcp_client/
sampling.rs

1//! MCP-Compliant Client-Side Sampling Support
2//!
3//! This module provides the correct MCP architecture for handling sampling requests.
4//! The client's role is to:
5//! 1. Receive sampling/createMessage requests from servers
6//! 2. Present them to users for approval (human-in-the-loop)
7//! 3. Delegate to external LLM services (which can be MCP servers themselves)
8//! 4. Return standardized results
9//!
10//! ## Perfect MCP Compliance
11//!
12//! Unlike embedding LLM APIs directly (anti-pattern), this implementation:
13//! - Delegates to external services
14//! - Maintains protocol boundaries
15//! - Enables composition and flexibility
16//! - Provides maximum developer experience through simplicity
17
18use async_trait::async_trait;
19use std::sync::Arc;
20use turbomcp_protocol::types::{CreateMessageRequest, CreateMessageResult};
21
22/// MCP-compliant sampling handler trait
23///
24/// The client receives sampling requests and delegates to configured LLM services.
25/// This maintains perfect separation of concerns per MCP specification.
26#[async_trait]
27pub trait SamplingHandler: Send + Sync + std::fmt::Debug {
28    /// Handle a sampling/createMessage request from a server
29    ///
30    /// This method should:
31    /// 1. Present the request to the user for approval
32    /// 2. Delegate to an external LLM service (could be another MCP server)
33    /// 3. Present the result to the user for review
34    /// 4. Return the approved result
35    ///
36    /// # Arguments
37    ///
38    /// * `request_id` - The JSON-RPC request ID from the server for proper response correlation
39    /// * `request` - The sampling request parameters
40    async fn handle_create_message(
41        &self,
42        request_id: String,
43        request: CreateMessageRequest,
44    ) -> Result<CreateMessageResult, Box<dyn std::error::Error + Send + Sync>>;
45}
46
47/// Default implementation that delegates to external MCP servers
48///
49/// This is the "batteries included" approach - it connects to LLM MCP servers
50/// but maintains perfect protocol compliance.
51#[derive(Debug)]
52pub struct DelegatingSamplingHandler {
53    /// Client instances for LLM MCP servers
54    llm_clients: Vec<Arc<dyn LLMServerClient>>,
55    /// User interaction handler
56    user_handler: Arc<dyn UserInteractionHandler>,
57}
58
59/// Interface for connecting to LLM MCP servers
60#[async_trait]
61pub trait LLMServerClient: Send + Sync + std::fmt::Debug {
62    /// Forward a sampling request to an LLM MCP server
63    async fn create_message(
64        &self,
65        request: CreateMessageRequest,
66    ) -> Result<CreateMessageResult, Box<dyn std::error::Error + Send + Sync>>;
67
68    /// Get server capabilities/model info
69    async fn get_server_info(&self)
70    -> Result<ServerInfo, Box<dyn std::error::Error + Send + Sync>>;
71}
72
73/// Interface for user interaction (human-in-the-loop)
74#[async_trait]
75pub trait UserInteractionHandler: Send + Sync + std::fmt::Debug {
76    /// Present sampling request to user for approval
77    async fn approve_request(
78        &self,
79        request: &CreateMessageRequest,
80    ) -> Result<bool, Box<dyn std::error::Error + Send + Sync>>;
81
82    /// Present result to user for review
83    async fn approve_response(
84        &self,
85        request: &CreateMessageRequest,
86        response: &CreateMessageResult,
87    ) -> Result<Option<CreateMessageResult>, Box<dyn std::error::Error + Send + Sync>>;
88}
89
90/// Server information for model selection
91#[derive(Debug, Clone)]
92pub struct ServerInfo {
93    pub name: String,
94    pub models: Vec<String>,
95    pub capabilities: Vec<String>,
96}
97
98#[async_trait]
99impl SamplingHandler for DelegatingSamplingHandler {
100    async fn handle_create_message(
101        &self,
102        _request_id: String,
103        request: CreateMessageRequest,
104    ) -> Result<CreateMessageResult, Box<dyn std::error::Error + Send + Sync>> {
105        // 1. Human-in-the-loop: Get user approval
106        if !self.user_handler.approve_request(&request).await? {
107            // FIXED: Return HandlerError::UserCancelled (code -1) instead of string error
108            // This ensures the error code is preserved when sent back to the server
109            return Err(Box::new(crate::handlers::HandlerError::UserCancelled));
110        }
111
112        // 2. Select appropriate LLM server based on model preferences
113        let selected_client = self.select_llm_client(&request).await?;
114
115        // 3. Delegate to external LLM MCP server
116        let result = selected_client.create_message(request.clone()).await?;
117
118        // 4. Present result for user review
119        let approved_result = self
120            .user_handler
121            .approve_response(&request, &result)
122            .await?;
123
124        Ok(approved_result.unwrap_or(result))
125    }
126}
127
128impl DelegatingSamplingHandler {
129    /// Create new handler with LLM server clients
130    pub fn new(
131        llm_clients: Vec<Arc<dyn LLMServerClient>>,
132        user_handler: Arc<dyn UserInteractionHandler>,
133    ) -> Self {
134        Self {
135            llm_clients,
136            user_handler,
137        }
138    }
139
140    /// Select best LLM client based on model preferences
141    async fn select_llm_client(
142        &self,
143        _request: &CreateMessageRequest,
144    ) -> Result<Arc<dyn LLMServerClient>, Box<dyn std::error::Error + Send + Sync>> {
145        // This is where the intelligence goes - matching model preferences
146        // to available LLM servers, exactly as the MCP spec describes
147
148        if let Some(first_client) = self.llm_clients.first() {
149            Ok(first_client.clone())
150        } else {
151            // FIXED: Return HandlerError::Configuration instead of string error
152            // This ensures proper error code mapping (-32601)
153            Err(Box::new(crate::handlers::HandlerError::Configuration {
154                message: "No LLM servers configured".to_string(),
155            }))
156        }
157    }
158}
159
160/// Default user handler that automatically approves (for development)
161#[derive(Debug)]
162pub struct AutoApprovingUserHandler;
163
164#[async_trait]
165impl UserInteractionHandler for AutoApprovingUserHandler {
166    async fn approve_request(
167        &self,
168        _request: &CreateMessageRequest,
169    ) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
170        Ok(true) // Auto-approve for development
171    }
172
173    async fn approve_response(
174        &self,
175        _request: &CreateMessageRequest,
176        _response: &CreateMessageResult,
177    ) -> Result<Option<CreateMessageResult>, Box<dyn std::error::Error + Send + Sync>> {
178        Ok(None) // Auto-approve, don't modify
179    }
180}