diff --git a/tools/Azure.Mcp.Tools.Functions/src/Models/SupportedLanguages.cs b/tools/Azure.Mcp.Tools.Functions/src/Models/SupportedLanguages.cs index 39ed8b01fe..f375080d25 100644 --- a/tools/Azure.Mcp.Tools.Functions/src/Models/SupportedLanguages.cs +++ b/tools/Azure.Mcp.Tools.Functions/src/Models/SupportedLanguages.cs @@ -10,5 +10,6 @@ public enum SupportedLanguages JavaScript, Java, CSharp, - PowerShell + PowerShell, + Go } diff --git a/tools/Azure.Mcp.Tools.Functions/src/Services/FunctionsService.cs b/tools/Azure.Mcp.Tools.Functions/src/Services/FunctionsService.cs index f80587f2e7..61b124e645 100644 --- a/tools/Azure.Mcp.Tools.Functions/src/Services/FunctionsService.cs +++ b/tools/Azure.Mcp.Tools.Functions/src/Services/FunctionsService.cs @@ -57,7 +57,7 @@ public sealed class FunctionsService( Merge ProjectFiles carefully with existing project files: - **local.settings.json**: Merge "Values" entries, don't overwrite existing connection strings - **host.json**: Keep existing extensionBundle settings, merge other sections - - **requirements.txt / package.json / pom.xml**: Add new dependencies, avoid duplicates + - **requirements.txt / package.json / pom.xml / go.mod / go.sum**: Add new dependencies, avoid duplicates If anything conflicts, ask the user before overwriting. Function files placement by language: @@ -65,6 +65,7 @@ public sealed class FunctionsService( - TypeScript/JavaScript: Place files in `src/functions/` - Java: Place files in `src/main/java/com/function/` - C#: Place files in the project root alongside the .csproj + - Go: Place `.go` files in the project root """; public async Task GetLanguageListAsync(CancellationToken cancellationToken = default) diff --git a/tools/Azure.Mcp.Tools.Functions/src/Services/LanguageMetadataProvider.cs b/tools/Azure.Mcp.Tools.Functions/src/Services/LanguageMetadataProvider.cs index 86b2fc0bf2..7d84bdc047 100644 --- a/tools/Azure.Mcp.Tools.Functions/src/Services/LanguageMetadataProvider.cs +++ b/tools/Azure.Mcp.Tools.Functions/src/Services/LanguageMetadataProvider.cs @@ -39,7 +39,8 @@ public sealed class LanguageMetadataProvider : ILanguageMetadataProvider ["javascript"] = "JavaScript", ["java"] = "Java", ["csharp"] = "CSharp", - ["powershell"] = "PowerShell" + ["powershell"] = "PowerShell", + ["go"] = "Go" }; /// @@ -310,6 +311,47 @@ func start ], TemplateParameterName = null, RecommendationNotes = null + }, + ["go"] = new LanguageInfoStatic + { + Name = "Go", + Runtime = "native", + ProgrammingModel = "Native Go worker SDK", + Prerequisites = ["Go 1.24+", "Azure Functions Core Tools v4.12+"], + DevelopmentTools = ["VS Code with Go + Azure Functions extensions", "Azure Functions Core Tools"], + InitCommand = "go mod init ", + RunCommand = "func start", + BuildCommand = "go build ./...", + ProjectFiles = ["go.mod", "go.sum"], + InitInstructions = """ + ## Go Azure Functions Project Setup + + 1. Initialize the Go module: + ```bash + go mod init + ``` + + 2. Add or merge the template's dependencies into `go.mod` + + 3. Build and run locally: + ```bash + go build ./... + func start + ``` + """, + ProjectStructure = + [ + "*.go # Go source files", + "go.mod # Go module definition and dependencies", + "go.sum # Go dependency checksums", + "host.json # Azure Functions host configuration", + "local.settings.json # Local development settings (do not commit)", + "README.md # Project documentation", + ".gitignore # Git ignore patterns", + ".funcignore # Files to exclude from deployment" + ], + TemplateParameterName = null, + RecommendationNotes = null } }; @@ -324,7 +366,8 @@ func start ["javascript"] = new RuntimeVersionInfo { Supported = ["20"], Default = "20" }, ["java"] = new RuntimeVersionInfo { Supported = ["21"], Default = "21" }, ["csharp"] = new RuntimeVersionInfo { Supported = ["8"], Default = "8" }, - ["powershell"] = new RuntimeVersionInfo { Supported = ["7.4"], Default = "7.4" } + ["powershell"] = new RuntimeVersionInfo { Supported = ["7.4"], Default = "7.4" }, + ["go"] = new RuntimeVersionInfo { Supported = [], Preview = ["1.0"], Default = "1.0" } }; /// diff --git a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/FunctionsCommandTests.cs b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/FunctionsCommandTests.cs index bb2442edcb..40f32547df 100644 --- a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/FunctionsCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/FunctionsCommandTests.cs @@ -25,7 +25,7 @@ namespace Azure.Mcp.Tools.Functions.Tests; public class FunctionsCommandTests(ITestOutputHelper output, TestProxyFixture fixture, LiveServerFixture liveServerFixture) : RecordedCommandTestsBase(output, fixture, liveServerFixture) { - private static readonly string[] s_expectedLanguages = ["python", "typescript", "javascript", "csharp", "java", "powershell"]; + private static readonly string[] s_expectedLanguages = ["python", "typescript", "javascript", "csharp", "java", "powershell", "go"]; /// /// Disable default sanitizer additions since Functions tests don't have @@ -71,8 +71,8 @@ public async Task ExecuteAsync_ReturnsAllSupportedLanguages() Assert.NotEmpty(languageList.FunctionsRuntimeVersion); Assert.NotEmpty(languageList.ExtensionBundleVersion); - // Verify all 6 expected languages are present - Assert.Equal(6, languageList.Languages.Count); + // Verify all 7 expected languages are present + Assert.Equal(7, languageList.Languages.Count); var languageNames = languageList.Languages.Select(l => l.Language).ToList(); foreach (var expected in s_expectedLanguages) { @@ -95,7 +95,8 @@ public async Task ExecuteAsync_ReturnsCorrectLanguageInfo_AllLanguages() ["javascript"] = ("Node.js - JavaScript", "node", "v4 (Schema-based)"), ["java"] = ("Java", "java", "Annotations-based"), ["csharp"] = ("dotnet-isolated - C#", "dotnet", "Isolated worker process"), - ["powershell"] = ("PowerShell", "powershell", "Script-based") + ["powershell"] = ("PowerShell", "powershell", "Script-based"), + ["go"] = ("Go", "native", "Native Go worker SDK") }; foreach (var (languageKey, expected) in expectations) @@ -126,16 +127,19 @@ public async Task ExecuteAsync_ReturnsRuntimeVersions_AllLanguages() var language = GetLanguage(languageList, languageKey); Assert.NotNull(language.RuntimeVersions); - Assert.NotEmpty(language.RuntimeVersions.Supported); Assert.NotEmpty(language.RuntimeVersions.Default); - // Default should be one of the supported versions - Assert.Contains(language.RuntimeVersions.Default, language.RuntimeVersions.Supported); + var availableVersions = language.RuntimeVersions.Supported + .Concat(language.RuntimeVersions.Preview ?? []) + .ToList(); + Assert.NotEmpty(availableVersions); + Assert.Contains(language.RuntimeVersions.Default, availableVersions); // Same versions should be in Info.RuntimeVersions Assert.NotNull(language.Info.RuntimeVersions); Assert.Equal(language.RuntimeVersions.Default, language.Info.RuntimeVersions.Default); Assert.Equal(language.RuntimeVersions.Supported, language.Info.RuntimeVersions.Supported); + Assert.Equal(language.RuntimeVersions.Preview, language.Info.RuntimeVersions.Preview); } } @@ -207,8 +211,8 @@ public async Task ExecuteAsync_LanguagesWithoutTemplateParameters() { var languageList = await GetLanguageListAsync(); - // Python, C#, and PowerShell don't have template parameters - var languagesWithoutParams = new[] { "python", "csharp", "powershell" }; + // Python, C#, PowerShell, and Go don't have template parameters + var languagesWithoutParams = new[] { "python", "csharp", "powershell", "go" }; foreach (var languageKey in languagesWithoutParams) { var language = GetLanguage(languageList, languageKey); diff --git a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Language/LanguageListCommandTests.cs b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Language/LanguageListCommandTests.cs index 80ccb684d5..407992c550 100644 --- a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Language/LanguageListCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Language/LanguageListCommandTests.cs @@ -164,7 +164,8 @@ public async Task ExecuteAsync_DeserializationValidation() ["JavaScript"] = new RuntimeVersionInfo { Supported = ["20", "22"], Preview = ["24"], Default = "22" }, ["Java"] = new RuntimeVersionInfo { Supported = ["8", "11", "17", "21"], Preview = ["25"], Default = "21" }, ["CSharp"] = new RuntimeVersionInfo { Supported = ["8", "9", "10"], FrameworkSupported = ["4.8.1"], Default = "8" }, - ["PowerShell"] = new RuntimeVersionInfo { Supported = ["7.4"], Default = "7.4" } + ["PowerShell"] = new RuntimeVersionInfo { Supported = ["7.4"], Default = "7.4" }, + ["Go"] = new RuntimeVersionInfo { Supported = [], Preview = ["1.0"], Default = "1.0" } } }; mockManifestService.FetchManifestAsync(Arg.Any()).Returns(manifest); @@ -188,7 +189,7 @@ public async Task ExecuteAsync_DeserializationValidation() var result = results[0]; Assert.Equal("4.x", result.FunctionsRuntimeVersion); - Assert.Equal(6, result.Languages.Count); + Assert.Equal(7, result.Languages.Count); // Verify all expected languages are present var languageNames = result.Languages.Select(l => l.Language).ToList(); @@ -198,5 +199,6 @@ public async Task ExecuteAsync_DeserializationValidation() Assert.Contains("java", languageNames); Assert.Contains("csharp", languageNames); Assert.Contains("powershell", languageNames); + Assert.Contains("go", languageNames); } } diff --git a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Project/ProjectGetCommandTests.cs b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Project/ProjectGetCommandTests.cs index 1c897292e7..802d3abac0 100644 --- a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Project/ProjectGetCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Project/ProjectGetCommandTests.cs @@ -156,6 +156,7 @@ public async Task ExecuteAsync_DeserializationValidation() [InlineData(SupportedLanguages.CSharp)] [InlineData(SupportedLanguages.JavaScript)] [InlineData(SupportedLanguages.PowerShell)] + [InlineData(SupportedLanguages.Go)] public async Task ExecuteAsync_ReturnsTemplateForAllLanguages(SupportedLanguages language) { // Arrange - use representative mocked data per language diff --git a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Services/FunctionsServiceHttpTests.cs b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Services/FunctionsServiceHttpTests.cs index 26a20b3c5f..af13c7de49 100644 --- a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Services/FunctionsServiceHttpTests.cs +++ b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Services/FunctionsServiceHttpTests.cs @@ -269,6 +269,41 @@ public async Task FetchManifestAsync_UsesPrimary_WhenPrimarySucceeds() #region FetchTemplateFilesViaArchiveAsync Tests + [Fact] + public async Task GetFunctionTemplateAsync_GoAddMode_IncludesGoMergeGuidance() + { + var zipBytes = CreateTestZipArchive(new Dictionary + { + ["Azure-repo-abc123/main.go"] = "package main", + ["Azure-repo-abc123/go.mod"] = "module example.com/functions", + ["Azure-repo-abc123/go.sum"] = "", + ["Azure-repo-abc123/host.json"] = "{\"version\": \"2.0\"}" + }); + var handler = new MockHttpMessageHandler(zipBytes, HttpStatusCode.OK); + var httpClientFactory = CreateHttpClientFactory(handler); + var manifest = new TemplateManifest + { + Version = "1.0", + Templates = [CreateTestEntry("go", ".", "http-trigger-go-azd")] + }; + _manifestService.FetchManifestAsync(Arg.Any()).Returns(manifest); + var service = CreateService(httpClientFactory); + + var result = await service.GetFunctionTemplateAsync( + SupportedLanguages.Go, + "http-trigger-go-azd", + null, + TemplateOutput.Add, + CancellationToken.None); + + Assert.Contains("Go: Place `.go` files in the project root", result.MergeInstructions); + Assert.NotNull(result.FunctionFiles); + Assert.NotNull(result.ProjectFiles); + Assert.Contains(result.FunctionFiles, file => file.FileName == "main.go"); + Assert.Contains(result.ProjectFiles, file => file.FileName == "go.mod"); + Assert.Contains(result.ProjectFiles, file => file.FileName == "go.sum"); + } + [Fact] public async Task FetchTemplateFilesViaArchiveAsync_ExtractsFiles_FromZipball() { diff --git a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Services/FunctionsServiceTests.cs b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Services/FunctionsServiceTests.cs index 3953d67b1b..a52ef3e8f1 100644 --- a/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Services/FunctionsServiceTests.cs +++ b/tools/Azure.Mcp.Tools.Functions/tests/Azure.Mcp.Tools.Functions.Tests/Services/FunctionsServiceTests.cs @@ -10,6 +10,38 @@ namespace Azure.Mcp.Tools.Functions.Tests.Services; public sealed class FunctionsServiceTests { + [Fact] + public void LanguageMetadataProvider_Go_ReturnsNativeWorkerMetadata() + { + var provider = new LanguageMetadataProvider(); + var runtimeVersions = new Dictionary + { + ["Go"] = new() { Supported = [], Preview = ["1.0"], Default = "1.0" } + }; + + var result = provider.GetLanguageInfo("go", runtimeVersions); + + Assert.NotNull(result); + Assert.Equal("Go", result.Name); + Assert.Equal("native", result.Runtime); + Assert.Equal("Native Go worker SDK", result.ProgrammingModel); + Assert.Equal("go mod init ", result.InitCommand); + Assert.Equal("go build ./...", result.BuildCommand); + Assert.Contains("go.mod", result.ProjectFiles); + Assert.Contains("go.sum", result.ProjectFiles); + Assert.Equal("1.0", result.RuntimeVersions.Default); + Assert.Contains("1.0", result.RuntimeVersions.Preview!); + } + + [Fact] + public void LanguageMetadataProvider_KnownProjectFiles_IncludesGoModuleFiles() + { + var provider = new LanguageMetadataProvider(); + + Assert.Contains("go.mod", provider.KnownProjectFiles); + Assert.Contains("go.sum", provider.KnownProjectFiles); + } + #region BuildRawGitHubUrl Tests [Theory]