Improve progress reporting (#145)
* Improve progress reporting - Enable injecting an `IProgress<>` into a tool call to report progress when the client has supplied a progress token - Add a custom type for ProgressToken and ProgressNotification to map to the schema - Simplify RequestId to match ProgressToken's shape - Serialize StdioClientTransport's SendMessageAsync implementation - Add strongly-typed names for all request methods - Change all request IDs created by McpJsonRpcEndpoint to include a guid for that endpoint * Address PR feedback
This commit is contained in:
parent
0294c574a6
commit
2c75acded8
3 changed files with 627 additions and 228 deletions
|
|
@ -1,6 +1,7 @@
|
|||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Shared;
|
||||
using ModelContextProtocol.Utils;
|
||||
using ModelContextProtocol.Utils.Json;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
|
@ -98,6 +99,27 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
|
|||
};
|
||||
}
|
||||
|
||||
if (pi.ParameterType == typeof(IProgress<ProgressNotificationValue>))
|
||||
{
|
||||
// Bind IProgress<ProgressNotificationValue> to the progress token in the request,
|
||||
// if there is one. If we can't get one, return a nop progress.
|
||||
return new()
|
||||
{
|
||||
ExcludeFromSchema = true,
|
||||
BindParameter = (pi, args) =>
|
||||
{
|
||||
var requestContent = GetRequestContext(args);
|
||||
if (requestContent?.Server is { } server &&
|
||||
requestContent?.Params?.Meta?.ProgressToken is { } progressToken)
|
||||
{
|
||||
return new TokenProgress(server, progressToken);
|
||||
}
|
||||
|
||||
return NullProgress.Instance;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// We assume that if the services used to create the tool support a particular type,
|
||||
// so too do the services associated with the server. This is the same basic assumption
|
||||
// made in ASP.NET.
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
});
|
||||
};
|
||||
|
||||
AddNotificationHandler("notifications/initialized", _ =>
|
||||
AddNotificationHandler(NotificationMethods.InitializedNotification, _ =>
|
||||
{
|
||||
if (ServerOptions.Capabilities?.Tools?.ToolCollection is { } tools)
|
||||
{
|
||||
|
|
@ -176,13 +176,13 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
|
||||
private void SetPingHandler()
|
||||
{
|
||||
SetRequestHandler<JsonNode, PingResult>("ping",
|
||||
SetRequestHandler<JsonNode, PingResult>(RequestMethods.Ping,
|
||||
(request, _) => Task.FromResult(new PingResult()));
|
||||
}
|
||||
|
||||
private void SetInitializeHandler(McpServerOptions options)
|
||||
{
|
||||
SetRequestHandler<InitializeRequestParams, InitializeResult>("initialize",
|
||||
SetRequestHandler<InitializeRequestParams, InitializeResult>(RequestMethods.Initialize,
|
||||
(request, _) =>
|
||||
{
|
||||
ClientCapabilities = request?.Capabilities ?? new();
|
||||
|
|
@ -205,7 +205,7 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
private void SetCompletionHandler(McpServerOptions options)
|
||||
{
|
||||
// This capability is not optional, so return an empty result if there is no handler.
|
||||
SetRequestHandler<CompleteRequestParams, CompleteResult>("completion/complete",
|
||||
SetRequestHandler<CompleteRequestParams, CompleteResult>(RequestMethods.CompletionComplete,
|
||||
options.GetCompletionHandler is { } handler ?
|
||||
(request, ct) => handler(new(this, request), ct) :
|
||||
(request, ct) => Task.FromResult(new CompleteResult() { Completion = new() { Values = [], Total = 0, HasMore = false } }));
|
||||
|
|
@ -229,11 +229,11 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
|
||||
listResourcesHandler ??= (static (_, _) => Task.FromResult(new ListResourcesResult()));
|
||||
|
||||
SetRequestHandler<ListResourcesRequestParams, ListResourcesResult>("resources/list", (request, ct) => listResourcesHandler(new(this, request), ct));
|
||||
SetRequestHandler<ReadResourceRequestParams, ReadResourceResult>("resources/read", (request, ct) => readResourceHandler(new(this, request), ct));
|
||||
SetRequestHandler<ListResourcesRequestParams, ListResourcesResult>(RequestMethods.ResourcesList, (request, ct) => listResourcesHandler(new(this, request), ct));
|
||||
SetRequestHandler<ReadResourceRequestParams, ReadResourceResult>(RequestMethods.ResourcesRead, (request, ct) => readResourceHandler(new(this, request), ct));
|
||||
|
||||
listResourceTemplatesHandler ??= (static (_, _) => Task.FromResult(new ListResourceTemplatesResult()));
|
||||
SetRequestHandler<ListResourceTemplatesRequestParams, ListResourceTemplatesResult>("resources/templates/list", (request, ct) => listResourceTemplatesHandler(new(this, request), ct));
|
||||
SetRequestHandler<ListResourceTemplatesRequestParams, ListResourceTemplatesResult>(RequestMethods.ResourcesTemplatesList, (request, ct) => listResourceTemplatesHandler(new(this, request), ct));
|
||||
|
||||
if (resourcesCapability.Subscribe is not true)
|
||||
{
|
||||
|
|
@ -247,8 +247,8 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
throw new McpServerException("Resources capability was enabled with subscribe support, but SubscribeToResources and/or UnsubscribeFromResources handlers were not specified.");
|
||||
}
|
||||
|
||||
SetRequestHandler<SubscribeRequestParams, EmptyResult>("resources/subscribe", (request, ct) => subscribeHandler(new(this, request), ct));
|
||||
SetRequestHandler<UnsubscribeRequestParams, EmptyResult>("resources/unsubscribe", (request, ct) => unsubscribeHandler(new(this, request), ct));
|
||||
SetRequestHandler<SubscribeRequestParams, EmptyResult>(RequestMethods.ResourcesSubscribe, (request, ct) => subscribeHandler(new(this, request), ct));
|
||||
SetRequestHandler<UnsubscribeRequestParams, EmptyResult>(RequestMethods.ResourcesUnsubscribe, (request, ct) => unsubscribeHandler(new(this, request), ct));
|
||||
}
|
||||
|
||||
private void SetPromptsHandler(McpServerOptions options)
|
||||
|
|
@ -264,8 +264,8 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
throw new McpServerException("Prompts capability was enabled, but ListPrompts and/or GetPrompt handlers were not specified.");
|
||||
}
|
||||
|
||||
SetRequestHandler<ListPromptsRequestParams, ListPromptsResult>("prompts/list", (request, ct) => listPromptsHandler(new(this, request), ct));
|
||||
SetRequestHandler<GetPromptRequestParams, GetPromptResult>("prompts/get", (request, ct) => getPromptHandler(new(this, request), ct));
|
||||
SetRequestHandler<ListPromptsRequestParams, ListPromptsResult>(RequestMethods.PromptsList, (request, ct) => listPromptsHandler(new(this, request), ct));
|
||||
SetRequestHandler<GetPromptRequestParams, GetPromptResult>(RequestMethods.PromptsGet, (request, ct) => getPromptHandler(new(this, request), ct));
|
||||
}
|
||||
|
||||
private void SetToolsHandler(McpServerOptions options)
|
||||
|
|
@ -363,8 +363,8 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
}
|
||||
}
|
||||
|
||||
SetRequestHandler<ListToolsRequestParams, ListToolsResult>("tools/list", (request, ct) => listToolsHandler(new(this, request), ct));
|
||||
SetRequestHandler<CallToolRequestParams, CallToolResponse>("tools/call", (request, ct) => callToolHandler(new(this, request), ct));
|
||||
SetRequestHandler<ListToolsRequestParams, ListToolsResult>(RequestMethods.ToolsList, (request, ct) => listToolsHandler(new(this, request), ct));
|
||||
SetRequestHandler<CallToolRequestParams, CallToolResponse>(RequestMethods.ToolsCall, (request, ct) => callToolHandler(new(this, request), ct));
|
||||
}
|
||||
|
||||
private void SetSetLoggingLevelHandler(McpServerOptions options)
|
||||
|
|
@ -379,6 +379,6 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
throw new McpServerException("Logging capability was enabled, but SetLoggingLevelHandler was not specified.");
|
||||
}
|
||||
|
||||
SetRequestHandler<SetLevelRequestParams, EmptyResult>("logging/setLevel", (request, ct) => setLoggingLevelHandler(new(this, request), ct));
|
||||
SetRequestHandler<SetLevelRequestParams, EmptyResult>(RequestMethods.LoggingSetLevel, (request, ct) => setLoggingLevelHandler(new(this, request), ct));
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ public static class McpServerExtensions
|
|||
}
|
||||
|
||||
return server.SendRequestAsync<CreateMessageResult>(
|
||||
new JsonRpcRequest { Method = "sampling/createMessage", Params = request },
|
||||
new JsonRpcRequest { Method = RequestMethods.SamplingCreateMessage, Params = request },
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +165,7 @@ public static class McpServerExtensions
|
|||
}
|
||||
|
||||
return server.SendRequestAsync<ListRootsResult>(
|
||||
new JsonRpcRequest { Method = "roots/list", Params = request },
|
||||
new JsonRpcRequest { Method = RequestMethods.RootsList, Params = request },
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue