Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions docs/azmcp-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -1094,6 +1094,10 @@ azmcp sql server firewall-rule list --subscription <subscription> \
--resource-group <resource-group> \
--server <server-name>

# List SQL servers in a resource group
azmcp sql server list --subscription <subscription> \
--resource-group <resource-group>

# Delete a SQL server
azmcp sql server delete --subscription <subscription> \
--resource-group <resource-group> \
Expand Down
2 changes: 2 additions & 0 deletions docs/e2eTestPrompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,8 @@ This file contains prompts used for end-to-end testing to ensure each tool is in
| azmcp_sql_server_firewall-rule_list | List all firewall rules for SQL server <server_name> |
| azmcp_sql_server_firewall-rule_list | Show me the firewall rules for SQL server <server_name> |
| azmcp_sql_server_firewall-rule_list | What firewall rules are configured for my SQL server <server_name>? |
| azmcp_sql_server_list | List all Azure SQL servers in resource group <resource_group_name> |
| azmcp_sql_server_list | Show me every SQL server available in resource group <resource_group_name> |
| azmcp_sql_server_show | Show me the details of Azure SQL server <server_name> in resource group <resource_group_name> |
| azmcp_sql_server_show | Get the configuration details for SQL server <server_name> |
| azmcp_sql_server_show | Display the properties of SQL server <server_name> |
Expand Down
2 changes: 2 additions & 0 deletions servers/Azure.Mcp.Server/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The Azure MCP Server updates automatically by default whenever a new release com

### Features Added

