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
20 changes: 20 additions & 0 deletions docs/azmcp-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,26 @@ azmcp sql elastic-pool list --subscription <subscription> \
### Azure SQL Server Operations

```bash
# Create a new SQL server
azmcp sql server create --subscription <subscription> \
--resource-group <resource-group> \
--server <server-name> \
--location <location> \
--admin-user <admin-username> \
--admin-password <admin-password> \
[--version <server-version>] \
[--public-network-access <enabled|disabled>]

# Delete a SQL server
azmcp sql server delete --subscription <subscription> \
--resource-group <resource-group> \
--server <server-name>

# Show details of a specific SQL server
azmcp sql server show --subscription <subscription> \
--resource-group <resource-group> \
--server <server-name>

# List Microsoft Entra ID administrators for a SQL server
azmcp sql server entra-admin list --subscription <subscription> \
--resource-group <resource-group> \
Expand Down
9 changes: 9 additions & 0 deletions docs/e2eTestPrompts.md
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,12 @@ This file contains prompts used for end-to-end testing to ensure each tool is in

| Tool Name | Test Prompt |
|:----------|:----------|
| azmcp_sql_server_create | Create a new Azure SQL server named <server_name> in resource group <resource_group_name> |
| azmcp_sql_server_create | Create an Azure SQL server with name <server_name> in location <location> with admin user <admin_user> |
| azmcp_sql_server_create | Set up a new SQL server called <server_name> in my resource group <resource_group_name> |
| azmcp_sql_server_delete | Delete the Azure SQL server <server_name> from resource group <resource_group_name> |
| azmcp_sql_server_delete | Remove the SQL server <server_name> from my subscription |
| azmcp_sql_server_delete | Delete SQL server <server_name> permanently |
| azmcp_sql_server_entra-admin_list | List Microsoft Entra ID administrators for SQL server <server_name> |
| azmcp_sql_server_entra-admin_list | Show me the Entra ID administrators configured for SQL server <server_name> |
| azmcp_sql_server_entra-admin_list | What Microsoft Entra ID administrators are set up for my SQL server <server_name>? |
Expand All @@ -399,6 +405,9 @@ 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_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> |

## Azure Storage

Expand Down
1 change: 1 addition & 0 deletions servers/Azure.Mcp.Server/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The Azure MCP Server updates automatically by default whenever a new release com
### Features Added

- Added elicitation support. An elicitation request is sent if the tool annotation secret hint is true. [[#404](https://github.com/microsoft/mcp/pull/404)]
- Added `azmcp sql server create`, `azmcp sql server delete`, `azmcp sql server show` to support SQL server create, delete, and show commands. [[#312](https://github.com/microsoft/mcp/pull/312)]
- Added the following Azure Managed Lustre commands: [[#100](https://github.com/microsoft/mcp/issues/100)]
- `azmcp_azuremanagedlustre_filesystem_get_sku_info`: Get information about Azure Managed Lustre SKU.

Expand Down
6 changes: 6 additions & 0 deletions servers/Azure.Mcp.Server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ The Azure MCP Server supercharges your agents with Azure context. Here are some
* "Delete a firewall rule from my Azure SQL server 'myserver'"
* "List all elastic pools in my Azure SQL server 'myserver'"
* "List Active Directory administrators for my Azure SQL server 'myserver'"
* "Create a new Azure SQL server in my resource group"
* "Show me details about my Azure SQL server 'myserver'"
* "Delete my Azure SQL server 'myserver'"

### 💾 Azure Storage

Expand Down Expand Up @@ -323,6 +326,9 @@ The Azure MCP Server supercharges your agents with Azure context. Here are some
### 🗄️ Azure SQL Server

* List Microsoft Entra ID administrators for SQL servers
* Create new SQL servers
* Show details and properties of SQL servers
* Delete SQL servers

### 💾 Azure Storage

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

using Azure.Mcp.Core.Commands;
using Azure.Mcp.Core.Extensions;
using Azure.Mcp.Core.Services.Telemetry;
using Azure.Mcp.Tools.Sql.Models;
using Azure.Mcp.Tools.Sql.Options;
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 ServerCreateCommand(ILogger<ServerCreateCommand> logger)
: BaseSqlCommand<ServerCreateOptions>(logger)
{
private const string CommandTitle = "Create SQL Server";

private readonly Option<string> _administratorLoginOption = SqlOptionDefinitions.AdministratorLoginOption;
private readonly Option<string> _administratorPasswordOption = SqlOptionDefinitions.AdministratorPasswordOption;
private readonly Option<string> _locationOption = SqlOptionDefinitions.LocationOption;
private readonly Option<string> _versionOption = SqlOptionDefinitions.VersionOption;
private readonly Option<string> _publicNetworkAccessOption = SqlOptionDefinitions.PublicNetworkAccessOption;

public override string Name => "create";

public override string Description =>
"""
Creates a new Azure SQL server in the specified resource group and location.
The server will be created with the specified administrator credentials and
optional configuration settings. Returns the created server with its properties
including the fully qualified domain name.
""";

public override string Title => CommandTitle;

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

protected override void RegisterOptions(Command command)
{
base.RegisterOptions(command);
command.Options.Add(_administratorLoginOption);
command.Options.Add(_administratorPasswordOption);
command.Options.Add(_locationOption);
command.Options.Add(_versionOption);
command.Options.Add(_publicNetworkAccessOption);
}

protected override ServerCreateOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);
options.AdministratorLogin = parseResult.GetValueOrDefault(_administratorLoginOption);
options.AdministratorPassword = parseResult.GetValueOrDefault(_administratorPasswordOption);
options.Location = parseResult.GetValueOrDefault(_locationOption);
options.Version = parseResult.GetValueOrDefault(_versionOption);
options.PublicNetworkAccess = parseResult.GetValueOrDefault(_publicNetworkAccessOption);
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 server = await sqlService.CreateServerAsync(
options.Server!,
options.ResourceGroup!,
options.Subscription!,
options.Location!,
options.AdministratorLogin!,
options.AdministratorPassword!,
options.Version,
options.PublicNetworkAccess,
options.RetryPolicy);

context.Response.Results = ResponseResult.Create(
new ServerCreateResult(server),
SqlJsonContext.Default.ServerCreateResult);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error creating SQL server. Server: {Server}, ResourceGroup: {ResourceGroup}, Location: {Location}, Options: {@Options}",
options.Server, options.ResourceGroup, options.Location, options);
HandleException(context, ex);
}

return context.Response;
}

protected override string GetErrorMessage(Exception ex) => ex switch
{
Azure.RequestFailedException reqEx when reqEx.Status == 409 =>
"A SQL server with this name already exists. Choose a different server name.",
Azure.RequestFailedException reqEx when reqEx.Status == 403 =>
$"Authorization failed creating the SQL server. Verify you have appropriate permissions. Details: {reqEx.Message}",
Azure.RequestFailedException reqEx when reqEx.Status == 400 =>
$"Invalid request parameters for SQL server creation: {reqEx.Message}",
Azure.RequestFailedException reqEx => reqEx.Message,
ArgumentException argEx => $"Invalid parameter: {argEx.Message}",
_ => base.GetErrorMessage(ex)
};

protected override int GetStatusCode(Exception ex) => ex switch
{
Azure.RequestFailedException reqEx => reqEx.Status,
ArgumentException => 400,
_ => base.GetStatusCode(ex)
};

internal record ServerCreateResult(SqlServer Server);
}
129 changes: 129 additions & 0 deletions tools/Azure.Mcp.Tools.Sql/src/Commands/Server/ServerDeleteCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Azure.Mcp.Core.Commands;
using Azure.Mcp.Core.Extensions;
using Azure.Mcp.Core.Services.Telemetry;
using Azure.Mcp.Tools.Sql.Options;
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 ServerDeleteCommand(ILogger<ServerDeleteCommand> logger)
: BaseSqlCommand<ServerDeleteOptions>(logger)
{
private const string CommandTitle = "Delete SQL Server";

private readonly Option<bool> _forceOption = SqlOptionDefinitions.ForceOption;

public override string Name => "delete";

public override string Description =>
"""
Deletes an Azure SQL server and all of its databases from the specified resource group.
This operation is irreversible and will permanently remove the server and all its data.
Use the --force flag to skip confirmation prompts.
""";

public override string Title => CommandTitle;

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

protected override void RegisterOptions(Command command)
{
base.RegisterOptions(command);
command.Options.Add(_forceOption);
}

protected override ServerDeleteOptions BindOptions(ParseResult parseResult)
{
var options = base.BindOptions(parseResult);
options.Force = parseResult.GetValueOrDefault(_forceOption);
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
{
// Show warning about destructive operation unless force is specified
if (!options.Force)
{
context.Response.Status = 200;
context.Response.Message =
$"WARNING: This operation will permanently delete the SQL server '{options.Server}' " +
$"and ALL its databases in resource group '{options.ResourceGroup}'. " +
$"This action cannot be undone. Use --force to confirm deletion.";
return context.Response;
}

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

var deleted = await sqlService.DeleteServerAsync(
options.Server!,
options.ResourceGroup!,
options.Subscription!,
options.RetryPolicy);

if (deleted)
{
context.Response.Results = ResponseResult.Create(
new ServerDeleteResult($"SQL server '{options.Server}' was successfully deleted.", true),
SqlJsonContext.Default.ServerDeleteResult);
}
else
{
context.Response.Status = 404;
context.Response.Message = $"SQL server '{options.Server}' not found in resource group '{options.ResourceGroup}'.";
}
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error deleting SQL server. Server: {Server}, ResourceGroup: {ResourceGroup}, Options: {@Options}",
options.Server, options.ResourceGroup, options);
HandleException(context, ex);
}

return context.Response;
}

protected override string GetErrorMessage(Exception ex) => ex switch
{
Azure.RequestFailedException reqEx when reqEx.Status == 404 =>
$"The given SQL server not found. It may have already been deleted.",
Azure.RequestFailedException reqEx when reqEx.Status == 403 =>
$"Authorization failed deleting the SQL server. Verify you have appropriate permissions. Details: {reqEx.Message}",
Azure.RequestFailedException reqEx when reqEx.Status == 409 =>
$"Cannot delete SQL server due to a conflict. It may be in use or have dependent resources. Details: {reqEx.Message}",
Azure.RequestFailedException reqEx => reqEx.Message,
ArgumentException argEx => $"Invalid parameter: {argEx.Message}",
_ => base.GetErrorMessage(ex)
};

protected override int GetStatusCode(Exception ex) => ex switch
{
Azure.RequestFailedException reqEx => reqEx.Status,
ArgumentException => 400,
_ => base.GetStatusCode(ex)
};

internal record ServerDeleteResult(string Message, bool Success);
}
Loading