Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.CommandLine.Parsing;
using Azure.Mcp.Core.Areas.Tools.Options;
using Azure.Mcp.Core.Commands;
using Azure.Mcp.Core.Extensions;
using Azure.Mcp.Core.Models;
using Azure.Mcp.Core.Models.Command;
using Azure.Mcp.Core.Models.Option;
using Microsoft.Extensions.Logging;

Expand All @@ -19,7 +23,7 @@ public sealed class ToolsListCommand(ILogger<ToolsListCommand> logger) : BaseCom
"""
List all available commands and their tools in a hierarchical structure. This command returns detailed information
about each command, including its name, description, full command path, available subcommands, and all supported
arguments. Use this to explore the CLI's functionality or to build interactive command interfaces.
arguments. Use --name to return only tool names, and --namespace to filter by specific namespaces.
""";

public override string Title => CommandTitle;
Expand All @@ -37,14 +41,19 @@ arguments. Use this to explore the CLI's functionality or to build interactive c
protected override void RegisterOptions(Command command)
{
base.RegisterOptions(command);
command.Options.Add(ToolsListOptionDefinitions.Namespaces);
command.Options.Add(ToolsListOptionDefinitions.NamespaceMode);
command.Options.Add(ToolsListOptionDefinitions.Namespace);
command.Options.Add(ToolsListOptionDefinitions.Name);
}

protected override ToolsListOptions BindOptions(ParseResult parseResult)
{
var namespaces = parseResult.GetValueOrDefault<string[]>(ToolsListOptionDefinitions.Namespace.Name) ?? [];
return new ToolsListOptions
{
Namespaces = parseResult.GetValueOrDefault(ToolsListOptionDefinitions.Namespaces)
NamespaceMode = parseResult.GetValueOrDefault(ToolsListOptionDefinitions.NamespaceMode),
Name = parseResult.GetValueOrDefault(ToolsListOptionDefinitions.Name),
Namespaces = namespaces.ToList()
};
}

Expand All @@ -55,8 +64,33 @@ public override async Task<CommandResponse> ExecuteAsync(CommandContext context,
var factory = context.GetService<CommandFactory>();
var options = BindOptions(parseResult);

// If the --namespaces flag is set, return distinct top‑level namespaces (child groups beneath root 'azmcp').
if (options.Namespaces)
// If the --name flag is set, return only tool names
if (options.Name)
{
// Get all visible commands and extract their tokenized names (full command paths)
var allToolNames = CommandFactory.GetVisibleCommands(factory.AllCommands)
.Select(kvp => kvp.Key) // Use the tokenized key instead of just the command name
.Where(name => !string.IsNullOrEmpty(name));

// Apply namespace filtering if specified
if (options.Namespaces.Count > 0)
{
var namespacePrefixes = options.Namespaces.Select(ns => $"azmcp_{ns}_").ToList();
allToolNames = allToolNames.Where(name =>
namespacePrefixes.Any(prefix => name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)));
}

var toolNames = await Task.Run(() => allToolNames
.OrderBy(name => name, StringComparer.OrdinalIgnoreCase)
.ToList());

var result = new ToolNamesResult(toolNames);
context.Response.Results = ResponseResult.Create(result, ModelsJsonContext.Default.ToolNamesResult);
return context.Response;
}

// If the --namespace-mode flag is set, return distinct top‑level namespaces (child groups beneath root 'azmcp').
if (options.NamespaceMode)
{
var ignored = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "server", "tools" };
var surfaced = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { "extension" };
Expand Down Expand Up @@ -99,9 +133,19 @@ public override async Task<CommandResponse> ExecuteAsync(CommandContext context,
return context.Response;
}

var tools = await Task.Run(() => CommandFactory.GetVisibleCommands(factory.AllCommands)
.Select(kvp => CreateCommand(kvp.Key, kvp.Value))
.ToList());
// Get all tools with full details
var allTools = CommandFactory.GetVisibleCommands(factory.AllCommands)
.Select(kvp => CreateCommand(kvp.Key, kvp.Value));

// Apply namespace filtering if specified
if (options.Namespaces.Count > 0)
{
var namespacePrefixes = options.Namespaces.Select(ns => $"azmcp {ns}").ToList();
allTools = allTools.Where(tool =>
namespacePrefixes.Any(prefix => tool.Command.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)));
}

var tools = await Task.Run(() => allTools.ToList());

context.Response.Results = ResponseResult.Create(tools, ModelsJsonContext.Default.ListCommandInfo);
return context.Response;
Expand Down Expand Up @@ -135,4 +179,6 @@ private static CommandInfo CreateCommand(string tokenizedName, IBaseCommand comm
Metadata = command.Metadata
};
}

public record ToolNamesResult(List<string> Names);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,26 @@ namespace Azure.Mcp.Core.Areas.Tools.Options;

public static class ToolsListOptionDefinitions
{
public const string NamespacesOptionName = "namespaces";
public const string NamespaceModeOptionName = "namespace-mode";
public const string NamespaceOptionName = "namespace";
public const string NameOptionName = "name";

public static readonly Option<bool> Namespaces = new($"--{NamespacesOptionName}")
public static readonly Option<bool> NamespaceMode = new($"--{NamespaceModeOptionName}")
{
Description = "If specified, returns a list of top-level service namespaces instead of individual tools.",
Required = false
};

public static readonly Option<string[]> Namespace = new($"--{NamespaceOptionName}")
{
Description = "Filter tools by namespace (e.g., 'storage', 'keyvault'). Can be specified multiple times to include multiple namespaces.",
Required = false,
AllowMultipleArgumentsPerToken = true
};

public static readonly Option<bool> Name = new($"--{NameOptionName}")
{
Description = "If specified, returns only tool names without descriptions or metadata.",
Required = false
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,15 @@ namespace Azure.Mcp.Core.Areas.Tools.Options;

public sealed class ToolsListOptions
{
public bool Namespaces { get; set; } = false;
public bool NamespaceMode { get; set; } = false;

/// <summary>
/// If true, returns only tool names without descriptions or metadata.
/// </summary>
public bool Name { get; set; } = false;

/// <summary>
/// Optional namespaces to filter tools. If provided, only tools from these namespaces will be returned.
/// </summary>
public List<string> Namespaces { get; set; } = new();
}
2 changes: 2 additions & 0 deletions core/Azure.Mcp.Core/src/Models/ModelsJsonContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System.Text.Json.Serialization;
using Azure.Mcp.Core.Areas.Tools.Commands;
using Azure.Mcp.Core.Commands;
using Azure.Mcp.Core.Models.Elicitation;

Expand All @@ -13,6 +14,7 @@ namespace Azure.Mcp.Core.Models;
[JsonSerializable(typeof(ElicitationSchemaRoot))]
[JsonSerializable(typeof(ElicitationSchemaProperty))]
[JsonSerializable(typeof(ToolMetadata))]
[JsonSerializable(typeof(ToolsListCommand.ToolNamesResult))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
public sealed partial class ModelsJsonContext : JsonSerializerContext
{
Expand Down
Loading
Loading