Disable STJ reflection for testing. (#266)
* Disable STJ reflection for testing. * Remove params method and redundant overload. --------- Co-authored-by: Stephen Toub <stoub@microsoft.com>
This commit is contained in:
parent
45dcfe07ca
commit
e3aa54eb3c
18 changed files with 155 additions and 75 deletions
|
|
@ -8,6 +8,7 @@ using ModelContextProtocol.Server;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace Microsoft.Extensions.DependencyInjection;
|
namespace Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
|
@ -24,6 +25,7 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</summary>
|
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</summary>
|
||||||
/// <typeparam name="TToolType">The tool type.</typeparam>
|
/// <typeparam name="TToolType">The tool type.</typeparam>
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
|
/// <param name="serializerOptions">The serializer options governing tool parameter marshalling.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
|
@ -35,7 +37,8 @@ public static partial class McpServerBuilderExtensions
|
||||||
DynamicallyAccessedMemberTypes.PublicMethods |
|
DynamicallyAccessedMemberTypes.PublicMethods |
|
||||||
DynamicallyAccessedMemberTypes.NonPublicMethods |
|
DynamicallyAccessedMemberTypes.NonPublicMethods |
|
||||||
DynamicallyAccessedMemberTypes.PublicConstructors)] TToolType>(
|
DynamicallyAccessedMemberTypes.PublicConstructors)] TToolType>(
|
||||||
this IMcpServerBuilder builder)
|
this IMcpServerBuilder builder,
|
||||||
|
JsonSerializerOptions? serializerOptions = null)
|
||||||
{
|
{
|
||||||
Throw.IfNull(builder);
|
Throw.IfNull(builder);
|
||||||
|
|
||||||
|
|
@ -44,8 +47,8 @@ public static partial class McpServerBuilderExtensions
|
||||||
if (toolMethod.GetCustomAttribute<McpServerToolAttribute>() is not null)
|
if (toolMethod.GetCustomAttribute<McpServerToolAttribute>() is not null)
|
||||||
{
|
{
|
||||||
builder.Services.AddSingleton((Func<IServiceProvider, McpServerTool>)(toolMethod.IsStatic ?
|
builder.Services.AddSingleton((Func<IServiceProvider, McpServerTool>)(toolMethod.IsStatic ?
|
||||||
services => McpServerTool.Create(toolMethod, options: new() { Services = services }) :
|
services => McpServerTool.Create(toolMethod, options: new() { Services = services, SerializerOptions = serializerOptions }) :
|
||||||
services => McpServerTool.Create(toolMethod, typeof(TToolType), new() { Services = services })));
|
services => McpServerTool.Create(toolMethod, typeof(TToolType), new() { Services = services, SerializerOptions = serializerOptions })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,6 +58,7 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</summary>
|
/// <summary>Adds <see cref="McpServerTool"/> instances to the service collection backing <paramref name="builder"/>.</summary>
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
/// <param name="toolTypes">Types with marked methods to add as tools to the server.</param>
|
/// <param name="toolTypes">Types with marked methods to add as tools to the server.</param>
|
||||||
|
/// <param name="serializerOptions">The serializer options governing tool parameter marshalling.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="toolTypes"/> is <see langword="null"/>.</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="toolTypes"/> is <see langword="null"/>.</exception>
|
||||||
|
|
@ -64,7 +68,7 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
|
/// instance for each. For instance methods, an instance will be constructed for each invocation of the tool.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
|
[RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
|
||||||
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, params IEnumerable<Type> toolTypes)
|
public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnumerable<Type> toolTypes, JsonSerializerOptions? serializerOptions = null)
|
||||||
{
|
{
|
||||||
Throw.IfNull(builder);
|
Throw.IfNull(builder);
|
||||||
Throw.IfNull(toolTypes);
|
Throw.IfNull(toolTypes);
|
||||||
|
|
@ -78,8 +82,8 @@ public static partial class McpServerBuilderExtensions
|
||||||
if (toolMethod.GetCustomAttribute<McpServerToolAttribute>() is not null)
|
if (toolMethod.GetCustomAttribute<McpServerToolAttribute>() is not null)
|
||||||
{
|
{
|
||||||
builder.Services.AddSingleton((Func<IServiceProvider, McpServerTool>)(toolMethod.IsStatic ?
|
builder.Services.AddSingleton((Func<IServiceProvider, McpServerTool>)(toolMethod.IsStatic ?
|
||||||
services => McpServerTool.Create(toolMethod, options: new() { Services = services }) :
|
services => McpServerTool.Create(toolMethod, options: new() { Services = services , SerializerOptions = serializerOptions }) :
|
||||||
services => McpServerTool.Create(toolMethod, toolType, new() { Services = services })));
|
services => McpServerTool.Create(toolMethod, toolType, new() { Services = services , SerializerOptions = serializerOptions })));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -92,6 +96,7 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// Adds types marked with the <see cref="McpServerToolTypeAttribute"/> attribute from the given assembly as tools to the server.
|
/// Adds types marked with the <see cref="McpServerToolTypeAttribute"/> attribute from the given assembly as tools to the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="builder">The builder instance.</param>
|
/// <param name="builder">The builder instance.</param>
|
||||||
|
/// <param name="serializerOptions">The serializer options governing tool parameter marshalling.</param>
|
||||||
/// <param name="toolAssembly">The assembly to load the types from. If <see langword="null"/>, the calling assembly will be used.</param>
|
/// <param name="toolAssembly">The assembly to load the types from. If <see langword="null"/>, the calling assembly will be used.</param>
|
||||||
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
|
||||||
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
|
||||||
|
|
@ -116,7 +121,7 @@ public static partial class McpServerBuilderExtensions
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
|
[RequiresUnreferencedCode(WithToolsRequiresUnreferencedCodeMessage)]
|
||||||
public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? toolAssembly = null)
|
public static IMcpServerBuilder WithToolsFromAssembly(this IMcpServerBuilder builder, Assembly? toolAssembly = null, JsonSerializerOptions? serializerOptions = null)
|
||||||
{
|
{
|
||||||
Throw.IfNull(builder);
|
Throw.IfNull(builder);
|
||||||
|
|
||||||
|
|
@ -125,7 +130,8 @@ public static partial class McpServerBuilderExtensions
|
||||||
return builder.WithTools(
|
return builder.WithTools(
|
||||||
from t in toolAssembly.GetTypes()
|
from t in toolAssembly.GetTypes()
|
||||||
where t.GetCustomAttribute<McpServerToolTypeAttribute>() is not null
|
where t.GetCustomAttribute<McpServerToolTypeAttribute>() is not null
|
||||||
select t);
|
select t,
|
||||||
|
serializerOptions);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
|
||||||
Name = options?.Name ?? method.GetCustomAttribute<McpServerToolAttribute>()?.Name,
|
Name = options?.Name ?? method.GetCustomAttribute<McpServerToolAttribute>()?.Name,
|
||||||
Description = options?.Description,
|
Description = options?.Description,
|
||||||
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
|
MarshalResult = static (result, _, cancellationToken) => new ValueTask<object?>(result),
|
||||||
|
SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
|
||||||
ConfigureParameterBinding = pi =>
|
ConfigureParameterBinding = pi =>
|
||||||
{
|
{
|
||||||
if (pi.ParameterType == typeof(RequestContext<CallToolRequestParams>))
|
if (pi.ParameterType == typeof(RequestContext<CallToolRequestParams>))
|
||||||
|
|
@ -314,7 +315,7 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
|
||||||
{
|
{
|
||||||
Content = [new()
|
Content = [new()
|
||||||
{
|
{
|
||||||
Text = JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object))),
|
Text = JsonSerializer.Serialize(result, AIFunction.JsonSerializerOptions.GetTypeInfo(typeof(object))),
|
||||||
Type = "text"
|
Type = "text"
|
||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
|
using ModelContextProtocol.Utils.Json;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Server;
|
namespace ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
|
@ -122,11 +124,19 @@ public sealed class McpServerToolCreateOptions
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public bool? ReadOnly { get; set; }
|
public bool? ReadOnly { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the JSON serializer options to use when marshalling data to/from JSON.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Defaults to <see cref="McpJsonUtilities.DefaultOptions"/> if left unspecified.
|
||||||
|
/// </remarks>
|
||||||
|
public JsonSerializerOptions? SerializerOptions { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a shallow clone of the current <see cref="McpServerToolCreateOptions"/> instance.
|
/// Creates a shallow clone of the current <see cref="McpServerToolCreateOptions"/> instance.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal McpServerToolCreateOptions Clone() =>
|
internal McpServerToolCreateOptions Clone() =>
|
||||||
new McpServerToolCreateOptions()
|
new McpServerToolCreateOptions
|
||||||
{
|
{
|
||||||
Services = Services,
|
Services = Services,
|
||||||
Name = Name,
|
Name = Name,
|
||||||
|
|
@ -135,6 +145,7 @@ public sealed class McpServerToolCreateOptions
|
||||||
Destructive = Destructive,
|
Destructive = Destructive,
|
||||||
Idempotent = Idempotent,
|
Idempotent = Idempotent,
|
||||||
OpenWorld = OpenWorld,
|
OpenWorld = OpenWorld,
|
||||||
ReadOnly = ReadOnly
|
ReadOnly = ReadOnly,
|
||||||
|
SerializerOptions = SerializerOptions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,11 @@
|
||||||
<RootNamespace>ModelContextProtocol.AspNetCore.Tests</RootNamespace>
|
<RootNamespace>ModelContextProtocol.AspNetCore.Tests</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||||
|
<!-- For better test coverage, only disable reflection in one of the targets -->
|
||||||
|
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Without this, tests are currently not showing results until all tests complete
|
<!-- Without this, tests are currently not showing results until all tests complete
|
||||||
https://xunit.net/docs/getting-started/v3/microsoft-testing-platform
|
https://xunit.net/docs/getting-started/v3/microsoft-testing-platform
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,12 @@ using ModelContextProtocol.Protocol.Transport;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
using ModelContextProtocol.Tests.Utils;
|
using ModelContextProtocol.Tests.Utils;
|
||||||
using ModelContextProtocol.Utils.Json;
|
using ModelContextProtocol.Utils.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using TestServerWithHosting.Tools;
|
using TestServerWithHosting.Tools;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Tests;
|
namespace ModelContextProtocol.Tests;
|
||||||
|
|
||||||
public class SseIntegrationTests(ITestOutputHelper outputHelper) : KestrelInMemoryTest(outputHelper)
|
public partial class SseIntegrationTests(ITestOutputHelper outputHelper) : KestrelInMemoryTest(outputHelper)
|
||||||
{
|
{
|
||||||
private SseClientTransportOptions DefaultTransportOptions = new()
|
private SseClientTransportOptions DefaultTransportOptions = new()
|
||||||
{
|
{
|
||||||
|
|
@ -41,7 +42,7 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : KestrelInMemo
|
||||||
await using var mcpClient = await ConnectMcpClient(httpClient);
|
await using var mcpClient = await ConnectMcpClient(httpClient);
|
||||||
|
|
||||||
// Send a test message through POST endpoint
|
// Send a test message through POST endpoint
|
||||||
await mcpClient.SendNotificationAsync("test/message", new { message = "Hello, SSE!" }, cancellationToken: TestContext.Current.CancellationToken);
|
await mcpClient.SendNotificationAsync("test/message", new Envelope { Message = "Hello, SSE!" }, serializerOptions: JsonContext.Default.Options, cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
Assert.True(true);
|
Assert.True(true);
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +58,7 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : KestrelInMemo
|
||||||
await using var mcpClient = await ConnectMcpClient(httpClient);
|
await using var mcpClient = await ConnectMcpClient(httpClient);
|
||||||
|
|
||||||
// Send a test message through POST endpoint
|
// Send a test message through POST endpoint
|
||||||
await mcpClient.SendNotificationAsync("test/message", new { message = "Hello, SSE!" }, cancellationToken: TestContext.Current.CancellationToken);
|
await mcpClient.SendNotificationAsync("test/message", new Envelope { Message = "Hello, SSE!" }, serializerOptions: JsonContext.Default.Options, cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
Assert.True(true);
|
Assert.True(true);
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +74,7 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : KestrelInMemo
|
||||||
mcpServer.RegisterNotificationHandler("test/notification", async (notification, cancellationToken) =>
|
mcpServer.RegisterNotificationHandler("test/notification", async (notification, cancellationToken) =>
|
||||||
{
|
{
|
||||||
Assert.Equal("Hello from client!", notification.Params?["message"]?.GetValue<string>());
|
Assert.Equal("Hello from client!", notification.Params?["message"]?.GetValue<string>());
|
||||||
await mcpServer.SendNotificationAsync("test/notification", new { message = "Hello from server!" }, cancellationToken: cancellationToken);
|
await mcpServer.SendNotificationAsync("test/notification", new Envelope { Message = "Hello from server!" }, serializerOptions: JsonContext.Default.Options, cancellationToken: cancellationToken);
|
||||||
});
|
});
|
||||||
return mcpServer.RunAsync(cancellationToken);
|
return mcpServer.RunAsync(cancellationToken);
|
||||||
});
|
});
|
||||||
|
|
@ -90,7 +91,7 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : KestrelInMemo
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send a test message through POST endpoint
|
// Send a test message through POST endpoint
|
||||||
await mcpClient.SendNotificationAsync("test/notification", new { message = "Hello from client!" }, cancellationToken: TestContext.Current.CancellationToken);
|
await mcpClient.SendNotificationAsync("test/notification", new Envelope { Message = "Hello from client!" }, serializerOptions: JsonContext.Default.Options, cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
var message = await receivedNotification.Task.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
var message = await receivedNotification.Task.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
|
||||||
Assert.Equal("Hello from server!", message);
|
Assert.Equal("Hello from server!", message);
|
||||||
|
|
@ -205,4 +206,13 @@ public class SseIntegrationTests(ITestOutputHelper outputHelper) : KestrelInMemo
|
||||||
await context.Response.WriteAsync("Accepted");
|
await context.Response.WriteAsync("Accepted");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class Envelope
|
||||||
|
{
|
||||||
|
public required string Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
|
||||||
|
[JsonSerializable(typeof(Envelope))]
|
||||||
|
partial class JsonContext : JsonSerializerContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using Microsoft.AspNetCore.Connections;
|
using Microsoft.AspNetCore.Connections;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
|
using ModelContextProtocol.Utils.Json;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
@ -124,7 +125,7 @@ public class Program
|
||||||
},
|
},
|
||||||
"required": ["message"]
|
"required": ["message"]
|
||||||
}
|
}
|
||||||
"""),
|
""", McpJsonUtilities.DefaultOptions),
|
||||||
},
|
},
|
||||||
new Tool()
|
new Tool()
|
||||||
{
|
{
|
||||||
|
|
@ -145,7 +146,7 @@ public class Program
|
||||||
},
|
},
|
||||||
"required": ["prompt", "maxTokens"]
|
"required": ["prompt", "maxTokens"]
|
||||||
}
|
}
|
||||||
"""),
|
""", McpJsonUtilities.DefaultOptions),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using ModelContextProtocol.Client;
|
||||||
using ModelContextProtocol.Protocol.Messages;
|
using ModelContextProtocol.Protocol.Messages;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
|
using ModelContextProtocol.Utils.Json;
|
||||||
using Moq;
|
using Moq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization.Metadata;
|
using System.Text.Json.Serialization.Metadata;
|
||||||
|
|
@ -379,7 +380,7 @@ public class McpClientExtensionsTests : ClientServerTestBase
|
||||||
await using (client.RegisterNotificationHandler(NotificationMethods.LoggingMessageNotification,
|
await using (client.RegisterNotificationHandler(NotificationMethods.LoggingMessageNotification,
|
||||||
(notification, cancellationToken) =>
|
(notification, cancellationToken) =>
|
||||||
{
|
{
|
||||||
Assert.True(channel.Writer.TryWrite(JsonSerializer.Deserialize<LoggingMessageNotificationParams>(notification.Params)));
|
Assert.True(channel.Writer.TryWrite(JsonSerializer.Deserialize<LoggingMessageNotificationParams>(notification.Params, McpJsonUtilities.DefaultOptions)));
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
|
|
@ -398,7 +399,7 @@ public class McpClientExtensionsTests : ClientServerTestBase
|
||||||
|
|
||||||
Assert.Equal("TestLogger", m.Logger);
|
Assert.Equal("TestLogger", m.Logger);
|
||||||
|
|
||||||
string ? s = JsonSerializer.Deserialize<string>(m.Data.Value);
|
string ? s = JsonSerializer.Deserialize<string>(m.Data.Value, McpJsonUtilities.DefaultOptions);
|
||||||
Assert.NotNull(s);
|
Assert.NotNull(s);
|
||||||
|
|
||||||
if (s.Contains("Information"))
|
if (s.Contains("Information"))
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using ModelContextProtocol.Client;
|
||||||
using ModelContextProtocol.Protocol.Messages;
|
using ModelContextProtocol.Protocol.Messages;
|
||||||
using ModelContextProtocol.Protocol.Transport;
|
using ModelContextProtocol.Protocol.Transport;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
|
using ModelContextProtocol.Utils.Json;
|
||||||
using Moq;
|
using Moq;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
|
|
@ -107,7 +108,7 @@ public class McpClientFactoryTests
|
||||||
Name = "NopTransport",
|
Name = "NopTransport",
|
||||||
Version = "1.0.0"
|
Version = "1.0.0"
|
||||||
},
|
},
|
||||||
}),
|
}, McpJsonUtilities.DefaultOptions),
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,14 @@ using ModelContextProtocol.Protocol.Messages;
|
||||||
using ModelContextProtocol.Protocol.Transport;
|
using ModelContextProtocol.Protocol.Transport;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Tests.Utils;
|
using ModelContextProtocol.Tests.Utils;
|
||||||
|
using ModelContextProtocol.Utils.Json;
|
||||||
using OpenAI;
|
using OpenAI;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Tests;
|
namespace ModelContextProtocol.Tests;
|
||||||
|
|
||||||
public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegrationTestFixture>
|
public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegrationTestFixture>
|
||||||
{
|
{
|
||||||
private static readonly string? s_openAIKey = Environment.GetEnvironmentVariable("AI:OpenAI:ApiKey");
|
private static readonly string? s_openAIKey = Environment.GetEnvironmentVariable("AI:OpenAI:ApiKey");
|
||||||
|
|
||||||
|
|
@ -261,7 +263,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
||||||
[
|
[
|
||||||
new(NotificationMethods.ResourceUpdatedNotification, (notification, cancellationToken) =>
|
new(NotificationMethods.ResourceUpdatedNotification, (notification, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var notificationParams = JsonSerializer.Deserialize<ResourceUpdatedNotificationParams>(notification.Params);
|
var notificationParams = JsonSerializer.Deserialize<ResourceUpdatedNotificationParams>(notification.Params, McpJsonUtilities.DefaultOptions);
|
||||||
tcs.TrySetResult(true);
|
tcs.TrySetResult(true);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
})
|
})
|
||||||
|
|
@ -291,7 +293,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
||||||
[
|
[
|
||||||
new(NotificationMethods.ResourceUpdatedNotification, (notification, cancellationToken) =>
|
new(NotificationMethods.ResourceUpdatedNotification, (notification, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var notificationParams = JsonSerializer.Deserialize<ResourceUpdatedNotificationParams>(notification.Params);
|
var notificationParams = JsonSerializer.Deserialize<ResourceUpdatedNotificationParams>(notification.Params, McpJsonUtilities.DefaultOptions);
|
||||||
receivedNotification.TrySetResult(true);
|
receivedNotification.TrySetResult(true);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
})
|
})
|
||||||
|
|
@ -442,13 +444,18 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
||||||
|
|
||||||
// Verify we can send notifications without errors
|
// Verify we can send notifications without errors
|
||||||
await client.SendNotificationAsync(NotificationMethods.RootsUpdatedNotification, cancellationToken: TestContext.Current.CancellationToken);
|
await client.SendNotificationAsync(NotificationMethods.RootsUpdatedNotification, cancellationToken: TestContext.Current.CancellationToken);
|
||||||
await client.SendNotificationAsync("test/notification", new { test = true }, cancellationToken: TestContext.Current.CancellationToken);
|
await client.SendNotificationAsync("test/notification", new TestNotification { Test = true }, cancellationToken: TestContext.Current.CancellationToken, serializerOptions: JsonContext3.Default.Options);
|
||||||
|
|
||||||
// assert
|
// assert
|
||||||
// no response to check, if no exception is thrown, it's a success
|
// no response to check, if no exception is thrown, it's a success
|
||||||
Assert.True(true);
|
Assert.True(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TestNotification
|
||||||
|
{
|
||||||
|
public required bool Test { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task CallTool_Stdio_MemoryServer()
|
public async Task CallTool_Stdio_MemoryServer()
|
||||||
{
|
{
|
||||||
|
|
@ -557,7 +564,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
||||||
[
|
[
|
||||||
new(NotificationMethods.LoggingMessageNotification, (notification, cancellationToken) =>
|
new(NotificationMethods.LoggingMessageNotification, (notification, cancellationToken) =>
|
||||||
{
|
{
|
||||||
var loggingMessageNotificationParameters = JsonSerializer.Deserialize<LoggingMessageNotificationParams>(notification.Params);
|
var loggingMessageNotificationParameters = JsonSerializer.Deserialize<LoggingMessageNotificationParams>(notification.Params, McpJsonUtilities.DefaultOptions);
|
||||||
if (loggingMessageNotificationParameters is not null)
|
if (loggingMessageNotificationParameters is not null)
|
||||||
{
|
{
|
||||||
receivedNotification.TrySetResult(true);
|
receivedNotification.TrySetResult(true);
|
||||||
|
|
@ -574,4 +581,7 @@ public class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIntegratio
|
||||||
// assert
|
// assert
|
||||||
await receivedNotification.Task;
|
await receivedNotification.Task;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonSerializable(typeof(TestNotification))]
|
||||||
|
partial class JsonContext3 : JsonSerializerContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ using ModelContextProtocol.Protocol.Messages;
|
||||||
using ModelContextProtocol.Protocol.Transport;
|
using ModelContextProtocol.Protocol.Transport;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
|
using ModelContextProtocol.Utils.Json;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO.Pipelines;
|
using System.IO.Pipelines;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
|
@ -18,7 +20,7 @@ using System.Threading.Channels;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Tests.Configuration;
|
namespace ModelContextProtocol.Tests.Configuration;
|
||||||
|
|
||||||
public class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
{
|
{
|
||||||
public McpServerBuilderExtensionsToolsTests(ITestOutputHelper testOutputHelper)
|
public McpServerBuilderExtensionsToolsTests(ITestOutputHelper testOutputHelper)
|
||||||
: base(testOutputHelper)
|
: base(testOutputHelper)
|
||||||
|
|
@ -47,7 +49,7 @@ public class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
"properties": {},
|
"properties": {},
|
||||||
"required": []
|
"required": []
|
||||||
}
|
}
|
||||||
"""),
|
""", McpJsonUtilities.DefaultOptions),
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -65,7 +67,7 @@ public class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
"properties": {},
|
"properties": {},
|
||||||
"required": []
|
"required": []
|
||||||
}
|
}
|
||||||
"""),
|
""", McpJsonUtilities.DefaultOptions),
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -83,7 +85,7 @@ public class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
"properties": {},
|
"properties": {},
|
||||||
"required": []
|
"required": []
|
||||||
}
|
}
|
||||||
"""),
|
""", McpJsonUtilities.DefaultOptions),
|
||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -107,7 +109,7 @@ public class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
throw new Exception($"Unknown tool '{request.Params?.Name}'");
|
throw new Exception($"Unknown tool '{request.Params?.Name}'");
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.WithTools<EchoTool>();
|
.WithTools<EchoTool>(serializerOptions: JsonContext.Default.Options);
|
||||||
|
|
||||||
services.AddSingleton(new ObjectWithId());
|
services.AddSingleton(new ObjectWithId());
|
||||||
}
|
}
|
||||||
|
|
@ -430,7 +432,7 @@ public class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
public void Register_Tools_From_Current_Assembly()
|
public void Register_Tools_From_Current_Assembly()
|
||||||
{
|
{
|
||||||
ServiceCollection sc = new();
|
ServiceCollection sc = new();
|
||||||
sc.AddMcpServer().WithToolsFromAssembly();
|
sc.AddMcpServer().WithToolsFromAssembly(serializerOptions: JsonContext.Default.Options);
|
||||||
IServiceProvider services = sc.BuildServiceProvider();
|
IServiceProvider services = sc.BuildServiceProvider();
|
||||||
|
|
||||||
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "Echo");
|
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "Echo");
|
||||||
|
|
@ -446,17 +448,17 @@ public class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
{
|
{
|
||||||
sc.AddSingleton(new ComplexObject());
|
sc.AddSingleton(new ComplexObject());
|
||||||
}
|
}
|
||||||
sc.AddMcpServer().WithTools(typeof(EchoTool));
|
sc.AddMcpServer().WithTools([typeof(EchoTool)], JsonContext.Default.Options);
|
||||||
IServiceProvider services = sc.BuildServiceProvider();
|
IServiceProvider services = sc.BuildServiceProvider();
|
||||||
|
|
||||||
McpServerTool tool = services.GetServices<McpServerTool>().First(t => t.ProtocolTool.Name == "EchoComplex");
|
McpServerTool tool = services.GetServices<McpServerTool>().First(t => t.ProtocolTool.Name == "EchoComplex");
|
||||||
if (parameterInServices)
|
if (parameterInServices)
|
||||||
{
|
{
|
||||||
Assert.DoesNotContain("\"complex\"", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema));
|
Assert.DoesNotContain("\"complex\"", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema, AIJsonUtilities.DefaultOptions));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Assert.Contains("\"complex\"", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema));
|
Assert.Contains("\"complex\"", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema, AIJsonUtilities.DefaultOptions));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -483,17 +485,17 @@ public class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
sc.AddMcpServer().WithToolsFromAssembly();
|
sc.AddMcpServer().WithToolsFromAssembly(serializerOptions: JsonContext.Default.Options);
|
||||||
IServiceProvider services = sc.BuildServiceProvider();
|
IServiceProvider services = sc.BuildServiceProvider();
|
||||||
|
|
||||||
McpServerTool tool = services.GetServices<McpServerTool>().First(t => t.ProtocolTool.Name == "EchoComplex");
|
McpServerTool tool = services.GetServices<McpServerTool>().First(t => t.ProtocolTool.Name == "EchoComplex");
|
||||||
if (lifetime is not null)
|
if (lifetime is not null)
|
||||||
{
|
{
|
||||||
Assert.DoesNotContain("\"complex\"", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema));
|
Assert.DoesNotContain("\"complex\"", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema, AIJsonUtilities.DefaultOptions));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Assert.Contains("\"complex\"", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema));
|
Assert.Contains("\"complex\"", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema, AIJsonUtilities.DefaultOptions));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -526,9 +528,9 @@ public class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
{
|
{
|
||||||
ServiceCollection sc = new();
|
ServiceCollection sc = new();
|
||||||
sc.AddMcpServer()
|
sc.AddMcpServer()
|
||||||
.WithTools<EchoTool>()
|
.WithTools<EchoTool>(serializerOptions: JsonContext.Default.Options)
|
||||||
.WithTools<AnotherToolType>()
|
.WithTools<AnotherToolType>(serializerOptions: JsonContext.Default.Options)
|
||||||
.WithTools(typeof(ToolTypeWithNoAttribute));
|
.WithTools([typeof(ToolTypeWithNoAttribute)], JsonContext.Default.Options);
|
||||||
IServiceProvider services = sc.BuildServiceProvider();
|
IServiceProvider services = sc.BuildServiceProvider();
|
||||||
|
|
||||||
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "double_echo");
|
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "double_echo");
|
||||||
|
|
@ -584,7 +586,7 @@ public class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
ConcurrentQueue<ProgressNotification> notifications = new();
|
ConcurrentQueue<ProgressNotification> notifications = new();
|
||||||
await using (client.RegisterNotificationHandler(NotificationMethods.ProgressNotification, (notification, cancellationToken) =>
|
await using (client.RegisterNotificationHandler(NotificationMethods.ProgressNotification, (notification, cancellationToken) =>
|
||||||
{
|
{
|
||||||
ProgressNotification pn = JsonSerializer.Deserialize<ProgressNotification>(notification.Params)!;
|
ProgressNotification pn = JsonSerializer.Deserialize<ProgressNotification>(notification.Params, McpJsonUtilities.DefaultOptions)!;
|
||||||
notifications.Enqueue(pn);
|
notifications.Enqueue(pn);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}))
|
}))
|
||||||
|
|
@ -598,7 +600,7 @@ public class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
},
|
},
|
||||||
cancellationToken: TestContext.Current.CancellationToken);
|
cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
Assert.Contains("done", JsonSerializer.Serialize(result));
|
Assert.Contains("done", JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions));
|
||||||
SpinWait.SpinUntil(() => notifications.Count == 10, TimeSpan.FromSeconds(10));
|
SpinWait.SpinUntil(() => notifications.Count == 10, TimeSpan.FromSeconds(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -765,4 +767,16 @@ public class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
{
|
{
|
||||||
public string Id { get; set; } = Guid.NewGuid().ToString("N");
|
public string Id { get; set; } = Guid.NewGuid().ToString("N");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonSerializable(typeof(bool))]
|
||||||
|
[JsonSerializable(typeof(int))]
|
||||||
|
[JsonSerializable(typeof(long))]
|
||||||
|
[JsonSerializable(typeof(double))]
|
||||||
|
[JsonSerializable(typeof(string))]
|
||||||
|
[JsonSerializable(typeof(DateTime))]
|
||||||
|
[JsonSerializable(typeof(DateTimeOffset))]
|
||||||
|
[JsonSerializable(typeof(ComplexObject))]
|
||||||
|
[JsonSerializable(typeof(string[]))]
|
||||||
|
[JsonSerializable(typeof(JsonElement))]
|
||||||
|
partial class JsonContext : JsonSerializerContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,8 @@ public static class McpJsonUtilitiesTests
|
||||||
public static void DefaultOptions_UseReflectionWhenEnabled()
|
public static void DefaultOptions_UseReflectionWhenEnabled()
|
||||||
{
|
{
|
||||||
var options = McpJsonUtilities.DefaultOptions;
|
var options = McpJsonUtilities.DefaultOptions;
|
||||||
bool isReflectionEnabled = JsonSerializer.IsReflectionEnabledByDefault;
|
|
||||||
Type anonType = new { Id = 42 }.GetType();
|
Type anonType = new { Id = 42 }.GetType();
|
||||||
|
|
||||||
Assert.True(isReflectionEnabled); // To be disabled once https://github.com/dotnet/extensions/pull/6241 is incorporated
|
Assert.Equal(JsonSerializer.IsReflectionEnabledByDefault, options.TryGetTypeInfo(anonType, out _));
|
||||||
Assert.Equal(isReflectionEnabled, options.TryGetTypeInfo(anonType, out _));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,11 @@
|
||||||
<RootNamespace>ModelContextProtocol.Tests</RootNamespace>
|
<RootNamespace>ModelContextProtocol.Tests</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||||
|
<!-- For better test coverage, only disable reflection in one of the targets -->
|
||||||
|
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Without this, tests are currently not showing results until all tests complete
|
<!-- Without this, tests are currently not showing results until all tests complete
|
||||||
https://xunit.net/docs/getting-started/v3/microsoft-testing-platform
|
https://xunit.net/docs/getting-started/v3/microsoft-testing-platform
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
|
using ModelContextProtocol.Utils.Json;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Tests.Protocol;
|
namespace ModelContextProtocol.Tests.Protocol;
|
||||||
|
|
@ -57,7 +58,7 @@ public static class ProtocolTypeTests
|
||||||
[InlineData(Role.Assistant, "\"assistant\"")]
|
[InlineData(Role.Assistant, "\"assistant\"")]
|
||||||
public static void SerializeRole_ShouldBeCamelCased(Role role, string expectedValue)
|
public static void SerializeRole_ShouldBeCamelCased(Role role, string expectedValue)
|
||||||
{
|
{
|
||||||
var actualValue = JsonSerializer.Serialize(role);
|
var actualValue = JsonSerializer.Serialize(role, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
Assert.Equal(expectedValue, actualValue);
|
Assert.Equal(expectedValue, actualValue);
|
||||||
}
|
}
|
||||||
|
|
@ -73,7 +74,7 @@ public static class ProtocolTypeTests
|
||||||
[InlineData(LoggingLevel.Emergency, "\"emergency\"")]
|
[InlineData(LoggingLevel.Emergency, "\"emergency\"")]
|
||||||
public static void SerializeLoggingLevel_ShouldBeCamelCased(LoggingLevel level, string expectedValue)
|
public static void SerializeLoggingLevel_ShouldBeCamelCased(LoggingLevel level, string expectedValue)
|
||||||
{
|
{
|
||||||
var actualValue = JsonSerializer.Serialize(level);
|
var actualValue = JsonSerializer.Serialize(level, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
Assert.Equal(expectedValue, actualValue);
|
Assert.Equal(expectedValue, actualValue);
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +85,7 @@ public static class ProtocolTypeTests
|
||||||
[InlineData(ContextInclusion.AllServers, "\"allServers\"")]
|
[InlineData(ContextInclusion.AllServers, "\"allServers\"")]
|
||||||
public static void ContextInclusion_ShouldBeCamelCased(ContextInclusion level, string expectedValue)
|
public static void ContextInclusion_ShouldBeCamelCased(ContextInclusion level, string expectedValue)
|
||||||
{
|
{
|
||||||
var actualValue = JsonSerializer.Serialize(level);
|
var actualValue = JsonSerializer.Serialize(level, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
Assert.Equal(expectedValue, actualValue);
|
Assert.Equal(expectedValue, actualValue);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using ModelContextProtocol.Protocol.Messages;
|
using ModelContextProtocol.Protocol.Messages;
|
||||||
|
using ModelContextProtocol.Utils.Json;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Tests.Protocol;
|
namespace ModelContextProtocol.Tests.Protocol;
|
||||||
|
|
@ -10,14 +11,14 @@ public class RequestIdTests
|
||||||
{
|
{
|
||||||
RequestId id = new("test-id");
|
RequestId id = new("test-id");
|
||||||
Assert.Equal("test-id", id.ToString());
|
Assert.Equal("test-id", id.ToString());
|
||||||
Assert.Equal("\"test-id\"", JsonSerializer.Serialize(id));
|
Assert.Equal("\"test-id\"", JsonSerializer.Serialize(id, McpJsonUtilities.DefaultOptions));
|
||||||
Assert.Same("test-id", id.Id);
|
Assert.Same("test-id", id.Id);
|
||||||
|
|
||||||
Assert.True(id.Equals(new("test-id")));
|
Assert.True(id.Equals(new("test-id")));
|
||||||
Assert.False(id.Equals(new("tEst-id")));
|
Assert.False(id.Equals(new("tEst-id")));
|
||||||
Assert.Equal("test-id".GetHashCode(), id.GetHashCode());
|
Assert.Equal("test-id".GetHashCode(), id.GetHashCode());
|
||||||
|
|
||||||
Assert.Equal(id, JsonSerializer.Deserialize<RequestId>(JsonSerializer.Serialize(id)));
|
Assert.Equal(id, JsonSerializer.Deserialize<RequestId>(JsonSerializer.Serialize(id, McpJsonUtilities.DefaultOptions), McpJsonUtilities.DefaultOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -25,7 +26,7 @@ public class RequestIdTests
|
||||||
{
|
{
|
||||||
RequestId id = new(42);
|
RequestId id = new(42);
|
||||||
Assert.Equal("42", id.ToString());
|
Assert.Equal("42", id.ToString());
|
||||||
Assert.Equal("42", JsonSerializer.Serialize(id));
|
Assert.Equal("42", JsonSerializer.Serialize(id, McpJsonUtilities.DefaultOptions));
|
||||||
Assert.Equal(42, Assert.IsType<long>(id.Id));
|
Assert.Equal(42, Assert.IsType<long>(id.Id));
|
||||||
|
|
||||||
Assert.True(id.Equals(new(42)));
|
Assert.True(id.Equals(new(42)));
|
||||||
|
|
@ -33,6 +34,6 @@ public class RequestIdTests
|
||||||
Assert.False(id.Equals(new("42")));
|
Assert.False(id.Equals(new("42")));
|
||||||
Assert.Equal(42L.GetHashCode(), id.GetHashCode());
|
Assert.Equal(42L.GetHashCode(), id.GetHashCode());
|
||||||
|
|
||||||
Assert.Equal(id, JsonSerializer.Deserialize<RequestId>(JsonSerializer.Serialize(id)));
|
Assert.Equal(id, JsonSerializer.Deserialize<RequestId>(JsonSerializer.Serialize(id, McpJsonUtilities.DefaultOptions), McpJsonUtilities.DefaultOptions));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using ModelContextProtocol.Protocol.Messages;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
using ModelContextProtocol.Tests.Utils;
|
using ModelContextProtocol.Tests.Utils;
|
||||||
|
using ModelContextProtocol.Utils.Json;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
|
|
@ -185,7 +186,7 @@ public class McpServerTests : LoggedTest
|
||||||
configureOptions: null,
|
configureOptions: null,
|
||||||
assertResult: response =>
|
assertResult: response =>
|
||||||
{
|
{
|
||||||
var result = JsonSerializer.Deserialize<InitializeResult>(response);
|
var result = JsonSerializer.Deserialize<InitializeResult>(response, McpJsonUtilities.DefaultOptions);
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.Equal("ModelContextProtocol.Tests", result.ServerInfo.Name);
|
Assert.Equal("ModelContextProtocol.Tests", result.ServerInfo.Name);
|
||||||
Assert.Equal("1.0.0.0", result.ServerInfo.Version);
|
Assert.Equal("1.0.0.0", result.ServerInfo.Version);
|
||||||
|
|
@ -217,7 +218,7 @@ public class McpServerTests : LoggedTest
|
||||||
configureOptions: null,
|
configureOptions: null,
|
||||||
assertResult: response =>
|
assertResult: response =>
|
||||||
{
|
{
|
||||||
var result = JsonSerializer.Deserialize<CompleteResult>(response);
|
var result = JsonSerializer.Deserialize<CompleteResult>(response, McpJsonUtilities.DefaultOptions);
|
||||||
Assert.NotNull(result?.Completion);
|
Assert.NotNull(result?.Completion);
|
||||||
Assert.Equal(["test"], result.Completion.Values);
|
Assert.Equal(["test"], result.Completion.Values);
|
||||||
Assert.Equal(2, result.Completion.Total);
|
Assert.Equal(2, result.Completion.Total);
|
||||||
|
|
@ -254,7 +255,7 @@ public class McpServerTests : LoggedTest
|
||||||
configureOptions: null,
|
configureOptions: null,
|
||||||
assertResult: response =>
|
assertResult: response =>
|
||||||
{
|
{
|
||||||
var result = JsonSerializer.Deserialize<ListResourceTemplatesResult>(response);
|
var result = JsonSerializer.Deserialize<ListResourceTemplatesResult>(response, McpJsonUtilities.DefaultOptions);
|
||||||
Assert.NotNull(result?.ResourceTemplates);
|
Assert.NotNull(result?.ResourceTemplates);
|
||||||
Assert.NotEmpty(result.ResourceTemplates);
|
Assert.NotEmpty(result.ResourceTemplates);
|
||||||
Assert.Equal("test", result.ResourceTemplates[0].UriTemplate);
|
Assert.Equal("test", result.ResourceTemplates[0].UriTemplate);
|
||||||
|
|
@ -283,7 +284,7 @@ public class McpServerTests : LoggedTest
|
||||||
configureOptions: null,
|
configureOptions: null,
|
||||||
assertResult: response =>
|
assertResult: response =>
|
||||||
{
|
{
|
||||||
var result = JsonSerializer.Deserialize<ListResourcesResult>(response);
|
var result = JsonSerializer.Deserialize<ListResourcesResult>(response, McpJsonUtilities.DefaultOptions);
|
||||||
Assert.NotNull(result?.Resources);
|
Assert.NotNull(result?.Resources);
|
||||||
Assert.NotEmpty(result.Resources);
|
Assert.NotEmpty(result.Resources);
|
||||||
Assert.Equal("test", result.Resources[0].Uri);
|
Assert.Equal("test", result.Resources[0].Uri);
|
||||||
|
|
@ -318,7 +319,7 @@ public class McpServerTests : LoggedTest
|
||||||
configureOptions: null,
|
configureOptions: null,
|
||||||
assertResult: response =>
|
assertResult: response =>
|
||||||
{
|
{
|
||||||
var result = JsonSerializer.Deserialize<ReadResourceResult>(response);
|
var result = JsonSerializer.Deserialize<ReadResourceResult>(response, McpJsonUtilities.DefaultOptions);
|
||||||
Assert.NotNull(result?.Contents);
|
Assert.NotNull(result?.Contents);
|
||||||
Assert.NotEmpty(result.Contents);
|
Assert.NotEmpty(result.Contents);
|
||||||
|
|
||||||
|
|
@ -355,7 +356,7 @@ public class McpServerTests : LoggedTest
|
||||||
configureOptions: null,
|
configureOptions: null,
|
||||||
assertResult: response =>
|
assertResult: response =>
|
||||||
{
|
{
|
||||||
var result = JsonSerializer.Deserialize<ListPromptsResult>(response);
|
var result = JsonSerializer.Deserialize<ListPromptsResult>(response, McpJsonUtilities.DefaultOptions);
|
||||||
Assert.NotNull(result?.Prompts);
|
Assert.NotNull(result?.Prompts);
|
||||||
Assert.NotEmpty(result.Prompts);
|
Assert.NotEmpty(result.Prompts);
|
||||||
Assert.Equal("test", result.Prompts[0].Name);
|
Assert.Equal("test", result.Prompts[0].Name);
|
||||||
|
|
@ -384,7 +385,7 @@ public class McpServerTests : LoggedTest
|
||||||
configureOptions: null,
|
configureOptions: null,
|
||||||
assertResult: response =>
|
assertResult: response =>
|
||||||
{
|
{
|
||||||
var result = JsonSerializer.Deserialize<GetPromptResult>(response);
|
var result = JsonSerializer.Deserialize<GetPromptResult>(response, McpJsonUtilities.DefaultOptions);
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.Equal("test", result.Description);
|
Assert.Equal("test", result.Description);
|
||||||
});
|
});
|
||||||
|
|
@ -418,7 +419,7 @@ public class McpServerTests : LoggedTest
|
||||||
configureOptions: null,
|
configureOptions: null,
|
||||||
assertResult: response =>
|
assertResult: response =>
|
||||||
{
|
{
|
||||||
var result = JsonSerializer.Deserialize<ListToolsResult>(response);
|
var result = JsonSerializer.Deserialize<ListToolsResult>(response, McpJsonUtilities.DefaultOptions);
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.NotEmpty(result.Tools);
|
Assert.NotEmpty(result.Tools);
|
||||||
Assert.Equal("test", result.Tools[0].Name);
|
Assert.Equal("test", result.Tools[0].Name);
|
||||||
|
|
@ -453,7 +454,7 @@ public class McpServerTests : LoggedTest
|
||||||
configureOptions: null,
|
configureOptions: null,
|
||||||
assertResult: response =>
|
assertResult: response =>
|
||||||
{
|
{
|
||||||
var result = JsonSerializer.Deserialize<CallToolResponse>(response);
|
var result = JsonSerializer.Deserialize<CallToolResponse>(response, McpJsonUtilities.DefaultOptions);
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.NotEmpty(result.Content);
|
Assert.NotEmpty(result.Content);
|
||||||
Assert.Equal("test", result.Content[0].Text);
|
Assert.Equal("test", result.Content[0].Text);
|
||||||
|
|
@ -581,7 +582,7 @@ public class McpServerTests : LoggedTest
|
||||||
|
|
||||||
public Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken)
|
public Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
CreateMessageRequestParams? rp = JsonSerializer.Deserialize<CreateMessageRequestParams>(request.Params);
|
CreateMessageRequestParams? rp = JsonSerializer.Deserialize<CreateMessageRequestParams>(request.Params, McpJsonUtilities.DefaultOptions);
|
||||||
|
|
||||||
Assert.NotNull(rp);
|
Assert.NotNull(rp);
|
||||||
Assert.Equal(0.75f, rp.Temperature);
|
Assert.Equal(0.75f, rp.Temperature);
|
||||||
|
|
@ -608,7 +609,7 @@ public class McpServerTests : LoggedTest
|
||||||
return Task.FromResult(new JsonRpcResponse
|
return Task.FromResult(new JsonRpcResponse
|
||||||
{
|
{
|
||||||
Id = new RequestId("0"),
|
Id = new RequestId("0"),
|
||||||
Result = JsonSerializer.SerializeToNode(result),
|
Result = JsonSerializer.SerializeToNode(result, McpJsonUtilities.DefaultOptions),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -658,11 +659,11 @@ public class McpServerTests : LoggedTest
|
||||||
Total = 100,
|
Total = 100,
|
||||||
Message = "Progress message",
|
Message = "Progress message",
|
||||||
},
|
},
|
||||||
}),
|
}, McpJsonUtilities.DefaultOptions),
|
||||||
}, TestContext.Current.CancellationToken);
|
}, TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
var notification = await notificationReceived.Task;
|
var notification = await notificationReceived.Task;
|
||||||
var progress = JsonSerializer.Deserialize<ProgressNotification>(notification.Params);
|
var progress = JsonSerializer.Deserialize<ProgressNotification>(notification.Params, McpJsonUtilities.DefaultOptions);
|
||||||
Assert.NotNull(progress);
|
Assert.NotNull(progress);
|
||||||
Assert.Equal("abc", progress.ProgressToken.ToString());
|
Assert.Equal("abc", progress.ProgressToken.ToString());
|
||||||
Assert.Equal(50, progress.Progress.Progress);
|
Assert.Equal(50, progress.Progress.Progress);
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
|
using ModelContextProtocol.Utils.Json;
|
||||||
using Moq;
|
using Moq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
|
@ -9,7 +10,7 @@ using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Tests.Server;
|
namespace ModelContextProtocol.Tests.Server;
|
||||||
|
|
||||||
public class McpServerToolTests
|
public partial class McpServerToolTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Create_InvalidArgs_Throws()
|
public void Create_InvalidArgs_Throws()
|
||||||
|
|
@ -36,7 +37,7 @@ public class McpServerToolTests
|
||||||
return "42";
|
return "42";
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.DoesNotContain("server", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema));
|
Assert.DoesNotContain("server", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema, McpJsonUtilities.DefaultOptions));
|
||||||
|
|
||||||
var result = await tool.InvokeAsync(
|
var result = await tool.InvokeAsync(
|
||||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||||
|
|
@ -86,7 +87,7 @@ public class McpServerToolTests
|
||||||
|
|
||||||
McpServerTool tool = services.GetRequiredService<McpServerTool>();
|
McpServerTool tool = services.GetRequiredService<McpServerTool>();
|
||||||
|
|
||||||
Assert.DoesNotContain("actualMyService", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema));
|
Assert.DoesNotContain("actualMyService", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema, McpJsonUtilities.DefaultOptions));
|
||||||
|
|
||||||
Mock<IMcpServer> mockServer = new();
|
Mock<IMcpServer> mockServer = new();
|
||||||
|
|
||||||
|
|
@ -127,9 +128,11 @@ public class McpServerToolTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SupportsDisposingInstantiatedDisposableTargets()
|
public async Task SupportsDisposingInstantiatedDisposableTargets()
|
||||||
{
|
{
|
||||||
|
McpServerToolCreateOptions options = new() { SerializerOptions = JsonContext2.Default.Options };
|
||||||
McpServerTool tool1 = McpServerTool.Create(
|
McpServerTool tool1 = McpServerTool.Create(
|
||||||
typeof(DisposableToolType).GetMethod(nameof(DisposableToolType.InstanceMethod))!,
|
typeof(DisposableToolType).GetMethod(nameof(DisposableToolType.InstanceMethod))!,
|
||||||
typeof(DisposableToolType));
|
typeof(DisposableToolType),
|
||||||
|
options);
|
||||||
|
|
||||||
var result = await tool1.InvokeAsync(
|
var result = await tool1.InvokeAsync(
|
||||||
new RequestContext<CallToolRequestParams>(null!, null),
|
new RequestContext<CallToolRequestParams>(null!, null),
|
||||||
|
|
@ -140,9 +143,11 @@ public class McpServerToolTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableTargets()
|
public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableTargets()
|
||||||
{
|
{
|
||||||
|
McpServerToolCreateOptions options = new() { SerializerOptions = JsonContext2.Default.Options };
|
||||||
McpServerTool tool1 = McpServerTool.Create(
|
McpServerTool tool1 = McpServerTool.Create(
|
||||||
typeof(AsyncDisposableToolType).GetMethod(nameof(AsyncDisposableToolType.InstanceMethod))!,
|
typeof(AsyncDisposableToolType).GetMethod(nameof(AsyncDisposableToolType.InstanceMethod))!,
|
||||||
typeof(AsyncDisposableToolType));
|
typeof(AsyncDisposableToolType),
|
||||||
|
options);
|
||||||
|
|
||||||
var result = await tool1.InvokeAsync(
|
var result = await tool1.InvokeAsync(
|
||||||
new RequestContext<CallToolRequestParams>(null!, null),
|
new RequestContext<CallToolRequestParams>(null!, null),
|
||||||
|
|
@ -153,9 +158,11 @@ public class McpServerToolTests
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableAndDisposableTargets()
|
public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableAndDisposableTargets()
|
||||||
{
|
{
|
||||||
|
McpServerToolCreateOptions options = new() { SerializerOptions = JsonContext2.Default.Options };
|
||||||
McpServerTool tool1 = McpServerTool.Create(
|
McpServerTool tool1 = McpServerTool.Create(
|
||||||
typeof(AsyncDisposableAndDisposableToolType).GetMethod(nameof(AsyncDisposableAndDisposableToolType.InstanceMethod))!,
|
typeof(AsyncDisposableAndDisposableToolType).GetMethod(nameof(AsyncDisposableAndDisposableToolType.InstanceMethod))!,
|
||||||
typeof(AsyncDisposableAndDisposableToolType));
|
typeof(AsyncDisposableAndDisposableToolType),
|
||||||
|
options);
|
||||||
|
|
||||||
var result = await tool1.InvokeAsync(
|
var result = await tool1.InvokeAsync(
|
||||||
new RequestContext<CallToolRequestParams>(null!, null),
|
new RequestContext<CallToolRequestParams>(null!, null),
|
||||||
|
|
@ -432,4 +439,10 @@ public class McpServerToolTests
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
|
||||||
|
[JsonSerializable(typeof(DisposableToolType))]
|
||||||
|
[JsonSerializable(typeof(AsyncDisposableToolType))]
|
||||||
|
[JsonSerializable(typeof(AsyncDisposableAndDisposableToolType))]
|
||||||
|
partial class JsonContext2 : JsonSerializerContext;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ public class StdioServerTransportTests : LoggedTest
|
||||||
var chineseResult = Encoding.UTF8.GetString(output.ToArray()).Trim();
|
var chineseResult = Encoding.UTF8.GetString(output.ToArray()).Trim();
|
||||||
var expectedChinese = JsonSerializer.Serialize(chineseMessage, McpJsonUtilities.DefaultOptions);
|
var expectedChinese = JsonSerializer.Serialize(chineseMessage, McpJsonUtilities.DefaultOptions);
|
||||||
Assert.Equal(expectedChinese, chineseResult);
|
Assert.Equal(expectedChinese, chineseResult);
|
||||||
Assert.Contains(JsonSerializer.Serialize(chineseText), chineseResult);
|
Assert.Contains(JsonSerializer.Serialize(chineseText, McpJsonUtilities.DefaultOptions), chineseResult);
|
||||||
|
|
||||||
// Test 2: Emoji (non-BMP Unicode using surrogate pairs)
|
// Test 2: Emoji (non-BMP Unicode using surrogate pairs)
|
||||||
var emojiText = "🔍 🚀 👍"; // Magnifying glass, rocket, thumbs up
|
var emojiText = "🔍 🚀 👍"; // Magnifying glass, rocket, thumbs up
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using ModelContextProtocol.Protocol.Messages;
|
using ModelContextProtocol.Protocol.Messages;
|
||||||
using ModelContextProtocol.Protocol.Transport;
|
using ModelContextProtocol.Protocol.Transport;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
|
using ModelContextProtocol.Utils.Json;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
|
|
||||||
|
|
@ -63,7 +64,7 @@ public class TestServerTransport : ITransport
|
||||||
Result = JsonSerializer.SerializeToNode(new ListRootsResult
|
Result = JsonSerializer.SerializeToNode(new ListRootsResult
|
||||||
{
|
{
|
||||||
Roots = []
|
Roots = []
|
||||||
}),
|
}, McpJsonUtilities.DefaultOptions),
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +73,7 @@ public class TestServerTransport : ITransport
|
||||||
await WriteMessageAsync(new JsonRpcResponse
|
await WriteMessageAsync(new JsonRpcResponse
|
||||||
{
|
{
|
||||||
Id = request.Id,
|
Id = request.Id,
|
||||||
Result = JsonSerializer.SerializeToNode(new CreateMessageResult { Content = new(), Model = "model", Role = Role.User }),
|
Result = JsonSerializer.SerializeToNode(new CreateMessageResult { Content = new(), Model = "model", Role = Role.User }, McpJsonUtilities.DefaultOptions),
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue