1pub use oxios_mcp::{
7 BLOCKED_MCP_SHELLS,
8 ClientInfo,
9 InitializeParams,
10 InitializeResult,
11 MappedResource,
12 McpBridge,
13 McpCapabilities,
14 McpClient,
15 McpContentBlock,
16 McpError,
17 McpRequest,
18 McpResponse,
19 McpServer,
20 McpServerConfig,
21 McpTool,
22 McpToolCallResult,
23 McpToolsResult,
24 ServerInfo,
25 sanitize_env,
27 validate_mcp_command,
28};
29
30use crate::tools::tool_types::{ArgumentDef, ToolDef};
31
32pub fn mcp_tool_to_tool_def(tool: &McpTool) -> ToolDef {
37 let arguments = if let Some(properties) = tool
38 .input_schema()
39 .get("properties")
40 .and_then(|p| p.as_object())
41 {
42 let required_list: Vec<&str> = tool
43 .input_schema()
44 .get("required")
45 .and_then(|r| r.as_array())
46 .map(|arr| arr.iter().filter_map(|v| v.as_str()).collect())
47 .unwrap_or_default();
48
49 properties
50 .iter()
51 .map(|(name, schema)| {
52 let description = schema
53 .get("description")
54 .and_then(|d| d.as_str())
55 .unwrap_or("No description")
56 .to_string();
57 let required =
58 required_list.iter().any(|r| *r == name) && schema.get("default").is_none();
59
60 ArgumentDef {
61 name: name.clone(),
62 description,
63 required,
64 default: schema
65 .get("default")
66 .and_then(|d| d.as_str().map(String::from)),
67 }
68 })
69 .collect()
70 } else {
71 Vec::new()
72 };
73
74 ToolDef {
75 name: tool.name().to_string(),
76 description: tool.description().to_string(),
77 arguments,
78 command: String::new(),
79 }
80}
81
82pub async fn list_tool_defs(bridge: &McpBridge) -> anyhow::Result<Vec<ToolDef>> {
84 let tools = bridge.list_tools().await?;
85 Ok(tools.iter().map(mcp_tool_to_tool_def).collect())
86}
87
88pub async fn cached_tool_defs(bridge: &McpBridge, server_name: &str) -> Option<Vec<ToolDef>> {
90 bridge
91 .cached_tools(server_name)
92 .await
93 .map(|tools| tools.iter().map(mcp_tool_to_tool_def).collect())
94}
95
96pub async fn refresh_tool_defs(
98 bridge: &McpBridge,
99 server_name: &str,
100) -> anyhow::Result<Vec<ToolDef>> {
101 let tools = bridge.refresh_tools(server_name).await?;
102 Ok(tools.iter().map(mcp_tool_to_tool_def).collect())
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_mcp_tool_to_tool_def() {
111 let mcp_tool = McpTool {
112 name: "test_tool".to_string(),
113 description: "A test tool".to_string(),
114 input_schema: serde_json::json!({
115 "type": "object",
116 "properties": {
117 "arg1": {
118 "type": "string",
119 "description": "First argument"
120 },
121 "arg2": {
122 "type": "number",
123 "description": "Second argument",
124 "default": "42"
125 }
126 },
127 "required": ["arg1"]
128 }),
129 };
130
131 let tool_def = mcp_tool_to_tool_def(&mcp_tool);
132
133 assert_eq!(tool_def.name, "test_tool");
134 assert_eq!(tool_def.description, "A test tool");
135 assert_eq!(tool_def.arguments.len(), 2);
136
137 let arg1 = tool_def
138 .arguments
139 .iter()
140 .find(|a| a.name == "arg1")
141 .unwrap();
142 assert!(arg1.required);
143 assert_eq!(arg1.description, "First argument");
144
145 let arg2 = tool_def
146 .arguments
147 .iter()
148 .find(|a| a.name == "arg2")
149 .unwrap();
150 assert!(!arg2.required);
151 assert_eq!(arg2.default, Some("42".to_string()));
152 }
153
154 #[test]
155 fn test_mcp_tool_to_tool_def_no_properties() {
156 let mcp_tool = McpTool {
157 name: "simple".to_string(),
158 description: "No args".to_string(),
159 input_schema: serde_json::json!({"type": "object"}),
160 };
161
162 let tool_def = mcp_tool_to_tool_def(&mcp_tool);
163 assert!(tool_def.arguments.is_empty());
164 assert_eq!(tool_def.command, "");
165 }
166}