From 8dc1f5d096ad727660ce43586965bdbd7e78f64f Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Mon, 31 Mar 2025 19:23:49 -0700 Subject: [PATCH] Configure hosted stdio servers to log to stderr (#173) * Configure hosted stdio servers to log to stderr * Fix newline * Use CreateApplicationBuilder where possible * Address PR feedback --- README.md | 13 ++++++++++++- samples/QuickstartClient/Program.cs | 2 +- samples/QuickstartWeatherServer/Program.cs | 8 +++++++- src/ModelContextProtocol/Logging/Log.cs | 4 ++-- .../Protocol/Transport/StdioClientTransport.cs | 2 +- tests/ModelContextProtocol.TestServer/Program.cs | 4 ++++ 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 76cae4e..364354b 100644 --- a/README.md +++ b/README.md @@ -83,13 +83,24 @@ It includes a simple echo tool as an example (this is included in the same file the employed overload of `WithTools` examines the current assembly for classes with the `McpServerToolType` attribute, and registers all methods with the `McpTool` attribute as tools.) +``` +dotnet add package ModelContextProtocol --prerelease +dotnet add package Microsoft.Extensions.Hosting +``` + ```csharp using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using ModelContextProtocol.Server; using System.ComponentModel; -var builder = Host.CreateEmptyApplicationBuilder(settings: null); +var builder = Host.CreateApplicationBuilder(args); +builder.Logging.AddConsole(consoleLogOptions => +{ + // Configure all logs to go to stderr + consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace; +}); builder.Services .AddMcpServer() .WithStdioServerTransport() diff --git a/samples/QuickstartClient/Program.cs b/samples/QuickstartClient/Program.cs index 364c2b8..1ecd40c 100644 --- a/samples/QuickstartClient/Program.cs +++ b/samples/QuickstartClient/Program.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Hosting; using ModelContextProtocol.Client; using ModelContextProtocol.Protocol.Transport; -var builder = Host.CreateEmptyApplicationBuilder(settings: null); +var builder = Host.CreateApplicationBuilder(args); builder.Configuration .AddEnvironmentVariables() diff --git a/samples/QuickstartWeatherServer/Program.cs b/samples/QuickstartWeatherServer/Program.cs index a191cb1..8c3b384 100644 --- a/samples/QuickstartWeatherServer/Program.cs +++ b/samples/QuickstartWeatherServer/Program.cs @@ -1,13 +1,19 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using System.Net.Http.Headers; -var builder = Host.CreateEmptyApplicationBuilder(settings: null); +var builder = Host.CreateApplicationBuilder(args); builder.Services.AddMcpServer() .WithStdioServerTransport() .WithToolsFromAssembly(); +builder.Logging.AddConsole(options => +{ + options.LogToStandardErrorThreshold = LogLevel.Trace; +}); + builder.Services.AddSingleton(_ => { var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") }; diff --git a/src/ModelContextProtocol/Logging/Log.cs b/src/ModelContextProtocol/Logging/Log.cs index d22c5d6..aa9b209 100644 --- a/src/ModelContextProtocol/Logging/Log.cs +++ b/src/ModelContextProtocol/Logging/Log.cs @@ -98,8 +98,8 @@ internal static partial class Log [LoggerMessage(Level = LogLevel.Information, Message = "Creating process for transport for {endpointName} with command {command}, arguments {arguments}, environment {environment}, working directory {workingDirectory}, shutdown timeout {shutdownTimeout}")] internal static partial void CreateProcessForTransport(this ILogger logger, string endpointName, string command, string? arguments, string environment, string workingDirectory, string shutdownTimeout); - [LoggerMessage(Level = LogLevel.Error, Message = "Transport for {endpointName} error: {data}")] - internal static partial void TransportError(this ILogger logger, string endpointName, string data); + [LoggerMessage(Level = LogLevel.Information, Message = "Transport for {endpointName} received stderr log: {data}")] + internal static partial void ReadStderr(this ILogger logger, string endpointName, string data); [LoggerMessage(Level = LogLevel.Information, Message = "Transport process start failed for {endpointName}")] internal static partial void TransportProcessStartFailed(this ILogger logger, string endpointName); diff --git a/src/ModelContextProtocol/Protocol/Transport/StdioClientTransport.cs b/src/ModelContextProtocol/Protocol/Transport/StdioClientTransport.cs index 0fca07e..774677f 100644 --- a/src/ModelContextProtocol/Protocol/Transport/StdioClientTransport.cs +++ b/src/ModelContextProtocol/Protocol/Transport/StdioClientTransport.cs @@ -85,7 +85,7 @@ public sealed class StdioClientTransport : IClientTransport process = new() { StartInfo = startInfo }; // Set up error logging - process.ErrorDataReceived += (sender, args) => logger.TransportError(endpointName, args.Data ?? "(no data)"); + process.ErrorDataReceived += (sender, args) => logger.ReadStderr(endpointName, args.Data ?? "(no data)"); // We need both stdin and stdout to use a no-BOM UTF-8 encoding. On .NET Core, // we can use ProcessStartInfo.StandardOutputEncoding/StandardInputEncoding, but diff --git a/tests/ModelContextProtocol.TestServer/Program.cs b/tests/ModelContextProtocol.TestServer/Program.cs index 312013f..8c70360 100644 --- a/tests/ModelContextProtocol.TestServer/Program.cs +++ b/tests/ModelContextProtocol.TestServer/Program.cs @@ -24,6 +24,10 @@ internal static class Program return LoggerFactory.Create(builder => { + builder.AddConsole(options => + { + options.LogToStandardErrorThreshold = LogLevel.Trace; + }); builder.AddSerilog(); }); }