Change default name casing of McpServerXx.Create tools/prompts/resources (#568)
This commit is contained in:
parent
4e290f8de9
commit
4411f1ce5e
10 changed files with 104 additions and 49 deletions
|
|
@ -68,7 +68,7 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
|
||||||
MethodInfo method, McpServerPromptCreateOptions? options) =>
|
MethodInfo method, McpServerPromptCreateOptions? options) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Name = options?.Name ?? method.GetCustomAttribute<McpServerPromptAttribute>()?.Name,
|
Name = options?.Name ?? method.GetCustomAttribute<McpServerPromptAttribute>()?.Name ?? AIFunctionMcpServerTool.DeriveName(method),
|
||||||
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,
|
SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource
|
||||||
MethodInfo method, McpServerResourceCreateOptions? options) =>
|
MethodInfo method, McpServerResourceCreateOptions? options) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Name = options?.Name ?? method.GetCustomAttribute<McpServerResourceAttribute>()?.Name,
|
Name = options?.Name ?? method.GetCustomAttribute<McpServerResourceAttribute>()?.Name ?? AIFunctionMcpServerTool.DeriveName(method),
|
||||||
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,
|
SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Nodes;
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Server;
|
namespace ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
|
@ -74,7 +75,7 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
|
||||||
MethodInfo method, McpServerToolCreateOptions? options) =>
|
MethodInfo method, McpServerToolCreateOptions? options) =>
|
||||||
new()
|
new()
|
||||||
{
|
{
|
||||||
Name = options?.Name ?? method.GetCustomAttribute<McpServerToolAttribute>()?.Name,
|
Name = options?.Name ?? method.GetCustomAttribute<McpServerToolAttribute>()?.Name ?? DeriveName(method),
|
||||||
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,
|
SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions,
|
||||||
|
|
@ -293,6 +294,63 @@ internal sealed partial class AIFunctionMcpServerTool : McpServerTool
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Creates a name to use based on the supplied method and naming policy.</summary>
|
||||||
|
internal static string DeriveName(MethodInfo method, JsonNamingPolicy? policy = null)
|
||||||
|
{
|
||||||
|
string name = method.Name;
|
||||||
|
|
||||||
|
// Remove any "Async" suffix if the method is an async method and if the method name isn't just "Async".
|
||||||
|
const string AsyncSuffix = "Async";
|
||||||
|
if (IsAsyncMethod(method) &&
|
||||||
|
name.EndsWith(AsyncSuffix, StringComparison.Ordinal) &&
|
||||||
|
name.Length > AsyncSuffix.Length)
|
||||||
|
{
|
||||||
|
name = name.Substring(0, name.Length - AsyncSuffix.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace anything other than ASCII letters or digits with underscores, trim off any leading or trailing underscores.
|
||||||
|
name = NonAsciiLetterDigitsRegex().Replace(name, "_").Trim('_');
|
||||||
|
|
||||||
|
// If after all our transformations the name is empty, just use the original method name.
|
||||||
|
if (name.Length == 0)
|
||||||
|
{
|
||||||
|
name = method.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case the name based on the provided naming policy.
|
||||||
|
return (policy ?? JsonNamingPolicy.SnakeCaseLower).ConvertName(name) ?? name;
|
||||||
|
|
||||||
|
static bool IsAsyncMethod(MethodInfo method)
|
||||||
|
{
|
||||||
|
Type t = method.ReturnType;
|
||||||
|
|
||||||
|
if (t == typeof(Task) || t == typeof(ValueTask))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t.IsGenericType)
|
||||||
|
{
|
||||||
|
t = t.GetGenericTypeDefinition();
|
||||||
|
if (t == typeof(Task<>) || t == typeof(ValueTask<>) || t == typeof(IAsyncEnumerable<>))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Regex that flags runs of characters other than ASCII digits or letters.</summary>
|
||||||
|
#if NET
|
||||||
|
[GeneratedRegex("[^0-9A-Za-z]+")]
|
||||||
|
private static partial Regex NonAsciiLetterDigitsRegex();
|
||||||
|
#else
|
||||||
|
private static Regex NonAsciiLetterDigitsRegex() => _nonAsciiLetterDigits;
|
||||||
|
private static readonly Regex _nonAsciiLetterDigits = new("[^0-9A-Za-z]+", RegexOptions.Compiled);
|
||||||
|
#endif
|
||||||
|
|
||||||
private static JsonElement? CreateOutputSchema(AIFunction function, McpServerToolCreateOptions? toolCreateOptions, out bool structuredOutputRequiresWrapping)
|
private static JsonElement? CreateOutputSchema(AIFunction function, McpServerToolCreateOptions? toolCreateOptions, out bool structuredOutputRequiresWrapping)
|
||||||
{
|
{
|
||||||
structuredOutputRequiresWrapping = false;
|
structuredOutputRequiresWrapping = false;
|
||||||
|
|
|
||||||
|
|
@ -80,7 +80,7 @@ public abstract class MapMcpTests(ITestOutputHelper testOutputHelper) : KestrelI
|
||||||
await using var mcpClient = await ConnectAsync();
|
await using var mcpClient = await ConnectAsync();
|
||||||
|
|
||||||
var response = await mcpClient.CallToolAsync(
|
var response = await mcpClient.CallToolAsync(
|
||||||
"EchoWithUserName",
|
"echo_with_user_name",
|
||||||
new Dictionary<string, object?>() { ["message"] = "Hello world!" },
|
new Dictionary<string, object?>() { ["message"] = "Hello world!" },
|
||||||
cancellationToken: TestContext.Current.CancellationToken);
|
cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -149,11 +149,11 @@ public partial class SseIntegrationTests(ITestOutputHelper outputHelper) : Kestr
|
||||||
var tools = await mcpClient.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
var tools = await mcpClient.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
Assert.Equal(2, tools.Count);
|
Assert.Equal(2, tools.Count);
|
||||||
Assert.Contains(tools, tools => tools.Name == "Echo");
|
Assert.Contains(tools, tools => tools.Name == "echo");
|
||||||
Assert.Contains(tools, tools => tools.Name == "sampleLLM");
|
Assert.Contains(tools, tools => tools.Name == "sampleLLM");
|
||||||
|
|
||||||
var echoResponse = await mcpClient.CallToolAsync(
|
var echoResponse = await mcpClient.CallToolAsync(
|
||||||
"Echo",
|
"echo",
|
||||||
new Dictionary<string, object?>
|
new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
["message"] = "from client!"
|
["message"] = "from client!"
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
||||||
var prompts = await client.ListPromptsAsync(TestContext.Current.CancellationToken);
|
var prompts = await client.ListPromptsAsync(TestContext.Current.CancellationToken);
|
||||||
Assert.Equal(6, prompts.Count);
|
Assert.Equal(6, prompts.Count);
|
||||||
|
|
||||||
var prompt = prompts.First(t => t.Name == nameof(SimplePrompts.ReturnsChatMessages));
|
var prompt = prompts.First(t => t.Name == "returns_chat_messages");
|
||||||
Assert.Equal("Returns chat messages", prompt.Description);
|
Assert.Equal("Returns chat messages", prompt.Description);
|
||||||
|
|
||||||
var result = await prompt.GetAsync(new Dictionary<string, object?>() { ["message"] = "hello" }, cancellationToken: TestContext.Current.CancellationToken);
|
var result = await prompt.GetAsync(new Dictionary<string, object?>() { ["message"] = "hello" }, cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
@ -171,7 +171,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
||||||
Assert.NotNull(prompts);
|
Assert.NotNull(prompts);
|
||||||
Assert.NotEmpty(prompts);
|
Assert.NotEmpty(prompts);
|
||||||
|
|
||||||
McpClientPrompt prompt = prompts.First(t => t.Name == nameof(SimplePrompts.ReturnsString));
|
McpClientPrompt prompt = prompts.First(t => t.Name == "returns_string");
|
||||||
|
|
||||||
Assert.Equal("This is a title", prompt.Title);
|
Assert.Equal("This is a title", prompt.Title);
|
||||||
}
|
}
|
||||||
|
|
@ -204,7 +204,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
||||||
await using IMcpClient client = await CreateMcpClientForServer();
|
await using IMcpClient client = await CreateMcpClientForServer();
|
||||||
|
|
||||||
var e = await Assert.ThrowsAsync<McpException>(async () => await client.GetPromptAsync(
|
var e = await Assert.ThrowsAsync<McpException>(async () => await client.GetPromptAsync(
|
||||||
nameof(SimplePrompts.ReturnsChatMessages),
|
"returns_chat_messages",
|
||||||
cancellationToken: TestContext.Current.CancellationToken));
|
cancellationToken: TestContext.Current.CancellationToken));
|
||||||
|
|
||||||
Assert.Equal(McpErrorCode.InternalError, e.ErrorCode);
|
Assert.Equal(McpErrorCode.InternalError, e.ErrorCode);
|
||||||
|
|
@ -242,7 +242,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
||||||
sc.AddMcpServer().WithPromptsFromAssembly();
|
sc.AddMcpServer().WithPromptsFromAssembly();
|
||||||
IServiceProvider services = sc.BuildServiceProvider();
|
IServiceProvider services = sc.BuildServiceProvider();
|
||||||
|
|
||||||
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == nameof(SimplePrompts.ReturnsChatMessages));
|
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == "returns_chat_messages");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -255,10 +255,10 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
||||||
.WithPrompts([McpServerPrompt.Create(() => "42", new() { Name = "Returns42" })]);
|
.WithPrompts([McpServerPrompt.Create(() => "42", new() { Name = "Returns42" })]);
|
||||||
IServiceProvider services = sc.BuildServiceProvider();
|
IServiceProvider services = sc.BuildServiceProvider();
|
||||||
|
|
||||||
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == nameof(SimplePrompts.ReturnsChatMessages));
|
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == "returns_chat_messages");
|
||||||
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == nameof(SimplePrompts.ThrowsException));
|
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == "throws_exception");
|
||||||
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == nameof(SimplePrompts.ReturnsString));
|
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == "returns_string");
|
||||||
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == nameof(MorePrompts.AnotherPrompt));
|
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == "another_prompt");
|
||||||
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == "Returns42");
|
Assert.Contains(services.GetServices<McpServerPrompt>(), t => t.ProtocolPrompt.Name == "Returns42");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
||||||
var resources = await client.ListResourcesAsync(TestContext.Current.CancellationToken);
|
var resources = await client.ListResourcesAsync(TestContext.Current.CancellationToken);
|
||||||
Assert.Equal(5, resources.Count);
|
Assert.Equal(5, resources.Count);
|
||||||
|
|
||||||
var resource = resources.First(t => t.Name == nameof(SimpleResources.SomeNeatDirectResource));
|
var resource = resources.First(t => t.Name == "some_neat_direct_resource");
|
||||||
Assert.Equal("Some neat direct resource", resource.Description);
|
Assert.Equal("Some neat direct resource", resource.Description);
|
||||||
|
|
||||||
var result = await resource.ReadAsync(cancellationToken: TestContext.Current.CancellationToken);
|
var result = await resource.ReadAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
@ -146,7 +146,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
||||||
var resources = await client.ListResourceTemplatesAsync(TestContext.Current.CancellationToken);
|
var resources = await client.ListResourceTemplatesAsync(TestContext.Current.CancellationToken);
|
||||||
Assert.Equal(3, resources.Count);
|
Assert.Equal(3, resources.Count);
|
||||||
|
|
||||||
var resource = resources.First(t => t.Name == nameof(SimpleResources.SomeNeatTemplatedResource));
|
var resource = resources.First(t => t.Name == "some_neat_templated_resource");
|
||||||
Assert.Equal("Some neat resource with parameters", resource.Description);
|
Assert.Equal("Some neat resource with parameters", resource.Description);
|
||||||
|
|
||||||
var result = await resource.ReadAsync(new Dictionary<string, object?>() { ["name"] = "hello" }, cancellationToken: TestContext.Current.CancellationToken);
|
var result = await resource.ReadAsync(new Dictionary<string, object?>() { ["name"] = "hello" }, cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
@ -204,13 +204,13 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
||||||
var resources = await client.ListResourcesAsync(cancellationToken: TestContext.Current.CancellationToken);
|
var resources = await client.ListResourcesAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||||
Assert.NotNull(resources);
|
Assert.NotNull(resources);
|
||||||
Assert.NotEmpty(resources);
|
Assert.NotEmpty(resources);
|
||||||
McpClientResource resource = resources.First(t => t.Name == nameof(SimpleResources.SomeNeatDirectResource));
|
McpClientResource resource = resources.First(t => t.Name == "some_neat_direct_resource");
|
||||||
Assert.Equal("This is a title", resource.Title);
|
Assert.Equal("This is a title", resource.Title);
|
||||||
|
|
||||||
var resourceTemplates = await client.ListResourceTemplatesAsync(cancellationToken: TestContext.Current.CancellationToken);
|
var resourceTemplates = await client.ListResourceTemplatesAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||||
Assert.NotNull(resourceTemplates);
|
Assert.NotNull(resourceTemplates);
|
||||||
Assert.NotEmpty(resourceTemplates);
|
Assert.NotEmpty(resourceTemplates);
|
||||||
McpClientResourceTemplate resourceTemplate = resourceTemplates.First(t => t.Name == nameof(SimpleResources.SomeNeatTemplatedResource));
|
McpClientResourceTemplate resourceTemplate = resourceTemplates.First(t => t.Name == "some_neat_templated_resource");
|
||||||
Assert.Equal("This is another title", resourceTemplate.Title);
|
Assert.Equal("This is another title", resourceTemplate.Title);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,8 +268,8 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
||||||
sc.AddMcpServer().WithResourcesFromAssembly();
|
sc.AddMcpServer().WithResourcesFromAssembly();
|
||||||
IServiceProvider services = sc.BuildServiceProvider();
|
IServiceProvider services = sc.BuildServiceProvider();
|
||||||
|
|
||||||
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResource?.Uri == $"resource://mcp/{nameof(SimpleResources.SomeNeatDirectResource)}");
|
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResource?.Uri == $"resource://mcp/some_neat_direct_resource");
|
||||||
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate?.UriTemplate == $"resource://mcp/{nameof(SimpleResources.SomeNeatTemplatedResource)}{{?name}}");
|
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate?.UriTemplate == $"resource://mcp/some_neat_templated_resource{{?name}}");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -282,9 +282,9 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
||||||
.WithResources([McpServerResource.Create(() => "42", new() { UriTemplate = "myResources:///returns42/{something}" })]);
|
.WithResources([McpServerResource.Create(() => "42", new() { UriTemplate = "myResources:///returns42/{something}" })]);
|
||||||
IServiceProvider services = sc.BuildServiceProvider();
|
IServiceProvider services = sc.BuildServiceProvider();
|
||||||
|
|
||||||
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResource?.Uri == $"resource://mcp/{nameof(SimpleResources.SomeNeatDirectResource)}");
|
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResource?.Uri == $"resource://mcp/some_neat_direct_resource");
|
||||||
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate?.UriTemplate == $"resource://mcp/{nameof(SimpleResources.SomeNeatTemplatedResource)}{{?name}}");
|
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate?.UriTemplate == $"resource://mcp/some_neat_templated_resource{{?name}}");
|
||||||
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate?.UriTemplate == $"resource://mcp/{nameof(MoreResources.AnotherNeatDirectResource)}");
|
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate?.UriTemplate == $"resource://mcp/another_neat_direct_resource");
|
||||||
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate.UriTemplate == "myResources:///returns42/{something}");
|
Assert.Contains(services.GetServices<McpServerResource>(), t => t.ProtocolResourceTemplate.UriTemplate == "myResources:///returns42/{something}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -126,8 +126,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||||
Assert.Equal(16, tools.Count);
|
Assert.Equal(16, tools.Count);
|
||||||
|
|
||||||
McpClientTool echoTool = tools.First(t => t.Name == "Echo");
|
McpClientTool echoTool = tools.First(t => t.Name == "echo");
|
||||||
Assert.Equal("Echo", echoTool.Name);
|
|
||||||
Assert.Equal("Echoes the input back to the client.", echoTool.Description);
|
Assert.Equal("Echoes the input back to the client.", echoTool.Description);
|
||||||
Assert.Equal("object", echoTool.JsonSchema.GetProperty("type").GetString());
|
Assert.Equal("object", echoTool.JsonSchema.GetProperty("type").GetString());
|
||||||
Assert.Equal(JsonValueKind.Object, echoTool.JsonSchema.GetProperty("properties").GetProperty("message").ValueKind);
|
Assert.Equal(JsonValueKind.Object, echoTool.JsonSchema.GetProperty("properties").GetProperty("message").ValueKind);
|
||||||
|
|
@ -165,8 +164,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||||
Assert.Equal(16, tools.Count);
|
Assert.Equal(16, tools.Count);
|
||||||
|
|
||||||
McpClientTool echoTool = tools.First(t => t.Name == "Echo");
|
McpClientTool echoTool = tools.First(t => t.Name == "echo");
|
||||||
Assert.Equal("Echo", echoTool.Name);
|
|
||||||
Assert.Equal("Echoes the input back to the client.", echoTool.Description);
|
Assert.Equal("Echoes the input back to the client.", echoTool.Description);
|
||||||
Assert.Equal("object", echoTool.JsonSchema.GetProperty("type").GetString());
|
Assert.Equal("object", echoTool.JsonSchema.GetProperty("type").GetString());
|
||||||
Assert.Equal(JsonValueKind.Object, echoTool.JsonSchema.GetProperty("properties").GetProperty("message").ValueKind);
|
Assert.Equal(JsonValueKind.Object, echoTool.JsonSchema.GetProperty("properties").GetProperty("message").ValueKind);
|
||||||
|
|
@ -231,7 +229,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
await using IMcpClient client = await CreateMcpClientForServer();
|
await using IMcpClient client = await CreateMcpClientForServer();
|
||||||
|
|
||||||
var result = await client.CallToolAsync(
|
var result = await client.CallToolAsync(
|
||||||
"Echo",
|
"echo",
|
||||||
new Dictionary<string, object?>() { ["message"] = "Peter" },
|
new Dictionary<string, object?>() { ["message"] = "Peter" },
|
||||||
cancellationToken: TestContext.Current.CancellationToken);
|
cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
|
|
@ -250,7 +248,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
await using IMcpClient client = await CreateMcpClientForServer();
|
await using IMcpClient client = await CreateMcpClientForServer();
|
||||||
|
|
||||||
var result = await client.CallToolAsync(
|
var result = await client.CallToolAsync(
|
||||||
"EchoArray",
|
"echo_array",
|
||||||
new Dictionary<string, object?>() { ["message"] = "Peter" },
|
new Dictionary<string, object?>() { ["message"] = "Peter" },
|
||||||
cancellationToken: TestContext.Current.CancellationToken);
|
cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
|
|
@ -274,7 +272,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
await using IMcpClient client = await CreateMcpClientForServer();
|
await using IMcpClient client = await CreateMcpClientForServer();
|
||||||
|
|
||||||
var result = await client.CallToolAsync(
|
var result = await client.CallToolAsync(
|
||||||
"ReturnNull",
|
"return_null",
|
||||||
cancellationToken: TestContext.Current.CancellationToken);
|
cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
|
|
@ -288,7 +286,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
await using IMcpClient client = await CreateMcpClientForServer();
|
await using IMcpClient client = await CreateMcpClientForServer();
|
||||||
|
|
||||||
var result = await client.CallToolAsync(
|
var result = await client.CallToolAsync(
|
||||||
"ReturnJson",
|
"return_json",
|
||||||
cancellationToken: TestContext.Current.CancellationToken);
|
cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
|
|
@ -305,7 +303,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
await using IMcpClient client = await CreateMcpClientForServer();
|
await using IMcpClient client = await CreateMcpClientForServer();
|
||||||
|
|
||||||
var result = await client.CallToolAsync(
|
var result = await client.CallToolAsync(
|
||||||
"ReturnInteger",
|
"return_integer",
|
||||||
cancellationToken: TestContext.Current.CancellationToken);
|
cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
Assert.NotNull(result.Content);
|
Assert.NotNull(result.Content);
|
||||||
|
|
@ -320,7 +318,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
await using IMcpClient client = await CreateMcpClientForServer();
|
await using IMcpClient client = await CreateMcpClientForServer();
|
||||||
|
|
||||||
var result = await client.CallToolAsync(
|
var result = await client.CallToolAsync(
|
||||||
"EchoComplex",
|
"echo_complex",
|
||||||
new Dictionary<string, object?>() { ["complex"] = JsonDocument.Parse("""{"Name": "Peter", "Age": 25}""").RootElement },
|
new Dictionary<string, object?>() { ["complex"] = JsonDocument.Parse("""{"Name": "Peter", "Age": 25}""").RootElement },
|
||||||
cancellationToken: TestContext.Current.CancellationToken);
|
cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
|
|
@ -340,7 +338,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
for (int i = 0; i < 2; i++)
|
for (int i = 0; i < 2; i++)
|
||||||
{
|
{
|
||||||
var result = await client.CallToolAsync(
|
var result = await client.CallToolAsync(
|
||||||
nameof(EchoTool.GetCtorParameter),
|
"get_ctor_parameter",
|
||||||
cancellationToken: TestContext.Current.CancellationToken);
|
cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
|
|
@ -366,7 +364,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
await using IMcpClient client = await CreateMcpClientForServer();
|
await using IMcpClient client = await CreateMcpClientForServer();
|
||||||
|
|
||||||
var result = await client.CallToolAsync(
|
var result = await client.CallToolAsync(
|
||||||
"ThrowException",
|
"throw_exception",
|
||||||
cancellationToken: TestContext.Current.CancellationToken);
|
cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
Assert.True(result.IsError);
|
Assert.True(result.IsError);
|
||||||
|
|
@ -393,7 +391,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
await using IMcpClient client = await CreateMcpClientForServer();
|
await using IMcpClient client = await CreateMcpClientForServer();
|
||||||
|
|
||||||
var result = await client.CallToolAsync(
|
var result = await client.CallToolAsync(
|
||||||
"Echo",
|
"echo",
|
||||||
cancellationToken: TestContext.Current.CancellationToken);
|
cancellationToken: TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
Assert.True(result.IsError);
|
Assert.True(result.IsError);
|
||||||
|
|
@ -436,7 +434,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
sc.AddMcpServer().WithToolsFromAssembly();
|
sc.AddMcpServer().WithToolsFromAssembly();
|
||||||
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
@ -452,7 +450,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
sc.AddMcpServer().WithTools([typeof(EchoTool)], BuilderToolsJsonContext.Default.Options);
|
sc.AddMcpServer().WithTools([typeof(EchoTool)], BuilderToolsJsonContext.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 == "echo_complex");
|
||||||
if (parameterInServices)
|
if (parameterInServices)
|
||||||
{
|
{
|
||||||
Assert.DoesNotContain("\"complex\"", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema, AIJsonUtilities.DefaultOptions));
|
Assert.DoesNotContain("\"complex\"", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema, AIJsonUtilities.DefaultOptions));
|
||||||
|
|
@ -495,7 +493,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
sc.AddMcpServer().WithToolsFromAssembly();
|
sc.AddMcpServer().WithToolsFromAssembly();
|
||||||
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 == "echo_complex");
|
||||||
if (lifetime is not null)
|
if (lifetime is not null)
|
||||||
{
|
{
|
||||||
Assert.DoesNotContain("\"complex\"", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema, AIJsonUtilities.DefaultOptions));
|
Assert.DoesNotContain("\"complex\"", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema, AIJsonUtilities.DefaultOptions));
|
||||||
|
|
@ -516,8 +514,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
Assert.NotNull(tools);
|
Assert.NotNull(tools);
|
||||||
Assert.NotEmpty(tools);
|
Assert.NotEmpty(tools);
|
||||||
|
|
||||||
var tool = tools.First(t => t.Name == "TestTool");
|
var tool = tools.First(t => t.Name == "test_tool");
|
||||||
Assert.Equal("TestTool", tool.Name);
|
|
||||||
Assert.Empty(tool.Description!);
|
Assert.Empty(tool.Description!);
|
||||||
Assert.Equal("object", tool.JsonSchema.GetProperty("type").GetString());
|
Assert.Equal("object", tool.JsonSchema.GetProperty("type").GetString());
|
||||||
|
|
||||||
|
|
@ -543,9 +540,9 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
|
|
||||||
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "double_echo");
|
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "double_echo");
|
||||||
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "DifferentName");
|
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "DifferentName");
|
||||||
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "MethodB");
|
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "method_b");
|
||||||
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "MethodC");
|
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "method_c");
|
||||||
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "MethodD");
|
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "method_d");
|
||||||
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "Returns42");
|
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "Returns42");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -591,7 +588,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
Assert.NotNull(tools);
|
Assert.NotNull(tools);
|
||||||
Assert.NotEmpty(tools);
|
Assert.NotEmpty(tools);
|
||||||
|
|
||||||
McpClientTool tool = tools.First(t => t.Name == nameof(EchoTool.EchoComplex));
|
McpClientTool tool = tools.First(t => t.Name == "echo_complex");
|
||||||
|
|
||||||
Assert.Equal("This is a title", tool.Title);
|
Assert.Equal("This is a title", tool.Title);
|
||||||
Assert.Equal("This is a title", tool.ProtocolTool.Title);
|
Assert.Equal("This is a title", tool.ProtocolTool.Title);
|
||||||
|
|
@ -607,7 +604,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
Assert.NotNull(tools);
|
Assert.NotNull(tools);
|
||||||
Assert.NotEmpty(tools);
|
Assert.NotEmpty(tools);
|
||||||
|
|
||||||
McpClientTool progressTool = tools.First(t => t.Name == nameof(EchoTool.SendsProgressNotifications));
|
McpClientTool progressTool = tools.First(t => t.Name == "sends_progress_notifications");
|
||||||
|
|
||||||
TaskCompletionSource tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
TaskCompletionSource tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
int remainingNotifications = 10;
|
int remainingNotifications = 10;
|
||||||
|
|
@ -660,7 +657,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
||||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||||
Assert.NotNull(tools);
|
Assert.NotNull(tools);
|
||||||
Assert.NotEmpty(tools);
|
Assert.NotEmpty(tools);
|
||||||
McpClientTool cancelableTool = tools.First(t => t.Name == nameof(EchoTool.InfiniteCancelableOperation));
|
McpClientTool cancelableTool = tools.First(t => t.Name == "infinite_cancelable_operation");
|
||||||
|
|
||||||
var requestId = new RequestId(Guid.NewGuid().ToString());
|
var requestId = new RequestId(Guid.NewGuid().ToString());
|
||||||
var invokeTask = client.SendRequestAsync<CallToolRequestParams, CallToolResult>(
|
var invokeTask = client.SendRequestAsync<CallToolRequestParams, CallToolResult>(
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ public partial class McpServerScopedTests : ClientServerTestBase
|
||||||
await using IMcpClient client = await CreateMcpClientForServer();
|
await using IMcpClient client = await CreateMcpClientForServer();
|
||||||
|
|
||||||
var tools = await client.ListToolsAsync(McpServerScopedTestsJsonContext.Default.Options, TestContext.Current.CancellationToken);
|
var tools = await client.ListToolsAsync(McpServerScopedTestsJsonContext.Default.Options, TestContext.Current.CancellationToken);
|
||||||
var tool = tools.First(t => t.Name == nameof(EchoTool.EchoComplex));
|
var tool = tools.First(t => t.Name == "echo_complex");
|
||||||
Assert.DoesNotContain("\"complex\"", JsonSerializer.Serialize(tool.JsonSchema, McpJsonUtilities.DefaultOptions));
|
Assert.DoesNotContain("\"complex\"", JsonSerializer.Serialize(tool.JsonSchema, McpJsonUtilities.DefaultOptions));
|
||||||
|
|
||||||
int startingConstructed = ComplexObject.Constructed;
|
int startingConstructed = ComplexObject.Constructed;
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ public class CancellationTests : ClientServerTestBase
|
||||||
await using var client = await CreateMcpClientForServer();
|
await using var client = await CreateMcpClientForServer();
|
||||||
|
|
||||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||||
var waitTool = tools.First(t => t.Name == nameof(WaitForCancellation));
|
var waitTool = tools.First(t => t.Name == "wait_for_cancellation");
|
||||||
|
|
||||||
CancellationTokenSource cts = new();
|
CancellationTokenSource cts = new();
|
||||||
var waitTask = waitTool.InvokeAsync(cancellationToken: cts.Token);
|
var waitTask = waitTool.InvokeAsync(cancellationToken: cts.Token);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue