using Microsoft.Extensions.AI; using ModelContextProtocol.Protocol; using System.Collections.ObjectModel; using System.Text.Json; namespace ModelContextProtocol.Client; /// /// Provides an that calls a tool via an . /// /// /// /// The class encapsulates an along with a description of /// a tool available via that client, allowing it to be invoked as an . This enables integration /// with AI models that support function calling capabilities. /// /// /// Tools retrieved from an MCP server can be customized for model presentation using methods like /// and without changing the underlying tool functionality. /// /// /// Typically, you would get instances of this class by calling the /// or extension methods on an instance. /// /// public sealed class McpClientTool : AIFunction { /// Additional properties exposed from tools. private static readonly ReadOnlyDictionary s_additionalProperties = new(new Dictionary() { ["Strict"] = false, // some MCP schemas may not meet "strict" requirements }); private readonly McpClient _client; private readonly string _name; private readonly string _description; private readonly IProgress? _progress; internal McpClientTool( McpClient client, Tool tool, JsonSerializerOptions serializerOptions, string? name = null, string? description = null, IProgress? progress = null) { _client = client; ProtocolTool = tool; JsonSerializerOptions = serializerOptions; _name = name ?? tool.Name; _description = description ?? tool.Description ?? string.Empty; _progress = progress; } /// /// Gets the protocol type for this instance. /// /// /// This property provides direct access to the underlying protocol representation of the tool, /// which can be useful for advanced scenarios or when implementing custom MCP client extensions. /// It contains the original metadata about the tool as provided by the server, including its /// name, description, and schema information before any customizations applied through methods /// like or . /// public Tool ProtocolTool { get; } /// public override string Name => _name; /// Gets the tool's title. public string? Title => ProtocolTool.Title ?? ProtocolTool.Annotations?.Title; /// public override string Description => _description; /// public override JsonElement JsonSchema => ProtocolTool.InputSchema; /// public override JsonElement? ReturnJsonSchema => ProtocolTool.OutputSchema; /// public override JsonSerializerOptions JsonSerializerOptions { get; } /// public override IReadOnlyDictionary AdditionalProperties => s_additionalProperties; /// protected async override ValueTask InvokeCoreAsync( AIFunctionArguments arguments, CancellationToken cancellationToken) { CallToolResult result = await CallAsync(arguments, _progress, JsonSerializerOptions, cancellationToken).ConfigureAwait(false); return JsonSerializer.SerializeToElement(result, McpJsonUtilities.JsonContext.Default.CallToolResult); } /// /// Invokes the tool on the server. /// /// An optional dictionary of arguments to pass to the tool. Each key represents a parameter name, /// and its associated value represents the argument value. /// /// /// An optional to have progress notifications reported to it. Setting this to a non- /// value will result in a progress token being included in the call, and any resulting progress notifications during the operation /// routed to this instance. /// /// /// The JSON serialization options governing argument serialization. If , the default serialization options will be used. /// /// The to monitor for cancellation requests. The default is . /// /// A task containing the from the tool execution. The response includes /// the tool's output content, which may be structured data, text, or an error message. /// /// /// The base method is overridden to invoke this method. /// The only difference in behavior is will serialize the resulting "/> /// such that the returned is a containing the serialized . /// This method is intended to be called directly by user code, whereas the base /// is intended to be used polymorphically via the base class, typically as part of an operation. /// /// The server could not find the requested tool, or the server encountered an error while processing the request. /// /// /// var result = await tool.CallAsync( /// new Dictionary<string, object?> /// { /// ["message"] = "Hello MCP!" /// }); /// /// public ValueTask CallAsync( IReadOnlyDictionary? arguments = null, IProgress? progress = null, JsonSerializerOptions? serializerOptions = null, CancellationToken cancellationToken = default) => _client.CallToolAsync(ProtocolTool.Name, arguments, progress, serializerOptions, cancellationToken); /// /// Creates a new instance of the tool but modified to return the specified name from its property. /// /// The model-facing name to give the tool. /// A new instance of with the provided name. /// /// /// This is useful for optimizing the tool name for specific models or for prefixing the tool name /// with a namespace to avoid conflicts. /// /// /// Changing the name can help with: /// /// /// Making the tool name more intuitive for the model /// Preventing name collisions when using tools from multiple sources /// Creating specialized versions of a general tool for specific contexts /// /// /// When invoking , the MCP server will still be called with /// the original tool name, so no mapping is required on the server side. This new name only affects /// the value returned from this instance's . /// /// public McpClientTool WithName(string name) => new(_client, ProtocolTool, JsonSerializerOptions, name, _description, _progress); /// /// Creates a new instance of the tool but modified to return the specified description from its property. /// /// The description to give the tool. /// /// /// Changing the description can help the model better understand the tool's purpose or provide more /// context about how the tool should be used. This is particularly useful when: /// /// /// The original description is too technical or lacks clarity for the model /// You want to add example usage scenarios to improve the model's understanding /// You need to tailor the tool's description for specific model requirements /// /// /// When invoking , the MCP server will still be called with /// the original tool description, so no mapping is required on the server side. This new description only affects /// the value returned from this instance's . /// /// /// A new instance of with the provided description. public McpClientTool WithDescription(string description) => new(_client, ProtocolTool, JsonSerializerOptions, _name, description, _progress); /// /// Creates a new instance of the tool but modified to report progress via the specified . /// /// The to which progress notifications should be reported. /// /// /// Adding an to the tool does not impact how it is reported to any AI model. /// Rather, when the tool is invoked, the request to the MCP server will include a unique progress token, /// and any progress notifications issued by the server with that progress token while the operation is in /// flight will be reported to the instance. /// /// /// Only one can be specified at a time. Calling again /// will overwrite any previously specified progress instance. /// /// /// A new instance of , configured with the provided progress instance. public McpClientTool WithProgress(IProgress progress) { Throw.IfNull(progress); return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, _name, _description, progress); } }