-
Notifications
You must be signed in to change notification settings - Fork 567
Open
Labels
enhancementNew feature or requestNew feature or request
Description
Is your feature request related to a problem? Please describe.
In a NativeAot project, use WithToolsFromAssembly() will cause IL2026 warning.
Describe the solution you'd like
We can collect all tool/prompt type and register them by SourceGenerator. I have made up demo
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace ModelContextProtocol.SourceGenerator;
/// <summary>
/// An incremental source generator that creates extension methods for IMcpServerBuilder to register tools and prompts
/// from assemblies. The generator looks for classes annotated with either <see cref="T:ModelContextProtocol.Server.McpServerToolTypeAttribute" /> or
/// <see cref="T:ModelContextProtocol.Server.McpServerPromptTypeAttribute" /> and generates corresponding registration methods.
/// </summary>
[Generator(LanguageNames.CSharp)]
public class McpToolsSourceGenerator : IIncrementalGenerator
{
private const string ExtClassName = "SGMcpServerBuilderExtensions";
private const string ToolsMethodName = "WithToolsFromAssemblySourceGen";
private const string PromptsMethodName = "WithPromptsFromAssemblySourceGen";
private const string DeclarationScript = $$"""
// <auto-generated/>
using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
namespace ModelContextProtocol.Server;
internal static partial class {{ExtClassName}} {
/// <summary>
/// Adds types marked with the <see cref="T:ModelContextProtocol.Server.McpServerToolTypeAttribute" /> attribute from your assembly as tools to the server.
/// This method's behaviour is similar to <see cref="M:Microsoft.Extensions.DependencyInjection.McpServerBuilderExtensions.WithToolsFromAssembly(Microsoft.Extensions.DependencyInjection.IMcpServerBuilder,System.Reflection.Assembly,System.Text.Json.JsonSerializerOptions)" />.
/// </summary>
internal static partial IMcpServerBuilder {{ToolsMethodName}}(this IMcpServerBuilder builder, JsonSerializerOptions serializerOptions = null);
/// <summary>
/// Adds types marked with the <see cref="T:ModelContextProtocol.Server.McpServerPromptTypeAttribute" /> attribute from your assembly as prompts to the server.
/// This method's behaviour is similar to <see cref="M:Microsoft.Extensions.DependencyInjection.McpServerBuilderExtensions.WithPromptsFromAssembly(Microsoft.Extensions.DependencyInjection.IMcpServerBuilder,System.Reflection.Assembly,System.Text.Json.JsonSerializerOptions)" />.
/// </summary>
internal static partial IMcpServerBuilder {{PromptsMethodName}}(this IMcpServerBuilder builder, JsonSerializerOptions serializerOptions = null);
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
private static void WithTool(IMcpServerBuilder builder,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicConstructors)] Type t,
JsonSerializerOptions serializerOptions) {
builder.WithTools([t], serializerOptions: serializerOptions);
}
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
private static void WithPrompt(IMcpServerBuilder builder,
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods |
DynamicallyAccessedMemberTypes.NonPublicMethods |
DynamicallyAccessedMemberTypes.PublicConstructors)] Type t,
JsonSerializerOptions serializerOptions) {
builder.WithPrompts([t], serializerOptions: serializerOptions);
}
}
""";
public void Initialize(IncrementalGeneratorInitializationContext context)
{
context.RegisterPostInitializationOutput(initializationContext =>
{
initializationContext.AddSource("SGMcpServerBuilderExtensions.Declaration.g.cs", DeclarationScript);
});
var tools = context.SyntaxProvider.ForAttributeWithMetadataName(
"ModelContextProtocol.Server.McpServerToolTypeAttribute",
(node, _) => node.IsKind(SyntaxKind.ClassDeclaration) || node.IsKind(SyntaxKind.RecordDeclaration),
GetFullname).Collect();
var prompts = context.SyntaxProvider.ForAttributeWithMetadataName(
"ModelContextProtocol.Server.McpServerPromptTypeAttribute",
(node, _) => node.IsKind(SyntaxKind.ClassDeclaration) || node.IsKind(SyntaxKind.RecordDeclaration),
GetFullname).Collect();
context.RegisterImplementationSourceOutput(tools, (productionContext, arr) =>
{
var body = string.Join("\n",
arr.Select(type => $"WithTool(builder, typeof({type}), serializerOptions: serializerOptions);")
);
productionContext.AddSource("SGMcpServerBuilderExtensions.ToolImplementation.g.cs",
$$"""
// <auto-generated/>
using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
namespace ModelContextProtocol.Server;
internal static partial class {{ExtClassName}} {
internal static partial IMcpServerBuilder {{ToolsMethodName}}(this IMcpServerBuilder builder, JsonSerializerOptions serializerOptions) {
{{body}}
return builder;
}
}
""");
});
context.RegisterImplementationSourceOutput(prompts, (productionContext, arr) =>
{
var body = string.Join("\n",
arr.Select(type => $"WithPrompt(builder, typeof({type}), serializerOptions: serializerOptions);")
);
productionContext.AddSource("SGMcpServerBuilderExtensions.PromptImplementation.g.cs",
$$"""
// <auto-generated/>
using Microsoft.Extensions.DependencyInjection;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
namespace ModelContextProtocol.Server;
internal static partial class {{ExtClassName}} {
internal static partial IMcpServerBuilder {{PromptsMethodName}}(this IMcpServerBuilder builder, JsonSerializerOptions serializerOptions) {
{{body}}
return builder;
}
}
""");
});
return;
string GetFullname(GeneratorAttributeSyntaxContext syntaxContext, CancellationToken ct)
{
var ns = syntaxContext.TargetSymbol.ContainingNamespace;
var name = syntaxContext.TargetSymbol.Name;
return $"global::{(ns?.IsGlobalNamespace ?? true ? "" : ns + ".")}{name}";
}
}
}To use it
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
// .WithToolsFromAssemblySourceGen(); // Custom json serializer options also support
.WithToolsFromAssemblySourceGen(GeneratedJsonContext.Default.Options);Describe alternatives you've considered
Consider adding the WithTool/WithPrompt methods to the McpServerBuilderExtensions in the SDK, and modify other methods to use them.
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request