Enable servers to log to the clients via ILogger (#229)
* Enable servers to log to the client via ILogger * Address feedback
This commit is contained in:
parent
3a2e9a228d
commit
3b83ad4103
8 changed files with 242 additions and 18 deletions
|
|
@ -1,6 +1,8 @@
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using ModelContextProtocol.Protocol.Messages;
|
using ModelContextProtocol.Protocol.Messages;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
|
using ModelContextProtocol.Server;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
using ModelContextProtocol.Utils.Json;
|
using ModelContextProtocol.Utils.Json;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
@ -631,6 +633,15 @@ public static class McpClientExtensions
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures the minimum logging level for the server.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client">The client.</param>
|
||||||
|
/// <param name="level">The minimum log level of messages to be generated.</param>
|
||||||
|
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||||
|
public static Task SetLoggingLevel(this IMcpClient client, LogLevel level, CancellationToken cancellationToken = default) =>
|
||||||
|
SetLoggingLevel(client, McpServer.ToLoggingLevel(level), cancellationToken);
|
||||||
|
|
||||||
/// <summary>Convers a dictionary with <see cref="object"/> values to a dictionary with <see cref="JsonElement"/> values.</summary>
|
/// <summary>Convers a dictionary with <see cref="object"/> values to a dictionary with <see cref="JsonElement"/> values.</summary>
|
||||||
private static IReadOnlyDictionary<string, JsonElement>? ToArgumentsDictionary(
|
private static IReadOnlyDictionary<string, JsonElement>? ToArgumentsDictionary(
|
||||||
IReadOnlyDictionary<string, object?>? arguments, JsonSerializerOptions options)
|
IReadOnlyDictionary<string, object?>? arguments, JsonSerializerOptions options)
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ public static class McpEndpointExtensions
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends a notification to the server with parameters.
|
/// Sends a notification to the server with no parameters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client">The client.</param>
|
/// <param name="client">The client.</param>
|
||||||
/// <param name="method">The notification method name.</param>
|
/// <param name="method">The notification method name.</param>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
namespace ModelContextProtocol.Protocol.Types;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace ModelContextProtocol.Protocol.Types;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An empty result object.
|
/// An empty result object.
|
||||||
|
|
@ -6,5 +8,6 @@
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class EmptyResult
|
public class EmptyResult
|
||||||
{
|
{
|
||||||
|
[JsonIgnore]
|
||||||
|
internal static Task<EmptyResult> CompletedTask { get; } = Task.FromResult(new EmptyResult());
|
||||||
}
|
}
|
||||||
|
|
@ -25,6 +25,9 @@ public interface IMcpServer : IMcpEndpoint
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IServiceProvider? Services { get; }
|
IServiceProvider? Services { get; }
|
||||||
|
|
||||||
|
/// <summary>Gets the last logging level set by the client, or <see langword="null"/> if it's never been set.</summary>
|
||||||
|
LoggingLevel? LoggingLevel { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs the server, listening for and handling client requests.
|
/// Runs the server, listening for and handling client requests.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Shared;
|
using ModelContextProtocol.Shared;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
using ModelContextProtocol.Utils.Json;
|
using ModelContextProtocol.Utils.Json;
|
||||||
using System.Diagnostics;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Server;
|
namespace ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
|
@ -26,6 +26,13 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
||||||
private string _endpointName;
|
private string _endpointName;
|
||||||
private int _started;
|
private int _started;
|
||||||
|
|
||||||
|
/// <summary>Holds a boxed <see cref="LoggingLevel"/> value for the server.</summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Initialized to non-null the first time SetLevel is used. This is stored as a strong box
|
||||||
|
/// rather than a nullable to be able to manipulate it atomically.
|
||||||
|
/// </remarks>
|
||||||
|
private StrongBox<LoggingLevel>? _loggingLevel;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of <see cref="McpServer"/>.
|
/// Creates a new instance of <see cref="McpServer"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -105,6 +112,9 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override string EndpointName => _endpointName;
|
public override string EndpointName => _endpointName;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public LoggingLevel? LoggingLevel => _loggingLevel?.Value;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task RunAsync(CancellationToken cancellationToken = default)
|
public async Task RunAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
|
@ -441,20 +451,48 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
||||||
|
|
||||||
private void SetSetLoggingLevelHandler(McpServerOptions options)
|
private void SetSetLoggingLevelHandler(McpServerOptions options)
|
||||||
{
|
{
|
||||||
if (options.Capabilities?.Logging is not { } loggingCapability)
|
// We don't require that the handler be provided, as we always store the provided
|
||||||
{
|
// log level to the server.
|
||||||
return;
|
var setLoggingLevelHandler = options.Capabilities?.Logging?.SetLoggingLevelHandler;
|
||||||
}
|
|
||||||
|
|
||||||
if (loggingCapability.SetLoggingLevelHandler is not { } setLoggingLevelHandler)
|
|
||||||
{
|
|
||||||
throw new McpException("Logging capability was enabled, but SetLoggingLevelHandler was not specified.");
|
|
||||||
}
|
|
||||||
|
|
||||||
RequestHandlers.Set(
|
RequestHandlers.Set(
|
||||||
RequestMethods.LoggingSetLevel,
|
RequestMethods.LoggingSetLevel,
|
||||||
(request, cancellationToken) => setLoggingLevelHandler(new(this, request), cancellationToken),
|
(request, cancellationToken) =>
|
||||||
|
{
|
||||||
|
// Store the provided level.
|
||||||
|
if (request is not null)
|
||||||
|
{
|
||||||
|
if (_loggingLevel is null)
|
||||||
|
{
|
||||||
|
Interlocked.CompareExchange(ref _loggingLevel, new(request.Level), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
_loggingLevel.Value = request.Level;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a handler was provided, now delegate to it.
|
||||||
|
if (setLoggingLevelHandler is not null)
|
||||||
|
{
|
||||||
|
return setLoggingLevelHandler(new(this, request), cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, consider it handled.
|
||||||
|
return EmptyResult.CompletedTask;
|
||||||
|
},
|
||||||
McpJsonUtilities.JsonContext.Default.SetLevelRequestParams,
|
McpJsonUtilities.JsonContext.Default.SetLevelRequestParams,
|
||||||
McpJsonUtilities.JsonContext.Default.EmptyResult);
|
McpJsonUtilities.JsonContext.Default.EmptyResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Maps a <see cref="LogLevel"/> to a <see cref="LoggingLevel"/>.</summary>
|
||||||
|
internal static LoggingLevel ToLoggingLevel(LogLevel level) =>
|
||||||
|
level switch
|
||||||
|
{
|
||||||
|
LogLevel.Trace => Protocol.Types.LoggingLevel.Debug,
|
||||||
|
LogLevel.Debug => Protocol.Types.LoggingLevel.Debug,
|
||||||
|
LogLevel.Information => Protocol.Types.LoggingLevel.Info,
|
||||||
|
LogLevel.Warning => Protocol.Types.LoggingLevel.Warning,
|
||||||
|
LogLevel.Error => Protocol.Types.LoggingLevel.Error,
|
||||||
|
LogLevel.Critical => Protocol.Types.LoggingLevel.Critical,
|
||||||
|
_ => Protocol.Types.LoggingLevel.Emergency,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using ModelContextProtocol.Protocol.Messages;
|
using ModelContextProtocol.Protocol.Messages;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
using ModelContextProtocol.Utils.Json;
|
using ModelContextProtocol.Utils.Json;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Server;
|
namespace ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
|
@ -153,6 +155,16 @@ public static class McpServerExtensions
|
||||||
return new SamplingChatClient(server);
|
return new SamplingChatClient(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Gets an <see cref="ILogger"/> on which logged messages will be sent as notifications to the client.</summary>
|
||||||
|
/// <param name="server">The server to wrap as an <see cref="ILogger"/>.</param>
|
||||||
|
/// <returns>An <see cref="ILogger"/> that can be used to log to the client..</returns>
|
||||||
|
public static ILoggerProvider AsClientLoggerProvider(this IMcpServer server)
|
||||||
|
{
|
||||||
|
Throw.IfNull(server);
|
||||||
|
|
||||||
|
return new ClientLoggerProvider(server);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Requests the client to list the roots it exposes.
|
/// Requests the client to list the roots it exposes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -210,4 +222,57 @@ public static class McpServerExtensions
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
void IDisposable.Dispose() { } // nop
|
void IDisposable.Dispose() { } // nop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides an <see cref="ILoggerProvider"/> implementation for creating loggers
|
||||||
|
/// that send logging message notifications to the client for logged messages.
|
||||||
|
/// </summary>
|
||||||
|
private sealed class ClientLoggerProvider(IMcpServer server) : ILoggerProvider
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public ILogger CreateLogger(string categoryName)
|
||||||
|
{
|
||||||
|
Throw.IfNull(categoryName);
|
||||||
|
|
||||||
|
return new ClientLogger(server, categoryName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
void IDisposable.Dispose() { }
|
||||||
|
|
||||||
|
private sealed class ClientLogger(IMcpServer server, string categoryName) : ILogger
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public IDisposable? BeginScope<TState>(TState state) where TState : notnull =>
|
||||||
|
null;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public bool IsEnabled(LogLevel logLevel) =>
|
||||||
|
server?.LoggingLevel is { } loggingLevel &&
|
||||||
|
McpServer.ToLoggingLevel(logLevel) >= loggingLevel;
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||||
|
{
|
||||||
|
if (!IsEnabled(logLevel))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Throw.IfNull(formatter);
|
||||||
|
|
||||||
|
Log(logLevel, formatter(state, exception));
|
||||||
|
|
||||||
|
void Log(LogLevel logLevel, string message)
|
||||||
|
{
|
||||||
|
_ = server.SendNotificationAsync(NotificationMethods.LoggingMessageNotification, new LoggingMessageNotificationParams()
|
||||||
|
{
|
||||||
|
Level = McpServer.ToLoggingLevel(logLevel),
|
||||||
|
Data = JsonSerializer.SerializeToElement(message, McpJsonUtilities.JsonContext.Default.String),
|
||||||
|
Logger = categoryName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Microsoft.Extensions.AI;
|
using Microsoft.Extensions.AI;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using ModelContextProtocol.Client;
|
using ModelContextProtocol.Client;
|
||||||
using ModelContextProtocol.Protocol.Messages;
|
using ModelContextProtocol.Protocol.Messages;
|
||||||
using ModelContextProtocol.Protocol.Transport;
|
using ModelContextProtocol.Protocol.Transport;
|
||||||
|
|
@ -10,6 +11,7 @@ using Moq;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization.Metadata;
|
using System.Text.Json.Serialization.Metadata;
|
||||||
|
using System.Threading.Channels;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Tests.Client;
|
namespace ModelContextProtocol.Tests.Client;
|
||||||
|
|
||||||
|
|
@ -19,6 +21,7 @@ public class McpClientExtensionsTests : LoggedTest
|
||||||
private readonly Pipe _serverToClientPipe = new();
|
private readonly Pipe _serverToClientPipe = new();
|
||||||
private readonly ServiceProvider _serviceProvider;
|
private readonly ServiceProvider _serviceProvider;
|
||||||
private readonly CancellationTokenSource _cts;
|
private readonly CancellationTokenSource _cts;
|
||||||
|
private readonly IMcpServer _server;
|
||||||
private readonly Task _serverTask;
|
private readonly Task _serverTask;
|
||||||
|
|
||||||
public McpClientExtensionsTests(ITestOutputHelper outputHelper)
|
public McpClientExtensionsTests(ITestOutputHelper outputHelper)
|
||||||
|
|
@ -36,9 +39,9 @@ public class McpClientExtensionsTests : LoggedTest
|
||||||
sc.AddSingleton(McpServerTool.Create([McpServerTool(Destructive = false, OpenWorld = true)](string i) => $"{i} Result", new() { Name = "ValuesSetViaOptions", Destructive = true, OpenWorld = false, ReadOnly = true }));
|
sc.AddSingleton(McpServerTool.Create([McpServerTool(Destructive = false, OpenWorld = true)](string i) => $"{i} Result", new() { Name = "ValuesSetViaOptions", Destructive = true, OpenWorld = false, ReadOnly = true }));
|
||||||
_serviceProvider = sc.BuildServiceProvider();
|
_serviceProvider = sc.BuildServiceProvider();
|
||||||
|
|
||||||
var server = _serviceProvider.GetRequiredService<IMcpServer>();
|
_server = _serviceProvider.GetRequiredService<IMcpServer>();
|
||||||
_cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken);
|
_cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken);
|
||||||
_serverTask = server.RunAsync(cancellationToken: _cts.Token);
|
_serverTask = _server.RunAsync(cancellationToken: _cts.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -374,4 +377,104 @@ public class McpClientExtensionsTests : LoggedTest
|
||||||
Assert.Equal("ToolWithNewDescription", redescribedTool.Description);
|
Assert.Equal("ToolWithNewDescription", redescribedTool.Description);
|
||||||
Assert.Equal(originalDescription, tool?.Description);
|
Assert.Equal(originalDescription, tool?.Description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task AsClientLoggerProvider_MessagesSentToClient()
|
||||||
|
{
|
||||||
|
IMcpClient client = await CreateMcpClientForServer();
|
||||||
|
|
||||||
|
ILoggerProvider loggerProvider = _server.AsClientLoggerProvider();
|
||||||
|
Assert.Throws<ArgumentNullException>("categoryName", () => loggerProvider.CreateLogger(null!));
|
||||||
|
|
||||||
|
ILogger logger = loggerProvider.CreateLogger("TestLogger");
|
||||||
|
Assert.NotNull(logger);
|
||||||
|
|
||||||
|
Assert.Null(logger.BeginScope(""));
|
||||||
|
|
||||||
|
Assert.Null(_server.LoggingLevel);
|
||||||
|
Assert.False(logger.IsEnabled(LogLevel.Trace));
|
||||||
|
Assert.False(logger.IsEnabled(LogLevel.Debug));
|
||||||
|
Assert.False(logger.IsEnabled(LogLevel.Information));
|
||||||
|
Assert.False(logger.IsEnabled(LogLevel.Warning));
|
||||||
|
Assert.False(logger.IsEnabled(LogLevel.Error));
|
||||||
|
Assert.False(logger.IsEnabled(LogLevel.Critical));
|
||||||
|
|
||||||
|
await client.SetLoggingLevel(LoggingLevel.Info, TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
|
DateTime start = DateTime.UtcNow;
|
||||||
|
while (_server.LoggingLevel is null)
|
||||||
|
{
|
||||||
|
await Task.Delay(1, TestContext.Current.CancellationToken);
|
||||||
|
Assert.True(DateTime.UtcNow - start < TimeSpan.FromSeconds(10), "Timed out waiting for logging level to be set");
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.Equal(LoggingLevel.Info, _server.LoggingLevel);
|
||||||
|
Assert.False(logger.IsEnabled(LogLevel.Trace));
|
||||||
|
Assert.False(logger.IsEnabled(LogLevel.Debug));
|
||||||
|
Assert.True(logger.IsEnabled(LogLevel.Information));
|
||||||
|
Assert.True(logger.IsEnabled(LogLevel.Warning));
|
||||||
|
Assert.True(logger.IsEnabled(LogLevel.Error));
|
||||||
|
Assert.True(logger.IsEnabled(LogLevel.Critical));
|
||||||
|
|
||||||
|
List<string> data = [];
|
||||||
|
var channel = Channel.CreateUnbounded<LoggingMessageNotificationParams?>();
|
||||||
|
|
||||||
|
await using (client.RegisterNotificationHandler(NotificationMethods.LoggingMessageNotification,
|
||||||
|
(notification, cancellationToken) =>
|
||||||
|
{
|
||||||
|
Assert.True(channel.Writer.TryWrite(JsonSerializer.Deserialize<LoggingMessageNotificationParams>(notification.Params)));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
logger.LogTrace("Trace {Message}", "message");
|
||||||
|
logger.LogDebug("Debug {Message}", "message");
|
||||||
|
logger.LogInformation("Information {Message}", "message");
|
||||||
|
logger.LogWarning("Warning {Message}", "message");
|
||||||
|
logger.LogError("Error {Message}", "message");
|
||||||
|
logger.LogCritical("Critical {Message}", "message");
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++)
|
||||||
|
{
|
||||||
|
var m = await channel.Reader.ReadAsync(TestContext.Current.CancellationToken);
|
||||||
|
Assert.NotNull(m);
|
||||||
|
Assert.NotNull(m.Data);
|
||||||
|
|
||||||
|
Assert.Equal("TestLogger", m.Logger);
|
||||||
|
|
||||||
|
string ? s = JsonSerializer.Deserialize<string>(m.Data.Value);
|
||||||
|
Assert.NotNull(s);
|
||||||
|
|
||||||
|
if (s.Contains("Information"))
|
||||||
|
{
|
||||||
|
Assert.Equal(LoggingLevel.Info, m.Level);
|
||||||
|
}
|
||||||
|
else if (s.Contains("Warning"))
|
||||||
|
{
|
||||||
|
Assert.Equal(LoggingLevel.Warning, m.Level);
|
||||||
|
}
|
||||||
|
else if (s.Contains("Error"))
|
||||||
|
{
|
||||||
|
Assert.Equal(LoggingLevel.Error, m.Level);
|
||||||
|
}
|
||||||
|
else if (s.Contains("Critical"))
|
||||||
|
{
|
||||||
|
Assert.Equal(LoggingLevel.Critical, m.Level);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Add(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.Writer.Complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert.False(await channel.Reader.WaitToReadAsync(TestContext.Current.CancellationToken));
|
||||||
|
Assert.Equal(
|
||||||
|
[
|
||||||
|
"Critical message",
|
||||||
|
"Error message",
|
||||||
|
"Information message",
|
||||||
|
"Warning message",
|
||||||
|
],
|
||||||
|
data.OrderBy(s => s));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -618,6 +618,7 @@ public class McpServerTests : LoggedTest
|
||||||
public Implementation? ClientInfo => throw new NotImplementedException();
|
public Implementation? ClientInfo => throw new NotImplementedException();
|
||||||
public McpServerOptions ServerOptions => throw new NotImplementedException();
|
public McpServerOptions ServerOptions => throw new NotImplementedException();
|
||||||
public IServiceProvider? Services => throw new NotImplementedException();
|
public IServiceProvider? Services => throw new NotImplementedException();
|
||||||
|
public LoggingLevel? LoggingLevel => throw new NotImplementedException();
|
||||||
public Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default) =>
|
public Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default) =>
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
public Task RunAsync(CancellationToken cancellationToken = default) =>
|
public Task RunAsync(CancellationToken cancellationToken = default) =>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue