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    async fn handle_create_message(
36        &self,
37        request: CreateMessageRequest,
38    ) -> Result<CreateMessageResult, Box<dyn std::error::Error + Send + Sync>>;
39}
40
41/// Default implementation that delegates to external MCP servers
42///
43/// This is the "batteries included" approach - it connects to LLM MCP servers
44/// but maintains perfect protocol compliance.
45#[derive(Debug)]
46pub struct DelegatingSamplingHandler {
47    /// Client instances for LLM MCP servers
48    llm_clients: Vec<Arc<dyn LLMServerClient>>,
49    /// User interaction handler
50    user_handler: Arc<dyn UserInteractionHandler>,
51}
52
53/// Interface for connecting to LLM MCP servers
54#[async_trait]
55pub trait LLMServerClient: Send + Sync + std::fmt::Debug {
56    /// Forward a sampling request to an LLM MCP server
57    async fn create_message(
58        &self,
59        request: CreateMessageRequest,
60    ) -> Result<CreateMessageResult, Box<dyn std::error::Error + Send + Sync>>;
61
62    /// Get server capabilities/model info
63    async fn get_server_info(&self)
64    -> Result<ServerInfo, Box<dyn std::error::Error + Send + Sync>>;
65}
66
67/// Interface for user interaction (human-in-the-loop)
68#[async_trait]
69pub trait UserInteractionHandler: Send + Sync + std::fmt::Debug {
70    /// Present sampling request to user for approval
71    async fn approve_request(
72        &self,
73        request: &CreateMessageRequest,
74    ) -> Result<bool, Box<dyn std::error::Error + Send + Sync>>;
75
76    /// Present result to user for review
77    async fn approve_response(
78        &self,
79        request: &CreateMessageRequest,
80        response: &CreateMessageResult,
81    ) -> Result<Option<CreateMessageResult>, Box<dyn std::error::Error + Send + Sync>>;
82}
83
84/// Server information for model selection
85#[derive(Debug, Clone)]
86pub struct ServerInfo {
87    pub name: String,
88    pub models: Vec<String>,
89    pub capabilities: Vec<String>,
90}
91
92#[async_trait]
93impl SamplingHandler for DelegatingSamplingHandler {
94    async fn handle_create_message(
95        &self,
96        request: CreateMessageRequest,
97    ) -> Result<CreateMessageResult, Box<dyn std::error::Error + Send + Sync>> {
98        // 1. Human-in-the-loop: Get user approval
99        if !self.user_handler.approve_request(&request).await? {
100            return Err("User rejected sampling request".into());
101        }
102
103        // 2. Select appropriate LLM server based on model preferences
104        let selected_client = self.select_llm_client(&request).await?;
105
106        // 3. Delegate to external LLM MCP server
107        let result = selected_client.create_message(request.clone()).await?;
108
109        // 4. Present result for user review
110        let approved_result = self
111            .user_handler
112            .approve_response(&request, &result)
113            .await?;
114
115        Ok(approved_result.unwrap_or(result))
116    }
117}
118
119impl DelegatingSamplingHandler {
120    /// Create new handler with LLM server clients
121    pub fn new(
122        llm_clients: Vec<Arc<dyn LLMServerClient>>,
123        user_handler: Arc<dyn UserInteractionHandler>,
124    ) -> Self {
125        Self {
126            llm_clients,
127            user_handler,
128        }
129    }
130
131    /// Select best LLM client based on model preferences
132    async fn select_llm_client(
133        &self,
134        _request: &CreateMessageRequest,
135    ) -> Result<Arc<dyn LLMServerClient>, Box<dyn std::error::Error + Send + Sync>> {
136        // This is where the intelligence goes - matching model preferences
137        // to available LLM servers, exactly as the MCP spec describes
138
139        if let Some(first_client) = self.llm_clients.first() {
140            Ok(first_client.clone())
141        } else {
142            Err("No LLM servers configured".into())
143        }
144    }
145}
146
147/// Default user handler that automatically approves (for development)
148#[derive(Debug)]
149pub struct AutoApprovingUserHandler;
150
151#[async_trait]
152impl UserInteractionHandler for AutoApprovingUserHandler {
153    async fn approve_request(
154        &self,
155        _request: &CreateMessageRequest,
156    ) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
157        Ok(true) // Auto-approve for development
158    }
159
160    async fn approve_response(
161        &self,
162        _request: &CreateMessageRequest,
163        _response: &CreateMessageResult,
164    ) -> Result<Option<CreateMessageResult>, Box<dyn std::error::Error + Send + Sync>> {
165        Ok(None) // Auto-approve, don't modify
166    }
167}