diff --git a/src/ModelContextProtocol/Client/McpClientTool.cs b/src/ModelContextProtocol/Client/McpClientTool.cs index 1fdc3a56..58cb02d5 100644 --- a/src/ModelContextProtocol/Client/McpClientTool.cs +++ b/src/ModelContextProtocol/Client/McpClientTool.cs @@ -1,5 +1,6 @@ using ModelContextProtocol.Protocol.Types; using ModelContextProtocol.Utils.Json; +using ModelContextProtocol.Utils; using Microsoft.Extensions.AI; using System.Text.Json; @@ -9,22 +10,51 @@ namespace ModelContextProtocol.Client; public sealed class McpClientTool : AIFunction { private readonly IMcpClient _client; + private readonly string _name; + private readonly string _description; - internal McpClientTool(IMcpClient client, Tool tool, JsonSerializerOptions serializerOptions) + internal McpClientTool(IMcpClient client, Tool tool, JsonSerializerOptions serializerOptions, string? name = null, string? description = null) { _client = client; ProtocolTool = tool; JsonSerializerOptions = serializerOptions; + _name = name ?? tool.Name; + _description = description ?? tool.Description ?? string.Empty; + } + + /// + /// Creates a new instance of the tool with the specified name. + /// This is useful for optimizing the tool name for specific models or for prefixing the tool name with a (usually server-derived) namespace to avoid conflicts. + /// The server will still be called with the original tool name, so no mapping is required. + /// + /// The model-facing name to give the tool. + /// Copy of this McpClientTool with the provided name + public McpClientTool WithName(string name) + { + return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, name, _description); + } + + /// + /// Creates a new instance of the tool with the specified description. + /// This can be used to provide modified or additional (e.g. examples) context to the model about the tool. + /// This will in general require a hard-coded mapping in the client. + /// It is not recommended to use this without running evaluations to ensure the model actually benefits from the custom description. + /// + /// The description to give the tool. + /// Copy of this McpClientTool with the provided description + public McpClientTool WithDescription(string description) + { + return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, _name, description); } /// Gets the protocol type for this instance. public Tool ProtocolTool { get; } /// - public override string Name => ProtocolTool.Name; + public override string Name => _name; /// - public override string Description => ProtocolTool.Description ?? string.Empty; + public override string Description => _description; /// public override JsonElement JsonSchema => ProtocolTool.InputSchema; diff --git a/tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs b/tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs index 7c3e92f9..02bd50d1 100644 --- a/tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs +++ b/tests/ModelContextProtocol.Tests/Client/McpClientExtensionsTests.cs @@ -171,4 +171,32 @@ public async Task GetPromptsAsync_HonorsJsonSerializerOptions() await Assert.ThrowsAsync(() => client.GetPromptAsync("Prompt", new Dictionary { ["i"] = 42 }, emptyOptions, cancellationToken: TestContext.Current.CancellationToken)); } + + [Fact] + public async Task WithName_ChangesToolName() + { + JsonSerializerOptions options = new(JsonSerializerOptions.Default); + IMcpClient client = await CreateMcpClientForServer(); + + var tool = (await client.ListToolsAsync(options, TestContext.Current.CancellationToken)).First(); + var originalName = tool.Name; + var renamedTool = tool.WithName("RenamedTool"); + + Assert.NotNull(renamedTool); + Assert.Equal("RenamedTool", renamedTool.Name); + Assert.Equal(originalName, tool?.Name); + } + + [Fact] + public async Task WithDescription_ChangesToolDescription() + { + JsonSerializerOptions options = new(JsonSerializerOptions.Default); + IMcpClient client = await CreateMcpClientForServer(); + var tool = (await client.ListToolsAsync(options, TestContext.Current.CancellationToken)).FirstOrDefault(); + var originalDescription = tool?.Description; + var redescribedTool = tool?.WithDescription("ToolWithNewDescription"); + Assert.NotNull(redescribedTool); + Assert.Equal("ToolWithNewDescription", redescribedTool.Description); + Assert.Equal(originalDescription, tool?.Description); + } } \ No newline at end of file