Skip to content

Commit 9816b55

Browse files
Refactor McpServer and McpServerResource classes; remove unused resource methods and update resource handling in tests
1 parent c6989de commit 9816b55

5 files changed

Lines changed: 56 additions & 69 deletions

File tree

src/ModelContextProtocol/Configuration/McpServerBuilderExtensions.cs

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using Microsoft.Extensions.DependencyInjection.Extensions;
2-
using Microsoft.Extensions.FileProviders;
32
using Microsoft.Extensions.Logging;
43
using Microsoft.Extensions.Options;
54
using ModelContextProtocol.Hosting;
@@ -245,21 +244,6 @@ where t.GetCustomAttribute<McpServerPromptTypeAttribute>() is not null
245244

246245
#region WithResources
247246

248-
private static McpServerResource ToResource(
249-
this IFileInfo fileInfo)
250-
{
251-
Throw.IfNullOrWhiteSpace(fileInfo.PhysicalPath);
252-
253-
return new()
254-
{
255-
ProtocolResource = new()
256-
{
257-
Uri = fileInfo.PhysicalPath,
258-
Name = fileInfo.Name,
259-
},
260-
};
261-
}
262-
263247
private static IMcpServerBuilder AddResource(
264248
this IMcpServerBuilder builder,
265249
McpServerResource resource)
@@ -302,23 +286,6 @@ public static IMcpServerBuilder WithResource(
302286
return builder.AddResource(resource);
303287
}
304288

305-
/// <summary>
306-
/// Adds a resource to the server's capabilities.
307-
/// </summary>
308-
/// <param name="builder">The builder instance.</param>
309-
/// <param name="fileInfo">The file info of the resource.</param>
310-
/// <returns>The <see cref="IMcpServerBuilder"/> instance.</returns>
311-
public static IMcpServerBuilder WithResource(
312-
this IMcpServerBuilder builder,
313-
IFileInfo fileInfo)
314-
{
315-
Throw.IfNull(builder);
316-
Throw.IfNull(fileInfo);
317-
318-
var resource = fileInfo.ToResource();
319-
return builder.AddResource(resource);
320-
}
321-
322289
/// <summary>
323290
/// Adds a collection of resources to the server's capabilities.
324291
/// </summary>
@@ -335,25 +302,6 @@ public static IMcpServerBuilder WithResources(
335302
return builder.AddResources(resources);
336303
}
337304

338-
/// <summary>
339-
/// Adds a collection of resources to the server's capabilities.
340-
/// </summary>
341-
/// <param name="builder">The builder instance.</param>
342-
/// <param name="fileInfos">The collection of the file info of the resources.</param>
343-
/// <returns>The <see cref="IMcpServerBuilder"/> instance.</returns>
344-
public static IMcpServerBuilder WithResources(
345-
this IMcpServerBuilder builder,
346-
params IEnumerable<IFileInfo> fileInfos)
347-
{
348-
Throw.IfNull(builder);
349-
Throw.IfNull(fileInfos);
350-
351-
return builder.AddResources(
352-
from fileInfo in fileInfos
353-
where fileInfo is not null
354-
select fileInfo.ToResource());
355-
}
356-
357305
#endregion
358306

359307
#region Handlers

src/ModelContextProtocol/Protocol/Types/ToolsCapability.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ public class ToolsCapability : IListCapability<McpServerTool>
6161
/// </remarks>
6262
[JsonIgnore]
6363
public McpServerPrimitiveCollection<McpServerTool>? ToolCollection { get; set; }
64+
6465
McpServerPrimitiveCollection<McpServerTool>? IListCapability<McpServerTool>.Collection
6566
{
6667
get => ToolCollection;

src/ModelContextProtocol/Server/McpServer.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
2929
/// rather than a nullable to be able to manipulate it atomically.
3030
/// </remarks>
3131
private StrongBox<LoggingLevel>? _loggingLevel;
32-
private readonly List<Disposable> _disposables = [];
32+
private readonly List<Action> _disposables = [];
3333

3434
/// <summary>
3535
/// Creates a new instance of <see cref="McpServer"/>.
@@ -63,6 +63,7 @@ public McpServer(ITransport transport, McpServerOptions options, ILoggerFactory?
6363
SetPingHandler();
6464

6565
var capabilities = options.Capabilities;
66+
6667
// Register any notification handlers that were provided.
6768
if (capabilities?.NotificationHandlers is { } notificationHandlers)
6869
{
@@ -120,7 +121,7 @@ public override async ValueTask DisposeUnsynchronizedAsync()
120121
{
121122
foreach (var disposable in _disposables)
122123
{
123-
disposable.Dispose();
124+
disposable();
124125
}
125126
_disposables.Clear();
126127
await base.DisposeUnsynchronizedAsync().ConfigureAwait(false);
@@ -203,7 +204,7 @@ await originalListResourcesHandler(request, cancellationToken).ConfigureAwait(fa
203204
return result;
204205
};
205206

206-
var isMissingListResourceHandlers = originalListResourcesHandler is not { } && listResourceTemplatesHandler is not { };
207+
var isMissingListResourceHandlers = originalListResourcesHandler is null && listResourceTemplatesHandler is null;
207208
if (resourceCollection is not { IsEmpty: false } && (isMissingListResourceHandlers || readResourceHandler is not { }))
208209
{
209210
throw new McpException("Resources capability was enabled, but ListResources, ListResourceTemplates, and/or ReadResource handlers were not specified.");
@@ -485,7 +486,7 @@ private void RegisterListChange<T>(IListCapability<T>? capability, string method
485486
void ChangedDelegate(object? sender, EventArgs e)
486487
=> _ = this.SendNotificationAsync(methodName);
487488
collection.Changed += ChangedDelegate;
488-
_disposables.Add(new(() => collection.Changed -= ChangedDelegate));
489+
_disposables.Add(() => collection.Changed -= ChangedDelegate);
489490
}
490491
}
491492

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,28 @@
1+
using Microsoft.Extensions.FileProviders;
12
using ModelContextProtocol.Protocol.Types;
23

34
namespace ModelContextProtocol.Server;
45

56
/// <summary>
67
/// Represents a resource that the server supports.
78
/// </summary>
8-
public class McpServerResource : IMcpServerPrimitive
9+
public abstract class McpServerResource : IMcpServerPrimitive
910
{
1011
/// <summary>
1112
/// The resource instance.
1213
/// </summary>
13-
public required Resource ProtocolResource { get; init; }
14+
public abstract required Resource ProtocolResource { get; init; }
1415

1516
/// <inheritdoc />
1617
public string Name => ProtocolResource.Name;
18+
19+
/// <summary>
20+
/// Gets the resource URI.
21+
/// </summary>
22+
/// <param name="request">The request context.</param>
23+
/// <param name="cancellationToken">The cancellation token.</param>
24+
/// <returns>The file info of the resource.</returns>
25+
public abstract Task<IFileInfo> GetFileInfoAsync(
26+
RequestContext<ReadResourceRequestParams> request,
27+
CancellationToken cancellationToken = default);
1728
}

tests/ModelContextProtocol.Tests/Configuration/McpServerBuilderExtensionsResourcesTests.cs

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Microsoft.Extensions.Options;
44
using ModelContextProtocol.Client;
55
using ModelContextProtocol.Protocol.Messages;
6+
using ModelContextProtocol.Protocol.Types;
67
using ModelContextProtocol.Server;
78

89
namespace ModelContextProtocol.Tests.Configuration;
@@ -13,17 +14,21 @@ public class McpServerBuilderExtensionsResourcesTests(ITestOutputHelper testOutp
1314
protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder)
1415
{
1516
mcpServerBuilder = mcpServerBuilder.WithResources(
16-
new FakeFileInfo()
17+
new FakeMcpServerResource()
1718
{
18-
Name = "test",
19-
PhysicalPath = "test.txt",
20-
Length = 0,
19+
ProtocolResource = new()
20+
{
21+
Name = "test",
22+
Uri = "test.txt",
23+
},
2124
},
22-
new FakeFileInfo()
25+
new FakeMcpServerResource()
2326
{
24-
Name = "test2",
25-
PhysicalPath = "test2.txt",
26-
Length = 0,
27+
ProtocolResource = new()
28+
{
29+
Name = "test2",
30+
Uri = "test2.txt",
31+
},
2732
});
2833
base.ConfigureServices(services, mcpServerBuilder);
2934
}
@@ -43,15 +48,36 @@ private class FakeFileInfo : IFileInfo
4348
public Stream CreateReadStream() => new MemoryStream();
4449
}
4550

51+
private class FakeMcpServerResource : McpServerResource
52+
{
53+
public override required Resource ProtocolResource { get; init; } = new()
54+
{
55+
Name = "test",
56+
Uri = "test.txt",
57+
};
58+
59+
public override Task<IFileInfo> GetFileInfoAsync(
60+
RequestContext<ReadResourceRequestParams> request,
61+
CancellationToken cancellationToken = default)
62+
{
63+
return Task.FromResult<IFileInfo>(new FakeFileInfo()
64+
{
65+
Name = request?.Params?.Uri ?? "test.txt",
66+
PhysicalPath = request?.Params?.Uri ?? "test.txt",
67+
Length = 0,
68+
});
69+
}
70+
}
71+
4672
[Fact]
4773
public void Adds_Resources_To_Server()
4874
{
4975
var serverOptions = ServiceProvider.GetRequiredService<IOptions<McpServerOptions>>().Value;
5076
var resources = serverOptions?.Capabilities?.Resources?.ResourceCollection;
5177
Assert.NotNull(resources);
5278
Assert.Equal(2, resources.Count);
53-
Assert.Equal("test", resources["test"].Name);
54-
Assert.Equal("test2", resources["test2"].Name);
79+
Assert.Equal("test", resources["test"].ProtocolResource.Name);
80+
Assert.Equal("test2", resources["test2"].ProtocolResource.Name);
5581
}
5682

5783
[Fact]
@@ -92,7 +118,7 @@ public async Task Can_Be_Notified_Of_ResourceList_Changes()
92118
Assert.NotNull(resources);
93119
Assert.Equal(2, resources.Count);
94120

95-
serverOptions?.Capabilities?.Resources?.ResourceCollection?.Add(new McpServerResource
121+
serverOptions?.Capabilities?.Resources?.ResourceCollection?.Add(new FakeMcpServerResource
96122
{
97123
ProtocolResource = new()
98124
{

0 commit comments

Comments
 (0)