From 4e421411d84bed7583079886577f215cb0b25d24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 19:43:48 +0000 Subject: [PATCH 1/5] Initial plan From 2a2409e402b480ab3c88813b7eaa40d88c1cd593 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 19:52:06 +0000 Subject: [PATCH 2/5] 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> --- .../XmlToDescriptionGenerator.cs | 12 +++- .../XmlToDescriptionGeneratorTests.cs | 64 ++++++++++++++++++- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/ModelContextProtocol.Analyzers/XmlToDescriptionGenerator.cs b/src/ModelContextProtocol.Analyzers/XmlToDescriptionGenerator.cs index 71982a3f1..da3014667 100644 --- a/src/ModelContextProtocol.Analyzers/XmlToDescriptionGenerator.cs +++ b/src/ModelContextProtocol.Analyzers/XmlToDescriptionGenerator.cs @@ -19,6 +19,14 @@ public sealed class XmlToDescriptionGenerator : IIncrementalGenerator { private const string GeneratedFileName = "ModelContextProtocol.Descriptions.g.cs"; + /// + /// A display format that produces fully-qualified type names with "global::" prefix + /// and includes nullability annotations. + /// + private static readonly SymbolDisplayFormat FullyQualifiedFormatWithNullability = + SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions( + SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); + public void Initialize(IncrementalGeneratorInitializationContext context) { // Extract method information for all MCP tools, prompts, and resources. @@ -125,7 +133,7 @@ private static MethodToGenerate ExtractMethodInfo( .Where(m => !m.IsKind(SyntaxKind.AsyncKeyword)) .Select(m => m.Text); string modifiersStr = string.Join(" ", modifiers); - string returnType = methodSymbol.ReturnType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); + string returnType = methodSymbol.ReturnType.ToDisplayString(FullyQualifiedFormatWithNullability); string methodName = methodSymbol.Name; // Extract parameters @@ -137,7 +145,7 @@ private static MethodToGenerate ExtractMethodInfo( var paramSyntax = i < parameterSyntaxList.Count ? parameterSyntaxList[i] : null; parameters[i] = new ParameterInfo( - ParameterType: param.Type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat), + ParameterType: param.Type.ToDisplayString(FullyQualifiedFormatWithNullability), Name: param.Name, HasDescriptionAttribute: descriptionAttribute is not null && HasAttribute(param, descriptionAttribute), XmlDescription: xmlDocs?.Parameters.TryGetValue(param.Name, out var pd) == true && !string.IsNullOrWhiteSpace(pd) ? pd : null, diff --git a/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs b/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs index b2cf83652..7ecb68d9c 100644 --- a/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs +++ b/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs @@ -1561,7 +1561,7 @@ namespace Test partial class TestTools { [Description("Async tool")] - public partial Task DoWorkAsync(string input); + public partial global::System.Threading.Tasks.Task DoWorkAsync(string input); } } """; @@ -1611,7 +1611,7 @@ namespace Test partial class TestTools { [Description("Static async tool")] - public static partial Task StaticAsyncMethod(string input); + public static partial global::System.Threading.Tasks.Task StaticAsyncMethod(string input); } } """; @@ -1663,7 +1663,7 @@ namespace Test partial class TestTools { [Description("Async tool with defaults")] - public static partial Task AsyncWithDefaults([Description("The input")] string input, [Description("Timeout in ms")] int timeout = 1000); + public static partial global::System.Threading.Tasks.Task AsyncWithDefaults([Description("The input")] string input, [Description("Timeout in ms")] int timeout = 1000); } } """; @@ -1719,6 +1719,64 @@ partial class TestTools AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } + [Fact] + public void Generator_WithEnumFromDifferentNamespace_GeneratesFullyQualifiedTypeName() + { + // This test validates the fix for the issue where parameter types from different namespaces + // caused build failures because the generator was emitting unqualified type names. + var result = RunGenerator(""" + using ModelContextProtocol.Server; + using System.ComponentModel; + using System.Threading.Tasks; + + namespace MyApp.Actions + { + public enum MyAction + { + One, + Two + } + } + + namespace MyApp + { + [McpServerToolType] + public sealed partial class Tools + { + /// Do a thing based on an action. + /// The action to perform. + [McpServerTool] + public async partial Task DoThing(MyApp.Actions.MyAction action) + => await Task.FromResult("ok"); + } + } + """); + + Assert.True(result.Success); + Assert.Single(result.GeneratedSources); + + var expected = $$""" + // + // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} + + #pragma warning disable + + using System.ComponentModel; + using ModelContextProtocol.Server; + + namespace MyApp + { + partial class Tools + { + [Description("Do a thing based on an action.")] + public partial global::System.Threading.Tasks.Task DoThing([Description("The action to perform.")] global::MyApp.Actions.MyAction action); + } + } + """; + + AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); + } + private GeneratorRunResult RunGenerator([StringSyntax("C#-test")] string source, params string[] expectedDiagnosticIds) { var syntaxTree = CSharpSyntaxTree.ParseText(source); From c4a06d5f705bf081a626a95f29f80baffe764e6a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 20:12:26 +0000 Subject: [PATCH 3/5] Address review feedback: rename field to s_camelCase and add test for unqualified types - Rename FullyQualifiedFormatWithNullability to s_fullyQualifiedFormatWithNullability - Add test for unqualified parameter type in source (using directive) to ensure the generator still produces fully qualified output Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../XmlToDescriptionGenerator.cs | 6 +- .../XmlToDescriptionGeneratorTests.cs | 62 +++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/src/ModelContextProtocol.Analyzers/XmlToDescriptionGenerator.cs b/src/ModelContextProtocol.Analyzers/XmlToDescriptionGenerator.cs index da3014667..c0cad01fe 100644 --- a/src/ModelContextProtocol.Analyzers/XmlToDescriptionGenerator.cs +++ b/src/ModelContextProtocol.Analyzers/XmlToDescriptionGenerator.cs @@ -23,7 +23,7 @@ public sealed class XmlToDescriptionGenerator : IIncrementalGenerator /// A display format that produces fully-qualified type names with "global::" prefix /// and includes nullability annotations. /// - private static readonly SymbolDisplayFormat FullyQualifiedFormatWithNullability = + private static readonly SymbolDisplayFormat s_fullyQualifiedFormatWithNullability = SymbolDisplayFormat.FullyQualifiedFormat.AddMiscellaneousOptions( SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier); @@ -133,7 +133,7 @@ private static MethodToGenerate ExtractMethodInfo( .Where(m => !m.IsKind(SyntaxKind.AsyncKeyword)) .Select(m => m.Text); string modifiersStr = string.Join(" ", modifiers); - string returnType = methodSymbol.ReturnType.ToDisplayString(FullyQualifiedFormatWithNullability); + string returnType = methodSymbol.ReturnType.ToDisplayString(s_fullyQualifiedFormatWithNullability); string methodName = methodSymbol.Name; // Extract parameters @@ -145,7 +145,7 @@ private static MethodToGenerate ExtractMethodInfo( var paramSyntax = i < parameterSyntaxList.Count ? parameterSyntaxList[i] : null; parameters[i] = new ParameterInfo( - ParameterType: param.Type.ToDisplayString(FullyQualifiedFormatWithNullability), + ParameterType: param.Type.ToDisplayString(s_fullyQualifiedFormatWithNullability), Name: param.Name, HasDescriptionAttribute: descriptionAttribute is not null && HasAttribute(param, descriptionAttribute), XmlDescription: xmlDocs?.Parameters.TryGetValue(param.Name, out var pd) == true && !string.IsNullOrWhiteSpace(pd) ? pd : null, diff --git a/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs b/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs index 7ecb68d9c..b00332223 100644 --- a/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs +++ b/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs @@ -1777,6 +1777,68 @@ partial class Tools AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } + [Fact] + public void Generator_WithUnqualifiedTypeInSource_GeneratesFullyQualifiedTypeName() + { + // This test validates that even when the source code uses an unqualified type name + // (via a using directive), the generator still emits fully qualified type names. + // This is the exact scenario from the original issue. + var result = RunGenerator(""" + using ModelContextProtocol.Server; + using System.ComponentModel; + using System.Threading.Tasks; + using MyApp.Actions; + + namespace MyApp.Actions + { + public enum MyAction + { + One, + Two + } + } + + namespace MyApp + { + [McpServerToolType] + public sealed partial class Tools + { + /// Do a thing based on an action. + /// The action to perform. + [McpServerTool] + public async partial Task DoThing(MyAction action) + => await Task.FromResult("ok"); + } + } + """); + + Assert.True(result.Success); + Assert.Single(result.GeneratedSources); + + // Even though the source uses unqualified "MyAction", the generated code should use + // the fully qualified "global::MyApp.Actions.MyAction" + var expected = $$""" + // + // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} + + #pragma warning disable + + using System.ComponentModel; + using ModelContextProtocol.Server; + + namespace MyApp + { + partial class Tools + { + [Description("Do a thing based on an action.")] + public partial global::System.Threading.Tasks.Task DoThing([Description("The action to perform.")] global::MyApp.Actions.MyAction action); + } + } + """; + + AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); + } + private GeneratorRunResult RunGenerator([StringSyntax("C#-test")] string source, params string[] expectedDiagnosticIds) { var syntaxTree = CSharpSyntaxTree.ParseText(source); From f849a830ebf0b9b60b17227328c5db50ab528187 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 21:58:59 +0000 Subject: [PATCH 4/5] Combine type qualification tests into a single Theory Replace Generator_WithEnumFromDifferentNamespace_GeneratesFullyQualifiedTypeName and Generator_WithUnqualifiedTypeInSource_GeneratesFullyQualifiedTypeName with a single Generator_WithTypeFromDifferentNamespace_GeneratesFullyQualifiedTypeName Theory that takes a bool for whether to fully-qualify types in source. Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../XmlToDescriptionGeneratorTests.cs | 83 ++++--------------- 1 file changed, 16 insertions(+), 67 deletions(-) diff --git a/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs b/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs index b00332223..285e0ea00 100644 --- a/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs +++ b/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs @@ -1719,75 +1719,24 @@ partial class TestTools AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } - [Fact] - public void Generator_WithEnumFromDifferentNamespace_GeneratesFullyQualifiedTypeName() + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Generator_WithTypeFromDifferentNamespace_GeneratesFullyQualifiedTypeName(bool useFullyQualifiedTypesInSource) { - // This test validates the fix for the issue where parameter types from different namespaces - // caused build failures because the generator was emitting unqualified type names. - var result = RunGenerator(""" - using ModelContextProtocol.Server; - using System.ComponentModel; - using System.Threading.Tasks; - - namespace MyApp.Actions - { - public enum MyAction - { - One, - Two - } - } + // This test validates that regardless of whether the source code uses fully qualified + // or unqualified type names, the generator always emits fully qualified type names + // with global:: prefix. This fixes the issue where parameter types from different + // namespaces caused build failures. + string usingDirective = useFullyQualifiedTypesInSource ? "" : "using MyApp.Actions;"; + string returnType = useFullyQualifiedTypesInSource ? "System.Threading.Tasks.Task" : "Task"; + string parameterType = useFullyQualifiedTypesInSource ? "MyApp.Actions.MyAction" : "MyAction"; - namespace MyApp - { - [McpServerToolType] - public sealed partial class Tools - { - /// Do a thing based on an action. - /// The action to perform. - [McpServerTool] - public async partial Task DoThing(MyApp.Actions.MyAction action) - => await Task.FromResult("ok"); - } - } - """); - - Assert.True(result.Success); - Assert.Single(result.GeneratedSources); - - var expected = $$""" - // - // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} - - #pragma warning disable - - using System.ComponentModel; - using ModelContextProtocol.Server; - - namespace MyApp - { - partial class Tools - { - [Description("Do a thing based on an action.")] - public partial global::System.Threading.Tasks.Task DoThing([Description("The action to perform.")] global::MyApp.Actions.MyAction action); - } - } - """; - - AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); - } - - [Fact] - public void Generator_WithUnqualifiedTypeInSource_GeneratesFullyQualifiedTypeName() - { - // This test validates that even when the source code uses an unqualified type name - // (via a using directive), the generator still emits fully qualified type names. - // This is the exact scenario from the original issue. - var result = RunGenerator(""" + var result = RunGenerator($$""" using ModelContextProtocol.Server; using System.ComponentModel; using System.Threading.Tasks; - using MyApp.Actions; + {{usingDirective}} namespace MyApp.Actions { @@ -1806,7 +1755,7 @@ public sealed partial class Tools /// Do a thing based on an action. /// The action to perform. [McpServerTool] - public async partial Task DoThing(MyAction action) + public async partial {{returnType}} DoThing({{parameterType}} action) => await Task.FromResult("ok"); } } @@ -1815,8 +1764,8 @@ public async partial Task DoThing(MyAction action) Assert.True(result.Success); Assert.Single(result.GeneratedSources); - // Even though the source uses unqualified "MyAction", the generated code should use - // the fully qualified "global::MyApp.Actions.MyAction" + // Regardless of source qualification, generated code should always use + // fully qualified type names with global:: prefix var expected = $$""" // // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} From 2f8cbe49cc8988db535f893ba39b259bed188986 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:36:21 +0000 Subject: [PATCH 5/5] Add comprehensive type scenario tests for fully-qualified type generation Add tests covering: - Generic List parameters - Generic Dictionary parameters - Array parameters - Nullable reference type parameters - Nested type parameters - Nullable value type (struct) parameters All tests verify that regardless of how types are referenced in source, the generator produces fully qualified type names with global:: prefix. Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com> --- .../XmlToDescriptionGeneratorTests.cs | 312 ++++++++++++++++++ 1 file changed, 312 insertions(+) diff --git a/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs b/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs index 285e0ea00..53fabfec3 100644 --- a/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs +++ b/tests/ModelContextProtocol.Analyzers.Tests/XmlToDescriptionGeneratorTests.cs @@ -1788,6 +1788,318 @@ partial class Tools AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } + [Fact] + public void Generator_WithGenericListParameter_GeneratesFullyQualifiedTypeName() + { + var result = RunGenerator(""" + using ModelContextProtocol.Server; + using System.ComponentModel; + using System.Collections.Generic; + + namespace MyApp.Models + { + public class Item { } + } + + namespace MyApp + { + [McpServerToolType] + public sealed partial class Tools + { + /// Process items. + /// The items to process. + [McpServerTool] + public static partial string ProcessItems(List items) + => "ok"; + } + } + """); + + Assert.True(result.Success); + Assert.Single(result.GeneratedSources); + + var expected = $$""" + // + // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} + + #pragma warning disable + + using System.ComponentModel; + using ModelContextProtocol.Server; + + namespace MyApp + { + partial class Tools + { + [Description("Process items.")] + public static partial string ProcessItems([Description("The items to process.")] global::System.Collections.Generic.List items); + } + } + """; + + AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); + } + + [Fact] + public void Generator_WithGenericDictionaryParameter_GeneratesFullyQualifiedTypeName() + { + var result = RunGenerator(""" + using ModelContextProtocol.Server; + using System.ComponentModel; + using System.Collections.Generic; + + namespace MyApp.Models + { + public class Key { } + public class Value { } + } + + namespace MyApp + { + [McpServerToolType] + public sealed partial class Tools + { + /// Process mapping. + /// The mapping to process. + [McpServerTool] + public static partial string ProcessMapping(Dictionary mapping) + => "ok"; + } + } + """); + + Assert.True(result.Success); + Assert.Single(result.GeneratedSources); + + var expected = $$""" + // + // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} + + #pragma warning disable + + using System.ComponentModel; + using ModelContextProtocol.Server; + + namespace MyApp + { + partial class Tools + { + [Description("Process mapping.")] + public static partial string ProcessMapping([Description("The mapping to process.")] global::System.Collections.Generic.Dictionary mapping); + } + } + """; + + AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); + } + + [Fact] + public void Generator_WithArrayParameter_GeneratesFullyQualifiedTypeName() + { + var result = RunGenerator(""" + using ModelContextProtocol.Server; + using System.ComponentModel; + + namespace MyApp.Models + { + public class Item { } + } + + namespace MyApp + { + [McpServerToolType] + public sealed partial class Tools + { + /// Process items array. + /// The items array to process. + [McpServerTool] + public static partial string ProcessItemsArray(MyApp.Models.Item[] items) + => "ok"; + } + } + """); + + Assert.True(result.Success); + Assert.Single(result.GeneratedSources); + + var expected = $$""" + // + // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} + + #pragma warning disable + + using System.ComponentModel; + using ModelContextProtocol.Server; + + namespace MyApp + { + partial class Tools + { + [Description("Process items array.")] + public static partial string ProcessItemsArray([Description("The items array to process.")] global::MyApp.Models.Item[] items); + } + } + """; + + AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); + } + + [Fact] + public void Generator_WithNullableReferenceTypeParameter_GeneratesFullyQualifiedTypeName() + { + var result = RunGenerator(""" + using ModelContextProtocol.Server; + using System.ComponentModel; + + namespace MyApp.Models + { + public class Item { } + } + + namespace MyApp + { + [McpServerToolType] + public sealed partial class Tools + { + /// Process optional item. + /// The optional item to process. + [McpServerTool] + public static partial string ProcessOptionalItem(MyApp.Models.Item? item) + => "ok"; + } + } + """); + + Assert.True(result.Success); + Assert.Single(result.GeneratedSources); + + var expected = $$""" + // + // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} + + #pragma warning disable + + using System.ComponentModel; + using ModelContextProtocol.Server; + + namespace MyApp + { + partial class Tools + { + [Description("Process optional item.")] + public static partial string ProcessOptionalItem([Description("The optional item to process.")] global::MyApp.Models.Item? item); + } + } + """; + + AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); + } + + [Fact] + public void Generator_WithNestedTypeParameter_GeneratesFullyQualifiedTypeName() + { + var result = RunGenerator(""" + using ModelContextProtocol.Server; + using System.ComponentModel; + + namespace MyApp.Models + { + public class Container + { + public class NestedItem { } + } + } + + namespace MyApp + { + [McpServerToolType] + public sealed partial class Tools + { + /// Process nested item. + /// The nested item to process. + [McpServerTool] + public static partial string ProcessNestedItem(MyApp.Models.Container.NestedItem item) + => "ok"; + } + } + """); + + Assert.True(result.Success); + Assert.Single(result.GeneratedSources); + + var expected = $$""" + // + // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} + + #pragma warning disable + + using System.ComponentModel; + using ModelContextProtocol.Server; + + namespace MyApp + { + partial class Tools + { + [Description("Process nested item.")] + public static partial string ProcessNestedItem([Description("The nested item to process.")] global::MyApp.Models.Container.NestedItem item); + } + } + """; + + AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); + } + + [Fact] + public void Generator_WithNullableValueTypeParameter_GeneratesFullyQualifiedTypeName() + { + var result = RunGenerator(""" + using ModelContextProtocol.Server; + using System.ComponentModel; + + namespace MyApp.Models + { + public struct MyStruct { } + } + + namespace MyApp + { + [McpServerToolType] + public sealed partial class Tools + { + /// Process optional struct. + /// The optional struct to process. + [McpServerTool] + public static partial string ProcessOptionalStruct(MyApp.Models.MyStruct? value) + => "ok"; + } + } + """); + + Assert.True(result.Success); + Assert.Single(result.GeneratedSources); + + var expected = $$""" + // + // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} + + #pragma warning disable + + using System.ComponentModel; + using ModelContextProtocol.Server; + + namespace MyApp + { + partial class Tools + { + [Description("Process optional struct.")] + public static partial string ProcessOptionalStruct([Description("The optional struct to process.")] global::MyApp.Models.MyStruct? value); + } + } + """; + + AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); + } + private GeneratorRunResult RunGenerator([StringSyntax("C#-test")] string source, params string[] expectedDiagnosticIds) { var syntaxTree = CSharpSyntaxTree.ParseText(source);