Skip to content

Commit 03dc90a

Browse files
authored
Use strong-typing of params in most remaining McpClientExtensions methods (#224)
1 parent faf12b6 commit 03dc90a

File tree

6 files changed

+74
-73
lines changed

6 files changed

+74
-73
lines changed

src/ModelContextProtocol/Client/McpClientExtensions.cs

Lines changed: 56 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public static Task PingAsync(this IMcpClient client, CancellationToken cancellat
2222
Throw.IfNull(client);
2323

2424
return client.SendRequestAsync(
25-
RequestMethods.Ping,
25+
RequestMethods.Ping,
2626
parameters: null,
2727
McpJsonUtilities.JsonContext.Default.Object!,
2828
McpJsonUtilities.JsonContext.Default.Object,
@@ -51,9 +51,9 @@ public static async Task<IList<McpClientTool>> ListToolsAsync(
5151
do
5252
{
5353
var toolResults = await client.SendRequestAsync(
54-
RequestMethods.ToolsList,
55-
CreateCursorDictionary(cursor)!,
56-
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
54+
RequestMethods.ToolsList,
55+
new() { Cursor = cursor },
56+
McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
5757
McpJsonUtilities.JsonContext.Default.ListToolsResult,
5858
cancellationToken: cancellationToken).ConfigureAwait(false);
5959

@@ -95,9 +95,9 @@ public static async IAsyncEnumerable<McpClientTool> EnumerateToolsAsync(
9595
do
9696
{
9797
var toolResults = await client.SendRequestAsync(
98-
RequestMethods.ToolsList,
99-
CreateCursorDictionary(cursor)!,
100-
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
98+
RequestMethods.ToolsList,
99+
new() { Cursor = cursor },
100+
McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
101101
McpJsonUtilities.JsonContext.Default.ListToolsResult,
102102
cancellationToken: cancellationToken).ConfigureAwait(false);
103103

@@ -127,9 +127,9 @@ public static async Task<IList<McpClientPrompt>> ListPromptsAsync(
127127
do
128128
{
129129
var promptResults = await client.SendRequestAsync(
130-
RequestMethods.PromptsList,
131-
CreateCursorDictionary(cursor)!,
132-
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
130+
RequestMethods.PromptsList,
131+
new() { Cursor = cursor },
132+
McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
133133
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
134134
cancellationToken: cancellationToken).ConfigureAwait(false);
135135

@@ -166,8 +166,8 @@ public static async IAsyncEnumerable<Prompt> EnumeratePromptsAsync(
166166
{
167167
var promptResults = await client.SendRequestAsync(
168168
RequestMethods.PromptsList,
169-
CreateCursorDictionary(cursor)!,
170-
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
169+
new() { Cursor = cursor },
170+
McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
171171
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
172172
cancellationToken: cancellationToken).ConfigureAwait(false);
173173

@@ -202,12 +202,10 @@ public static Task<GetPromptResult> GetPromptAsync(
202202
serializerOptions ??= McpJsonUtilities.DefaultOptions;
203203
serializerOptions.MakeReadOnly();
204204

205-
var parametersTypeInfo = serializerOptions.GetTypeInfo<IReadOnlyDictionary<string, object?>>();
206-
207205
return client.SendRequestAsync(
208206
RequestMethods.PromptsGet,
209-
CreateParametersDictionary(name, arguments),
210-
parametersTypeInfo,
207+
new() { Name = name, Arguments = ToArgumentsDictionary(arguments, serializerOptions) },
208+
McpJsonUtilities.JsonContext.Default.GetPromptRequestParams,
211209
McpJsonUtilities.JsonContext.Default.GetPromptResult,
212210
cancellationToken: cancellationToken);
213211
}
@@ -229,9 +227,9 @@ public static async Task<IList<ResourceTemplate>> ListResourceTemplatesAsync(
229227
do
230228
{
231229
var templateResults = await client.SendRequestAsync(
232-
RequestMethods.ResourcesTemplatesList,
233-
CreateCursorDictionary(cursor)!,
234-
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
230+
RequestMethods.ResourcesTemplatesList,
231+
new() { Cursor = cursor },
232+
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
235233
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
236234
cancellationToken: cancellationToken).ConfigureAwait(false);
237235

@@ -270,9 +268,9 @@ public static async IAsyncEnumerable<ResourceTemplate> EnumerateResourceTemplate
270268
do
271269
{
272270
var templateResults = await client.SendRequestAsync(
273-
RequestMethods.ResourcesTemplatesList,
274-
CreateCursorDictionary(cursor)!,
275-
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
271+
RequestMethods.ResourcesTemplatesList,
272+
new() { Cursor = cursor },
273+
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
276274
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
277275
cancellationToken: cancellationToken).ConfigureAwait(false);
278276

@@ -303,9 +301,9 @@ public static async Task<IList<Resource>> ListResourcesAsync(
303301
do
304302
{
305303
var resourceResults = await client.SendRequestAsync(
306-
RequestMethods.ResourcesList,
307-
CreateCursorDictionary(cursor)!,
308-
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
304+
RequestMethods.ResourcesList,
305+
new() { Cursor = cursor },
306+
McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
309307
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
310308
cancellationToken: cancellationToken).ConfigureAwait(false);
311309

@@ -344,9 +342,9 @@ public static async IAsyncEnumerable<Resource> EnumerateResourcesAsync(
344342
do
345343
{
346344
var resourceResults = await client.SendRequestAsync(
347-
RequestMethods.ResourcesList,
348-
CreateCursorDictionary(cursor)!,
349-
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
345+
RequestMethods.ResourcesList,
346+
new() { Cursor = cursor },
347+
McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
350348
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
351349
cancellationToken: cancellationToken).ConfigureAwait(false);
352350

@@ -373,9 +371,9 @@ public static Task<ReadResourceResult> ReadResourceAsync(
373371
Throw.IfNullOrWhiteSpace(uri);
374372

375373
return client.SendRequestAsync(
376-
RequestMethods.ResourcesRead,
377-
new Dictionary<string, object> { ["uri"] = uri },
378-
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
374+
RequestMethods.ResourcesRead,
375+
new() { Uri = uri },
376+
McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams,
379377
McpJsonUtilities.JsonContext.Default.ReadResourceResult,
380378
cancellationToken: cancellationToken);
381379
}
@@ -400,13 +398,13 @@ public static Task<CompleteResult> GetCompletionAsync(this IMcpClient client, Re
400398
}
401399

402400
return client.SendRequestAsync(
403-
RequestMethods.CompletionComplete,
404-
new Dictionary<string, object>
401+
RequestMethods.CompletionComplete,
402+
new()
405403
{
406-
["ref"] = reference,
407-
["argument"] = new Argument { Name = argumentName, Value = argumentValue }
404+
Ref = reference,
405+
Argument = new Argument { Name = argumentName, Value = argumentValue }
408406
},
409-
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
407+
McpJsonUtilities.JsonContext.Default.CompleteRequestParams,
410408
McpJsonUtilities.JsonContext.Default.CompleteResult,
411409
cancellationToken: cancellationToken);
412410
}
@@ -423,9 +421,9 @@ public static Task SubscribeToResourceAsync(this IMcpClient client, string uri,
423421
Throw.IfNullOrWhiteSpace(uri);
424422

425423
return client.SendRequestAsync(
426-
RequestMethods.ResourcesSubscribe,
427-
new Dictionary<string, object> { ["uri"] = uri },
428-
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
424+
RequestMethods.ResourcesSubscribe,
425+
new() { Uri = uri },
426+
McpJsonUtilities.JsonContext.Default.SubscribeRequestParams,
429427
McpJsonUtilities.JsonContext.Default.EmptyResult,
430428
cancellationToken: cancellationToken);
431429
}
@@ -443,8 +441,8 @@ public static Task UnsubscribeFromResourceAsync(this IMcpClient client, string u
443441

444442
return client.SendRequestAsync(
445443
RequestMethods.ResourcesUnsubscribe,
446-
new Dictionary<string, object> { ["uri"] = uri },
447-
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
444+
new() { Uri = uri },
445+
McpJsonUtilities.JsonContext.Default.UnsubscribeRequestParams,
448446
McpJsonUtilities.JsonContext.Default.EmptyResult,
449447
cancellationToken: cancellationToken);
450448
}
@@ -470,12 +468,10 @@ public static Task<CallToolResponse> CallToolAsync(
470468
serializerOptions ??= McpJsonUtilities.DefaultOptions;
471469
serializerOptions.MakeReadOnly();
472470

473-
var parametersTypeInfo = serializerOptions.GetTypeInfo<IReadOnlyDictionary<string, object?>>();
474-
475471
return client.SendRequestAsync(
476-
RequestMethods.ToolsCall,
477-
CreateParametersDictionary(toolName, arguments),
478-
parametersTypeInfo,
472+
RequestMethods.ToolsCall,
473+
new() { Name = toolName, Arguments = ToArgumentsDictionary(arguments, serializerOptions) },
474+
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
479475
McpJsonUtilities.JsonContext.Default.CallToolResponse,
480476
cancellationToken: cancellationToken);
481477
}
@@ -629,28 +625,28 @@ public static Task SetLoggingLevel(this IMcpClient client, LoggingLevel level, C
629625

630626
return client.SendRequestAsync(
631627
RequestMethods.LoggingSetLevel,
632-
new Dictionary<string, object> { ["level"] = level },
633-
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
628+
new() { Level = level },
629+
McpJsonUtilities.JsonContext.Default.SetLevelRequestParams,
634630
McpJsonUtilities.JsonContext.Default.EmptyResult,
635631
cancellationToken: cancellationToken);
636632
}
637633

638-
private static Dictionary<string, object?>? CreateCursorDictionary(string? cursor) =>
639-
cursor != null ? new() { ["cursor"] = cursor } : null;
640-
641-
private static Dictionary<string, object?> CreateParametersDictionary(
642-
string nameParameter, IReadOnlyDictionary<string, object?>? arguments)
634+
/// <summary>Convers a dictionary with <see cref="object"/> values to a dictionary with <see cref="JsonElement"/> values.</summary>
635+
private static IReadOnlyDictionary<string, JsonElement>? ToArgumentsDictionary(
636+
IReadOnlyDictionary<string, object?>? arguments, JsonSerializerOptions options)
643637
{
644-
Dictionary<string, object?> parameters = new()
645-
{
646-
["name"] = nameParameter
647-
};
638+
var typeInfo = options.GetTypeInfo<object?>();
648639

649-
if (arguments != null)
640+
Dictionary<string, JsonElement>? result = null;
641+
if (arguments is not null)
650642
{
651-
parameters["arguments"] = arguments;
643+
result = new(arguments.Count);
644+
foreach (var kvp in arguments)
645+
{
646+
result.Add(kvp.Key, kvp.Value is JsonElement je ? je : JsonSerializer.SerializeToElement(kvp.Value, typeInfo));
647+
}
652648
}
653649

654-
return parameters;
650+
return result;
655651
}
656652
}

src/ModelContextProtocol/Protocol/Types/CallToolRequestParams.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Text.Json;
2+
using System.Text.Json.Serialization;
23

34
namespace ModelContextProtocol.Protocol.Types;
45

@@ -11,12 +12,12 @@ public class CallToolRequestParams : RequestParams
1112
/// <summary>
1213
/// Tool name.
1314
/// </summary>
14-
[System.Text.Json.Serialization.JsonPropertyName("name")]
15+
[JsonPropertyName("name")]
1516
public required string Name { get; init; }
1617

1718
/// <summary>
1819
/// Optional arguments to pass to the tool.
1920
/// </summary>
20-
[System.Text.Json.Serialization.JsonPropertyName("arguments")]
21-
public Dictionary<string, JsonElement>? Arguments { get; init; }
21+
[JsonPropertyName("arguments")]
22+
public IReadOnlyDictionary<string, JsonElement>? Arguments { get; init; }
2223
}
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
namespace ModelContextProtocol.Protocol.Types;
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
3+
4+
namespace ModelContextProtocol.Protocol.Types;
25

36
/// <summary>
47
/// Used by the client to get a prompt provided by the server.
@@ -9,12 +12,12 @@ public class GetPromptRequestParams : RequestParams
912
/// <summary>
1013
/// he name of the prompt or prompt template.
1114
/// </summary>
12-
[System.Text.Json.Serialization.JsonPropertyName("name")]
15+
[JsonPropertyName("name")]
1316
public required string Name { get; init; }
1417

1518
/// <summary>
1619
/// Arguments to use for templating the prompt.
1720
/// </summary>
18-
[System.Text.Json.Serialization.JsonPropertyName("arguments")]
19-
public Dictionary<string, object>? Arguments { get; init; }
21+
[JsonPropertyName("arguments")]
22+
public IReadOnlyDictionary<string, JsonElement>? Arguments { get; init; }
2023
}

src/ModelContextProtocol/Server/AIFunctionMcpServerPrompt.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using Microsoft.Extensions.DependencyInjection;
33
using ModelContextProtocol.Protocol.Types;
44
using ModelContextProtocol.Utils;
5+
using ModelContextProtocol.Utils.Json;
56
using System.Diagnostics.CodeAnalysis;
67
using System.Reflection;
78
using System.Text.Json;
@@ -207,8 +208,8 @@ public override async Task<GetPromptResult> GetAsync(
207208
cancellationToken.ThrowIfCancellationRequested();
208209

209210
// TODO: Once we shift to the real AIFunctionFactory, the request should be passed via AIFunctionArguments.Context.
210-
Dictionary<string, object?> arguments = request.Params?.Arguments is IDictionary<string, object?> existingArgs ?
211-
new(existingArgs) :
211+
Dictionary<string, object?> arguments = request.Params?.Arguments is { } paramArgs ?
212+
paramArgs.ToDictionary(entry => entry.Key, entry => entry.Value.AsObject()) :
212213
[];
213214
arguments[RequestContextKey] = request;
214215

tests/ModelContextProtocol.TestServer/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,8 @@ private static PromptsCapability ConfigurePrompts()
249249
}
250250
else if (request.Params?.Name == "complex_prompt")
251251
{
252-
string temperature = request.Params.Arguments?["temperature"]?.ToString() ?? "unknown";
253-
string style = request.Params.Arguments?["style"]?.ToString() ?? "unknown";
252+
string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown";
253+
string style = request.Params.Arguments?["style"].ToString() ?? "unknown";
254254
messages.Add(new PromptMessage()
255255
{
256256
Role = Role.User,

tests/ModelContextProtocol.TestSseServer/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,8 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st
328328
}
329329
else if (request.Params.Name == "complex_prompt")
330330
{
331-
string temperature = request.Params.Arguments?["temperature"]?.ToString() ?? "unknown";
332-
string style = request.Params.Arguments?["style"]?.ToString() ?? "unknown";
331+
string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown";
332+
string style = request.Params.Arguments?["style"].ToString() ?? "unknown";
333333
messages.Add(new PromptMessage()
334334
{
335335
Role = Role.User,

0 commit comments

Comments
 (0)