- Added support for `azmcp sql server list` command to list SQL servers in a subscription and resource group. [[#503](https://github.com/microsoft/mcp/issues/503)]

### Breaking Changes

### Bugs Fixed
Expand Down
3 changes: 3 additions & 0 deletions servers/Azure.Mcp.Server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ The Azure MCP Server supercharges your agents with Azure context. Here are some

### 🗄️ Azure SQL Database

* "List all SQL servers in my subscription"
* "List all SQL servers in my resource group 'my-resource-group'"
* "Show me details about my Azure SQL database 'mydb'"
* "List all databases in my Azure SQL server 'myserver'"
* "Update the performance tier of my Azure SQL database 'mydb'"
Expand Down Expand Up @@ -326,6 +328,7 @@ The Azure MCP Server supercharges your agents with Azure context. Here are some
* List Microsoft Entra ID administrators for SQL servers
* Create new SQL servers
* Show details and properties of SQL servers
* List SQL servers in subscription or resource group
* Delete SQL servers

### 💾 Azure Storage
Expand Down
108 changes: 108 additions & 0 deletions tools/Azure.Mcp.Tools.Sql/src/Commands/Server/ServerListCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.CommandLine.Parsing;
using Azure;
using Azure.Mcp.Core.Commands;
using Azure.Mcp.Core.Extensions;
using Azure.Mcp.Core.Models.Option;
using Azure.Mcp.Tools.Sql.Commands;
using Azure.Mcp.Tools.Sql.Models;
using Azure.Mcp.Tools.Sql.Options.Server;
using Azure.Mcp.Tools.Sql.Services;
using Microsoft.Extensions.Logging;

namespace Azure.Mcp.Tools.Sql.Commands.Server;

public sealed class ServerListCommand(ILogger<ServerListCommand> logger)
: BaseSqlCommand<ServerListOptions>(logger)
{
private const string CommandTitle = "List SQL Servers";

public override string Name => "list";

public override string Description =>
"""
Lists Azure SQL servers within a resource group including fully qualified domain name, state,
administrator login, and network access settings. Use this command to discover SQL servers,
audit configurations, or verify deployment targets. Equivalent to 'az sql server list'.
Required parameters: subscription ID and resource group name.
Returns: JSON array of SQL server objects with metadata, network configuration, and tags.
""";

public override string Title => CommandTitle;

public override ToolMetadata Metadata => new()
{
Destructive = false,
Idempotent = true,
OpenWorld = true,
ReadOnly = true,
LocalRequired = false,
Secret = false
};

protected override void RegisterOptions(Command command)
{
// Only register subscription and resource group options, not server option
// since we're listing all servers in the resource group
command.Options.Add(OptionDefinitions.Common.Subscription.AsRequired());
command.Options.Add(OptionDefinitions.Common.ResourceGroup.AsRequired());
}

protected override ServerListOptions BindOptions(ParseResult parseResult)
{
var options = new ServerListOptions();
options.Subscription = parseResult.GetValueOrDefault<string>(OptionDefinitions.Common.Subscription.Name);
options.ResourceGroup = parseResult.GetValueOrDefault<string>(OptionDefinitions.Common.ResourceGroup.Name);
// Server property is inherited from BaseSqlOptions but not needed for listing
options.Server = null;
return options;
}

public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult)
{
if (!Validate(parseResult.CommandResult, context.Response).IsValid)
{
return context.Response;
}

var options = BindOptions(parseResult);

try
{
var sqlService = context.GetService<ISqlService>();

var servers = await sqlService.ListServersAsync(
options.ResourceGroup!,
options.Subscription!,
options.RetryPolicy);

context.Response.Results = ResponseResult.Create(
new ServerListResult(servers ?? []),
SqlJsonContext.Default.ServerListResult);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error listing SQL servers. ResourceGroup: {ResourceGroup}, Options: {@Options}",
options.ResourceGroup,
options);
HandleException(context, ex);
}

return context.Response;
}

protected override string GetErrorMessage(Exception ex) => ex switch
{
RequestFailedException reqEx when reqEx.Status == 403 =>
$"Authorization failed listing SQL servers. Verify you have appropriate permissions. Details: {reqEx.Message}",
RequestFailedException reqEx when reqEx.Status == 404 =>
"No SQL servers found for the specified resource group. Verify the resource group and subscription.",
RequestFailedException reqEx => reqEx.Message,
_ => base.GetErrorMessage(ex)
};

internal record ServerListResult(List<SqlServer> Servers);
}
1 change: 1 addition & 0 deletions tools/Azure.Mcp.Tools.Sql/src/Commands/SqlJsonContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ namespace Azure.Mcp.Tools.Sql.Commands;
[JsonSerializable(typeof(FirewallRuleDeleteCommand.FirewallRuleDeleteResult))]
[JsonSerializable(typeof(ServerCreateCommand.ServerCreateResult))]
[JsonSerializable(typeof(ServerDeleteCommand.ServerDeleteResult))]
[JsonSerializable(typeof(ServerListCommand.ServerListResult))]
[JsonSerializable(typeof(ServerShowCommand.ServerShowResult))]
[JsonSerializable(typeof(ElasticPoolListCommand.ElasticPoolListResult))]
[JsonSerializable(typeof(SqlDatabase))]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Azure.Mcp.Tools.Sql.Options;

namespace Azure.Mcp.Tools.Sql.Options.Server;

public class ServerListOptions : BaseSqlOptions
{
}
14 changes: 14 additions & 0 deletions tools/Azure.Mcp.Tools.Sql/src/Services/ISqlService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,20 @@ Task<SqlServer> GetServerAsync(
RetryPolicyOptions? retryPolicy,
CancellationToken cancellationToken = default);

/// <summary>
/// Lists SQL servers in a resource group.
/// </summary>
/// <param name="resourceGroup">The name of the resource group</param>
/// <param name="subscription">The subscription ID or name</param>
/// <param name="retryPolicy">Optional retry policy options</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>A list of SQL servers</returns>
Task<List<SqlServer>> ListServersAsync(
string resourceGroup,
string subscription,
RetryPolicyOptions? retryPolicy,
CancellationToken cancellationToken = default);

/// <summary>
/// Deletes a SQL server.
/// </summary>
Expand Down
74 changes: 74 additions & 0 deletions tools/Azure.Mcp.Tools.Sql/src/Services/SqlService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,60 @@ public async Task<SqlServer> GetServerAsync(
}
}

