Skip to content

Commit 412e26c

Browse files
CopilotAArnott
andcommitted
Address PR feedback: Fix MSBuild conditions, use namespace statements, implement recursive __VERSION__ replacement, and improve tests
Co-authored-by: AArnott <3548+AArnott@users.noreply.github.com>
1 parent 4de774e commit 412e26c

File tree

3 files changed

+168
-77
lines changed

3 files changed

+168
-77
lines changed

src/Nerdbank.GitVersioning.Tasks/StampMcpServerJson.cs

Lines changed: 129 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -8,90 +8,151 @@
88
using Microsoft.Build.Framework;
99
using Microsoft.Build.Utilities;
1010

11-
namespace Nerdbank.GitVersioning.Tasks
11+
namespace Nerdbank.GitVersioning.Tasks;
12+
13+
/// <summary>
14+
/// MSBuild task that stamps version information into an MCP server.json file.
15+
/// </summary>
16+
public class StampMcpServerJson : Microsoft.Build.Utilities.Task
1217
{
1318
/// <summary>
14-
/// MSBuild task that stamps version information into an MCP server.json file.
19+
/// Gets or sets the path to the source server.json file.
1520
/// </summary>
16-
public class StampMcpServerJson : Microsoft.Build.Utilities.Task
17-
{
18-
/// <summary>
19-
/// Gets or sets the path to the source server.json file.
20-
/// </summary>
21-
[Required]
22-
public string SourceServerJson { get; set; }
23-
24-
/// <summary>
25-
/// Gets or sets the path where the stamped server.json file should be written.
26-
/// </summary>
27-
[Required]
28-
public string OutputServerJson { get; set; }
29-
30-
/// <summary>
31-
/// Gets or sets the version to stamp into the server.json file.
32-
/// </summary>
33-
[Required]
34-
public string Version { get; set; }
35-
36-
/// <summary>
37-
/// Executes the task to stamp version information into the MCP server.json file.
38-
/// </summary>
39-
/// <returns><see langword="true"/> if the task succeeded; <see langword="false"/> otherwise.</returns>
40-
public override bool Execute()
21+
[Required]
22+
public string SourceServerJson { get; set; }
23+
24+
/// <summary>
25+
/// Gets or sets the path where the stamped server.json file should be written.
26+
/// </summary>
27+
[Required]
28+
public string OutputServerJson { get; set; }
29+
30+
/// <summary>
31+
/// Gets or sets the version to stamp into the server.json file.
32+
/// </summary>
33+
[Required]
34+
public string Version { get; set; }
35+
36+
/// <summary>
37+
/// Executes the task to stamp version information into the MCP server.json file.
38+
/// </summary>
39+
/// <returns><see langword="true"/> if the task succeeded; <see langword="false"/> otherwise.</returns>
40+
public override bool Execute()
41+
{
42+
try
4143
{
42-
try
44+
if (string.IsNullOrEmpty(this.SourceServerJson) || string.IsNullOrEmpty(this.OutputServerJson) || string.IsNullOrEmpty(this.Version))
4345
{
44-
if (string.IsNullOrEmpty(this.SourceServerJson) || string.IsNullOrEmpty(this.OutputServerJson) || string.IsNullOrEmpty(this.Version))
45-
{
46-
this.Log.LogError("SourceServerJson, OutputServerJson, and Version are required parameters.");
47-
return false;
48-
}
46+
this.Log.LogError("SourceServerJson, OutputServerJson, and Version are required parameters.");
47+
return !this.Log.HasLoggedErrors;
48+
}
4949

50-
if (!File.Exists(this.SourceServerJson))
51-
{
52-
this.Log.LogError($"Source server.json file not found: {this.SourceServerJson}");
53-
return false;
54-
}
50+
if (!File.Exists(this.SourceServerJson))
51+
{
52+
this.Log.LogError($"Source server.json file not found: {this.SourceServerJson}");
53+
return !this.Log.HasLoggedErrors;
54+
}
5555

56-
// Ensure output directory exists
57-
string outputDir = Path.GetDirectoryName(this.OutputServerJson);
58-
if (!string.IsNullOrEmpty(outputDir) && !Directory.Exists(outputDir))
59-
{
60-
Directory.CreateDirectory(outputDir);
61-
}
56+
// Ensure output directory exists
57+
string outputDir = Path.GetDirectoryName(this.OutputServerJson);
58+
if (!string.IsNullOrEmpty(outputDir))
59+
{
60+
Directory.CreateDirectory(outputDir);
61+
}
62+
63+
// Read and parse the server.json file
64+
string jsonContent = File.ReadAllText(this.SourceServerJson);
65+
JsonNode jsonNode = JsonNode.Parse(jsonContent);
6266

63-
// Read and parse the server.json file
64-
string jsonContent = File.ReadAllText(this.SourceServerJson);
65-
JsonNode jsonNode = JsonNode.Parse(jsonContent);
67+
if (jsonNode is JsonObject jsonObject)
68+
{
69+
// Replace all __VERSION__ placeholders in the JSON tree
70+
this.ReplaceVersionPlaceholders(jsonNode, this.Version);
6671

67-
if (jsonNode is JsonObject jsonObject)
72+
// Write the updated JSON with indentation for readability
73+
var options = new JsonSerializerOptions
6874
{
69-
// Stamp the version
70-
jsonObject["version"] = this.Version;
75+
WriteIndented = true,
76+
};
7177

72-
// Write the updated JSON with indentation for readability
73-
var options = new JsonSerializerOptions
74-
{
75-
WriteIndented = true,
76-
};
78+
string updatedJson = JsonSerializer.Serialize(jsonObject, options);
79+
File.WriteAllText(this.OutputServerJson, updatedJson);
80+
81+
this.Log.LogMessage(MessageImportance.Low, $"Stamped version '{this.Version}' into server.json: {this.OutputServerJson}");
82+
}
83+
else
84+
{
85+
this.Log.LogError($"server.json does not contain a valid JSON object: {this.SourceServerJson}");
86+
}
87+
}
88+
catch (Exception ex)
89+
{
90+
this.Log.LogErrorFromException(ex);
91+
}
92+
93+
return !this.Log.HasLoggedErrors;
94+
}
7795

78-
string updatedJson = JsonSerializer.Serialize(jsonObject, options);
79-
File.WriteAllText(this.OutputServerJson, updatedJson);
96+
/// <summary>
97+
/// Recursively walks the JSON tree and replaces any string values containing "__VERSION__" with the actual version.
98+
/// </summary>
99+
/// <param name="node">The JSON node to process.</param>
100+
/// <param name="version">The version string to replace "__VERSION__" with.</param>
101+
private void ReplaceVersionPlaceholders(JsonNode node, string version)
102+
{
103+
switch (node)
104+
{
105+
case JsonObject jsonObject:
106+
foreach (var property in jsonObject.ToArray())
107+
{
108+
if (property.Value != null)
109+
{
110+
this.ReplaceVersionPlaceholders(property.Value, version);
111+
}
112+
}
113+
break;
80114

81-
this.Log.LogMessage(MessageImportance.Low, $"Stamped version '{this.Version}' into server.json: {this.OutputServerJson}");
82-
return true;
115+
case JsonArray jsonArray:
116+
for (int i = 0; i < jsonArray.Count; i++)
117+
{
118+
if (jsonArray[i] != null)
119+
{
120+
this.ReplaceVersionPlaceholders(jsonArray[i], version);
121+
}
83122
}
84-
else
123+
break;
124+
125+
case JsonValue jsonValue:
126+
if (jsonValue.TryGetValue<string>(out string stringValue) && stringValue.Contains("__VERSION__"))
85127
{
86-
this.Log.LogError($"server.json does not contain a valid JSON object: {this.SourceServerJson}");
87-
return false;
128+
string replacedValue = stringValue.Replace("__VERSION__", version);
129+
JsonNode parent = jsonValue.Parent;
130+
if (parent is JsonObject parentObject)
131+
{
132+
// Find the property key for this value
133+
foreach (var kvp in parentObject)
134+
{
135+
if (ReferenceEquals(kvp.Value, jsonValue))
136+
{
137+
parentObject[kvp.Key] = replacedValue;
138+
break;
139+
}
140+
}
141+
}
142+
else if (parent is JsonArray parentArray)
143+
{
144+
// Find the index for this value
145+
for (int i = 0; i < parentArray.Count; i++)
146+
{
147+
if (ReferenceEquals(parentArray[i], jsonValue))
148+
{
149+
parentArray[i] = replacedValue;
150+
break;
151+
}
152+
}
153+
}
88154
}
89-
}
90-
catch (Exception ex)
91-
{
92-
this.Log.LogErrorFromException(ex);
93-
return false;
94-
}
155+
break;
95156
}
96157
}
97158
}

src/Nerdbank.GitVersioning.Tasks/build/Nerdbank.GitVersioning.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@
320320
<_NBGV_OriginalServerJson Include="$(MSBuildProjectDirectory)\server.json" Condition="Exists('$(MSBuildProjectDirectory)\server.json')" />
321321
</ItemGroup>
322322

323-
<PropertyGroup Condition="'@(_NBGV_OriginalServerJson)' != ''">
323+
<PropertyGroup>
324324
<_NBGV_StampedServerJsonPath>$(IntermediateOutputPath)server.json</_NBGV_StampedServerJsonPath>
325325
</PropertyGroup>
326326

test/Nerdbank.GitVersioning.Tests/BuildIntegrationManagedTests.cs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,28 @@ public BuildIntegrationManagedTests(ITestOutputHelper logger)
2424
[Fact]
2525
public async Task McpServerJson_VersionStamping()
2626
{
27-
// Create a sample server.json file
27+
// Create a sample server.json file based on the real MCP server template
2828
string serverJsonContent = @"{
29-
""name"": ""test-mcp-server"",
30-
""version"": ""0.0.0"",
31-
""description"": ""Test MCP server"",
32-
""runtime"": ""dotnet""
29+
""$schema"": ""https://modelcontextprotocol.io/schemas/draft/2025-07-09/server.json"",
30+
""description"": ""Test .NET MCP Server"",
31+
""name"": ""io.github.test/testmcpserver"",
32+
""version"": ""__VERSION__"",
33+
""packages"": [
34+
{
35+
""registry_type"": ""nuget"",
36+
""identifier"": ""Test.McpServer"",
37+
""version"": ""__VERSION__"",
38+
""transport"": {
39+
""type"": ""stdio""
40+
},
41+
""package_arguments"": [],
42+
""environment_variables"": []
43+
}
44+
],
45+
""repository"": {
46+
""url"": ""https://github.com/test/testmcpserver"",
47+
""source"": ""github""
48+
}
3349
}";
3450

