Enable injecting IMcpServer and friends into ctors (#570)
This commit is contained in:
parent
9d835a0625
commit
f3539cc2ca
3 changed files with 249 additions and 191 deletions
|
|
@ -1,9 +1,11 @@
|
|||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Primitives;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
using Moq;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
|
|
@ -44,6 +46,58 @@ public class McpServerPromptTests
|
|||
Assert.Equal("Hello", Assert.IsType<TextContentBlock>(result.Messages[0].Content).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsCtorInjection()
|
||||
{
|
||||
MyService expectedMyService = new();
|
||||
|
||||
ServiceCollection sc = new();
|
||||
sc.AddSingleton(expectedMyService);
|
||||
IServiceProvider services = sc.BuildServiceProvider();
|
||||
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
mockServer.SetupGet(s => s.Services).Returns(services);
|
||||
|
||||
MethodInfo? testMethod = typeof(HasCtorWithSpecialParameters).GetMethod(nameof(HasCtorWithSpecialParameters.TestPrompt));
|
||||
Assert.NotNull(testMethod);
|
||||
McpServerPrompt prompt = McpServerPrompt.Create(testMethod, r =>
|
||||
{
|
||||
Assert.NotNull(r.Services);
|
||||
return ActivatorUtilities.CreateInstance(r.Services, typeof(HasCtorWithSpecialParameters));
|
||||
}, new() { Services = services });
|
||||
|
||||
var result = await prompt.GetAsync(
|
||||
new RequestContext<GetPromptRequestParams>(mockServer.Object),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Messages);
|
||||
Assert.Single(result.Messages);
|
||||
Assert.Equal("True True True True", Assert.IsType<TextContentBlock>(result.Messages[0].Content).Text);
|
||||
}
|
||||
|
||||
private sealed class HasCtorWithSpecialParameters
|
||||
{
|
||||
private readonly MyService _ms;
|
||||
private readonly IMcpServer _server;
|
||||
private readonly RequestContext<GetPromptRequestParams> _request;
|
||||
private readonly IProgress<ProgressNotificationValue> _progress;
|
||||
|
||||
public HasCtorWithSpecialParameters(MyService ms, IMcpServer server, RequestContext<GetPromptRequestParams> request, IProgress<ProgressNotificationValue> progress)
|
||||
{
|
||||
Assert.NotNull(ms);
|
||||
Assert.NotNull(server);
|
||||
Assert.NotNull(request);
|
||||
Assert.NotNull(progress);
|
||||
|
||||
_ms = ms;
|
||||
_server = server;
|
||||
_request = request;
|
||||
_progress = progress;
|
||||
}
|
||||
|
||||
public string TestPrompt() => $"{_ms is not null} {_server is not null} {_request is not null} {_progress is not null}";
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsServiceFromDI()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -360,6 +360,59 @@ public partial class McpServerResourceTests
|
|||
Assert.Equal("42", ((TextResourceContents)result.Contents[0]).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsCtorInjection()
|
||||
{
|
||||
MyService expectedMyService = new();
|
||||
|
||||
ServiceCollection sc = new();
|
||||
sc.AddSingleton(expectedMyService);
|
||||
IServiceProvider services = sc.BuildServiceProvider();
|
||||
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
mockServer.SetupGet(s => s.Services).Returns(services);
|
||||
|
||||
MethodInfo? testMethod = typeof(HasCtorWithSpecialParameters).GetMethod(nameof(HasCtorWithSpecialParameters.TestResource));
|
||||
Assert.NotNull(testMethod);
|
||||
McpServerResource tool = McpServerResource.Create(testMethod, r =>
|
||||
{
|
||||
Assert.NotNull(r.Services);
|
||||
return ActivatorUtilities.CreateInstance(r.Services, typeof(HasCtorWithSpecialParameters));
|
||||
}, new() { Services = services });
|
||||
|
||||
var result = await tool.ReadAsync(
|
||||
new RequestContext<ReadResourceRequestParams>(mockServer.Object) { Params = new() { Uri = "https://something" } },
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Contents);
|
||||
Assert.Single(result.Contents);
|
||||
Assert.Equal("True True True True", Assert.IsType<TextResourceContents>(result.Contents[0]).Text);
|
||||
}
|
||||
|
||||
private sealed class HasCtorWithSpecialParameters
|
||||
{
|
||||
private readonly MyService _ms;
|
||||
private readonly IMcpServer _server;
|
||||
private readonly RequestContext<ReadResourceRequestParams> _request;
|
||||
private readonly IProgress<ProgressNotificationValue> _progress;
|
||||
|
||||
public HasCtorWithSpecialParameters(MyService ms, IMcpServer server, RequestContext<ReadResourceRequestParams> request, IProgress<ProgressNotificationValue> progress)
|
||||
{
|
||||
Assert.NotNull(ms);
|
||||
Assert.NotNull(server);
|
||||
Assert.NotNull(request);
|
||||
Assert.NotNull(progress);
|
||||
|
||||
_ms = ms;
|
||||
_server = server;
|
||||
_request = request;
|
||||
_progress = progress;
|
||||
}
|
||||
|
||||
[McpServerResource(UriTemplate = "https://something")]
|
||||
public string TestResource() => $"{_ms is not null} {_server is not null} {_request is not null} {_progress is not null}";
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ServiceLifetime.Singleton)]
|
||||
[InlineData(ServiceLifetime.Scoped)]
|
||||
|
|
|
|||
|
|
@ -50,6 +50,58 @@ public partial class McpServerToolTests
|
|||
Assert.Equal("42", (result.Content[0] as TextContentBlock)?.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SupportsCtorInjection()
|
||||
{
|
||||
MyService expectedMyService = new();
|
||||
|
||||
ServiceCollection sc = new();
|
||||
sc.AddSingleton(expectedMyService);
|
||||
IServiceProvider services = sc.BuildServiceProvider();
|
||||
|
||||
Mock<IMcpServer> mockServer = new();
|
||||
mockServer.SetupGet(s => s.Services).Returns(services);
|
||||
|
||||
MethodInfo? testMethod = typeof(HasCtorWithSpecialParameters).GetMethod(nameof(HasCtorWithSpecialParameters.TestTool));
|
||||
Assert.NotNull(testMethod);
|
||||
McpServerTool tool = McpServerTool.Create(testMethod, r =>
|
||||
{
|
||||
Assert.NotNull(r.Services);
|
||||
return ActivatorUtilities.CreateInstance(r.Services, typeof(HasCtorWithSpecialParameters));
|
||||
}, new() { Services = services });
|
||||
|
||||
var result = await tool.InvokeAsync(
|
||||
new RequestContext<CallToolRequestParams>(mockServer.Object),
|
||||
TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Content);
|
||||
Assert.Single(result.Content);
|
||||
Assert.Equal("True True True True", Assert.IsType<TextContentBlock>(result.Content[0]).Text);
|
||||
}
|
||||
|
||||
private sealed class HasCtorWithSpecialParameters
|
||||
{
|
||||
private readonly MyService _ms;
|
||||
private readonly IMcpServer _server;
|
||||
private readonly RequestContext<CallToolRequestParams> _request;
|
||||
private readonly IProgress<ProgressNotificationValue> _progress;
|
||||
|
||||
public HasCtorWithSpecialParameters(MyService ms, IMcpServer server, RequestContext<CallToolRequestParams> request, IProgress<ProgressNotificationValue> progress)
|
||||
{
|
||||
Assert.NotNull(ms);
|
||||
Assert.NotNull(server);
|
||||
Assert.NotNull(request);
|
||||
Assert.NotNull(progress);
|
||||
|
||||
_ms = ms;
|
||||
_server = server;
|
||||
_request = request;
|
||||
_progress = progress;
|
||||
}
|
||||
|
||||
public string TestTool() => $"{_ms is not null} {_server is not null} {_request is not null} {_progress is not null}";
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(ServiceLifetime.Singleton)]
|
||||
[InlineData(ServiceLifetime.Scoped)]
|
||||
|
|
|
|||
Loading…
Reference in a new issue