/// <summary>
/// Retrieves a list of SQL servers within a specific resource group.
/// </summary>
/// <param name="resourceGroup">The name of the resource group containing the servers</param>
/// <param name="subscription">The subscription ID or name</param>
/// <param name="retryPolicy">Optional retry policy configuration for resilient operations</param>
/// <param name="cancellationToken">Token to observe for cancellation requests</param>
/// <returns>A list of SQL servers found in the specified resource group</returns>
/// <exception cref="ArgumentException">Thrown when required parameters are null or empty</exception>
public async Task<List<SqlServer>> ListServersAsync(
string resourceGroup,
string subscription,
RetryPolicyOptions? retryPolicy,
CancellationToken cancellationToken = default)
{
ValidateRequiredParameters(resourceGroup, subscription);

try
{
var armClient = await CreateArmClientAsync(null, retryPolicy);
var subscriptionResource = armClient.GetSubscriptionResource(Azure.ResourceManager.Resources.SubscriptionResource.CreateResourceIdentifier(subscription));

Azure.ResourceManager.Resources.ResourceGroupResource resourceGroupResource;
try
{
var response = await subscriptionResource.GetResourceGroupAsync(resourceGroup, cancellationToken);
resourceGroupResource = response.Value;
}
catch (RequestFailedException reqEx) when (reqEx.Status == 404)
{
_logger.LogWarning(reqEx,
"Resource group not found when listing SQL servers. ResourceGroup: {ResourceGroup}, Subscription: {Subscription}",
resourceGroup, subscription);
return [];
}

var servers = new List<SqlServer>();

await foreach (var serverResource in resourceGroupResource.GetSqlServers().GetAllAsync(cancellationToken: cancellationToken))
{
servers.Add(ConvertToSqlServerModel(serverResource));
}

return servers;
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error listing SQL servers. ResourceGroup: {ResourceGroup}, Subscription: {Subscription}",
resourceGroup, subscription);
throw;
}
}

public async Task<bool> DeleteServerAsync(
string serverName,
string resourceGroup,
Expand Down Expand Up @@ -857,6 +911,26 @@ private static SqlDatabase ConvertToSqlDatabaseModel(JsonElement item)
);
}

private static SqlServer ConvertToSqlServerModel(SqlServerResource serverResource)
{
ArgumentNullException.ThrowIfNull(serverResource);

var data = serverResource.Data;
var tags = data.Tags?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) ?? new Dictionary<string, string>();

return new SqlServer(
Name: data.Name,
FullyQualifiedDomainName: data.FullyQualifiedDomainName,
Location: data.Location.ToString(),
ResourceGroup: data.Id.ResourceGroupName ?? "Unknown",
Subscription: data.Id.SubscriptionId ?? "Unknown",
AdministratorLogin: data.AdministratorLogin,
Version: data.Version,
State: data.State?.ToString(),
PublicNetworkAccess: data.PublicNetworkAccess?.ToString(),
Tags: tags.Count > 0 ? tags : null);
}

private static SqlServerEntraAdministrator ConvertToSqlServerEntraAdministratorModel(JsonElement item)
{
SqlServerAadAdministratorData? admin = SqlServerAadAdministratorData.FromJson(item);
Expand Down
1 change: 1 addition & 0 deletions tools/Azure.Mcp.Tools.Sql/src/SqlSetup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public void RegisterCommands(CommandGroup rootGroup, ILoggerFactory loggerFactor

server.AddCommand("create", new ServerCreateCommand(loggerFactory.CreateLogger<ServerCreateCommand>()));
server.AddCommand("delete", new ServerDeleteCommand(loggerFactory.CreateLogger<ServerDeleteCommand>()));
server.AddCommand("list", new ServerListCommand(loggerFactory.CreateLogger<ServerListCommand>()));
server.AddCommand("show", new ServerShowCommand(loggerFactory.CreateLogger<ServerShowCommand>()));

var elasticPool = new CommandGroup("elastic-pool", "SQL elastic pool operations");
Expand Down
Loading
Loading