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
This commit is contained in:
Stephen Halter 2025-03-31 19:23:49 -07:00 • committed by GitHub
commit 8dc1f5d096
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 27 additions and 6 deletions

View file

@ -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 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.) `McpTool` attribute as tools.)
```
dotnet add package ModelContextProtocol --prerelease
dotnet add package Microsoft.Extensions.Hosting
```
```csharp ```csharp
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server; using ModelContextProtocol.Server;
using System.ComponentModel; 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 builder.Services
.AddMcpServer() .AddMcpServer()
.WithStdioServerTransport() .WithStdioServerTransport()

View file

@ -5,7 +5,7 @@ using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Client; using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol.Transport; using ModelContextProtocol.Protocol.Transport;
var builder = Host.CreateEmptyApplicationBuilder(settings: null); var builder = Host.CreateApplicationBuilder(args);
builder.Configuration builder.Configuration
.AddEnvironmentVariables() .AddEnvironmentVariables()

View file

@ -1,13 +1,19 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Net.Http.Headers; using System.Net.Http.Headers;
var builder = Host.CreateEmptyApplicationBuilder(settings: null); var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMcpServer() builder.Services.AddMcpServer()
.WithStdioServerTransport() .WithStdioServerTransport()
.WithToolsFromAssembly(); .WithToolsFromAssembly();
builder.Logging.AddConsole(options =>
{
options.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services.AddSingleton(_ => builder.Services.AddSingleton(_ =>
{ {
var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") }; var client = new HttpClient() { BaseAddress = new Uri("https://api.weather.gov") };

View file

@ -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}")] [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); 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}")] [LoggerMessage(Level = LogLevel.Information, Message = "Transport for {endpointName} received stderr log: {data}")]
internal static partial void TransportError(this ILogger logger, string endpointName, string data); internal static partial void ReadStderr(this ILogger logger, string endpointName, string data);
[LoggerMessage(Level = LogLevel.Information, Message = "Transport process start failed for {endpointName}")] [LoggerMessage(Level = LogLevel.Information, Message = "Transport process start failed for {endpointName}")]
internal static partial void TransportProcessStartFailed(this ILogger logger, string endpointName); internal static partial void TransportProcessStartFailed(this ILogger logger, string endpointName);

View file

@ -85,7 +85,7 @@ public sealed class StdioClientTransport : IClientTransport
process = new() { StartInfo = startInfo }; process = new() { StartInfo = startInfo };
// Set up error logging // 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 need both stdin and stdout to use a no-BOM UTF-8 encoding. On .NET Core,
// we can use ProcessStartInfo.StandardOutputEncoding/StandardInputEncoding, but // we can use ProcessStartInfo.StandardOutputEncoding/StandardInputEncoding, but

View file

@ -24,6 +24,10 @@ internal static class Program
return LoggerFactory.Create(builder => return LoggerFactory.Create(builder =>
{ {
builder.AddConsole(options =>
{
options.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.AddSerilog(); builder.AddSerilog();
}); });
} }