avalonia-mcp-tools 0.1.0

MCP tools for AvaloniaUI development assistance
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
//! Security pattern tool - Security best practices generation
//!
//! This tool provides security patterns and best practices for AvaloniaUI applications.

use avalonia_mcp_core::error::AvaloniaMcpError;
use avalonia_mcp_core::markdown::MarkdownOutputBuilder;
use rmcp::model::{CallToolResult, Content};
use rmcp::tool;
use serde::{Deserialize, Serialize};
use schemars::JsonSchema;

/// Security pattern tool parameters
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct SecurityPatternParams {
    /// Security concern area (e.g., "authentication", "data-protection", "xss-prevention")
    pub area: Option<String>,
    /// Application type (e.g., "enterprise", "consumer", "kiosk")
    pub app_type: Option<String>,
    /// Include code examples
    pub include_examples: Option<bool>,
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct DataSecurityParams {
    pub security_area: Option<String>,
    pub include_encryption: Option<bool>,
    pub include_audit_logging: Option<bool>,
}

/// Security pattern tool for generating security best practices
#[derive(Debug, Clone, Default)]
pub struct SecurityPatternTool;

impl SecurityPatternTool {
    /// Create a new SecurityPatternTool instance
    pub fn new() -> Self {
        Self
    }

    /// Generate security patterns and best practices
    #[tool(description = "Generate security patterns and best practices for AvaloniaUI applications. Covers authentication, data protection, XSS prevention, and secure coding practices.")]
    pub async fn generate_security_pattern(
        &self,
        params: SecurityPatternParams,
    ) -> Result<CallToolResult, AvaloniaMcpError> {
        let area = params.area.as_deref().unwrap_or("general");
        let include_examples = params.include_examples.unwrap_or(true);

        tracing::info!(area, "Generating security patterns");

        let output = match area {
            "authentication" => self.generate_authentication_patterns(include_examples),
            "data-protection" => self.generate_data_protection_patterns(include_examples),
            "xss-prevention" => self.generate_xss_prevention_patterns(include_examples),
            "secure-coding" => self.generate_secure_coding_patterns(include_examples),
            _ => self.generate_general_security_patterns(include_examples),
        };

        Ok(CallToolResult::success(vec![Content::text(output)]))
    }

    #[tool(description = "Creates defensive data security patterns with proper encryption, sanitization, and audit logging for AvaloniaUI applications")]
    pub async fn generate_data_security_pattern(
        &self,
        params: DataSecurityParams,
    ) -> Result<CallToolResult, AvaloniaMcpError> {
        let security_area = params.security_area.as_deref().unwrap_or("encryption").to_lowercase();
        let include_encryption = params.include_encryption.unwrap_or(true);
        let include_audit = params.include_audit_logging.unwrap_or(true);

        let mut builder = MarkdownOutputBuilder::new()
            .heading(1, "Data Security Patterns")
            .heading(2, "Configuration")
            .task_list(vec![
                (true, format!("Area: {}", security_area)),
                (true, format!("Encryption: {}", include_encryption)),
                (true, format!("Audit Logging: {}", include_audit)),
            ])
            .heading(2, "Data Sanitization")
            .code_block(
                "csharp",
                r#"public static class InputSanitizer
{
    public static string Sanitize(string input)
    {
        if (string.IsNullOrEmpty(input)) return input;
        return WebUtility.HtmlEncode(input)
            .Replace("'", "''")  // SQL injection prevention
            .Trim();
    }
}"#,
            );

        if include_encryption {
            builder = builder
                .heading(2, "Encryption Implementation")
                .code_block(
                    "csharp",
                    r#"// AES Encryption for sensitive data
public class DataEncryptionService
{
    private readonly Aes _aes = Aes.Create();

    public string Encrypt(string plainText)
    {
        var encryptor = _aes.CreateEncryptor();
        var plainBytes = Encoding.UTF8.GetBytes(plainText);
        var cipherBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
        return Convert.ToBase64String(cipherBytes);
    }

    public string Decrypt(string cipherText)
    {
        var decryptor = _aes.CreateDecryptor();
        var cipherBytes = Convert.FromBase64String(cipherText);
        var plainBytes = decryptor.TransformFinalBlock(cipherBytes, 0, cipherBytes.Length);
        return Encoding.UTF8.GetString(plainBytes);
    }
}"#,
                );
        }

        if include_audit {
            builder = builder
                .heading(2, "Audit Logging")
                .code_block(
                    "csharp",
                    r#"public class AuditLogger
{
    private readonly ILogger<AuditLogger> _logger;

    public void LogAccess(string resource, string action, string userId)
    {
        _logger.LogInformation(
            "Audit: User {UserId} performed {Action} on {Resource} at {Timestamp}",
            userId, action, resource, DateTime.UtcNow);
    }

    public void LogSecurityEvent(string eventType, string details, string severity = "info")
    {
        _logger.LogWarning(
            "Security Event: {Type} - {Details} [{Severity}]",
            eventType, details, severity);
    }
}"#,
                );
        }

        builder = builder
            .heading(2, "Security Best Practices")
            .list(vec![
                "Encrypt sensitive data at rest",
                "Use parameterized queries",
                "Implement input validation",
                "Enable audit logging",
                "Follow principle of least privilege",
                "Regular security audits",
            ]);

        Ok(CallToolResult::success(vec![Content::text(builder.build())]))
    }

    /// Generate authentication patterns
    fn generate_authentication_patterns(&self, include_examples: bool) -> String {
        let mut builder = MarkdownOutputBuilder::new()
            .heading(1, "Authentication Security Patterns")
            .paragraph("Best practices for implementing secure authentication in AvaloniaUI applications.")
            .heading(2, "Key Principles")
            .list(vec![
                "Never store credentials in plain text",
                "Use secure credential storage (Windows Credential Manager, Keychain, etc.)",
                "Implement multi-factor authentication for sensitive operations",
                "Use token-based authentication with short expiration times",
                "Implement account lockout after failed attempts",
            ])
            .heading(2, "Implementation Patterns");

        if include_examples {
            builder = builder
                .heading(3, "Secure Credential Storage")
                .code_block("csharp", r#"// Use platform-specific secure storage
// Windows: CredentialManager
// macOS: Keychain
// Linux: libsecret

public interface ISecureStorage
{
    Task StoreAsync(string key, string value);
    Task<string?> RetrieveAsync(string key);
    Task DeleteAsync(string key);
}

// Implementation using protected data
public class SecureStorage : ISecureStorage
{
    public async Task StoreAsync(string key, string value)
    {
        var protectedData = ProtectedData.Protect(
            Encoding.UTF8.GetBytes(value),
            null,
            DataProtectionScope.CurrentUser);
        
        await SaveToFileAsync(key, protectedData);
    }
}"#)
                .heading(3, "Token-Based Authentication")
                .code_block("csharp", r#"// Implement token refresh mechanism
public class AuthenticationService
{
    private readonly ITokenService _tokenService;
    private DateTime _tokenExpiry;
    
    public async Task<bool> AuthenticateAsync(string username, string password)
    {
        var token = await _tokenService.RequestTokenAsync(username, password);
        _tokenExpiry = token.ExpiresAt.AddMinutes(-5); // Buffer time
        
        return true;
    }
    
    public async Task<string> GetValidTokenAsync()
    {
        if (DateTime.UtcNow >= _tokenExpiry)
        {
            await RefreshTokenAsync();
        }
        
        return _currentToken;
    }
}"#);
        }

        builder
            .heading(2, "Security Checklist")
            .task_list(vec![
                (true, "Credentials encrypted at rest"),
                (true, "Tokens have short expiration"),
                (true, "HTTPS for all network communication"),
                (true, "Account lockout implemented"),
                (false, "Multi-factor authentication enabled"),
            ])
            .build()
    }

    /// Generate data protection patterns
    fn generate_data_protection_patterns(&self, include_examples: bool) -> String {
        let mut builder = MarkdownOutputBuilder::new()
            .heading(1, "Data Protection Patterns")
            .paragraph("Protect sensitive data in AvaloniaUI applications using encryption and secure storage.")
            .heading(2, "Key Principles")
            .list(vec![
                "Encrypt sensitive data at rest",
                "Use platform-specific secure storage APIs",
                "Implement data classification (public, internal, confidential)",
                "Clear sensitive data from memory after use",
                "Use secure random number generation for keys",
            ])
            .heading(2, "Implementation Patterns");

        if include_examples {
            builder = builder
                .heading(3, "Data Encryption")
                .code_block("csharp", r#"// Use AES encryption for sensitive data
public class DataProtectionService
{
    private readonly Aes _aes;
    
    public DataProtectionService()
    {
        _aes = Aes.Create();
        _aes.KeySize = 256;
        _aes.Mode = CipherMode.GCM; // Authenticated encryption
    }
    
    public byte[] Encrypt(byte[] plaintext, byte[] key, byte[] iv)
    {
        using var transform = _aes.CreateEncryptor(key, iv);
        return transform.TransformFinalBlock(plaintext, 0, plaintext.Length);
    }
    
    public byte[] Decrypt(byte[] ciphertext, byte[] key, byte[] iv)
    {
        using var transform = _aes.CreateDecryptor(key, iv);
        return transform.TransformFinalBlock(ciphertext, 0, ciphertext.Length);
    }
}"#)
                .heading(3, "Secure Memory Handling")
                .code_block("csharp", r#"// Clear sensitive data from memory
public class SecureString : IDisposable
{
    private byte[] _data;
    
    public void Dispose()
    {
        if (_data != null)
        {
            // Overwrite with zeros
            Array.Clear(_data, 0, _data.Length);
            _data = null;
        }
    }
}"#);
        }

        builder
            .heading(2, "Security Checklist")
            .task_list(vec![
                (true, "Sensitive data encrypted"),
                (true, "Keys stored securely"),
                (true, "Memory cleared after use"),
                (false, "Data classification implemented"),
                (false, "Audit logging enabled"),
            ])
            .build()
    }

    /// Generate XSS prevention patterns
    fn generate_xss_prevention_patterns(&self, include_examples: bool) -> String {
        let mut builder = MarkdownOutputBuilder::new()
            .heading(1, "XSS Prevention Patterns")
            .paragraph("Prevent Cross-Site Scripting attacks in AvaloniaUI applications.")
            .heading(2, "Key Principles")
            .list(vec![
                "Never trust user input",
                "Encode all output displayed to users",
                "Use Content Security Policy (CSP) headers",
                "Validate and sanitize HTML input",
                "Use parameterized queries for database access",
            ])
            .heading(2, "Implementation Patterns");

        if include_examples {
            builder = builder
                .heading(3, "Input Validation")
                .code_block("csharp", r#"// Validate and sanitize user input
public class InputSanitizer
{
    private static readonly Regex ScriptPattern = 
        new Regex("<script.*?>.*?</script>", RegexOptions.IgnoreCase | RegexOptions.Singleline);
    
    public static string Sanitize(string input)
    {
        if (string.IsNullOrEmpty(input))
            return input;
        
        // Remove script tags
        var sanitized = ScriptPattern.Replace(input, string.Empty);
        
        // HTML encode remaining content
        return System.Net.WebUtility.HtmlEncode(sanitized);
    }
}"#)
                .heading(3, "Safe Text Display")
                .code_block("csharp", r#"// Use TextBlock instead of HtmlTextBlock for user content
<StackPanel>
    <!-- Safe: TextBlock automatically escapes content -->
    <TextBlock Text="{Binding UserInput}" />
    
    <!-- Dangerous: Avoid rendering raw HTML from users -->
    <!-- <local:HtmlTextBlock Html="{Binding UserHtml}" /> -->
</StackPanel>"#);
        }

        builder
            .heading(2, "Security Checklist")
            .task_list(vec![
                (true, "All user input validated"),
                (true, "Output encoded before display"),
                (true, "No raw HTML from users"),
                (false, "CSP headers configured"),
                (false, "Security headers implemented"),
            ])
            .build()
    }

    /// Generate secure coding patterns
    fn generate_secure_coding_patterns(&self, include_examples: bool) -> String {
        let mut builder = MarkdownOutputBuilder::new()
            .heading(1, "Secure Coding Patterns")
            .paragraph("General secure coding practices for AvaloniaUI applications.")
            .heading(2, "Key Principles")
            .list(vec![
                "Follow principle of least privilege",
                "Implement defense in depth",
                "Use secure defaults",
                "Log security events",
                "Keep dependencies updated",
                "Perform regular security audits",
            ])
            .heading(2, "Implementation Patterns");

        if include_examples {
            builder = builder
                .heading(3, "Exception Handling")
                .code_block("csharp", r#"// Don't expose internal errors to users
public class SecureExceptionHandler
{
    public static async Task HandleAsync(Exception ex)
    {
        // Log full details internally
        Logger.LogError(ex, "An error occurred");
        
        // Show generic message to user
        await ShowUserMessageAsync(
            "An unexpected error occurred. Please try again.");
        
        // Never expose:
        // - Stack traces
        // - Connection strings
        // - Internal paths
        // - Version information
    }
}"#)
                .heading(3, "Secure File Access")
                .code_block("csharp", r#"// Validate file paths to prevent directory traversal
public class SecureFileAccess
{
    private readonly string _baseDirectory;
    
    public async Task<string> ReadFileAsync(string relativePath)
    {
        // Validate and normalize path
        var fullPath = Path.GetFullPath(
            Path.Combine(_baseDirectory, relativePath));
        
        // Ensure path is within base directory
        if (!fullPath.StartsWith(_baseDirectory))
        {
            throw new SecurityException(
                "Access denied: Path traversal detected");
        }
        
        return await File.ReadAllTextAsync(fullPath);
    }
}"#);
        }

        builder
            .heading(2, "Security Checklist")
            .task_list(vec![
                (true, "Exceptions handled securely"),
                (true, "File paths validated"),
                (true, "Dependencies updated"),
                (true, "Security events logged"),
                (false, "Penetration testing performed"),
            ])
            .build()
    }

    /// Generate general security patterns
    fn generate_general_security_patterns(&self, include_examples: bool) -> String {
        let mut builder = MarkdownOutputBuilder::new()
            .heading(1, "General Security Patterns")
            .paragraph("Comprehensive security guidance for AvaloniaUI applications.")
            .heading(2, "Security Domains")
            .list(vec![
                "Authentication & Authorization",
                "Data Protection & Encryption",
                "Input Validation & Output Encoding",
                "Secure Communication",
                "Error Handling & Logging",
                "Dependency Management",
            ])
            .heading(2, "Quick Start Security Checklist")
            .task_list(vec![
                (false, "Implement secure authentication"),
                (false, "Encrypt sensitive data"),
                (false, "Validate all user input"),
                (false, "Use HTTPS for network calls"),
                (false, "Implement secure error handling"),
                (false, "Enable security logging"),
                (false, "Update all dependencies"),
                (false, "Perform security code review"),
            ])
            .heading(2, "Recommended Security Tools")
            .list(vec![
                "OWASP ZAP - Security testing",
                "SonarQube - Code quality and security",
                "Dependency-Check - Vulnerable dependency scanning",
                "dotnet-scan - .NET vulnerability scanning",
            ]);

        if include_examples {
            builder = builder
                .heading(2, "Security Configuration Template")
                .code_block("csharp", r#"// App security configuration
public class SecurityConfiguration
{
    // Authentication
    public int TokenExpirationMinutes { get; set; } = 30;
    public int MaxLoginAttempts { get; set; } = 5;
    public TimeSpan LockoutDuration { get; set; } = TimeSpan.FromMinutes(15);
    
    // Encryption
    public int EncryptionKeySize { get; set; } = 256;
    public string EncryptionAlgorithm { get; set; } = "AES-GCM";
    
    // Network
    public bool RequireHttps { get; set; } = true;
    public string[] AllowedOrigins { get; set; } = Array.Empty<string>();
    
    // Logging
    public bool LogSecurityEvents { get; set; } = true;
    public LogLevel SecurityLogLevel { get; set; } = LogLevel.Warning;
}"#);
        }

        builder
            .heading(2, "Next Steps")
            .numbered_list(vec![
                "Review authentication patterns",
                "Implement data protection",
                "Add input validation",
                "Configure secure logging",
                "Schedule security audit",
            ])
            .build()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_generate_security_pattern_general() {
        let tool = SecurityPatternTool::new();
        let params = SecurityPatternParams {
            area: None,
            app_type: None,
            include_examples: Some(true),
        };

        let result = tool.generate_security_pattern(params).await.unwrap();
        assert!(result.is_error.is_none() || result.is_error == Some(false));
        assert!(result.content.len() > 0);
    }

    #[tokio::test]
    async fn test_generate_security_pattern_authentication() {
        let tool = SecurityPatternTool::new();
        let params = SecurityPatternParams {
            area: Some("authentication".to_string()),
            app_type: None,
            include_examples: Some(true),
        };

        let result = tool.generate_security_pattern(params).await.unwrap();
        assert!(result.is_error.is_none() || result.is_error == Some(false));
    }

    #[tokio::test]
    async fn test_generate_security_pattern_no_examples() {
        let tool = SecurityPatternTool::new();
        let params = SecurityPatternParams {
            area: Some("general".to_string()),
            app_type: None,
            include_examples: Some(false),
        };

        let result = tool.generate_security_pattern(params).await.unwrap();
        assert!(result.is_error.is_none() || result.is_error == Some(false));
    }

    #[tokio::test]
    async fn test_generate_data_security_pattern_success() {
        let tool = SecurityPatternTool::new();
        let params = DataSecurityParams {
            security_area: Some("encryption".to_string()),
            include_encryption: Some(true),
            include_audit_logging: Some(true),
        };
        let result = tool.generate_data_security_pattern(params).await.unwrap();
        assert!(result.is_error.is_none() || result.is_error == Some(false));
        assert!(result.content.len() > 0);
    }

    #[tokio::test]
    async fn test_generate_data_security_pattern_no_encryption() {
        let tool = SecurityPatternTool::new();
        let params = DataSecurityParams {
            security_area: Some("sanitization".to_string()),
            include_encryption: Some(false),
            include_audit_logging: Some(true),
        };
        let result = tool.generate_data_security_pattern(params).await.unwrap();
        assert!(result.is_error.is_none() || result.is_error == Some(false));
    }

    #[tokio::test]
    async fn test_generate_data_security_pattern_no_audit() {
        let tool = SecurityPatternTool::new();
        let params = DataSecurityParams {
            security_area: Some("encryption".to_string()),
            include_encryption: Some(true),
            include_audit_logging: Some(false),
        };
        let result = tool.generate_data_security_pattern(params).await.unwrap();
        assert!(result.is_error.is_none() || result.is_error == Some(false));
    }

    #[tokio::test]
    async fn test_generate_data_security_pattern_defaults() {
        let tool = SecurityPatternTool::new();
        let params = DataSecurityParams {
            security_area: None,
            include_encryption: None,
            include_audit_logging: None,
        };
        let result = tool.generate_data_security_pattern(params).await.unwrap();
        assert!(result.is_error.is_none() || result.is_error == Some(false));
    }

    #[tokio::test]
    async fn test_generate_data_security_pattern_minimal() {
        let tool = SecurityPatternTool::new();
        let params = DataSecurityParams {
            security_area: Some("general".to_string()),
            include_encryption: Some(false),
            include_audit_logging: Some(false),
        };
        let result = tool.generate_data_security_pattern(params).await.unwrap();
        assert!(result.is_error.is_none() || result.is_error == Some(false));
    }
}