Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
97 changes: 87 additions & 10 deletions src/Bicep.McpServer.Core/BicepCompilerTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
using System.Collections.Immutable;
using System.ComponentModel;
using Bicep.Core;
using Bicep.Core.Diagnostics;
using Bicep.Core.Extensions;
using Bicep.Core.PrettyPrintV2;
using Bicep.Core.SourceGraph;
using Bicep.IO.Abstraction;
using ModelContextProtocol.Server;

Expand Down Expand Up @@ -39,6 +41,24 @@ public record GetFileReferencesResult(
[Description("The list of files being referenced by the specified Bicep or Bicep parameters file")]
ImmutableArray<Uri> FileUris);

public record BuildBicepResult(
[Description("Whether the compilation succeeded without errors")]
bool Success,
[Description("The compiled ARM template JSON, or null if compilation failed")]
string? Template,
[Description("The list of diagnostics from compilation")]
ImmutableArray<DiagnosticDefinition> Diagnostics);

public record BuildBicepparamResult(
[Description("Whether the compilation succeeded without errors")]
bool Success,
[Description("The compiled parameters JSON, or null if compilation failed")]
string? Parameters,
[Description("The compiled ARM template JSON, or null if not applicable or compilation failed")]
string? Template,
[Description("The list of diagnostics from compilation")]
ImmutableArray<DiagnosticDefinition> Diagnostics);

