From 6ca201e54bfa015cd6fe33a8c6fba44d875772b7 Mon Sep 17 00:00:00 2001 From: itanstar Date: Fri, 12 Sep 2025 14:36:07 +0900 Subject: [PATCH 01/47] First commit, add README --- awesome-azd/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 awesome-azd/README.md diff --git a/awesome-azd/README.md b/awesome-azd/README.md new file mode 100644 index 00000000..7ec49644 --- /dev/null +++ b/awesome-azd/README.md @@ -0,0 +1 @@ +이 디렉토리는 **Awesome AZD**를 기반으로 MCP 서버를 구현하기 위한 저장소입니다. \ No newline at end of file From 61a1b08e1b879e13844613c22da6c33dce2b1296 Mon Sep 17 00:00:00 2001 From: itanstar Date: Tue, 16 Sep 2025 14:55:15 +0900 Subject: [PATCH 02/47] feat(config): add AwesomeAzdAppSettings for OpenAPI configuration --- .../Configurations/AwesomeAzdAppSettings.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Configurations/AwesomeAzdAppSettings.cs diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Configurations/AwesomeAzdAppSettings.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Configurations/AwesomeAzdAppSettings.cs new file mode 100644 index 00000000..d43b3edb --- /dev/null +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Configurations/AwesomeAzdAppSettings.cs @@ -0,0 +1,19 @@ +using McpSamples.Shared.Configurations; + +using Microsoft.OpenApi.Models; + +namespace McpSamples.AwesomeAzd.HybridApp.Configurations; + +/// +/// This represents the application settings for awesome-azd app. +/// +public class AwesomeAzdAppSettings : AppSettings +{ + /// + public override OpenApiInfo OpenApi { get; set; } = new() + { + Title = "MCP Awesome Azd", + Version = "1.0.0", + Description = "A simple MCP server for searching and loading custom instructions from the awesome-azd repository." + }; +} From 53287a656f90ec1f003fa8a3df69400f82a58612 Mon Sep 17 00:00:00 2001 From: itanstar Date: Fri, 10 Oct 2025 17:12:50 +0900 Subject: [PATCH 03/47] feat(awesome-azd): add AwesomeAzdTemplate model --- .../Models/TemplateMedatadaModel.cs | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs new file mode 100644 index 00000000..d31a9628 --- /dev/null +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs @@ -0,0 +1,37 @@ +namespace McpSamples.AwesomeAzd.HybridApp.Models; + +/// +/// This represents a template from the awesome-azd API. +/// +public class AwesomeAzdTemplate +{ + /// + /// Gets or sets the unique identifier for the awesome-azd template. + /// + public string Id { get; set; } = string.Empty; + + /// + /// Gets or sets the title for the awesome-azd template. + /// + public string Title { get; set; } = string.Empty; + + /// + /// Gets or sets the description for the awesome-azd template. + /// + public string Description { get; set; } = string.Empty; + + /// + /// Gets or sets the author of the awesome-azd template. + /// + public string Author { get; set; } = string.Empty; + + /// + /// Gets or sets the source URL of the awesome-azd template. + /// + public string Source { get; set; } = string.Empty; + + /// + /// Gets or sets the tags associated with the awesome-azd template. + /// + public List Tags { get; set; } = new(); +} From 049edecd352df0ac817503282cb00a61469ef340 Mon Sep 17 00:00:00 2001 From: itanstar Date: Fri, 10 Oct 2025 17:33:07 +0900 Subject: [PATCH 04/47] feat(awesome-azd): add missing keys to AwesomeAzdTemplate model and Change the file name --- .../Models/AwesomeAzdTemplateModel.cs | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs new file mode 100644 index 00000000..9dd8300e --- /dev/null +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs @@ -0,0 +1,57 @@ +namespace McpSamples.AwesomeAzd.HybridApp.Models; + +/// +/// This represents a template from the awesome-azd API. +/// +public class AwesomeAzdTemplateModel +{ + /// + /// Gets or sets the unique identifier for the awesome-azd template. + /// + public string Id { get; set; } = string.Empty; + + /// + /// Gets or sets the title for the awesome-azd template. + /// + public string Title { get; set; } = string.Empty; + + /// + /// Gets or sets the description for the awesome-azd template. + /// + public string Description { get; set; } = string.Empty; + + /// + /// Gets or sets the preview image path for the awesome-azd template. + /// + public string Preview { get; set; } = string.Empty; + + /// + /// Gets or sets the author URL (e.g., GitHub profile or team page). + /// + public string AuthorUrl { get; set; } = string.Empty; + + /// + /// Gets or sets the author name of the awesome-azd template. + /// + public string Author { get; set; } = string.Empty; + + /// + /// Gets or sets the source repository URL of the awesome-azd template. + /// + public string Source { get; set; } = string.Empty; + + /// + /// Gets or sets the tags that describe the awesome-azd template. + /// + public List Tags { get; set; } = new(); + + /// + /// Gets or sets the list of Azure services used by the awesome-azd template. + /// + public List AzureServices { get; set; } = new(); + + /// + /// Gets or sets the list of main programming languages used by the awesome-azd template. + /// + public List Languages { get; set; } = new(); +} From fab78b2f85dbf59b71bee093341d4d7ef5103ab2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=90=EC=9D=98=EC=A7=84?= <126405314+itanstar@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:33:53 +0900 Subject: [PATCH 05/47] Delete awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs --- .../Models/TemplateMedatadaModel.cs | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs deleted file mode 100644 index d31a9628..00000000 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace McpSamples.AwesomeAzd.HybridApp.Models; - -/// -/// This represents a template from the awesome-azd API. -/// -public class AwesomeAzdTemplate -{ - /// - /// Gets or sets the unique identifier for the awesome-azd template. - /// - public string Id { get; set; } = string.Empty; - - /// - /// Gets or sets the title for the awesome-azd template. - /// - public string Title { get; set; } = string.Empty; - - /// - /// Gets or sets the description for the awesome-azd template. - /// - public string Description { get; set; } = string.Empty; - - /// - /// Gets or sets the author of the awesome-azd template. - /// - public string Author { get; set; } = string.Empty; - - /// - /// Gets or sets the source URL of the awesome-azd template. - /// - public string Source { get; set; } = string.Empty; - - /// - /// Gets or sets the tags associated with the awesome-azd template. - /// - public List Tags { get; set; } = new(); -} From 3bda37628ff1fb7dd0d2931968b557dcaae38e32 Mon Sep 17 00:00:00 2001 From: itanstar Date: Fri, 31 Oct 2025 14:23:13 +0900 Subject: [PATCH 06/47] feat(awesome-azd): add AwesomeAzdTemplateModel stub for build --- .../Models/AwesomeAzdTemplateModel.cs | 50 +------------------ .../Models/TemplateMedatadaModel.cs | 37 -------------- 2 files changed, 1 insertion(+), 86 deletions(-) delete mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs index 9dd8300e..a1e88024 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs @@ -5,53 +5,5 @@ namespace McpSamples.AwesomeAzd.HybridApp.Models; /// public class AwesomeAzdTemplateModel { - /// - /// Gets or sets the unique identifier for the awesome-azd template. - /// - public string Id { get; set; } = string.Empty; - - /// - /// Gets or sets the title for the awesome-azd template. - /// - public string Title { get; set; } = string.Empty; - - /// - /// Gets or sets the description for the awesome-azd template. - /// - public string Description { get; set; } = string.Empty; - - /// - /// Gets or sets the preview image path for the awesome-azd template. - /// - public string Preview { get; set; } = string.Empty; - - /// - /// Gets or sets the author URL (e.g., GitHub profile or team page). - /// - public string AuthorUrl { get; set; } = string.Empty; - - /// - /// Gets or sets the author name of the awesome-azd template. - /// - public string Author { get; set; } = string.Empty; - - /// - /// Gets or sets the source repository URL of the awesome-azd template. - /// - public string Source { get; set; } = string.Empty; - - /// - /// Gets or sets the tags that describe the awesome-azd template. - /// - public List Tags { get; set; } = new(); - - /// - /// Gets or sets the list of Azure services used by the awesome-azd template. - /// - public List AzureServices { get; set; } = new(); - - /// - /// Gets or sets the list of main programming languages used by the awesome-azd template. - /// - public List Languages { get; set; } = new(); + } diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs deleted file mode 100644 index d31a9628..00000000 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace McpSamples.AwesomeAzd.HybridApp.Models; - -/// -/// This represents a template from the awesome-azd API. -/// -public class AwesomeAzdTemplate -{ - /// - /// Gets or sets the unique identifier for the awesome-azd template. - /// - public string Id { get; set; } = string.Empty; - - /// - /// Gets or sets the title for the awesome-azd template. - /// - public string Title { get; set; } = string.Empty; - - /// - /// Gets or sets the description for the awesome-azd template. - /// - public string Description { get; set; } = string.Empty; - - /// - /// Gets or sets the author of the awesome-azd template. - /// - public string Author { get; set; } = string.Empty; - - /// - /// Gets or sets the source URL of the awesome-azd template. - /// - public string Source { get; set; } = string.Empty; - - /// - /// Gets or sets the tags associated with the awesome-azd template. - /// - public List Tags { get; set; } = new(); -} From 06829c0f61a0904c870cd79f60c7179a22aedebe Mon Sep 17 00:00:00 2001 From: itanstar Date: Fri, 31 Oct 2025 14:24:33 +0900 Subject: [PATCH 07/47] feat(awesome-azd): add AwesomeAzdService stub for build --- .../Services/AwesomeAzdService.cs | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs new file mode 100644 index 00000000..6944f166 --- /dev/null +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs @@ -0,0 +1,6 @@ +namespace McpSamples.AwesomeAzd.HybridApp.Services; + +public class TemplateService () +{ + +} \ No newline at end of file From 95d9d99b1aa48deaea22234baa7c1f1915e1253a Mon Sep 17 00:00:00 2001 From: itanstar Date: Fri, 31 Oct 2025 14:25:01 +0900 Subject: [PATCH 08/47] feat(awesome-azd): add AwesomeAzdTool stub for build --- .../McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs new file mode 100644 index 00000000..3f921075 --- /dev/null +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs @@ -0,0 +1,6 @@ +namespace McpSamples.AwesomeAzd.HybridApp.Tools; + +public class TemplateTool() +{ + +} From 82e98d9357c0a131e01c714255713f88c0715f9e Mon Sep 17 00:00:00 2001 From: itanstar Date: Fri, 31 Oct 2025 14:25:42 +0900 Subject: [PATCH 09/47] feat(awesome-azd): add Program.cs stub for build --- .../McpSamples.AwesomeAzd.HybridApp/Program.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Program.cs diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Program.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Program.cs new file mode 100644 index 00000000..ed107475 --- /dev/null +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Program.cs @@ -0,0 +1,18 @@ +using McpSamples.AwesomeAzd.HybridApp.Services; +using McpSamples.AwesomeAzd.HybridApp.Configurations; +using McpSamples.AwesomeAzd.HybridApp.Tools; +using McpSamples.Shared.Configurations; +using McpSamples.Shared.Extensions; +using McpSamples.Shared.OpenApi; + +var useStreamableHttp = AppSettings.UseStreamableHttp(Environment.GetEnvironmentVariables(), args); + +IHostApplicationBuilder builder = useStreamableHttp + ? WebApplication.CreateBuilder(args) + : Host.CreateApplicationBuilder(args); + +builder.Services.AddAppSettings(builder.Configuration, args); + +IHost app = builder.BuildApp(useStreamableHttp); + +await app.RunAsync(); From 125ff49ab61abb5147afd77db9acd5d227b2fa94 Mon Sep 17 00:00:00 2001 From: itanstar Date: Fri, 31 Oct 2025 14:28:00 +0900 Subject: [PATCH 10/47] feat(awesome-azd): Add McpSamples.AwesomeAzd.HybridApp.csproj --- .../McpSamples.AwesomeAzd.HybridApp.csproj | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/McpSamples.AwesomeAzd.HybridApp.csproj diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/McpSamples.AwesomeAzd.HybridApp.csproj b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/McpSamples.AwesomeAzd.HybridApp.csproj new file mode 100644 index 00000000..97267b2d --- /dev/null +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/McpSamples.AwesomeAzd.HybridApp.csproj @@ -0,0 +1,21 @@ + + + + net9.0 + latest + + enable + enable + + McpSamples.AwesomeAzd.HybridApp + McpSamples.AwesomeAzd.HybridApp + + e01e0699-c7a8-4900-be2b-6c4e27440ed7 + + + + + + + + From def116214de3845000b7e91e2ae90c9299f1de73 Mon Sep 17 00:00:00 2001 From: itanstar Date: Fri, 31 Oct 2025 14:29:38 +0900 Subject: [PATCH 11/47] feat(awesome-azd): Add launchSettings.json --- .../Properties/launchSettings.json | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Properties/launchSettings.json diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Properties/launchSettings.json b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Properties/launchSettings.json new file mode 100644 index 00000000..d46036a3 --- /dev/null +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5201", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} From 753656dbe6f429c7cfd8f7d3f94c03c83ebd83f6 Mon Sep 17 00:00:00 2001 From: itanstar Date: Fri, 31 Oct 2025 14:30:57 +0900 Subject: [PATCH 12/47] feat(awesome-azd): Add appsettings.json and appsettings.Development.json --- .../appsettings.Development.json | 8 ++++++++ .../src/McpSamples.AwesomeAzd.HybridApp/appsettings.json | 9 +++++++++ 2 files changed, 17 insertions(+) create mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/appsettings.Development.json create mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/appsettings.json diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/appsettings.Development.json b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/appsettings.json b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} From cde82f5576b34aa704663c39da07d904e4471cda Mon Sep 17 00:00:00 2001 From: itanstar Date: Sat, 1 Nov 2025 13:15:33 +0900 Subject: [PATCH 13/47] feat(awesome-azd): Add IAwesomeAzdService --- .../Services/IAwesomeAzdService.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs new file mode 100644 index 00000000..b60693b1 --- /dev/null +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs @@ -0,0 +1,32 @@ +using McpSamples.AwesomeAzd.HybridApp.Models; + +namespace McpSamples.AwesomeAzd.HybridApp.Services +{ + /// + /// Provides interfaces for metadata service operations for Awesome AZD templates. + /// + public interface IAwesomeAzdService + { + /// + /// Searches for relevant templates in the Awesome AZD repository based on keywords. + /// + /// The keywords to search for. + /// Cancellation token for the async operation. + /// A containing all matching search results. + Task> GetTemplateListAsync(string keywords, CancellationToken cancellationToken = default); + + /// + /// Retrieves template details from the cached template list obtained from . + /// + /// The template ID to retrieve. + /// The corresponding to the specified ID, or null if not found. + Task GetTemplateDetailByIdAsync(string id); + + /// + /// Generates the azd init command for a specific template. + /// + /// The template ID for which to generate the command. + /// A string containing the azd init command for the template. + Task GetTemplateInitCommandAsync(string id); + } +} From 6ab86bcfbb34182d025444311a20701222dea06f Mon Sep 17 00:00:00 2001 From: itanstar Date: Sat, 1 Nov 2025 14:42:41 +0900 Subject: [PATCH 14/47] docs(awesome-azd): add comments explaining JSON key name differences in AwesomeAzdTemplateModel --- .../Models/AwesomeAzdTemplateModel.cs | 3 ++ .../Models/TemplateMedatadaModel.cs | 37 ------------------- 2 files changed, 3 insertions(+), 37 deletions(-) delete mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs index 9dd8300e..590ae717 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs @@ -27,6 +27,7 @@ public class AwesomeAzdTemplateModel /// /// Gets or sets the author URL (e.g., GitHub profile or team page). + /// The keys "AuthorUrl" and "website" have the same meaning but are spelled differently in the JSON file. /// public string AuthorUrl { get; set; } = string.Empty; @@ -47,11 +48,13 @@ public class AwesomeAzdTemplateModel /// /// Gets or sets the list of Azure services used by the awesome-azd template. + /// The keys "azure_service" and "AzureService" have the same meaning but are spelled differently in the JSON file. /// public List AzureServices { get; set; } = new(); /// /// Gets or sets the list of main programming languages used by the awesome-azd template. + /// The keys "language" and "languages" have the same meaning but are spelled differently in the JSON file. /// public List Languages { get; set; } = new(); } diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs deleted file mode 100644 index d31a9628..00000000 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/TemplateMedatadaModel.cs +++ /dev/null @@ -1,37 +0,0 @@ -namespace McpSamples.AwesomeAzd.HybridApp.Models; - -/// -/// This represents a template from the awesome-azd API. -/// -public class AwesomeAzdTemplate -{ - /// - /// Gets or sets the unique identifier for the awesome-azd template. - /// - public string Id { get; set; } = string.Empty; - - /// - /// Gets or sets the title for the awesome-azd template. - /// - public string Title { get; set; } = string.Empty; - - /// - /// Gets or sets the description for the awesome-azd template. - /// - public string Description { get; set; } = string.Empty; - - /// - /// Gets or sets the author of the awesome-azd template. - /// - public string Author { get; set; } = string.Empty; - - /// - /// Gets or sets the source URL of the awesome-azd template. - /// - public string Source { get; set; } = string.Empty; - - /// - /// Gets or sets the tags associated with the awesome-azd template. - /// - public List Tags { get; set; } = new(); -} From 66db4277bb6c8e1363ef8f0b6b937a3f93b16598 Mon Sep 17 00:00:00 2001 From: itanstar Date: Mon, 3 Nov 2025 16:51:56 +0900 Subject: [PATCH 15/47] feat(awesome-azd): Implement core service logic --- .../Services/AwesomeAzdService.cs | 89 ++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs index 6944f166..80c2261f 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs @@ -1,6 +1,91 @@ namespace McpSamples.AwesomeAzd.HybridApp.Services; -public class TemplateService () +using System.Text.Json; +using McpSamples.AwesomeAzd.HybridApp.Models; + +public class AwesomeAzdService(HttpClient http, ILogger logger) : IAwesomeAzdService { - + private const string AwesomeAzdTemplateFileUrl = "https://raw.githubusercontent.com/Azure/awesome-azd/main/website/static/templates.json"; + + private List? _cachedTemplates; + + public async Task> GetTemplateListAsync(string keywords, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(keywords)) + { + return new List(); + } + + var templates = await GetTemplatesAsync(cancellationToken).ConfigureAwait(false); + + var searchTerms = keywords.Split(' ', StringSplitOptions.RemoveEmptyEntries) + .Select(term => term.Trim().ToLowerInvariant()) + .Where(term => string.IsNullOrWhiteSpace(term) != true) + .ToArray(); + + logger.LogInformation("Search terms: {terms}", string.Join(", ", searchTerms)); + + var result = templates + .Where(t => ContainsAnyKeyword(t.Title, searchTerms) + || ContainsAnyKeyword(t.Description, searchTerms) + || ContainsAnyKeyword(t.Author, searchTerms) + || ContainsAnyKeyword(t.Source, searchTerms) + || (t.Tags?.Any(tag => ContainsAnyKeyword(tag, searchTerms)) ?? false) + || (t.Languages?.Any(lang => ContainsAnyKeyword(lang, searchTerms)) ?? false) + || (t.AzureServices?.Any(svc => ContainsAnyKeyword(svc, searchTerms)) ?? false)) + .ToList(); + + _cachedTemplates = result; + return result; + } + + private async Task> GetTemplatesAsync(CancellationToken cancellationToken) + { + if (_cachedTemplates != null && _cachedTemplates.Any()) + { + return _cachedTemplates; + } + + try + { + logger.LogInformation("Fetching templates from {url}", AwesomeAzdTemplateFileUrl); + + var response = await http.GetAsync(AwesomeAzdTemplateFileUrl, cancellationToken); + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(cancellationToken); + _cachedTemplates = JsonSerializer.Deserialize>(json, + new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) + ?? new List(); + + logger.LogInformation("Loaded {count} templates.", _cachedTemplates.Count); + return _cachedTemplates; + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to fetch or deserialize templates."); + return new List(); + } + } + + private static bool ContainsAnyKeyword(string? text, string[] searchTerms) + { + if (string.IsNullOrWhiteSpace(text)) + { + return false; + } + + return searchTerms.Any(term => text.Contains(term, StringComparison.InvariantCultureIgnoreCase)); + } + + public Task GetTemplateDetailByIdAsync(string id) + { + throw new NotImplementedException(); + } + + public Task GetTemplateInitCommandAsync(string id) + { + throw new NotImplementedException(); + } + } \ No newline at end of file From a857054b4e279e1b549aa96ef23efc77e6ac9446 Mon Sep 17 00:00:00 2001 From: itanstar Date: Mon, 3 Nov 2025 16:55:27 +0900 Subject: [PATCH 16/47] feat(awesome-azd): Add GetTemplateListAsync tool to MCP server for template retrieval --- .../Tools/AwesomeAzdTool.cs | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs index 3f921075..38add70d 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs @@ -1,6 +1,51 @@ +using System.ComponentModel; + +using McpSamples.AwesomeAzd.HybridApp.Services; + +using ModelContextProtocol.Server; + namespace McpSamples.AwesomeAzd.HybridApp.Tools; -public class TemplateTool() +/// +/// Provides MCP tool operations for Azure Developer templates. +/// +public interface IAwesomeAzdTool { - + /// + /// Searches available Azure Developer templates by keyword. + /// + /// The keyword to search templates for + /// A list of matching template titles. + Task> GetTemplateListAsync(string keywords); +} + +/// +/// This represents the tools entity for Awesome Azd template operations. +/// +[McpServerToolType] +public class AwesomeAzdTool(IAwesomeAzdService service, ILogger logger) : IAwesomeAzdTool +{ + /// + [McpServerTool(Name = "get_templates", Title = "Search Azure Developer templates")] + [Description("Searches available Azure Developer templates by keyword.")] + public async Task> GetTemplateListAsync( + [Description("The keyword to search templates for")] string keywords) + { + var result = new List(); + + try + { + var templates = await service.GetTemplateListAsync(keywords).ConfigureAwait(false); + result = templates.Select(t => t.Title).ToList(); + + logger.LogInformation("Template search completed successfully for keyword '{Keywords}'.", keywords); + } + catch (Exception ex) + { + logger.LogError(ex, "Error occurred while searching templates with keyword '{Keywords}'.", keywords); + result.Add($"Error: {ex.Message}"); + } + + return result; + } } From 51368f25c6730144cb0f5bba63bb69396a06f7be Mon Sep 17 00:00:00 2001 From: itanstar Date: Mon, 3 Nov 2025 20:10:28 +0900 Subject: [PATCH 17/47] feat(awesome-azd):Add JSON configs for local stdio and local HTTP execution --- awesome-azd/.vscode/mcp.http.local.json | 8 ++++++++ awesome-azd/.vscode/mcp.stdio.local.json | 20 ++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 awesome-azd/.vscode/mcp.http.local.json create mode 100644 awesome-azd/.vscode/mcp.stdio.local.json diff --git a/awesome-azd/.vscode/mcp.http.local.json b/awesome-azd/.vscode/mcp.http.local.json new file mode 100644 index 00000000..27e5753c --- /dev/null +++ b/awesome-azd/.vscode/mcp.http.local.json @@ -0,0 +1,8 @@ +{ + "servers": { + "awesome-azd": { + "type": "http", + "url": "http://0.0.0.0:5201/mcp" + } + } +} diff --git a/awesome-azd/.vscode/mcp.stdio.local.json b/awesome-azd/.vscode/mcp.stdio.local.json new file mode 100644 index 00000000..3a1a7c26 --- /dev/null +++ b/awesome-azd/.vscode/mcp.stdio.local.json @@ -0,0 +1,20 @@ +{ + "inputs": [ + { + "type": "promptString", + "id": "consoleapp-project-path", + "description": "The absolute path to the console app project Directory" + } + ], + "servers": { + "awesome-azd": { + "type": "stdio", + "command": "dotnet", + "args": [ + "run", + "--project", + "${input:consoleapp-project-path}" + ] + } + } +} From 9eebc2f76b28133c32a3533a31b546985a4addbb Mon Sep 17 00:00:00 2001 From: itanstar Date: Tue, 4 Nov 2025 22:24:47 +0900 Subject: [PATCH 18/47] fix(awesome-azd): Change Prompt file name to TemplatePrompt.cs --- .../Prompts/TemplatePrompt.cs | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs new file mode 100644 index 00000000..d407b4c9 --- /dev/null +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs @@ -0,0 +1,80 @@ +using System.ComponentModel; +using ModelContextProtocol.Server; + +namespace McpSamples.AwesomeAzd.HybridApp.Prompts; + +/// +/// Provides an interface for generating prompts that guide template searches. +/// +public interface ITemplatePrompt +{ + /// + /// Gets a prompt for searching Azure templates by keyword. + /// + /// The keyword to search for. + /// A formatted search prompt. + string GetSearchPrompt(string keyword); +} + +/// +/// Represents the prompt entity for the Awesome AZD templates repository. +/// +[McpServerPromptType] +public class TemplatePrompt : ITemplatePrompt +{ + /// + [McpServerPrompt(Name = "get_template_search_prompt", Title = "Prompt for searching AZD templates")] + [Description("Get a prompt for searching Azure templates by keyword.")] + public string GetSearchPrompt( + [Description("The keyword to search for")] string keyword) + { + return $""" + Please search all Azure templates that are related to the search keyword `{keyword}`. + + Here's the process to follow: + + 1. Use the `awesome-azd` MCP server. + + 1. Search all templates in the **Awesome AZD repository** for the given keyword with get_templates tool. + + 1. Return a structured response in a **table format** that includes: + - Title + - Description + + 1. Example table format: + + | Title | Description | + |------------------|--------------------------------| + | Starter - Bicep | A starter template with Bicep | + + 1. Once a template is selected, **return all available details** about the selected template, including: + - `title` + - `description` + - `preview` + - `author` + - `authorUrl` + - `source` + - `tags` + - `azureServices` + - `languages` + - `id` + + 1. If the user wants to execute this template, provide a command guide using the following rule: + - Use the syntax: + `` + azd init -t + ``` + - `` is determined as follows: + - If the GitHub source URL starts with + `https://github.com/Azure-Samples/...`, + then use the organization/repository name: + ``` + e.g. azd init -t Azure-Samples/azure-openai-chat-frontend + ``` + - Otherwise, include the full `owner/repo` path: + ``` + e.g. azd init -t pascalvanderheiden/ais-apim-openai + ``` + """; + } +} \ No newline at end of file From 52c17265519a559230da2d6981fdf847400a4ea7 Mon Sep 17 00:00:00 2001 From: itanstar Date: Sat, 8 Nov 2025 12:51:45 +0900 Subject: [PATCH 19/47] fix(awesome-azd): delete command-making Task --- .../Services/AwesomeAzdService.cs | 5 ----- .../Services/IAwesomeAzdService.cs | 6 ------ 2 files changed, 11 deletions(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs index 80c2261f..2059d109 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs @@ -83,9 +83,4 @@ private static bool ContainsAnyKeyword(string? text, string[] searchTerms) throw new NotImplementedException(); } - public Task GetTemplateInitCommandAsync(string id) - { - throw new NotImplementedException(); - } - } \ No newline at end of file diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs index b60693b1..070d3bed 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs @@ -22,11 +22,5 @@ public interface IAwesomeAzdService /// The corresponding to the specified ID, or null if not found. Task GetTemplateDetailByIdAsync(string id); - /// - /// Generates the azd init command for a specific template. - /// - /// The template ID for which to generate the command. - /// A string containing the azd init command for the template. - Task GetTemplateInitCommandAsync(string id); } } From 6e87ac2f64d984aebcfb3dc7298a5064c3f69c44 Mon Sep 17 00:00:00 2001 From: itanstar Date: Mon, 10 Nov 2025 19:07:10 +0900 Subject: [PATCH 20/47] fix(awesome-azd): Rename GetTemplateDetailByIdAsync to GetTemplateDetailByTitleAsync and update parameter from id to title --- .../Services/AwesomeAzdService.cs | 3 ++- .../Services/IAwesomeAzdService.cs | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs index 2059d109..9b3e6949 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs @@ -58,6 +58,7 @@ private async Task> GetTemplatesAsync(Cancellation new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new List(); + logger.LogInformation("Loaded {count} templates.", _cachedTemplates.Count); return _cachedTemplates; } @@ -78,7 +79,7 @@ private static bool ContainsAnyKeyword(string? text, string[] searchTerms) return searchTerms.Any(term => text.Contains(term, StringComparison.InvariantCultureIgnoreCase)); } - public Task GetTemplateDetailByIdAsync(string id) + public async Task GetTemplateDetailByTitleAsync(string title, CancellationToken cancellationToken = default) { throw new NotImplementedException(); } diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs index 070d3bed..2dd63760 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs @@ -18,9 +18,10 @@ public interface IAwesomeAzdService /// /// Retrieves template details from the cached template list obtained from . /// - /// The template ID to retrieve. - /// The corresponding to the specified ID, or null if not found. - Task GetTemplateDetailByIdAsync(string id); + /// The template Title to retrieve. + /// Cancellation token for the async operation. + /// The corresponding to the specified Title, or error Model if not found. + Task GetTemplateDetailByTitleAsync(string title, CancellationToken cancellationToken = default); } } From dc55b1867a3ebd255aca759dc042e8335822f9d7 Mon Sep 17 00:00:00 2001 From: itanstar Date: Mon, 10 Nov 2025 19:26:35 +0900 Subject: [PATCH 21/47] fix(awesome-azd): Change AwesomeAzdTool to return AwesomeAzdTemplateModel instead of string --- .../Tools/AwesomeAzdTool.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs index 38add70d..296ecd47 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using McpSamples.AwesomeAzd.HybridApp.Services; +using McpSamples.AwesomeAzd.HybridApp.Models; using ModelContextProtocol.Server; @@ -16,7 +17,7 @@ public interface IAwesomeAzdTool /// /// The keyword to search templates for /// A list of matching template titles. - Task> GetTemplateListAsync(string keywords); + Task> GetTemplateListAsync(string keywords); } /// @@ -28,22 +29,26 @@ public class AwesomeAzdTool(IAwesomeAzdService service, ILogger /// [McpServerTool(Name = "get_templates", Title = "Search Azure Developer templates")] [Description("Searches available Azure Developer templates by keyword.")] - public async Task> GetTemplateListAsync( + public async Task> GetTemplateListAsync( [Description("The keyword to search templates for")] string keywords) { - var result = new List(); + var result = new List(); try { var templates = await service.GetTemplateListAsync(keywords).ConfigureAwait(false); - result = templates.Select(t => t.Title).ToList(); + result = templates.ToList(); logger.LogInformation("Template search completed successfully for keyword '{Keywords}'.", keywords); } catch (Exception ex) { logger.LogError(ex, "Error occurred while searching templates with keyword '{Keywords}'.", keywords); - result.Add($"Error: {ex.Message}"); + result.Add(new AwesomeAzdTemplateModel + { + Title = "Error", + Description = ex.Message + }); } return result; From 91266e265cb30ebcc300c74191d5a942f9f2e71c Mon Sep 17 00:00:00 2001 From: itanstar Date: Tue, 11 Nov 2025 23:33:59 +0900 Subject: [PATCH 22/47] fix(awesome-azd): Remove unused caching logic --- .../Services/AwesomeAzdService.cs | 18 ++++-------------- .../Services/IAwesomeAzdService.cs | 8 -------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs index 9b3e6949..02e16ce7 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs @@ -7,8 +7,6 @@ public class AwesomeAzdService(HttpClient http, ILogger logge { private const string AwesomeAzdTemplateFileUrl = "https://raw.githubusercontent.com/Azure/awesome-azd/main/website/static/templates.json"; - private List? _cachedTemplates; - public async Task> GetTemplateListAsync(string keywords, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(keywords)) @@ -35,16 +33,11 @@ public async Task> GetTemplateListAsync(string key || (t.AzureServices?.Any(svc => ContainsAnyKeyword(svc, searchTerms)) ?? false)) .ToList(); - _cachedTemplates = result; return result; } private async Task> GetTemplatesAsync(CancellationToken cancellationToken) { - if (_cachedTemplates != null && _cachedTemplates.Any()) - { - return _cachedTemplates; - } try { @@ -54,13 +47,14 @@ private async Task> GetTemplatesAsync(Cancellation response.EnsureSuccessStatusCode(); var json = await response.Content.ReadAsStringAsync(cancellationToken); - _cachedTemplates = JsonSerializer.Deserialize>(json, + + var result = JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new List(); + logger.LogInformation("Loaded {count} templates.", result.Count); - logger.LogInformation("Loaded {count} templates.", _cachedTemplates.Count); - return _cachedTemplates; + return result; } catch (Exception ex) { @@ -79,9 +73,5 @@ private static bool ContainsAnyKeyword(string? text, string[] searchTerms) return searchTerms.Any(term => text.Contains(term, StringComparison.InvariantCultureIgnoreCase)); } - public async Task GetTemplateDetailByTitleAsync(string title, CancellationToken cancellationToken = default) - { - throw new NotImplementedException(); - } } \ No newline at end of file diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs index 2dd63760..9bff84ac 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs @@ -15,13 +15,5 @@ public interface IAwesomeAzdService /// A containing all matching search results. Task> GetTemplateListAsync(string keywords, CancellationToken cancellationToken = default); - /// - /// Retrieves template details from the cached template list obtained from . - /// - /// The template Title to retrieve. - /// Cancellation token for the async operation. - /// The corresponding to the specified Title, or error Model if not found. - Task GetTemplateDetailByTitleAsync(string title, CancellationToken cancellationToken = default); - } } From 44264b11052fad1989b8669f775318b037e64eee Mon Sep 17 00:00:00 2001 From: itanstar Date: Tue, 11 Nov 2025 23:35:15 +0900 Subject: [PATCH 23/47] fix(awesome-azd): Add HttpClient registration for AwesomeAzdService --- awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Program.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Program.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Program.cs index ed107475..e7bcb43d 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Program.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Program.cs @@ -13,6 +13,8 @@ builder.Services.AddAppSettings(builder.Configuration, args); +builder.Services.AddHttpClient(); + IHost app = builder.BuildApp(useStreamableHttp); await app.RunAsync(); From 1441e2ad59b8d1be81e70b7aec6e483afd504fbe Mon Sep 17 00:00:00 2001 From: itanstar Date: Sun, 16 Nov 2025 17:49:02 +0900 Subject: [PATCH 24/47] feat(awesome-azd): Add basic command execution feature --- .../Models/AwesomeAzdTemplateModel.cs | 8 ++ .../Prompts/TemplatePrompt.cs | 86 +++++++++---------- .../Services/AwesomeAzdService.cs | 77 +++++++++++++++++ .../Services/IAwesomeAzdService.cs | 3 +- .../Tools/AwesomeAzdTool.cs | 31 +++++++ 5 files changed, 161 insertions(+), 44 deletions(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs index 590ae717..37a88312 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs @@ -58,3 +58,11 @@ public class AwesomeAzdTemplateModel /// public List Languages { get; set; } = new(); } + + +public class CommandExecutionResult +{ + public bool Success { get; set; } + public string Output { get; set; } = ""; + public string Error { get; set; } = ""; +} \ No newline at end of file diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs index d407b4c9..c743bffd 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs @@ -26,55 +26,55 @@ public class TemplatePrompt : ITemplatePrompt [McpServerPrompt(Name = "get_template_search_prompt", Title = "Prompt for searching AZD templates")] [Description("Get a prompt for searching Azure templates by keyword.")] public string GetSearchPrompt( - [Description("The keyword to search for")] string keyword) + [Description("The keyword to search for")] string keyword) { return $""" - Please search all Azure templates that are related to the search keyword `{keyword}`. + Please search all Azure templates related to the keyword `{keyword}`. - Here's the process to follow: + Follow this process: 1. Use the `awesome-azd` MCP server. + 2. Search all templates in the **Awesome AZD repository** using the `get_templates` tool. + 3. Return the results in the following **table format**: + + | Title | Description | Source | + |-------|-------------|--------| + | ... | ... | ... | + + 4. After showing the table, ask the user to **select one template by its Title**. + + 5. Once the user selects a Title: + - Retrieve the selected template's `source` value. + - Convert the `source` URL into the proper `` for AZD: + + Rules: + - If the URL starts with: + `https://github.com/Azure-Samples/...` + → Use `Azure-Samples/`. + + - Otherwise, extract the normal `owner/repo`. + + - Build the AZD command: + ``` + azd init -t --non-interactive --no-prompt + ``` + + **IMPORTANT** + MCP executes commands in a non-TTY environment. + Without `--non-interactive` and `--no-prompt`, `azd init` will wait for input and **never finish**. + So always include those flags. + + 6. **Immediately execute this command using the MCP tool `ExecuteTemplateCommandAsync`:** + + ``` + ExecuteTemplateCommandAsync("", "") + ``` + + - The working directory is where the template project should be initialized. + + 7. Return the execution result (Success, Output, Error) back to the user. - 1. Search all templates in the **Awesome AZD repository** for the given keyword with get_templates tool. - - 1. Return a structured response in a **table format** that includes: - - Title - - Description - - 1. Example table format: - - | Title | Description | - |------------------|--------------------------------| - | Starter - Bicep | A starter template with Bicep | - - 1. Once a template is selected, **return all available details** about the selected template, including: - - `title` - - `description` - - `preview` - - `author` - - `authorUrl` - - `source` - - `tags` - - `azureServices` - - `languages` - - `id` - - 1. If the user wants to execute this template, provide a command guide using the following rule: - - Use the syntax: - `` - azd init -t - ``` - - `` is determined as follows: - - If the GitHub source URL starts with - `https://github.com/Azure-Samples/...`, - then use the organization/repository name: - ``` - e.g. azd init -t Azure-Samples/azure-openai-chat-frontend - ``` - - Otherwise, include the full `owner/repo` path: - ``` - e.g. azd init -t pascalvanderheiden/ais-apim-openai - ``` """; } + } \ No newline at end of file diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs index 02e16ce7..c890607f 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs @@ -1,6 +1,10 @@ namespace McpSamples.AwesomeAzd.HybridApp.Services; +using System.Diagnostics; using System.Text.Json; +using System; +using System.Threading; +using System.Threading.Tasks; using McpSamples.AwesomeAzd.HybridApp.Models; public class AwesomeAzdService(HttpClient http, ILogger logger) : IAwesomeAzdService @@ -36,6 +40,79 @@ public async Task> GetTemplateListAsync(string key return result; } + public async Task ExecuteTemplateCommandAsync( + string command, + string? workingDirectory = null, + CancellationToken cancellationToken = default) + { + try + { + cancellationToken.ThrowIfCancellationRequested(); + + // ⭐ Default working directory 설정: 현재 폴더 + /test + if (string.IsNullOrWhiteSpace(workingDirectory)) + { + string current = Directory.GetCurrentDirectory(); + workingDirectory = Path.Combine(current, "test"); + + if (!Directory.Exists(workingDirectory)) + Directory.CreateDirectory(workingDirectory); + } + + var process = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "cmd.exe", + Arguments = $"/c {command}", + WorkingDirectory = workingDirectory, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + } + }; + + process.Start(); + + cancellationToken.Register(() => + { + try { process.Kill(); } catch { } + }); + + string output = await process.StandardOutput.ReadToEndAsync(); + string error = await process.StandardError.ReadToEndAsync(); + + await process.WaitForExitAsync(cancellationToken); + + return new CommandExecutionResult + { + Success = process.ExitCode == 0, + Output = output, + Error = error + }; + } + catch (OperationCanceledException) + { + return new CommandExecutionResult + { + Success = false, + Output = "", + Error = "Command execution cancelled" + }; + } + catch (Exception ex) + { + return new CommandExecutionResult + { + Success = false, + Output = "", + Error = ex.Message + }; + } + } + + private async Task> GetTemplatesAsync(CancellationToken cancellationToken) { diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs index 9bff84ac..8b9ed027 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs @@ -14,6 +14,7 @@ public interface IAwesomeAzdService /// Cancellation token for the async operation. /// A containing all matching search results. Task> GetTemplateListAsync(string keywords, CancellationToken cancellationToken = default); - + + Task ExecuteTemplateCommandAsync(string command, string workingDirectory, CancellationToken cancellationToken = default); } } diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs index 296ecd47..62f0504b 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs @@ -18,6 +18,8 @@ public interface IAwesomeAzdTool /// The keyword to search templates for /// A list of matching template titles. Task> GetTemplateListAsync(string keywords); + + Task ExecuteTemplateCommandAsync(string command, string workingDirectory); } /// @@ -53,4 +55,33 @@ public async Task> GetTemplateListAsync( return result; } + + /// + [McpServerTool(Name = "execute_command", Title = "Execute a terminal command in a specific directory")] + [Description("Executes a terminal command in the given working directory.")] + public async Task ExecuteTemplateCommandAsync( + [Description("Command to execute")] string command, + [Description("Working directory where the command will run")] string workingDirectory) + { + try + { + var result = await service.ExecuteTemplateCommandAsync(command, workingDirectory); + + logger.LogInformation( + "Command '{Command}' executed with success={Success}", command, result.Success); + + return result; + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to execute command '{Command}'", command); + + return new CommandExecutionResult + { + Success = false, + Output = "", + Error = ex.Message + }; + } + } } From bc90719e754364b692c2ea1dfa317e37ad1a8b25 Mon Sep 17 00:00:00 2001 From: itanstar Date: Sun, 16 Nov 2025 19:58:03 +0900 Subject: [PATCH 25/47] doc(awesome-azd):add XML documentation to CommandExecutionResult model --- .../Models/AwesomeAzdTemplateModel.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs index 37a88312..932b3d90 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs @@ -59,10 +59,25 @@ public class AwesomeAzdTemplateModel public List Languages { get; set; } = new(); } - +/// +/// Represents the result of executing a terminal command. +/// public class CommandExecutionResult { + /// + /// Gets or sets a value indicating whether the command executed successfully. + /// True if the command exited with code 0; otherwise, false. + /// public bool Success { get; set; } + + /// + /// Gets or sets the standard output produced by the executed command. + /// public string Output { get; set; } = ""; + + /// + /// Gets or sets the standard error produced by the executed command. + /// If an exception occurred or the command failed, this may contain the error message. + /// public string Error { get; set; } = ""; } \ No newline at end of file From ca13c3581bef86a3f23c39b73b65df60b60044c4 Mon Sep 17 00:00:00 2001 From: itanstar Date: Tue, 18 Nov 2025 16:50:19 +0900 Subject: [PATCH 26/47] feat(awesome-azd): Refactor MCP server to 2-stage architecture --- .../Prompts/TemplatePrompt.cs | 131 ++++++++++-------- .../Services/AwesomeAzdService.cs | 54 +++++++- 2 files changed, 123 insertions(+), 62 deletions(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs index c743bffd..0c601cd2 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs @@ -3,78 +3,97 @@ namespace McpSamples.AwesomeAzd.HybridApp.Prompts; -/// -/// Provides an interface for generating prompts that guide template searches. -/// public interface ITemplatePrompt { - /// - /// Gets a prompt for searching Azure templates by keyword. - /// - /// The keyword to search for. - /// A formatted search prompt. string GetSearchPrompt(string keyword); } -/// -/// Represents the prompt entity for the Awesome AZD templates repository. -/// [McpServerPromptType] public class TemplatePrompt : ITemplatePrompt { - /// [McpServerPrompt(Name = "get_template_search_prompt", Title = "Prompt for searching AZD templates")] [Description("Get a prompt for searching Azure templates by keyword.")] public string GetSearchPrompt( - [Description("The keyword to search for")] string keyword) + [Description("The keyword to search for")] string keyword) { return $""" Please search all Azure templates related to the keyword `{keyword}`. Follow this process: - 1. Use the `awesome-azd` MCP server. - 2. Search all templates in the **Awesome AZD repository** using the `get_templates` tool. - 3. Return the results in the following **table format**: - - | Title | Description | Source | - |-------|-------------|--------| - | ... | ... | ... | - - 4. After showing the table, ask the user to **select one template by its Title**. - - 5. Once the user selects a Title: - - Retrieve the selected template's `source` value. - - Convert the `source` URL into the proper `` for AZD: - - Rules: - - If the URL starts with: - `https://github.com/Azure-Samples/...` - → Use `Azure-Samples/`. - - - Otherwise, extract the normal `owner/repo`. - - - Build the AZD command: - ``` - azd init -t --non-interactive --no-prompt - ``` - - **IMPORTANT** - MCP executes commands in a non-TTY environment. - Without `--non-interactive` and `--no-prompt`, `azd init` will wait for input and **never finish**. - So always include those flags. - - 6. **Immediately execute this command using the MCP tool `ExecuteTemplateCommandAsync`:** - - ``` - ExecuteTemplateCommandAsync("", "") - ``` - - - The working directory is where the template project should be initialized. - - 7. Return the execution result (Success, Output, Error) back to the user. - + ------------------------------------------------------------ + 1. Search templates + ------------------------------------------------------------ + - Use the `awesome-azd` MCP server. + - Call the `get_templates` tool. + - Filter all templates whose metadata contains the keyword `{keyword}`. + + ------------------------------------------------------------ + 2. Return results as a table + ------------------------------------------------------------ + Return ONLY this table format: + + | Title | Description | Source | + |-------|-------------|---------| + | ... | ... | ... | + + After the table, ask the user for the following **three inputs**: + + - **Selected Title** (must match a Title from the table) + - **Working Directory** (default = "/") + - **Environment Name** (default = "myenv") + + Ask the user to separate input values with semicolons (;): ; <WorkingDirectory or empty> ; <EnvironmentName or empty> + ex) OpenAI Agent ; C:\projects\agent ; AzureEnv + + ------------------------------------------------------------ + 3. After the user selects a template (Title) + ------------------------------------------------------------ + - Find the template's `source` from the search results. + - Convert `source` URL into AZD `<path>` using these rules: + + If source starts with: + `https://github.com/Azure-Samples/<repo>` + → AZD path = `Azure-Samples/<repo>` + + Otherwise: + → Extract `owner/repo` from GitHub URL. + + ------------------------------------------------------------ + 4.1 Build the AZD init command + ------------------------------------------------------------ + Construct: + + azd init -t <path> --environment <envName> + + Notes: + - If user did NOT provide Environment Name: + Use default: `myenv` + + ------------------------------------------------------------ + 4.2 Working directory rules + ------------------------------------------------------------ + If the user provides a directory: + Use that directory. + + If the user does NOT provide a directory: + you MUST pass `null` as the working directory. + (The backend will automatically apply the default behavior.) + + ------------------------------------------------------------ + 5. Execute the command + ------------------------------------------------------------ + Use: + Call the `execute_command` tool. + ExecuteTemplateCommandAsync( + "<generated command>", + "<chosen working directory OR null>" + ); + + After execution, return: + - Success / Failure + - Output + - Error (if any) """; } - -} \ No newline at end of file +} diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs index c890607f..39d6ec2b 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs @@ -40,23 +40,28 @@ public async Task<List<AwesomeAzdTemplateModel>> GetTemplateListAsync(string key return result; } - public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync( - string command, - string? workingDirectory = null, - CancellationToken cancellationToken = default) + public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string command, string? workingDirectory = null, CancellationToken cancellationToken = default) { try { cancellationToken.ThrowIfCancellationRequested(); - // ⭐ Default working directory 설정: 현재 폴더 + /test if (string.IsNullOrWhiteSpace(workingDirectory)) { string current = Directory.GetCurrentDirectory(); - workingDirectory = Path.Combine(current, "test"); + logger.LogInformation("Initial CurrentDirectory = {path}", current); + + // 템플릿 이름 파싱 + string templateName = ExtractTemplateNameFromCommand(command); + logger.LogInformation("Parsed template name = {name}", templateName); + + workingDirectory = Path.Combine(current, templateName); if (!Directory.Exists(workingDirectory)) + { + logger.LogInformation("Creating default directory: {dir}", workingDirectory); Directory.CreateDirectory(workingDirectory); + } } var process = new Process @@ -151,4 +156,41 @@ private static bool ContainsAnyKeyword(string? text, string[] searchTerms) } + private string ExtractTemplateNameFromCommand(string command) + { + if (string.IsNullOrWhiteSpace(command)) + return "MyTemplate"; + + try + { + // -t 다음 값을 뜯어오기 + // 예: -t Azure-Samples/openai-mcp-agent-dotnet + var parts = command.Split(' ', StringSplitOptions.RemoveEmptyEntries); + + for (int i = 0; i < parts.Length - 1; i++) + { + if (parts[i] == "-t") + { + string path = parts[i + 1]; // Azure-Samples/openai-mcp-agent-dotnet + + // owner/repo → repo + if (path.Contains('/')) + { + string repo = path.Split('/').Last().Trim(); + if (!string.IsNullOrWhiteSpace(repo)) + return repo; + } + + return path; // fallback (owner 없이 repo만 왔을 때) + } + } + } + catch + { + // 무슨 일이 생겨도 default + } + + return "MyTemplate"; + } + } \ No newline at end of file From daa00b4d693cdf18e605c9a090fe7f7453b056a3 Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Tue, 18 Nov 2025 17:27:21 +0900 Subject: [PATCH 27/47] doc(awesome-azd): Add XML comments for ExecuteTemplateCommandAsync in IAwesomeAzdService --- .../Services/IAwesomeAzdService.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs index 8b9ed027..6389f571 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs @@ -14,7 +14,19 @@ public interface IAwesomeAzdService /// <param name="cancellationToken">Cancellation token for the async operation.</param> /// <returns>A <see cref="List{AwesomeAzdTemplateModel}"/> containing all matching search results.</returns> Task<List<AwesomeAzdTemplateModel>> GetTemplateListAsync(string keywords, CancellationToken cancellationToken = default); - + + /// <summary> + /// Executes a given AZD template command asynchronously. + /// </summary> + /// <param name="command">The AZD command to execute (e.g., "azd init -t owner/repo --environment myenv").</param> + /// <param name="workingDirectory"> + /// The directory where the command should be executed. + /// Can be <c>null</c> if a default directory should be used internally. + /// </param> + /// <param name="cancellationToken">Cancellation token to cancel the async operation.</param> + /// <returns> + /// A <see cref="CommandExecutionResult"/> containing the success status, output, and any error messages from the command execution. + /// </returns> Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string command, string workingDirectory, CancellationToken cancellationToken = default); } } From c30ac1d071f0f2c57fa83a0ff53a1a7499f44ad8 Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Tue, 18 Nov 2025 17:32:17 +0900 Subject: [PATCH 28/47] feat(awesome-azd): Add detailed logging for AwesomeAzdService methods --- .../Services/AwesomeAzdService.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs index 39d6ec2b..990e4dfd 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs @@ -45,13 +45,13 @@ public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string com try { cancellationToken.ThrowIfCancellationRequested(); + logger.LogInformation("Executing command: {command}", command); if (string.IsNullOrWhiteSpace(workingDirectory)) { string current = Directory.GetCurrentDirectory(); logger.LogInformation("Initial CurrentDirectory = {path}", current); - // 템플릿 이름 파싱 string templateName = ExtractTemplateNameFromCommand(command); logger.LogInformation("Parsed template name = {name}", templateName); @@ -59,7 +59,6 @@ public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string com if (!Directory.Exists(workingDirectory)) { - logger.LogInformation("Creating default directory: {dir}", workingDirectory); Directory.CreateDirectory(workingDirectory); } } @@ -77,7 +76,8 @@ public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string com CreateNoWindow = true } }; - + + logger.LogInformation("Creating default directory: {dir}", workingDirectory); process.Start(); cancellationToken.Register(() => @@ -99,6 +99,7 @@ public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string com } catch (OperationCanceledException) { + logger.LogWarning("Command execution cancelled by user"); return new CommandExecutionResult { Success = false, @@ -108,6 +109,7 @@ public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string com } catch (Exception ex) { + logger.LogError(ex, "Exception occurred while executing command"); return new CommandExecutionResult { Success = false, @@ -163,17 +165,14 @@ private string ExtractTemplateNameFromCommand(string command) try { - // -t 다음 값을 뜯어오기 - // 예: -t Azure-Samples/openai-mcp-agent-dotnet var parts = command.Split(' ', StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < parts.Length - 1; i++) { if (parts[i] == "-t") { - string path = parts[i + 1]; // Azure-Samples/openai-mcp-agent-dotnet + string path = parts[i + 1]; - // owner/repo → repo if (path.Contains('/')) { string repo = path.Split('/').Last().Trim(); @@ -181,13 +180,13 @@ private string ExtractTemplateNameFromCommand(string command) return repo; } - return path; // fallback (owner 없이 repo만 왔을 때) + return path; } } } catch { - // 무슨 일이 생겨도 default + } return "MyTemplate"; From 31bf0aa0e6136ee89c89d6c23a6bc00dbc7861a4 Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Tue, 18 Nov 2025 17:44:18 +0900 Subject: [PATCH 29/47] feat(awesome-azd): Create working directory if missing in ExecuteTemplateCommandAsync --- .../Services/AwesomeAzdService.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs index 990e4dfd..fe17fa36 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs @@ -63,6 +63,16 @@ public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string com } } + if (!Directory.Exists(workingDirectory)) + { + logger.LogInformation("Creating directory: {dir}", workingDirectory); + Directory.CreateDirectory(workingDirectory); + } + else + { + logger.LogInformation("Using existing directory: {dir}", workingDirectory); + } + var process = new Process { StartInfo = new ProcessStartInfo From cfd4b6664717a32d5f2f6795c5a58b15b484943a Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Tue, 18 Nov 2025 19:43:25 +0900 Subject: [PATCH 30/47] fix(awesome-azd): Re-add XML documentation for Prompt --- .../Prompts/TemplatePrompt.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs index 0c601cd2..52c608cd 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs @@ -3,14 +3,26 @@ namespace McpSamples.AwesomeAzd.HybridApp.Prompts; +/// <summary> +/// Provides an interface for generating prompts that guide template searches. +/// </summary> public interface ITemplatePrompt { + /// <summary> + /// Gets a prompt for searching Azure templates by keyword. + /// </summary> + /// <param name="keyword">The keyword to search for.</param> + /// <returns>A formatted search prompt.</returns> string GetSearchPrompt(string keyword); } +/// <summary> +/// Represents the prompt entity for the Awesome AZD templates repository. +/// </summary> [McpServerPromptType] public class TemplatePrompt : ITemplatePrompt { + /// <inheritdoc /> [McpServerPrompt(Name = "get_template_search_prompt", Title = "Prompt for searching AZD templates")] [Description("Get a prompt for searching Azure templates by keyword.")] public string GetSearchPrompt( From 69a378014e7e6865716d69fd830d781221d86c00 Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Sat, 22 Nov 2025 12:39:41 +0900 Subject: [PATCH 31/47] feat(awesome-azd): Support OS-specific command execution and use repo name in user home as default directory --- .../Prompts/TemplatePrompt.cs | 80 +++++------------- .../Services/AwesomeAzdService.cs | 82 ++++++++----------- .../Services/IAwesomeAzdService.cs | 17 ++-- .../Tools/AwesomeAzdTool.cs | 73 ++++++++++++++--- 4 files changed, 130 insertions(+), 122 deletions(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs index 52c608cd..72c707fb 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs @@ -31,81 +31,43 @@ public string GetSearchPrompt( return $""" Please search all Azure templates related to the keyword `{keyword}`. - Follow this process: - ------------------------------------------------------------ 1. Search templates ------------------------------------------------------------ - Use the `awesome-azd` MCP server. - - Call the `get_templates` tool. - - Filter all templates whose metadata contains the keyword `{keyword}`. - - ------------------------------------------------------------ - 2. Return results as a table - ------------------------------------------------------------ - Return ONLY this table format: - - | Title | Description | Source | - |-------|-------------|---------| - | ... | ... | ... | - - After the table, ask the user for the following **three inputs**: - - - **Selected Title** (must match a Title from the table) - - **Working Directory** (default = "<current_working_directory>/<template_name>") - - **Environment Name** (default = "myenv") - - Ask the user to separate input values with semicolons (;): <Title> ; <WorkingDirectory or empty> ; <EnvironmentName or empty> - ex) OpenAI Agent ; C:\projects\agent ; AzureEnv + - Call the `get_templates` tool with the keyword `{keyword}`. + - Return the results as a table with the columns: Title | Description | Source. + - Only include templates whose metadata matches the keyword. ------------------------------------------------------------ - 3. After the user selects a template (Title) + 2. Collect user input for execution ------------------------------------------------------------ - - Find the template's `source` from the search results. - - Convert `source` URL into AZD `<path>` using these rules: + After displaying the search results, ask the user to provide: - If source starts with: - `https://github.com/Azure-Samples/<repo>` - → AZD path = `Azure-Samples/<repo>` + 1. **Selected Template Source** (must match the Source column; e.g., GitHub URL) + 2. **Working Directory** (optional; leave empty for default/null) + 3. **Environment Name** (optional; leave empty for default/null) - Otherwise: - → Extract `owner/repo` from GitHub URL. + Example input: - ------------------------------------------------------------ - 4.1 Build the AZD init command - ------------------------------------------------------------ - Construct: - - azd init -t <path> --environment <envName> - - Notes: - - If user did NOT provide Environment Name: - Use default: `myenv` + https://github.com/Azure-Samples/azure-search-openai-demo-csharp ; C:\projects\agent ; AzureEnv ------------------------------------------------------------ - 4.2 Working directory rules + 3. Backend execution ------------------------------------------------------------ - If the user provides a directory: - Use that directory. + - Call the execute_template tool with a JSON object like this: + + "srcPath": "Selected Template Source", + "workingDirectory": <user input or null>, + "envName": <user input or null>" + - If the user does NOT provide a directory: - you MUST pass `null` as the working directory. - (The backend will automatically apply the default behavior.) - ------------------------------------------------------------ - 5. Execute the command - ------------------------------------------------------------ - Use: - Call the `execute_command` tool. - ExecuteTemplateCommandAsync( - "<generated command>", - "<chosen working directory OR null>" - ); + - If the user leaves **Working Directory** or **Environment Name** empty, + pass **null** for those values. The tool will handle defaults internally. - After execution, return: - - Success / Failure - - Output - - Error (if any) + - The tool will generate and execute the appropriate AZD command and return + success status, output, and any errors. """; } } diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs index fe17fa36..a9ace68e 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs @@ -40,28 +40,16 @@ public async Task<List<AwesomeAzdTemplateModel>> GetTemplateListAsync(string key return result; } - public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string command, string? workingDirectory = null, CancellationToken cancellationToken = default) + public async Task<ExecutionResult> ExecuteTemplateAsync(string srcPath, string workingDirectory, string envName, CancellationToken cancellationToken = default) { try { cancellationToken.ThrowIfCancellationRequested(); - logger.LogInformation("Executing command: {command}", command); - if (string.IsNullOrWhiteSpace(workingDirectory)) - { - string current = Directory.GetCurrentDirectory(); - logger.LogInformation("Initial CurrentDirectory = {path}", current); - - string templateName = ExtractTemplateNameFromCommand(command); - logger.LogInformation("Parsed template name = {name}", templateName); - - workingDirectory = Path.Combine(current, templateName); + string ownerRepo = ExtractOwnerRepo(srcPath); - if (!Directory.Exists(workingDirectory)) - { - Directory.CreateDirectory(workingDirectory); - } - } + string command = $"azd init -t {ownerRepo} --environment {envName}"; + logger.LogInformation("Generated command: {cmd}", command); if (!Directory.Exists(workingDirectory)) { @@ -72,13 +60,27 @@ public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string com { logger.LogInformation("Using existing directory: {dir}", workingDirectory); } - + + string fileName; + string arguments; + + if (OperatingSystem.IsWindows()) + { + fileName = "cmd.exe"; + arguments = $"/c {command}"; + } + else + { + fileName = "/bin/bash"; + arguments = $"-c \"{command}\""; + } + var process = new Process { StartInfo = new ProcessStartInfo { - FileName = "cmd.exe", - Arguments = $"/c {command}", + FileName = fileName, + Arguments = arguments, WorkingDirectory = workingDirectory, RedirectStandardOutput = true, RedirectStandardError = true, @@ -87,7 +89,7 @@ public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string com } }; - logger.LogInformation("Creating default directory: {dir}", workingDirectory); + logger.LogInformation("Executing: {file} {args}", fileName, arguments); process.Start(); cancellationToken.Register(() => @@ -100,7 +102,7 @@ public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string com await process.WaitForExitAsync(cancellationToken); - return new CommandExecutionResult + return new ExecutionResult { Success = process.ExitCode == 0, Output = output, @@ -110,17 +112,17 @@ public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string com catch (OperationCanceledException) { logger.LogWarning("Command execution cancelled by user"); - return new CommandExecutionResult + return new ExecutionResult { Success = false, Output = "", - Error = "Command execution cancelled" + Error = "Execution cancelled" }; } catch (Exception ex) { logger.LogError(ex, "Exception occurred while executing command"); - return new CommandExecutionResult + return new ExecutionResult { Success = false, Output = "", @@ -167,39 +169,23 @@ private static bool ContainsAnyKeyword(string? text, string[] searchTerms) return searchTerms.Any(term => text.Contains(term, StringComparison.InvariantCultureIgnoreCase)); } - - private string ExtractTemplateNameFromCommand(string command) + private string ExtractOwnerRepo(string srcPath) { - if (string.IsNullOrWhiteSpace(command)) - return "MyTemplate"; - try { - var parts = command.Split(' ', StringSplitOptions.RemoveEmptyEntries); - - for (int i = 0; i < parts.Length - 1; i++) + var uri = new Uri(srcPath); + var segments = uri.AbsolutePath.Trim('/').Split('/'); + if (segments.Length >= 2) { - if (parts[i] == "-t") - { - string path = parts[i + 1]; - - if (path.Contains('/')) - { - string repo = path.Split('/').Last().Trim(); - if (!string.IsNullOrWhiteSpace(repo)) - return repo; - } - - return path; - } + return $"{segments[0]}/{segments[1]}"; // owner/repo } } - catch + catch (Exception ex) { - + logger.LogError(ex, "Failed to parse srcPath as GitHub URL: {srcPath}", srcPath); } - return "MyTemplate"; + return srcPath; } } \ No newline at end of file diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs index 6389f571..aef4f08c 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs @@ -16,17 +16,24 @@ public interface IAwesomeAzdService Task<List<AwesomeAzdTemplateModel>> GetTemplateListAsync(string keywords, CancellationToken cancellationToken = default); /// <summary> - /// Executes a given AZD template command asynchronously. + /// Executes a given AZD template asynchronously using the specified GitHub repository as the source. /// </summary> - /// <param name="command">The AZD command to execute (e.g., "azd init -t owner/repo --environment myenv").</param> + /// <param name="srcPath"> + /// The GitHub repository URL of the template (e.g., "https://github.com/owner/repo"). + /// This will be converted internally into the AZD command. + /// </param> /// <param name="workingDirectory"> - /// The directory where the command should be executed. + /// The directory where the command should be executed. /// Can be <c>null</c> if a default directory should be used internally. /// </param> + /// <param name="envName"> + /// The name of the environment to apply. + /// Can be <c>null</c> to use the default environment internally (e.g., "myenv"). + /// </param> /// <param name="cancellationToken">Cancellation token to cancel the async operation.</param> /// <returns> - /// A <see cref="CommandExecutionResult"/> containing the success status, output, and any error messages from the command execution. + /// A <see cref="ExecutionResult"/> containing the success status, output, and any error messages from the command execution. /// </returns> - Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string command, string workingDirectory, CancellationToken cancellationToken = default); + Task<ExecutionResult> ExecuteTemplateAsync(string srcPath, string workingDirectory, string envName, CancellationToken cancellationToken = default); } } diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs index 62f0504b..766bdb87 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs @@ -19,7 +19,26 @@ public interface IAwesomeAzdTool /// <returns>A list of matching template titles.</returns> Task<List<AwesomeAzdTemplateModel>> GetTemplateListAsync(string keywords); - Task<CommandExecutionResult> ExecuteTemplateCommandAsync(string command, string workingDirectory); + /// <summary> + /// Executes a given AZD template asynchronously using the specified GitHub repository as the source. + /// If <paramref name="workingDirectory"/> or <paramref name="envName"/> is <c>null</c> or empty, + /// default values will be used internally ("myenv" for envName, user home directory for workingDirectory). + /// </summary> + /// <param name="srcPath"> + /// The GitHub repository URL of the template (e.g., "https://github.com/owner/repo"). + /// This will be converted internally into the AZD command. + /// </param> + /// <param name="workingDirectory"> + /// Optional directory where the command should be executed. + /// Pass <c>null</c> to use the default directory internally. + /// </param> + /// <param name="envName"> + /// Optional environment name. Pass <c>null</c> to use the default ("myenv") internally. + /// </param> + /// <returns> + /// A <see cref="ExecutionResult"/> containing the success status, output, and any error messages from the command execution. + /// </returns> + Task<ExecutionResult> ExecuteTemplateAsync(string srcPath, string? workingDirectory = null, string? envName = null); } /// <summary> @@ -57,26 +76,37 @@ public async Task<List<AwesomeAzdTemplateModel>> GetTemplateListAsync( } /// <inheritdoc /> - [McpServerTool(Name = "execute_command", Title = "Execute a terminal command in a specific directory")] - [Description("Executes a terminal command in the given working directory.")] - public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync( - [Description("Command to execute")] string command, - [Description("Working directory where the command will run")] string workingDirectory) + [McpServerTool(Name = "execute_template", Title = "Execute a template initialization in a specific directory")] + [Description("Executes a template initialization based on a GitHub template repository in the given working directory.")] + public async Task<ExecutionResult> ExecuteTemplateAsync( + [Description("GitHub repository URL for the template")] string srcPath, + [Description("Working directory where the command will run")] string? workingDirectory = null, + [Description("Name of the environment to apply")] string? envName = null) { try { - var result = await service.ExecuteTemplateCommandAsync(command, workingDirectory); + var environment = string.IsNullOrWhiteSpace(envName) ? "myenv" : envName; + var directory = string.IsNullOrWhiteSpace(workingDirectory) + ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ExtractRepoName(srcPath)) + : workingDirectory; + + var result = await service.ExecuteTemplateAsync(srcPath, directory, environment); logger.LogInformation( - "Command '{Command}' executed with success={Success}", command, result.Success); + "Template command executed for srcPath '{SrcPath}' with success={Success}", + srcPath, + result.Success + ); return result; } catch (Exception ex) { - logger.LogError(ex, "Failed to execute command '{Command}'", command); + logger.LogError(ex, + "Failed to execute template command for srcPath='{SrcPath}', workingDirectory='{WorkingDirectory}', envName='{EnvName}'", + srcPath, workingDirectory, envName); - return new CommandExecutionResult + return new ExecutionResult { Success = false, Output = "", @@ -84,4 +114,27 @@ public async Task<CommandExecutionResult> ExecuteTemplateCommandAsync( }; } } + + /// <summary> + /// Extracts the repository name from a GitHub URL (e.g., "owner/repo" -> "repo"). + /// </summary> + private string ExtractRepoName(string srcPath) + { + try + { + var uri = new Uri(srcPath); + var segments = uri.AbsolutePath.Trim('/').Split('/'); + if (segments.Length >= 2) + { + return segments[1]; // repo name + } + } + catch + { + logger.LogWarning("Failed to parse repo name from srcPath: {SrcPath}", srcPath); + } + + return srcPath; + } + } From 648433895c7195d342c6349babc3db84d832917d Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Mon, 24 Nov 2025 16:08:35 +0900 Subject: [PATCH 32/47] feat(awesome-azd): Change model name CommandExecutionResult to ExecutionResult --- .../Models/AwesomeAzdTemplateModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs index 932b3d90..6cd079a6 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs @@ -62,7 +62,7 @@ public class AwesomeAzdTemplateModel /// <summary> /// Represents the result of executing a terminal command. /// </summary> -public class CommandExecutionResult +public class ExecutionResult { /// <summary> /// Gets or sets a value indicating whether the command executed successfully. From 70363fd9967183c19fee1fe7938fe948b3bf90cb Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Mon, 24 Nov 2025 16:54:24 +0900 Subject: [PATCH 33/47] feat(awesome-azd): Rename AwesomeAzdTemplateModel.cs to AwesomeAzdTemplateDomain.cs --- .../{AwesomeAzdTemplateModel.cs => AwesomeAzdTemplateDomain.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/{AwesomeAzdTemplateModel.cs => AwesomeAzdTemplateDomain.cs} (100%) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateDomain.cs similarity index 100% rename from awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateModel.cs rename to awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateDomain.cs From e2833fe5659a64edbf96b13ff6b8d16e63b69fff Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Mon, 24 Nov 2025 22:21:19 +0900 Subject: [PATCH 34/47] feat(awesome-azd): Separate domain and response models; redefine AzdCommand for Run-in-Terminal execution --- .../Models/AwesomeAzdTemplateDomain.cs | 29 +------------------ .../Models/AwesomeAzdTemplateResponse.cs | 28 ++++++++++++++++++ .../Models/ExecutionResult.cs | 17 +++++++++++ 3 files changed, 46 insertions(+), 28 deletions(-) create mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateResponse.cs create mode 100644 awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/ExecutionResult.cs diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateDomain.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateDomain.cs index 6cd079a6..43648b7a 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateDomain.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateDomain.cs @@ -3,7 +3,7 @@ namespace McpSamples.AwesomeAzd.HybridApp.Models; /// <summary> /// This represents a template from the awesome-azd API. /// </summary> -public class AwesomeAzdTemplateModel +public class AwesomeAzdTemplateDomain { /// <summary> /// Gets or sets the unique identifier for the awesome-azd template. @@ -20,11 +20,6 @@ public class AwesomeAzdTemplateModel /// </summary> public string Description { get; set; } = string.Empty; - /// <summary> - /// Gets or sets the preview image path for the awesome-azd template. - /// </summary> - public string Preview { get; set; } = string.Empty; - /// <summary> /// Gets or sets the author URL (e.g., GitHub profile or team page). /// The keys "AuthorUrl" and "website" have the same meaning but are spelled differently in the JSON file. @@ -59,25 +54,3 @@ public class AwesomeAzdTemplateModel public List<string> Languages { get; set; } = new(); } -/// <summary> -/// Represents the result of executing a terminal command. -/// </summary> -public class ExecutionResult -{ - /// <summary> - /// Gets or sets a value indicating whether the command executed successfully. - /// True if the command exited with code 0; otherwise, false. - /// </summary> - public bool Success { get; set; } - - /// <summary> - /// Gets or sets the standard output produced by the executed command. - /// </summary> - public string Output { get; set; } = ""; - - /// <summary> - /// Gets or sets the standard error produced by the executed command. - /// If an exception occurred or the command failed, this may contain the error message. - /// </summary> - public string Error { get; set; } = ""; -} \ No newline at end of file diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateResponse.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateResponse.cs new file mode 100644 index 00000000..f97434b0 --- /dev/null +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AwesomeAzdTemplateResponse.cs @@ -0,0 +1,28 @@ +namespace McpSamples.AwesomeAzd.HybridApp.Models; + +/// <summary> +/// This represents a simplified model of an awesome-azd template for LLM responses. +/// </summary> +public class AwesomeAzdTemplateResponse +{ + /// <summary> + /// Gets or sets the unique identifier for the awesome-azd template. + /// </summary> + public string Id { get; set; } = string.Empty; + + /// <summary> + /// Gets or sets the title for the awesome-azd template. + /// </summary> + public string Title { get; set; } = string.Empty; + + /// <summary> + /// Gets or sets the description for the awesome-azd template. + /// </summary> + public string Description { get; set; } = string.Empty; + + /// <summary> + /// Gets or sets the source repository URL of the awesome-azd template. + /// </summary> + public string Source { get; set; } = string.Empty; + +} diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/ExecutionResult.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/ExecutionResult.cs new file mode 100644 index 00000000..740e456e --- /dev/null +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/ExecutionResult.cs @@ -0,0 +1,17 @@ +namespace McpSamples.AwesomeAzd.HybridApp.Models; + +/// <summary> +/// Represents a terminal command and its working directory. +/// </summary> +public class AzdCommand +{ + /// <summary> + /// The full command string to execute (e.g., azd init ...). + /// </summary> + public string Command { get; set; } = string.Empty; + + /// <summary> + /// The working directory in which the command should be executed. + /// </summary> + public string WorkingDirectory { get; set; } = string.Empty; +} \ No newline at end of file From 22da96dc2a063a873eca3872917cb2c2f6da8a6e Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Tue, 25 Nov 2025 02:29:08 +0900 Subject: [PATCH 35/47] feat(awesome-azd): shift template execution from MCP server to LLM using un_in_commands --- .../{ExecutionResult.cs => AzdCommand.cs} | 0 .../Prompts/TemplatePrompt.cs | 37 ++++-- .../Services/AwesomeAzdService.cs | 111 +++--------------- .../Services/IAwesomeAzdService.cs | 24 +--- .../Tools/AwesomeAzdTool.cs | 108 +++++++++-------- 5 files changed, 99 insertions(+), 181 deletions(-) rename awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/{ExecutionResult.cs => AzdCommand.cs} (100%) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/ExecutionResult.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AzdCommand.cs similarity index 100% rename from awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/ExecutionResult.cs rename to awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Models/AzdCommand.cs diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs index 72c707fb..336466c9 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs @@ -48,26 +48,43 @@ 1. Search templates 2. **Working Directory** (optional; leave empty for default/null) 3. **Environment Name** (optional; leave empty for default/null) + - If the user provides a **Selected Template Source**, you may proceed to execution immediately. + + - If the user does NOT provide a working directory: Automatically determine the host machine's user profile directory by running a terminal command (Run in Terminal) + + - On Windows: + echo %USERPROFILE% + + - On macOS or Linux: + echo $HOME + + Use the returned value as `hostUserProfile`. + Example input: - https://github.com/Azure-Samples/azure-search-openai-demo-csharp ; C:\projects\agent ; AzureEnv + https://github.com/Azure-Samples/azure-search-openai-demo-csharp ; C:\Users\<UserProfile>\Projects ; AzureEnv ------------------------------------------------------------ 3. Backend execution ------------------------------------------------------------ - - Call the execute_template tool with a JSON object like this: - + - Call the `make_command` MCP tool with a JSON object like this: + "srcPath": "Selected Template Source", + "hostUserProfile": "<host user's profile path>", "workingDirectory": <user input or null>, - "envName": <user input or null>" - - + "envName": <user input or null> - - If the user leaves **Working Directory** or **Environment Name** empty, - pass **null** for those values. The tool will handle defaults internally. + - The tool will return an `AzdCommand` object. + - Use `run in terminal` to execute it: + + 1. `mkdir -p <AzdCommand.WorkingDirectory>` (Linux/macOS) + or `New-Item -ItemType Directory -Force -Path <AzdCommand.WorkingDirectory>` (Windows PowerShell) + 2. `cd <AzdCommand.WorkingDirectory>` + 3. Run `<AzdCommand.Command>` - - The tool will generate and execute the appropriate AZD command and return - success status, output, and any errors. + - Once you receive the AzdCommand object, immediately execute it in the terminal. + - The command will initialize the template in the specified directory and return success status, output, and any errors. """; + } } diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs index a9ace68e..ec30c599 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs @@ -11,11 +11,11 @@ public class AwesomeAzdService(HttpClient http, ILogger<AwesomeAzdService> logge { private const string AwesomeAzdTemplateFileUrl = "https://raw.githubusercontent.com/Azure/awesome-azd/main/website/static/templates.json"; - public async Task<List<AwesomeAzdTemplateModel>> GetTemplateListAsync(string keywords, CancellationToken cancellationToken = default) + public async Task<List<AwesomeAzdTemplateResponse>> GetTemplateListAsync(string keywords, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(keywords)) { - return new List<AwesomeAzdTemplateModel>(); + return new List<AwesomeAzdTemplateResponse>(); } var templates = await GetTemplatesAsync(cancellationToken).ConfigureAwait(false); @@ -27,7 +27,7 @@ public async Task<List<AwesomeAzdTemplateModel>> GetTemplateListAsync(string key logger.LogInformation("Search terms: {terms}", string.Join(", ", searchTerms)); - var result = templates + var searchResult = templates .Where(t => ContainsAnyKeyword(t.Title, searchTerms) || ContainsAnyKeyword(t.Description, searchTerms) || ContainsAnyKeyword(t.Author, searchTerms) @@ -37,102 +37,19 @@ public async Task<List<AwesomeAzdTemplateModel>> GetTemplateListAsync(string key || (t.AzureServices?.Any(svc => ContainsAnyKeyword(svc, searchTerms)) ?? false)) .ToList(); - return result; - } - - public async Task<ExecutionResult> ExecuteTemplateAsync(string srcPath, string workingDirectory, string envName, CancellationToken cancellationToken = default) - { - try + var responseList = searchResult.Select(m => new AwesomeAzdTemplateResponse { - cancellationToken.ThrowIfCancellationRequested(); - - string ownerRepo = ExtractOwnerRepo(srcPath); - - string command = $"azd init -t {ownerRepo} --environment {envName}"; - logger.LogInformation("Generated command: {cmd}", command); - - if (!Directory.Exists(workingDirectory)) - { - logger.LogInformation("Creating directory: {dir}", workingDirectory); - Directory.CreateDirectory(workingDirectory); - } - else - { - logger.LogInformation("Using existing directory: {dir}", workingDirectory); - } - - string fileName; - string arguments; + Id = m.Id, + Title = m.Title, + Description = m.Description, + Source = m.Source + }).ToList(); - if (OperatingSystem.IsWindows()) - { - fileName = "cmd.exe"; - arguments = $"/c {command}"; - } - else - { - fileName = "/bin/bash"; - arguments = $"-c \"{command}\""; - } - - var process = new Process - { - StartInfo = new ProcessStartInfo - { - FileName = fileName, - Arguments = arguments, - WorkingDirectory = workingDirectory, - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - } - }; - - logger.LogInformation("Executing: {file} {args}", fileName, arguments); - process.Start(); - - cancellationToken.Register(() => - { - try { process.Kill(); } catch { } - }); - - string output = await process.StandardOutput.ReadToEndAsync(); - string error = await process.StandardError.ReadToEndAsync(); - - await process.WaitForExitAsync(cancellationToken); - - return new ExecutionResult - { - Success = process.ExitCode == 0, - Output = output, - Error = error - }; - } - catch (OperationCanceledException) - { - logger.LogWarning("Command execution cancelled by user"); - return new ExecutionResult - { - Success = false, - Output = "", - Error = "Execution cancelled" - }; - } - catch (Exception ex) - { - logger.LogError(ex, "Exception occurred while executing command"); - return new ExecutionResult - { - Success = false, - Output = "", - Error = ex.Message - }; - } + return responseList; } - private async Task<List<AwesomeAzdTemplateModel>> GetTemplatesAsync(CancellationToken cancellationToken) + private async Task<List<AwesomeAzdTemplateDomain>> GetTemplatesAsync(CancellationToken cancellationToken) { try @@ -144,9 +61,9 @@ private async Task<List<AwesomeAzdTemplateModel>> GetTemplatesAsync(Cancellation var json = await response.Content.ReadAsStringAsync(cancellationToken); - var result = JsonSerializer.Deserialize<List<AwesomeAzdTemplateModel>>(json, + var result = JsonSerializer.Deserialize<List<AwesomeAzdTemplateDomain>>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) - ?? new List<AwesomeAzdTemplateModel>(); + ?? new List<AwesomeAzdTemplateDomain>(); logger.LogInformation("Loaded {count} templates.", result.Count); @@ -155,7 +72,7 @@ private async Task<List<AwesomeAzdTemplateModel>> GetTemplatesAsync(Cancellation catch (Exception ex) { logger.LogError(ex, "Failed to fetch or deserialize templates."); - return new List<AwesomeAzdTemplateModel>(); + return new List<AwesomeAzdTemplateDomain>(); } } diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs index aef4f08c..aae8d013 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/IAwesomeAzdService.cs @@ -12,28 +12,8 @@ public interface IAwesomeAzdService /// </summary> /// <param name="keywords">The keywords to search for.</param> /// <param name="cancellationToken">Cancellation token for the async operation.</param> - /// <returns>A <see cref="List{AwesomeAzdTemplateModel}"/> containing all matching search results.</returns> - Task<List<AwesomeAzdTemplateModel>> GetTemplateListAsync(string keywords, CancellationToken cancellationToken = default); + /// <returns>A <see cref="List{AwesomeAzdTemplateResponse}"/> containing all matching search results.</returns> + Task<List<AwesomeAzdTemplateResponse>> GetTemplateListAsync(string keywords, CancellationToken cancellationToken = default); - /// <summary> - /// Executes a given AZD template asynchronously using the specified GitHub repository as the source. - /// </summary> - /// <param name="srcPath"> - /// The GitHub repository URL of the template (e.g., "https://github.com/owner/repo"). - /// This will be converted internally into the AZD command. - /// </param> - /// <param name="workingDirectory"> - /// The directory where the command should be executed. - /// Can be <c>null</c> if a default directory should be used internally. - /// </param> - /// <param name="envName"> - /// The name of the environment to apply. - /// Can be <c>null</c> to use the default environment internally (e.g., "myenv"). - /// </param> - /// <param name="cancellationToken">Cancellation token to cancel the async operation.</param> - /// <returns> - /// A <see cref="ExecutionResult"/> containing the success status, output, and any error messages from the command execution. - /// </returns> - Task<ExecutionResult> ExecuteTemplateAsync(string srcPath, string workingDirectory, string envName, CancellationToken cancellationToken = default); } } diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs index 766bdb87..f3868b63 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs @@ -17,28 +17,22 @@ public interface IAwesomeAzdTool /// </summary> /// <param name="keywords">The keyword to search templates for</param> /// <returns>A list of matching template titles.</returns> - Task<List<AwesomeAzdTemplateModel>> GetTemplateListAsync(string keywords); + Task<List<AwesomeAzdTemplateResponse>> GetTemplateListAsync(string keywords); /// <summary> - /// Executes a given AZD template asynchronously using the specified GitHub repository as the source. - /// If <paramref name="workingDirectory"/> or <paramref name="envName"/> is <c>null</c> or empty, - /// default values will be used internally ("myenv" for envName, user home directory for workingDirectory). + /// Generates an AzdCommand object with default working directory and environment. /// </summary> - /// <param name="srcPath"> - /// The GitHub repository URL of the template (e.g., "https://github.com/owner/repo"). - /// This will be converted internally into the AZD command. - /// </param> - /// <param name="workingDirectory"> - /// Optional directory where the command should be executed. - /// Pass <c>null</c> to use the default directory internally. - /// </param> - /// <param name="envName"> - /// Optional environment name. Pass <c>null</c> to use the default ("myenv") internally. - /// </param> - /// <returns> - /// A <see cref="ExecutionResult"/> containing the success status, output, and any error messages from the command execution. - /// </returns> - Task<ExecutionResult> ExecuteTemplateAsync(string srcPath, string? workingDirectory = null, string? envName = null); + /// <param name="srcPath">GitHub repository URL for the template</param> + /// <param name="hostUserProfile">Host user profile path (optional)</param> + /// <param name="workingDirectory">Working directory where the command would run (optional)</param> + /// <param name="envName">Name of the environment to apply (optional)</param> + /// <returns>A task that resolves to an <see cref="AzdCommand"/>.</returns> + Task<AzdCommand> CreateCommandAsync( + [Description("GitHub repository URL for the template")] string srcPath, + [Description("Host user profile path")] string hostUserProfile, + [Description("Working directory where the command would run")] string? workingDirectory = null, + [Description("Name of the environment to apply")] string? envName = null); + } /// <summary> @@ -50,22 +44,22 @@ public class AwesomeAzdTool(IAwesomeAzdService service, ILogger<AwesomeAzdTool> /// <inheritdoc /> [McpServerTool(Name = "get_templates", Title = "Search Azure Developer templates")] [Description("Searches available Azure Developer templates by keyword.")] - public async Task<List<AwesomeAzdTemplateModel>> GetTemplateListAsync( + public async Task<List<AwesomeAzdTemplateResponse>> GetTemplateListAsync( [Description("The keyword to search templates for")] string keywords) { - var result = new List<AwesomeAzdTemplateModel>(); + var result = new List<AwesomeAzdTemplateResponse>(); try { var templates = await service.GetTemplateListAsync(keywords).ConfigureAwait(false); - result = templates.ToList(); + result = templates; logger.LogInformation("Template search completed successfully for keyword '{Keywords}'.", keywords); } catch (Exception ex) { logger.LogError(ex, "Error occurred while searching templates with keyword '{Keywords}'.", keywords); - result.Add(new AwesomeAzdTemplateModel + result.Add(new AwesomeAzdTemplateResponse { Title = "Error", Description = ex.Message @@ -76,43 +70,34 @@ public async Task<List<AwesomeAzdTemplateModel>> GetTemplateListAsync( } /// <inheritdoc /> - [McpServerTool(Name = "execute_template", Title = "Execute a template initialization in a specific directory")] - [Description("Executes a template initialization based on a GitHub template repository in the given working directory.")] - public async Task<ExecutionResult> ExecuteTemplateAsync( + [McpServerTool(Name = "make_command", Title = "Generate an AzdCommand with defaults")] + [Description("Generates an AzdCommand with default working directory and environment without executing it.")] + public Task<AzdCommand> CreateCommandAsync( [Description("GitHub repository URL for the template")] string srcPath, - [Description("Working directory where the command will run")] string? workingDirectory = null, - [Description("Name of the environment to apply")] string? envName = null) + [Description("Host user profile path")] string hostUserProfile, + [Description("Working directory where the command would run")] string? workingDirectory = null, + [Description("Name of the environment to apply")] string? envName = null) { - try - { - var environment = string.IsNullOrWhiteSpace(envName) ? "myenv" : envName; - var directory = string.IsNullOrWhiteSpace(workingDirectory) - ? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ExtractRepoName(srcPath)) + var environment = string.IsNullOrWhiteSpace(envName) ? "myenv" : envName; + + // workingDirectory가 지정되지 않았다면, hostUserProfile 기준으로 경로 구성 + var directory = string.IsNullOrWhiteSpace(workingDirectory) + ? Path.Combine(hostUserProfile, ExtractRepoName(srcPath)) : workingDirectory; - var result = await service.ExecuteTemplateAsync(srcPath, directory, environment); + var ownerRepo = ExtractOwnerRepo(srcPath); - logger.LogInformation( - "Template command executed for srcPath '{SrcPath}' with success={Success}", - srcPath, - result.Success - ); + var command = $"azd init -t {ownerRepo} --environment {environment}"; - return result; - } - catch (Exception ex) + logger.LogInformation("Generated AzdCommand for srcPath '{ownerRepo}' at directory '{Directory}'", ownerRepo, directory); + + var azdCommand = new AzdCommand { - logger.LogError(ex, - "Failed to execute template command for srcPath='{SrcPath}', workingDirectory='{WorkingDirectory}', envName='{EnvName}'", - srcPath, workingDirectory, envName); + Command = command, + WorkingDirectory = directory + }; - return new ExecutionResult - { - Success = false, - Output = "", - Error = ex.Message - }; - } + return Task.FromResult(azdCommand); } /// <summary> @@ -137,4 +122,23 @@ private string ExtractRepoName(string srcPath) return srcPath; } + private string ExtractOwnerRepo(string srcPath) + { + try + { + var uri = new Uri(srcPath); + var segments = uri.AbsolutePath.Trim('/').Split('/'); + if (segments.Length >= 2) + { + return $"{segments[0]}/{segments[1]}"; // owner/repo + } + } + catch + { + // 실패하면 그대로 srcPath 반환 + } + + return srcPath; + } + } From 23e9fd15f574d3567efa9c8b4dd0ac7d7e813af7 Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Tue, 25 Nov 2025 22:23:22 +0900 Subject: [PATCH 36/47] feat(awesome-azd): add Azure Setting files --- awesome-azd/azure.yaml | 16 ++ awesome-azd/infra/abbreviations.json | 136 +++++++++++++++++ awesome-azd/infra/main.bicep | 47 ++++++ awesome-azd/infra/main.parameters.json | 18 +++ .../infra/modules/fetch-container-image.bicep | 8 + awesome-azd/infra/resources.bicep | 144 ++++++++++++++++++ 6 files changed, 369 insertions(+) create mode 100644 awesome-azd/azure.yaml create mode 100644 awesome-azd/infra/abbreviations.json create mode 100644 awesome-azd/infra/main.bicep create mode 100644 awesome-azd/infra/main.parameters.json create mode 100644 awesome-azd/infra/modules/fetch-container-image.bicep create mode 100644 awesome-azd/infra/resources.bicep diff --git a/awesome-azd/azure.yaml b/awesome-azd/azure.yaml new file mode 100644 index 00000000..5725f97f --- /dev/null +++ b/awesome-azd/azure.yaml @@ -0,0 +1,16 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Azure/azure-dev/main/schemas/v1.0/azure.yaml.json + +name: awesome-azd + +metadata: + template: azd-init@1.14.0 + +services: + awesome-azd: + project: src/McpSamples.AwesomeAzd.HybridApp + host: containerapp + language: dotnet + docker: + path: ../../../Dockerfile.awesome-azd-azure + context: ../../../ + remoteBuild: false diff --git a/awesome-azd/infra/abbreviations.json b/awesome-azd/infra/abbreviations.json new file mode 100644 index 00000000..1533dee5 --- /dev/null +++ b/awesome-azd/infra/abbreviations.json @@ -0,0 +1,136 @@ +{ + "analysisServicesServers": "as", + "apiManagementService": "apim-", + "appConfigurationStores": "appcs-", + "appManagedEnvironments": "cae-", + "appContainerApps": "ca-", + "authorizationPolicyDefinitions": "policy-", + "automationAutomationAccounts": "aa-", + "blueprintBlueprints": "bp-", + "blueprintBlueprintsArtifacts": "bpa-", + "cacheRedis": "redis-", + "cdnProfiles": "cdnp-", + "cdnProfilesEndpoints": "cdne-", + "cognitiveServicesAccounts": "cog-", + "cognitiveServicesFormRecognizer": "cog-fr-", + "cognitiveServicesTextAnalytics": "cog-ta-", + "computeAvailabilitySets": "avail-", + "computeCloudServices": "cld-", + "computeDiskEncryptionSets": "des", + "computeDisks": "disk", + "computeDisksOs": "osdisk", + "computeGalleries": "gal", + "computeSnapshots": "snap-", + "computeVirtualMachines": "vm", + "computeVirtualMachineScaleSets": "vmss-", + "containerInstanceContainerGroups": "ci", + "containerRegistryRegistries": "cr", + "containerServiceManagedClusters": "aks-", + "databricksWorkspaces": "dbw-", + "dataFactoryFactories": "adf-", + "dataLakeAnalyticsAccounts": "dla", + "dataLakeStoreAccounts": "dls", + "dataMigrationServices": "dms-", + "dBforMySQLServers": "mysql-", + "dBforPostgreSQLServers": "psql-", + "devicesIotHubs": "iot-", + "devicesProvisioningServices": "provs-", + "devicesProvisioningServicesCertificates": "pcert-", + "documentDBDatabaseAccounts": "cosmos-", + "documentDBMongoDatabaseAccounts": "cosmon-", + "eventGridDomains": "evgd-", + "eventGridDomainsTopics": "evgt-", + "eventGridEventSubscriptions": "evgs-", + "eventHubNamespaces": "evhns-", + "eventHubNamespacesEventHubs": "evh-", + "hdInsightClustersHadoop": "hadoop-", + "hdInsightClustersHbase": "hbase-", + "hdInsightClustersKafka": "kafka-", + "hdInsightClustersMl": "mls-", + "hdInsightClustersSpark": "spark-", + "hdInsightClustersStorm": "storm-", + "hybridComputeMachines": "arcs-", + "insightsActionGroups": "ag-", + "insightsComponents": "appi-", + "keyVaultVaults": "kv-", + "kubernetesConnectedClusters": "arck", + "kustoClusters": "dec", + "kustoClustersDatabases": "dedb", + "logicIntegrationAccounts": "ia-", + "logicWorkflows": "logic-", + "machineLearningServicesWorkspaces": "mlw-", + "managedIdentityUserAssignedIdentities": "id-", + "managementManagementGroups": "mg-", + "migrateAssessmentProjects": "migr-", + "networkApplicationGateways": "agw-", + "networkApplicationSecurityGroups": "asg-", + "networkAzureFirewalls": "afw-", + "networkBastionHosts": "bas-", + "networkConnections": "con-", + "networkDnsZones": "dnsz-", + "networkExpressRouteCircuits": "erc-", + "networkFirewallPolicies": "afwp-", + "networkFirewallPoliciesWebApplication": "waf", + "networkFirewallPoliciesRuleGroups": "wafrg", + "networkFrontDoors": "fd-", + "networkFrontdoorWebApplicationFirewallPolicies": "fdfp-", + "networkLoadBalancersExternal": "lbe-", + "networkLoadBalancersInternal": "lbi-", + "networkLoadBalancersInboundNatRules": "rule-", + "networkLocalNetworkGateways": "lgw-", + "networkNatGateways": "ng-", + "networkNetworkInterfaces": "nic-", + "networkNetworkSecurityGroups": "nsg-", + "networkNetworkSecurityGroupsSecurityRules": "nsgsr-", + "networkNetworkWatchers": "nw-", + "networkPrivateDnsZones": "pdnsz-", + "networkPrivateLinkServices": "pl-", + "networkPublicIPAddresses": "pip-", + "networkPublicIPPrefixes": "ippre-", + "networkRouteFilters": "rf-", + "networkRouteTables": "rt-", + "networkRouteTablesRoutes": "udr-", + "networkTrafficManagerProfiles": "traf-", + "networkVirtualNetworkGateways": "vgw-", + "networkVirtualNetworks": "vnet-", + "networkVirtualNetworksSubnets": "snet-", + "networkVirtualNetworksVirtualNetworkPeerings": "peer-", + "networkVirtualWans": "vwan-", + "networkVpnGateways": "vpng-", + "networkVpnGatewaysVpnConnections": "vcn-", + "networkVpnGatewaysVpnSites": "vst-", + "notificationHubsNamespaces": "ntfns-", + "notificationHubsNamespacesNotificationHubs": "ntf-", + "operationalInsightsWorkspaces": "log-", + "portalDashboards": "dash-", + "powerBIDedicatedCapacities": "pbi-", + "purviewAccounts": "pview-", + "recoveryServicesVaults": "rsv-", + "resourcesResourceGroups": "rg-", + "searchSearchServices": "srch-", + "serviceBusNamespaces": "sb-", + "serviceBusNamespacesQueues": "sbq-", + "serviceBusNamespacesTopics": "sbt-", + "serviceEndPointPolicies": "se-", + "serviceFabricClusters": "sf-", + "signalRServiceSignalR": "sigr", + "sqlManagedInstances": "sqlmi-", + "sqlServers": "sql-", + "sqlServersDataWarehouse": "sqldw-", + "sqlServersDatabases": "sqldb-", + "sqlServersDatabasesStretch": "sqlstrdb-", + "storageStorageAccounts": "st", + "storageStorageAccountsVm": "stvm", + "storSimpleManagers": "ssimp", + "streamAnalyticsCluster": "asa-", + "synapseWorkspaces": "syn", + "synapseWorkspacesAnalyticsWorkspaces": "synw", + "synapseWorkspacesSqlPoolsDedicated": "syndp", + "synapseWorkspacesSqlPoolsSpark": "synsp", + "timeSeriesInsightsEnvironments": "tsi-", + "webServerFarms": "plan-", + "webSitesAppService": "app-", + "webSitesAppServiceEnvironment": "ase-", + "webSitesFunctions": "func-", + "webStaticSites": "stapp-" +} diff --git a/awesome-azd/infra/main.bicep b/awesome-azd/infra/main.bicep new file mode 100644 index 00000000..7167fd1f --- /dev/null +++ b/awesome-azd/infra/main.bicep @@ -0,0 +1,47 @@ +targetScope = 'subscription' + +@minLength(1) +@maxLength(64) +@description('Name of the environment that can be used as part of naming resource convention') +param environmentName string + +@minLength(1) +@description('Primary location for all resources') +param location string + +param mcpAwesomeAzdExists bool + +@description('Id of the user or app to assign application roles') +param principalId string + +// Tags that should be applied to all resources. +// +// Note that 'azd-service-name' tags should be applied separately to service host resources. +// Example usage: +// tags: union(tags, { 'azd-service-name': <service name in azure.yaml> }) +var tags = { + 'azd-env-name': environmentName +} + +// Organize resources in a resource group +resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { + name: 'rg-${environmentName}' + location: location + tags: tags +} + +module resources 'resources.bicep' = { + scope: rg + name: 'resources' + params: { + location: location + tags: tags + principalId: principalId + mcpAwesomeAzdExists: mcpAwesomeAzdExists + } +} + +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = resources.outputs.AZURE_CONTAINER_REGISTRY_ENDPOINT +output AZURE_RESOURCE_MCP_AWESOME_AZD_ID string = resources.outputs.AZURE_RESOURCE_MCP_AWESOME_AZD_ID +output AZURE_RESOURCE_MCP_AWESOME_AZD_NAME string = resources.outputs.AZURE_RESOURCE_MCP_AWESOME_AZD_NAME +output AZURE_RESOURCE_MCP_AWESOME_AZD_FQDN string = resources.outputs.AZURE_RESOURCE_MCP_AWESOME_AZD_FQDN diff --git a/awesome-azd/infra/main.parameters.json b/awesome-azd/infra/main.parameters.json new file mode 100644 index 00000000..1889ec66 --- /dev/null +++ b/awesome-azd/infra/main.parameters.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "environmentName": { + "value": "${AZURE_ENV_NAME}" + }, + "location": { + "value": "${AZURE_LOCATION}" + }, + "mcpAwesomeAzdExists": { + "value": "${SERVICE_AWESOME_AZD_RESOURCE_EXISTS=false}" + }, + "principalId": { + "value": "${AZURE_PRINCIPAL_ID}" + } + } +} diff --git a/awesome-azd/infra/modules/fetch-container-image.bicep b/awesome-azd/infra/modules/fetch-container-image.bicep new file mode 100644 index 00000000..78d1e7ee --- /dev/null +++ b/awesome-azd/infra/modules/fetch-container-image.bicep @@ -0,0 +1,8 @@ +param exists bool +param name string + +resource existingApp 'Microsoft.App/containerApps@2023-05-02-preview' existing = if (exists) { + name: name +} + +output containers array = exists ? existingApp.properties.template.containers : [] diff --git a/awesome-azd/infra/resources.bicep b/awesome-azd/infra/resources.bicep new file mode 100644 index 00000000..c89e455f --- /dev/null +++ b/awesome-azd/infra/resources.bicep @@ -0,0 +1,144 @@ +@description('The location used for all deployed resources') +param location string = resourceGroup().location + +@description('Tags that will be applied to all resources') +param tags object = {} + +param mcpAwesomeAzdExists bool + +@description('Id of the user or app to assign application roles') +param principalId string + +var abbrs = loadJsonContent('./abbreviations.json') +var resourceToken = uniqueString(subscription().id, resourceGroup().id, location) + +// Monitor application with Azure Monitor +module monitoring 'br/public:avm/ptn/azd/monitoring:0.1.0' = { + name: 'monitoring' + params: { + logAnalyticsName: '${abbrs.operationalInsightsWorkspaces}${resourceToken}' + applicationInsightsName: '${abbrs.insightsComponents}${resourceToken}' + applicationInsightsDashboardName: '${abbrs.portalDashboards}${resourceToken}' + location: location + tags: tags + } +} + +// Container registry +module containerRegistry 'br/public:avm/res/container-registry/registry:0.1.1' = { + name: 'registry' + params: { + name: '${abbrs.containerRegistryRegistries}${resourceToken}' + location: location + tags: tags + publicNetworkAccess: 'Enabled' + roleAssignments: [ + { + principalId: mcpAwesomeAzdIdentity.outputs.principalId + principalType: 'ServicePrincipal' + // ACR pull role + roleDefinitionIdOrName: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '7f951dda-4ed3-4680-a7ca-43fe172d538d') + } + ] + } +} + +// Container apps environment +module containerAppsEnvironment 'br/public:avm/res/app/managed-environment:0.4.5' = { + name: 'container-apps-environment' + params: { + logAnalyticsWorkspaceResourceId: monitoring.outputs.logAnalyticsWorkspaceResourceId + name: '${abbrs.appManagedEnvironments}${resourceToken}' + location: location + zoneRedundant: false + } +} + +// User assigned identity +module mcpAwesomeAzdIdentity 'br/public:avm/res/managed-identity/user-assigned-identity:0.2.1' = { + name: 'mcpAwesomeAzdIdentity' + params: { + name: '${abbrs.managedIdentityUserAssignedIdentities}mcpawesomeazd-${resourceToken}' + location: location + } +} + +// Azure Container Apps +module mcpAwesomeAzdFetchLatestImage './modules/fetch-container-image.bicep' = { + name: 'mcpAwesomeAzd-fetch-image' + params: { + exists: mcpAwesomeAzdExists + name: 'awesome-azd' + } +} + +module mcpAwesomeAzd 'br/public:avm/res/app/container-app:0.8.0' = { + name: 'mcpAwesomeAzd' + params: { + name: 'awesome-azd' + ingressTargetPort: 8080 + scaleMinReplicas: 1 + scaleMaxReplicas: 10 + secrets: { + secureList: [ + ] + } + containers: [ + { + image: mcpAwesomeAzdFetchLatestImage.outputs.?containers[?0].?image ?? 'mcr.microsoft.com/azuredocs/containerapps-helloworld:latest' + name: 'main' + resources: { + cpu: json('0.5') + memory: '1.0Gi' + } + env: [ + { + name: 'APPLICATIONINSIGHTS_CONNECTION_STRING' + value: monitoring.outputs.applicationInsightsConnectionString + } + { + name: 'AZURE_CLIENT_ID' + value: mcpAwesomeAzdIdentity.outputs.clientId + } + { + name: 'PORT' + value: '8080' + } + ] + args: [ + '--http' + ] + } + ] + managedIdentities: { + systemAssigned: false + userAssignedResourceIds: [ + mcpAwesomeAzdIdentity.outputs.resourceId + ] + } + registries: [ + { + server: containerRegistry.outputs.loginServer + identity: mcpAwesomeAzdIdentity.outputs.resourceId + } + ] + environmentResourceId: containerAppsEnvironment.outputs.resourceId + corsPolicy: { + allowedOrigins: [ + 'https://make.preview.powerapps.com' + 'https://make.powerapps.com' + 'https://make.preview.powerautomate.com' + 'https://make.powerautomate.com' + 'https://copilotstudio.preview.microsoft.com' + 'https://copilotstudio.microsoft.com' + ] + } + location: location + tags: union(tags, { 'azd-service-name': 'awesome-azd' }) + } +} + +output AZURE_CONTAINER_REGISTRY_ENDPOINT string = containerRegistry.outputs.loginServer +output AZURE_RESOURCE_MCP_AWESOME_AZD_ID string = mcpAwesomeAzd.outputs.resourceId +output AZURE_RESOURCE_MCP_AWESOME_AZD_NAME string = mcpAwesomeAzd.outputs.name +output AZURE_RESOURCE_MCP_AWESOME_AZD_FQDN string = mcpAwesomeAzd.outputs.fqdn From 6d72810a120e7128dde2ef9e1e8c42555795d0c9 Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Tue, 25 Nov 2025 22:35:03 +0900 Subject: [PATCH 37/47] feat(awesome-azd): fix prompt to auto-execute AzdCommand immediately after input --- .../Prompts/TemplatePrompt.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs index 336466c9..48e8abbf 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs @@ -40,7 +40,7 @@ 1. Search templates - Only include templates whose metadata matches the keyword. ------------------------------------------------------------ - 2. Collect user input for execution + 2. Collect user input for execution and Backend execution ------------------------------------------------------------ After displaying the search results, ask the user to provide: @@ -53,7 +53,7 @@ 1. Search templates - If the user does NOT provide a working directory: Automatically determine the host machine's user profile directory by running a terminal command (Run in Terminal) - On Windows: - echo %USERPROFILE% + Write-Output $env:USERPROFILE - On macOS or Linux: echo $HOME @@ -64,9 +64,7 @@ Use the returned value as `hostUserProfile`. https://github.com/Azure-Samples/azure-search-openai-demo-csharp ; C:\Users\<UserProfile>\Projects ; AzureEnv - ------------------------------------------------------------ - 3. Backend execution - ------------------------------------------------------------ + - Call the `make_command` MCP tool with a JSON object like this: "srcPath": "Selected Template Source", @@ -82,7 +80,7 @@ 3. Backend execution 2. `cd <AzdCommand.WorkingDirectory>` 3. Run `<AzdCommand.Command>` - - Once you receive the AzdCommand object, immediately execute it in the terminal. + - Once you receive the AzdCommand object, immediately execute it in the terminal — no additional steps required. - The command will initialize the template in the specified directory and return success status, output, and any errors. """; From 8a7c5ebd3ea5f83cc248222dc969cdbcdc679b4b Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Tue, 25 Nov 2025 22:43:16 +0900 Subject: [PATCH 38/47] feat(awesome-azd): make hostUserProfile nullable and throw if both hostUserProfile and workingDirectory are null --- .../Tools/AwesomeAzdTool.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs index f3868b63..85537e2a 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs @@ -29,7 +29,7 @@ public interface IAwesomeAzdTool /// <returns>A task that resolves to an <see cref="AzdCommand"/>.</returns> Task<AzdCommand> CreateCommandAsync( [Description("GitHub repository URL for the template")] string srcPath, - [Description("Host user profile path")] string hostUserProfile, + [Description("Host user profile path")] string? hostUserProfile = null, [Description("Working directory where the command would run")] string? workingDirectory = null, [Description("Name of the environment to apply")] string? envName = null); @@ -74,12 +74,20 @@ public async Task<List<AwesomeAzdTemplateResponse>> GetTemplateListAsync( [Description("Generates an AzdCommand with default working directory and environment without executing it.")] public Task<AzdCommand> CreateCommandAsync( [Description("GitHub repository URL for the template")] string srcPath, - [Description("Host user profile path")] string hostUserProfile, + [Description("Host user profile path")] string? hostUserProfile = null, [Description("Working directory where the command would run")] string? workingDirectory = null, [Description("Name of the environment to apply")] string? envName = null) { var environment = string.IsNullOrWhiteSpace(envName) ? "myenv" : envName; + // workingDirectory와 hostUserProfile 둘 다 없으면 예외 처리 + if (string.IsNullOrWhiteSpace(workingDirectory) && string.IsNullOrWhiteSpace(hostUserProfile)) + { + var msg = "Both hostUserProfile and workingDirectory are null or empty. Please provide at least one valid path."; + logger.LogError(msg); + throw new ArgumentException(msg, nameof(workingDirectory)); + } + // workingDirectory가 지정되지 않았다면, hostUserProfile 기준으로 경로 구성 var directory = string.IsNullOrWhiteSpace(workingDirectory) ? Path.Combine(hostUserProfile, ExtractRepoName(srcPath)) From 5822a0312cd34924cd32917fc6b7709bb1a2f224 Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Tue, 25 Nov 2025 22:57:46 +0900 Subject: [PATCH 39/47] feat(awesome-azd): Add stdio container, http.remote json files --- awesome-azd/.vscode/mcp.http.container.json | 8 ++++++++ awesome-azd/.vscode/mcp.http.remote.json | 15 +++++++++++++++ awesome-azd/.vscode/mcp.stdio.container.json | 14 ++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 awesome-azd/.vscode/mcp.http.container.json create mode 100644 awesome-azd/.vscode/mcp.http.remote.json create mode 100644 awesome-azd/.vscode/mcp.stdio.container.json diff --git a/awesome-azd/.vscode/mcp.http.container.json b/awesome-azd/.vscode/mcp.http.container.json new file mode 100644 index 00000000..8574142b --- /dev/null +++ b/awesome-azd/.vscode/mcp.http.container.json @@ -0,0 +1,8 @@ +{ + "servers": { + "awesome-azd": { + "type": "http", + "url": "http://localhost:8080/mcp" + } + } +} diff --git a/awesome-azd/.vscode/mcp.http.remote.json b/awesome-azd/.vscode/mcp.http.remote.json new file mode 100644 index 00000000..bb4c0925 --- /dev/null +++ b/awesome-azd/.vscode/mcp.http.remote.json @@ -0,0 +1,15 @@ +{ + "inputs": [ + { + "type": "promptString", + "id": "acaapp-server-fqdn", + "description": "Azure Container Apps FQDN" + } + ], + "servers": { + "awesome-azd": { + "type": "http", + "url": "https://${input:acaapp-server-fqdn}/mcp" + } + } +} diff --git a/awesome-azd/.vscode/mcp.stdio.container.json b/awesome-azd/.vscode/mcp.stdio.container.json new file mode 100644 index 00000000..38ad3fac --- /dev/null +++ b/awesome-azd/.vscode/mcp.stdio.container.json @@ -0,0 +1,14 @@ +{ + "servers": { + "awesome-copilot": { + "type": "stdio", + "command": "docker", + "args": [ + "run", + "-i", + "--rm", + "ghcr.io/microsoft/mcp-dotnet-samples/awesome-azd:latest" + ] + } + } +} From b55608c669952ed312c6b08c5765cb20accc5c1f Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Sat, 29 Nov 2025 09:50:53 +0900 Subject: [PATCH 40/47] feat(awesome-azd): Removed the tools default behavior and made the parameters non-nullable --- awesome-azd/.vscode/mcp.stdio.container.json | 2 +- .../Prompts/TemplatePrompt.cs | 23 ++---- .../Properties/launchSettings.json | 11 ++- .../Tools/AwesomeAzdTool.cs | 79 +++---------------- 4 files changed, 27 insertions(+), 88 deletions(-) diff --git a/awesome-azd/.vscode/mcp.stdio.container.json b/awesome-azd/.vscode/mcp.stdio.container.json index 38ad3fac..d11f82e6 100644 --- a/awesome-azd/.vscode/mcp.stdio.container.json +++ b/awesome-azd/.vscode/mcp.stdio.container.json @@ -1,6 +1,6 @@ { "servers": { - "awesome-copilot": { + "awesome-azd": { "type": "stdio", "command": "docker", "args": [ diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs index 48e8abbf..82bbda9d 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Prompts/TemplatePrompt.cs @@ -50,15 +50,7 @@ 1. Search templates - If the user provides a **Selected Template Source**, you may proceed to execution immediately. - - If the user does NOT provide a working directory: Automatically determine the host machine's user profile directory by running a terminal command (Run in Terminal) - - - On Windows: - Write-Output $env:USERPROFILE - - - On macOS or Linux: - echo $HOME - - Use the returned value as `hostUserProfile`. + **IMPORTANT** - If the user does not provide a working directory or an environment name, keep asking repeatedly until they provide one. Example input: @@ -68,19 +60,16 @@ Use the returned value as `hostUserProfile`. - Call the `make_command` MCP tool with a JSON object like this: "srcPath": "Selected Template Source", - "hostUserProfile": "<host user's profile path>", - "workingDirectory": <user input or null>, - "envName": <user input or null> + "workingDirectory", + "envName" - The tool will return an `AzdCommand` object. - Use `run in terminal` to execute it: - 1. `mkdir -p <AzdCommand.WorkingDirectory>` (Linux/macOS) - or `New-Item -ItemType Directory -Force -Path <AzdCommand.WorkingDirectory>` (Windows PowerShell) - 2. `cd <AzdCommand.WorkingDirectory>` - 3. Run `<AzdCommand.Command>` + - mkdir "<AzdCommand.WorkingDirectory>" -Force; cd "<AzdCommand.WorkingDirectory>"; <AzdCommand.Command> (Window) + - mkdir -p "<AzdCommand.WorkingDirectory>" && cd "<AzdCommand.WorkingDirectory>" && <AzdCommand.Command> (MacOS, Linux) - - Once you receive the AzdCommand object, immediately execute it in the terminal — no additional steps required. + - Once the command is generated, execute it immediately without further confirmation. - The command will initialize the template in the specified directory and return success status, output, and any errors. """; diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Properties/launchSettings.json b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Properties/launchSettings.json index d46036a3..46456098 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Properties/launchSettings.json +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Properties/launchSettings.json @@ -9,6 +9,15 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:45201;http://localhost:5201", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } } } -} +} \ No newline at end of file diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs index 85537e2a..a355ce03 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs @@ -23,15 +23,13 @@ public interface IAwesomeAzdTool /// Generates an AzdCommand object with default working directory and environment. /// </summary> /// <param name="srcPath">GitHub repository URL for the template</param> - /// <param name="hostUserProfile">Host user profile path (optional)</param> - /// <param name="workingDirectory">Working directory where the command would run (optional)</param> - /// <param name="envName">Name of the environment to apply (optional)</param> + /// <param name="workingDirectory">Working directory where the command would run</param> + /// <param name="envName">Name of the environment to apply</param> /// <returns>A task that resolves to an <see cref="AzdCommand"/>.</returns> Task<AzdCommand> CreateCommandAsync( [Description("GitHub repository URL for the template")] string srcPath, - [Description("Host user profile path")] string? hostUserProfile = null, - [Description("Working directory where the command would run")] string? workingDirectory = null, - [Description("Name of the environment to apply")] string? envName = null); + [Description("Working directory where the command would run")] string workingDirectory, + [Description("Name of the environment to apply")] string envName); } @@ -71,82 +69,25 @@ public async Task<List<AwesomeAzdTemplateResponse>> GetTemplateListAsync( /// <inheritdoc /> [McpServerTool(Name = "make_command", Title = "Generate an AzdCommand with defaults")] - [Description("Generates an AzdCommand with default working directory and environment without executing it.")] + [Description("Generates an AzdCommand with default working directory.")] public Task<AzdCommand> CreateCommandAsync( [Description("GitHub repository URL for the template")] string srcPath, - [Description("Host user profile path")] string? hostUserProfile = null, - [Description("Working directory where the command would run")] string? workingDirectory = null, - [Description("Name of the environment to apply")] string? envName = null) + [Description("Working directory where the command would run")] string workingDirectory, + [Description("Name of the environment to apply")] string envName) { - var environment = string.IsNullOrWhiteSpace(envName) ? "myenv" : envName; - - // workingDirectory와 hostUserProfile 둘 다 없으면 예외 처리 - if (string.IsNullOrWhiteSpace(workingDirectory) && string.IsNullOrWhiteSpace(hostUserProfile)) - { - var msg = "Both hostUserProfile and workingDirectory are null or empty. Please provide at least one valid path."; - logger.LogError(msg); - throw new ArgumentException(msg, nameof(workingDirectory)); - } - // workingDirectory가 지정되지 않았다면, hostUserProfile 기준으로 경로 구성 - var directory = string.IsNullOrWhiteSpace(workingDirectory) - ? Path.Combine(hostUserProfile, ExtractRepoName(srcPath)) - : workingDirectory; - - var ownerRepo = ExtractOwnerRepo(srcPath); + var command = $"azd init -t {srcPath} --environment {envName}"; - var command = $"azd init -t {ownerRepo} --environment {environment}"; - - logger.LogInformation("Generated AzdCommand for srcPath '{ownerRepo}' at directory '{Directory}'", ownerRepo, directory); + logger.LogInformation("Generated AzdCommand for srcPath '{srcPath}' at directory '{workingDirectory}'", srcPath, workingDirectory); var azdCommand = new AzdCommand { Command = command, - WorkingDirectory = directory + WorkingDirectory = workingDirectory }; return Task.FromResult(azdCommand); } - /// <summary> - /// Extracts the repository name from a GitHub URL (e.g., "owner/repo" -> "repo"). - /// </summary> - private string ExtractRepoName(string srcPath) - { - try - { - var uri = new Uri(srcPath); - var segments = uri.AbsolutePath.Trim('/').Split('/'); - if (segments.Length >= 2) - { - return segments[1]; // repo name - } - } - catch - { - logger.LogWarning("Failed to parse repo name from srcPath: {SrcPath}", srcPath); - } - - return srcPath; - } - - private string ExtractOwnerRepo(string srcPath) - { - try - { - var uri = new Uri(srcPath); - var segments = uri.AbsolutePath.Trim('/').Split('/'); - if (segments.Length >= 2) - { - return $"{segments[0]}/{segments[1]}"; // owner/repo - } - } - catch - { - // 실패하면 그대로 srcPath 반환 - } - - return srcPath; - } } From 856daedcbba2614c2fad8aa6bfde368cd2229159 Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Sat, 29 Nov 2025 14:31:40 +0900 Subject: [PATCH 41/47] feat(awesome-azd): Removed unused Task --- .../Services/AwesomeAzdService.cs | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs index ec30c599..4e5193dc 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Services/AwesomeAzdService.cs @@ -86,23 +86,4 @@ private static bool ContainsAnyKeyword(string? text, string[] searchTerms) return searchTerms.Any(term => text.Contains(term, StringComparison.InvariantCultureIgnoreCase)); } - private string ExtractOwnerRepo(string srcPath) - { - try - { - var uri = new Uri(srcPath); - var segments = uri.AbsolutePath.Trim('/').Split('/'); - if (segments.Length >= 2) - { - return $"{segments[0]}/{segments[1]}"; // owner/repo - } - } - catch (Exception ex) - { - logger.LogError(ex, "Failed to parse srcPath as GitHub URL: {srcPath}", srcPath); - } - - return srcPath; - } - } \ No newline at end of file From 973b79f78d6c63ec4f38fb4eb30500c3823bee9a Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Mon, 1 Dec 2025 17:19:54 +0900 Subject: [PATCH 42/47] feat(awesome-azd): Add logic to change srcPath to ownerRepo before return and fix mcp.stdio.container.json --- awesome-azd/.vscode/mcp.stdio.container.json | 2 +- .../Tools/AwesomeAzdTool.cs | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/awesome-azd/.vscode/mcp.stdio.container.json b/awesome-azd/.vscode/mcp.stdio.container.json index d11f82e6..f8d0eeac 100644 --- a/awesome-azd/.vscode/mcp.stdio.container.json +++ b/awesome-azd/.vscode/mcp.stdio.container.json @@ -7,7 +7,7 @@ "run", "-i", "--rm", - "ghcr.io/microsoft/mcp-dotnet-samples/awesome-azd:latest" + "awesome-azd:latest" ] } } diff --git a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs index a355ce03..e99e868f 100644 --- a/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs +++ b/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp/Tools/AwesomeAzdTool.cs @@ -1,4 +1,5 @@ using System.ComponentModel; +using System.Text.RegularExpressions; using McpSamples.AwesomeAzd.HybridApp.Services; using McpSamples.AwesomeAzd.HybridApp.Models; @@ -46,7 +47,7 @@ public async Task<List<AwesomeAzdTemplateResponse>> GetTemplateListAsync( [Description("The keyword to search templates for")] string keywords) { var result = new List<AwesomeAzdTemplateResponse>(); - + try { var templates = await service.GetTemplateListAsync(keywords).ConfigureAwait(false); @@ -75,10 +76,17 @@ public Task<AzdCommand> CreateCommandAsync( [Description("Working directory where the command would run")] string workingDirectory, [Description("Name of the environment to apply")] string envName) { - - var command = $"azd init -t {srcPath} --environment {envName}"; + string ownerRepo = srcPath; + + var match = Regex.Match(srcPath, @"github\.com/([^/]+/[^/]+)"); + if (match.Success) + { + ownerRepo = match.Groups[1].Value; + } + + var command = $"azd init -t {ownerRepo} --environment {envName}"; - logger.LogInformation("Generated AzdCommand for srcPath '{srcPath}' at directory '{workingDirectory}'", srcPath, workingDirectory); + logger.LogInformation("Generated AzdCommand for ownerRepo '{ownerRepo}' at directory '{workingDirectory}'", ownerRepo, workingDirectory); var azdCommand = new AzdCommand { From 1b2eab4d9f713bb2ffe881c8a778dedd4e3eda40 Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Mon, 1 Dec 2025 20:14:12 +0900 Subject: [PATCH 43/47] feat(awesome-azd): Add a Dockerfile and a Docker file for Azure --- Dockerfile.awesome-azd | 26 ++++++++++++++++++++++++++ Dockerfile.awesome-azd-azure | 20 ++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 Dockerfile.awesome-azd create mode 100644 Dockerfile.awesome-azd-azure diff --git a/Dockerfile.awesome-azd b/Dockerfile.awesome-azd new file mode 100644 index 00000000..cb4e4adf --- /dev/null +++ b/Dockerfile.awesome-azd @@ -0,0 +1,26 @@ +# syntax=docker/dockerfile:1 + +FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build + +COPY ./shared/McpSamples.Shared /source/shared/McpSamples.Shared +COPY ./awesome-azd/src/McpSamples.AwesomeAzd.HybridApp /source/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp + +WORKDIR /source/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp + +ARG TARGETARCH +RUN case "$TARGETARCH" in \ + "amd64") RID="linux-musl-x64" ;; \ + "arm64") RID="linux-musl-arm64" ;; \ + *) RID="linux-musl-x64" ;; \ + esac && \ + dotnet publish -c Release -o /app -r $RID --self-contained false + +FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final + +WORKDIR /app + +COPY --from=build /app . + +USER $APP_UID + +ENTRYPOINT ["dotnet", "McpSamples.AwesomeAzd.HybridApp.dll"] diff --git a/Dockerfile.awesome-azd-azure b/Dockerfile.awesome-azd-azure new file mode 100644 index 00000000..e9a74c84 --- /dev/null +++ b/Dockerfile.awesome-azd-azure @@ -0,0 +1,20 @@ +# syntax=docker/dockerfile:1 + +FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build + +COPY ./shared/McpSamples.Shared /source/shared/McpSamples.Shared +COPY ./awesome-azd/src/McpSamples.AwesomeAzd.HybridApp /source/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp + +WORKDIR /source/awesome-azd/src/McpSamples.AwesomeAzd.HybridApp + +RUN dotnet publish -c Release -o /app --self-contained false + +FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final + +WORKDIR /app + +COPY --from=build /app . + +USER $APP_UID + +ENTRYPOINT ["dotnet", "McpSamples.AwesomeAzd.HybridApp.dll"] From 28f73adc6cfbe264eda2cf0e4a75f809f71abee7 Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Mon, 1 Dec 2025 20:34:14 +0900 Subject: [PATCH 44/47] feat(awesome-azd): Add McpAwesomeAzd.sln --- awesome-azd/McpAwesomeAzd.sln | 53 +++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 awesome-azd/McpAwesomeAzd.sln diff --git a/awesome-azd/McpAwesomeAzd.sln b/awesome-azd/McpAwesomeAzd.sln new file mode 100644 index 00000000..69a72dba --- /dev/null +++ b/awesome-azd/McpAwesomeAzd.sln @@ -0,0 +1,53 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "McpSamples.Shared", "..\shared\McpSamples.Shared\McpSamples.Shared.csproj", "{1A5BAB62-0AA5-4A38-B2FC-262A3E03C382}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "McpSamples.AwesomeAzd.HybridApp", "src\McpSamples.AwesomeAzd.HybridApp\McpSamples.AwesomeAzd.HybridApp.csproj", "{268B3C6A-8A50-4B8F-A74C-194E3875786C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A5BAB62-0AA5-4A38-B2FC-262A3E03C382}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A5BAB62-0AA5-4A38-B2FC-262A3E03C382}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A5BAB62-0AA5-4A38-B2FC-262A3E03C382}.Debug|x64.ActiveCfg = Debug|Any CPU + {1A5BAB62-0AA5-4A38-B2FC-262A3E03C382}.Debug|x64.Build.0 = Debug|Any CPU + {1A5BAB62-0AA5-4A38-B2FC-262A3E03C382}.Debug|x86.ActiveCfg = Debug|Any CPU + {1A5BAB62-0AA5-4A38-B2FC-262A3E03C382}.Debug|x86.Build.0 = Debug|Any CPU + {1A5BAB62-0AA5-4A38-B2FC-262A3E03C382}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A5BAB62-0AA5-4A38-B2FC-262A3E03C382}.Release|Any CPU.Build.0 = Release|Any CPU + {1A5BAB62-0AA5-4A38-B2FC-262A3E03C382}.Release|x64.ActiveCfg = Release|Any CPU + {1A5BAB62-0AA5-4A38-B2FC-262A3E03C382}.Release|x64.Build.0 = Release|Any CPU + {1A5BAB62-0AA5-4A38-B2FC-262A3E03C382}.Release|x86.ActiveCfg = Release|Any CPU + {1A5BAB62-0AA5-4A38-B2FC-262A3E03C382}.Release|x86.Build.0 = Release|Any CPU + {268B3C6A-8A50-4B8F-A74C-194E3875786C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {268B3C6A-8A50-4B8F-A74C-194E3875786C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {268B3C6A-8A50-4B8F-A74C-194E3875786C}.Debug|x64.ActiveCfg = Debug|Any CPU + {268B3C6A-8A50-4B8F-A74C-194E3875786C}.Debug|x64.Build.0 = Debug|Any CPU + {268B3C6A-8A50-4B8F-A74C-194E3875786C}.Debug|x86.ActiveCfg = Debug|Any CPU + {268B3C6A-8A50-4B8F-A74C-194E3875786C}.Debug|x86.Build.0 = Debug|Any CPU + {268B3C6A-8A50-4B8F-A74C-194E3875786C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {268B3C6A-8A50-4B8F-A74C-194E3875786C}.Release|Any CPU.Build.0 = Release|Any CPU + {268B3C6A-8A50-4B8F-A74C-194E3875786C}.Release|x64.ActiveCfg = Release|Any CPU + {268B3C6A-8A50-4B8F-A74C-194E3875786C}.Release|x64.Build.0 = Release|Any CPU + {268B3C6A-8A50-4B8F-A74C-194E3875786C}.Release|x86.ActiveCfg = Release|Any CPU + {268B3C6A-8A50-4B8F-A74C-194E3875786C}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {268B3C6A-8A50-4B8F-A74C-194E3875786C} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + EndGlobalSection +EndGlobal From 79e6b0552ba8d70f8f8156e6a72476502cb32a7d Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Mon, 1 Dec 2025 21:26:48 +0900 Subject: [PATCH 45/47] docs(awesome-azd): update README --- awesome-azd/README.md | 271 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 270 insertions(+), 1 deletion(-) diff --git a/awesome-azd/README.md b/awesome-azd/README.md index 7ec49644..d769d494 100644 --- a/awesome-azd/README.md +++ b/awesome-azd/README.md @@ -1 +1,270 @@ -이 디렉토리는 **Awesome AZD**를 기반으로 MCP 서버를 구현하기 위한 저장소입니다. \ No newline at end of file +# MCP Server: Awesome Azd + +This is an MCP server that provides search functionality for Awesome AZD templates from the [awesome-azd](https://github.com/Azure/awesome-azd) repository. + +## Install + + +## Prerequisites + +- [.NET 9 SDK](https://dotnet.microsoft.com/download/dotnet/9.0) +- [Visual Studio Code](https://code.visualstudio.com/) with + - [C# Dev Kit](https://marketplace.visualstudio.com/items/?itemName=ms-dotnettools.csdevkit) extension +- [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) +- [Azure Developer CLI](https://learn.microsoft.com/azure/developer/azure-developer-cli/install-azd) +- [Docker Desktop](https://docs.docker.com/get-started/get-docker/) + +## What's Included + +Awesome Copilot MCP server includes: + +| Building Block | Name | Description | Usage | +|----------------|--------------------------------|-----------------------------------------------------------------------|-----------------------------------------------| +| Tools | `get_templates` | Searches templates based on keywords in their descriptions. | `#get_templates` | +| Tools | `make_command` | Generates the azd init command to be executed. | `#make_command` | +| Prompts | `get_template_search_prompt` | Get a prompt for searching azd templates. | `/mcp.awesome-azd.get_template_search_prompt` | + +## Getting Started + +- [Getting repository root](#getting-repository-root) +- [Running MCP server](#running-mcp-server) + - [On a local machine](#on-a-local-machine) + - [In a container](#in-a-container) + - [On Azure](#on-azure) +- [Connect MCP server to an MCP host/client](#connect-mcp-server-to-an-mcp-hostclient) + - [VS Code + Agent Mode + Local MCP server](#vs-code--agent-mode--local-mcp-server) + +### Getting repository root + +1. Get the repository root. + + ```bash + # bash/zsh + REPOSITORY_ROOT=$(git rev-parse --show-toplevel) + ``` + + ```powershell + # PowerShell + $REPOSITORY_ROOT = git rev-parse --show-toplevel + ``` + +### Running MCP server + +#### On a local machine + +1. Run the MCP server app. + + ```bash + cd $REPOSITORY_ROOT/awesome-azd + dotnet run --project ./src/McpSamples.AwesomeAzd.HybridApp + ``` + + > Make sure take note the absolute directory path of the `McpSamples.AwesomeAzd.HybridApp` project. + + **Parameters**: + + - `--http`: The switch that indicates to run this MCP server as a streamable HTTP type. When this switch is added, the MCP server URL is `http://localhost:5201`. + + With this parameter, you can run the MCP server like: + + ```bash + dotnet run --project ./src/McpSamples.AwesomeAzd.HybridApp -- --http + ``` + +#### In a container + +1. Build the MCP server app as a container image. + + ```bash + cd $REPOSITORY_ROOT + docker build -f Dockerfile.awesome-azd -t awesome-azd:latest . + ``` + +1. Run the MCP server app in a container. + + ```bash + docker run -i --rm -p 8080:8080 awesome-azd:latest + ``` + + **Parameters**: + + - `--http`: The switch that indicates to run this MCP server as a streamable HTTP type. When this switch is added, the MCP server URL is `http://localhost:8080`. + + With this parameter, you can run the MCP server like: + + ```bash + # use local container image + docker run -i --rm -p 8080:8080 awesome-azd:latest --http + ``` + +#### On Azure + +1. Navigate to the directory. + + ```bash + cd $REPOSITORY_ROOT/awesome-azd + ``` + +1. Login to Azure. + + ```bash + # Login with Azure Developer CLI + azd auth login + ``` + +1. Deploy the MCP server app to Azure. + + ```bash + azd up + ``` + + While provisioning and deploying, you'll be asked to provide subscription ID, location, environment name. + +1. After the deployment is complete, get the information by running the following commands: + + - Azure Container Apps FQDN: + + ```bash + azd env get-value AZURE_RESOURCE_MCP_AWESOME_AZD_FQDN + ``` + +### Connect MCP server to an MCP host/client + +#### VS Code + Agent Mode + Local MCP server + +1. Copy `mcp.json` to the repository root. + + **For locally running MCP server (STDIO):** + + ```bash + mkdir -p $REPOSITORY_ROOT/.vscode + cp $REPOSITORY_ROOT/awesome-azd/.vscode/mcp.stdio.local.json \ + $REPOSITORY_ROOT/.vscode/mcp.json + ``` + + ```powershell + New-Item -Type Directory -Path $REPOSITORY_ROOT/.vscode -Force + Copy-Item -Path $REPOSITORY_ROOT/awesome-azd/.vscode/mcp.stdio.local.json ` + -Destination $REPOSITORY_ROOT/.vscode/mcp.json -Force + ``` + + **For locally running MCP server (HTTP):** + + ```bash + mkdir -p $REPOSITORY_ROOT/.vscode + cp $REPOSITORY_ROOT/awesome-azd/.vscode/mcp.http.local.json \ + $REPOSITORY_ROOT/.vscode/mcp.json + ``` + + ```powershell + New-Item -Type Directory -Path $REPOSITORY_ROOT/.vscode -Force + Copy-Item -Path $REPOSITORY_ROOT/awesome-azd/.vscode/mcp.http.local.json ` + -Destination $REPOSITORY_ROOT/.vscode/mcp.json -Force + ``` + + **For locally running MCP server in a container (STDIO):** + + ```bash + mkdir -p $REPOSITORY_ROOT/.vscode + cp $REPOSITORY_ROOT/awesome-azd/.vscode/mcp.stdio.container.json \ + $REPOSITORY_ROOT/.vscode/mcp.json + ``` + + ```powershell + New-Item -Type Directory -Path $REPOSITORY_ROOT/.vscode -Force + Copy-Item -Path $REPOSITORY_ROOT/awesome-azd/.vscode/mcp.stdio.container.json ` + -Destination $REPOSITORY_ROOT/.vscode/mcp.json -Force + ``` + + **For locally running MCP server in a container (HTTP):** + + ```bash + mkdir -p $REPOSITORY_ROOT/.vscode + cp $REPOSITORY_ROOT/awesome-azd/.vscode/mcp.http.container.json \ + $REPOSITORY_ROOT/.vscode/mcp.json + ``` + + ```powershell + New-Item -Type Directory -Path $REPOSITORY_ROOT/.vscode -Force + Copy-Item -Path $REPOSITORY_ROOT/awesome-azd/.vscode/mcp.http.container.json ` + -Destination $REPOSITORY_ROOT/.vscode/mcp.json -Force + ``` + + **For remotely running MCP server in a container (HTTP):** + + ```bash + mkdir -p $REPOSITORY_ROOT/.vscode + cp $REPOSITORY_ROOT/awesome-azd/.vscode/mcp.http.remote.json \ + $REPOSITORY_ROOT/.vscode/mcp.json + ``` + + ```powershell + New-Item -Type Directory -Path $REPOSITORY_ROOT/.vscode -Force + Copy-Item -Path $REPOSITORY_ROOT/awesome-azd/.vscode/mcp.http.remote.json ` + -Destination $REPOSITORY_ROOT/.vscode/mcp.json -Force + ``` + +1. Open Command Palette by typing `F1` or `Ctrl`+`Shift`+`P` on Windows or `Cmd`+`Shift`+`P` on Mac OS, and search `MCP: List Servers`. +1. Choose `awesome-azd` then click `Start Server`. +1. When prompted, enter one of the following values: + - The absolute directory path of the `McpSamples.AwesomeAzd.HybridApp` project + - The FQDN of Azure Container Apps. +1. Use a prompt by typing `/mcp.awesome-azd.get_template_search_prompt` and enter keywords to search. You'll get a prompt like: + + ```text + Please search all Azure templates related to the keyword `{keyword}`. + + ------------------------------------------------------------ + 1. Search templates + ------------------------------------------------------------ + - Use the `awesome-azd` MCP server. + - Call the `get_templates` tool with the keyword `{keyword}`. + - Return the results as a table with the columns: Title | Description | Source. + - Only include templates whose metadata matches the keyword. + + ------------------------------------------------------------ + 2. Collect user input for executi on and Backend execution + ------------------------------------------------------------ + After displaying the search results, ask the user to provide: + + 1. **Selected Template Source** (must match the Source column; e.g., GitHub URL) + 2. **Working Directory** (optional; leave empty for default/null) + 3. **Environment Name** (optional; leave empty for default/null) + + - If the user provides a **Selected Template Source**, you may proceed to execution immediately. + + **IMPORTANT** - If the user does not provide a working directory or an environment name, keep asking repeatedly until they provide one. + + Example input: + + https://github.com/Azure-Samples/azure-search-openai-demo-csharp ; C:\Users\<UserProfile>\Projects ; AzureEnv + + + - Call the `make_command` MCP tool with a JSON object like this: + + "srcPath": "Selected Template Source", + "workingDirectory", + "envName" + + - The tool will return an `AzdCommand` object. + - Use `run in terminal` to execute it: + + - mkdir "<AzdCommand.WorkingDirectory>" -Force; cd "<AzdCommand.WorkingDirectory>"; <AzdCommand.Command> (Window) + - mkdir -p "<AzdCommand.WorkingDirectory>" && cd "<AzdCommand.WorkingDirectory>" && <AzdCommand.Command> (MacOS, Linux) + + - Once the command is generated, execute it immediately without further confirmation. + - The command will initialize the template in the specified directory and return success status, output, and any errors. + + ``` + +1. After the search results appear, enter the following three values when prompted: + + - **Selected Template Source**: The template URL shown in the search result (Source column) + - **Working Directory**: The directory where the azd template will be initialized + - **Environment Name**: The name of the Azure Developer CLI environment to create or use + + Once you provide these values and confirm, the MCP server will automatically generate + the appropriate `azd init` command and execute it in your terminal. + + +1. Confirm the result. From 106c97fc6c8fbd1959eb224c1583edb15fecdb47 Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Mon, 1 Dec 2025 21:27:15 +0900 Subject: [PATCH 46/47] docs(awesome-azd): update README --- awesome-azd/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awesome-azd/README.md b/awesome-azd/README.md index d769d494..569b9c0e 100644 --- a/awesome-azd/README.md +++ b/awesome-azd/README.md @@ -16,7 +16,7 @@ This is an MCP server that provides search functionality for Awesome AZD templat ## What's Included -Awesome Copilot MCP server includes: +Awesome Azd MCP server includes: | Building Block | Name | Description | Usage | |----------------|--------------------------------|-----------------------------------------------------------------------|-----------------------------------------------| From 64c6749b51e7e7662602018db27590f188f1aa15 Mon Sep 17 00:00:00 2001 From: itanstar <thsdmlwls55@naver.com> Date: Mon, 1 Dec 2025 22:01:10 +0900 Subject: [PATCH 47/47] feat(awesome-azd): change azure docker build local to remote --- awesome-azd/azure.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awesome-azd/azure.yaml b/awesome-azd/azure.yaml index 5725f97f..77087419 100644 --- a/awesome-azd/azure.yaml +++ b/awesome-azd/azure.yaml @@ -13,4 +13,4 @@ services: docker: path: ../../../Dockerfile.awesome-azd-azure context: ../../../ - remoteBuild: false + remoteBuild: true