1use anyhow::{Context, Result, bail};
4use serde_json::{Value, json};
5use std::sync::Arc;
6
7use super::protocol::JsonRpcRequest;
8use super::protocol::{
9 ClientCapabilities, ClientInfo, InitializeParams, InitializeResult, McpToolCallResult,
10 McpToolDefinition, ToolCallParams, ToolsListResult,
11};
12use super::transport::McpTransport;
13
14pub const MCP_PROTOCOL_VERSION: &str = "2024-11-05";
16
17pub struct McpClient<T: McpTransport> {
38 transport: Arc<T>,
39 server_name: String,
40 server_info: Option<InitializeResult>,
41}
42
43impl<T: McpTransport> McpClient<T> {
44 pub async fn new(transport: Arc<T>, server_name: String) -> Result<Self> {
55 let mut client = Self {
56 transport,
57 server_name,
58 server_info: None,
59 };
60
61 client.initialize().await?;
62
63 Ok(client)
64 }
65
66 #[must_use]
70 pub const fn new_uninitialized(transport: Arc<T>, server_name: String) -> Self {
71 Self {
72 transport,
73 server_name,
74 server_info: None,
75 }
76 }
77
78 pub async fn initialize(&mut self) -> Result<&InitializeResult> {
86 let params = InitializeParams {
87 protocol_version: MCP_PROTOCOL_VERSION.to_string(),
88 capabilities: ClientCapabilities::default(),
89 client_info: ClientInfo {
90 name: "agent-sdk".to_string(),
91 version: env!("CARGO_PKG_VERSION").to_string(),
92 },
93 };
94
95 let request = JsonRpcRequest::new("initialize", Some(serde_json::to_value(¶ms)?), 0);
96
97 let response = self.transport.send(request).await?;
98
99 let result: InitializeResult = response
100 .result
101 .map(serde_json::from_value)
102 .transpose()
103 .context("Failed to parse initialize response")?
104 .context("Initialize response missing result")?;
105
106 let notification = JsonRpcRequest::new("notifications/initialized", None, 0);
108 let _ = self.transport.send(notification).await;
110
111 self.server_info = Some(result);
112
113 self.server_info
114 .as_ref()
115 .context("Server info not available")
116 }
117
118 #[must_use]
120 pub fn server_name(&self) -> &str {
121 &self.server_name
122 }
123
124 #[must_use]
126 pub const fn server_info(&self) -> Option<&InitializeResult> {
127 self.server_info.as_ref()
128 }
129
130 pub async fn list_tools(&self) -> Result<Vec<McpToolDefinition>> {
136 let request = JsonRpcRequest::new("tools/list", None, 0);
137
138 let response = self.transport.send(request).await?;
139
140 let result: ToolsListResult = response
141 .result
142 .map(serde_json::from_value)
143 .transpose()
144 .context("Failed to parse tools/list response")?
145 .context("tools/list response missing result")?;
146
147 Ok(result.tools)
148 }
149
150 pub async fn call_tool(&self, name: &str, arguments: Value) -> Result<McpToolCallResult> {
161 let params = ToolCallParams {
162 name: name.to_string(),
163 arguments: Some(arguments),
164 };
165
166 let request = JsonRpcRequest::new("tools/call", Some(serde_json::to_value(¶ms)?), 0);
167
168 let response = self.transport.send(request).await?;
169
170 if let Some(ref error) = response.error {
171 bail!("Tool call failed: {} (code {})", error.message, error.code);
172 }
173
174 let result: McpToolCallResult = response
175 .result
176 .map(serde_json::from_value)
177 .transpose()
178 .context("Failed to parse tools/call response")?
179 .context("tools/call response missing result")?;
180
181 Ok(result)
182 }
183
184 pub async fn call_tool_raw(
195 &self,
196 name: &str,
197 arguments: Option<Value>,
198 ) -> Result<McpToolCallResult> {
199 let args = arguments.unwrap_or_else(|| json!({}));
200 self.call_tool(name, args).await
201 }
202
203 pub async fn close(&self) -> Result<()> {
209 self.transport.close().await
210 }
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_mcp_protocol_version() {
219 assert!(!MCP_PROTOCOL_VERSION.is_empty());
220 }
221
222 #[test]
223 fn test_client_info() {
224 let info = ClientInfo {
225 name: "test".to_string(),
226 version: "1.0.0".to_string(),
227 };
228
229 let json = serde_json::to_string(&info).expect("serialize");
230 assert!(json.contains("test"));
231 assert!(json.contains("1.0.0"));
232 }
233}