opendev_tools_impl/
mcp_tool.rs1use std::collections::HashMap;
8use std::sync::Arc;
9
10use async_trait::async_trait;
11use opendev_mcp::McpManager;
12use opendev_mcp::models::{McpContent, McpToolSchema};
13use opendev_tools_core::traits::{BaseTool, ToolContext, ToolResult};
14
15pub struct McpBridgeTool {
20 tool_name: String,
22 tool_description: String,
24 schema: serde_json::Value,
26 server_name: String,
28 original_name: String,
30 manager: Arc<McpManager>,
32}
33
34impl std::fmt::Debug for McpBridgeTool {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 f.debug_struct("McpBridgeTool")
37 .field("tool_name", &self.tool_name)
38 .field("server_name", &self.server_name)
39 .field("original_name", &self.original_name)
40 .finish()
41 }
42}
43
44impl McpBridgeTool {
45 pub fn from_schema(schema: &McpToolSchema, manager: Arc<McpManager>) -> Self {
47 Self {
48 tool_name: format!("mcp__{}", schema.name),
49 tool_description: schema.description.clone(),
50 schema: schema.parameters.clone(),
51 server_name: schema.server_name.clone(),
52 original_name: schema.original_name.clone(),
53 manager,
54 }
55 }
56}
57
58#[async_trait]
59impl BaseTool for McpBridgeTool {
60 fn name(&self) -> &str {
61 &self.tool_name
62 }
63
64 fn description(&self) -> &str {
65 &self.tool_description
66 }
67
68 fn parameter_schema(&self) -> serde_json::Value {
69 if self.schema.is_object() {
71 self.schema.clone()
72 } else {
73 serde_json::json!({
75 "type": "object",
76 "properties": {},
77 "required": []
78 })
79 }
80 }
81
82 async fn execute(
83 &self,
84 args: HashMap<String, serde_json::Value>,
85 _ctx: &ToolContext,
86 ) -> ToolResult {
87 let arguments = serde_json::Value::Object(args.into_iter().collect());
88
89 match self
90 .manager
91 .call_tool(&self.server_name, &self.original_name, arguments)
92 .await
93 {
94 Ok(result) => {
95 let output = result
97 .content
98 .iter()
99 .filter_map(|c| match c {
100 McpContent::Text { text } => Some(text.as_str()),
101 _ => None,
102 })
103 .collect::<Vec<_>>()
104 .join("\n");
105
106 if result.is_error {
107 ToolResult::fail(if output.is_empty() {
108 "MCP tool returned an error".to_string()
109 } else {
110 output
111 })
112 } else {
113 ToolResult::ok(if output.is_empty() {
114 "(no output)".to_string()
115 } else {
116 output
117 })
118 }
119 }
120 Err(e) => ToolResult::fail(format!("MCP call failed: {e}")),
121 }
122 }
123}
124
125#[cfg(test)]
126#[path = "mcp_tool_tests.rs"]
127mod tests;