csharp-sdk/tests/ModelContextProtocol.Tests/Server/McpServerExtensionsTests.cs
Mackinnon Buck 38b4a26998
Restructure IMcpEndpoint, IMcpClient, and IMcpServer (#723)
* Remove `McpEndpoint` & `IMcpClient`

* Revise naming, add abstract classes

* Fix missing renames

* Move extension methods to instance methods

* Add back old API, but obsoleted

* Add back obsoleted factory classes

* Move deprecation attributes

* `SseClientTransport` -> `HttpClientTransport`

* Update src/ModelContextProtocol.Core/Server/AugmentedServiceProvider.cs

Co-authored-by: Stephen Halter <halter73@gmail.com>

* PR feedback

* PR feedback

* Add back dispose lock

* Fix docs projects

* PR feedback

* Undo `docs/*.md` changes

---------

Co-authored-by: Stephen Halter <halter73@gmail.com>
2025-09-16 10:22:10 -07:00

195 lines
7.6 KiB
C#

using Microsoft.Extensions.AI;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using Moq;
using System.Text.Json;
namespace ModelContextProtocol.Tests.Server;
#pragma warning disable CS0618 // Type or member is obsolete
public class McpServerExtensionsTests
{
[Fact]
public async Task SampleAsync_Request_Throws_When_Not_McpServer()
{
var server = new Mock<IMcpServer>(MockBehavior.Strict).Object;
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await server.SampleAsync(
new CreateMessageRequestParams { Messages = [new SamplingMessage { Role = Role.User, Content = new TextContentBlock { Text = "hi" } }] },
TestContext.Current.CancellationToken));
Assert.Contains("Prefer using 'McpServer.SampleAsync' instead", ex.Message);
}
[Fact]
public async Task SampleAsync_Messages_Throws_When_Not_McpServer()
{
var server = new Mock<IMcpServer>(MockBehavior.Strict).Object;
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await server.SampleAsync(
[new ChatMessage(ChatRole.User, "hi")], cancellationToken: TestContext.Current.CancellationToken));
Assert.Contains("Prefer using 'McpServer.SampleAsync' instead", ex.Message);
}
[Fact]
public void AsSamplingChatClient_Throws_When_Not_McpServer()
{
var server = new Mock<IMcpServer>(MockBehavior.Strict).Object;
var ex = Assert.Throws<InvalidOperationException>(server.AsSamplingChatClient);
Assert.Contains("Prefer using 'McpServer.AsSamplingChatClient' instead", ex.Message);
}
[Fact]
public void AsClientLoggerProvider_Throws_When_Not_McpServer()
{
var server = new Mock<IMcpServer>(MockBehavior.Strict).Object;
var ex = Assert.Throws<InvalidOperationException>(server.AsClientLoggerProvider);
Assert.Contains("Prefer using 'McpServer.AsClientLoggerProvider' instead", ex.Message);
}
[Fact]
public async Task RequestRootsAsync_Throws_When_Not_McpServer()
{
var server = new Mock<IMcpServer>(MockBehavior.Strict).Object;
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await server.RequestRootsAsync(
new ListRootsRequestParams(), TestContext.Current.CancellationToken));
Assert.Contains("Prefer using 'McpServer.RequestRootsAsync' instead", ex.Message);
}
[Fact]
public async Task ElicitAsync_Throws_When_Not_McpServer()
{
var server = new Mock<IMcpServer>(MockBehavior.Strict).Object;
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await server.ElicitAsync(
new ElicitRequestParams { Message = "hello" }, TestContext.Current.CancellationToken));
Assert.Contains("Prefer using 'McpServer.ElicitAsync' instead", ex.Message);
}
[Fact]
public async Task SampleAsync_Request_Forwards_To_McpServer_SendRequestAsync()
{
var mockServer = new Mock<McpServer> { CallBase = true };
var resultPayload = new CreateMessageResult
{
Content = new TextContentBlock { Text = "resp" },
Model = "test-model",
Role = Role.Assistant,
StopReason = "endTurn",
};
mockServer
.Setup(s => s.ClientCapabilities)
.Returns(new ClientCapabilities() { Sampling = new() });
mockServer
.Setup(s => s.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new JsonRpcResponse
{
Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
});
IMcpServer server = mockServer.Object;
var result = await server.SampleAsync(new CreateMessageRequestParams
{
Messages = [new SamplingMessage { Role = Role.User, Content = new TextContentBlock { Text = "hi" } }]
}, TestContext.Current.CancellationToken);
Assert.Equal("test-model", result.Model);
Assert.Equal(Role.Assistant, result.Role);
Assert.Equal("resp", Assert.IsType<TextContentBlock>(result.Content).Text);
mockServer.Verify(s => s.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task SampleAsync_Messages_Forwards_To_McpServer_SendRequestAsync()
{
var mockServer = new Mock<McpServer> { CallBase = true };
var resultPayload = new CreateMessageResult
{
Content = new TextContentBlock { Text = "resp" },
Model = "test-model",
Role = Role.Assistant,
StopReason = "endTurn",
};
mockServer
.Setup(s => s.ClientCapabilities)
.Returns(new ClientCapabilities() { Sampling = new() });
mockServer
.Setup(s => s.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new JsonRpcResponse
{
Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
});
IMcpServer server = mockServer.Object;
var chatResponse = await server.SampleAsync([new ChatMessage(ChatRole.User, "hi")], cancellationToken: TestContext.Current.CancellationToken);
Assert.Equal("test-model", chatResponse.ModelId);
var last = chatResponse.Messages.Last();
Assert.Equal(ChatRole.Assistant, last.Role);
Assert.Equal("resp", last.Text);
mockServer.Verify(s => s.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task RequestRootsAsync_Forwards_To_McpServer_SendRequestAsync()
{
var mockServer = new Mock<McpServer> { CallBase = true };
var resultPayload = new ListRootsResult { Roots = [new Root { Uri = "root://a" }] };
mockServer
.Setup(s => s.ClientCapabilities)
.Returns(new ClientCapabilities() { Roots = new() });
mockServer
.Setup(s => s.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new JsonRpcResponse
{
Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
});
IMcpServer server = mockServer.Object;
var result = await server.RequestRootsAsync(new ListRootsRequestParams(), TestContext.Current.CancellationToken);
Assert.Equal("root://a", result.Roots[0].Uri);
mockServer.Verify(s => s.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task ElicitAsync_Forwards_To_McpServer_SendRequestAsync()
{
var mockServer = new Mock<McpServer> { CallBase = true };
var resultPayload = new ElicitResult { Action = "accept" };
mockServer
.Setup(s => s.ClientCapabilities)
.Returns(new ClientCapabilities() { Elicitation = new() });
mockServer
.Setup(s => s.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(new JsonRpcResponse
{
Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
});
IMcpServer server = mockServer.Object;
var result = await server.ElicitAsync(new ElicitRequestParams { Message = "hi" }, TestContext.Current.CancellationToken);
Assert.Equal("accept", result.Action);
mockServer.Verify(s => s.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
}
}