Wrap each request in a service scope (#276)

* Wrap each request in a service scope

* Change request/notification delegates to return ValueTask

* Address feedback
This commit is contained in:
Stephen Toub 2025-04-11 19:19:12 -04:00 • committed by GitHub
commit f4c87f5e2f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 477 additions and 306 deletions

View file

@ -381,7 +381,7 @@ public class McpClientExtensionsTests : ClientServerTestBase
(notification, cancellationToken) =>
{
Assert.True(channel.Writer.TryWrite(JsonSerializer.Deserialize<LoggingMessageNotificationParams>(notification.Params, McpJsonUtilities.DefaultOptions)));
return Task.CompletedTask;
return default;
}))
{
logger.LogTrace("Trace {Message}", "message");

View file

@ -41,18 +41,19 @@ public class McpClientFactoryTests
{
Sampling = new SamplingCapability
{
SamplingHandler = (c, p, t) => Task.FromResult(
new CreateMessageResult {
SamplingHandler = async (c, p, t) =>
new CreateMessageResult
{
Content = new Content { Text = "result" },
Model = "test-model",
Role = Role.User,
StopReason = "endTurn"
}),
},
},
Roots = new RootsCapability
{
ListChanged = true,
RootsHandler = (t, r) => Task.FromResult(new ListRootsResult { Roots = [] }),
RootsHandler = async (t, r) => new ListRootsResult { Roots = [] },
}
}
};

View file

@ -265,7 +265,7 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
{
var notificationParams = JsonSerializer.Deserialize<ResourceUpdatedNotificationParams>(notification.Params, McpJsonUtilities.DefaultOptions);
tcs.TrySetResult(true);
return Task.CompletedTask;
return default;
})
]
}
@ -295,7 +295,7 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
{
var notificationParams = JsonSerializer.Deserialize<ResourceUpdatedNotificationParams>(notification.Params, McpJsonUtilities.DefaultOptions);
receivedNotification.TrySetResult(true);
return Task.CompletedTask;
return default;
})
]
}
@ -370,10 +370,10 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
{
Sampling = new()
{
SamplingHandler = (_, _, _) =>
SamplingHandler = async (_, _, _) =>
{
samplingHandlerCalls++;
return Task.FromResult(new CreateMessageResult
return new CreateMessageResult
{
Model = "test-model",
Role = Role.Assistant,
@ -382,7 +382,7 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
Type = "text",
Text = "Test response"
}
});
};
},
},
},
@ -569,7 +569,7 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
{
receivedNotification.TrySetResult(true);
}
return Task.CompletedTask;
return default;
})
]
}

View file

@ -25,7 +25,7 @@ public abstract class ClientServerTestBase : LoggedTest, IAsyncDisposable
.AddMcpServer()
.WithStreamServerTransport(_clientToServerPipe.Reader.AsStream(), _serverToClientPipe.Writer.AsStream());
ConfigureServices(sc, _builder);
ServiceProvider = sc.BuildServiceProvider();
ServiceProvider = sc.BuildServiceProvider(validateScopes: true);
_cts = CancellationTokenSource.CreateLinkedTokenSource(TestContext.Current.CancellationToken);
Server = ServiceProvider.GetRequiredService<IMcpServer>();

View file

