File: Agents\McpConfigFileHelper.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.Tool.csproj (aspire)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Nodes;
using Aspire.Cli.Resources;
 
namespace Aspire.Cli.Agents;
 
/// <summary>
/// Provides shared methods for reading and parsing MCP configuration files across agent environment scanners.
/// </summary>
internal static class McpConfigFileHelper
{
    /// <summary>
    /// Checks if a specific server is configured in an MCP config file under the given container key.
    /// </summary>
    /// <param name="configFilePath">The path to the MCP configuration file.</param>
    /// <param name="serverContainerKey">The JSON property name that holds the servers object (e.g., "servers", "mcpServers", "mcp").</param>
    /// <param name="serverName">The name of the server to check for.</param>
    /// <param name="preprocessContent">Optional function to preprocess file content before parsing (e.g., to strip JSONC comments).</param>
    /// <returns><c>true</c> if the server is configured; <c>false</c> otherwise (including when the file is missing or malformed).</returns>
    public static bool HasServerConfigured(string configFilePath, string serverContainerKey, string serverName, Func<string, string>? preprocessContent = null)
    {
        if (!File.Exists(configFilePath))
        {
            return false;
        }
 
        try
        {
            var content = File.ReadAllText(configFilePath);
 
            if (preprocessContent is not null)
            {
                content = preprocessContent(content);
            }
 
            var config = JsonNode.Parse(content)?.AsObject();
 
            if (config is null)
            {
                return false;
            }
 
            if (config.TryGetPropertyValue(serverContainerKey, out var serversNode) && serversNode is JsonObject servers)
            {
                return servers.ContainsKey(serverName);
            }
 
            return false;
        }
        catch (JsonException)
        {
            return false;
        }
    }
 
    /// <summary>
    /// Reads an existing MCP config file and parses it into a <see cref="JsonObject"/>, or creates a new one if the file doesn't exist.
    /// </summary>
    /// <param name="configFilePath">The path to the MCP configuration file.</param>
    /// <param name="cancellationToken">A cancellation token.</param>
    /// <param name="preprocessContent">Optional function to preprocess file content before parsing (e.g., to strip JSONC comments).</param>
    /// <returns>The parsed <see cref="JsonObject"/> from the file, or a new empty <see cref="JsonObject"/> if the file doesn't exist.</returns>
    /// <exception cref="InvalidOperationException">Thrown when the file exists but contains malformed JSON, wrapping the underlying <see cref="JsonException"/>.</exception>
    public static async Task<JsonObject> ReadConfigAsync(string configFilePath, CancellationToken cancellationToken, Func<string, string>? preprocessContent = null)
    {
        if (!File.Exists(configFilePath))
        {
            return new JsonObject();
        }
 
        var content = await File.ReadAllTextAsync(configFilePath, cancellationToken);
 
        if (preprocessContent is not null)
        {
            content = preprocessContent(content);
        }
 
        try
        {
            return JsonNode.Parse(content)?.AsObject() ?? new JsonObject();
        }
        catch (JsonException ex)
        {
            throw new InvalidOperationException(
                string.Format(CultureInfo.CurrentCulture, AgentCommandStrings.MalformedConfigFileError, configFilePath), ex);
        }
    }
}