Skip to content

Commit 1e44b84

Browse files
Merge branch 'main' into ddelimarsky/docs
2 parents b2ac242 + ed079b9 commit 1e44b84

File tree

13 files changed

+191
-92
lines changed

13 files changed

+191
-92
lines changed

README.MD

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
The official C# SDK for the [Model Context Protocol](https://modelcontextprotocol.io/), enabling .NET applications, services, and libraries to implement and interact with MCP clients and servers.
66

7-
> [!NOTE]
7+
> [!NOTE]
88
> This repo is still in preview, breaking changes can be introduced without prior notice.
99
1010
## About MCP
@@ -65,7 +65,7 @@ IList<AIFunction> tools = await client.GetAIFunctionsAsync();
6565
IChatClient chatClient = ...;
6666
var response = await chatClient.GetResponseAsync(
6767
"your prompt here",
68-
new()
68+
new()
6969
{
7070
Tools = [.. tools],
7171
});
@@ -110,7 +110,7 @@ using Microsoft.Extensions.Logging.Abstractions;
110110
McpServerOptions options = new()
111111
{
112112
ServerInfo = new() { Name = "MyServer", Version = "1.0.0" },
113-
Capabilities = new()
113+
Capabilities = new()
114114
{
115115
Tools = new()
116116
{
@@ -166,6 +166,10 @@ await server.StartAsync();
166166
await Task.Delay(Timeout.Infinite);
167167
```
168168

169+
## Acknowledgements
170+
171+
The starting point for this library was a project called [mcpdotnet](https://github.com/PederHP/mcpdotnet), initiated by [Peder Holdgaard Pederson](https://github.com/PederHP). We are grateful for the work done by Peder and other contributors to that repository, which created a solid foundation for this library.
172+
169173
## License
170174

171175
This project is licensed under the [MIT License](LICENSE).

src/ModelContextProtocol/Protocol/Transport/SseResponseStreamTransport.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public sealed class SseResponseStreamTransport(Stream sseResponseStream) : ITran
2020
private Utf8JsonWriter? _jsonWriter;
2121

2222
/// <inheritdoc/>
23-
public bool IsConnected => _sseWriteTask?.IsCompleted == false;
23+
public bool IsConnected { get; private set; }
2424

2525
/// <summary>
2626
/// Starts the transport and writes the JSON-RPC messages sent via <see cref="SendMessageAsync(IJsonRpcMessage, CancellationToken)"/>
@@ -41,6 +41,8 @@ void WriteJsonRpcMessageToBuffer(SseItem<IJsonRpcMessage?> item, IBufferWriter<b
4141
JsonSerializer.Serialize(GetUtf8JsonWriter(writer), item.Data, McpJsonUtilities.DefaultOptions.GetTypeInfo<IJsonRpcMessage?>());
4242
}
4343

44+
IsConnected = true;
45+
4446
// The very first SSE event isn't really an IJsonRpcMessage, but there's no API to write a single item of a different type,
4547
// so we fib and special-case the "endpoint" event type in the formatter.
4648
_outgoingSseChannel.Writer.TryWrite(new SseItem<IJsonRpcMessage?>(null, "endpoint"));
@@ -55,6 +57,7 @@ void WriteJsonRpcMessageToBuffer(SseItem<IJsonRpcMessage?> item, IBufferWriter<b
5557
/// <inheritdoc/>
5658
public ValueTask DisposeAsync()
5759
{
60+
IsConnected = false;
5861
_incomingChannel.Writer.TryComplete();
5962
_outgoingSseChannel.Writer.TryComplete();
6063
return new ValueTask(_sseWriteTask ?? Task.CompletedTask);
@@ -63,7 +66,7 @@ public ValueTask DisposeAsync()
6366
/// <inheritdoc/>
6467
public async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
6568
{
66-
if (_sseWriteTask is null)
69+
if (!IsConnected)
6770
{
6871
throw new InvalidOperationException($"Transport is not connected. Make sure to call {nameof(RunAsync)} first.");
6972
}
@@ -80,7 +83,7 @@ public async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken ca
8083
/// <exception cref="InvalidOperationException">Thrown when there is an attempt to process a message before calling <see cref="RunAsync(CancellationToken)"/>.</exception>
8184
public async Task OnMessageReceivedAsync(IJsonRpcMessage message, CancellationToken cancellationToken)
8285
{
83-
if (_sseWriteTask is null)
86+
if (!IsConnected)
8487
{
8588
throw new InvalidOperationException($"Transport is not connected. Make sure to call {nameof(RunAsync)} first.");
8689
}

tests/ModelContextProtocol.TestSseServer/Program.cs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ namespace ModelContextProtocol.TestSseServer;
1010

1111
public class Program
1212
{
13-
private static ILoggerFactory CreateLoggerFactory()
13+
private static ILoggerFactory CreateLoggerFactory() => LoggerFactory.Create(ConfigureSerilog);
14+
15+
public static void ConfigureSerilog(ILoggingBuilder loggingBuilder)
1416
{
15-
// Use serilog
1617
Log.Logger = new LoggerConfiguration()
1718
.MinimumLevel.Verbose() // Capture all log levels
1819
.WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "TestServer_.log"),
@@ -21,15 +22,12 @@ private static ILoggerFactory CreateLoggerFactory()
2122
.CreateLogger();
2223

2324
var logsPath = Path.Combine(AppContext.BaseDirectory, "testserver.log");
24-
return LoggerFactory.Create(builder =>
25-
{
26-
builder.AddSerilog();
27-
});
25+
loggingBuilder.AddSerilog();
2826
}
2927

3028
public static Task Main(string[] args) => MainAsync(args);
3129

32-
public static async Task MainAsync(string[] args, CancellationToken cancellationToken = default)
30+
public static async Task MainAsync(string[] args, ILoggerFactory? loggerFactory = null, CancellationToken cancellationToken = default)
3331
{
3432
Console.WriteLine("Starting server...");
3533

@@ -385,7 +383,7 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st
385383
},
386384
};
387385

388-
using var loggerFactory = CreateLoggerFactory();
386+
loggerFactory ??= CreateLoggerFactory();
389387
server = McpServerFactory.Create(new HttpListenerSseServerTransport("TestServer", 3001, loggerFactory), options, loggerFactory);
390388

391389
Console.WriteLine("Server initialized.");

tests/ModelContextProtocol.Tests/ClientIntegrationTestFixture.cs

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55

66
namespace ModelContextProtocol.Tests;
77

8-
public class ClientIntegrationTestFixture : IDisposable
8+
public class ClientIntegrationTestFixture
99
{
10-
public ILoggerFactory LoggerFactory { get; }
10+
private ILoggerFactory? _loggerFactory;
11+
1112
public McpClientOptions DefaultOptions { get; }
1213
public McpServerConfig EverythingServerConfig { get; }
1314
public McpServerConfig TestServerConfig { get; }
@@ -16,10 +17,6 @@ public class ClientIntegrationTestFixture : IDisposable
1617

1718
public ClientIntegrationTestFixture()
1819
{
19-
LoggerFactory = Microsoft.Extensions.Logging.LoggerFactory.Create(builder =>
20-
builder.AddConsole()
21-
.SetMinimumLevel(LogLevel.Debug));
22-
2320
DefaultOptions = new()
2421
{
2522
ClientInfo = new() { Name = "IntegrationTestClient", Version = "1.0.0" },
@@ -56,17 +53,16 @@ public ClientIntegrationTestFixture()
5653
}
5754
}
5855

56+
public void Initialize(ILoggerFactory loggerFactory)
57+
{
58+
_loggerFactory = loggerFactory;
59+
}
60+
5961
public Task<IMcpClient> CreateClientAsync(string clientId, McpClientOptions? clientOptions = null) =>
6062
McpClientFactory.CreateAsync(clientId switch
6163
{
6264
"everything" => EverythingServerConfig,
6365
"test_server" => TestServerConfig,
6466
_ => throw new ArgumentException($"Unknown client ID: {clientId}")
65-
}, clientOptions ?? DefaultOptions, loggerFactory: LoggerFactory);
66-
67-
public void Dispose()
68-
{
69-
LoggerFactory?.Dispose();
70-
GC.SuppressFinalize(this);
71-
}
67+
}, clientOptions ?? DefaultOptions, loggerFactory: _loggerFactory);
7268
}

tests/ModelContextProtocol.Tests/ClientIntegrationTests.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,25 @@
66
using System.Text.Json;
77
using ModelContextProtocol.Configuration;
88
using ModelContextProtocol.Protocol.Transport;
9+
using ModelContextProtocol.Tests.Utils;
910
using Xunit.Sdk;
1011
using System.Text.Encodings.Web;
1112
using System.Text.Json.Serialization.Metadata;
1213
using System.Text.Json.Serialization;
1314

1415
namespace ModelContextProtocol.Tests;
1516

16-
public class ClientIntegrationTests : IClassFixture<ClientIntegrationTestFixture>
17+
public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegrationTestFixture>
1718
{
1819
private static readonly string? s_openAIKey = Environment.GetEnvironmentVariable("AI:OpenAI:ApiKey")!;
1920

2021
private readonly ClientIntegrationTestFixture _fixture;
2122

22-
public ClientIntegrationTests(ClientIntegrationTestFixture fixture)
23+
public ClientIntegrationTests(ClientIntegrationTestFixture fixture, ITestOutputHelper testOutputHelper)
24+
: base(testOutputHelper)
2325
{
2426
_fixture = fixture;
27+
_fixture.Initialize(LoggerFactory);
2528
}
2629

2730
public static IEnumerable<object[]> GetClients() =>
@@ -474,7 +477,7 @@ public async Task CallTool_Stdio_MemoryServer()
474477
await using var client = await McpClientFactory.CreateAsync(
475478
serverConfig,
476479
clientOptions,
477-
loggerFactory: _fixture.LoggerFactory,
480+
loggerFactory: LoggerFactory,
478481
cancellationToken: TestContext.Current.CancellationToken);
479482

480483
// act
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
using ModelContextProtocol.Protocol.Transport;
22
using ModelContextProtocol.Protocol.Types;
33
using ModelContextProtocol.Server;
4-
using Microsoft.Extensions.Logging.Abstractions;
4+
using ModelContextProtocol.Tests.Utils;
55
using Moq;
66

77
namespace ModelContextProtocol.Tests.Server;
88

9-
public class McpServerFactoryTests
9+
public class McpServerFactoryTests : LoggedTest
1010
{
1111
private readonly Mock<IServerTransport> _serverTransport;
1212
private readonly McpServerOptions _options;
13-
private readonly IServiceProvider _serviceProvider;
1413

15-
public McpServerFactoryTests()
14+
public McpServerFactoryTests(ITestOutputHelper testOutputHelper)
15+
: base(testOutputHelper)
1616
{
1717
_serverTransport = new Mock<IServerTransport>();
1818
_options = new McpServerOptions
@@ -21,14 +21,13 @@ public McpServerFactoryTests()
2121
ProtocolVersion = "1.0",
2222
InitializationTimeout = TimeSpan.FromSeconds(30)
2323
};
24-
_serviceProvider = new Mock<IServiceProvider>().Object;
2524
}
2625

2726
[Fact]
2827
public async Task Create_Should_Initialize_With_Valid_Parameters()
2928
{
3029
// Arrange & Act
31-
await using IMcpServer server = McpServerFactory.Create(_serverTransport.Object, _options, NullLoggerFactory.Instance);
30+
await using IMcpServer server = McpServerFactory.Create(_serverTransport.Object, _options, LoggerFactory);
3231

3332
// Assert
3433
Assert.NotNull(server);
@@ -38,13 +37,13 @@ public async Task Create_Should_Initialize_With_Valid_Parameters()
3837
public void Constructor_Throws_For_Null_ServerTransport()
3938
{
4039
// Arrange, Act & Assert
41-
Assert.Throws<ArgumentNullException>("serverTransport", () => McpServerFactory.Create(null!, _options, NullLoggerFactory.Instance));
40+
Assert.Throws<ArgumentNullException>("serverTransport", () => McpServerFactory.Create(null!, _options, LoggerFactory));
4241
}
4342

4443
[Fact]
4544
public void Constructor_Throws_For_Null_Options()
4645
{
4746
// Arrange, Act & Assert
48-
Assert.Throws<ArgumentNullException>("serverOptions", () => McpServerFactory.Create(_serverTransport.Object, null!, NullLoggerFactory.Instance));
47+
Assert.Throws<ArgumentNullException>("serverOptions", () => McpServerFactory.Create(_serverTransport.Object, null!, LoggerFactory));
4948
}
5049
}

0 commit comments

Comments
 (0)