Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow override of name and description of McpClientTool #165

Merged
36 changes: 33 additions & 3 deletions src/ModelContextProtocol/Client/McpClientTool.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Utils.Json;
using ModelContextProtocol.Utils;
using Microsoft.Extensions.AI;
using System.Text.Json;

Expand All @@ -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;
}

/// <summary>
/// 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.
/// </summary>
/// <param name="name">The model-facing name to give the tool.</param>
/// <returns>Copy of this McpClientTool with the provided name</returns>
public McpClientTool WithName(string name)
{
return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, name, _description);
}

/// <summary>
/// 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.
/// </summary>
/// <param name="description">The description to give the tool.</param>
/// <returns>Copy of this McpClientTool with the provided description</returns>
public McpClientTool WithDescription(string description)
{
return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, _name, description);
}

/// <summary>Gets the protocol <see cref="Tool"/> type for this instance.</summary>
public Tool ProtocolTool { get; }

/// <inheritdoc/>
public override string Name => ProtocolTool.Name;
public override string Name => _name;

/// <inheritdoc/>
public override string Description => ProtocolTool.Description ?? string.Empty;
public override string Description => _description;

/// <inheritdoc/>
public override JsonElement JsonSchema => ProtocolTool.InputSchema;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,4 +171,32 @@ public async Task GetPromptsAsync_HonorsJsonSerializerOptions()

await Assert.ThrowsAsync<NotSupportedException>(() => client.GetPromptAsync("Prompt", new Dictionary<string, object?> { ["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);
}
}