@@ -5,6 +5,7 @@ use rust_mcp_sdk::{
55 macros:: { mcp_tool, JsonSchema } ,
66 tool_box,
77} ;
8+ use syncable_cli:: cli:: SeverityThreshold ;
89use std:: error:: Error ;
910use std:: fmt;
1011use std:: path:: Path ;
@@ -136,6 +137,79 @@ impl AnalysisScanTool {
136137 }
137138}
138139
140+ #[ mcp_tool(
141+ name = "vulnerability_scan" ,
142+ description = "Scans a project for known vulnerabilities."
143+ ) ]
144+
145+ #[ derive( Debug , :: serde:: Deserialize , :: serde:: Serialize , JsonSchema ) ]
146+ pub struct VulnerabilityScanTool {
147+ path : Option < String > ,
148+ }
149+
150+ impl VulnerabilityScanTool {
151+ pub async fn call_tool ( & self ) -> Result < CallToolResult , CallToolError > {
152+ let project_path_str = self . path . as_deref ( ) . unwrap_or ( "." ) ;
153+
154+ // Log to stderr so we don't interfere with MCP stdout JSON messages
155+ eprintln ! ( "🛡️ Scanning project for vulnerabilities: {}" , project_path_str) ;
156+ eprintln ! ( "➡️ Calling syncable_cli::handle_vulnerabilities..." ) ;
157+
158+ let vulnerability_results = tokio:: task:: spawn_blocking ( {
159+ let project_path = Path :: new ( project_path_str) . to_path_buf ( ) ;
160+ move || {
161+ // Create a runtime for the blocking task to handle the async function
162+ let rt = tokio:: runtime:: Runtime :: new ( ) . unwrap ( ) ;
163+ rt. block_on ( async {
164+ syncable_cli:: handle_vulnerabilities (
165+ project_path,
166+ None ,
167+ syncable_cli:: cli:: OutputFormat :: Json ,
168+ None ,
169+ ) . await
170+ } )
171+ }
172+ } ) . await ;
173+
174+ let vulnerability_results = match vulnerability_results {
175+ Ok ( result) => result,
176+ Err ( e) => return Err ( CallToolError :: new ( AnalyzeToolError ( format ! ( "Task panicked: {}" , e) ) ) ) ,
177+ } ;
178+
179+ match vulnerability_results {
180+ Ok ( analysis) => {
181+ let json_output = serde_json:: to_string_pretty ( & analysis) . unwrap_or_else ( |e| {
182+ format ! (
183+ "{{\" error\" : \" Failed to serialize analysis result: {}\" }}" ,
184+ e
185+ )
186+ } ) ;
187+
188+ eprintln ! ( "✅ handle_vulnerabilities returned ({} bytes)" , json_output. len( ) ) ;
189+
190+ // Validate JSON to ensure it's well-formed
191+ match serde_json:: from_str :: < serde_json:: Value > ( & json_output) {
192+ Ok ( _) => {
193+ eprintln ! ( "✅ JSON validation passed" ) ;
194+ eprintln ! ( "📤 Sending full response ({} bytes)" , json_output. len( ) ) ;
195+ Ok ( CallToolResult :: text_content ( vec ! [ TextContent :: new( json_output, None , None ) ] ) )
196+ }
197+ Err ( e) => {
198+ eprintln ! ( "⚠️ JSON validation failed: {}" , e) ;
199+ eprintln ! ( "First 500 chars: {}" , & json_output[ ..std:: cmp:: min( 500 , json_output. len( ) ) ] ) ;
200+ Err ( CallToolError :: new ( AnalyzeToolError ( format ! ( "Invalid JSON response: {}" , e) ) ) )
201+ }
202+ }
203+ }
204+ Err ( e) => {
205+ let error_message = format ! ( "Failed to analyze project for vulnerabilities: {}" , e) ;
206+ eprintln ! ( "❌ handle_vulnerabilities error: {}" , & error_message) ;
207+ Err ( CallToolError :: new ( AnalyzeToolError ( error_message) ) )
208+ }
209+ }
210+ }
211+ }
212+
139213#[ mcp_tool(
140214 name = "security_scan" ,
141215 description = "Scans a project for security vulnerabilities and secret leaks."
@@ -267,6 +341,7 @@ tool_box!(
267341 [
268342 AboutInfoTool ,
269343 AnalysisScanTool ,
344+ VulnerabilityScanTool ,
270345 SecurityScanTool ,
271346 DependencyScanTool
272347 ]
0 commit comments