File: Commands\McpToolsCommand.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.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.CommandLine;
using System.Globalization;
using System.Text.Json;
using Aspire.Cli.Backchannel;
using Aspire.Cli.Configuration;
using Aspire.Cli.Interaction;
using Aspire.Cli.Resources;
using Aspire.Cli.Telemetry;
using Aspire.Cli.Utils;
using Microsoft.Extensions.Logging;
using Spectre.Console;
 
namespace Aspire.Cli.Commands;
 
/// <summary>
/// Lists MCP tools exposed by running Aspire resources.
/// </summary>
internal sealed class McpToolsCommand : BaseCommand
{
    internal override HelpGroup HelpGroup => HelpGroup.ToolsAndConfiguration;
 
    private readonly IInteractionService _interactionService;
    private readonly AppHostConnectionResolver _connectionResolver;
 
    private static readonly OptionWithLegacy<FileInfo?> s_appHostOption = new("--apphost", "--project", SharedCommandStrings.AppHostOptionDescription);
    private static readonly Option<OutputFormat> s_formatOption = new("--format")
    {
        Description = "Output format (Table or Json)."
    };
 
    public McpToolsCommand(
        IInteractionService interactionService,
        IAuxiliaryBackchannelMonitor backchannelMonitor,
        IFeatures features,
        ICliUpdateNotifier updateNotifier,
        CliExecutionContext executionContext,
        AspireCliTelemetry telemetry,
        ILogger<McpToolsCommand> logger)
        : base("tools", "List MCP tools exposed by running resources.", features, updateNotifier, executionContext, interactionService, telemetry)
    {
        _interactionService = interactionService;
        _connectionResolver = new AppHostConnectionResolver(backchannelMonitor, interactionService, executionContext, logger);
 
        Options.Add(s_appHostOption);
        Options.Add(s_formatOption);
    }
 
    protected override async Task<int> ExecuteAsync(ParseResult parseResult, CancellationToken cancellationToken)
    {
        var passedAppHostProjectFile = parseResult.GetValue(s_appHostOption);
        var format = parseResult.GetValue(s_formatOption);
 
        var result = await _connectionResolver.ResolveConnectionAsync(
            passedAppHostProjectFile,
            SharedCommandStrings.ScanningForRunningAppHosts,
            string.Format(CultureInfo.CurrentCulture, SharedCommandStrings.SelectAppHost, "list MCP tools for"),
            SharedCommandStrings.AppHostNotRunning,
            cancellationToken);
 
        if (!result.Success)
        {
            _interactionService.DisplayMessage(KnownEmojis.Information, result.ErrorMessage);
            return ExitCodeConstants.Success;
        }
 
        var connection = result.Connection!;
        var snapshots = await connection.GetResourceSnapshotsAsync(cancellationToken);
        var resourcesWithTools = snapshots.Where(r => r.McpServer is not null).ToList();
 
        if (resourcesWithTools.Count == 0)
        {
            _interactionService.DisplayMessage(KnownEmojis.Information, "No resources with MCP tools found.");
            return ExitCodeConstants.Success;
        }
 
        if (format == OutputFormat.Json)
        {
            using var stream = new MemoryStream();
            using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }))
            {
                writer.WriteStartArray();
                foreach (var r in resourcesWithTools)
                {
                    var resourceName = r.DisplayName ?? r.Name;
                    foreach (var t in r.McpServer!.Tools)
                    {
                        writer.WriteStartObject();
                        writer.WriteString("resource", resourceName);
                        writer.WriteString("tool", t.Name);
                        writer.WriteString("description", t.Description ?? "");
                        writer.WritePropertyName("inputSchema");
                        t.InputSchema.WriteTo(writer);
                        writer.WriteEndObject();
                    }
                }
                writer.WriteEndArray();
            }
 
            _interactionService.DisplayRawText(System.Text.Encoding.UTF8.GetString(stream.ToArray()));
        }
        else
        {
            var table = new Table();
            table.AddColumn("Resource");
            table.AddColumn("Tool");
            table.AddColumn("Description");
 
            foreach (var resource in resourcesWithTools)
            {
                var resourceName = resource.DisplayName ?? resource.Name;
                foreach (var tool in resource.McpServer!.Tools)
                {
                    table.AddRow(
                        resourceName.EscapeMarkup(),
                        tool.Name.EscapeMarkup(),
                        (tool.Description ?? "").EscapeMarkup());
                }
            }
 
            _interactionService.DisplayRenderable(table);
        }
 
        return ExitCodeConstants.Success;
    }
}