Move notification handler registrations to capabilities (#207)
* Move notification handler registrations to capabilities Currently request handlers are set on the capability objects, but notification handlers are set after construction via an AddNotificationHandler method on the IMcpEndpoint interface. This moves handler specification to be at construction as well. This makes it more consistent with request handlers, simplifies the IMcpEndpoint interface to just be about message sending, and avoids a concurrency bug that could occur if someone tried to add a handler while the endpoint was processing notifications. * Address more feedback and further cleanup
This commit is contained in:
parent
8fcdf95785
commit
d21d933876
15 changed files with 292 additions and 288 deletions
|
|
@ -38,7 +38,6 @@ internal static class Program
|
|||
|
||||
McpServerOptions options = new()
|
||||
{
|
||||
ServerInfo = new Implementation() { Name = "TestServer", Version = "1.0.0" },
|
||||
Capabilities = new ServerCapabilities()
|
||||
{
|
||||
Tools = ConfigureTools(),
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ public class Program
|
|||
|
||||
private static void ConfigureOptions(McpServerOptions options)
|
||||
{
|
||||
options.ServerInfo = new Implementation() { Name = "TestServer", Version = "1.0.0" };
|
||||
options.Capabilities = new ServerCapabilities()
|
||||
{
|
||||
Tools = new(),
|
||||
|
|
|
|||
|
|
@ -11,29 +11,24 @@ namespace ModelContextProtocol.Tests.Client;
|
|||
|
||||
public class McpClientFactoryTests
|
||||
{
|
||||
private readonly McpClientOptions _defaultOptions = new()
|
||||
{
|
||||
ClientInfo = new() { Name = "TestClient", Version = "1.0.0" }
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public async Task CreateAsync_WithInvalidArgs_Throws()
|
||||
{
|
||||
await Assert.ThrowsAsync<ArgumentNullException>("serverConfig", () => McpClientFactory.CreateAsync((McpServerConfig)null!, _defaultOptions, cancellationToken: TestContext.Current.CancellationToken));
|
||||
await Assert.ThrowsAsync<ArgumentNullException>("serverConfig", () => McpClientFactory.CreateAsync((McpServerConfig)null!, cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
await Assert.ThrowsAsync<ArgumentException>("serverConfig", () => McpClientFactory.CreateAsync(new McpServerConfig()
|
||||
{
|
||||
Name = "name",
|
||||
Id = "id",
|
||||
TransportType = "somethingunsupported",
|
||||
}, _defaultOptions, cancellationToken: TestContext.Current.CancellationToken));
|
||||
}, cancellationToken: TestContext.Current.CancellationToken));
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => McpClientFactory.CreateAsync(new McpServerConfig()
|
||||
{
|
||||
Name = "name",
|
||||
Id = "id",
|
||||
TransportType = TransportTypes.StdIo,
|
||||
}, _defaultOptions, (_, __) => null!, cancellationToken: TestContext.Current.CancellationToken));
|
||||
}, createTransportFunc: (_, __) => null!, cancellationToken: TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -78,8 +73,7 @@ public class McpClientFactoryTests
|
|||
// Act
|
||||
await using var client = await McpClientFactory.CreateAsync(
|
||||
serverConfig,
|
||||
_defaultOptions,
|
||||
(_, __) => new NopTransport(),
|
||||
createTransportFunc: (_, __) => new NopTransport(),
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Assert
|
||||
|
|
@ -102,8 +96,7 @@ public class McpClientFactoryTests
|
|||
// Act
|
||||
await using var client = await McpClientFactory.CreateAsync(
|
||||
serverConfig,
|
||||
_defaultOptions,
|
||||
(_, __) => new NopTransport(),
|
||||
createTransportFunc: (_, __) => new NopTransport(),
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Assert
|
||||
|
|
@ -126,8 +119,7 @@ public class McpClientFactoryTests
|
|||
// Act
|
||||
await using var client = await McpClientFactory.CreateAsync(
|
||||
serverConfig,
|
||||
_defaultOptions,
|
||||
(_, __) => new NopTransport(),
|
||||
createTransportFunc: (_, __) => new NopTransport(),
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Assert
|
||||
|
|
@ -157,8 +149,7 @@ public class McpClientFactoryTests
|
|||
// Act
|
||||
await using var client = await McpClientFactory.CreateAsync(
|
||||
serverConfig,
|
||||
_defaultOptions,
|
||||
(_, __) => new NopTransport(),
|
||||
createTransportFunc: (_, __) => new NopTransport(),
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Assert
|
||||
|
|
@ -186,7 +177,7 @@ public class McpClientFactoryTests
|
|||
};
|
||||
|
||||
// act & assert
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => McpClientFactory.CreateAsync(config, _defaultOptions, cancellationToken: TestContext.Current.CancellationToken));
|
||||
await Assert.ThrowsAsync<ArgumentException>(() => McpClientFactory.CreateAsync(config, cancellationToken: TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -205,11 +196,6 @@ public class McpClientFactoryTests
|
|||
|
||||
var clientOptions = new McpClientOptions
|
||||
{
|
||||
ClientInfo = new Implementation
|
||||
{
|
||||
Name = "TestClient",
|
||||
Version = "1.0.0.0"
|
||||
},
|
||||
Capabilities = new ClientCapabilities
|
||||
{
|
||||
Sampling = new SamplingCapability
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ public class ClientIntegrationTestFixture
|
|||
{
|
||||
private ILoggerFactory? _loggerFactory;
|
||||
|
||||
public McpClientOptions DefaultOptions { get; }
|
||||
public McpServerConfig EverythingServerConfig { get; }
|
||||
public McpServerConfig TestServerConfig { get; }
|
||||
|
||||
|
|
@ -16,11 +15,6 @@ public class ClientIntegrationTestFixture
|
|||
|
||||
public ClientIntegrationTestFixture()
|
||||
{
|
||||
DefaultOptions = new()
|
||||
{
|
||||
ClientInfo = new() { Name = "IntegrationTestClient", Version = "1.0.0" },
|
||||
};
|
||||
|
||||
EverythingServerConfig = new()
|
||||
{
|
||||
Id = "everything",
|
||||
|
|
@ -63,5 +57,5 @@ public class ClientIntegrationTestFixture
|
|||
"everything" => EverythingServerConfig,
|
||||
"test_server" => TestServerConfig,
|
||||
_ => throw new ArgumentException($"Unknown client ID: {clientId}")
|
||||
}, clientOptions ?? DefaultOptions, loggerFactory: _loggerFactory);
|
||||
}, clientOptions, loggerFactory: _loggerFactory);
|
||||
}
|
||||
|
|
@ -5,10 +5,7 @@ using ModelContextProtocol.Protocol.Transport;
|
|||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Tests.Utils;
|
||||
using OpenAI;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace ModelContextProtocol.Tests;
|
||||
|
||||
|
|
@ -256,13 +253,22 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
|
||||
// act
|
||||
TaskCompletionSource<bool> tcs = new();
|
||||
await using var client = await _fixture.CreateClientAsync(clientId);
|
||||
client.AddNotificationHandler(NotificationMethods.ResourceUpdatedNotification, (notification) =>
|
||||
await using var client = await _fixture.CreateClientAsync(clientId, new()
|
||||
{
|
||||
var notificationParams = JsonSerializer.Deserialize<ResourceUpdatedNotificationParams>(notification.Params);
|
||||
tcs.TrySetResult(true);
|
||||
return Task.CompletedTask;
|
||||
Capabilities = new()
|
||||
{
|
||||
NotificationHandlers =
|
||||
[
|
||||
new(NotificationMethods.ResourceUpdatedNotification, notification =>
|
||||
{
|
||||
var notificationParams = JsonSerializer.Deserialize<ResourceUpdatedNotificationParams>(notification.Params);
|
||||
tcs.TrySetResult(true);
|
||||
return Task.CompletedTask;
|
||||
})
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
await client.SubscribeToResourceAsync("test://static/resource/1", TestContext.Current.CancellationToken);
|
||||
|
||||
await tcs.Task;
|
||||
|
|
@ -277,12 +283,20 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
|
||||
// act
|
||||
TaskCompletionSource<bool> receivedNotification = new();
|
||||
await using var client = await _fixture.CreateClientAsync(clientId);
|
||||
client.AddNotificationHandler(NotificationMethods.ResourceUpdatedNotification, (notification) =>
|
||||
await using var client = await _fixture.CreateClientAsync(clientId, new()
|
||||
{
|
||||
var notificationParams = JsonSerializer.Deserialize<ResourceUpdatedNotificationParams>(notification.Params);
|
||||
receivedNotification.TrySetResult(true);
|
||||
return Task.CompletedTask;
|
||||
Capabilities = new()
|
||||
{
|
||||
NotificationHandlers =
|
||||
[
|
||||
new(NotificationMethods.ResourceUpdatedNotification, (notification) =>
|
||||
{
|
||||
var notificationParams = JsonSerializer.Deserialize<ResourceUpdatedNotificationParams>(notification.Params);
|
||||
receivedNotification.TrySetResult(true);
|
||||
return Task.CompletedTask;
|
||||
})
|
||||
]
|
||||
}
|
||||
});
|
||||
await client.SubscribeToResourceAsync("test://static/resource/1", TestContext.Current.CancellationToken);
|
||||
|
||||
|
|
@ -350,7 +364,6 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
int samplingHandlerCalls = 0;
|
||||
await using var client = await _fixture.CreateClientAsync(clientId, new()
|
||||
{
|
||||
ClientInfo = new() { Name = "Sampling_Stdio", Version = "1.0.0" },
|
||||
Capabilities = new()
|
||||
{
|
||||
Sampling = new()
|
||||
|
|
@ -483,7 +496,6 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
// Get the MCP client and tools from it.
|
||||
await using var client = await McpClientFactory.CreateAsync(
|
||||
_fixture.EverythingServerConfig,
|
||||
_fixture.DefaultOptions,
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
var mappedTools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
|
|
@ -516,7 +528,6 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
.CreateSamplingHandler();
|
||||
await using var client = await McpClientFactory.CreateAsync(_fixture.EverythingServerConfig, new()
|
||||
{
|
||||
ClientInfo = new() { Name = nameof(SamplingViaChatClient_RequestResponseProperlyPropagated), Version = "1.0.0" },
|
||||
Capabilities = new()
|
||||
{
|
||||
Sampling = new()
|
||||
|
|
@ -543,15 +554,23 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
public async Task SetLoggingLevel_ReceivesLoggingMessages(string clientId)
|
||||
{
|
||||
TaskCompletionSource<bool> receivedNotification = new();
|
||||
await using var client = await _fixture.CreateClientAsync(clientId);
|
||||
client.AddNotificationHandler(NotificationMethods.LoggingMessageNotification, (notification) =>
|
||||
await using var client = await _fixture.CreateClientAsync(clientId, new()
|
||||
{
|
||||
var loggingMessageNotificationParameters = JsonSerializer.Deserialize<LoggingMessageNotificationParams>(notification.Params);
|
||||
if (loggingMessageNotificationParameters is not null)
|
||||
Capabilities = new()
|
||||
{
|
||||
receivedNotification.TrySetResult(true);
|
||||
NotificationHandlers =
|
||||
[
|
||||
new(NotificationMethods.LoggingMessageNotification, (notification) =>
|
||||
{
|
||||
var loggingMessageNotificationParameters = JsonSerializer.Deserialize<LoggingMessageNotificationParams>(notification.Params);
|
||||
if (loggingMessageNotificationParameters is not null)
|
||||
{
|
||||
receivedNotification.TrySetResult(true);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
})
|
||||
]
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
// act
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ public class McpServerBuilderExtensionsPromptsTests : LoggedTest, IAsyncDisposab
|
|||
Dispose();
|
||||
}
|
||||
|
||||
private async Task<IMcpClient> CreateMcpClientForServer()
|
||||
private async Task<IMcpClient> CreateMcpClientForServer(McpClientOptions? options = null)
|
||||
{
|
||||
return await McpClientFactory.CreateAsync(
|
||||
new McpServerConfig()
|
||||
|
|
@ -126,6 +126,7 @@ public class McpServerBuilderExtensionsPromptsTests : LoggedTest, IAsyncDisposab
|
|||
Name = "TestServer",
|
||||
TransportType = "ignored",
|
||||
},
|
||||
options,
|
||||
createTransportFunc: (_, _) => new StreamClientTransport(
|
||||
serverInput: _clientToServerPipe.Writer.AsStream(),
|
||||
serverOutput: _serverToClientPipe.Reader.AsStream(),
|
||||
|
|
@ -175,18 +176,23 @@ public class McpServerBuilderExtensionsPromptsTests : LoggedTest, IAsyncDisposab
|
|||
[Fact]
|
||||
public async Task Can_Be_Notified_Of_Prompt_Changes()
|
||||
{
|
||||
IMcpClient client = await CreateMcpClientForServer();
|
||||
Channel<JsonRpcNotification> listChanged = Channel.CreateUnbounded<JsonRpcNotification>();
|
||||
|
||||
IMcpClient client = await CreateMcpClientForServer(new()
|
||||
{
|
||||
Capabilities = new()
|
||||
{
|
||||
NotificationHandlers = [new("notifications/prompts/list_changed", notification =>
|
||||
{
|
||||
listChanged.Writer.TryWrite(notification);
|
||||
return Task.CompletedTask;
|
||||
})],
|
||||
},
|
||||
});
|
||||
|
||||
var prompts = await client.ListPromptsAsync(TestContext.Current.CancellationToken);
|
||||
Assert.Equal(6, prompts.Count);
|
||||
|
||||
Channel<JsonRpcNotification> listChanged = Channel.CreateUnbounded<JsonRpcNotification>();
|
||||
client.AddNotificationHandler("notifications/prompts/list_changed", notification =>
|
||||
{
|
||||
listChanged.Writer.TryWrite(notification);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
var notificationRead = listChanged.Reader.ReadAsync(TestContext.Current.CancellationToken);
|
||||
Assert.False(notificationRead.IsCompleted);
|
||||
|
||||
|
|
|
|||
|
|
@ -141,15 +141,16 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
Dispose();
|
||||
}
|
||||
|
||||
private async Task<IMcpClient> CreateMcpClientForServer()
|
||||
private async Task<IMcpClient> CreateMcpClientForServer(McpClientOptions? options = null)
|
||||
{
|
||||
return await McpClientFactory.CreateAsync(
|
||||
new McpServerConfig()
|
||||
{
|
||||
Id = "TestServer",
|
||||
Name = "TestServer",
|
||||
TransportType = "ignored",
|
||||
},
|
||||
new McpServerConfig()
|
||||
{
|
||||
Id = "TestServer",
|
||||
Name = "TestServer",
|
||||
TransportType = "ignored",
|
||||
},
|
||||
options,
|
||||
createTransportFunc: (_, _) => new StreamClientTransport(
|
||||
serverInput: _clientToServerPipe.Writer.AsStream(),
|
||||
_serverToClientPipe.Reader.AsStream(),
|
||||
|
|
@ -242,18 +243,23 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
[Fact]
|
||||
public async Task Can_Be_Notified_Of_Tool_Changes()
|
||||
{
|
||||
IMcpClient client = await CreateMcpClientForServer();
|
||||
Channel<JsonRpcNotification> listChanged = Channel.CreateUnbounded<JsonRpcNotification>();
|
||||
|
||||
IMcpClient client = await CreateMcpClientForServer(new()
|
||||
{
|
||||
Capabilities = new()
|
||||
{
|
||||
NotificationHandlers = [new(NotificationMethods.ToolListChangedNotification, notification =>
|
||||
{
|
||||
listChanged.Writer.TryWrite(notification);
|
||||
return Task.CompletedTask;
|
||||
})],
|
||||
},
|
||||
});
|
||||
|
||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.Equal(16, tools.Count);
|
||||
|
||||
Channel<JsonRpcNotification> listChanged = Channel.CreateUnbounded<JsonRpcNotification>();
|
||||
client.AddNotificationHandler(NotificationMethods.ToolListChangedNotification, notification =>
|
||||
{
|
||||
listChanged.Writer.TryWrite(notification);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
var notificationRead = listChanged.Reader.ReadAsync(TestContext.Current.CancellationToken);
|
||||
Assert.False(notificationRead.IsCompleted);
|
||||
|
||||
|
|
@ -622,12 +628,17 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
{
|
||||
ConcurrentQueue<ProgressNotification> notifications = new();
|
||||
|
||||
IMcpClient client = await CreateMcpClientForServer();
|
||||
client.AddNotificationHandler(NotificationMethods.ProgressNotification, notification =>
|
||||
IMcpClient client = await CreateMcpClientForServer(new()
|
||||
{
|
||||
ProgressNotification pn = JsonSerializer.Deserialize<ProgressNotification>(notification.Params)!;
|
||||
notifications.Enqueue(pn);
|
||||
return Task.CompletedTask;
|
||||
Capabilities = new()
|
||||
{
|
||||
NotificationHandlers = [new(NotificationMethods.ProgressNotification, notification =>
|
||||
{
|
||||
ProgressNotification pn = JsonSerializer.Deserialize<ProgressNotification>(notification.Params)!;
|
||||
notifications.Enqueue(pn);
|
||||
return Task.CompletedTask;
|
||||
})],
|
||||
},
|
||||
});
|
||||
|
||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Protocol.Transport;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Server;
|
||||
using OpenTelemetry.Trace;
|
||||
using System.Diagnostics;
|
||||
|
|
@ -49,7 +48,6 @@ public class DiagnosticTests
|
|||
|
||||
await using (IMcpServer server = McpServerFactory.Create(serverTransport, new()
|
||||
{
|
||||
ServerInfo = new Implementation { Name = "TestServer", Version = "1.0.0" },
|
||||
Capabilities = new()
|
||||
{
|
||||
Tools = new()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,13 @@
|
|||
<RootNamespace>ModelContextProtocol.Tests</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- Without this, tests are currently not showing results until all tests complete
|
||||
https://xunit.net/docs/getting-started/v3/microsoft-testing-platform
|
||||
-->
|
||||
<DisableTestingPlatformServerCapability>true</DisableTestingPlatformServerCapability>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="coverlet.collector">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Server;
|
||||
using ModelContextProtocol.Server;
|
||||
using ModelContextProtocol.Tests.Utils;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Server;
|
||||
|
|
@ -13,7 +12,6 @@ public class McpServerFactoryTests : LoggedTest
|
|||
{
|
||||
_options = new McpServerOptions
|
||||
{
|
||||
ServerInfo = new Implementation { Name = "TestServer", Version = "1.0" },
|
||||
ProtocolVersion = "1.0",
|
||||
InitializationTimeout = TimeSpan.FromSeconds(30)
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ public class McpServerTests : LoggedTest
|
|||
{
|
||||
return new McpServerOptions
|
||||
{
|
||||
ServerInfo = new Implementation { Name = "TestServer", Version = "1.0" },
|
||||
ProtocolVersion = "2024",
|
||||
InitializationTimeout = TimeSpan.FromSeconds(30),
|
||||
Capabilities = capabilities,
|
||||
|
|
@ -189,8 +188,8 @@ public class McpServerTests : LoggedTest
|
|||
{
|
||||
var result = JsonSerializer.Deserialize<InitializeResult>(response);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("TestServer", result.ServerInfo.Name);
|
||||
Assert.Equal("1.0", result.ServerInfo.Version);
|
||||
Assert.Equal("ModelContextProtocol.Tests", result.ServerInfo.Name);
|
||||
Assert.Equal("1.0.0.0", result.ServerInfo.Version);
|
||||
Assert.Equal("2024", result.ProtocolVersion);
|
||||
});
|
||||
}
|
||||
|
|
@ -634,8 +633,6 @@ public class McpServerTests : LoggedTest
|
|||
public Implementation? ClientInfo => throw new NotImplementedException();
|
||||
public McpServerOptions ServerOptions => throw new NotImplementedException();
|
||||
public IServiceProvider? Services => throw new NotImplementedException();
|
||||
public void AddNotificationHandler(string method, Func<JsonRpcNotification, Task> handler) =>
|
||||
throw new NotImplementedException();
|
||||
public Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
public Task RunAsync(CancellationToken cancellationToken = default) =>
|
||||
|
|
@ -649,13 +646,16 @@ public class McpServerTests : LoggedTest
|
|||
var options = CreateOptions();
|
||||
|
||||
var notificationReceived = new TaskCompletionSource<JsonRpcNotification>();
|
||||
options.Capabilities = new()
|
||||
{
|
||||
NotificationHandlers = [new(NotificationMethods.ProgressNotification, notification =>
|
||||
{
|
||||
notificationReceived.TrySetResult(notification);
|
||||
return Task.CompletedTask;
|
||||
})],
|
||||
};
|
||||
|
||||
var server = McpServerFactory.Create(transport, options, LoggerFactory);
|
||||
server.AddNotificationHandler(NotificationMethods.ProgressNotification, notification =>
|
||||
{
|
||||
notificationReceived.SetResult(notification);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
Task serverTask = server.RunAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
|
|
|
|||
|
|
@ -29,11 +29,6 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : LoggedTest(ou
|
|||
await using InMemoryTestSseServer server = new(CreatePortNumber(), LoggerFactory.CreateLogger<InMemoryTestSseServer>());
|
||||
await server.StartAsync();
|
||||
|
||||
var defaultOptions = new McpClientOptions
|
||||
{
|
||||
ClientInfo = new() { Name = "IntegrationTestClient", Version = "1.0.0" }
|
||||
};
|
||||
|
||||
var defaultConfig = new McpServerConfig
|
||||
{
|
||||
Id = "test_server",
|
||||
|
|
@ -46,7 +41,6 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : LoggedTest(ou
|
|||
// Act
|
||||
await using var client = await McpClientFactory.CreateAsync(
|
||||
defaultConfig,
|
||||
defaultOptions,
|
||||
loggerFactory: LoggerFactory,
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
|
|
@ -120,11 +114,6 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : LoggedTest(ou
|
|||
int samplingHandlerCalls = 0;
|
||||
var defaultOptions = new McpClientOptions
|
||||
{
|
||||
ClientInfo = new()
|
||||
{
|
||||
Name = "IntegrationTestClient",
|
||||
Version = "1.0.0"
|
||||
},
|
||||
Capabilities = new()
|
||||
{
|
||||
Sampling = new()
|
||||
|
|
@ -175,11 +164,6 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : LoggedTest(ou
|
|||
server.UseFullUrlForEndpointEvent = true;
|
||||
await server.StartAsync();
|
||||
|
||||
var defaultOptions = new McpClientOptions
|
||||
{
|
||||
ClientInfo = new() { Name = "IntegrationTestClient", Version = "1.0.0" }
|
||||
};
|
||||
|
||||
var defaultConfig = new McpServerConfig
|
||||
{
|
||||
Id = "test_server",
|
||||
|
|
@ -192,7 +176,6 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : LoggedTest(ou
|
|||
// Act
|
||||
await using var client = await McpClientFactory.CreateAsync(
|
||||
defaultConfig,
|
||||
defaultOptions,
|
||||
loggerFactory: LoggerFactory,
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
|
|
@ -213,12 +196,6 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : LoggedTest(ou
|
|||
await using InMemoryTestSseServer server = new(CreatePortNumber(), LoggerFactory.CreateLogger<InMemoryTestSseServer>());
|
||||
await server.StartAsync();
|
||||
|
||||
|
||||
var defaultOptions = new McpClientOptions
|
||||
{
|
||||
ClientInfo = new() { Name = "IntegrationTestClient", Version = "1.0.0" }
|
||||
};
|
||||
|
||||
var defaultConfig = new McpServerConfig
|
||||
{
|
||||
Id = "test_server",
|
||||
|
|
@ -229,24 +206,28 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : LoggedTest(ou
|
|||
};
|
||||
|
||||
// Act
|
||||
var receivedNotification = new TaskCompletionSource<string?>();
|
||||
await using var client = await McpClientFactory.CreateAsync(
|
||||
defaultConfig,
|
||||
defaultOptions,
|
||||
new()
|
||||
{
|
||||
Capabilities = new()
|
||||
{
|
||||
NotificationHandlers = [new("test/notification", args =>
|
||||
{
|
||||
var msg = args.Params?["message"]?.GetValue<string>();
|
||||
receivedNotification.SetResult(msg);
|
||||
|
||||
return Task.CompletedTask;
|
||||
})],
|
||||
},
|
||||
},
|
||||
loggerFactory: LoggerFactory,
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Wait for SSE connection to be established
|
||||
await server.WaitForConnectionAsync(TimeSpan.FromSeconds(10));
|
||||
|
||||
var receivedNotification = new TaskCompletionSource<string?>();
|
||||
client.AddNotificationHandler("test/notification", (args) =>
|
||||
{
|
||||
var msg = args.Params?["message"]?.GetValue<string>();
|
||||
receivedNotification.SetResult(msg);
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
// Act
|
||||
await server.SendTestNotificationAsync("Hello from server!");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Protocol.Transport;
|
||||
using ModelContextProtocol.Protocol.Transport;
|
||||
using ModelContextProtocol.Test.Utils;
|
||||
using ModelContextProtocol.Tests.Utils;
|
||||
using ModelContextProtocol.TestSseServer;
|
||||
|
|
@ -32,11 +31,6 @@ public class SseServerIntegrationTestFixture : IAsyncDisposable
|
|||
_serverTask = Program.MainAsync([port.ToString()], new XunitLoggerProvider(_delegatingTestOutputHelper), _stopCts.Token);
|
||||
}
|
||||
|
||||
public static McpClientOptions CreateDefaultClientOptions() => new()
|
||||
{
|
||||
ClientInfo = new() { Name = "IntegrationTestClient", Version = "1.0.0" },
|
||||
};
|
||||
|
||||
public void Initialize(ITestOutputHelper output)
|
||||
{
|
||||
_delegatingTestOutputHelper.CurrentTestOutputHelper = output;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ public class SseServerIntegrationTests : LoggedTest, IClassFixture<SseServerInte
|
|||
{
|
||||
return McpClientFactory.CreateAsync(
|
||||
_fixture.DefaultConfig,
|
||||
options ?? SseServerIntegrationTestFixture.CreateDefaultClientOptions(),
|
||||
options,
|
||||
loggerFactory: LoggerFactory,
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
}
|
||||
|
|
@ -220,8 +220,8 @@ public class SseServerIntegrationTests : LoggedTest, IClassFixture<SseServerInte
|
|||
// Set up the sampling handler
|
||||
int samplingHandlerCalls = 0;
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
var options = SseServerIntegrationTestFixture.CreateDefaultClientOptions();
|
||||
options.Capabilities ??= new();
|
||||
McpClientOptions options = new();
|
||||
options.Capabilities = new();
|
||||
options.Capabilities.Sampling ??= new();
|
||||
options.Capabilities.Sampling.SamplingHandler = async (_, _, _) =>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using Microsoft.Extensions.Options;
|
||||
using ModelContextProtocol.Protocol.Messages;
|
||||
using ModelContextProtocol.Protocol.Transport;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Server;
|
||||
using ModelContextProtocol.Tests.Utils;
|
||||
using ModelContextProtocol.Utils.Json;
|
||||
|
|
@ -21,11 +20,6 @@ public class StdioServerTransportTests : LoggedTest
|
|||
{
|
||||
_serverOptions = new McpServerOptions
|
||||
{
|
||||
ServerInfo = new Implementation
|
||||
{
|
||||
Name = "Test Server",
|
||||
Version = "1.0"
|
||||
},
|
||||
ProtocolVersion = "2.0",
|
||||
InitializationTimeout = TimeSpan.FromSeconds(10),
|
||||
ServerInstructions = "Test Instructions"
|
||||
|
|
@ -49,8 +43,6 @@ public class StdioServerTransportTests : LoggedTest
|
|||
|
||||
Assert.Throws<ArgumentNullException>("serverOptions", () => new StdioServerTransport((IOptions<McpServerOptions>)null!));
|
||||
Assert.Throws<ArgumentNullException>("serverOptions", () => new StdioServerTransport((McpServerOptions)null!));
|
||||
Assert.Throws<ArgumentNullException>("serverOptions.ServerInfo", () => new StdioServerTransport(new McpServerOptions() { ServerInfo = null! }));
|
||||
Assert.Throws<ArgumentNullException>("serverOptions.ServerInfo.Name", () => new StdioServerTransport(new McpServerOptions() { ServerInfo = new() { Name = null!, Version = "" } }));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Reference in a new issue