1use avalonia_mcp_core::error::AvaloniaMcpError;
3use avalonia_mcp_core::markdown::MarkdownOutputBuilder;
4use rmcp::model::{CallToolResult, Content};
5use rmcp::tool;
6use serde::{Deserialize, Serialize};
7use schemars::JsonSchema;
8
9#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
10pub struct DebuggingAssistantParams {
11 pub issue_type: Option<String>,
12 pub include_solutions: Option<bool>,
13}
14
15#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
16pub struct DebugUtilitiesParams {
17 pub utility_type: Option<String>,
18 pub include_devtools: Option<bool>,
19 pub include_telemetry: Option<bool>,
20}
21
22#[derive(Debug, Clone, Default)]
23pub struct DebuggingAssistantTool;
24
25impl DebuggingAssistantTool {
26 pub fn new() -> Self { Self }
27
28 #[tool(description = "Provide debugging assistance for AvaloniaUI applications. Covers common issues, debugging tools, breakpoints, and troubleshooting strategies.")]
29 pub async fn provide_debugging_assistance(
30 &self,
31 params: DebuggingAssistantParams,
32 ) -> Result<CallToolResult, AvaloniaMcpError> {
33 let include_solutions = params.include_solutions.unwrap_or(true);
34 let issue_type = params.issue_type.as_deref().unwrap_or("common");
35
36 let output = match issue_type {
37 "binding" => self.debug_binding_issues(include_solutions),
38 "layout" => self.debug_layout_issues(include_solutions),
39 "performance" => self.debug_performance_issues(include_solutions),
40 "memory" => self.debug_memory_issues(include_solutions),
41 _ => self.debug_common_issues(include_solutions),
42 };
43
44 Ok(CallToolResult::success(vec![Content::text(output)]))
45 }
46
47 #[tool(description = "Generates debug utilities and logging helpers for AvaloniaUI applications")]
48 pub async fn generate_debug_utilities(
49 &self,
50 params: DebugUtilitiesParams,
51 ) -> Result<CallToolResult, AvaloniaMcpError> {
52 let utility_type = params.utility_type.as_deref().unwrap_or("logger").to_lowercase();
53 let include_devtools = params.include_devtools.unwrap_or(true);
54 let include_telemetry = params.include_telemetry.unwrap_or(false);
55
56 let utility_code = match utility_type.as_str() {
57 "logger" => r#"public static class DebugLogger
58{
59 [Conditional("DEBUG")]
60 public static void Log(string message, [CallerMemberName] string member = "")
61 {
62 Console.WriteLine($"[{member}] {message}");
63 }
64
65 [Conditional("DEBUG")]
66 public static void LogBinding(object source, string property, object value)
67 {
68 Log($"Binding: {source.GetType().Name}.{property} = {value}");
69 }
70}"#,
71 "visualtree" => r#"public static class VisualTreeHelper
72{
73 public static void PrintVisualTree(Visual visual, int indent = 0)
74 {
75 var padding = new string(' ', indent * 2);
76 Console.WriteLine($"{padding}{visual.GetType().Name}");
77 foreach (var child in visual.GetVisualChildren())
78 {
79 PrintVisualTree(child, indent + 1);
80 }
81 }
82}"#,
83 "binding" => r#"public static class BindingDiagnostics
84{
85 public static void EnableBindingDebugging()
86 {
87 BindingLog.Enabled = true;
88 BindingLog.Subscribe(log =>
89 {
90 Console.WriteLine($"Binding: {log.Message}");
91 });
92 }
93}"#,
94 _ => "// Unknown utility type",
95 };
96
97 let mut builder = MarkdownOutputBuilder::new()
98 .heading(1, &format!("Debug Utilities: {}", utility_type))
99 .heading(2, "Utility Implementation")
100 .code_block("csharp", utility_code);
101
102 if include_devtools {
103 builder = builder
104 .heading(2, "DevTools Integration")
105 .code_block(
106 "csharp",
107 r#"// Enable DevTools in debug builds
108#if DEBUG
109app.SetupWithDevTools(options);
110#endif
111
112// Programmatic DevTools access
113public static void ShowDevTools()
114{
115 if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
116 {
117 var window = desktop.Windows.FirstOrDefault(w => w is DevToolsWindow);
118 window?.Show();
119 }
120}"#,
121 );
122 }
123
124 if include_telemetry {
125 builder = builder
126 .heading(2, "Telemetry Integration")
127 .code_block(
128 "csharp",
129 r#"public class AppTelemetry
130{
131 private readonly ILogger _logger;
132 public AppTelemetry(ILogger logger) => _logger = logger;
133
134 public void TrackEvent(string name, Dictionary<string, object>? properties = null)
135 {
136 _logger.LogInformation("Event: {Name} {@Properties}", name, properties);
137 }
138}"#,
139 );
140 }
141
142 builder = builder
143 .heading(2, "Setup Instructions")
144 .list(vec![
145 "Add utility classes to your project",
146 "Register services in Program.cs",
147 "Enable DevTools in debug builds only",
148 "Use structured logging",
149 ])
150 .heading(2, "Usage Tips")
151 .list(vec![
152 "Enable DevTools in debug builds only",
153 "Use structured logging for better analysis",
154 "Monitor performance metrics in production",
155 ]);
156
157 Ok(CallToolResult::success(vec![Content::text(builder.build())]))
158 }
159
160 fn debug_common_issues(&self, include_solutions: bool) -> String {
161 let mut builder = MarkdownOutputBuilder::new()
162 .heading(1, "Debugging Common Issues")
163 .paragraph("Troubleshooting guide for common AvaloniaUI issues.")
164 .heading(2, "Debugging Tools")
165 .list(vec![
166 "Visual Studio / Rider debugger",
167 "Avalonia DevTools (F12)",
168 "Serilog with file output",
169 "dotnet-trace for profiling",
170 ]);
171
172 if include_solutions {
173 builder = builder
174 .heading(2, "Binding Issues")
175 .code_block("csharp", r#"// Enable binding debug output
176AvaloniaLogging.LogLevel = LogLevel.Debug;
177AvaloniaLogging.Sink = new ConsoleSink
178{
179 Area = LogArea.Binding
180};
181
182// Common binding errors:
183// 1. Property not found: Check property name and INotifyPropertyChanged
184// 2. Type mismatch: Ensure source and target types match
185// 3. DataContext null: Set DataContext before bindings evaluate
186// 4. Thread affinity: Update UI on UI thread
187
188// Fix: Implement INotifyPropertyChanged properly
189public class ViewModel : INotifyPropertyChanged
190{
191 private string _name;
192 public string Name
193 {
194 get => _name;
195 set
196 {
197 _name = value;
198 OnPropertyChanged();
199 }
200 }
201
202 public event PropertyChangedEventHandler PropertyChanged;
203 protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
204 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
205}"#)
206 .heading(2, "Layout Issues")
207 .code_block("csharp", r#"// Debug layout with DevTools
208// Press F12 in running app
209
210// Common layout issues:
211// 1. Control not visible: Check Width/Height, Visibility, Parent container
212// 2. Overlapping controls: Check Grid.Row/Column, ZIndex
213// 3. Not filling space: Check Horizontal/VerticalAlignment
214// 4. Infinite measure: Avoid SizeToContent in ScrollViewer
215
216// Fix: Use layout debugging
217AvaloniaLogging.LogLevel = LogLevel.Debug;
218AvaloniaLogging.Sink = new ConsoleSink
219{
220 Area = LogArea.Layout
221};"#)
222 .heading(2, "Memory Leaks")
223 .code_block("csharp", r#"// Common memory leak sources:
224// 1. Event handlers not unsubscribed
225// 2. Static collections holding references
226// 3. Timers not stopped
227// 4. Observables not disposed
228
229// Fix: Use WeakEventManager for events
230WeakEventManager<EventHandler>.AddHandler(
231 button,
232 nameof(button.Click),
233 OnClick);
234
235// Fix: Dispose subscriptions
236private CompositeDisposable _disposables = new();
237
238observable.Subscribe(x => /* ... */)
239 .DisposeWith(_disposables);
240
241// Dispose in OnClosed
242protected override void OnClosed(EventArgs e)
243{
244 _disposables.Dispose();
245 base.OnClosed(e);
246}"#)
247 .heading(2, "Performance Issues")
248 .code_block("csharp", r#"// Profile with dotnet-trace
249// dotnet-trace collect --process-id <PID>
250
251// Common performance issues:
252// 1. Too many visual elements: Use virtualization
253// 2. Expensive bindings: Use x:CompileBindings
254// 3. Layout thrashing: Avoid frequent size changes
255// 4. Blocking UI thread: Use async/await
256
257// Fix: Enable UI virtualization
258<ListBox VirtualizationMode=\"Simple\">
259 <ListBox.ItemsPanel>
260 <ItemsPanelTemplate>
261 <VirtualizingStackPanel/>
262 </ItemsPanelTemplate>
263 </ListBox.ItemsPanel>
264</ListBox>"#);
265 }
266
267 builder.heading(2, "Debugging Checklist")
268 .task_list(vec![
269 (true, "Enable Avalonia DevTools"),
270 (true, "Check binding errors in output"),
271 (true, "Profile memory with dotnet-gcdump"),
272 (true, "Use breakpoints in ViewModels"),
273 (false, "Add logging for async operations"),
274 ])
275 .build()
276 }
277
278 fn debug_binding_issues(&self, _include_solutions: bool) -> String {
279 MarkdownOutputBuilder::new()
280 .heading(1, "Debugging Binding Issues")
281 .paragraph("Troubleshoot data binding problems.")
282 .heading(2, "Common Binding Errors")
283 .list(vec![
284 "Property not found on DataContext",
285 "Type conversion failed",
286 "Null reference in binding path",
287 "Collection not notifying changes",
288 ])
289 .heading(2, "Solutions")
290 .code_block("csharp", r#"// 1. Enable binding debug
291AvaloniaLogging.LogLevel = LogLevel.Debug;
292
293// 2. Check DataContext
294Debug.WriteLine($\"DataContext: {DataContext?.GetType().Name}\");
295
296// 3. Use compiled bindings for performance
297<x:CompileBindings>true</x:CompileBindings>
298
299// 4. Implement ObservableCollection for collections
300public ObservableCollection<Item> Items { get; } = new();"#)
301 .build()
302 }
303
304 fn debug_layout_issues(&self, _include_solutions: bool) -> String {
305 MarkdownOutputBuilder::new()
306 .heading(1, "Debugging Layout Issues")
307 .paragraph("Troubleshoot layout and rendering problems.")
308 .heading(2, "Common Layout Errors")
309 .list(vec![
310 "Control not visible",
311 "Incorrect sizing",
312 "Overlapping elements",
313 "Infinite measure loop",
314 ])
315 .heading(2, "Solutions")
316 .code_block("csharp", r#"// 1. Use DevTools (F12) to inspect visual tree
317// 2. Check HorizontalAlignment and VerticalAlignment
318// 3. Verify Grid.Row and Grid.Column attached properties
319// 4. Avoid SizeToContent=\"WidthAndHeight\" with ScrollViewer
320
321// Debug layout passes
322AvaloniaLogging.Sink = new ConsoleSink { Area = LogArea.Layout };"#)
323 .build()
324 }
325
326 fn debug_performance_issues(&self, _include_solutions: bool) -> String {
327 MarkdownOutputBuilder::new()
328 .heading(1, "Debugging Performance Issues")
329 .paragraph("Identify and fix performance bottlenecks.")
330 .heading(2, "Profiling Tools")
331 .list(vec!["dotnet-trace", "dotnet-gcdump", "Avalonia DevTools", "Visual Studio Profiler"])
332 .heading(2, "Common Issues")
333 .list(vec!["UI thread blocking", "Excessive GC", "Layout thrashing", "No virtualization"])
334 .build()
335 }
336
337 fn debug_memory_issues(&self, _include_solutions: bool) -> String {
338 MarkdownOutputBuilder::new()
339 .heading(1, "Debugging Memory Issues")
340 .paragraph("Find and fix memory leaks.")
341 .heading(2, "Memory Profiling")
342 .code_block("bash", r#"# Collect memory dump
343dotnet-gcdump collect --process-id <PID>
344
345# Analyze
346dotnet-dump analyze <dump>
347> gcroot
348> gcstat"#)
349 .heading(2, "Common Leaks")
350 .list(vec!["Event handlers", "Static collections", "Timers", "Observables"])
351 .build()
352 }
353}
354
355#[cfg(test)]
356mod tests {
357 use super::*;
358 #[tokio::test]
359 async fn test_provide_debugging_assistance() {
360 let tool = DebuggingAssistantTool::new();
361 let result = tool.provide_debugging_assistance(DebuggingAssistantParams { issue_type: None, include_solutions: Some(true) }).await.unwrap();
362 assert!(result.is_error.is_none() || result.is_error == Some(false));
363 }
364
365 #[tokio::test]
366 async fn test_generate_debug_utilities_logger() {
367 let tool = DebuggingAssistantTool::new();
368 let params = DebugUtilitiesParams {
369 utility_type: Some("logger".to_string()),
370 include_devtools: Some(true),
371 include_telemetry: Some(false),
372 };
373 let result = tool.generate_debug_utilities(params).await.unwrap();
374 assert!(result.is_error.is_none() || result.is_error == Some(false));
375 assert!(result.content.len() > 0);
376 }
377
378 #[tokio::test]
379 async fn test_generate_debug_utilities_visualtree() {
380 let tool = DebuggingAssistantTool::new();
381 let params = DebugUtilitiesParams {
382 utility_type: Some("visualtree".to_string()),
383 include_devtools: Some(false),
384 include_telemetry: Some(false),
385 };
386 let result = tool.generate_debug_utilities(params).await.unwrap();
387 assert!(result.is_error.is_none() || result.is_error == Some(false));
388 }
389
390 #[tokio::test]
391 async fn test_generate_debug_utilities_with_telemetry() {
392 let tool = DebuggingAssistantTool::new();
393 let params = DebugUtilitiesParams {
394 utility_type: Some("binding".to_string()),
395 include_devtools: Some(true),
396 include_telemetry: Some(true),
397 };
398 let result = tool.generate_debug_utilities(params).await.unwrap();
399 assert!(result.is_error.is_none() || result.is_error == Some(false));
400 }
401
402 #[tokio::test]
403 async fn test_generate_debug_utilities_unknown_type() {
404 let tool = DebuggingAssistantTool::new();
405 let params = DebugUtilitiesParams {
406 utility_type: Some("unknown".to_string()),
407 include_devtools: Some(false),
408 include_telemetry: Some(false),
409 };
410 let result = tool.generate_debug_utilities(params).await.unwrap();
411 assert!(result.is_error.is_none() || result.is_error == Some(false));
412 }
413
414 #[tokio::test]
415 async fn test_generate_debug_utilities_defaults() {
416 let tool = DebuggingAssistantTool::new();
417 let params = DebugUtilitiesParams {
418 utility_type: None,
419 include_devtools: None,
420 include_telemetry: None,
421 };
422 let result = tool.generate_debug_utilities(params).await.unwrap();
423 assert!(result.is_error.is_none() || result.is_error == Some(false));
424 }
425}