Skip to content

Commit 0b528bf

Browse files
committed
PR feedback
1 parent 0a6ebca commit 0b528bf

7 files changed

Lines changed: 106 additions & 89 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using Microsoft.Extensions.Caching.Distributed;
2+
using Microsoft.Extensions.Options;
3+
using ModelContextProtocol.Server;
4+
5+
namespace ModelContextProtocol.AspNetCore;
6+
7+
/// <summary>
8+
/// Configures <see cref="DistributedCacheEventStreamStoreOptions"/> by resolving
9+
/// the <see cref="IDistributedCache"/> from DI when not explicitly set.
10+
/// </summary>
11+
internal sealed class DistributedCacheEventStreamStoreOptionsSetup(IDistributedCache cache) : IConfigureOptions<DistributedCacheEventStreamStoreOptions>
12+
{
13+
public void Configure(DistributedCacheEventStreamStoreOptions options)
14+
{
15+
options.Cache ??= cache;
16+
}
17+
}

src/ModelContextProtocol.AspNetCore/HttpMcpServerBuilderExtensions.cs

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using Microsoft.AspNetCore.Authorization;
22
using Microsoft.Extensions.Caching.Distributed;
33
using Microsoft.Extensions.DependencyInjection.Extensions;
4-
using Microsoft.Extensions.Logging;
54
using Microsoft.Extensions.Options;
65
using ModelContextProtocol.AspNetCore;
76
using ModelContextProtocol.Server;
@@ -69,8 +68,7 @@ public static IMcpServerBuilder AddAuthorizationFilters(this IMcpServerBuilder b
6968
}
7069

