1use anyhow::Result;
2use log::{debug, error, info, warn};
3use serde_json::json;
4use std::path::PathBuf;
5use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader, BufWriter};
6
7use crate::{
8 lsp::RustAnalyzerClient,
9 protocol::mcp::{MCPError, MCPRequest, MCPResponse},
10};
11
12pub struct RustAnalyzerMCPServer {
13 pub(super) client: Option<RustAnalyzerClient>,
14 pub(super) workspace_root: PathBuf,
15}
16
17impl Default for RustAnalyzerMCPServer {
18 fn default() -> Self {
19 Self::new()
20 }
21}
22
23impl RustAnalyzerMCPServer {
24 pub fn new() -> Self {
25 Self {
26 client: None,
27 workspace_root: std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")),
28 }
29 }
30
31 pub fn with_workspace(workspace_root: PathBuf) -> Self {
32 let workspace_root = workspace_root.canonicalize().unwrap_or_else(|_| {
34 if workspace_root.is_absolute() {
36 workspace_root.clone()
37 } else {
38 std::env::current_dir()
39 .unwrap_or_else(|_| PathBuf::from("."))
40 .join(&workspace_root)
41 }
42 });
43
44 Self {
45 client: None,
46 workspace_root,
47 }
48 }
49
50 pub(super) async fn ensure_client_started(&mut self) -> Result<()> {
51 if let Some(client) = &mut self.client {
55 if client.is_gone() {
56 match client.exit_status() {
57 Some(status) => warn!("rust-analyzer exited ({status}), restarting it"),
58 None => warn!("rust-analyzer closed its connection, restarting it"),
59 }
60 self.client = None;
61 }
62 }
63
64 if self.client.is_none() {
65 let mut client = RustAnalyzerClient::new(self.workspace_root.clone());
66 client.start().await?;
67 self.client = Some(client);
68 }
69 Ok(())
70 }
71
72 pub(super) async fn open_document_if_needed(&mut self, file_path: &str) -> Result<String> {
73 let absolute_path = self.workspace_root.join(file_path);
74 let absolute_path = absolute_path
76 .canonicalize()
77 .unwrap_or_else(|_| absolute_path.clone());
78 let uri = format!("file://{}", absolute_path.display());
79 let content = tokio::fs::read_to_string(&absolute_path)
80 .await
81 .map_err(|e| anyhow::anyhow!("Failed to read file {}: {}", file_path, e))?;
82
83 let Some(client) = &mut self.client else {
84 return Err(anyhow::anyhow!("Client not initialized"));
85 };
86
87 client.open_document(&uri, &content).await?;
88 Ok(uri)
89 }
90
91 pub async fn run(&mut self) -> Result<()> {
98 info!("Starting rust-analyzer MCP server");
99
100 let stdin = tokio::io::stdin();
101 let stdout = tokio::io::stdout();
102 let mut reader = BufReader::new(stdin);
103 let mut writer = BufWriter::new(stdout);
104
105 let mut shutdown = ShutdownSignal::new()?;
109 let mut signals_seen = 0u32;
111 let mut result = Ok(());
113
114 loop {
115 let mut line = String::new();
116 let bytes_read = tokio::select! {
119 biased;
123 _ = shutdown.recv() => {
124 info!("Received shutdown signal");
125 signals_seen += 1;
126 break;
127 }
128 read = reader.read_line(&mut line) => match read {
129 Ok(n) => n,
130 Err(e) => {
131 error!("Error reading from stdin: {}", e);
132 result = Err(e.into());
133 break;
134 }
135 },
136 };
137
138 if bytes_read == 0 {
139 break; }
141
142 let line = line.trim();
143 if line.is_empty() {
144 continue;
145 }
146
147 let Ok(request) = serde_json::from_str::<MCPRequest>(line) else {
148 debug!("Failed to parse request: {}", line);
149 continue;
150 };
151
152 debug!("Received request: {}", request.method);
153 let response = tokio::select! {
156 biased;
157 _ = shutdown.recv() => {
158 info!("Received shutdown signal");
159 signals_seen += 1;
160 break;
161 }
162 response = self.handle_request(request) => response,
163 };
164 let response_json = match serde_json::to_string(&response) {
166 Ok(json) => json,
167 Err(e) => {
168 error!("Failed to serialize response: {}", e);
169 result = Err(e.into());
170 break;
171 }
172 };
173 let written = async {
176 writer.write_all(response_json.as_bytes()).await?;
177 writer.write_all(b"\n").await?;
178 writer.flush().await
179 };
180 let written = tokio::select! {
181 biased;
182 _ = shutdown.recv() => {
183 info!("Received shutdown signal");
184 signals_seen += 1;
185 break;
186 }
187 written = written => written,
188 };
189 if let Err(e) = written {
190 error!("Error writing to stdout: {}", e);
191 result = Err(e.into());
192 break;
193 }
194 }
195
196 info!("Shutting down");
200 if let Some(client) = &mut self.client {
201 let graceful = {
202 let shutting_down = client.shutdown();
203 tokio::pin!(shutting_down);
204 loop {
205 tokio::select! {
206 biased;
207 _ = shutdown.recv() => {
208 signals_seen += 1;
209 if signals_seen >= 2 {
210 info!("Received another shutdown signal, killing rust-analyzer");
211 break false;
212 }
213 }
214 res = &mut shutting_down => {
215 let _ = res;
216 break true;
217 }
218 }
219 }
220 };
221 if !graceful {
222 client.force_kill().await;
223 }
224 }
225
226 result
227 }
228
229 async fn handle_request(&mut self, request: MCPRequest) -> MCPResponse {
230 match request.method.as_str() {
231 "initialize" => MCPResponse::Success {
232 jsonrpc: "2.0".to_string(),
233 id: request.id,
234 result: json!({
235 "protocolVersion": "2024-11-05",
236 "serverInfo": {
237 "name": "rust-analyzer-mcp",
238 "version": env!("CARGO_PKG_VERSION")
239 },
240 "capabilities": {
241 "tools": {}
242 }
243 }),
244 },
245 "tools/list" => MCPResponse::Success {
246 jsonrpc: "2.0".to_string(),
247 id: request.id,
248 result: json!({
249 "tools": super::tools::get_tools()
250 }),
251 },
252 "tools/call" => {
253 let Some(params) = request.params else {
254 return MCPResponse::Error {
255 jsonrpc: "2.0".to_string(),
256 id: request.id,
257 error: MCPError {
258 code: -32602,
259 message: "Invalid params".to_string(),
260 data: None,
261 },
262 };
263 };
264
265 let Some(tool_name) = params["name"].as_str() else {
266 return MCPResponse::Error {
267 jsonrpc: "2.0".to_string(),
268 id: request.id,
269 error: MCPError {
270 code: -32602,
271 message: "Missing tool name".to_string(),
272 data: None,
273 },
274 };
275 };
276
277 let args = params
278 .get("arguments")
279 .cloned()
280 .unwrap_or_else(|| json!({}));
281
282 match super::handlers::handle_tool_call(self, tool_name, args).await {
283 Ok(result) => MCPResponse::Success {
284 jsonrpc: "2.0".to_string(),
285 id: request.id,
286 result: serde_json::to_value(result).unwrap(),
287 },
288 Err(e) => {
289 error!("Tool call error: {}", e);
290 MCPResponse::Error {
291 jsonrpc: "2.0".to_string(),
292 id: request.id,
293 error: MCPError {
294 code: -1,
295 message: e.to_string(),
296 data: None,
297 },
298 }
299 }
300 }
301 }
302 _ => MCPResponse::Error {
303 jsonrpc: "2.0".to_string(),
304 id: request.id,
305 error: MCPError {
306 code: -32601,
307 message: format!("Method not found: {}", request.method),
308 data: None,
309 },
310 },
311 }
312 }
313}
314
315struct ShutdownSignal {
322 #[cfg(unix)]
323 sigint: tokio::signal::unix::Signal,
324 #[cfg(unix)]
325 sigterm: tokio::signal::unix::Signal,
326 #[cfg(unix)]
327 sighup: tokio::signal::unix::Signal,
328 #[cfg(windows)]
329 ctrl_c: tokio::signal::windows::CtrlC,
330 #[cfg(windows)]
331 ctrl_close: tokio::signal::windows::CtrlClose,
332}
333
334impl ShutdownSignal {
335 fn new() -> Result<Self> {
336 #[cfg(unix)]
337 {
338 use tokio::signal::unix::{signal, SignalKind};
339
340 Ok(Self {
341 sigint: signal(SignalKind::interrupt())?,
342 sigterm: signal(SignalKind::terminate())?,
343 sighup: signal(SignalKind::hangup())?,
344 })
345 }
346 #[cfg(windows)]
347 {
348 use tokio::signal::windows;
349
350 Ok(Self {
351 ctrl_c: windows::ctrl_c()?,
352 ctrl_close: windows::ctrl_close()?,
353 })
354 }
355 #[cfg(not(any(unix, windows)))]
356 Ok(Self {})
357 }
358
359 async fn recv(&mut self) {
361 #[cfg(unix)]
362 {
363 tokio::select! {
364 _ = self.sigint.recv() => {}
365 _ = self.sigterm.recv() => {}
366 _ = self.sighup.recv() => {}
367 }
368 }
369 #[cfg(windows)]
370 {
371 tokio::select! {
372 _ = self.ctrl_c.recv() => {}
373 _ = self.ctrl_close.recv() => {}
374 }
375 }
376 #[cfg(not(any(unix, windows)))]
377 {
378 std::future::pending::<()>().await;
380 }
381 }
382}