From 4e1f15193c951828796c93cfce59e390d6b2bb64 Mon Sep 17 00:00:00 2001 From: Vinay Gera Date: Tue, 21 Oct 2025 10:10:47 -0700 Subject: [PATCH 1/4] fix serialization --- .../src/Commands/FoundryJsonContext.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs index 2baf641778..0f7f45d4ea 100644 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs +++ b/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs @@ -56,5 +56,18 @@ namespace Azure.Mcp.Tools.Foundry.Commands; [JsonSerializable(typeof(AgentsQueryAndEvaluateResult))] [JsonSerializable(typeof(AgentsEvaluateResult))] [JsonSerializable(typeof(CognitiveServicesAccountDeploymentData))] +[JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(Dictionary))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(List))] +[JsonSerializable(typeof(object))] +[JsonSerializable(typeof(string))] +[JsonSerializable(typeof(double))] +[JsonSerializable(typeof(int))] +[JsonSerializable(typeof(bool))] [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault)] internal sealed partial class FoundryJsonContext : JsonSerializerContext; From 7fada0b1dc446696fd8e4a97c45a01047d40f3a5 Mon Sep 17 00:00:00 2001 From: Vinay Gera Date: Thu, 23 Oct 2025 00:50:47 -0700 Subject: [PATCH 2/4] update descriptions --- .../src/Commands/FoundryJsonContext.cs | 15 +++------------ .../src/Services/FoundryService.cs | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs index 0f7f45d4ea..526832e8ef 100644 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs +++ b/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs @@ -4,6 +4,7 @@ using System.Text.Json.Serialization; using Azure.Mcp.Tools.Foundry.Commands; using Azure.Mcp.Tools.Foundry.Models; +using Azure.Mcp.Tools.Foundry.Services; using Azure.Mcp.Tools.Foundry.Services.Models; using Microsoft.Extensions.AI; using Microsoft.Extensions.AI.Evaluation; @@ -56,18 +57,8 @@ namespace Azure.Mcp.Tools.Foundry.Commands; [JsonSerializable(typeof(AgentsQueryAndEvaluateResult))] [JsonSerializable(typeof(AgentsEvaluateResult))] [JsonSerializable(typeof(CognitiveServicesAccountDeploymentData))] +[JsonSerializable(typeof(FoundryService.ToolDefinitionAIFunction))] +[JsonSerializable(typeof(List))] [JsonSerializable(typeof(Dictionary))] -[JsonSerializable(typeof(Dictionary))] -[JsonSerializable(typeof(Dictionary))] -[JsonSerializable(typeof(Dictionary))] -[JsonSerializable(typeof(List))] -[JsonSerializable(typeof(List))] -[JsonSerializable(typeof(List))] -[JsonSerializable(typeof(List))] -[JsonSerializable(typeof(object))] -[JsonSerializable(typeof(string))] -[JsonSerializable(typeof(double))] -[JsonSerializable(typeof(int))] -[JsonSerializable(typeof(bool))] [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault)] internal sealed partial class FoundryJsonContext : JsonSerializerContext; diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Services/FoundryService.cs b/tools/Azure.Mcp.Tools.Foundry/src/Services/FoundryService.cs index d8af53d65d..7a1f3ad2ca 100644 --- a/tools/Azure.Mcp.Tools.Foundry/src/Services/FoundryService.cs +++ b/tools/Azure.Mcp.Tools.Foundry/src/Services/FoundryService.cs @@ -872,7 +872,7 @@ public async Task ConnectAgent( QueryText = query, Response = convertedResponse, Query = convertedRequestMessages, - ToolDefinitions = JsonSerializer.Serialize(toolDefinitions, (JsonTypeInfo>)AIJsonUtilities.DefaultOptions.GetTypeInfo(typeof(List))), + ToolDefinitions = JsonSerializer.Serialize(toolDefinitions, FoundryJsonContext.Default.ListToolDefinitionAIFunction), Citations = citations }; } From eae8423b9334d1b2bbd15df079a4801fe48ee311 Mon Sep 17 00:00:00 2001 From: Vinay Gera Date: Thu, 23 Oct 2025 00:59:49 -0700 Subject: [PATCH 3/4] update config --- tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs b/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs index 526832e8ef..7c134edd0a 100644 --- a/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs +++ b/tools/Azure.Mcp.Tools.Foundry/src/Commands/FoundryJsonContext.cs @@ -59,6 +59,5 @@ namespace Azure.Mcp.Tools.Foundry.Commands; [JsonSerializable(typeof(CognitiveServicesAccountDeploymentData))] [JsonSerializable(typeof(FoundryService.ToolDefinitionAIFunction))] [JsonSerializable(typeof(List))] -[JsonSerializable(typeof(Dictionary))] [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault)] internal sealed partial class FoundryJsonContext : JsonSerializerContext; From 24eb5e3bee9313e0042633044a89f7b53ef68c89 Mon Sep 17 00:00:00 2001 From: Vinay Gera Date: Fri, 24 Oct 2025 02:29:19 -0700 Subject: [PATCH 4/4] add headless check --- .../Authentication/CustomChainedCredential.cs | 81 ++++++++++++++++++- 1 file changed, 77 insertions(+), 4 deletions(-) diff --git a/core/Azure.Mcp.Core/src/Services/Azure/Authentication/CustomChainedCredential.cs b/core/Azure.Mcp.Core/src/Services/Azure/Authentication/CustomChainedCredential.cs index 7bad1661e7..4c9862ae11 100644 --- a/core/Azure.Mcp.Core/src/Services/Azure/Authentication/CustomChainedCredential.cs +++ b/core/Azure.Mcp.Core/src/Services/Azure/Authentication/CustomChainedCredential.cs @@ -11,8 +11,7 @@ namespace Azure.Mcp.Core.Services.Azure.Authentication; /// -/// A custom token credential that chains multiple Azure credentials with a broker-enabled instance of -/// InteractiveBrowserCredential to provide a seamless authentication experience. +/// A custom token credential that chains multiple Azure credentials with optional browser-enabled authentication. /// /// /// The credential chain behavior can be controlled via the AZURE_TOKEN_CREDENTIALS environment variable: @@ -26,7 +25,7 @@ namespace Azure.Mcp.Core.Services.Azure.Authentication; /// Special behavior: When running in VS Code context (VSCODE_PID environment variable is set) and AZURE_TOKEN_CREDENTIALS is not explicitly specified, /// Visual Studio Code credential is automatically prioritized first in the chain. /// -/// After the credential chain, Interactive Browser Authentication with Identity Broker is always added as the final fallback. +/// Interactive Browser Authentication is automatically disabled in headless environments (no DISPLAY/Wayland, CI/CD, containers, Windows services). /// public class CustomChainedCredential(string? tenantId = null, ILogger? logger = null) : TokenCredential { @@ -56,6 +55,71 @@ private static bool ShouldUseOnlyBrokerCredential() return EnvironmentHelpers.GetEnvironmentVariableAsBool(OnlyUseBrokerCredentialEnvVarName); } + private static bool IsHeadlessEnvironment() + { + bool nonInteractive = !Environment.UserInteractive; + + // ---------- OS-scoped heuristics ---------- + bool noDisplay = false; + bool inDocker = false, inK8s = false, cgroupContainer = false; + + if (OperatingSystem.IsLinux()) + { + string? display = Environment.GetEnvironmentVariable("DISPLAY"); + string? wayland = Environment.GetEnvironmentVariable("WAYLAND_DISPLAY"); + string? xdg = Environment.GetEnvironmentVariable("XDG_SESSION_TYPE"); + noDisplay = string.IsNullOrEmpty(display) && + string.IsNullOrEmpty(wayland) && + string.IsNullOrEmpty(xdg); + + try { inDocker = File.Exists("/.dockerenv"); } catch { /* ignore */ } + try { inK8s = File.Exists("/var/run/secrets/kubernetes.io/serviceaccount/token"); } catch { /* ignore */ } + try { + cgroupContainer = + FileContainsAny("/proc/1/cgroup", "docker", "kubepods", "containerd") || + FileContainsAny("/proc/self/mountinfo", "containers"); + } catch { /* ignore */ } + } + // Note: On macOS, skip DISPLAY/Wayland checks. On Windows, skip /proc and container-file checks. + + // ---------- CI/CD ---------- + bool inCI = + IsEnvTrue("CI") || + IsEnvTrue("GITHUB_ACTIONS") || + IsEnvTrue("GITLAB_CI") || + IsEnvTrue("AZP_CI") || + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TEAMCITY_VERSION")) || + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("BUILD_NUMBER")) || + !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("TF_BUILD")); + + // ---------- Windows service hint ---------- + bool winServiceLike = OperatingSystem.IsWindows() && + string.Equals(Environment.GetEnvironmentVariable("SESSIONNAME"), "Services", StringComparison.OrdinalIgnoreCase); + + return nonInteractive || noDisplay || inCI || inDocker || inK8s || cgroupContainer || winServiceLike; + } + + // helpers + private static bool IsEnvTrue(string key) + { + var v = Environment.GetEnvironmentVariable(key); + return v != null && (v.Equals("1", StringComparison.OrdinalIgnoreCase) + || v.Equals("true", StringComparison.OrdinalIgnoreCase) + || v.Equals("yes", StringComparison.OrdinalIgnoreCase)); + } + + private static bool FileContainsAny(string path, params string[] needles) + { + try + { + var txt = File.ReadAllText(path); + foreach (var n in needles) + if (txt.IndexOf(n, StringComparison.OrdinalIgnoreCase) >= 0) return true; + } + catch { /* ignore */ } + return false; + } + private static TokenCredential CreateCredential(string? tenantId, ILogger? logger = null) { string? authRecordJson = Environment.GetEnvironmentVariable(AuthenticationRecordEnvVarName); @@ -92,7 +156,16 @@ private static TokenCredential CreateCredential(string? tenantId, ILogger