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