-
Notifications
You must be signed in to change notification settings - Fork 545
Resources-enhancements #257
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
Changes from 6 commits
a918739
4beb291
144413b
0179cfe
ada39ad
c6989de
9816b55
a4a61fd
d914fe5
2e2ad7f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
using ModelContextProtocol.Server; | ||
using System.Text.Json.Serialization; | ||
|
||
namespace ModelContextProtocol.Protocol.Types; | ||
|
||
/// <summary> | ||
/// Represents the tools capability configuration. | ||
/// </summary> | ||
/// <typeparam name="TPrimitive">The type of the primitive.</typeparam> | ||
internal interface IListCapability<TPrimitive> | ||
where TPrimitive : IMcpServerPrimitive | ||
{ | ||
/// <summary> | ||
/// Gets or sets whether this server supports notifications for changes to the tool list. | ||
/// </summary> | ||
[JsonPropertyName("listChanged")] | ||
public bool? ListChanged { get; set; } | ||
|
||
/// <summary> | ||
/// Gets or sets the handler for list tools requests. | ||
/// </summary> | ||
[JsonIgnore] | ||
public McpServerPrimitiveCollection<TPrimitive>? Collection { get; set; } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,9 +20,6 @@ internal sealed class McpServer : McpEndpoint, IMcpServer | |
|
||
private readonly ITransport _sessionTransport; | ||
|
||
private readonly EventHandler? _toolsChangedDelegate; | ||
private readonly EventHandler? _promptsChangedDelegate; | ||
|
||
private string _endpointName; | ||
private int _started; | ||
|
||
|
@@ -32,6 +29,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer | |
/// rather than a nullable to be able to manipulate it atomically. | ||
/// </remarks> | ||
private StrongBox<LoggingLevel>? _loggingLevel; | ||
private readonly List<Disposable> _disposables = []; | ||
Tyler-R-Kendrick marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
||
/// <summary> | ||
/// Creates a new instance of <see cref="McpServer"/>. | ||
|
@@ -64,32 +62,16 @@ public McpServer(ITransport transport, McpServerOptions options, ILoggerFactory? | |
SetCompletionHandler(options); | ||
SetPingHandler(); | ||
|
||
var capabilities = options.Capabilities; | ||
Tyler-R-Kendrick marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Register any notification handlers that were provided. | ||
if (options.Capabilities?.NotificationHandlers is { } notificationHandlers) | ||
if (capabilities?.NotificationHandlers is { } notificationHandlers) | ||
{ | ||
NotificationHandlers.RegisterRange(notificationHandlers); | ||
} | ||
|
||
// Now that everything has been configured, subscribe to any necessary notifications. | ||
if (ServerOptions.Capabilities?.Tools?.ToolCollection is { } tools) | ||
{ | ||
_toolsChangedDelegate = delegate | ||
{ | ||
_ = SendMessageAsync(new JsonRpcNotification() { Method = NotificationMethods.ToolListChangedNotification }); | ||
}; | ||
|
||
tools.Changed += _toolsChangedDelegate; | ||
} | ||
|
||
if (ServerOptions.Capabilities?.Prompts?.PromptCollection is { } prompts) | ||
{ | ||
_promptsChangedDelegate = delegate | ||
{ | ||
_ = SendMessageAsync(new JsonRpcNotification() { Method = NotificationMethods.PromptListChangedNotification }); | ||
}; | ||
|
||
prompts.Changed += _promptsChangedDelegate; | ||
} | ||
|
||
RegisterListChange(capabilities?.Tools, NotificationMethods.ToolListChangedNotification); | ||
RegisterListChange(capabilities?.Prompts, NotificationMethods.PromptListChangedNotification); | ||
RegisterListChange(capabilities?.Resources, NotificationMethods.ResourceListChangedNotification); | ||
|
||
// And initialize the session. | ||
InitializeSession(transport); | ||
|
@@ -136,18 +118,11 @@ public async Task RunAsync(CancellationToken cancellationToken = default) | |
|
||
public override async ValueTask DisposeUnsynchronizedAsync() | ||
{ | ||
if (_toolsChangedDelegate is not null && | ||
ServerOptions.Capabilities?.Tools?.ToolCollection is { } tools) | ||
{ | ||
tools.Changed -= _toolsChangedDelegate; | ||
} | ||
|
||
if (_promptsChangedDelegate is not null && | ||
ServerOptions.Capabilities?.Prompts?.PromptCollection is { } prompts) | ||
foreach (var disposable in _disposables) | ||
{ | ||
prompts.Changed -= _promptsChangedDelegate; | ||
disposable.Dispose(); | ||
} | ||
|
||
_disposables.Clear(); | ||
await base.DisposeUnsynchronizedAsync().ConfigureAwait(false); | ||
} | ||
|
||
|
@@ -210,21 +185,37 @@ private void SetResourcesHandler(McpServerOptions options) | |
|
||
var listResourcesHandler = resourcesCapability.ListResourcesHandler; | ||
var listResourceTemplatesHandler = resourcesCapability.ListResourceTemplatesHandler; | ||
var readResourceHandler = resourcesCapability.ReadResourceHandler; | ||
var resourceCollection = resourcesCapability.ResourceCollection; | ||
|
||
if ((listResourcesHandler is not { } && listResourceTemplatesHandler is not { }) || | ||
resourcesCapability.ReadResourceHandler is not { } readResourceHandler) | ||
var originalListResourcesHandler = listResourcesHandler; | ||
listResourcesHandler = async (request, cancellationToken) => | ||
{ | ||
throw new McpException("Resources capability was enabled, but ListResources and/or ReadResource handlers were not specified."); | ||
} | ||
ListResourcesResult result = originalListResourcesHandler is not null ? | ||
await originalListResourcesHandler(request, cancellationToken).ConfigureAwait(false) : | ||
new(); | ||
|
||
if (request.Params?.Cursor is null && resourceCollection is not null) | ||
{ | ||
result.Resources.AddRange(resourceCollection.Select(t => t.ProtocolResource)); | ||
} | ||
|
||
listResourcesHandler ??= (static (_, _) => Task.FromResult(new ListResourcesResult())); | ||
return result; | ||
}; | ||
|
||
var isMissingListResourceHandlers = originalListResourcesHandler is not { } && listResourceTemplatesHandler is not { }; | ||
if (resourceCollection is not { IsEmpty: false } && (isMissingListResourceHandlers || readResourceHandler is not { })) | ||
{ | ||
throw new McpException("Resources capability was enabled, but ListResources, ListResourceTemplates, and/or ReadResource handlers were not specified."); | ||
} | ||
|
||
|
||
RequestHandlers.Set( | ||
RequestMethods.ResourcesList, | ||
(request, cancellationToken) => listResourcesHandler(new(this, request), cancellationToken), | ||
McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams, | ||
McpJsonUtilities.JsonContext.Default.ListResourcesResult); | ||
|
||
readResourceHandler ??= static (_, _) => Task.FromResult(new ReadResourceResult()); | ||
RequestHandlers.Set( | ||
RequestMethods.ResourcesRead, | ||
(request, cancellationToken) => readResourceHandler(new(this, request), cancellationToken), | ||
|
@@ -483,6 +474,21 @@ private void SetSetLoggingLevelHandler(McpServerOptions options) | |
McpJsonUtilities.JsonContext.Default.EmptyResult); | ||
} | ||
|
||
private void RegisterListChange<T>(IListCapability<T>? capability, string methodName) | ||
where T : IMcpServerPrimitive | ||
{ | ||
// https://modelcontextprotocol.io/specification/2024-11-05/server/tools#capabilities | ||
// Look to spec for guidance on ListChanged over collection existance. | ||
Comment on lines
+551
to
+552
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ? |
||
if (capability?.Collection is { } collection) | ||
//&& capability.ListChanged is true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ? |
||
{ | ||
void ChangedDelegate(object? sender, EventArgs e) | ||
=> _ = this.SendNotificationAsync(methodName); | ||
collection.Changed += ChangedDelegate; | ||
_disposables.Add(new(() => collection.Changed -= ChangedDelegate)); | ||
Tyler-R-Kendrick marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
/// <summary>Maps a <see cref="LogLevel"/> to a <see cref="LoggingLevel"/>.</summary> | ||
internal static LoggingLevel ToLoggingLevel(LogLevel level) => | ||
level switch | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
using ModelContextProtocol.Protocol.Types; | ||
|
||
namespace ModelContextProtocol.Server; | ||
|
||
/// <summary> | ||
/// Represents a resource that the server supports. | ||
/// </summary> | ||
public class McpServerResource : IMcpServerPrimitive | ||
Tyler-R-Kendrick marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
{ | ||
/// <summary> | ||
/// The resource instance. | ||
/// </summary> | ||
public required Resource ProtocolResource { get; init; } | ||
|
||
/// <inheritdoc /> | ||
public string Name => ProtocolResource.Name; | ||
} | ||
Tyler-R-Kendrick marked this conversation as resolved.
Show resolved
Hide resolved
|
Uh oh!
There was an error while loading. Please reload this page.