Skip to content

Commit 18d7f7f

Browse files
colbytimmjongio
authored andcommitted
Add Azure Function App support with list command (Azure#863)
* Add custom words for function app to cspell configuration * Scaffold and setup Function app with list command * Add test setup bicep * Add unit tests for list command * Add live tests * Add Azure Function App operations and test prompts * Add "azurewebsites" to cspell dictionary and update README with Function App management instructions * Fix formatting * Remove unnecessary comments * Update FunctionAppListCommandTests to include ResourceGroupName in expected function app list * Update codeowners * Add additional test prompt for azmcp-aks-functionapp-list in e2eTestPrompts.md * Fix typo for function app e2e tests * Add additional words to cspell configuration for content connection strings from bicep * Update FunctionAppListCommand to inherit BaseFunctionAppCommand. Override ToolMetadata instead of using the attributes on ExecuteAsync. * Update subscription parameter name. Update cacheKey to remove unnecessary cache key type. * Update changelog to include function app list command * Rename areas directory to `functionapp` * Remove duplicate * Fix casing in function app directory paths in solution file * Update .github/CODEOWNERS * Update .github/CODEOWNERS * Update .github/CODEOWNERS * Update .github/CODEOWNERS * Remove base function app options since they are not necessary and extend SubscriptionOptions for the list options. * Remove option definition as it's not necessary for the list command. * Update command group description to align with other areas. * Refactor BaseFunctionAppCommand to use SubscriptionOptions and update FunctionAppListOptions to include necessary using directive. * Update FunctionApp to be record and update references and unit tests. * Remove SubscriptionId from FunctionAppInfo and update related tests * Refactor FunctionAppInfo to include JsonPropertyName attributes for serialization * Update Azure.ResourceManager.AppService package version to 1.4.1 * Remove telemetry tag based on consolidation change in Azure#935 --------- Co-authored-by: Jon Gallant <[email protected]>
1 parent be4f69b commit 18d7f7f

26 files changed

+863
-0
lines changed

.github/CODEOWNERS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@
8282
# ServiceLabel: %area-Foundry
8383
# ServiceOwners: @jayzzh @xiangyan99
8484

85+
# PRLabel: %area-FunctionApp
86+
/areas/functionapp/ @jongio @Azure/azure-mcp
87+
88+
# ServiceLabel: %area-FunctionApp
89+
# ServiceOwners: @jongio
90+
8591
# PRLabel: %area-Grafana
8692
/areas/grafana/ @weng5e @xiangyan99 @Azure/azure-mcp
8793

.vscode/cspell.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@
209209
"azureterraformbestpractices",
210210
"azuresdk",
211211
"azuretools",
212+
"azurewebsites",
212213
"cloudarchitect",
213214
"codegen",
214215
"codeium",
@@ -223,6 +224,8 @@
223224
"codesign",
224225
"CODEOWNERS",
225226
"containerapps",
227+
"CONTENTAZUREFILECONNECTIONSTRING",
228+
"CONTENTSHARE",
226229
"contoso",
227230
"Cosell",
228231
"cvzf",
@@ -244,6 +247,8 @@
244247
"exfiltration",
245248
"filefilters",
246249
"fnames",
250+
"functionapp",
251+
"functionapps",
247252
"gethealth",
248253
"grpcio",
249254
"Gsaascend",
@@ -317,6 +322,7 @@
317322
"resourcegroups",
318323
"Runtimes",
319324
"searchdocs",
325+
"serverfarms",
320326
"servicebus",
321327
"sessionhost",
322328
"setparam",

AzureMcp.sln

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureMcp.Core.UnitTests", "
295295
EndProject
296296
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureMcp.Tests", "core\tests\AzureMcp.Tests\AzureMcp.Tests.csproj", "{527FE0F6-40AE-4E71-A483-0F0A2368F2A7}"
297297
EndProject
298+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureMcp.FunctionApp", "areas\functionapp\src\AzureMcp.FunctionApp\AzureMcp.FunctionApp.csproj", "{E6E10688-A3CD-4C33-8E13-E0E905329272}"
299+
EndProject
300+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "functionapp", "functionapp", "{3310D97C-93BE-4434-BED7-81EB639B3141}"
301+
EndProject
302+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4A85B59D-2802-46D2-B9D1-CDFE11A37945}"
303+
EndProject
304+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C1C50B06-1175-49A1-81C6-59842EEFC51B}"
305+
EndProject
306+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureMcp.FunctionApp.LiveTests", "areas\functionapp\tests\AzureMcp.FunctionApp.LiveTests\AzureMcp.FunctionApp.LiveTests.csproj", "{59A3843F-39AD-45C9-90A6-EBD40D644451}"
307+
EndProject
308+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureMcp.FunctionApp.UnitTests", "areas\functionapp\tests\AzureMcp.FunctionApp.UnitTests\AzureMcp.FunctionApp.UnitTests.csproj", "{5918EA72-9701-4223-B7BB-C64EB81B6351}"
309+
EndProject
298310
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AzureMcp.AppConfig.UnitTests", "areas\appconfig\tests\AzureMcp.AppConfig.UnitTests\AzureMcp.AppConfig.UnitTests.csproj", "{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}"
299311
EndProject
300312
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "virtualdesktop", "virtualdesktop", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}"
@@ -1159,6 +1171,42 @@ Global
11591171
{527FE0F6-40AE-4E71-A483-0F0A2368F2A7}.Release|x64.Build.0 = Release|Any CPU
11601172
{527FE0F6-40AE-4E71-A483-0F0A2368F2A7}.Release|x86.ActiveCfg = Release|Any CPU
11611173
{527FE0F6-40AE-4E71-A483-0F0A2368F2A7}.Release|x86.Build.0 = Release|Any CPU
1174+
{E6E10688-A3CD-4C33-8E13-E0E905329272}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1175+
{E6E10688-A3CD-4C33-8E13-E0E905329272}.Debug|Any CPU.Build.0 = Debug|Any CPU
1176+
{E6E10688-A3CD-4C33-8E13-E0E905329272}.Debug|x64.ActiveCfg = Debug|Any CPU
1177+
{E6E10688-A3CD-4C33-8E13-E0E905329272}.Debug|x64.Build.0 = Debug|Any CPU
1178+
{E6E10688-A3CD-4C33-8E13-E0E905329272}.Debug|x86.ActiveCfg = Debug|Any CPU
1179+
{E6E10688-A3CD-4C33-8E13-E0E905329272}.Debug|x86.Build.0 = Debug|Any CPU
1180+
{E6E10688-A3CD-4C33-8E13-E0E905329272}.Release|Any CPU.ActiveCfg = Release|Any CPU
1181+
{E6E10688-A3CD-4C33-8E13-E0E905329272}.Release|Any CPU.Build.0 = Release|Any CPU
1182+
{E6E10688-A3CD-4C33-8E13-E0E905329272}.Release|x64.ActiveCfg = Release|Any CPU
1183+
{E6E10688-A3CD-4C33-8E13-E0E905329272}.Release|x64.Build.0 = Release|Any CPU
1184+
{E6E10688-A3CD-4C33-8E13-E0E905329272}.Release|x86.ActiveCfg = Release|Any CPU
1185+
{E6E10688-A3CD-4C33-8E13-E0E905329272}.Release|x86.Build.0 = Release|Any CPU
1186+
{59A3843F-39AD-45C9-90A6-EBD40D644451}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1187+
{59A3843F-39AD-45C9-90A6-EBD40D644451}.Debug|Any CPU.Build.0 = Debug|Any CPU
1188+
{59A3843F-39AD-45C9-90A6-EBD40D644451}.Debug|x64.ActiveCfg = Debug|Any CPU
1189+
{59A3843F-39AD-45C9-90A6-EBD40D644451}.Debug|x64.Build.0 = Debug|Any CPU
1190+
{59A3843F-39AD-45C9-90A6-EBD40D644451}.Debug|x86.ActiveCfg = Debug|Any CPU
1191+
{59A3843F-39AD-45C9-90A6-EBD40D644451}.Debug|x86.Build.0 = Debug|Any CPU
1192+
{59A3843F-39AD-45C9-90A6-EBD40D644451}.Release|Any CPU.ActiveCfg = Release|Any CPU
1193+
{59A3843F-39AD-45C9-90A6-EBD40D644451}.Release|Any CPU.Build.0 = Release|Any CPU
1194+
{59A3843F-39AD-45C9-90A6-EBD40D644451}.Release|x64.ActiveCfg = Release|Any CPU
1195+
{59A3843F-39AD-45C9-90A6-EBD40D644451}.Release|x64.Build.0 = Release|Any CPU
1196+
{59A3843F-39AD-45C9-90A6-EBD40D644451}.Release|x86.ActiveCfg = Release|Any CPU
1197+
{59A3843F-39AD-45C9-90A6-EBD40D644451}.Release|x86.Build.0 = Release|Any CPU
1198+
{5918EA72-9701-4223-B7BB-C64EB81B6351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1199+
{5918EA72-9701-4223-B7BB-C64EB81B6351}.Debug|Any CPU.Build.0 = Debug|Any CPU
1200+
{5918EA72-9701-4223-B7BB-C64EB81B6351}.Debug|x64.ActiveCfg = Debug|Any CPU
1201+
{5918EA72-9701-4223-B7BB-C64EB81B6351}.Debug|x64.Build.0 = Debug|Any CPU
1202+
{5918EA72-9701-4223-B7BB-C64EB81B6351}.Debug|x86.ActiveCfg = Debug|Any CPU
1203+
{5918EA72-9701-4223-B7BB-C64EB81B6351}.Debug|x86.Build.0 = Debug|Any CPU
1204+
{5918EA72-9701-4223-B7BB-C64EB81B6351}.Release|Any CPU.ActiveCfg = Release|Any CPU
1205+
{5918EA72-9701-4223-B7BB-C64EB81B6351}.Release|Any CPU.Build.0 = Release|Any CPU
1206+
{5918EA72-9701-4223-B7BB-C64EB81B6351}.Release|x64.ActiveCfg = Release|Any CPU
1207+
{5918EA72-9701-4223-B7BB-C64EB81B6351}.Release|x64.Build.0 = Release|Any CPU
1208+
{5918EA72-9701-4223-B7BB-C64EB81B6351}.Release|x86.ActiveCfg = Release|Any CPU
1209+
{5918EA72-9701-4223-B7BB-C64EB81B6351}.Release|x86.Build.0 = Release|Any CPU
11621210
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
11631211
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
11641212
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -1356,6 +1404,12 @@ Global
13561404
{2A3CD1B4-38A3-46A1-AEDC-2C2AC47CB8F1} = {8783C0BC-EE27-8E0C-7452-5882FB8E96CA}
13571405
{1AE3FC50-8E8C-4637-AAB1-A871D5FB4535} = {8783C0BC-EE27-8E0C-7452-5882FB8E96CA}
13581406
{527FE0F6-40AE-4E71-A483-0F0A2368F2A7} = {8783C0BC-EE27-8E0C-7452-5882FB8E96CA}
1407+
{3310D97C-93BE-4434-BED7-81EB639B3141} = {87783708-79E3-AD60-C783-1D52BE7DE4BB}
1408+
{4A85B59D-2802-46D2-B9D1-CDFE11A37945} = {3310D97C-93BE-4434-BED7-81EB639B3141}
1409+
{C1C50B06-1175-49A1-81C6-59842EEFC51B} = {3310D97C-93BE-4434-BED7-81EB639B3141}
1410+
{E6E10688-A3CD-4C33-8E13-E0E905329272} = {4A85B59D-2802-46D2-B9D1-CDFE11A37945}
1411+
{59A3843F-39AD-45C9-90A6-EBD40D644451} = {C1C50B06-1175-49A1-81C6-59842EEFC51B}
1412+
{5918EA72-9701-4223-B7BB-C64EB81B6351} = {C1C50B06-1175-49A1-81C6-59842EEFC51B}
13591413
{A3ADC1CC-6020-7233-DCFA-106CA917B0CD} = {7ECA6DB2-F8EF-407B-F2FD-DEF81B86CC73}
13601414
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {87783708-79E3-AD60-C783-1D52BE7DE4BB}
13611415
{3F159DE4-1438-4821-AA38-9BC3441661F0} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ The Azure MCP Server updates automatically by default whenever a new release com
66

77
### Features Added
88

9+
- Added support for listing Azure Function Apps via the command `azmcp-functionapp-list`. [[#863](https://github.com/Azure/azure-mcp/pull/863)]
10+
911
### Breaking Changes
1012

1113
### Bugs Fixed

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<PackageVersion Include="Azure.ResourceManager" Version="1.13.2" />
1919
<PackageVersion Include="Azure.ResourceManager.ApplicationInsights" Version="1.1.0-beta.1" />
2020
<PackageVersion Include="Azure.ResourceManager.AppConfiguration" Version="1.4.1" />
21+
<PackageVersion Include="Azure.ResourceManager.AppService" Version="1.4.1" />
2122
<PackageVersion Include="Azure.ResourceManager.Authorization" Version="1.1.5" />
2223
<PackageVersion Include="Azure.ResourceManager.ContainerRegistry" Version="1.3.0" />
2324
<PackageVersion Include="Azure.ResourceManager.DesktopVirtualization" Version="1.3.1" />

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ The Azure MCP Server supercharges your agents with Azure context. Here are some
151151
* List Azure Foundry models
152152
* Deploy foundry models
153153
* List foundry model deployments
154+
155+
### ☁️ Azure Function App
156+
157+
* List Azure Function Apps
154158

155159
### 🚀 Azure Managed Grafana
156160

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Runtime.CompilerServices;
5+
6+
[assembly: InternalsVisibleTo("AzureMcp.FunctionApp.UnitTests")]
7+
[assembly: InternalsVisibleTo("AzureMcp.FunctionApp.LiveTests")]
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<IsAotCompatible>true</IsAotCompatible>
4+
</PropertyGroup>
5+
<ItemGroup>
6+
<EmbeddedResource Include="**\Resources\*.txt" />
7+
<EmbeddedResource Include="**\Resources\*.json" />
8+
</ItemGroup>
9+
<ItemGroup>
10+
<ProjectReference Include="..\..\..\..\core\src\AzureMcp.Core\AzureMcp.Core.csproj" />
11+
</ItemGroup>
12+
<ItemGroup>
13+
<PackageReference Include="Azure.ResourceManager" />
14+
<PackageReference Include="Azure.ResourceManager.AppService" />
15+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
16+
<PackageReference Include="ModelContextProtocol" />
17+
<PackageReference Include="System.CommandLine" />
18+
</ItemGroup>
19+
</Project>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using System.Diagnostics.CodeAnalysis;
5+
using AzureMcp.Core.Commands;
6+
using AzureMcp.Core.Commands.Subscription;
7+
using AzureMcp.Core.Options;
8+
9+
namespace AzureMcp.FunctionApp.Commands;
10+
11+
public abstract class BaseFunctionAppCommand<
12+
[DynamicallyAccessedMembers(TrimAnnotations.CommandAnnotations)] TOptions>
13+
: SubscriptionCommand<TOptions> where TOptions : SubscriptionOptions, new();
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
using Azure;
5+
using AzureMcp.Core.Commands;
6+
using AzureMcp.Core.Services.Telemetry;
7+
using AzureMcp.FunctionApp.Models;
8+
using AzureMcp.FunctionApp.Options;
9+
using AzureMcp.FunctionApp.Services;
10+
using Microsoft.Extensions.Logging;
11+
12+
namespace AzureMcp.FunctionApp.Commands.FunctionApp;
13+
14+
public sealed class FunctionAppListCommand(ILogger<FunctionAppListCommand> logger)
15+
: BaseFunctionAppCommand<FunctionAppListOptions>()
16+
{
17+
private const string CommandTitle = "List Azure Function Apps";
18+
private readonly ILogger<FunctionAppListCommand> _logger = logger;
19+
20+
public override string Name => "list";
21+
22+
public override string Description =>
23+
"""
24+
Lists all Azure Function Apps in a subscription.
25+
Returns a list of function app details including name, location, status, and app service plan name.
26+
""";
27+
28+
public override string Title => CommandTitle;
29+
30+
public override ToolMetadata Metadata => new() { Destructive = false, ReadOnly = true };
31+
32+
public override async Task<CommandResponse> ExecuteAsync(CommandContext context, ParseResult parseResult)
33+
{
34+
var options = BindOptions(parseResult);
35+
36+
try
37+
{
38+
if (!Validate(parseResult.CommandResult, context.Response).IsValid)
39+
return context.Response;
40+
41+
var functionAppService = context.GetService<IFunctionAppService>();
42+
var functionApps = await functionAppService.ListFunctionApps(
43+
options.Subscription!,
44+
options.Tenant,
45+
options.RetryPolicy);
46+
47+
context.Response.Results = functionApps?.Count > 0
48+
? ResponseResult.Create(
49+
new FunctionAppListCommandResult(functionApps),
50+
FunctionAppJsonContext.Default.FunctionAppListCommandResult)
51+
: null;
52+
}
53+
catch (Exception ex)
54+
{
55+
_logger.LogError(ex, "Error listing function apps. Subscription: {Subscription}, Options: {@Options}",
56+
options.Subscription, options);
57+
HandleException(context, ex);
58+
}
59+
60+
return context.Response;
61+
}
62+
63+
protected override string GetErrorMessage(Exception ex) => ex switch
64+
{
65+
RequestFailedException reqEx when reqEx.Status == 404 =>
66+
"Subscription not found. Verify the subscription ID and you have access.",
67+
RequestFailedException reqEx when reqEx.Status == 403 =>
68+
$"Authorization failed accessing the function app resources. Details: {reqEx.Message}",
69+
RequestFailedException reqEx => reqEx.Message,
70+
_ => base.GetErrorMessage(ex)
71+
};
72+
73+
protected override int GetStatusCode(Exception ex) => ex switch
74+
{
75+
RequestFailedException reqEx => reqEx.Status,
76+
_ => base.GetStatusCode(ex)
77+
};
78+
79+
internal record FunctionAppListCommandResult(List<FunctionAppInfo> Results);
80+
}

0 commit comments

Comments
 (0)