Skip to content

Commit 991b484

Browse files
committed
rebase and error code fix
1 parent d94735d commit 991b484

File tree

8 files changed

+44
-44
lines changed

8 files changed

+44
-44
lines changed

docs/azmcp-commands.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ azmcp appconfig kv list --subscription <subscription> \
236236
[--key <key>] \
237237
[--label <label>]
238238

239-
# Lock (make it read-only) or unlock (remove read-only) a key-value setting
239+
# Lock (make it read-only) or unlock (remove read-only) a key-value setting
240240
azmcp appconfig kv lock set --subscription <subscription> \
241241
--account <account> \
242242
--key <key> \
@@ -695,7 +695,7 @@ azmcp keyvault key list --subscription <subscription> \
695695
Tools that handle sensitive data such as secrets require user consent before execution through a security mechanism called **elicitation**. When you run commands that access sensitive information, the MCP client will prompt you to confirm the operation before proceeding.
696696

697697
> **🛡️ Elicitation (user confirmation) Security Feature:**
698-
>
698+
>
699699
> Elicitation prompts appear when tools may expose sensitive information like:
700700
> - Key Vault secrets
701701
> - Connection strings and passwords
@@ -1115,7 +1115,7 @@ azmcp sql db delete --subscription <subscription> \
11151115
azmcp sql db list --subscription <subscription> \
11161116
--resource-group <resource-group> \
11171117
--server <server-name>
1118-
1118+
11191119
# Rename an existing SQL database to a new name within the same server
11201120
azmcp sql db rename --subscription <subscription> \
11211121
--resource-group <resource-group> \
@@ -1150,7 +1150,7 @@ azmcp sql db export --subscription <subscription> \
11501150
--database <database-name> \
11511151
--storage-uri <storage-uri> \
11521152
--storage-key <storage-key> \
1153-
--storage-key-type <StorageAccessKey|SharedAccessKey|ManagedIdentity> \
1153+
--storage-key-type <StorageAccessKey|SharedAccessKey> \
11541154
--admin-user <admin-user> \
11551155
--admin-password <admin-password> \
11561156
[--auth-type <SQL|ADPassword|ManagedIdentity>]

servers/Azure.Mcp.Server/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ The Azure MCP Server updates automatically by default whenever a new release com
1414

