Allow JsonSerializerOptions parameters for user-defined and inputs and hardcode everything else to the source generator. (#182)
* Allow user-defined JsonSerializerOptions for user-defined and inputs and hardcode everything else to the source generator. * Add validation for user-defined JsonSerializerOptions. * Remove out of date comment. * Address feedback and add tests
This commit is contained in:
parent
4b38d3e71b
commit
f1af251c32
12 changed files with 632 additions and 372 deletions
|
|
@ -1,9 +1,12 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Protocol.Transport;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Server;
|
||||
using ModelContextProtocol.Tests.Utils;
|
||||
using System.IO.Pipelines;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Client;
|
||||
|
||||
|
|
@ -72,7 +75,7 @@ public class McpClientExtensionsTests : LoggedTest
|
|||
{
|
||||
IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var tools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.Equal(12, tools.Count);
|
||||
var echo = tools.Single(t => t.Name == "Method4");
|
||||
var result = await echo.InvokeAsync(new Dictionary<string, object?>() { ["i"] = 42 }, TestContext.Current.CancellationToken);
|
||||
|
|
@ -98,7 +101,7 @@ public class McpClientExtensionsTests : LoggedTest
|
|||
{
|
||||
IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await foreach (var tool in client.EnumerateToolsAsync(TestContext.Current.CancellationToken))
|
||||
await foreach (var tool in client.EnumerateToolsAsync(cancellationToken: TestContext.Current.CancellationToken))
|
||||
{
|
||||
if (tool.Name == "Method4")
|
||||
{
|
||||
|
|
@ -110,4 +113,62 @@ public class McpClientExtensionsTests : LoggedTest
|
|||
|
||||
Assert.Fail("Couldn't find target method");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EnumerateToolsAsync_FlowsJsonSerializerOptions()
|
||||
{
|
||||
JsonSerializerOptions options = new(JsonSerializerOptions.Default);
|
||||
IMcpClient client = await CreateMcpClientForServer();
|
||||
bool hasTools = false;
|
||||
|
||||
await foreach (var tool in client.EnumerateToolsAsync(options, TestContext.Current.CancellationToken))
|
||||
{
|
||||
Assert.Same(options, tool.JsonSerializerOptions);
|
||||
hasTools = true;
|
||||
}
|
||||
|
||||
foreach (var tool in await client.ListToolsAsync(options, TestContext.Current.CancellationToken))
|
||||
{
|
||||
Assert.Same(options, tool.JsonSerializerOptions);
|
||||
}
|
||||
|
||||
Assert.True(hasTools);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EnumerateToolsAsync_HonorsJsonSerializerOptions()
|
||||
{
|
||||
JsonSerializerOptions emptyOptions = new() { TypeInfoResolver = JsonTypeInfoResolver.Combine() };
|
||||
IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var tool = (await client.ListToolsAsync(emptyOptions, TestContext.Current.CancellationToken)).First();
|
||||
await Assert.ThrowsAsync<NotSupportedException>(() => tool.InvokeAsync(new Dictionary<string, object?> { ["i"] = 42 }, TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendRequestAsync_HonorsJsonSerializerOptions()
|
||||
{
|
||||
JsonSerializerOptions emptyOptions = new() { TypeInfoResolver = JsonTypeInfoResolver.Combine() };
|
||||
IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await Assert.ThrowsAsync<NotSupportedException>(() => client.SendRequestAsync<CallToolRequestParams, CallToolResponse>("Method4", new() { Name = "tool" }, emptyOptions, cancellationToken: TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendNotificationAsync_HonorsJsonSerializerOptions()
|
||||
{
|
||||
JsonSerializerOptions emptyOptions = new() { TypeInfoResolver = JsonTypeInfoResolver.Combine() };
|
||||
IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await Assert.ThrowsAsync<NotSupportedException>(() => client.SendNotificationAsync("Method4", new { Value = 42 }, emptyOptions, cancellationToken: TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetPromptsAsync_HonorsJsonSerializerOptions()
|
||||
{
|
||||
JsonSerializerOptions emptyOptions = new() { TypeInfoResolver = JsonTypeInfoResolver.Combine() };
|
||||
IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await Assert.ThrowsAsync<NotSupportedException>(() => client.GetPromptAsync("Prompt", new Dictionary<string, object?> { ["i"] = 42 }, emptyOptions, cancellationToken: TestContext.Current.CancellationToken));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
using System.Threading.Channels;
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Protocol.Messages;
|
||||
using ModelContextProtocol.Protocol.Transport;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Client;
|
||||
|
||||
|
|
@ -206,16 +207,16 @@ public class McpClientFactoryTests
|
|||
_channel.Writer.TryWrite(new JsonRpcResponse
|
||||
{
|
||||
Id = ((JsonRpcRequest)message).Id,
|
||||
Result = new InitializeResult()
|
||||
Result = JsonSerializer.SerializeToNode(new InitializeResult
|
||||
{
|
||||
Capabilities = new ServerCapabilities(),
|
||||
ProtocolVersion = "2024-11-05",
|
||||
ServerInfo = new Implementation()
|
||||
ServerInfo = new Implementation
|
||||
{
|
||||
Name = "NopTransport",
|
||||
Version = "1.0.0"
|
||||
},
|
||||
}
|
||||
}),
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
|
||||
// act
|
||||
await using var client = await _fixture.CreateClientAsync(clientId);
|
||||
var tools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// assert
|
||||
Assert.NotEmpty(tools);
|
||||
|
|
@ -88,7 +88,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
{
|
||||
["message"] = "Hello MCP!"
|
||||
},
|
||||
TestContext.Current.CancellationToken
|
||||
cancellationToken: TestContext.Current.CancellationToken
|
||||
);
|
||||
|
||||
// assert
|
||||
|
|
@ -106,7 +106,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
|
||||
// act
|
||||
await using var client = await _fixture.CreateClientAsync(clientId);
|
||||
var aiFunctions = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
var aiFunctions = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
var echo = aiFunctions.Single(t => t.Name == "echo");
|
||||
var result = await echo.InvokeAsync([new KeyValuePair<string, object?>("message", "Hello MCP!")], TestContext.Current.CancellationToken);
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
|
||||
// act
|
||||
await using var client = await _fixture.CreateClientAsync(clientId);
|
||||
var result = await client.GetPromptAsync("simple_prompt", null, TestContext.Current.CancellationToken);
|
||||
var result = await client.GetPromptAsync("simple_prompt", null, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// assert
|
||||
Assert.NotNull(result);
|
||||
|
|
@ -160,7 +160,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
{ "temperature", "0.7" },
|
||||
{ "style", "formal" }
|
||||
};
|
||||
var result = await client.GetPromptAsync("complex_prompt", arguments, TestContext.Current.CancellationToken);
|
||||
var result = await client.GetPromptAsync("complex_prompt", arguments, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// assert
|
||||
Assert.NotNull(result);
|
||||
|
|
@ -176,7 +176,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
// act
|
||||
await using var client = await _fixture.CreateClientAsync(clientId);
|
||||
await Assert.ThrowsAsync<McpClientException>(() =>
|
||||
client.GetPromptAsync("non_existent_prompt", null, TestContext.Current.CancellationToken));
|
||||
client.GetPromptAsync("non_existent_prompt", null, cancellationToken: TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -259,7 +259,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
await using var client = await _fixture.CreateClientAsync(clientId);
|
||||
client.AddNotificationHandler(NotificationMethods.ResourceUpdatedNotification, (notification) =>
|
||||
{
|
||||
var notificationParams = JsonSerializer.Deserialize<ResourceUpdatedNotificationParams>(notification.Params!.ToString() ?? string.Empty);
|
||||
var notificationParams = JsonSerializer.Deserialize<ResourceUpdatedNotificationParams>(notification.Params);
|
||||
tcs.TrySetResult(true);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
|
@ -280,7 +280,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
await using var client = await _fixture.CreateClientAsync(clientId);
|
||||
client.AddNotificationHandler(NotificationMethods.ResourceUpdatedNotification, (notification) =>
|
||||
{
|
||||
var notificationParams = JsonSerializer.Deserialize<ResourceUpdatedNotificationParams>(notification.Params!.ToString() ?? string.Empty);
|
||||
var notificationParams = JsonSerializer.Deserialize<ResourceUpdatedNotificationParams>(notification.Params);
|
||||
receivedNotification.TrySetResult(true);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
|
@ -381,7 +381,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
["prompt"] = "Test prompt",
|
||||
["maxTokens"] = 100
|
||||
},
|
||||
TestContext.Current.CancellationToken);
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// assert
|
||||
Assert.NotNull(result);
|
||||
|
|
@ -429,7 +429,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
|
||||
// Verify we can send notifications without errors
|
||||
await client.SendNotificationAsync(NotificationMethods.RootsUpdatedNotification, cancellationToken: TestContext.Current.CancellationToken);
|
||||
await client.SendNotificationAsync("test/notification", new { test = true }, TestContext.Current.CancellationToken);
|
||||
await client.SendNotificationAsync("test/notification", new { test = true }, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// assert
|
||||
// no response to check, if no exception is thrown, it's a success
|
||||
|
|
@ -467,7 +467,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
var result = await client.CallToolAsync(
|
||||
"read_graph",
|
||||
new Dictionary<string, object?>(),
|
||||
TestContext.Current.CancellationToken);
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// assert
|
||||
Assert.NotNull(result);
|
||||
|
|
@ -485,7 +485,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
_fixture.EverythingServerConfig,
|
||||
_fixture.DefaultOptions,
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
var mappedTools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
var mappedTools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Create the chat client.
|
||||
using IChatClient chatClient = new OpenAIClient(s_openAIKey).AsChatClient("gpt-4o-mini")
|
||||
|
|
@ -529,7 +529,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
var result = await client.CallToolAsync("sampleLLM", new Dictionary<string, object?>()
|
||||
{
|
||||
["prompt"] = "In just a few words, what is the most famous tower in Paris?",
|
||||
}, TestContext.Current.CancellationToken);
|
||||
}, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEmpty(result.Content);
|
||||
|
|
@ -556,8 +556,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
|||
await using var client = await _fixture.CreateClientAsync(clientId);
|
||||
client.AddNotificationHandler(NotificationMethods.LoggingMessageNotification, (notification) =>
|
||||
{
|
||||
var loggingMessageNotificationParameters = JsonSerializer.Deserialize<LoggingMessageNotificationParams>(notification.Params!.ToString() ?? string.Empty,
|
||||
jsonSerializerOptions);
|
||||
var loggingMessageNotificationParameters = JsonSerializer.Deserialize<LoggingMessageNotificationParams>(notification.Params, jsonSerializerOptions);
|
||||
if (loggingMessageNotificationParameters is not null)
|
||||
{
|
||||
receivedNotification.TrySetResult(true);
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ public class McpServerBuilderExtensionsPromptsTests : LoggedTest, IAsyncDisposab
|
|||
var prompt = prompts.First(t => t.Name == nameof(SimplePrompts.ReturnsChatMessages));
|
||||
Assert.Equal("Returns chat messages", prompt.Description);
|
||||
|
||||
var result = await prompt.GetAsync(new Dictionary<string, object?>() { ["message"] = "hello" }, TestContext.Current.CancellationToken);
|
||||
var result = await prompt.GetAsync(new Dictionary<string, object?>() { ["message"] = "hello" }, cancellationToken: TestContext.Current.CancellationToken);
|
||||
var chatMessages = result.ToChatMessages();
|
||||
|
||||
Assert.NotNull(chatMessages);
|
||||
|
|
|
|||
|
|
@ -172,7 +172,7 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
{
|
||||
IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var tools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.Equal(16, tools.Count);
|
||||
|
||||
McpClientTool echoTool = tools.First(t => t.Name == "Echo");
|
||||
|
|
@ -217,7 +217,7 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
loggerFactory: LoggerFactory,
|
||||
cancellationToken: TestContext.Current.CancellationToken))
|
||||
{
|
||||
var tools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.Equal(16, tools.Count);
|
||||
|
||||
McpClientTool echoTool = tools.First(t => t.Name == "Echo");
|
||||
|
|
@ -244,7 +244,7 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
{
|
||||
IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var tools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.Equal(16, tools.Count);
|
||||
|
||||
Channel<JsonRpcNotification> listChanged = Channel.CreateUnbounded<JsonRpcNotification>();
|
||||
|
|
@ -265,7 +265,7 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
serverTools.Add(newTool);
|
||||
await notificationRead;
|
||||
|
||||
tools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.Equal(17, tools.Count);
|
||||
Assert.Contains(tools, t => t.Name == "NewTool");
|
||||
|
||||
|
|
@ -274,7 +274,7 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
serverTools.Remove(newTool);
|
||||
await notificationRead;
|
||||
|
||||
tools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.Equal(16, tools.Count);
|
||||
Assert.DoesNotContain(tools, t => t.Name == "NewTool");
|
||||
}
|
||||
|
|
@ -287,7 +287,7 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
var result = await client.CallToolAsync(
|
||||
"Echo",
|
||||
new Dictionary<string, object?>() { ["message"] = "Peter" },
|
||||
TestContext.Current.CancellationToken);
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Content);
|
||||
|
|
@ -305,7 +305,7 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
var result = await client.CallToolAsync(
|
||||
"EchoArray",
|
||||
new Dictionary<string, object?>() { ["message"] = "Peter" },
|
||||
TestContext.Current.CancellationToken);
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(result.Content);
|
||||
Assert.NotEmpty(result.Content);
|
||||
|
|
@ -536,7 +536,7 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
{
|
||||
IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var tools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.NotNull(tools);
|
||||
Assert.NotEmpty(tools);
|
||||
|
|
@ -612,26 +612,25 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
IMcpClient client = await CreateMcpClientForServer();
|
||||
client.AddNotificationHandler(NotificationMethods.ProgressNotification, notification =>
|
||||
{
|
||||
ProgressNotification pn = JsonSerializer.Deserialize<ProgressNotification>((JsonElement)notification.Params!)!;
|
||||
ProgressNotification pn = JsonSerializer.Deserialize<ProgressNotification>(notification.Params)!;
|
||||
notifications.Enqueue(pn);
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
var tools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(tools);
|
||||
Assert.NotEmpty(tools);
|
||||
|
||||
McpClientTool progressTool = tools.First(t => t.Name == nameof(EchoTool.SendsProgressNotifications));
|
||||
|
||||
var result = await client.SendRequestAsync<CallToolResponse>(new JsonRpcRequest()
|
||||
{
|
||||
Method = RequestMethods.ToolsCall,
|
||||
Params = new CallToolRequestParams()
|
||||
var result = await client.SendRequestAsync<CallToolRequestParams, CallToolResponse>(
|
||||
RequestMethods.ToolsCall,
|
||||
new CallToolRequestParams
|
||||
{
|
||||
Name = progressTool.ProtocolTool.Name,
|
||||
Meta = new() { ProgressToken = new("abc123") },
|
||||
},
|
||||
}, TestContext.Current.CancellationToken);
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Contains("done", JsonSerializer.Serialize(result));
|
||||
SpinWait.SpinUntil(() => notifications.Count == 10, TimeSpan.FromSeconds(10));
|
||||
|
|
@ -652,18 +651,17 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
{
|
||||
IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var tools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(tools);
|
||||
Assert.NotEmpty(tools);
|
||||
McpClientTool cancelableTool = tools.First(t => t.Name == nameof(EchoTool.InfiniteCancelableOperation));
|
||||
|
||||
var requestId = new RequestId(Guid.NewGuid().ToString());
|
||||
var invokeTask = client.SendRequestAsync<CallToolResponse>(new JsonRpcRequest()
|
||||
{
|
||||
Method = RequestMethods.ToolsCall,
|
||||
Id = requestId,
|
||||
Params = new CallToolRequestParams() { Name = cancelableTool.ProtocolTool.Name },
|
||||
}, TestContext.Current.CancellationToken);
|
||||
var invokeTask = client.SendRequestAsync<CallToolRequestParams, CallToolResponse>(
|
||||
RequestMethods.ToolsCall,
|
||||
new CallToolRequestParams { Name = cancelableTool.ProtocolTool.Name },
|
||||
requestId: requestId,
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
await client.SendNotificationAsync(
|
||||
NotificationMethods.CancelledNotification,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public class DiagnosticTests
|
|||
{
|
||||
await RunConnected(async (client, server) =>
|
||||
{
|
||||
var tools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(tools);
|
||||
Assert.NotEmpty(tools);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ using ModelContextProtocol.Protocol.Types;
|
|||
using ModelContextProtocol.Server;
|
||||
using ModelContextProtocol.Tests.Utils;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Server;
|
||||
|
||||
|
|
@ -171,7 +173,8 @@ public class McpServerTests : LoggedTest
|
|||
configureOptions: null,
|
||||
assertResult: response =>
|
||||
{
|
||||
Assert.IsType<PingResult>(response);
|
||||
JsonObject jObj = Assert.IsType<JsonObject>(response);
|
||||
Assert.Empty(jObj);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -184,9 +187,8 @@ public class McpServerTests : LoggedTest
|
|||
configureOptions: null,
|
||||
assertResult: response =>
|
||||
{
|
||||
Assert.IsType<InitializeResult>(response);
|
||||
|
||||
var result = (InitializeResult)response;
|
||||
var result = JsonSerializer.Deserialize<InitializeResult>(response);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("TestServer", result.ServerInfo.Name);
|
||||
Assert.Equal("1.0", result.ServerInfo.Version);
|
||||
Assert.Equal("2024", result.ProtocolVersion);
|
||||
|
|
@ -202,10 +204,8 @@ public class McpServerTests : LoggedTest
|
|||
configureOptions: null,
|
||||
assertResult: response =>
|
||||
{
|
||||
Assert.IsType<CompleteResult>(response);
|
||||
|
||||
var result = (CompleteResult)response;
|
||||
Assert.NotNull(result.Completion);
|
||||
var result = JsonSerializer.Deserialize<CompleteResult>(response);
|
||||
Assert.NotNull(result?.Completion);
|
||||
Assert.Empty(result.Completion.Values);
|
||||
Assert.Equal(0, result.Completion.Total);
|
||||
Assert.False(result.Completion.HasMore);
|
||||
|
|
@ -233,10 +233,8 @@ public class McpServerTests : LoggedTest
|
|||
},
|
||||
assertResult: response =>
|
||||
{
|
||||
Assert.IsType<CompleteResult>(response);
|
||||
|
||||
var result = (CompleteResult)response;
|
||||
Assert.NotNull(result.Completion);
|
||||
CompleteResult? result = JsonSerializer.Deserialize<CompleteResult>(response);
|
||||
Assert.NotNull(result?.Completion);
|
||||
Assert.NotEmpty(result.Completion.Values);
|
||||
Assert.Equal("test", result.Completion.Values[0]);
|
||||
Assert.Equal(2, result.Completion.Total);
|
||||
|
|
@ -273,10 +271,8 @@ public class McpServerTests : LoggedTest
|
|||
configureOptions: null,
|
||||
assertResult: response =>
|
||||
{
|
||||
Assert.IsType<ListResourceTemplatesResult>(response);
|
||||
|
||||
var result = (ListResourceTemplatesResult)response;
|
||||
Assert.NotNull(result.ResourceTemplates);
|
||||
var result = JsonSerializer.Deserialize<ListResourceTemplatesResult>(response);
|
||||
Assert.NotNull(result?.ResourceTemplates);
|
||||
Assert.NotEmpty(result.ResourceTemplates);
|
||||
Assert.Equal("test", result.ResourceTemplates[0].UriTemplate);
|
||||
});
|
||||
|
|
@ -304,10 +300,8 @@ public class McpServerTests : LoggedTest
|
|||
configureOptions: null,
|
||||
assertResult: response =>
|
||||
{
|
||||
Assert.IsType<ListResourcesResult>(response);
|
||||
|
||||
var result = (ListResourcesResult)response;
|
||||
Assert.NotNull(result.Resources);
|
||||
var result = JsonSerializer.Deserialize<ListResourcesResult>(response);
|
||||
Assert.NotNull(result?.Resources);
|
||||
Assert.NotEmpty(result.Resources);
|
||||
Assert.Equal("test", result.Resources[0].Uri);
|
||||
});
|
||||
|
|
@ -331,7 +325,7 @@ public class McpServerTests : LoggedTest
|
|||
{
|
||||
return Task.FromResult(new ReadResourceResult
|
||||
{
|
||||
Contents = [new TextResourceContents() { Text = "test" }]
|
||||
Contents = [new TextResourceContents { Text = "test" }]
|
||||
});
|
||||
},
|
||||
ListResourcesHandler = (request, ct) => throw new NotImplementedException(),
|
||||
|
|
@ -341,10 +335,8 @@ public class McpServerTests : LoggedTest
|
|||
configureOptions: null,
|
||||
assertResult: response =>
|
||||
{
|
||||
Assert.IsType<ReadResourceResult>(response);
|
||||
|
||||
var result = (ReadResourceResult)response;
|
||||
Assert.NotNull(result.Contents);
|
||||
var result = JsonSerializer.Deserialize<ReadResourceResult>(response);
|
||||
Assert.NotNull(result?.Contents);
|
||||
Assert.NotEmpty(result.Contents);
|
||||
|
||||
TextResourceContents textResource = Assert.IsType<TextResourceContents>(result.Contents[0]);
|
||||
|
|
@ -380,10 +372,8 @@ public class McpServerTests : LoggedTest
|
|||
configureOptions: null,
|
||||
assertResult: response =>
|
||||
{
|
||||
Assert.IsType<ListPromptsResult>(response);
|
||||
|
||||
var result = (ListPromptsResult)response;
|
||||
Assert.NotNull(result.Prompts);
|
||||
var result = JsonSerializer.Deserialize<ListPromptsResult>(response);
|
||||
Assert.NotNull(result?.Prompts);
|
||||
Assert.NotEmpty(result.Prompts);
|
||||
Assert.Equal("test", result.Prompts[0].Name);
|
||||
});
|
||||
|
|
@ -411,9 +401,8 @@ public class McpServerTests : LoggedTest
|
|||
configureOptions: null,
|
||||
assertResult: response =>
|
||||
{
|
||||
Assert.IsType<GetPromptResult>(response);
|
||||
|
||||
var result = (GetPromptResult)response;
|
||||
var result = JsonSerializer.Deserialize<GetPromptResult>(response);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("test", result.Description);
|
||||
});
|
||||
}
|
||||
|
|
@ -446,9 +435,8 @@ public class McpServerTests : LoggedTest
|
|||
configureOptions: null,
|
||||
assertResult: response =>
|
||||
{
|
||||
Assert.IsType<ListToolsResult>(response);
|
||||
|
||||
var result = (ListToolsResult)response;
|
||||
var result = JsonSerializer.Deserialize<ListToolsResult>(response);
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEmpty(result.Tools);
|
||||
Assert.Equal("test", result.Tools[0].Name);
|
||||
});
|
||||
|
|
@ -482,9 +470,8 @@ public class McpServerTests : LoggedTest
|
|||
configureOptions: null,
|
||||
assertResult: response =>
|
||||
{
|
||||
Assert.IsType<CallToolResponse>(response);
|
||||
|
||||
var result = (CallToolResponse)response;
|
||||
var result = JsonSerializer.Deserialize<CallToolResponse>(response);
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEmpty(result.Content);
|
||||
Assert.Equal("test", result.Content[0].Text);
|
||||
});
|
||||
|
|
@ -496,7 +483,7 @@ public class McpServerTests : LoggedTest
|
|||
await Throws_Exception_If_No_Handler_Assigned(new ServerCapabilities { Tools = new() }, RequestMethods.ToolsCall, "CallTool handler not configured");
|
||||
}
|
||||
|
||||
private async Task Can_Handle_Requests(ServerCapabilities? serverCapabilities, string method, Action<McpServerOptions>? configureOptions, Action<object> assertResult)
|
||||
private async Task Can_Handle_Requests(ServerCapabilities? serverCapabilities, string method, Action<McpServerOptions>? configureOptions, Action<JsonNode?> assertResult)
|
||||
{
|
||||
await using var transport = new TestServerTransport();
|
||||
var options = CreateOptions(serverCapabilities);
|
||||
|
|
@ -524,7 +511,6 @@ public class McpServerTests : LoggedTest
|
|||
|
||||
var response = await receivedMessage.Task.WaitAsync(TimeSpan.FromSeconds(5));
|
||||
Assert.NotNull(response);
|
||||
Assert.NotNull(response.Result);
|
||||
|
||||
assertResult(response.Result);
|
||||
|
||||
|
|
@ -610,10 +596,11 @@ public class McpServerTests : LoggedTest
|
|||
supportsSampling ? new ClientCapabilities { Sampling = new SamplingCapability() } :
|
||||
null;
|
||||
|
||||
public Task<T> SendRequestAsync<T>(JsonRpcRequest request, CancellationToken cancellationToken) where T : class
|
||||
public Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken)
|
||||
{
|
||||
CreateMessageRequestParams rp = Assert.IsType<CreateMessageRequestParams>(request.Params);
|
||||
CreateMessageRequestParams? rp = JsonSerializer.Deserialize<CreateMessageRequestParams>(request.Params);
|
||||
|
||||
Assert.NotNull(rp);
|
||||
Assert.Equal(0.75f, rp.Temperature);
|
||||
Assert.Equal(42, rp.MaxTokens);
|
||||
Assert.Equal(["."], rp.StopSequences);
|
||||
|
|
@ -634,7 +621,12 @@ public class McpServerTests : LoggedTest
|
|||
Role = "assistant",
|
||||
StopReason = "endTurn",
|
||||
};
|
||||
return Task.FromResult((T)(object)result);
|
||||
|
||||
return Task.FromResult(new JsonRpcResponse
|
||||
{
|
||||
Id = new RequestId("0"),
|
||||
Result = JsonSerializer.SerializeToNode(result),
|
||||
});
|
||||
}
|
||||
|
||||
public ValueTask DisposeAsync() => default;
|
||||
|
|
@ -670,7 +662,7 @@ public class McpServerTests : LoggedTest
|
|||
await transport.SendMessageAsync(new JsonRpcNotification
|
||||
{
|
||||
Method = NotificationMethods.ProgressNotification,
|
||||
Params = new ProgressNotification()
|
||||
Params = JsonSerializer.SerializeToNode(new ProgressNotification
|
||||
{
|
||||
ProgressToken = new("abc"),
|
||||
Progress = new()
|
||||
|
|
@ -679,11 +671,12 @@ public class McpServerTests : LoggedTest
|
|||
Total = 100,
|
||||
Message = "Progress message",
|
||||
},
|
||||
},
|
||||
}),
|
||||
}, TestContext.Current.CancellationToken);
|
||||
|
||||
var notification = await notificationReceived.Task;
|
||||
var progress = (ProgressNotification)notification.Params!;
|
||||
var progress = JsonSerializer.Deserialize<ProgressNotification>(notification.Params);
|
||||
Assert.NotNull(progress);
|
||||
Assert.Equal("abc", progress.ProgressToken.ToString());
|
||||
Assert.Equal(50, progress.Progress.Progress);
|
||||
Assert.Equal(100, progress.Progress.Total);
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using ModelContextProtocol.Client;
|
|||
using ModelContextProtocol.Protocol.Transport;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Tests.Utils;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Tests;
|
||||
|
||||
|
|
@ -41,7 +40,7 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : LoggedTest(ou
|
|||
await server.WaitForConnectionAsync(TimeSpan.FromSeconds(10));
|
||||
|
||||
// Send a test message through POST endpoint
|
||||
await client.SendNotificationAsync("test/message", new { message = "Hello, SSE!" }, TestContext.Current.CancellationToken);
|
||||
await client.SendNotificationAsync("test/message", new { message = "Hello, SSE!" }, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Assert
|
||||
Assert.True(true);
|
||||
|
|
@ -78,7 +77,7 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : LoggedTest(ou
|
|||
defaultOptions,
|
||||
loggerFactory: LoggerFactory,
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
var tools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// assert
|
||||
Assert.NotEmpty(tools);
|
||||
|
|
@ -145,7 +144,7 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : LoggedTest(ou
|
|||
{
|
||||
["prompt"] = "Test prompt",
|
||||
["maxTokens"] = 100
|
||||
}, TestContext.Current.CancellationToken);
|
||||
}, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// assert
|
||||
Assert.NotNull(result);
|
||||
|
|
@ -188,7 +187,7 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : LoggedTest(ou
|
|||
await server.WaitForConnectionAsync(TimeSpan.FromSeconds(10));
|
||||
|
||||
// Send a test message through POST endpoint
|
||||
await client.SendNotificationAsync("test/message", new { message = "Hello, SSE!" }, TestContext.Current.CancellationToken);
|
||||
await client.SendNotificationAsync("test/message", new { message = "Hello, SSE!" }, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// Assert
|
||||
Assert.True(true);
|
||||
|
|
@ -229,7 +228,7 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : LoggedTest(ou
|
|||
var receivedNotification = new TaskCompletionSource<string?>();
|
||||
client.AddNotificationHandler("test/notification", (args) =>
|
||||
{
|
||||
var msg = ((JsonElement?)args.Params)?.GetProperty("message").GetString();
|
||||
var msg = args.Params?["message"]?.GetValue<string>();
|
||||
receivedNotification.SetResult(msg);
|
||||
|
||||
return Task.CompletedTask;
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ public class SseServerIntegrationTests : LoggedTest, IClassFixture<SseServerInte
|
|||
|
||||
// act
|
||||
await using var client = await GetClientAsync();
|
||||
var tools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// assert
|
||||
Assert.NotNull(tools);
|
||||
|
|
@ -82,7 +82,7 @@ public class SseServerIntegrationTests : LoggedTest, IClassFixture<SseServerInte
|
|||
{
|
||||
["message"] = "Hello MCP!"
|
||||
},
|
||||
TestContext.Current.CancellationToken
|
||||
cancellationToken: TestContext.Current.CancellationToken
|
||||
);
|
||||
|
||||
// assert
|
||||
|
|
@ -168,7 +168,7 @@ public class SseServerIntegrationTests : LoggedTest, IClassFixture<SseServerInte
|
|||
|
||||
// act
|
||||
await using var client = await GetClientAsync();
|
||||
var result = await client.GetPromptAsync("simple_prompt", null, TestContext.Current.CancellationToken);
|
||||
var result = await client.GetPromptAsync("simple_prompt", null, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// assert
|
||||
Assert.NotNull(result);
|
||||
|
|
@ -187,7 +187,7 @@ public class SseServerIntegrationTests : LoggedTest, IClassFixture<SseServerInte
|
|||
{ "temperature", "0.7" },
|
||||
{ "style", "formal" }
|
||||
};
|
||||
var result = await client.GetPromptAsync("complex_prompt", arguments, TestContext.Current.CancellationToken);
|
||||
var result = await client.GetPromptAsync("complex_prompt", arguments, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// assert
|
||||
Assert.NotNull(result);
|
||||
|
|
@ -202,7 +202,7 @@ public class SseServerIntegrationTests : LoggedTest, IClassFixture<SseServerInte
|
|||
// act
|
||||
await using var client = await GetClientAsync();
|
||||
await Assert.ThrowsAsync<McpClientException>(() =>
|
||||
client.GetPromptAsync("non_existent_prompt", null, TestContext.Current.CancellationToken));
|
||||
client.GetPromptAsync("non_existent_prompt", null, cancellationToken: TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -238,7 +238,7 @@ public class SseServerIntegrationTests : LoggedTest, IClassFixture<SseServerInte
|
|||
["prompt"] = "Test prompt",
|
||||
["maxTokens"] = 100
|
||||
},
|
||||
TestContext.Current.CancellationToken);
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// assert
|
||||
Assert.NotNull(result);
|
||||
|
|
@ -262,7 +262,7 @@ public class SseServerIntegrationTests : LoggedTest, IClassFixture<SseServerInte
|
|||
{
|
||||
["message"] = $"Hello MCP! {i}"
|
||||
},
|
||||
TestContext.Current.CancellationToken
|
||||
cancellationToken: TestContext.Current.CancellationToken
|
||||
);
|
||||
|
||||
Assert.NotNull(result);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using ModelContextProtocol.Utils.Json;
|
|||
using System.IO.Pipelines;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Transport;
|
||||
|
||||
|
|
@ -153,10 +154,10 @@ public class StdioServerTransportTests : LoggedTest
|
|||
{
|
||||
Method = "test",
|
||||
Id = new RequestId(44),
|
||||
Params = new Dictionary<string, JsonElement>
|
||||
Params = new JsonObject
|
||||
{
|
||||
["text"] = JsonSerializer.SerializeToElement(chineseText)
|
||||
}
|
||||
["text"] = chineseText
|
||||
},
|
||||
};
|
||||
|
||||
// Clear output and send message
|
||||
|
|
@ -175,10 +176,10 @@ public class StdioServerTransportTests : LoggedTest
|
|||
{
|
||||
Method = "test",
|
||||
Id = new RequestId(45),
|
||||
Params = new Dictionary<string, JsonElement>
|
||||
Params = new JsonObject
|
||||
{
|
||||
["text"] = JsonSerializer.SerializeToElement(emojiText)
|
||||
}
|
||||
["text"] = emojiText
|
||||
},
|
||||
};
|
||||
|
||||
// Clear output and send message
|
||||
|
|
|
|||
|
|
@ -289,7 +289,7 @@ public sealed class InMemoryTestSseServer : IAsyncDisposable
|
|||
var jsonRpcResponse = new JsonRpcResponse()
|
||||
{
|
||||
Id = jsonRpcRequest.Id,
|
||||
Result = new InitializeResult()
|
||||
Result = JsonSerializer.SerializeToNode(new InitializeResult
|
||||
{
|
||||
ProtocolVersion = "2024-11-05",
|
||||
Capabilities = new(),
|
||||
|
|
@ -298,7 +298,7 @@ public sealed class InMemoryTestSseServer : IAsyncDisposable
|
|||
Name = "ExampleServer",
|
||||
Version = "1.0.0"
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
// Echo back to the HTTP response
|
||||
|
|
@ -369,7 +369,7 @@ public sealed class InMemoryTestSseServer : IAsyncDisposable
|
|||
{
|
||||
JsonRpc = "2.0",
|
||||
Method = "test/notification",
|
||||
Params = new { message = content }
|
||||
Params = JsonSerializer.SerializeToNode(new { message = content }),
|
||||
};
|
||||
|
||||
var serialized = JsonSerializer.Serialize(notification);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
using System.Threading.Channels;
|
||||
using ModelContextProtocol.Protocol.Messages;
|
||||
using ModelContextProtocol.Protocol.Messages;
|
||||
using ModelContextProtocol.Protocol.Transport;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Channels;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Utils;
|
||||
|
||||
|
|
@ -59,10 +60,10 @@ public class TestServerTransport : ITransport
|
|||
await WriteMessageAsync(new JsonRpcResponse
|
||||
{
|
||||
Id = request.Id,
|
||||
Result = new ModelContextProtocol.Protocol.Types.ListRootsResult
|
||||
Result = JsonSerializer.SerializeToNode(new ListRootsResult
|
||||
{
|
||||
Roots = []
|
||||
}
|
||||
}),
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +72,7 @@ public class TestServerTransport : ITransport
|
|||
await WriteMessageAsync(new JsonRpcResponse
|
||||
{
|
||||
Id = request.Id,
|
||||
Result = new CreateMessageResult { Content = new(), Model = "model", Role = "role" }
|
||||
Result = JsonSerializer.SerializeToNode(new CreateMessageResult { Content = new(), Model = "model", Role = "role" }),
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue