Allow JsonSerializerOptions parameters for user-defined and inputs and hardcode everything else to the source generator. (#182)
* Allow user-defined JsonSerializerOptions for user-defined and inputs and hardcode everything else to the source generator. * Add validation for user-defined JsonSerializerOptions. * Remove out of date comment. * Address feedback and add tests
This commit is contained in:
parent
4b38d3e71b
commit
f1af251c32
36 changed files with 632 additions and 372 deletions
|
|
@ -40,12 +40,14 @@ internal sealed class McpClient : McpJsonRpcEndpoint, IMcpClient
|
||||||
throw new InvalidOperationException($"Sampling capability was set but it did not provide a handler.");
|
throw new InvalidOperationException($"Sampling capability was set but it did not provide a handler.");
|
||||||
}
|
}
|
||||||
|
|
||||||
SetRequestHandler<CreateMessageRequestParams, CreateMessageResult>(
|
SetRequestHandler(
|
||||||
RequestMethods.SamplingCreateMessage,
|
RequestMethods.SamplingCreateMessage,
|
||||||
(request, cancellationToken) => samplingHandler(
|
(request, cancellationToken) => samplingHandler(
|
||||||
request,
|
request,
|
||||||
request?.Meta?.ProgressToken is { } token ? new TokenProgress(this, token) : NullProgress.Instance,
|
request?.Meta?.ProgressToken is { } token ? new TokenProgress(this, token) : NullProgress.Instance,
|
||||||
cancellationToken));
|
cancellationToken),
|
||||||
|
McpJsonUtilities.JsonContext.Default.CreateMessageRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.CreateMessageResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.Capabilities?.Roots is { } rootsCapability)
|
if (options.Capabilities?.Roots is { } rootsCapability)
|
||||||
|
|
@ -55,9 +57,11 @@ internal sealed class McpClient : McpJsonRpcEndpoint, IMcpClient
|
||||||
throw new InvalidOperationException($"Roots capability was set but it did not provide a handler.");
|
throw new InvalidOperationException($"Roots capability was set but it did not provide a handler.");
|
||||||
}
|
}
|
||||||
|
|
||||||
SetRequestHandler<ListRootsRequestParams, ListRootsResult>(
|
SetRequestHandler(
|
||||||
RequestMethods.RootsList,
|
RequestMethods.RootsList,
|
||||||
(request, cancellationToken) => rootsHandler(request, cancellationToken));
|
rootsHandler,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListRootsRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListRootsResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,21 +92,20 @@ internal sealed class McpClient : McpJsonRpcEndpoint, IMcpClient
|
||||||
using var initializationCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
using var initializationCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
initializationCts.CancelAfter(_options.InitializationTimeout);
|
initializationCts.CancelAfter(_options.InitializationTimeout);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Send initialize request
|
// Send initialize request
|
||||||
var initializeResponse = await SendRequestAsync<InitializeResult>(
|
var initializeResponse = await this.SendRequestAsync(
|
||||||
new JsonRpcRequest
|
RequestMethods.Initialize,
|
||||||
{
|
new InitializeRequestParams
|
||||||
Method = RequestMethods.Initialize,
|
{
|
||||||
Params = new InitializeRequestParams()
|
ProtocolVersion = _options.ProtocolVersion,
|
||||||
{
|
Capabilities = _options.Capabilities ?? new ClientCapabilities(),
|
||||||
ProtocolVersion = _options.ProtocolVersion,
|
ClientInfo = _options.ClientInfo
|
||||||
Capabilities = _options.Capabilities ?? new ClientCapabilities(),
|
},
|
||||||
ClientInfo = _options.ClientInfo
|
McpJsonUtilities.JsonContext.Default.InitializeRequestParams,
|
||||||
}
|
McpJsonUtilities.JsonContext.Default.InitializeResult,
|
||||||
},
|
cancellationToken: initializationCts.Token).ConfigureAwait(false);
|
||||||
initializationCts.Token).ConfigureAwait(false);
|
|
||||||
|
|
||||||
// Store server information
|
// Store server information
|
||||||
_logger.ServerCapabilitiesReceived(EndpointName,
|
_logger.ServerCapabilitiesReceived(EndpointName,
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,16 @@
|
||||||
using ModelContextProtocol.Protocol.Messages;
|
using Microsoft.Extensions.AI;
|
||||||
|
using ModelContextProtocol.Protocol.Messages;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
using ModelContextProtocol.Utils.Json;
|
using ModelContextProtocol.Utils.Json;
|
||||||
using Microsoft.Extensions.AI;
|
|
||||||
using System.Text.Json;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Client;
|
namespace ModelContextProtocol.Client;
|
||||||
|
|
||||||
/// <summary>Provides extension methods for interacting with an <see cref="IMcpClient"/>.</summary>
|
/// <summary>Provides extension methods for interacting with an <see cref="IMcpClient"/>.</summary>
|
||||||
public static class McpClientExtensions
|
public static class McpClientExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Sends a notification to the server with parameters.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client">The client.</param>
|
|
||||||
/// <param name="method">The notification method name.</param>
|
|
||||||
/// <param name="parameters">The parameters to send with the notification.</param>
|
|
||||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
|
||||||
public static Task SendNotificationAsync(this IMcpClient client, string method, object? parameters = null, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
Throw.IfNull(client);
|
|
||||||
Throw.IfNullOrWhiteSpace(method);
|
|
||||||
|
|
||||||
return client.SendMessageAsync(
|
|
||||||
new JsonRpcNotification { Method = method, Params = parameters },
|
|
||||||
cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends a ping request to verify server connectivity.
|
/// Sends a ping request to verify server connectivity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -38,34 +21,46 @@ public static class McpClientExtensions
|
||||||
{
|
{
|
||||||
Throw.IfNull(client);
|
Throw.IfNull(client);
|
||||||
|
|
||||||
return client.SendRequestAsync<dynamic>(
|
return client.SendRequestAsync(
|
||||||
CreateRequest(RequestMethods.Ping, null),
|
RequestMethods.Ping,
|
||||||
cancellationToken);
|
parameters: null,
|
||||||
|
McpJsonUtilities.JsonContext.Default.Object,
|
||||||
|
McpJsonUtilities.JsonContext.Default.Object,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves a list of available tools from the server.
|
/// Retrieves a list of available tools from the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client">The client.</param>
|
/// <param name="client">The client.</param>
|
||||||
|
/// <param name="serializerOptions">The serializer options governing tool parameter serialization.</param>
|
||||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
/// <returns>A list of all available tools.</returns>
|
/// <returns>A list of all available tools.</returns>
|
||||||
public static async Task<IList<McpClientTool>> ListToolsAsync(
|
public static async Task<IList<McpClientTool>> ListToolsAsync(
|
||||||
this IMcpClient client, CancellationToken cancellationToken = default)
|
this IMcpClient client,
|
||||||
|
JsonSerializerOptions? serializerOptions = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
Throw.IfNull(client);
|
Throw.IfNull(client);
|
||||||
|
|
||||||
|
serializerOptions ??= McpJsonUtilities.DefaultOptions;
|
||||||
|
serializerOptions.MakeReadOnly();
|
||||||
|
|
||||||
List<McpClientTool>? tools = null;
|
List<McpClientTool>? tools = null;
|
||||||
string? cursor = null;
|
string? cursor = null;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var toolResults = await client.SendRequestAsync<ListToolsResult>(
|
var toolResults = await client.SendRequestAsync(
|
||||||
CreateRequest(RequestMethods.ToolsList, CreateCursorDictionary(cursor)),
|
RequestMethods.ToolsList,
|
||||||
cancellationToken).ConfigureAwait(false);
|
CreateCursorDictionary(cursor),
|
||||||
|
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListToolsResult,
|
||||||
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
tools ??= new List<McpClientTool>(toolResults.Tools.Count);
|
tools ??= new List<McpClientTool>(toolResults.Tools.Count);
|
||||||
foreach (var tool in toolResults.Tools)
|
foreach (var tool in toolResults.Tools)
|
||||||
{
|
{
|
||||||
tools.Add(new McpClientTool(client, tool));
|
tools.Add(new McpClientTool(client, tool, serializerOptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor = toolResults.NextCursor;
|
cursor = toolResults.NextCursor;
|
||||||
|
|
@ -79,6 +74,7 @@ public static class McpClientExtensions
|
||||||
/// Creates an enumerable for asynchronously enumerating all available tools from the server.
|
/// Creates an enumerable for asynchronously enumerating all available tools from the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="client">The client.</param>
|
/// <param name="client">The client.</param>
|
||||||
|
/// <param name="serializerOptions">The serializer options governing tool parameter serialization.</param>
|
||||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
/// <returns>An asynchronous sequence of all available tools.</returns>
|
/// <returns>An asynchronous sequence of all available tools.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
|
@ -86,20 +82,28 @@ public static class McpClientExtensions
|
||||||
/// will result in requerying the server and yielding the sequence of available tools.
|
/// will result in requerying the server and yielding the sequence of available tools.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public static async IAsyncEnumerable<McpClientTool> EnumerateToolsAsync(
|
public static async IAsyncEnumerable<McpClientTool> EnumerateToolsAsync(
|
||||||
this IMcpClient client, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
this IMcpClient client,
|
||||||
|
JsonSerializerOptions? serializerOptions = null,
|
||||||
|
[EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
Throw.IfNull(client);
|
Throw.IfNull(client);
|
||||||
|
|
||||||
|
serializerOptions ??= McpJsonUtilities.DefaultOptions;
|
||||||
|
serializerOptions.MakeReadOnly();
|
||||||
|
|
||||||
string? cursor = null;
|
string? cursor = null;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var toolResults = await client.SendRequestAsync<ListToolsResult>(
|
var toolResults = await client.SendRequestAsync(
|
||||||
CreateRequest(RequestMethods.ToolsList, CreateCursorDictionary(cursor)),
|
RequestMethods.ToolsList,
|
||||||
cancellationToken).ConfigureAwait(false);
|
CreateCursorDictionary(cursor),
|
||||||
|
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListToolsResult,
|
||||||
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
foreach (var tool in toolResults.Tools)
|
foreach (var tool in toolResults.Tools)
|
||||||
{
|
{
|
||||||
yield return new McpClientTool(client, tool);
|
yield return new McpClientTool(client, tool, serializerOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
cursor = toolResults.NextCursor;
|
cursor = toolResults.NextCursor;
|
||||||
|
|
@ -122,9 +126,12 @@ public static class McpClientExtensions
|
||||||
string? cursor = null;
|
string? cursor = null;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var promptResults = await client.SendRequestAsync<ListPromptsResult>(
|
var promptResults = await client.SendRequestAsync(
|
||||||
CreateRequest(RequestMethods.PromptsList, CreateCursorDictionary(cursor)),
|
RequestMethods.PromptsList,
|
||||||
cancellationToken).ConfigureAwait(false);
|
CreateCursorDictionary(cursor),
|
||||||
|
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
|
||||||
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
prompts ??= new List<McpClientPrompt>(promptResults.Prompts.Count);
|
prompts ??= new List<McpClientPrompt>(promptResults.Prompts.Count);
|
||||||
foreach (var prompt in promptResults.Prompts)
|
foreach (var prompt in promptResults.Prompts)
|
||||||
|
|
@ -157,9 +164,12 @@ public static class McpClientExtensions
|
||||||
string? cursor = null;
|
string? cursor = null;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var promptResults = await client.SendRequestAsync<ListPromptsResult>(
|
var promptResults = await client.SendRequestAsync(
|
||||||
CreateRequest(RequestMethods.PromptsList, CreateCursorDictionary(cursor)),
|
RequestMethods.PromptsList,
|
||||||
cancellationToken).ConfigureAwait(false);
|
CreateCursorDictionary(cursor),
|
||||||
|
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListPromptsResult,
|
||||||
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
foreach (var prompt in promptResults.Prompts)
|
foreach (var prompt in promptResults.Prompts)
|
||||||
{
|
{
|
||||||
|
|
@ -177,17 +187,29 @@ public static class McpClientExtensions
|
||||||
/// <param name="client">The client.</param>
|
/// <param name="client">The client.</param>
|
||||||
/// <param name="name">The name of the prompt to retrieve</param>
|
/// <param name="name">The name of the prompt to retrieve</param>
|
||||||
/// <param name="arguments">Optional arguments for the prompt</param>
|
/// <param name="arguments">Optional arguments for the prompt</param>
|
||||||
|
/// <param name="serializerOptions">The serialization options governing argument serialization.</param>
|
||||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
/// <returns>A task containing the prompt's content and messages.</returns>
|
/// <returns>A task containing the prompt's content and messages.</returns>
|
||||||
public static Task<GetPromptResult> GetPromptAsync(
|
public static Task<GetPromptResult> GetPromptAsync(
|
||||||
this IMcpClient client, string name, IReadOnlyDictionary<string, object?>? arguments = null, CancellationToken cancellationToken = default)
|
this IMcpClient client,
|
||||||
|
string name,
|
||||||
|
IReadOnlyDictionary<string, object?>? arguments = null,
|
||||||
|
JsonSerializerOptions? serializerOptions = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
Throw.IfNull(client);
|
Throw.IfNull(client);
|
||||||
Throw.IfNullOrWhiteSpace(name);
|
Throw.IfNullOrWhiteSpace(name);
|
||||||
|
serializerOptions ??= McpJsonUtilities.DefaultOptions;
|
||||||
|
serializerOptions.MakeReadOnly();
|
||||||
|
|
||||||
return client.SendRequestAsync<GetPromptResult>(
|
var parametersTypeInfo = serializerOptions.GetTypeInfo<IReadOnlyDictionary<string, object?>>();
|
||||||
CreateRequest(RequestMethods.PromptsGet, CreateParametersDictionary(name, arguments)),
|
|
||||||
cancellationToken);
|
return client.SendRequestAsync(
|
||||||
|
RequestMethods.PromptsGet,
|
||||||
|
CreateParametersDictionary(name, arguments),
|
||||||
|
parametersTypeInfo,
|
||||||
|
McpJsonUtilities.JsonContext.Default.GetPromptResult,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -206,9 +228,12 @@ public static class McpClientExtensions
|
||||||
string? cursor = null;
|
string? cursor = null;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var templateResults = await client.SendRequestAsync<ListResourceTemplatesResult>(
|
var templateResults = await client.SendRequestAsync(
|
||||||
CreateRequest(RequestMethods.ResourcesTemplatesList, CreateCursorDictionary(cursor)),
|
RequestMethods.ResourcesTemplatesList,
|
||||||
cancellationToken).ConfigureAwait(false);
|
CreateCursorDictionary(cursor),
|
||||||
|
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
|
||||||
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (templates is null)
|
if (templates is null)
|
||||||
{
|
{
|
||||||
|
|
@ -244,9 +269,12 @@ public static class McpClientExtensions
|
||||||
string? cursor = null;
|
string? cursor = null;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var templateResults = await client.SendRequestAsync<ListResourceTemplatesResult>(
|
var templateResults = await client.SendRequestAsync(
|
||||||
CreateRequest(RequestMethods.ResourcesTemplatesList, CreateCursorDictionary(cursor)),
|
RequestMethods.ResourcesTemplatesList,
|
||||||
cancellationToken).ConfigureAwait(false);
|
CreateCursorDictionary(cursor),
|
||||||
|
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult,
|
||||||
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
foreach (var template in templateResults.ResourceTemplates)
|
foreach (var template in templateResults.ResourceTemplates)
|
||||||
{
|
{
|
||||||
|
|
@ -274,9 +302,12 @@ public static class McpClientExtensions
|
||||||
string? cursor = null;
|
string? cursor = null;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var resourceResults = await client.SendRequestAsync<ListResourcesResult>(
|
var resourceResults = await client.SendRequestAsync(
|
||||||
CreateRequest(RequestMethods.ResourcesList, CreateCursorDictionary(cursor)),
|
RequestMethods.ResourcesList,
|
||||||
cancellationToken).ConfigureAwait(false);
|
CreateCursorDictionary(cursor),
|
||||||
|
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
|
||||||
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
if (resources is null)
|
if (resources is null)
|
||||||
{
|
{
|
||||||
|
|
@ -312,9 +343,12 @@ public static class McpClientExtensions
|
||||||
string? cursor = null;
|
string? cursor = null;
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
var resourceResults = await client.SendRequestAsync<ListResourcesResult>(
|
var resourceResults = await client.SendRequestAsync(
|
||||||
CreateRequest(RequestMethods.ResourcesList, CreateCursorDictionary(cursor)),
|
RequestMethods.ResourcesList,
|
||||||
cancellationToken).ConfigureAwait(false);
|
CreateCursorDictionary(cursor),
|
||||||
|
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListResourcesResult,
|
||||||
|
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
foreach (var resource in resourceResults.Resources)
|
foreach (var resource in resourceResults.Resources)
|
||||||
{
|
{
|
||||||
|
|
@ -338,9 +372,12 @@ public static class McpClientExtensions
|
||||||
Throw.IfNull(client);
|
Throw.IfNull(client);
|
||||||
Throw.IfNullOrWhiteSpace(uri);
|
Throw.IfNullOrWhiteSpace(uri);
|
||||||
|
|
||||||
return client.SendRequestAsync<ReadResourceResult>(
|
return client.SendRequestAsync(
|
||||||
CreateRequest(RequestMethods.ResourcesRead, new Dictionary<string, object?>() { ["uri"] = uri }),
|
RequestMethods.ResourcesRead,
|
||||||
cancellationToken);
|
new Dictionary<string, object?> { ["uri"] = uri },
|
||||||
|
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ReadResourceResult,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -362,13 +399,16 @@ public static class McpClientExtensions
|
||||||
throw new ArgumentException($"Invalid reference: {validationMessage}", nameof(reference));
|
throw new ArgumentException($"Invalid reference: {validationMessage}", nameof(reference));
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.SendRequestAsync<CompleteResult>(
|
return client.SendRequestAsync(
|
||||||
CreateRequest(RequestMethods.CompletionComplete, new Dictionary<string, object?>()
|
RequestMethods.CompletionComplete,
|
||||||
|
new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
["ref"] = reference,
|
["ref"] = reference,
|
||||||
["argument"] = new Argument { Name = argumentName, Value = argumentValue }
|
["argument"] = new Argument { Name = argumentName, Value = argumentValue }
|
||||||
}),
|
},
|
||||||
cancellationToken);
|
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||||
|
McpJsonUtilities.JsonContext.Default.CompleteResult,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -382,9 +422,12 @@ public static class McpClientExtensions
|
||||||
Throw.IfNull(client);
|
Throw.IfNull(client);
|
||||||
Throw.IfNullOrWhiteSpace(uri);
|
Throw.IfNullOrWhiteSpace(uri);
|
||||||
|
|
||||||
return client.SendRequestAsync<EmptyResult>(
|
return client.SendRequestAsync(
|
||||||
CreateRequest(RequestMethods.ResourcesSubscribe, new Dictionary<string, object?>() { ["uri"] = uri }),
|
RequestMethods.ResourcesSubscribe,
|
||||||
cancellationToken);
|
new Dictionary<string, object?> { ["uri"] = uri },
|
||||||
|
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||||
|
McpJsonUtilities.JsonContext.Default.EmptyResult,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -398,9 +441,12 @@ public static class McpClientExtensions
|
||||||
Throw.IfNull(client);
|
Throw.IfNull(client);
|
||||||
Throw.IfNullOrWhiteSpace(uri);
|
Throw.IfNullOrWhiteSpace(uri);
|
||||||
|
|
||||||
return client.SendRequestAsync<EmptyResult>(
|
return client.SendRequestAsync(
|
||||||
CreateRequest(RequestMethods.ResourcesUnsubscribe, new Dictionary<string, object?>() { ["uri"] = uri }),
|
RequestMethods.ResourcesUnsubscribe,
|
||||||
cancellationToken);
|
new Dictionary<string, object?> { ["uri"] = uri },
|
||||||
|
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||||
|
McpJsonUtilities.JsonContext.Default.EmptyResult,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -409,17 +455,29 @@ public static class McpClientExtensions
|
||||||
/// <param name="client">The client.</param>
|
/// <param name="client">The client.</param>
|
||||||
/// <param name="toolName">The name of the tool to call.</param>
|
/// <param name="toolName">The name of the tool to call.</param>
|
||||||
/// <param name="arguments">Optional arguments for the tool.</param>
|
/// <param name="arguments">Optional arguments for the tool.</param>
|
||||||
|
/// <param name="serializerOptions">The serialization options governing argument serialization.</param>
|
||||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
/// <returns>A task containing the tool's response.</returns>
|
/// <returns>A task containing the tool's response.</returns>
|
||||||
public static Task<CallToolResponse> CallToolAsync(
|
public static Task<CallToolResponse> CallToolAsync(
|
||||||
this IMcpClient client, string toolName, IReadOnlyDictionary<string, object?>? arguments = null, CancellationToken cancellationToken = default)
|
this IMcpClient client,
|
||||||
|
string toolName,
|
||||||
|
IReadOnlyDictionary<string, object?>? arguments = null,
|
||||||
|
JsonSerializerOptions? serializerOptions = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
Throw.IfNull(client);
|
Throw.IfNull(client);
|
||||||
Throw.IfNull(toolName);
|
Throw.IfNull(toolName);
|
||||||
|
serializerOptions ??= McpJsonUtilities.DefaultOptions;
|
||||||
|
serializerOptions.MakeReadOnly();
|
||||||
|
|
||||||
return client.SendRequestAsync<CallToolResponse>(
|
var parametersTypeInfo = serializerOptions.GetTypeInfo<IReadOnlyDictionary<string, object?>>();
|
||||||
CreateRequest(RequestMethods.ToolsCall, CreateParametersDictionary(toolName, arguments)),
|
|
||||||
cancellationToken);
|
return client.SendRequestAsync(
|
||||||
|
RequestMethods.ToolsCall,
|
||||||
|
CreateParametersDictionary(toolName, arguments),
|
||||||
|
parametersTypeInfo,
|
||||||
|
McpJsonUtilities.JsonContext.Default.CallToolResponse,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -569,18 +627,14 @@ public static class McpClientExtensions
|
||||||
{
|
{
|
||||||
Throw.IfNull(client);
|
Throw.IfNull(client);
|
||||||
|
|
||||||
return client.SendRequestAsync<EmptyResult>(
|
return client.SendRequestAsync(
|
||||||
CreateRequest(RequestMethods.LoggingSetLevel, new Dictionary<string, object?>() { ["level"] = level }),
|
RequestMethods.LoggingSetLevel,
|
||||||
cancellationToken);
|
new Dictionary<string, object?> { ["level"] = level },
|
||||||
|
McpJsonUtilities.JsonContext.Default.DictionaryStringObject,
|
||||||
|
McpJsonUtilities.JsonContext.Default.EmptyResult,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JsonRpcRequest CreateRequest(string method, IReadOnlyDictionary<string, object?>? parameters) =>
|
|
||||||
new()
|
|
||||||
{
|
|
||||||
Method = method,
|
|
||||||
Params = parameters
|
|
||||||
};
|
|
||||||
|
|
||||||
private static Dictionary<string, object?>? CreateCursorDictionary(string? cursor) =>
|
private static Dictionary<string, object?>? CreateCursorDictionary(string? cursor) =>
|
||||||
cursor != null ? new() { ["cursor"] = cursor } : null;
|
cursor != null ? new() { ["cursor"] = cursor } : null;
|
||||||
|
|
||||||
|
|
@ -599,36 +653,4 @@ public static class McpClientExtensions
|
||||||
|
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Provides an AI function that calls a tool through <see cref="IMcpClient"/>.</summary>
|
|
||||||
private sealed class McpAIFunction(IMcpClient client, Tool tool) : AIFunction
|
|
||||||
{
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string Name => tool.Name;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override string Description => tool.Description ?? string.Empty;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override JsonElement JsonSchema => tool.InputSchema;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
public override JsonSerializerOptions JsonSerializerOptions => McpJsonUtilities.DefaultOptions;
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
|
||||||
protected async override Task<object?> InvokeCoreAsync(
|
|
||||||
IEnumerable<KeyValuePair<string, object?>> arguments, CancellationToken cancellationToken)
|
|
||||||
{
|
|
||||||
IReadOnlyDictionary<string, object?> argDict =
|
|
||||||
arguments as IReadOnlyDictionary<string, object?> ??
|
|
||||||
#if NET
|
|
||||||
arguments.ToDictionary();
|
|
||||||
#else
|
|
||||||
arguments.ToDictionary(kv => kv.Key, kv => kv.Value);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
CallToolResponse result = await client.CallToolAsync(tool.Name, argDict, cancellationToken).ConfigureAwait(false);
|
|
||||||
return JsonSerializer.SerializeToElement(result, McpJsonUtilities.JsonContext.Default.CallToolResponse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Client;
|
namespace ModelContextProtocol.Client;
|
||||||
|
|
||||||
|
|
@ -20,17 +21,19 @@ public sealed class McpClientPrompt
|
||||||
/// Retrieves a specific prompt with optional arguments.
|
/// Retrieves a specific prompt with optional arguments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="arguments">Optional arguments for the prompt</param>
|
/// <param name="arguments">Optional arguments for the prompt</param>
|
||||||
|
/// <param name="serializerOptions">The serialization options governing argument serialization.</param>
|
||||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
/// <returns>A task containing the prompt's content and messages.</returns>
|
/// <returns>A task containing the prompt's content and messages.</returns>
|
||||||
public async ValueTask<GetPromptResult> GetAsync(
|
public async ValueTask<GetPromptResult> GetAsync(
|
||||||
IEnumerable<KeyValuePair<string, object?>>? arguments = null,
|
IEnumerable<KeyValuePair<string, object?>>? arguments = null,
|
||||||
|
JsonSerializerOptions? serializerOptions = null,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
IReadOnlyDictionary<string, object?>? argDict =
|
IReadOnlyDictionary<string, object?>? argDict =
|
||||||
arguments as IReadOnlyDictionary<string, object?> ??
|
arguments as IReadOnlyDictionary<string, object?> ??
|
||||||
arguments?.ToDictionary();
|
arguments?.ToDictionary();
|
||||||
|
|
||||||
return await _client.GetPromptAsync(ProtocolPrompt.Name, argDict, cancellationToken).ConfigureAwait(false);
|
return await _client.GetPromptAsync(ProtocolPrompt.Name, argDict, serializerOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Gets the name of the prompt.</summary>
|
/// <summary>Gets the name of the prompt.</summary>
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,11 @@ public sealed class McpClientTool : AIFunction
|
||||||
{
|
{
|
||||||
private readonly IMcpClient _client;
|
private readonly IMcpClient _client;
|
||||||
|
|
||||||
internal McpClientTool(IMcpClient client, Tool tool)
|
internal McpClientTool(IMcpClient client, Tool tool, JsonSerializerOptions serializerOptions)
|
||||||
{
|
{
|
||||||
_client = client;
|
_client = client;
|
||||||
ProtocolTool = tool;
|
ProtocolTool = tool;
|
||||||
|
JsonSerializerOptions = serializerOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Gets the protocol <see cref="Tool"/> type for this instance.</summary>
|
/// <summary>Gets the protocol <see cref="Tool"/> type for this instance.</summary>
|
||||||
|
|
@ -29,7 +30,7 @@ public sealed class McpClientTool : AIFunction
|
||||||
public override JsonElement JsonSchema => ProtocolTool.InputSchema;
|
public override JsonElement JsonSchema => ProtocolTool.InputSchema;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override JsonSerializerOptions JsonSerializerOptions => McpJsonUtilities.DefaultOptions;
|
public override JsonSerializerOptions JsonSerializerOptions { get; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
protected async override Task<object?> InvokeCoreAsync(
|
protected async override Task<object?> InvokeCoreAsync(
|
||||||
|
|
@ -39,7 +40,7 @@ public sealed class McpClientTool : AIFunction
|
||||||
arguments as IReadOnlyDictionary<string, object?> ??
|
arguments as IReadOnlyDictionary<string, object?> ??
|
||||||
arguments.ToDictionary();
|
arguments.ToDictionary();
|
||||||
|
|
||||||
CallToolResponse result = await _client.CallToolAsync(ProtocolTool.Name, argDict, cancellationToken).ConfigureAwait(false);
|
CallToolResponse result = await _client.CallToolAsync(ProtocolTool.Name, argDict, JsonSerializerOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||||
return JsonSerializer.SerializeToElement(result, McpJsonUtilities.JsonContext.Default.CallToolResponse);
|
return JsonSerializer.SerializeToElement(result, McpJsonUtilities.JsonContext.Default.CallToolResponse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,12 +5,11 @@ namespace ModelContextProtocol;
|
||||||
/// <summary>Represents a client or server MCP endpoint.</summary>
|
/// <summary>Represents a client or server MCP endpoint.</summary>
|
||||||
public interface IMcpEndpoint : IAsyncDisposable
|
public interface IMcpEndpoint : IAsyncDisposable
|
||||||
{
|
{
|
||||||
/// <summary>Sends a generic JSON-RPC request to the connected endpoint.</summary>
|
/// <summary>Sends a JSON-RPC request to the connected endpoint.</summary>
|
||||||
/// <typeparam name="TResult">The expected response type.</typeparam>
|
|
||||||
/// <param name="request">The JSON-RPC request to send.</param>
|
/// <param name="request">The JSON-RPC request to send.</param>
|
||||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
/// <returns>A task containing the client's response.</returns>
|
/// <returns>A task containing the client's response.</returns>
|
||||||
Task<TResult> SendRequestAsync<TResult>(JsonRpcRequest request, CancellationToken cancellationToken = default) where TResult : class;
|
Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
/// <summary>Sends a message to the connected endpoint.</summary>
|
/// <summary>Sends a message to the connected endpoint.</summary>
|
||||||
/// <param name="message">The message.</param>
|
/// <param name="message">The message.</param>
|
||||||
|
|
|
||||||
|
|
@ -77,9 +77,6 @@ internal static partial class Log
|
||||||
[LoggerMessage(Level = LogLevel.Information, Message = "Request response received for {endpointName} with method {method}")]
|
[LoggerMessage(Level = LogLevel.Information, Message = "Request response received for {endpointName} with method {method}")]
|
||||||
internal static partial void RequestResponseReceived(this ILogger logger, string endpointName, string method);
|
internal static partial void RequestResponseReceived(this ILogger logger, string endpointName, string method);
|
||||||
|
|
||||||
[LoggerMessage(Level = LogLevel.Error, Message = "Request response type conversion error for {endpointName} with method {method}: expected {expectedType}")]
|
|
||||||
internal static partial void RequestResponseTypeConversionError(this ILogger logger, string endpointName, string method, Type expectedType);
|
|
||||||
|
|
||||||
[LoggerMessage(Level = LogLevel.Error, Message = "Request invalid response type for {endpointName} with method {method}")]
|
[LoggerMessage(Level = LogLevel.Error, Message = "Request invalid response type for {endpointName} with method {method}")]
|
||||||
internal static partial void RequestInvalidResponseType(this ILogger logger, string endpointName, string method);
|
internal static partial void RequestInvalidResponseType(this ILogger logger, string endpointName, string method);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,147 @@
|
||||||
using ModelContextProtocol.Protocol.Messages;
|
using ModelContextProtocol.Protocol.Messages;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
|
using ModelContextProtocol.Utils.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Text.Json.Serialization.Metadata;
|
||||||
|
|
||||||
namespace ModelContextProtocol;
|
namespace ModelContextProtocol;
|
||||||
|
|
||||||
/// <summary>Provides extension methods for interacting with an <see cref="IMcpEndpoint"/>.</summary>
|
/// <summary>Provides extension methods for interacting with an <see cref="IMcpEndpoint"/>.</summary>
|
||||||
public static class McpEndpointExtensions
|
public static class McpEndpointExtensions
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a JSON-RPC request and attempts to deserialize the result to <typeparamref name="TResult"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TParameters">The type of the request parameters to serialize from.</typeparam>
|
||||||
|
/// <typeparam name="TResult">The type of the result to deserialize to.</typeparam>
|
||||||
|
/// <param name="endpoint">The MCP client or server instance.</param>
|
||||||
|
/// <param name="method">The JSON-RPC method name to invoke.</param>
|
||||||
|
/// <param name="parameters">Object representing the request parameters.</param>
|
||||||
|
/// <param name="requestId">The request id for the request.</param>
|
||||||
|
/// <param name="serializerOptions">The options governing request serialization.</param>
|
||||||
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous operation. The task result contains the deserialized result.</returns>
|
||||||
|
public static Task<TResult> SendRequestAsync<TParameters, TResult>(
|
||||||
|
this IMcpEndpoint endpoint,
|
||||||
|
string method,
|
||||||
|
TParameters parameters,
|
||||||
|
JsonSerializerOptions? serializerOptions = null,
|
||||||
|
RequestId? requestId = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
where TResult : notnull
|
||||||
|
{
|
||||||
|
serializerOptions ??= McpJsonUtilities.DefaultOptions;
|
||||||
|
serializerOptions.MakeReadOnly();
|
||||||
|
|
||||||
|
JsonTypeInfo<TParameters> paramsTypeInfo = serializerOptions.GetTypeInfo<TParameters>();
|
||||||
|
JsonTypeInfo<TResult> resultTypeInfo = serializerOptions.GetTypeInfo<TResult>();
|
||||||
|
return SendRequestAsync(endpoint, method, parameters, paramsTypeInfo, resultTypeInfo, requestId, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a JSON-RPC request and attempts to deserialize the result to <typeparamref name="TResult"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TParameters">The type of the request parameters to serialize from.</typeparam>
|
||||||
|
/// <typeparam name="TResult">The type of the result to deserialize to.</typeparam>
|
||||||
|
/// <param name="endpoint">The MCP client or server instance.</param>
|
||||||
|
/// <param name="method">The JSON-RPC method name to invoke.</param>
|
||||||
|
/// <param name="parameters">Object representing the request parameters.</param>
|
||||||
|
/// <param name="parametersTypeInfo">The type information for request parameter serialization.</param>
|
||||||
|
/// <param name="resultTypeInfo">The type information for request parameter deserialization.</param>
|
||||||
|
/// <param name="requestId">The request id for the request.</param>
|
||||||
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
|
/// <returns>A task that represents the asynchronous operation. The task result contains the deserialized result.</returns>
|
||||||
|
internal static async Task<TResult> SendRequestAsync<TParameters, TResult>(
|
||||||
|
this IMcpEndpoint endpoint,
|
||||||
|
string method,
|
||||||
|
TParameters parameters,
|
||||||
|
JsonTypeInfo<TParameters> parametersTypeInfo,
|
||||||
|
JsonTypeInfo<TResult> resultTypeInfo,
|
||||||
|
RequestId? requestId = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
where TResult : notnull
|
||||||
|
{
|
||||||
|
Throw.IfNull(endpoint);
|
||||||
|
Throw.IfNullOrWhiteSpace(method);
|
||||||
|
Throw.IfNull(parametersTypeInfo);
|
||||||
|
Throw.IfNull(resultTypeInfo);
|
||||||
|
|
||||||
|
JsonRpcRequest jsonRpcRequest = new()
|
||||||
|
{
|
||||||
|
Method = method,
|
||||||
|
Params = JsonSerializer.SerializeToNode(parameters, parametersTypeInfo),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (requestId is { } id)
|
||||||
|
{
|
||||||
|
jsonRpcRequest.Id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonRpcResponse response = await endpoint.SendRequestAsync(jsonRpcRequest, cancellationToken).ConfigureAwait(false);
|
||||||
|
return JsonSerializer.Deserialize(response.Result, resultTypeInfo) ?? throw new JsonException("Unexpected JSON result in response.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a notification to the server with parameters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client">The client.</param>
|
||||||
|
/// <param name="method">The notification method name.</param>
|
||||||
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
|
public static Task SendNotificationAsync(this IMcpEndpoint client, string method, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
Throw.IfNull(client);
|
||||||
|
Throw.IfNullOrWhiteSpace(method);
|
||||||
|
return client.SendMessageAsync(new JsonRpcNotification { Method = method }, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a notification to the server with parameters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoint">The MCP client or server instance.</param>
|
||||||
|
/// <param name="method">The JSON-RPC method name to invoke.</param>
|
||||||
|
/// <param name="parameters">Object representing the request parameters.</param>
|
||||||
|
/// <param name="serializerOptions">The options governing request serialization.</param>
|
||||||
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
|
public static Task SendNotificationAsync<TParameters>(
|
||||||
|
this IMcpEndpoint endpoint,
|
||||||
|
string method,
|
||||||
|
TParameters parameters,
|
||||||
|
JsonSerializerOptions? serializerOptions = null,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
serializerOptions ??= McpJsonUtilities.DefaultOptions;
|
||||||
|
serializerOptions.MakeReadOnly();
|
||||||
|
|
||||||
|
JsonTypeInfo<TParameters> parametersTypeInfo = serializerOptions.GetTypeInfo<TParameters>();
|
||||||
|
return SendNotificationAsync(endpoint, method, parameters, parametersTypeInfo, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sends a notification to the server with parameters.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoint">The MCP client or server instance.</param>
|
||||||
|
/// <param name="method">The JSON-RPC method name to invoke.</param>
|
||||||
|
/// <param name="parameters">Object representing the request parameters.</param>
|
||||||
|
/// <param name="parametersTypeInfo">The type information for request parameter serialization.</param>
|
||||||
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
|
internal static Task SendNotificationAsync<TParameters>(
|
||||||
|
this IMcpEndpoint endpoint,
|
||||||
|
string method,
|
||||||
|
TParameters parameters,
|
||||||
|
JsonTypeInfo<TParameters> parametersTypeInfo,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
Throw.IfNull(endpoint);
|
||||||
|
Throw.IfNullOrWhiteSpace(method);
|
||||||
|
Throw.IfNull(parametersTypeInfo);
|
||||||
|
|
||||||
|
JsonNode? parametersJson = JsonSerializer.SerializeToNode(parameters, parametersTypeInfo);
|
||||||
|
return endpoint.SendMessageAsync(new JsonRpcNotification { Method = method, Params = parametersJson }, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Notifies the connected endpoint of progress.</summary>
|
/// <summary>Notifies the connected endpoint of progress.</summary>
|
||||||
/// <param name="endpoint">The endpoint issueing the notification.</param>
|
/// <param name="endpoint">The endpoint issuing the notification.</param>
|
||||||
/// <param name="progressToken">The <see cref="ProgressToken"/> identifying the operation.</param>
|
/// <param name="progressToken">The <see cref="ProgressToken"/> identifying the operation.</param>
|
||||||
/// <param name="progress">The progress update to send.</param>
|
/// <param name="progress">The progress update to send.</param>
|
||||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
|
|
@ -24,11 +158,11 @@ public static class McpEndpointExtensions
|
||||||
return endpoint.SendMessageAsync(new JsonRpcNotification()
|
return endpoint.SendMessageAsync(new JsonRpcNotification()
|
||||||
{
|
{
|
||||||
Method = NotificationMethods.ProgressNotification,
|
Method = NotificationMethods.ProgressNotification,
|
||||||
Params = new ProgressNotification()
|
Params = JsonSerializer.SerializeToNode(new ProgressNotification
|
||||||
{
|
{
|
||||||
ProgressToken = progressToken,
|
ProgressToken = progressToken,
|
||||||
Progress = progress,
|
Progress = progress,
|
||||||
},
|
}, McpJsonUtilities.JsonContext.Default.ProgressNotification),
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Protocol.Messages;
|
namespace ModelContextProtocol.Protocol.Messages;
|
||||||
|
|
||||||
|
|
@ -23,5 +24,5 @@ public record JsonRpcNotification : IJsonRpcMessage
|
||||||
/// Optional parameters for the notification.
|
/// Optional parameters for the notification.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("params")]
|
[JsonPropertyName("params")]
|
||||||
public object? Params { get; init; }
|
public JsonNode? Params { get; init; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Nodes;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Protocol.Messages;
|
namespace ModelContextProtocol.Protocol.Messages;
|
||||||
|
|
||||||
|
|
@ -29,5 +30,5 @@ public record JsonRpcRequest : IJsonRpcMessageWithId
|
||||||
/// Optional parameters for the method.
|
/// Optional parameters for the method.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("params")]
|
[JsonPropertyName("params")]
|
||||||
public object? Params { get; init; }
|
public JsonNode? Params { get; init; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Protocol.Messages;
|
namespace ModelContextProtocol.Protocol.Messages;
|
||||||
|
|
@ -23,5 +23,5 @@ public record JsonRpcResponse : IJsonRpcMessageWithId
|
||||||
/// The result of the method invocation.
|
/// The result of the method invocation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("result")]
|
[JsonPropertyName("result")]
|
||||||
public required object? Result { get; init; }
|
public required JsonNode? Result { get; init; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ internal sealed class SseClientSessionTransport : TransportBase
|
||||||
private Task? _receiveTask;
|
private Task? _receiveTask;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private readonly McpServerConfig _serverConfig;
|
private readonly McpServerConfig _serverConfig;
|
||||||
private readonly JsonSerializerOptions _jsonOptions;
|
|
||||||
private readonly TaskCompletionSource<bool> _connectionEstablished;
|
private readonly TaskCompletionSource<bool> _connectionEstablished;
|
||||||
|
|
||||||
private string EndpointName => $"Client (SSE) for ({_serverConfig.Id}: {_serverConfig.Name})";
|
private string EndpointName => $"Client (SSE) for ({_serverConfig.Id}: {_serverConfig.Name})";
|
||||||
|
|
@ -50,7 +49,6 @@ internal sealed class SseClientSessionTransport : TransportBase
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
_connectionCts = new CancellationTokenSource();
|
_connectionCts = new CancellationTokenSource();
|
||||||
_logger = (ILogger?)loggerFactory?.CreateLogger<SseClientTransport>() ?? NullLogger.Instance;
|
_logger = (ILogger?)loggerFactory?.CreateLogger<SseClientTransport>() ?? NullLogger.Instance;
|
||||||
_jsonOptions = McpJsonUtilities.DefaultOptions;
|
|
||||||
_connectionEstablished = new TaskCompletionSource<bool>();
|
_connectionEstablished = new TaskCompletionSource<bool>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -94,7 +92,7 @@ internal sealed class SseClientSessionTransport : TransportBase
|
||||||
throw new InvalidOperationException("Transport not connected");
|
throw new InvalidOperationException("Transport not connected");
|
||||||
|
|
||||||
using var content = new StringContent(
|
using var content = new StringContent(
|
||||||
JsonSerializer.Serialize(message, _jsonOptions.GetTypeInfo<IJsonRpcMessage>()),
|
JsonSerializer.Serialize(message, McpJsonUtilities.JsonContext.Default.IJsonRpcMessage),
|
||||||
Encoding.UTF8,
|
Encoding.UTF8,
|
||||||
"application/json"
|
"application/json"
|
||||||
);
|
);
|
||||||
|
|
@ -127,7 +125,7 @@ internal sealed class SseClientSessionTransport : TransportBase
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
JsonRpcResponse initializeResponse = JsonSerializer.Deserialize(responseContent, _jsonOptions.GetTypeInfo<JsonRpcResponse>()) ??
|
JsonRpcResponse initializeResponse = JsonSerializer.Deserialize(responseContent, McpJsonUtilities.JsonContext.Default.JsonRpcResponse) ??
|
||||||
throw new McpTransportException("Failed to initialize client");
|
throw new McpTransportException("Failed to initialize client");
|
||||||
|
|
||||||
_logger.TransportReceivedMessageParsed(EndpointName, messageId);
|
_logger.TransportReceivedMessageParsed(EndpointName, messageId);
|
||||||
|
|
@ -259,7 +257,7 @@ internal sealed class SseClientSessionTransport : TransportBase
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var message = JsonSerializer.Deserialize(data, _jsonOptions.GetTypeInfo<IJsonRpcMessage>());
|
var message = JsonSerializer.Deserialize(data, McpJsonUtilities.JsonContext.Default.IJsonRpcMessage);
|
||||||
if (message == null)
|
if (message == null)
|
||||||
{
|
{
|
||||||
_logger.TransportMessageParseUnexpectedType(EndpointName, data);
|
_logger.TransportMessageParseUnexpectedType(EndpointName, data);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
namespace ModelContextProtocol.Protocol.Types;
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace ModelContextProtocol.Protocol.Types;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used by the client to invoke a tool provided by the server.
|
/// Used by the client to invoke a tool provided by the server.
|
||||||
|
|
@ -16,5 +18,5 @@ public class CallToolRequestParams : RequestParams
|
||||||
/// Optional arguments to pass to the tool.
|
/// Optional arguments to pass to the tool.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[System.Text.Json.Serialization.JsonPropertyName("arguments")]
|
[System.Text.Json.Serialization.JsonPropertyName("arguments")]
|
||||||
public Dictionary<string,object>? Arguments { get; init; }
|
public Dictionary<string, JsonElement>? Arguments { get; init; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ public class LoggingMessageNotificationParams
|
||||||
public string? Logger { get; init; }
|
public string? Logger { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here.
|
/// The data to be logged, such as a string message or an object.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonPropertyName("data")]
|
[JsonPropertyName("data")]
|
||||||
public JsonElement? Data { get; init; }
|
public JsonElement? Data { get; init; }
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,8 @@ public abstract class ResourceContents
|
||||||
}
|
}
|
||||||
|
|
||||||
string? propertyName = reader.GetString();
|
string? propertyName = reader.GetString();
|
||||||
|
bool success = reader.Read();
|
||||||
|
Debug.Assert(success, "STJ must have buffered the entire object for us.");
|
||||||
|
|
||||||
switch (propertyName)
|
switch (propertyName)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -255,8 +255,8 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
|
||||||
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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ using ModelContextProtocol.Protocol.Transport;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Shared;
|
using ModelContextProtocol.Shared;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
using System.Text.Json.Nodes;
|
using ModelContextProtocol.Utils.Json;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Server;
|
namespace ModelContextProtocol.Server;
|
||||||
|
|
||||||
|
|
@ -131,13 +131,15 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
||||||
|
|
||||||
private void SetPingHandler()
|
private void SetPingHandler()
|
||||||
{
|
{
|
||||||
SetRequestHandler<JsonNode, PingResult>(RequestMethods.Ping,
|
SetRequestHandler(RequestMethods.Ping,
|
||||||
(request, _) => Task.FromResult(new PingResult()));
|
(request, _) => Task.FromResult(new PingResult()),
|
||||||
|
McpJsonUtilities.JsonContext.Default.JsonNode,
|
||||||
|
McpJsonUtilities.JsonContext.Default.PingResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetInitializeHandler(McpServerOptions options)
|
private void SetInitializeHandler(McpServerOptions options)
|
||||||
{
|
{
|
||||||
SetRequestHandler<InitializeRequestParams, InitializeResult>(RequestMethods.Initialize,
|
SetRequestHandler(RequestMethods.Initialize,
|
||||||
(request, _) =>
|
(request, _) =>
|
||||||
{
|
{
|
||||||
ClientCapabilities = request?.Capabilities ?? new();
|
ClientCapabilities = request?.Capabilities ?? new();
|
||||||
|
|
@ -147,23 +149,27 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
||||||
_endpointName = $"{_endpointName}, Client ({ClientInfo?.Name} {ClientInfo?.Version})";
|
_endpointName = $"{_endpointName}, Client ({ClientInfo?.Name} {ClientInfo?.Version})";
|
||||||
GetSessionOrThrow().EndpointName = _endpointName;
|
GetSessionOrThrow().EndpointName = _endpointName;
|
||||||
|
|
||||||
return Task.FromResult(new InitializeResult()
|
return Task.FromResult(new InitializeResult
|
||||||
{
|
{
|
||||||
ProtocolVersion = options.ProtocolVersion,
|
ProtocolVersion = options.ProtocolVersion,
|
||||||
Instructions = options.ServerInstructions,
|
Instructions = options.ServerInstructions,
|
||||||
ServerInfo = options.ServerInfo,
|
ServerInfo = options.ServerInfo,
|
||||||
Capabilities = ServerCapabilities ?? new(),
|
Capabilities = ServerCapabilities ?? new(),
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
McpJsonUtilities.JsonContext.Default.InitializeRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.InitializeResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetCompletionHandler(McpServerOptions options)
|
private void SetCompletionHandler(McpServerOptions options)
|
||||||
{
|
{
|
||||||
// This capability is not optional, so return an empty result if there is no handler.
|
// This capability is not optional, so return an empty result if there is no handler.
|
||||||
SetRequestHandler<CompleteRequestParams, CompleteResult>(RequestMethods.CompletionComplete,
|
SetRequestHandler(RequestMethods.CompletionComplete,
|
||||||
options.GetCompletionHandler is { } handler ?
|
options.GetCompletionHandler is { } handler ?
|
||||||
(request, ct) => handler(new(this, request), ct) :
|
(request, ct) => handler(new(this, request), ct) :
|
||||||
(request, ct) => Task.FromResult(new CompleteResult() { Completion = new() { Values = [], Total = 0, HasMore = false } }));
|
(request, ct) => Task.FromResult(new CompleteResult() { Completion = new() { Values = [], Total = 0, HasMore = false } }),
|
||||||
|
McpJsonUtilities.JsonContext.Default.CompleteRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.CompleteResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetResourcesHandler(McpServerOptions options)
|
private void SetResourcesHandler(McpServerOptions options)
|
||||||
|
|
@ -184,11 +190,24 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
||||||
|
|
||||||
listResourcesHandler ??= (static (_, _) => Task.FromResult(new ListResourcesResult()));
|
listResourcesHandler ??= (static (_, _) => Task.FromResult(new ListResourcesResult()));
|
||||||
|
|
||||||
SetRequestHandler<ListResourcesRequestParams, ListResourcesResult>(RequestMethods.ResourcesList, (request, ct) => listResourcesHandler(new(this, request), ct));
|
SetRequestHandler(
|
||||||
SetRequestHandler<ReadResourceRequestParams, ReadResourceResult>(RequestMethods.ResourcesRead, (request, ct) => readResourceHandler(new(this, request), ct));
|
RequestMethods.ResourcesList,
|
||||||
|
(request, ct) => listResourcesHandler(new(this, request), ct),
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListResourcesRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListResourcesResult);
|
||||||
|
|
||||||
|
SetRequestHandler(
|
||||||
|
RequestMethods.ResourcesRead,
|
||||||
|
(request, ct) => readResourceHandler(new(this, request), ct),
|
||||||
|
McpJsonUtilities.JsonContext.Default.ReadResourceRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ReadResourceResult);
|
||||||
|
|
||||||
listResourceTemplatesHandler ??= (static (_, _) => Task.FromResult(new ListResourceTemplatesResult()));
|
listResourceTemplatesHandler ??= (static (_, _) => Task.FromResult(new ListResourceTemplatesResult()));
|
||||||
SetRequestHandler<ListResourceTemplatesRequestParams, ListResourceTemplatesResult>(RequestMethods.ResourcesTemplatesList, (request, ct) => listResourceTemplatesHandler(new(this, request), ct));
|
SetRequestHandler(
|
||||||
|
RequestMethods.ResourcesTemplatesList,
|
||||||
|
(request, ct) => listResourceTemplatesHandler(new(this, request), ct),
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListResourceTemplatesResult);
|
||||||
|
|
||||||
if (resourcesCapability.Subscribe is not true)
|
if (resourcesCapability.Subscribe is not true)
|
||||||
{
|
{
|
||||||
|
|
@ -202,8 +221,17 @@ 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.");
|
throw new McpServerException("Resources capability was enabled with subscribe support, but SubscribeToResources and/or UnsubscribeFromResources handlers were not specified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
SetRequestHandler<SubscribeRequestParams, EmptyResult>(RequestMethods.ResourcesSubscribe, (request, ct) => subscribeHandler(new(this, request), ct));
|
SetRequestHandler(
|
||||||
SetRequestHandler<UnsubscribeRequestParams, EmptyResult>(RequestMethods.ResourcesUnsubscribe, (request, ct) => unsubscribeHandler(new(this, request), ct));
|
RequestMethods.ResourcesSubscribe,
|
||||||
|
(request, ct) => subscribeHandler(new(this, request), ct),
|
||||||
|
McpJsonUtilities.JsonContext.Default.SubscribeRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.EmptyResult);
|
||||||
|
|
||||||
|
SetRequestHandler(
|
||||||
|
RequestMethods.ResourcesUnsubscribe,
|
||||||
|
(request, ct) => unsubscribeHandler(new(this, request), ct),
|
||||||
|
McpJsonUtilities.JsonContext.Default.UnsubscribeRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.EmptyResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetPromptsHandler(McpServerOptions options)
|
private void SetPromptsHandler(McpServerOptions options)
|
||||||
|
|
@ -286,8 +314,17 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetRequestHandler<ListPromptsRequestParams, ListPromptsResult>(RequestMethods.PromptsList, (request, ct) => listPromptsHandler(new(this, request), ct));
|
SetRequestHandler(
|
||||||
SetRequestHandler<GetPromptRequestParams, GetPromptResult>(RequestMethods.PromptsGet, (request, ct) => getPromptHandler(new(this, request), ct));
|
RequestMethods.PromptsList,
|
||||||
|
(request, ct) => listPromptsHandler(new(this, request), ct),
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListPromptsRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListPromptsResult);
|
||||||
|
|
||||||
|
SetRequestHandler(
|
||||||
|
RequestMethods.PromptsGet,
|
||||||
|
(request, ct) => getPromptHandler(new(this, request), ct),
|
||||||
|
McpJsonUtilities.JsonContext.Default.GetPromptRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.GetPromptResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetToolsHandler(McpServerOptions options)
|
private void SetToolsHandler(McpServerOptions options)
|
||||||
|
|
@ -370,8 +407,17 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SetRequestHandler<ListToolsRequestParams, ListToolsResult>(RequestMethods.ToolsList, (request, ct) => listToolsHandler(new(this, request), ct));
|
SetRequestHandler(
|
||||||
SetRequestHandler<CallToolRequestParams, CallToolResponse>(RequestMethods.ToolsCall, (request, ct) => callToolHandler(new(this, request), ct));
|
RequestMethods.ToolsList,
|
||||||
|
(request, ct) => listToolsHandler(new(this, request), ct),
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListToolsRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListToolsResult);
|
||||||
|
|
||||||
|
SetRequestHandler(
|
||||||
|
RequestMethods.ToolsCall,
|
||||||
|
(request, ct) => callToolHandler(new(this, request), ct),
|
||||||
|
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.CallToolResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetSetLoggingLevelHandler(McpServerOptions options)
|
private void SetSetLoggingLevelHandler(McpServerOptions options)
|
||||||
|
|
@ -386,6 +432,10 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
||||||
throw new McpServerException("Logging capability was enabled, but SetLoggingLevelHandler was not specified.");
|
throw new McpServerException("Logging capability was enabled, but SetLoggingLevelHandler was not specified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
SetRequestHandler<SetLevelRequestParams, EmptyResult>(RequestMethods.LoggingSetLevel, (request, ct) => setLoggingLevelHandler(new(this, request), ct));
|
SetRequestHandler(
|
||||||
|
RequestMethods.LoggingSetLevel,
|
||||||
|
(request, ct) => setLoggingLevelHandler(new(this, request), ct),
|
||||||
|
McpJsonUtilities.JsonContext.Default.SetLevelRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.EmptyResult);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
using ModelContextProtocol.Protocol.Messages;
|
using Microsoft.Extensions.AI;
|
||||||
|
using ModelContextProtocol.Client;
|
||||||
|
using ModelContextProtocol.Protocol.Messages;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
using Microsoft.Extensions.AI;
|
using ModelContextProtocol.Utils.Json;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
|
@ -25,9 +27,12 @@ public static class McpServerExtensions
|
||||||
throw new ArgumentException("Client connected to the server does not support sampling.", nameof(server));
|
throw new ArgumentException("Client connected to the server does not support sampling.", nameof(server));
|
||||||
}
|
}
|
||||||
|
|
||||||
return server.SendRequestAsync<CreateMessageResult>(
|
return server.SendRequestAsync(
|
||||||
new JsonRpcRequest { Method = RequestMethods.SamplingCreateMessage, Params = request },
|
RequestMethods.SamplingCreateMessage,
|
||||||
cancellationToken);
|
request,
|
||||||
|
McpJsonUtilities.JsonContext.Default.CreateMessageRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.CreateMessageResult,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -164,9 +169,12 @@ public static class McpServerExtensions
|
||||||
throw new ArgumentException("Client connected to the server does not support roots.", nameof(server));
|
throw new ArgumentException("Client connected to the server does not support roots.", nameof(server));
|
||||||
}
|
}
|
||||||
|
|
||||||
return server.SendRequestAsync<ListRootsResult>(
|
return server.SendRequestAsync(
|
||||||
new JsonRpcRequest { Method = RequestMethods.RootsList, Params = request },
|
RequestMethods.RootsList,
|
||||||
cancellationToken);
|
request,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListRootsRequestParams,
|
||||||
|
McpJsonUtilities.JsonContext.Default.ListRootsResult,
|
||||||
|
cancellationToken: cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Provides an <see cref="IChatClient"/> implementation that's implemented via client sampling.</summary>
|
/// <summary>Provides an <see cref="IChatClient"/> implementation that's implemented via client sampling.</summary>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
|
||||||
using ModelContextProtocol.Protocol.Types;
|
using ModelContextProtocol.Protocol.Types;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
|
using ModelContextProtocol.Utils.Json;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
@ -476,7 +477,7 @@ internal static partial class TemporaryAIFunctionFactory
|
||||||
description: parameter.GetCustomAttribute<DescriptionAttribute>(inherit: true)?.Description,
|
description: parameter.GetCustomAttribute<DescriptionAttribute>(inherit: true)?.Description,
|
||||||
hasDefaultValue: parameter.HasDefaultValue,
|
hasDefaultValue: parameter.HasDefaultValue,
|
||||||
defaultValue: parameter.HasDefaultValue ? parameter.DefaultValue : null,
|
defaultValue: parameter.HasDefaultValue ? parameter.DefaultValue : null,
|
||||||
serializerOptions), AIJsonUtilities.DefaultOptions.GetTypeInfo<JsonElement>());
|
serializerOptions), McpJsonUtilities.JsonContext.Default.JsonElement);
|
||||||
|
|
||||||
parameterSchemas.Add(parameter.Name, parameterSchema);
|
parameterSchemas.Add(parameter.Name, parameterSchema);
|
||||||
if (!parameter.IsOptional)
|
if (!parameter.IsOptional)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using ModelContextProtocol.Protocol.Transport;
|
||||||
using ModelContextProtocol.Server;
|
using ModelContextProtocol.Server;
|
||||||
using ModelContextProtocol.Utils;
|
using ModelContextProtocol.Utils;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.Json.Serialization.Metadata;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Shared;
|
namespace ModelContextProtocol.Shared;
|
||||||
|
|
||||||
|
|
@ -38,14 +39,19 @@ internal abstract class McpJsonRpcEndpoint : IAsyncDisposable
|
||||||
_logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance;
|
_logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void SetRequestHandler<TRequest, TResponse>(string method, Func<TRequest?, CancellationToken, Task<TResponse>> handler)
|
protected void SetRequestHandler<TRequest, TResponse>(
|
||||||
=> _requestHandlers.Set(method, handler);
|
string method,
|
||||||
|
Func<TRequest?, CancellationToken, Task<TResponse>> handler,
|
||||||
|
JsonTypeInfo<TRequest> requestTypeInfo,
|
||||||
|
JsonTypeInfo<TResponse> responseTypeInfo)
|
||||||
|
|
||||||
|
=> _requestHandlers.Set(method, handler, requestTypeInfo, responseTypeInfo);
|
||||||
|
|
||||||
public void AddNotificationHandler(string method, Func<JsonRpcNotification, Task> handler)
|
public void AddNotificationHandler(string method, Func<JsonRpcNotification, Task> handler)
|
||||||
=> _notificationHandlers.Add(method, handler);
|
=> _notificationHandlers.Add(method, handler);
|
||||||
|
|
||||||
public Task<TResult> SendRequestAsync<TResult>(JsonRpcRequest request, CancellationToken cancellationToken = default) where TResult : class
|
public Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken = default)
|
||||||
=> GetSessionOrThrow().SendRequestAsync<TResult>(request, cancellationToken);
|
=> GetSessionOrThrow().SendRequestAsync(request, cancellationToken);
|
||||||
|
|
||||||
public Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
|
public Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||||
=> GetSessionOrThrow().SendMessageAsync(message, cancellationToken);
|
=> GetSessionOrThrow().SendMessageAsync(message, cancellationToken);
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ using System.Collections.Concurrent;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Diagnostics.Metrics;
|
using System.Diagnostics.Metrics;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
|
||||||
namespace ModelContextProtocol.Shared;
|
namespace ModelContextProtocol.Shared;
|
||||||
|
|
||||||
|
|
@ -43,7 +44,6 @@ internal sealed class McpSession : IDisposable
|
||||||
/// that can be used to request cancellation of the in-flight handler.
|
/// that can be used to request cancellation of the in-flight handler.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly ConcurrentDictionary<RequestId, CancellationTokenSource> _handlingRequests = new();
|
private readonly ConcurrentDictionary<RequestId, CancellationTokenSource> _handlingRequests = new();
|
||||||
private readonly JsonSerializerOptions _jsonOptions;
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
private readonly string _id = Guid.NewGuid().ToString("N");
|
private readonly string _id = Guid.NewGuid().ToString("N");
|
||||||
|
|
@ -81,7 +81,6 @@ internal sealed class McpSession : IDisposable
|
||||||
EndpointName = endpointName;
|
EndpointName = endpointName;
|
||||||
_requestHandlers = requestHandlers;
|
_requestHandlers = requestHandlers;
|
||||||
_notificationHandlers = notificationHandlers;
|
_notificationHandlers = notificationHandlers;
|
||||||
_jsonOptions = McpJsonUtilities.DefaultOptions;
|
|
||||||
_logger = logger ?? NullLogger.Instance;
|
_logger = logger ?? NullLogger.Instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -155,7 +154,7 @@ internal sealed class McpSession : IDisposable
|
||||||
}
|
}
|
||||||
else if (ex is not OperationCanceledException)
|
else if (ex is not OperationCanceledException)
|
||||||
{
|
{
|
||||||
var payload = JsonSerializer.Serialize(message, _jsonOptions.GetTypeInfo<IJsonRpcMessage>());
|
var payload = JsonSerializer.Serialize(message, McpJsonUtilities.JsonContext.Default.IJsonRpcMessage);
|
||||||
_logger.MessageHandlerError(EndpointName, message.GetType().Name, payload, ex);
|
_logger.MessageHandlerError(EndpointName, message.GetType().Name, payload, ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -295,7 +294,7 @@ internal sealed class McpSession : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.RequestHandlerCalled(EndpointName, request.Method);
|
_logger.RequestHandlerCalled(EndpointName, request.Method);
|
||||||
var result = await handler(request, cancellationToken).ConfigureAwait(false);
|
JsonNode? result = await handler(request, cancellationToken).ConfigureAwait(false);
|
||||||
_logger.RequestHandlerCompleted(EndpointName, request.Method);
|
_logger.RequestHandlerCompleted(EndpointName, request.Method);
|
||||||
await _transport.SendMessageAsync(new JsonRpcResponse
|
await _transport.SendMessageAsync(new JsonRpcResponse
|
||||||
{
|
{
|
||||||
|
|
@ -306,15 +305,14 @@ internal sealed class McpSession : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sends a generic JSON-RPC request to the server.
|
/// Sends a JSON-RPC request to the server.
|
||||||
/// It is strongly recommended use the capability-specific methods instead of this one.
|
/// It is strongly recommended use the capability-specific methods instead of this one.
|
||||||
/// Use this method for custom requests or those not yet covered explicitly by the endpoint implementation.
|
/// Use this method for custom requests or those not yet covered explicitly by the endpoint implementation.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TResult">The expected response type.</typeparam>
|
|
||||||
/// <param name="request">The JSON-RPC request to send.</param>
|
/// <param name="request">The JSON-RPC request to send.</param>
|
||||||
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
/// <param name="cancellationToken">A token to cancel the operation.</param>
|
||||||
/// <returns>A task containing the server's response.</returns>
|
/// <returns>A task containing the server's response.</returns>
|
||||||
public async Task<TResult> SendRequestAsync<TResult>(JsonRpcRequest request, CancellationToken cancellationToken) where TResult : class
|
public async Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (!_transport.IsConnected)
|
if (!_transport.IsConnected)
|
||||||
{
|
{
|
||||||
|
|
@ -352,7 +350,7 @@ internal sealed class McpSession : IDisposable
|
||||||
// Expensive logging, use the logging framework to check if the logger is enabled
|
// Expensive logging, use the logging framework to check if the logger is enabled
|
||||||
if (_logger.IsEnabled(LogLevel.Debug))
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
{
|
{
|
||||||
_logger.SendingRequestPayload(EndpointName, JsonSerializer.Serialize(request, _jsonOptions.GetTypeInfo<JsonRpcRequest>()));
|
_logger.SendingRequestPayload(EndpointName, JsonSerializer.Serialize(request, McpJsonUtilities.JsonContext.Default.JsonRpcRequest));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Less expensive information logging
|
// Less expensive information logging
|
||||||
|
|
@ -371,22 +369,9 @@ internal sealed class McpSession : IDisposable
|
||||||
|
|
||||||
if (response is JsonRpcResponse success)
|
if (response is JsonRpcResponse success)
|
||||||
{
|
{
|
||||||
// Convert the Result object to JSON and back to get our strongly-typed result
|
_logger.RequestResponseReceivedPayload(EndpointName, success.Result?.ToJsonString() ?? "null");
|
||||||
var resultJson = JsonSerializer.Serialize(success.Result, _jsonOptions.GetTypeInfo<object?>());
|
|
||||||
var resultObject = JsonSerializer.Deserialize(resultJson, _jsonOptions.GetTypeInfo<TResult>());
|
|
||||||
|
|
||||||
// Not expensive logging because we're already converting to JSON in order to get the result object
|
|
||||||
_logger.RequestResponseReceivedPayload(EndpointName, resultJson);
|
|
||||||
_logger.RequestResponseReceived(EndpointName, request.Method);
|
_logger.RequestResponseReceived(EndpointName, request.Method);
|
||||||
|
return success;
|
||||||
if (resultObject != null)
|
|
||||||
{
|
|
||||||
return resultObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Result object was null, this is unexpected
|
|
||||||
_logger.RequestResponseTypeConversionError(EndpointName, request.Method, typeof(TResult));
|
|
||||||
throw new McpClientException($"Unexpected response type {JsonSerializer.Serialize(success.Result, _jsonOptions.GetTypeInfo<TResult>())}, expected {typeof(TResult)}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unexpected response type
|
// Unexpected response type
|
||||||
|
|
@ -435,7 +420,7 @@ internal sealed class McpSession : IDisposable
|
||||||
|
|
||||||
if (_logger.IsEnabled(LogLevel.Debug))
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
{
|
{
|
||||||
_logger.SendingMessage(EndpointName, JsonSerializer.Serialize(message, _jsonOptions.GetTypeInfo<IJsonRpcMessage>()));
|
_logger.SendingMessage(EndpointName, JsonSerializer.Serialize(message, McpJsonUtilities.JsonContext.Default.IJsonRpcMessage));
|
||||||
}
|
}
|
||||||
|
|
||||||
await _transport.SendMessageAsync(message, cancellationToken).ConfigureAwait(false);
|
await _transport.SendMessageAsync(message, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
@ -461,26 +446,11 @@ internal sealed class McpSession : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CancelledNotification? GetCancelledNotificationParams(object? notificationParams)
|
private static CancelledNotification? GetCancelledNotificationParams(JsonNode? notificationParams)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
switch (notificationParams)
|
return JsonSerializer.Deserialize(notificationParams, McpJsonUtilities.JsonContext.Default.CancelledNotification);
|
||||||
{
|
|
||||||
case null:
|
|
||||||
return null;
|
|
||||||
|
|
||||||
case CancelledNotification cn:
|
|
||||||
return cn;
|
|
||||||
|
|
||||||
case JsonElement je:
|
|
||||||
return JsonSerializer.Deserialize(je, McpJsonUtilities.DefaultOptions.GetTypeInfo<CancelledNotification>());
|
|
||||||
|
|
||||||
default:
|
|
||||||
return JsonSerializer.Deserialize(
|
|
||||||
JsonSerializer.Serialize(notificationParams, McpJsonUtilities.DefaultOptions.GetTypeInfo<object?>()),
|
|
||||||
McpJsonUtilities.DefaultOptions.GetTypeInfo<CancelledNotification>());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
@ -515,15 +485,15 @@ internal sealed class McpSession : IDisposable
|
||||||
{
|
{
|
||||||
tags.Add("rpc.jsonrpc.request_id", request.Id.ToString());
|
tags.Add("rpc.jsonrpc.request_id", request.Id.ToString());
|
||||||
|
|
||||||
if (request.Params is JsonElement je)
|
if (request.Params is JsonObject paramsObj)
|
||||||
{
|
{
|
||||||
switch (request.Method)
|
switch (request.Method)
|
||||||
{
|
{
|
||||||
case RequestMethods.ToolsCall:
|
case RequestMethods.ToolsCall:
|
||||||
case RequestMethods.PromptsGet:
|
case RequestMethods.PromptsGet:
|
||||||
if (je.TryGetProperty("name", out var prop) && prop.ValueKind == JsonValueKind.String)
|
if (paramsObj.TryGetPropertyValue("name", out var prop) && prop?.GetValueKind() is JsonValueKind.String)
|
||||||
{
|
{
|
||||||
string name = prop.GetString()!;
|
string name = prop.GetValue<string>();
|
||||||
tags.Add("mcp.request.params.name", name);
|
tags.Add("mcp.request.params.name", name);
|
||||||
if (activity is not null)
|
if (activity is not null)
|
||||||
{
|
{
|
||||||
|
|
@ -533,9 +503,9 @@ internal sealed class McpSession : IDisposable
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RequestMethods.ResourcesRead:
|
case RequestMethods.ResourcesRead:
|
||||||
if (je.TryGetProperty("uri", out prop) && prop.ValueKind == JsonValueKind.String)
|
if (paramsObj.TryGetPropertyValue("uri", out prop) && prop?.GetValueKind() is JsonValueKind.String)
|
||||||
{
|
{
|
||||||
string uri = prop.GetString()!;
|
string uri = prop.GetValue<string>();
|
||||||
tags.Add("mcp.request.params.uri", uri);
|
tags.Add("mcp.request.params.uri", uri);
|
||||||
if (activity is not null)
|
if (activity is not null)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue