Add ToolAnnotations support (#124)
* Add ToolAnnotations support * Address feedback
This commit is contained in:
parent
2c1672cc1a
commit
62e92fa154
4 changed files with 645 additions and 394 deletions
|
|
@ -10,8 +10,8 @@ namespace ModelContextProtocol.Tests.Client;
|
|||
|
||||
public class McpClientExtensionsTests
|
||||
{
|
||||
private Pipe _clientToServerPipe = new();
|
||||
private Pipe _serverToClientPipe = new();
|
||||
private readonly Pipe _clientToServerPipe = new();
|
||||
private readonly Pipe _serverToClientPipe = new();
|
||||
private readonly IMcpServer _server;
|
||||
|
||||
public McpClientExtensionsTests()
|
||||
|
|
@ -22,8 +22,10 @@ public class McpClientExtensionsTests
|
|||
for (int f = 0; f < 10; f++)
|
||||
{
|
||||
string name = $"Method{f}";
|
||||
sc.AddSingleton(McpServerTool.Create((int i) => $"{name} Result {i}", name));
|
||||
sc.AddSingleton(McpServerTool.Create((int i) => $"{name} Result {i}", new() { Name = name }));
|
||||
}
|
||||
sc.AddSingleton(McpServerTool.Create([McpServerTool(Destructive = false, OpenWorld = true)](string i) => $"{i} Result", new() { Name = "ValuesSetViaAttr" }));
|
||||
sc.AddSingleton(McpServerTool.Create([McpServerTool(Destructive = false, OpenWorld = true)](string i) => $"{i} Result", new() { Name = "ValuesSetViaOptions", Destructive = true, OpenWorld = false, ReadOnly = true }));
|
||||
_server = sc.BuildServiceProvider().GetRequiredService<IMcpServer>();
|
||||
}
|
||||
|
||||
|
|
@ -60,10 +62,24 @@ public class McpClientExtensionsTests
|
|||
IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var tools = await client.ListToolsAsync(TestContext.Current.CancellationToken);
|
||||
Assert.Equal(10, tools.Count);
|
||||
Assert.Equal(12, tools.Count);
|
||||
var echo = tools.Single(t => t.Name == "Method4");
|
||||
var result = await echo.InvokeAsync(new Dictionary<string, object?>() { ["i"] = 42 }, TestContext.Current.CancellationToken);
|
||||
Assert.Contains("Method4 Result 42", result?.ToString());
|
||||
|
||||
var valuesSetViaAttr = tools.Single(t => t.Name == "ValuesSetViaAttr");
|
||||
Assert.Null(valuesSetViaAttr.ProtocolTool.Annotations?.Title);
|
||||
Assert.Null(valuesSetViaAttr.ProtocolTool.Annotations?.ReadOnlyHint);
|
||||
Assert.Null(valuesSetViaAttr.ProtocolTool.Annotations?.IdempotentHint);
|
||||
Assert.False(valuesSetViaAttr.ProtocolTool.Annotations?.DestructiveHint);
|
||||
Assert.True(valuesSetViaAttr.ProtocolTool.Annotations?.OpenWorldHint);
|
||||
|
||||
var valuesSetViaOptions = tools.Single(t => t.Name == "ValuesSetViaOptions");
|
||||
Assert.Null(valuesSetViaOptions.ProtocolTool.Annotations?.Title);
|
||||
Assert.True(valuesSetViaOptions.ProtocolTool.Annotations?.ReadOnlyHint);
|
||||
Assert.Null(valuesSetViaOptions.ProtocolTool.Annotations?.IdempotentHint);
|
||||
Assert.True(valuesSetViaOptions.ProtocolTool.Annotations?.DestructiveHint);
|
||||
Assert.False(valuesSetViaOptions.ProtocolTool.Annotations?.OpenWorldHint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
var serverTools = _server.ServerOptions.Capabilities?.Tools?.ToolCollection;
|
||||
Assert.NotNull(serverTools);
|
||||
|
||||
var newTool = McpServerTool.Create([McpServerTool(name: "NewTool")] () => "42");
|
||||
var newTool = McpServerTool.Create([McpServerTool(Name = "NewTool")] () => "42");
|
||||
serverTools.Add(newTool);
|
||||
await notificationRead;
|
||||
|
||||
|
|
@ -427,10 +427,42 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "MethodD");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_ExtractsToolAnnotations_AllSet()
|
||||
{
|
||||
var tool = McpServerTool.Create(EchoTool.ReturnInteger);
|
||||
Assert.NotNull(tool);
|
||||
Assert.NotNull(tool.ProtocolTool);
|
||||
|
||||
var annotations = tool.ProtocolTool.Annotations;
|
||||
Assert.NotNull(annotations);
|
||||
Assert.Equal("Return An Integer", annotations.Title);
|
||||
Assert.False(annotations.DestructiveHint);
|
||||
Assert.True(annotations.IdempotentHint);
|
||||
Assert.False(annotations.OpenWorldHint);
|
||||
Assert.True(annotations.ReadOnlyHint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_ExtractsToolAnnotations_SomeSet()
|
||||
{
|
||||
var tool = McpServerTool.Create(EchoTool.ReturnJson);
|
||||
Assert.NotNull(tool);
|
||||
Assert.NotNull(tool.ProtocolTool);
|
||||
|
||||
var annotations = tool.ProtocolTool.Annotations;
|
||||
Assert.NotNull(annotations);
|
||||
Assert.Null(annotations.Title);
|
||||
Assert.Null(annotations.DestructiveHint);
|
||||
Assert.False(annotations.IdempotentHint);
|
||||
Assert.Null(annotations.OpenWorldHint);
|
||||
Assert.Null(annotations.ReadOnlyHint);
|
||||
}
|
||||
|
||||
[McpServerToolType]
|
||||
public sealed class EchoTool(ObjectWithId objectFromDI)
|
||||
{
|
||||
private string _randomValue = Guid.NewGuid().ToString("N");
|
||||
private readonly string _randomValue = Guid.NewGuid().ToString("N");
|
||||
|
||||
[McpServerTool, Description("Echoes the input back to the client.")]
|
||||
public static string Echo([Description("the echoes message")] string message)
|
||||
|
|
@ -438,7 +470,7 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
return "hello " + message;
|
||||
}
|
||||
|
||||
[McpServerTool("double_echo"), Description("Echoes the input back to the client.")]
|
||||
[McpServerTool(Name = "double_echo"), Description("Echoes the input back to the client.")]
|
||||
public static string Echo2(string message)
|
||||
{
|
||||
return "hello hello" + message;
|
||||
|
|
@ -462,13 +494,13 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
return null;
|
||||
}
|
||||
|
||||
[McpServerTool]
|
||||
[McpServerTool(Idempotent = false)]
|
||||
public static JsonElement ReturnJson()
|
||||
{
|
||||
return JsonDocument.Parse("{\"SomeProp\": false}").RootElement;
|
||||
}
|
||||
|
||||
[McpServerTool]
|
||||
[McpServerTool(Title = "Return An Integer", Destructive = false, Idempotent = true, OpenWorld = false, ReadOnly = true)]
|
||||
public static int ReturnInteger()
|
||||
{
|
||||
return 5;
|
||||
|
|
@ -499,7 +531,7 @@ public class McpServerBuilderExtensionsToolsTests : LoggedTest, IAsyncDisposable
|
|||
[McpServerToolType]
|
||||
internal class AnotherToolType
|
||||
{
|
||||
[McpServerTool("DifferentName")]
|
||||
[McpServerTool(Name = "DifferentName")]
|
||||
private static string MethodA(int a) => a.ToString();
|
||||
|
||||
[McpServerTool]
|
||||
|
|
|
|||
|
|
@ -1,194 +0,0 @@
|
|||
using Microsoft.Extensions.AI;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Server;
|
||||
using Moq;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Server;
|
||||
public class McpServerToolReturnTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task CanReturnCollectionOfAIContent()
|
||||
{
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return new List<AIContent>() {
|
||||
new TextContent("text"),
|
||||
new DataContent("data:image/png;base64,1234"),
|
||||
new DataContent("data:audio/wav;base64,1234")
|
||||
};
|
||||
});
|
||||
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(3, result.Content.Count);
|
||||
|
||||
Assert.Equal("text", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
|
||||
Assert.Equal("1234", result.Content[1].Data);
|
||||
Assert.Equal("image/png", result.Content[1].MimeType);
|
||||
Assert.Equal("image", result.Content[1].Type);
|
||||
|
||||
Assert.Equal("1234", result.Content[2].Data);
|
||||
Assert.Equal("audio/wav", result.Content[2].MimeType);
|
||||
Assert.Equal("audio", result.Content[2].Type);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("text", "text")]
|
||||
[InlineData("data:image/png;base64,1234", "image")]
|
||||
[InlineData("data:audio/wav;base64,1234", "audio")]
|
||||
public async Task CanReturnSingleAIContent(string data, string type)
|
||||
{
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return type switch
|
||||
{
|
||||
"text" => (AIContent)new TextContent(data),
|
||||
"image" => new DataContent(data),
|
||||
"audio" => new DataContent(data),
|
||||
_ => throw new ArgumentException("Invalid type")
|
||||
};
|
||||
});
|
||||
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Single(result.Content);
|
||||
Assert.Equal(type, result.Content[0].Type);
|
||||
|
||||
if (type != "text")
|
||||
{
|
||||
Assert.NotNull(result.Content[0].MimeType);
|
||||
Assert.Equal(data.Split(',').Last(), result.Content[0].Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Null(result.Content[0].MimeType);
|
||||
Assert.Equal(data, result.Content[0].Text);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReturnNullAIContent()
|
||||
{
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return (string?)null;
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Empty(result.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReturnString()
|
||||
{
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return "42";
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Single(result.Content);
|
||||
Assert.Equal("42", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReturnCollectionOfStrings()
|
||||
{
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return new List<string>() { "42", "43" };
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal(2, result.Content.Count);
|
||||
Assert.Equal("42", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
Assert.Equal("43", result.Content[1].Text);
|
||||
Assert.Equal("text", result.Content[1].Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReturnMcpContent()
|
||||
{
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return new Content { Text = "42", Type = "text" };
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Single(result.Content);
|
||||
Assert.Equal("42", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReturnCollectionOfMcpContent()
|
||||
{
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return new List<Content>() { new() { Text = "42", Type = "text" }, new() { Data = "1234", Type = "image", MimeType = "image/png" } };
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal(2, result.Content.Count);
|
||||
Assert.Equal("42", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
Assert.Equal("1234", result.Content[1].Data);
|
||||
Assert.Equal("image", result.Content[1].Type);
|
||||
Assert.Equal("image/png", result.Content[1].MimeType);
|
||||
Assert.Null(result.Content[1].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReturnCallToolResponse()
|
||||
{
|
||||
CallToolResponse response = new()
|
||||
{
|
||||
Content = [new() { Text = "text", Type = "text" }, new() { Data = "1234", Type = "image" }]
|
||||
};
|
||||
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return response;
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Same(response, result);
|
||||
|
||||
Assert.Equal(2, result.Content.Count);
|
||||
Assert.Equal("text", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
Assert.Equal("1234", result.Content[1].Data);
|
||||
Assert.Equal("image", result.Content[1].Type);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Server;
|
||||
using Moq;
|
||||
|
|
@ -13,8 +14,10 @@ public class McpServerToolTests
|
|||
[Fact]
|
||||
public void Create_InvalidArgs_Throws()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>("function", () => McpServerTool.Create(null!));
|
||||
Assert.Throws<ArgumentNullException>("function", () => McpServerTool.Create((AIFunction)null!));
|
||||
Assert.Throws<ArgumentNullException>("method", () => McpServerTool.Create((MethodInfo)null!));
|
||||
Assert.Throws<ArgumentNullException>("method", () => McpServerTool.Create((MethodInfo)null!, typeof(object)));
|
||||
Assert.Throws<ArgumentNullException>("targetType", () => McpServerTool.Create(typeof(McpServerToolTests).GetMethod(nameof(Create_InvalidArgs_Throws))!, (Type)null!));
|
||||
Assert.Throws<ArgumentNullException>("method", () => McpServerTool.Create((Delegate)null!));
|
||||
}
|
||||
|
||||
|
|
@ -50,7 +53,7 @@ public class McpServerToolTests
|
|||
{
|
||||
Assert.Same(expectedMyService, actualMyService);
|
||||
return "42";
|
||||
}, services: services);
|
||||
}, new() { Services = services });
|
||||
|
||||
Assert.DoesNotContain("actualMyService", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema));
|
||||
|
||||
|
|
@ -82,7 +85,7 @@ public class McpServerToolTests
|
|||
{
|
||||
Assert.Null(actualMyService);
|
||||
return "42";
|
||||
}, services: services);
|
||||
}, new() { Services = services });
|
||||
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(null!, null),
|
||||
|
|
@ -129,6 +132,193 @@ public class McpServerToolTests
|
|||
Assert.Equal("""{"asyncDisposals":1,"disposals":0}""", result.Content[0].Text);
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public async Task CanReturnCollectionOfAIContent()
|
||||
{
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return new List<AIContent>() {
|
||||
new TextContent("text"),
|
||||
new DataContent("data:image/png;base64,1234"),
|
||||
new DataContent("data:audio/wav;base64,1234")
|
||||
};
|
||||
});
|
||||
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Equal(3, result.Content.Count);
|
||||
|
||||
Assert.Equal("text", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
|
||||
Assert.Equal("1234", result.Content[1].Data);
|
||||
Assert.Equal("image/png", result.Content[1].MimeType);
|
||||
Assert.Equal("image", result.Content[1].Type);
|
||||
|
||||
Assert.Equal("1234", result.Content[2].Data);
|
||||
Assert.Equal("audio/wav", result.Content[2].MimeType);
|
||||
Assert.Equal("audio", result.Content[2].Type);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("text", "text")]
|
||||
[InlineData("data:image/png;base64,1234", "image")]
|
||||
[InlineData("data:audio/wav;base64,1234", "audio")]
|
||||
public async Task CanReturnSingleAIContent(string data, string type)
|
||||
{
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return type switch
|
||||
{
|
||||
"text" => (AIContent)new TextContent(data),
|
||||
"image" => new DataContent(data),
|
||||
"audio" => new DataContent(data),
|
||||
_ => throw new ArgumentException("Invalid type")
|
||||
};
|
||||
});
|
||||
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Single(result.Content);
|
||||
Assert.Equal(type, result.Content[0].Type);
|
||||
|
||||
if (type != "text")
|
||||
{
|
||||
Assert.NotNull(result.Content[0].MimeType);
|
||||
Assert.Equal(data.Split(',').Last(), result.Content[0].Data);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Null(result.Content[0].MimeType);
|
||||
Assert.Equal(data, result.Content[0].Text);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReturnNullAIContent()
|
||||
{
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return (string?)null;
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Empty(result.Content);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReturnString()
|
||||
{
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return "42";
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Single(result.Content);
|
||||
Assert.Equal("42", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReturnCollectionOfStrings()
|
||||
{
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return new List<string>() { "42", "43" };
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal(2, result.Content.Count);
|
||||
Assert.Equal("42", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
Assert.Equal("43", result.Content[1].Text);
|
||||
Assert.Equal("text", result.Content[1].Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReturnMcpContent()
|
||||
{
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return new Content { Text = "42", Type = "text" };
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Single(result.Content);
|
||||
Assert.Equal("42", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReturnCollectionOfMcpContent()
|
||||
{
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return new List<Content>() { new() { Text = "42", Type = "text" }, new() { Data = "1234", Type = "image", MimeType = "image/png" } };
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.Equal(2, result.Content.Count);
|
||||
Assert.Equal("42", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
Assert.Equal("1234", result.Content[1].Data);
|
||||
Assert.Equal("image", result.Content[1].Type);
|
||||
Assert.Equal("image/png", result.Content[1].MimeType);
|
||||
Assert.Null(result.Content[1].Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanReturnCallToolResponse()
|
||||
{
|
||||
CallToolResponse response = new()
|
||||
{
|
||||
Content = [new() { Text = "text", Type = "text" }, new() { Data = "1234", Type = "image" }]
|
||||
};
|
||||
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
McpServerTool tool = McpServerTool.Create((IMcpServer server) =>
|
||||
{
|
||||
Assert.Same(mockServer.Object, server);
|
||||
return response;
|
||||
});
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
|
||||
TestContext.Current.CancellationToken);
|
||||
|
||||
Assert.Same(response, result);
|
||||
|
||||
Assert.Equal(2, result.Content.Count);
|
||||
Assert.Equal("text", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
Assert.Equal("1234", result.Content[1].Data);
|
||||
Assert.Equal("image", result.Content[1].Type);
|
||||
}
|
||||
|
||||
private sealed class MyService;
|
||||
|
||||
private class DisposableToolType : IDisposable
|
||||
|
|
|
|||
Loading…
Reference in a new issue