Update content according to latest spec (#513)
* Update content according to latest spec * Add Title to a bunch of types * Add _meta to lots of types * Address feedback, more cleanup, fix NAOT test failure
This commit is contained in:
parent
28d65470f4
commit
2bcff315f8
10 changed files with 1700 additions and 853 deletions
|
|
@ -1,6 +1,5 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace ModelContextProtocol.Client;
|
||||
|
|
@ -58,7 +57,7 @@ internal sealed partial class McpClient : McpEndpoint, IMcpClient
|
|||
RequestMethods.SamplingCreateMessage,
|
||||
(request, _, cancellationToken) => samplingHandler(
|
||||
request,
|
||||
request?.Meta?.ProgressToken is { } token ? new TokenProgress(this, token) : NullProgress.Instance,
|
||||
request?.ProgressToken is { } token ? new TokenProgress(this, token) : NullProgress.Instance,
|
||||
cancellationToken),
|
||||
McpJsonUtilities.JsonContext.Default.CreateMessageRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.CreateMessageResult);
|
||||
|
|
@ -180,9 +179,12 @@ internal sealed partial class McpClient : McpEndpoint, IMcpClient
|
|||
}
|
||||
|
||||
// Send initialized notification
|
||||
await SendMessageAsync(
|
||||
new JsonRpcNotification { Method = NotificationMethods.InitializedNotification },
|
||||
initializationCts.Token).ConfigureAwait(false);
|
||||
await this.SendNotificationAsync(
|
||||
NotificationMethods.InitializedNotification,
|
||||
new InitializedNotificationParams(),
|
||||
McpJsonUtilities.JsonContext.Default.InitializedNotificationParams,
|
||||
cancellationToken: initializationCts.Token).ConfigureAwait(false);
|
||||
|
||||
}
|
||||
catch (OperationCanceledException oce) when (initializationCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -639,11 +639,6 @@ public static class McpClientExtensions
|
|||
Throw.IfNull(reference);
|
||||
Throw.IfNullOrWhiteSpace(argumentName);
|
||||
|
||||
if (!reference.Validate(out string? validationMessage))
|
||||
{
|
||||
throw new ArgumentException($"Invalid reference: {validationMessage}", nameof(reference));
|
||||
}
|
||||
|
||||
return client.SendRequestAsync(
|
||||
RequestMethods.CompletionComplete,
|
||||
new()
|
||||
|
|
@ -812,7 +807,7 @@ public static class McpClientExtensions
|
|||
/// </param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>
|
||||
/// A task containing the <see cref="CallToolResponse"/> from the tool execution. The response includes
|
||||
/// A task containing the <see cref="CallToolResult"/> from the tool execution. The response includes
|
||||
/// the tool's output content, which may be structured data, text, or an error message.
|
||||
/// </returns>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="client"/> is <see langword="null"/>.</exception>
|
||||
|
|
@ -829,7 +824,7 @@ public static class McpClientExtensions
|
|||
/// });
|
||||
/// </code>
|
||||
/// </example>
|
||||
public static ValueTask<CallToolResponse> CallToolAsync(
|
||||
public static ValueTask<CallToolResult> CallToolAsync(
|
||||
this IMcpClient client,
|
||||
string toolName,
|
||||
IReadOnlyDictionary<string, object?>? arguments = null,
|
||||
|
|
@ -855,10 +850,10 @@ public static class McpClientExtensions
|
|||
Arguments = ToArgumentsDictionary(arguments, serializerOptions),
|
||||
},
|
||||
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.CallToolResponse,
|
||||
McpJsonUtilities.JsonContext.Default.CallToolResult,
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
static async ValueTask<CallToolResponse> SendRequestWithProgressAsync(
|
||||
static async ValueTask<CallToolResult> SendRequestWithProgressAsync(
|
||||
IMcpClient client,
|
||||
string toolName,
|
||||
IReadOnlyDictionary<string, object?>? arguments,
|
||||
|
|
@ -871,7 +866,7 @@ public static class McpClientExtensions
|
|||
await using var _ = client.RegisterNotificationHandler(NotificationMethods.ProgressNotification,
|
||||
(notification, cancellationToken) =>
|
||||
{
|
||||
if (JsonSerializer.Deserialize(notification.Params, McpJsonUtilities.JsonContext.Default.ProgressNotification) is { } pn &&
|
||||
if (JsonSerializer.Deserialize(notification.Params, McpJsonUtilities.JsonContext.Default.ProgressNotificationParams) is { } pn &&
|
||||
pn.ProgressToken == progressToken)
|
||||
{
|
||||
progress.Report(pn.Progress);
|
||||
|
|
@ -886,10 +881,10 @@ public static class McpClientExtensions
|
|||
{
|
||||
Name = toolName,
|
||||
Arguments = ToArgumentsDictionary(arguments, serializerOptions),
|
||||
Meta = new() { ProgressToken = progressToken },
|
||||
ProgressToken = progressToken,
|
||||
},
|
||||
McpJsonUtilities.JsonContext.Default.CallToolRequestParams,
|
||||
McpJsonUtilities.JsonContext.Default.CallToolResponse,
|
||||
McpJsonUtilities.JsonContext.Default.CallToolResult,
|
||||
cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
|
@ -924,29 +919,12 @@ public static class McpClientExtensions
|
|||
(options ??= new()).StopSequences = stopSequences.ToArray();
|
||||
}
|
||||
|
||||
List<ChatMessage> messages = [];
|
||||
foreach (SamplingMessage sm in requestParams.Messages)
|
||||
{
|
||||
ChatMessage message = new()
|
||||
{
|
||||
Role = sm.Role == Role.User ? ChatRole.User : ChatRole.Assistant,
|
||||
};
|
||||
|
||||
if (sm.Content is { Type: "text" })
|
||||
{
|
||||
message.Contents.Add(new TextContent(sm.Content.Text));
|
||||
}
|
||||
else if (sm.Content is { Type: "image" or "audio", MimeType: not null, Data: not null })
|
||||
{
|
||||
message.Contents.Add(new DataContent(Convert.FromBase64String(sm.Content.Data), sm.Content.MimeType));
|
||||
}
|
||||
else if (sm.Content is { Type: "resource", Resource: not null })
|
||||
{
|
||||
message.Contents.Add(sm.Content.Resource.ToAIContent());
|
||||
}
|
||||
|
||||
messages.Add(message);
|
||||
}
|
||||
List<ChatMessage> messages =
|
||||
(from sm in requestParams.Messages
|
||||
let aiContent = sm.Content.ToAIContent()
|
||||
where aiContent is not null
|
||||
select new ChatMessage(sm.Role == Role.Assistant ? ChatRole.Assistant : ChatRole.User, [aiContent]))
|
||||
.ToList();
|
||||
|
||||
return (messages, options);
|
||||
}
|
||||
|
|
@ -965,32 +943,21 @@ public static class McpClientExtensions
|
|||
|
||||
ChatMessage? lastMessage = chatResponse.Messages.LastOrDefault();
|
||||
|
||||
Content? content = null;
|
||||
ContentBlock? content = null;
|
||||
if (lastMessage is not null)
|
||||
{
|
||||
foreach (var lmc in lastMessage.Contents)
|
||||
{
|
||||
if (lmc is DataContent dc && (dc.HasTopLevelMediaType("image") || dc.HasTopLevelMediaType("audio")))
|
||||
{
|
||||
content = new()
|
||||
{
|
||||
Type = dc.HasTopLevelMediaType("image") ? "image" : "audio",
|
||||
MimeType = dc.MediaType,
|
||||
Data = dc.Base64Data.ToString(),
|
||||
};
|
||||
content = dc.ToContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content ??= new()
|
||||
{
|
||||
Text = lastMessage?.Text ?? string.Empty,
|
||||
Type = "text",
|
||||
};
|
||||
|
||||
return new()
|
||||
{
|
||||
Content = content,
|
||||
Content = content ?? new TextContentBlock { Text = lastMessage?.Text ?? string.Empty },
|
||||
Model = chatResponse.ModelId ?? "unknown",
|
||||
Role = lastMessage?.Role == ChatRole.User ? Role.User : Role.Assistant,
|
||||
StopReason = chatResponse.FinishReason == ChatFinishReason.Length ? "maxTokens" : "endTurn",
|
||||
|
|
@ -1024,7 +991,7 @@ public static class McpClientExtensions
|
|||
Throw.IfNull(requestParams);
|
||||
|
||||
var (messages, options) = requestParams.ToChatClientArguments();
|
||||
var progressToken = requestParams.Meta?.ProgressToken;
|
||||
var progressToken = requestParams.ProgressToken;
|
||||
|
||||
List<ChatResponseUpdate> updates = [];
|
||||
await foreach (var update in chatClient.GetStreamingResponseAsync(messages, options, cancellationToken).ConfigureAwait(false))
|
||||
|
|
@ -1107,7 +1074,7 @@ public static class McpClientExtensions
|
|||
SetLoggingLevel(client, McpServer.ToLoggingLevel(level), cancellationToken);
|
||||
|
||||
/// <summary>Convers a dictionary with <see cref="object"/> values to a dictionary with <see cref="JsonElement"/> values.</summary>
|
||||
private static IReadOnlyDictionary<string, JsonElement>? ToArgumentsDictionary(
|
||||
private static Dictionary<string, JsonElement>? ToArgumentsDictionary(
|
||||
IReadOnlyDictionary<string, object?>? arguments, JsonSerializerOptions options)
|
||||
{
|
||||
var typeInfo = options.GetTypeInfo<object?>();
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ namespace ModelContextProtocol.Client;
|
|||
/// These options are typically passed to <see cref="McpClientFactory.CreateAsync"/> when creating a client.
|
||||
/// They define client capabilities, protocol version, and other client-specific settings.
|
||||
/// </remarks>
|
||||
public class McpClientOptions
|
||||
public sealed class McpClientOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets information about this client implementation, including its name and version.
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ public sealed class McpClientPrompt
|
|||
/// <summary>Gets the name of the prompt.</summary>
|
||||
public string Name => ProtocolPrompt.Name;
|
||||
|
||||
/// <summary>Gets the title of the prompt.</summary>
|
||||
public string? Title => ProtocolPrompt.Title;
|
||||
|
||||
/// <summary>Gets a description of the prompt.</summary>
|
||||
public string? Description => ProtocolPrompt.Description;
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ public sealed class McpClientResource
|
|||
/// <summary>Gets the name of the resource.</summary>
|
||||
public string Name => ProtocolResource.Name;
|
||||
|
||||
/// <summary>Gets the title of the resource.</summary>
|
||||
public string? Title => ProtocolResource.Title;
|
||||
|
||||
/// <summary>Gets a description of the resource.</summary>
|
||||
public string? Description => ProtocolResource.Description;
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ public sealed class McpClientResourceTemplate
|
|||
/// <summary>Gets the name of the resource template.</summary>
|
||||
public string Name => ProtocolResourceTemplate.Name;
|
||||
|
||||
/// <summary>Gets the title of the resource template.</summary>
|
||||
public string? Title => ProtocolResourceTemplate.Title;
|
||||
|
||||
/// <summary>Gets a description of the resource template.</summary>
|
||||
public string? Description => ProtocolResourceTemplate.Description;
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,9 @@ public sealed class McpClientTool : AIFunction
|
|||
/// <inheritdoc/>
|
||||
public override string Name => _name;
|
||||
|
||||
/// <summary>Gets the tool's title.</summary>
|
||||
public string? Title => ProtocolTool.Title ?? ProtocolTool.Annotations?.Title;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Description => _description;
|
||||
|
||||
|
|
@ -84,8 +87,8 @@ public sealed class McpClientTool : AIFunction
|
|||
protected async override ValueTask<object?> InvokeCoreAsync(
|
||||
AIFunctionArguments arguments, CancellationToken cancellationToken)
|
||||
{
|
||||
CallToolResponse result = await CallAsync(arguments, _progress, JsonSerializerOptions, cancellationToken).ConfigureAwait(false);
|
||||
return JsonSerializer.SerializeToElement(result, McpJsonUtilities.JsonContext.Default.CallToolResponse);
|
||||
CallToolResult result = await CallAsync(arguments, _progress, JsonSerializerOptions, cancellationToken).ConfigureAwait(false);
|
||||
return JsonSerializer.SerializeToElement(result, McpJsonUtilities.JsonContext.Default.CallToolResult);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -104,14 +107,14 @@ public sealed class McpClientTool : AIFunction
|
|||
/// </param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>
|
||||
/// A task containing the <see cref="CallToolResponse"/> from the tool execution. The response includes
|
||||
/// A task containing the <see cref="CallToolResult"/> from the tool execution. The response includes
|
||||
/// the tool's output content, which may be structured data, text, or an error message.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// The base <see cref="AIFunction.InvokeAsync"/> method is overridden to invoke this <see cref="CallAsync"/> method.
|
||||
/// The only difference in behavior is <see cref="AIFunction.InvokeAsync"/> will serialize the resulting <see cref="CallToolResponse"/>"/>
|
||||
/// such that the <see cref="object"/> returned is a <see cref="JsonElement"/> containing the serialized <see cref="CallToolResponse"/>.
|
||||
/// This <see cref="CallToolResponse"/> method is intended to be called directly by user code, whereas the base <see cref="AIFunction.InvokeAsync"/>
|
||||
/// The only difference in behavior is <see cref="AIFunction.InvokeAsync"/> will serialize the resulting <see cref="CallToolResult"/>"/>
|
||||
/// such that the <see cref="object"/> returned is a <see cref="JsonElement"/> containing the serialized <see cref="CallToolResult"/>.
|
||||
/// This <see cref="CallToolResult"/> method is intended to be called directly by user code, whereas the base <see cref="AIFunction.InvokeAsync"/>
|
||||
/// is intended to be used polymorphically via the base class, typically as part of an <see cref="IChatClient"/> operation.
|
||||
/// </remarks>
|
||||
/// <exception cref="McpException">The server could not find the requested tool, or the server encountered an error while processing the request.</exception>
|
||||
|
|
@ -124,7 +127,7 @@ public sealed class McpClientTool : AIFunction
|
|||
/// });
|
||||
/// </code>
|
||||
/// </example>
|
||||
public ValueTask<CallToolResponse> CallAsync(
|
||||
public ValueTask<CallToolResult> CallAsync(
|
||||
IReadOnlyDictionary<string, object?>? arguments = null,
|
||||
IProgress<ProgressNotificationValue>? progress = null,
|
||||
JsonSerializerOptions? serializerOptions = null,
|
||||
|
|
@ -156,7 +159,7 @@ public sealed class McpClientTool : AIFunction
|
|||
/// </para>
|
||||
/// </remarks>
|
||||
public McpClientTool WithName(string name) =>
|
||||
new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, name, _description, _progress);
|
||||
new(_client, ProtocolTool, JsonSerializerOptions, name, _description, _progress);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the tool but modified to return the specified description from its <see cref="Description"/> property.
|
||||
|
|
@ -180,7 +183,7 @@ public sealed class McpClientTool : AIFunction
|
|||
/// </remarks>
|
||||
/// <returns>A new instance of <see cref="McpClientTool"/> with the provided description.</returns>
|
||||
public McpClientTool WithDescription(string description) =>
|
||||
new McpClientTool(_client, ProtocolTool, JsonSerializerOptions, _name, description, _progress);
|
||||
new(_client, ProtocolTool, JsonSerializerOptions, _name, description, _progress);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the tool but modified to report progress via the specified <see cref="IProgress{T}"/>.
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ namespace ModelContextProtocol.Client;
|
|||
/// <summary>
|
||||
/// Provides options for configuring <see cref="SseClientTransport"/> instances.
|
||||
/// </summary>
|
||||
public class SseClientTransportOptions
|
||||
public sealed class SseClientTransportOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the base address of the server for SSE connections.
|
||||
|
|
@ -69,5 +69,5 @@ public class SseClientTransportOptions
|
|||
/// <remarks>
|
||||
/// Use this property to specify custom HTTP headers that should be sent with each request to the server.
|
||||
/// </remarks>
|
||||
public Dictionary<string, string>? AdditionalHeaders { get; set; }
|
||||
public IDictionary<string, string>? AdditionalHeaders { get; set; }
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@ namespace ModelContextProtocol.Client;
|
|||
/// <summary>
|
||||
/// Provides options for configuring <see cref="StdioClientTransport"/> instances.
|
||||
/// </summary>
|
||||
public class StdioClientTransportOptions
|
||||
public sealed class StdioClientTransportOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the command to execute to start the server process.
|
||||
|
|
@ -53,7 +53,7 @@ public class StdioClientTransportOptions
|
|||
/// That includes removing the variables for any of this collection's entries with a null value.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public Dictionary<string, string?>? EnvironmentVariables { get; set; }
|
||||
public IDictionary<string, string?>? EnvironmentVariables { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timeout to wait for the server to shut down gracefully.
|
||||
|
|
|
|||
|
|
@ -244,7 +244,7 @@ internal sealed partial class StreamableHttpClientSessionTransport : TransportBa
|
|||
}
|
||||
}
|
||||
|
||||
internal static void CopyAdditionalHeaders(HttpRequestHeaders headers, Dictionary<string, string>? additionalHeaders, string? sessionId = null)
|
||||
internal static void CopyAdditionalHeaders(HttpRequestHeaders headers, IDictionary<string, string>? additionalHeaders, string? sessionId = null)
|
||||
{
|
||||
if (sessionId is not null)
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue