Skip to main content

synwire_mcp_adapters/
callbacks.rs

1//! MCP callback slots for logging, progress, and elicitation.
2
3use std::sync::Arc;
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7use synwire_core::BoxFuture;
8use synwire_core::agents::error::AgentError;
9use synwire_core::mcp::elicitation::{ElicitationRequest, ElicitationResult, OnElicitation};
10
11// ---------------------------------------------------------------------------
12// Logging callback
13// ---------------------------------------------------------------------------
14
15/// Log level as reported by an MCP server.
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
17#[non_exhaustive]
18pub enum McpLogLevel {
19    /// Debug-level message.
20    Debug,
21    /// Informational message.
22    Info,
23    /// Warning-level message.
24    Warning,
25    /// Error-level message.
26    Error,
27}
28
29/// A logging message emitted by an MCP server.
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct McpLoggingMessage {
32    /// Severity level of the message.
33    pub level: McpLogLevel,
34    /// The logger name or server component that produced the message.
35    pub logger: Option<String>,
36    /// Message data (may be a string or structured JSON).
37    pub data: Value,
38}
39
40/// Callback invoked when an MCP server emits a logging message.
41pub trait OnMcpLogging: Send + Sync {
42    /// Handle a logging message from an MCP server.
43    fn on_log(&self, server_name: &str, message: McpLoggingMessage);
44}
45
46// ---------------------------------------------------------------------------
47// Progress callback
48// ---------------------------------------------------------------------------
49
50/// A progress notification from an MCP server during a long-running operation.
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct McpProgressNotification {
53    /// Opaque token identifying the operation.
54    pub progress_token: String,
55    /// Number of units completed so far.
56    pub progress: u64,
57    /// Total number of units (if known).
58    pub total: Option<u64>,
59    /// Human-readable description of current activity.
60    pub message: Option<String>,
61}
62
63/// Callback invoked when an MCP server reports progress on an operation.
64pub trait OnMcpProgress: Send + Sync {
65    /// Handle a progress notification from an MCP server.
66    fn on_progress(&self, server_name: &str, notification: McpProgressNotification);
67}
68
69// ---------------------------------------------------------------------------
70// Default no-op implementations
71// ---------------------------------------------------------------------------
72
73/// A logging callback that discards all messages.
74#[derive(Debug, Default, Clone)]
75pub struct DiscardLogging;
76
77impl OnMcpLogging for DiscardLogging {
78    fn on_log(&self, _server_name: &str, _message: McpLoggingMessage) {}
79}
80
81/// A progress callback that discards all notifications.
82#[derive(Debug, Default, Clone)]
83pub struct DiscardProgress;
84
85impl OnMcpProgress for DiscardProgress {
86    fn on_progress(&self, _server_name: &str, _notification: McpProgressNotification) {}
87}
88
89/// A logging callback that forwards messages to the `tracing` framework.
90#[derive(Debug, Default, Clone)]
91pub struct TracingLogging;
92
93impl OnMcpLogging for TracingLogging {
94    fn on_log(&self, server_name: &str, message: McpLoggingMessage) {
95        match message.level {
96            McpLogLevel::Debug => {
97                tracing::debug!(server = %server_name, logger = ?message.logger, data = ?message.data, "MCP log");
98            }
99            McpLogLevel::Info => {
100                tracing::info!(server = %server_name, logger = ?message.logger, data = ?message.data, "MCP log");
101            }
102            McpLogLevel::Warning => {
103                tracing::warn!(server = %server_name, logger = ?message.logger, data = ?message.data, "MCP log");
104            }
105            McpLogLevel::Error => {
106                tracing::error!(server = %server_name, logger = ?message.logger, data = ?message.data, "MCP log");
107            }
108        }
109    }
110}
111
112// ---------------------------------------------------------------------------
113// McpCallbacks bundle
114// ---------------------------------------------------------------------------
115
116/// Bundle of callback handlers for MCP server events.
117///
118/// All fields have default no-op implementations so only the handlers you
119/// care about need to be provided.
120pub struct McpCallbacks {
121    /// Handler for log messages emitted by MCP servers.
122    pub logging: Arc<dyn OnMcpLogging>,
123    /// Handler for progress notifications from MCP servers.
124    pub progress: Arc<dyn OnMcpProgress>,
125    /// Handler for elicitation requests from MCP servers.
126    pub elicitation: Arc<dyn OnElicitation>,
127}
128
129impl std::fmt::Debug for McpCallbacks {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        f.debug_struct("McpCallbacks")
132            .field("logging", &"<handler>")
133            .field("progress", &"<handler>")
134            .field("elicitation", &"<handler>")
135            .finish()
136    }
137}
138
139impl Default for McpCallbacks {
140    fn default() -> Self {
141        Self {
142            logging: Arc::new(DiscardLogging),
143            progress: Arc::new(DiscardProgress),
144            elicitation: Arc::new(CancelAllElicitationsAdapter),
145        }
146    }
147}
148
149impl McpCallbacks {
150    /// Creates a new `McpCallbacks` with all handlers set to the defaults.
151    #[must_use]
152    pub fn new() -> Self {
153        Self::default()
154    }
155
156    /// Sets the logging callback.
157    #[must_use]
158    pub fn with_logging(mut self, logging: Arc<dyn OnMcpLogging>) -> Self {
159        self.logging = logging;
160        self
161    }
162
163    /// Sets the progress callback.
164    #[must_use]
165    pub fn with_progress(mut self, progress: Arc<dyn OnMcpProgress>) -> Self {
166        self.progress = progress;
167        self
168    }
169
170    /// Sets the elicitation callback.
171    #[must_use]
172    pub fn with_elicitation(mut self, elicitation: Arc<dyn OnElicitation>) -> Self {
173        self.elicitation = elicitation;
174        self
175    }
176}
177
178// ---------------------------------------------------------------------------
179// Adapter: synwire-core's CancelAllElicitations
180// ---------------------------------------------------------------------------
181
182/// Adapter that wraps the `CancelAllElicitations` default from synwire-core.
183#[derive(Debug)]
184struct CancelAllElicitationsAdapter;
185
186impl OnElicitation for CancelAllElicitationsAdapter {
187    fn elicit(
188        &self,
189        request: ElicitationRequest,
190    ) -> BoxFuture<'_, Result<ElicitationResult, AgentError>> {
191        Box::pin(async move {
192            Ok(ElicitationResult::Cancelled {
193                request_id: request.request_id,
194            })
195        })
196    }
197}