tycode_core/mcp/
client.rs1use std::process::Stdio;
2
3use crate::settings::config::McpServerConfig;
4use rmcp::{
5 model::{CallToolRequestParam, CallToolResult, Tool},
6 service::{RunningService, ServiceExt},
7 transport::{ConfigureCommandExt, TokioChildProcess},
8 ClientHandler,
9};
10use tokio::process::Command;
11use tracing::{debug, info};
12
13#[derive(Clone, Debug, Default)]
16struct SimpleClientHandler;
17
18impl ClientHandler for SimpleClientHandler {}
19
20pub struct McpClient {
21 name: String,
22 client_handle: RunningService<rmcp::RoleClient, SimpleClientHandler>,
23}
24
25impl McpClient {
26 pub async fn new(name: String, config: McpServerConfig) -> anyhow::Result<Self> {
27 info!(client_name = %name, "Initializing MCP client");
28
29 let cmd = Command::new(&config.command).configure(|c| {
30 c.args(&config.args);
31 c.envs(config.env.iter());
32 c.stderr(Stdio::null());
33 #[cfg(unix)]
34 c.process_group(0);
35 #[cfg(windows)]
36 {
37 const CREATE_NEW_PROCESS_GROUP: u32 = 0x00000200;
38 c.creation_flags(CREATE_NEW_PROCESS_GROUP);
39 }
40 });
41
42 let transport = TokioChildProcess::new(cmd)
43 .map_err(|e| anyhow::anyhow!("Failed to create MCP transport: {e:?}"))?;
44
45 let client: RunningService<rmcp::RoleClient, SimpleClientHandler> = SimpleClientHandler
46 .serve(transport)
47 .await
48 .map_err(|e| anyhow::anyhow!("Failed to serve MCP client: {e:?}"))?;
49
50 debug!(client_name = %name, "MCP client initialized successfully");
51
52 Ok(Self {
53 name,
54 client_handle: client,
55 })
56 }
57
58 pub async fn list_tools(&mut self) -> anyhow::Result<Vec<Tool>> {
59 let tools_response = self
60 .client_handle
61 .list_tools(Default::default())
62 .await
63 .map_err(|e| anyhow::anyhow!("Failed to list MCP tools: {e:?}"))?;
64
65 Ok(tools_response.tools)
66 }
67
68 pub async fn call_tool(
69 &mut self,
70 name: &str,
71 arguments: Option<serde_json::Value>,
72 ) -> anyhow::Result<CallToolResult> {
73 let request = CallToolRequestParam {
74 name: name.to_string().into(),
75 arguments: arguments.as_ref().and_then(|v| v.as_object().cloned()),
76 };
77
78 self.client_handle
79 .call_tool(request)
80 .await
81 .map_err(|e| anyhow::anyhow!("Failed to call MCP tool '{name}': {e:?}"))
82 }
83
84 pub async fn close(self) -> anyhow::Result<()> {
85 info!(client_name = %self.name, "Closing MCP client");
86
87 self.client_handle
88 .cancel()
89 .await
90 .map_err(|e| anyhow::anyhow!("Failed to cancel MCP client: {e:?}"))?;
91
92 debug!(client_name = %self.name, "MCP client closed successfully");
93
94 Ok(())
95 }
96}