diff --git a/src/ModelContextProtocol/Client/McpClientExtensions.cs b/src/ModelContextProtocol/Client/McpClientExtensions.cs
index f11f0295..e13bcdf1 100644
--- a/src/ModelContextProtocol/Client/McpClientExtensions.cs
+++ b/src/ModelContextProtocol/Client/McpClientExtensions.cs
@@ -13,6 +13,19 @@ namespace ModelContextProtocol.Client;
///
public static class McpClientExtensions
{
+ ///
+ /// A request from the client to the server, to enable or adjust logging.
+ ///
+ /// The client.
+ /// The logging level severity to set.
+ /// A token to cancel the operation.
+ ///
+ public static Task SetLogLevelAsync(this IMcpClient client, LoggingLevel loggingLevel, CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(client);
+ return client.SendNotificationAsync("logging/setLevel", new SetLevelRequestParams { Level = loggingLevel }, cancellationToken);
+ }
+
///
/// Sends a notification to the server with parameters.
///
diff --git a/src/ModelContextProtocol/Logging/McpLogger.cs b/src/ModelContextProtocol/Logging/McpLogger.cs
new file mode 100644
index 00000000..4c42bdea
--- /dev/null
+++ b/src/ModelContextProtocol/Logging/McpLogger.cs
@@ -0,0 +1,42 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Collections.Concurrent;
+using ModelContextProtocol.Protocol.Types;
+using ModelContextProtocol.Server;
+using System.Text.Json;
+using System.Diagnostics.CodeAnalysis;
+
+namespace ModelContextProtocol.Logging;
+
+internal class McpLogger(string categoryName, IMcpServer mcpServer) : ILogger
+{
+ public IDisposable? BeginScope(TState state) where TState : notnull
+ => default;
+
+ public bool IsEnabled(LogLevel logLevel)
+ => logLevel.ToLoggingLevel() <= mcpServer.LoggingLevel;
+
+ [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")]
+ [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "")]
+ public async void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
+ {
+ if (!IsEnabled(logLevel))
+ return;
+
+ var message = formatter(state, exception);
+ if (string.IsNullOrEmpty(message))
+ return;
+
+ // Use JsonSerializer to properly escape the string for JSON and turn it into a JsonElement
+ var json = JsonSerializer.Serialize(message);
+ var element = JsonSerializer.Deserialize(json);
+
+ await mcpServer.SendLogNotificationAsync(new LoggingMessageNotificationParams
+ {
+ Data = element,
+ Level = logLevel.ToLoggingLevel(),
+ Logger = categoryName
+ });
+ }
+}
diff --git a/src/ModelContextProtocol/Logging/McpLoggerProvider.cs b/src/ModelContextProtocol/Logging/McpLoggerProvider.cs
new file mode 100644
index 00000000..f67ad08d
--- /dev/null
+++ b/src/ModelContextProtocol/Logging/McpLoggerProvider.cs
@@ -0,0 +1,30 @@
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using ModelContextProtocol.Server;
+using System;
+using System.Collections.Concurrent;
+
+namespace ModelContextProtocol.Logging
+{
+ ///
+ /// Provides logging over MCP's notifications to send log messages to the client
+ ///
+ /// MCP Server.
+ public class McpLoggerProvider(IMcpServer mcpServer) : ILoggerProvider
+ {
+ ///
+ /// Creates a new instance of an MCP logger
+ ///
+ /// Logger Category Name
+ /// New Logger instance
+ public ILogger CreateLogger(string categoryName)
+ => new McpLogger(categoryName, mcpServer);
+
+ ///
+ /// Dispose
+ ///
+ public void Dispose()
+ {
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ModelContextProtocol/Logging/McpLoggingLevelExtensions.cs b/src/ModelContextProtocol/Logging/McpLoggingLevelExtensions.cs
new file mode 100644
index 00000000..a9d7cf74
--- /dev/null
+++ b/src/ModelContextProtocol/Logging/McpLoggingLevelExtensions.cs
@@ -0,0 +1,29 @@
+using Microsoft.Extensions.Logging;
+using ModelContextProtocol.Protocol.Types;
+
+namespace ModelContextProtocol.Logging;
+
+ internal static class McpLoggingLevelExtensions
+{
+ public static LogLevel ToLogLevel(this LoggingLevel level)
+ => level switch
+ {
+ LoggingLevel.Emergency or LoggingLevel.Alert or LoggingLevel.Critical => LogLevel.Critical,
+ LoggingLevel.Error => LogLevel.Error,
+ LoggingLevel.Warning => LogLevel.Warning,
+ LoggingLevel.Notice or LoggingLevel.Info => LogLevel.Information,
+ LoggingLevel.Debug => LogLevel.Debug,
+ _ => LogLevel.None,
+ };
+
+ public static LoggingLevel ToLoggingLevel(this LogLevel level)
+ => level switch
+ {
+ LogLevel.Critical => LoggingLevel.Critical,
+ LogLevel.Error => LoggingLevel.Error,
+ LogLevel.Warning => LoggingLevel.Warning,
+ LogLevel.Information => LoggingLevel.Info,
+ LogLevel.Debug or LogLevel.Trace => LoggingLevel.Debug,
+ _ => LoggingLevel.Info,
+ };
+}
diff --git a/src/ModelContextProtocol/Server/IMcpServer.cs b/src/ModelContextProtocol/Server/IMcpServer.cs
index 7168a41c..a9743a07 100644
--- a/src/ModelContextProtocol/Server/IMcpServer.cs
+++ b/src/ModelContextProtocol/Server/IMcpServer.cs
@@ -28,6 +28,11 @@ public interface IMcpServer : IAsyncDisposable
///
IServiceProvider? ServiceProvider { get; }
+ ///
+ /// Current Logging level
+ ///
+ LoggingLevel LoggingLevel { get; }
+
///
/// Adds a handler for client notifications of a specific method.
///
diff --git a/src/ModelContextProtocol/Server/McpServer.cs b/src/ModelContextProtocol/Server/McpServer.cs
index c2fdb074..7d9851d4 100644
--- a/src/ModelContextProtocol/Server/McpServer.cs
+++ b/src/ModelContextProtocol/Server/McpServer.cs
@@ -35,6 +35,10 @@ public McpServer(ITransport transport, McpServerOptions options, ILoggerFactory?
_serverTransport = transport as IServerTransport;
_options = options;
+
+ // Add the MCP Logger provider to send MCP notification messages to the client when logs occur
+ loggerFactory?.AddProvider(new McpLoggerProvider(this));
+
_logger = (ILogger?)loggerFactory?.CreateLogger() ?? NullLogger.Instance;
ServerInstructions = options.ServerInstructions;
ServiceProvider = serviceProvider;
@@ -44,6 +48,7 @@ public McpServer(ITransport transport, McpServerOptions options, ILoggerFactory?
IsInitialized = true;
return Task.CompletedTask;
});
+ AddLoggingLevelNotificationHandler(options);
SetInitializeHandler(options);
SetCompletionHandler(options);
@@ -65,6 +70,8 @@ public McpServer(ITransport transport, McpServerOptions options, ILoggerFactory?
///
public IServiceProvider? ServiceProvider { get; }
+ public LoggingLevel LoggingLevel { get; private set; }
+
///
public override string EndpointName =>
$"Server ({_options.ServerInfo.Name} {_options.ServerInfo.Version}), Client ({ClientInfo?.Name} {ClientInfo?.Version})";
@@ -140,6 +147,18 @@ options.GetCompletionHandler is { } handler ?
(request, ct) => Task.FromResult(new CompleteResult() { Completion = new() { Values = [], Total = 0, HasMore = false } }));
}
+ private void AddLoggingLevelNotificationHandler(McpServerOptions options)
+ {
+ AddNotificationHandler("notifications/message", notification =>
+ {
+ if (notification.Params is LoggingMessageNotificationParams loggingMessageNotificationParams)
+ {
+ LoggingLevel = loggingMessageNotificationParams.Level;
+ }
+ return Task.CompletedTask;
+ });
+ }
+
private void SetResourcesHandler(McpServerOptions options)
{
if (options.Capabilities?.Resources is not { } resourcesCapability)
diff --git a/src/ModelContextProtocol/Server/McpServerExtensions.cs b/src/ModelContextProtocol/Server/McpServerExtensions.cs
index 3f746004..77a7988d 100644
--- a/src/ModelContextProtocol/Server/McpServerExtensions.cs
+++ b/src/ModelContextProtocol/Server/McpServerExtensions.cs
@@ -10,6 +10,22 @@ namespace ModelContextProtocol.Server;
///
public static class McpServerExtensions
{
+ ///
+ /// Sends a logging message notification to the client.
+ ///
+ /// The server instance that will handle the log notification request.
+ /// Contains the details of the log message to be sent.
+ /// Allows the operation to be canceled if needed.
+ /// Returns a task representing the asynchronous operation.
+ public static Task SendLogNotificationAsync(this IMcpServer server, LoggingMessageNotificationParams loggingMessageNotification, CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(server);
+ Throw.IfNull(loggingMessageNotification);
+ return server.SendRequestAsync(
+ new JsonRpcRequest { Method = "notifications/message", Params = loggingMessageNotification },
+ cancellationToken);
+ }
+
///
/// Requests to sample an LLM via the client.
///