Support progress notifications with McpClientTool and McpClientExtensions.CallToolAsync (#312)
This commit is contained in:
parent
90748b1198
commit
227f440ab9
2 changed files with 174 additions and 47 deletions
|
|
@ -783,9 +783,14 @@ public static class McpClientExtensions
|
|||
/// </summary>
|
||||
/// <param name="client">The client instance used to communicate with the MCP server.</param>
|
||||
/// <param name="toolName">The name of the tool to call on the server..</param>
|
||||
/// <param name="arguments">Optional dictionary of arguments to pass to the tool. Each key represents a parameter name,
|
||||
/// <param name="arguments">An optional dictionary of arguments to pass to the tool. Each key represents a parameter name,
|
||||
/// and its associated value represents the argument value.
|
||||
/// </param>
|
||||
/// <param name="progress">
|
||||
/// An optional <see cref="IProgress{T}"/> to have progress notifications reported to it. Setting this to a non-<see langword="null"/>
|
||||
/// value will result in a progress token being included in the call, and any resulting progress notifications during the operation
|
||||
/// routed to this instance.
|
||||
/// </param>
|
||||
/// <param name="serializerOptions">
|
||||
/// The JSON serialization options governing argument serialization. If <see langword="null"/>, the default serialization options will be used.
|
||||
/// </param>
|
||||
|
|
@ -812,6 +817,7 @@ public static class McpClientExtensions
|
|||
this IMcpClient client,
|
||||
string toolName,
|
||||
IReadOnlyDictionary<string, object?>? arguments = null,
|
||||
IProgress<ProgressNotificationValue>? progress = null,
|
||||
JsonSerializerOptions? serializerOptions = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
|
@ -820,12 +826,56 @@ public static class McpClientExtensions
|
|||
serializerOptions ??= McpJsonUtilities.DefaultOptions;
|
||||
serializerOptions.MakeReadOnly();
|
||||
|
||||
if (progress is not null)
|
||||
{
|
||||
return SendRequestWithProgressAsync(client, toolName, arguments, progress, serializerOptions, cancellationToken);
|
||||
}
|
||||
|
||||
return client.SendRequestAsync(
|
||||
RequestMethods.ToolsCall,
|
||||
new() { Name = toolName, Arguments = ToArgumentsDictionary(arguments, serializerOptions) },
|
||||
new()
|
||||
{
|
||||
Name = toolName,
|
||||
Arguments = ToArgumentsDictionary(arguments, serializerOptions),
|
||||
},
|
||||
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.CallToolResponse,
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
static async Task<CallToolResponse> SendRequestWithProgressAsync(
|
||||
IMcpClient client,
|
||||
string toolName,
|
||||
IReadOnlyDictionary<string, object?>? arguments,
|
||||
IProgress<ProgressNotificationValue> progress,
|
||||
JsonSerializerOptions serializerOptions,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
ProgressToken progressToken = new(Guid.NewGuid().ToString("N"));
|
||||
|
||||
await using var _ = client.RegisterNotificationHandler(NotificationMethods.ProgressNotification,
|
||||
(notification, cancellationToken) =>
|
||||
{
|
||||
if (JsonSerializer.Deserialize(notification.Params, McpJsonUtilities.JsonContext.Default.ProgressNotification) is { } pn &&
|
||||
pn.ProgressToken == progressToken)
|
||||
{
|
||||
progress.Report(pn.Progress);
|
||||
}
|
||||
|
||||
return default;
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
return await client.SendRequestAsync(
|
||||
RequestMethods.ToolsCall,
|
||||
new()
|
||||
{
|
||||
Name = toolName,
|
||||
Arguments = ToArgumentsDictionary(arguments, serializerOptions),
|
||||
Meta = new() { ProgressToken = progressToken },
|
||||
},
|
||||
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.CallToolResponse,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Utils.Json;
|
||||
using Microsoft.Extensions.AI;
|
||||
using System.Text.Json;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Utils;
|
||||
using ModelContextProtocol.Utils.Json;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Client;
|
||||
|
||||
|
|
@ -36,14 +37,22 @@ public sealed class McpClientTool : AIFunction
|
|||
private readonly IMcpClient _client;
|
||||
private readonly string _name;
|
||||
private readonly string _description;
|
||||
private readonly IProgress<ProgressNotificationValue>? _progress;
|
||||
|
||||
internal McpClientTool(IMcpClient client, Tool tool, JsonSerializerOptions serializerOptions, string? name = null, string? description = null)
|
||||
internal McpClientTool(
|
||||
IMcpClient client,
|
||||
Tool tool,
|
||||
JsonSerializerOptions serializerOptions,
|
||||
string? name = null,
|
||||
string? description = null,
|
||||
IProgress<ProgressNotificationValue>? progress = null)
|
||||
{
|
||||
_client = client;
|
||||
ProtocolTool = tool;
|
||||
JsonSerializerOptions = serializerOptions;
|
||||
_name = name ?? tool.Name;
|
||||
_description = description ?? tool.Description ?? string.Empty;
|
||||
_progress = progress;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -77,7 +86,7 @@ public sealed class McpClientTool : AIFunction
|
|||
protected async override ValueTask<object?> InvokeCoreAsync(
|
||||
AIFunctionArguments arguments, CancellationToken cancellationToken)
|
||||
{
|
||||
CallToolResponse result = await _client.CallToolAsync(ProtocolTool.Name, arguments, JsonSerializerOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
CallToolResponse result = await _client.CallToolAsync(ProtocolTool.Name, arguments, _progress, JsonSerializerOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
return JsonSerializer.SerializeToElement(result, McpJsonUtilities.JsonContext.Default.CallToolResponse);
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +116,7 @@ public sealed class McpClientTool : AIFunction
|
|||
/// </remarks>
|
||||
public McpClientTool WithName(string name)
|
||||
{
|
||||
return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, name, _description);
|
||||
return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, name, _description, _progress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -133,6 +142,30 @@ public sealed class McpClientTool : AIFunction
|
|||
/// <returns>A new instance of <see cref="McpClientTool"/> with the provided description.</returns>
|
||||
public McpClientTool WithDescription(string description)
|
||||
{
|
||||
return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, _name, description);
|
||||
return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, _name, description, _progress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the tool but modified to report progress via the specified <see cref="IProgress{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="progress">The <see cref="IProgress{T}"/> to which progress notifications should be reported.</param>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Adding an <see cref="IProgress{T}"/> 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 <paramref name="progress"/> instance.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Only one <see cref="IProgress{T}"/> can be specified at a time. Calling <see cref="WithProgress"/> again
|
||||
/// will overwrite any previously specified progress instance.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <returns>A new instance of <see cref="McpClientTool"/>, configured with the provided progress instance.</returns>
|
||||
public McpClientTool WithProgress(IProgress<ProgressNotificationValue> progress)
|
||||
{
|
||||
Throw.IfNull(progress);
|
||||
|
||||
return new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, _name, _description, progress);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue