oxi_agent/mcp/
direct_tool.rs1use super::McpManager;
10use super::content;
11use super::types::{ConsentState, DirectToolDef};
12use crate::tools::{AgentTool, AgentToolResult, ToolContext};
13use async_trait::async_trait;
14use serde_json::Value;
15use std::sync::Arc;
16use tokio::sync::oneshot;
17
18pub struct McpDirectTool {
20 prefixed_name: String,
22 original_name: String,
24 server_name: String,
26 description: String,
28 schema: Value,
30 manager: Arc<McpManager>,
32}
33
34impl std::fmt::Debug for McpDirectTool {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 f.debug_struct("McpDirectTool")
37 .field("name", &self.prefixed_name)
38 .field("server", &self.server_name)
39 .finish()
40 }
41}
42
43impl McpDirectTool {
44 pub fn new(manager: Arc<McpManager>, def: DirectToolDef) -> Self {
46 Self {
47 prefixed_name: def.prefixed_name,
48 original_name: def.original_name,
49 server_name: def.server_name,
50 description: def.description,
51 schema: def
52 .input_schema
53 .unwrap_or_else(|| serde_json::json!({"type": "object", "properties": {}})),
54 manager,
55 }
56 }
57}
58
59#[async_trait]
60impl AgentTool for McpDirectTool {
61 fn name(&self) -> &str {
62 &self.prefixed_name
63 }
64
65 fn label(&self) -> &str {
66 &self.original_name
67 }
68
69 fn description(&self) -> &str {
70 &self.description
71 }
72
73 fn parameters_schema(&self) -> Value {
74 self.schema.clone()
75 }
76
77 fn essential(&self) -> bool {
78 false
79 }
80
81 async fn execute(
82 &self,
83 _tool_call_id: &str,
84 params: Value,
85 _signal: Option<oneshot::Receiver<()>>,
86 _ctx: &ToolContext,
87 ) -> Result<AgentToolResult, String> {
88 if self.manager.consent().check(&self.original_name) == ConsentState::Deny {
90 return Ok(AgentToolResult::error(format!(
91 "Tool '{}' is denied by consent policy",
92 self.original_name
93 )));
94 }
95
96 match self
98 .manager
99 .call_tool(&self.original_name, params, Some(&self.server_name))
100 .await
101 {
102 Ok(result) => {
103 self.manager.reset_idle_timer(&self.server_name);
106
107 if result.is_error {
108 let text = content::transform_mcp_content(&result.content);
109 Ok(AgentToolResult::error(format!("Error: {}", text)))
110 } else {
111 let text = content::transform_mcp_content(&result.content);
112 Ok(AgentToolResult::success(text))
113 }
114 }
115 Err(e) => Err(e.to_string()),
116 }
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn name_returns_prefixed_name_reference() {
126 let manager: Arc<McpManager> = Arc::new(McpManager::new_no_spawn());
129 let def = DirectToolDef {
130 prefixed_name: "chrome_take_screenshot".to_string(),
131 original_name: "take_screenshot".to_string(),
132 server_name: "chrome".to_string(),
133 description: "Take a screenshot".to_string(),
134 input_schema: Some(serde_json::json!({"type": "object"})),
135 };
136 let tool = McpDirectTool::new(manager, def);
137 assert_eq!(tool.name(), "chrome_take_screenshot");
138 assert_eq!(tool.label(), "take_screenshot");
139 }
140}