@ -21,7 +21,7 @@ public class McpServerBuilderExtensionsHandlerTests
[Fact]
public void WithListToolsHandler_Sets_Handler()
{
Func<RequestContext<ListToolsRequestParams>, CancellationToken, Task<ListToolsResult>> handler = (context, token) => Task.FromResult(new ListToolsResult());
Func<RequestContext<ListToolsRequestParams>, CancellationToken, ValueTask<ListToolsResult>> handler = async (context, token) => new ListToolsResult();
_builder.Object.WithListToolsHandler(handler);
@ -34,7 +34,7 @@ public class McpServerBuilderExtensionsHandlerTests
[Fact]
public void WithCallToolHandler_Sets_Handler()
{
Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>> handler = (context, token) => Task.FromResult(new CallToolResponse());
Func<RequestContext<CallToolRequestParams>, CancellationToken, ValueTask<CallToolResponse>> handler = async (context, token) => new CallToolResponse();
_builder.Object.WithCallToolHandler(handler);
@ -47,7 +47,7 @@ public class McpServerBuilderExtensionsHandlerTests
[Fact]
public void WithListPromptsHandler_Sets_Handler()
{
Func<RequestContext<ListPromptsRequestParams>, CancellationToken, Task<ListPromptsResult>> handler = (context, token) => Task.FromResult(new ListPromptsResult());
Func<RequestContext<ListPromptsRequestParams>, CancellationToken, ValueTask<ListPromptsResult>> handler = async (context, token) => new ListPromptsResult();
_builder.Object.WithListPromptsHandler(handler);
@ -60,7 +60,7 @@ public class McpServerBuilderExtensionsHandlerTests
[Fact]
public void WithGetPromptHandler_Sets_Handler()
{
Func<RequestContext<GetPromptRequestParams>, CancellationToken, Task<GetPromptResult>> handler = (context, token) => Task.FromResult(new GetPromptResult());
Func<RequestContext<GetPromptRequestParams>, CancellationToken, ValueTask<GetPromptResult>> handler = async (context, token) => new GetPromptResult();
_builder.Object.WithGetPromptHandler(handler);
@ -73,7 +73,7 @@ public class McpServerBuilderExtensionsHandlerTests
[Fact]
public void WithListResourceTemplatesHandler_Sets_Handler()
{
Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, Task<ListResourceTemplatesResult>> handler = (context, token) => Task.FromResult(new ListResourceTemplatesResult());
Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, ValueTask<ListResourceTemplatesResult>> handler = async (context, token) => new ListResourceTemplatesResult();
_builder.Object.WithListResourceTemplatesHandler(handler);
@ -86,7 +86,7 @@ public class McpServerBuilderExtensionsHandlerTests
[Fact]
public void WithListResourcesHandler_Sets_Handler()
{
Func<RequestContext<ListResourcesRequestParams>, CancellationToken, Task<ListResourcesResult>> handler = (context, token) => Task.FromResult(new ListResourcesResult());
Func<RequestContext<ListResourcesRequestParams>, CancellationToken, ValueTask<ListResourcesResult>> handler = async (context, token) => new ListResourcesResult();
_builder.Object.WithListResourcesHandler(handler);
@ -99,7 +99,7 @@ public class McpServerBuilderExtensionsHandlerTests
[Fact]
public void WithReadResourceHandler_Sets_Handler()
{
Func<RequestContext<ReadResourceRequestParams>, CancellationToken, Task<ReadResourceResult>> handler = (context, token) => Task.FromResult(new ReadResourceResult());
Func<RequestContext<ReadResourceRequestParams>, CancellationToken, ValueTask<ReadResourceResult>> handler = async (context, token) => new ReadResourceResult();
_builder.Object.WithReadResourceHandler(handler);
@ -112,7 +112,7 @@ public class McpServerBuilderExtensionsHandlerTests
[Fact]
public void WithCompleteHandler_Sets_Handler()
{
Func<RequestContext<CompleteRequestParams>, CancellationToken, Task<CompleteResult>> handler = (context, token) => Task.FromResult(new CompleteResult());
Func<RequestContext<CompleteRequestParams>, CancellationToken, ValueTask<CompleteResult>> handler = async (context, token) => new CompleteResult();
_builder.Object.WithCompleteHandler(handler);
@ -125,7 +125,7 @@ public class McpServerBuilderExtensionsHandlerTests
[Fact]
public void WithSubscribeToResourcesHandler_Sets_Handler()
{
Func<RequestContext<SubscribeRequestParams>, CancellationToken, Task<EmptyResult>> handler = (context, token) => Task.FromResult(new EmptyResult());
Func<RequestContext<SubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>> handler = async (context, token) => new EmptyResult();
_builder.Object.WithSubscribeToResourcesHandler(handler);
@ -138,7 +138,7 @@ public class McpServerBuilderExtensionsHandlerTests
[Fact]
public void WithUnsubscribeFromResourcesHandler_Sets_Handler()
{
Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, Task<EmptyResult>> handler = (context, token) => Task.FromResult(new EmptyResult());
Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>> handler = async (context, token) => new EmptyResult();
_builder.Object.WithUnsubscribeFromResourcesHandler(handler);

View file

@ -9,8 +9,6 @@ using System.ComponentModel;
using System.Text.Json.Serialization;
using System.Threading.Channels;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
namespace ModelContextProtocol.Tests.Configuration;
public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBase
@ -144,7 +142,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
await using (client.RegisterNotificationHandler("notifications/prompts/list_changed", (notification, cancellationToken) =>
{
listChanged.Writer.TryWrite(notification);
return Task.CompletedTask;
return default;
}))
{
serverPrompts.Add(newPrompt);

View file

@ -16,8 +16,6 @@ using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using System.Threading.Channels;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
namespace ModelContextProtocol.Tests.Configuration;
public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
@ -109,7 +107,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
throw new Exception($"Unknown tool '{request.Params?.Name}'");
}
})
.WithTools<EchoTool>(serializerOptions: JsonContext.Default.Options);
.WithTools<EchoTool>(serializerOptions: BuilderToolsJsonContext.Default.Options);
services.AddSingleton(new ObjectWithId());
}
@ -209,7 +207,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
await using (client.RegisterNotificationHandler(NotificationMethods.ToolListChangedNotification, (notification, cancellationToken) =>
{
listChanged.Writer.TryWrite(notification);
return Task.CompletedTask;
return default;
}))
{
serverTools.Add(newTool);
@ -431,8 +429,13 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
[Fact]
public void Register_Tools_From_Current_Assembly()
{
if (!JsonSerializer.IsReflectionEnabledByDefault)
{
return;
}
ServiceCollection sc = new();
sc.AddMcpServer().WithToolsFromAssembly(serializerOptions: JsonContext.Default.Options);
sc.AddMcpServer().WithToolsFromAssembly();
IServiceProvider services = sc.BuildServiceProvider();
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "Echo");
@ -448,7 +451,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
{
sc.AddSingleton(new ComplexObject());
}
sc.AddMcpServer().WithTools([typeof(EchoTool)], JsonContext.Default.Options);
sc.AddMcpServer().WithTools([typeof(EchoTool)], BuilderToolsJsonContext.Default.Options);
IServiceProvider services = sc.BuildServiceProvider();
McpServerTool tool = services.GetServices<McpServerTool>().First(t => t.ProtocolTool.Name == "EchoComplex");
@ -462,6 +465,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
}
}
[Theory]
[InlineData(ServiceLifetime.Singleton)]
[InlineData(ServiceLifetime.Scoped)]
@ -469,6 +473,11 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
[InlineData(null)]
public void WithToolsFromAssembly_Parameters_Satisfiable_From_DI(ServiceLifetime? lifetime)
{
if (!JsonSerializer.IsReflectionEnabledByDefault)
{
return;
}
ServiceCollection sc = new();
switch (lifetime)
{
@ -485,7 +494,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
break;
}
sc.AddMcpServer().WithToolsFromAssembly(serializerOptions: JsonContext.Default.Options);
sc.AddMcpServer().WithToolsFromAssembly();
IServiceProvider services = sc.BuildServiceProvider();
McpServerTool tool = services.GetServices<McpServerTool>().First(t => t.ProtocolTool.Name == "EchoComplex");
@ -528,9 +537,9 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
{
ServiceCollection sc = new();
sc.AddMcpServer()
.WithTools<EchoTool>(serializerOptions: JsonContext.Default.Options)
.WithTools<AnotherToolType>(serializerOptions: JsonContext.Default.Options)
.WithTools([typeof(ToolTypeWithNoAttribute)], JsonContext.Default.Options);
.WithTools<EchoTool>(serializerOptions: BuilderToolsJsonContext.Default.Options)
.WithTools<AnotherToolType>(serializerOptions: BuilderToolsJsonContext.Default.Options)
.WithTools([typeof(ToolTypeWithNoAttribute)], BuilderToolsJsonContext.Default.Options);
IServiceProvider services = sc.BuildServiceProvider();
Assert.Contains(services.GetServices<McpServerTool>(), t => t.ProtocolTool.Name == "double_echo");
@ -588,7 +597,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
{
ProgressNotification pn = JsonSerializer.Deserialize<ProgressNotification>(notification.Params, McpJsonUtilities.DefaultOptions)!;
notifications.Enqueue(pn);
return Task.CompletedTask;
return default;
}))
{
var result = await client.SendRequestAsync<CallToolRequestParams, CallToolResponse>(
@ -757,17 +766,17 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
public static string MethodD(string d) => d.ToString();
}
public class ObjectWithId
{
public string Id { get; set; } = Guid.NewGuid().ToString("N");
}
public class ComplexObject
{
public string? Name { get; set; }
public int Age { get; set; }
}
public class ObjectWithId
{
public string Id { get; set; } = Guid.NewGuid().ToString("N");
}
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(int))]
[JsonSerializable(typeof(long))]
@ -778,5 +787,5 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
[JsonSerializable(typeof(ComplexObject))]
[JsonSerializable(typeof(string[]))]
[JsonSerializable(typeof(JsonElement))]
partial class JsonContext : JsonSerializerContext;
}
partial class BuilderToolsJsonContext : JsonSerializerContext;
}

View file

@ -0,0 +1,75 @@
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Client;
using ModelContextProtocol.Server;
using ModelContextProtocol.Utils.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace ModelContextProtocol.Tests.Configuration;
public partial class McpServerScopedTests : ClientServerTestBase
{
public McpServerScopedTests(ITestOutputHelper testOutputHelper)
: base(testOutputHelper)
{
}
protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder)
{
mcpServerBuilder.WithTools<EchoTool>(serializerOptions: McpServerScopedTestsJsonContext.Default.Options);
services.AddScoped(_ => new ComplexObject() { Name = "Scoped" });
}
[Fact]
public async Task InjectScopedServiceAsArgument()
{
IMcpClient client = await CreateMcpClientForServer();
var tools = await client.ListToolsAsync(McpServerScopedTestsJsonContext.Default.Options, TestContext.Current.CancellationToken);
var tool = tools.First(t => t.Name == nameof(EchoTool.EchoComplex));
Assert.DoesNotContain("\"complex\"", JsonSerializer.Serialize(tool.JsonSchema, McpJsonUtilities.DefaultOptions));
int startingConstructed = ComplexObject.Constructed;
int startingDisposed = ComplexObject.Disposed;
for (int i = 1; i <= 10; i++)
{
Assert.Contains("\"Scoped\"", JsonSerializer.Serialize(await tool.InvokeAsync(cancellationToken: TestContext.Current.CancellationToken), McpJsonUtilities.DefaultOptions));
Assert.Equal(startingConstructed + i, ComplexObject.Constructed);
Assert.Equal(startingDisposed + i, ComplexObject.Disposed);
}
}
[McpServerToolType]
public sealed class EchoTool()
{
[McpServerTool]
public static string EchoComplex(ComplexObject complex) => complex.Name!;
}
public class ComplexObject : IAsyncDisposable
{
public static int Constructed;
public static int Disposed;
public ComplexObject()
{
Interlocked.Increment(ref Constructed);
}
public ValueTask DisposeAsync()
{
Interlocked.Increment(ref Disposed);
return default;
}
public string? Name { get; set; }
public int Age { get; set; }
}
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(ComplexObject))]
[JsonSerializable(typeof(JsonElement))]
partial class McpServerScopedTestsJsonContext : JsonSerializerContext;
}

View file

@ -77,10 +77,10 @@ public class DockerEverythingServerTests(ITestOutputHelper testOutputHelper) : L
{
Sampling = new()
{
SamplingHandler = (_, _, _) =>
SamplingHandler = async (_, _, _) =>
{
samplingHandlerCalls++;
return Task.FromResult(new CreateMessageResult
return new CreateMessageResult
{
Model = "test-model",
Role = Role.Assistant,
@ -89,7 +89,7 @@ public class DockerEverythingServerTests(ITestOutputHelper testOutputHelper) : L
Type = "text",
Text = "Test response"
}
});
};
},
},
},

View file

@ -39,7 +39,7 @@ public class CancellationTests : ClientServerTestBase
await using (Server.RegisterNotificationHandler(NotificationMethods.CancelledNotification, (notification, cancellationToken) =>
{
gotCancellation = true;
return Task.CompletedTask;
return default;
}))
{
await Assert.ThrowsAsync<OperationCanceledException>(() => client.ListToolsAsync(cancellationToken: new CancellationToken(true)));

View file

@ -25,7 +25,7 @@ public class NotificationHandlerTests : ClientServerTestBase
{
Interlocked.Increment(ref counter);
tcs.SetResult(true);
return Task.CompletedTask;
return default;
}))
{
await Server.SendNotificationAsync(NotificationName, TestContext.Current.CancellationToken);
@ -58,7 +58,7 @@ public class NotificationHandlerTests : ClientServerTestBase
tcs.TrySetResult(true);
}
return Task.CompletedTask;
return default;
});
}

View file

@ -21,16 +21,16 @@ public class McpServerHandlerTests
Assert.Null(handlers.SubscribeToResourcesHandler);
Assert.Null(handlers.UnsubscribeFromResourcesHandler);
handlers.ListToolsHandler = (p, c) => Task.FromResult(new ListToolsResult());
handlers.CallToolHandler = (p, c) => Task.FromResult(new CallToolResponse());
handlers.ListPromptsHandler = (p, c) => Task.FromResult(new ListPromptsResult());
handlers.GetPromptHandler = (p, c) => Task.FromResult(new GetPromptResult());
handlers.ListResourceTemplatesHandler = (p, c) => Task.FromResult(new ListResourceTemplatesResult());
handlers.ListResourcesHandler = (p, c) => Task.FromResult(new ListResourcesResult());
handlers.ReadResourceHandler = (p, c) => Task.FromResult(new ReadResourceResult());
handlers.CompleteHandler = (p, c) => Task.FromResult(new CompleteResult());
handlers.SubscribeToResourcesHandler = (s, c) => Task.FromResult(new EmptyResult());
handlers.UnsubscribeFromResourcesHandler = (s, c) => Task.FromResult(new EmptyResult());
handlers.ListToolsHandler = async (p, c) => new ListToolsResult();
handlers.CallToolHandler = async (p, c) => new CallToolResponse();
handlers.ListPromptsHandler = async (p, c) => new ListPromptsResult();
handlers.GetPromptHandler = async (p, c) => new GetPromptResult();
handlers.ListResourceTemplatesHandler = async (p, c) => new ListResourceTemplatesResult();
handlers.ListResourcesHandler = async (p, c) => new ListResourcesResult();
handlers.ReadResourceHandler = async (p, c) => new ReadResourceResult();
handlers.CompleteHandler = async (p, c) => new CompleteResult();
handlers.SubscribeToResourcesHandler = async (s, c) => new EmptyResult();
handlers.UnsubscribeFromResourcesHandler = async (s, c) => new EmptyResult();
Assert.NotNull(handlers.ListToolsHandler);
Assert.NotNull(handlers.CallToolHandler);

View file

@ -12,10 +12,7 @@ public class McpServerLoggingLevelTests
services.AddMcpServer()
.WithStdioServerTransport()
.WithSetLoggingLevelHandler((ctx, ct) =>
{
return Task.FromResult(new EmptyResult());
});
.WithSetLoggingLevelHandler(async (ctx, ct) => new EmptyResult());
var provider = services.BuildServiceProvider();
@ -29,10 +26,7 @@ public class McpServerLoggingLevelTests
services.AddMcpServer()
.WithStdioServerTransport()
.WithSetLoggingLevelHandler((ctx, ct) =>
{
return Task.FromResult(new EmptyResult());
});
.WithSetLoggingLevelHandler(async (ctx, ct) => new EmptyResult());
var provider = services.BuildServiceProvider();

View file

@ -33,7 +33,7 @@ public class McpServerPromptTests
Assert.DoesNotContain("server", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
var result = await prompt.GetAsync(
new RequestContext<GetPromptRequestParams>(mockServer.Object, null),
new RequestContext<GetPromptRequestParams>(mockServer.Object),
TestContext.Current.CancellationToken);
Assert.NotNull(result);
Assert.NotNull(result.Messages);
@ -59,16 +59,12 @@ public class McpServerPromptTests
Assert.Contains("something", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
Assert.DoesNotContain("actualMyService", prompt.ProtocolPrompt.Arguments?.Select(a => a.Name) ?? []);
Mock<IMcpServer> mockServer = new();
await Assert.ThrowsAsync<ArgumentException>(async () => await prompt.GetAsync(
new RequestContext<GetPromptRequestParams>(mockServer.Object, null),
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken));
mockServer.SetupGet(x => x.Services).Returns(services);
var result = await prompt.GetAsync(
new RequestContext<GetPromptRequestParams>(mockServer.Object, null),
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object) { Services = services },
TestContext.Current.CancellationToken);
Assert.Equal("Hello", result.Messages[0].Content.Text);
}
@ -89,7 +85,7 @@ public class McpServerPromptTests
}, new() { Services = services });
var result = await prompt.GetAsync(
new RequestContext<GetPromptRequestParams>(null!, null),
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken);
Assert.Equal("Hello", result.Messages[0].Content.Text);
}
@ -102,7 +98,7 @@ public class McpServerPromptTests
typeof(DisposablePromptType));
var result = await prompt1.GetAsync(
new RequestContext<GetPromptRequestParams>(null!, null),
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken);
Assert.Equal("disposals:1", result.Messages[0].Content.Text);
}
@ -115,7 +111,7 @@ public class McpServerPromptTests
typeof(AsyncDisposablePromptType));
var result = await prompt1.GetAsync(
new RequestContext<GetPromptRequestParams>(null!, null),
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken);
Assert.Equal("asyncDisposals:1", result.Messages[0].Content.Text);
}
@ -128,7 +124,7 @@ public class McpServerPromptTests
typeof(AsyncDisposableAndDisposablePromptType));
var result = await prompt1.GetAsync(
new RequestContext<GetPromptRequestParams>(null!, null),
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken);
Assert.Equal("disposals:0, asyncDisposals:1", result.Messages[0].Content.Text);
}
@ -144,7 +140,7 @@ public class McpServerPromptTests
});
var actual = await prompt.GetAsync(
new RequestContext<GetPromptRequestParams>(null!, null),
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken);
Assert.Same(expected, actual);
@ -161,7 +157,7 @@ public class McpServerPromptTests
});
var actual = await prompt.GetAsync(
new RequestContext<GetPromptRequestParams>(null!, null),
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken);
Assert.NotNull(actual);
@ -187,7 +183,7 @@ public class McpServerPromptTests
});
var actual = await prompt.GetAsync(
new RequestContext<GetPromptRequestParams>(null!, null),
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken);
Assert.NotNull(actual);
@ -218,7 +214,7 @@ public class McpServerPromptTests
});
var actual = await prompt.GetAsync(
new RequestContext<GetPromptRequestParams>(null!, null),
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken);
Assert.NotNull(actual);
@ -247,7 +243,7 @@ public class McpServerPromptTests
});
var actual = await prompt.GetAsync(
new RequestContext<GetPromptRequestParams>(null!, null),
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken);
Assert.NotNull(actual);
@ -280,7 +276,7 @@ public class McpServerPromptTests
});
var actual = await prompt.GetAsync(
new RequestContext<GetPromptRequestParams>(null!, null),
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken);
Assert.NotNull(actual);
@ -303,7 +299,7 @@ public class McpServerPromptTests
});
await Assert.ThrowsAsync<InvalidOperationException>(async () => await prompt.GetAsync(
new RequestContext<GetPromptRequestParams>(null!, null),
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken));
}
@ -316,7 +312,7 @@ public class McpServerPromptTests
});
await Assert.ThrowsAsync<InvalidOperationException>(async () => await prompt.GetAsync(
new RequestContext<GetPromptRequestParams>(null!, null),
new RequestContext<GetPromptRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken));
}

View file

@ -13,19 +13,19 @@ public class McpServerResourceTests
services.AddMcpServer()
.WithStdioServerTransport()
.WithListResourceTemplatesHandler((ctx, ct) =>
.WithListResourceTemplatesHandler(async (ctx, ct) =>
{
return Task.FromResult(new ListResourceTemplatesResult
return new ListResourceTemplatesResult
{
ResourceTemplates =
[
new ResourceTemplate { Name = "Static Resource", Description = "A static resource with a numeric ID", UriTemplate = "test://static/resource/{id}" }
]
});
};
})
.WithReadResourceHandler((ctx, ct) =>
.WithReadResourceHandler(async (ctx, ct) =>
{
return Task.FromResult(new ReadResourceResult
return new ReadResourceResult
{
Contents = [new TextResourceContents
{
@ -33,7 +33,7 @@ public class McpServerResourceTests
Text = "Static Resource",
MimeType = "text/plain",
}]
});
};
});
var provider = services.BuildServiceProvider();
@ -48,19 +48,19 @@ public class McpServerResourceTests
services.AddMcpServer()
.WithStdioServerTransport()
.WithListResourcesHandler((ctx, ct) =>
.WithListResourcesHandler(async (ctx, ct) =>
{
return Task.FromResult(new ListResourcesResult
return new ListResourcesResult
{
Resources =
[
new Resource { Name = "Static Resource", Description = "A static resource with a numeric ID", Uri = "test://static/resource/foo.txt" }
]
});
};
})
.WithReadResourceHandler((ctx, ct) =>
.WithReadResourceHandler(async (ctx, ct) =>
{
return Task.FromResult(new ReadResourceResult
return new ReadResourceResult
{
Contents = [new TextResourceContents
{
@ -68,7 +68,7 @@ public class McpServerResourceTests
Text = "Static Resource",
MimeType = "text/plain",
}]
});
};
});
var provider = services.BuildServiceProvider();
@ -82,9 +82,9 @@ public class McpServerResourceTests
var services = new ServiceCollection();
services.AddMcpServer()
.WithStdioServerTransport()
.WithReadResourceHandler((ctx, ct) =>
.WithReadResourceHandler(async (ctx, ct) =>
{
return Task.FromResult(new ReadResourceResult
return new ReadResourceResult
{
Contents = [new TextResourceContents
{
@ -92,7 +92,7 @@ public class McpServerResourceTests
Text = "Static Resource",
MimeType = "text/plain",
}]
});
};
});
var sp = services.BuildServiceProvider();
Assert.Throws<McpException>(() => sp.GetRequiredService<IMcpServer>());

View file

@ -202,8 +202,8 @@ public class McpServerTests : LoggedTest
{
Completions = new()
{
CompleteHandler = (request, ct) =>
Task.FromResult(new CompleteResult
CompleteHandler = async (request, ct) =>
new CompleteResult
{
Completion = new()
{
@ -211,7 +211,7 @@ public class McpServerTests : LoggedTest
Total = 2,
HasMore = true
}
})
}
}
},
method: RequestMethods.CompletionComplete,
@ -234,19 +234,19 @@ public class McpServerTests : LoggedTest
{
Resources = new()
{
ListResourceTemplatesHandler = (request, ct) =>
ListResourceTemplatesHandler = async (request, ct) =>
{
return Task.FromResult(new ListResourceTemplatesResult
return new ListResourceTemplatesResult
{
ResourceTemplates = [new() { UriTemplate = "test", Name = "Test Resource" }]
});
};
},
ListResourcesHandler = (request, ct) =>
ListResourcesHandler = async (request, ct) =>
{
return Task.FromResult(new ListResourcesResult
return new ListResourcesResult
{
Resources = [new() { Uri = "test", Name = "Test Resource" }]
});
};
},
ReadResourceHandler = (request, ct) => throw new NotImplementedException(),
}
@ -270,12 +270,12 @@ public class McpServerTests : LoggedTest
{
Resources = new()
{
ListResourcesHandler = (request, ct) =>
ListResourcesHandler = async (request, ct) =>
{
return Task.FromResult(new ListResourcesResult
return new ListResourcesResult
{
Resources = [new() { Uri = "test", Name = "Test Resource" }]
});
};
},
ReadResourceHandler = (request, ct) => throw new NotImplementedException(),
}
@ -305,12 +305,12 @@ public class McpServerTests : LoggedTest
{
Resources = new()
{
ReadResourceHandler = (request, ct) =>
ReadResourceHandler = async (request, ct) =>
{
return Task.FromResult(new ReadResourceResult
return new ReadResourceResult
{
Contents = [new TextResourceContents { Text = "test" }]
});
};
},
ListResourcesHandler = (request, ct) => throw new NotImplementedException(),
}
@ -342,12 +342,12 @@ public class McpServerTests : LoggedTest
{
Prompts = new()
{
ListPromptsHandler = (request, ct) =>
ListPromptsHandler = async (request, ct) =>
{
return Task.FromResult(new ListPromptsResult
return new ListPromptsResult
{
Prompts = [new() { Name = "test" }]
});
};
},
GetPromptHandler = (request, ct) => throw new NotImplementedException(),
},
@ -377,7 +377,7 @@ public class McpServerTests : LoggedTest
{
Prompts = new()
{
GetPromptHandler = (request, ct) => Task.FromResult(new GetPromptResult { Description = "test" }),
GetPromptHandler = async (request, ct) => new GetPromptResult { Description = "test" },
ListPromptsHandler = (request, ct) => throw new NotImplementedException(),
}
},
@ -405,12 +405,12 @@ public class McpServerTests : LoggedTest
{
Tools = new()
{
ListToolsHandler = (request, ct) =>
ListToolsHandler = async (request, ct) =>
{
return Task.FromResult(new ListToolsResult
return new ListToolsResult
{
Tools = [new() { Name = "test" }]
});
};
},
CallToolHandler = (request, ct) => throw new NotImplementedException(),
}
@ -440,12 +440,12 @@ public class McpServerTests : LoggedTest
{
Tools = new()
{
CallToolHandler = (request, ct) =>
CallToolHandler = async (request, ct) =>
{
return Task.FromResult(new CallToolResponse
return new CallToolResponse
{
Content = [new Content { Text = "test" }]
});
};
},
ListToolsHandler = (request, ct) => throw new NotImplementedException(),
}
@ -623,7 +623,7 @@ public class McpServerTests : LoggedTest
throw new NotImplementedException();
public Task RunAsync(CancellationToken cancellationToken = default) =>
throw new NotImplementedException();
public IAsyncDisposable RegisterNotificationHandler(string method, Func<JsonRpcNotification, CancellationToken, Task> handler) =>
public IAsyncDisposable RegisterNotificationHandler(string method, Func<JsonRpcNotification, CancellationToken, ValueTask> handler) =>
throw new NotImplementedException();
}
@ -639,7 +639,7 @@ public class McpServerTests : LoggedTest
NotificationHandlers = [new(NotificationMethods.ProgressNotification, (notification, cancellationToken) =>
{
notificationReceived.TrySetResult(notification);
return Task.CompletedTask;
return default;
})],
};

View file

@ -40,7 +40,7 @@ public partial class McpServerToolTests
Assert.DoesNotContain("server", JsonSerializer.Serialize(tool.ProtocolTool.InputSchema, McpJsonUtilities.DefaultOptions));
var result = await tool.InvokeAsync(
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
new RequestContext<CallToolRequestParams>(mockServer.Object),
TestContext.Current.CancellationToken);
Assert.Equal("42", result.Content[0].Text);
}
@ -92,14 +92,12 @@ public partial class McpServerToolTests
Mock<IMcpServer> mockServer = new();
var result = await tool.InvokeAsync(
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
new RequestContext<CallToolRequestParams>(mockServer.Object),
TestContext.Current.CancellationToken);
Assert.True(result.IsError);
mockServer.SetupGet(x => x.Services).Returns(services);
result = await tool.InvokeAsync(
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
new RequestContext<CallToolRequestParams>(mockServer.Object) { Services = services },
TestContext.Current.CancellationToken);
Assert.Equal("42", result.Content[0].Text);
}
@ -120,7 +118,7 @@ public partial class McpServerToolTests
}, new() { Services = services });
var result = await tool.InvokeAsync(
new RequestContext<CallToolRequestParams>(null!, null),
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken);
Assert.Equal("42", result.Content[0].Text);
}
@ -135,7 +133,7 @@ public partial class McpServerToolTests
options);
var result = await tool1.InvokeAsync(
new RequestContext<CallToolRequestParams>(null!, null),
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken);
Assert.Equal("""{"disposals":1}""", result.Content[0].Text);
}
@ -150,7 +148,7 @@ public partial class McpServerToolTests
options);
var result = await tool1.InvokeAsync(
new RequestContext<CallToolRequestParams>(null!, null),
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken);
Assert.Equal("""{"asyncDisposals":1}""", result.Content[0].Text);
}
@ -165,7 +163,7 @@ public partial class McpServerToolTests
options);
var result = await tool1.InvokeAsync(
new RequestContext<CallToolRequestParams>(null!, null),
new RequestContext<CallToolRequestParams>(new Mock<IMcpServer>().Object),
TestContext.Current.CancellationToken);
Assert.Equal("""{"asyncDisposals":1,"disposals":0}""", result.Content[0].Text);
}
@ -186,7 +184,7 @@ public partial class McpServerToolTests
});
var result = await tool.InvokeAsync(
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
new RequestContext<CallToolRequestParams>(mockServer.Object),
TestContext.Current.CancellationToken);
Assert.Equal(3, result.Content.Count);
@ -223,7 +221,7 @@ public partial class McpServerToolTests
});
var result = await tool.InvokeAsync(
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
new RequestContext<CallToolRequestParams>(mockServer.Object),
TestContext.Current.CancellationToken);
Assert.Single(result.Content);
@ -251,7 +249,7 @@ public partial class McpServerToolTests
return (string?)null;
});
var result = await tool.InvokeAsync(
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
new RequestContext<CallToolRequestParams>(mockServer.Object),
TestContext.Current.CancellationToken);
Assert.Empty(result.Content);
}
@ -266,7 +264,7 @@ public partial class McpServerToolTests
return "42";
});
var result = await tool.InvokeAsync(
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
new RequestContext<CallToolRequestParams>(mockServer.Object),
TestContext.Current.CancellationToken);
Assert.Single(result.Content);
Assert.Equal("42", result.Content[0].Text);
@ -283,7 +281,7 @@ public partial class McpServerToolTests
return new List<string>() { "42", "43" };
});
var result = await tool.InvokeAsync(
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
new RequestContext<CallToolRequestParams>(mockServer.Object),
TestContext.Current.CancellationToken);
Assert.Equal(2, result.Content.Count);
Assert.Equal("42", result.Content[0].Text);
@ -302,7 +300,7 @@ public partial class McpServerToolTests
return new Content { Text = "42", Type = "text" };
});
var result = await tool.InvokeAsync(
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
new RequestContext<CallToolRequestParams>(mockServer.Object),
TestContext.Current.CancellationToken);
Assert.Single(result.Content);
Assert.Equal("42", result.Content[0].Text);
@ -319,7 +317,7 @@ public partial class McpServerToolTests
return new List<Content>() { new() { Text = "42", Type = "text" }, new() { Data = "1234", Type = "image", MimeType = "image/png" } };
});
var result = await tool.InvokeAsync(
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
new RequestContext<CallToolRequestParams>(mockServer.Object),
TestContext.Current.CancellationToken);
Assert.Equal(2, result.Content.Count);
Assert.Equal("42", result.Content[0].Text);
@ -345,7 +343,7 @@ public partial class McpServerToolTests
return response;
});
var result = await tool.InvokeAsync(
new RequestContext<CallToolRequestParams>(mockServer.Object, null),
new RequestContext<CallToolRequestParams>(mockServer.Object),
TestContext.Current.CancellationToken);
Assert.Same(response, result);