diff --git a/servers/Azure.Mcp.Server/CHANGELOG.md b/servers/Azure.Mcp.Server/CHANGELOG.md index b1e6fdc4dd..a7c15961c1 100644 --- a/servers/Azure.Mcp.Server/CHANGELOG.md +++ b/servers/Azure.Mcp.Server/CHANGELOG.md @@ -10,6 +10,8 @@ The Azure MCP Server updates automatically by default whenever a new release com ### Bugs Fixed +- Fixed a bug where agents connect command result including file search result fail with serialization error. [[#1205](https://github.com/microsoft/mcp/pull/1205)] + ### Other Changes - Begin capturing information for the MCP client request's `_meta` store. [[#1154](https://github.com/microsoft/mcp/pull/1154)] diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs index 37eb5ca342..ec4cfaea85 100644 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs +++ b/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs @@ -64,5 +64,6 @@ namespace Azure.Mcp.Tools.Foundry.Commands; [JsonSerializable(typeof(ThreadCreateResult))] [JsonSerializable(typeof(ThreadListResult))] [JsonSerializable(typeof(ThreadGetMessagesResult))] +[JsonSerializable(typeof(List))] [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault)] internal sealed partial class FoundryJsonContext : JsonSerializerContext; diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentFileSearchResult.cs b/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentFileSearchResult.cs new file mode 100644 index 0000000000..ac11cc2d8c --- /dev/null +++ b/tools/Azure.Mcp.Tools.Foundry/src/Models/AgentFileSearchResult.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json.Serialization; +using Azure.AI.Agents.Persistent; + +namespace Azure.Mcp.Tools.Foundry.Models; + +internal class AgentFileSearchResult +{ + [JsonPropertyName("fileId")] + public string? FileId { get; set; } + + [JsonPropertyName("fileName")] + public string? FileName { get; set; } + + [JsonPropertyName("score")] + public float Score { get; set; } + + [JsonPropertyName("content")] + public IReadOnlyList? Content { get; set; } +} diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Services/FoundryService.cs b/tools/Azure.Mcp.Tools.Foundry/src/Services/FoundryService.cs index 66f50b1127..1b9be1dbf3 100644 --- a/tools/Azure.Mcp.Tools.Foundry/src/Services/FoundryService.cs +++ b/tools/Azure.Mcp.Tools.Foundry/src/Services/FoundryService.cs @@ -1257,26 +1257,10 @@ private List ConvertToolDefinitionsFromString(string? { foreach (RunStepToolCall toolCall in details.ToolCalls) { - Microsoft.Extensions.AI.ChatMessage CreateRequestMessage(string name, Dictionary arguments) => - new(ChatRole.Assistant, [new FunctionCallContent(toolCall.Id, name, arguments)]) - { - AuthorName = step.AssistantId, - MessageId = step.Id, - RawRepresentation = step, - }; - - Microsoft.Extensions.AI.ChatMessage CreateResponseMessage(object result) => - new(ChatRole.Tool, [new FunctionResultContent(toolCall.Id, result)]) - { - AuthorName = step.AssistantId, - MessageId = step.Id, - RawRepresentation = step, - }; - switch (toolCall) { case RunStepFunctionToolCall function: - yield return CreateRequestMessage(function.Name, Parse(function.Arguments) ?? []); + yield return CreateRequestMessage(toolCall.Id, function.Name, Parse(function.Arguments) ?? [], step); // TODO: output doesn't appear to be available in the API static Dictionary? Parse(string arguments) @@ -1288,43 +1272,44 @@ Microsoft.Extensions.AI.ChatMessage CreateResponseMessage(object result) => break; case RunStepCodeInterpreterToolCall code: - yield return CreateRequestMessage("code_interpreter", new() { ["input"] = code.Input }); - yield return CreateResponseMessage(string.Concat(code.Outputs.OfType().Select(o => o.Logs))); + yield return CreateRequestMessage(toolCall.Id, "code_interpreter", new() { ["input"] = code.Input }, step); + yield return CreateResponseMessage(toolCall.Id, string.Concat(code.Outputs.OfType().Select(o => o.Logs)), step); break; case RunStepBingGroundingToolCall bing: - yield return CreateRequestMessage("bing_grounding", new() { ["requesturl"] = bing.BingGrounding["requesturl"] }); + yield return CreateRequestMessage(toolCall.Id, "bing_grounding", new() { ["requesturl"] = bing.BingGrounding["requesturl"] }, step); break; case RunStepFileSearchToolCall fileSearch: - yield return CreateRequestMessage("file_search", new() + yield return CreateRequestMessage(toolCall.Id, "file_search", new() { ["ranking_options"] = JsonSerializer.SerializeToElement(new() { ["ranker"] = fileSearch.FileSearch.RankingOptions.Ranker, ["score_threshold"] = fileSearch.FileSearch.RankingOptions.ScoreThreshold, }, DictionaryTypeInfo) - }); - yield return CreateResponseMessage(fileSearch.FileSearch.Results.Select(r => new + }, step); + List fileSearchResults = fileSearch.FileSearch.Results.Select(r => new AgentFileSearchResult { - file_id = r.FileId, - file_name = r.FileName, - score = r.Score, - content = r.Content, - }).ToList()); + FileId = r.FileId, + FileName = r.FileName, + Score = r.Score, + Content = r.Content, + }).ToList(); + yield return CreateResponseMessage(toolCall.Id, fileSearchResults, step); break; case RunStepAzureAISearchToolCall aiSearch: - yield return CreateRequestMessage("azure_ai_search", new() { ["input"] = aiSearch.AzureAISearch["input"] }); - yield return CreateResponseMessage(new Dictionary + yield return CreateRequestMessage(toolCall.Id, "azure_ai_search", new() { ["input"] = aiSearch.AzureAISearch["input"] }, step); + yield return CreateResponseMessage(toolCall.Id, new Dictionary { ["output"] = aiSearch.AzureAISearch["output"] - }); + }, step); break; case RunStepMicrosoftFabricToolCall fabric: - yield return CreateRequestMessage("fabric_dataagent", new() { ["input"] = fabric.MicrosoftFabric["input"] }); - yield return CreateResponseMessage(fabric.MicrosoftFabric["output"]); + yield return CreateRequestMessage(toolCall.Id, "fabric_dataagent", new() { ["input"] = fabric.MicrosoftFabric["input"] }, step); + yield return CreateResponseMessage(toolCall.Id, fabric.MicrosoftFabric["output"], step); break; } } @@ -1688,4 +1673,54 @@ public string SerializeToolDefinition() return Encoding.UTF8.GetString(bytes.GetBuffer(), 0, (int)bytes.Length); } } + + internal static Microsoft.Extensions.AI.ChatMessage CreateRequestMessage(string toolCallId, string name, Dictionary arguments, RunStep step) + { + return new(ChatRole.Assistant, [new FunctionCallContent(toolCallId, name, arguments)]) + { + AuthorName = step.AssistantId, + MessageId = step.Id, + RawRepresentation = step, + }; + } + + internal static Microsoft.Extensions.AI.ChatMessage CreateResponseMessage(string toolCallId, List result, RunStep step) + { + return new(ChatRole.Tool, [new FunctionResultContent(toolCallId, result)]) + { + AuthorName = step.AssistantId, + MessageId = step.Id, + RawRepresentation = step, + }; + } + + internal static Microsoft.Extensions.AI.ChatMessage CreateResponseMessage(string toolCallId, JsonElement result, RunStep step) + { + return new(ChatRole.Tool, [new FunctionResultContent(toolCallId, result)]) + { + AuthorName = step.AssistantId, + MessageId = step.Id, + RawRepresentation = step, + }; + } + + internal static Microsoft.Extensions.AI.ChatMessage CreateResponseMessage(string toolCallId, string result, RunStep step) + { + return new(ChatRole.Tool, [new FunctionResultContent(toolCallId, result)]) + { + AuthorName = step.AssistantId, + MessageId = step.Id, + RawRepresentation = step, + }; + } + + internal static Microsoft.Extensions.AI.ChatMessage CreateResponseMessage(string toolCallId, IDictionary result, RunStep step) + { + return new(ChatRole.Tool, [new FunctionResultContent(toolCallId, result)]) + { + AuthorName = step.AssistantId, + MessageId = step.Id, + RawRepresentation = step, + }; + } }