Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 56 additions & 60 deletions src/ModelContextProtocol/Client/McpClientExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public static Task PingAsync(this IMcpClient client, CancellationToken cancellat
Throw.IfNull(client);

return client.SendRequestAsync(
RequestMethods.Ping,
RequestMethods.Ping,
parameters: null,
McpJsonUtilities.JsonContext.Default.Object!,
McpJsonUtilities.JsonContext.Default.Object,
Expand Down Expand Up @@ -51,9 +51,9 @@ public static async Task<IList<McpClientTool>> ListToolsAsync(
do
{
var toolResults = await client.SendRequestAsync(
RequestMethods.ToolsList,
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
RequestMethods.ToolsList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
McpJsonUtilities.JsonContext.Default.ListToolsResult,
cancellationToken: cancellationToken).ConfigureAwait(false);

Expand Down Expand Up @@ -95,9 +95,9 @@ public static async IAsyncEnumerable<McpClientTool> EnumerateToolsAsync(
do
{
var toolResults = await client.SendRequestAsync(
RequestMethods.ToolsList,
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
RequestMethods.ToolsList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
McpJsonUtilities.JsonContext.Default.ListToolsResult,
cancellationToken: cancellationToken).ConfigureAwait(false);

Expand Down Expand Up @@ -127,9 +127,9 @@ public static async Task<IList<McpClientPrompt>> ListPromptsAsync(
do
{
var promptResults = await client.SendRequestAsync(
RequestMethods.PromptsList,
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
RequestMethods.PromptsList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
cancellationToken: cancellationToken).ConfigureAwait(false);

Expand Down Expand Up @@ -166,8 +166,8 @@ public static async IAsyncEnumerable<Prompt> EnumeratePromptsAsync(
{
var promptResults = await client.SendRequestAsync(
RequestMethods.PromptsList,
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
cancellationToken: cancellationToken).ConfigureAwait(false);

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

var parametersTypeInfo = serializerOptions.GetTypeInfo<IReadOnlyDictionary<string, object?>>();

return client.SendRequestAsync(
RequestMethods.PromptsGet,
CreateParametersDictionary(name, arguments),
parametersTypeInfo,
new() { Name = name, Arguments = ToArgumentsDictionary(arguments, serializerOptions) },
McpJsonUtilities.JsonContext.Default.GetPromptRequestParams,
McpJsonUtilities.JsonContext.Default.GetPromptResult,
cancellationToken: cancellationToken);
}
Expand All @@ -229,9 +227,9 @@ public static async Task<IList<ResourceTemplate>> ListResourceTemplatesAsync(
do
{
var templateResults = await client.SendRequestAsync(
RequestMethods.ResourcesTemplatesList,
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
RequestMethods.ResourcesTemplatesList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
cancellationToken: cancellationToken).ConfigureAwait(false);

Expand Down Expand Up @@ -270,9 +268,9 @@ public static async IAsyncEnumerable<ResourceTemplate> EnumerateResourceTemplate
do
{
var templateResults = await client.SendRequestAsync(
RequestMethods.ResourcesTemplatesList,
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
RequestMethods.ResourcesTemplatesList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
cancellationToken: cancellationToken).ConfigureAwait(false);

Expand Down Expand Up @@ -303,9 +301,9 @@ public static async Task<IList<Resource>> ListResourcesAsync(
do
{
var resourceResults = await client.SendRequestAsync(
RequestMethods.ResourcesList,
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
RequestMethods.ResourcesList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
cancellationToken: cancellationToken).ConfigureAwait(false);

Expand Down Expand Up @@ -344,9 +342,9 @@ public static async IAsyncEnumerable<Resource> EnumerateResourcesAsync(
do
{
var resourceResults = await client.SendRequestAsync(
RequestMethods.ResourcesList,
CreateCursorDictionary(cursor)!,
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
RequestMethods.ResourcesList,
new() { Cursor = cursor },
McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
cancellationToken: cancellationToken).ConfigureAwait(false);

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

return client.SendRequestAsync(
RequestMethods.ResourcesRead,
new Dictionary<string, object> { ["uri"] = uri },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
RequestMethods.ResourcesRead,
new() { Uri = uri },
McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams,
McpJsonUtilities.JsonContext.Default.ReadResourceResult,
cancellationToken: cancellationToken);
}
Expand All @@ -400,13 +398,13 @@ public static Task<CompleteResult> GetCompletionAsync(this IMcpClient client, Re
}

return client.SendRequestAsync(
RequestMethods.CompletionComplete,
new Dictionary<string, object>
RequestMethods.CompletionComplete,
new()
{
["ref"] = reference,
["argument"] = new Argument { Name = argumentName, Value = argumentValue }
Ref = reference,
Argument = new Argument { Name = argumentName, Value = argumentValue }
},
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
McpJsonUtilities.JsonContext.Default.CompleteRequestParams,
McpJsonUtilities.JsonContext.Default.CompleteResult,
cancellationToken: cancellationToken);
}
Expand All @@ -423,9 +421,9 @@ public static Task SubscribeToResourceAsync(this IMcpClient client, string uri,
Throw.IfNullOrWhiteSpace(uri);

return client.SendRequestAsync(
RequestMethods.ResourcesSubscribe,
new Dictionary<string, object> { ["uri"] = uri },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
RequestMethods.ResourcesSubscribe,
new() { Uri = uri },
McpJsonUtilities.JsonContext.Default.SubscribeRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken);
}
Expand All @@ -443,8 +441,8 @@ public static Task UnsubscribeFromResourceAsync(this IMcpClient client, string u

return client.SendRequestAsync(
RequestMethods.ResourcesUnsubscribe,
new Dictionary<string, object> { ["uri"] = uri },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
new() { Uri = uri },
McpJsonUtilities.JsonContext.Default.UnsubscribeRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken);
}
Expand All @@ -470,12 +468,10 @@ public static Task<CallToolResponse> CallToolAsync(
serializerOptions ??= McpJsonUtilities.DefaultOptions;
serializerOptions.MakeReadOnly();

var parametersTypeInfo = serializerOptions.GetTypeInfo<IReadOnlyDictionary<string, object?>>();

return client.SendRequestAsync(
RequestMethods.ToolsCall,
CreateParametersDictionary(toolName, arguments),
parametersTypeInfo,
RequestMethods.ToolsCall,
new() { Name = toolName, Arguments = ToArgumentsDictionary(arguments, serializerOptions) },
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
McpJsonUtilities.JsonContext.Default.CallToolResponse,
cancellationToken: cancellationToken);
}
Expand Down Expand Up @@ -629,28 +625,28 @@ public static Task SetLoggingLevel(this IMcpClient client, LoggingLevel level, C

return client.SendRequestAsync(
RequestMethods.LoggingSetLevel,
new Dictionary<string, object> { ["level"] = level },
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
new() { Level = level },
McpJsonUtilities.JsonContext.Default.SetLevelRequestParams,
McpJsonUtilities.JsonContext.Default.EmptyResult,
cancellationToken: cancellationToken);
}

private static Dictionary<string, object?>? CreateCursorDictionary(string? cursor) =>
cursor != null ? new() { ["cursor"] = cursor } : null;

private static Dictionary<string, object?> CreateParametersDictionary(
string nameParameter, IReadOnlyDictionary<string, object?>? arguments)
/// <summary>Convers a dictionary with <see cref="object"/> values to a dictionary with <see cref="JsonElement"/> values.</summary>
private static IReadOnlyDictionary<string, JsonElement>? ToArgumentsDictionary(
IReadOnlyDictionary<string, object?>? arguments, JsonSerializerOptions options)
{
Dictionary<string, object?> parameters = new()
{
["name"] = nameParameter
};
var typeInfo = options.GetTypeInfo<object?>();

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;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol.Types;

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

/// <summary>
/// Optional arguments to pass to the tool.
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName("arguments")]
public Dictionary<string, JsonElement>? Arguments { get; init; }
[JsonPropertyName("arguments")]
public IReadOnlyDictionary<string, JsonElement>? Arguments { get; init; }
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
namespace ModelContextProtocol.Protocol.Types;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace ModelContextProtocol.Protocol.Types;

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

/// <summary>
/// Arguments to use for templating the prompt.
/// </summary>
[System.Text.Json.Serialization.JsonPropertyName("arguments")]
public Dictionary<string, object>? Arguments { get; init; }
[JsonPropertyName("arguments")]
public IReadOnlyDictionary<string, JsonElement>? Arguments { get; init; }
}
5 changes: 3 additions & 2 deletions src/ModelContextProtocol/Server/AIFunctionMcpServerPrompt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Protocol.Types;
using ModelContextProtocol.Utils;
using ModelContextProtocol.Utils.Json;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.Json;
Expand Down Expand Up @@ -207,8 +208,8 @@ public override async Task<GetPromptResult> GetAsync(
cancellationToken.ThrowIfCancellationRequested();

// 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 ?
new(existingArgs) :
Dictionary<string, object?> arguments = request.Params?.Arguments is { } paramArgs ?
paramArgs.ToDictionary(entry => entry.Key, entry => entry.Value.AsObject()) :
[];
arguments[RequestContextKey] = request;

Expand Down
4 changes: 2 additions & 2 deletions tests/ModelContextProtocol.TestServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,8 @@ private static PromptsCapability ConfigurePrompts()
}
else if (request.Params?.Name == "complex_prompt")
{
string temperature = request.Params.Arguments?["temperature"]?.ToString() ?? "unknown";
string style = request.Params.Arguments?["style"]?.ToString() ?? "unknown";
string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown";
string style = request.Params.Arguments?["style"].ToString() ?? "unknown";
messages.Add(new PromptMessage()
{
Role = Role.User,
Expand Down
4 changes: 2 additions & 2 deletions tests/ModelContextProtocol.TestSseServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -328,8 +328,8 @@ static CreateMessageRequestParams CreateRequestSamplingParams(string context, st
}
else if (request.Params.Name == "complex_prompt")
{
string temperature = request.Params.Arguments?["temperature"]?.ToString() ?? "unknown";
string style = request.Params.Arguments?["style"]?.ToString() ?? "unknown";
string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown";
string style = request.Params.Arguments?["style"].ToString() ?? "unknown";
messages.Add(new PromptMessage()
{
Role = Role.User,
Expand Down