using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using ModelContextProtocol.Interceptors.Protocol; using ModelContextProtocol.Interceptors.Server; using ModelContextProtocol.Server; namespace ModelContextProtocol.Interceptors; /// /// Extension methods for to register interceptors. /// public static class McpServerInterceptorBuilderExtensions { /// /// Registers interceptors from all methods on that are decorated /// with . /// public static IMcpServerBuilder WithInterceptors<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T>( this IMcpServerBuilder builder) { ArgumentNullException.ThrowIfNull(builder); EnsureSetupRegistered(builder.Services); foreach (var method in typeof(T).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) { if (method.GetCustomAttribute() is not null) { McpServerInterceptor interceptor; if (method.IsStatic) { interceptor = ReflectionMcpServerInterceptor.Create(method, target: null); } else { // For instance methods, we need a factory. Register the type in DI if not already. builder.Services.TryAddTransient(typeof(T)); interceptor = ReflectionMcpServerInterceptor.Create( method, target: null, new McpServerInterceptorCreateOptions { Services = null }); // Wrap with instance resolution var instanceInterceptor = new DeferredInstanceInterceptor(interceptor, method); builder.Services.AddSingleton(instanceInterceptor); continue; } builder.Services.AddSingleton(interceptor); } } return builder; } /// /// Registers interceptors from all methods on on the given instance. /// public static IMcpServerBuilder WithInterceptors<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] T>( this IMcpServerBuilder builder, T target) where T : class { ArgumentNullException.ThrowIfNull(builder); ArgumentNullException.ThrowIfNull(target); EnsureSetupRegistered(builder.Services); foreach (var method in typeof(T).GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)) { if (method.GetCustomAttribute() is not null) { var interceptor = ReflectionMcpServerInterceptor.Create(method, method.IsStatic ? null : target); builder.Services.AddSingleton(interceptor); } } return builder; } /// /// Registers the specified interceptors directly. /// public static IMcpServerBuilder WithInterceptors( this IMcpServerBuilder builder, params McpServerInterceptor[] interceptors) { ArgumentNullException.ThrowIfNull(builder); EnsureSetupRegistered(builder.Services); foreach (var interceptor in interceptors) { builder.Services.AddSingleton(interceptor); } return builder; } private static void EnsureSetupRegistered(IServiceCollection services) { // Only register once if (services.Any(d => d.ImplementationType == typeof(InterceptorServerOptionsSetup))) { return; } services.AddSingleton, InterceptorServerOptionsSetup>(); } /// /// Wraps an interceptor to resolve target instances from DI for instance methods. /// private sealed class DeferredInstanceInterceptor<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TTarget> : McpServerInterceptor { private readonly McpServerInterceptor _inner; private readonly MethodInfo _method; internal DeferredInstanceInterceptor(McpServerInterceptor inner, MethodInfo method) { _inner = inner; _method = method; } public override Interceptor ProtocolInterceptor => _inner.ProtocolInterceptor; public override IReadOnlyList Metadata => _inner.Metadata; public override async ValueTask InvokeAsync( InvokeInterceptorRequestParams request, McpServer server, IServiceProvider? services, CancellationToken cancellationToken = default) { if (services is null) { throw new InvalidOperationException($"Cannot resolve instance of type '{typeof(TTarget).Name}' without a service provider."); } var target = (TTarget)services.GetRequiredService(typeof(TTarget)); // Create a new interceptor bound to this instance and invoke it var boundInterceptor = ReflectionMcpServerInterceptor.Create(_method, target); return await boundInterceptor.InvokeAsync(request, server, services, cancellationToken); } } } /// /// Configures to register interceptor support. /// internal sealed class InterceptorServerOptionsSetup : IConfigureOptions { private readonly IEnumerable _interceptors; public InterceptorServerOptionsSetup(IEnumerable interceptors) { _interceptors = interceptors; } public void Configure(McpServerOptions options) { // Collect all interceptors into a primitive collection var collection = new McpServerPrimitiveCollection(); var allEvents = new HashSet(); foreach (var interceptor in _interceptors) { collection.Add(interceptor); foreach (var hook in interceptor.ProtocolInterceptor.Hooks) { foreach (var ev in hook.Events) { allEvents.Add(ev); } } } // Register the message filter var filter = new InterceptorMessageFilter(collection); options.Filters.Message.IncomingFilters.Add(filter.CreateFilter); // Advertise interceptor capability via Extensions options.Capabilities ??= new(); options.Capabilities.Extensions ??= new Dictionary(); // Serialize to JsonElement so the SDK's own serializer (which doesn't know about // InterceptorsCapability) can write it without type-info issues. var capability = new InterceptorsCapability { SupportedEvents = allEvents.ToList(), }; options.Capabilities.Extensions[InterceptorProtocolConstants.ExtensionCapabilityKey] = JsonSerializer.SerializeToElement( capability, InterceptorJsonUtilities.DefaultOptions); } }