Skip to content

Commit 4de774e

Browse files
CopilotAArnott
andcommitted
Add MCP server.json version stamping support
Co-authored-by: AArnott <3548+AArnott@users.noreply.github.com>
1 parent cbccbeb commit 4de774e

File tree

4 files changed

+176
-0
lines changed

4 files changed

+176
-0
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright (c) .NET Foundation and Contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.IO;
6+
using System.Text.Json;
7+
using System.Text.Json.Nodes;
8+
using Microsoft.Build.Framework;
9+
using Microsoft.Build.Utilities;
10+
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
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()
41+
{
42+
try
43+
{
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+
}
49+
50+
if (!File.Exists(this.SourceServerJson))
51+
{
52+
this.Log.LogError($"Source server.json file not found: {this.SourceServerJson}");
53+
return false;
54+
}
55+
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+
}
62+
63+
// Read and parse the server.json file
64+
string jsonContent = File.ReadAllText(this.SourceServerJson);
65+
JsonNode jsonNode = JsonNode.Parse(jsonContent);
66+
67+
if (jsonNode is JsonObject jsonObject)
68+
{
69+
// Stamp the version
70+
jsonObject["version"] = this.Version;
71+
72+
// Write the updated JSON with indentation for readability
73+
var options = new JsonSerializerOptions
74+
{
75+
WriteIndented = true,
76+
};
77+
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+
return true;
83+
}
84+
else
85+
{
86+
this.Log.LogError($"server.json does not contain a valid JSON object: {this.SourceServerJson}");
87+
return false;
88+
}
89+
}
90+
catch (Exception ex)
91+
{
92+
this.Log.LogErrorFromException(ex);
93+
return false;
94+
}
95+
}
96+
}
97+
}

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<UsingTask AssemblyFile="$(NerdbankGitVersioningTasksPath)Nerdbank.GitVersioning.Tasks.dll" TaskName="Nerdbank.GitVersioning.Tasks.NativeVersionInfo"/>
3737
<UsingTask AssemblyFile="$(NerdbankGitVersioningTasksPath)Nerdbank.GitVersioning.Tasks.dll" TaskName="Nerdbank.GitVersioning.Tasks.SetCloudBuildVariables"/>
3838
<UsingTask AssemblyFile="$(NerdbankGitVersioningTasksPath)Nerdbank.GitVersioning.Tasks.dll" TaskName="Nerdbank.GitVersioning.Tasks.CompareFiles"/>
39+
<UsingTask AssemblyFile="$(NerdbankGitVersioningTasksPath)Nerdbank.GitVersioning.Tasks.dll" TaskName="Nerdbank.GitVersioning.Tasks.StampMcpServerJson"/>
3940

4041
<Target Name="NBGV_SetDefaults">
4142
<!-- Workarounds for https://github.com/dotnet/Nerdbank.GitVersioning/issues/404 -->
@@ -310,6 +311,34 @@
310311
</PropertyGroup>
311312
</Target>
312313

314+
<!-- Support for MCP servers: stamp version in server.json -->
315+
<Target Name="NBGV_StampMcpServerJson"
316+
Condition="'$(PackageType)' == 'McpServer'"
317+
BeforeTargets="GenerateNuspec;_GetPackageFiles"
318+
DependsOnTargets="GetBuildVersion">
319+
<ItemGroup>
320+
<_NBGV_OriginalServerJson Include="$(MSBuildProjectDirectory)\server.json" Condition="Exists('$(MSBuildProjectDirectory)\server.json')" />
321+
</ItemGroup>
322+
323+
<PropertyGroup Condition="'@(_NBGV_OriginalServerJson)' != ''">
324+
<_NBGV_StampedServerJsonPath>$(IntermediateOutputPath)server.json</_NBGV_StampedServerJsonPath>
325+
</PropertyGroup>
326+
327+
<!-- Transform server.json with versioned content -->
328+
<Nerdbank.GitVersioning.Tasks.StampMcpServerJson
329+
Condition="'@(_NBGV_OriginalServerJson)' != ''"
330+
SourceServerJson="%(_NBGV_OriginalServerJson.Identity)"
331+
OutputServerJson="$(_NBGV_StampedServerJsonPath)"
332+
Version="$(Version)" />
333+
334+
<!-- Remove original server.json from packaging and add stamped version -->
335+
<ItemGroup Condition="'$(_NBGV_StampedServerJsonPath)' != ''">
336+
<Content Remove="server.json" />
337+
<None Remove="server.json" />
338+
<Content Include="$(_NBGV_StampedServerJsonPath)" PackagePath="server.json" Pack="true" />
339+
</ItemGroup>
340+
</Target>
341+
313342
<!-- Workaround till https://github.com/NuGet/NuGet.Client/issues/1064 is merged and used. -->
314343
<Target Name="_NBGV_CalculateNuSpecVersionHelper"
315344
BeforeTargets="GenerateNuspec"

