Skip to content

Wrap each request in a service scope #276

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ root = true
# C# files
[*.cs]

# Compiler
dotnet_diagnostic.CS1998.severity = suggestion # CS1998: Missing awaits

# Code Analysis
dotnet_diagnostic.CA1002.severity = none # CA1002: Do not expose generic lists
dotnet_diagnostic.CA1031.severity = none # CA1031: Do not catch general exception types
Expand Down
53 changes: 27 additions & 26 deletions samples/EverythingServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Server;

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously

var builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(consoleLogOptions =>
{
Expand All @@ -31,17 +33,17 @@
.WithTools<TinyImageTool>()
.WithPrompts<ComplexPromptType>()
.WithPrompts<SimplePromptType>()
.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) =>
{
var uri = ctx.Params?.Uri;

Expand All @@ -61,27 +63,27 @@

if (resource.MimeType == "text/plain")
{
return Task.FromResult(new ReadResourceResult
return new ReadResourceResult
{
Contents = [new TextResourceContents
{
Text = resource.Description!,
MimeType = resource.MimeType,
Uri = resource.Uri,
}]
});
};
}
else
{
return Task.FromResult(new ReadResourceResult
return new ReadResourceResult
{
Contents = [new BlobResourceContents
{
Blob = resource.Description!,
MimeType = resource.MimeType,
Uri = resource.Uri,
}]
});
};
}
})
.WithSubscribeToResourcesHandler(async (ctx, ct) =>
Expand All @@ -106,16 +108,16 @@ await ctx.Server.RequestSamplingAsync([

return new EmptyResult();
})
.WithUnsubscribeFromResourcesHandler((ctx, ct) =>
.WithUnsubscribeFromResourcesHandler(async (ctx, ct) =>
{
var uri = ctx.Params?.Uri;
if (uri is not null)
{
subscriptions.Remove(uri);
}
return Task.FromResult(new EmptyResult());
return new EmptyResult();
})
.WithCompleteHandler((ctx, ct) =>
.WithCompleteHandler(async (ctx, ct) =>
{
var exampleCompletions = new Dictionary<string, IEnumerable<string>>
{
Expand All @@ -128,7 +130,7 @@ await ctx.Server.RequestSamplingAsync([
{
throw new NotSupportedException($"Params are required.");
}

var @ref = @params.Ref;
var argument = @params.Argument;

Expand All @@ -138,15 +140,15 @@ await ctx.Server.RequestSamplingAsync([

if (resourceId is null)
{
return Task.FromResult(new CompleteResult());
return new CompleteResult();
}

var values = exampleCompletions["resourceId"].Where(id => id.StartsWith(argument.Value));

return Task.FromResult(new CompleteResult
return new CompleteResult
{
Completion = new Completion { Values = [..values], HasMore = false, Total = values.Count() }
});
Completion = new Completion { Values = [.. values], HasMore = false, Total = values.Count() }
};
}

if (@ref.Type == "ref/prompt")
Expand All @@ -157,10 +159,10 @@ await ctx.Server.RequestSamplingAsync([
}

var values = value.Where(value => value.StartsWith(argument.Value));
return Task.FromResult(new CompleteResult
return new CompleteResult
{
Completion = new Completion { Values = [..values], HasMore = false, Total = values.Count() }
});
Completion = new Completion { Values = [.. values], HasMore = false, Total = values.Count() }
};
}

throw new NotSupportedException($"Unknown reference type: {@ref.Type}");
Expand All @@ -175,15 +177,14 @@ await ctx.Server.RequestSamplingAsync([
_minimumLoggingLevel = ctx.Params.Level;

await ctx.Server.SendNotificationAsync("notifications/message", new
{
Level = "debug",
Logger = "test-server",
Data = $"Logging level set to {_minimumLoggingLevel}",
}, cancellationToken: ct);
{
Level = "debug",
Logger = "test-server",
Data = $"Logging level set to {_minimumLoggingLevel}",
}, cancellationToken: ct);

return new EmptyResult();
})
;
});

builder.Services.AddSingleton(subscriptions);
builder.Services.AddHostedService<SubscriptionMessageSender>();
Expand Down
2 changes: 1 addition & 1 deletion src/ModelContextProtocol/Client/McpClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,7 @@ internal static CreateMessageResult ToCreateMessageResult(this ChatResponse chat
/// </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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ where t.GetCustomAttribute<McpServerPromptTypeAttribute>() is not null
/// 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);

Expand Down Expand Up @@ -308,7 +308,7 @@ public static IMcpServerBuilder WithListResourceTemplatesHandler(this IMcpServer
/// 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);

Expand All @@ -328,7 +328,7 @@ public static IMcpServerBuilder WithListToolsHandler(this IMcpServerBuilder buil
/// 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);

Expand Down Expand Up @@ -361,7 +361,7 @@ public static IMcpServerBuilder WithCallToolHandler(this IMcpServerBuilder build
/// 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);

Expand All @@ -376,7 +376,7 @@ public static IMcpServerBuilder WithListPromptsHandler(this IMcpServerBuilder bu
/// <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);

Expand All @@ -397,7 +397,7 @@ public static IMcpServerBuilder WithGetPromptHandler(this IMcpServerBuilder buil
/// 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);

Expand All @@ -416,7 +416,7 @@ public static IMcpServerBuilder WithListResourcesHandler(this IMcpServerBuilder
/// 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);

Expand All @@ -435,7 +435,7 @@ public static IMcpServerBuilder WithReadResourceHandler(this IMcpServerBuilder b
/// 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);

Expand Down Expand Up @@ -465,7 +465,7 @@ public static IMcpServerBuilder WithCompleteHandler(this IMcpServerBuilder build
/// 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);

Expand Down Expand Up @@ -495,7 +495,7 @@ public static IMcpServerBuilder WithSubscribeToResourcesHandler(this IMcpServerB
/// 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);

Expand All @@ -522,7 +522,7 @@ public static IMcpServerBuilder WithUnsubscribeFromResourcesHandler(this IMcpSer
/// 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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
try
{
await session.RunAsync(stoppingToken);
await session.RunAsync(stoppingToken).ConfigureAwait(false);
}
finally
{
Expand Down
2 changes: 1 addition & 1 deletion src/ModelContextProtocol/IMcpEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public override async Task SendMessageAsync(IJsonRpcMessage message, Cancellatio

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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
2 changes: 1 addition & 1 deletion src/ModelContextProtocol/Protocol/Types/EmptyResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
4 changes: 2 additions & 2 deletions src/ModelContextProtocol/Protocol/Types/PromptsCapability.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
Loading