Skip to content

Commit d94735d

Browse files
committed
changes for sql db export
1 parent bc00503 commit d94735d

File tree

12 files changed

+942
-4
lines changed

12 files changed

+942
-4
lines changed

docs/azmcp-commands.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1142,6 +1142,18 @@ azmcp sql db update --subscription <subscription> \
11421142
[--elastic-pool-name <elastic-pool-name>] \
11431143
[--zone-redundant <true/false>] \
11441144
[--read-scale <Enabled|Disabled>]
1145+
1146+
# Export an Azure SQL Database to a BACPAC file in Azure Storage
1147+
azmcp sql db export --subscription <subscription> \
1148+
--resource-group <resource-group> \
1149+
--server <server-name> \
1150+
--database <database-name> \
1151+
--storage-uri <storage-uri> \
1152+
--storage-key <storage-key> \
1153+
--storage-key-type <StorageAccessKey|SharedAccessKey|ManagedIdentity> \
1154+
--admin-user <admin-user> \
1155+
--admin-password <admin-password> \
1156+
[--auth-type <SQL|ADPassword|ManagedIdentity>]
11451157
```
11461158

11471159
#### Elastic Pool

docs/e2eTestPrompts.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,8 @@ This file contains prompts used for end-to-end testing to ensure each tool is in
408408
| azmcp_sql_db_show | Show me the details of SQL database <database_name> in server <server_name> |
409409
| azmcp_sql_db_update | Update the performance tier of SQL database <database_name> on server <server_name> |
410410
| azmcp_sql_db_update | Scale SQL database <database_name> on server <server_name> to use <sku_name> SKU |
411+
| azmcp_sql_db_export | Export SQL database <database_name> from server <server_name> to Azure Storage |
412+
| azmcp_sql_db_export | Create a BACPAC backup of database <database_name> on server <server_name> |
411413

412414
## Azure SQL Elastic Pool Operations
413415

servers/Azure.Mcp.Server/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ All Azure MCP tools in a single server. The Azure MCP Server implements the [MCP
88
## Table of Contents
99
- [Overview](#overview)
1010
- [Installation](#installation)
11-
- [IDE Extensions](#ide-extensions)
11+
- [IDE Extensions](#ide-extensions)
1212
- [VS Code (Recommended)](#vs-code-recommended)
1313
- [Visual Studio 2022](#visual-studio-2022)
1414
- [IntelliJ IDEA](#intellij-idea)
15-
- [Package Managers](#package-managers)
15+
- [Package Managers](#package-managers)
1616
- [NuGet](#nuget)
1717
- [NPM](#npm)
1818
- [Docker](#docker)
@@ -110,7 +110,7 @@ Optionally, use `--env` or `--volume` to pass authentication values.
110110

111111
## <a id="custom-clients"></a> 🤖 Custom Clients
112112

113-
You can easily configure your MCP client to use the Azure MCP Server.
113+
You can easily configure your MCP client to use the Azure MCP Server.
114114

115115
<details>
116116
<summary>Have your client run the following command and access it via standard IO:</summary>
@@ -164,7 +164,7 @@ The Azure MCP Server supercharges your agents with Azure context. Here are some
164164
* List foundry model deployments
165165
* List knowledge indexes
166166
* Get knowledge index schema configuration
167-
167+
168168
### 🔎 Azure AI Search
169169
170170
* "What indexes do I have in my Azure AI Search service 'mysvc'?"
@@ -245,6 +245,7 @@ The Azure MCP Server supercharges your agents with Azure context. Here are some
245245
* "List all databases in my Azure SQL server 'myserver'"
246246
* "Update the performance tier of my Azure SQL database 'mydb'"
247247
* "Rename my Azure SQL database 'mydb' to 'newname'"
248+
* "Export my Azure SQL database 'mydb' to a BACPAC file in Azure Storage"
248249
* "List all firewall rules for my Azure SQL server 'myserver'"
249250
* "Create a firewall rule for my Azure SQL server 'myserver'"
250251
* "Delete a firewall rule from my Azure SQL server 'myserver'"
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.CommandLine;
5+
using System.CommandLine.Parsing;
6+
using Azure.Mcp.Core.Commands;
7+
using Azure.Mcp.Core.Extensions;
8+
using Azure.Mcp.Core.Models.Command;
9+
using Azure.Mcp.Tools.Sql.Commands;
10+
using Azure.Mcp.Tools.Sql.Models;
11+
using Azure.Mcp.Tools.Sql.Options;
12+
using Azure.Mcp.Tools.Sql.Options.Database;
13+
using Azure.Mcp.Tools.Sql.Services;
14+
using Azure.ResourceManager.Sql.Models;
15+
using Microsoft.Extensions.Logging;
16+
17+
namespace Azure.Mcp.Tools.Sql.Commands.Database;
18+
19+
public sealed class DatabaseExportCommand(ILogger<DatabaseExportCommand> logger)
20+
: BaseDatabaseCommand<DatabaseExportOptions>(logger)
21+
{
22+
private const string CommandTitle = "Export SQL Database";
23+
24+
public override string Name => "export";
25+
26+
public override string Description =>
27+
"""
28+
Export an Azure SQL Database to a BACPAC file in Azure Storage. This command creates a logical backup
29+
of the database schema and data that can be used for archiving or migration purposes. The export
30+
operation is equivalent to 'az sql db export'. Returns export operation information including status.
31+
""";
32+
33+
public override string Title => CommandTitle;
34+
35+
public override ToolMetadata Metadata => new()
36+
{
37+
Destructive = false,
38+
Idempotent = false,
39+
OpenWorld = false,
40+
ReadOnly = true,
41+
LocalRequired = false,
42+
Secret = true
43+
};
44+
45+
protected override void RegisterOptions(Command command)
46+
{
47+
base.RegisterOptions(command);
48+
command.Options.Add(SqlOptionDefinitions.StorageUriOption);
49+
command.Options.Add(SqlOptionDefinitions.StorageKeyOption);
50+
command.Options.Add(SqlOptionDefinitions.StorageKeyTypeOption);
51+
command.Options.Add(SqlOptionDefinitions.AdminUserOption);
52+
command.Options.Add(SqlOptionDefinitions.AdminPasswordOption);
53+
command.Options.Add(SqlOptionDefinitions.AuthTypeOption);
54+
}
55+
56+
protected override DatabaseExportOptions BindOptions(ParseResult parseResult)
57+
{
58+
var options = base.BindOptions(parseResult);
59+
options.StorageUri = parseResult.GetValueOrDefault(SqlOptionDefinitions.StorageUriOption);
60+
options.StorageKey = parseResult.GetValueOrDefault(SqlOptionDefinitions.StorageKeyOption);
61+
options.StorageKeyType = parseResult.GetValueOrDefault(SqlOptionDefinitions.StorageKeyTypeOption);
62+
options.AdminUser = parseResult.GetValueOrDefault(SqlOptionDefinitions.AdminUserOption);
63+
options.AdminPassword = parseResult.GetValueOrDefault(SqlOptionDefinitions.AdminPasswordOption);
64+
options.AuthType = parseResult.GetValueOrDefault(SqlOptionDefinitions.AuthTypeOption);
65+
return options;
66+
}
67+
68+
public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult)
69+
{
70+
if (!Validate(parseResult.CommandResult, context.Response).IsValid)
71+
{
72+
return context.Response;
73+
}
74+
75+
var options = BindOptions(parseResult);
76+
77+
// Additional validation for export-specific parameters
78+
if (string.IsNullOrEmpty(options.StorageUri))
79+
{
80+
context.Response.Status = 400;
81+
context.Response.Message = "Storage URI is required for database export.";
82+
return context.Response;
83+
}
84+
85+
if (string.IsNullOrEmpty(options.StorageKey))
86+
{
87+
context.Response.Status = 400;
88+
context.Response.Message = "Storage key is required for database export.";
89+
return context.Response;
90+
}
91+
92+
if (string.IsNullOrEmpty(options.StorageKeyType))
93+
{
94+
context.Response.Status = 400;
95+
context.Response.Message = "Storage key type is required for database export.";
96+
return context.Response;
97+
}
98+
99+
if (string.IsNullOrEmpty(options.AdminUser))
100+
{
101+
context.Response.Status = 400;
102+
context.Response.Message = "Administrator user is required for database export.";
103+
return context.Response;
104+
}
105+
106+
if (string.IsNullOrEmpty(options.AdminPassword))
107+
{
108+
context.Response.Status = 400;
109+
context.Response.Message = "Administrator password is required for database export.";
110+
return context.Response;
111+
}
112+
113+
// Validate storage key type
114+
var validStorageKeyTypes = new[] { "StorageAccessKey", "SharedAccessKey", "ManagedIdentity" };
115+
if (!validStorageKeyTypes.Contains(options.StorageKeyType, StringComparer.OrdinalIgnoreCase))
116+
{
117+
context.Response.Status = 400;
118+
context.Response.Message = $"Invalid storage key type '{options.StorageKeyType}'. Valid values are: {string.Join(", ", validStorageKeyTypes)}";
119+
return context.Response;
120+
}
121+
122+
// Validate storage URI format
123+
if (!Uri.TryCreate(options.StorageUri, UriKind.Absolute, out _))
124+
{
125+
context.Response.Status = 400;
126+
context.Response.Message = "Storage URI must be a valid absolute URI.";
127+
return context.Response;
128+
}
129+
130+
// Validate authentication type if provided
131+
if (!string.IsNullOrEmpty(options.AuthType))
132+
{
133+
var validAuthTypes = new[] { "SQL", "ADPassword", "ManagedIdentity" };
134+
if (!validAuthTypes.Contains(options.AuthType, StringComparer.OrdinalIgnoreCase))
135+
{
136+
context.Response.Status = 400;
137+
context.Response.Message = $"Invalid authentication type '{options.AuthType}'. Valid values are: {string.Join(", ", validAuthTypes)}";
138+
return context.Response;
139+
}
140+
}
141+
142+
try
143+
{
144+
var sqlService = context.GetService<ISqlService>();
145+
146+
var exportResult = await sqlService.ExportDatabaseAsync(
147+
options.Server!,
148+
options.Database!,
149+
options.ResourceGroup!,
150+
options.Subscription!,
151+
options.StorageUri!,
152+
options.StorageKey!,
153+
options.StorageKeyType!,
154+
options.AdminUser!,
155+
options.AdminPassword!,
156+
options.AuthType,
157+
options.RetryPolicy);
158+
159+
context.Response.Results = ResponseResult.Create(
160+
new DatabaseExportResult(exportResult),
161+
SqlJsonContext.Default.DatabaseExportResult);
162+
}
163+
catch (Exception ex)
164+
{
165+
_logger.LogError(ex,
166+
"Error exporting SQL database. Server: {Server}, Database: {Database}, ResourceGroup: {ResourceGroup}, StorageUri: {StorageUri}",
167+
options.Server, options.Database, options.ResourceGroup, options.StorageUri);
168+
HandleException(context, ex);
169+
}
170+
171+
return context.Response;
172+
}
173+
174+
protected override string GetErrorMessage(Exception ex) => ex switch
175+
{
176+
RequestFailedException reqEx when reqEx.Status == 404 =>
177+
"SQL database or server not found. Verify the database name, server name, resource group, and that you have access.",
178+
RequestFailedException reqEx when reqEx.Status == 403 =>
179+
$"Authorization failed exporting the SQL database. Verify you have appropriate permissions and the storage account is accessible. Details: {reqEx.Message}",
180+
RequestFailedException reqEx when reqEx.Status == 400 =>
181+
$"Invalid export parameters. Check your storage URI, credentials, and database configuration. Details: {reqEx.Message}",
182+
ArgumentException argEx =>
183+
$"Invalid argument: {argEx.Message}",
184+
RequestFailedException reqEx => reqEx.Message,
185+
_ => base.GetErrorMessage(ex)
186+
};
187+
188+
internal record DatabaseExportResult(SqlDatabaseExportResult ExportResult);
189+
}

tools/Azure.Mcp.Tools.Sql/src/Commands/SqlJsonContext.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace Azure.Mcp.Tools.Sql.Commands;
1717
[JsonSerializable(typeof(DatabaseCreateCommand.DatabaseCreateResult))]
1818
[JsonSerializable(typeof(DatabaseUpdateCommand.DatabaseUpdateResult))]
1919
[JsonSerializable(typeof(DatabaseRenameCommand.DatabaseRenameResult))]
20+
[JsonSerializable(typeof(DatabaseExportCommand.DatabaseExportResult))]
2021
[JsonSerializable(typeof(DatabaseDeleteCommand.DatabaseDeleteResult))]
2122
[JsonSerializable(typeof(EntraAdminListCommand.EntraAdminListResult))]
2223
[JsonSerializable(typeof(FirewallRuleListCommand.FirewallRuleListResult))]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Text.Json.Serialization;
5+
6+
namespace Azure.Mcp.Tools.Sql.Models;
7+
8+
public record SqlDatabaseExportResult(
9+
[property: JsonPropertyName("operationId")] string? OperationId,
10+
[property: JsonPropertyName("requestId")] string? RequestId,
11+
[property: JsonPropertyName("status")] string? Status,
12+
[property: JsonPropertyName("queuedTime")] DateTimeOffset? QueuedTime,
13+
[property: JsonPropertyName("lastModifiedTime")] DateTimeOffset? LastModifiedTime,
14+
[property: JsonPropertyName("serverName")] string? ServerName,
15+
[property: JsonPropertyName("databaseName")] string? DatabaseName,
16+
[property: JsonPropertyName("storageUri")] string? StorageUri,
17+
[property: JsonPropertyName("message")] string? Message
18+
);
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Text.Json.Serialization;
5+
6+
namespace Azure.Mcp.Tools.Sql.Options.Database;
7+
8+
public class DatabaseExportOptions : BaseDatabaseOptions
9+
{
10+
[JsonPropertyName(SqlOptionDefinitions.StorageUri)]
11+
public string? StorageUri { get; set; }
12+
13+
[JsonPropertyName(SqlOptionDefinitions.StorageKey)]
14+
public string? StorageKey { get; set; }
15+
16+
[JsonPropertyName(SqlOptionDefinitions.StorageKeyType)]
17+
public string? StorageKeyType { get; set; }
18+
19+
[JsonPropertyName(SqlOptionDefinitions.AdminUser)]
20+
public string? AdminUser { get; set; }
21+
22+
[JsonPropertyName(SqlOptionDefinitions.AdminPassword)]
23+
public string? AdminPassword { get; set; }
24+
25+
[JsonPropertyName(SqlOptionDefinitions.AuthType)]
26+
public string? AuthType { get; set; }
27+
}

tools/Azure.Mcp.Tools.Sql/src/Options/SqlOptionDefinitions.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ public static class SqlOptionDefinitions
2525
public const string ElasticPoolName = "elastic-pool-name";
2626
public const string ZoneRedundant = "zone-redundant";
2727
public const string ReadScale = "read-scale";
28+
public const string StorageUri = "storage-uri";
29+
public const string StorageKey = "storage-key";
30+
public const string StorageKeyType = "storage-key-type";
31+
public const string AdminUser = "admin-user";
32+
public const string AdminPassword = "admin-password";
33+
public const string AuthType = "auth-type";
2834

2935
public static readonly Option<string> Server = new(
3036
$"--{ServerName}"
@@ -185,4 +191,52 @@ public static class SqlOptionDefinitions
185191
Description = "Read scale option for the database (Enabled or Disabled).",
186192
Required = false
187193
};
194+
195+
public static readonly Option<string> StorageUriOption = new(
196+
$"--{StorageUri}"
197+
)
198+
{
199+
Description = "The storage URI for the BACPAC file (e.g., https://mystorageaccount.blob.core.windows.net/mycontainer/myfile.bacpac).",
200+
Required = true
201+
};
202+
203+
public static readonly Option<string> StorageKeyOption = new(
204+
$"--{StorageKey}"
205+
)
206+
{
207+
Description = "The storage access key or shared access signature for the storage account.",
208+
Required = true
209+
};
210+
211+
public static readonly Option<string> StorageKeyTypeOption = new(
212+
$"--{StorageKeyType}"
213+
)
214+
{
215+
Description = "The storage key type (StorageAccessKey, SharedAccessKey, or ManagedIdentity).",
216+
Required = true
217+
};
218+
219+
public static readonly Option<string> AdminUserOption = new(
220+
$"--{AdminUser}"
221+
)
222+
{
223+
Description = "The SQL Server administrator login name for database access.",
224+
Required = true
225+
};
226+
227+
public static readonly Option<string> AdminPasswordOption = new(
228+
$"--{AdminPassword}"
229+
)
230+
{
231+
Description = "The SQL Server administrator password for database access.",
232+
Required = true
233+
};
234+
235+
public static readonly Option<string> AuthTypeOption = new(
236+
$"--{AuthType}"
237+
)
238+
{
239+
Description = "The authentication type (SQL, ADPassword, or ManagedIdentity).",
240+
Required = false
241+
};
188242
}

0 commit comments

Comments
 (0)