Skip to content

Commit 156ed04

Browse files
committed
feat: enhance MsBuildHelper to locate output assemblies by probing multiple configurations, improving consistency for local and CI runs
1 parent 1b36a57 commit 156ed04

2 files changed

Lines changed: 75 additions & 19 deletions

File tree

DotNetMetadataMcpServer/MsBuildHelper.cs

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,38 +35,84 @@ public MsBuildHelper(ILogger<MsBuildHelper>? logger = null)
3535
}
3636

3737
var project = new Project(csprojPath, null, null, projectCollection);
38-
project.SetProperty("Configuration", configuration);
3938

40-
var assemblyName = project.GetPropertyValue("AssemblyName");
41-
var outputPath = project.GetPropertyValue("OutputPath")
42-
.Replace('\\', Path.DirectorySeparatorChar)
43-
.Replace('/', Path.DirectorySeparatorChar);
44-
45-
var targetFramework = project.GetPropertyValue("TargetFramework");
39+
// Try the requested configuration first, then fall back to the most common alternatives.
40+
var configurationsToTry = new List<string>();
41+
void addIfMissing(string cfg)
42+
{
43+
if (!configurationsToTry.Contains(cfg, StringComparer.OrdinalIgnoreCase))
44+
{
45+
configurationsToTry.Add(cfg);
46+
}
47+
}
4648

47-
if (string.IsNullOrWhiteSpace(assemblyName))
48-
assemblyName = Path.GetFileNameWithoutExtension(csprojPath);
49+
addIfMissing(configuration);
50+
addIfMissing("Release");
51+
addIfMissing("Debug");
4952

50-
if (string.IsNullOrWhiteSpace(outputPath))
51-
outputPath = Path.Combine("bin", configuration);
53+
string? finalAsmPath = null;
54+
string? chosenConfiguration = null;
55+
string assemblyName;
56+
string targetFramework;
5257

5358
var projDir = Path.GetDirectoryName(Path.GetFullPath(csprojPath)) ?? "";
54-
var fullOutputPath = Path.GetFullPath(Path.Combine(projDir, outputPath));
5559

56-
string? finalAsmPath = null;
57-
foreach (var ext in new[] { ".dll", ".exe" })
60+
foreach (var cfg in configurationsToTry)
5861
{
59-
var candidate = Path.Combine(fullOutputPath, assemblyName + ext);
60-
if (File.Exists(candidate))
62+
project.SetProperty("Configuration", cfg);
63+
64+
assemblyName = project.GetPropertyValue("AssemblyName");
65+
if (string.IsNullOrWhiteSpace(assemblyName))
66+
{
67+
assemblyName = Path.GetFileNameWithoutExtension(csprojPath);
68+
}
69+
70+
var outputPath = project.GetPropertyValue("OutputPath")
71+
.Replace('\\', Path.DirectorySeparatorChar)
72+
.Replace('/', Path.DirectorySeparatorChar);
73+
if (string.IsNullOrWhiteSpace(outputPath))
74+
{
75+
outputPath = Path.Combine("bin", cfg);
76+
}
77+
78+
targetFramework = project.GetPropertyValue("TargetFramework");
79+
80+
var fullOutputPath = Path.GetFullPath(Path.Combine(projDir, outputPath));
81+
foreach (var ext in new[] { ".dll", ".exe" })
82+
{
83+
var candidate = Path.Combine(fullOutputPath, assemblyName + ext);
84+
if (File.Exists(candidate))
85+
{
86+
finalAsmPath = candidate;
87+
chosenConfiguration = cfg;
88+
break;
89+
}
90+
}
91+
92+
if (finalAsmPath != null)
6193
{
62-
finalAsmPath = candidate;
6394
break;
6495
}
6596
}
97+
98+
// If not found, fall back to a reasonable default path for the originally requested configuration
99+
assemblyName = project.GetPropertyValue("AssemblyName");
100+
if (string.IsNullOrWhiteSpace(assemblyName))
101+
{
102+
assemblyName = Path.GetFileNameWithoutExtension(csprojPath);
103+
}
104+
targetFramework = project.GetPropertyValue("TargetFramework");
66105
if (finalAsmPath == null)
67106
{
68-
finalAsmPath = Path.Combine(fullOutputPath, assemblyName + ".dll");
69-
_logger.LogWarning("Assembly not found, assume {0}", finalAsmPath);
107+
var fallbackOutput = project.GetPropertyValue("OutputPath");
108+
if (string.IsNullOrWhiteSpace(fallbackOutput))
109+
{
110+
fallbackOutput = Path.Combine("bin", configuration);
111+
}
112+
var fullFallback = Path.GetFullPath(Path.Combine(projDir, fallbackOutput));
113+
finalAsmPath = Path.Combine(fullFallback, assemblyName + ".dll");
114+
_logger.LogWarning("Assembly not found for configurations [{Configs}], assuming {Path}",
115+
string.Join(", ", configurationsToTry), finalAsmPath);
70116
}
71117

72118
// Search for project.assets.json

readme.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ Replace `/path/to/DotNetMetadataMcpServer` with the actual path to the published
8181
- **The project must be built before scanning.** The server relies on compiled assemblies to extract type information, so make sure to build your project before using the tools.
8282
- **The tool doesn't follow references to other projects.** It only inspects the specified project and its NuGet dependencies. If you need to analyze multiple projects, you'll need to scan each one separately.
8383

84+
### How project outputs are located
85+
86+
When scanning a project, the server needs the compiled assembly path. `MsBuildHelper` evaluates the project and attempts to locate the output assembly by probing configurations in this order:
87+
88+
- the requested configuration (by default Debug), then
89+
- Release, and if still not found,
90+
- falls back to checking Debug again as a safe default.
91+
92+
The first existing output wins. If no output is found, a reasonable default path is assumed and a warning is logged. This makes local runs (often Debug) and CI runs (often Release) behave consistently. Ensure you build the project in one of these configurations before scanning.
93+
8494
## Usage
8595

8696
The server provides five main tools that can be used by AI agents:

0 commit comments

Comments
 (0)