Skip to content

Latest commit

 

History

History
357 lines (264 loc) · 11.4 KB

File metadata and controls

357 lines (264 loc) · 11.4 KB

Telemetry and Observability

This document describes the telemetry and observability features available in dotnet-mcp, leveraging the MCP C# SDK v1.1.

Overview

dotnet-mcp provides comprehensive telemetry and observability through:

  1. Built-in SDK Telemetry - Request duration logging and OpenTelemetry semantic conventions provided by the MCP C# SDK
  2. Structured Logging - Microsoft.Extensions.Logging with configurable log levels
  3. OpenTelemetry Integration - Optional instrumentation for tools, resources, and operations

Built-in SDK Telemetry (v1.1)

The MCP C# SDK v1.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)

dotnet-mcp also layers MCP progress notifications on top of this for long-running operations such as restore, build, test, publish, pack, tool installation, and workload management. For task-augmented requests, the server keeps progress updates within the MCP request/task lifecycle rather than relying on ad-hoc console output.

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:

builder.Logging.AddConsole(options =>
{
    options.LogToStandardErrorThreshold = LogLevel.Trace;
});

Log Levels

Configure log levels via environment variables or appsettings.json:

# 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:

{
  "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):

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:

{
  "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:

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<string>("ServiceName") ?? "dotnet-mcp";
var serviceVersion = openTelemetryConfig.GetValue<string>("ServiceVersion") ?? "1.0.0";

// Configure OpenTelemetry tracing
var tracesEnabled = openTelemetryConfig.GetSection("Traces").GetValue<bool>("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<bool>("ConsoleExporter");
            if (consoleExporter)
                tracing.AddConsoleExporter();
            
            var otlpSection = tracesSection.GetSection("OtlpExporter");
            var otlpEnabled = otlpSection.GetValue<bool>("Enabled");
            if (otlpEnabled)
            {
                var endpoint = otlpSection.GetValue<string>("Endpoint");
                tracing.AddOtlpExporter(options =>
                {
                    if (!string.IsNullOrEmpty(endpoint))
                        options.Endpoint = new Uri(endpoint);
                });
            }
        });
}

// Configure OpenTelemetry metrics
var metricsEnabled = openTelemetryConfig.GetSection("Metrics").GetValue<bool>("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<bool>("ConsoleExporter");
            if (consoleExporter)
                metrics.AddConsoleExporter();
            
            var otlpSection = metricsSection.GetSection("OtlpExporter");
            var otlpEnabled = otlpSection.GetValue<bool>("Enabled");
            if (otlpEnabled)
            {
                var endpoint = otlpSection.GetValue<string>("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 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:

export Logging__LogLevel__Default=Debug

For production:

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

# 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:

# 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

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