Skip to main content

tap_mcp/tools/
agent_management_tools.rs

1//! Agent management tools for TAP transactions
2
3use super::schema;
4use super::{error_text_response, success_text_response, ToolHandler};
5use crate::error::{Error, Result};
6use crate::mcp::protocol::{CallToolResult, Tool};
7use crate::tap_integration::TapIntegration;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::sync::Arc;
11use tap_msg::message::tap_message_trait::TapMessageBody;
12use tap_msg::message::{AddAgents, Agent, RemoveAgent, ReplaceAgent};
13use tracing::{debug, error};
14
15/// Tool for adding agents to a transaction
16pub struct AddAgentsTool {
17    tap_integration: Arc<TapIntegration>,
18}
19
20/// Parameters for adding agents
21#[derive(Debug, Deserialize)]
22struct AddAgentsParams {
23    agent_did: String, // The DID of the agent that will sign and send this message
24    transaction_id: String,
25    agents: Vec<AgentInfo>,
26}
27
28#[derive(Debug, Deserialize)]
29struct AgentInfo {
30    #[serde(rename = "@id")]
31    id: String,
32    role: String,
33    #[serde(rename = "for")]
34    for_party: String,
35}
36
37/// Response for adding agents
38#[derive(Debug, Serialize)]
39struct AddAgentsResponse {
40    transaction_id: String,
41    message_id: String,
42    status: String,
43    agents_added: usize,
44    added_at: String,
45}
46
47impl AddAgentsTool {
48    pub fn new(tap_integration: Arc<TapIntegration>) -> Self {
49        Self { tap_integration }
50    }
51
52    fn tap_integration(&self) -> &TapIntegration {
53        &self.tap_integration
54    }
55}
56
57#[async_trait::async_trait]
58impl ToolHandler for AddAgentsTool {
59    async fn handle(&self, arguments: Option<Value>) -> Result<CallToolResult> {
60        let params: AddAgentsParams = match arguments {
61            Some(args) => serde_json::from_value(args)
62                .map_err(|e| Error::invalid_parameter(format!("Invalid parameters: {}", e)))?,
63            None => {
64                return Ok(error_text_response(
65                    "Missing required parameters".to_string(),
66                ))
67            }
68        };
69
70        debug!(
71            "Adding {} agents to transaction: {}",
72            params.agents.len(),
73            params.transaction_id
74        );
75
76        // Create agents
77        let agents: Vec<Agent> = params
78            .agents
79            .iter()
80            .map(|agent_info| Agent::new(&agent_info.id, &agent_info.role, &agent_info.for_party))
81            .collect();
82
83        // Create add agents message
84        let add_agents = AddAgents::new(&params.transaction_id, agents);
85
86        // Validate the add agents message
87        if let Err(e) = add_agents.validate() {
88            return Ok(error_text_response(format!(
89                "AddAgents validation failed: {}",
90                e
91            )));
92        }
93
94        // Create DIDComm message using the specified agent DID
95        let didcomm_message = match add_agents.to_didcomm(&params.agent_did) {
96            Ok(msg) => msg,
97            Err(e) => {
98                return Ok(error_text_response(format!(
99                    "Failed to create DIDComm message: {}",
100                    e
101                )));
102            }
103        };
104
105        // Determine recipient from the message
106        let recipient_did = if !didcomm_message.to.is_empty() {
107            didcomm_message.to[0].clone()
108        } else {
109            return Ok(error_text_response(
110                "No recipient found for add agents message".to_string(),
111            ));
112        };
113
114        debug!(
115            "Sending add agents from {} to {} for transaction: {}",
116            params.agent_did, recipient_did, params.transaction_id
117        );
118
119        // Send the message through the TAP node
120        match self
121            .tap_integration()
122            .node()
123            .send_message(params.agent_did.clone(), didcomm_message.clone())
124            .await
125        {
126            Ok(packed_message) => {
127                debug!(
128                    "AddAgents message sent successfully to {}, packed message length: {}",
129                    recipient_did,
130                    packed_message.len()
131                );
132
133                let response = AddAgentsResponse {
134                    transaction_id: params.transaction_id,
135                    message_id: didcomm_message.id,
136                    status: "sent".to_string(),
137                    agents_added: add_agents.agents.len(),
138                    added_at: chrono::Utc::now().to_rfc3339(),
139                };
140
141                let response_json = serde_json::to_string_pretty(&response).map_err(|e| {
142                    Error::tool_execution(format!("Failed to serialize response: {}", e))
143                })?;
144
145                Ok(success_text_response(response_json))
146            }
147            Err(e) => {
148                error!("Failed to send add agents message: {}", e);
149                Ok(error_text_response(format!(
150                    "Failed to send add agents message: {}",
151                    e
152                )))
153            }
154        }
155    }
156
157    fn get_definition(&self) -> Tool {
158        Tool {
159            name: "tap_add_agents".to_string(),
160            description: "Adds agents to a TAP transaction using the AddAgents message (TAIP-5)"
161                .to_string(),
162            input_schema: schema::add_agents_schema(),
163        }
164    }
165}
166
167/// Tool for removing an agent from a transaction
168pub struct RemoveAgentTool {
169    tap_integration: Arc<TapIntegration>,
170}
171
172/// Parameters for removing an agent
173#[derive(Debug, Deserialize)]
174struct RemoveAgentParams {
175    agent_did: String, // The DID of the agent that will sign and send this message
176    transaction_id: String,
177    agent_to_remove: String,
178}
179
180/// Response for removing an agent
181#[derive(Debug, Serialize)]
182struct RemoveAgentResponse {
183    transaction_id: String,
184    message_id: String,
185    status: String,
186    removed_agent: String,
187    removed_at: String,
188}
189
190impl RemoveAgentTool {
191    pub fn new(tap_integration: Arc<TapIntegration>) -> Self {
192        Self { tap_integration }
193    }
194
195    fn tap_integration(&self) -> &TapIntegration {
196        &self.tap_integration
197    }
198}
199
200#[async_trait::async_trait]
201impl ToolHandler for RemoveAgentTool {
202    async fn handle(&self, arguments: Option<Value>) -> Result<CallToolResult> {
203        let params: RemoveAgentParams = match arguments {
204            Some(args) => serde_json::from_value(args)
205                .map_err(|e| Error::invalid_parameter(format!("Invalid parameters: {}", e)))?,
206            None => {
207                return Ok(error_text_response(
208                    "Missing required parameters".to_string(),
209                ))
210            }
211        };
212
213        debug!(
214            "Removing agent {} from transaction: {}",
215            params.agent_to_remove, params.transaction_id
216        );
217
218        // Create remove agent message
219        let remove_agent = RemoveAgent::new(&params.transaction_id, &params.agent_to_remove);
220
221        // Validate the remove agent message
222        if let Err(e) = remove_agent.validate() {
223            return Ok(error_text_response(format!(
224                "RemoveAgent validation failed: {}",
225                e
226            )));
227        }
228
229        // Create DIDComm message using the specified agent DID
230        let didcomm_message = match remove_agent.to_didcomm(&params.agent_did) {
231            Ok(msg) => msg,
232            Err(e) => {
233                return Ok(error_text_response(format!(
234                    "Failed to create DIDComm message: {}",
235                    e
236                )));
237            }
238        };
239
240        // Determine recipient from the message
241        let recipient_did = if !didcomm_message.to.is_empty() {
242            didcomm_message.to[0].clone()
243        } else {
244            return Ok(error_text_response(
245                "No recipient found for remove agent message".to_string(),
246            ));
247        };
248
249        debug!(
250            "Sending remove agent from {} to {} for transaction: {}",
251            params.agent_did, recipient_did, params.transaction_id
252        );
253
254        // Send the message through the TAP node
255        match self
256            .tap_integration()
257            .node()
258            .send_message(params.agent_did.clone(), didcomm_message.clone())
259            .await
260        {
261            Ok(packed_message) => {
262                debug!(
263                    "RemoveAgent message sent successfully to {}, packed message length: {}",
264                    recipient_did,
265                    packed_message.len()
266                );
267
268                let response = RemoveAgentResponse {
269                    transaction_id: params.transaction_id,
270                    message_id: didcomm_message.id,
271                    status: "sent".to_string(),
272                    removed_agent: params.agent_to_remove,
273                    removed_at: chrono::Utc::now().to_rfc3339(),
274                };
275
276                let response_json = serde_json::to_string_pretty(&response).map_err(|e| {
277                    Error::tool_execution(format!("Failed to serialize response: {}", e))
278                })?;
279
280                Ok(success_text_response(response_json))
281            }
282            Err(e) => {
283                error!("Failed to send remove agent message: {}", e);
284                Ok(error_text_response(format!(
285                    "Failed to send remove agent message: {}",
286                    e
287                )))
288            }
289        }
290    }
291
292    fn get_definition(&self) -> Tool {
293        Tool {
294            name: "tap_remove_agent".to_string(),
295            description:
296                "Removes an agent from a TAP transaction using the RemoveAgent message (TAIP-5)"
297                    .to_string(),
298            input_schema: schema::remove_agent_schema(),
299        }
300    }
301}
302
303/// Tool for replacing an agent in a transaction
304pub struct ReplaceAgentTool {
305    tap_integration: Arc<TapIntegration>,
306}
307
308/// Parameters for replacing an agent
309#[derive(Debug, Deserialize)]
310struct ReplaceAgentParams {
311    agent_did: String, // The DID of the agent that will sign and send this message
312    transaction_id: String,
313    original_agent: String,
314    new_agent: AgentInfo,
315}
316
317/// Response for replacing an agent
318#[derive(Debug, Serialize)]
319struct ReplaceAgentResponse {
320    transaction_id: String,
321    message_id: String,
322    status: String,
323    old_agent: String,
324    new_agent: String,
325    replaced_at: String,
326}
327
328impl ReplaceAgentTool {
329    pub fn new(tap_integration: Arc<TapIntegration>) -> Self {
330        Self { tap_integration }
331    }
332
333    fn tap_integration(&self) -> &TapIntegration {
334        &self.tap_integration
335    }
336}
337
338#[async_trait::async_trait]
339impl ToolHandler for ReplaceAgentTool {
340    async fn handle(&self, arguments: Option<Value>) -> Result<CallToolResult> {
341        let params: ReplaceAgentParams = match arguments {
342            Some(args) => serde_json::from_value(args)
343                .map_err(|e| Error::invalid_parameter(format!("Invalid parameters: {}", e)))?,
344            None => {
345                return Ok(error_text_response(
346                    "Missing required parameters".to_string(),
347                ))
348            }
349        };
350
351        debug!(
352            "Replacing agent {} with {} in transaction: {}",
353            params.original_agent, params.new_agent.id, params.transaction_id
354        );
355
356        // Create new agent
357        let replacement_agent = Agent::new(
358            &params.new_agent.id,
359            &params.new_agent.role,
360            &params.new_agent.for_party,
361        );
362
363        // Create replace agent message
364        let replace_agent = ReplaceAgent::new(
365            &params.transaction_id,
366            &params.original_agent,
367            replacement_agent,
368        );
369
370        // Validate the replace agent message
371        if let Err(e) = replace_agent.validate() {
372            return Ok(error_text_response(format!(
373                "ReplaceAgent validation failed: {}",
374                e
375            )));
376        }
377
378        // Create DIDComm message using the specified agent DID
379        let didcomm_message = match replace_agent.to_didcomm(&params.agent_did) {
380            Ok(msg) => msg,
381            Err(e) => {
382                return Ok(error_text_response(format!(
383                    "Failed to create DIDComm message: {}",
384                    e
385                )));
386            }
387        };
388
389        // Determine recipient from the message
390        let recipient_did = if !didcomm_message.to.is_empty() {
391            didcomm_message.to[0].clone()
392        } else {
393            return Ok(error_text_response(
394                "No recipient found for replace agent message".to_string(),
395            ));
396        };
397
398        debug!(
399            "Sending replace agent from {} to {} for transaction: {}",
400            params.agent_did, recipient_did, params.transaction_id
401        );
402
403        // Send the message through the TAP node
404        match self
405            .tap_integration()
406            .node()
407            .send_message(params.agent_did.clone(), didcomm_message.clone())
408            .await
409        {
410            Ok(packed_message) => {
411                debug!(
412                    "ReplaceAgent message sent successfully to {}, packed message length: {}",
413                    recipient_did,
414                    packed_message.len()
415                );
416
417                let response = ReplaceAgentResponse {
418                    transaction_id: params.transaction_id,
419                    message_id: didcomm_message.id,
420                    status: "sent".to_string(),
421                    old_agent: params.original_agent,
422                    new_agent: params.new_agent.id,
423                    replaced_at: chrono::Utc::now().to_rfc3339(),
424                };
425
426                let response_json = serde_json::to_string_pretty(&response).map_err(|e| {
427                    Error::tool_execution(format!("Failed to serialize response: {}", e))
428                })?;
429
430                Ok(success_text_response(response_json))
431            }
432            Err(e) => {
433                error!("Failed to send replace agent message: {}", e);
434                Ok(error_text_response(format!(
435                    "Failed to send replace agent message: {}",
436                    e
437                )))
438            }
439        }
440    }
441
442    fn get_definition(&self) -> Tool {
443        Tool {
444            name: "tap_replace_agent".to_string(),
445            description:
446                "Replaces an agent in a TAP transaction using the ReplaceAgent message (TAIP-5)"
447                    .to_string(),
448            input_schema: schema::replace_agent_schema(),
449        }
450    }
451}