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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ private static Task WriteJsonRpcErrorAsync(HttpContext context, string errorMess
{
var jsonRpcError = new JsonRpcError
{
Id = default,
Error = new()
{
Code = errorCode,
Expand Down
1 change: 1 addition & 0 deletions src/ModelContextProtocol.Core/AIContentExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ internal static ContentBlock ToContent(this AIContent content) =>
{
Blob = dataContent.Base64Data.ToString(),
MimeType = dataContent.MediaType,
Uri = string.Empty,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was implicitly string.Empty before, but now it's explicit. This PR doesn't introduce a behavioral change here, but we should decide whether the empty string should really be used here, as it's not actually a valid URI. It would also be strange to use dataContent.Uri, because then the data is specified in two places (once in Blob and once in Uri).

}
},

Expand Down
2 changes: 1 addition & 1 deletion src/ModelContextProtocol.Core/McpSessionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ public async Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, Canc
// Set request ID
if (request.Id.Id is null)
{
request = request.WithId(new RequestId(Interlocked.Increment(ref _lastRequestId)));
request.Id = new RequestId(Interlocked.Increment(ref _lastRequestId));
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This now mutates the request parameter directly instead of cloning it. I think that's fine, but please correct me if not. This may have just been a workaround for JsonRpcRequest inheriting init-only properties, but I'm not sure.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to avoid potential regressions in the scope of this change, I would suggest preserving the original cloning semantics even if done manually.

}

_propagator.InjectActivityContext(activity, request);
Expand Down
4 changes: 2 additions & 2 deletions src/ModelContextProtocol.Core/Protocol/Annotations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public sealed class Annotations
/// Gets or sets the intended audience for this content as an array of <see cref="Role"/> values.
/// </summary>
[JsonPropertyName("audience")]
public IList<Role>? Audience { get; init; }
public IList<Role>? Audience { get; set; }

/// <summary>
/// Gets or sets a value indicating how important this data is for operating the server.
Expand All @@ -25,7 +25,7 @@ public sealed class Annotations
/// 1 represents highest priority.
/// </remarks>
[JsonPropertyName("priority")]
public float? Priority { get; init; }
public float? Priority { get; set; }

/// <summary>
/// Gets or sets the moment the resource was last modified.
Expand Down
6 changes: 3 additions & 3 deletions src/ModelContextProtocol.Core/Protocol/Argument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public sealed class Argument
/// Gets or sets the name of the argument being completed.
/// </summary>
[JsonPropertyName("name")]
public string Name { get; set; } = string.Empty;
public required string Name { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW introducing required is also considered a breaking change.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. And the same is true for other changes made in this PR (e.g., IReadOnlyList<T> -> IList<T>). We'll have to decide which changes are worth their induced breaks.


/// <summary>
/// Gets or sets the current partial text value for which completion suggestions are requested.
Expand All @@ -25,5 +25,5 @@ public sealed class Argument
/// options should be generated.
/// </remarks>
[JsonPropertyName("value")]
public string Value { get; set; } = string.Empty;
}
public required string Value { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ public sealed class BlobResourceContents : ResourceContents
/// Gets or sets the base64-encoded string representing the binary data of the item.
/// </summary>
[JsonPropertyName("blob")]
public string Blob { get; set; } = string.Empty;
}
public required string Blob { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public sealed class CallToolRequestParams : RequestParams
{
/// <summary>Gets or sets the name of the tool to invoke.</summary>
[JsonPropertyName("name")]
public required string Name { get; init; }
public required string Name { get; set; }

/// <summary>
/// Gets or sets optional arguments to pass to the tool when invoking it on the server.
Expand All @@ -24,5 +24,5 @@ public sealed class CallToolRequestParams : RequestParams
/// a parameter name and its corresponding argument value.
/// </remarks>
[JsonPropertyName("arguments")]
public IReadOnlyDictionary<string, JsonElement>? Arguments { get; init; }
public IDictionary<string, JsonElement>? Arguments { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public sealed class CancelledNotificationParams : NotificationParams
/// This must match the ID of an in-flight request that the sender wishes to cancel.
/// </remarks>
[JsonPropertyName("requestId")]
public RequestId RequestId { get; set; }
public required RequestId RequestId { get; set; }

/// <summary>
/// Gets or sets an optional string describing the reason for the cancellation request.
Expand Down
2 changes: 1 addition & 1 deletion src/ModelContextProtocol.Core/Protocol/CompleteContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ public sealed class CompleteContext
/// Gets or sets previously-resolved variables in a URI template or prompt.
/// </summary>
[JsonPropertyName("arguments")]
public IDictionary<string, string>? Arguments { get; init; }
public IDictionary<string, string>? Arguments { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@ public sealed class CompleteRequestParams : RequestParams
/// Gets or sets the reference's information.
/// </summary>
[JsonPropertyName("ref")]
public required Reference Ref { get; init; }
public required Reference Ref { get; set; }

/// <summary>
/// Gets or sets the argument information for the completion request, specifying what is being completed
/// and the current partial input.
/// </summary>
[JsonPropertyName("argument")]
public required Argument Argument { get; init; }
public required Argument Argument { get; set; }

/// <summary>
/// Gets or sets additional, optional context for completions.
/// </summary>
[JsonPropertyName("context")]
public CompleteContext? Context { get; init; }
public CompleteContext? Context { get; set; }
}
38 changes: 20 additions & 18 deletions src/ModelContextProtocol.Core/Protocol/ContentBlock.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.Extensions.AI;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
Expand Down Expand Up @@ -39,7 +40,7 @@ private protected ContentBlock()
/// This determines the structure of the content object. Valid values include "image", "audio", "text", "resource", and "resource_link".
/// </remarks>
[JsonPropertyName("type")]
public string Type { get; set; } = string.Empty;
public abstract string Type { get; }

/// <summary>
/// Gets or sets optional annotations for the content.
Expand All @@ -49,7 +50,7 @@ private protected ContentBlock()
/// and the priority level of the content. Clients can use this information to filter or prioritize content for different roles.
/// </remarks>
[JsonPropertyName("annotations")]
public Annotations? Annotations { get; init; }
public Annotations? Annotations { get; set; }

/// <summary>
/// Provides a <see cref="JsonConverter"/> for <see cref="ContentBlock"/>.
Expand Down Expand Up @@ -276,8 +277,8 @@ public override void Write(Utf8JsonWriter writer, ContentBlock value, JsonSerial
/// <summary>Represents text provided to or from an LLM.</summary>
public sealed class TextContentBlock : ContentBlock
{
/// <summary>Initializes the instance of the <see cref="TextContentBlock"/> class.</summary>
public TextContentBlock() => Type = "text";
/// <inheritdoc/>
public override string Type => "text";

/// <summary>
/// Gets or sets the text content of the message.
Expand All @@ -298,8 +299,8 @@ public sealed class TextContentBlock : ContentBlock
/// <summary>Represents an image provided to or from an LLM.</summary>
public sealed class ImageContentBlock : ContentBlock
{
/// <summary>Initializes the instance of the <see cref="ImageContentBlock"/> class.</summary>
public ImageContentBlock() => Type = "image";
/// <inheritdoc/>
public override string Type => "image";

/// <summary>
/// Gets or sets the base64-encoded image data.
Expand Down Expand Up @@ -331,8 +332,8 @@ public sealed class ImageContentBlock : ContentBlock
/// <summary>Represents audio provided to or from an LLM.</summary>
public sealed class AudioContentBlock : ContentBlock
{
/// <summary>Initializes the instance of the <see cref="AudioContentBlock"/> class.</summary>
public AudioContentBlock() => Type = "audio";
/// <inheritdoc/>
public override string Type => "audio";

/// <summary>
/// Gets or sets the base64-encoded audio data.
Expand Down Expand Up @@ -367,8 +368,8 @@ public sealed class AudioContentBlock : ContentBlock
/// </remarks>
public sealed class EmbeddedResourceBlock : ContentBlock
{
/// <summary>Initializes the instance of the <see cref="ResourceLinkBlock"/> class.</summary>
public EmbeddedResourceBlock() => Type = "resource";
/// <inheritdoc/>
public override string Type => "resource";

/// <summary>
/// Gets or sets the resource content of the message when <see cref="Type"/> is "resource".
Expand Down Expand Up @@ -399,20 +400,21 @@ public sealed class EmbeddedResourceBlock : ContentBlock
/// </remarks>
public sealed class ResourceLinkBlock : ContentBlock
{
/// <summary>Initializes the instance of the <see cref="ResourceLinkBlock"/> class.</summary>
public ResourceLinkBlock() => Type = "resource_link";
/// <inheritdoc/>
public override string Type => "resource_link";

/// <summary>
/// Gets or sets the URI of this resource.
/// </summary>
[JsonPropertyName("uri")]
public required string Uri { get; init; }
[StringSyntax(StringSyntaxAttribute.Uri)]
public required string Uri { get; set; }

/// <summary>
/// Gets or sets a human-readable name for this resource.
/// </summary>
[JsonPropertyName("name")]
public required string Name { get; init; }
public required string Name { get; set; }

/// <summary>
/// Gets or sets a description of what this resource represents.
Expand All @@ -431,7 +433,7 @@ public sealed class ResourceLinkBlock : ContentBlock
/// </para>
/// </remarks>
[JsonPropertyName("description")]
public string? Description { get; init; }
public string? Description { get; set; }

/// <summary>
/// Gets or sets the MIME type of this resource.
Expand All @@ -447,7 +449,7 @@ public sealed class ResourceLinkBlock : ContentBlock
/// </para>
/// </remarks>
[JsonPropertyName("mimeType")]
public string? MimeType { get; init; }
public string? MimeType { get; set; }

/// <summary>
/// Gets or sets the size of the raw resource content (before base64 encoding), in bytes, if known.
Expand All @@ -456,5 +458,5 @@ public sealed class ResourceLinkBlock : ContentBlock
/// This can be used by applications to display file sizes and estimate context window usage.
/// </remarks>
[JsonPropertyName("size")]
public long? Size { get; init; }
}
public long? Size { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public sealed class CreateMessageRequestParams : RequestParams
/// The client may ignore this request.
/// </remarks>
[JsonPropertyName("includeContext")]
public ContextInclusion? IncludeContext { get; init; }
public ContextInclusion? IncludeContext { get; set; }

/// <summary>
/// Gets or sets the maximum number of tokens to generate in the LLM response, as requested by the server.
Expand All @@ -29,13 +29,13 @@ public sealed class CreateMessageRequestParams : RequestParams
/// response length and computation time. The client may choose to sample fewer tokens than requested.
/// </remarks>
[JsonPropertyName("maxTokens")]
public int? MaxTokens { get; init; }
public required int MaxTokens { get; set; }
Copy link
Collaborator Author

@MackinnonBuck MackinnonBuck Oct 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This property was an int? despite the schema declaring it as required. Maybe this was intentional. Happy to change back if so.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should use the schema as our source of truth when defining these model types.


/// <summary>
/// Gets or sets the messages requested by the server to be included in the prompt.
/// </summary>
[JsonPropertyName("messages")]
public required IReadOnlyList<SamplingMessage> Messages { get; init; }
public IList<SamplingMessage> Messages { get; set; } = [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this changed back to optional? Presumably not required by the schema?

Copy link
Collaborator Author

@MackinnonBuck MackinnonBuck Oct 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking here was that this property is still effectively "required" in that it's not declared as allowing null values. We just no longer require an explicit value on initialization, since a reasonable, non-null default exists ([]).

However, your comment made me realize that removing required actually does make the property "optional" in that we will no longer throw an exception on deserialization if the property is missing from the JSON payload. Maybe this is OK. We also don't appear to be using RespectNullableAnnotations, so the JSON input could still explicitly provide null for this property anyway. So, required alone still isn't enough to enforce that the JSON property has a specified, non-null value.

Curious about your thoughts - do you think it's fine to have a default initial value as a substitute for required?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or maybe we should consider enabling RespectNullableAnnotations so that we're able to enforce required-ness to a more complete degree?


/// <summary>
/// Gets or sets optional metadata to pass through to the LLM provider.
Expand All @@ -46,7 +46,7 @@ public sealed class CreateMessageRequestParams : RequestParams
/// that are specific to certain AI models or providers.
/// </remarks>
[JsonPropertyName("metadata")]
public JsonElement? Metadata { get; init; }
public JsonElement? Metadata { get; set; }

/// <summary>
/// Gets or sets the server's preferences for which model to select.
Expand All @@ -66,7 +66,7 @@ public sealed class CreateMessageRequestParams : RequestParams
/// </para>
/// </remarks>
[JsonPropertyName("modelPreferences")]
public ModelPreferences? ModelPreferences { get; init; }
public ModelPreferences? ModelPreferences { get; set; }

/// <summary>
/// Gets or sets optional sequences of characters that signal the LLM to stop generating text when encountered.
Expand All @@ -84,7 +84,7 @@ public sealed class CreateMessageRequestParams : RequestParams
/// </para>
/// </remarks>
[JsonPropertyName("stopSequences")]
public IReadOnlyList<string>? StopSequences { get; init; }
public IList<string>? StopSequences { get; set; }

/// <summary>
/// Gets or sets an optional system prompt the server wants to use for sampling.
Expand All @@ -93,11 +93,11 @@ public sealed class CreateMessageRequestParams : RequestParams
/// The client may modify or omit this prompt.
/// </remarks>
[JsonPropertyName("systemPrompt")]
public string? SystemPrompt { get; init; }
public string? SystemPrompt { get; set; }

/// <summary>
/// Gets or sets the temperature to use for sampling, as requested by the server.
/// </summary>
[JsonPropertyName("temperature")]
public float? Temperature { get; init; }
public float? Temperature { get; set; }
}
8 changes: 4 additions & 4 deletions src/ModelContextProtocol.Core/Protocol/CreateMessageResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public sealed class CreateMessageResult : Result
/// Gets or sets the content of the message.
/// </summary>
[JsonPropertyName("content")]
public required ContentBlock Content { get; init; }
public required ContentBlock Content { get; set; }

/// <summary>
/// Gets or sets the name of the model that generated the message.
Expand All @@ -29,7 +29,7 @@ public sealed class CreateMessageResult : Result
/// </para>
/// </remarks>
[JsonPropertyName("model")]
public required string Model { get; init; }
public required string Model { get; set; }

/// <summary>
/// Gets or sets the reason why message generation (sampling) stopped, if known.
Expand All @@ -43,11 +43,11 @@ public sealed class CreateMessageResult : Result
/// </list>
/// </remarks>
[JsonPropertyName("stopReason")]
public string? StopReason { get; init; }
public string? StopReason { get; set; }

/// <summary>
/// Gets or sets the role of the user who generated the message.
/// </summary>
[JsonPropertyName("role")]
public required Role Role { get; init; }
public required Role Role { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public sealed class ElicitRequestParams
/// Gets or sets the message to present to the user.
/// </summary>
[JsonPropertyName("message")]
public string Message { get; set; } = string.Empty;
public required string Message { get; set; }

/// <summary>
/// Gets or sets the requested schema.
Expand Down
19 changes: 19 additions & 0 deletions src/ModelContextProtocol.Core/Protocol/ElicitResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,25 @@ public sealed class ElicitResult<T> : Result
/// <summary>
/// Gets or sets the user action in response to the elicitation.
/// </summary>
/// <value>
/// Defaults to "cancel" if not explicitly set.
/// </value>
/// <remarks>
/// <list type="bullet">
/// <item>
/// <term>"accept"</term>
/// <description>User submitted the form/confirmed the action</description>
/// </item>
/// <item>
/// <term>"decline"</term>
/// <description>User explicitly declined the action</description>
/// </item>
/// <item>
/// <term>"cancel"</term>
/// <description>User dismissed without making an explicit choice (default)</description>
/// </item>
/// </list>
/// </remarks>
public string Action { get; set; } = "cancel";

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public sealed class GetPromptRequestParams : RequestParams
/// Gets or sets the name of the prompt.
/// </summary>
[JsonPropertyName("name")]
public required string Name { get; init; }
public required string Name { get; set; }

/// <summary>
/// Gets or sets arguments to use for templating the prompt when retrieving it from the server.
Expand All @@ -27,5 +27,5 @@ public sealed class GetPromptRequestParams : RequestParams
/// choose to use these arguments in any way it deems appropriate to generate the prompt.
/// </remarks>
[JsonPropertyName("arguments")]
public IReadOnlyDictionary<string, JsonElement>? Arguments { get; init; }
public IDictionary<string, JsonElement>? Arguments { get; set; }
}
Loading
Loading