Add tools support to sampling (#976)
This commit is contained in:
parent
ad86b53347
commit
15139cc569
14 changed files with 1623 additions and 425 deletions
|
|
@ -25,4 +25,127 @@ public class AIContentExtensionsTests
|
|||
JsonElement result = Assert.IsType<JsonElement>(frc.Result);
|
||||
Assert.Contains("This is a test message.", result.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToAIContent_ConvertsToolUseContentBlock()
|
||||
{
|
||||
Dictionary<string, object?> inputDict = new() { ["city"] = "Paris", ["units"] = "metric" };
|
||||
ToolUseContentBlock toolUse = new()
|
||||
{
|
||||
Id = "call_abc123",
|
||||
Name = "get_weather",
|
||||
Input = JsonSerializer.SerializeToElement(inputDict, McpJsonUtilities.DefaultOptions)
|
||||
};
|
||||
|
||||
AIContent? aiContent = toolUse.ToAIContent();
|
||||
|
||||
var functionCall = Assert.IsType<FunctionCallContent>(aiContent);
|
||||
Assert.Equal("call_abc123", functionCall.CallId);
|
||||
Assert.Equal("get_weather", functionCall.Name);
|
||||
Assert.NotNull(functionCall.Arguments);
|
||||
|
||||
var cityArg = Assert.IsType<JsonElement>(functionCall.Arguments["city"]);
|
||||
Assert.Equal("Paris", cityArg.GetString());
|
||||
var unitsArg = Assert.IsType<JsonElement>(functionCall.Arguments["units"]);
|
||||
Assert.Equal("metric", unitsArg.GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToAIContent_ConvertsToolResultContentBlock()
|
||||
{
|
||||
ToolResultContentBlock toolResult = new()
|
||||
{
|
||||
ToolUseId = "call_abc123",
|
||||
Content = [new TextContentBlock { Text = "Weather: 18°C" }],
|
||||
IsError = false
|
||||
};
|
||||
|
||||
AIContent? aiContent = toolResult.ToAIContent();
|
||||
|
||||
var functionResult = Assert.IsType<FunctionResultContent>(aiContent);
|
||||
Assert.Equal("call_abc123", functionResult.CallId);
|
||||
Assert.Null(functionResult.Exception);
|
||||
Assert.NotNull(functionResult.Result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToAIContent_ConvertsToolResultContentBlockWithError()
|
||||
{
|
||||
ToolResultContentBlock toolResult = new()
|
||||
{
|
||||
ToolUseId = "call_abc123",
|
||||
Content = [new TextContentBlock { Text = "Error: Invalid city" }],
|
||||
IsError = true
|
||||
};
|
||||
|
||||
AIContent? aiContent = toolResult.ToAIContent();
|
||||
|
||||
var functionResult = Assert.IsType<FunctionResultContent>(aiContent);
|
||||
Assert.Equal("call_abc123", functionResult.CallId);
|
||||
Assert.NotNull(functionResult.Exception);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToAIContent_ConvertsToolResultWithMultipleContent()
|
||||
{
|
||||
ToolResultContentBlock toolResult = new()
|
||||
{
|
||||
ToolUseId = "call_123",
|
||||
Content =
|
||||
[
|
||||
new TextContentBlock { Text = "Text result" },
|
||||
new ImageContentBlock { Data = Convert.ToBase64String([1, 2, 3]), MimeType = "image/png" }
|
||||
]
|
||||
};
|
||||
|
||||
AIContent? aiContent = toolResult.ToAIContent();
|
||||
|
||||
var functionResult = Assert.IsType<FunctionResultContent>(aiContent);
|
||||
Assert.Equal("call_123", functionResult.CallId);
|
||||
|
||||
var resultList = Assert.IsAssignableFrom<IList<AIContent>>(functionResult.Result);
|
||||
Assert.Equal(2, resultList.Count);
|
||||
Assert.IsType<TextContent>(resultList[0]);
|
||||
Assert.IsType<DataContent>(resultList[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToAIContent_ToolUseToFunctionCallRoundTrip()
|
||||
{
|
||||
Dictionary<string, object?> inputDict = new() { ["param1"] = "value1", ["param2"] = 42 };
|
||||
ToolUseContentBlock original = new()
|
||||
{
|
||||
Id = "call_123",
|
||||
Name = "test_tool",
|
||||
Input = JsonSerializer.SerializeToElement(inputDict, McpJsonUtilities.DefaultOptions)
|
||||
};
|
||||
|
||||
var functionCall = Assert.IsType<FunctionCallContent>(original.ToAIContent());
|
||||
|
||||
Assert.Equal("call_123", functionCall.CallId);
|
||||
Assert.Equal("test_tool", functionCall.Name);
|
||||
Assert.NotNull(functionCall.Arguments);
|
||||
|
||||
var param1 = Assert.IsType<JsonElement>(functionCall.Arguments["param1"]);
|
||||
Assert.Equal("value1", param1.GetString());
|
||||
var param2 = Assert.IsType<JsonElement>(functionCall.Arguments["param2"]);
|
||||
Assert.Equal(42, param2.GetInt32());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToAIContent_ToolResultToFunctionResultRoundTrip()
|
||||
{
|
||||
ToolResultContentBlock original = new()
|
||||
{
|
||||
ToolUseId = "call_123",
|
||||
Content = [new TextContentBlock { Text = "Result" }, new TextContentBlock { Text = "More data" }],
|
||||
IsError = false
|
||||
};
|
||||
|
||||
var functionResult = Assert.IsType<FunctionResultContent>(original.ToAIContent());
|
||||
|
||||
Assert.Equal("call_123", functionResult.CallId);
|
||||
Assert.False(functionResult.Exception != null);
|
||||
Assert.NotNull(functionResult.Result);
|
||||
}
|
||||
}
|
||||
|
|
@ -73,10 +73,10 @@ public class McpClientCreationTests
|
|||
RootsHandler = async (t, r) => new ListRootsResult { Roots = [] },
|
||||
SamplingHandler = async (c, p, t) => new CreateMessageResult
|
||||
{
|
||||
Content = new TextContentBlock { Text = "result" },
|
||||
Content = [new TextContentBlock { Text = "result" }],
|
||||
Model = "test-model",
|
||||
Role = Role.User,
|
||||
StopReason = "endTurn"
|
||||
StopReason = "endTurn",
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -219,7 +219,14 @@ public class McpClientExtensionsTests
|
|||
{
|
||||
var mockClient = new Mock<McpClient> { CallBase = true };
|
||||
|
||||
var resultPayload = new GetPromptResult { Messages = [new PromptMessage { Role = Role.User, Content = new TextContentBlock { Text = "hi" } }] };
|
||||
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>()))
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using ModelContextProtocol.Client;
|
|||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
using Moq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using System.Threading.Channels;
|
||||
|
|
@ -106,10 +107,10 @@ public class McpClientTests : ClientServerTestBase
|
|||
{
|
||||
Messages =
|
||||
[
|
||||
new SamplingMessage
|
||||
new SamplingMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new TextContentBlock { Text = "Hello" }
|
||||
Content = [new TextContentBlock { Text = "Hello" }]
|
||||
}
|
||||
],
|
||||
Temperature = temperature,
|
||||
|
|
@ -134,14 +135,14 @@ public class McpClientTests : ClientServerTestBase
|
|||
.Setup(client => client.GetStreamingResponseAsync(It.IsAny<IEnumerable<ChatMessage>>(), It.IsAny<ChatOptions>(), cancellationToken))
|
||||
.Returns(expectedResponse);
|
||||
|
||||
var handler = McpClientExtensions.CreateSamplingHandler(mockChatClient.Object);
|
||||
var handler = mockChatClient.Object.CreateSamplingHandler();
|
||||
|
||||
// Act
|
||||
var result = await handler(requestParams, Mock.Of<IProgress<ProgressNotificationValue>>(), cancellationToken);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("Hello, World!", (result.Content as TextContentBlock)?.Text);
|
||||
Assert.Equal("Hello, World!", result.Content.OfType<TextContentBlock>().FirstOrDefault()?.Text);
|
||||
Assert.Equal("test-model", result.Model);
|
||||
Assert.Equal(Role.Assistant, result.Role);
|
||||
Assert.Equal("endTurn", result.StopReason);
|
||||
|
|
@ -156,14 +157,14 @@ public class McpClientTests : ClientServerTestBase
|
|||
{
|
||||
Messages =
|
||||
[
|
||||
new SamplingMessage
|
||||
new SamplingMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new ImageContentBlock
|
||||
Content = [new ImageContentBlock
|
||||
{
|
||||
MimeType = "image/png",
|
||||
Data = Convert.ToBase64String(new byte[] { 1, 2, 3 })
|
||||
}
|
||||
}],
|
||||
}
|
||||
],
|
||||
MaxTokens = 100
|
||||
|
|
@ -188,14 +189,14 @@ public class McpClientTests : ClientServerTestBase
|
|||
.Setup(client => client.GetStreamingResponseAsync(It.IsAny<IEnumerable<ChatMessage>>(), It.IsAny<ChatOptions>(), cancellationToken))
|
||||
.Returns(expectedResponse);
|
||||
|
||||
var handler = McpClientExtensions.CreateSamplingHandler(mockChatClient.Object);
|
||||
var handler = mockChatClient.Object.CreateSamplingHandler();
|
||||
|
||||
// Act
|
||||
var result = await handler(requestParams, Mock.Of<IProgress<ProgressNotificationValue>>(), cancellationToken);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(expectedData, (result.Content as ImageContentBlock)?.Data);
|
||||
Assert.Equal(expectedData, result.Content.OfType<ImageContentBlock>().FirstOrDefault()?.Data);
|
||||
Assert.Equal("test-model", result.Model);
|
||||
Assert.Equal(Role.Assistant, result.Role);
|
||||
Assert.Equal("endTurn", result.StopReason);
|
||||
|
|
@ -222,7 +223,7 @@ public class McpClientTests : ClientServerTestBase
|
|||
new SamplingMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new EmbeddedResourceBlock { Resource = resource },
|
||||
Content = [new EmbeddedResourceBlock { Resource = resource }],
|
||||
}
|
||||
],
|
||||
MaxTokens = 100
|
||||
|
|
@ -247,7 +248,7 @@ public class McpClientTests : ClientServerTestBase
|
|||
.Setup(client => client.GetStreamingResponseAsync(It.IsAny<IEnumerable<ChatMessage>>(), It.IsAny<ChatOptions>(), cancellationToken))
|
||||
.Returns(expectedResponse);
|
||||
|
||||
var handler = McpClientExtensions.CreateSamplingHandler(mockChatClient.Object);
|
||||
var handler = mockChatClient.Object.CreateSamplingHandler();
|
||||
|
||||
// Act
|
||||
var result = await handler(requestParams, Mock.Of<IProgress<ProgressNotificationValue>>(), cancellationToken);
|
||||
|
|
@ -542,4 +543,120 @@ public class McpClientTests : ClientServerTestBase
|
|||
await using McpClient client = await CreateMcpClientForServer(new() { ProtocolVersion = protocolVersion });
|
||||
Assert.Equal(protocolVersion ?? "2025-06-18", client.NegotiatedProtocolVersion);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EndToEnd_SamplingWithTools_ServerUsesIChatClientWithFunctionInvocation_ClientHandlesSamplingWithIChatClient()
|
||||
{
|
||||
int getWeatherToolCallCount = 0;
|
||||
int askClientToolCallCount = 0;
|
||||
|
||||
Server.ServerOptions.ToolCollection?.Add(McpServerTool.Create(
|
||||
async (McpServer server, string query, CancellationToken cancellationToken) =>
|
||||
{
|
||||
askClientToolCallCount++;
|
||||
|
||||
var weatherTool = AIFunctionFactory.Create(
|
||||
(string location) =>
|
||||
{
|
||||
getWeatherToolCallCount++;
|
||||
return $"Weather in {location}: sunny, 22°C";
|
||||
},
|
||||
"get_weather", "Gets the weather for a location");
|
||||
|
||||
var response = await server
|
||||
.AsSamplingChatClient()
|
||||
.AsBuilder()
|
||||
.UseFunctionInvocation()
|
||||
.Build()
|
||||
.GetResponseAsync(query, new ChatOptions { Tools = [weatherTool] }, cancellationToken);
|
||||
|
||||
return response.Text ?? "No response";
|
||||
},
|
||||
new() { Name = "ask_client", Description = "Asks the client a question using sampling" }));
|
||||
|
||||
int samplingCallCount = 0;
|
||||
TestChatClient testChatClient = new((messages, options, ct) =>
|
||||
{
|
||||
int currentCall = samplingCallCount++;
|
||||
var lastMessage = messages.LastOrDefault();
|
||||
|
||||
// First call: Return a tool call request for get_weather
|
||||
if (currentCall == 0)
|
||||
{
|
||||
return Task.FromResult<ChatResponse>(new([
|
||||
new ChatMessage(ChatRole.User, messages.First().Contents),
|
||||
new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("call_weather_123", "get_weather", new Dictionary<string, object?> { ["location"] = "Paris" })])
|
||||
])
|
||||
{
|
||||
ModelId = "test-model",
|
||||
FinishReason = ChatFinishReason.ToolCalls
|
||||
});
|
||||
}
|
||||
// Second call (after tool result): Return final text response
|
||||
else
|
||||
{
|
||||
var toolResult = lastMessage?.Contents.OfType<FunctionResultContent>().FirstOrDefault();
|
||||
Assert.NotNull(toolResult);
|
||||
Assert.Equal("call_weather_123", toolResult.CallId);
|
||||
|
||||
string resultText = toolResult.Result?.ToString() ?? string.Empty;
|
||||
Assert.Contains("Weather in Paris: sunny", resultText);
|
||||
|
||||
return Task.FromResult<ChatResponse>(new([
|
||||
new ChatMessage(ChatRole.User, messages.First().Contents),
|
||||
new ChatMessage(ChatRole.Assistant, [new FunctionCallContent("call_weather_123", "get_weather", new Dictionary<string, object?> { ["location"] = "Paris" })]),
|
||||
new ChatMessage(ChatRole.User, [toolResult]),
|
||||
new ChatMessage(ChatRole.Assistant, [new TextContent($"Based on the weather data: {resultText}")])
|
||||
])
|
||||
{
|
||||
ModelId = "test-model",
|
||||
FinishReason = ChatFinishReason.Stop
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await using McpClient client = await CreateMcpClientForServer(new()
|
||||
{
|
||||
Handlers = new() { SamplingHandler = testChatClient.CreateSamplingHandler() },
|
||||
});
|
||||
|
||||
var result = await client.CallToolAsync(
|
||||
"ask_client",
|
||||
new Dictionary<string, object?> { ["query"] = "What's the weather in Paris?" },
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.Null(result.IsError);
|
||||
|
||||
var textContent = result.Content.OfType<TextContentBlock>().FirstOrDefault();
|
||||
Assert.NotNull(textContent);
|
||||
Assert.Contains("Weather in Paris: sunny, 22", textContent.Text);
|
||||
Assert.Equal(1, getWeatherToolCallCount);
|
||||
Assert.Equal(1, askClientToolCallCount);
|
||||
Assert.Equal(2, samplingCallCount);
|
||||
}
|
||||
|
||||
/// <summary>Simple test IChatClient implementation for testing.</summary>
|
||||
private sealed class TestChatClient(Func<IEnumerable<ChatMessage>, ChatOptions?, CancellationToken, Task<ChatResponse>> getResponse) : IChatClient
|
||||
{
|
||||
public Task<ChatResponse> GetResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options = null,
|
||||
CancellationToken cancellationToken = default) =>
|
||||
getResponse(messages, options, cancellationToken);
|
||||
|
||||
async IAsyncEnumerable<ChatResponseUpdate> IChatClient.GetStreamingResponseAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
ChatOptions? options,
|
||||
[EnumeratorCancellation] CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await GetResponseAsync(messages, options, cancellationToken).ConfigureAwait(false);
|
||||
foreach (var update in response.ToChatResponseUpdates())
|
||||
{
|
||||
yield return update;
|
||||
}
|
||||
}
|
||||
|
||||
object? IChatClient.GetService(Type serviceType, object? serviceKey) => null;
|
||||
void IDisposable.Dispose() { }
|
||||
}
|
||||
}
|
||||
|
|
@ -352,13 +352,10 @@ public class McpClientToolTests : ClientServerTestBase
|
|||
|
||||
Assert.IsType<JsonElement>(result);
|
||||
var jsonElement = (JsonElement)result!;
|
||||
Assert.True(jsonElement.TryGetProperty("content", out var contentArray));
|
||||
Assert.Equal(JsonValueKind.Array, contentArray.ValueKind);
|
||||
Assert.Equal(1, contentArray.GetArrayLength());
|
||||
Assert.True(jsonElement.TryGetProperty("content", out var contentValue));
|
||||
Assert.Equal(JsonValueKind.Array, contentValue.ValueKind);
|
||||
|
||||
var firstContent = contentArray[0];
|
||||
Assert.True(firstContent.TryGetProperty("type", out var typeProperty));
|
||||
Assert.Equal("resource_link", typeProperty.GetString());
|
||||
Assert.Equal(1, contentValue.GetArrayLength());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Tests.Utils;
|
||||
|
|
@ -383,7 +383,7 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
{
|
||||
Model = "test-model",
|
||||
Role = Role.Assistant,
|
||||
Content = new TextContentBlock { Text = "Test response" },
|
||||
Content = [new TextContentBlock { Text = "Test response" }],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Tests.Utils;
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ public class DockerEverythingServerTests(ITestOutputHelper testOutputHelper) : L
|
|||
{
|
||||
Model = "test-model",
|
||||
Role = Role.Assistant,
|
||||
Content = new TextContentBlock { Text = "Test response" },
|
||||
Content = [new TextContentBlock { Text = "Test response" }],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using Microsoft.Extensions.AI;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
|
|
@ -125,4 +124,105 @@ public class ContentBlockTests
|
|||
var textBlock = Assert.IsType<TextContentBlock>(contentBlock);
|
||||
Assert.Contains("Sample text", textBlock.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToolResultContentBlock_WithError_SerializationRoundtrips()
|
||||
{
|
||||
ToolResultContentBlock toolResult = new()
|
||||
{
|
||||
ToolUseId = "call_123",
|
||||
Content = [new TextContentBlock { Text = "Error: City not found" }],
|
||||
IsError = true
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize<ContentBlock>(toolResult, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<ContentBlock>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
var result = Assert.IsType<ToolResultContentBlock>(deserialized);
|
||||
Assert.Equal("call_123", result.ToolUseId);
|
||||
Assert.True(result.IsError);
|
||||
Assert.Single(result.Content);
|
||||
var textBlock = Assert.IsType<TextContentBlock>(result.Content[0]);
|
||||
Assert.Equal("Error: City not found", textBlock.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToolResultContentBlock_WithStructuredContent_SerializationRoundtrips()
|
||||
{
|
||||
ToolResultContentBlock toolResult = new()
|
||||
{
|
||||
ToolUseId = "call_123",
|
||||
Content =
|
||||
[
|
||||
new TextContentBlock { Text = "Result data" }
|
||||
],
|
||||
StructuredContent = JsonElement.Parse("""{"temperature":18,"condition":"cloudy"}"""),
|
||||
IsError = false
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize<ContentBlock>(toolResult, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<ContentBlock>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
var result = Assert.IsType<ToolResultContentBlock>(deserialized);
|
||||
Assert.Equal("call_123", result.ToolUseId);
|
||||
Assert.Single(result.Content);
|
||||
var textBlock = Assert.IsType<TextContentBlock>(result.Content[0]);
|
||||
Assert.Equal("Result data", textBlock.Text);
|
||||
Assert.NotNull(result.StructuredContent);
|
||||
Assert.Equal(18, result.StructuredContent.Value.GetProperty("temperature").GetInt32());
|
||||
Assert.Equal("cloudy", result.StructuredContent.Value.GetProperty("condition").GetString());
|
||||
Assert.False(result.IsError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToolResultContentBlock_SerializationRoundTrip()
|
||||
{
|
||||
ToolResultContentBlock toolResult = new()
|
||||
{
|
||||
ToolUseId = "call_123",
|
||||
Content =
|
||||
[
|
||||
new TextContentBlock { Text = "Result data" },
|
||||
new ImageContentBlock { Data = "base64data", MimeType = "image/png" }
|
||||
],
|
||||
StructuredContent = JsonElement.Parse("""{"temperature":18,"condition":"cloudy"}"""),
|
||||
IsError = false
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize<ContentBlock>(toolResult, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<ContentBlock>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
var result = Assert.IsType<ToolResultContentBlock>(deserialized);
|
||||
Assert.Equal("call_123", result.ToolUseId);
|
||||
Assert.Equal(2, result.Content.Count);
|
||||
var textBlock = Assert.IsType<TextContentBlock>(result.Content[0]);
|
||||
Assert.Equal("Result data", textBlock.Text);
|
||||
var imageBlock = Assert.IsType<ImageContentBlock>(result.Content[1]);
|
||||
Assert.Equal("base64data", imageBlock.Data);
|
||||
Assert.Equal("image/png", imageBlock.MimeType);
|
||||
Assert.NotNull(result.StructuredContent);
|
||||
Assert.Equal(18, result.StructuredContent.Value.GetProperty("temperature").GetInt32());
|
||||
Assert.Equal("cloudy", result.StructuredContent.Value.GetProperty("condition").GetString());
|
||||
Assert.False(result.IsError);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToolUseContentBlock_SerializationRoundTrip()
|
||||
{
|
||||
ToolUseContentBlock toolUse = new()
|
||||
{
|
||||
Id = "call_abc123",
|
||||
Name = "get_weather",
|
||||
Input = JsonElement.Parse("""{"city":"Paris","units":"metric"}""")
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize<ContentBlock>(toolUse, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<ContentBlock>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
var result = Assert.IsType<ToolUseContentBlock>(deserialized);
|
||||
Assert.Equal("call_abc123", result.Id);
|
||||
Assert.Equal("get_weather", result.Name);
|
||||
Assert.Equal("Paris", result.Input.GetProperty("city").GetString());
|
||||
Assert.Equal("metric", result.Input.GetProperty("units").GetString());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,174 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public class CreateMessageRequestParamsTests
|
||||
{
|
||||
[Fact]
|
||||
public void WithTools_SerializationRoundtrips()
|
||||
{
|
||||
CreateMessageRequestParams requestParams = new()
|
||||
{
|
||||
MaxTokens = 1000,
|
||||
Messages =
|
||||
[
|
||||
new SamplingMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = [new TextContentBlock { Text = "What's the weather in Paris?" }]
|
||||
}
|
||||
],
|
||||
Tools =
|
||||
[
|
||||
new Tool
|
||||
{
|
||||
Name = "get_weather",
|
||||
Description = "Get weather for a city",
|
||||
InputSchema = JsonElement.Parse("""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"city": { "type": "string" }
|
||||
},
|
||||
"required": ["city"]
|
||||
}
|
||||
""")
|
||||
}
|
||||
],
|
||||
ToolChoice = new ToolChoice { Mode = "auto" }
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(requestParams, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CreateMessageRequestParams>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(1000, deserialized.MaxTokens);
|
||||
Assert.NotNull(deserialized.Messages);
|
||||
Assert.Single(deserialized.Messages);
|
||||
Assert.Equal(Role.User, deserialized.Messages[0].Role);
|
||||
Assert.Single(deserialized.Messages[0].Content);
|
||||
var textContent = Assert.IsType<TextContentBlock>(deserialized.Messages[0].Content[0]);
|
||||
Assert.Equal("What's the weather in Paris?", textContent.Text);
|
||||
Assert.NotNull(deserialized.Tools);
|
||||
Assert.Single(deserialized.Tools);
|
||||
Assert.Equal("get_weather", deserialized.Tools[0].Name);
|
||||
Assert.Equal("Get weather for a city", deserialized.Tools[0].Description);
|
||||
Assert.Equal("object", deserialized.Tools[0].InputSchema.GetProperty("type").GetString());
|
||||
Assert.True(deserialized.Tools[0].InputSchema.GetProperty("properties").TryGetProperty("city", out var cityProp));
|
||||
Assert.Equal("string", cityProp.GetProperty("type").GetString());
|
||||
Assert.Single(deserialized.Tools[0].InputSchema.GetProperty("required").EnumerateArray());
|
||||
Assert.Equal("city", deserialized.Tools[0].InputSchema.GetProperty("required")[0].GetString());
|
||||
Assert.NotNull(deserialized.ToolChoice);
|
||||
Assert.Equal("auto", deserialized.ToolChoice.Mode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithToolChoiceRequired_SerializationRoundtrips()
|
||||
{
|
||||
CreateMessageRequestParams requestParams = new()
|
||||
{
|
||||
MaxTokens = 1000,
|
||||
Messages =
|
||||
[
|
||||
new SamplingMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = [new TextContentBlock { Text = "What's the weather?" }]
|
||||
}
|
||||
],
|
||||
Tools =
|
||||
[
|
||||
new Tool
|
||||
{
|
||||
Name = "get_weather",
|
||||
Description = "Get weather for a city",
|
||||
InputSchema = JsonElement.Parse("""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": { "city": { "type": "string" } },
|
||||
"required": ["city"]
|
||||
}
|
||||
""")
|
||||
}
|
||||
],
|
||||
ToolChoice = new ToolChoice { Mode = "required" }
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(requestParams, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CreateMessageRequestParams>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(1000, deserialized.MaxTokens);
|
||||
Assert.NotNull(deserialized.Messages);
|
||||
Assert.Single(deserialized.Messages);
|
||||
Assert.Equal(Role.User, deserialized.Messages[0].Role);
|
||||
Assert.Single(deserialized.Messages[0].Content);
|
||||
var textContent = Assert.IsType<TextContentBlock>(deserialized.Messages[0].Content[0]);
|
||||
Assert.Equal("What's the weather?", textContent.Text);
|
||||
Assert.NotNull(deserialized.Tools);
|
||||
Assert.Single(deserialized.Tools);
|
||||
Assert.Equal("get_weather", deserialized.Tools[0].Name);
|
||||
Assert.Equal("Get weather for a city", deserialized.Tools[0].Description);
|
||||
Assert.Equal("object", deserialized.Tools[0].InputSchema.GetProperty("type").GetString());
|
||||
Assert.NotNull(deserialized.ToolChoice);
|
||||
Assert.Equal("required", deserialized.ToolChoice.Mode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithToolChoiceNone_SerializationRoundtrips()
|
||||
{
|
||||
CreateMessageRequestParams requestParams = new()
|
||||
{
|
||||
MaxTokens = 1000,
|
||||
Messages =
|
||||
[
|
||||
new SamplingMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = [new TextContentBlock { Text = "What's the weather in Paris?" }]
|
||||
}
|
||||
],
|
||||
Tools =
|
||||
[
|
||||
new Tool
|
||||
{
|
||||
Name = "get_weather",
|
||||
Description = "Get weather for a city",
|
||||
InputSchema = JsonElement.Parse("""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": { "city": { "type": "string" } },
|
||||
"required": ["city"]
|
||||
}
|
||||
""")
|
||||
}
|
||||
],
|
||||
ToolChoice = new ToolChoice { Mode = "none" }
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(requestParams, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CreateMessageRequestParams>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(1000, deserialized.MaxTokens);
|
||||
Assert.NotNull(deserialized.Messages);
|
||||
Assert.Single(deserialized.Messages);
|
||||
Assert.Equal(Role.User, deserialized.Messages[0].Role);
|
||||
Assert.Single(deserialized.Messages[0].Content);
|
||||
var textContent = Assert.IsType<TextContentBlock>(deserialized.Messages[0].Content[0]);
|
||||
Assert.Equal("What's the weather in Paris?", textContent.Text);
|
||||
Assert.NotNull(deserialized.Tools);
|
||||
Assert.Single(deserialized.Tools);
|
||||
Assert.Equal("get_weather", deserialized.Tools[0].Name);
|
||||
Assert.Equal("Get weather for a city", deserialized.Tools[0].Description);
|
||||
Assert.Equal("object", deserialized.Tools[0].InputSchema.GetProperty("type").GetString());
|
||||
Assert.NotNull(deserialized.ToolChoice);
|
||||
Assert.Equal("none", deserialized.ToolChoice.Mode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,247 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public class CreateMessageResultTests
|
||||
{
|
||||
[Fact]
|
||||
public void CreateMessageResult_WithSingleContent_Serializes()
|
||||
{
|
||||
CreateMessageResult result = new()
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
Model = "test-model",
|
||||
Content = [new TextContentBlock { Text = "Hello" }],
|
||||
StopReason = "endTurn"
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CreateMessageResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Single(deserialized.Content);
|
||||
Assert.IsType<TextContentBlock>(deserialized.Content[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateMessageResult_WithMultipleToolUses_Serializes()
|
||||
{
|
||||
CreateMessageResult result = new()
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
Model = "test-model",
|
||||
Content =
|
||||
[
|
||||
new ToolUseContentBlock
|
||||
{
|
||||
Id = "call_1",
|
||||
Name = "tool1",
|
||||
Input = JsonElement.Parse("""{}""")
|
||||
},
|
||||
new ToolUseContentBlock
|
||||
{
|
||||
Id = "call_2",
|
||||
Name = "tool2",
|
||||
Input = JsonElement.Parse("""{}""")
|
||||
}
|
||||
],
|
||||
StopReason = "toolUse"
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CreateMessageResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(2, deserialized.Content.Count);
|
||||
Assert.All(deserialized.Content, c => Assert.IsType<ToolUseContentBlock>(c));
|
||||
Assert.Equal("call_1", ((ToolUseContentBlock)deserialized.Content[0]).Id);
|
||||
Assert.Equal("call_2", ((ToolUseContentBlock)deserialized.Content[1]).Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateMessageResult_WithMixedContent_Serializes()
|
||||
{
|
||||
CreateMessageResult result = new()
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
Model = "test-model",
|
||||
Content =
|
||||
[
|
||||
new TextContentBlock { Text = "Let me check that." },
|
||||
new ToolUseContentBlock
|
||||
{
|
||||
Id = "call_1",
|
||||
Name = "tool1",
|
||||
Input = JsonElement.Parse("""{}""")
|
||||
}
|
||||
],
|
||||
StopReason = "toolUse"
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CreateMessageResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(2, deserialized.Content.Count);
|
||||
Assert.IsType<TextContentBlock>(deserialized.Content[0]);
|
||||
Assert.IsType<ToolUseContentBlock>(deserialized.Content[1]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateMessageResult_EmptyContent_AllowedButUnusual()
|
||||
{
|
||||
CreateMessageResult result = new()
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
Model = "test-model",
|
||||
Content = [],
|
||||
StopReason = "endTurn"
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CreateMessageResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Empty(deserialized.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateMessageResult_WithImageContent_Serializes()
|
||||
{
|
||||
CreateMessageResult result = new()
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
Model = "test-model",
|
||||
Content =
|
||||
[
|
||||
new ImageContentBlock
|
||||
{
|
||||
Data = Convert.ToBase64String([1, 2, 3, 4, 5]),
|
||||
MimeType = "image/png"
|
||||
}
|
||||
],
|
||||
StopReason = "endTurn"
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CreateMessageResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Single(deserialized.Content);
|
||||
var imageBlock = Assert.IsType<ImageContentBlock>(deserialized.Content[0]);
|
||||
Assert.Equal("image/png", imageBlock.MimeType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateMessageResult_RoundTripWithAllFields()
|
||||
{
|
||||
CreateMessageResult original = new()
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
Model = "claude-3-sonnet",
|
||||
Content =
|
||||
[
|
||||
new TextContentBlock { Text = "I'll help you with that." },
|
||||
new ToolUseContentBlock
|
||||
{
|
||||
Id = "call_xyz",
|
||||
Name = "calculator",
|
||||
Input = JsonElement.Parse("""{"operation":"add","a":5,"b":3}""")
|
||||
}
|
||||
],
|
||||
StopReason = "toolUse",
|
||||
Meta = (JsonObject)JsonNode.Parse("""{"custom":"metadata"}""")!
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(original, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CreateMessageResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(Role.Assistant, deserialized.Role);
|
||||
Assert.Equal("claude-3-sonnet", deserialized.Model);
|
||||
Assert.Equal(2, deserialized.Content.Count);
|
||||
Assert.Equal("toolUse", deserialized.StopReason);
|
||||
Assert.NotNull(deserialized.Meta);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateMessageResult_WithToolUse_SerializationRoundtrips()
|
||||
{
|
||||
CreateMessageResult result = new()
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
Model = "test-model",
|
||||
Content =
|
||||
[
|
||||
new ToolUseContentBlock
|
||||
{
|
||||
Id = "call_123",
|
||||
Name = "get_weather",
|
||||
Input = JsonElement.Parse("""{"city":"Paris"}""")
|
||||
}
|
||||
],
|
||||
StopReason = "toolUse"
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CreateMessageResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(Role.Assistant, deserialized.Role);
|
||||
Assert.Equal("test-model", deserialized.Model);
|
||||
Assert.Equal("toolUse", deserialized.StopReason);
|
||||
Assert.Single(deserialized.Content);
|
||||
|
||||
var toolUse = Assert.IsType<ToolUseContentBlock>(deserialized.Content[0]);
|
||||
Assert.Equal("call_123", toolUse.Id);
|
||||
Assert.Equal("get_weather", toolUse.Name);
|
||||
Assert.Equal("Paris", toolUse.Input.GetProperty("city").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreateMessageResult_WithParallelToolUses_SerializationRoundtrips()
|
||||
{
|
||||
CreateMessageResult result = new()
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
Model = "test-model",
|
||||
Content =
|
||||
[
|
||||
new ToolUseContentBlock
|
||||
{
|
||||
Id = "call_abc123",
|
||||
Name = "get_weather",
|
||||
Input = JsonElement.Parse("""{"city":"Paris"}""")
|
||||
},
|
||||
new ToolUseContentBlock
|
||||
{
|
||||
Id = "call_def456",
|
||||
Name = "get_weather",
|
||||
Input = JsonElement.Parse("""{"city":"London"}""")
|
||||
}
|
||||
],
|
||||
StopReason = "toolUse"
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<CreateMessageResult>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(Role.Assistant, deserialized.Role);
|
||||
Assert.Equal("test-model", deserialized.Model);
|
||||
Assert.Equal("toolUse", deserialized.StopReason);
|
||||
Assert.Equal(2, deserialized.Content.Count);
|
||||
|
||||
var toolUse1 = Assert.IsType<ToolUseContentBlock>(deserialized.Content[0]);
|
||||
Assert.Equal("call_abc123", toolUse1.Id);
|
||||
Assert.Equal("get_weather", toolUse1.Name);
|
||||
Assert.Equal("Paris", toolUse1.Input.GetProperty("city").GetString());
|
||||
|
||||
var toolUse2 = Assert.IsType<ToolUseContentBlock>(deserialized.Content[1]);
|
||||
Assert.Equal("call_def456", toolUse2.Id);
|
||||
Assert.Equal("get_weather", toolUse2.Name);
|
||||
Assert.Equal("London", toolUse2.Input.GetProperty("city").GetString());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public class SamplingMessageTests
|
||||
{
|
||||
[Fact]
|
||||
public void WithToolResults_SerializationRoundtrips()
|
||||
{
|
||||
SamplingMessage message = new()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content =
|
||||
[
|
||||
new ToolResultContentBlock
|
||||
{
|
||||
ToolUseId = "call_123",
|
||||
Content =
|
||||
[
|
||||
new TextContentBlock { Text = "Weather in Paris: 18°C, partly cloudy" }
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(message, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<SamplingMessage>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(Role.User, deserialized.Role);
|
||||
Assert.Single(deserialized.Content);
|
||||
|
||||
var toolResult = Assert.IsType<ToolResultContentBlock>(deserialized.Content[0]);
|
||||
Assert.Equal("call_123", toolResult.ToolUseId);
|
||||
Assert.Single(toolResult.Content);
|
||||
|
||||
var textBlock = Assert.IsType<TextContentBlock>(toolResult.Content[0]);
|
||||
Assert.Equal("Weather in Paris: 18°C, partly cloudy", textBlock.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithMultipleToolResults_SerializationRoundtrips()
|
||||
{
|
||||
SamplingMessage message = new()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content =
|
||||
[
|
||||
new ToolResultContentBlock
|
||||
{
|
||||
ToolUseId = "call_abc123",
|
||||
Content = [new TextContentBlock { Text = "Weather in Paris: 18°C, partly cloudy" }]
|
||||
},
|
||||
new ToolResultContentBlock
|
||||
{
|
||||
ToolUseId = "call_def456",
|
||||
Content = [new TextContentBlock { Text = "Weather in London: 15°C, rainy" }]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(message, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<SamplingMessage>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(Role.User, deserialized.Role);
|
||||
Assert.Equal(2, deserialized.Content.Count);
|
||||
|
||||
var toolResult1 = Assert.IsType<ToolResultContentBlock>(deserialized.Content[0]);
|
||||
Assert.Equal("call_abc123", toolResult1.ToolUseId);
|
||||
Assert.Single(toolResult1.Content);
|
||||
var textBlock1 = Assert.IsType<TextContentBlock>(toolResult1.Content[0]);
|
||||
Assert.Equal("Weather in Paris: 18°C, partly cloudy", textBlock1.Text);
|
||||
|
||||
var toolResult2 = Assert.IsType<ToolResultContentBlock>(deserialized.Content[1]);
|
||||
Assert.Equal("call_def456", toolResult2.ToolUseId);
|
||||
Assert.Single(toolResult2.Content);
|
||||
var textBlock2 = Assert.IsType<TextContentBlock>(toolResult2.Content[0]);
|
||||
Assert.Equal("Weather in London: 15°C, rainy", textBlock2.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WithToolResultOnly_SerializationRoundtrips()
|
||||
{
|
||||
SamplingMessage message = new()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content =
|
||||
[
|
||||
new ToolResultContentBlock
|
||||
{
|
||||
ToolUseId = "call_123",
|
||||
Content = [new TextContentBlock { Text = "Result" }]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(message, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<SamplingMessage>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(Role.User, deserialized.Role);
|
||||
Assert.Single(deserialized.Content);
|
||||
var toolResult = Assert.IsType<ToolResultContentBlock>(deserialized.Content[0]);
|
||||
Assert.Equal("call_123", toolResult.ToolUseId);
|
||||
Assert.Single(toolResult.Content);
|
||||
var textBlock = Assert.IsType<TextContentBlock>(toolResult.Content[0]);
|
||||
Assert.Equal("Result", textBlock.Text);
|
||||
}
|
||||
}
|
||||
30
tests/ModelContextProtocol.Tests/Protocol/ToolChoiceTests.cs
Normal file
30
tests/ModelContextProtocol.Tests/Protocol/ToolChoiceTests.cs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
using ModelContextProtocol.Protocol;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Protocol;
|
||||
|
||||
public class ToolChoiceTests
|
||||
{
|
||||
[Fact]
|
||||
public void DefaultModeIsNull()
|
||||
{
|
||||
Assert.Null(new ToolChoice().Mode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("none")]
|
||||
[InlineData("required")]
|
||||
[InlineData("auto")]
|
||||
[InlineData("something_custom")]
|
||||
public void SerializesWithMode(string? mode)
|
||||
{
|
||||
ToolChoice toolChoice = new() { Mode = mode };
|
||||
|
||||
var json = JsonSerializer.Serialize(toolChoice, McpJsonUtilities.DefaultOptions);
|
||||
var deserialized = JsonSerializer.Deserialize<ToolChoice>(json, McpJsonUtilities.DefaultOptions);
|
||||
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal(mode, deserialized.Mode);
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ public class McpServerExtensionsTests
|
|||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(async () => await server.SampleAsync(
|
||||
new CreateMessageRequestParams
|
||||
{
|
||||
Messages = [new SamplingMessage { Role = Role.User, Content = new TextContentBlock { Text = "hi" } }],
|
||||
Messages = [new SamplingMessage { Role = Role.User, Content = [new TextContentBlock { Text = "hi" }] }],
|
||||
MaxTokens = 1000
|
||||
},
|
||||
TestContext.Current.CancellationToken));
|
||||
|
|
@ -80,7 +80,7 @@ public class McpServerExtensionsTests
|
|||
|
||||
var resultPayload = new CreateMessageResult
|
||||
{
|
||||
Content = new TextContentBlock { Text = "resp" },
|
||||
Content = [new TextContentBlock { Text = "resp" }],
|
||||
Model = "test-model",
|
||||
Role = Role.Assistant,
|
||||
StopReason = "endTurn",
|
||||
|
|
@ -101,13 +101,13 @@ public class McpServerExtensionsTests
|
|||
|
||||
var result = await server.SampleAsync(new CreateMessageRequestParams
|
||||
{
|
||||
Messages = [new SamplingMessage { Role = Role.User, Content = new TextContentBlock { Text = "hi" } }],
|
||||
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).Text);
|
||||
Assert.Equal("resp", Assert.IsType<TextContentBlock>(result.Content[0]).Text);
|
||||
mockServer.Verify(s => s.SendRequestAsync(It.IsAny<JsonRpcRequest>(), It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ public class McpServerExtensionsTests
|
|||
|
||||
var resultPayload = new CreateMessageResult
|
||||
{
|
||||
Content = new TextContentBlock { Text = "resp" },
|
||||
Content = [new TextContentBlock { Text = "resp" }],
|
||||
Model = "test-model",
|
||||
Role = Role.Assistant,
|
||||
StopReason = "endTurn",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
using ModelContextProtocol.Tests.Utils;
|
||||
|
|
@ -802,12 +802,12 @@ public class McpServerTests : LoggedTest
|
|||
Assert.Equal($"You are a helpful assistant.{Environment.NewLine}More system stuff.", rp.SystemPrompt);
|
||||
|
||||
Assert.Equal(2, rp.Messages.Count);
|
||||
Assert.Equal("I am going to France.", Assert.IsType<TextContentBlock>(rp.Messages[0].Content).Text);
|
||||
Assert.Equal("What is the most famous tower in Paris?", Assert.IsType<TextContentBlock>(rp.Messages[1].Content).Text);
|
||||
Assert.Equal("I am going to France.", Assert.IsType<TextContentBlock>(Assert.Single(rp.Messages[0].Content)).Text);
|
||||
Assert.Equal("What is the most famous tower in Paris?", Assert.IsType<TextContentBlock>(Assert.Single(rp.Messages[1].Content)).Text);
|
||||
|
||||
CreateMessageResult result = new()
|
||||
{
|
||||
Content = new TextContentBlock { Text = "The Eiffel Tower." },
|
||||
Content = [new TextContentBlock { Text = "The Eiffel Tower." }],
|
||||
Model = "amazingmodel",
|
||||
Role = Role.Assistant,
|
||||
StopReason = "endTurn",
|
||||
|
|
|
|||
Loading…
Reference in a new issue