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:
Stephen Toub 2025-06-16 19:39:41 -04:00 • committed by GitHub
commit 2bcff315f8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
121 changed files with 1700 additions and 853 deletions

View file

@ -29,11 +29,13 @@ public static class AIContentExtensions
{
Throw.IfNull(promptMessage);
AIContent? content = ToAIContent(promptMessage.Content);
return new()
{
RawRepresentation = promptMessage,
Role = promptMessage.Role == Role.User ? ChatRole.User : ChatRole.Assistant,
Contents = [ToAIContent(promptMessage.Content)]
Contents = content is not null ? [content] : [],
};
}
@ -81,32 +83,32 @@ public static class AIContentExtensions
return messages;
}
/// <summary>Creates a new <see cref="AIContent"/> from the content of a <see cref="Content"/>.</summary>
/// <param name="content">The <see cref="Content"/> to convert.</param>
/// <returns>The created <see cref="AIContent"/>.</returns>
/// <summary>Creates a new <see cref="AIContent"/> from the content of a <see cref="ContentBlock"/>.</summary>
/// <param name="content">The <see cref="ContentBlock"/> to convert.</param>
/// <returns>
/// The created <see cref="AIContent"/>. If the content can't be converted (such as when it's a resource link), <see langword="null"/> is returned.
/// </returns>
/// <remarks>
/// This method converts Model Context Protocol content types to the equivalent Microsoft.Extensions.AI
/// content types, enabling seamless integration between the protocol and AI client libraries.
/// </remarks>
public static AIContent ToAIContent(this Content content)
public static AIContent? ToAIContent(this ContentBlock content)
{
Throw.IfNull(content);
AIContent ac;
if (content is { Type: "image" or "audio", MimeType: not null, Data: not null })
AIContent? ac = content switch
{
ac = new DataContent(Convert.FromBase64String(content.Data), content.MimeType);
}
else if (content is { Type: "resource" } && content.Resource is { } resourceContents)
{
ac = resourceContents.ToAIContent();
}
else
{
ac = new TextContent(content.Text);
}
TextContentBlock textContent => new TextContent(textContent.Text),
ImageContentBlock imageContent => new DataContent(Convert.FromBase64String(imageContent.Data), imageContent.MimeType),
AudioContentBlock audioContent => new DataContent(Convert.FromBase64String(audioContent.Data), audioContent.MimeType),
EmbeddedResourceBlock resourceContent => resourceContent.Resource.ToAIContent(),
_ => null,
};
ac.RawRepresentation = content;
if (ac is not null)
{
ac.RawRepresentation = content;
}
return ac;
}
@ -135,8 +137,8 @@ public static class AIContentExtensions
return ac;
}
/// <summary>Creates a list of <see cref="AIContent"/> from a sequence of <see cref="Content"/>.</summary>
/// <param name="contents">The <see cref="Content"/> instances to convert.</param>
/// <summary>Creates a list of <see cref="AIContent"/> from a sequence of <see cref="ContentBlock"/>.</summary>
/// <param name="contents">The <see cref="ContentBlock"/> instances to convert.</param>
/// <returns>The created <see cref="AIContent"/> instances.</returns>
/// <remarks>
/// <para>
@ -145,15 +147,15 @@ public static class AIContentExtensions
/// when processing the contents of a message or response.
/// </para>
/// <para>
/// Each <see cref="Content"/> object is converted using <see cref="ToAIContent(Content)"/>,
/// Each <see cref="ContentBlock"/> object is converted using <see cref="ToAIContent(ContentBlock)"/>,
/// preserving the type-specific conversion logic for text, images, audio, and resources.
/// </para>
/// </remarks>
public static IList<AIContent> ToAIContents(this IEnumerable<Content> contents)
public static IList<AIContent> ToAIContents(this IEnumerable<ContentBlock> contents)
{
Throw.IfNull(contents);
return [.. contents.Select(ToAIContent)];
return [.. contents.Select(ToAIContent).OfType<AIContent>()];
}
/// <summary>Creates a list of <see cref="AIContent"/> from a sequence of <see cref="ResourceContents"/>.</summary>
@ -167,7 +169,7 @@ public static class AIContentExtensions
/// </para>
/// <para>
/// Each <see cref="ResourceContents"/> object is converted using <see cref="ToAIContent(ResourceContents)"/>,
/// preserving the type-specific conversion logic: text resources become <see cref="TextContent"/> objects and
/// preserving the type-specific conversion logic: text resources become <see cref="TextContentBlock"/> objects and
/// binary resources become <see cref="DataContent"/> objects.
/// </para>
/// </remarks>
@ -178,29 +180,38 @@ public static class AIContentExtensions
return [.. contents.Select(ToAIContent)];
}
internal static Content ToContent(this AIContent content) =>
internal static ContentBlock ToContent(this AIContent content) =>
content switch
{
TextContent textContent => new()
TextContent textContent => new TextContentBlock
{
Text = textContent.Text,
Type = "text",
},
DataContent dataContent => new()
DataContent dataContent when dataContent.HasTopLevelMediaType("image") => new ImageContentBlock()
{
Data = dataContent.Base64Data.ToString(),
MimeType = dataContent.MediaType,
Type =
dataContent.HasTopLevelMediaType("image") ? "image" :
dataContent.HasTopLevelMediaType("audio") ? "audio" :
"resource",
},
_ => new()
DataContent dataContent when dataContent.HasTopLevelMediaType("audio") => new AudioContentBlock()
{
Data = dataContent.Base64Data.ToString(),
MimeType = dataContent.MediaType,
},
DataContent dataContent => new EmbeddedResourceBlock()
{
Resource = new BlobResourceContents()
{
Blob = dataContent.Base64Data.ToString(),
MimeType = dataContent.MediaType,
}
},
_ => new TextContentBlock
{
Text = JsonSerializer.Serialize(content, McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(object))),
Type = "text",
}
};
}

