diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 68b2acf..476374f 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -8,7 +8,7 @@ on:
workflow_dispatch:
env:
- DOTNET_VERSION: '9.0.x'
+ DOTNET_VERSION: '10.0.x'
SOLUTION_FILE: 'DotNetDevMCP.sln'
jobs:
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index d3a7a2f..07ccfa6 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -36,7 +36,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
- dotnet-version: '9.0.x'
+ dotnet-version: '10.0.x'
- name: Restore dependencies
run: dotnet restore DotNetDevMCP.sln
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 319d54f..dfd0026 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -12,7 +12,7 @@ on:
type: string
env:
- DOTNET_VERSION: '9.0.x'
+ DOTNET_VERSION: '10.0.x'
SOLUTION_FILE: 'DotNetDevMCP.sln'
jobs:
diff --git a/.gitignore b/.gitignore
index 62067d4..b905e67 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,43 @@
-Nothing needs to be added to .gitignore since only a README.md file was modified and no build artifacts, dependencies, or temporary files were detected in the changes.
\ No newline at end of file
+```
+# Build artifacts
+**/bin/
+**/obj/
+**/out/
+*.dll
+*.exe
+*.pdb
+*.so
+*.dylib
+*.nupkg
+
+# Dependencies
+packages/
+**/node_modules/
+
+# Logs
+*.log
+
+# Environment
+.env
+.env.local
+*.env.*
+
+# Editors
+.vscode/
+.idea/
+*.swp
+*.swo
+
+# OS generated files
+.DS_Store
+Thumbs.db
+
+# Test and coverage
+coverage/
+**/TestResults/
+*.coverage
+
+# Local config
+**/appsettings.Development.json
+**/appsettings.Local.json
+```
\ No newline at end of file
diff --git a/src/DotNetDevMCP.Analysis/DotNetDevMCP.Analysis.csproj b/src/DotNetDevMCP.Analysis/DotNetDevMCP.Analysis.csproj
index b760144..58d26ff 100644
--- a/src/DotNetDevMCP.Analysis/DotNetDevMCP.Analysis.csproj
+++ b/src/DotNetDevMCP.Analysis/DotNetDevMCP.Analysis.csproj
@@ -6,4 +6,13 @@
enable
+
+
+
+
+
+
+
+
+
diff --git a/src/DotNetDevMCP.Analysis/Extensions/ServiceCollectionExtensions.cs b/src/DotNetDevMCP.Analysis/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..e9137b5
--- /dev/null
+++ b/src/DotNetDevMCP.Analysis/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,19 @@
+using Microsoft.Extensions.DependencyInjection;
+using DotNetDevMCP.Analysis.Services;
+
+namespace DotNetDevMCP.Analysis.Extensions;
+
+///
+/// Extension methods for registering Analysis services
+///
+public static class ServiceCollectionExtensions
+{
+ ///
+ /// Adds Analysis services to the service collection
+ ///
+ public static IServiceCollection AddAnalysisServices(this IServiceCollection services)
+ {
+ services.AddSingleton();
+ return services;
+ }
+}
diff --git a/src/DotNetDevMCP.Analysis/Mcp/Tools/AnalysisTools.cs b/src/DotNetDevMCP.Analysis/Mcp/Tools/AnalysisTools.cs
new file mode 100644
index 0000000..ad33f04
--- /dev/null
+++ b/src/DotNetDevMCP.Analysis/Mcp/Tools/AnalysisTools.cs
@@ -0,0 +1,115 @@
+using ModelContextProtocol.Server;
+using DotNetDevMCP.Analysis.Services;
+using DotNetDevMCP.Analysis.Models;
+
+namespace DotNetDevMCP.Analysis.Mcp.Tools;
+
+///
+/// MCP tools for code analysis and metrics
+///
+[McpServerToolType]
+public sealed class AnalysisTools(
+ CodeAnalysisService analysisService,
+ ILogger logger)
+{
+ private readonly CodeAnalysisService _analysisService = analysisService;
+ private readonly ILogger _logger = logger;
+
+ ///
+ /// Analyzes a .NET project or solution and returns comprehensive code metrics
+ ///
+ /// Path to the project file (.csproj), solution file (.sln), or directory
+ /// Include detailed code metrics (default: true)
+ /// Include dependency information (default: true)
+ [McpServerTool(Name = "dotnet_analyze_project")]
+ public async Task AnalyzeProjectAsync(
+ string path,
+ bool includeMetrics = true,
+ bool includeDependencies = true,
+ CancellationToken cancellationToken = default)
+ {
+ _logger.LogInformation("MCP: Analyzing project at {Path}", path);
+
+ if (!Directory.Exists(path) && !File.Exists(path))
+ {
+ throw new FileNotFoundException($"Path not found: {path}");
+ }
+
+ return await _analysisService.AnalyzeAsync(path, cancellationToken);
+ }
+
+ ///
+ /// Gets dependency information for a .NET project
+ ///
+ /// Path to the project file (.csproj)
+ [McpServerTool(Name = "dotnet_get_dependencies")]
+ public async Task GetDependenciesAsync(
+ string projectPath,
+ CancellationToken cancellationToken = default)
+ {
+ _logger.LogInformation("MCP: Getting dependencies for {Path}", projectPath);
+
+ if (!File.Exists(projectPath))
+ {
+ throw new FileNotFoundException($"Project file not found: {projectPath}");
+ }
+
+ return await _analysisService.GetDependenciesAsync(projectPath, cancellationToken);
+ }
+
+ ///
+ /// Analyzes code quality and identifies potential issues
+ ///
+ /// Path to the project or directory to analyze
+ [McpServerTool(Name = "dotnet_analyze_quality")]
+ public async Task AnalyzeQualityAsync(
+ string path,
+ CancellationToken cancellationToken = default)
+ {
+ _logger.LogInformation("MCP: Analyzing code quality at {Path}", path);
+
+ if (!Directory.Exists(path) && !File.Exists(path))
+ {
+ throw new FileNotFoundException($"Path not found: {path}");
+ }
+
+ return await _analysisService.AnalyzeQualityAsync(path, cancellationToken);
+ }
+
+ ///
+ /// Scans for outdated NuGet packages in a project
+ ///
+ /// Path to the project file (.csproj)
+ [McpServerTool(Name = "dotnet_scan_outdated_packages")]
+ public async Task> ScanOutdatedPackagesAsync(
+ string projectPath,
+ CancellationToken cancellationToken = default)
+ {
+ _logger.LogInformation("MCP: Scanning for outdated packages in {Path}", projectPath);
+
+ var deps = await _analysisService.GetDependenciesAsync(projectPath, cancellationToken);
+
+ // In a real implementation, this would check NuGet.org for latest versions
+ // For now, return all direct dependencies as potentially outdated
+ return deps.PackageDependencies
+ .Where(d => d.IsDirect)
+ .ToList()
+ .AsReadOnly();
+ }
+
+ ///
+ /// Detects circular dependencies in a solution
+ ///
+ /// Path to the solution file (.sln)
+ [McpServerTool(Name = "dotnet_detect_circular_dependencies")]
+ public async Task DetectCircularDependenciesAsync(
+ string solutionPath,
+ CancellationToken cancellationToken = default)
+ {
+ _logger.LogInformation("MCP: Detecting circular dependencies in {Path}", solutionPath);
+
+ // Simplified implementation - would need full graph analysis in production
+ var deps = await _analysisService.GetDependenciesAsync(solutionPath, cancellationToken);
+ return deps.HasCircularDependencies;
+ }
+}
diff --git a/src/DotNetDevMCP.Analysis/Models/AnalysisModels.cs b/src/DotNetDevMCP.Analysis/Models/AnalysisModels.cs
new file mode 100644
index 0000000..8709ad1
--- /dev/null
+++ b/src/DotNetDevMCP.Analysis/Models/AnalysisModels.cs
@@ -0,0 +1,91 @@
+namespace DotNetDevMCP.Analysis.Models;
+
+///
+/// Represents the result of a code analysis operation
+///
+public sealed record AnalysisResult(
+ string ProjectPath,
+ int TotalFiles,
+ int TotalLines,
+ int CodeLines,
+ int CommentLines,
+ int BlankLines,
+ int Classes,
+ int Methods,
+ double MaintainabilityIndex,
+ double CyclomaticComplexity,
+ DateTime AnalyzedAt
+);
+
+///
+/// Represents code quality metrics for a project
+///
+public sealed record CodeQualityMetrics(
+ string ProjectPath,
+ double TechnicalDebtHours,
+ int CodeSmells,
+ int Bugs,
+ int Vulnerabilities,
+ double Coverage,
+ double Duplication,
+ IReadOnlyList Issues,
+ DateTime AnalyzedAt
+);
+
+///
+/// Represents a single code issue
+///
+public sealed record CodeIssue(
+ string RuleId,
+ string Message,
+ string Severity,
+ string FilePath,
+ int LineNumber,
+ int ColumnNumber,
+ string? Suggestion = null
+);
+
+///
+/// Represents dependency information for a project
+///
+public sealed record DependencyInfo(
+ string ProjectPath,
+ IReadOnlyList PackageDependencies,
+ IReadOnlyList ProjectReferences,
+ IReadOnlyList FrameworkReferences,
+ bool HasCircularDependencies,
+ DateTime AnalyzedAt
+);
+
+///
+/// Represents a NuGet package dependency
+///
+public sealed record PackageDependency(
+ string Name,
+ string Version,
+ bool IsDirect,
+ bool IsDevelopmentDependency,
+ string? LicenseUrl = null,
+ bool? HasKnownVulnerabilities = null
+);
+
+///
+/// Represents a project reference
+///
+public sealed record ProjectReference(
+ string Name,
+ string Path,
+ bool IsProjectReference
+);
+
+///
+/// Request model for code analysis
+///
+public sealed record AnalysisRequest(
+ string Path,
+ bool IncludeMetrics = true,
+ bool IncludeDependencies = true,
+ bool IncludeIssues = true,
+ string? Configuration = null,
+ string? Platform = null
+);
diff --git a/src/DotNetDevMCP.Analysis/Services/CodeAnalysisService.cs b/src/DotNetDevMCP.Analysis/Services/CodeAnalysisService.cs
new file mode 100644
index 0000000..9ac31d4
--- /dev/null
+++ b/src/DotNetDevMCP.Analysis/Services/CodeAnalysisService.cs
@@ -0,0 +1,252 @@
+using System.Collections.Concurrent;
+using Microsoft.Build.Evaluation;
+using Microsoft.Build.Framework;
+using NuGet.ProjectModel;
+using DotNetDevMCP.Analysis.Models;
+
+namespace DotNetDevMCP.Analysis.Services;
+
+///
+/// Service for analyzing .NET code projects and solutions
+///
+public sealed class CodeAnalysisService(ILogger logger)
+{
+ private readonly ILogger _logger = logger;
+ private readonly ProjectCollection _projectCollection = new();
+
+ ///
+ /// Analyzes a project or solution and returns comprehensive metrics
+ ///
+ public async Task AnalyzeAsync(string path, CancellationToken cancellationToken = default)
+ {
+ _logger.LogInformation("Starting analysis of {Path}", path);
+
+ var files = Directory.Exists(path)
+ ? Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories)
+ : [path];
+
+ var totalLines = 0;
+ var codeLines = 0;
+ var commentLines = 0;
+ var blankLines = 0;
+ var classes = 0;
+ var methods = 0;
+
+ foreach (var file in files)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ var content = await File.ReadAllTextAsync(file, cancellationToken);
+ var lines = content.Split('\n');
+
+ totalLines += lines.Length;
+ blankLines += lines.Count(l => string.IsNullOrWhiteSpace(l));
+ commentLines += lines.Count(l => l.Trim().StartsWith("//") || l.Trim().StartsWith("/*") || l.Trim().StartsWith("*"));
+ codeLines += lines.Length - blankLines - commentLines;
+
+ classes += CountOccurrences(content, "class ") + CountOccurrences(content, "record ");
+ methods += CountOccurrences(content, "void ") + CountOccurrences(content, "async ") + CountOccurrences(content, "public ") + CountOccurrences(content, "private ");
+ }
+
+ var result = new AnalysisResult(
+ ProjectPath: path,
+ TotalFiles: files.Length,
+ TotalLines: totalLines,
+ CodeLines: codeLines,
+ CommentLines: commentLines,
+ BlankLines: blankLines,
+ Classes: classes,
+ Methods: methods,
+ MaintainabilityIndex: CalculateMaintainabilityIndex(codeLines, classes, methods),
+ CyclomaticComplexity: EstimateCyclomaticComplexity(files),
+ AnalyzedAt: DateTime.UtcNow
+ );
+
+ _logger.LogInformation("Analysis complete: {Files} files, {Lines} lines", files.Length, totalLines);
+ return result;
+ }
+
+ ///
+ /// Gets dependency information for a project
+ ///
+ public async Task GetDependenciesAsync(string projectPath, CancellationToken cancellationToken = default)
+ {
+ _logger.LogInformation("Analyzing dependencies for {Path}", projectPath);
+
+ var packageDependencies = new List();
+ var projectReferences = new List();
+ var frameworkReferences = new List();
+
+ if (File.Exists(projectPath))
+ {
+ var content = await File.ReadAllTextAsync(projectPath, cancellationToken);
+
+ // Parse package references
+ var packageRefs = System.Text.RegularExpressions.Regex.Matches(
+ content,
+ @"
+ /// Analyzes code quality and returns metrics
+ ///
+ public async Task AnalyzeQualityAsync(string path, CancellationToken cancellationToken = default)
+ {
+ _logger.LogInformation("Analyzing code quality for {Path}", path);
+
+ var issues = new List();
+
+ // Basic code smell detection
+ var files = Directory.Exists(path)
+ ? Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories)
+ : [path];
+
+ int codeSmells = 0;
+ int bugs = 0;
+ int vulnerabilities = 0;
+
+ foreach (var file in files)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ var content = await File.ReadAllTextAsync(file, cancellationToken);
+
+ // Detect long methods
+ var methods = System.Text.RegularExpressions.Regex.Matches(content, @"\{[^{}]*\}");
+ foreach (System.Text.RegularExpressions.Match method in methods)
+ {
+ var lineCount = method.Value.Count(c => c == '\n');
+ if (lineCount > 50)
+ {
+ codeSmells++;
+ issues.Add(new CodeIssue(
+ RuleId: "CS0001",
+ Message: "Method is too long (>50 lines)",
+ Severity: "Warning",
+ FilePath: file,
+ LineNumber: 0,
+ ColumnNumber: 0,
+ Suggestion: "Consider breaking this method into smaller methods"
+ ));
+ }
+ }
+
+ // Detect potential null reference issues
+ if (content.Contains(".ToString()") && !content.Contains("?."))
+ {
+ bugs++;
+ issues.Add(new CodeIssue(
+ RuleId: "CS0002",
+ Message: "Potential NullReferenceException",
+ Severity: "Warning",
+ FilePath: file,
+ LineNumber: 0,
+ ColumnNumber: 0,
+ Suggestion: "Use null-conditional operator (?.)"
+ ));
+ }
+ }
+
+ return new CodeQualityMetrics(
+ ProjectPath: path,
+ TechnicalDebtHours: codeSmells * 0.1,
+ CodeSmells: codeSmells,
+ Bugs: bugs,
+ Vulnerabilities: vulnerabilities,
+ Coverage: 0.0, // Would need test coverage tool integration
+ Duplication: 0.0, // Would need duplication detection
+ Issues: issues.AsReadOnly(),
+ AnalyzedAt: DateTime.UtcNow
+ );
+ }
+
+ private static int CountOccurrences(string text, string pattern)
+ {
+ var count = 0;
+ var index = 0;
+ while ((index = text.IndexOf(pattern, index, StringComparison.Ordinal)) != -1)
+ {
+ count++;
+ index += pattern.Length;
+ }
+ return count;
+ }
+
+ private static double CalculateMaintainabilityIndex(int codeLines, int classes, int methods)
+ {
+ // Simplified maintainability index calculation
+ if (codeLines == 0) return 100.0;
+
+ var avgMethodLength = codeLines / Math.Max(1, methods);
+ var avgMethodsPerClass = methods / Math.Max(1, classes);
+
+ var index = 171.0 - 5.2 * Math.Log(avgMethodLength) - 0.23 * avgMethodsPerClass - 16.2 * Math.Log(codeLines);
+ return Math.Max(0, Math.Min(100, index * 100 / 171));
+ }
+
+ private static double EstimateCyclomaticComplexity(string[] files)
+ {
+ var totalComplexity = 0;
+ foreach (var file in files)
+ {
+ if (!File.Exists(file)) continue;
+
+ var content = File.ReadAllText(file);
+ totalComplexity += CountOccurrences(content, "if ");
+ totalComplexity += CountOccurrences(content, "else ");
+ totalComplexity += CountOccurrences(content, "for ");
+ totalComplexity += CountOccurrences(content, "while ");
+ totalComplexity += CountOccurrences(content, "case ");
+ totalComplexity += CountOccurrences(content, "catch ");
+ totalComplexity += CountOccurrences(content, "&& ");
+ totalComplexity += CountOccurrences(content, "|| ");
+ }
+ return files.Length > 0 ? (double)totalComplexity / files.Length : 0;
+ }
+
+ public void Dispose() => _projectCollection.Dispose();
+}
diff --git a/src/DotNetDevMCP.Monitoring/DotNetDevMCP.Monitoring.csproj b/src/DotNetDevMCP.Monitoring/DotNetDevMCP.Monitoring.csproj
index b760144..e6b1083 100644
--- a/src/DotNetDevMCP.Monitoring/DotNetDevMCP.Monitoring.csproj
+++ b/src/DotNetDevMCP.Monitoring/DotNetDevMCP.Monitoring.csproj
@@ -6,4 +6,12 @@
enable
+
+
+
+
+
+
+
+
diff --git a/src/DotNetDevMCP.Monitoring/Extensions/ServiceCollectionExtensions.cs b/src/DotNetDevMCP.Monitoring/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 0000000..9e6e735
--- /dev/null
+++ b/src/DotNetDevMCP.Monitoring/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,19 @@
+using Microsoft.Extensions.DependencyInjection;
+using DotNetDevMCP.Monitoring.Services;
+
+namespace DotNetDevMCP.Monitoring.Extensions;
+
+///
+/// Extension methods for registering Monitoring services
+///
+public static class ServiceCollectionExtensions
+{
+ ///
+ /// Adds Monitoring services to the service collection
+ ///
+ public static IServiceCollection AddMonitoringServices(this IServiceCollection services)
+ {
+ services.AddSingleton();
+ return services;
+ }
+}
diff --git a/src/DotNetDevMCP.Monitoring/Mcp/Tools/MonitoringTools.cs b/src/DotNetDevMCP.Monitoring/Mcp/Tools/MonitoringTools.cs
new file mode 100644
index 0000000..68d1a52
--- /dev/null
+++ b/src/DotNetDevMCP.Monitoring/Mcp/Tools/MonitoringTools.cs
@@ -0,0 +1,137 @@
+using ModelContextProtocol.Server;
+using DotNetDevMCP.Monitoring.Services;
+using DotNetDevMCP.Monitoring.Models;
+
+namespace DotNetDevMCP.Monitoring.Mcp.Tools;
+
+///
+/// MCP tools for performance monitoring and profiling
+///
+[McpServerToolType]
+public sealed class MonitoringTools(
+ PerformanceMonitoringService monitoringService,
+ ILogger logger)
+{
+ private readonly PerformanceMonitoringService _monitoringService = monitoringService;
+ private readonly ILogger _logger = logger;
+
+ ///
+ /// Gets current performance metrics for the application
+ ///
+ [McpServerTool(Name = "dotnet_get_performance_metrics")]
+ public async Task GetPerformanceMetricsAsync(
+ CancellationToken cancellationToken = default)
+ {
+ _logger.LogInformation("MCP: Getting performance metrics");
+ return await _monitoringService.GetPerformanceMetricsAsync(cancellationToken);
+ }
+
+ ///
+ /// Gets system resource utilization metrics
+ ///
+ [McpServerTool(Name = "dotnet_get_resource_utilization")]
+ public async Task GetResourceUtilizationAsync(
+ CancellationToken cancellationToken = default)
+ {
+ _logger.LogInformation("MCP: Getting resource utilization");
+ return await _monitoringService.GetResourceUtilizationAsync(cancellationToken);
+ }
+
+ ///
+ /// Checks application health status
+ ///
+ [McpServerTool(Name = "dotnet_check_health")]
+ public async Task CheckHealthAsync(
+ CancellationToken cancellationToken = default)
+ {
+ _logger.LogInformation("MCP: Checking application health");
+ return await _monitoringService.CheckHealthAsync(cancellationToken);
+ }
+
+ ///
+ /// Starts a profiling session to collect performance data
+ ///
+ /// Name for the profiling session
+ /// Duration of the profiling session in seconds
+ /// Collect CPU samples (default: true)
+ /// Collect memory samples (default: true)
+ /// Collect GC events (default: true)
+ [McpServerTool(Name = "dotnet_start_profiling_session")]
+ public async Task StartProfilingSessionAsync(
+ string sessionName,
+ int durationSeconds = 10,
+ bool collectCpuSamples = true,
+ bool collectMemorySamples = true,
+ bool collectGcEvents = true,
+ CancellationToken cancellationToken = default)
+ {
+ _logger.LogInformation("MCP: Starting profiling session '{Session}' for {Duration}s",
+ sessionName, durationSeconds);
+
+ var config = new ProfilingConfig(
+ SessionName: sessionName,
+ Duration: TimeSpan.FromSeconds(durationSeconds),
+ CollectCpuSamples: collectCpuSamples,
+ CollectMemorySamples: collectMemorySamples,
+ CollectGcEvents: collectGcEvents,
+ CollectThreadEvents: true
+ );
+
+ return await _monitoringService.StartProfilingSessionAsync(config, cancellationToken);
+ }
+
+ ///
+ /// Gets garbage collection statistics
+ ///
+ [McpServerTool(Name = "dotnet_get_gc_stats")]
+ public async Task