test/Nerdbank.GitVersioning.Tests/BuildIntegrationManagedTests.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
// Copyright (c) .NET Foundation and Contributors. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System.Text.Json.Nodes;
5+
using Microsoft.Build.Construction;
6+
using Microsoft.Build.Framework;
47
using Nerdbank.GitVersioning;
58
using Xunit;
69

@@ -15,6 +18,52 @@ public BuildIntegrationManagedTests(ITestOutputHelper logger)
1518
{
1619
}
1720

21+
/// <summary>
22+
/// Verifies that MCP server.json files get version stamping when PackageType=McpServer.
23+
/// </summary>
24+
[Fact]
25+
public async Task McpServerJson_VersionStamping()
26+
{
27+
// Create a sample server.json file
28+
string serverJsonContent = @"{
29+
""name"": ""test-mcp-server"",
30+
""version"": ""0.0.0"",
31+
""description"": ""Test MCP server"",
32+
""runtime"": ""dotnet""
33+
}";
34+
35+
string serverJsonPath = Path.Combine(this.projectDirectory, "server.json");
36+
File.WriteAllText(serverJsonPath, serverJsonContent);
37+
38+
// Set PackageType to McpServer
39+
ProjectPropertyGroupElement propertyGroup = this.testProject.CreatePropertyGroupElement();
40+
this.testProject.AppendChild(propertyGroup);
41+
propertyGroup.AddProperty("PackageType", "McpServer");
42+
43+
this.WriteVersionFile();
44+
BuildResults result = await this.BuildAsync("NBGV_StampMcpServerJson", logVerbosity: LoggerVerbosity.Detailed);
45+
46+
// Verify the build succeeded
47+
Assert.Empty(result.LoggedEvents.OfType<BuildErrorEventArgs>());
48+
49+
// Verify the stamped server.json was created
50+
string stampedServerJsonPath = Path.Combine(this.projectDirectory, result.BuildResult.ProjectStateAfterBuild.GetPropertyValue("IntermediateOutputPath"), "server.json");
51+
Assert.True(File.Exists(stampedServerJsonPath), $"Expected stamped server.json at: {stampedServerJsonPath}");
52+
53+
// Verify the version was correctly stamped
54+
string stampedContent = File.ReadAllText(stampedServerJsonPath);
55+
var stampedJson = JsonNode.Parse(stampedContent) as JsonObject;
56+
Assert.NotNull(stampedJson);
57+
58+
string expectedVersion = result.BuildResult.ProjectStateAfterBuild.GetPropertyValue("Version");
59+
Assert.Equal(expectedVersion, stampedJson["version"]?.ToString());
60+
61+
// 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());
65+
}
66+
1867
protected override GitContext CreateGitContext(string path, string committish = null)
1968
=> GitContext.Create(path, committish, GitContext.Engine.ReadOnly);
2069

test/Nerdbank.GitVersioning.Tests/BuildIntegrationTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Globalization;
66
using System.Reflection;
77
using System.Text;
8+
using System.Text.Json.Nodes;
89
using System.Xml;
910
using Microsoft.Build.Construction;
1011
using Microsoft.Build.Evaluation;

0 commit comments

Comments
 (0)