View file

@ -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)
{

View file

@ -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?>();

View file

@ -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.

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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}"/>.

View file

@ -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; }
}

View file

@ -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.

View file

@ -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)
{

View file

@ -13,7 +13,7 @@ internal static class Diagnostics
internal static Meter Meter { get; } = new("Experimental.ModelContextProtocol");
internal static Histogram<double> CreateDurationHistogram(string name, string description, bool longBuckets) =>
Diagnostics.Meter.CreateHistogram<double>(name, "s", description
Meter.CreateHistogram<double>(name, "s", description
#if NET9_0_OR_GREATER
, advice: longBuckets ? LongSecondsBucketBoundaries : ShortSecondsBucketBoundaries
#endif

View file

@ -201,12 +201,12 @@ public static class McpEndpointExtensions
return endpoint.SendNotificationAsync(
NotificationMethods.ProgressNotification,
new ProgressNotification
new ProgressNotificationParams
{
ProgressToken = progressToken,
Progress = progress,
},
McpJsonUtilities.JsonContext.Default.ProgressNotification,
McpJsonUtilities.JsonContext.Default.ProgressNotificationParams,
cancellationToken);
}
}

View file

@ -1,8 +1,6 @@
using Microsoft.Extensions.AI;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
@ -98,10 +96,20 @@ public static partial class McpJsonUtilities
[JsonSerializable(typeof(JsonRpcResponse))]
[JsonSerializable(typeof(JsonRpcError))]
// MCP Notification Params
[JsonSerializable(typeof(CancelledNotificationParams))]
[JsonSerializable(typeof(InitializedNotificationParams))]
[JsonSerializable(typeof(LoggingMessageNotificationParams))]
[JsonSerializable(typeof(ProgressNotificationParams))]
[JsonSerializable(typeof(PromptListChangedNotificationParams))]
[JsonSerializable(typeof(ResourceListChangedNotificationParams))]
[JsonSerializable(typeof(ResourceUpdatedNotificationParams))]
[JsonSerializable(typeof(RootsListChangedNotificationParams))]
[JsonSerializable(typeof(ToolListChangedNotificationParams))]
// MCP Request Params / Results
[JsonSerializable(typeof(CallToolRequestParams))]
[JsonSerializable(typeof(CallToolResponse))]
[JsonSerializable(typeof(CancelledNotification))]
[JsonSerializable(typeof(CallToolResult))]
[JsonSerializable(typeof(CompleteRequestParams))]
[JsonSerializable(typeof(CompleteResult))]
[JsonSerializable(typeof(CreateMessageRequestParams))]
@ -123,17 +131,28 @@ public static partial class McpJsonUtilities
[JsonSerializable(typeof(ListRootsResult))]
[JsonSerializable(typeof(ListToolsRequestParams))]
[JsonSerializable(typeof(ListToolsResult))]
[JsonSerializable(typeof(LoggingMessageNotificationParams))]
[JsonSerializable(typeof(PingResult))]
[JsonSerializable(typeof(ProgressNotification))]
[JsonSerializable(typeof(ReadResourceRequestParams))]
[JsonSerializable(typeof(ReadResourceResult))]
[JsonSerializable(typeof(ResourceUpdatedNotificationParams))]
[JsonSerializable(typeof(SetLevelRequestParams))]
[JsonSerializable(typeof(SubscribeRequestParams))]
[JsonSerializable(typeof(UnsubscribeRequestParams))]
// MCP Content
[JsonSerializable(typeof(ContentBlock))]
[JsonSerializable(typeof(TextContentBlock))]
[JsonSerializable(typeof(ImageContentBlock))]
[JsonSerializable(typeof(AudioContentBlock))]
[JsonSerializable(typeof(EmbeddedResourceBlock))]
[JsonSerializable(typeof(ResourceLinkBlock))]
[JsonSerializable(typeof(PromptReference))]
[JsonSerializable(typeof(ResourceTemplateReference))]
[JsonSerializable(typeof(BlobResourceContents))]
[JsonSerializable(typeof(TextResourceContents))]
// Other MCP Types
[JsonSerializable(typeof(IReadOnlyDictionary<string, object>))]
[JsonSerializable(typeof(PromptMessage[]))]
[JsonSerializable(typeof(ProgressToken))]
// Primitive types for use in consuming AIFunctions
[JsonSerializable(typeof(string))]

View file

@ -273,7 +273,7 @@ internal sealed partial class McpSession : IDisposable
{
try
{
if (GetCancelledNotificationParams(notification.Params) is CancelledNotification cn &&
if (GetCancelledNotificationParams(notification.Params) is CancelledNotificationParams cn &&
_handlingRequests.TryGetValue(cn.RequestId, out var cts))
{
await cts.CancelAsync().ConfigureAwait(false);
@ -337,7 +337,7 @@ internal sealed partial class McpSession : IDisposable
_ = state.Item1.SendMessageAsync(new JsonRpcNotification
{
Method = NotificationMethods.CancelledNotification,
Params = JsonSerializer.SerializeToNode(new CancelledNotification { RequestId = state.Item2.Id }, McpJsonUtilities.JsonContext.Default.CancelledNotification),
Params = JsonSerializer.SerializeToNode(new CancelledNotificationParams { RequestId = state.Item2.Id }, McpJsonUtilities.JsonContext.Default.CancelledNotificationParams),
RelatedTransport = state.Item2.RelatedTransport,
});
}, Tuple.Create(this, request));
@ -495,7 +495,7 @@ internal sealed partial class McpSession : IDisposable
// server won't be sending a response, or per the specification, the response should be ignored. There are inherent
// race conditions here, so it's possible and allowed for the operation to complete before we get to this point.
if (message is JsonRpcNotification { Method: NotificationMethods.CancelledNotification } notification &&
GetCancelledNotificationParams(notification.Params) is CancelledNotification cn &&
GetCancelledNotificationParams(notification.Params) is CancelledNotificationParams cn &&
_pendingRequests.TryRemove(cn.RequestId, out var tcs))
{
tcs.TrySetCanceled(default);
@ -518,11 +518,11 @@ internal sealed partial class McpSession : IDisposable
private Task SendToRelatedTransportAsync(JsonRpcMessage message, CancellationToken cancellationToken)
=> (message.RelatedTransport ?? _transport).SendMessageAsync(message, cancellationToken);
private static CancelledNotification? GetCancelledNotificationParams(JsonNode? notificationParams)
private static CancelledNotificationParams? GetCancelledNotificationParams(JsonNode? notificationParams)
{
try
{
return JsonSerializer.Deserialize(notificationParams, McpJsonUtilities.JsonContext.Default.CancelledNotification);
return JsonSerializer.Deserialize(notificationParams, McpJsonUtilities.JsonContext.Default.CancelledNotificationParams);
}
catch
{

View file

@ -1,7 +1,7 @@
namespace ModelContextProtocol;
/// <summary>Provides a progress value that can be sent using <see cref="IProgress{ProgressNotificationValue}"/>.</summary>
public class ProgressNotificationValue
public sealed class ProgressNotificationValue
{
/// <summary>
/// Gets or sets the progress thus far.

View file

@ -9,13 +9,13 @@ namespace ModelContextProtocol.Protocol;
/// Annotations enable filtering and prioritization of content for different audiences.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public class Annotations
public sealed class Annotations
{
/// <summary>
/// Gets or sets the intended audience for this content as an array of <see cref="Role"/> values.
/// </summary>
[JsonPropertyName("audience")]
public Role[]? Audience { get; init; }
public IList<Role>? Audience { get; init; }
/// <summary>
/// Gets or sets a value indicating how important this data is for operating the server.
@ -26,4 +26,14 @@ public class Annotations
/// </remarks>
[JsonPropertyName("priority")]
public float? Priority { get; init; }
/// <summary>
/// Gets or sets the moment the resource was last modified.
/// </summary>
/// <remarks>
/// The corresponding JSON should be an ISO 8601 formatted string (e.g., \"2025-01-12T15:00:58Z\").
/// Examples: last activity timestamp in an open file, timestamp when the resource was attached, etc.
/// </remarks>
[JsonPropertyName("lastModified")]
public DateTimeOffset? LastModified { get; set; }
}

View file

@ -9,7 +9,7 @@ namespace ModelContextProtocol.Protocol;
/// This class is used when requesting completion suggestions for a particular field or parameter.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public class Argument
public sealed class Argument
{
/// <summary>
/// Gets or sets the name of the argument being completed.

View file

@ -20,7 +20,7 @@ namespace ModelContextProtocol.Protocol;
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for more details.
/// </para>
/// </remarks>
public class BlobResourceContents : ResourceContents
public sealed class BlobResourceContents : ResourceContents
{
/// <summary>
/// Gets or sets the base64-encoded string representing the binary data of the item.

View file

@ -7,10 +7,10 @@ namespace ModelContextProtocol.Protocol;
/// Represents the parameters used with a <see cref="RequestMethods.ToolsCall"/> request from a client to invoke a tool provided by the server.
/// </summary>
/// <remarks>
/// The server will respond with a <see cref="CallToolResponse"/> containing the result of the tool invocation.
/// The server will respond with a <see cref="CallToolResult"/> containing the result of the tool invocation.
/// See the <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">schema</see> for details.
/// </remarks>
public class CallToolRequestParams : RequestParams
public sealed class CallToolRequestParams : RequestParams
{
/// <summary>Gets or sets the name of the tool to invoke.</summary>
[JsonPropertyName("name")]

Some files were not shown because too many files have changed in this diff Show more