This document describes the telemetry and observability features available in dotnet-mcp, leveraging the MCP C# SDK v1.1.
dotnet-mcp provides comprehensive telemetry and observability through:
- Built-in SDK Telemetry - Request duration logging and OpenTelemetry semantic conventions provided by the MCP C# SDK
- Structured Logging - Microsoft.Extensions.Logging with configurable log levels
- OpenTelemetry Integration - Optional instrumentation for tools, resources, and operations
The MCP C# SDK v1.1 automatically provides telemetry aligned with OpenTelemetry semantic conventions.
All MCP request handlers automatically log request duration:
LogRequestHandlerCompleted- Successful request completion with durationLogRequestHandlerException- 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.
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
dotnet-mcp uses Microsoft.Extensions.Logging with console output to stderr:
builder.Logging.AddConsole(options =>
{
options.LogToStandardErrorThreshold = LogLevel.Trace;
});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=TraceOr via appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"ModelContextProtocol": "Debug",
"DotNetMcp": "Trace"
}
}
}Note: The
appsettings.jsonfiles in this repository are for local development of dotnet-mcp and are not packaged with theCommunity.Mcp.DotNettool. When using the installed tool (for example viadnx), 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 anappsettings.jsonin your working directory with the desired settings.
ModelContextProtocol.*- SDK-level logs (request handling, transport, serialization)DotNetMcp.*- Server-level logs (tool execution, resource access, errors)Microsoft.Hosting.*- Hosting infrastructure logs
For production deployments, you can integrate OpenTelemetry for distributed tracing, metrics, and advanced observability.
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.OpenTelemetryProtocolCreate 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"
}
}
}
}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 => { /* ... */ });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 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.
Failed requests are automatically logged with:
- Error type and message
- Request duration
- Tool/resource name
- Stack trace (in debug mode)
For development:
export Logging__LogLevel__Default=DebugFor production:
export Logging__LogLevel__Default=Information
export Logging__LogLevel__ModelContextProtocol=WarningTrack 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
Configure alerts for:
- Error rate > 5%
- P95 latency > 2x baseline
- Any request > 10 seconds
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
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
# Enable all debug logs
export Logging__LogLevel__Default=Debug
# Run the server
dotnet-mcpAll request durations are logged automatically at Information level:
# Filter for request completion logs
dotnet-mcp 2>&1 | grep "Request handler completed"- Enable trace logging:
export Logging__LogLevel__DotNetMcp=Trace - Look for slow operations in logs (duration > 1000ms)
- Check for repeated slow operations (cache misses)
- Compare against baseline metrics in
doc/performance-baseline.md
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