using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using System.Diagnostics.CodeAnalysis; using Xunit; namespace ModelContextProtocol.Analyzers.Tests; public partial class XmlToDescriptionGeneratorTests { [Fact] public void Generator_WithSummaryOnly_GeneratesMethodDescription() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial class TestTools { /// /// Test tool description /// [McpServerTool] public static partial string TestMethod(string input) { return input; } } """); 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 Test { partial class TestTools { [Description("Test tool description")] public static partial string TestMethod(string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithSummaryAndRemarks_CombinesInMethodDescription() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial class TestTools { /// /// Test tool summary /// /// /// Additional remarks /// [McpServerTool] public static partial string TestMethod(string input) { return input; } } """); Assert.True(result.Success); var expected = $$""" // // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} #pragma warning disable using System.ComponentModel; using ModelContextProtocol.Server; namespace Test { partial class TestTools { [Description("Test tool summary\nAdditional remarks")] public static partial string TestMethod(string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithParameterDocs_GeneratesParameterDescriptions() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial class TestTools { /// /// Test tool /// /// Input parameter description /// Count parameter description [McpServerTool] public static partial string TestMethod(string input, int count) { return input; } } """); Assert.True(result.Success); var expected = $$""" // // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} #pragma warning disable using System.ComponentModel; using ModelContextProtocol.Server; namespace Test { partial class TestTools { [Description("Test tool")] public static partial string TestMethod([Description("Input parameter description")] string input, [Description("Count parameter description")] int count); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithReturnDocs_GeneratesReturnDescription() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial class TestTools { /// /// Test tool /// /// The result of the operation [McpServerTool] public static partial string TestMethod(string input) { return input; } } """); Assert.True(result.Success); var expected = $$""" // // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} #pragma warning disable using System.ComponentModel; using ModelContextProtocol.Server; namespace Test { partial class TestTools { [Description("Test tool")] [return: Description("The result of the operation")] public static partial string TestMethod(string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithExistingMethodDescription_DoesNotGenerateMethodDescription() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial class TestTools { /// /// Test tool summary /// /// Result [McpServerTool] [Description("Already has description")] public static partial string TestMethod(string input) { return input; } } """); Assert.True(result.Success); var expected = $$""" // // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} #pragma warning disable using System.ComponentModel; using ModelContextProtocol.Server; namespace Test { partial class TestTools { [return: Description("Result")] public static partial string TestMethod(string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithExistingParameterDescription_SkipsThatParameter() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial class TestTools { /// /// Test tool /// /// Input description /// Count description [McpServerTool] public static partial string TestMethod(string input, [Description("Already has")] int count) { return input; } } """); Assert.True(result.Success); var expected = $$""" // // ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}} #pragma warning disable using System.ComponentModel; using ModelContextProtocol.Server; namespace Test { partial class TestTools { [Description("Test tool")] public static partial string TestMethod([Description("Input description")] string input, int count); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithoutMcpServerToolAttribute_DoesNotGenerate() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; public partial class TestTools { /// /// Test tool /// public static partial string TestMethod(string input) { return input; } } """); Assert.True(result.Success); Assert.Empty(result.GeneratedSources); } [Fact] public void Generator_WithoutPartialKeyword_DoesNotGenerate() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public class TestTools { /// /// Test tool /// [McpServerTool] public static string TestMethod(string input) { return input; } } """); Assert.True(result.Success); Assert.Empty(result.GeneratedSources); } [Fact] public void Generator_NonPartialMethodWithXmlDocs_ReportsMCP002Diagnostic() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public class TestTools { /// /// Test tool with documentation /// [McpServerTool] public static string TestMethod(string input) { return input; } } """); Assert.True(result.Success); Assert.Empty(result.GeneratedSources); // Should report MCP002 diagnostic var diagnostic = Assert.Single(result.Diagnostics, d => d.Id == "MCP002"); Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); Assert.Contains("TestMethod", diagnostic.GetMessage()); Assert.Contains("partial", diagnostic.GetMessage()); } [Fact] public void Generator_NonPartialMethodWithParameterDocs_ReportsMCP002Diagnostic() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public class TestTools { /// Input parameter [McpServerTool] public static string TestMethod(string input) { return input; } } """); Assert.True(result.Success); Assert.Empty(result.GeneratedSources); // Should report MCP002 diagnostic because parameter has documentation var diagnostic = Assert.Single(result.Diagnostics, d => d.Id == "MCP002"); Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); } [Fact] public void Generator_NonPartialMethodWithReturnDocs_ReportsMCP002Diagnostic() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public class TestTools { /// Return value [McpServerTool] public static string TestMethod(string input) { return input; } } """); Assert.True(result.Success); Assert.Empty(result.GeneratedSources); // Should report MCP002 diagnostic because return has documentation var diagnostic = Assert.Single(result.Diagnostics, d => d.Id == "MCP002"); Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); } [Fact] public void Generator_NonPartialMethodWithoutXmlDocs_DoesNotReportDiagnostic() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public class TestTools { [McpServerTool] public static string TestMethod(string input) { return input; } } """); Assert.True(result.Success); Assert.Empty(result.GeneratedSources); // Should NOT report MCP002 diagnostic because there's no XML documentation Assert.DoesNotContain(result.Diagnostics, d => d.Id == "MCP002"); } [Fact] public void Generator_NonPartialMethodWithEmptyXmlDocs_DoesNotReportDiagnostic() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public class TestTools { /// [McpServerTool] public static string TestMethod(string input) { return input; } } """); Assert.True(result.Success); Assert.Empty(result.GeneratedSources); // Should NOT report MCP002 diagnostic because XML documentation is empty Assert.DoesNotContain(result.Diagnostics, d => d.Id == "MCP002"); } [Fact] public void Generator_NonPartialMethodWithExistingDescriptions_DoesNotReportDiagnostic() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public class TestTools { /// Test tool /// Input param /// Return value [McpServerTool] [Description("Already has method description")] [return: Description("Already has return description")] public static string TestMethod([Description("Already has param description")] string input) { return input; } } """); Assert.True(result.Success); Assert.Empty(result.GeneratedSources); // Should NOT report MCP002 diagnostic because all descriptions already exist Assert.DoesNotContain(result.Diagnostics, d => d.Id == "MCP002"); } [Fact] public void Generator_NonPartialMethodWithPartialExistingDescriptions_ReportsMCP002Diagnostic() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public class TestTools { /// Test tool /// Input param [McpServerTool] [Description("Already has method description")] public static string TestMethod(string input) { return input; } } """); Assert.True(result.Success); Assert.Empty(result.GeneratedSources); // Should report MCP002 diagnostic because parameter description would be generated var diagnostic = Assert.Single(result.Diagnostics, d => d.Id == "MCP002"); Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); } [Fact] public void Generator_NonPartialPromptWithXmlDocs_ReportsMCP002Diagnostic() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerPromptType] public class TestPrompts { /// /// Test prompt /// [McpServerPrompt] public static string TestPrompt(string input) { return input; } } """); Assert.True(result.Success); Assert.Empty(result.GeneratedSources); // Should report MCP002 diagnostic for prompts too var diagnostic = Assert.Single(result.Diagnostics, d => d.Id == "MCP002"); Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); } [Fact] public void Generator_NonPartialResourceWithXmlDocs_ReportsMCP002Diagnostic() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerResourceType] public class TestResources { /// /// Test resource /// [McpServerResource("test://resource")] public static string TestResource(string input) { return input; } } """); Assert.True(result.Success); Assert.Empty(result.GeneratedSources); // Should report MCP002 diagnostic for resources too var diagnostic = Assert.Single(result.Diagnostics, d => d.Id == "MCP002"); Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); } [Fact] public void Generator_WithSpecialCharacters_EscapesCorrectly() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial class TestTools { /// /// Test with "quotes", \backslash, newline /// and tab characters. /// /// Parameter with "quotes" [McpServerTool] public static partial string TestEscaping(string input) { return input; } } """); 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 Test { partial class TestTools { [Description("Test with \"quotes\", \\backslash, newline and tab characters.")] public static partial string TestEscaping([Description("Parameter with \"quotes\"")] string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithInvalidXml_GeneratesPartialAndReportsDiagnostic() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial class TestTools { /// /// Test with [McpServerTool] public static partial string TestInvalidXml(string input) { return input; } } """); // Should not throw, generates partial implementation without Description attributes 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 Test { partial class TestTools { public static partial string TestInvalidXml(string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); // Should report a warning diagnostic var diagnostic = Assert.Single(result.Diagnostics, d => d.Id == "MCP001"); Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity); Assert.Contains("invalid", diagnostic.GetMessage(), StringComparison.OrdinalIgnoreCase); } [Fact] public void Generator_WithGenericType_GeneratesCorrectly() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial class TestTools { /// /// Test generic /// [McpServerTool] public static partial string TestGeneric(string input) { return input; } } """); 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 Test { partial class TestTools { [Description("Test generic")] public static partial string TestGeneric(string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithEmptyXmlComments_GeneratesPartialWithoutDescription() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial class TestTools { /// /// [McpServerTool] public static partial string TestEmpty(string input) { return input; } } """); 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 Test { partial class TestTools { public static partial string TestEmpty(string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithMultilineComments_CombinesIntoSingleLine() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial class TestTools { /// /// First line /// Second line /// Third line /// [McpServerTool] public static partial string TestMultiline(string input) { return input; } } """); 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 Test { partial class TestTools { [Description("First line Second line Third line")] public static partial string TestMultiline(string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithParametersOnly_GeneratesParameterDescriptions() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial class TestTools { /// Input parameter /// Count parameter [McpServerTool] public static partial string TestMethod(string input, int count) { return input; } } """); 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 Test { partial class TestTools { public static partial string TestMethod([Description("Input parameter")] string input, [Description("Count parameter")] int count); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithNestedType_GeneratesCorrectly() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; public partial class OuterClass { [McpServerToolType] public partial class InnerClass { /// /// Nested tool /// [McpServerTool] public static partial string NestedMethod(string input) { return input; } } } """); 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 Test { partial class OuterClass { partial class InnerClass { [Description("Nested tool")] public static partial string NestedMethod(string input); } } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithManyToolsAcrossMultipleNestedTypes_GeneratesCorrectly() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test.Outer; [McpServerToolType] public partial class RootTools { /// Root level tool 1 [McpServerTool] public static partial string RootTool1(string input) => input; /// Root level tool 2 /// The count [McpServerTool] public static partial string RootTool2(string input, int count) => input; } public partial class OuterContainer { [McpServerToolType] public partial class Level2A { /// Level 2A tool [McpServerTool] public static partial string Level2ATool(string input) => input; public partial class Level3 { [McpServerToolType] public partial class Level4 { /// Deep nested tool /// The result [McpServerTool] public static partial string DeepTool(string input) => input; } } } [McpServerToolType] public partial class Level2B { /// Level 2B tool 1 [McpServerTool] public static partial string Level2BTool1(string input) => input; /// Level 2B tool 2 [McpServerTool] public static partial string Level2BTool2(string input) => input; } } namespace Test.Resources; [McpServerResourceType] public partial class ResourceProviders { /// Test resource 1 /// The path [McpServerResource("test:///{path}")] public static partial string Resource1(string path) => path; /// Test resource 2 [McpServerResource("test2:///{id}")] public static partial string Resource2(string id) => id; } [McpServerPromptType] public partial class GlobalPrompts { /// Global prompt [McpServerPrompt] public static partial string GlobalPrompt(string input) => input; } """); 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 Test.Outer { partial class RootTools { [Description("Root level tool 1")] public static partial string RootTool1(string input); [Description("Root level tool 2")] public static partial string RootTool2(string input, [Description("The count")] int count); } partial class OuterContainer { partial class Level2A { [Description("Level 2A tool")] public static partial string Level2ATool(string input); } } partial class OuterContainer { partial class Level2A { partial class Level3 { partial class Level4 { [Description("Deep nested tool")] [return: Description("The result")] public static partial string DeepTool(string input); } } } } partial class OuterContainer { partial class Level2B { [Description("Level 2B tool 1")] public static partial string Level2BTool1(string input); [Description("Level 2B tool 2")] public static partial string Level2BTool2(string input); } } } namespace Test.Outer.Test.Resources { partial class GlobalPrompts { [Description("Global prompt")] public static partial string GlobalPrompt(string input); } partial class ResourceProviders { [Description("Test resource 1")] public static partial string Resource1([Description("The path")] string path); [Description("Test resource 2")] public static partial string Resource2(string id); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithRecordClass_GeneratesCorrectly() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial record TestTools { /// /// Record tool /// [McpServerTool] public static partial string RecordMethod(string input) { return input; } } """); 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 Test { partial record class TestTools { [Description("Record tool")] public static partial string RecordMethod(string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithRecordStruct_GeneratesCorrectly() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial record struct TestTools { /// /// Record struct tool /// [McpServerTool] public static partial string RecordStructMethod(string input) { return input; } } """); 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 Test { partial record struct TestTools { [Description("Record struct tool")] public static partial string RecordStructMethod(string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithVirtualMethod_GeneratesCorrectly() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public partial class TestTools { /// /// Virtual tool /// [McpServerTool] public virtual partial string VirtualMethod(string input) { return input; } } """); 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 Test { partial class TestTools { [Description("Virtual tool")] public virtual partial string VirtualMethod(string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithAbstractMethod_GeneratesCorrectly() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerToolType] public abstract partial class TestTools { /// /// Abstract tool /// [McpServerTool] public abstract partial string AbstractMethod(string input); } """); 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 Test { partial class TestTools { [Description("Abstract tool")] public abstract partial string AbstractMethod(string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithMcpServerPrompt_GeneratesCorrectly() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerPromptType] public partial class TestPrompts { /// /// Test prompt /// [McpServerPrompt] public static partial string TestPrompt(string input) { return input; } } """); 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 Test { partial class TestPrompts { [Description("Test prompt")] public static partial string TestPrompt(string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithMcpServerResource_GeneratesCorrectly() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; namespace Test; [McpServerResourceType] public partial class TestResources { /// /// Test resource /// [McpServerResource("test://resource")] public static partial string TestResource(string input) { return input; } } """); 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 Test { partial class TestResources { [Description("Test resource")] public static partial string TestResource(string input); } } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } [Fact] public void Generator_WithGlobalNamespace_GeneratesCorrectly() { var result = RunGenerator(""" using ModelContextProtocol.Server; using System.ComponentModel; [McpServerToolType] public partial class GlobalTools { /// /// Tool in global namespace /// [McpServerTool] public static partial string GlobalMethod(string input) { return input; } } """); 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; partial class GlobalTools { [Description("Tool in global namespace")] public static partial string GlobalMethod(string input); } """; AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString()); } private GeneratorRunResult RunGenerator([StringSyntax("C#-test")] string source) { var syntaxTree = CSharpSyntaxTree.ParseText(source); // Get reference assemblies - we need to include all the basic runtime types List referenceList = [ MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(System.ComponentModel.DescriptionAttribute).Assembly.Location), ]; // Add all necessary runtime assemblies var runtimePath = Path.GetDirectoryName(typeof(object).Assembly.Location)!; referenceList.Add(MetadataReference.CreateFromFile(Path.Combine(runtimePath, "System.Runtime.dll"))); referenceList.Add(MetadataReference.CreateFromFile(Path.Combine(runtimePath, "netstandard.dll"))); // Try to find and add ModelContextProtocol.Core try { var coreAssemblyPath = Path.Combine(AppContext.BaseDirectory, "ModelContextProtocol.Core.dll"); if (File.Exists(coreAssemblyPath)) { referenceList.Add(MetadataReference.CreateFromFile(coreAssemblyPath)); } } catch { // If we can't find it, the compilation will fail with appropriate errors } var compilation = CSharpCompilation.Create( "TestAssembly", [syntaxTree], referenceList, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); var driver = (CSharpGeneratorDriver)CSharpGeneratorDriver .Create(new XmlToDescriptionGenerator()) .RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics); var runResult = driver.GetRunResult(); return new GeneratorRunResult { Success = !diagnostics.Any(d => d.Severity == DiagnosticSeverity.Error), GeneratedSources = runResult.GeneratedTrees.Select(t => (t.FilePath, t.GetText())).ToList(), Diagnostics = diagnostics.ToList(), Compilation = outputCompilation }; } private static void AssertGeneratedSourceEquals( [StringSyntax("C#-test")] string expected, [StringSyntax("C#-test")] string actual) { // Normalize line endings to \n, remove trailing whitespace from each line, and trim the end static string Normalize(string s) { var lines = s.Replace("\r\n", "\n").Replace("\r", "\n").Split('\n'); for (int i = 0; i < lines.Length; i++) { lines[i] = lines[i].TrimEnd(); } return string.Join('\n', lines).TrimEnd(); } var normalizedExpected = Normalize(expected); var normalizedActual = Normalize(actual); Assert.Equal(normalizedExpected, normalizedActual); } private class GeneratorRunResult { public bool Success { get; set; } public List<(string FilePath, Microsoft.CodeAnalysis.Text.SourceText SourceText)> GeneratedSources { get; set; } = []; public List Diagnostics { get; set; } = []; public Compilation? Compilation { get; set; } } }