diff --git a/Directory.Packages.props b/Directory.Packages.props index fa70d20a..585b3c11 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -2,7 +2,7 @@ true 10.0.0-preview.3.25171.5 - 9.4.4-preview.1.25259.16 + 9.5.0-preview.1.25262.9 @@ -31,7 +31,7 @@ - + diff --git a/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.cs b/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.cs index 0be6467b..45fdadd0 100644 --- a/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.cs +++ b/src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.cs @@ -48,7 +48,7 @@ public static partial class McpServerBuilderExtensions { builder.Services.AddSingleton((Func)(toolMethod.IsStatic ? services => McpServerTool.Create(toolMethod, options: new() { Services = services, SerializerOptions = serializerOptions }) : - services => McpServerTool.Create(toolMethod, typeof(TToolType), new() { Services = services, SerializerOptions = serializerOptions }))); + services => McpServerTool.Create(toolMethod, static r => CreateTarget(r.Services, typeof(TToolType)), new() { Services = services, SerializerOptions = serializerOptions }))); } } @@ -105,7 +105,7 @@ public static IMcpServerBuilder WithTools(this IMcpServerBuilder builder, IEnume { builder.Services.AddSingleton((Func)(toolMethod.IsStatic ? services => McpServerTool.Create(toolMethod, options: new() { Services = services , SerializerOptions = serializerOptions }) : - services => McpServerTool.Create(toolMethod, toolType, new() { Services = services , SerializerOptions = serializerOptions }))); + services => McpServerTool.Create(toolMethod, r => CreateTarget(r.Services, toolType), new() { Services = services , SerializerOptions = serializerOptions }))); } } } @@ -188,7 +188,7 @@ where t.GetCustomAttribute() is not null { builder.Services.AddSingleton((Func)(promptMethod.IsStatic ? services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions }) : - services => McpServerPrompt.Create(promptMethod, typeof(TPromptType), new() { Services = services, SerializerOptions = serializerOptions }))); + services => McpServerPrompt.Create(promptMethod, static r => CreateTarget(r.Services, typeof(TPromptType)), new() { Services = services, SerializerOptions = serializerOptions }))); } } @@ -245,7 +245,7 @@ public static IMcpServerBuilder WithPrompts(this IMcpServerBuilder builder, IEnu { builder.Services.AddSingleton((Func)(promptMethod.IsStatic ? services => McpServerPrompt.Create(promptMethod, options: new() { Services = services, SerializerOptions = serializerOptions }) : - services => McpServerPrompt.Create(promptMethod, promptType, new() { Services = services, SerializerOptions = serializerOptions }))); + services => McpServerPrompt.Create(promptMethod, r => CreateTarget(r.Services, promptType), new() { Services = services, SerializerOptions = serializerOptions }))); } } } @@ -325,7 +325,7 @@ where t.GetCustomAttribute() is not null { builder.Services.AddSingleton((Func)(resourceTemplateMethod.IsStatic ? services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services }) : - services => McpServerResource.Create(resourceTemplateMethod, typeof(TResourceType), new() { Services = services }))); + services => McpServerResource.Create(resourceTemplateMethod, static r => CreateTarget(r.Services, typeof(TResourceType)), new() { Services = services }))); } } @@ -381,7 +381,7 @@ public static IMcpServerBuilder WithResources(this IMcpServerBuilder builder, IE { builder.Services.AddSingleton((Func)(resourceTemplateMethod.IsStatic ? services => McpServerResource.Create(resourceTemplateMethod, options: new() { Services = services }) : - services => McpServerResource.Create(resourceTemplateMethod, resourceTemplateType, new() { Services = services }))); + services => McpServerResource.Create(resourceTemplateMethod, r => CreateTarget(r.Services, resourceTemplateType), new() { Services = services }))); } } } @@ -775,4 +775,13 @@ private static void AddSingleSessionServerDependencies(IServiceCollection servic }); } #endregion + + #region Helpers + /// Creates an instance of the target object. + private static object CreateTarget( + IServiceProvider? services, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type) => + services is not null ? ActivatorUtilities.CreateInstance(services, type) : + Activator.CreateInstance(type)!; + #endregion } diff --git a/src/ModelContextProtocol/Server/AIFunctionMcpServerPrompt.cs b/src/ModelContextProtocol/Server/AIFunctionMcpServerPrompt.cs index a31a4a28..4cad449d 100644 --- a/src/ModelContextProtocol/Server/AIFunctionMcpServerPrompt.cs +++ b/src/ModelContextProtocol/Server/AIFunctionMcpServerPrompt.cs @@ -4,7 +4,6 @@ using ModelContextProtocol.Utils; using ModelContextProtocol.Utils.Json; using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; @@ -49,15 +48,20 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt /// public static new AIFunctionMcpServerPrompt Create( MethodInfo method, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type targetType, + Func, object> createTargetFunc, McpServerPromptCreateOptions? options) { Throw.IfNull(method); + Throw.IfNull(createTargetFunc); options = DeriveOptions(method, options); return Create( - AIFunctionFactory.Create(method, targetType, CreateAIFunctionFactoryOptions(method, options)), + AIFunctionFactory.Create(method, args => + { + var request = (RequestContext)args.Context![typeof(RequestContext)]!; + return createTargetFunc(request); + }, CreateAIFunctionFactoryOptions(method, options)), options); } @@ -69,7 +73,6 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions( Description = options?.Description, MarshalResult = static (result, _, cancellationToken) => new ValueTask(result), SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions, - CreateInstance = AIFunctionMcpServerTool.GetCreateInstanceFunc(), ConfigureParameterBinding = pi => { if (pi.ParameterType == typeof(RequestContext)) diff --git a/src/ModelContextProtocol/Server/AIFunctionMcpServerResource.cs b/src/ModelContextProtocol/Server/AIFunctionMcpServerResource.cs index be0cc84e..deb562f3 100644 --- a/src/ModelContextProtocol/Server/AIFunctionMcpServerResource.cs +++ b/src/ModelContextProtocol/Server/AIFunctionMcpServerResource.cs @@ -5,7 +5,6 @@ using ModelContextProtocol.Utils.Json; using System.Collections.Concurrent; using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; using System.Text; @@ -56,15 +55,20 @@ internal sealed class AIFunctionMcpServerResource : McpServerResource /// public static new AIFunctionMcpServerResource Create( MethodInfo method, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type targetType, + Func, object> createTargetFunc, McpServerResourceCreateOptions? options) { Throw.IfNull(method); + Throw.IfNull(createTargetFunc); options = DeriveOptions(method, options); return Create( - AIFunctionFactory.Create(method, targetType, CreateAIFunctionFactoryOptions(method, options)), + AIFunctionFactory.Create(method, args => + { + var request = (RequestContext)args.Context![typeof(RequestContext)]!; + return createTargetFunc(request); + }, CreateAIFunctionFactoryOptions(method, options)), options); } @@ -76,7 +80,6 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions( Description = options?.Description, MarshalResult = static (result, _, cancellationToken) => new ValueTask(result), SerializerOptions = McpJsonUtilities.DefaultOptions, - CreateInstance = AIFunctionMcpServerTool.GetCreateInstanceFunc(), ConfigureParameterBinding = pi => { if (pi.ParameterType == typeof(RequestContext)) diff --git a/src/ModelContextProtocol/Server/AIFunctionMcpServerTool.cs b/src/ModelContextProtocol/Server/AIFunctionMcpServerTool.cs index 872f8868..f02b300b 100644 --- a/src/ModelContextProtocol/Server/AIFunctionMcpServerTool.cs +++ b/src/ModelContextProtocol/Server/AIFunctionMcpServerTool.cs @@ -49,15 +49,20 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool /// public static new AIFunctionMcpServerTool Create( MethodInfo method, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type targetType, + Func, object> createTargetFunc, McpServerToolCreateOptions? options) { Throw.IfNull(method); + Throw.IfNull(createTargetFunc); options = DeriveOptions(method, options); return Create( - AIFunctionFactory.Create(method, targetType, CreateAIFunctionFactoryOptions(method, options)), + AIFunctionFactory.Create(method, args => + { + var request = (RequestContext)args.Context![typeof(RequestContext)]!; + return createTargetFunc(request); + }, CreateAIFunctionFactoryOptions(method, options)), options); } @@ -77,7 +82,6 @@ private static AIFunctionFactoryOptions CreateAIFunctionFactoryOptions( Description = options?.Description, MarshalResult = static (result, _, cancellationToken) => new ValueTask(result), SerializerOptions = options?.SerializerOptions ?? McpJsonUtilities.DefaultOptions, - CreateInstance = GetCreateInstanceFunc(), ConfigureParameterBinding = pi => { if (pi.ParameterType == typeof(RequestContext)) diff --git a/src/ModelContextProtocol/Server/McpServerPrompt.cs b/src/ModelContextProtocol/Server/McpServerPrompt.cs index 695a5cd2..df2650cb 100644 --- a/src/ModelContextProtocol/Server/McpServerPrompt.cs +++ b/src/ModelContextProtocol/Server/McpServerPrompt.cs @@ -3,7 +3,6 @@ using ModelContextProtocol.Client; using ModelContextProtocol.Protocol.Messages; using ModelContextProtocol.Protocol.Types; -using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; @@ -184,21 +183,19 @@ public static McpServerPrompt Create( /// instantiate each time the method is invoked. /// /// The instance method to be represented via the created . - /// - /// The to construct an instance of on which to invoke when - /// the resulting is invoked. If services are provided, - /// ActivatorUtilities.CreateInstance will be used to construct the instance using those services; otherwise, - /// is used, utilizing the type's public parameterless constructor. - /// If an instance can't be constructed, an exception is thrown during the function's invocation. + /// + /// Callback used on each function invocation to create an instance of the type on which the instance method + /// will be invoked. If the returned instance is or , it will + /// be disposed of after method completes its invocation. /// /// Optional options used in the creation of the to control its behavior. /// The created for invoking . /// is . public static McpServerPrompt Create( MethodInfo method, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type targetType, + Func, object> createTargetFunc, McpServerPromptCreateOptions? options = null) => - AIFunctionMcpServerPrompt.Create(method, targetType, options); + AIFunctionMcpServerPrompt.Create(method, createTargetFunc, options); /// Creates an that wraps the specified . /// The function to wrap. diff --git a/src/ModelContextProtocol/Server/McpServerResource.cs b/src/ModelContextProtocol/Server/McpServerResource.cs index 6f928c6a..b6ec15f8 100644 --- a/src/ModelContextProtocol/Server/McpServerResource.cs +++ b/src/ModelContextProtocol/Server/McpServerResource.cs @@ -2,7 +2,6 @@ using Microsoft.Extensions.DependencyInjection; using ModelContextProtocol.Protocol.Messages; using ModelContextProtocol.Protocol.Types; -using System.Diagnostics.CodeAnalysis; using System.Reflection; namespace ModelContextProtocol.Server; @@ -205,21 +204,19 @@ public static McpServerResource Create( /// instantiate each time the method is invoked. /// /// The instance method to be represented via the created . - /// - /// The to construct an instance of on which to invoke when - /// the resulting is invoked. If services are provided, - /// ActivatorUtilities.CreateInstance will be used to construct the instance using those services; otherwise, - /// is used, utilizing the type's public parameterless constructor. - /// If an instance can't be constructed, an exception is thrown during the function's invocation. + /// + /// Callback used on each function invocation to create an instance of the type on which the instance method + /// will be invoked. If the returned instance is or , it will + /// be disposed of after method completes its invocation. /// /// Optional options used in the creation of the to control its behavior. /// The created for invoking . /// is . public static McpServerResource Create( MethodInfo method, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type targetType, + Func, object> createTargetFunc, McpServerResourceCreateOptions? options = null) => - AIFunctionMcpServerResource.Create(method, targetType, options); + AIFunctionMcpServerResource.Create(method, createTargetFunc, options); /// Creates an that wraps the specified . /// The function to wrap. diff --git a/src/ModelContextProtocol/Server/McpServerTool.cs b/src/ModelContextProtocol/Server/McpServerTool.cs index ee88d18e..67458eff 100644 --- a/src/ModelContextProtocol/Server/McpServerTool.cs +++ b/src/ModelContextProtocol/Server/McpServerTool.cs @@ -4,7 +4,6 @@ using ModelContextProtocol.Protocol.Messages; using ModelContextProtocol.Protocol.Types; using ModelContextProtocol.Utils.Json; -using System.Diagnostics.CodeAnalysis; using System.Reflection; using System.Text.Json; @@ -187,21 +186,19 @@ public static McpServerTool Create( /// instantiate each time the method is invoked. /// /// The instance method to be represented via the created . - /// - /// The to construct an instance of on which to invoke when - /// the resulting is invoked. If services are provided, - /// ActivatorUtilities.CreateInstance will be used to construct the instance using those services; otherwise, - /// is used, utilizing the type's public parameterless constructor. - /// If an instance can't be constructed, an exception is thrown during the function's invocation. + /// + /// Callback used on each function invocation to create an instance of the type on which the instance method + /// will be invoked. If the returned instance is or , it will + /// be disposed of after method completes its invocation. /// /// Optional options used in the creation of the to control its behavior. /// The created for invoking . /// is . public static McpServerTool Create( MethodInfo method, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type targetType, + Func, object> createTargetFunc, McpServerToolCreateOptions? options = null) => - AIFunctionMcpServerTool.Create(method, targetType, options); + AIFunctionMcpServerTool.Create(method, createTargetFunc, options); /// Creates an that wraps the specified . /// The function to wrap. diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerPromptTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerPromptTests.cs index 48a5e09b..6d1c765f 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerPromptTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerPromptTests.cs @@ -15,8 +15,8 @@ public void Create_InvalidArgs_Throws() { Assert.Throws("function", () => McpServerPrompt.Create((AIFunction)null!)); Assert.Throws("method", () => McpServerPrompt.Create((MethodInfo)null!)); - Assert.Throws("method", () => McpServerPrompt.Create((MethodInfo)null!, typeof(object))); - Assert.Throws("targetType", () => McpServerPrompt.Create(typeof(McpServerPromptTests).GetMethod(nameof(Create_InvalidArgs_Throws))!, (Type)null!)); + Assert.Throws("method", () => McpServerPrompt.Create((MethodInfo)null!, _ => new object())); + Assert.Throws("createTargetFunc", () => McpServerPrompt.Create(typeof(McpServerPromptTests).GetMethod(nameof(Create_InvalidArgs_Throws))!, null!)); Assert.Throws("method", () => McpServerPrompt.Create((Delegate)null!)); } @@ -96,7 +96,7 @@ public async Task SupportsDisposingInstantiatedDisposableTargets() { McpServerPrompt prompt1 = McpServerPrompt.Create( typeof(DisposablePromptType).GetMethod(nameof(DisposablePromptType.InstanceMethod))!, - typeof(DisposablePromptType)); + _ => new DisposablePromptType()); var result = await prompt1.GetAsync( new RequestContext(new Mock().Object), @@ -109,7 +109,7 @@ public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableTargets() { McpServerPrompt prompt1 = McpServerPrompt.Create( typeof(AsyncDisposablePromptType).GetMethod(nameof(AsyncDisposablePromptType.InstanceMethod))!, - typeof(AsyncDisposablePromptType)); + _ => new AsyncDisposablePromptType()); var result = await prompt1.GetAsync( new RequestContext(new Mock().Object), @@ -122,7 +122,7 @@ public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableAndDisposable { McpServerPrompt prompt1 = McpServerPrompt.Create( typeof(AsyncDisposableAndDisposablePromptType).GetMethod(nameof(AsyncDisposableAndDisposablePromptType.InstanceMethod))!, - typeof(AsyncDisposableAndDisposablePromptType)); + _ => new AsyncDisposableAndDisposablePromptType()); var result = await prompt1.GetAsync( new RequestContext(new Mock().Object), diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs index 10b3d17d..631e09b9 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerResourceTests.cs @@ -109,8 +109,8 @@ public void Create_InvalidArgs_Throws() { Assert.Throws("function", () => McpServerResource.Create((AIFunction)null!, new() { UriTemplate = "test://hello" })); Assert.Throws("method", () => McpServerResource.Create((MethodInfo)null!)); - Assert.Throws("method", () => McpServerResource.Create((MethodInfo)null!, typeof(object))); - Assert.Throws("targetType", () => McpServerResource.Create(typeof(McpServerResourceTests).GetMethod(nameof(Create_InvalidArgs_Throws))!, (Type)null!)); + Assert.Throws("method", () => McpServerResource.Create((MethodInfo)null!, _ => new object())); + Assert.Throws("createTargetFunc", () => McpServerResource.Create(typeof(McpServerResourceTests).GetMethod(nameof(Create_InvalidArgs_Throws))!, null!)); Assert.Throws("method", () => McpServerResource.Create((Delegate)null!)); Assert.NotNull(McpServerResource.Create(typeof(DisposableResourceType).GetMethod(nameof(DisposableResourceType.InstanceMethod))!, new DisposableResourceType())); @@ -415,7 +415,7 @@ public async Task SupportsDisposingInstantiatedDisposableTargets() McpServerResource resource1 = McpServerResource.Create( typeof(DisposableResourceType).GetMethod(nameof(DisposableResourceType.InstanceMethod))!, - typeof(DisposableResourceType)); + _ => new DisposableResourceType()); var result = await resource1.ReadAsync( new RequestContext(new Mock().Object) { Params = new() { Uri = "test://static/resource/instanceMethod" } }, diff --git a/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs b/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs index 36f4b318..71283ef1 100644 --- a/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs +++ b/tests/ModelContextProtocol.Tests/Server/McpServerToolTests.cs @@ -18,7 +18,7 @@ public void Create_InvalidArgs_Throws() Assert.Throws("function", () => McpServerTool.Create((AIFunction)null!)); Assert.Throws("method", () => McpServerTool.Create((MethodInfo)null!)); Assert.Throws("method", () => McpServerTool.Create((MethodInfo)null!, typeof(object))); - Assert.Throws("targetType", () => McpServerTool.Create(typeof(McpServerToolTests).GetMethod(nameof(Create_InvalidArgs_Throws))!, (Type)null!)); + Assert.Throws("createTargetFunc", () => McpServerTool.Create(typeof(McpServerToolTests).GetMethod(nameof(Create_InvalidArgs_Throws))!, null!)); Assert.Throws("method", () => McpServerTool.Create((Delegate)null!)); Assert.NotNull(McpServerTool.Create(typeof(DisposableToolType).GetMethod(nameof(DisposableToolType.InstanceMethod))!, new DisposableToolType())); @@ -129,7 +129,7 @@ public async Task SupportsDisposingInstantiatedDisposableTargets() McpServerToolCreateOptions options = new() { SerializerOptions = JsonContext2.Default.Options }; McpServerTool tool1 = McpServerTool.Create( typeof(DisposableToolType).GetMethod(nameof(DisposableToolType.InstanceMethod))!, - typeof(DisposableToolType), + _ => new DisposableToolType(), options); var result = await tool1.InvokeAsync( @@ -144,7 +144,7 @@ public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableTargets() McpServerToolCreateOptions options = new() { SerializerOptions = JsonContext2.Default.Options }; McpServerTool tool1 = McpServerTool.Create( typeof(AsyncDisposableToolType).GetMethod(nameof(AsyncDisposableToolType.InstanceMethod))!, - typeof(AsyncDisposableToolType), + _ => new AsyncDisposableToolType(), options); var result = await tool1.InvokeAsync( @@ -163,7 +163,7 @@ public async Task SupportsAsyncDisposingInstantiatedAsyncDisposableAndDisposable McpServerToolCreateOptions options = new() { SerializerOptions = JsonContext2.Default.Options }; McpServerTool tool1 = McpServerTool.Create( typeof(AsyncDisposableAndDisposableToolType).GetMethod(nameof(AsyncDisposableAndDisposableToolType.InstanceMethod))!, - typeof(AsyncDisposableAndDisposableToolType), + static r => ActivatorUtilities.CreateInstance(r.Services!, typeof(AsyncDisposableAndDisposableToolType)), options); var result = await tool1.InvokeAsync(