diff --git a/.gitignore b/.gitignore index f769e12..e9d5d7d 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,10 @@ # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs +# appsettings files that may contain sensitive configuration +appsettings.Production.json +appsettings.*.local.json + # Mono auto generated files mono_crash.* diff --git a/DotNetMcp.Tests/Server/ServerCapabilitiesTests.cs b/DotNetMcp.Tests/Server/ServerCapabilitiesTests.cs index 1db2422..253c936 100644 --- a/DotNetMcp.Tests/Server/ServerCapabilitiesTests.cs +++ b/DotNetMcp.Tests/Server/ServerCapabilitiesTests.cs @@ -143,7 +143,7 @@ public async Task DotnetServerCapabilities_Supports_Cancellation_IsTrue() } [Fact] - public async Task DotnetServerCapabilities_Supports_Telemetry_IsFalse() + public async Task DotnetServerCapabilities_Supports_Telemetry_IsTrue() { // Act var result = await _tools.DotnetServerCapabilities(); @@ -153,8 +153,8 @@ public async Task DotnetServerCapabilities_Supports_Telemetry_IsFalse() .GetProperty("telemetry") .GetBoolean(); - // Assert - Telemetry is a future feature, should be false initially - Assert.False(telemetry); + // Assert - Telemetry is enabled via SDK v0.6+ (request duration logging, OpenTelemetry semantic conventions) + Assert.True(telemetry); } [Fact] @@ -231,7 +231,7 @@ public async Task DotnetServerCapabilities_JsonSchema_MatchesExpectedStructure() Assert.True(capabilities.Supports.StructuredErrors); Assert.True(capabilities.Supports.MachineReadable); Assert.True(capabilities.Supports.Cancellation); - Assert.False(capabilities.Supports.Telemetry); + Assert.True(capabilities.Supports.Telemetry); Assert.NotNull(capabilities.SdkVersions); Assert.NotEmpty(capabilities.SdkVersions.Installed); Assert.Equal("net10.0", capabilities.SdkVersions.Recommended); diff --git a/DotNetMcp/Server/ServerCapabilities.cs b/DotNetMcp/Server/ServerCapabilities.cs index 0b60419..4a303ee 100644 --- a/DotNetMcp/Server/ServerCapabilities.cs +++ b/DotNetMcp/Server/ServerCapabilities.cs @@ -63,7 +63,9 @@ public sealed class ServerFeatureSupport public bool Cancellation { get; init; } /// - /// Whether the server supports telemetry reporting (future feature) + /// Whether the server supports telemetry reporting. + /// When enabled, the server emits request duration logs and follows OpenTelemetry semantic conventions (SDK v0.6+). + /// See doc/telemetry.md for configuration details. /// [JsonPropertyName("telemetry")] public bool Telemetry { get; init; } diff --git a/DotNetMcp/Tools/Cli/DotNetCliTools.Misc.cs b/DotNetMcp/Tools/Cli/DotNetCliTools.Misc.cs index ad7c89c..44743d1 100644 --- a/DotNetMcp/Tools/Cli/DotNetCliTools.Misc.cs +++ b/DotNetMcp/Tools/Cli/DotNetCliTools.Misc.cs @@ -69,7 +69,7 @@ public async partial Task DotnetServerCapabilities() StructuredErrors = true, MachineReadable = true, Cancellation = true, - Telemetry = false // Future feature + Telemetry = true // SDK v0.6.0-preview.1 provides request duration logging and OpenTelemetry semantic conventions }, SdkVersions = new SdkVersionInfo { diff --git a/README.md b/README.md index a339798..c1d1b2f 100644 --- a/README.md +++ b/README.md @@ -1063,6 +1063,7 @@ Key files to start with: - 📖 [AI Assistant Best Practices Guide](doc/ai-assistant-guide.md) - **Workflows, prompts, integration patterns, and troubleshooting** - 📖 [Machine-Readable JSON Contract](doc/machine-readable-contract.md) - **v1.0 stable contract for programmatic tool consumption** - 📖 [Tool Surface Consolidation](doc/tool-surface-consolidation.md) - **Consolidated tool design and architecture** +- 📖 [Telemetry and Observability](doc/telemetry.md) - **Request duration logging, OpenTelemetry integration, and performance monitoring** - 📖 [SDK Integration Details](doc/sdk-integration.md) - Technical architecture and SDK usage - 📖 [Advanced Topics](doc/advanced-topics.md) - Performance, logging, and security details - 📖 [Releasing](doc/releasing.md) - How to cut a release (checklists + scripts) diff --git a/appsettings.Development.json b/appsettings.Development.json new file mode 100644 index 0000000..72272ba --- /dev/null +++ b/appsettings.Development.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/appsettings.json", + "Logging": { + "LogLevel": { + "Default": "Debug", + "ModelContextProtocol": "Debug", + "DotNetMcp": "Debug", + "Microsoft.Hosting": "Information" + } + }, + "OpenTelemetry": { + "ServiceName": "dotnet-mcp", + "ServiceVersion": "1.0.0-dev", + "Traces": { + "Enabled": false, + "ConsoleExporter": true, + "OtlpExporter": { + "Enabled": false, + "Endpoint": "http://localhost:4317" + } + }, + "Metrics": { + "Enabled": false, + "ConsoleExporter": true, + "OtlpExporter": { + "Enabled": false, + "Endpoint": "http://localhost:4317" + } + } + } +} diff --git a/appsettings.json b/appsettings.json new file mode 100644 index 0000000..1db779c --- /dev/null +++ b/appsettings.json @@ -0,0 +1,31 @@ +{ + "$schema": "https://json.schemastore.org/appsettings.json", + "Logging": { + "LogLevel": { + "Default": "Information", + "ModelContextProtocol": "Information", + "DotNetMcp": "Information", + "Microsoft.Hosting": "Warning" + } + }, + "OpenTelemetry": { + "ServiceName": "dotnet-mcp", + "ServiceVersion": "1.0.0", + "Traces": { + "Enabled": false, + "ConsoleExporter": false, + "OtlpExporter": { + "Enabled": false, + "Endpoint": "http://localhost:4317" + } + }, + "Metrics": { + "Enabled": false, + "ConsoleExporter": false, + "OtlpExporter": { + "Enabled": false, + "Endpoint": "http://localhost:4317" + } + } + } +} diff --git a/doc/performance-baseline.md b/doc/performance-baseline.md index e903f97..f1f4996 100644 --- a/doc/performance-baseline.md +++ b/doc/performance-baseline.md @@ -20,6 +20,11 @@ For comprehensive performance testing and regression detection, see [Issue #151] - More comprehensive tool coverage - Memory profiling +For telemetry and observability features, see [doc/telemetry.md](./telemetry.md): +- Request duration logging (SDK v0.6+) +- OpenTelemetry semantic conventions +- Distributed tracing integration + ## Test Methodology ### Configuration @@ -130,3 +135,48 @@ If results are consistently better: - Cache behavior significantly affects results (warmup is critical) - Results are informational only - tests never fail CI builds - For production performance budgets, use BenchmarkDotNet (Issue #151) + +## Using Telemetry for Performance Monitoring + +The MCP SDK v0.6+ automatically logs request duration for all tool invocations. To monitor performance in real-time: + +### View Request Durations + +All tool executions are logged with duration at `Information` level: + +```bash +# Run the server and filter for request completion logs +dotnet-mcp 2>&1 | grep "Request handler completed" +``` + +Example output: +``` +info: ModelContextProtocol.Server.McpServer[LogRequestHandlerCompleted] + Request handler completed: tools/call (DotnetSdkVersion) in 125ms + +info: ModelContextProtocol.Server.McpServer[LogRequestHandlerCompleted] + Request handler completed: tools/call (DotnetTemplateList) in 486ms +``` + +### Analyze Performance Trends + +Enable debug logging to see detailed execution traces: + +```bash +export Logging__LogLevel__Default=Debug +dotnet-mcp 2>&1 | tee performance-log.txt +``` + +Then analyze the log for: +- Average duration by tool +- P95 latency (95th percentile) +- Slowest operations +- Cache effectiveness + +### Compare Against Baselines + +Compare logged durations against the baselines in this document: +- DotnetSdkVersion: Expected ~100ms, investigate if >200ms +- DotnetTemplateList: Expected ~500ms (first run), ~50ms (cached) + +For detailed telemetry configuration and OpenTelemetry integration, see [doc/telemetry.md](./telemetry.md). diff --git a/doc/telemetry.md b/doc/telemetry.md new file mode 100644 index 0000000..6421e35 --- /dev/null +++ b/doc/telemetry.md @@ -0,0 +1,349 @@ +# Telemetry and Observability + +This document describes the telemetry and observability features available in dotnet-mcp, leveraging the MCP C# SDK v0.6.0-preview.1. + +## Overview + +dotnet-mcp provides comprehensive telemetry and observability through: + +1. **Built-in SDK Telemetry** - Request duration logging and OpenTelemetry semantic conventions (SDK v0.6+) +2. **Structured Logging** - Microsoft.Extensions.Logging with configurable log levels +3. **OpenTelemetry Integration** - Optional instrumentation for tools, resources, and operations + +## Built-in SDK Telemetry (v0.6+) + +The MCP C# SDK v0.6.0-preview.1 automatically provides telemetry aligned with OpenTelemetry semantic conventions. + +### Request Duration Logging + +All MCP request handlers automatically log request duration: + +- **`LogRequestHandlerCompleted`** - Successful request completion with duration +- **`LogRequestHandlerException`** - Failed request with duration and exception details + +These logs are emitted automatically by the SDK and include: +- Request method (tool invocation, resource access, etc.) +- Request parameters +- Execution duration (in milliseconds) +- Success/failure status +- Exception details (if applicable) + +### Log Examples + +``` +info: ModelContextProtocol.Server.McpServer[LogRequestHandlerCompleted] + Request handler completed: tools/call (DotnetSdkVersion) in 125ms + +info: ModelContextProtocol.Server.McpServer[LogRequestHandlerException] + Request handler failed: tools/call (DotnetProjectBuild) in 3450ms + Exception: System.InvalidOperationException: Build failed with exit code 1 +``` + +## Structured Logging Configuration + +### Default Configuration + +dotnet-mcp uses Microsoft.Extensions.Logging with console output to stderr: + +```csharp +builder.Logging.AddConsole(options => +{ + options.LogToStandardErrorThreshold = LogLevel.Trace; +}); +``` + +### Log Levels + +Configure log levels via environment variables or `appsettings.json`: + +```bash +# Set minimum log level +export Logging__LogLevel__Default=Information + +# Enable debug logging for MCP SDK +export Logging__LogLevel__ModelContextProtocol=Debug + +# Enable trace logging for dotnet-mcp +export Logging__LogLevel__DotNetMcp=Trace +``` + +Or via `appsettings.json`: + +```json +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "ModelContextProtocol": "Debug", + "DotNetMcp": "Trace" + } + } +} +``` + +> **Note**: The `appsettings.json` files in this repository are for local development of dotnet-mcp and are not packaged with the `Community.Mcp.DotNet` tool. When using the installed tool (for example via `dnx`), environment variables are the recommended way to configure logging and other settings, since they work regardless of installation method. If you prefer configuration files, create an `appsettings.json` in your working directory with the desired settings. + +### Log Categories + +- **`ModelContextProtocol.*`** - SDK-level logs (request handling, transport, serialization) +- **`DotNetMcp.*`** - Server-level logs (tool execution, resource access, errors) +- **`Microsoft.Hosting.*`** - Hosting infrastructure logs + +## OpenTelemetry Integration (Optional) + +For production deployments, you can integrate OpenTelemetry for distributed tracing, metrics, and advanced observability. + +### Installation + +Add OpenTelemetry packages to your deployment environment (latest stable versions recommended): + +```bash +dotnet add package OpenTelemetry.Extensions.Hosting +dotnet add package OpenTelemetry.Exporter.Console +dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol +``` + +### Configuration Example + +Create an `appsettings.OpenTelemetry.json` configuration file: + +```json +{ + "OpenTelemetry": { + "ServiceName": "dotnet-mcp", + "ServiceVersion": "1.0.0", + "Traces": { + "Enabled": true, + "ConsoleExporter": false, + "OtlpExporter": { + "Enabled": true, + "Endpoint": "http://localhost:4317" + } + }, + "Metrics": { + "Enabled": true, + "ConsoleExporter": false, + "OtlpExporter": { + "Enabled": true, + "Endpoint": "http://localhost:4317" + } + } + } +} +``` + +### Integration Code + +Modify `Program.cs` to add OpenTelemetry: + +```csharp +using OpenTelemetry; +using OpenTelemetry.Resources; +using OpenTelemetry.Trace; +using OpenTelemetry.Metrics; + +var builder = Host.CreateApplicationBuilder(args); + +// Add OpenTelemetry configuration +var openTelemetryConfig = builder.Configuration.GetSection("OpenTelemetry"); +var serviceName = openTelemetryConfig.GetValue("ServiceName") ?? "dotnet-mcp"; +var serviceVersion = openTelemetryConfig.GetValue("ServiceVersion") ?? "1.0.0"; + +// Configure OpenTelemetry tracing +var tracesEnabled = openTelemetryConfig.GetSection("Traces").GetValue("Enabled"); +if (tracesEnabled) +{ + builder.Services.AddOpenTelemetry() + .ConfigureResource(resource => resource + .AddService(serviceName, serviceVersion: serviceVersion)) + .WithTracing(tracing => + { + // NOTE: To capture custom spans from your application, you must first create corresponding + // ActivitySource instances (e.g., new ActivitySource("DotNetMcp")) in your code. + // The SDK does not currently implement custom ActivitySources (see Future Enhancements). + // When implemented, register them here: + // + // tracing + // .AddSource("DotNetMcp") + // .AddSource("ModelContextProtocol"); + + var tracesSection = openTelemetryConfig.GetSection("Traces"); + var consoleExporter = tracesSection.GetValue("ConsoleExporter"); + if (consoleExporter) + tracing.AddConsoleExporter(); + + var otlpSection = tracesSection.GetSection("OtlpExporter"); + var otlpEnabled = otlpSection.GetValue("Enabled"); + if (otlpEnabled) + { + var endpoint = otlpSection.GetValue("Endpoint"); + tracing.AddOtlpExporter(options => + { + if (!string.IsNullOrEmpty(endpoint)) + options.Endpoint = new Uri(endpoint); + }); + } + }); +} + +// Configure OpenTelemetry metrics +var metricsEnabled = openTelemetryConfig.GetSection("Metrics").GetValue("Enabled"); +if (metricsEnabled) +{ + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + // NOTE: To collect custom metrics from your application, you must first create corresponding + // Meter instances (e.g., new Meter("DotNetMcp")) in your code. + // The SDK does not currently implement custom Meters (see Future Enhancements). + // When implemented, register them here: + // + // metrics + // .AddMeter("DotNetMcp") + // .AddMeter("ModelContextProtocol"); + + var metricsSection = openTelemetryConfig.GetSection("Metrics"); + var consoleExporter = metricsSection.GetValue("ConsoleExporter"); + if (consoleExporter) + metrics.AddConsoleExporter(); + + var otlpSection = metricsSection.GetSection("OtlpExporter"); + var otlpEnabled = otlpSection.GetValue("Enabled"); + if (otlpEnabled) + { + var endpoint = otlpSection.GetValue("Endpoint"); + metrics.AddOtlpExporter(options => + { + if (!string.IsNullOrEmpty(endpoint)) + options.Endpoint = new Uri(endpoint); + }); + } + }); +} + +// Continue with standard MCP server configuration... +builder.Services.AddMcpServer(options => { /* ... */ }); +``` + +## Performance Metrics + +### Tool Execution Times + +The SDK automatically tracks and logs execution duration for all tool calls: + +- **Fast tools** (< 100ms): `DotnetSdkVersion`, `DotnetHelp` +- **Medium tools** (100-500ms): `DotnetTemplateList`, `DotnetPackageSearch` +- **Slow tools** (> 500ms): `DotnetProjectBuild`, `DotnetProjectTest`, `DotnetProjectPublish` + +See [doc/performance-baseline.md](./performance-baseline.md) for baseline performance measurements. + +### Resource Access Patterns + +Resource access is logged with duration: + +``` +info: ModelContextProtocol.Server.McpServer[LogRequestHandlerCompleted] + Request handler completed: resources/read (dotnet://info) in 45ms +``` + +> **Note**: The SDK logs request duration automatically. Cache status indicators like "(cached)" would require custom logging in your resource implementation. + +### Error Rates + +Failed requests are automatically logged with: +- Error type and message +- Request duration +- Tool/resource name +- Stack trace (in debug mode) + +## Monitoring Best Practices + +### 1. Enable Appropriate Log Levels + +For **development**: +```bash +export Logging__LogLevel__Default=Debug +``` + +For **production**: +```bash +export Logging__LogLevel__Default=Information +export Logging__LogLevel__ModelContextProtocol=Warning +``` + +### 2. Monitor Key Metrics + +Track these key performance indicators: + +- **P95 latency** - 95th percentile request duration +- **Error rate** - Failed requests / total requests +- **Slow operations** - Requests > 1000ms +- **Cache hit rate** - Cached responses / total responses + +### 3. Set Up Alerts + +Configure alerts for: +- Error rate > 5% +- P95 latency > 2x baseline +- Any request > 10 seconds + +### 4. Use Distributed Tracing + +For multi-service deployments, use OpenTelemetry OTLP exporters to send traces to: +- **Jaeger** - Open-source distributed tracing +- **Zipkin** - Distributed tracing system +- **Azure Monitor** - Cloud-native observability +- **Grafana Cloud** - Managed observability platform + +## Telemetry Data Privacy + +dotnet-mcp telemetry respects user privacy: + +- **No sensitive data** - Command output containing secrets or credentials is never logged +- **Sanitized parameters** - Sensitive parameters are redacted in logs +- **Opt-in only** - OpenTelemetry integration requires explicit configuration +- **Local by default** - Logs are written to stderr, not sent externally + +## Troubleshooting + +### Enable Debug Logging + +```bash +# Enable all debug logs +export Logging__LogLevel__Default=Debug + +# Run the server +dotnet-mcp +``` + +### View Request Durations + +All request durations are logged automatically at `Information` level: + +```bash +# Filter for request completion logs +dotnet-mcp 2>&1 | grep "Request handler completed" +``` + +### Analyze Performance Issues + +1. Enable trace logging: `export Logging__LogLevel__DotNetMcp=Trace` +2. Look for slow operations in logs (duration > 1000ms) +3. Check for repeated slow operations (cache misses) +4. Compare against baseline metrics in `doc/performance-baseline.md` + +## References + +- [MCP C# SDK v0.6 Release Notes](https://github.com/modelcontextprotocol/csharp-sdk/releases/tag/v0.6.0-preview.1) +- [OpenTelemetry .NET Documentation](https://opentelemetry.io/docs/languages/net/) +- [Performance Baseline Measurements](./performance-baseline.md) + +## Future Enhancements + +Planned telemetry improvements: + +- Custom ActivitySource for tool execution spans +- Metrics for cache hit/miss rates +- Performance budgets with automated regression detection +- Integration with Application Insights +- Grafana dashboard templates