1515
- Added support for listing SQL servers in a subscription and resource group via the command `azmcp_sql_server_list`. [[#503](https://github.com/microsoft/mcp/issues/503)]
1616
- Added support for renaming Azure SQL databases within a server while retaining configuration via the `azmcp sql db rename` command. [[#542](https://github.com/microsoft/mcp/pull/542)]
17+
- Added support for exporting Azure SQL databases to BACPAC files in Azure Storage via the `azmcp sql db export` command. [[#526](https://github.com/microsoft/mcp/pull/526)]
1718
- Added support for Azure App Service database management via the command `azmcp_appservice_database_add`. [[#59](https://github.com/microsoft/mcp/pull/59)]
1819
- Added the following Azure Foundry agents commands: [[#55](https://github.com/microsoft/mcp/pull/55)]
1920
- `azmcp_foundry_agents_connect`: Connect to an agent in an AI Foundry project and query it

tools/Azure.Mcp.Tools.Sql/src/Commands/Database/DatabaseExportCommand.cs

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
using System.CommandLine;
55
using System.CommandLine.Parsing;
6+
using System.Net;
67
using Azure.Mcp.Core.Commands;
78
using Azure.Mcp.Core.Extensions;
89
using Azure.Mcp.Core.Models.Command;
@@ -37,7 +38,7 @@ operation is equivalent to 'az sql db export'. Returns export operation informat
3738
Destructive = false,
3839
Idempotent = false,
3940
OpenWorld = false,
40-
ReadOnly = true,
41+
ReadOnly = false,
4142
LocalRequired = false,
4243
Secret = true
4344
};
@@ -77,63 +78,60 @@ public override async Task<CommandResponse> ExecuteAsync(CommandContext context,
7778
// Additional validation for export-specific parameters
7879
if (string.IsNullOrEmpty(options.StorageUri))
7980
{
80-
context.Response.Status = 400;
81+
context.Response.Status = HttpStatusCode.BadRequest;
8182
context.Response.Message = "Storage URI is required for database export.";
8283
return context.Response;
8384
}
8485

8586
if (string.IsNullOrEmpty(options.StorageKey))
8687
{
87-
context.Response.Status = 400;
88+
context.Response.Status = HttpStatusCode.BadRequest;
8889
context.Response.Message = "Storage key is required for database export.";
8990
return context.Response;
9091
}
9192

9293
if (string.IsNullOrEmpty(options.StorageKeyType))
9394
{
94-
context.Response.Status = 400;
95+
context.Response.Status = HttpStatusCode.BadRequest;
9596
context.Response.Message = "Storage key type is required for database export.";
9697
return context.Response;
9798
}
9899

99100
if (string.IsNullOrEmpty(options.AdminUser))
100101
{
101-
context.Response.Status = 400;
102+
context.Response.Status = HttpStatusCode.BadRequest;
102103
context.Response.Message = "Administrator user is required for database export.";
103104
return context.Response;
104105
}
105106

106107
if (string.IsNullOrEmpty(options.AdminPassword))
107108
{
108-
context.Response.Status = 400;
109+
context.Response.Status = HttpStatusCode.BadRequest;
109110
context.Response.Message = "Administrator password is required for database export.";
110111
return context.Response;
111112
}
112113

113-
// Validate storage key type
114-
var validStorageKeyTypes = new[] { "StorageAccessKey", "SharedAccessKey", "ManagedIdentity" };
114+
var validStorageKeyTypes = new[] { "StorageAccessKey", "SharedAccessKey" };
115115
if (!validStorageKeyTypes.Contains(options.StorageKeyType, StringComparer.OrdinalIgnoreCase))
116116
{
117-
context.Response.Status = 400;
117+
context.Response.Status = HttpStatusCode.BadRequest;
118118
context.Response.Message = $"Invalid storage key type '{options.StorageKeyType}'. Valid values are: {string.Join(", ", validStorageKeyTypes)}";
119119
return context.Response;
120120
}
121121

122-
// Validate storage URI format
123122
if (!Uri.TryCreate(options.StorageUri, UriKind.Absolute, out _))
124123
{
125-
context.Response.Status = 400;
124+
context.Response.Status = HttpStatusCode.BadRequest;
126125
context.Response.Message = "Storage URI must be a valid absolute URI.";
127126
return context.Response;
128127
}
129128

130-
// Validate authentication type if provided
131129
if (!string.IsNullOrEmpty(options.AuthType))
132130
{
133131
var validAuthTypes = new[] { "SQL", "ADPassword", "ManagedIdentity" };
134132
if (!validAuthTypes.Contains(options.AuthType, StringComparer.OrdinalIgnoreCase))
135133
{
136-
context.Response.Status = 400;
134+
context.Response.Status = HttpStatusCode.BadRequest;
137135
context.Response.Message = $"Invalid authentication type '{options.AuthType}'. Valid values are: {string.Join(", ", validAuthTypes)}";
138136
return context.Response;
139137
}
@@ -173,11 +171,11 @@ public override async Task<CommandResponse> ExecuteAsync(CommandContext context,
173171

174172
protected override string GetErrorMessage(Exception ex) => ex switch
175173
{
176-
RequestFailedException reqEx when reqEx.Status == 404 =>
174+
RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.NotFound =>
177175
"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 =>
176+
RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.Forbidden =>
179177
$"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 =>
178+
RequestFailedException reqEx when reqEx.Status == (int)HttpStatusCode.BadRequest =>
181179
$"Invalid export parameters. Check your storage URI, credentials, and database configuration. Details: {reqEx.Message}",
182180
ArgumentException argEx =>
183181
$"Invalid argument: {argEx.Message}",

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ namespace Azure.Mcp.Tools.Sql.Commands;
4343
[JsonSerializable(typeof(SqlElasticPoolProperties))]
4444
[JsonSerializable(typeof(SqlElasticPoolPerDatabaseSettings))]
4545
[JsonSerializable(typeof(SqlFirewallRuleData))]
46+
[JsonSerializable(typeof(SqlDatabaseExportResult))]
4647
[JsonSourceGenerationOptions(
4748
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
4849
WriteIndented = true,

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ public static class SqlOptionDefinitions
212212
$"--{StorageKeyType}"
213213
)
214214
{
215-
Description = "The storage key type (StorageAccessKey, SharedAccessKey, or ManagedIdentity).",
215+
Description = "The storage key type (StorageAccessKey or SharedAccessKey).",
216216
Required = true
217217
};
218218

tools/Azure.Mcp.Tools.Sql/src/Services/SqlService.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -402,19 +402,19 @@ public async Task<SqlDatabaseExportResult> ExportDatabaseAsync(
402402
var sqlServerResource = await resourceGroupResource.Value.GetSqlServers().GetAsync(serverName);
403403
var databaseResource = await sqlServerResource.Value.GetSqlDatabases().GetAsync(databaseName);
404404

405-
// Parse storage key type
406405
if (!Enum.TryParse<ResourceManager.Sql.Models.StorageKeyType>(storageKeyType, true, out var storageKeyTypeEnum))
407406
{
408-
throw new ArgumentException($"Invalid storage key type: {storageKeyType}. Valid values are: StorageAccessKey, SharedAccessKey, ManagedIdentity");
407+
throw new ArgumentException($"Invalid storage key type: {storageKeyType}. Valid values are: StorageAccessKey, SharedAccessKey");
409408
}
410409

411410
var exportDefinition = new ResourceManager.Sql.Models.DatabaseExportDefinition(
412411
storageKeyTypeEnum,
413412
storageKey,
414413
new Uri(storageUri),
415-
adminUser)
414+
adminUser,
415+
adminPassword)
416416
{
417-
AdministratorLoginPassword = adminPassword
417+
AuthenticationType = !string.IsNullOrEmpty(authType) ? authType : "Sql"
418418
};
419419

420420
var operation = await databaseResource.Value.ExportAsync(
@@ -428,7 +428,6 @@ public async Task<SqlDatabaseExportResult> ExportDatabaseAsync(
428428
"Successfully started SQL database export. Server: {Server}, Database: {Database}, ResourceGroup: {ResourceGroup}, OperationId: {OperationId}",
429429
serverName, databaseName, resourceGroup, result?.Id?.ToString());
430430

431-
// Convert string times to DateTimeOffset if possible
432431
DateTimeOffset? queuedTime = null;
433432
DateTimeOffset? lastModifiedTime = null;
434433

tools/Azure.Mcp.Tools.Sql/src/SqlSetup.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public void ConfigureServices(IServiceCollection services)
2525
services.AddSingleton<DatabaseListCommand>();
2626
services.AddSingleton<DatabaseCreateCommand>();
2727
services.AddSingleton<DatabaseRenameCommand>();
28+
services.AddSingleton<DatabaseExportCommand>();
2829
services.AddSingleton<DatabaseUpdateCommand>();
2930
services.AddSingleton<DatabaseDeleteCommand>();
3031

tools/Azure.Mcp.Tools.Sql/tests/Azure.Mcp.Tools.Sql.UnitTests/Database/DatabaseExportCommandTests.cs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Licensed under the MIT License.
33

44
using System.CommandLine;
5+
using System.Net;
56
using Azure.Mcp.Core.Models.Command;
67
using Azure.Mcp.Core.Options;
78
using Azure.Mcp.Tools.Sql.Commands.Database;
@@ -98,11 +99,11 @@ public async Task ExecuteAsync_WithValidParameters_ReturnsSuccessResult()
9899
// Assert
99100
Assert.NotNull(response);
100101
// Debug: Add detailed error info
101-
if (response.Status != 200)
102+
if (response.Status != HttpStatusCode.OK)
102103
{
103-
throw new Exception($"Expected 200 but got {response.Status}. Message: {response.Message}");
104+
throw new Exception($"Expected OK but got {response.Status}. Message: {response.Message}");
104105
}
105-
Assert.Equal(200, response.Status);
106+
Assert.Equal(HttpStatusCode.OK, response.Status);
106107
Assert.NotNull(response.Results);
107108
Assert.Equal("Success", response.Message);
108109
}
@@ -143,7 +144,7 @@ public async Task ExecuteAsync_WithServiceException_ReturnsErrorResult()
143144
var response = await _command.ExecuteAsync(_context, args);
144145

145146
// Assert
146-
Assert.Equal(500, response.Status);
147+
Assert.Equal(HttpStatusCode.InternalServerError, response.Status);
147148
Assert.Contains("Database not found", response.Message);
148149
}
149150

@@ -167,7 +168,7 @@ public async Task ExecuteAsync_WithMissingSubscription_ReturnsValidationError()
167168
var response = await _command.ExecuteAsync(_context, args);
168169

169170
// Assert
170-
Assert.Equal(400, response.Status);
171+
Assert.Equal(HttpStatusCode.BadRequest, response.Status);
171172
Assert.Contains("subscription", response.Message, StringComparison.OrdinalIgnoreCase);
172173
}
173174

@@ -191,7 +192,7 @@ public async Task ExecuteAsync_WithMissingStorageUri_ReturnsValidationError()
191192
var response = await _command.ExecuteAsync(_context, args);
192193

193194
// Assert
194-
Assert.Equal(400, response.Status);
195+
Assert.Equal(HttpStatusCode.BadRequest, response.Status);
195196
Assert.Contains("Missing Required options: --storage-uri", response.Message);
196197
}
197198

@@ -201,7 +202,7 @@ public async Task ExecuteAsync_WithInvalidStorageUri_ReturnsValidationError()
201202
// Arrange
202203
var args = _commandDefinition.Parse([
203204
"--subscription", "test-subscription",
204-
"--resource-group", "test-rg",
205+
"--resource-group", "test-rg",
205206
"--server", "test-server",
206207
"--database", "test-db",
207208
"--storage-uri", "invalid-uri",
@@ -216,7 +217,7 @@ public async Task ExecuteAsync_WithInvalidStorageUri_ReturnsValidationError()
216217
var response = await _command.ExecuteAsync(_context, args);
217218

218219
// Assert
219-
Assert.Equal(400, response.Status);
220+
Assert.Equal(HttpStatusCode.BadRequest, response.Status);
220221
Assert.Contains("Storage URI must be a valid absolute URI", response.Message);
221222
}
222223

@@ -231,7 +232,7 @@ public async Task ExecuteAsync_WithInvalidStorageKeyType_ReturnsValidationError(
231232
"--database", "test-db",
232233
"--storage-uri", "https://storage.blob.core.windows.net/container/export.bacpac",
233234
"--storage-key", "storagekey123",
234-
"--storage-key-type", "InvalidType",
235+
"--storage-key-type", "ManagedIdentity", // This is now invalid for storage key type
235236
"--admin-user", "admin",
236237
"--admin-password", "password123",
237238
"--auth-type", "SQL"
@@ -241,8 +242,8 @@ public async Task ExecuteAsync_WithInvalidStorageKeyType_ReturnsValidationError(
241242
var response = await _command.ExecuteAsync(_context, args);
242243

243244
// Assert
244-
Assert.Equal(400, response.Status);
245-
Assert.Contains("Invalid storage key type", response.Message);
245+
Assert.Equal(HttpStatusCode.BadRequest, response.Status);
246+
Assert.Contains("Invalid storage key type 'ManagedIdentity'. Valid values are: StorageAccessKey, SharedAccessKey", response.Message);
246247
}
247248

248249
[Fact]
@@ -266,7 +267,7 @@ public async Task ExecuteAsync_WithInvalidAuthenticationType_ReturnsValidationEr
266267
var response = await _command.ExecuteAsync(_context, args);
267268

268269
// Assert
269-
Assert.Equal(400, response.Status);
270+
Assert.Equal(HttpStatusCode.BadRequest, response.Status);
270271
Assert.Contains("Invalid authentication type", response.Message);
271272
}
272273

@@ -336,7 +337,6 @@ await _sqlService.Received(1).ExportDatabaseAsync(
336337
[Theory]
337338
[InlineData("StorageAccessKey")]
338339
[InlineData("SharedAccessKey")]
339-
[InlineData("ManagedIdentity")]
340340
public async Task ExecuteAsync_WithValidStorageKeyTypes_ExecutesSuccessfully(string storageKeyType)
341341
{
342342
// Arrange
@@ -384,7 +384,7 @@ public async Task ExecuteAsync_WithValidStorageKeyTypes_ExecutesSuccessfully(str
384384
var response = await _command.ExecuteAsync(_context, args);
385385

386386
// Assert
387-
Assert.Equal(200, response.Status);
387+
Assert.Equal(HttpStatusCode.OK, response.Status);
388388
}
389389

390390
[Theory]
@@ -438,7 +438,7 @@ public async Task ExecuteAsync_WithValidAuthTypes_ExecutesSuccessfully(string au
438438
var response = await _command.ExecuteAsync(_context, args);
439439

440440
// Assert
441-
Assert.Equal(200, response.Status);
441+
Assert.Equal(HttpStatusCode.OK, response.Status);
442442
}
443443

444444
[Fact]
@@ -461,7 +461,7 @@ public async Task ExecuteAsync_WithMissingAdminUser_ReturnsValidationError()
461461
var response = await _command.ExecuteAsync(_context, args);
462462

463463
// Assert
464-
Assert.Equal(400, response.Status);
464+
Assert.Equal(HttpStatusCode.BadRequest, response.Status);
465465
Assert.Contains("Missing Required options: --admin-user", response.Message);
466466
}
467467

@@ -485,7 +485,7 @@ public async Task ExecuteAsync_WithMissingAdminPassword_ReturnsValidationError()
485485
var response = await _command.ExecuteAsync(_context, args);
486486

487487
// Assert
488-
Assert.Equal(400, response.Status);
488+
Assert.Equal(HttpStatusCode.BadRequest, response.Status);
489489
Assert.Contains("Missing Required options: --admin-password", response.Message);
490490
}
491-
}
491+
}

0 commit comments

Comments
 (0)