mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
.NET: Add sample for invoking Foundry Toolbox tools from declarative workflows (#5829)
* Add sample for invoking Foundry Toolbox tools from declarative workflows * Addressed initial PR comments.
This commit is contained in:
@@ -3,12 +3,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Protocol;
|
||||
|
||||
@@ -24,6 +27,14 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.Mcp;
|
||||
/// </remarks>
|
||||
public sealed class DefaultMcpToolHandler : IMcpToolHandler, IAsyncDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Reserved <c>toolName</c> value that maps an <see cref="IMcpToolHandler.InvokeToolAsync"/> request
|
||||
/// to the MCP protocol <c>tools/list</c> discovery operation.
|
||||
/// </summary>
|
||||
public const string ListToolsToolName = "tools/list";
|
||||
|
||||
private static readonly JsonWriterOptions s_toolListJsonWriterOptions = new() { Indented = true };
|
||||
|
||||
private readonly Func<string, CancellationToken, Task<HttpClient?>>? _httpClientProvider;
|
||||
private readonly Dictionary<string, McpClient> _clients = [];
|
||||
private readonly Dictionary<string, HttpClient> _ownedHttpClients = [];
|
||||
@@ -53,9 +64,18 @@ public sealed class DefaultMcpToolHandler : IMcpToolHandler, IAsyncDisposable
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
// TODO: Handle connectionName and server label appropriately when Hosted scenario supports them. For now, ignore
|
||||
McpServerToolResultContent resultContent = new(Guid.NewGuid().ToString());
|
||||
if (IsListToolsToolName(toolName))
|
||||
{
|
||||
ThrowIfListToolsArgumentsSpecified(arguments);
|
||||
McpClient listToolsClient = await this.GetOrCreateClientAsync(serverUrl, serverLabel, headers, cancellationToken).ConfigureAwait(false);
|
||||
IList<McpClientTool> tools = await listToolsClient.ListToolsAsync(cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
return CreateListToolsResultContent(tools.Select(tool => tool.ProtocolTool));
|
||||
}
|
||||
|
||||
McpClient client = await this.GetOrCreateClientAsync(serverUrl, serverLabel, headers, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
McpServerToolResultContent resultContent = new(Guid.NewGuid().ToString());
|
||||
|
||||
// Convert IDictionary to IReadOnlyDictionary for CallToolAsync
|
||||
IReadOnlyDictionary<string, object?>? readOnlyArguments = arguments is null
|
||||
? null
|
||||
@@ -72,6 +92,23 @@ public sealed class DefaultMcpToolHandler : IMcpToolHandler, IAsyncDisposable
|
||||
return resultContent;
|
||||
}
|
||||
|
||||
internal static bool IsListToolsToolName(string toolName) =>
|
||||
string.Equals(toolName, ListToolsToolName, StringComparison.Ordinal);
|
||||
|
||||
internal static McpServerToolResultContent CreateListToolsResultContent(IEnumerable<Tool> tools)
|
||||
{
|
||||
Throw.IfNull(tools);
|
||||
|
||||
McpServerToolResultContent resultContent = new(Guid.NewGuid().ToString())
|
||||
{
|
||||
Outputs = []
|
||||
};
|
||||
|
||||
resultContent.Outputs.Add(new TextContent(SerializeToolsList(tools)));
|
||||
|
||||
return resultContent;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
@@ -183,6 +220,16 @@ public sealed class DefaultMcpToolHandler : IMcpToolHandler, IAsyncDisposable
|
||||
return hashCode.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static void ThrowIfListToolsArgumentsSpecified(IDictionary<string, object?>? arguments)
|
||||
{
|
||||
if (arguments is { Count: > 0 })
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"The reserved MCP '{ListToolsToolName}' operation does not accept tool arguments.",
|
||||
nameof(arguments));
|
||||
}
|
||||
}
|
||||
|
||||
private static void PopulateResultContent(McpServerToolResultContent resultContent, CallToolResult result)
|
||||
{
|
||||
// Ensure Outputs list is initialized
|
||||
@@ -230,6 +277,17 @@ public sealed class DefaultMcpToolHandler : IMcpToolHandler, IAsyncDisposable
|
||||
TextContentBlock text => new TextContent(text.Text),
|
||||
ImageContentBlock image => CreateDataContent(image.Data, image.MimeType ?? "image/*"),
|
||||
AudioContentBlock audio => CreateDataContent(audio.Data, audio.MimeType ?? "audio/*"),
|
||||
EmbeddedResourceBlock embedded => ConvertEmbeddedResource(embedded),
|
||||
_ => new TextContent(block.ToString() ?? string.Empty),
|
||||
};
|
||||
}
|
||||
|
||||
private static AIContent ConvertEmbeddedResource(EmbeddedResourceBlock block)
|
||||
{
|
||||
return block.Resource switch
|
||||
{
|
||||
TextResourceContents text => new TextContent(text.Text),
|
||||
BlobResourceContents blob => CreateDataContent(blob.Blob, blob.MimeType ?? "application/octet-stream"),
|
||||
_ => new TextContent(block.ToString() ?? string.Empty),
|
||||
};
|
||||
}
|
||||
@@ -255,4 +313,39 @@ public sealed class DefaultMcpToolHandler : IMcpToolHandler, IAsyncDisposable
|
||||
|
||||
return new DataContent($"data:{mediaType};base64,{base64}", mediaType);
|
||||
}
|
||||
|
||||
private static string SerializeToolsList(IEnumerable<Tool> tools)
|
||||
{
|
||||
using MemoryStream stream = new();
|
||||
using (Utf8JsonWriter writer = new(stream, s_toolListJsonWriterOptions))
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteStartArray("tools");
|
||||
|
||||
foreach (Tool tool in tools)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
writer.WriteString("name", tool.Name);
|
||||
writer.WriteString("description", tool.Description);
|
||||
writer.WritePropertyName("inputSchema");
|
||||
tool.InputSchema.WriteTo(writer);
|
||||
writer.WritePropertyName("outputSchema");
|
||||
if (tool.OutputSchema is JsonElement outputSchema)
|
||||
{
|
||||
outputSchema.WriteTo(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteNullValue();
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
writer.WriteEndArray();
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
|
||||
return Encoding.UTF8.GetString(stream.GetBuffer(), 0, (int)stream.Length);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user