Use strong-typing of params in most remaining McpClientExtensions methods (#224)

This commit is contained in:
Stephen Toub 2025-04-07 11:30:42 -04:00 • committed by GitHub
commit 03dc90aa33
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 74 additions and 73 deletions

View file

@ -52,8 +52,8 @@ public static class McpClientExtensions
{ {
var toolResults = await client.SendRequestAsync( var toolResults = await client.SendRequestAsync(
RequestMethods.ToolsList, RequestMethods.ToolsList,
CreateCursorDictionary(cursor)!, new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject, McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
McpJsonUtilities.JsonContext.Default.ListToolsResult, McpJsonUtilities.JsonContext.Default.ListToolsResult,
cancellationToken: cancellationToken).ConfigureAwait(false); cancellationToken: cancellationToken).ConfigureAwait(false);
@ -96,8 +96,8 @@ public static class McpClientExtensions
{ {
var toolResults = await client.SendRequestAsync( var toolResults = await client.SendRequestAsync(
RequestMethods.ToolsList, RequestMethods.ToolsList,
CreateCursorDictionary(cursor)!, new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject, McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
McpJsonUtilities.JsonContext.Default.ListToolsResult, McpJsonUtilities.JsonContext.Default.ListToolsResult,
cancellationToken: cancellationToken).ConfigureAwait(false); cancellationToken: cancellationToken).ConfigureAwait(false);
@ -128,8 +128,8 @@ public static class McpClientExtensions
{ {
var promptResults = await client.SendRequestAsync( var promptResults = await client.SendRequestAsync(
RequestMethods.PromptsList, RequestMethods.PromptsList,
CreateCursorDictionary(cursor)!, new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject, McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
McpJsonUtilities.JsonContext.Default.ListPromptsResult, McpJsonUtilities.JsonContext.Default.ListPromptsResult,
cancellationToken: cancellationToken).ConfigureAwait(false); cancellationToken: cancellationToken).ConfigureAwait(false);
@ -166,8 +166,8 @@ public static class McpClientExtensions
{ {
var promptResults = await client.SendRequestAsync( var promptResults = await client.SendRequestAsync(
RequestMethods.PromptsList, RequestMethods.PromptsList,
CreateCursorDictionary(cursor)!, new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject, McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
McpJsonUtilities.JsonContext.Default.ListPromptsResult, McpJsonUtilities.JsonContext.Default.ListPromptsResult,
cancellationToken: cancellationToken).ConfigureAwait(false); cancellationToken: cancellationToken).ConfigureAwait(false);
@ -202,12 +202,10 @@ public static class McpClientExtensions
serializerOptions ??= McpJsonUtilities.DefaultOptions; serializerOptions ??= McpJsonUtilities.DefaultOptions;
serializerOptions.MakeReadOnly(); serializerOptions.MakeReadOnly();
var parametersTypeInfo = serializerOptions.GetTypeInfo<IReadOnlyDictionary<string, object?>>();
return client.SendRequestAsync( return client.SendRequestAsync(
RequestMethods.PromptsGet, RequestMethods.PromptsGet,
CreateParametersDictionary(name, arguments), new() { Name = name, Arguments = ToArgumentsDictionary(arguments, serializerOptions) },
parametersTypeInfo, McpJsonUtilities.JsonContext.Default.GetPromptRequestParams,
McpJsonUtilities.JsonContext.Default.GetPromptResult, McpJsonUtilities.JsonContext.Default.GetPromptResult,
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
} }
@ -230,8 +228,8 @@ public static class McpClientExtensions
{ {
var templateResults = await client.SendRequestAsync( var templateResults = await client.SendRequestAsync(
RequestMethods.ResourcesTemplatesList, RequestMethods.ResourcesTemplatesList,
CreateCursorDictionary(cursor)!, new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject, McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult, McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
cancellationToken: cancellationToken).ConfigureAwait(false); cancellationToken: cancellationToken).ConfigureAwait(false);
@ -271,8 +269,8 @@ public static class McpClientExtensions
{ {
var templateResults = await client.SendRequestAsync( var templateResults = await client.SendRequestAsync(
RequestMethods.ResourcesTemplatesList, RequestMethods.ResourcesTemplatesList,
CreateCursorDictionary(cursor)!, new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject, McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult, McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
cancellationToken: cancellationToken).ConfigureAwait(false); cancellationToken: cancellationToken).ConfigureAwait(false);
@ -304,8 +302,8 @@ public static class McpClientExtensions
{ {
var resourceResults = await client.SendRequestAsync( var resourceResults = await client.SendRequestAsync(
RequestMethods.ResourcesList, RequestMethods.ResourcesList,
CreateCursorDictionary(cursor)!, new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject, McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourcesResult, McpJsonUtilities.JsonContext.Default.ListResourcesResult,
cancellationToken: cancellationToken).ConfigureAwait(false); cancellationToken: cancellationToken).ConfigureAwait(false);
@ -345,8 +343,8 @@ public static class McpClientExtensions
{ {
var resourceResults = await client.SendRequestAsync( var resourceResults = await client.SendRequestAsync(
RequestMethods.ResourcesList, RequestMethods.ResourcesList,
CreateCursorDictionary(cursor)!, new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject, McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourcesResult, McpJsonUtilities.JsonContext.Default.ListResourcesResult,
cancellationToken: cancellationToken).ConfigureAwait(false); cancellationToken: cancellationToken).ConfigureAwait(false);
@ -374,8 +372,8 @@ public static class McpClientExtensions
return client.SendRequestAsync( return client.SendRequestAsync(
RequestMethods.ResourcesRead, RequestMethods.ResourcesRead,
new Dictionary<string, object> { ["uri"] = uri }, new() { Uri = uri },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject, McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams,
McpJsonUtilities.JsonContext.Default.ReadResourceResult, McpJsonUtilities.JsonContext.Default.ReadResourceResult,
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
} }
@ -401,12 +399,12 @@ public static class McpClientExtensions
return client.SendRequestAsync( return client.SendRequestAsync(
RequestMethods.CompletionComplete, RequestMethods.CompletionComplete,
new Dictionary<string, object> new()
{ {
["ref"] = reference, Ref = reference,
["argument"] = new Argument { Name = argumentName, Value = argumentValue } Argument = new Argument { Name = argumentName, Value = argumentValue }
}, },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject, McpJsonUtilities.JsonContext.Default.CompleteRequestParams,
McpJsonUtilities.JsonContext.Default.CompleteResult, McpJsonUtilities.JsonContext.Default.CompleteResult,
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
} }
@ -424,8 +422,8 @@ public static class McpClientExtensions
return client.SendRequestAsync( return client.SendRequestAsync(
RequestMethods.ResourcesSubscribe, RequestMethods.ResourcesSubscribe,
new Dictionary<string, object> { ["uri"] = uri }, new() { Uri = uri },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject, McpJsonUtilities.JsonContext.Default.SubscribeRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult, McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
} }
@ -443,8 +441,8 @@ public static class McpClientExtensions
return client.SendRequestAsync( return client.SendRequestAsync(
RequestMethods.ResourcesUnsubscribe, RequestMethods.ResourcesUnsubscribe,
new Dictionary<string, object> { ["uri"] = uri }, new() { Uri = uri },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject, McpJsonUtilities.JsonContext.Default.UnsubscribeRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult, McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
} }
@ -470,12 +468,10 @@ public static class McpClientExtensions
serializerOptions ??= McpJsonUtilities.DefaultOptions; serializerOptions ??= McpJsonUtilities.DefaultOptions;
serializerOptions.MakeReadOnly(); serializerOptions.MakeReadOnly();
var parametersTypeInfo = serializerOptions.GetTypeInfo<IReadOnlyDictionary<string, object?>>();
return client.SendRequestAsync( return client.SendRequestAsync(
RequestMethods.ToolsCall, RequestMethods.ToolsCall,
CreateParametersDictionary(toolName, arguments), new() { Name = toolName, Arguments = ToArgumentsDictionary(arguments, serializerOptions) },
parametersTypeInfo, McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
McpJsonUtilities.JsonContext.Default.CallToolResponse, McpJsonUtilities.JsonContext.Default.CallToolResponse,
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
} }
@ -629,28 +625,28 @@ public static class McpClientExtensions
return client.SendRequestAsync( return client.SendRequestAsync(
RequestMethods.LoggingSetLevel, RequestMethods.LoggingSetLevel,
new Dictionary<string, object> { ["level"] = level }, new() { Level = level },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject, McpJsonUtilities.JsonContext.Default.SetLevelRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult, McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken); cancellationToken: cancellationToken);
} }
private static Dictionary<string, object?>? CreateCursorDictionary(string? cursor) => /// <summary>Convers a dictionary with <see cref="object"/> values to a dictionary with <see cref="JsonElement"/> values.</summary>
cursor != null ? new() { ["cursor"] = cursor } : null; private static IReadOnlyDictionary<string, JsonElement>? ToArgumentsDictionary(
IReadOnlyDictionary<string, object?>? arguments, JsonSerializerOptions options)
private static Dictionary<string, object?> CreateParametersDictionary(
string nameParameter, IReadOnlyDictionary<string, object?>? arguments)
{ {
Dictionary<string, object?> parameters = new() var typeInfo = options.GetTypeInfo<object?>();
{
["name"] = nameParameter
};
if (arguments != null) Dictionary<string, JsonElement>? result = null;
if (arguments is not null)
{ {
parameters["arguments"] = arguments; result = new(arguments.Count);
foreach (var kvp in arguments)
{
result.Add(kvp.Key, kvp.Value is JsonElement je ? je : JsonSerializer.SerializeToElement(kvp.Value, typeInfo));
}
} }
return parameters; return result;
} }
} }

View file

@ -1,4 +1,5 @@
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization;
namespace ModelContextProtocol.Protocol.Types; namespace ModelContextProtocol.Protocol.Types;
@ -11,12 +12,12 @@ public class CallToolRequestParams : RequestParams
/// <summary> /// <summary>
/// Tool name. /// Tool name.
/// </summary> /// </summary>
[System.Text.Json.Serialization.JsonPropertyName("name")] [JsonPropertyName("name")]
public required string Name { get; init; } public required string Name { get; init; }
/// <summary> /// <summary>
/// Optional arguments to pass to the tool. /// Optional arguments to pass to the tool.
/// </summary> /// </summary>
[System.Text.Json.Serialization.JsonPropertyName("arguments")] [JsonPropertyName("arguments")]
public Dictionary<string, JsonElement>? Arguments { get; init; } public IReadOnlyDictionary<string, JsonElement>? Arguments { get; init; }
} }

View file

@ -1,4 +1,7 @@
namespace ModelContextProtocol.Protocol.Types; using System.Text.Json;
using System.Text.Json.Serialization;
namespace ModelContextProtocol.Protocol.Types;
/// <summary> /// <summary>
/// Used by the client to get a prompt provided by the server. /// Used by the client to get a prompt provided by the server.
@ -9,12 +12,12 @@ public class GetPromptRequestParams : RequestParams
/// <summary> /// <summary>
/// he name of the prompt or prompt template. /// he name of the prompt or prompt template.
/// </summary> /// </summary>
[System.Text.Json.Serialization.JsonPropertyName("name")] [JsonPropertyName("name")]
public required string Name { get; init; } public required string Name { get; init; }
/// <summary> /// <summary>
/// Arguments to use for templating the prompt. /// Arguments to use for templating the prompt.
/// </summary> /// </summary>
[System.Text.Json.Serialization.JsonPropertyName("arguments")] [JsonPropertyName("arguments")]
public Dictionary<string, object>? Arguments { get; init; } public IReadOnlyDictionary<string, JsonElement>? Arguments { get; init; }
} }

View file

@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Protocol.Types; using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Utils; using ModelContextProtocol.Utils;
using ModelContextProtocol.Utils.Json;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
@ -207,8 +208,8 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
// TODO: Once we shift to the real AIFunctionFactory, the request should be passed via AIFunctionArguments.Context. // TODO: Once we shift to the real AIFunctionFactory, the request should be passed via AIFunctionArguments.Context.
Dictionary<string, object?> arguments = request.Params?.Arguments is IDictionary<string, object?> existingArgs ? Dictionary<string, object?> arguments = request.Params?.Arguments is { } paramArgs ?
new(existingArgs) : paramArgs.ToDictionary(entry => entry.Key, entry => entry.Value.AsObject()) :
[]; [];
arguments[RequestContextKey] = request; arguments[RequestContextKey] = request;

View file

@ -249,8 +249,8 @@ internal static class Program
} }
else if (request.Params?.Name == "complex_prompt") else if (request.Params?.Name == "complex_prompt")
{ {
string temperature = request.Params.Arguments?["temperature"]?.ToString() ?? "unknown"; string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown";
string style = request.Params.Arguments?["style"]?.ToString() ?? "unknown"; string style = request.Params.Arguments?["style"].ToString() ?? "unknown";
messages.Add(new PromptMessage() messages.Add(new PromptMessage()
{ {
Role = Role.User, Role = Role.User,

View file

@ -328,8 +328,8 @@ public class Program
} }
else if (request.Params.Name == "complex_prompt") else if (request.Params.Name == "complex_prompt")
{ {
string temperature = request.Params.Arguments?["temperature"]?.ToString() ?? "unknown"; string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown";
string style = request.Params.Arguments?["style"]?.ToString() ?? "unknown"; string style = request.Params.Arguments?["style"].ToString() ?? "unknown";
messages.Add(new PromptMessage() messages.Add(new PromptMessage()
{ {
Role = Role.User, Role = Role.User,