3551
string serverJsonPath = Path.Combine(this.projectDirectory, "server.json");
@@ -56,12 +72,26 @@ public async Task McpServerJson_VersionStamping()
5672
Assert.NotNull(stampedJson);
5773

5874
string expectedVersion = result.BuildResult.ProjectStateAfterBuild.GetPropertyValue("Version");
75+
76+
// Verify root version was stamped
5977
Assert.Equal(expectedVersion, stampedJson["version"]?.ToString());
6078

79+
// Verify package version was also stamped
80+
JsonArray packages = stampedJson["packages"]?.AsArray();
81+
Assert.NotNull(packages);
82+
Assert.Single(packages);
83+
84+
JsonObject package = packages[0]?.AsObject();
85+
Assert.NotNull(package);
86+
Assert.Equal(expectedVersion, package["version"]?.ToString());
87+
6188
// Verify other properties were preserved
62-
Assert.Equal("test-mcp-server", stampedJson["name"]?.ToString());
63-
Assert.Equal("Test MCP server", stampedJson["description"]?.ToString());
64-
Assert.Equal("dotnet", stampedJson["runtime"]?.ToString());
89+
Assert.Equal("io.github.test/testmcpserver", stampedJson["name"]?.ToString());
90+
Assert.Equal("Test .NET MCP Server", stampedJson["description"]?.ToString());
91+
Assert.Equal("Test.McpServer", package["identifier"]?.ToString());
92+
93+
// Verify that no __VERSION__ placeholders remain in the entire JSON
94+
Assert.DoesNotContain("__VERSION__", stampedContent);
6595
}
6696

6797
protected override GitContext CreateGitContext(string path, string committish = null)

0 commit comments

Comments
 (0)