Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/mcp-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 (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.
Expand Down
96 changes: 69 additions & 27 deletions src/Bicep.McpServer.Core/BicepCompilerTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand Down Expand Up @@ -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, but not both. If compilation fails due to errors, the Template field will be null.
""")]
public async Task<BuildBicepResult> 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. If provided, this content is compiled instead of reading from disk.")] 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));
Expand All @@ -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, but not both. If compilation fails due to errors, the Parameters field will be null.
""")]
public async Task<BuildBicepparamResult> 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. If provided, this content is compiled instead of reading from disk.")] 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));
Expand All @@ -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, 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<FormatResult> 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. If provided, this content is formatted instead of reading from disk.")] 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;
Expand Down Expand Up @@ -198,4 +205,39 @@ private static ImmutableArray<DiagnosticDefinition> GetDiagnostics(ImmutableDict
x.Span.Position,
x.Span.Length)))];
}

private async Task<Compilation> CreateCompilation(
string? filePath,
string? content,
IOUri inMemoryFileUri,
Func<IOUri, bool> hasExpectedExtension,
string invalidExtensionError,
Func<IOUri, string, ISourceFile> sourceFileFactory)
{
if (filePath is null && content is null)
{
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))
{
throw new ArgumentException(invalidExtensionError, filePath is null ? nameof(content) : 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);
}
}
45 changes: 45 additions & 0 deletions src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -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()
{
Expand Down Expand Up @@ -123,6 +147,27 @@ param location string
response.Diagnostics.Should().NotContain(x => x.Level == "Error");
}

[TestMethod]
public async Task BuildBicepparam_returns_diagnostics_for_in_memory_content()
{
var response = await tools.BuildBicepparam(content: """
param location = 'westus'
""");

response.Success.Should().BeFalse();
response.Parameters.Should().BeNull();
response.Diagnostics.Should().Contain(x => x.Level == "Error");
}

[TestMethod]
public async Task BuildBicep_throws_when_filePath_and_content_are_both_provided()
{
await FluentActions.Awaiting(() => tools.BuildBicep(filePath: "/main.bicep", content: "param location string = 'westus'"))
.Should()
.ThrowAsync<ArgumentException>()
.WithMessage("'filePath' and 'content' cannot both be provided.");
}

[TestMethod]
public async Task BuildBicepparam_returns_diagnostics_on_error()
{
Expand Down
71 changes: 49 additions & 22 deletions src/Bicep.McpServer.UnitTests/Files/ServerTests/tools.json
Original file line number Diff line number Diff line change
@@ -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, 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",
"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. If provided, this content is compiled instead of reading from disk.",
"type": [
"string",
"null"
],
"default": null
}
},
"required": [
"filePath"
]
}
},
"returnJsonSchema": {
"type": "object",
Expand Down Expand Up @@ -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, 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",
"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. If provided, this content is compiled instead of reading from disk.",
"type": [
"string",
"null"
],
"default": null
}
},
"required": [
"filePath"
]
}
},
"returnJsonSchema": {
"type": "object",
Expand Down Expand Up @@ -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, 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",
"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. If provided, this content is formatted instead of reading from disk.",
"type": [
"string",
"null"
],
"default": null
}
},
"required": [
"filePath"
]
}
},
"returnJsonSchema": {
"type": "object",
Expand Down Expand Up @@ -674,4 +701,4 @@
]
}
}
]
]