Skip to main content

tap_mcp/tools/
communication_tools.rs

1//! Communication tools for Trust Ping and Basic Message protocols
2
3use crate::error::{Error, Result};
4use crate::mcp::protocol::{CallToolResult, Tool};
5use crate::tap_integration::TapIntegration;
6use crate::tools::{error_text_response, success_text_response, ToolHandler};
7use async_trait::async_trait;
8use serde_json::{json, Value};
9use std::collections::HashMap;
10use std::sync::Arc;
11use tap_msg::didcomm::PlainMessage;
12use tap_msg::message::tap_message_trait::TapMessageBody;
13use tap_msg::message::{BasicMessage, TrustPing};
14use tracing::{debug, error};
15
16/// Tool for sending Trust Ping messages
17pub struct TrustPingTool {
18    tap_integration: Arc<TapIntegration>,
19}
20
21impl TrustPingTool {
22    pub fn new(tap_integration: Arc<TapIntegration>) -> Self {
23        Self { tap_integration }
24    }
25}
26
27#[async_trait]
28impl ToolHandler for TrustPingTool {
29    async fn handle(&self, arguments: Option<Value>) -> Result<CallToolResult> {
30        let args =
31            arguments.ok_or_else(|| Error::tool_execution("Arguments required".to_string()))?;
32
33        // Parse arguments
34        let from_did = args
35            .get("from_did")
36            .and_then(|v| v.as_str())
37            .ok_or_else(|| Error::tool_execution("from_did is required".to_string()))?;
38
39        let to_did = args
40            .get("to_did")
41            .and_then(|v| v.as_str())
42            .ok_or_else(|| Error::tool_execution("to_did is required".to_string()))?;
43
44        let response_requested = args
45            .get("response_requested")
46            .and_then(|v| v.as_bool())
47            .unwrap_or(true);
48
49        let comment = args
50            .get("comment")
51            .and_then(|v| v.as_str())
52            .map(|s| s.to_string());
53
54        debug!(
55            "Sending Trust Ping from {} to {}, response_requested: {}",
56            from_did, to_did, response_requested
57        );
58
59        // Create Trust Ping message
60        let mut ping = TrustPing::new().response_requested(response_requested);
61
62        if let Some(comment_text) = comment {
63            ping = TrustPing::with_comment(comment_text);
64            ping = ping.response_requested(response_requested);
65        }
66
67        // Create PlainMessage
68        let ping_message = PlainMessage {
69            id: uuid::Uuid::new_v4().to_string(),
70            typ: "application/didcomm-plain+json".to_string(),
71            type_: TrustPing::message_type().to_string(),
72            body: serde_json::to_value(&ping).map_err(|e| {
73                Error::tool_execution(format!("Failed to serialize Trust Ping: {}", e))
74            })?,
75            from: from_did.to_string(),
76            to: vec![to_did.to_string()],
77            thid: None,
78            pthid: None,
79            extra_headers: HashMap::new(),
80            attachments: None,
81            created_time: Some(chrono::Utc::now().timestamp_millis() as u64),
82            expires_time: None,
83            from_prior: None,
84        };
85
86        // Send the message through the underlying TAP node
87        match self
88            .tap_integration
89            .node()
90            .send_message(from_did.to_string(), ping_message)
91            .await
92        {
93            Ok(message_id) => {
94                let response_text = if response_requested {
95                    format!("Trust Ping sent successfully with ID: {}. Response requested from recipient.", message_id)
96                } else {
97                    format!(
98                        "Trust Ping sent successfully with ID: {}. No response expected.",
99                        message_id
100                    )
101                };
102                Ok(success_text_response(response_text))
103            }
104            Err(e) => {
105                error!("Failed to send Trust Ping: {}", e);
106                Ok(error_text_response(format!(
107                    "Failed to send Trust Ping: {}",
108                    e
109                )))
110            }
111        }
112    }
113
114    fn get_definition(&self) -> Tool {
115        Tool {
116            name: "tap_trust_ping".to_string(),
117            description: "Send a Trust Ping message to test connectivity with another agent"
118                .to_string(),
119            input_schema: json!({
120                "type": "object",
121                "properties": {
122                    "from_did": {
123                        "type": "string",
124                        "description": "The DID of the sender agent"
125                    },
126                    "to_did": {
127                        "type": "string",
128                        "description": "The DID of the recipient agent"
129                    },
130                    "response_requested": {
131                        "type": "boolean",
132                        "description": "Whether a response is requested (default: true)",
133                        "default": true
134                    },
135                    "comment": {
136                        "type": "string",
137                        "description": "Optional comment to include with the ping"
138                    }
139                },
140                "required": ["from_did", "to_did"],
141                "additionalProperties": false
142            }),
143        }
144    }
145}
146
147/// Tool for sending Basic Message (text messages)
148pub struct BasicMessageTool {
149    tap_integration: Arc<TapIntegration>,
150}
151
152impl BasicMessageTool {
153    pub fn new(tap_integration: Arc<TapIntegration>) -> Self {
154        Self { tap_integration }
155    }
156}
157
158#[async_trait]
159impl ToolHandler for BasicMessageTool {
160    async fn handle(&self, arguments: Option<Value>) -> Result<CallToolResult> {
161        let args =
162            arguments.ok_or_else(|| Error::tool_execution("Arguments required".to_string()))?;
163
164        // Parse arguments
165        let from_did = args
166            .get("from_did")
167            .and_then(|v| v.as_str())
168            .ok_or_else(|| Error::tool_execution("from_did is required".to_string()))?;
169
170        let to_did = args
171            .get("to_did")
172            .and_then(|v| v.as_str())
173            .ok_or_else(|| Error::tool_execution("to_did is required".to_string()))?;
174
175        let content = args
176            .get("content")
177            .and_then(|v| v.as_str())
178            .ok_or_else(|| Error::tool_execution("content is required".to_string()))?;
179
180        let locale = args
181            .get("locale")
182            .and_then(|v| v.as_str())
183            .map(|s| s.to_string());
184
185        debug!("Sending Basic Message from {} to {}", from_did, to_did);
186
187        // Create Basic Message
188        let basic_message = BasicMessage {
189            content: content.to_string(),
190            locale,
191            sent_time: Some(chrono::Utc::now().timestamp_millis() as u64),
192            metadata: HashMap::new(),
193        };
194
195        // Create PlainMessage
196        let message = PlainMessage {
197            id: uuid::Uuid::new_v4().to_string(),
198            typ: "application/didcomm-plain+json".to_string(),
199            type_: BasicMessage::message_type().to_string(),
200            body: serde_json::to_value(&basic_message).map_err(|e| {
201                Error::tool_execution(format!("Failed to serialize Basic Message: {}", e))
202            })?,
203            from: from_did.to_string(),
204            to: vec![to_did.to_string()],
205            thid: None,
206            pthid: None,
207            extra_headers: HashMap::new(),
208            attachments: None,
209            created_time: Some(chrono::Utc::now().timestamp_millis() as u64),
210            expires_time: None,
211            from_prior: None,
212        };
213
214        // Send the message through the underlying TAP node
215        match self
216            .tap_integration
217            .node()
218            .send_message(from_did.to_string(), message)
219            .await
220        {
221            Ok(message_id) => Ok(success_text_response(format!(
222                "Basic Message sent successfully with ID: {}. Content: \"{}\"",
223                message_id, content
224            ))),
225            Err(e) => {
226                error!("Failed to send Basic Message: {}", e);
227                Ok(error_text_response(format!(
228                    "Failed to send Basic Message: {}",
229                    e
230                )))
231            }
232        }
233    }
234
235    fn get_definition(&self) -> Tool {
236        Tool {
237            name: "tap_basic_message".to_string(),
238            description: "Send a basic text message to another agent".to_string(),
239            input_schema: json!({
240                "type": "object",
241                "properties": {
242                    "from_did": {
243                        "type": "string",
244                        "description": "The DID of the sender agent"
245                    },
246                    "to_did": {
247                        "type": "string",
248                        "description": "The DID of the recipient agent"
249                    },
250                    "content": {
251                        "type": "string",
252                        "description": "The text content of the message"
253                    },
254                    "locale": {
255                        "type": "string",
256                        "description": "Optional locale/language code (e.g., 'en-US')"
257                    }
258                },
259                "required": ["from_did", "to_did", "content"],
260                "additionalProperties": false
261            }),
262        }
263    }
264}