Skip to content

Commit 2a2409e

Browse files
Copilotstephentoub
andcommitted
Use fully-qualified type names in generated partial method signatures
Changes XmlToDescriptionGenerator to use FullyQualifiedFormat with global:: prefix instead of MinimallyQualifiedFormat when emitting parameter and return types in generated partial declarations. This fixes build failures when MCP tool methods have parameters with types defined in different namespaces (e.g., enums from separate namespace modules). Also includes nullability annotation support via SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier. Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
1 parent 4e42141 commit 2a2409e

2 files changed

Lines changed: 71 additions & 5 deletions

File tree

src/ModelContextProtocol.Analyzers/XmlToDescriptionGenerator.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ public sealed class XmlToDescriptionGenerator : IIncrementalGenerator
1919
{
2020
private const string GeneratedFileName = "ModelContextProtocol.Descriptions.g.cs";
2121

22+
/// <summary>
23+
/// A display format that produces fully-qualified type names with "global::" prefix
24+
/// and includes nullability annotations.
25+
/// </summary>
26+
private static readonly SymbolDisplayFormat FullyQualifiedFormatWithNullability =
27+
SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions(
28+
SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
29+
2230
public void Initialize(IncrementalGeneratorInitializationContext context)
2331
{
2432
// Extract method information for all MCP tools, prompts, and resources.
@@ -125,7 +133,7 @@ private static MethodToGenerate ExtractMethodInfo(
125133
.Where(m => !m.IsKind(SyntaxKind.AsyncKeyword))
126134
.Select(m => m.Text);
127135
string modifiersStr = string.Join(" ", modifiers);
128-
string returnType = methodSymbol.ReturnType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
136+
string returnType = methodSymbol.ReturnType.ToDisplayString(FullyQualifiedFormatWithNullability);
129137
string methodName = methodSymbol.Name;
130138

131139
// Extract parameters
@@ -137,7 +145,7 @@ private static MethodToGenerate ExtractMethodInfo(
137145
var paramSyntax = i < parameterSyntaxList.Count ? parameterSyntaxList[i] : null;
138146

139147
parameters[i] = new ParameterInfo(
140-
ParameterType: param.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat),
148+
ParameterType: param.Type.ToDisplayString(FullyQualifiedFormatWithNullability),
141149
Name: param.Name,
142150
HasDescriptionAttribute: descriptionAttribute is not null && HasAttribute(param, descriptionAttribute),
143151
XmlDescription: xmlDocs?.Parameters.TryGetValue(param.Name, out var pd) == true && !string.IsNullOrWhiteSpace(pd) ? pd : null,

tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1561,7 +1561,7 @@ namespace Test
15611561
partial class TestTools
15621562
{
15631563
[Description("Async tool")]
1564-
public partial Task<string> DoWorkAsync(string input);
1564+
public partial global::System.Threading.Tasks.Task<string> DoWorkAsync(string input);
15651565
}
15661566
}
15671567
""";
@@ -1611,7 +1611,7 @@ namespace Test
16111611
partial class TestTools
16121612
{
16131613
[Description("Static async tool")]
1614-
public static partial Task<string> StaticAsyncMethod(string input);
1614+
public static partial global::System.Threading.Tasks.Task<string> StaticAsyncMethod(string input);
16151615
}
16161616
}
16171617
""";
@@ -1663,7 +1663,7 @@ namespace Test
16631663
partial class TestTools
16641664
{
16651665
[Description("Async tool with defaults")]
1666-
public static partial Task<string> AsyncWithDefaults([Description("The input")] string input, [Description("Timeout in ms")] int timeout = 1000);
1666+
public static partial global::System.Threading.Tasks.Task<string> AsyncWithDefaults([Description("The input")] string input, [Description("Timeout in ms")] int timeout = 1000);
16671667
}
16681668
}
16691669
""";
@@ -1719,6 +1719,64 @@ partial class TestTools
17191719
AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString());
17201720
}
17211721

1722+
[Fact]
1723+
public void Generator_WithEnumFromDifferentNamespace_GeneratesFullyQualifiedTypeName()
1724+
{
1725+
// This test validates the fix for the issue where parameter types from different namespaces
1726+
// caused build failures because the generator was emitting unqualified type names.
1727+
var result = RunGenerator("""
1728+
using ModelContextProtocol.Server;
1729+
using System.ComponentModel;
1730+
using System.Threading.Tasks;
1731+
1732+
namespace MyApp.Actions
1733+
{
1734+
public enum MyAction
1735+
{
1736+
One,
1737+
Two
1738+
}
1739+
}
1740+
1741+
namespace MyApp
1742+
{
1743+
[McpServerToolType]
1744+
public sealed partial class Tools
1745+
{
1746+
/// <summary>Do a thing based on an action.</summary>
1747+
/// <param name="action">The action to perform.</param>
1748+
[McpServerTool]
1749+
public async partial Task<string> DoThing(MyApp.Actions.MyAction action)
1750+
=> await Task.FromResult("ok");
1751+
}
1752+
}
1753+
""");
1754+
1755+
Assert.True(result.Success);
1756+
Assert.Single(result.GeneratedSources);
1757+
1758+
var expected = $$"""
1759+
// <auto-generated/>
1760+
// ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}}
1761+
1762+
#pragma warning disable
1763+
1764+
using System.ComponentModel;
1765+
using ModelContextProtocol.Server;
1766+
1767+
namespace MyApp
1768+
{
1769+
partial class Tools
1770+
{
1771+
[Description("Do a thing based on an action.")]
1772+
public partial global::System.Threading.Tasks.Task<string> DoThing([Description("The action to perform.")] global::MyApp.Actions.MyAction action);
1773+
}
1774+
}
1775+
""";
1776+
1777+
AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString());
1778+
}
1779+
17221780
private GeneratorRunResult RunGenerator([StringSyntax("C#-test")] string source, params string[] expectedDiagnosticIds)
17231781
{
17241782
var syntaxTree = CSharpSyntaxTree.ParseText(source);

0 commit comments

Comments
 (0)