From 4ff5d5a0edc53800c46a856ca0a7abbe71327269 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 11:54:22 +0000 Subject: [PATCH 1/4] Initial plan From b6a24d352eb1afcfb2eba804f43c6cc41ccdb8ec Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 12:04:21 +0000 Subject: [PATCH 2/4] Add in-memory content support for MCP build and format tools Agent-Logs-Url: https://github.com/Azure/bicep/sessions/7bff4607-7972-41e3-b1a0-fb820beef2eb Co-authored-by: anthony-c-martin <38542602+anthony-c-martin@users.noreply.github.com> --- docs/mcp-tools.md | 5 +- .../BicepCompilerTools.cs | 91 +++++++++++++------ .../BicepCompilerToolsTests.cs | 48 ++++++++++ .../Files/ServerTests/tools.json | 71 ++++++++++----- 4 files changed, 164 insertions(+), 51 deletions(-) diff --git a/docs/mcp-tools.md b/docs/mcp-tools.md index 636c9443f08..c5117c05a27 100644 --- a/docs/mcp-tools.md +++ b/docs/mcp-tools.md @@ -13,8 +13,9 @@ We have built a Bicep MCP server with agentic tools to support Bicep code genera - `get_extension_resource_type_schema`: Gets the schema for a specific extension resource type. Accepts a canonical OCI artifact reference, resource type, and API version. - `list_well_known_extensions`: Lists well-known Bicep extensions (e.g., Microsoft Graph) with their dynamically-discovered version tags from MCR. This is not an exhaustive list; other extensions may exist. Use this to discover extensions and their versions for use with the extension resource type tools. - `list_avm_metadata`: Lists up-to-date metadata for all Azure Verified Modules (AVM). The return value is a newline-separated list of AVM metadata. Each line includes the module name, description, versions, and documentation URI for a specific module. -- `get_bicep_file_diagnostics`: Analyzes a Bicep file (`.bicep`) or Bicep parameters file (`.bicepparam`) and returns all compilation diagnostics including errors, warnings, and informational messages. -- `format_bicep_file`: Formats a Bicep file (`.bicep`) or Bicep parameters file (`.bicepparam`) according to official Bicep formatting standards, respecting `bicepconfig.json` settings. +- `build_bicep`: Compiles a Bicep file (`.bicep`) and returns the generated ARM template plus diagnostics. Accepts either an absolute file path or an in-memory content payload. +- `build_bicepparam`: Compiles a Bicep parameters file (`.bicepparam`) and returns generated parameters/template JSON plus diagnostics. Accepts either an absolute file path or an in-memory content payload. +- `format_bicep_file`: Formats a Bicep file (`.bicep`) or Bicep parameters file (`.bicepparam`) according to official Bicep formatting standards, respecting `bicepconfig.json` settings. Accepts either an absolute file path or an in-memory content payload. - `get_file_references`: Analyzes a Bicep or Bicep parameters file and returns a list of all files it references, including modules, parameter files, and other dependencies. - `decompile_arm_template_file`: Converts an ARM template JSON file into Bicep syntax (`.bicep`). Accepts files with `.json`, `.jsonc`, or `.arm` extensions. - `decompile_arm_parameters_file`: Converts an ARM template parameters JSON file into Bicep parameters syntax (`.bicepparam`). Accepts files with `.json`, `.jsonc`, or `.arm` extensions. diff --git a/src/Bicep.McpServer.Core/BicepCompilerTools.cs b/src/Bicep.McpServer.Core/BicepCompilerTools.cs index 5896065c4b9..0d19bfc2267 100644 --- a/src/Bicep.McpServer.Core/BicepCompilerTools.cs +++ b/src/Bicep.McpServer.Core/BicepCompilerTools.cs @@ -7,6 +7,7 @@ using Bicep.Core.Diagnostics; using Bicep.Core.Extensions; using Bicep.Core.PrettyPrintV2; +using Bicep.Core.Semantics; using Bicep.Core.SourceGraph; using Bicep.IO.Abstraction; using ModelContextProtocol.Server; @@ -17,6 +18,9 @@ namespace Bicep.McpServer.Core; public sealed class BicepCompilerTools( BicepCompiler compiler) { + private static readonly IOUri InMemoryBicepFileUri = new(IOUriScheme.Untitled, null, "/main.bicep"); + private static readonly IOUri InMemoryBicepParamFileUri = new(IOUriScheme.Untitled, null, "/main.bicepparam"); + public record DiagnosticDefinition( [Description("The .bicep or .bicepparam file URI")] Uri FileUri, @@ -69,18 +73,19 @@ Compiles a Bicep file (.bicep) to an ARM template JSON string and returns the re - Obtain the ARM template output for inspection or deployment The compiled ARM template JSON is returned along with any compilation diagnostics (errors, warnings, and informational messages). - The file path must be absolute. If compilation fails due to errors, the Template field will be null. + Provide either an absolute file path or in-memory content. If compilation fails due to errors, the Template field will be null. """)] public async Task BuildBicep( - [Description("The path to the .bicep file")] string filePath) + [Description("The path to the .bicep file. Required if content is not provided.")] string? filePath = null, + [Description("The in-memory .bicep file content. Required if filePath is not provided.")] string? content = null) { - var fileUri = IOUri.FromFilePath(filePath); - if (!fileUri.HasBicepExtension()) - { - throw new ArgumentException("The specified file must have a .bicep extension.", nameof(filePath)); - } - - var compilation = await compiler.CreateCompilation(fileUri); + var compilation = await CreateCompilation( + filePath, + content, + InMemoryBicepFileUri, + fileUri => fileUri.HasBicepExtension(), + "The specified file must have a .bicep extension.", + (fileUri, fileContent) => compiler.SourceFileFactory.CreateBicepFile(fileUri, fileContent)); var result = compilation.Emitter.Template(); return new BuildBicepResult(result.Success, result.Template, GetDiagnostics(result.Diagnostics)); @@ -96,18 +101,19 @@ Compiles a Bicep parameters file (.bicepparam) to a parameters JSON string and r - Obtain the parameters JSON output for inspection or deployment The compiled parameters JSON and the associated ARM template JSON are returned along with any compilation diagnostics (errors, warnings, and informational messages). - The file path must be absolute. If compilation fails due to errors, the Parameters field will be null. + Provide either an absolute file path or in-memory content. If compilation fails due to errors, the Parameters field will be null. """)] public async Task BuildBicepparam( - [Description("The path to the .bicepparam file")] string filePath) + [Description("The path to the .bicepparam file. Required if content is not provided.")] string? filePath = null, + [Description("The in-memory .bicepparam file content. Required if filePath is not provided.")] string? content = null) { - var fileUri = IOUri.FromFilePath(filePath); - if (!fileUri.HasBicepParamExtension()) - { - throw new ArgumentException("The specified file must have a .bicepparam extension.", nameof(filePath)); - } - - var compilation = await compiler.CreateCompilation(fileUri); + var compilation = await CreateCompilation( + filePath, + content, + InMemoryBicepParamFileUri, + fileUri => fileUri.HasBicepParamExtension(), + "The specified file must have a .bicepparam extension.", + (fileUri, fileContent) => compiler.SourceFileFactory.CreateBicepParamFile(fileUri, fileContent)); var result = compilation.Emitter.Parameters(); return new BuildBicepparamResult(result.Success, result.Parameters, result.Template?.Template, GetDiagnostics(result.Diagnostics)); @@ -127,18 +133,19 @@ Formats a Bicep file (.bicep) or Bicep parameters file (.bicepparam) according t - Newline character (LF, CRLF, CR) - Whether to insert a final newline - The file path must be absolute. Formatting preserves semantic meaning and only changes whitespace and layout. Files with syntax errors will still be formatted to the extent possible. + Provide either an absolute file path or in-memory content. Formatting preserves semantic meaning and only changes whitespace and layout. Files with syntax errors will still be formatted to the extent possible. """)] public async Task FormatBicepFile( - [Description("The path to the .bicep or .bicepparam file")] string filePath) + [Description("The path to the .bicep or .bicepparam file. Required if content is not provided.")] string? filePath = null, + [Description("The in-memory .bicep or .bicepparam file content. Required if filePath is not provided.")] string? content = null) { - var fileUri = IOUri.FromFilePath(filePath); - if (!fileUri.HasBicepExtension() && !fileUri.HasBicepParamExtension()) - { - throw new ArgumentException("The specified file must have a .bicep or .bicepparam extension.", nameof(filePath)); - } - - var compilation = await compiler.CreateCompilation(fileUri); + var compilation = await CreateCompilation( + filePath, + content, + InMemoryBicepFileUri, + fileUri => fileUri.HasBicepExtension() || fileUri.HasBicepParamExtension(), + "The specified file must have a .bicep or .bicepparam extension.", + (fileUri, fileContent) => compiler.SourceFileFactory.CreateSourceFile(fileUri, fileContent)); var sourceFile = compilation.GetEntrypointSemanticModel().SourceFile; var options = sourceFile.Configuration.Formatting.Data; @@ -198,4 +205,34 @@ private static ImmutableArray GetDiagnostics(ImmutableDict x.Span.Position, x.Span.Length)))]; } + + private async Task CreateCompilation( + string? filePath, + string? content, + IOUri inMemoryFileUri, + Func hasExpectedExtension, + string invalidExtensionError, + Func sourceFileFactory) + { + if (filePath is null && content is null) + { + throw new ArgumentException("Either filePath or content must be provided."); + } + + var fileUri = filePath is null ? inMemoryFileUri : IOUri.FromFilePath(filePath); + if (!hasExpectedExtension(fileUri)) + { + throw new ArgumentException(invalidExtensionError, nameof(filePath)); + } + + if (content is null) + { + return await compiler.CreateCompilation(fileUri); + } + + var workspace = new ActiveSourceFileSet(); + workspace.UpsertSourceFile(sourceFileFactory(fileUri, content)); + + return await compiler.CreateCompilation(fileUri, workspace); + } } diff --git a/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs b/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs index a185d10618e..f45ba67c0d9 100644 --- a/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs +++ b/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs @@ -38,6 +38,16 @@ param foo string response.Content.Should().Contain("param foo string"); } + [TestMethod] + public async Task FormatBicepFile_formats_in_memory_bicep_content() + { + var response = await tools.FormatBicepFile(content: """ + param foo string + """); + + response.Content.Should().Contain("param foo string"); + } + [TestMethod] public async Task GetFileReferences_returns_referenced_files() { @@ -82,6 +92,20 @@ public async Task BuildBicep_returns_compiled_template() response.Diagnostics.Should().NotContain(x => x.Level == "Error"); } + [TestMethod] + public async Task BuildBicep_returns_compiled_template_for_in_memory_content() + { + var response = await tools.BuildBicep(content: """ + param location string = 'westus' + output loc string = location + """); + + response.Success.Should().BeTrue(); + response.Template.Should().NotBeNullOrEmpty(); + response.Template.Should().Contain("\"$schema\""); + response.Diagnostics.Should().NotContain(x => x.Level == "Error"); + } + [TestMethod] public async Task BuildBicep_returns_diagnostics_on_error() { @@ -123,6 +147,30 @@ param location string response.Diagnostics.Should().NotContain(x => x.Level == "Error"); } + [TestMethod] + public async Task BuildBicepparam_returns_compiled_parameters_for_in_memory_content() + { + var outputFolder = FileHelper.SaveResultFiles(TestContext, [ + new("main.bicep", """ + param location string + output loc string = location + """), + ]); + + var response = await tools.BuildBicepparam( + Path.Combine(outputFolder, "main.bicepparam"), + """ + using 'main.bicep' + + param location = 'westus' + """); + + response.Success.Should().BeTrue(); + response.Parameters.Should().NotBeNullOrEmpty(); + response.Template.Should().NotBeNullOrEmpty(); + response.Diagnostics.Should().NotContain(x => x.Level == "Error"); + } + [TestMethod] public async Task BuildBicepparam_returns_diagnostics_on_error() { diff --git a/src/Bicep.McpServer.UnitTests/Files/ServerTests/tools.json b/src/Bicep.McpServer.UnitTests/Files/ServerTests/tools.json index 1e761711de4..9d0ece43324 100644 --- a/src/Bicep.McpServer.UnitTests/Files/ServerTests/tools.json +++ b/src/Bicep.McpServer.UnitTests/Files/ServerTests/tools.json @@ -1,18 +1,27 @@ [ { "name": "build_bicep", - "description": "Compiles a Bicep file (.bicep) to an ARM template JSON string and returns the result along with any diagnostics.\n\nUse this tool to:\n- Compile Bicep source code to ARM template JSON\n- Check for compilation errors before deployment\n- Obtain the ARM template output for inspection or deployment\n\nThe compiled ARM template JSON is returned along with any compilation diagnostics (errors, warnings, and informational messages).\nThe file path must be absolute. If compilation fails due to errors, the Template field will be null.", + "description": "Compiles a Bicep file (.bicep) to an ARM template JSON string and returns the result along with any diagnostics.\n\nUse this tool to:\n- Compile Bicep source code to ARM template JSON\n- Check for compilation errors before deployment\n- Obtain the ARM template output for inspection or deployment\n\nThe compiled ARM template JSON is returned along with any compilation diagnostics (errors, warnings, and informational messages).\nProvide either an absolute file path or in-memory content. If compilation fails due to errors, the Template field will be null.", "jsonSchema": { "type": "object", "properties": { "filePath": { - "description": "The path to the .bicep file", - "type": "string" + "description": "The path to the .bicep file. Required if content is not provided.", + "type": [ + "string", + "null" + ], + "default": null + }, + "content": { + "description": "The in-memory .bicep file content. Required if filePath is not provided.", + "type": [ + "string", + "null" + ], + "default": null } - }, - "required": [ - "filePath" - ] + } }, "returnJsonSchema": { "type": "object", @@ -89,18 +98,27 @@ }, { "name": "build_bicepparam", - "description": "Compiles a Bicep parameters file (.bicepparam) to a parameters JSON string and returns the result along with any diagnostics.\n\nUse this tool to:\n- Compile Bicep parameters source code to ARM parameters JSON\n- Check for compilation errors before deployment\n- Obtain the parameters JSON output for inspection or deployment\n\nThe compiled parameters JSON and the associated ARM template JSON are returned along with any compilation diagnostics (errors, warnings, and informational messages).\nThe file path must be absolute. If compilation fails due to errors, the Parameters field will be null.", + "description": "Compiles a Bicep parameters file (.bicepparam) to a parameters JSON string and returns the result along with any diagnostics.\n\nUse this tool to:\n- Compile Bicep parameters source code to ARM parameters JSON\n- Check for compilation errors before deployment\n- Obtain the parameters JSON output for inspection or deployment\n\nThe compiled parameters JSON and the associated ARM template JSON are returned along with any compilation diagnostics (errors, warnings, and informational messages).\nProvide either an absolute file path or in-memory content. If compilation fails due to errors, the Parameters field will be null.", "jsonSchema": { "type": "object", "properties": { "filePath": { - "description": "The path to the .bicepparam file", - "type": "string" + "description": "The path to the .bicepparam file. Required if content is not provided.", + "type": [ + "string", + "null" + ], + "default": null + }, + "content": { + "description": "The in-memory .bicepparam file content. Required if filePath is not provided.", + "type": [ + "string", + "null" + ], + "default": null } - }, - "required": [ - "filePath" - ] + } }, "returnJsonSchema": { "type": "object", @@ -259,18 +277,27 @@ }, { "name": "format_bicep_file", - "description": "Formats a Bicep file (.bicep) or Bicep parameters file (.bicepparam) according to official Bicep formatting standards.\n\nUse this tool to:\n- Apply consistent code formatting (indentation, spacing, line breaks) to Bicep files\n- Clean up manually edited or generated Bicep code before saving\n- Ensure code follows team formatting conventions\n\nThe formatter respects configuration settings from bicepconfig.json if present in the file's directory hierarchy, including:\n- Indentation style (spaces vs tabs) and size\n- Newline character (LF, CRLF, CR)\n- Whether to insert a final newline\n\nThe file path must be absolute. Formatting preserves semantic meaning and only changes whitespace and layout. Files with syntax errors will still be formatted to the extent possible.", + "description": "Formats a Bicep file (.bicep) or Bicep parameters file (.bicepparam) according to official Bicep formatting standards.\n\nUse this tool to:\n- Apply consistent code formatting (indentation, spacing, line breaks) to Bicep files\n- Clean up manually edited or generated Bicep code before saving\n- Ensure code follows team formatting conventions\n\nThe formatter respects configuration settings from bicepconfig.json if present in the file's directory hierarchy, including:\n- Indentation style (spaces vs tabs) and size\n- Newline character (LF, CRLF, CR)\n- Whether to insert a final newline\n\nProvide either an absolute file path or in-memory content. Formatting preserves semantic meaning and only changes whitespace and layout. Files with syntax errors will still be formatted to the extent possible.", "jsonSchema": { "type": "object", "properties": { "filePath": { - "description": "The path to the .bicep or .bicepparam file", - "type": "string" + "description": "The path to the .bicep or .bicepparam file. Required if content is not provided.", + "type": [ + "string", + "null" + ], + "default": null + }, + "content": { + "description": "The in-memory .bicep or .bicepparam file content. Required if filePath is not provided.", + "type": [ + "string", + "null" + ], + "default": null } - }, - "required": [ - "filePath" - ] + } }, "returnJsonSchema": { "type": "object", @@ -674,4 +701,4 @@ ] } } -] \ No newline at end of file +] From 0f77c262ac907e1a89ab8cee0e05c5a7d25f6668 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 12:08:19 +0000 Subject: [PATCH 3/4] Refine in-memory MCP compiler inputs and schema docs Agent-Logs-Url: https://github.com/Azure/bicep/sessions/7bff4607-7972-41e3-b1a0-fb820beef2eb Co-authored-by: anthony-c-martin <38542602+anthony-c-martin@users.noreply.github.com> --- docs/mcp-tools.md | 6 ++--- .../BicepCompilerTools.cs | 22 +++++++++---------- .../BicepCompilerToolsTests.cs | 13 +++++++++++ .../Files/ServerTests/tools.json | 18 +++++++-------- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/docs/mcp-tools.md b/docs/mcp-tools.md index c5117c05a27..29c8a8876f5 100644 --- a/docs/mcp-tools.md +++ b/docs/mcp-tools.md @@ -13,9 +13,9 @@ We have built a Bicep MCP server with agentic tools to support Bicep code genera - `get_extension_resource_type_schema`: Gets the schema for a specific extension resource type. Accepts a canonical OCI artifact reference, resource type, and API version. - `list_well_known_extensions`: Lists well-known Bicep extensions (e.g., Microsoft Graph) with their dynamically-discovered version tags from MCR. This is not an exhaustive list; other extensions may exist. Use this to discover extensions and their versions for use with the extension resource type tools. - `list_avm_metadata`: Lists up-to-date metadata for all Azure Verified Modules (AVM). The return value is a newline-separated list of AVM metadata. Each line includes the module name, description, versions, and documentation URI for a specific module. -- `build_bicep`: Compiles a Bicep file (`.bicep`) and returns the generated ARM template plus diagnostics. Accepts either an absolute file path or an in-memory content payload. -- `build_bicepparam`: Compiles a Bicep parameters file (`.bicepparam`) and returns generated parameters/template JSON plus diagnostics. Accepts either an absolute file path or an in-memory content payload. -- `format_bicep_file`: Formats a Bicep file (`.bicep`) or Bicep parameters file (`.bicepparam`) according to official Bicep formatting standards, respecting `bicepconfig.json` settings. Accepts either an absolute file path or an in-memory content payload. +- `build_bicep`: Compiles a Bicep file (`.bicep`) and returns the generated ARM template plus diagnostics. Accepts an absolute file path, an in-memory content payload, or both. +- `build_bicepparam`: Compiles a Bicep parameters file (`.bicepparam`) and returns generated parameters/template JSON plus diagnostics. Accepts an absolute file path, an in-memory content payload, or both. +- `format_bicep_file`: Formats a Bicep file (`.bicep`) or Bicep parameters file (`.bicepparam`) according to official Bicep formatting standards, respecting `bicepconfig.json` settings. Accepts an absolute file path, an in-memory content payload, or both. - `get_file_references`: Analyzes a Bicep or Bicep parameters file and returns a list of all files it references, including modules, parameter files, and other dependencies. - `decompile_arm_template_file`: Converts an ARM template JSON file into Bicep syntax (`.bicep`). Accepts files with `.json`, `.jsonc`, or `.arm` extensions. - `decompile_arm_parameters_file`: Converts an ARM template parameters JSON file into Bicep parameters syntax (`.bicepparam`). Accepts files with `.json`, `.jsonc`, or `.arm` extensions. diff --git a/src/Bicep.McpServer.Core/BicepCompilerTools.cs b/src/Bicep.McpServer.Core/BicepCompilerTools.cs index 0d19bfc2267..84d22a2cbde 100644 --- a/src/Bicep.McpServer.Core/BicepCompilerTools.cs +++ b/src/Bicep.McpServer.Core/BicepCompilerTools.cs @@ -73,11 +73,11 @@ Compiles a Bicep file (.bicep) to an ARM template JSON string and returns the re - Obtain the ARM template output for inspection or deployment The compiled ARM template JSON is returned along with any compilation diagnostics (errors, warnings, and informational messages). - Provide either an absolute file path or in-memory content. If compilation fails due to errors, the Template field will be null. + Provide an absolute file path, in-memory content, or both. If content is provided, it is compiled in-memory and filePath (if provided) is used as the entrypoint URI context. If compilation fails due to errors, the Template field will be null. """)] public async Task BuildBicep( - [Description("The path to the .bicep file. Required if content is not provided.")] string? filePath = null, - [Description("The in-memory .bicep file content. Required if filePath is not provided.")] string? content = null) + [Description("The path to the .bicep file. Required if content is not provided; when content is provided, this sets URI context.")] string? filePath = null, + [Description("The in-memory .bicep file content. If provided, this content is compiled instead of reading from disk.")] string? content = null) { var compilation = await CreateCompilation( filePath, @@ -101,11 +101,11 @@ Compiles a Bicep parameters file (.bicepparam) to a parameters JSON string and r - Obtain the parameters JSON output for inspection or deployment The compiled parameters JSON and the associated ARM template JSON are returned along with any compilation diagnostics (errors, warnings, and informational messages). - Provide either an absolute file path or in-memory content. If compilation fails due to errors, the Parameters field will be null. + Provide an absolute file path, in-memory content, or both. If content is provided, it is compiled in-memory and filePath (if provided) is used as the entrypoint URI context. If compilation fails due to errors, the Parameters field will be null. """)] public async Task BuildBicepparam( - [Description("The path to the .bicepparam file. Required if content is not provided.")] string? filePath = null, - [Description("The in-memory .bicepparam file content. Required if filePath is not provided.")] string? content = null) + [Description("The path to the .bicepparam file. Required if content is not provided; when content is provided, this sets URI context.")] string? filePath = null, + [Description("The in-memory .bicepparam file content. If provided, this content is compiled instead of reading from disk.")] string? content = null) { var compilation = await CreateCompilation( filePath, @@ -133,11 +133,11 @@ Formats a Bicep file (.bicep) or Bicep parameters file (.bicepparam) according t - Newline character (LF, CRLF, CR) - Whether to insert a final newline - Provide either an absolute file path or in-memory content. Formatting preserves semantic meaning and only changes whitespace and layout. Files with syntax errors will still be formatted to the extent possible. + Provide an absolute file path, in-memory content, or both. If content is provided, it is formatted in-memory and filePath (if provided) is used as the entrypoint URI context. Formatting preserves semantic meaning and only changes whitespace and layout. Files with syntax errors will still be formatted to the extent possible. """)] public async Task FormatBicepFile( - [Description("The path to the .bicep or .bicepparam file. Required if content is not provided.")] string? filePath = null, - [Description("The in-memory .bicep or .bicepparam file content. Required if filePath is not provided.")] string? content = null) + [Description("The path to the .bicep or .bicepparam file. Required if content is not provided; when content is provided, this sets URI context.")] string? filePath = null, + [Description("The in-memory .bicep or .bicepparam file content. If provided, this content is formatted instead of reading from disk.")] string? content = null) { var compilation = await CreateCompilation( filePath, @@ -216,13 +216,13 @@ private async Task CreateCompilation( { if (filePath is null && content is null) { - throw new ArgumentException("Either filePath or content must be provided."); + throw new ArgumentException("Either 'filePath' or 'content' must be provided."); } var fileUri = filePath is null ? inMemoryFileUri : IOUri.FromFilePath(filePath); if (!hasExpectedExtension(fileUri)) { - throw new ArgumentException(invalidExtensionError, nameof(filePath)); + throw new ArgumentException(invalidExtensionError, filePath is null ? nameof(content) : nameof(filePath)); } if (content is null) diff --git a/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs b/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs index f45ba67c0d9..805ac2dd6fa 100644 --- a/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs +++ b/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs @@ -171,6 +171,19 @@ param location string response.Diagnostics.Should().NotContain(x => x.Level == "Error"); } + [TestMethod] + public async Task BuildBicep_compiles_in_memory_content_when_filePath_and_content_are_both_provided() + { + var response = await tools.BuildBicep(filePath: "/main.bicep", content: """ + param location string = 'westus' + output loc string = location + """); + + response.Success.Should().BeTrue(); + response.Template.Should().NotBeNullOrEmpty(); + response.Diagnostics.Should().NotContain(x => x.Level == "Error"); + } + [TestMethod] public async Task BuildBicepparam_returns_diagnostics_on_error() { diff --git a/src/Bicep.McpServer.UnitTests/Files/ServerTests/tools.json b/src/Bicep.McpServer.UnitTests/Files/ServerTests/tools.json index 9d0ece43324..72bc21f64e2 100644 --- a/src/Bicep.McpServer.UnitTests/Files/ServerTests/tools.json +++ b/src/Bicep.McpServer.UnitTests/Files/ServerTests/tools.json @@ -1,12 +1,12 @@ [ { "name": "build_bicep", - "description": "Compiles a Bicep file (.bicep) to an ARM template JSON string and returns the result along with any diagnostics.\n\nUse this tool to:\n- Compile Bicep source code to ARM template JSON\n- Check for compilation errors before deployment\n- Obtain the ARM template output for inspection or deployment\n\nThe compiled ARM template JSON is returned along with any compilation diagnostics (errors, warnings, and informational messages).\nProvide either an absolute file path or in-memory content. If compilation fails due to errors, the Template field will be null.", + "description": "Compiles a Bicep file (.bicep) to an ARM template JSON string and returns the result along with any diagnostics.\n\nUse this tool to:\n- Compile Bicep source code to ARM template JSON\n- Check for compilation errors before deployment\n- Obtain the ARM template output for inspection or deployment\n\nThe compiled ARM template JSON is returned along with any compilation diagnostics (errors, warnings, and informational messages).\nProvide an absolute file path, in-memory content, or both. If content is provided, it is compiled in-memory and filePath (if provided) is used as the entrypoint URI context. If compilation fails due to errors, the Template field will be null.", "jsonSchema": { "type": "object", "properties": { "filePath": { - "description": "The path to the .bicep file. Required if content is not provided.", + "description": "The path to the .bicep file. Required if content is not provided; when content is provided, this sets URI context.", "type": [ "string", "null" @@ -14,7 +14,7 @@ "default": null }, "content": { - "description": "The in-memory .bicep file content. Required if filePath is not provided.", + "description": "The in-memory .bicep file content. If provided, this content is compiled instead of reading from disk.", "type": [ "string", "null" @@ -98,12 +98,12 @@ }, { "name": "build_bicepparam", - "description": "Compiles a Bicep parameters file (.bicepparam) to a parameters JSON string and returns the result along with any diagnostics.\n\nUse this tool to:\n- Compile Bicep parameters source code to ARM parameters JSON\n- Check for compilation errors before deployment\n- Obtain the parameters JSON output for inspection or deployment\n\nThe compiled parameters JSON and the associated ARM template JSON are returned along with any compilation diagnostics (errors, warnings, and informational messages).\nProvide either an absolute file path or in-memory content. If compilation fails due to errors, the Parameters field will be null.", + "description": "Compiles a Bicep parameters file (.bicepparam) to a parameters JSON string and returns the result along with any diagnostics.\n\nUse this tool to:\n- Compile Bicep parameters source code to ARM parameters JSON\n- Check for compilation errors before deployment\n- Obtain the parameters JSON output for inspection or deployment\n\nThe compiled parameters JSON and the associated ARM template JSON are returned along with any compilation diagnostics (errors, warnings, and informational messages).\nProvide an absolute file path, in-memory content, or both. If content is provided, it is compiled in-memory and filePath (if provided) is used as the entrypoint URI context. If compilation fails due to errors, the Parameters field will be null.", "jsonSchema": { "type": "object", "properties": { "filePath": { - "description": "The path to the .bicepparam file. Required if content is not provided.", + "description": "The path to the .bicepparam file. Required if content is not provided; when content is provided, this sets URI context.", "type": [ "string", "null" @@ -111,7 +111,7 @@ "default": null }, "content": { - "description": "The in-memory .bicepparam file content. Required if filePath is not provided.", + "description": "The in-memory .bicepparam file content. If provided, this content is compiled instead of reading from disk.", "type": [ "string", "null" @@ -277,12 +277,12 @@ }, { "name": "format_bicep_file", - "description": "Formats a Bicep file (.bicep) or Bicep parameters file (.bicepparam) according to official Bicep formatting standards.\n\nUse this tool to:\n- Apply consistent code formatting (indentation, spacing, line breaks) to Bicep files\n- Clean up manually edited or generated Bicep code before saving\n- Ensure code follows team formatting conventions\n\nThe formatter respects configuration settings from bicepconfig.json if present in the file's directory hierarchy, including:\n- Indentation style (spaces vs tabs) and size\n- Newline character (LF, CRLF, CR)\n- Whether to insert a final newline\n\nProvide either an absolute file path or in-memory content. Formatting preserves semantic meaning and only changes whitespace and layout. Files with syntax errors will still be formatted to the extent possible.", + "description": "Formats a Bicep file (.bicep) or Bicep parameters file (.bicepparam) according to official Bicep formatting standards.\n\nUse this tool to:\n- Apply consistent code formatting (indentation, spacing, line breaks) to Bicep files\n- Clean up manually edited or generated Bicep code before saving\n- Ensure code follows team formatting conventions\n\nThe formatter respects configuration settings from bicepconfig.json if present in the file's directory hierarchy, including:\n- Indentation style (spaces vs tabs) and size\n- Newline character (LF, CRLF, CR)\n- Whether to insert a final newline\n\nProvide an absolute file path, in-memory content, or both. If content is provided, it is formatted in-memory and filePath (if provided) is used as the entrypoint URI context. Formatting preserves semantic meaning and only changes whitespace and layout. Files with syntax errors will still be formatted to the extent possible.", "jsonSchema": { "type": "object", "properties": { "filePath": { - "description": "The path to the .bicep or .bicepparam file. Required if content is not provided.", + "description": "The path to the .bicep or .bicepparam file. Required if content is not provided; when content is provided, this sets URI context.", "type": [ "string", "null" @@ -290,7 +290,7 @@ "default": null }, "content": { - "description": "The in-memory .bicep or .bicepparam file content. Required if filePath is not provided.", + "description": "The in-memory .bicep or .bicepparam file content. If provided, this content is formatted instead of reading from disk.", "type": [ "string", "null" From f20ba396183286e3ad653808c5d649bd823aa9d2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 May 2026 16:24:02 +0000 Subject: [PATCH 4/4] Enforce exclusive filePath/content MCP inputs Agent-Logs-Url: https://github.com/Azure/bicep/sessions/49eca429-ab6f-46c4-84ca-45462ca10a14 Co-authored-by: anthony-c-martin <38542602+anthony-c-martin@users.noreply.github.com> --- docs/mcp-tools.md | 6 ++-- .../BicepCompilerTools.cs | 17 +++++---- .../BicepCompilerToolsTests.cs | 36 ++++++------------- .../Files/ServerTests/tools.json | 12 +++---- 4 files changed, 30 insertions(+), 41 deletions(-) diff --git a/docs/mcp-tools.md b/docs/mcp-tools.md index 29c8a8876f5..a26b2d43af5 100644 --- a/docs/mcp-tools.md +++ b/docs/mcp-tools.md @@ -13,9 +13,9 @@ We have built a Bicep MCP server with agentic tools to support Bicep code genera - `get_extension_resource_type_schema`: Gets the schema for a specific extension resource type. Accepts a canonical OCI artifact reference, resource type, and API version. - `list_well_known_extensions`: Lists well-known Bicep extensions (e.g., Microsoft Graph) with their dynamically-discovered version tags from MCR. This is not an exhaustive list; other extensions may exist. Use this to discover extensions and their versions for use with the extension resource type tools. - `list_avm_metadata`: Lists up-to-date metadata for all Azure Verified Modules (AVM). The return value is a newline-separated list of AVM metadata. Each line includes the module name, description, versions, and documentation URI for a specific module. -- `build_bicep`: Compiles a Bicep file (`.bicep`) and returns the generated ARM template plus diagnostics. Accepts an absolute file path, an in-memory content payload, or both. -- `build_bicepparam`: Compiles a Bicep parameters file (`.bicepparam`) and returns generated parameters/template JSON plus diagnostics. Accepts an absolute file path, an in-memory content payload, or both. -- `format_bicep_file`: Formats a Bicep file (`.bicep`) or Bicep parameters file (`.bicepparam`) according to official Bicep formatting standards, respecting `bicepconfig.json` settings. Accepts an absolute file path, an in-memory content payload, or both. +- `build_bicep`: Compiles a Bicep file (`.bicep`) and returns the generated ARM template plus diagnostics. Accepts either an absolute file path or an in-memory content payload (but not both). +- `build_bicepparam`: Compiles a Bicep parameters file (`.bicepparam`) and returns generated parameters/template JSON plus diagnostics. Accepts either an absolute file path or an in-memory content payload (but not both). +- `format_bicep_file`: Formats a Bicep file (`.bicep`) or Bicep parameters file (`.bicepparam`) according to official Bicep formatting standards, respecting `bicepconfig.json` settings. Accepts either an absolute file path or an in-memory content payload (but not both). - `get_file_references`: Analyzes a Bicep or Bicep parameters file and returns a list of all files it references, including modules, parameter files, and other dependencies. - `decompile_arm_template_file`: Converts an ARM template JSON file into Bicep syntax (`.bicep`). Accepts files with `.json`, `.jsonc`, or `.arm` extensions. - `decompile_arm_parameters_file`: Converts an ARM template parameters JSON file into Bicep parameters syntax (`.bicepparam`). Accepts files with `.json`, `.jsonc`, or `.arm` extensions. diff --git a/src/Bicep.McpServer.Core/BicepCompilerTools.cs b/src/Bicep.McpServer.Core/BicepCompilerTools.cs index 84d22a2cbde..b40edceca7c 100644 --- a/src/Bicep.McpServer.Core/BicepCompilerTools.cs +++ b/src/Bicep.McpServer.Core/BicepCompilerTools.cs @@ -73,10 +73,10 @@ Compiles a Bicep file (.bicep) to an ARM template JSON string and returns the re - Obtain the ARM template output for inspection or deployment The compiled ARM template JSON is returned along with any compilation diagnostics (errors, warnings, and informational messages). - Provide an absolute file path, in-memory content, or both. If content is provided, it is compiled in-memory and filePath (if provided) is used as the entrypoint URI context. If compilation fails due to errors, the Template field will be null. + Provide either an absolute file path or in-memory content, but not both. If compilation fails due to errors, the Template field will be null. """)] public async Task BuildBicep( - [Description("The path to the .bicep file. Required if content is not provided; when content is provided, this sets URI context.")] string? filePath = null, + [Description("The path to the .bicep file. Required if content is not provided.")] string? filePath = null, [Description("The in-memory .bicep file content. If provided, this content is compiled instead of reading from disk.")] string? content = null) { var compilation = await CreateCompilation( @@ -101,10 +101,10 @@ Compiles a Bicep parameters file (.bicepparam) to a parameters JSON string and r - Obtain the parameters JSON output for inspection or deployment The compiled parameters JSON and the associated ARM template JSON are returned along with any compilation diagnostics (errors, warnings, and informational messages). - Provide an absolute file path, in-memory content, or both. If content is provided, it is compiled in-memory and filePath (if provided) is used as the entrypoint URI context. If compilation fails due to errors, the Parameters field will be null. + Provide either an absolute file path or in-memory content, but not both. If compilation fails due to errors, the Parameters field will be null. """)] public async Task BuildBicepparam( - [Description("The path to the .bicepparam file. Required if content is not provided; when content is provided, this sets URI context.")] string? filePath = null, + [Description("The path to the .bicepparam file. Required if content is not provided.")] string? filePath = null, [Description("The in-memory .bicepparam file content. If provided, this content is compiled instead of reading from disk.")] string? content = null) { var compilation = await CreateCompilation( @@ -133,10 +133,10 @@ Formats a Bicep file (.bicep) or Bicep parameters file (.bicepparam) according t - Newline character (LF, CRLF, CR) - Whether to insert a final newline - Provide an absolute file path, in-memory content, or both. If content is provided, it is formatted in-memory and filePath (if provided) is used as the entrypoint URI context. Formatting preserves semantic meaning and only changes whitespace and layout. Files with syntax errors will still be formatted to the extent possible. + Provide either an absolute file path or in-memory content, but not both. Formatting preserves semantic meaning and only changes whitespace and layout. Files with syntax errors will still be formatted to the extent possible. """)] public async Task FormatBicepFile( - [Description("The path to the .bicep or .bicepparam file. Required if content is not provided; when content is provided, this sets URI context.")] string? filePath = null, + [Description("The path to the .bicep or .bicepparam file. Required if content is not provided.")] string? filePath = null, [Description("The in-memory .bicep or .bicepparam file content. If provided, this content is formatted instead of reading from disk.")] string? content = null) { var compilation = await CreateCompilation( @@ -219,6 +219,11 @@ private async Task CreateCompilation( throw new ArgumentException("Either 'filePath' or 'content' must be provided."); } + if (filePath is not null && content is not null) + { + throw new ArgumentException("'filePath' and 'content' cannot both be provided."); + } + var fileUri = filePath is null ? inMemoryFileUri : IOUri.FromFilePath(filePath); if (!hasExpectedExtension(fileUri)) { diff --git a/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs b/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs index 805ac2dd6fa..20909af34a2 100644 --- a/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs +++ b/src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs @@ -148,40 +148,24 @@ param location string } [TestMethod] - public async Task BuildBicepparam_returns_compiled_parameters_for_in_memory_content() + public async Task BuildBicepparam_returns_diagnostics_for_in_memory_content() { - var outputFolder = FileHelper.SaveResultFiles(TestContext, [ - new("main.bicep", """ - param location string - output loc string = location - """), - ]); - - var response = await tools.BuildBicepparam( - Path.Combine(outputFolder, "main.bicepparam"), - """ - using 'main.bicep' - + var response = await tools.BuildBicepparam(content: """ param location = 'westus' """); - response.Success.Should().BeTrue(); - response.Parameters.Should().NotBeNullOrEmpty(); - response.Template.Should().NotBeNullOrEmpty(); - response.Diagnostics.Should().NotContain(x => x.Level == "Error"); + response.Success.Should().BeFalse(); + response.Parameters.Should().BeNull(); + response.Diagnostics.Should().Contain(x => x.Level == "Error"); } [TestMethod] - public async Task BuildBicep_compiles_in_memory_content_when_filePath_and_content_are_both_provided() + public async Task BuildBicep_throws_when_filePath_and_content_are_both_provided() { - var response = await tools.BuildBicep(filePath: "/main.bicep", content: """ - param location string = 'westus' - output loc string = location - """); - - response.Success.Should().BeTrue(); - response.Template.Should().NotBeNullOrEmpty(); - response.Diagnostics.Should().NotContain(x => x.Level == "Error"); + await FluentActions.Awaiting(() => tools.BuildBicep(filePath: "/main.bicep", content: "param location string = 'westus'")) + .Should() + .ThrowAsync() + .WithMessage("'filePath' and 'content' cannot both be provided."); } [TestMethod] diff --git a/src/Bicep.McpServer.UnitTests/Files/ServerTests/tools.json b/src/Bicep.McpServer.UnitTests/Files/ServerTests/tools.json index 72bc21f64e2..d7b7c70b177 100644 --- a/src/Bicep.McpServer.UnitTests/Files/ServerTests/tools.json +++ b/src/Bicep.McpServer.UnitTests/Files/ServerTests/tools.json @@ -1,12 +1,12 @@ [ { "name": "build_bicep", - "description": "Compiles a Bicep file (.bicep) to an ARM template JSON string and returns the result along with any diagnostics.\n\nUse this tool to:\n- Compile Bicep source code to ARM template JSON\n- Check for compilation errors before deployment\n- Obtain the ARM template output for inspection or deployment\n\nThe compiled ARM template JSON is returned along with any compilation diagnostics (errors, warnings, and informational messages).\nProvide an absolute file path, in-memory content, or both. If content is provided, it is compiled in-memory and filePath (if provided) is used as the entrypoint URI context. If compilation fails due to errors, the Template field will be null.", + "description": "Compiles a Bicep file (.bicep) to an ARM template JSON string and returns the result along with any diagnostics.\n\nUse this tool to:\n- Compile Bicep source code to ARM template JSON\n- Check for compilation errors before deployment\n- Obtain the ARM template output for inspection or deployment\n\nThe compiled ARM template JSON is returned along with any compilation diagnostics (errors, warnings, and informational messages).\nProvide either an absolute file path or in-memory content, but not both. If compilation fails due to errors, the Template field will be null.", "jsonSchema": { "type": "object", "properties": { "filePath": { - "description": "The path to the .bicep file. Required if content is not provided; when content is provided, this sets URI context.", + "description": "The path to the .bicep file. Required if content is not provided.", "type": [ "string", "null" @@ -98,12 +98,12 @@ }, { "name": "build_bicepparam", - "description": "Compiles a Bicep parameters file (.bicepparam) to a parameters JSON string and returns the result along with any diagnostics.\n\nUse this tool to:\n- Compile Bicep parameters source code to ARM parameters JSON\n- Check for compilation errors before deployment\n- Obtain the parameters JSON output for inspection or deployment\n\nThe compiled parameters JSON and the associated ARM template JSON are returned along with any compilation diagnostics (errors, warnings, and informational messages).\nProvide an absolute file path, in-memory content, or both. If content is provided, it is compiled in-memory and filePath (if provided) is used as the entrypoint URI context. If compilation fails due to errors, the Parameters field will be null.", + "description": "Compiles a Bicep parameters file (.bicepparam) to a parameters JSON string and returns the result along with any diagnostics.\n\nUse this tool to:\n- Compile Bicep parameters source code to ARM parameters JSON\n- Check for compilation errors before deployment\n- Obtain the parameters JSON output for inspection or deployment\n\nThe compiled parameters JSON and the associated ARM template JSON are returned along with any compilation diagnostics (errors, warnings, and informational messages).\nProvide either an absolute file path or in-memory content, but not both. If compilation fails due to errors, the Parameters field will be null.", "jsonSchema": { "type": "object", "properties": { "filePath": { - "description": "The path to the .bicepparam file. Required if content is not provided; when content is provided, this sets URI context.", + "description": "The path to the .bicepparam file. Required if content is not provided.", "type": [ "string", "null" @@ -277,12 +277,12 @@ }, { "name": "format_bicep_file", - "description": "Formats a Bicep file (.bicep) or Bicep parameters file (.bicepparam) according to official Bicep formatting standards.\n\nUse this tool to:\n- Apply consistent code formatting (indentation, spacing, line breaks) to Bicep files\n- Clean up manually edited or generated Bicep code before saving\n- Ensure code follows team formatting conventions\n\nThe formatter respects configuration settings from bicepconfig.json if present in the file's directory hierarchy, including:\n- Indentation style (spaces vs tabs) and size\n- Newline character (LF, CRLF, CR)\n- Whether to insert a final newline\n\nProvide an absolute file path, in-memory content, or both. If content is provided, it is formatted in-memory and filePath (if provided) is used as the entrypoint URI context. Formatting preserves semantic meaning and only changes whitespace and layout. Files with syntax errors will still be formatted to the extent possible.", + "description": "Formats a Bicep file (.bicep) or Bicep parameters file (.bicepparam) according to official Bicep formatting standards.\n\nUse this tool to:\n- Apply consistent code formatting (indentation, spacing, line breaks) to Bicep files\n- Clean up manually edited or generated Bicep code before saving\n- Ensure code follows team formatting conventions\n\nThe formatter respects configuration settings from bicepconfig.json if present in the file's directory hierarchy, including:\n- Indentation style (spaces vs tabs) and size\n- Newline character (LF, CRLF, CR)\n- Whether to insert a final newline\n\nProvide either an absolute file path or in-memory content, but not both. Formatting preserves semantic meaning and only changes whitespace and layout. Files with syntax errors will still be formatted to the extent possible.", "jsonSchema": { "type": "object", "properties": { "filePath": { - "description": "The path to the .bicep or .bicepparam file. Required if content is not provided; when content is provided, this sets URI context.", + "description": "The path to the .bicep or .bicepparam file. Required if content is not provided.", "type": [ "string", "null"