[McpServerTool(Title = "Get Bicep File Diagnostics", Destructive = false, Idempotent = true, OpenWorld = true, ReadOnly = true, UseStructuredContent = true)]
[Description("""
Analyzes a Bicep file (.bicep) or Bicep parameters file (.bicepparam) and returns all compilation diagnostics including errors, warnings, and informational messages.
Expand All @@ -63,16 +83,61 @@ public async Task<ImmutableArray<DiagnosticDefinition>> GetBicepFileDiagnostics(

var compilation = await compiler.CreateCompilation(fileUri);

return [.. compilation
.GetAllDiagnosticsByBicepFile()
.SelectMany(kvp => kvp.Value.Select(x => new DiagnosticDefinition(
kvp.Key.FileHandle.Uri.ToUri(),
x.Code,
x.Level.ToString(),
x.Message,
x.Uri,
x.Span.Position,
x.Span.Length)))];
return GetDiagnostics(compilation.GetAllDiagnosticsByBicepFile());
}

[McpServerTool(Title = "Build Bicep", Destructive = false, Idempotent = true, OpenWorld = true, ReadOnly = true, UseStructuredContent = true)]
[Description("""
Compiles a Bicep file (.bicep) to an ARM template JSON string and returns the result along with any diagnostics.

Use this tool to:
- Compile Bicep source code to ARM template JSON
- Check for compilation errors before deployment
- 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.
""")]
public async Task<BuildBicepResult> BuildBicep(
[Description("The path to the .bicep file")] string filePath)
{
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 result = compilation.Emitter.Template();

return new BuildBicepResult(result.Success, result.Template, GetDiagnostics(result.Diagnostics));
}

[McpServerTool(Title = "Build Bicep Parameters", Destructive = false, Idempotent = true, OpenWorld = true, ReadOnly = true, UseStructuredContent = true)]
[Description("""
Compiles a Bicep parameters file (.bicepparam) to a parameters JSON string and returns the result along with any diagnostics.

Use this tool to:
- Compile Bicep parameters source code to ARM parameters JSON
- Check for compilation errors before deployment
- 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.
""")]
public async Task<BuildBicepparamResult> BuildBicepparam(
[Description("The path to the .bicepparam file")] string filePath)
{
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 result = compilation.Emitter.Parameters();

return new BuildBicepparamResult(result.Success, result.Parameters, result.Template?.Template, GetDiagnostics(result.Diagnostics));
}

[McpServerTool(Title = "Format Bicep File", Destructive = false, Idempotent = true, OpenWorld = true, ReadOnly = true, UseStructuredContent = true)]
Expand Down Expand Up @@ -148,4 +213,16 @@ public async Task<GetFileReferencesResult> GetFileReferences(
return new(
[.. fileUris.Select(x => x.ToUri()).OrderBy(x => x.ToString())]);
}

private static ImmutableArray<DiagnosticDefinition> GetDiagnostics(ImmutableDictionary<BicepSourceFile, ImmutableArray<IDiagnostic>> diagnosticsByFile)
{
return [.. diagnosticsByFile.SelectMany(kvp => kvp.Value.Select(x => new DiagnosticDefinition(
kvp.Key.FileHandle.Uri.ToUri(),
x.Code,
x.Level.ToString(),
x.Message,
x.Uri,
x.Span.Position,
x.Span.Length)))];
}
}
79 changes: 79 additions & 0 deletions src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,83 @@ param location string
"location.txt",
]);
}

[TestMethod]
public async Task BuildBicep_returns_compiled_template()
{
var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", """
param location string = 'westus'
output loc string = location
""");

var response = await tools.BuildBicep(bicepFilePath);

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()
{
var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", """
var foo string = 123
""");

var response = await tools.BuildBicep(bicepFilePath);

response.Success.Should().BeFalse();
response.Template.Should().BeNull();
response.Diagnostics.Should().HaveCountGreaterThanOrEqualTo(2);
var diagnostic = response.Diagnostics.Should().ContainSingle(x => x.Code == "BCP033").Subject;
diagnostic.Level.Should().Be("Error");
}

[TestMethod]
public async Task BuildBicepparam_returns_compiled_parameters()
{
var outputFolder = FileHelper.SaveResultFiles(TestContext, [
new("main.bicep", """
param location string
output loc string = location
"""),
new("main.bicepparam", """
using 'main.bicep'

param location = 'westus'
"""),
]);

var response = await tools.BuildBicepparam(Path.Combine(outputFolder, "main.bicepparam"));

response.Success.Should().BeTrue();
response.Parameters.Should().NotBeNullOrEmpty();
response.Parameters.Should().Contain("\"$schema\"");
response.Template.Should().NotBeNullOrEmpty();
response.Template.Should().Contain("\"$schema\"");
response.Diagnostics.Should().NotContain(x => x.Level == "Error");
}

[TestMethod]
public async Task BuildBicepparam_returns_diagnostics_on_error()
{
var outputFolder = FileHelper.SaveResultFiles(TestContext, [
new("main.bicep", """
param location string
"""),
new("main.bicepparam", """
using 'main.bicep'

param location = 123
"""),
]);

var response = await tools.BuildBicepparam(Path.Combine(outputFolder, "main.bicepparam"));

response.Success.Should().BeFalse();
response.Parameters.Should().BeNull();
response.Diagnostics.Should().HaveCountGreaterThanOrEqualTo(1);
response.Diagnostics.Should().Contain(x => x.Level == "Error");
}
}
184 changes: 184 additions & 0 deletions src/Bicep.McpServer.UnitTests/Files/ServerTests/tools.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,188 @@
[
{
"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.",
"jsonSchema": {
"type": "object",
"properties": {
"filePath": {
"description": "The path to the .bicep file",
"type": "string"
}
},
"required": [
"filePath"
]
},
"returnJsonSchema": {
"type": "object",
"properties": {
"success": {
"description": "Whether the compilation succeeded without errors",
"type": "boolean"
},
"template": {
"description": "The compiled ARM template JSON, or null if compilation failed",
"type": [
"string",
"null"
]
},
"diagnostics": {
"description": "The list of diagnostics from compilation",
"type": "array",
"items": {
"type": "object",
"properties": {
"fileUri": {
"description": "The .bicep or .bicepparam file URI",
"type": "string",
"format": "uri"
},
"code": {
"description": "The diagnostic code",
"type": "string"
},
"level": {
"description": "The diagnostic level",
"type": "string"
},
"message": {
"description": "The diagnostic message",
"type": "string"
},
"documentationUri": {
"description": "The documentation URI, if present",
"type": [
"string",
"null"
],
"format": "uri"
},
"position": {
"description": "The character position of the diagnostic in the file, as a zero-based index",
"type": "integer"
},
"length": {
"description": "The character length of the diagnostic",
"type": "integer"
}
},
"required": [
"fileUri",
"code",
"level",
"message",
"documentationUri",
"position",
"length"
]
}
}
},
"required": [
"success",
"template",
"diagnostics"
]
}
},
{
"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.",
"jsonSchema": {
"type": "object",
"properties": {
"filePath": {
"description": "The path to the .bicepparam file",
"type": "string"
}
},
"required": [
"filePath"
]
},
"returnJsonSchema": {
"type": "object",
"properties": {
"success": {
"description": "Whether the compilation succeeded without errors",
"type": "boolean"
},
"parameters": {
"description": "The compiled parameters JSON, or null if compilation failed",
"type": [
"string",
"null"
]
},
"template": {
"description": "The compiled ARM template JSON, or null if not applicable or compilation failed",
"type": [
"string",
"null"
]
},
"diagnostics": {
"description": "The list of diagnostics from compilation",
"type": "array",
"items": {
"type": "object",
"properties": {
"fileUri": {
"description": "The .bicep or .bicepparam file URI",
"type": "string",
"format": "uri"
},
"code": {
"description": "The diagnostic code",
"type": "string"
},
"level": {
"description": "The diagnostic level",
"type": "string"
},
"message": {
"description": "The diagnostic message",
"type": "string"
},
"documentationUri": {
"description": "The documentation URI, if present",
"type": [
"string",
"null"
],
"format": "uri"
},
"position": {
"description": "The character position of the diagnostic in the file, as a zero-based index",
"type": "integer"
},
"length": {
"description": "The character length of the diagnostic",
"type": "integer"
}
},
"required": [
"fileUri",
"code",
"level",
"message",
"documentationUri",
"position",
"length"
]
}
}
},
"required": [
"success",
"parameters",
"template",
"diagnostics"
]
}
},
{
"name": "decompile_arm_parameters_file",
"description": "Converts an ARM template parameters JSON file into Bicep parameters syntax (.bicepparam).\n\nUse this tool to:\n- Migrate ARM JSON parameter files to Bicep parameters format\n- Convert deployment parameter files when modernizing to Bicep\n- Generate .bicepparam files from existing ARM deployments\n\nAccepts files with .json, .jsonc, or .arm extensions. The file path must be absolute.\n\nThe generated .bicepparam file includes a 'using' statement placeholder that must be completed, all parameters with their values preserved, and KeyVault references converted to az.getSecret() function calls if present.",
Expand Down