|
11 | 11 | using Microsoft.Extensions.AI; |
12 | 12 | using System.Threading.Channels; |
13 | 13 | using ModelContextProtocol.Protocol.Messages; |
| 14 | +using Microsoft.Extensions.Options; |
| 15 | +using ModelContextProtocol.Tests.Utils; |
| 16 | +using Microsoft.Extensions.Logging; |
14 | 17 |
|
15 | 18 | namespace ModelContextProtocol.Tests.Configuration; |
16 | 19 |
|
17 | | -public class McpServerBuilderExtensionsToolsTests : IAsyncDisposable |
| 20 | +public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable |
18 | 21 | { |
19 | | - private Pipe _clientToServerPipe = new(); |
20 | | - private Pipe _serverToClientPipe = new(); |
| 22 | + private readonly Pipe _clientToServerPipe = new(); |
| 23 | + private readonly Pipe _serverToClientPipe = new(); |
| 24 | + private readonly ServiceProvider _serviceProvider; |
21 | 25 | private readonly IMcpServerBuilder _builder; |
22 | 26 | private readonly IMcpServer _server; |
23 | 27 |
|
24 | | - public McpServerBuilderExtensionsToolsTests() |
| 28 | + public McpServerBuilderExtensionsToolsTests(ITestOutputHelper testOutputHelper) |
| 29 | + : base(testOutputHelper) |
25 | 30 | { |
26 | 31 | ServiceCollection sc = new(); |
| 32 | + sc.AddSingleton(LoggerFactory); |
27 | 33 | sc.AddSingleton<IServerTransport>(new StdioServerTransport("TestServer", _clientToServerPipe.Reader.AsStream(), _serverToClientPipe.Writer.AsStream())); |
28 | 34 | sc.AddSingleton(new ObjectWithId()); |
29 | 35 | _builder = sc.AddMcpServer().WithTools<EchoTool>(); |
30 | | - _server = sc.BuildServiceProvider().GetRequiredService<IMcpServer>(); |
| 36 | + _serviceProvider = sc.BuildServiceProvider(); |
| 37 | + _server = _serviceProvider.GetRequiredService<IMcpServer>(); |
31 | 38 | } |
32 | 39 |
|
33 | 40 | public ValueTask DisposeAsync() |
34 | 41 | { |
35 | 42 | _clientToServerPipe.Writer.Complete(); |
36 | 43 | _serverToClientPipe.Writer.Complete(); |
37 | | - return _server.DisposeAsync(); |
| 44 | + return _serviceProvider.DisposeAsync(); |
38 | 45 | } |
39 | 46 |
|
40 | 47 | private async Task<IMcpClient> CreateMcpClientForServer() |
@@ -86,6 +93,63 @@ public async Task Can_List_Registered_Tools() |
86 | 93 | Assert.Equal("Echoes the input back to the client.", doubleEchoTool.Description); |
87 | 94 | } |
88 | 95 |
|
| 96 | + |
| 97 | + [Fact] |
| 98 | + public async Task Can_Create_Multiple_Servers_From_Options_And_List_Registered_Tools() |
| 99 | + { |
| 100 | + var options = _serviceProvider.GetRequiredService<IOptions<McpServerOptions>>().Value; |
| 101 | + var loggerFactory = _serviceProvider.GetRequiredService<ILoggerFactory>(); |
| 102 | + |
| 103 | + for (int i = 0; i < 2; i++) |
| 104 | + { |
| 105 | + var stdinPipe = new Pipe(); |
| 106 | + var stdoutPipe = new Pipe(); |
| 107 | + |
| 108 | + try |
| 109 | + { |
| 110 | + var transport = new StdioServerTransport($"TestServer_{i}", stdinPipe.Reader.AsStream(), stdoutPipe.Writer.AsStream()); |
| 111 | + var server = McpServerFactory.Create(transport, options, loggerFactory, _serviceProvider); |
| 112 | + |
| 113 | + await server.StartAsync(TestContext.Current.CancellationToken); |
| 114 | + |
| 115 | + var stdin = new StreamWriter(stdinPipe.Writer.AsStream()); |
| 116 | + var stdout = new StreamReader(stdoutPipe.Reader.AsStream()); |
| 117 | + |
| 118 | + var serverConfig = new McpServerConfig() |
| 119 | + { |
| 120 | + Id = $"TestServer_{i}", |
| 121 | + Name = $"TestServer_{i}", |
| 122 | + TransportType = "ignored", |
| 123 | + }; |
| 124 | + |
| 125 | + var client = await McpClientFactory.CreateAsync( |
| 126 | + serverConfig, |
| 127 | + createTransportFunc: (_, _) => new StreamClientTransport(stdout, stdin), |
| 128 | + cancellationToken: TestContext.Current.CancellationToken); |
| 129 | + |
| 130 | + var tools = await client.ListToolsAsync(TestContext.Current.CancellationToken); |
| 131 | + Assert.Equal(11, tools.Count); |
| 132 | + |
| 133 | + McpClientTool echoTool = tools.First(t => t.Name == "Echo"); |
| 134 | + Assert.Equal("Echo", echoTool.Name); |
| 135 | + Assert.Equal("Echoes the input back to the client.", echoTool.Description); |
| 136 | + Assert.Equal("object", echoTool.JsonSchema.GetProperty("type").GetString()); |
| 137 | + Assert.Equal(JsonValueKind.Object, echoTool.JsonSchema.GetProperty("properties").GetProperty("message").ValueKind); |
| 138 | + Assert.Equal("the echoes message", echoTool.JsonSchema.GetProperty("properties").GetProperty("message").GetProperty("description").GetString()); |
| 139 | + Assert.Equal(1, echoTool.JsonSchema.GetProperty("required").GetArrayLength()); |
| 140 | + |
| 141 | + McpClientTool doubleEchoTool = tools.First(t => t.Name == "double_echo"); |
| 142 | + Assert.Equal("double_echo", doubleEchoTool.Name); |
| 143 | + Assert.Equal("Echoes the input back to the client.", doubleEchoTool.Description); |
| 144 | + } |
| 145 | + finally |
| 146 | + { |
| 147 | + stdinPipe.Writer.Complete(); |
| 148 | + stdoutPipe.Writer.Complete(); |
| 149 | + } |
| 150 | + } |
| 151 | + } |
| 152 | + |
89 | 153 | [Fact] |
90 | 154 | public async Task Can_Be_Notified_Of_Tool_Changes() |
91 | 155 | { |
|
0 commit comments