Remove obsolete APIs from codebase and standardize MCP diagnostic IDs (#985)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: MackinnonBuck <10456961+MackinnonBuck@users.noreply.github.com> Co-authored-by: eiriktsarpalis <2813363+eiriktsarpalis@users.noreply.github.com> Co-authored-by: Mackinnon Buck <mackinnon.buck@gmail.com> Co-authored-by: jeffhandley <1031940+jeffhandley@users.noreply.github.com> Co-authored-by: Jeff Handley <jeffhandley@users.noreply.github.com>
This commit is contained in:
parent
5385c0de67
commit
136755f2be
8 changed files with 663 additions and 2674 deletions
|
|
@ -1,394 +0,0 @@
|
|||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using Moq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
public class McpClientExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task PingAsync_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.PingAsync(TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.PingAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPromptAsync_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.GetPromptAsync(
|
||||
"name", cancellationToken: TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.GetPromptAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CallToolAsync_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.CallToolAsync(
|
||||
"tool", cancellationToken: TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.CallToolAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListResourcesAsync_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.ListResourcesAsync(
|
||||
cancellationToken: TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.ListResourcesAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumerateResourcesAsync_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => client.EnumerateResourcesAsync(cancellationToken: TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.EnumerateResourcesAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubscribeToResourceAsync_String_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.SubscribeToResourceAsync(
|
||||
"mcp://resource/1", TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.SubscribeToResourceAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubscribeToResourceAsync_Uri_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.SubscribeToResourceAsync(
|
||||
new Uri("mcp://resource/1"), TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.SubscribeToResourceAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UnsubscribeFromResourceAsync_String_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.UnsubscribeFromResourceAsync(
|
||||
"mcp://resource/1", TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.UnsubscribeFromResourceAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UnsubscribeFromResourceAsync_Uri_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.UnsubscribeFromResourceAsync(
|
||||
new Uri("mcp://resource/1"), TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.UnsubscribeFromResourceAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadResourceAsync_String_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.ReadResourceAsync(
|
||||
"mcp://resource/1", TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.ReadResourceAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadResourceAsync_Uri_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.ReadResourceAsync(
|
||||
new Uri("mcp://resource/1"), TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.ReadResourceAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadResourceAsync_Template_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.ReadResourceAsync(
|
||||
"mcp://resource/{id}", new Dictionary<string, object?> { ["id"] = 1 }, TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.ReadResourceAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompleteAsync_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
var reference = new PromptReference { Name = "prompt" };
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.CompleteAsync(
|
||||
reference, "arg", "val", TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.CompleteAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListToolsAsync_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.ListToolsAsync(
|
||||
cancellationToken: TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.ListToolsAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumerateToolsAsync_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => client.EnumerateToolsAsync(cancellationToken: TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.EnumerateToolsAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListPromptsAsync_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.ListPromptsAsync(
|
||||
cancellationToken: TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.ListPromptsAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumeratePromptsAsync_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => client.EnumeratePromptsAsync(cancellationToken: TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.EnumeratePromptsAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListResourceTemplatesAsync_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await client.ListResourceTemplatesAsync(
|
||||
cancellationToken: TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.ListResourceTemplatesAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumerateResourceTemplatesAsync_Throws_When_Not_McpClient()
|
||||
{
|
||||
var client = new Mock<IMcpClient>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => client.EnumerateResourceTemplatesAsync(cancellationToken: TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpClient.EnumerateResourceTemplatesAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PingAsync_Forwards_To_McpClient_SendRequestAsync()
|
||||
{
|
||||
var mockClient = new Mock<McpClient> { CallBase = true };
|
||||
|
||||
mockClient
|
||||
.Setup(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new JsonRpcResponse
|
||||
{
|
||||
Result = JsonSerializer.SerializeToNode(new object(), McpJsonUtilities.DefaultOptions),
|
||||
});
|
||||
|
||||
IMcpClient client = mockClient.Object;
|
||||
|
||||
await client.PingAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
mockClient.Verify(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPromptAsync_Forwards_To_McpClient_SendRequestAsync()
|
||||
{
|
||||
var mockClient = new Mock<McpClient> { CallBase = true };
|
||||
|
||||
var resultPayload = new GetPromptResult
|
||||
{
|
||||
Messages = [new()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new TextContentBlock { Text = "hi" }
|
||||
}]
|
||||
};
|
||||
|
||||
mockClient
|
||||
.Setup(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new JsonRpcResponse
|
||||
{
|
||||
Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
|
||||
});
|
||||
|
||||
IMcpClient client = mockClient.Object;
|
||||
|
||||
var result = await client.GetPromptAsync("name", cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("hi", Assert.IsType<TextContentBlock>(result.Messages[0].Content).Text);
|
||||
mockClient.Verify(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CallToolAsync_Forwards_To_McpClient_SendRequestAsync()
|
||||
{
|
||||
var mockClient = new Mock<McpClient> { CallBase = true };
|
||||
|
||||
var callResult = new CallToolResult { Content = [new TextContentBlock { Text = "ok" }] };
|
||||
|
||||
mockClient
|
||||
.Setup(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new JsonRpcResponse
|
||||
{
|
||||
Result = JsonSerializer.SerializeToNode(callResult, McpJsonUtilities.DefaultOptions),
|
||||
});
|
||||
|
||||
IMcpClient client = mockClient.Object;
|
||||
|
||||
var result = await client.CallToolAsync("tool", cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("ok", Assert.IsType<TextContentBlock>(result.Content[0]).Text);
|
||||
mockClient.Verify(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SubscribeToResourceAsync_Forwards_To_McpClient_SendRequestAsync()
|
||||
{
|
||||
var mockClient = new Mock<McpClient> { CallBase = true };
|
||||
|
||||
mockClient
|
||||
.Setup(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new JsonRpcResponse
|
||||
{
|
||||
Result = JsonSerializer.SerializeToNode(new EmptyResult(), McpJsonUtilities.DefaultOptions),
|
||||
});
|
||||
|
||||
IMcpClient client = mockClient.Object;
|
||||
|
||||
await client.SubscribeToResourceAsync("mcp://resource/1", TestContext.Current.CancellationToken);
|
||||
|
||||
mockClient.Verify(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UnsubscribeFromResourceAsync_Forwards_To_McpClient_SendRequestAsync()
|
||||
{
|
||||
var mockClient = new Mock<McpClient> { CallBase = true };
|
||||
|
||||
mockClient
|
||||
.Setup(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new JsonRpcResponse
|
||||
{
|
||||
Result = JsonSerializer.SerializeToNode(new EmptyResult(), McpJsonUtilities.DefaultOptions),
|
||||
});
|
||||
|
||||
IMcpClient client = mockClient.Object;
|
||||
|
||||
await client.UnsubscribeFromResourceAsync("mcp://resource/1", TestContext.Current.CancellationToken);
|
||||
|
||||
mockClient.Verify(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CompleteAsync_Forwards_To_McpClient_SendRequestAsync()
|
||||
{
|
||||
var mockClient = new Mock<McpClient> { CallBase = true };
|
||||
|
||||
var completion = new Completion { Values = ["one", "two"] };
|
||||
var resultPayload = new CompleteResult { Completion = completion };
|
||||
|
||||
mockClient
|
||||
.Setup(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new JsonRpcResponse
|
||||
{
|
||||
Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
|
||||
});
|
||||
|
||||
IMcpClient client = mockClient.Object;
|
||||
|
||||
var result = await client.CompleteAsync(new PromptReference { Name = "p" }, "arg", "val", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Contains("one", result.Completion.Values);
|
||||
mockClient.Verify(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadResourceAsync_String_Forwards_To_McpClient_SendRequestAsync()
|
||||
{
|
||||
var mockClient = new Mock<McpClient> { CallBase = true };
|
||||
|
||||
var resultPayload = new ReadResourceResult { Contents = [] };
|
||||
|
||||
mockClient
|
||||
.Setup(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new JsonRpcResponse
|
||||
{
|
||||
Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
|
||||
});
|
||||
|
||||
IMcpClient client = mockClient.Object;
|
||||
|
||||
var result = await client.ReadResourceAsync("mcp://resource/1", TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(result);
|
||||
mockClient.Verify(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadResourceAsync_Uri_Forwards_To_McpClient_SendRequestAsync()
|
||||
{
|
||||
var mockClient = new Mock<McpClient> { CallBase = true };
|
||||
|
||||
var resultPayload = new ReadResourceResult { Contents = [] };
|
||||
|
||||
mockClient
|
||||
.Setup(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new JsonRpcResponse
|
||||
{
|
||||
Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
|
||||
});
|
||||
|
||||
IMcpClient client = mockClient.Object;
|
||||
|
||||
var result = await client.ReadResourceAsync(new Uri("mcp://resource/1"), TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(result);
|
||||
mockClient.Verify(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadResourceAsync_Template_Forwards_To_McpClient_SendRequestAsync()
|
||||
{
|
||||
var mockClient = new Mock<McpClient> { CallBase = true };
|
||||
|
||||
var resultPayload = new ReadResourceResult { Contents = [] };
|
||||
|
||||
mockClient
|
||||
.Setup(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new JsonRpcResponse
|
||||
{
|
||||
Result = JsonSerializer.SerializeToNode(resultPayload, McpJsonUtilities.DefaultOptions),
|
||||
});
|
||||
|
||||
IMcpClient client = mockClient.Object;
|
||||
|
||||
var result = await client.ReadResourceAsync("mcp://resource/{id}", new Dictionary<string, object?> { ["id"] = 1 }, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(result);
|
||||
mockClient.Verify(c => c.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using Moq;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
public class McpEndpointExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task SendRequestAsync_Generic_Throws_When_Not_McpSession()
|
||||
{
|
||||
var endpoint = new Mock<IMcpEndpoint>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await McpEndpointExtensions.SendRequestAsync<string, int>(
|
||||
endpoint, "method", "param", cancellationToken: TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpServer.SendRequestAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendNotificationAsync_Parameterless_Throws_When_Not_McpSession()
|
||||
{
|
||||
var endpoint = new Mock<IMcpEndpoint>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await McpEndpointExtensions.SendNotificationAsync(
|
||||
endpoint, "notify", cancellationToken: TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpServer.SendNotificationAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendNotificationAsync_Generic_Throws_When_Not_McpSession()
|
||||
{
|
||||
var endpoint = new Mock<IMcpEndpoint>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await McpEndpointExtensions.SendNotificationAsync(
|
||||
endpoint, "notify", "payload", cancellationToken: TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpServer.SendNotificationAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotifyProgressAsync_Throws_When_Not_McpSession()
|
||||
{
|
||||
var endpoint = new Mock<IMcpEndpoint>(MockBehavior.Strict).Object;
|
||||
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await McpEndpointExtensions.NotifyProgressAsync(
|
||||
endpoint, new ProgressToken("t1"), new ProgressNotificationValue { Progress = 0.5f }, cancellationToken: TestContext.Current.CancellationToken));
|
||||
Assert.Contains("Prefer using 'McpServer.NotifyProgressAsync' instead", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendRequestAsync_Generic_Forwards_To_McpSession_SendRequestAsync()
|
||||
{
|
||||
var mockSession = new Mock<McpSession> { CallBase = true };
|
||||
|
||||
mockSession
|
||||
.Setup(s => s.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
|
||||
.ReturnsAsync(new JsonRpcResponse
|
||||
{
|
||||
Result = JsonSerializer.SerializeToNode(42, McpJsonUtilities.DefaultOptions),
|
||||
});
|
||||
|
||||
IMcpEndpoint endpoint = mockSession.Object;
|
||||
|
||||
var result = await endpoint.SendRequestAsync<string, int>("method", "param", cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(42, result);
|
||||
mockSession.Verify(s => s.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendNotificationAsync_Parameterless_Forwards_To_McpSession_SendMessageAsync()
|
||||
{
|
||||
var mockSession = new Mock<McpSession> { CallBase = true };
|
||||
|
||||
mockSession
|
||||
.Setup(s => s.SendMessageAsync(It.IsAny<JsonRpcMessage>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
IMcpEndpoint endpoint = mockSession.Object;
|
||||
|
||||
await endpoint.SendNotificationAsync("notify", cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
mockSession.Verify(s => s.SendMessageAsync(It.IsAny<JsonRpcMessage>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendNotificationAsync_Generic_Forwards_To_McpSession_SendMessageAsync()
|
||||
{
|
||||
var mockSession = new Mock<McpSession> { CallBase = true };
|
||||
|
||||
mockSession
|
||||
.Setup(s => s.SendMessageAsync(It.IsAny<JsonRpcMessage>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
IMcpEndpoint endpoint = mockSession.Object;
|
||||
|
||||
await endpoint.SendNotificationAsync("notify", "payload", cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
mockSession.Verify(s => s.SendMessageAsync(It.IsAny<JsonRpcMessage>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task NotifyProgressAsync_Forwards_To_McpSession_SendMessageAsync()
|
||||
{
|
||||
var mockSession = new Mock<McpSession> { CallBase = true };
|
||||
|
||||
mockSession
|
||||
.Setup(s => s.SendMessageAsync(It.IsAny<JsonRpcMessage>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
IMcpEndpoint endpoint = mockSession.Object;
|
||||
|
||||
await endpoint.NotifyProgressAsync(new ProgressToken("progress-token"), new ProgressNotificationValue { Progress = 1 }, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
mockSession.Verify(s => s.SendMessageAsync(It.IsAny<JsonRpcMessage>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public class ElicitationDefaultValuesTests
|
||||
public static class ElicitationDefaultValuesTests
|
||||
{
|
||||
[Fact]
|
||||
public void StringSchema_Default_Serializes_Correctly()
|
||||
public static void StringSchema_Default_Serializes_Correctly()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.StringSchema
|
||||
|
|
@ -32,7 +30,7 @@ public class ElicitationDefaultValuesTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void StringSchema_Default_Null_DoesNotSerialize()
|
||||
public static void StringSchema_Default_Null_DoesNotSerialize()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.StringSchema
|
||||
|
|
@ -48,7 +46,7 @@ public class ElicitationDefaultValuesTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void NumberSchema_Default_Serializes_Correctly()
|
||||
public static void NumberSchema_Default_Serializes_Correctly()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.NumberSchema
|
||||
|
|
@ -74,7 +72,7 @@ public class ElicitationDefaultValuesTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void NumberSchema_Integer_Default_Serializes_Correctly()
|
||||
public static void NumberSchema_Integer_Default_Serializes_Correctly()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.NumberSchema
|
||||
|
|
@ -97,7 +95,7 @@ public class ElicitationDefaultValuesTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void NumberSchema_Default_Null_DoesNotSerialize()
|
||||
public static void NumberSchema_Default_Null_DoesNotSerialize()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.NumberSchema
|
||||
|
|
@ -113,50 +111,7 @@ public class ElicitationDefaultValuesTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumSchema_Default_Serializes_Correctly()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.EnumSchema
|
||||
{
|
||||
Title = "Priority",
|
||||
Description = "Task priority",
|
||||
Enum = ["low", "medium", "high"],
|
||||
EnumNames = ["Low Priority", "Medium Priority", "High Priority"],
|
||||
Default = "medium"
|
||||
};
|
||||
|
||||
// Act - serialize as base type to use the converter
|
||||
string json = JsonSerializer.Serialize<ElicitRequestParams.PrimitiveSchemaDefinition>(schema, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
var enumSchema = Assert.IsType<ElicitRequestParams.EnumSchema>(deserialized);
|
||||
Assert.Equal("medium", enumSchema.Default);
|
||||
Assert.Equal("Priority", enumSchema.Title);
|
||||
Assert.Equal("Task priority", enumSchema.Description);
|
||||
Assert.Contains("\"default\":\"medium\"", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumSchema_Default_Null_DoesNotSerialize()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.EnumSchema
|
||||
{
|
||||
Title = "Priority",
|
||||
Enum = ["low", "medium", "high"]
|
||||
};
|
||||
|
||||
// Act - serialize as base type to use the converter
|
||||
string json = JsonSerializer.Serialize<ElicitRequestParams.PrimitiveSchemaDefinition>(schema, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.DoesNotContain("\"default\"", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BooleanSchema_Default_True_Serializes_Correctly()
|
||||
public static void BooleanSchema_Default_True_Serializes_Correctly()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.BooleanSchema
|
||||
|
|
@ -178,7 +133,7 @@ public class ElicitationDefaultValuesTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void BooleanSchema_Default_False_Serializes_Correctly()
|
||||
public static void BooleanSchema_Default_False_Serializes_Correctly()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.BooleanSchema
|
||||
|
|
@ -199,7 +154,7 @@ public class ElicitationDefaultValuesTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void PrimitiveSchemaDefinition_StringSchema_WithDefault_RoundTrips()
|
||||
public static void PrimitiveSchemaDefinition_StringSchema_WithDefault_RoundTrips()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.StringSchema
|
||||
|
|
@ -221,7 +176,7 @@ public class ElicitationDefaultValuesTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void PrimitiveSchemaDefinition_NumberSchema_WithDefault_RoundTrips()
|
||||
public static void PrimitiveSchemaDefinition_NumberSchema_WithDefault_RoundTrips()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.NumberSchema
|
||||
|
|
@ -245,30 +200,110 @@ public class ElicitationDefaultValuesTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void PrimitiveSchemaDefinition_EnumSchema_WithDefault_RoundTrips()
|
||||
public static void UntitledSingleSelectEnumSchema_Default_Null_DoesNotSerialize()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.EnumSchema
|
||||
var schema = new ElicitRequestParams.UntitledSingleSelectEnumSchema
|
||||
{
|
||||
Title = "Status",
|
||||
Enum = ["draft", "published", "archived"],
|
||||
Default = "draft"
|
||||
Title = "Priority",
|
||||
Enum = ["low", "medium", "high"]
|
||||
};
|
||||
|
||||
// Act - serialize as base type to test the converter
|
||||
// Act - serialize as base type to use the converter
|
||||
string json = JsonSerializer.Serialize<ElicitRequestParams.PrimitiveSchemaDefinition>(schema, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
// EnumSchema without enumNames deserializes as UntitledSingleSelectEnumSchema
|
||||
var enumSchema = Assert.IsType<ElicitRequestParams.UntitledSingleSelectEnumSchema>(deserialized);
|
||||
Assert.Equal("draft", enumSchema.Default);
|
||||
Assert.Equal(["draft", "published", "archived"], enumSchema.Enum);
|
||||
Assert.DoesNotContain("\"default\"", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequestSchema_WithAllDefaultTypes_Serializes_Correctly()
|
||||
public static void TitledSingleSelectEnumSchema_Default_Null_DoesNotSerialize()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.TitledSingleSelectEnumSchema
|
||||
{
|
||||
Title = "Priority",
|
||||
OneOf =
|
||||
{
|
||||
new ElicitRequestParams.EnumSchemaOption { Title = "Low", Const = "low" },
|
||||
new ElicitRequestParams.EnumSchemaOption { Title = "Medium", Const = "medium" },
|
||||
new ElicitRequestParams.EnumSchemaOption { Title = "High", Const = "high" }
|
||||
}
|
||||
};
|
||||
|
||||
// Act - serialize as base type to use the converter
|
||||
string json = JsonSerializer.Serialize<ElicitRequestParams.PrimitiveSchemaDefinition>(schema, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.DoesNotContain("\"default\"", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void UntitledMultiSelectEnumSchema_Default_Null_DoesNotSerialize()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.UntitledMultiSelectEnumSchema
|
||||
{
|
||||
Title = "Tags",
|
||||
Items = new ElicitRequestParams.UntitledEnumItemsSchema
|
||||
{
|
||||
Enum = ["tag1", "tag2", "tag3"]
|
||||
}
|
||||
};
|
||||
|
||||
// Act - serialize as base type to use the converter
|
||||
string json = JsonSerializer.Serialize<ElicitRequestParams.PrimitiveSchemaDefinition>(schema, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.DoesNotContain("\"default\"", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void TitledMultiSelectEnumSchema_Default_Null_DoesNotSerialize()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.TitledMultiSelectEnumSchema
|
||||
{
|
||||
Title = "Tags",
|
||||
Items = new ElicitRequestParams.TitledEnumItemsSchema
|
||||
{
|
||||
AnyOf =
|
||||
[
|
||||
new ElicitRequestParams.EnumSchemaOption { Title = "Tag 1", Const = "tag1" },
|
||||
new ElicitRequestParams.EnumSchemaOption { Title = "Tag 2", Const = "tag2" },
|
||||
new ElicitRequestParams.EnumSchemaOption { Title = "Tag 3", Const = "tag3" }
|
||||
]
|
||||
}
|
||||
};
|
||||
// Act - serialize as base type to use the converter
|
||||
string json = JsonSerializer.Serialize<ElicitRequestParams.PrimitiveSchemaDefinition>(schema, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.DoesNotContain("\"default\"", json);
|
||||
}
|
||||
|
||||
#pragma warning disable MCP9001 // LegacyTitledEnumSchema is deprecated but supported for backward compatibility
|
||||
[Fact]
|
||||
public static void LegacyTitledEnumSchema_Default_Null_DoesNotSerialize()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.LegacyTitledEnumSchema
|
||||
{
|
||||
Title = "Legacy Options",
|
||||
Enum = ["option1", "option2"],
|
||||
EnumNames = ["Option 1", "Option 2"]
|
||||
};
|
||||
|
||||
// Act - serialize as base type to use the converter
|
||||
string json = JsonSerializer.Serialize<ElicitRequestParams.PrimitiveSchemaDefinition>(schema, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.DoesNotContain("\"default\"", json);
|
||||
}
|
||||
#pragma warning restore MCP9001
|
||||
|
||||
[Fact]
|
||||
public static void RequestSchema_WithAllDefaultTypes_Serializes_Correctly()
|
||||
{
|
||||
// Arrange
|
||||
var requestParams = new ElicitRequestParams
|
||||
|
|
@ -299,11 +334,46 @@ public class ElicitationDefaultValuesTests
|
|||
Title = "Active",
|
||||
Default = true
|
||||
},
|
||||
["status"] = new ElicitRequestParams.EnumSchema
|
||||
["status"] = new ElicitRequestParams.UntitledSingleSelectEnumSchema
|
||||
{
|
||||
Title = "Status",
|
||||
Enum = ["active", "inactive"],
|
||||
Default = "active"
|
||||
},
|
||||
["tags"] = new ElicitRequestParams.UntitledMultiSelectEnumSchema
|
||||
{
|
||||
Title = "Tags",
|
||||
Items = new ElicitRequestParams.UntitledEnumItemsSchema
|
||||
{
|
||||
Enum = ["tag1", "tag2", "tag3"]
|
||||
},
|
||||
Default = ["tag1", "tag3"]
|
||||
},
|
||||
["salutation"] = new ElicitRequestParams.TitledSingleSelectEnumSchema
|
||||
{
|
||||
Title = "Salutation",
|
||||
OneOf =
|
||||
{
|
||||
new ElicitRequestParams.EnumSchemaOption { Title = "N/A", Const = "none" },
|
||||
new ElicitRequestParams.EnumSchemaOption { Title = "Mr.", Const = "mr" },
|
||||
new ElicitRequestParams.EnumSchemaOption { Title = "Ms.", Const = "ms" },
|
||||
new ElicitRequestParams.EnumSchemaOption { Title = "Dr.", Const = "dr" }
|
||||
},
|
||||
Default = "none"
|
||||
},
|
||||
["categories"] = new ElicitRequestParams.TitledMultiSelectEnumSchema
|
||||
{
|
||||
Title = "Categories",
|
||||
Items = new ElicitRequestParams.TitledEnumItemsSchema
|
||||
{
|
||||
AnyOf =
|
||||
[
|
||||
new ElicitRequestParams.EnumSchemaOption { Title = "Category 1", Const = "cat1" },
|
||||
new ElicitRequestParams.EnumSchemaOption { Title = "Category 2", Const = "cat2" },
|
||||
new ElicitRequestParams.EnumSchemaOption { Title = "Category 3", Const = "cat3" }
|
||||
]
|
||||
},
|
||||
Default = ["cat2", "cat3"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -315,22 +385,30 @@ public class ElicitationDefaultValuesTests
|
|||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(5, deserialized.RequestedSchema.Properties.Count);
|
||||
|
||||
Assert.Equal(8, deserialized.RequestedSchema.Properties.Count);
|
||||
|
||||
var nameSchema = Assert.IsType<ElicitRequestParams.StringSchema>(deserialized.RequestedSchema.Properties["name"]);
|
||||
Assert.Equal("John Doe", nameSchema.Default);
|
||||
|
||||
|
||||
var ageSchema = Assert.IsType<ElicitRequestParams.NumberSchema>(deserialized.RequestedSchema.Properties["age"]);
|
||||
Assert.Equal(30, ageSchema.Default);
|
||||
|
||||
|
||||
var scoreSchema = Assert.IsType<ElicitRequestParams.NumberSchema>(deserialized.RequestedSchema.Properties["score"]);
|
||||
Assert.Equal(85.5, scoreSchema.Default);
|
||||
|
||||
|
||||
var activeSchema = Assert.IsType<ElicitRequestParams.BooleanSchema>(deserialized.RequestedSchema.Properties["active"]);
|
||||
Assert.True(activeSchema.Default);
|
||||
|
||||
// EnumSchema without enumNames deserializes as UntitledSingleSelectEnumSchema
|
||||
|
||||
var statusSchema = Assert.IsType<ElicitRequestParams.UntitledSingleSelectEnumSchema>(deserialized.RequestedSchema.Properties["status"]);
|
||||
Assert.Equal("active", statusSchema.Default);
|
||||
|
||||
var tagsSchema = Assert.IsType<ElicitRequestParams.UntitledMultiSelectEnumSchema>(deserialized.RequestedSchema.Properties["tags"]);
|
||||
Assert.Equal(new List<string> { "tag1", "tag3" }, tagsSchema.Default);
|
||||
|
||||
var salutationSchema = Assert.IsType<ElicitRequestParams.TitledSingleSelectEnumSchema>(deserialized.RequestedSchema.Properties["salutation"]);
|
||||
Assert.Equal("none", salutationSchema.Default);
|
||||
|
||||
var categoriesSchema = Assert.IsType<ElicitRequestParams.TitledMultiSelectEnumSchema>(deserialized.RequestedSchema.Properties["categories"]);
|
||||
Assert.Equal(new List<string> { "cat2", "cat3" }, categoriesSchema.Default);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ using ModelContextProtocol.Client;
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
namespace ModelContextProtocol.Tests.Configuration;
|
||||
|
||||
public partial class ElicitationTests : ClientServerTestBase
|
||||
|
|
@ -46,10 +44,14 @@ public partial class ElicitationTests : ClientServerTestBase
|
|||
Description = "description4",
|
||||
Default = true,
|
||||
},
|
||||
["prop4"] = new ElicitRequestParams.EnumSchema
|
||||
["prop4"] = new ElicitRequestParams.TitledSingleSelectEnumSchema
|
||||
{
|
||||
Enum = ["option1", "option2", "option3"],
|
||||
EnumNames = ["Name1", "Name2", "Name3"],
|
||||
OneOf =
|
||||
[
|
||||
new ElicitRequestParams.EnumSchemaOption { Const = "option1", Title = "Name1" },
|
||||
new ElicitRequestParams.EnumSchemaOption { Const = "option2", Title = "Name2" },
|
||||
new ElicitRequestParams.EnumSchemaOption { Const = "option3", Title = "Name3" },
|
||||
]
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
@ -104,9 +106,9 @@ public partial class ElicitationTests : ClientServerTestBase
|
|||
break;
|
||||
|
||||
case "prop4":
|
||||
var primitiveEnum = Assert.IsType<ElicitRequestParams.EnumSchema>(entry.Value);
|
||||
Assert.Equal(["option1", "option2", "option3"], primitiveEnum.Enum);
|
||||
Assert.Equal(["Name1", "Name2", "Name3"], primitiveEnum.EnumNames);
|
||||
var primitiveEnum = Assert.IsType<ElicitRequestParams.TitledSingleSelectEnumSchema>(entry.Value);
|
||||
Assert.Equal(["option1", "option2", "option3"], primitiveEnum.OneOf.Select(e => e.Const));
|
||||
Assert.Equal(["Name1", "Name2", "Name3"], primitiveEnum.OneOf.Select(e => e.Title));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -4,8 +4,6 @@ using ModelContextProtocol.Protocol;
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
namespace ModelContextProtocol.Tests.Configuration;
|
||||
|
||||
public partial class ElicitationTypedTests : ClientServerTestBase
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public class EnumSchemaTests
|
||||
|
|
@ -161,36 +159,9 @@ public class EnumSchemaTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void LegacyTitledEnumSchema_WithEnumNames_Deserializes_As_EnumSchema()
|
||||
public void SingleSelectEnum_WithEnum_Deserializes_As_UntitledSingleSelect()
|
||||
{
|
||||
// Arrange - JSON with enumNames should deserialize as EnumSchema for backward compatibility
|
||||
string json = """
|
||||
{
|
||||
"type": "string",
|
||||
"title": "Status",
|
||||
"enum": ["active", "inactive", "pending"],
|
||||
"enumNames": ["Active", "Inactive", "Pending"],
|
||||
"default": "active"
|
||||
}
|
||||
""";
|
||||
|
||||
// Act
|
||||
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
var result = Assert.IsType<ElicitRequestParams.EnumSchema>(deserialized);
|
||||
Assert.Equal("string", result.Type);
|
||||
Assert.Equal("Status", result.Title);
|
||||
Assert.Equal(["active", "inactive", "pending"], result.Enum);
|
||||
Assert.Equal(["Active", "Inactive", "Pending"], result.EnumNames);
|
||||
Assert.Equal("active", result.Default);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumSchema_WithoutEnumNames_Deserializes_As_UntitledSingleSelect()
|
||||
{
|
||||
// Arrange - JSON without enumNames should deserialize as UntitledSingleSelectEnumSchema
|
||||
// Arrange - JSON with enum should deserialize as UntitledSingleSelectEnumSchema
|
||||
string json = """
|
||||
{
|
||||
"type": "string",
|
||||
|
|
@ -213,7 +184,7 @@ public class EnumSchemaTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumSchema_WithOneOf_Deserializes_As_TitledSingleSelect()
|
||||
public void SingleSelectEnum_WithOneOf_Deserializes_As_TitledSingleSelect()
|
||||
{
|
||||
// Arrange - JSON with oneOf should deserialize as TitledSingleSelectEnumSchema
|
||||
string json = """
|
||||
|
|
@ -246,7 +217,7 @@ public class EnumSchemaTests
|
|||
[Fact]
|
||||
public void MultiSelectEnum_WithEnum_Deserializes_As_UntitledMultiSelect()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange - JSON with items.enum should deserialize as UntitledMultiSelectEnumSchema
|
||||
string json = """
|
||||
{
|
||||
"type": "array",
|
||||
|
|
@ -275,7 +246,7 @@ public class EnumSchemaTests
|
|||
[Fact]
|
||||
public void MultiSelectEnum_WithAnyOf_Deserializes_As_TitledMultiSelect()
|
||||
{
|
||||
// Arrange
|
||||
// Arrange - JSON with items.anyOf should deserialize as TitledMultiSelectEnumSchema
|
||||
string json = """
|
||||
{
|
||||
"type": "array",
|
||||
|
|
@ -306,4 +277,92 @@ public class EnumSchemaTests
|
|||
Assert.Equal("Administrator", result.Items.AnyOf[0].Title);
|
||||
Assert.Equal(["user"], result.Default);
|
||||
}
|
||||
|
||||
#pragma warning disable MCP9001 // EnumSchema and LegacyTitledEnumSchema are deprecated but supported for backward compatibility
|
||||
[Fact]
|
||||
public void LegacyTitledEnumSchema_Serializes_Correctly()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.LegacyTitledEnumSchema
|
||||
{
|
||||
Title = "Environment",
|
||||
Description = "Deployment environment",
|
||||
Enum = ["dev", "staging", "prod"],
|
||||
EnumNames = ["Development", "Staging", "Production"],
|
||||
Default = "staging"
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize<ElicitRequestParams.PrimitiveSchemaDefinition>(schema, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
var result = Assert.IsType<ElicitRequestParams.EnumSchema>(deserialized);
|
||||
Assert.Equal("string", result.Type);
|
||||
Assert.Equal("Environment", result.Title);
|
||||
Assert.Equal("Deployment environment", result.Description);
|
||||
Assert.Equal(["dev", "staging", "prod"], result.Enum);
|
||||
Assert.Equal(["Development", "Staging", "Production"], result.EnumNames);
|
||||
Assert.Equal("staging", result.Default);
|
||||
Assert.Contains("\"enumNames\":[\"Development\",\"Staging\",\"Production\"]", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumSchema_Serializes_Correctly()
|
||||
{
|
||||
// Arrange
|
||||
var schema = new ElicitRequestParams.EnumSchema
|
||||
{
|
||||
Title = "Environment",
|
||||
Description = "Deployment environment",
|
||||
Enum = ["dev", "staging", "prod"],
|
||||
EnumNames = ["Development", "Staging", "Production"],
|
||||
Default = "staging"
|
||||
};
|
||||
|
||||
// Act
|
||||
string json = JsonSerializer.Serialize<ElicitRequestParams.PrimitiveSchemaDefinition>(schema, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
var result = Assert.IsType<ElicitRequestParams.EnumSchema>(deserialized);
|
||||
Assert.Equal("string", result.Type);
|
||||
Assert.Equal("Environment", result.Title);
|
||||
Assert.Equal("Deployment environment", result.Description);
|
||||
Assert.Equal(["dev", "staging", "prod"], result.Enum);
|
||||
Assert.Equal(["Development", "Staging", "Production"], result.EnumNames);
|
||||
Assert.Equal("staging", result.Default);
|
||||
Assert.Contains("\"enumNames\":[\"Development\",\"Staging\",\"Production\"]", json);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Enum_WithEnumNames_Deserializes_As_EnumSchema()
|
||||
{
|
||||
// Arrange - JSON with enumNames should deserialize as (deprecated) EnumSchema
|
||||
string json = """
|
||||
{
|
||||
"type": "string",
|
||||
"title": "Environment",
|
||||
"description": "Deployment environment",
|
||||
"enum": ["dev", "staging", "prod"],
|
||||
"enumNames": ["Development", "Staging", "Production"],
|
||||
"default": "staging"
|
||||
}
|
||||
""";
|
||||
// Act
|
||||
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
var result = Assert.IsType<ElicitRequestParams.EnumSchema>(deserialized);
|
||||
Assert.Equal("string", result.Type);
|
||||
Assert.Equal("Environment", result.Title);
|
||||
Assert.Equal("Deployment environment", result.Description);
|
||||
Assert.Equal(["dev", "staging", "prod"], result.Enum);
|
||||
Assert.Equal(["Development", "Staging", "Production"], result.EnumNames);
|
||||
Assert.Equal("staging", result.Default);
|
||||
}
|
||||
#pragma warning restore MCP9001
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,179 +1,33 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public static class PrimitiveSchemaDefinitionTests
|
||||
{
|
||||
[Fact]
|
||||
public static void StringSchema_UnknownArrayProperty_IsIgnored()
|
||||
public static void StringSchema_UnknownProperties_AreIgnored()
|
||||
{
|
||||
// This test verifies that the PrimitiveSchemaDefinition.Converter properly skips unknown properties
|
||||
// even when they contain complex structures like arrays or objects.
|
||||
//
|
||||
// In this unexpected JSON, "unknownArray" appears inside a string schema (where it doesn't belong).
|
||||
// The converter should gracefully ignore this unknown property and successfully deserialize
|
||||
// the rest of the schema.
|
||||
|
||||
const string jsonWithUnknownArray = """
|
||||
{
|
||||
"type": "string",
|
||||
"title": "Test String",
|
||||
"minLength": 1,
|
||||
"maxLength": 100,
|
||||
"unknownArray": [
|
||||
{
|
||||
"nested": "value"
|
||||
},
|
||||
{
|
||||
"another": "object"
|
||||
}
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
jsonWithUnknownArray,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
var stringSchema = Assert.IsType<ElicitRequestParams.StringSchema>(result);
|
||||
Assert.Equal("string", stringSchema.Type);
|
||||
Assert.Equal("Test String", stringSchema.Title);
|
||||
Assert.Equal(1, stringSchema.MinLength);
|
||||
Assert.Equal(100, stringSchema.MaxLength);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void NumberSchema_UnknownObjectProperty_IsIgnored()
|
||||
{
|
||||
// Test that unknown properties with nested objects are properly skipped
|
||||
|
||||
const string jsonWithUnknownObject = """
|
||||
{
|
||||
"type": "number",
|
||||
"description": "Test Number",
|
||||
"minimum": 0,
|
||||
"maximum": 1000,
|
||||
"unknownObject": {
|
||||
"deeply": {
|
||||
"nested": {
|
||||
"value": "should be ignored"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
jsonWithUnknownObject,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
var numberSchema = Assert.IsType<ElicitRequestParams.NumberSchema>(result);
|
||||
Assert.Equal("number", numberSchema.Type);
|
||||
Assert.Equal("Test Number", numberSchema.Description);
|
||||
Assert.Equal(0, numberSchema.Minimum);
|
||||
Assert.Equal(1000, numberSchema.Maximum);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void BooleanSchema_UnknownMixedProperties_AreIgnored()
|
||||
{
|
||||
// Test multiple unknown properties with different types
|
||||
|
||||
const string jsonWithMixedUnknown = """
|
||||
{
|
||||
"type": "boolean",
|
||||
"title": "Test Boolean",
|
||||
"unknownString": "value",
|
||||
"unknownNumber": 42,
|
||||
"unknownArray": [1, 2, 3],
|
||||
"unknownObject": {"key": "value"},
|
||||
"unknownBool": true,
|
||||
"unknownNull": null,
|
||||
"default": false
|
||||
}
|
||||
""";
|
||||
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
jsonWithMixedUnknown,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
var boolSchema = Assert.IsType<ElicitRequestParams.BooleanSchema>(result);
|
||||
Assert.Equal("boolean", boolSchema.Type);
|
||||
Assert.Equal("Test Boolean", boolSchema.Title);
|
||||
Assert.False(boolSchema.Default);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void EnumSchema_UnknownNestedArrays_AreIgnored()
|
||||
{
|
||||
// Test complex unknown properties with arrays of objects
|
||||
|
||||
const string jsonWithNestedArrays = """
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["option1", "option2", "option3"],
|
||||
"enumNames": ["Name1", "Name2", "Name3"],
|
||||
"unknownComplex": [
|
||||
{
|
||||
"nested": [
|
||||
{"deep": "value1"},
|
||||
{"deep": "value2"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"nested": [
|
||||
{"deep": "value3"}
|
||||
]
|
||||
}
|
||||
],
|
||||
"default": "option1"
|
||||
}
|
||||
""";
|
||||
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
jsonWithNestedArrays,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
var enumSchema = Assert.IsType<ElicitRequestParams.EnumSchema>(result);
|
||||
Assert.Equal("string", enumSchema.Type);
|
||||
Assert.Equal(3, enumSchema.Enum.Count);
|
||||
Assert.Contains("option1", enumSchema.Enum);
|
||||
Assert.Contains("option2", enumSchema.Enum);
|
||||
Assert.Contains("option3", enumSchema.Enum);
|
||||
Assert.Equal(3, enumSchema.EnumNames!.Count);
|
||||
Assert.Contains("Name1", enumSchema.EnumNames);
|
||||
Assert.Contains("Name2", enumSchema.EnumNames);
|
||||
Assert.Contains("Name3", enumSchema.EnumNames);
|
||||
Assert.Equal("option1", enumSchema.Default);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void StringSchema_MultipleUnknownProperties_AllIgnored()
|
||||
{
|
||||
// Test that multiple unknown properties are all properly skipped
|
||||
|
||||
const string jsonWithMultipleUnknown = """
|
||||
const string json = """
|
||||
{
|
||||
"type": "string",
|
||||
"title": "Test",
|
||||
"unknownOne": {"a": 1},
|
||||
|
||||
"unknownNull": null,
|
||||
"unknownEmptyObject": {},
|
||||
"unknownObject": {"a": 1},
|
||||
"unknownEmptyArray": [],
|
||||
"unknownArray": [1, 2, 3],
|
||||
"unknownNestedObject": {"b": {"c": "d", "e": ["f"]}},
|
||||
|
||||
"minLength": 5,
|
||||
"unknownTwo": [1, 2, 3],
|
||||
"maxLength": 50,
|
||||
"unknownThree": {"b": {"c": "d"}},
|
||||
"format": "email"
|
||||
}
|
||||
""";
|
||||
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
jsonWithMultipleUnknown,
|
||||
json,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
|
|
@ -186,189 +40,256 @@ public static class PrimitiveSchemaDefinitionTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public static void IntegerSchema_UnknownArrayOfArrays_IsIgnored()
|
||||
public static void NumberSchema_UnknownProperties_AreIgnored()
|
||||
{
|
||||
// Test deeply nested array structures in unknown properties
|
||||
|
||||
const string jsonWithArrayOfArrays = """
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
"unknownNested": [
|
||||
[
|
||||
[1, 2, 3],
|
||||
[4, 5, 6]
|
||||
],
|
||||
[
|
||||
[7, 8, 9]
|
||||
]
|
||||
]
|
||||
}
|
||||
""";
|
||||
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
jsonWithArrayOfArrays,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
var numberSchema = Assert.IsType<ElicitRequestParams.NumberSchema>(result);
|
||||
Assert.Equal("integer", numberSchema.Type);
|
||||
Assert.Equal(1, numberSchema.Minimum);
|
||||
Assert.Equal(100, numberSchema.Maximum);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void StringSchema_EmptyUnknownArray_IsIgnored()
|
||||
{
|
||||
// Test empty arrays in unknown properties
|
||||
|
||||
const string jsonWithEmptyArray = """
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Test",
|
||||
"unknownEmpty": [],
|
||||
"minLength": 0
|
||||
}
|
||||
""";
|
||||
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
jsonWithEmptyArray,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
var stringSchema = Assert.IsType<ElicitRequestParams.StringSchema>(result);
|
||||
Assert.Equal("string", stringSchema.Type);
|
||||
Assert.Equal("Test", stringSchema.Description);
|
||||
Assert.Equal(0, stringSchema.MinLength);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void NumberSchema_EmptyUnknownObject_IsIgnored()
|
||||
{
|
||||
// Test empty objects in unknown properties
|
||||
|
||||
const string jsonWithEmptyObject = """
|
||||
const string json = """
|
||||
{
|
||||
"type": "number",
|
||||
"title": "Test Number",
|
||||
"unknownEmpty": {},
|
||||
"minimum": 0.0,
|
||||
"maximum": 100.0
|
||||
"description": "Test Number",
|
||||
|
||||
"unknownNull": null,
|
||||
"unknownEmptyObject": {},
|
||||
"unknownObject": {"a": 1},
|
||||
"unknownEmptyArray": [],
|
||||
"unknownArray": [1, 2, 3],
|
||||
"unknownNestedObject": {"b": {"c": "d", "e": ["f"]}},
|
||||
|
||||
"minimum": 0,
|
||||
"maximum": 1000
|
||||
}
|
||||
""";
|
||||
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
jsonWithEmptyObject,
|
||||
json,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
var numberSchema = Assert.IsType<ElicitRequestParams.NumberSchema>(result);
|
||||
Assert.Equal("number", numberSchema.Type);
|
||||
Assert.Equal("Test Number", numberSchema.Title);
|
||||
Assert.Equal(0.0, numberSchema.Minimum);
|
||||
Assert.Equal(100.0, numberSchema.Maximum);
|
||||
Assert.Equal("Test Number", numberSchema.Description);
|
||||
Assert.Equal(0, numberSchema.Minimum);
|
||||
Assert.Equal(1000, numberSchema.Maximum);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void EnumSchema_UnknownPropertiesBetweenRequired_AreIgnored()
|
||||
public static void BooleanSchema_UnknownProperties_AreIgnored()
|
||||
{
|
||||
// Test unknown properties interspersed with required ones
|
||||
|
||||
const string jsonWithInterspersedUnknown = """
|
||||
{
|
||||
"unknownFirst": {"x": 1},
|
||||
"type": "string",
|
||||
"unknownSecond": [1, 2],
|
||||
"enum": ["a", "b"],
|
||||
"unknownThird": {"nested": {"value": true}},
|
||||
"enumNames": ["Alpha", "Beta"]
|
||||
}
|
||||
""";
|
||||
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
jsonWithInterspersedUnknown,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
var enumSchema = Assert.IsType<ElicitRequestParams.EnumSchema>(result);
|
||||
Assert.Equal("string", enumSchema.Type);
|
||||
Assert.Equal(2, enumSchema.Enum.Count);
|
||||
Assert.Contains("a", enumSchema.Enum);
|
||||
Assert.Contains("b", enumSchema.Enum);
|
||||
Assert.Equal(2, enumSchema.EnumNames!.Count);
|
||||
Assert.Contains("Alpha", enumSchema.EnumNames);
|
||||
Assert.Contains("Beta", enumSchema.EnumNames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void BooleanSchema_VeryDeeplyNestedUnknown_IsIgnored()
|
||||
{
|
||||
// Test very deeply nested structures in unknown properties
|
||||
|
||||
const string jsonWithVeryDeepNesting = """
|
||||
const string json = """
|
||||
{
|
||||
"type": "boolean",
|
||||
"unknownDeep": {
|
||||
"level1": {
|
||||
"level2": {
|
||||
"level3": {
|
||||
"level4": {
|
||||
"level5": {
|
||||
"value": "deep"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"default": true
|
||||
"title": "Test Boolean",
|
||||
|
||||
"unknownNull": null,
|
||||
"unknownEmptyObject": {},
|
||||
"unknownObject": {"a": 1},
|
||||
"unknownEmptyArray": [],
|
||||
"unknownArray": [1, 2, 3],
|
||||
"unknownNestedObject": {"b": {"c": "d", "e": ["f"]}},
|
||||
|
||||
"default": false
|
||||
}
|
||||
""";
|
||||
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
jsonWithVeryDeepNesting,
|
||||
json,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(result);
|
||||
var boolSchema = Assert.IsType<ElicitRequestParams.BooleanSchema>(result);
|
||||
Assert.Equal("boolean", boolSchema.Type);
|
||||
Assert.True(boolSchema.Default);
|
||||
Assert.Equal("Test Boolean", boolSchema.Title);
|
||||
Assert.False(boolSchema.Default);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void EnumSchema_Deserialization_PreservesKnownProperties()
|
||||
public static void UntitledSingleSelectEnumSchema_UnknownProperties_AreIgnored()
|
||||
{
|
||||
// Test deserialization of enum schema with all properties
|
||||
|
||||
const string enumSchemaJson = """
|
||||
const string json = """
|
||||
{
|
||||
"type": "string",
|
||||
"title": "Test Enum",
|
||||
"description": "A test enum schema",
|
||||
"enum": ["option1", "option2", "option3"],
|
||||
"enumNames": ["Name1", "Name2", "Name3"],
|
||||
"default": "option2"
|
||||
|
||||
"unknownNull": null,
|
||||
"unknownEmptyObject": {},
|
||||
"unknownObject": {"a": 1},
|
||||
"unknownEmptyArray": [],
|
||||
"unknownArray": [1, 2, 3],
|
||||
"unknownNestedObject": {"b": {"c": "d", "e": ["f"]}},
|
||||
|
||||
"default": "option1"
|
||||
}
|
||||
""";
|
||||
|
||||
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
enumSchemaJson,
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
json,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
var enumSchema = Assert.IsType<ElicitRequestParams.EnumSchema>(deserialized);
|
||||
Assert.NotNull(result);
|
||||
var enumSchema = Assert.IsType<ElicitRequestParams.UntitledSingleSelectEnumSchema>(result);
|
||||
Assert.Equal("string", enumSchema.Type);
|
||||
Assert.Equal("Test Enum", enumSchema.Title);
|
||||
Assert.Equal("A test enum schema", enumSchema.Description);
|
||||
Assert.Equal(3, enumSchema.Enum.Count);
|
||||
Assert.Contains("option1", enumSchema.Enum);
|
||||
Assert.Contains("option2", enumSchema.Enum);
|
||||
Assert.Contains("option3", enumSchema.Enum);
|
||||
Assert.Equal(3, enumSchema.EnumNames!.Count);
|
||||
Assert.Contains("Name1", enumSchema.EnumNames);
|
||||
Assert.Contains("Name2", enumSchema.EnumNames);
|
||||
Assert.Contains("Name3", enumSchema.EnumNames);
|
||||
Assert.Equal("option1", enumSchema.Default);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void TitledSingleSelectEnumSchema_UnknownProperties_AreIgnored()
|
||||
{
|
||||
const string json = """
|
||||
{
|
||||
"type": "string",
|
||||
"oneOf": [
|
||||
{"const": "option1", "title": "Option 1"},
|
||||
{"const": "option2", "title": "Option 2"}
|
||||
],
|
||||
|
||||
"unknownNull": null,
|
||||
"unknownEmptyObject": {},
|
||||
"unknownObject": {"a": 1},
|
||||
"unknownEmptyArray": [],
|
||||
"unknownArray": [1, 2, 3],
|
||||
"unknownNestedObject": {"b": {"c": "d", "e": ["f"]}},
|
||||
|
||||
"default": "option2"
|
||||
}
|
||||
""";
|
||||
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
json,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
Assert.NotNull(result);
|
||||
var enumSchema = Assert.IsType<ElicitRequestParams.TitledSingleSelectEnumSchema>(result);
|
||||
Assert.Equal("string", enumSchema.Type);
|
||||
Assert.Equal(2, enumSchema.OneOf.Count);
|
||||
Assert.Contains(enumSchema.OneOf, option => option.Const == "option1" && option.Title == "Option 1");
|
||||
Assert.Contains(enumSchema.OneOf, option => option.Const == "option2" && option.Title == "Option 2");
|
||||
Assert.Equal("option2", enumSchema.Default);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void UntitledMultiSelectEnumSchema_UnknownProperties_AreIgnored()
|
||||
{
|
||||
const string json = """
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"unknownNull": null,
|
||||
"unknownEmptyObject": {},
|
||||
"unknownObject": {"a": 1},
|
||||
"unknownEmptyArray": [],
|
||||
"unknownArray": [1, 2, 3],
|
||||
"unknownNestedObject": {"b": {"c": "d", "e": ["f"]}},
|
||||
|
||||
"enum": ["optionA", "optionB", "optionC"]
|
||||
},
|
||||
|
||||
"unknownNull": null,
|
||||
"unknownEmptyObject": {},
|
||||
"unknownObject": {"a": 1},
|
||||
"unknownEmptyArray": [],
|
||||
"unknownArray": [1, 2, 3],
|
||||
"unknownNestedObject": {"b": {"c": "d", "e": ["f"]}},
|
||||
|
||||
"default": ["optionA", "optionC"]
|
||||
}
|
||||
""";
|
||||
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
json,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
Assert.NotNull(result);
|
||||
var enumSchema = Assert.IsType<ElicitRequestParams.UntitledMultiSelectEnumSchema>(result);
|
||||
Assert.Equal("array", enumSchema.Type);
|
||||
Assert.Equal(3, enumSchema.Items.Enum.Count);
|
||||
Assert.Contains("optionA", enumSchema.Items.Enum);
|
||||
Assert.Contains("optionB", enumSchema.Items.Enum);
|
||||
Assert.Contains("optionC", enumSchema.Items.Enum);
|
||||
Assert.Equal(2, enumSchema.Default!.Count);
|
||||
Assert.Contains("optionA", enumSchema.Default);
|
||||
Assert.Contains("optionC", enumSchema.Default);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public static void TitledMultiSelectEnumSchema_UnknownProperties_AreIgnored()
|
||||
{
|
||||
const string json = """
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"unknownNull": null,
|
||||
"unknownEmptyObject": {},
|
||||
"unknownObject": {"a": 1},
|
||||
"unknownEmptyArray": [],
|
||||
"unknownArray": [1, 2, 3],
|
||||
"unknownNestedObject": {"b": {"c": "d", "e": ["f"]}},
|
||||
|
||||
"anyOf": [
|
||||
{"const": "optionX", "title": "Option X"},
|
||||
{"const": "optionY", "title": "Option Y"},
|
||||
{"const": "optionZ", "title": "Option Z"}
|
||||
]
|
||||
},
|
||||
|
||||
"unknownNull": null,
|
||||
"unknownEmptyObject": {},
|
||||
"unknownObject": {"a": 1},
|
||||
"unknownEmptyArray": [],
|
||||
"unknownArray": [1, 2, 3],
|
||||
"unknownNestedObject": {"b": {"c": "d", "e": ["f"]}},
|
||||
|
||||
"default": ["optionX", "optionZ"]
|
||||
}
|
||||
""";
|
||||
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
json,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
Assert.NotNull(result);
|
||||
var enumSchema = Assert.IsType<ElicitRequestParams.TitledMultiSelectEnumSchema>(result);
|
||||
Assert.Equal("array", enumSchema.Type);
|
||||
Assert.Equal(3, enumSchema.Items.AnyOf.Count);
|
||||
Assert.Contains(enumSchema.Items.AnyOf, option => option.Const == "optionX" && option.Title == "Option X");
|
||||
Assert.Contains(enumSchema.Items.AnyOf, option => option.Const == "optionY" && option.Title == "Option Y");
|
||||
Assert.Contains(enumSchema.Items.AnyOf, option => option.Const == "optionZ" && option.Title == "Option Z");
|
||||
Assert.Equal(2, enumSchema.Default!.Count);
|
||||
Assert.Contains("optionX", enumSchema.Default);
|
||||
Assert.Contains("optionZ", enumSchema.Default);
|
||||
}
|
||||
|
||||
#pragma warning disable MCP9001 // LegacyTitledEnumSchema is deprecated but supported for backward compatibility
|
||||
[Fact]
|
||||
public static void LegacyTitledEnumSchema_UnknownProperties_AreIgnored()
|
||||
{
|
||||
const string json = """
|
||||
{
|
||||
"type": "string",
|
||||
"enum": ["option1", "option2"],
|
||||
|
||||
"unknownNull": null,
|
||||
"unknownEmptyObject": {},
|
||||
"unknownObject": {"a": 1},
|
||||
"unknownEmptyArray": [],
|
||||
"unknownArray": [1, 2, 3],
|
||||
"unknownNestedObject": {"b": {"c": "d", "e": ["f"]}},
|
||||
|
||||
"enumNames": ["Option 1", "Option 2"],
|
||||
"default": "option2"
|
||||
}
|
||||
""";
|
||||
|
||||
var result = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(
|
||||
json,
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
Assert.NotNull(result);
|
||||
var enumSchema = Assert.IsType<ElicitRequestParams.EnumSchema>(result);
|
||||
Assert.Equal("string", enumSchema.Type);
|
||||
Assert.Equal(2, enumSchema.Enum.Count);
|
||||
Assert.Contains("option1", enumSchema.Enum);
|
||||
Assert.Contains("option2", enumSchema.Enum);
|
||||
Assert.Contains("Option 1", enumSchema.EnumNames!);
|
||||
Assert.Contains("Option 2", enumSchema.EnumNames!);
|
||||
}
|
||||
#pragma warning restore MCP9001
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,217 +0,0 @@
|
|||
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" }] }],
|
||||
MaxTokens = 1000
|
||||
},
|
||||
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" }] }],
|
||||
MaxTokens = 1000
|
||||
}, TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal("test-model", result.Model);
|
||||
Assert.Equal(Role.Assistant, result.Role);
|
||||
Assert.Equal("resp", Assert.IsType<TextContentBlock>(result.Content[0]).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",
|
||||
};
|
||||
|
||||
const int CustomMaxSamplingOutputTokens = 500;
|
||||
|
||||
mockServer
|
||||
.Setup(s => s.ClientCapabilities)
|
||||
.Returns(new ClientCapabilities() { Sampling = new() });
|
||||
|
||||
mockServer
|
||||
.Setup(s => s.ServerOptions)
|
||||
.Returns(new McpServerOptions { MaxSamplingOutputTokens = CustomMaxSamplingOutputTokens });
|
||||
|
||||
CreateMessageRequestParams? capturedRequest = null;
|
||||
mockServer
|
||||
.Setup(s => s.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()))
|
||||
.Callback<JsonRpcRequest, CancellationToken>((request, _) =>
|
||||
{
|
||||
capturedRequest = JsonSerializer.Deserialize<CreateMessageRequestParams>(
|
||||
request.Params ?? throw new InvalidOperationException(),
|
||||
McpJsonUtilities.DefaultOptions);
|
||||
})
|
||||
.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);
|
||||
|
||||
// Verify that the default value was used
|
||||
Assert.NotNull(capturedRequest);
|
||||
Assert.Equal(CustomMaxSamplingOutputTokens, capturedRequest.MaxTokens);
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue