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
29 changed files with 477 additions and 306 deletions

View file

@ -948,7 +948,7 @@ public static class McpClientExtensions
/// </para>
/// </remarks>
/// <exception cref="ArgumentNullException"><paramref name="chatClient"/> is <see langword="null"/>.</exception>
public static Func<CreateMessageRequestParams?, IProgress<ProgressNotificationValue>, CancellationToken, Task<CreateMessageResult>> CreateSamplingHandler(
public static Func<CreateMessageRequestParams?, IProgress<ProgressNotificationValue>, CancellationToken, ValueTask<CreateMessageResult>> CreateSamplingHandler(
this IChatClient chatClient)
{
Throw.IfNull(chatClient);

View file

@ -275,7 +275,7 @@ public static partial class McpServerBuilderExtensions
/// resource system where templates define the URI patterns and the read handler provides the actual content.
/// </para>
/// </remarks>
public static IMcpServerBuilder WithListResourceTemplatesHandler(this IMcpServerBuilder builder, Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, Task<ListResourceTemplatesResult>> handler)
public static IMcpServerBuilder WithListResourceTemplatesHandler(this IMcpServerBuilder builder, Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, ValueTask<ListResourceTemplatesResult>> handler)
{
Throw.IfNull(builder);
@ -308,7 +308,7 @@ public static partial class McpServerBuilderExtensions
/// executes them when invoked by clients.
/// </para>
/// </remarks>
public static IMcpServerBuilder WithListToolsHandler(this IMcpServerBuilder builder, Func<RequestContext<ListToolsRequestParams>, CancellationToken, Task<ListToolsResult>> handler)
public static IMcpServerBuilder WithListToolsHandler(this IMcpServerBuilder builder, Func<RequestContext<ListToolsRequestParams>, CancellationToken, ValueTask<ListToolsResult>> handler)
{
Throw.IfNull(builder);
@ -328,7 +328,7 @@ public static partial class McpServerBuilderExtensions
/// This method is typically paired with <see cref="WithListToolsHandler"/> to provide a complete tools implementation,
/// where <see cref="WithListToolsHandler"/> advertises available tools and this handler executes them.
/// </remarks>
public static IMcpServerBuilder WithCallToolHandler(this IMcpServerBuilder builder, Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>> handler)
public static IMcpServerBuilder WithCallToolHandler(this IMcpServerBuilder builder, Func<RequestContext<CallToolRequestParams>, CancellationToken, ValueTask<CallToolResponse>> handler)
{
Throw.IfNull(builder);
@ -361,7 +361,7 @@ public static partial class McpServerBuilderExtensions
/// produces them when invoked by clients.
/// </para>
/// </remarks>
public static IMcpServerBuilder WithListPromptsHandler(this IMcpServerBuilder builder, Func<RequestContext<ListPromptsRequestParams>, CancellationToken, Task<ListPromptsResult>> handler)
public static IMcpServerBuilder WithListPromptsHandler(this IMcpServerBuilder builder, Func<RequestContext<ListPromptsRequestParams>, CancellationToken, ValueTask<ListPromptsResult>> handler)
{
Throw.IfNull(builder);
@ -376,7 +376,7 @@ public static partial class McpServerBuilderExtensions
/// <param name="handler">The handler function that processes prompt requests.</param>
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
public static IMcpServerBuilder WithGetPromptHandler(this IMcpServerBuilder builder, Func<RequestContext<GetPromptRequestParams>, CancellationToken, Task<GetPromptResult>> handler)
public static IMcpServerBuilder WithGetPromptHandler(this IMcpServerBuilder builder, Func<RequestContext<GetPromptRequestParams>, CancellationToken, ValueTask<GetPromptResult>> handler)
{
Throw.IfNull(builder);
@ -397,7 +397,7 @@ public static partial class McpServerBuilderExtensions
/// where this handler advertises available resources and the read handler provides their content when requested.
/// </para>
/// </remarks>
public static IMcpServerBuilder WithListResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<ListResourcesRequestParams>, CancellationToken, Task<ListResourcesResult>> handler)
public static IMcpServerBuilder WithListResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<ListResourcesRequestParams>, CancellationToken, ValueTask<ListResourcesResult>> handler)
{
Throw.IfNull(builder);
@ -416,7 +416,7 @@ public static partial class McpServerBuilderExtensions
/// This handler is typically paired with <see cref="WithListResourcesHandler"/> to provide a complete resources implementation,
/// where the list handler advertises available resources and the read handler provides their content when requested.
/// </remarks>
public static IMcpServerBuilder WithReadResourceHandler(this IMcpServerBuilder builder, Func<RequestContext<ReadResourceRequestParams>, CancellationToken, Task<ReadResourceResult>> handler)
public static IMcpServerBuilder WithReadResourceHandler(this IMcpServerBuilder builder, Func<RequestContext<ReadResourceRequestParams>, CancellationToken, ValueTask<ReadResourceResult>> handler)
{
Throw.IfNull(builder);
@ -435,7 +435,7 @@ public static partial class McpServerBuilderExtensions
/// The completion handler is invoked when clients request suggestions for argument values.
/// This enables auto-complete functionality for both prompt arguments and resource references.
/// </remarks>
public static IMcpServerBuilder WithCompleteHandler(this IMcpServerBuilder builder, Func<RequestContext<CompleteRequestParams>, CancellationToken, Task<CompleteResult>> handler)
public static IMcpServerBuilder WithCompleteHandler(this IMcpServerBuilder builder, Func<RequestContext<CompleteRequestParams>, CancellationToken, ValueTask<CompleteResult>> handler)
{
Throw.IfNull(builder);
@ -465,7 +465,7 @@ public static partial class McpServerBuilderExtensions
/// resources and to send appropriate notifications through the connection when resources change.
/// </para>
/// </remarks>
public static IMcpServerBuilder WithSubscribeToResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<SubscribeRequestParams>, CancellationToken, Task<EmptyResult>> handler)
public static IMcpServerBuilder WithSubscribeToResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<SubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>> handler)
{
Throw.IfNull(builder);
@ -495,7 +495,7 @@ public static partial class McpServerBuilderExtensions
/// to the specified resource.
/// </para>
/// </remarks>
public static IMcpServerBuilder WithUnsubscribeFromResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, Task<EmptyResult>> handler)
public static IMcpServerBuilder WithUnsubscribeFromResourcesHandler(this IMcpServerBuilder builder, Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>> handler)
{
Throw.IfNull(builder);
@ -522,7 +522,7 @@ public static partial class McpServerBuilderExtensions
/// most recently set level.
/// </para>
/// </remarks>
public static IMcpServerBuilder WithSetLoggingLevelHandler(this IMcpServerBuilder builder, Func<RequestContext<SetLevelRequestParams>, CancellationToken, Task<EmptyResult>> handler)
public static IMcpServerBuilder WithSetLoggingLevelHandler(this IMcpServerBuilder builder, Func<RequestContext<SetLevelRequestParams>, CancellationToken, ValueTask<EmptyResult>> handler)
{
Throw.IfNull(builder);

View file

@ -17,7 +17,7 @@ internal sealed class SingleSessionMcpServerHostedService(IMcpServer session, IH
{
try
{
await session.RunAsync(stoppingToken);
await session.RunAsync(stoppingToken).ConfigureAwait(false);
}
finally
{

View file

@ -69,5 +69,5 @@ public interface IMcpEndpoint : IAsyncDisposable
/// <param name="method">The notification method.</param>
/// <param name="handler">The handler to be invoked.</param>
/// <returns>An <see cref="IDisposable"/> that will remove the registered handler when disposed.</returns>
IAsyncDisposable RegisterNotificationHandler(string method, Func<JsonRpcNotification, CancellationToken, Task> handler);
IAsyncDisposable RegisterNotificationHandler(string method, Func<JsonRpcNotification, CancellationToken, ValueTask> handler);
}

View file

@ -82,7 +82,7 @@ public class StreamServerTransport : TransportBase
await JsonSerializer.SerializeAsync(_outputStream, message, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(IJsonRpcMessage)), cancellationToken).ConfigureAwait(false);
await _outputStream.WriteAsync(s_newlineBytes, cancellationToken).ConfigureAwait(false);
await _outputStream.FlushAsync(cancellationToken).ConfigureAwait(false);;
await _outputStream.FlushAsync(cancellationToken).ConfigureAwait(false);
_logger.TransportSentMessage(_endpointName, id);
}

View file

@ -77,5 +77,5 @@ public class ClientCapabilities
/// </para>
/// </remarks>
[JsonIgnore]
public IEnumerable<KeyValuePair<string, Func<JsonRpcNotification, CancellationToken, Task>>>? NotificationHandlers { get; set; }
public IEnumerable<KeyValuePair<string, Func<JsonRpcNotification, CancellationToken, ValueTask>>>? NotificationHandlers { get; set; }
}

View file

@ -33,5 +33,5 @@ public class CompletionsCapability
/// and should return appropriate completion suggestions.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<CompleteRequestParams>, CancellationToken, Task<CompleteResult>>? CompleteHandler { get; set; }
public Func<RequestContext<CompleteRequestParams>, CancellationToken, ValueTask<CompleteResult>>? CompleteHandler { get; set; }
}

View file

@ -9,5 +9,5 @@ namespace ModelContextProtocol.Protocol.Types;
public class EmptyResult
{
[JsonIgnore]
internal static Task<EmptyResult> CompletedTask { get; } = Task.FromResult(new EmptyResult());
internal static EmptyResult Instance { get; } = new();
}

View file

@ -19,5 +19,5 @@ public class LoggingCapability
/// Gets or sets the handler for set logging level requests from clients.
/// </summary>
[JsonIgnore]
public Func<RequestContext<SetLevelRequestParams>, CancellationToken, Task<EmptyResult>>? SetLoggingLevelHandler { get; set; }
public Func<RequestContext<SetLevelRequestParams>, CancellationToken, ValueTask<EmptyResult>>? SetLoggingLevelHandler { get; set; }
}

View file

@ -41,7 +41,7 @@ public class PromptsCapability
/// along with any prompts defined in <see cref="PromptCollection"/>.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<ListPromptsRequestParams>, CancellationToken, Task<ListPromptsResult>>? ListPromptsHandler { get; set; }
public Func<RequestContext<ListPromptsRequestParams>, CancellationToken, ValueTask<ListPromptsResult>>? ListPromptsHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.PromptsGet"/> requests.
@ -58,7 +58,7 @@ public class PromptsCapability
/// </para>
/// </remarks>
[JsonIgnore]
public Func<RequestContext<GetPromptRequestParams>, CancellationToken, Task<GetPromptResult>>? GetPromptHandler { get; set; }
public Func<RequestContext<GetPromptRequestParams>, CancellationToken, ValueTask<GetPromptResult>>? GetPromptHandler { get; set; }
/// <summary>
/// Gets or sets a collection of prompts that will be served by the server.

View file

@ -40,7 +40,7 @@ public class ResourcesCapability
/// allowing clients to discover available resource types and their access patterns.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, Task<ListResourceTemplatesResult>>? ListResourceTemplatesHandler { get; set; }
public Func<RequestContext<ListResourceTemplatesRequestParams>, CancellationToken, ValueTask<ListResourceTemplatesResult>>? ListResourceTemplatesHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesList"/> requests.
@ -50,7 +50,7 @@ public class ResourcesCapability
/// The implementation should return a <see cref="ListResourcesResult"/> with the matching resources.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<ListResourcesRequestParams>, CancellationToken, Task<ListResourcesResult>>? ListResourcesHandler { get; set; }
public Func<RequestContext<ListResourcesRequestParams>, CancellationToken, ValueTask<ListResourcesResult>>? ListResourcesHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesRead"/> requests.
@ -62,7 +62,7 @@ public class ResourcesCapability
/// its contents in a ReadResourceResult object.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<ReadResourceRequestParams>, CancellationToken, Task<ReadResourceResult>>? ReadResourceHandler { get; set; }
public Func<RequestContext<ReadResourceRequestParams>, CancellationToken, ValueTask<ReadResourceResult>>? ReadResourceHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesSubscribe"/> requests.
@ -75,7 +75,7 @@ public class ResourcesCapability
/// requiring polling.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<SubscribeRequestParams>, CancellationToken, Task<EmptyResult>>? SubscribeToResourcesHandler { get; set; }
public Func<RequestContext<SubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>>? SubscribeToResourcesHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ResourcesUnsubscribe"/> requests.
@ -86,5 +86,5 @@ public class ResourcesCapability
/// about the specified resource.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, Task<EmptyResult>>? UnsubscribeFromResourcesHandler { get; set; }
public Func<RequestContext<UnsubscribeRequestParams>, CancellationToken, ValueTask<EmptyResult>>? UnsubscribeFromResourcesHandler { get; set; }
}

View file

@ -41,5 +41,5 @@ public class RootsCapability
/// The handler receives request parameters and should return a <see cref="ListRootsResult"/> containing the collection of available roots.
/// </remarks>
[JsonIgnore]
public Func<ListRootsRequestParams?, CancellationToken, Task<ListRootsResult>>? RootsHandler { get; set; }
public Func<ListRootsRequestParams?, CancellationToken, ValueTask<ListRootsResult>>? RootsHandler { get; set; }
}

View file

@ -40,5 +40,5 @@ public class SamplingCapability
/// </para>
/// </remarks>
[JsonIgnore]
public Func<CreateMessageRequestParams?, IProgress<ProgressNotificationValue>, CancellationToken, Task<CreateMessageResult>>? SamplingHandler { get; set; }
public Func<CreateMessageRequestParams?, IProgress<ProgressNotificationValue>, CancellationToken, ValueTask<CreateMessageResult>>? SamplingHandler { get; set; }
}

View file

@ -83,5 +83,5 @@ public class ServerCapabilities
/// </para>
/// </remarks>
[JsonIgnore]
public IEnumerable<KeyValuePair<string, Func<JsonRpcNotification, CancellationToken, Task>>>? NotificationHandlers { get; set; }
public IEnumerable<KeyValuePair<string, Func<JsonRpcNotification, CancellationToken, ValueTask>>>? NotificationHandlers { get; set; }
}

View file

@ -34,7 +34,7 @@ public class ToolsCapability
/// and the tools from the collection will be combined to form the complete list of available tools.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<ListToolsRequestParams>, CancellationToken, Task<ListToolsResult>>? ListToolsHandler { get; set; }
public Func<RequestContext<ListToolsRequestParams>, CancellationToken, ValueTask<ListToolsResult>>? ListToolsHandler { get; set; }
/// <summary>
/// Gets or sets the handler for <see cref="RequestMethods.ToolsCall"/> requests.
@ -46,7 +46,7 @@ public class ToolsCapability
/// being called and its arguments, and should return a <see cref="CallToolResponse"/> with the execution results.
/// </remarks>
[JsonIgnore]
public Func<RequestContext<CallToolRequestParams>, CancellationToken, Task<CallToolResponse>>? CallToolHandler { get; set; }
public Func<RequestContext<CallToolRequestParams>, CancellationToken, ValueTask<CallToolResponse>>? CallToolHandler { get; set; }
/// <summary>
/// Gets or sets a collection of tools served by the server.

View file

@ -99,7 +99,7 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
{
ExcludeFromSchema = true,
BindParameter = (pi, args) =>
GetRequestContext(args)?.Server?.Services?.GetService(pi.ParameterType) ??
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
(pi.HasDefaultValue ? null :
throw new ArgumentException("No service of the requested type was found.")),
};
@ -111,7 +111,7 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
{
ExcludeFromSchema = true,
BindParameter = (pi, args) =>
(GetRequestContext(args)?.Server?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
(pi.HasDefaultValue ? null :
throw new ArgumentException("No service of the requested type was found.")),
};
@ -206,7 +206,7 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
/// <item><description><see cref="ChatMessage"/> objects are converted to prompt messages</description></item>
/// </list>
/// </remarks>
public override async Task<GetPromptResult> GetAsync(
public override async ValueTask<GetPromptResult> GetAsync(
RequestContext<GetPromptRequestParams> request, CancellationToken cancellationToken = default)
{
Throw.IfNull(request);
@ -214,7 +214,7 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
AIFunctionArguments arguments = new()
{
Services = request.Server?.Services,
Services = request.Services,
Context = new Dictionary<object, object?>() { [typeof(RequestContext<GetPromptRequestParams>)] = request }
};

View file

@ -120,7 +120,7 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
{
ExcludeFromSchema = true,
BindParameter = (pi, args) =>
GetRequestContext(args)?.Server?.Services?.GetService(pi.ParameterType) ??
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
(pi.HasDefaultValue ? null :
throw new ArgumentException("No service of the requested type was found.")),
};
@ -132,7 +132,7 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
{
ExcludeFromSchema = true,
BindParameter = (pi, args) =>
(GetRequestContext(args)?.Server?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
(pi.HasDefaultValue ? null :
throw new ArgumentException("No service of the requested type was found.")),
};
@ -237,7 +237,7 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
public override Tool ProtocolTool { get; }
/// <inheritdoc />
public override async Task<CallToolResponse> InvokeAsync(
public override async ValueTask<CallToolResponse> InvokeAsync(
RequestContext<CallToolRequestParams> request, CancellationToken cancellationToken = default)
{
Throw.IfNull(request);
@ -245,7 +245,7 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
AIFunctionArguments arguments = new()
{
Services = request.Server?.Services,
Services = request.Services,
Context = new Dictionary<object, object?>() { [typeof(RequestContext<CallToolRequestParams>)] = request }
};

View file

@ -24,7 +24,7 @@ public abstract class DelegatingMcpServerPrompt : McpServerPrompt
public override Prompt ProtocolPrompt => _innerPrompt.ProtocolPrompt;
/// <inheritdoc />
public override Task<GetPromptResult> GetAsync(
public override ValueTask<GetPromptResult> GetAsync(
RequestContext<GetPromptRequestParams> request,
CancellationToken cancellationToken = default) =>
_innerPrompt.GetAsync(request, cancellationToken);

View file

@ -24,7 +24,7 @@ public abstract class DelegatingMcpServerTool : McpServerTool
public override Tool ProtocolTool => _innerTool.ProtocolTool;
/// <inheritdoc />
public override Task<CallToolResponse> InvokeAsync(
public override ValueTask<CallToolResponse> InvokeAsync(
RequestContext<CallToolRequestParams> request,
CancellationToken cancellationToken = default) =>
_innerTool.InvokeAsync(request, cancellationToken);

View file

@ -1,3 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol.Messages;
using ModelContextProtocol.Protocol.Transport;
@ -7,6 +8,8 @@ using ModelContextProtocol.Utils;
using ModelContextProtocol.Utils.Json;
using System.Runtime.CompilerServices;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
namespace ModelContextProtocol.Server;
/// <inheritdoc />
@ -19,6 +22,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
};
private readonly ITransport _sessionTransport;
private readonly bool _servicesScopePerRequest;
private readonly EventHandler? _toolsChangedDelegate;
private readonly EventHandler? _promptsChangedDelegate;
@ -54,6 +58,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
ServerOptions = options;
Services = serviceProvider;
_endpointName = $"Server ({options.ServerInfo?.Name ?? DefaultImplementation.Name} {options.ServerInfo?.Version ?? DefaultImplementation.Version})";
_servicesScopePerRequest = options.ScopeRequests;
// Configure all request handlers based on the supplied options.
SetInitializeHandler(options);
@ -154,7 +159,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
private void SetPingHandler()
{
RequestHandlers.Set(RequestMethods.Ping,
(request, _) => Task.FromResult(new PingResult()),
async (request, _) => new PingResult(),
McpJsonUtilities.JsonContext.Default.JsonNode,
McpJsonUtilities.JsonContext.Default.PingResult);
}
@ -162,7 +167,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
private void SetInitializeHandler(McpServerOptions options)
{
RequestHandlers.Set(RequestMethods.Initialize,
(request, _) =>
async (request, _) =>
{
ClientCapabilities = request?.Capabilities ?? new();
ClientInfo = request?.ClientInfo;
@ -171,13 +176,13 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
_endpointName = $"{_endpointName}, Client ({ClientInfo?.Name} {ClientInfo?.Version})";
GetSessionOrThrow().EndpointName = _endpointName;
return Task.FromResult(new InitializeResult
return new InitializeResult
{
ProtocolVersion = options.ProtocolVersion,
Instructions = options.ServerInstructions,
ServerInfo = options.ServerInfo ?? DefaultImplementation,
Capabilities = ServerCapabilities ?? new(),
});
};
},
McpJsonUtilities.JsonContext.Default.InitializeRequestParams,
McpJsonUtilities.JsonContext.Default.InitializeResult);
@ -196,7 +201,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
// This capability is not optional, so return an empty result if there is no handler.
RequestHandlers.Set(
RequestMethods.CompletionComplete,
(request, cancellationToken) => completeHandler(new(this, request), cancellationToken),
(request, cancellationToken) => InvokeHandlerAsync(completeHandler, request, cancellationToken),
McpJsonUtilities.JsonContext.Default.CompleteRequestParams,
McpJsonUtilities.JsonContext.Default.CompleteResult);
}
@ -217,24 +222,24 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
throw new McpException("Resources capability was enabled, but ListResources and/or ReadResource handlers were not specified.");
}
listResourcesHandler ??= (static (_, _) => Task.FromResult(new ListResourcesResult()));
listResourcesHandler ??= static async (_, _) => new ListResourcesResult();
RequestHandlers.Set(
RequestMethods.ResourcesList,
(request, cancellationToken) => listResourcesHandler(new(this, request), cancellationToken),
(request, cancellationToken) => InvokeHandlerAsync(listResourcesHandler, request, cancellationToken),
McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourcesResult);
RequestHandlers.Set(
RequestMethods.ResourcesRead,
(request, cancellationToken) => readResourceHandler(new(this, request), cancellationToken),
(request, cancellationToken) => InvokeHandlerAsync(readResourceHandler, request, cancellationToken),
McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams,
McpJsonUtilities.JsonContext.Default.ReadResourceResult);
listResourceTemplatesHandler ??= (static (_, _) => Task.FromResult(new ListResourceTemplatesResult()));
listResourceTemplatesHandler ??= static async (_, _) => new ListResourceTemplatesResult();
RequestHandlers.Set(
RequestMethods.ResourcesTemplatesList,
(request, cancellationToken) => listResourceTemplatesHandler(new(this, request), cancellationToken),
(request, cancellationToken) => InvokeHandlerAsync(listResourceTemplatesHandler, request, cancellationToken),
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult);
@ -252,13 +257,13 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
RequestHandlers.Set(
RequestMethods.ResourcesSubscribe,
(request, cancellationToken) => subscribeHandler(new(this, request), cancellationToken),
(request, cancellationToken) => InvokeHandlerAsync(subscribeHandler, request, cancellationToken),
McpJsonUtilities.JsonContext.Default.SubscribeRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult);
RequestHandlers.Set(
RequestMethods.ResourcesUnsubscribe,
(request, cancellationToken) => unsubscribeHandler(new(this, request), cancellationToken),
(request, cancellationToken) => InvokeHandlerAsync(unsubscribeHandler, request, cancellationToken),
McpJsonUtilities.JsonContext.Default.UnsubscribeRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult);
}
@ -345,13 +350,13 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
RequestHandlers.Set(
RequestMethods.PromptsList,
(request, cancellationToken) => listPromptsHandler(new(this, request), cancellationToken),
(request, cancellationToken) => InvokeHandlerAsync(listPromptsHandler, request, cancellationToken),
McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
McpJsonUtilities.JsonContext.Default.ListPromptsResult);
RequestHandlers.Set(
RequestMethods.PromptsGet,
(request, cancellationToken) => getPromptHandler(new(this, request), cancellationToken),
(request, cancellationToken) => InvokeHandlerAsync(getPromptHandler, request, cancellationToken),
McpJsonUtilities.JsonContext.Default.GetPromptRequestParams,
McpJsonUtilities.JsonContext.Default.GetPromptResult);
}
@ -438,13 +443,13 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
RequestHandlers.Set(
RequestMethods.ToolsList,
(request, cancellationToken) => listToolsHandler(new(this, request), cancellationToken),
(request, cancellationToken) => InvokeHandlerAsync(listToolsHandler, request, cancellationToken),
McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
McpJsonUtilities.JsonContext.Default.ListToolsResult);
RequestHandlers.Set(
RequestMethods.ToolsCall,
(request, cancellationToken) => callToolHandler(new(this, request), cancellationToken),
(request, cancellationToken) => InvokeHandlerAsync(callToolHandler, request, cancellationToken),
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
McpJsonUtilities.JsonContext.Default.CallToolResponse);
}
@ -473,16 +478,51 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
// If a handler was provided, now delegate to it.
if (setLoggingLevelHandler is not null)
{
return setLoggingLevelHandler(new(this, request), cancellationToken);
return InvokeHandlerAsync(setLoggingLevelHandler, request, cancellationToken);
}
// Otherwise, consider it handled.
return EmptyResult.CompletedTask;
return new ValueTask<EmptyResult>(EmptyResult.Instance);
},
McpJsonUtilities.JsonContext.Default.SetLevelRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult);
}
private ValueTask<TResult> InvokeHandlerAsync<TParams, TResult>(
Func<RequestContext<TParams>, CancellationToken, ValueTask<TResult>> handler,
TParams? args,
CancellationToken cancellationToken)
{
return _servicesScopePerRequest ?
InvokeScopedAsync(handler, args, cancellationToken) :
handler(new(this) { Params = args }, cancellationToken);
async ValueTask<TResult> InvokeScopedAsync(
Func<RequestContext<TParams>, CancellationToken, ValueTask<TResult>> handler,
TParams? args,
CancellationToken cancellationToken)
{
var scope = Services?.GetService<IServiceScopeFactory>()?.CreateAsyncScope();
try
{
return await handler(
new RequestContext<TParams>(this)
{
Services = scope?.ServiceProvider ?? Services,
Params = args
},
cancellationToken).ConfigureAwait(false);
}
finally
{
if (scope is not null)
{
await scope.Value.DisposeAsync().ConfigureAwait(false);
}
}
}
}
/// <summary>Maps a <see cref="LogLevel"/> to a <see cref="LoggingLevel"/>.</summary>
internal static LoggingLevel ToLoggingLevel(LogLevel level) =>
level switch

Some files were not shown because too many files have changed in this diff Show more