7170
/// <summary>
72-
/// Registers a <see cref="DistributedCacheEventStreamStore"/> as the <see cref="ISseEventStreamStore"/> for SSE resumability,
73-
/// using the <see cref="IDistributedCache"/> already registered in the service collection.
71+
/// Registers a <see cref="DistributedCacheEventStreamStore"/> as the <see cref="ISseEventStreamStore"/> for SSE resumability.
7472
/// </summary>
7573
/// <param name="builder">The builder instance.</param>
7674
/// <param name="configureOptions">An optional action to configure <see cref="DistributedCacheEventStreamStoreOptions"/>.</param>
@@ -79,41 +77,19 @@ public static IMcpServerBuilder AddAuthorizationFilters(this IMcpServerBuilder b
7977
/// <remarks>
8078
/// <para>
8179
/// An <see cref="IDistributedCache"/> implementation must be registered in the service collection before calling this method.
82-
/// For example, call <c>builder.Services.AddDistributedMemoryCache()</c> for an in-memory cache or
83-
/// <c>builder.Services.AddStackExchangeRedisCache(...)</c> for Redis.
80+
/// The registered cache is automatically assigned to <see cref="DistributedCacheEventStreamStoreOptions.Cache"/>.
8481
/// </para>
8582
/// <para>
8683
/// To use a specific <see cref="IDistributedCache"/> instance instead of the one registered in DI,
87-
/// use the overload that accepts an <see cref="IDistributedCache"/> parameter.
84+
/// set the <see cref="DistributedCacheEventStreamStoreOptions.Cache"/> property in the <paramref name="configureOptions"/> callback.
8885
/// </para>
8986
/// </remarks>
9087
public static IMcpServerBuilder WithDistributedCacheEventStreamStore(this IMcpServerBuilder builder, Action<DistributedCacheEventStreamStoreOptions>? configureOptions = null)
91-
=> builder.WithDistributedCacheEventStreamStore(cache: null, configureOptions);
92-
93-
/// <summary>
94-
/// Registers a <see cref="DistributedCacheEventStreamStore"/> as the <see cref="ISseEventStreamStore"/> for SSE resumability,
95-
/// using the specified <see cref="IDistributedCache"/>.
96-
/// </summary>
97-
/// <param name="builder">The builder instance.</param>
98-
/// <param name="cache">The <see cref="IDistributedCache"/> to use for event storage, or <see langword="null"/> to resolve from DI.</param>
99-
/// <param name="configureOptions">An optional action to configure <see cref="DistributedCacheEventStreamStoreOptions"/>.</param>
100-
/// <returns>The builder provided in <paramref name="builder"/>.</returns>
101-
/// <exception cref="ArgumentNullException"><paramref name="builder"/> is <see langword="null"/>.</exception>
102-
/// <remarks>
103-
/// Use this overload when you have multiple <see cref="IDistributedCache"/> implementations or want to
104-
/// use a specific instance rather than the one registered in DI.
105-
/// </remarks>
106-
public static IMcpServerBuilder WithDistributedCacheEventStreamStore(this IMcpServerBuilder builder, IDistributedCache? cache, Action<DistributedCacheEventStreamStoreOptions>? configureOptions = null)
10788
{
10889
ArgumentNullException.ThrowIfNull(builder);
10990

110-
builder.Services.AddSingleton<ISseEventStreamStore>(sp =>
111-
{
112-
var resolvedCache = cache ?? sp.GetRequiredService<IDistributedCache>();
113-
var options = sp.GetService<IOptions<DistributedCacheEventStreamStoreOptions>>()?.Value;
114-
var logger = sp.GetService<ILogger<DistributedCacheEventStreamStore>>();
115-
return new DistributedCacheEventStreamStore(resolvedCache, options, logger);
116-
});
91+
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<DistributedCacheEventStreamStoreOptions>, DistributedCacheEventStreamStoreOptionsSetup>());
92+
builder.Services.AddSingleton<ISseEventStreamStore, DistributedCacheEventStreamStore>();
11793

11894
if (configureOptions is not null)
11995
{

src/ModelContextProtocol/McpServerOptionsSetup.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using Microsoft.Extensions.DependencyInjection;
21
using Microsoft.Extensions.Options;
32
using ModelContextProtocol.Server;
43

@@ -7,15 +6,15 @@ namespace ModelContextProtocol;
76
/// <summary>
87
/// Configures the McpServerOptions using additional services from DI.
98
/// </summary>
10-
/// <param name="serviceProvider">The service provider for resolving optional services.</param>
119
/// <param name="serverTools">The individually registered tools.</param>
1210
/// <param name="serverPrompts">The individually registered prompts.</param>
1311
/// <param name="serverResources">The individually registered resources.</param>
12+
/// <param name="taskStore">The optional task store registered in DI.</param>
1413
internal sealed class McpServerOptionsSetup(
15-
IServiceProvider serviceProvider,
1614
IEnumerable<McpServerTool> serverTools,
1715
IEnumerable<McpServerPrompt> serverPrompts,
18-
IEnumerable<McpServerResource> serverResources) : IConfigureOptions<McpServerOptions>
16+
IEnumerable<McpServerResource> serverResources,
17+
IMcpTaskStore? taskStore = null) : IConfigureOptions<McpServerOptions>
1918
{
2019
/// <summary>
2120
/// Configures the given McpServerOptions instance by setting server information
@@ -26,7 +25,7 @@ public void Configure(McpServerOptions options)
2625
{
2726
Throw.IfNull(options);
2827

29-
options.TaskStore ??= serviceProvider.GetService<IMcpTaskStore>();
28+
options.TaskStore ??= taskStore;
3029

3130
// Collect all of the provided tools into a tools collection. If the options already has
3231
// a collection, add to it, otherwise create a new one. We want to maintain the identity

src/ModelContextProtocol/Server/DistributedCacheEventStreamStore.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Microsoft.Extensions.Caching.Distributed;
22
using Microsoft.Extensions.Logging;
33
using Microsoft.Extensions.Logging.Abstractions;
4+
using Microsoft.Extensions.Options;
45
using ModelContextProtocol.Protocol;
56
using System.Net.ServerSentEvents;
67
using System.Runtime.CompilerServices;
@@ -31,14 +32,18 @@ public sealed partial class DistributedCacheEventStreamStore : ISseEventStreamSt
3132
/// <summary>
3233
/// Initializes a new instance of the <see cref="DistributedCacheEventStreamStore"/> class.
3334
/// </summary>
34-
/// <param name="cache">The distributed cache to use for storage.</param>
35-
/// <param name="options">Optional configuration options for the store.</param>
35+
/// <param name="options">Configuration options for the store, including the <see cref="IDistributedCache"/> to use.</param>
3636
/// <param name="logger">Optional logger for diagnostic output.</param>
37-
public DistributedCacheEventStreamStore(IDistributedCache cache, DistributedCacheEventStreamStoreOptions? options = null, ILogger<DistributedCacheEventStreamStore>? logger = null)
37+
public DistributedCacheEventStreamStore(IOptions<DistributedCacheEventStreamStoreOptions> options, ILogger<DistributedCacheEventStreamStore>? logger = null)
3838
{
39-
Throw.IfNull(cache);
40-
_cache = cache;
41-
_options = options ?? new();
39+
Throw.IfNull(options);
40+
41+
var optionsValue = options.Value;
42+
_cache = optionsValue.Cache ?? throw new InvalidOperationException(
43+
$"The '{nameof(DistributedCacheEventStreamStoreOptions)}.{nameof(DistributedCacheEventStreamStoreOptions.Cache)}' property must be set. " +
44+
$"When using 'WithDistributedCacheEventStreamStore()', the cache will be automatically configured from DI. " +
45+
$"When constructing manually, set the '{nameof(DistributedCacheEventStreamStoreOptions.Cache)}' property on the options object.");
46+
_options = optionsValue;
4247
_logger = logger ?? NullLogger<DistributedCacheEventStreamStore>.Instance;
4348
}
4449

src/ModelContextProtocol/Server/DistributedCacheEventStreamStoreOptions.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1+
using Microsoft.Extensions.Caching.Distributed;
2+
13
namespace ModelContextProtocol.Server;
24

35
/// <summary>
46
/// Configuration options for <see cref="DistributedCacheEventStreamStore"/>.
57
/// </summary>
68
public sealed class DistributedCacheEventStreamStoreOptions
79
{
10+
/// <summary>
11+
/// Gets or sets the <see cref="IDistributedCache"/> to use for event storage.
12+
/// </summary>
13+
/// <remarks>
14+
/// When using dependency injection with <c>WithDistributedCacheEventStreamStore()</c>, this is
15+
/// automatically populated from the <see cref="IDistributedCache"/> registered in DI.
16+
/// Set this property explicitly to use a specific cache instance.
17+
/// </remarks>
18+
public IDistributedCache? Cache { get; set; }
19+
820
/// <summary>
921
/// Gets or sets the sliding expiration for individual events in the cache.
1022
/// </summary>

tests/ModelContextProtocol.AspNetCore.Tests/DistributedCacheResumabilityIntegrationTests.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,17 +22,18 @@ namespace ModelContextProtocol.AspNetCore.Tests;
2222
/// </remarks>
2323
public class DistributedCacheResumabilityIntegrationTests(ITestOutputHelper testOutputHelper) : ResumabilityIntegrationTestsBase(testOutputHelper)
2424
{
25-
private MemoryDistributedCache? _cache;
26-
2725
/// <inheritdoc />
2826
protected override ValueTask<ISseEventStreamStore> CreateEventStreamStoreAsync()
2927
{
3028
// Create a new in-memory distributed cache for each test
31-
_cache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions()));
29+
var cache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions()));
3230

3331
// Configure the store with shorter expiration times suitable for testing
3432
var options = new DistributedCacheEventStreamStoreOptions
3533
{
34+
// Use the in-memory distributed cache
35+
Cache = cache,
36+
3637
// Use shorter polling interval for faster test execution
3738
StreamReaderPollingInterval = TimeSpan.FromMilliseconds(50),
3839

@@ -43,7 +44,7 @@ protected override ValueTask<ISseEventStreamStore> CreateEventStreamStoreAsync()
4344
MetadataAbsoluteExpiration = TimeSpan.FromMinutes(10),
4445
};
4546

46-
var store = new DistributedCacheEventStreamStore(_cache, options, LoggerFactory.CreateLogger<DistributedCacheEventStreamStore>());
47+
var store = new DistributedCacheEventStreamStore(Options.Create(options), LoggerFactory.CreateLogger<DistributedCacheEventStreamStore>());
4748
return new ValueTask<ISseEventStreamStore>(store);
4849
}
4950
}

0 commit comments

Comments
 (0)