1+ """
2+ This file contains the core business logic for the Rust Compiler MCP service.
3+ Note that the API integration and HTTP endpoints are now handled by app/mcp_tools.py.
4+ """
5+
6+ import os
7+ import tempfile
8+ import shutil
9+ from typing import Dict , List , Optional , Tuple
10+
11+ from app .compiler import RustCompiler
12+ from app .response_parser import ResponseParser
13+ from app .vector_store import QdrantStore
14+ from app .llm_client import LlamaEdgeClient
15+
16+ class RustCompilerMCP :
17+ def __init__ (self , vector_store = None , llm_client = None ):
18+ """
19+ Initialize the Rust Compiler MCP service
20+
21+ Args:
22+ vector_store: Vector store for searching similar examples
23+ llm_client: LLM client for generating fixes
24+ """
25+ self .compiler = RustCompiler ()
26+ self .parser = ResponseParser ()
27+
28+ # Use provided vector store or create a new one
29+ self .vector_store = vector_store
30+ if self .vector_store is None :
31+ self .vector_store = QdrantStore ()
32+ self .vector_store .create_collection ("project_examples" )
33+ self .vector_store .create_collection ("error_examples" )
34+
35+ # Use provided LLM client or create a new one
36+ self .llm_client = llm_client
37+ if self .llm_client is None :
38+ import os
39+ from dotenv import load_dotenv
40+ load_dotenv ()
41+ api_key = os .getenv ("LLM_API_KEY" )
42+ if api_key :
43+ self .llm_client = LlamaEdgeClient (api_key = api_key )
44+ else :
45+ raise ValueError ("LLM_API_KEY environment variable not set" )
46+
47+ def compile_rust_code (self , code_content : str ) -> Dict :
48+ """
49+ MCP function to compile Rust code
50+
51+ Args:
52+ code_content: String containing Rust code with file markers
53+
54+ Returns:
55+ Dict with compilation status, output, and error messages
56+ """
57+ # Create temp directory
58+ with tempfile .TemporaryDirectory () as temp_dir :
59+ # Parse the multi-file content
60+ files = self .parser .parse_response (code_content )
61+
62+ # Ensure project structure
63+ os .makedirs (os .path .join (temp_dir , "src" ), exist_ok = True )
64+
65+ # Write files
66+ file_paths = self .parser .write_files (files , temp_dir )
67+
68+ # Compile the project
69+ success , output = self .compiler .build_project (temp_dir )
70+
71+ if success :
72+ # Try running if compilation succeeded
73+ run_success , run_output = self .compiler .run_project (temp_dir )
74+
75+ # Store successful project in vector store for future reference
76+ self ._store_successful_project (files , code_content )
77+
78+ return {
79+ "success" : True ,
80+ "files" : file_paths ,
81+ "build_output" : output or "Build successful" ,
82+ "run_output" : run_output if run_success else "Failed to run project"
83+ }
84+ else :
85+ # Return error details
86+ error_context = self .compiler .extract_error_context (output )
87+ return {
88+ "success" : False ,
89+ "files" : file_paths ,
90+ "build_output" : output ,
91+ "error_details" : error_context
92+ }
93+
94+ def compile_and_fix_rust_code (self ,
95+ code_content : str ,
96+ description : str ,
97+ max_attempts : int = 3 ) -> Dict :
98+ """
99+ MCP function to compile Rust code and fix errors using LLM
100+
101+ Args:
102+ code_content: String containing Rust code with file markers
103+ description: Project description for context
104+ max_attempts: Maximum number of fix attempts
105+
106+ Returns:
107+ Dict with compilation status, output, fixes applied, and final code
108+ """
109+ # Create temp directory for this compilation
110+ with tempfile .TemporaryDirectory () as temp_dir :
111+ # Parse the multi-file content
112+ files = self .parser .parse_response (code_content )
113+
114+ # Ensure project structure
115+ os .makedirs (os .path .join (temp_dir , "src" ), exist_ok = True )
116+
117+ # Write files
118+ file_paths = self .parser .write_files (files , temp_dir )
119+
120+ # Check if we have similar projects in our dataset for reference
121+ project_reference = self ._find_similar_project (description )
122+
123+ attempts = []
124+ current_files = files .copy ()
125+
126+ # Try compiling and fixing up to max_attempts
127+ for attempt in range (max_attempts ):
128+ # Compile the project
129+ success , output = self .compiler .build_project (temp_dir )
130+
131+ # Store this attempt
132+ attempts .append ({
133+ "attempt" : attempt + 1 ,
134+ "success" : success ,
135+ "output" : output
136+ })
137+
138+ if success :
139+ # Try running if compilation succeeded
140+ run_success , run_output = self .compiler .run_project (temp_dir )
141+
142+ # Store this successful fix in our vector database
143+ if attempt > 0 : # Only store if we actually fixed something
144+ self ._store_successful_fix (code_content , current_files , output )
145+
146+ return {
147+ "success" : True ,
148+ "attempts" : attempts ,
149+ "final_files" : current_files ,
150+ "build_output" : output or "Build successful" ,
151+ "run_output" : run_output if run_success else "Failed to run project" ,
152+ "similar_project_used" : project_reference is not None
153+ }
154+
155+ # If we've reached max attempts without success, stop
156+ if attempt == max_attempts - 1 :
157+ break
158+
159+ # Extract error context for LLM
160+ error_context = self .compiler .extract_error_context (output )
161+
162+ # Find similar errors in vector DB
163+ similar_errors = self ._find_similar_errors (error_context ["full_error" ])
164+
165+ # Generate fix prompt
166+ fix_prompt = self ._generate_fix_prompt (
167+ description ,
168+ error_context ,
169+ similar_errors ,
170+ project_reference
171+ )
172+
173+ # Get fix from LLM
174+ fix_response = self .llm_client .generate_text (fix_prompt )
175+
176+ # Parse and apply fixes
177+ fixed_files = self .parser .parse_response (fix_response )
178+ for filename , content in fixed_files .items ():
179+ file_path = os .path .join (temp_dir , filename )
180+ os .makedirs (os .path .dirname (file_path ), exist_ok = True )
181+ with open (file_path , 'w' ) as f :
182+ f .write (content )
183+ current_files [filename ] = content
184+
185+ # If we've exhausted all attempts
186+ return {
187+ "success" : False ,
188+ "attempts" : attempts ,
189+ "final_files" : current_files ,
190+ "build_output" : attempts [- 1 ]["output" ],
191+ "error_details" : self .compiler .extract_error_context (attempts [- 1 ]["output" ]),
192+ "similar_project_used" : project_reference is not None ,
193+ "similar_errors_found" : len (similar_errors ) if 'similar_errors' in locals () else 0
194+ }
195+
196+ def _find_similar_project (self , description : str ) -> Optional [Dict ]:
197+ """Find similar project from vector store"""
198+ if not self .vector_store :
199+ return None
200+
201+ try :
202+ embeddings = self .llm_client .get_embeddings ([description ])[0 ]
203+ similar_projects = self .vector_store .search ("project_examples" , embeddings , limit = 1 )
204+
205+ if similar_projects :
206+ return similar_projects [0 ]
207+ except Exception as e :
208+ print (f"Error finding similar project: { e } " )
209+
210+ return None
211+
212+ def _find_similar_errors (self , error_text : str ) -> List [Dict ]:
213+ """Find similar error examples from vector store"""
214+ if not self .vector_store :
215+ return []
216+
217+ try :
218+ embeddings = self .llm_client .get_embeddings ([error_text ])[0 ]
219+ similar_errors = self .vector_store .search ("error_examples" , embeddings , limit = 3 )
220+ return similar_errors
221+ except Exception as e :
222+ print (f"Error finding similar errors: { e } " )
223+ return []
224+
225+ def _generate_fix_prompt (self ,
226+ description : str ,
227+ error_context : Dict ,
228+ similar_errors : List [Dict ],
229+ project_reference : Optional [Dict ]) -> str :
230+ """Generate prompt for fixing compilation errors"""
231+ fix_examples = ""
232+ if similar_errors :
233+ fix_examples = "Here are some examples of similar errors and their fixes:\n \n "
234+ for i , err in enumerate (similar_errors ):
235+ fix_examples += f"Example { i + 1 } :\n { err ['error' ]} \n Fix: { err ['solution' ]} \n \n "
236+
237+ reference_text = ""
238+ if project_reference :
239+ reference_text = f"\n Here's a similar project for reference:\n { project_reference ['example' ]} \n "
240+
241+ return f"""
242+ Here is a Rust project that failed to compile. Help me fix the compilation errors.
243+
244+ Project description: { description }
245+ { reference_text }
246+
247+ Compilation error:
248+ { error_context ["full_error" ]}
249+
250+ { fix_examples }
251+
252+ Please provide the fixed code for all affected files.
253+ """
254+
255+ def _store_successful_project (self , files : Dict [str , str ], code_content : str ):
256+ """Store successful project compilation in vector store"""
257+ if not self .vector_store :
258+ return
259+
260+ try :
261+ # Create a project description
262+ if "README.md" in files :
263+ description = files ["README.md" ][:500 ] # Use first 500 chars of README
264+ else :
265+ # Default to cargo.toml content
266+ description = files .get ("Cargo.toml" , "Rust project" )
267+
268+ # Try to get embedding or use dummy embedding if failed
269+ try :
270+ embeddings = self .llm_client .get_embeddings ([description ])[0 ]
271+ except Exception as e :
272+ print (f"Error getting embeddings: { e } " )
273+ # Use a dummy embedding of correct size
274+ embeddings = [0.0 ] * self .vector_store .embedding_size # Use configurable size
275+
276+ # Store in vector store
277+ self .vector_store .add_item (
278+ collection_name = "project_examples" ,
279+ vector = embeddings ,
280+ item = {
281+ "example" : code_content [:10000 ], # Limit size
282+ "description" : description
283+ }
284+ )
285+ except Exception as e :
286+ print (f"Failed to store project in vector DB: { e } " )
287+
288+ def _store_successful_fix (self , original_code : str , fixed_files : Dict [str , str ], error_output : str ):
289+ """Store successful error fix in vector store"""
290+ if not self .vector_store :
291+ return
292+
293+ try :
294+ # Create a combined fixed code representation
295+ fixed_code = "\n \n " .join ([
296+ f"[filename: { filename } ]\n { content } "
297+ for filename , content in fixed_files .items ()
298+ ])
299+
300+ # Get embedding of the error
301+ error_context = self .compiler .extract_error_context (error_output )
302+ embeddings = self .llm_client .get_embeddings ([error_context ["full_error" ]])[0 ]
303+
304+ # Store in vector store
305+ self .vector_store .add_item (
306+ collection_name = "error_examples" ,
307+ vector = embeddings ,
308+ item = {
309+ "error" : error_context ["full_error" ],
310+ "solution" : fixed_code [:5000 ], # Limit size
311+ "original_code" : original_code [:5000 ] # Limit size
312+ }
313+ )
314+ except Exception as e :
315+ print (f"Failed to store error fix in vector DB: { e } " )
316+
317+ def _fix_errors_with_llm (self , error_output , current_files , description , project_reference = None ):
318+ """Use LLM to fix compilation errors"""
319+ if not self .llm_client :
320+ print ("Warning: No LLM client available, returning original files" )
321+ return current_files
322+
323+ try :
324+ # Create prompt for error fixing
325+ prompt = self .prompt_generator .generate_error_fix_prompt (
326+ error_output = error_output ,
327+ current_files = current_files ,
328+ description = description ,
329+ project_reference = project_reference
330+ )
331+
332+ # Get LLM response
333+ response = self .llm_client .generate_text (
334+ prompt = prompt ,
335+ system_message = "You are an expert Rust developer fixing compilation errors." ,
336+ max_tokens = 4000 ,
337+ temperature = 0.2 # Lower temperature for more precise fixes
338+ )
339+
340+ # Parse response into fixed files
341+ fixed_files = self .parser .parse_response (response )
342+
343+ # Return the fixed files
344+ return fixed_files
345+ except Exception as e :
346+ print (f"Error fixing with LLM: { str (e )} " )
347+ # Return original files if LLM fails
348+ return current_files
0 commit comments