1use crate::tool::Tool;
21use crate::types::{JsonValue, ToolError, ToolResult};
22use async_trait::async_trait;
23use rust_mcp_sdk::{
24 mcp_client::{
25 client_runtime_core, ClientHandlerCore, McpClientOptions, ToMcpClientHandlerCore,
26 },
27 schema::{
28 CallToolRequestParams, ClientCapabilities, Implementation, InitializeRequestParams,
29 LATEST_PROTOCOL_VERSION,
30 },
31 McpClient as SdkMcpClient, StdioTransport, TransportOptions,
32};
33use std::collections::HashMap;
34use std::sync::Arc;
35
36pub struct McpClient {
56 inner: Arc<dyn SdkMcpClient>,
57}
58
59impl McpClient {
60 pub async fn new_stdio(command: &str, args: &[&str]) -> Result<Arc<Self>, String> {
71 let client_details = InitializeRequestParams {
73 capabilities: ClientCapabilities::default(),
74 client_info: Implementation {
75 name: "agent-runtime-mcp-client".into(),
76 version: env!("CARGO_PKG_VERSION").into(),
77 description: Some("MCP client for agent-runtime framework".into()),
78 title: None,
79 icons: vec![],
80 website_url: None,
81 },
82 protocol_version: LATEST_PROTOCOL_VERSION.into(),
83 meta: None,
84 };
85
86 let transport = StdioTransport::create_with_server_launch(
88 command,
89 args.iter().map(|s| s.to_string()).collect(),
90 None,
91 TransportOptions::default(),
92 )
93 .map_err(|e| format!("Failed to create transport: {}", e))?;
94
95 let handler = MinimalClientHandler {};
97
98 let client = client_runtime_core::create_client(McpClientOptions {
100 client_details,
101 transport,
102 handler: handler.to_mcp_client_handler(),
103 task_store: None,
104 server_task_store: None,
105 });
106
107 client
108 .clone()
109 .start()
110 .await
111 .map_err(|e| format!("Failed to start MCP client: {}", e))?;
112
113 Ok(Arc::new(Self { inner: client }))
114 }
115
116 pub async fn list_tools(&self) -> Result<Vec<McpToolInfo>, String> {
120 let response = self
121 .inner
122 .request_tool_list(None)
123 .await
124 .map_err(|e| format!("Failed to list tools: {}", e))?;
125
126 Ok(response
127 .tools
128 .into_iter()
129 .map(|tool| McpToolInfo {
130 name: tool.name,
131 description: tool.description.unwrap_or_default(),
132 input_schema: serde_json::to_value(&tool.input_schema).unwrap_or(JsonValue::Null),
133 })
134 .collect())
135 }
136
137 pub async fn call_tool(
141 &self,
142 name: &str,
143 arguments: HashMap<String, JsonValue>,
144 ) -> Result<JsonValue, String> {
145 let params = CallToolRequestParams {
146 name: name.to_string(),
147 arguments: Some(
148 arguments
149 .into_iter()
150 .collect::<serde_json::Map<String, JsonValue>>(),
151 ),
152 meta: None,
153 task: None,
154 };
155
156 let result = self
157 .inner
158 .request_tool_call(params)
159 .await
160 .map_err(|e| format!("MCP tool call failed: {}", e))?;
161
162 if let Some(content) = result.content.first() {
164 if let Ok(text_content) = content.as_text_content() {
165 Ok(JsonValue::String(text_content.text.clone()))
166 } else {
167 Ok(serde_json::to_value(content)
168 .map_err(|e| format!("Failed to serialize result: {}", e))?)
169 }
170 } else {
171 Ok(JsonValue::Null)
172 }
173 }
174}
175
176#[derive(Debug, Clone)]
178pub struct McpToolInfo {
179 pub name: String,
180 pub description: String,
181 pub input_schema: JsonValue,
182}
183
184struct MinimalClientHandler;
186
187use rust_mcp_sdk::schema::{
188 NotificationFromServer, ResultFromClient, RpcError, ServerJsonrpcRequest,
189};
190
191#[async_trait]
192impl ClientHandlerCore for MinimalClientHandler {
193 async fn handle_request(
194 &self,
195 _request: ServerJsonrpcRequest,
196 _runtime: &dyn SdkMcpClient,
197 ) -> Result<ResultFromClient, RpcError> {
198 Err(RpcError::method_not_found())
199 }
200
201 async fn handle_notification(
202 &self,
203 _notification: NotificationFromServer,
204 _runtime: &dyn SdkMcpClient,
205 ) -> Result<(), RpcError> {
206 Ok(())
207 }
208
209 async fn handle_error(
210 &self,
211 _error: &RpcError,
212 _runtime: &dyn SdkMcpClient,
213 ) -> Result<(), RpcError> {
214 Ok(())
215 }
216}
217
218pub struct McpTool {
223 name: String,
224 description: String,
225 input_schema: JsonValue,
226 client: Arc<McpClient>,
228}
229
230impl McpTool {
231 pub fn new(
233 name: String,
234 description: String,
235 input_schema: JsonValue,
236 client: Arc<McpClient>,
237 ) -> Self {
238 Self {
239 name,
240 description,
241 input_schema,
242 client,
243 }
244 }
245
246 pub fn from_info(info: McpToolInfo, client: Arc<McpClient>) -> Self {
248 Self::new(info.name, info.description, info.input_schema, client)
249 }
250}
251
252#[async_trait]
253impl Tool for McpTool {
254 fn name(&self) -> &str {
255 &self.name
256 }
257
258 fn description(&self) -> &str {
259 &self.description
260 }
261
262 fn input_schema(&self) -> JsonValue {
263 self.input_schema.clone()
264 }
265
266 async fn execute(&self, params: HashMap<String, JsonValue>) -> Result<ToolResult, ToolError> {
267 let start = std::time::Instant::now();
268
269 match self.client.call_tool(&self.name, params).await {
271 Ok(output) => Ok(ToolResult::success(
272 output,
273 start.elapsed().as_secs_f64() * 1000.0,
274 )),
275 Err(e) => Err(ToolError::ExecutionFailed(format!("MCP error: {}", e))),
276 }
277 }
278}