Skip to content

Commit 3107359

Browse files
Add build_bicep and build_bicepparam tools to the MCP server (#19602)
The MCP server lacked dedicated tools for compiling Bicep files and returning the compiled output. This PR replaces `get_bicep_file_diagnostics` with two dedicated build tools that both compile and return results. ## Changes - **Two new MCP tools** in `BicepCompilerTools`: - `build_bicep` — compiles a `.bicep` file via `compilation.Emitter.Template()`, returns `{ success, template, diagnostics }` - `build_bicepparam` — compiles a `.bicepparam` file via `compilation.Emitter.Parameters()`, returns `{ success, parameters, template, diagnostics }` - **Removed `get_bicep_file_diagnostics`** — superseded by the new build tools, which already include diagnostics in their responses - **Shared helper** `GetDiagnostics()` extracted to avoid duplicating the diagnostic projection logic - **New result records** `BuildBicepResult` and `BuildBicepparamResult` with inline `[Description]` annotations for MCP schema generation - **Unit tests** for both tools covering successful compilation and error cases - **`tools.json` baseline** updated to reflect the tool changes ## Example ```bicep // main.bicep param location string = 'westus' output loc string = location ``` Calling `build_bicep` returns: ```json { "success": true, "template": "{ \"$schema\": \"...\", ... }", "diagnostics": [] } ``` On error, `success` is `false`, `template`/`parameters` is `null`, and `diagnostics` contains the full error list. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: anthony-c-martin <38542602+anthony-c-martin@users.noreply.github.com>
1 parent a442118 commit 3107359

5 files changed

Lines changed: 339 additions & 123 deletions

File tree

global.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
"version": "10.0.203",
88
"rollForward": "latestPatch"
99
}
10-
}
10+
}

src/Bicep.McpServer.Core/BicepCompilerTools.cs

Lines changed: 74 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
using System.Collections.Immutable;
55
using System.ComponentModel;
66
using Bicep.Core;
7+
using Bicep.Core.Diagnostics;
78
using Bicep.Core.Extensions;
89
using Bicep.Core.PrettyPrintV2;
10+
using Bicep.Core.SourceGraph;
911
using Bicep.IO.Abstraction;
1012
using ModelContextProtocol.Server;
1113

@@ -39,40 +41,76 @@ public record GetFileReferencesResult(
3941
[Description("The list of files being referenced by the specified Bicep or Bicep parameters file")]
4042
ImmutableArray<Uri> FileUris);
4143

42-
[McpServerTool(Title = "Get Bicep File Diagnostics", Destructive = false, Idempotent = true, OpenWorld = true, ReadOnly = true, UseStructuredContent = true)]
44+
public record BuildBicepResult(
45+
[Description("Whether the compilation succeeded without errors")]
46+
bool Success,
47+
[Description("The compiled ARM template JSON, or null if compilation failed")]
48+
string? Template,
49+
[Description("The list of diagnostics from compilation")]
50+
ImmutableArray<DiagnosticDefinition> Diagnostics);
51+
52+
public record BuildBicepparamResult(
53+
[Description("Whether the compilation succeeded without errors")]
54+
bool Success,
55+
[Description("The compiled parameters JSON, or null if compilation failed")]
56+
string? Parameters,
57+
[Description("The compiled ARM template JSON, or null if not applicable or compilation failed")]
58+
string? Template,
59+
[Description("The list of diagnostics from compilation")]
60+
ImmutableArray<DiagnosticDefinition> Diagnostics);
61+
62+
[McpServerTool(Title = "Build Bicep", Destructive = false, Idempotent = true, OpenWorld = true, ReadOnly = true, UseStructuredContent = true)]
4363
[Description("""
44-
Analyzes a Bicep file (.bicep) or Bicep parameters file (.bicepparam) and returns all compilation diagnostics including errors, warnings, and informational messages.
45-
64+
Compiles a Bicep file (.bicep) to an ARM template JSON string and returns the result along with any diagnostics.
65+
4666
Use this tool to:
47-
- Validate Bicep syntax and identify compilation errors before deployment
48-
- Check for warnings about deprecated features, security issues, or best practice violations
49-
- Troubleshoot why a Bicep file isn't compiling
50-
- Understand the severity and location of issues in the code
51-
52-
Each diagnostic includes the error code (e.g., BCP033), severity level (Error/Warning/Info), descriptive message, exact position in the file, and a link to documentation for more details.
53-
The file path must be absolute. Diagnostics are returned for the specified file and any modules it references.
67+
- Compile Bicep source code to ARM template JSON
68+
- Check for compilation errors before deployment
69+
- Obtain the ARM template output for inspection or deployment
70+
71+
The compiled ARM template JSON is returned along with any compilation diagnostics (errors, warnings, and informational messages).
72+
The file path must be absolute. If compilation fails due to errors, the Template field will be null.
5473
""")]
55-
public async Task<ImmutableArray<DiagnosticDefinition>> GetBicepFileDiagnostics(
56-
[Description("The path to the .bicep or .bicepparam file")] string filePath)
74+
public async Task<BuildBicepResult> BuildBicep(
75+
[Description("The path to the .bicep file")] string filePath)
5776
{
5877
var fileUri = IOUri.FromFilePath(filePath);
59-
if (!fileUri.HasBicepExtension() && !fileUri.HasBicepParamExtension())
78+
if (!fileUri.HasBicepExtension())
6079
{
61-
throw new ArgumentException("The specified file must have a .bicep or .bicepparam extension.", nameof(filePath));
80+
throw new ArgumentException("The specified file must have a .bicep extension.", nameof(filePath));
81+
}
82+
83+
var compilation = await compiler.CreateCompilation(fileUri);
84+
var result = compilation.Emitter.Template();
85+
86+
return new BuildBicepResult(result.Success, result.Template, GetDiagnostics(result.Diagnostics));
87+
}
88+
89+
[McpServerTool(Title = "Build Bicep Parameters", Destructive = false, Idempotent = true, OpenWorld = true, ReadOnly = true, UseStructuredContent = true)]
90+
[Description("""
91+
Compiles a Bicep parameters file (.bicepparam) to a parameters JSON string and returns the result along with any diagnostics.
92+
93+
Use this tool to:
94+
- Compile Bicep parameters source code to ARM parameters JSON
95+
- Check for compilation errors before deployment
96+
- Obtain the parameters JSON output for inspection or deployment
97+
98+
The compiled parameters JSON and the associated ARM template JSON are returned along with any compilation diagnostics (errors, warnings, and informational messages).
99+
The file path must be absolute. If compilation fails due to errors, the Parameters field will be null.
100+
""")]
101+
public async Task<BuildBicepparamResult> BuildBicepparam(
102+
[Description("The path to the .bicepparam file")] string filePath)
103+
{
104+
var fileUri = IOUri.FromFilePath(filePath);
105+
if (!fileUri.HasBicepParamExtension())
106+
{
107+
throw new ArgumentException("The specified file must have a .bicepparam extension.", nameof(filePath));
62108
}
63109

64110
var compilation = await compiler.CreateCompilation(fileUri);
111+
var result = compilation.Emitter.Parameters();
65112

66-
return [.. compilation
67-
.GetAllDiagnosticsByBicepFile()
68-
.SelectMany(kvp => kvp.Value.Select(x => new DiagnosticDefinition(
69-
kvp.Key.FileHandle.Uri.ToUri(),
70-
x.Code,
71-
x.Level.ToString(),
72-
x.Message,
73-
x.Uri,
74-
x.Span.Position,
75-
x.Span.Length)))];
113+
return new BuildBicepparamResult(result.Success, result.Parameters, result.Template?.Template, GetDiagnostics(result.Diagnostics));
76114
}
77115

78116
[McpServerTool(Title = "Format Bicep File", Destructive = false, Idempotent = true, OpenWorld = true, ReadOnly = true, UseStructuredContent = true)]
@@ -148,4 +186,16 @@ public async Task<GetFileReferencesResult> GetFileReferences(
148186
return new(
149187
[.. fileUris.Select(x => x.ToUri()).OrderBy(x => x.ToString())]);
150188
}
189+
190+
private static ImmutableArray<DiagnosticDefinition> GetDiagnostics(ImmutableDictionary<BicepSourceFile, ImmutableArray<IDiagnostic>> diagnosticsByFile)
191+
{
192+
return [.. diagnosticsByFile.SelectMany(kvp => kvp.Value.Select(x => new DiagnosticDefinition(
193+
kvp.Key.FileHandle.Uri.ToUri(),
194+
x.Code,
195+
x.Level.ToString(),
196+
x.Message,
197+
x.Uri,
198+
x.Span.Position,
199+
x.Span.Length)))];
200+
}
151201
}

src/Bicep.McpServer.UnitTests/BicepCompilerToolsTests.cs

Lines changed: 79 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
using System.Collections.Immutable;
54
using System.Diagnostics.CodeAnalysis;
6-
using Bicep.Core.UnitTests.Assertions;
7-
using Bicep.Core.UnitTests.Baselines;
85
using Bicep.Core.UnitTests.Utils;
9-
using Bicep.IO.Abstraction;
106
using Bicep.McpServer.Core;
117
using FluentAssertions;
128
using Microsoft.Extensions.DependencyInjection;
@@ -30,25 +26,6 @@ private static IServiceProvider GetServiceProvider()
3026

3127
private readonly BicepCompilerTools tools = GetServiceProvider().GetRequiredService<BicepCompilerTools>();
3228

33-
[TestMethod]
34-
public async Task GetBicepFileDiagnostics_returns_list_of_diagnostics()
35-
{
36-
var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", """
37-
var foo string = 123
38-
""");
39-
40-
var response = await tools.GetBicepFileDiagnostics(bicepFilePath);
41-
42-
response.Should().HaveCountGreaterThanOrEqualTo(2);
43-
var diagnostic = response.Should().ContainSingle(x => x.Code == "BCP033").Subject;
44-
diagnostic.FileUri.Should().Be(IOUri.FromFilePath(bicepFilePath).ToUri());
45-
diagnostic.Level.Should().Be("Error");
46-
diagnostic.Message.Should().Be("Expected a value of type \"string\" but the provided value is of type \"123\".");
47-
diagnostic.Position.Should().Be(17);
48-
diagnostic.Length.Should().Be(3);
49-
diagnostic.DocumentationUri.Should().Be(new Uri("https://aka.ms/bicep/core-diagnostics#BCP033"));
50-
}
51-
5229
[TestMethod]
5330
public async Task FormatBicepFile_returns_formatted_bicep_content()
5431
{
@@ -88,4 +65,83 @@ param location string
8865
"location.txt",
8966
]);
9067
}
68+
69+
[TestMethod]
70+
public async Task BuildBicep_returns_compiled_template()
71+
{
72+
var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", """
73+
param location string = 'westus'
74+
output loc string = location
75+
""");
76+
77+
var response = await tools.BuildBicep(bicepFilePath);
78+
79+
response.Success.Should().BeTrue();
80+
response.Template.Should().NotBeNullOrEmpty();
81+
response.Template.Should().Contain("\"$schema\"");
82+
response.Diagnostics.Should().NotContain(x => x.Level == "Error");
83+
}
84+
85+
[TestMethod]
86+
public async Task BuildBicep_returns_diagnostics_on_error()
87+
{
88+
var bicepFilePath = FileHelper.SaveResultFile(TestContext, "main.bicep", """
89+
var foo string = 123
90+
""");
91+
92+
var response = await tools.BuildBicep(bicepFilePath);
93+
94+
response.Success.Should().BeFalse();
95+
response.Template.Should().BeNull();
96+
response.Diagnostics.Should().HaveCountGreaterThanOrEqualTo(2);
97+
var diagnostic = response.Diagnostics.Should().ContainSingle(x => x.Code == "BCP033").Subject;
98+
diagnostic.Level.Should().Be("Error");
99+
}
100+
101+
[TestMethod]
102+
public async Task BuildBicepparam_returns_compiled_parameters()
103+
{
104+
var outputFolder = FileHelper.SaveResultFiles(TestContext, [
105+
new("main.bicep", """
106+
param location string
107+
output loc string = location
108+
"""),
109+
new("main.bicepparam", """
110+
using 'main.bicep'
111+
112+
param location = 'westus'
113+
"""),
114+
]);
115+
116+
var response = await tools.BuildBicepparam(Path.Combine(outputFolder, "main.bicepparam"));
117+
118+
response.Success.Should().BeTrue();
119+
response.Parameters.Should().NotBeNullOrEmpty();
120+
response.Parameters.Should().Contain("\"$schema\"");
121+
response.Template.Should().NotBeNullOrEmpty();
122+
response.Template.Should().Contain("\"$schema\"");
123+
response.Diagnostics.Should().NotContain(x => x.Level == "Error");
124+
}
125+
126+
[TestMethod]
127+
public async Task BuildBicepparam_returns_diagnostics_on_error()
128+
{
129+
var outputFolder = FileHelper.SaveResultFiles(TestContext, [
130+
new("main.bicep", """
131+
param location string
132+
"""),
133+
new("main.bicepparam", """
134+
using 'main.bicep'
135+
136+
param location = 123
137+
"""),
138+
]);
139+
140+
var response = await tools.BuildBicepparam(Path.Combine(outputFolder, "main.bicepparam"));
141+
142+
response.Success.Should().BeFalse();
143+
response.Parameters.Should().BeNull();
144+
response.Diagnostics.Should().HaveCountGreaterThanOrEqualTo(1);
145+
response.Diagnostics.Should().Contain(x => x.Level == "Error");
146+
}
91147
}

0 commit comments

Comments
 (0)